GitHub - pgcentralfoundation/pgrx: Build Postgres Extensions with Rust! (original) (raw)

Logo

pgrx

Build Postgres Extensions with Rust!

GitHub Actions badge crates.io badge docs.rs badge Twitter Follow Discord Chat

pgrx is a framework for developing PostgreSQL extensions in Rust and strives to be as idiomatic and safe as possible.

pgrx supports Postgres 13 through Postgres 17.

Want to chat with us or get a question answered?

Key Features

System Requirements

PGRX has been tested to work on x86_64⹋ and aarch64⹋ Linux and aarch64 macOS and x86_64⹋ Windows targets. It is currently expected to work on other "Unix" OS with possible small changes, but those remain untested.

† PGRX has no MSRV policy, thus may require the latest stable version of Rust, available via Rustup

‡ A local PostgreSQL server installation is not required. On Linux and MacOS, cargo pgrx can download and compile PostgreSQL versions on its own. On Windows, cargo pgrx downloads precompiled PostgreSQL versions from EnterpriseDB.

⹋ PGRX has not been tested to work on 32-bit, but the library attempts to handle conversion of pg_sys::Datumto and from int8 and double types. Use it only for your own risk. We do not plan to add official support without considerable ongoing technical and financial contributions.

macOS

Running PGRX on a Mac requires some additional setup.

The Mac C compiler (clang) and related tools are bundled with XCode. XCode can be installed from the Mac App Store.

For additional C libraries, it's easiest to use Homebrew. In particular, you will probably need these if you don't have them already:

brew install git icu4c pkg-config

The config script that Postgres 17 uses in its build process does not automatically detect the Homebrew install directory. (Earlier versions of Postgres do not have this problem.) You may see this error:

configure: error: ICU library not found

To fix it, run

export PKG_CONFIG_PATH=/opt/homebrew/opt/icu4c/lib/pkgconfig

on the command line before you run cargo pgrx init

Every once in a while, XCode will update itself and move the directory that contains the C compiler. When the Postgres ./config process runs during the build, it grabs the current directory and stores it, which means that there will be build errors if you do a full rebuild of your project and the old directory has disappeared. The solution is re-run cargo pgrx init so the Postgres installs get rebuilt.

Windows

Running PGRX on Windows requires MSVC prerequisites.

On Windows, please follow https://rust-lang.github.io/rustup/installation/windows-msvc.html to set up it.

Getting Started

Before anything else, install the system dependencies.

Now install the cargo-pgrx sub-command.

cargo install --locked cargo-pgrx

Once cargo-pgrx is ready you can initialize the "PGRX Home" directory:

The init command downloads all currently supported PostgreSQL versions, compiles them to ${PGRX_HOME}, and runs initdb.

It's also possible to use an existing (user-writable) PostgreSQL install, or install a subset of versions, see the README.md of cargo-pgrx for details.

Now you can begin work on a specific pgrx extension:

cargo pgrx new my_extension cd my_extension

This will create a new directory for the extension crate.

$ tree 
.
├── Cargo.toml
├── my_extension.control
├── sql
└── src
    └── lib.rs

2 directories, 3 files

The new extension includes an example, so you can go ahead and run it right away.

This compiles the extension to a shared library, copies it to the specified Postgres installation, starts that Postgres instance and connects you to a database named the same as the extension.

Once cargo-pgrx drops us into psql we can load the extension and do a SELECT on the example function.

my_extension=# CREATE EXTENSION my_extension; CREATE EXTENSION

my_extension=# SELECT hello_my_extension(); hello_my_extension

Hello, my_extension (1 row)

For more details on how to manage pgrx extensions see Managing pgrx extensions.

Cross-compiling

So far, cross-compiling extensions with pgrx has only been demonstrated under nix. Proper support in nixpkgs is still in flux, so watch this space.

In order to cross-compile outside of nix, you will need two ingredients:

  1. A sysroot for the cross architecture, with rust configured to invoke the linker with flags to link against this sysroot.
  2. A pg_config program that provides values associated with postgres compiled for the desired cross architecture. This can be achieved by either emulating pg_config with qemu-user or similar, or replicating pg_config with a shell script.

Then, modify the standard build process in the following ways:

  1. Pass --no-run to cargo pgrx init, since we cannot run postgres for the cross machine.
  2. Pass one of the --pgXX flags to cargo pgrx init with the aforementioned pg_config.
  3. Pass the --pg-config flag to cargo pgrx package with the aforementioned pg_config.
  4. Pass the --target flag to cargo pgrx package with the rust triple of the cross machine.

cargo pgrx run will not not work.

Upgrading

As new Postgres versions are supported by pgrx, you can re-run the pgrx init process to download and compile them:

Mapping of Postgres types to Rust

