-
Notifications
You must be signed in to change notification settings - Fork 3.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding safe divide function #11904
Adding safe divide function #11904
Conversation
…mentation updates
Added label |
docs/misc/math-expr.md
Outdated
@@ -154,6 +154,7 @@ See javadoc of java.lang.Math for detailed explanation for each function. | |||
|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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's better to put this new function along with the 'div' function above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if either x or y is NULL?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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)
docs/querying/sql.md
Outdated
|`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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's better to put it along with the 'DIV' function above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|`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` | |
docs/misc/math-expr.md
Outdated
@@ -154,6 +154,7 @@ See javadoc of java.lang.Math for detailed explanation for each function. | |||
|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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if either x or y is NULL?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
docs/misc/math-expr.md
Outdated
@@ -154,6 +154,7 @@ See javadoc of java.lang.Math for detailed explanation for each function. | |||
|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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing apache license header, can copy from any other file
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
License headed added, alphabetical order maintained, null handling info added
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks like a couple of legit ci failures still
docs/misc/math-expr.md
Outdated
@@ -154,6 +154,7 @@ See javadoc of java.lang.Math for detailed explanation for each function. | |||
|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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
docs/querying/sql.md
Outdated
|`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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|`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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
.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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: this could also probably be SqlFunctionCategory.NUMERIC
, though tbh i'm not sure if it matters that much?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left as is
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 (though try not to auto format entire files when making changes if it makes a lot of noise as it is much harder to review)
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: