A transaction ends either successfully through an explicit commit command, or unsuccessfully in any of a number of ways. The following are the ways to end a transaction:
The transaction may be committed through its commit member function:
Xaction.commit();
The commit operation is sent to the backend at the point where the commit call occurs. Any exceptions generated by the database transaction will be thrown from here at the latest. The only exceptions that may be generated by Xaction beyond this point are related to incorrect handling of the transaction object, eg. if an attempt is made to abort Xaction after it has been committed, or runtime errors such as memory running out.
As a consequence, any streams or cursors nested within the transaction (to be discussed later) must have been closed before the commit(). To do otherwise could possibly allow a transaction to be committed before all related actions had completed. The library will throw an exception if any streams are still open when the transaction is ended.
A transaction is aborted if it is destroyed without having been explicitly committed:
{ work Xaction(Conn, "DemoTransaction"); // (Queries) } // Xaction destroyed here
work *XactionP = new work(Conn, "DemoTransaction"); // (Queries) delete XactionP; // Xaction destroyed here
try { work Xaction(Conn, "DemoTransaction"); // (Queries) Xaction.commit(); // If we get here, Xaction is committed } catch (...) { // If we get here, Xaction has been rolled back }
No matter where exactly the decision to abort is made, the actual abort operation is sent to the backend when the transaction's destructor is called. If the abort fails, eg. because the network connection has been lost, no error is reported [3] and the transaction will die of natural causes (either it has been closed by the backend already, or it soon will be if the connection is lost).
If a database error occurs during the transaction, such as an SQL syntax error or lost connection to the backend, the transaction is aborted.
work Xaction(Conn, "DemoTransaction"); try { // (Queries) Xaction.exec("SELECT !?^H^H^H^H"); // Fails: SQL syntax error } catch (...) { } Xaction.commit(); // ERROR: Xaction has already aborted!
For this reason, it is recommended always to include the "commit" operation inside the try block (if any) surrounding the transaction code, not after the catch block.
Think of it as a natural extension of structural programming: the transaction is "nested" within the connection, and the transaction code can be "nested" in a try/catch block.
No more queries may be issued to the transaction regardless of how it ended; an exception will be thrown if the application attempts to continue the transaction after that time. Ending a transaction more than once is an error, except that aborting it multiple times is tolerated to facilitate error handling.
[3] Throwing an exception from a destructor to report the error would have serious effects on program correctness. Never throw exceptions from a destructor.