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 1294b4e8cc [CALCITE-4512] GROUP BY expression with argument name same 
with SELECT field and alias causes validation error
1294b4e8cc is described below

commit 1294b4e8cc3dcee8db8dd73ad93315299d561067
Author: wangyanjing <[email protected]>
AuthorDate: Thu Aug 22 19:49:01 2024 +0800

    [CALCITE-4512] GROUP BY expression with argument name same with SELECT 
field and alias causes validation error
---
 .../calcite/sql/validate/SqlValidatorImpl.java     | 71 ++++++++++++++++------
 .../apache/calcite/test/SqlToRelConverterTest.java | 31 ++++++++++
 .../org/apache/calcite/test/SqlValidatorTest.java  | 40 ++++++++++++
 .../apache/calcite/test/SqlToRelConverterTest.xml  | 39 ++++++++++++
 core/src/test/resources/sql/misc.iq                | 18 ++++++
 5 files changed, 180 insertions(+), 19 deletions(-)

diff --git 
a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java 
b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
index af109c47ea..ba4e3203a9 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
@@ -127,10 +127,12 @@ import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 
 import org.apiguardian.api.API;
+import org.checkerframework.checker.initialization.qual.UnknownInitialization;
 import org.checkerframework.checker.nullness.qual.KeyFor;
 import org.checkerframework.checker.nullness.qual.NonNull;
 import org.checkerframework.checker.nullness.qual.Nullable;
 import org.checkerframework.checker.nullness.qual.PolyNull;
+import org.checkerframework.checker.nullness.qual.RequiresNonNull;
 import org.checkerframework.dataflow.qual.Pure;
 import org.slf4j.Logger;
 
@@ -7302,6 +7304,8 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
     final SqlSelect select;
     final SqlNode root;
     final Clause clause;
+    // Retain only expandable aliases or ordinals to prevent their expansion 
in a SQL call expr.
+    final Set<SqlNode> aliasOrdinalExpandSet = Sets.newIdentityHashSet();
 
     ExtendedExpander(SqlValidatorImpl validator, SqlValidatorScope scope,
         SqlSelect select, SqlNode root, Clause clause) {
@@ -7309,6 +7313,9 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
       this.select = select;
       this.root = root;
       this.clause = clause;
+      if (clause == Clause.GROUP_BY) {
+        addExpandableExpressions();
+      }
     }
 
     @Override public @Nullable SqlNode visit(SqlIdentifier id) {
@@ -7317,7 +7324,7 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
       }
 
       final boolean replaceAliases = 
