postgres://¶
PostgreSQL via jackc/pgx/v5 (using pgxpool). Provides a LISTEN/NOTIFY consumer and shares the sql:// component's SQL surface (pg can also use
sql://?driver=pgx&dsn=…).
URI shape¶
operation ∈ {query, insert, update, delete, execute,
copy, notify}.
The copy operation bulk-loads data using the PostgreSQL COPY
protocol (COPY … FROM STDIN) — the in-process equivalent of
psql \copy. It streams a file or CSV body straight to the server in
constant memory, with no per-row SQL, so GB-scale loads run at
set-based speed instead of paying the interpreted per-row cost of the
insert path (which trips the starlang eval cap at scale). See
Bulk COPY loader below.
LISTEN/NOTIFY consumer engages when either:
- ?channel=<name> is set (works with any path), OR
- the URI shape is postgres://<channel>/notify (path-driven).
The producer's notify operation issues pg_notify(channel, payload)
with the In body as the payload.
Parameters¶
| Param | Type | Default | Description |
|---|---|---|---|
dsn |
string | required | pgx-style connection string (postgres://user:pass@host:port/db?sslmode=…). |
channel |
string | — | Consumer: LISTEN channel name. When set, the consumer subscribes via LISTEN/NOTIFY instead of polling. Producer: target channel for the notify op. |
pollInterval |
duration | 30s |
Consumer (polling): interval between polls. |
pollSince |
string | — | Consumer (polling): column used as a watermark for change-data-capture polling. |
file |
string | — | Producer (copy): stream this local file into COPY … FROM STDIN instead of the exchange body. Constant memory — never materialised in Helix. |
columns |
string | — | Producer (copy): comma-separated column list. Raw-stream: order must match the CSV; positional row bodies: names the target columns. Omit (raw-stream) to load the table's columns in definition order. |
format |
string | csv |
Producer (copy): csv | text | binary. |
header |
bool | false |
Producer (copy): when true, the first CSV line is the header row and is skipped. |
delimiter |
string | , |
Producer (copy): field delimiter (csv/text). |
null |
string | — | Producer (copy): the string that represents SQL NULL (csv/text). |
quote |
string | " |
Producer (copy, csv): quote character. |
escape |
string | quote char | Producer (copy, csv): escape character. |
The SQL producer surface (CRUD ops plus outputType, :#name placeholders, batch, transacted) is shared
with the sql:// component. To opt into that surface,
either:
- Use
sql://...?driver=pgx&dsn=…(cross-driver, pgx viadatabase/sql), or - Stay on
postgres://for the pgx-native pool and CRUD ops.
Header conventions¶
LISTEN/NOTIFY consumer writes:
| Header | Value |
|---|---|
HelixPgChannel |
The notify channel name. |
HelixPgNotifyPID |
Backend PID that sent the NOTIFY. |
HelixPgPayload |
The notify payload (also set as the body). |
postgres.channel / postgres.notifyPid |
Legacy aliases (also written). |
postgres://...?notify producer writes:
| Header | Value |
|---|---|
postgres.channel |
The target channel. |
Body shape¶
- LISTEN/NOTIFY consumer: body is the payload string.
notifyproducer: body must be a string (or anything stringifiable viafmt.Sprintf("%v", body)).- CRUD producer: same as
sql://—map[string]anyfor insert/update,{query, params}for execute, etc. copyproducer: raw CSV bytes/string body (or afile=path) for raw-stream mode; a row slice ([]map[string]any,[][]any,[][]string) for row mode. Writespostgres.copyRows/postgres.rowsAffected= the number of rows loaded.
Examples¶
LISTEN consumer¶
trigger:
uri: postgres://?dsn=postgres://app:secret@db:5432/app&channel=alerts
steps:
- log:
message: "Got alert: ${body} on ${headers.HelixPgChannel}"
NOTIFY producer¶
- to: postgres://alerts/notify?dsn=postgres://app:secret@db:5432/app
# body becomes the payload of NOTIFY alerts, 'whatever-this-is'
CRUD insert¶
- to: postgres://users/insert?dsn=postgres://app:secret@db:5432/app
# body: {"name": "alice", "email": "a@example.com"}
CDC polling consumer (Helix legacy)¶
trigger:
uri: postgres://events/query?dsn=…&pollInterval=2s&pollSince=created_at
steps:
- process:
ref: handle-event
Bulk COPY loader¶
The copy op is the fast path for loading large files — the in-process
psql \copy. Point it at a file and it streams straight into the table
(≈1 M rows / 75 MB in ~2 s at constant memory; Postgres parses the CSV
server-side, so multiline quoted fields are handled natively):
The historical-backfill pattern — COPY into a staging table, then one
set-based INSERT … SELECT — keeps the whole job server-side (no
starlang, no 30 s eval cap, minutes for GB):
workflows:
- id: backfill-invoices
trigger: { uri: "timer://once?repeatCount=1" }
steps:
# 1. Stream the raw CSV into a staging table (constant memory).
- to: "postgres://staging.invoices_raw/copy?dsn=${PG_DSN}&file=/data/hist/invoices_2026.csv&header=true"
# 2. One set-based transform into the target (client filter, dedup, keys…).
# Set the exchange body to {query, params} first, then:
- to: "postgres://billing_iq_uat.invoice_details/execute?dsn=${PG_DSN}"
You can also feed copy from a streaming csv:// source in row
mode — each csv://?stream=true&batch=N batch is a []map[string]any
that copy loads via pgx.CopyFrom:
trigger: { uri: "csv:///data/big.csv?stream=true&batch=50000" }
steps:
- to: "postgres://staging.rows/copy?dsn=${PG_DSN}&columns=invoice_id,client_id,amount"
See also¶
sql://— driver-agnostic sibling with the full SQL surface.