Delete src/server/lua_engine.rs

This commit is contained in:
Григорий Сафронов 2025-12-11 22:21:14 +00:00
parent 143e90a70b
commit 9f065cb25c

View File

@ -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<Lua>,
}
impl LuaEngine {
/// Создает новый Lua движок с настройками по умолчанию
/// Инициализирует базовое окружение с глобальными функциями Futriix
pub fn new() -> Result<Self> {
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<crate::server::database::Database>,
sharding_manager: Arc<crate::server::sharding::ShardingManager>
) -> 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(())
}
}