Skip to content

Commit f934369

Browse files
committed
ES|QL: Address PR review comments
- Add more tests (in TS mode and in Standard mode) - Add TestClassSignatureTypes to fix docs generation when there are more than on constructors and not all parameters are exposed publicly. - Improve TBucket types resolution - Add SubstituteSurrogateExpressions before TranslateTimeSeriesAggregate to substitute TBucket by Bucket before it is used in TimeSeriesAggregate Closes #131068
1 parent b0d5599 commit f934369

File tree

15 files changed

+209
-68
lines changed

15 files changed

+209
-68
lines changed

docs/reference/query-languages/esql/_snippets/functions/types/tbucket.md

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

docs/reference/query-languages/esql/kibana/definition/functions/tbucket.json

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"properties": {
3+
"@timestamp": {
4+
"type": "boolean"
5+
},
6+
"client_ip": {
7+
"type": "ip"
8+
},
9+
"event_duration": {
10+
"type": "long"
11+
},
12+
"message": {
13+
"type": "keyword"
14+
}
15+
}
16+
}

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,12 @@ TS k8s
8989
// end::rate[]
9090
| SORT time_bucket DESC | LIMIT 2;
9191

92+
oneRateWithTBucket
93+
required_capability: metrics_command
94+
TS k8s
95+
| STATS max(rate(network.total_bytes_in)) BY time_bucket = tbucket(5minute)
96+
| SORT time_bucket DESC | LIMIT 2;
97+
9298
// tag::rate-result[]
9399
max(rate(network.total_bytes_in)): double | time_bucket:date
94100
6.980660660660663 | 2024-05-10T00:20:00.000Z
@@ -106,6 +112,15 @@ max(rate(network.total_bytes_in)):double | sum(rate(network.total_bytes_in)):dou
106112
14.97039381153305 | 63.00652190262822 | 2024-05-10T00:10:00.000Z
107113
;
108114

115+
twoRatesWithTBucket
116+
required_capability: metrics_command
117+
TS k8s | STATS max(rate(network.total_bytes_in)), sum(rate(network.total_bytes_in)) BY time_bucket = tbucket(5minute) | SORT time_bucket DESC | LIMIT 3;
118+
119+
max(rate(network.total_bytes_in)):double | sum(rate(network.total_bytes_in)):double | time_bucket:datetime
120+
6.980660660660663 | 23.959973363184154 | 2024-05-10T00:20:00.000Z
121+
23.702205882352942 | 94.9517511187984 | 2024-05-10T00:15:00.000Z
122+
14.97039381153305 | 63.00652190262822 | 2024-05-10T00:10:00.000Z
123+
;
109124

110125
oneRateWithBucketAndCluster
111126
required_capability: metrics_command

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/grouping/TBucket.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.List;
2626

2727
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.DEFAULT;
28+
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.SECOND;
2829
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isType;
2930

