Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

parser, planner: fix embedded setOprStmt will be seen as SetOprSelectList item and lost its orderBy and Limit (#49421) #49502

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions executor/explain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -569,3 +569,37 @@ func TestExplainJSON(t *testing.T) {
}
}
}

func TestIssues49377(t *testing.T) {
store := testkit.CreateMockStore(t)
tk := testkit.NewTestKit(t, store)
tk.MustExec("use test")
tk.MustExec("drop table if exists employee")
tk.MustExec("create table employee (employee_id int, name varchar(20), dept_id int)")
tk.MustExec("insert into employee values (1, 'Furina', 1), (2, 'Klee', 1), (3, 'Eula', 1), (4, 'Diluc', 2), (5, 'Tartaglia', 2)")

tk.MustQuery("select 1,1,1 union all ( " +
"(select * from employee where dept_id = 1) " +
"union all " +
"(select * from employee where dept_id = 1 order by employee_id) " +
"order by 1 limit 1 " +
");").Sort().Check(testkit.Rows("1 1 1", "1 Furina 1"))

tk.MustQuery("select 1,1,1 union all ( " +
"(select * from employee where dept_id = 1) " +
"union all " +
"(select * from employee where dept_id = 1 order by employee_id) " +
"order by 1" +
");").Sort().Check(testkit.Rows("1 1 1", "1 Furina 1", "1 Furina 1", "2 Klee 1", "2 Klee 1", "3 Eula 1", "3 Eula 1"))

tk.MustQuery("select * from employee where dept_id = 1 " +
"union all " +
"(select * from employee where dept_id = 1 order by employee_id) " +
"union all" +
"(" +
"select * from employee where dept_id = 1 " +
"union all " +
"(select * from employee where dept_id = 1 order by employee_id) " +
"limit 1" +
");").Sort().Check(testkit.Rows("1 Furina 1", "1 Furina 1", "1 Furina 1", "2 Klee 1", "2 Klee 1", "3 Eula 1", "3 Eula 1"))
}
2 changes: 2 additions & 0 deletions parser/ast/dml.go
Original file line number Diff line number Diff line change
Expand Up @@ -1540,6 +1540,8 @@ type SetOprSelectList struct {
With *WithClause
AfterSetOperator *SetOprType
Selects []Node
Limit *Limit
OrderBy *OrderByClause
}

