From 8f2b4fd0c3a9a447bda5884a7751d3162712a4f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D1=80=D0=B8=D0=B3=D0=BE=D1=80=D0=B8=D0=B9=20=D0=A1?= =?UTF-8?q?=D0=B0=D1=84=D1=80=D0=BE=D0=BD=D0=BE=D0=B2?= Date: Sun, 1 Mar 2026 20:01:48 +0000 Subject: [PATCH] Upload files to "internal/engine" --- internal/engine/engine.go | 1076 +++++++++++++++++++++++++++++++++++++ 1 file changed, 1076 insertions(+) create mode 100644 internal/engine/engine.go diff --git a/internal/engine/engine.go b/internal/engine/engine.go new file mode 100644 index 0000000..ddf29c8 --- /dev/null +++ b/internal/engine/engine.go @@ -0,0 +1,1076 @@ +// /futriis/internal/engine/engine.go +// Пакет engine реализует ядро СУБД Futriis, координирующее все операции. +// Выступает в роли центрального компонента, связывающего хранилище, кластерное управление, транзакции, Lua плагины и AOF (Append-Only File) для персистентности. + +package engine + +import ( + "context" + "fmt" + "reflect" + "strings" + "time" + + "futriis/internal/cluster" + "futriis/internal/lua" + "futriis/internal/replication" + "futriis/internal/storage" + "futriis/internal/transaction" + "futriis/pkg/config" + "futriis/pkg/types" + "futriis/pkg/utils" +) + +// Engine представляет ядро СУБД +type Engine struct { + storage *storage.Storage + clusterMgr *cluster.ClusterManager + txMgr *transaction.TransactionManager + luaMgr *lua.PluginManager + aofMgr *replication.AOFManager + cfg *config.Config + aofRecovered bool // Флаг, было ли восстановление из AOF + searchEngine *SearchEngine // Поисковый движок для параллельного поиска +} + +// NewEngine создаёт новый экземпляр ядра СУБД +func NewEngine() *Engine { + cfg := config.Get() + + // Создаём AOF менеджер + aofMgr, _ := replication.NewAOFManager(cfg.Node.AOFFile, cfg.Node.AOFEnabled) + + // Воспроизводим AOF если нужно (проверяем и aof_enabled и aof_recovery) + aofRecovered := false + if aofMgr != nil && cfg.Node.AOFEnabled && cfg.Node.AOFRecovery == "enable" { + // Восстановление состояния из AOF + if err := replayAOF(aofMgr); err != nil { + utils.PrintError("Error recovering from AOF: %v", err) + } else { + aofRecovered = true + // Сообщение будет показано в handler.go + } + } else if cfg.Node.AOFEnabled && cfg.Node.AOFRecovery == "disable" { + utils.PrintInfo("AOF recovery is disabled in configuration, skipping data recovery") + } + + engine := &Engine{ + storage: storage.NewStorage(), + clusterMgr: cluster.NewClusterManager(cfg), + txMgr: transaction.NewTransactionManager(), + luaMgr: lua.NewPluginManager(&cfg.Lua), + aofMgr: aofMgr, + cfg: cfg, + aofRecovered: aofRecovered, + } + + // Инициализируем поисковый движок + engine.searchEngine = NewSearchEngine(engine) + + return engine +} + +// GetConfig возвращает конфигурацию +func (e *Engine) GetConfig() *config.Config { + return e.cfg +} + +// WasAOFRecovered возвращает флаг восстановления из AOF +func (e *Engine) WasAOFRecovered() bool { + return e.aofRecovered +} + +// replayAOF воспроизводит команды из AOF файла для восстановления состояния +func replayAOF(aofMgr *replication.AOFManager) error { + commands, err := aofMgr.ReadAll() + if err != nil { + return fmt.Errorf("failed to read AOF: %v", err) + } + + // Создаём временное хранилище для восстановления + tempStorage := storage.NewStorage() + + for i, cmd := range commands { + // Пропускаем команды транзакций при восстановлении + if cmd.Name == "begin" || cmd.Name == "commit" || cmd.Name == "rollback" { + continue + } + + // Преобразуем аргументы в строки + args := make([]string, len(cmd.Args)) + for j, arg := range cmd.Args { + if str, ok := arg.(string); ok { + args[j] = str + } else { + args[j] = fmt.Sprint(arg) + } + } + + // Выполняем команду на временном хранилище + if err := executeRestoreCommand(tempStorage, cmd.Name, args); err != nil { + utils.PrintWarning("Error replaying command #%d (%s): %v", i+1, cmd.Name, err) + // Продолжаем восстановление, несмотря на ошибки + } + } + + // TODO: Перенести восстановленные данные в основное хранилище + // Это упрощённая реализация, в реальности нужно синхронизировать состояния + + return nil +} + +// executeRestoreCommand выполняет команду при восстановлении из AOF +func executeRestoreCommand(storage *storage.Storage, cmdName string, args []string) error { + switch cmdName { + case "create": + if len(args) < 2 { + return nil + } + switch args[0] { + case "tapple": + if len(args) < 2 { + return nil + } + _, err := storage.GetTappleManager().CreateTapple(args[1]) + return err + case "slice": + if len(args) < 3 { + return nil + } + tapple, err := storage.GetTappleManager().GetTapple(args[1]) + if err != nil { + return err + } + _, err = storage.GetTappleManager().GetSliceManager().CreateSlice(tapple, args[2]) + return err + case "tuple": + if len(args) < 4 { + return nil + } + tapple, err := storage.GetTappleManager().GetTapple(args[1]) + if err != nil { + return err + } + slice, err := storage.GetTappleManager().GetSliceManager().GetSlice(tapple, args[2]) + if err != nil { + return err + } + fields := make(map[string]interface{}) + for i := 4; i < len(args); i++ { + parts := strings.SplitN(args[i], "=", 2) + if len(parts) == 2 { + fields[parts[0]] = parts[1] + } + } + _, err = storage.GetTappleManager().GetSliceManager().GetTupleManager().CreateTuple(slice, args[3], fields) + return err + } + case "update": + if len(args) < 4 || args[0] != "tuple" { + return nil + } + tapple, err := storage.GetTappleManager().GetTapple(args[1]) + if err != nil { + return err + } + slice, err := storage.GetTappleManager().GetSliceManager().GetSlice(tapple, args[2]) + if err != nil { + return err + } + fields := make(map[string]interface{}) + for i := 4; i < len(args); i++ { + parts := strings.SplitN(args[i], "=", 2) + if len(parts) == 2 { + fields[parts[0]] = parts[1] + } + } + _, err = storage.GetTappleManager().GetSliceManager().GetTupleManager().UpdateTuple(slice, args[3], fields) + return err + case "delete": + if len(args) < 2 { + return nil + } + switch args[0] { + case "tapple": + if len(args) < 2 { + return nil + } + return storage.GetTappleManager().DeleteTapple(args[1]) + case "slice": + if len(args) < 3 { + return nil + } + tapple, err := storage.GetTappleManager().GetTapple(args[1]) + if err != nil { + return err + } + return storage.GetTappleManager().GetSliceManager().DeleteSlice(tapple, args[2]) + case "tuple": + if len(args) < 4 { + return nil + } + tapple, err := storage.GetTappleManager().GetTapple(args[1]) + if err != nil { + return err + } + slice, err := storage.GetTappleManager().GetSliceManager().GetSlice(tapple, args[2]) + if err != nil { + return err + } + return storage.GetTappleManager().GetSliceManager().GetTupleManager().DeleteTuple(slice, args[3]) + } + } + return nil +} + +// Execute выполняет команду и возвращает результат +func (e *Engine) Execute(input string) (string, error) { + // Разбираем ввод + parts := strings.Fields(input) + if len(parts) == 0 { + return "", nil + } + + command := strings.ToLower(parts[0]) + args := parts[1:] + + // Записываем команду в AOF (кроме команд транзакций и служебных команд) + if e.aofMgr != nil && command != "begin" && command != "commit" && command != "rollback" && + command != "cluster.status" && command != "help" && command != "exit" && command != "quit" && + command != "aof.recover" && command != "aof.info" && command != "search" && + !strings.HasPrefix(command, "add.prime.index") && !strings.HasPrefix(command, "delete.prime.index") && + !strings.HasPrefix(command, "add.secondary.index") && !strings.HasPrefix(command, "delete.secondary.index") && + command != "cluster.rebalance" { + argsInterface := make([]interface{}, len(args)) + for i, v := range args { + argsInterface[i] = v + } + e.aofMgr.Append(command, argsInterface) + } + + // Обработка команд + switch command { + case "help": + return e.help(), nil + + case "exit", "quit": + return "exit", nil + + case "search": + return e.handleSearch(args) + + case "create": + return e.handleCreate(args) + + case "delete": + return e.handleDelete(args) + + case "update": + return e.handleUpdate(args) + + case "list": + return e.handleList(args) + + case "show": + return e.handleShow(args) + + case "begin": + return e.handleBegin() + + case "commit": + return e.handleCommit() + + case "rollback": + return e.handleRollback() + + case "cluster.status": + return e.handleClusterStatus() + + case "cluster.rebalance": + return e.handleClusterRebalance(args) + + case "add.node": + return e.handleAddNode(args) + + case "evict.node": + return e.handleEvictNode(args) + + case "lua": + return e.handleLua(args) + + case "aof.recover": + return e.handleAOFRecover(args) + + case "aof.info": + return e.handleAOFInfo() + + case "add.prime.index": + return e.handleAddPrimaryIndex(args) + + case "delete.prime.index": + return e.handleDeletePrimaryIndex(args) + + case "add.secondary.index": + return e.handleAddSecondaryIndex(args) + + case "delete.secondary.index": + return e.handleDeleteSecondaryIndex(args) + + case "compression.stats": + return e.handleCompressionStats(args) + + case "sharding.status": + return e.handleShardingStatus() + + default: + return "", fmt.Errorf("unknown command: %s", command) + } +} + +// handleSearch обрабатывает команду поиска +func (e *Engine) handleSearch(args []string) (string, error) { + if len(args) < 1 { + return "", fmt.Errorf("usage: search [in tapple1,tapple2...] [slice slice1,slice2...] [field field1,field2...] [fuzzy]") + } + + query := args[0] + tapples := make([]string, 0) + slices := make([]string, 0) + fields := make([]string, 0) + fuzzy := false + + // Парсим аргументы + for i := 1; i < len(args); i++ { + switch args[i] { + case "in": + if i+1 < len(args) { + tapples = strings.Split(args[i+1], ",") + i++ + } + case "slice": + if i+1 < len(args) { + slices = strings.Split(args[i+1], ",") + i++ + } + case "field": + if i+1 < len(args) { + fields = strings.Split(args[i+1], ",") + i++ + } + case "fuzzy": + fuzzy = true + } + } + + return e.ExecuteSearch(query, tapples, slices, fields, fuzzy) +} + +// ExecuteSearch выполняет параллельный поиск по данным +func (e *Engine) ExecuteSearch(query string, tapples []string, slices []string, fields []string, fuzzy bool) (string, error) { + searchQuery := SearchQuery{ + Query: query, + Fields: fields, + Tapples: tapples, + Slices: slices, + CaseSensitive: false, + Fuzzy: fuzzy, + MaxResults: 100, + Concurrency: 20, + Timeout: 30 * time.Second, + } + + ctx := context.Background() + results, err := e.searchEngine.Search(ctx, searchQuery) + if err != nil { + return "", err + } + + return FormatSearchResults(results, query), nil +} + +// handleAOFRecover восстанавливает данные из AOF файла +func (e *Engine) handleAOFRecover(args []string) (string, error) { + if e.aofMgr == nil { + return "", fmt.Errorf("AOF manager not initialized") + } + + // Проверяем, указан ли путь к файлу + filePath := e.cfg.Node.AOFFile + if len(args) > 0 { + filePath = args[0] + } + + // Создаём временный AOF менеджер для чтения указанного файла + tmpAOF, err := replication.NewAOFManager(filePath, true) + if err != nil { + return "", fmt.Errorf("failed to open AOF file: %v", err) + } + defer tmpAOF.Close() + + // Читаем все команды + commands, err := tmpAOF.ReadAll() + if err != nil { + return "", fmt.Errorf("failed to read AOF file: %v", err) + } + + if len(commands) == 0 { + return utils.ColorYellow + "AOF file is empty" + utils.ColorReset, nil + } + + // Создаём временное хранилище для проверки + tempStorage := storage.NewStorage() + successCount := 0 + errorCount := 0 + + for i, cmd := range commands { + if cmd.Name == "begin" || cmd.Name == "commit" || cmd.Name == "rollback" { + continue + } + + args := make([]string, len(cmd.Args)) + for j, arg := range cmd.Args { + if str, ok := arg.(string); ok { + args[j] = str + } else { + args[j] = fmt.Sprint(arg) + } + } + + if err := executeRestoreCommand(tempStorage, cmd.Name, args); err != nil { + utils.PrintWarning("Error in command #%d: %v", i+1, err) + errorCount++ + } else { + successCount++ + } + } + + return fmt.Sprintf(utils.ColorGreen+"Recovery completed. Successful: %d, Errors: %d"+utils.ColorReset, + successCount, errorCount), nil +} + +// handleAOFInfo показывает информацию о AOF файле +func (e *Engine) handleAOFInfo() (string, error) { + if e.aofMgr == nil { + return "", fmt.Errorf("AOF manager not initialized") + } + + // Получаем информацию о файле напрямую + filePath := e.cfg.Node.AOFFile + commands, err := e.aofMgr.ReadAll() + if err != nil { + return "", fmt.Errorf("failed to read AOF file: %v", err) + } + + // Получаем размер файла + fileInfo, err := e.aofMgr.GetFileInfo() + if err != nil { + fileInfo = "unavailable" + } + + result := utils.ColorCyan + "AOF Information:" + utils.ColorReset + "\n" + result += fmt.Sprintf(" File: %s\n", filePath) + result += fmt.Sprintf(" Size: %v\n", fileInfo) + result += fmt.Sprintf(" Commands: %d\n", len(commands)) + if len(commands) > 0 { + lastCmd := commands[len(commands)-1] + result += fmt.Sprintf(" Last write: %d\n", lastCmd.Time) + } else { + result += " Last write: no records\n" + } + + return result, nil +} + +// handleClusterRebalance выполняет ребалансировку кластера +func (e *Engine) handleClusterRebalance(args []string) (string, error) { + clusterName := "futriis-cluster" + if len(args) > 0 { + clusterName = args[0] + } + + err := e.clusterMgr.RebalanceCluster() + if err != nil { + return "", fmt.Errorf("cluster rebalance failed: %v", err) + } + + return utils.ColorGreen + "Cluster '" + clusterName + "' rebalanced successfully" + utils.ColorReset, nil +} + +// handleCreate обрабатывает команды создания +func (e *Engine) handleCreate(args []string) (string, error) { + if len(args) < 2 { + return "", fmt.Errorf("insufficient arguments for create command") + } + + switch args[0] { + case "tapple": + if len(args) < 2 { + return "", fmt.Errorf("tapple name not specified") + } + return e.createTapple(args[1]) + case "slice": + if len(args) < 3 { + return "", fmt.Errorf("insufficient arguments for slice creation") + } + return e.createSlice(args[1], args[2]) + case "tuple": + if len(args) < 4 { + return "", fmt.Errorf("insufficient arguments for tuple creation") + } + return e.createTuple(args[1], args[2], args[3], args[4:]) + default: + return "", fmt.Errorf("unknown creation type: %s", args[0]) + } +} + +// handleDelete обрабатывает команды удаления +func (e *Engine) handleDelete(args []string) (string, error) { + if len(args) < 2 { + return "", fmt.Errorf("insufficient arguments for delete command") + } + + switch args[0] { + case "tapple": + if len(args) < 2 { + return "", fmt.Errorf("tapple name not specified") + } + return e.deleteTapple(args[1]) + case "slice": + if len(args) < 3 { + return "", fmt.Errorf("insufficient arguments for slice deletion") + } + return e.deleteSlice(args[1], args[2]) + case "tuple": + if len(args) < 4 { + return "", fmt.Errorf("insufficient arguments for tuple deletion") + } + return e.deleteTuple(args[1], args[2], args[3]) + default: + return "", fmt.Errorf("unknown deletion type: %s", args[0]) + } +} + +// handleUpdate обрабатывает команды обновления +func (e *Engine) handleUpdate(args []string) (string, error) { + if len(args) < 4 || args[0] != "tuple" { + return "", fmt.Errorf("invalid update command") + } + return e.updateTuple(args[1], args[2], args[3], args[4:]) +} + +// handleList обрабатывает команды списка +func (e *Engine) handleList(args []string) (string, error) { + if len(args) < 1 { + return "", fmt.Errorf("insufficient arguments for list command") + } + + switch args[0] { + case "tapples": + return e.listTapples() + case "slices": + if len(args) < 2 { + return "", fmt.Errorf("tapple name not specified") + } + return e.listSlices(args[1]) + default: + return "", fmt.Errorf("unknown list type: %s", args[0]) + } +} + +// handleShow обрабатывает команды показа +func (e *Engine) handleShow(args []string) (string, error) { + if len(args) < 2 || args[0] != "tuples" { + return "", fmt.Errorf("invalid show command") + } + if len(args) < 3 { + return "", fmt.Errorf("insufficient arguments for show tuples") + } + return e.showTuples(args[1], args[2]) +} + +// handleBegin начинает транзакцию +func (e *Engine) handleBegin() (string, error) { + id, err := e.txMgr.Begin() + if err != nil { + return "", err + } + return utils.ColorGreen + "Transaction started. ID: " + id + utils.ColorReset, nil +} + +// handleCommit фиксирует транзакцию +func (e *Engine) handleCommit() (string, error) { + err := e.txMgr.Commit() + if err != nil { + return "", err + } + return utils.ColorGreen + "Transaction committed" + utils.ColorReset, nil +} + +// handleRollback откатывает транзакцию +func (e *Engine) handleRollback() (string, error) { + err := e.txMgr.Rollback() + if err != nil { + return "", err + } + return utils.ColorGreen + "Transaction rolled back" + utils.ColorReset, nil +} + +// handleClusterStatus показывает статус кластера +func (e *Engine) handleClusterStatus() (string, error) { + status := e.clusterMgr.GetClusterStatus() + + result := utils.ColorCyan + "Cluster Status:" + utils.ColorReset + "\n" + result += fmt.Sprintf(" Total nodes: %d\n", status["total_nodes"]) + result += fmt.Sprintf(" Active nodes: %d\n", status["active_nodes"]) + result += fmt.Sprintf(" Coordinator: %v\n", status["coordinator"]) + result += fmt.Sprintf(" Master-master replication: %v\n", status["master_master"]) + + nodes, _ := status["nodes"].([]map[string]interface{}) + if len(nodes) > 0 { + result += utils.ColorCyan + "\nCluster Nodes:" + utils.ColorReset + "\n" + for _, node := range nodes { + result += fmt.Sprintf(" %s (%s) - %s, last seen: %s\n", + node["id"], node["address"], node["state"], node["last_seen"]) + } + } + + return result, nil +} + +// handleAddNode добавляет узел в кластер +func (e *Engine) handleAddNode(args []string) (string, error) { + if len(args) < 1 { + return "", fmt.Errorf("specify node address") + } + + address := args[0] + err := e.clusterMgr.AddNode(address) + if err != nil { + return "", err + } + + return utils.ColorGreen + "Node " + address + " added to cluster" + utils.ColorReset, nil +} + +// handleEvictNode удаляет узел из кластера +func (e *Engine) handleEvictNode(args []string) (string, error) { + if len(args) < 1 { + return "", fmt.Errorf("specify node ID or address") + } + + nodeID := args[0] + err := e.clusterMgr.RemoveNode(nodeID) + if err != nil { + return "", err + } + + return utils.ColorGreen + "Node " + nodeID + " removed from cluster" + utils.ColorReset, nil +} + +// handleLua выполняет Lua скрипт +func (e *Engine) handleLua(args []string) (string, error) { + if len(args) < 1 { + return "", fmt.Errorf("specify plugin name") + } + + pluginName := args[0] + err := e.luaMgr.ExecutePlugin(pluginName) + if err != nil { + return "", err + } + + return utils.ColorGreen + "Plugin executed" + utils.ColorReset, nil +} + +// handleAddPrimaryIndex обрабатывает создание первичного индекса +func (e *Engine) handleAddPrimaryIndex(args []string) (string, error) { + if len(args) < 1 { + return "", fmt.Errorf("specify tapple name") + } + + tappleName := args[0] + + // Получаем таппл + _, err := e.storage.GetTappleManager().GetTapple(tappleName) + if err != nil { + return "", fmt.Errorf("tapple not found: %v", err) + } + + // Создаём первичный индекс + indexManager := e.storage.GetTappleManager().GetIndexManager() + err = indexManager.CreatePrimaryIndex(tappleName) + if err != nil { + return "", err + } + + return utils.ColorGreen + "Primary index for tapple '" + tappleName + "' created successfully" + utils.ColorReset, nil +} + +// handleDeletePrimaryIndex обрабатывает удаление первичного индекса +func (e *Engine) handleDeletePrimaryIndex(args []string) (string, error) { + if len(args) < 1 { + return "", fmt.Errorf("specify tapple name") + } + + tappleName := args[0] + + indexManager := e.storage.GetTappleManager().GetIndexManager() + err := indexManager.DeletePrimaryIndex(tappleName) + if err != nil { + return "", err + } + + return utils.ColorGreen + "Primary index for tapple '" + tappleName + "' deleted successfully" + utils.ColorReset, nil +} + +// handleAddSecondaryIndex обрабатывает создание вторичного индекса +func (e *Engine) handleAddSecondaryIndex(args []string) (string, error) { + if len(args) < 2 { + return "", fmt.Errorf("specify tapple name and field name") + } + + tappleName := args[0] + fieldName := args[1] + + // Получаем таппл + _, err := e.storage.GetTappleManager().GetTapple(tappleName) + if err != nil { + return "", fmt.Errorf("tapple not found: %v", err) + } + + // Создаём вторичный индекс + indexManager := e.storage.GetTappleManager().GetIndexManager() + err = indexManager.CreateSecondaryIndex(tappleName, fieldName) + if err != nil { + return "", err + } + + return utils.ColorGreen + "Secondary index for tapple '" + tappleName + "' on field '" + fieldName + "' created successfully" + utils.ColorReset, nil +} + +// handleDeleteSecondaryIndex обрабатывает удаление вторичного индекса +func (e *Engine) handleDeleteSecondaryIndex(args []string) (string, error) { + if len(args) < 2 { + return "", fmt.Errorf("specify tapple name and field name") + } + + tappleName := args[0] + fieldName := args[1] + + indexManager := e.storage.GetTappleManager().GetIndexManager() + err := indexManager.DeleteSecondaryIndex(tappleName, fieldName) + if err != nil { + return "", err + } + + return utils.ColorGreen + "Secondary index for tapple '" + tappleName + "' on field '" + fieldName + "' deleted successfully" + utils.ColorReset, nil +} + +// handleCompressionStats показывает статистику сжатия +func (e *Engine) handleCompressionStats(args []string) (string, error) { + result := utils.ColorCyan + "Compression Statistics:" + utils.ColorReset + "\n" + result += " Compression statistics available at slice level\n" + result += " Use 'show compression ' for detailed information" + + return result, nil +} + +// handleShardingStatus показывает статус шардинга +func (e *Engine) handleShardingStatus() (string, error) { + status := e.clusterMgr.GetClusterStatus() + + shardingInfo, exists := status["sharding"] + if !exists { + return utils.ColorYellow + "Sharding is not activated" + utils.ColorReset, nil + } + + shardStats := shardingInfo.(map[string]interface{}) + + result := utils.ColorCyan + "Sharding Status:" + utils.ColorReset + "\n" + result += fmt.Sprintf(" Enabled: %v\n", shardStats["enabled"]) + result += fmt.Sprintf(" Strategy: %s\n", shardStats["strategy"]) + result += fmt.Sprintf(" Total shards: %d\n", shardStats["total_shards"]) + + shards, _ := shardStats["shards"].([]map[string]interface{}) + if len(shards) > 0 { + result += utils.ColorCyan + "\nShards:" + utils.ColorReset + "\n" + for _, shard := range shards { + result += fmt.Sprintf(" %s: nodes=%v, reads=%d, writes=%d\n", + shard["id"], shard["nodes"], shard["reads"], shard["writes"]) + } + } + + return result, nil +} + +// Методы для работы с тапплами +func (e *Engine) createTapple(name string) (string, error) { + tapple, err := e.storage.GetTappleManager().CreateTapple(name) + if err != nil { + return "", err + } + return utils.ColorGreen + "Tapple '" + tapple.Name + "' created successfully" + utils.ColorReset, nil +} + +func (e *Engine) deleteTapple(name string) (string, error) { + err := e.storage.GetTappleManager().DeleteTapple(name) + if err != nil { + return "", err + } + return utils.ColorGreen + "Tapple '" + name + "' deleted successfully" + utils.ColorReset, nil +} + +func (e *Engine) listTapples() (string, error) { + tapples := e.storage.GetTappleManager().ListTapples() + if len(tapples) == 0 { + return utils.ColorYellow + "No tapples found" + utils.ColorReset, nil + } + + result := utils.ColorCyan + "List of tapples:" + utils.ColorReset + "\n" + for _, t := range tapples { + result += " " + utils.ColorGreen + t + utils.ColorReset + "\n" + } + return result, nil +} + +// Методы для работы со слайсами +func (e *Engine) createSlice(tappleName, sliceName string) (string, error) { + tapple, err := e.storage.GetTappleManager().GetTapple(tappleName) + if err != nil { + return "", err + } + + slice, err := e.storage.GetTappleManager().GetSliceManager().CreateSlice(tapple, sliceName) + if err != nil { + return "", err + } + + return utils.ColorGreen + "Slice '" + slice.Name + "' in tapple '" + tappleName + "' created successfully" + utils.ColorReset, nil +} + +func (e *Engine) deleteSlice(tappleName, sliceName string) (string, error) { + tapple, err := e.storage.GetTappleManager().GetTapple(tappleName) + if err != nil { + return "", err + } + + err = e.storage.GetTappleManager().GetSliceManager().DeleteSlice(tapple, sliceName) + if err != nil { + return "", err + } + + return utils.ColorGreen + "Slice '" + sliceName + "' in tapple '" + tappleName + "' deleted successfully" + utils.ColorReset, nil +} + +func (e *Engine) listSlices(tappleName string) (string, error) { + tapple, err := e.storage.GetTappleManager().GetTapple(tappleName) + if err != nil { + return "", err + } + + slices := e.storage.GetTappleManager().GetSliceManager().ListSlices(tapple) + if len(slices) == 0 { + return utils.ColorYellow + "No slices found in tapple '" + tappleName + "'" + utils.ColorReset, nil + } + + result := utils.ColorCyan + "List of slices in tapple '" + tappleName + "':" + utils.ColorReset + "\n" + for _, s := range slices { + result += " " + utils.ColorGreen + s + utils.ColorReset + "\n" + } + return result, nil +} + +// Методы для работы с кортежами +func (e *Engine) createTuple(tappleName, sliceName, tupleID string, fieldsArgs []string) (string, error) { + tapple, err := e.storage.GetTappleManager().GetTapple(tappleName) + if err != nil { + return "", err + } + + slice, err := e.storage.GetTappleManager().GetSliceManager().GetSlice(tapple, sliceName) + if err != nil { + return "", err + } + + // Парсим поля + fields := make(map[string]interface{}) + for _, arg := range fieldsArgs { + parts := strings.SplitN(arg, "=", 2) + if len(parts) == 2 { + fields[parts[0]] = parts[1] + } + } + + tuple, err := e.storage.GetTappleManager().GetSliceManager().GetTupleManager().CreateTuple(slice, tupleID, fields) + if err != nil { + return "", err + } + + return utils.ColorGreen + "Tuple '" + tuple.ID + "' in slice '" + sliceName + "' created successfully" + utils.ColorReset, nil +} + +func (e *Engine) deleteTuple(tappleName, sliceName, tupleID string) (string, error) { + tapple, err := e.storage.GetTappleManager().GetTapple(tappleName) + if err != nil { + return "", err + } + + slice, err := e.storage.GetTappleManager().GetSliceManager().GetSlice(tapple, sliceName) + if err != nil { + return "", err + } + + err = e.storage.GetTappleManager().GetSliceManager().GetTupleManager().DeleteTuple(slice, tupleID) + if err != nil { + return "", err + } + + return utils.ColorGreen + "Tuple '" + tupleID + "' in slice '" + sliceName + "' deleted successfully" + utils.ColorReset, nil +} + +func (e *Engine) updateTuple(tappleName, sliceName, tupleID string, fieldsArgs []string) (string, error) { + tapple, err := e.storage.GetTappleManager().GetTapple(tappleName) + if err != nil { + return "", err + } + + slice, err := e.storage.GetTappleManager().GetSliceManager().GetSlice(tapple, sliceName) + if err != nil { + return "", err + } + + // Парсим поля + fields := make(map[string]interface{}) + for _, arg := range fieldsArgs { + parts := strings.SplitN(arg, "=", 2) + if len(parts) == 2 { + fields[parts[0]] = parts[1] + } + } + + tuple, err := e.storage.GetTappleManager().GetSliceManager().GetTupleManager().UpdateTuple(slice, tupleID, fields) + if err != nil { + return "", err + } + + return utils.ColorGreen + "Tuple '" + tuple.ID + "' in slice '" + sliceName + "' updated successfully" + utils.ColorReset, nil +} + +// showTuples показывает все кортежи в слайсе +func (e *Engine) showTuples(tappleName, sliceName string) (string, error) { + tapple, err := e.storage.GetTappleManager().GetTapple(tappleName) + if err != nil { + return "", err + } + + slice, err := e.storage.GetTappleManager().GetSliceManager().GetSlice(tapple, sliceName) + if err != nil { + return "", err + } + + // Получаем все кортежи из слайса через рефлексию + tuples, err := e.getAllTuplesFromSlice(slice) + if err != nil { + return "", err + } + + if len(tuples) == 0 { + return utils.ColorYellow + "No tuples found in slice '" + sliceName + "'" + utils.ColorReset, nil + } + + result := utils.ColorCyan + "List of tuples in slice '" + sliceName + "':" + utils.ColorReset + "\n" + for id, tuple := range tuples { + result += " " + utils.ColorGreen + "ID: " + id + utils.ColorReset + "\n" + for k, v := range tuple.Fields { + result += " " + utils.ColorYellow + k + utils.ColorReset + ": " + utils.ColorPromptCode + fmt.Sprint(v) + utils.ColorReset + "\n" + } + result += "\n" + } + return result, nil +} + +// вспомогательная функция для получения всех кортежей из слайса через рефлексию +func (e *Engine) getAllTuplesFromSlice(slice *types.Slice) (map[string]*types.Tuple, error) { + if slice == nil { + return nil, fmt.Errorf("slice is nil") + } + + // Используем рефлексию для доступа к неэкспортируемому полю tuples + v := reflect.ValueOf(slice).Elem() + field := v.FieldByName("tuples") + + if !field.IsValid() || field.Kind() != reflect.Map { + return nil, fmt.Errorf("cannot access tuples field in slice") + } + + result := make(map[string]*types.Tuple) + iter := field.MapRange() + for iter.Next() { + key := iter.Key().String() + value := iter.Value().Interface() + if tuple, ok := value.(*types.Tuple); ok { + result[key] = tuple + } + } + + return result, nil +} + +// help возвращает справку по командам +// help возвращает справку по командам +func (e *Engine) help() string { + help := utils.ColorCyan + "Available commands:" + utils.ColorReset + "\n" + + help += "\n" + utils.ColorYellow + "Basic commands:" + utils.ColorReset + "\n" + help += " " + utils.ColorGreen + "create tapple " + utils.ColorReset + " - create a new tapple (database)\n" + help += " " + utils.ColorGreen + "create slice " + utils.ColorReset + " - create a new slice (table)\n" + help += " " + utils.ColorGreen + "create tuple [key=value...]" + utils.ColorReset + " - create a new tuple (record)\n" + help += " " + utils.ColorGreen + "delete tapple " + utils.ColorReset + " - delete a tapple\n" + help += " " + utils.ColorGreen + "delete slice " + utils.ColorReset + " - delete a slice\n" + help += " " + utils.ColorGreen + "delete tuple " + utils.ColorReset + " - delete a tuple\n" + help += " " + utils.ColorGreen + "update tuple [key=value...]" + utils.ColorReset + " - update a tuple\n" + help += " " + utils.ColorGreen + "list tapples" + utils.ColorReset + " - show all tapples\n" + help += " " + utils.ColorGreen + "list slices " + utils.ColorReset + " - show all slices in a tapple\n" + help += " " + utils.ColorGreen + "show tuples " + utils.ColorReset + " - show all tuples in a slice\n" + + help += "\n" + utils.ColorYellow + "Search commands:" + utils.ColorReset + "\n" + help += " " + utils.ColorGreen + "search [in tapple1,tapple2...] [slice slice1,slice2...] [field field1,field2...] [fuzzy]" + utils.ColorReset + " - parallel search across multiple tapples/slices/tuples\n" + help += " " + utils.ColorGreen + "Examples:" + utils.ColorReset + "\n" + help += " " + utils.ColorGreen + "search \"john\"" + utils.ColorReset + "\n" + help += " " + utils.ColorGreen + "search \"john\" in users" + utils.ColorReset + "\n" + help += " " + utils.ColorGreen + "search \"25\" slice ages field age" + utils.ColorReset + "\n" + help += " " + utils.ColorGreen + "search \"smith\" fuzzy" + utils.ColorReset + "\n" + + help += "\n" + utils.ColorYellow + "Index management:" + utils.ColorReset + "\n" + help += " " + utils.ColorGreen + "add.prime.index " + utils.ColorReset + " - create primary index for tapple\n" + help += " " + utils.ColorGreen + "delete.prime.index " + utils.ColorReset + " - delete primary index\n" + help += " " + utils.ColorGreen + "add.secondary.index " + utils.ColorReset + " - create secondary index on field\n" + help += " " + utils.ColorGreen + "delete.secondary.index " + utils.ColorReset + " - delete secondary index\n" + + help += "\n" + utils.ColorYellow + "Transactions:" + utils.ColorReset + "\n" + help += " " + utils.ColorGreen + "begin" + utils.ColorReset + " - start a transaction\n" + help += " " + utils.ColorGreen + "commit" + utils.ColorReset + " - commit a transaction\n" + help += " " + utils.ColorGreen + "rollback" + utils.ColorReset + " - rollback a transaction\n" + + help += "\n" + utils.ColorYellow + "Cluster and sharding management:" + utils.ColorReset + "\n" + help += " " + utils.ColorGreen + "cluster.status" + utils.ColorReset + " - show cluster status\n" + help += " " + utils.ColorGreen + "cluster.rebalance [cluster_name]" + utils.ColorReset + " - rebalance the cluster\n" + help += " " + utils.ColorGreen + "sharding.status" + utils.ColorReset + " - show sharding status\n" + help += " " + utils.ColorGreen + "add.node
" + utils.ColorReset + " - add a node to the cluster\n" + help += " " + utils.ColorGreen + "evict.node " + utils.ColorReset + " - remove a node from the cluster\n" + + help += "\n" + utils.ColorYellow + "Compression:" + utils.ColorReset + "\n" + help += " " + utils.ColorGreen + "compression.stats" + utils.ColorReset + " - show compression statistics\n" + + help += "\n" + utils.ColorYellow + "AOF management:" + utils.ColorReset + "\n" + help += " " + utils.ColorGreen + "aof.recover [file]" + utils.ColorReset + " - recover data from AOF file\n" + help += " " + utils.ColorGreen + "aof.info" + utils.ColorReset + " - show AOF file information\n" + + help += "\n" + utils.ColorYellow + "Lua plugins:" + utils.ColorReset + "\n" + help += " " + utils.ColorGreen + "lua " + utils.ColorReset + " - execute Lua plugin\n" + + help += "\n" + utils.ColorYellow + "Other:" + utils.ColorReset + "\n" + help += " " + utils.ColorGreen + "exit/quit" + utils.ColorReset + " - exit the DBMS\n" + + return help +}