diff --git a/ddl/db_test.go b/ddl/db_test.go index 829a8efc6ceb6..0f4065f0f84a4 100644 --- a/ddl/db_test.go +++ b/ddl/db_test.go @@ -3299,14 +3299,108 @@ func (s *testDBSuite1) TestRenameMultiTables(c *C) { tk.MustExec("use test") tk.MustExec("create table t1(id int)") tk.MustExec("create table t2(id int)") - // Currently it will fail only. sql := fmt.Sprintf("rename table t1 to t3, t2 to t4") _, err := tk.Exec(sql) - c.Assert(err, NotNil) - originErr := errors.Cause(err) - c.Assert(originErr.Error(), Equals, "can't run multi schema change") + c.Assert(err, IsNil) + + tk.MustExec("drop table t3, t4") + + tk.MustExec("create table t1 (c1 int, c2 int)") + tk.MustExec("create table t2 (c1 int, c2 int)") + tk.MustExec("insert t1 values (1, 1), (2, 2)") + tk.MustExec("insert t2 values (1, 1), (2, 2)") + ctx := tk.Se.(sessionctx.Context) + is := domain.GetDomain(ctx).InfoSchema() + oldTblInfo1, err := is.TableByName(model.NewCIStr("test"), model.NewCIStr("t1")) + c.Assert(err, IsNil) + oldTblID1 := oldTblInfo1.Meta().ID + oldTblInfo2, err := is.TableByName(model.NewCIStr("test"), model.NewCIStr("t2")) + c.Assert(err, IsNil) + oldTblID2 := oldTblInfo2.Meta().ID + tk.MustExec("create database test1") + tk.MustExec("use test1") + tk.MustExec("rename table test.t1 to test1.t1, test.t2 to test1.t2") + is = domain.GetDomain(ctx).InfoSchema() + newTblInfo1, err := is.TableByName(model.NewCIStr("test1"), model.NewCIStr("t1")) + c.Assert(err, IsNil) + c.Assert(newTblInfo1.Meta().ID, Equals, oldTblID1) + newTblInfo2, err := is.TableByName(model.NewCIStr("test1"), model.NewCIStr("t2")) + c.Assert(err, IsNil) + c.Assert(newTblInfo2.Meta().ID, Equals, oldTblID2) + tk.MustQuery("select * from t1").Check(testkit.Rows("1 1", "2 2")) + tk.MustQuery("select * from t2").Check(testkit.Rows("1 1", "2 2")) + + // Make sure t1,t2 doesn't exist. + isExist := is.TableExists(model.NewCIStr("test"), model.NewCIStr("t1")) + c.Assert(isExist, IsFalse) + isExist = is.TableExists(model.NewCIStr("test"), model.NewCIStr("t2")) + c.Assert(isExist, IsFalse) + + // for the same database + tk.MustExec("use test1") + tk.MustExec("rename table test1.t1 to test1.t3, test1.t2 to test1.t4") + is = domain.GetDomain(ctx).InfoSchema() + newTblInfo1, err = is.TableByName(model.NewCIStr("test1"), model.NewCIStr("t3")) + c.Assert(err, IsNil) + c.Assert(newTblInfo1.Meta().ID, Equals, oldTblID1) + newTblInfo2, err = is.TableByName(model.NewCIStr("test1"), model.NewCIStr("t4")) + c.Assert(err, IsNil) + c.Assert(newTblInfo2.Meta().ID, Equals, oldTblID2) + tk.MustQuery("select * from t3").Check(testkit.Rows("1 1", "2 2")) + isExist = is.TableExists(model.NewCIStr("test1"), model.NewCIStr("t1")) + c.Assert(isExist, IsFalse) + tk.MustQuery("select * from t4").Check(testkit.Rows("1 1", "2 2")) + isExist = is.TableExists(model.NewCIStr("test1"), model.NewCIStr("t2")) + c.Assert(isExist, IsFalse) + tk.MustQuery("show tables").Check(testkit.Rows("t3", "t4")) - tk.MustExec("drop table t1, t2") + // for multi tables same database + tk.MustExec("create table t5 (c1 int, c2 int)") + tk.MustExec("insert t5 values (1, 1), (2, 2)") + is = domain.GetDomain(ctx).InfoSchema() + oldTblInfo3, err := is.TableByName(model.NewCIStr("test1"), model.NewCIStr("t5")) + c.Assert(err, IsNil) + oldTblID3 := oldTblInfo3.Meta().ID + tk.MustExec("rename table test1.t3 to test1.t1, test1.t4 to test1.t2, test1.t5 to test1.t3") + is = domain.GetDomain(ctx).InfoSchema() + newTblInfo1, err = is.TableByName(model.NewCIStr("test1"), model.NewCIStr("t1")) + c.Assert(err, IsNil) + c.Assert(newTblInfo1.Meta().ID, Equals, oldTblID1) + newTblInfo2, err = is.TableByName(model.NewCIStr("test1"), model.NewCIStr("t2")) + c.Assert(err, IsNil) + c.Assert(newTblInfo2.Meta().ID, Equals, oldTblID2) + newTblInfo3, err := is.TableByName(model.NewCIStr("test1"), model.NewCIStr("t3")) + c.Assert(err, IsNil) + c.Assert(newTblInfo3.Meta().ID, Equals, oldTblID3) + tk.MustQuery("show tables").Check(testkit.Rows("t1", "t2", "t3")) + + // for multi tables different databases + tk.MustExec("use test") + tk.MustExec("rename table test1.t1 to test.t2, test1.t2 to test.t3, test1.t3 to test.t4") + is = domain.GetDomain(ctx).InfoSchema() + newTblInfo1, err = is.TableByName(model.NewCIStr("test"), model.NewCIStr("t2")) + c.Assert(err, IsNil) + c.Assert(newTblInfo1.Meta().ID, Equals, oldTblID1) + newTblInfo2, err = is.TableByName(model.NewCIStr("test"), model.NewCIStr("t3")) + c.Assert(err, IsNil) + c.Assert(newTblInfo2.Meta().ID, Equals, oldTblID2) + newTblInfo3, err = is.TableByName(model.NewCIStr("test"), model.NewCIStr("t4")) + c.Assert(err, IsNil) + c.Assert(newTblInfo3.Meta().ID, Equals, oldTblID3) + tk.MustQuery("show tables").Check(testkit.Rows("t2", "t3", "t4")) + + // for failure case + failSQL := "rename table test_not_exist.t to test_not_exist.t, test_not_exist.t to test_not_exist.t" + tk.MustGetErrCode(failSQL, errno.ErrFileNotFound) + failSQL = "rename table test.test_not_exist to test.test_not_exist, test.test_not_exist to test.test_not_exist" + tk.MustGetErrCode(failSQL, errno.ErrFileNotFound) + failSQL = "rename table test.t_not_exist to test_not_exist.t, test.t_not_exist to test_not_exist.t" + tk.MustGetErrCode(failSQL, errno.ErrFileNotFound) + failSQL = "rename table test1.t2 to test_not_exist.t, test1.t2 to test_not_exist.t" + tk.MustGetErrCode(failSQL, errno.ErrFileNotFound) + + tk.MustExec("drop database test1") + tk.MustExec("drop database test") } func (s *testDBSuite2) TestAddNotNullColumn(c *C) { diff --git a/ddl/ddl.go b/ddl/ddl.go index 6d09b6908bee1..03890a12e97cc 100644 --- a/ddl/ddl.go +++ b/ddl/ddl.go @@ -105,6 +105,7 @@ type DDL interface { AlterTable(ctx sessionctx.Context, tableIdent ast.Ident, spec []*ast.AlterTableSpec) error TruncateTable(ctx sessionctx.Context, tableIdent ast.Ident) error RenameTable(ctx sessionctx.Context, oldTableIdent, newTableIdent ast.Ident, isAlterTable bool) error + RenameTables(ctx sessionctx.Context, oldTableIdent, newTableIdent []ast.Ident, isAlterTable bool) error LockTables(ctx sessionctx.Context, stmt *ast.LockTablesStmt) error UnlockTables(ctx sessionctx.Context, lockedTables []model.TableLockTpInfo) error CleanupTableLock(ctx sessionctx.Context, tables []*ast.TableName) error diff --git a/ddl/ddl_api.go b/ddl/ddl_api.go index ea6359cee6a65..60637350c00f0 100644 --- a/ddl/ddl_api.go +++ b/ddl/ddl_api.go @@ -66,6 +66,7 @@ const ( expressionIndexPrefix = "_V$" changingColumnPrefix = "_Col$_" changingIndexPrefix = "_Idx$_" + tableNotExist = -1 ) func (d *ddl) CreateSchema(ctx sessionctx.Context, schema model.CIStr, charsetInfo *ast.CharsetOpt) error { @@ -4480,57 +4481,137 @@ func (d *ddl) TruncateTable(ctx sessionctx.Context, ti ast.Ident) error { func (d *ddl) RenameTable(ctx sessionctx.Context, oldIdent, newIdent ast.Ident, isAlterTable bool) error { is := d.GetInfoSchemaWithInterceptor(ctx) + tables := make(map[string]int64) + schemas, tableID, err := extractTblInfos(is, oldIdent, newIdent, isAlterTable, tables) + if err != nil { + return err + } + + if schemas == nil { + return nil + } + + job := &model.Job{ + SchemaID: schemas[1].ID, + TableID: tableID, + SchemaName: schemas[1].Name.L, + Type: model.ActionRenameTable, + BinlogInfo: &model.HistoryInfo{}, + Args: []interface{}{schemas[0].ID, newIdent.Name}, + } + + err = d.doDDLJob(ctx, job) + err = d.callHookOnChanged(err) + return errors.Trace(err) +} + +func (d *ddl) RenameTables(ctx sessionctx.Context, oldIdents, newIdents []ast.Ident, isAlterTable bool) error { + is := d.GetInfoSchemaWithInterceptor(ctx) + tableNames := make([]*model.CIStr, 0, len(oldIdents)) + oldSchemaIDs := make([]int64, 0, len(oldIdents)) + newSchemaIDs := make([]int64, 0, len(oldIdents)) + tableIDs := make([]int64, 0, len(oldIdents)) + + var schemas []*model.DBInfo + var tableID int64 + var err error + + tables := make(map[string]int64) + for i := 0; i < len(oldIdents); i++ { + schemas, tableID, err = extractTblInfos(is, oldIdents[i], newIdents[i], isAlterTable, tables) + if err != nil { + return err + } + tableIDs = append(tableIDs, tableID) + tableNames = append(tableNames, &newIdents[i].Name) + oldSchemaIDs = append(oldSchemaIDs, schemas[0].ID) + newSchemaIDs = append(newSchemaIDs, schemas[1].ID) + } + + job := &model.Job{ + SchemaID: schemas[1].ID, + TableID: tableIDs[0], + SchemaName: schemas[1].Name.L, + Type: model.ActionRenameTables, + BinlogInfo: &model.HistoryInfo{}, + Args: []interface{}{oldSchemaIDs, newSchemaIDs, tableNames, tableIDs}, + } + + err = d.doDDLJob(ctx, job) + err = d.callHookOnChanged(err) + return errors.Trace(err) +} + +func extractTblInfos(is infoschema.InfoSchema, oldIdent, newIdent ast.Ident, isAlterTable bool, tables map[string]int64) ([]*model.DBInfo, int64, error) { oldSchema, ok := is.SchemaByName(oldIdent.Schema) if !ok { if isAlterTable { - return infoschema.ErrTableNotExists.GenWithStackByArgs(oldIdent.Schema, oldIdent.Name) + return nil, 0, infoschema.ErrTableNotExists.GenWithStackByArgs(oldIdent.Schema, oldIdent.Name) } - if is.TableExists(newIdent.Schema, newIdent.Name) { - return infoschema.ErrTableExists.GenWithStackByArgs(newIdent) + if tableExists(is, newIdent, tables) { + return nil, 0, infoschema.ErrTableExists.GenWithStackByArgs(newIdent) } - return errFileNotFound.GenWithStackByArgs(oldIdent.Schema, oldIdent.Name) + return nil, 0, errFileNotFound.GenWithStackByArgs(oldIdent.Schema, oldIdent.Name) } - oldTbl, err := is.TableByName(oldIdent.Schema, oldIdent.Name) - if err != nil { + if !tableExists(is, oldIdent, tables) { if isAlterTable { - return infoschema.ErrTableNotExists.GenWithStackByArgs(oldIdent.Schema, oldIdent.Name) + return nil, 0, infoschema.ErrTableNotExists.GenWithStackByArgs(oldIdent.Schema, oldIdent.Name) } - if is.TableExists(newIdent.Schema, newIdent.Name) { - return infoschema.ErrTableExists.GenWithStackByArgs(newIdent) + if tableExists(is, newIdent, tables) { + return nil, 0, infoschema.ErrTableExists.GenWithStackByArgs(newIdent) } - return errFileNotFound.GenWithStackByArgs(oldIdent.Schema, oldIdent.Name) + return nil, 0, errFileNotFound.GenWithStackByArgs(oldIdent.Schema, oldIdent.Name) } if isAlterTable && newIdent.Schema.L == oldIdent.Schema.L && newIdent.Name.L == oldIdent.Name.L { - // oldIdent is equal to newIdent, do nothing - return nil + //oldIdent is equal to newIdent, do nothing + return nil, 0, nil } newSchema, ok := is.SchemaByName(newIdent.Schema) if !ok { - return ErrErrorOnRename.GenWithStackByArgs( + return nil, 0, ErrErrorOnRename.GenWithStackByArgs( fmt.Sprintf("%s.%s", oldIdent.Schema, oldIdent.Name), fmt.Sprintf("%s.%s", newIdent.Schema, newIdent.Name), 168, fmt.Sprintf("Database `%s` doesn't exist", newIdent.Schema)) } - if is.TableExists(newIdent.Schema, newIdent.Name) { - return infoschema.ErrTableExists.GenWithStackByArgs(newIdent) + if tableExists(is, newIdent, tables) { + return nil, 0, infoschema.ErrTableExists.GenWithStackByArgs(newIdent) } if err := checkTooLongTable(newIdent.Name); err != nil { - return errors.Trace(err) + return nil, 0, errors.Trace(err) } + oldTableID := getTableID(is, oldIdent, tables) + oldIdentKey := getIdentKey(oldIdent) + tables[oldIdentKey] = tableNotExist + newIdentKey := getIdentKey(newIdent) + tables[newIdentKey] = oldTableID + return []*model.DBInfo{oldSchema, newSchema}, oldTableID, nil +} - job := &model.Job{ - SchemaID: newSchema.ID, - TableID: oldTbl.Meta().ID, - SchemaName: newSchema.Name.L, - Type: model.ActionRenameTable, - BinlogInfo: &model.HistoryInfo{}, - Args: []interface{}{oldSchema.ID, newIdent.Name}, +func tableExists(is infoschema.InfoSchema, ident ast.Ident, tables map[string]int64) bool { + identKey := getIdentKey(ident) + tableID, ok := tables[identKey] + if (ok && tableID != tableNotExist) || (!ok && is.TableExists(ident.Schema, ident.Name)) { + return true } + return false +} - err = d.doDDLJob(ctx, job) - err = d.callHookOnChanged(err) - return errors.Trace(err) +func getTableID(is infoschema.InfoSchema, ident ast.Ident, tables map[string]int64) int64 { + identKey := getIdentKey(ident) + tableID, ok := tables[identKey] + if !ok { + oldTbl, err := is.TableByName(ident.Schema, ident.Name) + if err != nil { + return tableNotExist + } + tableID = oldTbl.Meta().ID + } + return tableID +} + +func getIdentKey(ident ast.Ident) string { + return fmt.Sprintf("%s.%s", ident.Schema.L, ident.Name.L) } func getAnonymousIndex(t table.Table, colName model.CIStr) model.CIStr { diff --git a/ddl/ddl_worker.go b/ddl/ddl_worker.go index d09170a89bbb7..6506506158ad4 100644 --- a/ddl/ddl_worker.go +++ b/ddl/ddl_worker.go @@ -688,6 +688,8 @@ func (w *worker) runDDLJob(d *ddlCtx, t *meta.Meta, job *model.Job) (ver int64, ver, err = onAlterTablePartition(t, job) case model.ActionAlterSequence: ver, err = onAlterSequence(t, job) + case model.ActionRenameTables: + ver, err = onRenameTables(d, t, job) default: // Invalid job, cancel it. job.State = model.JobStateCancelled @@ -851,6 +853,28 @@ func updateSchemaVersion(t *meta.Meta, job *model.Job) (int64, error) { return 0, errors.Trace(err) } diff.TableID = job.TableID + case model.ActionRenameTables: + oldSchemaIDs := []int64{} + newSchemaIDs := []int64{} + tableNames := []*model.CIStr{} + tableIDs := []int64{} + err = job.DecodeArgs(&oldSchemaIDs, &newSchemaIDs, &tableNames, &tableIDs) + if err != nil { + return 0, errors.Trace(err) + } + affects := make([]*model.AffectedOption, len(newSchemaIDs)) + for i, newSchemaID := range newSchemaIDs { + affects[i] = &model.AffectedOption{ + SchemaID: newSchemaID, + TableID: tableIDs[i], + OldTableID: tableIDs[i], + OldSchemaID: oldSchemaIDs[i], + } + } + diff.TableID = tableIDs[0] + diff.SchemaID = newSchemaIDs[0] + diff.OldSchemaID = oldSchemaIDs[0] + diff.AffectedOpts = affects case model.ActionExchangeTablePartition: var ( ptSchemaID int64 diff --git a/ddl/failtest/fail_db_test.go b/ddl/failtest/fail_db_test.go index 3681a528d325d..701576dc644a1 100644 --- a/ddl/failtest/fail_db_test.go +++ b/ddl/failtest/fail_db_test.go @@ -119,7 +119,7 @@ func (s *testFailDBSuite) TestHalfwayCancelOperations(c *C) { // Test schema is correct. tk.MustExec("select * from t") // test for renaming table - c.Assert(failpoint.Enable("github.com/pingcap/tidb/ddl/renameTableErr", `return(true)`), IsNil) + c.Assert(failpoint.Enable("github.com/pingcap/tidb/ddl/renameTableErr", `return("ty")`), IsNil) defer func() { c.Assert(failpoint.Disable("github.com/pingcap/tidb/ddl/renameTableErr"), IsNil) }() @@ -127,6 +127,15 @@ func (s *testFailDBSuite) TestHalfwayCancelOperations(c *C) { tk.MustExec("insert into tx values(1)") _, err = tk.Exec("rename table tx to ty") c.Assert(err, NotNil) + tk.MustExec("create table ty(a int)") + tk.MustExec("insert into ty values(2)") + _, err = tk.Exec("rename table ty to tz, tx to ty") + c.Assert(err, NotNil) + _, err = tk.Exec("select * from tz") + c.Assert(err, NotNil) + _, err = tk.Exec("rename table tx to ty, ty to tz") + c.Assert(err, NotNil) + tk.MustQuery("select * from ty").Check(testkit.Rows("2")) // Make sure that the table's data has not been deleted. tk.MustQuery("select * from tx").Check(testkit.Rows("1")) // Execute ddl statement reload schema. diff --git a/ddl/rollingback.go b/ddl/rollingback.go index 3aff3ed13f13b..ff0bfbc5b3d3e 100644 --- a/ddl/rollingback.go +++ b/ddl/rollingback.go @@ -437,7 +437,7 @@ func convertJob2RollbackJob(w *worker, d *ddlCtx, t *meta.Meta, job *model.Job) case model.ActionModifyColumn: ver, err = rollingbackModifyColumn(t, job) case model.ActionRebaseAutoID, model.ActionShardRowID, model.ActionAddForeignKey, - model.ActionDropForeignKey, model.ActionRenameTable, + model.ActionDropForeignKey, model.ActionRenameTable, model.ActionRenameTables, model.ActionModifyTableCharsetAndCollate, model.ActionTruncateTablePartition, model.ActionModifySchemaCharsetAndCollate, model.ActionRepairTable, model.ActionModifyTableAutoIdCache, model.ActionAlterIndexVisibility, diff --git a/ddl/table.go b/ddl/table.go index bbd29a546def7..37c123cdb1cb3 100644 --- a/ddl/table.go +++ b/ddl/table.go @@ -638,12 +638,8 @@ func onRenameTable(d *ddlCtx, t *meta.Meta, job *model.Job) (ver int64, _ error) return ver, errors.Trace(err) } - tblInfo, err := getTableInfoAndCancelFaultJob(t, job, oldSchemaID) - if err != nil { - return ver, errors.Trace(err) - } newSchemaID := job.SchemaID - err = checkTableNotExists(d, t, newSchemaID, tableName.L) + err := checkTableNotExists(d, t, newSchemaID, tableName.L) if err != nil { if infoschema.ErrDatabaseNotExists.Equal(err) || infoschema.ErrTableExists.Equal(err) { job.State = model.JobStateCancelled @@ -651,6 +647,53 @@ func onRenameTable(d *ddlCtx, t *meta.Meta, job *model.Job) (ver int64, _ error) return ver, errors.Trace(err) } + ver, tblInfo, err := checkAndRenameTables(t, job, oldSchemaID, job.SchemaID, &tableName) + if err != nil { + return ver, errors.Trace(err) + } + + ver, err = updateSchemaVersion(t, job) + if err != nil { + return ver, errors.Trace(err) + } + job.FinishTableJob(model.JobStateDone, model.StatePublic, ver, tblInfo) + return ver, nil +} + +func onRenameTables(d *ddlCtx, t *meta.Meta, job *model.Job) (ver int64, _ error) { + oldSchemaIDs := []int64{} + newSchemaIDs := []int64{} + tableNames := []*model.CIStr{} + tableIDs := []int64{} + if err := job.DecodeArgs(&oldSchemaIDs, &newSchemaIDs, &tableNames, &tableIDs); err != nil { + job.State = model.JobStateCancelled + return ver, errors.Trace(err) + } + + tblInfo := &model.TableInfo{} + var err error + for i, oldSchemaID := range oldSchemaIDs { + job.TableID = tableIDs[i] + ver, tblInfo, err = checkAndRenameTables(t, job, oldSchemaID, newSchemaIDs[i], tableNames[i]) + if err != nil { + return ver, errors.Trace(err) + } + } + + ver, err = updateSchemaVersion(t, job) + if err != nil { + return ver, errors.Trace(err) + } + job.FinishTableJob(model.JobStateDone, model.StatePublic, ver, tblInfo) + return ver, nil +} + +func checkAndRenameTables(t *meta.Meta, job *model.Job, oldSchemaID int64, newSchemaID int64, tableName *model.CIStr) (ver int64, tblInfo *model.TableInfo, _ error) { + tblInfo, err := getTableInfoAndCancelFaultJob(t, job, oldSchemaID) + if err != nil { + return ver, tblInfo, errors.Trace(err) + } + var autoTableID int64 var autoRandID int64 shouldDelAutoID := false @@ -659,12 +702,12 @@ func onRenameTable(d *ddlCtx, t *meta.Meta, job *model.Job) (ver int64, _ error) autoTableID, err = t.GetAutoTableID(tblInfo.GetDBID(oldSchemaID), tblInfo.ID) if err != nil { job.State = model.JobStateCancelled - return ver, errors.Trace(err) + return ver, tblInfo, errors.Trace(err) } autoRandID, err = t.GetAutoRandomID(tblInfo.GetDBID(oldSchemaID), tblInfo.ID) if err != nil { job.State = model.JobStateCancelled - return ver, errors.Trace(err) + return ver, tblInfo, errors.Trace(err) } // It's compatible with old version. // TODO: Remove it. @@ -674,42 +717,39 @@ func onRenameTable(d *ddlCtx, t *meta.Meta, job *model.Job) (ver int64, _ error) err = t.DropTableOrView(oldSchemaID, tblInfo.ID, shouldDelAutoID) if err != nil { job.State = model.JobStateCancelled - return ver, errors.Trace(err) + return ver, tblInfo, errors.Trace(err) } failpoint.Inject("renameTableErr", func(val failpoint.Value) { - if val.(bool) { - job.State = model.JobStateCancelled - failpoint.Return(ver, errors.New("occur an error after renaming table")) + if valStr, ok := val.(string); ok { + if tableName.L == valStr { + job.State = model.JobStateCancelled + failpoint.Return(ver, tblInfo, errors.New("occur an error after renaming table")) + } } }) - tblInfo.Name = tableName + tblInfo.Name = *tableName err = t.CreateTableOrView(newSchemaID, tblInfo) if err != nil { job.State = model.JobStateCancelled - return ver, errors.Trace(err) + return ver, tblInfo, errors.Trace(err) } // Update the table's auto-increment ID. if newSchemaID != oldSchemaID { _, err = t.GenAutoTableID(newSchemaID, tblInfo.ID, autoTableID) if err != nil { job.State = model.JobStateCancelled - return ver, errors.Trace(err) + return ver, tblInfo, errors.Trace(err) } _, err = t.GenAutoRandomID(newSchemaID, tblInfo.ID, autoRandID) if err != nil { job.State = model.JobStateCancelled - return ver, errors.Trace(err) + return ver, tblInfo, errors.Trace(err) } } - ver, err = updateSchemaVersion(t, job) - if err != nil { - return ver, errors.Trace(err) - } - job.FinishTableJob(model.JobStateDone, model.StatePublic, ver, tblInfo) - return ver, nil + return ver, tblInfo, nil } func onModifyTableComment(t *meta.Meta, job *model.Job) (ver int64, _ error) { diff --git a/executor/ddl.go b/executor/ddl.go index e731d658c5d36..81e02f3d366c4 100644 --- a/executor/ddl.go +++ b/executor/ddl.go @@ -154,14 +154,23 @@ func (e *DDLExec) executeTruncateTable(s *ast.TruncateTableStmt) error { } func (e *DDLExec) executeRenameTable(s *ast.RenameTableStmt) error { - if len(s.TableToTables) != 1 { - // Now we only allow one schema changing at the same time. - return errors.Errorf("can't run multi schema change") - } - oldIdent := ast.Ident{Schema: s.OldTable.Schema, Name: s.OldTable.Name} - newIdent := ast.Ident{Schema: s.NewTable.Schema, Name: s.NewTable.Name} isAlterTable := false - err := domain.GetDomain(e.ctx).DDL().RenameTable(e.ctx, oldIdent, newIdent, isAlterTable) + var err error + if len(s.TableToTables) == 1 { + oldIdent := ast.Ident{Schema: s.TableToTables[0].OldTable.Schema, Name: s.TableToTables[0].OldTable.Name} + newIdent := ast.Ident{Schema: s.TableToTables[0].NewTable.Schema, Name: s.TableToTables[0].NewTable.Name} + err = domain.GetDomain(e.ctx).DDL().RenameTable(e.ctx, oldIdent, newIdent, isAlterTable) + } else { + oldIdents := make([]ast.Ident, 0, len(s.TableToTables)) + newIdents := make([]ast.Ident, 0, len(s.TableToTables)) + for _, tables := range s.TableToTables { + oldIdent := ast.Ident{Schema: tables.OldTable.Schema, Name: tables.OldTable.Name} + newIdent := ast.Ident{Schema: tables.NewTable.Schema, Name: tables.NewTable.Name} + oldIdents = append(oldIdents, oldIdent) + newIdents = append(newIdents, newIdent) + } + err = domain.GetDomain(e.ctx).DDL().RenameTables(e.ctx, oldIdents, newIdents, isAlterTable) + } return err } diff --git a/executor/ddl_test.go b/executor/ddl_test.go index eca31b0c5c525..7401b9e69acb9 100644 --- a/executor/ddl_test.go +++ b/executor/ddl_test.go @@ -30,6 +30,7 @@ import ( ddltestutil "github.com/pingcap/tidb/ddl/testutil" ddlutil "github.com/pingcap/tidb/ddl/util" "github.com/pingcap/tidb/domain" + "github.com/pingcap/tidb/errno" "github.com/pingcap/tidb/infoschema" "github.com/pingcap/tidb/kv" "github.com/pingcap/tidb/meta" @@ -1439,3 +1440,56 @@ func (s *testSuite6) TestAutoIncrementColumnErrorMessage(c *C) { _, err = tk.Exec("CREATE INDEX idx1 ON t1 ((t1_id + t1_id));") c.Assert(err.Error(), Equals, ddl.ErrExpressionIndexCanNotRefer.GenWithStackByArgs("idx1").Error()) } + +func (s *testRecoverTable) TestRenameMultiTables(c *C) { + c.Assert(failpoint.Enable("github.com/pingcap/tidb/meta/autoid/mockAutoIDChange", `return(true)`), IsNil) + defer func() { + c.Assert(failpoint.Disable("github.com/pingcap/tidb/meta/autoid/mockAutoIDChange"), IsNil) + }() + tk := testkit.NewTestKit(c, s.store) + + tk.MustExec("create database rename1") + tk.MustExec("create database rename2") + tk.MustExec("create database rename3") + tk.MustExec("create database rename4") + tk.MustExec("create table rename1.t1 (a int primary key auto_increment)") + tk.MustExec("create table rename3.t3 (a int primary key auto_increment)") + tk.MustExec("insert rename1.t1 values ()") + tk.MustExec("insert rename3.t3 values ()") + tk.MustExec("rename table rename1.t1 to rename2.t2, rename3.t3 to rename4.t4") + // Make sure the drop old database doesn't affect t2,t4's operations. + tk.MustExec("drop database rename1") + tk.MustExec("insert rename2.t2 values ()") + tk.MustExec("drop database rename3") + tk.MustExec("insert rename4.t4 values ()") + tk.MustQuery("select * from rename2.t2").Check(testkit.Rows("1", "2")) + tk.MustQuery("select * from rename4.t4").Check(testkit.Rows("1", "2")) + // Rename a table to another table in the same database. + tk.MustExec("rename table rename2.t2 to rename2.t1, rename4.t4 to rename4.t3") + tk.MustExec("insert rename2.t1 values ()") + tk.MustQuery("select * from rename2.t1").Check(testkit.Rows("1", "2", "3")) + tk.MustExec("insert rename4.t3 values ()") + tk.MustQuery("select * from rename4.t3").Check(testkit.Rows("1", "2", "3")) + tk.MustExec("drop database rename2") + tk.MustExec("drop database rename4") + + tk.MustExec("create database rename1") + tk.MustExec("create database rename2") + tk.MustExec("create database rename3") + tk.MustExec("create table rename1.t1 (a int primary key auto_increment)") + tk.MustExec("create table rename3.t3 (a int primary key auto_increment)") + tk.MustGetErrCode("rename table rename1.t1 to rename2.t2, rename3.t3 to rename2.t2", errno.ErrTableExists) + tk.MustExec("rename table rename1.t1 to rename2.t2, rename2.t2 to rename1.t1") + tk.MustExec("rename table rename1.t1 to rename2.t2, rename3.t3 to rename1.t1") + tk.MustExec("use rename1") + tk.MustQuery("show tables").Check(testkit.Rows("t1")) + tk.MustExec("use rename2") + tk.MustQuery("show tables").Check(testkit.Rows("t2")) + tk.MustExec("use rename3") + tk.MustExec("create table rename3.t3 (a int primary key auto_increment)") + tk.MustGetErrCode("rename table rename1.t1 to rename1.t2, rename1.t1 to rename3.t3", errno.ErrTableExists) + tk.MustGetErrCode("rename table rename1.t1 to rename1.t2, rename1.t1 to rename3.t4", errno.ErrFileNotFound) + tk.MustExec("drop database rename1") + tk.MustExec("drop database rename2") + tk.MustExec("drop database rename3") +} diff --git a/go.mod b/go.mod index 874de0aca9220..33f4eb4cf4a66 100644 --- a/go.mod +++ b/go.mod @@ -53,7 +53,7 @@ require ( github.com/pingcap/goleveldb v0.0.0-20191226122134-f82aafb29989 github.com/pingcap/kvproto v0.0.0-20201023092649-e6d6090277c9 github.com/pingcap/log v0.0.0-20200828042413-fce0951f1463 - github.com/pingcap/parser v0.0.0-20201024025010-3b2fb4b41d73 + github.com/pingcap/parser v0.0.0-20201028030005-1328d877c9f3 github.com/pingcap/sysutil v0.0.0-20200715082929-4c47bcac246a github.com/pingcap/tidb-tools v4.0.5-0.20200820092506-34ea90c93237+incompatible github.com/pingcap/tipb v0.0.0-20201026044621-45e60c77588f diff --git a/go.sum b/go.sum index d5e8e788366bb..97cfe37d0baf2 100644 --- a/go.sum +++ b/go.sum @@ -446,8 +446,8 @@ github.com/pingcap/log v0.0.0-20200117041106-d28c14d3b1cd/go.mod h1:4rbK1p9ILyIf github.com/pingcap/log v0.0.0-20200511115504-543df19646ad/go.mod h1:4rbK1p9ILyIfb6hU7OG2CiWSqMXnp3JMbiaVJ6mvoY8= github.com/pingcap/log v0.0.0-20200828042413-fce0951f1463 h1:Jboj+s4jSCp5E1WDgmRUv5rIFKFHaaSWuSZ4wMwXIcc= github.com/pingcap/log v0.0.0-20200828042413-fce0951f1463/go.mod h1:4rbK1p9ILyIfb6hU7OG2CiWSqMXnp3JMbiaVJ6mvoY8= -github.com/pingcap/parser v0.0.0-20201024025010-3b2fb4b41d73 h1:MN/e9n6GUBmmi6sKtriW0Dv1dfIohZRoKr/ZU1B4jrM= -github.com/pingcap/parser v0.0.0-20201024025010-3b2fb4b41d73/go.mod h1:74+OEdwM4B/jMpBRl92ch6CSmSYkQtv2TNxIjFdT/GE= +github.com/pingcap/parser v0.0.0-20201028030005-1328d877c9f3 h1:dfdPB1Ot9cNki/hVUgWFUiM8b05b5JCw7Oq9x6HaDeM= +github.com/pingcap/parser v0.0.0-20201028030005-1328d877c9f3/go.mod h1:74+OEdwM4B/jMpBRl92ch6CSmSYkQtv2TNxIjFdT/GE= github.com/pingcap/sysutil v0.0.0-20200206130906-2bfa6dc40bcd/go.mod h1:EB/852NMQ+aRKioCpToQ94Wl7fktV+FNnxf3CX/TTXI= github.com/pingcap/sysutil v0.0.0-20200715082929-4c47bcac246a h1:i2RElJ2aykSqZKeY+3SK18NHhajil8cQdG77wHe+P1Y= github.com/pingcap/sysutil v0.0.0-20200715082929-4c47bcac246a/go.mod h1:EB/852NMQ+aRKioCpToQ94Wl7fktV+FNnxf3CX/TTXI= @@ -495,7 +495,6 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sasha-s/go-deadlock v0.2.0/go.mod h1:StQn567HiB1fF2yJ44N9au7wOhrPS3iZqiDbRupzT10= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/sergi/go-diff v1.0.1-0.20180205163309-da645544ed44 h1:tB9NOR21++IjLyVx3/PCPhWMwqGNCMQEH96A6dMZ/gc= github.com/sergi/go-diff v1.0.1-0.20180205163309-da645544ed44/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shirou/gopsutil v2.19.10+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/gopsutil v2.20.3+incompatible h1:0JVooMPsT7A7HqEYdydp/OfjSOYSjhXV7w1hkKj/NPQ= diff --git a/infoschema/builder.go b/infoschema/builder.go index 4573d1ee2a01e..cc3fd22093ac1 100644 --- a/infoschema/builder.go +++ b/infoschema/builder.go @@ -94,7 +94,7 @@ func (b *Builder) ApplyDiff(m *meta.Meta, diff *model.SchemaDiff) ([]int64, erro } tmpIDs := tblIDs - if diff.Type == model.ActionRenameTable && diff.OldSchemaID != diff.SchemaID { + if (diff.Type == model.ActionRenameTable || diff.Type == model.ActionRenameTables) && diff.OldSchemaID != diff.SchemaID { oldRoDBInfo, ok := b.is.SchemaByID(diff.OldSchemaID) if !ok { return nil, ErrDatabaseNotExists.GenWithStackByArgs( diff --git a/planner/core/planbuilder.go b/planner/core/planbuilder.go index 498c25e3cd59a..9eebcf1d65570 100644 --- a/planner/core/planbuilder.go +++ b/planner/core/planbuilder.go @@ -3108,31 +3108,31 @@ func (b *PlanBuilder) buildDDL(ctx context.Context, node ast.DDLNode) (Plan, err case *ast.RenameTableStmt: if b.ctx.GetSessionVars().User != nil { authErr = ErrTableaccessDenied.GenWithStackByArgs("ALTER", b.ctx.GetSessionVars().User.AuthUsername, - b.ctx.GetSessionVars().User.AuthHostname, v.OldTable.Name.L) + b.ctx.GetSessionVars().User.AuthHostname, v.TableToTables[0].OldTable.Name.L) } - b.visitInfo = appendVisitInfo(b.visitInfo, mysql.AlterPriv, v.OldTable.Schema.L, - v.OldTable.Name.L, "", authErr) + b.visitInfo = appendVisitInfo(b.visitInfo, mysql.AlterPriv, v.TableToTables[0].OldTable.Schema.L, + v.TableToTables[0].OldTable.Name.L, "", authErr) if b.ctx.GetSessionVars().User != nil { authErr = ErrTableaccessDenied.GenWithStackByArgs("DROP", b.ctx.GetSessionVars().User.AuthUsername, - b.ctx.GetSessionVars().User.AuthHostname, v.OldTable.Name.L) + b.ctx.GetSessionVars().User.AuthHostname, v.TableToTables[0].OldTable.Name.L) } - b.visitInfo = appendVisitInfo(b.visitInfo, mysql.DropPriv, v.OldTable.Schema.L, - v.OldTable.Name.L, "", authErr) + b.visitInfo = appendVisitInfo(b.visitInfo, mysql.DropPriv, v.TableToTables[0].OldTable.Schema.L, + v.TableToTables[0].OldTable.Name.L, "", authErr) if b.ctx.GetSessionVars().User != nil { authErr = ErrTableaccessDenied.GenWithStackByArgs("CREATE", b.ctx.GetSessionVars().User.AuthUsername, - b.ctx.GetSessionVars().User.AuthHostname, v.NewTable.Name.L) + b.ctx.GetSessionVars().User.AuthHostname, v.TableToTables[0].NewTable.Name.L) } - b.visitInfo = appendVisitInfo(b.visitInfo, mysql.CreatePriv, v.NewTable.Schema.L, - v.NewTable.Name.L, "", authErr) + b.visitInfo = appendVisitInfo(b.visitInfo, mysql.CreatePriv, v.TableToTables[0].NewTable.Schema.L, + v.TableToTables[0].NewTable.Name.L, "", authErr) if b.ctx.GetSessionVars().User != nil { authErr = ErrTableaccessDenied.GenWithStackByArgs("INSERT", b.ctx.GetSessionVars().User.AuthUsername, - b.ctx.GetSessionVars().User.AuthHostname, v.NewTable.Name.L) + b.ctx.GetSessionVars().User.AuthHostname, v.TableToTables[0].NewTable.Name.L) } - b.visitInfo = appendVisitInfo(b.visitInfo, mysql.InsertPriv, v.NewTable.Schema.L, - v.NewTable.Name.L, "", authErr) + b.visitInfo = appendVisitInfo(b.visitInfo, mysql.InsertPriv, v.TableToTables[0].NewTable.Schema.L, + v.TableToTables[0].NewTable.Name.L, "", authErr) case *ast.RecoverTableStmt, *ast.FlashBackTableStmt: // Recover table command can only be executed by administrator. b.visitInfo = appendVisitInfo(b.visitInfo, mysql.SuperPriv, "", "", "", nil) diff --git a/planner/core/preprocess.go b/planner/core/preprocess.go index eb5cbad22d13e..08796ef2dd371 100644 --- a/planner/core/preprocess.go +++ b/planner/core/preprocess.go @@ -665,8 +665,8 @@ func (p *preprocessor) checkStatisticsOpGrammar(node ast.Node) { } func (p *preprocessor) checkRenameTableGrammar(stmt *ast.RenameTableStmt) { - oldTable := stmt.OldTable.Name.String() - newTable := stmt.NewTable.Name.String() + oldTable := stmt.TableToTables[0].OldTable.Name.String() + newTable := stmt.TableToTables[0].NewTable.Name.String() p.checkRenameTable(oldTable, newTable) }