diff --git a/core/dr-catalog/src/keywords.rs b/core/dr-catalog/src/keywords.rs index 84e1b8f..073851f 100644 --- a/core/dr-catalog/src/keywords.rs +++ b/core/dr-catalog/src/keywords.rs @@ -244,7 +244,8 @@ pub fn delete(conn: &Connection, id: KeywordId) -> Result { /// every assignment, and a query per keyword would be one statement per word /// in the library. pub fn list(conn: &Connection) -> Result, CatalogError> { - let mut stmt = conn.prepare( + ensure_term_index(conn); + let mut stmt = conn.prepare_cached( // DISTINCT image, not row: a word on two versions of one frame is one // photograph, and reporting two is the kind of small lie that makes a // user stop trusting the counts. @@ -269,6 +270,27 @@ pub fn list(conn: &Connection) -> Result, CatalogError> { Ok(rows) } +/// The index [`list`]'s per-word count is served from: `keywords_term` +/// with the version beside the word, so the count reads no `keywords` row. +/// +/// `keywords_term` alone gave the row id, and each of the 10,800 assignments +/// on the reference library cost a probe of the table for its version -- +/// 4 ms of the keyword panel's redraw, on every selection change. +/// +/// Created on first use rather than by a migration, for the reason +/// `duplicates::ensure_probe_table` gives: a new schema version makes every +/// older build refuse this catalog's snapshot at sync, and an older build +/// that meets an extra index ignores it. Once it exists, the statement is a +/// lookup in the schema (microseconds). A failure to create it is logged and +/// the list read without it: the index is a speed-up, never an answer. +fn ensure_term_index(conn: &Connection) { + if let Err(e) = conn.execute_batch( + "CREATE INDEX IF NOT EXISTS keywords_term_version ON keywords(keyword, version_id);", + ) { + log::warn!("keywords: could not create keywords_term_version: {e}"); + } +} + /// Assign a keyword to images, creating the keyword if it is new. /// /// The bulk form is the *only* form, because keywording a selection is the