Module Spindle_postgres.Pool

Connections, made up front and borrowed one at a time: the Postgres backend's pool (Rowtype_postgres.Pool), each connection bounded as Spindle_postgres.connect bounds one, and a transaction on a borrow.

type t
val create : sw:Eio.Switch.t -> net:_ Eio.Net.t -> mono_clock:_ Eio.Time.Mono.t -> ?size:int -> ?wait:Mtime.Span.t -> ?statement_timeout:Mtime.Span.t -> ?idle_in_transaction_timeout:Mtime.Span.t -> ?parameters:(string * string) list -> ?timeout:Mtime.Span.t -> ?statement_cache:int -> ?reset:bool -> ?max_lifetime:Mtime.Span.t -> ?idle_check:Mtime.Span.t -> Rowtype_postgres.Conninfo.t -> (t, [> Rowtype.error ]) Stdlib.result

The pool Rowtype_postgres.Pool.create makes, to the database target names, every option but the bounds passed through. The bounds and parameters are Spindle_postgres.connect's.

val use : ?wait:Mtime.Span.t -> t -> (Rowtype_postgres.conn -> ('a, [> `Pool of [ `Busy | `Closed ] ] as 'e) Stdlib.result) -> ('a, 'e) Stdlib.result

Borrow a connection for work, as Rowtype_postgres.Pool.use lends one, and give it back when work returns. `Pool `Busy -- no connection within the wait -- joins the work's own failures, so that an overloaded server says so instead of answering late, and so does `Pool `Closed, at once, for a pool closed or closing under the borrow, which will never lend again; Spindle_postgres.refusal words both.

val exec : ?wait:Mtime.Span.t -> t -> ('p, 'r) Rowtype.statement -> 'p -> ('r, [> `Pool of [ `Busy | `Closed ] | Rowtype.error ]) Stdlib.result

One statement on a borrowed connection: use around Rowtype_postgres.run, for the read that is the whole of its work. It is not run, which is the pool's own bracket (run).

Spindle_postgres.Pool.exec pool all_users ()
|> Result.map_error Spindle_postgres.refusal
val transaction : ?wait:Mtime.Span.t -> ?keep:('e -> bool) -> ?isolation:Rowtype_postgres.Transaction.isolation -> ?retries:int -> t -> (Rowtype_postgres.conn -> ('a, [> Rowtype_postgres.Transaction.failure | `Pool of [ `Busy | `Closed ] ] as 'e) Stdlib.result) -> ('a, 'e) Stdlib.result

use and Rowtype_postgres.Transaction.within together: borrow a connection and run one transaction on it, at isolation and run again up to retries times, Ok committing and Error rolling back unless keep says otherwise. A retry keeps the connection it borrowed. Every failure of its own -- no connection within the wait, a transaction that did not commit -- arrives in the caller's error type:

Spindle_postgres.Pool.transaction pool (fun db ->
    let* id = Pg.run db add_user (name, email) in
    let* () = Pg.run db log_event (id, "signed_up") in
    Ok id)
type stats = Rowtype_postgres.Pool.stats = {
  1. size : int;
  2. idle : int;
  3. waiting : int;
  4. replaced : int;
}
val stats : t -> stats

As Rowtype_postgres.Pool.stats.

val measure : ?name:string -> t -> Spindle.Metrics.t -> unit

The pool's connections as gauges, read from stats whenever the metrics are, so the pool keeps no count of its own: OpenTelemetry's db.client.connection.count by db.client.connection.state, idle or used, and db.client.connection.pending_requests, the borrows waiting -- each under db.client.connection.pool.name, postgres unless named, so two pools measured on one registry are two series of each.

Spindle_postgres.Pool.measure pool metrics
val check : ?name:string -> t -> Spindle.Health.check

The pool as a readiness check, postgres unless named: a connection borrowed and select 1 answered, within the pool's wait and the statement timeout. A pool with no connection idle passes without a borrow, since every one of them is lent to a request the database is answering -- failing it would take a server out of service for being busy, which is when it is needed most. A pool closed, or closing, fails it.

val close : t -> unit

As Rowtype_postgres.Pool.close.

val run : ?size:int -> ?wait:Mtime.Span.t -> ?statement_timeout:Mtime.Span.t -> ?idle_in_transaction_timeout:Mtime.Span.t -> ?parameters:(string * string) list -> ?timeout:Mtime.Span.t -> ?statement_cache:int -> ?reset:bool -> ?max_lifetime:Mtime.Span.t -> ?idle_check:Mtime.Span.t -> < net : _ Eio.Net.t ; mono_clock : _ Eio.Time.Mono.t.. > -> string -> (t -> 'a) -> 'a

run env target f is a program's pool: created on a switch of its own, with the network and the monotonic clock taken from env, handed to f, and closed when f returns or raises; cancelled, it passes the cancellation on, its connections ending with the switch --

Eio_main.run @@ fun env ->
Spindle_postgres.Pool.run env target @@ fun pool ->
Spindle.serve env ~resources:[ Spindle.Dep.resource db pool ] routes

target is a connection string, a postgres:// URL or a keyword list, read once by Rowtype_postgres.Conninfo.of_string. Every option is create's. A pool that cannot be opened -- a database that refuses, a connection string that does not parse -- is an error line on spindle-postgres saying why, and the program exits with status 1, because a server that cannot reach its database has nothing to serve. A program that would rather go on without one calls create.