From ee61a6808d00e7ad8e7bab0d03108289a82ed62d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Cea=20Fontenla?= Date: Fri, 13 Mar 2026 18:05:56 +0100 Subject: [PATCH 1/8] ESQL: Add generative tests for LIMIT BY --- .../esql/generator/EsqlQueryGenerator.java | 2 + .../command/pipe/LimitByGenerator.java | 147 ++++++++++++++++++ .../function/FullTextFunctionGenerator.java | 1 + 3 files changed, 150 insertions(+) create mode 100644 x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/LimitByGenerator.java diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/EsqlQueryGenerator.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/EsqlQueryGenerator.java index 0bd06e3b05cab..e22042f90acc3 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/EsqlQueryGenerator.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/EsqlQueryGenerator.java @@ -19,6 +19,7 @@ import org.elasticsearch.xpack.esql.generator.command.pipe.GrokGenerator; import org.elasticsearch.xpack.esql.generator.command.pipe.InlineStatsGenerator; import org.elasticsearch.xpack.esql.generator.command.pipe.KeepGenerator; +import org.elasticsearch.xpack.esql.generator.command.pipe.LimitByGenerator; 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; @@ -113,6 +114,7 @@ public class EsqlQueryGenerator { GrokGenerator.INSTANCE, KeepGenerator.INSTANCE, InlineStatsGenerator.INSTANCE, + LimitByGenerator.INSTANCE, LimitGenerator.INSTANCE, LookupJoinGenerator.INSTANCE, MvExpandGenerator.INSTANCE, diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/LimitByGenerator.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/LimitByGenerator.java new file mode 100644 index 0000000000000..b007352af5cf8 --- /dev/null +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/LimitByGenerator.java @@ -0,0 +1,147 @@ +/* + * 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.command.pipe; + +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; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.elasticsearch.test.ESTestCase.randomIntBetween; + +public class LimitByGenerator implements CommandGenerator { + + public static final String LIMIT_BY = "limit_by"; + public static final String LIMIT = "limit"; + public static final String GROUPINGS = "groupings"; + public static final CommandGenerator INSTANCE = new LimitByGenerator(); + + @Override + public CommandDescription generate( + List previousCommands, + List previousOutput, + QuerySchema schema, + QueryExecutor executor + ) { + List groupable = previousOutput.stream() + .filter(EsqlQueryGenerator::groupable) + .filter(EsqlQueryGenerator::fieldCanBeUsed) + .toList(); + if (groupable.isEmpty()) { + return EMPTY_DESCRIPTION; + } + + int limit = randomIntBetween(0, 100); + int groupingCount = randomIntBetween(1, Math.min(3, groupable.size())); + Set groupings = new LinkedHashSet<>(); + for (int i = 0; i < groupingCount; i++) { + String col = EsqlQueryGenerator.randomGroupableName(groupable); + if (col != null) { + groupings.add(col); + } + } + if (groupings.isEmpty()) { + return EMPTY_DESCRIPTION; + } + + String cmd = " | LIMIT " + limit + " BY " + String.join(", ", groupings); + return new CommandDescription(LIMIT_BY, this, cmd, Map.of(LIMIT, limit, GROUPINGS, List.copyOf(groupings))); + } + + @Override + public ValidationResult validateOutput( + List previousCommands, + CommandDescription commandDescription, + List previousColumns, + List> previousOutput, + List columns, + List> output + ) { + int limit = (int) commandDescription.context().get(LIMIT); + + if (output.size() > previousOutput.size()) { + return new ValidationResult( + false, + "LIMIT BY should not increase row count: was [" + previousOutput.size() + "], got [" + output.size() + "]" + ); + } + + if (limit == 0 && output.size() > 0) { + return new ValidationResult(false, "LIMIT 0 BY should return no rows, got [" + output.size() + "]"); + } + + ValidationResult columnsResult = CommandGenerator.expectSameColumns(previousCommands, previousColumns, columns); + if (columnsResult.success() == false) { + return columnsResult; + } + + return validatePerGroupRowCounts(commandDescription, columns, output, limit); + } + + @SuppressWarnings("unchecked") + private static ValidationResult validatePerGroupRowCounts( + CommandDescription commandDescription, + List columns, + List> output, + int limit + ) { + List groupings = (List) commandDescription.context().get(GROUPINGS); + + List groupingIndices = new ArrayList<>(groupings.size()); + for (String grouping : groupings) { + String rawName = EsqlQueryGenerator.unquote(grouping); + int idx = -1; + for (int i = 0; i < columns.size(); i++) { + if (columns.get(i).name().equals(rawName)) { + idx = i; + break; + } + } + if (idx == -1) { + return VALIDATION_OK; + } + groupingIndices.add(idx); + } + + Map, Integer> groupCounts = new HashMap<>(); + for (List row : output) { + List key = new ArrayList<>(groupingIndices.size()); + for (int idx : groupingIndices) { + Object value = row.get(idx); + key.add(value); + } + groupCounts.merge(key, 1, Integer::sum); + } + + for (var entry : groupCounts.entrySet()) { + if (entry.getValue() > limit) { + return new ValidationResult( + false, + "LIMIT " + + limit + + " BY: group " + + entry.getKey() + + " has [" + + entry.getValue() + + "] rows, expected at most [" + + limit + + "]" + ); + } + } + + return VALIDATION_OK; + } +} diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/function/FullTextFunctionGenerator.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/function/FullTextFunctionGenerator.java index 83af796d01da9..36e1f216ac340 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/function/FullTextFunctionGenerator.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/function/FullTextFunctionGenerator.java @@ -42,6 +42,7 @@ private static boolean isFullTextAllowed(List Date: Mon, 16 Mar 2026 13:12:21 +0100 Subject: [PATCH 2/8] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../xpack/esql/generator/command/pipe/LimitByGenerator.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/LimitByGenerator.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/LimitByGenerator.java index b007352af5cf8..ef85957d67f12 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/LimitByGenerator.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/LimitByGenerator.java @@ -35,6 +35,9 @@ public CommandDescription generate( QuerySchema schema, QueryExecutor executor ) { + if (previousCommands != null && previousCommands.stream().anyMatch(cmd -> "sort".equals(cmd.commandName()))) { + return EMPTY_DESCRIPTION; + } List groupable = previousOutput.stream() .filter(EsqlQueryGenerator::groupable) .filter(EsqlQueryGenerator::fieldCanBeUsed) From 15fc7f12be31ce1fa531a923db28dfb59324f97e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Cea=20Fontenla?= Date: Mon, 16 Mar 2026 13:24:53 +0100 Subject: [PATCH 3/8] Minor improvements --- .../command/pipe/InlineStatsGenerator.java | 2 +- .../command/pipe/LimitByGenerator.java | 16 +++++++-------- .../function/FullTextFunctionGenerator.java | 20 +++++++++++++------ 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/InlineStatsGenerator.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/InlineStatsGenerator.java index 973f459777ecf..28153e37c104b 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/InlineStatsGenerator.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/InlineStatsGenerator.java @@ -13,7 +13,7 @@ import java.util.List; public class InlineStatsGenerator extends StatsGenerator { - public static final String INLINE_STATS = "inline stats"; + public static final String INLINE_STATS = "inline_stats"; public static final CommandGenerator INSTANCE = new InlineStatsGenerator(); @Override diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/LimitByGenerator.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/LimitByGenerator.java index ef85957d67f12..f234b633a4fa3 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/LimitByGenerator.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/LimitByGenerator.java @@ -22,11 +22,11 @@ import static org.elasticsearch.test.ESTestCase.randomIntBetween; public class LimitByGenerator implements CommandGenerator { - - public static final String LIMIT_BY = "limit_by"; - public static final String LIMIT = "limit"; - public static final String GROUPINGS = "groupings"; public static final CommandGenerator INSTANCE = new LimitByGenerator(); + public static final String LIMIT_BY = "limit_by"; + + private static final String LIMIT_CONTEXT = "limit"; + public static final String GROUPINGS_CONTEXT = "groupings"; @Override public CommandDescription generate( @@ -35,7 +35,7 @@ public CommandDescription generate( QuerySchema schema, QueryExecutor executor ) { - if (previousCommands != null && previousCommands.stream().anyMatch(cmd -> "sort".equals(cmd.commandName()))) { + if (previousCommands.stream().anyMatch(cmd -> cmd.commandName().equals(SortGenerator.SORT))) { return EMPTY_DESCRIPTION; } List groupable = previousOutput.stream() @@ -60,7 +60,7 @@ public CommandDescription generate( } String cmd = " | LIMIT " + limit + " BY " + String.join(", ", groupings); - return new CommandDescription(LIMIT_BY, this, cmd, Map.of(LIMIT, limit, GROUPINGS, List.copyOf(groupings))); + return new CommandDescription(LIMIT_BY, this, cmd, Map.of(LIMIT_CONTEXT, limit, GROUPINGS_CONTEXT, List.copyOf(groupings))); } @Override @@ -72,7 +72,7 @@ public ValidationResult validateOutput( List columns, List> output ) { - int limit = (int) commandDescription.context().get(LIMIT); + int limit = (int) commandDescription.context().get(LIMIT_CONTEXT); if (output.size() > previousOutput.size()) { return new ValidationResult( @@ -100,7 +100,7 @@ private static ValidationResult validatePerGroupRowCounts( List> output, int limit ) { - List groupings = (List) commandDescription.context().get(GROUPINGS); + List groupings = (List) commandDescription.context().get(GROUPINGS_CONTEXT); List groupingIndices = new ArrayList<>(groupings.size()); for (String grouping : groupings) { diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/function/FullTextFunctionGenerator.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/function/FullTextFunctionGenerator.java index 36e1f216ac340..10e5bd630b7c1 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/function/FullTextFunctionGenerator.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/function/FullTextFunctionGenerator.java @@ -9,6 +9,12 @@ import org.elasticsearch.xpack.esql.generator.Column; 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.InlineStatsGenerator; +import org.elasticsearch.xpack.esql.generator.command.pipe.LimitByGenerator; +import org.elasticsearch.xpack.esql.generator.command.pipe.LimitGenerator; +import org.elasticsearch.xpack.esql.generator.command.pipe.MvExpandGenerator; +import org.elasticsearch.xpack.esql.generator.command.pipe.StatsGenerator; import java.util.ArrayList; import java.util.HashSet; @@ -41,12 +47,14 @@ private static boolean isFullTextAllowed(List Date: Wed, 18 Mar 2026 16:00:41 +0100 Subject: [PATCH 4/8] Removed flaky check --- .../esql/generator/command/pipe/LimitByGenerator.java | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/LimitByGenerator.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/LimitByGenerator.java index f234b633a4fa3..7dae662cda019 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/LimitByGenerator.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/LimitByGenerator.java @@ -74,14 +74,7 @@ public ValidationResult validateOutput( ) { int limit = (int) commandDescription.context().get(LIMIT_CONTEXT); - if (output.size() > previousOutput.size()) { - return new ValidationResult( - false, - "LIMIT BY should not increase row count: was [" + previousOutput.size() + "], got [" + output.size() + "]" - ); - } - - if (limit == 0 && output.size() > 0) { + if (limit == 0 && output.isEmpty() == false) { return new ValidationResult(false, "LIMIT 0 BY should return no rows, got [" + output.size() + "]"); } From 488a421c16a9539493ed1a68e95759c22b8ed85c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Cea=20Fontenla?= Date: Mon, 6 Apr 2026 12:34:10 +0200 Subject: [PATCH 5/8] Fix INLINE_STATS command name in Generative tests --- .../xpack/esql/generator/command/pipe/StatsGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/StatsGenerator.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/StatsGenerator.java index b7cc4bef22f54..25896cc9740fb 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/StatsGenerator.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/StatsGenerator.java @@ -43,7 +43,7 @@ public CommandDescription generate( return EMPTY_DESCRIPTION; } StringBuilder cmd = new StringBuilder(" | "); - cmd.append(commandName()); + cmd.append(commandName().replace("_", " ")); cmd.append(" "); int nStats = randomIntBetween(1, 5); for (int i = 0; i < nStats; i++) { From b6a06b46552b630a23ff6e2c93b665af685753d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Cea=20Fontenla?= Date: Mon, 6 Apr 2026 12:39:11 +0200 Subject: [PATCH 6/8] Remove SORT+LIMIT BY limitation from LimitByGenerator --- .../xpack/esql/generator/command/pipe/LimitByGenerator.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/LimitByGenerator.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/LimitByGenerator.java index 7dae662cda019..92ed7a85b9b1f 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/LimitByGenerator.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/LimitByGenerator.java @@ -35,9 +35,6 @@ public CommandDescription generate( QuerySchema schema, QueryExecutor executor ) { - if (previousCommands.stream().anyMatch(cmd -> cmd.commandName().equals(SortGenerator.SORT))) { - return EMPTY_DESCRIPTION; - } List groupable = previousOutput.stream() .filter(EsqlQueryGenerator::groupable) .filter(EsqlQueryGenerator::fieldCanBeUsed) From da51338e993edb26417bf9704d7493e15fe164a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Cea=20Fontenla?= Date: Mon, 6 Apr 2026 15:21:20 +0200 Subject: [PATCH 7/8] Fix using command gemerator static labels in GenerativeRestTests and improve LimitBy validation of columns --- .../rest/generative/GenerativeRestTest.java | 28 ++++++++++++------- .../command/pipe/LimitByGenerator.java | 10 ++++++- .../function/FullTextFunctionGenerator.java | 21 ++++++++------ 3 files changed, 40 insertions(+), 19 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 604aad0991243..96e6667ad7616 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 @@ -20,9 +20,17 @@ 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.generator.command.pipe.DissectGenerator; 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.GrokGenerator; +import org.elasticsearch.xpack.esql.generator.command.pipe.InlineStatsGenerator; 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.RegisteredDomainGenerator; +import org.elasticsearch.xpack.esql.generator.command.pipe.RenameGenerator; +import org.elasticsearch.xpack.esql.generator.command.pipe.StatsGenerator; +import org.elasticsearch.xpack.esql.generator.command.pipe.UriPartsGenerator; import org.elasticsearch.xpack.esql.generator.command.source.FromGenerator; import org.elasticsearch.xpack.esql.qa.rest.ProfileLogger; import org.elasticsearch.xpack.esql.qa.rest.RestEsqlTestCase; @@ -554,19 +562,19 @@ static List updateIndexMapped( Set createdColumns = new HashSet<>(); switch (commandName) { - case "eval" -> { + case EvalGenerator.EVAL -> { Object newCols = command.context().get(EvalGenerator.NEW_COLUMNS); if (newCols instanceof List list) { list.forEach(name -> createdColumns.add((String) name)); } } - case "grok" -> { + case GrokGenerator.GROK -> { Matcher gm = GROK_GENERATED_FIELD_PATTERN.matcher(command.commandString()); while (gm.find()) { createdColumns.add(unquote(gm.group(1))); } } - case "dissect" -> { + case DissectGenerator.DISSECT -> { Matcher dm = DISSECT_GENERATED_FIELD_PATTERN.matcher(command.commandString()); while (dm.find()) { String generated = dm.group(1); @@ -575,19 +583,19 @@ static List updateIndexMapped( } } } - case "mv_expand" -> { + case MvExpandGenerator.MV_EXPAND -> { String expanded = command.commandString().replaceFirst("(?i)^\\s*\\|\\s*mv_expand\\s+", "").trim(); // Not truly a newly created column, but we need to override the indexMapped flag so that full-text functions don't use it. // https://github.com/elastic/elasticsearch/issues/142713 createdColumns.add(unquote(expanded)); } - case "stats", "inline stats" -> { + case StatsGenerator.STATS, InlineStatsGenerator.INLINE_STATS -> { return newSchema.stream().map(col -> new Column(col.name(), col.type(), col.originalTypes(), false)).toList(); } - case "rename" -> { + case RenameGenerator.RENAME -> { return handleRenameIndexMapped(newSchema, prevMapped, command.commandString()); } - case "registered_domain" -> { + case RegisteredDomainGenerator.REGISTERED_DOMAIN -> { String prefix = (String) command.context().get("prefix"); if (prefix != null) { for (String subField : List.of("domain", "registered_domain", "top_level_domain", "subdomain")) { @@ -595,7 +603,7 @@ static List updateIndexMapped( } } } - case "uri_parts" -> { + case UriPartsGenerator.URI_PARTS -> { String prefix = (String) command.context().get("prefix"); if (prefix != null) { for (Column col : newSchema) { @@ -605,7 +613,7 @@ static List updateIndexMapped( } } } - case "enrich" -> { + case EnrichGenerator.ENRICH -> { // Enrich fields can shadow existing index columns, so we use the policy's declared enrich_fields // from the context to ensure they are marked as non-index-mapped even when names collide. Object enrichFieldsObj = command.context().get(EnrichGenerator.ENRICH_FIELDS); @@ -613,7 +621,7 @@ static List updateIndexMapped( enrichFieldsList.forEach(name -> createdColumns.add((String) name)); } } - case "lookup join" -> { + case LookupJoinGenerator.LOOKUP_JOIN -> { // LookupJoinGenerator embeds RENAME commands before the actual LOOKUP JOIN to align // left-side key columns with lookup index key names. Process these renames so that // fields renamed from non-index-mapped sources correctly inherit indexMapped=false diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/LimitByGenerator.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/LimitByGenerator.java index 92ed7a85b9b1f..0bc82f078f6e8 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/LimitByGenerator.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/LimitByGenerator.java @@ -103,7 +103,15 @@ private static ValidationResult validatePerGroupRowCounts( } } if (idx == -1) { - return VALIDATION_OK; + return new ValidationResult( + false, + "LIMIT " + + limit + + " BY: grouping column [" + + rawName + + "] was not found in the output schema. Available columns: " + + columns.stream().map(Column::name).toList() + ); } groupingIndices.add(idx); } diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/function/FullTextFunctionGenerator.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/function/FullTextFunctionGenerator.java index 03caa5f96443a..b2f88c8ffed08 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/function/FullTextFunctionGenerator.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/function/FullTextFunctionGenerator.java @@ -35,6 +35,18 @@ private FullTextFunctionGenerator() {} private static final Set QSTR_KQL_SAFE_COMMANDS = Set.of("from", "where", "sort"); + /** + * Commands after which full-text expressions (match, qstr, kql, etc.) are not allowed. + */ + private static final Set FULL_TEXT_FORBIDDEN_AFTER_COMMANDS = Set.of( + LimitGenerator.LIMIT, + LimitByGenerator.LIMIT_BY, + StatsGenerator.STATS, + InlineStatsGenerator.INLINE_STATS, + ChangePointGenerator.CHANGE_POINT, + MvExpandGenerator.MV_EXPAND + ); + private static boolean isFullTextAllowed(List previousCommands) { if (previousCommands == null || previousCommands.isEmpty()) { return false; @@ -43,14 +55,7 @@ private static boolean isFullTextAllowed(List Date: Tue, 7 Apr 2026 10:48:53 +0200 Subject: [PATCH 8/8] Make field private --- .../xpack/esql/generator/command/pipe/LimitByGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/LimitByGenerator.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/LimitByGenerator.java index 0bc82f078f6e8..a13cc7954f7dd 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/LimitByGenerator.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/generator/command/pipe/LimitByGenerator.java @@ -26,7 +26,7 @@ public class LimitByGenerator implements CommandGenerator { public static final String LIMIT_BY = "limit_by"; private static final String LIMIT_CONTEXT = "limit"; - public static final String GROUPINGS_CONTEXT = "groupings"; + private static final String GROUPINGS_CONTEXT = "groupings"; @Override public CommandDescription generate(