package store import ( "sort" "strconv" "strings" "time" "github.com/google/uuid" ) func (m *Memory) ListMaintenancePolicies(cursor string, limit int) ([]*MaintenancePolicy, string, bool, error) { if limit <= 0 { limit = 50 } m.mu.RLock() defer m.mu.RUnlock() all := make([]*MaintenancePolicy, 0, len(m.maintenancePolicies)) for _, p := range m.maintenancePolicies { all = append(all, p) } sort.Slice(all, func(i, j int) bool { if all[i].CreatedAt.Equal(all[j].CreatedAt) { return all[i].ID > all[j].ID } return all[i].CreatedAt.After(all[j].CreatedAt) }) off := parseMaintCursor(cursor) end := off + limit next := "" hasMore := false if end > len(all) { end = len(all) } else if end < len(all) { hasMore = true next = formatMaintCursor(end) } if off >= len(all) { return nil, "", false, nil } out := make([]*MaintenancePolicy, end-off) copy(out, all[off:end]) return out, next, hasMore, nil } func (m *Memory) GetMaintenancePolicy(id string) (*MaintenancePolicy, error) { m.mu.RLock() defer m.mu.RUnlock() p, ok := m.maintenancePolicies[id] if !ok { return nil, ErrNotFound } return cloneMaintenancePolicy(p), nil } func (m *Memory) CreateMaintenancePolicy(in *MaintenancePolicy) (*MaintenancePolicy, error) { if in == nil { return nil, ErrInvalidInput } vacuum := in.VacuumStrategy if vacuum == "" { vacuum = VacuumStrategyNone } if err := ValidateMaintenancePolicyInput(in.Name, in.TableName, vacuum, in.Schedule); err != nil { return nil, err } m.mu.Lock() defer m.mu.Unlock() now := time.Now().UTC() id := uuid.NewString() p := &MaintenancePolicy{ ID: id, Name: strings.TrimSpace(in.Name), TableName: strings.TrimSpace(in.TableName), Condition: NormalizeMaintenancePolicyCondition(in.Condition), RetentionPeriodSec: in.RetentionPeriodSec, MaxRows: in.MaxRows, VacuumStrategy: vacuum, Schedule: strings.TrimSpace(in.Schedule), Enabled: in.Enabled, DryRunEnabled: in.DryRunEnabled, CreatedAt: now, UpdatedAt: now, } m.maintenancePolicies[id] = p return cloneMaintenancePolicy(p), nil } func (m *Memory) UpdateMaintenancePolicy(id string, patch *MaintenancePolicyPatch) (*MaintenancePolicy, error) { if patch == nil { return nil, ErrInvalidInput } m.mu.Lock() defer m.mu.Unlock() p, ok := m.maintenancePolicies[id] if !ok { return nil, ErrNotFound } if patch.Name != nil { p.Name = strings.TrimSpace(*patch.Name) } if patch.TableName != nil { p.TableName = strings.TrimSpace(*patch.TableName) } if patch.Condition != nil { p.Condition = NormalizeMaintenancePolicyCondition(*patch.Condition) } if patch.RetentionPeriodSec != nil { p.RetentionPeriodSec = patch.RetentionPeriodSec } if patch.MaxRows != nil { p.MaxRows = patch.MaxRows } if patch.VacuumStrategy != nil { if !ValidVacuumStrategy(*patch.VacuumStrategy) { return nil, ErrInvalidInput } p.VacuumStrategy = strings.TrimSpace(*patch.VacuumStrategy) } if patch.Schedule != nil { p.Schedule = strings.TrimSpace(*patch.Schedule) } if patch.Enabled != nil { p.Enabled = *patch.Enabled } if patch.DryRunEnabled != nil { p.DryRunEnabled = *patch.DryRunEnabled } if err := ValidateMaintenancePolicyInput(p.Name, p.TableName, p.VacuumStrategy, p.Schedule); err != nil { return nil, err } p.UpdatedAt = time.Now().UTC() return cloneMaintenancePolicy(p), nil } func (m *Memory) DeleteMaintenancePolicy(id string) error { m.mu.Lock() defer m.mu.Unlock() if _, ok := m.maintenancePolicies[id]; !ok { return ErrNotFound } delete(m.maintenancePolicies, id) return nil } func (m *Memory) TouchMaintenancePolicyRun(id, status, errMsg string) error { m.mu.Lock() defer m.mu.Unlock() p, ok := m.maintenancePolicies[id] if !ok { return ErrNotFound } now := time.Now().UTC() p.LastRunAt = &now p.LastStatus = status p.LastError = errMsg p.UpdatedAt = now return nil } func (m *Memory) AppendMaintenancePolicyConfigAudit(actor, policyID, action string, before, after map[string]any) error { m.mu.Lock() defer m.mu.Unlock() row := &MaintenancePolicyConfigAudit{ ID: uuid.NewString(), PolicyID: policyID, ActorPrefix: strings.TrimSpace(actor), Action: action, Before: before, After: after, CreatedAt: time.Now().UTC(), } m.maintConfigAudit = append(m.maintConfigAudit, row) return nil } func (m *Memory) ListMaintenancePolicyConfigAudit(cursor string, limit int) ([]*MaintenancePolicyConfigAudit, string, bool, error) { if limit <= 0 { limit = 50 } m.mu.RLock() defer m.mu.RUnlock() all := append([]*MaintenancePolicyConfigAudit(nil), m.maintConfigAudit...) sort.Slice(all, func(i, j int) bool { if all[i].CreatedAt.Equal(all[j].CreatedAt) { return all[i].ID > all[j].ID } return all[i].CreatedAt.After(all[j].CreatedAt) }) off := parseMaintCursor(cursor) end := off + limit next := "" hasMore := false if end > len(all) { end = len(all) } else if end < len(all) { hasMore = true next = formatMaintCursor(end) } if off >= len(all) { return nil, "", false, nil } out := make([]*MaintenancePolicyConfigAudit, end-off) copy(out, all[off:end]) return out, next, hasMore, nil } func cloneMaintenancePolicy(p *MaintenancePolicy) *MaintenancePolicy { if p == nil { return nil } cp := *p if p.RetentionPeriodSec != nil { v := *p.RetentionPeriodSec cp.RetentionPeriodSec = &v } if p.MaxRows != nil { v := *p.MaxRows cp.MaxRows = &v } if p.LastRunAt != nil { t := *p.LastRunAt cp.LastRunAt = &t } return &cp } func parseMaintCursor(cursor string) int { if cursor == "" { return 0 } var off int for _, r := range cursor { if r < '0' || r > '9' { return 0 } off = off*10 + int(r-'0') } return off } func formatMaintCursor(off int) string { return strconv.Itoa(off) }