Browse files

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: bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
1 parent d1cd53d commit 6314a1b42e4f695b4c575585375fe91697911d5c @freakboy3742 freakboy3742 committed Feb 12, 2011
@@ -78,6 +78,7 @@ answer newbie questions, and generally made Django that much better:
Esdras Beleza <>
Chris Bennett <>
James Bennett
+ Shai Berger <>
Julian Bez
Arvis Bickovskis <>
Natalia Bidart <>
@@ -245,10 +245,11 @@ def close(self):
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):
@@ -5,12 +5,28 @@
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 @@ def execute(self, sql, params=()):
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 @@ def executemany(self, sql, param_list):
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 @@ def __getattr__(self, attr):
def __iter__(self):
return iter(self.cursor)
# Converters from database (string) to Python #
@@ -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
+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
+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
@@ -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
- @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
@@ -70,43 +70,43 @@ per-function or per-code-block basis.
These functions, described in detail below, can be used in two different ways:
* As a decorator_ on a particular function. For example::
from django.db import transaction
def viewfunc(request):
# ...
# this code executes inside a transaction
# ...
This technique works with all supported version of Python (that is, with
Python 2.4 and greater).
* As a `context manager`_ around a particular block of code::
from django.db import transaction
def viewfunc(request):
# ...
# this code executes using default transaction management
- # ...
+ # ...
with transaction.commit_on_success():
# ...
# this code executes inside a transaction
# ...
The ``with`` statement is new in Python 2.5, and so this syntax can only
be used with Python 2.5 and above.
.. _decorator:
.. _context manager:
For maximum compatibility, all of the examples below show transactions using the
decorator syntax, but all of the follow functions may be used as context
managers, too.
-.. note::
+.. note::
Although the examples below use view functions as examples, these
decorators and context managers can be used anywhere in your code
@@ -187,6 +187,25 @@ managers, too.
def viewfunc2(request):
+.. _topics-db-transactions-requirements:
+Requirements for transaction handling
+.. versionadded:: 1.3
+Django requires that every transaction that is opened is closed before
+the completion of a request. If you are using :func:`autocommit` (the
+default commit mode) or :func:`commit_on_success`, this will be done
+for you automatically. However, if you are manually managing
+transactions (using the :func:`commit_manually` decorator), you must
+ensure that the transaction is either committed or rolled back before
+a request is completed.
+This applies to all database operations, not just write operations. Even
+if your transaction only reads from the database, the transaction must
+be committed or rolled back before you complete a request.
How to globally deactivate transaction management
@@ -62,6 +62,7 @@ def test_concurrent_delete(self):
self.assertEqual(1, Book.objects.count())
+ transaction.commit()
class DeleteCascadeTests(TestCase):
@@ -610,6 +610,7 @@ def ticket_11101(self):
self.assertEqual(Thingy.objects.count(), 1)
self.assertEqual(Thingy.objects.count(), 0)
+ transaction.commit()
def test_ticket_11101(self):
@@ -0,0 +1,4 @@
+from django.db import models
+class Mod(models.Model):
+ fld = models.IntegerField()
Oops, something went wrong.

0 comments on commit 6314a1b

Please sign in to comment.