Skip to content

Commit

Permalink
[CALCITE-6566] JDBC adapter should generate PI function with parenthe…
Browse files Browse the repository at this point in the history
…ses in most dialects
  • Loading branch information
NobiGo committed Dec 11, 2024
1 parent a56d069 commit cd0b8da
Show file tree
Hide file tree
Showing 12 changed files with 243 additions and 42 deletions.
17 changes: 17 additions & 0 deletions core/src/main/java/org/apache/calcite/sql/SqlSyntax.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
4 changes: 3 additions & 1 deletion core/src/main/java/org/apache/calcite/sql/SqlUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ public static void unparseFunctionSyntax(SqlOperator operator,
// 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
Expand Down Expand Up @@ -406,7 +407,8 @@ public static void unparseSqlIdentifierSyntax(
// 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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
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;
Expand Down Expand Up @@ -170,44 +171,48 @@ public OracleSqlDialect(Context context) {
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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)}".
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ protected SqlDelegatingConformance(SqlConformance delegate) {
return delegate.allowNiladicParentheses();
}

@Override public boolean allowNiladicConstantWithoutParentheses() {
return delegate.allowNiladicConstantWithoutParentheses();
}
@Override public boolean allowExplicitRowValueConstructor() {
return delegate.allowExplicitRowValueConstructor();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1964,13 +1964,21 @@ protected SqlSelect createSourceSelectForDelete(SqlDelete call) {
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;
}
}
}
Expand Down Expand Up @@ -2072,7 +2080,8 @@ RelDataType deriveTypeImpl(
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,79 @@ private static String toSql(RelNode root, SqlDialect dialect,
.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"
Expand Down
52 changes: 52 additions & 0 deletions core/src/test/resources/sql/misc.iq
Original file line number Diff line number Diff line change
Expand Up @@ -1837,6 +1837,16 @@ values pi;

!ok

values pi();
+-------------------+
| EXPR$0 |
+-------------------+
| 3.141592653589793 |
+-------------------+
(1 row)

!ok

# DEGREES function
values (degrees(pi), degrees(-pi / 2));
+--------+--------+
Expand Down Expand Up @@ -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
Loading

0 comments on commit cd0b8da

Please sign in to comment.