From 82296eb76decdef742cf6bf6ec9f986842fd5806 Mon Sep 17 00:00:00 2001 From: Larisa Motova Date: Wed, 27 Aug 2025 21:59:55 -1000 Subject: [PATCH 1/7] [ES|QL] Add a TS variation of GenerativeIT This commit adds a variation of the GenerativeRestTest in ES|QL that queries indices marked as time series indices and runs time series aggregations on them (amongst all the other commands already supported in the Generative tests) --- .../qa/single_node/GenerativeMetricsIT.java | 43 +++++++ .../rest/generative/EsqlQueryGenerator.java | 119 +++++++++++++++++- .../rest/generative/GenerativeRestTest.java | 12 +- .../command/pipe/ForkGenerator.java | 2 +- .../command/pipe/MetricsStatsGenerator.java | 104 +++++++++++++++ .../command/source/MetricGenerator.java | 54 ++++++++ .../xpack/esql/CsvTestsDataLoader.java | 28 ++++- 7 files changed, 353 insertions(+), 9 deletions(-) create mode 100644 x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/GenerativeMetricsIT.java create mode 100644 x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/MetricsStatsGenerator.java create mode 100644 x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/source/MetricGenerator.java diff --git a/x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/GenerativeMetricsIT.java b/x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/GenerativeMetricsIT.java new file mode 100644 index 0000000000000..9dd060cf5b4ff --- /dev/null +++ b/x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/GenerativeMetricsIT.java @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.esql.qa.single_node; + +import com.carrotsearch.randomizedtesting.annotations.ThreadLeakFilters; + +import org.elasticsearch.test.TestClustersThreadFilter; +import org.elasticsearch.test.cluster.ElasticsearchCluster; +import org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.GenerativeRestTest; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.CommandGenerator; +import org.junit.ClassRule; + +@ThreadLeakFilters(filters = TestClustersThreadFilter.class) +public class GenerativeMetricsIT extends GenerativeRestTest { + @ClassRule + public static ElasticsearchCluster cluster = Clusters.testCluster(); + + @Override + protected String getTestRestCluster() { + return cluster.getHttpAddresses(); + } + + @Override + protected boolean supportsSourceFieldMapping() { + return cluster.getNumNodes() == 1; + } + + @Override + protected CommandGenerator sourceCommand() { + return EsqlQueryGenerator.timeSeriesSourceCommand(); + } + + @Override + protected boolean requiresTimeSeries() { + return true; + } +} diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/EsqlQueryGenerator.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/EsqlQueryGenerator.java index b6cf5ff3a8d15..39b4ba0e48c19 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/EsqlQueryGenerator.java +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/EsqlQueryGenerator.java @@ -19,12 +19,14 @@ import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.KeepGenerator; import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.LimitGenerator; import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.LookupJoinGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.MetricsStatsGenerator; import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.MvExpandGenerator; import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.RenameGenerator; import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.SortGenerator; import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.StatsGenerator; import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.WhereGenerator; import org.elasticsearch.xpack.esql.qa.rest.generative.command.source.FromGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.source.MetricGenerator; import java.util.List; import java.util.Set; @@ -51,6 +53,11 @@ public record QueryExecuted(String query, int depth, List outputSchema, */ static List SOURCE_COMMANDS = List.of(FromGenerator.INSTANCE); + /** + * Commands at the beginning of queries that begin queries on time series indices, eg. TS + */ + static List TIME_SERIES_SOURCE_COMMANDS = List.of(MetricGenerator.INSTANCE); + /** * These are downstream commands, ie. that cannot appear as the first command in a query */ @@ -72,14 +79,42 @@ public record QueryExecuted(String query, int depth, List outputSchema, WhereGenerator.INSTANCE ); + static List TIME_SERIES_PIPE_COMMANDS = List.of( + ChangePointGenerator.INSTANCE, + DissectGenerator.INSTANCE, + DropGenerator.INSTANCE, + EnrichGenerator.INSTANCE, + EvalGenerator.INSTANCE, + ForkGenerator.INSTANCE, + GrokGenerator.INSTANCE, + KeepGenerator.INSTANCE, + LimitGenerator.INSTANCE, + LookupJoinGenerator.INSTANCE, + MetricsStatsGenerator.INSTANCE, + MvExpandGenerator.INSTANCE, + RenameGenerator.INSTANCE, + SortGenerator.INSTANCE, + StatsGenerator.INSTANCE, + WhereGenerator.INSTANCE + ); + public static CommandGenerator sourceCommand() { return randomFrom(SOURCE_COMMANDS); } + public static CommandGenerator timeSeriesSourceCommand() { + return randomFrom(TIME_SERIES_SOURCE_COMMANDS); + } + public static CommandGenerator randomPipeCommandGenerator() { return randomFrom(PIPE_COMMANDS); } + public static CommandGenerator randomMetricsPipeCommandGenerator() { + // todo better way + return randomFrom(TIME_SERIES_PIPE_COMMANDS); + } + public interface Executor { void run(CommandGenerator generator, CommandGenerator.CommandDescription current); @@ -95,7 +130,8 @@ public static void generatePipeline( final int depth, CommandGenerator commandGenerator, final CommandGenerator.QuerySchema schema, - Executor executor + Executor executor, + boolean isTimeSeries ) { CommandGenerator.CommandDescription desc = commandGenerator.generate(List.of(), List.of(), schema); executor.run(commandGenerator, desc); @@ -107,7 +143,7 @@ public static void generatePipeline( if (executor.currentSchema().isEmpty()) { break; } - commandGenerator = EsqlQueryGenerator.randomPipeCommandGenerator(); + commandGenerator = isTimeSeries ? randomMetricsPipeCommandGenerator() : EsqlQueryGenerator.randomPipeCommandGenerator(); desc = commandGenerator.generate(executor.previousCommands(), executor.currentSchema(), schema); if (desc == CommandGenerator.EMPTY_DESCRIPTION) { continue; @@ -217,6 +253,61 @@ public static boolean sortable(Column col) { || col.type.equals("version"); } + public static String metricsAgg(List previousOutput) { + String outerCommand = randomFrom("min", "max", "sum", "count", "avg"); + String innerCommand = switch (randomIntBetween(0, 3)) { + case 0 -> { + // input can be numerics + aggregate_metric_double + String numericPlusAggMetricFieldName = randomMetricsNumericField(previousOutput); + if (numericPlusAggMetricFieldName == null) { + yield null; + } + yield switch ((randomIntBetween(0, 3))) { + case 0 -> "max_over_time(" + numericPlusAggMetricFieldName + ")"; + case 1 -> "min_over_time(" + numericPlusAggMetricFieldName + ")"; + case 2 -> "sum_over_time(" + numericPlusAggMetricFieldName + ")"; + default -> "avg_over_time(" + numericPlusAggMetricFieldName + ")"; + }; + } + case 1 -> { + // input can be a counter + String counterField = randomCounterField(previousOutput); + if (counterField == null) { + yield null; + } + yield "rate(" + counterField + ")"; + } + case 2 -> { + // numerics except aggregate_metric_double + // TODO: move to case 0 when support for aggregate_metric_double is added to these functions + String numericFieldName = randomNumericField(previousOutput); + if (numericFieldName == null) { + yield null; + } + yield (randomBoolean() ? "first_over_time(" : "last_over_time(") + numericFieldName + ")"; + } + default -> { + // TODO: add other types that count_over_time supports + String otherFieldName = randomBoolean() ? randomStringField(previousOutput) : randomNumericOrDateField(previousOutput); + if (otherFieldName == null) { + yield null; + } + if (randomBoolean()) { + yield "count_over_time(" + otherFieldName + ")"; + } else { + yield "count_distinct_over_time(" + otherFieldName + ")"; + // TODO: replace with the below + // yield "count_distinct_over_time(" + otherFieldName + (randomBoolean() ? ", " + randomNonNegativeInt() : "") + ")"; + } + } + }; + if (innerCommand == null) { + // TODO: figure out a default that maybe makes more sense than using a timestamp field + innerCommand = "count_over_time(" + randomDateField(previousOutput) + ")"; + } + return outerCommand + "(" + innerCommand + ")"; + } + public static String agg(List previousOutput) { String name = randomNumericOrDateField(previousOutput); if (name != null && randomBoolean()) { @@ -251,6 +342,30 @@ public static String randomNumericField(List previousOutput) { return randomName(previousOutput, Set.of("long", "integer", "double")); } + public static String randomMetricsNumericField(List previousOutput) { + Set allowedTypes = Set.of("double", "long", "unsigned_long", "integer", "aggregate_metric_double"); + List items = previousOutput.stream() + .filter( + x -> allowedTypes.contains(x.type()) + || (x.type().equals("unsupported") && canBeCastedToAggregateMetricDouble(x.originalTypes())) + ) + .map(Column::name) + .toList(); + if (items.isEmpty()) { + return null; + } + return items.get(randomIntBetween(0, items.size() - 1)); + } + + public static String randomCounterField(List previousOutput) { + return randomName(previousOutput, Set.of("counter_long", "counter_double", "counter_integer")); + } + + private static boolean canBeCastedToAggregateMetricDouble(List types) { + return types.contains("aggregate_metric_double") + && Set.of("double", "long", "unsigned_long", "integer", "aggregate_metric_double").containsAll(types); + } + public static String randomStringField(List previousOutput) { return randomName(previousOutput, Set.of("text", "keyword")); } diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeRestTest.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeRestTest.java index a4e3899ef17bc..f1ce104642dfb 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeRestTest.java +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeRestTest.java @@ -79,6 +79,10 @@ public void setup() throws IOException { protected abstract boolean supportsSourceFieldMapping(); + protected boolean requiresTimeSeries() { + return false; + } + @AfterClass public static void wipeTestData() throws IOException { try { @@ -142,10 +146,14 @@ public List currentSchema() { final List previousCommands = new ArrayList<>(); EsqlQueryGenerator.QueryExecuted previousResult; }; - EsqlQueryGenerator.generatePipeline(MAX_DEPTH, EsqlQueryGenerator.sourceCommand(), mappingInfo, exec); + EsqlQueryGenerator.generatePipeline(MAX_DEPTH, sourceCommand(), mappingInfo, exec, requiresTimeSeries()); } } + protected CommandGenerator sourceCommand() { + return EsqlQueryGenerator.sourceCommand(); + } + private static CommandGenerator.ValidationResult checkResults( List previousCommands, CommandGenerator commandGenerator, @@ -234,7 +242,7 @@ private static List originalTypes(Map x) { } private List availableIndices() throws IOException { - return availableDatasetsForEs(true, supportsSourceFieldMapping(), false).stream() + return availableDatasetsForEs(true, supportsSourceFieldMapping(), false, requiresTimeSeries()).stream() .filter(x -> x.requiresInferenceEndpoint() == false) .map(x -> x.indexName()) .toList(); diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/ForkGenerator.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/ForkGenerator.java index 74c4122156bc6..6cc07a4112e30 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/ForkGenerator.java +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/ForkGenerator.java @@ -114,7 +114,7 @@ public ValidationResult validateOutput( } }; - EsqlQueryGenerator.generatePipeline(3, gen, schema, exec); + EsqlQueryGenerator.generatePipeline(3, gen, schema, exec, false); if (exec.previousCommands().size() > 1) { String previousCmd = exec.previousCommands() .stream() diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/MetricsStatsGenerator.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/MetricsStatsGenerator.java new file mode 100644 index 0000000000000..885c90ed28238 --- /dev/null +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/MetricsStatsGenerator.java @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe; + +import org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.CommandGenerator; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.elasticsearch.test.ESTestCase.randomBoolean; +import static org.elasticsearch.test.ESTestCase.randomIntBetween; +import static org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator.randomDateField; + +public class MetricsStatsGenerator implements CommandGenerator { + + public static final String STATS = "stats"; + public static final CommandGenerator INSTANCE = new MetricsStatsGenerator(); + + @Override + public CommandDescription generate( + List previousCommands, + List previousOutput, + QuerySchema schema + ) { + // generates stats in the form of: + // `STATS some_aggregation(some_field) by optional_grouping_field, non_optional = bucket(time_field, 5minute)` + // where `some_aggregation` can be a time series aggregation, or a regular aggregation + // There is a variable number of aggregations per command + + List nonNull = previousOutput.stream() + .filter(EsqlQueryGenerator::fieldCanBeUsed) + .filter(x -> x.type().equals("null") == false) + .collect(Collectors.toList()); + if (nonNull.isEmpty()) { + return EMPTY_DESCRIPTION; + } + String timestamp = randomDateField(nonNull); + // if there's no timestamp field left, there's nothing to bucket on + if (timestamp == null) { + return EMPTY_DESCRIPTION; + } + + StringBuilder cmd = new StringBuilder(" | stats "); + + // TODO: increase range max to 5 + int nStats = randomIntBetween(1, 2); + for (int i = 0; i < nStats; i++) { + String name; + if (randomBoolean()) { + name = EsqlQueryGenerator.randomIdentifier(); + } else { + name = EsqlQueryGenerator.randomName(previousOutput); + if (name == null) { + name = EsqlQueryGenerator.randomIdentifier(); + } + } + // generate the aggregation + String expression = randomBoolean() ? EsqlQueryGenerator.metricsAgg(nonNull) : EsqlQueryGenerator.agg(nonNull); + if (i > 0) { + cmd.append(","); + } + cmd.append(" "); + cmd.append(name); + cmd.append(" = "); + cmd.append(expression); + } + + cmd.append(" by "); + if (randomBoolean()) { + var col = EsqlQueryGenerator.randomGroupableName(nonNull); + if (col != null) { + cmd.append(col + ", "); + } + } + // TODO: add alternative time buckets + cmd.append( + (randomBoolean() ? EsqlQueryGenerator.randomIdentifier() : EsqlQueryGenerator.randomName(previousOutput)) + + " = bucket(" + + timestamp + + ",1hour)" + ); + return new CommandDescription(STATS, this, cmd.toString(), Map.of()); + } + + @Override + public ValidationResult validateOutput( + List previousCommands, + CommandDescription commandDescription, + List previousColumns, + List> previousOutput, + List columns, + List> output + ) { + // TODO validate columns + return VALIDATION_OK; + } +} diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/source/MetricGenerator.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/source/MetricGenerator.java new file mode 100644 index 0000000000000..17a18815c71ae --- /dev/null +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/source/MetricGenerator.java @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.esql.qa.rest.generative.command.source; + +import org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.CommandGenerator; + +import java.util.List; +import java.util.Map; + +import static org.elasticsearch.test.ESTestCase.randomIntBetween; +import static org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator.indexPattern; + +public class MetricGenerator implements CommandGenerator { + + public static final MetricGenerator INSTANCE = new MetricGenerator(); + + @Override + public CommandDescription generate( + List previousCommands, + List previousOutput, + QuerySchema schema + ) { + StringBuilder result = new StringBuilder("ts "); + int items = randomIntBetween(1, 3); + List availableIndices = schema.baseIndices(); + for (int i = 0; i < items; i++) { + String pattern = indexPattern(availableIndices.get(randomIntBetween(0, availableIndices.size() - 1))); + if (i > 0) { + result.append(","); + } + result.append(pattern); + } + String query = result.toString(); + return new CommandDescription("ts", this, query, Map.of()); + } + + @Override + public ValidationResult validateOutput( + List previousCommands, + CommandDescription command, + List previousColumns, + List> previousOutput, + List columns, + List> output + ) { + return VALIDATION_OK; + } +} diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/CsvTestsDataLoader.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/CsvTestsDataLoader.java index c56ed4d489843..fa88984a88f4c 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/CsvTestsDataLoader.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/CsvTestsDataLoader.java @@ -320,7 +320,7 @@ public static void main(String[] args) throws IOException { } try (RestClient client = builder.build()) { - loadDataSetIntoEs(client, true, true, false, (restClient, indexName, indexMapping, indexSettings) -> { + loadDataSetIntoEs(client, true, true, false, false, (restClient, indexName, indexMapping, indexSettings) -> { // don't use ESRestTestCase methods here or, if you do, test running the main method before making the change StringBuilder jsonBody = new StringBuilder("{"); if (indexSettings != null && indexSettings.isEmpty() == false) { @@ -342,14 +342,16 @@ public static void main(String[] args) throws IOException { public static Set availableDatasetsForEs( boolean supportsIndexModeLookup, boolean supportsSourceFieldMapping, - boolean inferenceEnabled + boolean inferenceEnabled, + boolean requiresTimeSeries ) throws IOException { Set testDataSets = new HashSet<>(); for (TestDataset dataset : CSV_DATASET_MAP.values()) { if ((inferenceEnabled || dataset.requiresInferenceEndpoint == false) && (supportsIndexModeLookup || isLookupDataset(dataset) == false) - && (supportsSourceFieldMapping || isSourceMappingDataset(dataset) == false)) { + && (supportsSourceFieldMapping || isSourceMappingDataset(dataset) == false) + && (requiresTimeSeries == false || isTimeSeries(dataset))) { testDataSets.add(dataset); } } @@ -373,17 +375,34 @@ private static boolean isSourceMappingDataset(TestDataset dataset) throws IOExce return mappingNode.get("_source") != null; } + private static boolean isTimeSeries(TestDataset dataset) throws IOException { + Settings settings = dataset.readSettingsFile(); + String mode = settings.get("index.mode"); + return (mode != null && mode.equalsIgnoreCase("time_series")); + } + public static void loadDataSetIntoEs( RestClient client, boolean supportsIndexModeLookup, boolean supportsSourceFieldMapping, boolean inferenceEnabled + ) throws IOException { + loadDataSetIntoEs(client, supportsIndexModeLookup, supportsSourceFieldMapping, inferenceEnabled, false); + } + + public static void loadDataSetIntoEs( + RestClient client, + boolean supportsIndexModeLookup, + boolean supportsSourceFieldMapping, + boolean inferenceEnabled, + boolean timeSeriesOnly ) throws IOException { loadDataSetIntoEs( client, supportsIndexModeLookup, supportsSourceFieldMapping, inferenceEnabled, + timeSeriesOnly, (restClient, indexName, indexMapping, indexSettings) -> { ESRestTestCase.createIndex(restClient, indexName, indexSettings, indexMapping, null); } @@ -395,12 +414,13 @@ private static void loadDataSetIntoEs( boolean supportsIndexModeLookup, boolean supportsSourceFieldMapping, boolean inferenceEnabled, + boolean timeSeriesOnly, IndexCreator indexCreator ) throws IOException { Logger logger = LogManager.getLogger(CsvTestsDataLoader.class); Set loadedDatasets = new HashSet<>(); - for (var dataset : availableDatasetsForEs(supportsIndexModeLookup, supportsSourceFieldMapping, inferenceEnabled)) { + for (var dataset : availableDatasetsForEs(supportsIndexModeLookup, supportsSourceFieldMapping, inferenceEnabled, timeSeriesOnly)) { load(client, dataset, logger, indexCreator); loadedDatasets.add(dataset.indexName); } From 820f4b71fc0a8e56216d2b7d1faac8c3db6f9562 Mon Sep 17 00:00:00 2001 From: Larisa Motova Date: Wed, 10 Sep 2025 20:42:24 -1000 Subject: [PATCH 2/7] rename Metrics to TimeSeries and add a few more test cases and comments --- .../qa/rest/generative/EsqlQueryGenerator.java | 15 +++++++++------ ...nerator.java => TimeSeriesStatsGenerator.java} | 9 +++++---- ...ricGenerator.java => TimeSeriesGenerator.java} | 4 ++-- 3 files changed, 16 insertions(+), 12 deletions(-) rename x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/{MetricsStatsGenerator.java => TimeSeriesStatsGenerator.java} (92%) rename x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/source/{MetricGenerator.java => TimeSeriesGenerator.java} (92%) diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/EsqlQueryGenerator.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/EsqlQueryGenerator.java index 39b4ba0e48c19..e9fcd50469377 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/EsqlQueryGenerator.java +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/EsqlQueryGenerator.java @@ -19,14 +19,14 @@ import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.KeepGenerator; import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.LimitGenerator; import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.LookupJoinGenerator; -import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.MetricsStatsGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.TimeSeriesStatsGenerator; import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.MvExpandGenerator; import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.RenameGenerator; import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.SortGenerator; import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.StatsGenerator; import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.WhereGenerator; import org.elasticsearch.xpack.esql.qa.rest.generative.command.source.FromGenerator; -import org.elasticsearch.xpack.esql.qa.rest.generative.command.source.MetricGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.source.TimeSeriesGenerator; import java.util.List; import java.util.Set; @@ -56,7 +56,7 @@ public record QueryExecuted(String query, int depth, List outputSchema, /** * Commands at the beginning of queries that begin queries on time series indices, eg. TS */ - static List TIME_SERIES_SOURCE_COMMANDS = List.of(MetricGenerator.INSTANCE); + static List TIME_SERIES_SOURCE_COMMANDS = List.of(TimeSeriesGenerator.INSTANCE); /** * These are downstream commands, ie. that cannot appear as the first command in a query @@ -90,7 +90,7 @@ public record QueryExecuted(String query, int depth, List outputSchema, KeepGenerator.INSTANCE, LimitGenerator.INSTANCE, LookupJoinGenerator.INSTANCE, - MetricsStatsGenerator.INSTANCE, + TimeSeriesStatsGenerator.INSTANCE, MvExpandGenerator.INSTANCE, RenameGenerator.INSTANCE, SortGenerator.INSTANCE, @@ -262,10 +262,12 @@ public static String metricsAgg(List previousOutput) { if (numericPlusAggMetricFieldName == null) { yield null; } - yield switch ((randomIntBetween(0, 3))) { + yield switch ((randomIntBetween(0, 5))) { case 0 -> "max_over_time(" + numericPlusAggMetricFieldName + ")"; case 1 -> "min_over_time(" + numericPlusAggMetricFieldName + ")"; case 2 -> "sum_over_time(" + numericPlusAggMetricFieldName + ")"; + case 3 -> "present_over_time(" + numericPlusAggMetricFieldName + ")"; + case 4 -> "count_over_time(" + numericPlusAggMetricFieldName + ")"; default -> "avg_over_time(" + numericPlusAggMetricFieldName + ")"; }; } @@ -279,7 +281,8 @@ yield switch ((randomIntBetween(0, 3))) { } case 2 -> { // numerics except aggregate_metric_double - // TODO: move to case 0 when support for aggregate_metric_double is added to these functions + // TODO: add to case 0 when support for aggregate_metric_double is added to these functions + // TODO: add to case 1 when support for counters is added String numericFieldName = randomNumericField(previousOutput); if (numericFieldName == null) { yield null; diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/MetricsStatsGenerator.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/TimeSeriesStatsGenerator.java similarity index 92% rename from x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/MetricsStatsGenerator.java rename to x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/TimeSeriesStatsGenerator.java index 885c90ed28238..f55671d07b7b6 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/MetricsStatsGenerator.java +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/TimeSeriesStatsGenerator.java @@ -18,10 +18,10 @@ import static org.elasticsearch.test.ESTestCase.randomIntBetween; import static org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator.randomDateField; -public class MetricsStatsGenerator implements CommandGenerator { +public class TimeSeriesStatsGenerator implements CommandGenerator { public static final String STATS = "stats"; - public static final CommandGenerator INSTANCE = new MetricsStatsGenerator(); + public static final CommandGenerator INSTANCE = new TimeSeriesStatsGenerator(); @Override public CommandDescription generate( @@ -31,8 +31,9 @@ public CommandDescription generate( ) { // generates stats in the form of: // `STATS some_aggregation(some_field) by optional_grouping_field, non_optional = bucket(time_field, 5minute)` - // where `some_aggregation` can be a time series aggregation, or a regular aggregation - // There is a variable number of aggregations per command + // where `some_aggregation` can be a time series aggregation in the form of agg1(agg2_over_time(some_field)), + // or a regular aggregation. + // There is a variable number of aggregations per pipe List nonNull = previousOutput.stream() .filter(EsqlQueryGenerator::fieldCanBeUsed) diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/source/MetricGenerator.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/source/TimeSeriesGenerator.java similarity index 92% rename from x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/source/MetricGenerator.java rename to x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/source/TimeSeriesGenerator.java index 17a18815c71ae..075911cc5a141 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/source/MetricGenerator.java +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/source/TimeSeriesGenerator.java @@ -16,9 +16,9 @@ import static org.elasticsearch.test.ESTestCase.randomIntBetween; import static org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator.indexPattern; -public class MetricGenerator implements CommandGenerator { +public class TimeSeriesGenerator implements CommandGenerator { - public static final MetricGenerator INSTANCE = new MetricGenerator(); + public static final TimeSeriesGenerator INSTANCE = new TimeSeriesGenerator(); @Override public CommandDescription generate( From 5235b809d5a966fa432b56db622181be57bc167e Mon Sep 17 00:00:00 2001 From: Larisa Motova Date: Tue, 16 Sep 2025 00:10:55 -1000 Subject: [PATCH 3/7] mute tests, adjust some things, links to issues --- .../rest/generative/EsqlQueryGenerator.java | 42 +++++++++---------- .../rest/generative/GenerativeRestTest.java | 7 +++- .../pipe/TimeSeriesStatsGenerator.java | 9 ++-- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/EsqlQueryGenerator.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/EsqlQueryGenerator.java index e9fcd50469377..915d150eb8d7d 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/EsqlQueryGenerator.java +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/EsqlQueryGenerator.java @@ -19,11 +19,11 @@ import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.KeepGenerator; import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.LimitGenerator; import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.LookupJoinGenerator; -import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.TimeSeriesStatsGenerator; import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.MvExpandGenerator; import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.RenameGenerator; import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.SortGenerator; import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.StatsGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.TimeSeriesStatsGenerator; import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.WhereGenerator; import org.elasticsearch.xpack.esql.qa.rest.generative.command.source.FromGenerator; import org.elasticsearch.xpack.esql.qa.rest.generative.command.source.TimeSeriesGenerator; @@ -31,6 +31,7 @@ import java.util.List; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.elasticsearch.test.ESTestCase.randomAlphaOfLength; import static org.elasticsearch.test.ESTestCase.randomBoolean; @@ -79,24 +80,10 @@ public record QueryExecuted(String query, int depth, List outputSchema, WhereGenerator.INSTANCE ); - static List TIME_SERIES_PIPE_COMMANDS = List.of( - ChangePointGenerator.INSTANCE, - DissectGenerator.INSTANCE, - DropGenerator.INSTANCE, - EnrichGenerator.INSTANCE, - EvalGenerator.INSTANCE, - ForkGenerator.INSTANCE, - GrokGenerator.INSTANCE, - KeepGenerator.INSTANCE, - LimitGenerator.INSTANCE, - LookupJoinGenerator.INSTANCE, - TimeSeriesStatsGenerator.INSTANCE, - MvExpandGenerator.INSTANCE, - RenameGenerator.INSTANCE, - SortGenerator.INSTANCE, - StatsGenerator.INSTANCE, - WhereGenerator.INSTANCE - ); + static List TIME_SERIES_PIPE_COMMANDS = Stream.concat( + PIPE_COMMANDS.stream(), + Stream.of(TimeSeriesStatsGenerator.INSTANCE) + ).toList(); public static CommandGenerator sourceCommand() { return randomFrom(SOURCE_COMMANDS); @@ -111,7 +98,6 @@ public static CommandGenerator randomPipeCommandGenerator() { } public static CommandGenerator randomMetricsPipeCommandGenerator() { - // todo better way return randomFrom(TIME_SERIES_PIPE_COMMANDS); } @@ -143,7 +129,7 @@ public static void generatePipeline( if (executor.currentSchema().isEmpty()) { break; } - commandGenerator = isTimeSeries ? randomMetricsPipeCommandGenerator() : EsqlQueryGenerator.randomPipeCommandGenerator(); + commandGenerator = isTimeSeries ? randomMetricsPipeCommandGenerator() : randomPipeCommandGenerator(); desc = commandGenerator.generate(executor.previousCommands(), executor.currentSchema(), schema); if (desc == CommandGenerator.EMPTY_DESCRIPTION) { continue; @@ -266,7 +252,12 @@ yield switch ((randomIntBetween(0, 5))) { case 0 -> "max_over_time(" + numericPlusAggMetricFieldName + ")"; case 1 -> "min_over_time(" + numericPlusAggMetricFieldName + ")"; case 2 -> "sum_over_time(" + numericPlusAggMetricFieldName + ")"; - case 3 -> "present_over_time(" + numericPlusAggMetricFieldName + ")"; + case 3 -> { + if (outerCommand.equals("sum") || outerCommand.equals("avg")) { + yield null; + } + yield "present_over_time(" + numericPlusAggMetricFieldName + ")"; + } case 4 -> "count_over_time(" + numericPlusAggMetricFieldName + ")"; default -> "avg_over_time(" + numericPlusAggMetricFieldName + ")"; }; @@ -287,6 +278,13 @@ yield switch ((randomIntBetween(0, 5))) { if (numericFieldName == null) { yield null; } + if (previousOutput.stream() + .noneMatch( + column -> column.name.equals("@timestamp") && (column.type.equals("date_nanos") || column.type.equals("datetime")) + )) { + // first_over_time and last_over_time require @timestamp to be available and be either datetime or date_nanos + yield null; + } yield (randomBoolean() ? "first_over_time(" : "last_over_time(") + numericFieldName + ")"; } default -> { diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeRestTest.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeRestTest.java index 53cee08a87d48..0e5f67186fd06 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeRestTest.java +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeRestTest.java @@ -62,7 +62,12 @@ public abstract class GenerativeRestTest extends ESRestTestCase { "optimized incorrectly due to missing references", // https://github.com/elastic/elasticsearch/issues/131509 // Awaiting fixes for correctness - "Expecting at most \\[.*\\] columns, got \\[.*\\]" // https://github.com/elastic/elasticsearch/issues/129561 + "Expecting at most \\[.*\\] columns, got \\[.*\\]", // https://github.com/elastic/elasticsearch/issues/129561 + + // TS-command tests + "Invalid call to dataType on an unresolved object \\?LASTOVERTIME", // https://github.com/elastic/elasticsearch/issues/134791 + "class org.elasticsearch.compute.data..*Block cannot be cast to class org.elasticsearch.compute.data..*Block", // https://github.com/elastic/elasticsearch/issues/134793 + "Output has changed from \\[.*\\] to \\[.*\\]" // https://github.com/elastic/elasticsearch/issues/134794 ); public static final Set ALLOWED_ERROR_PATTERNS = ALLOWED_ERRORS.stream() diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/TimeSeriesStatsGenerator.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/TimeSeriesStatsGenerator.java index f55671d07b7b6..73cf46fc1e94e 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/TimeSeriesStatsGenerator.java +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/TimeSeriesStatsGenerator.java @@ -81,12 +81,9 @@ public CommandDescription generate( } } // TODO: add alternative time buckets - cmd.append( - (randomBoolean() ? EsqlQueryGenerator.randomIdentifier() : EsqlQueryGenerator.randomName(previousOutput)) - + " = bucket(" - + timestamp - + ",1hour)" - ); + // TODO: replace name of bucket with half chance of being EsqlQueryGenerator.randomName(previousOutput) if + // is fixed https://github.com/elastic/elasticsearch/issues/134796 + cmd.append(EsqlQueryGenerator.randomIdentifier() + " = bucket(" + timestamp + ",1hour)"); return new CommandDescription(STATS, this, cmd.toString(), Map.of()); } From d0e24d179af52eb1a6ba686044b707597c2b2a6b Mon Sep 17 00:00:00 2001 From: Larisa Motova Date: Tue, 16 Sep 2025 00:39:59 -1000 Subject: [PATCH 4/7] add absent_over_time --- .../esql/qa/rest/generative/EsqlQueryGenerator.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/EsqlQueryGenerator.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/EsqlQueryGenerator.java index 915d150eb8d7d..c1557a97cb93c 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/EsqlQueryGenerator.java +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/EsqlQueryGenerator.java @@ -248,7 +248,7 @@ public static String metricsAgg(List previousOutput) { if (numericPlusAggMetricFieldName == null) { yield null; } - yield switch ((randomIntBetween(0, 5))) { + yield switch ((randomIntBetween(0, 6))) { case 0 -> "max_over_time(" + numericPlusAggMetricFieldName + ")"; case 1 -> "min_over_time(" + numericPlusAggMetricFieldName + ")"; case 2 -> "sum_over_time(" + numericPlusAggMetricFieldName + ")"; @@ -258,7 +258,13 @@ yield switch ((randomIntBetween(0, 5))) { } yield "present_over_time(" + numericPlusAggMetricFieldName + ")"; } - case 4 -> "count_over_time(" + numericPlusAggMetricFieldName + ")"; + case 4 -> { + if (outerCommand.equals("sum") || outerCommand.equals("avg")) { + yield null; + } + yield "absent_over_time(" + numericPlusAggMetricFieldName + ")"; + } + case 5 -> "count_over_time(" + numericPlusAggMetricFieldName + ")"; default -> "avg_over_time(" + numericPlusAggMetricFieldName + ")"; }; } From 044e127c6b4c1580416ebcd10066c3aea9192bf6 Mon Sep 17 00:00:00 2001 From: Larisa Motova Date: Wed, 17 Sep 2025 00:47:16 -1000 Subject: [PATCH 5/7] more bug --- .../qa/rest/generative/GenerativeRestTest.java | 4 +++- .../command/pipe/TimeSeriesStatsGenerator.java | 15 ++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeRestTest.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeRestTest.java index 0e5f67186fd06..870c6f30eeeea 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeRestTest.java +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeRestTest.java @@ -67,7 +67,9 @@ public abstract class GenerativeRestTest extends ESRestTestCase { // TS-command tests "Invalid call to dataType on an unresolved object \\?LASTOVERTIME", // https://github.com/elastic/elasticsearch/issues/134791 "class org.elasticsearch.compute.data..*Block cannot be cast to class org.elasticsearch.compute.data..*Block", // https://github.com/elastic/elasticsearch/issues/134793 - "Output has changed from \\[.*\\] to \\[.*\\]" // https://github.com/elastic/elasticsearch/issues/134794 + "Output has changed from \\[.*\\] to \\[.*\\]", // https://github.com/elastic/elasticsearch/issues/134794 + "unsupported logical plan node \\[Join\\]", // https://github.com/elastic/elasticsearch/issues/134882 + "To perform a lookup join with index \\[.*\\], it must be a in lookup index mode" // https://github.com/elastic/elasticsearch/issues/134882 ); public static final Set ALLOWED_ERROR_PATTERNS = ALLOWED_ERRORS.stream() diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/TimeSeriesStatsGenerator.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/TimeSeriesStatsGenerator.java index 73cf46fc1e94e..c75327f2a3cc0 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/TimeSeriesStatsGenerator.java +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/TimeSeriesStatsGenerator.java @@ -48,6 +48,13 @@ public CommandDescription generate( return EMPTY_DESCRIPTION; } + // TODO: Switch back to using nonNull as possible arguments for aggregations. Using the timestamp field in both the bucket as well + // as as an argument in an aggregation causes all sorts of bizarre errors with confusing messages. + List acceptableFields = nonNull.stream() + .filter(c -> c.type().equals("datetime") == false && c.type().equals("date_nanos") == false) + .filter(c -> c.name().equals("@timestamp") == false) + .toList(); + StringBuilder cmd = new StringBuilder(" | stats "); // TODO: increase range max to 5 @@ -57,13 +64,15 @@ public CommandDescription generate( if (randomBoolean()) { name = EsqlQueryGenerator.randomIdentifier(); } else { - name = EsqlQueryGenerator.randomName(previousOutput); + name = EsqlQueryGenerator.randomName(acceptableFields); if (name == null) { name = EsqlQueryGenerator.randomIdentifier(); } } // generate the aggregation - String expression = randomBoolean() ? EsqlQueryGenerator.metricsAgg(nonNull) : EsqlQueryGenerator.agg(nonNull); + String expression = randomBoolean() + ? EsqlQueryGenerator.metricsAgg(acceptableFields) + : EsqlQueryGenerator.agg(acceptableFields); if (i > 0) { cmd.append(","); } @@ -75,7 +84,7 @@ public CommandDescription generate( cmd.append(" by "); if (randomBoolean()) { - var col = EsqlQueryGenerator.randomGroupableName(nonNull); + var col = EsqlQueryGenerator.randomGroupableName(acceptableFields); if (col != null) { cmd.append(col + ", "); } From d9afeb18601e953fb04c4af1b087c8c4ec9de7ea Mon Sep 17 00:00:00 2001 From: Larisa Motova Date: Thu, 18 Sep 2025 00:34:21 -1000 Subject: [PATCH 6/7] remove resolved issues and don't do stats after renaming --- .../rest/generative/EsqlQueryGenerator.java | 24 ++++++++++++++++++- .../rest/generative/GenerativeRestTest.java | 6 ++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/EsqlQueryGenerator.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/EsqlQueryGenerator.java index c1557a97cb93c..114fcde289cd9 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/EsqlQueryGenerator.java +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/EsqlQueryGenerator.java @@ -119,6 +119,7 @@ public static void generatePipeline( Executor executor, boolean isTimeSeries ) { + boolean canGenerateTimeSeries = isTimeSeries; CommandGenerator.CommandDescription desc = commandGenerator.generate(List.of(), List.of(), schema); executor.run(commandGenerator, desc); if (executor.continueExecuting() == false) { @@ -129,7 +130,28 @@ public static void generatePipeline( if (executor.currentSchema().isEmpty()) { break; } - commandGenerator = isTimeSeries ? randomMetricsPipeCommandGenerator() : randomPipeCommandGenerator(); + boolean commandAllowed = false; + while (commandAllowed == false) { + commandGenerator = isTimeSeries && canGenerateTimeSeries + ? randomMetricsPipeCommandGenerator() + : randomPipeCommandGenerator(); + if (isTimeSeries == false) { + commandAllowed = true; + } else { + if (commandGenerator.equals(TimeSeriesStatsGenerator.INSTANCE) || commandGenerator.equals(StatsGenerator.INSTANCE)) { + if (canGenerateTimeSeries) { + canGenerateTimeSeries = false; + commandAllowed = true; + } + } else if (commandGenerator.equals(RenameGenerator.INSTANCE)) { + // https://github.com/elastic/elasticsearch/issues/134994 + canGenerateTimeSeries = false; + commandAllowed = true; + } else { + commandAllowed = true; + } + } + } desc = commandGenerator.generate(executor.previousCommands(), executor.currentSchema(), schema); if (desc == CommandGenerator.EMPTY_DESCRIPTION) { continue; diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeRestTest.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeRestTest.java index 870c6f30eeeea..2c9abd8a58fd1 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeRestTest.java +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeRestTest.java @@ -65,11 +65,11 @@ public abstract class GenerativeRestTest extends ESRestTestCase { "Expecting at most \\[.*\\] columns, got \\[.*\\]", // https://github.com/elastic/elasticsearch/issues/129561 // TS-command tests + "time-series .* the first aggregation .* is not allowed", + "count_star .* can't be used with TS command", "Invalid call to dataType on an unresolved object \\?LASTOVERTIME", // https://github.com/elastic/elasticsearch/issues/134791 "class org.elasticsearch.compute.data..*Block cannot be cast to class org.elasticsearch.compute.data..*Block", // https://github.com/elastic/elasticsearch/issues/134793 - "Output has changed from \\[.*\\] to \\[.*\\]", // https://github.com/elastic/elasticsearch/issues/134794 - "unsupported logical plan node \\[Join\\]", // https://github.com/elastic/elasticsearch/issues/134882 - "To perform a lookup join with index \\[.*\\], it must be a in lookup index mode" // https://github.com/elastic/elasticsearch/issues/134882 + "Output has changed from \\[.*\\] to \\[.*\\]" // https://github.com/elastic/elasticsearch/issues/134794 ); public static final Set ALLOWED_ERROR_PATTERNS = ALLOWED_ERRORS.stream() From f775ea81b4d11393ec47f6489c1f699b15cb4c33 Mon Sep 17 00:00:00 2001 From: Larisa Motova Date: Thu, 18 Sep 2025 08:05:09 -1000 Subject: [PATCH 7/7] add additional allowed error --- .../xpack/esql/qa/rest/generative/GenerativeRestTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeRestTest.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeRestTest.java index 2c9abd8a58fd1..95a14f183416a 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeRestTest.java +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeRestTest.java @@ -67,6 +67,7 @@ public abstract class GenerativeRestTest extends ESRestTestCase { // TS-command tests "time-series .* the first aggregation .* is not allowed", "count_star .* can't be used with TS command", + "time_series aggregate.* can only be used with the TS command", "Invalid call to dataType on an unresolved object \\?LASTOVERTIME", // https://github.com/elastic/elasticsearch/issues/134791 "class org.elasticsearch.compute.data..*Block cannot be cast to class org.elasticsearch.compute.data..*Block", // https://github.com/elastic/elasticsearch/issues/134793 "Output has changed from \\[.*\\] to \\[.*\\]" // https://github.com/elastic/elasticsearch/issues/134794