1use serde_json::Value as JSONValue;
27use uqa_core::Value;
28
29use crate::ast::{
30 CreateFunction, CursorDirection, Expr, FromClause, FunctionBody, FunctionParamMode,
31 FunctionReturns, MergeWhen, Projection, RoutineColumnTypeReference, SelectStmt, Statement, CTE,
32};
33use crate::error::{Result, SQLError};
34
35#[derive(Debug, Clone)]
42pub struct PLpgSQLFunction {
43 pub datums: Vec<PLpgSQLDatum>,
44 pub action: PLpgSQLBlock,
45 pub new_datum: Option<usize>,
47 pub old_datum: Option<usize>,
49 pub found_datum: Option<usize>,
51}
52
53impl PLpgSQLFunction {
54 pub fn loop_local_variable_datums(&self) -> std::collections::BTreeSet<usize> {
58 let mut out = std::collections::BTreeSet::new();
59 collect_loop_local_vars_block(&self.action, &mut out);
60 out
61 }
62
63 pub fn cursor_argument_datums(&self) -> std::collections::BTreeSet<usize> {
66 let mut out = std::collections::BTreeSet::new();
67 for datum in &self.datums {
68 let PLpgSQLDatum::Var(var) = datum else {
69 continue;
70 };
71 let Some(argument_row) = var.cursor.as_ref().and_then(|cursor| cursor.argument_row)
72 else {
73 continue;
74 };
75 if let Some(PLpgSQLDatum::Row { fields }) = self.datums.get(argument_row) {
76 out.extend(fields.iter().map(|field| field.varno));
77 }
78 }
79 out
80 }
81}
82
83fn collect_loop_local_vars_block(
84 block: &PLpgSQLBlock,
85 out: &mut std::collections::BTreeSet<usize>,
86) {
87 collect_loop_local_vars_stmts(&block.body, out);
88 for arm in &block.exceptions {
89 collect_loop_local_vars_stmts(&arm.body, out);
90 }
91}
92
93fn collect_loop_local_vars_stmts(
94 stmts: &[PLpgSQLStmt],
95 out: &mut std::collections::BTreeSet<usize>,
96) {
97 for stmt in stmts {
98 match stmt {
99 PLpgSQLStmt::Block(block) => collect_loop_local_vars_block(block, out),
100 PLpgSQLStmt::If {
101 then_body,
102 elsifs,
103 else_body,
104 ..
105 } => {
106 collect_loop_local_vars_stmts(then_body, out);
107 for (_, body) in elsifs {
108 collect_loop_local_vars_stmts(body, out);
109 }
110 if let Some(body) = else_body {
111 collect_loop_local_vars_stmts(body, out);
112 }
113 }
114 PLpgSQLStmt::Case {
115 arms, else_body, ..
116 } => {
117 for (_, body) in arms {
118 collect_loop_local_vars_stmts(body, out);
119 }
120 if let Some(body) = else_body {
121 collect_loop_local_vars_stmts(body, out);
122 }
123 }
124 PLpgSQLStmt::Loop { body, .. } | PLpgSQLStmt::While { body, .. } => {
125 collect_loop_local_vars_stmts(body, out);
126 }
127 PLpgSQLStmt::ForI { var, body, .. } => {
128 out.insert(*var);
129 collect_loop_local_vars_stmts(body, out);
130 }
131 PLpgSQLStmt::ForCursor { target, body, .. } => {
132 out.insert(*target);
133 collect_loop_local_vars_stmts(body, out);
134 }
135 PLpgSQLStmt::ForQuery { body, .. }
136 | PLpgSQLStmt::ForDynamic { body, .. }
137 | PLpgSQLStmt::ForeachArray { body, .. } => {
138 collect_loop_local_vars_stmts(body, out);
139 }
140 _ => {}
141 }
142 }
143}
144
145#[derive(Debug, Clone)]
148pub enum PLpgSQLDatum {
149 Var(Box<PLpgSQLVar>),
150 Rec {
152 name: String,
153 },
154 RecField {
156 field: String,
157 parent: usize,
158 },
159 Row {
161 fields: Vec<PLpgSQLRowField>,
162 },
163}
164
165impl PLpgSQLDatum {
166 pub fn name(&self) -> Option<&str> {
167 match self {
168 PLpgSQLDatum::Var(v) => Some(&v.name),
169 PLpgSQLDatum::Rec { name } => Some(name),
170 PLpgSQLDatum::RecField { .. } | PLpgSQLDatum::Row { .. } => None,
171 }
172 }
173}
174
175#[derive(Debug, Clone)]
178pub struct PLpgSQLVar {
179 pub name: String,
180 pub type_oid: Option<u32>,
182 pub type_name: String,
185 pub type_reference: Option<RoutineColumnTypeReference>,
187 pub default: Option<Expr>,
188 pub constant: bool,
189 pub not_null: bool,
190 pub cursor: Option<PLpgSQLCursor>,
192 pub lineno: Option<i64>,
195}
196
197#[derive(Debug, Clone)]
198pub struct PLpgSQLCursor {
199 pub query: Statement,
200 pub argument_row: Option<usize>,
201 pub scroll: Option<bool>,
203}
204
205#[derive(Debug, Clone)]
206pub struct PLpgSQLCursorArgument {
207 pub name: Option<String>,
208 pub expr: Expr,
209}
210
211#[derive(Debug, Clone)]
213pub enum PLpgSQLCursorOpen {
214 Bound {
215 arguments: Vec<PLpgSQLCursorArgument>,
216 },
217 Static {
218 query: Box<Statement>,
219 scroll: Option<bool>,
220 },
221 Dynamic {
222 query: Expr,
223 params: Vec<Expr>,
224 scroll: Option<bool>,
225 },
226}
227
228#[derive(Debug, Clone)]
230pub enum PLpgSQLCursorCount {
231 Constant(i64),
232 Expression(Expr),
233}
234
235#[derive(Debug, Clone)]
237pub struct PLpgSQLRowField {
238 pub name: String,
239 pub varno: usize,
240}
241
242#[derive(Debug, Clone)]
244pub struct PLpgSQLBlock {
245 pub label: Option<String>,
246 pub body: Vec<PLpgSQLStmt>,
247 pub exceptions: Vec<PLpgSQLExceptionArm>,
248}
249
250#[derive(Debug, Clone)]
253pub struct PLpgSQLExceptionArm {
254 pub conditions: Vec<String>,
258 pub body: Vec<PLpgSQLStmt>,
259}
260
261#[derive(Debug, Clone, Copy, PartialEq, Eq)]
263pub enum RaiseLevel {
264 Debug,
265 Log,
266 Info,
267 Notice,
268 Warning,
269 Error,
270}
271
272impl RaiseLevel {
273 pub fn as_str(self) -> &'static str {
274 match self {
275 RaiseLevel::Debug => "DEBUG",
276 RaiseLevel::Log => "LOG",
277 RaiseLevel::Info => "INFO",
278 RaiseLevel::Notice => "NOTICE",
279 RaiseLevel::Warning => "WARNING",
280 RaiseLevel::Error => "ERROR",
281 }
282 }
283}
284
285#[derive(Debug, Clone)]
287pub enum IntoTarget {
288 Rec(usize),
290 Row(Vec<PLpgSQLRowField>),
292}
293
294#[derive(Debug, Clone)]
296pub enum PLpgSQLStmt {
297 Block(PLpgSQLBlock),
298 Assign {
300 target: usize,
301 expr: Expr,
302 },
303 If {
304 cond: Expr,
305 then_body: Vec<PLpgSQLStmt>,
306 elsifs: Vec<(Expr, Vec<PLpgSQLStmt>)>,
307 else_body: Option<Vec<PLpgSQLStmt>>,
308 },
309 Case {
312 t_expr: Option<Expr>,
313 t_varno: Option<usize>,
314 arms: Vec<(Expr, Vec<PLpgSQLStmt>)>,
315 else_body: Option<Vec<PLpgSQLStmt>>,
316 },
317 Loop {
318 label: Option<String>,
319 body: Vec<PLpgSQLStmt>,
320 },
321 While {
322 label: Option<String>,
323 cond: Expr,
324 body: Vec<PLpgSQLStmt>,
325 },
326 ForI {
328 label: Option<String>,
329 var: usize,
330 lower: Expr,
331 upper: Expr,
332 step: Option<Expr>,
333 reverse: bool,
334 body: Vec<PLpgSQLStmt>,
335 },
336 ForQuery {
338 label: Option<String>,
339 target: IntoTarget,
340 query: Statement,
341 body: Vec<PLpgSQLStmt>,
342 },
343 ForDynamic {
345 label: Option<String>,
346 target: IntoTarget,
347 query: Expr,
348 params: Vec<Expr>,
349 body: Vec<PLpgSQLStmt>,
350 },
351 ForCursor {
353 label: Option<String>,
354 target: usize,
355 cursor: usize,
356 arguments: Vec<PLpgSQLCursorArgument>,
357 body: Vec<PLpgSQLStmt>,
358 },
359 ForeachArray {
361 label: Option<String>,
362 target: usize,
363 slice: usize,
364 expr: Expr,
365 body: Vec<PLpgSQLStmt>,
366 },
367 Exit {
370 is_exit: bool,
371 label: Option<String>,
372 cond: Option<Expr>,
373 },
374 Return {
375 value: Option<PLpgSQLReturnValue>,
376 },
377 ReturnNext {
380 value: Option<PLpgSQLReturnValue>,
381 },
382 ReturnQuery {
383 query: Statement,
384 },
385 ReturnQueryExecute {
386 query: Expr,
387 params: Vec<Expr>,
388 },
389 Raise {
390 level: RaiseLevel,
391 condition: Option<String>,
392 message: Option<String>,
393 params: Vec<Expr>,
394 },
395 Assert {
397 condition: Expr,
398 message: Option<Expr>,
399 },
400 ExecSQL {
402 stmt: Statement,
403 into: Option<IntoTarget>,
404 strict: bool,
405 },
406 DynExecute {
408 query: Expr,
409 params: Vec<Expr>,
410 into: Option<IntoTarget>,
411 strict: bool,
412 },
413 Perform {
414 query: Statement,
415 },
416 OpenCursor {
417 cursor: usize,
418 open: PLpgSQLCursorOpen,
419 },
420 FetchCursor {
421 cursor: usize,
422 target: IntoTarget,
423 direction: CursorDirection,
424 count: PLpgSQLCursorCount,
425 },
426 MoveCursor {
427 cursor: usize,
428 direction: CursorDirection,
429 count: PLpgSQLCursorCount,
430 },
431 CloseCursor {
432 cursor: usize,
433 },
434 Commit {
436 chain: bool,
437 },
438 Rollback {
440 chain: bool,
441 },
442 GetDiagnostics {
444 items: Vec<(String, usize)>,
445 },
446}
447
448#[derive(Debug, Clone)]
451pub enum PLpgSQLReturnValue {
452 Expr(Expr),
453 Datum(usize),
454}
455
456mod binding;
464mod conditions;
465mod json_validation;
466mod lowering_expression;
467mod lowering_statement;
468mod parsing;
469
470use json_validation::{
471 ensure_single_tag, expect_tag, json_bool_or_false, json_i64_or_zero, json_kind,
472 json_optional_i64, json_optional_str, json_optional_usize, json_usize_or_zero,
473 normalize_plpgsql_type, optional_array, require, require_i64, require_nonempty_str,
474 validate_assignable_datum, validate_record_datum, validate_scalar_datum,
475};
476use lowering_expression::{lower_expr, lower_expr_list, lower_full_statement};
477use lowering_statement::{lower_block, lower_cursor_scroll_options};
478use parsing::{lower_row_fields, normalize_condition};
479
480pub use binding::{bind_expr, bind_select, bind_statement, ResolvedVariable, VariableResolver};
481pub use conditions::{condition_sqlstate, condition_sqlstates};
482pub use lowering_expression::compile_expression_text;
483pub use parsing::{
484 parse_do_block, parse_do_block_with_catalog, parse_function, parse_function_with_catalog,
485};
486pub use pg_query::{PlpgsqlCatalog, PlpgsqlType};
487
488#[cfg(test)]
489mod tests;
490
491pub mod runtime_diagnostics;