[3.2.x] Fixed #32374 -- Stopped recording migration application before deferred SQL.

Migrations cannot be recorded in the same transaction as its associated
DDL operations when some of it is deferred until the schema editor
context exits.

Regression in c86a3d80a2.

Backport of 0c42cdf0d2 from master
This commit is contained in:
Simon Charette 2021-01-20 19:00:36 -05:00 committed by Mariusz Felisiak
parent 900b2ce92b
commit 6520ce5251
2 changed files with 30 additions and 2 deletions

View File

@ -225,6 +225,7 @@ class MigrationExecutor:
# Alright, do it normally # Alright, do it normally
with self.connection.schema_editor(atomic=migration.atomic) as schema_editor: with self.connection.schema_editor(atomic=migration.atomic) as schema_editor:
state = migration.apply(state, schema_editor) state = migration.apply(state, schema_editor)
if not schema_editor.deferred_sql:
self.record_migration(migration) self.record_migration(migration)
migration_recorded = True migration_recorded = True
if not migration_recorded: if not migration_recorded:

View File

@ -684,6 +684,33 @@ class ExecutorTests(MigrationTestBase):
) )
self.assertTableNotExists('record_migration_model') self.assertTableNotExists('record_migration_model')
def test_migrations_not_applied_on_deferred_sql_failure(self):
"""Migrations are not recorded if deferred SQL application fails."""
class DeferredSQL:
def __str__(self):
raise DatabaseError('Failed to apply deferred SQL')
class Migration(migrations.Migration):
atomic = False
def apply(self, project_state, schema_editor, collect_sql=False):
schema_editor.deferred_sql.append(DeferredSQL())
executor = MigrationExecutor(connection)
with self.assertRaisesMessage(DatabaseError, 'Failed to apply deferred SQL'):
executor.apply_migration(
ProjectState(),
Migration('0001_initial', 'deferred_sql'),
)
# The migration isn't recorded as applied since it failed.
migration_recorder = MigrationRecorder(connection)
self.assertIs(
migration_recorder.migration_qs.filter(
app='deferred_sql', name='0001_initial',
).exists(),
False,
)
class FakeLoader: class FakeLoader:
def __init__(self, graph, applied): def __init__(self, graph, applied):