SecantusDB Rust PostgreSQL server
The same Rust storage as the MongoDB server, answering the PostgreSQL wire protocol instead. psql, psycopg, SQLAlchemy and any other stock PostgreSQL client connect and run real SQL — against the same WiredTiger home a MongoDB driver writes to. It is the newest of SecantusDB's servers — prebuilt for Linux x86_64 and macOS arm64, buildable from source anywhere else — and it is scored by psycopg's own test suite rather than by our own expectations.
Why a PostgreSQL server in a document database
Because the store underneath was never document-shaped by necessity — it is WiredTiger, the same B-tree engine MongoDB ships, and a row is as natural in it as a document. SecantusDB has served both protocols over one store since the Python implementation, where a pymongo-written collection is queryable as a table with no CREATE TABLE at all; this is that idea with a Rust server under it.
PostgreSQL's own parser
SQL is parsed by libpg_query — the real PostgreSQL grammar, vendored and compiled in — so a statement means here what it means there. No approximation layer inventing its own dialect.
One storage format
Tables live in the same WiredTiger home, in the same on-disk format every SecantusDB server uses. The Rust and Python PostgreSQL servers read and write each other's store — pinned in both directions by a regression test, because getting it wrong would be silent corruption rather than an error.
The extended protocol, properly
Parse / Bind / Describe / Execute, prepared statements, portals, and binary parameter and result formats. Not a simple-query shim — it is what every real driver actually speaks.
Real transactions
BEGIN / COMMIT / ROLLBACK backed by storage transactions, savepoints, and two-phase commit: PREPARE TRANSACTION makes the work durable and invisible, and a prepared transaction found on disk at startup is replayed when it is committed.
Error codes measured, not guessed
Every SQLSTATE, notice and detail line on the supported surface was measured against a live PostgreSQL 16 and matched — down to the DETAIL: Key (id)=(1) already exists. on a unique violation.
Scored by someone else's tests
psycopg 3's own tests run against it unmodified. Of the 5,731 that ran in the last measurement, 5,544 pass and none fail; the rest are tests psycopg itself skips or expects to fail.
What it speaks today
The surface grew by chasing a real client's test suite rather than a feature list, so what is here is what psycopg actually asks for.
| Area | What works |
|---|---|
| DDL | CREATE / ALTER / DROP TABLE, CREATE TABLE AS, TRUNCATE, constraints enforced (NOT NULL, CHECK, PRIMARY KEY, UNIQUE and FOREIGN KEY, single- and multi-column — a duplicate answers 23505 with PostgreSQL's DETAIL line), CREATE [UNIQUE] INDEX, views and materialized views, sequences and identity columns, schemas, partitioned tables, CREATE TYPE (enum, composite, range, base and shell types) |
| Queries | Joins of every kind over any number of tables, subqueries including correlated ones and LATERAL, WITH and WITH RECURSIVE, window functions with ROWS / RANGE / GROUPS frames, GROUP BY with ROLLUP / CUBE / GROUPING SETS, DISTINCT, UNION / INTERSECT / EXCEPT, LIKE and regular expressions, CASE, and EXPLAIN |
| Writes | INSERT, UPDATE and DELETE with RETURNING, INSERT ... ON CONFLICT, MERGE, UPDATE ... FROM, DELETE ... USING, and COPY in and out |
| Server-side code | CREATE FUNCTION and CREATE PROCEDURE in SQL and PL/pgSQL, DO blocks, triggers (BEFORE / AFTER / INSTEAD OF, row and statement), and rules |
| Types and functions | numeric exact past 34 digits, uuid, bytea, inet / cidr, arrays including multidimensional, composites, ranges and multiranges, enums, dates and timestamptz honouring the session zone, each answered in text and binary. json / jsonb with jsonpath, full-text search (tsvector / tsquery), to_char / to_number, the statistical aggregates, and the pgcrypto, pg_trgm, hstore and postgis extensions |
| Sessions | Transactions with savepoints and two-phase commit, server-side cursors (DECLARE / FETCH / MOVE), LISTEN / NOTIFY, SQL PREPARE, GUCs with GUC_REPORT (DateStyle, client_encoding with LATIN1 / LATIN9 transcoding), CancelRequest, pg_cancel_backend and pg_terminate_backend |
| Access control | TLS when started with --tls-cert-file and --tls-key-file, checked against PostgreSQL 15 for every sslmode. Client certificates with --tls-ca-file and --tls-client-cert (verify-ca, verify-full, cert), and --tls-require to refuse a connection that is not encrypted. A pg_hba.conf with --hba-file, read as PostgreSQL 15 reads it, to choose the login rule by user, database and address, host names, samehost / samenet and @file lists included. A private key encrypted with a passphrase, given by --tls-key-passphrase-command. A revocation list (--tls-crl-file) and a user name map (pg_ident.conf) for client certificates, and a reload of all of these files on SIGHUP or pg_reload_conf(). Roles with SCRAM-SHA-256 passwords, bound to the TLS channel when the client asks, GRANT / REVOKE down to the column, and row-level security |
| Catalogs | information_schema and the pg_catalog tables a client introspects: pg_class, pg_attribute, pg_type, pg_index, pg_constraint, pg_proc, pg_roles and the rest that psql's \d commands and SQLAlchemy's reflection read |
| Not yet | A password for every connection by default: without a pg_hba.conf, a role that has no password is let in, and so is an unknown user. The pg_hba.conf methods that need an outside service, such as ldap, gss and peer: each stops the server from starting. EXPLAIN shows the plan's shape with every cost at zero. A parenthesised step inside a ROLLUP or CUBE, as in rollup ((a, b), c), is refused. A foreign table can be declared and not read, because there is no foreign-data wrapper behind it. A collection written by a MongoDB driver is not visible as a table; a table written here does read back through the Rust MongoDB server as a collection. There is no Windows binary. Replication and standbys are out of scope for a single-node server |
Where that leaves it, measured on 2026-10-13 on macOS against the released 0.1.0-beta.15 binary, with psycopg 3.3.4: of the 5,731 tests that ran, the server passes 5,544 and fails none. psycopg skips 149 that need something out of scope and marks 34 as expected to fail; 4 more that it expects to fail pass here. Another 58 are left out on macOS, as psycopg's own CI leaves them out there: the tests that need its proxy fixture or assert wall-clock budgets of a few milliseconds, and two more that fail the same way against PostgreSQL itself. Whole type suites that a real client leans on — uuid, inet, enums, composites, ranges, COPY, server cursors — pass end to end. psycopg's suite measures the protocol and the types more than the SQL functions, so those are measured apart: a probe calls every built-in function in PostgreSQL 15's catalog on both servers, and of the 1,934 it can call, 1,890 answer the same (measured 2026-10-15). We are not publishing a head-to-head against the Python SQL server here: its last report predates the widening of this gauge, so the two ran different suites and the comparison would flatter one of them by accident.
Measured against PostgreSQL itself
Not against our own expectations: the released secantusd-pg 0.1.0-beta.15 and PostgreSQL 16 at its defaults, on the same 16-vCPU DigitalOcean droplet (Xeon Platinum 8280, ext4), every commit synced to disk on both, neither using TLS. Eight clients on two other droplets, four on each, reach the server over the private network. Statements a second, the median of seven 10 s trials, measured 2026-10-13 against PostgreSQL 16.15.
| Workload | Clients | PostgreSQL 16 | Rust PG server | PostgreSQL is faster by |
|---|---|---|---|---|
| insert, one table each | 8 | 20,106 ops/s | 15,866 ops/s | 1.27x |
| insert, shared table | 8 | 20,753 ops/s | 16,703 ops/s | 1.24x |
| update by primary key | 8 | 22,843 ops/s | 16,655 ops/s | 1.37x |
| select by primary key | 8 | 54,395 ops/s | 48,849 ops/s | 1.11x |
PostgreSQL is faster on every row: 1.24x to 1.37x on durable writes and 1.11x on reads by primary key, with eight clients. Eight clients writing one shared table were no slower than eight writing a table each. The client droplets' CPUs were at most 21% busy, so the server was the limit in every trial. 0.1.0-beta.15 changes no statement's path, and its ratios are those of 0.1.0-beta.14 (1.27x, 1.31x, 1.35x and 1.13x) to within the spread between trials. The rates themselves are higher on every row for both servers because this is another droplet: compare a ratio across two runs, and a rate only within one. An earlier table, for 0.1.0-beta.13, was unfair to PostgreSQL: Ubuntu's PostgreSQL offers TLS and the client took it, so PostgreSQL encrypted every statement and this server did not. That cost PostgreSQL about a fifth of its reads. Both run in the clear here. 0.1.0-beta.14 was the first release built with link-time optimisation and a profile. On the droplet it was measured on, 0.1.0-beta.13 answered 34,288 selects and 12,542 updates a second, where 0.1.0-beta.14 answered 41,038 and 14,366. The disk decides the write rows as much as either server does: this droplet's completed 12,223 fdatasync calls a second, and on another droplet of the same size, whose disk completed 1,700, one client's inserts ran at 613 a second on PostgreSQL and 568 on 0.1.0-beta.13. These are prepared statements. On an earlier run with the client on the server's droplet, before 0.1.0-beta.8, a statement sent as text with a different literal each time was where we were furthest behind: 380 microseconds against PostgreSQL's 128 for one row by primary key. 0.1.0-beta.8 plans such a statement once per shape. On a Mac, with the released binaries, that took it from 111 microseconds to 43, against PostgreSQL 15's 40; it has not been measured on the droplet yet.
Get it
A prebuilt archive for Linux x86_64 and macOS arm64, or from source on any other platform — there is no Windows build yet. Each archive ships with a .sha256. Binary releases are GitHub pre-releases, so they never show as “Latest” — the link below goes straight to the tag.
Prebuilt binary
Download, extract, run. No Rust toolchain, no Python, and since 0.1.0-beta.2 nothing but the platform's own C runtime — zlib and lz4 are linked in.
# from the release page, then:
./secantusd-pg ./data 127.0.0.1:5432
From crates.io
The secantus-pg crate builds the server and the WiredTiger it links. It needs a Rust toolchain, CMake, a C compiler and libclang. The binary takes a storage directory and an address.
cargo install secantus-pg --version 0.1.0-beta.17
secantusd-pg ./data 127.0.0.1:5432
Or from a checkout: git clone --recurse-submodules, then cargo build --release in crates/secantus-pg.
Connect
Any stock PostgreSQL client. The postgres role starts with no password, so none is asked for; give a role one with ALTER ROLE ... PASSWORD and it is checked from then on. A user name with no role also connects without a password, unless the server is started with --refuse-unknown-users.
psql -h 127.0.0.1 -p 5432 -U postgres
# or from Python
import psycopg
psycopg.connect("host=127.0.0.1 port=5432 user=postgres")
For TLS, start the server with a PEM certificate and its key, and the client can ask for it:
secantusd-pg ./data 127.0.0.1:5432 \
--tls-cert-file server.crt --tls-key-file server.key
psql "host=127.0.0.1 port=5432 user=postgres sslmode=require"
To require a client certificate that names the user, add a CA and a mode:
secantusd-pg ./data 127.0.0.1:5432 \
--tls-cert-file server.crt --tls-key-file server.key \
--tls-ca-file ca.pem --tls-client-cert verify-full
psql "host=127.0.0.1 port=5432 user=alice sslmode=verify-full \
sslrootcert=ca.pem sslcert=alice.crt sslkey=alice.key"
To choose the rule by user, database and address, give it a pg_hba.conf:
# pg_hba.conf
host all postgres 127.0.0.1/32 trust
hostssl appdb +staff 10.0.0.0/8 scram-sha-256
host all all all reject
secantusd-pg ./data 0.0.0.0:5432 \
--tls-cert-file server.crt --tls-key-file server.key \
--hba-file pg_hba.conf
Related reading: The SQL interface · psycopg conformance report (Python SQL server) · The Rust MongoDB server · The Python reference implementation