Skip to content

Commit 06d7431

Browse files
author
Marios Trivyzas
authored
SQL: Return error with ORDER BY on non-grouped. (#34855)
Previously, for some queries the validation for ORDER BY fields didn't kick in since a HAVING close or an ORDER BY with scalar function would add `Filter` and `Project` plans between the `OrderBy` and the `Aggregate`. Now the LogicalPlan tree beneath `OrderBy` is traversed and the ORDER BY fields are properly verified. Fixes: #34590
1 parent cf9aff9 commit 06d7431

File tree

2 files changed

+30
-8
lines changed

2 files changed

+30
-8
lines changed

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/analysis/analyzer/Verifier.java

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.elasticsearch.xpack.sql.plan.logical.Filter;
2626
import org.elasticsearch.xpack.sql.plan.logical.LogicalPlan;
2727
import org.elasticsearch.xpack.sql.plan.logical.OrderBy;
28+
import org.elasticsearch.xpack.sql.plan.logical.Project;
2829
import org.elasticsearch.xpack.sql.tree.Node;
2930
import org.elasticsearch.xpack.sql.type.DataType;
3031
import org.elasticsearch.xpack.sql.util.StringUtils;
@@ -238,8 +239,17 @@ private static boolean checkGroupByOrder(LogicalPlan p, Set<Failure> localFailur
238239
Set<LogicalPlan> groupingFailures, Map<String, Function> functions) {
239240
if (p instanceof OrderBy) {
240241
OrderBy o = (OrderBy) p;
241-
if (o.child() instanceof Aggregate) {
242-
Aggregate a = (Aggregate) o.child();
242+
LogicalPlan child = o.child();
243+
244+
if (child instanceof Project) {
245+
child = ((Project) child).child();
246+
}
247+
if (child instanceof Filter) {
248+
child = ((Filter) child).child();
249+
}
250+
251+
if (child instanceof Aggregate) {
252+
Aggregate a = (Aggregate) child;
243253

244254
Map<Expression, Node<?>> missing = new LinkedHashMap<>();
245255
o.order().forEach(oe -> {
@@ -253,7 +263,7 @@ private static boolean checkGroupByOrder(LogicalPlan p, Set<Failure> localFailur
253263
// take aliases declared inside the aggregates which point to the grouping (but are not included in there)
254264
// to correlate them to the order
255265
List<Expression> groupingAndMatchingAggregatesAliases = new ArrayList<>(a.groupings());
256-
266+
257267
a.aggregates().forEach(as -> {
258268
if (as instanceof Alias) {
259269
Alias al = (Alias) as;
@@ -262,10 +272,13 @@ private static boolean checkGroupByOrder(LogicalPlan p, Set<Failure> localFailur
262272
}
263273
}
264274
});
265-
266-
// make sure to compare attributes directly
267-
if (Expressions.anyMatch(groupingAndMatchingAggregatesAliases,
268-
g -> e.semanticEquals(e instanceof Attribute ? Expressions.attribute(g) : g))) {
275+
276+
// Make sure you can apply functions on top of the grouped by expressions in the ORDER BY:
277+
// e.g.: if "GROUP BY f2(f1(field))" you can "ORDER BY f4(f3(f2(f1(field))))"
278+
//
279+
// Also, make sure to compare attributes directly
280+
if (e.anyMatch(expression -> Expressions.anyMatch(groupingAndMatchingAggregatesAliases,
281+
g -> expression.semanticEquals(expression instanceof Attribute ? Expressions.attribute(g) : g)))) {
269282
return;
270283
}
271284

@@ -288,7 +301,6 @@ private static boolean checkGroupByOrder(LogicalPlan p, Set<Failure> localFailur
288301
return true;
289302
}
290303

291-
292304
private static boolean checkGroupByHaving(LogicalPlan p, Set<Failure> localFailures,
293305
Set<LogicalPlan> groupingFailures, Map<String, Function> functions) {
294306
if (p instanceof Filter) {

x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/analysis/analyzer/VerifierErrorMessagesTests.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,11 @@ public void testGroupByOrderByNonGrouped() {
118118
verify("SELECT MAX(int) FROM test GROUP BY text ORDER BY bool"));
119119
}
120120

121+
public void testGroupByOrderByNonGrouped_WithHaving() {
122+
assertEquals("1:71: Cannot order by non-grouped column [bool], expected [text]",
123+
verify("SELECT MAX(int) FROM test GROUP BY text HAVING MAX(int) > 10 ORDER BY bool"));
124+
}
125+
121126
public void testGroupByOrderByAliasedInSelectAllowed() {
122127
LogicalPlan lp = accepted("SELECT text t FROM test GROUP BY text ORDER BY t");
123128
assertNotNull(lp);
@@ -128,6 +133,11 @@ public void testGroupByOrderByScalarOverNonGrouped() {
128133
verify("SELECT MAX(int) FROM test GROUP BY text ORDER BY YEAR(date)"));
129134
}
130135

136+
public void testGroupByOrderByScalarOverNonGrouped_WithHaving() {
137+
assertEquals("1:71: Cannot order by non-grouped column [YEAR(date [UTC])], expected [text]",
138+
verify("SELECT MAX(int) FROM test GROUP BY text HAVING MAX(int) > 10 ORDER BY YEAR(date)"));
139+
}
140+
131141
public void testGroupByHavingNonGrouped() {
132142
assertEquals("1:48: Cannot filter by non-grouped column [int], expected [text]",
133143
verify("SELECT AVG(int) FROM test GROUP BY text HAVING int > 10"));

0 commit comments

Comments
 (0)