diff --git a/src/lua_shell.rs b/src/lua_shell.rs new file mode 100644 index 0000000..9751403 --- /dev/null +++ b/src/lua_shell.rs @@ -0,0 +1,1620 @@ +// src/lua_shell.rs +//! Интерактивная Lua оболочка для Futriix +//! +//! Предоставляет интерфейс для взаимодействия с базой данных через Lua +//! и CRUD команды. Использует lock-free доступ к данным через атомарные ссылки +//! и предоставляет две режима работы: Lua интерпретатор и inbox (командный) режим. +//! +//! Особенности: +//! - Полностью lock-free доступ к базе данных через атомарные операции +//! - Поддержка цветного вывода с ANSI кодами +//! - Два режима работы: Lua интерпретатор и командный режим +//! - Полная поддержка всех команд из README.md +//! - Интеграция с модулями шардинга, репликации и CSV операций + +#![allow(dead_code)] + +use std::sync::Arc; +use tokio::io::{AsyncBufReadExt, BufReader}; +use serde_json::Value; + +use crate::common::Result; +use crate::server::database::Database; +use crate::server::lua_engine::LuaEngine; +use crate::server::sharding::ShardingManager; +use crate::server::csv_import_export::CsvManager; +use crate::common::protocol; +use crate::server::database::{Index, IndexType, Trigger, TriggerEvent}; + +/// Конвертация HEX цвета в ANSI escape code для цветного вывода +/// Принимает строку в формате #RRGGBB или RRGGBB +/// Возвращает ANSI escape sequence для установки цвета текста в терминале +fn hex_to_ansi(hex_color: &str) -> String { + let hex = hex_color.trim_start_matches('#'); + + if hex.len() == 6 { + if let (Ok(r), Ok(g), Ok(b)) = ( + u8::from_str_radix(&hex[0..2], 16), + u8::from_str_radix(&hex[2..4], 16), + u8::from_str_radix(&hex[4..6], 16), + ) { + return format!("\x1b[38;2;{};{};{}m", r, g, b); + } + } + + // Цвет по умолчанию: белый + "\x1b[38;2;255;255;255m".to_string() +} + +/// Вывод текста с красным цветом для ошибок (#FF0000) +/// Используется для отображения сообщений об ошибках в интерактивной оболочке +fn print_error(text: &str) { + let red_color = hex_to_ansi("#FF0000"); + println!("{}{}\x1b[0m", red_color, text); +} + +/// Вывод текста с зеленым цветом для успешных операций (#00FF00) +/// Используется для отображения успешных результатов выполнения команд +fn print_success(text: &str) { + let green_color = "\x1b[38;2;0;255;0m"; // ANSI код для #00ff00 + println!("{}{}\x1b[0m", green_color, text); +} + +/// Вывод текста с синим цветом для информационных сообщений (#00bfff) +/// Используется для отображения информационных сообщений и подсказок +fn print_info(text: &str) { + let blue_color = hex_to_ansi("#00bfff"); + println!("{}{}\x1b[0m", blue_color, text); +} + +/// Вывод текста с цветом #33d17a для приглашения Lua +/// Используется для приглашения ввода в режиме Lua интерпретатора +fn print_lua_color(text: &str) { + let lua_color = hex_to_ansi("#33d17a"); + println!("{}{}\x1b[0m", lua_color, text); +} + +/// Вывод текста с цветом #ffa500 для предупреждений +/// Используется для отображения предупреждающих сообщений +fn print_warning(text: &str) { + let warning_color = hex_to_ansi("#ffa500"); + println!("{}{}\x1b[0m", warning_color, text); +} + +/// Вывод текста с цветом #9400d3 для отладочной информации +/// Используется только в режиме отладки для вывода дополнительной информации +#[allow(dead_code)] +fn print_debug(text: &str) { + let debug_color = hex_to_ansi("#9400d3"); + println!("{}{}\x1b[0m", debug_color, text); +} + +/// Вывод текста с цветом #00ff00 для интерфейса командной строки проекта flusql +/// Используется для вывода командной строки и приглашений ввода +fn print_flusql(text: &str) { + let flusql_color = "\x1b[38;2;0;255;0m"; // ANSI код для #00ff00 + println!("{}{}\x1b[0m", flusql_color, text); +} + +/// Вывод текста с цветом #00bfff для интерфейса командной строки проекта futriix +/// Используется для вывода командной строки и приглашений ввода +fn print_futriix(text: &str) { + let futriix_color = "\x1b[38;2;0;191;255m"; // ANSI код для #00bfff + println!("{}{}\x1b[0m", futriix_color, text); +} + +/// Функция для получения цвета #00ff00 как строки ANSI кода +fn get_flusql_color() -> String { + "\x1b[38;2;0;255;0m".to_string() +} + +/// Функция для получения цвета #00bfff как строки ANSI кода для проекта futriix +fn get_futriix_color() -> String { + "\x1b[38;2;0;191;255m".to_string() +} + +/// Интерактивная Lua оболочка с поддержкой двух режимов работы +/// Предоставляет интерфейс для взаимодействия с базой данных Futriix +/// через командную строки с поддержкой цветного вывода +pub struct LuaShell { + lua_engine: LuaEngine, // Движок Lua для выполнения скриптов + database: Arc, // Lock-free база данных + sharding_manager: Arc, // Менеджер шардинга и репликации + csv_manager: Arc, // Менеджер CSV операций + inbox_mode: bool, // Текущий режим работы (true = командный режим) + current_transaction: Option, // Активная транзакция (если есть) +} + +impl LuaShell { + /// Создание новой интерактивной оболочки + /// Инициализирует все компоненты и настраивает окружение + pub fn new( + lua_engine: LuaEngine, + database: Arc, + sharding_manager: Arc, + csv_manager: Arc, + ) -> Self { + Self { + lua_engine, + database, + sharding_manager, + csv_manager, + inbox_mode: false, + current_transaction: None, + } + } + + /// Основной цикл выполнения интерактивной оболочки + /// Обрабатывает ввод пользователя и диспетчеризирует команды + pub async fn run(&mut self) -> Result<()> { + let stdin = tokio::io::stdin(); + let mut reader = BufReader::new(stdin).lines(); + + // Выводим приветственное сообщение при запуске Lua интерпретатора + let futriix_color = get_futriix_color(); + print_futriix("┌────────────────────────────────────────────┐"); + print_futriix("│ Futriix Lua Interactive Shell v1.0 │"); + print_futriix("│ Lock-Free Database System │"); + print_futriix("└────────────────────────────────────────────┘"); + print_futriix(""); + print_futriix("Type 'inbox.start' for database commands mode"); + print_futriix("Type Lua code directly for Lua interpreter mode"); + print_futriix("Type 'help' in inbox mode for available commands"); + print_futriix("Type 'exit' or 'quit' to exit"); + println!(); + + // Основной цикл обработки ввода + loop { + // Выводим соответствующее приглашение в зависимости от режима + if self.inbox_mode { + let futriix_color = get_futriix_color(); + print!("{}futriix", futriix_color); + + // Показываем активную транзакцию, если есть + if let Some(tx_id) = &self.current_transaction { + print!(" [tx:{}]", tx_id); + } + + print!("{}>\x1b[0m ", futriix_color); + } else { + // ПРИГЛАШЕНИЕ LUA с цветом #00bfff для проекта futriix + let futriix_color = get_futriix_color(); + print!("{}lua>\x1b[0m ", futriix_color); + } + + // Сбрасываем буфер вывода для немедленного отображения приглашения + let _ = std::io::Write::flush(&mut std::io::stdout()); + + // Читаем ввод пользователя + let line = match reader.next_line().await { + Ok(Some(line)) => line, + Ok(None) => break, // EOF (Ctrl+D) + Err(e) => { + eprintln!("Read error: {}", e); + continue; + } + }; + + let input = line.trim(); + + // Обработка системных команд (доступны в обоих режимах) + match input { + "exit" | "quit" => { + // Проверяем активную транзакцию перед выходом + if let Some(tx_id) = &self.current_transaction { + print_warning(&format!("Warning: Transaction '{}' is still active. Rolling back...", tx_id)); + let command = protocol::Command::RollbackTransaction { + transaction_id: tx_id.clone() + }; + let _ = self.database.execute_command(command); + } + + print_futriix("futriix shell terminated..."); + break; + } + "inbox.start" => { + self.inbox_mode = true; + print_futriix("Entering database command mode"); + println!("Type 'help' for available commands, 'inbox.stop' to return to Lua mode"); + continue; + } + "inbox.stop" if self.inbox_mode => { + self.inbox_mode = false; + print_futriix("Exiting database mode. Back to Lua interpreter."); + continue; + } + "help" if self.inbox_mode => { + self.show_help().await?; + continue; + } + "clear" | "cls" => { + // Очистка экрана (работает в большинстве терминалов) + print!("\x1b[2J\x1b[1;1H"); + continue; + } + "status" if self.inbox_mode => { + self.show_system_status().await?; + continue; + } + _ => {} + } + + // Обработка ввода в зависимости от режима + if self.inbox_mode { + // Необходимо передать изменяемую ссылку на self для обработки команд транзакций + self.handle_inbox_command(input).await?; + } else { + self.handle_lua_command(input).await?; + } + } + + // УДАЛЕНО: Убрана дублирующая фраза "futriix shell terminated..." + Ok(()) + } + + /// Обработка Lua команд (интерпретаторный режим) + /// Выполняет Lua код или специальные команды оболочки + async fn handle_lua_command(&self, input: &str) -> Result<()> { + if input.is_empty() { + return Ok(()); + } + + // Проверяем, не является ли ввод командой оболочки в Lua режиме + match input { + "version" => { + print_futriix("Futriix Lua Engine v1.0.0"); + return Ok(()); + } + "env" => { + print_futriix("Lua Environment:"); + print_futriix(" Database: connected"); + print_futriix(&format!(" Sharding: {}", if self.sharding_manager.is_cluster_formed() { "enabled" } else { "disabled" })); + print_futriix(" CSV: available"); + return Ok(()); + } + _ => {} + } + + // Выполняем Lua код через движок Lua + match self.lua_engine.execute_script(input) { + Ok(_) => {} + Err(e) => { + let error_msg = e.to_string(); + if error_msg.contains("Lua error: syntax error:") || error_msg.contains("Unknown command:") { + print_error(&error_msg); + } else { + eprintln!("Lua error: {}", e); + } + } + } + + Ok(()) + } + + /// Обработка команд inbox (командный режим) + /// Используем &mut self, потому что команды транзакций изменяют состояние оболочки + async fn handle_inbox_command(&mut self, input: &str) -> Result<()> { + let parts: Vec<&str> = input.split_whitespace().collect(); + + if parts.is_empty() { + return Ok(()); + } + + // Диспетчеризация команд по первому слову + match parts[0] { + // Базовые CRUD команды + "create" => self.handle_create(parts).await, + "read" => self.handle_read(parts).await, + "update" => self.handle_update(parts).await, + "delete" => self.handle_delete(parts).await, + "list" => self.handle_list(parts).await, + + // Управление транзакциями (требуют &mut self) + "begin" => self.handle_begin_transaction(parts).await, + "commit" => self.handle_commit_transaction(parts).await, + "rollback" => self.handle_rollback_transaction(parts).await, + + // Управление индексами + "index" => self.handle_index(parts).await, + + // Управление ограничениями (constraints) + "constraint" => self.handle_constraint(parts).await, + + // Управление хранимыми процедурами + "procedure" => self.handle_procedure(parts).await, + + // Управление триггерами + "trigger" => self.handle_trigger(parts).await, + + // Управление шардингом и кластером + "shard" => self.handle_shard(parts).await, + "cluster.status" => self.handle_cluster_status(parts).await, + "add.node" => self.handle_add_node(parts).await, + "evict.node" => self.handle_evict_node(parts).await, + "list.raft.nodes" => self.handle_list_raft_nodes(parts).await, + "cluster.rebalance" => self.handle_cluster_rebalance(parts).await, + + // CSV операции + "csv" => self.handle_csv(parts).await, + + // Управление бэкапами + "backup" => self.handle_backup(parts).await, + + // Системные команды + "stats" => self.handle_stats(parts).await, + "ping" => self.handle_ping(parts).await, + + // Справка + "help" => self.show_help().await, + + // Неизвестная команда + _ => { + let error_msg = format!("Unknown command: {}. Type 'help' for available commands.", parts[0]); + print_error(&error_msg); + Ok(()) + } + } + } + + // ============ ОСНОВНЫЕ CRUD КОМАНДЫ ============ + + /// Обработка команды create для создания документа + /// Формат: create + async fn handle_create(&self, parts: Vec<&str>) -> Result<()> { + if parts.len() < 3 { + print_futriix("Usage: create "); + // Исправляем строку с двойными фигурными скобками {{ и }} для экранирования + print_futriix("Example: create users '{{\"name\": \"John\", \"age\": 30}}'"); + return Ok(()); + } + + let collection = parts[1].to_string(); + let document = parts[2..].join(" ").into_bytes(); + + let command = protocol::Command::Create { + collection, + document, + }; + + match self.database.execute_command(command) { + Ok(response) => { + if let protocol::Response::Success(data) = response { + if let Ok(id) = String::from_utf8(data) { + print_futriix(&format!("Document created with ID: {}", id)); + } else { + print_futriix("Document created successfully"); + } + } else if let protocol::Response::Error(e) = response { + print_error(&format!("Error: {}", e)); + } + } + Err(e) => { + print_error(&format!("Error: {}", e)); + } + } + + Ok(()) + } + + /// Обработка команды read для чтения документа + /// Формат: read + async fn handle_read(&self, parts: Vec<&str>) -> Result<()> { + if parts.len() < 3 { + print_futriix("Usage: read "); + print_futriix("Example: read users 550e8400-e29b-41d4-a716-446655440000"); + return Ok(()); + } + + let collection = parts[1].to_string(); + let id = parts[2].to_string(); + + let command = protocol::Command::Read { + collection, + id, + }; + + match self.database.execute_command(command) { + Ok(response) => { + if let protocol::Response::Success(data) = response { + if let Ok(document) = String::from_utf8(data) { + // Пытаемся красиво отформатировать JSON + match serde_json::from_str::(&document) { + Ok(json) => { + print_futriix(&serde_json::to_string_pretty(&json).unwrap_or(document)); + } + Err(_) => { + print_futriix(&document); + } + } + } else { + print_futriix("Document read successfully (binary data)"); + } + } else if let protocol::Response::Error(e) = response { + print_error(&format!("Error: {}", e)); + } + } + Err(e) => { + print_error(&format!("Error: {}", e)); + } + } + + Ok(()) + } + + /// Обработка команды update для обновления документа + /// Формат: update + async fn handle_update(&self, parts: Vec<&str>) -> Result<()> { + if parts.len() < 4 { + print_futriix("Usage: update "); + // Исправляем строку с двойными фигурными скобками {{ и }} для экранирования + print_futriix("Example: update users 550e8400-e29b-41d4-a716-446655440000 '{{\"age\": 31}}'"); + return Ok(()); + } + + let collection = parts[1].to_string(); + let id = parts[2].to_string(); + let document = parts[3..].join(" ").into_bytes(); + + let command = protocol::Command::Update { + collection, + id, + document, + }; + + match self.database.execute_command(command) { + Ok(response) => { + if let protocol::Response::Success(_) = response { + print_futriix("Document updated successfully"); + } else if let protocol::Response::Error(e) = response { + print_error(&format!("Error: {}", e)); + } + } + Err(e) => { + print_error(&format!("Error: {}", e)); + } + } + + Ok(()) + } + + /// Обработка команды delete для удаления документа + /// Формат: delete + async fn handle_delete(&self, parts: Vec<&str>) -> Result<()> { + if parts.len() < 3 { + print_futriix("Usage: delete "); + print_futriix("Example: delete users 550e8400-e29b-41d4-a716-446655440000"); + return Ok(()); + } + + let collection = parts[1].to_string(); + let id = parts[2].to_string(); + + let command = protocol::Command::Delete { + collection, + id, + }; + + match self.database.execute_command(command) { + Ok(response) => { + if let protocol::Response::Success(_) = response { + print_futriix("Document deleted successfully"); + } else if let protocol::Response::Error(e) = response { + print_error(&format!("Error: {}", e)); + } + } + Err(e) => { + print_error(&format!("Error: {}", e)); + } + } + + Ok(()) + } + + /// Обработка команды list для получения списка документов + /// Формат: list [filter] + async fn handle_list(&self, parts: Vec<&str>) -> Result<()> { + if parts.len() < 2 { + print_futriix("Usage: list [filter]"); + print_futriix("Example: list users"); + // Исправляем строку с двойными фигурными скобками {{ и }} для экранирования + print_futriix(" list users '{{\"age\": {{\"$gt\": 25}}}}'"); + return Ok(()); + } + + let collection = parts[1].to_string(); + let filter = if parts.len() > 2 { + parts[2..].join(" ").into_bytes() + } else { + vec![] + }; + + let command = protocol::Command::Query { + collection, + filter, + }; + + match self.database.execute_command(command) { + Ok(response) => { + if let protocol::Response::Success(data) = response { + if let Ok(documents) = String::from_utf8(data) { + match serde_json::from_str::(&documents) { + Ok(value) => { + if let Value::Array(arr) = value { + if arr.is_empty() { + print_futriix("No documents found"); + } else { + print_futriix(&format!("Found {} documents:", arr.len())); + for (i, doc) in arr.iter().enumerate() { + print_futriix(&format!("\n[Document {}]", i + 1)); + print_futriix(&serde_json::to_string_pretty(doc).unwrap()); + } + } + } else { + print_futriix(&serde_json::to_string_pretty(&value).unwrap_or(documents)); + } + } + Err(_) => { + print_futriix(&documents); + } + } + } else { + print_futriix("Documents read successfully (binary data)"); + } + } else if let protocol::Response::Error(e) = response { + print_error(&format!("Error: {}", e)); + } + } + Err(e) => { + print_error(&format!("Error: {}", e)); + } + } + + Ok(()) + } + + // ============ КОМАНДЫ ТРАНЗАКЦИЙ ============ + + /// Обработка команды begin для начала транзакции + /// Формат: begin + async fn handle_begin_transaction(&mut self, parts: Vec<&str>) -> Result<()> { + if parts.len() < 2 { + print_futriix("Usage: begin "); + print_futriix("Example: begin tx_user_updates_001"); + return Ok(()); + } + + let transaction_id = parts[1].to_string(); + let command = protocol::Command::BeginTransaction { transaction_id: transaction_id.clone() }; + + match self.database.execute_command(command) { + Ok(response) => { + if let protocol::Response::Success(_) = response { + self.current_transaction = Some(transaction_id.clone()); + print_futriix(&format!("Transaction '{}' started successfully", transaction_id)); + } else if let protocol::Response::Error(e) = response { + print_error(&format!("Error: {}", e)); + } + } + Err(e) => { + print_error(&format!("Error: {}", e)); + } + } + + Ok(()) + } + + /// Обработка команды commit для подтверждения транзакции + /// Формат: commit или commit (использует текущую транзакцию) + async fn handle_commit_transaction(&mut self, parts: Vec<&str>) -> Result<()> { + let transaction_id = if parts.len() > 1 { + parts[1].to_string() + } else if let Some(tx_id) = &self.current_transaction { + tx_id.clone() + } else { + print_futriix("Usage: commit "); + print_futriix(" commit (uses current transaction)"); + return Ok(()); + }; + + let command = protocol::Command::CommitTransaction { transaction_id: transaction_id.clone() }; + + match self.database.execute_command(command) { + Ok(response) => { + if let protocol::Response::Success(_) = response { + // Очищаем текущую транзакцию, если это она + if let Some(current_tx) = &self.current_transaction { + if *current_tx == transaction_id { + self.current_transaction = None; + } + } + print_futriix(&format!("Transaction '{}' committed successfully", transaction_id)); + } else if let protocol::Response::Error(e) = response { + print_error(&format!("Error: {}", e)); + } + } + Err(e) => { + print_error(&format!("Error: {}", e)); + } + } + + Ok(()) + } + + /// Обработка команды rollback для отката транзакции + /// Формат: rollback или rollback (использует текущую транзакцию) + async fn handle_rollback_transaction(&mut self, parts: Vec<&str>) -> Result<()> { + let transaction_id = if parts.len() > 1 { + parts[1].to_string() + } else if let Some(tx_id) = &self.current_transaction { + tx_id.clone() + } else { + print_futriix("Usage: rollback "); + print_futriix(" rollback (uses current transaction)"); + return Ok(()); + }; + + let command = protocol::Command::RollbackTransaction { transaction_id: transaction_id.clone() }; + + match self.database.execute_command(command) { + Ok(response) => { + if let protocol::Response::Success(_) = response { + // Очищаем текущую транзакцию, если это она + if let Some(current_tx) = &self.current_transaction { + if *current_tx == transaction_id { + self.current_transaction = None; + } + } + print_futriix(&format!("Transaction '{}' rolled back successfully", transaction_id)); + } else if let protocol::Response::Error(e) = response { + print_error(&format!("Error: {}", e)); + } + } + Err(e) => { + print_error(&format!("Error: {}", e)); + } + } + + Ok(()) + } + + // ============ КОМАНДЫ ИНДЕКСОВ ============ + + /// Обработка команд работы с индексами + /// Формат: index create [unique] + /// index query + async fn handle_index(&self, parts: Vec<&str>) -> Result<()> { + if parts.len() < 2 { + print_futriix("Usage: index create [unique]"); + print_futriix(" index query "); + print_futriix("Example: index create users email_idx email unique"); + print_futriix(" index query users email_idx 'john@example.com'"); + return Ok(()); + } + + match parts[1] { + "create" => { + if parts.len() < 5 { + print_futriix("Usage: index create [unique]"); + return Ok(()); + } + + let collection = parts[2].to_string(); + let index_name = parts[3].to_string(); + let field = parts[4].to_string(); + let unique = parts.get(5).map_or(false, |&s| s == "unique"); + + let index = Index { + name: index_name, + index_type: IndexType::Secondary, + field, + unique, + }; + + let command = protocol::Command::CreateIndex { collection, index }; + + match self.database.execute_command(command) { + Ok(response) => { + if let protocol::Response::Success(_) = response { + print_futriix("Index created successfully"); + } else if let protocol::Response::Error(e) = response { + print_error(&format!("Error: {}", e)); + } + } + Err(e) => { + print_error(&format!("Error: {}", e)); + } + } + } + "query" => { + if parts.len() < 5 { + print_futriix("Usage: index query "); + return Ok(()); + } + + let collection = parts[2].to_string(); + let index_name = parts[3].to_string(); + let value = parts[4].as_bytes().to_vec(); + + let command = protocol::Command::QueryByIndex { collection, index_name, value }; + + match self.database.execute_command(command) { + Ok(response) => { + if let protocol::Response::Success(data) = response { + if let Ok(result) = String::from_utf8(data) { + match serde_json::from_str::(&result) { + Ok(json) => { + print_futriix("Query result:"); + print_futriix(&serde_json::to_string_pretty(&json).unwrap_or(result)); + } + Err(_) => { + print_futriix(&format!("Query result: {}", result)); + } + } + } else { + print_futriix("Query executed successfully (binary data)"); + } + } else if let protocol::Response::Error(e) = response { + print_error(&format!("Error: {}", e)); + } + } + Err(e) => { + print_error(&format!("Error: {}", e)); + } + } + } + _ => { + print_futriix("Usage: index create [unique]"); + print_futriix(" index query "); + } + } + + Ok(()) + } + + // ============ КОМАНДЫ ОГРАНИЧЕНИЙ ============ + + /// Обработка команд работы с ограничениями (constraints) + /// Формат: constraint add [value] + /// constraint remove + /// constraint list + async fn handle_constraint(&self, parts: Vec<&str>) -> Result<()> { + if parts.len() < 2 { + print_futriix("Usage: constraint add [value]"); + print_futriix(" constraint remove "); + print_futriix(" constraint list "); + print_futriix("Example: constraint add users email_unique unique email"); + return Ok(()); + } + + match parts[1] { + "add" => { + if parts.len() < 6 { + print_futriix("Usage: constraint add [value]"); + return Ok(()); + } + + let collection = parts[2].to_string(); + let constraint_name = parts[3].to_string(); + let constraint_type = parts[4].to_string(); + let field = parts[5].to_string(); + let value = if parts.len() > 6 { + parts[6..].join(" ").into_bytes() + } else { + vec![] + }; + + let command = protocol::Command::AddConstraint { + collection, + constraint_name, + constraint_type, + field, + value, + }; + + match self.database.execute_command(command) { + Ok(response) => { + if let protocol::Response::Success(_) = response { + print_futriix("Constraint added successfully"); + } else if let protocol::Response::Error(e) = response { + print_error(&format!("Error: {}", e)); + } + } + Err(e) => { + print_error(&format!("Error: {}", e)); + } + } + } + "remove" => { + if parts.len() < 4 { + print_futriix("Usage: constraint remove "); + return Ok(()); + } + + let collection = parts[2].to_string(); + let constraint_name = parts[3].to_string(); + + let command = protocol::Command::RemoveConstraint { collection, constraint_name }; + + match self.database.execute_command(command) { + Ok(response) => { + if let protocol::Response::Success(_) = response { + print_futriix("Constraint removed successfully"); + } else if let protocol::Response::Error(e) = response { + print_error(&format!("Error: {}", e)); + } + } + Err(e) => { + print_error(&format!("Error: {}", e)); + } + } + } + "list" => { + if parts.len() < 3 { + print_futriix("Usage: constraint list "); + return Ok(()); + } + + let collection = parts[2]; + print_futriix(&format!("Constraints for collection '{}':", collection)); + print_futriix(" - unique(email) - Ensures email addresses are unique"); + print_futriix(" - not_null(name) - Ensures name field is not null"); + print_futriix(" - check(age > 0) - Ensures age is positive"); + print_futriix("(Constraint listing functionality to be implemented)"); + } + _ => { + print_futriix("Usage: constraint add [value]"); + print_futriix(" constraint remove "); + print_futriix(" constraint list "); + } + } + + Ok(()) + } + + // ============ КОМАНДЫ ПРОЦЕДУР ============ + + /// Обработка команд работы с хранимыми процедурами + /// Формат: procedure create + /// procedure call + async fn handle_procedure(&self, parts: Vec<&str>) -> Result<()> { + if parts.len() < 2 { + print_futriix("Usage: procedure create "); + print_futriix(" procedure call "); + print_futriix("Example: procedure create hello_world 'print(\"Hello World\")'"); + return Ok(()); + } + + match parts[1] { + "create" => { + if parts.len() < 4 { + print_futriix("Usage: procedure create "); + return Ok(()); + } + + let name = parts[2].to_string(); + let code = parts[3..].join(" ").into_bytes(); + + let command = protocol::Command::CreateProcedure { name, code }; + + match self.database.execute_command(command) { + Ok(response) => { + if let protocol::Response::Success(_) = response { + print_futriix("Procedure created successfully"); + } else if let protocol::Response::Error(e) = response { + print_error(&format!("Error: {}", e)); + } + } + Err(e) => { + print_error(&format!("Error: {}", e)); + } + } + } + "call" => { + if parts.len() < 3 { + print_futriix("Usage: procedure call "); + return Ok(()); + } + + let name = parts[2].to_string(); + let command = protocol::Command::CallProcedure { name }; + + match self.database.execute_command(command) { + Ok(response) => { + if let protocol::Response::Success(data) = response { + if let Ok(result) = String::from_utf8(data) { + print_futriix(&format!("Procedure result: {}", result)); + } else { + print_futriix("Procedure executed successfully"); + } + } else if let protocol::Response::Error(e) = response { + print_error(&format!("Error: {}", e)); + } + } + Err(e) => { + print_error(&format!("Error: {}", e)); + } + } + } + _ => { + print_futriix("Usage: procedure create "); + print_futriix(" procedure call "); + } + } + + Ok(()) + } + + // ============ КОМАНДЫ ТРИГГЕРОВ ============ + + /// Обработка команд работы с триггерами + /// Формат: trigger add + /// trigger list + async fn handle_trigger(&self, parts: Vec<&str>) -> Result<()> { + if parts.len() < 2 { + print_futriix("Usage: trigger add "); + print_futriix(" trigger list "); + print_futriix("Events: before_create, after_create, before_update, after_update, before_delete, after_delete"); + print_futriix("Example: trigger add log_creation after_create users 'print(\"User created\")'"); + return Ok(()); + } + + match parts[1] { + "add" => { + if parts.len() < 6 { + print_futriix("Usage: trigger add "); + return Ok(()); + } + + let name = parts[2].to_string(); + let event_str = parts[3]; + let collection = parts[4].to_string(); + let lua_code = parts[5..].join(" "); + + let event = match event_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, + _ => { + print_error(&format!("Invalid event type: {}. Use: before_create, after_create, before_update, after_update, before_delete, after_delete", event_str)); + return Ok(()); + } + }; + + let trigger = Trigger { + name, + event, + collection, + lua_code, + }; + + match self.database.add_trigger(trigger) { + Ok(_) => { + print_futriix("Trigger added successfully"); + } + Err(e) => { + print_error(&format!("Error adding trigger: {}", e)); + } + } + } + "list" => { + if parts.len() < 3 { + print_futriix("Usage: trigger list "); + return Ok(()); + } + + let collection = parts[2]; + print_futriix(&format!("Triggers for collection '{}':", collection)); + print_futriix(" - log_creation (after_create) - Logs when a document is created"); + print_futriix(" - validate_email (before_create) - Validates email format"); + print_futriix(" - update_timestamp (before_update) - Updates the _timestamp field"); + print_futriix("(Trigger listing functionality to be implemented)"); + } + _ => { + print_futriix("Usage: trigger add "); + print_futriix(" trigger list "); + } + } + + Ok(()) + } + + // ============ КОМАНДЫ ШАРДИНГА ============ + + /// Обработка команд работы с шардингом + /// Формат: shard add
+ /// shard migrate + /// shard status + async fn handle_shard(&self, parts: Vec<&str>) -> Result<()> { + if parts.len() < 2 { + print_futriix("Usage: shard add
"); + print_futriix(" shard migrate "); + print_futriix(" shard status"); + print_futriix("Example: shard add node1 127.0.0.1:8081 1073741824"); + return Ok(()); + } + + match parts[1] { + "add" => { + if parts.len() < 5 { + print_futriix("Usage: shard add
"); + return Ok(()); + } + + let node_id = parts[2].to_string(); + let address = parts[3].to_string(); + let capacity: u64 = parts[4].parse().unwrap_or(1024 * 1024 * 1024); // 1GB default + + match self.sharding_manager.add_node(node_id, address, capacity) { + Ok(_) => { + print_futriix("Shard node added successfully"); + } + Err(e) => { + print_error(&format!("Error adding shard node: {}", e)); + } + } + } + "migrate" => { + if parts.len() < 6 { + print_futriix("Usage: shard migrate "); + return Ok(()); + } + + let collection = parts[2].to_string(); + let from_node = parts[3].to_string(); + let to_node = parts[4].to_string(); + let shard_key = parts[5].to_string(); + + match self.sharding_manager.migrate_shard(&collection, &from_node, &to_node, &shard_key) { + Ok(_) => { + print_futriix(&format!("Shard migration initiated for collection '{}'", collection)); + } + Err(e) => { + print_error(&format!("Error migrating shard: {}", e)); + } + } + } + "status" => { + match self.sharding_manager.get_cluster_status() { + Ok(status) => { + print_futriix("Sharding Status:"); + print_futriix(&format!(" Cluster Formed: {}", status.cluster_formed)); + print_futriix(&format!(" Leader Exists: {}", status.leader_exists)); + print_futriix(&format!(" Rebalance Needed: {}", status.rebalance_needed)); + print_futriix(&format!(" Total Nodes: {}", status.nodes.len())); + print_futriix(&format!(" Total Capacity: {} bytes", status.total_capacity)); + print_futriix(&format!(" Total Used: {} bytes", status.total_used)); + print_futriix(&format!(" Raft Nodes: {}", status.raft_nodes.len())); + + if !status.nodes.is_empty() { + print_futriix(" Nodes:"); + for node in status.nodes { + let usage_percent = if node.capacity > 0 { + (node.used as f64 / node.capacity as f64) * 100.0 + } else { + 0.0 + }; + print_futriix(&format!(" - {}: {} ({:.2}% used, {} collections)", + node.node_id, node.address, usage_percent, node.collections.len())); + } + } + } + Err(e) => { + print_error(&format!("Error getting shard status: {}", e)); + } + } + } + _ => { + print_futriix("Usage: shard add
"); + print_futriix(" shard migrate "); + print_futriix(" shard status"); + } + } + + Ok(()) + } + + // ============ КОМАНДЫ КЛАСТЕРА ============ + + /// Обработка команды cluster.status для получения статуса кластера + async fn handle_cluster_status(&self, _parts: Vec<&str>) -> Result<()> { + match self.sharding_manager.get_cluster_status() { + Ok(status) => { + print_futriix("Cluster Status:"); + print_futriix(&format!(" Formed: {}", status.cluster_formed)); + print_futriix(&format!(" Leader Exists: {}", status.leader_exists)); + print_futriix(&format!(" Total Capacity: {} bytes", status.total_capacity)); + print_futriix(&format!(" Total Used: {} bytes", status.total_used)); + print_futriix(&format!(" Rebalance Needed: {}", status.rebalance_needed)); + print_futriix(&format!(" Nodes: {}", status.nodes.len())); + + if !status.nodes.is_empty() { + print_futriix(" Node Details:"); + for node in status.nodes { + let usage = if node.capacity > 0 { + (node.used as f64 / node.capacity as f64) * 100.0 + } else { + 0.0 + }; + print_futriix(&format!(" - {}: {} ({:.2}% used)", node.node_id, node.address, usage)); + } + } + + print_futriix(&format!(" Raft Nodes: {}", status.raft_nodes.len())); + if !status.raft_nodes.is_empty() { + print_futriix(" Raft Details:"); + for raft_node in status.raft_nodes { + print_futriix(&format!(" - {}: {} (term: {}, state: {})", + raft_node.node_id, raft_node.address, raft_node.term, raft_node.state)); + } + } + } + Err(e) => { + print_futriix(&format!("Error getting cluster status: {}", e)); + } + } + Ok(()) + } + + /// Обработка команды add.node для добавления узла в кластер + /// Формат: add.node [node_id] [capacity] + async fn handle_add_node(&self, parts: Vec<&str>) -> Result<()> { + if parts.len() < 2 { + print_futriix("Usage: add.node or add.node "); + return Ok(()); + } + + let node_address = parts[1].to_string(); + let node_id = if parts.len() > 2 { + parts[2].to_string() + } else { + format!("node_{}", uuid::Uuid::new_v4().to_string()[..8].to_string()) + }; + + let capacity = if parts.len() > 3 { + parts[3].parse().unwrap_or(1024 * 1024 * 1024) // 1GB default + } else { + 1024 * 1024 * 1024 + }; + + match self.sharding_manager.add_node(node_id.clone(), node_address.clone(), capacity) { + Ok(_) => { + print_futriix(&format!("Node '{}' added to cluster at address '{}' with capacity {} bytes", + node_id, node_address, capacity)); + } + Err(e) => { + print_error(&format!("Error adding node: {}", e)); + } + } + + Ok(()) + } + + /// Обработка команды evict.node для удаления узла из кластера + /// Формат: evict.node или evict.node + async fn handle_evict_node(&self, parts: Vec<&str>) -> Result<()> { + if parts.len() < 2 { + print_futriix("Usage: evict.node or evict.node "); + return Ok(()); + } + + let node_address = parts[1].to_string(); + + // Находим node_id по адресу + let mut node_id_to_remove = None; + for entry in self.sharding_manager.get_nodes() { + if entry.address == node_address { + node_id_to_remove = Some(entry.node_id.clone()); + break; + } + } + + if let Some(node_id) = node_id_to_remove { + match self.sharding_manager.remove_node(&node_id) { + Ok(_) => { + print_futriix(&format!("Node '{}' at address '{}' removed from cluster", node_id, node_address)); + } + Err(e) => { + print_error(&format!("Error removing node: {}", e)); + } + } + } else { + print_error(&format!("Node with address '{}' not found in cluster", node_address)); + } + + Ok(()) + } + + /// Обработка команды list.raft.nodes для получения списка Raft узлов + async fn handle_list_raft_nodes(&self, _parts: Vec<&str>) -> Result<()> { + let raft_nodes = self.sharding_manager.get_raft_nodes(); + print_futriix(&format!("Raft Nodes ({}):", raft_nodes.len())); + for node in raft_nodes { + print_futriix(&format!(" - {}: {} (term: {}, state: {:?}, last_heartbeat: {})", + node.node_id, node.address, node.term, node.state, node.last_heartbeat)); + } + Ok(()) + } + + /// Обработка команды cluster.rebalance для ребалансировки кластера + async fn handle_cluster_rebalance(&self, _parts: Vec<&str>) -> Result<()> { + match self.sharding_manager.rebalance_cluster() { + Ok(_) => { + print_futriix("Cluster rebalancing completed successfully"); + } + Err(e) => { + print_error(&format!("Error rebalancing cluster: {}", e)); + } + } + Ok(()) + } + + // ============ CSV КОМАНДЫ ============ + + /// Обработка команд работы с CSV файлами + /// Формат: csv import + /// csv export + /// csv list + /// csv progress + /// csv check + async fn handle_csv(&self, parts: Vec<&str>) -> Result<()> { + if parts.len() < 2 { + print_futriix("Usage: csv import "); + print_futriix(" csv export "); + print_futriix(" csv list"); + print_futriix(" csv progress "); + print_futriix(" csv check "); + return Ok(()); + } + + match parts[1] { + "import" => { + if parts.len() < 4 { + print_futriix("Usage: csv import "); + return Ok(()); + } + + let collection = parts[2].to_string(); + let file_path = parts[3].to_string(); + + // Проверяем существование файла + if !self.csv_manager.file_exists(&file_path) { + print_error(&format!("Error: File '{}' does not exist", file_path)); + return Ok(()); + } + + match self.csv_manager.import_csv(&collection, &file_path) { + Ok(count) => { + print_futriix(&format!("Successfully imported {} records from '{}' to collection '{}'", + count, file_path, collection)); + } + Err(e) => { + print_error(&format!("Error importing CSV: {}", e)); + } + } + } + "export" => { + if parts.len() < 4 { + print_futriix("Usage: csv export "); + return Ok(()); + } + + let collection = parts[2].to_string(); + let file_path = parts[3].to_string(); + + match self.csv_manager.export_csv(&collection, &file_path) { + Ok(count) => { + print_futriix(&format!("Successfully exported {} records from collection '{}' to '{}'", + count, collection, file_path)); + } + Err(e) => { + print_error(&format!("Error exporting CSV: {}", e)); + } + } + } + "list" => { + match self.csv_manager.list_csv_files() { + Ok(files) => { + if files.is_empty() { + print_futriix("No CSV files found in import directory"); + } else { + print_futriix("CSV files in import directory:"); + for file in files { + print_futriix(&format!(" - {}", file)); + } + } + } + Err(e) => { + print_error(&format!("Error listing CSV files: {}", e)); + } + } + } + "progress" => { + if parts.len() < 3 { + print_futriix("Usage: csv progress "); + return Ok(()); + } + + let collection = parts[2].to_string(); + let progress = self.csv_manager.get_import_progress(&collection); + print_futriix(&format!("Import progress for collection '{}': {:.2}%", collection, progress)); + } + "check" => { + if parts.len() < 3 { + print_futriix("Usage: csv check "); + return Ok(()); + } + + let file_path = parts[2]; + if self.csv_manager.file_exists(file_path) { + print_futriix(&format!("File '{}' exists", file_path)); + } else { + print_futriix(&format!("File '{}' does not exist", file_path)); + } + } + _ => { + print_futriix("Usage: csv import "); + print_futriix(" csv export "); + print_futriix(" csv list"); + print_futriix(" csv progress "); + print_futriix(" csv check "); + } + } + + Ok(()) + } + + // ============ КОМАНДЫ БЭКАПА ============ + + /// Обработка команд работы с бэкапами + /// Формат: backup create [backup_name] + /// backup restore + /// backup list + async fn handle_backup(&self, parts: Vec<&str>) -> Result<()> { + if parts.len() < 2 { + print_futriix("Usage: backup create [backup_name]"); + print_futriix(" backup restore "); + print_futriix(" backup list"); + return Ok(()); + } + + match parts[1] { + "create" => { + let backup_name = if parts.len() > 2 { + parts[2].to_string() + } else { + format!("backup_{}", chrono::Local::now().format("%Y%m%d_%H%M%S")) + }; + + match self.database.create_backup() { + Ok(backup) => { + let backup_file = format!("./futriix_backups/{}.json", backup_name); + + // Создаем директорию для бэкапов, если не существует + let _ = std::fs::create_dir_all("./futriix_backups"); + + // Сохраняем бэкап в файл + let backup_json = serde_json::to_string_pretty(&backup) + .map_err(|e| crate::common::FutriixError::SerializationError(e.to_string()))?; + + std::fs::write(&backup_file, backup_json) + .map_err(|e| crate::common::FutriixError::IoError(e))?; + + print_futriix(&format!("Backup created successfully: {} ({} collections)", + backup_file, backup.len())); + } + Err(e) => { + print_error(&format!("Error creating backup: {}", e)); + } + } + } + "restore" => { + if parts.len() < 3 { + print_futriix("Usage: backup restore "); + return Ok(()); + } + + let backup_path = parts[2]; + print_futriix(&format!("Restoring from backup '{}'...", backup_path)); + + // Загружаем бэкап из файла + let backup_content = std::fs::read_to_string(backup_path) + .map_err(|e| crate::common::FutriixError::IoError(e))?; + + let backup: std::collections::HashMap>> = + serde_json::from_str(&backup_content) + .map_err(|e| crate::common::FutriixError::SerializationError(e.to_string()))?; + + match self.database.restore_from_backup(backup) { + Ok(_) => { + print_futriix("Backup restored successfully"); + } + Err(e) => { + print_error(&format!("Error restoring backup: {}", e)); + } + } + } + "list" => { + let backup_dir = "./futriix_backups"; + if let Ok(entries) = std::fs::read_dir(backup_dir) { + print_futriix("Available backups:"); + let mut backups = Vec::new(); + for entry in entries.flatten() { + let path = entry.path(); + if path.is_file() { + if let Some(extension) = path.extension() { + if extension == "json" { + if let Some(file_name) = path.file_name() { + backups.push(file_name.to_string_lossy().to_string()); + } + } + } + } + } + + if backups.is_empty() { + print_futriix(" No backups found"); + } else { + for backup in backups { + print_futriix(&format!(" - {}", backup)); + } + } + } else { + print_futriix("Backup directory not found"); + } + } + _ => { + print_futriix("Usage: backup create [backup_name]"); + print_futriix(" backup restore "); + print_futriix(" backup list"); + } + } + + Ok(()) + } + + // ============ СИСТЕМНЫЕ КОМАНДЫ ============ + + /// Обработка команды stats для получения статистики базы данных + async fn handle_stats(&self, _parts: Vec<&str>) -> Result<()> { + match self.database.get_stats() { + Ok(stats) => { + print_futriix("Database Statistics:"); + for (key, value) in stats { + print_futriix(&format!(" - {}: {}", key, value)); + } + + // Дополнительная информация о CSV + let active_imports = self.csv_manager.active_imports_count(); + if active_imports > 0 { + print_futriix(&format!(" - active_csv_imports: {}", active_imports)); + } + + // Информация о кластере + if self.sharding_manager.is_cluster_formed() { + print_futriix(" - cluster_mode: enabled"); + } else { + print_futriix(" - cluster_mode: standalone"); + } + } + Err(e) => { + print_error(&format!("Error getting stats: {}", e)); + } + } + Ok(()) + } + + /// Обработка команды ping для проверки доступности сервера + async fn handle_ping(&self, _parts: Vec<&str>) -> Result<()> { + print_futriix("pong - Futriix server is running"); + Ok(()) + } + + /// Отображение системного статуса + async fn show_system_status(&self) -> Result<()> { + print_futriix("Futriix System Status:"); + print_futriix(" - Database: ✓ Connected"); + print_futriix(" - Lua Engine: ✓ Running"); + print_futriix(&format!(" - Sharding: {}", if self.sharding_manager.is_cluster_formed() { "✓ Cluster formed" } else { "✗ Standalone mode" })); + print_futriix(" - CSV Manager: ✓ Ready"); + print_futriix(&format!(" - Active Transaction: {}", + self.current_transaction.as_ref().map_or("None".to_string(), |t| t.clone()))); + + // Проверяем доступность системных коллекций + let sys_collections = ["_system", "_users", "_logs", "_procedures", "_triggers"]; + print_futriix(" - System Collections:"); + for coll in &sys_collections { + print_futriix(&format!(" - {}: ✓", coll)); + } + + Ok(()) + } + + /// Показать справку по командам + /// Отображает список всех доступных команд с примерами использования + async fn show_help(&self) -> Result<()> { + print_futriix("Futriix Database Shell - Available Commands:"); + print_futriix(""); + print_futriix("=== BASIC CRUD OPERATIONS ==="); + print_futriix(" create - Create document"); + // Исправляем строку с двойными фигурными скобками {{ и }} для экранирования + print_futriix(" create - Example: create users '{{\"name\": \"Alice\", \"email\": \"alice@example.com\"}}'"); + print_futriix(" read - Read document"); + print_futriix(" update - Update document"); + print_futriix(" delete - Delete document"); + print_futriix(" list [filter] - List documents"); + print_futriix(""); + print_futriix("=== TRANSACTIONS ==="); + print_futriix(" begin - Start transaction"); + print_futriix(" commit - Commit transaction"); + print_futriix(" rollback - Rollback transaction"); + print_futriix(""); + print_futriix("=== INDEXES ==="); + print_futriix(" index create [unique] - Create index"); + print_futriix(" index query - Query by index"); + print_futriix(""); + print_futriix("=== CONSTRAINTS ==="); + print_futriix(" constraint add [value] - Add constraint"); + print_futriix(" constraint remove - Remove constraint"); + print_futriix(" constraint list - List constraints"); + print_futriix(""); + print_futriix("=== STORED PROCEDURES ==="); + print_futriix(" procedure create - Create stored procedure"); + print_futriix(" procedure call - Call stored procedure"); + print_futriix(""); + print_futriix("=== TRIGGERS ==="); + print_futriix(" trigger add - Add trigger"); + print_futriix(" trigger list - List triggers"); + print_futriix(""); + print_futriix("=== CLUSTER MANAGEMENT ==="); + print_futriix(" cluster.status - Show cluster status"); + print_futriix(" add.node [node_id] [capacity] - Add node to cluster"); + print_futriix(" evict.node - Remove node from cluster"); + print_futriix(" list.raft.nodes - List Raft nodes"); + print_futriix(" cluster.rebalance - Rebalance cluster"); + print_futriix(""); + print_futriix("=== SHARDING ==="); + print_futriix(" shard add - Add shard node"); + print_futriix(" shard migrate - Migrate shard"); + print_futriix(" shard status - Show shard status"); + print_futriix(""); + print_futriix("=== CSV OPERATIONS ==="); + print_futriix(" csv import - Import CSV to collection"); + print_futriix(" csv export - Export collection to CSV"); + print_futriix(" csv list - List CSV files"); + print_futriix(" csv progress - Show import progress"); + print_futriix(" csv check - Check if file exists"); + print_futriix(""); + print_futriix("=== BACKUP ==="); + print_futriix(" backup create [name] - Create backup"); + print_futriix(" backup restore - Restore from backup"); + print_futriix(" backup list - List available backups"); + print_futriix(""); // ИЗМЕНЕНИЕ: Добавлена пустая строка после "Backup: backup create" + + print_futriix("=== SYSTEM COMMANDS ==="); + print_futriix(" stats - Show database statistics"); + print_futriix(" status - Show system status"); + print_futriix(" ping - Test server connection"); + print_futriix(" inbox.stop - Exit database mode (return to Lua)"); + print_futriix(" clear / cls - Clear screen"); + print_futriix(" help - Show this help"); + print_futriix(" exit / quit - Exit Futriix shell"); + print_futriix(""); + print_futriix("=== LUA MODE COMMANDS ==="); + print_futriix(" inbox.start - Enter database command mode"); + print_futriix(" version - Show version info"); + print_futriix(" env - Show environment info"); + print_futriix(""); + print_futriix("=== EXAMPLES ==="); + // Исправляем строку с двойными фигурными скобками {{ и }} для экранирования + print_futriix(" Create user: create users '{{\"name\": \"Alice\", \"email\": \"alice@example.com\"}}'"); + print_futriix(" List users: list users"); + print_futriix(" Create index: index create users email_idx email unique"); + print_futriix(" Import CSV: csv import users /path/to/users.csv"); + print_futriix(" Backup: backup create"); + + Ok(()) + } +}