Skip to content

Commit 1be0355

Browse files
committed
Allow parameter for ES|QL SAMPLE (elastic#129392)
* Allow parameter for ES|QL SAMPLE * fix test to work around issue elastic#120272 * remove unused postAnalysisVerification
1 parent 8d34447 commit 1be0355

File tree

10 files changed

+77
-62
lines changed

10 files changed

+77
-62
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,5 +389,5 @@ completionCommand
389389
;
390390

391391
sampleCommand
392-
: DEV_SAMPLE probability=decimalValue
392+
: DEV_SAMPLE probability=constant
393393
;

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -928,7 +928,7 @@ public enum Cap {
928928
TO_LOWER_EMPTY_STRING,
929929

930930
/**
931-
* Support parameters for LiMIT command.
931+
* Support parameters for LIMIT command.
932932
*/
933933
PARAMETER_FOR_LIMIT,
934934

@@ -965,7 +965,12 @@ public enum Cap {
965965
/**
966966
* Support for the SAMPLE command
967967
*/
968-
SAMPLE_V3(Build.current().isSnapshot());
968+
SAMPLE_V3(Build.current().isSnapshot()),
969+
970+
/**
971+
* Support parameters for SAMPLE command.
972+
*/
973+
PARAMETER_FOR_SAMPLE(Build.current().isSnapshot());
969974

970975
private final boolean enabled;
971976

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: 12 additions & 12 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: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -707,7 +707,15 @@ public Literal inferenceId(EsqlBaseParser.IdentifierOrParameterContext ctx) {
707707
}
708708

709709
public PlanFactory visitSampleCommand(EsqlBaseParser.SampleCommandContext ctx) {
710-
var probability = visitDecimalValue(ctx.probability);
711-
return plan -> new Sample(source(ctx), probability, plan);
710+
Source source = source(ctx);
711+
Object val = expression(ctx.probability).fold(FoldContext.small() /* TODO remove me */);
712+
if (val instanceof Double probability && probability > 0.0 && probability < 1.0) {
713+
return input -> new Sample(source, new Literal(source, probability, DataType.DOUBLE), input);
714+
} else {
715+
throw new ParsingException(
716+
source(ctx),
717+
"invalid value for SAMPLE probability [" + val + "], expecting a number between 0 and 1, exclusive"
718+
);
719+
}
712720
}
713721
}

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/Sample.java

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,16 @@
99
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
1010
import org.elasticsearch.common.io.stream.StreamInput;
1111
import org.elasticsearch.common.io.stream.StreamOutput;
12-
import org.elasticsearch.search.aggregations.bucket.sampler.random.RandomSamplingQuery;
13-
import org.elasticsearch.xpack.esql.capabilities.PostAnalysisVerificationAware;
1412
import org.elasticsearch.xpack.esql.capabilities.TelemetryAware;
15-
import org.elasticsearch.xpack.esql.common.Failures;
1613
import org.elasticsearch.xpack.esql.core.expression.Expression;
17-
import org.elasticsearch.xpack.esql.core.expression.FoldContext;
18-
import org.elasticsearch.xpack.esql.core.expression.Foldables;
1914
import org.elasticsearch.xpack.esql.core.tree.NodeInfo;
2015
import org.elasticsearch.xpack.esql.core.tree.Source;
2116
import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput;
2217

2318
import java.io.IOException;
2419
import java.util.Objects;
2520

26-
import static org.elasticsearch.xpack.esql.common.Failure.fail;
27-
28-
public class Sample extends UnaryPlan implements TelemetryAware, PostAnalysisVerificationAware {
21+
public class Sample extends UnaryPlan implements TelemetryAware {
2922
public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(LogicalPlan.class, "Sample", Sample::new);
3023

3124
private final Expression probability;
@@ -92,13 +85,4 @@ public boolean equals(Object obj) {
9285

9386
return Objects.equals(probability, other.probability) && Objects.equals(child(), other.child());
9487
}
95-
96-
@Override
97-
public void postAnalysisVerification(Failures failures) {
98-
try {
99-
RandomSamplingQuery.checkProbabilityRange((double) Foldables.valueOf(FoldContext.small(), probability));
100-
} catch (IllegalArgumentException e) {
101-
failures.add(fail(probability, e.getMessage()));
102-
}
103-
}
10488
}

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

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,6 @@
112112
import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.defaultInferenceResolution;
113113
import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.indexWithDateDateNanosUnionType;
114114
import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.loadMapping;
115-
import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.randomValueOtherThanTest;
116115
import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.tsdbIndexResolution;
117116
import static org.elasticsearch.xpack.esql.core.tree.Source.EMPTY;
118117
import static org.elasticsearch.xpack.esql.core.type.DataType.DATETIME;
@@ -3166,20 +3165,6 @@ private boolean isMultiTypeEsField(Expression e) {
31663165
return e instanceof FieldAttribute fa && fa.field() instanceof MultiTypeEsField;
31673166
}
31683167

3169-
public void testRandomSampleProbability() {
3170-
assumeTrue("requires SAMPLE capability", EsqlCapabilities.Cap.SAMPLE_V3.isEnabled());
3171-
3172-
var e = expectThrows(VerificationException.class, () -> analyze("FROM test | SAMPLE 1."));
3173-
assertThat(e.getMessage(), containsString("RandomSampling probability must be strictly between 0.0 and 1.0, was [1.0]"));
3174-
3175-
e = expectThrows(VerificationException.class, () -> analyze("FROM test | SAMPLE .0"));
3176-
assertThat(e.getMessage(), containsString("RandomSampling probability must be strictly between 0.0 and 1.0, was [0.0]"));
3177-
3178-
double p = randomValueOtherThanTest(d -> 0 < d && d < 1, () -> randomDoubleBetween(0, Double.MAX_VALUE, false));
3179-
e = expectThrows(VerificationException.class, () -> analyze("FROM test | SAMPLE " + p));
3180-
assertThat(e.getMessage(), containsString("RandomSampling probability must be strictly between 0.0 and 1.0, was [" + p + "]"));
3181-
}
3182-
31833168
private void verifyUnsupported(String query, String errorMessage) {
31843169
verifyUnsupported(query, errorMessage, "mapping-multi-field-variation.json");
31853170
}

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,25 @@ public void testInvalidLimit() {
168168
assertEquals("1:13: Invalid value for LIMIT [-1], expecting a non negative integer", error("row a = 1 | limit -1"));
169169
}
170170

171+
public void testInvalidSample() {
172+
assertEquals(
173+
"1:13: invalid value for SAMPLE probability [foo], expecting a number between 0 and 1, exclusive",
174+
error("row a = 1 | sample \"foo\"")
175+
);
176+
assertEquals(
177+
"1:13: invalid value for SAMPLE probability [-1.0], expecting a number between 0 and 1, exclusive",
178+
error("row a = 1 | sample -1.0")
179+
);
180+
assertEquals(
181+
"1:13: invalid value for SAMPLE probability [0], expecting a number between 0 and 1, exclusive",
182+
error("row a = 1 | sample 0")
183+
);
184+
assertEquals(
185+
"1:13: invalid value for SAMPLE probability [1], expecting a number between 0 and 1, exclusive",
186+
error("row a = 1 | sample 1")
187+
);
188+
}
189+
171190
private String error(String query) {
172191
ParsingException e = expectThrows(ParsingException.class, () -> defaultAnalyzer.analyze(parser.createStatement(query)));
173192
String message = e.getMessage();

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@
103103
import static org.hamcrest.Matchers.hasSize;
104104
import static org.hamcrest.Matchers.instanceOf;
105105
import static org.hamcrest.Matchers.is;
106+
import static org.hamcrest.Matchers.startsWith;
106107

107108
//@TestLogging(value = "org.elasticsearch.xpack.esql:TRACE", reason = "debug")
108109
public class StatementParserTests extends AbstractStatementParserTests {
@@ -3363,8 +3364,15 @@ public void testSample() {
33633364
assumeTrue("SAMPLE requires corresponding capability", EsqlCapabilities.Cap.SAMPLE_V3.isEnabled());
33643365
expectError("FROM test | SAMPLE .1 2", "line 1:23: extraneous input '2' expecting <EOF>");
33653366
expectError("FROM test | SAMPLE .1 \"2\"", "line 1:23: extraneous input '\"2\"' expecting <EOF>");
3366-
expectError("FROM test | SAMPLE 1", "line 1:20: mismatched input '1' expecting {DECIMAL_LITERAL, '+', '-'}");
3367-
expectError("FROM test | SAMPLE", "line 1:19: mismatched input '<EOF>' expecting {DECIMAL_LITERAL, '+', '-'}");
3367+
expectError(
3368+
"FROM test | SAMPLE 1",
3369+
"line 1:13: invalid value for SAMPLE probability [1], expecting a number between 0 and 1, exclusive"
3370+
);
3371+
expectThrows(
3372+
ParsingException.class,
3373+
startsWith("line 1:19: mismatched input '<EOF>' expecting {"),
3374+
() -> statement("FROM test | SAMPLE")
3375+
);
33683376
}
33693377

33703378
static Alias alias(String name, Expression value) {

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

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -359,20 +359,22 @@ setup:
359359
- match: {values.2: ["1",2.0,null,true,123,1674835275193]}
360360

361361
---
362-
"Test Unnamed Input Params Also For Limit":
362+
"Test Unnamed Input Params Also For Limit And Sample":
363363
- requires:
364364
test_runner_features: [ capabilities ]
365365
capabilities:
366366
- method: POST
367367
path: /_query
368368
parameters: [ ]
369-
capabilities: [ parameter_for_limit ]
369+
capabilities: [ parameter_for_limit, parameter_for_sample ]
370370
reason: "named or positional parameters"
371371
- do:
372372
esql.query:
373373
body:
374-
query: 'from test | eval x = ?, y = ?, z = ?, t = ?, u = ?, v = ? | keep x, y, z, t, u, v | limit ?'
375-
params: ["1", 2.0, null, true, 123, 1674835275193, 3]
374+
# The "| sort time" is to work around https://github.com/elastic/elasticsearch/issues/120272
375+
# TODO: remove it when the issue is fixed
376+
query: 'from test | sort time | sample ? | eval x = ?, y = ?, z = ?, t = ?, u = ?, v = ? | keep x, y, z, t, u, v | limit ?'
377+
params: [0.999999999999, "1", 2.0, null, true, 123, 1674835275193, 3]
376378

377379
- length: {columns: 6}
378380
- match: {columns.0.name: "x"}
@@ -401,14 +403,16 @@ setup:
401403
- method: POST
402404
path: /_query
403405
parameters: [ ]
404-
capabilities: [ parameter_for_limit ]
406+
capabilities: [ parameter_for_limit, parameter_for_sample ]
405407
reason: "named or positional parameters"
406408

407409
- do:
408410
esql.query:
409411
body:
410-
query: 'from test | eval x = ?, y = ?, z = ?, t = ?, u = ?, v = ? | keep x, y, z, t, u, v | limit ?'
411-
params: [{"n1" : "1"}, {"n2" : 2.0}, {"n3" : null}, {"n4" : true}, {"n5" : 123}, {"n6": 1674835275193}, {"l": 3}]
412+
# The "| sort time" is to work around https://github.com/elastic/elasticsearch/issues/120272
413+
# TODO: remove it when the issue is fixed
414+
query: 'from test | sort time | sample ? | eval x = ?, y = ?, z = ?, t = ?, u = ?, v = ? | keep x, y, z, t, u, v | limit ?'
415+
params: [{"s": 0.99999999999}, {"n1" : "1"}, {"n2" : 2.0}, {"n3" : null}, {"n4" : true}, {"n5" : 123}, {"n6": 1674835275193}, {"l": 3}]
412416

413417
- length: {columns: 6}
414418
- match: {columns.0.name: "x"}
@@ -465,14 +469,16 @@ setup:
465469
- method: POST
466470
path: /_query
467471
parameters: [ ]
468-
capabilities: [ parameter_for_limit ]
472+
capabilities: [ parameter_for_limit, parameter_for_sample ]
469473
reason: "named or positional parameters for field names"
470474

471475
- do:
472476
esql.query:
473477
body:
474-
query: 'from test | stats x = count(?f1), y = sum(?f2) by ?f3 | sort ?f3 | keep ?f3, x, y | limit ?l'
475-
params: [{"f1" : {"identifier" : "time"}}, {"f2" : { "identifier" : "count" }}, {"f3" : { "identifier" : "color"}}, {"l": 3}]
478+
# The "| sort time" is to work around https://github.com/elastic/elasticsearch/issues/120272
479+
# TODO: remove it when the issue is fixed
480+
query: 'from test | sort time | sample ?s | stats x = count(?f1), y = sum(?f2) by ?f3 | sort ?f3 | keep ?f3, x, y | limit ?l'
481+
params: [{"s": 0.99999999999}, {"f1" : {"identifier" : "time"}}, {"f2" : { "identifier" : "count" }}, {"f3" : { "identifier" : "color"}}, {"l": 3}]
476482

477483
- length: {columns: 3}
478484
- match: {columns.0.name: "color"}

0 commit comments

Comments
 (0)