1use crate::ast::ColumnType;
8use crate::{SQLError, SQLParam};
9use uqa_core::{
10 memory::{Produced, ProductionControl},
11 Value,
12};
13
14use crate::schema::ScalarTypeSchema;
15use crate::{scalar_call_arguments, RowSchema, ScalarExpr};
16
17use super::FunctionTypeResolver;
18
19#[doc(hidden)]
21pub type FunctionCallArgumentSignature = (Vec<Option<String>>, Vec<Option<ColumnType>>, bool);
22
23#[doc(hidden)]
25pub fn function_call_argument_signature(
26 arguments: &[ScalarExpr],
27 schema: &dyn ScalarTypeSchema,
28 params: &[SQLParam],
29 resolver: Option<&dyn FunctionTypeResolver>,
30) -> Result<FunctionCallArgumentSignature, SQLError> {
31 let call_arguments = scalar_call_arguments(arguments)?;
32 let explicit_variadic = call_arguments
33 .iter()
34 .any(|argument| argument.explicit_variadic);
35 let mut argument_names = Vec::with_capacity(call_arguments.len());
36 let mut argument_types = Vec::with_capacity(call_arguments.len());
37 for argument in call_arguments {
38 argument_names.push(argument.name.map(str::to_string));
39 let argument_type =
40 common_context_expression_type(argument.value, schema, params, resolver)?;
41 argument_types.push(effective_overload_argument_type_with_params(
42 argument.value,
43 argument_type,
44 params,
45 ));
46 }
47 Ok((argument_names, argument_types, explicit_variadic))
48}
49
50pub(super) fn local_routine_name(name: &str) -> String {
51 let lower = name.to_ascii_lowercase();
52 lower
53 .strip_prefix("pg_catalog.")
54 .unwrap_or(&lower)
55 .to_string()
56}
57
58pub(super) fn numeric_type() -> ColumnType {
59 ColumnType::Numeric {
60 precision: None,
61 scale: None,
62 }
63}
64
65pub(super) fn base_type(mut ty: &ColumnType) -> &ColumnType {
66 while let ColumnType::Domain { base, .. } = ty {
67 ty = base;
68 }
69 ty.without_temporal_modifiers()
70}
71
72pub(crate) fn literal_input_type_with_control(
74 mut ty: &ColumnType,
75 control: &ProductionControl<'_>,
76) -> Result<Produced<ColumnType>, SQLError> {
77 fn input_type(
78 ty: &ColumnType,
79 control: &ProductionControl<'_>,
80 ) -> Result<Produced<ColumnType>, SQLError> {
81 Ok(match ty {
82 ColumnType::IntervalWithFields { .. } | ColumnType::Domain { .. } => {
83 ty.clone_with_control(control)?
84 }
85 ColumnType::Array(element) => {
86 ColumnType::array_with_control(input_type(element, control)?, control)?
87 }
88 _ => ty.without_type_modifiers_with_control(control)?,
89 })
90 }
91 while let ColumnType::Domain { base, .. } = ty {
92 ty = base;
93 }
94 input_type(ty, control)
95}
96
97pub(crate) fn array_element_type(ty: &ColumnType) -> Option<&ColumnType> {
98 match base_type(ty) {
99 ColumnType::Array(element) => {
100 let mut element = element.as_ref();
101 while let ColumnType::Array(inner) = element {
102 element = inner;
103 }
104 Some(element)
105 }
106 ColumnType::Int2Vector => Some(&ColumnType::SmallInteger),
107 ColumnType::OidVector => Some(&ColumnType::Oid),
108 _ => None,
109 }
110}
111
112pub fn values_column_types(
113 rows: &[Vec<ScalarExpr>],
114 params: &[SQLParam],
115) -> Result<Vec<Option<ColumnType>>, SQLError> {
116 let width = rows.first().map_or(0, Vec::len);
117 let empty = RowSchema::default();
118 let mut types = vec![None; width];
119 for row in rows {
120 if row.len() != width {
121 return Err(SQLError::TypeMismatch(
122 "VALUES lists must all be the same length".into(),
123 ));
124 }
125 for (position, expression) in row.iter().enumerate() {
126 types[position] = merge_optional_types_in(
127 CommonTypeContext::Values,
128 types[position].take(),
129 common_context_expression_type(expression, &empty, params, None)?,
130 )?;
131 }
132 }
133 Ok(types
134 .into_iter()
135 .map(|ty| ty.or(Some(ColumnType::Text)))
136 .collect())
137}
138
139pub fn common_context_expression_type(
141 expression: &ScalarExpr,
142 schema: &dyn ScalarTypeSchema,
143 params: &[SQLParam],
144 resolver: Option<&dyn FunctionTypeResolver>,
145) -> Result<Option<ColumnType>, SQLError> {
146 common_context_expression_type_with_control(
147 expression,
148 schema,
149 params,
150 resolver,
151 &ProductionControl::uncontrolled(),
152 )
153 .map(|ty| {
154 ty.map(|ty| {
155 ty.into_uncontrolled()
156 .expect("ordinary common-context inference has no reservation")
157 })
158 })
159}
160
161pub(super) fn common_context_expression_type_with_control(
162 expression: &ScalarExpr,
163 schema: &dyn ScalarTypeSchema,
164 params: &[SQLParam],
165 resolver: Option<&dyn FunctionTypeResolver>,
166 control: &ProductionControl<'_>,
167) -> Result<Option<Produced<ColumnType>>, SQLError> {
168 control.check()?;
169 if matches!(expression, ScalarExpr::Literal(Value::Str(_) | Value::Null)) {
170 return Ok(None);
171 }
172 super::scalar_type_inner_with_control(expression, schema, params, resolver, control)
173}
174
175#[doc(hidden)]
177pub fn effective_overload_argument_type(
178 expression: &ScalarExpr,
179 resolved: Option<ColumnType>,
180) -> Option<ColumnType> {
181 if effective_overload_argument_type_ref_with_params(expression, resolved.as_ref(), &[])
182 .is_some()
183 {
184 resolved
185 } else {
186 None
187 }
188}
189
190#[doc(hidden)]
192pub fn effective_overload_argument_type_with_params(
193 expression: &ScalarExpr,
194 resolved: Option<ColumnType>,
195 params: &[SQLParam],
196) -> Option<ColumnType> {
197 if effective_overload_argument_type_ref_with_params(expression, resolved.as_ref(), params)
198 .is_some()
199 {
200 resolved
201 } else {
202 None
203 }
204}
205
206pub(super) fn effective_overload_argument_type_ref_with_params<'a>(
208 expression: &ScalarExpr,
209 resolved: Option<&'a ColumnType>,
210 params: &[SQLParam],
211) -> Option<&'a ColumnType> {
212 if let ScalarExpr::Param(index) = expression {
213 if index
214 .checked_sub(1)
215 .and_then(|index| params.get(index))
216 .is_some_and(|parameter| parameter.declared_scalar_type().is_some())
217 {
218 return resolved;
219 }
220 }
221 if matches!(expression, ScalarExpr::Literal(Value::Str(_) | Value::Null))
222 || matches!(expression, ScalarExpr::Param(_)) && matches!(resolved, Some(ColumnType::Text))
223 {
224 None
225 } else {
226 resolved
227 }
228}
229
230pub(super) fn parameter_type_with_control(
231 parameter: &SQLParam,
232 control: &ProductionControl<'_>,
233) -> Result<Option<Produced<ColumnType>>, SQLError> {
234 control.check()?;
235 let scalar = match parameter {
236 SQLParam::Scalar(value) => return value_type_with_control(value, control),
237 SQLParam::TypedScalar { ty, .. } | SQLParam::DeferredError { ty, .. } => {
238 return Ok(Some(ty.clone_with_control(control)?))
239 }
240 SQLParam::Vector(values) => u32::try_from(values.len()).ok().map(ColumnType::Vector),
241 SQLParam::Tensor(values) => values
242 .first()
243 .and_then(|values| u32::try_from(values.len()).ok())
244 .map(ColumnType::Tensor),
245 };
246 scalar
247 .map(|ty| {
248 control
249 .finish(ty, control.empty_reservation())
250 .map_err(Into::into)
251 })
252 .transpose()
253}
254
255pub(crate) fn value_type(value: &Value) -> Option<ColumnType> {
256 value_type_with_control(value, &ProductionControl::uncontrolled())
257 .expect("ordinary value type inference cannot be cancelled or limited")
258 .map(|value| {
259 value
260 .into_uncontrolled()
261 .expect("ordinary value type has no reservation")
262 })
263}
264
265pub(crate) fn value_type_with_control(
266 value: &Value,
267 control: &ProductionControl<'_>,
268) -> Result<Option<Produced<ColumnType>>, SQLError> {
269 control.check()?;
270 let scalar = match value {
271 Value::Null | Value::Map(_) | Value::Enum(_) => None,
273 Value::Void => Some(ColumnType::Void),
274 Value::Row(_) | Value::Record(_) => Some(ColumnType::Record),
275 Value::Bool(_) => Some(ColumnType::Boolean),
276 Value::Int(value) if i32::try_from(*value).is_ok() => Some(ColumnType::Integer),
277 Value::Int(_) => Some(ColumnType::BigInteger),
278 Value::Float(_) => Some(ColumnType::DoublePrecision),
279 Value::Decimal(_) => Some(numeric_type()),
280 Value::Str(_) => Some(ColumnType::Text),
281 Value::FixedChar(value) => {
282 let mut count = 0_usize;
283 for _ in value.chars() {
284 control.check()?;
285 count += 1;
286 }
287 u32::try_from(count).ok().map(ColumnType::Character)
288 }
289 Value::Bytes(_) => Some(ColumnType::Bytea),
290 Value::Temporal(value) => Some(match value {
291 uqa_core::TemporalValue::Date { .. } => ColumnType::Date,
292 uqa_core::TemporalValue::Time { .. } => ColumnType::Time,
293 uqa_core::TemporalValue::TimeTz { .. } => ColumnType::TimeTz,
294 uqa_core::TemporalValue::Timestamp { .. } => ColumnType::Timestamp,
295 uqa_core::TemporalValue::TimestampTz { .. } => ColumnType::TimestampTz,
296 uqa_core::TemporalValue::Interval { .. } => ColumnType::Interval,
297 }),
298 Value::Json(_) => Some(ColumnType::Json),
299 Value::JsonB(_) => Some(ColumnType::JsonB),
300 Value::LegacyVector(vector) => Some(match vector.kind() {
301 uqa_core::LegacyVectorKind::SmallInteger => ColumnType::Int2Vector,
302 uqa_core::LegacyVectorKind::Oid => ColumnType::OidVector,
303 }),
304 Value::Array(array) => {
305 let mut element = None;
306 if !merge_array_element_types(array.elements(), &mut element, control)? {
307 return Ok(None);
308 }
309 return element
310 .map(|element| ColumnType::array_with_control(element, control).map_err(Into::into))
311 .transpose();
312 }
313 Value::List(values) => {
314 let mut element = None;
315 for value in values {
316 let next = value_type_with_control(value, control)?;
317 match merge_value_types(element, next, control) {
318 Ok(merged) => element = merged,
319 Err(error) if matches!(error.sqlstate(), Some("53200" | "57014")) => {
320 return Err(error)
321 }
322 Err(_) => return Ok(None),
323 }
324 }
325 return element
326 .map(|element| ColumnType::array_with_control(element, control).map_err(Into::into))
327 .transpose();
328 }
329 };
330 scalar
331 .map(|ty| {
332 control
333 .finish(ty, control.empty_reservation())
334 .map_err(Into::into)
335 })
336 .transpose()
337}
338
339fn merge_array_element_types(
340 values: &[Value],
341 element: &mut Option<Produced<ColumnType>>,
342 control: &ProductionControl<'_>,
343) -> Result<bool, SQLError> {
344 for value in values {
345 control.check()?;
346 if let Value::List(nested) = value {
347 if !merge_array_element_types(nested, element, control)? {
348 return Ok(false);
349 }
350 } else {
351 match merge_value_types(
352 element.take(),
353 value_type_with_control(value, control)?,
354 control,
355 ) {
356 Ok(merged) => *element = merged,
357 Err(error) if matches!(error.sqlstate(), Some("53200" | "57014")) => {
358 return Err(error)
359 }
360 Err(_) => return Ok(false),
361 }
362 }
363 }
364 Ok(true)
365}
366
367pub(super) fn merge_value_types(
368 left: Option<Produced<ColumnType>>,
369 right: Option<Produced<ColumnType>>,
370 control: &ProductionControl<'_>,
371) -> Result<Option<Produced<ColumnType>>, SQLError> {
372 control.check()?;
373 match (left, right) {
374 (None, other) | (other, None) => Ok(other),
375 (Some(left), Some(right)) if *left == *right => Ok(Some(left)),
376 (Some(left), Some(right)) => common_type_with_control(&left, &right, control).map(Some),
377 }
378}
379
380pub(super) fn read_unknown_literals<'a>(
382 expressions: impl IntoIterator<Item = &'a ScalarExpr>,
383 target: &ColumnType,
384) -> Result<(), SQLError> {
385 if super::catalog_input_type(target) {
386 return Ok(());
387 }
388 let target = base_type(target).without_type_modifiers().catalog_name();
390 for expression in expressions {
391 if let ScalarExpr::Literal(Value::Str(text)) = expression {
392 crate::expr::cast_value_from(&Value::Str(text.clone()), &target, None)?;
393 }
394 }
395 Ok(())
396}
397
398pub(super) fn merge_optional_types_in(
400 context: CommonTypeContext,
401 left: Option<ColumnType>,
402 right: Option<ColumnType>,
403) -> Result<Option<ColumnType>, SQLError> {
404 match (left, right) {
405 (None, other) | (other, None) => Ok(other),
406 (Some(left), Some(right)) => common_type_in(context, &left, &right).map(Some),
407 }
408}
409
410pub(super) fn merge_value_types_in(
412 context: CommonTypeContext,
413 left: Option<Produced<ColumnType>>,
414 right: Option<Produced<ColumnType>>,
415 control: &ProductionControl<'_>,
416) -> Result<Option<Produced<ColumnType>>, SQLError> {
417 control.check()?;
418 match (left, right) {
419 (None, other) | (other, None) => Ok(other),
420 (Some(left), Some(right)) if *left == *right => Ok(Some(left)),
421 (Some(left), Some(right)) => select_pair_with_control(&left, &right, control)
422 .map(Some)
423 .map_err(|failure| failure.in_context(context)),
424 }
425}
426
427pub fn common_type(left: &ColumnType, right: &ColumnType) -> Result<ColumnType, SQLError> {
428 common_type_with_control(left, right, &ProductionControl::uncontrolled()).map(|value| {
429 value
430 .into_uncontrolled()
431 .expect("ordinary common type has no reservation")
432 })
433}
434
435pub fn common_type_in(
437 context: CommonTypeContext,
438 left: &ColumnType,
439 right: &ColumnType,
440) -> Result<ColumnType, SQLError> {
441 select_pair_with_control(left, right, &ProductionControl::uncontrolled())
442 .map(|value| {
443 value
444 .into_uncontrolled()
445 .expect("ordinary common type has no reservation")
446 })
447 .map_err(|failure| failure.in_context(context))
448}
449
450#[derive(Clone, Copy, Debug, PartialEq, Eq)]
452pub enum CommonTypeContext {
453 Union,
454 Intersect,
455 Except,
456 Values,
457 Case,
458 Coalesce,
459 Array,
460 Greatest,
461 Least,
462 In,
463 JoinUsing,
464 Cycle,
465}
466
467impl CommonTypeContext {
468 pub const fn label(self) -> &'static str {
469 match self {
470 Self::Union => "UNION",
471 Self::Intersect => "INTERSECT",
472 Self::Except => "EXCEPT",
473 Self::Values => "VALUES",
474 Self::Case => "CASE",
475 Self::Coalesce => "COALESCE",
476 Self::Array => "ARRAY",
477 Self::Greatest => "GREATEST",
478 Self::Least => "LEAST",
479 Self::In => "IN",
480 Self::JoinUsing => "JOIN/USING",
481 Self::Cycle => "CYCLE",
482 }
483 }
484
485 pub const fn coercion_label(self) -> &'static str {
487 match self {
488 Self::Case => "CASE/WHEN",
489 other => other.label(),
490 }
491 }
492
493 pub const fn set_operation(kind: crate::ast::SetOpKind) -> Self {
494 match kind {
495 crate::ast::SetOpKind::Union => Self::Union,
496 crate::ast::SetOpKind::Intersect => Self::Intersect,
497 crate::ast::SetOpKind::Except => Self::Except,
498 }
499 }
500
501 pub fn function(name: &str) -> Option<Self> {
503 match local_routine_name(name).as_str() {
504 "coalesce" => Some(Self::Coalesce),
505 "greatest" => Some(Self::Greatest),
506 "least" => Some(Self::Least),
507 _ => None,
508 }
509 }
510}
511
512#[derive(Debug)]
514pub(super) enum CommonTypeFailure {
515 Unmatched(Box<(ColumnType, ColumnType)>),
517 Inconvertible(Box<(ColumnType, ColumnType)>),
519 Failed(Box<SQLError>),
520}
521
522impl CommonTypeFailure {
523 fn failed(error: impl Into<SQLError>) -> Self {
524 Self::Failed(Box::new(error.into()))
525 }
526
527 fn in_context(self, context: CommonTypeContext) -> SQLError {
529 match self {
530 Self::Unmatched(types) => SQLError::Routine {
531 sqlstate: "42804".into(),
532 message: format!(
533 "{} types {} and {} cannot be matched",
534 context.label(),
535 types.0.sql_name(),
536 types.1.sql_name()
537 ),
538 },
539 Self::Inconvertible(types) => SQLError::Routine {
540 sqlstate: "42846".into(),
541 message: format!(
542 "{} could not convert type {} to {}",
543 context.coercion_label(),
544 types.0.sql_name(),
545 types.1.sql_name()
546 ),
547 },
548 Self::Failed(error) => *error,
549 }
550 }
551
552 fn without_context(self, left: &ColumnType, right: &ColumnType) -> SQLError {
554 match self {
555 Self::Failed(error) => *error,
556 Self::Unmatched(_) | Self::Inconvertible(_) => SQLError::TypeMismatch(format!(
557 "types {} and {} cannot be matched",
558 left.sql_name(),
559 right.sql_name()
560 )),
561 }
562 }
563}
564
565pub fn select_common_input_type(
567 types: &[Option<&ColumnType>],
568) -> Result<Option<ColumnType>, SQLError> {
569 select_common_input_type_with_control(types, &ProductionControl::uncontrolled()).map(
570 |selected| {
571 selected.map(|value| {
572 value
573 .into_uncontrolled()
574 .expect("ordinary common type has no reservation")
575 })
576 },
577 )
578}
579
580pub(super) fn select_common_input_type_with_control(
582 types: &[Option<&ColumnType>],
583 control: &ProductionControl<'_>,
584) -> Result<Option<Produced<ColumnType>>, SQLError> {
585 control.check()?;
586 if let Some(Some(first)) = types.first() {
587 if types.iter().all(|ty| ty.is_some_and(|ty| ty == *first)) {
588 return first
589 .clone_with_control(control)
590 .map(Some)
591 .map_err(Into::into);
592 }
593 }
594 let mut selected: Option<Produced<ColumnType>> = None;
595 for ty in types.iter().flatten() {
596 let ty = base_type(ty);
597 selected = Some(match selected {
598 None => ty.clone_with_control(control)?,
599 Some(current) => match common_type_with_control(¤t, ty, control) {
600 Ok(common) => common,
601 Err(error) if matches!(error.sqlstate(), Some("53200" | "57014")) => {
602 return Err(error)
603 }
604 Err(_) => return Ok(None),
605 },
606 });
607 }
608 match selected {
609 Some(selected) => Ok(Some(selected)),
610 None => control
611 .finish(ColumnType::Text, control.empty_reservation())
612 .map(Some)
613 .map_err(Into::into),
614 }
615}
616
617pub(super) fn common_type_with_control(
619 left: &ColumnType,
620 right: &ColumnType,
621 control: &ProductionControl<'_>,
622) -> Result<Produced<ColumnType>, SQLError> {
623 select_pair_with_control(left, right, control)
624 .map_err(|failure| failure.without_context(left, right))
625}
626
627fn select_pair_with_control(
629 left: &ColumnType,
630 right: &ColumnType,
631 control: &ProductionControl<'_>,
632) -> Result<Produced<ColumnType>, CommonTypeFailure> {
633 control.check().map_err(CommonTypeFailure::failed)?;
634 if left == right {
635 return left
636 .clone_with_control(control)
637 .map_err(CommonTypeFailure::failed);
638 }
639 if left != left.without_temporal_modifiers() || right != right.without_temporal_modifiers() {
640 return select_pair_with_control(
641 left.without_temporal_modifiers(),
642 right.without_temporal_modifiers(),
643 control,
644 );
645 }
646 if matches!(left, ColumnType::Domain { .. }) || matches!(right, ColumnType::Domain { .. }) {
647 return select_pair_with_control(base_type(left), base_type(right), control);
648 }
649 let scalar = if let Some(numeric) = common_numeric_type(left, right) {
650 numeric
651 } else if matches!(left, ColumnType::Oid) && is_integral_type(right)
652 || matches!(right, ColumnType::Oid) && is_integral_type(left)
653 {
654 ColumnType::Oid
655 } else if left.is_character_string() && right.is_character_string() {
656 match left {
657 ColumnType::Bpchar | ColumnType::Character(_) => ColumnType::Bpchar,
658 ColumnType::Varchar(_) => ColumnType::Varchar(None),
659 ColumnType::Name => ColumnType::Name,
660 _ => ColumnType::Text,
661 }
662 } else {
663 match (left, right) {
664 (ColumnType::Date, ColumnType::Timestamp)
665 | (ColumnType::Timestamp, ColumnType::Date) => ColumnType::Timestamp,
666 (ColumnType::Date | ColumnType::Timestamp, ColumnType::TimestampTz)
667 | (ColumnType::TimestampTz, ColumnType::Date | ColumnType::Timestamp) => {
668 ColumnType::TimestampTz
669 }
670 (ColumnType::Array(_), ColumnType::Array(_)) => {
671 let element = select_pair_with_control(
673 innermost_array_element(left),
674 innermost_array_element(right),
675 control,
676 )?;
677 return ColumnType::array_with_control(element, control)
678 .map_err(CommonTypeFailure::failed);
679 }
680 _ => same_category_common_type(left, right)?,
681 }
682 };
683 control
684 .finish(scalar, control.empty_reservation())
685 .map_err(CommonTypeFailure::failed)
686}
687
688fn same_category_common_type(
690 left: &ColumnType,
691 right: &ColumnType,
692) -> Result<ColumnType, CommonTypeFailure> {
693 use super::overload_resolution::{
694 routine_type_accepts_implicit_cast as implicit, routine_type_category,
695 routine_type_is_preferred,
696 };
697 let left_name = super::canonical_column_type_name(left);
698 let right_name = super::canonical_column_type_name(right);
699 if routine_type_category(&left_name) != routine_type_category(&right_name) {
700 return Err(CommonTypeFailure::Unmatched(Box::new((
701 left.clone(),
702 right.clone(),
703 ))));
704 }
705 let switch = !routine_type_is_preferred(&left_name)
706 && implicit(&left_name, &right_name)
707 && !implicit(&right_name, &left_name);
708 let (selected, other, selected_name, other_name) = if switch {
709 (right, left, &right_name, &left_name)
710 } else {
711 (left, right, &left_name, &right_name)
712 };
713 if implicit(other_name, selected_name) {
714 Ok(selected.clone())
715 } else {
716 Err(CommonTypeFailure::Inconvertible(Box::new((
717 other.clone(),
718 selected.clone(),
719 ))))
720 }
721}
722
723pub(super) mod case;
724
725fn innermost_array_element(mut ty: &ColumnType) -> &ColumnType {
727 while let ColumnType::Array(element) = ty {
728 ty = element;
729 }
730 ty
731}
732
733fn is_integral_type(ty: &ColumnType) -> bool {
734 matches!(
735 base_type(ty),
736 ColumnType::SmallInteger | ColumnType::Integer | ColumnType::BigInteger
737 )
738}
739
740pub(super) fn common_numeric_type(left: &ColumnType, right: &ColumnType) -> Option<ColumnType> {
741 let rank = numeric_rank(left)?.max(numeric_rank(right)?);
742 Some(match rank {
743 0 => ColumnType::SmallInteger,
744 1 => ColumnType::Integer,
745 2 => ColumnType::BigInteger,
746 3 => numeric_type(),
747 4 => ColumnType::Real,
748 _ => ColumnType::DoublePrecision,
749 })
750}
751
752pub(super) fn numeric_rank(ty: &ColumnType) -> Option<u8> {
753 match ty {
754 ColumnType::SmallInteger => Some(0),
755 ColumnType::Integer => Some(1),
756 ColumnType::BigInteger => Some(2),
757 ColumnType::Numeric { .. } => Some(3),
758 ColumnType::Real => Some(4),
759 ColumnType::DoublePrecision => Some(5),
760 _ => None,
761 }
762}
763
764pub(super) fn same_operator_type_with_control(
766 left: &ColumnType,
767 right: &ColumnType,
768 control: &ProductionControl<'_>,
769) -> Result<bool, uqa_core::ValueRetentionError> {
770 fn element(mut ty: &ColumnType) -> &ColumnType {
771 while let ColumnType::Array(inner) = ty {
772 ty = inner;
773 }
774 ty
775 }
776 let left = base_type(left);
777 let right = base_type(right);
778 let (left, right) = match (left, right) {
779 (ColumnType::Array(left), ColumnType::Array(right)) => (element(left), element(right)),
780 _ => (left, right),
781 };
782 let left = left.without_type_modifiers_with_control(control)?;
783 let right = right.without_type_modifiers_with_control(control)?;
784 Ok(*left == *right)
785}
786
787#[cfg(test)]
788mod production_tests;