Skip to content

Commit ceccf94

Browse files
committed
Handle null multivalues
1 parent 2c95a66 commit ceccf94

File tree

3 files changed

+114
-28
lines changed

3 files changed

+114
-28
lines changed

x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RestEsqlTestCase.java

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -692,19 +692,62 @@ public void testArrayValuesAllowedInValueParams() throws IOException {
692692

693693
Map<String, Object> responseMap = runEsql(
694694
RequestObjectBuilder.jsonBuilder()
695-
.query("row a = ?n1 | eval s = ?n2")
696-
.params("[{\"n1\" : [\"a1\", \"a2\"]}, {\"n2\" : [1, 2]}]")
695+
.query("row a = ?a | eval b = ?b, c = ?c, d = ?d, e = ?e, f = ?f")
696+
.params(
697+
"[{\"a\" : [\"a1\", \"a2\"]}, {\"b\" : [1, 2]}, {\"c\": [true, false]}, {\"d\": [1.1, 2.2]},"
698+
+ " {\"e\": [1674835275193, 1674835275193]}, {\"f\": [null, null]}]"
699+
)
700+
);
701+
System.out.println(responseMap);
702+
703+
ListMatcher values = matchesList().item(
704+
matchesList().item(matchesList().item("a1").item("a2"))
705+
.item(matchesList().item(1).item(2))
706+
.item(matchesList().item(true).item(false))
707+
.item(matchesList().item(1.1).item(2.2))
708+
.item(matchesList().item(1674835275193L).item(1674835275193L))
709+
.item(null) // constant null block, no multi-value
710+
);
711+
712+
assertResultMap(
713+
responseMap,
714+
matchesList().item(matchesMap().entry("name", "a").entry("type", "keyword"))
715+
.item(matchesMap().entry("name", "b").entry("type", "integer"))
716+
.item(matchesMap().entry("name", "c").entry("type", "boolean"))
717+
.item(matchesMap().entry("name", "d").entry("type", "double"))
718+
.item(matchesMap().entry("name", "e").entry("type", "long"))
719+
.item(matchesMap().entry("name", "f").entry("type", "null")),
720+
values
721+
);
722+
}
723+
724+
public void testArrayValuesAllowedInUnnamedParams() throws IOException {
725+
assumeTrue("multivalues for params", EsqlCapabilities.Cap.QUERY_PARAMS_MULTI_VALUES.isEnabled());
726+
727+
Map<String, Object> responseMap = runEsql(
728+
RequestObjectBuilder.jsonBuilder()
729+
.query("row a = ? | eval b = ?, c = ?, d = ?, e = ?, f = ?")
730+
.params("[[\"a1\", \"a2\"], [1, 2], [true, false], [1.1, 2.2], [1674835275193, 1674835275193], [null, null]]")
697731
);
698732
System.out.println(responseMap);
699733

700734
ListMatcher values = matchesList().item(
701-
matchesList().item(matchesList().item("a1").item("a2")).item(matchesList().item(1).item(2))
735+
matchesList().item(matchesList().item("a1").item("a2"))
736+
.item(matchesList().item(1).item(2))
737+
.item(matchesList().item(true).item(false))
738+
.item(matchesList().item(1.1).item(2.2))
739+
.item(matchesList().item(1674835275193L).item(1674835275193L))
740+
.item(null)
702741
);
703742

704743
assertResultMap(
705744
responseMap,
706745
matchesList().item(matchesMap().entry("name", "a").entry("type", "keyword"))
707-
.item(matchesMap().entry("name", "s").entry("type", "integer")),
746+
.item(matchesMap().entry("name", "b").entry("type", "integer"))
747+
.item(matchesMap().entry("name", "c").entry("type", "boolean"))
748+
.item(matchesMap().entry("name", "d").entry("type", "double"))
749+
.item(matchesMap().entry("name", "e").entry("type", "long"))
750+
.item(matchesMap().entry("name", "f").entry("type", "null")),
708751
values
709752
);
710753
}

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/RequestXContent.java

