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

executor: fix issue of execute prepared DML panic with foreign key cascade #39734

Merged
merged 11 commits into from
Dec 9, 2022
18 changes: 18 additions & 0 deletions executor/fktest/foreign_key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2521,3 +2521,21 @@ func TestTableLockInForeignKeyCascade(t *testing.T) {
tk.MustQuery("select * from t1 order by id").Check(testkit.Rows("2", "3"))
tk.MustQuery("select * from t2 order by id").Check(testkit.Rows("2", "3"))
}

func TestForeignKeyIssue39732(t *testing.T) {
store := testkit.CreateMockStore(t)
tk := testkit.NewTestKit(t, store)
tk.MustExec("set @@global.tidb_enable_stmt_summary=1")
tk.MustExec("set @@foreign_key_checks=1")
tk.MustExec("use test")
tk.MustExec("create user 'u1'@'%' identified by '';")
tk.MustExec("GRANT ALL PRIVILEGES ON *.* TO 'u1'@'%'")
err := tk.Session().Auth(&auth.UserIdentity{Username: "u1", Hostname: "localhost", CurrentUser: true, AuthUsername: "u1", AuthHostname: "%"}, nil, []byte("012345678901234567890"))
require.NoError(t, err)
tk.MustExec("create table t1 (id int key, leader int, index(leader), foreign key (leader) references t1(id) ON DELETE CASCADE);")
tk.MustExec("insert into t1 values (1, null), (10, 1), (11, 1), (20, 10)")
tk.MustExec(`prepare stmt1 from 'delete from t1 where id = ?';`)
tk.MustExec(`set @a = 1;`)
tk.MustExec("execute stmt1 using @a;")
tk.MustQuery("select * from t1 order by id").Check(testkit.Rows())
}
13 changes: 3 additions & 10 deletions planner/core/encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func EncodeFlatPlan(flat *FlatPhysicalPlan) string {
return ""
}
failpoint.Inject("mockPlanRowCount", func(val failpoint.Value) {
selectPlan := flat.Main.GetSelectPlan()
selectPlan, _ := flat.Main.GetSelectPlan()
for _, op := range selectPlan {
op.Origin.statsInfo().RowCount = float64(val.(int))
}
Expand Down Expand Up @@ -262,7 +262,7 @@ type planDigester struct {

// NormalizeFlatPlan normalizes a FlatPhysicalPlan and generates plan digest.
func NormalizeFlatPlan(flat *FlatPhysicalPlan) (normalized string, digest *parser.Digest) {
selectPlan := flat.Main.GetSelectPlan()
selectPlan, selectPlanOffset := flat.Main.GetSelectPlan()
if len(selectPlan) == 0 || !selectPlan[0].IsPhysicalPlan {
return "", parser.NewDigest(nil)
}
Expand All @@ -274,18 +274,11 @@ func NormalizeFlatPlan(flat *FlatPhysicalPlan) (normalized string, digest *parse
}()
// assume an operator costs around 30 bytes, preallocate space for them
d.buf.Grow(30 * len(selectPlan))
depthOffset := len(flat.Main) - len(selectPlan)
loop1:
for _, op := range selectPlan {
switch op.Origin.(type) {
case *FKCheck, *FKCascade:
// Generate plan digest doesn't need to contain the foreign key check/cascade plan, so just break the loop.
break loop1
}
taskTypeInfo := plancodec.EncodeTaskTypeForNormalize(op.IsRoot, op.StoreType)
p := op.Origin.(PhysicalPlan)
plancodec.NormalizePlanNode(
int(op.Depth-uint32(depthOffset)),
int(op.Depth-uint32(selectPlanOffset)),
op.Origin.TP(),
taskTypeInfo,
p.ExplainNormalizedInfo(),
Expand Down
22 changes: 17 additions & 5 deletions planner/core/flat_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,22 +55,34 @@ type FlatPhysicalPlan struct {
type FlatPlanTree []*FlatOperator

// GetSelectPlan skips Insert, Delete and Update at the beginning of the FlatPlanTree.
// GetSelectPlan also skips foreign key check/cascade plan.
crazycs520 marked this conversation as resolved.
Show resolved Hide resolved
// Note:
//
// It returns a reference to the original FlatPlanTree, please avoid modifying the returned value.
// It returns a reference to the original FlatPlanTree and the offset, please avoid modifying the returned value.
// Since you get a part of the original slice, you need to adjust the FlatOperator.Depth and FlatOperator.ChildrenIdx when using them.
crazycs520 marked this conversation as resolved.
Show resolved Hide resolved
func (e FlatPlanTree) GetSelectPlan() FlatPlanTree {
func (e FlatPlanTree) GetSelectPlan() (FlatPlanTree, int) {
if len(e) == 0 {
return nil
return nil, 0
}
hasDML := false
for i, op := range e {
switch op.Origin.(type) {
case *Insert, *Delete, *Update:
hasDML = true
default:
return e[i:]
if hasDML {
crazycs520 marked this conversation as resolved.
Show resolved Hide resolved
for j := i; j < len(e); j++ {
switch e[j].Origin.(type) {
// Skip foreign key check/cascade plan, since the later plan doesn't belong to select plan.
case *FKCheck, *FKCascade:
return e[i:j], i
crazycs520 marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
return e[i:], i
}
}
return nil
return nil, 0
}

// FlatOperator is a simplified operator.
Expand Down
2 changes: 1 addition & 1 deletion planner/core/hints.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func GenHintsFromFlatPlan(flat *FlatPhysicalPlan) []*ast.TableOptimizerHint {
nodeTp = utilhint.TypeDelete
}
var hints []*ast.TableOptimizerHint
selectPlan := flat.Main.GetSelectPlan()
selectPlan, _ := flat.Main.GetSelectPlan()
if len(selectPlan) == 0 || !selectPlan[0].IsPhysicalPlan {
return nil
}
Expand Down
2 changes: 2 additions & 0 deletions planner/core/plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ func TestNormalizedPlan(t *testing.T) {
newNormalized, newDigest := core.NormalizeFlatPlan(flat)
require.Equal(t, normalized, newNormalized)
require.Equal(t, digest, newDigest)
// Test for GenHintsFromFlatPlan won't panic.
core.GenHintsFromFlatPlan(flat)

normalizedPlan, err := plancodec.DecodeNormalizedPlan(normalized)
normalizedPlanRows := getPlanRows(normalizedPlan)
Expand Down