Errors
GuidesEvery failure is a value, and each says what a caller can do about it.
A statement’s failures
Section titled “A statement’s failures”| Error | What it means |
|---|---|
`Conflict (Some "users_email_key") |
a unique, exclusion or foreign-key constraint refused the write, named |
`Not_serializable |
a transaction a concurrent one won: running it again may succeed |
`Connection `Closed |
the connection was closed before the statement was sent, so nothing it would have done was done |
`Lost |
the connection failed with the statement in flight – a timeout, a broken socket, a server that ended the session – so a write may have been applied |
`Unsent |
a connection that could not be made, or made again – nothing answered – so nothing was sent and trying again is safe |
`Db |
anything else |
A NOT NULL or CHECK violation is the program’s mistake, so it is `Db.
`Conflict is named because a table with two unique columns has two
different answers; an HTTP layer answers it 409, not 500. A lost write is
never run again blindly, only once what it did is known or doing it twice is
harmless. Inside a transaction, a statement lost before its COMMIT did
nothing, since an open transaction ends with its connection; only a lost
COMMIT may have committed.
module S = Rowtypemodule Pg = Rowtype_postgres
let add_account = S.find ~params:S.text ~row:S.int "insert into accounts (email) values ($1) returning id"
type answer = Created of int | Taken | Try_again | Unknown | Failed
let sign_up db email = match Pg.run db add_account email with | Ok id -> Created id | Error (`Conflict (Some "accounts_email_key")) -> Taken | Error (`Unsent _ | `Not_serializable _) -> Try_again | Error (`Lost _) -> Unknown | Error e -> Logs.err (fun m -> m "sign-up: %s" (S.error_to_string e)); FailedThey are polymorphic variants, so they join your own errors in one result. A match on them ends in a catch-all, as here, since a later release may add one.
Words for a log
Section titled “Words for a log”S.error_to_string tells a statement’s failure in words for a log, and
Pg.error_to_string every failure the Postgres backend answers. Neither
ever holds a parameter’s value, since a code, a digest or a token is one: a
conflict is told by its constraint’s name.
Every failure in one result
Section titled “Every failure in one result”Every failure the backend answers is a Pg.error – a statement’s, a
transaction’s own, a pool’s `Pool `Busy or `Pool `Closed, and
verify’s `Disagreements –
so a transaction in a borrowed connection is one result, and
Pg.error_to_string tells all of it:
module Pg = Rowtype_postgres
let sign_up_in pool email = match Result.join (Pg.Pool.use pool (fun db -> Pg.Transaction.within db (fun db -> Pg.run db Sign_up.add_account email))) with | Ok id -> Some id | Error e -> Logs.err (fun m -> m "sign-up: %s" (Pg.error_to_string e)); NoneA transaction’s own failures are told
apart from its work’s, and a pool’s from a
statement’s. SQLite’s are Sq.error, told by Sq.error_to_string, and
its own page says how each arises there.
