pg_mentat

This Release
pg_mentat 1.10.1
Date
Status
Stable
Abstract
Datomic-compatible Datalog query engine for PostgreSQL
Description
pg_mentat implements the Datomic/Mentat data model (entity-attribute-value with time-travel) entirely within PostgreSQL. Provides Datalog query execution, pull API, temporal queries, and transaction processing via SQL functions.
Released By
gregburd
License
Apache 2.0
Resources
Special Files
Tags

Extensions

pg_mentat 1.10.1
Datomic-compatible Datalog query engine

Documentation

overflows
overflows
pg-as_of-s-c1-r2
pg-as_of-s-c1-r2
pg-as_of-m-c1-r2
pg-as_of-m-c1-r2
pg-aggregate-m-c1-r3
pg-aggregate-m-c1-r3
pg-predicate_scan-m-c1-r1
pg-predicate_scan-m-c1-r1
pg-db-settings
pg-db-settings
300k-q2_ref_traversal-eav
300k-q2_ref_traversal-eav
pg-concurrency_sweep-s-c8-r3
pg-concurrency_sweep-s-c8-r3
m-sum.timing
m-sum.timing
pg-since-xl-c1-r3
pg-since-xl-c1-r3
env
env
pg-point_lookup-s-c1-r1
pg-point_lookup-s-c1-r1
pg-concurrency_sweep-s-c8-r3
pg-concurrency_sweep-s-c8-r3
pg-pull-m-c1-r1
pg-pull-m-c1-r1
README
mentat_sqlite_ext: mentat as a SQLite loadable extension
100k-q4_predicate-mentat
100k-q4_predicate-mentat
pg-point_lookup-m-c1-r2
pg-point_lookup-m-c1-r2
README
pg_mentat Test Suite
pg-sizes-xl
pg-sizes-xl
pg-concurrency_sweep-m-c128-r2
pg-concurrency_sweep-m-c128-r2
pg-pull-s-c1-r3
pg-pull-s-c1-r3
pg-pull-xl-c1-r3
pg-pull-xl-c1-r3
sizes
sizes
pg-wm_write-s-c1-r1
pg-wm_write-s-c1-r1
before-190-s-q3
before-190-s-q3
pg-sizes-m
pg-sizes-m
pg-aggregate-l-c1-r1
pg-aggregate-l-c1-r1
pg-concurrency_sweep-m-c1-r1
pg-concurrency_sweep-m-c1-r1
pg-ref_traversal-m-c1-r3
pg-ref_traversal-m-c1-r3
fuzzystrmatch
Phonetic and Edit-Distance Functions via fuzzystrmatch
pg-input_bindings-xl-c1-r3
pg-input_bindings-xl-c1-r3
pg-ref_traversal-m-c1-r2
pg-ref_traversal-m-c1-r2
summary
mentat scale benchmark: final
pg-predicate_scan-l-c1-r1
pg-predicate_scan-l-c1-r1
1M-q2_ref_traversal-eav
1M-q2_ref_traversal-eav
pg-as_of-m-c1-r1
pg-as_of-m-c1-r1
pg-as_of-s-c1-r2
pg-as_of-s-c1-r2
pg-ref_traversal-m-c1-r1
pg-ref_traversal-m-c1-r1
pg_infer
Model-Knowledge Search via pg_infer
pg-concurrency_sweep-l-c32-r3
pg-concurrency_sweep-l-c32-r3
pg-point_lookup-s-c1-r1
pg-point_lookup-s-c1-r1
summary
Embedded mentat fixes (1.10.0): v1.9.0 vs 6fed515a
pg-aggregate-s-c1-r1
pg-aggregate-s-c1-r1
300k-q2_ref_traversal-mentat
300k-q2_ref_traversal-mentat
pg-input_bindings-s-c1-r1
pg-input_bindings-s-c1-r1
pg-concurrency_sweep-m-c1-r1
pg-concurrency_sweep-m-c1-r1
COMPLETION
mino-rs port — completion report (8-hour unattended run)
pg-wm_read-s-c8-r2
pg-wm_read-s-c8-r2
m-lbl.explain
m-lbl.explain
mino-integration-plan
Mentat × mino integration — review & plan
s-sum.timing
s-sum.timing
stmt-stats-1M
stmt-stats-1M
pg-as_of-s-c1-r1
pg-as_of-s-c1-r1
pg-since-l-c1-r2
pg-since-l-c1-r2
pg-since-m-c1-r1
pg-since-m-c1-r1
pg-concurrency_sweep-xl-c32-r1
pg-concurrency_sweep-xl-c32-r1
about
Project Mentat
pg-pull-m-c1-r3
pg-pull-m-c1-r3
pg-predicate_scan-m-c1-r1
pg-predicate_scan-m-c1-r1
env
env
pg-concurrency_sweep-m-c32-r1
pg-concurrency_sweep-m-c32-r1
pg-since-m-c1-r2
pg-since-m-c1-r2
pg-concurrency_sweep-s-c64-r2
pg-concurrency_sweep-s-c64-r2
report
mentat scale benchmark: duckdb-quack
pg-concurrency_sweep-s-c1-r2
pg-concurrency_sweep-s-c1-r2
pg-since-m-c1-r3
pg-since-m-c1-r3
pg-predicate_scan-m-c1-r3
pg-predicate_scan-m-c1-r3
pg-concurrency_sweep-s-c1-r3
pg-concurrency_sweep-s-c1-r3
pg-concurrency_sweep-s-c64-r3
pg-concurrency_sweep-s-c64-r3
pg-concurrency_sweep-s-c128-r1
pg-concurrency_sweep-s-c128-r1
pg-point_lookup-m-c1-r3
pg-point_lookup-m-c1-r3
env
env
pg-sustained-xl
pg-sustained-xl
timescaledb
Time-Series Storage via TimescaleDB
pg_cron
Scheduled Maintenance via pg_cron
README
README
pg-concurrency_sweep-l-c1-r3
pg-concurrency_sweep-l-c1-r3
s-q3.explain
s-q3.explain
pg-as_of-l-c1-r2
pg-as_of-l-c1-r2
300k-q3_aggregate-eav
300k-q3_aggregate-eav
300k-q1_point_lookup-mentat
300k-q1_point_lookup-mentat
pg-wm_read-s-c8-r3
pg-wm_read-s-c8-r3
checks
checks
checks
checks
pg-concurrency_sweep-s-c8-r2
pg-concurrency_sweep-s-c8-r2
checks
checks
pg-concurrency_sweep-m-c64-r3
pg-concurrency_sweep-m-c64-r3
pg-concurrency_sweep-m-c8-r2
pg-concurrency_sweep-m-c8-r2
pg-pull-m-c1-r1
pg-pull-m-c1-r1
protocol-overhead
protocol-overhead
pg-ref_traversal-s-c1-r3
pg-ref_traversal-s-c1-r3
pg-concurrency_sweep-s-c1-r3
pg-concurrency_sweep-s-c1-r3
pg-as_of-s-c1-r3
pg-as_of-s-c1-r3
env
env
s-nk.explain
s-nk.explain
pg-point_lookup-s-c1-r3
pg-point_lookup-s-c1-r3
pg-concurrency_sweep-l-c1-r2
pg-concurrency_sweep-l-c1-r2
sizes
sizes
pg-point_lookup-s-c1-r3
pg-point_lookup-s-c1-r3
pg-concurrency_sweep-m-c1-r2
pg-concurrency_sweep-m-c1-r2
m-lbl.timing
m-lbl.timing
README
README
m-sum.sql
m-sum.sql
pg-since-s-c1-r3
pg-since-s-c1-r3
pg-point_lookup-s-c1-r2
pg-point_lookup-s-c1-r2
pg-ref_traversal-s-c1-r1
pg-ref_traversal-s-c1-r1
1M-q4_predicate-eav
1M-q4_predicate-eav
pg-wm_write-s-c1-r3
pg-wm_write-s-c1-r3
pg-point_lookup-xl-c1-r1
pg-point_lookup-xl-c1-r1
scripting
Scripting: mino
pg-wm_read-s-c8-r1
pg-wm_read-s-c8-r1
pg-concurrency_sweep-m-c128-r3
pg-concurrency_sweep-m-c128-r3
pg-input_bindings-l-c1-r2
pg-input_bindings-l-c1-r2
pg-pull-m-c1-r3
pg-pull-m-c1-r3
pg-input_bindings-xl-c1-r2
pg-input_bindings-xl-c1-r2
pg-stat-statements-m
pg-stat-statements-m
env
env
pg-pull-m-c1-r2
pg-pull-m-c1-r2
pg-input_bindings-m-c1-r2
pg-input_bindings-m-c1-r2
s-lbl.sql
s-lbl.sql
pg-as_of-m-c1-r1
pg-as_of-m-c1-r1
1M-q3_aggregate-mentat
1M-q3_aggregate-mentat
compatibility_report
pg_mentat / mentatd – Datomic Compatibility Report
pg19_graph
Property Graph Queries via PG19 SQL/PGQ
pg-concurrency_sweep-s-c128-r2
pg-concurrency_sweep-s-c128-r2
pg-concurrency_sweep-l-c8-r1
pg-concurrency_sweep-l-c8-r1
RESULTS
Extension store cache: results
pg-concurrency_sweep-l-c128-r2
pg-concurrency_sweep-l-c128-r2
pg-concurrency_sweep-xl-c8-r3
pg-concurrency_sweep-xl-c8-r3
after-m1-m-q3
after-m1-m-q3
m-q3.explain
m-q3.explain
SUMMARY
Summary
pg-point_lookup-l-c1-r1
pg-point_lookup-l-c1-r1
pg-ref_traversal-l-c1-r1
pg-ref_traversal-l-c1-r1
pg-concurrency_sweep-m-c64-r1
pg-concurrency_sweep-m-c64-r1
pg_mentat-port-inventory
pg_mentat → embedded Mentat: advances port inventory
pg-concurrency_sweep-s-c128-r3
pg-concurrency_sweep-s-c128-r3
pg-concurrency_sweep-xl-c1-r1
pg-concurrency_sweep-xl-c1-r1
pg-as_of-xl-c1-r2
pg-as_of-xl-c1-r2
pg-concurrency_sweep-s-c8-r2
pg-concurrency_sweep-s-c8-r2
sizes-1M
sizes-1M
checks
checks
pg-concurrency_sweep-l-c128-r1
pg-concurrency_sweep-l-c128-r1
pg-pull-s-c1-r2
pg-pull-s-c1-r2
pg-concurrency_sweep-xl-c64-r2
pg-concurrency_sweep-xl-c64-r2
pg-concurrency_sweep-s-c1-r2
pg-concurrency_sweep-s-c1-r2
pg-predicate_scan-s-c1-r1
pg-predicate_scan-s-c1-r1
env
env
tutorial
Introduction
pg-stat-statements-xl
pg-stat-statements-xl
pg-ref_traversal-s-c1-r3
pg-ref_traversal-s-c1-r3
pg-concurrency_sweep-s-c32-r1
pg-concurrency_sweep-s-c32-r1
pg-concurrency_sweep-m-c1-r2
pg-concurrency_sweep-m-c1-r2
pgque
Transactional Event Stream via PgQue
pg-aggregate-m-c1-r3
pg-aggregate-m-c1-r3
pg-predicate_scan-l-c1-r2
pg-predicate_scan-l-c1-r2
pg-wm_write-xl-c1-r3
pg-wm_write-xl-c1-r3
m-nk.explain
m-nk.explain
pg-wm_read-m-c8-r3
pg-wm_read-m-c8-r3
pg-pull-s-c1-r3
pg-pull-s-c1-r3
pg-concurrency_sweep-xl-c64-r1
pg-concurrency_sweep-xl-c64-r1
pg-since-m-c1-r3
pg-since-m-c1-r3
pg-concurrency_sweep-s-c64-r1
pg-concurrency_sweep-s-c64-r1
pg-concurrency_sweep-m-c8-r3
pg-concurrency_sweep-m-c8-r3
autoindex-wide-range
autoindex-wide-range
s-lbl.timing
s-lbl.timing
m-q3.sql
m-q3.sql
pg-input_bindings-s-c1-r1
pg-input_bindings-s-c1-r1
pg-wm_write-m-c1-r3
pg-wm_write-m-c1-r3
introduction
Introduction
sizes-100k
sizes-100k
1M-q2_ref_traversal-mentat
1M-q2_ref_traversal-mentat
summary
DuckDB over Quack: three-way comparison
pg-input_bindings-m-c1-r1
pg-input_bindings-m-c1-r1
1M-q1_point_lookup-eav
1M-q1_point_lookup-eav
300k-q1_point_lookup-eav
300k-q1_point_lookup-eav
pg-sizes-s
pg-sizes-s
CHANGELOG
Changelog
README
mentat documentation
pg-wm_write-xl-c1-r2
pg-wm_write-xl-c1-r2
pg-pull-l-c1-r3
pg-pull-l-c1-r3
1M-q1_point_lookup-mentat
1M-q1_point_lookup-mentat
pg-predicate_scan-s-c1-r3
pg-predicate_scan-s-c1-r3
pg-input_bindings-m-c1-r3
pg-input_bindings-m-c1-r3
pg-point_lookup-xl-c1-r2
pg-point_lookup-xl-c1-r2
pg-predicate_scan-s-c1-r3
pg-predicate_scan-s-c1-r3
pg-wm_write-l-c1-r2
pg-wm_write-l-c1-r2
fuzzy-search
Approximate-Regex Search via pg_tre (optional)
pg-since-l-c1-r1
pg-since-l-c1-r1
pg-ref_traversal-xl-c1-r2
pg-ref_traversal-xl-c1-r2
pg-input_bindings-m-c1-r1
pg-input_bindings-m-c1-r1
duckdb-extension-plan
Plan: a mentat DuckDB extension in Rust (crates/duckdb)[duckdb_entrypoint_c_api]
checks
checks
pg-concurrency_sweep-l-c128-r3
pg-concurrency_sweep-l-c128-r3
pg-wm_write-s-c1-r2
pg-wm_write-s-c1-r2
pg-input_bindings-m-c1-r2
pg-input_bindings-m-c1-r2
pg-ref_traversal-xl-c1-r1
pg-ref_traversal-xl-c1-r1
sizes-300k
sizes-300k
pg-input_bindings-s-c1-r2
pg-input_bindings-s-c1-r2
s-q3.sql
s-q3.sql
pg-since-xl-c1-r2
pg-since-xl-c1-r2
pg-wm_read-l-c8-r2
pg-wm_read-l-c8-r2
pg-since-xl-c1-r1
pg-since-xl-c1-r1
README
mentat_duckdb — mentat as a DuckDB loadable extension
PERFORMANCE
mentatd Performance Guide
postgis
Geospatial Search via PostGIS
CHANGELOG
Changelog — mino-rs
pg-ref_traversal-s-c1-r2
pg-ref_traversal-s-c1-r2
pg-since-m-c1-r1
pg-since-m-c1-r1
CACHE_IMPLEMENTATION
Attribute Cache Implementation
README
mentatd
pg-concurrency_sweep-xl-c8-r2
pg-concurrency_sweep-xl-c8-r2
pg-aggregate-l-c1-r3
pg-aggregate-l-c1-r3
pg-concurrency_sweep-l-c8-r3
pg-concurrency_sweep-l-c8-r3
contributing
Contributing
pg-concurrency_sweep-m-c8-r1
pg-concurrency_sweep-m-c8-r1
pg-concurrency_sweep-xl-c64-r3
pg-concurrency_sweep-xl-c64-r3
after-m1-m-q4
after-m1-m-q4
pg-concurrency_sweep-l-c64-r3
pg-concurrency_sweep-l-c64-r3
pg-predicate_scan-m-c1-r2
pg-predicate_scan-m-c1-r2
s-sum.sql
s-sum.sql
pg-pull-m-c1-r2
pg-pull-m-c1-r2
pg-concurrency_sweep-l-c64-r1
pg-concurrency_sweep-l-c64-r1
pg_partman
Declarative Partitioning via pg_partman
operations
Operations: Throughput, Bloat, and the Live Projection
sizes
sizes
pg-input_bindings-l-c1-r1
pg-input_bindings-l-c1-r1
300k-q4_predicate-mentat
300k-q4_predicate-mentat
README
mentat_cli
pg-concurrency_sweep-l-c32-r1
pg-concurrency_sweep-l-c32-r1
pg-ref_traversal-xl-c1-r3
pg-ref_traversal-xl-c1-r3
pg-aggregate-m-c1-r2
pg-aggregate-m-c1-r2
pg-since-l-c1-r3
pg-since-l-c1-r3
README
Phase 2 benchmark harness for pg_mentat
pg-wm_write-m-c1-r2
pg-wm_write-m-c1-r2
postgres-fdw
Cross-Database Datalog with postgres_fdw
pg-concurrency_sweep-l-c32-r2
pg-concurrency_sweep-l-c32-r2
meta
meta
pg-aggregate-s-c1-r2
pg-aggregate-s-c1-r2
wal-probe
wal-probe
pg-concurrency_sweep-s-c32-r3
pg-concurrency_sweep-s-c32-r3
pg-aggregate-s-c1-r3
pg-aggregate-s-c1-r3
README
Historical SQL migrations
100k-q2_ref_traversal-eav
100k-q2_ref_traversal-eav
pg-pull-s-c1-r1
pg-pull-s-c1-r1
pg-point_lookup-m-c1-r3
pg-point_lookup-m-c1-r3
pg-point_lookup-s-c1-r2
pg-point_lookup-s-c1-r2
CONTRIBUTING
How to contribute to Project Mentat
m-nk.sql
m-nk.sql
pg-pull-xl-c1-r2
pg-pull-xl-c1-r2
m-sum.explain
m-sum.explain
threeway
threeway
env
env
pgvector
Vector Search via pgvector
m-q3.timing
m-q3.timing
pg-aggregate-s-c1-r1
pg-aggregate-s-c1-r1
README
PostgreSQL Schema Files for pg_mentat
sizes-1M
sizes-1M
pg-input_bindings-s-c1-r3
pg-input_bindings-s-c1-r3
pg-since-s-c1-r1
pg-since-s-c1-r1
pg-input_bindings-s-c1-r3
pg-input_bindings-s-c1-r3
summary
mentat scale benchmark: before
pg-concurrency_sweep-m-c8-r3
pg-concurrency_sweep-m-c8-r3
sizes
sizes
pg-ref_traversal-l-c1-r3
pg-ref_traversal-l-c1-r3
pg-ref_traversal-l-c1-r2
pg-ref_traversal-l-c1-r2
pg-wm_read-l-c8-r1
pg-wm_read-l-c8-r1
pg-aggregate-s-c1-r2
pg-aggregate-s-c1-r2
multi-tenancy
Multi-Tenancy with Per-Store Row-Level Security
pg-wm_read-l-c8-r3
pg-wm_read-l-c8-r3
pg-since-m-c1-r2
pg-since-m-c1-r2
pg-aggregate-l-c1-r2
pg-aggregate-l-c1-r2
pg-as_of-xl-c1-r3
pg-as_of-xl-c1-r3
pg-point_lookup-m-c1-r1
pg-point_lookup-m-c1-r1
100k-q2_ref_traversal-mentat
100k-q2_ref_traversal-mentat
pg-concurrency_sweep-l-c1-r1
pg-concurrency_sweep-l-c1-r1
stmt-stats-300k
stmt-stats-300k
pg-ref_traversal-s-c1-r1
pg-ref_traversal-s-c1-r1
pg-input_bindings-xl-c1-r1
pg-input_bindings-xl-c1-r1
README
mentatd Integration Tests
after-m1-m-q1
after-m1-m-q1
pg-concurrency_sweep-s-c1-r1
pg-concurrency_sweep-s-c1-r1
pg-concurrency_sweep-xl-c8-r1
pg-concurrency_sweep-xl-c8-r1
stmt-stats-100k
stmt-stats-100k
pg-concurrency_sweep-s-c1-r1
pg-concurrency_sweep-s-c1-r1
compare
compare
time-travel
Time Travel
pg-wm_write-m-c1-r1
pg-wm_write-m-c1-r1
pg-sizes-l
pg-sizes-l
pg-concurrency_sweep-s-c8-r1
pg-concurrency_sweep-s-c8-r1
pg-stat-statements-m
pg-stat-statements-m
CHANGES
Changes to the vendored rust-gc v0.5.1
pg-concurrency_sweep-m-c128-r1
pg-concurrency_sweep-m-c128-r1
server-store-handles
server-store-handles
pg-concurrency_sweep-m-c1-r3
pg-concurrency_sweep-m-c1-r3
pg-predicate_scan-l-c1-r3
pg-predicate_scan-l-c1-r3
pg-concurrency_sweep-s-c32-r1
pg-concurrency_sweep-s-c32-r1
pg-concurrency_sweep-xl-c1-r3
pg-concurrency_sweep-xl-c1-r3
pg-stat-statements-s
pg-stat-statements-s
pg-stat-statements-s
pg-stat-statements-s
pg-since-s-c1-r1
pg-since-s-c1-r1
dmesg-oom
dmesg-oom
m-nk.timing
m-nk.timing
tree
tree
sizes-300k
sizes-300k
pg-point_lookup-m-c1-r2
pg-point_lookup-m-c1-r2
registry-publishing
Registry publishing: DuckDB Community Extensions + PGXN
pg-concurrency_sweep-m-c32-r3
pg-concurrency_sweep-m-c32-r3
pg-ref_traversal-m-c1-r2
pg-ref_traversal-m-c1-r2
final-s-q3
final-s-q3
datalog
Datalog Query Language
pg-concurrency_sweep-m-c64-r2
pg-concurrency_sweep-m-c64-r2
1M-q3_aggregate-eav
1M-q3_aggregate-eav
final-m-q3
final-m-q3
pg-since-s-c1-r3
pg-since-s-c1-r3
pg-since-s-c1-r2
pg-since-s-c1-r2
SQL_INTEGRATION
SQL Integration Guide
compare
compare
pg-concurrency_sweep-l-c8-r2
pg-concurrency_sweep-l-c8-r2
README
Conformance corpus
100k-q4_predicate-eav
100k-q4_predicate-eav
pg-as_of-s-c1-r3
pg-as_of-s-c1-r3
sizes-100k
sizes-100k
env
env
pg-pull-s-c1-r1
pg-pull-s-c1-r1
pg-wm_write-xl-c1-r1
pg-wm_write-xl-c1-r1
100k-q1_point_lookup-mentat
100k-q1_point_lookup-mentat
2026-08-28-mino-rs-port
mino-rs: Rust port of the mino Clojure-dialect interpreter — Implementation Plan
schema
Schema Reference
pg-stat-statements-l
pg-stat-statements-l
README
README
pg-concurrency_sweep-m-c8-r1
pg-concurrency_sweep-m-c8-r1
pg-aggregate-m-c1-r2
pg-aggregate-m-c1-r2
VENDORED
Vendored crates
single-overflows
single-overflows
sizes
sizes
pg-concurrency_sweep-s-c8-r1
pg-concurrency_sweep-s-c8-r1
pg-aggregate-m-c1-r1
pg-aggregate-m-c1-r1
m-lbl.sql
m-lbl.sql
checks
checks
sizes
sizes
pg-as_of-m-c1-r3
pg-as_of-m-c1-r3
pg-pull-l-c1-r2
pg-pull-l-c1-r2
README
Datomic Client Compatibility Tests
100k-q3_aggregate-eav
100k-q3_aggregate-eav
pg-concurrency_sweep-l-c64-r2
pg-concurrency_sweep-l-c64-r2
pg-as_of-m-c1-r2
pg-as_of-m-c1-r2
checks
checks
EDN_TYPE
EDN Type Guide
pg-input_bindings-l-c1-r3
pg-input_bindings-l-c1-r3
final-m-q4
final-m-q4
s-q3.timing
s-q3.timing
wal-growth
wal-growth
pg-pull-s-c1-r2
pg-pull-s-c1-r2
README
pg_mentat
pg-pull-l-c1-r1
pg-pull-l-c1-r1
README
benchmarks/scale: multi-backend scale and load suite
RESUME-CHECKPOINT
Resume checkpoint — after 1.9.0 + first scale benchmark run
300k-q4_predicate-eav
300k-q4_predicate-eav
pg-input_bindings-s-c1-r2
pg-input_bindings-s-c1-r2
100k-q1_point_lookup-eav
100k-q1_point_lookup-eav
summary
mentat scale benchmark: after
summary
mentat scale benchmark: ext-base
quack-verify
quack-verify
MIGRATION_GUIDE
pg_mentat Migration Guide
before-190-m-q4
before-190-m-q4
pg-concurrency_sweep-s-c32-r3
pg-concurrency_sweep-s-c32-r3
README
README
s-nk.sql
s-nk.sql
pg-as_of-s-c1-r1
pg-as_of-s-c1-r1
pg-as_of-m-c1-r3
pg-as_of-m-c1-r3
100k-q3_aggregate-mentat
100k-q3_aggregate-mentat
pg-predicate_scan-s-c1-r1
pg-predicate_scan-s-c1-r1
pg-as_of-l-c1-r1
pg-as_of-l-c1-r1
pg-concurrency_sweep-m-c1-r3
pg-concurrency_sweep-m-c1-r3
pg-concurrency_sweep-m-c32-r3
pg-concurrency_sweep-m-c32-r3
pg-predicate_scan-m-c1-r3
pg-predicate_scan-m-c1-r3
pg-trgm
Trigram Similarity via pg_trgm
findings
findings
pg-wm_write-l-c1-r3
pg-wm_write-l-c1-r3
pg-concurrency_sweep-m-c32-r1
pg-concurrency_sweep-m-c32-r1
summary
pg_mentat 1.10.0: AVET, count, auto-index, edn_q_rows – results
status
status
mentatd
mentatd HTTP Server
pg-point_lookup-l-c1-r2
pg-point_lookup-l-c1-r2
s-sum.explain
s-sum.explain
CONCURRENCY_IMPROVEMENTS
Concurrency Improvements for Mentatd
pull-api
Pull API
README
rust-gc
README
README
pg-concurrency_sweep-m-c32-r2
pg-concurrency_sweep-m-c32-r2
summary
mentat scale benchmark: scale-2026-09-27T010840Z
cookbook-postgres-join
Cookbook: JOIN Datalog Results With Regular Postgres Tables
autoindex-narrow-range
autoindex-narrow-range
architecture
Architecture
pg-wm_read-xl-c8-r3
pg-wm_read-xl-c8-r3
README
README
datomic-compat
Datomic Compatibility
env
env
fresh-process
fresh-process
INTEGRATIONS
Postgres Extension Integrations — Plan
before-190-m-q1
before-190-m-q1
pg-wm_read-m-c8-r1
pg-wm_read-m-c8-r1
1M-q4_predicate-mentat
1M-q4_predicate-mentat
pg-aggregate-s-c1-r3
pg-aggregate-s-c1-r3
configuration
Configuration (GUCs)
s-lbl.explain
s-lbl.explain
pg-ref_traversal-m-c1-r3
pg-ref_traversal-m-c1-r3
sizes
sizes
pg-concurrency_sweep-xl-c32-r2
pg-concurrency_sweep-xl-c32-r2
rum
BM25-style Ranked Search via rum
s-nk.timing
s-nk.timing
pg-aggregate-m-c1-r1
pg-aggregate-m-c1-r1
after-m1-s-q3
after-m1-s-q3
pg-predicate_scan-s-c1-r2
pg-predicate_scan-s-c1-r2
pg-as_of-xl-c1-r1
pg-as_of-xl-c1-r1
pg-input_bindings-m-c1-r3
pg-input_bindings-m-c1-r3
pg-since-s-c1-r2
pg-since-s-c1-r2
pg-wm_read-xl-c8-r2
pg-wm_read-xl-c8-r2
embedded-m-oom
embedded-m-oom
pg-ref_traversal-s-c1-r2
pg-ref_traversal-s-c1-r2
pg-point_lookup-m-c1-r1
pg-point_lookup-m-c1-r1
mino-rs-port
mino-rs: Rust port of the mino Clojure-dialect interpreter — Implementation Plan
pg-point_lookup-l-c1-r3
pg-point_lookup-l-c1-r3
pg-pull-xl-c1-r1
pg-pull-xl-c1-r1
final-m-q1
final-m-q1
2026-09-24-merge-pg-mentat
Merge pg_mentat into mentat — one repo, two storage backends[test] fn deep_value_is_an_error_not_a_crash() {[test][test] fn in_bindings() {
pg-point_lookup-xl-c1-r3
pg-point_lookup-xl-c1-r3
sql-functions
SQL Function Reference
300k-q3_aggregate-mentat
300k-q3_aggregate-mentat
pg-ref_traversal-m-c1-r1
pg-ref_traversal-m-c1-r1
pg-concurrency_sweep-xl-c32-r3
pg-concurrency_sweep-xl-c32-r3
pg-concurrency_sweep-m-c8-r2
pg-concurrency_sweep-m-c8-r2
getting-started
Getting Started
phase2
Phase 2 Benchmark Results
pg-wm_read-xl-c8-r1
pg-wm_read-xl-c8-r1
pg-concurrency_sweep-s-c32-r2
pg-concurrency_sweep-s-c32-r2
README
Development flow for Mentat FFI
pg-predicate_scan-m-c1-r2
pg-predicate_scan-m-c1-r2
pg-concurrency_sweep-xl-c1-r2
pg-concurrency_sweep-xl-c1-r2
pg-sustained-write-xl
pg-sustained-write-xl
pg-wm_write-l-c1-r1
pg-wm_write-l-c1-r1
before-190-m-q3
before-190-m-q3
pg-as_of-l-c1-r3
pg-as_of-l-c1-r3
pg-concurrency_sweep-m-c32-r2
pg-concurrency_sweep-m-c32-r2
pg-wm_read-m-c8-r2
pg-wm_read-m-c8-r2
summary
mentat scale benchmark: ext-cache
pg-concurrency_sweep-s-c32-r2
pg-concurrency_sweep-s-c32-r2
pg-predicate_scan-s-c1-r2
pg-predicate_scan-s-c1-r2

README

Mentat

Mentat is a Datomic-like database: an entity-attribute-value store with an immutable transaction log, a Datalog query language, and a declarative pull API, all expressed in EDN. One repository builds three backends that share the same Datalog/EDN front-end:

  • Embedded (mentat) — a Rust library store on SQLite. cargo build, no external services. Drop it into an application the way you would SQLite. Also a SQLite loadable extension (mentat_sqlite_ext) so any SQLite host can call mentat from SQL.
  • PostgreSQL extension (pg_mentat) — the same data model implemented inside PostgreSQL via pgrx, reached through SQL functions. Plus mentatd, an HTTP/WebSocket server that fronts a pg_mentat database.
  • DuckDB extension (mentat_duckdb) — loads into DuckDB (LOAD mentat;) and exposes the embedded store as SQL functions, so Datalog results join against native DuckDB tables.

All three expose the same four SQL functions:

Function Does PostgreSQL SQLite ext DuckDB
edn_t transact EDN, return a JSON tx-report edn_t(edn) edn_t(db_path, edn) edn_t(db_path, edn)
edn_q run a Datalog query edn_q(query, inputs jsonb) → JSONB edn_q(db_path, query, inputs) → JSON text edn_q(db_path, query, inputs) → rows (table function)
edn_pull pull a pattern for one entity, as JSON edn_pull(pattern, entity) edn_pull(db_path, pattern, entity) edn_pull(db_path, pattern, entity)
edn_eval run a sandboxed mino script edn_eval(script) (script feature) edn_eval(db_path, script) edn_eval(db_path, script)

inputs is the same JSON everywhere: {"inputs": [...]} binds the query’s :in forms positionally (scalars, [?x ...] collections, [?a ?b] tuples, [[?a ?b]] relations, in any mix), {"asOf": tx} / {"since": tx} query the database as of or since a transaction, and {} means none. The SQLite and DuckDB functions take the mentat store’s file path first; that store is separate from whatever database the host has open.

All backends parse queries, transactions, and schema with one copy of the edn, core-traits, and core crates, so a query means the same thing on every backend. They descend from Mozilla’s Project Mentat; the PostgreSQL backend was formerly the separate pg_mentat project, now merged here.

Datomic is a trademark of its owner; this project is an independent reimplementation of the model, not affiliated with or endorsed by Datomic.

New to EDN or Datalog?

Mentat speaks EDN (Clojure’s data notation) and answers questions in Datalog, the query language Datomic made popular. If you know nothing about EDN or Datalog, these are good starting points:

Documentation

The full manual (architecture, Datalog reference, pull API, time travel, the PostgreSQL cookbook, operations, and the scripting layer) is an mdBook under docs/. Build it with mdbook build docs and open docs/book/index.html, or read the sources in docs/src/.

  • Architecture — the front-end/backend split, the crate map, and which features live on which backend.
  • Scripting — the mino mentat.store/* surface and edn_eval, with the full security model.

Embedded store (mentat, on SQLite)

Build and test the embedded side — a plain cargo build needs no pg_config, libclang, or PostgreSQL:

cargo build            # builds the SQLite side (workspace default members)
cargo test --workspace # runs the embedded test suite

Add it to a Rust project:

[dependencies]
mentat = { git = "https://codeberg.org/gregburd/mentat" }

Open a store, define an attribute, transact a fact, and query it:

use mentat::{Store, QueryResults, Queryable};

fn main() -> mentat::Result<()> {
    // A file path, or "" for an in-memory store.
    let mut store = Store::open("example.db")?;

    // Define schema, then assert a fact. `transact` takes EDN text.
    store.transact(r#"[
        {:db/ident       :person/name
         :db/valueType   :db.type/string
         :db/cardinality :db.cardinality/one}
    ]"#)?;
    store.transact(r#"[{:person/name "Alice"}]"#)?;

    // Query. `q_once` runs a query with optional inputs and returns a QueryOutput.
    let results = store
        .q_once(r#"[:find ?name :where [?e :person/name ?name]]"#, None)?
        .into();
    if let QueryResults::Rel(rows) = results {
        for row in rows.into_iter() {
            println!("{:?}", row); // [Alice]
        }
    }
    Ok(())
}

The embedded side also ships a C ABI (crates/sqlite/ffi) for non-Rust hosts and a CLI (crates/sqlite/cli).


PostgreSQL extension (pg_mentat)

pg_mentat needs a PostgreSQL install with development headers plus LLVM/clang (pgrx uses bindgen). It supports PostgreSQL 13–18; pg16 is the default feature.

Build and install

With cargo pgrx (0.17):

cargo install --locked cargo-pgrx --version 0.17.0
cargo pgrx init --pg16 $(which pg_config)     # one-time, points pgrx at your PG

cd crates/pg/pg_mentat
cargo pgrx install --release --no-default-features --features pg16

Or with Nix (no pgrx toolchain to set up by hand):

nix build .#pg_mentat-pg16     # or .#pg_mentat-pg13 … .#pg_mentat-pg18

Use it

CREATE EXTENSION pg_mentat;

-- Define a schema attribute. edn_t takes EDN transaction text.
SELECT edn_t('[
  {:db/ident       :person/name
   :db/valueType   :db.type/string
   :db/cardinality :db.cardinality/one}
]');

-- Assert a fact.
SELECT edn_t('[{:person/name "Alice"}]');

-- Query. edn_q takes an EDN query and a JSONB inputs map; returns JSONB.
SELECT edn_q(
  '[:find ?name :where [?e :person/name ?name]]',
  '{}'::jsonb
);

-- Bind :in inputs, or query the database as of an earlier transaction.
SELECT edn_q('[:find ?e :in ?name :where [?e :person/name ?name]]',
             '{"inputs": ["Alice"]}');
SELECT edn_q('[:find ?name :where [?e :person/name ?name]]', '{"asOf": 268435457}');

-- Pull all attributes for an entity (entity id 10001 here).
SELECT edn_pull('[*]', 10001);

mentat.q, mentat.t, and mentat.pull are shorter aliases for edn_q, edn_t, and edn_pull. The pre-1.9.0 names mentat_transact, mentat_query, mentat_pull, and mentat_eval still work as deprecated aliases and will be removed in a future major release; ALTER EXTENSION pg_mentat UPDATE adds the new names to an existing 1.8.0 install. As of 1.8.0 the embedded SQLite backend also does historical (as-of/since) Datalog queries, ?added history patterns, and collection/tuple/relation :in bindings. The PostgreSQL backend adds features unique to it: LISTEN/NOTIFY reactive subscriptions and integrations with pgvector, pg_trgm, PostGIS, rum, and more. See Architecture for the full feature-by-backend table.

mentatd

crates/pg/mentatd is an HTTP/WebSocket server that talks to a pg_mentat database over tokio-postgres (cargo build -p mentatd; no PostgreSQL headers needed). See the mentatd chapter.


DuckDB extension (mentat_duckdb)

mentat_duckdb (crates/duckdb) is a loadable DuckDB extension that embeds the mentat SQLite store and exposes it to DuckDB as SQL functions, so Datalog results can be joined against native DuckDB tables. It is built with duckdb-rs and pinned to DuckDB v1.5.5 (via the DuckDB unstable C API); the extension loads only into that DuckDB version, and bumping DuckDB means bumping the pin and rebuilding.

Build

The extension is a cdylib that needs a metadata footer, so it is built with the DuckDB extension-ci-tools Makefiles (a git submodule under crates/duckdb/):

git submodule update --init crates/duckdb/extension-ci-tools
cd crates/duckdb
make configure          # one-time: sets up the build platform + test venv
make debug              # -> build/debug/mentat.duckdb_extension
# make release for an optimized build

(mentat_duckdb is a workspace member but not a default member, so a plain cargo build never pulls the DuckDB toolchain.)

Use it

DuckDB refuses unsigned extensions unless started with -unsigned (or opened with allow_unsigned_extensions=true). With a DuckDB v1.5.5 CLI:

-- duckdb -unsigned
LOAD './build/debug/mentat.duckdb_extension';

-- Define a schema attribute and assert facts into an embedded mentat store.
-- Every function takes the store's file path as its first argument.
SELECT edn_t('/tmp/demo.mentat', '[
  {:db/ident :person/name :db/valueType :db.type/string :db/cardinality :db.cardinality/one}
]');
SELECT edn_t('/tmp/demo.mentat', '[{:person/name "Alice"} {:person/name "Bob"}]');

-- edn_q is a table function: run Datalog and get rows back.
SELECT * FROM edn_q('/tmp/demo.mentat',
  '[:find ?e ?name :where [?e :person/name ?name]]', '{}');

-- ...so it joins against native DuckDB tables.
CREATE TABLE ages(name VARCHAR, age INT);
INSERT INTO ages VALUES ('Alice', 30), ('Bob', 25);
SELECT m.name, a.age
FROM edn_q('/tmp/demo.mentat',
       '[:find ?e ?name :where [?e :person/name ?name]]', '{}') AS m(e, name)
JOIN ages a ON a.name = m.name;

-- :in inputs and time travel use the same options JSON as PostgreSQL.
SELECT * FROM edn_q('/tmp/demo.mentat',
  '[:find ?e :in [?name ...] :where [?e :person/name ?name]]',
  '{"inputs": [["Alice", "Bob"]]}');
SELECT * FROM edn_q('/tmp/demo.mentat',
  '[:find ?name :where [?e :person/name ?name]]', '{"asOf": 268435458}');

-- Pull an entity as JSON, or run a sandboxed mino script against the store.
SELECT edn_pull('/tmp/demo.mentat', '[*]', 65537);
SELECT edn_eval('/tmp/demo.mentat',
  '(mentat.store/q (mentat.store/db (mentat.store/open))
                   (quote [:find (count ?e) . :where [?e :person/name]]))');

edn_t returns a JSON tx-report. edn_q returns rows with every column as VARCHAR (cast for arithmetic, e.g. e::BIGINT); strings come back as plain text, keywords keep their leading colon. edn_pull returns JSON keyed by attribute (":person/name", plus ":db/id"). edn_eval runs in the same sandbox as PostgreSQL’s (no filesystem access, step/heap/depth limits) and is on by default (--no-default-features drops it). A DuckDB-native storage backend and typed result columns are planned; see docs/duckdb-extension-plan.md. Publishing to the DuckDB Community Extensions registry is documented in docs/registry-publishing.md.


SQLite loadable extension (mentat_sqlite_ext)

crates/sqlite/ext builds libmentat_sqlite.so, a SQLite loadable extension with the same four functions. Any SQLite host can load it: the sqlite3 CLI, Python’s sqlite3, or an application that calls sqlite3_load_extension. It embeds its own copy of the mentat engine (with its own SQLite) and reaches the host only through the host’s API table, so it never shares a file handle with the host. For that reason db_path must not be the host’s own database file.

cargo build --release -p mentat_sqlite_ext   # -> target/release/libmentat_sqlite.so
-- sqlite3
.load ./target/release/libmentat_sqlite
SELECT edn_t('/tmp/demo.mentat', '[{:db/ident :person/name :db/valueType :db.type/string :db/cardinality :db.cardinality/one}]');
SELECT edn_t('/tmp/demo.mentat', '[{:person/name "Alice"} {:person/name "Bob"}]');

-- edn_q returns JSON, so json_each() turns it into rows you can join.
SELECT json_extract(r.value, '$[1]') AS name
FROM json_each(edn_q('/tmp/demo.mentat',
       '[:find ?e ?name :where [?e :person/name ?name]]', '{}'), '$.results') AS r;

The functions are registered SQLITE_DIRECTONLY, so they can’t be called from views or triggers in a database schema you don’t control. Host SQLite 3.30 or newer is required. Like the DuckDB extension, it is a workspace member but not a default member. See crates/sqlite/ext/README.md.


Benchmarks

benchmarks/scale/ is a repeatable scale and load suite that runs the same scenarios against all four deployments: the embedded library, the SQLite extension, pg_mentat and the DuckDB extension. The scenarios are bulk load, point lookup, ref traversal, aggregate, predicate scan, pull, as-of/since, :in bindings, mixed read/write, a 1-128 client concurrency sweep, sustained load, and cold vs warm. Every scenario checks its answer as well as its timing. benchmarks/scale/compare.py OLD NEW flags regressions between two runs. The latest full run (r6id.metal, 1 TiB RAM, shared_buffers at 85% of RAM, up to 303M datoms) is in benchmarks/results/scale-2026-09-27T010840Z/. See benchmarks/scale/README.md to run it.

The 1.10.0 before/after runs for each fix are in benchmarks/results/{pg-autoindex,embedded-fixes,ext-cache,duckdb-quack}-*.


Indexes: automatic

Mentat manages value indexes itself, on both engines. Out of the box (mode schema), every value is indexed for lookup on PostgreSQL (an AVET index per current-state table), and on the embedded store every :db/unique attribute and :db/index ref gets a value index, created when you declare the attribute and dropped when you remove the flag. In adaptive mode mentat also watches the queries you run: an attribute that is repeatedly filtered by value (embedded) or by a range over history (PostgreSQL) gets its own partial index, and an index mentat created that goes unused for a while is dropped again. Mentat only ever drops indexes it created and recorded in its registry, never ones you made.

Embedded (Rust / CLI) PostgreSQL
Choose the mode Store::set_auto_index(AutoIndex::Adaptive), MENTAT_AUTO_INDEX=adaptive, or .tune adaptive in the CLI SET mentat.auto_index = 'adaptive' (superuser)
See what it would do Store::tune_indexes(true), .tune SELECT * FROM mentat_tune_indexes() (dry run)
Apply now Store::tune_indexes(false), .tune! SELECT * FROM mentat_tune_indexes(false)
Registry mentat_managed_indexes table mentat.managed_indexes

Tuning also runs on its own every so often (after a number of queries on the embedded store, or of transactions on PostgreSQL). On PostgreSQL it gives up rather than wait for a lock. See docs/src/configuration.md for the thresholds and idle windows.


Scripting: mino

Every backend embeds mino, a Clojure-dialect interpreter (crates/mino), which exposes a mentat.store/* primitive surface for scripting transactions and queries. It is the SQL function edn_eval on all three: in pg_mentat behind the optional script cargo feature, and on by default in the SQLite and DuckDB extensions, where (mentat.store/open) with no argument opens the db_path you passed.

-- pg_mentat, built with --features script (off by default).
SELECT edn_eval($$
  (let [conn (mentat.store/open)]
    (mentat.store/transact conn [{:person/name "Bob"}])
    (mentat.store/q (mentat.store/db conn)
                    '[:find ?name :where [?e :person/name ?name]]))
$$);

Security — edn_eval

edn_eval is callable by every role by design — the extension issues no REVOKE, so PostgreSQL’s default grants EXECUTE to PUBLIC. That is safe because the interpreter is sandboxed and resource-limited:

  • Sandboxed. It is built with mino_rs::Interpreter::sandboxed(): the language, regex, bignum, atoms, and the in-memory mentat.store/* surface are present, but every host-filesystem primitive (slurp, spit, rm-rf, mkdir-p, file-exists?) and the file-backed store are absent (unbound). A script cannot touch the server’s filesystem.
  • Resource-limited. Three superuser-only (PGC_SUSET) GUCs bound each call, and an ordinary role cannot raise them for its own session:
    • mentat.script_max_steps — default 10,000,000
    • mentat.script_max_heap_bytes — default 64 MiB
    • mentat.script_max_depth — default 2000

    Exceeding any of them throws an :eval/limit error instead of hanging, exhausting memory, or overflowing the stack. statement_timeout still applies on top, via an interrupt check hook.

  • No privilege gain. A script runs through SPI as the calling role, so it reaches only the stores that role can already query with edn_q/edn_t. It must not be made SECURITY DEFINER — that would turn it into a privilege escalation.

The SQLite and DuckDB extensions build the same sandboxed interpreter with fixed limits (10M steps, 64 MiB heap, depth 1000). They run inside your own process, so the sandbox mainly stops a script from reaching the host filesystem; the script can read and write only the store at db_path.


License

Apache-2.0 for the workspace; the crates/mino interpreter keeps its MIT license. See LICENSE.