Skip to content

Commit

Permalink
ddl: add more foreign key test case (#40052)
Browse files Browse the repository at this point in the history
close #40189
  • Loading branch information
crazycs520 authored Dec 28, 2022
1 parent 32f9604 commit bddfc62
Show file tree
Hide file tree
Showing 8 changed files with 336 additions and 1 deletion.
110 changes: 110 additions & 0 deletions ddl/fktest/foreign_key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"bytes"
"context"
"fmt"
"sync"
"testing"
"time"

Expand Down Expand Up @@ -1700,3 +1701,112 @@ func TestForeignKeyWithCacheTable(t *testing.T) {
tk.MustExec("alter table t2 nocache;")
tk.MustExec("drop table t1,t2;")
}

func TestForeignKeyAndConcurrentDDL(t *testing.T) {
store := testkit.CreateMockStore(t)
tk := testkit.NewTestKit(t, store)
tk.MustExec("set @@foreign_key_checks=1;")
tk.MustExec("use test")
// Test foreign key refer cache table.
tk.MustExec("create table t1 (a int, b int, c int, index(a), index(b), index(c));")
tk.MustExec("create table t2 (a int, b int, c int, index(a), index(b), index(c));")
tk2 := testkit.NewTestKit(t, store)
tk2.MustExec("set @@foreign_key_checks=1;")
tk2.MustExec("use test")
passCases := []struct {
prepare []string
ddl1 string
ddl2 string
}{
{
ddl1: "alter table t2 add constraint fk_1 foreign key (a) references t1(a)",
ddl2: "alter table t2 add constraint fk_2 foreign key (b) references t1(b)",
},
{
ddl1: "alter table t2 drop foreign key fk_1",
ddl2: "alter table t2 drop foreign key fk_2",
},
{
prepare: []string{
"alter table t2 drop index a",
},
ddl1: "alter table t2 add index(a)",
ddl2: "alter table t2 add constraint fk_1 foreign key (a) references t1(a)",
},
{
ddl1: "alter table t2 drop index c",
ddl2: "alter table t2 add constraint fk_2 foreign key (b) references t1(b)",
},
}
for _, ca := range passCases {
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
tk.MustExec(ca.ddl1)
}()
go func() {
defer wg.Done()
tk2.MustExec(ca.ddl2)
}()
wg.Wait()
}
errorCases := []struct {
prepare []string
ddl1 string
err1 string
ddl2 string
err2 string
}{
{
ddl1: "alter table t2 add constraint fk foreign key (a) references t1(a)",
err1: "[ddl:1826]Duplicate foreign key constraint name 'fk'",
ddl2: "alter table t2 add constraint fk foreign key (b) references t1(b)",
err2: "[ddl:1826]Duplicate foreign key constraint name 'fk'",
},
{
prepare: []string{
"alter table t2 add constraint fk_1 foreign key (a) references t1(a)",
},
ddl1: "alter table t2 drop foreign key fk_1",
err1: "[schema:1091]Can't DROP 'fk_1'; check that column/key exists",
ddl2: "alter table t2 drop foreign key fk_1",
err2: "[schema:1091]Can't DROP 'fk_1'; check that column/key exists",
},
{
ddl1: "alter table t2 drop index a",
err1: "[ddl:1553]Cannot drop index 'a': needed in a foreign key constraint",
ddl2: "alter table t2 add constraint fk_1 foreign key (a) references t1(a)",
err2: "[ddl:-1]Failed to add the foreign key constraint. Missing index for 'fk_1' foreign key columns in the table 't2'",
},
}
tk.MustExec("drop table t1,t2")
tk.MustExec("create table t1 (a int, b int, c int, index(a), index(b), index(c));")
tk.MustExec("create table t2 (a int, b int, c int, index(a), index(b), index(c));")
for i, ca := range errorCases {
for _, sql := range ca.prepare {
tk.MustExec(sql)
}
var wg sync.WaitGroup
var err1, err2 error
wg.Add(2)
go func() {
defer wg.Done()
err1 = tk.ExecToErr(ca.ddl1)
}()
go func() {
defer wg.Done()
err2 = tk2.ExecToErr(ca.ddl2)
}()
wg.Wait()
if (err1 == nil && err2 == nil) || (err1 != nil && err2 != nil) {
require.Failf(t, "both ddl1 and ddl2 execute success, but expect 1 error", fmt.Sprintf("idx: %v, err1: %v, err2: %v", i, err1, err2))
}
if err1 != nil {
require.Equal(t, ca.err1, err1.Error())
}
if err2 != nil {
require.Equal(t, ca.err2, err2.Error())
}
}
}
41 changes: 41 additions & 0 deletions ddl/metadatalocktest/mdl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,47 @@ func TestMDLBasicBatchPointGet(t *testing.T) {
require.Less(t, ts1, ts2)
}

