Skip to content

Development

Toolchain

ToolVersion
Ruststable, 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-pgrx0.19.2, exactly matching the pgrx pin (cargo install cargo-pgrx --version 0.19.2 --locked)
PostgreSQL18 (cargo feature pg18)
justthe command runner for every recipe below
Dockerfor 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.

sh
just pgrx-init      # once: cargo pgrx init --pg18 download
just dev            # cargo pgrx run pg18
just test-native    # cargo pgrx test pg18

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

sh
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 pgrdf
sql
CREATE EXTENSION pgrdf;
SELECT pgrdf.version(), pgrdf.build_id();
  • just build-ext uses compose/builder.Containerfile (based on rust:1.96-trixie, with cargo-pgrx 0.19.2 and PostgreSQL 18) and exports the build to compose/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; set PGRDF_RUN_RUNTIME=docker to use Docker. just runtimes prints 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 and pgrdf--<version>.sql do not match.
  • The repository's fixtures/ directory is mounted read-only at /fixtures, so pgrdf.load_turtle('/fixtures/…', …) works.
  • A local build names itself in build_id() with git describe --tags --always --dirty, so a workstation build is never mistaken for a release build.
sh
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 harness

Style gates

Both are enforced in CI:

sh
just fmt       # cargo fmt --all            (CI runs cargo fmt --all -- --check)
just clippy    # cargo clippy --no-default-features --features pg18 -- -D warnings

Run 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

  1. Write it. Add a #[pg_extern] function in the right module under src/. Follow its neighbours: annotate it with #[search_path(pgrdf, pg_temp)] so the caller's search_path cannot redirect name resolution inside it, and mark it strict if a NULL argument should return NULL.
  2. Classify it. Add a row to src/surface_manifest.tsv with its class (stable, internal, spike or deprecated). A #[pg_test] fails on any exported function missing from the manifest, and on any manifest row with no function.
  3. Respect custody. Any function that writes to a graph calls crate::storage::lock::require_unlocked(graph_id, "<name>") before writing.
  4. Refuse with a code. Raise deliberate refusals through crate::refuse(PgSqlErrorCode::…, message), choosing a semantic SQLSTATE (22023 for a bad argument, 42704 for an unknown graph, 55P03 for a lock, 0A000 for an unsupported construct, …). Make the message name what was refused and how to fix it.
  5. Test it. Add #[pg_test]s beside the code, and a regression file under tests/regression/sql/ with a hand-computed expected output. See Testing.
  6. Ship the upgrade path. pgrx generates the install script (pgrdf--<version>.sql) at package time, but existing databases reach the new version only through ALTER EXTENSION pgrdf UPDATE. Add the function's CREATE FUNCTION (and any DDL it needs) to the upgrade script sql/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.

pgRDF is released under the MIT license. Documentation built with VitePress, served via GitHub Pages.