uqa_sql/catalog/collations/
expression.rs1use crate::ast::{Expr, FunctionDispatch};
10use crate::{ColumnType, FunctionTypeResolver, SQLError, ScalarExpr};
11use uqa_core::{memory::ProductionControl, Value};
12
13pub struct Clause<'a, T> {
14 pub argument: &'a T,
15 pub name: &'a str,
16 pub oid: Option<u32>,
17}
18
19impl<T> Clause<'_, T> {
20 pub fn executable_name(&self) -> Result<std::borrow::Cow<'_, str>, SQLError> {
22 let Some(oid) = self.oid else {
23 return Ok(std::borrow::Cow::Borrowed(self.name));
24 };
25 let name = super::builtin_collation_name(i64::from(oid))
26 .ok_or_else(|| SQLError::Internal("unknown collation identity".into()))?;
27 Ok(std::borrow::Cow::Owned(format!(
28 "pg_catalog.{}",
29 crate::expr::quote_ident(name)
30 )))
31 }
32}
33
34pub(crate) fn validate_literal_collation(
36 ty: Option<&ColumnType>,
37 oid: Option<u32>,
38) -> Result<(), SQLError> {
39 if oid.is_some_and(|oid| oid != 0)
40 && ty.is_none_or(|ty| !crate::schema::composites::type_is_collatable(ty))
41 {
42 return Err(SQLError::Internal(
43 "constant collation does not match its declared type".into(),
44 ));
45 }
46 Ok(())
47}
48
49pub fn scalar_clause(expression: &ScalarExpr) -> Result<Option<Clause<'_, ScalarExpr>>, SQLError> {
50 let ScalarExpr::Func {
51 binding: Some(binding),
52 args,
53 ..
54 } = expression
55 else {
56 return Ok(None);
57 };
58 let Some(FunctionDispatch::Collate { oid }) = binding.dispatch else {
59 return Ok(None);
60 };
61 let [argument, ScalarExpr::Literal(Value::Str(name))] = args.as_slice() else {
62 return Err(SQLError::Internal("invalid COLLATE expression".into()));
63 };
64 Ok(Some(Clause {
65 argument,
66 name,
67 oid,
68 }))
69}
70
71pub fn ast_clause(expression: &Expr) -> Result<Option<Clause<'_, Expr>>, SQLError> {
72 let Expr::Func {
73 binding: Some(binding),
74 args,
75 ..
76 } = expression
77 else {
78 return Ok(None);
79 };
80 let Some(FunctionDispatch::Collate { oid }) = binding.dispatch else {
81 return Ok(None);
82 };
83 let [argument, Expr::Literal(Value::Str(name))] = args.as_slice() else {
84 return Err(SQLError::Internal("invalid COLLATE expression".into()));
85 };
86 Ok(Some(Clause {
87 argument,
88 name,
89 oid,
90 }))
91}
92
93pub(crate) fn discard_scalar_clause(expression: &mut ScalarExpr) -> Result<bool, SQLError> {
94 if scalar_clause(expression)?.is_none() {
95 return Ok(false);
96 }
97 let ScalarExpr::Func { args, .. } =
98 std::mem::replace(expression, ScalarExpr::Literal(Value::Null))
99 else {
100 unreachable!("validated collation clause");
101 };
102 *expression = args
103 .into_iter()
104 .next()
105 .expect("validated collation argument");
106 Ok(true)
107}
108
109pub(crate) fn discard_ast_clause(expression: &mut Expr) -> Result<bool, SQLError> {
110 if ast_clause(expression)?.is_none() {
111 return Ok(false);
112 }
113 let Expr::Func { args, .. } = std::mem::replace(expression, Expr::Literal(Value::Null)) else {
114 unreachable!("validated collation clause");
115 };
116 *expression = args
117 .into_iter()
118 .next()
119 .expect("validated collation argument");
120 Ok(true)
121}
122
123pub fn select(
124 name: &str,
125 selected: Option<u32>,
126 ty: Option<&ColumnType>,
127 catalog: Option<&dyn FunctionTypeResolver>,
128) -> Result<u32, SQLError> {
129 select_with_control(
130 name,
131 selected,
132 ty,
133 catalog,
134 &ProductionControl::uncontrolled(),
135 )
136}
137
138pub(crate) fn select_with_control(
139 name: &str,
140 selected: Option<u32>,
141 ty: Option<&ColumnType>,
142 catalog: Option<&dyn FunctionTypeResolver>,
143 control: &ProductionControl<'_>,
144) -> Result<u32, SQLError> {
145 control.check()?;
146 assert!(
147 catalog.is_none() || control.budget().is_none(),
148 "catalog callbacks are resolved outside retained generated inference"
149 );
150 if let Some(ty) = ty.filter(|ty| !crate::schema::composites::type_is_collatable(ty)) {
151 let ty = ty.without_type_modifiers_with_control(control)?;
152 let name = match catalog {
153 Some(catalog) => control.finish(catalog.format_type_name(&ty)?, None)?,
154 None => ty.display_name_with_control(control)?,
155 };
156 return Err(diagnostic(
157 control,
158 "42804",
159 format_args!("collations are not supported by type {}", name.as_str()),
160 )?);
161 }
162 if let Some(oid) = selected {
163 return valid_oid(oid);
164 }
165 if let Some(oid) = catalog
166 .map(|catalog| catalog.resolve_collation_name(name))
167 .transpose()?
168 .flatten()
169 {
170 return valid_oid(oid);
171 }
172 builtin_name(name, control)
173}
174
175fn valid_oid(oid: u32) -> Result<u32, SQLError> {
176 if oid == 0 {
177 Err(SQLError::Internal("invalid collation identity".into()))
178 } else {
179 Ok(oid)
180 }
181}
182
183fn builtin_name(name: &str, control: &ProductionControl<'_>) -> Result<u32, SQLError> {
185 let names = crate::compiler::parse_regobject_name_with_control(name, control)?
186 .ok_or_else(|| SQLError::Internal("malformed COLLATE name".into()))?;
187 let (schema, local) = match names.as_slice() {
188 [local] => (None, local),
189 [schema, local] => (Some(schema.as_str()), local),
190 [database, schema, local] if database == "uqa" => (Some(schema.as_str()), local),
191 [_, _, _] => {
192 return Err(diagnostic(
193 control,
194 "0A000",
195 format_args!(
196 "cross-database references are not implemented: {}",
197 QualifiedName(&names)
198 ),
199 )?)
200 }
201 _ => {
202 return Err(diagnostic(
203 control,
204 "42601",
205 format_args!(
206 "improper qualified name (too many dotted names): {}",
207 QualifiedName(&names)
208 ),
209 )?)
210 }
211 };
212 if let Some(schema) = schema.filter(|schema| !matches!(*schema, "pg_catalog" | "public")) {
213 return Err(diagnostic(
214 control,
215 "3F000",
216 format_args!("schema \"{schema}\" does not exist"),
217 )?);
218 }
219 if schema.is_none_or(|schema| schema == "pg_catalog") {
220 if let Some(oid) = super::builtin_collation_oid(local) {
221 return u32::try_from(oid)
222 .map_err(|_| SQLError::Internal("invalid collation identity".into()));
223 }
224 }
225 Err(diagnostic(
226 control,
227 "42704",
228 format_args!(
229 "collation \"{}\" for encoding \"UTF8\" does not exist",
230 QualifiedName(&names)
231 ),
232 )?)
233}
234
235struct QualifiedName<'a>(&'a [String]);
236
237impl std::fmt::Display for QualifiedName<'_> {
238 fn fmt(&self, output: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
239 for (position, component) in self.0.iter().enumerate() {
240 if position > 0 {
241 output.write_str(".")?;
242 }
243 output.write_str(component)?;
244 }
245 Ok(())
246 }
247}
248
249fn diagnostic(
250 control: &ProductionControl<'_>,
251 sqlstate: &str,
252 message: std::fmt::Arguments<'_>,
253) -> Result<SQLError, SQLError> {
254 let (message, _memory) = control.format(message)?.into_parts();
256 Ok(SQLError::Routine {
257 sqlstate: sqlstate.into(),
258 message,
259 })
260}
261
262#[cfg(test)]
263mod tests;