futr/src/lua.rs
2025-11-16 20:25:52 +03:00

424 lines
18 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//[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)),
}
}
}