pgx
is a framework for developing PostgreSQL extensions in Rust and strives to be as idiomatic and safe as possible.
pgx
supports Postgres v10-v14.
Feel free to join our Discord Server.
- A cargo sub-command (
cargo-pgx
) for managing thepgx
development environment- Quickly create a new extension template crate via
cargo pgx new
- Install, configure, compile, and privately install all required Postgres versions via
cargo pgx init
- Run your extension and interactively test with
psql
viacargo pgx run
- Unit-test your extension across multiple Postgres versions via
cargo pgx test
- Create installation packages for your extension via
cargo pgx package
- Quickly create a new extension template crate via
- Support Postgres v10-v14 from the same codebase
- Postgres Rust bindings are organized into
pgXX.rs
modules
- Postgres Rust bindings are organized into
- Use Rust feature gating to use version-specific APIs
- Seamlessly test against all versions
- Generates DDL for common SQL objects such as
- Functions
- Types
- Enums
- Hand-written SQL is supported through the
extension_sql!
&extension_sql_file!
macros - Control the order in which SQL is executed during
CREATE EXTENSION ...;
- Translates Rust
panic!
s into PostgresERROR
s that abort the transaction, not the process - Memory Management follows Rust's drop semantics, even in the face of
panic!
andelog(ERROR)
#[pg_guard]
procedural macro to ensure the above- Postgres
Datum
is simplyOption<T> where T: FromDatum
--NULL
Datums are safely represented asOption::None
#[pg_test]
proc-macro for unit testing in-process within Postgres
- Annotate functions with
#[pg_extern]
to expose them to Postgres - Return
impl std::iter::Iterator<Item = T> where T: IntoDatum
for automatic set-returning-functions (bothRETURNS SETOF
andRETURNS TABLE (...)
variants - DDL automatically generated
Postgres Type | Rust Type (as Option<T> ) |
---|---|
bytea |
Vec<u8> 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 |
pgx::Json(serde_json::Value) |
jsonb |
pgx::Json(serde_json::Value) |
date |
pgx::Date |
time |
pgx::Time |
timestamp |
pgx::Timestamp |
time with time zone |
pgx::TimeWithTimeZone |
timestamp with time zone |
pgx::TimestampWithTimeZone |
anyarray |
pgx::AnyArray |
anyelement |
pgx::AnyElement |
box |
pgx::pg_sys::BOX |
point |
pgx::pgx_sys::Point |
tid |
pgx::pg_sys::ItemPointerData |
cstring |
&std::ffi::CStr |
inet |
pgx::Inet(String) -- TODO: needs better support |
numeric |
pgx::Numeric(String) -- TODO: needs better support |
void |
() |
ARRAY[]::<type> |
Vec<Option<T>> or pgx::Array<T> (zero-copy) |
NULL |
Option::None |
internal |
pgx::PgBox<T> where T is any Rust/Postgres struct |
uuid |
pgx::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.
#[derive(PostgresType)]
to use a Rust struct as a Postgres type, represented as a CBOR-encoded object in-memory/on-disk, and JSON as human-readable- can provide custom implementations for custom in-memory/on-disk/human-readable representations
#[derive(PostgresEnum)]
to use a Rust enum as a Postgres enum- DDL automatically generated
- Safe access into SPI
- Transparently return owned Datums from an SPI context
- Safe access to Postgres'
MemoryContext
system viapgx::PgMemoryContexts
- Executor/planner/transaction/subtransaction hooks
- Safely use Postgres-provided pointers with
pgx::PgBox<T>
(akin toalloc::boxed::Box<T>
) #[pg_guard]
proc-macro for guardingextern "C"
Rust functions that need to be passed into Postgres- Access Postgres' logging system through
eprintln!
-like macros - Direct
unsafe
access to large parts of Postgres internals via thepgx::pg_sys
module - lots more!
rustc
(minimum version 1.52) andcargo
cargo install rustfmt
git
libclang.so
- Ubuntu:
libclang-dev
orclang
- RHEL:
clang
- Ubuntu:
- build dependencies for PostgreSQL
Note that a local Postgres installation is not required. pgx
will download and compile Postgres itself.
First you'll want to install the pgx
cargo sub-command from crates.io. You'll use it almost exclusively during
your development and testing workflow.
$ cargo install cargo-pgx
Next, pgx
needs to be initialized. You only need to do this once.
$ cargo pgx init
The init
command downloads Postgres versions v10, v11, v12, v13, compiles them to ~/.pgx/
, and runs initdb
.
These installations are needed by pgx
not only for auto-generating Rust bindings from each version's header files,
but also for pgx
's test framework.
See the documentation for cargo-pgx
for details on how to limit the required postgres versions.
$ cargo pgx new my_extension
$ cd my_extension
This will create a new directory for the extension crate.
my_extension/
├── Cargo.toml
├── my_extension.control
├── sql
│ ├── lib.generated.sql
│ └── load-order.txt
└── src
└── lib.rs
The new extension includes an example, so you can go ahead and run it right away.
$ cargo pgx run pg13 # or pg10 or pg11 or pg12
This compiles the extension to a shared library, copies it to the specified Postgres installation (in ~/.pgx/
),
starts that Postgres instance and connects you, via psql
, to a database named for the extension.
The first time, compilation takes a few minutes as pgx
needs to generate almost 200k lines of Rust "bindings" from
Postgres' header files.
Once compiled you'll be placed in a psql
shell, for, in this case, Postgres 13.
Now, 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)
You can upgrade your current cargo-pgx
installation by passing the --force
flag
to cargo install
:
$ cargo install --force cargo-pgx
As new Postgres versions are supported by pgx
, you can re-run the pgx init
process to download and compile them:
$ cargo pgx init
- cargo-pgx sub-command
- Custom Types
- Postgres Operator Functions and Operator Classes/Families
- Shared Memory Support
- various examples
There's probably more than are listed here, but a primary things of note are:
-
Threading is not really supported. Postgres is strictly single-threaded. As such, if you do venture into using threads, those threads MUST NOT call any internal Postgres function, or otherwise use any Postgres-provided pointer. There's also a potential problem with Postgres' use of
sigprocmask
. This was being discussed on the -hackers list, even with a patch provided, but the conversation seems to have stalled (https://www.postgresql.org/message-id/flat/5EF20168.2040508%40anastigmatix.net#4533edb74194d30adfa04a6a2ce635ba). -
async
interactions are unknown right now. -
pgx
uses lots ofunsafe
Rust. That's generally the nature of the beast when doing FFI wrappers, so be aware. -
Not all of Postgres' internals are included or even wrapped. This isn't due to it not being possible, it's simply due to it being an incredibly large task. If you identify internal Postgres APIs you need, open an issue and we'll get them exposed, at least through the
pgx::pg_sys
module. -
Windows is not supported. It could be, but will require a bit of work with
cargo-pgx
and figuring out how to compilepgx
's "cshim" static library. -
Sessions started before
ALTER EXTENSION my_extension UPDATE;
will continue to see the old version ofmy_extension
. New sessions will see the updated version of the extension.
There's a few things on our immediate TODO list
- Better trigger function support.
pgx
does support creating trigger functions in Rust (need examples!) but it doesn't automatically generate any of the DDL for them. This too likely needs a procmaro like#[pg_trigger]
- Automatic extension schema upgrade scripts, based on diffs from a previous git tag and HEAD. Likely, this
will be built into the
cargo-pgx
subcommand and make use of https://github.com/zombodb/postgres-parser. - More examples -- especially around memory management and the various derive macros
#[derive(PostgresType/Enum)]
We are most definitely open to contributions of any kind. Bug Reports, Feature Requests, Documentation, and even sponsorships.
If you'd like to contribute code via a Pull Request, please make it against our develop
branch. The master
branch is meant to represent what is currently available on crates.io.
Providing wrappers for Postgres' internals is not a straightforward task, and completely wrapping it is going
to take quite a bit of time. pgx
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 pgx
is greatly appreciated.
Copyright 2020 ZomboDB, LLC <[email protected]>. All rights reserved.
Use of this source code is governed by the MIT license that can be found in the LICENSE file.