feat(settings): add canonical typed storage for the settings contract
The cross-platform settings contract needs one typed store behind it before a resolver, routes or a migration can exist. This adds that storage to both user-store backends and holds them to identical behavior. PostgreSQL gets user_setting_values with the scope CHECK constraints, the five partial unique indexes that enforce one explicit value per identity, and the covering indexes the one-query read path needs, plus user_setting_mutations for mutation_id idempotency and the inert user_setting_migration_rejects audit table. The per-user SQLite store gets the same shape minus user_id, since that database is already user-scoped. The UserStore interface grows the typed operations: read one explicit value at one scope, collect every candidate row for a resolution request in a single query, upsert with a revision increment, unset, and the idempotency receipt operations. The resolution read deliberately returns unranked candidates so the resolver can rank in Go — one query per request, never one per scope, which the pgx query-count test pins. Delete behavior is application-enforced. Neither backend can inherit it from constraints: the SQLite store declares no foreign keys, and library, series and device columns are not FK targets in Postgres either. Profile deletion cascades to profile-anchored values while account scope survives, forgetting a device clears its profile_device values alongside the legacy overrides, and the library/series purges remove only what is scoped to that entity. The shared conformance suite covers all of it, including the set-versus-unset distinction for false, 0, "" and null, so a divergence between the two backends fails a test rather than reaching a client. Part of #376 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ package userdb
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
@@ -420,3 +421,49 @@ func (s *SQLiteUserStore) UpsertLibraryPlaybackPreference(_ context.Context, pre
|
||||
func (s *SQLiteUserStore) DeleteLibraryPlaybackPreference(_ context.Context, profileID string, libraryID int) error {
|
||||
return DeleteLibraryPlaybackPreference(s.db, profileID, libraryID)
|
||||
}
|
||||
|
||||
// --- Canonical typed setting values ---
|
||||
|
||||
func (s *SQLiteUserStore) GetSettingValue(_ context.Context, id userstore.SettingIdentity) (*userstore.SettingValue, error) {
|
||||
return GetSettingValue(s.db, id)
|
||||
}
|
||||
|
||||
func (s *SQLiteUserStore) ListSettingValuesForResolution(_ context.Context, query userstore.SettingResolutionQuery) ([]userstore.SettingValue, error) {
|
||||
return ListSettingValuesForResolution(s.db, query)
|
||||
}
|
||||
|
||||
func (s *SQLiteUserStore) UpsertSettingValue(_ context.Context, id userstore.SettingIdentity, value json.RawMessage) (*userstore.SettingValue, error) {
|
||||
return UpsertSettingValue(s.db, id, value)
|
||||
}
|
||||
|
||||
func (s *SQLiteUserStore) DeleteSettingValue(_ context.Context, id userstore.SettingIdentity) (bool, error) {
|
||||
return DeleteSettingValue(s.db, id)
|
||||
}
|
||||
|
||||
func (s *SQLiteUserStore) DeleteSettingValuesForProfile(_ context.Context, profileID string) (int64, error) {
|
||||
return DeleteSettingValuesForProfile(s.db, profileID)
|
||||
}
|
||||
|
||||
func (s *SQLiteUserStore) DeleteSettingValuesForDevice(_ context.Context, profileID, deviceID string) (int64, error) {
|
||||
return DeleteSettingValuesForDevice(s.db, profileID, deviceID)
|
||||
}
|
||||
|
||||
func (s *SQLiteUserStore) DeleteSettingValuesForLibrary(_ context.Context, libraryID int) (int64, error) {
|
||||
return DeleteSettingValuesForLibrary(s.db, libraryID)
|
||||
}
|
||||
|
||||
func (s *SQLiteUserStore) DeleteSettingValuesForSeries(_ context.Context, seriesID string) (int64, error) {
|
||||
return DeleteSettingValuesForSeries(s.db, seriesID)
|
||||
}
|
||||
|
||||
func (s *SQLiteUserStore) GetSettingMutation(_ context.Context, mutationID string) (*userstore.SettingMutationRecord, error) {
|
||||
return GetSettingMutation(s.db, mutationID)
|
||||
}
|
||||
|
||||
func (s *SQLiteUserStore) PutSettingMutation(_ context.Context, record userstore.SettingMutationRecord) (userstore.SettingMutationRecord, bool, error) {
|
||||
return PutSettingMutation(s.db, record)
|
||||
}
|
||||
|
||||
func (s *SQLiteUserStore) DeleteExpiredSettingMutations(_ context.Context, before time.Time) (int64, error) {
|
||||
return DeleteExpiredSettingMutations(s.db, before)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user