Where pg-datahike's architecture aligns with — and where it intentionally
diverges from — real PostgreSQL (compared against the ../postgres checkout).
The goal is a faithful PostgreSQL interface for realistic use — covering the
feature behind what real clients (drivers, ORMs, migration tools, analysts) do —
not passing every adversarial conformance string.
This document describes the design state: the execution model, what that model covers, the capabilities that fall outside it, and the boundaries that are intrinsic to Datahike.
pg-datahike compiles each SQL statement into a single Datalog query
(:find / :where / :in / :with / :order-by) over one immutable
Datahike snapshot, plus light post-processing (HAVING, window functions,
ORDER BY, FOR UPDATE locks). PostgreSQL is a tuple-at-a-time iterator
(volcano) executor with arbitrary nesting over MVCC storage.
That single-query model is elegant and covers the bulk of CRUD and analytics. Within it:
:where with shared logic vars; OUTER joins via
or-join; correlated EXISTS via not-join.d/db-with).A few capabilities live outside the single-query model (structural gaps below); most remaining differences are incremental type/catalog coverage, or boundaries intrinsic to Datahike.
Capabilities implemented and exercised by the conformance suites:
FOR UPDATE.CREATE; INSERT; SELECT in one string — the
SELECT sees the INSERT (implicit-transaction model across the batch).generate_series,
unnest) materialised into the virtual-table path.CREATE TYPE … AS (…), ROW(...), named and
anonymous records, text and binary codecs, composite parameters.::T and ::T[] casts.pg_type,
pg_class, pg_attribute, pg_namespace, and the recursive type-info
queries asyncpg/JDBC issue to build codecs.These need execution beyond one d/q over one snapshot:
OUTER LATERAL (LEFT JOIN LATERAL (…) ON true). An outer lateral keeps
the outer row with NULLs when the inner is empty; an empty collection binding
DROPS it, which is precisely the inner-join semantics the lateral support
relies on. Reproducing the outer form needs the or-join construction the
other OUTER joins use. Refused explicitly (0A000) rather than answered wrong.
LATERAL itself is no longer a structural gap — that was a mistaken
reading of the engine. Datahike's bind-by-fn applies a function once per
production tuple and expands the result through the binding form, so
[(f ?n) [[?v ?ord]]] already evaluates per outer row inside the ordinary
flat :where. Both FROM t, LATERAL generate_series(1, t.n) and
JOIN LATERAL (SELECT … WHERE x = t.id) s ON true compile to ONE Datalog
query with no speculative data — the subquery case by running the inner
through that same binding, with *from-bindings* holding the outer values.
That is why they keep the parse cache and fast-select lanes a
materialisation approach would forfeit.
Portal streaming / row limits. Extended-protocol Execute with a row cap
(PortalSuspended) — driver fetchSize, server-side cursors over large
results. Today all rows materialise from one d/q.
ProjectSet around grouping, windows, and DISTINCT ON. Target-list SRFs
now expand into rows, zip same-level SRFs with NULL padding, preserve nested
levels, and feed derived tables, CTAS, INSERT…SELECT, set operations, sorting,
and limits. PostgreSQL can place different ProjectSet levels on either side
of grouping, window, and distinct stages according to their references; the
combinations that need that movable stage are still rejected explicitly.
int4range, tsrange, …): text round-trips,
but the binary Range codec and range constructor functions are not yet
implemented.interval, the internal "char",
aclitem, range types, and less common polymorphic/operator overloads.pg_stat_activity, pg_index, pg_constraint,
pg_proc, etc. — added as tools require them.CREATE FUNCTION / plpgsql / DO: not implemented.These are inherent to the storage model and documented as boundaries, not gaps to close:
FOR UPDATE / advisory locks / savepoints are
approximated; genuinely concurrent isolation levels are not the model. This is
the most important boundary for pooled workloads.pg_temp isolation: drop-on-disconnect covers sequential use;
concurrent same-name temp tables across sessions are not namespaced.Can you improve this documentation?Edit on GitHub
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 |