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