From 5eceb888a01e5002696784c8bbb9ddcce916e3f0 Mon Sep 17 00:00:00 2001 From: Arpit Gupta Date: Sat, 30 May 2026 02:51:05 +0530 Subject: [PATCH 1/3] feat: add artist_id to sessions Mirrors rooms.artist_id (UUID, FK accounts(id), ON DELETE CASCADE) so the chat listing endpoint can filter by artist via the parent session. Adds two indexes: idx_sessions_artist_id for direct lookups and idx_sessions_account_artist_updated_at backing the sidebar's (account, artist, recency) ordering. Co-Authored-By: Claude Opus 4.7 (1M context) --- ...260530025046_add_artist_id_to_sessions.sql | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 supabase/migrations/20260530025046_add_artist_id_to_sessions.sql diff --git a/supabase/migrations/20260530025046_add_artist_id_to_sessions.sql b/supabase/migrations/20260530025046_add_artist_id_to_sessions.sql new file mode 100644 index 0000000..c066def --- /dev/null +++ b/supabase/migrations/20260530025046_add_artist_id_to_sessions.sql @@ -0,0 +1,25 @@ +-- Add artist_id to sessions, mirroring rooms.artist_id +-- (see 20250310153603_add_artist_id_to_rooms.sql). +-- +-- Each chat is provisioned inside one session; the artist the chat +-- belongs to lives on the session row so the chat listing endpoint +-- can filter via `sessions.artist_id`. Backfill from rooms happens +-- in a later data-migration step (rooms+memories → sessions+chats+ +-- chat_messages); this migration only adds the column + index. + +ALTER TABLE "public"."sessions" + ADD COLUMN "artist_id" UUID DEFAULT NULL; + +ALTER TABLE "public"."sessions" + ADD CONSTRAINT "sessions_artist_id_fkey" + FOREIGN KEY ("artist_id") REFERENCES "public"."accounts"("id") ON DELETE CASCADE NOT VALID; + +ALTER TABLE "public"."sessions" VALIDATE CONSTRAINT "sessions_artist_id_fkey"; + +-- Direct lookup of "all sessions for an artist". +CREATE INDEX IF NOT EXISTS "idx_sessions_artist_id" + ON "public"."sessions" ("artist_id"); + +-- Backs the chat sidebar's `(account, artist, recency)` filter ordering. +CREATE INDEX IF NOT EXISTS "idx_sessions_account_artist_updated_at" + ON "public"."sessions" ("account_id", "artist_id", "updated_at" DESC); From b04fe714bad4fffcb507d64a7d4c48adfd155786 Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Fri, 29 May 2026 20:26:09 -0500 Subject: [PATCH 2/3] feat: backfill artist_id inline; rename indexes to sessions convention MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Backfill from `rooms.artist_id` inline (matches the pattern of the cited legacy migration `20250310153603_add_artist_id_to_rooms.sql`) via the uuidv5 derivation `api/scripts/backfill/migrateRoom.ts` uses. Touches 17,985 of 23,252 migrated sessions (verified pre-merge). - Rename `idx_sessions_artist_id` -> `sessions_artist_id_idx` and `idx_sessions_account_artist_updated_at` -> `sessions_account_artist_idx` to match the suffix-style convention of the table's existing indexes (`sessions_account_id_idx`, `chats_session_id_idx`, etc.). - Drop `updated_at DESC` from the composite — the sidebar query orders by `chats.updated_at` (satisfied by `chats_session_id_idx`), not `sessions.updated_at`, so the trailing sort key was paying storage + write cost for a sort that wouldn't be used. Co-Authored-By: Claude Opus 4.7 (1M context) --- ...260530025046_add_artist_id_to_sessions.sql | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/supabase/migrations/20260530025046_add_artist_id_to_sessions.sql b/supabase/migrations/20260530025046_add_artist_id_to_sessions.sql index c066def..454705d 100644 --- a/supabase/migrations/20260530025046_add_artist_id_to_sessions.sql +++ b/supabase/migrations/20260530025046_add_artist_id_to_sessions.sql @@ -3,9 +3,9 @@ -- -- Each chat is provisioned inside one session; the artist the chat -- belongs to lives on the session row so the chat listing endpoint --- can filter via `sessions.artist_id`. Backfill from rooms happens --- in a later data-migration step (rooms+memories → sessions+chats+ --- chat_messages); this migration only adds the column + index. +-- can filter via `sessions.artist_id`. Backfill from rooms is done +-- inline below — mirrors the legacy migration's pattern of shipping +-- the schema change and the data migration atomically. ALTER TABLE "public"."sessions" ADD COLUMN "artist_id" UUID DEFAULT NULL; @@ -16,10 +16,28 @@ ALTER TABLE "public"."sessions" ALTER TABLE "public"."sessions" VALIDATE CONSTRAINT "sessions_artist_id_fkey"; +-- Backfill from legacy rooms via the uuidv5 derivation used by the +-- Phase 2 backfill script (api/scripts/backfill/migrateRoom.ts). The +-- fixed namespace is the same one the script hardcodes, so this +-- SQL reproduces the exact session id each migrated room was given. +-- Verified pre-merge: 17,985 of 23,252 migrated sessions have a +-- matching legacy room with a non-null artist_id. +UPDATE "public"."sessions" s +SET "artist_id" = r."artist_id" +FROM "public"."rooms" r +WHERE s."id" = uuid_generate_v5( + 'f47ac10b-58cc-4372-a567-0e02b2c3d479'::uuid, + r."id"::text + )::text + AND r."artist_id" IS NOT NULL; + -- Direct lookup of "all sessions for an artist". -CREATE INDEX IF NOT EXISTS "idx_sessions_artist_id" +CREATE INDEX IF NOT EXISTS "sessions_artist_id_idx" ON "public"."sessions" ("artist_id"); --- Backs the chat sidebar's `(account, artist, recency)` filter ordering. -CREATE INDEX IF NOT EXISTS "idx_sessions_account_artist_updated_at" - ON "public"."sessions" ("account_id", "artist_id", "updated_at" DESC); +-- Composite scoping index for the sidebar's `(account, artist)` filter. +-- Chat-side recency ordering is satisfied by the existing +-- `chats_session_id_idx` + the sort on `chats.updated_at`, so this +-- index does not include `sessions.updated_at`. +CREATE INDEX IF NOT EXISTS "sessions_account_artist_idx" + ON "public"."sessions" ("account_id", "artist_id"); From 5349bb5e075f22cd8aad36191566fbea6885f280 Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Fri, 29 May 2026 20:36:58 -0500 Subject: [PATCH 3/3] feat: drop the composite (account_id, artist_id) index Pre-merge EXPLAIN against a transactional dry-run on prod (BEGIN + full migration + EXPLAIN ANALYZE + ROLLBACK) showed the planner prefers `sessions_account_id_idx` (bitmap) + in-memory artist filter over the composite for the sidebar query shape (`WHERE account_id = X AND artist_id = Y`). The composite was paying storage + write cost for a plan that wouldn't be picked. The single-column `sessions_artist_id_idx` still serves "all sessions for an artist" lookups directly. The existing `sessions_account_id_idx` handles the account-only case unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../20260530025046_add_artist_id_to_sessions.sql | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/supabase/migrations/20260530025046_add_artist_id_to_sessions.sql b/supabase/migrations/20260530025046_add_artist_id_to_sessions.sql index 454705d..f5beca5 100644 --- a/supabase/migrations/20260530025046_add_artist_id_to_sessions.sql +++ b/supabase/migrations/20260530025046_add_artist_id_to_sessions.sql @@ -32,12 +32,11 @@ WHERE s."id" = uuid_generate_v5( AND r."artist_id" IS NOT NULL; -- Direct lookup of "all sessions for an artist". +-- A composite `(account_id, artist_id)` index was considered for the +-- sidebar's account+artist filter, but a pre-merge EXPLAIN against a +-- dry-run on prod showed PG already prefers the existing +-- `sessions_account_id_idx` (bitmap) + in-memory artist filter for this +-- query shape — adding the composite paid storage + write cost for a +-- plan the planner won't pick. Revisit if future query patterns change. CREATE INDEX IF NOT EXISTS "sessions_artist_id_idx" ON "public"."sessions" ("artist_id"); - --- Composite scoping index for the sidebar's `(account, artist)` filter. --- Chat-side recency ordering is satisfied by the existing --- `chats_session_id_idx` + the sort on `chats.updated_at`, so this --- index does not include `sessions.updated_at`. -CREATE INDEX IF NOT EXISTS "sessions_account_artist_idx" - ON "public"."sessions" ("account_id", "artist_id");