minato_txn (minato v0.18.6)

View Source

Transactions on a connection.

A transaction is session state rather than a statement, which is why it is here rather than in minato_query: every statement between BEGIN and COMMIT has to run on the same connection, and a client where that is not guaranteed has a transaction only by accident.

{ok, Value, Conn1} = minato_txn:transaction(Conn, fun(Open) ->
    {ok, _Result, Written} = minato_query:query(Open, ~"INSERT INTO t VALUES ($1)", [1]),
    {ok, done, Written}
end).

The function is given the connection and returns one back, because everything in this client threads the connection through. Returning {rollback, Reason, Conn} rolls back and reports Reason; raising rolls back and re-raises.

A COMMIT the server turns into a ROLLBACK

PostgreSQL answers COMMIT with the tag ROLLBACK when the transaction had already failed. Nothing was written, and a client that reads only "the COMMIT succeeded" reports success for work the server threw away. That is the failure mode that makes a job queue run a job twice or an ledger lose an entry, so commit/1 compares the tag it got with the statement it sent and answers {error, transaction_rolled_back, Conn} when they disagree.

ReadyForQuery carries the same news in its status byte, and status/1 reports it: idle outside a transaction, transaction inside a good one, failed inside one where a statement has already errored and everything further will be rejected until it is rolled back.

Savepoints

savepoint/2 and rollback_to/2 are the nested case. There is no nested transaction/2, because SQL has no nested BEGIN: a second BEGIN inside a transaction is a warning and a no-op, and a client that hid that would report a rollback the server never did.

Summary

Types

What went wrong. transaction_rolled_back is a COMMIT the server refused.

What the caller's function answers with.

Functions

Commit a transaction.

Begin a transaction.

Forget a savepoint, keeping everything done since it.

Roll a transaction back. A connection with nothing open is left as it is.

Undo everything since a savepoint, keeping the transaction open.

Name a point inside the transaction that rollback_to/2 can return to.

What the server last said about the transaction on this connection.

Run a function inside a transaction.

Types

conn()

-type conn() :: minato_conn:conn().

error()

-type error() :: minato_query:error() | transaction_rolled_back.

What went wrong. transaction_rolled_back is a COMMIT the server refused.

outcome()

-type outcome() :: {ok, term(), conn()} | {rollback, term(), conn()} | {error, error()}.

What the caller's function answers with.

{ok, Value, Conn} commits and {rollback, Reason, Conn} rolls back, both handing back the connection they finished with. {error, Reason} has no connection in it and is for the case where there is nothing left to finish the transaction on: a statement that failed at the socket rather than at the server. Neither COMMIT nor ROLLBACK is attempted for it.

Functions

commit(Conn)

-spec commit(conn()) -> {ok, conn()} | {error, error(), conn()} | {error, error()}.

Commit a transaction.

{error, transaction_rolled_back, Conn} is a server that answered the COMMIT with ROLLBACK because the transaction had already failed. Nothing was written.

open(Conn)

-spec open(conn()) -> {ok, conn()} | {error, error(), conn()} | {error, error()}.

Begin a transaction.

release(Conn, Name)

-spec release(conn(), binary()) -> {ok, conn()} | {error, error(), conn()} | {error, error()}.

Forget a savepoint, keeping everything done since it.

rollback(Conn)

-spec rollback(conn()) -> {ok, conn()} | {error, error(), conn()} | {error, error()}.

Roll a transaction back. A connection with nothing open is left as it is.

rollback_to(Conn, Name)

-spec rollback_to(conn(), binary()) -> {ok, conn()} | {error, error(), conn()} | {error, error()}.

Undo everything since a savepoint, keeping the transaction open.

This is what makes a failed statement recoverable: a transaction that has errored rejects everything until it is rolled back, and rolling back to a savepoint taken before the statement clears that without losing the work before it.

savepoint(Conn, Name)

-spec savepoint(conn(), binary()) -> {ok, conn()} | {error, error(), conn()} | {error, error()}.

Name a point inside the transaction that rollback_to/2 can return to.

status(Conn)

What the server last said about the transaction on this connection.

failed is a transaction in which a statement has already errored: every further statement is rejected until it is rolled back.

transaction(Conn, Fun)

-spec transaction(conn(), fun((conn()) -> outcome())) ->
                     {ok, term(), conn()} |
                     {rollback, term(), conn()} |
                     {error, error(), conn()} |
                     {error, error()}.

Run a function inside a transaction.

BEGIN first, the function next, then COMMIT for {ok, Value, Conn} and ROLLBACK for {rollback, Reason, Conn}. An exception rolls back and is re-raised, so a throw out of the middle of a transaction cannot leave the connection holding an open one.

Answers {error, transaction_rolled_back, Conn} when the server turned the COMMIT into a ROLLBACK, which is the case a caller must not read as success.

A function that answers {error, Reason} reports a connection that is gone: no COMMIT and no ROLLBACK is attempted, because there is nothing to attempt them on, and the failure is passed straight back.