torb/postgres - a PostgreSQL driver for TorbScript in pure TorbScript: SCRAM, TLS, parameters, LISTEN/NOTIFY
  • Python 93%
  • Shell 7%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Orion 7774d12c2b
Some checks failed
ci / ci (pull_request) Successful in 51s
ci / integration (pull_request) Successful in 1m25s
pdisc / pdisc (pull_request) Successful in 17s
ci / ci (push) Failing after 14s
ci / integration (push) Failing after 9s
pdisc / pdisc (push) Successful in 9s
chore: install pdisc (frontmatter, INDEX, CI gate)
2026-10-07 08:10:33 +02:00
.forgejo/workflows chore: install pdisc (frontmatter, INDEX, CI gate) 2026-10-07 08:10:33 +02:00
.githooks chore: install pdisc (frontmatter, INDEX, CI gate) 2026-10-07 08:10:33 +02:00
.github/workflows ci: install the locked dependencies before checking, testing and publishing 2026-09-29 07:11:08 +02:00
examples feat: a PostgreSQL driver: protocol 3.0, SCRAM, md5, TLS, parameters and types 2026-09-29 00:20:36 +02:00
scripts feat: a PostgreSQL driver: protocol 3.0, SCRAM, md5, TLS, parameters and types 2026-09-29 00:20:36 +02:00
src docs: the examples of PostgresSettings and ScramClient run as doc tests 2026-09-29 00:40:41 +02:00
tests feat: a PostgreSQL driver: protocol 3.0, SCRAM, md5, TLS, parameters and types 2026-09-29 00:20:36 +02:00
tools/pdisc chore: install pdisc (frontmatter, INDEX, CI gate) 2026-10-07 08:10:33 +02:00
.gitignore feat: a PostgreSQL driver: protocol 3.0, SCRAM, md5, TLS, parameters and types 2026-09-29 00:20:36 +02:00
.pdisc.yaml chore: install pdisc (frontmatter, INDEX, CI gate) 2026-10-07 08:10:33 +02:00
INDEX.md chore: install pdisc (frontmatter, INDEX, CI gate) 2026-10-07 08:10:33 +02:00
LICENSE feat: a PostgreSQL driver: protocol 3.0, SCRAM, md5, TLS, parameters and types 2026-09-29 00:20:36 +02:00
project.lock.trb build: take torb/sql 0.1.0 from packages.torb.dev instead of the sibling directory 2026-09-29 07:09:38 +02:00
project.trb build: take torb/sql 0.1.0 from packages.torb.dev instead of the sibling directory 2026-09-29 07:09:38 +02:00
README.md chore: install pdisc (frontmatter, INDEX, CI gate) 2026-10-07 08:10:33 +02:00

title summary keywords type updated body_hash
torb/postgres: PostgreSQL driver for TorbScript README of the pure-TorbScript PostgreSQL driver (wire protocol 3.0, SCRAM-SHA-256, TLS) that implements the torb/sql traits. Read when connecting a TorbScript program to PostgreSQL.
postgres
driver
scram-sha-256
wire protocol
tls
torb/sql
pool
reference 2026-10-07 e894c8322be8

torb/postgres

A PostgreSQL driver for TorbScript, in TorbScript over std/network and std/tls - no C, no libpq. It speaks the wire protocol 3.0, signs in with SCRAM-SHA-256, md5, a password in the clear or trust, encrypts through the TLS request, and is a driver of torb/sql: its Pool, Transaction, Row and SqlError are the ones every program uses.

use Pool, SqlError, decodedRows from "torb/sql"
use Postgres from "torb/postgres"

type Order {
  id: Int
  customer: String
  total: String
}

fn recentOrders(): Task<Result<List<Order>, SqlError>> {
  using pool = Pool(Postgres(), Uri.tryFrom("postgres://app:secret@localhost/shop?sslmode=require")?)
  const rows = pool.query("SELECT id, customer, total FROM orders WHERE placed > now() - $1::interval", [.Text("7 days")]).await()?
  decodedRows<Order>(rows)
}

Connecting

The connection string is libpq's: postgres://user:password@host:port/database?parameters. The query takes sslmode, application_name, connect_timeout (seconds), user, password, dbname, host and port; every other parameter is a run-time parameter of the session (?search_path=shop&statement_timeout=5000).

sslmode What it does
disable No TLS
allow, prefer (default) TLS where the server offers it and its certificate is trusted; in the clear otherwise
require, verify-ca, verify-full TLS with the certificate checked against the host's name, or no connection

