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

planner: play replay load restore the table with foreign key with right order. #56457

Merged
merged 14 commits into from
Oct 16, 2024
30 changes: 21 additions & 9 deletions pkg/domain/plan_replayer_dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,22 +110,34 @@ func (tne *tableNameExtractor) getTablesAndViews() (map[tableNamePair]struct{},
r[tablePair] = struct{}{}
}
// if the table has a foreign key, we need to add the referenced table to the list
tblInfo, err := tne.is.TableByName(tne.ctx, model.NewCIStr(tablePair.DBName), model.NewCIStr(tablePair.TableName))
err := findFK(tne.is, tablePair.DBName, tablePair.TableName, r)
if err != nil {
return nil, err
}
for _, fk := range tblInfo.Meta().ForeignKeys {
key := tableNamePair{
DBName: fk.RefSchema.L,
TableName: fk.RefTable.L,
IsView: false,
}
r[key] = struct{}{}
}
}
return r, nil
}

func findFK(is infoschema.InfoSchema, dbName, tableName string, tableMap map[tableNamePair]struct{}) error {
tblInfo, err := is.TableByName(context.Background(), model.NewCIStr(dbName), model.NewCIStr(tableName))
if err != nil {
return err
}
for _, fk := range tblInfo.Meta().ForeignKeys {
key := tableNamePair{
DBName: fk.RefSchema.L,
TableName: fk.RefTable.L,
IsView: false,
}
tableMap[key] = struct{}{}
err := findFK(is, key.DBName, key.TableName, tableMap)
if err != nil {
return err
}
}
return nil
}

