diff --git a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/TimeSeriesIT.java b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/TimeSeriesIT.java index 63dd77b2a4474..bbf108b4ed424 100644 --- a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/TimeSeriesIT.java +++ b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/TimeSeriesIT.java @@ -103,7 +103,9 @@ public void populateIndex() { "memory", "type=long,time_series_metric=gauge", "request_count", - "type=integer,time_series_metric=counter" + "type=integer,time_series_metric=counter", + "description", + "type=text,index=true" ) .get(); Map hostToClusters = new HashMap<>(); @@ -145,7 +147,9 @@ public void populateIndex() { "memory", doc.memory.getBytes(), "request_count", - doc.requestCount + doc.requestCount, + "description", + "host = " + doc.host ) .get(); } @@ -200,6 +204,23 @@ public void testImplicitAggregate() { } } + public void testGroupByTextField() { + List sortedGroups = docs.stream().map(d -> d.host).distinct().sorted().toList(); + client().admin().indices().prepareRefresh("hosts").get(); + for (String fn : List.of("count", "count_distinct", "sum", "avg", "max", "min")) { + try (var resp = run("TS hosts | STATS " + fn + "(cpu) BY description | SORT description | LIMIT 1")) { + List> rows = EsqlTestUtils.getValuesList(resp); + assertThat(rows, hasSize(1)); + assertThat(rows.get(0).get(1), equalTo("host = " + sortedGroups.get(0))); + } + } + try (var resp = run("TS hosts | STATS sum(rate(request_count)) BY description | SORT description | LIMIT 1")) { + List> rows = EsqlTestUtils.getValuesList(resp); + assertThat(rows, hasSize(1)); + assertThat(rows.get(0).get(1), equalTo("host = " + sortedGroups.get(0))); + } + } + public void testRateWithoutGrouping() { record RateKey(String cluster, String host) { diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/PostOptimizationPhasePlanVerifier.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/PostOptimizationPhasePlanVerifier.java index e187264b2432f..6570dc330ffd2 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/PostOptimizationPhasePlanVerifier.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/PostOptimizationPhasePlanVerifier.java @@ -8,9 +8,13 @@ package org.elasticsearch.xpack.esql.optimizer; import org.elasticsearch.xpack.esql.common.Failures; +import org.elasticsearch.xpack.esql.core.expression.Alias; import org.elasticsearch.xpack.esql.core.expression.Attribute; +import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.aggregate.Values; import org.elasticsearch.xpack.esql.optimizer.rules.physical.ProjectAwayColumns; import org.elasticsearch.xpack.esql.plan.QueryPlan; +import org.elasticsearch.xpack.esql.plan.logical.TimeSeriesAggregate; import org.elasticsearch.xpack.esql.plan.physical.EsQueryExec; import java.util.List; @@ -72,7 +76,13 @@ private static void verifyOutputNotChanged(QueryPlan optimizedPlan, List a instanceof TimeSeriesAggregate ts + && ts.aggregates().stream().anyMatch(g -> Alias.unwrap(g) instanceof Values v && v.field().dataType() == DataType.TEXT) + ); + boolean ignoreError = hasProjectAwayColumns || hasLookupJoinExec || hasTextGroupingInTimeSeries; if (ignoreError == false) { failures.add( fail(