futriis/internal/engine/engine.go

1077 lines
35 KiB
Go
Raw Normal View History

2026-03-01 20:01:48 +00:00
// /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 <query> [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 <tapple> <slice>' 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 <name>" + utils.ColorReset + " - create a new tapple (database)\n"
help += " " + utils.ColorGreen + "create slice <tapple> <name>" + utils.ColorReset + " - create a new slice (table)\n"
help += " " + utils.ColorGreen + "create tuple <tapple> <slice> <id> [key=value...]" + utils.ColorReset + " - create a new tuple (record)\n"
help += " " + utils.ColorGreen + "delete tapple <name>" + utils.ColorReset + " - delete a tapple\n"
help += " " + utils.ColorGreen + "delete slice <tapple> <name>" + utils.ColorReset + " - delete a slice\n"
help += " " + utils.ColorGreen + "delete tuple <tapple> <slice> <id>" + utils.ColorReset + " - delete a tuple\n"
help += " " + utils.ColorGreen + "update tuple <tapple> <slice> <id> [key=value...]" + utils.ColorReset + " - update a tuple\n"
help += " " + utils.ColorGreen + "list tapples" + utils.ColorReset + " - show all tapples\n"
help += " " + utils.ColorGreen + "list slices <tapple>" + utils.ColorReset + " - show all slices in a tapple\n"
help += " " + utils.ColorGreen + "show tuples <tapple> <slice>" + utils.ColorReset + " - show all tuples in a slice\n"
help += "\n" + utils.ColorYellow + "Search commands:" + utils.ColorReset + "\n"
help += " " + utils.ColorGreen + "search <query> [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 <tapple>" + utils.ColorReset + " - create primary index for tapple\n"
help += " " + utils.ColorGreen + "delete.prime.index <tapple>" + utils.ColorReset + " - delete primary index\n"
help += " " + utils.ColorGreen + "add.secondary.index <tapple> <field>" + utils.ColorReset + " - create secondary index on field\n"
help += " " + utils.ColorGreen + "delete.secondary.index <tapple> <field>" + 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 <address>" + utils.ColorReset + " - add a node to the cluster\n"
help += " " + utils.ColorGreen + "evict.node <node_id>" + 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 <plugin_name>" + 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
}