Expand description

Support for writing Rust trigger functions

A “no-op” trigger that gets the current PgHeapTuple, panicking (into a PostgreSQL error) if it doesn’t exist:

use pgx::{pg_trigger, pg_sys, PgHeapTuple, WhoAllocated, PgHeapTupleError, PgTrigger};

#[pg_trigger]
fn trigger_example(trigger: &PgTrigger) -> Result<
    PgHeapTuple<'_, impl WhoAllocated<pg_sys::HeapTupleData>>,
    PgHeapTupleError,
> {
    Ok(unsafe { trigger.current() }.expect("No current HeapTuple"))
}

Trigger functions only accept one argument, a PgTrigger, and they return a Result containing either a PgHeapTuple or any error that implements impl std::error::Error.

Use from SQL

The trigger_example example above would generate something like the following SQL:

-- pgx-examples/triggers/src/lib.rs:25
-- triggers::trigger_example
CREATE FUNCTION "trigger_example"()
    RETURNS TRIGGER
    LANGUAGE c
    AS 'MODULE_PATHNAME', 'trigger_example_wrapper';

Users could then use it like so:

CREATE TABLE test (
    id serial8 NOT NULL PRIMARY KEY,
    title varchar(50),
    description text,
    payload jsonb
);

CREATE TRIGGER test_trigger
    BEFORE INSERT ON test
    FOR EACH ROW
    EXECUTE PROCEDURE trigger_example();

INSERT INTO test (title, description, payload)
    VALUES ('Fox', 'a description', '{"key": "value"}');

This can also be done via the extension_sql attribute:

pgx::extension_sql!(
    r#"
CREATE TABLE test (
    id serial8 NOT NULL PRIMARY KEY,
    title varchar(50),
    description text,
    payload jsonb
);

CREATE TRIGGER test_trigger BEFORE INSERT ON test FOR EACH ROW EXECUTE PROCEDURE trigger_example();
INSERT INTO test (title, description, payload) VALUES ('Fox', 'a description', '{"key": "value"}');
"#,
    name = "create_trigger",
    requires = [ trigger_example ]
);

Working with WhoAllocated

Trigger functions can return PgHeapTuples which are AllocatedByRust or AllocatedByPostgres. In most cases, it can be inferred by the compiler using impl WhoAllocated<pg_sys::HeapTupleData>>.

When it can’t, the function definition permits for it to be specified:

use pgx::{pg_trigger, pg_sys, PgHeapTuple, AllocatedByRust, AllocatedByPostgres, PgHeapTupleError, PgTrigger};

#[pg_trigger]
fn example_allocated_by_rust(trigger: &PgTrigger) -> Result<
    PgHeapTuple<'_, AllocatedByRust>,
    PgHeapTupleError,
> {
    let current = unsafe { trigger.current() }.expect("No current HeapTuple");
    Ok(current.into_owned())
}

#[pg_trigger]
fn example_allocated_by_postgres(trigger: &PgTrigger) -> Result<
    PgHeapTuple<'_, AllocatedByPostgres>,
    PgHeapTupleError,
> {
    let current = unsafe { trigger.current() }.expect("No current HeapTuple");
    Ok(current)
}

Error Handling

Trigger functions can return any impl std::error::Error. Returned errors become PostgreSQL errors.

use pgx::{pg_trigger, pg_sys, PgHeapTuple, WhoAllocated, PgHeapTupleError, PgTrigger};

#[derive(thiserror::Error, Debug)]
enum CustomTriggerError {
    #[error("No current HeapTuple")]
    NoCurrentHeapTuple,
    #[error("pgx::PgHeapTupleError: {0}")]
    PgHeapTuple(PgHeapTupleError),
}

#[pg_trigger]
fn example_custom_error(trigger: &PgTrigger) -> Result<
    PgHeapTuple<'_, impl WhoAllocated<pg_sys::HeapTupleData>>,
    CustomTriggerError,
> {
    unsafe { trigger.current() }.ok_or(CustomTriggerError::NoCurrentHeapTuple)
}

Lifetimes

Triggers are free to use lifetimes to hone their code, the generated wrapper is as generous as possible.

use pgx::{pg_trigger, pg_sys, PgHeapTuple, AllocatedByRust, PgHeapTupleError, PgTrigger};

#[derive(thiserror::Error, Debug)]
enum CustomTriggerError<'a> {
    #[error("No current HeapTuple")]
    NoCurrentHeapTuple,
    #[error("pgx::PgHeapTupleError: {0}")]
    PgHeapTuple(PgHeapTupleError),
    #[error("A borrowed error variant: {0}")]
    SomeStr(&'a str),
}

#[pg_trigger]
fn example_lifetimes<'a, 'b>(trigger: &'a PgTrigger) -> Result<
    PgHeapTuple<'a, AllocatedByRust>,
    CustomTriggerError<'b>,
> {
    return Err(CustomTriggerError::SomeStr("Oopsie"))
}

Escape hatches

Unsafe pgx::pg_sys::FunctionCallInfo and pgx::pg_sys::TriggerData (include its contained pgx::pg_sys::Trigger) accessors are available..

Getting safe data all at once

Many PgTrigger functions are unsafe as they dereference pointers inside the [TriggerData][pgx::pg_sys::TriggerData] contained by the PgTrigger.

In cases where a safe API is desired, the PgTriggerSafe structure can be retrieved from PgTrigger::to_safe.

use pgx::{pg_trigger, pg_sys, PgHeapTuple, WhoAllocated, PgHeapTupleError, PgTrigger, PgTriggerError};

#[pg_trigger]
fn trigger_safe(trigger: &PgTrigger) -> Result<
    PgHeapTuple<'_, impl WhoAllocated<pg_sys::HeapTupleData>>,
    PgTriggerError,
> {
    let trigger_safe = unsafe { trigger.to_safe() }?;
    Ok(trigger_safe.current.expect("No current HeapTuple"))
}

Structs

The datatype accepted by a trigger

A newtype’d wrapper around a pg_sys::TriggerData.tg_event to prevent accidental misuse

Enums

The level of a trigger

The operation for which the trigger was fired

When a trigger happened

Indicates which trigger tuple to convert into a crate::PgHeapTuple.

Functions