218 lines
4.7 KiB
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)
|
|
}
|