pgx/trigger_support/mod.rs
1/*
2Portions Copyright 2019-2021 ZomboDB, LLC.
3Portions Copyright 2021-2022 Technology Concepts & Design, Inc. <support@tcdi.com>
4
5All rights reserved.
6
7Use of this source code is governed by the MIT license that can be found in the LICENSE file.
8*/
9
10/*! Support for writing Rust trigger functions
11
12A "no-op" trigger that gets the current [`PgHeapTuple`][crate::PgHeapTuple],
13panicking (into a PostgreSQL error) if it doesn't exist:
14
15```rust,no_run
16use pgx::prelude::*;
17
18#[pg_trigger]
19fn trigger_example<'a>(trigger: &'a PgTrigger<'a>) -> Result<
20 Option<PgHeapTuple<'a, impl WhoAllocated>>,
21 PgHeapTupleError,
22> {
23 Ok(Some(trigger.old().expect("No current HeapTuple")))
24}
25```
26
27Trigger functions only accept one argument, a [`PgTrigger`], and they return a [`Result`][std::result::Result] containing
28either a [`PgHeapTuple`][crate::PgHeapTuple] or any error that implements [`impl std::error::Error`][std::error::Error].
29
30# Use from SQL
31
32The `trigger_example` example above would generate something like the following SQL:
33
34```sql
35-- pgx-examples/triggers/src/lib.rs:25
36-- triggers::trigger_example
37CREATE FUNCTION "trigger_example"()
38 RETURNS TRIGGER
39 LANGUAGE c
40 AS 'MODULE_PATHNAME', 'trigger_example_wrapper';
41```
42
43Users could then use it like so:
44
45```sql
46CREATE TABLE test (
47 id serial8 NOT NULL PRIMARY KEY,
48 title varchar(50),
49 description text,
50 payload jsonb
51);
52
53CREATE TRIGGER test_trigger
54 BEFORE INSERT ON test
55 FOR EACH ROW
56 EXECUTE PROCEDURE trigger_example();
57
58INSERT INTO test (title, description, payload)
59 VALUES ('Fox', 'a description', '{"key": "value"}');
60```
61
62This can also be done via the [`extension_sql`][crate::extension_sql] attribute:
63
64```rust,no_run
65# use pgx::prelude::*;
66#
67# #[pg_trigger]
68# fn trigger_example<'a>(trigger: &'a PgTrigger<'a>) -> Result<
69# Option<PgHeapTuple<'a, impl WhoAllocated>>,
70# PgHeapTupleError,
71# > {
72# Ok(Some(trigger.old().expect("No current HeapTuple")))
73# }
74#
75pgx::extension_sql!(
76 r#"
77CREATE TABLE test (
78 id serial8 NOT NULL PRIMARY KEY,
79 title varchar(50),
80 description text,
81 payload jsonb
82);
83*
84CREATE TRIGGER test_trigger BEFORE INSERT ON test FOR EACH ROW EXECUTE PROCEDURE trigger_example();
85INSERT INTO test (title, description, payload) VALUES ('Fox', 'a description', '{"key": "value"}');
86"#,
87 name = "create_trigger",
88 requires = [ trigger_example ]
89);
90```
91
92# Working with [`WhoAllocated`][crate::WhoAllocated]
93
94Trigger functions can return [`PgHeapTuple`][crate::PgHeapTuple]s which are [`AllocatedByRust`][crate::AllocatedByRust]
95or [`AllocatedByPostgres`][crate::AllocatedByPostgres]. In most cases, it can be inferred by the compiler using
96[`impl WhoAllocated<pg_sys::HeapTupleData>>`][crate::WhoAllocated].
97
98When it can't, the function definition permits for it to be specified:
99
100```rust,no_run
101use pgx::prelude::*;
102
103#[pg_trigger]
104fn example_allocated_by_rust<'a>(trigger: &'a PgTrigger<'a>) -> Result<
105 Option<PgHeapTuple<'a, AllocatedByRust>>,
106 PgHeapTupleError,
107> {
108 let current = trigger.old().expect("No current HeapTuple");
109 Ok(Some(current.into_owned()))
110}
111
112#[pg_trigger]
113fn example_allocated_by_postgres<'a>(trigger: &'a PgTrigger<'a>) -> Result<
114 Option<PgHeapTuple<'a, AllocatedByPostgres>>,
115 PgHeapTupleError,
116> {
117 let current = trigger.old().expect("No current HeapTuple");
118 Ok(Some(current))
119}
120```
121
122# Error Handling
123
124Trigger functions can return any [`impl std::error::Error`][std::error::Error]. Returned errors
125become PostgreSQL errors.
126
127
128```rust,no_run
129use pgx::prelude::*;
130
131#[derive(thiserror::Error, Debug)]
132enum CustomTriggerError {
133 #[error("No current HeapTuple")]
134 NoCurrentHeapTuple,
135 #[error("pgx::PgHeapTupleError: {0}")]
136 PgHeapTuple(PgHeapTupleError),
137}
138
139#[pg_trigger]
140fn example_custom_error<'a>(trigger: &'a PgTrigger<'a>) -> Result<
141 Option<PgHeapTuple<'a, impl WhoAllocated>>,
142 CustomTriggerError,
143> {
144 trigger.old().map(|t| Some(t)).ok_or(CustomTriggerError::NoCurrentHeapTuple)
145}
146```
147
148# Lifetimes
149
150Triggers are free to use lifetimes to hone their code, the generated wrapper is as generous as possible.
151
152```rust,no_run
153use pgx::prelude::*;
154
155#[derive(thiserror::Error, Debug)]
156enum CustomTriggerError<'a> {
157 #[error("No current HeapTuple")]
158 NoCurrentHeapTuple,
159 #[error("pgx::PgHeapTupleError: {0}")]
160 PgHeapTuple(PgHeapTupleError),
161 #[error("A borrowed error variant: {0}")]
162 SomeStr(&'a str),
163}
164
165#[pg_trigger]
166fn example_lifetimes<'a, 'b>(trigger: &'a PgTrigger<'a>) -> Result<
167 Option<PgHeapTuple<'a, AllocatedByRust>>,
168 CustomTriggerError<'b>,
169> {
170 return Err(CustomTriggerError::SomeStr("Oopsie"))
171}
172```
173
174# Escape hatches
175
176Unsafe [`pgx::pg_sys::FunctionCallInfo`][crate::pg_sys::FunctionCallInfo] and
177[`pgx::pg_sys::TriggerData`][crate::pg_sys::TriggerData] (include its contained
178[`pgx::pg_sys::Trigger`][crate::pg_sys::Trigger]) accessors are available..
179```
180
181 */
182
183mod pg_trigger;
184mod pg_trigger_error;
185mod pg_trigger_level;
186mod pg_trigger_option;
187mod pg_trigger_when;
188mod trigger_tuple;
189
190pub use pg_trigger::PgTrigger;
191pub use pg_trigger_error::PgTriggerError;
192pub use pg_trigger_level::PgTriggerLevel;
193pub use pg_trigger_option::PgTriggerOperation;
194pub use pg_trigger_when::PgTriggerWhen;
195pub use trigger_tuple::TriggerTuple;
196
197use crate::{is_a, pg_sys};
198
199/// Represents the event that fired a trigger.
200///
201/// It is a newtype wrapper around a `pg_sys::TriggerData.tg_event` to prevent accidental misuse and
202/// provides helper methods for determining how the event was raised.
203#[derive(Debug, Copy, Clone)]
204#[repr(transparent)]
205pub struct TriggerEvent(u32);
206
207impl TriggerEvent {
208 #[inline]
209 pub fn fired_by_insert(&self) -> bool {
210 trigger_fired_by_insert(self.0)
211 }
212
213 #[inline]
214 pub fn fired_by_delete(&self) -> bool {
215 trigger_fired_by_delete(self.0)
216 }
217
218 #[inline]
219 pub fn fired_by_update(&self) -> bool {
220 trigger_fired_by_update(self.0)
221 }
222
223 #[inline]
224 pub fn fired_by_truncate(&self) -> bool {
225 trigger_fired_by_truncate(self.0)
226 }
227
228 #[inline]
229 pub fn fired_for_row(&self) -> bool {
230 trigger_fired_for_row(self.0)
231 }
232
233 #[inline]
234 pub fn fired_for_statement(&self) -> bool {
235 trigger_fired_for_statement(self.0)
236 }
237
238 #[inline]
239 pub fn fired_before(&self) -> bool {
240 trigger_fired_before(self.0)
241 }
242
243 #[inline]
244 pub fn fired_after(&self) -> bool {
245 trigger_fired_after(self.0)
246 }
247
248 #[inline]
249 pub fn fired_instead(&self) -> bool {
250 trigger_fired_instead(self.0)
251 }
252}
253
254#[inline]
255pub unsafe fn called_as_trigger(fcinfo: pg_sys::FunctionCallInfo) -> bool {
256 let fcinfo = fcinfo.as_ref().expect("fcinfo was null");
257 !fcinfo.context.is_null() && is_a(fcinfo.context, pg_sys::NodeTag_T_TriggerData)
258}
259
260#[inline]
261pub fn trigger_fired_by_insert(event: u32) -> bool {
262 event & pg_sys::TRIGGER_EVENT_OPMASK == pg_sys::TRIGGER_EVENT_INSERT
263}
264
265#[inline]
266pub fn trigger_fired_by_delete(event: u32) -> bool {
267 event & pg_sys::TRIGGER_EVENT_OPMASK == pg_sys::TRIGGER_EVENT_DELETE
268}
269
270#[inline]
271pub fn trigger_fired_by_update(event: u32) -> bool {
272 event & pg_sys::TRIGGER_EVENT_OPMASK == pg_sys::TRIGGER_EVENT_UPDATE
273}
274
275#[inline]
276pub fn trigger_fired_by_truncate(event: u32) -> bool {
277 event & pg_sys::TRIGGER_EVENT_OPMASK == pg_sys::TRIGGER_EVENT_TRUNCATE
278}
279
280#[inline]
281pub fn trigger_fired_for_row(event: u32) -> bool {
282 event & pg_sys::TRIGGER_EVENT_ROW != 0
283}
284
285#[inline]
286pub fn trigger_fired_for_statement(event: u32) -> bool {
287 !trigger_fired_for_row(event)
288}
289
290#[inline]
291pub fn trigger_fired_before(event: u32) -> bool {
292 event & pg_sys::TRIGGER_EVENT_TIMINGMASK == pg_sys::TRIGGER_EVENT_BEFORE
293}
294
295#[inline]
296pub fn trigger_fired_after(event: u32) -> bool {
297 event & pg_sys::TRIGGER_EVENT_TIMINGMASK == pg_sys::TRIGGER_EVENT_AFTER
298}
299
300#[inline]
301pub fn trigger_fired_instead(event: u32) -> bool {
302 event & pg_sys::TRIGGER_EVENT_TIMINGMASK == pg_sys::TRIGGER_EVENT_INSTEAD
303}