1616from typing import Any , Iterable , Optional , Sequence , Set , Tuple
1717
1818from sqlalchemy import text
19+ from sqlalchemy .engine import Engine
1920
2021HEROKU_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+
3146def 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
91106def 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