diff --git a/plan/dag_plan_test.go b/plan/dag_plan_test.go index 18f4215e46c55..298527a77a37b 100644 --- a/plan/dag_plan_test.go +++ b/plan/dag_plan_test.go @@ -11,18 +11,36 @@ // See the License for the specific language governing permissions and // limitations under the License. -package plan +package plan_test import ( . "github.com/pingcap/check" - "github.com/pingcap/tidb/ast" + "github.com/pingcap/tidb" + "github.com/pingcap/tidb/parser" + "github.com/pingcap/tidb/plan" "github.com/pingcap/tidb/util/testleak" ) +var _ = Suite(&testPlanSuite{}) + +type testPlanSuite struct { + *parser.Parser +} + +func (s *testPlanSuite) SetUpSuite(c *C) { + s.Parser = parser.New() +} + func (s *testPlanSuite) TestDAGPlanBuilderSimpleCase(c *C) { - UseDAGPlanBuilder = true + store, err := newStoreWithBootstrap() + c.Assert(err, IsNil) + defer store.Close() + se, err := tidb.CreateSession(store) + c.Assert(err, IsNil) + + plan.UseDAGPlanBuilder = true defer func() { - UseDAGPlanBuilder = false + plan.UseDAGPlanBuilder = false testleak.AfterTest(c)() }() tests := []struct { @@ -140,27 +158,27 @@ func (s *testPlanSuite) TestDAGPlanBuilderSimpleCase(c *C) { stmt, err := s.ParseOneStmt(tt.sql, "", "") c.Assert(err, IsNil, comment) - is, err := mockResolve(stmt) + err = se.NewTxn() c.Assert(err, IsNil) - builder := &planBuilder{ - allocator: new(idAllocator), - ctx: mockContext(), - colMapper: make(map[*ast.ColumnNameExpr]int), - is: is, - } - p := builder.build(stmt) - c.Assert(builder.err, IsNil) - p, err = doOptimize(builder.optFlag, p.(LogicalPlan), builder.ctx, builder.allocator) + is, err := plan.MockResolve(stmt) + c.Assert(err, IsNil) + p, err := plan.Optimize(se, stmt, is) c.Assert(err, IsNil) - c.Assert(ToString(p), Equals, tt.best, Commentf("for %s", tt.sql)) + c.Assert(plan.ToString(p), Equals, tt.best, Commentf("for %s", tt.sql)) } } func (s *testPlanSuite) TestDAGPlanBuilderJoin(c *C) { - UseDAGPlanBuilder = true + store, err := newStoreWithBootstrap() + c.Assert(err, IsNil) + defer store.Close() + se, err := tidb.CreateSession(store) + c.Assert(err, IsNil) + + plan.UseDAGPlanBuilder = true defer func() { - UseDAGPlanBuilder = false + plan.UseDAGPlanBuilder = false testleak.AfterTest(c)() }() tests := []struct { @@ -197,27 +215,24 @@ func (s *testPlanSuite) TestDAGPlanBuilderJoin(c *C) { stmt, err := s.ParseOneStmt(tt.sql, "", "") c.Assert(err, IsNil, comment) - is, err := mockResolve(stmt) + is, err := plan.MockResolve(stmt) c.Assert(err, IsNil) - - builder := &planBuilder{ - allocator: new(idAllocator), - ctx: mockContext(), - colMapper: make(map[*ast.ColumnNameExpr]int), - is: is, - } - p := builder.build(stmt) - c.Assert(builder.err, IsNil) - p, err = doOptimize(builder.optFlag, p.(LogicalPlan), builder.ctx, builder.allocator) + p, err := plan.Optimize(se, stmt, is) c.Assert(err, IsNil) - c.Assert(ToString(p), Equals, tt.best, Commentf("for %s", tt.sql)) + c.Assert(plan.ToString(p), Equals, tt.best, Commentf("for %s", tt.sql)) } } func (s *testPlanSuite) TestDAGPlanBuilderBasePhysicalPlan(c *C) { - UseDAGPlanBuilder = true + store, err := newStoreWithBootstrap() + c.Assert(err, IsNil) + defer store.Close() + se, err := tidb.CreateSession(store) + c.Assert(err, IsNil) + + plan.UseDAGPlanBuilder = true defer func() { - UseDAGPlanBuilder = false + plan.UseDAGPlanBuilder = false testleak.AfterTest(c)() }() tests := []struct { @@ -281,27 +296,24 @@ func (s *testPlanSuite) TestDAGPlanBuilderBasePhysicalPlan(c *C) { stmt, err := s.ParseOneStmt(tt.sql, "", "") c.Assert(err, IsNil, comment) - is, err := mockResolve(stmt) + is, err := plan.MockResolve(stmt) c.Assert(err, IsNil) - - builder := &planBuilder{ - allocator: new(idAllocator), - ctx: mockContext(), - colMapper: make(map[*ast.ColumnNameExpr]int), - is: is, - } - p := builder.build(stmt) - c.Assert(builder.err, IsNil) - p, err = doOptimize(builder.optFlag, p.(LogicalPlan), builder.ctx, builder.allocator) + p, err := plan.Optimize(se, stmt, is) c.Assert(err, IsNil) - c.Assert(ToString(p), Equals, tt.best, Commentf("for %s", tt.sql)) + c.Assert(plan.ToString(p), Equals, tt.best, Commentf("for %s", tt.sql)) } } func (s *testPlanSuite) TestDAGPlanBuilderUnion(c *C) { - UseDAGPlanBuilder = true + store, err := newStoreWithBootstrap() + c.Assert(err, IsNil) + defer store.Close() + se, err := tidb.CreateSession(store) + c.Assert(err, IsNil) + + plan.UseDAGPlanBuilder = true defer func() { - UseDAGPlanBuilder = false + plan.UseDAGPlanBuilder = false testleak.AfterTest(c)() }() tests := []struct { @@ -334,27 +346,92 @@ func (s *testPlanSuite) TestDAGPlanBuilderUnion(c *C) { stmt, err := s.ParseOneStmt(tt.sql, "", "") c.Assert(err, IsNil, comment) - is, err := mockResolve(stmt) + is, err := plan.MockResolve(stmt) + c.Assert(err, IsNil) + p, err := plan.Optimize(se, stmt, is) c.Assert(err, IsNil) + c.Assert(plan.ToString(p), Equals, tt.best, Commentf("for %s", tt.sql)) + } +} - builder := &planBuilder{ - allocator: new(idAllocator), - ctx: mockContext(), - colMapper: make(map[*ast.ColumnNameExpr]int), - is: is, - } - p := builder.build(stmt) - c.Assert(builder.err, IsNil) - p, err = doOptimize(builder.optFlag, p.(LogicalPlan), builder.ctx, builder.allocator) +func (s *testPlanSuite) TestDAGPlanBuilderUnionScan(c *C) { + store, err := newStoreWithBootstrap() + c.Assert(err, IsNil) + defer store.Close() + se, err := tidb.CreateSession(store) + c.Assert(err, IsNil) + + plan.UseDAGPlanBuilder = true + defer func() { + plan.UseDAGPlanBuilder = false + testleak.AfterTest(c)() + }() + tests := []struct { + sql string + best string + }{ + // Read table. + { + sql: "select * from t", + best: "TableReader(Table(t))->UnionScan([])", + }, + { + sql: "select * from t where b = 1", + best: "TableReader(Table(t)->Sel([eq(test.t.b, 1)]))->UnionScan([eq(test.t.b, 1)])", + }, + { + sql: "select * from t where a = 1", + best: "TableReader(Table(t))->UnionScan([eq(test.t.a, 1)])", + }, + { + sql: "select * from t where a = 1 order by a", + best: "TableReader(Table(t))->UnionScan([eq(test.t.a, 1)])", + }, + { + sql: "select * from t where a = 1 order by b", + best: "TableReader(Table(t))->UnionScan([eq(test.t.a, 1)])->Sort", + }, + { + sql: "select * from t where a = 1 limit 1", + best: "TableReader(Table(t))->UnionScan([eq(test.t.a, 1)])->Limit", + }, + { + sql: "select * from t where c = 1", + best: "IndexLookUp(Index(t.c_d_e)[[1,1]], Table(t))->UnionScan([eq(test.t.c, 1)])", + }, + { + sql: "select c from t where c = 1", + best: "IndexReader(Index(t.c_d_e)[[1,1]])->UnionScan([eq(test.t.c, 1)])", + }, + } + for _, tt := range tests { + comment := Commentf("for %s", tt.sql) + stmt, err := s.ParseOneStmt(tt.sql, "", "") + c.Assert(err, IsNil, comment) + + is, err := plan.MockResolve(stmt) + c.Assert(err, IsNil) + + err = se.NewTxn() c.Assert(err, IsNil) - c.Assert(ToString(p), Equals, tt.best, Commentf("for %s", tt.sql)) + // Make txn not read only. + se.Txn().Set(nil, nil) + p, err := plan.Optimize(se, stmt, is) + c.Assert(err, IsNil) + c.Assert(plan.ToString(p), Equals, tt.best, Commentf("for %s", tt.sql)) } } func (s *testPlanSuite) TestDAGPlanBuilderAgg(c *C) { - UseDAGPlanBuilder = true + store, err := newStoreWithBootstrap() + c.Assert(err, IsNil) + defer store.Close() + se, err := tidb.CreateSession(store) + c.Assert(err, IsNil) + + plan.UseDAGPlanBuilder = true defer func() { - UseDAGPlanBuilder = false + plan.UseDAGPlanBuilder = false testleak.AfterTest(c)() }() tests := []struct { @@ -392,19 +469,10 @@ func (s *testPlanSuite) TestDAGPlanBuilderAgg(c *C) { stmt, err := s.ParseOneStmt(tt.sql, "", "") c.Assert(err, IsNil, comment) - is, err := mockResolve(stmt) + is, err := plan.MockResolve(stmt) c.Assert(err, IsNil) - - builder := &planBuilder{ - allocator: new(idAllocator), - ctx: mockContext(), - colMapper: make(map[*ast.ColumnNameExpr]int), - is: is, - } - p := builder.build(stmt) - c.Assert(builder.err, IsNil) - p, err = doOptimize(builder.optFlag, p.(LogicalPlan), builder.ctx, builder.allocator) + p, err := plan.Optimize(se, stmt, is) c.Assert(err, IsNil) - c.Assert(ToString(p), Equals, tt.best, Commentf("for %s", tt.sql)) + c.Assert(plan.ToString(p), Equals, tt.best, Commentf("for %s", tt.sql)) } } diff --git a/plan/logical_plan_test.go b/plan/logical_plan_test.go index 4d4b55a236af7..a79e0cea76b43 100644 --- a/plan/logical_plan_test.go +++ b/plan/logical_plan_test.go @@ -61,7 +61,7 @@ func newStringType() types.FieldType { return *ft } -func mockResolve(node ast.Node) (infoschema.InfoSchema, error) { +func MockResolve(node ast.Node) (infoschema.InfoSchema, error) { indices := []*model.IndexInfo{ { Name: model.NewCIStr("c_d_e"), @@ -535,7 +535,7 @@ func (s *testPlanSuite) TestPredicatePushDown(c *C) { stmt, err := s.ParseOneStmt(ca.sql, "", "") c.Assert(err, IsNil, comment) - is, err := mockResolve(stmt) + is, err := MockResolve(stmt) c.Assert(err, IsNil, comment) builder := &planBuilder{ @@ -657,7 +657,7 @@ func (s *testPlanSuite) TestPlanBuilder(c *C) { stmt, err := s.ParseOneStmt(ca.sql, "", "") c.Assert(err, IsNil, comment) - is, err := mockResolve(stmt) + is, err := MockResolve(stmt) c.Assert(err, IsNil) builder := &planBuilder{ @@ -711,7 +711,7 @@ func (s *testPlanSuite) TestJoinReOrder(c *C) { stmt, err := s.ParseOneStmt(tt.sql, "", "") c.Assert(err, IsNil, comment) - is, err := mockResolve(stmt) + is, err := MockResolve(stmt) c.Assert(err, IsNil) builder := &planBuilder{ @@ -817,7 +817,7 @@ func (s *testPlanSuite) TestAggPushDown(c *C) { stmt, err := s.ParseOneStmt(tt.sql, "", "") c.Assert(err, IsNil, comment) - is, err := mockResolve(stmt) + is, err := MockResolve(stmt) c.Assert(err, IsNil) builder := &planBuilder{ @@ -1034,7 +1034,7 @@ func (s *testPlanSuite) TestRefine(c *C) { stmt, err := s.ParseOneStmt(tt.sql, "", "") c.Assert(err, IsNil, comment) - is, err := mockResolve(stmt) + is, err := MockResolve(stmt) c.Assert(err, IsNil) builder := &planBuilder{ @@ -1172,7 +1172,7 @@ func (s *testPlanSuite) TestColumnPruning(c *C) { stmt, err := s.ParseOneStmt(tt.sql, "", "") c.Assert(err, IsNil, comment) - is, err := mockResolve(stmt) + is, err := MockResolve(stmt) c.Assert(err, IsNil, comment) builder := &planBuilder{ @@ -1303,7 +1303,7 @@ func (s *testPlanSuite) TestValidate(c *C) { comment := Commentf("for %s", sql) stmt, err := s.ParseOneStmt(sql, "", "") c.Assert(err, IsNil, comment) - is, err := mockResolve(stmt) + is, err := MockResolve(stmt) c.Assert(err, IsNil) builder := &planBuilder{ allocator: new(idAllocator), @@ -1407,7 +1407,7 @@ func (s *testPlanSuite) TestUniqueKeyInfo(c *C) { stmt, err := s.ParseOneStmt(tt.sql, "", "") c.Assert(err, IsNil, comment) - is, err := mockResolve(stmt) + is, err := MockResolve(stmt) c.Assert(err, IsNil) builder := &planBuilder{ @@ -1456,7 +1456,7 @@ func (s *testPlanSuite) TestAggPrune(c *C) { stmt, err := s.ParseOneStmt(tt.sql, "", "") c.Assert(err, IsNil, comment) - is, err := mockResolve(stmt) + is, err := MockResolve(stmt) c.Assert(err, IsNil) builder := &planBuilder{ @@ -1620,7 +1620,7 @@ func (s *testPlanSuite) TestVisitInfo(c *C) { stmt, err := s.ParseOneStmt(tt.sql, "", "") c.Assert(err, IsNil, comment) - is, err := mockResolve(stmt) + is, err := MockResolve(stmt) c.Assert(err, IsNil) builder := &planBuilder{ @@ -1793,7 +1793,7 @@ func (s *testPlanSuite) TestTopNPushDown(c *C) { stmt, err := s.ParseOneStmt(tt.sql, "", "") c.Assert(err, IsNil, comment) - is, err := mockResolve(stmt) + is, err := MockResolve(stmt) c.Assert(err, IsNil) builder := &planBuilder{ diff --git a/plan/new_physical_plan_builder.go b/plan/new_physical_plan_builder.go index 5e034fa10c8b9..4b027308adfa9 100644 --- a/plan/new_physical_plan_builder.go +++ b/plan/new_physical_plan_builder.go @@ -274,6 +274,18 @@ func (p *baseLogicalPlan) convert2NewPhysicalPlan(prop *requiredProp) (taskProfi return task, p.storeTaskProfile(prop, task) } +func tryToAddUnionScan(cop *copTaskProfile, conds []expression.Expression, ctx context.Context, allocator *idAllocator) taskProfile { + if ctx.Txn() == nil || ctx.Txn().IsReadOnly() { + return cop + } + task := finishCopTask(cop, ctx, allocator) + us := PhysicalUnionScan{ + Conditions: conds, + }.init(allocator, ctx) + us.SetSchema(task.plan().Schema()) + return us.attach2TaskProfile(task) +} + // tryToGetMemTask will check if this table is a mem table. If it is, it will produce a task and store it. func (p *DataSource) tryToGetMemTask(prop *requiredProp) (task taskProfile, err error) { client := p.ctx.GetClient() @@ -433,11 +445,12 @@ func (p *DataSource) convertToIndexScan(prop *requiredProp, idx *model.IndexInfo copTask.cst = rowCount * descScanFactor } is.addPushedDownSelection(copTask) - task = copTask + task = tryToAddUnionScan(copTask, p.pushedDownConds, p.ctx, p.allocator) } else { is.OutOfOrder = true is.addPushedDownSelection(copTask) - task = prop.enforceProperty(copTask, p.ctx, p.allocator) + task = tryToAddUnionScan(copTask, p.pushedDownConds, p.ctx, p.allocator) + task = prop.enforceProperty(task, p.ctx, p.allocator) } return task, nil } @@ -538,8 +551,10 @@ func (p *DataSource) convertToTableScan(prop *requiredProp) (task taskProfile, e } ts.KeepOrder = true ts.addPushedDownSelection(copTask) + task = tryToAddUnionScan(copTask, p.pushedDownConds, p.ctx, p.allocator) } else { ts.addPushedDownSelection(copTask) + task = tryToAddUnionScan(copTask, p.pushedDownConds, p.ctx, p.allocator) task = prop.enforceProperty(task, p.ctx, p.allocator) } return task, nil diff --git a/plan/physical_plan_test.go b/plan/physical_plan_test.go index bbaf103776635..e35cc24a5da2f 100644 --- a/plan/physical_plan_test.go +++ b/plan/physical_plan_test.go @@ -73,7 +73,7 @@ func (s *testPlanSuite) TestPushDownAggregation(c *C) { c.Assert(err, IsNil, comment) ast.SetFlag(stmt) - is, err := mockResolve(stmt) + is, err := MockResolve(stmt) c.Assert(err, IsNil) builder := &planBuilder{ allocator: new(idAllocator), @@ -181,7 +181,7 @@ func (s *testPlanSuite) TestPushDownOrderByAndLimit(c *C) { c.Assert(err, IsNil, comment) ast.SetFlag(stmt) - is, err := mockResolve(stmt) + is, err := MockResolve(stmt) c.Assert(err, IsNil) builder := &planBuilder{ allocator: new(idAllocator), @@ -307,7 +307,7 @@ func (s *testPlanSuite) TestPushDownExpression(c *C) { c.Assert(err, IsNil, comment) ast.SetFlag(stmt) - is, err := mockResolve(stmt) + is, err := MockResolve(stmt) c.Assert(err, IsNil) builder := &planBuilder{ allocator: new(idAllocator), @@ -529,7 +529,7 @@ func (s *testPlanSuite) TestCBO(c *C) { stmt, err := s.ParseOneStmt(tt.sql, "", "") c.Assert(err, IsNil, comment) - is, err := mockResolve(stmt) + is, err := MockResolve(stmt) c.Assert(err, IsNil) builder := &planBuilder{ @@ -644,7 +644,7 @@ func (s *testPlanSuite) TestProjectionElimination(c *C) { c.Assert(err, IsNil, comment) ast.SetFlag(stmt) - is, err := mockResolve(stmt) + is, err := MockResolve(stmt) c.Assert(err, IsNil) builder := &planBuilder{ @@ -738,7 +738,7 @@ func (s *testPlanSuite) TestFilterConditionPushDown(c *C) { c.Assert(err, IsNil, comment) ast.SetFlag(stmt) - is, err := mockResolve(stmt) + is, err := MockResolve(stmt) c.Assert(err, IsNil) builder := &planBuilder{ allocator: new(idAllocator), @@ -793,7 +793,7 @@ func (s *testPlanSuite) TestAddCache(c *C) { c.Assert(err, IsNil, comment) ast.SetFlag(stmt) - is, err := mockResolve(stmt) + is, err := MockResolve(stmt) c.Assert(err, IsNil) builder := &planBuilder{ @@ -975,7 +975,7 @@ func (s *testPlanSuite) TestRangeBuilder(c *C) { sql := "select * from t where " + tt.exprStr stmt, err := s.ParseOneStmt(sql, "", "") c.Assert(err, IsNil, Commentf("error %v, for expr %s", err, tt.exprStr)) - is, err := mockResolve(stmt) + is, err := MockResolve(stmt) c.Assert(err, IsNil) builder := &planBuilder{ @@ -1029,7 +1029,7 @@ func (s *testPlanSuite) TestScanController(c *C) { c.Assert(err, IsNil, comment) ast.SetFlag(stmt) - is, err := mockResolve(stmt) + is, err := MockResolve(stmt) c.Assert(err, IsNil) builder := &planBuilder{ @@ -1133,7 +1133,7 @@ func (s *testPlanSuite) TestJoinAlgorithm(c *C) { c.Assert(err, IsNil, comment) ast.SetFlag(stmt) - is, err := mockResolve(stmt) + is, err := MockResolve(stmt) c.Assert(err, IsNil) builder := &planBuilder{ @@ -1190,7 +1190,7 @@ func (s *testPlanSuite) TestAutoJoinChosen(c *C) { c.Assert(err, IsNil, comment) ast.SetFlag(stmt) - is, err := mockResolve(stmt) + is, err := MockResolve(stmt) c.Assert(err, IsNil) ctx := mockContext() diff --git a/plan/stringer.go b/plan/stringer.go index 27962398ff4c0..39d76b51462b8 100644 --- a/plan/stringer.go +++ b/plan/stringer.go @@ -161,6 +161,8 @@ func toString(in Plan, strs []string, idxs []int) ([]string, []int) { str = fmt.Sprintf("IndexReader(%s)", ToString(x.copPlan)) case *PhysicalIndexLookUpReader: str = fmt.Sprintf("IndexLookUp(%s, %s)", ToString(x.indexPlan), ToString(x.tablePlan)) + case *PhysicalUnionScan: + str = fmt.Sprintf("UnionScan(%s)", x.Conditions) default: str = fmt.Sprintf("%T", in) }