1use uqa_core::{
10 memory::{Produced, ProductionControl, ProductionString, ProductionVec},
11 ArrayValue, Value,
12};
13
14use super::super::casting::{cast_value_from_with_control, parse_pg_array_literal_with_control};
15use super::{format_regtype_value_with_control, EngineHook};
16use crate::{
17 ast::ColumnType,
18 error::{Result, SQLError},
19};
20
21#[must_use]
22pub fn coercion_type_name(ty: &ColumnType) -> String {
23 coercion_type_name_with_control(ty, &ProductionControl::uncontrolled())
24 .expect("ordinary coercion type name")
25 .into_uncontrolled()
26 .expect("ordinary coercion type name owner")
27}
28
29fn coercion_type_name_with_control(
30 ty: &ColumnType,
31 control: &ProductionControl<'_>,
32) -> Result<Produced<String>> {
33 control.check()?;
34 match ty {
35 ColumnType::Domain { base, .. } => coercion_type_name_with_control(base, control),
36 ColumnType::Array(element) => {
37 let element = coercion_type_name_with_control(element, control)?;
38 let mut name = ProductionString::new(*control);
39 name.push_str(&element)?;
40 name.push_str("[]")?;
41 Ok(name.finish()?)
42 }
43 _ => Ok(ty.sql_name_with_control(control)?),
44 }
45}
46
47fn regrole_array_type(ty: &ColumnType) -> bool {
48 match ty {
49 ColumnType::Array(element) => {
50 matches!(element.as_ref(), ColumnType::Regrole) || regrole_array_type(element)
51 }
52 _ => false,
53 }
54}
55
56fn array_leaf_type(ty: &ColumnType) -> &ColumnType {
57 match ty {
58 ColumnType::Array(element) => array_leaf_type(element),
59 _ => ty,
60 }
61}
62
63fn optional_type_name(
64 name: &str,
65 control: &ProductionControl<'_>,
66) -> Result<Option<Produced<ColumnType>>> {
67 match ColumnType::from_sql_name_with_control(name, control) {
68 Ok(ty) => Ok(Some(ty)),
69 Err(error) if matches!(error.sqlstate(), Some("53200" | "57014")) => Err(error),
70 Err(_) => Ok(None),
71 }
72}
73
74pub fn cast_value_with_type_resolution(
76 value: &Value,
77 source_ty: Option<&str>,
78 target_ty: &str,
79 engine: Option<&dyn EngineHook>,
80) -> Result<Value> {
81 cast_value_with_type_resolution_with_control(
82 value,
83 source_ty,
84 target_ty,
85 engine,
86 &ProductionControl::uncontrolled(),
87 )?
88 .into_uncontrolled()
89 .map_err(|_| SQLError::Internal("ordinary catalog cast owner".into()))
90}
91
92pub fn cast_value_with_type_resolution_with_control(
94 value: &Value,
95 source_ty: Option<&str>,
96 target_ty: &str,
97 engine: Option<&dyn EngineHook>,
98 control: &ProductionControl<'_>,
99) -> Result<Produced<Value>> {
100 control.check()?;
101 let resolved_target = engine
102 .map(|engine| engine.resolve_type_name(target_ty))
103 .transpose()
104 .map_err(SQLError::Internal)?
105 .flatten()
106 .map(|ty| ty.retain_external_with_control(control))
107 .transpose()?;
108 control.check()?;
109 if let (Some(engine), Some(target)) = (engine, resolved_target.as_deref()) {
110 if let Some(value) = engine.cast_domain(value, source_ty, target)? {
111 return Ok(control.retain_external_value(value)?);
112 }
113 control.check()?;
114 if matches!(target, ColumnType::Array(_)) && requires_catalog_array_cast(target) {
115 return cast_catalog_array(value, source_ty, target, engine, control);
116 }
117 }
118 let resolved_source = match (engine, source_ty) {
119 (Some(engine), Some(source_ty)) => engine
120 .resolve_type_name(source_ty)
121 .map_err(SQLError::Internal)?
122 .map(|ty| {
123 let ty = ty.retain_external_with_control(control)?;
124 coercion_type_name_with_control(&ty, control)
125 })
126 .transpose()?,
127 _ => None,
128 };
129 let source_ty = resolved_source
130 .as_ref()
131 .map(|name| name.as_str())
132 .or(source_ty);
133 let target_name = resolved_target
134 .as_ref()
135 .map(|ty| coercion_type_name_with_control(ty, control))
136 .transpose()?;
137 let target_ty = target_name.as_ref().map_or(target_ty, |name| name.as_str());
138 let parsed_target = if resolved_target.is_some() {
139 None
140 } else {
141 optional_type_name(target_ty, control)?
142 };
143 let target_column_type = resolved_target.as_deref().or(parsed_target.as_deref());
144 cast_resolved_value(
145 value,
146 source_ty,
147 target_ty,
148 target_column_type,
149 engine,
150 control,
151 )
152}
153
154fn cast_resolved_value(
155 value: &Value,
156 source_ty: Option<&str>,
157 target_ty: &str,
158 target_column_type: Option<&ColumnType>,
159 engine: Option<&dyn EngineHook>,
160 control: &ProductionControl<'_>,
161) -> Result<Produced<Value>> {
162 if target_column_type.is_some_and(regrole_array_type) {
163 let source_column_type = source_ty
164 .map(|name| optional_type_name(name, control))
165 .transpose()?
166 .flatten();
167 let source_name = source_column_type
168 .as_deref()
169 .map(array_leaf_type)
170 .map(|ty| ty.sql_name_with_control(control))
171 .transpose()?;
172 return cast_array(
173 value,
174 source_name.as_ref().map(|name| name.as_str()),
175 "regrole",
176 "regrole[]",
177 engine,
178 control,
179 );
180 }
181 if target_ty.eq_ignore_ascii_case("text") {
182 if let Some(source_ty) = source_ty
183 .map(|source| optional_type_name(source, control))
184 .transpose()?
185 .flatten()
186 {
187 if let Some(text) =
188 format_regtype_value_with_control(value, &source_ty, engine, control)?
189 {
190 let (text, memory) = text.into_parts();
191 return Ok(control.finish(Value::Str(text), memory)?);
192 }
193 }
194 }
195 if let (Some(engine), Value::Str(name) | Value::FixedChar(name)) = (engine, value) {
196 let oid = resolve_regobject_input(name, target_ty, target_column_type, engine, control)?;
197 if let Some(oid) = oid {
198 return Ok(control.finish(Value::Int(oid), control.empty_reservation())?);
199 }
200 }
201 cast_value_from_with_control(value, target_ty, source_ty, control)
202}
203
204fn resolve_regobject_input(
205 name: &str,
206 target_ty: &str,
207 target_column_type: Option<&ColumnType>,
208 engine: &dyn EngineHook,
209 control: &ProductionControl<'_>,
210) -> Result<Option<i64>> {
211 enum ObjectKind {
212 Relation,
213 Routine,
214 Role,
215 Namespace,
216 Type,
217 }
218 let kind = if target_ty.eq_ignore_ascii_case("regclass") {
219 ObjectKind::Relation
220 } else if target_ty.eq_ignore_ascii_case("regprocedure") {
221 ObjectKind::Routine
222 } else if target_ty.eq_ignore_ascii_case("regrole") {
223 ObjectKind::Role
224 } else if matches!(target_column_type, Some(ColumnType::Regnamespace)) {
225 ObjectKind::Namespace
226 } else if matches!(target_column_type, Some(ColumnType::Regtype)) {
227 ObjectKind::Type
228 } else {
229 return Ok(None);
230 };
231 let oid = match kind {
232 ObjectKind::Relation => engine.resolve_regclass_input(name)?,
233 ObjectKind::Routine => engine
234 .resolve_regprocedure(name)
235 .map_err(SQLError::Internal)?,
236 ObjectKind::Role => engine.resolve_regrole(name)?,
237 ObjectKind::Namespace => engine.resolve_regnamespace(name)?,
238 ObjectKind::Type => engine.resolve_regtype_input(name)?,
239 };
240 control.check()?;
241 if oid.is_some() || matches!(kind, ObjectKind::Type) {
242 return Ok(oid);
243 }
244 let (sqlstate, message) = match kind {
245 ObjectKind::Relation => ("42P01", format!("relation \"{name}\" does not exist")),
246 ObjectKind::Routine => ("42883", format!("function {name} does not exist")),
247 ObjectKind::Role => ("42704", format!("role \"{name}\" does not exist")),
248 ObjectKind::Namespace => ("3F000", format!("schema \"{name}\" does not exist")),
249 ObjectKind::Type => unreachable!("unresolved regtype uses ordinary conversion"),
250 };
251 Err(SQLError::Routine {
252 sqlstate: sqlstate.into(),
253 message,
254 })
255}
256
257fn requires_catalog_array_cast(ty: &ColumnType) -> bool {
258 match ty {
259 ColumnType::Domain { .. } | ColumnType::Regtype => true,
260 ColumnType::Array(element) => requires_catalog_array_cast(element),
261 _ => false,
262 }
263}
264
265fn cast_catalog_array(
266 value: &Value,
267 source: Option<&str>,
268 target: &ColumnType,
269 engine: &dyn EngineHook,
270 control: &ProductionControl<'_>,
271) -> Result<Produced<Value>> {
272 if matches!(value, Value::Null) {
273 return Ok(control.finish(Value::Null, control.empty_reservation())?);
274 }
275 let source_element = source.map(|name| name.trim_end_matches("[]"));
276 let target_element = array_leaf_type(target).sql_name_with_control(control)?;
277 let target_name = target.sql_name_with_control(control)?;
278 cast_array(
279 value,
280 source_element,
281 &target_element,
282 &target_name,
283 Some(engine),
284 control,
285 )
286}
287
288fn cast_array(
289 value: &Value,
290 source: Option<&str>,
291 target_element: &str,
292 target_name: &str,
293 engine: Option<&dyn EngineHook>,
294 control: &ProductionControl<'_>,
295) -> Result<Produced<Value>> {
296 let parsed;
297 let array = match value {
298 Value::Array(array) => array,
299 Value::Str(text) => {
300 parsed = parse_pg_array_literal_with_control(text, control)?;
301 &parsed
302 }
303 other => {
304 return Err(SQLError::TypeMismatch(format!(
305 "CAST AS {target_name}: expected array, got {other:?}"
306 )))
307 }
308 };
309 let elements = cast_array_elements(array.elements(), source, target_element, engine, control)?;
310 let array = rebuild_array(array, elements, control)?
311 .ok_or_else(|| SQLError::TypeMismatch("array dimensions changed during cast".into()))?;
312 let (array, memory) = array.into_parts();
313 Ok(control.finish(Value::Array(array), memory)?)
314}
315
316fn cast_array_elements(
317 values: &[Value],
318 source: Option<&str>,
319 target: &str,
320 engine: Option<&dyn EngineHook>,
321 control: &ProductionControl<'_>,
322) -> Result<Produced<Vec<Value>>> {
323 let mut output = ProductionVec::new(*control);
324 output.reserve(values.len())?;
325 for value in values {
326 let value = match value {
327 Value::List(values) => {
328 let (values, memory) =
329 cast_array_elements(values, source, target, engine, control)?.into_parts();
330 control.finish(Value::List(values), memory)?
331 }
332 value => cast_value_with_type_resolution_with_control(
333 value, source, target, engine, control,
334 )?,
335 };
336 output.push_produced(value)?;
337 }
338 Ok(output.finish()?)
339}
340
341pub(super) fn rebuild_array(
342 source: &ArrayValue,
343 elements: Produced<Vec<Value>>,
344 control: &ProductionControl<'_>,
345) -> Result<Option<Produced<ArrayValue>>> {
346 let mut bounds = ProductionVec::new(*control);
347 bounds.reserve(source.lower_bounds().len())?;
348 for bound in source.lower_bounds() {
349 bounds.push_copy(*bound)?;
350 }
351 Ok(ArrayValue::with_lower_bounds_with_control(
352 elements,
353 bounds.finish()?,
354 control,
355 )?)
356}
357
358#[cfg(test)]
359mod tests;