pgrx_pg_sys/submodules/
pg_try.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
//LICENSE Portions Copyright 2019-2021 ZomboDB, LLC.
//LICENSE
//LICENSE Portions Copyright 2021-2023 Technology Concepts & Design, Inc.
//LICENSE
//LICENSE Portions Copyright 2023-2023 PgCentral Foundation, Inc. <contact@pgcentral.org>
//LICENSE
//LICENSE All rights reserved.
//LICENSE
//LICENSE Use of this source code is governed by the MIT license that can be found in the LICENSE file.
use crate::errcodes::PgSqlErrorCode;
use crate::panic::{downcast_panic_payload, CaughtError};
use std::collections::BTreeMap;
use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe, RefUnwindSafe, UnwindSafe};

/// [`PgTryBuilder`] is a mechanism to mimic Postgres C macros `PG_TRY` and `PG_CATCH`.
///
/// A primary difference is that the [`PgTryBuilder::finally()`] block runs even if a catch handler
/// rethrows (or throws a new) error.
pub struct PgTryBuilder<'a, R, F: FnOnce() -> R + UnwindSafe> {
    func: F,
    when: BTreeMap<
        PgSqlErrorCode,
        Box<dyn FnMut(CaughtError) -> R + 'a + UnwindSafe + RefUnwindSafe>,
    >,
    others: Option<Box<dyn FnMut(CaughtError) -> R + 'a + UnwindSafe + RefUnwindSafe>>,
    rust: Option<Box<dyn FnMut(CaughtError) -> R + 'a + UnwindSafe + RefUnwindSafe>>,
    finally: Option<Box<dyn FnMut() + 'a>>,
}

impl<'a, R, F: FnOnce() -> R + UnwindSafe> PgTryBuilder<'a, R, F> {
    /// Create a new `[PgTryBuilder]`.  The `func` argument specifies the closure that is to run.
    ///
    /// If it fails with either a Rust panic or a Postgres error, a registered catch handler
    /// for that specific error is run.  Whether one exists or not, the finally block also runs
    /// at the end, for any necessary cleanup.
    ///
    /// ## Example
    ///
    /// ```rust,no_run
    /// # use pgrx_pg_sys::{ereport, PgTryBuilder};
    /// # use pgrx_pg_sys::errcodes::PgSqlErrorCode;
    ///
    /// let i = 41;
    /// let mut finished = false;
    /// let result = PgTryBuilder::new(|| {
    ///     if i < 42 {
    ///         ereport!(ERROR, PgSqlErrorCode::ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE, "number too small");
    ///     }
    ///     i
    /// })
    ///     .catch_when(PgSqlErrorCode::ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE, |cause| cause.rethrow())
    ///     .finally(|| finished = true)
    ///     .execute();
    ///
    /// assert_eq!(finished, true);
    /// assert_eq!(result, 42);
    /// ```
    #[must_use = "must call `PgTryBuilder::execute(self)` in order for it to run"]
    pub fn new(func: F) -> Self {
        Self { func, when: Default::default(), others: None, rust: None, finally: None }
    }

    /// Add a catch handler to run should a specific error occur during execution.
    ///
    /// The argument to the catch handler closure is a [`CaughtError`] which can be
    /// rethrown via [`CaughtError::rethrow()`]
    ///
    /// The return value must be of the same type as the main execution block, or the supplied
    /// error cause must be rethrown.
    ///
    /// ## Safety
    ///
    /// While this function isn't itself unsafe, catching and then ignoring an important internal
    /// Postgres error may very well leave your database in an undesirable state.  This is your
    /// responsibility.
    #[must_use = "must call `PgTryBuilder::execute(self)` in order for it to run"]
    pub fn catch_when(
        mut self,
        error: PgSqlErrorCode,
        f: impl FnMut(CaughtError) -> R + 'a + UnwindSafe + RefUnwindSafe,
    ) -> Self {
        self.when.insert(error, Box::new(f));
        self
    }

    /// Add a catch-all handler to catch a raised error that wasn't explicitly caught via
    /// [PgTryBuilder::catch_when].
    ///
    /// The argument to the catch handler closure is a [`CaughtError`] which can be
    /// rethrown via [`CaughtError::rethrow()`].
    ///
    /// The return value must be of the same type as the main execution block, or the supplied
    /// error cause must be rethrown.
    ///
    /// ## Safety
    ///
    /// While this function isn't itself unsafe, catching and then ignoring an important internal
    /// Postgres error may very well leave your database in an undesirable state.  This is your
    /// responsibility.
    #[must_use = "must call `PgTryBuilder::execute(self)` in order for it to run"]
    pub fn catch_others(
        mut self,
        f: impl FnMut(CaughtError) -> R + 'a + UnwindSafe + RefUnwindSafe,
    ) -> Self {
        self.others = Some(Box::new(f));
        self
    }

