Skip to content

Commit 495037a

Browse files
committed
fix: use SQLAlchemy 2 Engine-safe execute for pgvector helpers
session.get_bind() returns an Engine which no longer has .execute; open a Connection so can_use_pgvector_similarity and similarity queries work on Postgres.
1 parent 13b86f0 commit 495037a

2 files changed

Lines changed: 39 additions & 18 deletions

File tree

application/database/db.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2581,14 +2581,18 @@ def find_most_similar_embedding_id(
25812581

25822582
sql = most_similar_id_sql(id_column)
25832583
bind = self.session.get_bind()
2584+
params = {
2585+
"q": to_pgvector_literal(query_embedding),
2586+
"doc_type": doc_type,
2587+
}
25842588
try:
2585-
row = bind.execute(
2586-
sql_text(sql),
2587-
{
2588-
"q": to_pgvector_literal(query_embedding),
2589-
"doc_type": doc_type,
2590-
},
2591-
).fetchone()
2589+
# Prefer the Session so we stay inside the app transaction; fall
2590+
# back to a Connection from the Engine for SQLAlchemy 2.
2591+
if hasattr(self.session, "execute"):
2592+
row = self.session.execute(sql_text(sql), params).fetchone()
2593+
else:
2594+
with bind.connect() as conn:
2595+
row = conn.execute(sql_text(sql), params).fetchone()
25922596
if not row:
25932597
return None, None
25942598
score = float(row.score)

application/database/pgvector_utils.py

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from typing import Any, Iterable, Optional, Sequence, Set, Tuple
1717

1818
from sqlalchemy import text
19+
from sqlalchemy.engine import Engine
1920

2021
HEROKU_REEMBED_WARNING = (
2122
"Do not re-embed the full corpus on Heroku/production dynos — likely "
@@ -28,6 +29,20 @@ class MultiDimEmbeddingError(RuntimeError):
2829
"""Raised when more than one embedding dimension is present in the DB."""
2930

3031

32+
def _execute(connection: Any, statement: Any, params: Optional[dict] = None):
33+
"""Run ``statement`` on a Connection/Session, or open one from an Engine.
34+
35+
SQLAlchemy 2 removed ``Engine.execute``; Alembic upgrade passes a
36+
Connection, while runtime code often has ``session.get_bind()`` → Engine.
37+
"""
38+
if isinstance(connection, Engine):
39+
with connection.connect() as conn:
40+
result = conn.execute(statement, params or {})
41+
conn.commit()
42+
return result
43+
return connection.execute(statement, params or {})
44+
45+
3146
def to_pgvector_literal(vector: Sequence[float]) -> str:
3247
"""Render a vector in pgvector's text input format: ``[1.0,2.0,3.0]``."""
3348
return "[" + ",".join(repr(float(x)) for x in vector) + "]"
@@ -65,8 +80,8 @@ def require_single_embedding_dim(connection: Any) -> int:
6580
Prefers ``embedding_dim`` metadata; falls back to CSV length. If the table
6681
is empty, uses ``CRE_EMBED_EXPECTED_DIM`` when set.
6782
"""
68-
rows = connection.execute(
69-
text("SELECT embedding_dim, embeddings FROM embeddings")
83+
rows = _execute(
84+
connection, text("SELECT embedding_dim, embeddings FROM embeddings")
7085
).fetchall()
7186
dims = collect_distinct_dims((row[0], row[1]) for row in rows)
7287
if len(dims) > 1:
@@ -90,19 +105,20 @@ def require_single_embedding_dim(connection: Any) -> int:
90105

91106
def embedding_vec_column_exists(connection: Any) -> bool:
92107
"""True when ``embeddings.embedding_vec`` exists (Postgres information_schema)."""
93-
if getattr(getattr(connection, "dialect", None), "name", None) != "postgresql":
94-
# SQLite / others: probe via PRAGMA or SQLAlchemy inspector if needed.
95-
dialect_name = getattr(getattr(connection, "dialect", None), "name", "")
96-
if dialect_name == "sqlite":
97-
rows = connection.execute(text("PRAGMA table_info(embeddings)")).fetchall()
98-
return any(r[1] == "embedding_vec" for r in rows)
108+
dialect_name = getattr(getattr(connection, "dialect", None), "name", "") or ""
109+
110+
if dialect_name == "sqlite":
111+
rows = _execute(connection, text("PRAGMA table_info(embeddings)")).fetchall()
112+
return any(r[1] == "embedding_vec" for r in rows)
113+
if dialect_name != "postgresql":
99114
return False
100-
row = connection.execute(
115+
row = _execute(
116+
connection,
101117
text(
102118
"SELECT 1 FROM information_schema.columns "
103119
"WHERE table_name = 'embeddings' AND column_name = 'embedding_vec' "
104120
"LIMIT 1"
105-
)
121+
),
106122
).fetchone()
107123
return row is not None
108124

@@ -112,7 +128,8 @@ def backfill_embedding_vec(connection: Any, dim: int) -> int:
112128
113129
Returns the number of rows reported updated (driver-dependent).
114130
"""
115-
result = connection.execute(
131+
result = _execute(
132+
connection,
116133
text(
117134
"""
118135
UPDATE embeddings

0 commit comments

Comments
 (0)