From 9e3e5057be6d259416fea00ecaa64b81bc8437de 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: Fri, 12 Jun 2026 22:53:01 +0000 Subject: [PATCH] Delete internal/storage/collection.go --- internal/storage/collection.go | 1354 -------------------------------- 1 file changed, 1354 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 988c7d1..0000000 --- a/internal/storage/collection.go +++ /dev/null @@ -1,1354 +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 доступ. -// Исправлено: корректная работа уникальных индексов, удаление из индексов при обновлении. - -package storage - -import ( - "fmt" - "sync" - "sync/atomic" - "time" - "strings" - - "futriis/internal/serializer" -) - -// Collection представляет коллекцию документов (аналог таблицы) -type Collection struct { - dbName string // имя базы данных (добавлено) - name string // имя коллекции - docs sync.Map // map[string]*Document - wait-free хранилище документов - indexes sync.Map // map[string]*Index - индексы для быстрого поиска - metadata *CollectionMetadata // Метаданные коллекции - docCount atomic.Int64 // Атомарный счётчик документов - sizeBytes atomic.Int64 // Атомарный размер коллекции в байтах - mu sync.RWMutex // Для операций, изменяющих структуру коллекции - constraints *Constraints // Ограничения коллекции - acl *CollectionACL // ACL для коллекции -} - -// 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 представляет индекс для ускорения поиска (хранится отдельно от документов) -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 sync.Map // map[interface{}]string - значение индекса -> ID документа -} - -// Constraints представляет ограничения на коллекцию -type Constraints struct { - mu sync.RWMutex - RequiredFields map[string]bool // Обязательные поля - UniqueFields map[string]bool // Уникальные поля (дополнительно к индексам) - MinValues map[string]float64 // Минимальные значения для числовых полей - MaxValues map[string]float64 // Максимальные значения для числовых полей - PatternFields map[string]string // Regexp паттерны для полей - EnumFields map[string][]interface{} // Допустимые значения для полей - CreatedAt int64 `msgpack:"created_at"` // Время создания ограничений - UpdatedAt int64 `msgpack:"updated_at"` // Время последнего обновления ограничений - ConstraintHistory []ConstraintChange `msgpack:"constraint_history"` // История изменений ограничений -} - -// 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 представляет список контроля доступа для коллекции -type CollectionACL struct { - mu sync.RWMutex - ReadRoles map[string]bool // Роли, имеющие доступ на чтение - WriteRoles map[string]bool // Роли, имеющие доступ на запись - DeleteRoles map[string]bool // Роли, имеющие доступ на удаление - AdminRoles map[string]bool // Роли, имеющие полный доступ - CreatedAt int64 `msgpack:"created_at"` // Время создания ACL - UpdatedAt int64 `msgpack:"updated_at"` // Время последнего обновления ACL - ACLHistory []ACLChange `msgpack:"acl_history"` // История изменений ACL -} - -// 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"` -} - -// CheckPermission проверяет наличие разрешения у роли -func (acl *CollectionACL) CheckPermission(role, operation string) bool { - acl.mu.RLock() - defer acl.mu.RUnlock() - - // Администратор имеет все права - if acl.AdminRoles[role] { - return true - } - - switch operation { - case "read": - return acl.ReadRoles[role] - case "write": - return acl.WriteRoles[role] - case "delete": - return acl.DeleteRoles[role] - default: - return false - } -} - -// NewCollection создаёт новую коллекцию -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: &Constraints{ - RequiredFields: make(map[string]bool), - UniqueFields: make(map[string]bool), - MinValues: make(map[string]float64), - MaxValues: make(map[string]float64), - PatternFields: make(map[string]string), - EnumFields: make(map[string][]interface{}), - CreatedAt: now, - UpdatedAt: now, - ConstraintHistory: make([]ConstraintChange, 0), - }, - acl: &CollectionACL{ - ReadRoles: make(map[string]bool), - WriteRoles: make(map[string]bool), - DeleteRoles: make(map[string]bool), - AdminRoles: make(map[string]bool), - CreatedAt: now, - UpdatedAt: now, - ACLHistory: make([]ACLChange, 0), - }, - } - - // Автоматически создаём первичный индекс по _id - if settings.AutoIndexID { - coll.CreateIndex("_id_", []string{"_id"}, true) - } - - // Запускаем фоновую задачу для удаления просроченных документов - if settings.TTLSeconds > 0 { - go coll.ttlCleanupLoop() - } - - // Аудит создания коллекции - AuditCollectionOperation("CREATE", dbName, name, settings) - - return coll -} - -// Name возвращает имя коллекции -func (c *Collection) Name() string { - return c.name -} - -// DBName возвращает имя базы данных -func (c *Collection) DBName() string { - return c.dbName -} - -// Insert вставляет документ в коллекцию (wait-free) -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) - } - - // Обновление индексов (wait-free) - c.updateIndexes(doc, true) - - // Обновление метаданных - c.docCount.Add(1) - c.sizeBytes.Add(int64(len(data))) - c.metadata.DocumentCount = c.docCount.Load() - c.metadata.SizeBytes = c.sizeBytes.Load() - c.metadata.UpdatedAt = time.Now().UnixMilli() - - // Аудит вставки документа - AuditDocumentOperation("INSERT", c.dbName, c.name, 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()) - } - - // Проверяем, не истёк ли TTL - 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.Unique { - // Уникальный индекс возвращает один документ - if docID, ok := index.data.Load(value); ok { - if doc, err := c.Find(docID.(string)); err == nil { - docs = append(docs, doc) - } - } - } else { - // Для неуникального индекса используем Range с правильным сравнением - index.data.Range(func(key, val interface{}) bool { - if compareValues(key, value) { - if doc, err := c.Find(val.(string)); err == nil { - docs = append(docs, doc) - } - } - return true - }) - } - - 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) - - index.data.Range(func(key, val interface{}) bool { - if keyStr, ok := key.(string); ok { - if strings.HasPrefix(keyStr, prefix) { - if doc, err := c.Find(val.(string)); err == nil { - docs = append(docs, doc) - } - } - } - return true - }) - - return docs, nil -} - -// Update обновляет документ по ID -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.removeFromIndexes(oldDoc) - c.addToIndexes(newDoc) - - // Сохраняем обновлённый документ - c.docs.Store(id, newDoc) - - c.metadata.UpdatedAt = time.Now().UnixMilli() - - // Аудит обновления документа - AuditDocumentOperation("UPDATE", c.dbName, c.name, id, updates) - - return nil -} - -// Delete удаляет документ по ID -func (c *Collection) Delete(id string) error { - val, ok := c.docs.Load(id) - if !ok { - return fmt.Errorf("key not found") - } - - doc := val.(*Document) - - if c.metadata.Settings.SoftDelete { - // Мягкое удаление - doc.SoftDelete() - c.docs.Store(id, doc) - - // Удаляем из индексов при мягком удалении - c.removeFromIndexes(doc) - - // Аудит мягкого удаления - AuditDocumentOperation("SOFT_DELETE", c.dbName, c.name, id, map[string]interface{}{ - "deleted_at": doc.DeletedAt, - }) - } else { - // Физическое удаление - // Удаляем из индексов - c.removeFromIndexes(doc) - - // Удаляем документ - c.docs.Delete(id) - - // Аудит физического удаления - AuditDocumentOperation("DELETE", c.dbName, c.name, id, nil) - } - - // Обновляем метаданные - c.docCount.Add(-1) - c.metadata.DocumentCount = c.docCount.Load() - c.metadata.UpdatedAt = time.Now().UnixMilli() - - return nil -} - -// PermanentDelete выполняет физическое удаление мягко удалённого документа -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.removeFromIndexes(doc) - - // Удаляем документ - c.docs.Delete(id) - - // Обновляем метаданные - c.docCount.Add(-1) - c.metadata.DocumentCount = c.docCount.Load() - c.metadata.UpdatedAt = time.Now().UnixMilli() - - // Аудит физического удаления - AuditDocumentOperation("PERMANENT_DELETE", c.dbName, c.name, id, nil) - - return nil -} - -// RestoreDeleted восстанавливает мягко удалённый документ -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") - } - - doc.Restore() - - // Восстанавливаем индексы - c.addToIndexes(doc) - - c.docs.Store(id, doc) - c.metadata.UpdatedAt = time.Now().UnixMilli() - - // Аудит восстановления - AuditDocumentOperation("RESTORE", c.dbName, c.name, id, nil) - - return nil -} - -// removeFromIndexes удаляет документ из всех индексов (wait-free) -func (c *Collection) removeFromIndexes(doc *Document) { - c.indexes.Range(func(key, value interface{}) bool { - index := value.(*Index) - indexValue := c.extractIndexValue(doc, index.Fields) - index.data.Delete(indexValue) - return true - }) -} - -// addToIndexes добавляет документ во все индексы (wait-free) -func (c *Collection) addToIndexes(doc *Document) { - c.indexes.Range(func(key, value interface{}) bool { - index := value.(*Index) - indexValue := c.extractIndexValue(doc, index.Fields) - if index.Unique { - index.data.LoadOrStore(indexValue, doc.ID) - } else { - index.data.Store(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, - } - - // Строим индекс на существующих документах (wait-free) - 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 _, loaded := index.data.LoadOrStore(indexValue, doc.ID); loaded { - // Найден дубликат - откатываем создание индекса - c.mu.Unlock() - return false - } - } else { - index.data.Store(indexValue, doc.ID) - } - return true - }) - - c.indexes.Store(name, index) - c.metadata.IndexCount++ - - // Аудит создания индекса - AuditCollectionOperation("CREATE_INDEX", c.dbName, c.name, map[string]interface{}{ - "index_name": name, - "fields": fields, - "unique": 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-- - - // Аудит удаления индекса - AuditCollectionOperation("DROP_INDEX", c.dbName, c.name, map[string]interface{}{ - "index_name": name, - }) - - 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 возвращает подробную информацию об индексах (для API) -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 { - indexValue := c.extractIndexValue(doc, index.Fields) - if _, exists := index.data.Load(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 { - 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.Load(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{} - - 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{}) { - nowMs, nowStr := GetCurrentTimestamp() - change := ConstraintChange{ - Timestamp: nowMs, - TimestampStr: nowStr, - Action: action, - ConstraintType: constraintType, - Field: field, - OldValue: oldVal, - NewValue: newVal, - } - - c.constraints.mu.Lock() - defer c.constraints.mu.Unlock() - c.constraints.ConstraintHistory = append(c.constraints.ConstraintHistory, change) - c.constraints.UpdatedAt = nowMs - - // Аудит изменения ограничения - 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) { - nowMs, nowStr := GetCurrentTimestamp() - change := ACLChange{ - Timestamp: nowMs, - TimestampStr: nowStr, - Action: action, - Role: role, - Permission: permission, - Granted: granted, - } - - c.acl.mu.Lock() - defer c.acl.mu.Unlock() - c.acl.ACLHistory = append(c.acl.ACLHistory, change) - c.acl.UpdatedAt = nowMs - - // Аудит изменения ACL - LogAudit("ACL_"+action, "ACL", fmt.Sprintf("%s.%s", c.dbName, c.name), map[string]interface{}{ - "role": role, - "permission": permission, - "granted": granted, - }) -} - -// ========== Constraints Methods ========== - -// AddRequiredField добавляет обязательное поле -func (c *Collection) AddRequiredField(field string) { - c.constraints.mu.Lock() - oldVal := c.constraints.RequiredFields[field] - c.constraints.RequiredFields[field] = true - c.constraints.mu.Unlock() - - c.logConstraintChange("ADD", "required", field, oldVal, true) -} - -// AddUniqueConstraint добавляет ограничение уникальности -func (c *Collection) AddUniqueConstraint(field string) { - c.constraints.mu.Lock() - oldVal := c.constraints.UniqueFields[field] - c.constraints.UniqueFields[field] = true - 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() - oldVal := c.constraints.MinValues[field] - c.constraints.MinValues[field] = min - c.constraints.mu.Unlock() - - c.logConstraintChange("ADD", "min", field, oldVal, min) -} - -// AddMaxConstraint добавляет максимальное значение -func (c *Collection) AddMaxConstraint(field string, max float64) { - c.constraints.mu.Lock() - oldVal := c.constraints.MaxValues[field] - c.constraints.MaxValues[field] = max - 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() - oldVal := c.constraints.PatternFields[field] - c.constraints.PatternFields[field] = pattern - c.constraints.mu.Unlock() - - c.logConstraintChange("ADD", "regex", field, oldVal, pattern) -} - -// AddEnumConstraint добавляет допустимые значения -func (c *Collection) AddEnumConstraint(field string, values []interface{}) { - c.constraints.mu.Lock() - oldVal := c.constraints.EnumFields[field] - c.constraints.EnumFields[field] = values - c.constraints.mu.Unlock() - - c.logConstraintChange("ADD", "enum", field, oldVal, values) -} - -// RemoveRequiredField удаляет обязательное поле -func (c *Collection) RemoveRequiredField(field string) { - c.constraints.mu.Lock() - oldVal := c.constraints.RequiredFields[field] - delete(c.constraints.RequiredFields, field) - c.constraints.mu.Unlock() - - c.logConstraintChange("REMOVE", "required", field, oldVal, nil) -} - -// RemoveUniqueConstraint удаляет ограничение уникальности -func (c *Collection) RemoveUniqueConstraint(field string) { - c.constraints.mu.Lock() - oldVal := c.constraints.UniqueFields[field] - delete(c.constraints.UniqueFields, field) - 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() - oldVal := c.constraints.MinValues[field] - delete(c.constraints.MinValues, field) - c.constraints.mu.Unlock() - - c.logConstraintChange("REMOVE", "min", field, oldVal, nil) -} - -// RemoveMaxConstraint удаляет максимальное значение -func (c *Collection) RemoveMaxConstraint(field string) { - c.constraints.mu.Lock() - oldVal := c.constraints.MaxValues[field] - delete(c.constraints.MaxValues, field) - c.constraints.mu.Unlock() - - c.logConstraintChange("REMOVE", "max", field, oldVal, nil) -} - -// RemoveRegexConstraint удаляет regexp паттерн -func (c *Collection) RemoveRegexConstraint(field string) { - c.constraints.mu.Lock() - oldVal := c.constraints.PatternFields[field] - delete(c.constraints.PatternFields, field) - c.constraints.mu.Unlock() - - c.logConstraintChange("REMOVE", "regex", field, oldVal, nil) -} - -// RemoveEnumConstraint удаляет допустимые значения -func (c *Collection) RemoveEnumConstraint(field string) { - c.constraints.mu.Lock() - oldVal := c.constraints.EnumFields[field] - delete(c.constraints.EnumFields, field) - c.constraints.mu.Unlock() - - c.logConstraintChange("REMOVE", "enum", field, oldVal, nil) -} - -// GetRequiredFields возвращает список обязательных полей -func (c *Collection) GetRequiredFields() []string { - c.constraints.mu.RLock() - defer c.constraints.mu.RUnlock() - - fields := make([]string, 0, len(c.constraints.RequiredFields)) - for field := range c.constraints.RequiredFields { - fields = append(fields, field) - } - return fields -} - -// GetUniqueConstraints возвращает список уникальных полей -func (c *Collection) GetUniqueConstraints() []string { - c.constraints.mu.RLock() - defer c.constraints.mu.RUnlock() - - fields := make([]string, 0, len(c.constraints.UniqueFields)) - for field := range c.constraints.UniqueFields { - fields = append(fields, field) - } - return fields -} - -// GetMinConstraints возвращает карту минимальных значений -func (c *Collection) GetMinConstraints() map[string]float64 { - c.constraints.mu.RLock() - defer c.constraints.mu.RUnlock() - - result := make(map[string]float64) - for field, val := range c.constraints.MinValues { - result[field] = val - } - return result -} - -// GetMaxConstraints возвращает карту максимальных значений -func (c *Collection) GetMaxConstraints() map[string]float64 { - c.constraints.mu.RLock() - defer c.constraints.mu.RUnlock() - - result := make(map[string]float64) - for field, val := range c.constraints.MaxValues { - result[field] = val - } - return result -} - -// GetEnumConstraints возвращает карту enum ограничений -func (c *Collection) GetEnumConstraints() map[string][]interface{} { - c.constraints.mu.RLock() - defer c.constraints.mu.RUnlock() - - result := make(map[string][]interface{}) - for field, vals := range c.constraints.EnumFields { - copied := make([]interface{}, len(vals)) - copy(copied, vals) - result[field] = copied - } - return result -} - -// GetRegexConstraints возвращает карту regex паттернов -func (c *Collection) GetRegexConstraints() map[string]string { - c.constraints.mu.RLock() - defer c.constraints.mu.RUnlock() - - result := make(map[string]string) - for field, pattern := range c.constraints.PatternFields { - result[field] = pattern - } - return result -} - -// GetConstraints возвращает все ограничения коллекции (для API) -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{} { - c.constraints.mu.RLock() - defer c.constraints.mu.RUnlock() - - return map[string]interface{}{ - "created_at": c.constraints.CreatedAt, - "updated_at": c.constraints.UpdatedAt, - "history_count": len(c.constraints.ConstraintHistory), - } -} - -// GetConstraintHistory возвращает историю изменений ограничений -func (c *Collection) GetConstraintHistory() []ConstraintChange { - c.constraints.mu.RLock() - defer c.constraints.mu.RUnlock() - - result := make([]ConstraintChange, len(c.constraints.ConstraintHistory)) - copy(result, c.constraints.ConstraintHistory) - return result -} - -// ValidateDocument проверяет документ на соответствие ограничениям -func (cons *Constraints) ValidateDocument(doc *Document) error { - cons.mu.RLock() - defer cons.mu.RUnlock() - - // Проверка обязательных полей - for field := range cons.RequiredFields { - if !doc.HasField(field) { - return fmt.Errorf("required field '%s' is missing", field) - } - } - - // Проверка числовых ограничений - for field, minVal := range cons.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) - } - } - } - } - - for field, maxVal := range cons.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) - } - } - } - } - - // Проверка regex паттернов - for field, pattern := range cons.PatternFields { - if val, err := doc.GetField(field); err == nil { - if strVal, ok := val.(string); ok { - // Простая проверка regex (можно заменить на regexp.MustCompile) - if pattern != "" && !strings.Contains(strVal, pattern) { - return fmt.Errorf("field '%s' value '%s' does not match pattern '%s'", field, strVal, pattern) - } - } - } - } - - // Проверка enum - for field, allowedValues := range cons.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 Methods ========== - -// SetACL устанавливает ACL для коллекции -func (c *Collection) SetACL(role string, canRead, canWrite, canDelete, isAdmin bool) { - c.acl.mu.Lock() - defer c.acl.mu.Unlock() - - c.acl.UpdatedAt = time.Now().UnixMilli() - - if canRead { - if !c.acl.ReadRoles[role] { - c.acl.ReadRoles[role] = true - c.logACLChange("GRANT", role, "read", true) - } - } - if canWrite { - if !c.acl.WriteRoles[role] { - c.acl.WriteRoles[role] = true - c.logACLChange("GRANT", role, "write", true) - } - } - if canDelete { - if !c.acl.DeleteRoles[role] { - c.acl.DeleteRoles[role] = true - c.logACLChange("GRANT", role, "delete", true) - } - } - if isAdmin { - if !c.acl.AdminRoles[role] { - c.acl.AdminRoles[role] = true - c.logACLChange("GRANT", role, "admin", true) - } - } -} - -// RevokeACL отзывает разрешения у роли -func (c *Collection) RevokeACL(role string, permission string) { - c.acl.mu.Lock() - defer c.acl.mu.Unlock() - - c.acl.UpdatedAt = time.Now().UnixMilli() - - switch permission { - case "read": - if c.acl.ReadRoles[role] { - delete(c.acl.ReadRoles, role) - c.logACLChange("REVOKE", role, "read", false) - } - case "write": - if c.acl.WriteRoles[role] { - delete(c.acl.WriteRoles, role) - c.logACLChange("REVOKE", role, "write", false) - } - case "delete": - if c.acl.DeleteRoles[role] { - delete(c.acl.DeleteRoles, role) - c.logACLChange("REVOKE", role, "delete", false) - } - case "admin": - if c.acl.AdminRoles[role] { - delete(c.acl.AdminRoles, role) - c.logACLChange("REVOKE", role, "admin", false) - } - } -} - -// CheckPermission проверяет наличие разрешения у роли -func (c *Collection) CheckPermission(role, operation string) bool { - return c.acl.CheckPermission(role, operation) -} - -// GetACLTimestamps возвращает временные метки ACL -func (c *Collection) GetACLTimestamps() map[string]interface{} { - c.acl.mu.RLock() - defer c.acl.mu.RUnlock() - - return map[string]interface{}{ - "created_at": c.acl.CreatedAt, - "updated_at": c.acl.UpdatedAt, - } -} - -// GetACLHistory возвращает историю изменений ACL -func (c *Collection) GetACLHistory() []ACLChange { - c.acl.mu.RLock() - defer c.acl.mu.RUnlock() - - result := make([]ACLChange, len(c.acl.ACLHistory)) - copy(result, c.acl.ACLHistory) - return result -} - -// GetACLUpdatedAt возвращает время последнего обновления ACL -func (c *Collection) GetACLUpdatedAt() int64 { - c.acl.mu.RLock() - defer c.acl.mu.RUnlock() - return c.acl.UpdatedAt -} - -// GetACLCreatedAt возвращает время создания ACL -func (c *Collection) GetACLCreatedAt() int64 { - c.acl.mu.RLock() - defer c.acl.mu.RUnlock() - return c.acl.CreatedAt -} - -// 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 - } -}