This is an automated email from the ASF dual-hosted git repository.
asolimando pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/main by this push:
new e25688995d [CALCITE-7636] Materialized view union rewriting drops rows
when the view filter is not null-rejecting
e25688995d is described below
commit e25688995dab5ea13a8eea5cad14ae6e27e0ea29
Author: Alessandro Solimando <[email protected]>
AuthorDate: Fri Jul 3 11:26:50 2026 +0200
[CALCITE-7636] Materialized view union rewriting drops rows when the view
filter is not null-rejecting
---
.../materialize/MaterializedViewAggregateRule.java | 2 +-
.../materialize/MaterializedViewJoinRule.java | 3 ++-
.../apache/calcite/test/MaterializationTest.java | 23 +++++++++++++++++
.../test/MaterializedViewRelOptRulesTest.java | 30 ++++++++++++++++++++++
4 files changed, 56 insertions(+), 2 deletions(-)
diff --git
a/core/src/main/java/org/apache/calcite/rel/rules/materialize/MaterializedViewAggregateRule.java
b/core/src/main/java/org/apache/calcite/rel/rules/materialize/MaterializedViewAggregateRule.java
index e6cf9e1058..404d0200ba 100644
---
a/core/src/main/java/org/apache/calcite/rel/rules/materialize/MaterializedViewAggregateRule.java
+++
b/core/src/main/java/org/apache/calcite/rel/rules/materialize/MaterializedViewAggregateRule.java
@@ -291,7 +291,7 @@ public abstract class MaterializedViewAggregateRule<C
extends MaterializedViewAg
otherCompensationPred = newOtherCompensationPred;
}
final RexNode queryCompensationPred =
- RexUtil.not(
+ rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_TRUE,
RexUtil.composeConjunction(rexBuilder,
ImmutableList.of(compensationColumnsEquiPred,
otherCompensationPred)));
diff --git
a/core/src/main/java/org/apache/calcite/rel/rules/materialize/MaterializedViewJoinRule.java
b/core/src/main/java/org/apache/calcite/rel/rules/materialize/MaterializedViewJoinRule.java
index 803033a6cc..b420811a55 100644
---
a/core/src/main/java/org/apache/calcite/rel/rules/materialize/MaterializedViewJoinRule.java
+++
b/core/src/main/java/org/apache/calcite/rel/rules/materialize/MaterializedViewJoinRule.java
@@ -29,6 +29,7 @@
import org.apache.calcite.rex.RexSimplify;
import org.apache.calcite.rex.RexTableInputRef.RelTableRef;
import org.apache.calcite.rex.RexUtil;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.tools.RelBuilder;
import org.apache.calcite.util.Pair;
@@ -203,7 +204,7 @@ public abstract class MaterializedViewJoinRule<C extends
MaterializedViewRule.Co
otherCompensationPred = newOtherCompensationPred;
}
final RexNode queryCompensationPred =
- RexUtil.not(
+ rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_TRUE,
RexUtil.composeConjunction(rexBuilder,
ImmutableList.of(compensationColumnsEquiPred,
otherCompensationPred)));
diff --git
a/core/src/test/java/org/apache/calcite/test/MaterializationTest.java
b/core/src/test/java/org/apache/calcite/test/MaterializationTest.java
index d23e1eecba..c91f0e75ec 100644
--- a/core/src/test/java/org/apache/calcite/test/MaterializationTest.java
+++ b/core/src/test/java/org/apache/calcite/test/MaterializationTest.java
@@ -128,6 +128,29 @@ public class MaterializationTest {
.sameResultWithMaterializationsDisabled();
}
+ /** Test case for
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-7636">[CALCITE-7636]
+ * Materialized view union rewriting drops rows when the view filter is not
+ * null-rejecting</a>.
+ *
+ * <p>The view filters on {@code commission > 400}, which is UNKNOWN for the
+ * null-commission employee. The union-rewritten result must keep that row
and
+ * therefore match the result computed without materializations. */
+ @Test void testUnionRewritingNullableAggregatePredicate() {
+ try (TryThreadLocal.Memo ignored = Prepare.THREAD_TRIM.push(true)) {
+ MaterializationService.setThreadLocal();
+ CalciteAssert.that()
+ .withMaterializations(HR_FKUK_MODEL, "m0",
+ "select \"deptno\", sum(\"salary\") as s from \"emps\" "
+ + "where \"deptno\" > 5 and \"commission\" > 400 group by
\"deptno\"")
+ .query("select \"deptno\", sum(\"salary\") as s from \"emps\" "
+ + "where \"deptno\" > 5 group by \"deptno\"")
+ .enableMaterializations(true)
+ .explainContains("EnumerableUnion(all=[true])")
+ .sameResultWithMaterializationsDisabled();
+ }
+ }
+
@Test void testViewMaterialization() {
try (TryThreadLocal.Memo ignored = Prepare.THREAD_TRIM.push(true)) {
MaterializationService.setThreadLocal();
diff --git
a/core/src/test/java/org/apache/calcite/test/MaterializedViewRelOptRulesTest.java
b/core/src/test/java/org/apache/calcite/test/MaterializedViewRelOptRulesTest.java
index a7a088081e..febf47cbbe 100644
---
a/core/src/test/java/org/apache/calcite/test/MaterializedViewRelOptRulesTest.java
+++
b/core/src/test/java/org/apache/calcite/test/MaterializedViewRelOptRulesTest.java
@@ -364,6 +364,36 @@ protected final MaterializedViewFixture sql(String
materialize,
.ok();
}
+ /** Test case for
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-7636">[CALCITE-7636]
+ * Materialized view union rewriting drops rows when the view filter is not
+ * null-rejecting</a>.
+ *
+ * <p>The view is filtered on a predicate over the nullable {@code
commission}
+ * column, so the predicate is UNKNOWN for rows where {@code commission} is
+ * null. The query-branch of the union must keep those rows (using
+ * {@code IS NOT TRUE}), otherwise they are dropped from both branches and
+ * whole groups disappear from the result. */
+ @Test void testAggregateMaterializationUnionRewritingNullablePredicate() {
+ sql("select \"deptno\", sum(\"salary\") as s\n"
+ + "from \"emps\" where \"deptno\" > 5 and \"commission\" > 1000\n"
+ + "group by \"deptno\"",
+ "select \"deptno\", sum(\"salary\") as s\n"
+ + "from \"emps\" where \"deptno\" > 5\n"
+ + "group by \"deptno\"")
+ .checkingThatResultContains(""
+ + "EnumerableAggregate(group=[{0}], S=[$SUM0($1)])\n"
+ + " EnumerableUnion(all=[true])\n"
+ + " EnumerableAggregate(group=[{1}], S=[$SUM0($3)])\n"
+ + " EnumerableCalc(expr#0..4=[{inputs}],
expr#5=[CAST($t1):INTEGER NOT NULL], "
+ + "expr#6=[5], expr#7=[>($t5, $t6)], expr#8=[1000],
expr#9=[CAST($t4):INTEGER], "
+ + "expr#10=[<($t8, $t9)], expr#11=[IS NOT TRUE($t10)],
expr#12=[AND($t7, $t11)], "
+ + "proj#0..4=[{exprs}], $condition=[$t12])\n"
+ + " EnumerableTableScan(table=[[hr, emps]])\n"
+ + " EnumerableTableScan(table=[[hr, MV0]])")
+ .ok();
+ }
+
@Test void testJoinAggregateMaterializationNoAggregateFuncs1() {
sql("select \"empid\", \"depts\".\"deptno\" from \"emps\"\n"
+ "join \"depts\" using (\"deptno\") where \"depts\".\"deptno\" >
10\n"