-
Notifications
You must be signed in to change notification settings - Fork 25.2k
Reject out of range numbers for float, double and half_float #25826
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
Reject out of range numbers for float, double and half_float #25826
Conversation
Since this is a community submitted pull request, a Jenkins build has not been kicked off automatically. Can an Elastic organization member please verify the contents of this patch and then kick off a build manually? |
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.
@fred84 Thanks for the PR, I left a few small comments.
@@ -370,6 +390,13 @@ Query rangeQuery(String field, Object lowerTerm, Object upperTerm, | |||
} | |||
|
|||
@Override | |||
void validateParsed(Number value) { | |||
if (!Double.isFinite(value.doubleValue())) { | |||
throw new IllegalArgumentException("[double] supports only finite values, but got [" + value.toString() + "]"); |
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.
Can we use this message format where we show what we got as an invalid value in the error messages for the other types too?
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.
I updated message format.
* @throws IllegalArgumentException if value is not finite for this type | ||
*/ | ||
void validateParsed(Number value) { | ||
} |
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.
Since every numeric type should implement this method, can we have this throw an UnsupportedOperationException
here so we get an error if we implement a new numeric type and forget to override this method?
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.
Or just make it abstract
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.
I made validation as a part of parse method in the same way as it did in byte/short/int/long. Thanks for suggestion, @rjernst!
final Map<String, String> outOfRangeValues = new HashMap<>(); | ||
outOfRangeValues.put("float", new BigDecimal("3.4028235E39").toString()); | ||
outOfRangeValues.put("double", new BigDecimal("1.7976931348623157E309").toString()); | ||
outOfRangeValues.put("half_float", new BigDecimal("65504.1").toString()); |
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.
As well as these values can we add infinite and Nan values to this test?
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.
Tests are ready. Also added out of range tests for byte/short/int/long
jenkins test this |
@fred84 the build failed but the failure doesn't seem to be related to your change. It also doesn't reproduce for me locally so I'm going to kick off another build and see if we get the error again. |
jenkins retest this |
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.
Why would the validation just be part of the parse method for each type? Then it would not have to be retrieved back out of Number.
* @throws IllegalArgumentException if value is not finite for this type | ||
*/ | ||
void validateParsed(Number value) { | ||
} |
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.
Or just make it abstract
@colings86 I updated PR. There some code duplication left, I'm not sure where to put Triple class used in 2 test cases. |
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.
@fred84 Thanks for updating the PR, I left a comment about the Triple class but I think this is getting close.
Could you also rebase your branch on the latest master and resolve the merge conflicts?
} | ||
} | ||
|
||
private static class Triple<K,V,M> { |
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.
Instead of this being a generic class with a generic name can we call this OutOfRangeSpec
, remove the generic arguments and instead use K -> String
, V -> Number
, M -> String
. Also I think it would be ok for you to declare this class here and then reuse it in the NumberFieldTypeTests
below instead of re-defining it
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.
Agreed. I only keep generic argument for value because I use both strings and numbers (BigInteger and BigDecimal) as input values.
@colings86 PR is updated. I also found that validation for min/max values in short/integer/long works not as intended. I will provide more details a bit later). |
@colings86 I moved proposed changes for byte/short/int/long validation to separate PR: fred84#2 |
@colings86 I think we should solve half_float/float/double validation in this PR and then I will create separate PR for byte/short/int/double. |
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 looks good to me overall but I left some minor comments about the handling of half floats and boxing.
private void validateParsed(Float value) { | ||
if ( | ||
value.isNaN() || value.isInfinite() | ||
|| value > 65504 |
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.
should probably be Math.abs(value) >= 65520
rather than 65504. 65504 is indeed the maximum value but values up to 65520 excluded would be rounded to 65504
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.
Agreed with Math.abs
, but do not understand about 65520. As I understand all finite floats greater than 65504 will be rounded to 65504 inside HalfFloatPoint.halfFloatToShortBits
. Am I right?
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.
No. Floats that are between 65504 and 65520 will be rounded to 65504 however floats that are equal or greater than 65520 will be converted to +Infinity.
if ( | ||
value.isNaN() || value.isInfinite() | ||
|| value > 65504 | ||
|| !Float.isFinite(HalfFloatPoint.sortableShortToHalfFloat(HalfFloatPoint.halfFloatToSortableShort(value))) |
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.
Those last checks are redundant. We should do only one of them.
@@ -231,22 +244,39 @@ Query rangeQuery(String field, Object lowerTerm, Object upperTerm, | |||
} | |||
return fields; | |||
} | |||
|
|||
private void validateParsed(Float value) { |
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.
let's make it take a float
so that we do not have to worry about null values
@@ -162,12 +162,25 @@ public TypeParser(NumberType type) { | |||
HALF_FLOAT("half_float", NumericType.HALF_FLOAT) { | |||
@Override | |||
Float parse(Object value, boolean coerce) { | |||
return (Float) FLOAT.parse(value, false); | |||
final Float result; |
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.
let's store it as a float in order to delay boxing as much as possible?
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.
agreed, I will fix in half_float, float and double.
}, | ||
FLOAT("float", NumericType.FLOAT) { | ||
@Override | ||
Float parse(Object value, boolean coerce) { | ||
final Float result; |
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.
let's store it as a float in order to delay boxing as much as possible?
@@ -308,16 +338,26 @@ Query rangeQuery(String field, Object lowerTerm, Object upperTerm, | |||
} | |||
return fields; | |||
} | |||
|
|||
private void validateParsed(Float value) { |
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.
please make it a float
rather than Float
and use Float.isFinite(value) == false
below
}, | ||
DOUBLE("double", NumericType.DOUBLE) { | ||
@Override | ||
Double parse(Object value, boolean coerce) { | ||
return objectToDouble(value); | ||
Double parsed = objectToDouble(value); |
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.
let's store it as a double in order to delay boxing as much as possible?
@@ -379,6 +419,12 @@ Query rangeQuery(String field, Object lowerTerm, Object upperTerm, | |||
} | |||
return fields; | |||
} | |||
|
|||
private void validateParsed(Double value) { |
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.
please make it a double rather than Double and use Double.isFinite(value) == false below
} catch (IllegalArgumentException e) { | ||
assertThat("Incorrect error message for [" + item.type + "] with value [" + item.value + "]", | ||
e.getMessage(), containsString(item.message)); | ||
} |
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.
could you use expectThrows
instead of try/fail/catch/assert?
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.
Despite expectThrows is more concise and readable, I think we should keep try/fail/catch/assert because
fail("Mapper parsing exception expected for [" + item.type + "] with value [" + item.value + "]");
shows which numbertype with which value failed.
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.
fair enough
|
||
OutOfRangeSpec.of(NumberType.HALF_FLOAT, 65504.1, "[half_float] supports only finite values"), | ||
OutOfRangeSpec.of(NumberType.FLOAT, 3.4028235E39, "[float] supports only finite values"), | ||
OutOfRangeSpec.of(NumberType.DOUBLE, new BigDecimal("1.7976931348623157E309"), "[double] supports only finite values"), |
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.
can you test negative values too?
2) no redudant checks in half_float validation 3) tests with negative values for half_float/float/double
@jpountz I updated PR. Please take a look. |
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.
LGTM. Thanks @fred84 !
|
||
private static Double parse(XContentParser parser, boolean coerce) throws IOException { | ||
return parser.doubleValue(coerce); | ||
} |
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 helper method does not look very useful
jenkins please test this |
* validate half float values * test upper bound for numeric mapper * test for upper bound for float, double and half_float * more tests on NaN and Infinity for NumberFieldMapper * fix checkstyle errors * minor renaming * comments for disabled test * tests for byte/short/integer/long removed and will be added in separate PR * remove unused import * Fix scaledfloat out of range validation message * 1) delayed autoboxing in numbertype.parse(...) 2) no redudant checks in half_float validation 3) tests with negative values for half_float/float/double
* validate half float values * test upper bound for numeric mapper * test for upper bound for float, double and half_float * more tests on NaN and Infinity for NumberFieldMapper * fix checkstyle errors * minor renaming * comments for disabled test * tests for byte/short/integer/long removed and will be added in separate PR * remove unused import * Fix scaledfloat out of range validation message * 1) delayed autoboxing in numbertype.parse(...) 2) no redudant checks in half_float validation 3) tests with negative values for half_float/float/double
@fred84 thanks for the PR, its now merged and backported to 6.x and 6.0. |
@colings86 @jpountz Thanks for review! |
Since elastic#25826 we reject infinite values for float, double and half_float datatypes. This change adds this restriction to the documentation for the supported datatypes. Closes elastic#27653
Fix for #25534
@colings86 Please take a look.