From b4f88e8e538feb79cfa692d790fef431ec56d4dc Mon Sep 17 00:00:00 2001 From: Luigi Dell'Aquila Date: Sun, 14 Sep 2025 12:55:25 +0200 Subject: [PATCH 1/2] ES|QL: refactor query generator and move it to testFixtures --- .../rest/generative/GenerativeRestTest.java | 56 +++++++------- .../esql/qa/rest/generative/README.asciidoc | 15 +--- .../xpack/esql/generator/Column.java | 12 +++ .../esql/generator}/EsqlQueryGenerator.java | 77 +++++++++---------- .../xpack/esql/generator/LookupIdx.java | 12 +++ .../xpack/esql/generator/LookupIdxColumn.java | 10 +++ .../xpack/esql/generator/QueryExecuted.java | 12 +++ .../xpack/esql/generator/QueryExecutor.java | 12 +++ .../xpack/esql/generator/README.asciidoc | 24 ++++++ .../generator}/command/CommandGenerator.java | 46 ++++++----- .../command/pipe/ChangePointGenerator.java | 25 +++--- .../command/pipe/DissectGenerator.java | 17 ++-- .../command/pipe/DropGenerator.java | 19 +++-- .../command/pipe/EnrichGenerator.java | 17 ++-- .../command/pipe/EvalGenerator.java | 19 +++-- .../command/pipe/ForkGenerator.java | 40 +++++----- .../command/pipe/GrokGenerator.java | 17 ++-- .../command/pipe/KeepGenerator.java | 17 ++-- .../command/pipe/LimitGenerator.java | 16 ++-- .../command/pipe/LookupJoinGenerator.java | 29 +++---- .../command/pipe/MvExpandGenerator.java | 17 ++-- .../command/pipe/RenameGenerator.java | 24 +++--- .../command/pipe/SortGenerator.java | 17 ++-- .../command/pipe/StatsGenerator.java | 19 +++-- .../command/pipe/WhereGenerator.java | 19 +++-- .../command/source/FromGenerator.java | 20 ++--- 26 files changed, 361 insertions(+), 247 deletions(-) create mode 100644 x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/Column.java rename x-pack/plugin/esql/qa/{server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative => testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator}/EsqlQueryGenerator.java (80%) create mode 100644 x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/LookupIdx.java create mode 100644 x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/LookupIdxColumn.java create mode 100644 x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/QueryExecuted.java create mode 100644 x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/QueryExecutor.java create mode 100644 x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/README.asciidoc rename x-pack/plugin/esql/qa/{server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative => testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator}/command/CommandGenerator.java (81%) rename x-pack/plugin/esql/qa/{server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative => testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator}/command/pipe/ChangePointGenerator.java (66%) rename x-pack/plugin/esql/qa/{server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative => testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator}/command/pipe/DissectGenerator.java (83%) rename x-pack/plugin/esql/qa/{server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative => testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator}/command/pipe/DropGenerator.java (85%) rename x-pack/plugin/esql/qa/{server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative => testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator}/command/pipe/EnrichGenerator.java (78%) rename x-pack/plugin/esql/qa/{server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative => testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator}/command/pipe/EvalGenerator.java (85%) rename x-pack/plugin/esql/qa/{server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative => testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator}/command/pipe/ForkGenerator.java (81%) rename x-pack/plugin/esql/qa/{server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative => testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator}/command/pipe/GrokGenerator.java (83%) rename x-pack/plugin/esql/qa/{server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative => testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator}/command/pipe/KeepGenerator.java (84%) rename x-pack/plugin/esql/qa/{server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative => testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator}/command/pipe/LimitGenerator.java (77%) rename x-pack/plugin/esql/qa/{server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative => testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator}/command/pipe/LookupJoinGenerator.java (80%) rename x-pack/plugin/esql/qa/{server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative => testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator}/command/pipe/MvExpandGenerator.java (73%) rename x-pack/plugin/esql/qa/{server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative => testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator}/command/pipe/RenameGenerator.java (82%) rename x-pack/plugin/esql/qa/{server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative => testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator}/command/pipe/SortGenerator.java (78%) rename x-pack/plugin/esql/qa/{server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative => testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator}/command/pipe/StatsGenerator.java (81%) rename x-pack/plugin/esql/qa/{server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative => testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator}/command/pipe/WhereGenerator.java (79%) rename x-pack/plugin/esql/qa/{server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative => testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator}/command/source/FromGenerator.java (68%) 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 46bcfb72fb07a..759952e5de556 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 @@ -9,13 +9,18 @@ import org.elasticsearch.client.Request; import org.elasticsearch.client.ResponseException; -import org.elasticsearch.core.Nullable; import org.elasticsearch.test.rest.ESRestTestCase; import org.elasticsearch.xpack.esql.AssertWarnings; import org.elasticsearch.xpack.esql.CsvTestsDataLoader; +import org.elasticsearch.xpack.esql.generator.Column; +import org.elasticsearch.xpack.esql.generator.EsqlQueryGenerator; +import org.elasticsearch.xpack.esql.generator.LookupIdx; +import org.elasticsearch.xpack.esql.generator.LookupIdxColumn; +import org.elasticsearch.xpack.esql.generator.QueryExecuted; +import org.elasticsearch.xpack.esql.generator.QueryExecutor; +import org.elasticsearch.xpack.esql.generator.command.CommandGenerator; import org.elasticsearch.xpack.esql.qa.rest.ProfileLogger; import org.elasticsearch.xpack.esql.qa.rest.RestEsqlTestCase; -import org.elasticsearch.xpack.esql.qa.rest.generative.command.CommandGenerator; import org.junit.AfterClass; import org.junit.Before; import org.junit.Rule; @@ -32,11 +37,11 @@ import static org.elasticsearch.xpack.esql.CsvTestsDataLoader.ENRICH_POLICIES; import static org.elasticsearch.xpack.esql.CsvTestsDataLoader.availableDatasetsForEs; import static org.elasticsearch.xpack.esql.CsvTestsDataLoader.loadDataSetIntoEs; -import static org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator.COLUMN_NAME; -import static org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator.COLUMN_ORIGINAL_TYPES; -import static org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator.COLUMN_TYPE; +import static org.elasticsearch.xpack.esql.generator.EsqlQueryGenerator.COLUMN_NAME; +import static org.elasticsearch.xpack.esql.generator.EsqlQueryGenerator.COLUMN_ORIGINAL_TYPES; +import static org.elasticsearch.xpack.esql.generator.EsqlQueryGenerator.COLUMN_TYPE; -public abstract class GenerativeRestTest extends ESRestTestCase { +public abstract class GenerativeRestTest extends ESRestTestCase implements QueryExecutor { @Rule(order = Integer.MIN_VALUE) public ProfileLogger profileLogger = new ProfileLogger(); @@ -104,9 +109,9 @@ public void run(CommandGenerator generator, CommandGenerator.CommandDescription previousCommands.add(current); final String command = current.commandString(); - final EsqlQueryGenerator.QueryExecuted result = previousResult == null - ? execute(command, 0, profileLogger) - : execute(previousResult.query() + command, previousResult.depth(), profileLogger); + final QueryExecuted result = previousResult == null + ? execute(command, 0) + : execute(previousResult.query() + command, previousResult.depth()); previousResult = result; final boolean hasException = result.exception() != null; @@ -133,16 +138,16 @@ public boolean continueExecuting() { } @Override - public List currentSchema() { + public List currentSchema() { return currentSchema; } boolean continueExecuting; - List currentSchema; + List currentSchema; final List previousCommands = new ArrayList<>(); - EsqlQueryGenerator.QueryExecuted previousResult; + QueryExecuted previousResult; }; - EsqlQueryGenerator.generatePipeline(MAX_DEPTH, EsqlQueryGenerator.sourceCommand(), mappingInfo, exec); + EsqlQueryGenerator.generatePipeline(MAX_DEPTH, EsqlQueryGenerator.sourceCommand(), mappingInfo, exec, this); } } @@ -150,8 +155,8 @@ private static CommandGenerator.ValidationResult checkResults( List previousCommands, CommandGenerator commandGenerator, CommandGenerator.CommandDescription commandDescription, - EsqlQueryGenerator.QueryExecuted previousResult, - EsqlQueryGenerator.QueryExecuted result + QueryExecuted previousResult, + QueryExecuted result ) { CommandGenerator.ValidationResult outputValidation = commandGenerator.validateOutput( previousCommands, @@ -172,7 +177,7 @@ private static CommandGenerator.ValidationResult checkResults( return outputValidation; } - private void checkException(EsqlQueryGenerator.QueryExecuted query) { + private void checkException(QueryExecuted query) { for (Pattern allowedError : ALLOWED_ERROR_PATTERNS) { if (isAllowedError(query.exception().getMessage(), allowedError)) { return; @@ -192,8 +197,9 @@ private static boolean isAllowedError(String errorMessage, Pattern allowedPatter return allowedPattern.matcher(errorWithoutLineBreaks).matches(); } + @Override @SuppressWarnings("unchecked") - public static EsqlQueryGenerator.QueryExecuted execute(String command, int depth, @Nullable ProfileLogger profileLogger) { + public QueryExecuted execute(String command, int depth) { try { Map json = RestEsqlTestCase.runEsql( new RestEsqlTestCase.RequestObjectBuilder().query(command).build(), @@ -201,26 +207,26 @@ public static EsqlQueryGenerator.QueryExecuted execute(String command, int depth profileLogger, RestEsqlTestCase.Mode.SYNC ); - List outputSchema = outputSchema(json); + List outputSchema = outputSchema(json); List> values = (List>) json.get("values"); - return new EsqlQueryGenerator.QueryExecuted(command, depth, outputSchema, values, null); + return new QueryExecuted(command, depth, outputSchema, values, null); } catch (Exception e) { - return new EsqlQueryGenerator.QueryExecuted(command, depth, null, null, e); + return new QueryExecuted(command, depth, null, null, e); } catch (AssertionError ae) { // this is for ensureNoWarnings() - return new EsqlQueryGenerator.QueryExecuted(command, depth, null, null, new RuntimeException(ae.getMessage())); + return new QueryExecuted(command, depth, null, null, new RuntimeException(ae.getMessage())); } } @SuppressWarnings("unchecked") - private static List outputSchema(Map a) { + private static List outputSchema(Map a) { List> cols = (List>) a.get("columns"); if (cols == null) { return null; } return cols.stream() - .map(x -> new EsqlQueryGenerator.Column((String) x.get(COLUMN_NAME), (String) x.get(COLUMN_TYPE), originalTypes(x))) + .map(x -> new Column((String) x.get(COLUMN_NAME), (String) x.get(COLUMN_TYPE), originalTypes(x))) .collect(Collectors.toList()); } @@ -240,10 +246,6 @@ private List availableIndices() throws IOException { .toList(); } - public record LookupIdxColumn(String name, String type) {} - - public record LookupIdx(String idxName, List keys) {} - private List lookupIndices() { List result = new ArrayList<>(); // we don't have key info from the dataset loader, let's hardcode it for now diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/README.asciidoc b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/README.asciidoc index 0c01e7c524ce3..d286cfa3687ad 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/README.asciidoc +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/README.asciidoc @@ -26,18 +26,11 @@ The result check happens at two levels: == Implementing your own command generator -If you implement a new command, and you want it to be tested by the generative tests, you can add a command generator here. +If you create a new command, we strongly recommend you to implement a command generator for it, so that it is tested +by the generative tests. -All you have to do is: +To do so, see the documentation in the +`x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/README.asciidoc` file. -* add a class in `org.elasticsearch.xpack.esql.qa.rest.generative.command.source` (if it's a source command) or in `org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe` (if it's a pipe command) -* Implement `CommandGenerator` interface (see its javadoc, it should be explicative. Or just have a look at one of the existing commands, eg. `SortGenerator`) -** Implement `CommandGenerator.generate()` method, that will return the command. -*** Have a look at `EsqlQueryGenerator`, it contains many utility methods that will help you generate random expressions. -** Implement `CommandGenerator.validateOutput()` to validate the output of the query. -* Add your class to `EsqlQueryGenerator.SOURCE_COMMANDS` (if it's a source command) or `EsqlQueryGenerator.PIPE_COMMANDS` (if it's a pipe command). -* Run `GenerativeIT` at least a couple of times: these tests can be pretty noisy. -* If you get unexpected errors (real bugs in ES|QL), please open an issue and add the error to `GenerativeRestTest.ALLOWED_ERRORS`. Run tests again until everything works fine. -IMPORTANT: be careful when validating the output (Eg. the row count), as ES|QL can be quite non-deterministic when there are no SORTs diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/Column.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/Column.java new file mode 100644 index 0000000000000..43f7776aadb87 --- /dev/null +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/Column.java @@ -0,0 +1,12 @@ +/* + * 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.generator; + +import java.util.List; + +public record Column(String name, String type, List originalTypes) {} 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/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/EsqlQueryGenerator.java similarity index 80% rename from x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/EsqlQueryGenerator.java rename to x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/EsqlQueryGenerator.java index b6cf5ff3a8d15..b868c232b65dc 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/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/EsqlQueryGenerator.java @@ -5,26 +5,26 @@ * 2.0. */ -package org.elasticsearch.xpack.esql.qa.rest.generative; +package org.elasticsearch.xpack.esql.generator; import org.elasticsearch.xpack.esql.CsvTestsDataLoader; -import org.elasticsearch.xpack.esql.qa.rest.generative.command.CommandGenerator; -import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.ChangePointGenerator; -import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.DissectGenerator; -import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.DropGenerator; -import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.EnrichGenerator; -import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.EvalGenerator; -import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.ForkGenerator; -import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.GrokGenerator; -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.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.generator.command.CommandGenerator; +import org.elasticsearch.xpack.esql.generator.command.pipe.ChangePointGenerator; +import org.elasticsearch.xpack.esql.generator.command.pipe.DissectGenerator; +import org.elasticsearch.xpack.esql.generator.command.pipe.DropGenerator; +import org.elasticsearch.xpack.esql.generator.command.pipe.EnrichGenerator; +import org.elasticsearch.xpack.esql.generator.command.pipe.EvalGenerator; +import org.elasticsearch.xpack.esql.generator.command.pipe.ForkGenerator; +import org.elasticsearch.xpack.esql.generator.command.pipe.GrokGenerator; +import org.elasticsearch.xpack.esql.generator.command.pipe.KeepGenerator; +import org.elasticsearch.xpack.esql.generator.command.pipe.LimitGenerator; +import org.elasticsearch.xpack.esql.generator.command.pipe.LookupJoinGenerator; +import org.elasticsearch.xpack.esql.generator.command.pipe.MvExpandGenerator; +import org.elasticsearch.xpack.esql.generator.command.pipe.RenameGenerator; +import org.elasticsearch.xpack.esql.generator.command.pipe.SortGenerator; +import org.elasticsearch.xpack.esql.generator.command.pipe.StatsGenerator; +import org.elasticsearch.xpack.esql.generator.command.pipe.WhereGenerator; +import org.elasticsearch.xpack.esql.generator.command.source.FromGenerator; import java.util.List; import java.util.Set; @@ -42,10 +42,6 @@ public class EsqlQueryGenerator { public static final String COLUMN_TYPE = "type"; public static final String COLUMN_ORIGINAL_TYPES = "original_types"; - public record Column(String name, String type, List originalTypes) {} - - public record QueryExecuted(String query, int depth, List outputSchema, List> result, Exception exception) {} - /** * These are commands that are at the beginning of the query, eg. FROM */ @@ -87,7 +83,7 @@ public interface Executor { boolean continueExecuting(); - List currentSchema(); + List currentSchema(); } @@ -95,9 +91,10 @@ public static void generatePipeline( final int depth, CommandGenerator commandGenerator, final CommandGenerator.QuerySchema schema, - Executor executor + Executor executor, + QueryExecutor queryExecutor ) { - CommandGenerator.CommandDescription desc = commandGenerator.generate(List.of(), List.of(), schema); + CommandGenerator.CommandDescription desc = commandGenerator.generate(List.of(), List.of(), schema, queryExecutor); executor.run(commandGenerator, desc); if (executor.continueExecuting() == false) { return; @@ -108,7 +105,7 @@ public static void generatePipeline( break; } commandGenerator = EsqlQueryGenerator.randomPipeCommandGenerator(); - desc = commandGenerator.generate(executor.previousCommands(), executor.currentSchema(), schema); + desc = commandGenerator.generate(executor.previousCommands(), executor.currentSchema(), schema, queryExecutor); if (desc == CommandGenerator.EMPTY_DESCRIPTION) { continue; } @@ -188,12 +185,12 @@ public static String randomGroupableName(List previousOutput) { } public static boolean groupable(Column col) { - return col.type.equals("keyword") - || col.type.equals("text") - || col.type.equals("long") - || col.type.equals("integer") - || col.type.equals("ip") - || col.type.equals("version"); + return col.type().equals("keyword") + || col.type().equals("text") + || col.type().equals("long") + || col.type().equals("integer") + || col.type().equals("ip") + || col.type().equals("version"); } /** @@ -209,12 +206,12 @@ public static String randomSortableName(List previousOutput) { } public static boolean sortable(Column col) { - return col.type.equals("keyword") - || col.type.equals("text") - || col.type.equals("long") - || col.type.equals("integer") - || col.type.equals("ip") - || col.type.equals("version"); + return col.type().equals("keyword") + || col.type().equals("text") + || col.type().equals("long") + || col.type().equals("integer") + || col.type().equals("ip") + || col.type().equals("version"); } public static String agg(List previousOutput) { @@ -308,7 +305,7 @@ public static String constantExpression() { /** * returns a random identifier or one of the existing names */ - public static String randomAttributeOrIdentifier(List previousOutput) { + public static String randomAttributeOrIdentifier(List previousOutput) { String name; if (randomBoolean()) { name = EsqlQueryGenerator.randomIdentifier(); @@ -335,7 +332,7 @@ public static boolean fieldCanBeUsed(Column field) { || field.name().equals("") // no dense vectors for now, they are not supported in most commands || field.type().contains("vector") - || field.originalTypes.stream().anyMatch(x -> x.contains("vector"))) == false; + || field.originalTypes().stream().anyMatch(x -> x.contains("vector"))) == false; } public static String unquote(String colName) { diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/LookupIdx.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/LookupIdx.java new file mode 100644 index 0000000000000..d03b086b97d6c --- /dev/null +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/LookupIdx.java @@ -0,0 +1,12 @@ +/* + * 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.generator; + +import java.util.List; + +public record LookupIdx(String idxName, List keys) {} diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/LookupIdxColumn.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/LookupIdxColumn.java new file mode 100644 index 0000000000000..8683d68682717 --- /dev/null +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/LookupIdxColumn.java @@ -0,0 +1,10 @@ +/* + * 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.generator; + +public record LookupIdxColumn(String name, String type) {} diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/QueryExecuted.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/QueryExecuted.java new file mode 100644 index 0000000000000..d9a37949e5eba --- /dev/null +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/QueryExecuted.java @@ -0,0 +1,12 @@ +/* + * 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.generator; + +import java.util.List; + +public record QueryExecuted(String query, int depth, List outputSchema, List> result, Exception exception) {} diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/QueryExecutor.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/QueryExecutor.java new file mode 100644 index 0000000000000..a6684aeb68ce3 --- /dev/null +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/QueryExecutor.java @@ -0,0 +1,12 @@ +/* + * 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.generator; + +public interface QueryExecutor { + QueryExecuted execute(String command, int depth); +} diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/README.asciidoc b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/README.asciidoc new file mode 100644 index 0000000000000..d9613f01e0b6b --- /dev/null +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/README.asciidoc @@ -0,0 +1,24 @@ += ES|QL Query Genereator + +These classes generate random ES|QL queries, that can then be used to test the ES|QL engine (eg. see `GenerativeIT`). +The intention is not to test the single commands, but rather to test how ES|QL query engine +(parser, optimizers, query layout, compute) manages very complex queries. + +== Implementing your own command generator + +If you implement a new command, and you want it to be tested by the generative tests, you can add a command generator here. + +All you have to do is: + +* add a class in `org.elasticsearch.xpack.esql.generator.command.source` (if it's a source command) + or in `org.elasticsearch.xpack.esql.generator.command.pipe` (if it's a pipe command) +* Implement `CommandGenerator` interface (see its javadoc, it should be explicative. Or just have a look at one of the existing commands, eg. `SortGenerator`) +** Implement `CommandGenerator.generate()` method, that will return the command. +*** Have a look at `EsqlQueryGenerator`, it contains many utility methods that will help you generate random expressions. +** Implement `CommandGenerator.validateOutput()` to validate the output of the query. +* Add your class to `EsqlQueryGenerator.SOURCE_COMMANDS` (if it's a source command) or `EsqlQueryGenerator.PIPE_COMMANDS` (if it's a pipe command). +* Run `GenerativeIT` at least a couple of times: these tests can be pretty noisy. +* If you get unexpected errors (real bugs in ES|QL), please open an issue and add the error to `GenerativeRestTest.ALLOWED_ERRORS`. Run tests again until everything works fine. + + +IMPORTANT: be careful when validating the output (Eg. the row count), as ES|QL can be quite non-deterministic when there are no SORTs diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/CommandGenerator.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/CommandGenerator.java similarity index 81% rename from x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/CommandGenerator.java rename to x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/CommandGenerator.java index 7d22ea4c66a94..e18748be154f8 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/CommandGenerator.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/CommandGenerator.java @@ -5,11 +5,13 @@ * 2.0. */ -package org.elasticsearch.xpack.esql.qa.rest.generative.command; +package org.elasticsearch.xpack.esql.generator.command; import org.elasticsearch.xpack.esql.CsvTestsDataLoader; -import org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator; -import org.elasticsearch.xpack.esql.qa.rest.generative.GenerativeRestTest; +import org.elasticsearch.xpack.esql.generator.Column; +import org.elasticsearch.xpack.esql.generator.EsqlQueryGenerator; +import org.elasticsearch.xpack.esql.generator.LookupIdx; +import org.elasticsearch.xpack.esql.generator.QueryExecutor; import java.util.List; import java.util.Map; @@ -30,11 +32,7 @@ public interface CommandGenerator { */ record CommandDescription(String commandName, CommandGenerator generator, String commandString, Map context) {} - record QuerySchema( - List baseIndices, - List lookupIndices, - List enrichPolicies - ) {} + record QuerySchema(List baseIndices, List lookupIndices, List enrichPolicies) {} record ValidationResult(boolean success, String errorMessage) {} @@ -42,8 +40,9 @@ record ValidationResult(boolean success, String errorMessage) {} @Override public CommandDescription generate( List previousCommands, - List previousOutput, - QuerySchema schema + List previousOutput, + QuerySchema schema, + QueryExecutor executor ) { return EMPTY_DESCRIPTION; } @@ -52,9 +51,9 @@ public CommandDescription generate( public ValidationResult validateOutput( List previousCommands, CommandDescription command, - List previousColumns, + List previousColumns, List> previousOutput, - List columns, + List columns, List> output ) { return VALIDATION_OK; @@ -70,13 +69,15 @@ public ValidationResult validateOutput( * @param previousCommands the list of the previous commands in the query * @param previousOutput the output returned by the query so far. * @param schema The columns returned by the query so far. It contains name and type information for each column. + * @param executor * @return All the details about the generated command. See {@link CommandDescription}. * If something goes wrong and for some reason you can't generate a command, you should return {@link CommandGenerator#EMPTY_DESCRIPTION} */ CommandDescription generate( List previousCommands, - List previousOutput, - QuerySchema schema + List previousOutput, + QuerySchema schema, + QueryExecutor executor ); /** @@ -87,7 +88,7 @@ CommandDescription generate( * @param command The description of the command you just generated. * It also contains the context information you stored during command generation. * @param previousColumns The output schema of the original query (without last generated command). - * It contains name and type information for each column, see {@link EsqlQueryGenerator.Column} + * It contains name and type information for each column, see {@link Column} * @param previousOutput The output of the original query (without last generated command), as a list (rows) of lists (columns) of values * @param columns The output schema of the full query (WITH last generated command). * @param output The output of the full query (WITH last generated command), as a list (rows) of lists (columns) of values @@ -98,9 +99,9 @@ CommandDescription generate( ValidationResult validateOutput( List previousCommands, CommandDescription command, - List previousColumns, + List previousColumns, List> previousOutput, - List columns, + List columns, List> output ); @@ -118,7 +119,7 @@ static ValidationResult expectSameRowCount( return VALIDATION_OK; } - static ValidationResult expectSameColumns(List previousColumns, List columns) { + static ValidationResult expectSameColumns(List previousColumns, List columns) { if (previousColumns.stream().anyMatch(x -> x.name().contains(""))) { return VALIDATION_OK; // known bug @@ -128,8 +129,8 @@ static ValidationResult expectSameColumns(List previo return new ValidationResult(false, "Expecting [" + previousColumns.size() + "] columns, got [" + columns.size() + "]"); } - List prevColNames = previousColumns.stream().map(EsqlQueryGenerator.Column::name).toList(); - List newColNames = columns.stream().map(EsqlQueryGenerator.Column::name).toList(); + List prevColNames = previousColumns.stream().map(Column::name).toList(); + List newColNames = columns.stream().map(Column::name).toList(); if (prevColNames.equals(newColNames) == false) { return new ValidationResult( false, @@ -143,10 +144,7 @@ static ValidationResult expectSameColumns(List previo /** * The command doesn't have to produce LESS columns than the previous query */ - static ValidationResult expectAtLeastSameNumberOfColumns( - List previousColumns, - List columns - ) { + static ValidationResult expectAtLeastSameNumberOfColumns(List previousColumns, List columns) { if (previousColumns.stream().anyMatch(x -> x.name().contains(""))) { return VALIDATION_OK; // known bug } diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/ChangePointGenerator.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/ChangePointGenerator.java similarity index 66% rename from x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/ChangePointGenerator.java rename to x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/ChangePointGenerator.java index a0d71c47d413b..2b04cc9aced80 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/ChangePointGenerator.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/ChangePointGenerator.java @@ -5,16 +5,16 @@ * 2.0. */ -package org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe; +package org.elasticsearch.xpack.esql.generator.command.pipe; -import org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator; -import org.elasticsearch.xpack.esql.qa.rest.generative.command.CommandGenerator; +import org.elasticsearch.xpack.esql.generator.Column; +import org.elasticsearch.xpack.esql.generator.EsqlQueryGenerator; +import org.elasticsearch.xpack.esql.generator.QueryExecutor; +import org.elasticsearch.xpack.esql.generator.command.CommandGenerator; import java.util.List; import java.util.Map; -import static org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator.randomAttributeOrIdentifier; - public class ChangePointGenerator implements CommandGenerator { public static final String CHANGE_POINT = "change_point"; public static final CommandGenerator INSTANCE = new ChangePointGenerator(); @@ -22,18 +22,19 @@ public class ChangePointGenerator implements CommandGenerator { @Override public CommandDescription generate( List previousCommands, - List previousOutput, - QuerySchema schema + List previousOutput, + QuerySchema schema, + QueryExecutor executor ) { String timestampField = EsqlQueryGenerator.randomDateField(previousOutput); String numericField = EsqlQueryGenerator.randomNumericField(previousOutput); if (timestampField == null || numericField == null) { return EMPTY_DESCRIPTION; } - String alias1 = randomAttributeOrIdentifier(previousOutput); - String alias2 = randomAttributeOrIdentifier(previousOutput); + String alias1 = EsqlQueryGenerator.randomAttributeOrIdentifier(previousOutput); + String alias2 = EsqlQueryGenerator.randomAttributeOrIdentifier(previousOutput); while (alias1.equals(alias2)) { - alias2 = randomAttributeOrIdentifier(previousOutput); + alias2 = EsqlQueryGenerator.randomAttributeOrIdentifier(previousOutput); } String cmd = " | CHANGE_POINT " + numericField + " ON " + timestampField + " AS " + alias1 + ", " + alias2; @@ -45,9 +46,9 @@ public CommandDescription generate( public ValidationResult validateOutput( List previousCommands, CommandDescription command, - List previousColumns, + List previousColumns, List> previousOutput, - List columns, + List columns, List> output ) { return CommandGenerator.expectAtLeastSameNumberOfColumns(previousColumns, columns); diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/DissectGenerator.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/DissectGenerator.java similarity index 83% rename from x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/DissectGenerator.java rename to x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/DissectGenerator.java index 83f8ae983dcde..6e0f95c7e7846 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/DissectGenerator.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/DissectGenerator.java @@ -5,10 +5,12 @@ * 2.0. */ -package org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe; +package org.elasticsearch.xpack.esql.generator.command.pipe; -import org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator; -import org.elasticsearch.xpack.esql.qa.rest.generative.command.CommandGenerator; +import org.elasticsearch.xpack.esql.generator.Column; +import org.elasticsearch.xpack.esql.generator.EsqlQueryGenerator; +import org.elasticsearch.xpack.esql.generator.QueryExecutor; +import org.elasticsearch.xpack.esql.generator.command.CommandGenerator; import java.util.List; import java.util.Map; @@ -24,8 +26,9 @@ public class DissectGenerator implements CommandGenerator { @Override public CommandDescription generate( List previousCommands, - List previousOutput, - QuerySchema schema + List previousOutput, + QuerySchema schema, + QueryExecutor executor ) { String field = EsqlQueryGenerator.randomStringField(previousOutput); if (field == null) { @@ -60,9 +63,9 @@ public CommandDescription generate( public ValidationResult validateOutput( List previousCommands, CommandDescription commandDescription, - List previousColumns, + List previousColumns, List> previousOutput, - List columns, + List columns, List> output ) { if (commandDescription == EMPTY_DESCRIPTION) { diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/DropGenerator.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/DropGenerator.java similarity index 85% rename from x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/DropGenerator.java rename to x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/DropGenerator.java index 8bf2597f808c0..97b754692f392 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/DropGenerator.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/DropGenerator.java @@ -5,10 +5,12 @@ * 2.0. */ -package org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe; +package org.elasticsearch.xpack.esql.generator.command.pipe; -import org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator; -import org.elasticsearch.xpack.esql.qa.rest.generative.command.CommandGenerator; +import org.elasticsearch.xpack.esql.generator.Column; +import org.elasticsearch.xpack.esql.generator.EsqlQueryGenerator; +import org.elasticsearch.xpack.esql.generator.QueryExecutor; +import org.elasticsearch.xpack.esql.generator.command.CommandGenerator; import java.util.HashSet; import java.util.List; @@ -29,8 +31,9 @@ public class DropGenerator implements CommandGenerator { @Override public CommandDescription generate( List previousCommands, - List previousOutput, - QuerySchema schema + List previousOutput, + QuerySchema schema, + QueryExecutor executor ) { if (previousOutput.size() < 2) { return CommandGenerator.EMPTY_DESCRIPTION; // don't drop all of them, just do nothing @@ -67,16 +70,16 @@ public CommandDescription generate( public ValidationResult validateOutput( List previousCommands, CommandDescription commandDescription, - List previousColumns, + List previousColumns, List> previousOutput, - List columns, + List columns, List> output ) { if (commandDescription == EMPTY_DESCRIPTION) { return VALIDATION_OK; } Set droppedColumns = (Set) commandDescription.context().get(DROPPED_COLUMNS); - List resultColNames = columns.stream().map(EsqlQueryGenerator.Column::name).toList(); + List resultColNames = columns.stream().map(Column::name).toList(); // expected column names are unquoted already for (String droppedColumn : droppedColumns) { if (resultColNames.contains(droppedColumn)) { diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/EnrichGenerator.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/EnrichGenerator.java similarity index 78% rename from x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/EnrichGenerator.java rename to x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/EnrichGenerator.java index aac8f16e13285..be3d547cf535c 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/EnrichGenerator.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/EnrichGenerator.java @@ -5,10 +5,12 @@ * 2.0. */ -package org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe; +package org.elasticsearch.xpack.esql.generator.command.pipe; -import org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator; -import org.elasticsearch.xpack.esql.qa.rest.generative.command.CommandGenerator; +import org.elasticsearch.xpack.esql.generator.Column; +import org.elasticsearch.xpack.esql.generator.EsqlQueryGenerator; +import org.elasticsearch.xpack.esql.generator.QueryExecutor; +import org.elasticsearch.xpack.esql.generator.command.CommandGenerator; import java.util.List; import java.util.Map; @@ -23,8 +25,9 @@ public class EnrichGenerator implements CommandGenerator { @Override public CommandDescription generate( List previousCommands, - List previousOutput, - QuerySchema schema + List previousOutput, + QuerySchema schema, + QueryExecutor executor ) { String field = EsqlQueryGenerator.randomKeywordField(previousOutput); if (field == null || schema.enrichPolicies().isEmpty()) { @@ -43,9 +46,9 @@ public CommandDescription generate( public ValidationResult validateOutput( List previousCommands, CommandDescription commandDescription, - List previousColumns, + List previousColumns, List> previousOutput, - List columns, + List columns, List> output ) { if (commandDescription == EMPTY_DESCRIPTION) { diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/EvalGenerator.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/EvalGenerator.java similarity index 85% rename from x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/EvalGenerator.java rename to x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/EvalGenerator.java index b49e313fa4e3f..8c2841dda3f04 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/EvalGenerator.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/EvalGenerator.java @@ -5,10 +5,12 @@ * 2.0. */ -package org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe; +package org.elasticsearch.xpack.esql.generator.command.pipe; -import org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator; -import org.elasticsearch.xpack.esql.qa.rest.generative.command.CommandGenerator; +import org.elasticsearch.xpack.esql.generator.Column; +import org.elasticsearch.xpack.esql.generator.EsqlQueryGenerator; +import org.elasticsearch.xpack.esql.generator.QueryExecutor; +import org.elasticsearch.xpack.esql.generator.command.CommandGenerator; import java.util.ArrayList; import java.util.List; @@ -26,8 +28,9 @@ public class EvalGenerator implements CommandGenerator { @Override public CommandDescription generate( List previousCommands, - List previousOutput, - QuerySchema schema + List previousOutput, + QuerySchema schema, + QueryExecutor executor ) { StringBuilder cmd = new StringBuilder(" | eval "); int nFields = randomIntBetween(1, 10); @@ -63,13 +66,13 @@ public CommandDescription generate( public ValidationResult validateOutput( List previousCommands, CommandDescription commandDescription, - List previousColumns, + List previousColumns, List> previousOutput, - List columns, + List columns, List> output ) { List expectedColumns = (List) commandDescription.context().get(NEW_COLUMNS); - List resultColNames = columns.stream().map(EsqlQueryGenerator.Column::name).toList(); + List resultColNames = columns.stream().map(Column::name).toList(); List lastColumns = resultColNames.subList(resultColNames.size() - expectedColumns.size(), resultColNames.size()); lastColumns = lastColumns.stream().map(EvalGenerator::unquote).toList(); // expected column names are unquoted already 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/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/ForkGenerator.java similarity index 81% rename from x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/ForkGenerator.java rename to x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/ForkGenerator.java index 74c4122156bc6..3dfca414a997a 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/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/ForkGenerator.java @@ -5,11 +5,13 @@ * 2.0. */ -package org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe; +package org.elasticsearch.xpack.esql.generator.command.pipe; -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.elasticsearch.xpack.esql.generator.Column; +import org.elasticsearch.xpack.esql.generator.EsqlQueryGenerator; +import org.elasticsearch.xpack.esql.generator.QueryExecuted; +import org.elasticsearch.xpack.esql.generator.QueryExecutor; +import org.elasticsearch.xpack.esql.generator.command.CommandGenerator; import java.util.ArrayList; import java.util.List; @@ -26,8 +28,9 @@ public class ForkGenerator implements CommandGenerator { @Override public CommandDescription generate( List previousCommands, - List previousOutput, - QuerySchema schema + List previousOutput, + QuerySchema schema, + QueryExecutor executor ) { // FORK can only be allowed once - so we skip adding another FORK if we already have one // otherwise, most generated queries would only result in a validation error @@ -60,9 +63,9 @@ public void run(CommandGenerator generator, CommandDescription current) { // schema, we append the command. Enforcing the same schema is stricter than the Fork needs (it only needs types to be // the same on columns which are present), but given we currently generate independent sub-pipelines, this way we can // generate more valid Fork queries. - final EsqlQueryGenerator.QueryExecuted result = previousResult == null - ? GenerativeRestTest.execute(command, 0, null) - : GenerativeRestTest.execute(previousResult.query() + command, previousResult.depth(), null); + final QueryExecuted result = previousResult == null + ? executor.execute(command, 0) + : executor.execute(previousResult.query() + command, previousResult.depth()); previousResult = result; continueExecuting = result.exception() == null && result.outputSchema().equals(previousOutput); @@ -82,21 +85,22 @@ public boolean continueExecuting() { } @Override - public List currentSchema() { + public List currentSchema() { return previousOutput; } final List previousCommands = new ArrayList<>(); boolean continueExecuting; - EsqlQueryGenerator.QueryExecuted previousResult; + QueryExecuted previousResult; }; var gen = new CommandGenerator() { @Override public CommandDescription generate( List previousCommands, - List previousOutput, - QuerySchema schema + List previousOutput, + QuerySchema schema, + QueryExecutor executor ) { return new CommandDescription(FORK, this, completeCommand.toString(), Map.of()); } @@ -105,16 +109,16 @@ public CommandDescription generate( public ValidationResult validateOutput( List previousCommands, CommandDescription command, - List previousColumns, + List previousColumns, List> previousOutput, - List columns, + List columns, List> output ) { return VALIDATION_OK; } }; - EsqlQueryGenerator.generatePipeline(3, gen, schema, exec); + EsqlQueryGenerator.generatePipeline(3, gen, schema, exec, executor); if (exec.previousCommands().size() > 1) { String previousCmd = exec.previousCommands() .stream() @@ -136,9 +140,9 @@ public ValidationResult validateOutput( public ValidationResult validateOutput( List previousCommands, CommandDescription command, - List previousColumns, + List previousColumns, List> previousOutput, - List columns, + List columns, List> output ) { return CommandGenerator.expectSameRowCount(previousCommands, previousOutput, output); diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/GrokGenerator.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/GrokGenerator.java similarity index 83% rename from x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/GrokGenerator.java rename to x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/GrokGenerator.java index 60322eb12c351..162ccbfc21e15 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/GrokGenerator.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/GrokGenerator.java @@ -5,10 +5,12 @@ * 2.0. */ -package org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe; +package org.elasticsearch.xpack.esql.generator.command.pipe; -import org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator; -import org.elasticsearch.xpack.esql.qa.rest.generative.command.CommandGenerator; +import org.elasticsearch.xpack.esql.generator.Column; +import org.elasticsearch.xpack.esql.generator.EsqlQueryGenerator; +import org.elasticsearch.xpack.esql.generator.QueryExecutor; +import org.elasticsearch.xpack.esql.generator.command.CommandGenerator; import java.util.List; import java.util.Map; @@ -24,8 +26,9 @@ public class GrokGenerator implements CommandGenerator { @Override public CommandDescription generate( List previousCommands, - List previousOutput, - QuerySchema schema + List previousOutput, + QuerySchema schema, + QueryExecutor executor ) { String field = EsqlQueryGenerator.randomStringField(previousOutput); if (field == null) { @@ -59,9 +62,9 @@ public CommandDescription generate( public ValidationResult validateOutput( List previousCommands, CommandDescription commandDescription, - List previousColumns, + List previousColumns, List> previousOutput, - List columns, + List columns, List> output ) { if (commandDescription == EMPTY_DESCRIPTION) { diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/KeepGenerator.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/KeepGenerator.java similarity index 84% rename from x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/KeepGenerator.java rename to x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/KeepGenerator.java index f3f522576124e..e4cceabb21c9f 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/KeepGenerator.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/KeepGenerator.java @@ -5,10 +5,12 @@ * 2.0. */ -package org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe; +package org.elasticsearch.xpack.esql.generator.command.pipe; -import org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator; -import org.elasticsearch.xpack.esql.qa.rest.generative.command.CommandGenerator; +import org.elasticsearch.xpack.esql.generator.Column; +import org.elasticsearch.xpack.esql.generator.EsqlQueryGenerator; +import org.elasticsearch.xpack.esql.generator.QueryExecutor; +import org.elasticsearch.xpack.esql.generator.command.CommandGenerator; import java.util.HashSet; import java.util.List; @@ -28,8 +30,9 @@ public class KeepGenerator implements CommandGenerator { @Override public CommandDescription generate( List previousCommands, - List previousOutput, - QuerySchema schema + List previousOutput, + QuerySchema schema, + QueryExecutor executor ) { int n = randomIntBetween(1, previousOutput.size()); Set proj = new HashSet<>(); @@ -63,9 +66,9 @@ public CommandDescription generate( public ValidationResult validateOutput( List previousCommands, CommandDescription commandDescription, - List previousColumns, + List previousColumns, List> previousOutput, - List columns, + List columns, List> output ) { if (commandDescription == EMPTY_DESCRIPTION) { diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/LimitGenerator.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/LimitGenerator.java similarity index 77% rename from x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/LimitGenerator.java rename to x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/LimitGenerator.java index fe94c8feda14a..bd2f49e4b079b 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/LimitGenerator.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/LimitGenerator.java @@ -5,10 +5,11 @@ * 2.0. */ -package org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe; +package org.elasticsearch.xpack.esql.generator.command.pipe; -import org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator; -import org.elasticsearch.xpack.esql.qa.rest.generative.command.CommandGenerator; +import org.elasticsearch.xpack.esql.generator.Column; +import org.elasticsearch.xpack.esql.generator.QueryExecutor; +import org.elasticsearch.xpack.esql.generator.command.CommandGenerator; import java.util.List; import java.util.Map; @@ -23,8 +24,9 @@ public class LimitGenerator implements CommandGenerator { @Override public CommandDescription generate( List previousCommands, - List previousOutput, - QuerySchema schema + List previousOutput, + QuerySchema schema, + QueryExecutor executor ) { int limit = randomIntBetween(0, 15000); String cmd = " | limit " + limit; @@ -36,9 +38,9 @@ public CommandDescription generate( public ValidationResult validateOutput( List previousCommands, CommandDescription commandDescription, - List previousColumns, + List previousColumns, List> previousOutput, - List columns, + List columns, List> output ) { int limit = (int) commandDescription.context().get(LIMIT); diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/LookupJoinGenerator.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/LookupJoinGenerator.java similarity index 80% rename from x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/LookupJoinGenerator.java rename to x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/LookupJoinGenerator.java index b1348491e73c3..011db8e2f0e9f 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/LookupJoinGenerator.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/LookupJoinGenerator.java @@ -5,11 +5,14 @@ * 2.0. */ -package org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe; +package org.elasticsearch.xpack.esql.generator.command.pipe; -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.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.esql.generator.Column; +import org.elasticsearch.xpack.esql.generator.LookupIdx; +import org.elasticsearch.xpack.esql.generator.LookupIdxColumn; +import org.elasticsearch.xpack.esql.generator.QueryExecutor; +import org.elasticsearch.xpack.esql.generator.command.CommandGenerator; import java.util.ArrayList; import java.util.HashSet; @@ -19,7 +22,6 @@ import static org.elasticsearch.test.ESTestCase.randomFrom; import static org.elasticsearch.test.ESTestCase.randomInt; -import static org.elasticsearch.test.ESTestCase.randomSubsetOf; public class LookupJoinGenerator implements CommandGenerator { @@ -29,17 +31,18 @@ public class LookupJoinGenerator implements CommandGenerator { @Override public CommandDescription generate( List previousCommands, - List previousOutput, - QuerySchema schema + List previousOutput, + QuerySchema schema, + QueryExecutor executor ) { - GenerativeRestTest.LookupIdx lookupIdx = randomFrom(schema.lookupIndices()); + LookupIdx lookupIdx = randomFrom(schema.lookupIndices()); String lookupIdxName = lookupIdx.idxName(); int joinColumnsCount = randomInt(lookupIdx.keys().size() - 1) + 1; // at least one column must be used for the join - List joinColumns = randomSubsetOf(joinColumnsCount, lookupIdx.keys()); + List joinColumns = ESTestCase.randomSubsetOf(joinColumnsCount, lookupIdx.keys()); List keyNames = new ArrayList<>(); List joinOn = new ArrayList<>(); Set usedColumns = new HashSet<>(); - for (GenerativeRestTest.LookupIdxColumn joinColumn : joinColumns) { + for (LookupIdxColumn joinColumn : joinColumns) { String idxKey = joinColumn.name(); String keyType = joinColumn.type(); @@ -47,7 +50,7 @@ public CommandDescription generate( if (candidateKeys.isEmpty()) { continue; // no candidate keys of the right type, skip this column } - EsqlQueryGenerator.Column key = randomFrom(candidateKeys); + Column key = randomFrom(candidateKeys); if (usedColumns.contains(key.name()) || usedColumns.contains(idxKey)) { continue; // already used this column from the lookup index, or will discard the main index column by RENAME'ing below, skip } else { @@ -82,9 +85,9 @@ public CommandDescription generate( public ValidationResult validateOutput( List previousCommands, CommandDescription commandDescription, - List previousColumns, + List previousColumns, List> previousOutput, - List columns, + List columns, List> output ) { if (commandDescription == EMPTY_DESCRIPTION) { diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/MvExpandGenerator.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/MvExpandGenerator.java similarity index 73% rename from x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/MvExpandGenerator.java rename to x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/MvExpandGenerator.java index 317a2e459094e..7be8d0bdff62a 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/MvExpandGenerator.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/MvExpandGenerator.java @@ -5,10 +5,12 @@ * 2.0. */ -package org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe; +package org.elasticsearch.xpack.esql.generator.command.pipe; -import org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator; -import org.elasticsearch.xpack.esql.qa.rest.generative.command.CommandGenerator; +import org.elasticsearch.xpack.esql.generator.Column; +import org.elasticsearch.xpack.esql.generator.EsqlQueryGenerator; +import org.elasticsearch.xpack.esql.generator.QueryExecutor; +import org.elasticsearch.xpack.esql.generator.command.CommandGenerator; import java.util.List; import java.util.Map; @@ -22,8 +24,9 @@ public class MvExpandGenerator implements CommandGenerator { @Override public CommandDescription generate( List previousCommands, - List previousOutput, - QuerySchema schema + List previousOutput, + QuerySchema schema, + QueryExecutor executor ) { String toExpand = EsqlQueryGenerator.randomName(previousOutput); if (toExpand == null) { @@ -37,9 +40,9 @@ public CommandDescription generate( public ValidationResult validateOutput( List previousCommands, CommandDescription commandDescription, - List previousColumns, + List previousColumns, List> previousOutput, - List columns, + List columns, List> output ) { if (commandDescription == EMPTY_DESCRIPTION) { diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/RenameGenerator.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/RenameGenerator.java similarity index 82% rename from x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/RenameGenerator.java rename to x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/RenameGenerator.java index 80b36c06e524e..132c0dc9840a7 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/RenameGenerator.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/RenameGenerator.java @@ -5,10 +5,12 @@ * 2.0. */ -package org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe; +package org.elasticsearch.xpack.esql.generator.command.pipe; -import org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator; -import org.elasticsearch.xpack.esql.qa.rest.generative.command.CommandGenerator; +import org.elasticsearch.xpack.esql.generator.Column; +import org.elasticsearch.xpack.esql.generator.EsqlQueryGenerator; +import org.elasticsearch.xpack.esql.generator.QueryExecutor; +import org.elasticsearch.xpack.esql.generator.command.CommandGenerator; import java.util.ArrayList; import java.util.HashMap; @@ -29,21 +31,19 @@ public class RenameGenerator implements CommandGenerator { @Override public CommandDescription generate( List previousCommands, - List previousOutput, - QuerySchema schema + List previousOutput, + QuerySchema schema, + QueryExecutor executor ) { int n = randomIntBetween(1, Math.min(3, previousOutput.size())); List proj = new ArrayList<>(); Map nameToType = new HashMap<>(); - for (EsqlQueryGenerator.Column column : previousOutput) { + for (Column column : previousOutput) { nameToType.put(column.name(), column.type()); } List names = new ArrayList<>( - previousOutput.stream() - .filter(EsqlQueryGenerator::fieldCanBeUsed) - .map(EsqlQueryGenerator.Column::name) - .collect(Collectors.toList()) + previousOutput.stream().filter(EsqlQueryGenerator::fieldCanBeUsed).map(Column::name).collect(Collectors.toList()) ); if (names.isEmpty()) { return EMPTY_DESCRIPTION; @@ -86,9 +86,9 @@ public CommandDescription generate( public ValidationResult validateOutput( List previousCommands, CommandDescription commandDescription, - List previousColumns, + List previousColumns, List> previousOutput, - List columns, + List columns, List> output ) { if (commandDescription == EMPTY_DESCRIPTION) { diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/SortGenerator.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/SortGenerator.java similarity index 78% rename from x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/SortGenerator.java rename to x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/SortGenerator.java index f7849d1c202f1..4d61c00d1391c 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/SortGenerator.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/SortGenerator.java @@ -5,10 +5,12 @@ * 2.0. */ -package org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe; +package org.elasticsearch.xpack.esql.generator.command.pipe; -import org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator; -import org.elasticsearch.xpack.esql.qa.rest.generative.command.CommandGenerator; +import org.elasticsearch.xpack.esql.generator.Column; +import org.elasticsearch.xpack.esql.generator.EsqlQueryGenerator; +import org.elasticsearch.xpack.esql.generator.QueryExecutor; +import org.elasticsearch.xpack.esql.generator.command.CommandGenerator; import java.util.HashSet; import java.util.List; @@ -27,8 +29,9 @@ public class SortGenerator implements CommandGenerator { @Override public CommandDescription generate( List previousCommands, - List previousOutput, - QuerySchema schema + List previousOutput, + QuerySchema schema, + QueryExecutor executor ) { int n = randomIntBetween(1, previousOutput.size()); Set proj = new HashSet<>(); @@ -50,9 +53,9 @@ public CommandDescription generate( public ValidationResult validateOutput( List previousCommands, CommandDescription commandDescription, - List previousColumns, + List previousColumns, List> previousOutput, - List columns, + List columns, List> output ) { return CommandGenerator.expectSameColumns(previousColumns, columns); diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/StatsGenerator.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/StatsGenerator.java similarity index 81% rename from x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/StatsGenerator.java rename to x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/StatsGenerator.java index b0ce7f43af997..30464c28a8464 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/StatsGenerator.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/StatsGenerator.java @@ -5,10 +5,12 @@ * 2.0. */ -package org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe; +package org.elasticsearch.xpack.esql.generator.command.pipe; -import org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator; -import org.elasticsearch.xpack.esql.qa.rest.generative.command.CommandGenerator; +import org.elasticsearch.xpack.esql.generator.Column; +import org.elasticsearch.xpack.esql.generator.EsqlQueryGenerator; +import org.elasticsearch.xpack.esql.generator.QueryExecutor; +import org.elasticsearch.xpack.esql.generator.command.CommandGenerator; import java.util.List; import java.util.Map; @@ -25,10 +27,11 @@ public class StatsGenerator implements CommandGenerator { @Override public CommandDescription generate( List previousCommands, - List previousOutput, - QuerySchema schema + List previousOutput, + QuerySchema schema, + QueryExecutor executor ) { - List nonNull = previousOutput.stream() + List nonNull = previousOutput.stream() .filter(EsqlQueryGenerator::fieldCanBeUsed) .filter(x -> x.type().equals("null") == false) .collect(Collectors.toList()); @@ -69,9 +72,9 @@ public CommandDescription generate( public ValidationResult validateOutput( List previousCommands, CommandDescription commandDescription, - List previousColumns, + List previousColumns, List> previousOutput, - List columns, + List columns, List> output ) { // TODO validate columns diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/WhereGenerator.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/WhereGenerator.java similarity index 79% rename from x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/WhereGenerator.java rename to x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/WhereGenerator.java index 28d9f563896d9..00a7f6cfe76d2 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/WhereGenerator.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/WhereGenerator.java @@ -5,10 +5,12 @@ * 2.0. */ -package org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe; +package org.elasticsearch.xpack.esql.generator.command.pipe; -import org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator; -import org.elasticsearch.xpack.esql.qa.rest.generative.command.CommandGenerator; +import org.elasticsearch.xpack.esql.generator.Column; +import org.elasticsearch.xpack.esql.generator.EsqlQueryGenerator; +import org.elasticsearch.xpack.esql.generator.QueryExecutor; +import org.elasticsearch.xpack.esql.generator.command.CommandGenerator; import java.util.List; import java.util.Map; @@ -21,7 +23,7 @@ public class WhereGenerator implements CommandGenerator { public static final String WHERE = "where"; public static final CommandGenerator INSTANCE = new WhereGenerator(); - public static String randomExpression(final int nConditions, List previousOutput) { + public static String randomExpression(final int nConditions, List previousOutput) { // TODO more complex conditions var result = new StringBuilder(); @@ -46,8 +48,9 @@ public static String randomExpression(final int nConditions, List previousCommands, - List previousOutput, - QuerySchema schema + List previousOutput, + QuerySchema schema, + QueryExecutor executor ) { String expression = randomExpression(randomIntBetween(1, 5), previousOutput); if (expression == null) { @@ -60,9 +63,9 @@ public CommandDescription generate( public ValidationResult validateOutput( List previousCommands, CommandDescription commandDescription, - List previousColumns, + List previousColumns, List> previousOutput, - List columns, + List columns, List> output ) { return CommandGenerator.expectSameColumns(previousColumns, columns); diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/source/FromGenerator.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/source/FromGenerator.java similarity index 68% rename from x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/source/FromGenerator.java rename to x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/source/FromGenerator.java index 05cd307a50755..13bfe92692f1b 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/source/FromGenerator.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/source/FromGenerator.java @@ -5,16 +5,17 @@ * 2.0. */ -package org.elasticsearch.xpack.esql.qa.rest.generative.command.source; +package org.elasticsearch.xpack.esql.generator.command.source; -import org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator; -import org.elasticsearch.xpack.esql.qa.rest.generative.command.CommandGenerator; +import org.elasticsearch.xpack.esql.generator.Column; +import org.elasticsearch.xpack.esql.generator.EsqlQueryGenerator; +import org.elasticsearch.xpack.esql.generator.QueryExecutor; +import org.elasticsearch.xpack.esql.generator.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 FromGenerator implements CommandGenerator { @@ -23,14 +24,15 @@ public class FromGenerator implements CommandGenerator { @Override public CommandDescription generate( List previousCommands, - List previousOutput, - QuerySchema schema + List previousOutput, + QuerySchema schema, + QueryExecutor executor ) { StringBuilder result = new StringBuilder("from "); 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))); + String pattern = EsqlQueryGenerator.indexPattern(availableIndices.get(randomIntBetween(0, availableIndices.size() - 1))); if (i > 0) { result.append(","); } @@ -44,9 +46,9 @@ public CommandDescription generate( public ValidationResult validateOutput( List previousCommands, CommandDescription commandDescription, - List previousColumns, + List previousColumns, List> previousOutput, - List columns, + List columns, List> output ) { return VALIDATION_OK; From d38cdd0ce4b3d59c66f67f192ce674d3532b3694 Mon Sep 17 00:00:00 2001 From: Luigi Dell'Aquila Date: Mon, 15 Sep 2025 14:16:45 +0200 Subject: [PATCH 2/2] Javadoc --- .../qa/rest/generative/GenerativeRestTest.java | 10 +++++----- .../xpack/esql/generator/Column.java | 6 ++++++ .../xpack/esql/generator/QueryExecuted.java | 8 ++++++++ .../xpack/esql/generator/QueryExecutor.java | 17 ++++++++++++++++- 4 files changed, 35 insertions(+), 6 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 759952e5de556..489ebb4a65858 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 @@ -199,22 +199,22 @@ private static boolean isAllowedError(String errorMessage, Pattern allowedPatter @Override @SuppressWarnings("unchecked") - public QueryExecuted execute(String command, int depth) { + public QueryExecuted execute(String query, int depth) { try { Map json = RestEsqlTestCase.runEsql( - new RestEsqlTestCase.RequestObjectBuilder().query(command).build(), + new RestEsqlTestCase.RequestObjectBuilder().query(query).build(), new AssertWarnings.AllowedRegexes(List.of(Pattern.compile(".*"))),// we don't care about warnings profileLogger, RestEsqlTestCase.Mode.SYNC ); List outputSchema = outputSchema(json); List> values = (List>) json.get("values"); - return new QueryExecuted(command, depth, outputSchema, values, null); + return new QueryExecuted(query, depth, outputSchema, values, null); } catch (Exception e) { - return new QueryExecuted(command, depth, null, null, e); + return new QueryExecuted(query, depth, null, null, e); } catch (AssertionError ae) { // this is for ensureNoWarnings() - return new QueryExecuted(command, depth, null, null, new RuntimeException(ae.getMessage())); + return new QueryExecuted(query, depth, null, null, new RuntimeException(ae.getMessage())); } } diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/Column.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/Column.java index 43f7776aadb87..8159c920ded77 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/Column.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/Column.java @@ -9,4 +9,10 @@ import java.util.List; +/** + * A column in the output schema of a query execution. + * @param name the field name + * @param type the field type + * @param originalTypes the original types, in case of a union type. + */ public record Column(String name, String type, List originalTypes) {} diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/QueryExecuted.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/QueryExecuted.java index d9a37949e5eba..44430b474bcdb 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/QueryExecuted.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/QueryExecuted.java @@ -9,4 +9,12 @@ import java.util.List; +/** + * Represents the execution of a query, in the context of generative tests. + * @param query the query string + * @param depth how many iterations have been executed in the current generative test sequence + * @param outputSchema The schema of the output + * @param result The actual results, as a list of rows. + * @param exception Null if the query was successful, otherwise the exception that was thrown by the execution. + */ public record QueryExecuted(String query, int depth, List outputSchema, List> result, Exception exception) {} diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/QueryExecutor.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/QueryExecutor.java index a6684aeb68ce3..37e7f01337860 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/QueryExecutor.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/QueryExecutor.java @@ -7,6 +7,21 @@ package org.elasticsearch.xpack.esql.generator; +/** + * This is used by generative tests and by command generators, + * to run queries for test or to run intermediate queries, + * eg. to compose a complex pipeline to be added to a query being generated (see ForkGenerator). + */ public interface QueryExecutor { - QueryExecuted execute(String command, int depth); + /** + * Execute the given command, returning the results. + * The depth is used to avoid infinite loops when commands generate sub-queries that are executed. + * @param query The command to execute + * @param depth Represents the number of iterations executed in current generative test sequence. + * It does not always correspond to the number of commands in the query, because some + * command generators may generate more than one command at a time. + * This value has to be passed to the resulting QueryExecuted. + * @return The results of the execution + */ + QueryExecuted execute(String query, int depth); }