require checks the certificate, unlike libpq's: std/tls has no switch that turns the check off. A server whose certificate a private root signed needs that root: Postgres(tls: TlsSettings(trusted: [rootPem])), or PostgresConnection.open(uri, TlsSettings(trusted: [rootPem])).

Statements

  • query(statement, parameters) runs one statement through the extended protocol and answers its rows. Placeholders are $1, $2; the parameters travel apart from the statement.
  • execute(statement, parameters) answers the count of rows touched. Without parameters it uses the simple protocol, which runs a script of several statements separated by ;.
  • command(statement, parameters) answers the command tag: INSERT 0 1, CREATE TABLE.
  • Transaction.begin(connection) or pool.begin() - released without commit(), a transaction rolls back.
  • takeNotices() answers the notices beside the statements (RAISE NOTICE); notification() waits for the next NOTIFY on a channel the session listens to - on a connection of its own, since it runs no statement while it waits.

A statement cancelled in the middle - a within that ran out - leaves the connection broken: every later statement fails at once as closed, and a pool drops it.

Types

Every value crosses in the text format. A column arrives as the SqlValue its type maps to; a parameter says its type by its OID, except text and JSON, whose type the server takes from where they stand.

Postgres SqlValue Notes
boolean Bool
smallint, integer, bigint, oid Integer a parameter goes as int8
real, double precision Real NaN and the infinities both ways
numeric Numeric its digits as text: std's Decimal does not compute yet
text, varchar, char, name Text
bytea Binary hex and escape output both read
timestamp with time zone Timestamp any session TimeZone; microseconds kept
timestamp LocalTimestamp the wall-clock digits, held as UTC
date Date of torb/sql
uuid Uuid of torb/sql
json, jsonb Json the document's text
arrays of all of these Array nested for more dimensions; bounds that do not start at 1 are dropped
everything else Text the server's spelling: interval, inet, money, geometric types, enums; infinity as a date or timestamp

Not supported yet

  • COPY ... FROM STDIN and TO STDOUT (refused, and the connection stays usable), the binary format, named prepared statements that are kept, a request to cancel a running statement, Unix sockets, several hosts in one string, and the PG* environment variables.
  • SCRAM-SHA-256-PLUS: channel binding needs the server's certificate, which std/tls does not hand out.
  • SASLprep of a password: a password is taken as its UTF-8 bytes, which is what Postgres itself does for one SASLprep refuses.
  • Signing in with SCRAM runs PBKDF2 with 4096 rounds in TorbScript: about 50 ms natively and about 3 seconds in the VM (torb run without --native), until std/digest has HMAC and PBKDF2 of its own.

Developing

$ torb format --check .
$ torb check .
$ torb test tests/unit                      # the codec, the types, the cryptography, a server double - no Docker
$ eval "$(sh scripts/postgres.sh start)"    # postgres:17-alpine in Docker, md5 and TLS on
$ torb test tests/integration               # against it
$ torb doc --check .
$ sh scripts/postgres.sh stop

tests/unit/server.test.trb runs the connection against a double of a server on loopback - trust, a password in the clear, md5, refusals, TLS through the TLS request, errors, notices and notifications, a connection that breaks - and tests/integration/postgres.test.trb against a real Postgres. The CI workflows start postgres:17-alpine as a service container with md5 on; TLS against a real server runs where TORB_POSTGRES_TLS=1 (the script sets it).

While torb/sql is not published, project.trb takes it from ../sql (source "torb/sql", path: "../sql"), and the CI clones it there. Once torb/sql 0.1.0 is on packages.torb.dev, delete the source line, run torb update, and commit project.lock.trb: torb publish refuses a dependency from a path.

Publishing

.forgejo/workflows/publish.yml and .github/workflows/publish.yml publish on a tag v* through trusted publishing. Before the first tag, an owner of torb on packages.torb.dev configures the publishers - the forge's, and the GitHub mirror's where the package is mirrored as TorbScript/postgres (drop that entry otherwise):

$ curl -X PUT https://packages.torb.dev/api/1/packages/torb/postgres/trusted-publishers \
    -H "Authorization: Bearer $TOKEN" \
    -d '{"publishers":[{"repository":"git.torb.dev/torb/postgres","workflow":"publish.yml","ref":"refs/tags/v*"},{"repository":"TorbScript/postgres","workflow":"publish.yml","ref":"refs/tags/v*","environment":"release"}],"required":true}'
  • docs/design/FIRST-PARTY.md - the torb/* packages and what goes there rather than into std.
  • torb/sql - the pool, transactions, rows and errors this driver uses.
  • The Postgres manual, "Frontend/Backend Protocol" - what src/message.trb implements.