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

xiong 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 cd0b8da25f [CALCITE-6566] JDBC adapter should generate PI function 
with parentheses in most dialects
cd0b8da25f is described below

commit cd0b8da25f18f258cf9b5d469d3377d17d5ed0dd
Author: Xiong Duan <[email protected]>
AuthorDate: Wed Nov 20 19:30:59 2024 +0800

    [CALCITE-6566] JDBC adapter should generate PI function with parentheses in 
most dialects
---
 .../java/org/apache/calcite/sql/SqlSyntax.java     | 17 +++++
 .../main/java/org/apache/calcite/sql/SqlUtil.java  |  4 +-
 .../calcite/sql/dialect/OracleSqlDialect.java      | 73 ++++++++++++----------
 .../calcite/sql/fun/SqlStdOperatorTable.java       |  2 +-
 .../sql/validate/SqlAbstractConformance.java       |  4 ++
 .../calcite/sql/validate/SqlConformance.java       | 24 +++++++
 .../calcite/sql/validate/SqlConformanceEnum.java   | 13 ++++
 .../sql/validate/SqlDelegatingConformance.java     |  3 +
 .../calcite/sql/validate/SqlValidatorImpl.java     | 17 +++--
 .../calcite/rel/rel2sql/RelToSqlConverterTest.java | 73 ++++++++++++++++++++++
 core/src/test/resources/sql/misc.iq                | 52 +++++++++++++++
 .../org/apache/calcite/test/SqlOperatorTest.java   |  3 +-
 12 files changed, 243 insertions(+), 42 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/sql/SqlSyntax.java 
b/core/src/main/java/org/apache/calcite/sql/SqlSyntax.java
index b1825d5dc2..826891794d 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlSyntax.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlSyntax.java
@@ -147,6 +147,23 @@ public enum SqlSyntax {
     }
   },
 
