From d5e52b452c62b9cfc966b1f5d0f937bdf15ad88b Mon Sep 17 00:00:00 2001 From: HuaiyuXu <391585975@qq.com> Date: Mon, 28 Oct 2019 11:21:39 +0800 Subject: [PATCH] planner: unfold the wildcard when creating view (#11818) (#12936) --- ddl/ddl_api.go | 52 ++++++++++++---------------- executor/executor_test.go | 4 +-- executor/show_test.go | 16 +++++++-- planner/core/logical_plan_builder.go | 44 ++++++++++++++++++----- planner/core/planbuilder.go | 30 ++++++++++++---- 5 files changed, 97 insertions(+), 49 deletions(-) diff --git a/ddl/ddl_api.go b/ddl/ddl_api.go index 77c4352418602..c23ec96c0e6af 100644 --- a/ddl/ddl_api.go +++ b/ddl/ddl_api.go @@ -1487,11 +1487,22 @@ func (d *ddl) CreateView(ctx sessionctx.Context, s *ast.CreateViewStmt) (err err if err = checkTooLongTable(ident.Name); err != nil { return err } - viewInfo, cols := buildViewInfoWithTableColumns(ctx, s) + viewInfo, err := buildViewInfo(ctx, s) + if err != nil { + return err + } - colObjects := make([]interface{}, 0, len(viewInfo.Cols)) - for _, col := range viewInfo.Cols { - colObjects = append(colObjects, col) + cols := make([]*table.Column, len(s.Cols)) + colObjects := make([]interface{}, 0, len(s.Cols)) + + for i, v := range s.Cols { + cols[i] = table.ToColumn(&model.ColumnInfo{ + Name: v, + ID: int64(i), + Offset: i, + State: model.StatePublic, + }) + colObjects = append(colObjects, v) } if err = checkTooLongColumn(colObjects); err != nil { @@ -1529,33 +1540,16 @@ func (d *ddl) CreateView(ctx sessionctx.Context, s *ast.CreateViewStmt) (err err return d.callHookOnChanged(err) } -func buildViewInfoWithTableColumns(ctx sessionctx.Context, s *ast.CreateViewStmt) (*model.ViewInfo, []*table.Column) { - viewInfo := &model.ViewInfo{Definer: s.Definer, Algorithm: s.Algorithm, - Security: s.Security, SelectStmt: s.Select.Text(), CheckOption: s.CheckOption, Cols: s.SchemaCols} - var tableColumns = make([]*table.Column, len(s.SchemaCols)) - if s.Cols == nil { - for i, v := range s.SchemaCols { - tableColumns[i] = table.ToColumn(&model.ColumnInfo{ - Name: v, - ID: int64(i), - Offset: i, - State: model.StatePublic, - Version: model.CurrLatestColumnInfoVersion, - }) - } - } else { - for i, v := range s.Cols { - tableColumns[i] = table.ToColumn(&model.ColumnInfo{ - Name: v, - ID: int64(i), - Offset: i, - State: model.StatePublic, - Version: model.CurrLatestColumnInfoVersion, - }) - } +func buildViewInfo(ctx sessionctx.Context, s *ast.CreateViewStmt) (*model.ViewInfo, error) { + // Always Use `format.RestoreNameBackQuotes` to restore `SELECT` statement despite the `ANSI_QUOTES` SQL Mode is enabled or not. + restoreFlag := format.RestoreStringSingleQuotes | format.RestoreKeyWordUppercase | format.RestoreNameBackQuotes + var sb strings.Builder + if err := s.Select.Restore(format.NewRestoreCtx(restoreFlag, &sb)); err != nil { + return nil, err } - return viewInfo, tableColumns + return &model.ViewInfo{Definer: s.Definer, Algorithm: s.Algorithm, + Security: s.Security, SelectStmt: sb.String(), CheckOption: s.CheckOption, Cols: nil}, nil } func checkPartitionByHash(ctx sessionctx.Context, pi *model.PartitionInfo, s *ast.CreateTableStmt, cols []*table.Column, tbInfo *model.TableInfo) error { diff --git a/executor/executor_test.go b/executor/executor_test.go index 286488f173888..d8648d9ae238a 100644 --- a/executor/executor_test.go +++ b/executor/executor_test.go @@ -3604,9 +3604,9 @@ func (s *testSuite) TestSelectView(c *C) { tk.MustExec("drop table view_t;") tk.MustExec("create table view_t(c int,d int)") err := tk.ExecToErr("select * from view1") - c.Assert(err.Error(), Equals, plannercore.ErrViewInvalid.GenWithStackByArgs("test", "view1").Error()) + c.Assert(err.Error(), Equals, "[planner:1054]Unknown column 'test.view_t.a' in 'field list'") err = tk.ExecToErr("select * from view2") - c.Assert(err.Error(), Equals, plannercore.ErrViewInvalid.GenWithStackByArgs("test", "view2").Error()) + c.Assert(err.Error(), Equals, "[planner:1054]Unknown column 'test.view_t.a' in 'field list'") err = tk.ExecToErr("select * from view3") c.Assert(err.Error(), Equals, "[planner:1054]Unknown column 'a' in 'field list'") tk.MustExec("drop table view_t;") diff --git a/executor/show_test.go b/executor/show_test.go index 13451dca6e839..45c3a2d5f12c9 100644 --- a/executor/show_test.go +++ b/executor/show_test.go @@ -446,16 +446,26 @@ func (s *testSuite2) TestShowCreateTable(c *C) { tk.MustExec("create table t1(a int,b int)") tk.MustExec("drop view if exists v1") tk.MustExec("create or replace definer=`root`@`127.0.0.1` view v1 as select * from t1") - tk.MustQuery("show create table v1").Check(testutil.RowsWithSep("|", "v1|CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`127.0.0.1` SQL SECURITY DEFINER VIEW `v1` (`a`, `b`) AS select * from t1 ")) - tk.MustQuery("show create view v1").Check(testutil.RowsWithSep("|", "v1|CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`127.0.0.1` SQL SECURITY DEFINER VIEW `v1` (`a`, `b`) AS select * from t1 ")) + tk.MustQuery("show create table v1").Check(testutil.RowsWithSep("|", "v1|CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`127.0.0.1` SQL SECURITY DEFINER VIEW `v1` (`a`, `b`) AS SELECT `test`.`t1`.`a`,`test`.`t1`.`b` FROM `test`.`t1` ")) + tk.MustQuery("show create view v1").Check(testutil.RowsWithSep("|", "v1|CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`127.0.0.1` SQL SECURITY DEFINER VIEW `v1` (`a`, `b`) AS SELECT `test`.`t1`.`a`,`test`.`t1`.`b` FROM `test`.`t1` ")) tk.MustExec("drop view v1") tk.MustExec("drop table t1") tk.MustExec("drop view if exists v") tk.MustExec("create or replace definer=`root`@`127.0.0.1` view v as select JSON_MERGE('{}', '{}') as col;") - tk.MustQuery("show create view v").Check(testutil.RowsWithSep("|", "v|CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`127.0.0.1` SQL SECURITY DEFINER VIEW `v` (`col`) AS select JSON_MERGE('{}', '{}') as col; ")) + tk.MustQuery("show create view v").Check(testutil.RowsWithSep("|", "v|CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`127.0.0.1` SQL SECURITY DEFINER VIEW `v` (`col`) AS SELECT JSON_MERGE('{}', '{}') AS `col` ")) tk.MustExec("drop view if exists v") + tk.MustExec("drop table if exists t1") + tk.MustExec("create table t1(a int,b int)") + tk.MustExec("create or replace definer=`root`@`127.0.0.1` view v1 as select avg(a),t1.* from t1 group by a") + tk.MustQuery("show create view v1").Check(testutil.RowsWithSep("|", "v1|CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`127.0.0.1` SQL SECURITY DEFINER VIEW `v1` (`avg(a)`, `a`, `b`) AS SELECT AVG(`a`),`test`.`t1`.`a`,`test`.`t1`.`b` FROM `test`.`t1` GROUP BY `a` ")) + tk.MustExec("drop view v1") + tk.MustExec("create or replace definer=`root`@`127.0.0.1` view v1 as select a+b, t1.* , a as c from t1") + tk.MustQuery("show create view v1").Check(testutil.RowsWithSep("|", "v1|CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`127.0.0.1` SQL SECURITY DEFINER VIEW `v1` (`a+b`, `a`, `b`, `c`) AS SELECT `a`+`b`,`test`.`t1`.`a`,`test`.`t1`.`b`,`a` AS `c` FROM `test`.`t1` ")) + tk.MustExec("drop table t1") + tk.MustExec("drop view v1") + // For issue #9211 tk.MustExec("create table t(c int, b int as (c + 1))ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;") tk.MustQuery("show create table `t`").Check(testutil.RowsWithSep("|", diff --git a/planner/core/logical_plan_builder.go b/planner/core/logical_plan_builder.go index b196e36146b61..fb0d637f28cdb 100644 --- a/planner/core/logical_plan_builder.go +++ b/planner/core/logical_plan_builder.go @@ -2076,6 +2076,9 @@ func (b *PlanBuilder) buildSelect(ctx context.Context, sel *ast.SelectStmt) (p L if err != nil { return nil, err } + if b.capFlag&canExpandAST != 0 { + originalFields = sel.Fields.Fields + } if sel.GroupBy != nil { p, gbyCols, err = b.resolveGbyExprs(ctx, p, sel.GroupBy, sel.Fields.Fields) @@ -2429,25 +2432,48 @@ func (b *PlanBuilder) BuildDataSourceFromView(ctx context.Context, dbName model. b.visitInfo = appendVisitInfo(b.visitInfo, mysql.ShowViewPriv, dbName.L, tableInfo.Name.L, "", ErrViewNoExplain) } - 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 { - col := selectLogicalPlan.Schema().FindColumnByName(tableInfo.View.Cols[i].L) - if col == nil { - return nil, ErrViewInvalid.GenWithStackByArgs(dbName.O, tableInfo.Name.O) + if len(tableInfo.Columns) != selectLogicalPlan.Schema().Len() { + return nil, ErrViewInvalid.GenWithStackByArgs(dbName.O, tableInfo.Name.O) + } + + return b.buildProjUponView(ctx, dbName, tableInfo, selectLogicalPlan) +} + +func (b *PlanBuilder) buildProjUponView(ctx context.Context, dbName model.CIStr, tableInfo *model.TableInfo, selectLogicalPlan Plan) (LogicalPlan, error) { + columnInfo := tableInfo.Cols() + cols := selectLogicalPlan.Schema().Columns + // In the old version of VIEW implementation, tableInfo.View.Cols is used to + // store the origin columns' names of the underlying SelectStmt used when + // creating the view. + if tableInfo.View.Cols != nil { + cols = cols[:0] + for _, info := range columnInfo { + col := selectLogicalPlan.Schema().FindColumnByName(info.Name.L) + if col == nil { + return nil, ErrViewInvalid.GenWithStackByArgs(dbName.O, tableInfo.Name.O) + } + cols = append(cols, col) + } + } + + projSchema := expression.NewSchema(make([]*expression.Column, 0, len(tableInfo.Columns))...) + projExprs := make([]expression.Expression, 0, len(tableInfo.Columns)) + for i, col := range cols { + origColName := col.ColName + if tableInfo.View.Cols != nil { + origColName = tableInfo.View.Cols[i] } projSchema.Append(&expression.Column{ UniqueID: b.ctx.GetSessionVars().AllocPlanColumnID(), TblName: col.TblName, OrigTblName: col.OrigTblName, - ColName: tableInfo.Cols()[i].Name, - OrigColName: tableInfo.View.Cols[i], + ColName: columnInfo[i].Name, + OrigColName: origColName, DBName: col.DBName, RetType: col.GetType(), }) projExprs = append(projExprs, col) } - projUponView := LogicalProjection{Exprs: projExprs}.Init(b.ctx) projUponView.SetChildren(selectLogicalPlan.(LogicalPlan)) projUponView.SetSchema(projSchema) diff --git a/planner/core/planbuilder.go b/planner/core/planbuilder.go index d57fa095f76e6..917a529342f10 100644 --- a/planner/core/planbuilder.go +++ b/planner/core/planbuilder.go @@ -174,6 +174,15 @@ var clauseMsg = map[clauseCode]string{ globalOrderByClause: "global ORDER clause", } +type capFlagType = uint64 + +const ( + _ capFlagType = iota + // canExpandAST indicates whether the origin AST can be expanded during plan + // building. ONLY used for `CreateViewStmt` now. + canExpandAST +) + // PlanBuilder builds Plan from an ast.Node. // It just builds the ast node straightforwardly. type PlanBuilder struct { @@ -186,7 +195,10 @@ type PlanBuilder struct { // visitInfo is used for privilege check. visitInfo []visitInfo tableHintInfo []tableHintInfo - optFlag uint64 + // optFlag indicates the flags of the optimizer rules. + optFlag uint64 + // capFlag indicates the capability flags. + capFlag capFlagType curClause clauseCode @@ -2080,17 +2092,23 @@ func (b *PlanBuilder) buildDDL(ctx context.Context, node ast.DDLNode) (Plan, err v.ReferTable.Name.L, "", authErr) } case *ast.CreateViewStmt: + b.capFlag |= canExpandAST + defer func() { + b.capFlag &= ^canExpandAST + }() plan, err := b.Build(ctx, v.Select) if err != nil { return nil, err } schema := plan.Schema() - if v.Cols != nil && len(v.Cols) != schema.Len() { - return nil, ddl.ErrViewWrongList + if v.Cols == nil { + v.Cols = make([]model.CIStr, len(schema.Columns)) + for i, col := range schema.Columns { + v.Cols[i] = col.ColName + } } - v.SchemaCols = make([]model.CIStr, schema.Len()) - for i, col := range schema.Columns { - v.SchemaCols[i] = col.ColName + if len(v.Cols) != schema.Len() { + return nil, ddl.ErrViewWrongList } if _, ok := plan.(LogicalPlan); ok { if b.ctx.GetSessionVars().User != nil {