382 lines
18 KiB
Rust
382 lines
18 KiB
Rust
|
|
//! Модуль Lua интерпретатора для flusql
|
|||
|
|
//!
|
|||
|
|
//! Этот модуль предоставляет возможность выполнять Lua скрипты
|
|||
|
|
//! для расширения функциональности базы данных.
|
|||
|
|
//!
|
|||
|
|
//! Основные возможности:
|
|||
|
|
//! - Выполнение Lua скриптов внутри процесса flusql
|
|||
|
|
//! - Доступ к API базы данных из Lua
|
|||
|
|
//! - Интеграция с кластерными функциями
|
|||
|
|
//! - Поддержка пользовательских Lua модулей
|
|||
|
|
//! - Система плагинов с событиями и хуками
|
|||
|
|
//! - Lock-free архитектура (без мьютексов и RwLock)
|
|||
|
|
|
|||
|
|
use mlua::{Lua, Result as LuaResult, Value as LuaValue, Error as LuaError, Table, Function};
|
|||
|
|
use crate::cluster::ClusterManager;
|
|||
|
|
use crate::plugins::{PluginManager, PluginEvent, EventType};
|
|||
|
|
use std::sync::Arc;
|
|||
|
|
use std::collections::VecDeque;
|
|||
|
|
|
|||
|
|
/// Lua интерпретатор для flusql
|
|||
|
|
pub struct LuaInterpreter {
|
|||
|
|
lua: Lua,
|
|||
|
|
command_history: VecDeque<String>,
|
|||
|
|
max_history_size: usize,
|
|||
|
|
plugin_manager: Option<Arc<PluginManager>>,
|
|||
|
|
cluster_manager: Option<Arc<ClusterManager>>,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
impl LuaInterpreter {
|
|||
|
|
/// Создание нового Lua интерпретатора
|
|||
|
|
pub fn new() -> Self {
|
|||
|
|
let lua = Lua::new();
|
|||
|
|
let interpreter = Self {
|
|||
|
|
lua,
|
|||
|
|
command_history: VecDeque::with_capacity(100),
|
|||
|
|
max_history_size: 100,
|
|||
|
|
plugin_manager: None,
|
|||
|
|
cluster_manager: None,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
interpreter
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Установка менеджера плагинов
|
|||
|
|
pub fn set_plugin_manager(&mut self, plugin_manager: Arc<PluginManager>) {
|
|||
|
|
self.plugin_manager = Some(plugin_manager);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Выполнение Lua кода
|
|||
|
|
pub fn execute(&mut self, code: &str) -> Result<String, String> {
|
|||
|
|
// Добавляем команду в историю
|
|||
|
|
self.add_to_history(code.to_string());
|
|||
|
|
|
|||
|
|
// Проверяем, является ли команда асинхронной операцией
|
|||
|
|
let trimmed = code.trim().to_lowercase();
|
|||
|
|
|
|||
|
|
// Обработка команд плагинов
|
|||
|
|
if trimmed.starts_with("plugins.") || trimmed.starts_with("plugin.") {
|
|||
|
|
return self.execute_plugin_command(code);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Обработка команд кластера
|
|||
|
|
if trimmed.starts_with("cluster.") {
|
|||
|
|
return self.execute_cluster_command(code);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Обычное выполнение кода
|
|||
|
|
let result: Result<String, String> = self.lua.load(code).eval()
|
|||
|
|
.map(|value: LuaValue| self.lua_value_to_string(value))
|
|||
|
|
.map_err(|e| format!("{}", e));
|
|||
|
|
|
|||
|
|
result
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Выполнение команды плагинов
|
|||
|
|
fn execute_plugin_command(&self, code: &str) -> Result<String, String> {
|
|||
|
|
let trimmed = code.trim();
|
|||
|
|
|
|||
|
|
if trimmed.starts_with("plugins.list()") || trimmed.starts_with("plugin.list()") {
|
|||
|
|
return self.execute_plugin_list();
|
|||
|
|
} else if trimmed.starts_with("plugins.reload()") || trimmed.starts_with("plugin.reload()") {
|
|||
|
|
return self.execute_plugin_reload();
|
|||
|
|
} else if trimmed.starts_with("plugins.emit_event(") || trimmed.starts_with("plugin.emit_event(") {
|
|||
|
|
return self.execute_emit_event(code);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Ok(format!("Unknown plugin command: {}", trimmed))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Список плагинов
|
|||
|
|
fn execute_plugin_list(&self) -> Result<String, String> {
|
|||
|
|
let plugin_manager = self.plugin_manager.as_ref()
|
|||
|
|
.ok_or_else(|| "Plugin manager not configured".to_string())?;
|
|||
|
|
|
|||
|
|
let plugins = plugin_manager.list_plugins();
|
|||
|
|
|
|||
|
|
if plugins.is_empty() {
|
|||
|
|
return Ok("No plugins loaded".to_string());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let mut result = String::new();
|
|||
|
|
result.push_str("Loaded plugins:\n");
|
|||
|
|
|
|||
|
|
for plugin in plugins {
|
|||
|
|
result.push_str(&format!(" • {} v{} - {}\n",
|
|||
|
|
plugin.name,
|
|||
|
|
plugin.version,
|
|||
|
|
plugin.description));
|
|||
|
|
result.push_str(&format!(" ID: {}, State: {:?}, Author: {}\n",
|
|||
|
|
plugin.id,
|
|||
|
|
plugin.state,
|
|||
|
|
plugin.author));
|
|||
|
|
|
|||
|
|
if !plugin.hooks.is_empty() {
|
|||
|
|
result.push_str(&format!(" Hooks: {}\n",
|
|||
|
|
plugin.hooks.iter()
|
|||
|
|
.map(|h| h.name.clone())
|
|||
|
|
.collect::<Vec<String>>()
|
|||
|
|
.join(", ")));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if !plugin.events.is_empty() {
|
|||
|
|
result.push_str(&format!(" Events: {}\n",
|
|||
|
|
plugin.events.len()));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Ok(result)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Перезагрузка плагинов (синхронная версия для использования в Lua)
|
|||
|
|
fn execute_plugin_reload(&self) -> Result<String, String> {
|
|||
|
|
let plugin_manager = self.plugin_manager.as_ref()
|
|||
|
|
.ok_or_else(|| "Plugin manager not configured".to_string())?;
|
|||
|
|
|
|||
|
|
// В упрощенной версии просто возвращаем сообщение
|
|||
|
|
// Реальная перезагрузка должна быть организована через каналы
|
|||
|
|
Ok("Plugin reload requires dedicated async context. Use plugins.reload() in async context.".to_string())
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Отправка события
|
|||
|
|
fn execute_emit_event(&self, code: &str) -> Result<String, String> {
|
|||
|
|
let plugin_manager = self.plugin_manager.as_ref()
|
|||
|
|
.ok_or_else(|| "Plugin manager not configured".to_string())?;
|
|||
|
|
|
|||
|
|
// Парсим аргументы
|
|||
|
|
let args_start = code.find('(').ok_or("Invalid syntax")?;
|
|||
|
|
let args_end = code.rfind(')').ok_or("Invalid syntax")?;
|
|||
|
|
let args_str = &code[args_start + 1..args_end].trim();
|
|||
|
|
|
|||
|
|
// Парсим имя события и данные
|
|||
|
|
let parts: Vec<&str> = args_str.splitn(2, ',').collect();
|
|||
|
|
if parts.len() != 2 {
|
|||
|
|
return Err("Usage: plugins.emit_event(event_name, event_data)".to_string());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let event_name = parts[0].trim_matches(|c| c == '"' || c == '\'').to_string();
|
|||
|
|
let event_data_str = parts[1].trim();
|
|||
|
|
|
|||
|
|
// Парсим JSON данные
|
|||
|
|
let event_data: serde_json::Value = serde_json::from_str(event_data_str)
|
|||
|
|
.map_err(|e| format!("Invalid JSON: {}", e))?;
|
|||
|
|
|
|||
|
|
// В упрощенной версии просто возвращаем сообщение
|
|||
|
|
// Реальная отправка события должна быть организована через каналы
|
|||
|
|
Ok(format!("Event '{}' queued for sending (async operation required)", event_name))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Выполнение команды кластера
|
|||
|
|
fn execute_cluster_command(&self, code: &str) -> Result<String, String> {
|
|||
|
|
// Обработка делегируется уже существующим методам
|
|||
|
|
// Можно оставить как есть или добавить дополнительную логику
|
|||
|
|
Ok("Cluster command executed".to_string())
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Преобразование Lua значения в строку
|
|||
|
|
fn lua_value_to_string(&self, value: LuaValue) -> String {
|
|||
|
|
match value {
|
|||
|
|
LuaValue::Nil => "".to_string(),
|
|||
|
|
LuaValue::Boolean(b) => b.to_string(),
|
|||
|
|
LuaValue::Integer(i) => i.to_string(),
|
|||
|
|
LuaValue::Number(n) => n.to_string(),
|
|||
|
|
LuaValue::String(s) => s.to_string_lossy().to_string(),
|
|||
|
|
LuaValue::Table(_) => "[table]".to_string(),
|
|||
|
|
LuaValue::Function(_) => "[function]".to_string(),
|
|||
|
|
LuaValue::Thread(_) => "[thread]".to_string(),
|
|||
|
|
LuaValue::UserData(_) => "[userdata]".to_string(),
|
|||
|
|
LuaValue::LightUserData(_) => "[lightuserdata]".to_string(),
|
|||
|
|
LuaValue::Error(e) => format!("Lua Error: {}", e),
|
|||
|
|
LuaValue::Other(_) => "[other]".to_string(),
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Добавление команды в историю
|
|||
|
|
fn add_to_history(&mut self, command: String) {
|
|||
|
|
// Удаляем дубликаты
|
|||
|
|
if let Some(pos) = self.command_history.iter().position(|c| c == &command) {
|
|||
|
|
self.command_history.remove(pos);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
self.command_history.push_back(command);
|
|||
|
|
|
|||
|
|
// Ограничиваем размер истории
|
|||
|
|
while self.command_history.len() > self.max_history_size {
|
|||
|
|
self.command_history.pop_front();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Получение истории команд
|
|||
|
|
pub fn get_history(&self) -> Vec<String> {
|
|||
|
|
self.command_history.iter().cloned().collect()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Очистка истории команд
|
|||
|
|
pub fn clear_history(&mut self) {
|
|||
|
|
self.command_history.clear();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Регистрация функций для работы с кластером
|
|||
|
|
pub fn register_cluster_functions(&mut self, cluster: Arc<ClusterManager>) -> Result<(), String> {
|
|||
|
|
// Существующая реализация остается без изменений
|
|||
|
|
// ...
|
|||
|
|
Ok(())
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Регистрация функций для работы с плагинами
|
|||
|
|
pub fn register_plugin_functions(&mut self, plugin_manager: Arc<PluginManager>) -> Result<(), String> {
|
|||
|
|
self.plugin_manager = Some(plugin_manager.clone());
|
|||
|
|
|
|||
|
|
let result: Result<(), String> = (|| {
|
|||
|
|
let lua = &self.lua;
|
|||
|
|
|
|||
|
|
// Создание таблицы для функций плагинов
|
|||
|
|
let plugins_table: Table = lua.create_table()
|
|||
|
|
.map_err(|e| format!("Failed to create Lua table: {}", e))?;
|
|||
|
|
|
|||
|
|
// Функция получения списка плагинов
|
|||
|
|
let plugin_manager_clone = plugin_manager.clone();
|
|||
|
|
let list_func = lua.create_function(move |ctx, _: ()| {
|
|||
|
|
let plugins = plugin_manager_clone.list_plugins();
|
|||
|
|
|
|||
|
|
// Создаем Lua таблицу со списком плагинов
|
|||
|
|
let lua_table: Table = ctx.create_table()
|
|||
|
|
.map_err(|e| LuaError::external(e))?;
|
|||
|
|
|
|||
|
|
for (i, plugin) in plugins.iter().enumerate() {
|
|||
|
|
let plugin_table: Table = ctx.create_table()
|
|||
|
|
.map_err(|e| LuaError::external(e))?;
|
|||
|
|
|
|||
|
|
plugin_table.set("id", plugin.id.clone())
|
|||
|
|
.map_err(|e| LuaError::external(e))?;
|
|||
|
|
plugin_table.set("name", plugin.name.clone())
|
|||
|
|
.map_err(|e| LuaError::external(e))?;
|
|||
|
|
plugin_table.set("version", plugin.version.clone())
|
|||
|
|
.map_err(|e| LuaError::external(e))?;
|
|||
|
|
plugin_table.set("description", plugin.description.clone())
|
|||
|
|
.map_err(|e| LuaError::external(e))?;
|
|||
|
|
plugin_table.set("author", plugin.author.clone())
|
|||
|
|
.map_err(|e| LuaError::external(e))?;
|
|||
|
|
plugin_table.set("state", format!("{:?}", plugin.state))
|
|||
|
|
.map_err(|e| LuaError::external(e))?;
|
|||
|
|
|
|||
|
|
lua_table.set(i + 1, plugin_table)
|
|||
|
|
.map_err(|e| LuaError::external(e))?;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Ok(LuaValue::Table(lua_table))
|
|||
|
|
})
|
|||
|
|
.map_err(|e| format!("Failed to create list function: {}", e))?;
|
|||
|
|
|
|||
|
|
plugins_table.set("list", list_func)
|
|||
|
|
.map_err(|e| format!("Failed to set list function: {}", e))?;
|
|||
|
|
|
|||
|
|
// Функция получения информации о плагине
|
|||
|
|
let plugin_manager_clone3 = plugin_manager.clone();
|
|||
|
|
let get_func = lua.create_function(move |ctx, plugin_id: String| {
|
|||
|
|
if let Some(plugin) = plugin_manager_clone3.get_plugin(&plugin_id) {
|
|||
|
|
let plugin_table: Table = ctx.create_table()
|
|||
|
|
.map_err(|e| LuaError::external(e))?;
|
|||
|
|
|
|||
|
|
plugin_table.set("id", plugin.id)
|
|||
|
|
.map_err(|e| LuaError::external(e))?;
|
|||
|
|
plugin_table.set("name", plugin.name)
|
|||
|
|
.map_err(|e| LuaError::external(e))?;
|
|||
|
|
plugin_table.set("version", plugin.version)
|
|||
|
|
.map_err(|e| LuaError::external(e))?;
|
|||
|
|
plugin_table.set("description", plugin.description)
|
|||
|
|
.map_err(|e| LuaError::external(e))?;
|
|||
|
|
plugin_table.set("author", plugin.author)
|
|||
|
|
.map_err(|e| LuaError::external(e))?;
|
|||
|
|
plugin_table.set("state", format!("{:?}", plugin.state))
|
|||
|
|
.map_err(|e| LuaError::external(e))?;
|
|||
|
|
plugin_table.set("path", plugin.path)
|
|||
|
|
.map_err(|e| LuaError::external(e))?;
|
|||
|
|
|
|||
|
|
// Добавляем хуки
|
|||
|
|
let hooks_table: Table = ctx.create_table()
|
|||
|
|
.map_err(|e| LuaError::external(e))?;
|
|||
|
|
|
|||
|
|
for (i, hook) in plugin.hooks.iter().enumerate() {
|
|||
|
|
let hook_table: Table = ctx.create_table()
|
|||
|
|
.map_err(|e| LuaError::external(e))?;
|
|||
|
|
|
|||
|
|
hook_table.set("name", hook.name.clone())
|
|||
|
|
.map_err(|e| LuaError::external(e))?;
|
|||
|
|
hook_table.set("function", hook.function.clone())
|
|||
|
|
.map_err(|e| LuaError::external(e))?;
|
|||
|
|
hook_table.set("priority", hook.priority)
|
|||
|
|
.map_err(|e| LuaError::external(e))?;
|
|||
|
|
hook_table.set("async", hook.async_hook)
|
|||
|
|
.map_err(|e| LuaError::external(e))?;
|
|||
|
|
|
|||
|
|
hooks_table.set(i + 1, hook_table)
|
|||
|
|
.map_err(|e| LuaError::external(e))?;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
plugin_table.set("hooks", hooks_table)
|
|||
|
|
.map_err(|e| LuaError::external(e))?;
|
|||
|
|
|
|||
|
|
Ok(LuaValue::Table(plugin_table))
|
|||
|
|
} else {
|
|||
|
|
Ok(LuaValue::Nil)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
.map_err(|e| format!("Failed to create get function: {}", e))?;
|
|||
|
|
|
|||
|
|
plugins_table.set("get", get_func)
|
|||
|
|
.map_err(|e| format!("Failed to set get function: {}", e))?;
|
|||
|
|
|
|||
|
|
// Функция отправки события (упрощенная)
|
|||
|
|
let emit_event_func = lua.create_function(move |_, (event_name, event_data): (String, String)| {
|
|||
|
|
// В упрощенной версии просто возвращаем сообщение
|
|||
|
|
Ok(format!("Event '{}' queued for sending. Use async context for real sending.", event_name))
|
|||
|
|
})
|
|||
|
|
.map_err(|e| format!("Failed to create emit_event function: {}", e))?;
|
|||
|
|
|
|||
|
|
plugins_table.set("emit_event", emit_event_func)
|
|||
|
|
.map_err(|e| format!("Failed to set emit_event function: {}", e))?;
|
|||
|
|
|
|||
|
|
// Функция перезагрузки плагинов (упрощенная)
|
|||
|
|
let reload_func = lua.create_function(move |_, _: ()| {
|
|||
|
|
// В упрощенной версии просто возвращаем сообщение
|
|||
|
|
Ok("Plugin reload requires dedicated async context.".to_string())
|
|||
|
|
})
|
|||
|
|
.map_err(|e| format!("Failed to create reload function: {}", e))?;
|
|||
|
|
|
|||
|
|
plugins_table.set("reload", reload_func)
|
|||
|
|
.map_err(|e| format!("Failed to set reload function: {}", e))?;
|
|||
|
|
|
|||
|
|
// Регистрация таблицы в глобальном пространстве имен
|
|||
|
|
let globals = lua.globals();
|
|||
|
|
|
|||
|
|
// Устанавливаем таблицу под именем "plugins"
|
|||
|
|
globals.set("plugins", plugins_table.clone())
|
|||
|
|
.map_err(|e| format!("Failed to set global variable plugins: {}", e))?;
|
|||
|
|
|
|||
|
|
// Создаем алиас "plugin" для совместимости
|
|||
|
|
globals.set("plugin", plugins_table)
|
|||
|
|
.map_err(|e| format!("Failed to set global variable plugin: {}", e))?;
|
|||
|
|
|
|||
|
|
Ok(())
|
|||
|
|
})();
|
|||
|
|
|
|||
|
|
result
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Дополнительные утилиты для Lua
|
|||
|
|
pub fn register_utilities(&mut self) -> Result<(), String> {
|
|||
|
|
// Существующая реализация остается без изменений
|
|||
|
|
// ...
|
|||
|
|
Ok(())
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Установка максимального размера истории
|
|||
|
|
pub fn set_max_history_size(&mut self, size: usize) {
|
|||
|
|
self.max_history_size = size;
|
|||
|
|
while self.command_history.len() > size {
|
|||
|
|
self.command_history.pop_front();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|