Skip to content

Commit e1fc706

Browse files
costinfelixbarny
authored andcommitted
PromQL Support in ESQL (elastic#137988)
* Introduce PromQL command Add PromQL support in ESQL under the PROMQL command. This commit includes the grammar, associated logical plan and basic rules for performing translation, from filtering to stats transpilation. * Grammar Stand-alone grammar definition and parser, currently 'embedded' inside ESQL (the current scenario). Grammar include to pass the valid tests (~200 queries) from Prometheus 3.5; the invalid ones are disabled for now since the parser needs to validate the syntax using * Transpilation Implement basic PromQL plan translation: Selector → Filter with label matcher conditions RangeSelector → Filter + Bucket for time bucketing WithinSeriesAggregate → ESQL function over selector AcrossSeriesAggregate → TimeSeriesAggregate with groupings --------- Co-authored-by: Felix Barnsteiner <[email protected]>
1 parent 74ff953 commit e1fc706

File tree

88 files changed

+16400
-2915
lines changed

Some content is hidden

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

88 files changed

+16400
-2915
lines changed

.gitattributes

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ CHANGELOG.asciidoc merge=union
44
# Windows
55
build-tools-internal/src/test/resources/org/elasticsearch/gradle/internal/release/*.asciidoc text eol=lf
66

7+
# ESQL parsing and source generated related assets
78
x-pack/plugin/esql/compute/src/main/generated/** linguist-generated=true
89
x-pack/plugin/esql/compute/src/main/generated-src/** linguist-generated=true
910
x-pack/plugin/esql/src/main/antlr/*.tokens linguist-generated=true
1011
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/*.interp linguist-generated=true
11-
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseLexer*.java linguist-generated=true
12-
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParser*.java linguist-generated=true
12+
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/*BaseLexer*.java linguist-generated=true
13+
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/*BaseParser*.java linguist-generated=true
1314
x-pack/plugin/esql/src/main/generated/** linguist-generated=true
1415
x-pack/plugin/esql/src/main/generated-src/** linguist-generated=true
1516

build-tools-internal/src/main/resources/checkstyle_suppressions.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<suppress files="modules[/\\]lang-painless[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]painless[/\\]antlr[/\\]SuggestLexer\.java" checks="." />
1313
<suppress files="plugin[/\\]sql[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]sql[/\\]parser[/\\]SqlBase(Base(Listener|Visitor)|Lexer|Listener|Parser|Visitor).java" checks="." />
1414
<suppress files="plugin[/\\]eql[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]eql[/\\]parser[/\\]EqlBase(Base(Listener|Visitor)|Lexer|Listener|Parser|Visitor).java" checks="." />
15-
<suppress files="plugin[/\\]esql[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]esql[/\\]parser[/\\]EsqlBase(Parser|Lexer).*.java" checks="." />
15+
<suppress files="plugin[/\\]esql[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]xpack[/\\]esql[/\\]parser[/\\](EsqlBase|PromqlBase)(Parser|Lexer).*.java" checks="." />
1616
<suppress files="x-pack[/\\]plugin[/\\]otel-data[/\\]build[/\\]generated[/\\]sources[/\\]" checks="." />
1717

1818
<!-- JNA requires the no-argument constructor on JNAKernel32Library.SizeT to be public-->

x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/Literal.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import java.io.IOException;
2424
import java.time.Duration;
25+
import java.time.Instant;
2526
import java.util.Collection;
2627
import java.util.Objects;
2728

@@ -217,6 +218,10 @@ public static Literal timeDuration(Source source, Duration literal) {
217218
return new Literal(source, literal, DataType.TIME_DURATION);
218219
}
219220

221+
public static Literal dateTime(Source source, Instant literal) {
222+
return new Literal(source, literal, DataType.DATETIME);
223+
}
224+
220225
public static Literal integer(Source source, Integer literal) {
221226
return new Literal(source, literal, INTEGER);
222227
}
@@ -229,6 +234,10 @@ public static Literal fromLong(Source source, Long literal) {
229234
return new Literal(source, literal, LONG);
230235
}
231236

237+
public static Expression fromBoolean(Source source, Boolean literal) {
238+
return new Literal(source, literal, DataType.BOOLEAN);
239+
}
240+
232241
private static BytesRef longAsWKB(DataType dataType, long encoded) {
233242
return dataType == GEO_POINT ? GEO.longAsWkb(encoded) : CARTESIAN.longAsWkb(encoded);
234243
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ public static Number mod(Number l, Number r) {
148148
return l.intValue() % r.intValue();
149149
}
150150

151-
static Number negate(Number n) {
151+
public static Number negate(Number n) {
152152
if (n == null) {
153153
return null;
154154
}

x-pack/plugin/esql/build.gradle

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,8 @@ pluginManager.withPlugin('com.diffplug.spotless') {
332332
// for some reason "${outputPath}/EsqlBaseParser*.java" does not match the same files...
333333
targetExclude "src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseLexer*.java",
334334
"src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParser*.java",
335+
"src/main/java/org/elasticsearch/xpack/esql/parser/PromqlBaseLexer*.java",
336+
"src/main/java/org/elasticsearch/xpack/esql/parser/PromqlBaseParser*.java",
335337
"src/main/generated/**/*.java",
336338
"src/main/generated-src/generated/**/*.java"
337339
toggleOffOn('begin generated imports', 'end generated imports')
@@ -345,6 +347,7 @@ tasks.register("cleanGenerated", Delete) {
345347
}
346348
delete fileTree(outputPath) {
347349
include 'EsqlBase*.java'
350+
include 'PromqlBase*.java'
348351
}
349352
}
350353

