my commit
This commit is contained in:
commit
43f2addd94
2237
Cargo.lock
generated
Normal file
2237
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
27
Cargo.toml
Normal file
27
Cargo.toml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
[package]
|
||||||
|
name = "futriix"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024" # По состоянию на октябрь 2025 года наиболее стабильная и последняя редакция языка Rust (Rust Edition)
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rlua = "0.20.1" # Библиотека для интеграции Rust с языком Lua. Позволяет выполнять Lua-скрипты из Rust, передавать данные между ними и использовать Lua в качестве скриптового движка (например, для плагинов или конфигураций).
|
||||||
|
ansi_term = "0.12.1" # Предоставляет инструменты для работы с ANSI-кодами в терминале, такими как цветной вывод текста, жирный шрифт и другие стили. Полезна для создания красивого CLI-интерфейса.
|
||||||
|
tokio = { version = "1.0", features = ["full"] } # Асинхронное рантайм для Rust, для написания неблокирующего кода (например, для сетевых запросов, многозадачности). С фичей "full" включает всё для полного асинхронного управления.
|
||||||
|
warp = "0.3" # Лёгкий веб-фреймворк для Rust, построенный на tokio. Упрощает создание HTTP-серверов, API и веб-приложений с поддержкой маршрутизации, middleware и т.д.
|
||||||
|
serde = { version = "1.0", features = ["derive"] } #Основная библиотека для сериализации и десериализации данных в Rust (преобразование структур в строки/байты и обратно). Фича "derive" генерирует код автоматически через макросы.
|
||||||
|
serde_json = "1.0" # Расширение для serde, для работы с JSON-форматом. Позволяет сериализовать/десериализовать данные в JSON.
|
||||||
|
toml = "0.5" # Библиотека для чтения и записи файлов в формате TOML (Tom's Obvious, Minimal Language).
|
||||||
|
rmp-serde = "1.1" # MessagePack
|
||||||
|
csv = "1.4.0" # Добавлена библиотека для CSV
|
||||||
|
chrono = { version = "0.4", features = ["serde"] } # Работа с датами, временем и периодами (например, парсинг ISO 8601). С фичей "serde" поддерживает сериализацию временных объектов.
|
||||||
|
log = "0.4" # Фрейморк для логирования в Rust, предоставляет трейты для записи сообщений (ошибки, предупреждения и т.д.) без привязки к конкретной реализации.
|
||||||
|
env_logger = "0.10" # Конкретная реализация логирования на основе переменных окружения. Работает с crate log, позволяет настраивать уровень логирования через переменные вроде RUST_LOG.
|
||||||
|
parking_lot = "0.12" #Более производительная альтернатива стандартным мьютексам и спинлокам в Rust, полезна для многопоточного кода с низкими оверхедами.
|
||||||
|
crossbeam = "0.8" #Инструменты для параллелизма, включая каналы для межпоточного общения, атомарные операции и структуры данных для конкурентного доступа.
|
||||||
|
rustyline = "12.0" # Библиотека для создания интерактивных REPL (read-eval-print loops) или терминалов с поддержкой истории команд, автодополнения и редактирования строк.
|
||||||
|
flate2 = "1.0" # Библиотека для сжатия и декомпрессии данных с помощью алгоритмов вроде gzip/deflate. В проекте нужнадля сжатия backup
|
||||||
|
md-5 = "0.10.6" # Библиотека для поддержки MD5
|
||||||
|
|
||||||
|
[dev-dependencies] # Блок для запуска тестов
|
||||||
|
tokio = { version = "1.0", features = ["full", "rt-multi-thread"] }
|
||||||
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
40
config/config.toml
Executable file
40
config/config.toml
Executable file
@ -0,0 +1,40 @@
|
|||||||
|
# Конфигурация сервера приложений и СУБД
|
||||||
|
|
||||||
|
http_port = 9080
|
||||||
|
db_path = "./data"
|
||||||
|
log_level = "info"
|
||||||
|
|
||||||
|
# Настройки кластера
|
||||||
|
cluster_enabled = false
|
||||||
|
node_id = "node_1"
|
||||||
|
cluster_nodes = [
|
||||||
|
"http://node1.example.com:9040",
|
||||||
|
"http://node2.example.com:9040",
|
||||||
|
"http://node3.example.com:9040"
|
||||||
|
]
|
||||||
|
|
||||||
|
# Настройки ACL (Access Control List)
|
||||||
|
acl_enabled = false
|
||||||
|
admin_users = [
|
||||||
|
"admin",
|
||||||
|
"root"
|
||||||
|
]
|
||||||
|
|
||||||
|
# Настройки репликации
|
||||||
|
master_master_replication = false
|
||||||
|
replication_nodes = [
|
||||||
|
"http://replica1.example.com:8080",
|
||||||
|
"http://replica2.example.com:8080"
|
||||||
|
]
|
||||||
|
|
||||||
|
# Настройки HTTPS
|
||||||
|
https_enabled = false
|
||||||
|
https_port = 8443
|
||||||
|
cert_file = "./cert.pem"
|
||||||
|
key_file = "./key.pem"
|
||||||
|
|
||||||
|
# Настройки HTTP2
|
||||||
|
http2_enabled = false
|
||||||
|
|
||||||
|
# Настройки хранимых процедур
|
||||||
|
stored_procedures_path = "./procedures"
|
||||||
18
lua-scripts/init.lua
Normal file
18
lua-scripts/init.lua
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
-- Автоматически загружаемый скрипт при запуске сервера
|
||||||
|
|
||||||
|
print("futriiX Lua environment initialized")
|
||||||
|
|
||||||
|
-- Вспомогательные функции
|
||||||
|
function help()
|
||||||
|
print("Available commands:")
|
||||||
|
print(" inbox.start - Enter database mode")
|
||||||
|
print(" db_create(collection, key, json) - Create document")
|
||||||
|
print(" exit - Exit application")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Глобальная функция для логирования
|
||||||
|
function log_info(message)
|
||||||
|
print("[INFO] " .. message)
|
||||||
|
end
|
||||||
|
|
||||||
|
log_info("Lua scripts loaded successfully")
|
||||||
50
lua-scripts/utils.lua
Normal file
50
lua-scripts/utils.lua
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
--utils.lua- является пользовательской библиотекой вспомогательных функций Lua для работы с СУБД futriix.
|
||||||
|
|
||||||
|
-- Выполняет роль:
|
||||||
|
-- Загрузчика в других Lua скриптах проекта
|
||||||
|
-- Загрузчика при инициализации Lua интерпретатора
|
||||||
|
-- Основного инструмента в хранимых процедурах проекта
|
||||||
|
|
||||||
|
function json_encode(tbl)
|
||||||
|
return require("serde").encode(tbl)
|
||||||
|
end
|
||||||
|
|
||||||
|
function json_decode(str)
|
||||||
|
return require("serde").decode(str)
|
||||||
|
end
|
||||||
|
|
||||||
|
function table_length(tbl)
|
||||||
|
local count = 0
|
||||||
|
for _ in pairs(tbl) do
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
return count
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Функция для MD5 хеширования
|
||||||
|
function md5_hash(text)
|
||||||
|
return md5(text)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Функция для создания пользователя с хешированным паролем
|
||||||
|
function create_user_with_password(space, username, password, user_data)
|
||||||
|
user_data = user_data or {}
|
||||||
|
user_data.password = md5_hash(password)
|
||||||
|
return insert(space, username, json_encode(user_data))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Функция для проверки пароля пользователя
|
||||||
|
function verify_user_password(space, username, password)
|
||||||
|
local user_json = get(space, username)
|
||||||
|
if user_json == "null" then
|
||||||
|
return false, "User not found"
|
||||||
|
end
|
||||||
|
|
||||||
|
local user_data = json_decode(user_json)
|
||||||
|
if user_data.password == md5_hash(password) then
|
||||||
|
return true, "Password correct"
|
||||||
|
else
|
||||||
|
return false, "Password incorrect"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
129
src/cli.rs
Normal file
129
src/cli.rs
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
use std::error::Error;
|
||||||
|
use ansi_term::Colour;
|
||||||
|
|
||||||
|
/// Парсер команд для интерпретатора
|
||||||
|
pub struct CommandParser;
|
||||||
|
|
||||||
|
impl CommandParser {
|
||||||
|
pub fn parse_command(line: &str) -> Result<Command, Box<dyn Error>> {
|
||||||
|
let parts: Vec<&str> = line.trim().split_whitespace().collect();
|
||||||
|
if parts.is_empty() {
|
||||||
|
return Ok(Command::Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
match parts[0].to_lowercase().as_str() {
|
||||||
|
"exit" | "quit" => Ok(Command::Exit),
|
||||||
|
"help" => Ok(Command::Help),
|
||||||
|
"backup" => {
|
||||||
|
if parts.len() >= 2 {
|
||||||
|
Ok(Command::Backup {
|
||||||
|
path: parts[1].to_string(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err("Usage: backup <path>".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"restore" => {
|
||||||
|
if parts.len() >= 2 {
|
||||||
|
Ok(Command::Restore {
|
||||||
|
path: parts[1].to_string(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err("Usage: restore <path>".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"create" => {
|
||||||
|
if parts.len() >= 3 && parts[1].to_lowercase() == "procedure" {
|
||||||
|
if parts.len() >= 4 {
|
||||||
|
Ok(Command::CreateProcedure {
|
||||||
|
name: parts[2].to_string(),
|
||||||
|
code: parts[3..].join(" "),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err("Usage: create procedure <name> <code>".into())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(Command::LuaCode(line.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"call" => {
|
||||||
|
if parts.len() >= 3 && parts[1].to_lowercase() == "procedure" {
|
||||||
|
Ok(Command::CallProcedure {
|
||||||
|
name: parts[2].to_string(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(Command::LuaCode(line.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"drop" => {
|
||||||
|
if parts.len() >= 3 && parts[1].to_lowercase() == "procedure" {
|
||||||
|
Ok(Command::DropProcedure {
|
||||||
|
name: parts[2].to_string(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(Command::LuaCode(line.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Ok(Command::LuaCode(line.to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print_help() {
|
||||||
|
println!("\n{}", Colour::Cyan.paint("Available commands:"));
|
||||||
|
println!("{}", Colour::Yellow.paint(" General:"));
|
||||||
|
println!(" exit, quit - Exit the interpreter");
|
||||||
|
println!(" help - Show this help message");
|
||||||
|
println!();
|
||||||
|
println!("{}", Colour::Yellow.paint(" Database operations:"));
|
||||||
|
println!(" backup <path> - Create database backup");
|
||||||
|
println!(" restore <path> - Restore database from backup");
|
||||||
|
println!();
|
||||||
|
println!("{}", Colour::Yellow.paint(" Stored procedures:"));
|
||||||
|
println!(" create procedure <name> <code> - Create stored procedure");
|
||||||
|
println!(" call procedure <name> - Execute stored procedure");
|
||||||
|
println!(" drop procedure <name> - Delete stored procedure");
|
||||||
|
println!();
|
||||||
|
println!("{}", Colour::Yellow.paint(" Lua operations:"));
|
||||||
|
println!(" Any valid Lua code - Execute Lua code with DB API");
|
||||||
|
println!();
|
||||||
|
println!("{}", Colour::Cyan.paint("Lua DB API functions:"));
|
||||||
|
println!(" create_space(name) - Create space");
|
||||||
|
println!(" delete_space(name) - Delete space");
|
||||||
|
println!(" insert(space, key, value) - Insert document");
|
||||||
|
println!(" get(space, key) - Get document");
|
||||||
|
println!(" update(space, key, value) - Update document");
|
||||||
|
println!(" delete(space, key) - Delete document");
|
||||||
|
println!(" create_procedure(name, code) - Create stored procedure");
|
||||||
|
println!(" drop_procedure(name) - Drop stored procedure");
|
||||||
|
println!(" call_procedure(name) - Call stored procedure");
|
||||||
|
println!(" create_backup(path) - Create backup");
|
||||||
|
println!(" restore_backup(path) - Restore backup");
|
||||||
|
println!(" enable_replication() - Enable replication");
|
||||||
|
println!(" disable_replication() - Disable replication");
|
||||||
|
println!(" sync_replication() - Sync replication");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Команды интерпретатора
|
||||||
|
pub enum Command {
|
||||||
|
Exit,
|
||||||
|
Help,
|
||||||
|
Empty,
|
||||||
|
LuaCode(String),
|
||||||
|
Backup {
|
||||||
|
path: String,
|
||||||
|
},
|
||||||
|
Restore {
|
||||||
|
path: String,
|
||||||
|
},
|
||||||
|
CreateProcedure {
|
||||||
|
name: String,
|
||||||
|
code: String,
|
||||||
|
},
|
||||||
|
CallProcedure {
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
|
DropProcedure {
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
290
src/cli/parser.rs
Normal file
290
src/cli/parser.rs
Normal file
@ -0,0 +1,290 @@
|
|||||||
|
use ansi_term::Colour;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
/// Парсер команд для CLI интерфейса
|
||||||
|
pub struct CommandParser {
|
||||||
|
history: VecDeque<String>,
|
||||||
|
max_history: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommandParser {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
history: VecDeque::new(),
|
||||||
|
max_history: 100,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(&self, input: &str) -> Result<ParsedCommand, String> {
|
||||||
|
let parts: Vec<&str> = input.trim().split_whitespace().collect();
|
||||||
|
|
||||||
|
if parts.is_empty() {
|
||||||
|
return Err("Empty command".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
match parts[0] {
|
||||||
|
"create" => self.parse_create(&parts),
|
||||||
|
"read" => self.parse_read(&parts),
|
||||||
|
"update" => self.parse_update(&parts),
|
||||||
|
"delete" => self.parse_delete(&parts),
|
||||||
|
"create.space" => self.parse_create_space(&parts),
|
||||||
|
"delete.space" => self.parse_delete_space(&parts),
|
||||||
|
"create.tuple" => self.parse_create_tuple(&parts),
|
||||||
|
"read.tuple" => self.parse_read_tuple(&parts),
|
||||||
|
"delete.tuple" => self.parse_delete_tuple(&parts),
|
||||||
|
"create.primary.index" => self.parse_create_primary_index(&parts),
|
||||||
|
"create.secondary.index" => self.parse_create_secondary_index(&parts),
|
||||||
|
"drop.primary.index" => self.parse_drop_primary_index(&parts),
|
||||||
|
"drop.secondary.index" => self.parse_drop_secondary_index(&parts),
|
||||||
|
"search.all.index" => self.parse_search_all_index(&parts),
|
||||||
|
"search.index" => self.parse_search_index(&parts),
|
||||||
|
"begin.transaction" => self.parse_begin_transaction(&parts),
|
||||||
|
"commit.transaction" => self.parse_commit_transaction(&parts),
|
||||||
|
"rollback.transaction" => self.parse_rollback_transaction(&parts),
|
||||||
|
"mode.csv" => Ok(ParsedCommand::ModeCsv),
|
||||||
|
"export" => self.parse_export(&parts),
|
||||||
|
"import" => self.parse_import(&parts),
|
||||||
|
"exit" => Ok(ParsedCommand::Exit),
|
||||||
|
"inbox.start" => Ok(ParsedCommand::InboxStart),
|
||||||
|
_ => Err(format!("Unknown command: {}", parts[0])),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_create(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
|
||||||
|
if parts.len() < 4 {
|
||||||
|
return Err("Usage: create <collection> <key> <json_value>".to_string());
|
||||||
|
}
|
||||||
|
Ok(ParsedCommand::Create {
|
||||||
|
collection: parts[1].to_string(),
|
||||||
|
key: parts[2].to_string(),
|
||||||
|
value: serde_json::from_str(parts[3])
|
||||||
|
.map_err(|e| format!("Invalid JSON: {}", e))?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_read(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
|
||||||
|
if parts.len() < 3 {
|
||||||
|
return Err("Usage: read <collection> <key>".to_string());
|
||||||
|
}
|
||||||
|
Ok(ParsedCommand::Read {
|
||||||
|
collection: parts[1].to_string(),
|
||||||
|
key: parts[2].to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_update(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
|
||||||
|
if parts.len() < 4 {
|
||||||
|
return Err("Usage: update <collection> <key> <json_value>".to_string());
|
||||||
|
}
|
||||||
|
Ok(ParsedCommand::Update {
|
||||||
|
collection: parts[1].to_string(),
|
||||||
|
key: parts[2].to_string(),
|
||||||
|
value: serde_json::from_str(parts[3])
|
||||||
|
.map_err(|e| format!("Invalid JSON: {}", e))?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_delete(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
|
||||||
|
if parts.len() < 3 {
|
||||||
|
return Err("Usage: delete <collection> <key>".to_string());
|
||||||
|
}
|
||||||
|
Ok(ParsedCommand::Delete {
|
||||||
|
collection: parts[1].to_string(),
|
||||||
|
key: parts[2].to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_create_space(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
|
||||||
|
if parts.len() < 2 {
|
||||||
|
return Err("Usage: create.space <space_name>".to_string());
|
||||||
|
}
|
||||||
|
Ok(ParsedCommand::CreateSpace {
|
||||||
|
name: parts[1].to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_delete_space(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
|
||||||
|
if parts.len() < 2 {
|
||||||
|
return Err("Usage: delete.space <space_name>".to_string());
|
||||||
|
}
|
||||||
|
Ok(ParsedCommand::DeleteSpace {
|
||||||
|
name: parts[1].to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_create_tuple(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
|
||||||
|
if parts.len() < 4 {
|
||||||
|
return Err("Usage: create.tuple <space> <tuple_id> <json_value>".to_string());
|
||||||
|
}
|
||||||
|
Ok(ParsedCommand::CreateTuple {
|
||||||
|
space: parts[1].to_string(),
|
||||||
|
tuple_id: parts[2].to_string(),
|
||||||
|
value: serde_json::from_str(parts[3])
|
||||||
|
.map_err(|e| format!("Invalid JSON: {}", e))?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_read_tuple(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
|
||||||
|
if parts.len() < 3 {
|
||||||
|
return Err("Usage: read.tuple <space> <tuple_id>".to_string());
|
||||||
|
}
|
||||||
|
Ok(ParsedCommand::ReadTuple {
|
||||||
|
space: parts[1].to_string(),
|
||||||
|
tuple_id: parts[2].to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_delete_tuple(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
|
||||||
|
if parts.len() < 3 {
|
||||||
|
return Err("Usage: delete.tuple <space> <tuple_id>".to_string());
|
||||||
|
}
|
||||||
|
Ok(ParsedCommand::DeleteTuple {
|
||||||
|
space: parts[1].to_string(),
|
||||||
|
tuple_id: parts[2].to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_create_primary_index(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
|
||||||
|
if parts.len() < 2 {
|
||||||
|
return Err("Usage: create.primary.index <collection>".to_string());
|
||||||
|
}
|
||||||
|
Ok(ParsedCommand::CreatePrimaryIndex {
|
||||||
|
collection: parts[1].to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_create_secondary_index(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
|
||||||
|
if parts.len() < 4 {
|
||||||
|
return Err("Usage: create.secondary.index <collection> <field_name> <index_type>".to_string());
|
||||||
|
}
|
||||||
|
Ok(ParsedCommand::CreateSecondaryIndex {
|
||||||
|
collection: parts[1].to_string(),
|
||||||
|
field: parts[2].to_string(),
|
||||||
|
index_type: parts[3].to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_drop_primary_index(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
|
||||||
|
if parts.len() < 2 {
|
||||||
|
return Err("Usage: drop.primary.index <collection>".to_string());
|
||||||
|
}
|
||||||
|
Ok(ParsedCommand::DropPrimaryIndex {
|
||||||
|
collection: parts[1].to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_drop_secondary_index(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
|
||||||
|
if parts.len() < 3 {
|
||||||
|
return Err("Usage: drop.secondary.index <collection> <field_name>".to_string());
|
||||||
|
}
|
||||||
|
Ok(ParsedCommand::DropSecondaryIndex {
|
||||||
|
collection: parts[1].to_string(),
|
||||||
|
field: parts[2].to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_search_all_index(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
|
||||||
|
if parts.len() < 2 {
|
||||||
|
return Err("Usage: search.all.index <collection>".to_string());
|
||||||
|
}
|
||||||
|
Ok(ParsedCommand::SearchAllIndex {
|
||||||
|
collection: parts[1].to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_search_index(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
|
||||||
|
if parts.len() < 4 {
|
||||||
|
return Err("Usage: search.index <collection> <field_name> <value>".to_string());
|
||||||
|
}
|
||||||
|
Ok(ParsedCommand::SearchIndex {
|
||||||
|
collection: parts[1].to_string(),
|
||||||
|
field: parts[2].to_string(),
|
||||||
|
value: parts[3].to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_begin_transaction(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
|
||||||
|
let tx_id = if parts.len() > 1 {
|
||||||
|
parts[1].to_string()
|
||||||
|
} else {
|
||||||
|
format!("tx_{}", chrono::Utc::now().timestamp_millis())
|
||||||
|
};
|
||||||
|
Ok(ParsedCommand::BeginTransaction {
|
||||||
|
transaction_id: tx_id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_commit_transaction(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
|
||||||
|
if parts.len() < 2 {
|
||||||
|
return Err("Usage: commit.transaction <transaction_id>".to_string());
|
||||||
|
}
|
||||||
|
Ok(ParsedCommand::CommitTransaction {
|
||||||
|
transaction_id: parts[1].to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_rollback_transaction(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
|
||||||
|
if parts.len() < 2 {
|
||||||
|
return Err("Usage: rollback.transaction <transaction_id>".to_string());
|
||||||
|
}
|
||||||
|
Ok(ParsedCommand::RollbackTransaction {
|
||||||
|
transaction_id: parts[1].to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_export(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
|
||||||
|
if parts.len() < 2 {
|
||||||
|
return Err("Usage: export <file_path>".to_string());
|
||||||
|
}
|
||||||
|
Ok(ParsedCommand::Export {
|
||||||
|
file_path: parts[1].to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_import(&self, parts: &[&str]) -> Result<ParsedCommand, String> {
|
||||||
|
if parts.len() < 2 {
|
||||||
|
return Err("Usage: import <file_path>".to_string());
|
||||||
|
}
|
||||||
|
Ok(ParsedCommand::Import {
|
||||||
|
file_path: parts[1].to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_to_history(&mut self, command: String) {
|
||||||
|
if self.history.len() >= self.max_history {
|
||||||
|
self.history.pop_front();
|
||||||
|
}
|
||||||
|
self.history.push_back(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_history(&self) -> &VecDeque<String> {
|
||||||
|
&self.history
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ParsedCommand {
|
||||||
|
Create { collection: String, key: String, value: serde_json::Value },
|
||||||
|
Read { collection: String, key: String },
|
||||||
|
Update { collection: String, key: String, value: serde_json::Value },
|
||||||
|
Delete { collection: String, key: String },
|
||||||
|
CreateSpace { name: String },
|
||||||
|
DeleteSpace { name: String },
|
||||||
|
CreateTuple { space: String, tuple_id: String, value: serde_json::Value },
|
||||||
|
ReadTuple { space: String, tuple_id: String },
|
||||||
|
DeleteTuple { space: String, tuple_id: String },
|
||||||
|
CreatePrimaryIndex { collection: String },
|
||||||
|
CreateSecondaryIndex { collection: String, field: String, index_type: String },
|
||||||
|
DropPrimaryIndex { collection: String },
|
||||||
|
DropSecondaryIndex { collection: String, field: String },
|
||||||
|
SearchAllIndex { collection: String },
|
||||||
|
SearchIndex { collection: String, field: String, value: String },
|
||||||
|
BeginTransaction { transaction_id: String },
|
||||||
|
CommitTransaction { transaction_id: String },
|
||||||
|
RollbackTransaction { transaction_id: String },
|
||||||
|
ModeCsv,
|
||||||
|
Export { file_path: String },
|
||||||
|
Import { file_path: String },
|
||||||
|
Exit,
|
||||||
|
InboxStart,
|
||||||
|
}
|
||||||
56
src/config.rs
Normal file
56
src/config.rs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Clone)]
|
||||||
|
pub struct Config {
|
||||||
|
pub http_port: u16,
|
||||||
|
pub db_path: String,
|
||||||
|
pub log_level: String,
|
||||||
|
|
||||||
|
// Параметры кластера
|
||||||
|
pub cluster_enabled: bool,
|
||||||
|
pub node_id: String,
|
||||||
|
pub cluster_nodes: Vec<String>,
|
||||||
|
|
||||||
|
// Параметры ACL
|
||||||
|
pub acl_enabled: bool,
|
||||||
|
pub admin_users: Vec<String>,
|
||||||
|
|
||||||
|
// Параметры репликации
|
||||||
|
pub master_master_replication: bool,
|
||||||
|
pub replication_nodes: Vec<String>,
|
||||||
|
|
||||||
|
// Параметры HTTPS
|
||||||
|
pub https_enabled: bool,
|
||||||
|
pub https_port: u16,
|
||||||
|
pub cert_file: String,
|
||||||
|
pub key_file: String,
|
||||||
|
|
||||||
|
// Параметры HTTP2
|
||||||
|
pub http2_enabled: bool,
|
||||||
|
|
||||||
|
// Настройки хранимых процедур
|
||||||
|
pub stored_procedures_path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Config {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
http_port: 8080,
|
||||||
|
db_path: "./data".to_string(),
|
||||||
|
log_level: "info".to_string(),
|
||||||
|
cluster_enabled: false,
|
||||||
|
node_id: "node_1".to_string(),
|
||||||
|
cluster_nodes: vec![],
|
||||||
|
acl_enabled: false,
|
||||||
|
admin_users: vec![],
|
||||||
|
master_master_replication: false,
|
||||||
|
replication_nodes: vec![],
|
||||||
|
https_enabled: false,
|
||||||
|
https_port: 8443,
|
||||||
|
cert_file: "./cert.pem".to_string(),
|
||||||
|
key_file: "./key.pem".to_string(),
|
||||||
|
http2_enabled: false,
|
||||||
|
stored_procedures_path: "./procedures".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
453
src/http.rs
Normal file
453
src/http.rs
Normal file
@ -0,0 +1,453 @@
|
|||||||
|
//[file name]: http.rs
|
||||||
|
|
||||||
|
use warp::Filter;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::convert::Infallible;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use crate::db::FutriixDB;
|
||||||
|
use crate::config::Config;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::path::Path;
|
||||||
|
use tokio::fs;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
/// Запуск HTTP сервера
|
||||||
|
pub async fn start_http_server(db: Arc<FutriixDB>, config: Config) {
|
||||||
|
let db_filter = warp::any().map(move || db.clone());
|
||||||
|
|
||||||
|
// Новый маршрут для быстрых команд через URL
|
||||||
|
let quick_commands = warp::path!("quick" / String / String / String / String)
|
||||||
|
.and(warp::post())
|
||||||
|
.and(db_filter.clone())
|
||||||
|
.and_then(quick_command_handler);
|
||||||
|
|
||||||
|
let quick_commands_get = warp::path!("quick" / String / String / String)
|
||||||
|
.and(warp::get())
|
||||||
|
.and(db_filter.clone())
|
||||||
|
.and_then(quick_command_get_handler);
|
||||||
|
|
||||||
|
// Маршруты для работы с документами
|
||||||
|
let create_space = warp::path!("space" / String)
|
||||||
|
.and(warp::post())
|
||||||
|
.and(db_filter.clone())
|
||||||
|
.and_then(create_space_handler);
|
||||||
|
|
||||||
|
let delete_space = warp::path!("space" / String)
|
||||||
|
.and(warp::delete())
|
||||||
|
.and(db_filter.clone())
|
||||||
|
.and_then(delete_space_handler);
|
||||||
|
|
||||||
|
let insert_document = warp::path!("document" / String / String)
|
||||||
|
.and(warp::post())
|
||||||
|
.and(warp::body::json())
|
||||||
|
.and(db_filter.clone())
|
||||||
|
.and_then(insert_document_handler);
|
||||||
|
|
||||||
|
let get_document = warp::path!("document" / String / String)
|
||||||
|
.and(warp::get())
|
||||||
|
.and(db_filter.clone())
|
||||||
|
.and_then(get_document_handler);
|
||||||
|
|
||||||
|
let update_document = warp::path!("document" / String / String)
|
||||||
|
.and(warp::put())
|
||||||
|
.and(warp::body::json())
|
||||||
|
.and(db_filter.clone())
|
||||||
|
.and_then(update_document_handler);
|
||||||
|
|
||||||
|
let delete_document = warp::path!("document" / String / String)
|
||||||
|
.and(warp::delete())
|
||||||
|
.and(db_filter.clone())
|
||||||
|
.and_then(delete_document_handler);
|
||||||
|
|
||||||
|
// Маршруты для работы с хранимыми процедурами
|
||||||
|
let create_procedure = warp::path!("procedure" / String)
|
||||||
|
.and(warp::post())
|
||||||
|
.and(warp::body::json())
|
||||||
|
.and(db_filter.clone())
|
||||||
|
.and_then(create_procedure_handler);
|
||||||
|
|
||||||
|
let call_procedure = warp::path!("procedure" / String)
|
||||||
|
.and(warp::get())
|
||||||
|
.and(db_filter.clone())
|
||||||
|
.and_then(call_procedure_handler);
|
||||||
|
|
||||||
|
let drop_procedure = warp::path!("procedure" / String)
|
||||||
|
.and(warp::delete())
|
||||||
|
.and(db_filter.clone())
|
||||||
|
.and_then(drop_procedure_handler);
|
||||||
|
|
||||||
|
let list_procedures = warp::path!("procedures")
|
||||||
|
.and(warp::get())
|
||||||
|
.and(db_filter.clone())
|
||||||
|
.and_then(list_procedures_handler);
|
||||||
|
|
||||||
|
// Маршруты для работы с бэкапами
|
||||||
|
let create_backup = warp::path!("backup")
|
||||||
|
.and(warp::post())
|
||||||
|
.and(warp::body::json())
|
||||||
|
.and(db_filter.clone())
|
||||||
|
.and_then(create_backup_handler);
|
||||||
|
|
||||||
|
let restore_backup = warp::path!("backup" / "restore")
|
||||||
|
.and(warp::post())
|
||||||
|
.and(warp::body::json())
|
||||||
|
.and(db_filter.clone())
|
||||||
|
.and_then(restore_backup_handler);
|
||||||
|
|
||||||
|
// Маршруты для работы с репликацией
|
||||||
|
let enable_replication = warp::path!("replication" / "enable")
|
||||||
|
.and(warp::post())
|
||||||
|
.and(db_filter.clone())
|
||||||
|
.and_then(enable_replication_handler);
|
||||||
|
|
||||||
|
let disable_replication = warp::path!("replication" / "disable")
|
||||||
|
.and(warp::post())
|
||||||
|
.and(db_filter.clone())
|
||||||
|
.and_then(disable_replication_handler);
|
||||||
|
|
||||||
|
// Маршруты для работы с MD5
|
||||||
|
let md5_hash = warp::path!("md5" / String)
|
||||||
|
.and(warp::get())
|
||||||
|
.and_then(md5_handler);
|
||||||
|
|
||||||
|
// Статические файлы
|
||||||
|
let static_files = warp::path("static")
|
||||||
|
.and(warp::fs::dir("./static"));
|
||||||
|
|
||||||
|
// Корневой маршрут для обслуживания HTML файлов
|
||||||
|
let root = warp::path::end()
|
||||||
|
.and(warp::get())
|
||||||
|
.and(warp::fs::file("./static/index.html"));
|
||||||
|
|
||||||
|
// Обслуживание статических файлов с правильными MIME типами
|
||||||
|
let static_assets = warp::path("assets")
|
||||||
|
.and(warp::fs::dir("./assets"));
|
||||||
|
|
||||||
|
// Комбинируем все маршруты
|
||||||
|
let routes = warp::any()
|
||||||
|
.and(
|
||||||
|
quick_commands
|
||||||
|
.or(quick_commands_get)
|
||||||
|
.or(create_space)
|
||||||
|
.or(delete_space)
|
||||||
|
.or(insert_document)
|
||||||
|
.or(get_document)
|
||||||
|
.or(update_document)
|
||||||
|
.or(delete_document)
|
||||||
|
.or(create_procedure)
|
||||||
|
.or(call_procedure)
|
||||||
|
.or(drop_procedure)
|
||||||
|
.or(list_procedures)
|
||||||
|
.or(create_backup)
|
||||||
|
.or(restore_backup)
|
||||||
|
.or(enable_replication)
|
||||||
|
.or(disable_replication)
|
||||||
|
.or(md5_hash)
|
||||||
|
.or(static_files)
|
||||||
|
.or(static_assets)
|
||||||
|
.or(root)
|
||||||
|
)
|
||||||
|
.with(warp::cors().allow_any_origin())
|
||||||
|
.with(warp::log("http"));
|
||||||
|
|
||||||
|
let port = config.http_port;
|
||||||
|
|
||||||
|
println!("HTTP server started on port {}", port);
|
||||||
|
|
||||||
|
if config.https_enabled {
|
||||||
|
// Запуск HTTPS сервера
|
||||||
|
let https_port = config.https_port;
|
||||||
|
println!("HTTPS server started on port {}", https_port);
|
||||||
|
|
||||||
|
// В реальной реализации здесь была бы настройка TLS
|
||||||
|
warp::serve(routes)
|
||||||
|
.run(([127, 0, 0, 1], port))
|
||||||
|
.await;
|
||||||
|
} else {
|
||||||
|
// Запуск HTTP сервера
|
||||||
|
warp::serve(routes)
|
||||||
|
.run(([127, 0, 0, 1], port))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработчик быстрых команд через URL
|
||||||
|
async fn quick_command_handler(
|
||||||
|
command: String,
|
||||||
|
space: String,
|
||||||
|
key: String,
|
||||||
|
value: String,
|
||||||
|
db: Arc<FutriixDB>,
|
||||||
|
) -> Result<impl warp::Reply, Infallible> {
|
||||||
|
match command.as_str() {
|
||||||
|
"create" => {
|
||||||
|
let json_value: Result<Value, _> = serde_json::from_str(&value);
|
||||||
|
match json_value {
|
||||||
|
Ok(value) => {
|
||||||
|
match db.insert(&space, &key, value) {
|
||||||
|
Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Document created"))),
|
||||||
|
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&format!("Invalid JSON: {}", e)))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"get" => {
|
||||||
|
match db.get(&space, &key) {
|
||||||
|
Ok(Some(value)) => Ok(warp::reply::json(&ApiResponse::data(value))),
|
||||||
|
Ok(None) => Ok(warp::reply::json(&ApiResponse::error("Document not found"))),
|
||||||
|
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"update" => {
|
||||||
|
let json_value: Result<Value, _> = serde_json::from_str(&value);
|
||||||
|
match json_value {
|
||||||
|
Ok(value) => {
|
||||||
|
match db.update(&space, &key, value) {
|
||||||
|
Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Document updated"))),
|
||||||
|
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&format!("Invalid JSON: {}", e)))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"delete" => {
|
||||||
|
match db.delete(&space, &key) {
|
||||||
|
Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Document deleted"))),
|
||||||
|
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"create_space" => {
|
||||||
|
match db.create_space(&space) {
|
||||||
|
Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Space created"))),
|
||||||
|
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"delete_space" => {
|
||||||
|
match db.delete_space(&space) {
|
||||||
|
Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Space deleted"))),
|
||||||
|
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Ok(warp::reply::json(&ApiResponse::error(&format!("Unknown command: {}", command)))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработчик быстрых GET команд через URL (для получения данных)
|
||||||
|
async fn quick_command_get_handler(
|
||||||
|
command: String,
|
||||||
|
space: String,
|
||||||
|
key: String,
|
||||||
|
db: Arc<FutriixDB>,
|
||||||
|
) -> Result<impl warp::Reply, Infallible> {
|
||||||
|
match command.as_str() {
|
||||||
|
"get" => {
|
||||||
|
match db.get(&space, &key) {
|
||||||
|
Ok(Some(value)) => Ok(warp::reply::json(&ApiResponse::data(value))),
|
||||||
|
Ok(None) => Ok(warp::reply::json(&ApiResponse::error("Document not found"))),
|
||||||
|
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Ok(warp::reply::json(&ApiResponse::error(&format!("Unknown command: {}", command)))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработчик MD5 хеширования
|
||||||
|
async fn md5_handler(text: String) -> Result<impl warp::Reply, Infallible> {
|
||||||
|
use md5::{Digest, Md5};
|
||||||
|
|
||||||
|
let mut hasher = Md5::new();
|
||||||
|
hasher.update(text.as_bytes());
|
||||||
|
let result = hasher.finalize();
|
||||||
|
let hash = format!("{:x}", result);
|
||||||
|
|
||||||
|
Ok(warp::reply::json(&ApiResponse::data(hash)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработчики для работы с пространствами
|
||||||
|
async fn create_space_handler(name: String, db: Arc<FutriixDB>) -> Result<impl warp::Reply, Infallible> {
|
||||||
|
match db.create_space(&name) {
|
||||||
|
Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Space created"))),
|
||||||
|
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete_space_handler(name: String, db: Arc<FutriixDB>) -> Result<impl warp::Reply, Infallible> {
|
||||||
|
match db.delete_space(&name) {
|
||||||
|
Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Space deleted"))),
|
||||||
|
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработчики для работы с документами
|
||||||
|
async fn insert_document_handler(
|
||||||
|
space: String,
|
||||||
|
key: String,
|
||||||
|
value: serde_json::Value,
|
||||||
|
db: Arc<FutriixDB>,
|
||||||
|
) -> Result<impl warp::Reply, Infallible> {
|
||||||
|
match db.insert(&space, &key, value) {
|
||||||
|
Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Document inserted"))),
|
||||||
|
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_document_handler(
|
||||||
|
space: String,
|
||||||
|
key: String,
|
||||||
|
db: Arc<FutriixDB>,
|
||||||
|
) -> Result<impl warp::Reply, Infallible> {
|
||||||
|
match db.get(&space, &key) {
|
||||||
|
Ok(Some(value)) => Ok(warp::reply::json(&ApiResponse::data(value))),
|
||||||
|
Ok(None) => Ok(warp::reply::json(&ApiResponse::error("Document not found"))),
|
||||||
|
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update_document_handler(
|
||||||
|
space: String,
|
||||||
|
key: String,
|
||||||
|
value: serde_json::Value,
|
||||||
|
db: Arc<FutriixDB>,
|
||||||
|
) -> Result<impl warp::Reply, Infallible> {
|
||||||
|
match db.update(&space, &key, value) {
|
||||||
|
Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Document updated"))),
|
||||||
|
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete_document_handler(
|
||||||
|
space: String,
|
||||||
|
key: String,
|
||||||
|
db: Arc<FutriixDB>,
|
||||||
|
) -> Result<impl warp::Reply, Infallible> {
|
||||||
|
match db.delete(&space, &key) {
|
||||||
|
Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Document deleted"))),
|
||||||
|
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработчики для работы с хранимыми процедурами
|
||||||
|
async fn create_procedure_handler(
|
||||||
|
name: String,
|
||||||
|
body: CreateProcedureRequest,
|
||||||
|
db: Arc<FutriixDB>,
|
||||||
|
) -> Result<impl warp::Reply, Infallible> {
|
||||||
|
match db.create_procedure(&name, &body.code) {
|
||||||
|
Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Procedure created"))),
|
||||||
|
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn call_procedure_handler(
|
||||||
|
name: String,
|
||||||
|
db: Arc<FutriixDB>,
|
||||||
|
) -> Result<impl warp::Reply, Infallible> {
|
||||||
|
match db.get_procedure(&name) {
|
||||||
|
Some(code) => {
|
||||||
|
// В реальной реализации здесь выполнялся бы код процедуры
|
||||||
|
Ok(warp::reply::json(&ApiResponse::data(code)))
|
||||||
|
}
|
||||||
|
None => Ok(warp::reply::json(&ApiResponse::error("Procedure not found"))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn drop_procedure_handler(
|
||||||
|
name: String,
|
||||||
|
db: Arc<FutriixDB>,
|
||||||
|
) -> Result<impl warp::Reply, Infallible> {
|
||||||
|
match db.drop_procedure(&name) {
|
||||||
|
Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Procedure dropped"))),
|
||||||
|
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn list_procedures_handler(
|
||||||
|
db: Arc<FutriixDB>,
|
||||||
|
) -> Result<impl warp::Reply, Infallible> {
|
||||||
|
let procedures = db.list_procedures();
|
||||||
|
Ok(warp::reply::json(&ApiResponse::data(procedures)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработчики для работы с бэкапами
|
||||||
|
async fn create_backup_handler(
|
||||||
|
body: BackupRequest,
|
||||||
|
db: Arc<FutriixDB>,
|
||||||
|
) -> Result<impl warp::Reply, Infallible> {
|
||||||
|
match db.create_backup(&body.path) {
|
||||||
|
Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Backup created"))),
|
||||||
|
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn restore_backup_handler(
|
||||||
|
body: BackupRequest,
|
||||||
|
db: Arc<FutriixDB>,
|
||||||
|
) -> Result<impl warp::Reply, Infallible> {
|
||||||
|
match db.restore_backup(&body.path) {
|
||||||
|
Ok(()) => Ok(warp::reply::json(&ApiResponse::success("Backup restored"))),
|
||||||
|
Err(e) => Ok(warp::reply::json(&ApiResponse::error(&e.to_string()))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработчики для работы с репликацией
|
||||||
|
async fn enable_replication_handler(
|
||||||
|
db: Arc<FutriixDB>,
|
||||||
|
) -> Result<impl warp::Reply, Infallible> {
|
||||||
|
db.enable_replication();
|
||||||
|
Ok(warp::reply::json(&ApiResponse::success("Replication enabled")))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn disable_replication_handler(
|
||||||
|
db: Arc<FutriixDB>,
|
||||||
|
) -> Result<impl warp::Reply, Infallible> {
|
||||||
|
db.disable_replication();
|
||||||
|
Ok(warp::reply::json(&ApiResponse::success("Replication disabled")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Структуры для запросов и ответов API
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct CreateProcedureRequest {
|
||||||
|
code: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct BackupRequest {
|
||||||
|
path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct ApiResponse<T = ()> {
|
||||||
|
success: bool,
|
||||||
|
message: Option<String>,
|
||||||
|
data: Option<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ApiResponse<()> {
|
||||||
|
fn success(message: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
success: true,
|
||||||
|
message: Some(message.to_string()),
|
||||||
|
data: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error(message: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
success: false,
|
||||||
|
message: Some(message.to_string()),
|
||||||
|
data: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> ApiResponse<T> {
|
||||||
|
fn data(data: T) -> Self {
|
||||||
|
Self {
|
||||||
|
success: true,
|
||||||
|
message: None,
|
||||||
|
data: Some(data),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
23
src/lib.rs
Normal file
23
src/lib.rs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
//lib.rs-это корневой файл библиотеки (library crate) в проекте Rust.
|
||||||
|
// Объявляет все основные модули (config, db, http, cli, lua)
|
||||||
|
//Re-export'ит ключевые структуры и функции для внешнего использования
|
||||||
|
// Служит точкой входа для других crates, которые хотят использовать futriix как библиотеку
|
||||||
|
|
||||||
|
pub mod config;
|
||||||
|
pub mod db;
|
||||||
|
pub mod http;
|
||||||
|
pub mod cli;
|
||||||
|
pub mod lua;
|
||||||
|
|
||||||
|
// Re-export основных компонентов
|
||||||
|
pub use db::FutriixDB;
|
||||||
|
pub use cli::CommandParser;
|
||||||
|
pub use config::Config;
|
||||||
|
pub use lua::LuaInterpreter;
|
||||||
|
|
||||||
|
// Re-export HTTP функций
|
||||||
|
pub use http::start_http_server;
|
||||||
|
|
||||||
|
// Re-export функций шифрования
|
||||||
|
pub use db::EncryptionManager;
|
||||||
|
|
||||||
423
src/lua.rs
Normal file
423
src/lua.rs
Normal file
@ -0,0 +1,423 @@
|
|||||||
|
//[file name]: lua.rs
|
||||||
|
|
||||||
|
use rlua::{Lua, Function, Value as LuaValue};
|
||||||
|
use std::error::Error;
|
||||||
|
use crate::db::FutriixDB;
|
||||||
|
use serde_json::Value;
|
||||||
|
use rustyline::error::ReadlineError;
|
||||||
|
use rustyline::{Editor, CompletionType, Config as EditorConfig};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fs::OpenOptions;
|
||||||
|
use std::io::Write;
|
||||||
|
use chrono::Utc;
|
||||||
|
use md5::{Digest, Md5};
|
||||||
|
|
||||||
|
/// Lua интерпретатор с поддержкой команд СУБД
|
||||||
|
pub struct LuaInterpreter {
|
||||||
|
db: FutriixDB,
|
||||||
|
lua: Lua,
|
||||||
|
editor: Editor<(), rustyline::history::DefaultHistory>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaInterpreter {
|
||||||
|
pub fn new(db: FutriixDB) -> Self {
|
||||||
|
let lua = Lua::new();
|
||||||
|
let config = EditorConfig::builder()
|
||||||
|
.completion_type(CompletionType::List)
|
||||||
|
.build();
|
||||||
|
let editor = Editor::with_config(config).unwrap();
|
||||||
|
|
||||||
|
Self { db, lua, editor }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(&mut self) -> Result<(), Box<dyn Error>> {
|
||||||
|
// Загрузка истории команд
|
||||||
|
let history_path = "history.txt";
|
||||||
|
if let Err(_) = self.editor.load_history(history_path) {
|
||||||
|
println!("No previous command history found.");
|
||||||
|
Self::log_to_appsr_file("[INFO] No previous command history found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Регистрация функций СУБД в Lua
|
||||||
|
self.register_db_functions()?;
|
||||||
|
|
||||||
|
println!("{}", ansi_term::Colour::RGB(0x00, 0xbf, 0xff).paint("Lua interpreter ready. Type 'exit' to quit."));
|
||||||
|
Self::log_to_appsr_file("[INFO] Lua interpreter ready. Type 'exit' to quit.");
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let readline = self.editor.readline(&ansi_term::Colour::RGB(0xb6, 0xff, 0x76).paint("lua> ").to_string());
|
||||||
|
match readline {
|
||||||
|
Ok(line) => {
|
||||||
|
if line.trim().eq_ignore_ascii_case("exit") {
|
||||||
|
Self::log_to_appsr_file("[INFO] User exited Lua interpreter");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = self.editor.add_history_entry(line.as_str());
|
||||||
|
|
||||||
|
// Логируем введённую команду
|
||||||
|
Self::log_to_appsr_file(&format!("[COMMAND] lua> {}", &line));
|
||||||
|
|
||||||
|
// Обработка специальных команд СУБД
|
||||||
|
if let Some(result) = self.handle_special_commands(&line).await {
|
||||||
|
println!("{}", result);
|
||||||
|
Self::log_to_appsr_file(&format!("[RESULT] {}", &result));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Выполнение как Lua кода
|
||||||
|
match self.execute_lua(&line).await {
|
||||||
|
Ok(result) => {
|
||||||
|
if !result.is_empty() {
|
||||||
|
println!("{}", result);
|
||||||
|
Self::log_to_appsr_file(&format!("[RESULT] {}", &result));
|
||||||
|
} else {
|
||||||
|
Self::log_to_appsr_file("[RESULT] Command executed successfully");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
let error_msg = format!("Error: {}", e);
|
||||||
|
eprintln!("{}", ansi_term::Colour::Red.paint(&error_msg));
|
||||||
|
Self::log_to_appsr_file(&format!("[ERROR] {}", &error_msg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(ReadlineError::Interrupted) => {
|
||||||
|
println!("CTRL-C");
|
||||||
|
Self::log_to_appsr_file("[INFO] CTRL-C received");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Err(ReadlineError::Eof) => {
|
||||||
|
println!("CTRL-D");
|
||||||
|
Self::log_to_appsr_file("[INFO] CTRL-D received");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
let error_msg = format!("Error: {:?}", err);
|
||||||
|
eprintln!("{}", error_msg);
|
||||||
|
Self::log_to_appsr_file(&format!("[ERROR] {}", error_msg));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сохранение истории команд
|
||||||
|
self.editor.save_history(history_path)
|
||||||
|
.map_err(|e| Box::new(e) as Box<dyn Error>)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Логирование в файл appsr.txt
|
||||||
|
fn log_to_appsr_file(message: &str) {
|
||||||
|
let timestamp = Utc::now().to_rfc3339();
|
||||||
|
let log_entry = format!("{} {}\n", timestamp, message);
|
||||||
|
|
||||||
|
if let Ok(mut file) = OpenOptions::new()
|
||||||
|
.create(true)
|
||||||
|
.append(true)
|
||||||
|
.open("history_appsr.txt")
|
||||||
|
{
|
||||||
|
let _ = file.write_all(log_entry.as_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_db_functions(&self) -> Result<(), Box<dyn Error>> {
|
||||||
|
let lua = &self.lua;
|
||||||
|
let globals = lua.globals();
|
||||||
|
|
||||||
|
// Функции для работы с пространствами
|
||||||
|
let db_clone_1 = self.db.clone();
|
||||||
|
let create_space = lua.create_function(move |_, name: String| {
|
||||||
|
db_clone_1.create_space(&name)
|
||||||
|
.map_err(|e| rlua::Error::RuntimeError(e))
|
||||||
|
})?;
|
||||||
|
globals.set("create_space", create_space)?;
|
||||||
|
|
||||||
|
let db_clone_2 = self.db.clone();
|
||||||
|
let delete_space = lua.create_function(move |_, name: String| {
|
||||||
|
db_clone_2.delete_space(&name)
|
||||||
|
.map_err(|e| rlua::Error::RuntimeError(e))
|
||||||
|
})?;
|
||||||
|
globals.set("delete_space", delete_space)?;
|
||||||
|
|
||||||
|
// Функции для работы с документами
|
||||||
|
let db_clone_3 = self.db.clone();
|
||||||
|
let insert = lua.create_function(move |_, (space, key, value): (String, String, String)| {
|
||||||
|
let json_value: Value = serde_json::from_str(&value)
|
||||||
|
.map_err(|e| rlua::Error::RuntimeError(e.to_string()))?;
|
||||||
|
db_clone_3.insert(&space, &key, json_value)
|
||||||
|
.map_err(|e| rlua::Error::RuntimeError(e))
|
||||||
|
})?;
|
||||||
|
globals.set("insert", insert)?;
|
||||||
|
|
||||||
|
let db_clone_4 = self.db.clone();
|
||||||
|
let get = lua.create_function(move |_, (space, key): (String, String)| {
|
||||||
|
match db_clone_4.get(&space, &key) {
|
||||||
|
Ok(Some(value)) => {
|
||||||
|
let json_str = serde_json::to_string(&value)
|
||||||
|
.map_err(|e| rlua::Error::RuntimeError(e.to_string()))?;
|
||||||
|
Ok(json_str)
|
||||||
|
}
|
||||||
|
Ok(None) => Ok("null".to_string()),
|
||||||
|
Err(e) => Err(rlua::Error::RuntimeError(e)),
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
globals.set("get", get)?;
|
||||||
|
|
||||||
|
let db_clone_5 = self.db.clone();
|
||||||
|
let update = lua.create_function(move |_, (space, key, value): (String, String, String)| {
|
||||||
|
let json_value: Value = serde_json::from_str(&value)
|
||||||
|
.map_err(|e| rlua::Error::RuntimeError(e.to_string()))?;
|
||||||
|
db_clone_5.update(&space, &key, json_value)
|
||||||
|
.map_err(|e| rlua::Error::RuntimeError(e))
|
||||||
|
})?;
|
||||||
|
globals.set("update", update)?;
|
||||||
|
|
||||||
|
let db_clone_6 = self.db.clone();
|
||||||
|
let delete = lua.create_function(move |_, (space, key): (String, String)| {
|
||||||
|
db_clone_6.delete(&space, &key)
|
||||||
|
.map_err(|e| rlua::Error::RuntimeError(e))
|
||||||
|
})?;
|
||||||
|
globals.set("delete", delete)?;
|
||||||
|
|
||||||
|
// Функции для работы с кортежами
|
||||||
|
let db_clone_7 = self.db.clone();
|
||||||
|
let create_tuple = lua.create_function(move |_, (space, tuple_id, value): (String, String, String)| {
|
||||||
|
let json_value: Value = serde_json::from_str(&value)
|
||||||
|
.map_err(|e| rlua::Error::RuntimeError(e.to_string()))?;
|
||||||
|
db_clone_7.create_tuple(&space, &tuple_id, json_value)
|
||||||
|
.map_err(|e| rlua::Error::RuntimeError(e))
|
||||||
|
})?;
|
||||||
|
globals.set("create_tuple", create_tuple)?;
|
||||||
|
|
||||||
|
let db_clone_8 = self.db.clone();
|
||||||
|
let read_tuple = lua.create_function(move |_, (space, tuple_id): (String, String)| {
|
||||||
|
match db_clone_8.read_tuple(&space, &tuple_id) {
|
||||||
|
Ok(Some(value)) => {
|
||||||
|
let json_str = serde_json::to_string(&value)
|
||||||
|
.map_err(|e| rlua::Error::RuntimeError(e.to_string()))?;
|
||||||
|
Ok(json_str)
|
||||||
|
}
|
||||||
|
Ok(None) => Ok("null".to_string()),
|
||||||
|
Err(e) => Err(rlua::Error::RuntimeError(e)),
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
globals.set("read_tuple", read_tuple)?;
|
||||||
|
|
||||||
|
let db_clone_9 = self.db.clone();
|
||||||
|
let delete_tuple = lua.create_function(move |_, (space, tuple_id): (String, String)| {
|
||||||
|
db_clone_9.delete_tuple(&space, &tuple_id)
|
||||||
|
.map_err(|e| rlua::Error::RuntimeError(e))
|
||||||
|
})?;
|
||||||
|
globals.set("delete_tuple", delete_tuple)?;
|
||||||
|
|
||||||
|
// Функции для работы с транзакциями
|
||||||
|
let db_clone_10 = self.db.clone();
|
||||||
|
let begin_transaction = lua.create_function(move |_, transaction_id: String| {
|
||||||
|
db_clone_10.begin_transaction(transaction_id)
|
||||||
|
.map_err(|e| rlua::Error::RuntimeError(e))
|
||||||
|
})?;
|
||||||
|
globals.set("begin_transaction", begin_transaction)?;
|
||||||
|
|
||||||
|
// Функции для работы с индексами
|
||||||
|
let db_clone_11 = self.db.clone();
|
||||||
|
let create_primary_index = lua.create_function(move |_, collection: String| {
|
||||||
|
db_clone_11.create_primary_index(&collection)
|
||||||
|
.map_err(|e| rlua::Error::RuntimeError(e))
|
||||||
|
})?;
|
||||||
|
globals.set("create_primary_index", create_primary_index)?;
|
||||||
|
|
||||||
|
let db_clone_12 = self.db.clone();
|
||||||
|
let create_secondary_index = lua.create_function(move |_, (collection, field, index_type): (String, String, String)| {
|
||||||
|
db_clone_12.create_secondary_index(&collection, &field, &index_type)
|
||||||
|
.map_err(|e| rlua::Error::RuntimeError(e))
|
||||||
|
})?;
|
||||||
|
globals.set("create_secondary_index", create_secondary_index)?;
|
||||||
|
|
||||||
|
// Функции для работы с хранимыми процедурами
|
||||||
|
let db_clone_13 = self.db.clone();
|
||||||
|
let create_procedure = lua.create_function(move |_, (name, code): (String, String)| {
|
||||||
|
db_clone_13.create_procedure(&name, &code)
|
||||||
|
.map_err(|e| rlua::Error::RuntimeError(e))
|
||||||
|
})?;
|
||||||
|
globals.set("create_procedure", create_procedure)?;
|
||||||
|
|
||||||
|
let db_clone_14 = self.db.clone();
|
||||||
|
let drop_procedure = lua.create_function(move |_, name: String| {
|
||||||
|
db_clone_14.drop_procedure(&name)
|
||||||
|
.map_err(|e| rlua::Error::RuntimeError(e))
|
||||||
|
})?;
|
||||||
|
globals.set("drop_procedure", drop_procedure)?;
|
||||||
|
|
||||||
|
let db_clone_15 = self.db.clone();
|
||||||
|
let call_procedure = lua.create_function(move |_, name: String| {
|
||||||
|
match db_clone_15.get_procedure(&name) {
|
||||||
|
Some(code) => {
|
||||||
|
// Здесь должна быть логика выполнения процедуры
|
||||||
|
// В упрощённой реализации просто возвращаем код
|
||||||
|
Ok(code)
|
||||||
|
}
|
||||||
|
None => Err(rlua::Error::RuntimeError("Procedure not found".to_string())),
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
globals.set("call_procedure", call_procedure)?;
|
||||||
|
|
||||||
|
// Функции для работы с бэкапами
|
||||||
|
let db_clone_16 = self.db.clone();
|
||||||
|
let create_backup = lua.create_function(move |_, backup_path: String| {
|
||||||
|
db_clone_16.create_backup(&backup_path)
|
||||||
|
.map_err(|e| rlua::Error::RuntimeError(e))
|
||||||
|
})?;
|
||||||
|
globals.set("create_backup", create_backup)?;
|
||||||
|
|
||||||
|
let db_clone_17 = self.db.clone();
|
||||||
|
let restore_backup = lua.create_function(move |_, backup_path: String| {
|
||||||
|
db_clone_17.restore_backup(&backup_path)
|
||||||
|
.map_err(|e| rlua::Error::RuntimeError(e))
|
||||||
|
})?;
|
||||||
|
globals.set("restore_backup", restore_backup)?;
|
||||||
|
|
||||||
|
// Функции для работы с репликацией
|
||||||
|
let db_clone_18 = self.db.clone();
|
||||||
|
let enable_replication = lua.create_function(move |_, ()| {
|
||||||
|
db_clone_18.enable_replication();
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
globals.set("enable_replication", enable_replication)?;
|
||||||
|
|
||||||
|
let db_clone_19 = self.db.clone();
|
||||||
|
let disable_replication = lua.create_function(move |_, ()| {
|
||||||
|
db_clone_19.disable_replication();
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
globals.set("disable_replication", disable_replication)?;
|
||||||
|
|
||||||
|
// Функция для MD5 хеширования
|
||||||
|
let md5_hash = lua.create_function(move |_, text: String| {
|
||||||
|
let mut hasher = Md5::new();
|
||||||
|
hasher.update(text.as_bytes());
|
||||||
|
let result = hasher.finalize();
|
||||||
|
Ok(format!("{:x}", result))
|
||||||
|
})?;
|
||||||
|
globals.set("md5", md5_hash)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_special_commands(&self, line: &str) -> Option<String> {
|
||||||
|
let parts: Vec<&str> = line.trim().split_whitespace().collect();
|
||||||
|
if parts.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
match parts[0].to_lowercase().as_str() {
|
||||||
|
"backup" => {
|
||||||
|
if parts.len() >= 2 {
|
||||||
|
let backup_path = parts[1];
|
||||||
|
match self.db.create_backup(backup_path) {
|
||||||
|
Ok(()) => Some("Backup created successfully".to_string()),
|
||||||
|
Err(e) => Some(format!("Backup error: {}", e)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Some("Usage: backup <backup_file_path>".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"restore" => {
|
||||||
|
if parts.len() >= 2 {
|
||||||
|
let backup_path = parts[1];
|
||||||
|
match self.db.restore_backup(backup_path) {
|
||||||
|
Ok(()) => Some("Backup restored successfully".to_string()),
|
||||||
|
Err(e) => Some(format!("Restore error: {}", e)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Some("Usage: restore <backup_file_path>".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"create" if parts.len() > 1 && parts[1].to_lowercase() == "procedure" => {
|
||||||
|
if parts.len() >= 4 {
|
||||||
|
let name = parts[2];
|
||||||
|
// Для создания процедуры нужен код, который обычно многострочный
|
||||||
|
// В упрощённой реализации просто возвращаем инструкцию
|
||||||
|
Some("Use: create_procedure('name', 'lua_code') in Lua".to_string())
|
||||||
|
} else {
|
||||||
|
Some("Usage: create procedure <name> <code>".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"call" if parts.len() > 1 && parts[1].to_lowercase() == "procedure" => {
|
||||||
|
if parts.len() >= 3 {
|
||||||
|
let name = parts[2];
|
||||||
|
match self.db.get_procedure(name) {
|
||||||
|
Some(ref code) => {
|
||||||
|
// Выполняем код процедуры
|
||||||
|
match self.execute_lua(code).await {
|
||||||
|
Ok(result) => Some(format!("Procedure result: {}", result)),
|
||||||
|
Err(e) => Some(format!("Procedure execution error: {}", e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Some(format!("Procedure '{}' not found", name)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Some("Usage: call procedure <name>".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"drop" if parts.len() > 1 && parts[1].to_lowercase() == "procedure" => {
|
||||||
|
if parts.len() >= 3 {
|
||||||
|
let name = parts[2];
|
||||||
|
match self.db.drop_procedure(name) {
|
||||||
|
Ok(()) => Some("Procedure dropped successfully".to_string()),
|
||||||
|
Err(e) => Some(format!("Drop procedure error: {}", e)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Some("Usage: drop procedure <name>".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"md5" => {
|
||||||
|
if parts.len() >= 2 {
|
||||||
|
let text = parts[1];
|
||||||
|
let mut hasher = Md5::new();
|
||||||
|
hasher.update(text.as_bytes());
|
||||||
|
let result = hasher.finalize();
|
||||||
|
Some(format!("MD5 hash: {:x}", result))
|
||||||
|
} else {
|
||||||
|
Some("Usage: md5 <text>".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn execute_lua(&self, code: &str) -> Result<String, Box<dyn Error>> {
|
||||||
|
let result = self.lua.load(code).eval::<LuaValue>()?;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
LuaValue::String(s) => Ok(s.to_str()?.to_string()),
|
||||||
|
LuaValue::Number(n) => Ok(n.to_string()),
|
||||||
|
LuaValue::Boolean(b) => Ok(b.to_string()),
|
||||||
|
LuaValue::Nil => Ok("".to_string()),
|
||||||
|
LuaValue::Table(t) => {
|
||||||
|
let mut result = HashMap::new();
|
||||||
|
for pair in t.pairs::<LuaValue, LuaValue>() {
|
||||||
|
let (key, value) = pair?;
|
||||||
|
result.insert(
|
||||||
|
Self::lua_value_to_string(key)?,
|
||||||
|
Self::lua_value_to_string(value)?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(serde_json::to_string(&result)?)
|
||||||
|
}
|
||||||
|
_ => Ok(format!("{:?}", result)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lua_value_to_string(value: LuaValue) -> Result<String, Box<dyn Error>> {
|
||||||
|
match value {
|
||||||
|
LuaValue::String(s) => Ok(s.to_str()?.to_string()),
|
||||||
|
LuaValue::Number(n) => Ok(n.to_string()),
|
||||||
|
LuaValue::Boolean(b) => Ok(b.to_string()),
|
||||||
|
LuaValue::Nil => Ok("null".to_string()),
|
||||||
|
_ => Ok(format!("{:?}", value)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
55
src/lua/bindings.rs
Normal file
55
src/lua/bindings.rs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
use rlua::{Lua, Result as LuaResult};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use crate::db::FutriixDB;
|
||||||
|
|
||||||
|
/// Регистрация функций СУБД в Lua окружении
|
||||||
|
pub fn register_db_functions(lua: &Lua, db: FutriixDB) -> LuaResult<()> {
|
||||||
|
let globals = lua.globals();
|
||||||
|
|
||||||
|
// Клонируем db для каждого замыкания
|
||||||
|
let db_clone_1 = db.clone();
|
||||||
|
let db_create = lua.create_function(move |_, (collection, key, value): (String, String, String)| {
|
||||||
|
let json_value: serde_json::Value = serde_json::from_str(&value)
|
||||||
|
.map_err(|e| rlua::Error::external(e))?;
|
||||||
|
db_clone_1.create_document(&collection, &key, json_value)
|
||||||
|
.map_err(|e| rlua::Error::external(e))?;
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
globals.set("db_create", db_create)?;
|
||||||
|
|
||||||
|
let db_clone_2 = db.clone();
|
||||||
|
let db_read = lua.create_function(move |_, (collection, key): (String, String)| {
|
||||||
|
match db_clone_2.read_document(&collection, &key) {
|
||||||
|
Ok(Some(value)) => Ok(serde_json::to_string(&value).map_err(|e| rlua::Error::external(e))?),
|
||||||
|
Ok(None) => Ok("null".to_string()),
|
||||||
|
Err(e) => Err(rlua::Error::external(e)),
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
globals.set("db_read", db_read)?;
|
||||||
|
|
||||||
|
let db_clone_3 = db.clone();
|
||||||
|
let db_update = lua.create_function(move |_, (collection, key, value): (String, String, String)| {
|
||||||
|
let json_value: serde_json::Value = serde_json::from_str(&value)
|
||||||
|
.map_err(|e| rlua::Error::external(e))?;
|
||||||
|
db_clone_3.update_document(&collection, &key, json_value)
|
||||||
|
.map_err(|e| rlua::Error::external(e))?;
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
globals.set("db_update", db_update)?;
|
||||||
|
|
||||||
|
let db_clone_4 = db.clone();
|
||||||
|
let db_delete = lua.create_function(move |_, (collection, key): (String, String)| {
|
||||||
|
db_clone_4.delete_document(&collection, &key)
|
||||||
|
.map_err(|e| rlua::Error::external(e))?;
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
globals.set("db_delete", db_delete)?;
|
||||||
|
|
||||||
|
// Регистрация других функций...
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
168
src/main.rs
Normal file
168
src/main.rs
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
use ansi_term::Colour;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::env;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::fs::OpenOptions;
|
||||||
|
use std::io::Write;
|
||||||
|
use chrono::Utc;
|
||||||
|
|
||||||
|
// Импортируем из библиотечной части
|
||||||
|
use futriix::{Config, cli::CommandParser, lua::LuaInterpreter, db::FutriixDB, http};
|
||||||
|
|
||||||
|
/// Главная точка входа приложения
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
// Вывод заголовка приложения с пустой строкой перед
|
||||||
|
println!();
|
||||||
|
println!("{}", Colour::RGB(0, 191, 255).paint("futriiX database server"));
|
||||||
|
println!("{}", Colour::RGB(0, 191, 255).paint("futriix 3i²(by 26.10.2025)"));
|
||||||
|
|
||||||
|
// Инициализация логгера
|
||||||
|
env_logger::init();
|
||||||
|
log::info!("[INFO] Starting futriiX server...");
|
||||||
|
|
||||||
|
// Логирование в файл appsr.txt
|
||||||
|
log_to_appsr_file("[INFO] Starting futriiX server...");
|
||||||
|
|
||||||
|
// Получаем аргументы командной строки
|
||||||
|
let args: Vec<String> = env::args().collect();
|
||||||
|
let config_path = if args.len() > 1 {
|
||||||
|
&args[1]
|
||||||
|
} else {
|
||||||
|
"config/config.toml"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Загрузка конфигурации
|
||||||
|
let config = match load_config(config_path) {
|
||||||
|
Ok(cfg) => {
|
||||||
|
let msg = format!("[INFO] Configuration loaded successfully from {}", config_path);
|
||||||
|
log::info!("{}", msg);
|
||||||
|
log_to_appsr_file(&msg);
|
||||||
|
cfg
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
let error_msg = format!("Error loading config from {}: {}", config_path, e);
|
||||||
|
eprintln!("{}", Colour::Red.paint(&error_msg));
|
||||||
|
eprintln!("{}", Colour::Yellow.paint("Using default configuration..."));
|
||||||
|
|
||||||
|
log_to_appsr_file(&format!("[ERROR] {}", error_msg));
|
||||||
|
log_to_appsr_file("[INFO] Using default configuration...");
|
||||||
|
|
||||||
|
// Используем значения по умолчанию
|
||||||
|
Config {
|
||||||
|
http_port: 8080,
|
||||||
|
db_path: "./data".to_string(),
|
||||||
|
log_level: "info".to_string(),
|
||||||
|
cluster_enabled: false,
|
||||||
|
node_id: "node_1".to_string(),
|
||||||
|
cluster_nodes: vec![],
|
||||||
|
acl_enabled: false,
|
||||||
|
admin_users: vec![],
|
||||||
|
master_master_replication: false,
|
||||||
|
replication_nodes: vec![],
|
||||||
|
https_enabled: false,
|
||||||
|
https_port: 8443,
|
||||||
|
cert_file: "./cert.pem".to_string(),
|
||||||
|
key_file: "./key.pem".to_string(),
|
||||||
|
http2_enabled: false,
|
||||||
|
stored_procedures_path: "./procedures".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Инициализация СУБД
|
||||||
|
let db = match FutriixDB::new(&config).await {
|
||||||
|
Ok(db_instance) => {
|
||||||
|
let msg = "[INFO] Database initialized successfully";
|
||||||
|
log::info!("{}", msg);
|
||||||
|
log_to_appsr_file(msg);
|
||||||
|
db_instance
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
let error_msg = format!("[ERROR] Failed to initialize database: {}", e);
|
||||||
|
log::error!("{}", error_msg);
|
||||||
|
log_to_appsr_file(&error_msg);
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Запуск HTTP сервера в фоновом режиме
|
||||||
|
let db_arc = Arc::new(db.clone());
|
||||||
|
let config_clone = config.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let msg = format!("[INFO] HTTP server starting on port {}", config_clone.http_port);
|
||||||
|
log::info!("{}", msg);
|
||||||
|
log_to_appsr_file(&msg);
|
||||||
|
http::start_http_server(db_arc, config_clone).await;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Вывод информации о состоянии
|
||||||
|
let cluster_status = if config.cluster_enabled {
|
||||||
|
format!("Cluster status: enabled (node: {})", config.node_id)
|
||||||
|
} else {
|
||||||
|
"Cluster status: single node (standalone mode)".to_string()
|
||||||
|
};
|
||||||
|
println!("{}", Colour::RGB(0, 255, 127).paint(&cluster_status));
|
||||||
|
log_to_appsr_file(&format!("[INFO] {}", cluster_status));
|
||||||
|
|
||||||
|
let acl_status = if config.acl_enabled {
|
||||||
|
"ACL: enabled"
|
||||||
|
} else {
|
||||||
|
"ACL: disabled"
|
||||||
|
};
|
||||||
|
println!("{}", Colour::RGB(0, 255, 127).paint(acl_status));
|
||||||
|
log_to_appsr_file(&format!("[INFO] {}", acl_status));
|
||||||
|
|
||||||
|
let replication_status = if config.master_master_replication {
|
||||||
|
"Master-Master replication: enabled"
|
||||||
|
} else {
|
||||||
|
"Master-Master replication: disabled"
|
||||||
|
};
|
||||||
|
println!("{}", Colour::RGB(0, 255, 127).paint(replication_status));
|
||||||
|
log_to_appsr_file(&format!("[INFO] {}", replication_status));
|
||||||
|
|
||||||
|
let history_path = "history.txt";
|
||||||
|
let history_abs_path = std::env::current_dir()?
|
||||||
|
.join(history_path)
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string();
|
||||||
|
println!("Command history file: {}", history_abs_path);
|
||||||
|
log_to_appsr_file(&format!("[INFO] Command history file: {}", history_abs_path));
|
||||||
|
|
||||||
|
// Запуск Lua интерпретатора
|
||||||
|
let mut lua_interpreter = LuaInterpreter::new(db);
|
||||||
|
if let Err(e) = lua_interpreter.run().await {
|
||||||
|
let error_msg = format!("[ERROR] Lua interpreter error: {}", e);
|
||||||
|
log::error!("{}", error_msg);
|
||||||
|
log_to_appsr_file(&error_msg);
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
let msg = "[INFO] futriiX server shutdown completed";
|
||||||
|
log::info!("{}", msg);
|
||||||
|
log_to_appsr_file(msg);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Загрузка конфигурации из файла
|
||||||
|
fn load_config(config_path: &str) -> Result<Config, Box<dyn Error>> {
|
||||||
|
let config_content = std::fs::read_to_string(config_path)?;
|
||||||
|
let config: Config = toml::from_str(&config_content)?;
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Логирование в файл appsr.txt
|
||||||
|
fn log_to_appsr_file(message: &str) {
|
||||||
|
let timestamp = Utc::now().to_rfc3339();
|
||||||
|
let log_entry = format!("{} {}\n", timestamp, message);
|
||||||
|
|
||||||
|
if let Ok(mut file) = OpenOptions::new()
|
||||||
|
.create(true)
|
||||||
|
.append(true)
|
||||||
|
.open("history_appsr.txt")
|
||||||
|
{
|
||||||
|
let _ = file.write_all(log_entry.as_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user