Adding safe divide function#11904
Conversation
…mentation updates
|
Added label |
| |remainder|remainder(x, y) returns the remainder operation on two arguments as prescribed by the IEEE 754 standard| | ||
| |rint|rint(x) returns value that is closest in value to x and is equal to a mathematical integer| | ||
| |round|round(x, y) returns the value of the x rounded to the y decimal places. While x can be an integer or floating-point number, y must be an integer. The type of the return value is specified by that of x. y defaults to 0 if omitted. When y is negative, x is rounded on the left side of the y decimal points. If x is `NaN`, x returns 0. If x is infinity, x will be converted to the nearest finite double. | | ||
| |safe_divide|safe_divide(x,y) returns the division of x by y if y is not equal to 0, returns 0 otherwise| |
There was a problem hiding this comment.
it's better to put this new function along with the 'div' function above.
There was a problem hiding this comment.
What if either x or y is NULL?
There was a problem hiding this comment.
they appear to be alphabetically sorted right now, maybe we should consider leaving it where it is (though I don't have a strong opinion)
It is worth documenting the null handling behavior (especially if we change it to return null in SQL compatible null handling mode)
| |`HUMAN_READABLE_BINARY_BYTE_FORMAT(value[, precision])`| Format a number in human-readable [IEC](https://en.wikipedia.org/wiki/Binary_prefix) format. For example, HUMAN_READABLE_BINARY_BYTE_FORMAT(1048576) returns `1.00 MiB`. `precision` must be in the range of [0,3] (default: 2). | | ||
| |`HUMAN_READABLE_DECIMAL_BYTE_FORMAT(value[, precision])`| Format a number in human-readable [SI](https://en.wikipedia.org/wiki/Binary_prefix) format. HUMAN_READABLE_DECIMAL_BYTE_FORMAT(1048576) returns `1.04 MB`. `precision` must be in the range of [0,3] (default: 2). `precision` must be in the range of [0,3] (default: 2). | | ||
| |`HUMAN_READABLE_DECIMAL_FORMAT(value[, precision])`| Format a number in human-readable SI format. For example, HUMAN_READABLE_DECIMAL_FORMAT(1048576) returns `1.04 M`. `precision` must be in the range of [0,3] (default: 2). | | ||
| |`SAFE_DIVIDE(x,y)`|Returns the division of x by y guarded on division by 0 | |
There was a problem hiding this comment.
It's better to put it along with the 'DIV' function above.
There was a problem hiding this comment.
| |`SAFE_DIVIDE(x,y)`|Returns the division of x by y guarded on division by 0 | | |
| |`SAFE_DIVIDE(x,y)`|Returns the division of x by y guarded on division by 0. In case y is 0 it returns 0, or `null` if `druid.generic.useDefaultValueForNull=false` | |
| |remainder|remainder(x, y) returns the remainder operation on two arguments as prescribed by the IEEE 754 standard| | ||
| |rint|rint(x) returns value that is closest in value to x and is equal to a mathematical integer| | ||
| |round|round(x, y) returns the value of the x rounded to the y decimal places. While x can be an integer or floating-point number, y must be an integer. The type of the return value is specified by that of x. y defaults to 0 if omitted. When y is negative, x is rounded on the left side of the y decimal points. If x is `NaN`, x returns 0. If x is infinity, x will be converted to the nearest finite double. | | ||
| |safe_divide|safe_divide(x,y) returns the division of x by y if y is not equal to 0, returns 0 otherwise| |
There was a problem hiding this comment.
What if either x or y is NULL?
clintropolis
left a comment
There was a problem hiding this comment.
nice, overall lgtm, just a few minor comments (and noticed a few minor formatting issues that checkstyle will probably flag) 👍
| if(y==0) { | ||
| return ExprEval.of(0); | ||
| } |
There was a problem hiding this comment.
hmm, looking at the behavior of another SAFE_DIVIDE I could find in other databases (https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-and-operators#safe_divide), I think we should consider returning null here if NullHandling.replaceWithDefault() is true
| if(y==0) { | |
| return ExprEval.of(0); | |
| } | |
| if (y == 0 && x != 0) { | |
| return ExprEval.ofLong(NullHandling.defaultLongValue()); | |
| } |
There was a problem hiding this comment.
Change made. Added a case and testcases where x or y is NaN or +- Infinity
| if(y==0) { | ||
| return ExprEval.of(0); | ||
| } |
There was a problem hiding this comment.
same suggestion (and also should preserve double typing)
| if(y==0) { | |
| return ExprEval.of(0); | |
| } | |
| if (y == 0 && x != 0) { | |
| return ExprEval.ofDouble(NullHandling.defaultDoubleValue()); | |
| } |
There was a problem hiding this comment.
Change made. Added a case and testcases where x or y is NaN or +- Infinity
| assertExpr("safe_divide(3, 0)", 0L); | ||
| assertExpr("safe_divide(3.7, 0.0)", 0L); |
There was a problem hiding this comment.
if other suggestion is applied:
| assertExpr("safe_divide(3, 0)", 0L); | |
| assertExpr("safe_divide(3.7, 0.0)", 0L); | |
| assertExpr("safe_divide(3, 0)", NullHandling.defaultLongValue()); | |
| assertExpr("safe_divide(3.7, 0.0)", NullHandling.defaultDoubleValue()); |
| |remainder|remainder(x, y) returns the remainder operation on two arguments as prescribed by the IEEE 754 standard| | ||
| |rint|rint(x) returns value that is closest in value to x and is equal to a mathematical integer| | ||
| |round|round(x, y) returns the value of the x rounded to the y decimal places. While x can be an integer or floating-point number, y must be an integer. The type of the return value is specified by that of x. y defaults to 0 if omitted. When y is negative, x is rounded on the left side of the y decimal points. If x is `NaN`, x returns 0. If x is infinity, x will be converted to the nearest finite double. | | ||
| |safe_divide|safe_divide(x,y) returns the division of x by y if y is not equal to 0, returns 0 otherwise| |
There was a problem hiding this comment.
they appear to be alphabetically sorted right now, maybe we should consider leaving it where it is (though I don't have a strong opinion)
It is worth documenting the null handling behavior (especially if we change it to return null in SQL compatible null handling mode)
| @@ -0,0 +1,34 @@ | |||
| package org.apache.druid.sql.calcite.expression.builtin; | |||
There was a problem hiding this comment.
missing apache license header, can copy from any other file
There was a problem hiding this comment.
License headed added, alphabetical order maintained, null handling info added
clintropolis
left a comment
There was a problem hiding this comment.
looks like a couple of legit ci failures still
| |remainder|remainder(x, y) returns the remainder operation on two arguments as prescribed by the IEEE 754 standard| | ||
| |rint|rint(x) returns value that is closest in value to x and is equal to a mathematical integer| | ||
| |round|round(x, y) returns the value of the x rounded to the y decimal places. While x can be an integer or floating-point number, y must be an integer. The type of the return value is specified by that of x. y defaults to 0 if omitted. When y is negative, x is rounded on the left side of the y decimal points. If x is `NaN`, x returns 0. If x is infinity, x will be converted to the nearest finite double. | | ||
| |safe_divide|safe_divide(x,y) returns the division of x by y if y is not equal to 0. In case y is 0 it returns null or default values (if returning default values are enabled). | |
There was a problem hiding this comment.
nit: to match other docs that describe difference between default and sql compatible null handling mode
| |safe_divide|safe_divide(x,y) returns the division of x by y if y is not equal to 0. In case y is 0 it returns null or default values (if returning default values are enabled). | | |
| |safe_divide|safe_divide(x,y) returns the division of x by y if y is not equal to 0. In case y is 0 it returns 0, or `null` if `druid.generic.useDefaultValueForNull=false`. | |
Also, since this file doesn't backtick the expression names, safe_divide needs added to the spelling exceptions file https://github.com/apache/druid/blob/master/website/.spelling#L1202
| |`HUMAN_READABLE_BINARY_BYTE_FORMAT(value[, precision])`| Format a number in human-readable [IEC](https://en.wikipedia.org/wiki/Binary_prefix) format. For example, HUMAN_READABLE_BINARY_BYTE_FORMAT(1048576) returns `1.00 MiB`. `precision` must be in the range of [0,3] (default: 2). | | ||
| |`HUMAN_READABLE_DECIMAL_BYTE_FORMAT(value[, precision])`| Format a number in human-readable [SI](https://en.wikipedia.org/wiki/Binary_prefix) format. HUMAN_READABLE_DECIMAL_BYTE_FORMAT(1048576) returns `1.04 MB`. `precision` must be in the range of [0,3] (default: 2). `precision` must be in the range of [0,3] (default: 2). | | ||
| |`HUMAN_READABLE_DECIMAL_FORMAT(value[, precision])`| Format a number in human-readable SI format. For example, HUMAN_READABLE_DECIMAL_FORMAT(1048576) returns `1.04 M`. `precision` must be in the range of [0,3] (default: 2). | | ||
| |`SAFE_DIVIDE(x,y)`|Returns the division of x by y guarded on division by 0 | |
There was a problem hiding this comment.
| |`SAFE_DIVIDE(x,y)`|Returns the division of x by y guarded on division by 0 | | |
| |`SAFE_DIVIDE(x,y)`|Returns the division of x by y guarded on division by 0. In case y is 0 it returns 0, or `null` if `druid.generic.useDefaultValueForNull=false` | |
| ExpressionType type = ExpressionType.DOUBLE; | ||
| for (Expr arg : args) { | ||
| type = ExpressionTypeConversion.function(type, arg.getOutputType(inspector)); | ||
| } | ||
| return ExpressionType.asArrayType(type); |
There was a problem hiding this comment.
this shouldn't be array typed
| ExpressionType type = ExpressionType.DOUBLE; | |
| for (Expr arg : args) { | |
| type = ExpressionTypeConversion.function(type, arg.getOutputType(inspector)); | |
| } | |
| return ExpressionType.asArrayType(type); | |
| return ExpressionTypeConversion.function( | |
| args.get(0).getOutputType(inspector), | |
| args.get(1).getOutputType(inspector) | |
| ); |
Could you also add some tests with a mix of longs and doubles to https://github.com/apache/druid/blob/master/core/src/test/java/org/apache/druid/math/expr/OutputTypeTest.java#L163?
| .operatorBuilder(StringUtils.toUpperCase(Function.SafeDivide.NAME)) | ||
| .operandTypeChecker(OperandTypes.ANY_NUMERIC) | ||
| .returnTypeInference(ReturnTypes.QUOTIENT_NULLABLE) | ||
| .functionCategory(SqlFunctionCategory.USER_DEFINED_FUNCTION) |
There was a problem hiding this comment.
nit: this could also probably be SqlFunctionCategory.NUMERIC, though tbh i'm not sure if it matters that much?
* IMPLY-4344: Adding safe divide function along with testcases and documentation updates * Changing based on review comments * Addressing review comments, fixing coding style, docs and spelling * Checkstyle passes for all code * Fixing expected results for infinity * Revert "Fixing expected results for infinity" This reverts commit 5fd5cd4. * Updating test result and a space in docs
Adds a Safe_Divide function
Description
Added a new safe divide function that is guarded against division by 0. Safe_divide(x,y) returns x/y if y is not equal to 0. It returns 0 otherwise.
Fixed the bug ...
Renamed the class ...
Added a forbidden-apis entry ...
Key changed/added classes in this PR
Safe_Divide function
Calcite layer direct operator to connect with newly created safe_divide native function
This PR has: