minato (minato v0.18.6)

View Source

The interface a caller uses: a pool, and queries against it.

{ok, _Pid} = minato:start_pool(main, #{size => 10,
                                       connection => #{host => "localhost",
                                                       user => ~"minato",
                                                       password => ~"minato",
                                                       database => ~"minato_test"}}),
{ok, #{rows := [{42}]}} = minato:query(main, ~"SELECT $1::int4", [42]).

Every function here borrows a connection, uses it, and gives it back. The connection layer underneath threads a connection through each call, because the caller owns the socket; this module hides that for the ordinary case and with/2 hands it to callers who need several statements on one connection.

Pools can also be declared in the application environment, which is what an application that wants its pool up before anything asks for it needs:

{minato, [{pools, #{main => #{size => 10, connection => #{user => ~"minato"}}}}]}

What a caller gets back

{ok, Result} or {error, Reason}, with no connection in it. A statement that failed on the server gives {error, {pgsql_error, Fields}} and the connection goes back to the pool, because a failed statement is a normal outcome. A connection that failed underneath gives {error, {socket, Reason}} and is closed and replaced rather than lent to somebody else.

Transactions

transaction/2 borrows one connection for the whole of the function, which is what makes it a transaction rather than a sequence of statements that happen to be nearby. The function is handed the connection and hands it back, since statements inside a transaction have to run on that one.

Summary

Types

What a call answers with once the connection has gone back to the pool.

A pool name, as given to start_pool/2 or declared in the environment.

Functions

Hand a borrowed connection to another process.

health/2 with a one second budget.

Ask whether this pool can reach the database right now.

Subscribe the calling process to a channel.

query/4 with the default decoding options.

Run one statement with parameters.

simple/3 with the default decoding options.

Run statements through the simple query protocol, with no parameters.

Start a listener and keep it under minato's supervisor.

Start a pool and keep it under minato's supervisor.

What the pool holds and what it has done.

Stop a listener and close its connection.

Stop a pool and close every connection it holds.

Run a function inside a transaction, on one borrowed connection.

Stop the calling process's subscription.

Borrow a connection for as long as the function needs it.

Types

answer(Value)

-type answer(Value) :: {ok, Value} | {error, term()}.

What a call answers with once the connection has gone back to the pool.

conn()

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

pool()

-type pool() :: minato_pool:name().

A pool name, as given to start_pool/2 or declared in the environment.

Functions

give_away(Pool, Conn, Pid)

-spec give_away(pool(), conn(), pid()) -> ok | {error, term()}.

Hand a borrowed connection to another process.

The socket moves and the pool starts watching the new owner instead of the caller, so a connection outlives the process that borrowed it when that is what was meant. The connection term is the caller's to send on; a receiver that is sent one without this call holds a term whose socket is somebody else's.

health(Pool)

-spec health(pool()) -> ok | {error, term()}.

health/2 with a one second budget.

health(Pool, Timeout)

-spec health(pool(), timeout()) -> ok | {error, term()}.

Ask whether this pool can reach the database right now.

Borrows a connection and runs SELECT 1 on it, both inside the timeout, so a pool that is up but has no connections, one whose connections have all been taken, and one whose server has gone away all answer {error, _} rather than ok.

This is the readiness probe. It is deliberately not a liveness probe: a pool that cannot reach its database is not broken, it is waiting, and restarting the node for it turns a database outage into an outage of everything. Report it as not ready, keep the process alive, and let the pool reconnect.

listen(Listener, Channel)

-spec listen(minato_listener:name(), binary()) -> ok | {error, term()}.

Subscribe the calling process to a channel.

Notifications arrive as {minato_notification, Channel, Payload, From}, and a reconnect arrives as {minato_listener, Name, resubscribed} - which means notifications were missed, because NOTIFY has no replay. See minato_listener.

query(Pool, Sql, Parameters)

-spec query(pool(), iodata(), [term()]) -> answer(minato_protocol:result()).

query/4 with the default decoding options.

query(Pool, Sql, Parameters, Opts)

-spec query(pool(), iodata(), [term()], minato_query:opts()) -> answer(minato_protocol:result()).

Run one statement with parameters.

The extended protocol, so the parameters are parameters and nothing is pasted into the SQL. See minato_query for what the result holds and which wire formats are asked for.

The statement is parsed once per connection and kept, so the same SQL costs one round trip after the first on each connection in the pool. minato_query:cached/4 documents what fills that cache and what happens when it is full.

simple(Pool, Sql)

-spec simple(pool(), iodata()) -> answer([minato_protocol:result()]).

simple/3 with the default decoding options.

simple(Pool, Sql, Opts)

-spec simple(pool(), iodata(), minato_query:opts()) -> answer([minato_protocol:result()]).

Run statements through the simple query protocol, with no parameters.

One result per statement. For DDL, SET, and anything else with no values in it; a statement with a value in it belongs in query/3.

start_listener(Name, Opts)

Start a listener and keep it under minato's supervisor.

A listener holds one connection of its own, because LISTEN is session state and a pooled connection would deliver notifications to whichever caller happened to hold it next.

start_pool(Name, Opts)

-spec start_pool(pool(), minato_pool:opts()) -> supervisor:startchild_ret().

Start a pool and keep it under minato's supervisor.

The pool is up before it has connections: they are opened in the background and retried with backoff, so an application that starts before its database starts anyway and the first query waits.

stats(Pool)

-spec stats(pool()) -> minato_pool:stats().

What the pool holds and what it has done.

stop_listener(Name)

-spec stop_listener(minato_listener:name()) -> ok | {error, not_found}.

Stop a listener and close its connection.

stop_pool(Name)

-spec stop_pool(pool()) -> ok | {error, not_found}.

Stop a pool and close every connection it holds.

transaction(Pool, Fun)

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

Run a function inside a transaction, on one borrowed connection.

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

{ok, Value} for a committed transaction, {rollback, Reason} for one the function asked to roll back, and {error, transaction_rolled_back} for a COMMIT the server turned into a ROLLBACK, which is not success. See minato_txn.

unlisten(Listener, Channel)

-spec unlisten(minato_listener:name(), binary()) -> ok | {error, term()}.

Stop the calling process's subscription.

with(Pool, Fun)

-spec with(pool(), fun((conn()) -> {keep, Value, conn()} | {discard, Value})) -> Value | {error, term()}.

Borrow a connection for as long as the function needs it.

The function is handed the connection and answers {keep, Value, Conn} with the connection it finished with, or {discard, Value} for a connection that should not be lent to anybody else. An exception discards the connection and is re-raised, because a connection abandoned part way through a result set has bytes on it that nobody has read.