Ticket #9964: 9964-r15054-bugfix.patch
File 9964-r15054-bugfix.patch, 14.6 KB (added by , 14 years ago) |
---|
-
django/db/backends/util.py
52 52 def __iter__(self): 53 53 return iter(self.cursor) 54 54 55 class CursorUseNotifyWrapper(object): 56 def __init__(self, cursor, on_use): 57 self.cursor = cursor 58 self.on_use = on_use 59 def __getattr__(self, attr): 60 self.on_use() 61 return getattr(self.cursor, attr) 62 def __iter__(self): 63 return iter(self.cursor) 64 55 65 ############################################### 56 66 # Converters from database (string) to Python # 57 67 ############################################### -
django/db/backends/__init__.py
22 22 self.settings_dict = settings_dict 23 23 self.alias = alias 24 24 self.use_debug_cursor = None 25 self.on_used = None 25 26 26 27 def __eq__(self, other): 27 28 return self.alias == other.alias … … 75 76 def cursor(self): 76 77 from django.conf import settings 77 78 cursor = self._cursor() 79 # Used by transaction mechanism to be notified that db was used 80 if self.on_used: 81 cursor = util.CursorUseNotifyWrapper(cursor, self.on_used) 78 82 if (self.use_debug_cursor or 79 83 (self.use_debug_cursor is None and settings.DEBUG)): 80 84 return self.make_debug_cursor(cursor) -
django/db/transaction.py
67 67 if thread_ident not in dirty or using not in dirty[thread_ident]: 68 68 dirty.setdefault(thread_ident, {}) 69 69 dirty[thread_ident][using] = False 70 def set_dirty_if_managed(): 71 if is_managed(using): set_dirty(using) 72 connection.on_used = set_dirty_if_managed 70 73 connection._enter_transaction_management(managed) 71 74 72 75 def leave_transaction_management(using=None): -
tests/regressiontests/transaction_regress/tests.py
1 from django.test import TransactionTestCase 2 from django.db import connection, transaction 3 from django.db.transaction import \ 4 commit_on_success, commit_manually, \ 5 TransactionManagementError 6 from django.core.exceptions import ImproperlyConfigured 7 from models import Mod 8 9 class Test9964(TransactionTestCase): 10 """ 11 Tests to make sure that transactions are properly closed 12 when they should be, and aren't left pending after operations 13 have been performed in them. 14 15 Prompted by http://code.djangoproject.com/ticket/9964 16 """ 17 def test_raw_committed_on_success(self): 18 """ 19 Make sure a transaction consisting of raw SQL execution gets 20 committed by the commit_on_success decorator. 21 """ 22 @commit_on_success 23 def raw_sql(): 24 "Write a record using raw sql under a commit_on_success decorator" 25 cursor = connection.cursor() 26 cursor.execute("INSERT into transaction_regress_mod (id,fld) values (17,18)") 27 28 raw_sql() 29 # Rollback so that if the decorator didn't commit, the record is unwritten 30 transaction.rollback() 31 try: 32 # Check that the record is in the DB 33 obj = Mod.objects.get(pk=17) 34 self.assertEqual(obj.fld, 18) 35 except Mod.DoesNotExist: 36 self.fail("transaction with raw sql not committed") 37 38 def test_commit_manually_enforced(self): 39 """ 40 Make sure that under commit_manually, even "read-only" transaction require closure 41 (commit or rollback), and a transaction left pending is treated as an error. 42 """ 43 @commit_manually 44 def non_comitter(): 45 "Execute a managed transaction with read-only operations and fail to commit" 46 _ = Mod.objects.count() 47 48 self.assertRaises(TransactionManagementError, non_comitter) 49 50 def test_commit_manually_commit_ok(self): 51 """ 52 Test that under commit_manually, a committed transaction is accepted by the transaction 53 management mechanisms 54 """ 55 @commit_manually 56 def committer(): 57 """ 58 Perform a database query, then commit the transaction 59 """ 60 _ = Mod.objects.count() 61 transaction.commit() 62 63 try: 64 committer() 65 except TransactionManagementError: 66 self.fail("Commit did not clear the transaction state") 67 68 def test_commit_manually_rollback_ok(self): 69 """ 70 Test that under commit_manually, a rolled-back transaction is accepted by the transaction 71 management mechanisms 72 """ 73 @commit_manually 74 def roller_back(): 75 """ 76 Perform a database query, then rollback the transaction 77 """ 78 _ = Mod.objects.count() 79 transaction.rollback() 80 81 try: 82 roller_back() 83 except TransactionManagementError: 84 self.fail("Rollback did not clear the transaction state") 85 86 def test_commit_manually_enforced_after_commit(self): 87 """ 88 Test that under commit_manually, if a transaction is committed and an operation is 89 performed later, we still require the new transaction to be closed 90 """ 91 @commit_manually 92 def fake_committer(): 93 "Query, commit, then query again, leaving with a pending transaction" 94 _ = Mod.objects.count() 95 transaction.commit() 96 _ = Mod.objects.count() 97 98 self.assertRaises(TransactionManagementError, fake_committer) 99 100 def test_reuse_cursor_reference(self): 101 """ 102 Make sure transaction closure is enforced even when the queries are performed 103 through a single cursor reference retrieved in the beginning 104 (this is to show why it is wrong to set the transaction dirty only when a cursor 105 is fetched from the connection). 106 """ 107 @commit_on_success 108 def reuse_cursor_ref(): 109 """ 110 Fetch a cursor, perform an query, rollback to close the transaction, 111 then write a record (in a new transaction) using the same cursor object 112 (reference). All this under commit_on_success, so the second insert should 113 be committed. 114 """ 115 cursor = connection.cursor() 116 cursor.execute("INSERT into transaction_regress_mod (id,fld) values (1,2)") 117 transaction.rollback() 118 cursor.execute("INSERT into transaction_regress_mod (id,fld) values (1,2)") 119 120 reuse_cursor_ref() 121 # Rollback so that if the decorator didn't commit, the record is unwritten 122 transaction.rollback() 123 try: 124 # Check that the record is in the DB 125 obj = Mod.objects.get(pk=1) 126 self.assertEquals(obj.fld, 2) 127 except Mod.DoesNotExist: 128 self.fail("After ending a transaction, cursor use no longer sets dirty") 129 130 def test_failing_query_transaction_closed(self): 131 """ 132 Make sure that under commit_on_success, a transaction is rolled back even if 133 the first database-modifying operation fails. 134 This is prompted by http://code.djangoproject.com/ticket/6669 (and based on sample 135 code posted there to exemplify the problem): Before Django 1.3, 136 transactions were only marked "dirty" by the save() function after it successfully 137 wrote the object to the database. 138 """ 139 from django.contrib.auth.models import User 140 141 @transaction.commit_on_success 142 def create_system_user(): 143 "Create a user in a transaction" 144 user = User.objects.create_user(username='system', password='iamr00t', email='root@SITENAME.com') 145 # Redundant, just makes sure the user id was read back from DB 146 Mod.objects.create(fld=user.id) 147 148 # Create a user 149 create_system_user() 150 151 try: 152 # The second call to create_system_user should fail for violating a unique constraint 153 # (it's trying to re-create the same user) 154 create_system_user() 155 except: 156 pass 157 else: 158 raise ImproperlyConfigured('Unique constraint not enforced on django.contrib.auth.models.User') 159 160 try: 161 # Try to read the database. If the last transaction was indeed closed, 162 # this should cause no problems 163 _ = User.objects.all()[0] 164 except: 165 self.fail("A transaction consisting of a failed operation was not closed.") -
tests/regressiontests/transaction_regress/models.py
1 from django.db import models 2 3 class Mod(models.Model): 4 fld = models.IntegerField() -
tests/regressiontests/delete_regress/tests.py
61 61 Book.objects.filter(pagecount__lt=250).delete() 62 62 transaction.commit() 63 63 self.assertEqual(1, Book.objects.count()) 64 transaction.commit() 64 65 65 66 class DeleteCascadeTests(TestCase): 66 67 def test_generic_relation_cascade(self): -
tests/regressiontests/fixtures_regress/tests.py
610 610 self.assertEqual(Thingy.objects.count(), 1) 611 611 transaction.rollback() 612 612 self.assertEqual(Thingy.objects.count(), 0) 613 transaction.commit() 613 614 614 615 def test_ticket_11101(self): 615 616 """Test that fixtures can be rolled back (ticket #11101).""" -
docs/topics/db/sql.txt
231 231 232 232 Transactions and raw SQL 233 233 ------------------------ 234 If you are using transaction decorators (such as ``commit_on_success``) to235 wrap your views and provide transaction control, you don't have to make a236 manual call to ``transaction.commit_unless_managed()`` -- you can manually237 commit if you want to, but you aren't required to, since the decorator will238 commit for you. However, if you don't manually commit your changes, you will239 need to manually mark the transaction as dirty, using240 ``transaction.set_dirty()``::241 234 242 @commit_on_success 243 def my_custom_sql_view(request, value): 244 from django.db import connection, transaction 245 cursor = connection.cursor() 235 .. versionchanged:: 1.3 246 236 247 # Data modifying operation 248 cursor.execute("UPDATE bar SET foo = 1 WHERE baz = %s", [value]) 237 If you write functions that modify the database via raw SQL, you need to 238 consider the transaction management mode they run under. If this may be 239 Django's default mode (by default or using the ``auto_commit`` 240 decorator), you need to make manual calls to 241 ``transaction.commit_unless_managed()`` to make sure the changes are 242 committed. If you are using transaction decorators (such as 243 ``commit_on_success``) to wrap your views and provide transaction 244 control, you don't have to make such calls. 249 245 250 # Since we modified data, mark the transaction as dirty251 transaction.set_dirty()252 246 253 # Data retrieval operation. This doesn't dirty the transaction,254 # so no call to set_dirty() is required.255 cursor.execute("SELECT foo FROM bar WHERE baz = %s", [value])256 row = cursor.fetchone()257 258 return render_to_response('template.html', {'row': row})259 260 The call to ``set_dirty()`` is made automatically when you use the Django ORM261 to make data modifying database calls. However, when you use raw SQL, Django262 has no way of knowing if your SQL modifies data or not. The manual call to263 ``set_dirty()`` ensures that Django knows that there are modifications that264 must be committed.265 266 247 Connections and cursors 267 248 ----------------------- 268 249 -
docs/releases/1.3.txt
378 378 379 379 380 380 381 Transaction management 382 ~~~~~~~~~~~~~~~~~~~~~~ 383 384 When using managed transactions -- that is, anything but the default 385 autocommit mode -- it is important when a transaction is marked as 386 "dirty". Dirty transactions are committed by the ``commit_on_success`` 387 decorator or the ``TransactionMiddleware``, and ``commit_manually`` 388 forces them to be closed explicitly; clean transactions "get a pass", 389 which means they are usually rolled back at the end of a request 390 when the connection is closed. 391 392 Until Django 1.3, transactions were only marked dirty when Django 393 was aware of a modifying operation performed in them; that is, either 394 some model was saved, some bulk update or delete was performed, or 395 the user explicitly called ``transaction.set_dirty()``. In Django 1.3, 396 a transaction is marked dirty when any database operation is performed; 397 this means you no longer need to set a transaction dirty explicitly 398 when you execute raw SQL or use a data-modifying ``select``. On the 399 other hand, you do need to explicitly close read-only transactions 400 under ``commit_manually``. For example, take a look at this code:: 401 402 @transaction.commit_manually 403 def my_view(request, name): 404 obj = get_object_or_404(MyObject, name__iexact=name) 405 return render_to_response('template', {'object':obj}) 406 407 Until Django 1.3, this works fine. With Django 1.3, this raises a 408 ``TransactionManagementError``; you need to end the transaction 409 explicitly, one way or another. 410 381 411 .. _deprecated-features-1.3: 382 412 383 413 Features deprecated in 1.3