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 41d88b41b7 [CALCITE-7574] RelDecorrelator.isFieldNotNullRecursive 
throws IndexOutOfBoundsException when decorrelating correlated scalar subquery 
with Aggregate
41d88b41b7 is described below

commit 41d88b41b756e49dd7e5b1fc07e61ce98be96a2d
Author: 雷书鹏 <[email protected]>
AuthorDate: Tue Jun 2 09:26:25 2026 +0800

    [CALCITE-7574] RelDecorrelator.isFieldNotNullRecursive throws 
IndexOutOfBoundsException when decorrelating correlated scalar subquery with 
Aggregate
    
    Root Cause:
    In isFieldNotNullRecursive, the Aggregate branch used ImmutableBitSet.size()
    for bounds checking. size() returns the bitset capacity, not the number of
    group keys. Changed to agg.getGroupCount() which correctly returns the
    actual number of group keys.
---
 .../apache/calcite/sql2rel/RelDecorrelator.java    |  2 +-
 .../calcite/sql2rel/RelDecorrelatorTest.java       | 80 ++++++++++++++++++++++
 core/src/test/resources/sql/sub-query.iq           | 19 +++++
 3 files changed, 100 insertions(+), 1 deletion(-)

diff --git a/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java 
b/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
index c38268fb4d..a450a659dc 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
@@ -3853,7 +3853,7 @@ private static boolean isFieldNotNullRecursive(RelNode 
rel, int index) {
       Aggregate agg = (Aggregate) rel;
       ImmutableBitSet groupSet = agg.getGroupSet();
 
-      if (index >= groupSet.size()) {
+      if (index >= agg.getGroupCount()) {
         return false;
       }
       return isFieldNotNullRecursive(agg.getInput(), 
groupSet.asList().get(index));
diff --git 
a/core/src/test/java/org/apache/calcite/sql2rel/RelDecorrelatorTest.java 
b/core/src/test/java/org/apache/calcite/sql2rel/RelDecorrelatorTest.java
index 99b7453192..15f96eaa9e 100644
--- a/core/src/test/java/org/apache/calcite/sql2rel/RelDecorrelatorTest.java
+++ b/core/src/test/java/org/apache/calcite/sql2rel/RelDecorrelatorTest.java
@@ -2124,4 +2124,84 @@ public static Frameworks.ConfigBuilder config() {
         + "        LogicalTableScan(table=[[scott, DEPT]])\n";
     assertThat(afterDecorrelation, hasTree(planAfterDecorrelation));
   }
+
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-7574";>[CALCITE-7574]
+   * RelDecorrelator.isFieldNotNullRecursive throws IndexOutOfBoundsException
+   * when decorrelating correlated scalar subquery with Aggregate</a>.
+   *
+   * <p>When decorrelating a correlated scalar subquery containing an 
Aggregate,
+   * {@code isFieldNotNullRecursive} incorrectly used {@code 
ImmutableBitSet.size()}
+   * (which returns the bitset capacity, typically 64 * words) instead of
+   * {@code Aggregate.getGroupCount()} (which returns the number of group 
keys).
+   * This caused an {@code IndexOutOfBoundsException} when the field index
+   * corresponded to an aggregate result field (not a group field).
+   */
+  @Test void testDecorrelateScalarSubQueryWithAggregate() {
+    final FrameworkConfig frameworkConfig = config().build();
+    final RelBuilder builder = RelBuilder.create(frameworkConfig);
+    final RelOptCluster cluster = builder.getCluster();
+    final Planner planner = Frameworks.getPlanner(frameworkConfig);
+    // Minimal case: outer FROM is an Aggregate, correlated subquery
+    // references the aggregate result field (s) in the correlation
+    // condition, triggering isFieldNotNullRecursive on an Aggregate with
+    // an index pointing to an aggregate result field.
+    final String sql = "select t.deptno,\n"
+        + "  (select count(*) from emp e\n"
+        + "   where e.deptno = t.deptno\n"
+        + "     and e.sal > t.s)\n"
+        + "from (select deptno, sum(sal) as s\n"
+        + "      from emp group by deptno) t";
+    final RelNode originalRel;
+    try {
+      final SqlNode parse = planner.parse(sql);
+      final SqlNode validate = planner.validate(parse);
+      originalRel = planner.rel(validate).rel;
+    } catch (Exception e) {
+      throw TestUtil.rethrow(e);
+    }
+
+    final HepProgram hepProgram = HepProgram.builder()
+        .addRuleCollection(
+            ImmutableList.of(
+                CoreRules.FILTER_SUB_QUERY_TO_CORRELATE,
+                CoreRules.PROJECT_SUB_QUERY_TO_CORRELATE,
+                CoreRules.JOIN_SUB_QUERY_TO_CORRELATE))
+        .build();
+    final Program program =
+        Programs.of(hepProgram, true,
+            requireNonNull(cluster.getMetadataProvider()));
+    final RelNode before =
+        program.run(cluster.getPlanner(), originalRel, cluster.traitSet(),
+            Collections.emptyList(), Collections.emptyList());
+
+    // Before the fix, decorrelateQuery would throw:
+    // java.lang.IndexOutOfBoundsException: index out of range: 0
+    //   at org.apache.calcite.util.ImmutableBitSet.nth
+    //   at ...RelDecorrelator.isFieldNotNullRecursive
+    final RelNode after =
+        RelDecorrelator.decorrelateQuery(before, builder, 
RuleSets.ofList(Collections.emptyList()),
+            RuleSets.ofList(Collections.emptyList()));
+
+    // Verify decorrelation produced a valid plan (no Correlate nodes)
+    final String planAfter = ""
+        + "LogicalProject(DEPTNO=[$0], EXPR$1=[$4])\n"
+        + "  LogicalJoin(condition=[AND(IS NOT DISTINCT FROM($0, $2), IS NOT 
DISTINCT FROM($1, $3))], joinType=[left])\n"
+        + "    LogicalAggregate(group=[{0}], S=[SUM($1)])\n"
+        + "      LogicalProject(DEPTNO=[$7], SAL=[$5])\n"
+        + "        LogicalTableScan(table=[[scott, EMP]])\n"
+        + "    LogicalProject(DEPTNO0=[$0], S=[$1], EXPR$0=[CASE(IS NOT 
NULL($4), $4, 0)])\n"
+        + "      LogicalJoin(condition=[AND(IS NOT DISTINCT FROM($0, $2), IS 
NOT DISTINCT FROM($1, $3))], joinType=[left])\n"
+        + "        LogicalAggregate(group=[{0}], S=[SUM($1)])\n"
+        + "          LogicalProject(DEPTNO=[$7], SAL=[$5])\n"
+        + "            LogicalTableScan(table=[[scott, EMP]])\n"
+        + "        LogicalAggregate(group=[{0, 1}], EXPR$0=[COUNT()])\n"
+        + "          LogicalProject(DEPTNO0=[$8], S=[$9])\n"
+        + "            LogicalJoin(condition=[AND(=($7, $8), 
>(CAST($5):DECIMAL(19, 2), $9))], joinType=[inner])\n"
+        + "              LogicalTableScan(table=[[scott, EMP]])\n"
+        + "              LogicalAggregate(group=[{0}], S=[SUM($1)])\n"
+        + "                LogicalProject(DEPTNO=[$7], SAL=[$5])\n"
+        + "                  LogicalTableScan(table=[[scott, EMP]])\n";
+    assertThat(after, hasTree(planAfter));
+  }
 }