Postgres Type Rust Type (as Option)
bytea Vec or &[u8] (zero-copy)
text String or &str (zero-copy)
varchar String or &str (zero-copy) or char
"char" i8
smallint i16
integer i32
bigint i64
oid u32
real f32
double precision f64
bool bool
json pgrx::Json(serde_json::Value)
jsonb pgrx::JsonB(serde_json::Value)
date pgrx::Date
time pgrx::Time
timestamp pgrx::Timestamp
time with time zone pgrx::TimeWithTimeZone
timestamp with time zone pgrx::TimestampWithTimeZone
anyarray pgrx::AnyArray
anyelement pgrx::AnyElement
box pgrx::pg_sys::BOX
point pgrx::pg_sys::Point
tid pgrx::pg_sys::ItemPointerData
cstring &core::ffi::CStr
inet pgrx::Inet(String) -- TODO: needs better support
numeric pgrx::Numeric<P, S> or pgrx::AnyNumeric
void ()
ARRAY[]:: Vec<Option> or pgrx::Array (zero-copy)
int4range pgrx::Range
int8range pgrx::Range
numrange pgrx::Range<Numeric<P, S>> or pgrx::Range
daterange pgrx::Rangepgrx::Date
tsrange pgrx::Rangepgrx::Timestamp
tstzrange pgrx::Rangepgrx::TimestampWithTimeZone
NULL Option::None
internal pgrx::PgBox where T is any Rust/Postgres struct
uuid pgrx::Uuid([u8; 16])

There are also IntoDatum and FromDatum traits for implementing additional type conversions, along with #[derive(PostgresType)] and #[derive(PostgresEnum)] for automatic conversion of custom types.

Note that text and varchar are converted to &str or String, so PGRX assumes any Postgres database you use it with has UTF-8-compatible encoding. Currently, PGRX will panic if it detects this is incorrect, to inform you, the programmer, that you were wrong. However, it is best to not rely on this behavior, as UTF-8 validation can be a performance hazard. This problem was previously assumed to simply not happen, and PGRX may decide to change the details of how it does UTF-8 validation checks in the future in order to mitigate performance hazards.

The default Postgres server encoding is SQL_ASCII, and it guarantees neither ASCII nor UTF-8 (as Postgres will then accept but ignore non-ASCII bytes). For best results, always use PGRX with UTF-8, and set database encodings explicitly upon database creation.

Digging Deeper

Caveats & Known Issues

There's probably more than are listed here, but a primary things of note are:

TODO

There's a few things on our immediate TODO list

Feature Flags

PGRX has optional feature flags for Rust code that do not involve configuring the version of Postgres used, but rather extend additional support for other kinds of Rust code. These are not included by default.

"unsafe-postgres": Allow compilation for Postgres forks that have a different ABI

As of Postgres 15, forks are allowed to specify they use a different ABI than canonical Postgres. Since pgrx makes countless assumptions about Postgres' internal ABI it is not possible for it to guarantee that a compiled pgrx extension will probably execute within such a Postgres fork. You, dear compiler runner, can make this guarantee for yourself by specifying the unsafe-postgresfeature flag. Otherwise, a pgrx extension will fail to compile with an error similar to:

error[E0080]: evaluation of constant value failed
   --> pgrx/src/lib.rs:151:5
    |
151 | /     assert!(
152 | |         same_slice(pg_sys::FMGR_ABI_EXTRA, b"xPostgreSQL\0"),
153 | |         "Unsupported Postgres ABI. Perhaps you need `--features unsafe-postgres`?",
154 | |     );
    | |_____^ the evaluated program panicked at 'Unsupported Postgres ABI. Perhaps you need `--features unsafe-postgres`?', pgrx/src/lib.rs:151:5
    |

Contributing

We are most definitely open to contributions of any kind. Bug Reports, Feature Requests, and Documentation.

If you'd like to contribute code via a Pull Request, please make it against our develop branch. The master branch is no longer used.

Providing wrappers for Postgres' internals is not a straightforward task, and completely wrapping it is going to take quite a bit of time. pgrx is generally ready for use now, and it will continue to be developed as time goes on. Your feedback about what you'd like to be able to do with pgrx is greatly appreciated.

Hacking

If you're hacking on pgrx and want to ensure your test will run correctly, you need to have the current implementation of cargo-pgrx (from the revision you're working on) in your PATH.

An easy way would be to install cargo-local-install:

cargo install cargo-local-install

and then run cargo local-install to install cargo-pgrx as specified in top-level's Cargo.toml.

Don't forget to prepend /path/to/pgrx/bin to your PATH!

This approach can also be used in extensions to ensure a matching version of cargo-pgrx is used.

License

Portions Copyright 2019-2021 ZomboDB, LLC.  
Portions Copyright 2021-2023 Technology Concepts & Design, Inc.
Portions Copyright 2023 PgCentral Foundation, Inc.

All rights reserved.
Use of this source code is governed by the MIT license that can be found in the LICENSE file.