+  /**
+   * Function syntax which takes no parentheses and return specific constant 
value, for
+   * example "PI".
+   *
+   * @see SqlConformance#allowNiladicConstantWithoutParentheses()
+   */
+  FUNCTION_ID_CONSTANT(FUNCTION) {
+    @Override public void unparse(
+        SqlWriter writer,
+        SqlOperator operator,
+        SqlCall call,
+        int leftPrec,
+        int rightPrec) {
+      SqlUtil.unparseFunctionSyntax(operator, writer, call, false);
+    }
+  },
+
   /**
    * Syntax of an internal operator, which does not appear in the SQL.
    */
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlUtil.java 
b/core/src/main/java/org/apache/calcite/sql/SqlUtil.java
index b507a50244..97b095d2f6 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlUtil.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlUtil.java
@@ -336,6 +336,7 @@ public abstract class SqlUtil {
         // when it has 0 args, not "LOCALTIME()".
         return;
       case FUNCTION_STAR: // E.g. "COUNT(*)"
+      case FUNCTION_ID_CONSTANT: // E.g. "PI()"
       case FUNCTION: // E.g. "RANK()"
       case ORDERED_FUNCTION: // E.g. "STRING_AGG(x)"
         // fall through - dealt with below
@@ -406,7 +407,8 @@ public abstract class SqlUtil {
       // with empty argument list, e.g. LOCALTIME, we should not quote
       // such identifier cause quoted `LOCALTIME` always represents a sql 
identifier.
       if (asFunctionID
-          || operator.getSyntax() == SqlSyntax.FUNCTION_ID) {
+          || operator.getSyntax() == SqlSyntax.FUNCTION_ID
+          || operator.getSyntax() == SqlSyntax.FUNCTION_ID_CONSTANT) {
         writer.keyword(identifier.getSimple());
         unparsedAsFunc = true;
       }
diff --git 
a/core/src/main/java/org/apache/calcite/sql/dialect/OracleSqlDialect.java 
b/core/src/main/java/org/apache/calcite/sql/dialect/OracleSqlDialect.java
index 75ce64286b..5856288057 100644
--- a/core/src/main/java/org/apache/calcite/sql/dialect/OracleSqlDialect.java
+++ b/core/src/main/java/org/apache/calcite/sql/dialect/OracleSqlDialect.java
@@ -28,6 +28,7 @@ import org.apache.calcite.sql.SqlDateLiteral;
 import org.apache.calcite.sql.SqlDialect;
 import org.apache.calcite.sql.SqlLiteral;
 import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlSyntax;
 import org.apache.calcite.sql.SqlTimeLiteral;
 import org.apache.calcite.sql.SqlTimestampLiteral;
 import org.apache.calcite.sql.SqlUtil;
@@ -170,44 +171,48 @@ public class OracleSqlDialect extends SqlDialect {
     if (call.getOperator() == SqlStdOperatorTable.SUBSTRING) {
       SqlUtil.unparseFunctionSyntax(SqlLibraryOperators.SUBSTR_ORACLE, writer,
           call, false);
-    } else {
-      switch (call.getKind()) {
-      case POSITION:
-        final SqlWriter.Frame frame = writer.startFunCall("INSTR");
-        writer.sep(",");
-        call.operand(1).unparse(writer, leftPrec, rightPrec);
-        writer.sep(",");
-        call.operand(0).unparse(writer, leftPrec, rightPrec);
-        if (3 == call.operandCount()) {
-          writer.sep(",");
-          call.operand(2).unparse(writer, leftPrec, rightPrec);
-        }
-        if (4 == call.operandCount()) {
-          writer.sep(",");
-          call.operand(2).unparse(writer, leftPrec, rightPrec);
-          writer.sep(",");
-          call.operand(3).unparse(writer, leftPrec, rightPrec);
-        }
-        writer.endFunCall(frame);
-        break;
-      case FLOOR:
-        if (call.operandCount() != 2) {
-          super.unparseCall(writer, call, leftPrec, rightPrec);
-          return;
-        }
-
-        final SqlLiteral timeUnitNode = call.operand(1);
-        final TimeUnitRange timeUnit = 
timeUnitNode.getValueAs(TimeUnitRange.class);
+      return;
+    }
 
-        SqlCall call2 =
-            SqlFloorFunction.replaceTimeUnitOperand(call, timeUnit.name(),
-                timeUnitNode.getParserPosition());
-        SqlFloorFunction.unparseDatetimeFunction(writer, call2, "TRUNC", true);
-        break;
+    if (call.getOperator().getSyntax() == SqlSyntax.FUNCTION_ID_CONSTANT) {
+      writer.sep(call.getOperator().getName());
+      return;
+    }
 
-      default:
+    switch (call.getKind()) {
+    case POSITION:
+      final SqlWriter.Frame frame = writer.startFunCall("INSTR");
+      writer.sep(",");
+      call.operand(1).unparse(writer, leftPrec, rightPrec);
+      writer.sep(",");
+      call.operand(0).unparse(writer, leftPrec, rightPrec);
+      if (3 == call.operandCount()) {
+        writer.sep(",");
+        call.operand(2).unparse(writer, leftPrec, rightPrec);
+      }
+      if (4 == call.operandCount()) {
+        writer.sep(",");
+        call.operand(2).unparse(writer, leftPrec, rightPrec);
+        writer.sep(",");
+        call.operand(3).unparse(writer, leftPrec, rightPrec);
+      }
+      writer.endFunCall(frame);
+      break;
+    case FLOOR:
+      if (call.operandCount() != 2) {
         super.unparseCall(writer, call, leftPrec, rightPrec);
+        return;
       }
+      final SqlLiteral timeUnitNode = call.operand(1);
+      final TimeUnitRange timeUnit = 
timeUnitNode.getValueAs(TimeUnitRange.class);
+
+      SqlCall call2 =
+          SqlFloorFunction.replaceTimeUnitOperand(call, timeUnit.name(),
+              timeUnitNode.getParserPosition());
+      SqlFloorFunction.unparseDatetimeFunction(writer, call2, "TRUNC", true);
+      break;
+    default:
+      super.unparseCall(writer, call, leftPrec, rightPrec);
     }
   }
 
diff --git 
a/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java 
b/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
index 9e0cfa92a2..a2e3d66504 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
@@ -1933,7 +1933,7 @@ public class SqlStdOperatorTable extends 
ReflectiveSqlOperatorTable {
   public static final SqlFunction PI =
       SqlBasicFunction.create("PI", ReturnTypes.DOUBLE, OperandTypes.NILADIC,
           SqlFunctionCategory.NUMERIC)
-          .withSyntax(SqlSyntax.FUNCTION_ID);
+          .withSyntax(SqlSyntax.FUNCTION_ID_CONSTANT);
 
   /** {@code FIRST} function to be used within {@code MATCH_RECOGNIZE}. */
   public static final SqlFunction FIRST =
diff --git 
a/core/src/main/java/org/apache/calcite/sql/validate/SqlAbstractConformance.java
 
b/core/src/main/java/org/apache/calcite/sql/validate/SqlAbstractConformance.java
index 9ff957a805..bf73e6dd83 100644
--- 
a/core/src/main/java/org/apache/calcite/sql/validate/SqlAbstractConformance.java
+++ 
b/core/src/main/java/org/apache/calcite/sql/validate/SqlAbstractConformance.java
@@ -93,6 +93,10 @@ public abstract class SqlAbstractConformance implements 
SqlConformance {
     return SqlConformanceEnum.DEFAULT.allowNiladicParentheses();
   }
 
+  @Override public boolean allowNiladicConstantWithoutParentheses() {
+    return SqlConformanceEnum.DEFAULT.allowNiladicConstantWithoutParentheses();
+  }
+
   @Override public boolean allowExplicitRowValueConstructor() {
     return SqlConformanceEnum.DEFAULT.allowExplicitRowValueConstructor();
   }
diff --git 
a/core/src/main/java/org/apache/calcite/sql/validate/SqlConformance.java 
b/core/src/main/java/org/apache/calcite/sql/validate/SqlConformance.java
index c38413e3dd..6909edcac2 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlConformance.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlConformance.java
@@ -361,6 +361,30 @@ public interface SqlConformance {
    */
   boolean allowNiladicParentheses();
 
+  /**
+   * Whether to allow parentheses to be specified in calls to niladic 
functions of
+   * returned the specific constant value.
+   *
+   * <p>For example, {@code PI} is a niladic function and return specific 
constant values pi.
+   * In standard SQL it must be invoked with parentheses:
+   *
+   * <blockquote><code>VALUES PI()</code></blockquote>
+   *
+   * <p>If {@code allowNiladicConstantWithoutParentheses}, the following 
syntax is also valid:
+   *
+   * <blockquote><code>VALUES PI</code></blockquote>
+   *
+   * <p>The same function include E which result is Euler's constant.
+   *
+   * <p>Among the built-in conformance levels, true in
+   * {@link SqlConformanceEnum#ORACLE_10},
+   * {@link SqlConformanceEnum#ORACLE_12},
+   * {@link SqlConformanceEnum#DEFAULT};
+   * {@link SqlConformanceEnum#LENIENT};
+   * false otherwise.
+   */
+  boolean allowNiladicConstantWithoutParentheses();
+
   /**
    * Whether to allow SQL syntax "{@code ROW(expr1, expr2, expr3)}".
    *
diff --git 
a/core/src/main/java/org/apache/calcite/sql/validate/SqlConformanceEnum.java 
b/core/src/main/java/org/apache/calcite/sql/validate/SqlConformanceEnum.java
index 352b1930d3..d5af742e5d 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlConformanceEnum.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlConformanceEnum.java
@@ -300,6 +300,19 @@ public enum SqlConformanceEnum implements SqlConformance {
     }
   }
 
+  @Override public boolean allowNiladicConstantWithoutParentheses() {
+    switch (this) {
+    case ORACLE_10:
+    case ORACLE_12:
+    case DEFAULT:
+    case LENIENT:
+    case BABEL:
+      return true;
+    default:
+      return false;
+    }
+  }
+
   @Override public boolean allowExplicitRowValueConstructor() {
     switch (this) {
     case DEFAULT:
diff --git 
a/core/src/main/java/org/apache/calcite/sql/validate/SqlDelegatingConformance.java
 
b/core/src/main/java/org/apache/calcite/sql/validate/SqlDelegatingConformance.java
index 1c9d033843..aecf713750 100644
--- 
a/core/src/main/java/org/apache/calcite/sql/validate/SqlDelegatingConformance.java
+++ 
b/core/src/main/java/org/apache/calcite/sql/validate/SqlDelegatingConformance.java
@@ -107,6 +107,9 @@ public class SqlDelegatingConformance implements 
SqlConformance {
     return delegate.allowNiladicParentheses();
   }
 
+  @Override public boolean allowNiladicConstantWithoutParentheses() {
+    return delegate.allowNiladicConstantWithoutParentheses();
+  }
   @Override public boolean allowExplicitRowValueConstructor() {
     return delegate.allowExplicitRowValueConstructor();
   }
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 0a80a414da..f216081fce 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
@@ -1964,13 +1964,21 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
       opTab.lookupOperatorOverloads(id, null, SqlSyntax.FUNCTION, list,
           catalogReader.nameMatcher());
       for (SqlOperator operator : list) {
-        if (operator.getSyntax() == SqlSyntax.FUNCTION_ID) {
+        if (operator.getSyntax() == SqlSyntax.FUNCTION_ID
+            || operator.getSyntax() == SqlSyntax.FUNCTION_ID_CONSTANT) {
           // Even though this looks like an identifier, it is a
           // actually a call to a function. Construct a fake
           // call to this function, so we can use the regular
           // operator validation.
-          return new SqlBasicCall(operator, ImmutableList.of(),
-              id.getParserPosition(), null).withExpanded(true);
+          SqlCall sqlCall =
+               new SqlBasicCall(operator, ImmutableList.of(), 
id.getParserPosition(), null)
+                   .withExpanded(true);
+          if (operator.getSyntax() == SqlSyntax.FUNCTION_ID_CONSTANT
+              && 
!this.config.conformance().allowNiladicConstantWithoutParentheses()) {
+            throw handleUnresolvedFunction(sqlCall, operator,
+                ImmutableList.of(), null);
+          }
+          return sqlCall;
         }
       }
     }
@@ -2072,7 +2080,8 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
     if (overloads.size() == 1) {
       SqlFunction fun = (SqlFunction) overloads.get(0);
       if ((fun.getSqlIdentifier() == null)
-          && (fun.getSyntax() != SqlSyntax.FUNCTION_ID)) {
+          && (fun.getSyntax() != SqlSyntax.FUNCTION_ID
+          && fun.getSyntax() != SqlSyntax.FUNCTION_ID_CONSTANT)) {
         final int expectedArgCount =
             fun.getOperandCountRange().getMin();
         throw newValidationError(call,
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 2349380ed2..7e82ccf841 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
@@ -357,6 +357,79 @@ class RelToSqlConverterTest {
         .withStarRocks().ok(expectedStarRocks);
   }
 
+  /** Test case for <a 
href="https://issues.apache.org/jira/browse/CALCITE-6566";>[CALCITE-6566]</a>
+   * JDBC adapter should generate PI function with parentheses in most 
dialects. */
+  @Test void testPiFunction() {
+    String query = "select PI()";
+    final String expected = "SELECT PI()\n"
+        + "FROM (VALUES (0)) AS \"t\" (\"ZERO\")";
+    final String expectedHive = "SELECT PI()";
+    final String expectedSpark = "SELECT PI()\n"
+        + "FROM (VALUES (0)) `t` (`ZERO`)";
+    final String expectedMssql = "SELECT PI()\n"
+        + "FROM (VALUES (0)) AS [t] ([ZERO])";
+    final String expectedMysql = "SELECT PI()";
+    final String expectedClickHouse = "SELECT PI()";
+    final String expectedPresto = "SELECT PI()\n"
+        + "FROM (VALUES (0)) AS \"t\" (\"ZERO\")";
+    final String expectedOracle = "SELECT PI\n"
+        + "FROM \"DUAL\"";
+    sql(query)
+        .ok(expected)
+        .withHive().ok(expectedHive)
+        .withSpark().ok(expectedSpark)
+        .withMssql().ok(expectedMssql)
+        .withMysql().ok(expectedMysql)
+        .withClickHouse().ok(expectedClickHouse)
+        .withPresto().ok(expectedPresto)
+        .withOracle().ok(expectedOracle);
+  }
+
+  @Test void testPiFunctionWithoutParentheses() {
+    String query = "select PI";
+    final String expected = "SELECT PI() AS \"PI\"\n"
+        + "FROM (VALUES (0)) AS \"t\" (\"ZERO\")";
+    final String expectedHive = "SELECT PI() `PI`";
+    final String expectedSpark = "SELECT PI() `PI`\n"
+        + "FROM (VALUES (0)) `t` (`ZERO`)";
+    final String expectedMssql = "SELECT PI() AS [PI]\n"
+        + "FROM (VALUES (0)) AS [t] ([ZERO])";
+    final String expectedMysql = "SELECT PI() AS `PI`";
+    final String expectedClickHouse = "SELECT PI() AS `PI`";
+    final String expectedPresto = "SELECT PI() AS \"PI\"\n"
+        + "FROM (VALUES (0)) AS \"t\" (\"ZERO\")";
+    final String expectedOracle = "SELECT PI \"PI\"\n"
+        + "FROM \"DUAL\"";
+    sql(query)
+        .ok(expected)
+        .withHive().ok(expectedHive)
+        .withSpark().ok(expectedSpark)
+        .withMssql().ok(expectedMssql)
+        .withMysql().ok(expectedMysql)
+        .withClickHouse().ok(expectedClickHouse)
+        .withPresto().ok(expectedPresto)
+        .withOracle().ok(expectedOracle);
+  }
+
+  @Test void testNiladicCurrentDateFunction() {
+    String query = "select CURRENT_DATE";
+    final String expected = "SELECT CURRENT_DATE AS \"CURRENT_DATE\"\n"
+        + "FROM (VALUES (0)) AS \"t\" (\"ZERO\")";
+    final String expectedPostgresql = "SELECT CURRENT_DATE AS 
\"CURRENT_DATE\"\n"
+        + "FROM (VALUES (0)) AS \"t\" (\"ZERO\")";
+    final String expectedSpark = "SELECT CURRENT_DATE `CURRENT_DATE`\n"
+        + "FROM (VALUES (0)) `t` (`ZERO`)";
+    final String expectedMysql = "SELECT CURRENT_DATE AS `CURRENT_DATE`";
+    final String expectedOracle = "SELECT CURRENT_DATE \"CURRENT_DATE\"\n"
+        + "FROM \"DUAL\"";
+    sql(query)
+        .ok(expected)
+        .withPostgresql().ok(expectedPostgresql)
+        .withSpark().ok(expectedSpark)
+        .withMysql().ok(expectedMysql)
+        .withOracle().ok(expectedOracle);
+  }
+
   @Test void testPivotToSqlFromProductTable() {
     String query = "select * from (\n"
         + "  select \"shelf_width\", \"net_weight\", \"product_id\"\n"
diff --git a/core/src/test/resources/sql/misc.iq 
b/core/src/test/resources/sql/misc.iq
index 08ecc00a6b..4fa8080b35 100644
--- a/core/src/test/resources/sql/misc.iq
+++ b/core/src/test/resources/sql/misc.iq
@@ -1837,6 +1837,16 @@ values pi;
 
 !ok
 
+values pi();
++-------------------+
+| EXPR$0            |
++-------------------+
+| 3.141592653589793 |
++-------------------+
+(1 row)
+
+!ok
+
 # DEGREES function
 values (degrees(pi), degrees(-pi / 2));
 +--------+--------+
@@ -2304,4 +2314,46 @@ GROUP BY floor(empno/2);
 
 !ok
 
+# [CALCITE-6566] JDBC adapter should generate PI function with parentheses in 
most dialects
+
+!use scott-mysql
+
+# PI function
+values pi;
+No match found for function signature PI()
+!error
+
+values pi();
++-------------------+
+| EXPR$0            |
++-------------------+
+| 3.141592653589793 |
++-------------------+
+(1 row)
+
+!ok
+
+!use scott
+
+# PI function
+values pi;
++-------------------+
+| PI                |
++-------------------+
+| 3.141592653589793 |
++-------------------+
+(1 row)
+
+!ok
+
+values pi();
++-------------------+
+| EXPR$0            |
++-------------------+
+| 3.141592653589793 |
++-------------------+
+(1 row)
+
+!ok
+
 # End misc.iq
diff --git a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java 
b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
index cea089a2ee..c00af0ff84 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -9583,8 +9583,7 @@ public class SqlOperatorTest {
     final SqlOperatorFixture f = fixture();
     f.setFor(SqlStdOperatorTable.PI, VmName.EXPAND);
     f.checkScalarApprox("PI", "DOUBLE NOT NULL", isWithin(3.1415d, 0.0001d));
-    f.checkFails("^PI()^",
-        "No match found for function signature PI\\(\\)", false);
+    f.checkScalarApprox("PI()", "DOUBLE NOT NULL", isWithin(3.1415d, 0.0001d));
 
     // assert that PI function is not dynamic [CALCITE-2750]
     assertThat("PI operator should not be identified as dynamic function",

Reply via email to