ergon_repo (ergon v0.5.0)
View SourceErgon's connection layer: a thin wrapper over pgo.
Ergon's use of the driver is deliberately shallow: the schema is installed by
ergon_migrate, and every statement is raw SQL loaded from priv/queries/
through ergon_sql. Nothing in Ergon assembles SQL at runtime.
Who owns the pool
pgo:start_pool/2 hands the pool to pgo_sup, a simple_one_for_one that
already restarts it, and returns a pid nothing in Ergon is linked to. So the pool
is not an ergon_sup child: ergon_app:start/2 calls start_pool/0 before
starting the tree, and pgo supervises the pool from there. Turning this into a
supervisor child spec would mean returning an unlinked pid to a supervisor, which
is a lie that surfaces later as a tree that will not shut down cleanly.
start_pool/0 is idempotent so a repeated call (tests, a manual restart) is a
no-op rather than an error.
Pool name
The pool is named ergon, not pgo's default, so starting Ergon inside a host
application never clobbers that host's own default pool. Every query here injects
pool => pool(), so callers never have to remember it.
Result shape
pgo:query/3 answers a bare map on success and {error, _} on failure.
query/2,3 normalises the success case to {ok, Map} so callers can pattern
match uniformly, which is what lets ergon_db express each operation as a single
maybe chain instead of nested cases.
Summary
Functions
The name of the pgo pool Ergon runs on.
The pgo pool configuration, resolved from the PG* environment at call time.
Run SQL with positional Params on Ergon's pool.
Like query/2 with extra pgo options.
Start the pool under pgo_sup. Idempotent.
Start an additional named pool, with Overrides merged over the usual PG*
configuration.
Run Fun inside a SQL transaction on Ergon's pool.
Like transaction/1 with extra pgo options.
Types
Functions
-spec pool() -> atom().
The name of the pgo pool Ergon runs on.
Defaults to ergon. Override with {ergon, [{ergon_repo, [{pool, my_pool}]}]}
to share a pool a host application already started.
-spec pool_config() -> pgo:pool_config().
The pgo pool configuration, resolved from the PG* environment at call time.
Read from the environment rather than baked into sys.config so credentials stay
out of the repository and a release picks them up at boot. Same variables and same
defaults as ergon_migrate:connect/0, which is the other half of this contract:
the boot-time migration connection and the runtime pool must reach the same
database.
-spec query(iodata(), [term()]) -> repo_result().
Run SQL with positional Params on Ergon's pool.
-spec query(iodata(), [term()], query_options()) -> repo_result().
Like query/2 with extra pgo options.
The options map is threaded all the way through from ergon_sql:query/3, which
is what lets a test pin every statement in a case to one checked-out connection,
and what lets a long-polling pgmq consumer read on a pool of its own.
pool is defaulted here rather than forced, so a caller that names one wins. One
caveat on that: inside transaction/1,2 the query must stay on the transaction's
pool, and pgo raises {in_other_pool_transaction, _} if it does not. Naming a
different pool is therefore only meaningful outside a transaction.
-spec start_pool() -> {ok, pid()}.
Start the pool under pgo_sup. Idempotent.
Called by ergon_app:start/2 before the supervision tree, so ergon_sql and
every later consumer can assume the pool exists.
The pool registers itself locally under its name, so an already-running pool is
detected with whereis/1 rather than by starting a second one and inspecting the
{already_started, _} error. That is not only tidier: pgo_sup is a
simple_one_for_one, so a duplicate start really does spawn a child that then
fails to register, and the failure surfaces as a supervisor report either way.
The whereis/1 check races only if two processes call this concurrently, which
boot does not.
Start an additional named pool, with Overrides merged over the usual PG*
configuration.
Used for connections that must not come out of the shared pool. A long-polling
pgmq consumer takes one of these sized 1, because its read blocks server-side
and would otherwise hold a shared connection idle for seconds at a time.
-spec transaction(fun(() -> Result)) -> Result | {error, term()}.
Run Fun inside a SQL transaction on Ergon's pool.
-spec transaction(fun(() -> Result), query_options()) -> Result | {error, term()}.
Like transaction/1 with extra pgo options.
pgo binds the transaction's connection in the process dictionary, so every
nested query/2,3 in Fun automatically rides it, with no connection threading.
Two things to know about the commit rule. pgo commits unless Fun raises:
returning {error, _} still commits. That is correct where the error path wrote
nothing (ergon_db:link/3 rejecting a cycle commits an empty transaction), and
harmless where a statement itself failed, because PostgreSQL has already aborted
the transaction and the COMMIT comes back as a rollback. To force a rollback from
a successful-looking path, throw.