1use std::collections::BTreeMap;
13use std::fmt::Write;
14use std::sync::{Arc, Mutex};
15
16use itertools::Itertools;
17use mz_expr::explain::{HumanizedExplain, HumanizerMode};
18use mz_expr::visit::Visit;
19use mz_expr::{
20 AggregateExpr, ColumnOrder, Id, JoinImplementation, LocalId, MirRelationExpr, MirScalarExpr,
21 RECURSION_LIMIT, non_nullable_columns,
22};
23use mz_ore::soft_panic_or_log;
24use mz_ore::stack::{CheckedRecursion, RecursionGuard, RecursionLimitError};
25use mz_repr::adt::range::Range;
26use mz_repr::explain::{DummyHumanizer, ExprHumanizer};
27use mz_repr::{
28 ColumnName, Datum, ReprColumnType, ReprRelationType, ReprScalarBaseType, ReprScalarType,
29};
30
31pub type SharedTypecheckingContext = Arc<Mutex<Context>>;
36
37pub fn empty_typechecking_context() -> SharedTypecheckingContext {
39 Arc::new(Mutex::new(BTreeMap::new()))
40}
41
42#[derive(Debug)]
47pub enum TypeError<'a> {
48 Unbound {
50 source: &'a MirRelationExpr,
52 id: Id,
54 typ: ReprRelationType,
56 },
57 NoSuchColumn {
59 source: &'a MirRelationExpr,
61 expr: &'a MirScalarExpr,
63 col: usize,
65 },
66 MismatchColumn {
68 source: &'a MirRelationExpr,
70 got: ReprColumnType,
72 expected: ReprColumnType,
74 diffs: Vec<ReprColumnTypeDifference>,
76 message: String,
78 },
79 MismatchColumns {
81 source: &'a MirRelationExpr,
83 got: Vec<ReprColumnType>,
85 expected: Vec<ReprColumnType>,
87 diffs: Vec<ReprRelationTypeDifference>,
89 message: String,
91 },
92 BadConstantRowLen {
94 source: &'a MirRelationExpr,
96 got: usize,
98 expected: Vec<ReprColumnType>,
100 },
101 BadConstantRow {
103 source: &'a MirRelationExpr,
105 mismatches: Vec<(usize, DatumTypeDifference)>,
107 expected: Vec<ReprColumnType>,
109 },
112 BadProject {
114 source: &'a MirRelationExpr,
116 got: Vec<usize>,
118 input_type: Vec<ReprColumnType>,
120 },
121 BadJoinEquivalence {
123 source: &'a MirRelationExpr,
125 got: Vec<ReprColumnType>,
127 message: String,
129 },
130 BadTopKGroupKey {
132 source: &'a MirRelationExpr,
134 k: usize,
136 input_type: Vec<ReprColumnType>,
138 },
139 BadTopKOrdering {
141 source: &'a MirRelationExpr,
143 order: ColumnOrder,
145 input_type: Vec<ReprColumnType>,
147 },
148 BadLetRecBindings {
150 source: &'a MirRelationExpr,
152 },
153 Shadowing {
155 source: &'a MirRelationExpr,
157 id: Id,
159 },
160 Recursion {
162 error: RecursionLimitError,
164 },
165 DisallowedDummy {
167 source: &'a MirRelationExpr,
169 },
170}
171
172impl<'a> From<RecursionLimitError> for TypeError<'a> {
173 fn from(error: RecursionLimitError) -> Self {
174 TypeError::Recursion { error }
175 }
176}
177
178type Context = BTreeMap<Id, Vec<ReprColumnType>>;
179
180#[derive(Clone, Debug, Hash)]
184pub enum ReprRelationTypeDifference {
185 Length {
187 len_sub: usize,
189 len_sup: usize,
191 },
192 Column {
194 col: usize,
196 diff: ReprColumnTypeDifference,
198 },
199}
200
201#[derive(Clone, Debug, Hash)]
206pub enum ReprColumnTypeDifference {
207 NotSubtype {
209 sub: ReprScalarType,
211 sup: ReprScalarType,
213 },
214 Nullability {
216 sub: ReprColumnType,
218 sup: ReprColumnType,
220 },
221 ElementType {
223 ctor: String,
225 element_type: Box<ReprColumnTypeDifference>,
227 },
228 RecordMissingFields {
230 missing: Vec<ColumnName>,
232 },
233 RecordFields {
235 fields: Vec<ReprColumnTypeDifference>,
237 },
238}
239
240impl ReprRelationTypeDifference {
241 pub fn ignore_nullability(self) -> Option<Self> {
245 use ReprRelationTypeDifference::*;
246
247 match self {
248 Length { .. } => Some(self),
249 Column { col, diff } => diff.ignore_nullability().map(|diff| Column { col, diff }),
250 }
251 }
252}
253
254impl ReprColumnTypeDifference {
255 pub fn ignore_nullability(self) -> Option<Self> {
259 use ReprColumnTypeDifference::*;
260
261 match self {
262 Nullability { .. } => None,
263 NotSubtype { .. } | RecordMissingFields { .. } => Some(self),
264 ElementType { ctor, element_type } => {
265 element_type
266 .ignore_nullability()
267 .map(|element_type| ElementType {
268 ctor,
269 element_type: Box::new(element_type),
270 })
271 }
272 RecordFields { fields } => {
273 let fields = fields
274 .into_iter()
275 .flat_map(|diff| diff.ignore_nullability())
276 .collect::<Vec<_>>();
277
278 if fields.is_empty() {
279 None
280 } else {
281 Some(RecordFields { fields })
282 }
283 }
284 }
285 }
286}
287
288pub fn relation_subtype_difference(
292 sub: &[ReprColumnType],
293 sup: &[ReprColumnType],
294) -> Vec<ReprRelationTypeDifference> {
295 let mut diffs = Vec::new();
296
297 if sub.len() != sup.len() {
298 diffs.push(ReprRelationTypeDifference::Length {
299 len_sub: sub.len(),
300 len_sup: sup.len(),
301 });
302
303 return diffs;
305 }
306
307 diffs.extend(
308 sub.iter()
309 .zip_eq(sup.iter())
310 .enumerate()
311 .flat_map(|(col, (sub_ty, sup_ty))| {
312 column_subtype_difference(sub_ty, sup_ty)
313 .into_iter()
314 .map(move |diff| ReprRelationTypeDifference::Column { col, diff })
315 }),
316 );
317
318 diffs
319}
320
321pub fn column_subtype_difference(
325 sub: &ReprColumnType,
326 sup: &ReprColumnType,
327) -> Vec<ReprColumnTypeDifference> {
328 let mut diffs = scalar_subtype_difference(&sub.scalar_type, &sup.scalar_type);
329
330 if sub.nullable && !sup.nullable {
331 diffs.push(ReprColumnTypeDifference::Nullability {
332 sub: sub.clone(),
333 sup: sup.clone(),
334 });
335 }
336
337 diffs
338}
339
340pub fn scalar_subtype_difference(
344 sub: &ReprScalarType,
345 sup: &ReprScalarType,
346) -> Vec<ReprColumnTypeDifference> {
347 use ReprScalarType::*;
348
349 let mut diffs = Vec::new();
350
351 match (sub, sup) {
352 (
353 List {
354 element_type: sub_elt,
355 ..
356 },
357 List {
358 element_type: sup_elt,
359 ..
360 },
361 )
362 | (
363 Map {
364 value_type: sub_elt,
365 ..
366 },
367 Map {
368 value_type: sup_elt,
369 ..
370 },
371 )
372 | (
373 Range {
374 element_type: sub_elt,
375 ..
376 },
377 Range {
378 element_type: sup_elt,
379 ..
380 },
381 )
382 | (Array(sub_elt), Array(sup_elt)) => {
383 let ctor = format!("{:?}", ReprScalarBaseType::from(sub));
384 diffs.extend(
385 scalar_subtype_difference(sub_elt, sup_elt)
386 .into_iter()
387 .map(|diff| ReprColumnTypeDifference::ElementType {
388 ctor: ctor.clone(),
389 element_type: Box::new(diff),
390 }),
391 );
392 }
393 (
394 Record {
395 fields: sub_fields, ..
396 },
397 Record {
398 fields: sup_fields, ..
399 },
400 ) => {
401 if sub_fields.len() != sup_fields.len() {
402 diffs.push(ReprColumnTypeDifference::NotSubtype {
403 sub: sub.clone(),
404 sup: sup.clone(),
405 });
406 return diffs;
407 }
408
409 for (sub_ty, sup_ty) in sub_fields.iter().zip_eq(sup_fields.iter()) {
410 diffs.extend(column_subtype_difference(sub_ty, sup_ty));
411 }
412 }
413 (_, _) => {
414 if ReprScalarBaseType::from(sub) != ReprScalarBaseType::from(sup) {
415 diffs.push(ReprColumnTypeDifference::NotSubtype {
416 sub: sub.clone(),
417 sup: sup.clone(),
418 })
419 }
420 }
421 };
422
423 diffs
424}
425
426pub fn scalar_union(
430 typ: &mut ReprScalarType,
431 other: &ReprScalarType,
432) -> Vec<ReprColumnTypeDifference> {
433 use ReprScalarType::*;
434
435 let mut diffs = Vec::new();
436
437 let ctor = ReprScalarBaseType::from(&*typ);
439 match (typ, other) {
440 (
441 List {
442 element_type: typ_elt,
443 },
444 List {
445 element_type: other_elt,
446 },
447 )
448 | (
449 Map {
450 value_type: typ_elt,
451 },
452 Map {
453 value_type: other_elt,
454 },
455 )
456 | (
457 Range {
458 element_type: typ_elt,
459 },
460 Range {
461 element_type: other_elt,
462 },
463 )
464 | (Array(typ_elt), Array(other_elt)) => {
465 let res = scalar_union(typ_elt.as_mut(), other_elt.as_ref());
466 diffs.extend(
467 res.into_iter()
468 .map(|diff| ReprColumnTypeDifference::ElementType {
469 ctor: format!("{ctor:?}"),
470 element_type: Box::new(diff),
471 }),
472 );
473 }
474 (
475 Record { fields: typ_fields },
476 Record {
477 fields: other_fields,
478 },
479 ) => {
480 if typ_fields.len() != other_fields.len() {
481 diffs.push(ReprColumnTypeDifference::NotSubtype {
482 sub: ReprScalarType::Record {
483 fields: typ_fields.clone(),
484 },
485 sup: other.clone(),
486 });
487 return diffs;
488 }
489
490 for (typ_ty, other_ty) in typ_fields.iter_mut().zip_eq(other_fields.iter()) {
491 diffs.extend(column_union(typ_ty, other_ty));
492 }
493 }
494 (typ, _) => {
495 if ctor != ReprScalarBaseType::from(other) {
496 diffs.push(ReprColumnTypeDifference::NotSubtype {
497 sub: typ.clone(),
498 sup: other.clone(),
499 })
500 }
501 }
502 };
503
504 diffs
505}
506
507pub fn column_union(
511 typ: &mut ReprColumnType,
512 other: &ReprColumnType,
513) -> Vec<ReprColumnTypeDifference> {
514 let diffs = scalar_union(&mut typ.scalar_type, &other.scalar_type);
515
516 if diffs.is_empty() {
517 typ.nullable |= other.nullable;
518 }
519
520 diffs
521}
522
523pub fn is_subtype_of(sub: &[ReprColumnType], sup: &[ReprColumnType]) -> bool {
528 if sub.len() != sup.len() {
529 return false;
530 }
531
532 sub.iter().zip_eq(sup.iter()).all(|(got, known)| {
533 (!known.nullable || got.nullable) && got.scalar_type == known.scalar_type
534 })
535}
536
537#[derive(Clone, Debug)]
539pub enum DatumTypeDifference {
540 Null {
542 expected: ReprScalarType,
544 },
545 Mismatch {
547 got_debug: String,
550 expected: ReprScalarType,
552 },
553 MismatchDimensions {
555 ctor: String,
557 got: usize,
559 expected: usize,
561 },
562 ElementType {
564 ctor: String,
566 element_type: Box<DatumTypeDifference>,
568 },
569}
570
571fn datum_difference_with_column_type(
577 datum: &Datum<'_>,
578 column_type: &ReprColumnType,
579) -> Result<(), DatumTypeDifference> {
580 fn difference_with_scalar_type(
581 datum: &Datum<'_>,
582 scalar_type: &ReprScalarType,
583 ) -> Result<(), DatumTypeDifference> {
584 fn mismatch(got: &Datum<'_>, expected: &ReprScalarType) -> Result<(), DatumTypeDifference> {
585 Err(DatumTypeDifference::Mismatch {
586 got_debug: format!("{got:?}"),
588 expected: expected.clone(),
589 })
590 }
591
592 if let ReprScalarType::Jsonb = scalar_type {
593 match datum {
595 Datum::Dummy => Ok(()), Datum::Null => Err(DatumTypeDifference::Null {
597 expected: ReprScalarType::Jsonb,
598 }),
599 Datum::JsonNull
600 | Datum::False
601 | Datum::True
602 | Datum::Numeric(_)
603 | Datum::String(_) => Ok(()),
604 Datum::List(list) => {
605 for elem in list.iter() {
606 difference_with_scalar_type(&elem, scalar_type)?;
607 }
608 Ok(())
609 }
610 Datum::Map(dict) => {
611 for (_, val) in dict.iter() {
612 difference_with_scalar_type(&val, scalar_type)?;
613 }
614 Ok(())
615 }
616 _ => mismatch(datum, scalar_type),
617 }
618 } else {
619 fn element_type_difference(
620 ctor: &str,
621 element_type: DatumTypeDifference,
622 ) -> DatumTypeDifference {
623 DatumTypeDifference::ElementType {
624 ctor: ctor.to_string(),
625 element_type: Box::new(element_type),
626 }
627 }
628 match (datum, scalar_type) {
629 (Datum::Dummy, _) => Ok(()), (Datum::Null, _) => Err(DatumTypeDifference::Null {
631 expected: scalar_type.clone(),
632 }),
633 (Datum::False, ReprScalarType::Bool) => Ok(()),
634 (Datum::False, _) => mismatch(datum, scalar_type),
635 (Datum::True, ReprScalarType::Bool) => Ok(()),
636 (Datum::True, _) => mismatch(datum, scalar_type),
637 (Datum::Int16(_), ReprScalarType::Int16) => Ok(()),
638 (Datum::Int16(_), _) => mismatch(datum, scalar_type),
639 (Datum::Int32(_), ReprScalarType::Int32) => Ok(()),
640 (Datum::Int32(_), _) => mismatch(datum, scalar_type),
641 (Datum::Int64(_), ReprScalarType::Int64) => Ok(()),
642 (Datum::Int64(_), _) => mismatch(datum, scalar_type),
643 (Datum::UInt8(_), ReprScalarType::UInt8) => Ok(()),
644 (Datum::UInt8(_), _) => mismatch(datum, scalar_type),
645 (Datum::UInt16(_), ReprScalarType::UInt16) => Ok(()),
646 (Datum::UInt16(_), _) => mismatch(datum, scalar_type),
647 (Datum::UInt32(_), ReprScalarType::UInt32) => Ok(()),
648 (Datum::UInt32(_), _) => mismatch(datum, scalar_type),
649 (Datum::UInt64(_), ReprScalarType::UInt64) => Ok(()),
650 (Datum::UInt64(_), _) => mismatch(datum, scalar_type),
651 (Datum::Float32(_), ReprScalarType::Float32) => Ok(()),
652 (Datum::Float32(_), _) => mismatch(datum, scalar_type),
653 (Datum::Float64(_), ReprScalarType::Float64) => Ok(()),
654 (Datum::Float64(_), _) => mismatch(datum, scalar_type),
655 (Datum::Date(_), ReprScalarType::Date) => Ok(()),
656 (Datum::Date(_), _) => mismatch(datum, scalar_type),
657 (Datum::Time(_), ReprScalarType::Time) => Ok(()),
658 (Datum::Time(_), _) => mismatch(datum, scalar_type),
659 (Datum::Timestamp(_), ReprScalarType::Timestamp { .. }) => Ok(()),
660 (Datum::Timestamp(_), _) => mismatch(datum, scalar_type),
661 (Datum::TimestampTz(_), ReprScalarType::TimestampTz { .. }) => Ok(()),
662 (Datum::TimestampTz(_), _) => mismatch(datum, scalar_type),
663 (Datum::Interval(_), ReprScalarType::Interval) => Ok(()),
664 (Datum::Interval(_), _) => mismatch(datum, scalar_type),
665 (Datum::Bytes(_), ReprScalarType::Bytes) => Ok(()),
666 (Datum::Bytes(_), _) => mismatch(datum, scalar_type),
667 (Datum::String(_), ReprScalarType::String) => Ok(()),
668 (Datum::String(_), _) => mismatch(datum, scalar_type),
669 (Datum::Uuid(_), ReprScalarType::Uuid) => Ok(()),
670 (Datum::Uuid(_), _) => mismatch(datum, scalar_type),
671 (Datum::Array(array), ReprScalarType::Array(t)) => {
672 for e in array.elements().iter() {
673 if let Datum::Null = e {
674 continue;
675 }
676
677 difference_with_scalar_type(&e, t)
678 .map_err(|e| element_type_difference("array", e))?;
679 }
680 Ok(())
681 }
682 (Datum::Array(array), ReprScalarType::Int2Vector) => {
683 if !array.has_int2vector_dims() {
684 return Err(DatumTypeDifference::MismatchDimensions {
687 ctor: "int2vector".to_string(),
688 got: array.dims().len(),
689 expected: 1,
690 });
691 }
692
693 for e in array.elements().iter() {
694 difference_with_scalar_type(&e, &ReprScalarType::Int16)
695 .map_err(|e| element_type_difference("int2vector", e))?;
696 }
697
698 Ok(())
699 }
700 (Datum::Array(_), _) => mismatch(datum, scalar_type),
701 (Datum::List(list), ReprScalarType::List { element_type, .. }) => {
702 for e in list.iter() {
703 if let Datum::Null = e {
704 continue;
705 }
706
707 difference_with_scalar_type(&e, element_type)
708 .map_err(|e| element_type_difference("list", e))?;
709 }
710 Ok(())
711 }
712 (Datum::List(list), ReprScalarType::Record { fields, .. }) => {
713 let len = list.iter().count();
714 if len != fields.len() {
715 return Err(DatumTypeDifference::MismatchDimensions {
716 ctor: "record".to_string(),
717 got: len,
718 expected: fields.len(),
719 });
720 }
721
722 for (e, t) in list.iter().zip_eq(fields) {
723 if let Datum::Null = e {
724 if t.nullable {
725 continue;
726 } else {
727 return Err(DatumTypeDifference::Null {
728 expected: t.scalar_type.clone(),
729 });
730 }
731 }
732
733 difference_with_scalar_type(&e, &t.scalar_type)
734 .map_err(|e| element_type_difference("record", e))?;
735 }
736 Ok(())
737 }
738 (Datum::List(_), _) => mismatch(datum, scalar_type),
739 (Datum::Map(map), ReprScalarType::Map { value_type, .. }) => {
740 for (_, v) in map.iter() {
741 if let Datum::Null = v {
742 continue;
743 }
744
745 difference_with_scalar_type(&v, value_type)
746 .map_err(|e| element_type_difference("map", e))?;
747 }
748 Ok(())
749 }
750 (Datum::Map(_), _) => mismatch(datum, scalar_type),
751 (Datum::JsonNull, _) => mismatch(datum, scalar_type),
752 (Datum::Numeric(_), ReprScalarType::Numeric) => Ok(()),
753 (Datum::Numeric(_), _) => mismatch(datum, scalar_type),
754 (Datum::MzTimestamp(_), ReprScalarType::MzTimestamp) => Ok(()),
755 (Datum::MzTimestamp(_), _) => mismatch(datum, scalar_type),
756 (Datum::Range(Range { inner }), ReprScalarType::Range { element_type }) => {
757 match inner {
758 None => Ok(()),
759 Some(inner) => {
760 if let Some(b) = inner.lower.bound {
761 difference_with_scalar_type(&b.datum(), element_type)
762 .map_err(|e| element_type_difference("range", e))?;
763 }
764 if let Some(b) = inner.upper.bound {
765 difference_with_scalar_type(&b.datum(), element_type)
766 .map_err(|e| element_type_difference("range", e))?;
767 }
768 Ok(())
769 }
770 }
771 }
772 (Datum::Range(_), _) => mismatch(datum, scalar_type),
773 (Datum::MzAclItem(_), ReprScalarType::MzAclItem) => Ok(()),
774 (Datum::MzAclItem(_), _) => mismatch(datum, scalar_type),
775 (Datum::AclItem(_), ReprScalarType::AclItem) => Ok(()),
776 (Datum::AclItem(_), _) => mismatch(datum, scalar_type),
777 }
778 }
779 }
780 if column_type.nullable {
781 if let Datum::Null = datum {
782 return Ok(());
783 }
784 }
785 difference_with_scalar_type(datum, &column_type.scalar_type)
786}
787
788fn row_difference_with_column_types<'a>(
789 source: &'a MirRelationExpr,
790 datums: &[Datum<'_>],
791 column_types: &[ReprColumnType],
792) -> Result<(), TypeError<'a>> {
793 if datums.len() != column_types.len() {
795 return Err(TypeError::BadConstantRowLen {
796 source,
797 got: datums.len(),
798 expected: column_types.to_vec(),
799 });
800 }
801
802 let mut mismatches = Vec::new();
804 for (i, (d, ty)) in datums.iter().zip_eq(column_types.iter()).enumerate() {
805 if let Err(e) = datum_difference_with_column_type(d, ty) {
806 mismatches.push((i, e));
807 }
808 }
809 if !mismatches.is_empty() {
810 return Err(TypeError::BadConstantRow {
811 source,
812 mismatches,
813 expected: column_types.to_vec(),
814 });
815 }
816
817 Ok(())
818}
819#[derive(Debug)]
821pub struct Typecheck {
822 ctx: SharedTypecheckingContext,
824 disallow_new_globals: bool,
826 strict_join_equivalences: bool,
828 disallow_dummy: bool,
830 recursion_guard: RecursionGuard,
832}
833
834impl CheckedRecursion for Typecheck {
835 fn recursion_guard(&self) -> &RecursionGuard {
836 &self.recursion_guard
837 }
838}
839
840impl Typecheck {
841 pub fn new(ctx: SharedTypecheckingContext) -> Self {
843 Self {
844 ctx,
845 disallow_new_globals: false,
846 strict_join_equivalences: false,
847 disallow_dummy: false,
848 recursion_guard: RecursionGuard::with_limit(RECURSION_LIMIT),
849 }
850 }
851
852 pub fn disallow_new_globals(mut self) -> Self {
856 self.disallow_new_globals = true;
857 self
858 }
859
860 pub fn strict_join_equivalences(mut self) -> Self {
864 self.strict_join_equivalences = true;
865
866 self
867 }
868
869 pub fn disallow_dummy(mut self) -> Self {
871 self.disallow_dummy = true;
872 self
873 }
874
875 pub fn typecheck<'a>(
886 &self,
887 expr: &'a MirRelationExpr,
888 ctx: &Context,
889 ) -> Result<Vec<ReprColumnType>, TypeError<'a>> {
890 use MirRelationExpr::*;
891
892 self.checked_recur(|tc| match expr {
893 Constant { typ, rows } => {
894 if let Ok(rows) = rows {
895 for (row, _id) in rows {
896 let datums = row.unpack();
897
898 let col_types = typ
899 .column_types
900 .iter()
901 .cloned()
902 .collect_vec();
903 row_difference_with_column_types(
904 expr, &datums, &col_types,
905 )?;
906
907 if self.disallow_dummy
908 && datums.iter().any(|d| d == &mz_repr::Datum::Dummy)
909 {
910 return Err(TypeError::DisallowedDummy {
911 source: expr,
912 });
913 }
914 }
915 }
916
917 Ok(typ.column_types.iter().cloned().collect_vec())
918 }
919 Get { typ, id, .. } => {
920 if let Id::Global(_global_id) = id {
921 if !ctx.contains_key(id) {
922 return Ok(typ.column_types.iter().cloned().collect_vec());
924 }
925 }
926
927 let ctx_typ = ctx.get(id).ok_or_else(|| TypeError::Unbound {
928 source: expr,
929 id: id.clone(),
930 typ: typ.clone(),
931 })?;
932
933 let column_types = typ.column_types.iter().cloned().collect_vec();
934
935 let diffs = relation_subtype_difference(&column_types, ctx_typ)
937 .into_iter()
938 .flat_map(|diff| diff.ignore_nullability())
939 .collect::<Vec<_>>();
940
941 if !diffs.is_empty() {
942 return Err(TypeError::MismatchColumns {
943 source: expr,
944 got: column_types,
945 expected: ctx_typ.clone(),
946 diffs,
947 message: "annotation did not match context type".to_string(),
948 });
949 }
950
951 Ok(column_types)
952 }
953 Project { input, outputs } => {
954 let t_in = tc.typecheck(input, ctx)?;
955
956 for x in outputs {
957 if *x >= t_in.len() {
958 return Err(TypeError::BadProject {
959 source: expr,
960 got: outputs.clone(),
961 input_type: t_in,
962 });
963 }
964 }
965
966 Ok(outputs.iter().map(|col| t_in[*col].clone()).collect())
967 }
968 Map { input, scalars } => {
969 let mut t_in = tc.typecheck(input, ctx)?;
970
971 for scalar_expr in scalars.iter() {
972 t_in.push(tc.typecheck_scalar(scalar_expr, expr, &t_in)?);
973
974 if self.disallow_dummy && scalar_expr.contains_dummy() {
975 return Err(TypeError::DisallowedDummy {
976 source: expr,
977 });
978 }
979 }
980
981 Ok(t_in)
982 }
983 FlatMap { input, func, exprs } => {
984 let mut t_in = tc.typecheck(input, ctx)?;
985
986 for scalar_expr in exprs {
987 let _t_expr = tc.typecheck_scalar(scalar_expr, expr, &t_in)?;
989
990 if self.disallow_dummy && scalar_expr.contains_dummy() {
991 return Err(TypeError::DisallowedDummy {
992 source: expr,
993 });
994 }
995 }
996
997 let t_out: Vec<ReprColumnType> = func
998 .output_type().column_types;
999
1000 t_in.extend(t_out);
1002 Ok(t_in)
1003 }
1004 Filter { input, predicates } => {
1005 let mut t_in = tc.typecheck(input, ctx)?;
1006
1007 for column in non_nullable_columns(predicates) {
1010 t_in[column].nullable = false;
1011 }
1012
1013 for scalar_expr in predicates {
1014 let t = tc.typecheck_scalar(scalar_expr, expr, &t_in)?;
1015
1016 if t.scalar_type != ReprScalarType::Bool {
1020 let sub = t.scalar_type.clone();
1021
1022 return Err(TypeError::MismatchColumn {
1023 source: expr,
1024 got: t,
1025 expected: ReprColumnType {
1026 scalar_type: ReprScalarType::Bool,
1027 nullable: true,
1028 },
1029 diffs: vec![ReprColumnTypeDifference::NotSubtype {
1030 sub,
1031 sup: ReprScalarType::Bool,
1032 }],
1033 message: "expected boolean condition".to_string(),
1034 });
1035 }
1036
1037 if self.disallow_dummy && scalar_expr.contains_dummy() {
1038 return Err(TypeError::DisallowedDummy {
1039 source: expr,
1040 });
1041 }
1042 }
1043
1044 Ok(t_in)
1045 }
1046 Join {
1047 inputs,
1048 equivalences,
1049 implementation,
1050 } => {
1051 let mut t_in_global = Vec::new();
1052 let mut t_in_local = vec![Vec::new(); inputs.len()];
1053
1054 for (i, input) in inputs.iter().enumerate() {
1055 let input_t = tc.typecheck(input, ctx)?;
1056 t_in_global.extend(input_t.clone());
1057 t_in_local[i] = input_t;
1058 }
1059
1060 for eq_class in equivalences {
1061 let mut t_exprs: Vec<ReprColumnType> = Vec::with_capacity(eq_class.len());
1062
1063 let mut all_nullable = true;
1064
1065 for scalar_expr in eq_class {
1066 let t_expr = tc.typecheck_scalar(scalar_expr, expr, &t_in_global)?;
1068
1069 if !t_expr.nullable {
1070 all_nullable = false;
1071 }
1072
1073 if let Some(t_first) = t_exprs.get(0) {
1074 let diffs = scalar_subtype_difference(
1075 &t_expr.scalar_type,
1076 &t_first.scalar_type,
1077 ).into_iter().filter_map(|d| d.ignore_nullability()).collect_vec();
1078 if !diffs.is_empty() {
1079 return Err(TypeError::MismatchColumn {
1080 source: expr,
1081 got: t_expr,
1082 expected: t_first.clone(),
1083 diffs,
1084 message: "equivalence class members \
1085 have different scalar types"
1086 .to_string(),
1087 });
1088 }
1089
1090 if self.strict_join_equivalences {
1094 if t_expr.nullable != t_first.nullable {
1095 let sub = t_expr.clone();
1096 let sup = t_first.clone();
1097
1098 let err = TypeError::MismatchColumn {
1099 source: expr,
1100 got: t_expr.clone(),
1101 expected: t_first.clone(),
1102 diffs: vec![
1103 ReprColumnTypeDifference::Nullability { sub, sup },
1104 ],
1105 message: "equivalence class members have \
1106 different nullability (and join \
1107 equivalence checking is strict)"
1108 .to_string(),
1109 };
1110
1111 ::tracing::debug!("{err}");
1113 }
1114 }
1115 }
1116
1117 if self.disallow_dummy && scalar_expr.contains_dummy() {
1118 return Err(TypeError::DisallowedDummy {
1119 source: expr,
1120 });
1121 }
1122
1123 t_exprs.push(t_expr);
1124 }
1125
1126 if self.strict_join_equivalences && all_nullable {
1127 let err = TypeError::BadJoinEquivalence {
1128 source: expr,
1129 got: t_exprs,
1130 message: "all expressions were nullable (and join equivalence checking is strict)".to_string(),
1131 };
1132
1133 ::tracing::debug!("{err}");
1135 }
1136 }
1137
1138 match implementation {
1140 JoinImplementation::Differential((start_idx, first_key, _), others) => {
1141 if let Some(key) = first_key {
1142 for k in key {
1143 let _ = tc.typecheck_scalar(k, expr, &t_in_local[*start_idx])?;
1144 }
1145 }
1146
1147 for (idx, key, _) in others {
1148 for k in key {
1149 let _ = tc.typecheck_scalar(k, expr, &t_in_local[*idx])?;
1150 }
1151 }
1152 }
1153 JoinImplementation::DeltaQuery(plans) => {
1154 for plan in plans {
1155 for (idx, key, _) in plan {
1156 for k in key {
1157 let _ = tc.typecheck_scalar(k, expr, &t_in_local[*idx])?;
1158 }
1159 }
1160 }
1161 }
1162 JoinImplementation::IndexedFilter(_coll_id, _idx_id, key, consts) => {
1163 let typ: Vec<ReprColumnType> = key
1164 .iter()
1165 .map(|k| tc.typecheck_scalar(k, expr, &t_in_global))
1166 .collect::<Result<Vec<ReprColumnType>, TypeError>>()?;
1167
1168 for row in consts {
1169 let datums = row.unpack();
1170
1171 row_difference_with_column_types(expr, &datums, &typ)?;
1172 }
1173 }
1174 JoinImplementation::Unimplemented => (),
1175 }
1176
1177 Ok(t_in_global)
1178 }
1179 Reduce {
1180 input,
1181 group_key,
1182 aggregates,
1183 monotonic: _,
1184 expected_group_size: _,
1185 } => {
1186 let t_in = tc.typecheck(input, ctx)?;
1187
1188 let mut t_out = group_key
1189 .iter()
1190 .map(|scalar_expr| tc.typecheck_scalar(scalar_expr, expr, &t_in))
1191 .collect::<Result<Vec<_>, _>>()?;
1192
1193 if self.disallow_dummy
1194 && group_key
1195 .iter()
1196 .any(|scalar_expr| scalar_expr.contains_dummy())
1197 {
1198 return Err(TypeError::DisallowedDummy {
1199 source: expr,
1200 });
1201 }
1202
1203 for agg in aggregates {
1204 t_out.push(tc.typecheck_aggregate(agg, expr, &t_in)?);
1205 }
1206
1207 Ok(t_out)
1208 }
1209 TopK {
1210 input,
1211 group_key,
1212 order_key,
1213 limit: _,
1214 offset: _,
1215 monotonic: _,
1216 expected_group_size: _,
1217 } => {
1218 let t_in = tc.typecheck(input, ctx)?;
1219
1220 for &k in group_key {
1221 if k >= t_in.len() {
1222 return Err(TypeError::BadTopKGroupKey {
1223 source: expr,
1224 k,
1225 input_type: t_in,
1226 });
1227 }
1228 }
1229
1230 for order in order_key {
1231 if order.column >= t_in.len() {
1232 return Err(TypeError::BadTopKOrdering {
1233 source: expr,
1234 order: order.clone(),
1235 input_type: t_in,
1236 });
1237 }
1238 }
1239
1240 Ok(t_in)
1241 }
1242 Negate { input } => tc.typecheck(input, ctx),
1243 Threshold { input } => tc.typecheck(input, ctx),
1244 Union { base, inputs } => {
1245 let mut t_base = tc.typecheck(base, ctx)?;
1246
1247 for input in inputs {
1248 let t_input = tc.typecheck(input, ctx)?;
1249
1250 let len_sub = t_base.len();
1251 let len_sup = t_input.len();
1252 if len_sub != len_sup {
1253 return Err(TypeError::MismatchColumns {
1254 source: expr,
1255 got: t_base.clone(),
1256 expected: t_input,
1257 diffs: vec![ReprRelationTypeDifference::Length {
1258 len_sub,
1259 len_sup,
1260 }],
1261 message: "Union branches have different numbers of columns".to_string(),
1262 });
1263 }
1264
1265 for (base_col, input_col) in t_base.iter_mut().zip_eq(t_input) {
1266 let diffs = column_union(base_col, &input_col);
1267 if !diffs.is_empty() {
1268 return Err(TypeError::MismatchColumn {
1269 source: expr,
1270 got: input_col,
1271 expected: base_col.clone(),
1272 diffs,
1273 message:
1274 "couldn't compute union of column types in Union"
1275 .to_string(),
1276 });
1277 }
1278
1279 }
1280 }
1281
1282 Ok(t_base)
1283 }
1284 Let { id, value, body } => {
1285 let t_value = tc.typecheck(value, ctx)?;
1286
1287 let binding = Id::Local(*id);
1288 if ctx.contains_key(&binding) {
1289 return Err(TypeError::Shadowing {
1290 source: expr,
1291 id: binding,
1292 });
1293 }
1294
1295 let mut body_ctx = ctx.clone();
1296 body_ctx.insert(Id::Local(*id), t_value);
1297
1298 tc.typecheck(body, &body_ctx)
1299 }
1300 LetRec { ids, values, body, limits: _ } => {
1301 if ids.len() != values.len() {
1302 return Err(TypeError::BadLetRecBindings { source: expr });
1303 }
1304
1305 let mut ctx = ctx.clone();
1308 for inner_expr in values.iter().chain(std::iter::once(body.as_ref())) {
1310 tc.collect_recursive_variable_types(inner_expr, ids, &mut ctx)?;
1311 }
1312
1313 for (id, value) in ids.iter().zip_eq(values.iter()) {
1314 let typ = tc.typecheck(value, &ctx)?;
1315
1316 let id = Id::Local(id.clone());
1317 if let Some(ctx_typ) = ctx.get_mut(&id) {
1318 for (base_col, input_col) in ctx_typ.iter_mut().zip_eq(typ) {
1319 let diffs = column_union(base_col, &input_col);
1321 if !diffs.is_empty() {
1322 return Err(TypeError::MismatchColumn {
1323 source: expr,
1324 got: input_col,
1325 expected: base_col.clone(),
1326 diffs,
1327 message:
1328 "couldn't compute union of column types in LetRec"
1329 .to_string(),
1330 })
1331 }
1332 }
1333 } else {
1334 ctx.insert(id, typ);
1336 }
1337 }
1338
1339 tc.typecheck(body, &ctx)
1340 }
1341 ArrangeBy { input, keys } => {
1342 let t_in = tc.typecheck(input, ctx)?;
1343
1344 for key in keys {
1345 for k in key {
1346 let _ = tc.typecheck_scalar(k, expr, &t_in)?;
1347 }
1348 }
1349
1350 Ok(t_in)
1351 }
1352 })
1353 }
1354
1355 fn collect_recursive_variable_types<'a>(
1359 &self,
1360 expr: &'a MirRelationExpr,
1361 ids: &[LocalId],
1362 ctx: &mut Context,
1363 ) -> Result<(), TypeError<'a>> {
1364 use MirRelationExpr::*;
1365
1366 self.checked_recur(|tc| {
1367 match expr {
1368 Get {
1369 id: Id::Local(id),
1370 typ,
1371 ..
1372 } => {
1373 if !ids.contains(id) {
1374 return Ok(());
1375 }
1376
1377 let id = Id::Local(id.clone());
1378 if let Some(ctx_typ) = ctx.get_mut(&id) {
1379 let typ = typ.column_types.iter().cloned().collect_vec();
1380
1381 if ctx_typ.len() != typ.len() {
1382 let diffs = relation_subtype_difference(&typ, ctx_typ);
1383
1384 return Err(TypeError::MismatchColumns {
1385 source: expr,
1386 got: typ,
1387 expected: ctx_typ.clone(),
1388 diffs,
1389 message: "environment and type annotation did not match"
1390 .to_string(),
1391 });
1392 }
1393
1394 for (base_col, input_col) in ctx_typ.iter_mut().zip_eq(typ) {
1395 let diffs = column_union(base_col, &input_col);
1396 if !diffs.is_empty() {
1397 return Err(TypeError::MismatchColumn {
1398 source: expr,
1399 got: input_col,
1400 expected: base_col.clone(),
1401 diffs,
1402 message:
1403 "couldn't compute union of column types in Get and context"
1404 .to_string(),
1405 });
1406 }
1407 }
1408 } else {
1409 ctx.insert(id, typ.column_types.iter().cloned().collect_vec());
1410 }
1411 }
1412 Get {
1413 id: Id::Global(..), ..
1414 }
1415 | Constant { .. } => (),
1416 Let { id, value, body } => {
1417 tc.collect_recursive_variable_types(value, ids, ctx)?;
1418
1419 if ids.contains(id) {
1421 return Err(TypeError::Shadowing {
1422 source: expr,
1423 id: Id::Local(*id),
1424 });
1425 }
1426
1427 tc.collect_recursive_variable_types(body, ids, ctx)?;
1428 }
1429 LetRec {
1430 ids: inner_ids,
1431 values,
1432 body,
1433 limits: _,
1434 } => {
1435 for inner_id in inner_ids {
1436 if ids.contains(inner_id) {
1437 return Err(TypeError::Shadowing {
1438 source: expr,
1439 id: Id::Local(*inner_id),
1440 });
1441 }
1442 }
1443
1444 for value in values {
1445 tc.collect_recursive_variable_types(value, ids, ctx)?;
1446 }
1447
1448 tc.collect_recursive_variable_types(body, ids, ctx)?;
1449 }
1450 Project { input, .. }
1451 | Map { input, .. }
1452 | FlatMap { input, .. }
1453 | Filter { input, .. }
1454 | Reduce { input, .. }
1455 | TopK { input, .. }
1456 | Negate { input }
1457 | Threshold { input }
1458 | ArrangeBy { input, .. } => {
1459 tc.collect_recursive_variable_types(input, ids, ctx)?;
1460 }
1461 Join { inputs, .. } => {
1462 for input in inputs {
1463 tc.collect_recursive_variable_types(input, ids, ctx)?;
1464 }
1465 }
1466 Union { base, inputs } => {
1467 tc.collect_recursive_variable_types(base, ids, ctx)?;
1468
1469 for input in inputs {
1470 tc.collect_recursive_variable_types(input, ids, ctx)?;
1471 }
1472 }
1473 }
1474
1475 Ok(())
1476 })
1477 }
1478
1479 fn typecheck_scalar<'a>(
1487 &self,
1488 expr: &'a MirScalarExpr,
1489 source: &'a MirRelationExpr,
1490 column_types: &[ReprColumnType],
1491 ) -> Result<ReprColumnType, TypeError<'a>> {
1492 use MirScalarExpr::*;
1493
1494 let mut types = Vec::<ReprColumnType>::new();
1495
1496 expr.try_visit_post(&mut |e: &'a MirScalarExpr| -> Result<(), TypeError<'a>> {
1497 let typ = match e {
1498 Column(i, _) => match column_types.get(*i) {
1499 Some(ty) => ty.clone(),
1500 None => {
1501 return Err(TypeError::NoSuchColumn {
1502 source,
1503 expr: e,
1504 col: *i,
1505 });
1506 }
1507 },
1508 Literal(row, typ) => {
1509 let typ = typ.clone();
1510 if let Ok(row) = row {
1511 let datums = row.unpack();
1512 row_difference_with_column_types(
1513 source,
1514 &datums,
1515 std::slice::from_ref(&typ),
1516 )?;
1517 }
1518
1519 typ
1520 }
1521 CallUnmaterializable(func) => func.output_type(),
1522 CallUnary { expr: _, func } => {
1523 let typ_in = types.pop().expect("CallUnary child");
1524 func.output_type(typ_in)
1525 }
1526 CallBinary {
1527 expr1: _,
1528 expr2: _,
1529 func,
1530 } => {
1531 let typ_in2 = types.pop().expect("CallBinary children");
1532 let typ_in1 = types.pop().expect("CallBinary children");
1533 func.output_type(&[typ_in1, typ_in2])
1534 }
1535 CallVariadic { exprs, func } => {
1536 assert!(types.len() >= exprs.len(), "CallVariadic children");
1537 let typ_in = types.split_off(types.len() - exprs.len());
1538 func.output_type(typ_in)
1539 }
1540 If {
1541 cond: _,
1542 then: _,
1543 els: _,
1544 } => {
1545 let else_type = types.pop().expect("If children");
1546 let mut then_type = types.pop().expect("If children");
1547 let cond_type = types.pop().expect("If children");
1548
1549 if cond_type.scalar_type != ReprScalarType::Bool {
1553 let sub = cond_type.scalar_type.clone();
1554
1555 return Err(TypeError::MismatchColumn {
1556 source,
1557 got: cond_type,
1558 expected: ReprColumnType {
1559 scalar_type: ReprScalarType::Bool,
1560 nullable: true,
1561 },
1562 diffs: vec![ReprColumnTypeDifference::NotSubtype {
1563 sub,
1564 sup: ReprScalarType::Bool,
1565 }],
1566 message: "expected boolean condition".to_string(),
1567 });
1568 }
1569
1570 let diffs = column_union(&mut then_type, &else_type);
1571 if !diffs.is_empty() {
1572 return Err(TypeError::MismatchColumn {
1573 source,
1574 got: then_type,
1575 expected: else_type,
1576 diffs,
1577 message: "couldn't compute union of column types for If".to_string(),
1578 });
1579 }
1580
1581 then_type
1582 }
1583 };
1584
1585 types.push(typ);
1586 Ok(())
1587 })?;
1588
1589 let typ = types.pop().expect("root type");
1590 assert!(
1591 types.is_empty(),
1592 "typecheck_scalar left {} types unconsumed",
1593 types.len()
1594 );
1595 Ok(typ)
1596 }
1597
1598 pub fn typecheck_aggregate<'a>(
1600 &self,
1601 expr: &'a AggregateExpr,
1602 source: &'a MirRelationExpr,
1603 column_types: &[ReprColumnType],
1604 ) -> Result<ReprColumnType, TypeError<'a>> {
1605 let t_in = self.typecheck_scalar(&expr.expr, source, column_types)?;
1606
1607 Ok(expr.func.output_type(t_in))
1610 }
1611}
1612
1613macro_rules! type_error {
1617 ($severity:expr, $($arg:tt)+) => {{
1618 if $severity {
1619 soft_panic_or_log!($($arg)+);
1620 } else {
1621 ::tracing::debug!($($arg)+);
1622 }
1623 }}
1624}
1625
1626impl crate::Transform for Typecheck {
1627 fn name(&self) -> &'static str {
1628 "Typecheck"
1629 }
1630
1631 fn actually_perform_transform(
1632 &self,
1633 relation: &mut MirRelationExpr,
1634 transform_ctx: &mut crate::TransformCtx,
1635 ) -> Result<(), crate::TransformError> {
1636 let mut typecheck_ctx = self.ctx.lock().expect("typecheck ctx");
1637
1638 let expected = transform_ctx
1639 .global_id
1640 .map_or_else(|| None, |id| typecheck_ctx.get(&Id::Global(id)));
1641
1642 if let Some(id) = transform_ctx.global_id {
1643 if self.disallow_new_globals
1644 && expected.is_none()
1645 && transform_ctx.global_id.is_some()
1646 && !id.is_transient()
1647 {
1648 type_error!(
1649 false, "type warning: new non-transient global id {id}\n{}",
1651 relation.pretty()
1652 );
1653 }
1654 }
1655
1656 let got = self.typecheck(relation, &typecheck_ctx);
1657
1658 let humanizer = mz_repr::explain::DummyHumanizer;
1659
1660 match (got, expected) {
1661 (Ok(got), Some(expected)) => {
1662 let id = transform_ctx.global_id.unwrap();
1663
1664 let diffs = relation_subtype_difference(expected, &got);
1666 if !diffs.is_empty() {
1667 let severity = diffs
1669 .iter()
1670 .any(|diff| diff.clone().ignore_nullability().is_some());
1671
1672 let err = TypeError::MismatchColumns {
1673 source: relation,
1674 got,
1675 expected: expected.clone(),
1676 diffs,
1677 message: format!(
1678 "a global id {id}'s type changed (was `expected` which should be a subtype of `got`) "
1679 ),
1680 };
1681
1682 type_error!(severity, "type error in known global id {id}:\n{err}");
1683 }
1684 }
1685 (Ok(got), None) => {
1686 if let Some(id) = transform_ctx.global_id {
1687 typecheck_ctx.insert(Id::Global(id), got);
1688 }
1689 }
1690 (Err(err), _) => {
1691 let (expected, binding) = match expected {
1692 Some(expected) => {
1693 let id = transform_ctx.global_id.unwrap();
1694 (
1695 format!("expected type {}\n", columns_pretty(expected, &humanizer)),
1696 format!("known global id {id}"),
1697 )
1698 }
1699 None => ("".to_string(), "transient query".to_string()),
1700 };
1701
1702 type_error!(
1703 true, "type error in {binding}:\n{err}\n{expected}{}",
1705 relation.pretty()
1706 );
1707 }
1708 }
1709
1710 Ok(())
1711 }
1712}
1713
1714pub fn columns_pretty<H>(cols: &[ReprColumnType], humanizer: &H) -> String
1716where
1717 H: ExprHumanizer,
1718{
1719 let mut s = String::with_capacity(2 + 3 * cols.len());
1720
1721 s.push('(');
1722
1723 let mut it = cols.iter().peekable();
1724 while let Some(col) = it.next() {
1725 s.push_str(&humanizer.humanize_column_type(col));
1726
1727 if it.peek().is_some() {
1728 s.push_str(", ");
1729 }
1730 }
1731
1732 s.push(')');
1733
1734 s
1735}
1736
1737impl ReprRelationTypeDifference {
1738 pub fn humanize<H>(&self, h: &H, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
1742 where
1743 H: ExprHumanizer,
1744 {
1745 use ReprRelationTypeDifference::*;
1746 match self {
1747 Length { len_sub, len_sup } => {
1748 writeln!(
1749 f,
1750 " number of columns do not match ({len_sub} != {len_sup})"
1751 )
1752 }
1753 Column { col, diff } => {
1754 writeln!(f, " column {col} differs:")?;
1755 diff.humanize(4, h, f)
1756 }
1757 }
1758 }
1759}
1760
1761impl ReprColumnTypeDifference {
1762 pub fn humanize<H>(
1764 &self,
1765 indent: usize,
1766 h: &H,
1767 f: &mut std::fmt::Formatter<'_>,
1768 ) -> std::fmt::Result
1769 where
1770 H: ExprHumanizer,
1771 {
1772 use ReprColumnTypeDifference::*;
1773
1774 write!(f, "{:indent$}", "")?;
1776
1777 match self {
1778 NotSubtype { sub, sup } => {
1779 let sub = h.humanize_scalar_type(sub);
1780 let sup = h.humanize_scalar_type(sup);
1781
1782 writeln!(f, "{sub} is a not a subtype of {sup}")
1783 }
1784 Nullability { sub, sup } => {
1785 let sub = h.humanize_column_type(sub);
1786 let sup = h.humanize_column_type(sup);
1787
1788 writeln!(f, "{sub} is nullable but {sup} is not")
1789 }
1790 ElementType { ctor, element_type } => {
1791 writeln!(f, "{ctor} element types differ:")?;
1792
1793 element_type.humanize(indent + 2, h, f)
1794 }
1795 RecordMissingFields { missing } => {
1796 write!(f, "missing column fields:")?;
1797 for col in missing {
1798 write!(f, " {col}")?;
1799 }
1800 f.write_char('\n')
1801 }
1802 RecordFields { fields } => {
1803 writeln!(f, "{} record fields differ:", fields.len())?;
1804
1805 for (i, diff) in fields.iter().enumerate() {
1806 writeln!(f, "{:indent$} field {i}:", "")?;
1807 diff.humanize(indent + 4, h, f)?;
1808 }
1809 Ok(())
1810 }
1811 }
1812 }
1813}
1814
1815impl DatumTypeDifference {
1816 pub fn humanize<H>(
1818 &self,
1819 indent: usize,
1820 h: &H,
1821 f: &mut std::fmt::Formatter<'_>,
1822 ) -> std::fmt::Result
1823 where
1824 H: ExprHumanizer,
1825 {
1826 write!(f, "{:indent$}", "")?;
1828
1829 match self {
1830 DatumTypeDifference::Null { expected } => {
1831 let expected = h.humanize_scalar_type(expected);
1832 writeln!(
1833 f,
1834 "unexpected null, expected representation type {expected}"
1835 )?
1836 }
1837 DatumTypeDifference::Mismatch {
1838 got_debug,
1839 expected,
1840 } => {
1841 let expected = h.humanize_scalar_type(expected);
1842 writeln!(
1844 f,
1845 "got datum {got_debug}, expected representation type {expected}"
1846 )?;
1847 }
1848 DatumTypeDifference::MismatchDimensions {
1849 ctor,
1850 got,
1851 expected,
1852 } => {
1853 writeln!(
1854 f,
1855 "{ctor} dimensions differ: got datum with dimension {got}, expected dimension {expected}"
1856 )?;
1857 }
1858 DatumTypeDifference::ElementType { ctor, element_type } => {
1859 writeln!(f, "{ctor} element types differ:")?;
1860 element_type.humanize(indent + 4, h, f)?;
1861 }
1862 }
1863
1864 Ok(())
1865 }
1866}
1867
1868#[allow(missing_debug_implementations)]
1870pub struct TypeErrorHumanizer<'a, 'b, H>
1871where
1872 H: ExprHumanizer,
1873{
1874 err: &'a TypeError<'a>,
1875 humanizer: &'b H,
1876}
1877
1878impl<'a, 'b, H> TypeErrorHumanizer<'a, 'b, H>
1879where
1880 H: ExprHumanizer,
1881{
1882 pub fn new(err: &'a TypeError, humanizer: &'b H) -> Self {
1884 Self { err, humanizer }
1885 }
1886}
1887
1888impl<'a, 'b, H> std::fmt::Display for TypeErrorHumanizer<'a, 'b, H>
1889where
1890 H: ExprHumanizer,
1891{
1892 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1893 self.err.humanize(self.humanizer, f)
1894 }
1895}
1896
1897impl<'a> std::fmt::Display for TypeError<'a> {
1898 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1899 TypeErrorHumanizer {
1900 err: self,
1901 humanizer: &DummyHumanizer,
1902 }
1903 .fmt(f)
1904 }
1905}
1906
1907impl<'a> TypeError<'a> {
1908 pub fn source(&self) -> Option<&'a MirRelationExpr> {
1910 use TypeError::*;
1911 match self {
1912 Unbound { source, .. }
1913 | NoSuchColumn { source, .. }
1914 | MismatchColumn { source, .. }
1915 | MismatchColumns { source, .. }
1916 | BadConstantRowLen { source, .. }
1917 | BadConstantRow { source, .. }
1918 | BadProject { source, .. }
1919 | BadJoinEquivalence { source, .. }
1920 | BadTopKGroupKey { source, .. }
1921 | BadTopKOrdering { source, .. }
1922 | BadLetRecBindings { source }
1923 | Shadowing { source, .. }
1924 | DisallowedDummy { source, .. } => Some(source),
1925 Recursion { .. } => None,
1926 }
1927 }
1928
1929 fn humanize<H>(&self, humanizer: &H, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
1930 where
1931 H: ExprHumanizer,
1932 {
1933 if let Some(source) = self.source() {
1934 writeln!(f, "In the MIR term:\n{}\n", source.pretty())?;
1935 }
1936
1937 use TypeError::*;
1938 match self {
1939 Unbound { source: _, id, typ } => {
1940 let typ = columns_pretty(&typ.column_types, humanizer);
1941 writeln!(f, "{id} is unbound\ndeclared type {typ}")?
1942 }
1943 NoSuchColumn {
1944 source: _,
1945 expr,
1946 col,
1947 } => writeln!(f, "{expr} references non-existent column {col}")?,
1948 MismatchColumn {
1949 source: _,
1950 got,
1951 expected,
1952 diffs,
1953 message,
1954 } => {
1955 let got = humanizer.humanize_column_type(got);
1956 let expected = humanizer.humanize_column_type(expected);
1957 writeln!(
1958 f,
1959 "mismatched column types: {message}\n got {got}\nexpected {expected}"
1960 )?;
1961
1962 for diff in diffs {
1963 diff.humanize(2, humanizer, f)?;
1964 }
1965 }
1966 MismatchColumns {
1967 source: _,
1968 got,
1969 expected,
1970 diffs,
1971 message,
1972 } => {
1973 let got = columns_pretty(got, humanizer);
1974 let expected = columns_pretty(expected, humanizer);
1975
1976 writeln!(
1977 f,
1978 "mismatched relation types: {message}\n got {got}\nexpected {expected}"
1979 )?;
1980
1981 for diff in diffs {
1982 diff.humanize(humanizer, f)?;
1983 }
1984 }
1985 BadConstantRowLen {
1986 source: _,
1987 got,
1988 expected,
1989 } => {
1990 let expected = columns_pretty(expected, humanizer);
1991 writeln!(
1992 f,
1993 "bad constant row\n row has length {got}\nexpected row of type {expected}"
1994 )?
1995 }
1996 BadConstantRow {
1997 source: _,
1998 mismatches,
1999 expected,
2000 } => {
2001 let expected = columns_pretty(expected, humanizer);
2002
2003 let num_mismatches = mismatches.len();
2004 let plural = if num_mismatches == 1 { "" } else { "es" };
2005 writeln!(
2006 f,
2007 "bad constant row\n got {num_mismatches} mismatch{plural}\nexpected row of type {expected}"
2008 )?;
2009
2010 if num_mismatches > 0 {
2011 writeln!(f, "")?;
2012 for (col, diff) in mismatches.iter() {
2013 writeln!(f, " column #{col}:")?;
2014 diff.humanize(8, humanizer, f)?;
2015 }
2016 }
2017 }
2018 BadProject {
2019 source: _,
2020 got,
2021 input_type,
2022 } => {
2023 let input_type = columns_pretty(input_type, humanizer);
2024
2025 writeln!(
2026 f,
2027 "projection of non-existant columns {got:?} from type {input_type}"
2028 )?
2029 }
2030 BadJoinEquivalence {
2031 source: _,
2032 got,
2033 message,
2034 } => {
2035 let got = columns_pretty(got, humanizer);
2036
2037 writeln!(f, "bad join equivalence {got}: {message}")?
2038 }
2039 BadTopKGroupKey {
2040 source: _,
2041 k,
2042 input_type,
2043 } => {
2044 let input_type = columns_pretty(input_type, humanizer);
2045
2046 writeln!(
2047 f,
2048 "TopK group key component references invalid column {k} in columns: {input_type}"
2049 )?
2050 }
2051 BadTopKOrdering {
2052 source: _,
2053 order,
2054 input_type,
2055 } => {
2056 let col = order.column;
2057 let num_cols = input_type.len();
2058 let are = if num_cols == 1 { "is" } else { "are" };
2059 let s = if num_cols == 1 { "" } else { "s" };
2060 let input_type = columns_pretty(input_type, humanizer);
2061
2062 let mode = HumanizedExplain::new(false);
2064 let order = mode.expr(order, None);
2065
2066 writeln!(
2067 f,
2068 "TopK ordering {order} references invalid column {col}\nthere {are} {num_cols} column{s}: {input_type}"
2069 )?
2070 }
2071 BadLetRecBindings { source: _ } => {
2072 writeln!(f, "LetRec ids and definitions don't line up")?
2073 }
2074 Shadowing { source: _, id } => writeln!(f, "id {id} is shadowed")?,
2075 DisallowedDummy { source: _ } => writeln!(f, "contains a dummy value")?,
2076 Recursion { error } => writeln!(f, "{error}")?,
2077 }
2078
2079 Ok(())
2080 }
2081}
2082
2083#[cfg(test)]
2084mod tests {
2085 use mz_ore::{assert_err, assert_ok};
2086 use mz_repr::{SqlColumnType, arb_datum, arb_datum_for_column};
2087 use proptest::prelude::*;
2088
2089 use super::*;
2090
2091 #[mz_ore::test]
2092 fn test_datum_type_difference() {
2093 let datum = Datum::Int16(1);
2094
2095 assert_ok!(datum_difference_with_column_type(
2096 &datum,
2097 &ReprColumnType {
2098 scalar_type: ReprScalarType::Int16,
2099 nullable: true,
2100 }
2101 ));
2102
2103 assert_err!(datum_difference_with_column_type(
2104 &datum,
2105 &ReprColumnType {
2106 scalar_type: ReprScalarType::Int32,
2107 nullable: false,
2108 }
2109 ));
2110 }
2111
2112 proptest! {
2113 #![proptest_config(ProptestConfig {
2114 cases: 5000,
2115 max_global_rejects: 2500,
2116 ..Default::default()
2117 })]
2118 #[mz_ore::test]
2119 #[cfg_attr(miri, ignore)]
2120 fn datum_type_difference_with_instance_of_on_valid_data(
2121 (src, datum) in any::<SqlColumnType>()
2122 .prop_flat_map(|src| {
2123 let datum = arb_datum_for_column(src.clone());
2124 (Just(src), datum)
2125 })
2126 ) {
2127 let typ = ReprColumnType::from(&src);
2128 let datum = Datum::from(&datum);
2129
2130 if datum.contains_dummy() {
2131 return Err(TestCaseError::reject("datum contains a dummy"));
2132 }
2133
2134 let diff = datum_difference_with_column_type(&datum, &typ);
2135 if datum.is_instance_of(&typ) {
2136 assert_ok!(diff);
2137 } else {
2138 assert_err!(diff);
2139 }
2140 }
2141 }
2142
2143 proptest! {
2144 #![proptest_config(ProptestConfig::with_cases(10000))]
2147 #[mz_ore::test]
2148 #[cfg_attr(miri, ignore)]
2149 fn datum_type_difference_agrees_with_is_instance_of_on_random_data(
2150 src in any::<SqlColumnType>(),
2151 datum in arb_datum(false),
2152 ) {
2153 let typ = ReprColumnType::from(&src);
2154 let datum = Datum::from(&datum);
2155
2156 assert!(!datum.contains_dummy(), "datum contains a dummy (bug in arb_datum)");
2157
2158 let diff = datum_difference_with_column_type(&datum, &typ);
2159 if datum.is_instance_of(&typ) {
2160 assert_ok!(diff);
2161 } else {
2162 assert_err!(diff);
2163 }
2164 }
2165 }
2166
2167 #[mz_ore::test]
2168 fn datum_type_difference_github_10039() {
2169 let typ = ReprColumnType {
2170 scalar_type: ReprScalarType::Record {
2171 fields: Box::new([ReprColumnType {
2172 scalar_type: ReprScalarType::UInt32,
2173 nullable: false,
2174 }]),
2175 },
2176 nullable: false,
2177 };
2178
2179 let mut row = mz_repr::Row::default();
2180 row.packer()
2181 .push_list(std::iter::once(mz_repr::Datum::Null));
2182 let datum = row.unpack_first();
2183
2184 assert!(!datum.is_instance_of(&typ));
2185 let diff = datum_difference_with_column_type(&datum, &typ);
2186 assert_err!(diff);
2187 }
2188 #[mz_ore::test]
2201 #[cfg_attr(miri, ignore)] fn deep_scalar_typechecks_without_recursing() {
2203 const DEPTH: usize = 20 * RECURSION_LIMIT;
2204 const THREAD_STACK_SIZE: usize = 256 << 10;
2205
2206 std::thread::Builder::new()
2207 .stack_size(THREAD_STACK_SIZE)
2208 .spawn(|| {
2209 let mut expr = MirScalarExpr::column(0);
2210 for _ in 0..DEPTH {
2211 expr = expr.not();
2212 }
2213
2214 let source = MirRelationExpr::constant(vec![], ReprRelationType::empty());
2215 let column_types = vec![ReprColumnType {
2216 scalar_type: ReprScalarType::Bool,
2217 nullable: false,
2218 }];
2219
2220 let outcome = Typecheck::new(empty_typechecking_context())
2221 .typecheck_scalar(&expr, &source, &column_types)
2222 .map(|typ| typ.scalar_type)
2223 .map_err(|err| match err {
2224 TypeError::Recursion { .. } => "recursion limit",
2225 _ => "type error",
2226 });
2227
2228 while let MirScalarExpr::CallUnary { expr: inner, .. } = expr {
2229 expr = *inner;
2230 }
2231
2232 assert_eq!(outcome, Ok(ReprScalarType::Bool));
2233 })
2234 .expect("spawn")
2235 .join()
2236 .expect("deep scalar typecheck panicked");
2237 }
2238}