// Copyright 2022 PingCAP, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package core_test import ( "context" "errors" "fmt" "math/rand" "strconv" "strings" "testing" "github.com/pingcap/tidb/expression" "github.com/pingcap/tidb/parser/mysql" plannercore "github.com/pingcap/tidb/planner/core" "github.com/pingcap/tidb/testkit" "github.com/pingcap/tidb/types" "github.com/pingcap/tidb/util" "github.com/stretchr/testify/require" ) type mockParameterizer struct { action string } func (mp *mockParameterizer) Parameterize(originSQL string) (paramSQL string, params []expression.Expression, ok bool, err error) { switch mp.action { case "error": return "", nil, false, errors.New("error") case "not_support": return "", nil, false, nil } // only support SQL like 'select * from t where col {op} {int} and ...' prefix := "select * from t where " if !strings.HasPrefix(originSQL, prefix) { return "", nil, false, nil } buf := make([]byte, 0, 32) buf = append(buf, prefix...) for i, condStr := range strings.Split(originSQL[len(prefix):], "and") { if i > 0 { buf = append(buf, " and "...) } tmp := strings.Split(strings.TrimSpace(condStr), " ") if len(tmp) != 3 { // col {op} {val} return "", nil, false, nil } buf = append(buf, tmp[0]...) buf = append(buf, tmp[1]...) buf = append(buf, '?') intParam, err := strconv.Atoi(tmp[2]) if err != nil { return "", nil, false, nil } params = append(params, &expression.Constant{Value: types.NewDatum(intParam), RetType: types.NewFieldType(mysql.TypeLong)}) } return string(buf), params, true, nil } func TestInitLRUWithSystemVar(t *testing.T) { store := testkit.CreateMockStore(t) tk := testkit.NewTestKit(t, store) tk.MustExec("set @@session.tidb_prepared_plan_cache_size = 0") // MinValue: 1 tk.MustQuery("select @@session.tidb_prepared_plan_cache_size").Check(testkit.Rows("1")) sessionVar := tk.Session().GetSessionVars() lru := plannercore.NewLRUPlanCache(uint(sessionVar.PreparedPlanCacheSize), 0, 0, tk.Session()) require.NotNil(t, lru) } func TestIssue40296(t *testing.T) { store := testkit.CreateMockStore(t) tk := testkit.NewTestKit(t, store) tk.MustExec(`create database test_40296`) tk.MustExec(`use test_40296`) tk.MustExec(`CREATE TABLE IDT_MULTI15880STROBJSTROBJ ( COL1 enum('aa','bb','cc','dd','ff','gg','kk','ll','mm','ee') DEFAULT NULL, COL2 decimal(20,0) DEFAULT NULL, COL3 date DEFAULT NULL, KEY U_M_COL4 (COL1,COL2), KEY U_M_COL5 (COL3,COL2))`) tk.MustExec(`insert into IDT_MULTI15880STROBJSTROBJ values("ee", -9605492323393070105, "0850-03-15")`) tk.MustExec(`set session tidb_enable_non_prepared_plan_cache=on`) tk.MustQuery(`select * from IDT_MULTI15880STROBJSTROBJ where col1 in ("dd", "dd") or col2 = 9923875910817805958 or col3 = "9994-11-11"`).Check( testkit.Rows()) tk.MustQuery(`select * from IDT_MULTI15880STROBJSTROBJ where col1 in ("aa", "aa") or col2 = -9605492323393070105 or col3 = "0005-06-22"`).Check( testkit.Rows("ee -9605492323393070105 0850-03-15")) tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("0")) // unary operator '-' is not supported now. } func TestNonPreparedPlanCacheWithExplain(t *testing.T) { store := testkit.CreateMockStore(t) tk := testkit.NewTestKit(t, store) tk.MustExec(`use test`) tk.MustExec("create table t(a int)") tk.MustExec("set tidb_enable_non_prepared_plan_cache=1") tk.MustExec("select * from t where a=1") // cache this plan tk.MustQuery("explain select * from t where a=2").Check(testkit.Rows( `Selection_8 10.00 root eq(test.t.a, 2)`, `└─TableReader_7 10.00 root data:Selection_6`, ` └─Selection_6 10.00 cop[tikv] eq(test.t.a, 2)`, ` └─TableFullScan_5 10000.00 cop[tikv] table:t keep order:false, stats:pseudo`)) tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("1")) tk.MustQuery("explain format=verbose select * from t where a=2").Check(testkit.Rows( `Selection_8 10.00 169474.57 root eq(test.t.a, 2)`, `└─TableReader_7 10.00 168975.57 root data:Selection_6`, ` └─Selection_6 10.00 2534000.00 cop[tikv] eq(test.t.a, 2)`, ` └─TableFullScan_5 10000.00 2035000.00 cop[tikv] table:t keep order:false, stats:pseudo`)) tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("1")) tk.MustQuery("explain analyze select * from t where a=2").CheckAt([]int{0, 1, 2, 3}, [][]interface{}{ {"Selection_8", "10.00", "0", "root"}, {"└─TableReader_7", "10.00", "0", "root"}, {" └─Selection_6", "10.00", "0", "cop[tikv]"}, {" └─TableFullScan_5", "10000.00", "0", "cop[tikv]"}, }) tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("1")) } func TestNonPreparedPlanCacheFallback(t *testing.T) { store := testkit.CreateMockStore(t) tk := testkit.NewTestKit(t, store) tk.MustExec(`use test`) tk.MustExec(`create table t (a int)`) for i := 0; i < 5; i++ { tk.MustExec(fmt.Sprintf("insert into t values (%v)", i)) } tk.MustExec("set tidb_enable_non_prepared_plan_cache=1") // inject a fault to GeneratePlanCacheStmtWithAST ctx := context.WithValue(context.Background(), "____GeneratePlanCacheStmtWithASTErr", struct{}{}) tk.MustQueryWithContext(ctx, "select * from t where a in (1, 2)").Sort().Check(testkit.Rows("1", "2")) tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("0")) // cannot generate PlanCacheStmt tk.MustQueryWithContext(ctx, "select * from t where a in (1, 3)").Sort().Check(testkit.Rows("1", "3")) tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("0")) // cannot generate PlanCacheStmt tk.MustQuery("select * from t where a in (1, 2)").Sort().Check(testkit.Rows("1", "2")) tk.MustQuery("select * from t where a in (1, 3)").Sort().Check(testkit.Rows("1", "3")) tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("1")) // no error // inject a fault to GetPlanFromSessionPlanCache tk.MustQuery("select * from t where a=1").Check(testkit.Rows("1")) // cache this plan tk.MustQuery("select * from t where a=2").Check(testkit.Rows("2")) // plan from cache tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("1")) ctx = context.WithValue(context.Background(), "____GetPlanFromSessionPlanCacheErr", struct{}{}) tk.MustQueryWithContext(ctx, "select * from t where a=3").Check(testkit.Rows("3")) tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("0")) // fallback to the normal opt-path tk.MustQueryWithContext(ctx, "select * from t where a=4").Check(testkit.Rows("4")) tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("0")) // fallback to the normal opt-path tk.MustQueryWithContext(context.Background(), "select * from t where a=0").Check(testkit.Rows("0")) tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("1")) // use the cached plan if no error // inject a fault to RestoreASTWithParams ctx = context.WithValue(context.Background(), "____GetPlanFromSessionPlanCacheErr", struct{}{}) ctx = context.WithValue(ctx, "____RestoreASTWithParamsErr", struct{}{}) _, err := tk.ExecWithContext(ctx, "select * from t where a=1") require.NotNil(t, err) } func TestNonPreparedPlanCacheBasically(t *testing.T) { store := testkit.CreateMockStore(t) tk := testkit.NewTestKit(t, store) tk.MustExec(`use test`) tk.MustExec(`create table t (a int, b int, c int, d int, primary key(a), key(b), key(c, d))`) for i := 0; i < 20; i++ { tk.MustExec(fmt.Sprintf("insert into t values (%v, %v, %v, %v)", i, rand.Intn(20), rand.Intn(20), rand.Intn(20))) } queries := []string{ "select * from t where a<10", "select * from t where a<13 and b<15", "select * from t where b=13", "select * from t where c<8", "select * from t where d>8", "select * from t where c=8 and d>10", "select * from t where a<12 and b<13 and c<12 and d>2", } for _, query := range queries { tk.MustExec(`set tidb_enable_non_prepared_plan_cache=0`) resultNormal := tk.MustQuery(query).Sort() tk.MustQuery(`select @@last_plan_from_cache`).Check(testkit.Rows("0")) tk.MustExec(`set tidb_enable_non_prepared_plan_cache=1`) tk.MustQuery(query) // first process tk.MustQuery(query).Sort().Check(resultNormal.Rows()) // equal to the result without plan-cache tk.MustQuery(`select @@last_plan_from_cache`).Check(testkit.Rows("1")) // this plan is from plan-cache } } func TestIssue38269(t *testing.T) { store := testkit.CreateMockStore(t) tk := testkit.NewTestKit(t, store) tk.MustExec(`set @@tidb_enable_prepared_plan_cache=1`) tk.MustExec("set @@tidb_enable_collect_execution_info=0") tk.MustExec("use test") tk.MustExec("create table t1(a int)") tk.MustExec("create table t2(a int, b int, c int, index idx(a, b))") tk.MustExec("prepare stmt1 from 'select /*+ inl_join(t2) */ * from t1 join t2 on t1.a = t2.a where t2.b in (?, ?, ?)'") tk.MustExec("set @a = 10, @b = 20, @c = 30, @d = 40, @e = 50, @f = 60") tk.MustExec("execute stmt1 using @a, @b, @c") tk.MustExec("execute stmt1 using @d, @e, @f") tkProcess := tk.Session().ShowProcess() ps := []*util.ProcessInfo{tkProcess} tk.Session().SetSessionManager(&testkit.MockSessionManager{PS: ps}) rows := tk.MustQuery(fmt.Sprintf("explain for connection %d", tkProcess.ID)).Rows() require.Contains(t, rows[6][4], "range: decided by [eq(test.t2.a, test.t1.a) in(test.t2.b, 40, 50, 60)]") } func TestIssue38533(t *testing.T) { store := testkit.CreateMockStore(t) tk := testkit.NewTestKit(t, store) tk.MustExec("use test") tk.MustExec("create table t (a int, key (a))") tk.MustExec(`prepare st from "select /*+ use_index(t, a) */ a from t where a=? and a=?"`) tk.MustExec(`set @a=1`) tk.MustExec(`execute st using @a, @a`) tkProcess := tk.Session().ShowProcess() ps := []*util.ProcessInfo{tkProcess} tk.Session().SetSessionManager(&testkit.MockSessionManager{PS: ps}) plan := tk.MustQuery(fmt.Sprintf("explain for connection %d", tkProcess.ID)).Rows() require.True(t, strings.Contains(plan[1][0].(string), "RangeScan")) // range-scan instead of full-scan tk.MustExec(`execute st using @a, @a`) tk.MustExec(`execute st using @a, @a`) tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("0")) } func TestInvalidRange(t *testing.T) { store := testkit.CreateMockStore(t) tk := testkit.NewTestKit(t, store) tk.MustExec("use test") tk.MustExec("create table t (a int, key(a))") tk.MustExec("prepare st from 'select * from t where a>? and a 123 tk.MustQuery("show warnings").Check(testkit.Rows("Warning 1105 skip plan-cache: '123' may be converted to INT")) tk.MustExec("prepare stmt from 'select * from t where a=? and a=?'") tk.MustExec("set @a=1, @b=1") tk.MustExec("execute stmt using @a, @b") // a=1 and a=1 -> a=1 tk.MustQuery("show warnings").Check(testkit.Rows("Warning 1105 skip plan-cache: some parameters may be overwritten")) } func TestIssue40224(t *testing.T) { store := testkit.CreateMockStore(t) tk := testkit.NewTestKit(t, store) tk.MustExec("use test") tk.MustExec("create table t (a int, key(a))") tk.MustExec("prepare st from 'select a from t where a in (?, ?)'") tk.MustExec("set @a=1.0, @b=2.0") tk.MustExec("execute st using @a, @b") tk.MustQuery("show warnings").Check(testkit.Rows("Warning 1105 skip plan-cache: '1.0' may be converted to INT")) tk.MustExec("execute st using @a, @b") tkProcess := tk.Session().ShowProcess() ps := []*util.ProcessInfo{tkProcess} tk.Session().SetSessionManager(&testkit.MockSessionManager{PS: ps}) tk.MustQuery(fmt.Sprintf("explain for connection %d", tkProcess.ID)).CheckAt([]int{0}, [][]interface{}{ {"IndexReader_6"}, {"└─IndexRangeScan_5"}, // range scan not full scan }) tk.MustExec("set @a=1, @b=2") tk.MustExec("execute st using @a, @b") tk.MustQuery("show warnings").Check(testkit.Rows()) // no warning for INT values tk.MustExec("execute st using @a, @b") tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("1")) // cacheable for INT tk.MustExec("execute st using @a, @b") tk.MustQuery(fmt.Sprintf("explain for connection %d", tkProcess.ID)).CheckAt([]int{0}, [][]interface{}{ {"IndexReader_6"}, {"└─IndexRangeScan_5"}, // range scan not full scan }) } func TestIssue40225(t *testing.T) { store := testkit.CreateMockStore(t) tk := testkit.NewTestKit(t, store) tk.MustExec("use test") tk.MustExec("create table t (a int, key(a))") tk.MustExec("prepare st from 'select * from t where a INT) since plan-cache is totally disabled. tk.MustExec("prepare st from 'select * from t where a>?'") tk.MustExec("set @a=1") tk.MustExec("execute st using @a") tk.MustExec("execute st using @a") tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("1")) tk.MustExec("create binding for select * from t where a>1 using select /*+ ignore_plan_cache() */ * from t where a>1") tk.MustExec("execute st using @a") tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("0")) tk.MustExec("execute st using @a") tk.MustQuery("select @@last_plan_from_binding").Check(testkit.Rows("1")) } func TestPlanCacheWithLimit(t *testing.T) { store := testkit.CreateMockStore(t) tk := testkit.NewTestKit(t, store) tk.MustExec("use test") tk.MustExec("drop table if exists t") tk.MustExec("create table t(a int primary key, b int)") testCases := []struct { sql string params []int }{ {"prepare stmt from 'select * from t limit ?'", []int{1}}, {"prepare stmt from 'select * from t limit ?, ?'", []int{1, 2}}, {"prepare stmt from 'delete from t order by a limit ?'", []int{1}}, {"prepare stmt from 'insert into t select * from t order by a desc limit ?'", []int{1}}, {"prepare stmt from 'insert into t select * from t order by a desc limit ?, ?'", []int{1, 2}}, {"prepare stmt from 'update t set a = 1 limit ?'", []int{1}}, {"prepare stmt from '(select * from t order by a limit ?) union (select * from t order by a desc limit ?)'", []int{1, 2}}, {"prepare stmt from 'select * from t where a = ? limit ?, ?'", []int{1, 1, 1}}, {"prepare stmt from 'select * from t where a in (?, ?) limit ?, ?'", []int{1, 2, 1, 1}}, } for idx, testCase := range testCases { tk.MustExec(testCase.sql) var using []string for i, p := range testCase.params { tk.MustExec(fmt.Sprintf("set @a%d = %d", i, p)) using = append(using, fmt.Sprintf("@a%d", i)) } tk.MustExec("execute stmt using " + strings.Join(using, ", ")) tk.MustExec("execute stmt using " + strings.Join(using, ", ")) tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("1")) if idx < 6 { tk.MustExec("set @a0 = 6") tk.MustExec("execute stmt using " + strings.Join(using, ", ")) tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("0")) } } tk.MustExec("prepare stmt from 'select * from t limit ?'") tk.MustExec("set @a = 10001") tk.MustExec("execute stmt using @a") tk.MustQuery("show warnings").Check(testkit.Rows("Warning 1105 skip plan-cache: limit count more than 10000")) } func TestIssue40679(t *testing.T) { store := testkit.CreateMockStore(t) tk := testkit.NewTestKit(t, store) tk.MustExec("use test") tk.MustExec("create table t (a int, key(a));") tk.MustExec("prepare st from 'select * from t use index(a) where a < ?'") tk.MustExec("set @a1=1.1") tk.MustExec("execute st using @a1") tkProcess := tk.Session().ShowProcess() ps := []*util.ProcessInfo{tkProcess} tk.Session().SetSessionManager(&testkit.MockSessionManager{PS: ps}) rows := tk.MustQuery(fmt.Sprintf("explain for connection %d", tkProcess.ID)).Rows() require.True(t, strings.Contains(rows[1][0].(string), "RangeScan")) // RangeScan not FullScan tk.MustExec("execute st using @a1") tk.MustQuery("show warnings").Check(testkit.Rows("Warning 1105 skip plan-cache: '1.1' may be converted to INT")) }