Getting Started

Installation

From Source

Prerequisites: - Rust 1.88+ (install via rustup) - PostgreSQL 15-17 with development headers - LLVM/Clang development libraries (for pgrx bindgen) - pkg-config, build-essential (or equivalent)

On Debian/Ubuntu:

sudo apt-get install -y postgresql-16 postgresql-server-dev-16 \
    libpq-dev build-essential pkg-config libclang-dev clang

On Fedora/RHEL:

sudo dnf install -y postgresql16-server postgresql16-devel \
    clang-devel llvm-devel pkg-config

On macOS (Homebrew):

brew install postgresql@16 llvm pkg-config

Then build and install:

git clone https://github.com/gburd/pg_mentat.git
cd pg_mentat

# Install cargo-pgrx (the PostgreSQL extension build tool)
cargo install --locked cargo-pgrx --version 0.19.3

# Tell pgrx where your PostgreSQL is installed
cargo pgrx init --pg16 $(which pg_config)

# Build and install the extension into your PostgreSQL
cd pg_mentat
cargo pgrx install --release --no-default-features --features pg16

Docker

docker build -t pg_mentat .
docker run -d --name pg_mentat \
  -e POSTGRES_PASSWORD=secret \
  -p 5432:5432 \
  pg_mentat

Or use Docker Compose for the full stack (pg_mentat + mentatd + Prometheus + Grafana):

docker compose -f docker/docker-compose.yml up -d

From Source (Nix)

If you use Nix, the provided flake handles all dependencies:

git clone https://github.com/gburd/pg_mentat.git
cd pg_mentat
nix develop

# Everything is pre-configured — just build
cd pg_mentat
cargo pgrx install --release --no-default-features --features pg16

Quick Start

1. Create the Extension

CREATE EXTENSION pg_mentat;

This creates the mentat schema with all required tables, types, indexes, and functions. A superuser or (since 1.17.0) any role with CREATE on the database can run it; see multi-tenancy for the privileges a non-superuser installer gets and how to grant them to others.

2. Define Schema Attributes

Schema attributes describe the shape of your data. Every attribute needs an ident (namespaced keyword), a value type, and a cardinality.

SELECT mentat.t('[
  {:db/ident       :person/name
   :db/valueType   :db.type/string
   :db/cardinality :db.cardinality/one
   :db/unique      :db.unique/identity}

  {:db/ident       :person/age
   :db/valueType   :db.type/long
   :db/cardinality :db.cardinality/one}

  {:db/ident       :person/email
   :db/valueType   :db.type/string
   :db/cardinality :db.cardinality/many}

  {:db/ident       :person/friends
   :db/valueType   :db.type/ref
   :db/cardinality :db.cardinality/many}
]');

3. Transact Data

Use tempids (string identifiers) to create new entities. References between entities in the same transaction resolve automatically.

SELECT mentat.t('[
  {:db/id "alice"
   :person/name "Alice"
   :person/age 30
   :person/email "alice@example.com"}

  {:db/id "bob"
   :person/name "Bob"
   :person/age 25
   :person/friends "alice"}
]');

The return value is a JSON report containing the transaction ID, timestamp, and tempid resolution map.

4. Query with Datalog

-- Find all people over 25
SELECT mentat.q('
  [:find ?name ?age
   :where [?e :person/name ?name]
          [?e :person/age ?age]
          [(> ?age 25)]]
');

-- Find friends-of-friends (with input binding)
SELECT mentat.q('
  [:find ?friend-name
   :in $ ?name
   :where [?e :person/name ?name]
          [?e :person/friends ?f]
          [?f :person/name ?friend-name]]
', '["Alice"]');

5. Pull Entity Data

-- Pull all attributes for entity 10001
SELECT mentat.pull('[*]', 10001);

-- Pull specific attributes with nested navigation
SELECT mentat.pull(
  '[:person/name :person/age {:person/friends [:person/name]}]',
  10001
);

6. Time Travel

-- Query the database as it was at transaction 1000
SELECT mentat.q('[:find ?name :where [?e :person/name ?name]]', '[]', 1000, NULL);

-- View transaction log
SELECT mentat.log('default', 1000, 1010);

-- What changed between two transactions?
SELECT mentat.diff('default', 1000, 1005);

Next Steps