diff --git a/pkg/executor/test/executor/BUILD.bazel b/pkg/executor/test/executor/BUILD.bazel index 7443ac982b576..5f088f9a45836 100644 --- a/pkg/executor/test/executor/BUILD.bazel +++ b/pkg/executor/test/executor/BUILD.bazel @@ -8,7 +8,7 @@ go_test( "main_test.go", ], flaky = True, - shard_count = 46, + shard_count = 47, deps = [ "//pkg/config", "//pkg/ddl", diff --git a/pkg/executor/test/executor/executor_test.go b/pkg/executor/test/executor/executor_test.go index 6e4d3376e5ead..f5c5ab409c19c 100644 --- a/pkg/executor/test/executor/executor_test.go +++ b/pkg/executor/test/executor/executor_test.go @@ -2724,3 +2724,36 @@ func TestProcessInfoOfSubQuery(t *testing.T) { tk2.MustQuery("select 1 from information_schema.processlist where TxnStart != '' and info like 'select%sleep% from t%'").Check(testkit.Rows("1")) wg.Wait() } + +func TestIssues49377(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + 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/pkg/parser/ast/dml.go b/pkg/parser/ast/dml.go index 69ea4efb3dd6c..c13b836b18331 100644 --- a/pkg/parser/ast/dml.go +++ b/pkg/parser/ast/dml.go @@ -1557,6 +1557,8 @@ type SetOprSelectList struct { With *WithClause AfterSetOperator *SetOprType Selects []Node + Limit *Limit + OrderBy *OrderByClause } // Restore implements Node interface. diff --git a/pkg/parser/parser.go b/pkg/parser/parser.go index 9b8adb2ec5af9..21766c70bca8a 100644 --- a/pkg/parser/parser.go +++ b/pkg/parser/parser.go @@ -19948,15 +19948,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/pkg/parser/parser.y b/pkg/parser/parser.y index 281735c9af7de..64629b4337161 100644 --- a/pkg/parser/parser.y +++ b/pkg/parser/parser.y @@ -10253,15 +10253,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/pkg/planner/core/casetest/physicalplantest/BUILD.bazel b/pkg/planner/core/casetest/physicalplantest/BUILD.bazel index 5281d694291c3..94eabd31c64a9 100644 --- a/pkg/planner/core/casetest/physicalplantest/BUILD.bazel +++ b/pkg/planner/core/casetest/physicalplantest/BUILD.bazel @@ -10,7 +10,7 @@ go_test( data = glob(["testdata/**"]), flaky = True, race = "on", - shard_count = 30, + shard_count = 31, deps = [ "//pkg/config", "//pkg/domain", diff --git a/pkg/planner/core/casetest/physicalplantest/physical_plan_test.go b/pkg/planner/core/casetest/physicalplantest/physical_plan_test.go index ba29c00fee313..3583d7caec5be 100644 --- a/pkg/planner/core/casetest/physicalplantest/physical_plan_test.go +++ b/pkg/planner/core/casetest/physicalplantest/physical_plan_test.go @@ -1352,6 +1352,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/pkg/planner/core/casetest/physicalplantest/testdata/plan_suite_in.json b/pkg/planner/core/casetest/physicalplantest/testdata/plan_suite_in.json index a4d6a105b6001..effc139ebeb82 100644 --- a/pkg/planner/core/casetest/physicalplantest/testdata/plan_suite_in.json +++ b/pkg/planner/core/casetest/physicalplantest/testdata/plan_suite_in.json @@ -594,5 +594,14 @@ "select /*+ agg_to_cop() hash_agg() */ count(1) from tbl_15 ;", "select /*+ agg_to_cop() stream_agg() */ avg( tbl_16.col_100 ) as r0 from tbl_16 where tbl_16.col_100 in ( 10672141 ) or tbl_16.col_104 in ( 'yfEG1t!*b' ,'C1*bqx_qyO' ,'vQ^yUpKHr&j#~' ) group by tbl_16.col_100 order by r0 limit 20 ;" ] + }, + { + "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/pkg/planner/core/casetest/physicalplantest/testdata/plan_suite_out.json b/pkg/planner/core/casetest/physicalplantest/testdata/plan_suite_out.json index 8a7fc8f06327e..ecfc1e604428e 100644 --- a/pkg/planner/core/casetest/physicalplantest/testdata/plan_suite_out.json +++ b/pkg/planner/core/casetest/physicalplantest/testdata/plan_suite_out.json @@ -3621,5 +3621,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->Column#16, 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->Column#16, 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/pkg/planner/core/logical_plan_builder.go b/pkg/planner/core/logical_plan_builder.go index 5ab1b940fcb14..d9bb0bdca85dd 100644 --- a/pkg/planner/core/logical_plan_builder.go +++ b/pkg/planner/core/logical_plan_builder.go @@ -2078,6 +2078,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 @@ -2176,7 +2182,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 @@ -2200,7 +2206,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