Skip to content

Commit 72ad539

Browse files
upper limit on the number of rounding points when converting roundTo to range queries
1 parent d43363e commit 72ad539

File tree

2 files changed

+73
-1
lines changed

2 files changed

+73
-1
lines changed

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/physical/local/ReplaceRoundToWithQueryAndTags.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
package org.elasticsearch.xpack.esql.optimizer.rules.physical.local;
99

1010
import org.elasticsearch.index.query.QueryBuilder;
11+
import org.elasticsearch.logging.LogManager;
12+
import org.elasticsearch.logging.Logger;
1113
import org.elasticsearch.xpack.esql.core.expression.Alias;
1214
import org.elasticsearch.xpack.esql.core.expression.Attribute;
1315
import org.elasticsearch.xpack.esql.core.expression.Expression;
@@ -253,6 +255,11 @@ public class ReplaceRoundToWithQueryAndTags extends PhysicalOptimizerRules.Param
253255
EvalExec,
254256
LocalPhysicalOptimizerContext> {
255257

258+
// this is the maximum number of rounding points supported by this rule,
259+
// it is the same as the maximum number of buckets used in Rounding.
260+
public static final int MAX_NUM_POINTS = 127;
261+
private static final Logger logger = LogManager.getLogger(ReplaceRoundToWithQueryAndTags.class);
262+
256263
@Override
257264
protected PhysicalPlan rule(EvalExec evalExec, LocalPhysicalOptimizerContext ctx) {
258265
PhysicalPlan plan = evalExec;
@@ -268,7 +275,18 @@ protected PhysicalPlan rule(EvalExec evalExec, LocalPhysicalOptimizerContext ctx
268275
.toList();
269276
// It is not clear how to push down multiple RoundTos, dealing with multiple RoundTos is out of the scope of this PR.
270277
if (roundTos.size() == 1) {
271-
plan = planRoundTo(roundTos.get(0), evalExec, queryExec, ctx);
278+
RoundTo roundTo = roundTos.get(0);
279+
int count = roundTo.points().size();
280+
if (count > MAX_NUM_POINTS) {
281+
logger.debug(
282+
"Skipping RoundTo push down for [{}], as it has [{}] points, which is more than [{}]",
283+
roundTo.source(),
284+
count,
285+
MAX_NUM_POINTS
286+
);
287+
return evalExec;
288+
}
289+
plan = planRoundTo(roundTo, evalExec, queryExec, ctx);
272290
}
273291
}
274292
return plan;

x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/physical/local/ReplaceRoundToWithQueryAndTagsTests.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,60 @@ public void testDateTruncBucketNotTransformToQueryAndTagsWithFork() {
391391
}
392392
}
393393

394+
// If the number of rounding points is 127 or less, the query is rewritten to use QueryAndTags.
395+
// If the number of rounding points is 128 or more, the query is not rewritten.
396+
public void testRoundToTransformToQueryAndTagsUpperLimit() {
397+
for (int numOfPoints : List.of(127, 128)) {
398+
StringBuilder points = new StringBuilder();
399+
for (int i = 0; i < numOfPoints; i++) {
400+
if (i > 0) {
401+
points.append(", ");
402+
}
403+
points.append(i);
404+
}
405+
String query = LoggerMessageFormat.format(null, """
406+
from test
407+
| stats count(*) by x = round_to(integer, {})
408+
""", points.toString());
409+
410+
PhysicalPlan plan = plannerOptimizer.plan(query, searchStats, makeAnalyzer("mapping-all-types.json"));
411+
412+
LimitExec limit = as(plan, LimitExec.class);
413+
AggregateExec agg = as(limit.child(), AggregateExec.class);
414+
assertThat(agg.getMode(), is(FINAL));
415+
List<? extends Expression> groupings = agg.groupings();
416+
NamedExpression grouping = as(groupings.get(0), NamedExpression.class);
417+
assertEquals("x", grouping.name());
418+
assertEquals(DataType.INTEGER, grouping.dataType());
419+
assertEquals(List.of("count(*)", "x"), Expressions.names(agg.aggregates()));
420+
ExchangeExec exchange = as(agg.child(), ExchangeExec.class);
421+
assertThat(exchange.inBetweenAggs(), is(true));
422+
agg = as(exchange.child(), AggregateExec.class);
423+
EvalExec evalExec = as(agg.child(), EvalExec.class);
424+
List<Alias> aliases = evalExec.fields();
425+
assertEquals(1, aliases.size());
426+
if (numOfPoints == 127) {
427+
FieldAttribute roundToTag = as(aliases.get(0).child(), FieldAttribute.class);
428+
assertTrue(roundToTag.name().startsWith("$$integer$round_to$"));
429+
EsQueryExec esQueryExec = as(evalExec.child(), EsQueryExec.class);
430+
List<EsQueryExec.QueryBuilderAndTags> queryBuilderAndTags = esQueryExec.queryBuilderAndTags();
431+
assertEquals(128, queryBuilderAndTags.size()); // 127 + nullBucket
432+
assertThrows(UnsupportedOperationException.class, esQueryExec::query);
433+
} else { // numOfPoints == 128, query rewrite does not happen
434+
RoundTo roundTo = as(aliases.get(0).child(), RoundTo.class);
435+
assertEquals(128, roundTo.points().size());
436+
FieldExtractExec fieldExtractExec = as(evalExec.child(), FieldExtractExec.class);
437+
EsQueryExec esQueryExec = as(fieldExtractExec.child(), EsQueryExec.class);
438+
List<EsQueryExec.QueryBuilderAndTags> queryBuilderAndTags = esQueryExec.queryBuilderAndTags();
439+
assertEquals(1, queryBuilderAndTags.size());
440+
EsQueryExec.QueryBuilderAndTags queryBuilder = queryBuilderAndTags.get(0);
441+
assertNull(queryBuilder.query());
442+
assertTrue(queryBuilder.tags().isEmpty());
443+
assertNull(esQueryExec.query());
444+
}
445+
}
446+
}
447+
394448
private static void verifyQueryAndTags(List<EsQueryExec.QueryBuilderAndTags> expected, List<EsQueryExec.QueryBuilderAndTags> actual) {
395449
assertEquals(expected.size(), actual.size());
396450
for (int i = 0; i < expected.size(); i++) {

0 commit comments

Comments
 (0)