diff --git a/cmd/server/tests/podcast_test.go b/cmd/server/tests/podcast_test.go index 32001f00..2357f035 100644 --- a/cmd/server/tests/podcast_test.go +++ b/cmd/server/tests/podcast_test.go @@ -471,6 +471,34 @@ func TestDeleteMediaItemPodcastGuard(t *testing.T) { }) } +// ---- reader_settings key width (§10 R5, item 2.1): composite +// prefix: keys have headroom past the old 50-char ceiling ---- + +func TestReaderSettingsCompositeKeyHeadroom(t *testing.T) { + setup := setupTestServer(t) + userID := getRegularUserID(t, setup.DB) + + // 57 chars — the deferred podcast_custom_order: shape. Under + // the old VARCHAR(50) this upsert fails; the widening (item 2.1) + // makes it ordinary preference state. + key := "podcast_custom_order:01921a2b-3c4d-5e6f-7a8b-9c0d1e2f3a4b" + require.Len(t, key, 57, "sentinel: the pinned key must exceed the old VARCHAR(50)") + + err := setup.DB.UpsertUserSettingByKey(seedCtx(), database.UpsertUserSettingByKeyParams{ + UserID: pgtype.UUID{Bytes: userID, Valid: true}, + SettingKey: key, + SettingValue: []byte(`{"order":"custom"}`), + }) + require.NoError(t, err, "composite keys longer than 50 chars must store (VARCHAR(120) headroom)") + + val, err := setup.DB.GetUserSettingByKey(seedCtx(), database.GetUserSettingByKeyParams{ + UserID: pgtype.UUID{Bytes: userID, Valid: true}, + SettingKey: key, + }) + require.NoError(t, err) + require.JSONEq(t, `{"order":"custom"}`, string(val)) +} + // ---- download worker: state machine, idempotency, file layout ---- func TestPodcastDownloadWorker(t *testing.T) { diff --git a/database/schema/schema.sql b/database/schema/schema.sql index b22f7a40..ac99748e 100644 --- a/database/schema/schema.sql +++ b/database/schema/schema.sql @@ -1628,12 +1628,19 @@ CREATE INDEX IF NOT EXISTS idx_dictionary_word ON dictionary_cache(word); CREATE TABLE IF NOT EXISTS reader_settings ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, - setting_key VARCHAR(50) NOT NULL, + setting_key VARCHAR(120) NOT NULL, setting_value JSONB NOT NULL, updated_at TIMESTAMPTZ DEFAULT NOW(), UNIQUE(user_id, setting_key) ); +-- Key-width headroom (podcast plan §10 R5, item 2.1): the composite +-- `podcast_order:` key sits at exactly 50 chars, and future +-- per-feed/per-collection preference keys must not be forced to +-- payload-stuff by a column width. Widening a varchar is metadata-only +-- (no table rewrite); idempotent on every boot. +ALTER TABLE reader_settings ALTER COLUMN setting_key TYPE VARCHAR(120); + CREATE INDEX IF NOT EXISTS idx_reader_settings_user ON reader_settings(user_id); -- PDF bookmarks (custom user bookmarks)