From a56f5eff16d85548a71eb54e4df44d185f2cc02f Mon Sep 17 00:00:00 2001 From: Kenan Yao Date: Thu, 11 Jul 2019 23:56:51 +0800 Subject: [PATCH 1/2] planner: support subquery in `SHOW` statement (#10942) Conflicts: planner/core/common_plans.go planner/core/find_best_task.go planner/core/initialize.go planner/core/planbuilder.go planner/core/resolve_indices.go --- executor/builder.go | 9 +---- planner/core/common_plans.go | 4 +- planner/core/find_best_task.go | 5 ++- planner/core/initialize.go | 4 +- planner/core/integration_test.go | 61 +++++++++++++++++++++++++++++++ planner/core/logical_plan_test.go | 2 +- planner/core/logical_plans.go | 4 ++ planner/core/physical_plans.go | 4 ++ planner/core/plan.go | 8 ++++ planner/core/planbuilder.go | 48 ++++++++++++++++++------ planner/core/point_get_plan.go | 3 ++ planner/core/resolve_indices.go | 7 ---- planner/core/stringer.go | 6 +-- 13 files changed, 128 insertions(+), 37 deletions(-) create mode 100644 planner/core/integration_test.go diff --git a/executor/builder.go b/executor/builder.go index 65ebac87f4301..f0bfca4a1fc06 100644 --- a/executor/builder.go +++ b/executor/builder.go @@ -532,14 +532,7 @@ func (b *executorBuilder) buildShow(v *plannercore.Show) Executor { b.err = errors.Trace(err) } } - if len(v.Conditions) == 0 { - return e - } - sel := &SelectionExec{ - baseExecutor: newBaseExecutor(b.ctx, v.Schema(), v.ExplainID(), e), - filters: v.Conditions, - } - return sel + return e } func (b *executorBuilder) buildSimple(v *plannercore.Simple) Executor { diff --git a/planner/core/common_plans.go b/planner/core/common_plans.go index 208cb41c41815..0d0157dece971 100644 --- a/planner/core/common_plans.go +++ b/planner/core/common_plans.go @@ -301,7 +301,7 @@ type Deallocate struct { // Show represents a show plan. type Show struct { - baseSchemaProducer + physicalSchemaProducer Tp ast.ShowStmtType // Databases/Tables/Columns/.... DBName string @@ -312,8 +312,6 @@ type Show struct { Full bool User *auth.UserIdentity // Used for show grants. - Conditions []expression.Expression - // Used by show variables GlobalScope bool } diff --git a/planner/core/find_best_task.go b/planner/core/find_best_task.go index 06b36bfe0df69..f1397ee36233c 100644 --- a/planner/core/find_best_task.go +++ b/planner/core/find_best_task.go @@ -72,7 +72,10 @@ func (p *LogicalTableDual) findBestTask(prop *property.PhysicalProperty) (task, if !prop.IsEmpty() { return invalidTask, nil } - dual := PhysicalTableDual{RowCount: p.RowCount}.init(p.ctx, p.stats) + dual := PhysicalTableDual{ + RowCount: p.RowCount, + placeHolder: p.placeHolder, + }.init(p.ctx, p.stats) dual.SetSchema(p.schema) return &rootTask{p: dual}, nil } diff --git a/planner/core/initialize.go b/planner/core/initialize.go index 933d20923e6bb..43416f7be0c82 100644 --- a/planner/core/initialize.go +++ b/planner/core/initialize.go @@ -225,7 +225,9 @@ func (p Insert) init(ctx sessionctx.Context) *Insert { } func (p Show) init(ctx sessionctx.Context) *Show { - p.basePlan = newBasePlan(ctx, TypeShow) + p.basePhysicalPlan = newBasePhysicalPlan(ctx, TypeShow, &p) + // Just use pseudo stats to avoid panic. + p.stats = &property.StatsInfo{RowCount: 1} return &p } diff --git a/planner/core/integration_test.go b/planner/core/integration_test.go new file mode 100644 index 0000000000000..ff7ca01e6dd92 --- /dev/null +++ b/planner/core/integration_test.go @@ -0,0 +1,61 @@ +// Copyright 2019 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_test + +import ( + . "github.com/pingcap/check" + "github.com/pingcap/tidb/util/testkit" +) + +var _ = Suite(&testIntegrationSuite{}) + +type testIntegrationSuite struct { +} + +func (s *testIntegrationSuite) TestShowSubquery(c *C) { + store, dom, err := newStoreWithBootstrap() + c.Assert(err, IsNil) + tk := testkit.NewTestKit(c, store) + defer func() { + dom.Close() + store.Close() + }() + tk.MustExec("use test") + tk.MustExec("drop table if exists t") + tk.MustExec("create table t(a varchar(10), b int, c int)") + tk.MustQuery("show columns from t where true").Check(testkit.Rows( + "a varchar(10) YES ", + "b int(11) YES ", + "c int(11) YES ", + )) + tk.MustQuery("show columns from t where field = 'b'").Check(testkit.Rows( + "b int(11) YES ", + )) + tk.MustQuery("show columns from t where field in (select 'b')").Check(testkit.Rows( + "b int(11) YES ", + )) + tk.MustQuery("show columns from t where field in (select 'b') and true").Check(testkit.Rows( + "b int(11) YES ", + )) + tk.MustQuery("show columns from t where field in (select 'b') and false").Check(testkit.Rows()) + tk.MustExec("insert into t values('c', 0, 0)") + tk.MustQuery("show columns from t where field < all (select a from t)").Check(testkit.Rows( + "a varchar(10) YES ", + "b int(11) YES ", + )) + tk.MustExec("insert into t values('b', 0, 0)") + tk.MustQuery("show columns from t where field < all (select a from t)").Check(testkit.Rows( + "a varchar(10) YES ", + )) +} diff --git a/planner/core/logical_plan_test.go b/planner/core/logical_plan_test.go index 391d7e7e1ce1d..e2bc5594c7f68 100644 --- a/planner/core/logical_plan_test.go +++ b/planner/core/logical_plan_test.go @@ -925,7 +925,7 @@ func (s *testPlanSuite) TestPlanBuilder(c *C) { }, { sql: "show columns from t where `Key` = 'pri' like 't*'", - plan: "Show([eq(cast(key), 0)])", + plan: "Show->Sel([eq(cast(key), 0)])", }, { sql: "do sleep(5)", diff --git a/planner/core/logical_plans.go b/planner/core/logical_plans.go index 9e85ef9378c34..3533ab227d8bd 100644 --- a/planner/core/logical_plans.go +++ b/planner/core/logical_plans.go @@ -303,6 +303,10 @@ type LogicalTableDual struct { logicalSchemaProducer RowCount int + // placeHolder indicates if this dual plan is a place holder in query optimization + // for data sources like `Show`, if true, the dual plan would be substituted by + // `Show` in the final plan. + placeHolder bool } // LogicalUnionScan is only used in non read-only txn. diff --git a/planner/core/physical_plans.go b/planner/core/physical_plans.go index 4f92e7fb84c9e..ca32af5651f20 100644 --- a/planner/core/physical_plans.go +++ b/planner/core/physical_plans.go @@ -373,6 +373,10 @@ type PhysicalTableDual struct { physicalSchemaProducer RowCount int + // placeHolder indicates if this dual plan is a place holder in query optimization + // for data sources like `Show`, if true, the dual plan would be substituted by + // `Show` in the final plan. + placeHolder bool } // CollectPlanStatsVersion uses to collect the statistics version of the plan. diff --git a/planner/core/plan.go b/planner/core/plan.go index bdd607476b7c3..8bed1ff11d1e9 100644 --- a/planner/core/plan.go +++ b/planner/core/plan.go @@ -136,6 +136,9 @@ type PhysicalPlan interface { // SetChildren sets the children for the plan. SetChildren(...PhysicalPlan) + // SetChild sets the ith child for the plan. + SetChild(i int, child PhysicalPlan) + // ResolveIndices resolves the indices for columns. After doing this, the columns can evaluate the rows by their indices. ResolveIndices() } @@ -289,6 +292,11 @@ func (p *basePhysicalPlan) SetChildren(children ...PhysicalPlan) { p.children = children } +// SetChild implements PhysicalPlan SetChild interface. +func (p *basePhysicalPlan) SetChild(i int, child PhysicalPlan) { + p.children[i] = child +} + func (p *basePlan) context() sessionctx.Context { return p.ctx } diff --git a/planner/core/planbuilder.go b/planner/core/planbuilder.go index de4e53457e876..b98c5bf919e88 100644 --- a/planner/core/planbuilder.go +++ b/planner/core/planbuilder.go @@ -979,32 +979,58 @@ func (b *planBuilder) buildShow(show *ast.ShowStmt) (Plan, error) { for _, col := range p.schema.Columns { col.UniqueID = b.ctx.GetSessionVars().AllocPlanColumnID() } - mockTablePlan := LogicalTableDual{}.init(b.ctx) + mockTablePlan := LogicalTableDual{placeHolder: true}.init(b.ctx) mockTablePlan.SetSchema(p.schema) + var err error + var np LogicalPlan + np = mockTablePlan if show.Pattern != nil { show.Pattern.Expr = &ast.ColumnNameExpr{ Name: &ast.ColumnName{Name: p.Schema().Columns[0].ColName}, } - expr, _, err := b.rewrite(show.Pattern, mockTablePlan, nil, false) + np, err = b.buildSelection(np, show.Pattern, nil) if err != nil { return nil, errors.Trace(err) } - p.Conditions = append(p.Conditions, expr) } if show.Where != nil { - conds := splitWhere(show.Where) - for _, cond := range conds { - expr, _, err := b.rewrite(cond, mockTablePlan, nil, false) - if err != nil { - return nil, errors.Trace(err) - } - p.Conditions = append(p.Conditions, expr) + np, err = b.buildSelection(np, show.Where, nil) + if err != nil { + return nil, err + } + } + if np != mockTablePlan { + fieldsLen := len(mockTablePlan.schema.Columns) + proj := LogicalProjection{Exprs: make([]expression.Expression, 0, fieldsLen)}.Init(b.ctx) + schema := expression.NewSchema(make([]*expression.Column, 0, fieldsLen)...) + for _, col := range mockTablePlan.schema.Columns { + proj.Exprs = append(proj.Exprs, col) + newCol := col.Clone().(*expression.Column) + newCol.UniqueID = b.ctx.GetSessionVars().AllocPlanColumnID() + schema.Append(newCol) + } + proj.SetSchema(schema) + proj.SetChildren(np) + physical, err := DoOptimize(b.optFlag|flagEliminateProjection, proj) + if err != nil { + return nil, err } - p.ResolveIndices() + return substitutePlaceHolderDual(physical, p), nil } return p, nil } +func substitutePlaceHolderDual(src PhysicalPlan, dst PhysicalPlan) PhysicalPlan { + if dual, ok := src.(*PhysicalTableDual); ok && dual.placeHolder { + return dst + } + for i, child := range src.Children() { + newChild := substitutePlaceHolderDual(child, dst) + src.SetChild(i, newChild) + } + return src +} + func (b *planBuilder) buildSimple(node ast.StmtNode) Plan { p := &Simple{Statement: node} diff --git a/planner/core/point_get_plan.go b/planner/core/point_get_plan.go index 03271175b78fe..48116d6655645 100644 --- a/planner/core/point_get_plan.go +++ b/planner/core/point_get_plan.go @@ -112,6 +112,9 @@ func (p *PointGetPlan) Children() []PhysicalPlan { // SetChildren sets the children for the plan. func (p *PointGetPlan) SetChildren(...PhysicalPlan) {} +// SetChild sets a specific child for the plan. +func (p *PointGetPlan) SetChild(i int, child PhysicalPlan) {} + // ResolveIndices resolves the indices for columns. After doing this, the columns can evaluate the rows by their indices. func (p *PointGetPlan) ResolveIndices() {} diff --git a/planner/core/resolve_indices.go b/planner/core/resolve_indices.go index 27562bc21838b..5b6b20c1bc847 100644 --- a/planner/core/resolve_indices.go +++ b/planner/core/resolve_indices.go @@ -234,13 +234,6 @@ func (p *Insert) ResolveIndices() { } } -// ResolveIndices implements Plan interface. -func (p *Show) ResolveIndices() { - for i, expr := range p.Conditions { - p.Conditions[i] = expr.ResolveIndices(p.schema) - } -} - func (p *physicalSchemaProducer) ResolveIndices() { p.basePhysicalPlan.ResolveIndices() if p.schema != nil { diff --git a/planner/core/stringer.go b/planner/core/stringer.go index 6cd078354469a..3b31ca8a44ac7 100644 --- a/planner/core/stringer.go +++ b/planner/core/stringer.go @@ -113,11 +113,7 @@ func toString(in Plan, strs []string, idxs []int) ([]string, []int) { case *ShowDDL: str = "ShowDDL" case *Show: - if len(x.Conditions) == 0 { - str = "Show" - } else { - str = fmt.Sprintf("Show(%s)", x.Conditions) - } + str = "Show" case *LogicalSort, *PhysicalSort: str = "Sort" case *LogicalJoin: From cef42405fdcce99b0ef7e68acbe6f6a42a368ed6 Mon Sep 17 00:00:00 2001 From: Kenan Yao Date: Fri, 26 Jul 2019 14:55:54 +0800 Subject: [PATCH 2/2] fix compile --- planner/core/planbuilder.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/planner/core/planbuilder.go b/planner/core/planbuilder.go index b98c5bf919e88..4cdba01fa8f69 100644 --- a/planner/core/planbuilder.go +++ b/planner/core/planbuilder.go @@ -1001,7 +1001,7 @@ func (b *planBuilder) buildShow(show *ast.ShowStmt) (Plan, error) { } if np != mockTablePlan { fieldsLen := len(mockTablePlan.schema.Columns) - proj := LogicalProjection{Exprs: make([]expression.Expression, 0, fieldsLen)}.Init(b.ctx) + proj := LogicalProjection{Exprs: make([]expression.Expression, 0, fieldsLen)}.init(b.ctx) schema := expression.NewSchema(make([]*expression.Column, 0, fieldsLen)...) for _, col := range mockTablePlan.schema.Columns { proj.Exprs = append(proj.Exprs, col) @@ -1011,7 +1011,7 @@ func (b *planBuilder) buildShow(show *ast.ShowStmt) (Plan, error) { } proj.SetSchema(schema) proj.SetChildren(np) - physical, err := DoOptimize(b.optFlag|flagEliminateProjection, proj) + physical, err := doOptimize(b.optFlag|flagEliminateProjection, proj) if err != nil { return nil, err }