Skip to content

Commit 90f0908

Browse files
ReplaceDateTruncBucketWithRoundTo
1 parent 9f66fa6 commit 90f0908

File tree

7 files changed

+213
-257
lines changed

7 files changed

+213
-257
lines changed

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/LocalSurrogateExpression.java

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

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

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
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;
2928
import org.elasticsearch.xpack.esql.expression.function.Example;
3029
import org.elasticsearch.xpack.esql.expression.function.FunctionInfo;
3130
import org.elasticsearch.xpack.esql.expression.function.FunctionType;
@@ -35,9 +34,7 @@
3534
import org.elasticsearch.xpack.esql.expression.function.scalar.math.Floor;
3635
import org.elasticsearch.xpack.esql.expression.predicate.operator.arithmetic.Div;
3736
import org.elasticsearch.xpack.esql.expression.predicate.operator.arithmetic.Mul;
38-
import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.EsqlBinaryComparison;
3937
import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput;
40-
import org.elasticsearch.xpack.esql.stats.SearchStats;
4138

4239
import java.io.IOException;
4340
import java.util.ArrayList;
@@ -51,7 +48,6 @@
5148
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isNumeric;
5249
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isType;
5350
import static org.elasticsearch.xpack.esql.expression.Validations.isFoldable;
54-
import static org.elasticsearch.xpack.esql.expression.function.scalar.date.DateTrunc.maybeSubstituteWithRoundTo;
5551
import static org.elasticsearch.xpack.esql.session.Configuration.DEFAULT_TZ;
5652
import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.dateTimeToLong;
5753

