Skip to content

Conversation

LucaCappelletti94
Copy link
Contributor

@LucaCappelletti94 LucaCappelletti94 commented May 13, 2025

This PR generally addresses issues such as #2060, #1446, #1364, and pksunkara/pgx_ulid#27, and supersedes PR #887.

  • Introduces the generation of the SEND and RECEIVE bindings.
  • Introduces the generation of the *_recv and *_send functions in rust, based on the existing serde_cbor-based methods cbor_encode and cbor_decode
  • Introduces the #[pg_binary_protocol] decorator to be used alongside the PostgresType derive

This PR is not API breaking, as even if people have created functions with naming clashes they would still need to add the attribute on the interested structs.

Many thanks to @YohDeadfall and @zommiommy for their help in understanding better pgrx internals and postgres's binary protocol.

The generated Rust & SQL

Given a custom rust type such as:

#[derive(serde::Serialize, serde::Deserialize, pgrx::PostgresType)]
#[pg_binary_protocol]
pub struct PositiveU32 {
    pub field: i32,
}

The resulting generated rust bindings look like:

#[doc(hidden)]
#[::pgrx::pgrx_macros::pg_extern(immutable, strict, parallel_safe)]
pub fn positiveu32_recv(
    internal: ::pgrx::datum::Internal,
) -> PositiveU32 {
    let buf = unsafe { internal.get_mut::<::pgrx::pg_sys::StringInfoData>().unwrap() };

    let mut serialized = ::pgrx::StringInfo::new();

    serialized.push_bytes(&[0u8; ::pgrx::pg_sys::VARHDRSZ]);
    serialized.push_bytes(unsafe {
        core::slice::from_raw_parts(
            buf.data as *const u8,
            buf.len as usize
        )
    });

    let size = serialized.len();
    let varlena = serialized.into_char_ptr();

    unsafe{
        ::pgrx::set_varsize_4b(varlena as *mut ::pgrx::pg_sys::varlena, size as i32);
        buf.cursor = buf.len;
        ::pgrx::datum::cbor_decode(varlena as *mut ::pgrx::pg_sys::varlena)
    }
}
#[doc(hidden)]
#[::pgrx::pgrx_macros::pg_extern(immutable, strict, parallel_safe)]
pub fn positiveu32_send(input: PositiveU32) -> Vec<u8> {
    use ::pgrx::datum::{FromDatum, IntoDatum};
    let Some(datum): Option<::pgrx::pg_sys::Datum> = input.into_datum() else {
        ::pgrx::error!("Datum of type `{}` is unexpectedly NULL.", stringify!(#name));
    };
    unsafe {
        let Some(serialized): Option<Vec<u8>> = FromDatum::from_datum(datum, false) else {
            ::pgrx::error!("Failed to CBOR-serialize Datum to type `{}`.", stringify!(#name));
        };
        serialized
    }
}

And the associated generated SQL looks like:

/* <begin connected objects> */
/*
This file is auto generated by pgrx.

The ordering of items is not stable, it is driven by a dependency graph.
*/
/* </end connected objects> */

/* <begin connected objects> */
-- utils/diesel-pgrx/example_extension/src/lib.rs:16
-- example_extension::PositiveU32
CREATE TYPE PositiveU32;

-- utils/diesel-pgrx/example_extension/src/lib.rs:16
-- example_extension::positiveu32_in
CREATE  FUNCTION "positiveu32_in"(
	"input" cstring /* core::option::Option<&core::ffi::c_str::CStr> */
) RETURNS PositiveU32 /* core::option::Option<example_extension::PositiveU32> */
IMMUTABLE PARALLEL SAFE
LANGUAGE c /* Rust */
AS 'MODULE_PATHNAME', 'positiveu32_in_wrapper';

-- utils/diesel-pgrx/example_extension/src/lib.rs:16
-- example_extension::positiveu32_out
CREATE  FUNCTION "positiveu32_out"(
	"input" PositiveU32 /* example_extension::PositiveU32 */
) RETURNS cstring /* alloc::ffi::c_str::CString */
IMMUTABLE STRICT PARALLEL SAFE
LANGUAGE c /* Rust */
AS 'MODULE_PATHNAME', 'positiveu32_out_wrapper';

-- utils/diesel-pgrx/example_extension/src/lib.rs:16
-- example_extension::positiveu32_recv
CREATE  FUNCTION "positiveu32_recv"(
	"internal" internal /* pgrx::datum::internal::Internal */
) RETURNS PositiveU32 /* example_extension::PositiveU32 */
IMMUTABLE STRICT PARALLEL SAFE
LANGUAGE c /* Rust */
AS 'MODULE_PATHNAME', 'positiveu32_recv_wrapper';

-- utils/diesel-pgrx/example_extension/src/lib.rs:16
-- example_extension::positiveu32_send
CREATE  FUNCTION "positiveu32_send"(
	"input" PositiveU32 /* example_extension::PositiveU32 */
) RETURNS bytea /* alloc::vec::Vec<u8> */
IMMUTABLE STRICT PARALLEL SAFE
LANGUAGE c /* Rust */
AS 'MODULE_PATHNAME', 'positiveu32_send_wrapper';


-- utils/diesel-pgrx/example_extension/src/lib.rs:16
-- example_extension::PositiveU32
CREATE TYPE PositiveU32 (
	INTERNALLENGTH = variable,
	INPUT = positiveu32_in, /* example_extension::positiveu32_in */
	OUTPUT = positiveu32_out, /* example_extension::positiveu32_out */
	RECEIVE = positiveu32_recv, /* example_extension::positiveu32_recv */
	SEND = positiveu32_send, /* example_extension::positiveu32_send */
	STORAGE = extended
);
/* </end connected objects> */

On Diesel traits

I use diesel extensively in my projects, and it uses the binary protocol to send data to PostgreSQL. This PR makes it possible to use Postgres's binary protocol with pgrx type, so I can now implement diesel's ToSql and FromSql traits as follows:

#[derive(
    Debug, diesel::FromSqlRow, diesel::AsExpression,
    serde::Serialize, serde::Deserialize, pgrx::PostgresType
)]
#[pg_binary_protocol]
#[diesel(sql_type = MyCustomTypeDiesel)]
pub struct MyCustomType {
    pub field1: String,
    pub field2: i32,
}

#[derive(
    Debug, Clone, Copy, Default, diesel::query_builder::QueryId, diesel::sql_types::SqlType,
)]
#[diesel(postgres_type(name = "my_custom_type"))]
pub struct MyCustomTypeDiesel;

