Macro pgx::composite_type

source ·
macro_rules! composite_type {
    ($lt:lifetime, $composite_type:expr) => { ... };
    ($composite_type:expr) => { ... };
}
Expand description

Composite type support

Support for working with types defined by SQL statements like:

CREATE TYPE Dog AS (
    name TEXT,
    scritches INT
);

To PostgreSQL, these types are a pgx::pg_sys::HeapTuple, which is a pointer to a pgx::pg_sys::HeapTupleData. pgx provides more idiomatic wrapping of this type with pgx::heap_tuple::PgHeapTuple.

This composite_type!() macro expands into a pgx::heap_tuple::PgHeapTuple.

assert_eq!(
    core::any::TypeId::of::<pgx::composite_type!("Dog")>(),
    core::any::TypeId::of::<pgx::heap_tuple::PgHeapTuple<'static, ::pgx::AllocatedByRust>>(),
);
assert_eq!(
    core::any::TypeId::of::<pgx::composite_type!('static, "Dog")>(),
    core::any::TypeId::of::<pgx::heap_tuple::PgHeapTuple<'static, ::pgx::AllocatedByRust>>(),
);
const DOG_COMPOSITE_TYPE_IDENT: &str = "Dog";
assert_eq!(
    core::any::TypeId::of::<pgx::composite_type!('static, DOG_COMPOSITE_TYPE_IDENT)>(),
    core::any::TypeId::of::<pgx::heap_tuple::PgHeapTuple<'static, ::pgx::AllocatedByRust>>(),
);

Inside a #[pg_extern]

Used inside of a #[pg_extern] definition, this macro alters the generated SQL to use the given composite type name.

Meaning that this function:

use pgx::{prelude::*, AllocatedByRust};

#[pg_extern]
fn scritch(
    maybe_dog: Option<::pgx::composite_type!("Dog")>,
) -> Option<pgx::composite_type!("Dog")> {
    // Gets resolved to:
    let maybe_dog: Option<PgHeapTuple<AllocatedByRust>> = maybe_dog;

    let maybe_dog = if let Some(mut dog) = maybe_dog {
        dog.set_by_name("scritches", dog.get_by_name::<i32>("scritches").unwrap())
            .unwrap();
        Some(dog)
    } else {
        None
    };

    maybe_dog
}

Would generate SQL similar to this:

-- a_bunch_of_dog_functions/src/lib.rs:3
-- a_bunch_of_dog_functions::scritch
CREATE FUNCTION "scritch"(
        "maybe_dog" Dog /* core::option::Option<pgx::heap_tuple::PgHeapTuple<pgx::pgbox::AllocatedByRust>> */
) RETURNS Dog /* core::option::Option<pgx::heap_tuple::PgHeapTuple<pgx::pgbox::AllocatedByRust>> */
LANGUAGE c /* Rust */
AS 'MODULE_PATHNAME', 'scritch_wrapper';

It’s possibly to use composite_type!() inside a default!() macro:

use pgx::{prelude::*, AllocatedByRust};

#[pg_extern]
fn this_dog_name_or_your_favorite_dog_name(
    dog: pgx::default!(pgx::composite_type!("Dog"), "ROW('Nami', 0)::Dog"),
) -> &str {
    // Gets resolved to:
    let dog: PgHeapTuple<AllocatedByRust> = dog;

    dog.get_by_name("name").unwrap().unwrap()
}

Composite types are very runtime failure heavy, as opposed to using PostgreSQL types pgx has a builtin compatible type for, or a #[derive(pgx::PostgresType) type. Those options can have their shape and API reasoned about at build time.

This runtime failure model is because the shape and layout, or even the name of the type could change during the runtime of the extension.

For example, a user of the extension could do something like:

CREATE TYPE Dog AS (
    name TEXT,
    scritches INT
);

CREATE EXTENSION a_bunch_of_dog_functions;

SELECT scritch(ROW('Nami', 0)::Dog);

ALTER TYPE Dog ADD ATTRIBUTE tail_wags INT;

SELECT scritch(ROW('Nami', 0, 0)::Dog);

Because of this, all interaction with composite types requires runtime lookup and type checking.

Creating composite types

It’s possible to create composite types of a given identifier with pgx::heap_tuple::PgHeapTuple::new_composite_type.