Типы maintenance_policy, методы store.Backend и реализации для PostgreSQL и in-memory. Co-authored-by: Cursor <[email protected]>
245 lines
5.8 KiB
Go
245 lines
5.8 KiB
Go
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)
|
|
}
|