pgx_pg_sys/submodules/pg_try.rs
1use crate::errcodes::PgSqlErrorCode;
2use crate::panic::{downcast_panic_payload, CaughtError};
3use std::collections::BTreeMap;
4use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe, RefUnwindSafe, UnwindSafe};
5
6/// [`PgTryBuilder`] is a mechanism to mimic Postgres C macros `PG_TRY` and `PG_CATCH`.
7///
8/// A primary difference is that the [`PgTryBuilder::finally()`] block runs even if a catch handler
9/// rethrows (or throws a new) error.
10pub struct PgTryBuilder<'a, R, F: FnOnce() -> R + UnwindSafe> {
11 func: F,
12 when: BTreeMap<
13 PgSqlErrorCode,
14 Box<dyn FnMut(CaughtError) -> R + 'a + UnwindSafe + RefUnwindSafe>,
15 >,
16 others: Option<Box<dyn FnMut(CaughtError) -> R + 'a + UnwindSafe + RefUnwindSafe>>,
17 rust: Option<Box<dyn FnMut(CaughtError) -> R + 'a + UnwindSafe + RefUnwindSafe>>,
18 finally: Option<Box<dyn FnMut() + 'a>>,
19}
20
21impl<'a, R, F: FnOnce() -> R + UnwindSafe> PgTryBuilder<'a, R, F> {
22 /// Create a new `[PgTryBuilder]`. The `func` argument specifies the closure that is to run.
23 ///
24 /// If it fails with either a Rust panic or a Postgres error, a registered catch handler
25 /// for that specific error is run. Whether one exists or not, the finally block also runs
26 /// at the end, for any necessary cleanup.
27 ///
28 /// ## Example
29 ///
30 /// ```rust,no_run
31 /// # use pgx_pg_sys::{ereport, PgTryBuilder};
32 /// # use pgx_pg_sys::errcodes::PgSqlErrorCode;
33 ///
34 /// let i = 41;
35 /// let mut finished = false;
36 /// let result = PgTryBuilder::new(|| {
37 /// if i < 42 {
38 /// ereport!(ERROR, PgSqlErrorCode::ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE, "number too small");
39 /// }
40 /// i
41 /// })
42 /// .catch_when(PgSqlErrorCode::ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE, |cause| cause.rethrow())
43 /// .finally(|| finished = true)
44 /// .execute();
45 ///
46 /// assert_eq!(finished, true);
47 /// assert_eq!(result, 42);
48 /// ```
49 #[must_use = "must call `PgTryBuilder::execute(self)` in order for it to run"]
50 pub fn new(func: F) -> Self {
51 Self { func, when: Default::default(), others: None, rust: None, finally: None }
52 }
53
54 /// Add a catch handler to run should a specific error occur during execution.
55 ///
56 /// The argument to the catch handler closure is a [`CaughtError`] which can be
57 /// rethrown via [`CaughtError::rethrow()`]
58 ///
59 /// The return value must be of the same type as the main execution block, or the supplied
60 /// error cause must be rethrown.
61 ///
62 /// ## Safety
63 ///
64 /// While this function isn't itself unsafe, catching and then ignoring an important internal
65 /// Postgres error may very well leave your database in an undesirable state. This is your
66 /// responsibility.
67 #[must_use = "must call `PgTryBuilder::execute(self)` in order for it to run"]
68 pub fn catch_when(
69 mut self,
70 error: PgSqlErrorCode,
71 f: impl FnMut(CaughtError) -> R + 'a + UnwindSafe + RefUnwindSafe,
72 ) -> Self {
73 self.when.insert(error, Box::new(f));
74 self
75 }
76
77 /// Add a catch-all handler to catch a raised error that wasn't explicitly caught via
78 /// [PgTryBuilder::catch_when].
79 ///
80 /// The argument to the catch handler closure is a [`CaughtError`] which can be
81 /// rethrown via [`CaughtError::rethrow()`].
82 ///
83 /// The return value must be of the same type as the main execution block, or the supplied
84 /// error cause must be rethrown.
85 ///
86 /// ## Safety
87 ///
88 /// While this function isn't itself unsafe, catching and then ignoring an important internal
89 /// Postgres error may very well leave your database in an undesirable state. This is your
90 /// responsibility.
91 #[must_use = "must call `PgTryBuilder::execute(self)` in order for it to run"]
92 pub fn catch_others(
93 mut self,
94 f: impl FnMut(CaughtError) -> R + 'a + UnwindSafe + RefUnwindSafe,
95 ) -> Self {
96 self.others = Some(Box::new(f));
97 self
98 }
99
100 /// Add a handler to specifically respond to a Rust panic.
101 ///
102 /// The catch handler's closure argument is a [`CaughtError::PostgresError {ereport, payload}`]
103 /// that you can inspect in whatever way makes sense.
104 ///
105 /// The return value must be of the same type as the main execution block, or the supplied
106 /// error cause must be rethrown.
107 ///
108 #[must_use = "must call `PgTryBuilder::execute(self)` in order for it to run"]
109 pub fn catch_rust_panic(
110 mut self,
111 f: impl FnMut(CaughtError) -> R + 'a + UnwindSafe + RefUnwindSafe,
112 ) -> Self {
113 self.rust = Some(Box::new(f));
114 self
115 }
116
117 /// The finally block, of which there can be only one. Successive calls to this function
118 /// will replace the prior finally block.
119 ///
120 /// The finally block closure is called after successful return from the main execution handler
121 /// or a catch handler. The finally block does not return a value.
122 #[must_use = "must call `PgTryBuilder::execute(self)` in order for it to run"]
123 pub fn finally(mut self, f: impl FnMut() + 'a) -> Self {
124 self.finally = Some(Box::new(f));
125 self
126 }
127
128 /// Run the main execution block closure. Any error raised will be passed to a registered
129 /// catch handler, and when finished, the finally block will be run.
130 pub fn execute(mut self) -> R {
131 let result = catch_unwind(self.func);
132
133 fn finally<F: FnMut()>(f: &mut Option<F>) {
134 if let Some(f) = f {
135 f()
136 }
137 }
138
139 let result = match result {
140 Ok(result) => result,
141 Err(error) => {
142 let (sqlerrcode, root_cause) = match downcast_panic_payload(error) {
143 CaughtError::RustPanic { ereport, payload } => {
144 let sqlerrcode = ereport.inner.sqlerrcode;
145 let panic = CaughtError::RustPanic { ereport, payload };
146 (sqlerrcode, panic)
147 }
148 CaughtError::ErrorReport(ereport) => {
149 let sqlerrcode = ereport.inner.sqlerrcode;
150 let panic = CaughtError::ErrorReport(ereport);
151 (sqlerrcode, panic)
152 }
153 CaughtError::PostgresError(ereport) => {
154 let sqlerrcode = ereport.inner.sqlerrcode;
155 let panic = CaughtError::PostgresError(ereport);
156 (sqlerrcode, panic)
157 }
158 };
159
160 // Postgres source docs says that a PG_TRY/PG_CATCH/PG_FINALLY block can't have
161 // both a CATCH and a FINALLY.
162 //
163 // We allow it by wrapping handler execution in its own `catch_unwind()` block
164 // and deferring the finally block execution until after it is complete
165 let handler_result = catch_unwind(AssertUnwindSafe(|| {
166 if let Some(mut handler) = self.when.remove(&sqlerrcode) {
167 // we have a registered catch handler for the error code we caught
168 return handler(root_cause);
169 } else if let Some(mut handler) = self.others {
170 // we have a registered "catch others" handler
171 return handler(root_cause);
172 } else if let Some(mut handler) = self.rust {
173 // we have a registered catch handler for a rust panic
174 if let cause @ CaughtError::RustPanic { .. } = root_cause {
175 // and we have a rust panic
176 return handler(cause);
177 }
178 }
179
180 // we have no handler capable of handling whatever error we have, so rethrow the root cause
181 root_cause.rethrow();
182 }));
183
184 let handler_result = match handler_result {
185 Ok(result) => result,
186 Err(caught) => {
187 let catch_handler_error = downcast_panic_payload(caught);
188
189 // make sure to run the finally block and then resume unwinding
190 // with this new panic
191 finally(&mut self.finally);
192 resume_unwind(Box::new(catch_handler_error))
193 }
194 };
195
196 // Being here means the catch handler didn't raise an error.
197 //
198 // Postgres says:
199 unsafe {
200 /*
201 * FlushErrorState --- flush the error state after error recovery
202 *
203 * This should be called by an error handler after it's done processing
204 * the error; or as soon as it's done CopyErrorData, if it intends to
205 * do stuff that is likely to provoke another error. You are not "out" of
206 * the error subsystem until you have done this.
207 */
208 crate::FlushErrorState();
209 }
210 handler_result
211 }
212 };
213
214 // `result` could be from a successful execution or returned from a catch handler
215 //
216 // Either way, the finally block needs to be run before we return it
217 finally(&mut self.finally);
218 result
219 }
220}