-
Notifications
You must be signed in to change notification settings - Fork 95
spike: evaluate SQLite vector storage for desktop mode #310
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
6e9b143
921aad8
cda6688
81ae551
15e472a
b8f42a4
160cd2e
bda6cb8
38d0d26
0d5d37f
66b4b42
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,213 @@ | ||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||
| SQLite + sqlite-vec proof of concept. | ||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| import sqlite3 | ||||||||||||||||||||||||||||
| import struct | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| EMBEDDING_DIM = 768 | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def create_connection(db_path=":memory:"): | ||||||||||||||||||||||||||||
| conn = sqlite3.connect(str(db_path)) | ||||||||||||||||||||||||||||
| conn.enable_load_extension(True) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||
| import sqlite_vec | ||||||||||||||||||||||||||||
| except ModuleNotFoundError as exc: | ||||||||||||||||||||||||||||
| raise RuntimeError( | ||||||||||||||||||||||||||||
| "sqlite-vec is required for this desktop-runtime proof of concept. " | ||||||||||||||||||||||||||||
| "Install it manually with `pip install sqlite-vec` before running " | ||||||||||||||||||||||||||||
| "the sqlite_vec_poc tests." | ||||||||||||||||||||||||||||
| ) from exc | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| sqlite_vec.load(conn) | ||||||||||||||||||||||||||||
| return conn | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def create_schema(conn, embedding_dim: int): | ||||||||||||||||||||||||||||
| conn.execute( | ||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||
| CREATE TABLE IF NOT EXISTS media ( | ||||||||||||||||||||||||||||
| id INTEGER PRIMARY KEY, | ||||||||||||||||||||||||||||
| filename TEXT NOT NULL, | ||||||||||||||||||||||||||||
| status TEXT NOT NULL | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| conn.execute( | ||||||||||||||||||||||||||||
| f""" | ||||||||||||||||||||||||||||
| CREATE VIRTUAL TABLE IF NOT EXISTS media_vectors | ||||||||||||||||||||||||||||
| USING vec0( | ||||||||||||||||||||||||||||
| media_id INTEGER PRIMARY KEY, | ||||||||||||||||||||||||||||
| embedding FLOAT[{embedding_dim}] | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| conn.commit() | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def insert_vector( | ||||||||||||||||||||||||||||
| conn, | ||||||||||||||||||||||||||||
| media_id: int, | ||||||||||||||||||||||||||||
| embedding: list[float], | ||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||
| blob = struct.pack(f"{len(embedding)}f", *embedding) | ||||||||||||||||||||||||||||
|
Comment on lines
+52
to
+57
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: wc -l backend/src/find_api/core/sqlite_vec_poc.pyRepository: Abhash-Chakraborty/Find Length of output: 115 🏁 Script executed: cat -n backend/src/find_api/core/sqlite_vec_poc.pyRepository: Abhash-Chakraborty/Find Length of output: 5602 Enforce embedding dimension contract at write/search boundaries (lines 57, 97-100, 123-126). Current vector functions ( Suggested patch EMBEDDING_DIM = 768
+def _validate_embedding_dim(embedding: list[float], expected_dim: int = EMBEDDING_DIM) -> None:
+ if len(embedding) != expected_dim:
+ raise ValueError(
+ f"embedding must have exactly {expected_dim} dimensions, got {len(embedding)}"
+ )
+
@@
def insert_vector(
@@
):
+ _validate_embedding_dim(embedding)
blob = struct.pack(f"{len(embedding)}f", *embedding)
@@
def search_vectors(
@@
):
+ _validate_embedding_dim(query_embedding)
blob = struct.pack(
@@
def search_media(
@@
):
+ _validate_embedding_dim(query_embedding)
blob = struct.pack(This aligns with the guideline: "Keep EMBEDDING_DIM aligned with the configured CLIP/SigLIP model and pgvector columns." 📝 Committable suggestion
Suggested change
🤖 Prompt for AI AgentsSource: Coding guidelines |
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| conn.execute( | ||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||
| INSERT INTO media_vectors(media_id, embedding) | ||||||||||||||||||||||||||||
| VALUES (?, ?) | ||||||||||||||||||||||||||||
| """, | ||||||||||||||||||||||||||||
| (media_id, blob), | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| conn.commit() | ||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: find . -type f -name "sqlite_vec_poc.py" | head -5Repository: Abhash-Chakraborty/Find Length of output: 113 🏁 Script executed: wc -l backend/src/find_api/core/sqlite_vec_poc.pyRepository: Abhash-Chakraborty/Find Length of output: 115 🏁 Script executed: cat -n backend/src/find_api/core/sqlite_vec_poc.pyRepository: Abhash-Chakraborty/Find Length of output: 5602 Make media+vector insertion atomic to prevent partial writes.
Wrap both operations in a transaction context to ensure atomicity: Suggested patch def insert_media(
self,
media_id,
filename,
embedding,
):
- insert_media(
- self.conn,
- media_id,
- filename,
- )
-
- insert_vector(
- self.conn,
- media_id,
- embedding,
- )
+ with self.conn:
+ insert_media(
+ self.conn,
+ media_id,
+ filename,
+ )
+ insert_vector(
+ self.conn,
+ media_id,
+ embedding,
+ )Also remove 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def insert_media( | ||||||||||||||||||||||||||||
| conn, | ||||||||||||||||||||||||||||
| media_id: int, | ||||||||||||||||||||||||||||
| filename: str, | ||||||||||||||||||||||||||||
| status: str = "indexed", | ||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||
| conn.execute( | ||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||
| INSERT INTO media(id, filename, status) | ||||||||||||||||||||||||||||
| VALUES (?, ?, ?) | ||||||||||||||||||||||||||||
| """, | ||||||||||||||||||||||||||||
| (media_id, filename, status), | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
| conn.commit() | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def count_vectors(conn) -> int: | ||||||||||||||||||||||||||||
| row = conn.execute("SELECT COUNT(*) FROM media_vectors").fetchone() | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| return row[0] | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def search_vectors( | ||||||||||||||||||||||||||||
| conn, | ||||||||||||||||||||||||||||
| query_embedding: list[float], | ||||||||||||||||||||||||||||
| limit: int = 10, | ||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||
| blob = struct.pack( | ||||||||||||||||||||||||||||
| f"{len(query_embedding)}f", | ||||||||||||||||||||||||||||
| *query_embedding, | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| rows = conn.execute( | ||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||
| SELECT | ||||||||||||||||||||||||||||
| media_id, | ||||||||||||||||||||||||||||
| distance | ||||||||||||||||||||||||||||
| FROM media_vectors | ||||||||||||||||||||||||||||
| WHERE embedding MATCH ? | ||||||||||||||||||||||||||||
| ORDER BY distance | ||||||||||||||||||||||||||||
| LIMIT ? | ||||||||||||||||||||||||||||
| """, | ||||||||||||||||||||||||||||
| (blob, limit), | ||||||||||||||||||||||||||||
| ).fetchall() | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| return rows | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def search_media( | ||||||||||||||||||||||||||||
| conn, | ||||||||||||||||||||||||||||
| query_embedding, | ||||||||||||||||||||||||||||
| limit=10, | ||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||
| blob = struct.pack( | ||||||||||||||||||||||||||||
| f"{len(query_embedding)}f", | ||||||||||||||||||||||||||||
| *query_embedding, | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| rows = conn.execute( | ||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||
| SELECT | ||||||||||||||||||||||||||||
| m.id, | ||||||||||||||||||||||||||||
| m.filename, | ||||||||||||||||||||||||||||
| v.distance | ||||||||||||||||||||||||||||
| FROM media_vectors v | ||||||||||||||||||||||||||||
| JOIN media m | ||||||||||||||||||||||||||||
| ON m.id = v.media_id | ||||||||||||||||||||||||||||
| WHERE embedding MATCH ? | ||||||||||||||||||||||||||||
| AND k = ? | ||||||||||||||||||||||||||||
| """, | ||||||||||||||||||||||||||||
| (blob, limit), | ||||||||||||||||||||||||||||
| ).fetchall() | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| return rows | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| class SQLiteVecPOC: | ||||||||||||||||||||||||||||
| def __init__(self, db_path=":memory:"): | ||||||||||||||||||||||||||||
| self.conn = create_connection(db_path) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def create_schema(self): | ||||||||||||||||||||||||||||
| create_schema( | ||||||||||||||||||||||||||||
| self.conn, | ||||||||||||||||||||||||||||
| EMBEDDING_DIM, | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def insert_media( | ||||||||||||||||||||||||||||
| self, | ||||||||||||||||||||||||||||
| media_id, | ||||||||||||||||||||||||||||
| filename, | ||||||||||||||||||||||||||||
| embedding, | ||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||
| insert_media( | ||||||||||||||||||||||||||||
| self.conn, | ||||||||||||||||||||||||||||
| media_id, | ||||||||||||||||||||||||||||
| filename, | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| insert_vector( | ||||||||||||||||||||||||||||
| self.conn, | ||||||||||||||||||||||||||||
| media_id, | ||||||||||||||||||||||||||||
| embedding, | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def search( | ||||||||||||||||||||||||||||
| self, | ||||||||||||||||||||||||||||
| embedding, | ||||||||||||||||||||||||||||
| limit=10, | ||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||
| rows = search_media( | ||||||||||||||||||||||||||||
| self.conn, | ||||||||||||||||||||||||||||
| embedding, | ||||||||||||||||||||||||||||
| limit, | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| return [ | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| "id": row[0], | ||||||||||||||||||||||||||||
| "filename": row[1], | ||||||||||||||||||||||||||||
| "distance": row[2], | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| for row in rows | ||||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def gallery_query(self): | ||||||||||||||||||||||||||||
| rows = self.conn.execute( | ||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||
| SELECT | ||||||||||||||||||||||||||||
| id, | ||||||||||||||||||||||||||||
| filename, | ||||||||||||||||||||||||||||
| status | ||||||||||||||||||||||||||||
| FROM media | ||||||||||||||||||||||||||||
| ORDER BY id | ||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||
| ).fetchall() | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| return [ | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| "id": row[0], | ||||||||||||||||||||||||||||
| "filename": row[1], | ||||||||||||||||||||||||||||
| "status": row[2], | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| for row in rows | ||||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| import importlib.util | ||
|
|
||
| import pytest | ||
|
|
||
| from find_api.core.sqlite_vec_poc import ( | ||
| EMBEDDING_DIM, | ||
| SQLiteVecPOC, | ||
| ) | ||
|
|
||
| SQLITE_VEC_AVAILABLE = importlib.util.find_spec("sqlite_vec") is not None | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def sqlite_vec_available(): | ||
| if not SQLITE_VEC_AVAILABLE: | ||
| pytest.skip("sqlite-vec is optional and not installed") | ||
|
|
||
|
|
||
| def test_missing_sqlite_vec_dependency_message(tmp_path): | ||
| if SQLITE_VEC_AVAILABLE: | ||
| pytest.skip("sqlite-vec is installed") | ||
|
|
||
| db_file = tmp_path / "sqlite_vec.db" | ||
|
|
||
| with pytest.raises(RuntimeError, match="sqlite-vec is required"): | ||
| SQLiteVecPOC(db_file) | ||
|
|
||
|
|
||
| def test_schema_creation(tmp_path, sqlite_vec_available): | ||
| db_file = tmp_path / "sqlite_vec.db" | ||
|
|
||
| poc = SQLiteVecPOC(db_file) | ||
| poc.create_schema() | ||
|
|
||
| assert db_file.exists() | ||
|
|
||
|
|
||
| def test_insert_768_dimension_vector(tmp_path, sqlite_vec_available): | ||
| db_file = tmp_path / "sqlite_vec.db" | ||
|
|
||
| poc = SQLiteVecPOC(db_file) | ||
| poc.create_schema() | ||
|
|
||
| poc.insert_media( | ||
| media_id=1, | ||
| filename="cat.jpg", | ||
| embedding=[0.1] * EMBEDDING_DIM, | ||
| ) | ||
|
|
||
| gallery = poc.gallery_query() | ||
|
|
||
| assert len(gallery) == 1 | ||
| assert gallery[0]["filename"] == "cat.jpg" | ||
|
|
||
|
|
||
| def test_similarity_search(tmp_path, sqlite_vec_available): | ||
| db_file = tmp_path / "sqlite_vec.db" | ||
|
|
||
| poc = SQLiteVecPOC(db_file) | ||
| poc.create_schema() | ||
|
|
||
| poc.insert_media( | ||
| 1, | ||
| "match.jpg", | ||
| [0.1] * EMBEDDING_DIM, | ||
| ) | ||
|
|
||
| poc.insert_media( | ||
| 2, | ||
| "far.jpg", | ||
| [0.2] * EMBEDDING_DIM, | ||
| ) | ||
|
|
||
| results = poc.search( | ||
| [0.1] * EMBEDDING_DIM, | ||
| limit=2, | ||
| ) | ||
|
|
||
| assert len(results) == 2 | ||
| assert results[0]["id"] == 1 | ||
|
|
||
|
|
||
| def test_gallery_query_shape(tmp_path, sqlite_vec_available): | ||
| db_file = tmp_path / "sqlite_vec.db" | ||
|
|
||
| poc = SQLiteVecPOC(db_file) | ||
| poc.create_schema() | ||
|
|
||
| poc.insert_media( | ||
| 1, | ||
| "image.jpg", | ||
| [0.1] * EMBEDDING_DIM, | ||
| ) | ||
|
|
||
| gallery = poc.gallery_query() | ||
|
|
||
| assert gallery == [ | ||
| { | ||
| "id": 1, | ||
| "filename": "image.jpg", | ||
| "status": "indexed", | ||
| } | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,7 +2,7 @@ | |
|
|
||
| - **Status:** Not started | ||
| - **Date:** 2026-05-18 | ||
| - **Last reviewed:** 2026-05-28 | ||
| - **Last reviewed:** 2026-06-19 | ||
| - **Owner:** Find maintainers | ||
| - **Related:** Issue #43, Roadmap [local-first-roadmap.md](../partial/local-first-roadmap.md), Framework choice [desktop-tauri-vs-electron-adr.md](../partial/desktop-tauri-vs-electron-adr.md) | ||
|
|
||
|
|
@@ -74,6 +74,37 @@ The desktop runtime must remain **local-first** by default: images, embeddings, | |
| - Query performance remains acceptable (<500ms for typical gallery + search) | ||
| - Single-file backup and export mechanisms are straightforward | ||
|
|
||
| **SQLite vector spike result (2026-06-19):** | ||
|
|
||
| A focused proof of concept now exists at `backend/src/find_api/core/sqlite_vec_poc.py` with tests in `backend/tests/test_sqlite_vec_poc.py`. It validates the basic desktop-mode shape without changing the production Docker/PostgreSQL runtime: | ||
|
|
||
| - creates a SQLite database and media metadata table | ||
| - loads `sqlite-vec` as an optional local extension | ||
| - creates a 768-dimensional vector table matching Find's current embedding size | ||
| - inserts media rows and vector blobs | ||
| - runs nearest-neighbor vector search | ||
| - returns a gallery-style metadata result shape | ||
|
|
||
| To run the spike manually: | ||
|
|
||
| ```bash | ||
| cd backend | ||
| pip install sqlite-vec | ||
| uv run pytest tests/test_sqlite_vec_poc.py -q | ||
| ``` | ||
|
Comment on lines
+88
to
+94
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # Check if uv is used in this project
fd -e "uv.lock" -o -e "pyproject.toml" | head -20Repository: Abhash-Chakraborty/Find Length of output: 184 🏁 Script executed: # Check for the file in question
fd "desktop-runtime-adr.md"Repository: Abhash-Chakraborty/Find Length of output: 113 🏁 Script executed: # List backend directory structure
ls -la backend/ 2>/dev/null || echo "backend directory not found"Repository: Abhash-Chakraborty/Find Length of output: 880 🏁 Script executed: # Read the actual file content around lines 88-94
sed -n '80,100p' docs/plans/not-started/desktop-runtime-adr.mdRepository: Abhash-Chakraborty/Find Length of output: 729 🏁 Script executed: # Check the backend pyproject.toml to understand the project setup
cat backend/pyproject.tomlRepository: Abhash-Chakraborty/Find Length of output: 1883 🏁 Script executed: # Check if there's a root pyproject.toml with uv tool configuration
cat pyproject.toml 2>/dev/null || echo "No root pyproject.toml"Repository: Abhash-Chakraborty/Find Length of output: 90 Use The repository uses uv for package management (evidenced by ♻️ Suggested edit cd backend
-pip install sqlite-vec
+uv pip install sqlite-vec
uv run pytest tests/test_sqlite_vec_poc.py -q🤖 Prompt for AI AgentsSource: Coding guidelines |
||
|
|
||
| The tests skip automatically when `sqlite-vec` is not installed because this is still a desktop-runtime spike, not a default backend dependency. | ||
|
|
||
| Current limitations: | ||
|
|
||
| - It does not replace PostgreSQL + pgvector in Docker mode. | ||
| - It does not cover migrations from the existing PostgreSQL schema. | ||
| - It does not benchmark larger libraries, concurrent writes, WAL behavior, or index build cost. | ||
| - It does not yet validate Find's full hybrid search behavior, filters, clustering joins, or queue interactions. | ||
| - It keeps `sqlite-vec` out of the default dependency set until the project decides whether desktop mode should ship it. | ||
|
|
||
| Follow-up implementation should only happen after the spike is benchmarked against realistic local libraries and the query abstraction is designed so PostgreSQL and SQLite can coexist cleanly. | ||
|
|
||
| --- | ||
|
|
||
| ### 4.2 Object Storage: MinIO → Local Filesystem | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, check if the file exists and view its content cat -n backend/src/find_api/core/sqlite_vec_poc.pyRepository: Abhash-Chakraborty/Find
Length of output: 5602
🏁 Script executed:
Repository: Abhash-Chakraborty/Find
Length of output: 143
🏁 Script executed:
Repository: Abhash-Chakraborty/Find
Length of output: 774
🏁 Script executed:
# Check the test file to see if create_connection is tested cat -n backend/tests/test_sqlite_vec_poc.pyRepository: Abhash-Chakraborty/Find
Length of output: 2909
🏁 Script executed:
Repository: Abhash-Chakraborty/Find
Length of output: 92
🏁 Script executed:
Repository: Abhash-Chakraborty/Find
Length of output: 418
🏁 Script executed:
Repository: Abhash-Chakraborty/Find
Length of output: 49
🏁 Script executed:
Repository: Abhash-Chakraborty/Find
Length of output: 2357
🏁 Script executed:
# Verify best practices for sqlite3 connection error handling web_search python sqlite3 connection cleanup best practices error handlingRepository: Abhash-Chakraborty/Find
Length of output: 206
🌐 Web query:
sqlite3 python connection cleanup best practices exception handling💡 Result:
When managing SQLite connections in Python, it is critical to distinguish between transaction management and resource cleanup. The built-in context manager for sqlite3 connections handles only transactions, not the closing of the database connection itself [1][2][3][4]. Best Practices for Cleanup and Exception Handling: 1. Use contextlib.closing for Automatic Cleanup: Since the standard sqlite3 connection context manager does not call.close, use contextlib.closing to ensure the connection is closed automatically when the block exits, regardless of whether an exception occurs [5][6][7][8]. import sqlite3 from contextlib import closing with closing(sqlite3.connect("database.db")) as conn: with conn: # Handles transaction commit/rollback conn.execute("INSERT INTO table_name VALUES (?)", (data,)) 2. Manual Cleanup with try/finally: If you do not use contextlib.closing, you must explicitly call.close in a finally block to guarantee the connection is released, even if an error occurs [9]. conn = sqlite3.connect("database.db") try: with conn: conn.execute("INSERT INTO table_name VALUES (?)", (data,)) except sqlite3.Error as e: print(f"Database error: {e}") finally: conn.close 3. Understanding Transaction Management: The context manager used directly on a connection object (e.g., with conn:) automatically commits if the block finishes successfully and rolls back if an exception is raised [1][2][4]. It is intended for managing transaction boundaries, not for closing the connection [7]. 4. Key Considerations: - Do not rely on garbage collection: Explicitly closing connections prevents resource leaks and file locking issues [9][8]. - Catch specific exceptions: Use sqlite3.Error or its subclasses (e.g., sqlite3.OperationalError, sqlite3.IntegrityError) to handle database-related issues specifically [1][2][9]. - Keep connections scoped: Open the connection as late as possible and close it as soon as you are done to minimize the time the database file is locked [9][10].
Citations:
🏁 Script executed:
Repository: Abhash-Chakraborty/Find
Length of output: 4569
🏁 Script executed:
Repository: Abhash-Chakraborty/Find
Length of output: 49
🏁 Script executed:
Repository: Abhash-Chakraborty/Find
Length of output: 167
🏁 Script executed:
Repository: Abhash-Chakraborty/Find
Length of output: 49
Close connection on missing
sqlite_vecimport and disable extension loading after load.On
ModuleNotFoundError, the opened DB connection is leaked (line 12). Additionally, extension loading should be disabled immediately aftersqlite_vec.load(conn)to minimize attack surface.Suggested patch
def create_connection(db_path=":memory:"): conn = sqlite3.connect(str(db_path)) conn.enable_load_extension(True) try: import sqlite_vec except ModuleNotFoundError as exc: + conn.close() raise RuntimeError( "sqlite-vec is required for this desktop-runtime proof of concept. " "Install it manually with `pip install sqlite-vec` before running " "the sqlite_vec_poc tests." ) from exc sqlite_vec.load(conn) + conn.enable_load_extension(False) return conn🤖 Prompt for AI Agents