Skip to content

Commit 6d63670

Browse files
Add support for parameters in LIMIT command (elastic#128464)
1 parent 488bd6a commit 6d63670

File tree

10 files changed

+244
-162
lines changed

10 files changed

+244
-162
lines changed

docs/changelog/128464.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 128464
2+
summary: Add support for parameters in LIMIT command
3+
area: ES|QL
4+
type: enhancement
5+
issues: []

x-pack/plugin/esql/src/main/antlr/EsqlBaseParser.g4

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ identifierOrParameter
187187
;
188188

189189
limitCommand
190-
: LIMIT INTEGER_LITERAL
190+
: LIMIT constant
191191
;
192192

193193
sortCommand

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1128,7 +1128,12 @@ public enum Cap {
11281128
/**
11291129
* Dense vector field type support
11301130
*/
1131-
DENSE_VECTOR_FIELD_TYPE(EsqlCorePlugin.DENSE_VECTOR_FEATURE_FLAG);
1131+
DENSE_VECTOR_FIELD_TYPE(EsqlCorePlugin.DENSE_VECTOR_FEATURE_FLAG),
1132+
1133+
/**
1134+
* Support parameters for LiMIT command.
1135+
*/
1136+
PARAMETER_FOR_LIMIT;
11321137

11331138
private final boolean enabled;
11341139

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

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 148 additions & 146 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,10 @@
9595
import static org.elasticsearch.xpack.esql.parser.ParserUtils.typedParsing;
9696
import static org.elasticsearch.xpack.esql.parser.ParserUtils.visitList;
9797
import static org.elasticsearch.xpack.esql.plan.logical.Enrich.Mode;
98-
import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.stringToInt;
9998

10099
/**
101100
* Translates what we get back from Antlr into the data structures the rest of the planner steps will act on. Generally speaking, things
102101
* which change the grammar will need to make changes here as well.
103-
*
104102
*/
105103
public class LogicalPlanBuilder extends ExpressionBuilder {
106104

@@ -384,8 +382,18 @@ public PlanFactory visitWhereCommand(EsqlBaseParser.WhereCommandContext ctx) {
384382
@Override
385383
public PlanFactory visitLimitCommand(EsqlBaseParser.LimitCommandContext ctx) {
386384
Source source = source(ctx);
387-
int limit = stringToInt(ctx.INTEGER_LITERAL().getText());
388-
return input -> new Limit(source, new Literal(source, limit, DataType.INTEGER), input);
385+
Object val = expression(ctx.constant()).fold(FoldContext.small() /* TODO remove me */);
386+
if (val instanceof Integer i) {
387+
if (i < 0) {
388+
throw new ParsingException(source, "Invalid value for LIMIT [" + val + "], expecting a non negative integer");
389+
}
390+
return input -> new Limit(source, new Literal(source, i, DataType.INTEGER), input);
391+
} else {
392+
throw new ParsingException(
393+
source,
394+
"Invalid value for LIMIT [" + val + ": " + val.getClass().getSimpleName() + "], expecting a non negative integer"
395+
);
396+
}
389397
}
390398

391399
@Override

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3304,7 +3304,7 @@ public void testForkError() {
33043304
( WHERE emp_no > 5 )
33053305
( WHERE emp_no > 6 | SORT emp_no | LIMIT 5 )
33063306
"""));
3307-
assertThat(pe.getMessage(), containsString("mismatched input 'me' expecting INTEGER_LITERAL"));
3307+
assertThat(pe.getMessage(), containsString("mismatched input 'me' expecting {"));
33083308

33093309
e = expectThrows(VerificationException.class, () -> analyze("""
33103310
FROM test

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,12 @@ public void testJoinTwiceOnTheSameField_TwoLookups() {
156156
);
157157
}
158158

159+
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+
}
164+
159165
private String error(String query) {
160166
ParsingException e = expectThrows(ParsingException.class, () -> defaultAnalyzer.analyze(parser.createStatement(query)));
161167
String message = e.getMessage();

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -858,7 +858,7 @@ public void testBasicLimitCommand() {
858858
}
859859

860860
public void testLimitConstraints() {
861-
expectError("from text | limit -1", "line 1:19: extraneous input '-' expecting INTEGER_LITERAL");
861+
expectError("from text | limit -1", "line 1:13: Invalid value for LIMIT [-1], expecting a non negative integer");
862862
}
863863

864864
public void testBasicSortCommand() {
@@ -3029,7 +3029,7 @@ public void testNamedFunctionArgumentInInvalidPositions() {
30293029
Map.entry("drop {}", "line 1:18: token recognition error at: '{'"),
30303030
Map.entry("rename a as {}", "line 1:25: token recognition error at: '{'"),
30313031
Map.entry("mv_expand {}", "line 1:23: token recognition error at: '{'"),
3032-
Map.entry("limit {}", "line 1:19: mismatched input '{' expecting INTEGER_LITERAL"),
3032+
Map.entry("limit {}", "line 1:19: extraneous input '{' expecting {QUOTED_STRING"),
30333033
Map.entry("enrich idx2 on f1 with f2 = {}", "line 1:41: token recognition error at: '{'"),
30343034
Map.entry("dissect {} \"%{bar}\"", "line 1:21: extraneous input '{' expecting {QUOTED_STRING, INTEGER_LITERAL"),
30353035
Map.entry("grok {} \"%{WORD:foo}\"", "line 1:18: extraneous input '{' expecting {QUOTED_STRING, INTEGER_LITERAL")

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

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,41 @@ FROM EVAL SORT LIMIT with documents_found:
412412
- match: {values.1: ["1",2.0,null,true,123,1674835275193]}
413413
- match: {values.2: ["1",2.0,null,true,123,1674835275193]}
414414

415+
---
416+
"Test Unnamed Input Params Also For Limit":
417+
- requires:
418+
test_runner_features: [ capabilities ]
419+
capabilities:
420+
- method: POST
421+
path: /_query
422+
parameters: [ ]
423+
capabilities: [ parameter_for_limit ]
424+
reason: "named or positional parameters"
425+
- do:
426+
esql.query:
427+
body:
428+
query: 'from test | eval x = ?, y = ?, z = ?, t = ?, u = ?, v = ? | keep x, y, z, t, u, v | limit ?'
429+
params: ["1", 2.0, null, true, 123, 1674835275193, 3]
430+
431+
- length: {columns: 6}
432+
- match: {columns.0.name: "x"}
433+
- match: {columns.0.type: "keyword"}
434+
- match: {columns.1.name: "y"}
435+
- match: {columns.1.type: "double"}
436+
- match: {columns.2.name: "z"}
437+
- match: {columns.2.type: "null"}
438+
- match: {columns.3.name: "t"}
439+
- match: {columns.3.type: "boolean"}
440+
- match: {columns.4.name: "u"}
441+
- match: {columns.4.type: "integer"}
442+
- match: {columns.5.name: "v"}
443+
- match: {columns.5.type: "long"}
444+
- length: {values: 3}
445+
- match: {values.0: ["1",2.0,null,true,123,1674835275193]}
446+
- match: {values.1: ["1",2.0,null,true,123,1674835275193]}
447+
- match: {values.2: ["1",2.0,null,true,123,1674835275193]}
448+
449+
415450
---
416451
"Test Named Input Params":
417452
- requires:
@@ -420,14 +455,14 @@ FROM EVAL SORT LIMIT with documents_found:
420455
- method: POST
421456
path: /_query
422457
parameters: [ ]
423-
capabilities: [ named_positional_parameter ]
458+
capabilities: [ parameter_for_limit ]
424459
reason: "named or positional parameters"
425460

426461
- do:
427462
esql.query:
428463
body:
429-
query: 'from test | eval x = ?, y = ?, z = ?, t = ?, u = ?, v = ? | keep x, y, z, t, u, v | limit 3'
430-
params: [{"n1" : "1"}, {"n2" : 2.0}, {"n3" : null}, {"n4" : true}, {"n5" : 123}, {"n6": 1674835275193}]
464+
query: 'from test | eval x = ?, y = ?, z = ?, t = ?, u = ?, v = ? | keep x, y, z, t, u, v | limit ?'
465+
params: [{"n1" : "1"}, {"n2" : 2.0}, {"n3" : null}, {"n4" : true}, {"n5" : 123}, {"n6": 1674835275193}, {"l": 3}]
431466

432467
- length: {columns: 6}
433468
- match: {columns.0.name: "x"}
@@ -484,14 +519,14 @@ FROM EVAL SORT LIMIT with documents_found:
484519
- method: POST
485520
path: /_query
486521
parameters: [ ]
487-
capabilities: [ named_parameter_for_field_and_function_names_simplified_syntax ]
522+
capabilities: [ parameter_for_limit ]
488523
reason: "named or positional parameters for field names"
489524

490525
- do:
491526
esql.query:
492527
body:
493-
query: 'from test | stats x = count(?f1), y = sum(?f2) by ?f3 | sort ?f3 | keep ?f3, x, y | limit 3'
494-
params: [{"f1" : {"identifier" : "time"}}, {"f2" : { "identifier" : "count" }}, {"f3" : { "identifier" : "color"}}]
528+
query: 'from test | stats x = count(?f1), y = sum(?f2) by ?f3 | sort ?f3 | keep ?f3, x, y | limit ?l'
529+
params: [{"f1" : {"identifier" : "time"}}, {"f2" : { "identifier" : "count" }}, {"f3" : { "identifier" : "color"}}, {"l": 3}]
495530

496531
- length: {columns: 3}
497532
- match: {columns.0.name: "color"}
@@ -505,6 +540,27 @@ FROM EVAL SORT LIMIT with documents_found:
505540
- match: {values.1: ["green",10,440]}
506541
- match: {values.2: ["red",20,860]}
507542

543+
544+
---
545+
"Test wrong LIMIT parameter":
546+
- requires:
547+
test_runner_features: [ capabilities ]
548+
capabilities:
549+
- method: POST
550+
path: /_query
551+
parameters: [ ]
552+
capabilities: [ parameter_for_limit ]
553+
reason: "named or positional parameters for field names"
554+
555+
- do:
556+
catch: "/Invalid value for LIMIT \\[foo: String\\], expecting a non negative integer/"
557+
esql.query:
558+
body:
559+
query: 'from test | limit ?l'
560+
params: [{"l": "foo"}]
561+
562+
563+
508564
---
509565
version is not allowed:
510566
- requires:

0 commit comments

Comments
 (0)