futr/src/lua.rs

424 lines
18 KiB
Rust
Raw Normal View History

2025-11-16 20:25:52 +03:00
//[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)),
}
}
}