Welcome to pgrx v0.20.0-beta.0, the first beta of v0.20.0. This is a big one.
pgrx now generates Rust versions of Postgres' C macros and static inline functions during every build, and the hand-written ports are gone. We also fixed a long list of safe APIs that could cause undefined behavior, dropped Postgres 13 and 14, moved to Postgres 19rc1, and now require Rust 1.99.1. cargo-pgrx can assemble upgrade scripts from SQL fragments and build RPM and DEB packages.
Many of these changes will break existing code, some of them in behavior rather than at compile time. Please read the v0.20.0 Migration Guide before upgrading.
Install with:
$ rustup update stable
$ cargo install cargo-pgrx --version 0.20.0-beta.0 --lockedAnd update your extension's dependencies with cargo pgrx upgrade --to =0.20.0-beta.0. Your existing cargo pgrx init configuration keeps working. To trade a downloaded Postgres 19 beta for 19rc1, run cargo pgrx init --pg19=download.
Builds now need a clang whose major version matches libclang, and should have llvm-ar from the same LLVM release. On Debian-likes that's apt install clang llvm libclang-dev. See Build requirements.
This is a beta. Please try it with your extensions and tell us what breaks before 0.20.0 ships.
Generated C Macros and Static Inline Functions
- Transpile PostgreSQL C macros into Rust macro_rules! by @eeeebbbbrrrr in #2389
- Shrink the C macro documentation snapshot and name generated support items by @eeeebbbbrrrr in #2391
- Translate PostgreSQL static inline function bodies into Rust by @eeeebbbbrrrr in #2392
- Replace handwritten PostgreSQL macro ports with generated macros by @eeeebbbbrrrr in #2390
- Run translated static inline functions under their
pg_sysnames by @eeeebbbbrrrr in #2441
This is the headline item, and it arrived on develop through #2405.
pgrx has always carried hand-written Rust ports of the Postgres macros and static inline functions it needed. They covered a small part of the headers, and they drifted from the C as Postgres changed from version to version.
The new pgrx-c-macros crate runs during every pgrx-pg-sys build and translates Postgres' function-like C macros into documented Rust macro_rules!. The translations keep C's arithmetic, integer promotions, casts and evaluation order, so they compute what the C computes for the headers you build against. You call them by their Postgres names and extract the result at the Rust boundary:
// Before (v0.19)
let len = pgrx::varsize_any(ptr);
check_for_interrupts!();
// After (v0.20)
let len = unsafe { pgrx::VARSIZE_ANY!(ptr).get() as usize };
pgrx::check_for_interrupts();A macro that can't be translated faithfully is skipped, and OUT_DIR/pgNN_macro_report.json says why.
Static inline functions get the same treatment. When the translator supports every statement in a function's body, the body becomes Rust, and pg_sys::NAME(...) runs that translation with the binding's ordinary signature. Otherwise pg_sys::NAME is still the guarded C binding. On PG16 and later, that means hot conversions such as Int32GetDatum no longer pay for a sigsetjmp and a thread check on every call.
The hand-written ports are removed, including pgrx::varsize_any, check_for_interrupts!, pg_sys::heap_tuple_get_struct and the pgrx::srf_* helpers. The C macro guide lists each one with its replacement. A generated macro that reads storage or backend state needs an unsafe block, even where the helper it replaces was safe.
Follow-up PRs made the generated macros nicer to use from Rust:
- Add a safe pgrx::check_for_interrupts() by @eeeebbbbrrrr in #2433
- Extract size_t-typed C macro results as usize by @eeeebbbbrrrr in #2426
- Add CExpression::get_as for checked integer extraction by @eeeebbbbrrrr in #2437
- Convert unsigned int and Datum macro results to Oid, TransactionId and Datum by @eeeebbbbrrrr in #2438
- Move the Oid, TransactionId and Datum macro-result conversions beside their types by @eeeebbbbrrrr in #2442
- Convert binding char pointers to plain-char Pointer with From by @eeeebbbbrrrr in #2439
- Add const fn alignment helpers to pg_sys by @eeeebbbbrrrr in #2434
- Add PgNode::is_a_ptr and PgNode::try_cast_ptr for raw node pointers by @eeeebbbbrrrr in #2432
- Give the tree walkers and mutators one signature on every version by @eeeebbbbrrrr in #2435
- Keep PostgreSQL's Oid, TransactionId and MultiXactId constants typed by @eeeebbbbrrrr in #2428
In short: .get_usize() and .get_as::<T>() replace most .get() as ... casts, pg_sys::Oid::from(...) and friends convert macro results, and pg_sys::maxalign and the rest of pg_sys::align work in const contexts. pg_sys::expression_tree_walker and the other walkers and mutators no longer need a cfg split between PG15 and PG16+. pg_sys::InvalidOid and the transaction ID constants are still Oid and TransactionId, so code written for 0.19 that uses them compiles unchanged.
Fixes along the way
- Generate C macros correctly with clang 19 and later by @eeeebbbbrrrr in #2422
- Keep the native call for inline bodies that need a pointer cast by @eeeebbbbrrrr in #2447
- Replace petgraph in pgrx-c-macros with an in-tree graph by @eeeebbbbrrrr in #2411
#2422 fixes builds with clang 21 and glibc 2.43, as on Ubuntu 26.04, where TIMESTAMP_NOT_FINITE! and related items were missing. #2447 fixes the Windows build after #2441. The PG18-on-Windows fix is in #2415, below.
Soundness Fixes
- Fix pgrx APIs that let safe code cause undefined behavior by @eeeebbbbrrrr in #2400
Several pgrx APIs let safe code read freed, misaligned or mistyped Postgres memory. Fixing them changed signatures and behavior. The migration guide has a section for each, but the highlights are:
- Copies instead of borrows.
PgVarlena<T>now holds its value in Rust. APgBox<T>read from an array element or tuple attribute is a copy inCurrentMemoryContext.PgTupleDescreturnsFormData_pg_attributeby value and no longer dereferences toPgBox<TupleDescData>.PgRelation::name()andnamespace()returnString, andPgRelationno longer dereferences toRelationData. - New
unsafe fns.AnyArray::into,heap_getattr,heap_getattr_datum_ex,composite_row_type_make_tuple,DatumWithTypeInfo::into_value, and the<type>_recvfunction generated for#[pg_binary_protocol]types depend on their input being right, so they are nowunsafe. The newAnyArray::value::<T>()andAnyElement::value::<T>()check the type and return aResult. - Borrowed
PostgresTypevalues. A#[derive(PostgresType)]type with a lifetime now borrows from the Datum it was read from.Vec<Part<'_>>arguments no longer compile; useArray<'_, Part<'_>>. fn_callchecks argument types against the called function's parameters and returnsFnCallError::IncompatibleArgumentTypeinstead of crashing the backend.FnCallErroris now#[non_exhaustive].rust_regtypeinfinds an extension's own types throughpg_dependinstead ofsearch_path, so an extension's own type namedPointno longer resolves topg_catalog.point.PgVarlena::newrequiresT: Default. Use theunsafePgVarlena::new_zeroedwhere all-zero bytes are a validT.- Text array elements are checked. Reading
textarray elements as&strorStringnow checks their encoding asFromDatumfor&strdoes, and panics instead of producing an invalidstr.
Postgres Versions
- Drop PostgreSQL 13 and 14 support by @eeeebbbbrrrr in #2387
- Bump PostgreSQL 19 support to rc1 by @eeeebbbbrrrr in #2420
pgrx now supports Postgres 15 through 19. Remove the pg13 and pg14 features from your Cargo.toml and change default if it names one of them. New extensions default to pg15.
- Fix building for PostgreSQL 19 on Windows (MSVC) by @mstaros in #2388
- Type the PG19 BUFFER_LOCK_* re-exports as BufferLockMode::Type by @eeeebbbbrrrr in #2410
- Check PG19's LockBuffer and index lock constants in every build by @eeeebbbbrrrr in #2412
#2388 fixes building for PG19 on Windows with MSVC. The BUFFER_LOCK_* constants have the type LockBuffer takes, which is u32 on Linux and macOS and i32 on MSVC. Code that builds for several versions should pass them as pg_sys::BUFFER_LOCK_SHARE as _.
Rust 1.99.1
- Require Rust 1.99.1 by @eeeebbbbrrrr in #2444
Not 1.99.0, which can miscompile an extern "C" function that returns bool on x86_64 (rust-lang/rust#163911). pgrx hands Postgres plenty of those, such as GUC check hooks.
cargo-pgrx
SQL Migration Fragments
- Add cargo pgrx migrate for SQL migration fragments by @eeeebbbbrrrr in #2407
- Make SQL migration fragments opt-in, fix release assembly, and document them by @eeeebbbbrrrr in #2406
- Warn that temporary fragment versions differ from the crate version by @eeeebbbbrrrr in #2431
An extension can now keep its upgrade SQL as one small file per change in sql/unreleased/ instead of one hand-edited script per release. That helps when several pull requests change the schema at once, or when fixes get backported. Opt in from Cargo.toml:
[package.metadata.pgrx]
unreleased-sql-dir = "sql/unreleased"cargo pgrx install, run, test, regress and bench then assemble the fragments into a temporary upgrade script so ALTER EXTENSION ... UPDATE works during development. cargo pgrx migrate assemble writes the real versioned script at release time, and cargo pgrx migrate check lints fragments for dependency cycles and undeclared dependencies. Extensions that don't opt in see no change.
This started as @stuhood's work in #2380. The new Upgrading Extensions chapter of the book and the migration_fragments example show how to use it.
RPM and DEB Packages
- Build RPM and DEB packages with cargo pgrx package by @daamien in #2324
- Resolve relative package sources against the crate directory by @eeeebbbbrrrr in #2429
- Add
cargo pgrx package --no-distrib-packagesby @eeeebbbbrrrr in #2430
On Linux, cargo pgrx package now builds .rpm and .deb files itself. Add an empty [package.metadata.rpm] or [package.metadata.deb] section to Cargo.toml and you get a package laid out like the PGDG ones, filled in from your crate's name, version, description, license and authors. Any key you set in the section overrides the default. --no-distrib-packages skips them when you only want the staged files.
If you already have a [package.metadata.deb] section for cargo deb, it uses different keys and can now make cargo pgrx package fail. See Existing cargo deb metadata.
cargo pgrx regress
- Version-Specific Regress Tests by @daamien in #2402
- Accept several test names in
cargo pgrx regressby @eeeebbbbrrrr in #2423 - Match only
setup.sqlas thecargo pgrx regresssetup file by @eeeebbbbrrrr in #2424
A regression test can now say which Postgres versions it runs on with a comment line such as -- pgrx-regress-skip pg15 pg16 or -- pgrx-regress-only pg17 pg18. On other versions it's reported as skipped and needs no expected output.
cargo pgrx regress also takes several test names now, and exits with status 1 when a name matches no test. Only a file named exactly setup.sql is the setup file, so a test like post_setup.sql now runs as an ordinary test.
Other cargo-pgrx Changes
cargo pgrx upgrade without --to failed on Cargo 1.85 and newer. It works again.
- feat: accept any pgrx patch release within cargo-pgrx's minor series by @Vinnu124 in #2419
- Let a prerelease cargo-pgrx accept matching pgrx prereleases by @eeeebbbbrrrr in #2443
cargo-pgrx 0.20.x accepts any pgrx 0.20.x, so you no longer need to upgrade the CLI for every pgrx patch release. A prerelease cargo-pgrx accepts the same and later prereleases, as well as the stable release.
New Features
- Add LWLocks for dynamic shared memory and parallel foreign scans by @eeeebbbbrrrr in #2409
pgrx::lwlock::dsm::DsmLwLockTranche and pgrx::lwlock::scan::ParallelScanLwLock provide LWLocks that live in dynamic shared memory, which a parallel foreign scan needs and PgLwLock can't provide. The new parallel_scan_lwlock example implements a parallel FDW with them. This is @fkomauli's work from #2395.
- re-export postgres Client from test framework by @eeeebbbbrrrr in #2393
#[pg_test] tests can now open their own connections back to Postgres through pgrx_tests::postgres, which makes multi-connection tests possible.
- Bind utils/timeout.h by @eeeebbbbrrrr in #2445
- Accept arrays whose element type the element reader accepts by @eeeebbbbrrrr in #2436
Bugfixes
- Fix aggregate deserial function signature by @eeeebbbbrrrr in #2354
Any aggregate that implemented Aggregate::deserial failed CREATE AGGREGATE, so no pgrx aggregate with an internal state could run in parallel. deserial now takes the serialized bytes and returns Self::State.
- Treat a not-null NULL pointer as uninitialized in Internal by @eeeebbbbrrrr in #2414
- Document liveness and aliasing requirements for Internal's unsafe methods by @eeeebbbbrrrr in #2416
Dependencies
With #2411, petgraph and its Zlib-licensed foldhash dependency are gone from every pgrx extension's dependency tree. Note that the generated schema can now list statements in a different order. If an extension_sql! block names an object from elsewhere in your extension, make sure it lists that object in requires, or CREATE EXTENSION can fail. See Order of the generated schema.
Documentation
- Update docs for v0.20.0 and rename migration guides by @eeeebbbbrrrr in #2427
- Fix broken link on the docs landing page and typos by @toyeshhm in #2397
- Document that GucSetting::get panics off the active thread by @rawsun007 in #2394
- Fix rustdoc warnings by @eeeebbbbrrrr in #2448
Project Administrativa
- Merge 0.20.0-develop by @eeeebbbbrrrr in #2405
- Speed up CI and fix PG18 C macros on Windows by @eeeebbbbrrrr in #2415
- Clarify AGENTS.md rules for PR descriptions and formatting by @eeeebbbbrrrr in #2417
- Require maintainer push access before agents open PRs by @eeeebbbbrrrr in #2421
- Mark wal_decoder unpublishable and fix the publish.sh dependency graph by @eeeebbbbrrrr in #2446
New Contributors
- @toyeshhm made their first contribution in #2397
- @rawsun007 made their first contribution in #2394
- @mstaros made their first contribution in #2388
- @Vinnu124 made their first contribution in #2419
Thanks also to @stuhood and @fkomauli, whose work became the SQL fragment and DSM LWLock features, and to @daamien, @isdaniel and @cbandy. Your work is appreciated!
Full Changelog: v0.19.3...v0.20.0-beta.0