A pure MoonBit PostgreSQL client, wire protocol from scratch.
Dependencies
moon add jaredzhou/moonpglet conn = @moonpg.connect("postgres://user:pw@localhost:5432/db")
// Simple query
let rows = conn.query("SELECT id, name FROM users")
while rows.has_next() {
let row = rows.get_row()
let id : Int = row.get(0)
let name : String = row.get_by_name("name")
println("\{id}: \{name}")
}
rows.close()
// Parameterised — extended query protocol
conn.execute(
"UPDATE users SET email = $1 WHERE id = $2",
params=[None, 42], // Option[T] encodes NULL
) |> ignore
conn.close()let pool = Pool::new(PoolConfig::new(
"postgres://user:pw@localhost:5432/db",
max_conns=10,
min_idle=2,
))
// Implicit acquire — rows.close() returns conn to pool
let rows = pool.query("SELECT 1")
while rows.has_next() {
let row = rows.get_row()
// ...
}
rows.close()
let row = pool.query_one("SELECT 42")
pool.execute("INSERT INTO t (x) VALUES ($1)", params=[1]) |> ignore
// Explicit acquire — caller controls release
let pc = pool.acquire()
let r = pc.execute("INSERT INTO t (x) VALUES ($1)", params=[99])
r.close()
pc.release()// System CA (default)
let conn = @moonpg.connect("postgres://user:pw@host/db?sslmode=require")
// Custom CA certificate
let conn = @moonpg.connect(
"postgres://user:pw@host/db?sslmode=verify-ca&sslrootcert=/etc/certs/ca.pem",
)
// Client certificate
let conn = @moonpg.connect(
"postgres://user:pw@host/db?sslmode=require&sslcert=/etc/certs/client.pem&sslkey=/etc/certs/client.key",
)// TCP connect timeout (seconds)
@moonpg.connect("postgres://host/db?connect_timeout=5")
// Server-side statement timeout (milliseconds)
@moonpg.connect("postgres://host/db?statement_timeout=30000")// Ensure we're talking to a read-write primary
@moonpg.connect("postgres://host/db?target_session_attrs=read-write")
// Read-only standby is fine
@moonpg.connect("postgres://host/db?target_session_attrs=read-only")let pid = conn.backend_pid() // server process ID
let ver = conn.param("server_version") // e.g. Some("16.4")
let tz = conn.param("TimeZone") // e.g. Some("UTC")
if conn.is_closed() { ... }@async.with_task_group() <| group => {
let listener = conn.listen("events", group)
// Notify from another connection
group.spawn_bg() <| () => {
let c2 = @moonpg.connect(conninfo)
c2.notify("events", payload="hello")
}
// Block until a notification arrives
let notif = listener.recv()
println("\{notif.channel}: \{notif.payload}")
}// Bulk insert from an iterator — one row in memory at a time
conn.copy_in("COPY users (name, age) FROM STDIN", ["alice\t30", "bob\t25"].iter())
// Streaming COPY writer
let w = conn.begin_copy("users", ["name", "age"])
w.write_row(["alice", 30])
w.write_row(["bob", 25])
let result = w.finish()let pool = Pool::new(PoolConfig::new(
"postgres://user:pw@localhost:5432/db",
max_conns=10,
min_idle=2,
max_idle_sec=300,
max_lifetime_sec=3600,
health_check=true, // ping idle conns before checkout
maintenance_interval_sec=60, // background idle-pool sweeper
))
// Start background maintenance
pool.start_maintenance()
// Inspect pool
let stats = pool.stats()
println("active=\{stats.active_connections} idle=\{stats.idle_connections}")
pool.close()// Auto-commit / rollback
///|
let result = begin_func(conn, async fn(tx) {
tx.execute("INSERT INTO users (name) VALUES ($1)", params=["alice"]) |> ignore
tx.query_one("SELECT id FROM users WHERE name = $1", params=["alice"]).get(0)
}) // exception → rollback; success → commit///|
let id : Int = row.get(0) // raises on NULL
///|
let email : String? = row.get_by_name("email") // NULL → None
///|
let ts : Timestamp = row.get(2) // pg timestamp → Unix µs| user | password | auth method |
|---|---|---|
| moonpg_plain | plain_pass | password |
| moonpg_md5 | md5_pass | md5 |
| moonpg_scram | scram_pass | scram-sha-256 |
# Use env var or defaults (see conn_test.mbt)
MOONPG_CONNINFO="postgres://moonpg_plain:plain_pass@localhost:5432/moonpg_test" \
moon test --target nativewhile rows.has_next() {
let row = rows.get_row()
let v : Int = row.get(0)
}
rows.close()pub(open) trait Tx : QueryExecutor {
async fn commit(Self) -> Unit raise PgError
async fn rollback(Self) -> Unit raise PgError
}impl QueryExecutor for Connectionasync fn execute(self : Connection, sql : String, params? : Array[&ToValue]) -> ExecResult raise PgErrorimpl TxBeginner for Connectionasync fn Connection::begin_copy(self : Connection, table : String, columns : Array[String]) -> CopyWriter raise PgErrorasync fn Connection::copy_in(self : Connection, sql : String, rows : Iter[String]) -> ExecResult raise PgErrorconn.copy_in("COPY t FROM STDIN", my_rows.iter())async fn[X] Connection::listen(self : Connection, channel : String, group : TaskGroup[X]) -> Listener raise PgError@async.with_task_group() <| group => {
let listener = conn.listen("events", group)
for ;; {
let notif = listener.recv()
group.spawn_bg() <| () => { handle(notif) }
}
}async fn Connection::notify(self : Connection, channel : String, payload? : String) -> Unit raise PgErrorlet w = conn.begin_copy("users", ["name", "age"])
w.write_row(["Alice", 30])
w.write_row(["Bob", 25])
let result = w.finish()pub(all) enum IsolationLevel {
ReadCommitted
RepeatableRead
Serializable
}pub(all) struct Pool {
queue : Queue[IdleConn]
conninfo : String
max_conns : Int
min_idle : Int
max_idle_sec : Int
max_lifetime_sec : Int
health_check : Bool
maintenance_interval_sec : Int
count : Ref[Int]
idle_count : Ref[Int]
running : Ref[Bool]
acquire_count : Ref[Int64]
acquire_wait_count : Ref[Int64]
acquire_wait_duration : Ref[Int64]
}impl QueryExecutor for Poolimpl TxBeginner for Poolpub(all) struct PoolConfig {
conninfo : String
max_conns : Int
min_idle : Int
max_idle_sec : Int
max_lifetime_sec : Int
health_check : Bool
maintenance_interval_sec : Int
}let pool = Pool::new(PoolConfig::new("postgres://...", max_conns=10))
// Option A: implicit acquire — Pool auto-manages lifecycle
let rows = pool.query("SELECT 1")
while rows.has_next() { let row = rows.get_row() }
rows.close() // PoolRows.close() returns conn to pool
let row = pool.query_one("SELECT 42")
pool.execute("INSERT ...") |> ignore
let tx = pool.begin_tx()
tx.commit() // PoolDbTx.commit() returns conn to pool
// Option B: explicit acquire — caller manages lifecycle
let pc = pool.acquire()
defer pc.release()
let rows = pc.query("SELECT 1")
rows.close() // PoolRows.close() returns conn to pool
let row = pc.query_one("SELECT 42")
pc.release() // caller releasesfn PoolConfig::new(conninfo : String, max_conns? : Int, min_idle? : Int, max_idle_sec? : Int, max_lifetime_sec? : Int, health_check? : Bool, maintenance_interval_sec? : Int) -> PoolConfigimpl QueryExecutor for PoolDbTxasync fn execute(self : PoolDbTx, sql : String, params? : Array[&ToValue]) -> ExecResult raise PgErrorpub(all) struct PoolStats {
total_connections : Int
idle_connections : Int
active_connections : Int
acquire_count : Int64
acquire_wait_count : Int64
acquire_wait_duration_ms : Int64
}pub(all) struct Timestamp(Int64)pub enum Value {
Null
Bool(Bool)
Int(Int)
Int64(Int64)
Float(Double)
String(String)
Bytes(Bytes)
}let new_id = begin_func(conn, fn(tx) {
let row = tx.query_one("INSERT INTO users (name) VALUES ($1) RETURNING id", params=["alice"])
row.get(0)
})Install
Download zipA pure MoonBit PostgreSQL client, wire protocol from scratch.
Dependencies