• 
      

    Fix index introspection on PostgreSQL 18.

    Review Request #14923 — Created March 17, 2026 and submitted

    Information

    Django Evolution
    master

    Reviewers

    PostgreSQL 18 stores NOT NULL constraints in pg_constraint (with
    contype='n'). Django's get_constraints() includes these in its
    results, but with all type flags (index, unique, primary_key, check,
    foreign_key) set to False. get_constraints_for_table() was passing
    through all constraint entries unconditionally, causing these NOT NULL
    constraints to be tracked as regular non-unique indexes in
    DatabaseState (e.g., "tests_testmodel_int_field1_not_null").

    This caused two categories of failures:

    1. ChangeField operations that modified db_index or unique would
      find unexpected NOT NULL indexes in the database state, causing
      assertion failures.

    2. ChangeMeta(indexes) operations would see NOT NULL constraint
      entries instead of the actual Django-managed indexes, generating
      incorrect DROP/CREATE INDEX SQL.

    The fix filters get_constraints_for_table() to only include entries
    where index, unique, or primary_key is True. This excludes NOT NULL
    constraints, check constraints, and foreign key constraint entries --
    none of which should be tracked as indexes in DatabaseState. (Foreign
    key fields still have their actual indexes captured via separate
    index=True entries, and check constraints are handled through Django's
    Meta.constraints system.)

    Ran dbtests-minmax

    Summary ID
    Fix index introspection on PostgreSQL 18.
    PostgreSQL 18 stores `NOT NULL` constraints in `pg_constraint` (with `contype='n'`). Django's `get_constraints()` includes these in its results, but with all type flags (index, unique, primary_key, check, foreign_key) set to False. `get_constraints_for_table()` was passing through all constraint entries unconditionally, causing these NOT NULL constraints to be tracked as regular non-unique indexes in `DatabaseState` (e.g., "tests_testmodel_int_field1_not_null"). This caused two categories of failures: 1. `ChangeField` operations that modified `db_index` or `unique` would find unexpected `NOT NULL` indexes in the database state, causing assertion failures. 2. `ChangeMeta(indexes)` operations would see `NOT NULL` constraint entries instead of the actual Django-managed indexes, generating incorrect `DROP/CREATE INDEX SQL`. The fix filters `get_constraints_for_table()` to only include entries where index, unique, or primary_key is True. This excludes NOT NULL constraints, check constraints, and foreign key constraint entries -- none of which should be tracked as indexes in DatabaseState. (Foreign key fields still have their actual indexes captured via separate `index=True` entries, and check constraints are handled through Django's `Meta.constraints` system.) Testing Done: Ran dbtests-minmax
    wyzwxmuzpkxnwrpzokqqzpoosrtoznnm
    Description From Last Updated

    We're ultimately going to want to look up other details on constraints, so I think this method is most helpful …

    chipx86 chipx86

    This will need unit tests for DatabaseState.rescan_tables with the different types of checks, and the ChangeField and ChangeMeta failures.

    chipx86 chipx86
    chipx86
    1. 
        
    2. Show all issues

      We're ultimately going to want to look up other details on constraints, so I think this method is most helpful as an unfiltered wrapper around introspection.get_constraints() (even once the legacy DB implementation is removed, this takes care of cursor setup and normalization).

      I think the approach I'd prefer here is to include the other fields we need in the results, and then filter at the call site that handles indexing. That keeps the filtering where it's needed and frees us up to perform lookups for things like Check Constraints and primary key lookup later.

    3. Show all issues

      This will need unit tests for DatabaseState.rescan_tables with the different types of checks, and the ChangeField and ChangeMeta failures.

    4. 
        
    david
    chipx86
    1. Ship It!
    2. 
        
    david
    Review request changed
    Status:
    Completed
    Change Summary:
    Pushed to release-3.x (128a72d)