-
Notifications
You must be signed in to change notification settings - Fork 24.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SQL: Disallow queries with inner LIMIT that cannot be executed in ES #75960
Changes from all commits
545be94
b3a235a
5fa12c7
264e042
5cb4017
9f40d30
8d8d992
a6ab253
226d795
2edf92d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -100,7 +100,16 @@ public LogicalPlan visitQueryNoWith(QueryNoWithContext ctx) { | |
if (ctx.orderBy().isEmpty() == false) { | ||
List<OrderByContext> orders = ctx.orderBy(); | ||
OrderByContext endContext = orders.get(orders.size() - 1); | ||
plan = new OrderBy(source(ctx.ORDER(), endContext), plan, visitList(ctx.orderBy(), Order.class)); | ||
Source source = source(ctx.ORDER(), endContext); | ||
List<Order> order = visitList(ctx.orderBy(), Order.class); | ||
|
||
if (plan instanceof Limit) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
// Limit from TOP clauses must be the parent of the OrderBy clause | ||
Limit limit = (Limit) plan; | ||
plan = limit.replaceChild(new OrderBy(source, limit.child(), order)); | ||
} else { | ||
plan = new OrderBy(source, plan, order); | ||
} | ||
} | ||
|
||
LimitClauseContext limitClause = ctx.limitClause(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,11 +7,14 @@ | |
package org.elasticsearch.xpack.sql.planner; | ||
|
||
import org.elasticsearch.xpack.ql.common.Failure; | ||
import org.elasticsearch.xpack.ql.expression.Order; | ||
import org.elasticsearch.xpack.ql.util.Holder; | ||
import org.elasticsearch.xpack.sql.plan.physical.AggregateExec; | ||
import org.elasticsearch.xpack.sql.plan.physical.FilterExec; | ||
import org.elasticsearch.xpack.sql.plan.physical.LimitExec; | ||
import org.elasticsearch.xpack.sql.plan.physical.OrderExec; | ||
import org.elasticsearch.xpack.sql.plan.physical.PhysicalPlan; | ||
import org.elasticsearch.xpack.sql.plan.physical.PivotExec; | ||
import org.elasticsearch.xpack.sql.plan.physical.UnaryExec; | ||
import org.elasticsearch.xpack.sql.plan.physical.Unexecutable; | ||
import org.elasticsearch.xpack.sql.plan.physical.UnplannedExec; | ||
|
||
|
@@ -59,20 +62,23 @@ static List<Failure> verifyExecutingPlan(PhysicalPlan plan) { | |
} | ||
|
||
private static void checkForNonCollapsableSubselects(PhysicalPlan plan, List<Failure> failures) { | ||
Holder<Boolean> hasLimit = new Holder<>(Boolean.FALSE); | ||
Holder<List<Order>> orderBy = new Holder<>(); | ||
Holder<LimitExec> limit = new Holder<>(); | ||
Holder<UnaryExec> limitedExec = new Holder<>(); | ||
|
||
plan.forEachUp(p -> { | ||
if (hasLimit.get() == false && p instanceof LimitExec) { | ||
hasLimit.set(Boolean.TRUE); | ||
return; | ||
} | ||
if (p instanceof OrderExec) { | ||
if (hasLimit.get() && orderBy.get() != null && ((OrderExec) p).order().equals(orderBy.get()) == false) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Related to the removed query comment: can't we keep this "pass-through" check? I.e. chaining multiple equal orderings if they aren't combined with other clauses? |
||
failures.add(fail(p, "Cannot use ORDER BY on top of a subquery with ORDER BY and LIMIT")); | ||
} else { | ||
orderBy.set(((OrderExec) p).order()); | ||
if (limit.get() == null && p instanceof LimitExec) { | ||
limit.set((LimitExec) p); | ||
} else if (limit.get() != null && limitedExec.get() == null) { | ||
if (p instanceof OrderExec || p instanceof FilterExec || p instanceof PivotExec || p instanceof AggregateExec) { | ||
limitedExec.set((UnaryExec) p); | ||
} | ||
} | ||
}); | ||
|
||
if (limitedExec.get() != null) { | ||
failures.add( | ||
fail(limit.get(), "LIMIT or TOP cannot be used in a subquery if outer query contains GROUP BY, ORDER BY, PIVOT or WHERE") | ||
); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wondering if this (type of) sequence of sub-selects couldn't be flattened safely. The outer ones don't change the ordering and out of multiple limitations, the smallest value could be determined (there's already code doing this comparison, IIRC?).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this particular query cannot be flattened as well because it also first limits and then sorts.
But I think you're right that there is a special case where the outer ORDER BY is redundant and could be eliminated as in
SELECT * FROM (SELECT * FROM test_emp ORDER BY emp_no LIMIT 10) ORDER BY emp_no
. This query should be equivalent toSELECT * FROM (SELECT * FROM test_emp ORDER BY emp_no LIMIT 10)
which can safely be flattened.I think the same principle applies for redundant filters as well:
SELECT * FROM (SELECT * FROM test_emp WHERE emp_no > 10 LIMIT 10) WHERE emp_no > 10
. And maybe alsoGROUP BY
s.I have the feeling this would best be implemented as an additional optimization though. A rule that eliminates redundant WHERE and ORDER BY.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just noticed that in this particular case, the query could even be flattened because the order is not specified in the inner query. This would be a somewhat surprising behavior but probably aligned with SQL's "unordered bag of tuples" semantics.