impl diesel::serialize::ToSql<MyCustomTypeDiesel, diesel::pg::Pg> for MyCustomType
{
    fn to_sql<'b>(&'b self, out: &mut diesel::serialize::Output<'b, '_, diesel::pg::Pg>) -> diesel::serialize::Result {
        use std::io::Write;
        serde_cbor::to_writer(out, self)?;
        Ok(diesel::serialize::IsNull::No)
    }
}

impl diesel::deserialize::FromSql<MyCustomTypeDiesel, diesel::pg::Pg> for MyCustomType
{
    fn from_sql(raw: diesel::pg::PgValue<'_>) -> diesel::deserialize::Result<Self> {
        Ok(serde_cbor::from_slice(raw.as_bytes())?)
    }
}

P.S. Sorry for the very small commits, but I could only figure out how to compile pgrx inside a Docker and therefore to test it in there I had to push every single small test edit.

P.P.S I have also tried to add support for fixed-size types, but there is too much varlena-specific methods at this time and the amount of code it would be needed for that makes it necessarily a separate PR

@LucaCappelletti94 LucaCappelletti94 marked this pull request as ready for review May 16, 2025 20:30
@LucaCappelletti94 LucaCappelletti94 changed the title Working on Binary protocol Added pg_binary_protocol attribute to derive send and receive functions for PostgresType May 16, 2025
@eeeebbbbrrrr
Copy link
Contributor

@LucaCappelletti94 this is on my radar -- I just don't have time to dedicate to a proper review at the moment. I hope to get to it this week.

@LucaCappelletti94
Copy link
Contributor Author

No worries, let me know if there is anything I can do to facilitate the review process

@charmitro
Copy link
Contributor

FWIW, this overall looks ok to me, in terms of what it does. I tested it on one of my projects that has custom types.