diff --git a/core/src/test/resources/sql/sub-query.iq 
b/core/src/test/resources/sql/sub-query.iq
index b301dae413..a04c8634d2 100644
--- a/core/src/test/resources/sql/sub-query.iq
+++ b/core/src/test/resources/sql/sub-query.iq
@@ -9237,4 +9237,23 @@ SELECT deptno, dname > SOME(SELECT empno FROM emp) AS b 
FROM dept;
 For input string: "ACCOUNTING"
 !error
 
+# [CALCITE-7574] RelDecorrelator.isFieldNotNullRecursive throws
+# IndexOutOfBoundsException when decorrelating correlated scalar subquery
+# with Aggregate
+# Before fix: java.lang.IndexOutOfBoundsException: index out of range: 0
+SELECT t.deptno,
+  (SELECT COUNT(*) FROM emp e
+   WHERE e.deptno = t.deptno AND e.sal > t.s)
+FROM (SELECT deptno, SUM(sal) AS s FROM emp GROUP BY deptno) t;
++--------+--------+
+| DEPTNO | EXPR$1 |
++--------+--------+
+|     10 |      0 |
+|     20 |      0 |
+|     30 |      0 |
++--------+--------+
+(3 rows)
+
+!ok
+
 # End sub-query.iq

Reply via email to