Skip to content

Demo: a pgrust target that runs pgrx extensions inside a Rust Postgres backend - #2381

Draft
malisper wants to merge 2 commits into
pgcentralfoundation:developfrom
malisper:pgrust-target
Draft

malisper wants to merge 2 commits into
pgcentralfoundation:developfrom
malisper:pgrust-target

Conversation

@malisper

Copy link
Copy Markdown

Draft / RFC, not for merging as is. This branch shows what it takes to run unchanged pgrx extensions inside a Postgres backend that is not the C one: it adds a pgrust target feature beside pg13..pg19, where pgrust is an in-progress Rust reimplementation of the PostgreSQL backend (thread-per-backend, Result-based errors, explicit memory contexts, no C ABI). Ten extensions run from unchanged source: the pgrx-examples strings, arrays, srf, json, spi, aggregate, composite_type, plus pg_jsonschema, pg_graphql (introspection, queries, mutations) and a custom-GUC/_PG_init test extension.

The feature does not build in this repository on its own: pgrx-pg-sys's backend module is written over the host's crates, declared here as path dependencies that resolve only inside the pgrust tree (this repo is vendored there under crates/pgrx). Everything else, including CI with the feature off, is unaffected. The point of the PR is the code and the shape, and to ask whether a non-C target is something pgrx would want to carry.

What changes

Everything above the boundary layer is untouched: #[pg_extern], Spi, JsonB, Array, PgHeapTuple, aggregates, GUCs, the SQL entity graph. Below it, under #[cfg(feature = "pgrust")]:

  • pgrx-pg-sys/src/pgrust/ replaces the bindgen bindings. C-layout types are harvested from the PG18 bindings (types.rs, generated by harvest-bindings.py; on-datum layouts are identical in the host). Every function pgrx calls is hand-written over a host crate: memory (mem.rs), errors (error.rs), the C-shaped call frame built around the host's native frame (fmgr.rs), SQL-callable builtins with the C fn(FunctionCallInfo) -> Datum shape (bridge.rs), tuple descriptors and heap tuples (tuples.rs), custom GUCs (guc.rs), SRFs (srf.rs), and the registry (registry.rs).
  • Macros. finfo_v1_extern_c emits the host's native wrapper signature and a linkme registry entry instead of a #[no_mangle] symbol plus pg_finfo_*. __pgrx_schema_entry! also adds each entity payload to a registry slice, so the host generates the extension SQL at startup with PgrxSql::build().to_sql() (no object file to read a section from). pg_module_magic! emits an extension descriptor and an init_seams() instead of Pg_magic_func. #[pg_guard] fn _PG_init joins a slice the host runs on first load.
  • Runtime arms. CurrentMemoryContext reads go through one accessor (a per-thread cell on the host); SetOfIterator/TableIterator materialize instead of value-per-call; Spi gets a second implementation (pgrx/src/spi_pgrust/) over the host's SPI handles; GucSetting::get() reads the host's store; the thread check and the sigsetjmp FFI boundary are compiled out; modules that need C-shaped backend state (bgworkers, shmem, lwlock, hooks, triggers, rel, nodes, list, fn_call, syscache) are gated out for now.
  • Errors. A host shim that gets Err(PgError) panics with a payload that downcast_panic_payload maps to CaughtError::PostgresError (original error kept), so PgTryBuilder works unchanged; the wrapper's single catch_unwind turns any payload back into the host's Err.

docs/pgrust/pgrx-target.md has the full contract, costs, and gaps. pgrust-e2e/ is the SQL suite the ten extensions were verified with.

Not in this branch

The host-side changes (an embedded-extension registry so CREATE EXTENSION reads the generated script from the crate, runtime custom-GUC definition), the ten extension crates, and the harness that builds and links them. Not done anywhere yet: enum GUCs and GUC hooks, range types, background workers, shared memory, hooks, triggers, cargo pgrx test.

Questions for maintainers

  1. Is a target that is not a Postgres major version something pgrx is willing to carry as a feature, given it stays cfg-isolated?
  2. The cleanest split would be a host-abstraction crate that pgrx depends on and hosts implement; this branch takes the direct route (host crates as deps of pgrx-pg-sys) to show the surface first. Which shape would you want?

🤖 Generated with Claude Code

malisper and others added 2 commits September 20, 2026 10:03
…kend

Draft/RFC. A `pgrust` feature beside pg13..pg19 that replaces the C-ABI
boundary layer (bindgen bindings, sigsetjmp barrier, CurrentMemoryContext
global, dlopen/dlsym) with a backend module written over pgrust's crates,
while everything above it (#[pg_extern], Spi, datum conversions, SRFs,
aggregates, composite types, GUCs, the SQL entity graph) stays unchanged.

Ten extensions run from unchanged source under the target: the examples
strings, arrays, srf, json, spi, aggregate, composite_type, plus
pg_jsonschema, pg_graphql and a GUC/_PG_init test extension. The feature's
host path deps resolve only inside the pgrust tree; CI with the feature
off is unaffected. See docs/pgrust/pgrx-target.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@malisper malisper changed the title RFC: a pgrust target that runs pgrx extensions inside a Rust Postgres backend Demo: a pgrust target that runs pgrx extensions inside a Rust Postgres backend Sep 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant