Skip to content

Commit d488078

Browse files
authored
Merge pull request #84 from kraken-tech/s-cooper18/fix/more-useful-constraint-deletion-output
Bugfix: drop unique constraint sqlmigrate is noop
2 parents 47e909f + 1a907a9 commit d488078

File tree

3 files changed

+79
-4
lines changed

3 files changed

+79
-4
lines changed

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@ Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to
88

99
## [Unreleased]
1010

11-
_No notable unreleased changes_
11+
### Fixed
12+
13+
- `SaferRemoveUniqueConstraint` now produces the best-effort plan for a migration that would be
14+
performed if the constraint wanting to be removed does in fact exist when running `sqlmigrate`.
15+
Previously, this would act as if the constraint did not exist is using `sqlmigrate`.
16+
- `SaferAddUniqueConstraint` now produces a backwards plan as if the constraint has already been
17+
created when running `sqlmigrate`.
1218

1319
## [0.1.19] - 2025-04-04
1420

src/django_pg_migration_tools/operations.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,7 @@ def drop_unique_constraint(
506506
)
507507
return
508508

509-
if not self._constraint_exists(schema_editor, constraint):
509+
if not self._constraint_exists(schema_editor, constraint, collect_default=True):
510510
# Nothing to delete.
511511
return
512512

@@ -591,7 +591,9 @@ def _can_create_constraint(
591591
constraint: models.UniqueConstraint,
592592
raise_if_exists: bool,
593593
) -> bool:
594-
constraint_exists = self._constraint_exists(schema_editor, constraint)
594+
constraint_exists = self._constraint_exists(
595+
schema_editor, constraint, collect_default=False
596+
)
595597
if raise_if_exists and constraint_exists:
596598
raise ConstraintAlreadyExists(
597599
f"Cannot create a constraint with the name "
@@ -619,7 +621,7 @@ def _constraint_exists(
619621
self,
620622
schema_editor: base_schema.BaseDatabaseSchemaEditor,
621623
constraint: models.UniqueConstraint | models.CheckConstraint,
622-
collect_default: bool = False,
624+
collect_default: bool,
623625
) -> bool:
624626
return _run_introspection_query(
625627
schema_editor,

tests/django_pg_migration_tools/test_operations.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -910,6 +910,41 @@ def test_when_collecting_only(self):
910910
== 'ALTER TABLE "example_app_intmodel" ADD CONSTRAINT "unique_int_field" UNIQUE USING INDEX "unique_int_field";'
911911
)
912912

913+
@pytest.mark.django_db(transaction=True)
914+
def test_when_collecting_only_reversed(self):
915+
# Prove that the constraint exists before the operation removes it.
916+
with connection.cursor() as cursor:
917+
cursor.execute(
918+
psycopg_sql.SQL(operations.ConstraintQueries.CHECK_EXISTING_CONSTRAINT)
919+
.format(constraint_name=psycopg_sql.Literal("unique_char_field"))
920+
.as_string(cursor.connection)
921+
)
922+
assert cursor.fetchone()
923+
924+
project_state = ProjectState()
925+
project_state.add_model(ModelState.from_model(CharModel))
926+
new_state = project_state.clone()
927+
928+
operation = operations.SaferAddUniqueConstraint(
929+
model_name="charmodel",
930+
constraint=UniqueConstraint(
931+
fields=("char_field",),
932+
name="unique_char_field",
933+
),
934+
)
935+
936+
with connection.schema_editor(atomic=False, collect_sql=True) as editor:
937+
with utils.CaptureQueriesContext(connection) as queries:
938+
operation.database_backwards(
939+
self.app_label, editor, from_state=project_state, to_state=new_state
940+
)
941+
942+
assert len(editor.collected_sql) == 1
943+
assert len(queries) == 0
944+
assert editor.collected_sql[0] == (
945+
'ALTER TABLE "example_app_charmodel" DROP CONSTRAINT "unique_char_field";'
946+
)
947+
913948
# Disable the overall test transaction because a unique concurrent index
914949
# creation followed by a constraint addition cannot be triggered/tested
915950
# inside of a transaction.
@@ -1576,6 +1611,38 @@ def test_when_not_allowed_to_migrate_by_the_router(self):
15761611
# the router.
15771612
assert len(queries) == 0
15781613

1614+
@pytest.mark.django_db(transaction=True)
1615+
def test_when_collecting_only(self):
1616+
# Prove that the constraint exists before the operation removes it.
1617+
with connection.cursor() as cursor:
1618+
cursor.execute(
1619+
psycopg_sql.SQL(operations.ConstraintQueries.CHECK_EXISTING_CONSTRAINT)
1620+
.format(constraint_name=psycopg_sql.Literal("unique_char_field"))
1621+
.as_string(cursor.connection)
1622+
)
1623+
assert cursor.fetchone()
1624+
1625+
project_state = ProjectState()
1626+
project_state.add_model(ModelState.from_model(CharModel))
1627+
new_state = project_state.clone()
1628+
1629+
operation = operations.SaferRemoveUniqueConstraint(
1630+
model_name="charmodel",
1631+
name="unique_char_field",
1632+
)
1633+
1634+
with connection.schema_editor(atomic=False, collect_sql=True) as editor:
1635+
with utils.CaptureQueriesContext(connection) as queries:
1636+
operation.database_forwards(
1637+
self.app_label, editor, from_state=project_state, to_state=new_state
1638+
)
1639+
1640+
assert len(editor.collected_sql) == 1
1641+
assert len(queries) == 0
1642+
assert editor.collected_sql[0] == (
1643+
'ALTER TABLE "example_app_charmodel" DROP CONSTRAINT "unique_char_field";'
1644+
)
1645+
15791646
@pytest.mark.django_db(transaction=True)
15801647
def test_does_nothing_if_constraint_does_not_exist(self):
15811648
# Remove the constraint so that the migration becomes a noop.

0 commit comments

Comments
 (0)