Manage the transaction

The transaction system of the database server only guarantees transaction semantics on all objects that are internal to the database. However, transactions might also include operations on external objects. A UDR might perform such operations, such as creating a temporary file or sending a message.

For transactions that consist of operations on both internal and external objects, you can use one of the following types of callbacks to commit or to undo (if possible) the operations on the external objects, based on the transaction status:
  • Commit-abort callback (MI_EVENT_COMMIT_ABORT)
  • End-of-statement callback (MI_EVENT_END_STMT)

    Each SQL statement behaves like a subtransaction when in a transaction block or like a transaction when not in a transaction block.

  • End-of-transaction callback (MI_EVENT_END_XACT)

    Registration of a commit-abort callback is preferable.

  • Savepoint callback (MI_EVENT_SAVEPOINT)

    Each cursor flush behaves like a subtransaction when in a transaction block.

To enable these callbacks to roll back a transaction, the DataBlade® API allows end-of-statement and end-of-transaction callbacks to raise an exception and register their own exception callbacks.

The database server calls an end-of-statement or end-of-transaction callback before it attempts to commit the transaction. When called before a commit, these callbacks receive a transition descriptor that has a transition state of MI_NORMAL_END. However, if either of these callbacks encounters an error during its execution, you probably do not want to allow the transaction to commit.

To cause an event to be stopped or rolled back, you can raise an exception from a state-transition callback by calling the mi_db_error_raise() function. When a state-transition callback raises an exception, the DataBlade API takes the following actions:
  1. Terminates further processing of the end-of-statement or end-of-transaction callback.
  2. Terminates the current transaction and changes the transition state from MI_NORMAL_END (commit) to MI_ABORT_END (rollback).
  3. Invokes any end-of-statement or end-of-transaction callbacks again, this time with the MI_ABORT_END transition state.
  4. Invokes any exception callback that the end-of-statement or end-of-transaction callback has registered to handle the exception.
An end-of-transaction callback executes within a C UDR when the MI_EVENT_END_XACT event occurs. The following code implements an end-of-transaction callback named endxact_callback(), which inserts a row into a database table, tran_state, to indicate the state of the current transaction:
MI_CALLBACK_STATUS MI_PROC_CALLBACK
endxact_callback(event_type, conn, event_data, user_data)
   MI_EVENT_TYPE event_type;
   MI_CONNECTION *conn;
   void *event_data;
   void *user_data;
{
   MI_CONNECTION *cb_conn;
   cb_conn = mi_open(NULL, NULL, NULL);
   (void) mi_register_callback(cb_conn, MI_Exception,
      eox_excpt_callback(), NULL, NULL);
   if ( event_type == MI_EVENT_END_XACT )
      {
      mi_integer change_type;

      change_type = mi_transition(event_data);
      switch ( change_type )
         {
         case MI_NORMAL_END:
            ret = mi_exec(cb_conn,"insert \
               into tran_state \
               values(\"Transaction Committed.\")\;",
               0);
            if ( ret == MI_ERROR )
               mi_db_error_raise(cb_conn, MI_EXCEPTION,
                  "Unable to save transaction \
                  state: Commit.");
            break;
         case MI_ABORT_END:
            ret = mi_exec(cb_conn,"insert \
               into tran_state \
               values(\"Transaction Aborted.\")\;",
               0);
            if ( ret == MI_ERROR )
               make_log_entry(log_file, 
                  "Unable to save transaction state: \
                  Roll Back.");
            break;

         default:
            mi_exec(cb_conn,"insert into tran_state \
               values(\"Unhandled Transaction \
                  Event.\")\;", 0);
            break;
         }
      }
   else
      {
      mi_exec(cb_conn, "insert into tran_state \
         values(\"Unhandled Event.\");", 0);
      break;
      }

   mi_close(cb_conn);
   return MI_CB_CONTINUE;
}

MI_CALLBACK_STATUS MI_PROC_CALLBACK
eox_excpt_callback(event_type, conn, event_data, user_data)
   MI_EVENT_TYPE event_type;
   MI_CONNECTION *conn;
   void *event_data;
   void *user_data;
{
   ... Perform clean-up tasks ...
   return MI_CB_CONTINUE;
}
The database server invokes the endxact_callback() callback with a transition state of MI_NORMAL_END just before it commits the transaction. The endxact_callback() function executes as follows:
  1. It executes the MI_NORMAL_END case in the switch statement.
  2. If mi_exec() in the MI_NORMAL_END case fails, mi_exec() returns MI_ERROR, as does any DataBlade API function except mi_db_error_raise().
  3. The condition in the if statement evaluates to TRUE and mi_db_error_raise() executes, which raises an exception.
  4. The DataBlade API invokes the registered exception callback, eox_excpt_callback().
  5. Because eox_excpt_callback() returns a status of MI_CB_CONTINUE, the database server aborts the transaction.
    If eox_excpt_callback() had instead returned MI_CB_EXC_HANDLED, execution would continue at the next statement of the endxact_callback() callback:
    mi_close(cb_conn);

    The endxact_callback() function would then return MI_CB_CONTINUE, which would cause the database server to commit current transaction.

  6. Before the database server aborts the transaction, it invokes endxact_callback() with a transition state of MI_ABORT_END. This second invocation of endxact_callback() proceeds as follows:
    1. It executes the MI_ABORT_END case in the switch statement.
    2. It calls mi_exec() to execute the INSERT statement.
    3. If mi_exec() fails, the database server invokes the registered exception callback eox_excpt_callback(). The mi_exec() function does not return MI_ERROR. Because eox_excpt_callback() returns MI_CB_CONTINUE, control does not return to endxact_callback().
If endxact_callback() had not registered its own exception callback, then when mi_exec() in the MI_NORMAL_END case fails, execution would proceed as follows:
  1. The mi_exec() function returns MI_ERROR.
  2. The condition in the if statement evaluates to TRUE and mi_db_error_raise() executes, which raises an exception.
  3. When mi_db_error_raise() throws an exception, the database server aborts the transaction.
  4. Before the database server aborts the transaction, it invokes endxact_callback() with a transition state of MI_ABORT_END. This second invocation of endxact_callback() proceeds as follows:
    1. Execute the MI_ABORT_END case in the switch statement.
    2. Call mi_exec() to execute the INSERT statement.
    3. If mi_exec() fails, it returns MI_ERROR.
    4. The condition in the if statement evaluates to TRUE and the user-defined make_log_entry() function makes a log entry in a text file.

Unlike some other types of callbacks, an end-of-transaction callback can register callbacks of its own. The preceding end-of-transaction callback registers the excpt_callback() to handle database server exceptions that might arise from the INSERT statements that mi_exec() executes. For a sample implementation of the excpt_callback() callback, see A simple exception callback .