- Add migration 021: recipe_browser_fts FTS5 table on category + keywords columns, eliminating LIKE '%keyword%' full sequential scans on 3.1M rows - _count_recipes_for_keywords now uses FTS5 MATCH (O(log N) vs O(N)) - browse_recipes reuses cached count, eliminating the second COUNT(*) scan per page request; ORDER BY r.id replaces the unindexed ORDER BY title sort - Module-level _COUNT_CACHE keyed by (db_path, keywords) means domain-switch category counts are computed once per process lifetime feat(find): dietary preset grid, Big 9 allergen pills, Hard Day Mode surface - Dietary constraints replaced with toggle-button preset grid (8 options) + free-text "Other" field; removes dense freeform text input - Allergies replaced with Big 9 pill picker (peanuts, tree nuts, shellfish, fish, milk, eggs, wheat, soy, sesame) + "Other" for custom entries - Hard Day Mode surfaced as a standalone aria-pressed button above the dietary collapsible; no longer buried inside a collapsed section - Active-state dot indicators on both collapsibles show filter engagement at a glance without expanding fix(a11y): aria-describedby wiring for wildcard checkbox and tag inputs (#40) - Persistent hint spans replace placeholder-only instructions for constraint and allergy fields (WCAG 3.3.2) fix(browse): auto-select highest-count category on domain switch (#41) - Eliminates the 3-decision cold start (domain → category → content) - Surprise Me button added for zero-decision random navigation
43 lines
1.7 KiB
SQL
43 lines
1.7 KiB
SQL
-- Migration 021: FTS5 inverted index for the recipe browser (category + keywords).
|
|
--
|
|
-- The browser domain queries were using LIKE '%keyword%' against category and
|
|
-- keywords columns — a leading wildcard prevents any B-tree index use, so every
|
|
-- query was a full sequential scan of 3.1M rows. This FTS5 index replaces those
|
|
-- scans with O(log N) token lookups.
|
|
--
|
|
-- Content-table backed: stores only the inverted index, no text duplication.
|
|
-- The keywords column is a JSON array; FTS5 tokenises it as plain text, stripping
|
|
-- the punctuation, which gives correct per-word matching.
|
|
--
|
|
-- One-time rebuild cost on 3.1M rows: ~20-40 seconds at first startup.
|
|
-- Subsequent startups skip this migration (IF NOT EXISTS guard).
|
|
|
|
CREATE VIRTUAL TABLE IF NOT EXISTS recipe_browser_fts USING fts5(
|
|
category,
|
|
keywords,
|
|
content=recipes,
|
|
content_rowid=id,
|
|
tokenize="unicode61"
|
|
);
|
|
|
|
INSERT INTO recipe_browser_fts(recipe_browser_fts) VALUES('rebuild');
|
|
|
|
CREATE TRIGGER IF NOT EXISTS recipe_browser_fts_ai
|
|
AFTER INSERT ON recipes BEGIN
|
|
INSERT INTO recipe_browser_fts(rowid, category, keywords)
|
|
VALUES (new.id, new.category, new.keywords);
|
|
END;
|
|
|
|
CREATE TRIGGER IF NOT EXISTS recipe_browser_fts_ad
|
|
AFTER DELETE ON recipes BEGIN
|
|
INSERT INTO recipe_browser_fts(recipe_browser_fts, rowid, category, keywords)
|
|
VALUES ('delete', old.id, old.category, old.keywords);
|
|
END;
|
|
|
|
CREATE TRIGGER IF NOT EXISTS recipe_browser_fts_au
|
|
AFTER UPDATE ON recipes BEGIN
|
|
INSERT INTO recipe_browser_fts(recipe_browser_fts, rowid, category, keywords)
|
|
VALUES ('delete', old.id, old.category, old.keywords);
|
|
INSERT INTO recipe_browser_fts(rowid, category, keywords)
|
|
VALUES (new.id, new.category, new.keywords);
|
|
END;
|