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::{
17 BinaryFunc, Eval, EvalError, MapFilterProject, MfpPlan, MirScalarExpr, UnaryFunc,
18 UnmaterializableFunc, VariadicFunc, func,
19};
20fn datum_is_nan(datum: Datum) -> bool {
29 match datum {
30 Datum::Float32(f) => f.is_nan(),
31 Datum::Float64(f) => f.is_nan(),
32 Datum::Numeric(n) => n.0.is_nan(),
33 _ => false,
34 }
35}
36
37fn datum_is_infinite(datum: Datum) -> bool {
39 match datum {
40 Datum::Float32(f) => f.is_infinite(),
41 Datum::Float64(f) => f.is_infinite(),
42 Datum::Numeric(n) => n.0.is_infinite(),
43 _ => false,
44 }
45}
46
47#[derive(Clone, Eq, PartialEq, Debug)]
49enum Values<'a> {
50 Empty,
52 Within(Datum<'a>, Datum<'a>),
55 Nested(BTreeMap<Datum<'a>, ResultSpec<'a>>),
59 All,
62}
63
64impl<'a> Values<'a> {
65 fn just(a: Datum<'a>) -> Values<'a> {
66 match a {
67 Datum::Map(datum_map) => Values::Nested(
68 datum_map
69 .iter()
70 .map(|(key, val)| (key.into(), ResultSpec::value(val)))
71 .collect(),
72 ),
73 other => Self::Within(other, other),
74 }
75 }
76
77 fn union(self, other: Values<'a>) -> Values<'a> {
78 match (self, other) {
79 (Values::Empty, r) => r,
80 (r, Values::Empty) => r,
81 (Values::Within(a0, a1), Values::Within(b0, b1)) => {
82 Values::Within(a0.min(b0), a1.max(b1))
83 }
84 (Values::Nested(a), Values::Nested(mut b)) => {
85 let mut merged = BTreeMap::new();
90 for (key, a_spec) in a {
91 if let Some(b_spec) = b.remove(&key) {
92 let unioned = a_spec.union(b_spec);
93 if unioned != ResultSpec::anything() {
94 merged.insert(key, unioned);
95 }
96 }
97 }
98 if merged.is_empty() {
99 Values::All
100 } else {
101 Values::Nested(merged)
102 }
103 }
104 _ => Values::All,
105 }
106 }
107
108 fn intersect(self, other: Values<'a>) -> Values<'a> {
109 match (self, other) {
110 (Values::Empty, _) => Values::Empty,
111 (_, Values::Empty) => Values::Empty,
112 (Values::Within(a0, a1), Values::Within(b0, b1)) => {
113 let min = a0.max(b0);
114 let max = a1.min(b1);
115 if min <= max {
116 Values::Within(min, max)
117 } else {
118 Values::Empty
119 }
120 }
121 (Values::Nested(mut a), Values::Nested(b)) => {
122 for (datum, other_spec) in b {
123 let spec = a.entry(datum).or_insert_with(ResultSpec::anything);
124 *spec = spec.clone().intersect(other_spec);
125 }
126 Values::Nested(a)
127 }
128 (Values::All, v) => v,
129 (v, Values::All) => v,
130 (nested @ Values::Nested(_), Values::Within(_, _))
137 | (Values::Within(_, _), nested @ Values::Nested(_)) => nested,
138 }
139 }
140
141 fn may_contain(&self, value: Datum<'a>) -> bool {
142 match self {
143 Values::Empty => false,
144 Values::Within(min, max) => *min <= value && value <= *max,
145 Values::All => true,
146 Values::Nested(field_map) => match value {
147 Datum::Map(datum_map) => {
148 datum_map
149 .iter()
150 .all(|(key, val)| match field_map.get(&key.into()) {
151 None => true,
152 Some(nested) => nested.may_contain(val),
153 })
154 }
155 _ => false,
156 },
157 }
158 }
159
160 fn as_single(&self) -> Option<Datum<'a>> {
169 match self {
170 Values::Within(a, b) if a == b => Some(*a),
171 _ => None,
172 }
173 }
174}
175
176#[derive(Debug, Clone, Eq, PartialEq)]
181pub struct ResultSpec<'a> {
182 nullable: bool,
184 fallible: bool,
186 values: Values<'a>,
188}
189
190impl<'a> ResultSpec<'a> {
191 pub fn nothing() -> Self {
193 ResultSpec {
194 nullable: false,
195 fallible: false,
196 values: Values::Empty,
197 }
198 }
199
200 pub fn anything() -> Self {
202 ResultSpec {
203 nullable: true,
204 fallible: true,
205 values: Values::All,
206 }
207 }
208
209 pub fn any_infallible() -> Self {
211 ResultSpec {
212 nullable: true,
213 fallible: false,
214 values: Values::All,
215 }
216 }
217
218 pub fn null() -> Self {
220 ResultSpec {
221 nullable: true,
222 ..Self::nothing()
223 }
224 }
225
226 pub fn fails() -> Self {
228 ResultSpec {
229 fallible: true,
230 ..Self::nothing()
231 }
232 }
233
234 pub fn has_type(col: &ReprColumnType, fallible: bool) -> ResultSpec<'a> {
236 let values = match &col.scalar_type {
237 ReprScalarType::Bool => Values::Within(Datum::False, Datum::True),
238 _ => Values::All,
240 };
241 ResultSpec {
242 nullable: col.nullable,
243 fallible,
244 values,
245 }
246 }
247
248 pub fn value(value: Datum<'a>) -> ResultSpec<'a> {
250 match value {
251 Datum::Null => Self::null(),
252 nonnull => ResultSpec {
253 values: Values::just(nonnull),
254 ..Self::nothing()
255 },
256 }
257 }
258
259 pub fn value_between(min: Datum<'a>, max: Datum<'a>) -> ResultSpec<'a> {
261 assert!(!min.is_null());
262 assert!(!max.is_null());
263 if min <= max {
264 ResultSpec {
265 values: Values::Within(min, max),
266 ..ResultSpec::nothing()
267 }
268 } else {
269 ResultSpec::nothing()
270 }
271 }
272
273 pub fn value_all() -> ResultSpec<'a> {
275 ResultSpec {
276 values: Values::All,
277 ..ResultSpec::nothing()
278 }
279 }
280
281 pub fn map_spec(map: BTreeMap<Datum<'a>, ResultSpec<'a>>) -> ResultSpec<'a> {
283 ResultSpec {
284 values: Values::Nested(map),
285 ..ResultSpec::nothing()
286 }
287 }
288
289 pub fn union(self, other: ResultSpec<'a>) -> ResultSpec<'a> {
291 ResultSpec {
292 nullable: self.nullable || other.nullable,
293 fallible: self.fallible || other.fallible,
294 values: self.values.union(other.values),
295 }
296 }
297
298 pub fn intersect(self, other: ResultSpec<'a>) -> ResultSpec<'a> {
300 ResultSpec {
301 nullable: self.nullable && other.nullable,
302 fallible: self.fallible && other.fallible,
303 values: self.values.intersect(other.values),
304 }
305 }
306
307 pub fn may_contain(&self, value: Datum<'a>) -> bool {
309 if value == Datum::Null {
310 return self.nullable;
311 }
312
313 self.values.may_contain(value)
314 }
315
316 pub fn may_fail(&self) -> bool {
318 self.fallible
319 }
320
321 fn is_single_value(&self) -> bool {
329 self.values.as_single().is_some()
330 }
331
332 fn may_be_infinite(&self) -> bool {
336 match &self.values {
337 Values::Within(min, max) => datum_is_infinite(*min) || datum_is_infinite(*max),
338 Values::All => true,
339 Values::Empty | Values::Nested(_) => false,
340 }
341 }
342
343 fn flat_map(
354 &self,
355 is_monotone: bool,
356 mut result_map: impl FnMut(Result<Datum<'a>, EvalError>) -> ResultSpec<'a>,
357 ) -> ResultSpec<'a> {
358 let null_spec = if self.nullable {
359 result_map(Ok(Datum::Null))
360 } else {
361 ResultSpec::nothing()
362 };
363
364 let error_spec = if self.fallible {
365 let map_err = result_map(Err(EvalError::Internal("".into())));
369 let raise_err = ResultSpec::fails();
370 raise_err.union(map_err)
375 } else {
376 ResultSpec::nothing()
377 };
378
379 let values_spec = match self.values {
380 Values::Empty => ResultSpec::nothing(),
381 Values::Within(min, max) if min == max => result_map(Ok(min)),
383 Values::Within(Datum::False, Datum::True) => {
385 result_map(Ok(Datum::False)).union(result_map(Ok(Datum::True)))
386 }
387 Values::Within(min, max) if is_monotone && !datum_is_nan(min) && !datum_is_nan(max) => {
394 let min_result = result_map(Ok(min));
395 let max_result = result_map(Ok(max));
396 match (min_result, max_result) {
405 (
408 ResultSpec {
409 nullable: n1,
410 fallible: f1,
411 values: a_values @ Values::Within(..),
412 },
413 ResultSpec {
414 nullable: n2,
415 fallible: f2,
416 values: b_values @ Values::Within(..),
417 },
418 ) => ResultSpec {
419 nullable: n1 || n2,
420 fallible: f1 || f2,
421 values: a_values.union(b_values),
422 },
423 (
429 ResultSpec {
430 nullable: true,
431 fallible: false,
432 values: Values::Empty,
433 },
434 ResultSpec {
435 nullable: true,
436 fallible: false,
437 values: Values::Empty,
438 },
439 ) => ResultSpec::null(),
440 _ => ResultSpec::anything(),
442 }
443 }
444 Values::Within(_, _) | Values::Nested(_) | Values::All => ResultSpec::anything(),
446 };
447
448 null_spec.union(error_spec).union(values_spec)
449 }
450}
451
452pub trait Interpreter {
461 type Summary: Clone + Debug + Sized;
462
463 fn column(&self, id: usize) -> Self::Summary;
465
466 fn literal(&self, result: &Result<Row, EvalError>, col_type: &ReprColumnType) -> Self::Summary;
469 fn unmaterializable(&self, func: &UnmaterializableFunc) -> Self::Summary;
474
475 fn unary(&self, func: &UnaryFunc, expr: Self::Summary) -> Self::Summary;
477
478 fn binary(&self, func: &BinaryFunc, left: Self::Summary, right: Self::Summary)
480 -> Self::Summary;
481
482 fn variadic(&self, func: &VariadicFunc, exprs: Vec<Self::Summary>) -> Self::Summary;
484
485 fn cond(&self, cond: Self::Summary, then: Self::Summary, els: Self::Summary) -> Self::Summary;
487
488 fn expr(&self, expr: &MirScalarExpr) -> Self::Summary {
490 match expr {
491 MirScalarExpr::Column(id, _name) => self.column(*id),
492 MirScalarExpr::Literal(value, col_type) => self.literal(value, col_type),
493 MirScalarExpr::CallUnmaterializable(func) => self.unmaterializable(func),
494 MirScalarExpr::CallUnary { func, expr } => {
495 let expr_range = self.expr(expr);
496 self.unary(func, expr_range)
497 }
498 MirScalarExpr::CallBinary { func, expr1, expr2 } => {
499 let expr1_range = self.expr(expr1);
500 let expr2_range = self.expr(expr2);
501 self.binary(func, expr1_range, expr2_range)
502 }
503 MirScalarExpr::CallVariadic { func, exprs } => {
504 let exprs: Vec<_> = exprs.into_iter().map(|e| self.expr(e)).collect();
505 self.variadic(func, exprs)
506 }
507 MirScalarExpr::If { cond, then, els } => {
508 let cond_range = self.expr(cond);
509 let then_range = self.expr(then);
510 let els_range = self.expr(els);
511 self.cond(cond_range, then_range, els_range)
512 }
513 }
514 }
515
516 fn mfp_filter(&self, mfp: &MapFilterProject) -> Self::Summary {
519 let mfp_eval = MfpEval::new(self, mfp.input_arity, &mfp.expressions);
520 let predicates = mfp
522 .predicates
523 .iter()
524 .map(|(_, e)| mfp_eval.expr(e))
525 .collect();
526 mfp_eval.variadic(&And.into(), predicates)
527 }
528
529 fn mfp_plan_filter(&self, plan: &MfpPlan) -> Self::Summary {
532 let mfp_eval = MfpEval::new(self, plan.mfp.input_arity, &plan.mfp.expressions);
533 let mut results: Vec<_> = plan
535 .mfp
536 .predicates
537 .iter()
538 .map(|(_, e)| mfp_eval.expr(e))
539 .collect();
540 let mz_now = mfp_eval.unmaterializable(&UnmaterializableFunc::MzNow);
541 for bound in &plan.lower_bounds {
542 let bound_range = mfp_eval.expr(bound);
543 let result = mfp_eval.binary(&BinaryFunc::Lte(func::Lte), bound_range, mz_now.clone());
544 results.push(result);
545 }
546 for bound in &plan.upper_bounds {
547 let bound_range = mfp_eval.expr(bound);
548 let result = mfp_eval.binary(&BinaryFunc::Gte(func::Gte), bound_range, mz_now.clone());
549 results.push(result);
550 }
551 self.variadic(&And.into(), results)
552 }
553}
554
555pub(crate) struct MfpEval<'a, E: Interpreter + ?Sized> {
558 evaluator: &'a E,
559 input_arity: usize,
560 expressions: Vec<E::Summary>,
561}
562
563impl<'a, E: Interpreter + ?Sized> MfpEval<'a, E> {
564 pub(crate) fn new(evaluator: &'a E, input_arity: usize, expressions: &[MirScalarExpr]) -> Self {
565 let mut mfp_eval = MfpEval {
566 evaluator,
567 input_arity,
568 expressions: vec![],
569 };
570 for expr in expressions {
571 let result = mfp_eval.expr(expr);
572 mfp_eval.expressions.push(result);
573 }
574 mfp_eval
575 }
576}
577
578impl<'a, E: Interpreter + ?Sized> Interpreter for MfpEval<'a, E> {
579 type Summary = E::Summary;
580
581 fn column(&self, id: usize) -> Self::Summary {
582 if id < self.input_arity {
583 self.evaluator.column(id)
584 } else {
585 self.expressions[id - self.input_arity].clone()
586 }
587 }
588
589 fn literal(&self, result: &Result<Row, EvalError>, col_type: &ReprColumnType) -> Self::Summary {
590 self.evaluator.literal(result, col_type)
591 }
592
593 fn unmaterializable(&self, func: &UnmaterializableFunc) -> Self::Summary {
594 self.evaluator.unmaterializable(func)
595 }
596
597 fn unary(&self, func: &UnaryFunc, expr: Self::Summary) -> Self::Summary {
598 self.evaluator.unary(func, expr)
599 }
600
601 fn binary(
602 &self,
603 func: &BinaryFunc,
604 left: Self::Summary,
605 right: Self::Summary,
606 ) -> Self::Summary {
607 self.evaluator.binary(func, left, right)
608 }
609
610 fn variadic(&self, func: &VariadicFunc, exprs: Vec<Self::Summary>) -> Self::Summary {
611 self.evaluator.variadic(func, exprs)
612 }
613
614 fn cond(&self, cond: Self::Summary, then: Self::Summary, els: Self::Summary) -> Self::Summary {
615 self.evaluator.cond(cond, then, els)
616 }
617}
618
619struct SpecialUnary {
624 map_fn: for<'a, 'b> fn(&'b ColumnSpecs<'a>, ResultSpec<'a>) -> ResultSpec<'a>,
625 pushdownable: bool,
626}
627
628impl SpecialUnary {
629 fn for_func(func: &UnaryFunc) -> Option<SpecialUnary> {
631 fn eagerly<'b>(
635 spec: ResultSpec<'b>,
636 value_fn: impl FnOnce(Values<'b>) -> ResultSpec<'b>,
637 ) -> ResultSpec<'b> {
638 let result = match spec.values {
639 Values::Empty => ResultSpec::nothing(),
640 other => value_fn(other),
641 };
642 ResultSpec {
643 fallible: spec.fallible || result.fallible,
644 nullable: spec.nullable || result.nullable,
645 values: result.values,
646 }
647 }
648 match func {
649 UnaryFunc::TryParseMonotonicIso8601Timestamp(_) => Some(SpecialUnary {
650 map_fn: |specs, range| {
651 let expr = MirScalarExpr::CallUnary {
652 func: UnaryFunc::TryParseMonotonicIso8601Timestamp(
653 crate::func::TryParseMonotonicIso8601Timestamp,
654 ),
655 expr: Box::new(MirScalarExpr::column(0)),
656 };
657 let eval = |d| specs.eval_result(expr.eval(&[d], specs.arena));
658
659 eagerly(range, |values| {
660 match values {
661 Values::Within(a, b) if a == b => eval(a),
662 Values::Within(a, b) => {
663 let spec = eval(a).union(eval(b));
664 let values_spec = if spec.nullable {
665 ResultSpec::value_all()
671 } else {
672 spec
673 };
674 values_spec.union(ResultSpec::null())
678 }
679 _ => ResultSpec::any_infallible(),
682 }
683 })
684 },
685 pushdownable: true,
686 }),
687 _ => None,
688 }
689 }
690}
691
692struct AbstractFunc {
703 handler: AbstractFuncHandler,
704 pushdownable: (bool, bool),
710}
711
712enum AbstractFuncHandler {
714 Override(for<'a> fn(ResultSpec<'a>, ResultSpec<'a>) -> ResultSpec<'a>),
717 DynamicMonotone(fn(&ResultSpec<'_>, &ResultSpec<'_>) -> (bool, bool)),
724}
725
726impl AbstractFunc {
727 fn for_func(func: &BinaryFunc) -> Option<AbstractFunc> {
729 fn eagerly<'b>(
733 left: ResultSpec<'b>,
734 right: ResultSpec<'b>,
735 value_fn: impl FnOnce(Values<'b>, Values<'b>) -> ResultSpec<'b>,
736 ) -> ResultSpec<'b> {
737 let result = match (left.values, right.values) {
738 (Values::Empty, _) | (_, Values::Empty) => ResultSpec::nothing(),
739 (l, r) => value_fn(l, r),
740 };
741 ResultSpec {
742 fallible: left.fallible || right.fallible || result.fallible,
743 nullable: left.nullable || right.nullable || result.nullable,
744 values: result.values,
745 }
746 }
747
748 fn jsonb_get_string<'b>(
749 left: ResultSpec<'b>,
750 right: ResultSpec<'b>,
751 stringify: bool,
752 ) -> ResultSpec<'b> {
753 eagerly(left, right, |left, right| {
754 let nested_spec = match (left, right) {
755 (Values::Nested(mut map_spec), Values::Within(key, key2)) if key == key2 => {
756 map_spec.remove(&key)
757 }
758 _ => None,
759 };
760
761 if let Some(field_spec) = nested_spec {
762 if stringify {
763 let values = match field_spec.values {
766 Values::Empty => Values::Empty,
767 Values::Within(min @ Datum::String(_), max @ Datum::String(_)) => {
768 Values::Within(min, max)
769 }
770 Values::Within(_, _) | Values::Nested(_) | Values::All => Values::All,
771 };
772 ResultSpec {
773 values,
774 ..field_spec
775 }
776 } else {
777 field_spec
778 }
779 } else {
780 ResultSpec::any_infallible()
785 }
786 })
787 }
788
789 fn eq<'b>(left: ResultSpec<'b>, right: ResultSpec<'b>) -> ResultSpec<'b> {
790 eagerly(left, right, |left, right| {
791 let maybe_true = match left.clone().intersect(right.clone()) {
793 Values::Empty => ResultSpec::nothing(),
794 _ => ResultSpec::value(Datum::True),
795 };
796
797 let maybe_false = match left.union(right) {
800 Values::Within(a, b) if a == b => ResultSpec::nothing(),
801 _ => ResultSpec::value(Datum::False),
802 };
803
804 maybe_true.union(maybe_false)
805 })
806 }
807
808 fn timestamp_plus_interval_monotone(
817 _left: &ResultSpec<'_>,
818 right: &ResultSpec<'_>,
819 ) -> (bool, bool) {
820 let months_zero = matches!(
821 right.values.as_single(),
822 Some(Datum::Interval(i)) if i.months == 0,
823 );
824 (months_zero, months_zero)
825 }
826
827 match func {
828 BinaryFunc::JsonbGetString(_) => Some(AbstractFunc {
829 handler: AbstractFuncHandler::Override(|l, r| jsonb_get_string(l, r, false)),
830 pushdownable: (true, false),
831 }),
832 BinaryFunc::JsonbGetStringStringify(_) => Some(AbstractFunc {
833 handler: AbstractFuncHandler::Override(|l, r| jsonb_get_string(l, r, true)),
834 pushdownable: (true, false),
835 }),
836 BinaryFunc::Eq(_) => Some(AbstractFunc {
837 handler: AbstractFuncHandler::Override(eq),
838 pushdownable: (true, true),
839 }),
840 BinaryFunc::AddTimestampInterval(_)
841 | BinaryFunc::AddTimestampTzInterval(_)
842 | BinaryFunc::SubTimestampInterval(_)
843 | BinaryFunc::SubTimestampTzInterval(_) => Some(AbstractFunc {
844 handler: AbstractFuncHandler::DynamicMonotone(timestamp_plus_interval_monotone),
845 pushdownable: (true, false),
852 }),
853 _ => None,
854 }
855 }
856}
857
858#[derive(Clone, Debug)]
859pub struct ColumnSpec<'a> {
860 pub col_type: ReprColumnType,
861 pub range: ResultSpec<'a>,
862}
863
864#[derive(Clone, Debug)]
870pub struct ColumnSpecs<'a> {
871 pub relation: &'a ReprRelationType,
872 pub columns: Vec<ResultSpec<'a>>,
873 pub unmaterializables: BTreeMap<UnmaterializableFunc, ResultSpec<'a>>,
874 pub arena: &'a RowArena,
875}
876
877impl<'a> ColumnSpecs<'a> {
878 const MAX_EVAL_ARGS: usize = 6;
885
886 pub fn new(relation: &'a ReprRelationType, arena: &'a RowArena) -> Self {
889 let columns = relation
890 .column_types
891 .iter()
892 .map(|ct| ResultSpec::has_type(ct, false))
893 .collect();
894 ColumnSpecs {
895 relation,
896 columns,
897 unmaterializables: Default::default(),
898 arena,
899 }
900 }
901
902 pub fn push_column(&mut self, id: usize, update: ResultSpec<'a>) {
905 let range = self.columns.get_mut(id).expect("valid column id");
906 *range = range.clone().intersect(update);
907 }
908
909 pub fn push_unmaterializable(&mut self, func: UnmaterializableFunc, update: ResultSpec<'a>) {
912 let range = self
913 .unmaterializables
914 .entry(func.clone())
915 .or_insert_with(|| ResultSpec::has_type(&func.output_type(), true));
916 *range = range.clone().intersect(update);
917 }
918
919 fn eval_result<'b, E>(&self, result: Result<Datum<'b>, E>) -> ResultSpec<'a> {
920 match result {
921 Ok(Datum::Null) => ResultSpec {
922 nullable: true,
923 ..ResultSpec::nothing()
924 },
925 Ok(d) => ResultSpec {
926 values: Values::just(self.arena.make_datum(|packer| packer.push(d))),
927 ..ResultSpec::nothing()
928 },
929 Err(_) => ResultSpec {
930 fallible: true,
931 ..ResultSpec::nothing()
932 },
933 }
934 }
935
936 fn set_literal(expr: &mut MirScalarExpr, update: Result<Datum, EvalError>) {
937 match expr {
938 MirScalarExpr::Literal(literal, col_type) => match update {
939 Err(error) => *literal = Err(error),
940 Ok(datum) => {
941 assert!(
942 datum.is_instance_of(col_type),
943 "{datum:?} must be an instance of {col_type:?}"
944 );
945 match literal {
946 Ok(row) => row.packer().push(datum),
948 literal => *literal = Ok(Row::pack_slice(&[datum])),
949 }
950 }
951 },
952 _ => panic!("not a literal"),
953 }
954 }
955
956 fn set_argument(expr: &mut MirScalarExpr, arg: usize, value: Result<Datum, EvalError>) {
957 match (expr, arg) {
958 (MirScalarExpr::CallUnary { expr, .. }, 0) => Self::set_literal(expr, value),
959 (MirScalarExpr::CallBinary { expr1, .. }, 0) => Self::set_literal(expr1, value),
960 (MirScalarExpr::CallBinary { expr2, .. }, 1) => Self::set_literal(expr2, value),
961 (MirScalarExpr::CallVariadic { exprs, .. }, n) if n < exprs.len() => {
962 Self::set_literal(&mut exprs[n], value)
963 }
964 _ => panic!("illegal argument for expression"),
965 }
966 }
967
968 fn placeholder(col_type: ReprColumnType) -> MirScalarExpr {
972 MirScalarExpr::Literal(Err(EvalError::Internal("".into())), col_type)
973 }
974}
975
976impl<'a> Interpreter for ColumnSpecs<'a> {
977 type Summary = ColumnSpec<'a>;
978
979 fn column(&self, id: usize) -> Self::Summary {
980 let col_type = self.relation.column_types[id].clone();
981 let range = self.columns[id].clone();
982 ColumnSpec { col_type, range }
983 }
984
985 fn literal(&self, result: &Result<Row, EvalError>, col_type: &ReprColumnType) -> Self::Summary {
986 let col_type = col_type.clone();
987 let range = self.eval_result(result.as_ref().map(|row| {
988 self.arena
989 .make_datum(|packer| packer.push(row.unpack_first()))
990 }));
991 ColumnSpec { col_type, range }
992 }
993
994 fn unmaterializable(&self, func: &UnmaterializableFunc) -> Self::Summary {
995 let col_type = func.output_type();
996 let range = self
997 .unmaterializables
998 .get(func)
999 .cloned()
1000 .unwrap_or_else(|| ResultSpec::has_type(&func.output_type(), true));
1001 ColumnSpec { col_type, range }
1002 }
1003
1004 fn unary(&self, func: &UnaryFunc, summary: Self::Summary) -> Self::Summary {
1005 let fallible = func.could_error() || summary.range.fallible;
1006 let input_multivalued = !summary.range.is_single_value();
1014 let mapped_spec = if let Some(special) = SpecialUnary::for_func(func) {
1015 (special.map_fn)(self, summary.range)
1016 } else {
1017 let is_monotone = func.is_monotone();
1018 let mut expr = MirScalarExpr::CallUnary {
1019 func: func.clone(),
1020 expr: Box::new(Self::placeholder(summary.col_type.clone())),
1021 };
1022 summary.range.flat_map(is_monotone, |datum| {
1023 Self::set_argument(&mut expr, 0, datum);
1024 self.eval_result(expr.eval(&[], self.arena))
1025 })
1026 };
1027
1028 let col_type = func.output_type(summary.col_type);
1029
1030 let mut range = mapped_spec.intersect(ResultSpec::has_type(&col_type, fallible));
1031 if fallible && input_multivalued {
1035 range.fallible = true;
1036 }
1037 ColumnSpec { col_type, range }
1038 }
1039
1040 fn binary(
1041 &self,
1042 func: &BinaryFunc,
1043 left: Self::Summary,
1044 right: Self::Summary,
1045 ) -> Self::Summary {
1046 let fallible = func.could_error() || left.range.fallible || right.range.fallible;
1047 let inputs_multivalued = !left.range.is_single_value() || !right.range.is_single_value();
1050 let operand_may_be_infinite = left.range.may_be_infinite() || right.range.may_be_infinite();
1051
1052 let special = AbstractFunc::for_func(func);
1053 let (left_monotonic, right_monotonic) = match &special {
1054 Some(AbstractFunc {
1055 handler: AbstractFuncHandler::DynamicMonotone(monotone_fn),
1056 ..
1057 }) => monotone_fn(&left.range, &right.range),
1058 _ => func.is_monotone(),
1059 };
1060
1061 let mapped_spec = match special {
1062 Some(AbstractFunc {
1063 handler: AbstractFuncHandler::Override(f),
1064 ..
1065 }) => f(left.range, right.range),
1066 _ => {
1067 let mut expr = MirScalarExpr::CallBinary {
1068 func: func.clone(),
1069 expr1: Box::new(Self::placeholder(left.col_type.clone())),
1070 expr2: Box::new(Self::placeholder(right.col_type.clone())),
1071 };
1072 left.range.flat_map(left_monotonic, |left_result| {
1073 Self::set_argument(&mut expr, 0, left_result);
1074 right.range.flat_map(right_monotonic, |right_result| {
1075 Self::set_argument(&mut expr, 1, right_result);
1076 self.eval_result(expr.eval(&[], self.arena))
1077 })
1078 })
1079 }
1080 };
1081
1082 let col_type = func.output_type(&[left.col_type, right.col_type]);
1083
1084 let mut range = mapped_spec.intersect(ResultSpec::has_type(&col_type, fallible));
1085 if fallible && inputs_multivalued {
1088 range.fallible = true;
1089 }
1090 if operand_may_be_infinite && !func.is_infinity_monotone() {
1098 range.values = Values::All;
1099 }
1100 ColumnSpec { col_type, range }
1101 }
1102
1103 fn variadic(&self, func: &VariadicFunc, args: Vec<Self::Summary>) -> Self::Summary {
1104 let fallible = func.could_error() || args.iter().any(|s| s.range.fallible);
1105 let inputs_multivalued = args.iter().any(|s| !s.range.is_single_value());
1106 if func.is_associative() && args.len() > 2 {
1107 return args
1110 .into_iter()
1111 .reduce(|a, b| self.variadic(func, vec![a, b]))
1112 .expect("reducing over a non-empty argument list");
1113 }
1114
1115 let mapped_spec = if args.len() >= Self::MAX_EVAL_ARGS {
1116 ResultSpec::anything()
1117 } else {
1118 fn eval_loop<'a>(
1119 is_monotonic: bool,
1120 expr: &mut MirScalarExpr,
1121 args: &[ColumnSpec<'a>],
1122 index: usize,
1123 datum_map: &mut impl FnMut(&MirScalarExpr) -> ResultSpec<'a>,
1124 ) -> ResultSpec<'a> {
1125 if index >= args.len() {
1126 datum_map(expr)
1127 } else {
1128 args[index].range.flat_map(is_monotonic, |datum| {
1129 ColumnSpecs::set_argument(expr, index, datum);
1130 eval_loop(is_monotonic, expr, args, index + 1, datum_map)
1131 })
1132 }
1133 }
1134
1135 let mut fn_expr = MirScalarExpr::CallVariadic {
1136 func: func.clone(),
1137 exprs: args
1138 .iter()
1139 .map(|spec| Self::placeholder(spec.col_type.clone()))
1140 .collect(),
1141 };
1142 eval_loop(func.is_monotone(), &mut fn_expr, &args, 0, &mut |expr| {
1143 self.eval_result(expr.eval(&[], self.arena))
1144 })
1145 };
1146
1147 let col_types = args.into_iter().map(|spec| spec.col_type).collect();
1148 let col_type = func.output_type(col_types);
1149
1150 let mut range = mapped_spec.intersect(ResultSpec::has_type(&col_type, fallible));
1151 if fallible && inputs_multivalued {
1154 range.fallible = true;
1155 }
1156
1157 ColumnSpec { col_type, range }
1158 }
1159
1160 fn cond(&self, cond: Self::Summary, then: Self::Summary, els: Self::Summary) -> Self::Summary {
1161 let col_type = then
1162 .col_type
1163 .union(&els.col_type)
1164 .expect("failed type union for cond during abstract interpretation");
1165
1166 let range = cond
1167 .range
1168 .flat_map(true, |datum| match datum {
1169 Ok(Datum::True) => then.range.clone(),
1170 Ok(Datum::False) | Ok(Datum::Null) => els.range.clone(),
1175 _ => ResultSpec::fails(),
1176 })
1177 .intersect(ResultSpec::has_type(&col_type, true));
1178
1179 ColumnSpec { col_type, range }
1180 }
1181
1182 fn mfp_filter(&self, mfp: &MapFilterProject) -> Self::Summary {
1196 let mfp_eval = MfpEval::new(self, mfp.input_arity, &mfp.expressions);
1197 let predicates = mfp
1198 .predicates
1199 .iter()
1200 .map(|(_, e)| mfp_eval.expr(e))
1201 .collect();
1202 let mut result = self.variadic(&And.into(), predicates);
1203 if mfp_eval.expressions.iter().any(|s| s.range.fallible) {
1204 result.range.fallible = true;
1205 }
1206 result
1207 }
1208
1209 fn mfp_plan_filter(&self, plan: &MfpPlan) -> Self::Summary {
1210 let mfp_eval = MfpEval::new(self, plan.mfp.input_arity, &plan.mfp.expressions);
1211 let mut results: Vec<_> = plan
1212 .mfp
1213 .predicates
1214 .iter()
1215 .map(|(_, e)| mfp_eval.expr(e))
1216 .collect();
1217 let mz_now = mfp_eval.unmaterializable(&UnmaterializableFunc::MzNow);
1218 for bound in &plan.lower_bounds {
1219 let bound_range = mfp_eval.expr(bound);
1220 let result = mfp_eval.binary(&BinaryFunc::Lte(func::Lte), bound_range, mz_now.clone());
1221 results.push(result);
1222 }
1223 for bound in &plan.upper_bounds {
1224 let bound_range = mfp_eval.expr(bound);
1225 let result = mfp_eval.binary(&BinaryFunc::Gte(func::Gte), bound_range, mz_now.clone());
1226 results.push(result);
1227 }
1228 let mut result = self.variadic(&And.into(), results);
1229 if mfp_eval.expressions.iter().any(|s| s.range.fallible) {
1230 result.range.fallible = true;
1231 }
1232 result
1233 }
1234}
1235
1236#[derive(Debug)]
1245pub struct Trace;
1246
1247#[derive(Copy, Clone, Debug, PartialOrd, PartialEq, Ord, Eq)]
1254pub enum TraceSummary {
1255 Constant,
1258 Dynamic,
1263 Unknown,
1266}
1267
1268impl TraceSummary {
1269 fn apply_fn(self, pushdownable: bool) -> Self {
1274 match self {
1275 TraceSummary::Constant => TraceSummary::Constant,
1276 TraceSummary::Dynamic => match pushdownable {
1277 true => TraceSummary::Dynamic,
1278 false => TraceSummary::Unknown,
1279 },
1280 TraceSummary::Unknown => TraceSummary::Unknown,
1281 }
1282 }
1283
1284 pub fn pushdownable(self) -> bool {
1286 match self {
1287 TraceSummary::Constant | TraceSummary::Dynamic => true,
1288 TraceSummary::Unknown => false,
1289 }
1290 }
1291}
1292
1293impl Interpreter for Trace {
1294 type Summary = TraceSummary;
1295
1296 fn column(&self, _id: usize) -> Self::Summary {
1297 TraceSummary::Dynamic
1298 }
1299
1300 fn literal(
1301 &self,
1302 _result: &Result<Row, EvalError>,
1303 _col_type: &ReprColumnType,
1304 ) -> Self::Summary {
1305 TraceSummary::Constant
1306 }
1307
1308 fn unmaterializable(&self, _func: &UnmaterializableFunc) -> Self::Summary {
1309 TraceSummary::Dynamic
1310 }
1311
1312 fn unary(&self, func: &UnaryFunc, expr: Self::Summary) -> Self::Summary {
1313 let pushdownable = match SpecialUnary::for_func(func) {
1314 None => func.is_monotone(),
1315 Some(special) => special.pushdownable,
1316 };
1317 expr.apply_fn(pushdownable)
1318 }
1319
1320 fn binary(
1321 &self,
1322 func: &BinaryFunc,
1323 left: Self::Summary,
1324 right: Self::Summary,
1325 ) -> Self::Summary {
1326 let (left_pushdownable, right_pushdownable) = match AbstractFunc::for_func(func) {
1327 None => func.is_monotone(),
1328 Some(special) => special.pushdownable,
1329 };
1330 left.apply_fn(left_pushdownable)
1331 .max(right.apply_fn(right_pushdownable))
1332 }
1333
1334 fn variadic(&self, func: &VariadicFunc, exprs: Vec<Self::Summary>) -> Self::Summary {
1335 if !func.is_associative() && exprs.len() >= ColumnSpecs::MAX_EVAL_ARGS {
1336 return TraceSummary::Unknown;
1339 }
1340
1341 let pushdownable_fn = func.is_monotone();
1342 exprs
1343 .into_iter()
1344 .map(|pushdownable_arg| pushdownable_arg.apply_fn(pushdownable_fn))
1345 .max()
1346 .unwrap_or(TraceSummary::Constant)
1347 }
1348
1349 fn cond(&self, cond: Self::Summary, then: Self::Summary, els: Self::Summary) -> Self::Summary {
1350 let cond = cond.min(TraceSummary::Dynamic);
1353 cond.max(then).max(els)
1354 }
1355}
1356
1357#[cfg(test)]
1358mod tests {
1359 use itertools::Itertools;
1360 use mz_repr::adt::datetime::DateTimeUnits;
1361 use mz_repr::{Datum, PropDatum, RowArena, SqlScalarType};
1362 use proptest::prelude::*;
1363 use proptest::sample::{Index, select};
1364
1365 use crate::func::*;
1366 use crate::scalar::func::variadic::Concat;
1367 use crate::{BinaryFunc, MirScalarExpr, UnaryFunc};
1368
1369 use super::*;
1370
1371 #[derive(Debug)]
1372 struct ExpressionData {
1373 relation_type: ReprRelationType,
1374 specs: Vec<ResultSpec<'static>>,
1375 rows: Vec<Row>,
1376 expr: MirScalarExpr,
1377 }
1378
1379 const NUM_TYPE: ReprScalarType = ReprScalarType::Numeric;
1384 static SCALAR_TYPES: &[ReprScalarType] = &[
1385 ReprScalarType::Bool,
1386 ReprScalarType::Jsonb,
1387 NUM_TYPE,
1388 ReprScalarType::Int32,
1389 ReprScalarType::Float32,
1390 ReprScalarType::Float64,
1391 ReprScalarType::Date,
1392 ReprScalarType::Timestamp,
1393 ReprScalarType::MzTimestamp,
1394 ReprScalarType::Interval,
1395 ReprScalarType::String,
1396 ];
1397
1398 const INTERESTING_UNARY_FUNCS: &[UnaryFunc] = {
1399 &[
1400 UnaryFunc::CastNumericToMzTimestamp(CastNumericToMzTimestamp),
1401 UnaryFunc::CastTimestampToMzTimestamp(CastTimestampToMzTimestamp),
1402 UnaryFunc::NegNumeric(NegNumeric),
1403 UnaryFunc::NegFloat64(NegFloat64),
1404 UnaryFunc::CastJsonbToNumeric(CastJsonbToNumeric(None)),
1405 UnaryFunc::CastJsonbToBool(CastJsonbToBool),
1406 UnaryFunc::CastJsonbToString(CastJsonbToString),
1407 UnaryFunc::DateTruncTimestamp(DateTruncTimestamp(DateTimeUnits::Epoch)),
1408 UnaryFunc::ExtractTimestamp(ExtractTimestamp(DateTimeUnits::Epoch)),
1409 UnaryFunc::ExtractDate(ExtractDate(DateTimeUnits::Epoch)),
1410 UnaryFunc::Not(Not),
1411 UnaryFunc::IsNull(IsNull),
1412 UnaryFunc::IsFalse(IsFalse),
1413 UnaryFunc::TryParseMonotonicIso8601Timestamp(TryParseMonotonicIso8601Timestamp),
1414 ]
1415 };
1416
1417 fn unary_typecheck(func: &UnaryFunc, arg: &ReprColumnType) -> bool {
1418 use UnaryFunc::*;
1419 match func {
1420 CastNumericToMzTimestamp(_) | NegNumeric(_) => arg.scalar_type == NUM_TYPE,
1421 NegFloat64(_) => arg.scalar_type == ReprScalarType::Float64,
1422 CastTimestampToMzTimestamp(_) => arg.scalar_type == ReprScalarType::Timestamp,
1423 CastJsonbToNumeric(_) | CastJsonbToBool(_) | CastJsonbToString(_) => {
1424 arg.scalar_type == ReprScalarType::Jsonb
1425 }
1426 ExtractTimestamp(_) | DateTruncTimestamp(_) => {
1427 arg.scalar_type == ReprScalarType::Timestamp
1428 }
1429 ExtractDate(_) => arg.scalar_type == ReprScalarType::Date,
1430 Not(_) => arg.scalar_type == ReprScalarType::Bool,
1431 IsNull(_) => true,
1432 TryParseMonotonicIso8601Timestamp(_) => arg.scalar_type == ReprScalarType::String,
1433 _ => false,
1434 }
1435 }
1436
1437 fn interesting_binary_funcs() -> Vec<BinaryFunc> {
1438 vec![
1439 AddTimestampInterval.into(),
1440 AddNumeric.into(),
1441 SubNumeric.into(),
1442 MulNumeric.into(),
1443 DivNumeric.into(),
1444 AddFloat64.into(),
1445 SubFloat64.into(),
1446 MulFloat64.into(),
1447 DivFloat64.into(),
1448 MulFloat32.into(),
1449 DivFloat32.into(),
1450 RoundNumericBinary.into(),
1451 Eq.into(),
1452 Lt.into(),
1453 Gt.into(),
1454 Lte.into(),
1455 Gte.into(),
1456 DateTruncUnitsTimestamp.into(),
1457 JsonbGetString.into(),
1458 JsonbGetStringStringify.into(),
1459 ]
1460 }
1461
1462 fn binary_typecheck(func: &BinaryFunc, arg0: &ReprColumnType, arg1: &ReprColumnType) -> bool {
1463 use BinaryFunc::*;
1464 match func {
1465 AddTimestampInterval(_) => {
1466 arg0.scalar_type == ReprScalarType::Timestamp
1467 && arg1.scalar_type == ReprScalarType::Interval
1468 }
1469 AddNumeric(_) | SubNumeric(_) | MulNumeric(_) | DivNumeric(_) => {
1470 arg0.scalar_type == NUM_TYPE && arg1.scalar_type == NUM_TYPE
1471 }
1472 AddFloat64(_) | SubFloat64(_) | MulFloat64(_) | DivFloat64(_) => {
1473 arg0.scalar_type == ReprScalarType::Float64
1474 && arg1.scalar_type == ReprScalarType::Float64
1475 }
1476 MulFloat32(_) | DivFloat32(_) => {
1477 arg0.scalar_type == ReprScalarType::Float32
1478 && arg1.scalar_type == ReprScalarType::Float32
1479 }
1480 RoundNumeric(_) => {
1481 arg0.scalar_type == NUM_TYPE && arg1.scalar_type == ReprScalarType::Int32
1482 }
1483 Eq(_) | Lt(_) | Gt(_) | Lte(_) | Gte(_) => arg0.scalar_type == arg1.scalar_type,
1484 DateTruncTimestamp(_) => {
1485 arg0.scalar_type == ReprScalarType::String
1486 && arg1.scalar_type == ReprScalarType::Timestamp
1487 }
1488 JsonbGetString(_) | JsonbGetStringStringify(_) => {
1489 arg0.scalar_type == ReprScalarType::Jsonb
1490 && arg1.scalar_type == ReprScalarType::String
1491 }
1492 _ => false,
1493 }
1494 }
1495
1496 const INTERESTING_VARIADIC_FUNCS: &[VariadicFunc] = {
1497 use crate::scalar::func::variadic as v;
1498 use VariadicFunc::*;
1499 &[
1500 Coalesce(v::Coalesce),
1501 Greatest(v::Greatest),
1502 Least(v::Least),
1503 And(v::And),
1504 Or(v::Or),
1505 Concat(v::Concat),
1506 ConcatWs(v::ConcatWs),
1507 ]
1508 };
1509
1510 fn variadic_typecheck(func: &VariadicFunc, args: &[ReprColumnType]) -> bool {
1511 use VariadicFunc::*;
1512 fn all_eq<'a>(
1513 iter: impl IntoIterator<Item = &'a ReprColumnType>,
1514 other: &ReprScalarType,
1515 ) -> bool {
1516 iter.into_iter().all(|t| t.scalar_type == *other)
1517 }
1518 match func {
1519 Coalesce(_) | Greatest(_) | Least(_) => match args {
1520 [] => true,
1521 [first, rest @ ..] => all_eq(rest, &first.scalar_type),
1522 },
1523 And(_) | Or(_) => all_eq(args, &ReprScalarType::Bool),
1524 Concat(_) => all_eq(args, &ReprScalarType::String),
1525 ConcatWs(_) => args.len() > 1 && all_eq(args, &ReprScalarType::String),
1526 _ => false,
1527 }
1528 }
1529
1530 fn gen_datums_for_type(typ: &ReprColumnType) -> BoxedStrategy<Datum<'static>> {
1531 let mut values: Vec<Datum<'static>> = SqlScalarType::from_repr(&typ.scalar_type)
1532 .interesting_datums()
1533 .collect();
1534 if typ.nullable {
1535 values.push(Datum::Null)
1536 }
1537 select(values).boxed()
1538 }
1539
1540 fn gen_column() -> impl Strategy<Value = (ReprColumnType, Datum<'static>, ResultSpec<'static>)>
1541 {
1542 let col_type = (select(SCALAR_TYPES), any::<bool>())
1543 .prop_map(|(t, b)| t.nullable(b))
1544 .prop_filter("need at least one value", |c| {
1545 SqlScalarType::from_repr(&c.scalar_type)
1546 .interesting_datums()
1547 .count()
1548 > 0
1549 });
1550
1551 let result_spec = select(vec![
1552 ResultSpec::nothing(),
1553 ResultSpec::null(),
1554 ResultSpec::anything(),
1555 ResultSpec::value_all(),
1556 ]);
1557
1558 (col_type, result_spec).prop_flat_map(|(col, result_spec)| {
1559 gen_datums_for_type(&col).prop_map(move |datum| {
1560 let result_spec = result_spec.clone().union(ResultSpec::value(datum));
1561 (col.clone(), datum, result_spec)
1562 })
1563 })
1564 }
1565
1566 fn gen_expr_for_relation(
1567 relation: &ReprRelationType,
1568 ) -> BoxedStrategy<(MirScalarExpr, ReprColumnType)> {
1569 let column_gen = {
1570 let column_types = relation.column_types.clone();
1571 any::<Index>()
1572 .prop_map(move |idx| {
1573 let id = idx.index(column_types.len());
1574 (MirScalarExpr::column(id), column_types[id].clone())
1575 })
1576 .boxed()
1577 };
1578
1579 let literal_gen = (select(SCALAR_TYPES), any::<bool>())
1580 .prop_map(|(s, b)| s.nullable(b))
1581 .prop_flat_map(|ct| {
1582 let error_gen = any::<EvalError>().prop_map(Err).boxed();
1583 let value_gen = gen_datums_for_type(&ct)
1584 .prop_map(move |datum| Ok(Row::pack_slice(&[datum])))
1585 .boxed();
1586 error_gen.prop_union(value_gen).prop_map(move |result| {
1587 (MirScalarExpr::Literal(result, ct.clone()), ct.clone())
1588 })
1589 })
1590 .boxed();
1591
1592 column_gen
1593 .prop_union(literal_gen)
1594 .prop_recursive(4, 64, 8, |self_gen| {
1595 let unary_gen = (select(INTERESTING_UNARY_FUNCS), self_gen.clone())
1596 .prop_filter_map("unary func", |(func, (expr_in, type_in))| {
1597 if !unary_typecheck(&func, &type_in) {
1598 return None;
1599 }
1600 let type_out = func.output_type(type_in);
1601 let expr_out = MirScalarExpr::CallUnary {
1602 func,
1603 expr: Box::new(expr_in),
1604 };
1605 Some((expr_out, type_out))
1606 })
1607 .boxed();
1608 let binary_gen = (
1609 select(interesting_binary_funcs()),
1610 self_gen.clone(),
1611 self_gen.clone(),
1612 )
1613 .prop_filter_map(
1614 "binary func",
1615 |(func, (expr_left, type_left), (expr_right, type_right))| {
1616 if !binary_typecheck(&func, &type_left, &type_right) {
1617 return None;
1618 }
1619 let type_out = func.output_type(&[type_left, type_right]);
1620 let expr_out = MirScalarExpr::CallBinary {
1621 func,
1622 expr1: Box::new(expr_left),
1623 expr2: Box::new(expr_right),
1624 };
1625 Some((expr_out, type_out))
1626 },
1627 )
1628 .boxed();
1629 let variadic_gen = (
1630 select(INTERESTING_VARIADIC_FUNCS),
1631 prop::collection::vec(self_gen.clone(), 1..4),
1632 )
1633 .prop_filter_map("variadic func", |(func, exprs)| {
1634 let (exprs_in, type_in): (_, Vec<_>) = exprs.into_iter().unzip();
1635 if !variadic_typecheck(&func, &type_in) {
1636 return None;
1637 }
1638 let type_out = func.output_type(type_in);
1639 let expr_out = MirScalarExpr::CallVariadic {
1640 func,
1641 exprs: exprs_in,
1642 };
1643 Some((expr_out, type_out))
1644 })
1645 .boxed();
1646 let if_gen = {
1653 let bool_type = ReprScalarType::Bool.nullable(true);
1654 let cond_gen = gen_datums_for_type(&bool_type).prop_map(move |datum| {
1655 MirScalarExpr::Literal(Ok(Row::pack_slice(&[datum])), bool_type.clone())
1656 });
1657 (cond_gen, self_gen.clone())
1658 .prop_flat_map(|(cond_expr, (then_expr, then_type))| {
1659 let out_type = then_type.clone();
1660 gen_datums_for_type(&then_type).prop_map(move |datum| {
1661 let els_expr = MirScalarExpr::Literal(
1662 Ok(Row::pack_slice(&[datum])),
1663 out_type.clone(),
1664 );
1665 let expr_out = MirScalarExpr::If {
1666 cond: Box::new(cond_expr.clone()),
1667 then: Box::new(then_expr.clone()),
1668 els: Box::new(els_expr),
1669 };
1670 (expr_out, out_type.clone())
1671 })
1672 })
1673 .boxed()
1674 };
1675
1676 unary_gen
1677 .prop_union(binary_gen)
1678 .boxed()
1679 .prop_union(variadic_gen)
1680 .boxed()
1681 .prop_union(if_gen)
1682 })
1683 .boxed()
1684 }
1685
1686 fn gen_expr_data() -> impl Strategy<Value = ExpressionData> {
1687 let columns = prop::collection::vec(gen_column(), 1..10);
1688 columns.prop_flat_map(|data| {
1689 let (columns, datums, specs): (Vec<_>, Vec<_>, Vec<_>) = data.into_iter().multiunzip();
1690 let relation = ReprRelationType::new(columns);
1691 let row = Row::pack_slice(&datums);
1692 gen_expr_for_relation(&relation).prop_map(move |(expr, _)| ExpressionData {
1693 relation_type: relation.clone(),
1694 specs: specs.clone(),
1695 rows: vec![row.clone()],
1696 expr,
1697 })
1698 })
1699 }
1700
1701 #[mz_ore::test]
1702 #[cfg_attr(miri, ignore)] fn test_trivial_spec_matches() {
1704 fn check(datum: PropDatum) -> Result<(), TestCaseError> {
1705 let datum: Datum = (&datum).into();
1706 let spec = if datum.is_null() {
1707 ResultSpec::null()
1708 } else {
1709 ResultSpec::value(datum)
1710 };
1711 assert!(spec.may_contain(datum));
1712 Ok(())
1713 }
1714
1715 proptest!(|(datum in mz_repr::arb_datum(true))| {
1716 check(datum)?;
1717 });
1718
1719 assert!(ResultSpec::fails().may_fail());
1720 }
1721
1722 #[mz_ore::test]
1723 #[cfg_attr(miri, ignore)] fn test_equivalence() {
1725 fn check(data: ExpressionData) -> Result<(), TestCaseError> {
1726 let ExpressionData {
1727 relation_type,
1728 specs,
1729 rows,
1730 expr,
1731 } = data;
1732
1733 let arena = RowArena::new();
1737 let mut interpreter = ColumnSpecs::new(&relation_type, &arena);
1738 for (id, spec) in specs.into_iter().enumerate() {
1739 interpreter.push_column(id, spec);
1740 }
1741
1742 let spec = interpreter.expr(&expr);
1743
1744 for row in &rows {
1745 let datums: Vec<_> = row.iter().collect();
1746 let eval_result = expr.eval(&datums, &arena);
1747 match eval_result {
1748 Ok(value) => {
1749 assert!(spec.range.may_contain(value))
1750 }
1751 Err(_) => {
1752 assert!(spec.range.may_fail());
1753 }
1754 }
1755 }
1756
1757 Ok(())
1758 }
1759
1760 proptest!(|(data in gen_expr_data())| {
1761 check(data)?;
1762 });
1763 }
1764
1765 fn gen_range_column()
1774 -> impl Strategy<Value = (ReprColumnType, Datum<'static>, ResultSpec<'static>)> {
1775 select(SCALAR_TYPES)
1776 .prop_map(|t| t.nullable(false))
1777 .prop_filter("need at least two distinct values for a range", |c| {
1778 let mut datums: Vec<Datum> = SqlScalarType::from_repr(&c.scalar_type)
1779 .interesting_datums()
1780 .filter(|d| !d.is_null())
1781 .collect();
1782 datums.sort();
1783 datums.dedup();
1784 datums.len() >= 2
1785 })
1786 .prop_flat_map(|col| {
1787 let mut datums: Vec<Datum<'static>> = SqlScalarType::from_repr(&col.scalar_type)
1788 .interesting_datums()
1789 .filter(|d| !d.is_null())
1790 .collect();
1791 datums.sort();
1792 datums.dedup();
1793 (
1794 Just(col),
1795 Just(datums),
1796 any::<Index>(),
1797 any::<Index>(),
1798 any::<Index>(),
1799 )
1800 .prop_map(|(col, datums, a, b, c)| {
1801 let n = datums.len();
1802 let mut idxs = [a.index(n), b.index(n), c.index(n)];
1803 idxs.sort();
1804 let lo = datums[idxs[0]];
1805 let mid = datums[idxs[1]];
1806 let hi = datums[idxs[2]];
1807 let spec = ResultSpec::value_between(lo, hi);
1808 (col, mid, spec)
1809 })
1810 })
1811 }
1812
1813 fn gen_range_expr_data() -> impl Strategy<Value = ExpressionData> {
1814 let columns = prop::collection::vec(gen_range_column(), 1..10);
1815 columns.prop_flat_map(|data| {
1816 let (columns, datums, specs): (Vec<_>, Vec<_>, Vec<_>) = data.into_iter().multiunzip();
1817 let relation = ReprRelationType::new(columns);
1818 let row = Row::pack_slice(&datums);
1819 gen_expr_for_relation(&relation).prop_map(move |(expr, _)| ExpressionData {
1820 relation_type: relation.clone(),
1821 specs: specs.clone(),
1822 rows: vec![row.clone()],
1823 expr,
1824 })
1825 })
1826 }
1827
1828 #[mz_ore::test]
1839 #[cfg_attr(miri, ignore)] fn test_equivalence_ranges() {
1841 fn check(data: ExpressionData) -> Result<(), TestCaseError> {
1842 let ExpressionData {
1843 relation_type,
1844 specs,
1845 rows,
1846 expr,
1847 } = data;
1848
1849 let arena = RowArena::new();
1850 let mut interpreter = ColumnSpecs::new(&relation_type, &arena);
1851 for (id, spec) in specs.into_iter().enumerate() {
1852 interpreter.push_column(id, spec);
1853 }
1854
1855 let spec = interpreter.expr(&expr);
1856
1857 for row in &rows {
1858 let datums: Vec<_> = row.iter().collect();
1859 let eval_result = expr.eval(&datums, &arena);
1860 match eval_result {
1861 Ok(value) => {
1862 prop_assert!(
1863 spec.range.may_contain(value),
1864 "interpreter ruled out a value the evaluator produced \
1865 for an interior input: expr={expr:?} row={row:?} \
1866 value={value:?} spec={:?}",
1867 spec.range,
1868 );
1869 }
1870 Err(_) => {
1871 prop_assert!(
1872 spec.range.may_fail(),
1873 "interpreter ruled out an error the evaluator produced \
1874 for an interior input: expr={expr:?} row={row:?}",
1875 );
1876 }
1877 }
1878 }
1879
1880 Ok(())
1881 }
1882
1883 let config = ProptestConfig {
1888 cases: 2048,
1889 max_local_rejects: 1 << 20,
1890 ..ProptestConfig::default()
1891 };
1892 proptest!(config, |(data in gen_range_expr_data())| {
1893 check(data)?;
1894 });
1895 }
1896
1897 #[mz_ore::test]
1906 #[cfg_attr(miri, ignore)] fn test_result_spec_lattice_laws() {
1908 #[derive(Debug, Clone)]
1911 enum Recipe {
1912 Nothing,
1913 Null,
1914 Fails,
1915 Anything,
1916 ValueAll,
1917 Value(PropDatum),
1918 Between(PropDatum, PropDatum),
1919 Map(Vec<(PropDatum, Recipe)>),
1920 Union(Box<Recipe>, Box<Recipe>),
1921 }
1922
1923 fn materialize(recipe: &Recipe) -> ResultSpec<'_> {
1924 match recipe {
1925 Recipe::Nothing => ResultSpec::nothing(),
1926 Recipe::Null => ResultSpec::null(),
1927 Recipe::Fails => ResultSpec::fails(),
1928 Recipe::Anything => ResultSpec::anything(),
1929 Recipe::ValueAll => ResultSpec::value_all(),
1930 Recipe::Value(pd) => ResultSpec::value(pd.into()),
1931 Recipe::Between(a, b) => {
1932 let (a, b): (Datum, Datum) = (a.into(), b.into());
1933 if a.is_null() || b.is_null() {
1934 ResultSpec::nothing()
1935 } else if a <= b {
1936 ResultSpec::value_between(a, b)
1937 } else {
1938 ResultSpec::value_between(b, a)
1939 }
1940 }
1941 Recipe::Map(entries) => {
1942 let mut map = BTreeMap::new();
1943 for (key, val) in entries {
1944 let key: Datum = key.into();
1945 if !key.is_null() {
1946 map.insert(key, materialize(val));
1947 }
1948 }
1949 ResultSpec::map_spec(map)
1950 }
1951 Recipe::Union(a, b) => materialize(a).union(materialize(b)),
1952 }
1953 }
1954
1955 fn recipe_strategy() -> impl Strategy<Value = Recipe> {
1956 let leaf = proptest::strategy::Union::new(vec![
1957 Just(Recipe::Nothing).boxed(),
1958 Just(Recipe::Null).boxed(),
1959 Just(Recipe::Fails).boxed(),
1960 Just(Recipe::Anything).boxed(),
1961 Just(Recipe::ValueAll).boxed(),
1962 mz_repr::arb_datum(false).prop_map(Recipe::Value).boxed(),
1963 (mz_repr::arb_datum(false), mz_repr::arb_datum(false))
1964 .prop_map(|(a, b)| Recipe::Between(a, b))
1965 .boxed(),
1966 ]);
1967 leaf.prop_recursive(3, 24, 4, |inner| {
1968 proptest::strategy::Union::new(vec![
1969 prop::collection::vec((mz_repr::arb_datum(false), inner.clone()), 0..3)
1970 .prop_map(Recipe::Map)
1971 .boxed(),
1972 (inner.clone(), inner.clone())
1973 .prop_map(|(a, b)| Recipe::Union(Box::new(a), Box::new(b)))
1974 .boxed(),
1975 ])
1976 })
1977 }
1978
1979 fn check(a: Recipe, b: Recipe, v: PropDatum) -> Result<(), TestCaseError> {
1980 let a_spec = materialize(&a);
1981 let b_spec = materialize(&b);
1982 let v: Datum = (&v).into();
1983
1984 let in_a = a_spec.may_contain(v);
1985 let in_b = b_spec.may_contain(v);
1986
1987 if in_a || in_b {
1988 prop_assert!(
1989 a_spec.clone().union(b_spec.clone()).may_contain(v),
1990 "union dropped a value: a={a:?} b={b:?} v={v:?}",
1991 );
1992 }
1993 if in_a && in_b {
1994 prop_assert!(
1995 a_spec.intersect(b_spec).may_contain(v),
1996 "intersect dropped a common value: a={a:?} b={b:?} v={v:?}",
1997 );
1998 }
1999 Ok(())
2000 }
2001
2002 proptest!(
2003 ProptestConfig::with_cases(4096),
2004 |(a in recipe_strategy(), b in recipe_strategy(), v in mz_repr::arb_datum(true))| {
2005 check(a, b, v)?;
2006 }
2007 );
2008 }
2009
2010 #[mz_ore::test]
2021 #[cfg_attr(miri, ignore)] fn test_neg_numeric_nan_range() {
2023 use mz_repr::adt::numeric::Numeric;
2024
2025 let neg = MirScalarExpr::CallUnary {
2026 func: UnaryFunc::NegNumeric(NegNumeric),
2027 expr: Box::new(MirScalarExpr::column(0)),
2028 };
2029
2030 let relation = ReprRelationType::new(vec![ReprScalarType::Numeric.nullable(false)]);
2031 let arena = RowArena::new();
2032 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2033 interpreter.push_column(
2034 0,
2035 ResultSpec::value_between(
2036 Datum::from(Numeric::from(f64::NEG_INFINITY)),
2037 Datum::from(Numeric::from(f64::NAN)),
2038 ),
2039 );
2040
2041 let spec = interpreter.expr(&neg);
2042
2043 let actual = neg
2045 .eval(&[Datum::from(Numeric::from(-1.0f64))], &arena)
2046 .expect("eval succeeds");
2047 assert!(
2048 spec.range.may_contain(actual),
2049 "interpreter must not rule out {actual:?}, which the evaluator \
2050 produces for an interior input; got spec {:?}",
2051 spec.range,
2052 );
2053 }
2054
2055 #[mz_ore::test]
2066 #[cfg_attr(miri, ignore)] fn test_fallible_monotone_interior_error() {
2068 use mz_repr::adt::numeric::Numeric;
2069
2070 let cast = MirScalarExpr::CallUnary {
2071 func: UnaryFunc::CastNumericToMzTimestamp(CastNumericToMzTimestamp),
2072 expr: Box::new(MirScalarExpr::column(0)),
2073 };
2074
2075 let relation = ReprRelationType::new(vec![ReprScalarType::Numeric.nullable(false)]);
2076 let arena = RowArena::new();
2077 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2078 interpreter.push_column(
2079 0,
2080 ResultSpec::value_between(
2081 Datum::from(Numeric::from(0.0f64)),
2082 Datum::from(Numeric::from(2.0f64)),
2083 ),
2084 );
2085
2086 let spec = interpreter.expr(&cast);
2087
2088 let interior = Datum::from(Numeric::from(1.5f64));
2091 assert!(
2092 cast.eval(&[interior], &arena).is_err(),
2093 "precondition: a fractional numeric fails to cast to mz_timestamp",
2094 );
2095 assert!(
2096 spec.range.may_fail(),
2097 "interpreter must surface that a monotone-but-fallible function may \
2098 error on an interior value it never sampled; got spec {:?}",
2099 spec.range,
2100 );
2101 }
2102
2103 #[mz_ore::test]
2111 #[cfg_attr(miri, ignore)]
2112 fn test_mfp_unreferenced_fallible_expression() {
2113 use crate::scalar::func::CastStringToUuid;
2114
2115 let mfp = MapFilterProject {
2121 expressions: vec![MirScalarExpr::CallUnary {
2122 func: UnaryFunc::CastStringToUuid(CastStringToUuid),
2123 expr: Box::new(MirScalarExpr::column(0)),
2124 }],
2125 predicates: vec![(
2126 1,
2127 MirScalarExpr::literal_ok(Datum::True, ReprScalarType::Bool),
2128 )],
2129 projection: vec![0, 1],
2130 input_arity: 1,
2131 };
2132
2133 let relation = ReprRelationType::new(vec![ReprScalarType::String.nullable(false)]);
2134 let arena = RowArena::new();
2135 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2136 interpreter.push_column(
2138 0,
2139 ResultSpec::value_between(Datum::String("not-a-uuid"), Datum::String("not-a-uuid")),
2140 );
2141 let spec = interpreter.mfp_filter(&mfp);
2142 assert!(
2143 spec.range.may_fail(),
2144 "an MFP expression that errors on the stats range must propagate \
2145 fallibility, otherwise persist filter pushdown can wrongly discard \
2146 a part that produces error rows",
2147 );
2148 }
2149
2150 #[mz_ore::test]
2160 #[cfg_attr(miri, ignore)]
2161 fn test_mfp_filter_fallibility_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 input_arity = relation_type.column_types.len();
2171 let mfp = MapFilterProject {
2172 expressions: vec![expr.clone()],
2173 predicates: vec![],
2174 projection: (0..input_arity).collect(),
2175 input_arity,
2176 };
2177
2178 let arena = RowArena::new();
2179 let mut interpreter = ColumnSpecs::new(&relation_type, &arena);
2180 for (id, spec) in specs.into_iter().enumerate() {
2181 interpreter.push_column(id, spec);
2182 }
2183 let summary = interpreter.mfp_filter(&mfp);
2184
2185 for row in &rows {
2186 let datums: Vec<_> = row.iter().collect();
2187 if expr.eval(&datums, &arena).is_err() {
2188 prop_assert!(
2189 summary.range.may_fail(),
2190 "mfp_filter must surface the fallibility of an \
2191 unreferenced MFP expression: row {:?} errored at \
2192 runtime but the interpreter ruled out errors",
2193 row,
2194 );
2195 }
2196 }
2197 Ok(())
2198 }
2199
2200 proptest!(|(data in gen_expr_data())| {
2201 check(data)?;
2202 });
2203 }
2204
2205 #[mz_ore::test]
2206 fn test_mfp() {
2207 use MirScalarExpr::*;
2209
2210 let mfp = MapFilterProject {
2211 expressions: vec![],
2212 predicates: vec![
2213 (
2215 1,
2216 CallUnary {
2217 func: UnaryFunc::IsNull(IsNull),
2218 expr: Box::new(CallBinary {
2219 func: MulInt32.into(),
2220 expr1: Box::new(MirScalarExpr::column(0)),
2221 expr2: Box::new(MirScalarExpr::column(0)),
2222 }),
2223 },
2224 ),
2225 (
2227 1,
2228 CallBinary {
2229 func: Eq.into(),
2230 expr1: Box::new(MirScalarExpr::column(0)),
2231 expr2: Box::new(MirScalarExpr::literal_ok(
2232 Datum::Int32(1727694505),
2233 ReprScalarType::Int32,
2234 )),
2235 },
2236 ),
2237 ],
2238 projection: vec![],
2239 input_arity: 1,
2240 };
2241
2242 let relation = ReprRelationType::new(vec![ReprScalarType::Int32.nullable(true)]);
2243 let arena = RowArena::new();
2244 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2245 interpreter.push_column(0, ResultSpec::value(Datum::Int32(-1294725158)));
2246 let spec = interpreter.mfp_filter(&mfp);
2247 assert!(spec.range.may_fail());
2248 }
2249
2250 #[mz_ore::test]
2251 fn test_concat() {
2252 let expr = MirScalarExpr::call_variadic(
2253 Concat,
2254 vec![
2255 MirScalarExpr::column(0),
2256 MirScalarExpr::literal_ok(Datum::String("a"), ReprScalarType::String),
2257 MirScalarExpr::literal_ok(Datum::String("b"), ReprScalarType::String),
2258 ],
2259 );
2260
2261 let relation = ReprRelationType::new(vec![ReprScalarType::String.nullable(false)]);
2262 let arena = RowArena::new();
2263 let interpreter = ColumnSpecs::new(&relation, &arena);
2264 let spec = interpreter.expr(&expr);
2265 assert!(spec.range.may_contain(Datum::String("blab")));
2266 }
2267
2268 #[mz_ore::test]
2269 fn test_eval_range() {
2270 let period_ms = MirScalarExpr::literal_ok(Datum::Int64(10), ReprScalarType::Int64);
2282 let expr = MirScalarExpr::CallBinary {
2283 func: Gte.into(),
2284 expr1: Box::new(MirScalarExpr::CallUnmaterializable(
2285 UnmaterializableFunc::MzNow,
2286 )),
2287 expr2: Box::new(MirScalarExpr::CallUnary {
2288 func: UnaryFunc::CastInt64ToMzTimestamp(CastInt64ToMzTimestamp),
2289 expr: Box::new(MirScalarExpr::CallBinary {
2290 func: MulInt64.into(),
2291 expr1: Box::new(period_ms.clone()),
2292 expr2: Box::new(MirScalarExpr::CallBinary {
2293 func: DivInt64.into(),
2294 expr1: Box::new(MirScalarExpr::column(0)),
2295 expr2: Box::new(period_ms),
2296 }),
2297 }),
2298 }),
2299 };
2300 let relation = ReprRelationType::new(vec![ReprScalarType::Int64.nullable(false)]);
2301
2302 {
2303 let arena = RowArena::new();
2305 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2306 interpreter.push_unmaterializable(
2307 UnmaterializableFunc::MzNow,
2308 ResultSpec::value_between(
2309 Datum::MzTimestamp(10.into()),
2310 Datum::MzTimestamp(20.into()),
2311 ),
2312 );
2313 interpreter.push_column(0, ResultSpec::value_between(30i64.into(), 40i64.into()));
2314
2315 let range_out = interpreter.expr(&expr).range;
2316 assert!(range_out.may_contain(Datum::False));
2317 assert!(!range_out.may_contain(Datum::True));
2318 assert!(!range_out.may_contain(Datum::Null));
2319 assert!(range_out.may_fail());
2320 }
2321
2322 {
2323 let arena = RowArena::new();
2325 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2326 interpreter.push_unmaterializable(
2327 UnmaterializableFunc::MzNow,
2328 ResultSpec::value_between(
2329 Datum::MzTimestamp(10.into()),
2330 Datum::MzTimestamp(35.into()),
2331 ),
2332 );
2333 interpreter.push_column(0, ResultSpec::value_between(30i64.into(), 40i64.into()));
2334
2335 let range_out = interpreter.expr(&expr).range;
2336 assert!(range_out.may_contain(Datum::False));
2337 assert!(range_out.may_contain(Datum::True));
2338 assert!(!range_out.may_contain(Datum::Null));
2339 assert!(range_out.may_fail());
2340 }
2341 }
2342
2343 #[mz_ore::test]
2344 #[cfg_attr(miri, ignore)] fn test_jsonb() {
2346 let arena = RowArena::new();
2347
2348 let expr = MirScalarExpr::column(0)
2349 .call_binary(
2350 MirScalarExpr::literal_ok(Datum::from("ts"), ReprScalarType::String),
2351 JsonbGetString,
2352 )
2353 .call_unary(CastJsonbToNumeric(None));
2354
2355 let relation = ReprRelationType::new(vec![ReprScalarType::Jsonb.nullable(true)]);
2356 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2357 interpreter.push_column(
2358 0,
2359 ResultSpec::map_spec(
2360 [(
2361 "ts".into(),
2362 ResultSpec::value_between(
2363 Datum::Numeric(100.into()),
2364 Datum::Numeric(300.into()),
2365 ),
2366 )]
2367 .into_iter()
2368 .collect(),
2369 ),
2370 );
2371
2372 let range_out = interpreter.expr(&expr).range;
2373 assert!(!range_out.may_contain(Datum::Numeric(0.into())));
2374 assert!(range_out.may_contain(Datum::Numeric(200.into())));
2375 assert!(!range_out.may_contain(Datum::Numeric(400.into())));
2376 }
2377
2378 #[mz_ore::test]
2379 fn test_nested_union_partial_overlap() {
2380 let a = ResultSpec::map_spec(
2385 [
2386 ("x".into(), ResultSpec::value(Datum::String("a"))),
2387 ("y".into(), ResultSpec::value(Datum::String("b"))),
2388 ("c".into(), ResultSpec::value(Datum::String("c"))),
2389 ]
2390 .into_iter()
2391 .collect(),
2392 );
2393 let b = ResultSpec::map_spec(
2394 [
2395 ("x".into(), ResultSpec::value(Datum::String("a2"))),
2396 ("y".into(), ResultSpec::value(Datum::String("b2"))),
2397 ("z".into(), ResultSpec::value(Datum::String("z"))),
2398 ]
2399 .into_iter()
2400 .collect(),
2401 );
2402
2403 let unioned = a.union(b);
2404
2405 let arena = RowArena::new();
2409 let relation = ReprRelationType::new(vec![ReprScalarType::Jsonb.nullable(false)]);
2410
2411 {
2413 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2414 interpreter.push_column(0, unioned.clone());
2415 let expr = MirScalarExpr::column(0).call_binary(
2416 MirScalarExpr::literal_ok(Datum::from("c"), ReprScalarType::String),
2417 JsonbGetStringStringify,
2418 );
2419 assert!(interpreter.expr(&expr).range.may_contain(Datum::Null));
2420 }
2421
2422 {
2424 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2425 interpreter.push_column(0, unioned.clone());
2426 let expr = MirScalarExpr::column(0).call_binary(
2427 MirScalarExpr::literal_ok(Datum::from("z"), ReprScalarType::String),
2428 JsonbGetStringStringify,
2429 );
2430 assert!(interpreter.expr(&expr).range.may_contain(Datum::Null));
2431 }
2432
2433 {
2435 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2436 interpreter.push_column(0, unioned);
2437 let expr = MirScalarExpr::column(0).call_binary(
2438 MirScalarExpr::literal_ok(Datum::from("x"), ReprScalarType::String),
2439 JsonbGetStringStringify,
2440 );
2441 let x_range = interpreter.expr(&expr).range;
2442 assert!(x_range.may_contain(Datum::String("a")));
2443 assert!(x_range.may_contain(Datum::String("a2")));
2444 }
2445 }
2446
2447 #[mz_ore::test]
2448 #[cfg_attr(miri, ignore)] fn test_case_over_jsonb_columns() {
2450 let arena = RowArena::new();
2454
2455 let expr = MirScalarExpr::If {
2457 cond: Box::new(MirScalarExpr::column(0)),
2458 then: Box::new(MirScalarExpr::column(1)),
2459 els: Box::new(MirScalarExpr::column(2)),
2460 }
2461 .call_binary(
2462 MirScalarExpr::literal_ok(Datum::from("y"), ReprScalarType::String),
2463 JsonbGetStringStringify,
2464 )
2465 .call_unary(UnaryFunc::IsNull(IsNull));
2466
2467 let relation = ReprRelationType::new(vec![
2468 ReprScalarType::Bool.nullable(false),
2469 ReprScalarType::Jsonb.nullable(false),
2470 ReprScalarType::Jsonb.nullable(false),
2471 ]);
2472 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2473 interpreter.push_column(0, ResultSpec::value_between(Datum::False, Datum::True));
2474 interpreter.push_column(
2475 1,
2476 ResultSpec::map_spec(
2477 [("x".into(), ResultSpec::value(Datum::String("a")))]
2478 .into_iter()
2479 .collect(),
2480 ),
2481 );
2482 interpreter.push_column(
2483 2,
2484 ResultSpec::map_spec(
2485 [("y".into(), ResultSpec::value(Datum::String("b")))]
2486 .into_iter()
2487 .collect(),
2488 ),
2489 );
2490
2491 let range_out = interpreter.expr(&expr).range;
2492 assert!(range_out.may_contain(Datum::True));
2495 }
2496
2497 #[mz_ore::test]
2498 fn test_like() {
2499 let arena = RowArena::new();
2500
2501 let expr = MirScalarExpr::CallUnary {
2502 func: UnaryFunc::IsLikeMatch(IsLikeMatch(
2503 crate::like_pattern::compile("%whatever%", true).unwrap(),
2504 )),
2505 expr: Box::new(MirScalarExpr::column(0)),
2506 };
2507
2508 let relation = ReprRelationType::new(vec![ReprScalarType::String.nullable(true)]);
2509 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2510 interpreter.push_column(
2511 0,
2512 ResultSpec::value_between(Datum::String("aardvark"), Datum::String("zebra")),
2513 );
2514
2515 let range_out = interpreter.expr(&expr).range;
2516 assert!(
2517 !range_out.fallible,
2518 "like function should not error on non-error input"
2519 );
2520 assert!(range_out.may_contain(Datum::True));
2521 assert!(range_out.may_contain(Datum::False));
2522 assert!(range_out.may_contain(Datum::Null));
2523 }
2524
2525 #[mz_ore::test]
2526 fn test_try_parse_monotonic_iso8601_timestamp() {
2527 use chrono::NaiveDateTime;
2528
2529 let arena = RowArena::new();
2530
2531 let expr = MirScalarExpr::CallUnary {
2532 func: UnaryFunc::TryParseMonotonicIso8601Timestamp(TryParseMonotonicIso8601Timestamp),
2533 expr: Box::new(MirScalarExpr::column(0)),
2534 };
2535
2536 let relation = ReprRelationType::new(vec![ReprScalarType::String.nullable(true)]);
2537 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2539 interpreter.push_column(
2540 0,
2541 ResultSpec::value_between(
2542 Datum::String("2024-01-11T00:00:00.000Z"),
2543 Datum::String("2024-01-11T20:00:00.000Z"),
2544 ),
2545 );
2546
2547 let timestamp = |ts| {
2548 Datum::Timestamp(
2549 NaiveDateTime::parse_from_str(ts, "%Y-%m-%dT%H:%M:%S")
2550 .unwrap()
2551 .try_into()
2552 .unwrap(),
2553 )
2554 };
2555
2556 let range_out = interpreter.expr(&expr).range;
2557 assert!(!range_out.fallible);
2558 assert!(range_out.nullable);
2559 assert!(!range_out.may_contain(timestamp("2024-01-10T10:00:00")));
2560 assert!(range_out.may_contain(timestamp("2024-01-11T10:00:00")));
2561 assert!(!range_out.may_contain(timestamp("2024-01-12T10:00:00")));
2562
2563 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2565 interpreter.push_column(
2566 0,
2567 ResultSpec::value_between(Datum::String("2024-01-1"), Datum::String("2024-01-2")),
2568 );
2569
2570 let range_out = interpreter.expr(&expr).range;
2571 assert!(!range_out.fallible);
2572 assert!(range_out.nullable);
2573 assert!(range_out.may_contain(timestamp("2024-01-10T10:00:00")));
2574 assert!(range_out.may_contain(timestamp("2024-01-11T10:00:00")));
2575 assert!(range_out.may_contain(timestamp("2024-01-12T10:00:00")));
2576
2577 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2579 interpreter.push_column(
2580 0,
2581 ResultSpec::value_between(
2582 Datum::String("2024-01-1"),
2583 Datum::String("2024-01-12T10:00:00"),
2584 )
2585 .union(ResultSpec::null()),
2586 );
2587
2588 let range_out = interpreter.expr(&expr).range;
2589 assert!(!range_out.fallible);
2590 assert!(range_out.nullable);
2591 assert!(range_out.may_contain(timestamp("2024-01-10T10:00:00")));
2592 assert!(range_out.may_contain(timestamp("2024-01-11T10:00:00")));
2593 assert!(range_out.may_contain(timestamp("2024-01-12T10:00:00")));
2594
2595 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2597 interpreter.push_column(
2598 0,
2599 ResultSpec::value_between(
2600 Datum::String("2024-01-11T10:00:00.000Z"),
2601 Datum::String("2024-01-11T10:00:00.000Z"),
2602 ),
2603 );
2604
2605 let range_out = interpreter.expr(&expr).range;
2606 assert!(!range_out.fallible);
2607 assert!(!range_out.nullable);
2608 assert!(!range_out.may_contain(timestamp("2024-01-10T10:00:00")));
2609 assert!(range_out.may_contain(timestamp("2024-01-11T10:00:00")));
2610 assert!(!range_out.may_contain(timestamp("2024-01-12T10:00:00")));
2611 }
2612
2613 #[mz_ore::test]
2614 fn test_inequality() {
2615 let arena = RowArena::new();
2616
2617 let expr = MirScalarExpr::column(0).call_binary(
2618 MirScalarExpr::CallUnmaterializable(UnmaterializableFunc::MzNow),
2619 Gte,
2620 );
2621
2622 let relation = ReprRelationType::new(vec![ReprScalarType::MzTimestamp.nullable(true)]);
2623 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2624 interpreter.push_column(
2625 0,
2626 ResultSpec::value_between(
2627 Datum::MzTimestamp(1704736444949u64.into()),
2628 Datum::MzTimestamp(1704736444949u64.into()),
2629 )
2630 .union(ResultSpec::null()),
2631 );
2632 interpreter.push_unmaterializable(
2633 UnmaterializableFunc::MzNow,
2634 ResultSpec::value_between(
2635 Datum::MzTimestamp(1704738791000u64.into()),
2636 Datum::MzTimestamp(18446744073709551615u64.into()),
2637 ),
2638 );
2639
2640 let range_out = interpreter.expr(&expr).range;
2641 assert!(
2642 !range_out.fallible,
2643 "<= function should not error on non-error input"
2644 );
2645 assert!(!range_out.may_contain(Datum::True));
2646 assert!(range_out.may_contain(Datum::False));
2647 assert!(range_out.may_contain(Datum::Null));
2648 }
2649
2650 #[mz_ore::test]
2658 #[cfg_attr(miri, ignore)]
2659 fn test_add_timestamp_interval_non_monotone() {
2660 use chrono::NaiveDateTime;
2661 use mz_repr::adt::interval::Interval;
2662 use mz_repr::adt::timestamp::CheckedTimestamp;
2663 use mz_repr::{Datum, Row};
2664
2665 let arena = RowArena::new();
2666
2667 let ts_lit = |s: &str| {
2679 let mut row = Row::default();
2680 row.packer().push(Datum::Timestamp(
2681 CheckedTimestamp::from_timestamplike(
2682 NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S").unwrap(),
2683 )
2684 .unwrap(),
2685 ));
2686 MirScalarExpr::Literal(Ok(row), ReprScalarType::Timestamp.nullable(false))
2687 };
2688 let interval = |months: i32, days: i32, micros: i64| {
2689 Datum::Interval(Interval {
2690 months,
2691 days,
2692 micros,
2693 })
2694 };
2695
2696 let expr = ts_lit("2024-01-31T00:00:00")
2698 .call_binary(MirScalarExpr::column(0), AddTimestampInterval)
2699 .call_binary(ts_lit("2024-03-15T00:00:00"), Gte);
2700
2701 let relation = ReprRelationType::new(vec![ReprScalarType::Interval.nullable(false)]);
2702 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2703 interpreter.push_column(
2704 0,
2705 ResultSpec::value_between(interval(0, 31, 0), interval(1, 0, 0)),
2706 );
2707
2708 let range_out = interpreter.expr(&expr).range;
2709 assert!(
2716 range_out.may_contain(Datum::True),
2717 "interpreter incorrectly ruled out matching rows; \
2718 add_timestamp_interval is not monotone in the interval argument",
2719 );
2720 }
2721
2722 #[mz_ore::test]
2729 #[cfg_attr(miri, ignore)]
2730 fn test_timestamp_plus_interval_dynamic_monotone() {
2731 use chrono::NaiveDateTime;
2732 use mz_repr::adt::interval::Interval;
2733 use mz_repr::adt::timestamp::CheckedTimestamp;
2734 use mz_repr::{Datum, Row};
2735
2736 let arena = RowArena::new();
2737
2738 let ts = |s: &str| {
2739 Datum::Timestamp(
2740 CheckedTimestamp::from_timestamplike(
2741 NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S").unwrap(),
2742 )
2743 .unwrap(),
2744 )
2745 };
2746 let interval_lit = |months: i32, days: i32, micros: i64| {
2747 let mut row = Row::default();
2748 row.packer().push(Datum::Interval(Interval {
2749 months,
2750 days,
2751 micros,
2752 }));
2753 MirScalarExpr::Literal(Ok(row), ReprScalarType::Interval.nullable(false))
2754 };
2755
2756 let relation = ReprRelationType::new(vec![ReprScalarType::Timestamp.nullable(false)]);
2757
2758 {
2764 let expr = MirScalarExpr::column(0)
2765 .call_binary(interval_lit(0, 1, 0), SubTimestampInterval)
2766 .call_binary(
2767 MirScalarExpr::Literal(
2768 Ok({
2769 let mut r = Row::default();
2770 r.packer().push(ts("2024-01-15T00:00:00"));
2771 r
2772 }),
2773 ReprScalarType::Timestamp.nullable(false),
2774 ),
2775 Lt,
2776 );
2777 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2778 interpreter.push_column(
2779 0,
2780 ResultSpec::value_between(ts("2024-01-15T00:00:00"), ts("2024-01-20T00:00:00")),
2781 );
2782 let range_out = interpreter.expr(&expr).range;
2783 assert!(
2784 range_out.may_contain(Datum::True),
2785 "day-only interval should preserve tight bounds",
2786 );
2787 assert!(
2788 range_out.may_contain(Datum::False),
2789 "day-only interval should preserve tight bounds",
2790 );
2791 }
2792
2793 {
2798 let expr = MirScalarExpr::column(0)
2799 .call_binary(interval_lit(0, 1, 0), SubTimestampInterval)
2800 .call_binary(
2801 MirScalarExpr::Literal(
2802 Ok({
2803 let mut r = Row::default();
2804 r.packer().push(ts("2024-01-15T00:00:00"));
2805 r
2806 }),
2807 ReprScalarType::Timestamp.nullable(false),
2808 ),
2809 Lt,
2810 );
2811 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2812 interpreter.push_column(
2813 0,
2814 ResultSpec::value_between(ts("2024-01-17T00:00:00"), ts("2024-01-20T00:00:00")),
2815 );
2816 let range_out = interpreter.expr(&expr).range;
2817 assert!(
2818 !range_out.may_contain(Datum::True),
2819 "day-only interval should narrow out impossible matches",
2820 );
2821 }
2822
2823 {
2828 let expr = MirScalarExpr::column(0)
2829 .call_binary(interval_lit(1, 0, 0), SubTimestampInterval)
2830 .call_binary(
2831 MirScalarExpr::Literal(
2832 Ok({
2833 let mut r = Row::default();
2834 r.packer().push(ts("2024-01-15T00:00:00"));
2835 r
2836 }),
2837 ReprScalarType::Timestamp.nullable(false),
2838 ),
2839 Lt,
2840 );
2841 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2842 interpreter.push_column(
2843 0,
2844 ResultSpec::value_between(ts("2024-01-17T00:00:00"), ts("2024-01-20T00:00:00")),
2845 );
2846 let range_out = interpreter.expr(&expr).range;
2847 assert!(
2848 range_out.may_contain(Datum::True),
2849 "month-bearing interval must conservatively admit True",
2850 );
2851 assert!(
2852 range_out.may_contain(Datum::False),
2853 "month-bearing interval must conservatively admit False",
2854 );
2855 }
2856 }
2857
2858 #[mz_ore::test]
2867 #[cfg_attr(miri, ignore)]
2868 fn proptest_timestamp_plus_interval_monotone_when_months_zero() {
2869 use mz_repr::adt::interval::Interval;
2870 use mz_repr::{Datum, RowArena, SqlScalarType, arb_datum_for_scalar};
2871 use proptest::prelude::*;
2872
2873 let timestamp_strat = || arb_datum_for_scalar(SqlScalarType::Timestamp { precision: None });
2874 let zero_month_interval_strat =
2881 (any::<i32>(), any::<i64>()).prop_map(|(days, micros)| Interval {
2882 months: 0,
2883 days,
2884 micros,
2885 });
2886
2887 let expr = MirScalarExpr::CallBinary {
2888 func: AddTimestampInterval.into(),
2889 expr1: Box::new(MirScalarExpr::column(0)),
2890 expr2: Box::new(MirScalarExpr::column(1)),
2891 };
2892 let arena = RowArena::new();
2893
2894 proptest!(|(
2895 t1 in timestamp_strat(),
2896 t2 in timestamp_strat(),
2897 i in zero_month_interval_strat,
2898 )| {
2899 let t1 = match t1 { PropDatum::Timestamp(t) => t, _ => unreachable!() };
2900 let t2 = match t2 { PropDatum::Timestamp(t) => t, _ => unreachable!() };
2901 let i = Datum::Interval(i);
2902 let r1 = expr.eval(&[Datum::Timestamp(t1), i], &arena);
2903 let r2 = expr.eval(&[Datum::Timestamp(t2), i], &arena);
2904 if let (Ok(Datum::Timestamp(r1)), Ok(Datum::Timestamp(r2))) = (r1, r2) {
2907 prop_assert_eq!(t1.cmp(&t2), r1.cmp(&r2));
2908 }
2909 });
2910 }
2911
2912 #[mz_ore::test]
2917 #[cfg_attr(miri, ignore)]
2918 fn test_date_bin_timestamp_non_monotone() {
2919 use chrono::NaiveDateTime;
2920 use mz_repr::adt::interval::Interval;
2921 use mz_repr::adt::timestamp::CheckedTimestamp;
2922 use mz_repr::{Datum, Row};
2923
2924 let arena = RowArena::new();
2925
2926 let ts_lit = |s: &str| {
2927 let mut row = Row::default();
2928 row.packer().push(Datum::Timestamp(
2929 CheckedTimestamp::from_timestamplike(
2930 NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S").unwrap(),
2931 )
2932 .unwrap(),
2933 ));
2934 MirScalarExpr::Literal(Ok(row), ReprScalarType::Timestamp.nullable(false))
2935 };
2936 let interval = |months: i32, days: i32, micros: i64| {
2937 Datum::Interval(Interval {
2938 months,
2939 days,
2940 micros,
2941 })
2942 };
2943
2944 let expr = MirScalarExpr::column(0)
2961 .call_binary(ts_lit("2024-01-01T12:00:00"), DateBinTimestamp)
2962 .call_binary(ts_lit("2024-01-01T06:00:00"), Gt);
2963
2964 let relation = ReprRelationType::new(vec![ReprScalarType::Interval.nullable(false)]);
2965 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2966 interpreter.push_column(
2967 0,
2968 ResultSpec::value_between(interval(0, 1, 0), interval(0, 2, 0)),
2969 );
2970
2971 let range_out = interpreter.expr(&expr).range;
2972 assert!(
2973 range_out.may_contain(Datum::True),
2974 "date_bin is not monotone in the stride argument; \
2975 interior strides can produce outputs outside the endpoint-bounded \
2976 box, so the interpreter must admit True for `>`-style predicates",
2977 );
2978 }
2979
2980 #[mz_ore::test]
2981 fn test_trace() {
2982 use super::Trace;
2983
2984 let expr = MirScalarExpr::column(0).call_binary(
2985 MirScalarExpr::column(1)
2986 .call_binary(MirScalarExpr::column(3).call_unary(NegInt64), AddInt64),
2987 Gte,
2988 );
2989 let summary = Trace.expr(&expr);
2990 assert!(summary.pushdownable());
2991 }
2992}