• 
      

    Fix index introspection on PostgreSQL 18.

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

    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
    Checks run (2 succeeded)
    flake8 passed.
    JSHint passed.