Commit 6314a1b4 authored by Russell Keith-Magee's avatar Russell Keith-Magee
Browse files

Fixed #9964 -- Ensure that all database operations make transactions dirty,...

Fixed #9964 -- Ensure that all database operations make transactions dirty, not just write operations. Many thanks to Shai Berger for his work and persistence on this issue.

This is BACKWARDS INCOMPATIBLE for anyone relying on the current behavior that allows manually managed read-only transactions to be left dangling without a manual commit or rollback.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@15493 bcc190cf-cafb-0310-a4f2-bffc1f526a37
parent d1cd53d9
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -78,6 +78,7 @@ answer newbie questions, and generally made Django that much better:
    Esdras Beleza <linux@esdrasbeleza.com>
    Chris Bennett <chrisrbennett@yahoo.com>
    James Bennett
    Shai Berger <shai@platonix.com>
    Julian Bez
    Arvis Bickovskis <viestards.lists@gmail.com>
    Natalia Bidart <nataliabidart@gmail.com>
+3 −2
Original line number Diff line number Diff line
@@ -245,10 +245,11 @@ class BaseDatabaseWrapper(local):
            self.connection = None

    def cursor(self):
        cursor = self._cursor()
        if (self.use_debug_cursor or
            (self.use_debug_cursor is None and settings.DEBUG)):
            return self.make_debug_cursor(cursor)
            cursor = self.make_debug_cursor(self._cursor())
        else:
            cursor = util.CursorWrapper(self._cursor(), self)
        return cursor

    def make_debug_cursor(self, cursor):
+23 −6
Original line number Diff line number Diff line
@@ -5,12 +5,28 @@ from time import time
from django.utils.hashcompat import md5_constructor
from django.utils.log import getLogger


logger = getLogger('django.db.backends')

class CursorDebugWrapper(object):
    def __init__(self, cursor, db):

class CursorWrapper(object):
    def __init__(self, cursor, connection):
        self.cursor = cursor
        self.db = db # Instance of a BaseDatabaseWrapper subclass
        self.connection = connection

    def __getattr__(self, attr):
        if self.connection.is_managed():
            self.connection.set_dirty()
        if attr in self.__dict__:
            return self.__dict__[attr]
        else:
            return getattr(self.cursor, attr)

    def __iter__(self):
        return iter(self.cursor)


class CursorDebugWrapper(CursorWrapper):

    def execute(self, sql, params=()):
        start = time()
@@ -19,8 +35,8 @@ class CursorDebugWrapper(object):
        finally:
            stop = time()
            duration = stop - start
            sql = self.db.ops.last_executed_query(self.cursor, sql, params)
            self.db.queries.append({
            sql = self.connection.ops.last_executed_query(self.cursor, sql, params)
            self.connection.queries.append({
                'sql': sql,
                'time': "%.3f" % duration,
            })
@@ -35,7 +51,7 @@ class CursorDebugWrapper(object):
        finally:
            stop = time()
            duration = stop - start
            self.db.queries.append({
            self.connection.queries.append({
                'sql': '%s times: %s' % (len(param_list), sql),
                'time': "%.3f" % duration,
            })
@@ -52,6 +68,7 @@ class CursorDebugWrapper(object):
    def __iter__(self):
        return iter(self.cursor)


###############################################
# Converters from database (string) to Python #
###############################################
+36 −0
Original line number Diff line number Diff line
@@ -604,6 +604,42 @@ domain):

.. _corresponding deprecated features section: loading_of_translations_from_the_project_directory_

Transaction management
~~~~~~~~~~~~~~~~~~~~~~

When using managed transactions -- that is, anything but the default
autocommit mode -- it is important when a transaction is marked as
"dirty". Dirty transactions are committed by the
:func:`~django.db.transaction.commit_on_success` decorator or the
:class:`~django.middleware.transaction.TransactionMiddleware`, and
:func:`~django.db.transaction.commit_manually` forces them to be
closed explicitly; clean transactions "get a pass", which means they
are usually rolled back at the end of a request when the connection is
closed.

Until Django 1.3, transactions were only marked dirty when Django was
aware of a modifying operation performed in them; that is, either some
model was saved, some bulk update or delete was performed, or the user
explicitly called ``transaction.set_dirty()``. In Django 1.3, a
transaction is marked dirty when *any* database operation is
performed.

As a result of this change, you no longer need to set a transaction
dirty explicitly when you execute raw SQL or use a data-modifying
``SELECT``. However, you *do* need to explicitly close any read-only
transactions that are being managed using
:func:`~django.db.transaction.commit_manually`. For example::

      @transaction.commit_manually
      def my_view(request, name):
      	  obj = get_object_or_404(MyObject, name__iexact=name)
	  return render_to_response('template', {'object':obj})

Prior to Django 1.3, this would work without error. However, under
Django 1.3, this will raise a :class:`TransactionManagementError` because
the read operation that retrieves the ``MyObject`` instance leaves the
transaction in a dirty state.

.. _deprecated-features-1.3:

Features deprecated in 1.3
+8 −28
Original line number Diff line number Diff line
@@ -233,37 +233,17 @@ alias::

Transactions and raw SQL
------------------------
If you are using transaction decorators (such as ``commit_on_success``) to
wrap your views and provide transaction control, you don't have to make a
manual call to ``transaction.commit_unless_managed()`` -- you can manually
commit if you want to, but you aren't required to, since the decorator will
commit for you. However, if you don't manually commit your changes, you will
need to manually mark the transaction as dirty, using
``transaction.set_dirty()``::

    @commit_on_success
    def my_custom_sql_view(request, value):
        from django.db import connection, transaction
        cursor = connection.cursor()

        # Data modifying operation
        cursor.execute("UPDATE bar SET foo = 1 WHERE baz = %s", [value])

        # Since we modified data, mark the transaction as dirty
        transaction.set_dirty()

        # Data retrieval operation. This doesn't dirty the transaction,
        # so no call to set_dirty() is required.
        cursor.execute("SELECT foo FROM bar WHERE baz = %s", [value])
        row = cursor.fetchone()
When you make a raw SQL call, Django will automatically mark the
current transaction as dirty. You must then ensure that the
transaction containing those calls is closed correctly. See :ref:`the
notes on the requirements of Django's transaction handling
<topics-db-transactions-requirements>` for more details.

        return render_to_response('template.html', {'row': row})
.. versionchanged:: 1.3

The call to ``set_dirty()`` is made automatically when you use the Django ORM
to make data modifying database calls. However, when you use raw SQL, Django
has no way of knowing if your SQL modifies data or not. The manual call to
``set_dirty()`` ensures that Django knows that there are modifications that
must be committed.
Prior to Django 1.3, it was necessary to manually mark a transaction
as dirty using ``transaction.set_dirty()`` when using raw SQL calls.

Connections and cursors
-----------------------
Loading