Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/changelog/121156.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 121156
summary: Remove redundant sorts from execution plan
area: ES|QL
type: bug
issues: []
Original file line number Diff line number Diff line change
Expand Up @@ -1346,3 +1346,50 @@ language_code:integer | language_name:keyword | country:text
1 | English | United States of America
1 | English | null
;


sortBeforeAndAfterJoin
required_capability: join_lookup_v12
required_capability: remove_redundant_sort

FROM employees
| sort first_name
| EVAL language_code = languages
| LOOKUP JOIN languages_lookup ON language_code
| WHERE emp_no >= 10091 AND emp_no < 10094
| SORT emp_no
| KEEP emp_no, language_code, language_name
;

emp_no:integer | language_code:integer | language_name:keyword
10091 | 3 | Spanish
10092 | 1 | English
10093 | 3 | Spanish
;



sortBeforeAndAfterMultipleJoinAndMvExpand
required_capability: join_lookup_v12
required_capability: remove_redundant_sort

FROM employees
| sort first_name
| EVAL language_code = languages
| LOOKUP JOIN languages_lookup ON language_code
| WHERE emp_no >= 10091 AND emp_no < 10094
| SORT language_name
| MV_EXPAND first_name
| SORT first_name
| MV_EXPAND last_name
| SORT last_name
| LOOKUP JOIN languages_lookup ON language_code
| SORT emp_no
| KEEP emp_no, language_code, language_name
;

emp_no:integer | language_code:integer | language_name:keyword
10091 | 3 | Spanish
10092 | 1 | English
10093 | 3 | Spanish
;
Original file line number Diff line number Diff line change
Expand Up @@ -404,3 +404,17 @@ from employees | where emp_no == 10003 | mv_expand first_name | keep first_name
first_name:keyword
Parto
;


sortBeforeAndAfterMvExpand
from employees
| sort first_name
| mv_expand job_positions
| sort emp_no, job_positions
| keep emp_no, job_positions
| limit 2;