func TestMDLAddForeignKey(t *testing.T) {
store, dom := testkit.CreateMockStoreAndDomain(t)
sv := server.CreateMockServer(t, store)

sv.SetDomain(dom)
dom.InfoSyncer().SetSessionManager(sv)
defer sv.Close()

conn1 := server.CreateMockConn(t, sv)
tk := testkit.NewTestKitWithSession(t, store, conn1.Context().Session)
conn2 := server.CreateMockConn(t, sv)
tkDDL := testkit.NewTestKitWithSession(t, store, conn2.Context().Session)
tk.MustExec("use test")
tk.MustExec("set global tidb_enable_metadata_lock=1")
tk.MustExec("create table t1(id int key);")
tk.MustExec("create table t2(id int key);")

tk.MustExec("begin")
tk.MustExec("insert into t2 values(1);")

var wg sync.WaitGroup
var ddlErr error
wg.Add(1)
var ts2 time.Time
go func() {
defer wg.Done()
ddlErr = tkDDL.ExecToErr("alter table test.t2 add foreign key (id) references t1(id)")
ts2 = time.Now()
}()

time.Sleep(2 * time.Second)

ts1 := time.Now()
tk.MustExec("commit")

wg.Wait()
require.Error(t, ddlErr)
require.Equal(t, "[ddl:1452]Cannot add or update a child row: a foreign key constraint fails (`test`.`t2`, CONSTRAINT `fk_1` FOREIGN KEY (`id`) REFERENCES `t1` (`id`))", ddlErr.Error())
require.Less(t, ts1, ts2)
}