    /// Add a handler to specifically respond to a Rust panic.
    ///
    /// The catch handler's closure argument is a [`CaughtError::PostgresError {ereport, payload}`]
    /// that you can inspect in whatever way makes sense.
    ///
    /// The return value must be of the same type as the main execution block, or the supplied
    /// error cause must be rethrown.
    ///
    #[must_use = "must call `PgTryBuilder::execute(self)` in order for it to run"]
    pub fn catch_rust_panic(
        mut self,
        f: impl FnMut(CaughtError) -> R + 'a + UnwindSafe + RefUnwindSafe,
    ) -> Self {
        self.rust = Some(Box::new(f));
        self
    }

    /// The finally block, of which there can be only one.  Successive calls to this function
    /// will replace the prior finally block.
    ///
    /// The finally block closure is called after successful return from the main execution handler
    /// or a catch handler.  The finally block does not return a value.
    #[must_use = "must call `PgTryBuilder::execute(self)` in order for it to run"]
    pub fn finally(mut self, f: impl FnMut() + 'a) -> Self {
        self.finally = Some(Box::new(f));
        self
    }

    /// Run the main execution block closure.  Any error raised will be passed to a registered
    /// catch handler, and when finished, the finally block will be run.
    pub fn execute(mut self) -> R {
        let result = catch_unwind(self.func);

        fn finally<F: FnMut()>(f: &mut Option<F>) {
            if let Some(f) = f {
                f()
            }
        }

        let result = match result {
            Ok(result) => result,
            Err(error) => {
                let (sqlerrcode, root_cause) = match downcast_panic_payload(error) {
                    CaughtError::RustPanic { ereport, payload } => {
                        let sqlerrcode = ereport.inner.sqlerrcode;
                        let panic = CaughtError::RustPanic { ereport, payload };
                        (sqlerrcode, panic)
                    }
                    CaughtError::ErrorReport(ereport) => {
                        let sqlerrcode = ereport.inner.sqlerrcode;
                        let panic = CaughtError::ErrorReport(ereport);
                        (sqlerrcode, panic)
                    }
                    CaughtError::PostgresError(ereport) => {
                        let sqlerrcode = ereport.inner.sqlerrcode;
                        let panic = CaughtError::PostgresError(ereport);
                        (sqlerrcode, panic)
                    }
                };

                // Postgres source docs says that a PG_TRY/PG_CATCH/PG_FINALLY block can't have
                // both a CATCH and a FINALLY.
                //
                // We allow it by wrapping handler execution in its own `catch_unwind()` block
                // and deferring the finally block execution until after it is complete
                let handler_result = catch_unwind(AssertUnwindSafe(|| {
                    if let Some(mut handler) = self.when.remove(&sqlerrcode) {
                        // we have a registered catch handler for the error code we caught
                        return handler(root_cause);
                    } else if let Some(mut handler) = self.others {
                        // we have a registered "catch others" handler
                        return handler(root_cause);
                    } else if let Some(mut handler) = self.rust {
                        // we have a registered catch handler for a rust panic
                        if let cause @ CaughtError::RustPanic { .. } = root_cause {
                            // and we have a rust panic
                            return handler(cause);
                        }
                    }

                    // we have no handler capable of handling whatever error we have, so rethrow the root cause
                    root_cause.rethrow();
                }));

                let handler_result = match handler_result {
                    Ok(result) => result,
                    Err(caught) => {
                        let catch_handler_error = downcast_panic_payload(caught);

                        // make sure to run the finally block and then resume unwinding
                        // with this new panic
                        finally(&mut self.finally);
                        resume_unwind(Box::new(catch_handler_error))
                    }
                };

                // Being here means the catch handler didn't raise an error.
                //
                // Postgres says:
                unsafe {
                    /*
                     * FlushErrorState --- flush the error state after error recovery
                     *
                     * This should be called by an error handler after it's done processing
                     * the error; or as soon as it's done CopyErrorData, if it intends to
                     * do stuff that is likely to provoke another error.  You are not "out" of
                     * the error subsystem until you have done this.
                     */
                    crate::FlushErrorState();
                }
                handler_result
            }
        };

        // `result` could be from a successful execution or returned from a catch handler
        //
        // Either way, the finally block needs to be run before we return it
        finally(&mut self.finally);
        result
    }
}