%Transactions and Versioning
%   Committing and Aborting
%   Subtransactions
%   Undoing
%   Versions
%   Multithreaded ZODB Programs
   

\section{Transactions and Versioning}

%\subsection{Committing and Aborting}
% XXX There seems very little to say about commit/abort...

\subsection{Subtransactions}

Subtransactions can be created within a transaction.  Each
subtransaction can be individually committed and aborted, but the
changes within a subtransaction are not truly committed until the
containing transaction is committed.

The primary purpose of subtransactions is to decrease the memory usage
of transactions that touch a very large number of objects.  Consider a
transaction during which 200,000 objects are modified.  All the
objects that are modified in a single transaction have to remain in
memory until the transaction is committed, because the ZODB can't
discard them from the object cache.  This can potentially make the
memory usage quite large.  With subtransactions, a commit can be be
performed at intervals, say, every 10,000 objects.  Those 10,000
objects are then written to permanent storage and can be purged from
the cache to free more space.

To commit a subtransaction instead of a full transaction, 
pass a true value to the \method{commit()}
or \method{abort()} method of the \class{Transaction} object.

\begin{verbatim}
# Commit a subtransaction
get_transaction().commit(1)     

# Abort a subtransaction
get_transaction().abort(1)   
\end{verbatim}

A new subtransaction is automatically started on committing or
aborting the previous subtransaction.


\subsection{Undoing Changes}

Some types of \class{Storage} support undoing a transaction even after
it's been committed.  You can tell if this is the case by calling the
\method{supportsUndo()} method of the \class{DB} instance, which
returns true if the underlying storage supports undo.  Alternatively
you can call the \method{supportsUndo()} method on the underlying
storage instance.

If a database supports undo, then the \method{undoLog(\var{start},
\var{end}\optional{, func})} method on the \class{DB} instance returns
the log of past transactions, returning transactions between the times
\var{start} and \var{end}, measured in seconds from the epoch.   
If present, \var{func} is a function that acts as a filter on the
transactions to be returned; it's passed a dictionary representing
each transaction, and only transactions for which \var{func} returns
true will be included in the list of transactions returned to the
caller of \method{undoLog()}.  The dictionary contains keys for
various properties of the transaction.  The most important keys are
\samp{id}, for the transaction ID, and \samp{time}, for the time at
which the transaction was committed.  

\begin{verbatim}
>>> print storage.undoLog(0, sys.maxint)
[{'description': '',
  'id': 'AzpGEGqU/0QAAAAAAAAGMA',
  'time': 981126744.98,
  'user_name': ''},
 {'description': '',
  'id': 'AzpGC/hUOKoAAAAAAAAFDQ',
  'time': 981126478.202,
  'user_name': ''}
  ...
\end{verbatim}

To store a description and a user name on a commit, get the current
transaction and call the \method{note(\var{text})} method to store a
description, and the
\method{setUser(\var{user_name})} method to store the user name.  
While \method{setUser()} overwrites the current user name and replaces
it with the new value, the \method{note()} method always adds the text
to the transaction's description, so it can be called several times to
log several different changes made in the course of a single
transaction.

\begin{verbatim}
get_transaction().setUser('amk')
get_transaction().note('Change ownership')
\end{verbatim}

To undo a transaction, call the \method{DB.undo(\var{id})} method,
passing it the ID of the transaction to undo.  If the transaction
can't be undone, a \exception{ZODB.POSException.UndoError} exception
will be raised, with the message ``non-undoable
transaction''.  Usually this will happen because later transactions
modified the objects affected by the transaction you're trying to
undo.

After you call \method{undo()} you must commit the transaction for the
undo to actually be applied.
\footnote{There are actually two different ways a storage can
implement the undo feature.  Most of the storages that ship with ZODB
use the transactional form of undo described in the main text.  Some
storages may use a non-transactional undo makes changes visible
immediately.}  There is one glitch in the undo process.  The thread
that calls undo may not see the changes to the object until it calls
\method{Connection.sync()} or commits another transaction.

\subsection{Versions}

While many subtransactions can be contained within a single regular
transaction, it's also possible to contain many regular transactions
within a long-running transaction, called a version in ZODB
terminology.  Inside a version, any number of transactions can be
created and committed or rolled back, but the changes within a version
are not made visible to other connections to the same ZODB.

Not all storages support versions, but you can test for versioning
ability by calling \method{supportsVersions()} method of the
\class{DB} instance, which returns true if the underlying storage
supports versioning.

A version can be selected when creating the \class{Connection}
instance using the \method{DB.open(\optional{\var{version}})} method.
The \var{version} argument must be a string that will be used as the
name of the version.

\begin{verbatim}
vers_conn = db.open(version='Working version')
\end{verbatim}

Transactions can then be committed and aborted using this versioned
connection.  Other connections that don't specify a version, or
provide a different version name, will not see changes committed
within the version named \samp{Working~version}.  To commit or abort a
version, which will either make the changes visible to all clients or
roll them back, call the \method{DB.commitVersion()} or
\method{DB.abortVersion()} methods.
XXX what are the source and dest arguments for?

The ZODB makes no attempt to reconcile changes between different
versions.  Instead, the first version which modifies an object will
gain a lock on that object.  Attempting to modify the object from a
different version or from an unversioned connection will cause a
\exception{ZODB.POSException.VersionLockError} to be raised:

\begin{verbatim}
from ZODB.POSException import VersionLockError

try:
    get_transaction().commit()
except VersionLockError, (obj_id, version):
    print ('Cannot commit; object %s '
           'locked by version %s' % (obj_id, version))
\end{verbatim}

The exception provides the ID of the locked object, and the name of
the version having a lock on it.

\subsection{Multithreaded ZODB Programs}

ZODB databases can be accessed from multithreaded Python programs.
The \class{Storage} and \class{DB} instances can be shared among
several threads, as long as individual \class{Connection} instances
are created for each thread.  

