diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java index 306630e6cc89..edde88f7588a 100644 --- a/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java +++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java @@ -132,7 +132,7 @@ private SqlLibraryOperators() { public static final SqlFunction DATEADD = new SqlTimestampAddFunction("DATEADD"); - /** The "DATE_ADD(start_date, num_days)" function + /** The "DATE_ADD(date, numDays)" function * (Spark) Returns the date that is num_days after start_date. */ @LibraryOperator(libraries = {SPARK}) public static final SqlFunction DATE_ADD_SPARK = @@ -140,6 +140,14 @@ private SqlLibraryOperators() { OperandTypes.DATE_ANY) .withFunctionType(SqlFunctionCategory.TIMEDATE); + /** The "DATE_SUB(date, numDays)" function + * (Spark) Returns the date that is num_days before start_date.*/ + @LibraryOperator(libraries = {SPARK}) + public static final SqlFunction DATE_SUB_SPARK = + SqlBasicFunction.create(SqlKind.DATE_SUB, ReturnTypes.DATE_NULLABLE, + OperandTypes.DATE_ANY) + .withFunctionType(SqlFunctionCategory.TIMEDATE); + /** The "ADD_MONTHS(start_date, num_months)" function * (SPARK) Returns the date that is num_months after start_date. */ @LibraryOperator(libraries = {ORACLE, SPARK}) diff --git a/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java b/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java index 3428c4cea331..7d9c807cd03f 100644 --- a/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java +++ b/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java @@ -216,6 +216,8 @@ private StandardConvertletTable() { new TimestampDiffConvertlet()); registerOp(SqlLibraryOperators.DATE_SUB, new TimestampSubConvertlet()); + registerOp(SqlLibraryOperators.DATE_SUB_SPARK, + new TimestampSubConvertlet()); registerOp(SqlLibraryOperators.DATETIME_ADD, new TimestampAddConvertlet()); registerOp(SqlLibraryOperators.DATETIME_DIFF, @@ -2309,10 +2311,20 @@ private static class TimestampSubConvertlet implements SqlRexConvertlet { // => timestamp - count * INTERVAL '1' UNIT final RexBuilder rexBuilder = cx.getRexBuilder(); final SqlParserPos pos = call.getParserPosition(); - final SqlBasicCall operandCall = call.operand(1); - SqlIntervalQualifier qualifier = operandCall.operand(1); - final RexNode op1 = cx.convertExpression(operandCall.operand(0)); - final RexNode op2 = cx.convertExpression(call.operand(0)); + SqlIntervalQualifier qualifier; + final RexNode op1; + final RexNode op2; + if (call.getOperator() == SqlLibraryOperators.DATE_SUB_SPARK) { + // Spark-style 'DATE_SUB(date, integer days)' + qualifier = new SqlIntervalQualifier(TimeUnit.DAY, null, SqlParserPos.ZERO); + op2 = handleFirstParameter(cx, rexBuilder, call); + op1 = handleSecondParameter(cx, rexBuilder, call); + } else { + final SqlBasicCall operandCall = call.operand(1); + qualifier = operandCall.operand(1); + op1 = cx.convertExpression(operandCall.operand(0)); + op2 = cx.convertExpression(call.operand(0)); + } final TimeFrame timeFrame = cx.getValidator().validateTimeFrame(qualifier); final TimeUnit unit = first(timeFrame.unit(), TimeUnit.EPOCH); final RexNode interval2Sub; diff --git a/site/_docs/reference.md b/site/_docs/reference.md index 3d855df7fb72..b46a6c87afae 100644 --- a/site/_docs/reference.md +++ b/site/_docs/reference.md @@ -2807,6 +2807,7 @@ In the following: | s | DATE_ADD(date, numDays) | Returns the DATE that is *numDays* after *date* | b | DATE_DIFF(date, date2, timeUnit) | Returns the whole number of *timeUnit* between *date* and *date2* | b | DATE_SUB(date, interval) | Returns the DATE value that occurs *interval* before *date* +| s | DATE_SUB(date, numDays) | Returns the DATE that is *numDays* before *date* | b | DATE_TRUNC(date, timeUnit) | Truncates *date* to the granularity of *timeUnit*, rounding to the beginning of the unit | o r s | DECODE(value, value1, result1 [, valueN, resultN ]* [, default ]) | Compares *value* to each *valueN* value one by one; if *value* is equal to a *valueN*, returns the corresponding *resultN*, else returns *default*, or NULL if *default* is not specified | p r | DIFFERENCE(string, string) | Returns a measure of the similarity of two strings, namely the number of character positions that their `SOUNDEX` values have in common: 4 if the `SOUNDEX` values are same and 0 if the `SOUNDEX` values are totally different diff --git a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java index a51779223a93..44e1958e82c6 100644 --- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java +++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java @@ -14031,6 +14031,40 @@ void testTimestampDiff(boolean coercionEnabled) { false); } + /** Test case for + * [CALCITE-6612] + * Add DATE_SUB function (enabled in Spark library). + */ + @Test void testDateSubSpark() { + final SqlOperatorFixture f0 = fixture() + .setFor(SqlLibraryOperators.DATE_SUB_SPARK); + f0.checkFails("^date_sub(date '2008-12-25', " + + "5)^", + "No match found for function signature " + + "DATE_SUB\\(, \\)", false); + + final SqlOperatorFixture f = f0.withLibrary(SqlLibrary.SPARK); + f.checkScalar("date_sub(date '2016-02-22', 2)", + "2016-02-20", + "DATE NOT NULL"); + f.checkScalar("date_sub(date '2016-03-01', 2)", + "2016-02-28", + "DATE NOT NULL"); + f.checkScalar("date_sub(timestamp '2016-02-22 13:00:01', '-2.0')", + "2016-02-24", + "DATE NOT NULL"); + f.checkScalar("date_sub(timestamp '2016-02-22 13:00:01', -2)", + "2016-02-24", + "DATE NOT NULL"); + f.checkNull("date_sub(CAST(NULL AS DATE), 5)"); + f.checkNull("date_sub(date '2016-02-22', CAST(NULL AS INTEGER))"); + f.checkNull("date_sub(CAST(NULL AS DATE), CAST(NULL AS INTEGER))"); + f.checkFails("^date_sub(time '13:00:01', -2)^", INVALID_ARGUMENTS_TYPE_VALIDATION_ERROR, + false); + f.checkFails("^date_sub(1, -2)^", INVALID_ARGUMENTS_TYPE_VALIDATION_ERROR, + false); + } + /** Test case for * [CALCITE-6396] * Add ADD_MONTHS function (enabled in Oracle, Spark library).