Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CALCITE-5180] Implement BigQuery Date/Time Type Aliases and Constructors #3023

Closed
wants to merge 11 commits into from
3 changes: 1 addition & 2 deletions babel/src/main/codegen/config.fmpp
Expand Up @@ -531,9 +531,8 @@ data: {

# List of methods for parsing builtin function calls.
# Return type of method implementation should be "SqlNode".
# Example: "DateFunctionCall()".
# Example: "DateaddFunctionCall()".
builtinFunctionCallMethods: [
"DateFunctionCall()"
"DateaddFunctionCall()"
]

Expand Down
20 changes: 0 additions & 20 deletions babel/src/main/codegen/includes/parserImpls.ftl
Expand Up @@ -22,26 +22,6 @@ JoinType LeftSemiJoin() :
<LEFT> <SEMI> <JOIN> { return JoinType.LEFT_SEMI_JOIN; }
}

SqlNode DateFunctionCall() :
{
final SqlFunctionCategory funcType = SqlFunctionCategory.USER_DEFINED_FUNCTION;
final SqlIdentifier qualifiedName;
final Span s;
final SqlLiteral quantifier;
final List<? extends SqlNode> args;
}
{
<DATE> {
s = span();
qualifiedName = new SqlIdentifier(unquotedIdentifier(), getPos());
}
args = FunctionParameterList(ExprContext.ACCEPT_SUB_QUERY) {
quantifier = (SqlLiteral) args.get(0);
args.remove(0);
return createCall(qualifiedName, s.end(this), funcType, quantifier, args);
}
}

