Skip to content

Commit 67c98e9

Browse files
authored
QL: add unsigned_long type support (#65145)
This introduces the UNSIGNED_LONG type to QL following its availability in ES (#60050). The type is mapped to a BigInteger whose value is checked against the UL bounds. The SQL will now support the type as literal and in the arithmetic functions; the non-arithmetic functions however are unchanged (i.e. they still require a long / int parameter where that is the case). The type is version-gated: for the driver SQL clients (only) the server checks their version and in case this is lower than the one introducing the UL support, it fails the request, for queries, or simply hidden in catalog functions (similar to how UNSUPPORTED is currently treated in the similar case) The JDBC tests are adjusted to read the (bwc) version of the driver they are run against and selectively disable part of the tests accordingly. Closes #63312
1 parent fa95bb3 commit 67c98e9

File tree

82 files changed

+3488
-868
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+3488
-868
lines changed

docs/changelog/65145.yaml

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 65145
2+
summary: Add `unsigned_long` type support
3+
area: Query Languages
4+
type: enhancement
5+
issues:
6+
- 63312

x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/parser/ExpressionBuilder.java

+2-14
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@
4949
import org.elasticsearch.xpack.ql.expression.predicate.operator.comparison.LessThanOrEqual;
5050
import org.elasticsearch.xpack.ql.expression.predicate.regex.Like;
5151
import org.elasticsearch.xpack.ql.tree.Source;
52-
import org.elasticsearch.xpack.ql.type.DataType;
5352
import org.elasticsearch.xpack.ql.type.DataTypes;
5453
import org.elasticsearch.xpack.ql.util.StringUtils;
5554

@@ -240,10 +239,9 @@ public Literal visitIntegerLiteral(EqlBaseParser.IntegerLiteralContext ctx) {
240239
Source source = source(ctx);
241240
String text = ctx.getText();
242241

243-
long value;
244-
245242
try {
246-
value = Long.valueOf(StringUtils.parseLong(text));
243+
Number value = StringUtils.parseIntegral(text);
244+
return new Literal(source, value, DataTypes.fromJava(value));
247245
} catch (QlIllegalArgumentException siae) {
248246
// if it's too large, then quietly try to parse as a float instead
249247
try {
@@ -252,16 +250,6 @@ public Literal visitIntegerLiteral(EqlBaseParser.IntegerLiteralContext ctx) {
252250

253251
throw new ParsingException(source, siae.getMessage());
254252
}
255-
256-
Object val = Long.valueOf(value);
257-
DataType type = DataTypes.LONG;
258-
259-
// try to downsize to int if possible (since that's the most common type)
260-
if ((int) value == value) {
261-
type = DataTypes.INTEGER;
262-
val = Integer.valueOf((int) value);
263-
}
264-
return new Literal(source, val, type);
265253
}
266254

267255
@Override

x-pack/plugin/eql/src/main/resources/org/elasticsearch/xpack/eql/plugin/eql_whitelist.txt

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class org.elasticsearch.xpack.ql.expression.function.scalar.whitelist.InternalQl
1919
double nullSafeSortNumeric(Number)
2020
String nullSafeSortString(Object)
2121
Number nullSafeCastNumeric(Number, String)
22+
Number nullSafeCastToUnsignedLong(Number)
2223

2324
#
2425
# ASCII Functions

x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/analysis/VerifierTests.java

+1
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ public void testNumeric() {
237237
accept(idxr, "foo where float_field == 0");
238238
accept(idxr, "foo where half_float_field == 0");
239239
accept(idxr, "foo where scaled_float_field == 0");
240+
accept(idxr, "foo where unsigned_long_field == 0");
240241

241242
// Test query against unsupported field type int
242243
assertEquals(

x-pack/plugin/eql/src/test/resources/mapping-numeric.json

+3
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
"scaled_float_field": {
3535
"type" : "scaled_float"
3636
},
37+
"unsigned_long_field" : {
38+
"type": "unsigned_long"
39+
},
3740
"wrong_int_type_field": {
3841
"type" : "int"
3942
}

x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/execution/search/QlSourceBuilder.java

-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
* the resulting ES document as a field.
2323
*/
2424
public class QlSourceBuilder {
25-
public static final Version SWITCH_TO_FIELDS_API_VERSION = Version.V_7_10_0;
2625
public static final Version INTRODUCING_MISSING_ORDER_IN_COMPOSITE_AGGS_VERSION = Version.V_7_16_0;
2726
// The LinkedHashMaps preserve the order of the fields in the response
2827
private final Set<FieldAndFormat> fetchFields = new LinkedHashSet<>();

x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/expression/function/scalar/ScalarFunction.java

+12-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.elasticsearch.xpack.ql.expression.function.Function;
1313
import org.elasticsearch.xpack.ql.expression.function.aggregate.AggregateFunction;
1414
import org.elasticsearch.xpack.ql.expression.function.grouping.GroupingFunction;
15+
import org.elasticsearch.xpack.ql.expression.gen.script.Params;
1516
import org.elasticsearch.xpack.ql.expression.gen.script.ParamsBuilder;
1617
import org.elasticsearch.xpack.ql.expression.gen.script.ScriptTemplate;
1718
import org.elasticsearch.xpack.ql.expression.gen.script.Scripts;
@@ -24,10 +25,12 @@
2425
import java.util.List;
2526

2627
import static java.util.Collections.emptyList;
28+
import static org.elasticsearch.common.logging.LoggerMessageFormat.format;
2729
import static org.elasticsearch.xpack.ql.expression.gen.script.ParamsBuilder.paramsBuilder;
2830
import static org.elasticsearch.xpack.ql.expression.gen.script.Scripts.PARAM;
2931
import static org.elasticsearch.xpack.ql.type.DataTypes.DATETIME;
3032
import static org.elasticsearch.xpack.ql.type.DataTypes.LONG;
33+
import static org.elasticsearch.xpack.ql.type.DataTypes.UNSIGNED_LONG;
3134

3235
/**
3336
* A {@code ScalarFunction} is a {@code Function} that takes values from some
@@ -150,7 +153,15 @@ protected ScriptTemplate scriptWithGrouping(GroupingFunction grouping) {
150153
}
151154

152155
protected ScriptTemplate scriptWithField(FieldAttribute field) {
153-
return new ScriptTemplate(processScript(Scripts.DOC_VALUE), paramsBuilder().variable(field.name()).build(), dataType());
156+
Params params = paramsBuilder().variable(field.name()).build();
157+
// unsigned_long fields get returned in scripts as plain longs, so a conversion is required
158+
return field.dataType() != UNSIGNED_LONG
159+
? new ScriptTemplate(processScript(Scripts.DOC_VALUE), params, dataType())
160+
: new ScriptTemplate(
161+
processScript(format("{ql}.", "nullSafeCastToUnsignedLong({})", Scripts.DOC_VALUE)),
162+
params,
163+
UNSIGNED_LONG
164+
);
154165
}
155166

156167
protected String processScript(String script) {

x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/expression/function/scalar/whitelist/InternalQlScriptUtils.java

+5
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.Map;
2424

2525
import static org.elasticsearch.xpack.ql.type.DataTypeConverter.convert;
26+
import static org.elasticsearch.xpack.ql.type.DataTypeConverter.toUnsignedLong;
2627
import static org.elasticsearch.xpack.ql.type.DataTypes.fromTypeName;
2728

2829
public class InternalQlScriptUtils {
@@ -58,6 +59,10 @@ public static Number nullSafeCastNumeric(Number number, String typeName) {
5859
return number == null || Double.isNaN(number.doubleValue()) ? null : (Number) convert(number, fromTypeName(typeName));
5960
}
6061

62+
public static Number nullSafeCastToUnsignedLong(Number number) {
63+
return number == null || Double.isNaN(number.doubleValue()) ? null : toUnsignedLong(number);
64+
}
65+
6166
//
6267
// Operators
6368
//

x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/expression/predicate/operator/arithmetic/Arithmetics.java

+36
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@
88

99
import org.elasticsearch.xpack.ql.QlIllegalArgumentException;
1010

11+
import java.math.BigInteger;
1112
import java.util.function.BiFunction;
1213

14+
import static org.elasticsearch.xpack.ql.util.NumericUtils.asUnsignedLong;
15+
1316
/**
1417
* Arithmetic operation using the type widening rules of the JLS 5.6.2 namely
1518
* widen to double or float or long or int in this order.
@@ -43,6 +46,10 @@ public static Number add(Number l, Number r) {
4346
if (l instanceof Float || r instanceof Float) {
4447
return Float.valueOf(l.floatValue() + r.floatValue());
4548
}
49+
if (l instanceof BigInteger || r instanceof BigInteger) {
50+
BigInteger bi = asBigInteger(l).add(asBigInteger(r));
51+
return asUnsignedLong(bi);
52+
}
4653
if (l instanceof Long || r instanceof Long) {
4754
return Long.valueOf(Math.addExact(l.longValue(), r.longValue()));
4855
}
@@ -61,6 +68,10 @@ public static Number sub(Number l, Number r) {
6168
if (l instanceof Float || r instanceof Float) {
6269
return Float.valueOf(l.floatValue() - r.floatValue());
6370
}
71+
if (l instanceof BigInteger || r instanceof BigInteger) {
72+
BigInteger bi = asBigInteger(l).subtract(asBigInteger(r));
73+
return asUnsignedLong(bi);
74+
}
6475
if (l instanceof Long || r instanceof Long) {
6576
return Long.valueOf(Math.subtractExact(l.longValue(), r.longValue()));
6677
}
@@ -79,6 +90,13 @@ public static Number mul(Number l, Number r) {
7990
if (l instanceof Float || r instanceof Float) {
8091
return Float.valueOf(l.floatValue() * r.floatValue());
8192
}
93+
if (l instanceof BigInteger || r instanceof BigInteger) {
94+
BigInteger bi = asBigInteger(l).multiply(asBigInteger(r));
95+
// Note: in case of unsigned_long overflow (or underflow, with negative fixed point numbers), the exception is thrown.
96+
// This is unlike the way some other traditional RDBMS that support unsigned types work, which simply promote the result to a
97+
// floating point type, but in line with how our implementation treats other fixed point type operations (i.e. Math#xxExact()).
98+
return asUnsignedLong(bi);
99+
}
82100
if (l instanceof Long || r instanceof Long) {
83101
return Long.valueOf(Math.multiplyExact(l.longValue(), r.longValue()));
84102
}
@@ -97,6 +115,10 @@ public static Number div(Number l, Number r) {
97115
if (l instanceof Float || r instanceof Float) {
98116
return l.floatValue() / r.floatValue();
99117
}
118+
if (l instanceof BigInteger || r instanceof BigInteger) {
119+
BigInteger bi = asBigInteger(l).divide(asBigInteger(r));
120+
return asUnsignedLong(bi);
121+
}
100122
if (l instanceof Long || r instanceof Long) {
101123
return l.longValue() / r.longValue();
102124
}
@@ -115,6 +137,10 @@ public static Number mod(Number l, Number r) {
115137
if (l instanceof Float || r instanceof Float) {
116138
return Float.valueOf(l.floatValue() % r.floatValue());
117139
}
140+
if (l instanceof BigInteger || r instanceof BigInteger) {
141+
BigInteger bi = asBigInteger(l).remainder(asBigInteger(r));
142+
return asUnsignedLong(bi);
143+
}
118144
if (l instanceof Long || r instanceof Long) {
119145
return Long.valueOf(l.longValue() % r.longValue());
120146
}
@@ -141,10 +167,20 @@ static Number negate(Number n) {
141167
}
142168
return Float.valueOf(-n.floatValue());
143169
}
170+
if (n instanceof BigInteger) {
171+
if (((BigInteger) n).signum() != 0) {
172+
throw new ArithmeticException("unsigned_long overflow"); // in the scope of the unsigned_long type
173+
}
174+
return n;
175+
}
144176
if (n instanceof Long) {
145177
return Long.valueOf(Math.negateExact(n.longValue()));
146178
}
147179

148180
return Integer.valueOf(Math.negateExact(n.intValue()));
149181
}
182+
183+
public static BigInteger asBigInteger(Number n) {
184+
return n instanceof BigInteger ? (BigInteger) n : BigInteger.valueOf(n.longValue());
185+
}
150186
}

x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/expression/predicate/operator/comparison/Comparisons.java

+6
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@
66
*/
77
package org.elasticsearch.xpack.ql.expression.predicate.operator.comparison;
88

9+
import java.math.BigInteger;
910
import java.util.Set;
1011

12+
import static org.elasticsearch.xpack.ql.expression.predicate.operator.arithmetic.Arithmetics.asBigInteger;
13+
1114
/**
1215
* Comparison utilities.
1316
*/
@@ -92,6 +95,9 @@ private static Integer compare(Number l, Number r) {
9295
if (l instanceof Float || r instanceof Float) {
9396
return Float.compare(l.floatValue(), r.floatValue());
9497
}
98+
if (l instanceof BigInteger || r instanceof BigInteger) {
99+
return asBigInteger(l).compareTo(asBigInteger(r));
100+
}
95101
if (l instanceof Long || r instanceof Long) {
96102
return Long.compare(l.longValue(), r.longValue());
97103
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.ql.index;
9+
10+
import org.elasticsearch.Version;
11+
import org.elasticsearch.xpack.ql.type.DataType;
12+
import org.elasticsearch.xpack.ql.type.EsField;
13+
import org.elasticsearch.xpack.ql.type.UnsupportedEsField;
14+
15+
import java.util.Map;
16+
17+
import static org.elasticsearch.xpack.ql.index.VersionCompatibilityChecks.isTypeSupportedInVersion;
18+
import static org.elasticsearch.xpack.ql.type.DataTypes.isPrimitive;
19+
import static org.elasticsearch.xpack.ql.type.Types.propagateUnsupportedType;
20+
21+
public final class IndexCompatibility {
22+
23+
public static Map<String, EsField> compatible(Map<String, EsField> mapping, Version version) {
24+
for (Map.Entry<String, EsField> entry : mapping.entrySet()) {
25+
EsField esField = entry.getValue();
26+
DataType dataType = esField.getDataType();
27+
if (isPrimitive(dataType) == false) {
28+
compatible(esField.getProperties(), version);
29+
} else if (isTypeSupportedInVersion(dataType, version) == false) {
30+
EsField field = new UnsupportedEsField(entry.getKey(), dataType.name(), null, esField.getProperties());
31+
entry.setValue(field);
32+
propagateUnsupportedType(entry.getKey(), dataType.name(), esField.getProperties());
33+
}
34+
}
35+
return mapping;
36+
}
37+
38+
public static EsIndex compatible(EsIndex esIndex, Version version) {
39+
compatible(esIndex.mapping(), version);
40+
return esIndex;
41+
}
42+
43+
public static IndexResolution compatible(IndexResolution indexResolution, Version version) {
44+
if (indexResolution.isValid()) {
45+
compatible(indexResolution.get(), version);
46+
}
47+
return indexResolution;
48+
}
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.ql.index;
9+
10+
import org.elasticsearch.Version;
11+
import org.elasticsearch.core.Nullable;
12+
import org.elasticsearch.xpack.ql.type.DataType;
13+
14+
import static org.elasticsearch.Version.V_8_2_0;
15+
import static org.elasticsearch.xpack.ql.type.DataTypes.UNSIGNED_LONG;
16+
17+
public final class VersionCompatibilityChecks {
18+
19+
public static final Version INTRODUCING_UNSIGNED_LONG = V_8_2_0;
20+
21+
private VersionCompatibilityChecks() {}
22+
23+
public static boolean isTypeSupportedInVersion(DataType dataType, Version version) {
24+
if (dataType == UNSIGNED_LONG) {
25+
return supportsUnsignedLong(version);
26+
}
27+
return true;
28+
}
29+
30+
/**
31+
* Does the provided {@code version} support the unsigned_long type (PR#60050)?
32+
*/
33+
public static boolean supportsUnsignedLong(Version version) {
34+
return INTRODUCING_UNSIGNED_LONG.compareTo(version) <= 0;
35+
}
36+
37+
public static @Nullable Version versionIntroducingType(DataType dataType) {
38+
if (dataType == UNSIGNED_LONG) {
39+
return INTRODUCING_UNSIGNED_LONG;
40+
}
41+
42+
return null;
43+
}
44+
}

0 commit comments

Comments
 (0)