Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

*: fix issue of multi-schema change with foreign key #40042

Merged
merged 10 commits into from
Dec 21, 2022
8 changes: 8 additions & 0 deletions ddl/ddl_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -3264,6 +3264,14 @@ func (d *ddl) AlterTable(ctx context.Context, sctx sessionctx.Context, stmt *ast
return dbterror.ErrOptOnCacheTable.GenWithStackByArgs("Alter Table")
}
}
// set name for anonymous foreign key.
maxForeignKeyID := tb.Meta().MaxForeignKeyID
for _, spec := range validSpecs {
if spec.Tp == ast.AlterTableAddConstraint && spec.Constraint.Tp == ast.ConstraintForeignKey && spec.Constraint.Name == "" {
maxForeignKeyID++
spec.Constraint.Name = fmt.Sprintf("fk_%d", maxForeignKeyID)
}
}

if len(validSpecs) > 1 {
sctx.GetSessionVars().StmtCtx.MultiSchemaInfo = model.NewMultiSchemaInfo()
Expand Down
30 changes: 30 additions & 0 deletions ddl/fktest/foreign_key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1544,3 +1544,33 @@ func getLatestSchemaDiff(t *testing.T, tk *testkit.TestKit) *model.SchemaDiff {
require.NoError(t, err)
return diff
}

func TestTestMultiSchemaAddForeignKey(t *testing.T) {
store, _ := testkit.CreateMockStoreAndDomain(t)
tk := testkit.NewTestKit(t, store)
tk.MustExec("set @@foreign_key_checks=1;")
tk.MustExec("use test")
tk.MustExec("create table t1 (id int key);")
tk.MustExec("create table t2 (a int, b int);")
tk.MustExec("alter table t2 add foreign key (a) references t1(id), add foreign key (b) references t1(id)")
tk.MustExec("alter table t2 add column c int, add column d int")
tk.MustExec("alter table t2 add foreign key (c) references t1(id), add foreign key (d) references t1(id), add index(c), add index(d)")
tk.MustExec("drop table t2")
tk.MustExec("create table t2 (a int, b int, index idx1(a), index idx2(b));")
err := tk.ExecToErr("alter table t2 drop index idx1, drop index idx2, add foreign key (a) references t1(id), add foreign key (b) references t1(id)")
crazycs520 marked this conversation as resolved.
Show resolved Hide resolved
require.Error(t, err)
require.Equal(t, "[ddl:1553]Cannot drop index 'idx1': needed in a foreign key constraint", err.Error())
tk.MustExec("alter table t2 drop index idx1, drop index idx2")
tk.MustExec("alter table t2 add foreign key (a) references t1(id), add foreign key (b) references t1(id)")
tk.MustQuery("show create table t2").Check(testkit.Rows("t2 CREATE TABLE `t2` (\n" +
" `a` int(11) DEFAULT NULL,\n" +
" `b` int(11) DEFAULT NULL,\n" +
" KEY `fk_1` (`a`),\n" +
" KEY `fk_2` (`b`),\n" +
" CONSTRAINT `fk_1` FOREIGN KEY (`a`) REFERENCES `test`.`t1` (`id`),\n" +
" CONSTRAINT `fk_2` FOREIGN KEY (`b`) REFERENCES `test`.`t1` (`id`)\n" +
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin"))
tk.MustExec("drop table t2")
tk.MustExec("create table t2 (a int, b int, index idx0(a,b), index idx1(a), index idx2(b));")
tk.MustExec("alter table t2 drop index idx1, add foreign key (a) references t1(id), add foreign key (b) references t1(id)")
}
45 changes: 44 additions & 1 deletion ddl/multi_schema_change.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,10 @@ func fillMultiSchemaInfo(info *model.MultiSchemaInfo, job *model.Job) (err error
case model.ActionRebaseAutoID, model.ActionModifyTableComment, model.ActionModifyTableCharsetAndCollate:
case model.ActionAddForeignKey:
fkInfo := job.Args[0].(*model.FKInfo)
info.ForeignKeys = append(info.ForeignKeys, fkInfo.Name)
info.AddForeignKeys = append(info.AddForeignKeys, model.AddForeignKeyInfo{
Name: fkInfo.Name,
Cols: fkInfo.Cols,
})
default:
return dbterror.ErrRunMultiSchemaChanges.FastGenByArgs(job.Type.String())
}
Expand Down Expand Up @@ -323,6 +326,41 @@ func checkOperateSameColAndIdx(info *model.MultiSchemaInfo) error {
return checkIndexes(info.AlterIndexes, true)
}

func checkOperateDropIndexUseByForeignKey(info *model.MultiSchemaInfo, t table.Table) error {
var remainIndexes, droppingIndexes []*model.IndexInfo
tbInfo := t.Meta()
for _, idx := range tbInfo.Indices {
dropping := false
for _, name := range info.DropIndexes {
if name.L == idx.Name.L {
dropping = true
break
}
}
if dropping {
droppingIndexes = append(droppingIndexes, idx)
} else {
remainIndexes = append(remainIndexes, idx)
}
}

for _, fk := range info.AddForeignKeys {
if droppingIdx := findIndexByColumns(tbInfo, droppingIndexes, fk.Cols...); droppingIdx != nil && findIndexByColumns(tbInfo, remainIndexes, fk.Cols...) == nil {
return dbterror.ErrDropIndexNeededInForeignKey.GenWithStackByArgs(droppingIdx.Name)
}
}
return nil
}

func findIndexByColumns(tbInfo *model.TableInfo, indexes []*model.IndexInfo, cols ...model.CIStr) *model.IndexInfo {
for _, index := range indexes {
if model.IsIndexPrefixCovered(tbInfo, index, cols...) {
return index
}
}
return nil
}
crazycs520 marked this conversation as resolved.
Show resolved Hide resolved

func checkMultiSchemaInfo(info *model.MultiSchemaInfo, t table.Table) error {
err := checkOperateSameColAndIdx(info)
if err != nil {
Expand All @@ -334,6 +372,11 @@ func checkMultiSchemaInfo(info *model.MultiSchemaInfo, t table.Table) error {
return err
}

err = checkOperateDropIndexUseByForeignKey(info, t)
if err != nil {
return err
}

return checkAddColumnTooManyColumns(len(t.Cols()) + len(info.AddColumns) - len(info.DropColumns))
}

Expand Down
9 changes: 8 additions & 1 deletion parser/model/ddl.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,12 +316,19 @@ type MultiSchemaInfo struct {
AddIndexes []CIStr `json:"-"`
DropIndexes []CIStr `json:"-"`
AlterIndexes []CIStr `json:"-"`
ForeignKeys []CIStr `json:"-"`

AddForeignKeys []AddForeignKeyInfo `json:"-"`

RelativeColumns []CIStr `json:"-"`
PositionColumns []CIStr `json:"-"`
}

// AddForeignKeyInfo contains foreign key information.
type AddForeignKeyInfo struct {
Name CIStr
Cols []CIStr
}

// NewMultiSchemaInfo new a MultiSchemaInfo.
func NewMultiSchemaInfo() *MultiSchemaInfo {
return &MultiSchemaInfo{
Expand Down