89 lines
2.5 KiB
Go
89 lines
2.5 KiB
Go
package postgres
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sort"
|
|
)
|
|
|
|
const LoadRuntimeSettingsSQL = `
|
|
SELECT setting_key, setting_value
|
|
FROM platform_runtime_settings
|
|
WHERE setting_key = ANY($1::text[])
|
|
ORDER BY setting_key
|
|
`
|
|
|
|
const UpsertRuntimeSettingSQL = `
|
|
INSERT INTO platform_runtime_settings (setting_key, setting_value)
|
|
VALUES ($1, $2)
|
|
ON CONFLICT (setting_key) DO UPDATE SET
|
|
setting_value = EXCLUDED.setting_value,
|
|
revision = platform_runtime_settings.revision + 1,
|
|
updated_at = now()
|
|
`
|
|
|
|
// LoadRuntimeSettings returns only the requested runtime setting keys. The
|
|
// caller owns the editable-key whitelist; the database adapter owns storage.
|
|
func (db *Database) LoadRuntimeSettings(ctx context.Context, keys []string) (map[string]string, error) {
|
|
if db.config.Backend != BackendPostgres || db.querier == nil {
|
|
return nil, fmt.Errorf("PostgreSQL is unavailable when ZHINIAN_DATA_BACKEND=%s", db.config.Backend)
|
|
}
|
|
if len(keys) == 0 {
|
|
return map[string]string{}, nil
|
|
}
|
|
rows, err := db.querier.Query(ctx, LoadRuntimeSettingsSQL, keys)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("load runtime settings: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
values := make(map[string]string, len(keys))
|
|
for rows.Next() {
|
|
var key, value string
|
|
if err := rows.Scan(&key, &value); err != nil {
|
|
return nil, fmt.Errorf("scan runtime setting: %w", err)
|
|
}
|
|
values[key] = value
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, fmt.Errorf("read runtime settings: %w", err)
|
|
}
|
|
return values, nil
|
|
}
|
|
|
|
// SaveRuntimeSettings atomically upserts a partial set of settings. Sorting
|
|
// makes lock acquisition deterministic when concurrent requests touch several
|
|
// keys at once.
|
|
func (db *Database) SaveRuntimeSettings(ctx context.Context, values map[string]string) error {
|
|
if db.config.Backend != BackendPostgres || db.transactions == nil {
|
|
return fmt.Errorf("PostgreSQL is unavailable when ZHINIAN_DATA_BACKEND=%s", db.config.Backend)
|
|
}
|
|
if len(values) == 0 {
|
|
return nil
|
|
}
|
|
keys := make([]string, 0, len(values))
|
|
for key := range values {
|
|
keys = append(keys, key)
|
|
}
|
|
sort.Strings(keys)
|
|
tx, err := db.transactions.Begin(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("begin runtime settings update: %w", err)
|
|
}
|
|
finished := false
|
|
defer func() {
|
|
if !finished {
|
|
_ = tx.Rollback(ctx)
|
|
}
|
|
}()
|
|
for _, key := range keys {
|
|
if err := tx.Exec(ctx, UpsertRuntimeSettingSQL, key, values[key]); err != nil {
|
|
return fmt.Errorf("save runtime setting %s: %w", key, err)
|
|
}
|
|
}
|
|
if err := tx.Commit(ctx); err != nil {
|
|
return fmt.Errorf("commit runtime settings update: %w", err)
|
|
}
|
|
finished = true
|
|
return nil
|
|
}
|