emp_no:integer | job_positions:keyword
10001 | Accountant
10001 | Senior Python Developer
;
Original file line number Diff line number Diff line change
Expand Up @@ -786,7 +786,14 @@ public enum Cap {
/**
* Support for aggregate_metric_double type
*/
AGGREGATE_METRIC_DOUBLE(AGGREGATE_METRIC_DOUBLE_FEATURE_FLAG.isEnabled());
AGGREGATE_METRIC_DOUBLE(AGGREGATE_METRIC_DOUBLE_FEATURE_FLAG.isEnabled()),

/**
* Fix for https://github.com/elastic/elasticsearch/issues/120817
* and https://github.com/elastic/elasticsearch/issues/120803
* Support for queries that have multiple SORTs that cannot become TopN
*/
REMOVE_REDUNDANT_SORT;

private final boolean enabled;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import org.elasticsearch.xpack.esql.VerificationException;
import org.elasticsearch.xpack.esql.common.Failures;
import org.elasticsearch.xpack.esql.core.type.DataType;
import org.elasticsearch.xpack.esql.optimizer.rules.logical.AddDefaultTopN;
import org.elasticsearch.xpack.esql.optimizer.rules.logical.BooleanFunctionEqualsElimination;
import org.elasticsearch.xpack.esql.optimizer.rules.logical.BooleanSimplification;
import org.elasticsearch.xpack.esql.optimizer.rules.logical.CombineBinaryComparisons;
Expand All @@ -32,7 +31,7 @@
import org.elasticsearch.xpack.esql.optimizer.rules.logical.PruneEmptyPlans;
import org.elasticsearch.xpack.esql.optimizer.rules.logical.PruneFilters;
import org.elasticsearch.xpack.esql.optimizer.rules.logical.PruneLiteralsInOrderBy;
import org.elasticsearch.xpack.esql.optimizer.rules.logical.PruneOrderByBeforeStats;
import org.elasticsearch.xpack.esql.optimizer.rules.logical.PruneRedundantOrderBy;
import org.elasticsearch.xpack.esql.optimizer.rules.logical.PruneRedundantSortClauses;
import org.elasticsearch.xpack.esql.optimizer.rules.logical.PushDownAndCombineFilters;
import org.elasticsearch.xpack.esql.optimizer.rules.logical.PushDownAndCombineLimits;
Expand Down Expand Up @@ -116,10 +115,9 @@ protected List<Batch<LogicalPlan>> batches() {

protected static List<Batch<LogicalPlan>> rules() {
var skip = new Batch<>("Skip Compute", new SkipQueryOnLimitZero());
var defaultTopN = new Batch<>("Add default TopN", new AddDefaultTopN());
var label = new Batch<>("Set as Optimized", Limiter.ONCE, new SetAsOptimized());

return asList(substitutions(), operators(), skip, cleanup(), defaultTopN, label);
return asList(substitutions(), operators(), skip, cleanup(), label);
}

protected static Batch<LogicalPlan> substitutions() {
Expand Down Expand Up @@ -189,7 +187,7 @@ protected static Batch<LogicalPlan> operators() {
new PushDownRegexExtract(),
new PushDownEnrich(),
new PushDownAndCombineOrderBy(),
new PruneOrderByBeforeStats(),
new PruneRedundantOrderBy(),
new PruneRedundantSortClauses()
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ public Failures verify(LogicalPlan plan) {
PlanConsistencyChecker.checkPlan(p, dependencyFailures);

if (failures.hasFailures() == false) {
if (p instanceof PostOptimizationVerificationAware pova) {
pova.postOptimizationVerification(failures);
}
p.forEachExpression(ex -> {
if (ex instanceof PostOptimizationVerificationAware va) {
va.postOptimizationVerification(failures);
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.esql.optimizer.rules.logical;

import org.elasticsearch.xpack.esql.plan.logical.Aggregate;
import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan;
import org.elasticsearch.xpack.esql.plan.logical.OrderBy;
import org.elasticsearch.xpack.esql.plan.logical.SortAgnostic;
import org.elasticsearch.xpack.esql.plan.logical.TopN;
import org.elasticsearch.xpack.esql.plan.logical.UnaryPlan;

import java.util.ArrayDeque;
import java.util.Collections;
import java.util.Deque;
import java.util.IdentityHashMap;
import java.util.Set;

/**
* SORT cannot be executed without a LIMIT, as ES|QL doesn't support unbounded sort (yet).
* <p>
* The planner tries to push down LIMIT and transform all the unbounded sorts into a TopN.
* In some cases it's not possible though, eg.
* <p>
* from test | sort x | lookup join lookup on x | sort y
* <p>
* from test | sort x | mv_expand x | sort y
* <p>
* "sort y" will become a TopN due to the addition of the default Limit, but "sort x" will remain unbounded,
* so the query could not be executed.
* <p>
* In most cases though, following commands can make the previous SORTs redundant,
* because it will re-sort previously sorted results (eg. if there is another SORT)
* or because the order will be scrambled by another command (eg. a STATS)
* <p>
* This rule finds and prunes redundant SORTs, attempting to make the plan executable.
*/
public class PruneRedundantOrderBy extends OptimizerRules.OptimizerRule<LogicalPlan> {

@Override
protected LogicalPlan rule(LogicalPlan plan) {
if (plan instanceof OrderBy || plan instanceof TopN || plan instanceof Aggregate) {
Set<OrderBy> redundant = findRedundantSort(((UnaryPlan) plan).child());
if (redundant.isEmpty()) {
return plan;
}
return plan.transformDown(p -> redundant.contains(p) ? ((UnaryPlan) p).child() : p);
} else {
return plan;
}
}

/**
* breadth-first recursion to find redundant SORTs in the children tree.
* Returns an identity set (we need to compare and prune the exact instances)
*/
private Set<OrderBy> findRedundantSort(LogicalPlan plan) {
Set<OrderBy> result = Collections.newSetFromMap(new IdentityHashMap<>());

Deque<LogicalPlan> toCheck = new ArrayDeque<>();
toCheck.push(plan);

while (true) {
if (toCheck.isEmpty()) {
return result;
}
LogicalPlan p = toCheck.pop();
if (p instanceof OrderBy ob) {
result.add(ob);
toCheck.push(ob.child());
} else if (p instanceof SortAgnostic) {
for (LogicalPlan child : p.children()) {
toCheck.push(child);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
import org.elasticsearch.xpack.esql.plan.physical.LimitExec;
import org.elasticsearch.xpack.esql.plan.physical.LocalSourceExec;
import org.elasticsearch.xpack.esql.plan.physical.MvExpandExec;
import org.elasticsearch.xpack.esql.plan.physical.OrderExec;
import org.elasticsearch.xpack.esql.plan.physical.ProjectExec;
import org.elasticsearch.xpack.esql.plan.physical.ShowExec;
import org.elasticsearch.xpack.esql.plan.physical.SubqueryExec;
Expand Down Expand Up @@ -103,7 +102,6 @@ public static List<NamedWriteableRegistry.Entry> phsyical() {
LimitExec.ENTRY,
LocalSourceExec.ENTRY,
MvExpandExec.ENTRY,
OrderExec.ENTRY,
ProjectExec.ENTRY,
ShowExec.ENTRY,
SubqueryExec.ENTRY,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
import static org.elasticsearch.xpack.esql.expression.NamedExpressions.mergeOutputAttributes;
import static org.elasticsearch.xpack.esql.plan.logical.Filter.checkFilterConditionDataType;

public class Aggregate extends UnaryPlan implements PostAnalysisVerificationAware, TelemetryAware {
public class Aggregate extends UnaryPlan implements PostAnalysisVerificationAware, TelemetryAware, SortAgnostic {
public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(
LogicalPlan.class,
"Aggregate",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import java.util.List;
import java.util.Objects;

public class Drop extends UnaryPlan implements TelemetryAware {
public class Drop extends UnaryPlan implements TelemetryAware, SortAgnostic {
private final List<NamedExpression> removals;

public Drop(Source source, LogicalPlan child, List<NamedExpression> removals) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
import static org.elasticsearch.xpack.esql.core.expression.Expressions.asAttributes;
import static org.elasticsearch.xpack.esql.expression.NamedExpressions.mergeOutputAttributes;

public class Enrich extends UnaryPlan implements GeneratingPlan<Enrich>, PostAnalysisPlanVerificationAware, TelemetryAware {
public class Enrich extends UnaryPlan implements GeneratingPlan<Enrich>, PostAnalysisPlanVerificationAware, TelemetryAware, SortAgnostic {
public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(
LogicalPlan.class,
"Enrich",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
import static org.elasticsearch.xpack.esql.core.expression.Expressions.asAttributes;
import static org.elasticsearch.xpack.esql.expression.NamedExpressions.mergeOutputAttributes;

public class Eval extends UnaryPlan implements GeneratingPlan<Eval>, PostAnalysisVerificationAware, TelemetryAware {
public class Eval extends UnaryPlan implements GeneratingPlan<Eval>, PostAnalysisVerificationAware, TelemetryAware, SortAgnostic {
public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(LogicalPlan.class, "Eval", Eval::new);

private final List<Alias> fields;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
* {@code SELECT x FROM y WHERE z ..} the "WHERE" clause is a Filter. A
* {@code Filter} has a "condition" Expression that does the filtering.
*/
public class Filter extends UnaryPlan implements PostAnalysisVerificationAware, TelemetryAware {
public class Filter extends UnaryPlan implements PostAnalysisVerificationAware, TelemetryAware, SortAgnostic {
public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(LogicalPlan.class, "Filter", Filter::new);

private final Expression condition;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
* underlying aggregate.
* </p>
*/
public class InlineStats extends UnaryPlan implements NamedWriteable, SurrogateLogicalPlan, TelemetryAware {
public class InlineStats extends UnaryPlan implements NamedWriteable, SurrogateLogicalPlan, TelemetryAware, SortAgnostic {
public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(
LogicalPlan.class,
"InlineStats",
Expand Down
Loading