1use std::collections::BTreeMap;
11use std::fmt::Debug;
12
13use mz_repr::{Datum, ReprColumnType, ReprRelationType, ReprScalarType, Row, RowArena};
14
15use crate::scalar::func::variadic::And;
16use crate::scalar::optimizable::OptimizableExpr;
17use crate::{
18 BinaryFunc, Eval, EvalError, MapFilterProject, MfpPlan, MirScalarExpr, UnaryFunc,
19 UnmaterializableFunc, VariadicFunc, func,
20};
21fn datum_is_nan(datum: Datum) -> bool {
30 match datum {
31 Datum::Float32(f) => f.is_nan(),
32 Datum::Float64(f) => f.is_nan(),
33 Datum::Numeric(n) => n.0.is_nan(),
34 _ => false,
35 }
36}
37
38fn datum_is_infinite(datum: Datum) -> bool {
40 match datum {
41 Datum::Float32(f) => f.is_infinite(),
42 Datum::Float64(f) => f.is_infinite(),
43 Datum::Numeric(n) => n.0.is_infinite(),
44 _ => false,
45 }
46}
47
48#[derive(Clone, Eq, PartialEq, Debug)]
50enum Values<'a> {
51 Empty,
53 Within(Datum<'a>, Datum<'a>),
56 Nested(BTreeMap<Datum<'a>, ResultSpec<'a>>),
60 All,
63}
64
65impl<'a> Values<'a> {
66 fn just(a: Datum<'a>) -> Values<'a> {
67 match a {
68 Datum::Map(datum_map) => Values::Nested(
69 datum_map
70 .iter()
71 .map(|(key, val)| (key.into(), ResultSpec::value(val)))
72 .collect(),
73 ),
74 other => Self::Within(other, other),
75 }
76 }
77
78 fn union(self, other: Values<'a>) -> Values<'a> {
79 match (self, other) {
80 (Values::Empty, r) => r,
81 (r, Values::Empty) => r,
82 (Values::Within(a0, a1), Values::Within(b0, b1)) => {
83 Values::Within(a0.min(b0), a1.max(b1))
84 }
85 (Values::Nested(a), Values::Nested(mut b)) => {
86 let mut merged = BTreeMap::new();
91 for (key, a_spec) in a {
92 if let Some(b_spec) = b.remove(&key) {
93 let unioned = a_spec.union(b_spec);
94 if unioned != ResultSpec::anything() {
95 merged.insert(key, unioned);
96 }
97 }
98 }
99 if merged.is_empty() {
100 Values::All
101 } else {
102 Values::Nested(merged)
103 }
104 }
105 _ => Values::All,
106 }
107 }
108
109 fn intersect(self, other: Values<'a>) -> Values<'a> {
110 match (self, other) {
111 (Values::Empty, _) => Values::Empty,
112 (_, Values::Empty) => Values::Empty,
113 (Values::Within(a0, a1), Values::Within(b0, b1)) => {
114 let min = a0.max(b0);
115 let max = a1.min(b1);
116 if min <= max {
117 Values::Within(min, max)
118 } else {
119 Values::Empty
120 }
121 }
122 (Values::Nested(mut a), Values::Nested(b)) => {
123 for (datum, other_spec) in b {
124 let spec = a.entry(datum).or_insert_with(ResultSpec::anything);
125 *spec = spec.clone().intersect(other_spec);
126 }
127 Values::Nested(a)
128 }
129 (Values::All, v) => v,
130 (v, Values::All) => v,
131 (nested @ Values::Nested(_), Values::Within(_, _))
138 | (Values::Within(_, _), nested @ Values::Nested(_)) => nested,
139 }
140 }
141
142 fn may_contain(&self, value: Datum<'a>) -> bool {
143 match self {
144 Values::Empty => false,
145 Values::Within(min, max) => *min <= value && value <= *max,
146 Values::All => true,
147 Values::Nested(field_map) => match value {
148 Datum::Map(datum_map) => {
149 datum_map
150 .iter()
151 .all(|(key, val)| match field_map.get(&key.into()) {
152 None => true,
153 Some(nested) => nested.may_contain(val),
154 })
155 }
156 _ => false,
157 },
158 }
159 }
160
161 fn as_single(&self) -> Option<Datum<'a>> {
170 match self {
171 Values::Within(a, b) if a == b => Some(*a),
172 _ => None,
173 }
174 }
175}
176
177#[derive(Debug, Clone, Eq, PartialEq)]
182pub struct ResultSpec<'a> {
183 nullable: bool,
185 fallible: bool,
187 values: Values<'a>,
189}
190
191impl<'a> ResultSpec<'a> {
192 pub fn nothing() -> Self {
194 ResultSpec {
195 nullable: false,
196 fallible: false,
197 values: Values::Empty,
198 }
199 }
200
201 pub fn anything() -> Self {
203 ResultSpec {
204 nullable: true,
205 fallible: true,
206 values: Values::All,
207 }
208 }
209
210 pub fn any_infallible() -> Self {
212 ResultSpec {
213 nullable: true,
214 fallible: false,
215 values: Values::All,
216 }
217 }
218
219 pub fn null() -> Self {
221 ResultSpec {
222 nullable: true,
223 ..Self::nothing()
224 }
225 }
226
227 pub fn fails() -> Self {
229 ResultSpec {
230 fallible: true,
231 ..Self::nothing()
232 }
233 }
234
235 pub fn has_type(col: &ReprColumnType, fallible: bool) -> ResultSpec<'a> {
237 let values = match &col.scalar_type {
238 ReprScalarType::Bool => Values::Within(Datum::False, Datum::True),
239 _ => Values::All,
241 };
242 ResultSpec {
243 nullable: col.nullable,
244 fallible,
245 values,
246 }
247 }
248
249 pub fn value(value: Datum<'a>) -> ResultSpec<'a> {
251 match value {
252 Datum::Null => Self::null(),
253 nonnull => ResultSpec {
254 values: Values::just(nonnull),
255 ..Self::nothing()
256 },
257 }
258 }
259
260 pub fn value_between(min: Datum<'a>, max: Datum<'a>) -> ResultSpec<'a> {
269 assert!(!min.is_null());
270 assert!(!max.is_null());
271 if min <= max {
272 ResultSpec {
273 values: Values::Within(min, max),
274 ..ResultSpec::nothing()
275 }
276 } else {
277 ResultSpec::value_all()
278 }
279 }
280
281 pub fn value_all() -> ResultSpec<'a> {
283 ResultSpec {
284 values: Values::All,
285 ..ResultSpec::nothing()
286 }
287 }
288
289 pub fn map_spec(map: BTreeMap<Datum<'a>, ResultSpec<'a>>) -> ResultSpec<'a> {
291 ResultSpec {
292 values: Values::Nested(map),
293 ..ResultSpec::nothing()
294 }
295 }
296
297 pub fn union(self, other: ResultSpec<'a>) -> ResultSpec<'a> {
299 ResultSpec {
300 nullable: self.nullable || other.nullable,
301 fallible: self.fallible || other.fallible,
302 values: self.values.union(other.values),
303 }
304 }
305
306 pub fn intersect(self, other: ResultSpec<'a>) -> ResultSpec<'a> {
308 ResultSpec {
309 nullable: self.nullable && other.nullable,
310 fallible: self.fallible && other.fallible,
311 values: self.values.intersect(other.values),
312 }
313 }
314
315 pub fn may_contain(&self, value: Datum<'a>) -> bool {
317 if value == Datum::Null {
318 return self.nullable;
319 }
320
321 self.values.may_contain(value)
322 }
323
324 pub fn may_fail(&self) -> bool {
326 self.fallible
327 }
328
329 fn is_single_value(&self) -> bool {
337 self.values.as_single().is_some()
338 }
339
340 fn may_be_infinite(&self) -> bool {
344 match &self.values {
345 Values::Within(min, max) => datum_is_infinite(*min) || datum_is_infinite(*max),
346 Values::All => true,
347 Values::Empty | Values::Nested(_) => false,
348 }
349 }
350
351 fn flat_map(
362 &self,
363 is_monotone: bool,
364 mut result_map: impl FnMut(Result<Datum<'a>, EvalError>) -> ResultSpec<'a>,
365 ) -> ResultSpec<'a> {
366 let null_spec = if self.nullable {
367 result_map(Ok(Datum::Null))
368 } else {
369 ResultSpec::nothing()
370 };
371
372 let error_spec = if self.fallible {
373 let map_err = result_map(Err(EvalError::Internal("".into())));
377 let raise_err = ResultSpec::fails();
378 raise_err.union(map_err)
383 } else {
384 ResultSpec::nothing()
385 };
386
387 let values_spec = match self.values {
388 Values::Empty => ResultSpec::nothing(),
389 Values::Within(min, max) if min == max => result_map(Ok(min)),
391 Values::Within(Datum::False, Datum::True) => {
393 result_map(Ok(Datum::False)).union(result_map(Ok(Datum::True)))
394 }
395 Values::Within(min, max) if is_monotone && !datum_is_nan(min) && !datum_is_nan(max) => {
402 let min_result = result_map(Ok(min));
403 let max_result = result_map(Ok(max));
404 match (min_result, max_result) {
413 (
416 ResultSpec {
417 nullable: n1,
418 fallible: f1,
419 values: a_values @ Values::Within(..),
420 },
421 ResultSpec {
422 nullable: n2,
423 fallible: f2,
424 values: b_values @ Values::Within(..),
425 },
426 ) => ResultSpec {
427 nullable: n1 || n2,
428 fallible: f1 || f2,
429 values: a_values.union(b_values),
430 },
431 (
437 ResultSpec {
438 nullable: true,
439 fallible: false,
440 values: Values::Empty,
441 },
442 ResultSpec {
443 nullable: true,
444 fallible: false,
445 values: Values::Empty,
446 },
447 ) => ResultSpec::null(),
448 _ => ResultSpec::anything(),
450 }
451 }
452 Values::Within(_, _) | Values::Nested(_) | Values::All => ResultSpec::anything(),
454 };
455
456 null_spec.union(error_spec).union(values_spec)
457 }
458}
459
460pub trait Interpreter {
469 type Summary: Clone + Debug + Sized;
470
471 fn column(&self, id: usize) -> Self::Summary;
473
474 fn literal(&self, result: &Result<Row, EvalError>, col_type: &ReprColumnType) -> Self::Summary;
477 fn unmaterializable(&self, func: &UnmaterializableFunc) -> Self::Summary;
482
483 fn unary(&self, func: &UnaryFunc, expr: Self::Summary) -> Self::Summary;
485
486 fn binary(&self, func: &BinaryFunc, left: Self::Summary, right: Self::Summary)
488 -> Self::Summary;
489
490 fn variadic(&self, func: &VariadicFunc, exprs: Vec<Self::Summary>) -> Self::Summary;
492
493 fn cond(&self, cond: Self::Summary, then: Self::Summary, els: Self::Summary) -> Self::Summary;
495
496 fn expr<X: Interpretable>(&self, expr: &X) -> Self::Summary {
498 expr.interpret(self)
499 }
500
501 fn mfp_filter<X: Interpretable + OptimizableExpr>(
504 &self,
505 mfp: &MapFilterProject<X>,
506 ) -> Self::Summary {
507 let mfp_eval = MfpEval::new(self, mfp.input_arity, &mfp.expressions);
508 let predicates = mfp
510 .predicates
511 .iter()
512 .map(|(_, e)| mfp_eval.expr(e))
513 .collect();
514 mfp_eval.variadic(&And.into(), predicates)
515 }
516
517 fn mfp_plan_filter<X: Interpretable + OptimizableExpr>(
520 &self,
521 plan: &MfpPlan<X>,
522 ) -> Self::Summary {
523 let mfp_eval = MfpEval::new(self, plan.mfp.input_arity, &plan.mfp.expressions);
524 let mut results: Vec<_> = plan
526 .mfp
527 .predicates
528 .iter()
529 .map(|(_, e)| mfp_eval.expr(e))
530 .collect();
531 let mz_now = mfp_eval.unmaterializable(&UnmaterializableFunc::MzNow);
532 for bound in &plan.lower_bounds {
533 let bound_range = mfp_eval.expr(bound);
534 let result = mfp_eval.binary(&BinaryFunc::Lte(func::Lte), bound_range, mz_now.clone());
535 results.push(result);
536 }
537 for bound in &plan.upper_bounds {
538 let bound_range = mfp_eval.expr(bound);
539 let result = mfp_eval.binary(&BinaryFunc::Gte(func::Gte), bound_range, mz_now.clone());
540 results.push(result);
541 }
542 self.variadic(&And.into(), results)
543 }
544}
545
546pub trait Interpretable {
551 fn interpret<I: Interpreter + ?Sized>(&self, interpreter: &I) -> I::Summary;
553}
554
555impl Interpretable for MirScalarExpr {
556 fn interpret<I: Interpreter + ?Sized>(&self, interpreter: &I) -> I::Summary {
557 match self {
558 MirScalarExpr::Column(id, _name) => interpreter.column(*id),
559 MirScalarExpr::Literal(value, col_type) => interpreter.literal(value, col_type),
560 MirScalarExpr::CallUnmaterializable(func) => interpreter.unmaterializable(func),
561 MirScalarExpr::CallUnary { func, expr } => {
562 let expr_range = expr.interpret(interpreter);
563 interpreter.unary(func, expr_range)
564 }
565 MirScalarExpr::CallBinary { func, expr1, expr2 } => {
566 let expr1_range = expr1.interpret(interpreter);
567 let expr2_range = expr2.interpret(interpreter);
568 interpreter.binary(func, expr1_range, expr2_range)
569 }
570 MirScalarExpr::CallVariadic { func, exprs } => {
571 let exprs: Vec<_> = exprs.iter().map(|e| e.interpret(interpreter)).collect();
572 interpreter.variadic(func, exprs)
573 }
574 MirScalarExpr::If { cond, then, els } => {
575 let cond_range = cond.interpret(interpreter);
576 let then_range = then.interpret(interpreter);
577 let els_range = els.interpret(interpreter);
578 interpreter.cond(cond_range, then_range, els_range)
579 }
580 }
581 }
582}
583
584pub(crate) struct MfpEval<'a, E: Interpreter + ?Sized> {
587 evaluator: &'a E,
588 input_arity: usize,
589 expressions: Vec<E::Summary>,
590}
591
592impl<'a, E: Interpreter + ?Sized> MfpEval<'a, E> {
593 pub(crate) fn new<X: Interpretable>(
594 evaluator: &'a E,
595 input_arity: usize,
596 expressions: &[X],
597 ) -> Self {
598 let mut mfp_eval = MfpEval {
599 evaluator,
600 input_arity,
601 expressions: vec![],
602 };
603 for expr in expressions {
604 let result = mfp_eval.expr(expr);
605 mfp_eval.expressions.push(result);
606 }
607 mfp_eval
608 }
609}
610
611impl<'a, E: Interpreter + ?Sized> Interpreter for MfpEval<'a, E> {
612 type Summary = E::Summary;
613
614 fn column(&self, id: usize) -> Self::Summary {
615 if id < self.input_arity {
616 self.evaluator.column(id)
617 } else {
618 self.expressions[id - self.input_arity].clone()
619 }
620 }
621
622 fn literal(&self, result: &Result<Row, EvalError>, col_type: &ReprColumnType) -> Self::Summary {
623 self.evaluator.literal(result, col_type)
624 }
625
626 fn unmaterializable(&self, func: &UnmaterializableFunc) -> Self::Summary {
627 self.evaluator.unmaterializable(func)
628 }
629
630 fn unary(&self, func: &UnaryFunc, expr: Self::Summary) -> Self::Summary {
631 self.evaluator.unary(func, expr)
632 }
633
634 fn binary(
635 &self,
636 func: &BinaryFunc,
637 left: Self::Summary,
638 right: Self::Summary,
639 ) -> Self::Summary {
640 self.evaluator.binary(func, left, right)
641 }
642
643 fn variadic(&self, func: &VariadicFunc, exprs: Vec<Self::Summary>) -> Self::Summary {
644 self.evaluator.variadic(func, exprs)
645 }
646
647 fn cond(&self, cond: Self::Summary, then: Self::Summary, els: Self::Summary) -> Self::Summary {
648 self.evaluator.cond(cond, then, els)
649 }
650}
651
652struct SpecialUnary {
657 map_fn: for<'a, 'b> fn(&'b ColumnSpecs<'a>, ResultSpec<'a>) -> ResultSpec<'a>,
658 pushdownable: bool,
659}
660
661impl SpecialUnary {
662 fn for_func(func: &UnaryFunc) -> Option<SpecialUnary> {
664 fn eagerly<'b>(
668 spec: ResultSpec<'b>,
669 value_fn: impl FnOnce(Values<'b>) -> ResultSpec<'b>,
670 ) -> ResultSpec<'b> {
671 let result = match spec.values {
672 Values::Empty => ResultSpec::nothing(),
673 other => value_fn(other),
674 };
675 ResultSpec {
676 fallible: spec.fallible || result.fallible,
677 nullable: spec.nullable || result.nullable,
678 values: result.values,
679 }
680 }
681 match func {
682 UnaryFunc::TryParseMonotonicIso8601Timestamp(_) => Some(SpecialUnary {
683 map_fn: |specs, range| {
684 let expr = MirScalarExpr::CallUnary {
685 func: UnaryFunc::TryParseMonotonicIso8601Timestamp(
686 crate::func::TryParseMonotonicIso8601Timestamp,
687 ),
688 expr: Box::new(MirScalarExpr::column(0)),
689 };
690 let eval = |d| specs.eval_result(expr.eval(&[d], specs.arena));
691
692 eagerly(range, |values| {
693 match values {
694 Values::Within(a, b) if a == b => eval(a),
695 Values::Within(a, b) => {
696 let spec = eval(a).union(eval(b));
697 let values_spec = if spec.nullable {
698 ResultSpec::value_all()
704 } else {
705 spec
706 };
707 values_spec.union(ResultSpec::null())
711 }
712 _ => ResultSpec::any_infallible(),
715 }
716 })
717 },
718 pushdownable: true,
719 }),
720 _ => None,
721 }
722 }
723}
724
725struct AbstractFunc {
736 handler: AbstractFuncHandler,
737 pushdownable: (bool, bool),
743}
744
745enum AbstractFuncHandler {
747 Override(for<'a> fn(ResultSpec<'a>, ResultSpec<'a>) -> ResultSpec<'a>),
750 DynamicMonotone(fn(&ResultSpec<'_>, &ResultSpec<'_>) -> (bool, bool)),
757}
758
759impl AbstractFunc {
760 fn for_func(func: &BinaryFunc) -> Option<AbstractFunc> {
762 fn eagerly<'b>(
766 left: ResultSpec<'b>,
767 right: ResultSpec<'b>,
768 value_fn: impl FnOnce(Values<'b>, Values<'b>) -> ResultSpec<'b>,
769 ) -> ResultSpec<'b> {
770 let result = match (left.values, right.values) {
771 (Values::Empty, _) | (_, Values::Empty) => ResultSpec::nothing(),
772 (l, r) => value_fn(l, r),
773 };
774 ResultSpec {
775 fallible: left.fallible || right.fallible || result.fallible,
776 nullable: left.nullable || right.nullable || result.nullable,
777 values: result.values,
778 }
779 }
780
781 fn jsonb_get_string<'b>(
782 left: ResultSpec<'b>,
783 right: ResultSpec<'b>,
784 stringify: bool,
785 ) -> ResultSpec<'b> {
786 eagerly(left, right, |left, right| {
787 let nested_spec = match (left, right) {
788 (Values::Nested(mut map_spec), Values::Within(key, key2)) if key == key2 => {
789 map_spec.remove(&key)
790 }
791 _ => None,
792 };
793
794 if let Some(field_spec) = nested_spec {
795 if stringify {
796 let values = match field_spec.values {
799 Values::Empty => Values::Empty,
800 Values::Within(min @ Datum::String(_), max @ Datum::String(_)) => {
801 Values::Within(min, max)
802 }
803 Values::Within(_, _) | Values::Nested(_) | Values::All => Values::All,
804 };
805 ResultSpec {
806 values,
807 ..field_spec
808 }
809 } else {
810 field_spec
811 }
812 } else {
813 ResultSpec::any_infallible()
818 }
819 })
820 }
821
822 fn eq<'b>(left: ResultSpec<'b>, right: ResultSpec<'b>) -> ResultSpec<'b> {
823 eagerly(left, right, |left, right| {
824 let maybe_true = match left.clone().intersect(right.clone()) {
826 Values::Empty => ResultSpec::nothing(),
827 _ => ResultSpec::value(Datum::True),
828 };
829
830 let maybe_false = match left.union(right) {
833 Values::Within(a, b) if a == b => ResultSpec::nothing(),
834 _ => ResultSpec::value(Datum::False),
835 };
836
837 maybe_true.union(maybe_false)
838 })
839 }
840
841 fn timestamp_plus_interval_monotone(
850 _left: &ResultSpec<'_>,
851 right: &ResultSpec<'_>,
852 ) -> (bool, bool) {
853 let months_zero = matches!(
854 right.values.as_single(),
855 Some(Datum::Interval(i)) if i.months == 0,
856 );
857 (months_zero, months_zero)
858 }
859
860 match func {
861 BinaryFunc::JsonbGetString(_) => Some(AbstractFunc {
862 handler: AbstractFuncHandler::Override(|l, r| jsonb_get_string(l, r, false)),
863 pushdownable: (true, false),
864 }),
865 BinaryFunc::JsonbGetStringStringify(_) => Some(AbstractFunc {
866 handler: AbstractFuncHandler::Override(|l, r| jsonb_get_string(l, r, true)),
867 pushdownable: (true, false),
868 }),
869 BinaryFunc::Eq(_) => Some(AbstractFunc {
870 handler: AbstractFuncHandler::Override(eq),
871 pushdownable: (true, true),
872 }),
873 BinaryFunc::AddTimestampInterval(_)
874 | BinaryFunc::AddTimestampTzInterval(_)
875 | BinaryFunc::SubTimestampInterval(_)
876 | BinaryFunc::SubTimestampTzInterval(_) => Some(AbstractFunc {
877 handler: AbstractFuncHandler::DynamicMonotone(timestamp_plus_interval_monotone),
878 pushdownable: (true, false),
885 }),
886 _ => None,
887 }
888 }
889}
890
891#[derive(Clone, Debug)]
892pub struct ColumnSpec<'a> {
893 pub col_type: ReprColumnType,
894 pub range: ResultSpec<'a>,
895}
896
897#[derive(Clone, Debug)]
903pub struct ColumnSpecs<'a> {
904 pub relation: &'a ReprRelationType,
905 pub columns: Vec<ResultSpec<'a>>,
906 pub unmaterializables: BTreeMap<UnmaterializableFunc, ResultSpec<'a>>,
907 pub arena: &'a RowArena,
908}
909
910impl<'a> ColumnSpecs<'a> {
911 const MAX_EVAL_ARGS: usize = 6;
918
919 pub fn new(relation: &'a ReprRelationType, arena: &'a RowArena) -> Self {
922 let columns = relation
923 .column_types
924 .iter()
925 .map(|ct| ResultSpec::has_type(ct, false))
926 .collect();
927 ColumnSpecs {
928 relation,
929 columns,
930 unmaterializables: Default::default(),
931 arena,
932 }
933 }
934
935 pub fn push_column(&mut self, id: usize, update: ResultSpec<'a>) {
938 let range = self.columns.get_mut(id).expect("valid column id");
939 *range = range.clone().intersect(update);
940 }
941
942 pub fn push_unmaterializable(&mut self, func: UnmaterializableFunc, update: ResultSpec<'a>) {
945 let range = self
946 .unmaterializables
947 .entry(func.clone())
948 .or_insert_with(|| ResultSpec::has_type(&func.output_type(), true));
949 *range = range.clone().intersect(update);
950 }
951
952 fn eval_result<'b, E>(&self, result: Result<Datum<'b>, E>) -> ResultSpec<'a> {
953 match result {
954 Ok(Datum::Null) => ResultSpec {
955 nullable: true,
956 ..ResultSpec::nothing()
957 },
958 Ok(d) => ResultSpec {
959 values: Values::just(self.arena.make_datum(|packer| packer.push(d))),
960 ..ResultSpec::nothing()
961 },
962 Err(_) => ResultSpec {
963 fallible: true,
964 ..ResultSpec::nothing()
965 },
966 }
967 }
968
969 fn set_literal(expr: &mut MirScalarExpr, update: Result<Datum, EvalError>) {
970 match expr {
971 MirScalarExpr::Literal(literal, col_type) => match update {
972 Err(error) => *literal = Err(error),
973 Ok(datum) => {
974 assert!(
975 datum.is_instance_of(col_type),
976 "{datum:?} must be an instance of {col_type:?}"
977 );
978 match literal {
979 Ok(row) => row.packer().push(datum),
981 literal => *literal = Ok(Row::pack_slice(&[datum])),
982 }
983 }
984 },
985 _ => panic!("not a literal"),
986 }
987 }
988
989 fn set_argument(expr: &mut MirScalarExpr, arg: usize, value: Result<Datum, EvalError>) {
990 match (expr, arg) {
991 (MirScalarExpr::CallUnary { expr, .. }, 0) => Self::set_literal(expr, value),
992 (MirScalarExpr::CallBinary { expr1, .. }, 0) => Self::set_literal(expr1, value),
993 (MirScalarExpr::CallBinary { expr2, .. }, 1) => Self::set_literal(expr2, value),
994 (MirScalarExpr::CallVariadic { exprs, .. }, n) if n < exprs.len() => {
995 Self::set_literal(&mut exprs[n], value)
996 }
997 _ => panic!("illegal argument for expression"),
998 }
999 }
1000
1001 fn placeholder(col_type: ReprColumnType) -> MirScalarExpr {
1005 MirScalarExpr::Literal(Err(EvalError::Internal("".into())), col_type)
1006 }
1007}
1008
1009impl<'a> Interpreter for ColumnSpecs<'a> {
1010 type Summary = ColumnSpec<'a>;
1011
1012 fn column(&self, id: usize) -> Self::Summary {
1013 let col_type = self.relation.column_types[id].clone();
1014 let range = self.columns[id].clone();
1015 ColumnSpec { col_type, range }
1016 }
1017
1018 fn literal(&self, result: &Result<Row, EvalError>, col_type: &ReprColumnType) -> Self::Summary {
1019 let col_type = col_type.clone();
1020 let range = self.eval_result(result.as_ref().map(|row| {
1021 self.arena
1022 .make_datum(|packer| packer.push(row.unpack_first()))
1023 }));
1024 ColumnSpec { col_type, range }
1025 }
1026
1027 fn unmaterializable(&self, func: &UnmaterializableFunc) -> Self::Summary {
1028 let col_type = func.output_type();
1029 let range = self
1030 .unmaterializables
1031 .get(func)
1032 .cloned()
1033 .unwrap_or_else(|| ResultSpec::has_type(&func.output_type(), true));
1034 ColumnSpec { col_type, range }
1035 }
1036
1037 fn unary(&self, func: &UnaryFunc, summary: Self::Summary) -> Self::Summary {
1038 let fallible = func.could_error() || summary.range.fallible;
1039 let input_multivalued = !summary.range.is_single_value();
1047 let mapped_spec = if let Some(special) = SpecialUnary::for_func(func) {
1048 (special.map_fn)(self, summary.range)
1049 } else {
1050 let is_monotone = func.is_monotone();
1051 let mut expr = MirScalarExpr::CallUnary {
1052 func: func.clone(),
1053 expr: Box::new(Self::placeholder(summary.col_type.clone())),
1054 };
1055 summary.range.flat_map(is_monotone, |datum| {
1056 Self::set_argument(&mut expr, 0, datum);
1057 self.eval_result(expr.eval(&[], self.arena))
1058 })
1059 };
1060
1061 let col_type = func.output_type(summary.col_type);
1062
1063 let mut range = mapped_spec.intersect(ResultSpec::has_type(&col_type, fallible));
1064 if fallible && input_multivalued {
1068 range.fallible = true;
1069 }
1070 ColumnSpec { col_type, range }
1071 }
1072
1073 fn binary(
1074 &self,
1075 func: &BinaryFunc,
1076 left: Self::Summary,
1077 right: Self::Summary,
1078 ) -> Self::Summary {
1079 let fallible = func.could_error() || left.range.fallible || right.range.fallible;
1080 let inputs_multivalued = !left.range.is_single_value() || !right.range.is_single_value();
1083 let operand_may_be_infinite = left.range.may_be_infinite() || right.range.may_be_infinite();
1084
1085 let special = AbstractFunc::for_func(func);
1086 let (left_monotonic, right_monotonic) = match &special {
1087 Some(AbstractFunc {
1088 handler: AbstractFuncHandler::DynamicMonotone(monotone_fn),
1089 ..
1090 }) => monotone_fn(&left.range, &right.range),
1091 _ => func.is_monotone(),
1092 };
1093
1094 let mapped_spec = match special {
1095 Some(AbstractFunc {
1096 handler: AbstractFuncHandler::Override(f),
1097 ..
1098 }) => f(left.range, right.range),
1099 _ => {
1100 let mut expr = MirScalarExpr::CallBinary {
1101 func: func.clone(),
1102 expr1: Box::new(Self::placeholder(left.col_type.clone())),
1103 expr2: Box::new(Self::placeholder(right.col_type.clone())),
1104 };
1105 left.range.flat_map(left_monotonic, |left_result| {
1106 Self::set_argument(&mut expr, 0, left_result);
1107 right.range.flat_map(right_monotonic, |right_result| {
1108 Self::set_argument(&mut expr, 1, right_result);
1109 self.eval_result(expr.eval(&[], self.arena))
1110 })
1111 })
1112 }
1113 };
1114
1115 let col_type = func.output_type(&[left.col_type, right.col_type]);
1116
1117 let mut range = mapped_spec.intersect(ResultSpec::has_type(&col_type, fallible));
1118 if fallible && inputs_multivalued {
1121 range.fallible = true;
1122 }
1123 if operand_may_be_infinite && !func.is_infinity_monotone() {
1131 range.values = Values::All;
1132 }
1133 ColumnSpec { col_type, range }
1134 }
1135
1136 fn variadic(&self, func: &VariadicFunc, args: Vec<Self::Summary>) -> Self::Summary {
1137 let fallible = func.could_error() || args.iter().any(|s| s.range.fallible);
1138 let inputs_multivalued = args.iter().any(|s| !s.range.is_single_value());
1139 if func.is_associative() && args.len() > 2 {
1140 return args
1143 .into_iter()
1144 .reduce(|a, b| self.variadic(func, vec![a, b]))
1145 .expect("reducing over a non-empty argument list");
1146 }
1147
1148 let mapped_spec = if args.len() >= Self::MAX_EVAL_ARGS {
1149 ResultSpec::anything()
1150 } else {
1151 fn eval_loop<'a>(
1152 is_monotonic: bool,
1153 expr: &mut MirScalarExpr,
1154 args: &[ColumnSpec<'a>],
1155 index: usize,
1156 datum_map: &mut impl FnMut(&MirScalarExpr) -> ResultSpec<'a>,
1157 ) -> ResultSpec<'a> {
1158 if index >= args.len() {
1159 datum_map(expr)
1160 } else {
1161 args[index].range.flat_map(is_monotonic, |datum| {
1162 ColumnSpecs::set_argument(expr, index, datum);
1163 eval_loop(is_monotonic, expr, args, index + 1, datum_map)
1164 })
1165 }
1166 }
1167
1168 let mut fn_expr = MirScalarExpr::CallVariadic {
1169 func: func.clone(),
1170 exprs: args
1171 .iter()
1172 .map(|spec| Self::placeholder(spec.col_type.clone()))
1173 .collect(),
1174 };
1175 eval_loop(func.is_monotone(), &mut fn_expr, &args, 0, &mut |expr| {
1176 self.eval_result(expr.eval(&[], self.arena))
1177 })
1178 };
1179
1180 let col_types = args.into_iter().map(|spec| spec.col_type).collect();
1181 let col_type = func.output_type(col_types);
1182
1183 let mut range = mapped_spec.intersect(ResultSpec::has_type(&col_type, fallible));
1184 if fallible && inputs_multivalued {
1187 range.fallible = true;
1188 }
1189
1190 ColumnSpec { col_type, range }
1191 }
1192
1193 fn cond(&self, cond: Self::Summary, then: Self::Summary, els: Self::Summary) -> Self::Summary {
1194 let col_type = then
1195 .col_type
1196 .union(&els.col_type)
1197 .expect("failed type union for cond during abstract interpretation");
1198
1199 let range = cond
1200 .range
1201 .flat_map(true, |datum| match datum {
1202 Ok(Datum::True) => then.range.clone(),
1203 Ok(Datum::False) | Ok(Datum::Null) => els.range.clone(),
1208 _ => ResultSpec::fails(),
1209 })
1210 .intersect(ResultSpec::has_type(&col_type, true));
1211
1212 ColumnSpec { col_type, range }
1213 }
1214
1215 fn mfp_filter<X: Interpretable + OptimizableExpr>(
1229 &self,
1230 mfp: &MapFilterProject<X>,
1231 ) -> Self::Summary {
1232 let mfp_eval = MfpEval::new(self, mfp.input_arity, &mfp.expressions);
1233 let predicates = mfp
1234 .predicates
1235 .iter()
1236 .map(|(_, e)| mfp_eval.expr(e))
1237 .collect();
1238 let mut result = self.variadic(&And.into(), predicates);
1239 if mfp_eval.expressions.iter().any(|s| s.range.fallible) {
1240 result.range.fallible = true;
1241 }
1242 result
1243 }
1244
1245 fn mfp_plan_filter<X: Interpretable + OptimizableExpr>(
1246 &self,
1247 plan: &MfpPlan<X>,
1248 ) -> Self::Summary {
1249 let mfp_eval = MfpEval::new(self, plan.mfp.input_arity, &plan.mfp.expressions);
1250 let mut results: Vec<_> = plan
1251 .mfp
1252 .predicates
1253 .iter()
1254 .map(|(_, e)| mfp_eval.expr(e))
1255 .collect();
1256 let mz_now = mfp_eval.unmaterializable(&UnmaterializableFunc::MzNow);
1257 for bound in &plan.lower_bounds {
1258 let bound_range = mfp_eval.expr(bound);
1259 let result = mfp_eval.binary(&BinaryFunc::Lte(func::Lte), bound_range, mz_now.clone());
1260 results.push(result);
1261 }
1262 for bound in &plan.upper_bounds {
1263 let bound_range = mfp_eval.expr(bound);
1264 let result = mfp_eval.binary(&BinaryFunc::Gte(func::Gte), bound_range, mz_now.clone());
1265 results.push(result);
1266 }
1267 let mut result = self.variadic(&And.into(), results);
1268 if mfp_eval.expressions.iter().any(|s| s.range.fallible) {
1269 result.range.fallible = true;
1270 }
1271 result
1272 }
1273}
1274
1275#[derive(Debug)]
1284pub struct Trace;
1285
1286#[derive(Copy, Clone, Debug, PartialOrd, PartialEq, Ord, Eq)]
1293pub enum TraceSummary {
1294 Constant,
1297 Dynamic,
1302 Unknown,
1305}
1306
1307impl TraceSummary {
1308 fn apply_fn(self, pushdownable: bool) -> Self {
1313 match self {
1314 TraceSummary::Constant => TraceSummary::Constant,
1315 TraceSummary::Dynamic => match pushdownable {
1316 true => TraceSummary::Dynamic,
1317 false => TraceSummary::Unknown,
1318 },
1319 TraceSummary::Unknown => TraceSummary::Unknown,
1320 }
1321 }
1322
1323 pub fn pushdownable(self) -> bool {
1325 match self {
1326 TraceSummary::Constant | TraceSummary::Dynamic => true,
1327 TraceSummary::Unknown => false,
1328 }
1329 }
1330}
1331
1332impl Interpreter for Trace {
1333 type Summary = TraceSummary;
1334
1335 fn column(&self, _id: usize) -> Self::Summary {
1336 TraceSummary::Dynamic
1337 }
1338
1339 fn literal(
1340 &self,
1341 _result: &Result<Row, EvalError>,
1342 _col_type: &ReprColumnType,
1343 ) -> Self::Summary {
1344 TraceSummary::Constant
1345 }
1346
1347 fn unmaterializable(&self, _func: &UnmaterializableFunc) -> Self::Summary {
1348 TraceSummary::Dynamic
1349 }
1350
1351 fn unary(&self, func: &UnaryFunc, expr: Self::Summary) -> Self::Summary {
1352 let pushdownable = match SpecialUnary::for_func(func) {
1353 None => func.is_monotone(),
1354 Some(special) => special.pushdownable,
1355 };
1356 expr.apply_fn(pushdownable)
1357 }
1358
1359 fn binary(
1360 &self,
1361 func: &BinaryFunc,
1362 left: Self::Summary,
1363 right: Self::Summary,
1364 ) -> Self::Summary {
1365 let (left_pushdownable, right_pushdownable) = match AbstractFunc::for_func(func) {
1366 None => func.is_monotone(),
1367 Some(special) => special.pushdownable,
1368 };
1369 left.apply_fn(left_pushdownable)
1370 .max(right.apply_fn(right_pushdownable))
1371 }
1372
1373 fn variadic(&self, func: &VariadicFunc, exprs: Vec<Self::Summary>) -> Self::Summary {
1374 if !func.is_associative() && exprs.len() >= ColumnSpecs::MAX_EVAL_ARGS {
1375 return TraceSummary::Unknown;
1378 }
1379
1380 let pushdownable_fn = func.is_monotone();
1381 exprs
1382 .into_iter()
1383 .map(|pushdownable_arg| pushdownable_arg.apply_fn(pushdownable_fn))
1384 .max()
1385 .unwrap_or(TraceSummary::Constant)
1386 }
1387
1388 fn cond(&self, cond: Self::Summary, then: Self::Summary, els: Self::Summary) -> Self::Summary {
1389 let cond = cond.min(TraceSummary::Dynamic);
1392 cond.max(then).max(els)
1393 }
1394}
1395
1396#[cfg(test)]
1397mod tests {
1398 use itertools::Itertools;
1399 use mz_repr::adt::datetime::DateTimeUnits;
1400 use mz_repr::{Datum, PropDatum, RowArena, SqlScalarType};
1401 use proptest::prelude::*;
1402 use proptest::sample::{Index, select};
1403
1404 use crate::func::*;
1405 use crate::scalar::func::variadic::Concat;
1406 use crate::{BinaryFunc, MirScalarExpr, UnaryFunc};
1407
1408 use super::*;
1409
1410 #[derive(Debug)]
1411 struct ExpressionData {
1412 relation_type: ReprRelationType,
1413 specs: Vec<ResultSpec<'static>>,
1414 rows: Vec<Row>,
1415 expr: MirScalarExpr,
1416 }
1417
1418 const NUM_TYPE: ReprScalarType = ReprScalarType::Numeric;
1423 static SCALAR_TYPES: &[ReprScalarType] = &[
1424 ReprScalarType::Bool,
1425 ReprScalarType::Jsonb,
1426 NUM_TYPE,
1427 ReprScalarType::Int16,
1428 ReprScalarType::Int32,
1429 ReprScalarType::Int64,
1430 ReprScalarType::UInt16,
1431 ReprScalarType::UInt32,
1432 ReprScalarType::UInt64,
1433 ReprScalarType::Float32,
1434 ReprScalarType::Float64,
1435 ReprScalarType::Date,
1436 ReprScalarType::Time,
1437 ReprScalarType::Timestamp,
1438 ReprScalarType::TimestampTz,
1439 ReprScalarType::MzTimestamp,
1440 ReprScalarType::Interval,
1441 ReprScalarType::String,
1442 ];
1443
1444 const INTERESTING_UNARY_FUNCS: &[UnaryFunc] = {
1445 &[
1446 UnaryFunc::CastNumericToMzTimestamp(CastNumericToMzTimestamp),
1447 UnaryFunc::CastTimestampToMzTimestamp(CastTimestampToMzTimestamp),
1448 UnaryFunc::NegNumeric(NegNumeric),
1449 UnaryFunc::NegFloat64(NegFloat64),
1450 UnaryFunc::CastJsonbToNumeric(CastJsonbToNumeric(None)),
1451 UnaryFunc::CastJsonbToBool(CastJsonbToBool),
1452 UnaryFunc::CastJsonbToString(CastJsonbToString),
1453 UnaryFunc::DateTruncTimestamp(DateTruncTimestamp(DateTimeUnits::Epoch)),
1454 UnaryFunc::ExtractTimestamp(ExtractTimestamp(DateTimeUnits::Epoch)),
1455 UnaryFunc::ExtractDate(ExtractDate(DateTimeUnits::Epoch)),
1456 UnaryFunc::Not(Not),
1457 UnaryFunc::IsNull(IsNull),
1458 UnaryFunc::IsFalse(IsFalse),
1459 UnaryFunc::TryParseMonotonicIso8601Timestamp(TryParseMonotonicIso8601Timestamp),
1460 UnaryFunc::NegInt32(NegInt32),
1465 UnaryFunc::NegInt64(NegInt64),
1466 UnaryFunc::CastInt32ToUint32(CastInt32ToUint32),
1467 UnaryFunc::CastInt64ToInt32(CastInt64ToInt32),
1468 UnaryFunc::CastInt64ToNumeric(CastInt64ToNumeric(None)),
1469 UnaryFunc::CastFloat64ToInt64(CastFloat64ToInt64),
1470 UnaryFunc::CastFloat64ToFloat32(CastFloat64ToFloat32),
1471 UnaryFunc::CastFloat32ToFloat64(CastFloat32ToFloat64),
1472 UnaryFunc::CastNumericToInt64(CastNumericToInt64),
1473 UnaryFunc::CeilNumeric(CeilNumeric),
1474 UnaryFunc::FloorNumeric(FloorNumeric),
1475 UnaryFunc::CastDateToTimestamp(CastDateToTimestamp(None)),
1476 UnaryFunc::CastTimestampToTimestampTz(CastTimestampToTimestampTz {
1477 from: None,
1478 to: None,
1479 }),
1480 UnaryFunc::CastTimestampTzToTimestamp(CastTimestampTzToTimestamp {
1481 from: None,
1482 to: None,
1483 }),
1484 UnaryFunc::ExtractTimestamp(ExtractTimestamp(DateTimeUnits::Year)),
1487 UnaryFunc::ExtractTimestamp(ExtractTimestamp(DateTimeUnits::Month)),
1488 UnaryFunc::ExtractTimestampTz(ExtractTimestampTz(DateTimeUnits::Epoch)),
1489 UnaryFunc::ExtractTimestampTz(ExtractTimestampTz(DateTimeUnits::Year)),
1490 UnaryFunc::CastBoolToInt32(CastBoolToInt32),
1494 UnaryFunc::CastBoolToString(CastBoolToString),
1495 UnaryFunc::NegInt16(NegInt16),
1496 UnaryFunc::CastInt16ToInt32(CastInt16ToInt32),
1497 UnaryFunc::CastInt16ToInt64(CastInt16ToInt64),
1498 UnaryFunc::CastInt16ToFloat32(CastInt16ToFloat32),
1499 UnaryFunc::CastInt16ToFloat64(CastInt16ToFloat64),
1500 UnaryFunc::CastInt16ToUint16(CastInt16ToUint16),
1501 UnaryFunc::CastInt16ToNumeric(CastInt16ToNumeric(None)),
1502 UnaryFunc::CastInt32ToInt16(CastInt32ToInt16),
1503 UnaryFunc::CastInt32ToInt64(CastInt32ToInt64),
1504 UnaryFunc::CastInt32ToFloat32(CastInt32ToFloat32),
1505 UnaryFunc::CastInt32ToFloat64(CastInt32ToFloat64),
1506 UnaryFunc::CastInt32ToUint16(CastInt32ToUint16),
1507 UnaryFunc::CastInt32ToNumeric(CastInt32ToNumeric(None)),
1508 UnaryFunc::CastInt32ToMzTimestamp(CastInt32ToMzTimestamp),
1509 UnaryFunc::CastInt64ToInt16(CastInt64ToInt16),
1510 UnaryFunc::CastInt64ToFloat32(CastInt64ToFloat32),
1511 UnaryFunc::CastInt64ToFloat64(CastInt64ToFloat64),
1512 UnaryFunc::CastInt64ToUint64(CastInt64ToUint64),
1513 UnaryFunc::CastInt64ToMzTimestamp(CastInt64ToMzTimestamp),
1514 UnaryFunc::CastUint64ToUint32(CastUint64ToUint32),
1515 UnaryFunc::CastUint64ToInt32(CastUint64ToInt32),
1516 UnaryFunc::CastUint64ToNumeric(CastUint64ToNumeric(None)),
1517 UnaryFunc::CastUint64ToMzTimestamp(CastUint64ToMzTimestamp),
1518 UnaryFunc::NegFloat32(NegFloat32),
1519 UnaryFunc::FloorFloat32(FloorFloat32),
1520 UnaryFunc::CastFloat32ToInt32(CastFloat32ToInt32),
1521 UnaryFunc::CastFloat32ToNumeric(CastFloat32ToNumeric(None)),
1522 UnaryFunc::FloorFloat64(FloorFloat64),
1523 UnaryFunc::CastFloat64ToInt32(CastFloat64ToInt32),
1524 UnaryFunc::CastFloat64ToUint64(CastFloat64ToUint64),
1525 UnaryFunc::CastFloat64ToNumeric(CastFloat64ToNumeric(None)),
1526 UnaryFunc::RoundNumeric(RoundNumeric),
1527 UnaryFunc::TruncNumeric(TruncNumeric),
1528 UnaryFunc::Log10Numeric(Log10Numeric),
1529 UnaryFunc::CastNumericToFloat64(CastNumericToFloat64),
1530 UnaryFunc::CastNumericToInt32(CastNumericToInt32),
1531 UnaryFunc::CastTimestampToDate(CastTimestampToDate),
1532 UnaryFunc::CastDateToMzTimestamp(CastDateToMzTimestamp),
1533 UnaryFunc::StepMzTimestamp(StepMzTimestamp),
1534 UnaryFunc::CastBoolToStringNonstandard(CastBoolToStringNonstandard),
1538 UnaryFunc::CastBoolToInt64(CastBoolToInt64),
1539 UnaryFunc::CastInt16ToUint32(CastInt16ToUint32),
1540 UnaryFunc::CastInt16ToUint64(CastInt16ToUint64),
1541 UnaryFunc::CastInt32ToUint64(CastInt32ToUint64),
1542 UnaryFunc::CastInt64ToUint16(CastInt64ToUint16),
1543 UnaryFunc::CastInt64ToUint32(CastInt64ToUint32),
1544 UnaryFunc::CastUint16ToUint32(CastUint16ToUint32),
1545 UnaryFunc::CastUint16ToUint64(CastUint16ToUint64),
1546 UnaryFunc::CastUint16ToInt16(CastUint16ToInt16),
1547 UnaryFunc::CastUint16ToInt32(CastUint16ToInt32),
1548 UnaryFunc::CastUint16ToFloat32(CastUint16ToFloat32),
1549 UnaryFunc::CastUint16ToFloat64(CastUint16ToFloat64),
1550 UnaryFunc::CastUint16ToNumeric(CastUint16ToNumeric(None)),
1551 UnaryFunc::CastUint16ToInt64(CastUint16ToInt64),
1552 UnaryFunc::BitNotUint16(BitNotUint16),
1553 UnaryFunc::CastUint32ToUint16(CastUint32ToUint16),
1554 UnaryFunc::CastUint32ToUint64(CastUint32ToUint64),
1555 UnaryFunc::CastUint32ToInt32(CastUint32ToInt32),
1556 UnaryFunc::CastUint32ToInt64(CastUint32ToInt64),
1557 UnaryFunc::CastUint32ToFloat32(CastUint32ToFloat32),
1558 UnaryFunc::CastUint32ToFloat64(CastUint32ToFloat64),
1559 UnaryFunc::CastUint32ToNumeric(CastUint32ToNumeric(None)),
1560 UnaryFunc::CastUint32ToInt16(CastUint32ToInt16),
1561 UnaryFunc::CastUint32ToMzTimestamp(CastUint32ToMzTimestamp),
1562 UnaryFunc::BitNotUint32(BitNotUint32),
1563 UnaryFunc::CastUint64ToUint16(CastUint64ToUint16),
1564 UnaryFunc::CastUint64ToInt16(CastUint64ToInt16),
1565 UnaryFunc::CastUint64ToInt64(CastUint64ToInt64),
1566 UnaryFunc::CastUint64ToFloat32(CastUint64ToFloat32),
1567 UnaryFunc::CastUint64ToFloat64(CastUint64ToFloat64),
1568 UnaryFunc::BitNotUint64(BitNotUint64),
1569 UnaryFunc::CastFloat32ToInt16(CastFloat32ToInt16),
1570 UnaryFunc::CastFloat32ToInt64(CastFloat32ToInt64),
1571 UnaryFunc::CastFloat32ToUint16(CastFloat32ToUint16),
1572 UnaryFunc::CastFloat32ToUint32(CastFloat32ToUint32),
1573 UnaryFunc::CastFloat32ToUint64(CastFloat32ToUint64),
1574 UnaryFunc::CastFloat64ToInt16(CastFloat64ToInt16),
1575 UnaryFunc::CastFloat64ToUint16(CastFloat64ToUint16),
1576 UnaryFunc::CastFloat64ToUint32(CastFloat64ToUint32),
1577 UnaryFunc::CastJsonbToInt16(CastJsonbToInt16),
1578 UnaryFunc::CastJsonbToInt32(CastJsonbToInt32),
1579 UnaryFunc::CastJsonbToInt64(CastJsonbToInt64),
1580 UnaryFunc::CastJsonbToFloat32(CastJsonbToFloat32),
1581 UnaryFunc::CastJsonbToFloat64(CastJsonbToFloat64),
1582 UnaryFunc::CastNumericToInt16(CastNumericToInt16),
1583 UnaryFunc::CastNumericToFloat32(CastNumericToFloat32),
1584 UnaryFunc::CastNumericToUint16(CastNumericToUint16),
1585 UnaryFunc::CastNumericToUint32(CastNumericToUint32),
1586 UnaryFunc::CastNumericToUint64(CastNumericToUint64),
1587 UnaryFunc::CastTimestampTzToDate(CastTimestampTzToDate),
1588 UnaryFunc::CastTimestampTzToMzTimestamp(CastTimestampTzToMzTimestamp),
1589 UnaryFunc::DateTruncTimestampTz(DateTruncTimestampTz(DateTimeUnits::Epoch)),
1590 UnaryFunc::CastDateToTimestampTz(CastDateToTimestampTz(None)),
1591 UnaryFunc::ExtractDate(ExtractDate(DateTimeUnits::Year)),
1592 UnaryFunc::ExtractDate(ExtractDate(DateTimeUnits::Day)),
1593 ]
1594 };
1595
1596 fn unary_typecheck(func: &UnaryFunc, arg: &ReprColumnType) -> bool {
1597 use UnaryFunc::*;
1598 match func {
1599 CastNumericToMzTimestamp(_)
1600 | NegNumeric(_)
1601 | CastNumericToInt64(_)
1602 | CeilNumeric(_)
1603 | FloorNumeric(_)
1604 | RoundNumeric(_)
1605 | TruncNumeric(_)
1606 | Log10Numeric(_)
1607 | CastNumericToFloat64(_)
1608 | CastNumericToInt32(_)
1609 | CastNumericToInt16(_)
1610 | CastNumericToFloat32(_)
1611 | CastNumericToUint16(_)
1612 | CastNumericToUint32(_)
1613 | CastNumericToUint64(_) => arg.scalar_type == NUM_TYPE,
1614 NegFloat64(_)
1615 | CastFloat64ToInt64(_)
1616 | CastFloat64ToFloat32(_)
1617 | FloorFloat64(_)
1618 | CastFloat64ToInt32(_)
1619 | CastFloat64ToUint64(_)
1620 | CastFloat64ToNumeric(_)
1621 | CastFloat64ToInt16(_)
1622 | CastFloat64ToUint16(_)
1623 | CastFloat64ToUint32(_) => arg.scalar_type == ReprScalarType::Float64,
1624 CastFloat32ToFloat64(_)
1625 | NegFloat32(_)
1626 | FloorFloat32(_)
1627 | CastFloat32ToInt32(_)
1628 | CastFloat32ToNumeric(_)
1629 | CastFloat32ToInt16(_)
1630 | CastFloat32ToInt64(_)
1631 | CastFloat32ToUint16(_)
1632 | CastFloat32ToUint32(_)
1633 | CastFloat32ToUint64(_) => arg.scalar_type == ReprScalarType::Float32,
1634 NegInt16(_)
1635 | CastInt16ToInt32(_)
1636 | CastInt16ToInt64(_)
1637 | CastInt16ToFloat32(_)
1638 | CastInt16ToFloat64(_)
1639 | CastInt16ToUint16(_)
1640 | CastInt16ToNumeric(_)
1641 | CastInt16ToUint32(_)
1642 | CastInt16ToUint64(_) => arg.scalar_type == ReprScalarType::Int16,
1643 NegInt32(_)
1644 | CastInt32ToUint32(_)
1645 | CastInt32ToInt16(_)
1646 | CastInt32ToInt64(_)
1647 | CastInt32ToFloat32(_)
1648 | CastInt32ToFloat64(_)
1649 | CastInt32ToUint16(_)
1650 | CastInt32ToNumeric(_)
1651 | CastInt32ToMzTimestamp(_)
1652 | CastInt32ToUint64(_) => arg.scalar_type == ReprScalarType::Int32,
1653 NegInt64(_)
1654 | CastInt64ToInt32(_)
1655 | CastInt64ToNumeric(_)
1656 | CastInt64ToInt16(_)
1657 | CastInt64ToFloat32(_)
1658 | CastInt64ToFloat64(_)
1659 | CastInt64ToUint64(_)
1660 | CastInt64ToMzTimestamp(_)
1661 | CastInt64ToUint16(_)
1662 | CastInt64ToUint32(_) => arg.scalar_type == ReprScalarType::Int64,
1663 CastUint16ToUint32(_)
1664 | CastUint16ToUint64(_)
1665 | CastUint16ToInt16(_)
1666 | CastUint16ToInt32(_)
1667 | CastUint16ToFloat32(_)
1668 | CastUint16ToFloat64(_)
1669 | CastUint16ToNumeric(_)
1670 | CastUint16ToInt64(_)
1671 | BitNotUint16(_) => arg.scalar_type == ReprScalarType::UInt16,
1672 CastUint32ToUint16(_)
1673 | CastUint32ToUint64(_)
1674 | CastUint32ToInt32(_)
1675 | CastUint32ToInt64(_)
1676 | CastUint32ToFloat32(_)
1677 | CastUint32ToFloat64(_)
1678 | CastUint32ToNumeric(_)
1679 | CastUint32ToInt16(_)
1680 | CastUint32ToMzTimestamp(_)
1681 | BitNotUint32(_) => arg.scalar_type == ReprScalarType::UInt32,
1682 CastUint64ToUint32(_)
1683 | CastUint64ToInt32(_)
1684 | CastUint64ToNumeric(_)
1685 | CastUint64ToMzTimestamp(_)
1686 | CastUint64ToUint16(_)
1687 | CastUint64ToInt16(_)
1688 | CastUint64ToInt64(_)
1689 | CastUint64ToFloat32(_)
1690 | CastUint64ToFloat64(_)
1691 | BitNotUint64(_) => arg.scalar_type == ReprScalarType::UInt64,
1692 StepMzTimestamp(_) => arg.scalar_type == ReprScalarType::MzTimestamp,
1693 CastBoolToInt32(_)
1694 | CastBoolToString(_)
1695 | CastBoolToStringNonstandard(_)
1696 | CastBoolToInt64(_) => arg.scalar_type == ReprScalarType::Bool,
1697 CastTimestampToMzTimestamp(_)
1698 | CastTimestampToTimestampTz(_)
1699 | CastTimestampToDate(_) => arg.scalar_type == ReprScalarType::Timestamp,
1700 CastTimestampTzToTimestamp(_)
1701 | ExtractTimestampTz(_)
1702 | CastTimestampTzToDate(_)
1703 | CastTimestampTzToMzTimestamp(_)
1704 | DateTruncTimestampTz(_) => arg.scalar_type == ReprScalarType::TimestampTz,
1705 CastJsonbToNumeric(_)
1706 | CastJsonbToBool(_)
1707 | CastJsonbToString(_)
1708 | CastJsonbToInt16(_)
1709 | CastJsonbToInt32(_)
1710 | CastJsonbToInt64(_)
1711 | CastJsonbToFloat32(_)
1712 | CastJsonbToFloat64(_) => arg.scalar_type == ReprScalarType::Jsonb,
1713 ExtractTimestamp(_) | DateTruncTimestamp(_) => {
1714 arg.scalar_type == ReprScalarType::Timestamp
1715 }
1716 ExtractDate(_)
1717 | CastDateToTimestamp(_)
1718 | CastDateToMzTimestamp(_)
1719 | CastDateToTimestampTz(_) => arg.scalar_type == ReprScalarType::Date,
1720 Not(_) => arg.scalar_type == ReprScalarType::Bool,
1721 IsNull(_) => true,
1722 TryParseMonotonicIso8601Timestamp(_) => arg.scalar_type == ReprScalarType::String,
1723 TryCast(f) => unary_typecheck(f.inner(), arg),
1724 _ => false,
1725 }
1726 }
1727
1728 const TRY_CAST_INNER_FUNCS: &[UnaryFunc] = &[
1732 UnaryFunc::CastInt64ToInt32(CastInt64ToInt32),
1733 UnaryFunc::CastInt32ToInt16(CastInt32ToInt16),
1734 UnaryFunc::CastInt64ToUint64(CastInt64ToUint64),
1735 UnaryFunc::CastFloat64ToInt64(CastFloat64ToInt64),
1736 UnaryFunc::CastFloat64ToFloat32(CastFloat64ToFloat32),
1737 UnaryFunc::CastNumericToInt32(CastNumericToInt32),
1738 UnaryFunc::CastNumericToMzTimestamp(CastNumericToMzTimestamp),
1739 UnaryFunc::CastJsonbToNumeric(CastJsonbToNumeric(None)),
1740 UnaryFunc::CastJsonbToBool(CastJsonbToBool),
1741 UnaryFunc::CastTimestampToMzTimestamp(CastTimestampToMzTimestamp),
1742 ];
1743
1744 fn interesting_unary_funcs() -> Vec<UnaryFunc> {
1748 let mut funcs = INTERESTING_UNARY_FUNCS.to_vec();
1749 funcs.extend(TRY_CAST_INNER_FUNCS.iter().map(|cast| {
1750 assert!(
1751 cast.could_error(),
1752 "{cast} never errors, so wrapping it is pointless"
1753 );
1754 UnaryFunc::try_cast_if_fallible(cast.clone())
1755 }));
1756 funcs
1757 }
1758
1759 fn interesting_binary_funcs() -> Vec<BinaryFunc> {
1760 vec![
1761 AddTimestampInterval.into(),
1762 AddNumeric.into(),
1763 SubNumeric.into(),
1764 MulNumeric.into(),
1765 DivNumeric.into(),
1766 AddFloat64.into(),
1767 SubFloat64.into(),
1768 MulFloat64.into(),
1769 DivFloat64.into(),
1770 MulFloat32.into(),
1771 DivFloat32.into(),
1772 RoundNumericBinary.into(),
1773 Eq.into(),
1774 Lt.into(),
1775 Gt.into(),
1776 Lte.into(),
1777 Gte.into(),
1778 DateTruncUnitsTimestamp.into(),
1779 JsonbGetString.into(),
1780 JsonbGetStringStringify.into(),
1781 AddInt32.into(),
1785 SubInt32.into(),
1786 MulInt32.into(),
1787 DivInt32.into(),
1788 AddInt64.into(),
1789 MulInt64.into(),
1790 AddFloat32.into(),
1791 SubFloat32.into(),
1792 TextConcatBinary.into(),
1794 AddDateInterval.into(),
1796 AddTimeInterval.into(),
1797 SubInt64.into(),
1799 DivInt64.into(),
1800 SubTimestamp.into(),
1801 SubDate.into(),
1802 AddInterval.into(),
1803 SubInterval.into(),
1804 AddInt16.into(),
1807 SubInt16.into(),
1808 MulInt16.into(),
1809 DivInt16.into(),
1810 AddUint16.into(),
1811 SubUint16.into(),
1812 MulUint16.into(),
1813 DivUint16.into(),
1814 AddUint32.into(),
1815 SubUint32.into(),
1816 MulUint32.into(),
1817 DivUint32.into(),
1818 AddUint64.into(),
1819 SubUint64.into(),
1820 MulUint64.into(),
1821 DivUint64.into(),
1822 SubTime.into(),
1823 SubTimestampTz.into(),
1824 AddDateTime.into(),
1825 SubDateInterval.into(),
1826 DateBinTimestamp.into(),
1827 ]
1828 }
1829
1830 fn binary_typecheck(func: &BinaryFunc, arg0: &ReprColumnType, arg1: &ReprColumnType) -> bool {
1831 use BinaryFunc::*;
1832 match func {
1833 AddTimestampInterval(_) => {
1834 arg0.scalar_type == ReprScalarType::Timestamp
1835 && arg1.scalar_type == ReprScalarType::Interval
1836 }
1837 AddNumeric(_) | SubNumeric(_) | MulNumeric(_) | DivNumeric(_) => {
1838 arg0.scalar_type == NUM_TYPE && arg1.scalar_type == NUM_TYPE
1839 }
1840 AddFloat64(_) | SubFloat64(_) | MulFloat64(_) | DivFloat64(_) => {
1841 arg0.scalar_type == ReprScalarType::Float64
1842 && arg1.scalar_type == ReprScalarType::Float64
1843 }
1844 MulFloat32(_) | DivFloat32(_) => {
1845 arg0.scalar_type == ReprScalarType::Float32
1846 && arg1.scalar_type == ReprScalarType::Float32
1847 }
1848 RoundNumeric(_) => {
1849 arg0.scalar_type == NUM_TYPE && arg1.scalar_type == ReprScalarType::Int32
1850 }
1851 Eq(_) | Lt(_) | Gt(_) | Lte(_) | Gte(_) => arg0.scalar_type == arg1.scalar_type,
1852 DateTruncTimestamp(_) => {
1853 arg0.scalar_type == ReprScalarType::String
1854 && arg1.scalar_type == ReprScalarType::Timestamp
1855 }
1856 JsonbGetString(_) | JsonbGetStringStringify(_) => {
1857 arg0.scalar_type == ReprScalarType::Jsonb
1858 && arg1.scalar_type == ReprScalarType::String
1859 }
1860 AddInt32(_) | SubInt32(_) | MulInt32(_) | DivInt32(_) => {
1861 arg0.scalar_type == ReprScalarType::Int32
1862 && arg1.scalar_type == ReprScalarType::Int32
1863 }
1864 AddInt64(_) | MulInt64(_) | SubInt64(_) | DivInt64(_) => {
1865 arg0.scalar_type == ReprScalarType::Int64
1866 && arg1.scalar_type == ReprScalarType::Int64
1867 }
1868 SubTimestamp(_) => {
1869 arg0.scalar_type == ReprScalarType::Timestamp
1870 && arg1.scalar_type == ReprScalarType::Timestamp
1871 }
1872 SubDate(_) => {
1873 arg0.scalar_type == ReprScalarType::Date && arg1.scalar_type == ReprScalarType::Date
1874 }
1875 AddInterval(_) | SubInterval(_) => {
1876 arg0.scalar_type == ReprScalarType::Interval
1877 && arg1.scalar_type == ReprScalarType::Interval
1878 }
1879 AddInt16(_) | SubInt16(_) | MulInt16(_) | DivInt16(_) => {
1880 arg0.scalar_type == ReprScalarType::Int16
1881 && arg1.scalar_type == ReprScalarType::Int16
1882 }
1883 AddUint16(_) | SubUint16(_) | MulUint16(_) | DivUint16(_) => {
1884 arg0.scalar_type == ReprScalarType::UInt16
1885 && arg1.scalar_type == ReprScalarType::UInt16
1886 }
1887 AddUint32(_) | SubUint32(_) | MulUint32(_) | DivUint32(_) => {
1888 arg0.scalar_type == ReprScalarType::UInt32
1889 && arg1.scalar_type == ReprScalarType::UInt32
1890 }
1891 AddUint64(_) | SubUint64(_) | MulUint64(_) | DivUint64(_) => {
1892 arg0.scalar_type == ReprScalarType::UInt64
1893 && arg1.scalar_type == ReprScalarType::UInt64
1894 }
1895 SubTime(_) => {
1896 arg0.scalar_type == ReprScalarType::Time && arg1.scalar_type == ReprScalarType::Time
1897 }
1898 SubTimestampTz(_) => {
1899 arg0.scalar_type == ReprScalarType::TimestampTz
1900 && arg1.scalar_type == ReprScalarType::TimestampTz
1901 }
1902 AddDateTime(_) => {
1903 arg0.scalar_type == ReprScalarType::Date && arg1.scalar_type == ReprScalarType::Time
1904 }
1905 SubDateInterval(_) => {
1906 arg0.scalar_type == ReprScalarType::Date
1907 && arg1.scalar_type == ReprScalarType::Interval
1908 }
1909 DateBinTimestamp(_) => {
1910 arg0.scalar_type == ReprScalarType::Interval
1911 && arg1.scalar_type == ReprScalarType::Timestamp
1912 }
1913 AddFloat32(_) | SubFloat32(_) => {
1914 arg0.scalar_type == ReprScalarType::Float32
1915 && arg1.scalar_type == ReprScalarType::Float32
1916 }
1917 TextConcat(_) => {
1918 arg0.scalar_type == ReprScalarType::String
1919 && arg1.scalar_type == ReprScalarType::String
1920 }
1921 AddDateInterval(_) => {
1922 arg0.scalar_type == ReprScalarType::Date
1923 && arg1.scalar_type == ReprScalarType::Interval
1924 }
1925 AddTimeInterval(_) => {
1926 arg0.scalar_type == ReprScalarType::Time
1927 && arg1.scalar_type == ReprScalarType::Interval
1928 }
1929 _ => false,
1930 }
1931 }
1932
1933 const INTERESTING_VARIADIC_FUNCS: &[VariadicFunc] = {
1934 use crate::scalar::func::variadic as v;
1935 use VariadicFunc::*;
1936 &[
1937 Coalesce(v::Coalesce),
1938 Greatest(v::Greatest),
1939 Least(v::Least),
1940 And(v::And),
1941 Or(v::Or),
1942 Concat(v::Concat),
1943 ConcatWs(v::ConcatWs),
1944 ]
1945 };
1946
1947 fn variadic_typecheck(func: &VariadicFunc, args: &[ReprColumnType]) -> bool {
1948 use VariadicFunc::*;
1949 fn all_eq<'a>(
1950 iter: impl IntoIterator<Item = &'a ReprColumnType>,
1951 other: &ReprScalarType,
1952 ) -> bool {
1953 iter.into_iter().all(|t| t.scalar_type == *other)
1954 }
1955 match func {
1956 Coalesce(_) | Greatest(_) | Least(_) => match args {
1957 [] => true,
1958 [first, rest @ ..] => all_eq(rest, &first.scalar_type),
1959 },
1960 And(_) | Or(_) => all_eq(args, &ReprScalarType::Bool),
1961 Concat(_) => all_eq(args, &ReprScalarType::String),
1962 ConcatWs(_) => args.len() > 1 && all_eq(args, &ReprScalarType::String),
1963 _ => false,
1964 }
1965 }
1966
1967 fn gen_datums_for_type(typ: &ReprColumnType) -> BoxedStrategy<Datum<'static>> {
1968 let mut values: Vec<Datum<'static>> = SqlScalarType::from_repr(&typ.scalar_type)
1969 .interesting_datums()
1970 .collect();
1971 if typ.nullable {
1972 values.push(Datum::Null)
1973 }
1974 select(values).boxed()
1975 }
1976
1977 fn gen_column() -> impl Strategy<Value = (ReprColumnType, Datum<'static>, ResultSpec<'static>)>
1978 {
1979 let col_type = (select(SCALAR_TYPES), any::<bool>())
1980 .prop_map(|(t, b)| t.nullable(b))
1981 .prop_filter("need at least one value", |c| {
1982 SqlScalarType::from_repr(&c.scalar_type)
1983 .interesting_datums()
1984 .count()
1985 > 0
1986 });
1987
1988 let result_spec = select(vec![
1989 ResultSpec::nothing(),
1990 ResultSpec::null(),
1991 ResultSpec::anything(),
1992 ResultSpec::value_all(),
1993 ]);
1994
1995 (col_type, result_spec).prop_flat_map(|(col, result_spec)| {
1996 gen_datums_for_type(&col).prop_map(move |datum| {
1997 let result_spec = result_spec.clone().union(ResultSpec::value(datum));
1998 (col.clone(), datum, result_spec)
1999 })
2000 })
2001 }
2002
2003 fn gen_expr_for_relation(
2004 relation: &ReprRelationType,
2005 ) -> BoxedStrategy<(MirScalarExpr, ReprColumnType)> {
2006 let column_gen = {
2007 let column_types = relation.column_types.clone();
2008 any::<Index>()
2009 .prop_map(move |idx| {
2010 let id = idx.index(column_types.len());
2011 (MirScalarExpr::column(id), column_types[id].clone())
2012 })
2013 .boxed()
2014 };
2015
2016 let literal_gen = (select(SCALAR_TYPES), any::<bool>())
2017 .prop_map(|(s, b)| s.nullable(b))
2018 .prop_flat_map(|ct| {
2019 let error_gen = any::<EvalError>().prop_map(Err).boxed();
2020 let value_gen = gen_datums_for_type(&ct)
2021 .prop_map(move |datum| Ok(Row::pack_slice(&[datum])))
2022 .boxed();
2023 error_gen.prop_union(value_gen).prop_map(move |result| {
2024 (MirScalarExpr::Literal(result, ct.clone()), ct.clone())
2025 })
2026 })
2027 .boxed();
2028
2029 column_gen
2030 .prop_union(literal_gen)
2031 .prop_recursive(4, 64, 8, |self_gen| {
2032 let unary_gen = (select(interesting_unary_funcs()), self_gen.clone())
2033 .prop_filter_map("unary func", |(func, (expr_in, type_in))| {
2034 if !unary_typecheck(&func, &type_in) {
2035 return None;
2036 }
2037 let type_out = func.output_type(type_in);
2038 let expr_out = MirScalarExpr::CallUnary {
2039 func,
2040 expr: Box::new(expr_in),
2041 };
2042 Some((expr_out, type_out))
2043 })
2044 .boxed();
2045 let binary_gen = (
2046 select(interesting_binary_funcs()),
2047 self_gen.clone(),
2048 self_gen.clone(),
2049 )
2050 .prop_filter_map(
2051 "binary func",
2052 |(func, (expr_left, type_left), (expr_right, type_right))| {
2053 if !binary_typecheck(&func, &type_left, &type_right) {
2054 return None;
2055 }
2056 let type_out = func.output_type(&[type_left, type_right]);
2057 let expr_out = MirScalarExpr::CallBinary {
2058 func,
2059 expr1: Box::new(expr_left),
2060 expr2: Box::new(expr_right),
2061 };
2062 Some((expr_out, type_out))
2063 },
2064 )
2065 .boxed();
2066 let variadic_gen = (
2067 select(INTERESTING_VARIADIC_FUNCS),
2068 prop::collection::vec(self_gen.clone(), 1..4),
2069 )
2070 .prop_filter_map("variadic func", |(func, exprs)| {
2071 let (exprs_in, type_in): (_, Vec<_>) = exprs.into_iter().unzip();
2072 if !variadic_typecheck(&func, &type_in) {
2073 return None;
2074 }
2075 let type_out = func.output_type(type_in);
2076 let expr_out = MirScalarExpr::CallVariadic {
2077 func,
2078 exprs: exprs_in,
2079 };
2080 Some((expr_out, type_out))
2081 })
2082 .boxed();
2083 let if_gen = {
2090 let bool_type = ReprScalarType::Bool.nullable(true);
2091 let cond_gen = gen_datums_for_type(&bool_type).prop_map(move |datum| {
2092 MirScalarExpr::Literal(Ok(Row::pack_slice(&[datum])), bool_type.clone())
2093 });
2094 (cond_gen, self_gen.clone())
2095 .prop_flat_map(|(cond_expr, (then_expr, then_type))| {
2096 let out_type = then_type.clone();
2097 gen_datums_for_type(&then_type).prop_map(move |datum| {
2098 let els_expr = MirScalarExpr::Literal(
2099 Ok(Row::pack_slice(&[datum])),
2100 out_type.clone(),
2101 );
2102 let expr_out = MirScalarExpr::If {
2103 cond: Box::new(cond_expr.clone()),
2104 then: Box::new(then_expr.clone()),
2105 els: Box::new(els_expr),
2106 };
2107 (expr_out, out_type.clone())
2108 })
2109 })
2110 .boxed()
2111 };
2112
2113 unary_gen
2114 .prop_union(binary_gen)
2115 .boxed()
2116 .prop_union(variadic_gen)
2117 .boxed()
2118 .prop_union(if_gen)
2119 })
2120 .boxed()
2121 }
2122
2123 fn gen_expr_data() -> impl Strategy<Value = ExpressionData> {
2124 let columns = prop::collection::vec(gen_column(), 1..10);
2125 columns.prop_flat_map(|data| {
2126 let (columns, datums, specs): (Vec<_>, Vec<_>, Vec<_>) = data.into_iter().multiunzip();
2127 let relation = ReprRelationType::new(columns);
2128 let row = Row::pack_slice(&datums);
2129 gen_expr_for_relation(&relation).prop_map(move |(expr, _)| ExpressionData {
2130 relation_type: relation.clone(),
2131 specs: specs.clone(),
2132 rows: vec![row.clone()],
2133 expr,
2134 })
2135 })
2136 }
2137
2138 #[mz_ore::test]
2139 #[cfg_attr(miri, ignore)] fn test_trivial_spec_matches() {
2141 fn check(datum: PropDatum) -> Result<(), TestCaseError> {
2142 let datum: Datum = (&datum).into();
2143 let spec = if datum.is_null() {
2144 ResultSpec::null()
2145 } else {
2146 ResultSpec::value(datum)
2147 };
2148 assert!(spec.may_contain(datum));
2149 Ok(())
2150 }
2151
2152 proptest!(|(datum in mz_repr::arb_datum(true))| {
2153 check(datum)?;
2154 });
2155
2156 assert!(ResultSpec::fails().may_fail());
2157 }
2158
2159 #[mz_ore::test]
2160 #[cfg_attr(miri, ignore)] fn test_equivalence() {
2162 fn check(data: ExpressionData) -> Result<(), TestCaseError> {
2163 let ExpressionData {
2164 relation_type,
2165 specs,
2166 rows,
2167 expr,
2168 } = data;
2169
2170 let arena = RowArena::new();
2174 let mut interpreter = ColumnSpecs::new(&relation_type, &arena);
2175 for (id, spec) in specs.into_iter().enumerate() {
2176 interpreter.push_column(id, spec);
2177 }
2178
2179 let spec = interpreter.expr(&expr);
2180
2181 for row in &rows {
2182 let datums: Vec<_> = row.iter().collect();
2183 let eval_result = expr.eval(&datums, &arena);
2184 match eval_result {
2185 Ok(value) => {
2186 assert!(spec.range.may_contain(value))
2187 }
2188 Err(_) => {
2189 assert!(spec.range.may_fail());
2190 }
2191 }
2192 }
2193
2194 Ok(())
2195 }
2196
2197 proptest!(|(data in gen_expr_data())| {
2198 check(data)?;
2199 });
2200 }
2201
2202 fn gen_range_column()
2211 -> impl Strategy<Value = (ReprColumnType, Datum<'static>, ResultSpec<'static>)> {
2212 select(SCALAR_TYPES)
2213 .prop_map(|t| t.nullable(false))
2214 .prop_filter("need at least two distinct values for a range", |c| {
2215 let mut datums: Vec<Datum> = SqlScalarType::from_repr(&c.scalar_type)
2216 .interesting_datums()
2217 .filter(|d| !d.is_null())
2218 .collect();
2219 datums.sort();
2220 datums.dedup();
2221 datums.len() >= 2
2222 })
2223 .prop_flat_map(|col| {
2224 let mut datums: Vec<Datum<'static>> = SqlScalarType::from_repr(&col.scalar_type)
2225 .interesting_datums()
2226 .filter(|d| !d.is_null())
2227 .collect();
2228 datums.sort();
2229 datums.dedup();
2230 (
2231 Just(col),
2232 Just(datums),
2233 any::<Index>(),
2234 any::<Index>(),
2235 any::<Index>(),
2236 )
2237 .prop_map(|(col, datums, a, b, c)| {
2238 let n = datums.len();
2239 let mut idxs = [a.index(n), b.index(n), c.index(n)];
2240 idxs.sort();
2241 let lo = datums[idxs[0]];
2242 let mid = datums[idxs[1]];
2243 let hi = datums[idxs[2]];
2244 let spec = ResultSpec::value_between(lo, hi);
2245 (col, mid, spec)
2246 })
2247 })
2248 }
2249
2250 fn gen_range_expr_data() -> impl Strategy<Value = ExpressionData> {
2251 let columns = prop::collection::vec(gen_range_column(), 1..10);
2252 columns.prop_flat_map(|data| {
2253 let (columns, datums, specs): (Vec<_>, Vec<_>, Vec<_>) = data.into_iter().multiunzip();
2254 let relation = ReprRelationType::new(columns);
2255 let row = Row::pack_slice(&datums);
2256 gen_expr_for_relation(&relation).prop_map(move |(expr, _)| ExpressionData {
2257 relation_type: relation.clone(),
2258 specs: specs.clone(),
2259 rows: vec![row.clone()],
2260 expr,
2261 })
2262 })
2263 }
2264
2265 #[mz_ore::test]
2276 #[cfg_attr(miri, ignore)] fn test_equivalence_ranges() {
2278 fn check(data: ExpressionData) -> Result<(), TestCaseError> {
2279 let ExpressionData {
2280 relation_type,
2281 specs,
2282 rows,
2283 expr,
2284 } = data;
2285
2286 let arena = RowArena::new();
2287 let mut interpreter = ColumnSpecs::new(&relation_type, &arena);
2288 for (id, spec) in specs.into_iter().enumerate() {
2289 interpreter.push_column(id, spec);
2290 }
2291
2292 let spec = interpreter.expr(&expr);
2293
2294 for row in &rows {
2295 let datums: Vec<_> = row.iter().collect();
2296 let eval_result = expr.eval(&datums, &arena);
2297 match eval_result {
2298 Ok(value) => {
2299 prop_assert!(
2300 spec.range.may_contain(value),
2301 "interpreter ruled out a value the evaluator produced \
2302 for an interior input: expr={expr:?} row={row:?} \
2303 value={value:?} spec={:?}",
2304 spec.range,
2305 );
2306 }
2307 Err(_) => {
2308 prop_assert!(
2309 spec.range.may_fail(),
2310 "interpreter ruled out an error the evaluator produced \
2311 for an interior input: expr={expr:?} row={row:?}",
2312 );
2313 }
2314 }
2315 }
2316
2317 Ok(())
2318 }
2319
2320 let default = ProptestConfig::default();
2329 let cases = if std::env::var_os("PROPTEST_CASES").is_some() {
2330 default.cases
2331 } else {
2332 2048
2333 };
2334 let config = ProptestConfig {
2335 cases,
2336 max_local_rejects: cases.saturating_mul(512),
2337 ..default
2338 };
2339 proptest!(config, |(data in gen_range_expr_data())| {
2340 check(data)?;
2341 });
2342 }
2343
2344 #[mz_ore::test]
2353 #[cfg_attr(miri, ignore)] fn test_result_spec_lattice_laws() {
2355 #[derive(Debug, Clone)]
2358 enum Recipe {
2359 Nothing,
2360 Null,
2361 Fails,
2362 Anything,
2363 ValueAll,
2364 Value(PropDatum),
2365 Between(PropDatum, PropDatum),
2366 Map(Vec<(PropDatum, Recipe)>),
2367 Union(Box<Recipe>, Box<Recipe>),
2368 }
2369
2370 fn materialize(recipe: &Recipe) -> ResultSpec<'_> {
2371 match recipe {
2372 Recipe::Nothing => ResultSpec::nothing(),
2373 Recipe::Null => ResultSpec::null(),
2374 Recipe::Fails => ResultSpec::fails(),
2375 Recipe::Anything => ResultSpec::anything(),
2376 Recipe::ValueAll => ResultSpec::value_all(),
2377 Recipe::Value(pd) => ResultSpec::value(pd.into()),
2378 Recipe::Between(a, b) => {
2379 let (a, b): (Datum, Datum) = (a.into(), b.into());
2380 if a.is_null() || b.is_null() {
2381 ResultSpec::nothing()
2382 } else if a <= b {
2383 ResultSpec::value_between(a, b)
2384 } else {
2385 ResultSpec::value_between(b, a)
2386 }
2387 }
2388 Recipe::Map(entries) => {
2389 let mut map = BTreeMap::new();
2390 for (key, val) in entries {
2391 let key: Datum = key.into();
2392 if !key.is_null() {
2393 map.insert(key, materialize(val));
2394 }
2395 }
2396 ResultSpec::map_spec(map)
2397 }
2398 Recipe::Union(a, b) => materialize(a).union(materialize(b)),
2399 }
2400 }
2401
2402 fn recipe_strategy() -> impl Strategy<Value = Recipe> {
2403 let leaf = proptest::strategy::Union::new(vec![
2404 Just(Recipe::Nothing).boxed(),
2405 Just(Recipe::Null).boxed(),
2406 Just(Recipe::Fails).boxed(),
2407 Just(Recipe::Anything).boxed(),
2408 Just(Recipe::ValueAll).boxed(),
2409 mz_repr::arb_datum(false).prop_map(Recipe::Value).boxed(),
2410 (mz_repr::arb_datum(false), mz_repr::arb_datum(false))
2411 .prop_map(|(a, b)| Recipe::Between(a, b))
2412 .boxed(),
2413 ]);
2414 leaf.prop_recursive(3, 24, 4, |inner| {
2415 proptest::strategy::Union::new(vec![
2416 prop::collection::vec((mz_repr::arb_datum(false), inner.clone()), 0..3)
2417 .prop_map(Recipe::Map)
2418 .boxed(),
2419 (inner.clone(), inner.clone())
2420 .prop_map(|(a, b)| Recipe::Union(Box::new(a), Box::new(b)))
2421 .boxed(),
2422 ])
2423 })
2424 }
2425
2426 fn check(a: Recipe, b: Recipe, v: PropDatum) -> Result<(), TestCaseError> {
2427 let a_spec = materialize(&a);
2428 let b_spec = materialize(&b);
2429 let v: Datum = (&v).into();
2430
2431 let in_a = a_spec.may_contain(v);
2432 let in_b = b_spec.may_contain(v);
2433
2434 if in_a || in_b {
2435 prop_assert!(
2436 a_spec.clone().union(b_spec.clone()).may_contain(v),
2437 "union dropped a value: a={a:?} b={b:?} v={v:?}",
2438 );
2439 }
2440 if in_a && in_b {
2441 prop_assert!(
2442 a_spec.intersect(b_spec).may_contain(v),
2443 "intersect dropped a common value: a={a:?} b={b:?} v={v:?}",
2444 );
2445 }
2446 Ok(())
2447 }
2448
2449 proptest!(
2450 ProptestConfig::with_cases(4096),
2451 |(a in recipe_strategy(), b in recipe_strategy(), v in mz_repr::arb_datum(true))| {
2452 check(a, b, v)?;
2453 }
2454 );
2455 }
2456
2457 #[mz_ore::test]
2468 #[cfg_attr(miri, ignore)] fn test_neg_numeric_nan_range() {
2470 use mz_repr::adt::numeric::Numeric;
2471
2472 let neg = MirScalarExpr::CallUnary {
2473 func: UnaryFunc::NegNumeric(NegNumeric),
2474 expr: Box::new(MirScalarExpr::column(0)),
2475 };
2476
2477 let relation = ReprRelationType::new(vec![ReprScalarType::Numeric.nullable(false)]);
2478 let arena = RowArena::new();
2479 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2480 interpreter.push_column(
2481 0,
2482 ResultSpec::value_between(
2483 Datum::from(Numeric::from(f64::NEG_INFINITY)),
2484 Datum::from(Numeric::from(f64::NAN)),
2485 ),
2486 );
2487
2488 let spec = interpreter.expr(&neg);
2489
2490 let actual = neg
2492 .eval(&[Datum::from(Numeric::from(-1.0f64))], &arena)
2493 .expect("eval succeeds");
2494 assert!(
2495 spec.range.may_contain(actual),
2496 "interpreter must not rule out {actual:?}, which the evaluator \
2497 produces for an interior input; got spec {:?}",
2498 spec.range,
2499 );
2500 }
2501
2502 #[mz_ore::test]
2513 #[cfg_attr(miri, ignore)] fn test_fallible_monotone_interior_error() {
2515 use mz_repr::adt::numeric::Numeric;
2516
2517 let cast = MirScalarExpr::CallUnary {
2518 func: UnaryFunc::CastNumericToMzTimestamp(CastNumericToMzTimestamp),
2519 expr: Box::new(MirScalarExpr::column(0)),
2520 };
2521
2522 let relation = ReprRelationType::new(vec![ReprScalarType::Numeric.nullable(false)]);
2523 let arena = RowArena::new();
2524 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2525 interpreter.push_column(
2526 0,
2527 ResultSpec::value_between(
2528 Datum::from(Numeric::from(0.0f64)),
2529 Datum::from(Numeric::from(2.0f64)),
2530 ),
2531 );
2532
2533 let spec = interpreter.expr(&cast);
2534
2535 let interior = Datum::from(Numeric::from(1.5f64));
2538 assert!(
2539 cast.eval(&[interior], &arena).is_err(),
2540 "precondition: a fractional numeric fails to cast to mz_timestamp",
2541 );
2542 assert!(
2543 spec.range.may_fail(),
2544 "interpreter must surface that a monotone-but-fallible function may \
2545 error on an interior value it never sampled; got spec {:?}",
2546 spec.range,
2547 );
2548 }
2549
2550 #[mz_ore::test]
2558 #[cfg_attr(miri, ignore)]
2559 fn test_mfp_unreferenced_fallible_expression() {
2560 use crate::scalar::func::CastStringToUuid;
2561
2562 let mfp = MapFilterProject {
2568 expressions: vec![MirScalarExpr::CallUnary {
2569 func: UnaryFunc::CastStringToUuid(CastStringToUuid),
2570 expr: Box::new(MirScalarExpr::column(0)),
2571 }],
2572 predicates: vec![(
2573 1,
2574 MirScalarExpr::literal_ok(Datum::True, ReprScalarType::Bool),
2575 )],
2576 projection: vec![0, 1],
2577 input_arity: 1,
2578 };
2579
2580 let relation = ReprRelationType::new(vec![ReprScalarType::String.nullable(false)]);
2581 let arena = RowArena::new();
2582 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2583 interpreter.push_column(
2585 0,
2586 ResultSpec::value_between(Datum::String("not-a-uuid"), Datum::String("not-a-uuid")),
2587 );
2588 let spec = interpreter.mfp_filter(&mfp);
2589 assert!(
2590 spec.range.may_fail(),
2591 "an MFP expression that errors on the stats range must propagate \
2592 fallibility, otherwise persist filter pushdown can wrongly discard \
2593 a part that produces error rows",
2594 );
2595 }
2596
2597 #[mz_ore::test]
2607 #[cfg_attr(miri, ignore)]
2608 fn test_mfp_filter_fallibility_equivalence() {
2609 fn check(data: ExpressionData) -> Result<(), TestCaseError> {
2610 let ExpressionData {
2611 relation_type,
2612 specs,
2613 rows,
2614 expr,
2615 } = data;
2616
2617 let input_arity = relation_type.column_types.len();
2618 let mfp = MapFilterProject {
2619 expressions: vec![expr.clone()],
2620 predicates: vec![],
2621 projection: (0..input_arity).collect(),
2622 input_arity,
2623 };
2624
2625 let arena = RowArena::new();
2626 let mut interpreter = ColumnSpecs::new(&relation_type, &arena);
2627 for (id, spec) in specs.into_iter().enumerate() {
2628 interpreter.push_column(id, spec);
2629 }
2630 let summary = interpreter.mfp_filter(&mfp);
2631
2632 for row in &rows {
2633 let datums: Vec<_> = row.iter().collect();
2634 if expr.eval(&datums, &arena).is_err() {
2635 prop_assert!(
2636 summary.range.may_fail(),
2637 "mfp_filter must surface the fallibility of an \
2638 unreferenced MFP expression: row {:?} errored at \
2639 runtime but the interpreter ruled out errors",
2640 row,
2641 );
2642 }
2643 }
2644 Ok(())
2645 }
2646
2647 proptest!(|(data in gen_expr_data())| {
2648 check(data)?;
2649 });
2650 }
2651
2652 #[mz_ore::test]
2653 fn test_mfp() {
2654 use MirScalarExpr::*;
2656
2657 let mfp = MapFilterProject {
2658 expressions: vec![],
2659 predicates: vec![
2660 (
2662 1,
2663 CallUnary {
2664 func: UnaryFunc::IsNull(IsNull),
2665 expr: Box::new(CallBinary {
2666 func: MulInt32.into(),
2667 expr1: Box::new(MirScalarExpr::column(0)),
2668 expr2: Box::new(MirScalarExpr::column(0)),
2669 }),
2670 },
2671 ),
2672 (
2674 1,
2675 CallBinary {
2676 func: Eq.into(),
2677 expr1: Box::new(MirScalarExpr::column(0)),
2678 expr2: Box::new(MirScalarExpr::literal_ok(
2679 Datum::Int32(1727694505),
2680 ReprScalarType::Int32,
2681 )),
2682 },
2683 ),
2684 ],
2685 projection: vec![],
2686 input_arity: 1,
2687 };
2688
2689 let relation = ReprRelationType::new(vec![ReprScalarType::Int32.nullable(true)]);
2690 let arena = RowArena::new();
2691 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2692 interpreter.push_column(0, ResultSpec::value(Datum::Int32(-1294725158)));
2693 let spec = interpreter.mfp_filter(&mfp);
2694 assert!(spec.range.may_fail());
2695 }
2696
2697 #[mz_ore::test]
2698 fn test_concat() {
2699 let expr = MirScalarExpr::call_variadic(
2700 Concat,
2701 vec![
2702 MirScalarExpr::column(0),
2703 MirScalarExpr::literal_ok(Datum::String("a"), ReprScalarType::String),
2704 MirScalarExpr::literal_ok(Datum::String("b"), ReprScalarType::String),
2705 ],
2706 );
2707
2708 let relation = ReprRelationType::new(vec![ReprScalarType::String.nullable(false)]);
2709 let arena = RowArena::new();
2710 let interpreter = ColumnSpecs::new(&relation, &arena);
2711 let spec = interpreter.expr(&expr);
2712 assert!(spec.range.may_contain(Datum::String("blab")));
2713 }
2714
2715 #[mz_ore::test]
2716 fn test_eval_range() {
2717 let period_ms = MirScalarExpr::literal_ok(Datum::Int64(10), ReprScalarType::Int64);
2729 let expr = MirScalarExpr::CallBinary {
2730 func: Gte.into(),
2731 expr1: Box::new(MirScalarExpr::CallUnmaterializable(
2732 UnmaterializableFunc::MzNow,
2733 )),
2734 expr2: Box::new(MirScalarExpr::CallUnary {
2735 func: UnaryFunc::CastInt64ToMzTimestamp(CastInt64ToMzTimestamp),
2736 expr: Box::new(MirScalarExpr::CallBinary {
2737 func: MulInt64.into(),
2738 expr1: Box::new(period_ms.clone()),
2739 expr2: Box::new(MirScalarExpr::CallBinary {
2740 func: DivInt64.into(),
2741 expr1: Box::new(MirScalarExpr::column(0)),
2742 expr2: Box::new(period_ms),
2743 }),
2744 }),
2745 }),
2746 };
2747 let relation = ReprRelationType::new(vec![ReprScalarType::Int64.nullable(false)]);
2748
2749 {
2750 let arena = RowArena::new();
2752 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2753 interpreter.push_unmaterializable(
2754 UnmaterializableFunc::MzNow,
2755 ResultSpec::value_between(
2756 Datum::MzTimestamp(10.into()),
2757 Datum::MzTimestamp(20.into()),
2758 ),
2759 );
2760 interpreter.push_column(0, ResultSpec::value_between(30i64.into(), 40i64.into()));
2761
2762 let range_out = interpreter.expr(&expr).range;
2763 assert!(range_out.may_contain(Datum::False));
2764 assert!(!range_out.may_contain(Datum::True));
2765 assert!(!range_out.may_contain(Datum::Null));
2766 assert!(range_out.may_fail());
2767 }
2768
2769 {
2770 let arena = RowArena::new();
2772 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2773 interpreter.push_unmaterializable(
2774 UnmaterializableFunc::MzNow,
2775 ResultSpec::value_between(
2776 Datum::MzTimestamp(10.into()),
2777 Datum::MzTimestamp(35.into()),
2778 ),
2779 );
2780 interpreter.push_column(0, ResultSpec::value_between(30i64.into(), 40i64.into()));
2781
2782 let range_out = interpreter.expr(&expr).range;
2783 assert!(range_out.may_contain(Datum::False));
2784 assert!(range_out.may_contain(Datum::True));
2785 assert!(!range_out.may_contain(Datum::Null));
2786 assert!(range_out.may_fail());
2787 }
2788 }
2789
2790 #[mz_ore::test]
2791 #[cfg_attr(miri, ignore)] fn test_jsonb() {
2793 let arena = RowArena::new();
2794
2795 let expr = MirScalarExpr::column(0)
2796 .call_binary(
2797 MirScalarExpr::literal_ok(Datum::from("ts"), ReprScalarType::String),
2798 JsonbGetString,
2799 )
2800 .call_unary(CastJsonbToNumeric(None));
2801
2802 let relation = ReprRelationType::new(vec![ReprScalarType::Jsonb.nullable(true)]);
2803 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2804 interpreter.push_column(
2805 0,
2806 ResultSpec::map_spec(
2807 [(
2808 "ts".into(),
2809 ResultSpec::value_between(
2810 Datum::Numeric(100.into()),
2811 Datum::Numeric(300.into()),
2812 ),
2813 )]
2814 .into_iter()
2815 .collect(),
2816 ),
2817 );
2818
2819 let range_out = interpreter.expr(&expr).range;
2820 assert!(!range_out.may_contain(Datum::Numeric(0.into())));
2821 assert!(range_out.may_contain(Datum::Numeric(200.into())));
2822 assert!(!range_out.may_contain(Datum::Numeric(400.into())));
2823 }
2824
2825 #[mz_ore::test]
2826 fn test_nested_union_partial_overlap() {
2827 let a = ResultSpec::map_spec(
2832 [
2833 ("x".into(), ResultSpec::value(Datum::String("a"))),
2834 ("y".into(), ResultSpec::value(Datum::String("b"))),
2835 ("c".into(), ResultSpec::value(Datum::String("c"))),
2836 ]
2837 .into_iter()
2838 .collect(),
2839 );
2840 let b = ResultSpec::map_spec(
2841 [
2842 ("x".into(), ResultSpec::value(Datum::String("a2"))),
2843 ("y".into(), ResultSpec::value(Datum::String("b2"))),
2844 ("z".into(), ResultSpec::value(Datum::String("z"))),
2845 ]
2846 .into_iter()
2847 .collect(),
2848 );
2849
2850 let unioned = a.union(b);
2851
2852 let arena = RowArena::new();
2856 let relation = ReprRelationType::new(vec![ReprScalarType::Jsonb.nullable(false)]);
2857
2858 {
2860 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2861 interpreter.push_column(0, unioned.clone());
2862 let expr = MirScalarExpr::column(0).call_binary(
2863 MirScalarExpr::literal_ok(Datum::from("c"), ReprScalarType::String),
2864 JsonbGetStringStringify,
2865 );
2866 assert!(interpreter.expr(&expr).range.may_contain(Datum::Null));
2867 }
2868
2869 {
2871 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2872 interpreter.push_column(0, unioned.clone());
2873 let expr = MirScalarExpr::column(0).call_binary(
2874 MirScalarExpr::literal_ok(Datum::from("z"), ReprScalarType::String),
2875 JsonbGetStringStringify,
2876 );
2877 assert!(interpreter.expr(&expr).range.may_contain(Datum::Null));
2878 }
2879
2880 {
2882 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2883 interpreter.push_column(0, unioned);
2884 let expr = MirScalarExpr::column(0).call_binary(
2885 MirScalarExpr::literal_ok(Datum::from("x"), ReprScalarType::String),
2886 JsonbGetStringStringify,
2887 );
2888 let x_range = interpreter.expr(&expr).range;
2889 assert!(x_range.may_contain(Datum::String("a")));
2890 assert!(x_range.may_contain(Datum::String("a2")));
2891 }
2892 }
2893
2894 #[mz_ore::test]
2895 #[cfg_attr(miri, ignore)] fn test_case_over_jsonb_columns() {
2897 let arena = RowArena::new();
2901
2902 let expr = MirScalarExpr::If {
2904 cond: Box::new(MirScalarExpr::column(0)),
2905 then: Box::new(MirScalarExpr::column(1)),
2906 els: Box::new(MirScalarExpr::column(2)),
2907 }
2908 .call_binary(
2909 MirScalarExpr::literal_ok(Datum::from("y"), ReprScalarType::String),
2910 JsonbGetStringStringify,
2911 )
2912 .call_unary(UnaryFunc::IsNull(IsNull));
2913
2914 let relation = ReprRelationType::new(vec![
2915 ReprScalarType::Bool.nullable(false),
2916 ReprScalarType::Jsonb.nullable(false),
2917 ReprScalarType::Jsonb.nullable(false),
2918 ]);
2919 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2920 interpreter.push_column(0, ResultSpec::value_between(Datum::False, Datum::True));
2921 interpreter.push_column(
2922 1,
2923 ResultSpec::map_spec(
2924 [("x".into(), ResultSpec::value(Datum::String("a")))]
2925 .into_iter()
2926 .collect(),
2927 ),
2928 );
2929 interpreter.push_column(
2930 2,
2931 ResultSpec::map_spec(
2932 [("y".into(), ResultSpec::value(Datum::String("b")))]
2933 .into_iter()
2934 .collect(),
2935 ),
2936 );
2937
2938 let range_out = interpreter.expr(&expr).range;
2939 assert!(range_out.may_contain(Datum::True));
2942 }
2943
2944 #[mz_ore::test]
2945 fn test_like() {
2946 let arena = RowArena::new();
2947
2948 let expr = MirScalarExpr::CallUnary {
2949 func: UnaryFunc::IsLikeMatch(IsLikeMatch(
2950 crate::like_pattern::compile("%whatever%", true).unwrap(),
2951 )),
2952 expr: Box::new(MirScalarExpr::column(0)),
2953 };
2954
2955 let relation = ReprRelationType::new(vec![ReprScalarType::String.nullable(true)]);
2956 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2957 interpreter.push_column(
2958 0,
2959 ResultSpec::value_between(Datum::String("aardvark"), Datum::String("zebra")),
2960 );
2961
2962 let range_out = interpreter.expr(&expr).range;
2963 assert!(
2964 !range_out.fallible,
2965 "like function should not error on non-error input"
2966 );
2967 assert!(range_out.may_contain(Datum::True));
2968 assert!(range_out.may_contain(Datum::False));
2969 assert!(range_out.may_contain(Datum::Null));
2970 }
2971
2972 #[mz_ore::test]
2973 fn test_try_parse_monotonic_iso8601_timestamp() {
2974 use chrono::NaiveDateTime;
2975
2976 let arena = RowArena::new();
2977
2978 let expr = MirScalarExpr::CallUnary {
2979 func: UnaryFunc::TryParseMonotonicIso8601Timestamp(TryParseMonotonicIso8601Timestamp),
2980 expr: Box::new(MirScalarExpr::column(0)),
2981 };
2982
2983 let relation = ReprRelationType::new(vec![ReprScalarType::String.nullable(true)]);
2984 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2986 interpreter.push_column(
2987 0,
2988 ResultSpec::value_between(
2989 Datum::String("2024-01-11T00:00:00.000Z"),
2990 Datum::String("2024-01-11T20:00:00.000Z"),
2991 ),
2992 );
2993
2994 let timestamp = |ts| {
2995 Datum::Timestamp(
2996 NaiveDateTime::parse_from_str(ts, "%Y-%m-%dT%H:%M:%S")
2997 .unwrap()
2998 .try_into()
2999 .unwrap(),
3000 )
3001 };
3002
3003 let range_out = interpreter.expr(&expr).range;
3004 assert!(!range_out.fallible);
3005 assert!(range_out.nullable);
3006 assert!(!range_out.may_contain(timestamp("2024-01-10T10:00:00")));
3007 assert!(range_out.may_contain(timestamp("2024-01-11T10:00:00")));
3008 assert!(!range_out.may_contain(timestamp("2024-01-12T10:00:00")));
3009
3010 let mut interpreter = ColumnSpecs::new(&relation, &arena);
3012 interpreter.push_column(
3013 0,
3014 ResultSpec::value_between(Datum::String("2024-01-1"), Datum::String("2024-01-2")),
3015 );
3016
3017 let range_out = interpreter.expr(&expr).range;
3018 assert!(!range_out.fallible);
3019 assert!(range_out.nullable);
3020 assert!(range_out.may_contain(timestamp("2024-01-10T10:00:00")));
3021 assert!(range_out.may_contain(timestamp("2024-01-11T10:00:00")));
3022 assert!(range_out.may_contain(timestamp("2024-01-12T10:00:00")));
3023
3024 let mut interpreter = ColumnSpecs::new(&relation, &arena);
3026 interpreter.push_column(
3027 0,
3028 ResultSpec::value_between(
3029 Datum::String("2024-01-1"),
3030 Datum::String("2024-01-12T10:00:00"),
3031 )
3032 .union(ResultSpec::null()),
3033 );
3034
3035 let range_out = interpreter.expr(&expr).range;
3036 assert!(!range_out.fallible);
3037 assert!(range_out.nullable);
3038 assert!(range_out.may_contain(timestamp("2024-01-10T10:00:00")));
3039 assert!(range_out.may_contain(timestamp("2024-01-11T10:00:00")));
3040 assert!(range_out.may_contain(timestamp("2024-01-12T10:00:00")));
3041
3042 let mut interpreter = ColumnSpecs::new(&relation, &arena);
3044 interpreter.push_column(
3045 0,
3046 ResultSpec::value_between(
3047 Datum::String("2024-01-11T10:00:00.000Z"),
3048 Datum::String("2024-01-11T10:00:00.000Z"),
3049 ),
3050 );
3051
3052 let range_out = interpreter.expr(&expr).range;
3053 assert!(!range_out.fallible);
3054 assert!(!range_out.nullable);
3055 assert!(!range_out.may_contain(timestamp("2024-01-10T10:00:00")));
3056 assert!(range_out.may_contain(timestamp("2024-01-11T10:00:00")));
3057 assert!(!range_out.may_contain(timestamp("2024-01-12T10:00:00")));
3058 }
3059
3060 #[mz_ore::test]
3061 fn test_inequality() {
3062 let arena = RowArena::new();
3063
3064 let expr = MirScalarExpr::column(0).call_binary(
3065 MirScalarExpr::CallUnmaterializable(UnmaterializableFunc::MzNow),
3066 Gte,
3067 );
3068
3069 let relation = ReprRelationType::new(vec![ReprScalarType::MzTimestamp.nullable(true)]);
3070 let mut interpreter = ColumnSpecs::new(&relation, &arena);
3071 interpreter.push_column(
3072 0,
3073 ResultSpec::value_between(
3074 Datum::MzTimestamp(1704736444949u64.into()),
3075 Datum::MzTimestamp(1704736444949u64.into()),
3076 )
3077 .union(ResultSpec::null()),
3078 );
3079 interpreter.push_unmaterializable(
3080 UnmaterializableFunc::MzNow,
3081 ResultSpec::value_between(
3082 Datum::MzTimestamp(1704738791000u64.into()),
3083 Datum::MzTimestamp(18446744073709551615u64.into()),
3084 ),
3085 );
3086
3087 let range_out = interpreter.expr(&expr).range;
3088 assert!(
3089 !range_out.fallible,
3090 "<= function should not error on non-error input"
3091 );
3092 assert!(!range_out.may_contain(Datum::True));
3093 assert!(range_out.may_contain(Datum::False));
3094 assert!(range_out.may_contain(Datum::Null));
3095 }
3096
3097 #[mz_ore::test]
3105 #[cfg_attr(miri, ignore)]
3106 fn test_add_timestamp_interval_non_monotone() {
3107 use chrono::NaiveDateTime;
3108 use mz_repr::adt::interval::Interval;
3109 use mz_repr::adt::timestamp::CheckedTimestamp;
3110 use mz_repr::{Datum, Row};
3111
3112 let arena = RowArena::new();
3113
3114 let ts_lit = |s: &str| {
3126 let mut row = Row::default();
3127 row.packer().push(Datum::Timestamp(
3128 CheckedTimestamp::from_timestamplike(
3129 NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S").unwrap(),
3130 )
3131 .unwrap(),
3132 ));
3133 MirScalarExpr::Literal(Ok(row), ReprScalarType::Timestamp.nullable(false))
3134 };
3135 let interval = |months: i32, days: i32, micros: i64| {
3136 Datum::Interval(Interval {
3137 months,
3138 days,
3139 micros,
3140 })
3141 };
3142
3143 let expr = ts_lit("2024-01-31T00:00:00")
3145 .call_binary(MirScalarExpr::column(0), AddTimestampInterval)
3146 .call_binary(ts_lit("2024-03-15T00:00:00"), Gte);
3147
3148 let relation = ReprRelationType::new(vec![ReprScalarType::Interval.nullable(false)]);
3149 let mut interpreter = ColumnSpecs::new(&relation, &arena);
3150 interpreter.push_column(
3151 0,
3152 ResultSpec::value_between(interval(0, 31, 0), interval(1, 0, 0)),
3153 );
3154
3155 let range_out = interpreter.expr(&expr).range;
3156 assert!(
3163 range_out.may_contain(Datum::True),
3164 "interpreter incorrectly ruled out matching rows; \
3165 add_timestamp_interval is not monotone in the interval argument",
3166 );
3167 }
3168
3169 #[mz_ore::test]
3176 #[cfg_attr(miri, ignore)]
3177 fn test_timestamp_plus_interval_dynamic_monotone() {
3178 use chrono::NaiveDateTime;
3179 use mz_repr::adt::interval::Interval;
3180 use mz_repr::adt::timestamp::CheckedTimestamp;
3181 use mz_repr::{Datum, Row};
3182
3183 let arena = RowArena::new();
3184
3185 let ts = |s: &str| {
3186 Datum::Timestamp(
3187 CheckedTimestamp::from_timestamplike(
3188 NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S").unwrap(),
3189 )
3190 .unwrap(),
3191 )
3192 };
3193 let interval_lit = |months: i32, days: i32, micros: i64| {
3194 let mut row = Row::default();
3195 row.packer().push(Datum::Interval(Interval {
3196 months,
3197 days,
3198 micros,
3199 }));
3200 MirScalarExpr::Literal(Ok(row), ReprScalarType::Interval.nullable(false))
3201 };
3202
3203 let relation = ReprRelationType::new(vec![ReprScalarType::Timestamp.nullable(false)]);
3204
3205 {
3211 let expr = MirScalarExpr::column(0)
3212 .call_binary(interval_lit(0, 1, 0), SubTimestampInterval)
3213 .call_binary(
3214 MirScalarExpr::Literal(
3215 Ok({
3216 let mut r = Row::default();
3217 r.packer().push(ts("2024-01-15T00:00:00"));
3218 r
3219 }),
3220 ReprScalarType::Timestamp.nullable(false),
3221 ),
3222 Lt,
3223 );
3224 let mut interpreter = ColumnSpecs::new(&relation, &arena);
3225 interpreter.push_column(
3226 0,
3227 ResultSpec::value_between(ts("2024-01-15T00:00:00"), ts("2024-01-20T00:00:00")),
3228 );
3229 let range_out = interpreter.expr(&expr).range;
3230 assert!(
3231 range_out.may_contain(Datum::True),
3232 "day-only interval should preserve tight bounds",
3233 );
3234 assert!(
3235 range_out.may_contain(Datum::False),
3236 "day-only interval should preserve tight bounds",
3237 );
3238 }
3239
3240 {
3245 let expr = MirScalarExpr::column(0)
3246 .call_binary(interval_lit(0, 1, 0), SubTimestampInterval)
3247 .call_binary(
3248 MirScalarExpr::Literal(
3249 Ok({
3250 let mut r = Row::default();
3251 r.packer().push(ts("2024-01-15T00:00:00"));
3252 r
3253 }),
3254 ReprScalarType::Timestamp.nullable(false),
3255 ),
3256 Lt,
3257 );
3258 let mut interpreter = ColumnSpecs::new(&relation, &arena);
3259 interpreter.push_column(
3260 0,
3261 ResultSpec::value_between(ts("2024-01-17T00:00:00"), ts("2024-01-20T00:00:00")),
3262 );
3263 let range_out = interpreter.expr(&expr).range;
3264 assert!(
3265 !range_out.may_contain(Datum::True),
3266 "day-only interval should narrow out impossible matches",
3267 );
3268 }
3269
3270 {
3275 let expr = MirScalarExpr::column(0)
3276 .call_binary(interval_lit(1, 0, 0), SubTimestampInterval)
3277 .call_binary(
3278 MirScalarExpr::Literal(
3279 Ok({
3280 let mut r = Row::default();
3281 r.packer().push(ts("2024-01-15T00:00:00"));
3282 r
3283 }),
3284 ReprScalarType::Timestamp.nullable(false),
3285 ),
3286 Lt,
3287 );
3288 let mut interpreter = ColumnSpecs::new(&relation, &arena);
3289 interpreter.push_column(
3290 0,
3291 ResultSpec::value_between(ts("2024-01-17T00:00:00"), ts("2024-01-20T00:00:00")),
3292 );
3293 let range_out = interpreter.expr(&expr).range;
3294 assert!(
3295 range_out.may_contain(Datum::True),
3296 "month-bearing interval must conservatively admit True",
3297 );
3298 assert!(
3299 range_out.may_contain(Datum::False),
3300 "month-bearing interval must conservatively admit False",
3301 );
3302 }
3303 }
3304
3305 #[mz_ore::test]
3314 #[cfg_attr(miri, ignore)]
3315 fn proptest_timestamp_plus_interval_monotone_when_months_zero() {
3316 use mz_repr::adt::interval::Interval;
3317 use mz_repr::{Datum, RowArena, SqlScalarType, arb_datum_for_scalar};
3318 use proptest::prelude::*;
3319
3320 let timestamp_strat = || arb_datum_for_scalar(SqlScalarType::Timestamp { precision: None });
3321 let zero_month_interval_strat =
3328 (any::<i32>(), any::<i64>()).prop_map(|(days, micros)| Interval {
3329 months: 0,
3330 days,
3331 micros,
3332 });
3333
3334 let expr = MirScalarExpr::CallBinary {
3335 func: AddTimestampInterval.into(),
3336 expr1: Box::new(MirScalarExpr::column(0)),
3337 expr2: Box::new(MirScalarExpr::column(1)),
3338 };
3339 let arena = RowArena::new();
3340
3341 proptest!(|(
3342 t1 in timestamp_strat(),
3343 t2 in timestamp_strat(),
3344 i in zero_month_interval_strat,
3345 )| {
3346 let t1 = match t1 { PropDatum::Timestamp(t) => t, _ => unreachable!() };
3347 let t2 = match t2 { PropDatum::Timestamp(t) => t, _ => unreachable!() };
3348 let i = Datum::Interval(i);
3349 let r1 = expr.eval(&[Datum::Timestamp(t1), i], &arena);
3350 let r2 = expr.eval(&[Datum::Timestamp(t2), i], &arena);
3351 if let (Ok(Datum::Timestamp(r1)), Ok(Datum::Timestamp(r2))) = (r1, r2) {
3354 prop_assert_eq!(t1.cmp(&t2), r1.cmp(&r2));
3355 }
3356 });
3357 }
3358
3359 #[mz_ore::test]
3364 #[cfg_attr(miri, ignore)]
3365 fn test_date_bin_timestamp_non_monotone() {
3366 use chrono::NaiveDateTime;
3367 use mz_repr::adt::interval::Interval;
3368 use mz_repr::adt::timestamp::CheckedTimestamp;
3369 use mz_repr::{Datum, Row};
3370
3371 let arena = RowArena::new();
3372
3373 let ts_lit = |s: &str| {
3374 let mut row = Row::default();
3375 row.packer().push(Datum::Timestamp(
3376 CheckedTimestamp::from_timestamplike(
3377 NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S").unwrap(),
3378 )
3379 .unwrap(),
3380 ));
3381 MirScalarExpr::Literal(Ok(row), ReprScalarType::Timestamp.nullable(false))
3382 };
3383 let interval = |months: i32, days: i32, micros: i64| {
3384 Datum::Interval(Interval {
3385 months,
3386 days,
3387 micros,
3388 })
3389 };
3390
3391 let expr = MirScalarExpr::column(0)
3408 .call_binary(ts_lit("2024-01-01T12:00:00"), DateBinTimestamp)
3409 .call_binary(ts_lit("2024-01-01T06:00:00"), Gt);
3410
3411 let relation = ReprRelationType::new(vec![ReprScalarType::Interval.nullable(false)]);
3412 let mut interpreter = ColumnSpecs::new(&relation, &arena);
3413 interpreter.push_column(
3414 0,
3415 ResultSpec::value_between(interval(0, 1, 0), interval(0, 2, 0)),
3416 );
3417
3418 let range_out = interpreter.expr(&expr).range;
3419 assert!(
3420 range_out.may_contain(Datum::True),
3421 "date_bin is not monotone in the stride argument; \
3422 interior strides can produce outputs outside the endpoint-bounded \
3423 box, so the interpreter must admit True for `>`-style predicates",
3424 );
3425 }
3426
3427 #[mz_ore::test]
3432 #[cfg_attr(miri, ignore)]
3433 fn test_round_numeric_representation_independent() {
3434 use mz_repr::adt::date::Date;
3435
3436 let arena = RowArena::new();
3437 let lit = |d: Datum, ty: ReprScalarType| {
3438 let mut row = Row::default();
3439 row.packer().push(d);
3440 MirScalarExpr::Literal(Ok(row), ty.nullable(false))
3441 };
3442
3443 let expr = lit(
3446 Datum::Date(Date::from_pg_epoch(0).unwrap()),
3447 ReprScalarType::Date,
3448 )
3449 .call_unary(UnaryFunc::ExtractDate(ExtractDate(DateTimeUnits::Epoch)))
3450 .call_binary(
3451 lit(Datum::Int32(i32::MAX), ReprScalarType::Int32),
3452 BinaryFunc::from(RoundNumericBinary),
3453 );
3454
3455 let relation = ReprRelationType::new(vec![]);
3456 let range = ColumnSpecs::new(&relation, &arena).expr(&expr).range;
3457 match expr.eval(&[], &arena) {
3458 Ok(value) => assert!(
3459 range.may_contain(value),
3460 "interpreter ruled out {value:?}, which the evaluator produced: {range:?}",
3461 ),
3462 Err(_) => assert!(
3463 range.may_fail(),
3464 "interpreter ruled out the error the evaluator produced: {range:?}",
3465 ),
3466 }
3467 }
3468
3469 #[mz_ore::test]
3470 fn test_trace() {
3471 use super::Trace;
3472
3473 let expr = MirScalarExpr::column(0).call_binary(
3474 MirScalarExpr::column(1)
3475 .call_binary(MirScalarExpr::column(3).call_unary(NegInt64), AddInt64),
3476 Gte,
3477 );
3478 let summary = Trace.expr(&expr);
3479 assert!(summary.pushdownable());
3480 }
3481}