Skip to content

Commit 3ce34c6

Browse files
author
Daniele Briggi
committed
feat: implement remove document
1 parent c62d6d0 commit 3ce34c6

6 files changed

Lines changed: 293 additions & 6 deletions

File tree

src/sqlite_rag/cli.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,35 @@ def list_documents():
7777
@app.command()
7878
def remove(identifier: str):
7979
"""Remove document by path or UUID"""
80-
pass
80+
rag = SQLiteRag()
81+
82+
# Find the document first
83+
document = rag.find_document(identifier)
84+
if not document:
85+
typer.echo(f"Document not found: {identifier}")
86+
raise typer.Exit(1)
87+
88+
# Show document details
89+
typer.echo(f"Found document:")
90+
typer.echo(f"ID: {document.id}")
91+
typer.echo(f"URI: {document.uri or 'N/A'}")
92+
typer.echo(f"Created: {document.created_at.strftime('%Y-%m-%d %H:%M:%S') if document.created_at else 'N/A'}")
93+
typer.echo(f"Content preview: {document.content[:200]}{'...' if len(document.content) > 200 else ''}")
94+
typer.echo()
95+
96+
# Ask for confirmation
97+
confirm = typer.confirm("Are you sure you want to delete this document?")
98+
if not confirm:
99+
typer.echo("Cancelled.")
100+
return
101+
102+
# Remove the document
103+
success = rag.remove_document(identifier)
104+
if success:
105+
typer.echo("Document removed successfully.")
106+
else:
107+
typer.echo("Failed to remove document.")
108+
raise typer.Exit(1)
81109

82110

83111
@app.command()

src/sqlite_rag/repository.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,41 @@ def list_documents(self) -> list[Document]:
5757
)
5858

5959
return documents
60+
61+
def find_document_by_id_or_uri(self, identifier: str) -> Document | None:
62+
"""Find document by ID or URI"""
63+
cursor = self._conn.cursor()
64+
cursor.execute(
65+
"SELECT id, content, uri, metadata, created_at FROM documents WHERE id = ? OR uri = ?",
66+
(identifier, identifier)
67+
)
68+
row = cursor.fetchone()
69+
70+
if row:
71+
doc_id, content, uri, metadata, created_at = row
72+
return Document(
73+
id=doc_id,
74+
content=content,
75+
uri=uri,
76+
metadata=json.loads(metadata),
77+
created_at=created_at,
78+
)
79+
return None
80+
81+
def remove_document(self, document_id: str) -> bool:
82+
"""Remove document and its chunks by document ID"""
83+
cursor = self._conn.cursor()
84+
85+
# Check if document exists
86+
cursor.execute("SELECT COUNT(*) FROM documents WHERE id = ?", (document_id,))
87+
if cursor.fetchone()[0] == 0:
88+
return False
89+
90+
# Remove chunks first (foreign key constraint)
91+
cursor.execute("DELETE FROM chunks WHERE document_id = ?", (document_id,))
92+
93+
# Remove document
94+
cursor.execute("DELETE FROM documents WHERE id = ?", (document_id,))
95+
96+
self._conn.commit()
97+
return True

src/sqlite_rag/sqliterag.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,22 @@ def list_documents(self) -> list[Document]:
9797

9898
return self._repository.list_documents()
9999

100+
def find_document(self, identifier: str) -> Document | None:
101+
"""Find document by ID or URI"""
102+
self._ensure_initialized()
103+
return self._repository.find_document_by_id_or_uri(identifier)
104+
105+
def remove_document(self, identifier: str) -> bool:
106+
"""Remove document by ID or URI"""
107+
self._ensure_initialized()
108+
109+
# First find the document to get its ID
110+
document = self._repository.find_document_by_id_or_uri(identifier)
111+
if not document:
112+
return False
113+
114+
return self._repository.remove_document(document.id or "")
115+
100116
# def search(
101117
# self, query: str, top_k: int = 10
102118
# ) -> list[Document]:

tests/test_engine.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import pytest
22

