use serde::{Deserialize, Serialize};
use crate::DataType;
use crate::sql::LogicalExpr;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ProceduralDialect {
PlPgSql,
TSql,
PlSql,
Db2Pl,
Auto,
}
impl Default for ProceduralDialect {
fn default() -> Self {
ProceduralDialect::Auto
}
}
impl std::fmt::Display for ProceduralDialect {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ProceduralDialect::PlPgSql => write!(f, "PL/pgSQL"),
ProceduralDialect::TSql => write!(f, "T-SQL"),
ProceduralDialect::PlSql => write!(f, "PL/SQL"),
ProceduralDialect::Db2Pl => write!(f, "DB2 SQL PL"),
ProceduralDialect::Auto => write!(f, "Auto"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProceduralBlock {
pub label: Option<String>,
pub declarations: Vec<VariableDeclaration>,
pub statements: Vec<ProceduralStatement>,
pub exception_handlers: Vec<ExceptionHandler>,
}
impl ProceduralBlock {
pub fn new() -> Self {
Self {
label: None,
declarations: Vec::new(),
statements: Vec::new(),
exception_handlers: Vec::new(),
}
}
}
impl Default for ProceduralBlock {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VariableDeclaration {
pub name: String,
pub data_type: Option<DataType>,
pub default: Option<LogicalExpr>,
pub is_constant: bool,
pub not_null: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ProceduralStatement {
Assignment {
target: String,
value: LogicalExpr,
},
If {
condition: LogicalExpr,
then_block: Vec<ProceduralStatement>,
elsif_branches: Vec<(LogicalExpr, Vec<ProceduralStatement>)>,
else_block: Option<Vec<ProceduralStatement>>,
},
Case {
when_branches: Vec<(LogicalExpr, Vec<ProceduralStatement>)>,
else_block: Option<Vec<ProceduralStatement>>,
},
SimpleCase {
operand: LogicalExpr,
when_branches: Vec<(LogicalExpr, Vec<ProceduralStatement>)>,
else_block: Option<Vec<ProceduralStatement>>,
},
Loop {
label: Option<String>,
body: Vec<ProceduralStatement>,
},
While {
label: Option<String>,
condition: LogicalExpr,
body: Vec<ProceduralStatement>,
},
ForNumeric {
label: Option<String>,
variable: String,
lower_bound: LogicalExpr,
upper_bound: LogicalExpr,
step: Option<LogicalExpr>,
reverse: bool,
body: Vec<ProceduralStatement>,
},
ForQuery {
label: Option<String>,
record_variable: String,
query: String,
body: Vec<ProceduralStatement>,
},
Exit {
label: Option<String>,
when_condition: Option<LogicalExpr>,
},
Continue {
label: Option<String>,
when_condition: Option<LogicalExpr>,
},
Return {
value: Option<LogicalExpr>,
},
ReturnNext {
value: LogicalExpr,
},
ReturnQuery {
query: String,
},
Raise {
level: RaiseLevel,
message: Option<LogicalExpr>,
sqlstate: Option<String>,
detail: Option<LogicalExpr>,
hint: Option<LogicalExpr>,
},
Execute {
sql: String,
into_variables: Vec<String>,
},
ExecuteDynamic {
sql_expression: LogicalExpr,
into_variables: Vec<String>,
using_parameters: Vec<LogicalExpr>,
},
Block(ProceduralBlock),
Null,
Print {
message: LogicalExpr,
},
SelectInto {
query: String,
variables: Vec<String>,
},
OpenCursor {
cursor_name: String,
query: Option<String>,
},
FetchCursor {
cursor_name: String,
into_variables: Vec<String>,
direction: FetchDirection,
},
CloseCursor {
cursor_name: String,
},
Call {
procedure_name: String,
arguments: Vec<LogicalExpr>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum RaiseLevel {
Debug,
Log,
Info,
Notice,
Warning,
Exception,
}
impl Default for RaiseLevel {
fn default() -> Self {
RaiseLevel::Exception
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum FetchDirection {
Next,
Prior,
First,
Last,
Absolute(i64),
Relative(i64),
}
impl Default for FetchDirection {
fn default() -> Self {
FetchDirection::Next
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExceptionHandler {
pub conditions: Vec<ExceptionCondition>,
pub body: Vec<ProceduralStatement>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ExceptionCondition {
Named(String),
SqlState(String),
Others,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RoutineDefinition {
pub name: String,
pub schema: Option<String>,
pub parameters: Vec<RoutineParameter>,
pub return_type: Option<ReturnType>,
pub language: String,
pub volatility: Volatility,
pub security_definer: bool,
pub body: ProceduralBlock,
pub dialect: ProceduralDialect,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RoutineParameter {
pub name: String,
pub data_type: DataType,
pub mode: ParameterMode,
pub default: Option<LogicalExpr>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum ParameterMode {
#[default]
In,
Out,
InOut,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ReturnType {
Scalar(DataType),
Table {
columns: Vec<(String, DataType)>,
},
SetOf(DataType),
Void,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum Volatility {
#[default]
Volatile,
Stable,
Immutable,
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn test_procedural_block() {
let mut block = ProceduralBlock::new();
block.label = Some("outer_block".to_string());
block.declarations.push(VariableDeclaration {
name: "counter".to_string(),
data_type: Some(DataType::Int4),
default: None,
is_constant: false,
not_null: false,
});
block.statements.push(ProceduralStatement::Null);
assert_eq!(block.label, Some("outer_block".to_string()));
assert_eq!(block.declarations.len(), 1);
assert_eq!(block.statements.len(), 1);
}
#[test]
fn test_dialect_display() {
assert_eq!(ProceduralDialect::PlPgSql.to_string(), "PL/pgSQL");
assert_eq!(ProceduralDialect::TSql.to_string(), "T-SQL");
assert_eq!(ProceduralDialect::PlSql.to_string(), "PL/SQL");
assert_eq!(ProceduralDialect::Db2Pl.to_string(), "DB2 SQL PL");
}
#[test]
fn test_variable_declaration() {
let decl = VariableDeclaration {
name: "my_var".to_string(),
data_type: Some(DataType::Text),
default: None,
is_constant: true,
not_null: true,
};
assert!(decl.is_constant);
assert!(decl.not_null);
}
}