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;