From f0694ca563004f3a835acf7738b333bc01d227fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D1=80=D0=B8=D0=B3=D0=BE=D1=80=D0=B8=D0=B9=20=D0=A1?= =?UTF-8?q?=D0=B0=D1=84=D1=80=D0=BE=D0=BD=D0=BE=D0=B2?= Date: Sun, 4 Oct 2026 21:01:50 +0000 Subject: [PATCH] Delete internal/storage/collection.go --- internal/storage/collection.go | 1998 -------------------------------- 1 file changed, 1998 deletions(-) delete mode 100644 internal/storage/collection.go diff --git a/internal/storage/collection.go b/internal/storage/collection.go deleted file mode 100644 index 646a56c..0000000 --- a/internal/storage/collection.go +++ /dev/null @@ -1,1998 +0,0 @@ -/* - * 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/storage/collection.go -// Назначение: Реализация коллекции с индексами (первичными и вторичными). -// Индексы хранятся отдельно от документов, обеспечивают wait-free доступ. -// Исправлено: корректная работа уникальных индексов, удаление из индексов при обновлении. -// Lock-free: ACL и Constraints переведены на атомарные операции. -// MVCC: удалён (2026) — заменён на WAL-based time-travel. -// -// ИЗМЕНЕНО (2026): Инвертированный индекс Index.data переведён с sync.Map -// на lock-free skip list (см. internal/storage/skiplist.go). -// -// ИСПРАВЛЕНО (2026): Устранены race condition в CollectionACL и Constraints. -// Добавлены sync.RWMutex для защиты операций чтения/записи map. -// Все методы load*/store* теперь используют блокировку. -// -// ИЗМЕНЕНО (2026): Удалены все вызовы MVCCManager (mvccManager, mvccEnabled, -// CreateVersion, GetVersionAt, GetAllVersions, GetLatestVersion, GetMVCCStats). -// Time-travel теперь реализован через WAL (см. transactions.go: -// GetDocumentAtTimestamp, GetDocumentVersionsSince). -// -// ИЗМЕНЕНО (2026): Добавлено AOF-логирование в Insert, Update, Delete, -// PermanentDelete, RestoreDeleted (см. aof.go: LogAOFInsert, LogAOFUpdate, -// LogAOFDelete, LogAOFRestore). - -package storage - -import ( - "fmt" - "sync" - "sync/atomic" - "time" - "strings" - - "futriis/internal/serializer" -) - -// ============================================================================= -// КОНСТАНТЫ (удалены дублирующиеся, используются из trigger.go) -// ============================================================================= - -// MaxVersionsPerDoc, VersionRetentionDays, VisibilityMapSize -// определены в trigger.go - -// ============================================================================= -// Collection - ОСНОВНАЯ СТРУКТУРА -// ============================================================================= - -// Collection представляет коллекцию документов (аналог таблицы). -type Collection struct { - dbName string // имя базы данных - name string // имя коллекции - docs sync.Map // map[string]*Document - wait-free хранилище документов - indexes sync.Map // map[string]*Index - индексы для быстрого поиска (lock-free) - metadata *CollectionMetadata // Метаданные коллекции - docCount atomic.Int64 // Атомарный счётчик документов - sizeBytes atomic.Int64 // Атомарный размер коллекции в байтах - mu sync.RWMutex // Для операций, изменяющих структуру коллекции - constraints *Constraints // Ограничения коллекции (lock-free) - acl *CollectionACL // ACL для коллекции (lock-free) - triggers sync.Map // map[string]*Trigger - триггеры коллекции - - // ИСПРАВЛЕНО: Блокировка для согласованности индексов при SoftDelete - indexMu sync.Mutex // Защита атомарности операций с индексами -} - -// CollectionMetadata содержит метаданные коллекции. -type CollectionMetadata struct { - Name string `msgpack:"name"` - CreatedAt int64 `msgpack:"created_at"` - UpdatedAt int64 `msgpack:"updated_at"` - DeletedAt int64 `msgpack:"deleted_at"` - DocumentCount int64 `msgpack:"document_count"` - SizeBytes int64 `msgpack:"size_bytes"` - IndexCount int `msgpack:"index_count"` - Settings *CollectionSettings `msgpack:"settings"` -} - -// CollectionSettings содержит настройки коллекции. -type CollectionSettings struct { - MaxDocuments int `msgpack:"max_documents"` // Максимальное количество документов (0 = безлимит) - ValidateSchema bool `msgpack:"validate_schema"` // Валидировать схему документов - AutoIndexID bool `msgpack:"auto_index_id"` // Автоматически индексировать поле _id - TTLSeconds int `msgpack:"ttl_seconds"` // Время жизни документов (0 = бессрочно) - SoftDelete bool `msgpack:"soft_delete"` // Мягкое удаление документов -} - -// Index представляет индекс для ускорения поиска. -// -// ИЗМЕНЕНО: поле data теперь *SkipList (lock-free skip list) вместо sync.Map. -type Index struct { - Name string `msgpack:"name"` - Fields []string `msgpack:"fields"` // Поля для индексации - Unique bool `msgpack:"unique"` // Уникальный индекс - CreatedAt int64 `msgpack:"created_at"` // Время создания индекса - UpdatedAt int64 `msgpack:"updated_at"` // Время последнего обновления индекса - data *SkipList // значение индекса -> ID документа (lock-free skip list) -} - -// Constraints представляет ограничения на коллекцию (lock-free версия). -type Constraints struct { - mu sync.RWMutex - - // Атомарные указатели для lock-free доступа к данным ограничений - requiredFieldsPtr atomic.Value // map[string]bool - uniqueFieldsPtr atomic.Value // map[string]bool - minValuesPtr atomic.Value // map[string]float64 - maxValuesPtr atomic.Value // map[string]float64 - patternFieldsPtr atomic.Value // map[string]string - enumFieldsPtr atomic.Value // map[string][]interface{} - - createdAt int64 - updatedAt atomic.Int64 - constraintHistory atomic.Value // []ConstraintChange -} - -// ConstraintChange представляет изменение ограничения. -type ConstraintChange struct { - Timestamp int64 `msgpack:"timestamp"` - TimestampStr string `msgpack:"timestamp_str"` - Action string `msgpack:"action"` // ADD, REMOVE, MODIFY - ConstraintType string `msgpack:"constraint_type"` // required, unique, min, max, enum, regex - Field string `msgpack:"field"` - OldValue interface{} `msgpack:"old_value,omitempty"` - NewValue interface{} `msgpack:"new_value,omitempty"` -} - -// CollectionACL представляет список контроля доступа для коллекции (lock-free версия). -type CollectionACL struct { - mu sync.RWMutex - - // Атомарные указатели для lock-free доступа к ACL - readRolesPtr atomic.Value // map[string]bool - writeRolesPtr atomic.Value // map[string]bool - deleteRolesPtr atomic.Value // map[string]bool - adminRolesPtr atomic.Value // map[string]bool - - createdAt int64 - updatedAt atomic.Int64 - aclHistory atomic.Value // []ACLChange -} - -// ACLChange представляет изменение ACL. -type ACLChange struct { - Timestamp int64 `msgpack:"timestamp"` - TimestampStr string `msgpack:"timestamp_str"` - Action string `msgpack:"action"` // GRANT, REVOKE, SET - Role string `msgpack:"role"` - Permission string `msgpack:"permission"` // read, write, delete, admin - Granted bool `msgpack:"granted"` -} - -// NewConstraints создаёт новый экземпляр Constraints с lock-free реализацией. -func NewConstraints() *Constraints { - c := &Constraints{ - createdAt: time.Now().UnixMilli(), - } - - c.requiredFieldsPtr.Store(make(map[string]bool)) - c.uniqueFieldsPtr.Store(make(map[string]bool)) - c.minValuesPtr.Store(make(map[string]float64)) - c.maxValuesPtr.Store(make(map[string]float64)) - c.patternFieldsPtr.Store(make(map[string]string)) - c.enumFieldsPtr.Store(make(map[string][]interface{})) - c.constraintHistory.Store(make([]ConstraintChange, 0)) - c.updatedAt.Store(c.createdAt) - - return c -} - -// loadRequiredFields загружает карту обязательных полей (lock-free). -func (cons *Constraints) loadRequiredFields() map[string]bool { - cons.mu.RLock() - defer cons.mu.RUnlock() - val := cons.requiredFieldsPtr.Load() - if val == nil { - return make(map[string]bool) - } - return val.(map[string]bool) -} - -// storeRequiredFields сохраняет карту обязательных полей (lock-free). -func (cons *Constraints) storeRequiredFields(newMap map[string]bool) { - cons.mu.Lock() - defer cons.mu.Unlock() - cons.requiredFieldsPtr.Store(newMap) -} - -// loadUniqueFields загружает карту уникальных полей (lock-free). -func (cons *Constraints) loadUniqueFields() map[string]bool { - cons.mu.RLock() - defer cons.mu.RUnlock() - val := cons.uniqueFieldsPtr.Load() - if val == nil { - return make(map[string]bool) - } - return val.(map[string]bool) -} - -// storeUniqueFields сохраняет карту уникальных полей (lock-free). -func (cons *Constraints) storeUniqueFields(newMap map[string]bool) { - cons.mu.Lock() - defer cons.mu.Unlock() - cons.uniqueFieldsPtr.Store(newMap) -} - -// loadMinValues загружает карту минимальных значений (lock-free). -func (cons *Constraints) loadMinValues() map[string]float64 { - cons.mu.RLock() - defer cons.mu.RUnlock() - val := cons.minValuesPtr.Load() - if val == nil { - return make(map[string]float64) - } - return val.(map[string]float64) -} - -// storeMinValues сохраняет карту минимальных значений (lock-free). -func (cons *Constraints) storeMinValues(newMap map[string]float64) { - cons.mu.Lock() - defer cons.mu.Unlock() - cons.minValuesPtr.Store(newMap) -} - -// loadMaxValues загружает карту максимальных значений (lock-free). -func (cons *Constraints) loadMaxValues() map[string]float64 { - cons.mu.RLock() - defer cons.mu.RUnlock() - val := cons.maxValuesPtr.Load() - if val == nil { - return make(map[string]float64) - } - return val.(map[string]float64) -} - -// storeMaxValues сохраняет карту максимальных значений (lock-free). -func (cons *Constraints) storeMaxValues(newMap map[string]float64) { - cons.mu.Lock() - defer cons.mu.Unlock() - cons.maxValuesPtr.Store(newMap) -} - -// loadPatternFields загружает карту regex паттернов (lock-free). -func (cons *Constraints) loadPatternFields() map[string]string { - cons.mu.RLock() - defer cons.mu.RUnlock() - val := cons.patternFieldsPtr.Load() - if val == nil { - return make(map[string]string) - } - return val.(map[string]string) -} - -// storePatternFields сохраняет карту regex паттернов (lock-free). -func (cons *Constraints) storePatternFields(newMap map[string]string) { - cons.mu.Lock() - defer cons.mu.Unlock() - cons.patternFieldsPtr.Store(newMap) -} - -// loadEnumFields загружает карту enum ограничений (lock-free). -func (cons *Constraints) loadEnumFields() map[string][]interface{} { - cons.mu.RLock() - defer cons.mu.RUnlock() - val := cons.enumFieldsPtr.Load() - if val == nil { - return make(map[string][]interface{}) - } - original := val.(map[string][]interface{}) - result := make(map[string][]interface{}) - for k, v := range original { - copied := make([]interface{}, len(v)) - copy(copied, v) - result[k] = copied - } - return result -} - -// storeEnumFields сохраняет карту enum ограничений (lock-free). -func (cons *Constraints) storeEnumFields(newMap map[string][]interface{}) { - cons.mu.Lock() - defer cons.mu.Unlock() - cons.enumFieldsPtr.Store(newMap) -} - -// loadConstraintHistory загружает историю изменений (lock-free). -func (cons *Constraints) loadConstraintHistory() []ConstraintChange { - cons.mu.RLock() - defer cons.mu.RUnlock() - val := cons.constraintHistory.Load() - if val == nil { - return make([]ConstraintChange, 0) - } - return val.([]ConstraintChange) -} - -// storeConstraintHistory сохраняет историю изменений (lock-free). -func (cons *Constraints) storeConstraintHistory(history []ConstraintChange) { - cons.mu.Lock() - defer cons.mu.Unlock() - cons.constraintHistory.Store(history) -} - -// NewCollectionACL создаёт новый экземпляр CollectionACL с lock-free реализацией. -func NewCollectionACL() *CollectionACL { - acl := &CollectionACL{ - createdAt: time.Now().UnixMilli(), - } - - acl.readRolesPtr.Store(make(map[string]bool)) - acl.writeRolesPtr.Store(make(map[string]bool)) - acl.deleteRolesPtr.Store(make(map[string]bool)) - acl.adminRolesPtr.Store(make(map[string]bool)) - acl.aclHistory.Store(make([]ACLChange, 0)) - acl.updatedAt.Store(acl.createdAt) - - return acl -} - -// loadReadRoles загружает роли чтения (lock-free). -func (acl *CollectionACL) loadReadRoles() map[string]bool { - acl.mu.RLock() - defer acl.mu.RUnlock() - val := acl.readRolesPtr.Load() - if val == nil { - return make(map[string]bool) - } - return val.(map[string]bool) -} - -// storeReadRoles сохраняет роли чтения (lock-free). -func (acl *CollectionACL) storeReadRoles(roles map[string]bool) { - acl.mu.Lock() - defer acl.mu.Unlock() - acl.readRolesPtr.Store(roles) -} - -// loadWriteRoles загружает роли записи (lock-free). -func (acl *CollectionACL) loadWriteRoles() map[string]bool { - acl.mu.RLock() - defer acl.mu.RUnlock() - val := acl.writeRolesPtr.Load() - if val == nil { - return make(map[string]bool) - } - return val.(map[string]bool) -} - -// storeWriteRoles сохраняет роли записи (lock-free). -func (acl *CollectionACL) storeWriteRoles(roles map[string]bool) { - acl.mu.Lock() - defer acl.mu.Unlock() - acl.writeRolesPtr.Store(roles) -} - -// loadDeleteRoles загружает роли удаления (lock-free). -func (acl *CollectionACL) loadDeleteRoles() map[string]bool { - acl.mu.RLock() - defer acl.mu.RUnlock() - val := acl.deleteRolesPtr.Load() - if val == nil { - return make(map[string]bool) - } - return val.(map[string]bool) -} - -// storeDeleteRoles сохраняет роли удаления (lock-free). -func (acl *CollectionACL) storeDeleteRoles(roles map[string]bool) { - acl.mu.Lock() - defer acl.mu.Unlock() - acl.deleteRolesPtr.Store(roles) -} - -// loadAdminRoles загружает административные роли (lock-free). -func (acl *CollectionACL) loadAdminRoles() map[string]bool { - acl.mu.RLock() - defer acl.mu.RUnlock() - val := acl.adminRolesPtr.Load() - if val == nil { - return make(map[string]bool) - } - return val.(map[string]bool) -} - -// storeAdminRoles сохраняет административные роли (lock-free). -func (acl *CollectionACL) storeAdminRoles(roles map[string]bool) { - acl.mu.Lock() - defer acl.mu.Unlock() - acl.adminRolesPtr.Store(roles) -} - -// loadACLHistory загружает историю ACL (lock-free). -func (acl *CollectionACL) loadACLHistory() []ACLChange { - acl.mu.RLock() - defer acl.mu.RUnlock() - val := acl.aclHistory.Load() - if val == nil { - return make([]ACLChange, 0) - } - return val.([]ACLChange) -} - -// storeACLHistory сохраняет историю ACL (lock-free). -func (acl *CollectionACL) storeACLHistory(history []ACLChange) { - acl.mu.Lock() - defer acl.mu.Unlock() - acl.aclHistory.Store(history) -} - -// CheckPermission проверяет наличие разрешения у роли (lock-free). -func (acl *CollectionACL) CheckPermission(role, operation string) bool { - acl.mu.RLock() - defer acl.mu.RUnlock() - - if val := acl.adminRolesPtr.Load(); val != nil { - if adminRoles, ok := val.(map[string]bool); ok && adminRoles[role] { - return true - } - } - - switch operation { - case "read": - if val := acl.readRolesPtr.Load(); val != nil { - if readRoles, ok := val.(map[string]bool); ok { - return readRoles[role] - } - } - case "write": - if val := acl.writeRolesPtr.Load(); val != nil { - if writeRoles, ok := val.(map[string]bool); ok { - return writeRoles[role] - } - } - case "delete": - if val := acl.deleteRolesPtr.Load(); val != nil { - if deleteRoles, ok := val.(map[string]bool); ok { - return deleteRoles[role] - } - } - } - return false -} - -// NewCollection создаёт новую коллекцию. -// -// ИЗМЕНЕНО: удалены поля mvccEnabled, mvccManager, MVCCEnabled, -// MaxVersions из CollectionSettings. -func NewCollection(dbName, name string, settings *CollectionSettings) *Collection { - if settings == nil { - settings = &CollectionSettings{ - MaxDocuments: 0, - ValidateSchema: false, - AutoIndexID: true, - TTLSeconds: 0, - SoftDelete: false, - } - } - - now := time.Now().UnixMilli() - coll := &Collection{ - dbName: dbName, - name: name, - metadata: &CollectionMetadata{ - Name: name, - CreatedAt: now, - UpdatedAt: now, - DeletedAt: 0, - DocumentCount: 0, - SizeBytes: 0, - IndexCount: 0, - Settings: settings, - }, - constraints: NewConstraints(), - acl: NewCollectionACL(), - } - - if settings.AutoIndexID { - coll.CreateIndex("_id_", []string{"_id"}, true) - } - - if settings.TTLSeconds > 0 { - go coll.ttlCleanupLoop() - } - - AuditCollectionOperation("CREATE", dbName, name, settings) - - return coll -} - -// ============================================================================= -// МЕТОДЫ ДЛЯ РАБОТЫ С ТРИГГЕРАМИ -// ============================================================================= - -// AddTrigger добавляет триггер в коллекцию. -func (c *Collection) AddTrigger(trigger *Trigger) error { - if trigger.Name == "" { - return fmt.Errorf("trigger name cannot be empty") - } - - if _, exists := c.triggers.Load(trigger.Name); exists { - return fmt.Errorf("trigger '%s' already exists", trigger.Name) - } - - trigger.CreatedAt = time.Now().UnixMilli() - trigger.UpdatedAt = trigger.CreatedAt - - c.triggers.Store(trigger.Name, trigger) - c.mu.Lock() - c.metadata.UpdatedAt = time.Now().UnixMilli() - c.mu.Unlock() - - AuditDocumentOperation("CREATE_TRIGGER", c.dbName, c.name, trigger.Name, map[string]interface{}{ - "event": trigger.Event, - "action": trigger.Action, - }) - - return nil -} - -// DropTrigger удаляет триггер из коллекции. -func (c *Collection) DropTrigger(name string) error { - if _, exists := c.triggers.LoadAndDelete(name); !exists { - return fmt.Errorf("trigger '%s' not found", name) - } - - c.mu.Lock() - c.metadata.UpdatedAt = time.Now().UnixMilli() - c.mu.Unlock() - - AuditDocumentOperation("DROP_TRIGGER", c.dbName, c.name, name, nil) - - return nil -} - -// ListTriggers возвращает список всех триггеров в коллекции. -func (c *Collection) ListTriggers() []*Trigger { - triggers := make([]*Trigger, 0) - c.triggers.Range(func(key, value interface{}) bool { - triggers = append(triggers, value.(*Trigger)) - return true - }) - return triggers -} - -// GetTrigger возвращает триггер по имени. -func (c *Collection) GetTrigger(name string) (*Trigger, bool) { - if val, ok := c.triggers.Load(name); ok { - return val.(*Trigger), true - } - return nil, false -} - -// EnableTrigger включает триггер. -func (c *Collection) EnableTrigger(name string) error { - val, ok := c.triggers.Load(name) - if !ok { - return fmt.Errorf("trigger '%s' not found", name) - } - - trigger := val.(*Trigger) - trigger.Enabled = true - trigger.UpdatedAt = time.Now().UnixMilli() - c.triggers.Store(name, trigger) - - c.mu.Lock() - c.metadata.UpdatedAt = time.Now().UnixMilli() - c.mu.Unlock() - - AuditDocumentOperation("ENABLE_TRIGGER", c.dbName, c.name, name, nil) - - return nil -} - -// DisableTrigger отключает триггер. -func (c *Collection) DisableTrigger(name string) error { - val, ok := c.triggers.Load(name) - if !ok { - return fmt.Errorf("trigger '%s' not found", name) - } - - trigger := val.(*Trigger) - trigger.Enabled = false - trigger.UpdatedAt = time.Now().UnixMilli() - c.triggers.Store(name, trigger) - - c.mu.Lock() - c.metadata.UpdatedAt = time.Now().UnixMilli() - c.mu.Unlock() - - AuditDocumentOperation("DISABLE_TRIGGER", c.dbName, c.name, name, nil) - - return nil -} - -// executeTriggers выполняет триггеры для заданного события. -func (c *Collection) executeTriggers(event string, docID string, data map[string]interface{}) error { - triggers := c.ListTriggers() - for _, trigger := range triggers { - if !trigger.Enabled || trigger.Event != event { - continue - } - - LogTriggerExecution(trigger.Name, event, c.name, docID, trigger.Action, true, nil) - } - return nil -} - -// ============================================================================= -// БАЗОВЫЕ МЕТОДЫ КОЛЛЕКЦИИ -// ============================================================================= - -// Name возвращает имя коллекции. -func (c *Collection) Name() string { - return c.name -} - -// DBName возвращает имя базы данных. -func (c *Collection) DBName() string { - return c.dbName -} - -// Insert вставляет документ в коллекцию. -// -// ИЗМЕНЕНО: удалена работа с MVCC. Добавлено AOF-логирование. -func (c *Collection) Insert(doc *Document) error { - if err := c.constraints.ValidateDocument(doc); err != nil { - return fmt.Errorf("constraint violation: %v", err) - } - - if c.metadata.Settings.MaxDocuments > 0 { - if c.docCount.Load() >= int64(c.metadata.Settings.MaxDocuments) { - return fmt.Errorf("collection is full: max documents %d reached", c.metadata.Settings.MaxDocuments) - } - } - - if c.metadata.Settings.ValidateSchema { - if err := c.validateDocument(doc); err != nil { - return fmt.Errorf("document validation failed: %v", err) - } - } - - if err := c.checkUniqueConstraints(doc); err != nil { - return err - } - - doc.CreatedAt = time.Now().UnixMilli() - doc.UpdatedAt = doc.CreatedAt - doc.DeletedAt = 0 - - data, err := serializer.Marshal(doc) - if err != nil { - return fmt.Errorf("failed to serialize document: %v", err) - } - - if _, loaded := c.docs.LoadOrStore(doc.ID, doc); loaded { - return fmt.Errorf("document with id %s already exists", doc.ID) - } - - c.updateIndexes(doc, true) - - c.docCount.Add(1) - c.sizeBytes.Add(int64(len(data))) - - c.mu.Lock() - c.metadata.DocumentCount = c.docCount.Load() - c.metadata.SizeBytes = c.sizeBytes.Load() - c.metadata.UpdatedAt = time.Now().UnixMilli() - c.mu.Unlock() - - AuditDocumentOperation("INSERT", c.dbName, c.name, doc.ID, doc.GetFields()) - - // AOF-логирование (для мутаций вне batch) - LogAOFInsert(c.dbName, c.name, doc) - - c.executeTriggers("AFTER_INSERT", doc.ID, doc.GetFields()) - - return nil -} - -// InsertFromMap создаёт и вставляет документ из map. -func (c *Collection) InsertFromMap(fields map[string]interface{}) error { - doc := NewDocument() - for k, v := range fields { - doc.SetField(k, v) - } - return c.Insert(doc) -} - -// Find находит документ по ID (с использованием первичного индекса). -func (c *Collection) Find(id string) (*Document, error) { - if val, ok := c.docs.Load(id); ok { - doc := val.(*Document) - - if doc.IsDeleted() && c.metadata.Settings.SoftDelete { - return nil, fmt.Errorf("document deleted at %s", doc.GetDeletedAtStr()) - } - - if c.metadata.Settings.TTLSeconds > 0 { - if time.Now().UnixMilli()-doc.CreatedAt > int64(c.metadata.Settings.TTLSeconds*1000) { - if c.metadata.Settings.SoftDelete { - doc.SoftDelete() - c.docs.Store(id, doc) - } else { - c.Delete(id) - } - return nil, fmt.Errorf("key not found") - } - } - return doc, nil - } - return nil, fmt.Errorf("key not found") -} - -// FindIncludingDeleted находит документ даже если он мягко удалён. -func (c *Collection) FindIncludingDeleted(id string) (*Document, error) { - if val, ok := c.docs.Load(id); ok { - return val.(*Document), nil - } - return nil, fmt.Errorf("key not found") -} - -// compareValues сравнивает два значения с учётом типа. -func compareValues(a, b interface{}) bool { - if a == nil && b == nil { - return true - } - if a == nil || b == nil { - return false - } - - if a == b { - return true - } - - aStr := fmt.Sprintf("%v", a) - bStr := fmt.Sprintf("%v", b) - return aStr == bStr -} - -// FindByIndex находит документы по значению индексированного поля. -func (c *Collection) FindByIndex(indexName string, value interface{}) ([]*Document, error) { - idxVal, ok := c.indexes.Load(indexName) - if !ok { - return nil, fmt.Errorf("index not found: %s", indexName) - } - - index := idxVal.(*Index) - docs := make([]*Document, 0) - - if index.data == nil { - return docs, nil - } - - docIDs := index.data.SearchAll(value) - for _, docID := range docIDs { - if doc, err := c.Find(docID); err == nil { - docs = append(docs, doc) - } - } - - return docs, nil -} - -// FindByIndexPrefix находит документы по префиксу индекса. -func (c *Collection) FindByIndexPrefix(indexName string, prefix string) ([]*Document, error) { - idxVal, ok := c.indexes.Load(indexName) - if !ok { - return nil, fmt.Errorf("index not found: %s", indexName) - } - - index := idxVal.(*Index) - docs := make([]*Document, 0) - - if index.data == nil { - return docs, nil - } - - index.data.RangeWithKeyPrefix(prefix, func(key interface{}, docID string) bool { - if doc, err := c.Find(docID); err == nil { - docs = append(docs, doc) - } - return true - }) - - return docs, nil -} - -// Update обновляет документ по ID. -// -// ИЗМЕНЕНО: удалена работа с MVCC. Добавлено AOF-логирование. -func (c *Collection) Update(id string, updates map[string]interface{}) error { - val, ok := c.docs.Load(id) - if !ok { - return fmt.Errorf("key not found") - } - - oldDoc := val.(*Document) - - if oldDoc.IsDeleted() && c.metadata.Settings.SoftDelete { - return fmt.Errorf("cannot update deleted document") - } - - newDoc := oldDoc.Clone() - if err := newDoc.Update(updates); err != nil { - return err - } - - newDoc.UpdatedAt = time.Now().UnixMilli() - - if err := c.constraints.ValidateDocument(newDoc); err != nil { - return fmt.Errorf("constraint violation: %v", err) - } - - if err := c.checkUniqueConstraintsUpdate(oldDoc, newDoc); err != nil { - return err - } - - c.executeTriggers("BEFORE_UPDATE", id, newDoc.GetFields()) - - c.indexMu.Lock() - c.removeFromIndexes(oldDoc) - c.addToIndexes(newDoc) - c.indexMu.Unlock() - - c.docs.Store(id, newDoc) - - c.mu.Lock() - c.metadata.UpdatedAt = time.Now().UnixMilli() - c.mu.Unlock() - - AuditDocumentOperation("UPDATE", c.dbName, c.name, id, updates) - - // AOF-логирование - LogAOFUpdate(c.dbName, c.name, id, updates) - - c.executeTriggers("AFTER_UPDATE", id, newDoc.GetFields()) - - return nil -} - -// Delete удаляет документ по ID. -// -// ИЗМЕНЕНО: удалена работа с MVCC. Добавлено AOF-логирование. -func (c *Collection) Delete(id string) error { - val, ok := c.docs.Load(id) - if !ok { - return fmt.Errorf("key not found") - } - - doc := val.(*Document) - - c.executeTriggers("BEFORE_DELETE", id, doc.GetFields()) - - if c.metadata.Settings.SoftDelete { - c.indexMu.Lock() - - doc.SoftDelete() - c.docs.Store(id, doc) - c.removeFromIndexes(doc) - - c.indexMu.Unlock() - - AuditDocumentOperation("SOFT_DELETE", c.dbName, c.name, id, map[string]interface{}{ - "deleted_at": doc.DeletedAt, - }) - } else { - c.indexMu.Lock() - - c.removeFromIndexes(doc) - c.docs.Delete(id) - - c.indexMu.Unlock() - - AuditDocumentOperation("DELETE", c.dbName, c.name, id, nil) - } - - // AOF-логирование - LogAOFDelete(c.dbName, c.name, id) - - c.docCount.Add(-1) - c.mu.Lock() - c.metadata.DocumentCount = c.docCount.Load() - c.metadata.UpdatedAt = time.Now().UnixMilli() - c.mu.Unlock() - - c.executeTriggers("AFTER_DELETE", id, nil) - - return nil -} - -// PermanentDelete выполняет физическое удаление мягко удалённого документа. -// -// ИЗМЕНЕНО: удалена работа с MVCC. Добавлено AOF-логирование. -func (c *Collection) PermanentDelete(id string) error { - val, ok := c.docs.Load(id) - if !ok { - return fmt.Errorf("key not found") - } - - doc := val.(*Document) - - if !doc.IsDeleted() { - return fmt.Errorf("document is not soft deleted, use Delete() instead") - } - - c.indexMu.Lock() - - c.removeFromIndexes(doc) - c.docs.Delete(id) - - c.indexMu.Unlock() - - c.docCount.Add(-1) - c.mu.Lock() - c.metadata.DocumentCount = c.docCount.Load() - c.metadata.UpdatedAt = time.Now().UnixMilli() - c.mu.Unlock() - - AuditDocumentOperation("PERMANENT_DELETE", c.dbName, c.name, id, nil) - - // AOF-логирование (физическое удаление логируем как delete) - LogAOFDelete(c.dbName, c.name, id) - - return nil -} - -// RestoreDeleted восстанавливает мягко удалённый документ. -// -// ИЗМЕНЕНО: удалена работа с MVCC. Добавлено AOF-логирование. -func (c *Collection) RestoreDeleted(id string) error { - val, ok := c.docs.Load(id) - if !ok { - return fmt.Errorf("key not found") - } - - doc := val.(*Document) - - if !doc.IsDeleted() { - return fmt.Errorf("document is not deleted") - } - - c.indexMu.Lock() - - doc.Restore() - c.addToIndexes(doc) - c.docs.Store(id, doc) - - c.indexMu.Unlock() - - c.mu.Lock() - c.metadata.UpdatedAt = time.Now().UnixMilli() - c.mu.Unlock() - - AuditDocumentOperation("RESTORE", c.dbName, c.name, id, nil) - - // AOF-логирование - LogAOFRestore(c.dbName, c.name, id) - - return nil -} - -// removeFromIndexes удаляет документ из всех индексов. -func (c *Collection) removeFromIndexes(doc *Document) { - c.indexes.Range(func(key, value interface{}) bool { - index := value.(*Index) - if index.data == nil { - return true - } - indexValue := c.extractIndexValue(doc, index.Fields) - index.data.Delete(indexValue) - return true - }) -} - -// addToIndexes добавляет документ во все индексы. -func (c *Collection) addToIndexes(doc *Document) { - c.indexes.Range(func(key, value interface{}) bool { - index := value.(*Index) - if index.data == nil { - return true - } - indexValue := c.extractIndexValue(doc, index.Fields) - index.data.Insert(indexValue, doc.ID) - return true - }) -} - -// CreateIndex создаёт новый индекс на коллекции. -func (c *Collection) CreateIndex(name string, fields []string, unique bool) error { - c.mu.Lock() - defer c.mu.Unlock() - - if _, exists := c.indexes.Load(name); exists { - return fmt.Errorf("index %s already exists", name) - } - - now := time.Now().UnixMilli() - index := &Index{ - Name: name, - Fields: fields, - Unique: unique, - CreatedAt: now, - UpdatedAt: now, - data: NewSkipList(), - } - - var buildErr error - c.docs.Range(func(key, value interface{}) bool { - doc := value.(*Document) - if doc.IsDeleted() && c.metadata.Settings.SoftDelete { - return true - } - indexValue := c.extractIndexValue(doc, fields) - if unique { - if _, exists := index.data.Search(indexValue); exists { - buildErr = fmt.Errorf("duplicate key for index %s: %v", name, indexValue) - return false - } - } - index.data.Insert(indexValue, doc.ID) - return true - }) - - if buildErr != nil { - return buildErr - } - - c.indexes.Store(name, index) - c.metadata.IndexCount++ - - AuditIndexOperation("CREATE_INDEX", c.dbName, c.name, name, fields, unique) - - return nil -} - -// DropIndex удаляет индекс. -func (c *Collection) DropIndex(name string) error { - if _, exists := c.indexes.LoadAndDelete(name); !exists { - return fmt.Errorf("index not found: %s", name) - } - c.metadata.IndexCount-- - - AuditIndexOperation("DROP_INDEX", c.dbName, c.name, name, nil, false) - - return nil -} - -// GetIndexes возвращает список всех индексов. -func (c *Collection) GetIndexes() []string { - names := make([]string, 0) - c.indexes.Range(func(key, value interface{}) bool { - names = append(names, key.(string)) - return true - }) - return names -} - -// GetIndexesInfo возвращает подробную информацию об индексах. -func (c *Collection) GetIndexesInfo() []map[string]interface{} { - indexes := make([]map[string]interface{}, 0) - c.indexes.Range(func(key, value interface{}) bool { - idx := value.(*Index) - indexes = append(indexes, map[string]interface{}{ - "name": idx.Name, - "fields": idx.Fields, - "unique": idx.Unique, - "created_at": idx.CreatedAt, - "updated_at": idx.UpdatedAt, - }) - return true - }) - return indexes -} - -// extractIndexValue извлекает значение из документа для индексации. -func (c *Collection) extractIndexValue(doc *Document, fields []string) interface{} { - if len(fields) == 1 { - val, _ := doc.GetField(fields[0]) - return val - } - - parts := make([]string, 0, len(fields)) - for _, field := range fields { - if val, err := doc.GetField(field); err == nil { - parts = append(parts, fmt.Sprintf("%v", val)) - } else { - parts = append(parts, "NULL") - } - } - return strings.Join(parts, "|") -} - -// updateIndexes обновляет индексы для документа. -func (c *Collection) updateIndexes(doc *Document, add bool) { - if add { - c.addToIndexes(doc) - } else { - c.removeFromIndexes(doc) - } -} - -// checkUniqueConstraints проверяет уникальные индексы перед вставкой. -func (c *Collection) checkUniqueConstraints(doc *Document) error { - var errs []string - - c.indexes.Range(func(key, value interface{}) bool { - index := value.(*Index) - if index.Unique && index.data != nil { - indexValue := c.extractIndexValue(doc, index.Fields) - if _, exists := index.data.Search(indexValue); exists { - errs = append(errs, fmt.Sprintf("duplicate key for index %s: %v", index.Name, indexValue)) - } - } - return true - }) - - if len(errs) > 0 { - return fmt.Errorf("%s", strings.Join(errs, "; ")) - } - return nil -} - -// checkUniqueConstraintsUpdate проверяет уникальность при обновлении. -func (c *Collection) checkUniqueConstraintsUpdate(oldDoc, newDoc *Document) error { - var errs []string - - c.indexes.Range(func(key, value interface{}) bool { - index := value.(*Index) - if index.Unique && index.data != nil { - oldValue := c.extractIndexValue(oldDoc, index.Fields) - newValue := c.extractIndexValue(newDoc, index.Fields) - - if fmt.Sprintf("%v", oldValue) != fmt.Sprintf("%v", newValue) { - if _, exists := index.data.Search(newValue); exists { - errs = append(errs, fmt.Sprintf("duplicate key for index %s: %v", index.Name, newValue)) - } - } - } - return true - }) - - if len(errs) > 0 { - return fmt.Errorf("%s", strings.Join(errs, "; ")) - } - return nil -} - -// validateDocument валидирует документ согласно схеме коллекции. -func (c *Collection) validateDocument(doc *Document) error { - if doc.ID == "" { - return fmt.Errorf("document must have _id field") - } - return nil -} - -// ttlCleanupLoop периодически удаляет просроченные документы. -func (c *Collection) ttlCleanupLoop() { - ticker := time.NewTicker(time.Duration(c.metadata.Settings.TTLSeconds/2) * time.Second) - defer ticker.Stop() - - for range ticker.C { - now := time.Now().UnixMilli() - toDelete := make([]string, 0) - - c.docs.Range(func(key, value interface{}) bool { - doc := value.(*Document) - if !doc.IsDeleted() && now-doc.CreatedAt > int64(c.metadata.Settings.TTLSeconds*1000) { - toDelete = append(toDelete, doc.ID) - } - return true - }) - - for _, id := range toDelete { - c.Delete(id) - } - } -} - -// Count возвращает количество активных документов в коллекции. -func (c *Collection) Count() int64 { - if c.metadata.Settings.SoftDelete { - count := int64(0) - c.docs.Range(func(key, value interface{}) bool { - doc := value.(*Document) - if !doc.IsDeleted() { - count++ - } - return true - }) - return count - } - return c.docCount.Load() -} - -// CountAll возвращает общее количество документов (включая мягко удалённые). -func (c *Collection) CountAll() int64 { - return c.docCount.Load() -} - -// CountDeleted возвращает количество мягко удалённых документов. -func (c *Collection) CountDeleted() int64 { - if !c.metadata.Settings.SoftDelete { - return 0 - } - - count := int64(0) - c.docs.Range(func(key, value interface{}) bool { - doc := value.(*Document) - if doc.IsDeleted() { - count++ - } - return true - }) - return count -} - -// Size возвращает размер коллекции в байтах. -func (c *Collection) Size() int64 { - return c.sizeBytes.Load() -} - -// GetAllDocuments возвращает все активные документы коллекции. -func (c *Collection) GetAllDocuments() []*Document { - docs := make([]*Document, 0, c.docCount.Load()) - c.docs.Range(func(key, value interface{}) bool { - doc := value.(*Document) - if !c.metadata.Settings.SoftDelete || !doc.IsDeleted() { - docs = append(docs, doc) - } - return true - }) - return docs -} - -// GetAllDocumentsIncludingDeleted возвращает все документы (включая мягко удалённые). -func (c *Collection) GetAllDocumentsIncludingDeleted() []*Document { - docs := make([]*Document, 0, c.docCount.Load()) - c.docs.Range(func(key, value interface{}) bool { - docs = append(docs, value.(*Document)) - return true - }) - return docs -} - -// FindByFilter находит документы по произвольному фильтру. -func (c *Collection) FindByFilter(filter func(*Document) bool) []*Document { - results := make([]*Document, 0) - c.docs.Range(func(key, value interface{}) bool { - doc := value.(*Document) - if (!c.metadata.Settings.SoftDelete || !doc.IsDeleted()) && filter(doc) { - results = append(results, doc) - } - return true - }) - return results -} - -// GetMetadata возвращает метаданные коллекции. -func (c *Collection) GetMetadata() *CollectionMetadata { - c.mu.RLock() - defer c.mu.RUnlock() - return c.metadata -} - -// GetTimestamps возвращает временные метки коллекции. -func (c *Collection) GetTimestamps() map[string]int64 { - c.mu.RLock() - defer c.mu.RUnlock() - return map[string]int64{ - "created_at": c.metadata.CreatedAt, - "updated_at": c.metadata.UpdatedAt, - "deleted_at": c.metadata.DeletedAt, - } -} - -// Drop удаляет все документы из коллекции. -func (c *Collection) Drop() error { - c.mu.Lock() - defer c.mu.Unlock() - - c.docs = sync.Map{} - c.indexes = sync.Map{} - c.triggers = sync.Map{} - - if c.metadata.Settings.AutoIndexID { - c.CreateIndex("_id_", []string{"_id"}, true) - } - - c.docCount.Store(0) - c.sizeBytes.Store(0) - c.metadata.DocumentCount = 0 - c.metadata.SizeBytes = 0 - c.metadata.UpdatedAt = time.Now().UnixMilli() - - AuditCollectionOperation("DROP", c.dbName, c.name, nil) - - return nil -} - -// logConstraintChange логирует изменение ограничения. -func (c *Collection) logConstraintChange(action, constraintType, field string, oldVal, newVal interface{}) { - LogAudit("CONSTRAINT_"+action, "CONSTRAINT", fmt.Sprintf("%s.%s", c.dbName, c.name), map[string]interface{}{ - "constraint_type": constraintType, - "field": field, - "old_value": oldVal, - "new_value": newVal, - }) -} - -// logACLChange логирует изменение ACL. -func (c *Collection) logACLChange(action, role, permission string, granted bool) { - LogAudit("ACL_"+action, "ACL", fmt.Sprintf("%s.%s", c.dbName, c.name), map[string]interface{}{ - "role": role, - "permission": permission, - "granted": granted, - }) -} - -// ============================================================================= -// МЕТОДЫ ОГРАНИЧЕНИЙ (Constraints) -// ============================================================================= - -// AddRequiredField добавляет обязательное поле. -func (c *Collection) AddRequiredField(field string) { - c.constraints.mu.Lock() - oldMap := c.constraints.loadRequiredFieldsUnsafe() - oldVal := oldMap[field] - - newMap := make(map[string]bool) - for k, v := range oldMap { - newMap[k] = v - } - newMap[field] = true - - c.constraints.requiredFieldsPtr.Store(newMap) - c.constraints.mu.Unlock() - - c.logConstraintChange("ADD", "required", field, oldVal, true) -} - -// AddUniqueConstraint добавляет ограничение уникальности. -func (c *Collection) AddUniqueConstraint(field string) { - c.constraints.mu.Lock() - oldMap := c.constraints.loadUniqueFieldsUnsafe() - oldVal := oldMap[field] - - newMap := make(map[string]bool) - for k, v := range oldMap { - newMap[k] = v - } - newMap[field] = true - - c.constraints.uniqueFieldsPtr.Store(newMap) - c.constraints.mu.Unlock() - - c.logConstraintChange("ADD", "unique", field, oldVal, true) - c.CreateIndex("unique_"+field, []string{field}, true) -} - -// AddMinConstraint добавляет минимальное значение. -func (c *Collection) AddMinConstraint(field string, min float64) { - c.constraints.mu.Lock() - oldMap := c.constraints.loadMinValuesUnsafe() - oldVal := oldMap[field] - - newMap := make(map[string]float64) - for k, v := range oldMap { - newMap[k] = v - } - newMap[field] = min - - c.constraints.minValuesPtr.Store(newMap) - c.constraints.mu.Unlock() - - c.logConstraintChange("ADD", "min", field, oldVal, min) -} - -// AddMaxConstraint добавляет максимальное значение. -func (c *Collection) AddMaxConstraint(field string, max float64) { - c.constraints.mu.Lock() - oldMap := c.constraints.loadMaxValuesUnsafe() - oldVal := oldMap[field] - - newMap := make(map[string]float64) - for k, v := range oldMap { - newMap[k] = v - } - newMap[field] = max - - c.constraints.maxValuesPtr.Store(newMap) - c.constraints.mu.Unlock() - - c.logConstraintChange("ADD", "max", field, oldVal, max) -} - -// AddRegexConstraint добавляет regexp паттерн. -func (c *Collection) AddRegexConstraint(field string, pattern string) { - c.constraints.mu.Lock() - oldMap := c.constraints.loadPatternFieldsUnsafe() - oldVal := oldMap[field] - - newMap := make(map[string]string) - for k, v := range oldMap { - newMap[k] = v - } - newMap[field] = pattern - - c.constraints.patternFieldsPtr.Store(newMap) - c.constraints.mu.Unlock() - - c.logConstraintChange("ADD", "regex", field, oldVal, pattern) -} - -// AddEnumConstraint добавляет допустимые значения. -func (c *Collection) AddEnumConstraint(field string, values []interface{}) { - c.constraints.mu.Lock() - oldMap := c.constraints.loadEnumFieldsUnsafe() - oldVal := oldMap[field] - - newMap := make(map[string][]interface{}) - for k, v := range oldMap { - copiedVals := make([]interface{}, len(v)) - copy(copiedVals, v) - newMap[k] = copiedVals - } - copiedValues := make([]interface{}, len(values)) - copy(copiedValues, values) - newMap[field] = copiedValues - - c.constraints.enumFieldsPtr.Store(newMap) - c.constraints.mu.Unlock() - - c.logConstraintChange("ADD", "enum", field, oldVal, values) -} - -// RemoveRequiredField удаляет обязательное поле. -func (c *Collection) RemoveRequiredField(field string) { - c.constraints.mu.Lock() - oldMap := c.constraints.loadRequiredFieldsUnsafe() - oldVal := oldMap[field] - - newMap := make(map[string]bool) - for k, v := range oldMap { - if k != field { - newMap[k] = v - } - } - - c.constraints.requiredFieldsPtr.Store(newMap) - c.constraints.mu.Unlock() - - c.logConstraintChange("REMOVE", "required", field, oldVal, nil) -} - -// RemoveUniqueConstraint удаляет ограничение уникальности. -func (c *Collection) RemoveUniqueConstraint(field string) { - c.constraints.mu.Lock() - oldMap := c.constraints.loadUniqueFieldsUnsafe() - oldVal := oldMap[field] - - newMap := make(map[string]bool) - for k, v := range oldMap { - if k != field { - newMap[k] = v - } - } - - c.constraints.uniqueFieldsPtr.Store(newMap) - c.constraints.mu.Unlock() - - c.logConstraintChange("REMOVE", "unique", field, oldVal, nil) - c.DropIndex("unique_" + field) -} - -// RemoveMinConstraint удаляет минимальное значение. -func (c *Collection) RemoveMinConstraint(field string) { - c.constraints.mu.Lock() - oldMap := c.constraints.loadMinValuesUnsafe() - oldVal := oldMap[field] - - newMap := make(map[string]float64) - for k, v := range oldMap { - if k != field { - newMap[k] = v - } - } - - c.constraints.minValuesPtr.Store(newMap) - c.constraints.mu.Unlock() - - c.logConstraintChange("REMOVE", "min", field, oldVal, nil) -} - -// RemoveMaxConstraint удаляет максимальное значение. -func (c *Collection) RemoveMaxConstraint(field string) { - c.constraints.mu.Lock() - oldMap := c.constraints.loadMaxValuesUnsafe() - oldVal := oldMap[field] - - newMap := make(map[string]float64) - for k, v := range oldMap { - if k != field { - newMap[k] = v - } - } - - c.constraints.maxValuesPtr.Store(newMap) - c.constraints.mu.Unlock() - - c.logConstraintChange("REMOVE", "max", field, oldVal, nil) -} - -// RemoveRegexConstraint удаляет regexp паттерн. -func (c *Collection) RemoveRegexConstraint(field string) { - c.constraints.mu.Lock() - oldMap := c.constraints.loadPatternFieldsUnsafe() - oldVal := oldMap[field] - - newMap := make(map[string]string) - for k, v := range oldMap { - if k != field { - newMap[k] = v - } - } - - c.constraints.patternFieldsPtr.Store(newMap) - c.constraints.mu.Unlock() - - c.logConstraintChange("REMOVE", "regex", field, oldVal, nil) -} - -// RemoveEnumConstraint удаляет допустимые значения. -func (c *Collection) RemoveEnumConstraint(field string) { - c.constraints.mu.Lock() - oldMap := c.constraints.loadEnumFieldsUnsafe() - oldVal := oldMap[field] - - newMap := make(map[string][]interface{}) - for k, v := range oldMap { - if k != field { - copiedVals := make([]interface{}, len(v)) - copy(copiedVals, v) - newMap[k] = copiedVals - } - } - - c.constraints.enumFieldsPtr.Store(newMap) - c.constraints.mu.Unlock() - - c.logConstraintChange("REMOVE", "enum", field, oldVal, nil) -} - -// GetRequiredFields возвращает список обязательных полей. -func (c *Collection) GetRequiredFields() []string { - fieldsMap := c.constraints.loadRequiredFields() - fields := make([]string, 0, len(fieldsMap)) - for field := range fieldsMap { - fields = append(fields, field) - } - return fields -} - -// GetUniqueConstraints возвращает список уникальных полей. -func (c *Collection) GetUniqueConstraints() []string { - fieldsMap := c.constraints.loadUniqueFields() - fields := make([]string, 0, len(fieldsMap)) - for field := range fieldsMap { - fields = append(fields, field) - } - return fields -} - -// GetMinConstraints возвращает карту минимальных значений. -func (c *Collection) GetMinConstraints() map[string]float64 { - result := c.constraints.loadMinValues() - resultCopy := make(map[string]float64) - for k, v := range result { - resultCopy[k] = v - } - return resultCopy -} - -// GetMaxConstraints возвращает карту максимальных значений. -func (c *Collection) GetMaxConstraints() map[string]float64 { - result := c.constraints.loadMaxValues() - resultCopy := make(map[string]float64) - for k, v := range result { - resultCopy[k] = v - } - return resultCopy -} - -// GetEnumConstraints возвращает карту enum ограничений. -func (c *Collection) GetEnumConstraints() map[string][]interface{} { - result := c.constraints.loadEnumFields() - resultCopy := make(map[string][]interface{}) - for k, v := range result { - copied := make([]interface{}, len(v)) - copy(copied, v) - resultCopy[k] = copied - } - return resultCopy -} - -// GetRegexConstraints возвращает карту regex паттернов. -func (c *Collection) GetRegexConstraints() map[string]string { - result := c.constraints.loadPatternFields() - resultCopy := make(map[string]string) - for k, v := range result { - resultCopy[k] = v - } - return resultCopy -} - -// GetConstraints возвращает все ограничения коллекции. -func (c *Collection) GetConstraints() map[string]interface{} { - return map[string]interface{}{ - "required_fields": c.GetRequiredFields(), - "unique_fields": c.GetUniqueConstraints(), - "min_values": c.GetMinConstraints(), - "max_values": c.GetMaxConstraints(), - "enum_values": c.GetEnumConstraints(), - "regex_patterns": c.GetRegexConstraints(), - } -} - -// GetConstraintTimestamps возвращает временные метки ограничений. -func (c *Collection) GetConstraintTimestamps() map[string]interface{} { - return map[string]interface{}{ - "created_at": c.constraints.createdAt, - "updated_at": c.constraints.updatedAt.Load(), - "history_count": len(c.constraints.loadConstraintHistory()), - } -} - -// GetConstraintHistory возвращает историю изменений ограничений. -func (c *Collection) GetConstraintHistory() []ConstraintChange { - history := c.constraints.loadConstraintHistory() - result := make([]ConstraintChange, len(history)) - copy(result, history) - return result -} - -// ValidateDocument проверяет документ на соответствие ограничениям. -func (cons *Constraints) ValidateDocument(doc *Document) error { - requiredFields := cons.loadRequiredFields() - for field := range requiredFields { - if !doc.HasField(field) { - return fmt.Errorf("required field '%s' is missing", field) - } - } - - minValues := cons.loadMinValues() - for field, minVal := range minValues { - if val, err := doc.GetField(field); err == nil { - if numVal, ok := toFloat64(val); ok { - if numVal < minVal { - return fmt.Errorf("field '%s' value %v is less than minimum %v", field, numVal, minVal) - } - } - } - } - - maxValues := cons.loadMaxValues() - for field, maxVal := range maxValues { - if val, err := doc.GetField(field); err == nil { - if numVal, ok := toFloat64(val); ok { - if numVal > maxVal { - return fmt.Errorf("field '%s' value %v exceeds maximum %v", field, numVal, maxVal) - } - } - } - } - - patternFields := cons.loadPatternFields() - for field, pattern := range patternFields { - if val, err := doc.GetField(field); err == nil { - if strVal, ok := val.(string); ok { - if pattern != "" && !strings.Contains(strVal, pattern) { - return fmt.Errorf("field '%s' value '%s' does not match pattern '%s'", field, strVal, pattern) - } - } - } - } - - enumFields := cons.loadEnumFields() - for field, allowedValues := range enumFields { - if val, err := doc.GetField(field); err == nil { - found := false - for _, allowed := range allowedValues { - if fmt.Sprintf("%v", val) == fmt.Sprintf("%v", allowed) { - found = true - break - } - } - if !found { - return fmt.Errorf("field '%s' value '%v' not in allowed list", field, val) - } - } - } - - return nil -} - -// ============================================================================= -// МЕТОДЫ ACL -// ============================================================================= - -// SetACL устанавливает ACL для коллекции. -func (c *Collection) SetACL(role string, canRead, canWrite, canDelete, isAdmin bool) { - c.acl.updatedAt.Store(time.Now().UnixMilli()) - - if canRead { - c.acl.mu.Lock() - oldRoles := c.acl.loadReadRolesUnsafe() - if !oldRoles[role] { - newRoles := make(map[string]bool) - for k, v := range oldRoles { - newRoles[k] = v - } - newRoles[role] = true - c.acl.readRolesPtr.Store(newRoles) - c.acl.mu.Unlock() - c.logACLChange("GRANT", role, "read", true) - } else { - c.acl.mu.Unlock() - } - } - if canWrite { - c.acl.mu.Lock() - oldRoles := c.acl.loadWriteRolesUnsafe() - if !oldRoles[role] { - newRoles := make(map[string]bool) - for k, v := range oldRoles { - newRoles[k] = v - } - newRoles[role] = true - c.acl.writeRolesPtr.Store(newRoles) - c.acl.mu.Unlock() - c.logACLChange("GRANT", role, "write", true) - } else { - c.acl.mu.Unlock() - } - } - if canDelete { - c.acl.mu.Lock() - oldRoles := c.acl.loadDeleteRolesUnsafe() - if !oldRoles[role] { - newRoles := make(map[string]bool) - for k, v := range oldRoles { - newRoles[k] = v - } - newRoles[role] = true - c.acl.deleteRolesPtr.Store(newRoles) - c.acl.mu.Unlock() - c.logACLChange("GRANT", role, "delete", true) - } else { - c.acl.mu.Unlock() - } - } - if isAdmin { - c.acl.mu.Lock() - oldRoles := c.acl.loadAdminRolesUnsafe() - if !oldRoles[role] { - newRoles := make(map[string]bool) - for k, v := range oldRoles { - newRoles[k] = v - } - newRoles[role] = true - c.acl.adminRolesPtr.Store(newRoles) - c.acl.mu.Unlock() - c.logACLChange("GRANT", role, "admin", true) - } else { - c.acl.mu.Unlock() - } - } -} - -// RevokeACL отзывает разрешения у роли. -func (c *Collection) RevokeACL(role string, permission string) { - c.acl.updatedAt.Store(time.Now().UnixMilli()) - - switch permission { - case "read": - c.acl.mu.Lock() - oldRoles := c.acl.loadReadRolesUnsafe() - if oldRoles[role] { - newRoles := make(map[string]bool) - for k, v := range oldRoles { - if k != role { - newRoles[k] = v - } - } - c.acl.readRolesPtr.Store(newRoles) - c.acl.mu.Unlock() - c.logACLChange("REVOKE", role, "read", false) - } else { - c.acl.mu.Unlock() - } - case "write": - c.acl.mu.Lock() - oldRoles := c.acl.loadWriteRolesUnsafe() - if oldRoles[role] { - newRoles := make(map[string]bool) - for k, v := range oldRoles { - if k != role { - newRoles[k] = v - } - } - c.acl.writeRolesPtr.Store(newRoles) - c.acl.mu.Unlock() - c.logACLChange("REVOKE", role, "write", false) - } else { - c.acl.mu.Unlock() - } - case "delete": - c.acl.mu.Lock() - oldRoles := c.acl.loadDeleteRolesUnsafe() - if oldRoles[role] { - newRoles := make(map[string]bool) - for k, v := range oldRoles { - if k != role { - newRoles[k] = v - } - } - c.acl.deleteRolesPtr.Store(newRoles) - c.acl.mu.Unlock() - c.logACLChange("REVOKE", role, "delete", false) - } else { - c.acl.mu.Unlock() - } - case "admin": - c.acl.mu.Lock() - oldRoles := c.acl.loadAdminRolesUnsafe() - if oldRoles[role] { - newRoles := make(map[string]bool) - for k, v := range oldRoles { - if k != role { - newRoles[k] = v - } - } - c.acl.adminRolesPtr.Store(newRoles) - c.acl.mu.Unlock() - c.logACLChange("REVOKE", role, "admin", false) - } else { - c.acl.mu.Unlock() - } - } -} - -// CheckPermission проверяет наличие разрешения у роли. -func (c *Collection) CheckPermission(role, operation string) bool { - return c.acl.CheckPermission(role, operation) -} - -// GetACLTimestamps возвращает временные метки ACL. -func (c *Collection) GetACLTimestamps() map[string]interface{} { - return map[string]interface{}{ - "created_at": c.acl.createdAt, - "updated_at": c.acl.updatedAt.Load(), - } -} - -// GetACLHistory возвращает историю изменений ACL. -func (c *Collection) GetACLHistory() []ACLChange { - history := c.acl.loadACLHistory() - result := make([]ACLChange, len(history)) - copy(result, history) - return result -} - -// GetACLUpdatedAt возвращает время последнего обновления ACL. -func (c *Collection) GetACLUpdatedAt() int64 { - return c.acl.updatedAt.Load() -} - -// GetACLCreatedAt возвращает время создания ACL. -func (c *Collection) GetACLCreatedAt() int64 { - return c.acl.createdAt -} - -// ============================================================================= -// ВНУТРЕННИЕ МЕТОДЫ БЕЗ БЛОКИРОВКИ -// ============================================================================= - -// loadRequiredFieldsUnsafe загружает карту обязательных полей без блокировки. -func (cons *Constraints) loadRequiredFieldsUnsafe() map[string]bool { - val := cons.requiredFieldsPtr.Load() - if val == nil { - return make(map[string]bool) - } - return val.(map[string]bool) -} - -// loadUniqueFieldsUnsafe загружает карту уникальных полей без блокировки. -func (cons *Constraints) loadUniqueFieldsUnsafe() map[string]bool { - val := cons.uniqueFieldsPtr.Load() - if val == nil { - return make(map[string]bool) - } - return val.(map[string]bool) -} - -// loadMinValuesUnsafe загружает карту минимальных значений без блокировки. -func (cons *Constraints) loadMinValuesUnsafe() map[string]float64 { - val := cons.minValuesPtr.Load() - if val == nil { - return make(map[string]float64) - } - return val.(map[string]float64) -} - -// loadMaxValuesUnsafe загружает карту максимальных значений без блокировки. -func (cons *Constraints) loadMaxValuesUnsafe() map[string]float64 { - val := cons.maxValuesPtr.Load() - if val == nil { - return make(map[string]float64) - } - return val.(map[string]float64) -} - -// loadPatternFieldsUnsafe загружает карту regex паттернов без блокировки. -func (cons *Constraints) loadPatternFieldsUnsafe() map[string]string { - val := cons.patternFieldsPtr.Load() - if val == nil { - return make(map[string]string) - } - return val.(map[string]string) -} - -// loadEnumFieldsUnsafe загружает карту enum ограничений без блокировки. -func (cons *Constraints) loadEnumFieldsUnsafe() map[string][]interface{} { - val := cons.enumFieldsPtr.Load() - if val == nil { - return make(map[string][]interface{}) - } - return val.(map[string][]interface{}) -} - -// loadReadRolesUnsafe загружает роли чтения без блокировки. -func (acl *CollectionACL) loadReadRolesUnsafe() map[string]bool { - val := acl.readRolesPtr.Load() - if val == nil { - return make(map[string]bool) - } - return val.(map[string]bool) -} - -// loadWriteRolesUnsafe загружает роли записи без блокировки. -func (acl *CollectionACL) loadWriteRolesUnsafe() map[string]bool { - val := acl.writeRolesPtr.Load() - if val == nil { - return make(map[string]bool) - } - return val.(map[string]bool) -} - -// loadDeleteRolesUnsafe загружает роли удаления без блокировки. -func (acl *CollectionACL) loadDeleteRolesUnsafe() map[string]bool { - val := acl.deleteRolesPtr.Load() - if val == nil { - return make(map[string]bool) - } - return val.(map[string]bool) -} - -// loadAdminRolesUnsafe загружает административные роли без блокировки. -func (acl *CollectionACL) loadAdminRolesUnsafe() map[string]bool { - val := acl.adminRolesPtr.Load() - if val == nil { - return make(map[string]bool) - } - return val.(map[string]bool) -} - -// toFloat64 конвертирует interface{} в float64. -func toFloat64(val interface{}) (float64, bool) { - switch v := val.(type) { - case int: - return float64(v), true - case int64: - return float64(v), true - case float64: - return v, true - case float32: - return float64(v), true - default: - return 0, false - } -}