Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ jobs:
run: nimble test -y

- name: Install async_postgres
run: nimble install -y
run: |
rm -rf ~/.nimble/pkgs2/async_postgres-*
nimble install -y

- name: Compile examples (asyncdispatch)
run: for f in examples/*.nim; do nim c "$f"; done
Expand Down
4 changes: 1 addition & 3 deletions examples/large_object.nim
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,14 @@ proc main() {.async.} =
let size = await lo.loSize()
echo "Size: ", size, " bytes"

# Streaming read
# Streaming read, then clean up in the same transaction
conn.withTransaction:
conn.withLargeObject(lo, oid, INV_READ):
echo "\nStreaming read:"
let cb = makeLoReadCallback:
echo " chunk (", data.len, " bytes): ", data.toString()
await lo.loReadStream(cb, chunkSize = 10)

# Clean up
conn.withTransaction:
await conn.loUnlink(oid)
echo "\nDeleted Large Object: oid=", oid

Expand Down
4 changes: 2 additions & 2 deletions examples/pipeline.nim
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ proc main() {.async.} =
echo "Operation ", i, " (", r.queryResult.rowCount, " rows):"
for row in r.queryResult.rows:
var cols: seq[string]
for j in 0 ..< r.queryResult.fields.len:
cols.add(r.queryResult.fields[j].name & "=" & row.getStr(j))
for field in r.queryResult.fields:
cols.add(field.name & "=" & row.getStr(field.name))
echo " ", cols.join(", ")

waitFor main()
19 changes: 10 additions & 9 deletions examples/pool.nim
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@
import pkg/async_postgres

proc main() {.async.} =
let connConfig = ConnConfig(
host: "127.0.0.1",
port: 15432,
user: "test",
password: "test",
database: "test",
sslMode: sslDisable,
let connConfig = initConnConfig(
host = "127.0.0.1",
port = 15432,
user = "test",
password = "test",
database = "test",
sslMode = sslDisable,
)

let pool = await newPool(PoolConfig(connConfig: connConfig, minSize: 2, maxSize: 5))
let pool = await newPool(initPoolConfig(connConfig, minSize = 2, maxSize = 5))
defer:
await pool.close()

Expand All @@ -41,9 +41,10 @@ proc main() {.async.} =

# Run concurrent queries using withConnection
proc countExpensive(): Future[int64] {.async.} =
let minPrice = 150'i32
pool.withConnection(conn):
return await conn.queryValue(
int64, "SELECT count(*) FROM products WHERE price > $1", @[toPgParam(150'i32)]
int64, sql"SELECT count(*) FROM products WHERE price > {minPrice}"
)

proc cheapest(): Future[string] {.async.} =
Expand Down
18 changes: 9 additions & 9 deletions examples/pool_cluster.nim
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@
import pkg/async_postgres

proc main() {.async.} =
let connConfig = ConnConfig(
host: "127.0.0.1",
port: 15432,
user: "test",
password: "test",
database: "test",
sslMode: sslDisable,
let connConfig = initConnConfig(
host = "127.0.0.1",
port = 15432,
user = "test",
password = "test",
database = "test",
sslMode = sslDisable,
)

let primaryConfig = PoolConfig(connConfig: connConfig, minSize: 1, maxSize: 3)
let replicaConfig = PoolConfig(connConfig: connConfig, minSize: 1, maxSize: 3)
let primaryConfig = initPoolConfig(connConfig, minSize = 1, maxSize = 3)
let replicaConfig = initPoolConfig(connConfig, minSize = 1, maxSize = 3)

# fallbackPrimary: if replica is unavailable, fall back to primary for reads
let cluster =
Expand Down
69 changes: 69 additions & 0 deletions examples/query_direct.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
## Zero-allocation query macros.
##
## Demonstrates `queryDirect` and `execDirect`, which encode parameters into
## the connection's send buffer at compile time. They avoid the intermediate
## `seq[PgParam]` and per-parameter `seq[byte]` allocations that the regular
## `query` / `exec` path incurs, making them suitable for hot paths where
## SQL is a literal and parameters are scalars.
##
## Constraints (vs. regular `query` / `exec`):
## - SQL must be a string literal or compile-time constant.
## - Arguments are positional (`$1, $2, …`) — no `{expr}` sugar.
## - No `timeout` parameter.
## - Same per-connection statement cache as `query` / `exec`; the statement
## is parsed once server-side and rebound on subsequent calls.
##
## Usage:
## nim c -r examples/query_direct.nim

import pkg/async_postgres

const Dsn = "postgresql://test:test@127.0.0.1:15432/test?sslmode=disable"

proc main() {.async.} =
let conn = await connect(Dsn)
defer:
await conn.close()

discard await conn.exec(
"""
CREATE TEMP TABLE metrics (
id serial PRIMARY KEY,
host text NOT NULL,
cpu float8 NOT NULL,
ts int8 NOT NULL
)
"""
)

# execDirect: zero-alloc INSERT in a tight loop. The SQL literal is parsed
# once (server-side plan cached), and each call re-binds scalars directly
# into the send buffer without heap allocations for the parameter list.
let host = "worker-1"
for i in 0 ..< 5:
let cpu = 0.1 * float64(i)
let ts = int64(1_700_000_000 + i)
discard await conn.execDirect(
"INSERT INTO metrics (host, cpu, ts) VALUES ($1, $2, $3)", host, cpu, ts
)

# queryDirect: zero-alloc read. Returns the same QueryResult shape as `query`.
let threshold = 0.2'f64
let qr = await conn.queryDirect(
"SELECT host, cpu, ts FROM metrics WHERE cpu >= $1 ORDER BY ts", threshold
)
echo "Rows above ", threshold, ":"
for row in qr.rows:
echo " host=",
row.getStr("host"), " cpu=", row.getStr("cpu"), " ts=", row.getInt("ts")

# queryDirect drives `queryValue` the same way: wrap it manually since
# `queryValue[T]` takes a regular `seq[PgParam]`. For a single scalar,
# `queryDirect` + `rows[0].get(0, T)` is zero-alloc end-to-end.
let targetHost = "worker-1"
let count = await conn.queryDirect(
"SELECT count(*)::int8 FROM metrics WHERE host = $1", targetHost
)
echo "Rows for ", targetHost, ": ", count.rows[0].get(0, int64)

waitFor main()
99 changes: 99 additions & 0 deletions examples/query_variants.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
## Query variants example.
##
## Demonstrates query convenience procs beyond the basic `query` / `exec` /
## `queryValue` entry points: optional-result accessors, existence checks,
## row callbacks, and the simple-protocol variants for session-level commands
## or multi-statement batches.
##
## Usage:
## nim c -r examples/query_variants.nim

import std/options

import pkg/async_postgres

const Dsn = "postgresql://test:test@127.0.0.1:15432/test?sslmode=disable"

proc main() {.async.} =
let conn = await connect(Dsn)
defer:
await conn.close()

# simpleExec: session-level command via the simple query protocol.
# Appropriate for SET / VACUUM / single-shot DDL where the extended
# protocol's Parse/Bind round trip and plan cache entry would be wasted.
discard await conn.simpleExec("SET TIME ZONE 'UTC'")

discard await conn.exec(
"""
CREATE TEMP TABLE employees (
id serial PRIMARY KEY,
name text NOT NULL,
team text,
salary int4
)
"""
)
discard await conn.exec(
"""
INSERT INTO employees (name, team, salary) VALUES
('Alice', 'platform', 800),
('Bob', 'platform', 650),
('Charlie', 'data', NULL),
('Dave', NULL, 700)
"""
)

# queryExists: boolean "does any row match" without fetching data.
let dataTeam = "data"
let hasData =
await conn.queryExists(sql"SELECT 1 FROM employees WHERE team = {dataTeam}")
echo "Has data team? ", hasData

# queryValueOrDefault: return a fallback when the row or value is NULL.
# Here Charlie has no salary, so the default kicks in.
let charlieName = "Charlie"
let charlieSalary = await conn.queryValueOrDefault(
int32, sql"SELECT salary FROM employees WHERE name = {charlieName}", default = 0'i32
)
echo "Charlie's salary (0 = unknown): ", charlieSalary

# queryValueOpt: distinguish "no row / NULL" from a real value.
let daveName = "Dave"
let maybeTeam =
await conn.queryValueOpt(sql"SELECT team FROM employees WHERE name = {daveName}")
if maybeTeam.isSome:
echo "Dave's team: ", maybeTeam.get
else:
echo "Dave has no team"

# queryRowOpt: single-row lookup that tolerates a missing row.
let nobody = "Nobody"
let missing = await conn.queryRowOpt(
sql"SELECT name, salary FROM employees WHERE name = {nobody}"
)
echo "Missing lookup isSome: ", missing.isSome

# queryColumn: collect one column across all rows as `seq[string]`.
let names = await conn.queryColumn("SELECT name FROM employees ORDER BY id")
echo "Names: ", names

# queryEach: stream rows through a callback without building a QueryResult.
# The Row is only valid during the callback — copy out what you need.
var totalSalary = 0'i64
let rowCb: RowCallback = proc(row: Row) =
if not row.isNull("salary"):
totalSalary += row.getInt("salary")
let processed = await conn.queryEach("SELECT salary FROM employees", callback = rowCb)
echo "Processed ", processed, " rows, total salary: ", totalSalary

# simpleQuery: multiple `;`-separated statements in one round trip.
# Returns one QueryResult per statement. Handy for read-only introspection
# batches; not usable for parameterised queries.
let multi = await conn.simpleQuery(
"SELECT count(*) FROM employees; SELECT count(DISTINCT team) FROM employees"
)
echo "Total employees: ", multi[0].rows[0].getStr(0)
echo "Distinct teams: ", multi[1].rows[0].getStr(0)

waitFor main()