//[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> { // Загрузка истории команд 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)?; 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> { 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 { 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 ".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 ".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 ".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 ".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 ".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 ".to_string()) } } _ => None, } } async fn execute_lua(&self, code: &str) -> Result> { let result = self.lua.load(code).eval::()?; 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::() { 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> { 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)), } } }