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 838ec33a02 [CALCITE-7929] RelDecorrelator replaces an uncorrelated 
EXISTS with TRUE when the query also has a correlated sub-query
838ec33a02 is described below

commit 838ec33a020f9823b95a275fee959f4b6d04dcee
Author: Mihai Budiu <[email protected]>
AuthorDate: Tue Nov 10 14:23:01 2026 -0800

    [CALCITE-7929] RelDecorrelator replaces an uncorrelated EXISTS with TRUE 
when the query also has a correlated sub-query
    
    Signed-off-by: Mihai Budiu <[email protected]>
---
 .../apache/calcite/sql2rel/RelDecorrelator.java    |  7 +-
 core/src/test/resources/sql/sub-query.iq           | 92 ++++++++++++++++++++++
 2 files changed, 98 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 64c84bd9dc..98f48ae274 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
@@ -726,10 +726,15 @@ static boolean isSameForNoRowsAndNullRow(Aggregate 
aggregate) {
     int newPos = 0;
 
     final List<Integer> groupKeyIndices = rel.getGroupSet().asList();
+    // If decorrelation adds no correlation key, keep the constant keys:
+    // GROUP BY TRUE gives no row for an empty input, but GROUP BY ()
+    // gives one row.
+    final boolean keepConstantKeys = frame.corDefOutputs.isEmpty();
     final NavigableMap<Integer, RexLiteral> omittedConstants = new TreeMap<>();
     for (int i = 0; i < oldGroupKeyCount; i++) {
       final int idx = groupKeyIndices.get(i);
-      final RexLiteral constant = projectedLiteral(newInput, idx);
+      final RexLiteral constant =
+          keepConstantKeys ? null : projectedLiteral(newInput, idx);
       if (constant != null) {
         // Exclude constants. Aggregate({true}) occurs because Aggregate({})
         // would generate 1 row even when applied to an empty table.
diff --git a/core/src/test/resources/sql/sub-query.iq 
b/core/src/test/resources/sql/sub-query.iq
index e4cb4efad8..d80b028b4b 100644
--- a/core/src/test/resources/sql/sub-query.iq
+++ b/core/src/test/resources/sql/sub-query.iq
@@ -11331,3 +11331,95 @@ from dept;
 !ok
 
 !set expand false
+
+# [CALCITE-7929] RelDecorrelator replaces an uncorrelated EXISTS with TRUE
+# when the query also has a correlated sub-query.
+# The expected output of each of the next 5 queries was validated in two
+# ways: PostgreSQL 14 with the same EMP and DEPT rows gives the same
+# result, and so does Calcite with the top-down decorrelator.
+
+# No employee earns more than 10000, so E is FALSE in each row
+select deptno,
+  exists (select 1 from emp where sal > 10000) as e,
+  exists (select 1 from emp where emp.deptno = dept.deptno) as ec
+from dept;
++--------+-------+-------+
+| DEPTNO | E     | EC    |
++--------+-------+-------+
+|     10 | false | true  |
+|     20 | false | true  |
+|     30 | false | true  |
+|     40 | false | false |
++--------+-------+-------+
+(4 rows)
+
+!ok
+
+# Same sub-queries in WHERE: the uncorrelated EXISTS is FALSE, so no rows
+select deptno
+from dept
+where exists (select 1 from emp where sal > 10000)
+  and exists (select 1 from emp where emp.deptno = dept.deptno);
++--------+
+| DEPTNO |
++--------+
++--------+
+(0 rows)
+
+!ok
+
+# GROUP BY a constant over an empty input gives no group, so C is NULL.
+# PostgreSQL rejects GROUP BY 'a', so it ran with GROUP BY 'a'::text.
+select deptno,
+  (select count(*) from emp where sal > 10000 group by 'a') as c,
+  exists (select 1 from emp where emp.deptno = dept.deptno) as ec
+from dept;
++--------+---+-------+
+| DEPTNO | C | EC    |
++--------+---+-------+
+|     10 |   | true  |
+|     20 |   | true  |
+|     30 |   | true  |
+|     40 |   | false |
++--------+---+-------+
+(4 rows)
+
+!ok
+
+# The uncorrelated EXISTS inside the correlated sub-query is FALSE,
+# so EC is FALSE in each row
+select deptno,
+  exists (select 1 from emp where emp.deptno = dept.deptno
+          and exists (select 1 from emp where sal > 10000)) as ec
+from dept;
++--------+-------+
+| DEPTNO | EC    |
++--------+-------+
+|     10 | false |
+|     20 | false |
+|     30 | false |
+|     40 | false |
++--------+-------+
+(4 rows)
+
+!ok
+
+# ROLLUP mixes a constant key and a non-constant key. The sub-query is
+# uncorrelated, so the constant key must stay; dropping it made the
+# grouping sets invalid. PostgreSQL ran it with ROLLUP('a'::text, job).
+select deptno,
+  (select count(*) from (select job from emp
+                         group by rollup('a', job)) t) as c,
+  exists (select 1 from emp where emp.deptno = dept.deptno) as ec
+from dept;
++--------+---+-------+
+| DEPTNO | C | EC    |
++--------+---+-------+
+|     10 | 7 | true  |
+|     20 | 7 | true  |
+|     30 | 7 | true  |
+|     40 | 7 | false |
++--------+---+-------+
+(4 rows)
+
+!ok

Reply via email to