522 lines
17 KiB
Go
522 lines
17 KiB
Go
// /futriis/internal/engine/engine.go
|
||
// Пакет engine реализует ядро СУБД, координирующее все операции
|
||
package engine
|
||
|
||
import (
|
||
"fmt"
|
||
"strings"
|
||
|
||
"futriis/internal/cluster"
|
||
"futriis/internal/lua"
|
||
"futriis/internal/replication"
|
||
"futriis/internal/storage"
|
||
"futriis/internal/transaction"
|
||
"futriis/pkg/config"
|
||
"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
|
||
}
|
||
|
||
// NewEngine создаёт новый экземпляр ядра СУБД
|
||
func NewEngine() *Engine {
|
||
cfg := config.Get()
|
||
|
||
// Создаём AOF менеджер
|
||
aofMgr, _ := replication.NewAOFManager(cfg.Node.AOFFile, cfg.Node.AOFEnabled)
|
||
|
||
// Воспроизводим AOF если нужно
|
||
if aofMgr != nil && cfg.Node.AOFEnabled {
|
||
// TODO: восстановление состояния из AOF
|
||
}
|
||
|
||
return &Engine{
|
||
storage: storage.NewStorage(),
|
||
clusterMgr: cluster.NewClusterManager(cfg),
|
||
txMgr: transaction.NewTransactionManager(),
|
||
luaMgr: lua.NewPluginManager(&cfg.Lua),
|
||
aofMgr: aofMgr,
|
||
cfg: cfg,
|
||
}
|
||
}
|
||
|
||
// 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 {
|
||
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 "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 "add.node":
|
||
return e.handleAddNode(args)
|
||
|
||
case "evict.node":
|
||
return e.handleEvictNode(args)
|
||
|
||
case "lua":
|
||
return e.handleLua(args)
|
||
|
||
default:
|
||
return "", fmt.Errorf("неизвестная команда: %s", command)
|
||
}
|
||
}
|
||
|
||
// handleCreate обрабатывает команды создания
|
||
func (e *Engine) handleCreate(args []string) (string, error) {
|
||
if len(args) < 2 {
|
||
return "", fmt.Errorf("недостаточно аргументов для команды create")
|
||
}
|
||
|
||
switch args[0] {
|
||
case "tapple":
|
||
if len(args) < 2 {
|
||
return "", fmt.Errorf("не указано имя таппла")
|
||
}
|
||
return e.createTapple(args[1])
|
||
case "slice":
|
||
if len(args) < 3 {
|
||
return "", fmt.Errorf("недостаточно аргументов для создания слайса")
|
||
}
|
||
return e.createSlice(args[1], args[2])
|
||
case "tuple":
|
||
if len(args) < 4 {
|
||
return "", fmt.Errorf("недостаточно аргументов для создания кортежа")
|
||
}
|
||
return e.createTuple(args[1], args[2], args[3], args[4:])
|
||
default:
|
||
return "", fmt.Errorf("неизвестный тип создания: %s", args[0])
|
||
}
|
||
}
|
||
|
||
// handleDelete обрабатывает команды удаления
|
||
func (e *Engine) handleDelete(args []string) (string, error) {
|
||
if len(args) < 2 {
|
||
return "", fmt.Errorf("недостаточно аргументов для команды delete")
|
||
}
|
||
|
||
switch args[0] {
|
||
case "tapple":
|
||
if len(args) < 2 {
|
||
return "", fmt.Errorf("не указано имя таппла")
|
||
}
|
||
return e.deleteTapple(args[1])
|
||
case "slice":
|
||
if len(args) < 3 {
|
||
return "", fmt.Errorf("недостаточно аргументов для удаления слайса")
|
||
}
|
||
return e.deleteSlice(args[1], args[2])
|
||
case "tuple":
|
||
if len(args) < 4 {
|
||
return "", fmt.Errorf("недостаточно аргументов для удаления кортежа")
|
||
}
|
||
return e.deleteTuple(args[1], args[2], args[3])
|
||
default:
|
||
return "", fmt.Errorf("неизвестный тип удаления: %s", args[0])
|
||
}
|
||
}
|
||
|
||
// handleUpdate обрабатывает команды обновления
|
||
func (e *Engine) handleUpdate(args []string) (string, error) {
|
||
if len(args) < 4 || args[0] != "tuple" {
|
||
return "", fmt.Errorf("неверная команда update")
|
||
}
|
||
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("недостаточно аргументов для команды list")
|
||
}
|
||
|
||
switch args[0] {
|
||
case "tapples":
|
||
return e.listTapples()
|
||
case "slices":
|
||
if len(args) < 2 {
|
||
return "", fmt.Errorf("не указано имя таппла")
|
||
}
|
||
return e.listSlices(args[1])
|
||
default:
|
||
return "", fmt.Errorf("неизвестный тип списка: %s", args[0])
|
||
}
|
||
}
|
||
|
||
// handleShow обрабатывает команды показа
|
||
func (e *Engine) handleShow(args []string) (string, error) {
|
||
if len(args) < 2 || args[0] != "tuples" {
|
||
return "", fmt.Errorf("неверная команда show")
|
||
}
|
||
if len(args) < 3 {
|
||
return "", fmt.Errorf("недостаточно аргументов для 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 + "Транзакция начата. ID: " + id + utils.ColorReset, nil
|
||
}
|
||
|
||
// handleCommit фиксирует транзакцию
|
||
func (e *Engine) handleCommit() (string, error) {
|
||
err := e.txMgr.Commit()
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
return utils.ColorGreen + "Транзакция зафиксирована" + utils.ColorReset, nil
|
||
}
|
||
|
||
// handleRollback откатывает транзакцию
|
||
func (e *Engine) handleRollback() (string, error) {
|
||
err := e.txMgr.Rollback()
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
return utils.ColorGreen + "Транзакция отменена" + utils.ColorReset, nil
|
||
}
|
||
|
||
// handleClusterStatus показывает статус кластера
|
||
func (e *Engine) handleClusterStatus() (string, error) {
|
||
status := e.clusterMgr.GetClusterStatus()
|
||
|
||
result := utils.ColorCyan + "Статус кластера:" + utils.ColorReset + "\n"
|
||
result += fmt.Sprintf(" Всего узлов: %d\n", status["total_nodes"])
|
||
result += fmt.Sprintf(" Активных узлов: %d\n", status["active_nodes"])
|
||
result += fmt.Sprintf(" Координатор: %v\n", status["coordinator"])
|
||
|
||
nodes, _ := status["nodes"].([]map[string]interface{})
|
||
if len(nodes) > 0 {
|
||
result += utils.ColorCyan + "\nУзлы кластера:" + 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("укажите адрес узла")
|
||
}
|
||
|
||
address := args[0]
|
||
err := e.clusterMgr.AddNode(address)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
return utils.ColorGreen + "Узел " + address + " добавлен в кластер" + utils.ColorReset, nil
|
||
}
|
||
|
||
// handleEvictNode удаляет узел из кластера
|
||
func (e *Engine) handleEvictNode(args []string) (string, error) {
|
||
if len(args) < 1 {
|
||
return "", fmt.Errorf("укажите ID узла или адрес")
|
||
}
|
||
|
||
nodeID := args[0]
|
||
err := e.clusterMgr.RemoveNode(nodeID)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
return utils.ColorGreen + "Узел " + nodeID + " удален из кластера" + utils.ColorReset, nil
|
||
}
|
||
|
||
// handleLua выполняет Lua скрипт
|
||
func (e *Engine) handleLua(args []string) (string, error) {
|
||
if len(args) < 1 {
|
||
return "", fmt.Errorf("укажите имя плагина")
|
||
}
|
||
|
||
pluginName := args[0]
|
||
err := e.luaMgr.ExecutePlugin(pluginName)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
return utils.ColorGreen + "Плагин выполнен" + utils.ColorReset, 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.Name + "' успешно создан" + 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 + "Таппл '" + name + "' успешно удален" + utils.ColorReset, nil
|
||
}
|
||
|
||
func (e *Engine) listTapples() (string, error) {
|
||
tapples := e.storage.GetTappleManager().ListTapples()
|
||
if len(tapples) == 0 {
|
||
return utils.ColorYellow + "Тапплы не найдены" + utils.ColorReset, nil
|
||
}
|
||
|
||
result := utils.ColorCyan + "Список тапплов:" + 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.Name + "' в таппле '" + tappleName + "' успешно создан" + 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 + "Слайс '" + sliceName + "' в таппле '" + tappleName + "' успешно удален" + 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 + "Слайсы в таппле '" + tappleName + "' не найдены" + utils.ColorReset, nil
|
||
}
|
||
|
||
result := utils.ColorCyan + "Список слайсов в таппле '" + 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.ID + "' в слайсе '" + sliceName + "' успешно создан" + 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 + "Кортеж '" + tupleID + "' в слайсе '" + sliceName + "' успешно удален" + 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.ID + "' в слайсе '" + sliceName + "' успешно обновлен" + 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
|
||
}
|
||
|
||
slice.RLock()
|
||
defer slice.RUnlock()
|
||
|
||
if len(slice.Tuples) == 0 {
|
||
return utils.ColorYellow + "Кортежи в слайсе '" + sliceName + "' не найдены" + utils.ColorReset, nil
|
||
}
|
||
|
||
result := utils.ColorCyan + "Список кортежей в слайсе '" + sliceName + "':" + utils.ColorReset + "\n"
|
||
for id, tuple := range slice.Tuples {
|
||
result += " " + utils.ColorGreen + "ID: " + id + utils.ColorReset + "\n"
|
||
for k, v := range tuple.Fields {
|
||
// ИСПРАВЛЕНО: заменено ColorPrompt на ColorPromptCode
|
||
result += " " + utils.ColorYellow + k + utils.ColorReset + ": " + utils.ColorPromptCode + fmt.Sprint(v) + utils.ColorReset + "\n"
|
||
}
|
||
result += "\n"
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
// help возвращает справку по командам
|
||
func (e *Engine) help() string {
|
||
help := utils.ColorCyan + "Доступные команды:" + utils.ColorReset + "\n"
|
||
|
||
help += "\n" + utils.ColorYellow + "Основные команды:" + utils.ColorReset + "\n"
|
||
help += " " + utils.ColorGreen + "create tapple <name>" + utils.ColorReset + " - создать новый таппл (базу данных)\n"
|
||
help += " " + utils.ColorGreen + "create slice <tapple> <name>" + utils.ColorReset + " - создать новый слайс (таблицу)\n"
|
||
help += " " + utils.ColorGreen + "create tuple <tapple> <slice> <id> [key=value...]" + utils.ColorReset + " - создать новый кортеж (запись)\n"
|
||
help += " " + utils.ColorGreen + "delete tapple <name>" + utils.ColorReset + " - удалить таппл\n"
|
||
help += " " + utils.ColorGreen + "delete slice <tapple> <name>" + utils.ColorReset + " - удалить слайс\n"
|
||
help += " " + utils.ColorGreen + "delete tuple <tapple> <slice> <id>" + utils.ColorReset + " - удалить кортеж\n"
|
||
help += " " + utils.ColorGreen + "update tuple <tapple> <slice> <id> [key=value...]" + utils.ColorReset + " - обновить кортеж\n"
|
||
help += " " + utils.ColorGreen + "list tapples" + utils.ColorReset + " - показать все тапплы\n"
|
||
help += " " + utils.ColorGreen + "list slices <tapple>" + utils.ColorReset + " - показать все слайсы в таппле\n"
|
||
help += " " + utils.ColorGreen + "show tuples <tapple> <slice>" + utils.ColorReset + " - показать все кортежи в слайсе\n"
|
||
|
||
help += "\n" + utils.ColorYellow + "Транзакции:" + utils.ColorReset + "\n"
|
||
help += " " + utils.ColorGreen + "begin" + utils.ColorReset + " - начать транзакцию\n"
|
||
help += " " + utils.ColorGreen + "commit" + utils.ColorReset + " - зафиксировать транзакцию\n"
|
||
help += " " + utils.ColorGreen + "rollback" + utils.ColorReset + " - отменить транзакцию\n"
|
||
|
||
help += "\n" + utils.ColorYellow + "Управление кластером:" + utils.ColorReset + "\n"
|
||
help += " " + utils.ColorGreen + "cluster.status" + utils.ColorReset + " - показать статус кластера\n"
|
||
help += " " + utils.ColorGreen + "add.node <address>" + utils.ColorReset + " - добавить узел в кластер\n"
|
||
help += " " + utils.ColorGreen + "evict.node <node_id>" + utils.ColorReset + " - удалить узел из кластера\n"
|
||
|
||
help += "\n" + utils.ColorYellow + "Lua плагины:" + utils.ColorReset + "\n"
|
||
help += " " + utils.ColorGreen + "lua <plugin_name>" + utils.ColorReset + " - выполнить Lua плагин\n"
|
||
|
||
help += "\n" + utils.ColorYellow + "Прочее:" + utils.ColorReset + "\n"
|
||
help += " " + utils.ColorGreen + "exit/quit" + utils.ColorReset + " - выйти из СУБД\n"
|
||
|
||
return help
|
||
}
|