// Copyright 2020 PingCAP, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // See the License for the specific language governing permissions and // limitations under the License. package core import ( "fmt" "math" . "github.com/pingcap/check" "github.com/pingcap/tidb/expression" "github.com/pingcap/tidb/planner/property" "github.com/pingcap/tidb/sessionctx" ) var _ = Suite(&testFindBestTaskSuite{}) type testFindBestTaskSuite struct { ctx sessionctx.Context } func (s *testFindBestTaskSuite) SetUpSuite(c *C) { s.ctx = MockContext() } type mockDataSource struct { baseLogicalPlan } func (ds mockDataSource) Init(ctx sessionctx.Context) *mockDataSource { ds.baseLogicalPlan = newBaseLogicalPlan(ctx, "mockDS", &ds, 0) return &ds } func (ds *mockDataSource) findBestTask(prop *property.PhysicalProperty) (task, error) { // It can satisfy any of the property! // Just use a TableDual for convenience. p := PhysicalTableDual{}.Init(ds.ctx, &property.StatsInfo{RowCount: 1}, 0) task := &rootTask{ p: p, cst: 10000, } return task, nil } // mockLogicalPlan4Test is a LogicalPlan which is used for unit test. // The basic assumption: // 1. mockLogicalPlan4Test can generate tow kinds of physical plan: physicalPlan1 and // physicalPlan2. physicalPlan1 can pass the property only when they are the same // order; while physicalPlan2 cannot match any of the property(in other words, we can // generate it only when then property is empty). // 2. We have a hint for physicalPlan2. // 3. If the property is empty, we still need to check `canGeneratePlan2` to decide // whether it can generate physicalPlan2. type mockLogicalPlan4Test struct { baseLogicalPlan // hasHintForPlan2 indicates whether this mockPlan contains hint. // This hint is used to generate physicalPlan2. See the implementation // of exhaustPhysicalPlans(). hasHintForPlan2 bool // canGeneratePlan2 indicates whether this plan can generate physicalPlan2. canGeneratePlan2 bool // costOverflow indicates whether this plan will generate physical plan whose cost is overflowed. costOverflow bool } func (p mockLogicalPlan4Test) Init(ctx sessionctx.Context) *mockLogicalPlan4Test { p.baseLogicalPlan = newBaseLogicalPlan(ctx, "mockPlan", &p, 0) return &p } func (p *mockLogicalPlan4Test) getPhysicalPlan1(prop *property.PhysicalProperty) PhysicalPlan { physicalPlan1 := mockPhysicalPlan4Test{planType: 1, costOverflow: p.costOverflow}.Init(p.ctx) physicalPlan1.stats = &property.StatsInfo{RowCount: 1} physicalPlan1.childrenReqProps = make([]*property.PhysicalProperty, 1) physicalPlan1.childrenReqProps[0] = prop.Clone() return physicalPlan1 } func (p *mockLogicalPlan4Test) getPhysicalPlan2(prop *property.PhysicalProperty) PhysicalPlan { physicalPlan2 := mockPhysicalPlan4Test{planType: 2, costOverflow: p.costOverflow}.Init(p.ctx) physicalPlan2.stats = &property.StatsInfo{RowCount: 1} physicalPlan2.childrenReqProps = make([]*property.PhysicalProperty, 1) physicalPlan2.childrenReqProps[0] = property.NewPhysicalProperty(prop.TaskTp, nil, false, prop.ExpectedCnt, false) return physicalPlan2 } func (p *mockLogicalPlan4Test) exhaustPhysicalPlans(prop *property.PhysicalProperty) ([]PhysicalPlan, bool) { plan1 := make([]PhysicalPlan, 0, 1) plan2 := make([]PhysicalPlan, 0, 1) if prop.IsEmpty() && p.canGeneratePlan2 { // Generate PhysicalPlan2 when the property is empty. plan2 = append(plan2, p.getPhysicalPlan2(prop)) if p.hasHintForPlan2 { return plan2, true } } if all, _ := prop.AllSameOrder(); all { // Generate PhysicalPlan1 when properties are the same order. plan1 = append(plan1, p.getPhysicalPlan1(prop)) } if p.hasHintForPlan2 { // The hint cannot work. if prop.IsEmpty() { p.ctx.GetSessionVars().StmtCtx.AppendWarning(fmt.Errorf("the hint is inapplicable for plan2")) } return plan1, false } return append(plan1, plan2...), true } type mockPhysicalPlan4Test struct { basePhysicalPlan // 1 or 2 for physicalPlan1 or physicalPlan2. // See the comment of mockLogicalPlan4Test. planType int costOverflow bool } func (p mockPhysicalPlan4Test) Init(ctx sessionctx.Context) *mockPhysicalPlan4Test { p.basePhysicalPlan = newBasePhysicalPlan(ctx, "mockPlan", &p, 0) return &p } func (p *mockPhysicalPlan4Test) attach2Task(tasks ...task) task { t := tasks[0].copy() attachPlan2Task(p, t) if p.costOverflow { t.addCost(math.MaxFloat64) } else { t.addCost(1) } return t } func (s *testFindBestTaskSuite) TestCostOverflow(c *C) { ctx := MockContext() // Plan Tree: mockPlan -> mockDataSource mockPlan := mockLogicalPlan4Test{costOverflow: true}.Init(ctx) mockDS := mockDataSource{}.Init(ctx) mockPlan.SetChildren(mockDS) // An empty property is enough for this test. prop := property.NewPhysicalProperty(property.RootTaskType, nil, false, 0, false) t, err := mockPlan.findBestTask(prop) c.Assert(err, IsNil) // The cost should be overflowed, but the task shouldn't be invalid. c.Assert(t.invalid(), IsFalse) c.Assert(t.cost(), Equals, math.MaxFloat64) } func (s *testFindBestTaskSuite) TestEnforcedProperty(c *C) { ctx := MockContext() // PlanTree : mockLogicalPlan -> mockDataSource mockPlan := mockLogicalPlan4Test{}.Init(ctx) mockDS := mockDataSource{}.Init(ctx) mockPlan.SetChildren(mockDS) col0 := &expression.Column{UniqueID: 1} col1 := &expression.Column{UniqueID: 2} // Use different order, so that mockLogicalPlan cannot generate any of the // physical plans. item0 := property.Item{Col: col0, Desc: false} item1 := property.Item{Col: col1, Desc: true} items := []property.Item{item0, item1} prop0 := &property.PhysicalProperty{ Items: items, Enforced: false, } // should return invalid task because no physical plan can match this property. task, err := mockPlan.findBestTask(prop0) c.Assert(err, IsNil) c.Assert(task.invalid(), IsTrue) prop1 := &property.PhysicalProperty{ Items: items, Enforced: true, } // should return the valid task when the property is enforced. task, err = mockPlan.findBestTask(prop1) c.Assert(err, IsNil) c.Assert(task.invalid(), IsFalse) } func (s *testFindBestTaskSuite) TestHintCannotFitProperty(c *C) { ctx := MockContext() // PlanTree : mockLogicalPlan -> mockDataSource mockPlan0 := mockLogicalPlan4Test{ hasHintForPlan2: true, canGeneratePlan2: true, }.Init(ctx) mockDS := mockDataSource{}.Init(ctx) mockPlan0.SetChildren(mockDS) col0 := &expression.Column{UniqueID: 1} item0 := property.Item{Col: col0} items := []property.Item{item0} // case 1, The property is not empty and enforced, should enforce a sort. prop0 := &property.PhysicalProperty{ Items: items, Enforced: true, } task, err := mockPlan0.findBestTask(prop0) c.Assert(err, IsNil) c.Assert(task.invalid(), IsFalse) _, enforcedSort := task.plan().(*PhysicalSort) c.Assert(enforcedSort, IsTrue) plan2 := task.plan().Children()[0] mockPhysicalPlan, ok := plan2.(*mockPhysicalPlan4Test) c.Assert(ok, IsTrue) c.Assert(mockPhysicalPlan.planType, Equals, 2) // case 2, The property is not empty but not enforced, still need to enforce a sort // to ensure the hint can work prop1 := &property.PhysicalProperty{ Items: items, Enforced: false, } task, err = mockPlan0.findBestTask(prop1) c.Assert(err, IsNil) c.Assert(task.invalid(), IsFalse) _, enforcedSort = task.plan().(*PhysicalSort) c.Assert(enforcedSort, IsTrue) plan2 = task.plan().Children()[0] mockPhysicalPlan, ok = plan2.(*mockPhysicalPlan4Test) c.Assert(ok, IsTrue) c.Assert(mockPhysicalPlan.planType, Equals, 2) // case 3, The hint cannot work even if the property is empty, should return a warning // and generate physicalPlan1. prop2 := &property.PhysicalProperty{ Items: items, Enforced: false, } mockPlan1 := mockLogicalPlan4Test{ hasHintForPlan2: true, canGeneratePlan2: false, }.Init(ctx) mockPlan1.SetChildren(mockDS) task, err = mockPlan1.findBestTask(prop2) c.Assert(err, IsNil) c.Assert(task.invalid(), IsFalse) c.Assert(ctx.GetSessionVars().StmtCtx.WarningCount(), Equals, uint16(1)) // Because physicalPlan1 can match the property, so we should get it. mockPhysicalPlan, ok = task.plan().(*mockPhysicalPlan4Test) c.Assert(ok, IsTrue) c.Assert(mockPhysicalPlan.planType, Equals, 1) // case 4, Similar to case 3, but the property is enforced now. Ths result should be // the same with case 3. ctx.GetSessionVars().StmtCtx.SetWarnings(nil) prop3 := &property.PhysicalProperty{ Items: items, Enforced: true, } task, err = mockPlan1.findBestTask(prop3) c.Assert(err, IsNil) c.Assert(task.invalid(), IsFalse) c.Assert(ctx.GetSessionVars().StmtCtx.WarningCount(), Equals, uint16(1)) // Because physicalPlan1 can match the property, so we don't need to enforce a sort. mockPhysicalPlan, ok = task.plan().(*mockPhysicalPlan4Test) c.Assert(ok, IsTrue) c.Assert(mockPhysicalPlan.planType, Equals, 1) }