3-
from chunker import Chunker
4-
from engine import Engine
5-
from models.chunk import Chunk
6-
from models.document import Document
7-
from repository import Repository
3+
from sqlite_rag.chunker import Chunker
4+
from sqlite_rag.engine import Engine
5+
from sqlite_rag.models.chunk import Chunk
6+
from sqlite_rag.models.document import Document
7+
from sqlite_rag.repository import Repository
88

99

1010
@pytest.fixture

tests/test_repository.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,96 @@ def test_list_documents_empty(self, db_conn):
113113
documents = repo.list_documents()
114114

115115
assert len(documents) == 0
116+
117+
def test_find_document_by_id_or_uri_by_id(self, db_conn):
118+
conn, settings = db_conn
119+
repo = Repository(conn, settings)
120+
121+
# Add a document
122+
doc = Document(
123+
content="Test document content.",
124+
uri="test.txt",
125+
metadata={"author": "test"}
126+
)
127+
doc_id = repo.add_document(doc)
128+
129+
# Find by ID
130+
found_doc = repo.find_document_by_id_or_uri(doc_id)
131+
132+
assert found_doc is not None
133+
assert found_doc.id == doc_id
134+
assert found_doc.content == "Test document content."
135+
assert found_doc.uri == "test.txt"
136+
assert found_doc.metadata == {"author": "test"}
137+
138+
def test_find_document_by_id_or_uri_by_uri(self, db_conn):
139+
conn, settings = db_conn
140+
repo = Repository(conn, settings)
141+
142+
# Add a document
143+
doc = Document(
144+
content="Test document content.",
145+
uri="test.txt",
146+
metadata={"author": "test"}
147+
)
148+
repo.add_document(doc)
149+
150+
# Find by URI
151+
found_doc = repo.find_document_by_id_or_uri("test.txt")
152+
153+
assert found_doc is not None
154+
assert found_doc.content == "Test document content."
155+
assert found_doc.uri == "test.txt"
156+
assert found_doc.metadata == {"author": "test"}
157+
158+
def test_find_document_by_id_or_uri_not_found(self, db_conn):
159+
conn, settings = db_conn
160+
repo = Repository(conn, settings)
161+
162+
# Try to find non-existent document
163+
found_doc = repo.find_document_by_id_or_uri("nonexistent")
164+
165+
assert found_doc is None
166+
167+
def test_remove_document_success(self, db_conn):
168+
conn, settings = db_conn
169+
repo = Repository(conn, settings)
170+
171+
# Add a document with chunks
172+
doc = Document(
173+
content="Test document content.",
174+
uri="test.txt",
175+
metadata={"author": "test"}
176+
)
177+
doc.chunks = [
178+
Chunk(content="Chunk 1", embedding=b"\x00" * 384),
179+
Chunk(content="Chunk 2", embedding=b"\x00" * 384),
180+
]
181+
doc_id = repo.add_document(doc)
182+
183+
# Verify document and chunks exist
184+
cursor = conn.cursor()
185+
cursor.execute("SELECT COUNT(*) FROM documents WHERE id = ?", (doc_id,))
186+
assert cursor.fetchone()[0] == 1
187+
cursor.execute("SELECT COUNT(*) FROM chunks WHERE document_id = ?", (doc_id,))
188+
assert cursor.fetchone()[0] == 2
189+
190+
# Remove document
191+
success = repo.remove_document(doc_id)
192+
193+
assert success is True
194+
195+
# Verify document and chunks are removed
196+
cursor.execute("SELECT COUNT(*) FROM documents WHERE id = ?", (doc_id,))
197+
assert cursor.fetchone()[0] == 0
198+
cursor.execute("SELECT COUNT(*) FROM chunks WHERE document_id = ?", (doc_id,))
199+
assert cursor.fetchone()[0] == 0
200+
201+
def test_remove_document_not_found(self, db_conn):
202+
conn, settings = db_conn
203+
repo = Repository(conn, settings)
204+
205+
# Try to remove non-existent document
206+
success = repo.remove_document("nonexistent-id")
207+
208+
assert success is False

