SagaSaga
SagaPgo

Getting started

A short walk through wiring up a connection and running your first query.

Install

Add saga_pgo to your project.toml:

[deps]
saga_pgo = { git = "https://github.com/dylantf/saga_pgo" }

Then saga install to fetch it.

Configure a connection

A Config describes how to reach the database. default_config fills in sensible defaults (localhost, port 5432, postgres user, no password) and you override what you need with a record update:

import SagaPgo (default_config, connect)

let config =
  { default_config "mydb" |
    user: "postgres",
    password: "postgres",
    port: 5432,
  }

let conn = connect config

connect starts a pgo pool and gives you back a Connection handle. The handle is what you pass to every query, execute, and transaction call. By default, the pool name is the database name.

Each concurrently running pool needs a distinct pool_name. This lets a primary and replica target the same PostgreSQL database without colliding:

let primary_config =
  { default_config "mydb" |
    pool_name: "mydb_primary",
    host: "primary.internal",
  }

let replica_config =
  { default_config "mydb" |
    pool_name: "mydb_replica",
    host: "replica.internal",
  }

let primary = connect primary_config
let replica = connect replica_config

Pool names become registered Erlang atoms. Treat them as a bounded set of names from application configuration; do not generate an unbounded name per request.

Handlers

The Postgres effect describes how SQL gets executed. The library ships a real handler — pg — that talks to pgo. Install it at the top of your effectful block with with:

import SagaPgo (pg, query)
import SagaPgo.Types as T

main () = {
  let conn = connect config
  let _ = query conn "INSERT INTO users (name) VALUES ($1)" [T.pg_text "Alice"]
  println "inserted"
} with {pg, console}

For transactions, also install pg_transaction — see the transactions guide.

Two ways to run a query

The low-level primitive is query. It takes a SQL string and a flat list of Value parameters:

query conn "SELECT id FROM users WHERE name = $1" [T.pg_text "Alice"]

For anything more than a one-liner, prefer the builder. It tracks placeholder numbering for you and composes cleanly:

sql "SELECT id, name FROM users"
|> push "WHERE name ="
|> push_bind (T.pg_text "Alice")
|> execute conn

Both return Result (Returned Dynamic) QueryError. Returned carries the row count and a list of rows; each row is Dynamic, so you decode columns out of it positionally — see the types and decoding guide.

What's next

  • Queries — the builder in depth, including bulk inserts with push_values.
  • Transactions — wrapping work in a transaction and the rules around it.
  • Types and decoding — encoding parameters and decoding columns back into Saga values.