@@ -534,7 +534,85 @@ def test_autogenerate_detects_changed_partial_predicate(engine):
534534
535535
536536# ---------------------------------------------------------------------------
537- # 2d. Autogenerate round-trip converges (no diff after applying ops)
537+ # 2d. Matching partial indexes should not emit drift
538+ # ---------------------------------------------------------------------------
539+
540+ def test_autogenerate_no_op_when_partial_indexes_match (engine ):
541+ _setup_autogen_table (engine , with_index = False )
542+ try :
543+ with engine .begin () as conn :
544+ conn .execute (text (
545+ f'CREATE INDEX "{ _AG_IDX } " ON "{ _AG_TABLE } " '
546+ f"USING bm25 (id, description) WITH (key_field='id') WHERE (id > 2)"
547+ ))
548+
549+ m = MetaData ()
550+ t = Table (_AG_TABLE , m , Column ("id" , Integer , primary_key = True ), Column ("description" , Text ))
551+ from sqlalchemy .schema import Index
552+
553+ Index (
554+ _AG_IDX ,
555+ BM25Field (t .c .id ),
556+ BM25Field (t .c .description ),
557+ postgresql_using = "bm25" ,
558+ postgresql_with = {"key_field" : "id" },
559+ postgresql_where = t .c .id > 2 ,
560+ )
561+
562+ upgrade_ops = _run_comparator (engine , m )
563+ our_ops = [
564+ op for op in upgrade_ops .ops
565+ if getattr (op , "index_name" , None ) == _AG_IDX
566+ ]
567+ assert not our_ops , f"Expected no ops for matching partial index, got: { our_ops } "
568+ finally :
569+ _teardown_autogen_table (engine )
570+
571+
572+ # ---------------------------------------------------------------------------
573+ # 2e. String-literal case drift in partial predicates should be detected
574+ # ---------------------------------------------------------------------------
575+
576+ def test_autogenerate_detects_changed_partial_string_literal_case (engine ):
577+ _setup_autogen_table (engine , with_index = False )
578+ try :
579+ with engine .begin () as conn :
580+ conn .execute (text (
581+ f'CREATE INDEX "{ _AG_IDX } " ON "{ _AG_TABLE } " '
582+ f"USING bm25 (id, description) WITH (key_field='id') "
583+ f"WHERE (description = 'ACTIVE')"
584+ ))
585+
586+ m = MetaData ()
587+ t = Table (_AG_TABLE , m , Column ("id" , Integer , primary_key = True ), Column ("description" , Text ))
588+ from sqlalchemy .schema import Index
589+
590+ Index (
591+ _AG_IDX ,
592+ BM25Field (t .c .id ),
593+ BM25Field (t .c .description ),
594+ postgresql_using = "bm25" ,
595+ postgresql_with = {"key_field" : "id" },
596+ postgresql_where = "description = 'active'::text" ,
597+ )
598+
599+ upgrade_ops = _run_comparator (engine , m )
600+ drop_ops = [
601+ op for op in upgrade_ops .ops
602+ if isinstance (op , pdb_alembic .DropBM25IndexOp ) and op .index_name == _AG_IDX
603+ ]
604+ create_ops = [
605+ op for op in upgrade_ops .ops
606+ if isinstance (op , pdb_alembic .CreateBM25IndexOp ) and op .index_name == _AG_IDX
607+ ]
608+ assert len (drop_ops ) == 1 , "Expected DropBM25IndexOp for string-literal case change"
609+ assert len (create_ops ) == 1 , "Expected CreateBM25IndexOp for string-literal case change"
610+ finally :
611+ _teardown_autogen_table (engine )
612+
613+
614+ # ---------------------------------------------------------------------------
615+ # 2f. Autogenerate round-trip converges (no diff after applying ops)
538616# ---------------------------------------------------------------------------
539617
540618def test_autogenerate_round_trip_converges (engine ):
@@ -553,7 +631,7 @@ def test_autogenerate_round_trip_converges(engine):
553631
554632
555633# ---------------------------------------------------------------------------
556- # 2e . Full lifecycle: create → query → reindex → query → drop
634+ # 2g . Full lifecycle: create → query → reindex → query → drop
557635# ---------------------------------------------------------------------------
558636
559637_LIFECYCLE_TABLE = "alembic_lifecycle_test"
@@ -597,7 +675,7 @@ def test_alembic_create_reindex_drop_is_queryable(engine):
597675
598676
599677# ---------------------------------------------------------------------------
600- # 2f . Reindex concurrently with AUTOCOMMIT
678+ # 2h . Reindex concurrently with AUTOCOMMIT
601679# ---------------------------------------------------------------------------
602680
603681_CONC_TABLE = "alembic_conc_test"
@@ -638,7 +716,7 @@ def test_alembic_reindex_concurrently_autocommit(engine):
638716
639717
640718# ---------------------------------------------------------------------------
641- # 2g . Autogenerate detects changed key_field
719+ # 2i . Autogenerate detects changed key_field
642720# ---------------------------------------------------------------------------
643721
644722def test_autogenerate_detects_changed_key_field (engine ):
@@ -651,14 +729,15 @@ def test_autogenerate_detects_changed_key_field(engine):
651729 f"USING bm25 (id, description) WITH (key_field='id')"
652730 ))
653731
654- # MetaData declares key_field='description' (different)
732+ # MetaData declares key_field='description' (different) but keeps the
733+ # expression list identical so only key_field drift is under test.
655734 m = MetaData ()
656735 t = Table (_AG_TABLE , m , Column ("id" , Integer , primary_key = True ), Column ("description" , Text ))
657736 from sqlalchemy .schema import Index
658737 Index (
659738 _AG_IDX ,
660- BM25Field (t .c .description ),
661739 BM25Field (t .c .id ),
740+ BM25Field (t .c .description ),
662741 postgresql_using = "bm25" ,
663742 postgresql_with = {"key_field" : "description" },
664743 )
@@ -674,7 +753,7 @@ def test_autogenerate_detects_changed_key_field(engine):
674753
675754
676755# ---------------------------------------------------------------------------
677- # 2h . Expression index lifecycle (with tokenizer)
756+ # 2j . Expression index lifecycle (with tokenizer)
678757# ---------------------------------------------------------------------------
679758
680759_EXPR_TABLE = "alembic_expr_test"
@@ -719,7 +798,7 @@ def test_alembic_expression_index_lifecycle(engine):
719798
720799
721800# ---------------------------------------------------------------------------
722- # 2i . Multi-tokenizer expression lifecycle
801+ # 2k . Multi-tokenizer expression lifecycle
723802# ---------------------------------------------------------------------------
724803
725804_MULTI_TABLE = "alembic_multi_tok_test"
0 commit comments