Development
Toolchain
| Tool | Version |
|---|---|
| Rust | stable, 1.96 or newer (rust-version in Cargo.toml; rust-toolchain.toml selects stable with rustfmt and clippy); edition 2024 |
| pgrx | =0.19.2 |
| cargo-pgrx | 0.19.2, exactly matching the pgrx pin (cargo install cargo-pgrx --version 0.19.2 --locked) |
| PostgreSQL | 18 (cargo feature pg18) |
just | the command runner for every recipe below |
| Docker | for the builder image and the local server; Podman also works |
The recipes are defined in the Justfile; just --list prints them.
Fast loop: pgrx-managed PostgreSQL
cargo pgrx run pg18 builds the extension, installs it into a PostgreSQL 18 managed by pgrx, and opens psql. Edit Rust, quit psql, run it again.
just pgrx-init # once: cargo pgrx init --pg18 download
just dev # cargo pgrx run pg18
just test-native # cargo pgrx test pg18Use this loop for iterating on code and for the unit and #[pg_test] suites. It does not reproduce the deployment: pgrx's server uses its own file locations and build flags, and it does not preload pgRDF, so the shared term cache and the staged loader are off unless you add shared_preload_libraries = 'pgrdf' to that server's postgresql.conf. Host builds can fail to link on some platforms (macOS in particular); the container loop below always works.
Container loop: a stock postgres:18 server
This loop builds the Linux .so in a builder container and mounts the result, file by file, into an unmodified postgres:18-trixie server. It is the same setup CI uses. Details are in compose/README.md.
just build-ext # build pgrdf.so + SQL in the builder image (Docker)
PGRDF_RUN_RUNTIME=docker just compose-up # start PostgreSQL 18 with the build mounted in
PGRDF_RUN_RUNTIME=docker just psql # psql as pgrdf/pgrdf on database pgrdfCREATE EXTENSION pgrdf;
SELECT pgrdf.version(), pgrdf.build_id();just build-extusescompose/builder.Containerfile(based onrust:1.96-trixie, with cargo-pgrx 0.19.2 and PostgreSQL 18) and exports the build tocompose/extensions/. The first build needs about 5 GB for the builder image.- The compose recipes (
compose-up,compose-down,compose-logs,psql,smoke) run on Podman by default; setPGRDF_RUN_RUNTIME=dockerto use Docker.just runtimesprints the current choice. - The server runs with
shared_preload_libraries=pgrdf. A small check container runs first and refuses to start the stack if the mounted control file andpgrdf--<version>.sqldo not match. - The repository's
fixtures/directory is mounted read-only at/fixtures, sopgrdf.load_turtle('/fixtures/…', …)works. - A local build names itself in
build_id()withgit describe --tags --always --dirty, so a workstation build is never mistaken for a release build.
just smoke # build, start, CREATE EXTENSION, print the version
just test # pgrx integration tests inside the builder image
just test-artifact-parity # prove the mounted files are a fresh build of this tree
just smoke-cold # tear down, rebuild, start, then run every compose harnessStyle gates
Both are enforced in CI:
just fmt # cargo fmt --all (CI runs cargo fmt --all -- --check)
just clippy # cargo clippy --no-default-features --features pg18 -- -D warningsRun clippy with the pinned toolchain. An older host compiler fails the minimum-version check before it reaches your code.
Module-level documentation lives in //! comments at the top of each source file; there are no per-module README files.
Adding a SQL function
- Write it. Add a
#[pg_extern]function in the right module undersrc/. Follow its neighbours: annotate it with#[search_path(pgrdf, pg_temp)]so the caller'ssearch_pathcannot redirect name resolution inside it, and mark itstrictif a NULL argument should return NULL. - Classify it. Add a row to
src/surface_manifest.tsvwith its class (stable,internal,spikeordeprecated). A#[pg_test]fails on any exported function missing from the manifest, and on any manifest row with no function. - Respect custody. Any function that writes to a graph calls
crate::storage::lock::require_unlocked(graph_id, "<name>")before writing. - Refuse with a code. Raise deliberate refusals through
crate::refuse(PgSqlErrorCode::…, message), choosing a semantic SQLSTATE (22023for a bad argument,42704for an unknown graph,55P03for a lock,0A000for an unsupported construct, …). Make the message name what was refused and how to fix it. - Test it. Add
#[pg_test]s beside the code, and a regression file undertests/regression/sql/with a hand-computed expected output. See Testing. - Ship the upgrade path. pgrx generates the install script (
pgrdf--<version>.sql) at package time, but existing databases reach the new version only throughALTER EXTENSION pgrdf UPDATE. Add the function'sCREATE FUNCTION(and any DDL it needs) to the upgrade scriptsql/pgrdf--<previous>--<next>.sql.
Changing the version
The version appears in several places that must agree with each other and with the release tag: Cargo.toml (and the pgrdf entry in Cargo.lock), default_version in pgrdf.control, both version fields in META.json, the pgrdf--<version>.sql mount line in compose/compose.yml, and the expected output of 00-smoke.sql. CI boots the compose stack on every push, so a stale mount line fails long before a release; the release workflow checks the rest. See Release.