tests/test_sqlite_rag.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,115 @@ def test_list_documents(self, db_settings):
9999
assert len(documents) == 2
100100
assert documents[0].content == "Document 1 content."
101101
assert documents[1].content == "Document 2 content."
102+
103+
def test_find_document_by_id(self, db_settings):
104+
rag = SQLiteRag(db_settings)
105+
106+
rag.add_text("Test document content.", uri="test.txt", metadata={"author": "test"})
107+
documents = rag.list_documents()
108+
doc_id = documents[0].id
109+
110+
# Find by ID
111+
assert doc_id is not None
112+
found_doc = rag.find_document(doc_id)
113+
114+
assert found_doc is not None
115+
assert found_doc.id == doc_id
116+
assert found_doc.content == "Test document content."
117+
assert found_doc.uri == "test.txt"
118+
assert found_doc.metadata == {"author": "test"}
119+
120+
def test_find_document_by_uri(self, db_settings):
121+
rag = SQLiteRag(db_settings)
122+
123+
rag.add_text("Test document content.", uri="test.txt", metadata={"author": "test"})
124+
125+
# Find by URI
126+
found_doc = rag.find_document("test.txt")
127+
128+
assert found_doc is not None
129+
assert found_doc.content == "Test document content."
130+
assert found_doc.uri == "test.txt"
131+
assert found_doc.metadata == {"author": "test"}
132+
133+
def test_find_document_not_found(self, db_settings):
134+
rag = SQLiteRag(db_settings)
135+
136+
found_doc = rag.find_document("nonexistent")
137+
138+
assert found_doc is None
139+
140+
def test_remove_document_by_id(self, db_settings):
141+
rag = SQLiteRag(db_settings)
142+
143+
rag.add_text("Test document content.", uri="test.txt", metadata={"author": "test"})
144+
documents = rag.list_documents()
145+
doc_id = documents[0].id
146+
147+
# Verify document exists
148+
assert len(documents) == 1
149+
150+
# Remove by ID
151+
assert doc_id is not None
152+
success = rag.remove_document(doc_id)
153+
154+
assert success is True
155+
156+
# Verify document is removed
157+
documents = rag.list_documents()
158+
assert len(documents) == 0
159+
160+
def test_remove_document_by_uri(self, db_settings):
161+
rag = SQLiteRag(db_settings)
162+
163+
rag.add_text("Test document content.", uri="test.txt", metadata={"author": "test"})
164+
165+
# Verify document exists
166+
documents = rag.list_documents()
167+
assert len(documents) == 1
168+
169+
# Remove by URI
170+
success = rag.remove_document("test.txt")
171+
172+
assert success is True
173+
174+
# Verify document is removed
175+
documents = rag.list_documents()
176+
assert len(documents) == 0
177+
178+
def test_remove_document_not_found(self, db_settings):
179+
rag = SQLiteRag(db_settings)
180+
181+
success = rag.remove_document("nonexistent")
182+
183+
assert success is False
184+
185+
def test_remove_document_with_chunks(self, db_settings):
186+
rag = SQLiteRag(db_settings)
187+
188+
# Add document that will create chunks
189+
rag.add_text("This is a longer document that should create multiple chunks when processed by the chunker.", uri="test.txt")
190+
191+
# Verify document and chunks exist
192+
documents = rag.list_documents()
193+
assert len(documents) == 1
194+
doc_id = documents[0].id
195+
196+
cursor = rag._conn.cursor()
197+
cursor.execute("SELECT COUNT(*) FROM chunks WHERE document_id = ?", (doc_id,))
198+
chunk_count = cursor.fetchone()[0]
199+
assert chunk_count > 0
200+
201+
# Remove document
202+
assert doc_id is not None
203+
success = rag.remove_document(doc_id)
204+
205+
assert success is True
206+
207+
# Verify document and chunks are removed
208+
documents = rag.list_documents()
209+
assert len(documents) == 0
210+
211+
cursor.execute("SELECT COUNT(*) FROM chunks WHERE document_id = ?", (doc_id,))
212+
chunk_count = cursor.fetchone()[0]
213+
assert chunk_count == 0

0 commit comments

Comments
 (0)