SagaPgo
Types
Config
record Config {
host: String,
port: Int,
database: String,
user: String,
password: String,
pool_size: Int,
ssl: Bool
}Connection
opaque type ConnectionQueryError
type QueryError =
| ConstraintViolated (message: String) (constraint: String) (detail: String)
| PostgresqlError (code: String) (severity: String) (message: String)
| UnexpectedArgumentCount (expected: Int) (got: Int)
| UnexpectedArgumentType (expected: String) (got: String)
| UnexpectedResultType (expected: String)
| QueryTimeout
| ConnectionUnavailableReturned
record Returned a {
count: Int,
rows: List a
}QueryBuilder
opaque type QueryBuilderA composable query builder. Build up a SQL string and its parameters with
push, push_bind, and push_values, then finalize by passing it to
build together with an executor function (typically execute).
Effects
Postgres
effect Postgres {
fun raw_execute : Connection -> String -> List Value -> Result (Returned Dynamic) QueryError
}Transaction
effect Transaction {
fun transaction : Connection -> Unit -> Result a QueryError needs {Postgres} -> Result a QueryError
}Transactions. Handlers for Transaction declare needs {Postgres} because
the user's callback uses Postgres operations. The real handler delegates to
pgo:transaction, so all queries inside the callback automatically join the
same transaction connection via pgo's process dictionary.
Multi-shot continuations whose captured slice escapes the callback boundary are not supported and will run their re-invocations outside any transaction.
Handlers
pg
handler pg for PostgresThe real Postgres handler. Stateless — describes how to execute SQL by delegating to the bridge. The connection is passed in by the caller.
pg_transaction
handler pg_transaction for Transaction needs {Postgres}The real Transaction handler. Drives the lifecycle from saga (we can't
pass an effectful saga lambda to pgo:transaction because the lambda
compiles to a CPS closure Erlang can't invoke). The bridge primitives
acquire / release a connection and stash it in pgo's process dictionary,
so raw_execute! calls inside f () automatically join the transaction.
Functions
default_config
fun default_config : String -> Configquery
fun query : Connection -> String -> List Value -> Result (Returned Dynamic) QueryError needs {Postgres}Run a SQL statement directly with a flat parameter list. This is the
low-level primitive — use it when you don't need the builder, or when
building a higher-level query API on top of this library. For application
code that wants ergonomics, prefer the builder: sql "..." |> ... |> execute conn.
transaction
fun transaction : Connection -> Unit -> Result a QueryError needs {Postgres} -> Result a QueryError needs {Postgres, Transaction}Run a callback inside a database transaction. All Postgres effects
performed inside the callback against the same connection are routed to the
transaction, and the transaction commits if the callback returns Ok, rolls
back if it returns Err. Uses pgo's process-dictionary mechanism under the
hood, so don't let captured continuations from inside the callback escape
the boundary — re-invoking them later will run outside any transaction.
sql
fun sql : String -> QueryBuilderStart a new query builder from a SQL prefix. The prefix is treated as raw
text — if it contains pre-written $N placeholders, attach their values
with bind_values. Otherwise, use push_bind / push_values to add
auto-numbered placeholders. Don't mix the two modes for the same query.
bind_values
fun bind_values : List Value -> QueryBuilder -> QueryBuilderBind a flat list of values to the pre-written $N placeholders in the SQL.
Records the values and advances the placeholder counter, but does not emit
any SQL. Use this when the prefix already contains $1, $2, ....
push
fun push : String -> QueryBuilder -> QueryBuilderAppend a SQL fragment, automatically prepending a space so callers don't have to manage spacing manually. Empty strings are a no-op.
push_bind
fun push_bind : Value -> QueryBuilder -> QueryBuilderBind one parameter, emitting a $N placeholder (with leading space) at the
current position so it reads cleanly after a SQL operator like name =.
push_values
fun push_values : (rows: List a) -> (bind_row: a -> List Value) -> QueryBuilder -> QueryBuilderAppend (p1, p2, ...) row groups for each row, auto-numbering placeholders
and flattening parameters. The first call emits values; subsequent calls
append more rows separated by commas, so multiple push_values calls merge
into one contiguous values clause. An empty rows list is a no-op.
Note: don't interleave non-values pushes between consecutive push_values
calls — the comma joining will produce broken SQL.
execute
fun execute : Connection -> QueryBuilder -> Result (Returned Dynamic) QueryError needs {Postgres}Finalize the builder and execute the query against the given connection.
This is the primary way to run queries — use query only if you don't
need the builder. Conn comes first for partial application:
sql "..." |> push_values ... |> execute conn
connect
fun connect : Config -> ConnectionStart a connection pool and return a Connection handle. Pass this to
query, execute, and transaction. Multiple calls to connect give
you multiple independent pools.