PostgreSQL wire protocol server for Datahike.
Starts a pgwire-compatible server that accepts SQL queries from standard PostgreSQL clients (psql, DBeaver, JDBC, Python/psycopg2, etc.) and translates them to Datahike Datalog queries.
Usage: (require '[datahike.api :as d]) (require '[datahike.pg.server :as pg])
(def conn (d/connect cfg)) (def server (pg/start-server conn {:port 5432})) ;; ... use any PostgreSQL client ... (pg/stop-server server)
PostgreSQL wire protocol server for Datahike.
Starts a pgwire-compatible server that accepts SQL queries from standard
PostgreSQL clients (psql, DBeaver, JDBC, Python/psycopg2, etc.) and
translates them to Datahike Datalog queries.
Usage:
(require '[datahike.api :as d])
(require '[datahike.pg.server :as pg])
(def conn (d/connect cfg))
(def server (pg/start-server conn {:port 5432}))
;; ... use any PostgreSQL client ...
(pg/stop-server server)Maximum rows a SELECT may return before it fails with SQLSTATE 54000. Dynamically bound per handler so every result-formatting path, including set operations and the compiled prepared-statement lane, shares one guard.
Maximum rows a SELECT may return before it fails with SQLSTATE 54000. Dynamically bound per handler so every result-formatting path, including set operations and the compiled prepared-statement lane, shares one guard.
Bind false to bypass the cache. For perf comparisons only; production code should leave this on.
Bind false to bypass the cache. For perf comparisons only; production code should leave this on.
(add-database! server-result name conn)Add a Datahike conn to a running pg-datahike server's registry under
name. Subsequent client connections with database=name will
route to it. Returns the new registry contents.
Symmetric with the SQL CREATE DATABASE path: add-database! is
the Clojure-side knob, :on-create-database is the SQL-side
knob, and they share the same atom so either source is visible
to both.
Add a Datahike conn to a running pg-datahike server's registry under `name`. Subsequent client connections with `database=name` will route to it. Returns the new registry contents. Symmetric with the SQL `CREATE DATABASE` path: `add-database!` is the Clojure-side knob, `:on-create-database` is the SQL-side knob, and they share the same atom so either source is visible to both.
Hit/miss counter for the schema cache. Atom whose
identity is stable across reloads so external observers can hold
a reference and watch counts. Mutated by every schema-cached
call; read for inspection / verification harnesses.
Hit/miss counter for the schema cache. Atom whose identity is stable across reloads so external observers can hold a reference and watch counts. Mutated by every `schema-cached` call; read for inspection / verification harnesses.
(databases server-result)Return the current set of database names registered with the server.
Return the current set of database names registered with the server.
(functions-by-name+arity db fn-name arity)Every stored overload of this name with this many arguments. An
arity of nil means every overload of the name.
Every stored overload of this name with this many arguments. An `arity` of nil means every overload of the name.
(invalidate-schema-cache!)Clear the per-schema cache. Called from every DDL exec branch. Also clears the enriched-db catalog cache, whose schema-hash key is blind to registry-entity additions (CREATE TYPE/ENUM/DOMAIN) and ALTER typmod changes — see sql/invalidate-catalog-cache!.
Clear the per-schema cache. Called from every DDL exec branch. Also clears the enriched-db catalog cache, whose schema-hash key is blind to registry-entity additions (CREATE TYPE/ENUM/DOMAIN) and ALTER typmod changes — see sql/invalidate-catalog-cache!.
(make-query-handler conn & [opts])Create a PgWireServer.QueryHandler that dispatches SQL to Datahike.
conn: a Datahike connection
opts: optional map with
:on-query (fn [sql]) invoked on every SQL string
:compat :strict (default) | :permissive — named bundle
of features to silently accept, see compat-presets.
:silently-accept a set of reject-kinds to swallow on top of the
preset. Valid kinds: :grant :revoke :policy :rls
:create-extension. These return a synthetic
success tag (e.g. "GRANT") instead of SQLSTATE
0A000.
:db-name string — the database name this handler represents.
Returned by current_database(); defaults to
"datahike" when omitted.
:registered-databases
seq of strings — all database names registered at
the server. Surfaced in the virtual pg_database
catalog so \l and DatabaseMetaData enumerate the
server's tenancy. Typically supplied by
start-server with the keys of its registry;
omit for single-DB / bare-handler use.
:dispatch-stats optional atom; when supplied, the handler bumps
:fast-path-count or :full-parse-count on each
parse-sql invocation depending on whether
catalog/system-query? matched. Used by tests
and observability tooling to detect when an
upgrade silently demotes a probe to the slow
path.
:max-result-rows positive integer — maximum rows returned by one SELECT
before SQLSTATE 54000 (default 100000). Set false to
disable the guard. Safe query shapes push cap+1 into
Datahike; every shape is checked before wire encoding.
Supports temporal session variables: SET datahike.as_of = '2024-01-15T00:00:00Z' SET datahike.since = '2024-01-01T00:00:00Z' SET datahike.history = 'true' RESET datahike.as_of
Create a PgWireServer.QueryHandler that dispatches SQL to Datahike.
conn: a Datahike connection
opts: optional map with
:on-query (fn [sql]) invoked on every SQL string
:compat :strict (default) | :permissive — named bundle
of features to silently accept, see compat-presets.
:silently-accept a set of reject-kinds to swallow on top of the
preset. Valid kinds: :grant :revoke :policy :rls
:create-extension. These return a synthetic
success tag (e.g. "GRANT") instead of SQLSTATE
0A000.
:db-name string — the database name this handler represents.
Returned by `current_database()`; defaults to
"datahike" when omitted.
:registered-databases
seq of strings — all database names registered at
the server. Surfaced in the virtual `pg_database`
catalog so \l and DatabaseMetaData enumerate the
server's tenancy. Typically supplied by
`start-server` with the keys of its registry;
omit for single-DB / bare-handler use.
:dispatch-stats optional atom; when supplied, the handler bumps
:fast-path-count or :full-parse-count on each
parse-sql invocation depending on whether
catalog/system-query? matched. Used by tests
and observability tooling to detect when an
upgrade silently demotes a probe to the slow
path.
:max-result-rows positive integer — maximum rows returned by one SELECT
before SQLSTATE 54000 (default 100000). Set false to
disable the guard. Safe query shapes push cap+1 into
Datahike; every shape is checked before wire encoding.
Supports temporal session variables:
SET datahike.as_of = '2024-01-15T00:00:00Z'
SET datahike.since = '2024-01-01T00:00:00Z'
SET datahike.history = 'true'
RESET datahike.as_of(make-query-handler-factory registry-or-atom & [opts])Build a QueryHandlerFactory that routes on the StartupMessage's
database parameter.
registry: {name → conn} map of database name to Datahike conn. opts: forwarded to make-query-handler (e.g. :on-query, :compat).
Clients that pass database=X land on (registry "X"). An optional
:branch suffix — database=X:feature — pins the connection's
session-state to the :feature branch from the first query. Both
the current_database() reply and the pg_database catalog still
report the base name; the branch is a session-state detail.
Unknown base-names get a handler that errors every query with 3D000 invalid_catalog_name — matching PG's behaviour on a non-existent db.
Intended for callers who want to wrap their own PgWireServer (e.g.
custom host/port binding); start-server uses it internally.
Build a QueryHandlerFactory that routes on the StartupMessage's
`database` parameter.
registry: {name → conn} map of database name to Datahike conn.
opts: forwarded to make-query-handler (e.g. :on-query, :compat).
Clients that pass `database=X` land on `(registry "X")`. An optional
`:branch` suffix — `database=X:feature` — pins the connection's
session-state to the `:feature` branch from the first query. Both
the `current_database()` reply and the `pg_database` catalog still
report the base name; the branch is a session-state detail.
Unknown base-names get a handler that errors every query with 3D000
invalid_catalog_name — matching PG's behaviour on a non-existent db.
Intended for callers who want to wrap their own PgWireServer (e.g.
custom host/port binding); `start-server` uses it internally.(nextval! conn seq-name)Atomically advance the named sequence on conn and return the new
long. Shared core of SELECT nextval(...) and INSERT-VALUES nextval
resolution.
PG semantics: a nextval advance is never rolled back, even by
ROLLBACK on the surrounding transaction. We match that by always
committing to the live conn, regardless of any session's :in-tx?
state.
Atomicity: optimistic CAS-retry. Each iteration reads the current :seq/value, computes the new value, and submits
[[:db/cas seq-eid :__seq__/value curr new]]
to d/transact. Datahike's transactor serialises tx applications
per conn, so two concurrent CAS submissions see each other: the
first wins, the second's old-val no longer matches the
transactor's current value and the CAS raises :transact/cas. We
re-read and retry. The loser thread will see the winner's new
value, compute the next slot, and commit cleanly.
Why CAS instead of the simpler :db.fn/call pattern: Datahike's
writer batches multiple in-flight transactions for a single
commit, then writes the SAME :db-after (the batch's final db)
into every tx-report in the batch. So reading (:db-after report)
to recover the value this call assigned doesn't work — it returns
the LAST tx in the batch. CAS sidesteps that: the value we
intended is the literal new slot of the op-vec, available
without reading :db-after.
Throws ex-info :undefined-sequence if the named sequence does
not exist, and :serialization-failure if the contention retry
budget is exhausted.
Atomically advance the named sequence on `conn` and return the new
long. Shared core of `SELECT nextval(...)` and INSERT-VALUES nextval
resolution.
PG semantics: a `nextval` advance is never rolled back, even by
ROLLBACK on the surrounding transaction. We match that by always
committing to the live conn, regardless of any session's `:in-tx?`
state.
Atomicity: optimistic CAS-retry. Each iteration reads the current
:__seq__/value, computes the new value, and submits
[[:db/cas seq-eid :__seq__/value curr new]]
to `d/transact`. Datahike's transactor serialises tx applications
per conn, so two concurrent CAS submissions see each other: the
first wins, the second's old-val no longer matches the
transactor's current value and the CAS raises `:transact/cas`. We
re-read and retry. The loser thread will see the winner's new
value, compute the next slot, and commit cleanly.
Why CAS instead of the simpler `:db.fn/call` pattern: Datahike's
writer batches multiple in-flight transactions for a single
commit, then writes the SAME `:db-after` (the batch's final db)
into every tx-report in the batch. So reading `(:db-after report)`
to recover the value this call assigned doesn't work — it returns
the LAST tx in the batch. CAS sidesteps that: the value we
intended is the literal `new` slot of the op-vec, available
without reading `:db-after`.
Throws ex-info `:undefined-sequence` if the named sequence does
not exist, and `:serialization-failure` if the contention retry
budget is exhausted.(password-authenticator f)Adapt (fn [connection password-chars] -> boolean) to the pgwire
PasswordAuthenticator interface. connection contains :user,
:database, :remote-address, :tls?, and :startup-parameters.
The password is a mutable char array which is cleared immediately after the callback returns. Do not retain it. Authentication failures must return false rather than exposing a reason to the client.
Adapt `(fn [connection password-chars] -> boolean)` to the pgwire PasswordAuthenticator interface. `connection` contains `:user`, `:database`, `:remote-address`, `:tls?`, and `:startup-parameters`. The password is a mutable char array which is cleared immediately after the callback returns. Do not retain it. Authentication failures must return false rather than exposing a reason to the client.
Ring of recent commits' write sets: [{:max-tx N :eas #{[e a]…}} …], newest last, capped. Lets the COMMIT conflict check test row-level overlap in O(concurrent writes) instead of scanning the whole database, and only for the transactions that actually committed in our window. Commits that bypass transact-tx-buffer! (COPY batches, direct DDL) leave gaps — the checker detects incomplete coverage and aborts conservatively. ::sequence-only records non-transactional nextval reservations without treating them as ordinary row writes.
Ring of recent commits' write sets: [{:max-tx N :eas #{[e a]…}} …],
newest last, capped. Lets the COMMIT conflict check test row-level
overlap in O(concurrent writes) instead of scanning the whole
database, and only for the transactions that actually committed in
our window. Commits that bypass transact-tx-buffer! (COPY batches,
direct DDL) leave gaps — the checker detects incomplete coverage and
aborts conservatively. ::sequence-only records non-transactional
nextval reservations without treating them as ordinary row writes.(remove-database! server-result name)Remove a database from a running pg-datahike server's registry. Does NOT release the conn or delete the backing store — that's the operator's call. Returns the new registry contents.
Remove a database from a running pg-datahike server's registry. Does NOT release the conn or delete the backing store — that's the operator's call. Returns the new registry contents.
Clear the advisory-lock registry (a test-fixture helper). Re-exported:
the registry itself lives in datahike.pg.locks, which the SQL layer
requires to answer a TRANSLATED pg_advisory_lock(…).
Clear the advisory-lock registry (a test-fixture helper). Re-exported: the registry itself lives in `datahike.pg.locks`, which the SQL layer requires to answer a TRANSLATED `pg_advisory_lock(…)`.
(reset-lock-registry!)Clear the server-wide row-lock registry. Intended for test fixtures — in production, locks are released on COMMIT/ROLLBACK/DISCARD ALL and when the handler's TCP connection closes. Do not call this from handler code.
Clear the server-wide row-lock registry. Intended for test fixtures — in production, locks are released on COMMIT/ROLLBACK/DISCARD ALL and when the handler's TCP connection closes. Do not call this from handler code.
(ssl-context-from-pkcs12 path password)Load a server SSLContext from a PKCS#12 keystore.
password unlocks both the keystore and its private key. Operators can
generate or convert this deployment artefact with keytool or OpenSSL.
Load a server SSLContext from a PKCS#12 keystore. `password` unlocks both the keystore and its private key. Operators can generate or convert this deployment artefact with `keytool` or OpenSSL.
(start-server conn-or-registry
&
[{:keys [port host on-query default on-create-database
on-delete-database database-template]
:or {port 5432 host "127.0.0.1" default "datahike"}
:as opts}])Start a PostgreSQL wire protocol server for one or more Datahike connections.
conn-or-registry is either:
{"datahike" conn}), or{name → conn} of database name to conn. Clients route
via the StartupMessage database parameter (e.g.
jdbc:postgresql://…/prod lands on the "prod" conn).Options:
:port — Port to listen on (default 5432)
:host — Host to bind to (default "127.0.0.1")
:users — Small deployment-owned {username password} map. Mutually
exclusive with :authenticator.
:authenticator
— PgWireServer.PasswordAuthenticator or
(fn [connection password-chars] -> boolean).
:ssl-context
— Preconfigured javax.net.ssl.SSLContext.
:tls — {:keystore path :keystore-password secret} shorthand for
a PKCS#12 server keystore. Mutually exclusive with
:ssl-context.
:require-tls?
— Reject plaintext startup before requesting a password.
Automatically true for every non-loopback bind.
:on-query — Callback (fn [sql-string]) for logging
:max-result-rows
— Maximum rows returned by one SELECT before SQLSTATE 54000
(default 100000). Set false to disable the guard.
:default — Database name used when conn-or-registry is a bare
conn (default "datahike"). Ignored when a map is
supplied.
:on-create-database
— Hook (fn [db-name parsed-options]) -> conn that runs
when a SQL client issues CREATE DATABASE name [WITH …].
Without this hook configured, CREATE DATABASE
returns SQLSTATE 0A000 (provisioning from SQL is a
deployment policy decision, not a default). See
datahike.pg.sql.database/db-from-template for the
common template-driven helper.
:on-delete-database
— Hook (fn [db-name conn parsed-options]) that runs on
DROP DATABASE name. Should release the conn and
delete the backing store. Symmetric with
:on-create-database; without it, DROP DATABASE
returns 0A000.
:database-template
— Convenience shorthand: a partial datahike config
template that pg-datahike uses to build both
:on-create-database and :on-delete-database via
db-from-template / db-delete-from-template. The
template can interpolate {{name}} in string values
(handy for file backends with per-database paths).
Mutually composable with the explicit hooks; if both
are given, the explicit hook wins.
Returns a map with :server (PgWireServer), :registry-atom (an atom
holding the live {name → conn} map — mutated by SQL CREATE/DROP
DATABASE and add-database! / remove-database!), :port, :host.
Examples: ;; single DB, no SQL provisioning (def srv (pg/start-server conn {:port 5433}))
;; multi-DB, static (def srv (pg/start-server {"prod" prod-conn "staging" staging-conn} {:port 5432}))
;; SQL CREATE/DROP DATABASE provisioned in-memory (def srv (pg/start-server {} {:port 5432 :database-template {:store {:backend :memory} :schema-flexibility :write}}))
(pg/stop-server srv)
Start a PostgreSQL wire protocol server for one or more Datahike
connections.
`conn-or-registry` is either:
- a single Datahike conn (convenience — treated as
`{"datahike" conn}`), or
- a map `{name → conn}` of database name to conn. Clients route
via the StartupMessage `database` parameter (e.g.
`jdbc:postgresql://…/prod` lands on the `"prod"` conn).
Options:
:port — Port to listen on (default 5432)
:host — Host to bind to (default "127.0.0.1")
:users — Small deployment-owned `{username password}` map. Mutually
exclusive with :authenticator.
:authenticator
— PgWireServer.PasswordAuthenticator or
`(fn [connection password-chars] -> boolean)`.
:ssl-context
— Preconfigured javax.net.ssl.SSLContext.
:tls — `{:keystore path :keystore-password secret}` shorthand for
a PKCS#12 server keystore. Mutually exclusive with
:ssl-context.
:require-tls?
— Reject plaintext startup before requesting a password.
Automatically true for every non-loopback bind.
:on-query — Callback (fn [sql-string]) for logging
:max-result-rows
— Maximum rows returned by one SELECT before SQLSTATE 54000
(default 100000). Set false to disable the guard.
:default — Database name used when `conn-or-registry` is a bare
conn (default "datahike"). Ignored when a map is
supplied.
:on-create-database
— Hook (fn [db-name parsed-options]) -> conn that runs
when a SQL client issues `CREATE DATABASE name [WITH …]`.
Without this hook configured, `CREATE DATABASE`
returns SQLSTATE 0A000 (provisioning from SQL is a
deployment policy decision, not a default). See
`datahike.pg.sql.database/db-from-template` for the
common template-driven helper.
:on-delete-database
— Hook (fn [db-name conn parsed-options]) that runs on
`DROP DATABASE name`. Should release the conn and
delete the backing store. Symmetric with
`:on-create-database`; without it, DROP DATABASE
returns 0A000.
:database-template
— Convenience shorthand: a partial datahike config
template that pg-datahike uses to build both
`:on-create-database` and `:on-delete-database` via
`db-from-template` / `db-delete-from-template`. The
template can interpolate `{{name}}` in string values
(handy for file backends with per-database paths).
Mutually composable with the explicit hooks; if both
are given, the explicit hook wins.
Returns a map with :server (PgWireServer), :registry-atom (an atom
holding the live {name → conn} map — mutated by SQL CREATE/DROP
DATABASE and `add-database!` / `remove-database!`), :port, :host.
Examples:
;; single DB, no SQL provisioning
(def srv (pg/start-server conn {:port 5433}))
;; multi-DB, static
(def srv (pg/start-server {"prod" prod-conn
"staging" staging-conn}
{:port 5432}))
;; SQL CREATE/DROP DATABASE provisioned in-memory
(def srv (pg/start-server {}
{:port 5432
:database-template {:store {:backend :memory}
:schema-flexibility :write}}))
(pg/stop-server srv)(stop-server {:keys [server]})Stop a running PgWire server.
Stop a running PgWire server.
(sync-composites-to-codec! db)Push every known composite type's ordered field OIDs into PgParamCodec's
registry so the binary record codec can encode record_out text as a PG
binary record. Cheap (one query); called on CREATE TYPE and lazily when a
query's result/params reference a composite OID.
Push every known composite type's ordered field OIDs into PgParamCodec's registry so the binary record codec can encode `record_out` text as a PG binary record. Cheap (one query); called on CREATE TYPE and lazily when a query's result/params reference a composite OID.
(table-triggers db table)Every trigger defined on table, as spec maps with their names.
Every trigger defined on `table`, as spec maps with their names.
(users-authenticator users)Build a constant-time password authenticator from {username password}.
Intended for small deployment-owned credential maps, not as a user store.
Build a constant-time password authenticator from `{username password}`.
Intended for small deployment-owned credential maps, not as a user store.cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |