flusql/src/cli.rs

1820 lines
85 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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>]) -> String {
if rows.is_empty() {
return String::from("Нет данных");
}
let colors = get_color_codes();
// Рассчитываем ширину колонок с учетом отображения Unicode
let mut col_widths: Vec<usize> = 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<String>,
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<String> {
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<String> {
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<String> {
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<String, std::io::Error> {
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<String, std::io::Error> {
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<dyn std::error::Error>> {
// Инициализация истории команд
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<String> = 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::<usize>() {
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<String> = 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(&current_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(&current_session);
for (i, cmd) in history_list.iter().enumerate() {
println!(" {}: {}", i + 1, cmd);
}
}
"history clear" => {
// Очистить историю
history.clear(&current_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<usize, String> {
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<usize, String> {
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::<Vec<String>>()
.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()
}