diff --git a/cmd/explaintest/r/topn_push_down.result b/cmd/explaintest/r/topn_push_down.result index e160d04af6bf2..a92ad36b9b19c 100644 --- a/cmd/explaintest/r/topn_push_down.result +++ b/cmd/explaintest/r/topn_push_down.result @@ -186,9 +186,8 @@ Projection_13 0.00 root te.expect_time └─IndexScan_101 10.00 cop table:p, index:relate_id, range: decided by [tr.id], keep order:false, stats:pseudo desc select 1 as a from dual order by a limit 1; id count task operator info -Projection_7 1.00 root 1 -└─Limit_8 1.00 root offset:0, count:1 - └─TableDual_11 1.00 root rows:1 +Projection_6 1.00 root 1 +└─TableDual_7 1.00 root rows:1 drop table if exists t1; drop table if exists t2; create table t1(a bigint, b bigint); diff --git a/cmd/explaintest/r/topn_pushdown.result b/cmd/explaintest/r/topn_pushdown.result new file mode 100644 index 0000000000000..19457028e055c --- /dev/null +++ b/cmd/explaintest/r/topn_pushdown.result @@ -0,0 +1,9 @@ +explain select * from ((select 4 as a) union all (select 33 as a)) tmp order by a desc limit 1; +id count task operator info +TopN_17 1.00 root tmp.a:desc, offset:0, count:1 +└─Union_21 2.00 root + ├─Projection_22 1.00 root cast(a) + │ └─Projection_23 1.00 root 4 + │ └─TableDual_24 1.00 root rows:1 + └─Projection_26 1.00 root 33 + └─TableDual_27 1.00 root rows:1 diff --git a/cmd/explaintest/t/topn_pushdown.test b/cmd/explaintest/t/topn_pushdown.test new file mode 100644 index 0000000000000..7ff3889a63ae6 --- /dev/null +++ b/cmd/explaintest/t/topn_pushdown.test @@ -0,0 +1 @@ +explain select * from ((select 4 as a) union all (select 33 as a)) tmp order by a desc limit 1; diff --git a/executor/join_test.go b/executor/join_test.go index 1ca53cf38f3b1..255f1befb90da 100644 --- a/executor/join_test.go +++ b/executor/join_test.go @@ -1368,3 +1368,16 @@ func (s *testSuite2) TestScalarFuncNullSemiJoin(c *C) { tk.MustExec("insert into s values(null, 1)") tk.MustQuery("select a in (select a+b from s) from t").Check(testkit.Rows("", "")) } + +func (s *testSuite2) TestInjectProjOnTopN(c *C) { + tk := testkit.NewTestKit(c, s.store) + tk.MustExec("use test") + tk.MustExec("drop table if exists t1") + tk.MustExec("drop table if exists t2") + tk.MustExec("create table t1(a bigint, b bigint)") + tk.MustExec("create table t2(a bigint, b bigint)") + tk.MustExec("insert into t1 values(1, 1)") + tk.MustQuery("select t1.a+t1.b as result from t1 left join t2 on 1 = 0 order by result limit 20;").Check(testkit.Rows( + "2", + )) +} diff --git a/planner/core/physical_plan_test.go b/planner/core/physical_plan_test.go index 77e7529eedbd3..2b8fc1230f63a 100644 --- a/planner/core/physical_plan_test.go +++ b/planner/core/physical_plan_test.go @@ -115,7 +115,7 @@ func (s *testPlanSuite) TestDAGPlanBuilderSimpleCase(c *C) { // Test TopN push down in table single read. { sql: "select c from t order by t.a + t.b limit 1", - best: "TableReader(Table(t)->TopN([plus(test.t.a, test.t.b)],0,1))->Projection->TopN([col_3],0,1)->Projection", + best: "TableReader(Table(t)->TopN([plus(test.t.a, test.t.b)],0,1))->Projection->TopN([col_3],0,1)->Projection->Projection", }, // Test Limit push down in table single read. { @@ -1219,7 +1219,7 @@ func (s *testPlanSuite) TestAggEliminater(c *C) { // If max/min contains scalar function, we can still do transformation. { sql: "select max(a+1) from t;", - best: "TableReader(Table(t)->Sel([not(isnull(plus(test.t.a, 1)))])->TopN([plus(test.t.a, 1) true],0,1))->Projection->TopN([col_1 true],0,1)->Projection->StreamAgg", + best: "TableReader(Table(t)->Sel([not(isnull(plus(test.t.a, 1)))])->TopN([plus(test.t.a, 1) true],0,1))->Projection->TopN([col_1 true],0,1)->Projection->Projection->StreamAgg", }, // Do nothing to max+min. { diff --git a/planner/core/rule_inject_extra_projection.go b/planner/core/rule_inject_extra_projection.go index 81037ae4807ae..69d44294032c6 100644 --- a/planner/core/rule_inject_extra_projection.go +++ b/planner/core/rule_inject_extra_projection.go @@ -39,8 +39,8 @@ func NewProjInjector() *projInjector { } func (pe *projInjector) inject(plan PhysicalPlan) PhysicalPlan { - for _, child := range plan.Children() { - pe.inject(child) + for i, child := range plan.Children() { + plan.Children()[i] = pe.inject(child) } switch p := plan.(type) { @@ -205,5 +205,6 @@ func injectProjBelowSort(p PhysicalPlan, orderByItems []*ByItems) PhysicalPlan { if origChildProj, isChildProj := childPlan.(*PhysicalProjection); isChildProj { refine4NeighbourProj(bottomProj, origChildProj) } + return topProj } diff --git a/planner/core/rule_topn_push_down.go b/planner/core/rule_topn_push_down.go index 8c68cdf894b81..ddebf02121632 100644 --- a/planner/core/rule_topn_push_down.go +++ b/planner/core/rule_topn_push_down.go @@ -14,6 +14,7 @@ package core import ( + "github.com/cznic/mathutil" "github.com/pingcap/tidb/expression" ) @@ -38,6 +39,18 @@ func (s *baseLogicalPlan) pushDownTopN(topN *LogicalTopN) LogicalPlan { // setChild set p as topn's child. func (lt *LogicalTopN) setChild(p LogicalPlan) LogicalPlan { + // Remove this TopN if its child is a TableDual. + dual, isDual := p.(*LogicalTableDual) + if isDual { + numDualRows := uint64(dual.RowCount) + if numDualRows < lt.Offset { + dual.RowCount = 0 + return dual + } + dual.RowCount = int(mathutil.MinUint64(numDualRows-lt.Offset, lt.Count)) + return dual + } + if lt.isLimit() { limit := LogicalLimit{ Count: lt.Count,