Skip to content

Commit

Permalink
planner: add more test for plan cache (#46640)
Browse files Browse the repository at this point in the history
ref #36598
  • Loading branch information
qw4990 authored Sep 5, 2023
1 parent 5cbcc86 commit d4d025c
Showing 1 changed file with 297 additions and 0 deletions.
297 changes: 297 additions & 0 deletions planner/core/plan_cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1761,6 +1761,303 @@ func TestIssue41828(t *testing.T) {
tk.MustQuery(`execute stmt using @a,@b,@c,@d,@e,@f`).Check(testkit.Rows())
}

func convertQueryToPrepExecStmt(q string) (normalQuery, prepStmt string, parameters []string) {
// select ... from t where a = #?1# and b = #?2#
normalQuery = strings.ReplaceAll(q, "#", "")
normalQuery = strings.ReplaceAll(normalQuery, "?", "")
vs := strings.Split(q, "#")
for i := 0; i < len(vs); i++ {
if len(vs[i]) == 0 {
continue
}
if vs[i][0] == '?' {
parameters = append(parameters, vs[i][1:])
vs[i] = "?"
}
}
return normalQuery, fmt.Sprintf(`prepare st from '%v'`, strings.Join(vs, "")), parameters
}

func planCachePointGetPrepareData(tk *testkit.TestKit) {
tk.MustExec(`use test`)
tk.MustExec(`drop table if exists t1`)
tk.MustExec(`drop table if exists t2`)
t := func() string {
types := []string{"int", "varchar(10)", "decimal(10, 2)", "double"}
return types[rand.Intn(len(types))]
}
tk.MustExec(fmt.Sprintf(`create table t1 (a %v, b %v, c %v, d %v, primary key(a), unique key(b), unique key(c), unique key(c))`, t(), t(), t(), t()))
tk.MustExec(fmt.Sprintf(`create table t2 (a %v, b %v, c %v, d %v, primary key(a, b), unique key(c, d))`, t(), t(), t(), t()))

var vals []string
for i := 0; i < 50; i++ {
vals = append(vals, fmt.Sprintf("('%v.%v', '%v.%v', '%v.%v', '%v.%v')",
i-20, rand.Intn(5),
i-20, rand.Intn(5),
i-20, rand.Intn(5),
i-20, rand.Intn(5)))
}
tk.MustExec(fmt.Sprintf(`insert into t1 values %v`, strings.Join(vals, ",")))
tk.MustExec(`insert into t1 values ('31', '31', null, null), ('32', null, 32, null)`)
tk.MustExec(fmt.Sprintf(`insert into t2 values %v`, strings.Join(vals, ",")))
}

func planCachePointGetQueries(isNonPrep bool) []string {
v := func() string {
var vStr string
switch rand.Intn(3) {
case 0: // int
vStr = fmt.Sprintf("%v", rand.Intn(50)-20)
case 1: // double
vStr = fmt.Sprintf("%v.%v", rand.Intn(50)-20, rand.Intn(100))
default: // string
vStr = fmt.Sprintf("'%v.%v'", rand.Intn(50)-20, rand.Intn(100))
}
if !isNonPrep {
vStr = fmt.Sprintf("#?%v#", vStr)
}
return vStr
}
f := func() string {
cols := []string{"a", "b", "c", "d"}
col := cols[rand.Intn(len(cols))]
ops := []string{"=", ">", "<", ">=", "<=", "in", "is null"}
op := ops[rand.Intn(len(ops))]
if op == "in" {
return fmt.Sprintf("%v %v (%v, %v, %v)", col, op, v(), v(), v())
} else if op == "is null" {
return fmt.Sprintf("%v %v", col, op)
} else {
return fmt.Sprintf("%v %v %v", col, op, v())
}
}
var queries []string
for i := 0; i < 50; i++ {
queries = append(queries, fmt.Sprintf("select * from t1 where %v", f()))
queries = append(queries, fmt.Sprintf("select * from t1 where %v and %v", f(), f()))
queries = append(queries, fmt.Sprintf("select * from t1 where %v and %v and %v", f(), f(), f()))
queries = append(queries, fmt.Sprintf("select * from t1 where %v and %v and %v and %v", f(), f(), f(), f()))
queries = append(queries, fmt.Sprintf("select * from t1 where %v and %v or %v", f(), f(), f()))
queries = append(queries, fmt.Sprintf("select * from t1 where %v and %v or %v and %v", f(), f(), f(), f()))
queries = append(queries, fmt.Sprintf("select * from t2 where %v", f()))
queries = append(queries, fmt.Sprintf("select * from t2 where %v and %v", f(), f()))
queries = append(queries, fmt.Sprintf("select * from t2 where %v and %v and %v", f(), f(), f()))
queries = append(queries, fmt.Sprintf("select * from t2 where %v and %v and %v and %v", f(), f(), f(), f()))
queries = append(queries, fmt.Sprintf("select * from t2 where %v and %v or %v", f(), f(), f()))
queries = append(queries, fmt.Sprintf("select * from t2 where %v and %v or %v and %v", f(), f(), f(), f()))
}
return queries
}

func planCacheIntConvertQueries(isNonPrep bool) []string {
cols := []string{"a", "b", "c", "d"}
ops := []string{"=", ">", "<", ">=", "<=", "in", "is null"}
v := func() string {
var val string
switch rand.Intn(3) {
case 0:
val = fmt.Sprintf("%v", 2000+rand.Intn(20)-10)
case 1:
val = fmt.Sprintf("%v.0", 2000+rand.Intn(20)-10)
default:
val = fmt.Sprintf("'%v'", 2000+rand.Intn(20)-10)
}
if !isNonPrep {
val = fmt.Sprintf("#?%v#", val)
}
return val
}
f := func() string {
col := cols[rand.Intn(len(cols))]
op := ops[rand.Intn(len(ops))]
if op == "is null" {
return fmt.Sprintf("%v is null", col)
} else if op == "in" {
if rand.Intn(2) == 0 {
return fmt.Sprintf("%v in (%v)", col, v())
}
return fmt.Sprintf("%v in (%v, %v, %v)", col, v(), v(), v())
}
return fmt.Sprintf("%v %v %v", col, op, v())
}
fields := func() string {
var fs []string
for _, f := range []string{"a", "b", "c", "d"} {
if rand.Intn(4) == 0 {
continue
}
fs = append(fs, f)
}
if len(fs) == 0 {
return "*"
}
return strings.Join(fs, ", ")
}
var queries []string
for i := 0; i < 50; i++ {
queries = append(queries, fmt.Sprintf("select %v from t where %v", fields(), f()))
queries = append(queries, fmt.Sprintf("select %v from t where %v and %v", fields(), f(), f()))
queries = append(queries, fmt.Sprintf("select %v from t where %v and %v and %v", fields(), f(), f(), f()))
queries = append(queries, fmt.Sprintf("select %v from t where %v or %v", fields(), f(), f()))
queries = append(queries, fmt.Sprintf("select %v from t where %v or %v or %v", fields(), f(), f(), f()))
queries = append(queries, fmt.Sprintf("select %v from t where %v and %v or %v", fields(), f(), f(), f()))
}
return queries
}

func planCacheIntConvertPrepareData(tk *testkit.TestKit) {
tk.MustExec(`use test`)
tk.MustExec(`drop table if exists t`)
tk.MustExec(`create table t(a int, b year, c double, d varchar(16), key(a), key(b), key(c))`)
vals := make([]string, 0, 50)
for i := 0; i < 50; i++ {
a := fmt.Sprintf("%v", 2000+rand.Intn(20)-10)
if rand.Intn(10) == 0 {
a = "null"
}
b := fmt.Sprintf("%v", 2000+rand.Intn(20)-10)
if rand.Intn(10) == 0 {
b = "null"
}
c := fmt.Sprintf("%v.0", 2000+rand.Intn(20)-10)
if rand.Intn(10) == 0 {
c = "null"
}
d := fmt.Sprintf("'%v'", 2000+rand.Intn(20)-10)
if rand.Intn(10) == 0 {
d = "null"
}
vals = append(vals, fmt.Sprintf("(%s, %s, %s, %s)", a, b, c, d))
}
tk.MustExec("insert into t values " + strings.Join(vals, ","))
}

func planCacheIndexMergeQueries(isNonPrep bool) []string {
ops := []string{"=", ">", "<", ">=", "<=", "in", "mod", "is null"}
f := func(col string) string {
n := rand.Intn(20) - 10
nStr := fmt.Sprintf("%v", n)
if !isNonPrep {
nStr = fmt.Sprintf("#?%v#", n)
}

op := ops[rand.Intn(len(ops))]
if op == "in" {
switch rand.Intn(3) {
case 0: // 1 element
return fmt.Sprintf("%s %s (%s)", col, op, nStr)
case 1: // multiple same elements
return fmt.Sprintf("%s %s (%s, %s, %s)", col, op, nStr, nStr, nStr)
default: // multiple different elements
if isNonPrep {
return fmt.Sprintf("%s %s (%d, %d)", col, op, n, n+1)
}
return fmt.Sprintf("%s %s (#?%d#, #?%d#)", col, op, n, n+1)
}
} else if op == "mod" { // this filter cannot be used to build range
return fmt.Sprintf("mod(%s, %s)=0", col, nStr)
} else if op == "is null" {
return fmt.Sprintf("%s %s", col, op)
} else {
return fmt.Sprintf("%s %s %s", col, op, nStr)
}
}
fields := func() string {
switch rand.Intn(5) {
case 0:
return "a"
case 1:
return "a, b"
case 2:
return "a, c"
case 3:
return "d"
default:
return "*"
}
}
var queries []string
for i := 0; i < 50; i++ {
queries = append(queries, fmt.Sprintf("select /*+ use_index_merge(t, a, b) */ %s from t where %s and %s", fields(), f("a"), f("b")))
queries = append(queries, fmt.Sprintf("select /*+ use_index_merge(t, a, c) */ %s from t where %s and %s", fields(), f("a"), f("c")))
queries = append(queries, fmt.Sprintf("select /*+ use_index_merge(t, a, b, c) */ %s from t where %s and %s and %s", fields(), f("a"), f("b"), f("c")))
queries = append(queries, fmt.Sprintf("select /*+ use_index_merge(t, a, b) */ %s from t where %s or %s", fields(), f("a"), f("b")))
queries = append(queries, fmt.Sprintf("select /*+ use_index_merge(t, a, c) */ %s from t where %s or %s", fields(), f("a"), f("c")))
queries = append(queries, fmt.Sprintf("select /*+ use_index_merge(t, a, b, c) */ %s from t where %s or %s or %s", fields(), f("a"), f("b"), f("c")))
queries = append(queries, fmt.Sprintf("select /*+ use_index_merge(t, a, b) */ %s from t where %s and %s and %s", fields(), f("a"), f("a"), f("b")))
queries = append(queries, fmt.Sprintf("select /*+ use_index_merge(t, a, c) */ %s from t where %s and %s and %s", fields(), f("a"), f("c"), f("c")))
queries = append(queries, fmt.Sprintf("select /*+ use_index_merge(t, a, b, c) */ %s from t where %s and %s and %s and %s", fields(), f("a"), f("b"), f("b"), f("c")))
queries = append(queries, fmt.Sprintf("select /*+ use_index_merge(t, a, b) */ %s from t where (%s and %s) or %s", fields(), f("a"), f("a"), f("b")))
queries = append(queries, fmt.Sprintf("select /*+ use_index_merge(t, a, c) */ %s from t where %s or (%s and %s)", fields(), f("a"), f("c"), f("c")))
queries = append(queries, fmt.Sprintf("select /*+ use_index_merge(t, a, b, c) */ %s from t where %s or (%s and %s) or %s", fields(), f("a"), f("b"), f("b"), f("c")))
}
return queries
}

func planCacheIndexMergePrepareData(tk *testkit.TestKit) {
tk.MustExec("use test")
tk.MustExec("drop table if exists t")
tk.MustExec("create table t(a int, b int, c int, d int, key(a), key(b), key(c))")
vals := make([]string, 0, 50)
v := func() string {
if rand.Intn(10) == 0 {
return "null"
}
return fmt.Sprintf("%d", rand.Intn(20)-10)
}
for i := 0; i < 50; i++ {
vals = append(vals, fmt.Sprintf("(%s, %s, %s, %s)", v(), v(), v(), v()))
}
tk.MustExec("insert into t values " + strings.Join(vals, ","))
}

func TestPlanCacheRandomCases(t *testing.T) {
store := testkit.CreateMockStore(t)
tk := testkit.NewTestKit(t, store)
tk.MustExec("use test")

testRandomPlanCacheCases(t, planCacheIndexMergePrepareData, planCacheIndexMergeQueries)
testRandomPlanCacheCases(t, planCacheIntConvertPrepareData, planCacheIntConvertQueries)
testRandomPlanCacheCases(t, planCachePointGetPrepareData, planCachePointGetQueries)
}

func testRandomPlanCacheCases(t *testing.T,
prepFunc func(tk *testkit.TestKit),
queryFunc func(isNonPrep bool) []string) {
store := testkit.CreateMockStore(t)
tk := testkit.NewTestKit(t, store)
prepFunc(tk)

// prepared plan cache
for _, q := range queryFunc(true) {
tk.MustExec("set tidb_enable_non_prepared_plan_cache=0")
result1 := tk.MustQuery(q).Sort()
tk.MustExec("set tidb_enable_non_prepared_plan_cache=1")
result2 := tk.MustQuery(q).Sort()
require.True(t, result1.Equal(result2.Rows()))
}

// non prepared plan cache
for _, q := range queryFunc(false) {
q, prepStmt, parameters := convertQueryToPrepExecStmt(q)
result1 := tk.MustQuery(q).Sort()
tk.MustExec(prepStmt)
var xs []string
for i, p := range parameters {
tk.MustExec(fmt.Sprintf("set @x%d = %s", i, p))
xs = append(xs, fmt.Sprintf("@x%d", i))
}
var execStmt string
if len(xs) == 0 {
execStmt = "execute st"
} else {
execStmt = fmt.Sprintf("execute st using %s", strings.Join(xs, ", "))
}
result2 := tk.MustQuery(execStmt).Sort()
require.True(t, result1.Equal(result2.Rows()))
}
}

func TestPlanCacheSubquerySPMEffective(t *testing.T) {
store := testkit.CreateMockStore(t)
tk := testkit.NewTestKit(t, store)
Expand Down

0 comments on commit d4d025c

Please sign in to comment.