338 lines
10 KiB
Go
338 lines
10 KiB
Go
// Файл: 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.<collection>.<operation>()
|
||
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() {
|
||
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
|
||
}
|