Skip to content

Commit

Permalink
[CALCITE-4134] Interval expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
julianhyde committed Jul 28, 2020
1 parent e17a5c9 commit 03c76a7
Show file tree
Hide file tree
Showing 24 changed files with 444 additions and 108 deletions.
159 changes: 134 additions & 25 deletions core/src/main/codegen/templates/Parser.jj
Original file line number Diff line number Diff line change
Expand Up @@ -3770,7 +3770,7 @@ SqlNode AtomicRowExpression() :
}
{
(
e = Literal()
e = LiteralOrIntervalExpression()
|
e = DynamicParam()
|
Expand Down Expand Up @@ -4007,11 +4007,29 @@ SqlDrop SqlDrop() :
* Usually returns an SqlLiteral, but a continued string literal
* is an SqlCall expression, which concatenates 2 or more string
* literals; the validator reduces this.
*
* <p>If the context allows both literals and expressions,
* use {@link #LiteralOrIntervalExpression}, which requires less
* lookahead.
*/
SqlNode Literal() :
{
SqlNode e;
}
{
(
e = NonIntervalLiteral()
|
e = IntervalLiteral()
)
{ return e; }
}

/** Parses a literal that is not an interval literal. */
SqlNode NonIntervalLiteral() :
{
final SqlNode e;
}
{
(
e = NumericLiteral()
Expand All @@ -4021,8 +4039,6 @@ SqlNode Literal() :
e = SpecialLiteral()
|
e = DateTimeLiteral()
|
e = IntervalLiteral()
<#-- additional literal parser methods are included here -->
<#list parser.literalParserMethods as method>
|
Expand All @@ -4032,8 +4048,25 @@ SqlNode Literal() :
{
return e;
}
}


/** Parses a literal or an interval expression.
*
* <p>We include them in the same production because it is difficult to
* distinguish interval literals from interval expression (both of which
* start with the {@code INTERVAL} keyword); this way, we can use less
* LOOKAHEAD. */
SqlNode LiteralOrIntervalExpression() :
{
final SqlNode e;
}
{
(
e = IntervalLiteralOrExpression()
|
e = NonIntervalLiteral()
)
{ return e; }
}

/** Parses a unsigned numeric literal */
Expand Down Expand Up @@ -4416,6 +4449,53 @@ SqlLiteral IntervalLiteral() :
}
}

/** Parses an interval literal (e.g. {@code INTERVAL '2:3' HOUR TO MINUTE})
* or an interval expression (e.g. {@code INTERVAL emp.empno MINUTE}
* or {@code INTERVAL 3 MONTHS}). */
SqlNode IntervalLiteralOrExpression() :
{
final String p;
final SqlIntervalQualifier intervalQualifier;
int sign = 1;
final Span s;
SqlNode e;
}
{
<INTERVAL> { s = span(); }
[
<MINUS> { sign = -1; }
|
<PLUS> { sign = 1; }
]
(
// literal (with quoted string)
<QUOTED_STRING> { p = token.image; }
intervalQualifier = IntervalQualifier() {
return SqlParserUtil.parseIntervalLiteral(s.end(intervalQualifier),
sign, p, intervalQualifier);
}
|
// To keep parsing simple, any expressions besides numeric literal and
// identifiers must be enclosed in parentheses.
(
<LPAREN>
e = Expression(ExprContext.ACCEPT_SUB_QUERY)
<RPAREN>
|
e = UnsignedNumericLiteral()
|
e = CompoundIdentifier()
)
intervalQualifier = IntervalQualifierStart() {
if (sign == -1) {
e = SqlStdOperatorTable.UNARY_MINUS.createCall(e.getParserPosition(), e);
}
return SqlStdOperatorTable.INTERVAL.createCall(s.end(this), e,
intervalQualifier);
}
)
}

TimeUnit Year() :
{
}
Expand Down Expand Up @@ -4472,34 +4552,36 @@ TimeUnit Second() :

SqlIntervalQualifier IntervalQualifier() :
{
final Span s;
final TimeUnit start;
TimeUnit end = null;
int startPrec = RelDataType.PRECISION_NOT_SPECIFIED;
int secondFracPrec = RelDataType.PRECISION_NOT_SPECIFIED;
}
{
(
start = Year() [ <LPAREN> startPrec = UnsignedIntLiteral() <RPAREN> ]
start = Year() { s = span(); } startPrec = PrecisionOpt()
[
LOOKAHEAD(2) <TO> end = Month()
]
|
start = Month() [ <LPAREN> startPrec = UnsignedIntLiteral() <RPAREN> ]
start = Month() { s = span(); } startPrec = PrecisionOpt()
|
start = Day() [ <LPAREN> startPrec = UnsignedIntLiteral() <RPAREN> ]
[ LOOKAHEAD(2) <TO>
start = Day() { s = span(); } startPrec = PrecisionOpt()
[
LOOKAHEAD(2) <TO>
(
end = Hour()
|
end = Minute()
|
end = Second()
[ <LPAREN> secondFracPrec = UnsignedIntLiteral() <RPAREN> ]
end = Second() secondFracPrec = PrecisionOpt()
)
]
|
start = Hour() [ <LPAREN> startPrec = UnsignedIntLiteral() <RPAREN> ]
[ LOOKAHEAD(2) <TO>
start = Hour() { s = span(); } startPrec = PrecisionOpt()
[
LOOKAHEAD(2) <TO>
(
end = Minute()
|
Expand All @@ -4508,26 +4590,54 @@ SqlIntervalQualifier IntervalQualifier() :
)
]
|
start = Minute() [ <LPAREN> startPrec = UnsignedIntLiteral() <RPAREN> ]
[ LOOKAHEAD(2) <TO>
(
end = Second()
[ <LPAREN> secondFracPrec = UnsignedIntLiteral() <RPAREN> ]
)
start = Minute() { s = span(); } startPrec = PrecisionOpt()
[
LOOKAHEAD(2) <TO> end = Second()
[ <LPAREN> secondFracPrec = UnsignedIntLiteral() <RPAREN> ]
]
|
start = Second()
start = Second() { s = span(); }
[
<LPAREN> startPrec = UnsignedIntLiteral()
[ <COMMA> secondFracPrec = UnsignedIntLiteral() ]
<RPAREN>
]
)
{
return new SqlIntervalQualifier(start, startPrec, end, secondFracPrec,
s.end(this));
}
}

/** Interval qualifier without 'TO unit'. */
SqlIntervalQualifier IntervalQualifierStart() :
{
final Span s;
final TimeUnit start;
int startPrec = RelDataType.PRECISION_NOT_SPECIFIED;
int secondFracPrec = RelDataType.PRECISION_NOT_SPECIFIED;
}
{
(
(
start = Year()
| start = Month()
| start = Day()
| start = Hour()
| start = Minute()
)
{ s = span(); }
startPrec = PrecisionOpt()
|
start = Second() { s = span(); }
[ <LPAREN> startPrec = UnsignedIntLiteral()
[ <COMMA> secondFracPrec = UnsignedIntLiteral() ]
<RPAREN>
]
)
{
return new SqlIntervalQualifier(start,
startPrec,
end,
secondFracPrec,
getPos());
return new SqlIntervalQualifier(start, startPrec, null, secondFracPrec,
s.end(this));
}
}

Expand Down Expand Up @@ -5260,7 +5370,6 @@ int PrecisionOpt() :
int precision = -1;
}
{
LOOKAHEAD(2)
<LPAREN>
precision = UnsignedIntLiteral()
<RPAREN>
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/org/apache/calcite/sql/SqlDialect.java
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,7 @@ public void unparseSqlIntervalQualifier(SqlWriter writer,
public void unparseSqlIntervalLiteral(SqlWriter writer,
SqlIntervalLiteral literal, int leftPrec, int rightPrec) {
SqlIntervalLiteral.IntervalValue interval =
(SqlIntervalLiteral.IntervalValue) literal.getValue();
literal.getValueAs(SqlIntervalLiteral.IntervalValue.class);
writer.keyword("INTERVAL");
if (interval.getSign() == -1) {
writer.print("-");
Expand Down
5 changes: 4 additions & 1 deletion core/src/main/java/org/apache/calcite/sql/SqlKind.java
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,9 @@ public enum SqlKind {
/** {@code CASE} expression. */
CASE,

/** {@code INTERVAL} expression. */
INTERVAL,

/** {@code NULLIF} operator. */
NULLIF,

Expand Down Expand Up @@ -1066,7 +1069,7 @@ public enum SqlKind {
FILTER, WITHIN_GROUP, IGNORE_NULLS, RESPECT_NULLS,
DESCENDING, CUBE, ROLLUP, GROUPING_SETS, EXTEND, LATERAL,
SELECT, JOIN, OTHER_FUNCTION, POSITION, CAST, TRIM, FLOOR, CEIL,
TIMESTAMP_ADD, TIMESTAMP_DIFF, EXTRACT,
TIMESTAMP_ADD, TIMESTAMP_DIFF, EXTRACT, INTERVAL,
LITERAL_CHAIN, JDBC_FN, PRECEDING, FOLLOWING, ORDER_BY,
NULLS_FIRST, NULLS_LAST, COLLECTION_TABLE, TABLESAMPLE,
VALUES, WITH, WITH_ITEM, ITEM, SKIP_TO_FIRST, SKIP_TO_LAST,
Expand Down
18 changes: 18 additions & 0 deletions core/src/main/java/org/apache/calcite/sql/SqlLiteral.java
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,20 @@ public Object getValue() {
return value;
}

/**
* Returns the value of this literal as a particular type.
*
* <p>The type might be the internal type, or other convenient types.
* For example, numeric literals' values are stored internally as
* {@link BigDecimal}, but other numeric types such as {@link Long} and
* {@link Double} are also allowed.
*
* @param clazz Desired value type
* @param <T> Value type
* @return Value of the literal
*
* @throws AssertionError if the value type is not supported
*/
public <T> T getValueAs(Class<T> clazz) {
if (clazz.isInstance(value)) {
return clazz.cast(value);
Expand Down Expand Up @@ -320,6 +334,8 @@ public <T> T getValueAs(Class<T> clazz) {
return clazz.cast(BigDecimal.valueOf(getValueAs(Long.class)));
} else if (clazz == TimeUnitRange.class) {
return clazz.cast(valMonth.getIntervalQualifier().timeUnitRange);
} else if (clazz == SqlIntervalQualifier.class) {
return clazz.cast(valMonth.getIntervalQualifier());
}
break;
case INTERVAL_DAY:
Expand All @@ -341,6 +357,8 @@ public <T> T getValueAs(Class<T> clazz) {
return clazz.cast(BigDecimal.valueOf(getValueAs(Long.class)));
} else if (clazz == TimeUnitRange.class) {
return clazz.cast(valTime.getIntervalQualifier().timeUnitRange);
} else if (clazz == SqlIntervalQualifier.class) {
return clazz.cast(valTime.getIntervalQualifier());
}
break;
}
Expand Down
11 changes: 11 additions & 0 deletions core/src/main/java/org/apache/calcite/sql/SqlNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,17 @@ public abstract void unparse(
int leftPrec,
int rightPrec);

public void unparseWithParentheses(SqlWriter writer, int leftPrec,
int rightPrec, boolean parentheses) {
if (parentheses) {
final SqlWriter.Frame frame = writer.startList("(", ")");
unparse(writer, 0, 0);
writer.endList(frame);
} else {
unparse(writer, leftPrec, rightPrec);
}
}

public SqlParserPos getParserPosition() {
return pos;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,10 @@ public BigQuerySqlDialect(SqlDialect.Context context) {
}

/** BigQuery interval syntax: INTERVAL int64 time_unit. */
@Override public void unparseSqlIntervalLiteral(
SqlWriter writer, SqlIntervalLiteral literal, int leftPrec, int rightPrec) {
@Override public void unparseSqlIntervalLiteral(SqlWriter writer,
SqlIntervalLiteral literal, int leftPrec, int rightPrec) {
SqlIntervalLiteral.IntervalValue interval =
(SqlIntervalLiteral.IntervalValue) literal.getValue();
literal.getValueAs(SqlIntervalLiteral.IntervalValue.class);
writer.keyword("INTERVAL");
if (interval.getSign() == -1) {
writer.print("-");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public Db2SqlDialect(Context context) {
// If one operand is a timestamp, the other operand can be any of teh duration.

SqlIntervalLiteral.IntervalValue interval =
(SqlIntervalLiteral.IntervalValue) literal.getValue();
literal.getValueAs(SqlIntervalLiteral.IntervalValue.class);
if (interval.getSign() == -1) {
writer.print("-");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ private void unparseFloor(SqlWriter writer, SqlCall call) {
private void unparseSqlIntervalLiteralMssql(
SqlWriter writer, SqlIntervalLiteral literal, int sign) {
final SqlIntervalLiteral.IntervalValue interval =
(SqlIntervalLiteral.IntervalValue) literal.getValue();
literal.getValueAs(SqlIntervalLiteral.IntervalValue.class);
unparseSqlIntervalQualifier(writer, interval.getIntervalQualifier(),
RelDataTypeSystem.DEFAULT);
writer.sep(",", true);
Expand Down
Loading

0 comments on commit 03c76a7

Please sign in to comment.