summaryrefslogtreecommitdiff
path: root/tests/transactions
diff options
context:
space:
mode:
authorFlorian Apolloner <florian@apolloner.eu>2018-09-27 09:45:10 +0200
committerFlorian Apolloner <apollo13@users.noreply.github.com>2018-10-17 12:19:02 +0200
commitbc7dd8490b882b2cefdc7faf431dc64c532b79c9 (patch)
tree5b5a10b8a05034a2d2c41870f90f7df7fc2c94a1 /tests/transactions
parent38f3de86bd0bfa4c9b57db1237fa55e9fa88bc6e (diff)
Fixed #21171 -- Avoided starting a transaction when a single (or atomic queries) are executed.
Checked the following locations: * Model.save(): If there are parents involved, take the safe way and use transactions since this should be an all or nothing operation. If the model has no parents: * Signals are executed before and after the previous existing transaction -- they were never been part of the transaction. * if `force_insert` is set then only one query is executed -> atomic by definition and no transaction needed. * same applies to `force_update`. * If a primary key is set and no `force_*` is set Django will try an UPDATE and if that returns zero rows it tries an INSERT. The first case is completly save (single query). In the second case a transaction should not produce different results since the update query is basically a no-op then (might miss something though). * QuerySet.update(): no signals issued, single query -> no transaction needed. * Model/Collector.delete(): This one is fun due to the fact that is does many things at once. Most importantly though: It does send signals as part of the transaction, so for maximum backwards compatibility we need to be conservative. To ensure maximum compatibility the transaction here is removed only if the following holds true: * A single instance is being deleted. * There are no signal handlers attached to that instance. * There are no deletions/updates to cascade. * There are no parents which also need deletion.
Diffstat (limited to 'tests/transactions')
-rw-r--r--tests/transactions/tests.py48
1 files changed, 47 insertions, 1 deletions
diff --git a/tests/transactions/tests.py b/tests/transactions/tests.py
index 637a20e7e0..af3416aeaa 100644
--- a/tests/transactions/tests.py
+++ b/tests/transactions/tests.py
@@ -399,7 +399,7 @@ class AtomicMySQLTests(TransactionTestCase):
class AtomicMiscTests(TransactionTestCase):
- available_apps = []
+ available_apps = ['transactions']
def test_wrap_callable_instance(self):
"""#20028 -- Atomic must support wrapping callable instances."""
@@ -433,6 +433,52 @@ class AtomicMiscTests(TransactionTestCase):
# This is expected to fail because the savepoint no longer exists.
connection.savepoint_rollback(sid)
+ def test_mark_for_rollback_on_error_in_transaction(self):
+ with transaction.atomic(savepoint=False):
+
+ # Swallow the intentional error raised.
+ with self.assertRaisesMessage(Exception, "Oops"):
+
+ # Wrap in `mark_for_rollback_on_error` to check if the transaction is marked broken.
+ with transaction.mark_for_rollback_on_error():
+
+ # Ensure that we are still in a good state.
+ self.assertFalse(transaction.get_rollback())
+
+ raise Exception("Oops")
+
+ # Ensure that `mark_for_rollback_on_error` marked the transaction as broken …
+ self.assertTrue(transaction.get_rollback())
+
+ # … and further queries fail.
+ msg = "You can't execute queries until the end of the 'atomic' block."
+ with self.assertRaisesMessage(transaction.TransactionManagementError, msg):
+ Reporter.objects.create()
+
+ # Transaction errors are reset at the end of an transaction, so this should just work.
+ Reporter.objects.create()
+
+ def test_mark_for_rollback_on_error_in_autocommit(self):
+ self.assertTrue(transaction.get_autocommit())
+
+ # Swallow the intentional error raised.
+ with self.assertRaisesMessage(Exception, "Oops"):
+
+ # Wrap in `mark_for_rollback_on_error` to check if the transaction is marked broken.
+ with transaction.mark_for_rollback_on_error():
+
+ # Ensure that we are still in a good state.
+ self.assertFalse(transaction.get_connection().needs_rollback)
+
+ raise Exception("Oops")
+
+ # Ensure that `mark_for_rollback_on_error` did not mark the transaction
+ # as broken, since we are in autocommit mode …
+ self.assertFalse(transaction.get_connection().needs_rollback)
+
+ # … and further queries work nicely.
+ Reporter.objects.create()
+
@skipIfDBFeature('autocommits_when_autocommit_is_off')
class NonAutocommitTests(TransactionTestCase):