SqlNode DateaddFunctionCall() :
{
final Span s;
Expand Down
11 changes: 6 additions & 5 deletions babel/src/test/java/org/apache/calcite/test/BabelParserTest.java
Expand Up @@ -213,15 +213,16 @@ class BabelParserTest extends SqlParserTest {
/** PostgreSQL and Redshift allow TIMESTAMP literals that contain only a
* date part. */
@Test void testShortTimestampLiteral() {
// Parser doesn't actually check the contents of the string. The validator
// will convert it to '1969-07-20 00:00:00', when it has decided that
// TIMESTAMP maps to the TIMESTAMP type.
sql("select timestamp '1969-07-20'")
.ok("SELECT TIMESTAMP '1969-07-20 00:00:00'");
.ok("SELECT TIMESTAMP '1969-07-20'");
// PostgreSQL allows the following. We should too.
sql("select ^timestamp '1969-07-20 1:2'^")
.fails("Illegal TIMESTAMP literal '1969-07-20 1:2': not in format "
+ "'yyyy-MM-dd HH:mm:ss'"); // PostgreSQL gives 1969-07-20 01:02:00
.ok("SELECT TIMESTAMP '1969-07-20 1:2'");
sql("select ^timestamp '1969-07-20:23:'^")
.fails("Illegal TIMESTAMP literal '1969-07-20:23:': not in format "
+ "'yyyy-MM-dd HH:mm:ss'"); // PostgreSQL gives 1969-07-20 23:00:00
.ok("SELECT TIMESTAMP '1969-07-20:23:'");
}

/** Tests parsing PostgreSQL-style "::" cast operator. */
Expand Down
12 changes: 0 additions & 12 deletions babel/src/test/java/org/apache/calcite/test/BabelQuidemTest.java
Expand Up @@ -17,7 +17,6 @@
package org.apache.calcite.test;

import org.apache.calcite.config.CalciteConnectionProperty;
import org.apache.calcite.config.Lex;
import org.apache.calcite.jdbc.CalciteConnection;
import org.apache.calcite.materialize.MaterializationService;
import org.apache.calcite.plan.Contexts;
Expand Down Expand Up @@ -102,17 +101,6 @@ public static void main(String[] args) throws Exception {
SqlConformanceEnum.BABEL)
.with(CalciteConnectionProperty.LENIENT_OPERATOR_LOOKUP, true)
.connect();
case "scott-big-query":
return CalciteAssert.that()
.with(CalciteAssert.Config.SCOTT)
.with(CalciteConnectionProperty.FUN, "standard,bigquery")
.with(CalciteConnectionProperty.LEX, Lex.BIG_QUERY)
.with(CalciteConnectionProperty.PARSER_FACTORY,
SqlBabelParserImpl.class.getName() + "#FACTORY")
.with(CalciteConnectionProperty.CONFORMANCE,
SqlConformanceEnum.BABEL)
.with(CalciteConnectionProperty.LENIENT_OPERATOR_LOOKUP, true)
.connect();
case "scott-postgresql":
return CalciteAssert.that()
.with(CalciteAssert.SchemaSpec.SCOTT)
Expand Down
117 changes: 114 additions & 3 deletions core/src/main/codegen/templates/Parser.jj
Expand Up @@ -4034,6 +4034,7 @@ SqlNode AtomicRowExpression() :
}
{
(
LOOKAHEAD(2)
e = LiteralOrIntervalExpression()
|
e = DynamicParam()
Expand Down Expand Up @@ -4609,15 +4610,116 @@ SqlLiteral DateTimeLiteral() :
}
|
<DATE> { s = span(); } p = SimpleStringLiteral() {
return SqlParserUtil.parseDateLiteral(p, s.end(this));
return SqlLiteral.createUnknown("DATE", p, s.end(this));
}
|
<DATETIME> { s = span(); } p = SimpleStringLiteral() {
return SqlLiteral.createUnknown("DATETIME", p, s.end(this));
}
|
<TIME> { s = span(); } p = SimpleStringLiteral() {
return SqlParserUtil.parseTimeLiteral(p, s.end(this));
return SqlLiteral.createUnknown("TIME", p, s.end(this));
}
|
LOOKAHEAD(2)
<TIMESTAMP> { s = span(); } p = SimpleStringLiteral() {
return SqlParserUtil.parseTimestampLiteral(p, s.end(this));
return SqlLiteral.createUnknown("TIMESTAMP", p, s.end(this));
}
|
<TIMESTAMP> { s = span(); } <WITH> <LOCAL> <TIME> <ZONE> p = SimpleStringLiteral() {
return SqlLiteral.createUnknown("TIMESTAMP WITH LOCAL TIME ZONE", p, s.end(this));
}
}

/**
* Parses BigQuery's built-in DATE() function.
*/
SqlNode DateFunctionCall() :
{
final SqlFunctionCategory funcType = SqlFunctionCategory.TIMEDATE;
final SqlIdentifier qualifiedName;
final Span s;
final SqlLiteral quantifier;
final List<? extends SqlNode> args;
}
{
<DATE> {
s = span();
qualifiedName = new SqlIdentifier(unquotedIdentifier(), getPos());
}
args = FunctionParameterList(ExprContext.ACCEPT_SUB_QUERY) {
quantifier = (SqlLiteral) args.get(0);
args.remove(0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this line doing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you look at the production for FunctionParameterList() you'll see it start like this:

{
    <LPAREN>
    (
        qualifier = AllOrDistinct() { list.add(qualifier); }
    |
        { list.add(null); }
    )
    ...

Here, list is the return value (args here in DateFunctionCall()). So the first argument is always either null, or either ALL or DISTINCT, since some functions can take those qualifiers. Here, it should always be null, although I suppose this means the parser would silently accept something like DATE(DISTINCT 2023, 01, 18), which it probably shouldn't.

return createCall(qualifiedName, s.end(this), funcType, quantifier, args);
}
}

/**
* Parses BigQuery's built-in DATETIME() function.
*/
SqlNode DatetimeFunctionCall() :
{
final SqlFunctionCategory funcType = SqlFunctionCategory.TIMEDATE;
final SqlIdentifier qualifiedName;
final Span s;
final SqlLiteral quantifier;
final List<? extends SqlNode> args;
}
{
<DATETIME> {
s = span();
qualifiedName = new SqlIdentifier(unquotedIdentifier(), getPos());
}
args = FunctionParameterList(ExprContext.ACCEPT_SUB_QUERY) {
quantifier = (SqlLiteral) args.get(0);
args.remove(0);
return createCall(qualifiedName, s.end(this), funcType, quantifier, args);
}
}

/**
* Parses BigQuery's built-in TIMESTAMP() function.
*/
SqlNode TimestampFunctionCall() :
{
final SqlFunctionCategory funcType = SqlFunctionCategory.TIMEDATE;
final SqlIdentifier qualifiedName;
final Span s;
final SqlLiteral quantifier;
final List<? extends SqlNode> args;
}
{
<TIMESTAMP> {
s = span();
qualifiedName = new SqlIdentifier(unquotedIdentifier(), getPos());
}
args = FunctionParameterList(ExprContext.ACCEPT_SUB_QUERY) {
quantifier = (SqlLiteral) args.get(0);
args.remove(0);
return createCall(qualifiedName, s.end(this), funcType, quantifier, args);
}
}

/**
* Parses BigQuery's built-in TIME() function.
*/
SqlNode TimeFunctionCall() :
{
final SqlFunctionCategory funcType = SqlFunctionCategory.TIMEDATE;
final SqlIdentifier qualifiedName;
final Span s;
final SqlLiteral quantifier;
final List<? extends SqlNode> args;
}
{
<TIME> {
s = span();
qualifiedName = new SqlIdentifier(unquotedIdentifier(), getPos());
}
args = FunctionParameterList(ExprContext.ACCEPT_SUB_QUERY) {
quantifier = (SqlLiteral) args.get(0);
args.remove(0);
return createCall(qualifiedName, s.end(this), funcType, quantifier, args);
}
}

Expand Down Expand Up @@ -6035,6 +6137,14 @@ SqlNode BuiltinFunctionCall() :
<RPAREN> {
return SqlStdOperatorTable.TRIM.createCall(s.end(this), args);
}
|
node = DateFunctionCall() { return node; }
|
node = DatetimeFunctionCall() { return node; }
|
node = TimeFunctionCall() { return node; }
|
node = TimestampFunctionCall() { return node; }
|
node = TimestampAddFunctionCall() { return node; }
|
Expand Down Expand Up @@ -7536,6 +7646,7 @@ SqlPostfixOperator PostfixRowOperator() :
| < DATA: "DATA" >
| < DATABASE: "DATABASE" >
| < DATE: "DATE" >
| < DATETIME: "DATETIME" >
| < DATETIME_INTERVAL_CODE: "DATETIME_INTERVAL_CODE" >
| < DATETIME_INTERVAL_PRECISION: "DATETIME_INTERVAL_PRECISION" >
| < DAY: "DAY" >
Expand Down
Expand Up @@ -124,6 +124,7 @@
import static org.apache.calcite.sql.fun.SqlLibraryOperators.COSH;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.DATE;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.DATEADD;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.DATETIME;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.DATE_FROM_UNIX_DATE;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.DAYNAME;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.DIFFERENCE;
Expand Down Expand Up @@ -157,6 +158,8 @@
import static org.apache.calcite.sql.fun.SqlLibraryOperators.STARTS_WITH;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.STRCMP;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.TANH;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.TIME;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.TIMESTAMP;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.TIMESTAMP_MICROS;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.TIMESTAMP_MILLIS;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.TIMESTAMP_SECONDS;
Expand Down Expand Up @@ -533,6 +536,11 @@ Builder populate2() {
defineMethod(DATE_FROM_UNIX_DATE, "dateFromUnixDate", NullPolicy.STRICT);
defineMethod(UNIX_DATE, "unixDate", NullPolicy.STRICT);

defineMethod(DATE, "date", NullPolicy.STRICT);
defineMethod(DATETIME, "datetime", NullPolicy.STRICT);
defineMethod(TIMESTAMP, "timestamp", NullPolicy.STRICT);
defineMethod(TIME, "time", NullPolicy.STRICT);

map.put(IS_NULL, new IsNullImplementor());
map.put(IS_NOT_NULL, new IsNotNullImplementor());
map.put(IS_TRUE, new IsTrueImplementor());
Expand Down Expand Up @@ -607,7 +615,6 @@ Builder populate2() {

map.put(COALESCE, new CoalesceImplementor());
map.put(CAST, new CastImplementor());
map.put(DATE, new CastImplementor());

map.put(REINTERPRET, new ReinterpretImplementor());

Expand Down
Expand Up @@ -1409,7 +1409,7 @@ public static SqlNode toSql(RexLiteral literal) {
return SqlLiteral.createTime(castNonNull(literal.getValueAs(TimeString.class)),
literal.getType().getPrecision(), POS);
case TIMESTAMP:
return SqlLiteral.createTimestamp(
return SqlLiteral.createTimestamp(typeName,
castNonNull(literal.getValueAs(TimestampString.class)),
literal.getType().getPrecision(), POS);
case ANY:
Expand Down
Expand Up @@ -85,7 +85,7 @@ public RexToSqlNodeConverterImpl(RexSqlConvertletTable convertletTable) {
// Timestamp
if (SqlTypeFamily.TIMESTAMP.getTypeNames().contains(
literal.getTypeName())) {
return SqlLiteral.createTimestamp(
return SqlLiteral.createTimestamp(literal.getTypeName(),
requireNonNull(literal.getValueAs(TimestampString.class),
"literal.getValueAs(TimestampString.class)"),
0,
Expand Down