mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
fix(core): load sqlite-vec for embedding-status query (#901)
Signed-off-by: phernandez <paul@basicmachines.co> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@ from typing import Optional, Sequence, Union
|
||||
|
||||
|
||||
from loguru import logger
|
||||
from sqlalchemy import inspect as sa_inspect, select, text
|
||||
from sqlalchemy import Executable, inspect as sa_inspect, select, text
|
||||
from sqlalchemy.exc import NoResultFound, OperationalError
|
||||
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
|
||||
|
||||
@@ -258,6 +258,28 @@ class ProjectRepository(Repository[Project]):
|
||||
logger.debug(f"Deleted Project and search rows for project_id: {entity_id}")
|
||||
return True
|
||||
|
||||
async def scalar_vec_query(
|
||||
self, query: Executable, params: Optional[dict] = None
|
||||
) -> Optional[int]:
|
||||
"""Run a scalar COUNT query that reads the sqlite-vec vec0 table.
|
||||
|
||||
Extension loading is per-connection, so the bare pooled session used by
|
||||
`execute_query` cannot read vec0 virtual tables — SQLite raises
|
||||
"no such module: vec0". This helper loads sqlite-vec on the session it
|
||||
opens before running the query, reusing the same loader as project delete.
|
||||
|
||||
Returns None when sqlite-vec cannot be loaded on this Python build, so
|
||||
callers can fall back to the genuinely-missing-dependency path.
|
||||
"""
|
||||
async with db.scoped_session(self.session_maker) as session:
|
||||
# Trigger: query reads the vec0-backed search_vector_embeddings table.
|
||||
# Why: vec0 modules are only visible on a connection that loaded sqlite-vec.
|
||||
# Outcome: load the extension here, or signal absence so the caller degrades.
|
||||
if not await _load_sqlite_vec_on_session(session):
|
||||
return None
|
||||
result = await session.execute(query, params)
|
||||
return result.scalar()
|
||||
|
||||
async def update_path(self, project_id: int, new_path: str) -> Optional[Project]:
|
||||
"""Update project path.
|
||||
|
||||
|
||||
@@ -1047,10 +1047,27 @@ class ProjectService:
|
||||
f"WHERE c.project_id = :project_id {chunk_entity_exists}"
|
||||
)
|
||||
|
||||
embeddings_result = await self.repository.execute_query(
|
||||
embeddings_sql, {"project_id": project_id}
|
||||
)
|
||||
total_embeddings = embeddings_result.scalar() or 0
|
||||
# The embeddings/orphan JOINs read search_vector_embeddings, a vec0
|
||||
# virtual table. On SQLite that table is only visible on a connection
|
||||
# that loaded sqlite-vec, so route these through scalar_vec_query which
|
||||
# loads the extension first. Postgres has no per-connection extension
|
||||
# and uses the bare pooled session.
|
||||
async def _vec_scalar(vec_sql) -> int:
|
||||
if is_postgres:
|
||||
result = await self.repository.execute_query(
|
||||
vec_sql, {"project_id": project_id}
|
||||
)
|
||||
return result.scalar() or 0
|
||||
count = await self.repository.scalar_vec_query(vec_sql, {"project_id": project_id})
|
||||
# Trigger: sqlite-vec genuinely can't load on this Python build.
|
||||
# Why: without the extension the vec0 JOIN can't run at all.
|
||||
# Outcome: raise the canonical error so the except block emits the
|
||||
# true "sqlite-vec unavailable" fallback instead of reporting 0.
|
||||
if count is None:
|
||||
raise SAOperationalError(str(vec_sql), {}, Exception("no such module: vec0"))
|
||||
return count
|
||||
|
||||
total_embeddings = await _vec_scalar(embeddings_sql)
|
||||
|
||||
# Orphaned chunks (chunks without embeddings — indicates interrupted indexing)
|
||||
if is_postgres:
|
||||
@@ -1065,11 +1082,7 @@ class ProjectService:
|
||||
"LEFT JOIN search_vector_embeddings e ON e.rowid = c.id "
|
||||
f"WHERE c.project_id = :project_id AND e.rowid IS NULL {chunk_entity_exists}"
|
||||
)
|
||||
|
||||
orphan_result = await self.repository.execute_query(
|
||||
orphan_sql, {"project_id": project_id}
|
||||
)
|
||||
orphaned_chunks = orphan_result.scalar() or 0
|
||||
orphaned_chunks = await _vec_scalar(orphan_sql)
|
||||
except SAOperationalError as exc:
|
||||
# Trigger: sqlite_master can list vec0 virtual tables even when sqlite-vec
|
||||
# is not loaded in the current Python runtime.
|
||||
|
||||
Reference in New Issue
Block a user