Datomic Compatibility
pg_mentat implements the core Datomic data model and query language as a PostgreSQL extension. This document reflects the actual implementation status of the current codebase.
Overview
pg_mentat provides Datomic-compatible schema, transactions, and Datalog queries via SQL functions. The companion mentatd server exposes an HTTP API compatible with Datomic client libraries.
Transaction Operations
| Feature |
Datomic |
pg_mentat |
Status |
|---|
:db/add |
Yes |
Yes |
Complete |
:db/retract |
Yes |
Yes |
Complete |
:db/retractEntity |
Yes |
Yes |
Complete – cascades :db/isComponent children and retracts inbound refs (1.14.0); accepts a lookup ref |
:db.fn/cas |
Yes |
Yes |
Complete – compare-and-swap; accepts a lookup ref |
with (speculative) |
Yes |
Yes |
Complete – mentat_with() |
| Map notation |
Yes |
Yes |
Complete |
| Tempids |
Yes |
Yes |
Complete |
| Lookup refs |
Yes |
Yes |
Complete |
| Upsert (unique/identity) |
Yes |
Yes |
Complete |
| Tx metadata (:db/txInstant) |
Yes |
Yes |
Complete |
| Negative tempids |
Yes |
No |
N/A – use string tempids |
:db/fn (database functions) |
Yes (Clojure) |
Yes (mino, script builds) |
1.14.0 – sandboxed mino (fn [db & args] ...) returning tx-data; see Transaction functions |
Transaction functions
In builds with the script feature, an ident carrying :db/fn is a
transaction function. Its value is mino (a Clojure dialect) source for
(fn [db & args] ...) returning tx-data, which is spliced into the calling
transaction (and may itself invoke tx-functions, built-ins, or lookup refs):
SELECT edn_t($$[{:db/ident :acct/deposit
:db/fn "(fn [db id amt]
(let [bal (ffirst (mentat.store/q db
'[:find ?b :in $ ?id :where [?e :acct/id ?id] [?e :acct/balance ?b]]
id))]
[[:db/cas [:acct/id id] :acct/balance bal (+ bal amt)]]))"}]$$);
SELECT edn_t('[[:acct/deposit "A-1" 50]]');
The function sees the database as of the start of the transaction (as in
Datomic) and runs in the same sandbox as edn_eval: no filesystem or network,
bounded by mentat.script_max_steps / _heap_bytes / _depth, and
mentat.txfn_max_depth (default 16) bounds tx-functions calling tx-functions.
It runs as the calling role. Use :db.fn/cas in the returned tx-data for an
atomic read-modify-write, as above.
Query Operations
Find Specifications
| Feature |
Datomic |
pg_mentat |
Status |
|---|
Relation [:find ?x ?y] |
Yes |
Yes |
Complete |
Collection [:find [?x ...]] |
Yes |
Yes |
Complete |
Tuple [:find [?x ?y]] |
Yes |
Yes |
Complete |
Scalar [:find ?x .] |
Yes |
Yes |
Complete |
Where Clauses
| Feature |
Datomic |
pg_mentat |
Status |
|---|
| Basic patterns |
Yes |
Yes |
Complete |
| NOT clauses |
Yes |
Yes |
Complete – NOT EXISTS subquery |
| NOT-JOIN |
Yes |
Yes |
Complete |
| OR clauses |
Yes |
Yes |
Complete – UNION |
| OR-JOIN |
Yes |
Yes |
Complete |
| Feature |
Datomic |
pg_mentat |
Status |
|---|
Scalar :in ?x |
Yes |
Yes |
Complete |
Collection :in [?x ...] |
Yes |
Yes |
Complete |
Tuple :in [?x ?y] |
Yes |
Yes |
Complete |
Relation :in [[?x ?y]] |
Yes |
Yes |
Complete |
Query Functions
| Function |
Datomic |
pg_mentat |
Status |
|---|
get-else |
Yes |
Yes |
Complete – LEFT JOIN + COALESCE |
missing? |
Yes |
Yes |
Complete – NOT EXISTS subquery |
ground |
Yes |
Yes |
Complete – constant binding |
get-some |
Yes |
No |
Not implemented |
tuple |
Yes |
No |
Not implemented |
untuple |
Yes |
No |
Not implemented |
Predicates
| Feature |
Datomic |
pg_mentat |
Status |
|---|
=, != |
Yes |
Yes |
Complete |
<, >, <=, >= |
Yes |
Yes |
Complete |
Arithmetic (+, -, *, /) |
Yes |
Yes |
Complete |
like / ilike |
N/A |
Yes |
Extension – PostgreSQL LIKE/ILIKE |
| Type-safety guard |
Yes |
Yes |
Complete – mismatched types produce empty set |
Aggregates
| Aggregate |
Datomic |
pg_mentat |
Status |
|---|
count |
Yes |
Yes |
Complete |
count-distinct |
Yes |
Yes |
Complete |
sum |
Yes |
Yes |
Complete |
min |
Yes |
Yes |
Complete |
max |
Yes |
Yes |
Complete |
avg |
Yes |
Yes |
Complete |
sample |
Yes |
Yes |
Complete |
median |
Yes |
No |
Not implemented |
variance |
Yes |
No |
Not implemented |
stddev |
Yes |
No |
Not implemented |
Rules
| Feature |
Datomic |
pg_mentat |
Status |
|---|
| Named rules |
Yes |
Yes |
Complete |
| Recursive rules |
Yes |
Yes |
Complete – WITH RECURSIVE CTE |
| Rule sets (multiple heads) |
Yes |
Yes |
Complete |
| Rules with predicates |
Yes |
Yes |
Complete |
Pull API
| Feature |
Datomic |
pg_mentat |
Status |
|---|
| Attribute selection |
Yes |
Yes |
Complete |
Wildcard [*] |
Yes |
Yes |
Complete |
Reverse lookup [:_attr] |
Yes |
Yes |
Complete |
| Nested/map specs |
Yes |
Yes |
Complete |
Recursion {:attr ...} |
Yes |
Yes |
Complete – cycle detection |
Bounded recursion {:attr N} |
Yes |
Yes |
Complete |
| Default values |
Yes |
Yes |
Complete |
Rename (:as) |
Yes |
Yes |
Complete |
Limit (:limit N) |
Yes |
Yes |
Complete |
| Component auto-expand |
Yes |
Yes |
Complete |
pull-many |
Yes |
Yes |
Complete |
| Namespace wildcard |
Yes |
No |
Not implemented |
Schema
| Feature |
Datomic |
pg_mentat |
Status |
|---|
:db/ident |
Yes |
Yes |
Complete |
:db/valueType |
Yes |
Yes |
Complete (9 types) |
:db/cardinality |
Yes |
Yes |
Complete (one/many) |
:db/unique (value) |
Yes |
Yes |
Complete |
:db/unique (identity) |
Yes |
Yes |
Complete + upsert |
:db/index |
Yes |
Yes |
Complete |
:db/fulltext |
Yes |
Yes |
Complete – BM25 scoring |
:db/isComponent |
Yes |
Yes |
Complete – cascade retraction |
:db/noHistory |
Yes |
Yes |
Complete |
:db/doc |
Yes |
Yes |
Complete |
| Schema alteration |
Yes |
Yes |
Complete |
Value Types
| Type |
Datomic |
pg_mentat |
Storage |
|---|
:db.type/boolean |
Yes |
Yes |
BOOLEAN in datoms_boolean_new |
:db.type/long |
Yes |
Yes |
BIGINT in datoms_long_new |
:db.type/double |
Yes |
Yes |
DOUBLE PRECISION in datoms_double_new |
:db.type/string |
Yes |
Yes |
TEXT in datoms_text_new |
:db.type/keyword |
Yes |
Yes |
TEXT in datoms_keyword_new |
:db.type/ref |
Yes |
Yes |
BIGINT in datoms_ref_new |
:db.type/instant |
Yes |
Yes |
TIMESTAMPTZ in datoms_instant_new |
:db.type/uuid |
Yes |
Yes |
UUID in datoms_uuid_new |
:db.type/bytes |
Yes |
Yes |
BYTEA in datoms_bytes_new |
:db.type/bigint |
Yes |
No |
Not implemented |
:db.type/bigdec |
Yes |
No |
Not implemented |
:db.type/uri |
Yes |
No |
Not Planned – use string |
:db.type/tuple |
Yes |
No |
Not Planned – Datomic Cloud only |
Time-Travel
| Feature |
Datomic |
pg_mentat |
Status |
|---|
as-of |
Yes |
Yes |
Complete – mentat_as_of() |
since |
Yes |
Yes |
Complete – mentat_since() |
history |
Yes |
Yes |
Complete – mentat_history() |
tx-range |
Yes |
Yes |
Complete – mentat_tx_range() |
| Basis-t |
Yes |
Yes |
Complete – MAX(tx) query |
Excision
| Feature |
Datomic |
pg_mentat |
Status |
|---|
| Entity excision |
Yes |
Yes |
Complete – mentat_excise() |
| Partition gating |
Yes |
Yes |
Complete – allow_excision flag |
| Schema protection |
Yes |
Yes |
Complete – entid < 10000 blocked |
| Dangling ref check |
Yes |
Yes |
Complete |
| Excision log |
N/A |
Yes |
Extension – audit trail |
Subscriptions / tx-report-queue
| Feature |
Datomic |
pg_mentat |
Status |
|---|
| tx-report-queue |
Yes |
Yes |
Complete – LISTEN/NOTIFY triggers |
| Subscribe by query |
No |
Yes |
Extension – mentat_subscribe() |
| Unsubscribe |
Yes |
Yes |
Complete – mentat_unsubscribe() |
mentatd HTTP Protocol
| Feature |
Datomic Client |
mentatd |
Status |
|---|
| list-databases |
Yes |
Yes |
Complete |
| create-database |
Yes |
Yes |
Complete |
| delete-database |
Yes |
Yes |
Complete |
| connect |
Yes |
Yes |
Complete |
| transact |
Yes |
Yes |
Complete |
| query |
Yes |
Yes |
Complete |
| pull |
Yes |
Yes |
Complete |
| entity |
Yes |
Yes |
Complete |
| EDN serialization |
Yes |
Yes |
Complete |
| Transit+JSON |
Yes |
Yes |
Complete |
| CORS |
N/A |
Yes |
Complete |
| HTTP/2 |
Yes |
No |
Not implemented |
| Streaming responses |
Yes |
No |
Not implemented |
Known Incompatibilities
| Feature |
Reason |
Workaround |
|---|
:db/fn body language |
Datomic fns are JVM Clojure; pg_mentat runs sandboxed mino |
Port the body (same contract: db + args -> tx-data) |
| Peer mode |
Architecture incompatible (embedded JVM) |
Use mentatd HTTP API |
| Datomic Analytics |
Proprietary |
PostgreSQL analytics tools |
| Negative tempids |
Not supported |
Use string tempids |
| Peer cache |
No equivalent |
Application-layer caching |
Migration from Datomic
Schema definitions transfer directly. Remove #db/id partition references:
;; Datomic
[{:db/id #db/id[:db.part/db]
:db/ident :person/name
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one
:db.install/_attribute :db.part/db}]
;; pg_mentat (same minus partition boilerplate)
[{:db/ident :person/name
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one}]
Queries work unchanged. Transactions work unchanged. Pull patterns work unchanged.