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, .. } => return Ok(Some(ty.clone_with_control(control)?)),
238 SQLParam::Vector(values) => u32::try_from(values.len()).ok().map(ColumnType::Vector),
239 SQLParam::Tensor(values) => values
240 .first()
241 .and_then(|values| u32::try_from(values.len()).ok())
242 .map(ColumnType::Tensor),
243 };
244 scalar
245 .map(|ty| {
246 control
247 .finish(ty, control.empty_reservation())
248 .map_err(Into::into)
249 })
250 .transpose()
251}
252
253pub(crate) fn value_type(value: &Value) -> Option<ColumnType> {
254 value_type_with_control(value, &ProductionControl::uncontrolled())
255 .expect("ordinary value type inference cannot be cancelled or limited")
256 .map(|value| {
257 value
258 .into_uncontrolled()
259 .expect("ordinary value type has no reservation")
260 })
261}
262
263pub(crate) fn value_type_with_control(
264 value: &Value,
265 control: &ProductionControl<'_>,
266) -> Result<Option<Produced<ColumnType>>, SQLError> {
267 control.check()?;
268 let scalar = match value {
269 Value::Null | Value::Map(_) | Value::Enum(_) => None,
271 Value::Void => Some(ColumnType::Void),
272 Value::Row(_) | Value::Record(_) => Some(ColumnType::Record),
273 Value::Bool(_) => Some(ColumnType::Boolean),
274 Value::Int(value) if i32::try_from(*value).is_ok() => Some(ColumnType::Integer),
275 Value::Int(_) => Some(ColumnType::BigInteger),
276 Value::Float(_) => Some(ColumnType::DoublePrecision),
277 Value::Decimal(_) => Some(numeric_type()),
278 Value::Str(_) => Some(ColumnType::Text),
279 Value::FixedChar(value) => {
280 let mut count = 0_usize;
281 for _ in value.chars() {
282 control.check()?;
283 count += 1;
284 }
285 u32::try_from(count).ok().map(ColumnType::Character)
286 }
287 Value::Bytes(_) => Some(ColumnType::Bytea),
288 Value::Temporal(value) => Some(match value {
289 uqa_core::TemporalValue::Date { .. } => ColumnType::Date,
290 uqa_core::TemporalValue::Time { .. } => ColumnType::Time,
291 uqa_core::TemporalValue::TimeTz { .. } => ColumnType::TimeTz,
292 uqa_core::TemporalValue::Timestamp { .. } => ColumnType::Timestamp,
293 uqa_core::TemporalValue::TimestampTz { .. } => ColumnType::TimestampTz,
294 uqa_core::TemporalValue::Interval { .. } => ColumnType::Interval,
295 }),
296 Value::Json(_) => Some(ColumnType::Json),
297 Value::JsonB(_) => Some(ColumnType::JsonB),
298 Value::LegacyVector(vector) => Some(match vector.kind() {
299 uqa_core::LegacyVectorKind::SmallInteger => ColumnType::Int2Vector,
300 uqa_core::LegacyVectorKind::Oid => ColumnType::OidVector,
301 }),
302 Value::Array(array) => {
303 let mut element = None;
304 if !merge_array_element_types(array.elements(), &mut element, control)? {
305 return Ok(None);
306 }
307 return element
308 .map(|element| ColumnType::array_with_control(element, control).map_err(Into::into))
309 .transpose();
310 }
311 Value::List(values) => {
312 let mut element = None;
313 for value in values {
314 let next = value_type_with_control(value, control)?;
315 match merge_value_types(element, next, control) {
316 Ok(merged) => element = merged,
317 Err(error) if matches!(error.sqlstate(), Some("53200" | "57014")) => {
318 return Err(error)
319 }
320 Err(_) => return Ok(None),
321 }
322 }
323 return element
324 .map(|element| ColumnType::array_with_control(element, control).map_err(Into::into))
325 .transpose();
326 }
327 };
328 scalar
329 .map(|ty| {
330 control
331 .finish(ty, control.empty_reservation())
332 .map_err(Into::into)
333 })
334 .transpose()
335}
336
337fn merge_array_element_types(
338 values: &[Value],
339 element: &mut Option<Produced<ColumnType>>,
340 control: &ProductionControl<'_>,
341) -> Result<bool, SQLError> {
342 for value in values {
343 control.check()?;
344 if let Value::List(nested) = value {
345 if !merge_array_element_types(nested, element, control)? {
346 return Ok(false);
347 }
348 } else {
349 match merge_value_types(
350 element.take(),
351 value_type_with_control(value, control)?,
352 control,
353 ) {
354 Ok(merged) => *element = merged,
355 Err(error) if matches!(error.sqlstate(), Some("53200" | "57014")) => {
356 return Err(error)
357 }
358 Err(_) => return Ok(false),
359 }
360 }
361 }
362 Ok(true)
363}
364
365pub(super) fn merge_value_types(
366 left: Option<Produced<ColumnType>>,
367 right: Option<Produced<ColumnType>>,
368 control: &ProductionControl<'_>,
369) -> Result<Option<Produced<ColumnType>>, SQLError> {
370 control.check()?;
371 match (left, right) {
372 (None, other) | (other, None) => Ok(other),
373 (Some(left), Some(right)) if *left == *right => Ok(Some(left)),
374 (Some(left), Some(right)) => common_type_with_control(&left, &right, control).map(Some),
375 }
376}
377
378pub(super) fn read_unknown_literals<'a>(
380 expressions: impl IntoIterator<Item = &'a ScalarExpr>,
381 target: &ColumnType,
382) -> Result<(), SQLError> {
383 if super::catalog_input_type(target) {
384 return Ok(());
385 }
386 let target = base_type(target).without_type_modifiers().catalog_name();
388 for expression in expressions {
389 if let ScalarExpr::Literal(Value::Str(text)) = expression {
390 crate::expr::cast_value_from(&Value::Str(text.clone()), &target, None)?;
391 }
392 }
393 Ok(())
394}
395
396pub(super) fn merge_optional_types_in(
398 context: CommonTypeContext,
399 left: Option<ColumnType>,
400 right: Option<ColumnType>,
401) -> Result<Option<ColumnType>, SQLError> {
402 match (left, right) {
403 (None, other) | (other, None) => Ok(other),
404 (Some(left), Some(right)) => common_type_in(context, &left, &right).map(Some),
405 }
406}
407
408pub(super) fn merge_value_types_in(
410 context: CommonTypeContext,
411 left: Option<Produced<ColumnType>>,
412 right: Option<Produced<ColumnType>>,
413 control: &ProductionControl<'_>,
414) -> Result<Option<Produced<ColumnType>>, SQLError> {
415 control.check()?;
416 match (left, right) {
417 (None, other) | (other, None) => Ok(other),
418 (Some(left), Some(right)) if *left == *right => Ok(Some(left)),
419 (Some(left), Some(right)) => select_pair_with_control(&left, &right, control)
420 .map(Some)
421 .map_err(|failure| failure.in_context(context)),
422 }
423}
424
425pub fn common_type(left: &ColumnType, right: &ColumnType) -> Result<ColumnType, SQLError> {
426 common_type_with_control(left, right, &ProductionControl::uncontrolled()).map(|value| {
427 value
428 .into_uncontrolled()
429 .expect("ordinary common type has no reservation")
430 })
431}
432
433pub fn common_type_in(
435 context: CommonTypeContext,
436 left: &ColumnType,
437 right: &ColumnType,
438) -> Result<ColumnType, SQLError> {
439 select_pair_with_control(left, right, &ProductionControl::uncontrolled())
440 .map(|value| {
441 value
442 .into_uncontrolled()
443 .expect("ordinary common type has no reservation")
444 })
445 .map_err(|failure| failure.in_context(context))
446}
447
448#[derive(Clone, Copy, Debug, PartialEq, Eq)]
450pub enum CommonTypeContext {
451 Union,
452 Intersect,
453 Except,
454 Values,
455 Case,
456 Coalesce,
457 Array,
458 Greatest,
459 Least,
460 In,
461 JoinUsing,
462 Cycle,
463}
464
465impl CommonTypeContext {
466 pub const fn label(self) -> &'static str {
467 match self {
468 Self::Union => "UNION",
469 Self::Intersect => "INTERSECT",
470 Self::Except => "EXCEPT",
471 Self::Values => "VALUES",
472 Self::Case => "CASE",
473 Self::Coalesce => "COALESCE",
474 Self::Array => "ARRAY",
475 Self::Greatest => "GREATEST",
476 Self::Least => "LEAST",
477 Self::In => "IN",
478 Self::JoinUsing => "JOIN/USING",
479 Self::Cycle => "CYCLE",
480 }
481 }
482
483 pub const fn coercion_label(self) -> &'static str {
485 match self {
486 Self::Case => "CASE/WHEN",
487 other => other.label(),
488 }
489 }
490
491 pub const fn set_operation(kind: crate::ast::SetOpKind) -> Self {
492 match kind {
493 crate::ast::SetOpKind::Union => Self::Union,
494 crate::ast::SetOpKind::Intersect => Self::Intersect,
495 crate::ast::SetOpKind::Except => Self::Except,
496 }
497 }
498
499 pub fn function(name: &str) -> Option<Self> {
501 match local_routine_name(name).as_str() {
502 "coalesce" => Some(Self::Coalesce),
503 "greatest" => Some(Self::Greatest),
504 "least" => Some(Self::Least),
505 _ => None,
506 }
507 }
508}
509
510#[derive(Debug)]
512pub(super) enum CommonTypeFailure {
513 Unmatched(Box<(ColumnType, ColumnType)>),
515 Inconvertible(Box<(ColumnType, ColumnType)>),
517 Failed(Box<SQLError>),
518}
519
520impl CommonTypeFailure {
521 fn failed(error: impl Into<SQLError>) -> Self {
522 Self::Failed(Box::new(error.into()))
523 }
524
525 fn in_context(self, context: CommonTypeContext) -> SQLError {
527 match self {
528 Self::Unmatched(types) => SQLError::Routine {
529 sqlstate: "42804".into(),
530 message: format!(
531 "{} types {} and {} cannot be matched",
532 context.label(),
533 types.0.sql_name(),
534 types.1.sql_name()
535 ),
536 },
537 Self::Inconvertible(types) => SQLError::Routine {
538 sqlstate: "42846".into(),
539 message: format!(
540 "{} could not convert type {} to {}",
541 context.coercion_label(),
542 types.0.sql_name(),
543 types.1.sql_name()
544 ),
545 },
546 Self::Failed(error) => *error,
547 }
548 }
549
550 fn without_context(self, left: &ColumnType, right: &ColumnType) -> SQLError {
552 match self {
553 Self::Failed(error) => *error,
554 Self::Unmatched(_) | Self::Inconvertible(_) => SQLError::TypeMismatch(format!(
555 "types {} and {} cannot be matched",
556 left.sql_name(),
557 right.sql_name()
558 )),
559 }
560 }
561}
562
563pub fn select_common_input_type(
565 types: &[Option<&ColumnType>],
566) -> Result<Option<ColumnType>, SQLError> {
567 select_common_input_type_with_control(types, &ProductionControl::uncontrolled()).map(
568 |selected| {
569 selected.map(|value| {
570 value
571 .into_uncontrolled()
572 .expect("ordinary common type has no reservation")
573 })
574 },
575 )
576}
577
578pub(super) fn select_common_input_type_with_control(
580 types: &[Option<&ColumnType>],
581 control: &ProductionControl<'_>,
582) -> Result<Option<Produced<ColumnType>>, SQLError> {
583 control.check()?;
584 if let Some(Some(first)) = types.first() {
585 if types.iter().all(|ty| ty.is_some_and(|ty| ty == *first)) {
586 return first
587 .clone_with_control(control)
588 .map(Some)
589 .map_err(Into::into);
590 }
591 }
592 let mut selected: Option<Produced<ColumnType>> = None;
593 for ty in types.iter().flatten() {
594 let ty = base_type(ty);
595 selected = Some(match selected {
596 None => ty.clone_with_control(control)?,
597 Some(current) => match common_type_with_control(¤t, ty, control) {
598 Ok(common) => common,
599 Err(error) if matches!(error.sqlstate(), Some("53200" | "57014")) => {
600 return Err(error)
601 }
602 Err(_) => return Ok(None),
603 },
604 });
605 }
606 match selected {
607 Some(selected) => Ok(Some(selected)),
608 None => control
609 .finish(ColumnType::Text, control.empty_reservation())
610 .map(Some)
611 .map_err(Into::into),
612 }
613}
614
615pub(super) fn common_type_with_control(
617 left: &ColumnType,
618 right: &ColumnType,
619 control: &ProductionControl<'_>,
620) -> Result<Produced<ColumnType>, SQLError> {
621 select_pair_with_control(left, right, control)
622 .map_err(|failure| failure.without_context(left, right))
623}
624
625fn select_pair_with_control(
627 left: &ColumnType,
628 right: &ColumnType,
629 control: &ProductionControl<'_>,
630) -> Result<Produced<ColumnType>, CommonTypeFailure> {
631 control.check().map_err(CommonTypeFailure::failed)?;
632 if left == right {
633 return left
634 .clone_with_control(control)
635 .map_err(CommonTypeFailure::failed);
636 }
637 if left != left.without_temporal_modifiers() || right != right.without_temporal_modifiers() {
638 return select_pair_with_control(
639 left.without_temporal_modifiers(),
640 right.without_temporal_modifiers(),
641 control,
642 );
643 }
644 if matches!(left, ColumnType::Domain { .. }) || matches!(right, ColumnType::Domain { .. }) {
645 return select_pair_with_control(base_type(left), base_type(right), control);
646 }
647 let scalar = if let Some(numeric) = common_numeric_type(left, right) {
648 numeric
649 } else if matches!(left, ColumnType::Oid) && is_integral_type(right)
650 || matches!(right, ColumnType::Oid) && is_integral_type(left)
651 {
652 ColumnType::Oid
653 } else if left.is_character_string() && right.is_character_string() {
654 match left {
655 ColumnType::Bpchar | ColumnType::Character(_) => ColumnType::Bpchar,
656 ColumnType::Varchar(_) => ColumnType::Varchar(None),
657 ColumnType::Name => ColumnType::Name,
658 _ => ColumnType::Text,
659 }
660 } else {
661 match (left, right) {
662 (ColumnType::Date, ColumnType::Timestamp)
663 | (ColumnType::Timestamp, ColumnType::Date) => ColumnType::Timestamp,
664 (ColumnType::Date | ColumnType::Timestamp, ColumnType::TimestampTz)
665 | (ColumnType::TimestampTz, ColumnType::Date | ColumnType::Timestamp) => {
666 ColumnType::TimestampTz
667 }
668 (ColumnType::Array(_), ColumnType::Array(_)) => {
669 let element = select_pair_with_control(
671 innermost_array_element(left),
672 innermost_array_element(right),
673 control,
674 )?;
675 return ColumnType::array_with_control(element, control)
676 .map_err(CommonTypeFailure::failed);
677 }
678 _ => same_category_common_type(left, right)?,
679 }
680 };
681 control
682 .finish(scalar, control.empty_reservation())
683 .map_err(CommonTypeFailure::failed)
684}
685
686fn same_category_common_type(
688 left: &ColumnType,
689 right: &ColumnType,
690) -> Result<ColumnType, CommonTypeFailure> {
691 use super::overload_resolution::{
692 routine_type_accepts_implicit_cast as implicit, routine_type_category,
693 routine_type_is_preferred,
694 };
695 let left_name = super::canonical_column_type_name(left);
696 let right_name = super::canonical_column_type_name(right);
697 if routine_type_category(&left_name) != routine_type_category(&right_name) {
698 return Err(CommonTypeFailure::Unmatched(Box::new((
699 left.clone(),
700 right.clone(),
701 ))));
702 }
703 let switch = !routine_type_is_preferred(&left_name)
704 && implicit(&left_name, &right_name)
705 && !implicit(&right_name, &left_name);
706 let (selected, other, selected_name, other_name) = if switch {
707 (right, left, &right_name, &left_name)
708 } else {
709 (left, right, &left_name, &right_name)
710 };
711 if implicit(other_name, selected_name) {
712 Ok(selected.clone())
713 } else {
714 Err(CommonTypeFailure::Inconvertible(Box::new((
715 other.clone(),
716 selected.clone(),
717 ))))
718 }
719}
720
721pub(super) mod case;
722
723fn innermost_array_element(mut ty: &ColumnType) -> &ColumnType {
725 while let ColumnType::Array(element) = ty {
726 ty = element;
727 }
728 ty
729}
730
731fn is_integral_type(ty: &ColumnType) -> bool {
732 matches!(
733 base_type(ty),
734 ColumnType::SmallInteger | ColumnType::Integer | ColumnType::BigInteger
735 )
736}
737
738pub(super) fn common_numeric_type(left: &ColumnType, right: &ColumnType) -> Option<ColumnType> {
739 let rank = numeric_rank(left)?.max(numeric_rank(right)?);
740 Some(match rank {
741 0 => ColumnType::SmallInteger,
742 1 => ColumnType::Integer,
743 2 => ColumnType::BigInteger,
744 3 => numeric_type(),
745 4 => ColumnType::Real,
746 _ => ColumnType::DoublePrecision,
747 })
748}
749
750pub(super) fn numeric_rank(ty: &ColumnType) -> Option<u8> {
751 match ty {
752 ColumnType::SmallInteger => Some(0),
753 ColumnType::Integer => Some(1),
754 ColumnType::BigInteger => Some(2),
755 ColumnType::Numeric { .. } => Some(3),
756 ColumnType::Real => Some(4),
757 ColumnType::DoublePrecision => Some(5),
758 _ => None,
759 }
760}
761
762pub(super) fn same_operator_type_with_control(
764 left: &ColumnType,
765 right: &ColumnType,
766 control: &ProductionControl<'_>,
767) -> Result<bool, uqa_core::ValueRetentionError> {
768 fn element(mut ty: &ColumnType) -> &ColumnType {
769 while let ColumnType::Array(inner) = ty {
770 ty = inner;
771 }
772 ty
773 }
774 let left = base_type(left);
775 let right = base_type(right);
776 let (left, right) = match (left, right) {
777 (ColumnType::Array(left), ColumnType::Array(right)) => (element(left), element(right)),
778 _ => (left, right),
779 };
780 let left = left.without_type_modifiers_with_control(control)?;
781 let right = right.without_type_modifiers_with_control(control)?;
782 Ok(*left == *right)
783}
784
785#[cfg(test)]
786mod production_tests;