Skip to content

Commit 7eb2d4a

Browse files
Address feedback
1 parent dffd748 commit 7eb2d4a

File tree

35 files changed

+846
-224
lines changed

35 files changed

+846
-224
lines changed

docs/reference/mapping/types/numeric.asciidoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ The following parameters are accepted by numeric types:
116116
<<coerce,`coerce`>>::
117117

118118
Try to convert strings to numbers and truncate fractions for integers.
119-
Accepts `true` (default) and `false`. Not applicable for unsigned_long.
119+
Accepts `true` (default) and `false`. Not applicable for `unsigned_long`.
120120

121121
<<doc-values,`doc_values`>>::
122122

docs/reference/mapping/types/unsigned_long.asciidoc

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,7 @@
99

1010
Unsigned long is a numeric field type that represents an unsigned 64-bit
1111
integer with a minimum value of 0 and a maximum value of +2^64^-1+
12-
(from 0 to 18446744073709551615).
13-
14-
At index-time, an indexed value is converted to the singed long range:
15-
[- 9223372036854775808, 9223372036854775807] by subtracting +2^63^+ from it
16-
and stored as a singed long taking 8 bytes.
17-
At query-time, the same conversion is done on query terms.
12+
(from 0 to 18446744073709551615 inclusive).
1813

1914
[source,console]
2015
--------------------------------------------------
@@ -63,7 +58,11 @@ GET /my_index/_search
6358
--------------------------------
6459
//TEST[continued]
6560

66-
Range queries can contain ranges with decimal parts.
61+
Range query terms can contain values with decimal parts.
62+
In this case {es} converts them to integer values:
63+
`gte` and `gt` terms are converted to the nearest integer up inclusive,
64+
and `lt` and `lte` ranges are converted to the nearest integer down inclusive.
65+
6766
It is recommended to pass ranges as strings to ensure they are parsed
6867
without any loss of precision.
6968

@@ -83,13 +82,13 @@ GET /my_index/_search
8382
--------------------------------
8483
//TEST[continued]
8584

86-
WARNING: Unlike term and range queries, sorting and aggregations on
87-
unsigned_long data may return imprecise results. For sorting and aggregations
88-
double representation of unsigned longs is used, which means that long values
89-
are first converted to double values. During this conversion,
90-
for long values greater than +2^53^+ there could be some loss of
91-
precision for the least significant digits. Long values less than +2^53^+
92-
are converted accurately.
85+
86+
For queries with sort on an `unsigned_long` field,
87+
for a particular document {es} returns a sort value of the type `Long`
88+
if the value of this document is within the range of long values,
89+
or of the type `BigIntger` if the value exceeds this range.
90+
91+
WARNING: Not all {es} clients can properly handle big integer values.
9392

9493
[source,console]
9594
--------------------------------
@@ -98,15 +97,25 @@ GET /my_index/_search
9897
"query": {
9998
"match_all" : {}
10099
},
101-
"sort" : {"my_counter" : "desc"} <1>
100+
"sort" : {"my_counter" : "desc"}
102101
}
103102
--------------------------------
104103
//TEST[continued]
105-
<1> As both document values: "18446744073709551614" and "18446744073709551615"
106-
are converted to the same double value: "1.8446744073709552E19", this
107-
descending sort may return imprecise results, as the document with a lower
108-
value of "18446744073709551614" may come before the document
109-
with a higher value of "18446744073709551615".
104+
105+
Similarly to sort values, script values of an `unsigned_long` field
106+
produce `BigInteger` or `Long` values. The same values: `BigInteger` or
107+
`Long` are returned as keys for `terms` aggregation.
108+
109+
==== Queries with mixed numeric types
110+
111+
Search queries across several numeric types one of which `unsigned_long` are supported,
112+
except queries with sort. Thus, a sort query across two indexes where the same field
113+
is `unsigned_long` in one index, and `long` in another, doesn't produce correct results
114+
and must be avoided. If there is a need for a such kind of sorting, script based
115+
sorting can be used instead.
116+
Aggregations across several numeric types one of which `unsigned_long` are supported,
117+
except a terms aggregation.
118+
110119

111120
[[unsigned-long-params]]
112121
==== Parameters for unsigned long fields

libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentParser.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,8 +206,6 @@ <T> Map<String, T> map(
206206

207207
long longValue() throws IOException;
208208

209-
long unsignedLongValue() throws IOException;
210-
211209
float floatValue() throws IOException;
212210

213211
double doubleValue() throws IOException;

libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentSubParser.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -224,11 +224,6 @@ public long longValue() throws IOException {
224224
return parser.longValue();
225225
}
226226

227-
@Override
228-
public long unsignedLongValue() throws IOException {
229-
return parser.unsignedLongValue();
230-
}
231-
232227
@Override
233228
public float floatValue() throws IOException {
234229
return parser.floatValue();

libs/x-content/src/main/java/org/elasticsearch/common/xcontent/json/JsonXContentParser.java

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
import org.elasticsearch.core.internal.io.IOUtils;
3131

3232
import java.io.IOException;
33-
import java.math.BigInteger;
3433
import java.nio.CharBuffer;
3534

3635
public class JsonXContentParser extends AbstractXContentParser {
@@ -167,26 +166,6 @@ public long doLongValue() throws IOException {
167166
return parser.getLongValue();
168167
}
169168

170-
@Override
171-
protected long doUnsignedLongValue() throws IOException {
172-
JsonParser.NumberType numberType = parser.getNumberType();
173-
if ((numberType == JsonParser.NumberType.INT) || (numberType == JsonParser.NumberType.LONG)) {
174-
long longValue = parser.getLongValue();
175-
if (longValue < 0) {
176-
throw new IllegalArgumentException("Value [" + longValue + "] is out of range for unsigned long.");
177-
}
178-
return longValue;
179-
} else if (numberType == JsonParser.NumberType.BIG_INTEGER) {
180-
BigInteger bigIntegerValue = parser.getBigIntegerValue();
181-
if (bigIntegerValue.compareTo(BIGINTEGER_MAX_UNSIGNED_LONG_VALUE) > 0 || bigIntegerValue.compareTo(BigInteger.ZERO) < 0) {
182-
throw new IllegalArgumentException("Value [" + bigIntegerValue + "] is out of range for unsigned long");
183-
}
184-
return bigIntegerValue.longValue();
185-
} else { // for all other value types including numbers with decimal parts
186-
throw new IllegalArgumentException("For input string: [" + parser.getValueAsString() + "].");
187-
}
188-
}
189-
190169
@Override
191170
public float doFloatValue() throws IOException {
192171
return parser.getFloatValue();

libs/x-content/src/main/java/org/elasticsearch/common/xcontent/support/AbstractXContentParser.java

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,6 @@ public abstract class AbstractXContentParser implements XContentParser {
4646
// references to this policy decision throughout the codebase and find
4747
// and change any code that needs to apply an alternative policy.
4848
public static final boolean DEFAULT_NUMBER_COERCE_POLICY = true;
49-
public static BigInteger BIGINTEGER_MAX_UNSIGNED_LONG_VALUE = BigInteger.ONE.shiftLeft(64).subtract(BigInteger.ONE); // 2^64 -1
50-
5149

5250
private static void checkCoerceString(boolean coerce, Class<? extends Number> clazz) {
5351
if (!coerce) {
@@ -210,26 +208,8 @@ public long longValue(boolean coerce) throws IOException {
210208
return result;
211209
}
212210

213-
@Override
214-
public long unsignedLongValue() throws IOException {
215-
Token token = currentToken();
216-
if (token == Token.VALUE_STRING) {
217-
return Long.parseUnsignedLong(text());
218-
}
219-
long result = doUnsignedLongValue();
220-
return result;
221-
}
222-
223211
protected abstract long doLongValue() throws IOException;
224212

225-
/**
226-
* Returns an unsigned long value of the current numeric token.
227-
* The method must check for proper boundaries: [0; 2^64-1], and also check that it doesn't have a decimal part.
228-
* An exception is raised if any of the conditions is violated.
229-
* Numeric tokens greater than Long.MAX_VALUE must be returned as negative values.
230-
*/
231-
protected abstract long doUnsignedLongValue() throws IOException;
232-
233213
@Override
234214
public float floatValue() throws IOException {
235215
return floatValue(DEFAULT_NUMBER_COERCE_POLICY);

libs/x-content/src/main/java/org/elasticsearch/common/xcontent/support/MapXContentParser.java

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -73,26 +73,6 @@ protected long doLongValue() throws IOException {
7373
return numberValue().longValue();
7474
}
7575

76-
@Override
77-
protected long doUnsignedLongValue() throws IOException {
78-
Number value = numberValue();
79-
if ((value instanceof Integer) || (value instanceof Long) || (value instanceof Short) || (value instanceof Byte)) {
80-
long longValue = value.longValue();
81-
if (longValue < 0) {
82-
throw new IllegalArgumentException("Value [" + longValue + "] is out of range for unsigned long.");
83-
}
84-
return longValue;
85-
} else if (value instanceof BigInteger) {
86-
BigInteger bigIntegerValue = (BigInteger) value;
87-
if (bigIntegerValue.compareTo(BIGINTEGER_MAX_UNSIGNED_LONG_VALUE) > 0 || bigIntegerValue.compareTo(BigInteger.ZERO) < 0) {
88-
throw new IllegalArgumentException("Value [" + bigIntegerValue + "] is out of range for unsigned long.");
89-
}
90-
return bigIntegerValue.longValue();
91-
} else {
92-
throw new IllegalArgumentException("For input string: [" + value.toString() + "].");
93-
}
94-
}
95-
9676
@Override
9777
protected float doFloatValue() throws IOException {
9878
return numberValue().floatValue();

modules/lang-painless/src/main/java/org/elasticsearch/painless/Def.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.lang.invoke.MethodHandle;
3030
import java.lang.invoke.MethodHandles;
3131
import java.lang.invoke.MethodType;
32+
import java.math.BigInteger;
3233
import java.time.ZonedDateTime;
3334
import java.util.BitSet;
3435
import java.util.Collections;
@@ -734,6 +735,8 @@ public static double defTodoubleImplicit(final Object value) {
734735
return (float)value;
735736
} else if (value instanceof Double) {
736737
return (double)value;
738+
} else if (value instanceof BigInteger) {
739+
return ((BigInteger)value).doubleValue();
737740
} else {
738741
throw new ClassCastException("cannot implicitly cast " +
739742
"def [" + PainlessLookupUtility.typeToUnboxedType(value.getClass()).getCanonicalName() + "] to " +
@@ -866,7 +869,8 @@ public static double defTodoubleExplicit(final Object value) {
866869
value instanceof Integer ||
867870
value instanceof Long ||
868871
value instanceof Float ||
869-
value instanceof Double
872+
value instanceof Double ||
873+
value instanceof BigInteger
870874
) {
871875
return ((Number)value).doubleValue();
872876
} else {
@@ -1004,7 +1008,9 @@ public static Double defToDoubleImplicit(final Object value) {
10041008
} else if (value instanceof Float) {
10051009
return (double)(float)value;
10061010
} else if (value instanceof Double) {
1007-
return (Double)value;
1011+
return (Double) value;
1012+
} else if (value instanceof BigInteger) {
1013+
return ((BigInteger)value).doubleValue();
10081014
} else {
10091015
throw new ClassCastException("cannot implicitly cast " +
10101016
"def [" + PainlessLookupUtility.typeToUnboxedType(value.getClass()).getCanonicalName() + "] to " +
@@ -1151,7 +1157,8 @@ public static Double defToDoubleExplicit(final Object value) {
11511157
value instanceof Integer ||
11521158
value instanceof Long ||
11531159
value instanceof Float ||
1154-
value instanceof Double
1160+
value instanceof Double ||
1161+
value instanceof BigInteger
11551162
) {
11561163
return ((Number)value).doubleValue();
11571164
} else {

modules/lang-painless/src/test/java/org/elasticsearch/painless/DefCastTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ public void testdefTodoubleImplicit() {
166166
assertEquals((double)0, exec("def d = Long.valueOf(0); double b = d; b"));
167167
assertEquals((double)0, exec("def d = Float.valueOf(0); double b = d; b"));
168168
assertEquals((double)0, exec("def d = Double.valueOf(0); double b = d; b"));
169+
assertEquals((double)0, exec("def d = BigInteger.valueOf(0); double b = d; b"));
169170
expectScriptThrows(ClassCastException.class, () -> exec("def d = new ArrayList(); double b = d;"));
170171
}
171172

@@ -485,7 +486,7 @@ public void testdefToFloatImplicit() {
485486
expectScriptThrows(ClassCastException.class, () -> exec("def d = Double.valueOf(0); Float b = d;"));
486487
expectScriptThrows(ClassCastException.class, () -> exec("def d = new ArrayList(); Float b = d;"));
487488
}
488-
489+
489490
public void testdefToDoubleImplicit() {
490491
expectScriptThrows(ClassCastException.class, () -> exec("def d = 'string'; Double b = d;"));
491492
expectScriptThrows(ClassCastException.class, () -> exec("def d = true; Double b = d;"));

server/src/main/java/org/elasticsearch/common/io/stream/StreamInput.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import java.io.FilterInputStream;
5252
import java.io.IOException;
5353
import java.io.InputStream;
54+
import java.math.BigInteger;
5455
import java.nio.file.AccessDeniedException;
5556
import java.nio.file.AtomicMoveNotSupportedException;
5657
import java.nio.file.DirectoryNotEmptyException;
@@ -348,6 +349,11 @@ public Long readOptionalLong() throws IOException {
348349
return null;
349350
}
350351

352+
public BigInteger readBigInteger() throws IOException {
353+
return new BigInteger(readString());
354+
}
355+
356+
351357
@Nullable
352358
public Text readOptionalText() throws IOException {
353359
int length = readInt();
@@ -760,6 +766,8 @@ public Object readGenericValue() throws IOException {
760766
return readCollection(StreamInput::readGenericValue, LinkedHashSet::new, Collections.emptySet());
761767
case 25:
762768
return readCollection(StreamInput::readGenericValue, HashSet::new, Collections.emptySet());
769+
case 26:
770+
return readBigInteger();
763771
default:
764772
throw new IOException("Can't read unknown type [" + type + "]");
765773
}

0 commit comments

Comments
 (0)