@@ -384,52 +387,93 @@ tasks.register("regenParser", JavaExec) {
384387
"${file(grammarPath)}/EsqlBaseParser.g4"
385388
}
386389

390+
tasks.register("regenPromqlLexer", JavaExec) {
391+
dependsOn "cleanGenerated"
392+
mainClass = 'org.antlr.v4.Tool'
393+
classpath = configurations.regenerate
394+
systemProperty 'file.encoding', 'UTF-8'
395+
systemProperty 'user.language', 'en'
396+
systemProperty 'user.country', 'US'
397+
systemProperty 'user.variant', ''
398+
args '-Werror',
399+
'-package', 'org.elasticsearch.xpack.esql.parser',
400+
'-listener',
401+
'-visitor',
402+
'-lib', "${file(grammarPath)}",
403+
'-o', outputPath,
404+
"${file(grammarPath)}/PromqlBaseLexer.g4"
405+
}
406+
407+
tasks.register("regenPromqlParser", JavaExec) {
408+
dependsOn "cleanGenerated"
409+
dependsOn "regenPromqlLexer"
410+
mainClass = 'org.antlr.v4.Tool'
411+
classpath = configurations.regenerate
412+
systemProperty 'file.encoding', 'UTF-8'
413+
systemProperty 'user.language', 'en'
414+
systemProperty 'user.country', 'US'
415+
systemProperty 'user.variant', ''
416+
args '-Werror',
417+
'-package', 'org.elasticsearch.xpack.esql.parser',
418+
'-listener',
419+
'-visitor',
420+
'-lib', outputPath,
421+
'-lib', "${file(grammarPath)}",
422+
'-o', outputPath,
423+
"${file(grammarPath)}/PromqlBaseParser.g4"
424+
}
425+
387426
tasks.register("regen") {
388427
dependsOn "regenParser"
428+
dependsOn "regenPromqlParser"
389429
doLast {
390430
// moves token files to grammar directory for use with IDE's
391431
ant.move(file: "${outputPath}/EsqlBaseLexer.tokens", toDir: grammarPath)
392432
ant.move(file: "${outputPath}/EsqlBaseParser.tokens", toDir: grammarPath)
433+
ant.move(file: "${outputPath}/PromqlBaseLexer.tokens", toDir: grammarPath)
434+
ant.move(file: "${outputPath}/PromqlBaseParser.tokens", toDir: grammarPath)
435+
393436
// make the generated classes package private
394437
ant.replaceregexp(
395-
match: 'public ((interface|class) \\QEsqlBase(Parser|Lexer)\\E\\w+)',
438+
match: 'public ((interface|class) \\Q(Es|Prom)qlBase(Parser|Lexer)\\E\\w+)',
396439
replace: '\\1',
397440
encoding: 'UTF-8'
398441
) {
399-
fileset(dir: outputPath, includes: 'EsqlBase*.java')
442+
fileset(dir: outputPath, includes: '*qlBase*.java')
400443
}
401444
// nuke timestamps/filenames in generated files
402445
ant.replaceregexp(
403446
match: '\\Q// Generated from \\E.*',
404447
replace: '\\/\\/ ANTLR GENERATED CODE: DO NOT EDIT',
405448
encoding: 'UTF-8'
406449
) {
407-
fileset(dir: outputPath, includes: 'EsqlBase*.java')
450+
fileset(dir: outputPath, includes: '*qlBase*.java')
408451
}
409452
// remove tabs in antlr generated files
410453
ant.replaceregexp(match: '\t', flags: 'g', replace: ' ', encoding: 'UTF-8') {
411-
fileset(dir: outputPath, includes: 'EsqlBase*.java')
454+
fileset(dir: outputPath, includes: '*qlBase*.java')
412455
}
413456
// suppress this-escape warnings on EsqlBaseLexer
414457
ant.replaceregexp(
415-
match: 'public EsqlBaseLexer',
416-
replace: '@SuppressWarnings("this-escape")${line.separator} public EsqlBaseLexer',
458+
match: 'public ((Es|Prom)qlBaseLexer)',
459+
replace: '@SuppressWarnings("this-escape")${line.separator} public \\1',
417460
encoding: 'UTF-8'
418461
) {
419-
fileset(dir: outputPath, includes: 'EsqlBaseLexer.java')
462+
fileset(dir: outputPath, includes: '*qlBaseLexer.java')
420463
}
464+
421465
// suppress this-escape warnings on all internal EsqlBaseParser class constructores
422466
ant.replaceregexp(
423467
match: '([ ]+)public ([A-Z][a-z]+[a-z,A-Z]+\\()',
424468
flags: 'g',
425469
replace: '\\1@SuppressWarnings("this-escape")${line.separator}\\1public \\2',
426470
encoding: 'UTF-8'
427471
) {
428-
fileset(dir: outputPath, includes: 'EsqlBaseParser.java')
472+
fileset(dir: outputPath, includes: '*qlBaseParser.java')
429473
}
430474
// fix line endings
431475
ant.fixcrlf(srcdir: outputPath, eol: 'lf') {
432-
patternset(includes: 'EsqlBase*.java')
476+
patternset(includes: '*qlBase*.java')
433477
}
434478
}
435479
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ protected void shouldSkipTest(String testName) throws IOException {
6060
testCase.requiredCapabilities.contains(SUBQUERY_IN_FROM_COMMAND.capabilityName())
6161
);
6262

63+
assumeFalse("Tests using PROMQL are not supported for now", testCase.requiredCapabilities.contains(PROMQL_V0.capabilityName()));
64+
6365
assumeTrue("Cluster needs to support FORK", hasCapabilities(adminClient(), List.of(FORK_V9.capabilityName())));
6466
}
6567
}

