Skip to content

Commit 030fda2

Browse files
committed
optimizer: avoid deconstructing temporal filters in ANF
1 parent 21172ca commit 030fda2

File tree

3 files changed

+94
-1
lines changed

3 files changed

+94
-1
lines changed

src/expr/src/linear.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1490,6 +1490,7 @@ pub fn memoize_expr(
14901490
if let MirScalarExpr::If { cond, .. } = e {
14911491
return Some(vec![cond]);
14921492
}
1493+
14931494
// We should not eagerly memoize `COALESCE` expressions after the first,
14941495
// as they are only meant to be evaluated if the preceding expressions
14951496
// evaluate to NULL. We could memoize any preceding by expressions that
@@ -1501,6 +1502,13 @@ pub fn memoize_expr(
15011502
{
15021503
return Some(exprs.iter_mut().take(1).collect());
15031504
}
1505+
1506+
// We should not deconstruct temporal filters, because `MfpPlan::create_from` expects
1507+
// those to be in a specific form.
1508+
if e.is_temporal_filter() {
1509+
return Some(vec![]);
1510+
}
1511+
15041512
None
15051513
},
15061514
&mut |e| {
@@ -1520,6 +1528,8 @@ pub fn memoize_expr(
15201528
}
15211529
}
15221530
_ => {
1531+
// TODO: OOO (Optimizer Optimization Opportunity):
1532+
// we are quadratic in expression size because of this .iter().position
15231533
if let Some(position) = memoized_parts.iter().position(|e2| e2 == e) {
15241534
// Any complex expression that already exists as a prior column can
15251535
// be replaced by a reference to that column.
@@ -1634,6 +1644,7 @@ pub mod util {
16341644
pub mod plan {
16351645
use std::iter;
16361646

1647+
use mz_ore::soft_assert_or_log;
16371648
use mz_proto::{IntoRustIfSome, ProtoType, RustType, TryFromProtoError};
16381649
use mz_repr::{Datum, Diff, Row, RowArena};
16391650
use proptest::prelude::*;
@@ -1843,6 +1854,7 @@ pub mod plan {
18431854
});
18441855

18451856
for predicate in temporal.into_iter() {
1857+
let is_temporal_filter = predicate.is_temporal_filter(); // for the asserts
18461858
// Supported temporal predicates are exclusively binary operators.
18471859
if let MirScalarExpr::CallBinary {
18481860
mut func,
@@ -1863,6 +1875,10 @@ pub mod plan {
18631875
BinaryFunc::Gt => BinaryFunc::Lt,
18641876
BinaryFunc::Gte => BinaryFunc::Lte,
18651877
x => {
1878+
soft_assert_or_log!(
1879+
!is_temporal_filter,
1880+
"MfpPlan::create_from and is_temporal_filter should agree"
1881+
);
18661882
return Err(format!(
18671883
"Unsupported binary temporal operation: {:?}",
18681884
x
@@ -1876,6 +1892,10 @@ pub mod plan {
18761892
|| *expr1
18771893
!= MirScalarExpr::CallUnmaterializable(UnmaterializableFunc::MzNow)
18781894
{
1895+
soft_assert_or_log!(
1896+
!is_temporal_filter,
1897+
"MfpPlan::create_from and is_temporal_filter should agree"
1898+
);
18791899
return Err(format!(
18801900
"Unsupported temporal predicate. Note: `mz_now()` must be directly compared to a mz_timestamp-castable expression. Expression found: {}",
18811901
MirScalarExpr::CallBinary { func, expr1, expr2 },
@@ -1913,7 +1933,15 @@ pub mod plan {
19131933
));
19141934
}
19151935
}
1936+
soft_assert_or_log!(
1937+
is_temporal_filter,
1938+
"MfpPlan::create_from and is_temporal_filter should agree"
1939+
);
19161940
} else {
1941+
soft_assert_or_log!(
1942+
!is_temporal_filter,
1943+
"MfpPlan::create_from and is_temporal_filter should agree"
1944+
);
19171945
return Err(format!(
19181946
"Unsupported temporal predicate. Note: `mz_now()` must be directly compared to a non-temporal expression of mz_timestamp-castable type. Expression found: {}",
19191947
predicate,

src/expr/src/scalar.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,28 @@ impl MirScalarExpr {
728728
)
729729
}
730730

731+
pub fn is_temporal_filter(&self) -> bool {
732+
if let MirScalarExpr::CallBinary { func, expr1, expr2 } = self {
733+
if (matches!(
734+
**expr1,
735+
MirScalarExpr::CallUnmaterializable(UnmaterializableFunc::MzNow)
736+
) || matches!(
737+
**expr2,
738+
MirScalarExpr::CallUnmaterializable(UnmaterializableFunc::MzNow)
739+
)) && matches!(
740+
func,
741+
BinaryFunc::Eq
742+
| BinaryFunc::Gt
743+
| BinaryFunc::Gte
744+
| BinaryFunc::Lt
745+
| BinaryFunc::Lte
746+
) {
747+
return true;
748+
}
749+
}
750+
false
751+
}
752+
731753
#[deprecated = "Use `might_error` instead"]
732754
pub fn contains_error_if_null(&self) -> bool {
733755
let mut worklist = vec![self];

test/sqllogictest/temporal.slt

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,9 @@ WHERE insert_ts >= mz_now() - 30;
245245
statement ok
246246
CREATE TABLE t2(ts timestamp, x int);
247247

248+
statement ok
249+
INSERT INTO t2 VALUES ('2024-01-03 00:00:00', 5), ('2024-01-04 00:00:00', 6);
250+
248251
statement ok
249252
SELECT *
250253
FROM t2
@@ -256,6 +259,9 @@ SELECT *
256259
FROM t2
257260
WHERE ts + INTERVAL '30' minutes >= mz_now();
258261

262+
statement ok
263+
SELECT * FROM mv1;
264+
259265
query error db error: ERROR: WHERE clause error: operator does not exist: mz_timestamp \- interval
260266
SELECT *
261267
FROM t2
@@ -313,15 +319,21 @@ WHERE
313319
ts + INTERVAL '30' minutes >= mz_now();
314320

315321
statement ok
316-
SELECT * FROM v_one_off;
322+
SELECT * FROM v_maintained;
317323

318324
statement ok
319325
CREATE DEFAULT INDEX ON v_maintained;
320326

327+
statement ok
328+
SELECT * FROM v_maintained;
329+
321330
statement ok
322331
CREATE MATERIALIZED VIEW mv2 AS
323332
SELECT * FROM v_maintained;
324333

334+
statement ok
335+
SELECT * FROM mv2;
336+
325337
simple
326338
DECLARE c CURSOR FOR SUBSCRIBE v_maintained;
327339
FETCH 0 c;
@@ -338,6 +350,9 @@ WHERE
338350
ts + INTERVAL '30' minutes >= mz_now()
339351
AND x != 7;
340352

353+
statement ok
354+
SELECT * FROM mv3;
355+
341356
# UNION ALL workaround for OR
342357

343358
query error db error: ERROR: Unsupported temporal predicate\. Note: `mz_now\(\)` must be directly compared to a non\-temporal expression of mz_timestamp\-castable type\. Expression found: \(\(#1\{x\} = 7\) OR \(timestamp_to_mz_timestamp\(\(#0\{ts\} \+ 00:30:00\)\) >= mz_now\(\)\)\)
@@ -363,3 +378,31 @@ UNION ALL
363378
ts + INTERVAL '30' minutes >= mz_now()
364379
AND x != 7
365380
);
381+
382+
statement ok
383+
SELECT * FROM mv4;
384+
385+
# Regression test for https://github.com/MaterializeInc/database-issues/issues/9643
386+
# Two different temporal filters on different reads of the same source.
387+
388+
statement ok
389+
SELECT *
390+
FROM t2
391+
WHERE ts + INTERVAL '30' minutes >= mz_now()
392+
UNION ALL
393+
SELECT *
394+
FROM t2
395+
WHERE ts + INTERVAL '60' minutes >= mz_now();
396+
397+
statement ok
398+
CREATE MATERIALIZED VIEW mv5 AS
399+
SELECT *
400+
FROM t2
401+
WHERE ts + INTERVAL '30' minutes >= mz_now()
402+
UNION ALL
403+
SELECT *
404+
FROM t2
405+
WHERE ts + INTERVAL '60' minutes >= mz_now();
406+
407+
statement ok
408+
SELECT * FROM mv5;

0 commit comments

Comments
 (0)