futriis/internal/engine/engine.go

1077 lines
35 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

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

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