From f37069720a593daab186678751251d0819c4975a Mon Sep 17 00:00:00 2001 From: Pontoporeia Date: Fri, 27 Mar 2026 13:48:22 +0100 Subject: [PATCH] schema: add composite index (is_published, year DESC) + fix stale migration 005 - Add idx_theses_pub_year composite index on theses(is_published, year DESC) to schema.sql; replaces the need for the query planner to pick between the two separate idx_theses_published / idx_theses_year indexes and sort with a temp B-tree. Every public query filters on is_published=1 and orders/filters by year, so this covering index eliminates the sort pass for those queries. - Create storage/migrations/006_add_composite_index.sql and apply to both posterg.db and test.db. - Fix storage/migrations/005_add_banner.sql: the view recreation in that file still referenced the pre-migration-001 table/column names (thesis_keywords, keywords.keyword). Updated to use thesis_tags / tags tg to match the canonical schema.sql. The live DB was unaffected (migration 001 ran before 005), but the file was misleading and would fail if ever re-run from scratch. --- TODO.md | 4 ++-- storage/migrations/005_add_banner.sql | 6 +++--- .../migrations/006_add_composite_index.sql | 8 ++++++++ storage/posterg.db | Bin 204800 -> 208896 bytes storage/schema.sql | 1 + storage/test.db | Bin 237568 -> 237568 bytes 6 files changed, 14 insertions(+), 5 deletions(-) create mode 100644 storage/migrations/006_add_composite_index.sql diff --git a/TODO.md b/TODO.md index de69c4f..b9c9222 100644 --- a/TODO.md +++ b/TODO.md @@ -351,7 +351,7 @@ Goal: rename the tables and column to the canonical M2M pattern (`tags`, `thesis Eliminates full-database read-locks on every write; makes concurrent PHP-FPM workers safe. Also add `PRAGMA cache_size = -8000` (≈8 MB page cache) while there. -- [ ] **Composite index `(is_published, year DESC)`** on `theses` — every public query filters on both. +- [x] **Composite index `(is_published, year DESC)`** on `theses` — every public query filters on both. Currently `idx_theses_published` and `idx_theses_year` are separate; the query planner picks one and sorts the other with a temp B-tree. A single covering index eliminates the sort: `CREATE INDEX IF NOT EXISTS idx_theses_pub_year ON theses(is_published, year DESC);` @@ -370,7 +370,7 @@ Goal: rename the tables and column to the canonical M2M pattern (`tags`, `thesis `Database::getPublishedAuthors(): array` that queries `thesis_authors JOIN authors` directly, avoiding the view entirely. -- [ ] **`migration 005` view is stale in the file** — `005_add_banner.sql` recreates the view still +- [x] **`migration 005` view is stale in the file** — `005_add_banner.sql` recreates the view still referencing `thesis_keywords` / `keywords.keyword` (the old pre-migration-001 names). The file is already applied to the live DB (correctly, since 001 ran first), but the migration file itself is wrong and misleading. Fix: rewrite it to reference `thesis_tags` / `tags.name`, diff --git a/storage/migrations/005_add_banner.sql b/storage/migrations/005_add_banner.sql index 614b593..821d946 100644 --- a/storage/migrations/005_add_banner.sql +++ b/storage/migrations/005_add_banner.sql @@ -38,7 +38,7 @@ SELECT GROUP_CONCAT(DISTINCT CASE WHEN ts.role = 'lecteur' THEN s.name END) as jury_lecteurs, GROUP_CONCAT(DISTINCT l.name) as languages, GROUP_CONCAT(DISTINCT fmt.name) as formats, - GROUP_CONCAT(DISTINCT k.keyword) as keywords + GROUP_CONCAT(DISTINCT tg.name) as keywords FROM theses t LEFT JOIN orientations o ON t.orientation_id = o.id LEFT JOIN ap_programs ap ON t.ap_program_id = ap.id @@ -53,8 +53,8 @@ LEFT JOIN thesis_languages tl ON t.id = tl.thesis_id LEFT JOIN languages l ON tl.language_id = l.id LEFT JOIN thesis_formats tf ON t.id = tf.thesis_id LEFT JOIN format_types fmt ON tf.format_id = fmt.id -LEFT JOIN thesis_keywords tk ON t.id = tk.thesis_id -LEFT JOIN keywords k ON tk.keyword_id = k.id +LEFT JOIN thesis_tags tt ON t.id = tt.thesis_id +LEFT JOIN tags tg ON tt.tag_id = tg.id GROUP BY t.id; -- Recreate public view diff --git a/storage/migrations/006_add_composite_index.sql b/storage/migrations/006_add_composite_index.sql new file mode 100644 index 0000000..a630999 --- /dev/null +++ b/storage/migrations/006_add_composite_index.sql @@ -0,0 +1,8 @@ +-- Migration 006: Add composite covering index (is_published, year DESC) on theses +-- +-- Every public-facing query filters on is_published = 1 AND orders/filters by year. +-- The existing separate idx_theses_published and idx_theses_year force the query +-- planner to pick one index and sort the other via a temp B-tree. +-- This single covering index eliminates the extra sort pass. + +CREATE INDEX IF NOT EXISTS idx_theses_pub_year ON theses(is_published, year DESC); diff --git a/storage/posterg.db b/storage/posterg.db index 47303f8aca406fa06da901ddc2d4d9fea6586396..234cb1608f6248b5cd8fe9a1816b4e6fa3b6dedc 100644 GIT binary patch delta 175 zcmZoTz|-)6XM(h#4g&*&F%ZLm%0wMwMxDll)&$1Z1g5PC%uD$B+1R!*@Z0fa@NQtA z%dW__-7tZ16YInYA2l*f*u{+{85@N&^HNePGE*wzOEOZ6Q;XvZN|WL%Qxl6IT;u72 y#!RAmNa70qehLsdjZCoWoXp~k)D#^BkR}Be*I;MO?TX^e_c<83HZuzR=LZ0V>@=AG delta 69 zcmZp8z|(MmXM(h#HUk5L5fHAA% diff --git a/storage/schema.sql b/storage/schema.sql index f94b81a..e360068 100644 --- a/storage/schema.sql +++ b/storage/schema.sql @@ -306,6 +306,7 @@ INSERT OR IGNORE INTO pages (slug, title, content) VALUES CREATE INDEX IF NOT EXISTS idx_theses_year ON theses(year); CREATE INDEX IF NOT EXISTS idx_theses_published ON theses(is_published); +CREATE INDEX IF NOT EXISTS idx_theses_pub_year ON theses(is_published, year DESC); CREATE INDEX IF NOT EXISTS idx_theses_identifier ON theses(identifier); CREATE INDEX IF NOT EXISTS idx_theses_orientation ON theses(orientation_id); CREATE INDEX IF NOT EXISTS idx_theses_ap_program ON theses(ap_program_id); diff --git a/storage/test.db b/storage/test.db index cfe9a81a387417986230503f7bdc1cf15297fa4a..bf8505691c7e9590f3273d02eb25fad39164853f 100644 GIT binary patch delta 360 zcmZoTz}IkqZ-TVo9tH*mDRB7Y};<7UBzNd9_u7EUHc_UEinngv3?5P;JB zP?`@)Gec=6HlRLE_7@QDOI|3=1Esm4G#8ZSgwh;PnjK2BZBt-kf56DNm4o>YzYGu4 zV+MXLz9QBWYy~WttP@$3n71lc*k&xPrf*0z^(D6RbKXvp6F)MMnXoNx{W6*jaPC JpD^>L9stGVS~36t delta 96 zcmZoTz}IkqZ-TVoZUzPhD