clause.shouldReplaceAliases(validator.config);
-      if (!replaceAliases) {
+      if (!replaceAliases || (clause == Clause.GROUP_BY && 
!aliasOrdinalExpandSet.contains(id))) {
         final SelectScope scope = validator.getRawSelectScopeNonNull(select);
         SqlNode node = expandCommonColumn(select, id, scope, validator);
         if (node != id) {
@@ -7368,24 +7375,7 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
           || !validator.config().conformance().isGroupByOrdinal()) {
         return super.visit(literal);
       }
-      boolean isOrdinalLiteral = literal == root;
-      switch (root.getKind()) {
-      case GROUPING_SETS:
-      case ROLLUP:
-      case CUBE:
-        if (root instanceof SqlBasicCall) {
-          List<SqlNode> operandList = ((SqlBasicCall) root).getOperandList();
-          for (SqlNode node : operandList) {
-            if (node.equals(literal)) {
-              isOrdinalLiteral = true;
-              break;
-            }
-          }
-        }
-        break;
-      default:
-        break;
-      }
+      boolean isOrdinalLiteral = aliasOrdinalExpandSet.contains(literal);
       if (isOrdinalLiteral) {
         switch (literal.getTypeName()) {
         case DECIMAL:
@@ -7411,6 +7401,49 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
       return super.visit(literal);
     }
 
+    /**
+     * Add all possible expandable 'group by' expression to set, which is
+     * used to check whether expr could be expanded as alias or ordinal.
+     */
+    @RequiresNonNull({"root"})
+    private void addExpandableExpressions(@UnknownInitialization 
ExtendedExpander this) {
+      switch (root.getKind()) {
+      case IDENTIFIER:
+      case LITERAL:
+        aliasOrdinalExpandSet.add(root);
+        break;
+      case GROUPING_SETS:
+      case ROLLUP:
+      case CUBE:
+        if (root instanceof SqlBasicCall) {
+          List<SqlNode> operandList = ((SqlBasicCall) root).getOperandList();
+          for (SqlNode sqlNode : operandList) {
+            addIdentifierOrdinal2ExpandSet(sqlNode);
+          }
+        }
+        break;
+      default:
+        break;
+      }
+    }
+
+    /**
+     * Identifier or literal in grouping sets, rollup, cube will be eligible 
for alias.
+     *
+     * @param sqlNode expression within grouping sets, rollup, cube
+     */
+    private void addIdentifierOrdinal2ExpandSet(@UnknownInitialization 
ExtendedExpander this,
+        SqlNode sqlNode) {
+      if (sqlNode.getKind() == SqlKind.ROW) {
+        List<SqlNode> rowOperandList = ((SqlCall) sqlNode).getOperandList();
+        for (SqlNode node : rowOperandList) {
+          addIdentifierOrdinal2ExpandSet(node);
+        }
+      } else if (sqlNode.getKind() == SqlKind.IDENTIFIER || sqlNode.getKind() 
== SqlKind.LITERAL) {
+        aliasOrdinalExpandSet.add(sqlNode);
+      }
+    }
+
     /**
      * Returns whether a given node contains a {@link SqlIdentifier}.
      *
diff --git 
a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java 
b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
index d11d633e75..0e2e835846 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
@@ -463,6 +463,37 @@ class SqlToRelConverterTest extends SqlToRelTestBase {
         .withConformance(SqlConformanceEnum.LENIENT).ok();
   }
 
+  /**
+   * Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-4512";>[CALCITE-4512]
+   * GROUP BY expression with argument name same with SELECT field and alias 
causes
+   * validation error</a>.
+   */
+  @Test void testGroupByExprArgFieldSameWithAlias() {
+    final String sql = "SELECT floor(deptno / 2) AS deptno\n"
+        + "FROM emp\n"
+        + "GROUP BY floor(deptno / 2)";
+    sql(sql)
+        .withConformance(SqlConformanceEnum.LENIENT)
+        .ok();
+  }
+
+  /**
+   * Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-4512";>[CALCITE-4512]
+   * GROUP BY expression with argument name same with SELECT field and alias 
causes
+   * validation error</a>.
+   */
+  @Test void testGroupByExprArgFieldSameWithAlias2() {
+    final String sql = "SELECT deptno / 2 AS deptno, deptno / 2 as empno, 
sum(sal)\n"
+        + "FROM emp\n"
+        + "GROUP BY GROUPING SETS "
+        + "((deptno), (empno, deptno / 2), (2, 1), ((1, 2), (deptno, deptno / 
2)))";
+    sql(sql)
+        .withConformance(SqlConformanceEnum.LENIENT)
+        .ok();
+  }
+
   @Test void testAliasInHaving() {
     sql("select count(empno) as e from emp having e > 1")
         .withConformance(SqlConformanceEnum.LENIENT).ok();
diff --git a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java 
b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
index 89ae082113..75bd3498ad 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -25,8 +25,12 @@ import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.rel.type.RelDataTypeSystem;
 import org.apache.calcite.rel.type.TimeFrameSet;
 import org.apache.calcite.runtime.CalciteContextException;
+import org.apache.calcite.sql.SqlBasicFunction;
 import org.apache.calcite.sql.SqlCollation;
+import org.apache.calcite.sql.SqlFunction;
+import org.apache.calcite.sql.SqlFunctionCategory;
 import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.SqlOperator;
 import org.apache.calcite.sql.SqlOperatorTable;
@@ -37,9 +41,12 @@ import org.apache.calcite.sql.fun.SqlStdOperatorTable;
 import org.apache.calcite.sql.parser.SqlParseException;
 import org.apache.calcite.sql.parser.SqlParser;
 import org.apache.calcite.sql.type.ArraySqlType;
+import org.apache.calcite.sql.type.OperandTypes;
+import org.apache.calcite.sql.type.ReturnTypes;
 import org.apache.calcite.sql.type.SqlTypeFactoryImpl;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.sql.type.SqlTypeUtil;
+import org.apache.calcite.sql.util.SqlOperatorTables;
 import org.apache.calcite.sql.util.SqlShuttle;
 import org.apache.calcite.sql.validate.SqlAbstractConformance;
 import org.apache.calcite.sql.validate.SqlConformance;
@@ -6381,6 +6388,35 @@ public class SqlValidatorTest extends 
SqlValidatorTestCase {
     // Group by alias with strict conformance should fail.
     sql("select empno as e from emp group by ^e^")
         .withConformance(strict).fails("Column 'E' not found in any table");
+
+    sql("select floor(empno/2) as empno from emp group by floor(empno/2)")
+        .withConformance(strict).ok()
+        .withConformance(lenient).ok();
+  }
+
+  /**
+   * Tests validation of alias in function within GROUP BY.
+   *
+   * @see SqlConformance#isGroupByAlias()
+   */
+  @Test void testAliasInFunctionWithinGroupBy() {
+    final SqlConformanceEnum lenient = SqlConformanceEnum.LENIENT;
+    final SqlFunction date_add =
+        SqlBasicFunction.create(SqlKind.DATE_ADD, ReturnTypes.DATE,
+                OperandTypes.STRING_INTEGER)
+            .withFunctionType(SqlFunctionCategory.TIMEDATE);
+
+    sql("select date_add('2024-01-01', empno) as empno from emp group by 
^year(empno)^")
+        .withOperatorTable(
+            SqlOperatorTables.chain(
+            SqlOperatorTables.of(date_add), SqlStdOperatorTable.instance()))
+        .withConformance(lenient)
+        .fails("Cannot apply 'EXTRACT' to arguments of "
+            + "type 'EXTRACT\\(<INTERVAL YEAR> FROM <INTEGER>\\)'\\. "
+            + "Supported form\\(s\\): "
+            + "'EXTRACT\\(<DATETIME_INTERVAL> FROM <DATETIME_INTERVAL>\\)'\n"
+            + "'EXTRACT\\(<DATETIME_INTERVAL> FROM <DATETIME>\\)'\n"
+            + "'EXTRACT\\(<INTERVAL_DAY_TIME> FROM <INTERVAL_YEAR_MONTH>\\)'");
   }
 
   /**
@@ -6446,6 +6482,10 @@ public class SqlValidatorTest extends 
SqlValidatorTestCase {
     sql("select deptno from emp group by ^100^, deptno")
         .withConformance(lenient).fails("Ordinal out of range")
         .withConformance(strict).ok();
+    sql("select floor(e.deptno / 2) AS deptno from emp as e\n"
+        + "join dept as d on e.deptno = d.deptno group by ^deptno^")
+        .withConformance(strict).fails("Column 'DEPTNO' is ambiguous")
+        .withConformance(lenient).ok();
   }
 
   /** Test case for
diff --git 
a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml 
b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
index f62be7d0bf..8fa9233809 100644
--- a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
@@ -2409,6 +2409,45 @@ ROLLUP (deptno, job)]]>
 LogicalAggregate(group=[{0, 1}], groups=[[{0, 1}, {0}, {1}, {}]], 
EXPR$2=[COUNT()])
   LogicalProject(DEPTNO=[$7], JOB=[$2])
     LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testGroupByExprArgFieldSameWithAlias">
+    <Resource name="sql">
+      <![CDATA[SELECT floor(deptno / 2) AS deptno
+FROM emp
+GROUP BY floor(deptno / 2)]]>
+    </Resource>
+    <Resource name="plan">
+      <![CDATA[
+LogicalAggregate(group=[{0}])
+  LogicalProject(DEPTNO=[FLOOR(/($7, 2))])
+    LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testGroupByExprArgFieldSameWithAlias2">
+    <Resource name="sql">
+      <![CDATA[SELECT deptno / 2 AS deptno, deptno / 2 as empno, sum(sal)
+FROM emp
+GROUP BY GROUPING SETS ((deptno), (empno, deptno / 2), (2, 1), ((1, 2), 
(deptno, deptno / 2)))]]>
+    </Resource>
+    <Resource name="plan">
+      <![CDATA[
+LogicalProject(DEPTNO=[$0], EMPNO=[$0], EXPR$2=[$1])
+  LogicalUnion(all=[true])
+    LogicalAggregate(group=[{0}], EXPR$2=[SUM($1)])
+      LogicalProject(EMPNO=[/($7, 2)], SAL=[$5])
+        LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+    LogicalAggregate(group=[{0}], EXPR$2=[SUM($1)])
+      LogicalProject(EMPNO=[/($7, 2)], SAL=[$5])
+        LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+    LogicalAggregate(group=[{0}], EXPR$2=[SUM($1)])
+      LogicalProject(EMPNO=[/($7, 2)], SAL=[$5])
+        LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+    LogicalAggregate(group=[{0}], EXPR$2=[SUM($1)])
+      LogicalProject(EMPNO=[/($7, 2)], SAL=[$5])
+        LogicalTableScan(table=[[CATALOG, SALES, EMP]])
 ]]>
     </Resource>
   </TestCase>
diff --git a/core/src/test/resources/sql/misc.iq 
b/core/src/test/resources/sql/misc.iq
index 40bcc82ef9..08ecc00a6b 100644
--- a/core/src/test/resources/sql/misc.iq
+++ b/core/src/test/resources/sql/misc.iq
@@ -2286,4 +2286,22 @@ FROM "hr"."emps";
 
 !ok
 
+!use post
+
+# [CALCITE-4512] GROUP BY expression with argument name same with SELECT field 
and alias causes validation error
+SELECT floor(empno/2) as empno
+FROM emps
+GROUP BY floor(empno/2);
++-------+
+| EMPNO |
++-------+
+|    50 |
+|    55 |
+|    60 |
+|    65 |
++-------+
+(4 rows)
+
+!ok
+
 # End misc.iq

Reply via email to