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
//
// Unified Query Algebra
//
// Copyright (c) 2023-2026 Cognica, Inc.
//
//! Block execution and subtransaction-backed exception handling.
use super::{
arm_matches, catchable, routine_message, Flow, Interpreter, PLpgSQLBlock, PLpgSQLStmt, SQLError,
};
impl Interpreter<'_> {
/// Run one block, routing failures through its EXCEPTION arms.
pub(super) fn exec_block(&mut self, block: &PLpgSQLBlock) -> Result<Flow, SQLError> {
let result = if block.exceptions.is_empty() {
self.exec_stmts(&block.body)
} else {
self.exec_exception_block(block)
};
match result {
Ok(Flow::Exit(Some(label))) if block.label.as_deref() == Some(label.as_str()) => {
Ok(Flow::Normal)
}
other => other,
}
}
/// `PostgreSQL` executes the guarded body of a block with `EXCEPTION`
/// inside a subtransaction. Database changes made before an error are
/// rolled back before its handler runs, while PL/pgSQL datum values stay
/// unchanged. The engine's nested transaction frame provides those same
/// memory-snapshot and persistent-backend savepoint semantics.
pub(super) fn exec_exception_block(&mut self, block: &PLpgSQLBlock) -> Result<Flow, SQLError> {
if self.engine.transaction_depth() == 0 {
return Err(SQLError::Internal(
"PL/pgSQL exception block executed outside a statement transaction".into(),
));
}
self.engine.begin()?;
match self.exec_stmts(&block.body) {
Ok(flow) => {
self.engine.commit()?;
Ok(flow)
}
Err(error) => {
if let Err(rollback_error) = self.engine.rollback() {
return Err(SQLError::Internal(format!(
"PL/pgSQL exception-block rollback failed: {rollback_error}; original error: {error}"
)));
}
if !catchable(&error) {
return Err(error);
}
let state = error
.sqlstate()
.ok_or_else(|| {
SQLError::Internal(format!(
"caught PL/pgSQL error has no SQLSTATE: {error}"
))
})?
.to_string();
let message = routine_message(&error);
let mut arm = None;
for candidate in &block.exceptions {
if arm_matches(&candidate.conditions, &state)? {
arm = Some(candidate);
break;
}
}
match arm {
Some(arm) => {
self.err_stack.push((state, message));
let handled = self.exec_stmts(&arm.body);
self.err_stack.pop();
handled
}
None => Err(error),
}
}
}
}
pub(super) fn exec_stmts(&mut self, stmts: &[PLpgSQLStmt]) -> Result<Flow, SQLError> {
for stmt in stmts {
match self.exec_stmt(stmt)? {
Flow::Normal => {}
flow => return Ok(flow),
}
}
Ok(Flow::Normal)
}
}