Contents
- SQL Function Reference
- Function Naming Convention
- Transaction Functions
- Query Functions
- mentat.q(query, inputs) / edn_q(query, inputs)
- edn_q_rows(query, inputs DEFAULT '{}') – streamed rows
- mentat_tune_indexes(dry_run DEFAULT true) – automatic indexes
- mentat.explain(query) / mentat_explain(query)
- mentat_query_sql(query) — Generated SQL
- mentat_query_view(name, query) — Create SQL VIEW from Datalog
- Pull Functions
- Schema Functions
- Store Management
- Time Travel Functions
- Excision Functions
- Subscription Functions
- Materialized View Functions
- Statistics & Monitoring
- EDN Helper Functions
- Bootstrap Functions
SQL Function Reference
All pg_mentat functions live in the mentat schema. After CREATE EXTENSION pg_mentat, they are accessible as mentat.function_name().
Function Naming Convention
pg_mentat provides two sets of function names:
Convenience aliases (recommended for everyday use in the default mentat schema):
SELECT mentat.t('[{:person/name "Alice"}]');
SELECT mentat.q('[:find ?e :where [?e :person/name "Alice"]]');
SELECT mentat.pull('[*]', 10001);
Core functions use the cross-backend edn_* names shared with the SQLite
and DuckDB extensions (since 1.9.0):
SELECT edn_t('[{:person/name "Alice"}]');
SELECT edn_q('[:find ?e :where [?e :person/name "Alice"]]', '{}');
SELECT edn_pull('[*]', 10001);
SELECT edn_eval('(+ 1 2)'); -- only in builds with the `script` feature
Deprecated names. Before 1.9.0 these were mentat_transact, mentat_query,
mentat_pull and mentat_eval. The old names still work: they are thin SQL
wrappers over the edn_* functions (COMMENTed as deprecated) and will be
removed in a future major release. Other functions keep their mentat_ prefix.
Quick Reference
| Convenience alias | Full function | Description |
|---|---|---|
mentat.t(edn) |
edn_t(edn) |
Transact EDN data |
mentat.q(query, inputs) |
edn_q(query, inputs) |
Run a Datalog query |
| – | edn_q_rows(query, inputs) |
Run a query, one JSONB row per result |
mentat.pull(pattern, eid) |
edn_pull(pattern, eid) |
Pull entity attributes |
mentat.pull_many(pattern, eids) |
mentat_pull_many(pattern, eids) |
Pull multiple entities |
mentat.entity(eid) |
mentat_entity(eid) |
All attributes as JSON |
mentat.schema() |
mentat_schema() |
Current schema |
mentat.explain(query) |
mentat_explain(query) |
Show generated SQL |
mentat.stats() |
mentat_query_stats() |
Execution statistics |
mentat.storage() |
mentat_storage_stats() |
Storage statistics |
mentat.cache_stats() |
mentat_stmt_cache_stats() |
Statement cache info |
mentat.cache_clear() |
mentat_stmt_cache_clear() |
Clear statement cache |
Transaction Functions
mentat.t(edn) / edn_t(edn)
Execute a transaction. Returns a JSON transaction report with tx_id, tx_instant, and tempids.
SELECT mentat.t('[
{:db/id "tempid-1"
:person/name "Alice"
:person/age 30}
]');
The t alias transacts against the default store. Use the full function with a store argument for named stores:
SELECT mentat.mentat_transact_store('analytics', '[{:event/type "click"}]');
mentat_with(edn) — Speculative Transaction
Execute a transaction without persisting it. Returns the same report format, but writes nothing. Useful for validation or “what-if” analysis.
SELECT mentat.mentat_with('[
{:person/name "Test" :person/age 99}
]');
Query Functions
mentat.q(query, inputs) / edn_q(query, inputs)
Execute a Datalog query. Returns JSONB with columns and results.
-- Simple query
SELECT mentat.q('
[:find ?name ?age
:where [?e :person/name ?name]
[?e :person/age ?age]
[(> ?age 21)]]
');
-- With input bindings (positional, matching :in clause order)
SELECT mentat.q('
[:find ?name
:in $ ?min-age
:where [?e :person/name ?name]
[?e :person/age ?age]
[(>= ?age ?min-age)]]
', '[25]');
The inputs parameter is a JSON value:
- Simple array for positional bindings: '[25]'
- Empty for no inputs: '{}' or '[]'
edn_q_rows(query, inputs DEFAULT '{}') – streamed rows
RETURNS SETOF JSONB: one JSON array per result row, in :find order,
with the same value encoding and the same inputs (:in bindings,
asOf / since / history, limit / offset) as edn_q. Use it when
the result is large – edn_q returns one JSONB value, which PostgreSQL
caps at 1 GB and which must be built in memory before the first row
reaches the client – or when you want to join / filter / aggregate the
rows in SQL:
SELECT r->>0 AS name, (r->>1)::int AS age
FROM edn_q_rows('[:find ?n ?a :where [?e :person/name ?n] [?e :person/age ?a]]') r
WHERE (r->>1)::int > 21;
Rows are read from a cursor in batches of 1000. mentat.max_result_rows
applies exactly as for edn_q (more rows than the limit is an error, not
a silent truncation), so both functions give the same answer for the
same settings; SET LOCAL mentat.max_result_rows = 0 for an unbounded
stream. Default store only (like edn_q).
mentat_tune_indexes(dry_run DEFAULT true) – automatic indexes
Report (dry run) or apply the automatic index manager’s creates / drops:
RETURNS TABLE(action, index_name, table_name, reason). See
Operations: automatic index management.
mentat.explain(query) / mentat_explain(query)
Show the generated SQL and PostgreSQL’s EXPLAIN output without executing the query.
SELECT mentat.explain('[:find ?name :where [?e :person/name ?name]]');
mentat_query_sql(query) — Generated SQL
Return only the generated SQL string (no execution, no EXPLAIN).
SELECT mentat.mentat_query_sql('[:find ?name :where [?e :person/name ?name]]');
mentat_query_view(name, query) — Create SQL VIEW from Datalog
Create a PostgreSQL VIEW backed by a Datalog query:
SELECT mentat.mentat_query_view('people_over_30', '
[:find ?name ?age ?email
:where [?e :person/name ?name]
[?e :person/age ?age]
[?e :person/email ?email]
[(> ?age 30)]]
');
-- Now use it like any SQL view
SELECT * FROM mentat.people_over_30 WHERE name LIKE 'A%';
Pull Functions
mentat.pull(pattern, eid) / edn_pull(pattern, eid)
Pull attributes for a single entity. Returns a nested JSON document.
-- Pull everything
SELECT mentat.pull('[*]', 10001);
-- Pull specific attributes with nested refs
SELECT mentat.pull('[
:person/name
:person/age
{:person/friends [:person/name :person/age]}
]', 10001);
-- Reverse lookup: who has this entity as a friend?
SELECT mentat.pull('[:person/name :person/_friends]', 10001);
-- With modifiers
SELECT mentat.pull('[
:person/name
{(:person/friends :limit 5 :as :top-friends) [:person/name]}
{(:person/_friends :as :admirers) [:person/name]}
]', 10001);
mentat.pull_many(pattern, eids) / mentat_pull_many(pattern, eids)
Pull the same pattern for multiple entities. Returns a JSON array.
SELECT mentat.pull_many('[:person/name :person/age]', ARRAY[10001, 10002, 10003]);
mentat.entity(eid) / mentat_entity(eid)
Return all current attributes for an entity as a flat JSON map (equivalent to pull('[*]', eid)).
SELECT mentat.entity(10001);
Schema Functions
mentat.schema() / mentat_schema()
Return the full schema as JSON, keyed by attribute ident.
SELECT mentat.schema();
Store Management
mentat.create_store(name, description)
Create a new isolated store with its own schema, tables, and indexes.
SELECT mentat.create_store('analytics', 'Event tracking store');
mentat.drop_store(name)
Drop a store and all its data (irreversible).
mentat.list_stores()
List all stores with metadata.
mentat.rename_store(old_name, new_name)
Rename an existing store.
Time Travel Functions
mentat.log(store, from_tx, to_tx)
Return the transaction log for a range of transactions.
SELECT mentat.log('default', 1000001, 1000010);
mentat.diff(store, from_tx, to_tx)
Compute the diff between two points in time — what was added and retracted.
SELECT mentat.diff('default', 1000003, 1000007);
Time-travel via query parameters
Pass as_of_tx or since_tx to query functions:
-- Query the database as of transaction 1000005
SELECT mentat.q('
[:find ?name :where [?e :person/name ?name]]
', '[]', 1000005, NULL);
Excision Functions
mentat_excise(store, entity_id, attribute)
Permanently remove datoms from the database, including all history. This is the only operation that truly deletes data (GDPR compliance).
-- Remove all data for an entity
SELECT mentat.mentat_excise('default', 10042, NULL);
-- Remove only a specific attribute
SELECT mentat.mentat_excise('default', 10042, ':person/email');
Subscription Functions
mentat.subscribe(store, name, query)
Subscribe to changes matching a Datalog query pattern. Uses PostgreSQL LISTEN/NOTIFY.
SELECT mentat.subscribe('default', 'new_people',
'[:find ?e :where [?e :person/name]]');
-- In another session:
LISTEN mentat_subscription_new_people;
mentat.unsubscribe(store, name)
Remove a subscription.
Materialized View Functions
mentat.materialize(store, name, query)
Create a materialized view from a Datalog query for faster repeated access.
SELECT mentat.materialize('default', 'active_users',
'[:find ?e ?name :where [?e :person/name ?name] [?e :person/active true]]');
mentat.refresh(store, name)
Refresh a materialized view with current data.
Statistics & Monitoring
mentat.stats() / mentat_query_stats()
Query execution statistics: call counts, timing, cache hit rates.
mentat.storage() / mentat_storage_stats()
Storage statistics: row counts, table sizes, index sizes.
mentat.cache_stats() / mentat_stmt_cache_stats()
Prepared statement cache statistics.
mentat.cache_clear() / mentat_stmt_cache_clear()
Clear the statement cache.
mentat_health_check()
Extension health check (returns JSON with status, version, store count).
mentat_slow_queries(threshold_ms)
Return recently logged slow queries exceeding the given threshold.
EDN Helper Functions
These operate on EDN-formatted text values and are installed in the public schema for convenience.
| Function | Description |
|---|---|
edn_get(edn, key) |
Extract a value from an EDN map |
edn_nth(edn, index) |
Extract Nth element from an EDN vector |
edn_count(edn) |
Count elements in an EDN collection |
edn_keys(edn) |
Keys of an EDN map as a vector |
edn_values(edn) |
Values of an EDN map as a vector |
edn_contains(edn, key) |
Check if a map contains a key |
edn_type(edn) |
Type of an EDN value |
edn_pretty(edn, width) |
Pretty-print EDN with indentation |
Bootstrap Functions
mentat.bootstrap_schema()
Re-run the bootstrap schema installation. Called automatically during CREATE EXTENSION but can be invoked to repair a corrupted schema.