From 4d530739abe4f0369b1d5661d85b626cb3e94fe8 Mon Sep 17 00:00:00 2001 From: Arenatlx <314806019@qq.com> Date: Fri, 22 Dec 2023 19:00:25 +0800 Subject: [PATCH] cherry-pick: mannual cherry-pick 49421 back to release 7.1 (#49592) close pingcap/tidb#49377 --- executor/explain_test.go | 34 +++++++ parser/ast/dml.go | 2 + parser/parser.go | 8 +- parser/parser.y | 8 +- planner/core/casetest/physical_plan_test.go | 26 +++++ .../core/casetest/testdata/plan_suite_in.json | 9 ++ .../casetest/testdata/plan_suite_out.json | 98 +++++++++++++++++++ planner/core/logical_plan_builder.go | 10 +- 8 files changed, 191 insertions(+), 4 deletions(-) diff --git a/executor/explain_test.go b/executor/explain_test.go index 902f7c96eb2de..8495bc73bad0d 100644 --- a/executor/explain_test.go +++ b/executor/explain_test.go @@ -702,3 +702,37 @@ func TestExplainFormatPlanCache(t *testing.T) { tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("0")) } } + +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")) +} diff --git a/parser/ast/dml.go b/parser/ast/dml.go index f8c534170377a..d1bf9bfb64152 100644 --- a/parser/ast/dml.go +++ b/parser/ast/dml.go @@ -1550,6 +1550,8 @@ type SetOprSelectList struct { With *WithClause AfterSetOperator *SetOprType Selects []Node + Limit *Limit + OrderBy *OrderByClause } // Restore implements Node interface. diff --git a/parser/parser.go b/parser/parser.go index 0e9117e78406f..fc1bd4cfcfe70 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -19364,15 +19364,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}} diff --git a/parser/parser.y b/parser/parser.y index 9f1efd71f5101..6d881c6e16d2b 100644 --- a/parser/parser.y +++ b/parser/parser.y @@ -9994,15 +9994,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}} diff --git a/planner/core/casetest/physical_plan_test.go b/planner/core/casetest/physical_plan_test.go index 93fb721d20563..61936ee7c2443 100644 --- a/planner/core/casetest/physical_plan_test.go +++ b/planner/core/casetest/physical_plan_test.go @@ -2433,6 +2433,32 @@ func TestCountStarForTiFlash(t *testing.T) { } } +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 := 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...)) + } +} + func TestHashAggPushdownToTiFlashCompute(t *testing.T) { var ( input []string diff --git a/planner/core/casetest/testdata/plan_suite_in.json b/planner/core/casetest/testdata/plan_suite_in.json index 024374e955d7b..b4034ab0ed541 100644 --- a/planner/core/casetest/testdata/plan_suite_in.json +++ b/planner/core/casetest/testdata/plan_suite_in.json @@ -1332,5 +1332,14 @@ "select * from tcommon where (a = 1 and c = 2) or (b = 1) order by c limit 2", "select * from thash use index(idx_ac, idx_bc) where a = 1 or b = 1 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);" + ] } ] diff --git a/planner/core/casetest/testdata/plan_suite_out.json b/planner/core/casetest/testdata/plan_suite_out.json index 3a6ab8d934db6..341a68b3d50bf 100644 --- a/planner/core/casetest/testdata/plan_suite_out.json +++ b/planner/core/casetest/testdata/plan_suite_out.json @@ -8721,5 +8721,103 @@ ] } ] + }, + { + "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 + } + ] } ] diff --git a/planner/core/logical_plan_builder.go b/planner/core/logical_plan_builder.go index 8b4a551c42e9f..29a369f60695e 100644 --- a/planner/core/logical_plan_builder.go +++ b/planner/core/logical_plan_builder.go @@ -1824,6 +1824,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 @@ -1922,7 +1928,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 @@ -1946,7 +1952,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