feat: Add full-text search with FTS5

Implements FTS5-based full-text search for notes as specified in ADR-034.

Changes:
- Created migration 005_add_fts5_search.sql with FTS5 virtual table
- Created starpunk/search.py module with search functions
- Integrated FTS index updates into create_note() and update_note()
- DELETE trigger automatically removes notes from FTS index
- INSERT/UPDATE handled by application code (files not in DB)

Features:
- Porter stemming for better English search
- Unicode normalization for international characters
- Relevance ranking with snippets
- Graceful degradation if FTS5 unavailable
- Helper function to rebuild index if needed

Note: Initial FTS index population needs to be added to app startup.
Part of v1.1.0 (Phase 3).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-25 10:03:28 -07:00
parent 8352c3ab7c
commit b3c1b16617
3 changed files with 314 additions and 1 deletions

View File

@@ -286,6 +286,17 @@ def create_note(
# Create Note object
note = Note.from_row(row, data_dir)
# 9. UPDATE FTS INDEX (if available)
try:
from starpunk.search import update_fts_index, has_fts_table
db_path = Path(current_app.config["DATABASE_PATH"])
if has_fts_table(db_path):
update_fts_index(db, note_id, slug, content)
db.commit()
except Exception as e:
# FTS update failure should not prevent note creation
current_app.logger.warning(f"Failed to update FTS index for note {slug}: {e}")
return note
@@ -676,7 +687,19 @@ def update_note(
f"Failed to update note: {existing_note.slug}",
)
# 6. RETURN UPDATED NOTE
# 6. UPDATE FTS INDEX (if available and content changed)
if content is not None:
try:
from starpunk.search import update_fts_index, has_fts_table
db_path = Path(current_app.config["DATABASE_PATH"])
if has_fts_table(db_path):
update_fts_index(db, existing_note.id, existing_note.slug, content)
db.commit()
except Exception as e:
# FTS update failure should not prevent note update
current_app.logger.warning(f"Failed to update FTS index for note {existing_note.slug}: {e}")
# 7. RETURN UPDATED NOTE
updated_note = get_note(slug=existing_note.slug, load_content=True)
return updated_note