Skip to content

Commit 79b86c8

Browse files
[ES|QL] Rewrite RoundTo to QueryAndTags (elastic#132512)
* Rewrite RoundTo to QueryAndTags
1 parent 4e9af10 commit 79b86c8

File tree

25 files changed

+1432
-226
lines changed

25 files changed

+1432
-226
lines changed

docs/changelog/132512.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 132512
2+
summary: Rewrite `RoundTo` to `QueryAndTags`
3+
area: ES|QL
4+
type: enhancement
5+
issues: []

x-pack/plugin/esql/src/main/generated-src/org/elasticsearch/xpack/esql/expression/function/scalar/math/RoundToDouble.java

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

x-pack/plugin/esql/src/main/generated-src/org/elasticsearch/xpack/esql/expression/function/scalar/math/RoundToInt.java

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

x-pack/plugin/esql/src/main/generated-src/org/elasticsearch/xpack/esql/expression/function/scalar/math/RoundToLong.java

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.elasticsearch.xpack.esql.core.tree.NodeInfo;
1919
import org.elasticsearch.xpack.esql.core.tree.Source;
2020
import org.elasticsearch.xpack.esql.core.type.DataType;
21+
import org.elasticsearch.xpack.esql.core.type.DataTypeConverter;
2122
import org.elasticsearch.xpack.esql.expression.Foldables;
2223
import org.elasticsearch.xpack.esql.expression.function.Example;
2324
import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesTo;
@@ -30,6 +31,7 @@
3031
import java.io.IOException;
3132
import java.util.List;
3233
import java.util.Map;
34+
import java.util.Objects;
3335

3436
import static org.elasticsearch.common.logging.LoggerMessageFormat.format;
3537
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isFoldable;
@@ -181,7 +183,8 @@ public ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvaluator) {
181183
ExpressionEvaluator.Factory field = toEvaluator.apply(field());
182184
field = Cast.cast(source(), field().dataType(), dataType, field);
183185
List<Object> points = Iterators.toList(Iterators.map(points().iterator(), p -> Foldables.valueOf(toEvaluator.foldCtx(), p)));
184-
return build.build(source(), field, points);
186+
List<Object> sortedPoints = sortedRoundingPoints(points, dataType); // provide sorted points to the evaluator
187+
return build.build(source(), field, sortedPoints);
185188
}
186189

