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, EvalError, MapFilterProject, MfpPlan, MirScalarExpr, UnaryFunc,
18 UnmaterializableFunc, VariadicFunc, func,
19};
20
21#[derive(Clone, Eq, PartialEq, Debug)]
23enum Values<'a> {
24 Empty,
26 Within(Datum<'a>, Datum<'a>),
29 Nested(BTreeMap<Datum<'a>, ResultSpec<'a>>),
33 All,
36}
37
38impl<'a> Values<'a> {
39 fn just(a: Datum<'a>) -> Values<'a> {
40 match a {
41 Datum::Map(datum_map) => Values::Nested(
42 datum_map
43 .iter()
44 .map(|(key, val)| (key.into(), ResultSpec::value(val)))
45 .collect(),
46 ),
47 other => Self::Within(other, other),
48 }
49 }
50
51 fn union(self, other: Values<'a>) -> Values<'a> {
52 match (self, other) {
53 (Values::Empty, r) => r,
54 (r, Values::Empty) => r,
55 (Values::Within(a0, a1), Values::Within(b0, b1)) => {
56 Values::Within(a0.min(b0), a1.max(b1))
57 }
58 (Values::Nested(mut a), Values::Nested(mut b)) => {
59 a.retain(|datum, values| {
60 if let Some(other_values) = b.remove(datum) {
61 *values = values.clone().union(other_values);
62 }
63 *values != ResultSpec::anything()
64 });
65 if a.is_empty() {
66 Values::All
67 } else {
68 Values::Nested(a)
69 }
70 }
71 _ => Values::All,
72 }
73 }
74
75 fn intersect(self, other: Values<'a>) -> Values<'a> {
76 match (self, other) {
77 (Values::Empty, _) => Values::Empty,
78 (_, Values::Empty) => Values::Empty,
79 (Values::Within(a0, a1), Values::Within(b0, b1)) => {
80 let min = a0.max(b0);
81 let max = a1.min(b1);
82 if min <= max {
83 Values::Within(min, max)
84 } else {
85 Values::Empty
86 }
87 }
88 (Values::Nested(mut a), Values::Nested(b)) => {
89 for (datum, other_spec) in b {
90 let spec = a.entry(datum).or_insert_with(ResultSpec::anything);
91 *spec = spec.clone().intersect(other_spec);
92 }
93 Values::Nested(a)
94 }
95 (Values::All, v) => v,
96 (v, Values::All) => v,
97 (Values::Nested(_), Values::Within(_, _)) => Values::Empty,
98 (Values::Within(_, _), Values::Nested(_)) => Values::Empty,
99 }
100 }
101
102 fn may_contain(&self, value: Datum<'a>) -> bool {
103 match self {
104 Values::Empty => false,
105 Values::Within(min, max) => *min <= value && value <= *max,
106 Values::All => true,
107 Values::Nested(field_map) => match value {
108 Datum::Map(datum_map) => {
109 datum_map
110 .iter()
111 .all(|(key, val)| match field_map.get(&key.into()) {
112 None => true,
113 Some(nested) => nested.may_contain(val),
114 })
115 }
116 _ => false,
117 },
118 }
119 }
120}
121
122#[derive(Debug, Clone, Eq, PartialEq)]
127pub struct ResultSpec<'a> {
128 nullable: bool,
130 fallible: bool,
132 values: Values<'a>,
134}
135
136impl<'a> ResultSpec<'a> {
137 pub fn nothing() -> Self {
139 ResultSpec {
140 nullable: false,
141 fallible: false,
142 values: Values::Empty,
143 }
144 }
145
146 pub fn anything() -> Self {
148 ResultSpec {
149 nullable: true,
150 fallible: true,
151 values: Values::All,
152 }
153 }
154
155 pub fn any_infallible() -> Self {
157 ResultSpec {
158 nullable: true,
159 fallible: false,
160 values: Values::All,
161 }
162 }
163
164 pub fn null() -> Self {
166 ResultSpec {
167 nullable: true,
168 ..Self::nothing()
169 }
170 }
171
172 pub fn fails() -> Self {
174 ResultSpec {
175 fallible: true,
176 ..Self::nothing()
177 }
178 }
179
180 pub fn has_type(col: &ReprColumnType, fallible: bool) -> ResultSpec<'a> {
182 let values = match &col.scalar_type {
183 ReprScalarType::Bool => Values::Within(Datum::False, Datum::True),
184 _ => Values::All,
186 };
187 ResultSpec {
188 nullable: col.nullable,
189 fallible,
190 values,
191 }
192 }
193
194 pub fn value(value: Datum<'a>) -> ResultSpec<'a> {
196 match value {
197 Datum::Null => Self::null(),
198 nonnull => ResultSpec {
199 values: Values::just(nonnull),
200 ..Self::nothing()
201 },
202 }
203 }
204
205 pub fn value_between(min: Datum<'a>, max: Datum<'a>) -> ResultSpec<'a> {
207 assert!(!min.is_null());
208 assert!(!max.is_null());
209 if min <= max {
210 ResultSpec {
211 values: Values::Within(min, max),
212 ..ResultSpec::nothing()
213 }
214 } else {
215 ResultSpec::nothing()
216 }
217 }
218
219 pub fn value_all() -> ResultSpec<'a> {
221 ResultSpec {
222 values: Values::All,
223 ..ResultSpec::nothing()
224 }
225 }
226
227 pub fn map_spec(map: BTreeMap<Datum<'a>, ResultSpec<'a>>) -> ResultSpec<'a> {
229 ResultSpec {
230 values: Values::Nested(map),
231 ..ResultSpec::nothing()
232 }
233 }
234
235 pub fn union(self, other: ResultSpec<'a>) -> ResultSpec<'a> {
237 ResultSpec {
238 nullable: self.nullable || other.nullable,
239 fallible: self.fallible || other.fallible,
240 values: self.values.union(other.values),
241 }
242 }
243
244 pub fn intersect(self, other: ResultSpec<'a>) -> ResultSpec<'a> {
246 ResultSpec {
247 nullable: self.nullable && other.nullable,
248 fallible: self.fallible && other.fallible,
249 values: self.values.intersect(other.values),
250 }
251 }
252
253 pub fn may_contain(&self, value: Datum<'a>) -> bool {
255 if value == Datum::Null {
256 return self.nullable;
257 }
258
259 self.values.may_contain(value)
260 }
261
262 pub fn may_fail(&self) -> bool {
264 self.fallible
265 }
266
267 fn flat_map(
278 &self,
279 is_monotone: bool,
280 mut result_map: impl FnMut(Result<Datum<'a>, EvalError>) -> ResultSpec<'a>,
281 ) -> ResultSpec<'a> {
282 let null_spec = if self.nullable {
283 result_map(Ok(Datum::Null))
284 } else {
285 ResultSpec::nothing()
286 };
287
288 let error_spec = if self.fallible {
289 let map_err = result_map(Err(EvalError::Internal("".into())));
293 let raise_err = ResultSpec::fails();
294 raise_err.union(map_err)
299 } else {
300 ResultSpec::nothing()
301 };
302
303 let values_spec = match self.values {
304 Values::Empty => ResultSpec::nothing(),
305 Values::Within(min, max) if min == max => result_map(Ok(min)),
307 Values::Within(Datum::False, Datum::True) => {
309 result_map(Ok(Datum::False)).union(result_map(Ok(Datum::True)))
310 }
311 Values::Within(min, max) if is_monotone => {
314 let min_result = result_map(Ok(min));
315 let max_result = result_map(Ok(max));
316 match (min_result, max_result) {
317 (
319 ResultSpec {
320 nullable: false,
321 fallible: false,
322 values: a_values,
323 },
324 ResultSpec {
325 nullable: false,
326 fallible: false,
327 values: b_values,
328 },
329 ) => ResultSpec {
330 nullable: false,
331 fallible: false,
332 values: a_values.union(b_values),
333 },
334 (
336 ResultSpec {
337 nullable: true,
338 fallible: false,
339 values: Values::Empty,
340 },
341 ResultSpec {
342 nullable: true,
343 fallible: false,
344 values: Values::Empty,
345 },
346 ) => ResultSpec::null(),
347 _ => ResultSpec::anything(),
349 }
350 }
351 Values::Within(_, _) | Values::Nested(_) | Values::All => ResultSpec::anything(),
353 };
354
355 null_spec.union(error_spec).union(values_spec)
356 }
357}
358
359pub trait Interpreter {
368 type Summary: Clone + Debug + Sized;
369
370 fn column(&self, id: usize) -> Self::Summary;
372
373 fn literal(&self, result: &Result<Row, EvalError>, col_type: &ReprColumnType) -> Self::Summary;
376 fn unmaterializable(&self, func: &UnmaterializableFunc) -> Self::Summary;
381
382 fn unary(&self, func: &UnaryFunc, expr: Self::Summary) -> Self::Summary;
384
385 fn binary(&self, func: &BinaryFunc, left: Self::Summary, right: Self::Summary)
387 -> Self::Summary;
388
389 fn variadic(&self, func: &VariadicFunc, exprs: Vec<Self::Summary>) -> Self::Summary;
391
392 fn cond(&self, cond: Self::Summary, then: Self::Summary, els: Self::Summary) -> Self::Summary;
394
395 fn expr(&self, expr: &MirScalarExpr) -> Self::Summary {
397 match expr {
398 MirScalarExpr::Column(id, _name) => self.column(*id),
399 MirScalarExpr::Literal(value, col_type) => self.literal(value, col_type),
400 MirScalarExpr::CallUnmaterializable(func) => self.unmaterializable(func),
401 MirScalarExpr::CallUnary { func, expr } => {
402 let expr_range = self.expr(expr);
403 self.unary(func, expr_range)
404 }
405 MirScalarExpr::CallBinary { func, expr1, expr2 } => {
406 let expr1_range = self.expr(expr1);
407 let expr2_range = self.expr(expr2);
408 self.binary(func, expr1_range, expr2_range)
409 }
410 MirScalarExpr::CallVariadic { func, exprs } => {
411 let exprs: Vec<_> = exprs.into_iter().map(|e| self.expr(e)).collect();
412 self.variadic(func, exprs)
413 }
414 MirScalarExpr::If { cond, then, els } => {
415 let cond_range = self.expr(cond);
416 let then_range = self.expr(then);
417 let els_range = self.expr(els);
418 self.cond(cond_range, then_range, els_range)
419 }
420 }
421 }
422
423 fn mfp_filter(&self, mfp: &MapFilterProject) -> Self::Summary {
426 let mfp_eval = MfpEval::new(self, mfp.input_arity, &mfp.expressions);
427 let predicates = mfp
429 .predicates
430 .iter()
431 .map(|(_, e)| mfp_eval.expr(e))
432 .collect();
433 mfp_eval.variadic(&And.into(), predicates)
434 }
435
436 fn mfp_plan_filter(&self, plan: &MfpPlan) -> Self::Summary {
439 let mfp_eval = MfpEval::new(self, plan.mfp.input_arity, &plan.mfp.expressions);
440 let mut results: Vec<_> = plan
442 .mfp
443 .predicates
444 .iter()
445 .map(|(_, e)| mfp_eval.expr(e))
446 .collect();
447 let mz_now = mfp_eval.unmaterializable(&UnmaterializableFunc::MzNow);
448 for bound in &plan.lower_bounds {
449 let bound_range = mfp_eval.expr(bound);
450 let result = mfp_eval.binary(&BinaryFunc::Lte(func::Lte), bound_range, mz_now.clone());
451 results.push(result);
452 }
453 for bound in &plan.upper_bounds {
454 let bound_range = mfp_eval.expr(bound);
455 let result = mfp_eval.binary(&BinaryFunc::Gte(func::Gte), bound_range, mz_now.clone());
456 results.push(result);
457 }
458 self.variadic(&And.into(), results)
459 }
460}
461
462pub(crate) struct MfpEval<'a, E: Interpreter + ?Sized> {
465 evaluator: &'a E,
466 input_arity: usize,
467 expressions: Vec<E::Summary>,
468}
469
470impl<'a, E: Interpreter + ?Sized> MfpEval<'a, E> {
471 pub(crate) fn new(evaluator: &'a E, input_arity: usize, expressions: &[MirScalarExpr]) -> Self {
472 let mut mfp_eval = MfpEval {
473 evaluator,
474 input_arity,
475 expressions: vec![],
476 };
477 for expr in expressions {
478 let result = mfp_eval.expr(expr);
479 mfp_eval.expressions.push(result);
480 }
481 mfp_eval
482 }
483}
484
485impl<'a, E: Interpreter + ?Sized> Interpreter for MfpEval<'a, E> {
486 type Summary = E::Summary;
487
488 fn column(&self, id: usize) -> Self::Summary {
489 if id < self.input_arity {
490 self.evaluator.column(id)
491 } else {
492 self.expressions[id - self.input_arity].clone()
493 }
494 }
495
496 fn literal(&self, result: &Result<Row, EvalError>, col_type: &ReprColumnType) -> Self::Summary {
497 self.evaluator.literal(result, col_type)
498 }
499
500 fn unmaterializable(&self, func: &UnmaterializableFunc) -> Self::Summary {
501 self.evaluator.unmaterializable(func)
502 }
503
504 fn unary(&self, func: &UnaryFunc, expr: Self::Summary) -> Self::Summary {
505 self.evaluator.unary(func, expr)
506 }
507
508 fn binary(
509 &self,
510 func: &BinaryFunc,
511 left: Self::Summary,
512 right: Self::Summary,
513 ) -> Self::Summary {
514 self.evaluator.binary(func, left, right)
515 }
516
517 fn variadic(&self, func: &VariadicFunc, exprs: Vec<Self::Summary>) -> Self::Summary {
518 self.evaluator.variadic(func, exprs)
519 }
520
521 fn cond(&self, cond: Self::Summary, then: Self::Summary, els: Self::Summary) -> Self::Summary {
522 self.evaluator.cond(cond, then, els)
523 }
524}
525
526struct SpecialUnary {
531 map_fn: for<'a, 'b> fn(&'b ColumnSpecs<'a>, ResultSpec<'a>) -> ResultSpec<'a>,
532 pushdownable: bool,
533}
534
535impl SpecialUnary {
536 fn for_func(func: &UnaryFunc) -> Option<SpecialUnary> {
538 fn eagerly<'b>(
542 spec: ResultSpec<'b>,
543 value_fn: impl FnOnce(Values<'b>) -> ResultSpec<'b>,
544 ) -> ResultSpec<'b> {
545 let result = match spec.values {
546 Values::Empty => ResultSpec::nothing(),
547 other => value_fn(other),
548 };
549 ResultSpec {
550 fallible: spec.fallible || result.fallible,
551 nullable: spec.nullable || result.nullable,
552 values: result.values,
553 }
554 }
555 match func {
556 UnaryFunc::TryParseMonotonicIso8601Timestamp(_) => Some(SpecialUnary {
557 map_fn: |specs, range| {
558 let expr = MirScalarExpr::CallUnary {
559 func: UnaryFunc::TryParseMonotonicIso8601Timestamp(
560 crate::func::TryParseMonotonicIso8601Timestamp,
561 ),
562 expr: Box::new(MirScalarExpr::column(0)),
563 };
564 let eval = |d| specs.eval_result(expr.eval(&[d], specs.arena));
565
566 eagerly(range, |values| {
567 match values {
568 Values::Within(a, b) if a == b => eval(a),
569 Values::Within(a, b) => {
570 let spec = eval(a).union(eval(b));
571 let values_spec = if spec.nullable {
572 ResultSpec::value_all()
578 } else {
579 spec
580 };
581 values_spec.union(ResultSpec::null())
585 }
586 _ => ResultSpec::any_infallible(),
589 }
590 })
591 },
592 pushdownable: true,
593 }),
594 _ => None,
595 }
596 }
597}
598
599struct SpecialBinary {
604 map_fn: for<'a> fn(ResultSpec<'a>, ResultSpec<'a>) -> ResultSpec<'a>,
605 pushdownable: (bool, bool),
606}
607
608impl SpecialBinary {
609 fn for_func(func: &BinaryFunc) -> Option<SpecialBinary> {
611 fn eagerly<'b>(
615 left: ResultSpec<'b>,
616 right: ResultSpec<'b>,
617 value_fn: impl FnOnce(Values<'b>, Values<'b>) -> ResultSpec<'b>,
618 ) -> ResultSpec<'b> {
619 let result = match (left.values, right.values) {
620 (Values::Empty, _) | (_, Values::Empty) => ResultSpec::nothing(),
621 (l, r) => value_fn(l, r),
622 };
623 ResultSpec {
624 fallible: left.fallible || right.fallible || result.fallible,
625 nullable: left.nullable || right.nullable || result.nullable,
626 values: result.values,
627 }
628 }
629
630 fn jsonb_get_string<'b>(
631 left: ResultSpec<'b>,
632 right: ResultSpec<'b>,
633 stringify: bool,
634 ) -> ResultSpec<'b> {
635 eagerly(left, right, |left, right| {
636 let nested_spec = match (left, right) {
637 (Values::Nested(mut map_spec), Values::Within(key, key2)) if key == key2 => {
638 map_spec.remove(&key)
639 }
640 _ => None,
641 };
642
643 if let Some(field_spec) = nested_spec {
644 if stringify {
645 let values = match field_spec.values {
648 Values::Empty => Values::Empty,
649 Values::Within(min @ Datum::String(_), max @ Datum::String(_)) => {
650 Values::Within(min, max)
651 }
652 Values::Within(_, _) | Values::Nested(_) | Values::All => Values::All,
653 };
654 ResultSpec {
655 values,
656 ..field_spec
657 }
658 } else {
659 field_spec
660 }
661 } else {
662 ResultSpec::any_infallible()
667 }
668 })
669 }
670
671 fn eq<'b>(left: ResultSpec<'b>, right: ResultSpec<'b>) -> ResultSpec<'b> {
672 eagerly(left, right, |left, right| {
673 let maybe_true = match left.clone().intersect(right.clone()) {
675 Values::Empty => ResultSpec::nothing(),
676 _ => ResultSpec::value(Datum::True),
677 };
678
679 let maybe_false = match left.union(right) {
682 Values::Within(a, b) if a == b => ResultSpec::nothing(),
683 _ => ResultSpec::value(Datum::False),
684 };
685
686 maybe_true.union(maybe_false)
687 })
688 }
689
690 match func {
691 BinaryFunc::JsonbGetString(_) => Some(SpecialBinary {
692 map_fn: |l, r| jsonb_get_string(l, r, false),
693 pushdownable: (true, false),
694 }),
695 BinaryFunc::JsonbGetStringStringify(_) => Some(SpecialBinary {
696 map_fn: |l, r| jsonb_get_string(l, r, true),
697 pushdownable: (true, false),
698 }),
699 BinaryFunc::Eq(_) => Some(SpecialBinary {
700 map_fn: eq,
701 pushdownable: (true, true),
702 }),
703 _ => None,
704 }
705 }
706}
707
708#[derive(Clone, Debug)]
709pub struct ColumnSpec<'a> {
710 pub col_type: ReprColumnType,
711 pub range: ResultSpec<'a>,
712}
713
714#[derive(Clone, Debug)]
720pub struct ColumnSpecs<'a> {
721 pub relation: &'a ReprRelationType,
722 pub columns: Vec<ResultSpec<'a>>,
723 pub unmaterializables: BTreeMap<UnmaterializableFunc, ResultSpec<'a>>,
724 pub arena: &'a RowArena,
725}
726
727impl<'a> ColumnSpecs<'a> {
728 const MAX_EVAL_ARGS: usize = 6;
735
736 pub fn new(relation: &'a ReprRelationType, arena: &'a RowArena) -> Self {
739 let columns = relation
740 .column_types
741 .iter()
742 .map(|ct| ResultSpec::has_type(ct, false))
743 .collect();
744 ColumnSpecs {
745 relation,
746 columns,
747 unmaterializables: Default::default(),
748 arena,
749 }
750 }
751
752 pub fn push_column(&mut self, id: usize, update: ResultSpec<'a>) {
755 let range = self.columns.get_mut(id).expect("valid column id");
756 *range = range.clone().intersect(update);
757 }
758
759 pub fn push_unmaterializable(&mut self, func: UnmaterializableFunc, update: ResultSpec<'a>) {
762 let range = self
763 .unmaterializables
764 .entry(func.clone())
765 .or_insert_with(|| ResultSpec::has_type(&func.output_type(), true));
766 *range = range.clone().intersect(update);
767 }
768
769 fn eval_result<'b, E>(&self, result: Result<Datum<'b>, E>) -> ResultSpec<'a> {
770 match result {
771 Ok(Datum::Null) => ResultSpec {
772 nullable: true,
773 ..ResultSpec::nothing()
774 },
775 Ok(d) => ResultSpec {
776 values: Values::just(self.arena.make_datum(|packer| packer.push(d))),
777 ..ResultSpec::nothing()
778 },
779 Err(_) => ResultSpec {
780 fallible: true,
781 ..ResultSpec::nothing()
782 },
783 }
784 }
785
786 fn set_literal(expr: &mut MirScalarExpr, update: Result<Datum, EvalError>) {
787 match expr {
788 MirScalarExpr::Literal(literal, col_type) => match update {
789 Err(error) => *literal = Err(error),
790 Ok(datum) => {
791 assert!(
792 datum.is_instance_of(col_type),
793 "{datum:?} must be an instance of {col_type:?}"
794 );
795 match literal {
796 Ok(row) => row.packer().push(datum),
798 literal => *literal = Ok(Row::pack_slice(&[datum])),
799 }
800 }
801 },
802 _ => panic!("not a literal"),
803 }
804 }
805
806 fn set_argument(expr: &mut MirScalarExpr, arg: usize, value: Result<Datum, EvalError>) {
807 match (expr, arg) {
808 (MirScalarExpr::CallUnary { expr, .. }, 0) => Self::set_literal(expr, value),
809 (MirScalarExpr::CallBinary { expr1, .. }, 0) => Self::set_literal(expr1, value),
810 (MirScalarExpr::CallBinary { expr2, .. }, 1) => Self::set_literal(expr2, value),
811 (MirScalarExpr::CallVariadic { exprs, .. }, n) if n < exprs.len() => {
812 Self::set_literal(&mut exprs[n], value)
813 }
814 _ => panic!("illegal argument for expression"),
815 }
816 }
817
818 fn placeholder(col_type: ReprColumnType) -> MirScalarExpr {
822 MirScalarExpr::Literal(Err(EvalError::Internal("".into())), col_type)
823 }
824}
825
826impl<'a> Interpreter for ColumnSpecs<'a> {
827 type Summary = ColumnSpec<'a>;
828
829 fn column(&self, id: usize) -> Self::Summary {
830 let col_type = self.relation.column_types[id].clone();
831 let range = self.columns[id].clone();
832 ColumnSpec { col_type, range }
833 }
834
835 fn literal(&self, result: &Result<Row, EvalError>, col_type: &ReprColumnType) -> Self::Summary {
836 let col_type = col_type.clone();
837 let range = self.eval_result(result.as_ref().map(|row| {
838 self.arena
839 .make_datum(|packer| packer.push(row.unpack_first()))
840 }));
841 ColumnSpec { col_type, range }
842 }
843
844 fn unmaterializable(&self, func: &UnmaterializableFunc) -> Self::Summary {
845 let col_type = func.output_type();
846 let range = self
847 .unmaterializables
848 .get(func)
849 .cloned()
850 .unwrap_or_else(|| ResultSpec::has_type(&func.output_type(), true));
851 ColumnSpec { col_type, range }
852 }
853
854 fn unary(&self, func: &UnaryFunc, summary: Self::Summary) -> Self::Summary {
855 let fallible = func.could_error() || summary.range.fallible;
856 let mapped_spec = if let Some(special) = SpecialUnary::for_func(func) {
857 (special.map_fn)(self, summary.range)
858 } else {
859 let is_monotone = func.is_monotone();
860 let mut expr = MirScalarExpr::CallUnary {
861 func: func.clone(),
862 expr: Box::new(Self::placeholder(summary.col_type.clone())),
863 };
864 summary.range.flat_map(is_monotone, |datum| {
865 Self::set_argument(&mut expr, 0, datum);
866 self.eval_result(expr.eval(&[], self.arena))
867 })
868 };
869
870 let col_type = func.output_type(summary.col_type);
871
872 let range = mapped_spec.intersect(ResultSpec::has_type(&col_type, fallible));
873 ColumnSpec { col_type, range }
874 }
875
876 fn binary(
877 &self,
878 func: &BinaryFunc,
879 left: Self::Summary,
880 right: Self::Summary,
881 ) -> Self::Summary {
882 let (left_monotonic, right_monotonic) = func.is_monotone();
883 let fallible = func.could_error() || left.range.fallible || right.range.fallible;
884
885 let mapped_spec = if let Some(special) = SpecialBinary::for_func(func) {
886 (special.map_fn)(left.range, right.range)
887 } else {
888 let mut expr = MirScalarExpr::CallBinary {
889 func: func.clone(),
890 expr1: Box::new(Self::placeholder(left.col_type.clone())),
891 expr2: Box::new(Self::placeholder(right.col_type.clone())),
892 };
893 left.range.flat_map(left_monotonic, |left_result| {
894 Self::set_argument(&mut expr, 0, left_result);
895 right.range.flat_map(right_monotonic, |right_result| {
896 Self::set_argument(&mut expr, 1, right_result);
897 self.eval_result(expr.eval(&[], self.arena))
898 })
899 })
900 };
901
902 let col_type = func.output_type(&[left.col_type, right.col_type]);
903
904 let range = mapped_spec.intersect(ResultSpec::has_type(&col_type, fallible));
905 ColumnSpec { col_type, range }
906 }
907
908 fn variadic(&self, func: &VariadicFunc, args: Vec<Self::Summary>) -> Self::Summary {
909 let fallible = func.could_error() || args.iter().any(|s| s.range.fallible);
910 if func.is_associative() && args.len() > 2 {
911 return args
914 .into_iter()
915 .reduce(|a, b| self.variadic(func, vec![a, b]))
916 .expect("reducing over a non-empty argument list");
917 }
918
919 let mapped_spec = if args.len() >= Self::MAX_EVAL_ARGS {
920 ResultSpec::anything()
921 } else {
922 fn eval_loop<'a>(
923 is_monotonic: bool,
924 expr: &mut MirScalarExpr,
925 args: &[ColumnSpec<'a>],
926 index: usize,
927 datum_map: &mut impl FnMut(&MirScalarExpr) -> ResultSpec<'a>,
928 ) -> ResultSpec<'a> {
929 if index >= args.len() {
930 datum_map(expr)
931 } else {
932 args[index].range.flat_map(is_monotonic, |datum| {
933 ColumnSpecs::set_argument(expr, index, datum);
934 eval_loop(is_monotonic, expr, args, index + 1, datum_map)
935 })
936 }
937 }
938
939 let mut fn_expr = MirScalarExpr::CallVariadic {
940 func: func.clone(),
941 exprs: args
942 .iter()
943 .map(|spec| Self::placeholder(spec.col_type.clone()))
944 .collect(),
945 };
946 eval_loop(func.is_monotone(), &mut fn_expr, &args, 0, &mut |expr| {
947 self.eval_result(expr.eval(&[], self.arena))
948 })
949 };
950
951 let col_types = args.into_iter().map(|spec| spec.col_type).collect();
952 let col_type = func.output_type(col_types);
953
954 let range = mapped_spec.intersect(ResultSpec::has_type(&col_type, fallible));
955
956 ColumnSpec { col_type, range }
957 }
958
959 fn cond(&self, cond: Self::Summary, then: Self::Summary, els: Self::Summary) -> Self::Summary {
960 let col_type = then
961 .col_type
962 .union(&els.col_type)
963 .expect("failed type union for cond during abstract interpretation");
964
965 let range = cond
966 .range
967 .flat_map(true, |datum| match datum {
968 Ok(Datum::True) => then.range.clone(),
969 Ok(Datum::False) => els.range.clone(),
970 _ => ResultSpec::fails(),
971 })
972 .intersect(ResultSpec::has_type(&col_type, true));
973
974 ColumnSpec { col_type, range }
975 }
976}
977
978#[derive(Debug)]
987pub struct Trace;
988
989#[derive(Copy, Clone, Debug, PartialOrd, PartialEq, Ord, Eq)]
996pub enum TraceSummary {
997 Constant,
1000 Dynamic,
1005 Unknown,
1008}
1009
1010impl TraceSummary {
1011 fn apply_fn(self, pushdownable: bool) -> Self {
1016 match self {
1017 TraceSummary::Constant => TraceSummary::Constant,
1018 TraceSummary::Dynamic => match pushdownable {
1019 true => TraceSummary::Dynamic,
1020 false => TraceSummary::Unknown,
1021 },
1022 TraceSummary::Unknown => TraceSummary::Unknown,
1023 }
1024 }
1025
1026 pub fn pushdownable(self) -> bool {
1028 match self {
1029 TraceSummary::Constant | TraceSummary::Dynamic => true,
1030 TraceSummary::Unknown => false,
1031 }
1032 }
1033}
1034
1035impl Interpreter for Trace {
1036 type Summary = TraceSummary;
1037
1038 fn column(&self, _id: usize) -> Self::Summary {
1039 TraceSummary::Dynamic
1040 }
1041
1042 fn literal(
1043 &self,
1044 _result: &Result<Row, EvalError>,
1045 _col_type: &ReprColumnType,
1046 ) -> Self::Summary {
1047 TraceSummary::Constant
1048 }
1049
1050 fn unmaterializable(&self, _func: &UnmaterializableFunc) -> Self::Summary {
1051 TraceSummary::Dynamic
1052 }
1053
1054 fn unary(&self, func: &UnaryFunc, expr: Self::Summary) -> Self::Summary {
1055 let pushdownable = match SpecialUnary::for_func(func) {
1056 None => func.is_monotone(),
1057 Some(special) => special.pushdownable,
1058 };
1059 expr.apply_fn(pushdownable)
1060 }
1061
1062 fn binary(
1063 &self,
1064 func: &BinaryFunc,
1065 left: Self::Summary,
1066 right: Self::Summary,
1067 ) -> Self::Summary {
1068 let (left_pushdownable, right_pushdownable) = match SpecialBinary::for_func(func) {
1069 None => func.is_monotone(),
1070 Some(special) => special.pushdownable,
1071 };
1072 left.apply_fn(left_pushdownable)
1073 .max(right.apply_fn(right_pushdownable))
1074 }
1075
1076 fn variadic(&self, func: &VariadicFunc, exprs: Vec<Self::Summary>) -> Self::Summary {
1077 if !func.is_associative() && exprs.len() >= ColumnSpecs::MAX_EVAL_ARGS {
1078 return TraceSummary::Unknown;
1081 }
1082
1083 let pushdownable_fn = func.is_monotone();
1084 exprs
1085 .into_iter()
1086 .map(|pushdownable_arg| pushdownable_arg.apply_fn(pushdownable_fn))
1087 .max()
1088 .unwrap_or(TraceSummary::Constant)
1089 }
1090
1091 fn cond(&self, cond: Self::Summary, then: Self::Summary, els: Self::Summary) -> Self::Summary {
1092 let cond = cond.min(TraceSummary::Dynamic);
1095 cond.max(then).max(els)
1096 }
1097}
1098
1099#[cfg(test)]
1100mod tests {
1101 use itertools::Itertools;
1102 use mz_repr::adt::datetime::DateTimeUnits;
1103 use mz_repr::{Datum, PropDatum, RowArena, SqlScalarType};
1104 use proptest::prelude::*;
1105 use proptest::sample::{Index, select};
1106
1107 use crate::func::*;
1108 use crate::scalar::func::variadic::Concat;
1109 use crate::{BinaryFunc, MirScalarExpr, UnaryFunc};
1110
1111 use super::*;
1112
1113 #[derive(Debug)]
1114 struct ExpressionData {
1115 relation_type: ReprRelationType,
1116 specs: Vec<ResultSpec<'static>>,
1117 rows: Vec<Row>,
1118 expr: MirScalarExpr,
1119 }
1120
1121 const NUM_TYPE: ReprScalarType = ReprScalarType::Numeric;
1126 static SCALAR_TYPES: &[ReprScalarType] = &[
1127 ReprScalarType::Bool,
1128 ReprScalarType::Jsonb,
1129 NUM_TYPE,
1130 ReprScalarType::Date,
1131 ReprScalarType::Timestamp,
1132 ReprScalarType::MzTimestamp,
1133 ReprScalarType::String,
1134 ];
1135
1136 const INTERESTING_UNARY_FUNCS: &[UnaryFunc] = {
1137 &[
1138 UnaryFunc::CastNumericToMzTimestamp(CastNumericToMzTimestamp),
1139 UnaryFunc::NegNumeric(NegNumeric),
1140 UnaryFunc::CastJsonbToNumeric(CastJsonbToNumeric(None)),
1141 UnaryFunc::CastJsonbToBool(CastJsonbToBool),
1142 UnaryFunc::CastJsonbToString(CastJsonbToString),
1143 UnaryFunc::DateTruncTimestamp(DateTruncTimestamp(DateTimeUnits::Epoch)),
1144 UnaryFunc::ExtractTimestamp(ExtractTimestamp(DateTimeUnits::Epoch)),
1145 UnaryFunc::ExtractDate(ExtractDate(DateTimeUnits::Epoch)),
1146 UnaryFunc::Not(Not),
1147 UnaryFunc::IsNull(IsNull),
1148 UnaryFunc::IsFalse(IsFalse),
1149 UnaryFunc::TryParseMonotonicIso8601Timestamp(TryParseMonotonicIso8601Timestamp),
1150 ]
1151 };
1152
1153 fn unary_typecheck(func: &UnaryFunc, arg: &ReprColumnType) -> bool {
1154 use UnaryFunc::*;
1155 match func {
1156 CastNumericToMzTimestamp(_) | NegNumeric(_) => arg.scalar_type == NUM_TYPE,
1157 CastJsonbToNumeric(_) | CastJsonbToBool(_) | CastJsonbToString(_) => {
1158 arg.scalar_type == ReprScalarType::Jsonb
1159 }
1160 ExtractTimestamp(_) | DateTruncTimestamp(_) => {
1161 arg.scalar_type == ReprScalarType::Timestamp
1162 }
1163 ExtractDate(_) => arg.scalar_type == ReprScalarType::Date,
1164 Not(_) => arg.scalar_type == ReprScalarType::Bool,
1165 IsNull(_) => true,
1166 TryParseMonotonicIso8601Timestamp(_) => arg.scalar_type == ReprScalarType::String,
1167 _ => false,
1168 }
1169 }
1170
1171 fn interesting_binary_funcs() -> Vec<BinaryFunc> {
1172 vec![
1173 AddTimestampInterval.into(),
1174 AddNumeric.into(),
1175 SubNumeric.into(),
1176 MulNumeric.into(),
1177 DivNumeric.into(),
1178 Eq.into(),
1179 Lt.into(),
1180 Gt.into(),
1181 Lte.into(),
1182 Gte.into(),
1183 DateTruncUnitsTimestamp.into(),
1184 JsonbGetString.into(),
1185 JsonbGetStringStringify.into(),
1186 ]
1187 }
1188
1189 fn binary_typecheck(func: &BinaryFunc, arg0: &ReprColumnType, arg1: &ReprColumnType) -> bool {
1190 use BinaryFunc::*;
1191 match func {
1192 AddTimestampInterval(_) => {
1193 arg0.scalar_type == ReprScalarType::Timestamp
1194 && arg1.scalar_type == ReprScalarType::Interval
1195 }
1196 AddNumeric(_) | SubNumeric(_) | MulNumeric(_) | DivNumeric(_) => {
1197 arg0.scalar_type == NUM_TYPE && arg1.scalar_type == NUM_TYPE
1198 }
1199 Eq(_) | Lt(_) | Gt(_) | Lte(_) | Gte(_) => arg0.scalar_type == arg1.scalar_type,
1200 DateTruncTimestamp(_) => {
1201 arg0.scalar_type == ReprScalarType::String
1202 && arg1.scalar_type == ReprScalarType::Timestamp
1203 }
1204 JsonbGetString(_) | JsonbGetStringStringify(_) => {
1205 arg0.scalar_type == ReprScalarType::Jsonb
1206 && arg1.scalar_type == ReprScalarType::String
1207 }
1208 _ => false,
1209 }
1210 }
1211
1212 const INTERESTING_VARIADIC_FUNCS: &[VariadicFunc] = {
1213 use crate::scalar::func::variadic as v;
1214 use VariadicFunc::*;
1215 &[
1216 Coalesce(v::Coalesce),
1217 Greatest(v::Greatest),
1218 Least(v::Least),
1219 And(v::And),
1220 Or(v::Or),
1221 Concat(v::Concat),
1222 ConcatWs(v::ConcatWs),
1223 ]
1224 };
1225
1226 fn variadic_typecheck(func: &VariadicFunc, args: &[ReprColumnType]) -> bool {
1227 use VariadicFunc::*;
1228 fn all_eq<'a>(
1229 iter: impl IntoIterator<Item = &'a ReprColumnType>,
1230 other: &ReprScalarType,
1231 ) -> bool {
1232 iter.into_iter().all(|t| t.scalar_type == *other)
1233 }
1234 match func {
1235 Coalesce(_) | Greatest(_) | Least(_) => match args {
1236 [] => true,
1237 [first, rest @ ..] => all_eq(rest, &first.scalar_type),
1238 },
1239 And(_) | Or(_) => all_eq(args, &ReprScalarType::Bool),
1240 Concat(_) => all_eq(args, &ReprScalarType::String),
1241 ConcatWs(_) => args.len() > 1 && all_eq(args, &ReprScalarType::String),
1242 _ => false,
1243 }
1244 }
1245
1246 fn gen_datums_for_type(typ: &ReprColumnType) -> BoxedStrategy<Datum<'static>> {
1247 let mut values: Vec<Datum<'static>> = SqlScalarType::from_repr(&typ.scalar_type)
1248 .interesting_datums()
1249 .collect();
1250 if typ.nullable {
1251 values.push(Datum::Null)
1252 }
1253 select(values).boxed()
1254 }
1255
1256 fn gen_column() -> impl Strategy<Value = (ReprColumnType, Datum<'static>, ResultSpec<'static>)>
1257 {
1258 let col_type = (select(SCALAR_TYPES), any::<bool>())
1259 .prop_map(|(t, b)| t.nullable(b))
1260 .prop_filter("need at least one value", |c| {
1261 SqlScalarType::from_repr(&c.scalar_type)
1262 .interesting_datums()
1263 .count()
1264 > 0
1265 });
1266
1267 let result_spec = select(vec![
1268 ResultSpec::nothing(),
1269 ResultSpec::null(),
1270 ResultSpec::anything(),
1271 ResultSpec::value_all(),
1272 ]);
1273
1274 (col_type, result_spec).prop_flat_map(|(col, result_spec)| {
1275 gen_datums_for_type(&col).prop_map(move |datum| {
1276 let result_spec = result_spec.clone().union(ResultSpec::value(datum));
1277 (col.clone(), datum, result_spec)
1278 })
1279 })
1280 }
1281
1282 fn gen_expr_for_relation(
1283 relation: &ReprRelationType,
1284 ) -> BoxedStrategy<(MirScalarExpr, ReprColumnType)> {
1285 let column_gen = {
1286 let column_types = relation.column_types.clone();
1287 any::<Index>()
1288 .prop_map(move |idx| {
1289 let id = idx.index(column_types.len());
1290 (MirScalarExpr::column(id), column_types[id].clone())
1291 })
1292 .boxed()
1293 };
1294
1295 let literal_gen = (select(SCALAR_TYPES), any::<bool>())
1296 .prop_map(|(s, b)| s.nullable(b))
1297 .prop_flat_map(|ct| {
1298 let error_gen = any::<EvalError>().prop_map(Err).boxed();
1299 let value_gen = gen_datums_for_type(&ct)
1300 .prop_map(move |datum| Ok(Row::pack_slice(&[datum])))
1301 .boxed();
1302 error_gen.prop_union(value_gen).prop_map(move |result| {
1303 (MirScalarExpr::Literal(result, ct.clone()), ct.clone())
1304 })
1305 })
1306 .boxed();
1307
1308 column_gen
1309 .prop_union(literal_gen)
1310 .prop_recursive(4, 64, 8, |self_gen| {
1311 let unary_gen = (select(INTERESTING_UNARY_FUNCS), self_gen.clone())
1312 .prop_filter_map("unary func", |(func, (expr_in, type_in))| {
1313 if !unary_typecheck(&func, &type_in) {
1314 return None;
1315 }
1316 let type_out = func.output_type(type_in);
1317 let expr_out = MirScalarExpr::CallUnary {
1318 func,
1319 expr: Box::new(expr_in),
1320 };
1321 Some((expr_out, type_out))
1322 })
1323 .boxed();
1324 let binary_gen = (
1325 select(interesting_binary_funcs()),
1326 self_gen.clone(),
1327 self_gen.clone(),
1328 )
1329 .prop_filter_map(
1330 "binary func",
1331 |(func, (expr_left, type_left), (expr_right, type_right))| {
1332 if !binary_typecheck(&func, &type_left, &type_right) {
1333 return None;
1334 }
1335 let type_out = func.output_type(&[type_left, type_right]);
1336 let expr_out = MirScalarExpr::CallBinary {
1337 func,
1338 expr1: Box::new(expr_left),
1339 expr2: Box::new(expr_right),
1340 };
1341 Some((expr_out, type_out))
1342 },
1343 )
1344 .boxed();
1345 let variadic_gen = (
1346 select(INTERESTING_VARIADIC_FUNCS),
1347 prop::collection::vec(self_gen, 1..4),
1348 )
1349 .prop_filter_map("variadic func", |(func, exprs)| {
1350 let (exprs_in, type_in): (_, Vec<_>) = exprs.into_iter().unzip();
1351 if !variadic_typecheck(&func, &type_in) {
1352 return None;
1353 }
1354 let type_out = func.output_type(type_in);
1355 let expr_out = MirScalarExpr::CallVariadic {
1356 func,
1357 exprs: exprs_in,
1358 };
1359 Some((expr_out, type_out))
1360 })
1361 .boxed();
1362
1363 unary_gen
1364 .prop_union(binary_gen)
1365 .boxed()
1366 .prop_union(variadic_gen)
1367 })
1368 .boxed()
1369 }
1370
1371 fn gen_expr_data() -> impl Strategy<Value = ExpressionData> {
1372 let columns = prop::collection::vec(gen_column(), 1..10);
1373 columns.prop_flat_map(|data| {
1374 let (columns, datums, specs): (Vec<_>, Vec<_>, Vec<_>) = data.into_iter().multiunzip();
1375 let relation = ReprRelationType::new(columns);
1376 let row = Row::pack_slice(&datums);
1377 gen_expr_for_relation(&relation).prop_map(move |(expr, _)| ExpressionData {
1378 relation_type: relation.clone(),
1379 specs: specs.clone(),
1380 rows: vec![row.clone()],
1381 expr,
1382 })
1383 })
1384 }
1385
1386 #[mz_ore::test]
1387 #[cfg_attr(miri, ignore)] fn test_trivial_spec_matches() {
1389 fn check(datum: PropDatum) -> Result<(), TestCaseError> {
1390 let datum: Datum = (&datum).into();
1391 let spec = if datum.is_null() {
1392 ResultSpec::null()
1393 } else {
1394 ResultSpec::value(datum)
1395 };
1396 assert!(spec.may_contain(datum));
1397 Ok(())
1398 }
1399
1400 proptest!(|(datum in mz_repr::arb_datum(true))| {
1401 check(datum)?;
1402 });
1403
1404 assert!(ResultSpec::fails().may_fail());
1405 }
1406
1407 #[mz_ore::test]
1408 #[cfg_attr(miri, ignore)] fn test_equivalence() {
1410 fn check(data: ExpressionData) -> Result<(), TestCaseError> {
1411 let ExpressionData {
1412 relation_type,
1413 specs,
1414 rows,
1415 expr,
1416 } = data;
1417
1418 let arena = RowArena::new();
1422 let mut interpreter = ColumnSpecs::new(&relation_type, &arena);
1423 for (id, spec) in specs.into_iter().enumerate() {
1424 interpreter.push_column(id, spec);
1425 }
1426
1427 let spec = interpreter.expr(&expr);
1428
1429 for row in &rows {
1430 let datums: Vec<_> = row.iter().collect();
1431 let eval_result = expr.eval(&datums, &arena);
1432 match eval_result {
1433 Ok(value) => {
1434 assert!(spec.range.may_contain(value))
1435 }
1436 Err(_) => {
1437 assert!(spec.range.may_fail());
1438 }
1439 }
1440 }
1441
1442 Ok(())
1443 }
1444
1445 proptest!(|(data in gen_expr_data())| {
1446 check(data)?;
1447 });
1448 }
1449
1450 #[mz_ore::test]
1451 fn test_mfp() {
1452 use MirScalarExpr::*;
1454
1455 let mfp = MapFilterProject {
1456 expressions: vec![],
1457 predicates: vec![
1458 (
1460 1,
1461 CallUnary {
1462 func: UnaryFunc::IsNull(IsNull),
1463 expr: Box::new(CallBinary {
1464 func: MulInt32.into(),
1465 expr1: Box::new(MirScalarExpr::column(0)),
1466 expr2: Box::new(MirScalarExpr::column(0)),
1467 }),
1468 },
1469 ),
1470 (
1472 1,
1473 CallBinary {
1474 func: Eq.into(),
1475 expr1: Box::new(MirScalarExpr::column(0)),
1476 expr2: Box::new(MirScalarExpr::literal_ok(
1477 Datum::Int32(1727694505),
1478 ReprScalarType::Int32,
1479 )),
1480 },
1481 ),
1482 ],
1483 projection: vec![],
1484 input_arity: 1,
1485 };
1486
1487 let relation = ReprRelationType::new(vec![ReprScalarType::Int32.nullable(true)]);
1488 let arena = RowArena::new();
1489 let mut interpreter = ColumnSpecs::new(&relation, &arena);
1490 interpreter.push_column(0, ResultSpec::value(Datum::Int32(-1294725158)));
1491 let spec = interpreter.mfp_filter(&mfp);
1492 assert!(spec.range.may_fail());
1493 }
1494
1495 #[mz_ore::test]
1496 fn test_concat() {
1497 let expr = MirScalarExpr::call_variadic(
1498 Concat,
1499 vec![
1500 MirScalarExpr::column(0),
1501 MirScalarExpr::literal_ok(Datum::String("a"), ReprScalarType::String),
1502 MirScalarExpr::literal_ok(Datum::String("b"), ReprScalarType::String),
1503 ],
1504 );
1505
1506 let relation = ReprRelationType::new(vec![ReprScalarType::String.nullable(false)]);
1507 let arena = RowArena::new();
1508 let interpreter = ColumnSpecs::new(&relation, &arena);
1509 let spec = interpreter.expr(&expr);
1510 assert!(spec.range.may_contain(Datum::String("blab")));
1511 }
1512
1513 #[mz_ore::test]
1514 fn test_eval_range() {
1515 let period_ms = MirScalarExpr::literal_ok(Datum::Int64(10), ReprScalarType::Int64);
1517 let expr = MirScalarExpr::CallBinary {
1518 func: Gte.into(),
1519 expr1: Box::new(MirScalarExpr::CallUnmaterializable(
1520 UnmaterializableFunc::MzNow,
1521 )),
1522 expr2: Box::new(MirScalarExpr::CallUnary {
1523 func: UnaryFunc::CastInt64ToMzTimestamp(CastInt64ToMzTimestamp),
1524 expr: Box::new(MirScalarExpr::CallBinary {
1525 func: MulInt64.into(),
1526 expr1: Box::new(period_ms.clone()),
1527 expr2: Box::new(MirScalarExpr::CallBinary {
1528 func: DivInt64.into(),
1529 expr1: Box::new(MirScalarExpr::column(0)),
1530 expr2: Box::new(period_ms),
1531 }),
1532 }),
1533 }),
1534 };
1535 let relation = ReprRelationType::new(vec![ReprScalarType::Int64.nullable(false)]);
1536
1537 {
1538 let arena = RowArena::new();
1540 let mut interpreter = ColumnSpecs::new(&relation, &arena);
1541 interpreter.push_unmaterializable(
1542 UnmaterializableFunc::MzNow,
1543 ResultSpec::value_between(
1544 Datum::MzTimestamp(10.into()),
1545 Datum::MzTimestamp(20.into()),
1546 ),
1547 );
1548 interpreter.push_column(0, ResultSpec::value_between(30i64.into(), 40i64.into()));
1549
1550 let range_out = interpreter.expr(&expr).range;
1551 assert!(range_out.may_contain(Datum::False));
1552 assert!(!range_out.may_contain(Datum::True));
1553 assert!(!range_out.may_contain(Datum::Null));
1554 assert!(!range_out.may_fail());
1555 }
1556
1557 {
1558 let arena = RowArena::new();
1560 let mut interpreter = ColumnSpecs::new(&relation, &arena);
1561 interpreter.push_unmaterializable(
1562 UnmaterializableFunc::MzNow,
1563 ResultSpec::value_between(
1564 Datum::MzTimestamp(10.into()),
1565 Datum::MzTimestamp(35.into()),
1566 ),
1567 );
1568 interpreter.push_column(0, ResultSpec::value_between(30i64.into(), 40i64.into()));
1569
1570 let range_out = interpreter.expr(&expr).range;
1571 assert!(range_out.may_contain(Datum::False));
1572 assert!(range_out.may_contain(Datum::True));
1573 assert!(!range_out.may_contain(Datum::Null));
1574 assert!(!range_out.may_fail());
1575 }
1576 }
1577
1578 #[mz_ore::test]
1579 #[cfg_attr(miri, ignore)] fn test_jsonb() {
1581 let arena = RowArena::new();
1582
1583 let expr = MirScalarExpr::column(0)
1584 .call_binary(
1585 MirScalarExpr::literal_ok(Datum::from("ts"), ReprScalarType::String),
1586 JsonbGetString,
1587 )
1588 .call_unary(CastJsonbToNumeric(None));
1589
1590 let relation = ReprRelationType::new(vec![ReprScalarType::Jsonb.nullable(true)]);
1591 let mut interpreter = ColumnSpecs::new(&relation, &arena);
1592 interpreter.push_column(
1593 0,
1594 ResultSpec::map_spec(
1595 [(
1596 "ts".into(),
1597 ResultSpec::value_between(
1598 Datum::Numeric(100.into()),
1599 Datum::Numeric(300.into()),
1600 ),
1601 )]
1602 .into_iter()
1603 .collect(),
1604 ),
1605 );
1606
1607 let range_out = interpreter.expr(&expr).range;
1608 assert!(!range_out.may_contain(Datum::Numeric(0.into())));
1609 assert!(range_out.may_contain(Datum::Numeric(200.into())));
1610 assert!(!range_out.may_contain(Datum::Numeric(400.into())));
1611 }
1612
1613 #[mz_ore::test]
1614 fn test_like() {
1615 let arena = RowArena::new();
1616
1617 let expr = MirScalarExpr::CallUnary {
1618 func: UnaryFunc::IsLikeMatch(IsLikeMatch(
1619 crate::like_pattern::compile("%whatever%", true).unwrap(),
1620 )),
1621 expr: Box::new(MirScalarExpr::column(0)),
1622 };
1623
1624 let relation = ReprRelationType::new(vec![ReprScalarType::String.nullable(true)]);
1625 let mut interpreter = ColumnSpecs::new(&relation, &arena);
1626 interpreter.push_column(
1627 0,
1628 ResultSpec::value_between(Datum::String("aardvark"), Datum::String("zebra")),
1629 );
1630
1631 let range_out = interpreter.expr(&expr).range;
1632 assert!(
1633 !range_out.fallible,
1634 "like function should not error on non-error input"
1635 );
1636 assert!(range_out.may_contain(Datum::True));
1637 assert!(range_out.may_contain(Datum::False));
1638 assert!(range_out.may_contain(Datum::Null));
1639 }
1640
1641 #[mz_ore::test]
1642 fn test_try_parse_monotonic_iso8601_timestamp() {
1643 use chrono::NaiveDateTime;
1644
1645 let arena = RowArena::new();
1646
1647 let expr = MirScalarExpr::CallUnary {
1648 func: UnaryFunc::TryParseMonotonicIso8601Timestamp(TryParseMonotonicIso8601Timestamp),
1649 expr: Box::new(MirScalarExpr::column(0)),
1650 };
1651
1652 let relation = ReprRelationType::new(vec![ReprScalarType::String.nullable(true)]);
1653 let mut interpreter = ColumnSpecs::new(&relation, &arena);
1655 interpreter.push_column(
1656 0,
1657 ResultSpec::value_between(
1658 Datum::String("2024-01-11T00:00:00.000Z"),
1659 Datum::String("2024-01-11T20:00:00.000Z"),
1660 ),
1661 );
1662
1663 let timestamp = |ts| {
1664 Datum::Timestamp(
1665 NaiveDateTime::parse_from_str(ts, "%Y-%m-%dT%H:%M:%S")
1666 .unwrap()
1667 .try_into()
1668 .unwrap(),
1669 )
1670 };
1671
1672 let range_out = interpreter.expr(&expr).range;
1673 assert!(!range_out.fallible);
1674 assert!(range_out.nullable);
1675 assert!(!range_out.may_contain(timestamp("2024-01-10T10:00:00")));
1676 assert!(range_out.may_contain(timestamp("2024-01-11T10:00:00")));
1677 assert!(!range_out.may_contain(timestamp("2024-01-12T10:00:00")));
1678
1679 let mut interpreter = ColumnSpecs::new(&relation, &arena);
1681 interpreter.push_column(
1682 0,
1683 ResultSpec::value_between(Datum::String("2024-01-1"), Datum::String("2024-01-2")),
1684 );
1685
1686 let range_out = interpreter.expr(&expr).range;
1687 assert!(!range_out.fallible);
1688 assert!(range_out.nullable);
1689 assert!(range_out.may_contain(timestamp("2024-01-10T10:00:00")));
1690 assert!(range_out.may_contain(timestamp("2024-01-11T10:00:00")));
1691 assert!(range_out.may_contain(timestamp("2024-01-12T10:00:00")));
1692
1693 let mut interpreter = ColumnSpecs::new(&relation, &arena);
1695 interpreter.push_column(
1696 0,
1697 ResultSpec::value_between(
1698 Datum::String("2024-01-1"),
1699 Datum::String("2024-01-12T10:00:00"),
1700 )
1701 .union(ResultSpec::null()),
1702 );
1703
1704 let range_out = interpreter.expr(&expr).range;
1705 assert!(!range_out.fallible);
1706 assert!(range_out.nullable);
1707 assert!(range_out.may_contain(timestamp("2024-01-10T10:00:00")));
1708 assert!(range_out.may_contain(timestamp("2024-01-11T10:00:00")));
1709 assert!(range_out.may_contain(timestamp("2024-01-12T10:00:00")));
1710
1711 let mut interpreter = ColumnSpecs::new(&relation, &arena);
1713 interpreter.push_column(
1714 0,
1715 ResultSpec::value_between(
1716 Datum::String("2024-01-11T10:00:00.000Z"),
1717 Datum::String("2024-01-11T10:00:00.000Z"),
1718 ),
1719 );
1720
1721 let range_out = interpreter.expr(&expr).range;
1722 assert!(!range_out.fallible);
1723 assert!(!range_out.nullable);
1724 assert!(!range_out.may_contain(timestamp("2024-01-10T10:00:00")));
1725 assert!(range_out.may_contain(timestamp("2024-01-11T10:00:00")));
1726 assert!(!range_out.may_contain(timestamp("2024-01-12T10:00:00")));
1727 }
1728
1729 #[mz_ore::test]
1730 fn test_inequality() {
1731 let arena = RowArena::new();
1732
1733 let expr = MirScalarExpr::column(0).call_binary(
1734 MirScalarExpr::CallUnmaterializable(UnmaterializableFunc::MzNow),
1735 Gte,
1736 );
1737
1738 let relation = ReprRelationType::new(vec![ReprScalarType::MzTimestamp.nullable(true)]);
1739 let mut interpreter = ColumnSpecs::new(&relation, &arena);
1740 interpreter.push_column(
1741 0,
1742 ResultSpec::value_between(
1743 Datum::MzTimestamp(1704736444949u64.into()),
1744 Datum::MzTimestamp(1704736444949u64.into()),
1745 )
1746 .union(ResultSpec::null()),
1747 );
1748 interpreter.push_unmaterializable(
1749 UnmaterializableFunc::MzNow,
1750 ResultSpec::value_between(
1751 Datum::MzTimestamp(1704738791000u64.into()),
1752 Datum::MzTimestamp(18446744073709551615u64.into()),
1753 ),
1754 );
1755
1756 let range_out = interpreter.expr(&expr).range;
1757 assert!(
1758 !range_out.fallible,
1759 "<= function should not error on non-error input"
1760 );
1761 assert!(!range_out.may_contain(Datum::True));
1762 assert!(range_out.may_contain(Datum::False));
1763 assert!(range_out.may_contain(Datum::Null));
1764 }
1765
1766 #[mz_ore::test]
1767 fn test_trace() {
1768 use super::Trace;
1769
1770 let expr = MirScalarExpr::column(0).call_binary(
1771 MirScalarExpr::column(1)
1772 .call_binary(MirScalarExpr::column(3).call_unary(NegInt64), AddInt64),
1773 Gte,
1774 );
1775 let summary = Trace.expr(&expr);
1776 assert!(summary.pushdownable());
1777 }
1778}