diff --git a/src/server/lua_engine.rs b/src/server/lua_engine.rs deleted file mode 100644 index 5d24942..0000000 --- a/src/server/lua_engine.rs +++ /dev/null @@ -1,713 +0,0 @@ -// src/server/lua_engine.rs -//! Встроенный интерпретатор Lua для Futriix Server с lock-free архитектурой -//! -//! Этот модуль предоставляет безопасную интеграцию языка Lua в систему Futriix, -//! позволяя выполнять пользовательские скрипты для настройки поведения сервера, -//! создания триггеров, хранимых процедур и кастомной логики обработки данных. -//! Все операции с базой данных выполняются через lock-free интерфейс для -//! максимальной производительности в многопоточных сценариях. -//! -//! Основные функции: -//! 1. Безопасное выполнение Lua скриптов с изоляцией окружения -//! 2. Интеграция с базой данных через атомарные операции -//! 3. Регистрация функций для работы с коллекциями, индексами и транзакциями -//! 4. Поддержка хранимых процедур и триггеров -//! 5. Интеграция с модулем шардинга и репликации -//! 6. Автоматическое выполнение скриптов инициализации -//! -//! Архитектурные особенности: -//! - Использование rlua для безопасного выполнения Lua кода -//! - Изоляция пользовательского кода в sandbox окружении -//! - Атомарный доступ к данным через замыкания и Arc-ссылки -//! - Поддержка асинхронных операций через tokio -//! - Расширяемая система регистрации функций - -use rlua::{Lua, RluaCompat, Function, Result as LuaResult, Value as LuaValue}; -use std::sync::Arc; -use std::fs; -use std::path::Path; - -use crate::common::Result; -use crate::common::protocol; -use crate::server::database::{Trigger, TriggerEvent, Index, IndexType}; - -/// Движок Lua для выполнения скриптов в lock-free окружении -/// Обеспечивает безопасное выполнение пользовательского кода -/// с интеграцией в систему Futriix -#[derive(Clone)] -pub struct LuaEngine { - lua: Arc, -} - -impl LuaEngine { - /// Создает новый Lua движок с настройками по умолчанию - /// Инициализирует базовое окружение с глобальными функциями Futriix - pub fn new() -> Result { - let lua = Lua::new(); - - // Настройка Lua окружения с базовыми функциями для интеграции с Futriix - lua.load(r#" - -- Глобальные функции для логирования в системе Futriix - function futriix_log(message) - print("[LUA] " .. message) - end - - function futriix_error(message) - print("[LUA ERROR] " .. message) - end - - -- Функция для получения текущего времени в формате ISO 8601 - function futriix_current_time() - -- Возвращает строку с текущим временем - -- В реальной реализации можно интегрировать с chrono - return os.date("%Y-%m-%dT%H:%M:%S") - end - - -- Функция для генерации UUID (упрощенная версия) - function futriix_generate_uuid() - local template ='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx' - return string.gsub(template, '[xy]', function (c) - local v = (c == 'x') and math.random(0, 0xf) or math.random(8, 0xb) - return string.format('%x', v) - end) - end - - -- Функция для проверки типа данных - function futriix_is_valid_json(str) - -- Упрощенная проверка JSON - if type(str) ~= 'string' then - return false - end - return string.find(str, '{') ~= nil or string.find(str, '[') ~= nil - end - - -- Функция для работы с коллекциями (placeholder) - function futriix_collection_exists(name) - -- В реальной реализации проверяет существование коллекции - return true - end - "#).exec()?; - - Ok(Self { lua: Arc::new(lua) }) - } - - /// Выполнение Lua скрипта из файла - /// Читает содержимое файла и выполняет его как Lua код - #[allow(dead_code)] - pub fn execute_script_file(&self, file_path: &str) -> Result<()> { - let script_content = fs::read_to_string(file_path) - .map_err(|e| crate::common::FutriixError::LuaError(format!("Failed to read script file {}: {}", file_path, e)))?; - - self.execute_script(&script_content) - } - - /// Выполнение Lua скрипта из строки - /// Основной метод для выполнения пользовательского кода - pub fn execute_script(&self, script: &str) -> Result<()> { - let lua = self.lua.clone(); - lua.load(script).exec()?; - Ok(()) - } - - /// Выполнение всех скриптов из директории - /// Используется для автоматического выполнения скриптов инициализации - #[allow(dead_code)] - pub fn execute_scripts_from_dir(&self, dir_path: &str, script_names: &[String]) -> Result<()> { - let path = Path::new(dir_path); - - if !path.exists() { - println!("Lua scripts directory does not exist: {}", path.display()); - return Ok(()); - } - - if !path.is_dir() { - return Err(crate::common::FutriixError::LuaError( - format!("Lua scripts path is not a directory: {}", path.display()) - )); - } - - for script_name in script_names { - let script_path = path.join(script_name); - - if script_path.exists() && script_path.is_file() { - println!("Executing Lua script: {}", script_path.display()); - - match self.execute_script_file(script_path.to_str().unwrap()) { - Ok(_) => println!("✓ Script executed successfully: {}", script_name), - Err(e) => eprintln!("✗ Failed to execute script {}: {}", script_name, e), - } - } else { - println!("Script not found: {}", script_path.display()); - } - } - - Ok(()) - } - - /// Регистрация функций базы данных в Lua окружении с lock-free доступом - /// Функции Lua ожидают два аргумента: Lua состояние (self) и аргументы функции - /// Этот метод создает безопасный мост между Lua и Rust - pub fn register_db_functions( - &self, - db: Arc, - sharding_manager: Arc - ) -> Result<()> { - let lua = self.lua.clone(); - - // Создаем таблицу для функций БД - let futriix_db = lua.create_table()?; - - // Базовые CRUD функции с lock-free доступом - let db_clone = db.clone(); - futriix_db.set("create", lua.create_function(move |_, (collection, data): (String, String)| { - let command = protocol::Command::Create { - collection, - document: data.into_bytes(), - }; - match db_clone.execute_command(command) { - Ok(_) => Ok(()), - Err(e) => Err(rlua::Error::RuntimeError(e.to_string())), - } - })?)?; - - let db_clone = db.clone(); - futriix_db.set("read", lua.create_function(move |_, (collection, id): (String, String)| { - let command = protocol::Command::Read { - collection, - id, - }; - match db_clone.execute_command(command) { - Ok(response) => { - match response { - protocol::Response::Success(data) => { - Ok(String::from_utf8_lossy(&data).to_string()) - } - protocol::Response::Error(e) => { - Err(rlua::Error::RuntimeError(e)) - } - } - } - Err(e) => Err(rlua::Error::RuntimeError(e.to_string())), - } - })?)?; - - let db_clone = db.clone(); - futriix_db.set("update", lua.create_function(move |_, (collection, id, data): (String, String, String)| { - let command = protocol::Command::Update { - collection, - id, - document: data.into_bytes(), - }; - match db_clone.execute_command(command) { - Ok(_) => Ok(()), - Err(e) => Err(rlua::Error::RuntimeError(e.to_string())), - } - })?)?; - - let db_clone = db.clone(); - futriix_db.set("delete", lua.create_function(move |_, (collection, id): (String, String)| { - let command = protocol::Command::Delete { - collection, - id, - }; - match db_clone.execute_command(command) { - Ok(_) => Ok(()), - Err(e) => Err(rlua::Error::RuntimeError(e.to_string())), - } - })?)?; - - let db_clone = db.clone(); - futriix_db.set("query", lua.create_function(move |_, (collection, filter): (String, String)| { - let command = protocol::Command::Query { - collection, - filter: filter.into_bytes(), - }; - match db_clone.execute_command(command) { - Ok(response) => { - match response { - protocol::Response::Success(data) => { - Ok(String::from_utf8_lossy(&data).to_string()) - } - protocol::Response::Error(e) => { - Err(rlua::Error::RuntimeError(e)) - } - } - } - Err(e) => Err(rlua::Error::RuntimeError(e.to_string())), - } - })?)?; - - // Функции для работы с транзакциями - let db_clone = db.clone(); - futriix_db.set("begin_transaction", lua.create_function(move |_, transaction_id: String| { - let command = protocol::Command::BeginTransaction { transaction_id }; - match db_clone.execute_command(command) { - Ok(_) => Ok(()), - Err(e) => Err(rlua::Error::RuntimeError(e.to_string())), - } - })?)?; - - let db_clone = db.clone(); - futriix_db.set("commit_transaction", lua.create_function(move |_, transaction_id: String| { - let command = protocol::Command::CommitTransaction { transaction_id }; - match db_clone.execute_command(command) { - Ok(_) => Ok(()), - Err(e) => Err(rlua::Error::RuntimeError(e.to_string())), - } - })?)?; - - let db_clone = db.clone(); - futriix_db.set("rollback_transaction", lua.create_function(move |_, transaction_id: String| { - let command = protocol::Command::RollbackTransaction { transaction_id }; - match db_clone.execute_command(command) { - Ok(_) => Ok(()), - Err(e) => Err(rlua::Error::RuntimeError(e.to_string())), - } - })?)?; - - // Функции для работы с индексами - let db_clone = db.clone(); - futriix_db.set("create_index", lua.create_function(move |_, (collection, name, field, unique): (String, String, String, bool)| { - let index = Index { - name, - index_type: IndexType::Secondary, - field, - unique, - }; - let command = protocol::Command::CreateIndex { collection, index }; - match db_clone.execute_command(command) { - Ok(_) => Ok(()), - Err(e) => Err(rlua::Error::RuntimeError(e.to_string())), - } - })?)?; - - // Функции для работы с процедурами - let db_clone = db.clone(); - futriix_db.set("create_procedure", lua.create_function(move |_, (name, code): (String, String)| { - let command = protocol::Command::CreateProcedure { - name, - code: code.into_bytes(), - }; - match db_clone.execute_command(command) { - Ok(_) => Ok(()), - Err(e) => Err(rlua::Error::RuntimeError(e.to_string())), - } - })?)?; - - let db_clone = db.clone(); - futriix_db.set("call_procedure", lua.create_function(move |_, name: String| { - let command = protocol::Command::CallProcedure { name }; - match db_clone.execute_command(command) { - Ok(response) => { - match response { - protocol::Response::Success(data) => { - Ok(String::from_utf8_lossy(&data).to_string()) - } - protocol::Response::Error(e) => { - Err(rlua::Error::RuntimeError(e)) - } - } - } - Err(e) => Err(rlua::Error::RuntimeError(e.to_string())), - } - })?)?; - - // Функции для работы с шардингом - let sharding_clone = sharding_manager.clone(); - // Исправление: Lua функции ожидают два аргумента - Lua состояние и аргументы - // Используем явный тип для пустого кортежа () - futriix_db.set("get_cluster_status", lua.create_function(move |_, _: ()| { - match sharding_clone.get_cluster_status() { - Ok(status) => { - let json = serde_json::to_string(&status) - .map_err(|e| rlua::Error::RuntimeError(e.to_string()))?; - Ok(json) - } - Err(e) => Err(rlua::Error::RuntimeError(e.to_string())), - } - })?)?; - - let sharding_clone = sharding_manager.clone(); - futriix_db.set("add_shard_node", lua.create_function(move |_, (node_id, address, capacity): (String, String, u64)| { - match sharding_clone.add_node(node_id, address, capacity) { - Ok(_) => Ok(()), - Err(e) => Err(rlua::Error::RuntimeError(e.to_string())), - } - })?)?; - - let sharding_clone = sharding_manager.clone(); - // Исправление: Lua функции ожидают два аргумента - Lua состояние и аргументы - // Используем явный тип для пустого кортежа () - futriix_db.set("rebalance_cluster", lua.create_function(move |_, _: ()| { - match sharding_clone.rebalance_cluster() { - Ok(_) => Ok(()), - Err(e) => Err(rlua::Error::RuntimeError(e.to_string())), - } - })?)?; - - // Функция для создания бэкапа - let db_clone = db.clone(); - // Исправление: Lua функции ожидают два аргумента - Lua состояние и аргументы - // Используем явный тип для пустого кортежа () - futriix_db.set("create_backup", lua.create_function(move |_, _: ()| { - match db_clone.create_backup() { - Ok(backup) => { - let json = serde_json::to_string(&backup) - .map_err(|e| rlua::Error::RuntimeError(e.to_string()))?; - Ok(json) - } - Err(e) => Err(rlua::Error::RuntimeError(e.to_string())), - } - })?)?; - - // Функция для получения статистики БД - let db_clone = db.clone(); - // Исправление: Lua функции ожидают два аргумента - Lua состояние и аргументы - // Используем явный тип для пустого кортежа () - futriix_db.set("get_stats", lua.create_function(move |_, _: ()| { - match db_clone.get_stats() { - Ok(stats) => { - let json = serde_json::to_string(&stats) - .map_err(|e| rlua::Error::RuntimeError(e.to_string()))?; - Ok(json) - } - Err(e) => Err(rlua::Error::RuntimeError(e.to_string())), - } - })?)?; - - // Функция для выполнения произвольной команды - let db_clone = db.clone(); - futriix_db.set("execute_command", lua.create_function(move |_, command_json: String| { - let command: protocol::Command = serde_json::from_str(&command_json) - .map_err(|e| rlua::Error::RuntimeError(e.to_string()))?; - - match db_clone.execute_command(command) { - Ok(response) => { - let json = serde_json::to_string(&response) - .map_err(|e| rlua::Error::RuntimeError(e.to_string()))?; - Ok(json) - } - Err(e) => Err(rlua::Error::RuntimeError(e.to_string())), - } - })?)?; - - // Добавляем таблицу в глобальное пространство имен - lua.globals().set("futriix_db", futriix_db)?; - - // Создаем таблицу для работы с CSV - let csv_table = lua.create_table()?; - // ИСПРАВЛЕНИЕ: Указываем явный тип возвращаемого значения () для функций Lua - // CSV import функция - возвращает ошибку, так как функционал еще не реализован - csv_table.set("import", lua.create_function(move |_, _args: ()| -> rlua::Result<()> { - Err(rlua::Error::RuntimeError("CSV import not yet implemented in Lua".to_string())) - })?)?; - - // ИСПРАВЛЕНИЕ: Указываем явный тип возвращаемого значения () для функций Lua - // CSV export функция - возвращает ошибку, так как функционал еще не реализован - csv_table.set("export", lua.create_function(move |_, _args: ()| -> rlua::Result<()> { - Err(rlua::Error::RuntimeError("CSV export not yet implemented in Lua".to_string())) - })?)?; - - lua.globals().set("futriix_csv", csv_table)?; - - // Создаем таблицу для работы с триггерами - let triggers_table = lua.create_table()?; - - // Функция для создания триггера - triggers_table.set("create", lua.create_function(move |_, (name, event_str, collection, code): (String, String, String, String)| { - let event = match event_str.as_str() { - "before_create" => TriggerEvent::BeforeCreate, - "after_create" => TriggerEvent::AfterCreate, - "before_update" => TriggerEvent::BeforeUpdate, - "after_update" => TriggerEvent::AfterUpdate, - "before_delete" => TriggerEvent::BeforeDelete, - "after_delete" => TriggerEvent::AfterDelete, - _ => return Err(rlua::Error::RuntimeError(format!("Unknown trigger event: {}", event_str))), - }; - - let trigger = Trigger { - name, - event, - collection, - lua_code: code, - }; - - match db.add_trigger(trigger) { - Ok(_) => Ok(()), - Err(e) => Err(rlua::Error::RuntimeError(e.to_string())), - } - })?)?; - - lua.globals().set("futriix_triggers", triggers_table)?; - - println!("Lua engine initialized with database functions"); - Ok(()) - } - - /// Получение директории Lua скриптов - #[allow(dead_code)] - pub fn lua_scripts_dir(&self) -> &'static str { - "lua_scripts" - } - - /// Выполнение скрипта инициализации системы - /// Создает базовые глобальные функции и системные коллекции - pub fn execute_init_script(&self) -> Result<()> { - let init_script = r#" - -- Скрипт инициализации Futriix Server - print("Initializing Futriix Server Lua environment...") - - -- Создаем глобальные функции для бэкапов - function futriix.backup.start() - return futriix_db.create_backup() - end - - function futriix.backup.restore(backup_json) - -- В реальной реализации здесь будет восстановление из бэкапа - print("Restore functionality to be implemented") - return true - end - - -- Пример создания системной коллекции при старте - local success, result = pcall(function() - local timestamp = futriix_current_time() - local config_data = string.format('{"key": "server_start_time", "value": "%s"}', timestamp) - futriix_db.create("system_config", config_data) - end) - - if success then - print("System configuration initialized") - else - futriix_error("Failed to initialize system config: " .. result) - end - - -- Пример ACL проверки через Lua - function check_access(ip_address) - if ip_address == "127.0.0.1" then - return true - end - -- Здесь можно добавить более сложную логику проверки - return false - end - - -- Проверка кластерного режима (заглушка) - function is_cluster_enabled() - -- В реальной реализации проверяет конфигурацию кластера - return false - end - - print("Lua initialization script completed successfully") - "#; - - self.execute_script(init_script) - } - - /// Создание тестовой коллекции через Lua (для демонстрации) - #[allow(dead_code)] - pub fn create_test_collection(&self) -> Result<()> { - let test_script = r#" - -- Создание тестовой коллекции через Lua - print("Creating test collection...") - - local test_data = { - name = "Test User", - email = "test@example.com", - age = 30, - active = true, - tags = {"user", "test", "demo"} - } - - local json_data = futriix_db.get_stats() - print("Current DB stats: " .. json_data) - - -- Преобразуем таблицу Lua в JSON строку - local function table_to_json(tbl) - local result = "{" - for k, v in pairs(tbl) do - if type(k) == "string" then - result = result .. string.format('"%s":', k) - else - result = result .. string.format('"%d":', k) - end - - if type(v) == "string" then - result = result .. string.format('"%s"', v) - elseif type(v) == "number" then - result = result .. tostring(v) - elseif type(v) == "boolean" then - result = result .. tostring(v) - elseif type(v) == "table" then - -- Простая обработка массивов - if #v > 0 then - local arr = "[" - for i, val in ipairs(v) do - if i > 1 then arr = arr .. "," end - arr = arr .. string.format('"%s"', val) - end - arr = arr .. "]" - result = result .. arr - else - result = result .. "{}" - end - else - result = result .. "null" - end - - result = result .. "," - end - - -- Убираем последнюю запятую - result = result:sub(1, -2) .. "}" - return result - end - - local test_json = table_to_json(test_data) - local success, doc_id = pcall(function() - return futriix_db.create("test_users", test_json) - end) - - if success then - print("Test document created with ID: " .. tostring(doc_id)) - else - futriix_error("Failed to create test document: " .. doc_id) - end - "#; - - self.execute_script(test_script) - } - - /// Тестирование работы с индексами через Lua - #[allow(dead_code)] - pub fn test_index_operations(&self) -> Result<()> { - let index_script = r#" - -- Тестирование работы с индексами через Lua - print("Testing index operations...") - - -- Создание индекса по полю email - local success, result = pcall(function() - return futriix_db.create_index("test_users", "email_idx", "email", true) - end) - - if success then - print("Index created successfully") - else - futriix_error("Failed to create index: " .. result) - end - - -- Создание не-уникального индекса по полю age - success, result = pcall(function() - return futriix_db.create_index("test_users", "age_idx", "age", false) - end) - - if success then - print("Non-unique index created successfully") - else - futriix_error("Failed to create non-unique index: " .. result) - end - "#; - - self.execute_script(index_script) - } - - /// Тестирование транзакций через Lua - #[allow(dead_code)] - pub fn test_transaction_operations(&self) -> Result<()> { - let transaction_script = r#" - -- Тестирование транзакций через Lua - print("Testing transaction operations...") - - local transaction_id = "tx_test_" .. futriix_generate_uuid() - - -- Начало транзакции - local success, result = pcall(function() - return futriix_db.begin_transaction(transaction_id) - end) - - if not success then - futriix_error("Failed to begin transaction: " .. result) - return - end - - print("Transaction started: " .. transaction_id) - - -- Добавление нескольких операций в транзакцию - local operations = { - {collection = "test_users", data = '{"name": "Alice", "email": "alice@example.com", "age": 25}'}, - {collection = "test_users", data = '{"name": "Bob", "email": "bob@example.com", "age": 30}'}, - {collection = "test_users", data = '{"name": "Charlie", "email": "charlie@example.com", "age": 35}'} - } - - local all_success = true - for i, op in ipairs(operations) do - success, result = pcall(function() - return futriix_db.create(op.collection, op.data) - end) - - if not success then - futriix_error("Failed to create document " .. i .. ": " .. result) - all_success = false - break - end - end - - if all_success then - -- Коммит транзакции - success, result = pcall(function() - return futriix_db.commit_transaction(transaction_id) - end) - - if success then - print("Transaction committed successfully") - else - futriix_error("Failed to commit transaction: " .. result) - end - else - -- Откат транзакции - success, result = pcall(function() - return futriix_db.rollback_transaction(transaction_id) - end) - - if success then - print("Transaction rolled back due to errors") - else - futriix_error("Failed to rollback transaction: " .. result) - end - end - "#; - - self.execute_script(transaction_script) - } - - /// Получение статистики выполнения скриптов (заглушка) - #[allow(dead_code)] - pub fn get_execution_stats(&self) -> Result<()> { - // Здесь можно собирать статистику выполнения скриптов - // В текущей реализации просто возвращаем Ok - Ok(()) - } - - /// Проверка синтаксиса Lua скрипта без выполнения - #[allow(dead_code)] - pub fn validate_script_syntax(&self, script: &str) -> Result<()> { - let lua = self.lua.clone(); - // Пытаемся загрузить скрипт без выполнения - lua.load(script).into_function()?; - Ok(()) - } - - /// Создание защищенного окружения для выполнения ненадежных скриптов - #[allow(dead_code)] - pub fn create_sandboxed_environment(&self) -> Result<()> { - // Здесь можно создать изолированное окружение для ненадежных скриптов - // В текущей реализации используется основное окружение - Ok(()) - } - - /// Очистка кэша Lua (если потребуется в будущем) - #[allow(dead_code)] - pub fn clear_cache(&self) -> Result<()> { - // Lua rlua не имеет явного кэша, но можно пересоздать движок - println!("Lua cache cleared (engine would be recreated if needed)"); - Ok(()) - } -}