Warning
SecantusDB is past initial proving but its API surface (CLI flags,
public class signatures) may still shift before 1.0. The on-disk
format is WiredTiger's — the same engine MongoDB uses — and the
schema we layer on top (collection / index / oplog tables) has been
stable across releases; the test suite runs against real on-disk
WiredTiger storage and the
persistence tests explicitly verify
close-and-reopen round-trips. That said, we don't yet ship a migration
tool or a formal compatibility guarantee, so please don't put
production data here yet — production deployments that need durable
data across upgrades should still run a real mongod or postgres.
A surrogate single-node database for your tests. SecantusDB speaks
the real MongoDB and PostgreSQL wire protocols on a real TCP
socket, over the same WiredTiger storage engine MongoDB ships. Point
your existing driver at it — pymongo, mongo-go-driver, mongosh,
psycopg, psql, SQLAlchemy — and your application code doesn't know
the difference, as long as it only needs single-node behaviour. No
mongod or postgres to install, no port conflicts, parallel-test
friendly, embedded in-process or as a standalone daemon.
Two ways to get it, depending on which server you want.
The Python servers come from PyPI and start in-process in two lines
(the PostgreSQL one needs the sql extra):
pip install SecantusDB # or "SecantusDB[sql]" for the PostgreSQL serverfrom pymongo import MongoClient
from secantus import SecantusDBServer
with SecantusDBServer(storage_path="./secantus-data") as server: # port 0 = OS-assigned
client = MongoClient(server.uri)
db = client["mydb"]
db["users"].insert_one({"_id": 1, "name": "Joe"})
assert db["users"].find_one({"_id": 1})["name"] == "Joe"import psycopg
from secantus.sql import SecantusPGServer
with SecantusPGServer(storage_path="./secantus-pg-data") as server:
with psycopg.connect(server.uri, autocommit=True) as conn:
conn.execute("CREATE TABLE users (id int PRIMARY KEY, name text)")
conn.execute("INSERT INTO users VALUES (1, 'Joe')")
assert conn.execute("SELECT name FROM users WHERE id = 1").fetchone() == ("Joe",)The Rust servers are the fast ones, and they ship as Rust crates on crates.io, not inside the Python wheel:
cargo install secantus-mdb --version 0.5.3-beta.196 # MongoDB: the `secantusd-rs` daemon
cargo install secantus-pg --version 0.1.0-beta.17 # PostgreSQL: the `secantusd-pg` daemon
secantusd-rs --port 27017 --storage-path ./secantus-dataBoth crates are pre-releases, so cargo install needs the --version:
without it cargo stops at "nothing to install". The current versions are on
the secantus-mdb and
secantus-pg pages. Each crate is
also a library: secantus_mdb::Server and secantus_pg::PgServer start a
server inside a Rust test. Prebuilt archives of both Rust servers are attached
to their tags on GitHub Releases.
Single-node only by design: replica sets, sharding, streaming replication, and anything else that depends on real cluster topology are out of scope. Within that scope, SecantusDB is the database your driver thinks it's talking to — same handshake, same wire frames, same error codes.
Two wire protocols, two implementations of each, one storage format. Reach for Rust to run it; reach for Python to read it.
| Server | Wire | Run it as | Role |
|---|---|---|---|
| Rust MongoDB server | MongoDB | secantusd-rs / secantus_mdb::Server |
The flagship. The secantus-mdb crate; prebuilt binaries per platform |
| Rust PostgreSQL server | PostgreSQL | secantusd-pg |
The newest. Prebuilt secantusd-pg binaries on GitHub Releases, and the secantus-pg crate |
| Python MongoDB server | MongoDB | SecantusDBServer / secantusd-py |
The readable reference — every operator, stage and error message lands here first |
| Python PostgreSQL server | PostgreSQL | SecantusPGServer / secantusd-py-pg |
The reference for the SQL surface, and still the most complete one |
Each server is held to the real product it imitates — mongod for
the two MongoDB servers, PostgreSQL for the two SQL servers — never to
another SecantusDB server. See
The two servers for what each
one does not support yet.
Sharing one store. All four read and write the same on-disk format, but how they can share it depends on the pair:
- The Python pair can share one
Storageobject in one process (SecantusPGServer(..., storage=mongo_server.storage)) and serve both protocols at once: a collection written withpymongois queryable as a SQL table,JOINs and all, with noCREATE TABLE. - Separate processes — including the two Rust servers — can only
hand a directory over, never serve it concurrently. WiredTiger
takes an exclusive file lock, so a second server pointed at a directory
that is already open exits rather than opening it. Between the Rust
pair the hand-over works in one direction only: a table written by
secantusd-pgreads back throughsecantusd-rsas a collection, but apymongo-written collection is not visible to the Rust PostgreSQL server until something runsCREATE TABLEfor it.
SecantusDB uses the same WiredTiger C library mongod ships —
vendored at vendor/wiredtiger/ (mongodb-7.0.33) and built from source
into the wheel. There is no re-implementation of the storage engine:
B-trees, page eviction, write-ahead logging, durability, and on-disk
format are all WiredTiger's.
The layers above storage — command dispatch, query planning, the
operator engines — are where the servers differ. On a like-for-like
benchmark the Rust MongoDB server runs within 1.0×–3.2× of mongod
per operation across three runs (reads at the low end, multi-stage
aggregation at the high end); the Python server runs 2×–27×. See
docs/benchmark.md for
the numbers and methodology. The right use is tests, dev, CI,
containers, and single-node prototypes where conformance and
WiredTiger durability matter more than per-operation latency.
Everything a single-node application needs from the wire — the
handshake (hello / isMaster / ping / buildInfo / ...), CRUD
(insert / find / update / delete / findAndModify / count /
drop), cursors with getMore / killCursors, aggregation pipelines
and the expression language they need, multi-document transactions, and
change streams (single-node, oplog-backed; collection / db / cluster
scope; resume tokens; fullDocument: "updateLookup"; pre-images via
fullDocumentBeforeChange; blocking awaitData getMore). All backed by
a real query planner with index acceleration — single-field,
compound, mixed-direction, multikey, partial, sparse, TTL, sort —
explain output (IXSCAN vs COLLSCAN), and geo support
($geoWithin / $geoIntersects / $near / $nearSphere, $geoNear,
2dsphere and 2d indexes).
The target is mongod 8.x, and the Rust server passes 99.5% of pymongo's own unmodified test suite (1,205 of 1,210 run, 2026-09-28). Twelve other official drivers run their suites against it too — see the conformance validation summary.
Security: SCRAM-SHA-256 authentication, the MONGODB-X509
cert-as-username mechanism, native TLS / mTLS, and role-based
authorization with mongod's built-in roles. All off by default; turn
auth on with --auth (or require_auth=True), provision users with
createUser, then connect with the standard
MongoClient(uri, username=, password=) shape. See
Authentication.
What's out of scope: real replica sets, sharding, auth mechanisms
beyond SCRAM / MONGODB-X509 (no LDAP / Kerberos / GSSAPI / AWS / OIDC),
OP_COMPRESSED, text / hashed / wildcard indexes, and $where /
$function / $accumulator / mapReduce (no embedded JS runtime). If
you need those, run a real mongod.
The subset of the PostgreSQL wire protocol real clients use — the
extended query protocol (Parse / Bind / Describe / Execute), prepared
statements and portals, text and binary formats, transactions with
savepoints and two-phase commit, COPY, server-side cursors,
LISTEN / NOTIFY, and the catalog tables a client introspects.
The Rust PostgreSQL server parses SQL with libpg_query — the real
PostgreSQL grammar. Of the 5,731 tests of psycopg 3's own unmodified
suite that ran against it on 2026-10-10 (macOS, psycopg 3.3.4), it passes
5,544 and fails none; psycopg skips 149 and expects 34 to fail. That
gauge measures the protocol and the type system. The query language now
covers joins, correlated subqueries and CTEs, window functions, GROUP BY
with GROUPING SETS, views, indexes, ALTER TABLE, ON CONFLICT,
MERGE, triggers and PL/pgSQL functions, and it offers TLS
(--tls-cert-file / --tls-key-file), with client certificates and SCRAM
channel binding since 0.1.0-beta.10. Not yet: a foreign
table's rows, and a collection written through the MongoDB server read as a
table.
EXPLAIN prints a plan with zero costs. What it does not do it refuses
with SQLSTATE 0A000 rather than answering wrongly.
The Python PostgreSQL server has the wider SQL surface, including
schema-on-read over MongoDB collections (nested documents surface as
jsonb with ->, ->>, #>). It needs the sql extra:
pip install "SecantusDB[sql]"
secantusd-py-pg --host 127.0.0.1 --port 5432 --storage-path ./secantus-dataSee SQL / PostgreSQL interface for the supported-SQL matrix and examples.
pip install SecantusDBPre-built wheels are published for CPython 3.10, 3.11, 3.12, and 3.13 on:
- macOS arm64 (Apple Silicon)
- Linux x86_64 and aarch64 (manylinux_2_28 / glibc, and musllinux_1_2 / Alpine)
- Windows AMD64
Each wheel carries both Python servers and WiredTiger itself — no separate package, no compile step, no system build tools required. It does not carry the Rust servers (see below). macOS Intel (x86_64) is not in the wheel matrix.
Both Rust servers install from crates.io: cargo install secantus-mdb --version 0.5.3-beta.196 and cargo install secantus-pg --version 0.1.0-beta.17.
Each needs a Rust toolchain and builds WiredTiger as part of the crate.
Standalone archives of secantusd-rs (Linux x86_64, macOS arm64,
Windows x86_64) and secantusd-pg (Linux x86_64, macOS arm64) are
attached to the secantusdb-v* and secantusd-pg-v* tags on
GitHub Releases, for
when you want the server without Python at all.
If your platform isn't in the matrix above, pip install SecantusDB
falls back to the sdist and compiles WiredTiger from source. That
needs three native build tools on PATH:
cmake(>= 3.21)ninjaswig(>= 4.0)
| Platform | Install prerequisites |
|---|---|
| macOS (Homebrew) | brew install cmake ninja swig |
| Debian/Ubuntu | sudo apt-get install -y cmake ninja-build swig |
| Fedora/RHEL | sudo dnf install -y cmake ninja-build swig |
| Alpine | apk add --no-cache cmake ninja swig build-base |
See Installation for dev-install instructions.
Installing the secantus-mdb crate puts secantusd-rs on your PATH (or
unpack a release archive). Run it like you'd run mongod:
secantusd-rs --host 127.0.0.1 --port 27017 --storage-path ./secantus-dataThe Rust PostgreSQL server comes from the secantus-pg crate or a release
archive, and takes positional arguments:
secantusd-pg ./secantus-pg-data 127.0.0.1:5432Then point any driver or tool at it — no application code changes, just the connection string:
mongosh mongodb://127.0.0.1:27017
mongodump --uri mongodb://127.0.0.1:27017 --out ./dump
psql "host=127.0.0.1 port=5432 dbname=postgres user=postgres"The Python reference servers run the same way as secantusd-py and
secantusd-py-pg.
A walk through the operations a typical application exercises — connect, insert, index, query, drop. Full version with explanations: examples in the docs.
import tempfile
from pymongo import MongoClient
from secantus import SecantusDBServer
# A throwaway directory so the snippet is self-contained; pass a real
# path to keep the data across restarts.
with SecantusDBServer(storage_path=tempfile.mkdtemp()) as server:
client = MongoClient(server.uri)
cellar = client["wine_cellar"]
bottles = cellar["bottles"]
# --- Insert ---
bottles.insert_one(
{"_id": 1, "name": "Pommard 2018", "region": "Burgundy", "year": 2018}
)
bottles.insert_many(
[
{"_id": 2, "name": "Brunello 2015", "region": "Tuscany", "year": 2015},
{"_id": 3, "name": "Barolo 2017", "region": "Piedmont", "year": 2017},
{"_id": 4, "name": "Pommard 2020", "region": "Burgundy", "year": 2020},
]
)
# --- Indexes ---
bottles.create_index([("year", 1)]) # single-field
bottles.create_index([("region", 1), ("year", -1)]) # compound
# --- Query ---
drinkable_now = list(
bottles.find({"year": {"$lte": 2018}}).sort("year")
)
assert [b["name"] for b in drinkable_now] == [
"Brunello 2015",
"Barolo 2017",
"Pommard 2018",
]
by_region = list(
bottles.aggregate(
[
{"$group": {"_id": "$region", "count": {"$sum": 1}}},
{"$sort": {"_id": 1}},
]
)
)
# --- Drop ---
bottles.drop() # one collection
client.drop_database("wine_cellar") # whole databaseAn optional local console — browse collections, watch live metrics, tail change streams, inspect query plans, manage users, and take backups (including point-in-time recovery) against any SecantusDB or MongoDB-wire server you already have running.
pip install 'SecantusDB[admin]'
secantus-admin --uri mongodb://127.0.0.1:27017It's dev-tool shaped, not a production console: loopback-only, gated by a local token, with every script and stylesheet served from the package. See the admin UI docs for a tour of all 22 pages.
Full docs are at secantusdb.com/docs — with the Rust server's own tree at secantusdb.com/docs/rust. Highlights:
- Quickstart — embedding in tests, running standalone.
- The two servers — Rust vs Python server, and what each doesn't support yet.
- SQL / PostgreSQL interface — the supported SQL surface.
- Architecture — the layered design.
- Indexes — what
find()andaggregateaccelerate,explainsemantics, hints, partial indexes, TTL. - Aggregation — supported pipeline stages and expression operators.
- Compatibility — the divergences you should know about before you point an application at SecantusDB.
- Conformance validation — each official driver's own test suite run unmodified against SecantusDB, with a cross-driver summary table and a per-driver report. The other-language gauges catch wire-protocol bugs that pymongo's permissive client accepts silently (e.g. int32-vs-int64 cursor ids).
git clone https://github.com/jdrumgoole/SecantusDB.git
cd SecantusDB
git submodule update --init vendor/wiredtiger
./inv sync # uv sync --all-extras, rebuilding the Rust core
uv run python -m pytest # runs in parallel under pytest-xdistCommon workflows:
uv run python -m invoke fmt # ruff format
uv run python -m invoke lint # ruff check
uv run python -m invoke test # pytest, parallel
uv run python -m invoke docs # build Sphinx docs (warnings as errors)SecantusDB is dual-licensed:
- Code — GPL-2.0-only. See
LICENSE. SecantusDB bundles the WiredTiger storage engine (itself GPL-2/GPL-3), so the combined work is GPL. - Written content — Creative Commons Attribution 4.0
International (CC-BY 4.0).
See
LICENSE-DOCS. CoversREADME.md, everything underdocs/, the validation reports, andpymongo_validation/README.md. Operational instructions to AI assistants (CLAUDE.md) and vendored third-party content (undervendor/) are out of scope.
