Test environment

Fresh PostgreSQL 18 in Docker with everything installed on first start: the example schema and seed data (example/init.sql), bulk data for plan comparisons (bigdata.sql) and the self-installing generator (relation_sql.sql) — loading it creates the event trigger and all relation functions.

Bulk volumes: 100k clients + 1k managers, 70k profile details, 120k addresses, 20k items in a 3-level tree, 300k documents, 1.35M document lines. First start takes about a minute.

Start

docker compose up -d

Connect

psql postgresql://postgres:postgres@localhost:5440/postgres

Check

SELECT status, command FROM relation_sql();

Expected: event trigger: installed, relation functions: 16 ok, 0 to sync, 0 foreign, 0 duplicate.

Play

psql postgresql://postgres:postgres@localhost:5440/postgres -f ../example/query.sql

Or add a table and watch the functions appear on their own:

CREATE TABLE note (
  id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY
  , profile_id BIGINT NOT NULL REFERENCES profile (id)
  , body TEXT NOT NULL
);
SELECT p.name, n.body FROM profile p, note_list(p) n;

Dropping a table needs CASCADE — its relation functions depend on the row type; the event trigger cleans up whatever remains:

DROP TABLE note CASCADE;

Compare plans

EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF)
SELECT d.doc_number
FROM profile p, client_document_list(p) d
WHERE p.email = 'user50000@load.test'
;

Run each query twice and read the second, warm-cache run — the first one pays for cold buffers; TIMING OFF keeps totals free of instrumentation overhead. Same query with an explicit JOIN document d ON d.client_id = p.client_id produces a node-for-node identical plan; captured side-by-side pairs live in EXPLAIN.md.

Reset

docker compose down -v