func TestMDLRRUpdateSchema(t *testing.T) {
store, dom := testkit.CreateMockStoreAndDomain(t)
sv := server.CreateMockServer(t, store)
Expand Down
1 change: 1 addition & 0 deletions executor/fktest/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ go_test(
"//infoschema",
"//kv",
"//meta/autoid",
"//parser",
"//parser/ast",
"//parser/auth",
"//parser/format",
Expand Down
90 changes: 90 additions & 0 deletions executor/fktest/foreign_key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ import (
"strconv"
"strings"
"sync"
"sync/atomic"
"testing"
"time"

"github.com/pingcap/tidb/config"
"github.com/pingcap/tidb/executor"
"github.com/pingcap/tidb/infoschema"
"github.com/pingcap/tidb/kv"
"github.com/pingcap/tidb/parser"
"github.com/pingcap/tidb/parser/ast"
"github.com/pingcap/tidb/parser/auth"
"github.com/pingcap/tidb/parser/format"
Expand Down Expand Up @@ -2643,3 +2645,91 @@ func TestForeignKeyOnReplaceInto(t *testing.T) {
tk.MustExec("replace into t1 values (1, 'new-boss', null)")
tk.MustQuery("select id from t1 order by id").Check(testkit.Rows("1"))
}

func TestForeignKeyLargeTxnErr(t *testing.T) {
store := testkit.CreateMockStore(t)
tk := testkit.NewTestKit(t, store)
tk.MustExec("set @@foreign_key_checks=1")
tk.MustExec("use test")
tk.MustExec("create table t1 (id int auto_increment key, pid int, name varchar(200), index(pid));")
tk.MustExec("insert into t1 (name) values ('abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890');")
for i := 0; i < 8; i++ {
tk.MustExec("insert into t1 (name) select name from t1;")
}
tk.MustQuery("select count(*) from t1").Check(testkit.Rows("256"))
tk.MustExec("update t1 set pid=1 where id>1")
tk.MustExec("alter table t1 add foreign key (pid) references t1 (id) on update cascade")
originLimit := atomic.LoadUint64(&kv.TxnTotalSizeLimit)
defer func() {
atomic.StoreUint64(&kv.TxnTotalSizeLimit, originLimit)
}()
// Set the limitation to a small value, make it easier to reach the limitation.
atomic.StoreUint64(&kv.TxnTotalSizeLimit, 10240)
tk.MustQuery("select sum(id) from t1").Check(testkit.Rows("32896"))
// foreign key cascade behaviour will cause ErrTxnTooLarge.
tk.MustGetDBError("update t1 set id=id+100000 where id=1", kv.ErrTxnTooLarge)
tk.MustQuery("select sum(id) from t1").Check(testkit.Rows("32896"))
tk.MustGetDBError("update t1 set id=id+100000 where id=1", kv.ErrTxnTooLarge)
tk.MustQuery("select id,pid from t1 where id<3 order by id").Check(testkit.Rows("1 <nil>", "2 1"))
tk.MustExec("set @@foreign_key_checks=0")
tk.MustExec("update t1 set id=id+100000 where id=1")
tk.MustQuery("select id,pid from t1 where id<3 or pid is null order by id").Check(testkit.Rows("2 1", "100001 <nil>"))
}

func TestForeignKeyAndLockView(t *testing.T) {
store := testkit.CreateMockStore(t)
tk := testkit.NewTestKit(t, store)
tk.MustExec("use test")
tk.MustExec("create table t1 (id int key)")
tk.MustExec("create table t2 (id int key, foreign key (id) references t1(id) ON DELETE CASCADE ON UPDATE CASCADE)")
tk.MustExec("insert into t1 values (1)")
tk.MustExec("insert into t2 values (1)")
tk.MustExec("begin pessimistic")
tk.MustExec("set @@foreign_key_checks=0")
tk.MustExec("update t2 set id=2")

tk2 := testkit.NewTestKit(t, store)
tk2.MustExec("set @@foreign_key_checks=1")
tk2.MustExec("use test")
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
tk2.MustExec("begin pessimistic")
tk2.MustExec("update t1 set id=2 where id=1")
tk2.MustExec("commit")
}()
time.Sleep(time.Millisecond * 200)
_, digest := parser.NormalizeDigest("update t1 set id=2 where id=1")
tk.MustQuery("select CURRENT_SQL_DIGEST from information_schema.tidb_trx where state='LockWaiting' and db='test'").Check(testkit.Rows(digest.String()))
tk.MustGetErrMsg("update t1 set id=2", "[executor:1213]Deadlock found when trying to get lock; try restarting transaction")
wg.Wait()
}

func TestForeignKeyAndMemoryTracker(t *testing.T) {
store := testkit.CreateMockStore(t)
tk := testkit.NewTestKit(t, store)
tk.MustExec("set @@foreign_key_checks=1")
tk.MustExec("use test")
tk.MustExec("create table t1 (id int auto_increment key, pid int, name varchar(200), index(pid));")
tk.MustExec("insert into t1 (name) values ('abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz');")
for i := 0; i < 8; i++ {
tk.MustExec("insert into t1 (name) select name from t1;")
}
tk.MustQuery("select count(*) from t1").Check(testkit.Rows("256"))
tk.MustExec("update t1 set pid=1 where id>1")
tk.MustExec("alter table t1 add foreign key (pid) references t1 (id) on update cascade")
tk.MustQuery("select sum(id) from t1").Check(testkit.Rows("32896"))
defer tk.MustExec("SET GLOBAL tidb_mem_oom_action = DEFAULT")
tk.MustExec("SET GLOBAL tidb_mem_oom_action='CANCEL'")
tk.MustExec("set @@tidb_mem_quota_query=40960;")
// foreign key cascade behaviour will exceed memory quota.
err := tk.ExecToErr("update t1 set id=id+100000 where id=1")
require.Error(t, err)
require.Contains(t, err.Error(), "Out Of Memory Quota!")
tk.MustQuery("select id,pid from t1 where id = 1").Check(testkit.Rows("1 <nil>"))
tk.MustExec("set @@foreign_key_checks=0")
// After disable foreign_key_checks, following DML will execute successful.
tk.MustExec("update t1 set id=id+100000 where id=1")
tk.MustQuery("select id,pid from t1 where id<3 or pid is null order by id").Check(testkit.Rows("2 1", "100001 <nil>"))
}
26 changes: 26 additions & 0 deletions tests/realtikvtest/addindextest/add_index_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,29 @@ func TestCreateMultiColsIndex(t *testing.T) {
ctx := initTest(t)
testTwoColsFrame(ctx, coliIDs, coljIDs, addIndexMultiCols)
}

func TestAddForeignKeyWithAutoCreateIndex(t *testing.T) {
store := realtikvtest.CreateMockStoreAndSetup(t)
tk := testkit.NewTestKit(t, store)
tk.MustExec("drop database if exists fk_index;")
tk.MustExec("create database fk_index;")
tk.MustExec("use fk_index;")
tk.MustExec(`set global tidb_ddl_enable_fast_reorg=1;`)
tk.MustExec("create table employee (id bigint auto_increment key, pid bigint)")
tk.MustExec("insert into employee (id) values (1),(2),(3),(4),(5),(6),(7),(8)")
for i := 0; i < 14; i++ {
tk.MustExec("insert into employee (pid) select pid from employee")
}
tk.MustExec("update employee set pid=id-1 where id>1")
tk.MustQuery("select count(*) from employee").Check(testkit.Rows("131072"))
tk.MustExec("alter table employee add foreign key fk_1(pid) references employee(id)")
tk.MustExec("alter table employee drop foreign key fk_1")
tk.MustExec("alter table employee drop index fk_1")
tk.MustExec("update employee set pid=0 where id=1")
tk.MustGetErrMsg("alter table employee add foreign key fk_1(pid) references employee(id)",
"[ddl:1452]Cannot add or update a child row: a foreign key constraint fails (`fk_index`.`employee`, CONSTRAINT `fk_1` FOREIGN KEY (`pid`) REFERENCES `employee` (`id`))")
tk.MustExec("update employee set pid=null where id=1")
tk.MustExec("insert into employee (pid) select pid from employee")
tk.MustExec("update employee set pid=id-1 where id>1 and pid is null")
tk.MustExec("alter table employee add foreign key fk_1(pid) references employee(id)")
}
1 change: 1 addition & 0 deletions tests/realtikvtest/pessimistictest/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ go_test(
"//parser/model",
"//parser/mysql",
"//parser/terror",
"//planner/core",
"//session",
"//sessionctx/variable",
"//sessiontxn",
Expand Down
61 changes: 61 additions & 0 deletions tests/realtikvtest/pessimistictest/pessimistic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
"github.com/pingcap/tidb/parser/model"
"github.com/pingcap/tidb/parser/mysql"
"github.com/pingcap/tidb/parser/terror"
plannercore "github.com/pingcap/tidb/planner/core"
"github.com/pingcap/tidb/session"
"github.com/pingcap/tidb/sessionctx/variable"
"github.com/pingcap/tidb/sessiontxn"
Expand Down Expand Up @@ -2816,6 +2817,66 @@ func TestAsyncCommitCalTSFail(t *testing.T) {
tk2.MustExec("commit")
}

func TestAsyncCommitAndForeignKey(t *testing.T) {
defer config.RestoreFunc()()
config.UpdateGlobal(func(conf *config.Config) {
conf.TiKVClient.AsyncCommit.SafeWindow = time.Second
conf.TiKVClient.AsyncCommit.AllowedClockDrift = 0
})
store := realtikvtest.CreateMockStoreAndSetup(t)
tk := createAsyncCommitTestKit(t, store)
tk.MustExec("drop table if exists t_parent, t_child")
tk.MustExec("create table t_parent (id int primary key)")
tk.MustExec("create table t_child (id int primary key, pid int, foreign key (pid) references t_parent(id) on delete cascade on update cascade)")
tk.MustExec("insert into t_parent values (1),(2),(3),(4)")
tk.MustExec("insert into t_child values (1,1),(2,2),(3,3)")
tk.MustExec("set tidb_enable_1pc = true")
tk.MustExec("begin pessimistic")
tk.MustExec("delete from t_parent where id in (1,4)")
tk.MustExec("update t_parent set id=22 where id=2")
tk.MustExec("commit")
tk.MustQuery("select * from t_parent order by id").Check(testkit.Rows("3", "22"))
tk.MustQuery("select * from t_child order by id").Check(testkit.Rows("2 22", "3 3"))
}

func TestTransactionIsolationAndForeignKey(t *testing.T) {
if !*realtikvtest.WithRealTiKV {
t.Skip("The test only support test with tikv.")
}
store := realtikvtest.CreateMockStoreAndSetup(t)
tk := testkit.NewTestKit(t, store)
tk2 := testkit.NewTestKit(t, store)
tk.MustExec("use test")
tk2.MustExec("use test")
tk.MustExec("drop table if exists t1,t2")
tk.MustExec("create table t1 (id int primary key)")
tk.MustExec("create table t2 (id int primary key, pid int, foreign key (pid) references t1(id) on delete cascade on update cascade)")
tk.MustExec("insert into t1 values (1)")
tk.MustExec("set tx_isolation = 'READ-COMMITTED'")
tk.MustExec("begin pessimistic")
tk.MustExec("insert into t2 values (1,1)")
tk.MustGetDBError("insert into t2 values (2,2)", plannercore.ErrNoReferencedRow2)
tk2.MustExec("insert into t1 values (2)")
tk.MustQuery("select * from t1").Check(testkit.Rows("1", "2"))
tk.MustExec("insert into t2 values (2,2)")
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
tk2.MustExec("delete from t1 where id=2")
}()
time.Sleep(time.Millisecond * 10)
tk.MustExec("commit")
wg.Wait()
tk.MustQuery("select * from t1").Check(testkit.Rows("1"))
tk.MustQuery("select * from t2").Check(testkit.Rows("1 1"))
tk2.MustExec("delete from t1 where id=1")
tk.MustQuery("select * from t1").Check(testkit.Rows())
tk.MustQuery("select * from t2").Check(testkit.Rows())
tk.MustExec("admin check table t1")
tk.MustExec("admin check table t2")
}

func TestChangeLockToPut(t *testing.T) {
store := realtikvtest.CreateMockStoreAndSetup(t)

Expand Down
Loading

0 comments on commit bddfc62

Please sign in to comment.