use super::declaration::RoutineTypeCatalog;
use crate::ast::{
ColumnType, CreateFunction, FunctionBody, RoutineAttributeClause, RoutineAttributeClauses,
RoutineBodyError,
};
use crate::SQLError;
fn definition_error(message: impl Into<String>) -> SQLError {
SQLError::Diagnostic {
sqlstate: "42P13".into(),
message: message.into(),
detail: None,
hint: None,
}
}
fn routine_error(sqlstate: &str, message: impl Into<String>) -> SQLError {
SQLError::Diagnostic {
sqlstate: sqlstate.into(),
message: message.into(),
detail: None,
hint: None,
}
}
pub fn check_attribute_clauses(
clauses: &RoutineAttributeClauses,
is_procedure: bool,
) -> Result<(), SQLError> {
let mut seen = Vec::<RoutineAttributeClause>::new();
for &clause in &clauses.clauses {
if is_procedure && clause.rejected_by_procedures() {
return Err(definition_error(
"invalid attribute in procedure definition",
));
}
if !clause.repeatable() && seen.contains(&clause) {
return Err(routine_error("42601", "conflicting or redundant options"));
}
seen.push(clause);
}
Ok(())
}
pub fn validate_cost(cost: Option<f32>) -> Result<(), SQLError> {
if cost.is_some_and(|cost| cost <= 0.0) {
return Err(routine_error("22023", "COST must be positive"));
}
Ok(())
}
pub fn validate_rows(rows: Option<f32>) -> Result<(), SQLError> {
if rows.is_some_and(|rows| rows <= 0.0) {
return Err(routine_error("22023", "ROWS must be positive"));
}
Ok(())
}
pub fn validate_rows_applicability(rows: Option<f32>, returns_set: bool) -> Result<(), SQLError> {
if rows.is_some() && !returns_set {
return Err(routine_error(
"22023",
"ROWS is not applicable when function does not return a set",
));
}
Ok(())
}
pub fn validate_parallel(clauses: &RoutineAttributeClauses) -> Result<(), SQLError> {
if clauses.invalid_parallel.is_some() {
return Err(routine_error(
"42601",
"parameter \"parallel\" must be SAFE, RESTRICTED, or UNSAFE",
));
}
Ok(())
}
pub fn validate_routine_language(def: &CreateFunction) -> Result<(), SQLError> {
if def.language.is_empty() {
return Err(definition_error("no language specified"));
}
if !matches!(def.language.as_str(), "plpgsql" | "sql") {
return Err(routine_error(
"42704",
format!("language \"{}\" does not exist", def.language),
));
}
Ok(())
}
pub fn validate_transforms(
catalog: &dyn RoutineTypeCatalog,
def: &CreateFunction,
clauses: &RoutineAttributeClauses,
) -> Result<(), SQLError> {
let Some(transform) = clauses.transform_types.first() else {
return Ok(());
};
let ty = catalog
.resolve_catalog_column_type(&transform.type_name)
.ok_or_else(|| {
routine_error(
"42704",
format!("type \"{}\" does not exist", transform.written),
)
})?;
let element = match &ty {
ColumnType::Array(element) => element.as_ref(),
ColumnType::Domain { base, .. } => match base.as_ref() {
ColumnType::Array(element) => element.as_ref(),
_ => &ty,
},
_ => &ty,
};
Err(routine_error(
"42704",
format!(
"transform for type {} language \"{}\" does not exist",
catalog.format_type(element)?,
def.language
),
))
}
pub fn validate_body_form(
def: &CreateFunction,
clauses: &RoutineAttributeClauses,
) -> Result<(), SQLError> {
match clauses.body_error {
Some(RoutineBodyError::Missing) => {
return Err(definition_error("no function body specified"));
}
Some(RoutineBodyError::Duplicate) => {
return Err(definition_error("duplicate function body specified"));
}
Some(RoutineBodyError::ExtraAsItems) | None => {}
}
if matches!(def.body, FunctionBody::Statements(_)) && def.language != "sql" {
return Err(definition_error(
"inline SQL function body only valid for language SQL",
));
}
if clauses.body_error == Some(RoutineBodyError::ExtraAsItems) {
return Err(definition_error(format!(
"only one AS item needed for language \"{}\"",
def.language
)));
}
Ok(())
}
pub fn reject_window_function(
def: &CreateFunction,
clauses: &RoutineAttributeClauses,
) -> Result<(), SQLError> {
if clauses.clauses.contains(&RoutineAttributeClause::Window) {
return Err(SQLError::Unsupported(format!(
"{}: WINDOW functions",
if def.is_procedure {
"CREATE PROCEDURE"
} else {
"CREATE FUNCTION"
}
)));
}
Ok(())
}