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-5423] Implement TIMESTAMP_DIFF function (compatible with Big… #22

Open
wants to merge 1 commit into
base: test-5408-druid
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions babel/src/main/codegen/config.fmpp
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,7 @@ data: {
# "TIME"
"TIME_TRUNC"
# "TIMESTAMP"
"TIMESTAMP_DIFF"
"TIMESTAMP_TRUNC"
"TIMEZONE_HOUR"
"TIMEZONE_MINUTE"
Expand Down
37 changes: 18 additions & 19 deletions babel/src/test/resources/sql/big-query.iq
Original file line number Diff line number Diff line change
Expand Up @@ -1622,43 +1622,42 @@ SELECT

# Display of results may differ, depending upon the environment and
# time zone where this query was executed.
!if (false) {
SELECT
TIMESTAMP("2010-07-07 10:20:00+00") AS later_timestamp,
TIMESTAMP("2008-12-25 15:30:00+00") AS earlier_timestamp,
TIMESTAMP_DIFF(TIMESTAMP "2010-07-07 10:20:00+00", TIMESTAMP "2008-12-25 15:30:00+00", HOUR) AS hours;
+-------------------------+-------------------------+-------+
| later_timestamp | earlier_timestamp | hours |
+-------------------------+-------------------------+-------+
| 2010-07-07 10:20:00 UTC | 2008-12-25 15:30:00 UTC | 13410 |
+-------------------------+-------------------------+-------+
TIMESTAMP "2010-07-07 10:20:00" AS later_timestamp,
TIMESTAMP "2008-12-25 15:30:00" AS earlier_timestamp,
TIMESTAMP_DIFF(TIMESTAMP "2010-07-07 10:20:00", TIMESTAMP "2008-12-25 15:30:00", HOUR) AS hours;
+---------------------+---------------------+-------+
| later_timestamp | earlier_timestamp | hours |
+---------------------+---------------------+-------+
| 2010-07-07 10:20:00 | 2008-12-25 15:30:00 | 13410 |
+---------------------+---------------------+-------+
(1 row)

!ok
!}

# In the following example, the first timestamp occurs
# before the second timestamp, resulting in a negative output.
!if (false) {
SELECT TIMESTAMP_DIFF(TIMESTAMP "2018-08-14", TIMESTAMP "2018-10-14", DAY);
SELECT TIMESTAMP_DIFF(TIMESTAMP "2018-08-14", TIMESTAMP "2018-10-14", DAY) AS negative_diff;
+---------------+
| negative_diff |
+---------------+
| -61 |
| -61 |
+---------------+
(1 row)

!ok
!}

# In this example, the result is 0 because only the number
# of whole specified HOUR intervals are included.
!if (false) {
SELECT TIMESTAMP_DIFF("2001-02-01 01:00:00", "2001-02-01 00:00:01", HOUR);
SELECT TIMESTAMP_DIFF("2001-02-01 01:00:00", "2001-02-01 00:00:01", HOUR) AS negative_diff;
+---------------+
| negative_diff |
+---------------+
| 0 |
| 0 |
+---------------+
!ok
!}
(1 row)

!ok
#####################################################################
# DATE_TRUNC
#
Expand Down
1 change: 1 addition & 0 deletions core/src/main/codegen/default_config.fmpp
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ parser: {
"TIME_TRUNC"
"TIMESTAMPADD"
"TIMESTAMPDIFF"
"TIMESTAMP_DIFF"
"TIMESTAMP_TRUNC"
"TOP_LEVEL_COUNT"
"TRANSACTION"
Expand Down
36 changes: 36 additions & 0 deletions core/src/main/codegen/templates/Parser.jj
Original file line number Diff line number Diff line change
Expand Up @@ -6039,6 +6039,8 @@ SqlNode BuiltinFunctionCall() :
node = TimestampAddFunctionCall() { return node; }
|
node = TimestampDiffFunctionCall() { return node; }
|
node = TimestampDiff3FunctionCall() { return node; }
|
node = TimestampTruncFunctionCall() { return node; }
|
Expand Down Expand Up @@ -6588,6 +6590,33 @@ SqlCall TimestampDiffFunctionCall() :
}
}

/**
* Parses a call to BigQuery's TIMESTAMP_DIFF.
*
* <p>The difference between TIMESTAMPDIFF and TIMESTAMP_DIFF is the ordering of
* the parameters and the arrangement of the subtraction.
* TIMESTAMPDIFF uses (unit, timestamp1, timestamp2) with (t2 - t1), while
* TIMESTAMP_DIFF uses (timestamp1, timestamp2, unit) with (t1 - t2).
*/
SqlCall TimestampDiff3FunctionCall() :
{
final List<SqlNode> args = new ArrayList<SqlNode>();
final Span s;
final SqlIntervalQualifier unit;
}
{
<TIMESTAMP_DIFF> { s = span(); }
<LPAREN>
AddExpression(args, ExprContext.ACCEPT_SUB_QUERY)
<COMMA>
AddExpression(args, ExprContext.ACCEPT_SUB_QUERY)
<COMMA>
unit = TimeUnitOrName() { args.add(unit); }
<RPAREN> {
return SqlLibraryOperators.TIMESTAMP_DIFF3.createCall(s.end(this), args);
}
}

/**
* Parses a call to TIMESTAMP_TRUNC.
*/
Expand Down Expand Up @@ -7132,6 +7161,12 @@ SqlNode JdbcFunctionCall() :
name = call.getOperator().getName();
args = new SqlNodeList(call.getOperandList(), getPos());
}
|
LOOKAHEAD(1)
call = TimestampDiff3FunctionCall() {
name = call.getOperator().getName();
args = new SqlNodeList(call.getOperandList(), getPos());
}
|
LOOKAHEAD(3)
call = TimestampDiffFunctionCall() {
Expand Down Expand Up @@ -7977,6 +8012,7 @@ SqlPostfixOperator PostfixRowOperator() :
| < TIMESTAMP: "TIMESTAMP" >
| < TIMESTAMPADD: "TIMESTAMPADD" >
| < TIMESTAMPDIFF: "TIMESTAMPDIFF" >
| < TIMESTAMP_DIFF: "TIMESTAMP_DIFF" >
| < TIMESTAMP_TRUNC: "TIMESTAMP_TRUNC" >
| < TIMEZONE_HOUR: "TIMEZONE_HOUR" >
| < TIMEZONE_MINUTE: "TIMEZONE_MINUTE" >
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ private SqlLibraryOperators() {
* argument. */
@LibraryOperator(libraries = {MSSQL, POSTGRESQL})
public static final SqlFunction DATEDIFF =
new SqlTimestampDiffFunction("DATEDIFF");
new SqlTimestampDiffFunction("DATEDIFF",
OperandTypes.family(SqlTypeFamily.ANY, SqlTypeFamily.DATE, SqlTypeFamily.DATE));

/** The "DATE_PART(timeUnit, datetime)" function
* (Databricks, Postgres, Redshift, Snowflake). */
Expand Down Expand Up @@ -660,6 +661,16 @@ private SqlLibraryOperators() {
OperandTypes.TIMESTAMP_INTERVAL)
.withFunctionType(SqlFunctionCategory.TIMEDATE);

/** The "TIMESTAMP_DIFF(timestamp_expression, timestamp_expression, date_time_part)"
* function (BigQuery) returns the number of date_time_part between the two timestamp
* expressions.
*
* <p>TIMESTAMP_DIFF(t1, t2, unit) is equivalent to TIMESTAMPDIFF(</p>*/
@LibraryOperator(libraries = {BIG_QUERY})
public static final SqlFunction TIMESTAMP_DIFF3 =
new SqlTimestampDiffFunction("TIMESTAMP_DIFF",
OperandTypes.family(SqlTypeFamily.TIMESTAMP, SqlTypeFamily.TIMESTAMP, SqlTypeFamily.ANY));

/** The "TIME_TRUNC(time_expression, time_part)" function (BigQuery);
* truncates a TIME value to the granularity of time_part. The TIME value is
* always rounded to the beginning of time_part. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1889,7 +1889,8 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {

/** The <code>TIMESTAMPDIFF</code> function. */
public static final SqlFunction TIMESTAMP_DIFF =
new SqlTimestampDiffFunction("TIMESTAMPDIFF");
new SqlTimestampDiffFunction("TIMESTAMPDIFF",
OperandTypes.family(SqlTypeFamily.ANY, SqlTypeFamily.TIMESTAMP, SqlTypeFamily.TIMESTAMP));

/**
* Use of the <code>IN_FENNEL</code> operator forces the argument to be
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@
import org.apache.calcite.sql.SqlIntervalQualifier;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlOperatorBinding;
import org.apache.calcite.sql.type.OperandTypes;
import org.apache.calcite.sql.type.SqlTypeFamily;
import org.apache.calcite.sql.type.SqlOperandTypeChecker;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.sql.validate.SqlValidatorScope;
Expand Down Expand Up @@ -61,23 +60,33 @@
class SqlTimestampDiffFunction extends SqlFunction {
private static RelDataType inferReturnType2(SqlOperatorBinding opBinding) {
final RelDataTypeFactory typeFactory = opBinding.getTypeFactory();
TimeUnit timeUnit = opBinding.getOperandLiteralValue(0, TimeUnit.class);
TimeUnit timeUnit;
RelDataType type1;
RelDataType type2;
if (opBinding.isOperandLiteral(0, true)) {
type1 = opBinding.getOperandType(0);
type2 = opBinding.getOperandType(1);
timeUnit = opBinding.getOperandLiteralValue(2, TimeUnit.class);
} else {
timeUnit = opBinding.getOperandLiteralValue(0, TimeUnit.class);
type1 = opBinding.getOperandType(1);
type2 = opBinding.getOperandType(2);
}
SqlTypeName sqlTypeName =
timeUnit == TimeUnit.NANOSECOND
? SqlTypeName.BIGINT
: SqlTypeName.INTEGER;
return typeFactory.createTypeWithNullability(
typeFactory.createSqlType(sqlTypeName),
opBinding.getOperandType(1).isNullable()
|| opBinding.getOperandType(2).isNullable());
type1.isNullable()
|| type2.isNullable());
}

/** Creates a SqlTimestampDiffFunction. */
SqlTimestampDiffFunction(String name) {
SqlTimestampDiffFunction(String name, SqlOperandTypeChecker operandTypeChecker) {
super(name, SqlKind.TIMESTAMP_DIFF,
SqlTimestampDiffFunction::inferReturnType2, null,
OperandTypes.family(SqlTypeFamily.ANY, SqlTypeFamily.DATETIME,
SqlTypeFamily.DATETIME),
operandTypeChecker,
SqlFunctionCategory.TIMEDATE);
}

Expand All @@ -87,14 +96,20 @@ private static RelDataType inferReturnType2(SqlOperatorBinding opBinding) {

// This is either a time unit or a time frame:
//
// * In "TIMESTAMPADD(YEAR, 2, x)" operand 0 is a SqlIntervalQualifier
// with startUnit = YEAR and timeFrameName = null.
// * In "TIMESTAMPDIFF(YEAR, timestamp1, timestamp2)" operand 0 is a SqlIntervalQualifier
// with startUnit = YEAR and timeFrameName = null. The same is true for BigQuery's
// TIMESTAMP_DIFF() however the SqlIntervalQualifier is operand 2 due to differing
// parameter orders.
//
// * In "TIMESTAMPADD(MINUTE15, 2, x) operand 0 is a SqlIntervalQualifier
// with startUnit = EPOCH and timeFrameName = 'MINUTE15'.
// * In "TIMESTAMP_ADD(MINUTE15, timestamp1, timestamp2) operand 0 is a SqlIntervalQualifier
// with startUnit = EPOCH and timeFrameName = 'MINUTE15'. As above, for BigQuery's
// TIMESTAMP_DIFF() the SqlIntervalQualifier is found in operand 2 instead.
//
// If the latter, check that timeFrameName is valid.
validator.validateTimeFrame(
(SqlIntervalQualifier) call.getOperandList().get(0));
if (call.operand(2) instanceof SqlIntervalQualifier) {
validator.validateTimeFrame((SqlIntervalQualifier) call.operand(2));
} else {
validator.validateTimeFrame((SqlIntervalQualifier) call.operand(0));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ private StandardConvertletTable() {

registerOp(SqlLibraryOperators.TIMESTAMP_ADD2,
new TimestampAddConvertlet());
registerOp(SqlLibraryOperators.TIMESTAMP_DIFF3,
new TimestampDiffConvertlet());

registerOp(SqlLibraryOperators.NVL, StandardConvertletTable::convertNvl);
registerOp(SqlLibraryOperators.DECODE,
Expand Down Expand Up @@ -1898,13 +1900,24 @@ private static class TimestampDiffConvertlet implements SqlRexConvertlet {
@Override public RexNode convertCall(SqlRexContext cx, SqlCall call) {
// TIMESTAMPDIFF(unit, t1, t2)
// => (t2 - t1) UNIT
// TIMESTAMP_DIFF(t1, t2, unit)
// => (t1 - t2) UNIT
SqlIntervalQualifier qualifier;
final RexNode op1;
final RexNode op2;
if (call.operand(0).getKind() == SqlKind.INTERVAL_QUALIFIER) {
qualifier = call.operand(0);
op1 = cx.convertExpression(call.operand(1));
op2 = cx.convertExpression(call.operand(2));
} else {
qualifier = call.operand(2);
op1 = cx.convertExpression(call.operand(1));
op2 = cx.convertExpression(call.operand(0));
}
final RexBuilder rexBuilder = cx.getRexBuilder();
SqlIntervalQualifier qualifier = call.operand(0);
final TimeFrame timeFrame = cx.getValidator().validateTimeFrame(qualifier);
final TimeUnit unit = first(timeFrame.unit(), TimeUnit.EPOCH);

final RexNode op1 = cx.convertExpression(call.operand(1));
final RexNode op2 = cx.convertExpression(call.operand(2));
if (unit == TimeUnit.EPOCH && qualifier.timeFrameName != null) {
// Custom time frames have a different path. They are kept as names, and
// then handled by Java functions.
Expand Down
4 changes: 3 additions & 1 deletion site/_docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -1017,6 +1017,7 @@ TIES,
**TIMESTAMP**,
TIMESTAMPADD,
TIMESTAMPDIFF,
TIMESTAMP_DIFF,
TIMESTAMP_TRUNC,
**TIMEZONE_HOUR**,
**TIMEZONE_MINUTE**,
Expand Down Expand Up @@ -2655,7 +2656,8 @@ semantics.
| m | STRCMP(string, string) | Returns 0 if both of the strings are same and returns -1 when the first argument is smaller than the second and 1 when the second one is smaller than the first one
| b m o p | SUBSTR(string, position [, substringLength ]) | Returns a portion of *string*, beginning at character *position*, *substringLength* characters long. SUBSTR calculates lengths using characters as defined by the input character set
| b o | TANH(numeric) | Returns the hyperbolic tangent of *numeric*
| b | TIMESTAMP_ADD(timestamp, interval int64 date_part) | Adds int64_expression units of date_part to the timestamp, independent of any time zone.
| b | TIMESTAMP_ADD(timestamp, interval) | Adds *interval* to *timestamp*, independent of any time zone
| b | TIMESTAMP_DIFF(timestamp, timestamp2, timeUnit) | Returns the whole number of *timeUnit* between *timestamp* and *timestamp2*. Equivalent to `TIMESTAMPDIFF(timeUnit, timestamp2, timestamp)` and `(timestamp - timestamp2) timeUnit`
| b | TIMESTAMP_MICROS(integer) | Returns the TIMESTAMP that is *integer* microseconds after 1970-01-01 00:00:00
| b | TIMESTAMP_MILLIS(integer) | Returns the TIMESTAMP that is *integer* milliseconds after 1970-01-01 00:00:00
| b | TIMESTAMP_SECONDS(integer) | Returns the TIMESTAMP that is *integer* seconds after 1970-01-01 00:00:00
Expand Down
Loading
Loading