Skip to content

Commit d05c24c

Browse files
committed
SQL: Generate relevant error message when grouping functions are not used in GROUP BY (#38017)
* Add checks for Grouping functions restriction to be placed inside GROUP BY * Fixed bug where GROUP BY HISTOGRAM (not using alias) wasn't recognized properly in the Verifier due to functions equality not working correctly. (cherry picked from commit 6968f09)
1 parent de7accb commit d05c24c

File tree

5 files changed

+92
-19
lines changed

5 files changed

+92
-19
lines changed

x-pack/plugin/sql/qa/src/main/resources/agg.csv-spec

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,29 @@ SELECT HISTOGRAM(emp_no % 100, 10) AS h, COUNT(*) as c FROM test_emp GROUP BY h
311311
0 |10
312312
;
313313

314+
histogramGroupByWithoutAlias
315+
schema::h:ts|c:l
316+
SELECT HISTOGRAM(birth_date, INTERVAL 1 YEAR) AS h, COUNT(*) as c FROM test_emp GROUP BY HISTOGRAM(birth_date, INTERVAL 1 YEAR) ORDER BY h DESC;
317+
318+
h | c
319+
--------------------+---------------
320+
1964-02-02T00:00:00Z|5
321+
1963-02-07T00:00:00Z|7
322+
1962-02-12T00:00:00Z|6
323+
1961-02-17T00:00:00Z|8
324+
1960-02-23T00:00:00Z|7
325+
1959-02-28T00:00:00Z|9
326+
1958-03-05T00:00:00Z|6
327+
1957-03-10T00:00:00Z|6
328+
1956-03-15T00:00:00Z|4
329+
1955-03-21T00:00:00Z|4
330+
1954-03-26T00:00:00Z|7
331+
1953-03-31T00:00:00Z|10
332+
1952-04-05T00:00:00Z|10
333+
1951-04-11T00:00:00Z|1
334+
null |10
335+
;
336+
314337
countAll
315338
schema::all_names:l|c:l
316339
SELECT COUNT(ALL first_name) all_names, COUNT(*) c FROM test_emp;

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.elasticsearch.xpack.sql.expression.function.Functions;
2020
import org.elasticsearch.xpack.sql.expression.function.Score;
2121
import org.elasticsearch.xpack.sql.expression.function.aggregate.AggregateFunctionAttribute;
22+
import org.elasticsearch.xpack.sql.expression.function.grouping.GroupingFunction;
2223
import org.elasticsearch.xpack.sql.expression.function.grouping.GroupingFunctionAttribute;
2324
import org.elasticsearch.xpack.sql.expression.function.scalar.ScalarFunction;
2425
import org.elasticsearch.xpack.sql.expression.predicate.conditional.ConditionalFunction;
@@ -225,6 +226,7 @@ Collection<Failure> verify(LogicalPlan plan) {
225226
validateInExpression(p, localFailures);
226227
validateConditional(p, localFailures);
227228

229+
checkGroupingFunctionInGroupBy(p, localFailures);
228230
checkFilterOnAggs(p, localFailures);
229231
checkFilterOnGrouping(p, localFailures);
230232

@@ -560,6 +562,24 @@ private static boolean checkGroupMatch(Expression e, Node<?> source, List<Expres
560562
}
561563
return false;
562564
}
565+
566+
private static void checkGroupingFunctionInGroupBy(LogicalPlan p, Set<Failure> localFailures) {
567+
// check if the query has a grouping function (Histogram) but no GROUP BY
568+
if (p instanceof Project) {
569+
Project proj = (Project) p;
570+
proj.projections().forEach(e -> e.forEachDown(f ->
571+
localFailures.add(fail(f, "[%s] needs to be part of the grouping", Expressions.name(f))), GroupingFunction.class));
572+
} else if (p instanceof Aggregate) {
573+
// if it does have a GROUP BY, check if the groupings contain the grouping functions (Histograms)
574+
Aggregate a = (Aggregate) p;
575+
a.aggregates().forEach(agg -> agg.forEachDown(e -> {
576+
if (a.groupings().size() == 0
577+
|| Expressions.anyMatch(a.groupings(), g -> g instanceof Function && e.functionEquals((Function) g)) == false) {
578+
localFailures.add(fail(e, "[%s] needs to be part of the grouping", Expressions.name(e)));
579+
}
580+
}, GroupingFunction.class));
581+
}
582+
}
563583

564584
private static void checkFilterOnAggs(LogicalPlan p, Set<Failure> localFailures) {
565585
if (p instanceof Filter) {

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/grouping/GroupingFunction.java

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,16 +57,6 @@ public GroupingFunctionAttribute toAttribute() {
5757
return lazyAttribute;
5858
}
5959

60-
@Override
61-
public final GroupingFunction replaceChildren(List<Expression> newChildren) {
62-
if (newChildren.size() != 1) {
63-
throw new IllegalArgumentException("expected [1] child but received [" + newChildren.size() + "]");
64-
}
65-
return replaceChild(newChildren.get(0));
66-
}
67-
68-
protected abstract GroupingFunction replaceChild(Expression newChild);
69-
7060
@Override
7161
protected Pipe makePipe() {
7262
// unresolved AggNameInput (should always get replaced by the folder)

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/grouping/Histogram.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@
1010
import org.elasticsearch.xpack.sql.expression.Expressions;
1111
import org.elasticsearch.xpack.sql.expression.Expressions.ParamOrdinal;
1212
import org.elasticsearch.xpack.sql.expression.Literal;
13-
import org.elasticsearch.xpack.sql.tree.Location;
1413
import org.elasticsearch.xpack.sql.tree.NodeInfo;
14+
import org.elasticsearch.xpack.sql.tree.Location;
1515
import org.elasticsearch.xpack.sql.type.DataType;
1616
import org.elasticsearch.xpack.sql.type.DataTypes;
1717

1818
import java.time.ZoneId;
19+
import java.util.Collections;
20+
import java.util.List;
1921
import java.util.Objects;
2022

2123
public class Histogram extends GroupingFunction {
@@ -24,8 +26,8 @@ public class Histogram extends GroupingFunction {
2426
private final ZoneId zoneId;
2527

2628
public Histogram(Location location, Expression field, Expression interval, ZoneId zoneId) {
27-
super(location, field);
28-
this.interval = (Literal) interval;
29+
super(location, field, Collections.singletonList(interval));
30+
this.interval = Literal.of(interval);
2931
this.zoneId = zoneId;
3032
}
3133

@@ -51,10 +53,13 @@ protected TypeResolution resolveType() {
5153

5254
return resolution;
5355
}
54-
56+
5557
@Override
56-
protected GroupingFunction replaceChild(Expression newChild) {
57-
return new Histogram(location(), newChild, interval, zoneId);
58+
public final GroupingFunction replaceChildren(List<Expression> newChildren) {
59+
if (newChildren.size() != 2) {
60+
throw new IllegalArgumentException("expected [2] children but received [" + newChildren.size() + "]");
61+
}
62+
return new Histogram(location(), newChildren.get(0), newChildren.get(1), zoneId);
5863
}
5964

6065
@Override

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

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,7 @@ public void testAggsInWhere() {
506506
}
507507

508508
public void testHistogramInFilter() {
509-
assertEquals("1:63: Cannot filter on grouping function [HISTOGRAM(date)], use its argument instead",
509+
assertEquals("1:63: Cannot filter on grouping function [HISTOGRAM(date,INTERVAL 1 MONTH)], use its argument instead",
510510
error("SELECT HISTOGRAM(date, INTERVAL 1 MONTH) AS h FROM test WHERE "
511511
+ "HISTOGRAM(date, INTERVAL 1 MONTH) > CAST('2000-01-01' AS DATE) GROUP BY h"));
512512
}
@@ -520,15 +520,50 @@ public void testHistogramInHaving() {
520520

521521
public void testGroupByScalarOnTopOfGrouping() {
522522
assertEquals(
523-
"1:14: Cannot combine [HISTOGRAM(date)] grouping function inside GROUP BY, "
524-
+ "found [MONTH_OF_YEAR(HISTOGRAM(date) [Z])]; consider moving the expression inside the histogram",
523+
"1:14: Cannot combine [HISTOGRAM(date,INTERVAL 1 MONTH)] grouping function inside GROUP BY, "
524+
+ "found [MONTH_OF_YEAR(HISTOGRAM(date,INTERVAL 1 MONTH) [Z])]; consider moving the expression inside the histogram",
525525
error("SELECT MONTH(HISTOGRAM(date, INTERVAL 1 MONTH)) AS h FROM test GROUP BY h"));
526526
}
527527

528528
public void testAggsInHistogram() {
529529
assertEquals("1:47: Cannot use an aggregate [MAX] for grouping",
530530
error("SELECT MAX(date) FROM test GROUP BY HISTOGRAM(MAX(int), 1)"));
531531
}
532+
533+
public void testHistogramNotInGrouping() {
534+
assertEquals("1:8: [HISTOGRAM(date,INTERVAL 1 MONTH)] needs to be part of the grouping",
535+
error("SELECT HISTOGRAM(date, INTERVAL 1 MONTH) AS h FROM test"));
536+
}
537+
538+
public void testHistogramNotInGroupingWithCount() {
539+
assertEquals("1:8: [HISTOGRAM(date,INTERVAL 1 MONTH)] needs to be part of the grouping",
540+
error("SELECT HISTOGRAM(date, INTERVAL 1 MONTH) AS h, COUNT(*) FROM test"));
541+
}
542+
543+
public void testHistogramNotInGroupingWithMaxFirst() {
544+
assertEquals("1:19: [HISTOGRAM(date,INTERVAL 1 MONTH)] needs to be part of the grouping",
545+
error("SELECT MAX(date), HISTOGRAM(date, INTERVAL 1 MONTH) AS h FROM test"));
546+
}
547+
548+
public void testHistogramWithoutAliasNotInGrouping() {
549+
assertEquals("1:8: [HISTOGRAM(date,INTERVAL 1 MONTH)] needs to be part of the grouping",
550+
error("SELECT HISTOGRAM(date, INTERVAL 1 MONTH) FROM test"));
551+
}
552+
553+
public void testTwoHistogramsNotInGrouping() {
554+
assertEquals("1:48: [HISTOGRAM(date,INTERVAL 1 DAY)] needs to be part of the grouping",
555+
error("SELECT HISTOGRAM(date, INTERVAL 1 MONTH) AS h, HISTOGRAM(date, INTERVAL 1 DAY) FROM test GROUP BY h"));
556+
}
557+
558+
public void testHistogramNotInGrouping_WithGroupByField() {
559+
assertEquals("1:8: [HISTOGRAM(date,INTERVAL 1 MONTH)] needs to be part of the grouping",
560+
error("SELECT HISTOGRAM(date, INTERVAL 1 MONTH) FROM test GROUP BY date"));
561+
}
562+
563+
public void testScalarOfHistogramNotInGrouping() {
564+
assertEquals("1:14: [HISTOGRAM(date,INTERVAL 1 MONTH)] needs to be part of the grouping",
565+
error("SELECT MONTH(HISTOGRAM(date, INTERVAL 1 MONTH)) FROM test"));
566+
}
532567

533568
public void testErrorMessageForPercentileWithSecondArgBasedOnAField() {
534569
assertEquals("1:8: Second argument of PERCENTILE must be a constant, received [ABS(int)]",

0 commit comments

Comments
 (0)