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.
This commit is contained in:
parent
75abb5b8a6
commit
6968f0925b
|
@ -319,6 +319,29 @@ SELECT HISTOGRAM(emp_no % 100, 10) AS h, COUNT(*) as c FROM test_emp GROUP BY h
|
||||||
0 |10
|
0 |10
|
||||||
;
|
;
|
||||||
|
|
||||||
|
histogramGroupByWithoutAlias
|
||||||
|
schema::h:ts|c:l
|
||||||
|
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;
|
||||||
|
|
||||||
|
h | c
|
||||||
|
--------------------+---------------
|
||||||
|
1964-02-02T00:00:00Z|5
|
||||||
|
1963-02-07T00:00:00Z|7
|
||||||
|
1962-02-12T00:00:00Z|6
|
||||||
|
1961-02-17T00:00:00Z|8
|
||||||
|
1960-02-23T00:00:00Z|7
|
||||||
|
1959-02-28T00:00:00Z|9
|
||||||
|
1958-03-05T00:00:00Z|6
|
||||||
|
1957-03-10T00:00:00Z|6
|
||||||
|
1956-03-15T00:00:00Z|4
|
||||||
|
1955-03-21T00:00:00Z|4
|
||||||
|
1954-03-26T00:00:00Z|7
|
||||||
|
1953-03-31T00:00:00Z|10
|
||||||
|
1952-04-05T00:00:00Z|10
|
||||||
|
1951-04-11T00:00:00Z|1
|
||||||
|
null |10
|
||||||
|
;
|
||||||
|
|
||||||
countAll
|
countAll
|
||||||
schema::all_names:l|c:l
|
schema::all_names:l|c:l
|
||||||
SELECT COUNT(ALL first_name) all_names, COUNT(*) c FROM test_emp;
|
SELECT COUNT(ALL first_name) all_names, COUNT(*) c FROM test_emp;
|
||||||
|
|
|
@ -23,6 +23,7 @@ import org.elasticsearch.xpack.sql.expression.function.aggregate.AggregateFuncti
|
||||||
import org.elasticsearch.xpack.sql.expression.function.aggregate.Max;
|
import org.elasticsearch.xpack.sql.expression.function.aggregate.Max;
|
||||||
import org.elasticsearch.xpack.sql.expression.function.aggregate.Min;
|
import org.elasticsearch.xpack.sql.expression.function.aggregate.Min;
|
||||||
import org.elasticsearch.xpack.sql.expression.function.aggregate.TopHits;
|
import org.elasticsearch.xpack.sql.expression.function.aggregate.TopHits;
|
||||||
|
import org.elasticsearch.xpack.sql.expression.function.grouping.GroupingFunction;
|
||||||
import org.elasticsearch.xpack.sql.expression.function.grouping.GroupingFunctionAttribute;
|
import org.elasticsearch.xpack.sql.expression.function.grouping.GroupingFunctionAttribute;
|
||||||
import org.elasticsearch.xpack.sql.expression.function.scalar.ScalarFunction;
|
import org.elasticsearch.xpack.sql.expression.function.scalar.ScalarFunction;
|
||||||
import org.elasticsearch.xpack.sql.expression.predicate.conditional.ConditionalFunction;
|
import org.elasticsearch.xpack.sql.expression.predicate.conditional.ConditionalFunction;
|
||||||
|
@ -229,6 +230,7 @@ public final class Verifier {
|
||||||
validateInExpression(p, localFailures);
|
validateInExpression(p, localFailures);
|
||||||
validateConditional(p, localFailures);
|
validateConditional(p, localFailures);
|
||||||
|
|
||||||
|
checkGroupingFunctionInGroupBy(p, localFailures);
|
||||||
checkFilterOnAggs(p, localFailures);
|
checkFilterOnAggs(p, localFailures);
|
||||||
checkFilterOnGrouping(p, localFailures);
|
checkFilterOnGrouping(p, localFailures);
|
||||||
|
|
||||||
|
@ -587,6 +589,24 @@ public final class Verifier {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void checkGroupingFunctionInGroupBy(LogicalPlan p, Set<Failure> localFailures) {
|
||||||
|
// check if the query has a grouping function (Histogram) but no GROUP BY
|
||||||
|
if (p instanceof Project) {
|
||||||
|
Project proj = (Project) p;
|
||||||
|
proj.projections().forEach(e -> e.forEachDown(f ->
|
||||||
|
localFailures.add(fail(f, "[{}] needs to be part of the grouping", Expressions.name(f))), GroupingFunction.class));
|
||||||
|
} else if (p instanceof Aggregate) {
|
||||||
|
// if it does have a GROUP BY, check if the groupings contain the grouping functions (Histograms)
|
||||||
|
Aggregate a = (Aggregate) p;
|
||||||
|
a.aggregates().forEach(agg -> agg.forEachDown(e -> {
|
||||||
|
if (a.groupings().size() == 0
|
||||||
|
|| Expressions.anyMatch(a.groupings(), g -> g instanceof Function && e.functionEquals((Function) g)) == false) {
|
||||||
|
localFailures.add(fail(e, "[{}] needs to be part of the grouping", Expressions.name(e)));
|
||||||
|
}
|
||||||
|
}, GroupingFunction.class));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void checkFilterOnAggs(LogicalPlan p, Set<Failure> localFailures) {
|
private static void checkFilterOnAggs(LogicalPlan p, Set<Failure> localFailures) {
|
||||||
if (p instanceof Filter) {
|
if (p instanceof Filter) {
|
||||||
Filter filter = (Filter) p;
|
Filter filter = (Filter) p;
|
||||||
|
|
|
@ -57,16 +57,6 @@ public abstract class GroupingFunction extends Function {
|
||||||
return lazyAttribute;
|
return lazyAttribute;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public final GroupingFunction replaceChildren(List<Expression> newChildren) {
|
|
||||||
if (newChildren.size() != 1) {
|
|
||||||
throw new IllegalArgumentException("expected [1] child but received [" + newChildren.size() + "]");
|
|
||||||
}
|
|
||||||
return replaceChild(newChildren.get(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract GroupingFunction replaceChild(Expression newChild);
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Pipe makePipe() {
|
protected Pipe makePipe() {
|
||||||
// unresolved AggNameInput (should always get replaced by the folder)
|
// unresolved AggNameInput (should always get replaced by the folder)
|
||||||
|
|
|
@ -10,12 +10,14 @@ import org.elasticsearch.xpack.sql.expression.Expression;
|
||||||
import org.elasticsearch.xpack.sql.expression.Expressions;
|
import org.elasticsearch.xpack.sql.expression.Expressions;
|
||||||
import org.elasticsearch.xpack.sql.expression.Expressions.ParamOrdinal;
|
import org.elasticsearch.xpack.sql.expression.Expressions.ParamOrdinal;
|
||||||
import org.elasticsearch.xpack.sql.expression.Literal;
|
import org.elasticsearch.xpack.sql.expression.Literal;
|
||||||
import org.elasticsearch.xpack.sql.tree.Source;
|
|
||||||
import org.elasticsearch.xpack.sql.tree.NodeInfo;
|
import org.elasticsearch.xpack.sql.tree.NodeInfo;
|
||||||
|
import org.elasticsearch.xpack.sql.tree.Source;
|
||||||
import org.elasticsearch.xpack.sql.type.DataType;
|
import org.elasticsearch.xpack.sql.type.DataType;
|
||||||
import org.elasticsearch.xpack.sql.type.DataTypes;
|
import org.elasticsearch.xpack.sql.type.DataTypes;
|
||||||
|
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public class Histogram extends GroupingFunction {
|
public class Histogram extends GroupingFunction {
|
||||||
|
@ -24,8 +26,8 @@ public class Histogram extends GroupingFunction {
|
||||||
private final ZoneId zoneId;
|
private final ZoneId zoneId;
|
||||||
|
|
||||||
public Histogram(Source source, Expression field, Expression interval, ZoneId zoneId) {
|
public Histogram(Source source, Expression field, Expression interval, ZoneId zoneId) {
|
||||||
super(source, field);
|
super(source, field, Collections.singletonList(interval));
|
||||||
this.interval = (Literal) interval;
|
this.interval = Literal.of(interval);
|
||||||
this.zoneId = zoneId;
|
this.zoneId = zoneId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,8 +55,11 @@ public class Histogram extends GroupingFunction {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected GroupingFunction replaceChild(Expression newChild) {
|
public final GroupingFunction replaceChildren(List<Expression> newChildren) {
|
||||||
return new Histogram(source(), newChild, interval, zoneId);
|
if (newChildren.size() != 2) {
|
||||||
|
throw new IllegalArgumentException("expected [2] children but received [" + newChildren.size() + "]");
|
||||||
|
}
|
||||||
|
return new Histogram(source(), newChildren.get(0), newChildren.get(1), zoneId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -540,6 +540,41 @@ public class VerifierErrorMessagesTests extends ESTestCase {
|
||||||
error("SELECT MAX(date) FROM test GROUP BY HISTOGRAM(MAX(int), 1)"));
|
error("SELECT MAX(date) FROM test GROUP BY HISTOGRAM(MAX(int), 1)"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testHistogramNotInGrouping() {
|
||||||
|
assertEquals("1:8: [HISTOGRAM(date, INTERVAL 1 MONTH)] needs to be part of the grouping",
|
||||||
|
error("SELECT HISTOGRAM(date, INTERVAL 1 MONTH) AS h FROM test"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testHistogramNotInGroupingWithCount() {
|
||||||
|
assertEquals("1:8: [HISTOGRAM(date, INTERVAL 1 MONTH)] needs to be part of the grouping",
|
||||||
|
error("SELECT HISTOGRAM(date, INTERVAL 1 MONTH) AS h, COUNT(*) FROM test"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testHistogramNotInGroupingWithMaxFirst() {
|
||||||
|
assertEquals("1:19: [HISTOGRAM(date, INTERVAL 1 MONTH)] needs to be part of the grouping",
|
||||||
|
error("SELECT MAX(date), HISTOGRAM(date, INTERVAL 1 MONTH) AS h FROM test"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testHistogramWithoutAliasNotInGrouping() {
|
||||||
|
assertEquals("1:8: [HISTOGRAM(date, INTERVAL 1 MONTH)] needs to be part of the grouping",
|
||||||
|
error("SELECT HISTOGRAM(date, INTERVAL 1 MONTH) FROM test"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testTwoHistogramsNotInGrouping() {
|
||||||
|
assertEquals("1:48: [HISTOGRAM(date, INTERVAL 1 DAY)] needs to be part of the grouping",
|
||||||
|
error("SELECT HISTOGRAM(date, INTERVAL 1 MONTH) AS h, HISTOGRAM(date, INTERVAL 1 DAY) FROM test GROUP BY h"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testHistogramNotInGrouping_WithGroupByField() {
|
||||||
|
assertEquals("1:8: [HISTOGRAM(date, INTERVAL 1 MONTH)] needs to be part of the grouping",
|
||||||
|
error("SELECT HISTOGRAM(date, INTERVAL 1 MONTH) FROM test GROUP BY date"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testScalarOfHistogramNotInGrouping() {
|
||||||
|
assertEquals("1:14: [HISTOGRAM(date, INTERVAL 1 MONTH)] needs to be part of the grouping",
|
||||||
|
error("SELECT MONTH(HISTOGRAM(date, INTERVAL 1 MONTH)) FROM test"));
|
||||||
|
}
|
||||||
|
|
||||||
public void testErrorMessageForPercentileWithSecondArgBasedOnAField() {
|
public void testErrorMessageForPercentileWithSecondArgBasedOnAField() {
|
||||||
assertEquals("1:8: Second argument of PERCENTILE must be a constant, received [ABS(int)]",
|
assertEquals("1:8: Second argument of PERCENTILE must be a constant, received [ABS(int)]",
|
||||||
error("SELECT PERCENTILE(int, ABS(int)) FROM test"));
|
error("SELECT PERCENTILE(int, ABS(int)) FROM test"));
|
||||||
|
|
Loading…
Reference in New Issue