Transactor framework


class  pqxx::transactor< TRANSACTION >


using pqxx::transactor< TRANSACTION >::argument_type = TRANSACTION


template<typename TRANSACTION_CALLBACK >
auto pqxx::perform (const TRANSACTION_CALLBACK &callback, int attempts=3) -> decltype(callback())
 Simple way to execute a transaction with automatic retry. More...
 pqxx::transactor< TRANSACTION >::transactor (const std::string &TName="transactor")
void pqxx::transactor< TRANSACTION >::operator() (TRANSACTION &T)
 Overridable transaction definition; insert your database code here. More...
void pqxx::transactor< TRANSACTION >::on_abort (const char[]) noexcept
 Optional overridable function to be called if transaction is aborted. More...
void pqxx::transactor< TRANSACTION >::on_commit ()
 Optional overridable function to be called after successful commit. More...
void pqxx::transactor< TRANSACTION >::on_doubt () noexcept
 Overridable function to be called when "in doubt" about outcome. More...
std::string pqxx::transactor< TRANSACTION >::name () const
 The transactor's name. More...
template<typename TRANSACTOR >
void pqxx::connection_base::perform (const TRANSACTOR &T, int Attempts)

Detailed Description

Sometimes your application needs to execute a transaction that should be retried if it fails. For example, your REST API might be handling an HTTP request in its own database transaction, and if it fails for transient reasons, you simply want to "replay" the whole request from the start, in a fresh transaction.

One of those transient reasons might be a deadlock during a SERIALIZABLE or REPEATABLE READ transaction. Another reason might be that your network connection to the database fails, and perhaps you don't just want to give up when that happens.

In situations like these, the right thing to do is often to restart your transaction from scratch. You won't necessarily want to execute the exact same SQL commands with the exact same data, but you'll want to re-run the same application code that produced those SQL commands.

The transactor framework makes it a little easier for you to do this safely, and avoid typical pitfalls. You encapsulate the work that you want to do in a transaction into something that you pass to perform.

Transactors come in two flavours.

The old, pre-C++11 way is to derive a class from the transactor template, and pass an instance of it to your connection's connection_base::perform member function. That function will create a transaction object and pass it to your transactor, handle any exceptions, commit or abort, and repeat as appropriate.

The new, simpler C++11-based way is to write your transaction code as a lambda (or other callable), which creates its own transaction object, does its work, and commits at the end. You pass that callback to pqxx::perform. If any given attempt fails, its transaction object goes out of scope and gets destroyed, so that it aborts implicitly. Your callback can return its results to the calling code.

Typedef Documentation

◆ argument_type

template<typename TRANSACTION = transaction<read_committed>>
using pqxx::transactor< TRANSACTION >::argument_type = TRANSACTION

Function Documentation

◆ name()

template<typename TRANSACTION = transaction<read_committed>>
std::string pqxx::transactor< TRANSACTION >::name ( ) const

The transactor's name.

◆ on_abort()

template<typename TRANSACTION = transaction<read_committed>>
void pqxx::transactor< TRANSACTION >::on_abort ( const char  [])

Optional overridable function to be called if transaction is aborted.

This need not imply complete failure; the transactor will automatically retry the operation a number of times before giving up. on_abort() will be called for each of the failed attempts.

One parameter is passed in by the framework: an error string describing why the transaction failed. This will also be logged to the connection's notice processor.

◆ on_commit()

template<typename TRANSACTION = transaction<read_committed>>
void pqxx::transactor< TRANSACTION >::on_commit ( )

Optional overridable function to be called after successful commit.

If your on_commit() throws an exception, the actual back-end transaction will remain committed, so any changes in the database remain regardless of how this function terminates.

◆ on_doubt()

template<typename TRANSACTION = transaction<read_committed>>
void pqxx::transactor< TRANSACTION >::on_doubt ( )

Overridable function to be called when "in doubt" about outcome.

This may happen if the connection to the backend is lost while attempting to commit. In that case, the backend may have committed the transaction but is unable to confirm this to the frontend; or the transaction may have failed, causing it to be rolled back, but again without acknowledgement to the client program. The best way to deal with this situation is typically to wave red flags in the user's face and ask him to investigate.

The robusttransaction class is intended to reduce the chances of this error occurring, at a certain cost in performance.

See also

◆ operator()()

template<typename TRANSACTION = transaction<read_committed>>
void pqxx::transactor< TRANSACTION >::operator() ( TRANSACTION &  T)

Overridable transaction definition; insert your database code here.

The operation will be retried if the connection to the backend is lost or the operation fails, but not if the connection is broken in such a way as to leave the library in doubt as to whether the operation succeeded. In that case, an in_doubt_error will be thrown.

Recommended practice is to allow this operator to modify only the transactor itself, and the dedicated transaction object it is passed as an argument. This is what makes side effects, retrying etc. controllable in the transactor framework.

TDedicated transaction context created to perform this operation.

Referenced by pqxx::transactor< TRANSACTION >::transactor().

◆ perform() [1/2]

template<typename TRANSACTION_CALLBACK >
auto pqxx::perform ( const TRANSACTION_CALLBACK &  callback,
int  attempts = 3 
) -> decltype(callback())

Simple way to execute a transaction with automatic retry.

Executes your transaction code as a callback. Repeats it until it completes normally, or it throws an error other than the few libpqxx-generated exceptions that the framework understands, or after a given number of failed attempts, or if the transaction ends in an "in-doubt" state.

(An in-doubt state is one where libpqxx cannot determine whether the server finally committed a transaction or not. This can happen if the network connection to the server is lost just while we're waiting for its reply to a "commit" statement. The server may have completed the commit, or not, but it can't tell you because there's no longer a connection.

Using this still takes a bit of care. If your callback makes use of data from the database, you'll probably have to query that data within your callback. If the attempt to perform your callback fails, and the framework tries again, you'll be in a new transaction and the data in the database may have changed under your feet.

Also be careful about changing variables or data structures from within your callback. The run may still fail, and perhaps get run again. The ideal way to do it (in most cases) is to return your result from your callback, and change your program's data after perform completes successfully.

This function replaces an older, more complicated transactor framework. The new function is a simpler, more lambda-friendly way of doing the same thing.

callbackTransaction code that can be called with no arguments.
attemptsMaximum number of times to attempt performing callback. Must be greater than zero.
Whatever your callback returns.

Referenced by pqxx::connection_base::set_client_encoding().

◆ perform() [2/2]

template<typename TRANSACTOR >
void pqxx::connection_base::perform ( const TRANSACTOR &  T,
int  Attempts 
Pre-C++11 transactor function.

This has been superseded by the new transactor framework and pqxx::perform.

Invokes the given transactor, making at most Attempts attempts to perform the encapsulated code. If the code throws any exception other than broken_connection, it will be aborted right away.

TThe transactor to be executed.
AttemptsMaximum number of attempts to be made to execute T.

◆ transactor()

template<typename TRANSACTION = transaction<read_committed>>
pqxx::transactor< TRANSACTION >::transactor ( const std::string &  TName = "transactor< TRANSACTION >")