func (*tableNameExtractor) Enter(in ast.Node) (ast.Node, bool) {
if _, ok := in.(*ast.TableName); ok {
return in, true
Expand Down
37 changes: 26 additions & 11 deletions pkg/executor/plan_replayer.go
Original file line number Diff line number Diff line change
Expand Up @@ -482,17 +482,9 @@ func (e *PlanReplayerLoadInfo) Update(data []byte) error {
}

// build schema and table first
for _, zipFile := range z.File {
if zipFile.Name == fmt.Sprintf("schema/%v", domain.PlanReplayerSchemaMetaFile) {
continue
}
path := strings.Split(zipFile.Name, "/")
if len(path) == 2 && strings.Compare(path[0], "schema") == 0 && zipFile.Mode().IsRegular() {
err = createSchemaAndItems(e.Ctx, zipFile)
if err != nil {
return err
}
}
err = e.createTable(z)
if err != nil {
return err
}

// set tiflash replica if exists
Expand Down Expand Up @@ -530,3 +522,26 @@ func (e *PlanReplayerLoadInfo) Update(data []byte) error {
}
return nil
}

func (e *PlanReplayerLoadInfo) createTable(z *zip.Reader) error {
origin := e.Ctx.GetSessionVars().ForeignKeyChecks
// We need to disable foreign key check when we create schema and tables.
// because the order of creating schema and tables is not guaranteed.
e.Ctx.GetSessionVars().ForeignKeyChecks = false
defer func() {
e.Ctx.GetSessionVars().ForeignKeyChecks = origin
}()
for _, zipFile := range z.File {
if zipFile.Name == fmt.Sprintf("schema/%v", domain.PlanReplayerSchemaMetaFile) {
continue
}
path := strings.Split(zipFile.Name, "/")
if len(path) == 2 && strings.Compare(path[0], "schema") == 0 && zipFile.Mode().IsRegular() {
err := createSchemaAndItems(e.Ctx, zipFile)
if err != nil {
return err
}
}
}
return nil
}
57 changes: 55 additions & 2 deletions pkg/server/handler/optimizor/plan_replayer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,8 @@ func prepareData4PlanReplayer(t *testing.T, client *testserverclient.TestServerC
tk.MustExec("create database planReplayer")
tk.MustExec("use planReplayer")
tk.MustExec("create table t(a int)")
tk.MustExec("CREATE TABLE authors (id INT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(100) NOT NULL,email VARCHAR(100) UNIQUE NOT NULL);")
tk.MustExec("CREATE TABLE books (id INT PRIMARY KEY AUTO_INCREMENT,title VARCHAR(200) NOT NULL,publication_date DATE NOT NULL,author_id INT,FOREIGN KEY (author_id) REFERENCES authors(id) ON DELETE CASCADE);")
err = h.HandleDDLEvent(<-h.DDLEventCh())
require.NoError(t, err)
tk.MustExec("insert into t values(1), (2), (3), (4)")
Expand Down Expand Up @@ -249,7 +251,7 @@ func prepareData4PlanReplayer(t *testing.T, client *testserverclient.TestServerC
return filename, filename3
}

func TestIssue56458(t *testing.T) {
func TestPlanReplayerWithMultiForeignKey(t *testing.T) {
store := testkit.CreateMockStore(t)
dom, err := session.GetDomain(store)
require.NoError(t, err)
Expand Down Expand Up @@ -285,17 +287,60 @@ func TestIssue56458(t *testing.T) {
"meta.txt",
"schema/planreplayer.t.schema.txt",
"schema/planreplayer.v.schema.txt",
"schema/planreplayer2.t.schema.txt",
"schema/schema_meta.txt",
"session_bindings.sql",
"sql/sql0.sql",
"sql_meta.toml",
"stats/planreplayer.t.json",
"stats/planreplayer.v.json",
"stats/planreplayer2.t.json",
"statsMem/planreplayer.t.txt",
"statsMem/planreplayer.v.txt",
"statsMem/planreplayer2.t.txt",
"table_tiflash_replica.txt",
"variables.toml",
}, filesInReplayer)

// 3. check plan replayer load
// 3-1. write the plan replayer file from manual command to a file
path := "/tmp/plan_replayer.zip"
fp, err := os.Create(path)
require.NoError(t, err)
require.NotNil(t, fp)
defer func() {
require.NoError(t, fp.Close())
require.NoError(t, os.Remove(path))
}()

_, err = io.Copy(fp, bytes.NewReader(body))
require.NoError(t, err)
require.NoError(t, fp.Sync())

// 3-2. connect to tidb and use PLAN REPLAYER LOAD to load this file
db, err := sql.Open("mysql", client.GetDSN(func(config *mysql.Config) {
config.AllowAllFiles = true
}))
require.NoError(t, err, "Error connecting")
defer func() {
err := db.Close()
require.NoError(t, err)
}()
tk := testkit.NewDBTestKit(t, db)
tk.MustExec("use planReplayer")
tk.MustExec("drop table planReplayer.t")
tk.MustExec("drop table planReplayer2.t")
tk.MustExec("drop table planReplayer.v")
tk.MustExec(`plan replayer load "/tmp/plan_replayer.zip"`)

// 3-3. check whether binding takes effect
tk.MustExec(`select a, b from t where a in (1, 2, 3)`)
rows := tk.MustQuery("select @@last_plan_from_binding")
require.True(t, rows.Next(), "unexpected data")
var count int64
err = rows.Scan(&count)
require.NoError(t, err)
require.Equal(t, int64(1), count)
}

func TestIssue43192(t *testing.T) {
Expand Down Expand Up @@ -404,11 +449,19 @@ func prepareData4Issue56458(t *testing.T, client *testserverclient.TestServerCli
tk := testkit.NewDBTestKit(t, db)

tk.MustExec("create database planReplayer")
tk.MustExec("create database planReplayer2")
tk.MustExec("use planReplayer")

tk.MustExec("CREATE TABLE v(id INT PRIMARY KEY AUTO_INCREMENT);")
tk.MustExec("create table t(a int, b int, INDEX ia (a), INDEX ib (b), author_id int, FOREIGN KEY (author_id) REFERENCES v(id) ON DELETE CASCADE);")
err = h.HandleDDLEvent(<-h.DDLEventCh())
require.NoError(t, err)
tk.MustExec("create table planReplayer2.t(a int, b int, INDEX ia (a), INDEX ib (b), author_id int, FOREIGN KEY (author_id) REFERENCES planReplayer.v(id) ON DELETE CASCADE);")
err = h.HandleDDLEvent(<-h.DDLEventCh())
require.NoError(t, err)
tk.MustExec("create table t(a int, b int, INDEX ia (a), INDEX ib (b), author_id int, FOREIGN KEY (author_id) REFERENCES planReplayer2.t(a) ON DELETE CASCADE);")
err = h.HandleDDLEvent(<-h.DDLEventCh())
require.NoError(t, err)

tk.MustExec("create global binding for select a, b from t where a in (1, 2, 3) using select a, b from t use index (ib) where a in (1, 2, 3)")
rows := tk.MustQuery("plan replayer dump explain select a, b from t where a in (1, 2, 3)")
require.True(t, rows.Next(), "unexpected data")
Expand Down