Skip to content

Commit 842bfce

Browse files
author
Daniele Briggi
committed
fix(search): add/remove chunks to fts table
1 parent 287c7f8 commit 842bfce

7 files changed

Lines changed: 55 additions & 34 deletions

File tree

src/sqlite_rag/cli.py

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def add(
3434
):
3535
"""Add a file path to the database"""
3636
rag = SQLiteRag()
37-
rag.add(path, recursively=recursive)
37+
rag.add(path, recursive=recursive)
3838

3939

4040
@app.command()
@@ -56,8 +56,6 @@ def list_documents():
5656

5757
# Print stats
5858
typer.echo(f"Total documents: {len(documents)}")
59-
typer.echo("Documents:")
60-
typer.echo("-" * 106)
6159

6260
typer.echo(f"{'ID':<36} {'URI/Content':<50} {'Created At':<20}")
6361
typer.echo("-" * 106)
@@ -76,32 +74,36 @@ def list_documents():
7674
@app.command()
7775
def remove(
7876
identifier: str,
79-
yes: bool = typer.Option(False, "-y", "--yes", help="Skip confirmation prompt")
77+
yes: bool = typer.Option(False, "-y", "--yes", help="Skip confirmation prompt"),
8078
):
8179
"""Remove document by path or UUID"""
8280
rag = SQLiteRag()
83-
81+
8482
# Find the document first
8583
document = rag.find_document(identifier)
8684
if not document:
8785
typer.echo(f"Document not found: {identifier}")
8886
raise typer.Exit(1)
89-
87+
9088
# Show document details
9189
typer.echo(f"Found document:")
9290
typer.echo(f"ID: {document.id}")
9391
typer.echo(f"URI: {document.uri or 'N/A'}")
94-
typer.echo(f"Created: {document.created_at.strftime('%Y-%m-%d %H:%M:%S') if document.created_at else 'N/A'}")
95-
typer.echo(f"Content preview: {document.content[:200]}{'...' if len(document.content) > 200 else ''}")
92+
typer.echo(
93+
f"Created: {document.created_at.strftime('%Y-%m-%d %H:%M:%S') if document.created_at else 'N/A'}"
94+
)
95+
typer.echo(
96+
f"Content preview: {document.content[:200]}{'...' if len(document.content) > 200 else ''}"
97+
)
9698
typer.echo()
97-
99+
98100
# Ask for confirmation unless -y flag is used
99101
if not yes:
100102
confirm = typer.confirm("Are you sure you want to delete this document?")
101103
if not confirm:
102104
typer.echo("Cancelled.")
103105
return
104-
106+
105107
# Remove the document
106108
success = rag.remove_document(identifier)
107109
if success:
@@ -128,7 +130,15 @@ def search(
128130
query: str, limit: int = typer.Option(10, help="Number of results to return")
129131
):
130132
"""Search for documents using hybrid vector + full-text search"""
131-
pass
133+
rag = SQLiteRag()
134+
results = rag.search(query, top_k=limit)
135+
136+
if not results:
137+
typer.echo("No documents found matching the query.")
138+
return
139+
140+
typer.echo(f"Found {len(results)} documents:")
141+
132142

133143

134144
def repl_mode():

