-- Migration 008: FTS5 Full-Text Search Index -- Schema version: 8 -- Adds full-text search on documents table with sync triggers -- Full-text search with porter stemmer and prefix indexes for type-ahead CREATE VIRTUAL TABLE documents_fts USING fts5( title, content_text, content='documents', content_rowid='id', tokenize='porter unicode61', prefix='2 3 4' ); -- Keep FTS in sync via triggers. -- IMPORTANT: COALESCE(title, '') ensures FTS5 external-content table never -- receives NULL values, which can cause inconsistencies with delete operations. -- FTS5 delete requires exact match of original values; NULL != NULL in SQL, -- so a NULL title on insert would make the delete trigger fail silently. CREATE TRIGGER documents_ai AFTER INSERT ON documents BEGIN INSERT INTO documents_fts(rowid, title, content_text) VALUES (new.id, COALESCE(new.title, ''), new.content_text); END; CREATE TRIGGER documents_ad AFTER DELETE ON documents BEGIN INSERT INTO documents_fts(documents_fts, rowid, title, content_text) VALUES('delete', old.id, COALESCE(old.title, ''), old.content_text); END; -- Only rebuild FTS when searchable text actually changes (not metadata-only updates) CREATE TRIGGER documents_au AFTER UPDATE ON documents WHEN old.title IS NOT new.title OR old.content_text != new.content_text BEGIN INSERT INTO documents_fts(documents_fts, rowid, title, content_text) VALUES('delete', old.id, COALESCE(old.title, ''), old.content_text); INSERT INTO documents_fts(rowid, title, content_text) VALUES (new.id, COALESCE(new.title, ''), new.content_text); END; -- Update schema version INSERT INTO schema_version (version, applied_at, description) VALUES (8, strftime('%s', 'now') * 1000, 'FTS5 full-text search index with sync triggers');