From 29fe8b905c70f1c6875ade1fa4b57cc93dd97246 Mon Sep 17 00:00:00 2001 From: 4gray Date: Sat, 2 May 2026 10:27:38 +0200 Subject: [PATCH] perf(database): partial covering index for visible categories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three queries filter on categories.hidden = false: - getGlobalRecentlyAdded — every dashboard load (now sorts by added DESC on the indexed content side after the prior CAST removal) - searchContent — Xtream search with excludeHidden - globalSearch — global Xtream search with excludeHidden All three start from content and join into categories on the category_id PK. The hidden filter is then applied per-row, requiring a row lookup for every result candidate — even before LIMIT. Add a partial covering index on categories: idx_categories_visible(id, playlist_id, type) WHERE hidden = 0. Two wins: - Covering: SQLite serves the join (id) plus filters (playlist_id, type) and the hidden = 0 predicate directly from the index without ever touching the categories row. - Partial: hidden categories are absent from the index, so they're pruned before any work happens — no row lookup, no comparison. Schema mirrored in Drizzle so the ORM stays the single source of truth. CREATE INDEX IF NOT EXISTS so existing DBs pick it up on next startup with no migration needed. Co-Authored-By: Claude Opus 4.7 (1M context) Entire-Checkpoint: f957cd9849e0 --- libs/shared/database/src/lib/connection.ts | 7 +++++++ libs/shared/database/src/lib/schema.ts | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/libs/shared/database/src/lib/connection.ts b/libs/shared/database/src/lib/connection.ts index 451781cc4..213571181 100644 --- a/libs/shared/database/src/lib/connection.ts +++ b/libs/shared/database/src/lib/connection.ts @@ -147,6 +147,13 @@ const CREATE_TABLE_STATEMENTS = [ `CREATE INDEX IF NOT EXISTS idx_content_xtream ON content(xtream_id)`, `CREATE INDEX IF NOT EXISTS idx_content_type_added ON content(type, added)`, `CREATE INDEX IF NOT EXISTS idx_categories_type ON categories(type)`, + // Partial covering index for visible categories — supports the dashboard's + // getGlobalRecentlyAdded plus searchContent/globalSearch when excludeHidden + // is set. SQLite can satisfy the join (category_id PK lookup) plus the + // hidden = 0 filter directly from this index without touching the + // categories row, and hidden categories are absent so they're skipped + // before any row lookup. + `CREATE INDEX IF NOT EXISTS idx_categories_visible ON categories(id, playlist_id, type) WHERE hidden = 0`, `CREATE UNIQUE INDEX IF NOT EXISTS favorites_content_playlist_unique ON favorites(content_id, playlist_id)`, `CREATE INDEX IF NOT EXISTS favorites_playlist_idx ON favorites(playlist_id)`, `CREATE INDEX IF NOT EXISTS favorites_content_idx ON favorites(content_id)`, diff --git a/libs/shared/database/src/lib/schema.ts b/libs/shared/database/src/lib/schema.ts index aa9b2a3e6..c6c3c2b7e 100644 --- a/libs/shared/database/src/lib/schema.ts +++ b/libs/shared/database/src/lib/schema.ts @@ -72,6 +72,12 @@ export const categories = sqliteTable( playlistTypeXtreamUnique: uniqueIndex( 'categories_playlist_type_xtream_unique' ).on(table.playlistId, table.type, table.xtreamId), + // Partial covering index for visible categories — see connection.ts + // for the full rationale. SQLite can satisfy joins + hidden filter + // directly from this index for the dashboard / search queries. + visibleIdx: index('idx_categories_visible') + .on(table.id, table.playlistId, table.type) + .where(sql`hidden = 0`), }) );