@LucaCappelletti94
Copy link
Contributor Author

I will likely be doing an extended version afterwards, first trying to tackle the fixed size types as described in my issue #2076

@LucaCappelletti94
Copy link
Contributor Author

Please do let me know whether there is anything I can do to facilitate the review of this PR - I want to build the fixed-size type derive on top of this PR, and I would like to make sure that first this one is considered a-okay

Copy link
Contributor

@eeeebbbbrrrr eeeebbbbrrrr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great and well thought out and implemented.

Thanks for your patience and most especially thanks for your work.

@eeeebbbbrrrr eeeebbbbrrrr merged commit c662ecd into pgcentralfoundation:develop May 31, 2025
30 checks passed
eeeebbbbrrrr added a commit that referenced this pull request Jun 28, 2025
Welcome to pgrx v0.15.0. This begins a new series for pgrx that includes
support for Postgres 18. As of this release, that means Postgres
18beta1.

This release does contain a few breaking API changes but they're largely
mechanical. Don't worry, the compiler will let you know!

As always, please install our CI tool with `cargo install cargo-pgrx
--version 0.15.0 --locked` and then run `cargo pgrx upgrade` in all of
your extension crates.

If you want to start working with Postgres 18beta1, you'll also need to
re-init your pgrx environment with `cargo pgrx init`. That will
automatically detect all the latest Postgres versions, including
18beta1.

At the top here, I'd like to thank @silver-ymz for the 18beta1 support.
It was a pleasant surprise to see that work come from the community --
it's no easy task to add a new Postgres version to pgrx!

That said, as Postgres 18 is currently beta, you should consider pgrx'
support for it as beta too. Please report any problems with 18beta1 (or
discrepancies with other versions) as GitHub issues.

Also, this release requires rust v1.88.0 or greater. `if-let` chains are
now a thing and we're not afraid to use them.

# What's Changed

## Postgres 18beta1 Support

* Support Postgres 18beta1 by @silver-ymz in
#2056
* pg18 support: add header and implement `#define` by @eeeebbbbrrrr in
#2094
* improve pg_magic_func by @usamoi in
#2088


## More Headers

* Added `catalog/heap.h` binding by @ccleve in
#2072
* include `utils/pg_status.h` by @eeeebbbbrrrr in
#2091


## `cargo-pgrx` improvements

* Pass `LLVM_*` variables to `--runas` command by @theory in
#2083
* `does_db_exist()`: fix `psql` argument order by @eeeebbbbrrrr in
#2093
* `cargo pgrx regress` output is no longer fully buffered by
@eeeebbbbrrrr in #2095
* Detect `pgrx_embed` name from lib name by @YohDeadfall in
#2035
* Fixed error message if no artifact found by @YohDeadfall in
#2034
* `cargo-pgrx`: use system certificate store for HTTPS validation by
@charmitro in #2074
* Decoding command output in Windows by @if0ne in
#2084


## Breaking Changes

* fix GUC by @usamoi in
#2064
* refactor GUC by @usamoi in
#2066

## New Stuff

* Added `pg_binary_protocol` attribute to derive send and receive
functions for `PostgresType` by @LucaCappelletti94 in
#2068
* Expose guc hooks by @thesuhas in
#2075
* Allows to create multiple aggregates for the same Rust type by @if0ne
in #2078



## General Code Cleanup

* `cargo clippy --fix` by @eeeebbbbrrrr in
#2092
* Use `if-let` to unpack Options by @stuhood in
#2089
* docs: fix typo in `rust_byte_slice_to_bytea()` docs by @burmecia in
#2071
* Added a missing `#[doc(hidden)]` by @LucaCappelletti94 in
#2079

## Administrative

* Updated Fedora to latest in CI by @YohDeadfall in
#2085
* fix ci on beta rust (1.89) by @usamoi in
#2087

## New Contributors

Much thanks to our new contributors! Your work is sincerely appreciated!

* @charmitro made their first contribution in
#2074
* @thesuhas made their first contribution in
#2075
* @if0ne made their first contribution in
#2084
* @stuhood made their first contribution in
#2089

**Full Changelog**:
v0.14.3...v0.15.0
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.

3 participants