Skip to content

Commit cd0b8da

Browse files
committed
[CALCITE-6566] JDBC adapter should generate PI function with parentheses in most dialects
1 parent a56d069 commit cd0b8da

File tree

12 files changed

+243
-42
lines changed

12 files changed

+243
-42
lines changed

core/src/main/java/org/apache/calcite/sql/SqlSyntax.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,23 @@ public enum SqlSyntax {
147147
}
148148
},
149149

150+
/**
151+
* Function syntax which takes no parentheses and return specific constant value, for
152+
* example "PI".
153+
*
154+
* @see SqlConformance#allowNiladicConstantWithoutParentheses()
155+
*/
156+
FUNCTION_ID_CONSTANT(FUNCTION) {
157+
@Override public void unparse(
158+
SqlWriter writer,
159+
SqlOperator operator,
160+
SqlCall call,
161+
int leftPrec,
162+
int rightPrec) {
163+
SqlUtil.unparseFunctionSyntax(operator, writer, call, false);
164+
}
165+
},
166+
150167
/**
151168
* Syntax of an internal operator, which does not appear in the SQL.
152169
*/

core/src/main/java/org/apache/calcite/sql/SqlUtil.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@ public static void unparseFunctionSyntax(SqlOperator operator,
336336
// when it has 0 args, not "LOCALTIME()".
337337
return;
338338
case FUNCTION_STAR: // E.g. "COUNT(*)"
339+
case FUNCTION_ID_CONSTANT: // E.g. "PI()"
339340
case FUNCTION: // E.g. "RANK()"
340341
case ORDERED_FUNCTION: // E.g. "STRING_AGG(x)"
341342
// fall through - dealt with below
@@ -406,7 +407,8 @@ public static void unparseSqlIdentifierSyntax(
406407
// with empty argument list, e.g. LOCALTIME, we should not quote
407408
// such identifier cause quoted `LOCALTIME` always represents a sql identifier.
408409
if (asFunctionID
409-
|| operator.getSyntax() == SqlSyntax.FUNCTION_ID) {
410+
|| operator.getSyntax() == SqlSyntax.FUNCTION_ID
411+
|| operator.getSyntax() == SqlSyntax.FUNCTION_ID_CONSTANT) {
410412
writer.keyword(identifier.getSimple());
411413
unparsedAsFunc = true;
412414
}

core/src/main/java/org/apache/calcite/sql/dialect/OracleSqlDialect.java

Lines changed: 39 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.apache.calcite.sql.SqlDialect;
2929
import org.apache.calcite.sql.SqlLiteral;
3030
import org.apache.calcite.sql.SqlNode;
31+
import org.apache.calcite.sql.SqlSyntax;
3132
import org.apache.calcite.sql.SqlTimeLiteral;
3233
import org.apache.calcite.sql.SqlTimestampLiteral;
3334
import org.apache.calcite.sql.SqlUtil;
@@ -170,44 +171,48 @@ public OracleSqlDialect(Context context) {
170171
if (call.getOperator() == SqlStdOperatorTable.SUBSTRING) {
171172
SqlUtil.unparseFunctionSyntax(SqlLibraryOperators.SUBSTR_ORACLE, writer,
172173
call, false);
173-
} else {
174-
switch (call.getKind()) {
175-
case POSITION:
176-
final SqlWriter.Frame frame = writer.startFunCall("INSTR");
177-
writer.sep(",");
178-
call.operand(1).unparse(writer, leftPrec, rightPrec);
179-
writer.sep(",");
180-
call.operand(0).unparse(writer, leftPrec, rightPrec);
181-
if (3 == call.operandCount()) {
182-
writer.sep(",");
183-
call.operand(2).unparse(writer, leftPrec, rightPrec);
184-
}
185-
if (4 == call.operandCount()) {
186-
writer.sep(",");
187-
call.operand(2).unparse(writer, leftPrec, rightPrec);
188-
writer.sep(",");
189-
call.operand(3).unparse(writer, leftPrec, rightPrec);
190-
}
191-
writer.endFunCall(frame);
192-
break;
193-
case FLOOR:
194-
if (call.operandCount() != 2) {
195-
super.unparseCall(writer, call, leftPrec, rightPrec);
196-
return;
197-
}
198-
199-
final SqlLiteral timeUnitNode = call.operand(1);
200-
final TimeUnitRange timeUnit = timeUnitNode.getValueAs(TimeUnitRange.class);
174+
return;
175+
}
201176

202-
SqlCall call2 =
203-
SqlFloorFunction.replaceTimeUnitOperand(call, timeUnit.name(),
204-
timeUnitNode.getParserPosition());
205-
SqlFloorFunction.unparseDatetimeFunction(writer, call2, "TRUNC", true);
206-
break;
177+
if (call.getOperator().getSyntax() == SqlSyntax.FUNCTION_ID_CONSTANT) {
178+
writer.sep(call.getOperator().getName());
179+
return;
180+
}
207181

208-
default:
182+
switch (call.getKind()) {
183+
case POSITION:
184+
final SqlWriter.Frame frame = writer.startFunCall("INSTR");
185+
writer.sep(",");
186+
call.operand(1).unparse(writer, leftPrec, rightPrec);
187+
writer.sep(",");
188+
call.operand(0).unparse(writer, leftPrec, rightPrec);
189+
if (3 == call.operandCount()) {
190+
writer.sep(",");
191+
call.operand(2).unparse(writer, leftPrec, rightPrec);
192+
}
193+
if (4 == call.operandCount()) {
194+
writer.sep(",");
195+
call.operand(2).unparse(writer, leftPrec, rightPrec);
196+
writer.sep(",");
197+
call.operand(3).unparse(writer, leftPrec, rightPrec);
198+
}
199+
writer.endFunCall(frame);
200+
break;
201+
case FLOOR:
202+
if (call.operandCount() != 2) {
209203
super.unparseCall(writer, call, leftPrec, rightPrec);
204+
return;
210205
}
206+
final SqlLiteral timeUnitNode = call.operand(1);
207+
final TimeUnitRange timeUnit = timeUnitNode.getValueAs(TimeUnitRange.class);
208+
209+
SqlCall call2 =
210+
SqlFloorFunction.replaceTimeUnitOperand(call, timeUnit.name(),
211+
timeUnitNode.getParserPosition());
212+
SqlFloorFunction.unparseDatetimeFunction(writer, call2, "TRUNC", true);
213+
break;
214+
default:
215+
super.unparseCall(writer, call, leftPrec, rightPrec);
211216
}
212217
}
213218

core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1933,7 +1933,7 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
19331933
public static final SqlFunction PI =
19341934
SqlBasicFunction.create("PI", ReturnTypes.DOUBLE, OperandTypes.NILADIC,
19351935
SqlFunctionCategory.NUMERIC)
1936-
.withSyntax(SqlSyntax.FUNCTION_ID);
1936+
.withSyntax(SqlSyntax.FUNCTION_ID_CONSTANT);
19371937

19381938
/** {@code FIRST} function to be used within {@code MATCH_RECOGNIZE}. */
19391939
public static final SqlFunction FIRST =

core/src/main/java/org/apache/calcite/sql/validate/SqlAbstractConformance.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ public abstract class SqlAbstractConformance implements SqlConformance {
9393
return SqlConformanceEnum.DEFAULT.allowNiladicParentheses();
9494
}
9595

96+
@Override public boolean allowNiladicConstantWithoutParentheses() {
97+
return SqlConformanceEnum.DEFAULT.allowNiladicConstantWithoutParentheses();
98+
}
99+
96100
@Override public boolean allowExplicitRowValueConstructor() {
97101
return SqlConformanceEnum.DEFAULT.allowExplicitRowValueConstructor();
98102
}

core/src/main/java/org/apache/calcite/sql/validate/SqlConformance.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,30 @@ public interface SqlConformance {
361361
*/
362362
boolean allowNiladicParentheses();
363363

364+
/**
365+
* Whether to allow parentheses to be specified in calls to niladic functions of
366+
* returned the specific constant value.
367+
*
368+
* <p>For example, {@code PI} is a niladic function and return specific constant values pi.
369+
* In standard SQL it must be invoked with parentheses:
370+
*
371+
* <blockquote><code>VALUES PI()</code></blockquote>
372+
*
373+
* <p>If {@code allowNiladicConstantWithoutParentheses}, the following syntax is also valid:
374+
*
375+
* <blockquote><code>VALUES PI</code></blockquote>
376+
*
377+
* <p>The same function include E which result is Euler's constant.
378+
*
379+
* <p>Among the built-in conformance levels, true in
380+
* {@link SqlConformanceEnum#ORACLE_10},
381+
* {@link SqlConformanceEnum#ORACLE_12},
382+
* {@link SqlConformanceEnum#DEFAULT};
383+
* {@link SqlConformanceEnum#LENIENT};
384+
* false otherwise.
385+
*/
386+
boolean allowNiladicConstantWithoutParentheses();
387+
364388
/**
365389
* Whether to allow SQL syntax "{@code ROW(expr1, expr2, expr3)}".
366390
*

core/src/main/java/org/apache/calcite/sql/validate/SqlConformanceEnum.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,19 @@ public enum SqlConformanceEnum implements SqlConformance {
300300
}
301301
}
302302

303+
@Override public boolean allowNiladicConstantWithoutParentheses() {
304+
switch (this) {
305+
case ORACLE_10:
306+
case ORACLE_12:
307+
case DEFAULT:
308+
case LENIENT:
309+
case BABEL:
310+
return true;
311+
default:
312+
return false;
313+
}
314+
}
315+
303316
@Override public boolean allowExplicitRowValueConstructor() {
304317
switch (this) {
305318
case DEFAULT:

core/src/main/java/org/apache/calcite/sql/validate/SqlDelegatingConformance.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ protected SqlDelegatingConformance(SqlConformance delegate) {
107107
return delegate.allowNiladicParentheses();
108108
}
109109

110+
@Override public boolean allowNiladicConstantWithoutParentheses() {
111+
return delegate.allowNiladicConstantWithoutParentheses();
112+
}
110113
@Override public boolean allowExplicitRowValueConstructor() {
111114
return delegate.allowExplicitRowValueConstructor();
112115
}

core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1964,13 +1964,21 @@ protected SqlSelect createSourceSelectForDelete(SqlDelete call) {
19641964
opTab.lookupOperatorOverloads(id, null, SqlSyntax.FUNCTION, list,
19651965
catalogReader.nameMatcher());
19661966
for (SqlOperator operator : list) {
1967-
if (operator.getSyntax() == SqlSyntax.FUNCTION_ID) {
1967+
if (operator.getSyntax() == SqlSyntax.FUNCTION_ID
1968+
|| operator.getSyntax() == SqlSyntax.FUNCTION_ID_CONSTANT) {
19681969
// Even though this looks like an identifier, it is a
19691970
// actually a call to a function. Construct a fake
19701971
// call to this function, so we can use the regular
19711972
// operator validation.
1972-
return new SqlBasicCall(operator, ImmutableList.of(),
1973-
id.getParserPosition(), null).withExpanded(true);
1973+
SqlCall sqlCall =
1974+
new SqlBasicCall(operator, ImmutableList.of(), id.getParserPosition(), null)
1975+
.withExpanded(true);
1976+
if (operator.getSyntax() == SqlSyntax.FUNCTION_ID_CONSTANT
1977+
&& !this.config.conformance().allowNiladicConstantWithoutParentheses()) {
1978+
throw handleUnresolvedFunction(sqlCall, operator,
1979+
ImmutableList.of(), null);
1980+
}
1981+
return sqlCall;
19741982
}
19751983
}
19761984
}
@@ -2072,7 +2080,8 @@ RelDataType deriveTypeImpl(
20722080
if (overloads.size() == 1) {
20732081
SqlFunction fun = (SqlFunction) overloads.get(0);
20742082
if ((fun.getSqlIdentifier() == null)
2075-
&& (fun.getSyntax() != SqlSyntax.FUNCTION_ID)) {
2083+
&& (fun.getSyntax() != SqlSyntax.FUNCTION_ID
2084+
&& fun.getSyntax() != SqlSyntax.FUNCTION_ID_CONSTANT)) {
20762085
final int expectedArgCount =
20772086
fun.getOperandCountRange().getMin();
20782087
throw newValidationError(call,

core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,79 @@ private static String toSql(RelNode root, SqlDialect dialect,
357357
.withStarRocks().ok(expectedStarRocks);
358358
}
359359

360+
/** Test case for <a href="https://issues.apache.org/jira/browse/CALCITE-6566">[CALCITE-6566]</a>
361+
* JDBC adapter should generate PI function with parentheses in most dialects. */
362+
@Test void testPiFunction() {
363+
String query = "select PI()";
364+
final String expected = "SELECT PI()\n"
365+
+ "FROM (VALUES (0)) AS \"t\" (\"ZERO\")";
366+
final String expectedHive = "SELECT PI()";
367+
final String expectedSpark = "SELECT PI()\n"
368+
+ "FROM (VALUES (0)) `t` (`ZERO`)";
369+
final String expectedMssql = "SELECT PI()\n"
370+
+ "FROM (VALUES (0)) AS [t] ([ZERO])";
371+
final String expectedMysql = "SELECT PI()";
372+
final String expectedClickHouse = "SELECT PI()";
373+
final String expectedPresto = "SELECT PI()\n"
374+
+ "FROM (VALUES (0)) AS \"t\" (\"ZERO\")";
375+
final String expectedOracle = "SELECT PI\n"
376+
+ "FROM \"DUAL\"";
377+
sql(query)
378+
.ok(expected)
379+
.withHive().ok(expectedHive)
380+
.withSpark().ok(expectedSpark)
381+
.withMssql().ok(expectedMssql)
382+
.withMysql().ok(expectedMysql)
383+
.withClickHouse().ok(expectedClickHouse)
384+
.withPresto().ok(expectedPresto)
385+
.withOracle().ok(expectedOracle);
386+
}
387+
388+
@Test void testPiFunctionWithoutParentheses() {
389+
String query = "select PI";
390+
final String expected = "SELECT PI() AS \"PI\"\n"
391+
+ "FROM (VALUES (0)) AS \"t\" (\"ZERO\")";
392+
final String expectedHive = "SELECT PI() `PI`";
393+
final String expectedSpark = "SELECT PI() `PI`\n"
394+
+ "FROM (VALUES (0)) `t` (`ZERO`)";
395+
final String expectedMssql = "SELECT PI() AS [PI]\n"
396+
+ "FROM (VALUES (0)) AS [t] ([ZERO])";
397+
final String expectedMysql = "SELECT PI() AS `PI`";
398+
final String expectedClickHouse = "SELECT PI() AS `PI`";
399+
final String expectedPresto = "SELECT PI() AS \"PI\"\n"
400+
+ "FROM (VALUES (0)) AS \"t\" (\"ZERO\")";
401+
final String expectedOracle = "SELECT PI \"PI\"\n"
402+
+ "FROM \"DUAL\"";
403+
sql(query)
404+
.ok(expected)
405+
.withHive().ok(expectedHive)
406+
.withSpark().ok(expectedSpark)
407+
.withMssql().ok(expectedMssql)
408+
.withMysql().ok(expectedMysql)
409+
.withClickHouse().ok(expectedClickHouse)
410+
.withPresto().ok(expectedPresto)
411+
.withOracle().ok(expectedOracle);
412+
}
413+
414+
@Test void testNiladicCurrentDateFunction() {
415+
String query = "select CURRENT_DATE";
416+
final String expected = "SELECT CURRENT_DATE AS \"CURRENT_DATE\"\n"
417+
+ "FROM (VALUES (0)) AS \"t\" (\"ZERO\")";
418+
final String expectedPostgresql = "SELECT CURRENT_DATE AS \"CURRENT_DATE\"\n"
419+
+ "FROM (VALUES (0)) AS \"t\" (\"ZERO\")";
420+
final String expectedSpark = "SELECT CURRENT_DATE `CURRENT_DATE`\n"
421+
+ "FROM (VALUES (0)) `t` (`ZERO`)";
422+
final String expectedMysql = "SELECT CURRENT_DATE AS `CURRENT_DATE`";
423+
final String expectedOracle = "SELECT CURRENT_DATE \"CURRENT_DATE\"\n"
424+
+ "FROM \"DUAL\"";
425+
sql(query)
426+
.ok(expected)
427+
.withPostgresql().ok(expectedPostgresql)
428+
.withSpark().ok(expectedSpark)
429+
.withMysql().ok(expectedMysql)
430+
.withOracle().ok(expectedOracle);
431+
}
432+
360433
@Test void testPivotToSqlFromProductTable() {
361434
String query = "select * from (\n"
362435
+ " select \"shelf_width\", \"net_weight\", \"product_id\"\n"

core/src/test/resources/sql/misc.iq

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1837,6 +1837,16 @@ values pi;
18371837

18381838
!ok
18391839

1840+
values pi();
1841+
+-------------------+
1842+
| EXPR$0 |
1843+
+-------------------+
1844+
| 3.141592653589793 |
1845+
+-------------------+
1846+
(1 row)
1847+
1848+
!ok
1849+
18401850
# DEGREES function
18411851
values (degrees(pi), degrees(-pi / 2));
18421852
+--------+--------+
@@ -2304,4 +2314,46 @@ GROUP BY floor(empno/2);
23042314

23052315
!ok
23062316

2317+
# [CALCITE-6566] JDBC adapter should generate PI function with parentheses in most dialects
2318+
2319+
!use scott-mysql
2320+
2321+
# PI function
2322+
values pi;
2323+
No match found for function signature PI()
2324+
!error
2325+
2326+
values pi();
2327+
+-------------------+
2328+
| EXPR$0 |
2329+
+-------------------+
2330+
| 3.141592653589793 |
2331+
+-------------------+
2332+
(1 row)
2333+
2334+
!ok
2335+
2336+
!use scott
2337+
2338+
# PI function
2339+
values pi;
2340+
+-------------------+
2341+
| PI |
2342+
+-------------------+
2343+
| 3.141592653589793 |
2344+
+-------------------+
2345+
(1 row)
2346+
2347+
!ok
2348+
2349+
values pi();
2350+
+-------------------+
2351+
| EXPR$0 |
2352+
+-------------------+
2353+
| 3.141592653589793 |
2354+
+-------------------+
2355+
(1 row)
2356+
2357+
!ok
2358+
23072359
# End misc.iq

0 commit comments

Comments
 (0)