From 542c8a69e00429ac30fef4b45b64b998268418db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D1=80=D0=B8=D0=B3=D0=BE=D1=80=D0=B8=D0=B9=20=D0=A1?= =?UTF-8?q?=D0=B0=D1=84=D1=80=D0=BE=D0=BD=D0=BE=D0=B2?= Date: Wed, 14 Jan 2026 20:58:37 +0000 Subject: [PATCH] Upload files to "src" --- src/cli.rs | 1819 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1819 insertions(+) create mode 100644 src/cli.rs diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..c4f5e0a --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,1819 @@ +// cli.rs - модуль реализующий REPL-интерфейс в проекте fluSQL +// Инициализирует два режима работв: 'lua' и 'sql' + +use std::collections::VecDeque; +use std::fs::File; +use std::io::{self, Write, Read, BufReader, BufWriter, BufRead}; +use std::path::Path; + +use unicode_width::UnicodeWidthStr; + +use crate::cluster::ClusterManager; +use crate::history::CommandHistory; +use crate::lua::LuaInterpreter; +use crate::lua_mode::LuaModeContext; +use crate::plugins::{PluginManager, PluginConfig}; +use crate::utils::config::Config; +use std::sync::Arc; + +/// Проверка поддержки цветного вывода в терминале +fn supports_color() -> bool { + // Проверяем переменную окружения TERM + if let Ok(term) = std::env::var("TERM") { + // Поддерживаемые терминалы для всех дистрибутивов Linux + let supported_terms = [ + "xterm", "xterm-256color", "screen", "screen-256color", "tmux", "tmux-256color", + "rxvt", "rxvt-unicode", "linux", "vt100", "vt220", "ansi", "dumb" + ]; + + for supported in &supported_terms { + if term.contains(supported) { + return true; + } + } + } + + // Проверяем наличие переменной COLORTERM (используется в современных дистрибутивах) + if std::env::var("COLORTERM").is_ok() { + return true; + } + + // Проверяем, является ли stdout tty + if atty::is(atty::Stream::Stdout) { + // Дополнительные проверки для разных дистрибутивов + if cfg!(target_os = "linux") { + // Проверяем наличие известных окружений рабочего стола + let desktop_env = std::env::var("XDG_CURRENT_DESKTOP").unwrap_or_default(); + let session = std::env::var("DESKTOP_SESSION").unwrap_or_default(); + + // Поддерживаемые окружения + let supported_desktops = [ + "GNOME", "KDE", "XFCE", "LXDE", "LXQt", "MATE", "Cinnamon", "Pantheon", + "Deepin", "Budgie", "Unity", "i3", "awesome", "dwm", "bspwm", "herbstluftwm" + ]; + + for desktop in &supported_desktops { + if desktop_env.contains(desktop) || session.contains(desktop) { + return true; + } + } + } + } + + false +} + +/// Получение кодов цвета для текущего терминала +fn get_color_codes() -> ColorCodes { + if supports_color() { + ColorCodes { + reset: "\x1b[0m", + bold: "\x1b[1m", + faint: "\x1b[2m", + italic: "\x1b[3m", + underline: "\x1b[4m", + blink: "\x1b[5m", + + // Основные цвета + black: "\x1b[30m", + red: "\x1b[31m", + green: "\x1b[32m", + yellow: "\x1b[33m", + blue: "\x1b[34m", + magenta: "\x1b[35m", + cyan: "\x1b[36m", + white: "\x1b[37m", + + // Яркие цвета + bright_black: "\x1b[90m", + bright_red: "\x1b[91m", + bright_green: "\x1b[92m", + bright_yellow: "\x1b[93m", + bright_blue: "\x1b[94m", + bright_magenta: "\x1b[95m", + bright_cyan: "\x1b[96m", + bright_white: "\x1b[97m", + + // Фоны + bg_black: "\x1b[40m", + bg_red: "\x1b[41m", + bg_green: "\x1b[42m", + bg_yellow: "\x1b[43m", + bg_blue: "\x1b[44m", + bg_magenta: "\x1b[45m", + bg_cyan: "\x1b[46m", + bg_white: "\x1b[47m", + + // Яркие фоны + bg_bright_black: "\x1b[100m", + bg_bright_red: "\x1b[101m", + bg_bright_green: "\x1b[102m", + bg_bright_yellow: "\x1b[103m", + bg_bright_blue: "\x1b[104m", + bg_bright_magenta: "\x1b[105m", + bg_bright_cyan: "\x1b[106m", + bg_bright_white: "\x1b[107m", + + // ОСНОВНОЙ ЦВЕТ #00bfff для всего интерфейса flusql + // True color: RGB(0, 191, 255) - яркий голубой/синий + // 256-color: код 39 (яркий голубой) - ближайший к #00bfff + header_color: "\x1b[38;5;39m", // #00bfff для 256-цветных терминалов + header_color_true: "\x1b[38;2;0;191;255m", // #00bfff для true color терминалов + success_color: "\x1b[38;5;39m", // #00bfff для успешных операций + warning_color: "\x1b[38;5;214m", // Оранжевый + error_color: "\x1b[38;5;196m", // Красный + info_color: "\x1b[38;5;39m", // #00bfff для информационных сообщений + accent_color: "\x1b[38;5;39m", // #00bfff как акцентный цвет + + // Специальные коды для отладки + test_true_color: "\x1b[38;2;0;191;255m", + test_256_color_39: "\x1b[38;5;39m", + test_256_color_38: "\x1b[38;5;38m", + test_256_color_33: "\x1b[38;5;33m", + } + } else { + ColorCodes::no_color() + } +} + +/// Коды цветов для терминала +#[derive(Clone)] +struct ColorCodes { + reset: &'static str, + bold: &'static str, + faint: &'static str, + italic: &'static str, + underline: &'static str, + blink: &'static str, + + black: &'static str, + red: &'static str, + green: &'static str, + yellow: &'static str, + blue: &'static str, + magenta: &'static str, + cyan: &'static str, + white: &'static str, + + bright_black: &'static str, + bright_red: &'static str, + bright_green: &'static str, + bright_yellow: &'static str, + bright_blue: &'static str, + bright_magenta: &'static str, + bright_cyan: &'static str, + bright_white: &'static str, + + bg_black: &'static str, + bg_red: &'static str, + bg_green: &'static str, + bg_yellow: &'static str, + bg_blue: &'static str, + bg_magenta: &'static str, + bg_cyan: &'static str, + bg_white: &'static str, + + bg_bright_black: &'static str, + bg_bright_red: &'static str, + bg_bright_green: &'static str, + bg_bright_yellow: &'static str, + bg_bright_blue: &'static str, + bg_bright_magenta: &'static str, + bg_bright_cyan: &'static str, + bg_bright_white: &'static str, + + // Основной цвет проекта flusql - #00bfff (яркий голубой/синий) + header_color: &'static str, + header_color_true: &'static str, + success_color: &'static str, + warning_color: &'static str, + error_color: &'static str, + info_color: &'static str, + accent_color: &'static str, + + // Коды для тестирования + test_true_color: &'static str, + test_256_color_39: &'static str, + test_256_color_38: &'static str, + test_256_color_33: &'static str, +} + +impl ColorCodes { + fn no_color() -> Self { + Self { + reset: "", + bold: "", + faint: "", + italic: "", + underline: "", + blink: "", + + black: "", + red: "", + green: "", + yellow: "", + blue: "", + magenta: "", + cyan: "", + white: "", + + bright_black: "", + bright_red: "", + bright_green: "", + bright_yellow: "", + bright_blue: "", + bright_magenta: "", + bright_cyan: "", + bright_white: "", + + bg_black: "", + bg_red: "", + bg_green: "", + bg_yellow: "", + bg_blue: "", + bg_magenta: "", + bg_cyan: "", + bg_white: "", + + bg_bright_black: "", + bg_bright_red: "", + bg_bright_green: "", + bg_bright_yellow: "", + bg_bright_blue: "", + bg_bright_magenta: "", + bg_bright_cyan: "", + bg_bright_white: "", + + header_color: "", + header_color_true: "", + success_color: "", + warning_color: "", + error_color: "", + info_color: "", + accent_color: "", + + test_true_color: "", + test_256_color_39: "", + test_256_color_38: "", + test_256_color_33: "", + } + } + + /// Прямая раскраска текста указанным кодом + fn colorize(&self, text: &str, color: &str) -> String { + if color.is_empty() { + text.to_string() + } else { + format!("{}{}{}", color, text, self.reset) + } + } + + /// Основной цвет flusql - #00bfff для заголовков + fn header(&self, text: &str) -> String { + self.apply_primary_color(text, self.header_color_true, self.header_color) + } + + /// Успешные операций - цвет #00bfff + fn success(&self, text: &str) -> String { + self.apply_primary_color(text, self.header_color_true, self.success_color) + } + + /// Предупреждения - оранжевый + fn warning(&self, text: &str) -> String { + self.colorize(text, self.warning_color) + } + + /// Ошибки - красный + fn error(&self, text: &str) -> String { + self.colorize(text, self.error_color) + } + + /// Информационные сообщения - цвет #00bfff (заменяет cyan) + fn info(&self, text: &str) -> String { + self.apply_primary_color(text, self.header_color_true, self.info_color) + } + + /// Акцентный цвет - #00bfff (заменяет cyan) + fn accent(&self, text: &str) -> String { + self.apply_primary_color(text, self.header_color_true, self.accent_color) + } + + /// Жирный текст + fn bold(&self, text: &str) -> String { + self.colorize(text, self.bold) + } + + /// Подчеркнутый текст + fn underline(&self, text: &str) -> String { + self.colorize(text, self.underline) + } + + /// Основной метод применения цвета #00bfff + fn apply_primary_color(&self, text: &str, true_color: &str, fallback_256: &str) -> String { + if !supports_color() { + return text.to_string(); + } + + if self.supports_true_color() { + self.colorize(text, true_color) + } else if self.supports_256_colors() { + self.colorize(text, fallback_256) + } else { + // Для 8-цветных терминалов используем стандартный cyan (ближайший к #00bfff) + self.colorize(text, self.cyan) + } + } + + /// Проверка поддержки true color (24-bit цветов) + fn supports_true_color(&self) -> bool { + if !supports_color() { + return false; + } + + // Проверяем переменные окружения для поддержки true color + if let Ok(colorterm) = std::env::var("COLORTERM") { + if colorterm.contains("truecolor") || colorterm.contains("24bit") { + return true; + } + } + + // Проверяем переменную TERM для современных терминалов + if let Ok(term) = std::env::var("TERM") { + let truecolor_terms = [ + "xterm-truecolor", + "xterm-256color", // Многие современные терминалы с 256 цветами поддерживают true color + "screen-truecolor", + "screen-256color", + "tmux-truecolor", + "tmux-256color", + "rxvt-unicode-256color", + "alacritty", + "kitty", + "wezterm", + "foot", + "konsole", + "gnome-terminal", + "terminology", + ]; + + for truecolor_term in &truecolor_terms { + if term.contains(truecolor_term) { + return true; + } + } + } + + false + } + + /// Проверка поддержки 256 цветов + fn supports_256_colors(&self) -> bool { + if !supports_color() { + return false; + } + + if let Ok(term) = std::env::var("TERM") { + term.contains("256color") + } else { + false + } + } + + /// Тестирование цветов (для отладки) + fn test_colors(&self) -> String { + let mut result = String::new(); + result.push_str("\n=== ТЕСТИРОВАНИЕ ЦВЕТОВ flusql ===\n"); + result.push_str(&format!("True color #00bfff: {}Это цвет #00bfff (RGB: 0, 191, 255){}\n", + self.test_true_color, self.reset)); + result.push_str(&format!("256-color код 39: {}Это код 39 (яркий голубой){}\n", + self.test_256_color_39, self.reset)); + result.push_str(&format!("256-color код 38: {}Это код 38 (голубой){}\n", + self.test_256_color_38, self.reset)); + result.push_str(&format!("256-color код 33: {}Это код 33 (темно-голубой){}\n", + self.test_256_color_33, self.reset)); + result.push_str(&format!("Стандартный cyan: {}Это стандартный cyan (ближайший к #00bfff){}\n", + self.cyan, self.reset)); + result.push_str(&format!("Стандартный синий: {}Это стандартный синий{}\n", + self.blue, self.reset)); + result.push_str("===================================\n"); + result + } +} + +/// Получить ширину строки в терминале (учитывает Unicode символы) +fn display_width(s: &str) -> usize { + UnicodeWidthStr::width(s) +} + +/// Форматирование таблицы с использованием непрерывных линий +fn format_table(headers: &[String], rows: &[Vec]) -> String { + if rows.is_empty() { + return String::from("Нет данных"); + } + + let colors = get_color_codes(); + + // Рассчитываем ширину колонок с учетом отображения Unicode + let mut col_widths: Vec = headers.iter().map(|h| display_width(h)).collect(); + + for row in rows { + for (i, cell) in row.iter().enumerate() { + if i < col_widths.len() { + col_widths[i] = col_widths[i].max(display_width(cell)); + } + } + } + + // Добавляем отступы + for width in &mut col_widths { + *width += 2; // По 1 пробелу с каждой стороны + } + + let mut result = String::new(); + + // Верхняя граница + result.push('┌'); + for (i, &width) in col_widths.iter().enumerate() { + for _ in 0..width { + result.push('─'); + } + if i < col_widths.len() - 1 { + result.push('┬'); + } + } + result.push_str("┐\n"); + + // Заголовки + result.push('│'); + for (i, (header, &width)) in headers.iter().zip(col_widths.iter()).enumerate() { + let header_width = display_width(header); + let padding = width - header_width; + let left_pad = padding / 2; + let right_pad = padding - left_pad; + result.push_str(&format!("{}{}{}", + " ".repeat(left_pad), + colors.bold(header), + " ".repeat(right_pad))); + result.push('│'); + } + result.push('\n'); + + // Разделитель заголовков + result.push('├'); + for (i, &width) in col_widths.iter().enumerate() { + for _ in 0..width { + result.push('─'); + } + if i < col_widths.len() - 1 { + result.push('┼'); + } + } + result.push_str("┤\n"); + + // Данные + for (row_idx, row) in rows.iter().enumerate() { + result.push('│'); + for (i, (cell, &width)) in row.iter().zip(col_widths.iter()).enumerate() { + let cell_width = display_width(cell); + let padding = width - cell_width; + + // Для первой колонки - выравнивание по левому краю, для остальных - по центру + if i == 0 { + result.push_str(&format!(" {}{}{}", + cell, + " ".repeat(padding - 1), + "")); + } else { + let left_pad = padding / 2; + let right_pad = padding - left_pad; + result.push_str(&format!("{}{}{}", + " ".repeat(left_pad), + cell, + " ".repeat(right_pad))); + } + result.push('│'); + } + result.push('\n'); + + // Добавляем горизонтальные линии между строками (кроме последней) + if row_idx < rows.len() - 1 { + result.push('├'); + for (i, &width) in col_widths.iter().enumerate() { + for _ in 0..width { + result.push('─'); + } + if i < col_widths.len() - 1 { + result.push('┼'); + } + } + result.push_str("┤\n"); + } + } + + // Нижняя граница + result.push('└'); + for (i, &width) in col_widths.iter().enumerate() { + for _ in 0..width { + result.push('─'); + } + if i < col_widths.len() - 1 { + result.push('┴'); + } + } + result.push('┘'); + + result +} + +/// Форматирование таблицы с одной колонкой +fn format_single_column_table(items: &[String]) -> String { + if items.is_empty() { + return String::new(); + } + + let colors = get_color_codes(); + + // Находим максимальную ширину строки с учетом Unicode + let max_width = items.iter().map(|item| display_width(item)).max().unwrap_or(0); + + // Ширина колонки + let col_width = max_width + 2; // +2 для пробелов с каждой стороны + + let mut result = String::new(); + + // Верхняя граница + result.push('┌'); + for _ in 0..col_width { + result.push('─'); + } + result.push('┐'); + result.push('\n'); + + // Элементы + for (item_idx, item) in items.iter().enumerate() { + result.push('│'); + result.push(' '); + + // Разделяем на маркер и текст + let (marker, text) = if let Some(stripped) = item.strip_prefix("• ") { + ("• ", stripped) + } else { + ("", item.as_str()) + }; + + // Используем цвет #00bfff для всех информационных сообщений (заменяет cyan) + result.push_str(&colors.info(marker)); + result.push_str(text); + result.push_str(colors.reset); + + // Заполняем оставшееся пространство пробелами с учетом ширины в символы + let item_width = display_width(item); + let spaces_needed = col_width - item_width - 1; // -1 для пробела в начале + if spaces_needed > 0 { + result.push_str(&" ".repeat(spaces_needed)); + } + + result.push('│'); + result.push('\n'); + + // Добавляем горизонтальные линии между строками (кроме последней) + if item_idx < items.len() - 1 { + result.push('├'); + for _ in 0..col_width { + result.push('─'); + } + result.push('┤'); + result.push('\n'); + } + } + + // Нижняя граница + result.push('└'); + for _ in 0..col_width { + result.push('─'); + } + result.push('┘'); + result.push('\n'); + + result +} + +/// Показать справку в виде таблицы с горизонтальными линиями +fn show_help_table() -> String { + let colors = get_color_codes(); + let mut output = String::new(); + + // Таблица команд + let headers = vec!["Команда".to_string(), "Описание".to_string(), "Пример".to_string()]; + + let rows = vec![ + vec!["CREATE DATABASE".to_string(), "Создать базу данных".to_string(), "CREATE DATABASE mydb;".to_string()], + vec!["DROP DATABASE".to_string(), "Удалить базу данных".to_string(), "DROP DATABASE mydb;".to_string()], + vec!["SHOW DATABASES".to_string(), "Показать список баз данных".to_string(), "SHOW DATABASES;".to_string()], + vec!["\\dt".to_string(), "Показать таблицы в текущей БД".to_string(), "\\dt;".to_string()], + vec!["\\d таблица".to_string(), "Описание структуры таблицы".to_string(), "\\d users;".to_string()], + vec!["USE".to_string(), "Использовать базу данных".to_string(), "USE mydb;".to_string()], + vec!["CREATE TABLE".to_string(), "Создать таблицу".to_string(), "CREATE TABLE users (id INT, name VARCHAR(255));".to_string()], + vec!["ALTER TABLE".to_string(), "Изменить таблицу".to_string(), "ALTER TABLE users ADD COLUMN age INT;".to_string()], + vec!["DROP TABLE".to_string(), "Удалить таблицу".to_string(), "DROP TABLE users;".to_string()], + vec!["DROP TABLE CASCADE".to_string(), "Удалить таблицу с зависимостями".to_string(), "DROP TABLE users CASCADE;".to_string()], + vec!["SELECT".to_string(), "Выбрать данные".to_string(), "SELECT * FROM users;".to_string()], + vec!["INSERT".to_string(), "Вставить данные".to_string(), "INSERT INTO users (id, name) VALUES (1, 'Alice');".to_string()], + vec!["UPDATE".to_string(), "Обновить данные".to_string(), "UPDATE users SET age = 30 WHERE id = 1;".to_string()], + vec!["DELETE".to_string(), "Удалить данные".to_string(), "DELETE FROM users WHERE id = 1;".to_string()], + vec!["CREATE INDEX".to_string(), "Создать индекс".to_string(), "CREATE INDEX idx_users_id ON users (id);".to_string()], + vec!["DROP INDEX".to_string(), "Удалить индекс".to_string(), "DROP INDEX idx_users_id;".to_string()], + vec!["CREATE TRIGGER".to_string(), "Создать триггер".to_string(), "CREATE TRIGGER check_age BEFORE INSERT ON users FOR EACH ROW EXECUTE FUNCTION check_age();".to_string()], + vec!["DROP TRIGGER".to_string(), "Удалить триггер".to_string(), "DROP TRIGGER check_age ON users;".to_string()], + vec!["EXPLAIN".to_string(), "Объяснить план выполнения".to_string(), "EXPLAIN SELECT * FROM users;".to_string()], + vec!["EXPLAIN ANALYZE".to_string(), "Выполнить и объяснить запрос".to_string(), "EXPLAIN ANALYZE SELECT * FROM users;".to_string()], + vec!["BEGIN".to_string(), "Начать транзакцию".to_string(), "BEGIN;".to_string()], + vec!["COMMIT".to_string(), "Зафиксировать транзакцию".to_string(), "COMMIT;".to_string()], + vec!["ROLLBACK".to_string(), "Откатить транзакцию".to_string(), "ROLLBACK;".to_string()], + vec!["COPY TO".to_string(), "Экспорт в CSV".to_string(), "COPY users TO 'users.csv' WITH CSV HEADER;".to_string()], + vec!["COPY FROM".to_string(), "Импорт из CSV".to_string(), "COPY users FROM 'users.csv' WITH CSV HEADER;".to_string()], + vec!["CREATE SEQUENCE".to_string(), "Создать последовательность".to_string(), "CREATE SEQUENCE users_id_seq;".to_string()], + vec!["CREATE TYPE".to_string(), "Создать составной тип".to_string(), "CREATE TYPE address AS (street TEXT, city TEXT);".to_string()], + vec!["CREATE VIEW".to_string(), "Создать представление".to_string(), "CREATE VIEW user_emails AS SELECT id, email FROM users;".to_string()], + vec!["HISTORY".to_string(), "Показать историю команд".to_string(), "HISTORY;".to_string()], + vec!["HISTORY CLEAR".to_string(), "Очистить историю команд".to_string(), "HISTORY CLEAR;".to_string()], + vec!["HISTORY EXPORT".to_string(), "Экспорт истории в файл".to_string(), "HISTORY EXPORT 'history.txt';".to_string()], + vec!["!!".to_string(), "Повторить последнюю команду".to_string(), "!!".to_string()], + vec!["!n".to_string(), "Выполнить команду из истории по номеру".to_string(), "!3".to_string()], + vec!["HELP".to_string(), "Показать справку".to_string(), "HELP;".to_string()], + vec!["EXIT".to_string(), "Выйти из программы".to_string(), "EXIT;".to_string()], + vec!["QUIT".to_string(), "Выйти из программы".to_string(), "QUIT;".to_string()], + vec!["lua.mode".to_string(), "Войти в Lua режим".to_string(), "lua.mode".to_string()], + vec!["plugins list".to_string(), "Список загруженных плагинов".to_string(), "plugins list".to_string()], + vec!["plugin load".to_string(), "Загрузить плагин".to_string(), "plugin load './plugins/example.lua'".to_string()], + vec!["plugin unload".to_string(), "Выгрузить плагин".to_string(), "plugin unload 'plugin_id'".to_string()], + vec!["plugins reload".to_string(), "Перезагрузить все плагины".to_string(), "plugins reload".to_string()], + ]; + + // Команды управления кластером в Lua режиме + let cluster_headers = vec!["Функция Lua".to_string(), "Описание".to_string()]; + + let cluster_rows = vec![ + vec!["cluster.get_status()".to_string(), "Получить полный статус кластера".to_string()], + vec!["cluster.coord_status()".to_string(), "Показать IP-адрес текущего координатора".to_string()], + vec!["cluster.add_node(node_id, address)".to_string(), "Добавить узел".to_string()], + vec!["cluster.evict(node_id)".to_string(), "Исключить узел из кластера".to_string()], + vec!["cluster.elect_coordinator()".to_string(), "Выбрать нового координатора".to_string()], + vec!["cluster.rebalance()".to_string(), "Ребалансировать кластер".to_string()], + vec!["cluster.add_shard(shard_id, master, slaves)".to_string(), "Добавить шард".to_string()], + vec!["cluster.remove_shard(shard_id)".to_string(), "Удалить шард".to_string()], + vec!["cluster.start_replication(source, target)".to_string(), "Начать репликацию".to_string()], + ]; + + // Команды плагинов в Lua режиме + let plugin_headers = vec!["Функция Lua".to_string(), "Описание".to_string()]; + + let plugin_rows = vec![ + vec!["plugins.list()".to_string(), "Список загруженных плагинов".to_string()], + vec!["plugins.get(plugin_id)".to_string(), "Получить информацию о плагине".to_string()], + vec!["plugins.reload()".to_string(), "Перезагрузить все плагины".to_string()], + vec!["plugins.emit_event(event_name, data)".to_string(), "Отправить событие плагинам".to_string()], + ]; + + // Тестирование цветов (можно отключить) + // output.push_str(&colors.test_colors()); + + // Форматируем основную таблицу команд + output.push_str(&format!("\n{}\n", colors.header("ОСНОВНЫЕ КОМАНДЫ:"))); + output.push_str(&format_table(&headers, &rows)); + + // Команды управления кластера в Lua режиме + output.push_str(&format!("\n{}\n", colors.header("КОМАНДЫ УПРАВЛЕНИЯ КЛАСТЕРОМ (только в Lua режиме):"))); + output.push_str(&format!("{}\n", colors.info("1. Введите команду 'lua.mode' для входа в Lua режим"))); + output.push_str(&format!("{}\n", colors.info("2. В Lua режиме используйте функции:"))); + output.push_str(&format_table(&cluster_headers, &cluster_rows)); + + // Команды плагинов в Lua режиме + output.push_str(&format!("\n{}\n", colors.header("ПЛАГИНЫ (только в Lua режиме):"))); + output.push_str(&format!("{}\n", colors.info("1. Введите команду 'lua.mode' для входа в Lua режим"))); + output.push_str(&format!("{}\n", colors.info("2. В Lua режиме используйте функции:"))); + output.push_str(&format_table(&plugin_headers, &plugin_rows)); + + // Автоматические перевыборы координатора + output.push_str(&format!("\n{}\n", colors.header("АВТОМАТИЧЕСКИЕ ПЕРЕВЫБОРЫ КООРДИНАТОРА:"))); + output.push_str(&format_single_column_table(&[ + "• Координатор автоматически перевыбирается при его отказе".to_string(), + "• Новый координатор выбирается среди онлайн узлов".to_string(), + "• Критерий выбора: узел с наибольшим количеством шардов".to_string(), + "• Heartbeat проверки каждые 15 секунд".to_string(), + "• Timeout координатора: 30 секунд без heartbeat".to_string(), + ])); + + // История команд + output.push_str(&format!("\n{}\n", colors.header("ИСТОРИЯ КОМАНД:"))); + output.push_str(&format_single_column_table(&[ + "• История автоматически сохраняется между сессиями".to_string(), + "• Используйте стрелки вверх/вниз для навигации по истории".to_string(), + "• !! - повторить последнюю команду".to_string(), + "• !n - выполнить команду с номером n из истории".to_string(), + "• HISTORY - показать всю историю команд".to_string(), + "• HISTORY CLEAR - очистить историю".to_string(), + "• HISTORY EXPORT 'file' - экспортировать историю в файл".to_string(), + ])); + + // Система плагинов + output.push_str(&format!("\n{}\n", colors.header("СИСТЕМА ПЛАГИНОВ:"))); + output.push_str(&format_single_column_table(&[ + "• Плагины пишутся на Lua и загружаются из директории 'plugins'".to_string(), + "• Поддержка изоляции (sandbox) для безопасности".to_string(), + "• Система событий и триггеров".to_string(), + "• Зависимости между плагинами".to_string(), + "• plugins list - список плагинов".to_string(), + "• plugin load 'path' - загрузить плагин".to_string(), + "• plugin unload 'id' - выгрузить плагин".to_string(), + "• plugins reload - перезагрузить все плагины".to_string(), + ])); + + // Конфигурация + output.push_str(&format!("\n{}\n", colors.header("КОНФИГУРАЦИЯ:"))); + output.push_str(&format_single_column_table(&[ + "• Конфигурация загружается из config.toml".to_string(), + "• Настройки сервера: порт, хост, пул потоков".to_string(), + "• Настройки БД: размер страницы, кэш, WAL, MVCC".to_string(), + "• Настройки плагинов: директория, sandbox, таймауты".to_string(), + "• Настройки сети и безопасности".to_string(), + "• Переменная окружения FLUSQL_CONFIG для указания пути к конфигу".to_string(), + ])); + + // Информация о поддержке цветов + if supports_color() { + if get_color_codes().supports_true_color() { + output.push_str(&format!("\n{} {} {}\n", colors.header("ЦВЕТНОЙ ВЫВОД:"), colors.success("активирован (true color)"), colors.reset)); + } else if get_color_codes().supports_256_colors() { + output.push_str(&format!("\n{} {} {}\n", colors.header("ЦВЕТНОЙ ВЫВОД:"), colors.success("активирован (256 цветов)"), colors.reset)); + } else { + output.push_str(&format!("\n{} {} {}\n", colors.header("ЦВЕТНОЙ ВЫВОД:"), colors.success("активирован (8 цветов)"), colors.reset)); + } + } else { + output.push_str(&format!("\n{} {} {}\n", colors.header("ЦВЕТНОЙ ВЫВОД:"), colors.warning("недоступен"), colors.reset)); + } + + output +} + +/// Показать справку для Lua режима +fn show_lua_help_table() -> String { + let colors = get_color_codes(); + let mut output = String::new(); + + output.push_str(&format!("\n{}\n", colors.header("КОМАНДЫ LUA РЕЖИМА:"))); + + // Основные команды Lua режима + let main_headers = vec!["Команда".to_string(), "Описание".to_string()]; + let main_rows = vec![ + vec!["sql.mode".to_string(), "Вернуться в SQL режим".to_string()], + vec!["exit / quit".to_string(), "Выйти из Lua режима".to_string()], + vec!["help".to_string(), "Показать эту справку".to_string()], + vec!["history".to_string(), "Показать историю Lua команд".to_string()], + vec!["clear-history".to_string(), "Очистить историю Lua команд".to_string()], + ]; + output.push_str(&format_table(&main_headers, &main_rows)); + + // Функции кластера + output.push_str(&format!("\n{}\n", colors.header("ФУНКЦИИ КЛАСТЕРА:"))); + let cluster_headers = vec!["Функция Lua".to_string(), "Описание".to_string()]; + let cluster_rows = vec![ + vec!["cluster.get_status()".to_string(), "Получить полный статус кластера".to_string()], + vec!["cluster.coord_status()".to_string(), "Показать IP-адрес текущего координатора".to_string()], + vec!["cluster.add_node(node_id, address)".to_string(), "Добавить узел".to_string()], + vec!["cluster.evict(node_id)".to_string(), "Исключить узел из кластера".to_string()], + vec!["cluster.elect_coordinator()".to_string(), "Выбрать нового координатора".to_string()], + vec!["cluster.rebalance()".to_string(), "Ребалансировать кластер".to_string()], + vec!["cluster.add_shard(shard_id, master, slaves)".to_string(), "Добавить шард".to_string()], + vec!["cluster.remove_shard(shard_id)".to_string(), "Удалить шард".to_string()], + vec!["cluster.start_replication(source, target)".to_string(), "Начать репликации".to_string()], + ]; + output.push_str(&format_table(&cluster_headers, &cluster_rows)); + + // Функции плагинов + output.push_str(&format!("\n{}\n", colors.header("ФУНКЦИИ ПЛАГИНОВ:"))); + let plugin_headers = vec!["Функция Lua".to_string(), "Описание".to_string()]; + let plugin_rows = vec![ + vec!["plugins.list()".to_string(), "Список загруженных плагинов".to_string()], + vec!["plugins.get(plugin_id)".to_string(), "Получить информацию о плагине".to_string()], + vec!["plugins.reload()".to_string(), "Перезагрузить все плагины".to_string()], + vec!["plugins.emit_event(event_name, data)".to_string(), "Отправить событие плагинам".to_string()], + ]; + output.push_str(&format_table(&plugin_headers, &plugin_rows)); + + // Примеры Lua кода + output.push_str(&format!("\n{}\n", colors.header("ПРИМЕРЫ LUA КОДА:"))); + output.push_str(&format_single_column_table(&[ + "• print('Hello from Lua!')".to_string(), + "• for i=1,5 do print('Number: ' .. i) end".to_string(), + "• status = cluster.get_status()".to_string(), + "• print('Coordinator: ' .. status.coordinator_id)".to_string(), + "• plugins.list()".to_string(), + "• plugins.emit_event('custom.event', '{ \"data\": \"value\" }')".to_string(), + ])); + + // Встроенные функции Lua + output.push_str(&format!("\n{}\n", colors.header("ВСТРОЕННЫЕ ФУНКЦИИ LUA:"))); + output.push_str(&format_single_column_table(&[ + "• Все стандартные функции Lua доступны".to_string(), + "• math, string, table, os, io - стандартные библиотеки".to_string(), + "• print() - вывод в консоль".to_string(), + "• type() - определение типа переменной".to_string(), + "• pairs() и ipairs() - итерация по таблицам".to_string(), + ])); + + output +} + +/// Структура для управления историей команд в реальном времени +struct RealtimeHistory { + commands: VecDeque, + current_index: isize, + max_size: usize, +} + +impl RealtimeHistory { + fn new(max_size: usize) -> Self { + Self { + commands: VecDeque::with_capacity(max_size), + current_index: -1, + max_size, + } + } + + fn add(&mut self, command: String) { + // Удаляем дубликаты + if let Some(pos) = self.commands.iter().position(|c| c == &command) { + self.commands.remove(pos); + } + + self.commands.push_back(command); + while self.commands.len() > self.max_size { + self.commands.pop_front(); + } + + // Сбрасываем индекс + self.current_index = -1; + } + + fn get_previous(&mut self) -> Option { + if self.commands.is_empty() { + return None; + } + + if self.current_index < 0 { + // Начинаем с последней команды + self.current_index = self.commands.len() as isize - 1; + } else if self.current_index > 0 { + self.current_index -= 1; + } else { + // Уже на первой команде, возвращаем ее + return self.commands.get(0).cloned(); + } + + self.commands.get(self.current_index as usize).cloned() + } + + fn get_next(&mut self) -> Option { + if self.commands.is_empty() || self.current_index < 0 { + return None; + } + + if self.current_index < self.commands.len() as isize - 1 { + self.current_index += 1; + self.commands.get(self.current_index as usize).cloned() + } else { + // Достигли конца, сбрасываем индекс и возвращаем пустую строку + self.current_index = -1; + Some(String::new()) + } + } + + fn reset_index(&mut self) { + self.current_index = -1; + } + + fn get_all(&self) -> Vec { + self.commands.iter().cloned().collect() + } + + fn clear(&mut self) { + self.commands.clear(); + self.current_index = -1; + } +} + +/// Функция для чтения ввода с поддержкой истории (аналог readline) +fn read_line_with_history(prompt: &str, history: &mut RealtimeHistory) -> Result { + let colors = get_color_codes(); + print!("{}", colors.header(prompt)); + io::stdout().flush()?; + + let mut input = String::new(); + let mut cursor_pos = 0; + let mut saved_input = String::new(); + + // Устанавливаем raw режим для терминала + let mut termios = libc::termios { c_iflag: 0, c_oflag: 0, c_cflag: 0, c_lflag: 0, c_line: 0, c_cc: [0; 32], c_ispeed: 0, c_ospeed: 0 }; + + unsafe { + // Получаем текущие настройки терминала + if libc::tcgetattr(0, &mut termios) != 0 { + // Если не удалось получить настройки терминала, используем простой ввод + return read_line_simple(prompt); + } + + let mut raw_termios = termios; + // Отключаем канонический режим и эхо + raw_termios.c_lflag &= !(libc::ICANON | libc::ECHO); + // Устанавливаем минимальное количество символов для чтения и timeout + raw_termios.c_cc[libc::VMIN] = 1; + raw_termios.c_cc[libc::VTIME] = 0; + + // Применяем raw режим + if libc::tcsetattr(0, libc::TCSANOW, &raw_termios) != 0 { + // Если не удалось установить raw режим, используем простой ввод + return read_line_simple(prompt); + } + } + + // Функция восстановления настроек терминала + let restore_termios = move || { + unsafe { + libc::tcsetattr(0, libc::TCSANOW, &termios); + } + }; + + loop { + let mut buffer = [0; 1]; + + // Читаем один символ + let bytes_read = match io::stdin().read(&mut buffer) { + Ok(bytes) => bytes, + Err(e) => { + restore_termios(); + return Err(e); + } + }; + + if bytes_read == 0 { + restore_termios(); + return Ok(input); + } + + let c = buffer[0]; + + match c { + // Enter (13 или 10) + 13 | 10 => { + restore_termios(); + println!(); + let trimmed = input.trim().to_string(); + if !trimmed.is_empty() { + history.add(trimmed.clone()); + } + return Ok(trimmed); + } + // Backspace или Delete + 8 | 127 => { + if cursor_pos > 0 { + input.remove(cursor_pos - 1); + cursor_pos -= 1; + + // Очищаем строку и перерисовываем + print!("\r{}", " ".repeat(prompt.len() + input.len() + 2)); + print!("\r{}{}", colors.header(prompt), input); + io::stdout().flush()?; + } + } + // Escape последовательности (стрелки и т.д.) + 27 => { + // Читаем следующие 2 символа для определения escape последовательности + let mut next_buffer = [0; 2]; + if io::stdin().read(&mut next_buffer[0..1]).is_ok() && next_buffer[0] == 91 { + if io::stdin().read(&mut next_buffer[1..2]).is_ok() { + match next_buffer[1] { + // Стрелка вверх + 65 => { + if let Some(prev_command) = history.get_previous() { + // Сохраняем текущий ввод + if history.current_index == history.commands.len() as isize - 1 { + saved_input = input.clone(); + } + + input = prev_command.clone(); + cursor_pos = input.len(); + + print!("\r{}", " ".repeat(prompt.len() + input.len() + 2)); + print!("\r{}{}", colors.header(prompt), input); + io::stdout().flush()?; + } + } + // Стрелка вниз + 66 => { + if let Some(next_command) = history.get_next() { + input = next_command.clone(); + if input.is_empty() && !saved_input.is_empty() { + input = saved_input.clone(); + saved_input.clear(); + } + cursor_pos = input.len(); + + print!("\r{}", " ".repeat(prompt.len() + input.len() + 2)); + print!("\r{}{}", colors.header(prompt), input); + io::stdout().flush()?; + } + } + // Стрелка вправо + 67 => { + if cursor_pos < input.len() { + cursor_pos += 1; + // Показываем позицию курсора + print!("\r{}{}", colors.header(prompt), input); + // Позиционируем курсор + print!("\x1b[{}C", cursor_pos + prompt.len() - input.len()); + io::stdout().flush()?; + } + } + // Стрелка влево + 68 => { + if cursor_pos > 0 { + cursor_pos -= 1; + // Показываем позицию курсора + print!("\r{}{}", colors.header(prompt), input); + // Позиционируем курсор + print!("\x1b[{}C", cursor_pos + prompt.len() - input.len()); + io::stdout().flush()?; + } + } + // Home (часто 72 или 1~) + 72 | 49 => { + cursor_pos = 0; + print!("\r{}{}", colors.header(prompt), input); + print!("\x1b[{}C", prompt.len()); + io::stdout().flush()?; + } + // End (часто 70 или 4~) + 70 | 52 => { + cursor_pos = input.len(); + print!("\r{}{}", colors.header(prompt), input); + io::stdout().flush()?; + } + // Delete (51~) + 51 => { + if cursor_pos < input.len() { + let _ = input.remove(cursor_pos); + print!("\r{}", " ".repeat(prompt.len() + input.len() + 2)); + print!("\r{}{}", colors.header(prompt), input); + io::stdout().flush()?; + } + } + _ => {} + } + } + } + } + // Ctrl+C (3) - прерывание + 3 => { + restore_termios(); + println!("^C"); + return Err(io::Error::new(io::ErrorKind::Interrupted, "Ctrl+C pressed")); + } + // Ctrl+D (4) - EOF + 4 => { + restore_termios(); + return Ok(String::new()); + } + // Обычные символы + _ if c >= 32 => { + // Безопасное преобразование u8 в char + let ch = char::from_u32(c as u32).unwrap_or('?'); + input.insert(cursor_pos, ch); + cursor_pos += 1; + + // Очищаем строку и перерисовываем + print!("\r{}", " ".repeat(prompt.len() + input.len() + 2)); + print!("\r{}{}", colors.header(prompt), input); + io::stdout().flush()?; + } + _ => {} + } + } +} + +/// Простой ввод строки без поддержки стрелок (fallback) +fn read_line_simple(prompt: &str) -> Result { + let colors = get_color_codes(); + print!("{}", colors.header(prompt)); + io::stdout().flush()?; + + let mut input = String::new(); + io::stdin().read_line(&mut input)?; + + Ok(input.trim().to_string()) +} + +/// Запуск REPL интерфейса +pub async fn start_repl() -> Result<(), Box> { + // Инициализация истории команд + let history = CommandHistory::new(1000, ".flusql_history"); + let current_session = "default".to_string(); + + let colors = get_color_codes(); + + println!(); + println!("{}", colors.header("Distributed wide-column store")); + println!("{}", colors.header(&format!("version {}", crate::VERSION))); + + // Загрузка конфигурации + let config = Config::from_env_or_file("config.toml").unwrap_or_default(); + + // Инициализация менеджера кластера (из конфигурации) + let cluster_manager = Arc::new(ClusterManager::new( + &config.cluster.node_id, + &config.cluster.node_address + )); + + // Инициализация Lua интерпретатора + let mut lua_interpreter = LuaInterpreter::new(); + + // Регистрируем функции кластера в Lua + if let Err(e) = lua_interpreter.register_cluster_functions(Arc::clone(&cluster_manager)) { + println!("{}", colors.warning(&format!("Failed to register cluster functions: {}", e))); + } + + // Инициализация менеджера плагинов + let plugins_config = PluginConfig { + enabled: config.plugins.enabled, + plugins_dir: config.plugins.plugins_dir.clone(), + auto_load: config.plugins.auto_load, + }; + + let mut plugin_manager = PluginManager::new(plugins_config); + + if config.plugins.enabled { + match plugin_manager.initialize().await { + Ok(_) => { + println!("{}", colors.success("Plugin system initialized")); + + // Загружаем плагины если авто-загрузка включена + if config.plugins.auto_load { + match plugin_manager.load_all_plugins().await { + Ok(loaded_plugins) => { + let count: usize = loaded_plugins.len(); + println!("{}", colors.success(&format!("Loaded {} plugins", count))); + } + Err(e) => { + println!("{}", colors.warning(&format!("Failed to load plugins: {}", e))); + } + } + } + } + Err(e) => { + println!("{}", colors.warning(&format!("Failed to initialize plugin system: {}", e))); + } + } + } + + // Регистрируем функции плагинов в Lua + if let Err(e) = lua_interpreter.register_plugin_functions(Arc::new(plugin_manager.clone())) { + println!("{}", colors.warning(&format!("Failed to register plugin functions: {}", e))); + } + + // Инициализация Lua утилит + if let Err(e) = lua_interpreter.register_utilities() { + println!("{}", colors.warning(&format!("Failed to register Lua utilities: {}", e))); + } + + // Информация о системе + if cfg!(target_os = "linux") { + if let Ok(os_release) = std::fs::read_to_string("/etc/os-release") { + for line in os_release.lines() { + if line.starts_with("PRETTY_NAME=") { + let distro_name = line.trim_start_matches("PRETTY_NAME=").trim_matches('"'); + println!("{}", colors.info(&format!("Running on: {}", distro_name))); + break; + } + } + } + } + + // Информация о конфигурации + println!("{}", colors.info(&format!("Data directory: {}", config.database.data_dir))); + println!("{}", colors.info(&format!("Page size: {} bytes", config.database.page_size))); + println!("{}", colors.info(&format!("Server: {}:{}", config.server.host, config.server.port))); + println!("{}", colors.info(&format!("Plugins: {}", if config.plugins.enabled { "enabled" } else { "disabled" }))); + println!("{}", colors.info(&format!("Max connections: {}", config.server.max_connections))); + println!("{}", colors.info(&format!("Cache size: {} MB", config.database.cache_size_mb))); + + if supports_color() { + if colors.supports_true_color() { + println!("{}", colors.success("Цветной вывод активирован (true color)")); + } else if colors.supports_256_colors() { + println!("{}", colors.success("Цветной вывод активирован (256 цветов)")); + } else { + println!("{}", colors.success("Цветной вывод активирован (8 цветов)")); + } + } else { + println!("{}", colors.warning("Цветной вывод недоступен")); + } + + println!(); + + // Основной цикл REPL + let mut last_command = String::new(); + let mut command_history: VecDeque = VecDeque::with_capacity(1000); + let mut lua_mode = false; // Флаг режима Lua + let mut lua_mode_context = LuaModeContext::new(); // Контекст Lua режима + + // Создаем отдельные истории для SQL и Lua режимов + let mut sql_history = RealtimeHistory::new(1000); + let mut lua_history = RealtimeHistory::new(1000); + + loop { + // Выбор приглашения в зависимости от режима + let prompt = if lua_mode { "flusql(lua)€ " } else { "flusql> " }; + + // Используем соответствующую историю в зависимости от режима + let input_result = if lua_mode { + read_line_with_history(prompt, &mut lua_history) + } else { + read_line_with_history(prompt, &mut sql_history) + }; + + let input = match input_result { + Ok(input) => input, + Err(e) => { + if e.kind() == io::ErrorKind::Interrupted { + // Ctrl+C - очищаем строку и продолжаем + println!(); + continue; + } + println!("{}", colors.error(&format!("Ошибка ввода: {}", e))); + continue; + } + }; + + if input.is_empty() { + continue; + } + + // Обработка специальных команд истории + let processed_input = if input == "!!" { + if last_command.is_empty() { + println!("{}", colors.error("No previous command")); + continue; + } + last_command.clone() + } else if input.starts_with('!') && input.len() > 1 { + // Команда вида !n - выполнить команду из истории по номеру + if let Ok(n) = input[1..].parse::() { + if n > 0 && n <= command_history.len() { + command_history[n - 1].clone() + } else { + println!("{}", colors.error(&format!("Invalid history number: {}", n))); + continue; + } + } else { + // Поиск по префиксу + let prefix = &input[1..]; + let matches: Vec = command_history.iter() + .filter(|cmd| cmd.starts_with(prefix)) + .cloned() + .collect(); + + if matches.is_empty() { + println!("{}", colors.error(&format!("No matching command in history for: {}", prefix))); + continue; + } else if matches.len() == 1 { + matches[0].clone() + } else { + println!("{}", colors.error("Multiple matches found:")); + for (i, cmd) in matches.iter().enumerate() { + println!(" {}: {}", i + 1, cmd); + } + continue; + } + } + } else { + input.to_string() + }; + + // Сохраняем команду в истории (кроме переключения режимов) + if processed_input != "lua.mode" && processed_input != "sql.mode" { + history.add(¤t_session, &processed_input); + + // Добавляем в локальную историю для навигации стрелками + command_history.push_back(processed_input.clone()); + while command_history.len() > 1000 { + command_history.pop_front(); + } + + last_command = processed_input.clone(); + } + + // Сбрасываем индекс истории для реального времени + if lua_mode { + lua_history.reset_index(); + } else { + sql_history.reset_index(); + } + + // Обработка команд в зависимости от режима + if lua_mode { + // Lua режим + // Проверка и преобразование команд REPL в вызовы Lua функций + let lua_command = transform_repl_command_to_lua(&processed_input); + + match lua_command.to_lowercase().as_str() { + "sql.mode" | "exit" | "quit" => { + // Выход из Lua режима + println!("{}", colors.info("Exiting Lua mode, returning to SQL mode")); + lua_mode = false; + } + "help" => { + println!("{}", show_lua_help_table()); + } + "history" => { + // Показать историю команд Lua + println!("{}", colors.info("Lua Command History:")); + let lua_history_all = lua_mode_context.history.get_all_history(); + for (i, cmd) in lua_history_all.iter().enumerate() { + println!(" {}: {}", i + 1, cmd); + } + } + "clear-history" | "history clear" => { + // Очистить историю команд Lua + lua_mode_context.history.clear_history(); + lua_history.clear(); + println!("{}", colors.info("Lua history cleared")); + } + _ => { + // Добавляем команду в историю Lua + lua_mode_context.history.add_command(processed_input.clone()); + + // Выполнение Lua кода + match lua_interpreter.execute(&lua_command) { + Ok(result) => { + // Выводим результат только если он не пустой (не "nil") + if !result.is_empty() { + println!("{}", result); + } + } + Err(e) => { + println!("{}", colors.error(&format!("Lua Error: {}", e))); + } + } + } + } + } else { + // SQL режим + match processed_input.to_lowercase().as_str() { + "exit" | "quit" => { + // Сохраняем историю перед выходом + if let Err(e) = history.save_to_file().await { + println!("{}", colors.error(&format!("Warning: Failed to save command history: {}", e))); + } + + // Останавливаем систему плагинов + if config.plugins.enabled { + println!("{}", colors.info("Stopping plugin system...")); + // Здесь должна быть логика остановки плагинов + } + + println!("{}", colors.header("closed flusql...")); + break; + } + "help" => { + println!("{}", show_help_table()); + } + "history" => { + // Показать историю команд + println!("{}", colors.info("Command History:")); + println!("{}", colors.info(&format!("Session: {}", current_session))); + let history_list = history.get_history(¤t_session); + for (i, cmd) in history_list.iter().enumerate() { + println!(" {}: {}", i + 1, cmd); + } + } + "history clear" => { + // Очистить историю + history.clear(¤t_session); + command_history.clear(); + sql_history.clear(); + println!("{}", colors.info("History cleared")); + } + _ if processed_input.starts_with("history export ") => { + // Экспорт истории в файл + let export_path = processed_input.trim_start_matches("history export ").trim(); + if export_path.is_empty() { + println!("{}", colors.error("Usage: HISTORY EXPORT 'file_path';")); + } else { + let export_path = export_path.trim_matches(|c| c == '\'' || c == '"'); + match history.export_history(export_path).await { + Ok(_) => println!("{}", colors.success(&format!("History exported to {}", export_path))), + Err(e) => println!("{}", colors.error(&format!("Error exporting history: {}", e))), + } + } + } + "plugins list" => { + if !config.plugins.enabled { + println!("{}", colors.warning("Plugin system is disabled")); + continue; + } + + let plugins = plugin_manager.list_plugins(); + println!("{}", colors.info(&format!("Loaded plugins: {}", plugins.len()))); + + for plugin in plugins { + println!(" {} v{} - {}", + colors.accent(&plugin.name), + plugin.version, + plugin.description); + println!(" State: {:?}, Author: {}", plugin.state, plugin.author); + if !plugin.hooks.is_empty() { + println!(" Hooks: {}", plugin.hooks.len()); + } + } + } + _ if processed_input.starts_with("plugin load ") => { + if !config.plugins.enabled { + println!("{}", colors.warning("Plugin system is disabled")); + continue; + } + + let plugin_path = processed_input.trim_start_matches("plugin load ").trim(); + if plugin_path.is_empty() { + println!("{}", colors.error("Usage: PLUGIN LOAD 'path/to/plugin.lua';")); + continue; + } + + let plugin_path = plugin_path.trim_matches(|c| c == '\'' || c == '"'); + println!("{}", colors.info(&format!("Loading plugin from: {}", plugin_path))); + + match plugin_manager.load_plugin(Path::new(plugin_path)).await { + Ok(plugin_id) => { + println!("{}", colors.success(&format!("Plugin loaded with ID: {}", plugin_id))); + // Инициализируем плагин после загрузки + if let Err(e) = plugin_manager.initialize_plugin(&plugin_id).await { + println!("{}", colors.warning(&format!("Failed to initialize plugin: {}", e))); + } + } + Err(e) => { + println!("{}", colors.error(&format!("Failed to load plugin: {}", e))); + } + } + } + _ if processed_input.starts_with("plugin unload ") => { + if !config.plugins.enabled { + println!("{}", colors.warning("Plugin system is disabled")); + continue; + } + + let plugin_id = processed_input.trim_start_matches("plugin unload ").trim(); + if plugin_id.is_empty() { + println!("{}", colors.error("Usage: PLUGIN UNLOAD 'plugin_id';")); + continue; + } + + let plugin_id = plugin_id.trim_matches(|c| c == '\'' || c == '"'); + println!("{}", colors.info(&format!("Unloading plugin: {}", plugin_id))); + + match plugin_manager.unload_plugin(plugin_id).await { + Ok(_) => { + println!("{}", colors.success("Plugin unloaded successfully")); + } + Err(e) => { + println!("{}", colors.error(&format!("Failed to unload plugin: {}", e))); + } + } + } + "plugins reload" => { + if !config.plugins.enabled { + println!("{}", colors.warning("Plugin system is disabled")); + continue; + } + + println!("{}", colors.info("Reloading all plugins...")); + match plugin_manager.load_all_plugins().await { + Ok(loaded_plugins) => { + let count: usize = loaded_plugins.len(); + println!("{}", colors.success(&format!("Reloaded {} plugins", count))); + } + Err(e) => { + println!("{}", colors.error(&format!("Failed to reload plugins: {}", e))); + } + } + } + "config show" => { + println!("{}", colors.info("Current configuration:")); + println!(" Server: {}:{}", config.server.host, config.server.port); + println!(" Data directory: {}", config.database.data_dir); + println!(" Page size: {} bytes", config.database.page_size); + println!(" Cache size: {} MB", config.database.cache_size_mb); + println!(" Max connections: {}", config.server.max_connections); + println!(" Plugins enabled: {}", config.plugins.enabled); + println!(" Plugins directory: {}", config.plugins.plugins_dir); + } + "lua.mode" => { + println!(); + println!("{}", colors.info("Type 'help' to see available Lua functions")); + println!("{}", colors.info("Type 'sql.mode' to return to SQL mode")); + println!("{}", colors.info("Try: print('Hello from Lua!') or plugins.list()")); + println!(); // Добавленная пустая строка + lua_mode = true; + } + _ => { + // Проверяем команды экспорта/импорта CSV + if processed_input.to_lowercase().starts_with("copy to") { + // Экспорт данных в CSV + match parse_copy_command(&processed_input) { + Ok((table_name, file_path, with_header)) => { + println!("{}", colors.info(&format!("Exporting table '{}' to CSV file: {}", table_name, file_path))); + + // Здесь должна быть реальная логика экспорта + match export_to_csv(&table_name, &file_path, with_header) { + Ok(rows_exported) => { + println!("{}", colors.success(&format!("Successfully exported {} rows to {}", rows_exported, file_path))); + } + Err(e) => { + println!("{}", colors.error(&format!("Export error: {}", e))); + } + } + } + Err(e) => { + println!("{}", colors.error(&format!("COPY TO parse error: {}", e))); + } + } + } else if processed_input.to_lowercase().starts_with("copy from") { + // Импорт данных из CSV + match parse_copy_command(&processed_input) { + Ok((table_name, file_path, with_header)) => { + println!("{}", colors.info(&format!("Importing CSV file '{}' to table: {}", file_path, table_name))); + + // Здесь должна быть реальная логика импорта + match import_from_csv(&table_name, &file_path, with_header) { + Ok(rows_imported) => { + println!("{}", colors.success(&format!("Successfully imported {} rows from {}", rows_imported, file_path))); + } + Err(e) => { + println!("{}", colors.error(&format!("Import error: {}", e))); + } + } + } + Err(e) => { + println!("{}", colors.error(&format!("COPY FROM parse error: {}", e))); + } + } + } else { + // Парсинг и выполнение SQL команды + match crate::parser::sql::SqlParser::new().parse(&processed_input) { + Ok(sql_query) => { + println!("{}", colors.success("Parsed SQL query successfully")); + println!("{}", colors.info(&format!("Query: {:?}", sql_query))); + + // Обработка новых команд + match sql_query { + crate::parser::sql::SqlQuery::Explain { query, analyze, .. } => { + println!("{}", colors.info(&format!("Explain {}query:", if analyze { "Analyze " } else { "" }))); + println!(" {:?}", query); + println!("{}", colors.info("Explain functionality not fully implemented yet")); + } + crate::parser::sql::SqlQuery::CreateTrigger { name, table, .. } => { + println!("{}", colors.success(&format!("Creating trigger '{}' on table '{}'", name, table))); + println!("{}", colors.info("Trigger creation not fully implemented yet")); + } + crate::parser::sql::SqlQuery::DropTrigger { name, table, .. } => { + println!("{}", colors.success(&format!("Dropping trigger '{}' from table '{}'", name, table))); + println!("{}", colors.info("Trigger deletion not fully implemented yet")); + } + crate::parser::sql::SqlQuery::ShowTables => { + println!("{}", colors.info("Showing tables (functionality not implemented yet)")); + } + crate::parser::sql::SqlQuery::DescribeTable { table_name } => { + println!("{}", colors.info(&format!("Describing table '{}' (functionality not implemented yet)", table_name))); + } + crate::parser::sql::SqlQuery::CopyTo { table, file_path, format, header, delimiter, .. } => { + let delimiter_str = delimiter.as_ref() + .map(|d| d.as_str()) + .unwrap_or("default"); + println!("{}", colors.info(&format!("Copying table '{}' to '{}' (format: {}, header: {}, delimiter: '{}')", + table, file_path, format, header, delimiter_str))); + // Здесь должен быть вызов функции экспорта + } + crate::parser::sql::SqlQuery::CopyFrom { table, file_path, format, header, delimiter, .. } => { + let delimiter_str = delimiter.as_ref() + .map(|d| d.as_str()) + .unwrap_or("default"); + println!("{}", colors.info(&format!("Copying from '{}' to table '{}' (format: {}, header: {}, delimiter: '{}')", + file_path, table, format, header, delimiter_str))); + // Здесь должен быть вызов функции импорта + } + _ => { + println!("{}", colors.warning(&format!("Command not fully implemented yet: {}", processed_input))); + } + } + } + Err(e) => { + println!("{}", colors.error(&format!("SQL Parse Error: {}", e))); + } + } + } + } + } + } + } + + Ok(()) +} + +/// Парсинг команд COPY TO/COPY FROM +fn parse_copy_command(input: &str) -> Result<(String, String, bool), String> { + let input_lower = input.to_lowercase(); + + if input_lower.starts_with("copy to ") || input_lower.starts_with("copy from ") { + let parts: Vec<&str> = input.split_whitespace().collect(); + + if parts.len() < 5 { + return Err("Invalid COPY command format".to_string()); + } + + let direction = parts[0].to_lowercase(); // "to" или "from" + let table_name = parts[1].to_string(); + let keyword = parts[2].to_lowercase(); // должно быть "to" или "from" + + // Проверяем правильность формата + if (direction == "copy" && keyword == "to" && parts[0].to_lowercase() == "copy") || + (direction == "copy" && keyword == "from" && parts[0].to_lowercase() == "copy") { + + // Извлекаем путь к файлу (может быть в кавычках) + let file_path_part = parts[3]; + let file_path = file_path_part.trim_matches(|c| c == '\'' || c == '"').to_string(); + + // Проверяем, есть ли опции + let mut with_header = false; + let mut i = 4; + while i < parts.len() { + if parts[i].to_lowercase() == "with" && i + 2 < parts.len() { + if parts[i+1].to_lowercase() == "csv" && parts[i+2].to_lowercase() == "header" { + with_header = true; + i += 3; + } else { + i += 1; + } + } else { + i += 1; + } + } + + return Ok((table_name, file_path, with_header)); + } + } + + Err("Failed to parse COPY command".to_string()) +} + +/// Экспорт данных в CSV (заглушка для демонстрации) +fn export_to_csv(table_name: &str, file_path: &str, with_header: bool) -> Result { + println!("Exporting table '{}' to CSV file '{}' (header: {})", + table_name, file_path, with_header); + + // В реальной реализации здесь будет логика экспорта данных из БД в CSV + // Для демонстрации создаем пустой файл + match File::create(file_path) { + Ok(_) => { + println!("CSV file created: {}", file_path); + Ok(0) // Возвращаем 0 экспортированных строк (заглушка) + } + Err(e) => Err(format!("Failed to create CSV file: {}", e)) + } +} + +/// Импорт данных из CSV (заглушка для демонстрации) +fn import_from_csv(table_name: &str, file_path: &str, with_header: bool) -> Result { + println!("Importing CSV file '{}' to table '{}' (header: {})", + file_path, table_name, with_header); + + // В реальной реализации здесь будет логика импорта данных из CSV в БД + // Для демонстрации проверяем существование файла + match File::open(file_path) { + Ok(file) => { + let reader = BufReader::new(file); + let mut line_count = 0; + + // Подсчитываем строки в файле + for line in reader.lines() { + match line { + Ok(_) => line_count += 1, + Err(e) => return Err(format!("Error reading CSV file: {}", e)) + } + } + + // Если есть заголовок, уменьшаем счетчик на 1 + let data_rows = if with_header && line_count > 0 { + line_count - 1 + } else { + line_count + }; + + println!("Found {} rows in CSV file", data_rows); + Ok(data_rows) // Возвращаем количество импортированных строк + } + Err(e) => Err(format!("Failed to open CSV file: {}", e)) + } +} + +/// Преобразование команд REPL в вызовы Lua функций +/// Это позволяет использовать знакомые команды в Lua режиме +fn transform_repl_command_to_lua(command: &str) -> String { + let trimmed = command.trim(); + + // Обрабатываем команды плагинов + if trimmed.starts_with("plugins ") { + let parts: Vec<&str> = trimmed.split_whitespace().collect(); + if parts.len() >= 2 { + match parts[1] { + "list" => return "plugins.list()".to_string(), + "reload" => return "plugins.reload()".to_string(), + _ => {} + } + } + } else if trimmed.starts_with("plugin ") { + let parts: Vec<&str> = trimmed.split_whitespace().collect(); + if parts.len() >= 3 { + match parts[1] { + "load" if parts.len() >= 3 => { + return format!("plugins.load_plugin(\"{}\")", parts[2]); + } + "unload" if parts.len() >= 3 => { + return format!("plugins.unload_plugin(\"{}\")", parts[2]); + } + _ => {} + } + } + } + + // Обрабатываем команды кластера + else if trimmed.starts_with("cluster ") { + let parts: Vec<&str> = trimmed.split_whitespace().collect(); + if parts.len() >= 2 { + match parts[1] { + "status" | "get_status" => return "cluster.get_status()".to_string(), + "coord_status" => return "cluster.coord_status()".to_string(), + "add_node" if parts.len() >= 4 => { + return format!("cluster.add_node(\"{}\", \"{}\")", parts[2], parts[3]); + } + "evict" if parts.len() >= 3 => { + return format!("cluster.evict(\"{}\")", parts[2]); + } + "elect_coordinator" | "elect" => return "cluster.elect_coordinator()".to_string(), + "rebalance" => return "cluster.rebalance()".to_string(), + "add_shard" if parts.len() >= 5 => { + // Упрощенный формат: cluster add_shard shard_id master_node slave1,slave2 + let shard_id = parts[2]; + let master = parts[3]; + let slaves_str = if parts.len() > 4 { parts[4] } else { "" }; + let slaves: Vec<&str> = slaves_str.split(',').filter(|s| !s.is_empty()).collect(); + + if slaves.is_empty() { + return format!("cluster.add_shard(\"{}\", \"{}\", {{}})", shard_id, master); + } else { + let slaves_lua = slaves.iter() + .enumerate() + .map(|(i, slave)| format!("[{}] = \"{}\"", i + 1, slave)) + .collect::>() + .join(", "); + return format!("cluster.add_shard(\"{}\", \"{}\", {{{}}})", shard_id, master, slaves_lua); + } + } + "remove_shard" if parts.len() >= 3 => { + return format!("cluster.remove_shard(\"{}\")", parts[2]); + } + "start_replication" if parts.len() >= 4 => { + return format!("cluster.start_replication(\"{}\", \"{}\")", parts[2], parts[3]); + } + _ => {} + } + } + } + + // Обработка команд конфигурации + else if trimmed.starts_with("config ") { + let parts: Vec<&str> = trimmed.split_whitespace().collect(); + if parts.len() >= 2 { + match parts[1] { + "show" => return "print('Use config.toml file for configuration')".to_string(), + _ => {} + } + } + } + + // Если команда уже выглядит как вызов Lua функции, оставляем как есть + // Проверяем на наличие скобок + if trimmed.contains('(') && trimmed.contains(')') { + return trimmed.to_string(); + } + + // Возвращаем как есть (будет обработано как Lua код) + trimmed.to_string() +}