• 
      

    Fix ChangeField failure with index+column type changes on MySQL.

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

    Information

    Django Evolution
    master

    Reviewers

    When a ChangeField mutation changes both the field type and
    unique/db_index attributes, MySQL's MODIFY COLUMN runs before the
    index is dropped. MySQL rejects this for TEXT or BLOB columns with
    the error "BLOB/TEXT column used in key specification without a key
    length".

    This happened because ChangeField.mutate() returns early after
    change_column_type() when change_column_type_sets_attrs is True
    (MySQL), skipping the change_column_attrs() call that handles index
    drops.

    change_column_type() now processes db_index/unique changes from
    new_attrs when change_column_type_sets_attrs is True, emitting
    index drops as pre-SQL (before the MODIFY) and index adds as post-SQL
    (after). The SQLite3 backend also calls the new helper to keep in-memory
    database state in sync.

    • Ran unit tests without the fix and verified that the new tests failed with
      the same issue I saw in the wild.
    • Ran unit tests with the fix and verified that it was now fixed.
    • Ran dbtests for mysql, sqlite, and postgresql.
    • Verified that this fixes the upgrade issue I was running into.
    Summary ID
    Fix ChangeField failure with index+column type changes on MySQL.
    When a `ChangeField` mutation changes both the field type and `unique`/`db_index` attributes, MySQL's `MODIFY COLUMN` runs before the index is dropped. MySQL rejects this for `TEXT` or `BLOB` columns with the error "BLOB/TEXT column used in key specification without a key length". This happened because `ChangeField.mutate()` returns early after `change_column_type()` when `change_column_type_sets_attrs` is `True` (MySQL), skipping the `change_column_attrs()` call that handles index drops. `change_column_type()` now processes `db_index`/`unique` changes from `new_attrs` when `change_column_type_sets_attrs` is `True`, emitting index drops as pre-SQL (before the `MODIFY`) and index adds as post-SQL (after). The SQLite3 backend also calls the new helper to keep in-memory database state in sync. Testing Done: - Ran unit tests without the fix and verified that the new tests failed with the same issue I saw in the wild. - Ran unit tests with the fix and verified that it was now fixed. - Ran dbtests for mysql, sqlite, and postgresql. - Verified that this fixes the upgrade issue I was running into.
    qnonyqrrwwqqowzwqpqlwpvvslzxpvum
    Description From Last Updated

    Testing doesn't say, but I assume this also fixed the issue found in the wild?

    chipx86 chipx86

    continuation line over-indented for visual indent Column: 48 Error code: E127

    reviewbot reviewbot

    Versions are listed in descending order.

    chipx86 chipx86

    Mind breaking the "This is necessary" into a new paragraph? There's a lot to process in this paragraph.

    chipx86 chipx86

    This function has the following possible states: Old column New column Result Unique Unique No change Unique Indexed Remove unique, …

    chipx86 chipx86

    Can we pull database_state out? This is used multiple times.

    chipx86 chipx86

    Can you use keyword arguments here?

    chipx86 chipx86

    Can you use keyword arguments here? I have no idea what True or None relate to.

    chipx86 chipx86
    Checks run (1 failed, 1 succeeded)
    flake8 failed.
    JSHint passed.

    flake8

    david
    chipx86
    1. This seems like the right approach. Went through the logic carefully and have some notes mostly to help ensure no future issues.

    2. Show all issues

      Testing doesn't say, but I assume this also fixed the issue found in the wild?

    3. django_evolution/db/common.py (Diff revision 2)
       
       
       
       
       
       
       
      Show all issues

      Versions are listed in descending order.

    4. django_evolution/db/common.py (Diff revision 2)
       
       
      Show all issues

      Mind breaking the "This is necessary" into a new paragraph? There's a lot to process in this paragraph.

    5. django_evolution/db/common.py (Diff revision 2)
       
       
      Show all issues

      This function has the following possible states:

      Old column New column Result
      Unique Unique No change
      Unique Indexed Remove unique, add index
      Unique Non-indexed/unique Remove unique
      Unique (missing index) Unique No change
      Unique (missing index) Indexed Create index
      Unique (missing index) Non-indexed/unique No change
      Indexed Unique Drop index, add unique
      Indexed Indexed No change
      Indexed Non-indexed/unique Drop index
      Non-indexed/unique Unique Add unique
      Non-indexed/unique Indexed Add index
      Non-indexed/unique Non-indexed/unique No change

      I think I have that right.

      Some of these are implicitly covered by other tests, but we should ensure we have tests covering all combinations of unique/indexed states so there are no surprises now or as code evolves.

      It would also be great to have a truth table like this in the code showing the expectations for future us.

      Notable above, the "Unique (missing index) -> Unique" does nothing, which is probably right, but perhaps worth calling out because it won't result in a database index being installed.

    6. django_evolution/db/common.py (Diff revision 2)
       
       
      Show all issues

      Can we pull database_state out? This is used multiple times.

    7. django_evolution/db/common.py (Diff revision 2)
       
       
      Show all issues

      Can you use keyword arguments here?

    8. django_evolution/db/common.py (Diff revision 2)
       
       
       
       
      Show all issues

      Can you use keyword arguments here? I have no idea what True or None relate to.

    9. 
        
    david
    Review request changed
    Testing Done:
       
    • Ran unit tests without the fix and verified that the new tests failed with
      the same issue I saw in the wild.
       
    • Ran unit tests with the fix and verified that it was now fixed.
       
    • Ran dbtests for mysql, sqlite, and postgresql.
      +
    • Verified that this fixes the upgrade issue I was running into.
    Commits:
    Summary ID
    Fix ChangeField failure with index+column type changes on MySQL.
    When a `ChangeField` mutation changes both the field type and `unique`/`db_index` attributes, MySQL's `MODIFY COLUMN` runs before the index is dropped. MySQL rejects this for `TEXT` or `BLOB` columns with the error "BLOB/TEXT column used in key specification without a key length". This happened because `ChangeField.mutate()` returns early after `change_column_type()` when `change_column_type_sets_attrs` is `True` (MySQL), skipping the `change_column_attrs()` call that handles index drops. `change_column_type()` now processes `db_index`/`unique` changes from `new_attrs` when `change_column_type_sets_attrs` is `True`, emitting index drops as pre-SQL (before the `MODIFY`) and index adds as post-SQL (after). The SQLite3 backend also calls the new helper to keep in-memory database state in sync. Testing Done: - Ran unit tests without the fix and verified that the new tests failed with the same issue I saw in the wild. - Ran unit tests with the fix and verified that it was now fixed. - Ran dbtests for mysql, sqlite, and postgresql.
    qnonyqrrwwqqowzwqpqlwpvvslzxpvum
    Fix ChangeField failure with index+column type changes on MySQL.
    When a `ChangeField` mutation changes both the field type and `unique`/`db_index` attributes, MySQL's `MODIFY COLUMN` runs before the index is dropped. MySQL rejects this for `TEXT` or `BLOB` columns with the error "BLOB/TEXT column used in key specification without a key length". This happened because `ChangeField.mutate()` returns early after `change_column_type()` when `change_column_type_sets_attrs` is `True` (MySQL), skipping the `change_column_attrs()` call that handles index drops. `change_column_type()` now processes `db_index`/`unique` changes from `new_attrs` when `change_column_type_sets_attrs` is `True`, emitting index drops as pre-SQL (before the `MODIFY`) and index adds as post-SQL (after). The SQLite3 backend also calls the new helper to keep in-memory database state in sync. Testing Done: - Ran unit tests without the fix and verified that the new tests failed with the same issue I saw in the wild. - Ran unit tests with the fix and verified that it was now fixed. - Ran dbtests for mysql, sqlite, and postgresql. - Verified that this fixes the upgrade issue I was running into.
    qnonyqrrwwqqowzwqpqlwpvvslzxpvum

    Checks run (2 succeeded)

    flake8 passed.
    JSHint passed.