add pattern match to search index, index permalink

This commit is contained in:
phernandez
2025-01-15 16:02:53 -06:00
parent 6ffbf6595a
commit a509ef1e2a
3 changed files with 87 additions and 18 deletions
+9 -9
View File
@@ -9,18 +9,18 @@ CREATE VIRTUAL TABLE IF NOT EXISTS search_index USING fts5(
id UNINDEXED, -- Row ID
title, -- Title for searching
content, -- Main searchable content
permalink UNINDEXED, -- Stable identifier
file_path UNINDEXED, -- Physical location
type UNINDEXED, -- entity/relation/observation
permalink, -- Stable identifier (now indexed for path search)
file_path UNINDEXED, -- Physical location
type UNINDEXED, -- entity/relation/observation
-- Relation fields
from_id UNINDEXED, -- Source entity
to_id UNINDEXED, -- Target entity
from_id UNINDEXED, -- Source entity
to_id UNINDEXED, -- Target entity
relation_type UNINDEXED, -- Type of relation
-- Observation fields
entity_id UNINDEXED, -- Parent entity
category UNINDEXED, -- Observation category
entity_id UNINDEXED, -- Parent entity
category UNINDEXED, -- Observation category
-- Common fields
metadata UNINDEXED, -- JSON metadata
@@ -28,7 +28,7 @@ CREATE VIRTUAL TABLE IF NOT EXISTS search_index USING fts5(
updated_at UNINDEXED, -- Last update
-- Configuration
tokenize='porter unicode61',
prefix='2,3'
tokenize=\"unicode61 separators '/'\", -- Treat / as part of tokens
prefix='1,2,3,4' -- Support longer prefixes for paths
);
""")
@@ -31,6 +31,34 @@ class SearchRepository:
return f'"{term}"'
return term
def _convert_pattern_to_fts(self, pattern: str) -> str:
"""Convert glob pattern to FTS5 query.
Examples:
specs/search -> "specs/search"
specs/* -> "specs/"*
specs/*/doc -> "specs/" AND "service"
*/doc -> "service"
"""
if not pattern:
return ""
# Split on * to identify wildcard positions
parts = pattern.split('*')
# No wildcards - do exact phrase match
if len(parts) == 1:
return f'"{pattern}"'
# Strip empty parts and clean up
parts = [p.strip('/') for p in parts if p.strip('/')]
# For patterns with wildcards, quote each part
quoted = [f'"{p}"' for p in parts]
# Join with AND to ensure all parts match
return ' AND '.join(quoted)
async def search(self, query: SearchQuery) -> List[SearchResult]:
"""Search across all indexed content with fuzzy matching."""
conditions = []
@@ -42,10 +70,12 @@ class SearchRepository:
params["text"] = f"{search_text}*"
conditions.append("(title MATCH :text OR content MATCH :text)")
# Handle pattern search on permalink if specified
# Handle pattern search on permalink using FTS
if query.permalink_pattern:
params["permalink_pattern"] = query.permalink_pattern
conditions.append("permalink LIKE :permalink_pattern")
fts_pattern = self._convert_pattern_to_fts(query.permalink_pattern)
if fts_pattern:
params["permalink_pattern"] = fts_pattern
conditions.append("permalink MATCH :permalink_pattern")
# Handle type filter
if query.types: