@@ -66,6 +66,9 @@ def create_bm25_index(
6666 )
6767 )
6868
69+ def reverse (self ) -> MigrateOperation :
70+ return DropBM25IndexOp (index_name = self .index_name , if_exists = True , schema = self .table_schema )
71+
6972
7073@Operations .implementation_for (CreateBM25IndexOp )
7174def _create_bm25_index_impl (operations : Operations , operation : CreateBM25IndexOp ) -> None :
@@ -97,16 +100,62 @@ def _render_create_bm25_index_op(autogen_context, op: CreateBM25IndexOp) -> str:
97100
98101@Operations .register_operation ("drop_bm25_index" )
99102class DropBM25IndexOp (MigrateOperation ):
100- def __init__ (self , index_name : str , if_exists : bool = True , schema : str | None = None ) -> None :
103+ def __init__ (
104+ self ,
105+ index_name : str ,
106+ if_exists : bool = True ,
107+ schema : str | None = None ,
108+ * ,
109+ table_name : str | None = None ,
110+ expressions : list [str ] | None = None ,
111+ key_field : str | None = None ,
112+ where : str | None = None ,
113+ ) -> None :
101114 self .index_name = index_name
102115 self .if_exists = if_exists
103116 self .schema = schema
117+ self .table_name = table_name
118+ self .expressions = expressions
119+ self .key_field = key_field
120+ self .where = where
104121
105122 @classmethod
106123 def drop_bm25_index (
107- cls , operations : Operations , index_name : str , if_exists : bool = True , schema : str | None = None
124+ cls ,
125+ operations : Operations ,
126+ index_name : str ,
127+ if_exists : bool = True ,
128+ schema : str | None = None ,
129+ * ,
130+ table_name : str | None = None ,
131+ expressions : list [str ] | None = None ,
132+ key_field : str | None = None ,
133+ where : str | None = None ,
108134 ) -> MigrateOperation :
109- return operations .invoke (cls (index_name = index_name , if_exists = if_exists , schema = schema ))
135+ return operations .invoke (
136+ cls (
137+ index_name = index_name ,
138+ if_exists = if_exists ,
139+ schema = schema ,
140+ table_name = table_name ,
141+ expressions = expressions ,
142+ key_field = key_field ,
143+ where = where ,
144+ )
145+ )
146+
147+ def reverse (self ) -> MigrateOperation :
148+ if self .table_name is None or self .expressions is None or self .key_field is None :
149+ raise NotImplementedError ("DropBM25IndexOp requires recreate metadata for Alembic downgrade generation" )
150+
151+ return CreateBM25IndexOp (
152+ index_name = self .index_name ,
153+ table_name = self .table_name ,
154+ expressions = self .expressions ,
155+ key_field = self .key_field ,
156+ table_schema = self .schema ,
157+ where = self .where ,
158+ )
110159
111160
112161@Operations .implementation_for (DropBM25IndexOp )
@@ -120,6 +169,14 @@ def _render_drop_bm25_index_op(autogen_context, op: DropBM25IndexOp) -> str:
120169 parts = [repr (op .index_name ), f"if_exists={ op .if_exists !r} " ]
121170 if op .schema is not None :
122171 parts .append (f"schema={ op .schema !r} " )
172+ if op .table_name is not None :
173+ parts .append (f"table_name={ op .table_name !r} " )
174+ if op .expressions is not None :
175+ parts .append (f"expressions={ op .expressions !r} " )
176+ if op .key_field is not None :
177+ parts .append (f"key_field={ op .key_field !r} " )
178+ if op .where is not None :
179+ parts .append (f"where={ op .where !r} " )
123180 return f"op.drop_bm25_index({ ', ' .join (parts )} )"
124181
125182
@@ -204,12 +261,28 @@ def _render_bm25_expression(expr: ClauseElement) -> str:
204261 return str (expr .compile (dialect = postgresql .dialect (), compile_kwargs = {"literal_binds" : True })) # type: ignore[no-untyped-call]
205262
206263
207- def _strip_relation_qualifiers (expr : str , table_name : str ) -> str :
208- # SQLAlchemy may render column refs as `table.col` in metadata compilation;
209- # CREATE INDEX field lists should be table-local expressions.
210- stripped = expr .replace (f'"{ table_name } ".' , "" )
211- stripped = stripped .replace (f"{ table_name } ." , "" )
212- return stripped
264+ def _strip_relation_qualifiers (expr : str , table_name : str , schema_name : str | None ) -> str :
265+ # SQLAlchemy may render column refs as `table.col` or `schema.col` in metadata
266+ # compilation; CREATE INDEX field lists should be table-local expressions.
267+ qualifier_patterns : list [str ] = []
268+ for name in (schema_name , table_name ):
269+ if not name :
270+ continue
271+ qualifier_patterns .append (re .escape (_quote_ident (name )))
272+ qualifier_patterns .append (rf"(?<![\w\"]){ re .escape (name )} (?![\w\"])" )
273+
274+ if not qualifier_patterns :
275+ return expr
276+
277+ qualifier_re = re .compile (rf"('(?:''|[^'])*')|(?P<qualifier>(?:{ '|' .join (qualifier_patterns )} ))\." )
278+
279+ def _strip_match (match : re .Match [str ]) -> str :
280+ literal = match .group (1 )
281+ if literal is not None :
282+ return literal
283+ return ""
284+
285+ return qualifier_re .sub (_strip_match , expr )
213286
214287
215288def _normalize_bm25_expression (expr : str ) -> str :
@@ -312,8 +385,9 @@ def _render_where_from_index(index) -> str | None:
312385 )
313386 ),
314387 index .table .name ,
388+ index .table .schema ,
315389 )
316- return _strip_relation_qualifiers (str (where_clause ), index .table .name )
390+ return _strip_relation_qualifiers (str (where_clause ), index .table .name , index . table . schema )
317391
318392
319393def _suppress_standard_bm25_ops (upgrade_ops , bm25_names : set [str ]) -> None :
@@ -363,15 +437,27 @@ def _compare_bm25_indexes(autogen_context, upgrade_ops, schemas) -> PriorityDisp
363437 # Emit drop ops for indexes present in DB but absent from MetaData.
364438 for key in db_bm25 :
365439 if key not in meta_bm25 :
366- upgrade_ops .ops .append (DropBM25IndexOp (index_name = key [1 ], if_exists = True , schema = key [0 ]))
440+ db = db_bm25 [key ]
441+ upgrade_ops .ops .append (
442+ DropBM25IndexOp (
443+ index_name = key [1 ],
444+ if_exists = True ,
445+ schema = key [0 ],
446+ table_name = db ["table_name" ],
447+ expressions = db ["expressions" ],
448+ key_field = db ["key_field" ],
449+ where = db .get ("where" ),
450+ )
451+ )
367452
368453 # Emit create ops for indexes present in MetaData but absent from DB.
369454 # Also re-create indexes whose expression list, key_field, or WHERE clause differs from the DB.
370455 for key , index in meta_bm25 .items ():
371456 with_opts = index .dialect_options ["postgresql" ].get ("with" ) or {}
372457 key_field = with_opts .get ("key_field" , "" )
373458 expressions = [
374- _strip_relation_qualifiers (_render_bm25_expression (expr ), index .table .name ) for expr in index .expressions
459+ _strip_relation_qualifiers (_render_bm25_expression (expr ), index .table .name , index .table .schema )
460+ for expr in index .expressions
375461 ]
376462 meta_where = _render_where_from_index (index )
377463 create_op = CreateBM25IndexOp (
@@ -393,7 +479,17 @@ def _compare_bm25_indexes(autogen_context, upgrade_ops, schemas) -> PriorityDisp
393479 key_field_changed = db ["key_field" ] != key_field
394480 where_changed = _normalize_where (db .get ("where" )) != _normalize_where (meta_where )
395481 if expressions_changed or key_field_changed or where_changed :
396- upgrade_ops .ops .append (DropBM25IndexOp (index_name = key [1 ], if_exists = True , schema = key [0 ]))
482+ upgrade_ops .ops .append (
483+ DropBM25IndexOp (
484+ index_name = key [1 ],
485+ if_exists = True ,
486+ schema = key [0 ],
487+ table_name = db ["table_name" ],
488+ expressions = db ["expressions" ],
489+ key_field = db ["key_field" ],
490+ where = db .get ("where" ),
491+ )
492+ )
397493 upgrade_ops .ops .append (create_op )
398494
399495 return PriorityDispatchResult .CONTINUE
0 commit comments