x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries-avg-over-time.csv-spec

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,25 @@ cost:double | time_bucket:datetime
1717
36.375 | 2024-05-10T00:11:00.000Z
1818
;
1919

20+
avg_over_time_of_double_no_grouping_single_bucket
21+
required_capability: ts_command_v0
22+
required_capability: tbucket
23+
TS k8s
24+
| STATS cost=sum(avg_over_time(network.cost)) BY time_bucket = tbucket(1h);
25+
26+
cost:double | time_bucket:datetime
27+
56.32254035241995 | 2024-05-10T00:00:00.000Z
28+
;
29+
30+
avg_over_time_of_double_no_grouping_single_bucket_promql
31+
required_capability: promql_v0
32+
TS k8s
33+
| PROMQL step 1h (sum(avg_over_time(network.cost[1h])));
34+
35+
sum(avg_over_time(network.cost[1h])):double | TBUCKET:date
36+
56.32254035241995 | 2024-05-10T00:00:00.000Z
37+
;
38+
2039
avg_over_time_of_integer
2140
required_capability: ts_command_v0
2241
TS k8s | STATS clients = avg(avg_over_time(network.eth0.currently_connected_clients)) BY time_bucket = bucket(@timestamp,1minute) | SORT time_bucket | LIMIT 10;

x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries.csv-spec

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,25 @@ max(rate(network.total_bytes_in)): double | time_bucket:date
132132
23.702205882352942 | 2024-05-10T00:15:00.000Z
133133
;
134134

135+
oneRateWithSingleTBucket
136+
required_capability: ts_command_v0
137+
required_capability: tbucket
138+
TS k8s
139+
| STATS max(rate(network.total_bytes_in)) BY time_bucket = tbucket(1h);
140+
141+
max(rate(network.total_bytes_in)): double | time_bucket:date
142+
4.379885057471264 | 2024-05-10T00:00:00.000Z
143+
;
144+
145+
oneRateWithSingleTBucketPromql
146+
required_capability: promql_v0
147+
TS k8s
148+
| PROMQL step 1h (max(rate(network.total_bytes_in[1h])));
149+
150+
max(rate(network.total_bytes_in[1h])):double | TBUCKET:date
151+
4.379885057471264 | 2024-05-10T00:00:00.000Z
152+
;
153+
135154
twoRatesWithBucket
136155
required_capability: ts_command_v0
137156
TS k8s | STATS max(rate(network.total_bytes_in)), sum(rate(network.total_bytes_in)) BY time_bucket = bucket(@timestamp,5minute) | SORT time_bucket DESC | LIMIT 3;

0 commit comments

Comments
 (0)