Module pgx::trigger_support
source · Expand description
Support for writing Rust trigger functions
A “no-op” trigger that gets the current [PgHeapTuple
][crate::PgHeapTuple],
panicking (into a PostgreSQL error) if it doesn’t exist:
use pgx::{pg_trigger, pg_sys, heap_tuple::{PgHeapTuple, PgHeapTupleError}, WhoAllocated, 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
][crate::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 [PgHeapTuple
][crate::PgHeapTuple]s 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, heap_tuple::{PgHeapTuple, PgHeapTupleError}, WhoAllocated, AllocatedByRust, AllocatedByPostgres, 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, heap_tuple::{PgHeapTuple, PgHeapTupleError}, WhoAllocated, 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, heap_tuple::{PgHeapTuple, PgHeapTupleError}, WhoAllocated, AllocatedByRust, 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
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, heap_tuple::{PgHeapTuple, PgHeapTupleError}, WhoAllocated, 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
pg_sys::TriggerData.tg_event
to prevent accidental misuse