From f91a04621ea8d7a657ff755645d823026257e898 Mon Sep 17 00:00:00 2001 From: Pankrat Date: Sat, 30 Jan 2016 21:46:28 +0100 Subject: Fixed #25833 -- Added support for non-atomic migrations. Added the Migration.atomic attribute which can be set to False for non-atomic migrations. --- tests/migrations/test_commands.py | 13 +++++++++ tests/migrations/test_executor.py | 27 ++++++++++++++++++ .../0001_initial.py | 25 +++++++++++++++++ .../test_migrations_atomic_operation/__init__.py | 0 .../test_migrations_non_atomic/0001_initial.py | 32 ++++++++++++++++++++++ .../test_migrations_non_atomic/__init__.py | 0 6 files changed, 97 insertions(+) create mode 100644 tests/migrations/test_migrations_atomic_operation/0001_initial.py create mode 100644 tests/migrations/test_migrations_atomic_operation/__init__.py create mode 100644 tests/migrations/test_migrations_non_atomic/0001_initial.py create mode 100644 tests/migrations/test_migrations_non_atomic/__init__.py (limited to 'tests') diff --git a/tests/migrations/test_commands.py b/tests/migrations/test_commands.py index 4eb96de2e9..68fa658d76 100644 --- a/tests/migrations/test_commands.py +++ b/tests/migrations/test_commands.py @@ -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", diff --git a/tests/migrations/test_executor.py b/tests/migrations/test_executor.py index 1bbed48e3c..b51e7be90e 100644 --- a/tests/migrations/test_executor.py +++ b/tests/migrations/test_executor.py @@ -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", diff --git a/tests/migrations/test_migrations_atomic_operation/0001_initial.py b/tests/migrations/test_migrations_atomic_operation/0001_initial.py new file mode 100644 index 0000000000..11bd2f7ce2 --- /dev/null +++ b/tests/migrations/test_migrations_atomic_operation/0001_initial.py @@ -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), + ] diff --git a/tests/migrations/test_migrations_atomic_operation/__init__.py b/tests/migrations/test_migrations_atomic_operation/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/migrations/test_migrations_non_atomic/0001_initial.py b/tests/migrations/test_migrations_non_atomic/0001_initial.py new file mode 100644 index 0000000000..47a03a16f9 --- /dev/null +++ b/tests/migrations/test_migrations_non_atomic/0001_initial.py @@ -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)), + ], + ), + ] diff --git a/tests/migrations/test_migrations_non_atomic/__init__.py b/tests/migrations/test_migrations_non_atomic/__init__.py new file mode 100644 index 0000000000..e69de29bb2 -- cgit v1.3