use serde_json::Value as JSONValue;
use uqa_core::Value;
use crate::ast::{
CreateFunction, Expr, FromClause, FunctionBody, FunctionParamMode, FunctionReturns, MergeWhen,
Projection, RoutineColumnTypeReference, SelectStmt, Statement, CTE,
};
use crate::error::{Result, SQLError};
#[derive(Debug, Clone)]
pub struct PLpgSQLFunction {
pub datums: Vec<PLpgSQLDatum>,
pub action: PLpgSQLBlock,
pub found_datum: Option<usize>,
}
impl PLpgSQLFunction {
pub fn fori_variable_datums(&self) -> std::collections::BTreeSet<usize> {
let mut out = std::collections::BTreeSet::new();
collect_fori_vars_block(&self.action, &mut out);
out
}
pub fn cursor_argument_datums(&self) -> std::collections::BTreeSet<usize> {
let mut out = std::collections::BTreeSet::new();
for datum in &self.datums {
let PLpgSQLDatum::Var(var) = datum else {
continue;
};
let Some(argument_row) = var.cursor.as_ref().and_then(|cursor| cursor.argument_row)
else {
continue;
};
if let Some(PLpgSQLDatum::Row { fields }) = self.datums.get(argument_row) {
out.extend(fields.iter().map(|field| field.varno));
}
}
out
}
}
fn collect_fori_vars_block(block: &PLpgSQLBlock, out: &mut std::collections::BTreeSet<usize>) {
collect_fori_vars_stmts(&block.body, out);
for arm in &block.exceptions {
collect_fori_vars_stmts(&arm.body, out);
}
}
fn collect_fori_vars_stmts(stmts: &[PLpgSQLStmt], out: &mut std::collections::BTreeSet<usize>) {
for stmt in stmts {
match stmt {
PLpgSQLStmt::Block(block) => collect_fori_vars_block(block, out),
PLpgSQLStmt::If {
then_body,
elsifs,
else_body,
..
} => {
collect_fori_vars_stmts(then_body, out);
for (_, body) in elsifs {
collect_fori_vars_stmts(body, out);
}
if let Some(body) = else_body {
collect_fori_vars_stmts(body, out);
}
}
PLpgSQLStmt::Case {
arms, else_body, ..
} => {
for (_, body) in arms {
collect_fori_vars_stmts(body, out);
}
if let Some(body) = else_body {
collect_fori_vars_stmts(body, out);
}
}
PLpgSQLStmt::Loop { body, .. } | PLpgSQLStmt::While { body, .. } => {
collect_fori_vars_stmts(body, out);
}
PLpgSQLStmt::ForI { var, body, .. } => {
out.insert(*var);
collect_fori_vars_stmts(body, out);
}
PLpgSQLStmt::ForQuery { body, .. } => collect_fori_vars_stmts(body, out),
_ => {}
}
}
}
#[derive(Debug, Clone)]
pub enum PLpgSQLDatum {
Var(Box<PLpgSQLVar>),
Rec {
name: String,
},
RecField {
field: String,
parent: usize,
},
Row {
fields: Vec<PLpgSQLRowField>,
},
}
impl PLpgSQLDatum {
pub fn name(&self) -> Option<&str> {
match self {
PLpgSQLDatum::Var(v) => Some(&v.name),
PLpgSQLDatum::Rec { name } => Some(name),
PLpgSQLDatum::RecField { .. } | PLpgSQLDatum::Row { .. } => None,
}
}
}
#[derive(Debug, Clone)]
pub struct PLpgSQLVar {
pub name: String,
pub type_name: String,
pub type_reference: Option<RoutineColumnTypeReference>,
pub default: Option<Expr>,
pub constant: bool,
pub not_null: bool,
pub cursor: Option<PLpgSQLCursor>,
pub lineno: Option<i64>,
}
#[derive(Debug, Clone)]
pub struct PLpgSQLCursor {
pub query: Statement,
pub argument_row: Option<usize>,
}
#[derive(Debug, Clone)]
pub struct PLpgSQLCursorArgument {
pub name: Option<String>,
pub expr: Expr,
}
#[derive(Debug, Clone)]
pub struct PLpgSQLRowField {
pub name: String,
pub varno: usize,
}
#[derive(Debug, Clone)]
pub struct PLpgSQLBlock {
pub label: Option<String>,
pub body: Vec<PLpgSQLStmt>,
pub exceptions: Vec<PLpgSQLExceptionArm>,
}
#[derive(Debug, Clone)]
pub struct PLpgSQLExceptionArm {
pub conditions: Vec<String>,
pub body: Vec<PLpgSQLStmt>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RaiseLevel {
Debug,
Log,
Info,
Notice,
Warning,
Error,
}
impl RaiseLevel {
pub fn as_str(self) -> &'static str {
match self {
RaiseLevel::Debug => "DEBUG",
RaiseLevel::Log => "LOG",
RaiseLevel::Info => "INFO",
RaiseLevel::Notice => "NOTICE",
RaiseLevel::Warning => "WARNING",
RaiseLevel::Error => "ERROR",
}
}
}
#[derive(Debug, Clone)]
pub enum IntoTarget {
Rec(usize),
Row(Vec<PLpgSQLRowField>),
}
#[derive(Debug, Clone)]
pub enum PLpgSQLStmt {
Block(PLpgSQLBlock),
Assign {
target: usize,
expr: Expr,
},
If {
cond: Expr,
then_body: Vec<PLpgSQLStmt>,
elsifs: Vec<(Expr, Vec<PLpgSQLStmt>)>,
else_body: Option<Vec<PLpgSQLStmt>>,
},
Case {
t_expr: Option<Expr>,
t_varno: Option<usize>,
arms: Vec<(Expr, Vec<PLpgSQLStmt>)>,
else_body: Option<Vec<PLpgSQLStmt>>,
},
Loop {
label: Option<String>,
body: Vec<PLpgSQLStmt>,
},
While {
label: Option<String>,
cond: Expr,
body: Vec<PLpgSQLStmt>,
},
ForI {
label: Option<String>,
var: usize,
lower: Expr,
upper: Expr,
step: Option<Expr>,
reverse: bool,
body: Vec<PLpgSQLStmt>,
},
ForQuery {
label: Option<String>,
target: IntoTarget,
query: Statement,
body: Vec<PLpgSQLStmt>,
},
Exit {
is_exit: bool,
label: Option<String>,
cond: Option<Expr>,
},
Return {
value: Option<PLpgSQLReturnValue>,
},
ReturnNext {
value: Option<PLpgSQLReturnValue>,
},
ReturnQuery {
query: Statement,
},
ReturnQueryExecute {
query: Expr,
params: Vec<Expr>,
},
Raise {
level: RaiseLevel,
condition: Option<String>,
message: Option<String>,
params: Vec<Expr>,
},
ExecSQL {
stmt: Statement,
into: Option<IntoTarget>,
strict: bool,
},
DynExecute {
query: Expr,
params: Vec<Expr>,
into: Option<IntoTarget>,
strict: bool,
},
Perform {
query: Statement,
},
OpenCursor {
cursor: usize,
arguments: Vec<PLpgSQLCursorArgument>,
},
FetchCursor {
cursor: usize,
target: IntoTarget,
direction: i64,
count: i64,
},
CloseCursor {
cursor: usize,
},
GetDiagnostics {
items: Vec<(String, usize)>,
},
}
#[derive(Debug, Clone)]
pub enum PLpgSQLReturnValue {
Expr(Expr),
Datum(usize),
}
mod binding;
mod conditions;
mod json_validation;
mod lowering_expression;
mod lowering_statement;
mod parsing;
use json_validation::{
ensure_single_tag, expect_tag, json_bool_or_false, json_i64_or_zero, json_kind,
json_optional_i64, json_optional_str, json_optional_usize, json_usize_or_zero,
normalize_plpgsql_type, optional_array, require, require_i64, require_nonempty_str,
validate_assignable_datum, validate_record_datum, validate_scalar_datum,
};
use lowering_expression::{lower_expr, lower_expr_list, lower_full_statement};
use lowering_statement::lower_block;
use parsing::{lower_row_fields, normalize_condition};
pub use binding::{bind_expr, bind_select, bind_statement, VariableResolver};
pub use conditions::{condition_sqlstate, condition_sqlstates};
pub use lowering_expression::compile_expression_text;
pub use parsing::{parse_do_block, parse_function};
#[cfg(test)]
mod tests;