225 lines
7.4 KiB
Go
225 lines
7.4 KiB
Go
|
|
// Файл: internal/storage/engine.go
|
|||
|
|
// Назначение: In-memory движок хранения документов с поддержкой коллекций,
|
|||
|
|
// слайсов (аналог БД), тапплов (аналог таблиц), полей и кортежей.
|
|||
|
|
// Полностью wait-free с использованием sync.Map и атомарных операций.
|
|||
|
|
|
|||
|
|
package storage
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"fmt"
|
|||
|
|
"sync"
|
|||
|
|
"sync/atomic"
|
|||
|
|
|
|||
|
|
"futriis/internal/log"
|
|||
|
|
"futriis/internal/serializer"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// Storage представляет основное хранилище баз данных
|
|||
|
|
type Storage struct {
|
|||
|
|
databases sync.Map // map[string]*Database
|
|||
|
|
pageSize int64
|
|||
|
|
logger *log.Logger
|
|||
|
|
totalDocs atomic.Int64
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Database представляет базу данных (аналог слайса в реляционных СУБД)
|
|||
|
|
type Database struct {
|
|||
|
|
name string
|
|||
|
|
collections sync.Map // map[string]*Collection
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// NewStorage создаёт новый экземпляр хранилища
|
|||
|
|
func NewStorage(pageSizeMB int, logger *log.Logger) *Storage {
|
|||
|
|
return &Storage{
|
|||
|
|
pageSize: int64(pageSizeMB) * 1024 * 1024,
|
|||
|
|
logger: logger,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// CreateDatabase создаёт новую базу данных
|
|||
|
|
func (s *Storage) CreateDatabase(name string) error {
|
|||
|
|
if _, exists := s.databases.LoadOrStore(name, &Database{name: name}); exists {
|
|||
|
|
return fmt.Errorf("database already exists")
|
|||
|
|
}
|
|||
|
|
AuditDatabaseOperation("CREATE", name)
|
|||
|
|
s.logger.Info("Database created: " + name)
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// GetDatabase возвращает базу данных по имени
|
|||
|
|
func (s *Storage) GetDatabase(name string) (*Database, error) {
|
|||
|
|
if val, ok := s.databases.Load(name); ok {
|
|||
|
|
return val.(*Database), nil
|
|||
|
|
}
|
|||
|
|
return nil, fmt.Errorf("database not found")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// DropDatabase удаляет базу данных
|
|||
|
|
func (s *Storage) DropDatabase(name string) error {
|
|||
|
|
if _, ok := s.databases.LoadAndDelete(name); !ok {
|
|||
|
|
return fmt.Errorf("database not found")
|
|||
|
|
}
|
|||
|
|
AuditDatabaseOperation("DROP", name)
|
|||
|
|
s.logger.Info("Database dropped: " + name)
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ListDatabases возвращает список всех баз данных
|
|||
|
|
func (s *Storage) ListDatabases() []string {
|
|||
|
|
databases := make([]string, 0)
|
|||
|
|
s.databases.Range(func(key, value interface{}) bool {
|
|||
|
|
databases = append(databases, key.(string))
|
|||
|
|
return true
|
|||
|
|
})
|
|||
|
|
return databases
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Name возвращает имя базы данных
|
|||
|
|
func (db *Database) Name() string {
|
|||
|
|
return db.name
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// CreateCollection создаёт новую коллекцию в базе данных
|
|||
|
|
func (db *Database) CreateCollection(name string) error {
|
|||
|
|
if _, exists := db.collections.LoadOrStore(name, NewCollection(name, nil)); exists {
|
|||
|
|
return fmt.Errorf("collection already exists")
|
|||
|
|
}
|
|||
|
|
AuditCollectionOperation("CREATE", db.name, name, nil)
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// CreateCollectionWithSettings создаёт коллекцию с настройками
|
|||
|
|
func (db *Database) CreateCollectionWithSettings(name string, settings *CollectionSettings) error {
|
|||
|
|
if _, exists := db.collections.LoadOrStore(name, NewCollection(name, settings)); exists {
|
|||
|
|
return fmt.Errorf("collection already exists")
|
|||
|
|
}
|
|||
|
|
AuditCollectionOperation("CREATE", db.name, name, settings)
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// GetCollection возвращает коллекцию по имени
|
|||
|
|
func (db *Database) GetCollection(name string) (*Collection, error) {
|
|||
|
|
if val, ok := db.collections.Load(name); ok {
|
|||
|
|
return val.(*Collection), nil
|
|||
|
|
}
|
|||
|
|
return nil, fmt.Errorf("collection not found")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// DropCollection удаляет коллекцию
|
|||
|
|
func (db *Database) DropCollection(name string) error {
|
|||
|
|
if _, ok := db.collections.LoadAndDelete(name); !ok {
|
|||
|
|
return fmt.Errorf("collection not found")
|
|||
|
|
}
|
|||
|
|
AuditCollectionOperation("DROP", db.name, name, nil)
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ListCollections возвращает список всех коллекций в базе данных
|
|||
|
|
func (db *Database) ListCollections() []string {
|
|||
|
|
collections := make([]string, 0)
|
|||
|
|
db.collections.Range(func(key, value interface{}) bool {
|
|||
|
|
collections = append(collections, key.(string))
|
|||
|
|
return true
|
|||
|
|
})
|
|||
|
|
return collections
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// GetTotalDocuments возвращает общее количество документов во всех коллекциях
|
|||
|
|
func (s *Storage) GetTotalDocuments() int64 {
|
|||
|
|
return s.totalDocs.Load()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// GetPageSize возвращает размер страницы памяти
|
|||
|
|
func (s *Storage) GetPageSize() int64 {
|
|||
|
|
return s.pageSize
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// SerializeDatabase сериализует всю базу данных в MessagePack
|
|||
|
|
func (db *Database) SerializeDatabase() ([]byte, error) {
|
|||
|
|
dbData := make(map[string]interface{})
|
|||
|
|
|
|||
|
|
db.collections.Range(func(key, value interface{}) bool {
|
|||
|
|
coll := value.(*Collection)
|
|||
|
|
collData := make(map[string]interface{})
|
|||
|
|
|
|||
|
|
// Собираем все документы коллекции
|
|||
|
|
docs := coll.GetAllDocuments()
|
|||
|
|
collDocs := make([]*Document, 0, len(docs))
|
|||
|
|
for _, doc := range docs {
|
|||
|
|
collDocs = append(collDocs, doc)
|
|||
|
|
}
|
|||
|
|
collData["documents"] = collDocs
|
|||
|
|
collData["metadata"] = coll.GetMetadata()
|
|||
|
|
|
|||
|
|
dbData[key.(string)] = collData
|
|||
|
|
return true
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
return serializer.Marshal(dbData)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// DeserializeDatabase десериализует базу данных из MessagePack
|
|||
|
|
func (db *Database) DeserializeDatabase(data []byte) error {
|
|||
|
|
var dbData map[string]interface{}
|
|||
|
|
if err := serializer.Unmarshal(data, &dbData); err != nil {
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for collName, collDataRaw := range dbData {
|
|||
|
|
collData := collDataRaw.(map[string]interface{})
|
|||
|
|
|
|||
|
|
// Создаём коллекцию
|
|||
|
|
settings := &CollectionSettings{
|
|||
|
|
MaxDocuments: 0,
|
|||
|
|
ValidateSchema: false,
|
|||
|
|
AutoIndexID: true,
|
|||
|
|
TTLSeconds: 0,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if metaRaw, ok := collData["metadata"]; ok {
|
|||
|
|
if meta, ok := metaRaw.(*CollectionMetadata); ok {
|
|||
|
|
if meta.Settings != nil {
|
|||
|
|
settings = meta.Settings
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
coll := NewCollection(collName, settings)
|
|||
|
|
|
|||
|
|
// Восстанавливаем документы
|
|||
|
|
if docsRaw, ok := collData["documents"]; ok {
|
|||
|
|
if docs, ok := docsRaw.([]*Document); ok {
|
|||
|
|
for _, doc := range docs {
|
|||
|
|
coll.Insert(doc)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
db.collections.Store(collName, coll)
|
|||
|
|
AuditCollectionOperation("RESTORE", db.name, collName, settings)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// GetDatabaseNames возвращает имена всех баз данных
|
|||
|
|
func (s *Storage) GetDatabaseNames() []string {
|
|||
|
|
return s.ListDatabases()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ExistsDatabase проверяет существование базы данных
|
|||
|
|
func (s *Storage) ExistsDatabase(name string) bool {
|
|||
|
|
_, ok := s.databases.Load(name)
|
|||
|
|
return ok
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// GetDatabaseCount возвращает количество баз данных
|
|||
|
|
func (s *Storage) GetDatabaseCount() int {
|
|||
|
|
count := 0
|
|||
|
|
s.databases.Range(func(key, value interface{}) bool {
|
|||
|
|
count++
|
|||
|
|
return true
|
|||
|
|
})
|
|||
|
|
return count
|
|||
|
|
}
|