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
//
// Unified Query Algebra
//
// Copyright (c) 2023-2026 Cognica, Inc.
//
//! Block execution and subtransaction-backed exception handling.
use super::{arm_matches, 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 mut names = Vec::with_capacity(block.initvarnos.len());
let result = self
.initialize_block(block, &mut names)
.and_then(|()| self.exec_initialized_block(block));
for name in names.iter().rev() {
self.pop_binding(name);
}
result
}
fn initialize_block(
&mut self,
block: &PLpgSQLBlock,
names: &mut Vec<String>,
) -> Result<(), SQLError> {
for index in &block.initvarnos {
// Re-entry resets values but does not discard prepared expressions.
self.values[*index] = super::Value::Null;
self.record_types.remove(index);
if let super::PLpgSQLDatum::Var(variable) = &self.datums[*index] {
let (value, source) = match &variable.default {
Some(expression) => self.eval_expr_with_type(expression)?,
None => (super::Value::Null, None),
};
let value = super::coerce_routine_value_from(
self.services.expressions,
&value,
&variable.type_name,
source.as_ref(),
)?;
if variable.not_null && matches!(value, super::Value::Null) {
return Err(SQLError::Routine {
sqlstate: "22004".into(),
message: format!(
"null value cannot be assigned to variable \"{}\" declared NOT NULL",
variable.name
),
});
}
self.values[*index] = value;
}
// A declaration's DEFAULT sees preceding declarations and the
// enclosing scope, before this declaration starts shadowing it.
// Cursor arguments occur in initvarnos but their namespace is
// visible only while preparing that cursor's query.
if !self.cursor_arguments.contains(index) {
let name = self.datum_name(*index)?;
self.push_binding(&name, *index);
names.push(name);
}
}
Ok(())
}
fn exec_initialized_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.services.transactions.depth() == 0 {
return Err(SQLError::Internal(
"PL/pgSQL exception block executed outside a statement transaction".into(),
));
}
self.services.transactions.begin()?;
match self.exec_stmts(&block.body) {
Ok(flow) => {
self.services.transactions.commit()?;
Ok(flow)
}
Err(error) => {
if let Err(rollback_error) = self.services.transactions.rollback() {
return Err(SQLError::Internal(format!(
"PL/pgSQL exception-block rollback failed: {rollback_error}; original error: {error}"
)));
}
// A termination of the session is reported at FATAL, which no handler catches.
if matches!(&error, SQLError::Cancelled(cancelled) if cancelled.reason.terminates_session())
{
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) => {
// A handler that catches a cancellation consumes it, as PostgreSQL reports a cancel or timeout interrupt once.
if let SQLError::Cancelled(cancelled) = &error {
self.services.runtime.cancellation.clear(cancelled.reason);
}
self.err_stack.push(super::CaughtError {
diagnostics: (state, message),
cause: error,
});
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)
}
}