]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- write a new section describing the "subtransactions=True" flag in full detail
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 1 Nov 2010 19:59:53 +0000 (15:59 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 1 Nov 2010 19:59:53 +0000 (15:59 -0400)
doc/build/core/connections.rst
doc/build/orm/session.rst
lib/sqlalchemy/orm/session.py

index 91e1d698b318fa030a02f6fc3ac06e56f3770db1..7591e73e220112f8f5fc7729741642b8c5572220 100644 (file)
@@ -144,6 +144,11 @@ guaranteed to ``rollback()`` or ``commit()``::
         trans.rollback()
         raise
 
+.. _connections_nested_transactions:
+
+Nesting of Transaction Blocks
+------------------------------
+
 The :class:`~sqlalchemy.engine.base.Transaction` object also handles "nested"
 behavior by keeping track of the outermost begin/commit pair. In this example,
 two functions both issue a transaction on a Connection, but only the outermost
index 16ca7aff00198084564a50719ef252cb39dc27ae..fabac0470ff7cb182606b38513f18148acdce93f 100644 (file)
@@ -996,6 +996,8 @@ statement::
         item1.foo = 'bar'
         item2.bar = 'foo'
 
+.. _session_begin_nested:
+
 Using SAVEPOINT
 ---------------
 
@@ -1028,6 +1030,95 @@ session is expired, thus causing all subsequent attribute/instance access to
 reference the full state of the :class:`~sqlalchemy.orm.session.Session` right
 before :func:`~sqlalchemy.orm.session.Session.begin_nested` was called.
 
+.. _session_subtransactions:
+
+Using Subtransactions
+---------------------
+
+A subtransaction, as offered by the ``subtransactions=True`` flag of :meth:`.Session.begin`,
+is a non-transactional, delimiting construct that
+allows nesting of calls to :meth:`~.Session.begin` and :meth:`~.Session.commit`.
+It's purpose is to allow the construction of code that can function within a transaction
+both independently of any external code that starts a transaction,
+as well as within a block that has already demarcated a transaction.  By "non-transactional", we
+mean that no actual transactional dialogue with the database is generated by this flag beyond that of
+a single call to :meth:`~.Session.begin`, regardless of how many times the method
+is called within a transaction.
+
+The subtransaction feature is in fact intrinsic to any call to :meth:`~.Session.flush`, which uses
+it internally to ensure that the series of flush steps are enclosed within a transaction,
+regardless of the setting of ``autocommit`` or the presence of an existing transactional context.
+However, explicit usage of the ``subtransactions=True`` flag is generally only useful with an 
+application that uses the
+:class:`.Session` in "autocommit=True" mode, and calls :meth:`~.Session.begin` explicitly
+in order to demarcate transactions.  For this reason the subtransaction feature is not
+commonly used in an explicit way, except for apps that integrate SQLAlchemy-level transaction control with
+the transaction control of another library or subsystem.  For true, general purpose "nested" 
+transactions, where a rollback affects only a portion of the work which has proceeded, 
+savepoints should be used, documented in :ref:`session_begin_nested`.
+
+The feature is the ORM equivalent to the pattern described at :ref:`connections_nested_transactions`, 
+where any number of functions can call :meth:`.Connection.begin` and :meth:`.Transaction.commit`
+as though they are the initiator of the transaction, but in fact may be participating
+in an already ongoing transaction.
+
+As is the case with the non-ORM :class:`.Transaction` object,
+calling :meth:`.Session.rollback` rolls back the **entire**
+transaction, which was initiated by the first call to
+:meth:`.Session.begin` (whether this call was explicit by the
+end user, or implicit in an ``autocommit=False`` scenario).
+However, the :class:`.Session` still considers itself to be in a
+"partially rolled back" state until :meth:`.Session.rollback` is
+called explicitly for each call that was made to
+:meth:`.Session.begin`, where "partially rolled back" means that
+no further SQL operations can proceed until each level
+of the transaction has been acounted for, unless the :meth:`~.Session.close` method
+is called which cancels all transactional markers. For a full exposition on 
+the rationale for this,
+please see `But why isn't the one automatic call to ROLLBACK
+enough ? Why must I ROLLBACK again?
+<http://www.sqlalchemy.org/trac/wiki/FAQ#ButwhyisnttheoneautomaticcalltoROLLBACKenoughWhymustIROLLBACKagain>`_.
+In short, if subtransactions are used as intended, that is, as a means to nest multiple
+begin/commit pairs, the appropriate rollback calls naturally occur in any case.
+
+An example of ``subtransactions=True`` is nearly identical to that of the non-ORM method.   The nesting
+of transactions, as well as the natural calling of "rollback" for all transactions, is illustrated::
+
+    # method_a starts a transaction and calls method_b
+    def method_a(session):
+        session.begin(subtransactions=True) # open a transaction.  If there was
+                                            # no previous call to begin(), this will
+                                            # be a real transaction.
+        try:
+            method_b(session)
+            session.commit()  # transaction is committed here
+        except:
+            session.rollback() # rolls back the transaction
+            raise
+
+    # method_b also starts a transaction
+    def method_b(connection):
+        session.begin(subtransactions=True) # open a transaction - this 
+                                            # runs in the context of method_a()'s 
+                                            # transaction
+        try:
+            session.add(SomeObject('bat', 'lala'))
+            session.commit()  # transaction is not committed yet
+        except:
+            session.rollback() # rolls back the transaction, in this case
+                               # the one that was initiated in method_a().
+            raise
+
+    # create a Session and call method_a
+    session = Session(autocommit=True)
+    method_a(session)
+    session.close()
+
+Since the :meth:`.Session.flush` method uses a subtransaction, a failed flush
+will always issue a rollback which then affects the state of the outermost transaction (unless a SAVEPOINT
+is in use).   This forces the need to issue :meth:`~.Session.rollback` for the full operation
+before subsequent SQL operations can proceed.
+
 Enabling Two-Phase Commit
 -------------------------
 
index f2bbb9e4586e3de400264c4a9537239447fa3133..80c353ebca58446ebbc3d16a7a5ac4630429f7e0 100644 (file)
@@ -532,20 +532,13 @@ class Session(object):
         transaction or nested transaction, an error is raised, unless
         ``subtransactions=True`` or ``nested=True`` is specified.
 
-        The ``subtransactions=True`` flag indicates that this ``begin()`` can
-        create a subtransaction if a transaction is already in progress.  A
-        subtransaction is a non-transactional, delimiting construct that
-        allows matching begin()/commit() pairs to be nested together, with
-        only the outermost begin/commit pair actually affecting transactional
-        state.  When a rollback is issued, the subtransaction will directly
-        roll back the innermost real transaction, however each subtransaction
-        still must be explicitly rolled back to maintain proper stacking of
-        subtransactions.
-
-        If no transaction is in progress, then a real transaction is begun.
-
+        The ``subtransactions=True`` flag indicates that this :meth:`~.Session.begin` 
+        can create a subtransaction if a transaction is already in progress.  
+        For documentation on subtransactions, please see :ref:`session_subtransactions`.
+        
         The ``nested`` flag begins a SAVEPOINT transaction and is equivalent
-        to calling ``begin_nested()``.
+        to calling :meth:`~.Session.begin_nested`. For documentation on SAVEPOINT
+        transactions, please see :ref:`session_begin_nested`.
 
         """
         if self.transaction is not None:
@@ -567,10 +560,8 @@ class Session(object):
         The target database(s) must support SQL SAVEPOINTs or a
         SQLAlchemy-supported vendor implementation of the idea.
 
-        The nested transaction is a real transation, unlike a "subtransaction"
-        which corresponds to multiple ``begin()`` calls.  The next
-        ``rollback()`` or ``commit()`` call will operate upon this nested
-        transaction.
+        For documentation on SAVEPOINT
+        transactions, please see :ref:`session_begin_nested`.
 
         """
         return self.begin(nested=True)