// 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() }