Lines changed: 55 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -194,32 +194,31 @@ private static QueryParams parseParams(XContentParser p) throws IOException {
194194
namedParams.add(currentParam);
195195
}
196196
} else {
197-
paramValue = null;
198-
if (token == XContentParser.Token.VALUE_STRING) {
199-
paramValue = p.text();
200-
type = DataType.KEYWORD;
201-
} else if (token == XContentParser.Token.VALUE_NUMBER) {
202-
XContentParser.NumberType numberType = p.numberType();
203-
if (numberType == XContentParser.NumberType.INT) {
204-
paramValue = p.intValue();
205-
type = DataType.INTEGER;
206-
} else if (numberType == XContentParser.NumberType.LONG) {
207-
paramValue = p.longValue();
208-
type = DataType.LONG;
209-
} else if (numberType == XContentParser.NumberType.DOUBLE) {
210-
paramValue = p.doubleValue();
211-
type = DataType.DOUBLE;
197+
if (token == XContentParser.Token.START_ARRAY) {
198+
DataType currentType = null;
199+
List<Object> paramValues = new ArrayList<>();
200+
while ((p.nextToken()) != XContentParser.Token.END_ARRAY) {
201+
ParamValueAndType valueAndDataType = parseSingleParamValue(p, errors);
202+
if (currentType == null) {
203+
currentType = valueAndDataType.type;
204+
} else if (currentType != valueAndDataType.type) {
205+
errors.add(
206+
new XContentParseException(
207+
loc,
208+
"Unnamed parameter has values from different types, found "
209+
+ currentType
210+
+ " and "
211+
+ valueAndDataType.type
212+
)
213+
);
214+
}
215+
paramValues.add(valueAndDataType.value);
212216
}
213-
} else if (token == XContentParser.Token.VALUE_BOOLEAN) {
214-
paramValue = p.booleanValue();
215-
type = DataType.BOOLEAN;
216-
} else if (token == XContentParser.Token.VALUE_NULL) {
217-
type = DataType.NULL;
217+
unNamedParams.add(new QueryParam(null, paramValues, currentType, VALUE));
218218
} else {
219-
errors.add(new XContentParseException(loc, token + " is not supported as a parameter"));
219+
ParamValueAndType valueAndDataType = parseSingleParamValue(p, errors);
220+
unNamedParams.add(new QueryParam(null, valueAndDataType.value, valueAndDataType.type, VALUE));
220221
}
221-
currentParam = new QueryParam(null, paramValue, type, VALUE);
222-
unNamedParams.add(currentParam);
223222
}
224223
}
225224
}
@@ -243,6 +242,39 @@ private static QueryParams parseParams(XContentParser p) throws IOException {
243242
return new QueryParams(namedParams.isEmpty() ? unNamedParams : namedParams);
244243
}
245244

245+
private record ParamValueAndType(Object value, DataType type) {}
246+
247+
private static ParamValueAndType parseSingleParamValue(XContentParser p, List<XContentParseException> errors) throws IOException {
248+
Object paramValue = null;
249+
DataType type = null;
250+
XContentParser.Token token = p.currentToken();
251+
if (token == XContentParser.Token.VALUE_STRING) {
252+
paramValue = p.text();
253+
type = DataType.KEYWORD;
254+
} else if (token == XContentParser.Token.VALUE_NUMBER) {
255+
XContentParser.NumberType numberType = p.numberType();
256+
if (numberType == XContentParser.NumberType.INT) {
257+
paramValue = p.intValue();
258+
type = DataType.INTEGER;
259+
} else if (numberType == XContentParser.NumberType.LONG) {
260+
paramValue = p.longValue();
261+
type = DataType.LONG;
262+
} else if (numberType == XContentParser.NumberType.DOUBLE) {
263+
paramValue = p.doubleValue();
264+
type = DataType.DOUBLE;
265+
}
266+
} else if (token == XContentParser.Token.VALUE_BOOLEAN) {
267+
paramValue = p.booleanValue();
268+
type = DataType.BOOLEAN;
269+
} else if (token == XContentParser.Token.VALUE_NULL) {
270+
type = DataType.NULL;
271+
} else {
272+
XContentLocation loc = p.getTokenLocation();
273+
errors.add(new XContentParseException(loc, token + " is not supported as a parameter"));
274+
}
275+
return new ParamValueAndType(paramValue, type);
276+
}
277+
246278
private static void checkParamNameValidity(String name, List<XContentParseException> errors, XContentLocation loc) {
247279
if (isValidParamName(name) == false) {
248280
errors.add(

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/evaluator/EvalMapper.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,18 @@ private static Block block(Literal lit, BlockFactory blockFactory, int positions
273273
if (multiValue.isEmpty()) {
274274
return blockFactory.newConstantNullBlock(positions);
275275
}
276-
var wrapper = BlockUtils.wrapperFor(blockFactory, ElementType.fromJava(multiValue.get(0).getClass()), positions);
276+
ElementType type = ElementType.NULL;
277+
for (Object elem : multiValue) {
278+
if (elem != null) {
279+
type = ElementType.fromJava(elem.getClass());
280+
break;
281+
}
282+
}
283+
if (type == ElementType.NULL) {
284+
// all values are null
285+
return blockFactory.newConstantNullBlock(positions);
286+
}
287+
var wrapper = BlockUtils.wrapperFor(blockFactory, type, positions);
277288
for (int i = 0; i < positions; i++) {
278289
wrapper.accept(multiValue);
279290
}

0 commit comments

Comments
 (0)