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