Ticket #9964: 9964-make-managed-transactions-dirty-v2.patch

File 9964-make-managed-transactions-dirty-v2.patch, 8.9 KB (added by Shai Berger, 14 years ago)

improved patch: more robust detection of use (with added test), more tests fixed, against 14617

  • django/db/backends/util.py

     
    5252    def __iter__(self):
    5353        return iter(self.cursor)
    5454
     55class 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
    5565###############################################
    5666# Converters from database (string) to Python #
    5767###############################################
  • django/db/backends/__init__.py

     
    2222        self.alias = alias
    2323        self.vendor = 'unknown'
    2424        self.use_debug_cursor = None
     25        self.on_used = None
    2526
    2627    def __eq__(self, other):
    2728        return self.settings_dict == other.settings_dict
     
    7576    def cursor(self):
    7677        from django.conf import settings
    7778        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)
    7882        if (self.use_debug_cursor or
    7983            (self.use_debug_cursor is None and settings.DEBUG)):
    8084            return self.make_debug_cursor(cursor)
  • django/db/transaction.py

     
    6767    if thread_ident not in dirty or using not in dirty[thread_ident]:
    6868        dirty.setdefault(thread_ident, {})
    6969        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
    7073    connection._enter_transaction_management(managed)
    7174
    7275def leave_transaction_management(using=None):
  • tests/regressiontests/transaction_regress/tests.py

     
     1from django.test import TransactionTestCase
     2from django.db import connection, transaction
     3from django.db.transaction import \
     4    commit_on_success, commit_manually, \
     5    TransactionManagementError
     6from models import Mod
     7
     8class Test9964(TransactionTestCase):
     9
     10    def test_raw_committed_on_success(self):
     11       
     12        @commit_on_success
     13        def raw_sql():
     14            cursor = connection.cursor()
     15            cursor.execute("INSERT into transaction_regress_mod (id,fld) values (17,18)")
     16           
     17        raw_sql()
     18        transaction.rollback()
     19        try:
     20            obj = Mod.objects.get(pk=17)
     21            self.assertEqual(obj.fld, 18)
     22        except Mod.DoesNotExist:
     23            self.fail("transaction with raw sql not committed")
     24
     25    def test_commit_manually_enforced(self):
     26        @commit_manually
     27        def non_comitter():
     28            _ = Mod.objects.count()
     29           
     30        self.assertRaises(TransactionManagementError, non_comitter)
     31
     32    def test_commit_manually_commit_ok(self):
     33        @commit_manually
     34        def committer():
     35            _ = Mod.objects.count()
     36            transaction.commit()
     37       
     38        try:
     39            committer()
     40        except TransactionManagementError:
     41            self.fail("Commit did not clear the transaction state")
     42
     43    def test_commit_manually_rollback_ok(self):
     44        @commit_manually
     45        def roller_back():
     46            _ = Mod.objects.count()
     47            transaction.rollback()
     48       
     49        try:
     50            roller_back()
     51        except TransactionManagementError:
     52            self.fail("Rollback did not clear the transaction state")
     53
     54    def test_commit_manually_enforced_after_commit(self):
     55        @commit_manually
     56        def fake_committer():
     57            _ = Mod.objects.count()
     58            transaction.commit()
     59            _ = Mod.objects.count()           
     60           
     61        self.assertRaises(TransactionManagementError, fake_committer)
     62
     63    def test_reuse_cursor_reference(self):
     64        @commit_on_success
     65        def reuse_cursor_ref():
     66            cursor = connection.cursor()
     67            cursor.execute("INSERT into transaction_regress_mod (id,fld) values (1,2)")
     68            transaction.rollback()
     69            cursor.execute("INSERT into transaction_regress_mod (id,fld) values (1,2)")
     70           
     71        reuse_cursor_ref()
     72        transaction.rollback()
     73        try:
     74            obj = Mod.objects.get(pk=1)
     75            self.assertEquals(obj.fld, 2)
     76        except Mod.DoesNotExist:
     77            self.fail("After ending a transaction, cursor use no longer sets dirty")
  • tests/regressiontests/transaction_regress/models.py

     
     1from django.db import models
     2
     3class Mod(models.Model):
     4    fld = models.IntegerField()
     5from django.db import models
     6
     7class Mod(models.Model):
     8    fld = models.IntegerField()
  • tests/regressiontests/delete_regress/tests.py

     
    6161        Book.objects.filter(pagecount__lt=250).delete()
    6262        transaction.commit()
    6363        self.assertEqual(1, Book.objects.count())
     64        transaction.commit()
    6465
    6566class DeleteCascadeTests(TestCase):
    6667    def test_generic_relation_cascade(self):
  • tests/regressiontests/fixtures_regress/tests.py

     
    610610        self.assertEqual(Thingy.objects.count(), 1)
    611611        transaction.rollback()
    612612        self.assertEqual(Thingy.objects.count(), 0)
     613        transaction.commit()
    613614
    614615    def test_ticket_11101(self):
    615616        """Test that fixtures can be rolled back (ticket #11101)."""
  • docs/topics/db/sql.txt

     
    231231
    232232Transactions and raw SQL
    233233------------------------
    234 If you are using transaction decorators (such as ``commit_on_success``) to
    235 wrap your views and provide transaction control, you don't have to make a
    236 manual call to ``transaction.commit_unless_managed()`` -- you can manually
    237 commit if you want to, but you aren't required to, since the decorator will
    238 commit for you. However, if you don't manually commit your changes, you will
    239 need to manually mark the transaction as dirty, using
    240 ``transaction.set_dirty()``::
    241234
    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
    246236
    247         # Data modifying operation
    248         cursor.execute("UPDATE bar SET foo = 1 WHERE baz = %s", [value])
     237If you write functions that modify the database via raw SQL, you need to
     238consider the transaction management mode they run under. If this may be
     239Django's default mode (by default or using the ``auto_commit``
     240decorator), you need to make manual calls to
     241``transaction.commit_unless_managed()`` to make sure the changes are
     242committed. If you are using transaction decorators (such as
     243``commit_on_success``) to wrap your views and provide transaction
     244control, you don't have to make such calls.
    249245
    250         # Since we modified data, mark the transaction as dirty
    251         transaction.set_dirty()
    252246
    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 ORM
    261 to make data modifying database calls. However, when you use raw SQL, Django
    262 has no way of knowing if your SQL modifies data or not. The manual call to
    263 ``set_dirty()`` ensures that Django knows that there are modifications that
    264 must be committed.
    265 
    266247Connections and cursors
    267248-----------------------
    268249
Back to Top