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 {

Reply via email to