187190
interface Build {
@@ -195,4 +198,27 @@ interface Build {
195198
Map.entry(LONG, RoundToLong.BUILD),
196199
Map.entry(DOUBLE, RoundToDouble.BUILD)
197200
);
201+
202+
public static List<Object> sortedRoundingPoints(List<Object> points, DataType dataType) {
203+
List<Number> pointsTobeSorted = points.stream().filter(Objects::nonNull).map(p -> (Number) p).toList();
204+
205+
return switch (dataType) {
206+
case INTEGER -> pointsTobeSorted.stream()
207+
.mapToInt(Number::intValue)
208+
.sorted()
209+
.boxed()
210+
.collect(java.util.stream.Collectors.toList());
211+
case DOUBLE -> pointsTobeSorted.stream()
212+
.mapToDouble(Number::doubleValue)
213+
.sorted()
214+
.boxed()
215+
.collect(java.util.stream.Collectors.toList());
216+
case LONG, DATETIME, DATE_NANOS -> pointsTobeSorted.stream()
217+
.mapToLong(DataTypeConverter::safeToLong)
218+
.sorted()
219+
.boxed()
220+
.collect(java.util.stream.Collectors.toList());
221+
default -> throw new IllegalArgumentException("Unsupported data type: " + dataType);
222+
};
223+
}
198224
}

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/X-RoundTo.java.st

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import java.util.Arrays;
2525
class RoundTo$Type$ {
2626
static final RoundTo.Build BUILD = (source, field, points) -> {
2727
$type$[] f = points.stream().mapTo$Type$(p -> ((Number) p).$type$Value()).toArray();
28-
Arrays.sort(f);
2928
return switch (f.length) {
3029
// TODO should be a consistent way to do the 0 version - is CASE(MV_COUNT(f) == 1, f[0])
3130
case 1 -> new RoundTo$Type$1Evaluator.Factory(source, field, f[0]);

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.elasticsearch.xpack.esql.optimizer.rules.physical.local.PushSampleToSource;
1919
import org.elasticsearch.xpack.esql.optimizer.rules.physical.local.PushStatsToSource;
2020
import org.elasticsearch.xpack.esql.optimizer.rules.physical.local.PushTopNToSource;
21+
import org.elasticsearch.xpack.esql.optimizer.rules.physical.local.ReplaceRoundToWithQueryAndTags;
2122
import org.elasticsearch.xpack.esql.optimizer.rules.physical.local.ReplaceSourceAttributes;
2223
import org.elasticsearch.xpack.esql.optimizer.rules.physical.local.SpatialDocValuesExtraction;
2324
import org.elasticsearch.xpack.esql.optimizer.rules.physical.local.SpatialShapeBoundsExtraction;
@@ -60,7 +61,7 @@ protected List<Batch<PhysicalPlan>> batches() {
6061
}
6162

6263
protected static List<Batch<PhysicalPlan>> rules(boolean optimizeForEsSource) {
63-
List<Rule<?, PhysicalPlan>> esSourceRules = new ArrayList<>(6);
64+
List<Rule<?, PhysicalPlan>> esSourceRules = new ArrayList<>(7);
6465
esSourceRules.add(new ReplaceSourceAttributes());
6566
if (optimizeForEsSource) {
6667
esSourceRules.add(new PushTopNToSource());
@@ -74,6 +75,12 @@ protected static List<Batch<PhysicalPlan>> rules(boolean optimizeForEsSource) {
7475
// execute the rules multiple times to improve the chances of things being pushed down
7576
@SuppressWarnings("unchecked")
7677
var pushdown = new Batch<PhysicalPlan>("Push to ES", esSourceRules.toArray(Rule[]::new));
78+
79+
// execute the SubstituteRoundToWithQueryAndTags rule once after all the other pushdown rules are applied, as this rule generate
80+
// multiple QueryBuilders according the number of RoundTo points, it should be applied after all the other eligible pushdowns are
81+
// done, and it should be executed only once.
82+
var substitutionRules = new Batch<>("Substitute RoundTo with QueryAndTags", Limiter.ONCE, new ReplaceRoundToWithQueryAndTags());
83+
7784
// add the field extraction in just one pass
7885
// add it at the end after all the other rules have ran
7986
var fieldExtraction = new Batch<>(
@@ -84,6 +91,6 @@ protected static List<Batch<PhysicalPlan>> rules(boolean optimizeForEsSource) {
8491
new SpatialShapeBoundsExtraction(),
8592
new ParallelizeTimeSeriesSource()
8693
);
87-
return List.of(pushdown, fieldExtraction);
94+
return optimizeForEsSource ? List.of(pushdown, substitutionRules, fieldExtraction) : List.of(pushdown, fieldExtraction);
8895
}
8996
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,10 @@ private static PhysicalPlan rewrite(
126126
queryExec.indexMode(),
127127
queryExec.indexNameWithModes(),
128128
queryExec.output(),
129-
query,
130129
queryExec.limit(),
131130
queryExec.sorts(),
132-
queryExec.estimatedRowSize()
131+
queryExec.estimatedRowSize(),
132+
List.of(new EsQueryExec.QueryBuilderAndTags(query, List.of()))
133133
);
134134
// If the eval contains other aliases, not just field attributes, we need to keep them in the plan
135135
PhysicalPlan plan = evalFields.isEmpty() ? queryExec : new EvalExec(filterExec.source(), queryExec, evalFields);

0 commit comments

Comments
 (0)