From 321ebe836b297a166add40e423fb14d0a680dc05 Mon Sep 17 00:00:00 2001 From: Yuanjia Zhang Date: Sat, 28 Jan 2023 14:49:53 +0800 Subject: [PATCH] This is an automated cherry-pick of #40686 Signed-off-by: ti-chi-bot --- expression/builtin_compare.go | 25 +- planner/core/expression_rewriter.go | 5 + planner/core/plan_cache_test.go | 524 ++++++++++++++++++++++++++++ 3 files changed, 552 insertions(+), 2 deletions(-) create mode 100644 planner/core/plan_cache_test.go diff --git a/expression/builtin_compare.go b/expression/builtin_compare.go index 1c72eaa012149..4adbb7a4f1ebe 100644 --- a/expression/builtin_compare.go +++ b/expression/builtin_compare.go @@ -1565,12 +1565,11 @@ func (c *compareFunctionClass) refineArgs(ctx sessionctx.Context, args []Express arg0Type, arg1Type := args[0].GetType(), args[1].GetType() arg0IsInt := arg0Type.EvalType() == types.ETInt arg1IsInt := arg1Type.EvalType() == types.ETInt - arg0IsString := arg0Type.EvalType() == types.ETString - arg1IsString := arg1Type.EvalType() == types.ETString arg0, arg0IsCon := args[0].(*Constant) arg1, arg1IsCon := args[1].(*Constant) isExceptional, finalArg0, finalArg1 := false, args[0], args[1] isPositiveInfinite, isNegativeInfinite := false, false +<<<<<<< HEAD if MaybeOverOptimized4PlanCache(ctx, args) { // To keep the result be compatible with MySQL, refine `int non-constant str constant` // here and skip this refine operation in all other cases for safety. @@ -1584,8 +1583,15 @@ func (c *compareFunctionClass) refineArgs(ctx sessionctx.Context, args []Express // We should remove the mutable constant for correctness, because its value may be changed. RemoveMutableConst(ctx, args) } +======= +>>>>>>> 465ab74532 (planner: skip the plan cache if non-int values are converted into int when optimization (#40686)) // int non-constant [cmp] non-int constant if arg0IsInt && !arg0IsCon && !arg1IsInt && arg1IsCon { + if MaybeOverOptimized4PlanCache(ctx, []Expression{arg1}) { + ctx.GetSessionVars().StmtCtx.SetSkipPlanCache(errors.Errorf("skip plan-cache: '%v' may be converted to INT", arg1.String())) + RemoveMutableConst(ctx, args) + } + arg1, isExceptional = RefineComparedConstant(ctx, *arg0Type, arg1, c.op) // Why check not null flag // eg: int_col > const_val(which is less than min_int32) @@ -1613,6 +1619,11 @@ func (c *compareFunctionClass) refineArgs(ctx sessionctx.Context, args []Express } // non-int constant [cmp] int non-constant if arg1IsInt && !arg1IsCon && !arg0IsInt && arg0IsCon { + if MaybeOverOptimized4PlanCache(ctx, []Expression{arg0}) { + ctx.GetSessionVars().StmtCtx.SetSkipPlanCache(errors.Errorf("skip plan-cache: '%v' may be converted to INT", arg0.String())) + RemoveMutableConst(ctx, args) + } + arg0, isExceptional = RefineComparedConstant(ctx, *arg1Type, arg0, symmetricOp[c.op]) if !isExceptional || (isExceptional && mysql.HasNotNullFlag(arg1Type.GetFlag())) { finalArg0 = arg0 @@ -1630,6 +1641,11 @@ func (c *compareFunctionClass) refineArgs(ctx sessionctx.Context, args []Express } // int constant [cmp] year type if arg0IsCon && arg0IsInt && arg1Type.GetType() == mysql.TypeYear && !arg0.Value.IsNull() { + if MaybeOverOptimized4PlanCache(ctx, []Expression{arg0}) { + ctx.GetSessionVars().StmtCtx.SetSkipPlanCache(errors.Errorf("skip plan-cache: '%v' may be converted to YEAR", arg0.String())) + RemoveMutableConst(ctx, args) + } + adjusted, failed := types.AdjustYear(arg0.Value.GetInt64(), false) if failed == nil { arg0.Value.SetInt64(adjusted) @@ -1638,6 +1654,11 @@ func (c *compareFunctionClass) refineArgs(ctx sessionctx.Context, args []Express } // year type [cmp] int constant if arg1IsCon && arg1IsInt && arg0Type.GetType() == mysql.TypeYear && !arg1.Value.IsNull() { + if MaybeOverOptimized4PlanCache(ctx, []Expression{arg1}) { + ctx.GetSessionVars().StmtCtx.SetSkipPlanCache(errors.Errorf("skip plan-cache: '%v' may be converted to YEAR", arg1.String())) + RemoveMutableConst(ctx, args) + } + adjusted, failed := types.AdjustYear(arg1.Value.GetInt64(), false) if failed == nil { arg1.Value.SetInt64(adjusted) diff --git a/planner/core/expression_rewriter.go b/planner/core/expression_rewriter.go index 3026574d8e424..a843c8e4a96a6 100644 --- a/planner/core/expression_rewriter.go +++ b/planner/core/expression_rewriter.go @@ -1504,9 +1504,14 @@ func (er *expressionRewriter) inToExpression(lLen int, not bool, tp *types.Field } else { continue } +<<<<<<< HEAD } else if er.sctx.GetSessionVars().StmtCtx.SkipPlanCache { // We should remove the mutable constant for correctness, because its value may be changed. expression.RemoveMutableConst(er.sctx, []expression.Expression{c}) +======= + er.sctx.GetSessionVars().StmtCtx.SetSkipPlanCache(errors.Errorf("skip plan-cache: '%v' may be converted to INT", c.String())) + expression.RemoveMutableConst(er.sctx, args) +>>>>>>> 465ab74532 (planner: skip the plan cache if non-int values are converted into int when optimization (#40686)) } args[i], isExceptional = expression.RefineComparedConstant(er.sctx, *leftFt, c, opcode.EQ) if isExceptional { diff --git a/planner/core/plan_cache_test.go b/planner/core/plan_cache_test.go new file mode 100644 index 0000000000000..8788fa9b72c59 --- /dev/null +++ b/planner/core/plan_cache_test.go @@ -0,0 +1,524 @@ +// 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, plannercore.PickPlanFromBucket, 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")) +}