package nigiri import ( "fmt" "maps" "os" "reflect" "sort" "strings" "sync" ) type Validatable interface { Validate() error } type IndexBuilder[T any] func(allItems map[int]*T) any type BaseStore[T any] struct { items map[int]*T maxID int mu sync.RWMutex itemType reflect.Type indices map[string]any indexBuilders map[string]IndexBuilder[T] schema *SchemaInfo uniqueIndices map[string]map[any]int validator any walLogger WALLogger storeName string } // ============================================================================ // Core Store Management // ============================================================================ func NewBaseStore[T any]() *BaseStore[T] { var zero T schema := ParseSchema[T]() store := &BaseStore[T]{ items: make(map[int]*T), itemType: reflect.TypeOf(zero), indices: make(map[string]any), indexBuilders: make(map[string]IndexBuilder[T]), schema: schema, uniqueIndices: make(map[string]map[any]int), } for fieldName, constraints := range schema.Constraints { for _, constraint := range constraints { if constraint.Type == ConstraintUnique { store.uniqueIndices[fieldName] = make(map[any]int) } } } store.registerSchemaIndices() return store } func (bs *BaseStore[T]) SetValidator(validator any) { bs.mu.Lock() defer bs.mu.Unlock() bs.validator = validator } func (bs *BaseStore[T]) SetWALLogger(logger WALLogger, name string) { bs.walLogger = logger bs.storeName = name } func (bs *BaseStore[T]) EntityExists(id int) bool { bs.mu.RLock() defer bs.mu.RUnlock() _, exists := bs.items[id] return exists } func (bs *BaseStore[T]) GetNextID() int { bs.mu.Lock() defer bs.mu.Unlock() bs.maxID++ return bs.maxID } func (bs *BaseStore[T]) Clear() { bs.mu.Lock() defer bs.mu.Unlock() if bs.walLogger != nil { bs.walLogger.LogOperation(bs.storeName, "clear", 0, nil) } bs.items = make(map[int]*T) bs.maxID = 0 for fieldName := range bs.uniqueIndices { bs.uniqueIndices[fieldName] = make(map[any]int) } bs.rebuildIndicesUnsafe() } // ============================================================================ // Validation & Constraints // ============================================================================ func (bs *BaseStore[T]) ValidateConstraints(id int, item *T) error { itemValue := reflect.ValueOf(item).Elem() for fieldName, constraints := range bs.schema.Constraints { fieldValue := itemValue.FieldByName(fieldName) if !fieldValue.IsValid() { continue } for _, constraint := range constraints { switch constraint.Type { case ConstraintRequired: if isZeroValue(fieldValue) { return fmt.Errorf("field %s is required", fieldName) } case ConstraintUnique: value := fieldValue.Interface() if existingID, exists := bs.uniqueIndices[fieldName][value]; exists && existingID != id { return fmt.Errorf("field %s value %v already exists", fieldName, value) } } } } return nil } func (bs *BaseStore[T]) validateRelationships(item *T) error { if bs.validator == nil { return nil } validator, ok := bs.validator.(interface { EntityExists(entityName string, id int) bool }) if !ok { return nil } itemValue := reflect.ValueOf(item).Elem() for fieldName, constraint := range bs.schema.Relationships { fieldValue := itemValue.FieldByName(fieldName) if !fieldValue.IsValid() { continue } switch constraint.Relationship { case RelationshipManyToOne: if !fieldValue.IsNil() { targetItem := fieldValue.Elem() idField := targetItem.FieldByName("ID") if idField.IsValid() { targetID := int(idField.Int()) if !validator.EntityExists(constraint.Target, targetID) { return fmt.Errorf("foreign key violation: %s references non-existent %s.%d", fieldName, constraint.Target, targetID) } } } case RelationshipOneToMany: if !fieldValue.IsNil() { for i := 0; i < fieldValue.Len(); i++ { elem := fieldValue.Index(i) if !elem.IsNil() { targetItem := elem.Elem() idField := targetItem.FieldByName("ID") if idField.IsValid() { targetID := int(idField.Int()) if !validator.EntityExists(constraint.Target, targetID) { return fmt.Errorf("foreign key violation: %s[%d] references non-existent %s.%d", fieldName, i, constraint.Target, targetID) } } } } } } } return nil } func isZeroValue(v reflect.Value) bool { return v.Interface() == reflect.Zero(v.Type()).Interface() } func (bs *BaseStore[T]) updateUniqueIndices(id int, item *T, add bool) { itemValue := reflect.ValueOf(item).Elem() for fieldName := range bs.uniqueIndices { fieldValue := itemValue.FieldByName(fieldName) if !fieldValue.IsValid() { continue } value := fieldValue.Interface() if add { bs.uniqueIndices[fieldName][value] = id } else { delete(bs.uniqueIndices[fieldName], value) } } } // ============================================================================ // Indexing // ============================================================================ func (bs *BaseStore[T]) registerSchemaIndices() { for fieldName, indexName := range bs.schema.Indices { bs.RegisterIndex(indexName, BuildFieldLookupIndex[T](fieldName)) } } func (bs *BaseStore[T]) RegisterIndex(name string, builder IndexBuilder[T]) { bs.mu.Lock() defer bs.mu.Unlock() bs.indexBuilders[name] = builder } func (bs *BaseStore[T]) GetIndex(name string) (any, bool) { bs.mu.RLock() defer bs.mu.RUnlock() index, exists := bs.indices[name] return index, exists } func (bs *BaseStore[T]) RebuildIndices() { bs.mu.Lock() defer bs.mu.Unlock() bs.rebuildIndicesUnsafe() } func (bs *BaseStore[T]) rebuildIndicesUnsafe() { allItems := make(map[int]*T, len(bs.items)) maps.Copy(allItems, bs.items) for name, builder := range bs.indexBuilders { bs.indices[name] = builder(allItems) } } // ============================================================================ // CRUD Operations // ============================================================================ func (bs *BaseStore[T]) Add(id int, item *T) error { bs.mu.Lock() defer bs.mu.Unlock() if err := bs.ValidateConstraints(id, item); err != nil { return err } if err := bs.validateRelationships(item); err != nil { return err } if validatable, ok := any(item).(Validatable); ok { if err := validatable.Validate(); err != nil { return err } } if bs.walLogger != nil { bs.walLogger.LogOperation(bs.storeName, "add", id, item) } bs.updateUniqueIndices(id, item, true) bs.items[id] = item if id > bs.maxID { bs.maxID = id } bs.rebuildIndicesUnsafe() return nil } func (bs *BaseStore[T]) Update(id int, item *T) error { return bs.Add(id, item) } func (bs *BaseStore[T]) Create(item *T) (int, error) { id := bs.GetNextID() err := bs.Add(id, item) return id, err } func (bs *BaseStore[T]) Remove(id int) { bs.mu.Lock() defer bs.mu.Unlock() if bs.walLogger != nil { bs.walLogger.LogOperation(bs.storeName, "remove", id, nil) } if item, exists := bs.items[id]; exists { bs.updateUniqueIndices(id, item, false) } delete(bs.items, id) bs.rebuildIndicesUnsafe() } // Unsafe operations for performance-critical paths func (bs *BaseStore[T]) AddUnsafe(id int, item *T) { bs.mu.Lock() defer bs.mu.Unlock() bs.items[id] = item if id > bs.maxID { bs.maxID = id } } func (bs *BaseStore[T]) RemoveUnsafe(id int) { bs.mu.Lock() defer bs.mu.Unlock() delete(bs.items, id) } // ============================================================================ // Querying & Retrieval // ============================================================================ func (bs *BaseStore[T]) Find(id int) (*T, bool) { bs.mu.RLock() defer bs.mu.RUnlock() item, exists := bs.items[id] return item, exists } func (bs *BaseStore[T]) GetByID(id int) (*T, bool) { return bs.Find(id) } func (bs *BaseStore[T]) GetAll() map[int]*T { bs.mu.RLock() defer bs.mu.RUnlock() result := make(map[int]*T, len(bs.items)) maps.Copy(result, bs.items) return result } func (bs *BaseStore[T]) AllSorted(indexName string) []*T { bs.mu.RLock() defer bs.mu.RUnlock() if index, exists := bs.indices[indexName]; exists { if sortedIDs, ok := index.([]int); ok { result := make([]*T, 0, len(sortedIDs)) for _, id := range sortedIDs { if item, exists := bs.items[id]; exists { result = append(result, item) } } return result } } ids := make([]int, 0, len(bs.items)) for id := range bs.items { ids = append(ids, id) } sort.Ints(ids) result := make([]*T, 0, len(ids)) for _, id := range ids { result = append(result, bs.items[id]) } return result } func (bs *BaseStore[T]) LookupByIndex(indexName, key string) (*T, bool) { bs.mu.RLock() defer bs.mu.RUnlock() if index, exists := bs.indices[indexName]; exists { if lookupMap, ok := index.(map[string]int); ok { if id, found := lookupMap[key]; found { if item, exists := bs.items[id]; exists { return item, true } } } } return nil, false } func (bs *BaseStore[T]) GroupByIndex(indexName string, key any) []*T { bs.mu.RLock() defer bs.mu.RUnlock() if index, exists := bs.indices[indexName]; exists { switch groupMap := index.(type) { case map[int][]int: if intKey, ok := key.(int); ok { if ids, found := groupMap[intKey]; found { result := make([]*T, 0, len(ids)) for _, id := range ids { if item, exists := bs.items[id]; exists { result = append(result, item) } } return result } } case map[string][]int: if strKey, ok := key.(string); ok { if ids, found := groupMap[strKey]; found { result := make([]*T, 0, len(ids)) for _, id := range ids { if item, exists := bs.items[id]; exists { result = append(result, item) } } return result } } } } return []*T{} } func (bs *BaseStore[T]) FilterByIndex(indexName string, filterFunc func(*T) bool) []*T { bs.mu.RLock() defer bs.mu.RUnlock() var sourceIDs []int if index, exists := bs.indices[indexName]; exists { if sortedIDs, ok := index.([]int); ok { sourceIDs = sortedIDs } } if sourceIDs == nil { for id := range bs.items { sourceIDs = append(sourceIDs, id) } sort.Ints(sourceIDs) } var result []*T for _, id := range sourceIDs { if item, exists := bs.items[id]; exists && filterFunc(item) { result = append(result, item) } } return result } // ============================================================================ // Migration Operations // ============================================================================ func (bs *BaseStore[T]) ApplyMigration(migrationFunc func(map[int]*T) error) error { bs.mu.Lock() defer bs.mu.Unlock() itemsCopy := make(map[int]*T, len(bs.items)) maps.Copy(itemsCopy, bs.items) if err := migrationFunc(itemsCopy); err != nil { return fmt.Errorf("migration failed: %w", err) } bs.items = itemsCopy bs.rebuildIndicesUnsafe() return nil } func (bs *BaseStore[T]) MigrateFromJSON(filename string, migrationCommands []string, migrator *Migrator) error { data, err := os.ReadFile(filename) if err != nil { return fmt.Errorf("failed to read file: %w", err) } result := data for _, command := range migrationCommands { result, err = migrator.ApplyCommand(result, command) if err != nil { return fmt.Errorf("command '%s' failed: %w", command, err) } } backupPath := filename + ".backup" if err := os.WriteFile(backupPath, data, 0644); err != nil { return fmt.Errorf("failed to create backup: %w", err) } tempFile := filename + ".migrated" if err := os.WriteFile(tempFile, result, 0644); err != nil { return fmt.Errorf("failed to write migrated data: %w", err) } if err := bs.ValidateAfterMigration(tempFile); err != nil { os.Remove(tempFile) return err } if err := os.Rename(tempFile, filename); err != nil { return fmt.Errorf("failed to replace original file: %w", err) } return bs.LoadFromJSON(filename) } func (bs *BaseStore[T]) BatchMigrate(filename string, commands []string) error { if len(commands) == 0 { return nil } migrator := NewMigrator() return bs.MigrateFromJSON(filename, commands, migrator) } // ============================================================================ // Index Builder Functions // ============================================================================ func BuildFieldLookupIndex[T any](fieldName string) IndexBuilder[T] { return func(allItems map[int]*T) any { index := make(map[string]int) for id, item := range allItems { itemValue := reflect.ValueOf(item).Elem() fieldValue := itemValue.FieldByName(fieldName) if !fieldValue.IsValid() { continue } key := fmt.Sprintf("%v", fieldValue.Interface()) index[key] = id } return index } } func BuildStringLookupIndex[T any](keyFunc func(*T) string) IndexBuilder[T] { return func(allItems map[int]*T) any { index := make(map[string]int) for id, item := range allItems { index[keyFunc(item)] = id } return index } } func BuildCaseInsensitiveLookupIndex[T any](keyFunc func(*T) string) IndexBuilder[T] { return func(allItems map[int]*T) any { index := make(map[string]int) for id, item := range allItems { key := strings.ToLower(keyFunc(item)) index[key] = id } return index } } func BuildIntGroupIndex[T any](keyFunc func(*T) int) IndexBuilder[T] { return func(allItems map[int]*T) any { index := make(map[int][]int) for id, item := range allItems { key := keyFunc(item) index[key] = append(index[key], id) } for key := range index { sort.Ints(index[key]) } return index } } func BuildStringGroupIndex[T any](keyFunc func(*T) string) IndexBuilder[T] { return func(allItems map[int]*T) any { index := make(map[string][]int) for id, item := range allItems { key := keyFunc(item) index[key] = append(index[key], id) } for key := range index { sort.Ints(index[key]) } return index } } func BuildSortedListIndex[T any](sortFunc func(*T, *T) bool) IndexBuilder[T] { return func(allItems map[int]*T) any { ids := make([]int, 0, len(allItems)) for id := range allItems { ids = append(ids, id) } sort.Slice(ids, func(i, j int) bool { return sortFunc(allItems[ids[i]], allItems[ids[j]]) }) return ids } } func BuildFilteredIntGroupIndex[T any](filterFunc func(*T) bool, keyFunc func(*T) int) IndexBuilder[T] { return func(allItems map[int]*T) any { index := make(map[int][]int) for id, item := range allItems { if filterFunc(item) { key := keyFunc(item) index[key] = append(index[key], id) } } for key := range index { sort.Ints(index[key]) } return index } } func BuildFilteredStringGroupIndex[T any](filterFunc func(*T) bool, keyFunc func(*T) string) IndexBuilder[T] { return func(allItems map[int]*T) any { index := make(map[string][]int) for id, item := range allItems { if filterFunc(item) { key := keyFunc(item) index[key] = append(index[key], id) } } for key := range index { sort.Ints(index[key]) } return index } } // ============================================================================ // Utility Functions // ============================================================================ func NewSingleton[S any](initFunc func() *S) func() *S { var store *S var once sync.Once return func() *S { once.Do(func() { store = initFunc() }) return store } } // ============================================================================ // Find Functions // ============================================================================ // FindByField performs a linear search for items where fieldName equals value func (bs *BaseStore[T]) FindByField(fieldName string, value any) []*T { bs.mu.RLock() defer bs.mu.RUnlock() var result []*T for _, item := range bs.items { itemValue := reflect.ValueOf(item).Elem() fieldValue := itemValue.FieldByName(fieldName) if !fieldValue.IsValid() { continue } if fieldValue.Interface() == value { result = append(result, item) } } return result } // FindFirstByField returns the first item where fieldName equals value func (bs *BaseStore[T]) FindFirstByField(fieldName string, value any) (*T, bool) { bs.mu.RLock() defer bs.mu.RUnlock() for _, item := range bs.items { itemValue := reflect.ValueOf(item).Elem() fieldValue := itemValue.FieldByName(fieldName) if !fieldValue.IsValid() { continue } if fieldValue.Interface() == value { return item, true } } return nil, false } // FindByFields performs a linear search with multiple field conditions (AND logic) func (bs *BaseStore[T]) FindByFields(conditions map[string]any) []*T { bs.mu.RLock() defer bs.mu.RUnlock() var result []*T for _, item := range bs.items { itemValue := reflect.ValueOf(item).Elem() matches := true for fieldName, expectedValue := range conditions { fieldValue := itemValue.FieldByName(fieldName) if !fieldValue.IsValid() || fieldValue.Interface() != expectedValue { matches = false break } } if matches { result = append(result, item) } } return result } // FindByPredicate allows custom filtering logic func (bs *BaseStore[T]) FindByPredicate(predicate func(*T) bool) []*T { bs.mu.RLock() defer bs.mu.RUnlock() var result []*T for _, item := range bs.items { if predicate(item) { result = append(result, item) } } return result }