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};
20#[derive(Clone, Eq, PartialEq, Debug)]
22enum Values<'a> {
23 Empty,
25 Within(Datum<'a>, Datum<'a>),
28 Nested(BTreeMap<Datum<'a>, ResultSpec<'a>>),
32 All,
35}
36
37impl<'a> Values<'a> {
38 fn just(a: Datum<'a>) -> Values<'a> {
39 match a {
40 Datum::Map(datum_map) => Values::Nested(
41 datum_map
42 .iter()
43 .map(|(key, val)| (key.into(), ResultSpec::value(val)))
44 .collect(),
45 ),
46 other => Self::Within(other, other),
47 }
48 }
49
50 fn union(self, other: Values<'a>) -> Values<'a> {
51 match (self, other) {
52 (Values::Empty, r) => r,
53 (r, Values::Empty) => r,
54 (Values::Within(a0, a1), Values::Within(b0, b1)) => {
55 Values::Within(a0.min(b0), a1.max(b1))
56 }
57 (Values::Nested(a), Values::Nested(mut b)) => {
58 let mut merged = BTreeMap::new();
63 for (key, a_spec) in a {
64 if let Some(b_spec) = b.remove(&key) {
65 let unioned = a_spec.union(b_spec);
66 if unioned != ResultSpec::anything() {
67 merged.insert(key, unioned);
68 }
69 }
70 }
71 if merged.is_empty() {
72 Values::All
73 } else {
74 Values::Nested(merged)
75 }
76 }
77 _ => Values::All,
78 }
79 }
80
81 fn intersect(self, other: Values<'a>) -> Values<'a> {
82 match (self, other) {
83 (Values::Empty, _) => Values::Empty,
84 (_, Values::Empty) => Values::Empty,
85 (Values::Within(a0, a1), Values::Within(b0, b1)) => {
86 let min = a0.max(b0);
87 let max = a1.min(b1);
88 if min <= max {
89 Values::Within(min, max)
90 } else {
91 Values::Empty
92 }
93 }
94 (Values::Nested(mut a), Values::Nested(b)) => {
95 for (datum, other_spec) in b {
96 let spec = a.entry(datum).or_insert_with(ResultSpec::anything);
97 *spec = spec.clone().intersect(other_spec);
98 }
99 Values::Nested(a)
100 }
101 (Values::All, v) => v,
102 (v, Values::All) => v,
103 (Values::Nested(_), Values::Within(_, _)) => Values::Empty,
104 (Values::Within(_, _), Values::Nested(_)) => Values::Empty,
105 }
106 }
107
108 fn may_contain(&self, value: Datum<'a>) -> bool {
109 match self {
110 Values::Empty => false,
111 Values::Within(min, max) => *min <= value && value <= *max,
112 Values::All => true,
113 Values::Nested(field_map) => match value {
114 Datum::Map(datum_map) => {
115 datum_map
116 .iter()
117 .all(|(key, val)| match field_map.get(&key.into()) {
118 None => true,
119 Some(nested) => nested.may_contain(val),
120 })
121 }
122 _ => false,
123 },
124 }
125 }
126
127 fn as_single(&self) -> Option<Datum<'a>> {
136 match self {
137 Values::Within(a, b) if a == b => Some(*a),
138 _ => None,
139 }
140 }
141}
142
143#[derive(Debug, Clone, Eq, PartialEq)]
148pub struct ResultSpec<'a> {
149 nullable: bool,
151 fallible: bool,
153 values: Values<'a>,
155}
156
157impl<'a> ResultSpec<'a> {
158 pub fn nothing() -> Self {
160 ResultSpec {
161 nullable: false,
162 fallible: false,
163 values: Values::Empty,
164 }
165 }
166
167 pub fn anything() -> Self {
169 ResultSpec {
170 nullable: true,
171 fallible: true,
172 values: Values::All,
173 }
174 }
175
176 pub fn any_infallible() -> Self {
178 ResultSpec {
179 nullable: true,
180 fallible: false,
181 values: Values::All,
182 }
183 }
184
185 pub fn null() -> Self {
187 ResultSpec {
188 nullable: true,
189 ..Self::nothing()
190 }
191 }
192
193 pub fn fails() -> Self {
195 ResultSpec {
196 fallible: true,
197 ..Self::nothing()
198 }
199 }
200
201 pub fn has_type(col: &ReprColumnType, fallible: bool) -> ResultSpec<'a> {
203 let values = match &col.scalar_type {
204 ReprScalarType::Bool => Values::Within(Datum::False, Datum::True),
205 _ => Values::All,
207 };
208 ResultSpec {
209 nullable: col.nullable,
210 fallible,
211 values,
212 }
213 }
214
215 pub fn value(value: Datum<'a>) -> ResultSpec<'a> {
217 match value {
218 Datum::Null => Self::null(),
219 nonnull => ResultSpec {
220 values: Values::just(nonnull),
221 ..Self::nothing()
222 },
223 }
224 }
225
226 pub fn value_between(min: Datum<'a>, max: Datum<'a>) -> ResultSpec<'a> {
228 assert!(!min.is_null());
229 assert!(!max.is_null());
230 if min <= max {
231 ResultSpec {
232 values: Values::Within(min, max),
233 ..ResultSpec::nothing()
234 }
235 } else {
236 ResultSpec::nothing()
237 }
238 }
239
240 pub fn value_all() -> ResultSpec<'a> {
242 ResultSpec {
243 values: Values::All,
244 ..ResultSpec::nothing()
245 }
246 }
247
248 pub fn map_spec(map: BTreeMap<Datum<'a>, ResultSpec<'a>>) -> ResultSpec<'a> {
250 ResultSpec {
251 values: Values::Nested(map),
252 ..ResultSpec::nothing()
253 }
254 }
255
256 pub fn union(self, other: ResultSpec<'a>) -> ResultSpec<'a> {
258 ResultSpec {
259 nullable: self.nullable || other.nullable,
260 fallible: self.fallible || other.fallible,
261 values: self.values.union(other.values),
262 }
263 }
264
265 pub fn intersect(self, other: ResultSpec<'a>) -> ResultSpec<'a> {
267 ResultSpec {
268 nullable: self.nullable && other.nullable,
269 fallible: self.fallible && other.fallible,
270 values: self.values.intersect(other.values),
271 }
272 }
273
274 pub fn may_contain(&self, value: Datum<'a>) -> bool {
276 if value == Datum::Null {
277 return self.nullable;
278 }
279
280 self.values.may_contain(value)
281 }
282
283 pub fn may_fail(&self) -> bool {
285 self.fallible
286 }
287
288 fn flat_map(
299 &self,
300 is_monotone: bool,
301 mut result_map: impl FnMut(Result<Datum<'a>, EvalError>) -> ResultSpec<'a>,
302 ) -> ResultSpec<'a> {
303 let null_spec = if self.nullable {
304 result_map(Ok(Datum::Null))
305 } else {
306 ResultSpec::nothing()
307 };
308
309 let error_spec = if self.fallible {
310 let map_err = result_map(Err(EvalError::Internal("".into())));
314 let raise_err = ResultSpec::fails();
315 raise_err.union(map_err)
320 } else {
321 ResultSpec::nothing()
322 };
323
324 let values_spec = match self.values {
325 Values::Empty => ResultSpec::nothing(),
326 Values::Within(min, max) if min == max => result_map(Ok(min)),
328 Values::Within(Datum::False, Datum::True) => {
330 result_map(Ok(Datum::False)).union(result_map(Ok(Datum::True)))
331 }
332 Values::Within(min, max) if is_monotone => {
335 let min_result = result_map(Ok(min));
336 let max_result = result_map(Ok(max));
337 match (min_result, max_result) {
338 (
340 ResultSpec {
341 nullable: false,
342 fallible: false,
343 values: a_values,
344 },
345 ResultSpec {
346 nullable: false,
347 fallible: false,
348 values: b_values,
349 },
350 ) => ResultSpec {
351 nullable: false,
352 fallible: false,
353 values: a_values.union(b_values),
354 },
355 (
357 ResultSpec {
358 nullable: true,
359 fallible: false,
360 values: Values::Empty,
361 },
362 ResultSpec {
363 nullable: true,
364 fallible: false,
365 values: Values::Empty,
366 },
367 ) => ResultSpec::null(),
368 _ => ResultSpec::anything(),
370 }
371 }
372 Values::Within(_, _) | Values::Nested(_) | Values::All => ResultSpec::anything(),
374 };
375
376 null_spec.union(error_spec).union(values_spec)
377 }
378}
379
380pub trait Interpreter {
389 type Summary: Clone + Debug + Sized;
390
391 fn column(&self, id: usize) -> Self::Summary;
393
394 fn literal(&self, result: &Result<Row, EvalError>, col_type: &ReprColumnType) -> Self::Summary;
397 fn unmaterializable(&self, func: &UnmaterializableFunc) -> Self::Summary;
402
403 fn unary(&self, func: &UnaryFunc, expr: Self::Summary) -> Self::Summary;
405
406 fn binary(&self, func: &BinaryFunc, left: Self::Summary, right: Self::Summary)
408 -> Self::Summary;
409
410 fn variadic(&self, func: &VariadicFunc, exprs: Vec<Self::Summary>) -> Self::Summary;
412
413 fn cond(&self, cond: Self::Summary, then: Self::Summary, els: Self::Summary) -> Self::Summary;
415
416 fn expr(&self, expr: &MirScalarExpr) -> Self::Summary {
418 match expr {
419 MirScalarExpr::Column(id, _name) => self.column(*id),
420 MirScalarExpr::Literal(value, col_type) => self.literal(value, col_type),
421 MirScalarExpr::CallUnmaterializable(func) => self.unmaterializable(func),
422 MirScalarExpr::CallUnary { func, expr } => {
423 let expr_range = self.expr(expr);
424 self.unary(func, expr_range)
425 }
426 MirScalarExpr::CallBinary { func, expr1, expr2 } => {
427 let expr1_range = self.expr(expr1);
428 let expr2_range = self.expr(expr2);
429 self.binary(func, expr1_range, expr2_range)
430 }
431 MirScalarExpr::CallVariadic { func, exprs } => {
432 let exprs: Vec<_> = exprs.into_iter().map(|e| self.expr(e)).collect();
433 self.variadic(func, exprs)
434 }
435 MirScalarExpr::If { cond, then, els } => {
436 let cond_range = self.expr(cond);
437 let then_range = self.expr(then);
438 let els_range = self.expr(els);
439 self.cond(cond_range, then_range, els_range)
440 }
441 }
442 }
443
444 fn mfp_filter(&self, mfp: &MapFilterProject) -> Self::Summary {
447 let mfp_eval = MfpEval::new(self, mfp.input_arity, &mfp.expressions);
448 let predicates = mfp
450 .predicates
451 .iter()
452 .map(|(_, e)| mfp_eval.expr(e))
453 .collect();
454 mfp_eval.variadic(&And.into(), predicates)
455 }
456
457 fn mfp_plan_filter(&self, plan: &MfpPlan) -> Self::Summary {
460 let mfp_eval = MfpEval::new(self, plan.mfp.input_arity, &plan.mfp.expressions);
461 let mut results: Vec<_> = plan
463 .mfp
464 .predicates
465 .iter()
466 .map(|(_, e)| mfp_eval.expr(e))
467 .collect();
468 let mz_now = mfp_eval.unmaterializable(&UnmaterializableFunc::MzNow);
469 for bound in &plan.lower_bounds {
470 let bound_range = mfp_eval.expr(bound);
471 let result = mfp_eval.binary(&BinaryFunc::Lte(func::Lte), bound_range, mz_now.clone());
472 results.push(result);
473 }
474 for bound in &plan.upper_bounds {
475 let bound_range = mfp_eval.expr(bound);
476 let result = mfp_eval.binary(&BinaryFunc::Gte(func::Gte), bound_range, mz_now.clone());
477 results.push(result);
478 }
479 self.variadic(&And.into(), results)
480 }
481}
482
483pub(crate) struct MfpEval<'a, E: Interpreter + ?Sized> {
486 evaluator: &'a E,
487 input_arity: usize,
488 expressions: Vec<E::Summary>,
489}
490
491impl<'a, E: Interpreter + ?Sized> MfpEval<'a, E> {
492 pub(crate) fn new(evaluator: &'a E, input_arity: usize, expressions: &[MirScalarExpr]) -> Self {
493 let mut mfp_eval = MfpEval {
494 evaluator,
495 input_arity,
496 expressions: vec![],
497 };
498 for expr in expressions {
499 let result = mfp_eval.expr(expr);
500 mfp_eval.expressions.push(result);
501 }
502 mfp_eval
503 }
504}
505
506impl<'a, E: Interpreter + ?Sized> Interpreter for MfpEval<'a, E> {
507 type Summary = E::Summary;
508
509 fn column(&self, id: usize) -> Self::Summary {
510 if id < self.input_arity {
511 self.evaluator.column(id)
512 } else {
513 self.expressions[id - self.input_arity].clone()
514 }
515 }
516
517 fn literal(&self, result: &Result<Row, EvalError>, col_type: &ReprColumnType) -> Self::Summary {
518 self.evaluator.literal(result, col_type)
519 }
520
521 fn unmaterializable(&self, func: &UnmaterializableFunc) -> Self::Summary {
522 self.evaluator.unmaterializable(func)
523 }
524
525 fn unary(&self, func: &UnaryFunc, expr: Self::Summary) -> Self::Summary {
526 self.evaluator.unary(func, expr)
527 }
528
529 fn binary(
530 &self,
531 func: &BinaryFunc,
532 left: Self::Summary,
533 right: Self::Summary,
534 ) -> Self::Summary {
535 self.evaluator.binary(func, left, right)
536 }
537
538 fn variadic(&self, func: &VariadicFunc, exprs: Vec<Self::Summary>) -> Self::Summary {
539 self.evaluator.variadic(func, exprs)
540 }
541
542 fn cond(&self, cond: Self::Summary, then: Self::Summary, els: Self::Summary) -> Self::Summary {
543 self.evaluator.cond(cond, then, els)
544 }
545}
546
547struct SpecialUnary {
552 map_fn: for<'a, 'b> fn(&'b ColumnSpecs<'a>, ResultSpec<'a>) -> ResultSpec<'a>,
553 pushdownable: bool,
554}
555
556impl SpecialUnary {
557 fn for_func(func: &UnaryFunc) -> Option<SpecialUnary> {
559 fn eagerly<'b>(
563 spec: ResultSpec<'b>,
564 value_fn: impl FnOnce(Values<'b>) -> ResultSpec<'b>,
565 ) -> ResultSpec<'b> {
566 let result = match spec.values {
567 Values::Empty => ResultSpec::nothing(),
568 other => value_fn(other),
569 };
570 ResultSpec {
571 fallible: spec.fallible || result.fallible,
572 nullable: spec.nullable || result.nullable,
573 values: result.values,
574 }
575 }
576 match func {
577 UnaryFunc::TryParseMonotonicIso8601Timestamp(_) => Some(SpecialUnary {
578 map_fn: |specs, range| {
579 let expr = MirScalarExpr::CallUnary {
580 func: UnaryFunc::TryParseMonotonicIso8601Timestamp(
581 crate::func::TryParseMonotonicIso8601Timestamp,
582 ),
583 expr: Box::new(MirScalarExpr::column(0)),
584 };
585 let eval = |d| specs.eval_result(expr.eval(&[d], specs.arena));
586
587 eagerly(range, |values| {
588 match values {
589 Values::Within(a, b) if a == b => eval(a),
590 Values::Within(a, b) => {
591 let spec = eval(a).union(eval(b));
592 let values_spec = if spec.nullable {
593 ResultSpec::value_all()
599 } else {
600 spec
601 };
602 values_spec.union(ResultSpec::null())
606 }
607 _ => ResultSpec::any_infallible(),
610 }
611 })
612 },
613 pushdownable: true,
614 }),
615 _ => None,
616 }
617 }
618}
619
620struct AbstractFunc {
631 handler: AbstractFuncHandler,
632 pushdownable: (bool, bool),
638}
639
640enum AbstractFuncHandler {
642 Override(for<'a> fn(ResultSpec<'a>, ResultSpec<'a>) -> ResultSpec<'a>),
645 DynamicMonotone(fn(&ResultSpec<'_>, &ResultSpec<'_>) -> (bool, bool)),
652}
653
654impl AbstractFunc {
655 fn for_func(func: &BinaryFunc) -> Option<AbstractFunc> {
657 fn eagerly<'b>(
661 left: ResultSpec<'b>,
662 right: ResultSpec<'b>,
663 value_fn: impl FnOnce(Values<'b>, Values<'b>) -> ResultSpec<'b>,
664 ) -> ResultSpec<'b> {
665 let result = match (left.values, right.values) {
666 (Values::Empty, _) | (_, Values::Empty) => ResultSpec::nothing(),
667 (l, r) => value_fn(l, r),
668 };
669 ResultSpec {
670 fallible: left.fallible || right.fallible || result.fallible,
671 nullable: left.nullable || right.nullable || result.nullable,
672 values: result.values,
673 }
674 }
675
676 fn jsonb_get_string<'b>(
677 left: ResultSpec<'b>,
678 right: ResultSpec<'b>,
679 stringify: bool,
680 ) -> ResultSpec<'b> {
681 eagerly(left, right, |left, right| {
682 let nested_spec = match (left, right) {
683 (Values::Nested(mut map_spec), Values::Within(key, key2)) if key == key2 => {
684 map_spec.remove(&key)
685 }
686 _ => None,
687 };
688
689 if let Some(field_spec) = nested_spec {
690 if stringify {
691 let values = match field_spec.values {
694 Values::Empty => Values::Empty,
695 Values::Within(min @ Datum::String(_), max @ Datum::String(_)) => {
696 Values::Within(min, max)
697 }
698 Values::Within(_, _) | Values::Nested(_) | Values::All => Values::All,
699 };
700 ResultSpec {
701 values,
702 ..field_spec
703 }
704 } else {
705 field_spec
706 }
707 } else {
708 ResultSpec::any_infallible()
713 }
714 })
715 }
716
717 fn eq<'b>(left: ResultSpec<'b>, right: ResultSpec<'b>) -> ResultSpec<'b> {
718 eagerly(left, right, |left, right| {
719 let maybe_true = match left.clone().intersect(right.clone()) {
721 Values::Empty => ResultSpec::nothing(),
722 _ => ResultSpec::value(Datum::True),
723 };
724
725 let maybe_false = match left.union(right) {
728 Values::Within(a, b) if a == b => ResultSpec::nothing(),
729 _ => ResultSpec::value(Datum::False),
730 };
731
732 maybe_true.union(maybe_false)
733 })
734 }
735
736 fn timestamp_plus_interval_monotone(
745 _left: &ResultSpec<'_>,
746 right: &ResultSpec<'_>,
747 ) -> (bool, bool) {
748 let months_zero = matches!(
749 right.values.as_single(),
750 Some(Datum::Interval(i)) if i.months == 0,
751 );
752 (months_zero, months_zero)
753 }
754
755 match func {
756 BinaryFunc::JsonbGetString(_) => Some(AbstractFunc {
757 handler: AbstractFuncHandler::Override(|l, r| jsonb_get_string(l, r, false)),
758 pushdownable: (true, false),
759 }),
760 BinaryFunc::JsonbGetStringStringify(_) => Some(AbstractFunc {
761 handler: AbstractFuncHandler::Override(|l, r| jsonb_get_string(l, r, true)),
762 pushdownable: (true, false),
763 }),
764 BinaryFunc::Eq(_) => Some(AbstractFunc {
765 handler: AbstractFuncHandler::Override(eq),
766 pushdownable: (true, true),
767 }),
768 BinaryFunc::AddTimestampInterval(_)
769 | BinaryFunc::AddTimestampTzInterval(_)
770 | BinaryFunc::SubTimestampInterval(_)
771 | BinaryFunc::SubTimestampTzInterval(_) => Some(AbstractFunc {
772 handler: AbstractFuncHandler::DynamicMonotone(timestamp_plus_interval_monotone),
773 pushdownable: (true, false),
780 }),
781 _ => None,
782 }
783 }
784}
785
786#[derive(Clone, Debug)]
787pub struct ColumnSpec<'a> {
788 pub col_type: ReprColumnType,
789 pub range: ResultSpec<'a>,
790}
791
792#[derive(Clone, Debug)]
798pub struct ColumnSpecs<'a> {
799 pub relation: &'a ReprRelationType,
800 pub columns: Vec<ResultSpec<'a>>,
801 pub unmaterializables: BTreeMap<UnmaterializableFunc, ResultSpec<'a>>,
802 pub arena: &'a RowArena,
803}
804
805impl<'a> ColumnSpecs<'a> {
806 const MAX_EVAL_ARGS: usize = 6;
813
814 pub fn new(relation: &'a ReprRelationType, arena: &'a RowArena) -> Self {
817 let columns = relation
818 .column_types
819 .iter()
820 .map(|ct| ResultSpec::has_type(ct, false))
821 .collect();
822 ColumnSpecs {
823 relation,
824 columns,
825 unmaterializables: Default::default(),
826 arena,
827 }
828 }
829
830 pub fn push_column(&mut self, id: usize, update: ResultSpec<'a>) {
833 let range = self.columns.get_mut(id).expect("valid column id");
834 *range = range.clone().intersect(update);
835 }
836
837 pub fn push_unmaterializable(&mut self, func: UnmaterializableFunc, update: ResultSpec<'a>) {
840 let range = self
841 .unmaterializables
842 .entry(func.clone())
843 .or_insert_with(|| ResultSpec::has_type(&func.output_type(), true));
844 *range = range.clone().intersect(update);
845 }
846
847 fn eval_result<'b, E>(&self, result: Result<Datum<'b>, E>) -> ResultSpec<'a> {
848 match result {
849 Ok(Datum::Null) => ResultSpec {
850 nullable: true,
851 ..ResultSpec::nothing()
852 },
853 Ok(d) => ResultSpec {
854 values: Values::just(self.arena.make_datum(|packer| packer.push(d))),
855 ..ResultSpec::nothing()
856 },
857 Err(_) => ResultSpec {
858 fallible: true,
859 ..ResultSpec::nothing()
860 },
861 }
862 }
863
864 fn set_literal(expr: &mut MirScalarExpr, update: Result<Datum, EvalError>) {
865 match expr {
866 MirScalarExpr::Literal(literal, col_type) => match update {
867 Err(error) => *literal = Err(error),
868 Ok(datum) => {
869 assert!(
870 datum.is_instance_of(col_type),
871 "{datum:?} must be an instance of {col_type:?}"
872 );
873 match literal {
874 Ok(row) => row.packer().push(datum),
876 literal => *literal = Ok(Row::pack_slice(&[datum])),
877 }
878 }
879 },
880 _ => panic!("not a literal"),
881 }
882 }
883
884 fn set_argument(expr: &mut MirScalarExpr, arg: usize, value: Result<Datum, EvalError>) {
885 match (expr, arg) {
886 (MirScalarExpr::CallUnary { expr, .. }, 0) => Self::set_literal(expr, value),
887 (MirScalarExpr::CallBinary { expr1, .. }, 0) => Self::set_literal(expr1, value),
888 (MirScalarExpr::CallBinary { expr2, .. }, 1) => Self::set_literal(expr2, value),
889 (MirScalarExpr::CallVariadic { exprs, .. }, n) if n < exprs.len() => {
890 Self::set_literal(&mut exprs[n], value)
891 }
892 _ => panic!("illegal argument for expression"),
893 }
894 }
895
896 fn placeholder(col_type: ReprColumnType) -> MirScalarExpr {
900 MirScalarExpr::Literal(Err(EvalError::Internal("".into())), col_type)
901 }
902}
903
904impl<'a> Interpreter for ColumnSpecs<'a> {
905 type Summary = ColumnSpec<'a>;
906
907 fn column(&self, id: usize) -> Self::Summary {
908 let col_type = self.relation.column_types[id].clone();
909 let range = self.columns[id].clone();
910 ColumnSpec { col_type, range }
911 }
912
913 fn literal(&self, result: &Result<Row, EvalError>, col_type: &ReprColumnType) -> Self::Summary {
914 let col_type = col_type.clone();
915 let range = self.eval_result(result.as_ref().map(|row| {
916 self.arena
917 .make_datum(|packer| packer.push(row.unpack_first()))
918 }));
919 ColumnSpec { col_type, range }
920 }
921
922 fn unmaterializable(&self, func: &UnmaterializableFunc) -> Self::Summary {
923 let col_type = func.output_type();
924 let range = self
925 .unmaterializables
926 .get(func)
927 .cloned()
928 .unwrap_or_else(|| ResultSpec::has_type(&func.output_type(), true));
929 ColumnSpec { col_type, range }
930 }
931
932 fn unary(&self, func: &UnaryFunc, summary: Self::Summary) -> Self::Summary {
933 let fallible = func.could_error() || summary.range.fallible;
934 let mapped_spec = if let Some(special) = SpecialUnary::for_func(func) {
935 (special.map_fn)(self, summary.range)
936 } else {
937 let is_monotone = func.is_monotone();
938 let mut expr = MirScalarExpr::CallUnary {
939 func: func.clone(),
940 expr: Box::new(Self::placeholder(summary.col_type.clone())),
941 };
942 summary.range.flat_map(is_monotone, |datum| {
943 Self::set_argument(&mut expr, 0, datum);
944 self.eval_result(expr.eval(&[], self.arena))
945 })
946 };
947
948 let col_type = func.output_type(summary.col_type);
949
950 let range = mapped_spec.intersect(ResultSpec::has_type(&col_type, fallible));
951 ColumnSpec { col_type, range }
952 }
953
954 fn binary(
955 &self,
956 func: &BinaryFunc,
957 left: Self::Summary,
958 right: Self::Summary,
959 ) -> Self::Summary {
960 let fallible = func.could_error() || left.range.fallible || right.range.fallible;
961
962 let special = AbstractFunc::for_func(func);
963 let (left_monotonic, right_monotonic) = match &special {
964 Some(AbstractFunc {
965 handler: AbstractFuncHandler::DynamicMonotone(monotone_fn),
966 ..
967 }) => monotone_fn(&left.range, &right.range),
968 _ => func.is_monotone(),
969 };
970
971 let mapped_spec = match special {
972 Some(AbstractFunc {
973 handler: AbstractFuncHandler::Override(f),
974 ..
975 }) => f(left.range, right.range),
976 _ => {
977 let mut expr = MirScalarExpr::CallBinary {
978 func: func.clone(),
979 expr1: Box::new(Self::placeholder(left.col_type.clone())),
980 expr2: Box::new(Self::placeholder(right.col_type.clone())),
981 };
982 left.range.flat_map(left_monotonic, |left_result| {
983 Self::set_argument(&mut expr, 0, left_result);
984 right.range.flat_map(right_monotonic, |right_result| {
985 Self::set_argument(&mut expr, 1, right_result);
986 self.eval_result(expr.eval(&[], self.arena))
987 })
988 })
989 }
990 };
991
992 let col_type = func.output_type(&[left.col_type, right.col_type]);
993
994 let range = mapped_spec.intersect(ResultSpec::has_type(&col_type, fallible));
995 ColumnSpec { col_type, range }
996 }
997
998 fn variadic(&self, func: &VariadicFunc, args: Vec<Self::Summary>) -> Self::Summary {
999 let fallible = func.could_error() || args.iter().any(|s| s.range.fallible);
1000 if func.is_associative() && args.len() > 2 {
1001 return args
1004 .into_iter()
1005 .reduce(|a, b| self.variadic(func, vec![a, b]))
1006 .expect("reducing over a non-empty argument list");
1007 }
1008
1009 let mapped_spec = if args.len() >= Self::MAX_EVAL_ARGS {
1010 ResultSpec::anything()
1011 } else {
1012 fn eval_loop<'a>(
1013 is_monotonic: bool,
1014 expr: &mut MirScalarExpr,
1015 args: &[ColumnSpec<'a>],
1016 index: usize,
1017 datum_map: &mut impl FnMut(&MirScalarExpr) -> ResultSpec<'a>,
1018 ) -> ResultSpec<'a> {
1019 if index >= args.len() {
1020 datum_map(expr)
1021 } else {
1022 args[index].range.flat_map(is_monotonic, |datum| {
1023 ColumnSpecs::set_argument(expr, index, datum);
1024 eval_loop(is_monotonic, expr, args, index + 1, datum_map)
1025 })
1026 }
1027 }
1028
1029 let mut fn_expr = MirScalarExpr::CallVariadic {
1030 func: func.clone(),
1031 exprs: args
1032 .iter()
1033 .map(|spec| Self::placeholder(spec.col_type.clone()))
1034 .collect(),
1035 };
1036 eval_loop(func.is_monotone(), &mut fn_expr, &args, 0, &mut |expr| {
1037 self.eval_result(expr.eval(&[], self.arena))
1038 })
1039 };
1040
1041 let col_types = args.into_iter().map(|spec| spec.col_type).collect();
1042 let col_type = func.output_type(col_types);
1043
1044 let range = mapped_spec.intersect(ResultSpec::has_type(&col_type, fallible));
1045
1046 ColumnSpec { col_type, range }
1047 }
1048
1049 fn cond(&self, cond: Self::Summary, then: Self::Summary, els: Self::Summary) -> Self::Summary {
1050 let col_type = then
1051 .col_type
1052 .union(&els.col_type)
1053 .expect("failed type union for cond during abstract interpretation");
1054
1055 let range = cond
1056 .range
1057 .flat_map(true, |datum| match datum {
1058 Ok(Datum::True) => then.range.clone(),
1059 Ok(Datum::False) => els.range.clone(),
1060 _ => ResultSpec::fails(),
1061 })
1062 .intersect(ResultSpec::has_type(&col_type, true));
1063
1064 ColumnSpec { col_type, range }
1065 }
1066
1067 fn mfp_filter(&self, mfp: &MapFilterProject) -> Self::Summary {
1081 let mfp_eval = MfpEval::new(self, mfp.input_arity, &mfp.expressions);
1082 let predicates = mfp
1083 .predicates
1084 .iter()
1085 .map(|(_, e)| mfp_eval.expr(e))
1086 .collect();
1087 let mut result = self.variadic(&And.into(), predicates);
1088 if mfp_eval.expressions.iter().any(|s| s.range.fallible) {
1089 result.range.fallible = true;
1090 }
1091 result
1092 }
1093
1094 fn mfp_plan_filter(&self, plan: &MfpPlan) -> Self::Summary {
1095 let mfp_eval = MfpEval::new(self, plan.mfp.input_arity, &plan.mfp.expressions);
1096 let mut results: Vec<_> = plan
1097 .mfp
1098 .predicates
1099 .iter()
1100 .map(|(_, e)| mfp_eval.expr(e))
1101 .collect();
1102 let mz_now = mfp_eval.unmaterializable(&UnmaterializableFunc::MzNow);
1103 for bound in &plan.lower_bounds {
1104 let bound_range = mfp_eval.expr(bound);
1105 let result = mfp_eval.binary(&BinaryFunc::Lte(func::Lte), bound_range, mz_now.clone());
1106 results.push(result);
1107 }
1108 for bound in &plan.upper_bounds {
1109 let bound_range = mfp_eval.expr(bound);
1110 let result = mfp_eval.binary(&BinaryFunc::Gte(func::Gte), bound_range, mz_now.clone());
1111 results.push(result);
1112 }
1113 let mut result = self.variadic(&And.into(), results);
1114 if mfp_eval.expressions.iter().any(|s| s.range.fallible) {
1115 result.range.fallible = true;
1116 }
1117 result
1118 }
1119}
1120
1121#[derive(Debug)]
1130pub struct Trace;
1131
1132#[derive(Copy, Clone, Debug, PartialOrd, PartialEq, Ord, Eq)]
1139pub enum TraceSummary {
1140 Constant,
1143 Dynamic,
1148 Unknown,
1151}
1152
1153impl TraceSummary {
1154 fn apply_fn(self, pushdownable: bool) -> Self {
1159 match self {
1160 TraceSummary::Constant => TraceSummary::Constant,
1161 TraceSummary::Dynamic => match pushdownable {
1162 true => TraceSummary::Dynamic,
1163 false => TraceSummary::Unknown,
1164 },
1165 TraceSummary::Unknown => TraceSummary::Unknown,
1166 }
1167 }
1168
1169 pub fn pushdownable(self) -> bool {
1171 match self {
1172 TraceSummary::Constant | TraceSummary::Dynamic => true,
1173 TraceSummary::Unknown => false,
1174 }
1175 }
1176}
1177
1178impl Interpreter for Trace {
1179 type Summary = TraceSummary;
1180
1181 fn column(&self, _id: usize) -> Self::Summary {
1182 TraceSummary::Dynamic
1183 }
1184
1185 fn literal(
1186 &self,
1187 _result: &Result<Row, EvalError>,
1188 _col_type: &ReprColumnType,
1189 ) -> Self::Summary {
1190 TraceSummary::Constant
1191 }
1192
1193 fn unmaterializable(&self, _func: &UnmaterializableFunc) -> Self::Summary {
1194 TraceSummary::Dynamic
1195 }
1196
1197 fn unary(&self, func: &UnaryFunc, expr: Self::Summary) -> Self::Summary {
1198 let pushdownable = match SpecialUnary::for_func(func) {
1199 None => func.is_monotone(),
1200 Some(special) => special.pushdownable,
1201 };
1202 expr.apply_fn(pushdownable)
1203 }
1204
1205 fn binary(
1206 &self,
1207 func: &BinaryFunc,
1208 left: Self::Summary,
1209 right: Self::Summary,
1210 ) -> Self::Summary {
1211 let (left_pushdownable, right_pushdownable) = match AbstractFunc::for_func(func) {
1212 None => func.is_monotone(),
1213 Some(special) => special.pushdownable,
1214 };
1215 left.apply_fn(left_pushdownable)
1216 .max(right.apply_fn(right_pushdownable))
1217 }
1218
1219 fn variadic(&self, func: &VariadicFunc, exprs: Vec<Self::Summary>) -> Self::Summary {
1220 if !func.is_associative() && exprs.len() >= ColumnSpecs::MAX_EVAL_ARGS {
1221 return TraceSummary::Unknown;
1224 }
1225
1226 let pushdownable_fn = func.is_monotone();
1227 exprs
1228 .into_iter()
1229 .map(|pushdownable_arg| pushdownable_arg.apply_fn(pushdownable_fn))
1230 .max()
1231 .unwrap_or(TraceSummary::Constant)
1232 }
1233
1234 fn cond(&self, cond: Self::Summary, then: Self::Summary, els: Self::Summary) -> Self::Summary {
1235 let cond = cond.min(TraceSummary::Dynamic);
1238 cond.max(then).max(els)
1239 }
1240}
1241
1242#[cfg(test)]
1243mod tests {
1244 use itertools::Itertools;
1245 use mz_repr::adt::datetime::DateTimeUnits;
1246 use mz_repr::{Datum, PropDatum, RowArena, SqlScalarType};
1247 use proptest::prelude::*;
1248 use proptest::sample::{Index, select};
1249
1250 use crate::func::*;
1251 use crate::scalar::func::variadic::Concat;
1252 use crate::{BinaryFunc, MirScalarExpr, UnaryFunc};
1253
1254 use super::*;
1255
1256 #[derive(Debug)]
1257 struct ExpressionData {
1258 relation_type: ReprRelationType,
1259 specs: Vec<ResultSpec<'static>>,
1260 rows: Vec<Row>,
1261 expr: MirScalarExpr,
1262 }
1263
1264 const NUM_TYPE: ReprScalarType = ReprScalarType::Numeric;
1269 static SCALAR_TYPES: &[ReprScalarType] = &[
1270 ReprScalarType::Bool,
1271 ReprScalarType::Jsonb,
1272 NUM_TYPE,
1273 ReprScalarType::Date,
1274 ReprScalarType::Timestamp,
1275 ReprScalarType::MzTimestamp,
1276 ReprScalarType::String,
1277 ];
1278
1279 const INTERESTING_UNARY_FUNCS: &[UnaryFunc] = {
1280 &[
1281 UnaryFunc::CastNumericToMzTimestamp(CastNumericToMzTimestamp),
1282 UnaryFunc::NegNumeric(NegNumeric),
1283 UnaryFunc::CastJsonbToNumeric(CastJsonbToNumeric(None)),
1284 UnaryFunc::CastJsonbToBool(CastJsonbToBool),
1285 UnaryFunc::CastJsonbToString(CastJsonbToString),
1286 UnaryFunc::DateTruncTimestamp(DateTruncTimestamp(DateTimeUnits::Epoch)),
1287 UnaryFunc::ExtractTimestamp(ExtractTimestamp(DateTimeUnits::Epoch)),
1288 UnaryFunc::ExtractDate(ExtractDate(DateTimeUnits::Epoch)),
1289 UnaryFunc::Not(Not),
1290 UnaryFunc::IsNull(IsNull),
1291 UnaryFunc::IsFalse(IsFalse),
1292 UnaryFunc::TryParseMonotonicIso8601Timestamp(TryParseMonotonicIso8601Timestamp),
1293 ]
1294 };
1295
1296 fn unary_typecheck(func: &UnaryFunc, arg: &ReprColumnType) -> bool {
1297 use UnaryFunc::*;
1298 match func {
1299 CastNumericToMzTimestamp(_) | NegNumeric(_) => arg.scalar_type == NUM_TYPE,
1300 CastJsonbToNumeric(_) | CastJsonbToBool(_) | CastJsonbToString(_) => {
1301 arg.scalar_type == ReprScalarType::Jsonb
1302 }
1303 ExtractTimestamp(_) | DateTruncTimestamp(_) => {
1304 arg.scalar_type == ReprScalarType::Timestamp
1305 }
1306 ExtractDate(_) => arg.scalar_type == ReprScalarType::Date,
1307 Not(_) => arg.scalar_type == ReprScalarType::Bool,
1308 IsNull(_) => true,
1309 TryParseMonotonicIso8601Timestamp(_) => arg.scalar_type == ReprScalarType::String,
1310 _ => false,
1311 }
1312 }
1313
1314 fn interesting_binary_funcs() -> Vec<BinaryFunc> {
1315 vec![
1316 AddTimestampInterval.into(),
1317 AddNumeric.into(),
1318 SubNumeric.into(),
1319 MulNumeric.into(),
1320 DivNumeric.into(),
1321 Eq.into(),
1322 Lt.into(),
1323 Gt.into(),
1324 Lte.into(),
1325 Gte.into(),
1326 DateTruncUnitsTimestamp.into(),
1327 JsonbGetString.into(),
1328 JsonbGetStringStringify.into(),
1329 ]
1330 }
1331
1332 fn binary_typecheck(func: &BinaryFunc, arg0: &ReprColumnType, arg1: &ReprColumnType) -> bool {
1333 use BinaryFunc::*;
1334 match func {
1335 AddTimestampInterval(_) => {
1336 arg0.scalar_type == ReprScalarType::Timestamp
1337 && arg1.scalar_type == ReprScalarType::Interval
1338 }
1339 AddNumeric(_) | SubNumeric(_) | MulNumeric(_) | DivNumeric(_) => {
1340 arg0.scalar_type == NUM_TYPE && arg1.scalar_type == NUM_TYPE
1341 }
1342 Eq(_) | Lt(_) | Gt(_) | Lte(_) | Gte(_) => arg0.scalar_type == arg1.scalar_type,
1343 DateTruncTimestamp(_) => {
1344 arg0.scalar_type == ReprScalarType::String
1345 && arg1.scalar_type == ReprScalarType::Timestamp
1346 }
1347 JsonbGetString(_) | JsonbGetStringStringify(_) => {
1348 arg0.scalar_type == ReprScalarType::Jsonb
1349 && arg1.scalar_type == ReprScalarType::String
1350 }
1351 _ => false,
1352 }
1353 }
1354
1355 const INTERESTING_VARIADIC_FUNCS: &[VariadicFunc] = {
1356 use crate::scalar::func::variadic as v;
1357 use VariadicFunc::*;
1358 &[
1359 Coalesce(v::Coalesce),
1360 Greatest(v::Greatest),
1361 Least(v::Least),
1362 And(v::And),
1363 Or(v::Or),
1364 Concat(v::Concat),
1365 ConcatWs(v::ConcatWs),
1366 ]
1367 };
1368
1369 fn variadic_typecheck(func: &VariadicFunc, args: &[ReprColumnType]) -> bool {
1370 use VariadicFunc::*;
1371 fn all_eq<'a>(
1372 iter: impl IntoIterator<Item = &'a ReprColumnType>,
1373 other: &ReprScalarType,
1374 ) -> bool {
1375 iter.into_iter().all(|t| t.scalar_type == *other)
1376 }
1377 match func {
1378 Coalesce(_) | Greatest(_) | Least(_) => match args {
1379 [] => true,
1380 [first, rest @ ..] => all_eq(rest, &first.scalar_type),
1381 },
1382 And(_) | Or(_) => all_eq(args, &ReprScalarType::Bool),
1383 Concat(_) => all_eq(args, &ReprScalarType::String),
1384 ConcatWs(_) => args.len() > 1 && all_eq(args, &ReprScalarType::String),
1385 _ => false,
1386 }
1387 }
1388
1389 fn gen_datums_for_type(typ: &ReprColumnType) -> BoxedStrategy<Datum<'static>> {
1390 let mut values: Vec<Datum<'static>> = SqlScalarType::from_repr(&typ.scalar_type)
1391 .interesting_datums()
1392 .collect();
1393 if typ.nullable {
1394 values.push(Datum::Null)
1395 }
1396 select(values).boxed()
1397 }
1398
1399 fn gen_column() -> impl Strategy<Value = (ReprColumnType, Datum<'static>, ResultSpec<'static>)>
1400 {
1401 let col_type = (select(SCALAR_TYPES), any::<bool>())
1402 .prop_map(|(t, b)| t.nullable(b))
1403 .prop_filter("need at least one value", |c| {
1404 SqlScalarType::from_repr(&c.scalar_type)
1405 .interesting_datums()
1406 .count()
1407 > 0
1408 });
1409
1410 let result_spec = select(vec![
1411 ResultSpec::nothing(),
1412 ResultSpec::null(),
1413 ResultSpec::anything(),
1414 ResultSpec::value_all(),
1415 ]);
1416
1417 (col_type, result_spec).prop_flat_map(|(col, result_spec)| {
1418 gen_datums_for_type(&col).prop_map(move |datum| {
1419 let result_spec = result_spec.clone().union(ResultSpec::value(datum));
1420 (col.clone(), datum, result_spec)
1421 })
1422 })
1423 }
1424
1425 fn gen_expr_for_relation(
1426 relation: &ReprRelationType,
1427 ) -> BoxedStrategy<(MirScalarExpr, ReprColumnType)> {
1428 let column_gen = {
1429 let column_types = relation.column_types.clone();
1430 any::<Index>()
1431 .prop_map(move |idx| {
1432 let id = idx.index(column_types.len());
1433 (MirScalarExpr::column(id), column_types[id].clone())
1434 })
1435 .boxed()
1436 };
1437
1438 let literal_gen = (select(SCALAR_TYPES), any::<bool>())
1439 .prop_map(|(s, b)| s.nullable(b))
1440 .prop_flat_map(|ct| {
1441 let error_gen = any::<EvalError>().prop_map(Err).boxed();
1442 let value_gen = gen_datums_for_type(&ct)
1443 .prop_map(move |datum| Ok(Row::pack_slice(&[datum])))
1444 .boxed();
1445 error_gen.prop_union(value_gen).prop_map(move |result| {
1446 (MirScalarExpr::Literal(result, ct.clone()), ct.clone())
1447 })
1448 })
1449 .boxed();
1450
1451 column_gen
1452 .prop_union(literal_gen)
1453 .prop_recursive(4, 64, 8, |self_gen| {
1454 let unary_gen = (select(INTERESTING_UNARY_FUNCS), self_gen.clone())
1455 .prop_filter_map("unary func", |(func, (expr_in, type_in))| {
1456 if !unary_typecheck(&func, &type_in) {
1457 return None;
1458 }
1459 let type_out = func.output_type(type_in);
1460 let expr_out = MirScalarExpr::CallUnary {
1461 func,
1462 expr: Box::new(expr_in),
1463 };
1464 Some((expr_out, type_out))
1465 })
1466 .boxed();
1467 let binary_gen = (
1468 select(interesting_binary_funcs()),
1469 self_gen.clone(),
1470 self_gen.clone(),
1471 )
1472 .prop_filter_map(
1473 "binary func",
1474 |(func, (expr_left, type_left), (expr_right, type_right))| {
1475 if !binary_typecheck(&func, &type_left, &type_right) {
1476 return None;
1477 }
1478 let type_out = func.output_type(&[type_left, type_right]);
1479 let expr_out = MirScalarExpr::CallBinary {
1480 func,
1481 expr1: Box::new(expr_left),
1482 expr2: Box::new(expr_right),
1483 };
1484 Some((expr_out, type_out))
1485 },
1486 )
1487 .boxed();
1488 let variadic_gen = (
1489 select(INTERESTING_VARIADIC_FUNCS),
1490 prop::collection::vec(self_gen, 1..4),
1491 )
1492 .prop_filter_map("variadic func", |(func, exprs)| {
1493 let (exprs_in, type_in): (_, Vec<_>) = exprs.into_iter().unzip();
1494 if !variadic_typecheck(&func, &type_in) {
1495 return None;
1496 }
1497 let type_out = func.output_type(type_in);
1498 let expr_out = MirScalarExpr::CallVariadic {
1499 func,
1500 exprs: exprs_in,
1501 };
1502 Some((expr_out, type_out))
1503 })
1504 .boxed();
1505
1506 unary_gen
1507 .prop_union(binary_gen)
1508 .boxed()
1509 .prop_union(variadic_gen)
1510 })
1511 .boxed()
1512 }
1513
1514 fn gen_expr_data() -> impl Strategy<Value = ExpressionData> {
1515 let columns = prop::collection::vec(gen_column(), 1..10);
1516 columns.prop_flat_map(|data| {
1517 let (columns, datums, specs): (Vec<_>, Vec<_>, Vec<_>) = data.into_iter().multiunzip();
1518 let relation = ReprRelationType::new(columns);
1519 let row = Row::pack_slice(&datums);
1520 gen_expr_for_relation(&relation).prop_map(move |(expr, _)| ExpressionData {
1521 relation_type: relation.clone(),
1522 specs: specs.clone(),
1523 rows: vec![row.clone()],
1524 expr,
1525 })
1526 })
1527 }
1528
1529 #[mz_ore::test]
1530 #[cfg_attr(miri, ignore)] fn test_trivial_spec_matches() {
1532 fn check(datum: PropDatum) -> Result<(), TestCaseError> {
1533 let datum: Datum = (&datum).into();
1534 let spec = if datum.is_null() {
1535 ResultSpec::null()
1536 } else {
1537 ResultSpec::value(datum)
1538 };
1539 assert!(spec.may_contain(datum));
1540 Ok(())
1541 }
1542
1543 proptest!(|(datum in mz_repr::arb_datum(true))| {
1544 check(datum)?;
1545 });
1546
1547 assert!(ResultSpec::fails().may_fail());
1548 }
1549
1550 #[mz_ore::test]
1551 #[cfg_attr(miri, ignore)] fn test_equivalence() {
1553 fn check(data: ExpressionData) -> Result<(), TestCaseError> {
1554 let ExpressionData {
1555 relation_type,
1556 specs,
1557 rows,
1558 expr,
1559 } = data;
1560
1561 let arena = RowArena::new();
1565 let mut interpreter = ColumnSpecs::new(&relation_type, &arena);
1566 for (id, spec) in specs.into_iter().enumerate() {
1567 interpreter.push_column(id, spec);
1568 }
1569
1570 let spec = interpreter.expr(&expr);
1571
1572 for row in &rows {
1573 let datums: Vec<_> = row.iter().collect();
1574 let eval_result = expr.eval(&datums, &arena);
1575 match eval_result {
1576 Ok(value) => {
1577 assert!(spec.range.may_contain(value))
1578 }
1579 Err(_) => {
1580 assert!(spec.range.may_fail());
1581 }
1582 }
1583 }
1584
1585 Ok(())
1586 }
1587
1588 proptest!(|(data in gen_expr_data())| {
1589 check(data)?;
1590 });
1591 }
1592
1593 #[mz_ore::test]
1601 #[cfg_attr(miri, ignore)]
1602 fn test_mfp_unreferenced_fallible_expression() {
1603 use crate::scalar::func::CastStringToUuid;
1604
1605 let mfp = MapFilterProject {
1611 expressions: vec![MirScalarExpr::CallUnary {
1612 func: UnaryFunc::CastStringToUuid(CastStringToUuid),
1613 expr: Box::new(MirScalarExpr::column(0)),
1614 }],
1615 predicates: vec![(
1616 1,
1617 MirScalarExpr::literal_ok(Datum::True, ReprScalarType::Bool),
1618 )],
1619 projection: vec![0, 1],
1620 input_arity: 1,
1621 };
1622
1623 let relation = ReprRelationType::new(vec![ReprScalarType::String.nullable(false)]);
1624 let arena = RowArena::new();
1625 let mut interpreter = ColumnSpecs::new(&relation, &arena);
1626 interpreter.push_column(
1628 0,
1629 ResultSpec::value_between(Datum::String("not-a-uuid"), Datum::String("not-a-uuid")),
1630 );
1631 let spec = interpreter.mfp_filter(&mfp);
1632 assert!(
1633 spec.range.may_fail(),
1634 "an MFP expression that errors on the stats range must propagate \
1635 fallibility, otherwise persist filter pushdown can wrongly discard \
1636 a part that produces error rows",
1637 );
1638 }
1639
1640 #[mz_ore::test]
1650 #[cfg_attr(miri, ignore)]
1651 fn test_mfp_filter_fallibility_equivalence() {
1652 fn check(data: ExpressionData) -> Result<(), TestCaseError> {
1653 let ExpressionData {
1654 relation_type,
1655 specs,
1656 rows,
1657 expr,
1658 } = data;
1659
1660 let input_arity = relation_type.column_types.len();
1661 let mfp = MapFilterProject {
1662 expressions: vec![expr.clone()],
1663 predicates: vec![],
1664 projection: (0..input_arity).collect(),
1665 input_arity,
1666 };
1667
1668 let arena = RowArena::new();
1669 let mut interpreter = ColumnSpecs::new(&relation_type, &arena);
1670 for (id, spec) in specs.into_iter().enumerate() {
1671 interpreter.push_column(id, spec);
1672 }
1673 let summary = interpreter.mfp_filter(&mfp);
1674
1675 for row in &rows {
1676 let datums: Vec<_> = row.iter().collect();
1677 if expr.eval(&datums, &arena).is_err() {
1678 prop_assert!(
1679 summary.range.may_fail(),
1680 "mfp_filter must surface the fallibility of an \
1681 unreferenced MFP expression: row {:?} errored at \
1682 runtime but the interpreter ruled out errors",
1683 row,
1684 );
1685 }
1686 }
1687 Ok(())
1688 }
1689
1690 proptest!(|(data in gen_expr_data())| {
1691 check(data)?;
1692 });
1693 }
1694
1695 #[mz_ore::test]
1696 fn test_mfp() {
1697 use MirScalarExpr::*;
1699
1700 let mfp = MapFilterProject {
1701 expressions: vec![],
1702 predicates: vec![
1703 (
1705 1,
1706 CallUnary {
1707 func: UnaryFunc::IsNull(IsNull),
1708 expr: Box::new(CallBinary {
1709 func: MulInt32.into(),
1710 expr1: Box::new(MirScalarExpr::column(0)),
1711 expr2: Box::new(MirScalarExpr::column(0)),
1712 }),
1713 },
1714 ),
1715 (
1717 1,
1718 CallBinary {
1719 func: Eq.into(),
1720 expr1: Box::new(MirScalarExpr::column(0)),
1721 expr2: Box::new(MirScalarExpr::literal_ok(
1722 Datum::Int32(1727694505),
1723 ReprScalarType::Int32,
1724 )),
1725 },
1726 ),
1727 ],
1728 projection: vec![],
1729 input_arity: 1,
1730 };
1731
1732 let relation = ReprRelationType::new(vec![ReprScalarType::Int32.nullable(true)]);
1733 let arena = RowArena::new();
1734 let mut interpreter = ColumnSpecs::new(&relation, &arena);
1735 interpreter.push_column(0, ResultSpec::value(Datum::Int32(-1294725158)));
1736 let spec = interpreter.mfp_filter(&mfp);
1737 assert!(spec.range.may_fail());
1738 }
1739
1740 #[mz_ore::test]
1741 fn test_concat() {
1742 let expr = MirScalarExpr::call_variadic(
1743 Concat,
1744 vec![
1745 MirScalarExpr::column(0),
1746 MirScalarExpr::literal_ok(Datum::String("a"), ReprScalarType::String),
1747 MirScalarExpr::literal_ok(Datum::String("b"), ReprScalarType::String),
1748 ],
1749 );
1750
1751 let relation = ReprRelationType::new(vec![ReprScalarType::String.nullable(false)]);
1752 let arena = RowArena::new();
1753 let interpreter = ColumnSpecs::new(&relation, &arena);
1754 let spec = interpreter.expr(&expr);
1755 assert!(spec.range.may_contain(Datum::String("blab")));
1756 }
1757
1758 #[mz_ore::test]
1759 fn test_eval_range() {
1760 let period_ms = MirScalarExpr::literal_ok(Datum::Int64(10), ReprScalarType::Int64);
1762 let expr = MirScalarExpr::CallBinary {
1763 func: Gte.into(),
1764 expr1: Box::new(MirScalarExpr::CallUnmaterializable(
1765 UnmaterializableFunc::MzNow,
1766 )),
1767 expr2: Box::new(MirScalarExpr::CallUnary {
1768 func: UnaryFunc::CastInt64ToMzTimestamp(CastInt64ToMzTimestamp),
1769 expr: Box::new(MirScalarExpr::CallBinary {
1770 func: MulInt64.into(),
1771 expr1: Box::new(period_ms.clone()),
1772 expr2: Box::new(MirScalarExpr::CallBinary {
1773 func: DivInt64.into(),
1774 expr1: Box::new(MirScalarExpr::column(0)),
1775 expr2: Box::new(period_ms),
1776 }),
1777 }),
1778 }),
1779 };
1780 let relation = ReprRelationType::new(vec![ReprScalarType::Int64.nullable(false)]);
1781
1782 {
1783 let arena = RowArena::new();
1785 let mut interpreter = ColumnSpecs::new(&relation, &arena);
1786 interpreter.push_unmaterializable(
1787 UnmaterializableFunc::MzNow,
1788 ResultSpec::value_between(
1789 Datum::MzTimestamp(10.into()),
1790 Datum::MzTimestamp(20.into()),
1791 ),
1792 );
1793 interpreter.push_column(0, ResultSpec::value_between(30i64.into(), 40i64.into()));
1794
1795 let range_out = interpreter.expr(&expr).range;
1796 assert!(range_out.may_contain(Datum::False));
1797 assert!(!range_out.may_contain(Datum::True));
1798 assert!(!range_out.may_contain(Datum::Null));
1799 assert!(!range_out.may_fail());
1800 }
1801
1802 {
1803 let arena = RowArena::new();
1805 let mut interpreter = ColumnSpecs::new(&relation, &arena);
1806 interpreter.push_unmaterializable(
1807 UnmaterializableFunc::MzNow,
1808 ResultSpec::value_between(
1809 Datum::MzTimestamp(10.into()),
1810 Datum::MzTimestamp(35.into()),
1811 ),
1812 );
1813 interpreter.push_column(0, ResultSpec::value_between(30i64.into(), 40i64.into()));
1814
1815 let range_out = interpreter.expr(&expr).range;
1816 assert!(range_out.may_contain(Datum::False));
1817 assert!(range_out.may_contain(Datum::True));
1818 assert!(!range_out.may_contain(Datum::Null));
1819 assert!(!range_out.may_fail());
1820 }
1821 }
1822
1823 #[mz_ore::test]
1824 #[cfg_attr(miri, ignore)] fn test_jsonb() {
1826 let arena = RowArena::new();
1827
1828 let expr = MirScalarExpr::column(0)
1829 .call_binary(
1830 MirScalarExpr::literal_ok(Datum::from("ts"), ReprScalarType::String),
1831 JsonbGetString,
1832 )
1833 .call_unary(CastJsonbToNumeric(None));
1834
1835 let relation = ReprRelationType::new(vec![ReprScalarType::Jsonb.nullable(true)]);
1836 let mut interpreter = ColumnSpecs::new(&relation, &arena);
1837 interpreter.push_column(
1838 0,
1839 ResultSpec::map_spec(
1840 [(
1841 "ts".into(),
1842 ResultSpec::value_between(
1843 Datum::Numeric(100.into()),
1844 Datum::Numeric(300.into()),
1845 ),
1846 )]
1847 .into_iter()
1848 .collect(),
1849 ),
1850 );
1851
1852 let range_out = interpreter.expr(&expr).range;
1853 assert!(!range_out.may_contain(Datum::Numeric(0.into())));
1854 assert!(range_out.may_contain(Datum::Numeric(200.into())));
1855 assert!(!range_out.may_contain(Datum::Numeric(400.into())));
1856 }
1857
1858 #[mz_ore::test]
1859 fn test_nested_union_partial_overlap() {
1860 let a = ResultSpec::map_spec(
1865 [
1866 ("x".into(), ResultSpec::value(Datum::String("a"))),
1867 ("y".into(), ResultSpec::value(Datum::String("b"))),
1868 ("c".into(), ResultSpec::value(Datum::String("c"))),
1869 ]
1870 .into_iter()
1871 .collect(),
1872 );
1873 let b = ResultSpec::map_spec(
1874 [
1875 ("x".into(), ResultSpec::value(Datum::String("a2"))),
1876 ("y".into(), ResultSpec::value(Datum::String("b2"))),
1877 ("z".into(), ResultSpec::value(Datum::String("z"))),
1878 ]
1879 .into_iter()
1880 .collect(),
1881 );
1882
1883 let unioned = a.union(b);
1884
1885 let arena = RowArena::new();
1889 let relation = ReprRelationType::new(vec![ReprScalarType::Jsonb.nullable(false)]);
1890
1891 {
1893 let mut interpreter = ColumnSpecs::new(&relation, &arena);
1894 interpreter.push_column(0, unioned.clone());
1895 let expr = MirScalarExpr::column(0).call_binary(
1896 MirScalarExpr::literal_ok(Datum::from("c"), ReprScalarType::String),
1897 JsonbGetStringStringify,
1898 );
1899 assert!(interpreter.expr(&expr).range.may_contain(Datum::Null));
1900 }
1901
1902 {
1904 let mut interpreter = ColumnSpecs::new(&relation, &arena);
1905 interpreter.push_column(0, unioned.clone());
1906 let expr = MirScalarExpr::column(0).call_binary(
1907 MirScalarExpr::literal_ok(Datum::from("z"), ReprScalarType::String),
1908 JsonbGetStringStringify,
1909 );
1910 assert!(interpreter.expr(&expr).range.may_contain(Datum::Null));
1911 }
1912
1913 {
1915 let mut interpreter = ColumnSpecs::new(&relation, &arena);
1916 interpreter.push_column(0, unioned);
1917 let expr = MirScalarExpr::column(0).call_binary(
1918 MirScalarExpr::literal_ok(Datum::from("x"), ReprScalarType::String),
1919 JsonbGetStringStringify,
1920 );
1921 let x_range = interpreter.expr(&expr).range;
1922 assert!(x_range.may_contain(Datum::String("a")));
1923 assert!(x_range.may_contain(Datum::String("a2")));
1924 }
1925 }
1926
1927 #[mz_ore::test]
1928 #[cfg_attr(miri, ignore)] fn test_case_over_jsonb_columns() {
1930 let arena = RowArena::new();
1934
1935 let expr = MirScalarExpr::If {
1937 cond: Box::new(MirScalarExpr::column(0)),
1938 then: Box::new(MirScalarExpr::column(1)),
1939 els: Box::new(MirScalarExpr::column(2)),
1940 }
1941 .call_binary(
1942 MirScalarExpr::literal_ok(Datum::from("y"), ReprScalarType::String),
1943 JsonbGetStringStringify,
1944 )
1945 .call_unary(UnaryFunc::IsNull(IsNull));
1946
1947 let relation = ReprRelationType::new(vec![
1948 ReprScalarType::Bool.nullable(false),
1949 ReprScalarType::Jsonb.nullable(false),
1950 ReprScalarType::Jsonb.nullable(false),
1951 ]);
1952 let mut interpreter = ColumnSpecs::new(&relation, &arena);
1953 interpreter.push_column(0, ResultSpec::value_between(Datum::False, Datum::True));
1954 interpreter.push_column(
1955 1,
1956 ResultSpec::map_spec(
1957 [("x".into(), ResultSpec::value(Datum::String("a")))]
1958 .into_iter()
1959 .collect(),
1960 ),
1961 );
1962 interpreter.push_column(
1963 2,
1964 ResultSpec::map_spec(
1965 [("y".into(), ResultSpec::value(Datum::String("b")))]
1966 .into_iter()
1967 .collect(),
1968 ),
1969 );
1970
1971 let range_out = interpreter.expr(&expr).range;
1972 assert!(range_out.may_contain(Datum::True));
1975 }
1976
1977 #[mz_ore::test]
1978 fn test_like() {
1979 let arena = RowArena::new();
1980
1981 let expr = MirScalarExpr::CallUnary {
1982 func: UnaryFunc::IsLikeMatch(IsLikeMatch(
1983 crate::like_pattern::compile("%whatever%", true).unwrap(),
1984 )),
1985 expr: Box::new(MirScalarExpr::column(0)),
1986 };
1987
1988 let relation = ReprRelationType::new(vec![ReprScalarType::String.nullable(true)]);
1989 let mut interpreter = ColumnSpecs::new(&relation, &arena);
1990 interpreter.push_column(
1991 0,
1992 ResultSpec::value_between(Datum::String("aardvark"), Datum::String("zebra")),
1993 );
1994
1995 let range_out = interpreter.expr(&expr).range;
1996 assert!(
1997 !range_out.fallible,
1998 "like function should not error on non-error input"
1999 );
2000 assert!(range_out.may_contain(Datum::True));
2001 assert!(range_out.may_contain(Datum::False));
2002 assert!(range_out.may_contain(Datum::Null));
2003 }
2004
2005 #[mz_ore::test]
2006 fn test_try_parse_monotonic_iso8601_timestamp() {
2007 use chrono::NaiveDateTime;
2008
2009 let arena = RowArena::new();
2010
2011 let expr = MirScalarExpr::CallUnary {
2012 func: UnaryFunc::TryParseMonotonicIso8601Timestamp(TryParseMonotonicIso8601Timestamp),
2013 expr: Box::new(MirScalarExpr::column(0)),
2014 };
2015
2016 let relation = ReprRelationType::new(vec![ReprScalarType::String.nullable(true)]);
2017 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2019 interpreter.push_column(
2020 0,
2021 ResultSpec::value_between(
2022 Datum::String("2024-01-11T00:00:00.000Z"),
2023 Datum::String("2024-01-11T20:00:00.000Z"),
2024 ),
2025 );
2026
2027 let timestamp = |ts| {
2028 Datum::Timestamp(
2029 NaiveDateTime::parse_from_str(ts, "%Y-%m-%dT%H:%M:%S")
2030 .unwrap()
2031 .try_into()
2032 .unwrap(),
2033 )
2034 };
2035
2036 let range_out = interpreter.expr(&expr).range;
2037 assert!(!range_out.fallible);
2038 assert!(range_out.nullable);
2039 assert!(!range_out.may_contain(timestamp("2024-01-10T10:00:00")));
2040 assert!(range_out.may_contain(timestamp("2024-01-11T10:00:00")));
2041 assert!(!range_out.may_contain(timestamp("2024-01-12T10:00:00")));
2042
2043 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2045 interpreter.push_column(
2046 0,
2047 ResultSpec::value_between(Datum::String("2024-01-1"), Datum::String("2024-01-2")),
2048 );
2049
2050 let range_out = interpreter.expr(&expr).range;
2051 assert!(!range_out.fallible);
2052 assert!(range_out.nullable);
2053 assert!(range_out.may_contain(timestamp("2024-01-10T10:00:00")));
2054 assert!(range_out.may_contain(timestamp("2024-01-11T10:00:00")));
2055 assert!(range_out.may_contain(timestamp("2024-01-12T10:00:00")));
2056
2057 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2059 interpreter.push_column(
2060 0,
2061 ResultSpec::value_between(
2062 Datum::String("2024-01-1"),
2063 Datum::String("2024-01-12T10:00:00"),
2064 )
2065 .union(ResultSpec::null()),
2066 );
2067
2068 let range_out = interpreter.expr(&expr).range;
2069 assert!(!range_out.fallible);
2070 assert!(range_out.nullable);
2071 assert!(range_out.may_contain(timestamp("2024-01-10T10:00:00")));
2072 assert!(range_out.may_contain(timestamp("2024-01-11T10:00:00")));
2073 assert!(range_out.may_contain(timestamp("2024-01-12T10:00:00")));
2074
2075 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2077 interpreter.push_column(
2078 0,
2079 ResultSpec::value_between(
2080 Datum::String("2024-01-11T10:00:00.000Z"),
2081 Datum::String("2024-01-11T10:00:00.000Z"),
2082 ),
2083 );
2084
2085 let range_out = interpreter.expr(&expr).range;
2086 assert!(!range_out.fallible);
2087 assert!(!range_out.nullable);
2088 assert!(!range_out.may_contain(timestamp("2024-01-10T10:00:00")));
2089 assert!(range_out.may_contain(timestamp("2024-01-11T10:00:00")));
2090 assert!(!range_out.may_contain(timestamp("2024-01-12T10:00:00")));
2091 }
2092
2093 #[mz_ore::test]
2094 fn test_inequality() {
2095 let arena = RowArena::new();
2096
2097 let expr = MirScalarExpr::column(0).call_binary(
2098 MirScalarExpr::CallUnmaterializable(UnmaterializableFunc::MzNow),
2099 Gte,
2100 );
2101
2102 let relation = ReprRelationType::new(vec![ReprScalarType::MzTimestamp.nullable(true)]);
2103 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2104 interpreter.push_column(
2105 0,
2106 ResultSpec::value_between(
2107 Datum::MzTimestamp(1704736444949u64.into()),
2108 Datum::MzTimestamp(1704736444949u64.into()),
2109 )
2110 .union(ResultSpec::null()),
2111 );
2112 interpreter.push_unmaterializable(
2113 UnmaterializableFunc::MzNow,
2114 ResultSpec::value_between(
2115 Datum::MzTimestamp(1704738791000u64.into()),
2116 Datum::MzTimestamp(18446744073709551615u64.into()),
2117 ),
2118 );
2119
2120 let range_out = interpreter.expr(&expr).range;
2121 assert!(
2122 !range_out.fallible,
2123 "<= function should not error on non-error input"
2124 );
2125 assert!(!range_out.may_contain(Datum::True));
2126 assert!(range_out.may_contain(Datum::False));
2127 assert!(range_out.may_contain(Datum::Null));
2128 }
2129
2130 #[mz_ore::test]
2138 #[cfg_attr(miri, ignore)]
2139 fn test_add_timestamp_interval_non_monotone() {
2140 use chrono::NaiveDateTime;
2141 use mz_repr::adt::interval::Interval;
2142 use mz_repr::adt::timestamp::CheckedTimestamp;
2143 use mz_repr::{Datum, Row};
2144
2145 let arena = RowArena::new();
2146
2147 let ts_lit = |s: &str| {
2159 let mut row = Row::default();
2160 row.packer().push(Datum::Timestamp(
2161 CheckedTimestamp::from_timestamplike(
2162 NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S").unwrap(),
2163 )
2164 .unwrap(),
2165 ));
2166 MirScalarExpr::Literal(Ok(row), ReprScalarType::Timestamp.nullable(false))
2167 };
2168 let interval = |months: i32, days: i32, micros: i64| {
2169 Datum::Interval(Interval {
2170 months,
2171 days,
2172 micros,
2173 })
2174 };
2175
2176 let expr = ts_lit("2024-01-31T00:00:00")
2178 .call_binary(MirScalarExpr::column(0), AddTimestampInterval)
2179 .call_binary(ts_lit("2024-03-15T00:00:00"), Gte);
2180
2181 let relation = ReprRelationType::new(vec![ReprScalarType::Interval.nullable(false)]);
2182 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2183 interpreter.push_column(
2184 0,
2185 ResultSpec::value_between(interval(0, 31, 0), interval(1, 0, 0)),
2186 );
2187
2188 let range_out = interpreter.expr(&expr).range;
2189 assert!(
2196 range_out.may_contain(Datum::True),
2197 "interpreter incorrectly ruled out matching rows; \
2198 add_timestamp_interval is not monotone in the interval argument",
2199 );
2200 }
2201
2202 #[mz_ore::test]
2209 #[cfg_attr(miri, ignore)]
2210 fn test_timestamp_plus_interval_dynamic_monotone() {
2211 use chrono::NaiveDateTime;
2212 use mz_repr::adt::interval::Interval;
2213 use mz_repr::adt::timestamp::CheckedTimestamp;
2214 use mz_repr::{Datum, Row};
2215
2216 let arena = RowArena::new();
2217
2218 let ts = |s: &str| {
2219 Datum::Timestamp(
2220 CheckedTimestamp::from_timestamplike(
2221 NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S").unwrap(),
2222 )
2223 .unwrap(),
2224 )
2225 };
2226 let interval_lit = |months: i32, days: i32, micros: i64| {
2227 let mut row = Row::default();
2228 row.packer().push(Datum::Interval(Interval {
2229 months,
2230 days,
2231 micros,
2232 }));
2233 MirScalarExpr::Literal(Ok(row), ReprScalarType::Interval.nullable(false))
2234 };
2235
2236 let relation = ReprRelationType::new(vec![ReprScalarType::Timestamp.nullable(false)]);
2237
2238 {
2244 let expr = MirScalarExpr::column(0)
2245 .call_binary(interval_lit(0, 1, 0), SubTimestampInterval)
2246 .call_binary(
2247 MirScalarExpr::Literal(
2248 Ok({
2249 let mut r = Row::default();
2250 r.packer().push(ts("2024-01-15T00:00:00"));
2251 r
2252 }),
2253 ReprScalarType::Timestamp.nullable(false),
2254 ),
2255 Lt,
2256 );
2257 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2258 interpreter.push_column(
2259 0,
2260 ResultSpec::value_between(ts("2024-01-15T00:00:00"), ts("2024-01-20T00:00:00")),
2261 );
2262 let range_out = interpreter.expr(&expr).range;
2263 assert!(
2264 range_out.may_contain(Datum::True),
2265 "day-only interval should preserve tight bounds",
2266 );
2267 assert!(
2268 range_out.may_contain(Datum::False),
2269 "day-only interval should preserve tight bounds",
2270 );
2271 }
2272
2273 {
2278 let expr = MirScalarExpr::column(0)
2279 .call_binary(interval_lit(0, 1, 0), SubTimestampInterval)
2280 .call_binary(
2281 MirScalarExpr::Literal(
2282 Ok({
2283 let mut r = Row::default();
2284 r.packer().push(ts("2024-01-15T00:00:00"));
2285 r
2286 }),
2287 ReprScalarType::Timestamp.nullable(false),
2288 ),
2289 Lt,
2290 );
2291 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2292 interpreter.push_column(
2293 0,
2294 ResultSpec::value_between(ts("2024-01-17T00:00:00"), ts("2024-01-20T00:00:00")),
2295 );
2296 let range_out = interpreter.expr(&expr).range;
2297 assert!(
2298 !range_out.may_contain(Datum::True),
2299 "day-only interval should narrow out impossible matches",
2300 );
2301 }
2302
2303 {
2308 let expr = MirScalarExpr::column(0)
2309 .call_binary(interval_lit(1, 0, 0), SubTimestampInterval)
2310 .call_binary(
2311 MirScalarExpr::Literal(
2312 Ok({
2313 let mut r = Row::default();
2314 r.packer().push(ts("2024-01-15T00:00:00"));
2315 r
2316 }),
2317 ReprScalarType::Timestamp.nullable(false),
2318 ),
2319 Lt,
2320 );
2321 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2322 interpreter.push_column(
2323 0,
2324 ResultSpec::value_between(ts("2024-01-17T00:00:00"), ts("2024-01-20T00:00:00")),
2325 );
2326 let range_out = interpreter.expr(&expr).range;
2327 assert!(
2328 range_out.may_contain(Datum::True),
2329 "month-bearing interval must conservatively admit True",
2330 );
2331 assert!(
2332 range_out.may_contain(Datum::False),
2333 "month-bearing interval must conservatively admit False",
2334 );
2335 }
2336 }
2337
2338 #[mz_ore::test]
2347 #[cfg_attr(miri, ignore)]
2348 fn proptest_timestamp_plus_interval_monotone_when_months_zero() {
2349 use mz_repr::adt::interval::Interval;
2350 use mz_repr::{Datum, RowArena, SqlScalarType, arb_datum_for_scalar};
2351 use proptest::prelude::*;
2352
2353 let timestamp_strat = || arb_datum_for_scalar(SqlScalarType::Timestamp { precision: None });
2354 let zero_month_interval_strat =
2361 (any::<i32>(), any::<i64>()).prop_map(|(days, micros)| Interval {
2362 months: 0,
2363 days,
2364 micros,
2365 });
2366
2367 let expr = MirScalarExpr::CallBinary {
2368 func: AddTimestampInterval.into(),
2369 expr1: Box::new(MirScalarExpr::column(0)),
2370 expr2: Box::new(MirScalarExpr::column(1)),
2371 };
2372 let arena = RowArena::new();
2373
2374 proptest!(|(
2375 t1 in timestamp_strat(),
2376 t2 in timestamp_strat(),
2377 i in zero_month_interval_strat,
2378 )| {
2379 let t1 = match t1 { PropDatum::Timestamp(t) => t, _ => unreachable!() };
2380 let t2 = match t2 { PropDatum::Timestamp(t) => t, _ => unreachable!() };
2381 let i = Datum::Interval(i);
2382 let r1 = expr.eval(&[Datum::Timestamp(t1), i], &arena);
2383 let r2 = expr.eval(&[Datum::Timestamp(t2), i], &arena);
2384 if let (Ok(Datum::Timestamp(r1)), Ok(Datum::Timestamp(r2))) = (r1, r2) {
2387 prop_assert_eq!(t1.cmp(&t2), r1.cmp(&r2));
2388 }
2389 });
2390 }
2391
2392 #[mz_ore::test]
2397 #[cfg_attr(miri, ignore)]
2398 fn test_date_bin_timestamp_non_monotone() {
2399 use chrono::NaiveDateTime;
2400 use mz_repr::adt::interval::Interval;
2401 use mz_repr::adt::timestamp::CheckedTimestamp;
2402 use mz_repr::{Datum, Row};
2403
2404 let arena = RowArena::new();
2405
2406 let ts_lit = |s: &str| {
2407 let mut row = Row::default();
2408 row.packer().push(Datum::Timestamp(
2409 CheckedTimestamp::from_timestamplike(
2410 NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S").unwrap(),
2411 )
2412 .unwrap(),
2413 ));
2414 MirScalarExpr::Literal(Ok(row), ReprScalarType::Timestamp.nullable(false))
2415 };
2416 let interval = |months: i32, days: i32, micros: i64| {
2417 Datum::Interval(Interval {
2418 months,
2419 days,
2420 micros,
2421 })
2422 };
2423
2424 let expr = MirScalarExpr::column(0)
2441 .call_binary(ts_lit("2024-01-01T12:00:00"), DateBinTimestamp)
2442 .call_binary(ts_lit("2024-01-01T06:00:00"), Gt);
2443
2444 let relation = ReprRelationType::new(vec![ReprScalarType::Interval.nullable(false)]);
2445 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2446 interpreter.push_column(
2447 0,
2448 ResultSpec::value_between(interval(0, 1, 0), interval(0, 2, 0)),
2449 );
2450
2451 let range_out = interpreter.expr(&expr).range;
2452 assert!(
2453 range_out.may_contain(Datum::True),
2454 "date_bin is not monotone in the stride argument; \
2455 interior strides can produce outputs outside the endpoint-bounded \
2456 box, so the interpreter must admit True for `>`-style predicates",
2457 );
2458 }
2459
2460 #[mz_ore::test]
2461 fn test_trace() {
2462 use super::Trace;
2463
2464 let expr = MirScalarExpr::column(0).call_binary(
2465 MirScalarExpr::column(1)
2466 .call_binary(MirScalarExpr::column(3).call_unary(NegInt64), AddInt64),
2467 Gte,
2468 );
2469 let summary = Trace.expr(&expr);
2470 assert!(summary.pushdownable());
2471 }
2472}