Skip to content

Commit f697b13

Browse files
committed
ESQL: Fix LIMIT NPE with null value (#130914)
Fixes #130908
1 parent 15d5fe0 commit f697b13

File tree

7 files changed

+68
-24
lines changed

7 files changed

+68
-24
lines changed

docs/changelog/130914.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 130914
2+
summary: Fix LIMIT NPE with null value
3+
area: ES|QL
4+
type: bug
5+
issues:
6+
- 130908

x-pack/plugin/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ tasks.named("yamlRestCompatTestTransform").configure({ task ->
136136
task.skipTest("esql/191_lookup_join_on_datastreams/data streams not supported in LOOKUP JOIN", "Added support for aliases in JOINs")
137137
task.skipTest("esql/190_lookup_join/non-lookup index", "Error message changed")
138138
task.skipTest("esql/192_lookup_join_on_aliases/alias-pattern-multiple", "Error message changed")
139+
task.skipTest("esql/10_basic/Test wrong LIMIT parameter", "Error message changed")
139140
})
140141

141142
tasks.named('yamlRestCompatTest').configure {

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1175,6 +1175,11 @@ public enum Cap {
11751175
*/
11761176
PARAMETER_FOR_LIMIT,
11771177

1178+
/**
1179+
* Changed and normalized the LIMIT error message.
1180+
*/
1181+
NORMALIZED_LIMIT_ERROR_MESSAGE,
1182+
11781183
/**
11791184
* Dense vector field type support
11801185
*/

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/LogicalPlanBuilder.java

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -391,21 +391,22 @@ public PlanFactory visitWhereCommand(EsqlBaseParser.WhereCommandContext ctx) {
391391
public PlanFactory visitLimitCommand(EsqlBaseParser.LimitCommandContext ctx) {
392392
Source source = source(ctx);
393393
Object val = expression(ctx.constant()).fold(FoldContext.small() /* TODO remove me */);
394-
if (val instanceof Integer i) {
395-
if (i < 0) {
396-
throw new ParsingException(source, "Invalid value for LIMIT [" + i + "], expecting a non negative integer");
397-
}
394+
if (val instanceof Integer i && i >= 0) {
398395
return input -> new Limit(source, new Literal(source, i, DataType.INTEGER), input);
399-
} else {
400-
throw new ParsingException(
401-
source,
402-
"Invalid value for LIMIT ["
403-
+ BytesRefs.toString(val)
404-
+ ": "
405-
+ (expression(ctx.constant()).dataType() == KEYWORD ? "String" : val.getClass().getSimpleName())
406-
+ "], expecting a non negative integer"
407-
);
408396
}
397+
398+
String valueType = expression(ctx.constant()).dataType().typeName();
399+
400+
throw new ParsingException(
401+
source,
402+
"value of ["
403+
+ source.text()
404+
+ "] must be a non negative integer, found value ["
405+
+ ctx.constant().getText()
406+
+ "] type ["
407+
+ valueType
408+
+ "]"
409+
);
409410
}
410411

411412
@Override

x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/ParsingTests.java

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@
1919
import org.elasticsearch.xpack.esql.index.EsIndex;
2020
import org.elasticsearch.xpack.esql.index.IndexResolution;
2121
import org.elasticsearch.xpack.esql.parser.EsqlParser;
22+
import org.elasticsearch.xpack.esql.parser.ParserUtils;
2223
import org.elasticsearch.xpack.esql.parser.ParsingException;
24+
import org.elasticsearch.xpack.esql.parser.QueryParam;
25+
import org.elasticsearch.xpack.esql.parser.QueryParams;
2326
import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan;
2427
import org.elasticsearch.xpack.esql.plan.logical.Row;
2528
import org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter;
@@ -157,9 +160,34 @@ public void testJoinTwiceOnTheSameField_TwoLookups() {
157160
}
158161

159162
public void testInvalidLimit() {
160-
assertEquals("1:13: Invalid value for LIMIT [foo: String], expecting a non negative integer", error("row a = 1 | limit \"foo\""));
161-
assertEquals("1:13: Invalid value for LIMIT [1.2: Double], expecting a non negative integer", error("row a = 1 | limit 1.2"));
162-
assertEquals("1:13: Invalid value for LIMIT [-1], expecting a non negative integer", error("row a = 1 | limit -1"));
163+
assertLimitWithAndWithoutParams("foo", "\"foo\"", DataType.KEYWORD);
164+
assertLimitWithAndWithoutParams(1.2, "1.2", DataType.DOUBLE);
165+
assertLimitWithAndWithoutParams(-1, "-1", DataType.INTEGER);
166+
assertLimitWithAndWithoutParams(true, "true", DataType.BOOLEAN);
167+
assertLimitWithAndWithoutParams(false, "false", DataType.BOOLEAN);
168+
assertLimitWithAndWithoutParams(null, "null", DataType.NULL);
169+
}
170+
171+
private void assertLimitWithAndWithoutParams(Object value, String valueText, DataType type) {
172+
assertEquals(
173+
"1:13: value of [limit "
174+
+ valueText
175+
+ "] must be a non negative integer, found value ["
176+
+ valueText
177+
+ "] type ["
178+
+ type.typeName()
179+
+ "]",
180+
error("row a = 1 | limit " + valueText)
181+
);
182+
183+
assertEquals(
184+
"1:13: value of [limit ?param] must be a non negative integer, found value [?param] type [" + type.typeName() + "]",
185+
error(
186+
"row a = 1 | limit ?param",
187+
new QueryParams(List.of(new QueryParam("param", value, type, ParserUtils.ParamClassification.VALUE)))
188+
)
189+
);
190+
163191
}
164192

165193
public void testInvalidSample() {
@@ -181,13 +209,20 @@ public void testInvalidSample() {
181209
);
182210
}
183211

184-
private String error(String query) {
185-
ParsingException e = expectThrows(ParsingException.class, () -> defaultAnalyzer.analyze(parser.createStatement(query)));
212+
private String error(String query, QueryParams params) {
213+
ParsingException e = expectThrows(
214+
ParsingException.class,
215+
() -> defaultAnalyzer.analyze(parser.createStatement(query, params))
216+
);
186217
String message = e.getMessage();
187218
assertTrue(message.startsWith("line "));
188219
return message.substring("line ".length());
189220
}
190221

222+
private String error(String query) {
223+
return error(query, new QueryParams());
224+
}
225+
191226
private static IndexResolution loadIndexResolution(String name) {
192227
return IndexResolution.valid(new EsIndex(INDEX_NAME, LoadMapping.loadMapping(name)));
193228
}

x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/parser/StatementParserTests.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -945,10 +945,6 @@ public void testBasicLimitCommand() {
945945
assertThat(limit.children().get(0).children().get(0), instanceOf(UnresolvedRelation.class));
946946
}
947947

948-
public void testLimitConstraints() {
949-
expectError("from text | limit -1", "line 1:13: Invalid value for LIMIT [-1], expecting a non negative integer");
950-
}
951-
952948
public void testBasicSortCommand() {
953949
LogicalPlan plan = statement("from text | where true | sort a+b asc nulls first, x desc nulls last | sort y asc | sort z desc");
954950
assertThat(plan, instanceOf(OrderBy.class));

x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/10_basic.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -555,11 +555,11 @@ FROM EVAL SORT LIMIT with documents_found:
555555
- method: POST
556556
path: /_query
557557
parameters: [ ]
558-
capabilities: [ parameter_for_limit ]
558+
capabilities: [ normalized_limit_error_message ]
559559
reason: "named or positional parameters for field names"
560560

561561
- do:
562-
catch: "/Invalid value for LIMIT \\[foo: String\\], expecting a non negative integer/"
562+
catch: "/value of \\[limit \\?l\\] must be a non negative integer, found value \\[\\?l\\] type \\[keyword\\]/"
563563
esql.query:
564564
body:
565565
query: 'from test | limit ?l'

0 commit comments

Comments
 (0)