This is an automated email from the ASF dual-hosted git repository.

libenchao pushed a commit to branch site
in repository https://gitbox.apache.org/repos/asf/calcite.git

commit b377a545983993cd8ffea3189663bcc583042730
Author: Tanner Clary <[email protected]>
AuthorDate: Wed Aug 16 14:02:54 2023 -0700

    [CALCITE-5933] Add SAFE_DIVIDE function (enabled in BigQuery library)
---
 babel/src/test/resources/sql/big-query.iq          | 555 +++++++++++++++++++++
 .../calcite/adapter/enumerable/RexImpTable.java    |  78 +--
 .../org/apache/calcite/runtime/SqlFunctions.java   | 252 ++++++++++
 .../calcite/sql/fun/SqlLibraryOperators.java       |  45 ++
 .../org/apache/calcite/sql/type/ReturnTypes.java   |  22 +
 .../org/apache/calcite/util/BuiltInMethod.java     |   4 +
 site/_docs/reference.md                            |   3 +
 .../org/apache/calcite/test/SqlOperatorTest.java   | 341 +++++++++++++
 8 files changed, 1270 insertions(+), 30 deletions(-)

diff --git a/babel/src/test/resources/sql/big-query.iq 
b/babel/src/test/resources/sql/big-query.iq
index 429ec7b830..c2df06dc91 100755
--- a/babel/src/test/resources/sql/big-query.iq
+++ b/babel/src/test/resources/sql/big-query.iq
@@ -600,6 +600,340 @@ FROM t;
 !ok
 !}
 
+#####################################################################
+# SAFE_ADD
+#
+# SAFE_ADD(value1, value2)
+#
+# Equivalent to the addition operator (+), but returns NULL if 
overflow/underflow occurs.
+SELECT SAFE_ADD(5, 4) as result;
++--------+
+| result |
++--------+
+|      9 |
++--------+
+(1 row)
+
+!ok
+
+# Overflow occurs if result is greater than 2^63 - 1
+SELECT SAFE_ADD(9223372036854775807, 2) as overflow_result;
++-----------------+
+| overflow_result |
++-----------------+
+|                 |
++-----------------+
+(1 row)
+
+!ok
+
+# Underflow occurs if result is less than -2^63
+SELECT SAFE_ADD(-9223372036854775806, -3) as underflow_result;
++------------------+
+| underflow_result |
++------------------+
+|                  |
++------------------+
+(1 row)
+
+!ok
+
+SELECT SAFE_ADD(CAST(1.7e308 as DOUBLE), CAST(1.7e308 as DOUBLE)) as 
double_overflow;
++-----------------+
+| double_overflow |
++-----------------+
+|                 |
++-----------------+
+(1 row)
+
+!ok
+
+SELECT SAFE_ADD(9, cast(9.999999999999999999e75 as DECIMAL(38, 19))) as 
decimal_overflow;
++------------------+
+| decimal_overflow |
++------------------+
+|                  |
++------------------+
+(1 row)
+
+!ok
+
+# NaN arguments should return NaN
+SELECT SAFE_ADD(CAST('NaN' AS DOUBLE), CAST(3 as BIGINT)) as NaN_result;
++------------+
+| NaN_result |
++------------+
+|        NaN |
++------------+
+(1 row)
+
+!ok
+
+#####################################################################
+# SAFE_DIVIDE
+#
+# SAFE_DIVIDE(value1, value2)
+#
+# Equivalent to the divide operator (/), but returns NULL if 
overflow/underflow occurs 
+# or if value2 is zero.
+SELECT SAFE_DIVIDE(20, 4) as result;
++--------+
+| result |
++--------+
+|    5.0 |
++--------+
+(1 row)
+
+!ok
+
+# Dividing by zero should return NULL
+SELECT SAFE_DIVIDE(-9223372036854775806, 0) as zero_result;
++-------------+
+| zero_result |
++-------------+
+|             |
++-------------+
+(1 row)
+
+!ok
+
+SELECT SAFE_DIVIDE(CAST(1.7e308 as DOUBLE), CAST(1.7e-308 as DOUBLE)) as 
double_overflow;
++-----------------+
+| double_overflow |
++-----------------+
+|                 |
++-----------------+
+(1 row)
+
+!ok
+
+SELECT SAFE_DIVIDE(CAST(-3.5e75 AS DECIMAL(76, 0)), CAST(3.5e-75 AS 
DECIMAL(76, 0))) 
+    as decimal_overflow;
++------------------+
+| decimal_overflow |
++------------------+
+|                  |
++------------------+
+(1 row)
+
+!ok
+
+# NaN arguments should return NaN
+SELECT SAFE_DIVIDE(CAST('NaN' AS DOUBLE), CAST(3 as BIGINT)) as NaN_result;
++------------+
+| NaN_result |
++------------+
+|        NaN |
++------------+
+(1 row)
+
+!ok
+
+# Expression parameters should be valid
+SELECT SAFE_DIVIDE(1, CAST(1 AS DOUBLE) / CAST(9223372036854775807 AS DOUBLE)) 
as expr_result;
++----------------------+
+| expr_result          |
++----------------------+
+| 9.223372036854776E18 |
++----------------------+
+(1 row)
+
+!ok
+#####################################################################
+# SAFE_MULTIPLY
+#
+# SAFE_MULTIPLY(value1, value2)
+#
+# Equivalent to the mulitply operator (*), but returns NULL if 
overflow/underflow occurs.
+SELECT SAFE_MULTIPLY(5, 4) as result;
++--------+
+| result |
++--------+
+|     20 |
++--------+
+(1 row)
+
+!ok
+
+# Overflow occurs if result is greater than 2^63 - 1
+SELECT SAFE_MULTIPLY(9223372036854775807, 2) as overflow_result;
++-----------------+
+| overflow_result |
++-----------------+
+|                 |
++-----------------+
+(1 row)
+
+!ok
+
+# Underflow occurs if result is less than -2^63
+SELECT SAFE_MULTIPLY(-9223372036854775806, 3) as underflow_result;
++------------------+
+| underflow_result |
++------------------+
+|                  |
++------------------+
+(1 row)
+
+!ok
+
+SELECT SAFE_MULTIPLY(CAST(1.7e308 as DOUBLE), CAST(3 as BIGINT)) as 
double_overflow;
++-----------------+
+| double_overflow |
++-----------------+
+|                 |
++-----------------+
+(1 row)
+
+!ok
+
+SELECT SAFE_MULTIPLY(CAST(-3.5e75 AS DECIMAL(76, 0)), CAST(10 AS BIGINT)) as 
decimal_overflow;
++------------------+
+| decimal_overflow |
++------------------+
+|                  |
++------------------+
+(1 row)
+
+!ok
+
+# NaN arguments should return NaN
+SELECT SAFE_MULTIPLY(CAST('NaN' AS DOUBLE), CAST(3 as BIGINT)) as NaN_result;
++------------+
+| NaN_result |
++------------+
+|        NaN |
++------------+
+(1 row)
+
+!ok
+
+#####################################################################
+# SAFE_NEGATE
+#
+# SAFE_NEGATE(value)
+#
+# Returns value * -1, or NULL on overflow.
+SELECT SAFE_NEGATE(5) as result;
++--------+
+| result |
++--------+
+|     -5 |
++--------+
+(1 row)
+
+!ok
+
+SELECT SAFE_NEGATE(-5) as result;
++--------+
+| result |
++--------+
+|      5 |
++--------+
+(1 row)
+
+!ok
+
+SELECT SAFE_NEGATE(-9223372036854775808) as overflow_result;
++-----------------+
+| overflow_result |
++-----------------+
+|                 |
++-----------------+
+(1 row)
+
+!ok
+
+SELECT SAFE_NEGATE(1 + 2 + 3 + 4) as expr_result;
++-------------+
+| expr_result |
++-------------+
+|         -10 |
++-------------+
+(1 row)
+
+!ok
+
+#####################################################################
+# SAFE_SUBTRACT
+#
+# SAFE_SUBTRACT(value1, value2)
+#
+# Equivalent to the subtraction operator (-), but returns NULL if 
overflow/underflow occurs.
+SELECT SAFE_SUBTRACT(5, 4) as result;
++--------+
+| result |
++--------+
+|      1 |
++--------+
+(1 row)
+
+!ok
+
+# Overflow occurs if result is greater than 2^63 - 1
+SELECT SAFE_SUBTRACT(9223372036854775807, -2) as overflow_result;
++-----------------+
+| overflow_result |
++-----------------+
+|                 |
++-----------------+
+(1 row)
+
+!ok
+
+# Underflow occurs if result is less than -2^63
+SELECT SAFE_SUBTRACT(-9223372036854775806, 3) as underflow_result;
++------------------+
+| underflow_result |
++------------------+
+|                  |
++------------------+
+(1 row)
+
+!ok
+
+SELECT SAFE_SUBTRACT(CAST(1.7e308 as DOUBLE), CAST(-1.7e308 as DOUBLE)) as 
double_overflow;
++-----------------+
+| double_overflow |
++-----------------+
+|                 |
++-----------------+
+(1 row)
+
+!ok
+
+SELECT SAFE_SUBTRACT(9, cast(-9.999999999999999999e75 as DECIMAL(38, 19))) as 
decimal_overflow;
++------------------+
+| decimal_overflow |
++------------------+
+|                  |
++------------------+
+(1 row)
+
+!ok
+
+# NaN arguments should return NaN
+SELECT SAFE_SUBTRACT(CAST('NaN' AS DOUBLE), CAST(3 as BIGINT)) as NaN_result;
++------------+
+| NaN_result |
++------------+
+|        NaN |
++------------+
+(1 row)
+
+!ok
+
+# Expression parameters should be valid
+SELECT SAFE_SUBTRACT(1 + 2 + 3, 5) as expr_result;
++-------------+
+| expr_result |
++-------------+
+|           1 |
++-------------+
+(1 row)
+
+!ok
+
 #####################################################################
 # NOT EQUAL Operator (value1 != value2)
 #
