diff --git a/internal/commands/crud.go b/internal/commands/crud.go new file mode 100644 index 0000000..d793bd8 --- /dev/null +++ b/internal/commands/crud.go @@ -0,0 +1,348 @@ +/* + * Copyright 2026 Safronov Grigorii + * + * Licensed under the CDDL, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * You may obtain a copy of the License at + * https://opensource.org/licenses/CDDL-1.0 + */ + +// Файл: internal/commands/crud.go +// Назначение: Парсинг и выполнение CRUD-команд для работы с документами, +// коллекциями и базами данных c добавлением аудита. Поддерживает MongoDB-подобный синтаксис. + +package commands + +import ( + "fmt" + "strings" + + "futriis/internal/storage" + "futriis/internal/cluster" + "futriis/pkg/utils" +) + +// Execute выполняет команду CRUD +func Execute(store *storage.Storage, coord *cluster.RaftCoordinator, cmd string) error { + // Простейший парсинг для демонстрации + if strings.HasPrefix(cmd, "use ") { + dbName := strings.TrimPrefix(cmd, "use ") + if err := store.CreateDatabase(dbName); err != nil && err.Error() != "database already exists" { + return err + } + storage.AuditDatabaseOperation("USE", dbName) + return nil + } + + if cmd == "show dbs" { + return showDatabases(store) + } + + if cmd == "show collections" { + return showCollections(store) + } + + if strings.HasPrefix(cmd, "db.") { + return executeDatabaseCommand(store, coord, cmd) + } + + return fmt.Errorf("%s", utils.ColorizeText("unknown command: "+cmd, "\033[31m")) +} + +// ExecuteTransaction выполняет команды транзакций MongoDB-подобного синтаксиса +func ExecuteTransaction(store *storage.Storage, coord *cluster.RaftCoordinator, cmd string) error { + if strings.Contains(cmd, "startSession()") { + if err := storage.InitTransactionManager("futriis.wal"); err != nil { + return err + } + utils.Println("Session started") + storage.LogAudit("START", "SESSION", "global", map[string]interface{}{"action": "start_session"}) + return nil + } + + if strings.Contains(cmd, "startTransaction()") { + _ = storage.BeginTransaction() + utils.Println("Transaction started") + storage.LogAudit("START", "TRANSACTION", "current", map[string]interface{}{"action": "begin_transaction"}) + return nil + } + + if strings.Contains(cmd, "commitTransaction()") { + if err := storage.CommitCurrentTransaction(); err != nil { + return err + } + utils.Println("Transaction committed successfully") + storage.LogAudit("COMMIT", "TRANSACTION", "current", map[string]interface{}{"action": "commit_transaction"}) + return nil + } + + if strings.Contains(cmd, "abortTransaction()") { + if err := storage.AbortCurrentTransaction(); err != nil { + return err + } + utils.Println("Transaction aborted") + storage.LogAudit("ABORT", "TRANSACTION", "current", map[string]interface{}{"action": "abort_transaction"}) + return nil + } + + return fmt.Errorf("%s", utils.ColorizeText("unknown transaction command: "+cmd, "\033[31m")) +} + +// showDatabases отображает список всех баз данных +func showDatabases(store *storage.Storage) error { + databases := store.ListDatabases() + if len(databases) == 0 { + utils.Println("No databases found") + return nil + } + + utils.Println("\nDatabases:") + for _, db := range databases { + utils.Println(" - " + db) + } + return nil +} + +// showCollections отображает список коллекций в текущей базе данных +func showCollections(store *storage.Storage) error { + databases := store.ListDatabases() + if len(databases) == 0 { + utils.Println("No databases found") + return nil + } + + db, err := store.GetDatabase(databases[0]) + if err != nil { + return err + } + + collections := db.ListCollections() + if len(collections) == 0 { + utils.Println("No collections found") + return nil + } + + utils.Println("\nCollections in database '" + databases[0] + "':") + for _, coll := range collections { + utils.Println(" - " + coll) + } + return nil +} + +// executeDatabaseCommand выполняет команду вида db..() +func executeDatabaseCommand(store *storage.Storage, coord *cluster.RaftCoordinator, cmd string) error { + parts := strings.SplitN(cmd, ".", 3) + if len(parts) < 3 { + return fmt.Errorf("%s", utils.ColorizeText("invalid database command format", "\033[31m")) + } + + collectionPart := parts[1] + operationPart := parts[2] + + var collectionName, operation string + + if strings.Contains(collectionPart, ".") { + collParts := strings.SplitN(collectionPart, ".", 2) + collectionName = collParts[0] + operation = collParts[1] + } else { + collectionName = collectionPart + opParts := strings.SplitN(operationPart, "(", 2) + if len(opParts) < 1 { + return fmt.Errorf("%s", utils.ColorizeText("invalid operation format", "\033[31m")) + } + operation = opParts[0] + } + + databases := store.ListDatabases() + if len(databases) == 0 { + if err := store.CreateDatabase("test"); err != nil { + return err + } + storage.AuditDatabaseOperation("CREATE", "test") + databases = store.ListDatabases() + } + + db, err := store.GetDatabase(databases[0]) + if err != nil { + return err + } + + coll, err := db.GetCollection(collectionName) + if err != nil { + if err := db.CreateCollection(collectionName); err != nil { + return err + } + storage.AuditCollectionOperation("CREATE", databases[0], collectionName, nil) + coll, _ = db.GetCollection(collectionName) + } + + switch operation { + case "insert", "insertOne": + return executeInsertWithTransaction(coll, operationPart, databases[0], collectionName) + case "find", "findOne": + return executeFindWithTransaction(coll, operationPart) + case "update", "updateOne": + return executeUpdateWithTransaction(coll, operationPart, databases[0], collectionName) + case "remove", "delete", "deleteOne": + return executeDeleteWithTransaction(coll, operationPart, databases[0], collectionName) + default: + return fmt.Errorf("%s", utils.ColorizeText("unknown operation: "+operation, "\033[31m")) + } +} + +func executeInsertWithTransaction(coll *storage.Collection, operationPart, dbName, collName string) error { + start := strings.Index(operationPart, "(") + end := strings.LastIndex(operationPart, ")") + if start == -1 || end == -1 { + return fmt.Errorf("%s", utils.ColorizeText("invalid insert syntax", "\033[31m")) + } + + dataStr := operationPart[start+1 : end] + dataStr = strings.TrimSpace(dataStr) + + if dataStr == "" || dataStr == "{}" { + return fmt.Errorf("%s", utils.ColorizeText("empty document", "\033[31m")) + } + + doc := storage.NewDocument() + + dataStr = strings.Trim(dataStr, "{}") + if dataStr != "" { + fields := strings.Split(dataStr, ",") + for _, field := range fields { + field = strings.TrimSpace(field) + if field == "" { + continue + } + parts := strings.SplitN(field, ":", 2) + if len(parts) == 2 { + key := strings.TrimSpace(parts[0]) + value := strings.TrimSpace(parts[1]) + value = strings.Trim(value, "\"'") + doc.SetField(key, value) + } + } + } + + if storage.HasActiveTransaction() { + // ИСПРАВЛЕНО: передаём только coll, opType, doc (сигнатура из transaction.go) + if err := storage.AddToTransaction(coll, "insert", doc); err != nil { + return err + } + utils.Println("Document staged for transaction") + return nil + } + + if err := coll.Insert(doc); err != nil { + return err + } + + // Аудит операции вставки документа + storage.AuditDocumentOperation("INSERT", dbName, collName, doc.ID, doc.GetFields()) + + utils.Println("Inserted document with _id: " + doc.ID) + return nil +} + +func executeFindWithTransaction(coll *storage.Collection, operationPart string) error { + start := strings.Index(operationPart, "{_id:") + if start == -1 { + docs := coll.GetAllDocuments() + if len(docs) == 0 { + utils.Println("No documents found") + return nil + } + + utils.Println("\nFound " + utils.ColorizeTextInt(len(docs)) + " documents:") + for _, doc := range docs { + utils.Println(" _id: " + doc.ID + ", fields: " + utils.ColorizeTextAny(doc.GetFields())) + } + return nil + } + + end := strings.Index(operationPart[start:], "}") + if end == -1 { + return fmt.Errorf("%s", utils.ColorizeText("invalid find syntax", "\033[31m")) + } + + idPart := operationPart[start+5 : start+end] + idPart = strings.TrimSpace(idPart) + idPart = strings.Trim(idPart, "\"'") + + if storage.HasActiveTransaction() { + doc, err := storage.FindInTransaction(coll, idPart) + if err != nil { + return err + } + utils.Println("Found document (in transaction): _id: " + doc.ID + ", fields: " + utils.ColorizeTextAny(doc.GetFields())) + return nil + } + + doc, err := coll.Find(idPart) + if err != nil { + return err + } + + utils.Println("Found document: _id: " + doc.ID + ", fields: " + utils.ColorizeTextAny(doc.GetFields())) + return nil +} + +func executeUpdateWithTransaction(coll *storage.Collection, operationPart, dbName, collName string) error { + if storage.HasActiveTransaction() { + utils.Println("Update operation staged for transaction") + storage.LogAudit("STAGE", "UPDATE", collName, map[string]interface{}{"database": dbName}) + return nil + } + + // Извлечение ID из строки обновления + start := strings.Index(operationPart, "{_id:") + if start == -1 { + return fmt.Errorf("%s", utils.ColorizeText("update requires _id filter", "\033[31m")) + } + + end := strings.Index(operationPart[start:], "}") + if end == -1 { + return fmt.Errorf("%s", utils.ColorizeText("invalid update syntax", "\033[31m")) + } + + idPart := operationPart[start+5 : start+end] + idPart = strings.TrimSpace(idPart) + idPart = strings.Trim(idPart, "\"'") + + storage.AuditDocumentOperation("UPDATE", dbName, collName, idPart, nil) + utils.Println("Update operation - to be implemented") + return nil +} + +func executeDeleteWithTransaction(coll *storage.Collection, operationPart, dbName, collName string) error { + if storage.HasActiveTransaction() { + utils.Println("Delete operation staged for transaction") + storage.LogAudit("STAGE", "DELETE", collName, map[string]interface{}{"database": dbName}) + return nil + } + + // Извлечение ID из строки удаления + start := strings.Index(operationPart, "{_id:") + if start == -1 { + return fmt.Errorf("%s", utils.ColorizeText("delete requires _id filter", "\033[31m")) + } + + end := strings.Index(operationPart[start:], "}") + if end == -1 { + return fmt.Errorf("%s", utils.ColorizeText("invalid delete syntax", "\033[31m")) + } + + idPart := operationPart[start+5 : start+end] + idPart = strings.TrimSpace(idPart) + idPart = strings.Trim(idPart, "\"'") + + if err := coll.Delete(idPart); err != nil { + return err + } + + storage.AuditDocumentOperation("DELETE", dbName, collName, idPart, nil) + utils.Println("Delete operation - to be implemented") + return nil +}