@@ -64,8 +60,7 @@
6460
public class Bucket extends GroupingFunction.EvaluatableGroupingFunction
6561
implements
6662
PostOptimizationVerificationAware,
67-
TwoOptionalArguments,
68-
LocalSurrogateExpression {
63+
TwoOptionalArguments {
6964
public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "Bucket", Bucket::new);
7065

7166
// TODO maybe we should just cover the whole of representable dates here - like ten years, 100 years, 1000 years, all the way up.
@@ -306,7 +301,7 @@ private Rounding.Prepared getDateRounding(FoldContext foldContext) {
306301
return getDateRounding(foldContext, null, null);
307302
}
308303

309-
private Rounding.Prepared getDateRounding(FoldContext foldContext, Long min, Long max) {
304+
public Rounding.Prepared getDateRounding(FoldContext foldContext, Long min, Long max) {
310305
assert field.dataType() == DataType.DATETIME || field.dataType() == DataType.DATE_NANOS : "expected date type; got " + field;
311306
if (buckets.dataType().isWholeNumber()) {
312307
int b = ((Number) buckets.fold(foldContext)).intValue();
@@ -497,18 +492,4 @@ public Expression to() {
497492
public String toString() {
498493
return "Bucket{" + "field=" + field + ", buckets=" + buckets + ", from=" + from + ", to=" + to + '}';
499494
}
500-
501-
@Override
502-
public Expression surrogate(SearchStats searchStats, List<EsqlBinaryComparison> binaryComparisons) {
503-
// LocalSubstituteSurrogateExpressions should make sure this doesn't happen
504-
assert searchStats != null : "SearchStats cannot be null";
505-
return maybeSubstituteWithRoundTo(
506-
source(),
507-
field(),
508-
buckets(),
509-
searchStats,
510-
binaryComparisons,
511-
(interval, minValue, maxValue) -> getDateRounding(FoldContext.small(), minValue, maxValue)
512-
);
513-
}
514495
}

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

Lines changed: 2 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
package org.elasticsearch.xpack.esql.expression.function.scalar.date;
99

1010
import org.elasticsearch.common.Rounding;
11-
import org.elasticsearch.common.TriFunction;
1211
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
1312
import org.elasticsearch.common.io.stream.StreamInput;
1413
import org.elasticsearch.common.io.stream.StreamOutput;
@@ -17,61 +16,38 @@
1716
import org.elasticsearch.compute.ann.Fixed;
1817
import org.elasticsearch.compute.operator.EvalOperator.ExpressionEvaluator;
1918
import org.elasticsearch.core.TimeValue;
20-
import org.elasticsearch.core.Tuple;
21-
import org.elasticsearch.logging.LogManager;
22-
import org.elasticsearch.logging.Logger;
2319
import org.elasticsearch.xpack.esql.core.expression.Expression;
24-
import org.elasticsearch.xpack.esql.core.expression.FieldAttribute;
25-
import org.elasticsearch.xpack.esql.core.expression.FoldContext;
26-
import org.elasticsearch.xpack.esql.core.expression.Literal;
2720
import org.elasticsearch.xpack.esql.core.tree.NodeInfo;
2821
import org.elasticsearch.xpack.esql.core.tree.Source;
2922
import org.elasticsearch.xpack.esql.core.type.DataType;
30-
import org.elasticsearch.xpack.esql.core.type.MultiTypeEsField;
31-
import org.elasticsearch.xpack.esql.core.util.Holder;
32-
import org.elasticsearch.xpack.esql.expression.LocalSurrogateExpression;
3323
import org.elasticsearch.xpack.esql.expression.function.Example;
3424
import org.elasticsearch.xpack.esql.expression.function.FunctionInfo;
3525
import org.elasticsearch.xpack.esql.expression.function.Param;
3626
import org.elasticsearch.xpack.esql.expression.function.scalar.EsqlScalarFunction;
37-
import org.elasticsearch.xpack.esql.expression.function.scalar.math.RoundTo;
38-
import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.Equals;
39-
import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.EsqlBinaryComparison;
40-
import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.GreaterThan;
41-
import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.GreaterThanOrEqual;
42-
import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.LessThan;
43-
import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.LessThanOrEqual;
4427
import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput;
45-
import org.elasticsearch.xpack.esql.stats.SearchStats;
4628

4729
import java.io.IOException;
4830
import java.time.Duration;
4931
import java.time.Period;
5032
import java.time.ZoneId;
51-
import java.util.Arrays;
5233
import java.util.List;
5334
import java.util.Map;
5435
import java.util.concurrent.TimeUnit;
55-
import java.util.stream.Collectors;
5636

5737
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.FIRST;
5838
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.SECOND;
5939
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isType;
6040
import static org.elasticsearch.xpack.esql.core.type.DataType.DATETIME;
6141
import static org.elasticsearch.xpack.esql.core.type.DataType.DATE_NANOS;
62-
import static org.elasticsearch.xpack.esql.core.type.DataType.isDateTime;
6342
import static org.elasticsearch.xpack.esql.session.Configuration.DEFAULT_TZ;
64-
import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.dateWithTypeToString;
6543

66-
public class DateTrunc extends EsqlScalarFunction implements LocalSurrogateExpression {
44+
public class DateTrunc extends EsqlScalarFunction {
6745
public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(
6846
Expression.class,
6947
"DateTrunc",
7048
DateTrunc::new
7149
);
7250

73-
private static final Logger logger = LogManager.getLogger(DateTrunc.class);
74-
7551
@FunctionalInterface
7652
public interface DateTruncFactoryProvider {
7753
ExpressionEvaluator.Factory apply(Source source, ExpressionEvaluator.Factory lhs, Rounding.Prepared rounding);
@@ -129,7 +105,7 @@ public String getWriteableName() {
129105
return ENTRY.name;
130106
}
131107

132-
Expression interval() {
108+
public Expression interval() {
133109
return interval;
134110
}
135111

@@ -293,96 +269,4 @@ public static ExpressionEvaluator.Factory evaluator(
293269
) {
294270
return evaluatorMap.get(forType).apply(source, fieldEvaluator, rounding);
295271
}
296-
297-
@Override
298-
public Expression surrogate(SearchStats searchStats, List<EsqlBinaryComparison> binaryComparisons) {
299-
// LocalSubstituteSurrogateExpressions should make sure this doesn't happen
300-
assert searchStats != null : "SearchStats cannot be null";
301-
return maybeSubstituteWithRoundTo(
302-
source(),
303-
field(),
304-
interval(),
305-
searchStats,
306-
binaryComparisons,
307-
(interval, minValue, maxValue) -> createRounding(interval, DEFAULT_TZ, minValue, maxValue)
308-
);
309-
}
310-
311-
public static RoundTo maybeSubstituteWithRoundTo(
312-
Source source,
313-
Expression field,
314-
Expression foldableTimeExpression,
315-
SearchStats searchStats,
316-
List<EsqlBinaryComparison> binaryComparisons,
317-
TriFunction<Object, Long, Long, Rounding.Prepared> roundingFunction
318-
) {
319-
if (field instanceof FieldAttribute fa && fa.field() instanceof MultiTypeEsField == false && isDateTime(fa.dataType())) {
320-
// Extract min/max from SearchStats
321-
DataType fieldType = fa.dataType();
322-
FieldAttribute.FieldName fieldName = fa.fieldName();
323-
var min = searchStats.min(fieldName);
324-
var max = searchStats.max(fieldName);
325-
// Extract min/max from query
326-
Tuple<Long, Long> minMaxFromPredicates = minMaxFromPredicates(binaryComparisons);
327-
Long minFromPredicates = minMaxFromPredicates.v1();
328-
Long maxFromPredicates = minMaxFromPredicates.v2();
329-
// Consolidate min/max from SearchStats and query
330-
if (minFromPredicates instanceof Long minValue) {
331-
min = min instanceof Long m ? Math.max(m, minValue) : minValue;
332-
}
333-
if (maxFromPredicates instanceof Long maxValue) {
334-
max = max instanceof Long m ? Math.min(m, maxValue) : maxValue;
335-
}
336-
// If min/max is available create rounding with them
337-
if (min instanceof Long minValue && max instanceof Long maxValue && foldableTimeExpression.foldable() && minValue <= maxValue) {
338-
Object foldedInterval = foldableTimeExpression.fold(FoldContext.small() /* TODO remove me */);
339-
Rounding.Prepared rounding = roundingFunction.apply(foldedInterval, minValue, maxValue);
340-
long[] roundingPoints = rounding.fixedRoundingPoints();
341-
if (roundingPoints == null) {
342-
logger.trace(
343-
"Fixed rounding point is null for field {}, minValue {} in string format {} and maxValue {} in string format {}",
344-
fieldName,
345-
minValue,
346-
dateWithTypeToString(minValue, fieldType),
347-
maxValue,
348-
dateWithTypeToString(maxValue, fieldType)
349-
);
350-
return null;
351-
}
352-
// Convert to round_to function with the roundings
353-
List<Expression> points = Arrays.stream(roundingPoints)
354-
.mapToObj(l -> new Literal(Source.EMPTY, l, fieldType))
355-
.collect(Collectors.toList());
356-
return new RoundTo(source, field, points);
357-
}
358-
}
359-
return null;
360-
}
361-
362-
private static Tuple<Long, Long> minMaxFromPredicates(List<EsqlBinaryComparison> binaryComparisons) {
363-
long[] min = new long[] { Long.MIN_VALUE };
364-
long[] max = new long[] { Long.MAX_VALUE };
365-
Holder<Boolean> foundMinValue = new Holder<>(false);
366-
Holder<Boolean> foundMaxValue = new Holder<>(false);
367-
for (EsqlBinaryComparison binaryComparison : binaryComparisons) {
368-
if (binaryComparison.right() instanceof Literal l) {
369-
long value = Long.parseLong(l.value().toString());
370-
if (binaryComparison instanceof Equals) {
371-
return new Tuple<>(value, value);
372-
}
373-
if (binaryComparison instanceof GreaterThan || binaryComparison instanceof GreaterThanOrEqual) {
374-
if (value >= min[0]) {
375-
min[0] = value;
376-
foundMinValue.set(true);
377-
}
378-
} else if (binaryComparison instanceof LessThan || binaryComparison instanceof LessThanOrEqual) {
379-
if (value <= max[0]) {
380-
max[0] = value;
381-
foundMaxValue.set(true);
382-
}
383-
}
384-
}
385-
}
386-
return new Tuple<>(foundMinValue.get() ? min[0] : null, foundMaxValue.get() ? max[0] : null);
387-
}
388272
}

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/LocalLogicalPlanOptimizer.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import org.elasticsearch.xpack.esql.optimizer.rules.logical.local.InferIsNotNull;
1616
import org.elasticsearch.xpack.esql.optimizer.rules.logical.local.InferNonNullAggConstraint;
1717
import org.elasticsearch.xpack.esql.optimizer.rules.logical.local.LocalPropagateEmptyRelation;
18-
import org.elasticsearch.xpack.esql.optimizer.rules.logical.local.LocalSubstituteSurrogateExpressions;
18+
import org.elasticsearch.xpack.esql.optimizer.rules.logical.local.ReplaceDateTruncBucketWithRoundTo;
1919
import org.elasticsearch.xpack.esql.optimizer.rules.logical.local.ReplaceFieldWithConstantOrNull;
2020
import org.elasticsearch.xpack.esql.optimizer.rules.logical.local.ReplaceTopNWithLimitAndSort;
2121
import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan;
@@ -48,7 +48,7 @@ public class LocalLogicalPlanOptimizer extends ParameterizedRuleExecutor<Logical
4848
new ReplaceFieldWithConstantOrNull(),
4949
new InferIsNotNull(),
5050
new InferNonNullAggConstraint(),
51-
new LocalSubstituteSurrogateExpressions()
51+
new ReplaceDateTruncBucketWithRoundTo()
5252
),
5353
localOperators(),
5454
cleanup()

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

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

0 commit comments

Comments
 (0)