uqa_sql/routines/
attributes.rs1use super::declaration::RoutineTypeCatalog;
10use crate::ast::{
11 ColumnType, CreateFunction, FunctionBody, RoutineAttributeClause, RoutineAttributeClauses,
12 RoutineBodyError,
13};
14use crate::SQLError;
15
16fn definition_error(message: impl Into<String>) -> SQLError {
17 SQLError::Diagnostic {
18 sqlstate: "42P13".into(),
19 message: message.into(),
20 detail: None,
21 hint: None,
22 }
23}
24
25fn routine_error(sqlstate: &str, message: impl Into<String>) -> SQLError {
26 SQLError::Diagnostic {
27 sqlstate: sqlstate.into(),
28 message: message.into(),
29 detail: None,
30 hint: None,
31 }
32}
33
34pub fn check_attribute_clauses(
36 clauses: &RoutineAttributeClauses,
37 is_procedure: bool,
38) -> Result<(), SQLError> {
39 let mut seen = Vec::<RoutineAttributeClause>::new();
40 for &clause in &clauses.clauses {
41 if is_procedure && clause.rejected_by_procedures() {
42 return Err(definition_error(
43 "invalid attribute in procedure definition",
44 ));
45 }
46 if !clause.repeatable() && seen.contains(&clause) {
47 return Err(routine_error("42601", "conflicting or redundant options"));
48 }
49 seen.push(clause);
50 }
51 Ok(())
52}
53
54pub fn validate_cost(cost: Option<f32>) -> Result<(), SQLError> {
56 if cost.is_some_and(|cost| cost <= 0.0) {
57 return Err(routine_error("22023", "COST must be positive"));
58 }
59 Ok(())
60}
61
62pub fn validate_rows(rows: Option<f32>) -> Result<(), SQLError> {
64 if rows.is_some_and(|rows| rows <= 0.0) {
65 return Err(routine_error("22023", "ROWS must be positive"));
66 }
67 Ok(())
68}
69
70pub fn validate_rows_applicability(rows: Option<f32>, returns_set: bool) -> Result<(), SQLError> {
72 if rows.is_some() && !returns_set {
73 return Err(routine_error(
74 "22023",
75 "ROWS is not applicable when function does not return a set",
76 ));
77 }
78 Ok(())
79}
80
81pub fn validate_parallel(clauses: &RoutineAttributeClauses) -> Result<(), SQLError> {
83 if clauses.invalid_parallel.is_some() {
84 return Err(routine_error(
85 "42601",
86 "parameter \"parallel\" must be SAFE, RESTRICTED, or UNSAFE",
87 ));
88 }
89 Ok(())
90}
91
92pub fn validate_routine_language(def: &CreateFunction) -> Result<(), SQLError> {
94 if def.language.is_empty() {
95 return Err(definition_error("no language specified"));
96 }
97 if !matches!(def.language.as_str(), "plpgsql" | "sql") {
98 return Err(routine_error(
99 "42704",
100 format!("language \"{}\" does not exist", def.language),
101 ));
102 }
103 Ok(())
104}
105
106pub fn validate_transforms(
108 catalog: &dyn RoutineTypeCatalog,
109 def: &CreateFunction,
110 clauses: &RoutineAttributeClauses,
111) -> Result<(), SQLError> {
112 let Some(transform) = clauses.transform_types.first() else {
113 return Ok(());
114 };
115 let ty = catalog
116 .resolve_catalog_column_type(&transform.type_name)
117 .ok_or_else(|| {
118 routine_error(
119 "42704",
120 format!("type \"{}\" does not exist", transform.written),
121 )
122 })?;
123 let element = match &ty {
125 ColumnType::Array(element) => element.as_ref(),
126 ColumnType::Domain { base, .. } => match base.as_ref() {
127 ColumnType::Array(element) => element.as_ref(),
128 _ => &ty,
129 },
130 _ => &ty,
131 };
132 Err(routine_error(
133 "42704",
134 format!(
135 "transform for type {} language \"{}\" does not exist",
136 catalog.format_type(element)?,
137 def.language
138 ),
139 ))
140}
141
142pub fn validate_body_form(
144 def: &CreateFunction,
145 clauses: &RoutineAttributeClauses,
146) -> Result<(), SQLError> {
147 match clauses.body_error {
148 Some(RoutineBodyError::Missing) => {
149 return Err(definition_error("no function body specified"));
150 }
151 Some(RoutineBodyError::Duplicate) => {
152 return Err(definition_error("duplicate function body specified"));
153 }
154 Some(RoutineBodyError::ExtraAsItems) | None => {}
155 }
156 if matches!(def.body, FunctionBody::Statements(_)) && def.language != "sql" {
157 return Err(definition_error(
158 "inline SQL function body only valid for language SQL",
159 ));
160 }
161 if clauses.body_error == Some(RoutineBodyError::ExtraAsItems) {
162 return Err(definition_error(format!(
163 "only one AS item needed for language \"{}\"",
164 def.language
165 )));
166 }
167 Ok(())
168}
169
170pub fn reject_window_function(
172 def: &CreateFunction,
173 clauses: &RoutineAttributeClauses,
174) -> Result<(), SQLError> {
175 if clauses.clauses.contains(&RoutineAttributeClause::Window) {
176 return Err(SQLError::Unsupported(format!(
177 "{}: WINDOW functions",
178 if def.is_procedure {
179 "CREATE PROCEDURE"
180 } else {
181 "CREATE FUNCTION"
182 }
183 )));
184 }
185 Ok(())
186}