From 0c99845d1935b5d199b4d7fd04078c9385ba8aec Mon Sep 17 00:00:00 2001
From: caicancai <2356672992@qq.com>
Date: Mon, 30 Sep 2024 18:06:37 +0800
Subject: [PATCH] [CALCITE-6612] Add DATE_SUB function(enabled in Spark
library)
---
.../calcite/sql/fun/SqlLibraryOperators.java | 10 +++++-
.../sql2rel/StandardConvertletTable.java | 20 ++++++++---
site/_docs/reference.md | 1 +
.../apache/calcite/test/SqlOperatorTest.java | 34 +++++++++++++++++++
4 files changed, 60 insertions(+), 5 deletions(-)
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).