Files
kandev/apps/backend/internal/runtimeflags/sqlite.go
T
2026-09-07 19:28:31 +01:00

112 lines
3.3 KiB
Go

package runtimeflags
import (
"context"
"fmt"
"time"
"github.com/jmoiron/sqlx"
"github.com/kandev/kandev/internal/db/dialect"
)
type SQLiteStore struct {
writer *sqlx.DB
reader *sqlx.DB
}
// Override is one persisted runtime flag override, including its database
// timestamps. It is useful to callers that need to distinguish an upserted
// row from a value reconstructed from the effective flag registry.
type Override struct {
Key string
Value bool
CreatedAt time.Time
UpdatedAt time.Time
}
func NewSQLiteStore(writer, reader *sqlx.DB) (*SQLiteStore, error) {
store := &SQLiteStore{writer: writer, reader: reader}
if err := store.initSchema(); err != nil {
return nil, err
}
return store, nil
}
func (s *SQLiteStore) initSchema() error {
timestampType := "DATETIME"
if dialect.IsPostgres(s.writer.DriverName()) {
timestampType = "TIMESTAMPTZ"
}
_, err := s.writer.Exec(fmt.Sprintf(`CREATE TABLE IF NOT EXISTS runtime_flag_overrides (
key TEXT PRIMARY KEY,
value INTEGER NOT NULL CHECK (value IN (0, 1)),
created_at %s NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at %s NOT NULL DEFAULT CURRENT_TIMESTAMP
)`, timestampType, timestampType))
if err != nil {
return fmt.Errorf("create runtime_flag_overrides: %w", err)
}
return nil
}
func (s *SQLiteStore) ListOverrides(ctx context.Context) (map[string]bool, error) {
rows, err := s.reader.QueryxContext(ctx, `SELECT key, value FROM runtime_flag_overrides`)
if err != nil {
return nil, fmt.Errorf("list runtime flag overrides: %w", err)
}
defer func() { _ = rows.Close() }()
out := map[string]bool{}
for rows.Next() {
var key string
var value int
if err := rows.Scan(&key, &value); err != nil {
return nil, fmt.Errorf("scan runtime flag override: %w", err)
}
out[key] = value != 0
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("iterate runtime flag overrides: %w", err)
}
return out, nil
}
// GetOverride reads one override and its persisted timestamps.
func (s *SQLiteStore) GetOverride(ctx context.Context, key string) (Override, error) {
var row struct {
Key string `db:"key"`
Value int `db:"value"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
}
if err := s.reader.GetContext(ctx, &row, s.reader.Rebind(`
SELECT key, value, created_at, updated_at FROM runtime_flag_overrides WHERE key = ?
`), key); err != nil {
return Override{}, err
}
return Override{Key: row.Key, Value: row.Value != 0, CreatedAt: row.CreatedAt, UpdatedAt: row.UpdatedAt}, nil
}
func (s *SQLiteStore) SetOverride(ctx context.Context, key string, value bool) error {
now := time.Now().UTC()
intValue := 0
if value {
intValue = 1
}
_, err := s.writer.ExecContext(ctx, s.writer.Rebind(`INSERT INTO runtime_flag_overrides (key, value, created_at, updated_at)
VALUES (?, ?, ?, ?)
ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`),
key, intValue, now, now)
if err != nil {
return fmt.Errorf("set runtime flag override %q: %w", key, err)
}
return nil
}
func (s *SQLiteStore) DeleteOverride(ctx context.Context, key string) error {
if _, err := s.writer.ExecContext(ctx, s.writer.Rebind(`DELETE FROM runtime_flag_overrides WHERE key = ?`), key); err != nil {
return fmt.Errorf("delete runtime flag override %q: %w", key, err)
}
return nil
}