3031
/**
@@ -97,7 +98,9 @@ protected TypeResolution resolveType() {
9798
if (childrenResolved() == false) {
9899
return new TypeResolution("Unresolved children");
99100
}
100-
return isType(buckets, DataType::isTemporalAmount, sourceText(), DEFAULT, "date_period", "time_duration");
101+
return isType(buckets, DataType::isTemporalAmount, sourceText(), DEFAULT, "date_period", "time_duration").and(
102+
isType(timestamp, dt -> dt == DataType.DATETIME || dt == DataType.DATE_NANOS, sourceText(), SECOND, "date_nanos or datetime")
103+
);
101104
}
102105

103106
@Override

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizer.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ protected static Batch<LogicalPlan> substitutions() {
142142
new ReplaceAggregateAggExpressionWithEval(),
143143
// lastly replace surrogate functions
144144
new SubstituteSurrogateAggregations(),
145+
new SubstituteSurrogateExpressions(),
145146
// translate metric aggregates after surrogate substitution and replace nested expressions with eval (again)
146147
new TranslateTimeSeriesAggregate(),
147148
new PruneUnusedIndexMode(),

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
@@ -4117,7 +4117,7 @@ public void testGroupingOverridesInInlinestats() {
41174117
""", "Found 1 problem\n" + "line 2:49: Unknown column [x]", "mapping-default.json");
41184118
}
41194119

4120-
public void testTBucketWithIntervalInStringInBothAggregationAndGrouping() {
4120+
public void testTBucketWithDatePeriodInBothAggregationAndGrouping() {
41214121
LogicalPlan plan = analyze("""
41224122
FROM sample_data
41234123
| STATS min = MIN(@timestamp), max = MAX(@timestamp) BY bucket = TBUCKET(1 week)

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ public class VerifierTests extends ESTestCase {
7676
private static final EsqlParser parser = new EsqlParser();
7777
private final Analyzer defaultAnalyzer = AnalyzerTestUtils.expandedDefaultAnalyzer();
7878
private final Analyzer fullTextAnalyzer = AnalyzerTestUtils.analyzer(loadMapping("mapping-full_text_search.json", "test"));
79+
private final Analyzer sampleDataAnalyzer = AnalyzerTestUtils.analyzer(loadMapping("mapping-sample_data.json", "test"));
80+
private final Analyzer oddSampleDataAnalyzer = AnalyzerTestUtils.analyzer(loadMapping("mapping-odd-timestamp.json", "test"));
7981
private final Analyzer tsdb = AnalyzerTestUtils.analyzer(AnalyzerTestUtils.tsdbIndexResolution());
8082

8183
private final List<String> TIME_DURATIONS = List.of("millisecond", "second", "minute", "hour");
@@ -2501,6 +2503,38 @@ public void testVectorSimilarityFunctionsNullArgs() throws Exception {
25012503
}
25022504
}
25032505

2506+
public void testInvalidTBucketCalls() {
2507+
assertThat(error("from test | stats max(emp_no) by tbucket(1 hour)"), equalTo("1:34: Unknown column [@timestamp]"));
2508+
assertThat(
2509+
error("from test | stats max(event_duration) by tbucket()", sampleDataAnalyzer, ParsingException.class),
2510+
equalTo("1:42: error building [tbucket]: expects exactly one argument")
2511+
);
2512+
assertThat(
2513+
error("from test | stats max(event_duration) by tbucket(\"@tbucket\", 1 hour)", sampleDataAnalyzer, ParsingException.class),
2514+
equalTo("1:42: error building [tbucket]: expects exactly one argument")
2515+
);
2516+
assertThat(
2517+
error("from test | stats max(event_duration) by tbucket(1 hr)", sampleDataAnalyzer, ParsingException.class),
2518+
equalTo("1:50: Unexpected temporal unit: 'hr'")
2519+
);
2520+
assertThat(
2521+
error("from test | stats max(event_duration) by tbucket(\"1\")", sampleDataAnalyzer),
2522+
equalTo("1:42: argument of [tbucket(\"1\")] must be [date_period or time_duration], found value [\"1\"] type [keyword]")
2523+
);
2524+
assertThat(
2525+
error("from test | stats max(event_duration) by tbucket(\"1 hour\")", oddSampleDataAnalyzer),
2526+
equalTo(
2527+
"1:42: second argument of [tbucket(\"1 hour\")] must be [date_nanos or datetime], found value [@timestamp] type [boolean]"
2528+
)
2529+
);
2530+
for (String interval : List.of("1 minu", "1 dy", "1.5 minutes", "0.5 days", "minutes 1", "day 5")) {
2531+
assertThat(
2532+
error("from test | stats max(event_duration) by tbucket(\"" + interval + "\")", sampleDataAnalyzer),
2533+
containsString("1:50: Cannot convert string [" + interval + "] to [DATE_PERIOD or TIME_DURATION]")
2534+
);
2535+
}
2536+
}
2537+
25042538
private void checkVectorSimilarityFunctionsNullArgs(String functionInvocation, String argOrdinal) throws Exception {
25052539
assertThat(
25062540
error("from test | eval similarity = " + functionInvocation, fullTextAnalyzer),

x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractFunctionTestCase.java

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@
6666
import java.util.ArrayList;
6767
import java.util.Arrays;
6868
import java.util.Collections;
69-
import java.util.HashMap;
7069
import java.util.HashSet;
7170
import java.util.List;
7271
import java.util.Locale;
@@ -758,13 +757,13 @@ public static void testFunctionInfo() {
758757
for (int i = 0; i < args.size(); i++) {
759758
typesFromSignature.add(new HashSet<>());
760759
}
761-
for (Map.Entry<List<DataType>, DataType> entry : signatures(testClass).entrySet()) {
762-
List<DataType> types = entry.getKey();
760+
for (TestClassSignatureTypes entry : signatures(testClass)) {
761+
List<DataType> types = entry.argTypes();
763762
for (int i = 0; i < args.size() && i < types.size(); i++) {
764763
typesFromSignature.get(i).add(types.get(i).esNameIfPossible());
765764
}
766-
if (DataType.UNDER_CONSTRUCTION.containsKey(entry.getValue()) == false) {
767-
returnFromSignature.add(entry.getValue().esNameIfPossible());
765+
if (DataType.UNDER_CONSTRUCTION.containsKey(entry.returnType()) == false) {
766+
returnFromSignature.add(entry.returnType().esNameIfPossible());
768767
}
769768
}
770769

@@ -838,7 +837,8 @@ public static void testFunctionLicenseChecks() throws Exception {
838837
TestCheckLicense checkLicense = new TestCheckLicense();
839838

840839
// Go through all signatures and assert that the license is as expected
841-
signatures(testClass).forEach((signature, returnType) -> {
840+
signatures(testClass).forEach((signatureItem) -> {
841+
List<DataType> signature = signatureItem.argTypes();
842842
try {
843843
License.OperationMode license = licenseChecker.invoke(signature);
844844
assertNotNull("License should not be null", license);
@@ -933,14 +933,14 @@ protected final void assertTypeResolutionFailure(Expression expression) {
933933
/**
934934
* Unique signatures in this test’s parameters.
935935
*/
936-
private static Map<List<DataType>, DataType> signatures;
936+
private static Set<TestClassSignatureTypes> signatures;
937937

938-
public static Map<List<DataType>, DataType> signatures(Class<?> testClass) {
938+
public static Set<TestClassSignatureTypes> signatures(Class<?> testClass) {
939939
if (signatures != null && classGeneratingSignatures == testClass) {
940940
return signatures;
941941
}
942942
classGeneratingSignatures = testClass;
943-
signatures = new HashMap<>();
943+
signatures = new HashSet<>();
944944
Set<Method> paramsFactories = new ClassModel(testClass).getAnnotatedLeafMethods(ParametersFactory.class).keySet();
945945
assertThat(paramsFactories, hasSize(1));
946946
Method paramsFactory = paramsFactories.iterator().next();
@@ -960,7 +960,7 @@ public static Map<List<DataType>, DataType> signatures(Class<?> testClass) {
960960
continue;
961961
}
962962
List<DataType> types = tc.getData().stream().map(TestCaseSupplier.TypedData::type).toList();
963-
signatures.putIfAbsent(signatureTypes(testClass, types), tc.expectedType());
963+
signatures.add(new TestClassSignatureTypes(signatureTypes(testClass, types), tc.expectedType()));
964964
}
965965
return signatures;
966966
}

0 commit comments

Comments
 (0)