@@ -648,6 +982,227 @@ SELECT (19 % 19) as result;
 
 !ok
 
+#####################################################################
+# REGEXP_CONTAINS(value, regexp)
+#
+# Takes two STRING values. Returns TRUE if value is a partial match
+# for the regular expression, regexp.
+# If the regexp argument is invalid, the function returns an error.
+# Uses java.util.regex as a standard for regex processing
+# in Calcite instead of RE2 used by BigQuery/GoogleSQL.
+
+SELECT
+  email,
+  REGEXP_CONTAINS(email, '@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+') AS is_valid
+FROM
+  (SELECT
+    ARRAY['[email protected]', '[email protected]', 'www.example.net']
+    AS addresses),
+  UNNEST(addresses) AS email;
++-----------------+----------+
+| email           | is_valid |
++-----------------+----------+
+| [email protected] | true     |
+| [email protected] | true     |
+| www.example.net | false    |
++-----------------+----------+
+(3 rows)
+
+!ok
+
+SELECT
+  email,
+  REGEXP_CONTAINS(email, '^([\w.+-]+@foo\.com|[\w.+-]+@bar\.org)$')
+    AS valid_email_address,
+  REGEXP_CONTAINS(email, '^[\w.+-]+@foo\.com|[\w.+-]+@bar\.org$')
+    AS without_parentheses
+FROM
+  (SELECT
+    ARRAY['[email protected]', '[email protected]', '[email protected]', '[email protected]', 
'[email protected]']
+    AS addresses),
+  UNNEST(addresses) AS email;
++----------------+---------------------+---------------------+
+| email          | valid_email_address | without_parentheses |
++----------------+---------------------+---------------------+
+| [email protected]      | true                | true                |
+| [email protected] | false               | true                |
+| [email protected]      | true                | true                |
+| [email protected]     | false               | true                |
+| [email protected]      | false               | false               |
++----------------+---------------------+---------------------+
+(5 rows)
+
+!ok
+
+SELECT REGEXP_CONTAINS('abc def ghi', '(abc');
+Invalid regular expression for REGEXP_CONTAINS: 'Unclosed group near index 4 
(abc'
+!error
+
+SELECT REGEXP_CONTAINS('abc def ghi', '[z-a]');
+Invalid regular expression for REGEXP_CONTAINS: 'Illegal character range near 
index 3 [z-a]    ^'
+!error
+
+SELECT REGEXP_CONTAINS('abc def ghi', '{2,1}');
+Invalid regular expression for REGEXP_CONTAINS: 'Illegal repetition range near 
index 4 {2,1}     ^'
+!error
+
+#####################################################################
+# REGEXP_EXTRACT(value, regexp[, position[, occurrence]])
+#
+# Returns the substring in value that matches the regexp.
+# Returns NULL if there is no match, or if position or occurrence are beyond 
range.
+# Returns an exception if regex, position or occurrence are invalid.
+
+WITH email_addresses AS
+  (SELECT '[email protected]' as email
+  UNION ALL
+  SELECT '[email protected]' as email
+  UNION ALL
+  SELECT '[email protected]' as email)
+SELECT
+  REGEXP_EXTRACT(email, '^[a-zA-Z0-9_.+-]+')
+  AS user_name
+FROM email_addresses;
++-----------+
+| user_name |
++-----------+
+| foo       |
+| bar       |
+| baz       |
++-----------+
+(3 rows)
+
+!ok
+
+WITH
+characters AS (
+ SELECT 'ab' AS value, '.b' AS regex UNION ALL
+ SELECT 'ab' AS value, '(.)b' AS regex UNION ALL
+ SELECT 'xyztb' AS value, '(.)+b' AS regex UNION ALL
+ SELECT 'ab' AS value, '(z)?b' AS regex
+)
+SELECT value, regex, REGEXP_EXTRACT(value, regex) AS result FROM characters;
++-------+-------+--------+
+| value | regex | result |
++-------+-------+--------+
+| ab    | (.)b  | a      |
+| ab    | (z)?b |        |
+| ab    | .b    | ab     |
+| xyztb | (.)+b | t      |
++-------+-------+--------+
+(4 rows)
+
+!ok
+
+WITH example AS
+(SELECT 'Hello Helloo and Hellooo' AS value, 'H?ello+' AS regex, 1 as position,
+1 AS occurrence UNION ALL
+SELECT 'Hello Helloo and Hellooo', 'H?ello+', 1, 2 UNION ALL
+SELECT 'Hello Helloo and Hellooo', 'H?ello+', 1, 3 UNION ALL
+SELECT 'Hello Helloo and Hellooo', 'H?ello+', 1, 4 UNION ALL
+SELECT 'Hello Helloo and Hellooo', 'H?ello+', 2, 1 UNION ALL
+SELECT 'Hello Helloo and Hellooo', 'H?ello+', 3, 1 UNION ALL
+SELECT 'Hello Helloo and Hellooo', 'H?ello+', 3, 2 UNION ALL
+SELECT 'Hello Helloo and Hellooo', 'H?ello+', 3, 3 UNION ALL
+SELECT 'Hello Helloo and Hellooo', 'H?ello+', 20, 1 UNION ALL
+SELECT 'cats&dogs&rabbits' ,'\\w+&', 1, 2 UNION ALL
+SELECT 'cats&dogs&rabbits', '\\w+&', 2, 3
+)
+SELECT value, regex, position, occurrence, REGEXP_EXTRACT(value, regex,
+position, occurrence) AS regexp_value FROM example;
++--------------------------+---------+----------+------------+--------------+
+| value                    | regex   | position | occurrence | regexp_value |
++--------------------------+---------+----------+------------+--------------+
+| Hello Helloo and Hellooo | H?ello+ |        1 |          1 | Hello        |
+| Hello Helloo and Hellooo | H?ello+ |        1 |          2 | Helloo       |
+| Hello Helloo and Hellooo | H?ello+ |        1 |          3 | Hellooo      |
+| Hello Helloo and Hellooo | H?ello+ |        1 |          4 |              |
+| Hello Helloo and Hellooo | H?ello+ |        2 |          1 | ello         |
+| Hello Helloo and Hellooo | H?ello+ |       20 |          1 |              |
+| Hello Helloo and Hellooo | H?ello+ |        3 |          1 | Helloo       |
+| Hello Helloo and Hellooo | H?ello+ |        3 |          2 | Hellooo      |
+| Hello Helloo and Hellooo | H?ello+ |        3 |          3 |              |
+| cats&dogs&rabbits        | \\w+&   |        1 |          2 |              |
+| cats&dogs&rabbits        | \\w+&   |        2 |          3 |              |
++--------------------------+---------+----------+------------+--------------+
+(11 rows)
+
+!ok
+
+SELECT REGEXP_EXTRACT("abcadcabcaecghi", "a.+c");
++--------------+
+| EXPR$0       |
++--------------+
+| abcadcabcaec |
++--------------+
+(1 row)
+
+!ok
+
+SELECT REGEXP_EXTRACT("abcadcabcaecghi", "abc(a.c)", 4);
++--------+
+| EXPR$0 |
++--------+
+| aec    |
++--------+
+(1 row)
+
+!ok
+
+SELECT REGEXP_EXTRACT("abcadcabcaecghi", "a.c", 25);
++--------+
+| EXPR$0 |
++--------+
+|        |
++--------+
+(1 row)
+
+!ok
+
+SELECT REGEXP_EXTRACT("abcadcabcaecghi", "a.c", 1, 5);
++--------+
+| result |
++--------+
+| true   |
++--------+
+(1 row)
+
+!ok
+
+SELECT (5 != 5) as result;
++--------+
+| result |
++--------+
+| false  |
++--------+
+(1 row)
+
+!ok
+
+#####################################################################
+# MODULO % OPERATOR (value1 % value2)
+#
+# Returns the remainder of dividing value1 by value2.
+SELECT (5 % 3) as result;
++--------+
+| result |
++--------+
+|      2 |
++--------+
+(1 row)
+
+!ok
+
+SELECT (19 % 19) as result;
++--------+
+| result |
++--------+
+|      0 |
++--------+
+(1 row)
+
+!ok
+
 #####################################################################
 # SPLIT
 #
diff --git 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
index 43242677b6..322d7e63b7 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
@@ -218,6 +218,9 @@ import static 
org.apache.calcite.sql.fun.SqlLibraryOperators.RIGHT;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.RLIKE;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.RPAD;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.SAFE_CAST;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.SAFE_DIVIDE;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.SAFE_MULTIPLY;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.SAFE_NEGATE;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.SAFE_OFFSET;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.SAFE_ORDINAL;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.SEC;
@@ -579,36 +582,51 @@ public class RexImpTable {
       map.put(LOG, new LogImplementor());
       map.put(LOG10, new LogImplementor());
 
-      map.put(RAND, new RandImplementor());
-      map.put(RAND_INTEGER, new RandIntegerImplementor());
-
-      defineMethod(ACOS, "acos", NullPolicy.STRICT);
-      defineMethod(ACOSH, "acosh", NullPolicy.STRICT);
-      defineMethod(ASIN, "asin", NullPolicy.STRICT);
-      defineMethod(ASINH, "asinh", NullPolicy.STRICT);
-      defineMethod(ATAN, "atan", NullPolicy.STRICT);
-      defineMethod(ATAN2, "atan2", NullPolicy.STRICT);
-      defineMethod(ATANH, "atanh", NullPolicy.STRICT);
-      defineMethod(CBRT, "cbrt", NullPolicy.STRICT);
-      defineMethod(COS, "cos", NullPolicy.STRICT);
-      defineMethod(COSH, "cosh", NullPolicy.STRICT);
-      defineMethod(COT, "cot", NullPolicy.STRICT);
-      defineMethod(COTH, "coth", NullPolicy.STRICT);
-      defineMethod(CSC, "csc", NullPolicy.STRICT);
-      defineMethod(CSCH, "csch", NullPolicy.STRICT);
-      defineMethod(DEGREES, "degrees", NullPolicy.STRICT);
-      defineMethod(POW, "power", NullPolicy.STRICT);
-      defineMethod(RADIANS, "radians", NullPolicy.STRICT);
-      defineMethod(ROUND, "sround", NullPolicy.STRICT);
-      defineMethod(SEC, "sec", NullPolicy.STRICT);
-      defineMethod(SECH, "sech", NullPolicy.STRICT);
-      defineMethod(SIGN, "sign", NullPolicy.STRICT);
-      defineMethod(SIN, "sin", NullPolicy.STRICT);
-      defineMethod(SINH, "sinh", NullPolicy.STRICT);
-      defineMethod(TAN, "tan", NullPolicy.STRICT);
-      defineMethod(TANH, "tanh", NullPolicy.STRICT);
-      defineMethod(TRUNC, "struncate", NullPolicy.STRICT);
-      defineMethod(TRUNCATE, "struncate", NullPolicy.STRICT);
+      defineReflective(RAND, BuiltInMethod.RAND.method,
+          BuiltInMethod.RAND_SEED.method);
+      defineReflective(RAND_INTEGER, BuiltInMethod.RAND_INTEGER.method,
+          BuiltInMethod.RAND_INTEGER_SEED.method);
+
+      defineMethod(ACOS, BuiltInMethod.ACOS.method, NullPolicy.STRICT);
+      defineMethod(ACOSH, BuiltInMethod.ACOSH.method, NullPolicy.STRICT);
+      defineMethod(ASIN, BuiltInMethod.ASIN.method, NullPolicy.STRICT);
+      defineMethod(ASINH, BuiltInMethod.ASINH.method, NullPolicy.STRICT);
+      defineMethod(ATAN, BuiltInMethod.ATAN.method, NullPolicy.STRICT);
+      defineMethod(ATAN2, BuiltInMethod.ATAN2.method, NullPolicy.STRICT);
+      defineMethod(ATANH, BuiltInMethod.ATANH.method, NullPolicy.STRICT);
+      defineMethod(CBRT, BuiltInMethod.CBRT.method, NullPolicy.STRICT);
+      defineMethod(COS, BuiltInMethod.COS.method, NullPolicy.STRICT);
+      defineMethod(COSH, BuiltInMethod.COSH.method, NullPolicy.STRICT);
+      defineMethod(COT, BuiltInMethod.COT.method, NullPolicy.STRICT);
+      defineMethod(COTH, BuiltInMethod.COTH.method, NullPolicy.STRICT);
+      defineMethod(CSC, BuiltInMethod.CSC.method, NullPolicy.STRICT);
+      defineMethod(CSCH, BuiltInMethod.CSCH.method, NullPolicy.STRICT);
+      defineMethod(DEGREES, BuiltInMethod.DEGREES.method, NullPolicy.STRICT);
+      defineMethod(IS_INF, BuiltInMethod.IS_INF.method, NullPolicy.STRICT);
+      defineMethod(IS_NAN, BuiltInMethod.IS_NAN.method, NullPolicy.STRICT);
+      defineMethod(POW, BuiltInMethod.POWER.method, NullPolicy.STRICT);
+      defineMethod(RADIANS, BuiltInMethod.RADIANS.method, NullPolicy.STRICT);
+      defineMethod(ROUND, BuiltInMethod.SROUND.method, NullPolicy.STRICT);
+      defineMethod(SEC, BuiltInMethod.SEC.method, NullPolicy.STRICT);
+      defineMethod(SECH, BuiltInMethod.SECH.method, NullPolicy.STRICT);
+      defineMethod(SIGN, BuiltInMethod.SIGN.method, NullPolicy.STRICT);
+      defineMethod(SIN, BuiltInMethod.SIN.method, NullPolicy.STRICT);
+      defineMethod(SINH, BuiltInMethod.SINH.method, NullPolicy.STRICT);
+      defineMethod(TAN, BuiltInMethod.TAN.method, NullPolicy.STRICT);
+      defineMethod(TANH, BuiltInMethod.TANH.method, NullPolicy.STRICT);
+      defineMethod(TRUNC, BuiltInMethod.STRUNCATE.method, NullPolicy.STRICT);
+      defineMethod(TRUNCATE, BuiltInMethod.STRUNCATE.method, 
NullPolicy.STRICT);
+
+      map.put(SAFE_ADD,
+          new SafeArithmeticImplementor(BuiltInMethod.SAFE_ADD.method));
+      map.put(SAFE_DIVIDE,
+          new SafeArithmeticImplementor(BuiltInMethod.SAFE_DIVIDE.method));
+      map.put(SAFE_MULTIPLY,
+          new SafeArithmeticImplementor(BuiltInMethod.SAFE_MULTIPLY.method));
+      map.put(SAFE_NEGATE,
+          new SafeArithmeticImplementor(BuiltInMethod.SAFE_MULTIPLY.method));
+      map.put(SAFE_SUBTRACT,
+          new SafeArithmeticImplementor(BuiltInMethod.SAFE_SUBTRACT.method));
 
       map.put(PI, new PiImplementor());
       return populate2();
diff --git a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java 
b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
index 69834c84f6..bbd34f5916 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
@@ -1619,6 +1619,258 @@ public class SqlFunctions {
     throw notArithmetic("*", b0, b1);
   }
 
+  /** SQL <code>SAFE_ADD</code> function applied to long values. */
+  public static @Nullable Long safeAdd(long b0, long b1) {
+    try {
+      return Math.addExact(b0, b1);
+    } catch (ArithmeticException e) {
+      return null;
+    }
+  }
+
+  /** SQL <code>SAFE_ADD</code> function applied to long and BigDecimal 
values. */
+  public static @Nullable BigDecimal safeAdd(long b0, BigDecimal b1) {
+    BigDecimal ans = BigDecimal.valueOf(b0).add(b1);
+    return safeDecimal(ans) ? ans : null;
+  }
+
+  /** SQL <code>SAFE_ADD</code> function applied to BigDecimal and long 
values. */
+  public static @Nullable BigDecimal safeAdd(BigDecimal b0, long b1) {
+    return safeAdd(b1, b0);
+  }
+
+  /** SQL <code>SAFE_ADD</code> function applied to BigDecimal values. */
+  public static @Nullable BigDecimal safeAdd(BigDecimal b0, BigDecimal b1) {
+    BigDecimal ans = b0.add(b1);
+    return safeDecimal(ans) ? ans : null;
+  }
+
+  /** SQL <code>SAFE_ADD</code> function applied to double and long values. */
+  public static @Nullable Double safeAdd(double b0, long b1) {
+    double ans = b0 + b1;
+    return safeDouble(ans) || !Double.isFinite(b0) ? ans : null;
+  }
+
+  /** SQL <code>SAFE_ADD</code> function applied to long and double values. */
+  public static @Nullable Double safeAdd(long b0, double b1) {
+    return safeAdd(b1, b0);
+  }
+
+  /** SQL <code>SAFE_ADD</code> function applied to double and BigDecimal 
values. */
+  public static @Nullable Double safeAdd(double b0, BigDecimal b1) {
+    double ans = b0 + b1.doubleValue();
+    return safeDouble(ans) || !Double.isFinite(b0) ? ans : null;
+  }
+
+  /** SQL <code>SAFE_ADD</code> function applied to BigDecimal and double 
values. */
+  public static @Nullable Double safeAdd(BigDecimal b0, double b1) {
+    return safeAdd(b1, b0);
+  }
+
+  /** SQL <code>SAFE_ADD</code> function applied to double values. */
+  public static @Nullable Double safeAdd(double b0, double b1) {
+    double ans = b0 + b1;
+    boolean isFinite = Double.isFinite(b0) && Double.isFinite(b1);
+    return safeDouble(ans) || !isFinite ? ans : null;
+  }
+
+  /** SQL <code>SAFE_MULTIPLY</code> function applied to long values. */
+  public static @Nullable Double safeDivide(long b0, long b1) {
+    double ans = (double) b0 / b1;
+    return safeDouble(ans) && b1 != 0 ? ans : null;
+  }
+
+  /** SQL <code>SAFE_DIVIDE</code> function applied to long and BigDecimal 
values. */
+  public static @Nullable BigDecimal safeDivide(long b0, BigDecimal b1) {
+    try {
+      BigDecimal ans = BigDecimal.valueOf(b0).divide(b1);
+      return safeDecimal(ans) ? ans : null;
+    } catch (ArithmeticException e) {
+      return null;
+    }
+  }
+
+  /** SQL <code>SAFE_DIVIDE</code> function applied to BigDecimal and long 
values. */
+  public static @Nullable BigDecimal safeDivide(BigDecimal b0, long b1) {
+    try {
+      BigDecimal ans = b0.divide(BigDecimal.valueOf(b1));
+      return safeDecimal(ans) ? ans : null;
+    } catch (ArithmeticException e) {
+      return null;
+    }
+  }
+
+  /** SQL <code>SAFE_DIVIDE</code> function applied to BigDecimal values. */
+  public static @Nullable BigDecimal safeDivide(BigDecimal b0, BigDecimal b1) {
+    try {
+      BigDecimal ans = b0.divide(b1);
+      return safeDecimal(ans) ? ans : null;
+    } catch (ArithmeticException e) {
+      return null;
+    }
+  }
+
+  /** SQL <code>SAFE_DIVIDE</code> function applied to double and long values. 
*/
+  public static @Nullable Double safeDivide(double b0, long b1) {
+    double ans = b0 / b1;
+    return safeDouble(ans) || !Double.isFinite(b0) ? ans : null;
+  }
+
+  /** SQL <code>SAFE_DIVIDE</code> function applied to long and double values. 
*/
+  public static @Nullable Double safeDivide(long b0, double b1) {
+    double ans = b0 / b1;
+    return safeDouble(ans) || !Double.isFinite(b1) ? ans : null;
+  }
+
+  /** SQL <code>SAFE_DIVIDE</code> function applied to double and BigDecimal 
values. */
+  public static @Nullable Double safeDivide(double b0, BigDecimal b1) {
+    double ans = b0 / b1.doubleValue();
+    return safeDouble(ans) || !Double.isFinite(b0) ? ans : null;
+  }
+
+  /** SQL <code>SAFE_DIVIDE</code> function applied to BigDecimal and double 
values. */
+  public static @Nullable Double safeDivide(BigDecimal b0, double b1) {
+    double ans = b0.doubleValue() / b1;
+    return safeDouble(ans) || !Double.isFinite(b1) ? ans : null;
+  }
+
+  /** SQL <code>SAFE_DIVIDE</code> function applied to double values. */
+  public static @Nullable Double safeDivide(double b0, double b1) {
+    double ans = b0 / b1;
+    boolean isFinite = Double.isFinite(b0) && Double.isFinite(b1);
+    return safeDouble(ans) || !isFinite ? ans : null;
+  }
+
+  /** SQL <code>SAFE_MULTIPLY</code> function applied to long values. */
+  public static @Nullable Long safeMultiply(long b0, long b1) {
+    try {
+      return Math.multiplyExact(b0, b1);
+    } catch (ArithmeticException e) {
+      return null;
+    }
+  }
+
+  /** SQL <code>SAFE_MULTIPLY</code> function applied to long and BigDecimal 
values. */
+  public static @Nullable BigDecimal safeMultiply(long b0, BigDecimal b1) {
+    BigDecimal ans = BigDecimal.valueOf(b0).multiply(b1);
+    return safeDecimal(ans) ? ans : null;
+  }
+
+  /** SQL <code>SAFE_MULTIPLY</code> function applied to BigDecimal and long 
values. */
+  public static @Nullable BigDecimal safeMultiply(BigDecimal b0, long b1) {
+    return safeMultiply(b1, b0);
+  }
+
+  /** SQL <code>SAFE_MULTIPLY</code> function applied to BigDecimal values. */
+  public static @Nullable BigDecimal safeMultiply(BigDecimal b0, BigDecimal 
b1) {
+    BigDecimal ans = b0.multiply(b1);
+    return safeDecimal(ans) ? ans : null;
+  }
+
+  /** SQL <code>SAFE_MULTIPLY</code> function applied to double and long 
values. */
+  public static @Nullable Double safeMultiply(double b0, long b1) {
+    double ans = b0 * b1;
+    return safeDouble(ans) || !Double.isFinite(b0) ? ans : null;
+  }
+
+  /** SQL <code>SAFE_MULTIPLY</code> function applied to long and double 
values. */
+  public static @Nullable Double safeMultiply(long b0, double b1) {
+    return safeMultiply(b1, b0);
+  }
+
+  /** SQL <code>SAFE_MULTIPLY</code> function applied to double and BigDecimal 
values. */
+  public static @Nullable Double safeMultiply(double b0, BigDecimal b1) {
+    double ans = b0 * b1.doubleValue();
+    return safeDouble(ans) || !Double.isFinite(b0) ? ans : null;
+  }
+
+  /** SQL <code>SAFE_MULTIPLY</code> function applied to BigDecimal and double 
values. */
+  public static @Nullable Double safeMultiply(BigDecimal b0, double b1) {
+    return safeMultiply(b1, b0);
+  }
+
+  /** SQL <code>SAFE_MULTIPLY</code> function applied to double values. */
+  public static @Nullable Double safeMultiply(double b0, double b1) {
+    double ans = b0 * b1;
+    boolean isFinite = Double.isFinite(b0) && Double.isFinite(b1);
+    return safeDouble(ans) || !isFinite ? ans : null;
+  }
+
+  /** SQL <code>SAFE_SUBTRACT</code> function applied to long values. */
+  public static @Nullable Long safeSubtract(long b0, long b1) {
+    try {
+      return Math.subtractExact(b0, b1);
+    } catch (ArithmeticException e) {
+      return null;
+    }
+  }
+
+  /** SQL <code>SAFE_SUBTRACT</code> function applied to long and BigDecimal 
values. */
+  public static @Nullable BigDecimal safeSubtract(long b0, BigDecimal b1) {
+    BigDecimal ans = BigDecimal.valueOf(b0).subtract(b1);
+    return safeDecimal(ans) ? ans : null;
+  }
+
+  /** SQL <code>SAFE_SUBTRACT</code> function applied to BigDecimal and long 
values. */
+  public static @Nullable BigDecimal safeSubtract(BigDecimal b0, long b1) {
+    BigDecimal ans = b0.subtract(BigDecimal.valueOf(b1));
+    return safeDecimal(ans) ? ans : null;
+  }
+
+  /** SQL <code>SAFE_SUBTRACT</code> function applied to BigDecimal values. */
+  public static @Nullable BigDecimal safeSubtract(BigDecimal b0, BigDecimal 
b1) {
+    BigDecimal ans = b0.subtract(b1);
+    return safeDecimal(ans) ? ans : null;
+  }
+
+  /** SQL <code>SAFE_SUBTRACT</code> function applied to double and long 
values. */
+  public static @Nullable Double safeSubtract(double b0, long b1) {
+    double ans = b0 - b1;
+    return safeDouble(ans) || !Double.isFinite(b0) ? ans : null;
+  }
+
+  /** SQL <code>SAFE_SUBTRACT</code> function applied to long and double 
values. */
+  public static @Nullable Double safeSubtract(long b0, double b1) {
+    double ans = b0 - b1;
+    return safeDouble(ans) || !Double.isFinite(b1) ? ans : null;
+  }
+
+  /** SQL <code>SAFE_SUBTRACT</code> function applied to double and BigDecimal 
values. */
+  public static @Nullable Double safeSubtract(double b0, BigDecimal b1) {
+    double ans = b0 - b1.doubleValue();
+    return safeDouble(ans) || !Double.isFinite(b0) ? ans : null;
+  }
+
+  /** SQL <code>SAFE_SUBTRACT</code> function applied to BigDecimal and double 
values. */
+  public static @Nullable Double safeSubtract(BigDecimal b0, double b1) {
+    double ans = b0.doubleValue() - b1;
+    return safeDouble(ans) || !Double.isFinite(b1) ? ans : null;
+  }
+
+  /** SQL <code>SAFE_SUBTRACT</code> function applied to double values. */
+  public static @Nullable Double safeSubtract(double b0, double b1) {
+    double ans = b0 - b1;
+    boolean isFinite = Double.isFinite(b0) && Double.isFinite(b1);
+    return safeDouble(ans) || !isFinite ? ans : null;
+  }
+
+  /** Returns whether a BigDecimal value is safe (that is, has not overflowed).
+   * According to BigQuery, BigDecimal overflow occurs if the precision is 
greater
+   * than 76 or the scale is greater than 38. */
+  private static boolean safeDecimal(BigDecimal b) {
+    return b.scale() <= 38 && b.precision() <= 76;
+  }
+
+  /** Returns whether a double value is safe (that is, has not overflowed). */
+  private static boolean safeDouble(double d) {
+    // If the double is positive and falls between the MIN and MAX double 
values,
+    // overflow has not occurred. If the double is negative and falls between 
the
+    // negated MIN and MAX double values, overflow has not occurred. Otherwise,
+    // overflow has occurred. Important to note that 'Double.MIN_VALUE' refers 
to
+    // minimum positive value.
+    return Math.abs(d) > Double.MIN_VALUE && Math.abs(d) < Double.MAX_VALUE || 
d == 0;
+  }
+
   private static RuntimeException notArithmetic(String op, Object b0,
       Object b1) {
     return RESOURCE.invalidTypesForArithmetic(b0.getClass().toString(),
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 18abd19975..489ba81a3f 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
@@ -1626,6 +1626,51 @@ public abstract class SqlLibraryOperators {
           OperandTypes.family(SqlTypeFamily.TIMESTAMP, SqlTypeFamily.TIMESTAMP,
               SqlTypeFamily.ANY));
 
+  /** The "SAFE_ADD(numeric1, numeric2)" function; equivalent to the {@code +} 
operator but
+   * returns null if overflow occurs. */
+  @LibraryOperator(libraries = {BIG_QUERY})
+  public static final SqlFunction SAFE_ADD =
+      SqlBasicFunction.create("SAFE_ADD",
+          ReturnTypes.SUM_FORCE_NULLABLE,
+          OperandTypes.NUMERIC_NUMERIC,
+          SqlFunctionCategory.NUMERIC);
+
+  /** The "SAFE_DIVIDE(numeric1, numeric2)" function; equivalent to the {@code 
/} operator but
+   * returns null if an error occurs, such as overflow or division by zero. */
+  @LibraryOperator(libraries = {BIG_QUERY})
+  public static final SqlFunction SAFE_DIVIDE =
+      SqlBasicFunction.create("SAFE_DIVIDE",
+          
ReturnTypes.DOUBLE_IF_INTEGERS.orElse(ReturnTypes.QUOTIENT_FORCE_NULLABLE),
+          OperandTypes.NUMERIC_NUMERIC,
+          SqlFunctionCategory.NUMERIC);
+
+  /** The "SAFE_MULTIPLY(numeric1, numeric2)" function; equivalent to the 
{@code *} operator but
+   * returns null if overflow occurs. */
+  @LibraryOperator(libraries = {BIG_QUERY})
+  public static final SqlFunction SAFE_MULTIPLY =
+      SqlBasicFunction.create("SAFE_MULTIPLY",
+          ReturnTypes.PRODUCT_FORCE_NULLABLE,
+          OperandTypes.NUMERIC_NUMERIC,
+          SqlFunctionCategory.NUMERIC);
+
+  /** The "SAFE_NEGATE(numeric)" function; negates {@code numeric} and returns 
null if overflow
+   * occurs. */
+  @LibraryOperator(libraries = {BIG_QUERY})
+  public static final SqlFunction SAFE_NEGATE =
+      SqlBasicFunction.create("SAFE_NEGATE",
+          ReturnTypes.ARG0_FORCE_NULLABLE,
+          OperandTypes.NUMERIC,
+          SqlFunctionCategory.NUMERIC);
+
+  /** The "SAFE_SUBTRACT(numeric1, numeric2)" function; equivalent to the 
{@code -} operator but
+   * returns null if overflow occurs. */
+  @LibraryOperator(libraries = {BIG_QUERY})
+  public static final SqlFunction SAFE_SUBTRACT =
+      SqlBasicFunction.create("SAFE_SUBTRACT",
+          ReturnTypes.SUM_FORCE_NULLABLE,
+          OperandTypes.NUMERIC_NUMERIC,
+          SqlFunctionCategory.NUMERIC);
+
   /** The "CHAR(n)" function; returns the character whose ASCII code is
    * {@code n} % 256, or null if {@code n} &lt; 0. */
   @LibraryOperator(libraries = {MYSQL, SPARK})
diff --git a/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java 
b/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java
index e10107f28b..cba66b366b 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java
@@ -804,6 +804,28 @@ public abstract class ReturnTypes {
   public static final SqlReturnTypeInference DECIMAL_QUOTIENT_NULLABLE =
       DECIMAL_QUOTIENT.andThen(SqlTypeTransforms.TO_NULLABLE);
 
+  /**
+  * Type-inference strategy whereby the result type of a call is
+  * {@link #DOUBLE} if both operands are integer types.
+  */
+  public static final SqlReturnTypeInference DOUBLE_IF_INTEGERS = opBinding -> 
{
+    RelDataTypeFactory typeFactory = opBinding.getTypeFactory();
+    SqlTypeName type1 = opBinding.getOperandType(0).getSqlTypeName();
+    SqlTypeName type2 = opBinding.getOperandType(1).getSqlTypeName();
+    boolean isInts = SqlTypeName.INT_TYPES.contains(type1) && 
SqlTypeName.INT_TYPES.contains(type2);
+    return isInts ? typeFactory.createTypeWithNullability(
+        typeFactory.createSqlType(SqlTypeName.DOUBLE), true) : null;
+  };
+
+  /**
+   * Same as {@link #DECIMAL_QUOTIENT_NULLABLE} but returns with nullability 
if any of
+   * the operands is nullable or the operation results in overflow by using
+   * {@link org.apache.calcite.sql.type.SqlTypeTransforms#FORCE_NULLABLE}. 
Also handles
+   * multiplication for integers, not just decimals.
+   */
+  public static final SqlReturnTypeInference QUOTIENT_FORCE_NULLABLE =
+      
DECIMAL_QUOTIENT_NULLABLE.orElse(LEAST_RESTRICTIVE).andThen(SqlTypeTransforms.FORCE_NULLABLE);
+
   /**
    * Type-inference strategy whereby the result type of a call is
    * {@link #DECIMAL_QUOTIENT_NULLABLE} with a fallback to
diff --git a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java 
b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
index b02870f351..69f0566fe0 100644
--- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
+++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
@@ -452,6 +452,10 @@ public enum BuiltInMethod {
   RAND_INTEGER(RandomFunction.class, "randInteger", int.class),
   RAND_INTEGER_SEED(RandomFunction.class, "randIntegerSeed", int.class,
       int.class),
+  SAFE_ADD(SqlFunctions.class, "safeAdd", double.class, double.class),
+  SAFE_DIVIDE(SqlFunctions.class, "safeDivide", double.class, double.class),
+  SAFE_MULTIPLY(SqlFunctions.class, "safeMultiply", double.class, 
double.class),
+  SAFE_SUBTRACT(SqlFunctions.class, "safeSubtract", double.class, 
double.class),
   LOG(SqlFunctions.class, "log", long.class, long.class),
   TANH(SqlFunctions.class, "tanh", long.class),
   SINH(SqlFunctions.class, "sinh", long.class),
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index 12f6467fc1..d5f605fbe5 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -2783,6 +2783,9 @@ BigQuery's type system uses confusingly different names 
for types and functions:
 | b o | RPAD(string, length[, pattern ])             | Returns a string or 
bytes value that consists of *string* appended to *length* with *pattern*
 | b o | RTRIM(string)                                | Returns *string* with 
all blanks removed from the end
 | b | SAFE_CAST(value AS type)                       | Converts *value* to 
*type*, returning NULL if conversion fails
+| b | SAFE_DIVIDE(numeric1, numeric2)                | Returns *numeric1* / 
*numeric2*, or NULL on overflow or if *numeric2* is zero
+| b | SAFE_MULTIPLY(numeric1, numeric2)              | Returns *numeric1* * 
*numeric2*, or NULL on overflow
+| b | SAFE_NEGATE(numeric)                           | Returns *numeric* * -1, 
or NULL on overflow
 | b | SAFE_OFFSET(index)                             | Similar to `OFFSET` 
except null is returned if *index* is out of bounds
 | b | SAFE_ORDINAL(index)                            | Similar to `OFFSET` 
except *index* begins at 1 and null is returned if *index* is out of bounds
 | * | SEC(numeric)                                   | Returns the secant of 
*numeric* in radians
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 7771cf1948..880ea466cb 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -6889,6 +6889,347 @@ public class SqlOperatorTest {
     f.checkNull("truncate(cast(null as double))");
   }
 
+  @Test void testSafeAddFunc() {
+    final SqlOperatorFixture f0 = 
fixture().setFor(SqlLibraryOperators.SAFE_ADD);
+    f0.checkFails("^safe_add(2, 3)^",
+        "No match found for function signature "
+        + "SAFE_ADD\\(<NUMERIC>, <NUMERIC>\\)", false);
+    final SqlOperatorFixture f = f0.withLibrary(SqlLibrary.BIG_QUERY);
+    // Basic test for each of the 9 2-permutations of BIGINT, DECIMAL, and 
FLOAT
+    f.checkScalar("safe_add(cast(20 as bigint), cast(20 as bigint))",
+        "40", "BIGINT");
+    f.checkScalar("safe_add(cast(20 as bigint), cast(1.2345 as decimal(5,4)))",
+        "21.2345", "DECIMAL(19, 4)");
+    f.checkScalar("safe_add(cast(1.2345 as decimal(5,4)), cast(20 as bigint))",
+        "21.2345", "DECIMAL(19, 4)");
+    f.checkScalar("safe_add(cast(1.2345 as decimal(5,4)), cast(2.0 as 
decimal(2, 1)))",
+        "3.2345", "DECIMAL(6, 4)");
+    f.checkScalar("safe_add(cast(3 as double), cast(3 as bigint))",
+        "6.0", "DOUBLE");
+    f.checkScalar("safe_add(cast(3 as bigint), cast(3 as double))",
+        "6.0", "DOUBLE");
+    f.checkScalar("safe_add(cast(3 as double), cast(1.2345 as decimal(5, 4)))",
+        "4.2345", "DOUBLE");
+    f.checkScalar("safe_add(cast(1.2345 as decimal(5, 4)), cast(3 as double))",
+        "4.2345", "DOUBLE");
+    f.checkScalar("safe_add(cast(3 as double), cast(3 as double))",
+        "6.0", "DOUBLE");
+    // Tests for + and - Infinity
+    f.checkScalar("safe_add(cast('Infinity' as double), cast(3 as double))",
+        "Infinity", "DOUBLE");
+    f.checkScalar("safe_add(cast('-Infinity' as double), cast(3 as double))",
+        "-Infinity", "DOUBLE");
+    f.checkScalar("safe_add(cast('-Infinity' as double), "
+        + "cast('Infinity' as double))", "NaN", "DOUBLE");
+    // Tests for NaN
+    f.checkScalar("safe_add(cast('NaN' as double), cast(3 as bigint))",
+        "NaN", "DOUBLE");
+    f.checkScalar("safe_add(cast('NaN' as double), cast(1.23 as decimal(3, 
2)))",
+        "NaN", "DOUBLE");
+    f.checkScalar("safe_add(cast('NaN' as double), cast('Infinity' as 
double))",
+        "NaN", "DOUBLE");
+    f.checkScalar("safe_add(cast(3 as bigint), cast('NaN' as double))",
+        "NaN", "DOUBLE");
+    f.checkScalar("safe_add(cast(1.23 as decimal(3, 2)), cast('NaN' as 
double))",
+        "NaN", "DOUBLE");
+    // Overflow test for each pairing
+    f.checkNull("safe_add(cast(20 as bigint), "
+        + "cast(9223372036854775807 as bigint))");
+    f.checkNull("safe_add(cast(-20 as bigint), "
+        + "cast(-9223372036854775807 as bigint))");
+    f.checkNull("safe_add(9, cast(9.999999999999999999e75 as DECIMAL(38, 
19)))");
+    f.checkNull("safe_add(-9, cast(-9.999999999999999999e75 as DECIMAL(38, 
19)))");
+    f.checkNull("safe_add(cast(9.999999999999999999e75 as DECIMAL(38, 19)), 
9)");
+    f.checkNull("safe_add(cast(-9.999999999999999999e75 as DECIMAL(38, 19)), 
-9)");
+    f.checkNull("safe_add(cast(9.9e75 as DECIMAL(76, 0)), "
+        + "cast(9.9e75 as DECIMAL(76, 0)))");
+    f.checkNull("safe_add(cast(-9.9e75 as DECIMAL(76, 0)), "
+        + "cast(-9.9e75 as DECIMAL(76, 0)))");
+    f.checkNull("safe_add(cast(1.7976931348623157e308 as double), "
+        + "cast(9.9e7 as decimal(76, 0)))");
+    f.checkNull("safe_add(cast(-1.7976931348623157e308 as double), "
+        + "cast(-9.9e7 as decimal(76, 0)))");
+    f.checkNull("safe_add(cast(9.9e7 as decimal(76, 0)), "
+        + "cast(1.7976931348623157e308 as double))");
+    f.checkNull("safe_add(cast(-9.9e7 as decimal(76, 0)), "
+        + "cast(-1.7976931348623157e308 as double))");
+    f.checkNull("safe_add(cast(1.7976931348623157e308 as double), cast(3 as 
bigint))");
+    f.checkNull("safe_add(cast(-1.7976931348623157e308 as double), "
+        + "cast(-3 as bigint))");
+    f.checkNull("safe_add(cast(3 as bigint), cast(1.7976931348623157e308 as 
double))");
+    f.checkNull("safe_add(cast(-3 as bigint), "
+        + "cast(-1.7976931348623157e308 as double))");
+    f.checkNull("safe_add(cast(3 as double), cast(1.7976931348623157e308 as 
double))");
+    f.checkNull("safe_add(cast(-3 as double), "
+        + "cast(-1.7976931348623157e308 as double))");
+    // Check that null argument retuns null
+    f.checkNull("safe_add(cast(null as double), cast(3 as bigint))");
+    f.checkNull("safe_add(cast(3 as double), cast(null as bigint))");
+  }
+
+  @Test void testSafeDivideFunc() {
+    final SqlOperatorFixture f0 = 
fixture().setFor(SqlLibraryOperators.SAFE_DIVIDE);
+    f0.checkFails("^safe_divide(2, 3)^",
+        "No match found for function signature "
+        + "SAFE_DIVIDE\\(<NUMERIC>, <NUMERIC>\\)", false);
+    final SqlOperatorFixture f = f0.withLibrary(SqlLibrary.BIG_QUERY);
+    // Basic test for each of the 9 2-permutations of BIGINT, DECIMAL, and 
FLOAT
+    f.checkScalar("safe_divide(cast(2 as bigint), cast(4 as bigint))",
+        "0.5", "DOUBLE");
+    f.checkScalar("safe_divide(cast(15 as bigint), cast(1.2 as decimal(2,1)))",
+        "12.5", "DECIMAL(19, 0)");
+    f.checkScalar("safe_divide(cast(4.5 as decimal(2,1)), cast(3 as bigint))",
+        "1.5", "DECIMAL(19, 18)");
+    f.checkScalar("safe_divide(cast(4.5 as decimal(2,1)), "
+        + "cast(1.5 as decimal(2, 1)))", "3", "DECIMAL(8, 6)");
+    f.checkScalar("safe_divide(cast(3 as double), cast(3 as bigint))",
+        "1.0", "DOUBLE");
+    f.checkScalar("safe_divide(cast(3 as bigint), cast(3 as double))",
+        "1.0", "DOUBLE");
+    f.checkScalar("safe_divide(cast(3 as double), cast(1.5 as decimal(5, 4)))",
+        "2.0", "DOUBLE");
+    f.checkScalar("safe_divide(cast(1.5 as decimal(5, 4)), cast(3 as double))",
+        "0.5", "DOUBLE");
+    f.checkScalar("safe_divide(cast(3 as double), cast(3 as double))",
+        "1.0", "DOUBLE");
+    // Tests for + and - Infinity
+    f.checkScalar("safe_divide(cast('Infinity' as double), cast(3 as double))",
+        "Infinity", "DOUBLE");
+    f.checkScalar("safe_divide(cast('-Infinity' as double), cast(3 as 
double))",
+        "-Infinity", "DOUBLE");
+    f.checkScalar("safe_divide(cast('-Infinity' as double), "
+        + "cast('Infinity' as double))", "NaN", "DOUBLE");
+    // Tests for NaN
+    f.checkScalar("safe_divide(cast('NaN' as double), cast(3 as bigint))",
+        "NaN", "DOUBLE");
+    f.checkScalar("safe_divide(cast('NaN' as double), cast(1.23 as decimal(3, 
2)))",
+        "NaN", "DOUBLE");
+    f.checkScalar("safe_divide(cast('NaN' as double), cast('Infinity' as 
double))",
+        "NaN", "DOUBLE");
+    f.checkScalar("safe_divide(cast(3 as bigint), cast('NaN' as double))",
+        "NaN", "DOUBLE");
+    f.checkScalar("safe_divide(cast(1.23 as decimal(3, 2)), cast('NaN' as 
double))",
+        "NaN", "DOUBLE");
+    f.checkNull("safe_divide(cast(0 as bigint), cast(0 as bigint))");
+    f.checkNull("safe_divide(cast(0 as bigint), cast(0 as double))");
+    f.checkNull("safe_divide(cast(0 as bigint), cast(0 as decimal(1, 0)))");
+    f.checkNull("safe_divide(cast(0 as double), cast(0 as bigint))");
+    f.checkNull("safe_divide(cast(0 as double), cast(0 as double))");
+    f.checkNull("safe_divide(cast(0 as double), cast(0 as decimal(1, 0)))");
+    f.checkNull("safe_divide(cast(1.5 as decimal(2, 1)), cast(0 as bigint))");
+    f.checkNull("safe_divide(cast(1.5 as decimal(2, 1)), cast(0 as double))");
+    f.checkNull("safe_divide(cast(1.5 as decimal(2, 1)), cast(0 as decimal(1, 
0)))");
+    // Overflow test for each pairing
+    f.checkNull("safe_divide(cast(10 as bigint), cast(3.5e-75 as DECIMAL(76, 
0)))");
+    f.checkNull("safe_divide(cast(10 as bigint), cast(-3.5e75 as DECIMAL(76, 
0)))");
+    f.checkNull("safe_divide(cast(3.5e75 as DECIMAL(76, 0)), "
+        + "cast(1.5 as DECIMAL(2, 1)))");
+    f.checkNull("safe_divide(cast(-3.5e75 as DECIMAL(76, 0)), "
+        + "cast(1.5 as DECIMAL(2, 1)))");
+    f.checkNull("safe_divide(cast(1.7e308 as double), cast(0.5 as decimal(3, 
2)))");
+    f.checkNull("safe_divide(cast(-1.7e308 as double), cast(0.5 as decimal(2, 
1)))");
+    f.checkNull("safe_divide(cast(5e20 as decimal(1, 0)), cast(1.7e-309 as 
double))");
+    f.checkNull("safe_divide(cast(5e20 as decimal(1, 0)), cast(-1.7e-309 as 
double))");
+    f.checkNull("safe_divide(cast(3 as bigint), cast(1.7e-309 as double))");
+    f.checkNull("safe_divide(cast(3 as bigint), cast(-1.7e-309 as double))");
+    f.checkNull("safe_divide(cast(3 as double), cast(1.7e-309 as double))");
+    f.checkNull("safe_divide(cast(3 as double), cast(-1.7e-309 as double))");
+    // Check that null argument retuns null
+    f.checkNull("safe_divide(cast(null as double), cast(3 as bigint))");
+    f.checkNull("safe_divide(cast(3 as double), cast(null as bigint))");
+  }
+
+  @Test void testSafeMultiplyFunc() {
+    final SqlOperatorFixture f0 = 
fixture().setFor(SqlLibraryOperators.SAFE_MULTIPLY);
+    f0.checkFails("^safe_multiply(2, 3)^",
+        "No match found for function signature "
+        + "SAFE_MULTIPLY\\(<NUMERIC>, <NUMERIC>\\)", false);
+    final SqlOperatorFixture f = f0.withLibrary(SqlLibrary.BIG_QUERY);
+    // Basic test for each of the 9 2-permutations of BIGINT, DECIMAL, and 
FLOAT
+    f.checkScalar("safe_multiply(cast(20 as bigint), cast(20 as bigint))",
+        "400", "BIGINT");
+    f.checkScalar("safe_multiply(cast(20 as bigint), cast(1.2345 as 
decimal(5,4)))",
+        "24.6900", "DECIMAL(19, 4)");
+    f.checkScalar("safe_multiply(cast(1.2345 as decimal(5,4)), cast(20 as 
bigint))",
+        "24.6900", "DECIMAL(19, 4)");
+    f.checkScalar("safe_multiply(cast(1.2345 as decimal(5,4)), "
+        + "cast(2.0 as decimal(2, 1)))", "2.46900", "DECIMAL(7, 5)");
+    f.checkScalar("safe_multiply(cast(3 as double), cast(3 as bigint))",
+        "9.0", "DOUBLE");
+    f.checkScalar("safe_multiply(cast(3 as bigint), cast(3 as double))",
+        "9.0", "DOUBLE");
+    f.checkScalar("safe_multiply(cast(3 as double), cast(1.2345 as decimal(5, 
4)))",
+        "3.7035", "DOUBLE");
+    f.checkScalar("safe_multiply(cast(1.2345 as decimal(5, 4)), cast(3 as 
double))",
+        "3.7035", "DOUBLE");
+    f.checkScalar("safe_multiply(cast(3 as double), cast(3 as double))",
+        "9.0", "DOUBLE");
+    // Tests for + and - Infinity
+    f.checkScalar("safe_multiply(cast('Infinity' as double), cast(3 as 
double))",
+        "Infinity", "DOUBLE");
+    f.checkScalar("safe_multiply(cast('-Infinity' as double), cast(3 as 
double))",
+        "-Infinity", "DOUBLE");
+    f.checkScalar("safe_multiply(cast('-Infinity' as double), "
+        + "cast('Infinity' as double))", "-Infinity", "DOUBLE");
+    // Tests for NaN
+    f.checkScalar("safe_multiply(cast('NaN' as double), cast(3 as bigint))",
+        "NaN", "DOUBLE");
+    f.checkScalar("safe_multiply(cast('NaN' as double), cast(1.23 as 
decimal(3, 2)))",
+        "NaN", "DOUBLE");
+    f.checkScalar("safe_multiply(cast('NaN' as double), cast('Infinity' as 
double))",
+        "NaN", "DOUBLE");
+    f.checkScalar("safe_multiply(cast(3 as bigint), cast('NaN' as double))",
+        "NaN", "DOUBLE");
+    f.checkScalar("safe_multiply(cast(1.23 as decimal(3, 2)), cast('NaN' as 
double))",
+        "NaN", "DOUBLE");
+    // Overflow test for each pairing
+    f.checkNull("safe_multiply(cast(20 as bigint), "
+        + "cast(9223372036854775807 as bigint))");
+    f.checkNull("safe_multiply(cast(20 as bigint), "
+        + "cast(-9223372036854775807 as bigint))");
+    f.checkNull("safe_multiply(cast(10 as bigint), cast(3.5e75 as DECIMAL(76, 
0)))");
+    f.checkNull("safe_multiply(cast(10 as bigint), cast(-3.5e75 as DECIMAL(76, 
0)))");
+    f.checkNull("safe_multiply(cast(3.5e75 as DECIMAL(76, 0)), cast(10 as 
bigint))");
+    f.checkNull("safe_multiply(cast(-3.5e75 as DECIMAL(76, 0)), cast(10 as 
bigint))");
+    f.checkNull("safe_multiply(cast(3.5e75 as DECIMAL(76, 0)), "
+        + "cast(1.5 as DECIMAL(2, 1)))");
+    f.checkNull("safe_multiply(cast(-3.5e75 as DECIMAL(76, 0)), "
+        + "cast(1.5 as DECIMAL(2, 1)))");
+    f.checkNull("safe_multiply(cast(1.7e308 as double), cast(1.23 as 
decimal(3, 2)))");
+    f.checkNull("safe_multiply(cast(-1.7e308 as double), cast(1.2 as 
decimal(2, 1)))");
+    f.checkNull("safe_multiply(cast(1.2 as decimal(2, 1)), cast(1.7e308 as 
double))");
+    f.checkNull("safe_multiply(cast(1.2 as decimal(2, 1)), cast(-1.7e308 as 
double))");
+    f.checkNull("safe_multiply(cast(1.7e308 as double), cast(3 as bigint))");
+    f.checkNull("safe_multiply(cast(-1.7e308 as double), cast(3 as bigint))");
+    f.checkNull("safe_multiply(cast(3 as bigint), cast(1.7e308 as double))");
+    f.checkNull("safe_multiply(cast(3 as bigint), cast(-1.7e308 as double))");
+    f.checkNull("safe_multiply(cast(3 as double), cast(1.7e308 as double))");
+    f.checkNull("safe_multiply(cast(3 as double), cast(-1.7e308 as double))");
+    // Check that null argument retuns null
+    f.checkNull("safe_multiply(cast(null as double), cast(3 as bigint))");
+    f.checkNull("safe_multiply(cast(3 as double), cast(null as bigint))");
+  }
+
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-5770";>[CALCITE-5770]
+   * Add SAFE_NEGATE function (enabled in BigQuery library)</a>.
+   */
+  @Test void testSafeNegateFunc() {
+    final SqlOperatorFixture f0 = 
fixture().setFor(SqlLibraryOperators.SAFE_NEGATE);
+    f0.checkFails("^safe_negate(2)^",
+        "No match found for function signature "
+        + "SAFE_NEGATE\\(<NUMERIC>\\)", false);
+    final SqlOperatorFixture f = f0.withLibrary(SqlLibrary.BIG_QUERY);
+    f.checkScalar("safe_negate(cast(20 as bigint))", "-20",
+        "BIGINT");
+    f.checkScalar("safe_negate(cast(-20 as bigint))", "20",
+        "BIGINT");
+    f.checkScalar("safe_negate(cast(1.5 as decimal(2, 1)))", "-1.5",
+        "DECIMAL(2, 1)");
+    f.checkScalar("safe_negate(cast(-1.5 as decimal(2, 1)))", "1.5",
+        "DECIMAL(2, 1)");
+    f.checkScalar("safe_negate(cast(12.3456 as double))", "-12.3456",
+        "DOUBLE");
+    f.checkScalar("safe_negate(cast(-12.3456 as double))", "12.3456",
+        "DOUBLE");
+    // Infinity and NaN tests
+    f.checkScalar("safe_negate(cast('Infinity' as double))",
+        "-Infinity", "DOUBLE");
+    f.checkScalar("safe_negate(cast('-Infinity' as double))",
+        "Infinity", "DOUBLE");
+    f.checkScalar("safe_negate(cast('NaN' as double))",
+        "NaN", "DOUBLE");
+    // Null cases are rarer for SAFE_NEGATE
+    f.checkNull("safe_negate(-9223372036854775808)");
+    f.checkNull("safe_negate(-1 + -9223372036854775807)");
+    f.checkNull("safe_negate(cast(null as bigint))");
+  }
+
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-5770";>[CALCITE-5770]
+   * Add SAFE_SUBTRACT function (enabled in BigQuery library)</a>.
+   */
+  @Test void testSafeSubtractFunc() {
+    final SqlOperatorFixture f0 = 
fixture().setFor(SqlLibraryOperators.SAFE_SUBTRACT);
+    f0.checkFails("^safe_subtract(2, 3)^",
+        "No match found for function signature "
+        + "SAFE_SUBTRACT\\(<NUMERIC>, <NUMERIC>\\)", false);
+    final SqlOperatorFixture f = f0.withLibrary(SqlLibrary.BIG_QUERY);
+    // Basic test for each of the 9 2-permutations of BIGINT, DECIMAL, and 
FLOAT
+    f.checkScalar("safe_subtract(cast(20 as bigint), cast(20 as bigint))",
+        "0", "BIGINT");
+    f.checkScalar("safe_subtract(cast(20 as bigint), cast(-1.2345 as 
decimal(5,4)))",
+        "21.2345", "DECIMAL(19, 4)");
+    f.checkScalar("safe_subtract(cast(1.2345 as decimal(5,4)), cast(-20 as 
bigint))",
+        "21.2345", "DECIMAL(19, 4)");
+    f.checkScalar("safe_subtract(cast(1.23 as decimal(3,2)), "
+        + "cast(-2.0 as decimal(2, 1)))", "3.23", "DECIMAL(4, 2)");
+    f.checkScalar("safe_subtract(cast(3 as double), cast(-3 as bigint))",
+        "6.0", "DOUBLE");
+    f.checkScalar("safe_subtract(cast(3 as bigint), cast(-3 as double))",
+        "6.0", "DOUBLE");
+    f.checkScalar("safe_subtract(cast(3 as double), cast(-1.2345 as decimal(5, 
4)))",
+        "4.2345", "DOUBLE");
+    f.checkScalar("safe_subtract(cast(1.2345 as decimal(5, 4)), cast(-3 as 
double))",
+        "4.2345", "DOUBLE");
+    f.checkScalar("safe_subtract(cast(3 as double), cast(3 as double))",
+        "0.0", "DOUBLE");
+    // Tests for + and - Infinity
+    f.checkScalar("safe_subtract(cast('Infinity' as double), cast(3 as 
double))",
+        "Infinity", "DOUBLE");
+    f.checkScalar("safe_subtract(cast('-Infinity' as double), cast(3 as 
double))",
+        "-Infinity", "DOUBLE");
+    f.checkScalar("safe_subtract(cast('Infinity' as double), "
+        + "cast('Infinity' as double))", "NaN", "DOUBLE");
+    // Tests for NaN
+    f.checkScalar("safe_subtract(cast('NaN' as double), cast(3 as bigint))",
+        "NaN", "DOUBLE");
+    f.checkScalar("safe_subtract(cast('NaN' as double), cast(1.23 as 
decimal(3, 2)))",
+        "NaN", "DOUBLE");
+    f.checkScalar("safe_subtract(cast('NaN' as double), cast('Infinity' as 
double))",
+        "NaN", "DOUBLE");
+    f.checkScalar("safe_subtract(cast(3 as bigint), cast('NaN' as double))",
+        "NaN", "DOUBLE");
+    f.checkScalar("safe_subtract(cast(1.23 as decimal(3, 2)), cast('NaN' as 
double))",
+        "NaN", "DOUBLE");
+    // Overflow test for each pairing
+    f.checkNull("safe_subtract(cast(20 as bigint), "
+        + "cast(-9223372036854775807 as bigint))");
+    f.checkNull("safe_subtract(cast(-20 as bigint), "
+        + "cast(9223372036854775807 as bigint))");
+    f.checkNull("safe_subtract(9, cast(-9.999999999999999999e75 as DECIMAL(38, 
19)))");
+    f.checkNull("safe_subtract(-9, cast(9.999999999999999999e75 as DECIMAL(38, 
19)))");
+    f.checkNull("safe_subtract(cast(-9.999999999999999999e75 as DECIMAL(38, 
19)), 9)");
+    f.checkNull("safe_subtract(cast(9.999999999999999999e75 as DECIMAL(38, 
19)), -9)");
+    f.checkNull("safe_subtract(cast(-9.9e75 as DECIMAL(76, 0)), "
+        + "cast(9.9e75 as DECIMAL(76, 0)))");
+    f.checkNull("safe_subtract(cast(9.9e75 as DECIMAL(76, 0)), "
+        + "cast(-9.9e75 as DECIMAL(76, 0)))");
+    f.checkNull("safe_subtract(cast(1.7976931348623157e308 as double), "
+        + "cast(-9.9e7 as decimal(76, 0)))");
+    f.checkNull("safe_subtract(cast(-1.7976931348623157e308 as double), "
+        + "cast(9.9e7 as decimal(76, 0)))");
+    f.checkNull("safe_subtract(cast(9.9e7 as decimal(76, 0)), "
+        + "cast(-1.7976931348623157e308 as double))");
+    f.checkNull("safe_subtract(cast(-9.9e7 as decimal(76, 0)), "
+        + "cast(1.7976931348623157e308 as double))");
+    f.checkNull("safe_subtract(cast(1.7976931348623157e308 as double), "
+        + "cast(-3 as bigint))");
+    f.checkNull("safe_subtract(cast(-1.7976931348623157e308 as double), "
+        + "cast(3 as bigint))");
+    f.checkNull("safe_subtract(cast(3 as bigint), "
+        + "cast(-1.7976931348623157e308 as double))");
+    f.checkNull("safe_subtract(cast(-3 as bigint), "
+        + "cast(1.7976931348623157e308 as double))");
+    f.checkNull("safe_subtract(cast(3 as double), "
+        + "cast(-1.7976931348623157e308 as double))");
+    f.checkNull("safe_subtract(cast(-3 as double), "
+        + "cast(1.7976931348623157e308 as double))");
+    // Check that null argument retuns null
+    f.checkNull("safe_subtract(cast(null as double), cast(3 as bigint))");
+    f.checkNull("safe_subtract(cast(3 as double), cast(null as bigint))");
+  }
+
   @Test void testNullifFunc() {
     final SqlOperatorFixture f = fixture();
     f.setFor(SqlStdOperatorTable.NULLIF, VM_EXPAND);

Reply via email to