- Python 93%
- Shell 7%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| .forgejo/workflows | ||
| .githooks | ||
| .github/workflows | ||
| examples | ||
| scripts | ||
| src | ||
| tests | ||
| tools/pdisc | ||
| .gitignore | ||
| .pdisc.yaml | ||
| INDEX.md | ||
| LICENSE | ||
| project.lock.trb | ||
| project.trb | ||
| README.md | ||
| 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. |
|
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)orpool.begin()- released withoutcommit(), a transaction rolls back.takeNotices()answers the notices beside the statements (RAISE NOTICE);notification()waits for the nextNOTIFYon 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 STDINandTO 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 thePG*environment variables.SCRAM-SHA-256-PLUS: channel binding needs the server's certificate, whichstd/tlsdoes 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 runwithout--native), untilstd/digesthas 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}'
Related
- docs/design/FIRST-PARTY.md -
the
torb/*packages and what goes there rather than intostd. - torb/sql - the pool, transactions, rows and errors this driver uses.
- The Postgres manual, "Frontend/Backend Protocol" - what
src/message.trbimplements.