// Restore implements Node interface.
Expand Down
8 changes: 7 additions & 1 deletion parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -18356,15 +18356,21 @@ yynewstate:
}
var setOprList2 []ast.Node
var with2 *ast.WithClause
var limit2 *ast.Limit
var orderBy2 *ast.OrderByClause
switch x := yyS[yypt-0].expr.(*ast.SubqueryExpr).Query.(type) {
case *ast.SelectStmt:
setOprList2 = []ast.Node{x}
with2 = x.With
case *ast.SetOprStmt:
// child setOprStmt's limit and order should also make sense
// we should separate it out from other normal SetOprSelectList.
setOprList2 = x.SelectList.Selects
with2 = x.With
limit2 = x.Limit
orderBy2 = x.OrderBy
}
nextSetOprList := &ast.SetOprSelectList{Selects: setOprList2, With: with2}
nextSetOprList := &ast.SetOprSelectList{Selects: setOprList2, With: with2, Limit: limit2, OrderBy: orderBy2}
nextSetOprList.AfterSetOperator = yyS[yypt-1].item.(*ast.SetOprType)
setOprList := append(setOprList1, nextSetOprList)
setOpr := &ast.SetOprStmt{SelectList: &ast.SetOprSelectList{Selects: setOprList}}
Expand Down
8 changes: 7 additions & 1 deletion parser/parser.y
Original file line number Diff line number Diff line change
Expand Up @@ -9754,15 +9754,21 @@ SetOprStmtWoutLimitOrderBy:
}
var setOprList2 []ast.Node
var with2 *ast.WithClause
var limit2 *ast.Limit
var orderBy2 *ast.OrderByClause
switch x := $3.(*ast.SubqueryExpr).Query.(type) {
case *ast.SelectStmt:
setOprList2 = []ast.Node{x}
with2 = x.With
case *ast.SetOprStmt:
// child setOprStmt's limit and order should also make sense
// we should separate it out from other normal SetOprSelectList.
setOprList2 = x.SelectList.Selects
with2 = x.With
limit2 = x.Limit
orderBy2 = x.OrderBy
}
nextSetOprList := &ast.SetOprSelectList{Selects: setOprList2, With: with2}
nextSetOprList := &ast.SetOprSelectList{Selects: setOprList2, With: with2, Limit: limit2, OrderBy: orderBy2}
nextSetOprList.AfterSetOperator = $2.(*ast.SetOprType)
setOprList := append(setOprList1, nextSetOprList)
setOpr := &ast.SetOprStmt{SelectList: &ast.SetOprSelectList{Selects: setOprList}}
Expand Down
12 changes: 9 additions & 3 deletions planner/core/logical_plan_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -1818,6 +1818,12 @@ func (b *PlanBuilder) buildSetOpr(ctx context.Context, setOpr *ast.SetOprStmt) (
if *x.AfterSetOperator != ast.Intersect && *x.AfterSetOperator != ast.IntersectAll {
breakIteration = true
}
if x.Limit != nil || x.OrderBy != nil {
// when SetOprSelectList's limit and order-by is not nil, it means itself is converted from
// an independent ast.SetOprStmt in parser, its data should be evaluated first, and ordered
// by given items and conduct a limit on it, then it can only be integrated with other brothers.
breakIteration = true
}
}
if breakIteration {
break
Expand Down Expand Up @@ -1916,7 +1922,7 @@ func (b *PlanBuilder) buildIntersect(ctx context.Context, selects []ast.Node) (L
leftPlan, err = b.buildSelect(ctx, x)
case *ast.SetOprSelectList:
afterSetOperator = x.AfterSetOperator
leftPlan, err = b.buildSetOpr(ctx, &ast.SetOprStmt{SelectList: x, With: x.With})
leftPlan, err = b.buildSetOpr(ctx, &ast.SetOprStmt{SelectList: x, With: x.With, Limit: x.Limit, OrderBy: x.OrderBy})
}
if err != nil {
return nil, nil, err
Expand All @@ -1940,7 +1946,7 @@ func (b *PlanBuilder) buildIntersect(ctx context.Context, selects []ast.Node) (L
// TODO: support intersect all
return nil, nil, errors.Errorf("TiDB do not support intersect all")
}
rightPlan, err = b.buildSetOpr(ctx, &ast.SetOprStmt{SelectList: x, With: x.With})
rightPlan, err = b.buildSetOpr(ctx, &ast.SetOprStmt{SelectList: x, With: x.With, Limit: x.Limit, OrderBy: x.OrderBy})
}
if err != nil {
return nil, nil, err
Expand Down Expand Up @@ -7273,7 +7279,7 @@ func (b *PlanBuilder) buildRecursiveCTE(ctx context.Context, cte ast.ResultSetNo
p, err = b.buildSelect(ctx, y)
afterOpr = y.AfterSetOperator
case *ast.SetOprSelectList:
p, err = b.buildSetOpr(ctx, &ast.SetOprStmt{SelectList: y, With: y.With})
p, err = b.buildSetOpr(ctx, &ast.SetOprStmt{SelectList: y, With: y.With, Limit: y.Limit, OrderBy: y.OrderBy})
afterOpr = y.AfterSetOperator
}

Expand Down
26 changes: 26 additions & 0 deletions planner/core/physical_plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2562,3 +2562,29 @@ func TestIndexMergeOrderPushDown(t *testing.T) {
tk.MustQuery("explain format = 'brief' " + ts).Check(testkit.Rows(output[i].Plan...))
}
}

func TestIssues49377Plan(t *testing.T) {
store := testkit.CreateMockStore(t)
tk := testkit.NewTestKit(t, store)
tk.MustExec("use test")
tk.MustExec("drop table if exists employee")
tk.MustExec("create table employee (employee_id int, name varchar(20), dept_id int)")

var (
input []string
output []struct {
SQL string
Plan []string
Warning []string
}
)
planSuiteData := core.GetPlanSuiteData()
planSuiteData.LoadTestCases(t, &input, &output)
for i, ts := range input {
testdata.OnRecord(func() {
output[i].SQL = ts
output[i].Plan = testdata.ConvertRowsToStrings(tk.MustQuery("explain format = 'brief' " + ts).Rows())
})
tk.MustQuery("explain format = 'brief' " + ts).Check(testkit.Rows(output[i].Plan...))
}
}
9 changes: 9 additions & 0 deletions planner/core/testdata/plan_suite_in.json
Original file line number Diff line number Diff line change
Expand Up @@ -1148,5 +1148,14 @@
"select * from t where (a = 1 or b = 2) and c = 3 order by c limit 2",
"select * from t where (a = 1 or b = 2) and c in (1, 2, 3) order by c limit 2"
]
},
{
"name": "TestIssues49377Plan",
"cases": [
"select 1,1,1 union all ((select * from employee where dept_id = 1) union all ( select * from employee where dept_id = 1 order by employee_id ) order by 1 );",
"select 1,1,1 union all ((select * from employee where dept_id = 1) union all ( select * from employee where dept_id = 1 order by employee_id ) order by 1 limit 1);",
"select * from employee where dept_id = 1 union all ( select * from employee where dept_id = 1 order by employee_id) union all ( select * from employee where dept_id = 1 union all ( select * from employee where dept_id = 1 order by employee_id ) limit 1);",
"select * from employee where dept_id = 1 union all ( select * from employee where dept_id = 1 order by employee_id) union all ( select * from employee where dept_id = 1 union all ( select * from employee where dept_id = 1 order by employee_id ) order by 1 limit 1);"
]
}
]
98 changes: 98 additions & 0 deletions planner/core/testdata/plan_suite_out.json
Original file line number Diff line number Diff line change
Expand Up @@ -6945,5 +6945,103 @@
"Warning": null
}
]
},
{
"Name": "TestIssues49377Plan",
"Cases": [
{
"SQL": "select 1,1,1 union all ((select * from employee where dept_id = 1) union all ( select * from employee where dept_id = 1 order by employee_id ) order by 1 );",
"Plan": [
"Union 21.00 root ",
"├─Projection 1.00 root 1->Column#15, 1->Column#16, 1->Column#17",
"│ └─TableDual 1.00 root rows:1",
"└─Projection 20.00 root cast(Column#12, bigint(11) BINARY)->Column#15, Column#13, cast(Column#14, bigint(11) BINARY)->Column#17",
" └─Sort 20.00 root Column#12",
" └─Union 20.00 root ",
" ├─TableReader 10.00 root data:Selection",
" │ └─Selection 10.00 cop[tikv] eq(test.employee.dept_id, 1)",
" │ └─TableFullScan 10000.00 cop[tikv] table:employee keep order:false, stats:pseudo",
" └─Sort 10.00 root test.employee.employee_id",
" └─TableReader 10.00 root data:Selection",
" └─Selection 10.00 cop[tikv] eq(test.employee.dept_id, 1)",
" └─TableFullScan 10000.00 cop[tikv] table:employee keep order:false, stats:pseudo"
],
"Warning": null
},
{
"SQL": "select 1,1,1 union all ((select * from employee where dept_id = 1) union all ( select * from employee where dept_id = 1 order by employee_id ) order by 1 limit 1);",
"Plan": [
"Union 2.00 root ",
"├─Projection 1.00 root 1->Column#15, 1->Column#16, 1->Column#17",
"│ └─TableDual 1.00 root rows:1",
"└─Projection 1.00 root cast(Column#12, bigint(11) BINARY)->Column#15, Column#13, cast(Column#14, bigint(11) BINARY)->Column#17",
" └─TopN 1.00 root Column#12, offset:0, count:1",
" └─Union 2.00 root ",
" ├─TopN 1.00 root test.employee.employee_id, offset:0, count:1",
" │ └─TableReader 1.00 root data:TopN",
" │ └─TopN 1.00 cop[tikv] test.employee.employee_id, offset:0, count:1",
" │ └─Selection 10.00 cop[tikv] eq(test.employee.dept_id, 1)",
" │ └─TableFullScan 10000.00 cop[tikv] table:employee keep order:false, stats:pseudo",
" └─TopN 1.00 root test.employee.employee_id, offset:0, count:1",
" └─TableReader 1.00 root data:TopN",
" └─TopN 1.00 cop[tikv] test.employee.employee_id, offset:0, count:1",
" └─Selection 10.00 cop[tikv] eq(test.employee.dept_id, 1)",
" └─TableFullScan 10000.00 cop[tikv] table:employee keep order:false, stats:pseudo"
],
"Warning": null
},
{
"SQL": "select * from employee where dept_id = 1 union all ( select * from employee where dept_id = 1 order by employee_id) union all ( select * from employee where dept_id = 1 union all ( select * from employee where dept_id = 1 order by employee_id ) limit 1);",
"Plan": [
"Union 21.00 root ",
"├─TableReader 10.00 root data:Selection",
"│ └─Selection 10.00 cop[tikv] eq(test.employee.dept_id, 1)",
"│ └─TableFullScan 10000.00 cop[tikv] table:employee keep order:false, stats:pseudo",
"├─Sort 10.00 root test.employee.employee_id",
"│ └─TableReader 10.00 root data:Selection",
"│ └─Selection 10.00 cop[tikv] eq(test.employee.dept_id, 1)",
"│ └─TableFullScan 10000.00 cop[tikv] table:employee keep order:false, stats:pseudo",
"└─Limit 1.00 root offset:0, count:1",
" └─Union 1.00 root ",
" ├─Limit 1.00 root offset:0, count:1",
" │ └─TableReader 1.00 root data:Limit",
" │ └─Limit 1.00 cop[tikv] offset:0, count:1",
" │ └─Selection 1.00 cop[tikv] eq(test.employee.dept_id, 1)",
" │ └─TableFullScan 1000.00 cop[tikv] table:employee keep order:false, stats:pseudo",
" └─TopN 1.00 root test.employee.employee_id, offset:0, count:1",
" └─TableReader 1.00 root data:TopN",
" └─TopN 1.00 cop[tikv] test.employee.employee_id, offset:0, count:1",
" └─Selection 10.00 cop[tikv] eq(test.employee.dept_id, 1)",
" └─TableFullScan 10000.00 cop[tikv] table:employee keep order:false, stats:pseudo"
],
"Warning": null
},
{
"SQL": "select * from employee where dept_id = 1 union all ( select * from employee where dept_id = 1 order by employee_id) union all ( select * from employee where dept_id = 1 union all ( select * from employee where dept_id = 1 order by employee_id ) order by 1 limit 1);",
"Plan": [
"Union 21.00 root ",
"├─TableReader 10.00 root data:Selection",
"│ └─Selection 10.00 cop[tikv] eq(test.employee.dept_id, 1)",
"│ └─TableFullScan 10000.00 cop[tikv] table:employee keep order:false, stats:pseudo",
"├─Sort 10.00 root test.employee.employee_id",
"│ └─TableReader 10.00 root data:Selection",
"│ └─Selection 10.00 cop[tikv] eq(test.employee.dept_id, 1)",
"│ └─TableFullScan 10000.00 cop[tikv] table:employee keep order:false, stats:pseudo",
"└─TopN 1.00 root Column#17, offset:0, count:1",
" └─Union 2.00 root ",
" ├─TopN 1.00 root test.employee.employee_id, offset:0, count:1",
" │ └─TableReader 1.00 root data:TopN",
" │ └─TopN 1.00 cop[tikv] test.employee.employee_id, offset:0, count:1",
" │ └─Selection 10.00 cop[tikv] eq(test.employee.dept_id, 1)",
" │ └─TableFullScan 10000.00 cop[tikv] table:employee keep order:false, stats:pseudo",
" └─TopN 1.00 root test.employee.employee_id, offset:0, count:1",
" └─TableReader 1.00 root data:TopN",
" └─TopN 1.00 cop[tikv] test.employee.employee_id, offset:0, count:1",
" └─Selection 10.00 cop[tikv] eq(test.employee.dept_id, 1)",
" └─TableFullScan 10000.00 cop[tikv] table:employee keep order:false, stats:pseudo"
],
"Warning": null
}
]
}
]