This is an automated email from the ASF dual-hosted git repository.
mihaibudiu 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 8accbe6913 [CALCITE-7916] Preserve input predicates through Window
8accbe6913 is described below
commit 8accbe69136a6164078510f2205965693fba9b62
Author: Atharv Rastogi <[email protected]>
AuthorDate: Mon Nov 9 17:21:32 2026 +0530
[CALCITE-7916] Preserve input predicates through Window
---
.../calcite/rel/metadata/RelMdPredicates.java | 14 ++
.../apache/calcite/test/RelMdPredicatesTest.java | 146 +++++++++++++++++++++
.../GeneratedMetadata_PredicatesHandler.java | 2 +
3 files changed, 162 insertions(+)
diff --git
a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPredicates.java
b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPredicates.java
index c37853448c..a96ab93a7a 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPredicates.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPredicates.java
@@ -40,6 +40,7 @@
import org.apache.calcite.rel.core.TableScan;
import org.apache.calcite.rel.core.Union;
import org.apache.calcite.rel.core.Values;
+import org.apache.calcite.rel.core.Window;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.rex.RexCall;
@@ -548,6 +549,19 @@ public RelOptPredicateList getPredicates(Sort sort,
RelMetadataQuery mq) {
return mq.getPulledUpPredicates(input);
}
+ /**
+ * Infers predicates for a Window.
+ */
+ public RelOptPredicateList getPredicates(Window window, RelMetadataQuery mq)
{
+ // A Window preserves its input fields and appends its aggregate results,
+ // so predicates on the input fields remain valid without remapping.
+ final RelOptPredicateList inputPredicates =
+ mq.getPulledUpPredicates(window.getInput());
+ // Inferred predicates belong to an input Join, not to the Window.
+ return RelOptPredicateList.of(window.getCluster().getRexBuilder(),
+ inputPredicates.pulledUpPredicates);
+ }
+
/**
* Infers predicates for a TableModify.
*/
diff --git
a/core/src/test/java/org/apache/calcite/test/RelMdPredicatesTest.java
b/core/src/test/java/org/apache/calcite/test/RelMdPredicatesTest.java
index 4e2721a674..f7026498bf 100644
--- a/core/src/test/java/org/apache/calcite/test/RelMdPredicatesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelMdPredicatesTest.java
@@ -17,9 +17,17 @@
package org.apache.calcite.test;
import org.apache.calcite.plan.RelOptPredicateList;
+import org.apache.calcite.plan.RelOptRule;
+import org.apache.calcite.plan.hep.HepPlanner;
+import org.apache.calcite.plan.hep.HepProgramBuilder;
import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Filter;
import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.Project;
+import org.apache.calcite.rel.core.Window;
import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.rules.CoreRules;
+import org.apache.calcite.rex.RexInputRef;
import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.fun.SqlLibraryOperators;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
@@ -35,11 +43,149 @@
import static org.apache.calcite.test.Matchers.sortsAs;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertTrue;
/** Tests for {@link org.apache.calcite.rel.metadata.RelMdPredicates} class. */
public class RelMdPredicatesTest {
+ /** Test case for
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-7916">[CALCITE-7916]
+ * RelMdPredicates should preserve input predicates through Window</a>. */
+ @Test void testPullUpConstantThroughWindow() {
+ final Window window = window("select *, row_number() over (order by empno)
as rn\n"
+ + "from emp where deptno = 10");
+ final RelMetadataQuery mq = window.getCluster().getMetadataQuery();
+ final RelOptPredicateList predicates = windowPredicates(window);
+ assertThat(predicates.pulledUpPredicates, sortsAs("[=($7, 10)]"));
+ assertEquals(mq.getPulledUpPredicates(window.getInput()).constantMap,
+ predicates.constantMap);
+ assertEquals(1, predicates.constantMap.size());
+ assertTrue(predicates.constantMap.keySet().stream()
+ .allMatch(ref -> ref instanceof RexInputRef
+ && ((RexInputRef) ref).getIndex()
+ < window.getInput().getRowType().getFieldCount()));
+ }
+
+ @Test void testPullUpPredicatesThroughWindow() {
+ final Window window = window("select *, row_number() over (order by empno)
as rn\n"
+ + "from emp where sal > 1000 and mgr is not null");
+ final RelOptPredicateList predicates = windowPredicates(window);
+ assertThat(predicates.pulledUpPredicates,
+ sortsAs("[>($5, 1000), IS NOT NULL($3)]"));
+ assertTrue(predicates.constantMap.isEmpty());
+ }
+
+ @Test void testPullUpNullConstantThroughWindow() {
+ final Window window = window("select *, row_number() over (order by empno)
as rn\n"
+ + "from emp where mgr is null");
+ final RelMetadataQuery mq = window.getCluster().getMetadataQuery();
+ final RelOptPredicateList predicates = windowPredicates(window);
+ assertThat(predicates.pulledUpPredicates, sortsAs("[IS NULL($3)]"));
+ assertEquals(mq.getPulledUpPredicates(window.getInput()).constantMap,
+ predicates.constantMap);
+ assertEquals(1, predicates.constantMap.size());
+ }
+
+ @Test void testWindowWithoutInputPredicates() {
+ final Window window = window("select *, row_number() over (order by empno)
as rn\n"
+ + "from emp");
+ assertTrue(RelOptPredicateList.isEmpty(windowPredicates(window)));
+ }
+
+ @Test void testWindowConstantsAreNotOutputConstants() {
+ final Window window = window("select *,\n"
+ + "sum(sal) over (order by empno rows between 1 preceding and current
row) as s\n"
+ + "from emp where deptno = 10");
+ assertEquals(1, window.constants.size());
+ final RelMetadataQuery mq = window.getCluster().getMetadataQuery();
+ final RelOptPredicateList predicates = windowPredicates(window);
+ assertThat(predicates.pulledUpPredicates, sortsAs("[=($7, 10)]"));
+ assertEquals(mq.getPulledUpPredicates(window.getInput()).constantMap,
+ predicates.constantMap);
+ assertEquals(1, predicates.constantMap.size());
+ }
+
+ @Test void testWindowDoesNotPullUpJoinInferredPredicates() {
+ final Window window = window("select e.*, d.name,\n"
+ + "row_number() over (order by e.empno) as rn\n"
+ + "from (select * from emp where deptno > 10) e\n"
+ + "join (select * from dept where deptno < 30) d\n"
+ + "on e.deptno = d.deptno");
+ final RelMetadataQuery mq = window.getCluster().getMetadataQuery();
+ final RelOptPredicateList inputPredicates =
+ mq.getPulledUpPredicates(window.getInput());
+ assertFalse(inputPredicates.leftInferredPredicates.isEmpty());
+ assertFalse(inputPredicates.rightInferredPredicates.isEmpty());
+ final RelOptPredicateList predicates = windowPredicates(window);
+ assertEquals(inputPredicates.pulledUpPredicates,
predicates.pulledUpPredicates);
+ assertEquals(inputPredicates.constantMap, predicates.constantMap);
+ assertTrue(predicates.leftInferredPredicates.isEmpty());
+ assertTrue(predicates.rightInferredPredicates.isEmpty());
+ }
+
+ @Test void testReduceProjectAboveWindow() {
+ final RelNode rel = lowerWindows("select\n"
+ + "case when deptno = 10 then rn else -1 end as selected_rn\n"
+ + "from (select deptno, row_number() over (order by empno) as rn\n"
+ + "from emp where deptno = 10) ranked");
+ final RelNode reduced =
+ applyRules(rel, CoreRules.PROJECT_REDUCE_EXPRESSIONS,
CoreRules.PROJECT_MERGE);
+ final Project project = assertInstanceOf(Project.class, reduced);
+ final Window window = assertInstanceOf(Window.class, project.getInput());
+ final RexInputRef result =
+ assertInstanceOf(RexInputRef.class, project.getProjects().get(0));
+ assertEquals(window.getInput().getRowType().getFieldCount(),
result.getIndex());
+ assertEquals(1, countFilters(window));
+ }
+
+ @Test void testReduceFilterAboveWindow() {
+ final RelNode rel = lowerWindows("select *\n"
+ + "from (select *, row_number() over (order by empno) as rn\n"
+ + "from emp where deptno = 10) ranked\n"
+ + "where deptno = 10");
+ assertEquals(2, countFilters(rel));
+ final RelNode reduced = applyRules(rel,
CoreRules.FILTER_REDUCE_EXPRESSIONS);
+ assertEquals(1, countFilters(reduced));
+ final Window window = findWindow(reduced);
+ assertInstanceOf(Filter.class, window.getInput());
+ }
+
+ private static Window window(String sql) {
+ return findWindow(lowerWindows(sql));
+ }
+
+ private static RelOptPredicateList windowPredicates(Window window) {
+ return
window.getCluster().getMetadataQuery().getPulledUpPredicates(window);
+ }
+
+ private static Window findWindow(RelNode rel) {
+ while (!(rel instanceof Window)) {
+ assertEquals(1, rel.getInputs().size());
+ rel = rel.getInput(0);
+ }
+ return (Window) rel;
+ }
+
+ private static RelNode lowerWindows(String sql) {
+ final RelNode rel = RelMetadataFixture.DEFAULT.withSql(sql).toRel();
+ return applyRules(rel, CoreRules.PROJECT_TO_LOGICAL_PROJECT_AND_WINDOW);
+ }
+
+ private static RelNode applyRules(RelNode rel, RelOptRule... rules) {
+ final HepPlanner planner = new HepPlanner(new HepProgramBuilder()
+ .addRuleCollection(Arrays.asList(rules)).build());
+ planner.setRoot(rel);
+ return planner.findBestExp();
+ }
+
+ private static int countFilters(RelNode rel) {
+ return (rel instanceof Filter ? 1 : 0)
+ +
rel.getInputs().stream().mapToInt(RelMdPredicatesTest::countFilters).sum();
+ }
+
@ParameterizedTest(name = "{0}")
@CsvFileSource(resources = "RelMdPredicatesTestPullUpFromJoin.csv",
delimiter = ';')
void testPullUpPredicatesFromJoin(JoinRelType joinType, String
expectedPredicates) {
diff --git
a/core/src/test/resources/org/apache/calcite/rel/metadata/janino/GeneratedMetadata_PredicatesHandler.java
b/core/src/test/resources/org/apache/calcite/rel/metadata/janino/GeneratedMetadata_PredicatesHandler.java
index 597a7d4380..5cc2eecde7 100644
---
a/core/src/test/resources/org/apache/calcite/rel/metadata/janino/GeneratedMetadata_PredicatesHandler.java
+++
b/core/src/test/resources/org/apache/calcite/rel/metadata/janino/GeneratedMetadata_PredicatesHandler.java
@@ -90,6 +90,8 @@ private org.apache.calcite.plan.RelOptPredicateList
getPredicates_(
return provider0.getPredicates((org.apache.calcite.rel.core.Union) r,
mq);
} else if (r instanceof org.apache.calcite.rel.core.Values) {
return provider0.getPredicates((org.apache.calcite.rel.core.Values) r,
mq);
+ } else if (r instanceof org.apache.calcite.rel.core.Window) {
+ return provider0.getPredicates((org.apache.calcite.rel.core.Window) r,
mq);
} else if (r instanceof org.apache.calcite.rel.RelNode) {
return provider0.getPredicates((org.apache.calcite.rel.RelNode) r, mq);
} else {