This is an automated email from the ASF dual-hosted git repository.
mbudiu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/main by this push:
new 010000e2c5 [CALCITE-6396] Add ADD_MONTHS function (enabled in Oracle,
Spark library)
010000e2c5 is described below
commit 010000e2c571e49fc725f4b3a7b339e16a4dd7be
Author: cancaicai <[email protected]>
AuthorDate: Sat Aug 10 16:11:05 2024 +0800
[CALCITE-6396] Add ADD_MONTHS function (enabled in Oracle, Spark library)
---
.../main/java/org/apache/calcite/sql/SqlKind.java | 3 +
.../calcite/sql/fun/SqlLibraryOperators.java | 6 ++
.../org/apache/calcite/sql/type/OperandTypes.java | 3 +
.../calcite/sql2rel/StandardConvertletTable.java | 42 ++++++++++++--
site/_docs/reference.md | 1 +
.../org/apache/calcite/test/SqlOperatorTest.java | 67 ++++++++++++++++++++++
6 files changed, 117 insertions(+), 5 deletions(-)
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlKind.java
b/core/src/main/java/org/apache/calcite/sql/SqlKind.java
index dccab4755f..0b6b48bd09 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlKind.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlKind.java
@@ -487,6 +487,9 @@ public enum SqlKind {
/** {@code DATE_ADD} function (BigQuery Semantics). */
DATE_ADD,
+ /** {@code ADD_MONTHS} function (Oracle, Spark). */
+ ADD_MONTHS,
+
/** {@code DATE_TRUNC} function (BigQuery). */
DATE_TRUNC,
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 a3069cec11..131ac7c3a7 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,6 +132,12 @@ public abstract class SqlLibraryOperators {
public static final SqlFunction DATEADD =
new SqlTimestampAddFunction("DATEADD");
+ @LibraryOperator(libraries = {ORACLE, SPARK})
+ public static final SqlFunction ADD_MONTHS =
+ SqlBasicFunction.create(SqlKind.ADD_MONTHS, ReturnTypes.ARG0_NULLABLE,
+ OperandTypes.DATE_ANY)
+ .withFunctionType(SqlFunctionCategory.TIMEDATE);
+
/** The "DATEDIFF(timeUnit, datetime, datetime2)" function
* (Microsoft SQL Server, Redshift, Snowflake).
*
diff --git a/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java
b/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java
index 5ba35b889b..6182a406ec 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java
@@ -1200,6 +1200,9 @@ public abstract class OperandTypes {
public static final SqlSingleOperandTypeChecker DATE_INTERVAL =
family(SqlTypeFamily.DATE, SqlTypeFamily.DATETIME_INTERVAL);
+ public static final SqlSingleOperandTypeChecker DATE_ANY =
+ family(SqlTypeFamily.DATE, SqlTypeFamily.ANY);
+
public static final SqlSingleOperandTypeChecker DATE_CHARACTER =
family(SqlTypeFamily.DATE, SqlTypeFamily.CHARACTER);
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 57d7eba15d..4e8b4fb892 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
@@ -207,6 +207,8 @@ public class StandardConvertletTable extends
ReflectiveConvertletTable {
registerOp(SqlLibraryOperators.DATE_ADD,
new TimestampAddConvertlet());
+ registerOp(SqlLibraryOperators.ADD_MONTHS,
+ new TimestampAddConvertlet());
registerOp(SqlLibraryOperators.DATE_DIFF,
new TimestampDiffConvertlet());
registerOp(SqlLibraryOperators.DATE_SUB,
@@ -2140,11 +2142,41 @@ public class StandardConvertletTable extends
ReflectiveConvertletTable {
final RexNode op2;
switch (call.operandCount()) {
case 2:
- // BigQuery-style 'TIMESTAMP_ADD(timestamp, interval)'
- final SqlBasicCall operandCall = call.operand(1);
- qualifier = operandCall.operand(1);
- op1 = cx.convertExpression(operandCall.operand(0));
- op2 = cx.convertExpression(call.operand(0));
+ // Oracle-style 'ADD_MONTHS(date, integer months)'
+ if (call.getOperator() == SqlLibraryOperators.ADD_MONTHS) {
+ qualifier = new SqlIntervalQualifier(TimeUnit.MONTH, null,
SqlParserPos.ZERO);
+ RexNode opfirstparameter = cx.convertExpression(call.operand(0));
+ if (opfirstparameter.getType().getSqlTypeName() ==
SqlTypeName.TIMESTAMP) {
+ RelDataType timestampType =
cx.getTypeFactory().createSqlType(SqlTypeName.DATE);
+ op2 = rexBuilder.makeCast(timestampType, opfirstparameter);
+ } else {
+ op2 = cx.convertExpression(call.operand(0));
+ }
+
+ RexNode opsecondparameter = cx.convertExpression(call.operand(1));
+ RelDataTypeFactory typeFactory = cx.getTypeFactory();
+
+ // In Calcite, cast('1.2' as integer) is invalid.
+ // For details, see
https://issues.apache.org/jira/browse/CALCITE-1439
+ // When trying to cast a string value to an integer type, Calcite may
+ // encounter errors if the string value cannot be successfully
converted.
+ // To handle this, the string needs to be first converted to a
double type,
+ // and then the double value can be converted to an integer type.
+ // Since the final target type is integer, converting the string to
double first
+ // will not lose precision for the add_months operation.
+ if (opsecondparameter.getType().getSqlTypeName() ==
SqlTypeName.CHAR) {
+ RelDataType doubleType =
typeFactory.createSqlType(SqlTypeName.DOUBLE);
+ opsecondparameter = rexBuilder.makeCast(doubleType,
opsecondparameter);
+ }
+ RelDataType intType = typeFactory.createSqlType(SqlTypeName.INTEGER);
+ op1 = rexBuilder.makeCast(intType, opsecondparameter);
+ } else {
+ // BigQuery-style 'TIMESTAMP_ADD(timestamp, interval)'
+ final SqlBasicCall operandCall = call.operand(1);
+ qualifier = operandCall.operand(1);
+ op1 = cx.convertExpression(operandCall.operand(0));
+ op2 = cx.convertExpression(call.operand(0));
+ }
break;
default:
// JDBC-style 'TIMESTAMPADD(unit, count, timestamp)'
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index fe8ff90365..02b81864ed 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -2696,6 +2696,7 @@ In the following:
| m | expr1 <=> expr2 | Whether two values are
equal, treating null values as the same, and it's similar to `IS NOT DISTINCT
FROM`
| p | ACOSD(numeric) | Returns the inverse
cosine of *numeric* in degrees as a double. Returns NaN if *numeric* is NaN.
Fails if *numeric* is less than -1.0 or greater than 1.0.
| * | ACOSH(numeric) | Returns the inverse
hyperbolic cosine of *numeric*
+| o s | ADD_MONTHS(start_date, num_months) | Returns the date that
is *num_months* after *start_date*
| s | ARRAY([expr [, expr ]*]) | Construct an array in
Apache Spark. The function allows users to use `ARRAY()` to create an empty
array
| s | ARRAY_APPEND(array, element) | Appends an *element* to
the end of the *array* and returns the result. Type of *element* should be
similar to type of the elements of the *array*. If the *array* is null, the
function will return null. If an *element* that is null, the null *element*
will be added to the end of the *array*
| s | ARRAY_COMPACT(array) | Removes null values
from the *array*
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 18f4a4e9fb..44e5978670 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -13824,6 +13824,73 @@ public class SqlOperatorTest {
f.checkNull("date_add(CAST(NULL AS DATE), interval 5 day)");
}
+ /** Test case for
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-6396">[CALCITE-6396]
+ * Add ADD_MONTHS function (enabled in Oracle, Spark library)</a>.
+ */
+ @Test void testAddMonths() {
+ final SqlOperatorFixture f0 = fixture()
+ .setFor(SqlLibraryOperators.ADD_MONTHS);
+ f0.checkFails("^add_months(date '2008-12-25', "
+ + "5)^",
+ "No match found for function signature "
+ + "ADD_MONTHS\\(<DATE>, <NUMERIC>\\)", false);
+ final Consumer<SqlOperatorFixture> consumer = f -> {
+ f.checkScalar("add_months(date '2016-02-22', 2)",
+ "2016-04-22",
+ "DATE NOT NULL");
+ f.checkScalar("add_months(date '2016-02-22', -2)",
+ "2015-12-22",
+ "DATE NOT NULL");
+ f.checkScalar("add_months('2016-08-31',1)",
+ "2016-09-30",
+ "DATE NOT NULL");
+ f.checkScalar("add_months('2016-08-31',1.0)",
+ "2016-09-30",
+ "DATE NOT NULL");
+ f.checkScalar("add_months('2016-08-31','1.0')",
+ "2016-09-30",
+ "DATE NOT NULL");
+ f.checkScalar("add_months('2016-08-31','1.1')",
+ "2016-09-30",
+ "DATE NOT NULL");
+ f.checkScalar("add_months('2016-08-31','1.6')",
+ "2016-09-30",
+ "DATE NOT NULL");
+ f.checkScalar("add_months(date '2016-02-22', -2.0)",
+ "2015-12-22",
+ "DATE NOT NULL");
+ f.checkScalar("add_months(date '2016-02-22', 2.0)",
+ "2016-04-22",
+ "DATE NOT NULL");
+ f.checkScalar("add_months(date '2016-02-22', '2')",
+ "2016-04-22",
+ "DATE NOT NULL");
+ f.checkScalar("add_months(date '2016-02-22', '-2')",
+ "2015-12-22",
+ "DATE NOT NULL");
+ f.checkScalar("add_months(timestamp '2016-02-22 13:00:01', '-2')",
+ "2015-12-22",
+ "DATE NOT NULL");
+ f.checkScalar("add_months(timestamp '2016-02-22 13:00:01', '-2.0')",
+ "2015-12-22",
+ "DATE NOT NULL");
+ f.checkScalar("add_months(timestamp '2016-02-22 13:00:01', -2)",
+ "2015-12-22",
+ "DATE NOT NULL");
+ f.checkScalar("add_months(timestamp '2016-02-22 13:00:01', -2.0)",
+ "2015-12-22",
+ "DATE NOT NULL");
+ f.checkFails("add_months(date '2016-02-22', '1e+1000')",
+ "Value Infinity out of range",
+ true);
+ f.checkNull("add_months(CAST(NULL AS DATE), 5)");
+ f.checkNull("add_months(date '2016-02-22', CAST(NULL AS INTEGER))");
+ f.checkNull("add_months(CAST(NULL AS DATE), CAST(NULL AS INTEGER))");
+ };
+ f0.forEachLibrary(list(SqlLibrary.ORACLE, SqlLibrary.SPARK), consumer);
+ }
+
@Test void testDateSub() {
final SqlOperatorFixture f0 = fixture()
.setFor(SqlLibraryOperators.DATE_SUB);