Skip to content

Commit b854521

Browse files
authored
br: repair the index that any foreign key is needed in (#62418)
close #61642
1 parent 98bbd14 commit b854521

File tree

16 files changed

+980
-44
lines changed

16 files changed

+980
-44
lines changed

br/pkg/checkpoint/log_restore.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,19 @@ type CheckpointIngestIndexRepairSQL struct {
249249
IndexRepaired bool `json:"-"`
250250
}
251251

252+
type CheckpointForeignKeyUpdateSQL struct {
253+
FKID int64 `json:"fk-id"`
254+
SchemaName string `json:"schema-name"`
255+
TableName string `json:"table-name"`
256+
FKName string `json:"fk-name"`
257+
AddSQL string `json:"add-sql"`
258+
AddArgs []any `json:"add-args"`
259+
260+
OldForeignKeyFound bool `json:"-"`
261+
ForeignKeyUpdated bool `json:"-"`
262+
}
263+
252264
type CheckpointIngestIndexRepairSQLs struct {
253-
SQLs []CheckpointIngestIndexRepairSQL
265+
SQLs []CheckpointIngestIndexRepairSQL
266+
FKSQLs []CheckpointForeignKeyUpdateSQL
254267
}

br/pkg/restore/ingestrec/BUILD.bazel

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
22

33
go_library(
44
name = "ingestrec",
5-
srcs = ["ingest_recorder.go"],
5+
srcs = [
6+
"foreign_key.go",
7+
"ingest_recorder.go",
8+
],
69
importpath = "github.com/pingcap/tidb/br/pkg/restore/ingestrec",
710
visibility = ["//visibility:public"],
811
deps = [
912
"//pkg/infoschema",
1013
"//pkg/meta/model",
1114
"//pkg/parser/ast",
15+
"//pkg/parser/mysql",
1216
"//pkg/types",
1317
"@com_github_pingcap_errors//:errors",
1418
"@com_github_pingcap_log//:log",
@@ -19,17 +23,23 @@ go_library(
1923
go_test(
2024
name = "ingestrec_test",
2125
timeout = "short",
22-
srcs = ["ingest_recorder_test.go"],
26+
srcs = [
27+
"export_test.go",
28+
"foreign_key_test.go",
29+
"ingest_recorder_test.go",
30+
],
31+
embed = [":ingestrec"],
2332
flaky = True,
24-
shard_count = 3,
33+
shard_count = 7,
2534
deps = [
26-
":ingestrec",
35+
"//br/pkg/utiltest",
2736
"//pkg/kv",
2837
"//pkg/meta",
2938
"//pkg/meta/model",
3039
"//pkg/parser/ast",
3140
"//pkg/session",
3241
"//pkg/store/mockstore",
42+
"//pkg/testkit",
3343
"@com_github_pingcap_errors//:errors",
3444
"@com_github_stretchr_testify//require",
3545
],
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright 2025 PingCAP, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package ingestrec
16+
17+
func (m *TableForeignKeyRecordManager) GetFKRecordMap() map[ForeignKeyRecordKey]*ForeignKeyRecord {
18+
return m.fkRecordMap
19+
}
20+
21+
func (m *TableForeignKeyRecordManager) GetReferredFKRecordMap() map[ForeignKeyRecordKey]*ForeignKeyRecord {
22+
return m.referredFKRecordMap
23+
}
24+
25+
func (m *ForeignKeyRecordManager) GetFKRecordMap() map[ForeignKeyRecordKey]*ForeignKeyRecord {
26+
return m.fkRecordMap
27+
}
28+
29+
func (i *IngestRecorder) GetFKRecordMap() map[ForeignKeyRecordKey]*ForeignKeyRecord {
30+
return i.foreignKeyRecordManager.fkRecordMap
31+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// Copyright 2025 PingCAP, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package ingestrec
16+
17+
import (
18+
"context"
19+
"maps"
20+
21+
"github.com/pingcap/errors"
22+
"github.com/pingcap/tidb/pkg/infoschema"
23+
"github.com/pingcap/tidb/pkg/meta/model"
24+
"github.com/pingcap/tidb/pkg/parser/ast"
25+
"github.com/pingcap/tidb/pkg/parser/mysql"
26+
)
27+
28+
type ForeignKeyRecordKey struct {
29+
ChildSchemaNameO string
30+
ChildTableNameO string
31+
FKNameO string
32+
}
33+
34+
type ForeignKeyRecord struct {
35+
model.FKInfo
36+
ChildSchemaNameO string
37+
ChildTableNameO string
38+
}
39+
40+
func newForeignKeyRecordKey(
41+
childSchemaNameO string,
42+
childTableNameO string,
43+
fk *model.FKInfo,
44+
) (ForeignKeyRecordKey, *ForeignKeyRecord) {
45+
return ForeignKeyRecordKey{
46+
ChildSchemaNameO: childSchemaNameO,
47+
ChildTableNameO: childTableNameO,
48+
FKNameO: fk.Name.O,
49+
}, &ForeignKeyRecord{
50+
FKInfo: *fk,
51+
ChildSchemaNameO: childSchemaNameO,
52+
ChildTableNameO: childTableNameO,
53+
}
54+
}
55+
56+
type ForeignKeyRecordManager struct {
57+
fkRecordMap map[ForeignKeyRecordKey]*ForeignKeyRecord
58+
}
59+
60+
func NewForeignKeyRecordManager() *ForeignKeyRecordManager {
61+
return &ForeignKeyRecordManager{
62+
fkRecordMap: make(map[ForeignKeyRecordKey]*ForeignKeyRecord),
63+
}
64+
}
65+
66+
type TableForeignKeyRecordManager struct {
67+
fkRecordMap map[ForeignKeyRecordKey]*ForeignKeyRecord
68+
referredFKRecordMap map[ForeignKeyRecordKey]*ForeignKeyRecord
69+
}
70+
71+
func (m *ForeignKeyRecordManager) Merge(tm *TableForeignKeyRecordManager) {
72+
maps.Copy(m.fkRecordMap, tm.fkRecordMap)
73+
maps.Copy(m.fkRecordMap, tm.referredFKRecordMap)
74+
}
75+
76+
func NewForeignKeyRecordManagerForTables(
77+
ctx context.Context,
78+
infoSchema infoschema.InfoSchema,
79+
dbName ast.CIStr,
80+
tableInfo *model.TableInfo,
81+
) (*TableForeignKeyRecordManager, error) {
82+
tm := &TableForeignKeyRecordManager{
83+
fkRecordMap: make(map[ForeignKeyRecordKey]*ForeignKeyRecord),
84+
referredFKRecordMap: make(map[ForeignKeyRecordKey]*ForeignKeyRecord),
85+
}
86+
tableFKs := tableInfo.ForeignKeys
87+
tableReferredFKs := infoSchema.GetTableReferredForeignKeys(dbName.L, tableInfo.Name.L)
88+
for _, tableFK := range tableFKs {
89+
if tableInfo.PKIsHandle && len(tableFK.Cols) == 1 {
90+
refColInfo := model.FindColumnInfo(tableInfo.Columns, tableFK.Cols[0].L)
91+
if refColInfo != nil && mysql.HasPriKeyFlag(refColInfo.GetFlag()) {
92+
continue
93+
}
94+
}
95+
key, value := newForeignKeyRecordKey(dbName.O, tableInfo.Name.O, tableFK)
96+
tm.fkRecordMap[key] = value
97+
}
98+
for _, tableReferredFK := range tableReferredFKs {
99+
if tableInfo.PKIsHandle && len(tableReferredFK.Cols) == 1 {
100+
refColInfo := model.FindColumnInfo(tableInfo.Columns, tableReferredFK.Cols[0].L)
101+
if refColInfo != nil && mysql.HasPriKeyFlag(refColInfo.GetFlag()) {
102+
continue
103+
}
104+
}
105+
childTableInfo, err := infoSchema.TableByName(ctx, tableReferredFK.ChildSchema, tableReferredFK.ChildTable)
106+
if err != nil {
107+
return nil, errors.Trace(err)
108+
}
109+
for _, tableFK := range childTableInfo.Meta().ForeignKeys {
110+
if tableReferredFK.ChildFKName.O == tableFK.Name.O {
111+
key, value := newForeignKeyRecordKey(tableReferredFK.ChildSchema.O, tableReferredFK.ChildTable.O, tableFK)
112+
tm.referredFKRecordMap[key] = value
113+
break
114+
}
115+
}
116+
}
117+
return tm, nil
118+
}
119+
120+
func (tm *TableForeignKeyRecordManager) RemoveForeignKeys(tableInfo *model.TableInfo, indexInfo *model.IndexInfo) {
121+
for key, fkRecord := range tm.fkRecordMap {
122+
if model.IsIndexPrefixCovered(tableInfo, indexInfo, fkRecord.Cols...) {
123+
delete(tm.fkRecordMap, key)
124+
}
125+
}
126+
for key, fkRecord := range tm.referredFKRecordMap {
127+
if model.IsIndexPrefixCovered(tableInfo, indexInfo, fkRecord.RefCols...) {
128+
delete(tm.referredFKRecordMap, key)
129+
}
130+
}
131+
}
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
// Copyright 2025 PingCAP, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package ingestrec_test
16+
17+
import (
18+
"context"
19+
"testing"
20+
21+
"github.com/pingcap/tidb/br/pkg/restore/ingestrec"
22+
"github.com/pingcap/tidb/br/pkg/utiltest"
23+
"github.com/pingcap/tidb/pkg/parser/ast"
24+
"github.com/pingcap/tidb/pkg/testkit"
25+
"github.com/stretchr/testify/require"
26+
)
27+
28+
func TestForeignKeyRecordManager(t *testing.T) {
29+
ctx := context.Background()
30+
s := utiltest.CreateRestoreSchemaSuite(t)
31+
tk := testkit.NewTestKit(t, s.Mock.Storage)
32+
33+
tk.MustExec("create table test.parent (id int, index i1(id))")
34+
tk.MustExec("create table test.child (id int, pid int, index i1(pid), foreign key (pid) references test.parent (id) on delete cascade)")
35+
36+
infoSchema := s.Mock.InfoSchema()
37+
childTableInfo, err := infoSchema.TableInfoByName(ast.NewCIStr("test"), ast.NewCIStr("child"))
38+
require.NoError(t, err)
39+
parentTableInfo, err := infoSchema.TableInfoByName(ast.NewCIStr("test"), ast.NewCIStr("parent"))
40+
require.NoError(t, err)
41+
childTableIndexI1 := childTableInfo.Indices[0]
42+
parentTableIndexI1 := parentTableInfo.Indices[0]
43+
{
44+
foreignKeyRecordManager := ingestrec.NewForeignKeyRecordManager()
45+
childTableForeignKeyRecordManager, err := ingestrec.NewForeignKeyRecordManagerForTables(ctx, infoSchema, ast.NewCIStr("test"), childTableInfo)
46+
require.NoError(t, err)
47+
parentTableForeignKeyRecordManager, err := ingestrec.NewForeignKeyRecordManagerForTables(ctx, infoSchema, ast.NewCIStr("test"), parentTableInfo)
48+
require.NoError(t, err)
49+
require.Len(t, childTableForeignKeyRecordManager.GetFKRecordMap(), 1)
50+
require.Len(t, childTableForeignKeyRecordManager.GetReferredFKRecordMap(), 0)
51+
require.Len(t, parentTableForeignKeyRecordManager.GetFKRecordMap(), 0)
52+
require.Len(t, parentTableForeignKeyRecordManager.GetReferredFKRecordMap(), 1)
53+
foreignKeyRecordManager.Merge(childTableForeignKeyRecordManager)
54+
foreignKeyRecordManager.Merge(parentTableForeignKeyRecordManager)
55+
require.Len(t, foreignKeyRecordManager.GetFKRecordMap(), 1)
56+
}
57+
{
58+
foreignKeyRecordManager := ingestrec.NewForeignKeyRecordManager()
59+
childTableForeignKeyRecordManager, err := ingestrec.NewForeignKeyRecordManagerForTables(ctx, infoSchema, ast.NewCIStr("test"), childTableInfo)
60+
require.NoError(t, err)
61+
parentTableForeignKeyRecordManager, err := ingestrec.NewForeignKeyRecordManagerForTables(ctx, infoSchema, ast.NewCIStr("test"), parentTableInfo)
62+
require.NoError(t, err)
63+
childTableForeignKeyRecordManager.RemoveForeignKeys(childTableInfo, childTableIndexI1)
64+
require.Len(t, childTableForeignKeyRecordManager.GetFKRecordMap(), 0)
65+
require.Len(t, childTableForeignKeyRecordManager.GetReferredFKRecordMap(), 0)
66+
require.Len(t, parentTableForeignKeyRecordManager.GetFKRecordMap(), 0)
67+
require.Len(t, parentTableForeignKeyRecordManager.GetReferredFKRecordMap(), 1)
68+
foreignKeyRecordManager.Merge(childTableForeignKeyRecordManager)
69+
foreignKeyRecordManager.Merge(parentTableForeignKeyRecordManager)
70+
require.Len(t, foreignKeyRecordManager.GetFKRecordMap(), 1)
71+
}
72+
{
73+
foreignKeyRecordManager := ingestrec.NewForeignKeyRecordManager()
74+
childTableForeignKeyRecordManager, err := ingestrec.NewForeignKeyRecordManagerForTables(ctx, infoSchema, ast.NewCIStr("test"), childTableInfo)
75+
require.NoError(t, err)
76+
parentTableForeignKeyRecordManager, err := ingestrec.NewForeignKeyRecordManagerForTables(ctx, infoSchema, ast.NewCIStr("test"), parentTableInfo)
77+
require.NoError(t, err)
78+
parentTableForeignKeyRecordManager.RemoveForeignKeys(parentTableInfo, parentTableIndexI1)
79+
require.Len(t, childTableForeignKeyRecordManager.GetFKRecordMap(), 1)
80+
require.Len(t, childTableForeignKeyRecordManager.GetReferredFKRecordMap(), 0)
81+
require.Len(t, parentTableForeignKeyRecordManager.GetFKRecordMap(), 0)
82+
require.Len(t, parentTableForeignKeyRecordManager.GetReferredFKRecordMap(), 0)
83+
foreignKeyRecordManager.Merge(childTableForeignKeyRecordManager)
84+
foreignKeyRecordManager.Merge(parentTableForeignKeyRecordManager)
85+
require.Len(t, foreignKeyRecordManager.GetFKRecordMap(), 1)
86+
}
87+
{
88+
foreignKeyRecordManager := ingestrec.NewForeignKeyRecordManager()
89+
childTableForeignKeyRecordManager, err := ingestrec.NewForeignKeyRecordManagerForTables(ctx, infoSchema, ast.NewCIStr("test"), childTableInfo)
90+
require.NoError(t, err)
91+
parentTableForeignKeyRecordManager, err := ingestrec.NewForeignKeyRecordManagerForTables(ctx, infoSchema, ast.NewCIStr("test"), parentTableInfo)
92+
require.NoError(t, err)
93+
childTableForeignKeyRecordManager.RemoveForeignKeys(childTableInfo, childTableIndexI1)
94+
parentTableForeignKeyRecordManager.RemoveForeignKeys(parentTableInfo, parentTableIndexI1)
95+
require.Len(t, childTableForeignKeyRecordManager.GetFKRecordMap(), 0)
96+
require.Len(t, childTableForeignKeyRecordManager.GetReferredFKRecordMap(), 0)
97+
require.Len(t, parentTableForeignKeyRecordManager.GetFKRecordMap(), 0)
98+
require.Len(t, parentTableForeignKeyRecordManager.GetReferredFKRecordMap(), 0)
99+
foreignKeyRecordManager.Merge(childTableForeignKeyRecordManager)
100+
foreignKeyRecordManager.Merge(parentTableForeignKeyRecordManager)
101+
require.Len(t, foreignKeyRecordManager.GetFKRecordMap(), 0)
102+
}
103+
}
104+
105+
func TestForeignKeyRecordManagerForPK1(t *testing.T) {
106+
ctx := context.Background()
107+
s := utiltest.CreateRestoreSchemaSuite(t)
108+
tk := testkit.NewTestKit(t, s.Mock.Storage)
109+
110+
tk.MustExec("create table test.parent (id int primary key, pid int, index i1(id, pid))")
111+
tk.MustExec("create table test.child (id int, pid int, index i1(pid), foreign key (pid) references test.parent (id) on delete cascade)")
112+
113+
infoSchema := s.Mock.InfoSchema()
114+
childTableInfo, err := infoSchema.TableInfoByName(ast.NewCIStr("test"), ast.NewCIStr("child"))
115+
require.NoError(t, err)
116+
parentTableInfo, err := infoSchema.TableInfoByName(ast.NewCIStr("test"), ast.NewCIStr("parent"))
117+
require.NoError(t, err)
118+
childTableIndexI1 := childTableInfo.Indices[0]
119+
120+
foreignKeyRecordManager := ingestrec.NewForeignKeyRecordManager()
121+
childTableForeignKeyRecordManager, err := ingestrec.NewForeignKeyRecordManagerForTables(ctx, infoSchema, ast.NewCIStr("test"), childTableInfo)
122+
require.NoError(t, err)
123+
parentTableForeignKeyRecordManager, err := ingestrec.NewForeignKeyRecordManagerForTables(ctx, infoSchema, ast.NewCIStr("test"), parentTableInfo)
124+
require.NoError(t, err)
125+
childTableForeignKeyRecordManager.RemoveForeignKeys(childTableInfo, childTableIndexI1)
126+
require.Len(t, childTableForeignKeyRecordManager.GetFKRecordMap(), 0)
127+
require.Len(t, childTableForeignKeyRecordManager.GetReferredFKRecordMap(), 0)
128+
require.Len(t, parentTableForeignKeyRecordManager.GetFKRecordMap(), 0)
129+
require.Len(t, parentTableForeignKeyRecordManager.GetReferredFKRecordMap(), 0)
130+
foreignKeyRecordManager.Merge(childTableForeignKeyRecordManager)
131+
foreignKeyRecordManager.Merge(parentTableForeignKeyRecordManager)
132+
require.Len(t, foreignKeyRecordManager.GetFKRecordMap(), 0)
133+
}
134+
135+
func TestForeignKeyRecordManagerForPK2(t *testing.T) {
136+
ctx := context.Background()
137+
s := utiltest.CreateRestoreSchemaSuite(t)
138+
tk := testkit.NewTestKit(t, s.Mock.Storage)
139+
140+
tk.MustExec("create table test.parent (id int, pid int, index i1(id, pid))")
141+
tk.MustExec("create table test.child (id int, pid int primary key, index i1(pid, id), foreign key (pid) references test.parent (id) on delete cascade)")
142+
143+
infoSchema := s.Mock.InfoSchema()
144+
childTableInfo, err := infoSchema.TableInfoByName(ast.NewCIStr("test"), ast.NewCIStr("child"))
145+
require.NoError(t, err)
146+
parentTableInfo, err := infoSchema.TableInfoByName(ast.NewCIStr("test"), ast.NewCIStr("parent"))
147+
require.NoError(t, err)
148+
parentTableIndexI1 := parentTableInfo.Indices[0]
149+
150+
foreignKeyRecordManager := ingestrec.NewForeignKeyRecordManager()
151+
childTableForeignKeyRecordManager, err := ingestrec.NewForeignKeyRecordManagerForTables(ctx, infoSchema, ast.NewCIStr("test"), childTableInfo)
152+
require.NoError(t, err)
153+
parentTableForeignKeyRecordManager, err := ingestrec.NewForeignKeyRecordManagerForTables(ctx, infoSchema, ast.NewCIStr("test"), parentTableInfo)
154+
require.NoError(t, err)
155+
parentTableForeignKeyRecordManager.RemoveForeignKeys(parentTableInfo, parentTableIndexI1)
156+
require.Len(t, childTableForeignKeyRecordManager.GetFKRecordMap(), 0)
157+
require.Len(t, childTableForeignKeyRecordManager.GetReferredFKRecordMap(), 0)
158+
require.Len(t, parentTableForeignKeyRecordManager.GetFKRecordMap(), 0)
159+
require.Len(t, parentTableForeignKeyRecordManager.GetReferredFKRecordMap(), 0)
160+
foreignKeyRecordManager.Merge(childTableForeignKeyRecordManager)
161+
foreignKeyRecordManager.Merge(parentTableForeignKeyRecordManager)
162+
require.Len(t, foreignKeyRecordManager.GetFKRecordMap(), 0)
163+
}

0 commit comments

Comments
 (0)