Nigiri/collection.go

218 lines
4.7 KiB
Go

package nigiri
import (
"fmt"
"os"
"path/filepath"
"regexp"
"sync"
)
type StoreManager interface {
LoadData(path string) error
SaveData(path string) error
Clear()
EntityExists(id int) bool
SetValidator(validator any)
}
type WALLogger interface {
LogOperation(store, operation string, id int, data any)
}
type Collection struct {
baseDir string
stores map[string]StoreManager
migrator *Migrator
wal *WAL
mu sync.RWMutex
}
// ============================================================================
// Core Collection Management
// ============================================================================
func NewCollection(baseDir string) *Collection {
return &Collection{
baseDir: baseDir,
stores: make(map[string]StoreManager),
}
}
func (c *Collection) Close() error {
if err := c.Checkpoint(); err != nil {
return err
}
if c.wal != nil {
return c.wal.close()
}
return nil
}
// ============================================================================
// Store Management
// ============================================================================
func (c *Collection) Add(name string, store StoreManager) error {
if err := c.ensureWAL(); err != nil {
return err
}
c.mu.Lock()
defer c.mu.Unlock()
c.stores[name] = store
store.SetValidator(c)
if walStore, ok := store.(interface{ SetWALLogger(WALLogger, string) }); ok {
walStore.SetWALLogger(c, name)
}
path := filepath.Join(c.baseDir, name+".json")
if _, err := os.Stat(path); err == nil {
return store.LoadData(path)
}
return nil
}
func Get[T any](c *Collection, name string) *BaseStore[T] {
c.mu.RLock()
defer c.mu.RUnlock()
if store, exists := c.stores[name]; exists {
if typed, ok := store.(*BaseStore[T]); ok {
return typed
}
}
return nil
}
// ============================================================================
// Persistence Operations
// ============================================================================
func (c *Collection) Load() error {
c.mu.RLock()
defer c.mu.RUnlock()
os.MkdirAll(c.baseDir, 0755)
for name, store := range c.stores {
path := filepath.Join(c.baseDir, name+".json")
if err := store.LoadData(path); err != nil {
return fmt.Errorf("load %s: %w", name, err)
}
}
return nil
}
func (c *Collection) Save() error {
c.mu.RLock()
defer c.mu.RUnlock()
os.MkdirAll(c.baseDir, 0755)
for name, store := range c.stores {
path := filepath.Join(c.baseDir, name+".json")
if err := store.SaveData(path); err != nil {
return fmt.Errorf("save %s: %w", name, err)
}
}
return nil
}
func (c *Collection) Clear() {
c.mu.RLock()
defer c.mu.RUnlock()
for _, store := range c.stores {
store.Clear()
}
}
func (c *Collection) Checkpoint() error {
if c.wal != nil {
if err := c.wal.flush(); err != nil {
return err
}
}
if err := c.Save(); err != nil {
return err
}
if c.wal != nil {
return c.wal.truncate()
}
return nil
}
// ============================================================================
// Validation & Relationships
// ============================================================================
func (c *Collection) EntityExists(entityName string, id int) bool {
c.mu.RLock()
defer c.mu.RUnlock()
if store, exists := c.stores[entityName]; exists {
return store.EntityExists(id)
}
return false
}
// ============================================================================
// WAL Operations
// ============================================================================
func (c *Collection) LogOperation(store, operation string, id int, data any) {
if c.wal != nil {
c.wal.logOperation(store, operation, id, data)
}
}
func (c *Collection) ensureWAL() error {
if c.wal != nil {
return nil
}
wal, err := newWAL(c.baseDir)
if err != nil {
return err
}
c.wal = wal
return wal.recover(c)
}
// ============================================================================
// Migration Operations
// ============================================================================
func (c *Collection) GetMigrator() *Migrator {
if c.migrator == nil {
c.migrator = NewMigrator()
}
return c.migrator
}
func (c *Collection) RegisterMigrationCommand(name string, pattern *regexp.Regexp, handler MigrationHandler) {
c.GetMigrator().RegisterCommand(name, pattern, handler)
}
func (c *Collection) MigrateStore(storeName, command string) error {
c.mu.Lock()
defer c.mu.Unlock()
path := filepath.Join(c.baseDir, storeName+".json")
return c.GetMigrator().MigrateFile(path, command)
}
func (c *Collection) RunMigrationScript(scriptFile string) error {
return c.GetMigrator().RunScript(c.baseDir, scriptFile)
}
func (c *Collection) CreateMigrationCLI() *MigrationCLI {
return NewMigrationCLI(c)
}