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> {
268 assert!(!min.is_null());
269 assert!(!max.is_null());
270 if min <= max {
271 ResultSpec {
272 values: Values::Within(min, max),
273 ..ResultSpec::nothing()
274 }
275 } else {
276 ResultSpec::value_all()
277 }
278 }
279
280 pub fn value_all() -> ResultSpec<'a> {
282 ResultSpec {
283 values: Values::All,
284 ..ResultSpec::nothing()
285 }
286 }
287
288 pub fn map_spec(map: BTreeMap<Datum<'a>, ResultSpec<'a>>) -> ResultSpec<'a> {
290 ResultSpec {
291 values: Values::Nested(map),
292 ..ResultSpec::nothing()
293 }
294 }
295
296 pub fn union(self, other: ResultSpec<'a>) -> ResultSpec<'a> {
298 ResultSpec {
299 nullable: self.nullable || other.nullable,
300 fallible: self.fallible || other.fallible,
301 values: self.values.union(other.values),
302 }
303 }
304
305 pub fn intersect(self, other: ResultSpec<'a>) -> ResultSpec<'a> {
307 ResultSpec {
308 nullable: self.nullable && other.nullable,
309 fallible: self.fallible && other.fallible,
310 values: self.values.intersect(other.values),
311 }
312 }
313
314 pub fn may_contain(&self, value: Datum<'a>) -> bool {
316 if value == Datum::Null {
317 return self.nullable;
318 }
319
320 self.values.may_contain(value)
321 }
322
323 pub fn may_fail(&self) -> bool {
325 self.fallible
326 }
327
328 fn is_single_value(&self) -> bool {
336 self.values.as_single().is_some()
337 }
338
339 fn may_be_infinite(&self) -> bool {
343 match &self.values {
344 Values::Within(min, max) => datum_is_infinite(*min) || datum_is_infinite(*max),
345 Values::All => true,
346 Values::Empty | Values::Nested(_) => false,
347 }
348 }
349
350 fn flat_map(
361 &self,
362 is_monotone: bool,
363 mut result_map: impl FnMut(Result<Datum<'a>, EvalError>) -> ResultSpec<'a>,
364 ) -> ResultSpec<'a> {
365 let null_spec = if self.nullable {
366 result_map(Ok(Datum::Null))
367 } else {
368 ResultSpec::nothing()
369 };
370
371 let error_spec = if self.fallible {
372 let map_err = result_map(Err(EvalError::Internal("".into())));
376 let raise_err = ResultSpec::fails();
377 raise_err.union(map_err)
382 } else {
383 ResultSpec::nothing()
384 };
385
386 let values_spec = match self.values {
387 Values::Empty => ResultSpec::nothing(),
388 Values::Within(min, max) if min == max => result_map(Ok(min)),
390 Values::Within(Datum::False, Datum::True) => {
392 result_map(Ok(Datum::False)).union(result_map(Ok(Datum::True)))
393 }
394 Values::Within(min, max) if is_monotone && !datum_is_nan(min) && !datum_is_nan(max) => {
401 let min_result = result_map(Ok(min));
402 let max_result = result_map(Ok(max));
403 match (min_result, max_result) {
412 (
415 ResultSpec {
416 nullable: n1,
417 fallible: f1,
418 values: a_values @ Values::Within(..),
419 },
420 ResultSpec {
421 nullable: n2,
422 fallible: f2,
423 values: b_values @ Values::Within(..),
424 },
425 ) => ResultSpec {
426 nullable: n1 || n2,
427 fallible: f1 || f2,
428 values: a_values.union(b_values),
429 },
430 (
436 ResultSpec {
437 nullable: true,
438 fallible: false,
439 values: Values::Empty,
440 },
441 ResultSpec {
442 nullable: true,
443 fallible: false,
444 values: Values::Empty,
445 },
446 ) => ResultSpec::null(),
447 _ => ResultSpec::anything(),
449 }
450 }
451 Values::Within(_, _) | Values::Nested(_) | Values::All => ResultSpec::anything(),
453 };
454
455 null_spec.union(error_spec).union(values_spec)
456 }
457}
458
459pub trait Interpreter {
468 type Summary: Clone + Debug + Sized;
469
470 fn column(&self, id: usize) -> Self::Summary;
472
473 fn literal(&self, result: &Result<Row, EvalError>, col_type: &ReprColumnType) -> Self::Summary;
476 fn unmaterializable(&self, func: &UnmaterializableFunc) -> Self::Summary;
481
482 fn unary(&self, func: &UnaryFunc, expr: Self::Summary) -> Self::Summary;
484
485 fn binary(&self, func: &BinaryFunc, left: Self::Summary, right: Self::Summary)
487 -> Self::Summary;
488
489 fn variadic(&self, func: &VariadicFunc, exprs: Vec<Self::Summary>) -> Self::Summary;
491
492 fn cond(&self, cond: Self::Summary, then: Self::Summary, els: Self::Summary) -> Self::Summary;
494
495 fn expr(&self, expr: &MirScalarExpr) -> Self::Summary {
497 match expr {
498 MirScalarExpr::Column(id, _name) => self.column(*id),
499 MirScalarExpr::Literal(value, col_type) => self.literal(value, col_type),
500 MirScalarExpr::CallUnmaterializable(func) => self.unmaterializable(func),
501 MirScalarExpr::CallUnary { func, expr } => {
502 let expr_range = self.expr(expr);
503 self.unary(func, expr_range)
504 }
505 MirScalarExpr::CallBinary { func, expr1, expr2 } => {
506 let expr1_range = self.expr(expr1);
507 let expr2_range = self.expr(expr2);
508 self.binary(func, expr1_range, expr2_range)
509 }
510 MirScalarExpr::CallVariadic { func, exprs } => {
511 let exprs: Vec<_> = exprs.into_iter().map(|e| self.expr(e)).collect();
512 self.variadic(func, exprs)
513 }
514 MirScalarExpr::If { cond, then, els } => {
515 let cond_range = self.expr(cond);
516 let then_range = self.expr(then);
517 let els_range = self.expr(els);
518 self.cond(cond_range, then_range, els_range)
519 }
520 }
521 }
522
523 fn mfp_filter(&self, mfp: &MapFilterProject) -> Self::Summary {
526 let mfp_eval = MfpEval::new(self, mfp.input_arity, &mfp.expressions);
527 let predicates = mfp
529 .predicates
530 .iter()
531 .map(|(_, e)| mfp_eval.expr(e))
532 .collect();
533 mfp_eval.variadic(&And.into(), predicates)
534 }
535
536 fn mfp_plan_filter(&self, plan: &MfpPlan) -> Self::Summary {
539 let mfp_eval = MfpEval::new(self, plan.mfp.input_arity, &plan.mfp.expressions);
540 let mut results: Vec<_> = plan
542 .mfp
543 .predicates
544 .iter()
545 .map(|(_, e)| mfp_eval.expr(e))
546 .collect();
547 let mz_now = mfp_eval.unmaterializable(&UnmaterializableFunc::MzNow);
548 for bound in &plan.lower_bounds {
549 let bound_range = mfp_eval.expr(bound);
550 let result = mfp_eval.binary(&BinaryFunc::Lte(func::Lte), bound_range, mz_now.clone());
551 results.push(result);
552 }
553 for bound in &plan.upper_bounds {
554 let bound_range = mfp_eval.expr(bound);
555 let result = mfp_eval.binary(&BinaryFunc::Gte(func::Gte), bound_range, mz_now.clone());
556 results.push(result);
557 }
558 self.variadic(&And.into(), results)
559 }
560}
561
562pub(crate) struct MfpEval<'a, E: Interpreter + ?Sized> {
565 evaluator: &'a E,
566 input_arity: usize,
567 expressions: Vec<E::Summary>,
568}
569
570impl<'a, E: Interpreter + ?Sized> MfpEval<'a, E> {
571 pub(crate) fn new(evaluator: &'a E, input_arity: usize, expressions: &[MirScalarExpr]) -> Self {
572 let mut mfp_eval = MfpEval {
573 evaluator,
574 input_arity,
575 expressions: vec![],
576 };
577 for expr in expressions {
578 let result = mfp_eval.expr(expr);
579 mfp_eval.expressions.push(result);
580 }
581 mfp_eval
582 }
583}
584
585impl<'a, E: Interpreter + ?Sized> Interpreter for MfpEval<'a, E> {
586 type Summary = E::Summary;
587
588 fn column(&self, id: usize) -> Self::Summary {
589 if id < self.input_arity {
590 self.evaluator.column(id)
591 } else {
592 self.expressions[id - self.input_arity].clone()
593 }
594 }
595
596 fn literal(&self, result: &Result<Row, EvalError>, col_type: &ReprColumnType) -> Self::Summary {
597 self.evaluator.literal(result, col_type)
598 }
599
600 fn unmaterializable(&self, func: &UnmaterializableFunc) -> Self::Summary {
601 self.evaluator.unmaterializable(func)
602 }
603
604 fn unary(&self, func: &UnaryFunc, expr: Self::Summary) -> Self::Summary {
605 self.evaluator.unary(func, expr)
606 }
607
608 fn binary(
609 &self,
610 func: &BinaryFunc,
611 left: Self::Summary,
612 right: Self::Summary,
613 ) -> Self::Summary {
614 self.evaluator.binary(func, left, right)
615 }
616
617 fn variadic(&self, func: &VariadicFunc, exprs: Vec<Self::Summary>) -> Self::Summary {
618 self.evaluator.variadic(func, exprs)
619 }
620
621 fn cond(&self, cond: Self::Summary, then: Self::Summary, els: Self::Summary) -> Self::Summary {
622 self.evaluator.cond(cond, then, els)
623 }
624}
625
626struct SpecialUnary {
631 map_fn: for<'a, 'b> fn(&'b ColumnSpecs<'a>, ResultSpec<'a>) -> ResultSpec<'a>,
632 pushdownable: bool,
633}
634
635impl SpecialUnary {
636 fn for_func(func: &UnaryFunc) -> Option<SpecialUnary> {
638 fn eagerly<'b>(
642 spec: ResultSpec<'b>,
643 value_fn: impl FnOnce(Values<'b>) -> ResultSpec<'b>,
644 ) -> ResultSpec<'b> {
645 let result = match spec.values {
646 Values::Empty => ResultSpec::nothing(),
647 other => value_fn(other),
648 };
649 ResultSpec {
650 fallible: spec.fallible || result.fallible,
651 nullable: spec.nullable || result.nullable,
652 values: result.values,
653 }
654 }
655 match func {
656 UnaryFunc::TryParseMonotonicIso8601Timestamp(_) => Some(SpecialUnary {
657 map_fn: |specs, range| {
658 let expr = MirScalarExpr::CallUnary {
659 func: UnaryFunc::TryParseMonotonicIso8601Timestamp(
660 crate::func::TryParseMonotonicIso8601Timestamp,
661 ),
662 expr: Box::new(MirScalarExpr::column(0)),
663 };
664 let eval = |d| specs.eval_result(expr.eval(&[d], specs.arena));
665
666 eagerly(range, |values| {
667 match values {
668 Values::Within(a, b) if a == b => eval(a),
669 Values::Within(a, b) => {
670 let spec = eval(a).union(eval(b));
671 let values_spec = if spec.nullable {
672 ResultSpec::value_all()
678 } else {
679 spec
680 };
681 values_spec.union(ResultSpec::null())
685 }
686 _ => ResultSpec::any_infallible(),
689 }
690 })
691 },
692 pushdownable: true,
693 }),
694 _ => None,
695 }
696 }
697}
698
699struct AbstractFunc {
710 handler: AbstractFuncHandler,
711 pushdownable: (bool, bool),
717}
718
719enum AbstractFuncHandler {
721 Override(for<'a> fn(ResultSpec<'a>, ResultSpec<'a>) -> ResultSpec<'a>),
724 DynamicMonotone(fn(&ResultSpec<'_>, &ResultSpec<'_>) -> (bool, bool)),
731}
732
733impl AbstractFunc {
734 fn for_func(func: &BinaryFunc) -> Option<AbstractFunc> {
736 fn eagerly<'b>(
740 left: ResultSpec<'b>,
741 right: ResultSpec<'b>,
742 value_fn: impl FnOnce(Values<'b>, Values<'b>) -> ResultSpec<'b>,
743 ) -> ResultSpec<'b> {
744 let result = match (left.values, right.values) {
745 (Values::Empty, _) | (_, Values::Empty) => ResultSpec::nothing(),
746 (l, r) => value_fn(l, r),
747 };
748 ResultSpec {
749 fallible: left.fallible || right.fallible || result.fallible,
750 nullable: left.nullable || right.nullable || result.nullable,
751 values: result.values,
752 }
753 }
754
755 fn jsonb_get_string<'b>(
756 left: ResultSpec<'b>,
757 right: ResultSpec<'b>,
758 stringify: bool,
759 ) -> ResultSpec<'b> {
760 eagerly(left, right, |left, right| {
761 let nested_spec = match (left, right) {
762 (Values::Nested(mut map_spec), Values::Within(key, key2)) if key == key2 => {
763 map_spec.remove(&key)
764 }
765 _ => None,
766 };
767
768 if let Some(field_spec) = nested_spec {
769 if stringify {
770 let values = match field_spec.values {
773 Values::Empty => Values::Empty,
774 Values::Within(min @ Datum::String(_), max @ Datum::String(_)) => {
775 Values::Within(min, max)
776 }
777 Values::Within(_, _) | Values::Nested(_) | Values::All => Values::All,
778 };
779 ResultSpec {
780 values,
781 ..field_spec
782 }
783 } else {
784 field_spec
785 }
786 } else {
787 ResultSpec::any_infallible()
792 }
793 })
794 }
795
796 fn eq<'b>(left: ResultSpec<'b>, right: ResultSpec<'b>) -> ResultSpec<'b> {
797 eagerly(left, right, |left, right| {
798 let maybe_true = match left.clone().intersect(right.clone()) {
800 Values::Empty => ResultSpec::nothing(),
801 _ => ResultSpec::value(Datum::True),
802 };
803
804 let maybe_false = match left.union(right) {
807 Values::Within(a, b) if a == b => ResultSpec::nothing(),
808 _ => ResultSpec::value(Datum::False),
809 };
810
811 maybe_true.union(maybe_false)
812 })
813 }
814
815 fn timestamp_plus_interval_monotone(
824 _left: &ResultSpec<'_>,
825 right: &ResultSpec<'_>,
826 ) -> (bool, bool) {
827 let months_zero = matches!(
828 right.values.as_single(),
829 Some(Datum::Interval(i)) if i.months == 0,
830 );
831 (months_zero, months_zero)
832 }
833
834 match func {
835 BinaryFunc::JsonbGetString(_) => Some(AbstractFunc {
836 handler: AbstractFuncHandler::Override(|l, r| jsonb_get_string(l, r, false)),
837 pushdownable: (true, false),
838 }),
839 BinaryFunc::JsonbGetStringStringify(_) => Some(AbstractFunc {
840 handler: AbstractFuncHandler::Override(|l, r| jsonb_get_string(l, r, true)),
841 pushdownable: (true, false),
842 }),
843 BinaryFunc::Eq(_) => Some(AbstractFunc {
844 handler: AbstractFuncHandler::Override(eq),
845 pushdownable: (true, true),
846 }),
847 BinaryFunc::AddTimestampInterval(_)
848 | BinaryFunc::AddTimestampTzInterval(_)
849 | BinaryFunc::SubTimestampInterval(_)
850 | BinaryFunc::SubTimestampTzInterval(_) => Some(AbstractFunc {
851 handler: AbstractFuncHandler::DynamicMonotone(timestamp_plus_interval_monotone),
852 pushdownable: (true, false),
859 }),
860 _ => None,
861 }
862 }
863}
864
865#[derive(Clone, Debug)]
866pub struct ColumnSpec<'a> {
867 pub col_type: ReprColumnType,
868 pub range: ResultSpec<'a>,
869}
870
871#[derive(Clone, Debug)]
877pub struct ColumnSpecs<'a> {
878 pub relation: &'a ReprRelationType,
879 pub columns: Vec<ResultSpec<'a>>,
880 pub unmaterializables: BTreeMap<UnmaterializableFunc, ResultSpec<'a>>,
881 pub arena: &'a RowArena,
882}
883
884impl<'a> ColumnSpecs<'a> {
885 const MAX_EVAL_ARGS: usize = 6;
892
893 pub fn new(relation: &'a ReprRelationType, arena: &'a RowArena) -> Self {
896 let columns = relation
897 .column_types
898 .iter()
899 .map(|ct| ResultSpec::has_type(ct, false))
900 .collect();
901 ColumnSpecs {
902 relation,
903 columns,
904 unmaterializables: Default::default(),
905 arena,
906 }
907 }
908
909 pub fn push_column(&mut self, id: usize, update: ResultSpec<'a>) {
912 let range = self.columns.get_mut(id).expect("valid column id");
913 *range = range.clone().intersect(update);
914 }
915
916 pub fn push_unmaterializable(&mut self, func: UnmaterializableFunc, update: ResultSpec<'a>) {
919 let range = self
920 .unmaterializables
921 .entry(func.clone())
922 .or_insert_with(|| ResultSpec::has_type(&func.output_type(), true));
923 *range = range.clone().intersect(update);
924 }
925
926 fn eval_result<'b, E>(&self, result: Result<Datum<'b>, E>) -> ResultSpec<'a> {
927 match result {
928 Ok(Datum::Null) => ResultSpec {
929 nullable: true,
930 ..ResultSpec::nothing()
931 },
932 Ok(d) => ResultSpec {
933 values: Values::just(self.arena.make_datum(|packer| packer.push(d))),
934 ..ResultSpec::nothing()
935 },
936 Err(_) => ResultSpec {
937 fallible: true,
938 ..ResultSpec::nothing()
939 },
940 }
941 }
942
943 fn set_literal(expr: &mut MirScalarExpr, update: Result<Datum, EvalError>) {
944 match expr {
945 MirScalarExpr::Literal(literal, col_type) => match update {
946 Err(error) => *literal = Err(error),
947 Ok(datum) => {
948 assert!(
949 datum.is_instance_of(col_type),
950 "{datum:?} must be an instance of {col_type:?}"
951 );
952 match literal {
953 Ok(row) => row.packer().push(datum),
955 literal => *literal = Ok(Row::pack_slice(&[datum])),
956 }
957 }
958 },
959 _ => panic!("not a literal"),
960 }
961 }
962
963 fn set_argument(expr: &mut MirScalarExpr, arg: usize, value: Result<Datum, EvalError>) {
964 match (expr, arg) {
965 (MirScalarExpr::CallUnary { expr, .. }, 0) => Self::set_literal(expr, value),
966 (MirScalarExpr::CallBinary { expr1, .. }, 0) => Self::set_literal(expr1, value),
967 (MirScalarExpr::CallBinary { expr2, .. }, 1) => Self::set_literal(expr2, value),
968 (MirScalarExpr::CallVariadic { exprs, .. }, n) if n < exprs.len() => {
969 Self::set_literal(&mut exprs[n], value)
970 }
971 _ => panic!("illegal argument for expression"),
972 }
973 }
974
975 fn placeholder(col_type: ReprColumnType) -> MirScalarExpr {
979 MirScalarExpr::Literal(Err(EvalError::Internal("".into())), col_type)
980 }
981}
982
983impl<'a> Interpreter for ColumnSpecs<'a> {
984 type Summary = ColumnSpec<'a>;
985
986 fn column(&self, id: usize) -> Self::Summary {
987 let col_type = self.relation.column_types[id].clone();
988 let range = self.columns[id].clone();
989 ColumnSpec { col_type, range }
990 }
991
992 fn literal(&self, result: &Result<Row, EvalError>, col_type: &ReprColumnType) -> Self::Summary {
993 let col_type = col_type.clone();
994 let range = self.eval_result(result.as_ref().map(|row| {
995 self.arena
996 .make_datum(|packer| packer.push(row.unpack_first()))
997 }));
998 ColumnSpec { col_type, range }
999 }
1000
1001 fn unmaterializable(&self, func: &UnmaterializableFunc) -> Self::Summary {
1002 let col_type = func.output_type();
1003 let range = self
1004 .unmaterializables
1005 .get(func)
1006 .cloned()
1007 .unwrap_or_else(|| ResultSpec::has_type(&func.output_type(), true));
1008 ColumnSpec { col_type, range }
1009 }
1010
1011 fn unary(&self, func: &UnaryFunc, summary: Self::Summary) -> Self::Summary {
1012 let fallible = func.could_error() || summary.range.fallible;
1013 let input_multivalued = !summary.range.is_single_value();
1021 let mapped_spec = if let Some(special) = SpecialUnary::for_func(func) {
1022 (special.map_fn)(self, summary.range)
1023 } else {
1024 let is_monotone = func.is_monotone();
1025 let mut expr = MirScalarExpr::CallUnary {
1026 func: func.clone(),
1027 expr: Box::new(Self::placeholder(summary.col_type.clone())),
1028 };
1029 summary.range.flat_map(is_monotone, |datum| {
1030 Self::set_argument(&mut expr, 0, datum);
1031 self.eval_result(expr.eval(&[], self.arena))
1032 })
1033 };
1034
1035 let col_type = func.output_type(summary.col_type);
1036
1037 let mut range = mapped_spec.intersect(ResultSpec::has_type(&col_type, fallible));
1038 if fallible && input_multivalued {
1042 range.fallible = true;
1043 }
1044 ColumnSpec { col_type, range }
1045 }
1046
1047 fn binary(
1048 &self,
1049 func: &BinaryFunc,
1050 left: Self::Summary,
1051 right: Self::Summary,
1052 ) -> Self::Summary {
1053 let fallible = func.could_error() || left.range.fallible || right.range.fallible;
1054 let inputs_multivalued = !left.range.is_single_value() || !right.range.is_single_value();
1057 let operand_may_be_infinite = left.range.may_be_infinite() || right.range.may_be_infinite();
1058
1059 let special = AbstractFunc::for_func(func);
1060 let (left_monotonic, right_monotonic) = match &special {
1061 Some(AbstractFunc {
1062 handler: AbstractFuncHandler::DynamicMonotone(monotone_fn),
1063 ..
1064 }) => monotone_fn(&left.range, &right.range),
1065 _ => func.is_monotone(),
1066 };
1067
1068 let mapped_spec = match special {
1069 Some(AbstractFunc {
1070 handler: AbstractFuncHandler::Override(f),
1071 ..
1072 }) => f(left.range, right.range),
1073 _ => {
1074 let mut expr = MirScalarExpr::CallBinary {
1075 func: func.clone(),
1076 expr1: Box::new(Self::placeholder(left.col_type.clone())),
1077 expr2: Box::new(Self::placeholder(right.col_type.clone())),
1078 };
1079 left.range.flat_map(left_monotonic, |left_result| {
1080 Self::set_argument(&mut expr, 0, left_result);
1081 right.range.flat_map(right_monotonic, |right_result| {
1082 Self::set_argument(&mut expr, 1, right_result);
1083 self.eval_result(expr.eval(&[], self.arena))
1084 })
1085 })
1086 }
1087 };
1088
1089 let col_type = func.output_type(&[left.col_type, right.col_type]);
1090
1091 let mut range = mapped_spec.intersect(ResultSpec::has_type(&col_type, fallible));
1092 if fallible && inputs_multivalued {
1095 range.fallible = true;
1096 }
1097 if operand_may_be_infinite && !func.is_infinity_monotone() {
1105 range.values = Values::All;
1106 }
1107 ColumnSpec { col_type, range }
1108 }
1109
1110 fn variadic(&self, func: &VariadicFunc, args: Vec<Self::Summary>) -> Self::Summary {
1111 let fallible = func.could_error() || args.iter().any(|s| s.range.fallible);
1112 let inputs_multivalued = args.iter().any(|s| !s.range.is_single_value());
1113 if func.is_associative() && args.len() > 2 {
1114 return args
1117 .into_iter()
1118 .reduce(|a, b| self.variadic(func, vec![a, b]))
1119 .expect("reducing over a non-empty argument list");
1120 }
1121
1122 let mapped_spec = if args.len() >= Self::MAX_EVAL_ARGS {
1123 ResultSpec::anything()
1124 } else {
1125 fn eval_loop<'a>(
1126 is_monotonic: bool,
1127 expr: &mut MirScalarExpr,
1128 args: &[ColumnSpec<'a>],
1129 index: usize,
1130 datum_map: &mut impl FnMut(&MirScalarExpr) -> ResultSpec<'a>,
1131 ) -> ResultSpec<'a> {
1132 if index >= args.len() {
1133 datum_map(expr)
1134 } else {
1135 args[index].range.flat_map(is_monotonic, |datum| {
1136 ColumnSpecs::set_argument(expr, index, datum);
1137 eval_loop(is_monotonic, expr, args, index + 1, datum_map)
1138 })
1139 }
1140 }
1141
1142 let mut fn_expr = MirScalarExpr::CallVariadic {
1143 func: func.clone(),
1144 exprs: args
1145 .iter()
1146 .map(|spec| Self::placeholder(spec.col_type.clone()))
1147 .collect(),
1148 };
1149 eval_loop(func.is_monotone(), &mut fn_expr, &args, 0, &mut |expr| {
1150 self.eval_result(expr.eval(&[], self.arena))
1151 })
1152 };
1153
1154 let col_types = args.into_iter().map(|spec| spec.col_type).collect();
1155 let col_type = func.output_type(col_types);
1156
1157 let mut range = mapped_spec.intersect(ResultSpec::has_type(&col_type, fallible));
1158 if fallible && inputs_multivalued {
1161 range.fallible = true;
1162 }
1163
1164 ColumnSpec { col_type, range }
1165 }
1166
1167 fn cond(&self, cond: Self::Summary, then: Self::Summary, els: Self::Summary) -> Self::Summary {
1168 let col_type = then
1169 .col_type
1170 .union(&els.col_type)
1171 .expect("failed type union for cond during abstract interpretation");
1172
1173 let range = cond
1174 .range
1175 .flat_map(true, |datum| match datum {
1176 Ok(Datum::True) => then.range.clone(),
1177 Ok(Datum::False) | Ok(Datum::Null) => els.range.clone(),
1182 _ => ResultSpec::fails(),
1183 })
1184 .intersect(ResultSpec::has_type(&col_type, true));
1185
1186 ColumnSpec { col_type, range }
1187 }
1188
1189 fn mfp_filter(&self, mfp: &MapFilterProject) -> Self::Summary {
1203 let mfp_eval = MfpEval::new(self, mfp.input_arity, &mfp.expressions);
1204 let predicates = mfp
1205 .predicates
1206 .iter()
1207 .map(|(_, e)| mfp_eval.expr(e))
1208 .collect();
1209 let mut result = self.variadic(&And.into(), predicates);
1210 if mfp_eval.expressions.iter().any(|s| s.range.fallible) {
1211 result.range.fallible = true;
1212 }
1213 result
1214 }
1215
1216 fn mfp_plan_filter(&self, plan: &MfpPlan) -> Self::Summary {
1217 let mfp_eval = MfpEval::new(self, plan.mfp.input_arity, &plan.mfp.expressions);
1218 let mut results: Vec<_> = plan
1219 .mfp
1220 .predicates
1221 .iter()
1222 .map(|(_, e)| mfp_eval.expr(e))
1223 .collect();
1224 let mz_now = mfp_eval.unmaterializable(&UnmaterializableFunc::MzNow);
1225 for bound in &plan.lower_bounds {
1226 let bound_range = mfp_eval.expr(bound);
1227 let result = mfp_eval.binary(&BinaryFunc::Lte(func::Lte), bound_range, mz_now.clone());
1228 results.push(result);
1229 }
1230 for bound in &plan.upper_bounds {
1231 let bound_range = mfp_eval.expr(bound);
1232 let result = mfp_eval.binary(&BinaryFunc::Gte(func::Gte), bound_range, mz_now.clone());
1233 results.push(result);
1234 }
1235 let mut result = self.variadic(&And.into(), results);
1236 if mfp_eval.expressions.iter().any(|s| s.range.fallible) {
1237 result.range.fallible = true;
1238 }
1239 result
1240 }
1241}
1242
1243#[derive(Debug)]
1252pub struct Trace;
1253
1254#[derive(Copy, Clone, Debug, PartialOrd, PartialEq, Ord, Eq)]
1261pub enum TraceSummary {
1262 Constant,
1265 Dynamic,
1270 Unknown,
1273}
1274
1275impl TraceSummary {
1276 fn apply_fn(self, pushdownable: bool) -> Self {
1281 match self {
1282 TraceSummary::Constant => TraceSummary::Constant,
1283 TraceSummary::Dynamic => match pushdownable {
1284 true => TraceSummary::Dynamic,
1285 false => TraceSummary::Unknown,
1286 },
1287 TraceSummary::Unknown => TraceSummary::Unknown,
1288 }
1289 }
1290
1291 pub fn pushdownable(self) -> bool {
1293 match self {
1294 TraceSummary::Constant | TraceSummary::Dynamic => true,
1295 TraceSummary::Unknown => false,
1296 }
1297 }
1298}
1299
1300impl Interpreter for Trace {
1301 type Summary = TraceSummary;
1302
1303 fn column(&self, _id: usize) -> Self::Summary {
1304 TraceSummary::Dynamic
1305 }
1306
1307 fn literal(
1308 &self,
1309 _result: &Result<Row, EvalError>,
1310 _col_type: &ReprColumnType,
1311 ) -> Self::Summary {
1312 TraceSummary::Constant
1313 }
1314
1315 fn unmaterializable(&self, _func: &UnmaterializableFunc) -> Self::Summary {
1316 TraceSummary::Dynamic
1317 }
1318
1319 fn unary(&self, func: &UnaryFunc, expr: Self::Summary) -> Self::Summary {
1320 let pushdownable = match SpecialUnary::for_func(func) {
1321 None => func.is_monotone(),
1322 Some(special) => special.pushdownable,
1323 };
1324 expr.apply_fn(pushdownable)
1325 }
1326
1327 fn binary(
1328 &self,
1329 func: &BinaryFunc,
1330 left: Self::Summary,
1331 right: Self::Summary,
1332 ) -> Self::Summary {
1333 let (left_pushdownable, right_pushdownable) = match AbstractFunc::for_func(func) {
1334 None => func.is_monotone(),
1335 Some(special) => special.pushdownable,
1336 };
1337 left.apply_fn(left_pushdownable)
1338 .max(right.apply_fn(right_pushdownable))
1339 }
1340
1341 fn variadic(&self, func: &VariadicFunc, exprs: Vec<Self::Summary>) -> Self::Summary {
1342 if !func.is_associative() && exprs.len() >= ColumnSpecs::MAX_EVAL_ARGS {
1343 return TraceSummary::Unknown;
1346 }
1347
1348 let pushdownable_fn = func.is_monotone();
1349 exprs
1350 .into_iter()
1351 .map(|pushdownable_arg| pushdownable_arg.apply_fn(pushdownable_fn))
1352 .max()
1353 .unwrap_or(TraceSummary::Constant)
1354 }
1355
1356 fn cond(&self, cond: Self::Summary, then: Self::Summary, els: Self::Summary) -> Self::Summary {
1357 let cond = cond.min(TraceSummary::Dynamic);
1360 cond.max(then).max(els)
1361 }
1362}
1363
1364#[cfg(test)]
1365mod tests {
1366 use itertools::Itertools;
1367 use mz_repr::adt::datetime::DateTimeUnits;
1368 use mz_repr::{Datum, PropDatum, RowArena, SqlScalarType};
1369 use proptest::prelude::*;
1370 use proptest::sample::{Index, select};
1371
1372 use crate::func::*;
1373 use crate::scalar::func::variadic::Concat;
1374 use crate::{BinaryFunc, MirScalarExpr, UnaryFunc};
1375
1376 use super::*;
1377
1378 #[derive(Debug)]
1379 struct ExpressionData {
1380 relation_type: ReprRelationType,
1381 specs: Vec<ResultSpec<'static>>,
1382 rows: Vec<Row>,
1383 expr: MirScalarExpr,
1384 }
1385
1386 const NUM_TYPE: ReprScalarType = ReprScalarType::Numeric;
1391 static SCALAR_TYPES: &[ReprScalarType] = &[
1392 ReprScalarType::Bool,
1393 ReprScalarType::Jsonb,
1394 NUM_TYPE,
1395 ReprScalarType::Int16,
1396 ReprScalarType::Int32,
1397 ReprScalarType::Int64,
1398 ReprScalarType::UInt16,
1399 ReprScalarType::UInt32,
1400 ReprScalarType::UInt64,
1401 ReprScalarType::Float32,
1402 ReprScalarType::Float64,
1403 ReprScalarType::Date,
1404 ReprScalarType::Time,
1405 ReprScalarType::Timestamp,
1406 ReprScalarType::TimestampTz,
1407 ReprScalarType::MzTimestamp,
1408 ReprScalarType::Interval,
1409 ReprScalarType::String,
1410 ];
1411
1412 const INTERESTING_UNARY_FUNCS: &[UnaryFunc] = {
1413 &[
1414 UnaryFunc::CastNumericToMzTimestamp(CastNumericToMzTimestamp),
1415 UnaryFunc::CastTimestampToMzTimestamp(CastTimestampToMzTimestamp),
1416 UnaryFunc::NegNumeric(NegNumeric),
1417 UnaryFunc::NegFloat64(NegFloat64),
1418 UnaryFunc::CastJsonbToNumeric(CastJsonbToNumeric(None)),
1419 UnaryFunc::CastJsonbToBool(CastJsonbToBool),
1420 UnaryFunc::CastJsonbToString(CastJsonbToString),
1421 UnaryFunc::DateTruncTimestamp(DateTruncTimestamp(DateTimeUnits::Epoch)),
1422 UnaryFunc::ExtractTimestamp(ExtractTimestamp(DateTimeUnits::Epoch)),
1423 UnaryFunc::ExtractDate(ExtractDate(DateTimeUnits::Epoch)),
1424 UnaryFunc::Not(Not),
1425 UnaryFunc::IsNull(IsNull),
1426 UnaryFunc::IsFalse(IsFalse),
1427 UnaryFunc::TryParseMonotonicIso8601Timestamp(TryParseMonotonicIso8601Timestamp),
1428 UnaryFunc::NegInt32(NegInt32),
1433 UnaryFunc::NegInt64(NegInt64),
1434 UnaryFunc::CastInt32ToUint32(CastInt32ToUint32),
1435 UnaryFunc::CastInt64ToInt32(CastInt64ToInt32),
1436 UnaryFunc::CastInt64ToNumeric(CastInt64ToNumeric(None)),
1437 UnaryFunc::CastFloat64ToInt64(CastFloat64ToInt64),
1438 UnaryFunc::CastFloat64ToFloat32(CastFloat64ToFloat32),
1439 UnaryFunc::CastFloat32ToFloat64(CastFloat32ToFloat64),
1440 UnaryFunc::CastNumericToInt64(CastNumericToInt64),
1441 UnaryFunc::CeilNumeric(CeilNumeric),
1442 UnaryFunc::FloorNumeric(FloorNumeric),
1443 UnaryFunc::CastDateToTimestamp(CastDateToTimestamp(None)),
1444 UnaryFunc::CastTimestampToTimestampTz(CastTimestampToTimestampTz {
1445 from: None,
1446 to: None,
1447 }),
1448 UnaryFunc::CastTimestampTzToTimestamp(CastTimestampTzToTimestamp {
1449 from: None,
1450 to: None,
1451 }),
1452 UnaryFunc::ExtractTimestamp(ExtractTimestamp(DateTimeUnits::Year)),
1455 UnaryFunc::ExtractTimestamp(ExtractTimestamp(DateTimeUnits::Month)),
1456 UnaryFunc::ExtractTimestampTz(ExtractTimestampTz(DateTimeUnits::Epoch)),
1457 UnaryFunc::ExtractTimestampTz(ExtractTimestampTz(DateTimeUnits::Year)),
1458 UnaryFunc::CastBoolToInt32(CastBoolToInt32),
1462 UnaryFunc::CastBoolToString(CastBoolToString),
1463 UnaryFunc::NegInt16(NegInt16),
1464 UnaryFunc::CastInt16ToInt32(CastInt16ToInt32),
1465 UnaryFunc::CastInt16ToInt64(CastInt16ToInt64),
1466 UnaryFunc::CastInt16ToFloat32(CastInt16ToFloat32),
1467 UnaryFunc::CastInt16ToFloat64(CastInt16ToFloat64),
1468 UnaryFunc::CastInt16ToUint16(CastInt16ToUint16),
1469 UnaryFunc::CastInt16ToNumeric(CastInt16ToNumeric(None)),
1470 UnaryFunc::CastInt32ToInt16(CastInt32ToInt16),
1471 UnaryFunc::CastInt32ToInt64(CastInt32ToInt64),
1472 UnaryFunc::CastInt32ToFloat32(CastInt32ToFloat32),
1473 UnaryFunc::CastInt32ToFloat64(CastInt32ToFloat64),
1474 UnaryFunc::CastInt32ToUint16(CastInt32ToUint16),
1475 UnaryFunc::CastInt32ToNumeric(CastInt32ToNumeric(None)),
1476 UnaryFunc::CastInt32ToMzTimestamp(CastInt32ToMzTimestamp),
1477 UnaryFunc::CastInt64ToInt16(CastInt64ToInt16),
1478 UnaryFunc::CastInt64ToFloat32(CastInt64ToFloat32),
1479 UnaryFunc::CastInt64ToFloat64(CastInt64ToFloat64),
1480 UnaryFunc::CastInt64ToUint64(CastInt64ToUint64),
1481 UnaryFunc::CastInt64ToMzTimestamp(CastInt64ToMzTimestamp),
1482 UnaryFunc::CastUint64ToUint32(CastUint64ToUint32),
1483 UnaryFunc::CastUint64ToInt32(CastUint64ToInt32),
1484 UnaryFunc::CastUint64ToNumeric(CastUint64ToNumeric(None)),
1485 UnaryFunc::CastUint64ToMzTimestamp(CastUint64ToMzTimestamp),
1486 UnaryFunc::NegFloat32(NegFloat32),
1487 UnaryFunc::FloorFloat32(FloorFloat32),
1488 UnaryFunc::CastFloat32ToInt32(CastFloat32ToInt32),
1489 UnaryFunc::CastFloat32ToNumeric(CastFloat32ToNumeric(None)),
1490 UnaryFunc::FloorFloat64(FloorFloat64),
1491 UnaryFunc::CastFloat64ToInt32(CastFloat64ToInt32),
1492 UnaryFunc::CastFloat64ToUint64(CastFloat64ToUint64),
1493 UnaryFunc::CastFloat64ToNumeric(CastFloat64ToNumeric(None)),
1494 UnaryFunc::RoundNumeric(RoundNumeric),
1495 UnaryFunc::TruncNumeric(TruncNumeric),
1496 UnaryFunc::Log10Numeric(Log10Numeric),
1497 UnaryFunc::CastNumericToFloat64(CastNumericToFloat64),
1498 UnaryFunc::CastNumericToInt32(CastNumericToInt32),
1499 UnaryFunc::CastTimestampToDate(CastTimestampToDate),
1500 UnaryFunc::CastDateToMzTimestamp(CastDateToMzTimestamp),
1501 UnaryFunc::StepMzTimestamp(StepMzTimestamp),
1502 UnaryFunc::CastBoolToStringNonstandard(CastBoolToStringNonstandard),
1506 UnaryFunc::CastBoolToInt64(CastBoolToInt64),
1507 UnaryFunc::CastInt16ToUint32(CastInt16ToUint32),
1508 UnaryFunc::CastInt16ToUint64(CastInt16ToUint64),
1509 UnaryFunc::CastInt32ToUint64(CastInt32ToUint64),
1510 UnaryFunc::CastInt64ToUint16(CastInt64ToUint16),
1511 UnaryFunc::CastInt64ToUint32(CastInt64ToUint32),
1512 UnaryFunc::CastUint16ToUint32(CastUint16ToUint32),
1513 UnaryFunc::CastUint16ToUint64(CastUint16ToUint64),
1514 UnaryFunc::CastUint16ToInt16(CastUint16ToInt16),
1515 UnaryFunc::CastUint16ToInt32(CastUint16ToInt32),
1516 UnaryFunc::CastUint16ToFloat32(CastUint16ToFloat32),
1517 UnaryFunc::CastUint16ToFloat64(CastUint16ToFloat64),
1518 UnaryFunc::CastUint16ToNumeric(CastUint16ToNumeric(None)),
1519 UnaryFunc::CastUint16ToInt64(CastUint16ToInt64),
1520 UnaryFunc::BitNotUint16(BitNotUint16),
1521 UnaryFunc::CastUint32ToUint16(CastUint32ToUint16),
1522 UnaryFunc::CastUint32ToUint64(CastUint32ToUint64),
1523 UnaryFunc::CastUint32ToInt32(CastUint32ToInt32),
1524 UnaryFunc::CastUint32ToInt64(CastUint32ToInt64),
1525 UnaryFunc::CastUint32ToFloat32(CastUint32ToFloat32),
1526 UnaryFunc::CastUint32ToFloat64(CastUint32ToFloat64),
1527 UnaryFunc::CastUint32ToNumeric(CastUint32ToNumeric(None)),
1528 UnaryFunc::CastUint32ToInt16(CastUint32ToInt16),
1529 UnaryFunc::CastUint32ToMzTimestamp(CastUint32ToMzTimestamp),
1530 UnaryFunc::BitNotUint32(BitNotUint32),
1531 UnaryFunc::CastUint64ToUint16(CastUint64ToUint16),
1532 UnaryFunc::CastUint64ToInt16(CastUint64ToInt16),
1533 UnaryFunc::CastUint64ToInt64(CastUint64ToInt64),
1534 UnaryFunc::CastUint64ToFloat32(CastUint64ToFloat32),
1535 UnaryFunc::CastUint64ToFloat64(CastUint64ToFloat64),
1536 UnaryFunc::BitNotUint64(BitNotUint64),
1537 UnaryFunc::CastFloat32ToInt16(CastFloat32ToInt16),
1538 UnaryFunc::CastFloat32ToInt64(CastFloat32ToInt64),
1539 UnaryFunc::CastFloat32ToUint16(CastFloat32ToUint16),
1540 UnaryFunc::CastFloat32ToUint32(CastFloat32ToUint32),
1541 UnaryFunc::CastFloat32ToUint64(CastFloat32ToUint64),
1542 UnaryFunc::CastFloat64ToInt16(CastFloat64ToInt16),
1543 UnaryFunc::CastFloat64ToUint16(CastFloat64ToUint16),
1544 UnaryFunc::CastFloat64ToUint32(CastFloat64ToUint32),
1545 UnaryFunc::CastJsonbToInt16(CastJsonbToInt16),
1546 UnaryFunc::CastJsonbToInt32(CastJsonbToInt32),
1547 UnaryFunc::CastJsonbToInt64(CastJsonbToInt64),
1548 UnaryFunc::CastJsonbToFloat32(CastJsonbToFloat32),
1549 UnaryFunc::CastJsonbToFloat64(CastJsonbToFloat64),
1550 UnaryFunc::CastNumericToInt16(CastNumericToInt16),
1551 UnaryFunc::CastNumericToFloat32(CastNumericToFloat32),
1552 UnaryFunc::CastNumericToUint16(CastNumericToUint16),
1553 UnaryFunc::CastNumericToUint32(CastNumericToUint32),
1554 UnaryFunc::CastNumericToUint64(CastNumericToUint64),
1555 UnaryFunc::CastTimestampTzToDate(CastTimestampTzToDate),
1556 UnaryFunc::CastTimestampTzToMzTimestamp(CastTimestampTzToMzTimestamp),
1557 UnaryFunc::DateTruncTimestampTz(DateTruncTimestampTz(DateTimeUnits::Epoch)),
1558 UnaryFunc::CastDateToTimestampTz(CastDateToTimestampTz(None)),
1559 UnaryFunc::ExtractDate(ExtractDate(DateTimeUnits::Year)),
1560 UnaryFunc::ExtractDate(ExtractDate(DateTimeUnits::Day)),
1561 ]
1562 };
1563
1564 fn unary_typecheck(func: &UnaryFunc, arg: &ReprColumnType) -> bool {
1565 use UnaryFunc::*;
1566 match func {
1567 CastNumericToMzTimestamp(_)
1568 | NegNumeric(_)
1569 | CastNumericToInt64(_)
1570 | CeilNumeric(_)
1571 | FloorNumeric(_)
1572 | RoundNumeric(_)
1573 | TruncNumeric(_)
1574 | Log10Numeric(_)
1575 | CastNumericToFloat64(_)
1576 | CastNumericToInt32(_)
1577 | CastNumericToInt16(_)
1578 | CastNumericToFloat32(_)
1579 | CastNumericToUint16(_)
1580 | CastNumericToUint32(_)
1581 | CastNumericToUint64(_) => arg.scalar_type == NUM_TYPE,
1582 NegFloat64(_)
1583 | CastFloat64ToInt64(_)
1584 | CastFloat64ToFloat32(_)
1585 | FloorFloat64(_)
1586 | CastFloat64ToInt32(_)
1587 | CastFloat64ToUint64(_)
1588 | CastFloat64ToNumeric(_)
1589 | CastFloat64ToInt16(_)
1590 | CastFloat64ToUint16(_)
1591 | CastFloat64ToUint32(_) => arg.scalar_type == ReprScalarType::Float64,
1592 CastFloat32ToFloat64(_)
1593 | NegFloat32(_)
1594 | FloorFloat32(_)
1595 | CastFloat32ToInt32(_)
1596 | CastFloat32ToNumeric(_)
1597 | CastFloat32ToInt16(_)
1598 | CastFloat32ToInt64(_)
1599 | CastFloat32ToUint16(_)
1600 | CastFloat32ToUint32(_)
1601 | CastFloat32ToUint64(_) => arg.scalar_type == ReprScalarType::Float32,
1602 NegInt16(_)
1603 | CastInt16ToInt32(_)
1604 | CastInt16ToInt64(_)
1605 | CastInt16ToFloat32(_)
1606 | CastInt16ToFloat64(_)
1607 | CastInt16ToUint16(_)
1608 | CastInt16ToNumeric(_)
1609 | CastInt16ToUint32(_)
1610 | CastInt16ToUint64(_) => arg.scalar_type == ReprScalarType::Int16,
1611 NegInt32(_)
1612 | CastInt32ToUint32(_)
1613 | CastInt32ToInt16(_)
1614 | CastInt32ToInt64(_)
1615 | CastInt32ToFloat32(_)
1616 | CastInt32ToFloat64(_)
1617 | CastInt32ToUint16(_)
1618 | CastInt32ToNumeric(_)
1619 | CastInt32ToMzTimestamp(_)
1620 | CastInt32ToUint64(_) => arg.scalar_type == ReprScalarType::Int32,
1621 NegInt64(_)
1622 | CastInt64ToInt32(_)
1623 | CastInt64ToNumeric(_)
1624 | CastInt64ToInt16(_)
1625 | CastInt64ToFloat32(_)
1626 | CastInt64ToFloat64(_)
1627 | CastInt64ToUint64(_)
1628 | CastInt64ToMzTimestamp(_)
1629 | CastInt64ToUint16(_)
1630 | CastInt64ToUint32(_) => arg.scalar_type == ReprScalarType::Int64,
1631 CastUint16ToUint32(_)
1632 | CastUint16ToUint64(_)
1633 | CastUint16ToInt16(_)
1634 | CastUint16ToInt32(_)
1635 | CastUint16ToFloat32(_)
1636 | CastUint16ToFloat64(_)
1637 | CastUint16ToNumeric(_)
1638 | CastUint16ToInt64(_)
1639 | BitNotUint16(_) => arg.scalar_type == ReprScalarType::UInt16,
1640 CastUint32ToUint16(_)
1641 | CastUint32ToUint64(_)
1642 | CastUint32ToInt32(_)
1643 | CastUint32ToInt64(_)
1644 | CastUint32ToFloat32(_)
1645 | CastUint32ToFloat64(_)
1646 | CastUint32ToNumeric(_)
1647 | CastUint32ToInt16(_)
1648 | CastUint32ToMzTimestamp(_)
1649 | BitNotUint32(_) => arg.scalar_type == ReprScalarType::UInt32,
1650 CastUint64ToUint32(_)
1651 | CastUint64ToInt32(_)
1652 | CastUint64ToNumeric(_)
1653 | CastUint64ToMzTimestamp(_)
1654 | CastUint64ToUint16(_)
1655 | CastUint64ToInt16(_)
1656 | CastUint64ToInt64(_)
1657 | CastUint64ToFloat32(_)
1658 | CastUint64ToFloat64(_)
1659 | BitNotUint64(_) => arg.scalar_type == ReprScalarType::UInt64,
1660 StepMzTimestamp(_) => arg.scalar_type == ReprScalarType::MzTimestamp,
1661 CastBoolToInt32(_)
1662 | CastBoolToString(_)
1663 | CastBoolToStringNonstandard(_)
1664 | CastBoolToInt64(_) => arg.scalar_type == ReprScalarType::Bool,
1665 CastTimestampToMzTimestamp(_)
1666 | CastTimestampToTimestampTz(_)
1667 | CastTimestampToDate(_) => arg.scalar_type == ReprScalarType::Timestamp,
1668 CastTimestampTzToTimestamp(_)
1669 | ExtractTimestampTz(_)
1670 | CastTimestampTzToDate(_)
1671 | CastTimestampTzToMzTimestamp(_)
1672 | DateTruncTimestampTz(_) => arg.scalar_type == ReprScalarType::TimestampTz,
1673 CastJsonbToNumeric(_)
1674 | CastJsonbToBool(_)
1675 | CastJsonbToString(_)
1676 | CastJsonbToInt16(_)
1677 | CastJsonbToInt32(_)
1678 | CastJsonbToInt64(_)
1679 | CastJsonbToFloat32(_)
1680 | CastJsonbToFloat64(_) => arg.scalar_type == ReprScalarType::Jsonb,
1681 ExtractTimestamp(_) | DateTruncTimestamp(_) => {
1682 arg.scalar_type == ReprScalarType::Timestamp
1683 }
1684 ExtractDate(_)
1685 | CastDateToTimestamp(_)
1686 | CastDateToMzTimestamp(_)
1687 | CastDateToTimestampTz(_) => arg.scalar_type == ReprScalarType::Date,
1688 Not(_) => arg.scalar_type == ReprScalarType::Bool,
1689 IsNull(_) => true,
1690 TryParseMonotonicIso8601Timestamp(_) => arg.scalar_type == ReprScalarType::String,
1691 _ => false,
1692 }
1693 }
1694
1695 fn interesting_binary_funcs() -> Vec<BinaryFunc> {
1696 vec![
1697 AddTimestampInterval.into(),
1698 AddNumeric.into(),
1699 SubNumeric.into(),
1700 MulNumeric.into(),
1701 DivNumeric.into(),
1702 AddFloat64.into(),
1703 SubFloat64.into(),
1704 MulFloat64.into(),
1705 DivFloat64.into(),
1706 MulFloat32.into(),
1707 DivFloat32.into(),
1708 RoundNumericBinary.into(),
1709 Eq.into(),
1710 Lt.into(),
1711 Gt.into(),
1712 Lte.into(),
1713 Gte.into(),
1714 DateTruncUnitsTimestamp.into(),
1715 JsonbGetString.into(),
1716 JsonbGetStringStringify.into(),
1717 AddInt32.into(),
1721 SubInt32.into(),
1722 MulInt32.into(),
1723 DivInt32.into(),
1724 AddInt64.into(),
1725 MulInt64.into(),
1726 AddFloat32.into(),
1727 SubFloat32.into(),
1728 TextConcatBinary.into(),
1730 AddDateInterval.into(),
1732 AddTimeInterval.into(),
1733 SubInt64.into(),
1735 DivInt64.into(),
1736 SubTimestamp.into(),
1737 SubDate.into(),
1738 AddInterval.into(),
1739 SubInterval.into(),
1740 AddInt16.into(),
1743 SubInt16.into(),
1744 MulInt16.into(),
1745 DivInt16.into(),
1746 AddUint16.into(),
1747 SubUint16.into(),
1748 MulUint16.into(),
1749 DivUint16.into(),
1750 AddUint32.into(),
1751 SubUint32.into(),
1752 MulUint32.into(),
1753 DivUint32.into(),
1754 AddUint64.into(),
1755 SubUint64.into(),
1756 MulUint64.into(),
1757 DivUint64.into(),
1758 SubTime.into(),
1759 SubTimestampTz.into(),
1760 AddDateTime.into(),
1761 SubDateInterval.into(),
1762 DateBinTimestamp.into(),
1763 ]
1764 }
1765
1766 fn binary_typecheck(func: &BinaryFunc, arg0: &ReprColumnType, arg1: &ReprColumnType) -> bool {
1767 use BinaryFunc::*;
1768 match func {
1769 AddTimestampInterval(_) => {
1770 arg0.scalar_type == ReprScalarType::Timestamp
1771 && arg1.scalar_type == ReprScalarType::Interval
1772 }
1773 AddNumeric(_) | SubNumeric(_) | MulNumeric(_) | DivNumeric(_) => {
1774 arg0.scalar_type == NUM_TYPE && arg1.scalar_type == NUM_TYPE
1775 }
1776 AddFloat64(_) | SubFloat64(_) | MulFloat64(_) | DivFloat64(_) => {
1777 arg0.scalar_type == ReprScalarType::Float64
1778 && arg1.scalar_type == ReprScalarType::Float64
1779 }
1780 MulFloat32(_) | DivFloat32(_) => {
1781 arg0.scalar_type == ReprScalarType::Float32
1782 && arg1.scalar_type == ReprScalarType::Float32
1783 }
1784 RoundNumeric(_) => {
1785 arg0.scalar_type == NUM_TYPE && arg1.scalar_type == ReprScalarType::Int32
1786 }
1787 Eq(_) | Lt(_) | Gt(_) | Lte(_) | Gte(_) => arg0.scalar_type == arg1.scalar_type,
1788 DateTruncTimestamp(_) => {
1789 arg0.scalar_type == ReprScalarType::String
1790 && arg1.scalar_type == ReprScalarType::Timestamp
1791 }
1792 JsonbGetString(_) | JsonbGetStringStringify(_) => {
1793 arg0.scalar_type == ReprScalarType::Jsonb
1794 && arg1.scalar_type == ReprScalarType::String
1795 }
1796 AddInt32(_) | SubInt32(_) | MulInt32(_) | DivInt32(_) => {
1797 arg0.scalar_type == ReprScalarType::Int32
1798 && arg1.scalar_type == ReprScalarType::Int32
1799 }
1800 AddInt64(_) | MulInt64(_) | SubInt64(_) | DivInt64(_) => {
1801 arg0.scalar_type == ReprScalarType::Int64
1802 && arg1.scalar_type == ReprScalarType::Int64
1803 }
1804 SubTimestamp(_) => {
1805 arg0.scalar_type == ReprScalarType::Timestamp
1806 && arg1.scalar_type == ReprScalarType::Timestamp
1807 }
1808 SubDate(_) => {
1809 arg0.scalar_type == ReprScalarType::Date && arg1.scalar_type == ReprScalarType::Date
1810 }
1811 AddInterval(_) | SubInterval(_) => {
1812 arg0.scalar_type == ReprScalarType::Interval
1813 && arg1.scalar_type == ReprScalarType::Interval
1814 }
1815 AddInt16(_) | SubInt16(_) | MulInt16(_) | DivInt16(_) => {
1816 arg0.scalar_type == ReprScalarType::Int16
1817 && arg1.scalar_type == ReprScalarType::Int16
1818 }
1819 AddUint16(_) | SubUint16(_) | MulUint16(_) | DivUint16(_) => {
1820 arg0.scalar_type == ReprScalarType::UInt16
1821 && arg1.scalar_type == ReprScalarType::UInt16
1822 }
1823 AddUint32(_) | SubUint32(_) | MulUint32(_) | DivUint32(_) => {
1824 arg0.scalar_type == ReprScalarType::UInt32
1825 && arg1.scalar_type == ReprScalarType::UInt32
1826 }
1827 AddUint64(_) | SubUint64(_) | MulUint64(_) | DivUint64(_) => {
1828 arg0.scalar_type == ReprScalarType::UInt64
1829 && arg1.scalar_type == ReprScalarType::UInt64
1830 }
1831 SubTime(_) => {
1832 arg0.scalar_type == ReprScalarType::Time && arg1.scalar_type == ReprScalarType::Time
1833 }
1834 SubTimestampTz(_) => {
1835 arg0.scalar_type == ReprScalarType::TimestampTz
1836 && arg1.scalar_type == ReprScalarType::TimestampTz
1837 }
1838 AddDateTime(_) => {
1839 arg0.scalar_type == ReprScalarType::Date && arg1.scalar_type == ReprScalarType::Time
1840 }
1841 SubDateInterval(_) => {
1842 arg0.scalar_type == ReprScalarType::Date
1843 && arg1.scalar_type == ReprScalarType::Interval
1844 }
1845 DateBinTimestamp(_) => {
1846 arg0.scalar_type == ReprScalarType::Interval
1847 && arg1.scalar_type == ReprScalarType::Timestamp
1848 }
1849 AddFloat32(_) | SubFloat32(_) => {
1850 arg0.scalar_type == ReprScalarType::Float32
1851 && arg1.scalar_type == ReprScalarType::Float32
1852 }
1853 TextConcat(_) => {
1854 arg0.scalar_type == ReprScalarType::String
1855 && arg1.scalar_type == ReprScalarType::String
1856 }
1857 AddDateInterval(_) => {
1858 arg0.scalar_type == ReprScalarType::Date
1859 && arg1.scalar_type == ReprScalarType::Interval
1860 }
1861 AddTimeInterval(_) => {
1862 arg0.scalar_type == ReprScalarType::Time
1863 && arg1.scalar_type == ReprScalarType::Interval
1864 }
1865 _ => false,
1866 }
1867 }
1868
1869 const INTERESTING_VARIADIC_FUNCS: &[VariadicFunc] = {
1870 use crate::scalar::func::variadic as v;
1871 use VariadicFunc::*;
1872 &[
1873 Coalesce(v::Coalesce),
1874 Greatest(v::Greatest),
1875 Least(v::Least),
1876 And(v::And),
1877 Or(v::Or),
1878 Concat(v::Concat),
1879 ConcatWs(v::ConcatWs),
1880 ]
1881 };
1882
1883 fn variadic_typecheck(func: &VariadicFunc, args: &[ReprColumnType]) -> bool {
1884 use VariadicFunc::*;
1885 fn all_eq<'a>(
1886 iter: impl IntoIterator<Item = &'a ReprColumnType>,
1887 other: &ReprScalarType,
1888 ) -> bool {
1889 iter.into_iter().all(|t| t.scalar_type == *other)
1890 }
1891 match func {
1892 Coalesce(_) | Greatest(_) | Least(_) => match args {
1893 [] => true,
1894 [first, rest @ ..] => all_eq(rest, &first.scalar_type),
1895 },
1896 And(_) | Or(_) => all_eq(args, &ReprScalarType::Bool),
1897 Concat(_) => all_eq(args, &ReprScalarType::String),
1898 ConcatWs(_) => args.len() > 1 && all_eq(args, &ReprScalarType::String),
1899 _ => false,
1900 }
1901 }
1902
1903 fn gen_datums_for_type(typ: &ReprColumnType) -> BoxedStrategy<Datum<'static>> {
1904 let mut values: Vec<Datum<'static>> = SqlScalarType::from_repr(&typ.scalar_type)
1905 .interesting_datums()
1906 .collect();
1907 if typ.nullable {
1908 values.push(Datum::Null)
1909 }
1910 select(values).boxed()
1911 }
1912
1913 fn gen_column() -> impl Strategy<Value = (ReprColumnType, Datum<'static>, ResultSpec<'static>)>
1914 {
1915 let col_type = (select(SCALAR_TYPES), any::<bool>())
1916 .prop_map(|(t, b)| t.nullable(b))
1917 .prop_filter("need at least one value", |c| {
1918 SqlScalarType::from_repr(&c.scalar_type)
1919 .interesting_datums()
1920 .count()
1921 > 0
1922 });
1923
1924 let result_spec = select(vec![
1925 ResultSpec::nothing(),
1926 ResultSpec::null(),
1927 ResultSpec::anything(),
1928 ResultSpec::value_all(),
1929 ]);
1930
1931 (col_type, result_spec).prop_flat_map(|(col, result_spec)| {
1932 gen_datums_for_type(&col).prop_map(move |datum| {
1933 let result_spec = result_spec.clone().union(ResultSpec::value(datum));
1934 (col.clone(), datum, result_spec)
1935 })
1936 })
1937 }
1938
1939 fn gen_expr_for_relation(
1940 relation: &ReprRelationType,
1941 ) -> BoxedStrategy<(MirScalarExpr, ReprColumnType)> {
1942 let column_gen = {
1943 let column_types = relation.column_types.clone();
1944 any::<Index>()
1945 .prop_map(move |idx| {
1946 let id = idx.index(column_types.len());
1947 (MirScalarExpr::column(id), column_types[id].clone())
1948 })
1949 .boxed()
1950 };
1951
1952 let literal_gen = (select(SCALAR_TYPES), any::<bool>())
1953 .prop_map(|(s, b)| s.nullable(b))
1954 .prop_flat_map(|ct| {
1955 let error_gen = any::<EvalError>().prop_map(Err).boxed();
1956 let value_gen = gen_datums_for_type(&ct)
1957 .prop_map(move |datum| Ok(Row::pack_slice(&[datum])))
1958 .boxed();
1959 error_gen.prop_union(value_gen).prop_map(move |result| {
1960 (MirScalarExpr::Literal(result, ct.clone()), ct.clone())
1961 })
1962 })
1963 .boxed();
1964
1965 column_gen
1966 .prop_union(literal_gen)
1967 .prop_recursive(4, 64, 8, |self_gen| {
1968 let unary_gen = (select(INTERESTING_UNARY_FUNCS), self_gen.clone())
1969 .prop_filter_map("unary func", |(func, (expr_in, type_in))| {
1970 if !unary_typecheck(&func, &type_in) {
1971 return None;
1972 }
1973 let type_out = func.output_type(type_in);
1974 let expr_out = MirScalarExpr::CallUnary {
1975 func,
1976 expr: Box::new(expr_in),
1977 };
1978 Some((expr_out, type_out))
1979 })
1980 .boxed();
1981 let binary_gen = (
1982 select(interesting_binary_funcs()),
1983 self_gen.clone(),
1984 self_gen.clone(),
1985 )
1986 .prop_filter_map(
1987 "binary func",
1988 |(func, (expr_left, type_left), (expr_right, type_right))| {
1989 if !binary_typecheck(&func, &type_left, &type_right) {
1990 return None;
1991 }
1992 let type_out = func.output_type(&[type_left, type_right]);
1993 let expr_out = MirScalarExpr::CallBinary {
1994 func,
1995 expr1: Box::new(expr_left),
1996 expr2: Box::new(expr_right),
1997 };
1998 Some((expr_out, type_out))
1999 },
2000 )
2001 .boxed();
2002 let variadic_gen = (
2003 select(INTERESTING_VARIADIC_FUNCS),
2004 prop::collection::vec(self_gen.clone(), 1..4),
2005 )
2006 .prop_filter_map("variadic func", |(func, exprs)| {
2007 let (exprs_in, type_in): (_, Vec<_>) = exprs.into_iter().unzip();
2008 if !variadic_typecheck(&func, &type_in) {
2009 return None;
2010 }
2011 let type_out = func.output_type(type_in);
2012 let expr_out = MirScalarExpr::CallVariadic {
2013 func,
2014 exprs: exprs_in,
2015 };
2016 Some((expr_out, type_out))
2017 })
2018 .boxed();
2019 let if_gen = {
2026 let bool_type = ReprScalarType::Bool.nullable(true);
2027 let cond_gen = gen_datums_for_type(&bool_type).prop_map(move |datum| {
2028 MirScalarExpr::Literal(Ok(Row::pack_slice(&[datum])), bool_type.clone())
2029 });
2030 (cond_gen, self_gen.clone())
2031 .prop_flat_map(|(cond_expr, (then_expr, then_type))| {
2032 let out_type = then_type.clone();
2033 gen_datums_for_type(&then_type).prop_map(move |datum| {
2034 let els_expr = MirScalarExpr::Literal(
2035 Ok(Row::pack_slice(&[datum])),
2036 out_type.clone(),
2037 );
2038 let expr_out = MirScalarExpr::If {
2039 cond: Box::new(cond_expr.clone()),
2040 then: Box::new(then_expr.clone()),
2041 els: Box::new(els_expr),
2042 };
2043 (expr_out, out_type.clone())
2044 })
2045 })
2046 .boxed()
2047 };
2048
2049 unary_gen
2050 .prop_union(binary_gen)
2051 .boxed()
2052 .prop_union(variadic_gen)
2053 .boxed()
2054 .prop_union(if_gen)
2055 })
2056 .boxed()
2057 }
2058
2059 fn gen_expr_data() -> impl Strategy<Value = ExpressionData> {
2060 let columns = prop::collection::vec(gen_column(), 1..10);
2061 columns.prop_flat_map(|data| {
2062 let (columns, datums, specs): (Vec<_>, Vec<_>, Vec<_>) = data.into_iter().multiunzip();
2063 let relation = ReprRelationType::new(columns);
2064 let row = Row::pack_slice(&datums);
2065 gen_expr_for_relation(&relation).prop_map(move |(expr, _)| ExpressionData {
2066 relation_type: relation.clone(),
2067 specs: specs.clone(),
2068 rows: vec![row.clone()],
2069 expr,
2070 })
2071 })
2072 }
2073
2074 #[mz_ore::test]
2075 #[cfg_attr(miri, ignore)] fn test_trivial_spec_matches() {
2077 fn check(datum: PropDatum) -> Result<(), TestCaseError> {
2078 let datum: Datum = (&datum).into();
2079 let spec = if datum.is_null() {
2080 ResultSpec::null()
2081 } else {
2082 ResultSpec::value(datum)
2083 };
2084 assert!(spec.may_contain(datum));
2085 Ok(())
2086 }
2087
2088 proptest!(|(datum in mz_repr::arb_datum(true))| {
2089 check(datum)?;
2090 });
2091
2092 assert!(ResultSpec::fails().may_fail());
2093 }
2094
2095 #[mz_ore::test]
2096 #[cfg_attr(miri, ignore)] fn test_equivalence() {
2098 fn check(data: ExpressionData) -> Result<(), TestCaseError> {
2099 let ExpressionData {
2100 relation_type,
2101 specs,
2102 rows,
2103 expr,
2104 } = data;
2105
2106 let arena = RowArena::new();
2110 let mut interpreter = ColumnSpecs::new(&relation_type, &arena);
2111 for (id, spec) in specs.into_iter().enumerate() {
2112 interpreter.push_column(id, spec);
2113 }
2114
2115 let spec = interpreter.expr(&expr);
2116
2117 for row in &rows {
2118 let datums: Vec<_> = row.iter().collect();
2119 let eval_result = expr.eval(&datums, &arena);
2120 match eval_result {
2121 Ok(value) => {
2122 assert!(spec.range.may_contain(value))
2123 }
2124 Err(_) => {
2125 assert!(spec.range.may_fail());
2126 }
2127 }
2128 }
2129
2130 Ok(())
2131 }
2132
2133 proptest!(|(data in gen_expr_data())| {
2134 check(data)?;
2135 });
2136 }
2137
2138 fn gen_range_column()
2147 -> impl Strategy<Value = (ReprColumnType, Datum<'static>, ResultSpec<'static>)> {
2148 select(SCALAR_TYPES)
2149 .prop_map(|t| t.nullable(false))
2150 .prop_filter("need at least two distinct values for a range", |c| {
2151 let mut datums: Vec<Datum> = SqlScalarType::from_repr(&c.scalar_type)
2152 .interesting_datums()
2153 .filter(|d| !d.is_null())
2154 .collect();
2155 datums.sort();
2156 datums.dedup();
2157 datums.len() >= 2
2158 })
2159 .prop_flat_map(|col| {
2160 let mut datums: Vec<Datum<'static>> = SqlScalarType::from_repr(&col.scalar_type)
2161 .interesting_datums()
2162 .filter(|d| !d.is_null())
2163 .collect();
2164 datums.sort();
2165 datums.dedup();
2166 (
2167 Just(col),
2168 Just(datums),
2169 any::<Index>(),
2170 any::<Index>(),
2171 any::<Index>(),
2172 )
2173 .prop_map(|(col, datums, a, b, c)| {
2174 let n = datums.len();
2175 let mut idxs = [a.index(n), b.index(n), c.index(n)];
2176 idxs.sort();
2177 let lo = datums[idxs[0]];
2178 let mid = datums[idxs[1]];
2179 let hi = datums[idxs[2]];
2180 let spec = ResultSpec::value_between(lo, hi);
2181 (col, mid, spec)
2182 })
2183 })
2184 }
2185
2186 fn gen_range_expr_data() -> impl Strategy<Value = ExpressionData> {
2187 let columns = prop::collection::vec(gen_range_column(), 1..10);
2188 columns.prop_flat_map(|data| {
2189 let (columns, datums, specs): (Vec<_>, Vec<_>, Vec<_>) = data.into_iter().multiunzip();
2190 let relation = ReprRelationType::new(columns);
2191 let row = Row::pack_slice(&datums);
2192 gen_expr_for_relation(&relation).prop_map(move |(expr, _)| ExpressionData {
2193 relation_type: relation.clone(),
2194 specs: specs.clone(),
2195 rows: vec![row.clone()],
2196 expr,
2197 })
2198 })
2199 }
2200
2201 #[mz_ore::test]
2212 #[cfg_attr(miri, ignore)] fn test_equivalence_ranges() {
2214 fn check(data: ExpressionData) -> Result<(), TestCaseError> {
2215 let ExpressionData {
2216 relation_type,
2217 specs,
2218 rows,
2219 expr,
2220 } = data;
2221
2222 let arena = RowArena::new();
2223 let mut interpreter = ColumnSpecs::new(&relation_type, &arena);
2224 for (id, spec) in specs.into_iter().enumerate() {
2225 interpreter.push_column(id, spec);
2226 }
2227
2228 let spec = interpreter.expr(&expr);
2229
2230 for row in &rows {
2231 let datums: Vec<_> = row.iter().collect();
2232 let eval_result = expr.eval(&datums, &arena);
2233 match eval_result {
2234 Ok(value) => {
2235 prop_assert!(
2236 spec.range.may_contain(value),
2237 "interpreter ruled out a value the evaluator produced \
2238 for an interior input: expr={expr:?} row={row:?} \
2239 value={value:?} spec={:?}",
2240 spec.range,
2241 );
2242 }
2243 Err(_) => {
2244 prop_assert!(
2245 spec.range.may_fail(),
2246 "interpreter ruled out an error the evaluator produced \
2247 for an interior input: expr={expr:?} row={row:?}",
2248 );
2249 }
2250 }
2251 }
2252
2253 Ok(())
2254 }
2255
2256 let default = ProptestConfig::default();
2265 let cases = if std::env::var_os("PROPTEST_CASES").is_some() {
2266 default.cases
2267 } else {
2268 2048
2269 };
2270 let config = ProptestConfig {
2271 cases,
2272 max_local_rejects: cases.saturating_mul(512),
2273 ..default
2274 };
2275 proptest!(config, |(data in gen_range_expr_data())| {
2276 check(data)?;
2277 });
2278 }
2279
2280 #[mz_ore::test]
2289 #[cfg_attr(miri, ignore)] fn test_result_spec_lattice_laws() {
2291 #[derive(Debug, Clone)]
2294 enum Recipe {
2295 Nothing,
2296 Null,
2297 Fails,
2298 Anything,
2299 ValueAll,
2300 Value(PropDatum),
2301 Between(PropDatum, PropDatum),
2302 Map(Vec<(PropDatum, Recipe)>),
2303 Union(Box<Recipe>, Box<Recipe>),
2304 }
2305
2306 fn materialize(recipe: &Recipe) -> ResultSpec<'_> {
2307 match recipe {
2308 Recipe::Nothing => ResultSpec::nothing(),
2309 Recipe::Null => ResultSpec::null(),
2310 Recipe::Fails => ResultSpec::fails(),
2311 Recipe::Anything => ResultSpec::anything(),
2312 Recipe::ValueAll => ResultSpec::value_all(),
2313 Recipe::Value(pd) => ResultSpec::value(pd.into()),
2314 Recipe::Between(a, b) => {
2315 let (a, b): (Datum, Datum) = (a.into(), b.into());
2316 if a.is_null() || b.is_null() {
2317 ResultSpec::nothing()
2318 } else if a <= b {
2319 ResultSpec::value_between(a, b)
2320 } else {
2321 ResultSpec::value_between(b, a)
2322 }
2323 }
2324 Recipe::Map(entries) => {
2325 let mut map = BTreeMap::new();
2326 for (key, val) in entries {
2327 let key: Datum = key.into();
2328 if !key.is_null() {
2329 map.insert(key, materialize(val));
2330 }
2331 }
2332 ResultSpec::map_spec(map)
2333 }
2334 Recipe::Union(a, b) => materialize(a).union(materialize(b)),
2335 }
2336 }
2337
2338 fn recipe_strategy() -> impl Strategy<Value = Recipe> {
2339 let leaf = proptest::strategy::Union::new(vec![
2340 Just(Recipe::Nothing).boxed(),
2341 Just(Recipe::Null).boxed(),
2342 Just(Recipe::Fails).boxed(),
2343 Just(Recipe::Anything).boxed(),
2344 Just(Recipe::ValueAll).boxed(),
2345 mz_repr::arb_datum(false).prop_map(Recipe::Value).boxed(),
2346 (mz_repr::arb_datum(false), mz_repr::arb_datum(false))
2347 .prop_map(|(a, b)| Recipe::Between(a, b))
2348 .boxed(),
2349 ]);
2350 leaf.prop_recursive(3, 24, 4, |inner| {
2351 proptest::strategy::Union::new(vec![
2352 prop::collection::vec((mz_repr::arb_datum(false), inner.clone()), 0..3)
2353 .prop_map(Recipe::Map)
2354 .boxed(),
2355 (inner.clone(), inner.clone())
2356 .prop_map(|(a, b)| Recipe::Union(Box::new(a), Box::new(b)))
2357 .boxed(),
2358 ])
2359 })
2360 }
2361
2362 fn check(a: Recipe, b: Recipe, v: PropDatum) -> Result<(), TestCaseError> {
2363 let a_spec = materialize(&a);
2364 let b_spec = materialize(&b);
2365 let v: Datum = (&v).into();
2366
2367 let in_a = a_spec.may_contain(v);
2368 let in_b = b_spec.may_contain(v);
2369
2370 if in_a || in_b {
2371 prop_assert!(
2372 a_spec.clone().union(b_spec.clone()).may_contain(v),
2373 "union dropped a value: a={a:?} b={b:?} v={v:?}",
2374 );
2375 }
2376 if in_a && in_b {
2377 prop_assert!(
2378 a_spec.intersect(b_spec).may_contain(v),
2379 "intersect dropped a common value: a={a:?} b={b:?} v={v:?}",
2380 );
2381 }
2382 Ok(())
2383 }
2384
2385 proptest!(
2386 ProptestConfig::with_cases(4096),
2387 |(a in recipe_strategy(), b in recipe_strategy(), v in mz_repr::arb_datum(true))| {
2388 check(a, b, v)?;
2389 }
2390 );
2391 }
2392
2393 #[mz_ore::test]
2404 #[cfg_attr(miri, ignore)] fn test_neg_numeric_nan_range() {
2406 use mz_repr::adt::numeric::Numeric;
2407
2408 let neg = MirScalarExpr::CallUnary {
2409 func: UnaryFunc::NegNumeric(NegNumeric),
2410 expr: Box::new(MirScalarExpr::column(0)),
2411 };
2412
2413 let relation = ReprRelationType::new(vec![ReprScalarType::Numeric.nullable(false)]);
2414 let arena = RowArena::new();
2415 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2416 interpreter.push_column(
2417 0,
2418 ResultSpec::value_between(
2419 Datum::from(Numeric::from(f64::NEG_INFINITY)),
2420 Datum::from(Numeric::from(f64::NAN)),
2421 ),
2422 );
2423
2424 let spec = interpreter.expr(&neg);
2425
2426 let actual = neg
2428 .eval(&[Datum::from(Numeric::from(-1.0f64))], &arena)
2429 .expect("eval succeeds");
2430 assert!(
2431 spec.range.may_contain(actual),
2432 "interpreter must not rule out {actual:?}, which the evaluator \
2433 produces for an interior input; got spec {:?}",
2434 spec.range,
2435 );
2436 }
2437
2438 #[mz_ore::test]
2449 #[cfg_attr(miri, ignore)] fn test_fallible_monotone_interior_error() {
2451 use mz_repr::adt::numeric::Numeric;
2452
2453 let cast = MirScalarExpr::CallUnary {
2454 func: UnaryFunc::CastNumericToMzTimestamp(CastNumericToMzTimestamp),
2455 expr: Box::new(MirScalarExpr::column(0)),
2456 };
2457
2458 let relation = ReprRelationType::new(vec![ReprScalarType::Numeric.nullable(false)]);
2459 let arena = RowArena::new();
2460 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2461 interpreter.push_column(
2462 0,
2463 ResultSpec::value_between(
2464 Datum::from(Numeric::from(0.0f64)),
2465 Datum::from(Numeric::from(2.0f64)),
2466 ),
2467 );
2468
2469 let spec = interpreter.expr(&cast);
2470
2471 let interior = Datum::from(Numeric::from(1.5f64));
2474 assert!(
2475 cast.eval(&[interior], &arena).is_err(),
2476 "precondition: a fractional numeric fails to cast to mz_timestamp",
2477 );
2478 assert!(
2479 spec.range.may_fail(),
2480 "interpreter must surface that a monotone-but-fallible function may \
2481 error on an interior value it never sampled; got spec {:?}",
2482 spec.range,
2483 );
2484 }
2485
2486 #[mz_ore::test]
2494 #[cfg_attr(miri, ignore)]
2495 fn test_mfp_unreferenced_fallible_expression() {
2496 use crate::scalar::func::CastStringToUuid;
2497
2498 let mfp = MapFilterProject {
2504 expressions: vec![MirScalarExpr::CallUnary {
2505 func: UnaryFunc::CastStringToUuid(CastStringToUuid),
2506 expr: Box::new(MirScalarExpr::column(0)),
2507 }],
2508 predicates: vec![(
2509 1,
2510 MirScalarExpr::literal_ok(Datum::True, ReprScalarType::Bool),
2511 )],
2512 projection: vec![0, 1],
2513 input_arity: 1,
2514 };
2515
2516 let relation = ReprRelationType::new(vec![ReprScalarType::String.nullable(false)]);
2517 let arena = RowArena::new();
2518 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2519 interpreter.push_column(
2521 0,
2522 ResultSpec::value_between(Datum::String("not-a-uuid"), Datum::String("not-a-uuid")),
2523 );
2524 let spec = interpreter.mfp_filter(&mfp);
2525 assert!(
2526 spec.range.may_fail(),
2527 "an MFP expression that errors on the stats range must propagate \
2528 fallibility, otherwise persist filter pushdown can wrongly discard \
2529 a part that produces error rows",
2530 );
2531 }
2532
2533 #[mz_ore::test]
2543 #[cfg_attr(miri, ignore)]
2544 fn test_mfp_filter_fallibility_equivalence() {
2545 fn check(data: ExpressionData) -> Result<(), TestCaseError> {
2546 let ExpressionData {
2547 relation_type,
2548 specs,
2549 rows,
2550 expr,
2551 } = data;
2552
2553 let input_arity = relation_type.column_types.len();
2554 let mfp = MapFilterProject {
2555 expressions: vec![expr.clone()],
2556 predicates: vec![],
2557 projection: (0..input_arity).collect(),
2558 input_arity,
2559 };
2560
2561 let arena = RowArena::new();
2562 let mut interpreter = ColumnSpecs::new(&relation_type, &arena);
2563 for (id, spec) in specs.into_iter().enumerate() {
2564 interpreter.push_column(id, spec);
2565 }
2566 let summary = interpreter.mfp_filter(&mfp);
2567
2568 for row in &rows {
2569 let datums: Vec<_> = row.iter().collect();
2570 if expr.eval(&datums, &arena).is_err() {
2571 prop_assert!(
2572 summary.range.may_fail(),
2573 "mfp_filter must surface the fallibility of an \
2574 unreferenced MFP expression: row {:?} errored at \
2575 runtime but the interpreter ruled out errors",
2576 row,
2577 );
2578 }
2579 }
2580 Ok(())
2581 }
2582
2583 proptest!(|(data in gen_expr_data())| {
2584 check(data)?;
2585 });
2586 }
2587
2588 #[mz_ore::test]
2589 fn test_mfp() {
2590 use MirScalarExpr::*;
2592
2593 let mfp = MapFilterProject {
2594 expressions: vec![],
2595 predicates: vec![
2596 (
2598 1,
2599 CallUnary {
2600 func: UnaryFunc::IsNull(IsNull),
2601 expr: Box::new(CallBinary {
2602 func: MulInt32.into(),
2603 expr1: Box::new(MirScalarExpr::column(0)),
2604 expr2: Box::new(MirScalarExpr::column(0)),
2605 }),
2606 },
2607 ),
2608 (
2610 1,
2611 CallBinary {
2612 func: Eq.into(),
2613 expr1: Box::new(MirScalarExpr::column(0)),
2614 expr2: Box::new(MirScalarExpr::literal_ok(
2615 Datum::Int32(1727694505),
2616 ReprScalarType::Int32,
2617 )),
2618 },
2619 ),
2620 ],
2621 projection: vec![],
2622 input_arity: 1,
2623 };
2624
2625 let relation = ReprRelationType::new(vec![ReprScalarType::Int32.nullable(true)]);
2626 let arena = RowArena::new();
2627 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2628 interpreter.push_column(0, ResultSpec::value(Datum::Int32(-1294725158)));
2629 let spec = interpreter.mfp_filter(&mfp);
2630 assert!(spec.range.may_fail());
2631 }
2632
2633 #[mz_ore::test]
2634 fn test_concat() {
2635 let expr = MirScalarExpr::call_variadic(
2636 Concat,
2637 vec![
2638 MirScalarExpr::column(0),
2639 MirScalarExpr::literal_ok(Datum::String("a"), ReprScalarType::String),
2640 MirScalarExpr::literal_ok(Datum::String("b"), ReprScalarType::String),
2641 ],
2642 );
2643
2644 let relation = ReprRelationType::new(vec![ReprScalarType::String.nullable(false)]);
2645 let arena = RowArena::new();
2646 let interpreter = ColumnSpecs::new(&relation, &arena);
2647 let spec = interpreter.expr(&expr);
2648 assert!(spec.range.may_contain(Datum::String("blab")));
2649 }
2650
2651 #[mz_ore::test]
2652 fn test_eval_range() {
2653 let period_ms = MirScalarExpr::literal_ok(Datum::Int64(10), ReprScalarType::Int64);
2665 let expr = MirScalarExpr::CallBinary {
2666 func: Gte.into(),
2667 expr1: Box::new(MirScalarExpr::CallUnmaterializable(
2668 UnmaterializableFunc::MzNow,
2669 )),
2670 expr2: Box::new(MirScalarExpr::CallUnary {
2671 func: UnaryFunc::CastInt64ToMzTimestamp(CastInt64ToMzTimestamp),
2672 expr: Box::new(MirScalarExpr::CallBinary {
2673 func: MulInt64.into(),
2674 expr1: Box::new(period_ms.clone()),
2675 expr2: Box::new(MirScalarExpr::CallBinary {
2676 func: DivInt64.into(),
2677 expr1: Box::new(MirScalarExpr::column(0)),
2678 expr2: Box::new(period_ms),
2679 }),
2680 }),
2681 }),
2682 };
2683 let relation = ReprRelationType::new(vec![ReprScalarType::Int64.nullable(false)]);
2684
2685 {
2686 let arena = RowArena::new();
2688 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2689 interpreter.push_unmaterializable(
2690 UnmaterializableFunc::MzNow,
2691 ResultSpec::value_between(
2692 Datum::MzTimestamp(10.into()),
2693 Datum::MzTimestamp(20.into()),
2694 ),
2695 );
2696 interpreter.push_column(0, ResultSpec::value_between(30i64.into(), 40i64.into()));
2697
2698 let range_out = interpreter.expr(&expr).range;
2699 assert!(range_out.may_contain(Datum::False));
2700 assert!(!range_out.may_contain(Datum::True));
2701 assert!(!range_out.may_contain(Datum::Null));
2702 assert!(range_out.may_fail());
2703 }
2704
2705 {
2706 let arena = RowArena::new();
2708 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2709 interpreter.push_unmaterializable(
2710 UnmaterializableFunc::MzNow,
2711 ResultSpec::value_between(
2712 Datum::MzTimestamp(10.into()),
2713 Datum::MzTimestamp(35.into()),
2714 ),
2715 );
2716 interpreter.push_column(0, ResultSpec::value_between(30i64.into(), 40i64.into()));
2717
2718 let range_out = interpreter.expr(&expr).range;
2719 assert!(range_out.may_contain(Datum::False));
2720 assert!(range_out.may_contain(Datum::True));
2721 assert!(!range_out.may_contain(Datum::Null));
2722 assert!(range_out.may_fail());
2723 }
2724 }
2725
2726 #[mz_ore::test]
2727 #[cfg_attr(miri, ignore)] fn test_jsonb() {
2729 let arena = RowArena::new();
2730
2731 let expr = MirScalarExpr::column(0)
2732 .call_binary(
2733 MirScalarExpr::literal_ok(Datum::from("ts"), ReprScalarType::String),
2734 JsonbGetString,
2735 )
2736 .call_unary(CastJsonbToNumeric(None));
2737
2738 let relation = ReprRelationType::new(vec![ReprScalarType::Jsonb.nullable(true)]);
2739 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2740 interpreter.push_column(
2741 0,
2742 ResultSpec::map_spec(
2743 [(
2744 "ts".into(),
2745 ResultSpec::value_between(
2746 Datum::Numeric(100.into()),
2747 Datum::Numeric(300.into()),
2748 ),
2749 )]
2750 .into_iter()
2751 .collect(),
2752 ),
2753 );
2754
2755 let range_out = interpreter.expr(&expr).range;
2756 assert!(!range_out.may_contain(Datum::Numeric(0.into())));
2757 assert!(range_out.may_contain(Datum::Numeric(200.into())));
2758 assert!(!range_out.may_contain(Datum::Numeric(400.into())));
2759 }
2760
2761 #[mz_ore::test]
2762 fn test_nested_union_partial_overlap() {
2763 let a = ResultSpec::map_spec(
2768 [
2769 ("x".into(), ResultSpec::value(Datum::String("a"))),
2770 ("y".into(), ResultSpec::value(Datum::String("b"))),
2771 ("c".into(), ResultSpec::value(Datum::String("c"))),
2772 ]
2773 .into_iter()
2774 .collect(),
2775 );
2776 let b = ResultSpec::map_spec(
2777 [
2778 ("x".into(), ResultSpec::value(Datum::String("a2"))),
2779 ("y".into(), ResultSpec::value(Datum::String("b2"))),
2780 ("z".into(), ResultSpec::value(Datum::String("z"))),
2781 ]
2782 .into_iter()
2783 .collect(),
2784 );
2785
2786 let unioned = a.union(b);
2787
2788 let arena = RowArena::new();
2792 let relation = ReprRelationType::new(vec![ReprScalarType::Jsonb.nullable(false)]);
2793
2794 {
2796 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2797 interpreter.push_column(0, unioned.clone());
2798 let expr = MirScalarExpr::column(0).call_binary(
2799 MirScalarExpr::literal_ok(Datum::from("c"), ReprScalarType::String),
2800 JsonbGetStringStringify,
2801 );
2802 assert!(interpreter.expr(&expr).range.may_contain(Datum::Null));
2803 }
2804
2805 {
2807 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2808 interpreter.push_column(0, unioned.clone());
2809 let expr = MirScalarExpr::column(0).call_binary(
2810 MirScalarExpr::literal_ok(Datum::from("z"), ReprScalarType::String),
2811 JsonbGetStringStringify,
2812 );
2813 assert!(interpreter.expr(&expr).range.may_contain(Datum::Null));
2814 }
2815
2816 {
2818 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2819 interpreter.push_column(0, unioned);
2820 let expr = MirScalarExpr::column(0).call_binary(
2821 MirScalarExpr::literal_ok(Datum::from("x"), ReprScalarType::String),
2822 JsonbGetStringStringify,
2823 );
2824 let x_range = interpreter.expr(&expr).range;
2825 assert!(x_range.may_contain(Datum::String("a")));
2826 assert!(x_range.may_contain(Datum::String("a2")));
2827 }
2828 }
2829
2830 #[mz_ore::test]
2831 #[cfg_attr(miri, ignore)] fn test_case_over_jsonb_columns() {
2833 let arena = RowArena::new();
2837
2838 let expr = MirScalarExpr::If {
2840 cond: Box::new(MirScalarExpr::column(0)),
2841 then: Box::new(MirScalarExpr::column(1)),
2842 els: Box::new(MirScalarExpr::column(2)),
2843 }
2844 .call_binary(
2845 MirScalarExpr::literal_ok(Datum::from("y"), ReprScalarType::String),
2846 JsonbGetStringStringify,
2847 )
2848 .call_unary(UnaryFunc::IsNull(IsNull));
2849
2850 let relation = ReprRelationType::new(vec![
2851 ReprScalarType::Bool.nullable(false),
2852 ReprScalarType::Jsonb.nullable(false),
2853 ReprScalarType::Jsonb.nullable(false),
2854 ]);
2855 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2856 interpreter.push_column(0, ResultSpec::value_between(Datum::False, Datum::True));
2857 interpreter.push_column(
2858 1,
2859 ResultSpec::map_spec(
2860 [("x".into(), ResultSpec::value(Datum::String("a")))]
2861 .into_iter()
2862 .collect(),
2863 ),
2864 );
2865 interpreter.push_column(
2866 2,
2867 ResultSpec::map_spec(
2868 [("y".into(), ResultSpec::value(Datum::String("b")))]
2869 .into_iter()
2870 .collect(),
2871 ),
2872 );
2873
2874 let range_out = interpreter.expr(&expr).range;
2875 assert!(range_out.may_contain(Datum::True));
2878 }
2879
2880 #[mz_ore::test]
2881 fn test_like() {
2882 let arena = RowArena::new();
2883
2884 let expr = MirScalarExpr::CallUnary {
2885 func: UnaryFunc::IsLikeMatch(IsLikeMatch(
2886 crate::like_pattern::compile("%whatever%", true).unwrap(),
2887 )),
2888 expr: Box::new(MirScalarExpr::column(0)),
2889 };
2890
2891 let relation = ReprRelationType::new(vec![ReprScalarType::String.nullable(true)]);
2892 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2893 interpreter.push_column(
2894 0,
2895 ResultSpec::value_between(Datum::String("aardvark"), Datum::String("zebra")),
2896 );
2897
2898 let range_out = interpreter.expr(&expr).range;
2899 assert!(
2900 !range_out.fallible,
2901 "like function should not error on non-error input"
2902 );
2903 assert!(range_out.may_contain(Datum::True));
2904 assert!(range_out.may_contain(Datum::False));
2905 assert!(range_out.may_contain(Datum::Null));
2906 }
2907
2908 #[mz_ore::test]
2909 fn test_try_parse_monotonic_iso8601_timestamp() {
2910 use chrono::NaiveDateTime;
2911
2912 let arena = RowArena::new();
2913
2914 let expr = MirScalarExpr::CallUnary {
2915 func: UnaryFunc::TryParseMonotonicIso8601Timestamp(TryParseMonotonicIso8601Timestamp),
2916 expr: Box::new(MirScalarExpr::column(0)),
2917 };
2918
2919 let relation = ReprRelationType::new(vec![ReprScalarType::String.nullable(true)]);
2920 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2922 interpreter.push_column(
2923 0,
2924 ResultSpec::value_between(
2925 Datum::String("2024-01-11T00:00:00.000Z"),
2926 Datum::String("2024-01-11T20:00:00.000Z"),
2927 ),
2928 );
2929
2930 let timestamp = |ts| {
2931 Datum::Timestamp(
2932 NaiveDateTime::parse_from_str(ts, "%Y-%m-%dT%H:%M:%S")
2933 .unwrap()
2934 .try_into()
2935 .unwrap(),
2936 )
2937 };
2938
2939 let range_out = interpreter.expr(&expr).range;
2940 assert!(!range_out.fallible);
2941 assert!(range_out.nullable);
2942 assert!(!range_out.may_contain(timestamp("2024-01-10T10:00:00")));
2943 assert!(range_out.may_contain(timestamp("2024-01-11T10:00:00")));
2944 assert!(!range_out.may_contain(timestamp("2024-01-12T10:00:00")));
2945
2946 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2948 interpreter.push_column(
2949 0,
2950 ResultSpec::value_between(Datum::String("2024-01-1"), Datum::String("2024-01-2")),
2951 );
2952
2953 let range_out = interpreter.expr(&expr).range;
2954 assert!(!range_out.fallible);
2955 assert!(range_out.nullable);
2956 assert!(range_out.may_contain(timestamp("2024-01-10T10:00:00")));
2957 assert!(range_out.may_contain(timestamp("2024-01-11T10:00:00")));
2958 assert!(range_out.may_contain(timestamp("2024-01-12T10:00:00")));
2959
2960 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2962 interpreter.push_column(
2963 0,
2964 ResultSpec::value_between(
2965 Datum::String("2024-01-1"),
2966 Datum::String("2024-01-12T10:00:00"),
2967 )
2968 .union(ResultSpec::null()),
2969 );
2970
2971 let range_out = interpreter.expr(&expr).range;
2972 assert!(!range_out.fallible);
2973 assert!(range_out.nullable);
2974 assert!(range_out.may_contain(timestamp("2024-01-10T10:00:00")));
2975 assert!(range_out.may_contain(timestamp("2024-01-11T10:00:00")));
2976 assert!(range_out.may_contain(timestamp("2024-01-12T10:00:00")));
2977
2978 let mut interpreter = ColumnSpecs::new(&relation, &arena);
2980 interpreter.push_column(
2981 0,
2982 ResultSpec::value_between(
2983 Datum::String("2024-01-11T10:00:00.000Z"),
2984 Datum::String("2024-01-11T10:00:00.000Z"),
2985 ),
2986 );
2987
2988 let range_out = interpreter.expr(&expr).range;
2989 assert!(!range_out.fallible);
2990 assert!(!range_out.nullable);
2991 assert!(!range_out.may_contain(timestamp("2024-01-10T10:00:00")));
2992 assert!(range_out.may_contain(timestamp("2024-01-11T10:00:00")));
2993 assert!(!range_out.may_contain(timestamp("2024-01-12T10:00:00")));
2994 }
2995
2996 #[mz_ore::test]
2997 fn test_inequality() {
2998 let arena = RowArena::new();
2999
3000 let expr = MirScalarExpr::column(0).call_binary(
3001 MirScalarExpr::CallUnmaterializable(UnmaterializableFunc::MzNow),
3002 Gte,
3003 );
3004
3005 let relation = ReprRelationType::new(vec![ReprScalarType::MzTimestamp.nullable(true)]);
3006 let mut interpreter = ColumnSpecs::new(&relation, &arena);
3007 interpreter.push_column(
3008 0,
3009 ResultSpec::value_between(
3010 Datum::MzTimestamp(1704736444949u64.into()),
3011 Datum::MzTimestamp(1704736444949u64.into()),
3012 )
3013 .union(ResultSpec::null()),
3014 );
3015 interpreter.push_unmaterializable(
3016 UnmaterializableFunc::MzNow,
3017 ResultSpec::value_between(
3018 Datum::MzTimestamp(1704738791000u64.into()),
3019 Datum::MzTimestamp(18446744073709551615u64.into()),
3020 ),
3021 );
3022
3023 let range_out = interpreter.expr(&expr).range;
3024 assert!(
3025 !range_out.fallible,
3026 "<= function should not error on non-error input"
3027 );
3028 assert!(!range_out.may_contain(Datum::True));
3029 assert!(range_out.may_contain(Datum::False));
3030 assert!(range_out.may_contain(Datum::Null));
3031 }
3032
3033 #[mz_ore::test]
3041 #[cfg_attr(miri, ignore)]
3042 fn test_add_timestamp_interval_non_monotone() {
3043 use chrono::NaiveDateTime;
3044 use mz_repr::adt::interval::Interval;
3045 use mz_repr::adt::timestamp::CheckedTimestamp;
3046 use mz_repr::{Datum, Row};
3047
3048 let arena = RowArena::new();
3049
3050 let ts_lit = |s: &str| {
3062 let mut row = Row::default();
3063 row.packer().push(Datum::Timestamp(
3064 CheckedTimestamp::from_timestamplike(
3065 NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S").unwrap(),
3066 )
3067 .unwrap(),
3068 ));
3069 MirScalarExpr::Literal(Ok(row), ReprScalarType::Timestamp.nullable(false))
3070 };
3071 let interval = |months: i32, days: i32, micros: i64| {
3072 Datum::Interval(Interval {
3073 months,
3074 days,
3075 micros,
3076 })
3077 };
3078
3079 let expr = ts_lit("2024-01-31T00:00:00")
3081 .call_binary(MirScalarExpr::column(0), AddTimestampInterval)
3082 .call_binary(ts_lit("2024-03-15T00:00:00"), Gte);
3083
3084 let relation = ReprRelationType::new(vec![ReprScalarType::Interval.nullable(false)]);
3085 let mut interpreter = ColumnSpecs::new(&relation, &arena);
3086 interpreter.push_column(
3087 0,
3088 ResultSpec::value_between(interval(0, 31, 0), interval(1, 0, 0)),
3089 );
3090
3091 let range_out = interpreter.expr(&expr).range;
3092 assert!(
3099 range_out.may_contain(Datum::True),
3100 "interpreter incorrectly ruled out matching rows; \
3101 add_timestamp_interval is not monotone in the interval argument",
3102 );
3103 }
3104
3105 #[mz_ore::test]
3112 #[cfg_attr(miri, ignore)]
3113 fn test_timestamp_plus_interval_dynamic_monotone() {
3114 use chrono::NaiveDateTime;
3115 use mz_repr::adt::interval::Interval;
3116 use mz_repr::adt::timestamp::CheckedTimestamp;
3117 use mz_repr::{Datum, Row};
3118
3119 let arena = RowArena::new();
3120
3121 let ts = |s: &str| {
3122 Datum::Timestamp(
3123 CheckedTimestamp::from_timestamplike(
3124 NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S").unwrap(),
3125 )
3126 .unwrap(),
3127 )
3128 };
3129 let interval_lit = |months: i32, days: i32, micros: i64| {
3130 let mut row = Row::default();
3131 row.packer().push(Datum::Interval(Interval {
3132 months,
3133 days,
3134 micros,
3135 }));
3136 MirScalarExpr::Literal(Ok(row), ReprScalarType::Interval.nullable(false))
3137 };
3138
3139 let relation = ReprRelationType::new(vec![ReprScalarType::Timestamp.nullable(false)]);
3140
3141 {
3147 let expr = MirScalarExpr::column(0)
3148 .call_binary(interval_lit(0, 1, 0), SubTimestampInterval)
3149 .call_binary(
3150 MirScalarExpr::Literal(
3151 Ok({
3152 let mut r = Row::default();
3153 r.packer().push(ts("2024-01-15T00:00:00"));
3154 r
3155 }),
3156 ReprScalarType::Timestamp.nullable(false),
3157 ),
3158 Lt,
3159 );
3160 let mut interpreter = ColumnSpecs::new(&relation, &arena);
3161 interpreter.push_column(
3162 0,
3163 ResultSpec::value_between(ts("2024-01-15T00:00:00"), ts("2024-01-20T00:00:00")),
3164 );
3165 let range_out = interpreter.expr(&expr).range;
3166 assert!(
3167 range_out.may_contain(Datum::True),
3168 "day-only interval should preserve tight bounds",
3169 );
3170 assert!(
3171 range_out.may_contain(Datum::False),
3172 "day-only interval should preserve tight bounds",
3173 );
3174 }
3175
3176 {
3181 let expr = MirScalarExpr::column(0)
3182 .call_binary(interval_lit(0, 1, 0), SubTimestampInterval)
3183 .call_binary(
3184 MirScalarExpr::Literal(
3185 Ok({
3186 let mut r = Row::default();
3187 r.packer().push(ts("2024-01-15T00:00:00"));
3188 r
3189 }),
3190 ReprScalarType::Timestamp.nullable(false),
3191 ),
3192 Lt,
3193 );
3194 let mut interpreter = ColumnSpecs::new(&relation, &arena);
3195 interpreter.push_column(
3196 0,
3197 ResultSpec::value_between(ts("2024-01-17T00:00:00"), ts("2024-01-20T00:00:00")),
3198 );
3199 let range_out = interpreter.expr(&expr).range;
3200 assert!(
3201 !range_out.may_contain(Datum::True),
3202 "day-only interval should narrow out impossible matches",
3203 );
3204 }
3205
3206 {
3211 let expr = MirScalarExpr::column(0)
3212 .call_binary(interval_lit(1, 0, 0), SubTimestampInterval)
3213 .call_binary(
3214 MirScalarExpr::Literal(
3215 Ok({
3216 let mut r = Row::default();
3217 r.packer().push(ts("2024-01-15T00:00:00"));
3218 r
3219 }),
3220 ReprScalarType::Timestamp.nullable(false),
3221 ),
3222 Lt,
3223 );
3224 let mut interpreter = ColumnSpecs::new(&relation, &arena);
3225 interpreter.push_column(
3226 0,
3227 ResultSpec::value_between(ts("2024-01-17T00:00:00"), ts("2024-01-20T00:00:00")),
3228 );
3229 let range_out = interpreter.expr(&expr).range;
3230 assert!(
3231 range_out.may_contain(Datum::True),
3232 "month-bearing interval must conservatively admit True",
3233 );
3234 assert!(
3235 range_out.may_contain(Datum::False),
3236 "month-bearing interval must conservatively admit False",
3237 );
3238 }
3239 }
3240
3241 #[mz_ore::test]
3250 #[cfg_attr(miri, ignore)]
3251 fn proptest_timestamp_plus_interval_monotone_when_months_zero() {
3252 use mz_repr::adt::interval::Interval;
3253 use mz_repr::{Datum, RowArena, SqlScalarType, arb_datum_for_scalar};
3254 use proptest::prelude::*;
3255
3256 let timestamp_strat = || arb_datum_for_scalar(SqlScalarType::Timestamp { precision: None });
3257 let zero_month_interval_strat =
3264 (any::<i32>(), any::<i64>()).prop_map(|(days, micros)| Interval {
3265 months: 0,
3266 days,
3267 micros,
3268 });
3269
3270 let expr = MirScalarExpr::CallBinary {
3271 func: AddTimestampInterval.into(),
3272 expr1: Box::new(MirScalarExpr::column(0)),
3273 expr2: Box::new(MirScalarExpr::column(1)),
3274 };
3275 let arena = RowArena::new();
3276
3277 proptest!(|(
3278 t1 in timestamp_strat(),
3279 t2 in timestamp_strat(),
3280 i in zero_month_interval_strat,
3281 )| {
3282 let t1 = match t1 { PropDatum::Timestamp(t) => t, _ => unreachable!() };
3283 let t2 = match t2 { PropDatum::Timestamp(t) => t, _ => unreachable!() };
3284 let i = Datum::Interval(i);
3285 let r1 = expr.eval(&[Datum::Timestamp(t1), i], &arena);
3286 let r2 = expr.eval(&[Datum::Timestamp(t2), i], &arena);
3287 if let (Ok(Datum::Timestamp(r1)), Ok(Datum::Timestamp(r2))) = (r1, r2) {
3290 prop_assert_eq!(t1.cmp(&t2), r1.cmp(&r2));
3291 }
3292 });
3293 }
3294
3295 #[mz_ore::test]
3300 #[cfg_attr(miri, ignore)]
3301 fn test_date_bin_timestamp_non_monotone() {
3302 use chrono::NaiveDateTime;
3303 use mz_repr::adt::interval::Interval;
3304 use mz_repr::adt::timestamp::CheckedTimestamp;
3305 use mz_repr::{Datum, Row};
3306
3307 let arena = RowArena::new();
3308
3309 let ts_lit = |s: &str| {
3310 let mut row = Row::default();
3311 row.packer().push(Datum::Timestamp(
3312 CheckedTimestamp::from_timestamplike(
3313 NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S").unwrap(),
3314 )
3315 .unwrap(),
3316 ));
3317 MirScalarExpr::Literal(Ok(row), ReprScalarType::Timestamp.nullable(false))
3318 };
3319 let interval = |months: i32, days: i32, micros: i64| {
3320 Datum::Interval(Interval {
3321 months,
3322 days,
3323 micros,
3324 })
3325 };
3326
3327 let expr = MirScalarExpr::column(0)
3344 .call_binary(ts_lit("2024-01-01T12:00:00"), DateBinTimestamp)
3345 .call_binary(ts_lit("2024-01-01T06:00:00"), Gt);
3346
3347 let relation = ReprRelationType::new(vec![ReprScalarType::Interval.nullable(false)]);
3348 let mut interpreter = ColumnSpecs::new(&relation, &arena);
3349 interpreter.push_column(
3350 0,
3351 ResultSpec::value_between(interval(0, 1, 0), interval(0, 2, 0)),
3352 );
3353
3354 let range_out = interpreter.expr(&expr).range;
3355 assert!(
3356 range_out.may_contain(Datum::True),
3357 "date_bin is not monotone in the stride argument; \
3358 interior strides can produce outputs outside the endpoint-bounded \
3359 box, so the interpreter must admit True for `>`-style predicates",
3360 );
3361 }
3362
3363 #[mz_ore::test]
3368 #[cfg_attr(miri, ignore)]
3369 fn test_round_numeric_representation_independent() {
3370 use mz_repr::adt::date::Date;
3371
3372 let arena = RowArena::new();
3373 let lit = |d: Datum, ty: ReprScalarType| {
3374 let mut row = Row::default();
3375 row.packer().push(d);
3376 MirScalarExpr::Literal(Ok(row), ty.nullable(false))
3377 };
3378
3379 let expr = lit(
3382 Datum::Date(Date::from_pg_epoch(0).unwrap()),
3383 ReprScalarType::Date,
3384 )
3385 .call_unary(UnaryFunc::ExtractDate(ExtractDate(DateTimeUnits::Epoch)))
3386 .call_binary(
3387 lit(Datum::Int32(i32::MAX), ReprScalarType::Int32),
3388 BinaryFunc::from(RoundNumericBinary),
3389 );
3390
3391 let relation = ReprRelationType::new(vec![]);
3392 let range = ColumnSpecs::new(&relation, &arena).expr(&expr).range;
3393 match expr.eval(&[], &arena) {
3394 Ok(value) => assert!(
3395 range.may_contain(value),
3396 "interpreter ruled out {value:?}, which the evaluator produced: {range:?}",
3397 ),
3398 Err(_) => assert!(
3399 range.may_fail(),
3400 "interpreter ruled out the error the evaluator produced: {range:?}",
3401 ),
3402 }
3403 }
3404
3405 #[mz_ore::test]
3406 fn test_trace() {
3407 use super::Trace;
3408
3409 let expr = MirScalarExpr::column(0).call_binary(
3410 MirScalarExpr::column(1)
3411 .call_binary(MirScalarExpr::column(3).call_unary(NegInt64), AddInt64),
3412 Gte,
3413 );
3414 let summary = Trace.expr(&expr);
3415 assert!(summary.pushdownable());
3416 }
3417}