src/sqlite_rag/database.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ def initialize(conn: sqlite3.Connection, settings: Settings) -> sqlite3.Connecti
1212
"""Initialize the database with extensions and schema"""
1313
conn.enable_load_extension(True)
1414
try:
15-
conn.load_extension(str(Path(__file__).parent.parent.parent / "extensions" / "ai"))
15+
conn.load_extension(
16+
str(Path(__file__).parent.parent.parent / "extensions" / "ai")
17+
)
1618
conn.load_extension(
1719
str(Path(__file__).parent.parent.parent / "extensions" / "vector")
1820
)
@@ -61,7 +63,6 @@ def _create_schema(conn: sqlite3.Connection, settings: Settings):
6163
cursor.execute(
6264
"""
6365
CREATE TABLE IF NOT EXISTS chunks (
64-
id INTEGER PRIMARY KEY AUTOINCREMENT,
6566
document_id TEXT,
6667
content TEXT,
6768
embedding BLOB,
@@ -72,7 +73,7 @@ def _create_schema(conn: sqlite3.Connection, settings: Settings):
7273

7374
cursor.execute(
7475
"""
75-
CREATE VIRTUAL TABLE IF NOT EXISTS chunks_fts USING fts5(content, content='chunks', content_rowid='id');
76+
CREATE VIRTUAL TABLE IF NOT EXISTS chunks_fts USING fts5(content, content='chunks', content_rowid='rowid');
7677
"""
7778
)
7879

src/sqlite_rag/engine.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ def search(self, query: str, limit: int = 10) -> list[Document]:
8181
v.distance
8282
FROM chunks AS c
8383
JOIN vector_quantize_scan('chunks', 'embedding', vector_convert_f32(:query_embedding), :k) AS v
84-
ON c.id = v.rowid
84+
ON c.rowid = v.rowid
8585
),
8686
-- Full-text search results
8787
fts_matches AS (
@@ -90,7 +90,7 @@ def search(self, query: str, limit: int = 10) -> list[Document]:
9090
c.document_id,
9191
rank AS score
9292
FROM chunks_fts AS c_fts
93-
JOIN chunks AS c ON c_fts.rowid = c.id
93+
JOIN chunks AS c ON c_fts.rowid = c.rowid
9494
WHERE c_fts.content MATCH :query
9595
LIMIT :k
9696
),
@@ -127,7 +127,7 @@ def search(self, query: str, limit: int = 10) -> list[Document]:
127127
FROM matches;
128128
""",
129129
{
130-
"query": f'"{query}"',
130+
"query": query + "*",
131131
"query_embedding": query_embedding,
132132
"k": limit,
133133
# TODO: move to settings or costants

src/sqlite_rag/repository.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ def add_document(self, document: Document) -> str:
3333
"INSERT INTO chunks (document_id, content, embedding) VALUES (?, ?, vector_convert_f32(?))",
3434
(document_id, chunk.content, chunk.embedding),
3535
)
36+
cursor.execute(
37+
"INSERT INTO chunks_fts (rowid, content) VALUES (last_insert_rowid(), ?)",
38+
(chunk.content,),
39+
)
3640

3741
self._conn.commit()
3842

@@ -62,11 +66,11 @@ def find_document_by_id_or_uri(self, identifier: str) -> Document | None:
6266
"""Find document by ID or URI"""
6367
cursor = self._conn.cursor()
6468
cursor.execute(
65-
"SELECT id, content, uri, metadata, created_at FROM documents WHERE id = ? OR uri = ?",
66-
(identifier, identifier)
69+
"SELECT id, content, uri, metadata, created_at FROM documents WHERE id = ? OR uri = ?",
70+
(identifier, identifier),
6771
)
6872
row = cursor.fetchone()
69-
73+
7074
if row:
7175
doc_id, content, uri, metadata, created_at = row
7276
return Document(
@@ -81,17 +85,21 @@ def find_document_by_id_or_uri(self, identifier: str) -> Document | None:
8185
def remove_document(self, document_id: str) -> bool:
8286
"""Remove document and its chunks by document ID"""
8387
cursor = self._conn.cursor()
84-
88+
8589
# Check if document exists
8690
cursor.execute("SELECT COUNT(*) FROM documents WHERE id = ?", (document_id,))
8791
if cursor.fetchone()[0] == 0:
8892
return False
89-
90-
# Remove chunks first (foreign key constraint)
93+
94+
# Remove chunks first
95+
cursor.execute(
96+
"DELETE FROM chunks_fts WHERE rowid IN (SELECT rowid FROM chunks WHERE document_id = ?)",
97+
(document_id,),
98+
)
9199
cursor.execute("DELETE FROM chunks WHERE document_id = ?", (document_id,))
92-
100+
93101
# Remove document
94102
cursor.execute("DELETE FROM documents WHERE id = ?", (document_id,))
95-
103+
96104
self._conn.commit()
97105
return True

src/sqlite_rag/sqliterag.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def __init__(self, settings: Optional[Settings] = None):
1919
if settings is None:
2020
# TODO: load defaults or from the database
2121
settings = Settings(
22-
model_path_or_name="./all-MiniLM-L6-v2.e4ce9877.q8_0.gguf",
22+
model_path_or_name="./Qwen3-Embedding-0.6B-Q8_0.gguf",
2323
db_path="sqliterag.db",
2424
)
2525

@@ -102,8 +102,10 @@ def remove_document(self, identifier: str) -> bool:
102102

103103
return self._repository.remove_document(document.id or "")
104104

105-
# def search(
106-
# self, query: str, top_k: int = 10
107-
# ) -> list[Document]:
108-
# """Search for documents matching the query"""
109-
# self._ensure_initialized()
105+
def search(
106+
self, query: str, top_k: int = 10
107+
) -> list[Document]:
108+
"""Search for documents matching the query"""
109+
self._ensure_initialized()
110+
111+
return self._engine.search(query, limit=top_k)

tests/conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
def db_conn():
1212
with tempfile.NamedTemporaryFile(suffix=".db") as tmp_db:
1313
settings = Settings(
14-
model_path_or_name="./all-MiniLM-L6-v2.e4ce9877.q8_0.gguf",
14+
model_path_or_name="./Qwen3-Embedding-0.6B-Q8_0.gguf",
1515
db_path=tmp_db.name,
1616
)
1717

@@ -27,7 +27,7 @@ def db_conn():
2727
def db_settings() -> Settings:
2828
with tempfile.NamedTemporaryFile(suffix=".db") as tmp_db:
2929
settings = Settings(
30-
model_path_or_name="./all-MiniLM-L6-v2.e4ce9877.q8_0.gguf",
30+
model_path_or_name="./Qwen3-Embedding-0.6B-Q8_0.gguf",
3131
db_path=tmp_db.name,
3232
)
3333
return settings

tests/test_engine.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ def test_search_fts_results(self, db_conn):
139139
engine.quantize()
140140

141141
# Act
142-
results = engine.search("brown fox animal", limit=5)
142+
results = engine.search("quick brown fox", limit=5)
143143

144144
assert len(results) > 0
145145
assert doc1_id == results[0].id

0 commit comments

Comments
 (0)