Files
futriix/internal/storage/collection.go
T

1999 lines
58 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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
}
}