Fixed #25833 -- Added support for non-atomic migrations.

Added the Migration.atomic attribute which can be set to False
for non-atomic migrations.
This commit is contained in:
Pankrat 2016-01-30 21:46:28 +01:00 committed by Tim Graham
parent 0edb8a146f
commit f91a04621e
14 changed files with 181 additions and 16 deletions

View file

@ -362,6 +362,19 @@ class MigrateTests(MigrationTestBase):
# Cleanup by unmigrating everything
call_command("migrate", "migrations", "zero", verbosity=0)
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_non_atomic"})
def test_sqlmigrate_for_non_atomic_migration(self):
"""
Transaction wrappers aren't shown for non-atomic migrations.
"""
out = six.StringIO()
call_command("sqlmigrate", "migrations", "0001", stdout=out)
output = out.getvalue().lower()
queries = [q.strip() for q in output.splitlines()]
if connection.ops.start_transaction_sql():
self.assertNotIn(connection.ops.start_transaction_sql().lower(), queries)
self.assertNotIn(connection.ops.end_transaction_sql().lower(), queries)
@override_settings(
INSTALLED_APPS=[
"migrations.migrations_test_apps.migrated_app",

View file

@ -100,6 +100,33 @@ class ExecutorTests(MigrationTestBase):
self.assertTableNotExists("migrations_author")
self.assertTableNotExists("migrations_book")
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_non_atomic"})
def test_non_atomic_migration(self):
"""
Applying a non-atomic migration works as expected.
"""
executor = MigrationExecutor(connection)
with self.assertRaisesMessage(RuntimeError, "Abort migration"):
executor.migrate([("migrations", "0001_initial")])
self.assertTableExists("migrations_publisher")
migrations_apps = executor.loader.project_state(("migrations", "0001_initial")).apps
Publisher = migrations_apps.get_model("migrations", "Publisher")
self.assertTrue(Publisher.objects.exists())
self.assertTableNotExists("migrations_book")
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_atomic_operation"})
def test_atomic_operation_in_non_atomic_migration(self):
"""
An atomic operation is properly rolled back inside a non-atomic
migration.
"""
executor = MigrationExecutor(connection)
with self.assertRaisesMessage(RuntimeError, "Abort migration"):
executor.migrate([("migrations", "0001_initial")])
migrations_apps = executor.loader.project_state(("migrations", "0001_initial")).apps
Editor = migrations_apps.get_model("migrations", "Editor")
self.assertFalse(Editor.objects.exists())
@override_settings(MIGRATION_MODULES={
"migrations": "migrations.test_migrations",
"migrations2": "migrations2.test_migrations_2",

View file

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
def raise_error(apps, schema_editor):
# Test atomic operation in non-atomic migration is wrapped in transaction
Editor = apps.get_model('migrations', 'Editor')
Editor.objects.create(name='Test Editor')
raise RuntimeError('Abort migration')
class Migration(migrations.Migration):
atomic = False
operations = [
migrations.CreateModel(
"Editor",
[
("name", models.CharField(primary_key=True, max_length=255)),
],
),
migrations.RunPython(raise_error, atomic=True),
]

View file

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
def raise_error(apps, schema_editor):
# Test operation in non-atomic migration is not wrapped in transaction
Publisher = apps.get_model('migrations', 'Publisher')
Publisher.objects.create(name='Test Publisher')
raise RuntimeError('Abort migration')
class Migration(migrations.Migration):
atomic = False
operations = [
migrations.CreateModel(
"Publisher",
[
("name", models.CharField(primary_key=True, max_length=255)),
],
),
migrations.RunPython(raise_error),
migrations.CreateModel(
"Book",
[
("title", models.CharField(primary_key=True, max_length=255)),
("publisher", models.ForeignKey("migrations.Publisher", models.SET_NULL, null=True)),
],
),
]