Skip to content

Commit c371f8e

Browse files
authored
domain: fix play replay dump cannot save the table in the foreign key's reference (#56512) (#56565)
close #56458
1 parent 09a5c26 commit c371f8e

File tree

4 files changed

+194
-5
lines changed

4 files changed

+194
-5
lines changed

pkg/domain/extract.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,10 @@ func (w *extractWorker) handleIsView(ctx context.Context, p *extractPlanPackage)
288288
if tne.err != nil {
289289
return tne.err
290290
}
291-
r := tne.getTablesAndViews()
291+
r, err := tne.getTablesAndViews()
292+
if err != nil {
293+
return err
294+
}
292295
for t := range r {
293296
p.tables[t] = struct{}{}
294297
}

pkg/domain/plan_replayer_dump.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ type tableNameExtractor struct {
9696
err error
9797
}
9898

99-
func (tne *tableNameExtractor) getTablesAndViews() map[tableNamePair]struct{} {
99+
func (tne *tableNameExtractor) getTablesAndViews() (map[tableNamePair]struct{}, error) {
100100
r := make(map[tableNamePair]struct{})
101101
for tablePair := range tne.names {
102102
if tablePair.IsView {
@@ -108,8 +108,21 @@ func (tne *tableNameExtractor) getTablesAndViews() map[tableNamePair]struct{} {
108108
if !ok {
109109
r[tablePair] = struct{}{}
110110
}
111+
// if the table has a foreign key, we need to add the referenced table to the list
112+
tblInfo, err := tne.is.TableByName(model.NewCIStr(tablePair.DBName), model.NewCIStr(tablePair.TableName))
113+
if err != nil {
114+
return nil, err
115+
}
116+
for _, fk := range tblInfo.Meta().ForeignKeys {
117+
key := tableNamePair{
118+
DBName: fk.RefSchema.L,
119+
TableName: fk.RefTable.L,
120+
IsView: false,
121+
}
122+
r[key] = struct{}{}
123+
}
111124
}
112-
return r
125+
return r, nil
113126
}
114127

115128
func (tne *tableNameExtractor) Enter(in ast.Node) (ast.Node, bool) {
@@ -771,7 +784,7 @@ func extractTableNames(ctx context.Context, sctx sessionctx.Context,
771784
if tableExtractor.err != nil {
772785
return nil, tableExtractor.err
773786
}
774-
return tableExtractor.getTablesAndViews(), nil
787+
return tableExtractor.getTablesAndViews()
775788
}
776789

777790
func getStatsForTable(do *Domain, pair tableNamePair, historyStatsTS uint64) (*util.JSONTable, []string, error) {

pkg/server/handler/optimizor/BUILD.bazel

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ go_test(
4343
"statistics_handler_test.go",
4444
],
4545
flaky = True,
46-
shard_count = 4,
46+
shard_count = 6,
4747
deps = [
4848
":optimizor",
4949
"//pkg/config",
@@ -60,6 +60,7 @@ go_test(
6060
"//pkg/store/mockstore/unistore",
6161
"//pkg/testkit",
6262
"//pkg/testkit/testsetup",
63+
"//pkg/util/replayer",
6364
"//pkg/util/topsql/state",
6465
"@com_github_burntsushi_toml//:toml",
6566
"@com_github_go_sql_driver_mysql//:mysql",

pkg/server/handler/optimizor/plan_replayer_test.go

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import (
4242
"github.com/pingcap/tidb/pkg/session"
4343
util2 "github.com/pingcap/tidb/pkg/statistics/handle/util"
4444
"github.com/pingcap/tidb/pkg/testkit"
45+
"github.com/pingcap/tidb/pkg/util/replayer"
4546
"github.com/stretchr/testify/require"
4647
"github.com/tikv/client-go/v2/oracle"
4748
)
@@ -246,6 +247,177 @@ func prepareData4PlanReplayer(t *testing.T, client *testserverclient.TestServerC
246247
return filename, filename3
247248
}
248249

250+
func TestIssue56458(t *testing.T) {
251+
store := testkit.CreateMockStore(t)
252+
dom, err := session.GetDomain(store)
253+
require.NoError(t, err)
254+
// 1. setup and prepare plan replayer files by manual command and capture
255+
server, client := prepareServerAndClientForTest(t, store, dom)
256+
defer server.Close()
257+
258+
filename := prepareData4Issue56458(t, client, dom)
259+
defer os.RemoveAll(replayer.GetPlanReplayerDirName())
260+
261+
// 2. check the contents of the plan replayer zip files.
262+
var filesInReplayer []string
263+
collectFileNameAndAssertFileSize := func(f *zip.File) {
264+
// collect file name
265+
filesInReplayer = append(filesInReplayer, f.Name)
266+
}
267+
268+
// 2-1. check the plan replayer file from manual command
269+
resp0, err := client.FetchStatus(filepath.Join("/plan_replayer/dump/", filename))
270+
require.NoError(t, err)
271+
defer func() {
272+
require.NoError(t, resp0.Body.Close())
273+
}()
274+
body, err := io.ReadAll(resp0.Body)
275+
require.NoError(t, err)
276+
forEachFileInZipBytes(t, body, collectFileNameAndAssertFileSize)
277+
slices.Sort(filesInReplayer)
278+
require.Equal(t, []string{
279+
"config.toml",
280+
"debug_trace/debug_trace0.json",
281+
"explain.txt",
282+
"global_bindings.sql",
283+
"meta.txt",
284+
"schema/planreplayer.t.schema.txt",
285+
"schema/planreplayer.v.schema.txt",
286+
"schema/schema_meta.txt",
287+
"session_bindings.sql",
288+
"sql/sql0.sql",
289+
"sql_meta.toml",
290+
"stats/planreplayer.t.json",
291+
"stats/planreplayer.v.json",
292+
"statsMem/planreplayer.t.txt",
293+
"statsMem/planreplayer.v.txt",
294+
"table_tiflash_replica.txt",
295+
"variables.toml",
296+
}, filesInReplayer)
297+
}
298+
299+
func TestIssue43192(t *testing.T) {
300+
store := testkit.CreateMockStore(t)
301+
dom, err := session.GetDomain(store)
302+
require.NoError(t, err)
303+
// 1. setup and prepare plan replayer files by manual command and capture
304+
server, client := prepareServerAndClientForTest(t, store, dom)
305+
defer server.Close()
306+
307+
filename := prepareData4Issue43192(t, client, dom)
308+
defer os.RemoveAll(replayer.GetPlanReplayerDirName())
309+
310+
// 2. check the contents of the plan replayer zip files.
311+
var filesInReplayer []string
312+
collectFileNameAndAssertFileSize := func(f *zip.File) {
313+
// collect file name
314+
filesInReplayer = append(filesInReplayer, f.Name)
315+
}
316+
317+
// 2-1. check the plan replayer file from manual command
318+
resp0, err := client.FetchStatus(filepath.Join("/plan_replayer/dump/", filename))
319+
require.NoError(t, err)
320+
defer func() {
321+
require.NoError(t, resp0.Body.Close())
322+
}()
323+
body, err := io.ReadAll(resp0.Body)
324+
require.NoError(t, err)
325+
forEachFileInZipBytes(t, body, collectFileNameAndAssertFileSize)
326+
slices.Sort(filesInReplayer)
327+
require.Equal(t, expectedFilesInReplayer, filesInReplayer)
328+
329+
// 3. check plan replayer load
330+
// 3-1. write the plan replayer file from manual command to a file
331+
path := "/tmp/plan_replayer.zip"
332+
fp, err := os.Create(path)
333+
require.NoError(t, err)
334+
require.NotNil(t, fp)
335+
defer func() {
336+
require.NoError(t, fp.Close())
337+
require.NoError(t, os.Remove(path))
338+
}()
339+
340+
_, err = io.Copy(fp, bytes.NewReader(body))
341+
require.NoError(t, err)
342+
require.NoError(t, fp.Sync())
343+
344+
// 3-2. connect to tidb and use PLAN REPLAYER LOAD to load this file
345+
db, err := sql.Open("mysql", client.GetDSN(func(config *mysql.Config) {
346+
config.AllowAllFiles = true
347+
}))
348+
require.NoError(t, err, "Error connecting")
349+
defer func() {
350+
err := db.Close()
351+
require.NoError(t, err)
352+
}()
353+
tk := testkit.NewDBTestKit(t, db)
354+
tk.MustExec("use planReplayer")
355+
tk.MustExec("drop table planReplayer.t")
356+
tk.MustExec(`plan replayer load "/tmp/plan_replayer.zip"`)
357+
358+
// 3-3. check whether binding takes effect
359+
tk.MustExec(`select a, b from t where a in (1, 2, 3)`)
360+
rows := tk.MustQuery("select @@last_plan_from_binding")
361+
require.True(t, rows.Next(), "unexpected data")
362+
var count int64
363+
err = rows.Scan(&count)
364+
require.NoError(t, err)
365+
require.Equal(t, int64(1), count)
366+
}
367+
368+
func prepareData4Issue43192(t *testing.T, client *testserverclient.TestServerClient, dom *domain.Domain) string {
369+
h := dom.StatsHandle()
370+
db, err := sql.Open("mysql", client.GetDSN())
371+
require.NoError(t, err, "Error connecting")
372+
defer func() {
373+
err := db.Close()
374+
require.NoError(t, err)
375+
}()
376+
tk := testkit.NewDBTestKit(t, db)
377+
378+
tk.MustExec("create database planReplayer")
379+
tk.MustExec("use planReplayer")
380+
tk.MustExec("create table t(a int, b int, INDEX ia (a), INDEX ib (b));")
381+
err = h.HandleDDLEvent(<-h.DDLEventCh())
382+
require.NoError(t, err)
383+
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)")
384+
rows := tk.MustQuery("plan replayer dump explain select a, b from t where a in (1, 2, 3)")
385+
require.True(t, rows.Next(), "unexpected data")
386+
var filename string
387+
require.NoError(t, rows.Scan(&filename))
388+
require.NoError(t, rows.Close())
389+
rows = tk.MustQuery("select @@tidb_last_plan_replayer_token")
390+
require.True(t, rows.Next(), "unexpected data")
391+
return filename
392+
}
393+
394+
func prepareData4Issue56458(t *testing.T, client *testserverclient.TestServerClient, dom *domain.Domain) string {
395+
h := dom.StatsHandle()
396+
db, err := sql.Open("mysql", client.GetDSN())
397+
require.NoError(t, err, "Error connecting")
398+
defer func() {
399+
err := db.Close()
400+
require.NoError(t, err)
401+
}()
402+
tk := testkit.NewDBTestKit(t, db)
403+
404+
tk.MustExec("create database planReplayer")
405+
tk.MustExec("use planReplayer")
406+
tk.MustExec("CREATE TABLE v(id INT PRIMARY KEY AUTO_INCREMENT);")
407+
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);")
408+
err = h.HandleDDLEvent(<-h.DDLEventCh())
409+
require.NoError(t, err)
410+
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)")
411+
rows := tk.MustQuery("plan replayer dump explain select a, b from t where a in (1, 2, 3)")
412+
require.True(t, rows.Next(), "unexpected data")
413+
var filename string
414+
require.NoError(t, rows.Scan(&filename))
415+
require.NoError(t, rows.Close())
416+
rows = tk.MustQuery("select @@tidb_last_plan_replayer_token")
417+
require.True(t, rows.Next(), "unexpected data")
418+
return filename
419+
}
420+
249421
func forEachFileInZipBytes(t *testing.T, b []byte, fn func(file *zip.File)) {
250422
br := bytes.NewReader(b)
251423
z, err := zip.NewReader(br, int64(len(b)))

0 commit comments

Comments
 (0)