diff --git a/ast/ddl.go b/ast/ddl.go index 72f806d773..8ff321e292 100644 --- a/ast/ddl.go +++ b/ast/ddl.go @@ -397,6 +397,7 @@ type CreateTableStmt struct { IfNotExists bool Table *TableName + ReferTable *TableName Cols []*ColumnDef Constraints []*Constraint Options []*TableOption @@ -414,6 +415,13 @@ func (n *CreateTableStmt) Accept(v Visitor) (Node, bool) { return n, false } n.Table = node.(*TableName) + if n.ReferTable != nil { + node, ok = n.ReferTable.Accept(v) + if !ok { + return n, false + } + n.ReferTable = node.(*TableName) + } for i, val := range n.Cols { node, ok = val.Accept(v) if !ok { diff --git a/ddl/ddl.go b/ddl/ddl.go index 61be5d3b17..7551aa16d5 100644 --- a/ddl/ddl.go +++ b/ddl/ddl.go @@ -100,6 +100,7 @@ type DDL interface { DropSchema(ctx context.Context, schema model.CIStr) error CreateTable(ctx context.Context, ident ast.Ident, cols []*ast.ColumnDef, constrs []*ast.Constraint, options []*ast.TableOption) error + CreateTableWithLike(ctx context.Context, ident, referIdent ast.Ident) error DropTable(ctx context.Context, tableIdent ast.Ident) (err error) CreateIndex(ctx context.Context, tableIdent ast.Ident, unique bool, indexName model.CIStr, columnNames []*ast.IndexColName) error diff --git a/ddl/ddl_api.go b/ddl/ddl_api.go index 09dedf7997..e90b2c88be 100644 --- a/ddl/ddl_api.go +++ b/ddl/ddl_api.go @@ -571,6 +571,45 @@ func (d *ddl) buildTableInfo(tableName model.CIStr, cols []*table.Column, constr return } +func (d *ddl) CreateTableWithLike(ctx context.Context, ident, referIdent ast.Ident) error { + is := d.GetInformationSchema() + _, ok := is.SchemaByName(referIdent.Schema) + if !ok { + return infoschema.ErrTableNotExists.GenByArgs(referIdent.Schema, referIdent.Name) + } + referTbl, err := is.TableByName(referIdent.Schema, referIdent.Name) + if err != nil { + return infoschema.ErrTableNotExists.GenByArgs(referIdent.Schema, referIdent.Name) + } + schema, ok := is.SchemaByName(ident.Schema) + if !ok { + return infoschema.ErrDatabaseNotExists.GenByArgs(ident.Schema) + } + if is.TableExists(ident.Schema, ident.Name) { + return infoschema.ErrTableExists.GenByArgs(ident) + } + + tblInfo := *referTbl.Meta() + tblInfo.Name = ident.Name + tblInfo.AutoIncID = 0 + tblInfo.ForeignKeys = nil + tblInfo.ID, err = d.genGlobalID() + if err != nil { + return errors.Trace(err) + } + job := &model.Job{ + SchemaID: schema.ID, + TableID: tblInfo.ID, + Type: model.ActionCreateTable, + BinlogInfo: &model.HistoryInfo{}, + Args: []interface{}{tblInfo}, + } + + err = d.doDDLJob(ctx, job) + err = d.callHookOnChanged(err) + return errors.Trace(err) +} + func (d *ddl) CreateTable(ctx context.Context, ident ast.Ident, colDefs []*ast.ColumnDef, constraints []*ast.Constraint, options []*ast.TableOption) (err error) { is := d.GetInformationSchema() diff --git a/ddl/ddl_db_test.go b/ddl/ddl_db_test.go index 80400cbc10..f648069bd7 100644 --- a/ddl/ddl_db_test.go +++ b/ddl/ddl_db_test.go @@ -894,6 +894,55 @@ func (s *testDBSuite) TestUpdateMultipleTable(c *C) { tk.MustQuery("select * from t1").Check(testkit.Rows("8 1 9", "8 2 9")) } +func (s *testDBSuite) TestCreateTableWithLike(c *C) { + defer testleak.AfterTest(c) + store, err := tidb.NewStore("memory://create_table_like") + c.Assert(err, IsNil) + s.tk = testkit.NewTestKit(c, store) + _, err = tidb.BootstrapSession(store) + c.Assert(err, IsNil) + + // for the same database + s.tk.MustExec("use test") + s.tk.MustExec("create table tt(id int primary key)") + s.tk.MustExec("create table t (c1 int not null auto_increment, c2 int, constraint cc foreign key (c2) references tt(id), primary key(c1)) auto_increment = 10") + s.tk.MustExec("insert into t set c2=1") + s.tk.MustExec("create table t1 like test.t") + s.tk.MustExec("insert into t1 set c2=11") + s.tk.MustQuery("select * from t").Check(testkit.Rows("10 1")) + s.tk.MustQuery("select * from t1").Check(testkit.Rows("1 11")) + ctx := s.tk.Se.(context.Context) + is := sessionctx.GetDomain(ctx).InfoSchema() + tbl, err := is.TableByName(model.NewCIStr("test"), model.NewCIStr("t1")) + c.Assert(err, IsNil) + tblInfo := tbl.Meta() + c.Assert(tblInfo.ForeignKeys, IsNil) + c.Assert(tblInfo.PKIsHandle, Equals, true) + col := tblInfo.Columns[0] + hasNotNull := tmysql.HasNotNullFlag(col.Flag) + c.Assert(hasNotNull, IsTrue) + // for different databases + s.tk.MustExec("create database test1") + s.tk.MustExec("use test1") + s.tk.MustExec("create table t1 like test.t") + s.tk.MustExec("insert into t1 set c2=11") + s.tk.MustQuery("select * from t1").Check(testkit.Rows("1 11")) + is = sessionctx.GetDomain(ctx).InfoSchema() + tbl, err = is.TableByName(model.NewCIStr("test1"), model.NewCIStr("t1")) + c.Assert(err, IsNil) + c.Assert(tbl.Meta().ForeignKeys, IsNil) + + // for failure cases + failSQL := fmt.Sprintf("create table t1 like test_not_exist.t") + s.testErrorCode(c, failSQL, tmysql.ErrNoSuchTable) + failSQL = fmt.Sprintf("create table t1 like test.t_not_exist") + s.testErrorCode(c, failSQL, tmysql.ErrNoSuchTable) + failSQL = fmt.Sprintf("create table test_not_exis.t1 like test.t") + s.testErrorCode(c, failSQL, tmysql.ErrBadDB) + failSQL = fmt.Sprintf("create table t1 like test.t") + s.testErrorCode(c, failSQL, tmysql.ErrTableExists) +} + func (s *testDBSuite) TestTruncateTable(c *C) { defer testleak.AfterTest(c) store, err := tidb.NewStore("memory://truncate_table") diff --git a/executor/ddl.go b/executor/ddl.go index dd2cfce745..b4fe96dd84 100644 --- a/executor/ddl.go +++ b/executor/ddl.go @@ -146,7 +146,13 @@ func (e *DDLExec) executeCreateDatabase(s *ast.CreateDatabaseStmt) error { func (e *DDLExec) executeCreateTable(s *ast.CreateTableStmt) error { ident := ast.Ident{Schema: s.Table.Schema, Name: s.Table.Name} - err := sessionctx.GetDomain(e.ctx).DDL().CreateTable(e.ctx, ident, s.Cols, s.Constraints, s.Options) + var err error + if s.ReferTable == nil { + err = sessionctx.GetDomain(e.ctx).DDL().CreateTable(e.ctx, ident, s.Cols, s.Constraints, s.Options) + } else { + referIdent := ast.Ident{Schema: s.ReferTable.Schema, Name: s.ReferTable.Name} + err = sessionctx.GetDomain(e.ctx).DDL().CreateTableWithLike(e.ctx, ident, referIdent) + } if terror.ErrorEqual(err, infoschema.ErrTableExists) { if s.IfNotExists { return nil diff --git a/parser/parser.y b/parser/parser.y index eaf31266a9..87b0bffef3 100644 --- a/parser/parser.y +++ b/parser/parser.y @@ -1610,6 +1610,14 @@ CreateTableStmt: Options: $8.([]*ast.TableOption), } } +| "CREATE" "TABLE" IfNotExists TableName "LIKE" TableName + { + $$ = &ast.CreateTableStmt{ + Table: $4.(*ast.TableName), + ReferTable: $6.(*ast.TableName), + IfNotExists: $3.(bool), + } + } DefaultKwdOpt: {} diff --git a/parser/parser_test.go b/parser/parser_test.go index 65d7e5211a..1d333cb77f 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -1152,15 +1152,17 @@ func (s *testParserSuite) TestDDL(c *C) { union_name varbinary(52) NOT NULL, union_id int(11) DEFAULT '0', PRIMARY KEY (union_name)) ENGINE=MyISAM DEFAULT CHARSET=binary;`, true}, - // create table with multiple index options + // Create table with multiple index options. {`create table t (c int, index ci (c) USING BTREE COMMENT "123");`, true}, // for default value {"CREATE TABLE sbtest (id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, k integer UNSIGNED DEFAULT '0' NOT NULL, c char(120) DEFAULT '' NOT NULL, pad char(60) DEFAULT '' NOT NULL, PRIMARY KEY (id) )", true}, {"create table test (create_date TIMESTAMP NOT NULL COMMENT '创建日期 create date' DEFAULT now());", true}, {"create table ts (t int, v timestamp(3) default CURRENT_TIMESTAMP(3));", true}, - // Create table with primary key name. {"create table if not exists `t` (`id` int not null auto_increment comment '消息ID', primary key `pk_id` (`id`) );", true}, + // Create table with like. + {"create table a like b", true}, + {"create table if not exists a like b", true}, // for alter table {"ALTER TABLE t ADD COLUMN a SMALLINT UNSIGNED", true}, diff --git a/plan/logical_plan_test.go b/plan/logical_plan_test.go index 00ed18bb93..f0370a887f 100644 --- a/plan/logical_plan_test.go +++ b/plan/logical_plan_test.go @@ -1445,6 +1445,13 @@ func (s *testPlanSuite) TestVisitInfo(c *C) { {mysql.CreatePriv, "test", "t", ""}, }, }, + { + sql: "create table t1 like t", + ans: []visitInfo{ + {mysql.CreatePriv, "test", "t1", ""}, + {mysql.SelectPriv, "test", "t", ""}, + }, + }, { sql: "create database test", ans: []visitInfo{ diff --git a/plan/planbuilder.go b/plan/planbuilder.go index 3aed9ca849..229e6f7ae2 100644 --- a/plan/planbuilder.go +++ b/plan/planbuilder.go @@ -700,6 +700,13 @@ func (b *planBuilder) buildDDL(node ast.DDLNode) Plan { db: v.Table.Schema.L, table: v.Table.Name.L, }) + if v.ReferTable != nil { + b.visitInfo = append(b.visitInfo, visitInfo{ + privilege: mysql.SelectPriv, + db: v.ReferTable.Schema.L, + table: v.ReferTable.Name.L, + }) + } case *ast.DropDatabaseStmt: b.visitInfo = append(b.visitInfo, visitInfo{ privilege: mysql.DropPriv,