minato_txn (minato v0.18.6)
View SourceTransactions 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
-type conn() :: minato_conn:conn().
-type error() :: minato_query:error() | transaction_rolled_back.
What went wrong. transaction_rolled_back is a COMMIT the server refused.
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 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.
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.
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.
Name a point inside the transaction that rollback_to/2 can return to.
-spec status(conn()) -> minato_protocol:transaction_status().
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.
-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.