diff --git a/django/db/backends/base/schema.py b/django/db/backends/base/schema.py index af30f7f7fc00..73a62cb18a9e 100644 --- a/django/db/backends/base/schema.py +++ b/django/db/backends/base/schema.py @@ -658,6 +658,25 @@ def _delete_composed_index(self, model, fields, constraint_kwargs, sql): } meta_index_names = {constraint.name for constraint in model._meta.indexes} columns = [model._meta.get_field(field).column for field in fields] + + # Check if the constraint is still in deferred_sql. This happens when + # CreateModel with unique_together or index_together is followed by + # AlterUniqueTogether/AlterIndexTogether in the same migration. + is_unique_constraint = constraint_kwargs.get("unique") is True + table = model._meta.db_table + if is_unique_constraint: + for deferred in list(self.deferred_sql): + if ( + isinstance(deferred, Statement) + and deferred.references_table(table) + and all( + deferred.references_column(table, column) for column in columns + ) + and deferred.parts["columns"].columns == columns + ): + self.deferred_sql.remove(deferred) + return + constraint_names = self._constraint_names( model, columns, @@ -665,7 +684,7 @@ def _delete_composed_index(self, model, fields, constraint_kwargs, sql): **constraint_kwargs, ) if ( - constraint_kwargs.get("unique") is True + is_unique_constraint and constraint_names and self.connection.features.allows_multiple_constraints_on_same_fields ): diff --git a/tests/migrations/test_operations.py b/tests/migrations/test_operations.py index 49bda86d1f8f..cdcb41a199e9 100644 --- a/tests/migrations/test_operations.py +++ b/tests/migrations/test_operations.py @@ -3823,6 +3823,62 @@ def test_alter_unique_together_remove(self): operation.describe(), "Alter unique_together for Pony (0 constraint(s))" ) + def test_alter_unique_together_deferred(self): + """ + AlterUniqueTogether handles deferred SQL constraints from previous + operations. Regression test for #31317. + """ + app_label = "test_aluntod" + self.apply_operations( + app_label, + ProjectState(), + operations=[ + migrations.CreateModel( + "Pony", + fields=[ + ("id", models.AutoField(primary_key=True)), + ("pink", models.IntegerField(default=3)), + ("weight", models.FloatField()), + ], + options={"unique_together": {("pink",)}}, + ), + migrations.AlterUniqueTogether( + name="Pony", + unique_together={("pink", "weight")}, + ), + ], + ) + + table_name = f"{app_label}_pony" + self.assertUniqueConstraintExists(table_name, ("pink", "weight"), value=True) + self.assertUniqueConstraintExists(table_name, ("pink",), value=False) + + def test_alter_unique_together_deferred_overlapping_columns(self): + app_label = "test_aluntodoc" + self.apply_operations( + app_label, + ProjectState(), + operations=[ + migrations.CreateModel( + "Pony", + fields=[ + ("id", models.AutoField(primary_key=True)), + ("pink", models.IntegerField(default=3)), + ("weight", models.FloatField()), + ], + options={"unique_together": [("pink", "weight"), ("pink",)]}, + ), + migrations.AlterUniqueTogether( + name="Pony", + unique_together={("pink", "weight")}, + ), + ], + ) + + table_name = f"{app_label}_pony" + self.assertUniqueConstraintExists(table_name, ("pink", "weight"), value=True) + self.assertUniqueConstraintExists(table_name, ("pink",), value=False) + @skipUnlessDBFeature("allows_multiple_constraints_on_same_fields") def test_remove_unique_together_on_pk_field(self): app_label = "test_rutopkf" @@ -4414,6 +4470,35 @@ def test_alter_index_together_remove_with_unique_together(self): self.assertIndexNotExists(table_name, ["pink", "weight"]) self.assertUniqueConstraintExists(table_name, ["pink", "weight"]) + def test_alter_index_together_deferred_overlapping_columns(self): + app_label = "test_alintodoc" + self.apply_operations( + app_label, + ProjectState(), + operations=[ + migrations.CreateModel( + "Pony", + fields=[ + ("id", models.AutoField(primary_key=True)), + ("pink", models.IntegerField(default=3)), + ("weight", models.FloatField()), + ], + options={ + "unique_together": [("pink",)], + "index_together": [("pink",)], + }, + ), + migrations.AlterIndexTogether( + name="Pony", + index_together=set(), + ), + ], + ) + + table_name = f"{app_label}_pony" + self.assertIndexNotExists(table_name, ["pink"]) + self.assertUniqueConstraintExists(table_name, ("pink",), value=True) + def test_add_constraint(self): project_state = self.set_up_test_model("test_addconstraint") gt_check = models.Q(pink__gt=2)