424 lines
18 KiB
Rust
424 lines
18 KiB
Rust
//[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)),
|
||
}
|
||
}
|
||
}
|
||
|