Skip to content

Commit

Permalink
planner: check for only_full_group_by in ORDER BY and HAVING (#21216)
Browse files Browse the repository at this point in the history
  • Loading branch information
dyzsr authored Dec 14, 2020
1 parent 6e7613f commit 49b926e
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 8 deletions.
1 change: 1 addition & 0 deletions errno/errcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -823,6 +823,7 @@ const (
ErrMaxExecTimeExceeded = 1907
ErrInvalidFieldSize = 3013
ErrInvalidArgumentForLogarithm = 3020
ErrAggregateOrderNonAggQuery = 3029
ErrIncorrectType = 3064
ErrFieldInOrderNotSelect = 3065
ErrAggregateInOrderNotSelect = 3066
Expand Down
1 change: 1 addition & 0 deletions errno/errname.go
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,7 @@ var MySQLErrName = map[uint16]*mysql.ErrMessage{
ErrUnresolvedHintName: mysql.Message("Unresolved name '%s' for %s hint", nil),
ErrInvalidFieldSize: mysql.Message("Invalid size for column '%s'.", nil),
ErrInvalidArgumentForLogarithm: mysql.Message("Invalid argument for logarithm", nil),
ErrAggregateOrderNonAggQuery: mysql.Message("Expression #%d of ORDER BY contains aggregate function and applies to the result of a non-aggregated query", nil),
ErrIncorrectType: mysql.Message("Incorrect type for argument %s in function %s.", nil),
ErrFieldInOrderNotSelect: mysql.Message("Expression #%d of ORDER BY clause is not in SELECT list, references column '%s' which is not in SELECT list; this is incompatible with %s", nil),
ErrAggregateInOrderNotSelect: mysql.Message("Expression #%d of ORDER BY clause is not in SELECT list, contains aggregate function; this is incompatible with %s", nil),
Expand Down
5 changes: 5 additions & 0 deletions errors.toml
Original file line number Diff line number Diff line change
Expand Up @@ -936,6 +936,11 @@ error = '''
Internal : %s
'''

["planner:3029"]
error = '''
Expression #%d of ORDER BY contains aggregate function and applies to the result of a non-aggregated query
'''

["planner:3065"]
error = '''
Expression #%d of ORDER BY clause is not in SELECT list, references column '%s' which is not in SELECT list; this is incompatible with %s
Expand Down
1 change: 1 addition & 0 deletions planner/core/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ var (
ErrWrongNumberOfColumnsInSelect = dbterror.ClassOptimizer.NewStd(mysql.ErrWrongNumberOfColumnsInSelect)
ErrBadGeneratedColumn = dbterror.ClassOptimizer.NewStd(mysql.ErrBadGeneratedColumn)
ErrFieldNotInGroupBy = dbterror.ClassOptimizer.NewStd(mysql.ErrFieldNotInGroupBy)
ErrAggregateOrderNonAggQuery = dbterror.ClassOptimizer.NewStd(mysql.ErrAggregateOrderNonAggQuery)
ErrFieldInOrderNotSelect = dbterror.ClassOptimizer.NewStd(mysql.ErrFieldInOrderNotSelect)
ErrAggregateInOrderNotSelect = dbterror.ClassOptimizer.NewStd(mysql.ErrAggregateInOrderNotSelect)
ErrBadTable = dbterror.ClassOptimizer.NewStd(mysql.ErrBadTable)
Expand Down
12 changes: 12 additions & 0 deletions planner/core/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1953,6 +1953,18 @@ func (s *testIntegrationSuite) TestMultiUpdateOnPrimaryKey(c *C) {
tk.MustQuery("select * from t").Check(testkit.Rows("11 12"))
}

func (s *testIntegrationSuite) TestOrderByHavingNotInSelect(c *C) {
tk := testkit.NewTestKit(c, s.store)
tk.MustExec("use test")
tk.MustExec("drop table if exists ttest")
tk.MustExec("create table ttest (v1 int, v2 int)")
tk.MustExec("insert into ttest values(1, 2), (4,6), (1, 7)")
tk.MustGetErrMsg("select v1 from ttest order by count(v2)",
"[planner:3029]Expression #1 of ORDER BY contains aggregate function and applies to the result of a non-aggregated query")
tk.MustGetErrMsg("select v1 from ttest having count(v2)",
"[planner:8123]In aggregated query without GROUP BY, expression #1 of SELECT list contains nonaggregated column 'v1'; this is incompatible with sql_mode=only_full_group_by")
}

func (s *testIntegrationSuite) TestUpdateSetDefault(c *C) {
// #20598
tk := testkit.NewTestKit(c, s.store)
Expand Down
46 changes: 38 additions & 8 deletions planner/core/logical_plan_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -2410,7 +2410,7 @@ func (b *PlanBuilder) checkOnlyFullGroupBy(p LogicalPlan, sel *ast.SelectStmt) (
if sel.GroupBy != nil {
err = b.checkOnlyFullGroupByWithGroupClause(p, sel)
} else {
err = b.checkOnlyFullGroupByWithOutGroupClause(p, sel.Fields.Fields)
err = b.checkOnlyFullGroupByWithOutGroupClause(p, sel)
}
return err
}
Expand Down Expand Up @@ -2484,32 +2484,58 @@ func (b *PlanBuilder) checkOnlyFullGroupByWithGroupClause(p LogicalPlan, sel *as
return nil
}

func (b *PlanBuilder) checkOnlyFullGroupByWithOutGroupClause(p LogicalPlan, fields []*ast.SelectField) error {
func (b *PlanBuilder) checkOnlyFullGroupByWithOutGroupClause(p LogicalPlan, sel *ast.SelectStmt) error {
resolver := colResolverForOnlyFullGroupBy{}
for idx, field := range fields {
resolver.curClause = fieldList
for idx, field := range sel.Fields.Fields {
resolver.exprIdx = idx
field.Accept(&resolver)
err := resolver.Check()
if err != nil {
return err
}
}
if resolver.firstNonAggCol != nil {
if sel.Having != nil {
sel.Having.Expr.Accept(&resolver)
err := resolver.Check()
if err != nil {
return err
}
}
if sel.OrderBy != nil {
resolver.curClause = orderByClause
for idx, byItem := range sel.OrderBy.Items {
resolver.exprIdx = idx
byItem.Expr.Accept(&resolver)
err := resolver.Check()
if err != nil {
return err
}
}
}
}
return nil
}

// colResolverForOnlyFullGroupBy visits Expr tree to find out if an Expr tree is an aggregation function.
// If so, find out the first column name that not in an aggregation function.
type colResolverForOnlyFullGroupBy struct {
firstNonAggCol *ast.ColumnName
exprIdx int
firstNonAggColIdx int
hasAggFuncOrAnyValue bool
firstNonAggCol *ast.ColumnName
exprIdx int
firstNonAggColIdx int
hasAggFuncOrAnyValue bool
firstOrderByAggColIdx int
curClause clauseCode
}

func (c *colResolverForOnlyFullGroupBy) Enter(node ast.Node) (ast.Node, bool) {
switch t := node.(type) {
case *ast.AggregateFuncExpr:
c.hasAggFuncOrAnyValue = true
if c.curClause == orderByClause {
c.firstOrderByAggColIdx = c.exprIdx
}
return node, true
case *ast.FuncCallExpr:
// enable function `any_value` in aggregation even `ONLY_FULL_GROUP_BY` is set
Expand All @@ -2534,7 +2560,11 @@ func (c *colResolverForOnlyFullGroupBy) Leave(node ast.Node) (ast.Node, bool) {

func (c *colResolverForOnlyFullGroupBy) Check() error {
if c.hasAggFuncOrAnyValue && c.firstNonAggCol != nil {
return ErrMixOfGroupFuncAndFields.GenWithStackByArgs(c.firstNonAggColIdx+1, c.firstNonAggCol.Name.O)
if c.curClause == fieldList {
return ErrMixOfGroupFuncAndFields.GenWithStackByArgs(c.firstNonAggColIdx+1, c.firstNonAggCol.Name.O)
} else if c.curClause == orderByClause {
return ErrAggregateOrderNonAggQuery.GenWithStackByArgs(c.firstOrderByAggColIdx + 1)
}
}
return nil
}
Expand Down

0 comments on commit 49b926e

Please sign in to comment.