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_elements_with_control, format_regtype_value_with_control, EngineHook};
16use crate::{
17 ast::ColumnType,
18 error::{Result, SQLError},
19};
20
21mod datestyle;
22mod timezone;
23
24pub trait CatalogInputFunctions {
26 fn read_unknown_input(&self, text: &str, target: &ColumnType) -> Result<Value>;
27}
28
29pub(crate) fn requires_domain_array_input(target: &ColumnType) -> bool {
30 matches!(target, ColumnType::Array(element) if matches!(array_leaf_type(element), ColumnType::Domain { .. }))
31}
32
33pub(crate) fn requires_catalog_constant_input(target: &ColumnType) -> bool {
35 requires_domain_array_input(target)
36 || match target {
37 ColumnType::Composite(_) => true,
38 ColumnType::Array(element) | ColumnType::Domain { base: element, .. } => {
39 requires_catalog_constant_input(element)
40 }
41 _ => false,
42 }
43}
44
45pub fn read_catalog_input(
47 text: &str,
48 target: &ColumnType,
49 engine: &dyn EngineHook,
50) -> Result<Value> {
51 if requires_domain_array_input(target) {
52 return read_catalog_array_input(text, target, engine);
53 }
54 if !requires_catalog_constant_input(target) {
55 return Err(SQLError::Internal(
56 "catalog input requires a composite or domain array".into(),
57 ));
58 }
59 super::cast_value_with_type_resolution(
60 &Value::Str(text.into()),
61 Some("unknown"),
62 &target.catalog_name(),
63 Some(engine),
64 )
65}
66
67pub fn read_catalog_array_input(
69 text: &str,
70 target: &ColumnType,
71 engine: &dyn EngineHook,
72) -> Result<Value> {
73 if !requires_domain_array_input(target) {
74 return Err(SQLError::Internal(
75 "catalog array input requires domain elements".into(),
76 ));
77 }
78 let control = ProductionControl::uncontrolled();
79 let parsed = parse_pg_array_literal_with_control(text, &control)?
80 .into_uncontrolled()
81 .map_err(|_| SQLError::Internal("ordinary catalog array input owner".into()))?;
82 cast_catalog_array(&Value::Array(parsed), None, target, engine, &control)?
83 .into_uncontrolled()
84 .map_err(|_| SQLError::Internal("ordinary catalog array input owner".into()))
85}
86
87#[must_use]
88pub fn coercion_type_name(ty: &ColumnType) -> String {
89 coercion_type_name_with_control(ty, &ProductionControl::uncontrolled())
90 .expect("ordinary coercion type name")
91 .into_uncontrolled()
92 .expect("ordinary coercion type name owner")
93}
94
95fn coercion_type_name_with_control(
96 ty: &ColumnType,
97 control: &ProductionControl<'_>,
98) -> Result<Produced<String>> {
99 control.check()?;
100 match ty {
101 ColumnType::Domain { base, .. } => coercion_type_name_with_control(base, control),
102 ColumnType::Array(element) => {
103 let element = coercion_type_name_with_control(element, control)?;
104 let mut name = ProductionString::new(*control);
105 name.push_str(&element)?;
106 name.push_str("[]")?;
107 Ok(name.finish()?)
108 }
109 _ => Ok(ty.sql_name_with_control(control)?),
110 }
111}
112
113fn is_string_type(ty: &ColumnType) -> bool {
115 matches!(
116 ty,
117 ColumnType::Text | ColumnType::Name | ColumnType::Varchar(_) | ColumnType::Character(_)
118 )
119}
120
121fn regrole_array_type(ty: &ColumnType) -> bool {
122 match ty {
123 ColumnType::Array(element) => {
124 matches!(element.as_ref(), ColumnType::Regrole) || regrole_array_type(element)
125 }
126 _ => false,
127 }
128}
129
130fn array_leaf_type(ty: &ColumnType) -> &ColumnType {
131 match ty {
132 ColumnType::Array(element) => array_leaf_type(element),
133 _ => ty,
134 }
135}
136
137fn optional_type_name(
138 name: &str,
139 control: &ProductionControl<'_>,
140) -> Result<Option<Produced<ColumnType>>> {
141 match ColumnType::from_sql_name_with_control(name, control) {
142 Ok(ty) => Ok(Some(ty)),
143 Err(error) if matches!(error.sqlstate(), Some("53200" | "57014")) => Err(error),
144 Err(_) => Ok(None),
145 }
146}
147
148pub fn cast_value_with_type_resolution(
150 value: &Value,
151 source_ty: Option<&str>,
152 target_ty: &str,
153 engine: Option<&dyn EngineHook>,
154) -> Result<Value> {
155 cast_value_with_type_resolution_with_control(
156 value,
157 source_ty,
158 target_ty,
159 engine,
160 &ProductionControl::uncontrolled(),
161 )?
162 .into_uncontrolled()
163 .map_err(|_| SQLError::Internal("ordinary catalog cast owner".into()))
164}
165
166fn output_for_cast<'a>(
168 engine: &dyn EngineHook,
169 value: &Value,
170 source_ty: Option<&'a str>,
171 resolved_source_type: Option<&ColumnType>,
172 target: &ColumnType,
173) -> Result<Option<(Value, Option<&'a str>)>> {
174 if let Value::Enum(label) = value {
175 let output =
176 super::super::enums::enum_output_for_cast(engine.enum_labels(), label, target)?;
177 return Ok(Some((output, Some("text"))));
178 }
179 if let (Value::Record(_), Some(source @ ColumnType::Composite(_))) =
181 (value, resolved_source_type)
182 {
183 if is_string_type(target) {
184 let text = crate::result::format_postgres_text(value, source, Some(engine))?;
185 return Ok(Some((Value::Str(text), Some("text"))));
186 }
187 }
188 if matches!(value, Value::Record(_) | Value::Row(_))
189 && is_string_type(target)
190 && super::super::enums::contains_enum_carrier(value)
191 {
192 let rendered = super::super::enums::render_enum_labels(engine.enum_labels(), value)?;
193 return Ok(Some((rendered, source_ty)));
194 }
195 Ok(None)
196}
197
198fn enum_array_output(
200 value: &Value,
201 source_ty: Option<&str>,
202 target_ty: &str,
203 target: &ColumnType,
204 engine: &dyn EngineHook,
205 control: &ProductionControl<'_>,
206) -> Result<Option<Produced<Value>>> {
207 if !matches!(value, Value::Array(_))
208 || !is_string_type(target)
209 || !super::super::enums::contains_enum_carrier(value)
210 {
211 return Ok(None);
212 }
213 let text_array = ColumnType::Array(Box::new(ColumnType::Text));
214 let labels = cast_catalog_array(value, source_ty, &text_array, engine, control)?;
215 cast_value_from_with_control(&labels, target_ty, Some("text[]"), control).map(Some)
216}
217
218pub fn cast_value_with_type_resolution_with_control(
220 value: &Value,
221 source_ty: Option<&str>,
222 target_ty: &str,
223 engine: Option<&dyn EngineHook>,
224 control: &ProductionControl<'_>,
225) -> Result<Produced<Value>> {
226 control.check()?;
227 let resolved_target = engine
228 .map(|engine| engine.resolve_type_name(target_ty))
229 .transpose()
230 .map_err(SQLError::Internal)?
231 .flatten()
232 .map(|ty| ty.retain_external_with_control(control))
233 .transpose()?;
234 control.check()?;
235 let resolved_source_type = match (engine, source_ty) {
236 (Some(engine), Some(source)) => engine
237 .resolve_type_name(source)
238 .map_err(SQLError::Internal)?,
239 _ => None,
240 };
241 let parsed_target = if resolved_target.is_some() {
242 None
243 } else {
244 optional_type_name(target_ty, control)?
245 };
246 let target_column_type = resolved_target.as_deref().or(parsed_target.as_deref());
247 let _date_order = datestyle::input_scope(value, target_column_type, engine, control)?;
248 if let (Some(engine), Some(target)) = (engine, resolved_target.as_deref()) {
249 if let Some(value) = engine.cast_domain(value, source_ty, target)? {
250 return Ok(control.retain_external_value(value)?);
251 }
252 if let Some(value) = super::super::enums::cast_to_enum(
253 engine.enum_labels(),
254 value,
255 resolved_source_type.as_ref(),
256 target,
257 )? {
258 return Ok(control.retain_external_value(value)?);
259 }
260 if let Some(value) = super::super::composites::cast_to_composite(
261 engine,
262 value,
263 resolved_source_type.as_ref(),
264 target,
265 )? {
266 return Ok(control.retain_external_value(value)?);
267 }
268 if let Some(output) =
269 enum_array_output(value, source_ty, target_ty, target, engine, control)?
270 {
271 return Ok(output);
272 }
273 if let Some((output, output_source)) = output_for_cast(
274 engine,
275 value,
276 source_ty,
277 resolved_source_type.as_ref(),
278 target,
279 )? {
280 return cast_value_with_type_resolution_with_control(
281 &output,
282 output_source,
283 target_ty,
284 Some(engine),
285 control,
286 );
287 }
288 control.check()?;
289 if matches!(target, ColumnType::Array(_))
290 && (requires_catalog_array_cast(target)
291 || super::super::enums::contains_enum_carrier(value)
292 || resolved_source_type
293 .as_ref()
294 .is_some_and(super::super::enums::is_enum_bearing))
295 {
296 return cast_catalog_array(value, source_ty, target, engine, control);
297 }
298 }
299 let resolved_source = match (engine, source_ty) {
300 (Some(engine), Some(source_ty)) => engine
301 .resolve_type_name(source_ty)
302 .map_err(SQLError::Internal)?
303 .map(|ty| {
304 let ty = ty.retain_external_with_control(control)?;
305 coercion_type_name_with_control(&ty, control)
306 })
307 .transpose()?,
308 _ => None,
309 };
310 let source_ty = resolved_source.as_deref().map(String::as_str).or(source_ty);
311 let target_name = resolved_target
312 .as_ref()
313 .map(|ty| coercion_type_name_with_control(ty, control))
314 .transpose()?;
315 let target_ty = target_name.as_ref().map_or(target_ty, |name| name.as_str());
316 cast_resolved_value(
317 value,
318 source_ty,
319 target_ty,
320 target_column_type,
321 engine,
322 control,
323 )
324}
325
326fn cast_resolved_value(
327 value: &Value,
328 source_ty: Option<&str>,
329 target_ty: &str,
330 target_column_type: Option<&ColumnType>,
331 engine: Option<&dyn EngineHook>,
332 control: &ProductionControl<'_>,
333) -> Result<Produced<Value>> {
334 if target_column_type.is_some_and(regrole_array_type) {
335 let source_column_type = source_ty
336 .map(|name| optional_type_name(name, control))
337 .transpose()?
338 .flatten();
339 let source_name = source_column_type
340 .as_deref()
341 .map(array_leaf_type)
342 .map(|ty| ty.sql_name_with_control(control))
343 .transpose()?;
344 return cast_array(
345 value,
346 source_name.as_ref().map(|name| name.as_str()),
347 "regrole",
348 "regrole[]",
349 engine,
350 control,
351 );
352 }
353 if let Some(ColumnType::Array(element)) = target_column_type {
355 if is_string_type(element) {
356 if let Some(source_ty) = source_ty
357 .map(|source| optional_type_name(source, control))
358 .transpose()?
359 .flatten()
360 {
361 if let Some(elements) =
362 format_regtype_elements_with_control(value, &source_ty, engine, control)?
363 {
364 let (elements, _memory) = elements.into_parts();
365 return cast_value_from_with_control(
366 &Value::Array(elements),
367 target_ty,
368 Some("text[]"),
369 control,
370 );
371 }
372 }
373 }
374 }
375 let text_target = target_ty.eq_ignore_ascii_case("text");
376 if text_target || target_column_type.is_some_and(is_string_type) {
377 if let Some(source_ty) = source_ty
378 .map(|source| optional_type_name(source, control))
379 .transpose()?
380 .flatten()
381 {
382 if let Some(text) =
383 format_regtype_value_with_control(value, &source_ty, engine, control)?
384 {
385 let (text, memory) = text.into_parts();
386 if text_target {
387 return Ok(control.finish(Value::Str(text), memory)?);
388 }
389 return cast_value_from_with_control(
390 &Value::Str(text),
391 target_ty,
392 Some("text"),
393 control,
394 );
395 }
396 }
397 }
398 if let (Some(engine), Value::Str(name) | Value::FixedChar(name)) = (engine, value) {
399 let oid = resolve_regobject_input(name, target_ty, target_column_type, engine, control)?;
400 if let Some(oid) = oid {
401 return Ok(control.finish(Value::Int(oid), control.empty_reservation())?);
402 }
403 }
404 let zoned = timezone::cast_local_timestamp(value, target_column_type, engine, control)?;
405 cast_value_from_with_control(
406 zoned.as_ref().unwrap_or(value),
407 target_ty,
408 source_ty,
409 control,
410 )
411}
412
413fn resolve_regobject_input(
414 name: &str,
415 target_ty: &str,
416 target_column_type: Option<&ColumnType>,
417 engine: &dyn EngineHook,
418 control: &ProductionControl<'_>,
419) -> Result<Option<i64>> {
420 enum ObjectKind {
421 Relation,
422 RoutineName,
423 Routine,
424 Role,
425 Namespace,
426 Type,
427 }
428 let kind = if target_ty.eq_ignore_ascii_case("regclass") {
429 ObjectKind::Relation
430 } else if matches!(target_column_type, Some(ColumnType::Regproc))
431 || target_ty.eq_ignore_ascii_case("regproc")
432 {
433 ObjectKind::RoutineName
434 } else if target_ty.eq_ignore_ascii_case("regprocedure") {
435 ObjectKind::Routine
436 } else if target_ty.eq_ignore_ascii_case("regrole") {
437 ObjectKind::Role
438 } else if matches!(target_column_type, Some(ColumnType::Regnamespace)) {
439 ObjectKind::Namespace
440 } else if matches!(target_column_type, Some(ColumnType::Regtype)) {
441 ObjectKind::Type
442 } else {
443 return Ok(None);
444 };
445 let oid = match kind {
446 ObjectKind::Relation => engine.resolve_regclass_input(name)?,
447 ObjectKind::RoutineName => return engine.resolve_regproc(name),
449 ObjectKind::Routine => engine.resolve_regprocedure_input(name)?,
450 ObjectKind::Role => engine.resolve_regrole(name)?,
451 ObjectKind::Namespace => engine.resolve_regnamespace(name)?,
452 ObjectKind::Type => engine.resolve_regtype_input(name)?,
453 };
454 control.check()?;
455 if oid.is_some() || matches!(kind, ObjectKind::Type) {
456 return Ok(oid);
457 }
458 let (sqlstate, message) = match kind {
459 ObjectKind::Relation => ("42P01", format!("relation \"{name}\" does not exist")),
460 ObjectKind::Routine => ("42883", format!("function \"{name}\" does not exist")),
461 ObjectKind::Role => ("42704", format!("role \"{name}\" does not exist")),
462 ObjectKind::Namespace => ("3F000", format!("schema \"{name}\" does not exist")),
463 ObjectKind::Type | ObjectKind::RoutineName => {
464 unreachable!("regtype and regproc input return before reporting a missing object")
465 }
466 };
467 Err(SQLError::Routine {
468 sqlstate: sqlstate.into(),
469 message,
470 })
471}
472
473fn requires_catalog_array_cast(ty: &ColumnType) -> bool {
475 match ty {
476 ColumnType::Domain { .. }
477 | ColumnType::Regtype
478 | ColumnType::Regclass
479 | ColumnType::Regproc
480 | ColumnType::Regprocedure
481 | ColumnType::Regnamespace
482 | ColumnType::Enum(_)
483 | ColumnType::Composite(_)
484 | ColumnType::TimestampTz
485 | ColumnType::TimestampTzPrecision(_) => true,
486 ColumnType::Array(element) => requires_catalog_array_cast(element),
487 _ => false,
488 }
489}
490
491fn cast_catalog_array(
492 value: &Value,
493 source: Option<&str>,
494 target: &ColumnType,
495 engine: &dyn EngineHook,
496 control: &ProductionControl<'_>,
497) -> Result<Produced<Value>> {
498 if matches!(value, Value::Null) {
499 return Ok(control.finish(Value::Null, control.empty_reservation())?);
500 }
501 let source_element = source.map(|name| name.trim_end_matches("[]"));
502 let leaf = array_leaf_type(target);
503 let target_element = control.copy_text(&leaf.catalog_name())?;
504 let target_name = target.sql_name_with_control(control)?;
505 cast_array(
506 value,
507 source_element,
508 &target_element,
509 &target_name,
510 Some(engine),
511 control,
512 )
513}
514
515fn cast_array(
516 value: &Value,
517 source: Option<&str>,
518 target_element: &str,
519 target_name: &str,
520 engine: Option<&dyn EngineHook>,
521 control: &ProductionControl<'_>,
522) -> Result<Produced<Value>> {
523 let parsed;
524 let array = match value {
525 Value::Array(array) => array,
526 Value::Str(text) => {
527 parsed = parse_pg_array_literal_with_control(text, control)?;
528 &parsed
529 }
530 other => {
531 return Err(SQLError::TypeMismatch(format!(
532 "CAST AS {target_name}: expected array, got {other:?}"
533 )))
534 }
535 };
536 let elements = cast_array_elements(array.elements(), source, target_element, engine, control)?;
537 let array = rebuild_array(array, elements, control)?
538 .ok_or_else(|| SQLError::TypeMismatch("array dimensions changed during cast".into()))?;
539 let (array, memory) = array.into_parts();
540 Ok(control.finish(Value::Array(array), memory)?)
541}
542
543fn cast_array_elements(
544 values: &[Value],
545 source: Option<&str>,
546 target: &str,
547 engine: Option<&dyn EngineHook>,
548 control: &ProductionControl<'_>,
549) -> Result<Produced<Vec<Value>>> {
550 let mut output = ProductionVec::new(*control);
551 output.reserve(values.len())?;
552 for value in values {
553 let value = match value {
554 Value::List(values) => {
555 let (values, memory) =
556 cast_array_elements(values, source, target, engine, control)?.into_parts();
557 control.finish(Value::List(values), memory)?
558 }
559 value => cast_value_with_type_resolution_with_control(
560 value, source, target, engine, control,
561 )?,
562 };
563 output.push_produced(value)?;
564 }
565 Ok(output.finish()?)
566}
567
568pub(super) fn rebuild_array(
569 source: &ArrayValue,
570 elements: Produced<Vec<Value>>,
571 control: &ProductionControl<'_>,
572) -> Result<Option<Produced<ArrayValue>>> {
573 let mut bounds = ProductionVec::new(*control);
574 bounds.reserve(source.lower_bounds().len())?;
575 for bound in source.lower_bounds() {
576 bounds.push_copy(*bound)?;
577 }
578 Ok(ArrayValue::with_lower_bounds_with_control(
579 elements,
580 bounds.finish()?,
581 control,
582 )?)
583}
584
585#[cfg(test)]
586mod tests;