Skip to content

Commit 04ae527

Browse files
[ES|QL] Substitute date_trunc with round_to when the pre-calculated rounding points are available (#128639)
* consolidate min/max in SearchStats and substitue date_trunc/bucket with round_to
1 parent c2fa78f commit 04ae527

File tree

16 files changed

+568
-125
lines changed

16 files changed

+568
-125
lines changed

docs/changelog/128639.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 128639
2+
summary: Substitue `date_trunc` with `round_to` when the pre-calculated rounding points
3+
are available
4+
area: ES|QL
5+
type: enhancement
6+
issues: []

x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/EsqlTestUtils.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,12 +290,12 @@ public long count(FieldName field, BytesRef value) {
290290
}
291291

292292
@Override
293-
public byte[] min(FieldName field, DataType dataType) {
293+
public Object min(FieldName field) {
294294
return null;
295295
}
296296

297297
@Override
298-
public byte[] max(FieldName field, DataType dataType) {
298+
public Object max(FieldName field) {
299299
return null;
300300
}
301301

@@ -381,6 +381,27 @@ public String toString() {
381381
}
382382
}
383383

384+
public static class TestSearchStatsWithMinMax extends TestSearchStats {
385+
386+
private final Map<String, Object> minValues;
387+
private final Map<String, Object> maxValues;
388+
389+
public TestSearchStatsWithMinMax(Map<String, Object> minValues, Map<String, Object> maxValues) {
390+
this.minValues = minValues;
391+
this.maxValues = maxValues;
392+
}
393+
394+
@Override
395+
public Object min(FieldName field) {
396+
return minValues.get(field.string());
397+
}
398+
399+
@Override
400+
public Object max(FieldName field) {
401+
return maxValues.get(field.string());
402+
}
403+
}
404+
384405
public static final TestSearchStats TEST_SEARCH_STATS = new TestSearchStats();
385406

386407
private static final Map<String, Map<String, Column>> TABLES = tables();
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.esql.expression;
9+
10+
import org.elasticsearch.xpack.esql.core.expression.Expression;
11+
import org.elasticsearch.xpack.esql.stats.SearchStats;
12+
13+
/**
14+
* Interface signaling to the local logical plan optimizer that the declaring expression
15+
* has to be replaced by a different form.
16+
* Implement this on {@code Function}s when:
17+
* <ul>
18+
* <li>The expression can be rewritten to another expression on data node, with the statistics available in SearchStats.
19+
* Like {@code DateTrunc} and {@code Bucket} could be rewritten to {@code RoundTo} with the min/max values on the date field.
20+
* </li>
21+
* </ul>
22+
*/
23+
public interface LocalSurrogateExpression {
24+
/**
25+
* Returns the expression to be replaced by or {@code null} if this cannot be replaced.
26+
*/
27+
Expression surrogate(SearchStats searchStats);
28+
}

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/grouping/Bucket.java

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.elasticsearch.xpack.esql.core.tree.NodeInfo;
2626
import org.elasticsearch.xpack.esql.core.tree.Source;
2727
import org.elasticsearch.xpack.esql.core.type.DataType;
28+
import org.elasticsearch.xpack.esql.expression.LocalSurrogateExpression;
2829
import org.elasticsearch.xpack.esql.expression.function.Example;
2930
import org.elasticsearch.xpack.esql.expression.function.FunctionInfo;
3031
import org.elasticsearch.xpack.esql.expression.function.FunctionType;
@@ -35,10 +36,9 @@
3536
import org.elasticsearch.xpack.esql.expression.predicate.operator.arithmetic.Div;
3637
import org.elasticsearch.xpack.esql.expression.predicate.operator.arithmetic.Mul;
3738
import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput;
39+
import org.elasticsearch.xpack.esql.stats.SearchStats;
3840

3941
import java.io.IOException;
40-
import java.time.ZoneId;
41-
import java.time.ZoneOffset;
4242
import java.util.ArrayList;
4343
import java.util.List;
4444

@@ -50,6 +50,8 @@
5050
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isNumeric;
5151
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isType;
5252
import static org.elasticsearch.xpack.esql.expression.Validations.isFoldable;
53+
import static org.elasticsearch.xpack.esql.expression.function.scalar.date.DateTrunc.maybeSubstituteWithRoundTo;
54+
import static org.elasticsearch.xpack.esql.session.Configuration.DEFAULT_TZ;
5355
import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.dateTimeToLong;
5456

5557
/**
@@ -61,7 +63,8 @@
6163
public class Bucket extends GroupingFunction.EvaluatableGroupingFunction
6264
implements
6365
PostOptimizationVerificationAware,
64-
TwoOptionalArguments {
66+
TwoOptionalArguments,
67+
LocalSurrogateExpression {
6568
public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "Bucket", Bucket::new);
6669

6770
// TODO maybe we should just cover the whole of representable dates here - like ten years, 100 years, 1000 years, all the way up.
@@ -87,8 +90,6 @@ public class Bucket extends GroupingFunction.EvaluatableGroupingFunction
8790
Rounding.builder(TimeValue.timeValueMillis(10)).build(),
8891
Rounding.builder(TimeValue.timeValueMillis(1)).build(), };
8992

90-
private static final ZoneId DEFAULT_TZ = ZoneOffset.UTC; // TODO: plug in the config
91-
9293
private final Expression field;
9394
private final Expression buckets;
9495
private final Expression from;
@@ -301,15 +302,22 @@ public Rounding.Prepared getDateRoundingOrNull(FoldContext foldCtx) {
301302
}
302303

303304
private Rounding.Prepared getDateRounding(FoldContext foldContext) {
305+
return getDateRounding(foldContext, null, null);
306+
}
307+
308+
private Rounding.Prepared getDateRounding(FoldContext foldContext, Long min, Long max) {
304309
assert field.dataType() == DataType.DATETIME || field.dataType() == DataType.DATE_NANOS : "expected date type; got " + field;
305310
if (buckets.dataType().isWholeNumber()) {
306311
int b = ((Number) buckets.fold(foldContext)).intValue();
307312
long f = foldToLong(foldContext, from);
308313
long t = foldToLong(foldContext, to);
314+
if (min != null && max != null) {
315+
return new DateRoundingPicker(b, f, t).pickRounding().prepare(min, max);
316+
}
309317
return new DateRoundingPicker(b, f, t).pickRounding().prepareForUnknown();
310318
} else {
311319
assert DataType.isTemporalAmount(buckets.dataType()) : "Unexpected span data type [" + buckets.dataType() + "]";
312-
return DateTrunc.createRounding(buckets.fold(foldContext), DEFAULT_TZ);
320+
return DateTrunc.createRounding(buckets.fold(foldContext), DEFAULT_TZ, min, max);
313321
}
314322
}
315323

@@ -488,4 +496,17 @@ public Expression to() {
488496
public String toString() {
489497
return "Bucket{" + "field=" + field + ", buckets=" + buckets + ", from=" + from + ", to=" + to + '}';
490498
}
499+
500+
@Override
501+
public Expression surrogate(SearchStats searchStats) {
502+
// LocalSubstituteSurrogateExpressions should make sure this doesn't happen
503+
assert searchStats != null : "SearchStats cannot be null";
504+
return maybeSubstituteWithRoundTo(
505+
source(),
506+
field(),
507+
buckets(),
508+
searchStats,
509+
(interval, minValue, maxValue) -> getDateRounding(FoldContext.small(), minValue, maxValue)
510+
);
511+
}
491512
}

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/BinaryDateTimeFunction.java

Lines changed: 0 additions & 62 deletions
This file was deleted.

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiff.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
public class DateDiff extends EsqlScalarFunction {
5858
public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "DateDiff", DateDiff::new);
5959

60-
public static final ZoneId UTC = ZoneId.of("Z");
60+
public static final ZoneId UTC = org.elasticsearch.xpack.esql.core.util.DateUtils.UTC;
6161

6262
private final Expression unit;
6363
private final Expression startTimestamp;

0 commit comments

Comments
 (0)