Skip to content

Commit a0ac115

Browse files
committed
SQL: Verify GROUP BY ordering on grouped columns (#30585)
Due to the way composite aggregation works, ordering in GROUP BY can be applied only through grouped columns which now the analyzer verifier enforces. Fix 29900
1 parent 89dc3ff commit a0ac115

File tree

2 files changed

+37
-5
lines changed

2 files changed

+37
-5
lines changed

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

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -211,12 +211,13 @@ static Collection<Failure> verify(LogicalPlan plan) {
211211

212212
/**
213213
* Check validity of Aggregate/GroupBy.
214-
* This rule is needed for two reasons:
214+
* This rule is needed for multiple reasons:
215215
* 1. a user might specify an invalid aggregate (SELECT foo GROUP BY bar)
216216
* 2. the order/having might contain a non-grouped attribute. This is typically
217217
* caught by the Analyzer however if wrapped in a function (ABS()) it gets resolved
218218
* (because the expression gets resolved little by little without being pushed down,
219219
* without the Analyzer modifying anything.
220+
* 3. composite agg (used for GROUP BY) allows ordering only on the group keys
220221
*/
221222
private static boolean checkGroupBy(LogicalPlan p, Set<Failure> localFailures,
222223
Map<String, Function> resolvedFunctions, Set<LogicalPlan> groupingFailures) {
@@ -225,7 +226,7 @@ && checkGroupByOrder(p, localFailures, groupingFailures, resolvedFunctions)
225226
&& checkGroupByHaving(p, localFailures, groupingFailures, resolvedFunctions);
226227
}
227228

228-
// check whether an orderBy failed
229+
// check whether an orderBy failed or if it occurs on a non-key
229230
private static boolean checkGroupByOrder(LogicalPlan p, Set<Failure> localFailures,
230231
Set<LogicalPlan> groupingFailures, Map<String, Function> functions) {
231232
if (p instanceof OrderBy) {
@@ -234,7 +235,23 @@ private static boolean checkGroupByOrder(LogicalPlan p, Set<Failure> localFailur
234235
Aggregate a = (Aggregate) o.child();
235236

236237
Map<Expression, Node<?>> missing = new LinkedHashMap<>();
237-
o.order().forEach(oe -> oe.collectFirstChildren(c -> checkGroupMatch(c, oe, a.groupings(), missing, functions)));
238+
o.order().forEach(oe -> {
239+
Expression e = oe.child();
240+
// cannot order by aggregates (not supported by composite)
241+
if (Functions.isAggregate(e)) {
242+
missing.put(e, oe);
243+
return;
244+
}
245+
246+
// make sure to compare attributes directly
247+
if (Expressions.anyMatch(a.groupings(),
248+
g -> e.semanticEquals(e instanceof Attribute ? Expressions.attribute(g) : g))) {
249+
return;
250+
}
251+
252+
// nothing matched, cannot group by it
253+
missing.put(e, oe);
254+
});
238255

239256
if (!missing.isEmpty()) {
240257
String plural = missing.size() > 1 ? "s" : StringUtils.EMPTY;

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

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ public void testGroupByOrderByNonGrouped() {
111111
}
112112

113113
public void testGroupByOrderByScalarOverNonGrouped() {
114-
assertEquals("1:50: Cannot order by non-grouped column [date], expected [text]",
114+
assertEquals("1:50: Cannot order by non-grouped column [YEAR(date [UTC])], expected [text]",
115115
verify("SELECT MAX(int) FROM test GROUP BY text ORDER BY YEAR(date)"));
116116
}
117117

@@ -144,4 +144,19 @@ public void testUnsupportedType() {
144144
assertEquals("1:8: Cannot use field [unsupported] type [ip_range] as is unsupported",
145145
verify("SELECT unsupported FROM test"));
146146
}
147-
}
147+
148+
public void testGroupByOrderByNonKey() {
149+
assertEquals("1:52: Cannot order by non-grouped column [a], expected [bool]",
150+
verify("SELECT AVG(int) a FROM test GROUP BY bool ORDER BY a"));
151+
}
152+
153+
public void testGroupByOrderByFunctionOverKey() {
154+
assertEquals("1:44: Cannot order by non-grouped column [MAX(int)], expected [int]",
155+
verify("SELECT int FROM test GROUP BY int ORDER BY MAX(int)"));
156+
}
157+
158+
public void testGroupByOrderByScore() {
159+
assertEquals("1:44: Cannot order by non-grouped column [SCORE()], expected [int]",
160+
verify("SELECT int FROM test GROUP BY int ORDER BY SCORE()"));
161+
}
162+
}

0 commit comments

Comments
 (0)