From b60541c63c7a93b6dec89fad0a220b0cd7d0ba27 Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 18 Dec 2018 12:39:20 +0800 Subject: [PATCH 1/5] implement select from view --- executor/executor_test.go | 35 +++++++++++++++++++++++ planner/core/errors.go | 1 + planner/core/logical_plan_builder.go | 42 ++++++++++++++++++++++++++++ planner/core/logical_plan_test.go | 33 +++++++++++++++++++++- planner/core/mock.go | 31 ++++++++++++++++++++ 5 files changed, 141 insertions(+), 1 deletion(-) diff --git a/executor/executor_test.go b/executor/executor_test.go index e97d793c982a0..c5b1a2bebd257 100644 --- a/executor/executor_test.go +++ b/executor/executor_test.go @@ -3428,3 +3428,38 @@ func (s *testSuite) TestSelectHashPartitionTable(c *C) { " └─TableScan_13 10000.00 cop table:th, partition:, range:[-inf,+inf], keep order:false, stats:pseudo", )) } + +func (s *testSuite) TestSelectView(c *C) { + tk := testkit.NewTestKit(c, s.store) + tk.MustExec("use test") + tk.MustExec("create table view_t (a int,b int)") + tk.MustExec("insert into view_t values(1,2)") + tk.MustExec("create view view1 as select * from view_t") + tk.MustExec("create view view2(c,d) as select * from view_t") + tk.MustExec("create view view3(c,d) as select a,b from view_t") + tk.MustQuery("select * from view1;").Check(testkit.Rows("1 2")) + tk.MustQuery("select * from view2;").Check(testkit.Rows("1 2")) + tk.MustQuery("select * from view3;").Check(testkit.Rows("1 2")) + tk.MustExec("drop table view_t;") + tk.MustExec("create table view_t(c int,d int)") + _, err := tk.Exec("select * from view1") + c.Assert(err.Error(), Equals, plannercore.ErrViewInvalid.GenWithStackByArgs("test", "view1").Error()) + _, err = tk.Exec("select * from view2") + c.Assert(err.Error(), Equals, plannercore.ErrViewInvalid.GenWithStackByArgs("test", "view2").Error()) + _, err = tk.Exec("select * from view3") + c.Assert(err.Error(), Equals, "[planner:1054]Unknown column 'a' in 'field list'") + tk.MustExec("drop table view_t;") + tk.MustExec("create table view_t(a int,b int,c int)") + tk.MustExec("insert into view_t values(1,2,3)") + tk.MustQuery("select * from view1;").Check(testkit.Rows("1 2")) + tk.MustQuery("select * from view2;").Check(testkit.Rows("1 2")) + tk.MustQuery("select * from view3;").Check(testkit.Rows("1 2")) + tk.MustExec("alter table view_t drop column a") + tk.MustExec("alter table view_t add column a int after b") + tk.MustExec("update view_t set a=1;") + tk.MustQuery("select * from view1;").Check(testkit.Rows("1 2")) + tk.MustQuery("select * from view2;").Check(testkit.Rows("1 2")) + tk.MustQuery("select * from view3;").Check(testkit.Rows("1 2")) + tk.MustExec("drop table view_t;") + tk.MustExec("drop view view1,view2,view3;") +} diff --git a/planner/core/errors.go b/planner/core/errors.go index 48645a062988a..6d029c490f533 100644 --- a/planner/core/errors.go +++ b/planner/core/errors.go @@ -87,6 +87,7 @@ var ( ErrMixOfGroupFuncAndFields = terror.ClassOptimizer.New(codeMixOfGroupFuncAndFields, "In aggregated query without GROUP BY, expression #%d of SELECT list contains nonaggregated column '%s'; this is incompatible with sql_mode=only_full_group_by") ErrNonUniqTable = terror.ClassOptimizer.New(codeNonUniqTable, mysql.MySQLErrName[mysql.ErrNonuniqTable]) ErrWrongValueCountOnRow = terror.ClassOptimizer.New(mysql.ErrWrongValueCountOnRow, mysql.MySQLErrName[mysql.ErrWrongValueCountOnRow]) + ErrViewInvalid = terror.ClassOptimizer.New(mysql.ErrViewInvalid, mysql.MySQLErrName[mysql.ErrViewInvalid]) ) func init() { diff --git a/planner/core/logical_plan_builder.go b/planner/core/logical_plan_builder.go index 1e73bd7ea98ba..1cc7583f8d4f5 100644 --- a/planner/core/logical_plan_builder.go +++ b/planner/core/logical_plan_builder.go @@ -1902,6 +1902,10 @@ func (b *PlanBuilder) buildDataSource(tn *ast.TableName) (LogicalPlan, error) { tableInfo := tbl.Meta() b.visitInfo = appendVisitInfo(b.visitInfo, mysql.SelectPriv, dbName.L, tableInfo.Name.L, "") + if tableInfo.IsView() { + return b.buildDataSourceFromView(dbName, tableInfo) + } + if tableInfo.GetPartitionInfo() != nil { b.optFlag = b.optFlag | flagPartitionProcessor } @@ -1994,6 +1998,44 @@ func (b *PlanBuilder) buildDataSource(tn *ast.TableName) (LogicalPlan, error) { return result, nil } +func (b *PlanBuilder) buildDataSourceFromView(dbName model.CIStr, tableInfo *model.TableInfo) (LogicalPlan, error) { + var ( + selectNode ast.StmtNode + selectLogicalPlan Plan + err error + ) + charset, collation := b.ctx.GetSessionVars().GetCharsetInfo() + selectNode, err = parser.New().ParseOneStmt(tableInfo.View.SelectStmt, charset, collation) + if err != nil { + return nil, err + } + selectLogicalPlan, err = b.Build(selectNode) + if err != nil { + return nil, err + } + projSchema := expression.NewSchema(make([]*expression.Column, 0, len(tableInfo.View.Cols))...) + for i := range tableInfo.View.Cols { + col := selectLogicalPlan.Schema().FindColumnByName(tableInfo.View.Cols[i].L) + if col == nil { + return nil, ErrViewInvalid.GenWithStackByArgs(dbName.O, tableInfo.Name.O) + } + projSchema.Append(&expression.Column{ + UniqueID: col.UniqueID, + TblName: col.TblName, + OrigTblName: col.OrigTblName, + ColName: tableInfo.Cols()[i].Name, + OrigColName: tableInfo.View.Cols[i], + DBName: col.DBName, + RetType: col.GetType(), + }) + } + + projUponView := LogicalProjection{Exprs: expression.Column2Exprs(projSchema.Columns)}.Init(b.ctx) + projUponView.SetChildren(selectLogicalPlan.(LogicalPlan)) + projUponView.SetSchema(projSchema) + return projUponView, nil +} + // projectVirtualColumns is only for DataSource. If some table has virtual generated columns, // we add a projection on the original DataSource, and calculate those columns in the projection // so that plans above it can reference generated columns by their name. diff --git a/planner/core/logical_plan_test.go b/planner/core/logical_plan_test.go index f9a3497fbae12..24c7cfc0e1b85 100644 --- a/planner/core/logical_plan_test.go +++ b/planner/core/logical_plan_test.go @@ -45,7 +45,7 @@ type testPlanSuite struct { } func (s *testPlanSuite) SetUpSuite(c *C) { - s.is = infoschema.MockInfoSchema([]*model.TableInfo{MockTable()}) + s.is = infoschema.MockInfoSchema([]*model.TableInfo{MockTable(), MockView()}) s.ctx = MockContext() s.Parser = parser.New() } @@ -1873,3 +1873,34 @@ func (s *testPlanSuite) TestOuterJoinEliminator(c *C) { c.Assert(ToString(p), Equals, tt.best, comment) } } + +func (s *testPlanSuite) TestSelectView(c *C) { + defer func() { + testleak.AfterTest(c)() + }() + tests := []struct { + sql string + best string + }{ + { + sql: "select * from v", + best: "DataScan(t)->Projection", + }, + } + for i, tt := range tests { + comment := Commentf("case:%v sql:%s", i, tt.sql) + stmt, err := s.ParseOneStmt(tt.sql, "", "") + c.Assert(err, IsNil, comment) + Preprocess(s.ctx, stmt, s.is, false) + builder := &PlanBuilder{ + ctx: MockContext(), + is: s.is, + colMapper: make(map[*ast.ColumnNameExpr]int), + } + p, err := builder.Build(stmt) + c.Assert(err, IsNil) + p, err = logicalOptimize(builder.optFlag, p.(LogicalPlan)) + c.Assert(err, IsNil) + c.Assert(ToString(p), Equals, tt.best, comment) + } +} diff --git a/planner/core/mock.go b/planner/core/mock.go index 316a4c389bb92..4b4a6a12e54da 100644 --- a/planner/core/mock.go +++ b/planner/core/mock.go @@ -14,6 +14,7 @@ package core import ( + "github.com/pingcap/parser/auth" "github.com/pingcap/parser/model" "github.com/pingcap/parser/mysql" "github.com/pingcap/tidb/domain" @@ -249,6 +250,36 @@ func MockTable() *model.TableInfo { return table } +// MockView is only used for plan related tests. +func MockView() *model.TableInfo { + selectStmt := "select b,c,d from t" + col0 := &model.ColumnInfo{ + State: model.StatePublic, + Offset: 0, + Name: model.NewCIStr("b"), + ID: 1, + } + col1 := &model.ColumnInfo{ + State: model.StatePublic, + Offset: 1, + Name: model.NewCIStr("c"), + ID: 2, + } + col2 := &model.ColumnInfo{ + State: model.StatePublic, + Offset: 2, + Name: model.NewCIStr("d"), + ID: 3, + } + view := &model.ViewInfo{SelectStmt: selectStmt, Security: model.SecurityDefiner, Definer: &auth.UserIdentity{Username: "root", Hostname: ""}, Cols: []model.CIStr{col0.Name, col1.Name, col2.Name}} + table := &model.TableInfo{ + Name: model.NewCIStr("v"), + Columns: []*model.ColumnInfo{col0, col1, col2}, + View: view, + } + return table +} + // MockContext is only used for plan related tests. func MockContext() sessionctx.Context { ctx := mock.NewContext() From c855ba4979f7038e298f053fedae798800590680 Mon Sep 17 00:00:00 2001 From: AndrewDi Date: Mon, 24 Dec 2018 19:40:29 +0800 Subject: [PATCH 2/5] Fix projection --- planner/core/logical_plan_builder.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/planner/core/logical_plan_builder.go b/planner/core/logical_plan_builder.go index 1cc7583f8d4f5..a28cee7c349c5 100644 --- a/planner/core/logical_plan_builder.go +++ b/planner/core/logical_plan_builder.go @@ -2020,7 +2020,7 @@ func (b *PlanBuilder) buildDataSourceFromView(dbName model.CIStr, tableInfo *mod return nil, ErrViewInvalid.GenWithStackByArgs(dbName.O, tableInfo.Name.O) } projSchema.Append(&expression.Column{ - UniqueID: col.UniqueID, + UniqueID: b.ctx.GetSessionVars().AllocPlanColumnID(), TblName: col.TblName, OrigTblName: col.OrigTblName, ColName: tableInfo.Cols()[i].Name, @@ -2030,7 +2030,7 @@ func (b *PlanBuilder) buildDataSourceFromView(dbName model.CIStr, tableInfo *mod }) } - projUponView := LogicalProjection{Exprs: expression.Column2Exprs(projSchema.Columns)}.Init(b.ctx) + projUponView := LogicalProjection{Exprs: expression.Column2Exprs(selectLogicalPlan.Schema().Columns)}.Init(b.ctx) projUponView.SetChildren(selectLogicalPlan.(LogicalPlan)) projUponView.SetSchema(projSchema) return projUponView, nil From 90ed2eb0fecbcd95ecfe7a449d5596ed843dad52 Mon Sep 17 00:00:00 2001 From: AndrewDi Date: Mon, 24 Dec 2018 20:02:51 +0800 Subject: [PATCH 3/5] fix column order --- planner/core/logical_plan_builder.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/planner/core/logical_plan_builder.go b/planner/core/logical_plan_builder.go index a28cee7c349c5..b85ef8c42794f 100644 --- a/planner/core/logical_plan_builder.go +++ b/planner/core/logical_plan_builder.go @@ -2014,6 +2014,7 @@ func (b *PlanBuilder) buildDataSourceFromView(dbName model.CIStr, tableInfo *mod return nil, err } projSchema := expression.NewSchema(make([]*expression.Column, 0, len(tableInfo.View.Cols))...) + projUponCols := make([]*expression.Column, 0, len(tableInfo.View.Cols)) for i := range tableInfo.View.Cols { col := selectLogicalPlan.Schema().FindColumnByName(tableInfo.View.Cols[i].L) if col == nil { @@ -2028,9 +2029,10 @@ func (b *PlanBuilder) buildDataSourceFromView(dbName model.CIStr, tableInfo *mod DBName: col.DBName, RetType: col.GetType(), }) + projUponCols = append(projUponCols, col) } - projUponView := LogicalProjection{Exprs: expression.Column2Exprs(selectLogicalPlan.Schema().Columns)}.Init(b.ctx) + projUponView := LogicalProjection{Exprs: expression.Column2Exprs(projUponCols)}.Init(b.ctx) projUponView.SetChildren(selectLogicalPlan.(LogicalPlan)) projUponView.SetSchema(projSchema) return projUponView, nil From 46ad968621c221d456d4942a2e4024aa4ed21747 Mon Sep 17 00:00:00 2001 From: AndrewDi Date: Mon, 24 Dec 2018 20:59:40 +0800 Subject: [PATCH 4/5] remove expression convert --- planner/core/logical_plan_builder.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/planner/core/logical_plan_builder.go b/planner/core/logical_plan_builder.go index b85ef8c42794f..2546fe18d4984 100644 --- a/planner/core/logical_plan_builder.go +++ b/planner/core/logical_plan_builder.go @@ -2014,7 +2014,7 @@ func (b *PlanBuilder) buildDataSourceFromView(dbName model.CIStr, tableInfo *mod return nil, err } projSchema := expression.NewSchema(make([]*expression.Column, 0, len(tableInfo.View.Cols))...) - projUponCols := make([]*expression.Column, 0, len(tableInfo.View.Cols)) + projExprs := make([]expression.Expression, 0, len(tableInfo.View.Cols)) for i := range tableInfo.View.Cols { col := selectLogicalPlan.Schema().FindColumnByName(tableInfo.View.Cols[i].L) if col == nil { @@ -2029,10 +2029,10 @@ func (b *PlanBuilder) buildDataSourceFromView(dbName model.CIStr, tableInfo *mod DBName: col.DBName, RetType: col.GetType(), }) - projUponCols = append(projUponCols, col) + projExprs = append(projExprs, col) } - projUponView := LogicalProjection{Exprs: expression.Column2Exprs(projUponCols)}.Init(b.ctx) + projUponView := LogicalProjection{Exprs: projExprs}.Init(b.ctx) projUponView.SetChildren(selectLogicalPlan.(LogicalPlan)) projUponView.SetSchema(projSchema) return projUponView, nil From 941cec912cc80b7e2840b3b3850dabc0ab1c8b2e Mon Sep 17 00:00:00 2001 From: AndrewDi Date: Wed, 26 Dec 2018 14:00:27 +0800 Subject: [PATCH 5/5] remove var define --- planner/core/logical_plan_builder.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/planner/core/logical_plan_builder.go b/planner/core/logical_plan_builder.go index 2546fe18d4984..d0230e29e4620 100644 --- a/planner/core/logical_plan_builder.go +++ b/planner/core/logical_plan_builder.go @@ -1999,20 +1999,16 @@ func (b *PlanBuilder) buildDataSource(tn *ast.TableName) (LogicalPlan, error) { } func (b *PlanBuilder) buildDataSourceFromView(dbName model.CIStr, tableInfo *model.TableInfo) (LogicalPlan, error) { - var ( - selectNode ast.StmtNode - selectLogicalPlan Plan - err error - ) charset, collation := b.ctx.GetSessionVars().GetCharsetInfo() - selectNode, err = parser.New().ParseOneStmt(tableInfo.View.SelectStmt, charset, collation) + selectNode, err := parser.New().ParseOneStmt(tableInfo.View.SelectStmt, charset, collation) if err != nil { return nil, err } - selectLogicalPlan, err = b.Build(selectNode) + selectLogicalPlan, err := b.Build(selectNode) if err != nil { return nil, err } + projSchema := expression.NewSchema(make([]*expression.Column, 0, len(tableInfo.View.Cols))...) projExprs := make([]expression.Expression, 0, len(tableInfo.View.Cols)) for i := range tableInfo.View.Cols {