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.17.0

# 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.

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