This is an automated email from the ASF dual-hosted git repository.

mbudiu 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 859469d9ea [CALCITE-7112] Correlation variable in HAVING clause causes 
UnsupportedOperationException in RelToSql conversion
859469d9ea is described below

commit 859469d9eab0d271932a603ea4f6d6872c1b9cd1
Author: Niels Pardon <[email protected]>
AuthorDate: Mon Jul 28 16:58:56 2025 +0200

    [CALCITE-7112] Correlation variable in HAVING clause causes 
UnsupportedOperationException in RelToSql conversion
    
    Signed-off-by: Niels Pardon <[email protected]>
---
 .../apache/calcite/rel/rel2sql/SqlImplementor.java | 117 +++++++++++----------
 .../calcite/rel/rel2sql/RelToSqlConverterTest.java |  37 +++++++
 2 files changed, 101 insertions(+), 53 deletions(-)

diff --git 
a/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java 
b/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java
index df60328908..1b92f18b96 100644
--- a/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java
+++ b/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java
@@ -1897,59 +1897,7 @@ private Builder builder(RelNode rel, Set<Clause> 
clauses) {
       if (!selectList.equals(SqlNodeList.SINGLETON_STAR)) {
         final boolean aliasRef = expectedClauses.contains(Clause.HAVING)
             && dialect.getConformance().isHavingAlias();
-        newContext = new Context(dialect, selectList.size()) {
-          @Override public SqlImplementor implementor() {
-            return SqlImplementor.this;
-          }
-
-          @Override public SqlNode field(int ordinal) {
-            final SqlNode selectItem = selectList.get(ordinal);
-            switch (selectItem.getKind()) {
-            case AS:
-              final SqlCall asCall = (SqlCall) selectItem;
-              SqlNode alias = asCall.operand(1);
-              if (aliasRef && !SqlUtil.isGeneratedAlias(((SqlIdentifier) 
alias).getSimple())) {
-                // For BigQuery, given the query
-                //   SELECT SUM(x) AS x FROM t HAVING(SUM(t.x) > 0)
-                // we can generate
-                //   SELECT SUM(x) AS x FROM t HAVING(x > 0)
-                // because 'x' in HAVING resolves to the 'AS x' not 't.x'.
-                return alias;
-              }
-              return asCall.operand(0);
-            default:
-              break;
-            }
-            return selectItem;
-          }
-
-          @Override public SqlNode orderField(int ordinal) {
-            // If the field expression is an unqualified column identifier
-            // and matches a different alias, use an ordinal.
-            // For example, given
-            //    SELECT deptno AS empno, empno AS x FROM emp ORDER BY 
emp.empno
-            // we generate
-            //    SELECT deptno AS empno, empno AS x FROM emp ORDER BY 2
-            // "ORDER BY empno" would give incorrect result;
-            // "ORDER BY x" is acceptable but is not preferred.
-            final SqlNode node = super.orderField(ordinal);
-            if (node instanceof SqlIdentifier
-                && ((SqlIdentifier) node).isSimple()) {
-              final String name = ((SqlIdentifier) node).getSimple();
-              for (Ord<SqlNode> selectItem : Ord.zip(selectList)) {
-                if (selectItem.i != ordinal) {
-                  final @Nullable String alias =
-                      SqlValidatorUtil.alias(selectItem.e);
-                  if (name.equalsIgnoreCase(alias) && 
dialect.getConformance().isSortByAlias()) {
-                    return SqlLiteral.createExactNumeric(
-                        Integer.toString(ordinal + 1), SqlParserPos.ZERO);
-                  }
-                }
-              }
-            }
-            return node;
-          }
-        };
+        newContext = new SelectListContext(dialect, selectList.size(), 
aliasRef, selectList);
       } else {
         boolean qualified =
             !dialect.hasImplicitTableAlias() || aliases.size() > 1;
@@ -2337,6 +2285,69 @@ Result withExpectedClauses(boolean ignoreClauses,
           : new Result(node, clauses, neededAlias, neededType, aliases, anon,
               ignoreClauses, ImmutableSet.copyOf(expectedClauses), 
expectedRel);
     }
+
+    /**
+     * A context that uses a select list.
+     */
+    private final class SelectListContext extends BaseContext {
+      private final boolean aliasRef;
+      private final SqlNodeList selectList;
+
+      private SelectListContext(
+          SqlDialect dialect, int fieldCount, boolean aliasRef, SqlNodeList 
selectList) {
+        super(dialect, fieldCount);
+        this.aliasRef = aliasRef;
+        this.selectList = selectList;
+      }
+
+      @Override public SqlNode field(int ordinal) {
+        final SqlNode selectItem = selectList.get(ordinal);
+        switch (selectItem.getKind()) {
+        case AS:
+          final SqlCall asCall = (SqlCall) selectItem;
+          SqlNode alias = asCall.operand(1);
+          if (aliasRef && !SqlUtil.isGeneratedAlias(((SqlIdentifier) 
alias).getSimple())) {
+            // For BigQuery, given the query
+            //   SELECT SUM(x) AS x FROM t HAVING(SUM(t.x) > 0)
+            // we can generate
+            //   SELECT SUM(x) AS x FROM t HAVING(x > 0)
+            // because 'x' in HAVING resolves to the 'AS x' not 't.x'.
+            return alias;
+          }
+          return asCall.operand(0);
+        default:
+          break;
+        }
+        return selectItem;
+      }
+
+      @Override public SqlNode orderField(int ordinal) {
+        // If the field expression is an unqualified column identifier
+        // and matches a different alias, use an ordinal.
+        // For example, given
+        //    SELECT deptno AS empno, empno AS x FROM emp ORDER BY emp.empno
+        // we generate
+        //    SELECT deptno AS empno, empno AS x FROM emp ORDER BY 2
+        // "ORDER BY empno" would give incorrect result;
+        // "ORDER BY x" is acceptable but is not preferred.
+        final SqlNode node = super.orderField(ordinal);
+        if (node instanceof SqlIdentifier
+            && ((SqlIdentifier) node).isSimple()) {
+          final String name = ((SqlIdentifier) node).getSimple();
+          for (Ord<SqlNode> selectItem : Ord.zip(selectList)) {
+            if (selectItem.i != ordinal) {
+              final @Nullable String alias =
+                  SqlValidatorUtil.alias(selectItem.e);
+              if (name.equalsIgnoreCase(alias) && 
dialect.getConformance().isSortByAlias()) {
+                return SqlLiteral.createExactNumeric(
+                    Integer.toString(ordinal + 1), SqlParserPos.ZERO);
+              }
+            }
+          }
+        }
+        return node;
+      }
+    }
   }
 
   /** Builder. */
diff --git 
a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java 
b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
index 6157c24f9a..7283f43870 100644
--- 
a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
+++ 
b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
@@ -45,6 +45,7 @@
 import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.rel.type.RelDataTypeSystem;
 import org.apache.calcite.rel.type.RelDataTypeSystemImpl;
+import org.apache.calcite.rex.RexCorrelVariable;
 import org.apache.calcite.runtime.FlatLists;
 import org.apache.calcite.runtime.Hook;
 import org.apache.calcite.schema.SchemaPlus;
@@ -90,6 +91,7 @@
 import org.apache.calcite.tools.RuleSet;
 import org.apache.calcite.tools.RuleSets;
 import org.apache.calcite.util.ConversionUtil;
+import org.apache.calcite.util.Holder;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.calcite.util.TestUtil;
 import org.apache.calcite.util.Util;
@@ -10454,6 +10456,41 @@ private void checkLiteral2(String expression, String 
expected) {
         .ok(expected);
   }
 
+  /** Test case of
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-7112";>[CALCITE-7112] 
Correlation
+   * variable in HAVING clause causes UnsupportedOperationException in 
RelToSql conversion</a>. */
+  @Test void testCorrelationVariableInHavingClause() {
+    final Holder<RexCorrelVariable> v = Holder.empty();
+    final Function<RelBuilder, RelNode> relFn = b -> b
+        .scan("DEPT")
+        .variable(v::set)
+        .project(
+            ImmutableList.of(
+                b.field("DEPTNO"),
+                b.field("DNAME"),
+                b.scalarQuery(unused ->
+                    b.scan("EMP")
+                        .aggregate(b.groupKey("DEPTNO"), b.countStar("COUNT"))
+                        .filter(b.equals(b.field("DEPTNO"), b.field(v.get(), 
"DEPTNO")))
+                        .project(b.field("COUNT"))
+                        .build())),
+            ImmutableList.of(),
+            false,
+            ImmutableList.of(v.get().id))
+        .build();
+
+    final String expected = "SELECT "
+        + "\"DEPTNO\", "
+        + "\"DNAME\", "
+        + "(((SELECT COUNT(*) AS \"COUNT\"\n"
+        + "FROM \"scott\".\"EMP\"\n"
+        + "GROUP BY \"DEPTNO\"\n"
+        + "HAVING \"DEPTNO\" = \"DEPT\".\"DEPTNO\"))) AS \"$f2\"\n"
+        + "FROM \"scott\".\"DEPT\"";
+
+    relFn(relFn).ok(expected);
+  }
+
   /** Fluid interface to run tests. */
   static class Sql {
     private final CalciteAssert.SchemaSpec schemaSpec;

Reply via email to