Skip to content

Commit 4dcac2f

Browse files
committed
fix(postgres-tests): avoid schema init conflicts; harden race handling
- Ensure Postgres test fixture populates db module globals so app lifespan doesn't rerun migrations\n- Tighten PostgresSearchRepository tsquery error detection (don't swallow missing-table errors)\n- Handle Postgres unique constraint message for file_path race condition Signed-off-by: phernandez <[email protected]>
1 parent 0be71e3 commit 4dcac2f

File tree

3 files changed

+29
-8
lines changed

3 files changed

+29
-8
lines changed

src/basic_memory/repository/postgres_search_repository.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -329,15 +329,21 @@ async def search(
329329
result = await session.execute(text(sql), params)
330330
rows = result.fetchall()
331331
except Exception as e:
332-
# Handle tsquery syntax errors
333-
if "tsquery" in str(e).lower() or "syntax error" in str(e).lower(): # pragma: no cover
332+
# Handle tsquery syntax errors (and only those).
333+
#
334+
# Important: Postgres errors for other failures (e.g. missing table) will still mention
335+
# `to_tsquery(...)` in the SQL text, so checking for the substring "tsquery" is too broad.
336+
msg = str(e).lower()
337+
if (
338+
"syntax error in tsquery" in msg
339+
or "invalid input syntax for type tsquery" in msg
340+
): # pragma: no cover
334341
logger.warning(f"tsquery syntax error for search term: {search_text}, error: {e}")
335-
# Return empty results rather than crashing
336342
return []
337-
else:
338-
# Re-raise other database errors
339-
logger.error(f"Database error during search: {e}")
340-
raise
343+
344+
# Re-raise other database errors
345+
logger.error(f"Database error during search: {e}")
346+
raise
341347

342348
results = [
343349
SearchIndexRow(

src/basic_memory/sync/sync_service.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -766,7 +766,13 @@ async def sync_regular_file(self, path: str, new: bool = True) -> Tuple[Optional
766766
return entity, checksum
767767
except IntegrityError as e:
768768
# Handle race condition where entity was created by another process
769-
if "UNIQUE constraint failed: entity.file_path" in str(e):
769+
msg = str(e)
770+
if (
771+
"UNIQUE constraint failed: entity.file_path" in msg
772+
or "uix_entity_file_path_project" in msg
773+
or "duplicate key value violates unique constraint" in msg
774+
and "file_path" in msg
775+
):
770776
logger.info(
771777
f"Entity already exists for file_path={path}, updating instead of creating"
772778
)

tests/conftest.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,13 @@ async def engine_factory(
206206
autoflush=False,
207207
)
208208

209+
# Important: wire the engine/session into the global db module state.
210+
# Some codepaths (e.g. app initialization / MCP lifespan) call db.get_or_create_db(),
211+
# which would otherwise create a separate engine and run migrations, conflicting with
212+
# our test-created schema (and causing DuplicateTableError).
213+
db._engine = engine
214+
db._session_maker = session_maker
215+
209216
from basic_memory.models.search import (
210217
CREATE_POSTGRES_SEARCH_INDEX_TABLE,
211218
CREATE_POSTGRES_SEARCH_INDEX_FTS,
@@ -229,6 +236,8 @@ async def engine_factory(
229236
yield engine, session_maker
230237

231238
await engine.dispose()
239+
db._engine = None
240+
db._session_maker = None
232241
else:
233242
# SQLite mode
234243
db_type = DatabaseType.MEMORY

0 commit comments

Comments
 (0)