diff --git a/pkg/expression/util.go b/pkg/expression/util.go index 6e2b67e86a7b3..de4b480cebb67 100644 --- a/pkg/expression/util.go +++ b/pkg/expression/util.go @@ -1348,9 +1348,9 @@ func IsMutableEffectsExpr(expr Expression) bool { return false } -// IsInmutableExpr checks whether this expression only consists of foldable functions and inmutable constants. -// This expression can be evaluated by using `expr.Eval(chunk.Row{})` directly if it's inmutable. -func IsInmutableExpr(expr Expression) bool { +// IsImmutableFunc checks whether this expression only consists of foldable functions. +// This expression can be evaluated by using `expr.Eval(chunk.Row{})` directly and the result won't change if it's immutable. +func IsImmutableFunc(expr Expression) bool { switch x := expr.(type) { case *ScalarFunction: if _, ok := unFoldableFunctions[x.FuncName.L]; ok { @@ -1360,18 +1360,13 @@ func IsInmutableExpr(expr Expression) bool { return false } for _, arg := range x.GetArgs() { - if !IsInmutableExpr(arg) { + if !IsImmutableFunc(arg) { return false } } return true - case *Constant: - if x.DeferredExpr != nil || x.ParamMarker != nil { - return false - } - return true default: - return false + return true } } diff --git a/pkg/planner/core/indexmerge_path.go b/pkg/planner/core/indexmerge_path.go index 9e2083fcfef6e..5cf6e5553daa0 100644 --- a/pkg/planner/core/indexmerge_path.go +++ b/pkg/planner/core/indexmerge_path.go @@ -1307,7 +1307,7 @@ func buildPartialPaths4MVIndex( virColVals = append(virColVals, v) case ast.JSONContains: // (json_contains(a->'$.zip', '[1, 2, 3]') isIntersection = true - virColVals, ok = jsonArrayExpr2Exprs(sctx.GetExprCtx(), sf.GetArgs()[1], jsonType) + virColVals, ok = jsonArrayExpr2Exprs(sctx.GetExprCtx(), ast.JSONContains, sf.GetArgs()[1], jsonType) if !ok || len(virColVals) == 0 { // json_contains(JSON, '[]') is TRUE. If the row has an empty array, it'll not exist on multi-valued index, // but the `json_contains(array, '[]')` is still true, so also don't try to scan on the index. @@ -1323,7 +1323,7 @@ func buildPartialPaths4MVIndex( return nil, false, false, nil } var ok bool - virColVals, ok = jsonArrayExpr2Exprs(sctx.GetExprCtx(), sf.GetArgs()[1-jsonPathIdx], jsonType) + virColVals, ok = jsonArrayExpr2Exprs(sctx.GetExprCtx(), ast.JSONOverlaps, sf.GetArgs()[1-jsonPathIdx], jsonType) if !ok || len(virColVals) == 0 { // forbid empty array for safety return nil, false, false, nil } @@ -1639,8 +1639,12 @@ func checkFilter4MVIndexColumn(sctx PlanContext, filter expression.Expression, i } // jsonArrayExpr2Exprs converts a JsonArray expression to expression list: cast('[1, 2, 3]' as JSON) --> []expr{1, 2, 3} -func jsonArrayExpr2Exprs(sctx expression.EvalContext, jsonArrayExpr expression.Expression, targetType *types.FieldType) ([]expression.Expression, bool) { - if !expression.IsInmutableExpr(jsonArrayExpr) || jsonArrayExpr.GetType().EvalType() != types.ETJson { +func jsonArrayExpr2Exprs(sctx expression.BuildContext, jsonFuncName string, jsonArrayExpr expression.Expression, targetType *types.FieldType) ([]expression.Expression, bool) { + if expression.MaybeOverOptimized4PlanCache(sctx, []expression.Expression{jsonArrayExpr}) { + // skip plan cache and try to generate the best plan in this case. + sctx.GetSessionVars().StmtCtx.SetSkipPlanCache(errors.NewNoStackError(jsonFuncName + " function with immutable parameters can affect index selection")) + } + if !expression.IsImmutableFunc(jsonArrayExpr) || jsonArrayExpr.GetType().EvalType() != types.ETJson { return nil, false } diff --git a/pkg/planner/core/indexmerge_path_test.go b/pkg/planner/core/indexmerge_path_test.go index 1c5f9b3fc9c09..a8499a0b0a311 100644 --- a/pkg/planner/core/indexmerge_path_test.go +++ b/pkg/planner/core/indexmerge_path_test.go @@ -287,7 +287,7 @@ func TestPlanCacheMVIndex(t *testing.T) { tk.MustExec(`set @@tidb_opt_fix_control = "45798:on"`) - check := func(sql string, params ...string) { + check := func(hitCache bool, sql string, params ...string) { sqlWithoutParam := sql var setStmt, usingStmt string for i, p := range params { @@ -306,33 +306,37 @@ func TestPlanCacheMVIndex(t *testing.T) { result.Check(result1.Rows()) result2 := tk.MustQuery(fmt.Sprintf("execute stmt using %v", usingStmt)).Sort() result.Check(result2.Rows()) - tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("1")) + if hitCache { + tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("1")) + } else { + require.Greater(t, len(tk.MustQuery("show warnings").Rows()), 0) // show the reason + } } randV := func(vs ...string) string { return vs[rand.Intn(len(vs))] } for i := 0; i < 50; i++ { - check(`select * from ti where (? member of (short_link)) and (ti.country = ?)`, fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(3))) - check(`select * from ti where (? member of (f_profile_ids) AND (ti.m_item_set_id = ?) AND (ti.country = ?))`, fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(3))) - check(`select * from ti where (? member of (short_link))`, fmt.Sprintf("'%v'", rand.Intn(30))) - check(`select * from ti where (? member of (long_link)) AND (ti.country = ?)`, fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(3))) - check(`select * from ti where (? member of (long_link))`, fmt.Sprintf("'%v'", rand.Intn(30))) - check(`select * from ti where (? member of (f_profile_ids) AND (ti.m_item_set_id = ?) AND (ti.country = ?))`, fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(3))) - check(`select * from ti where (m_id = ? and m_item_id = ? and country = ?) OR (? member of (short_link) and not json_overlaps(product_sources, ?) and country = ?)`, + check(true, `select * from ti where (? member of (short_link)) and (ti.country = ?)`, fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(3))) + check(true, `select * from ti where (? member of (f_profile_ids) AND (ti.m_item_set_id = ?) AND (ti.country = ?))`, fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(3))) + check(true, `select * from ti where (? member of (short_link))`, fmt.Sprintf("'%v'", rand.Intn(30))) + check(true, `select * from ti where (? member of (long_link)) AND (ti.country = ?)`, fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(3))) + check(true, `select * from ti where (? member of (long_link))`, fmt.Sprintf("'%v'", rand.Intn(30))) + check(true, `select * from ti where (? member of (f_profile_ids) AND (ti.m_item_set_id = ?) AND (ti.country = ?))`, fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(3))) + check(true, `select * from ti where (m_id = ? and m_item_id = ? and country = ?) OR (? member of (short_link) and not json_overlaps(product_sources, ?) and country = ?)`, fmt.Sprintf("%v", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(3)), fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(3)), randV(`'["0","1","2"]'`, `'["0"]'`, `'["1","2"]'`)) - check(`select * from ti where ? member of (domains) AND ? member of (signatures) AND ? member of (f_profile_ids) AND ? member of (short_link) AND ? member of (long_link) AND ? member of (f_item_ids)`, + check(true, `select * from ti where ? member of (domains) AND ? member of (signatures) AND ? member of (f_profile_ids) AND ? member of (short_link) AND ? member of (long_link) AND ? member of (f_item_ids)`, fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("%v", rand.Intn(30))) - check(`select * from ti where ? member of (domains) AND ? member of (signatures) AND ? member of (f_profile_ids) AND ? member of (short_link) AND ? member of (long_link) AND ? member of (f_item_ids) AND m_item_id IS NULL AND m_id = ? AND country IS NOT NULL`, + check(true, `select * from ti where ? member of (domains) AND ? member of (signatures) AND ? member of (f_profile_ids) AND ? member of (short_link) AND ? member of (long_link) AND ? member of (f_item_ids) AND m_item_id IS NULL AND m_id = ? AND country IS NOT NULL`, fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("%v", rand.Intn(30)), fmt.Sprintf("%v", rand.Intn(30))) - check(`select * from ti where ? member of (f_profile_ids) AND ? member of (short_link) AND json_overlaps(product_sources, ?)`, fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(30)), randV(`'["0","1","2"]'`, `'["0"]'`, `'["1","2"]'`)) - check(`select * from ti where ? member of (short_link) AND ti.country = "0" AND NOT ? member of (long_link) AND ti.m_item_id = "0"`, fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(30))) - check(`select * from ti WHERE ? member of (domains) AND ? member of (signatures) AND json_contains(f_profile_ids, ?)`, fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(30)), randV(`'["0","1","2"]'`, `'["0"]'`, `'["1","2"]'`)) - check(`select * from ti where ? member of (short_link) AND ? member of (long_link) OR ? member of (f_profile_ids) AND ti.m_item_id = "0" OR ti.m_item_set_id = ?`, fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(30))) - check(`select * from ti where ? member of (domains) OR ? member of (signatures) OR ? member of (f_profile_ids) OR json_contains(f_profile_ids, ?)`, fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("%v", rand.Intn(30)), randV(`"[0,1]"`, `"[0,1,2]"`, `"[0]"`)) - check(`select * from ti WHERE ? member of (domains) OR ? member of (signatures) OR ? member of (long_link) OR json_contains(f_profile_ids, ?)`, fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(30)), randV(`"[0,1]"`, `"[0,1,2]"`, `"[0]"`)) - check(`select * from ti where ? member of (domains) OR ? member of (signatures) OR (? member of (f_profile_ids))`, fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("%v", rand.Intn(30))) - check(`select * from ti where ? member of (domains) OR ? member of (signatures) OR json_overlaps(f_profile_ids, ?)`, fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(30)), randV(`"[0,1]"`, `"[0,1,2]"`, `"[0]"`)) + check(true, `select * from ti where ? member of (f_profile_ids) AND ? member of (short_link) AND json_overlaps(product_sources, ?)`, fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(30)), randV(`'["0","1","2"]'`, `'["0"]'`, `'["1","2"]'`)) + check(true, `select * from ti where ? member of (short_link) AND ti.country = "0" AND NOT ? member of (long_link) AND ti.m_item_id = "0"`, fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(30))) + check(true, `select * from ti where ? member of (short_link) AND ? member of (long_link) OR ? member of (f_profile_ids) AND ti.m_item_id = "0" OR ti.m_item_set_id = ?`, fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(30))) + check(true, `select * from ti where ? member of (domains) OR ? member of (signatures) OR (? member of (f_profile_ids))`, fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("%v", rand.Intn(30))) + check(false, `select * from ti where ? member of (domains) OR ? member of (signatures) OR json_overlaps(f_profile_ids, ?)`, fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(30)), randV(`"[0,1]"`, `"[0,1,2]"`, `"[0]"`)) + check(false, `select * from ti where ? member of (domains) OR ? member of (signatures) OR ? member of (f_profile_ids) OR json_contains(f_profile_ids, ?)`, fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("%v", rand.Intn(30)), randV(`"[0,1]"`, `"[0,1,2]"`, `"[0]"`)) + check(false, `select * from ti WHERE ? member of (domains) OR ? member of (signatures) OR ? member of (long_link) OR json_contains(f_profile_ids, ?)`, fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(30)), randV(`"[0,1]"`, `"[0,1,2]"`, `"[0]"`)) + check(false, `select * from ti WHERE ? member of (domains) AND ? member of (signatures) AND json_contains(f_profile_ids, ?)`, fmt.Sprintf("'%v'", rand.Intn(30)), fmt.Sprintf("'%v'", rand.Intn(30)), randV(`'["0","1","2"]'`, `'["0"]'`, `'["1","2"]'`)) } } diff --git a/pkg/planner/core/plan_cache_test.go b/pkg/planner/core/plan_cache_test.go index 94fd04cf7f07a..8292dac135d76 100644 --- a/pkg/planner/core/plan_cache_test.go +++ b/pkg/planner/core/plan_cache_test.go @@ -1328,7 +1328,7 @@ func insertValuesForMVIndex(nRows int, colTypes ...string) string { return strings.Join(stmtVals, ", ") } -func verifyPlanCacheForMVIndex(t *testing.T, tk *testkit.TestKit, isIndexMerge bool, queryTemplate string, colTypes ...string) { +func verifyPlanCacheForMVIndex(t *testing.T, tk *testkit.TestKit, isIndexMerge, hitCache bool, queryTemplate string, colTypes ...string) { for i := 0; i < 5; i++ { var vals []string for _, colType := range colTypes { @@ -1357,21 +1357,21 @@ func verifyPlanCacheForMVIndex(t *testing.T, tk *testkit.TestKit, isIndexMerge b } result1 := tk.MustQuery(fmt.Sprintf("execute stmt using %v", usingStmt)).Sort() result.Check(result1.Rows()) - if isIndexMerge { + if isIndexMerge && hitCache { tk.MustQuery(`show warnings`).Check(testkit.Rows()) // no warning } result2 := tk.MustQuery(fmt.Sprintf("execute stmt using %v", usingStmt)).Sort() result.Check(result2.Rows()) - if isIndexMerge { + if isIndexMerge && hitCache { tk.MustQuery(`show warnings`).Check(testkit.Rows()) // no warning } result3 := tk.MustQuery(fmt.Sprintf("execute stmt using %v", usingStmt)).Sort() result.Check(result3.Rows()) - if isIndexMerge { + if isIndexMerge && hitCache { tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("1")) // hit the cache } - if isIndexMerge { + if isIndexMerge && hitCache { // check the plan result4 := tk.MustQuery(fmt.Sprintf("execute stmt using %v", usingStmt)).Sort() result.Check(result4.Rows()) tkProcess := tk.Session().ShowProcess() @@ -1399,28 +1399,28 @@ func TestPlanCacheMVIndexRandomly(t *testing.T) { tk.MustExec(`drop table if exists t2`) tk.MustExec(`create table t2(a json, b json, c int, d int, e int, index idx(c, (cast(a as signed array))), index idx2((cast(b as signed array)), c), index idx3(c, d), index idx4(d))`) tk.MustExec(fmt.Sprintf("insert into t2 values %v", insertValuesForMVIndex(100, "json-signed", "json-signed", "int", "int", "int"))) - verifyPlanCacheForMVIndex(t, tk, true, + verifyPlanCacheForMVIndex(t, tk, true, true, `select /*+ use_index_merge(t2, idx2, idx) */ * from t2 where (? member of (a) and c=?) or (? member of (b) and c=?)`, `int`, `int`, `int`, `int`) - verifyPlanCacheForMVIndex(t, tk, true, + verifyPlanCacheForMVIndex(t, tk, true, true, `select /*+ use_index_merge(t2, idx2, idx) */ * from t2 where (? member of (a) and c=? and d=?) or (? member of (b) and c=? and d=?)`, `int`, `int`, `int`, `int`, `int`, `int`) - verifyPlanCacheForMVIndex(t, tk, true, + verifyPlanCacheForMVIndex(t, tk, true, false, `select /*+ use_index_merge(t2, idx2, idx) */ * from t2 where ( json_contains(a, ?) and c=? and d=?) or (? member of (b) and c=? and d=?)`, `json-signed`, `int`, `int`, `int`, `int`, `int`) - verifyPlanCacheForMVIndex(t, tk, true, + verifyPlanCacheForMVIndex(t, tk, true, false, `select /*+ use_index_merge(t2, idx2, idx) */ * from t2 where ( json_overlaps(a, ?) and c=? and d=?) or (? member of (b) and c=? and d=?)`, `json-signed`, `int`, `int`, `int`, `int`, `int`) - verifyPlanCacheForMVIndex(t, tk, true, + verifyPlanCacheForMVIndex(t, tk, true, true, `select /*+ use_index_merge(t2, idx2, idx, idx4) */ * from t2 where ( json_contains(a, ?) and d=?) or (? member of (b) and c=? and d=?)`, `json-signed`, `int`, `int`, `int`, `int`) - verifyPlanCacheForMVIndex(t, tk, true, + verifyPlanCacheForMVIndex(t, tk, true, true, `select /*+ use_index_merge(t2, idx2, idx) */ * from t2 where (? member of (a) and ? member of (b) and c=?) or (? member of (b) and c=?)`, `int`, `int`, `int`, `int`, `int`) - verifyPlanCacheForMVIndex(t, tk, false, + verifyPlanCacheForMVIndex(t, tk, false, true, `select * from t2 where (? member of (a) and ? member of (b) and c=?) or (? member of (b) and c=?) or e=?`, `int`, `int`, `int`, `int`, `int`, `int`) - verifyPlanCacheForMVIndex(t, tk, true, + verifyPlanCacheForMVIndex(t, tk, true, true, `select /*+ use_index_merge(t2, idx2, idx, idx4) */ * from t2 where (? member of (a) and ? member of (b) and c=?) or (? member of (b) and c=?) or d=?`, `int`, `int`, `int`, `int`, `int`, `int`) @@ -1430,22 +1430,22 @@ func TestPlanCacheMVIndexRandomly(t *testing.T) { tk.MustExec(fmt.Sprintf("insert into t1 values %v", insertValuesForMVIndex(100, "json-signed", "json-signed", "int", "int"))) tk.MustExec(`create table t2(a json, b json, c int, d int, index idx(c, (cast(a as signed array))), index idx2((cast(b as signed array)), c), index idx3(c, d), index idx4(d))`) tk.MustExec(fmt.Sprintf("insert into t2 values %v", insertValuesForMVIndex(100, "json-signed", "json-signed", "int", "int"))) - verifyPlanCacheForMVIndex(t, tk, true, + verifyPlanCacheForMVIndex(t, tk, true, true, `select /*+ use_index_merge(t1, idx, idx2) */ * from t1 where ? member of (a) and ? member of (b)`, `int`, `int`) - verifyPlanCacheForMVIndex(t, tk, true, + verifyPlanCacheForMVIndex(t, tk, true, true, `select /*+ use_index_merge(t2, idx, idx2) */ * from t2 where ? member of (a) and ? member of (b) and c=?`, `int`, `int`, `int`) - verifyPlanCacheForMVIndex(t, tk, true, + verifyPlanCacheForMVIndex(t, tk, true, true, `select /*+ use_index_merge(t2, idx, idx2, idx4) */ * from t2 where ? member of (a) and ? member of (b) and c=? and d=?`, `int`, `int`, `int`, `int`) - verifyPlanCacheForMVIndex(t, tk, true, + verifyPlanCacheForMVIndex(t, tk, true, false, `select /*+ use_index_merge(t2, idx2, idx, idx3) */ * from t2 where json_contains(a, ?) and c=? and ? member of (b) and d=?`, `json-signed`, `int`, `int`, `int`) - verifyPlanCacheForMVIndex(t, tk, true, + verifyPlanCacheForMVIndex(t, tk, true, false, `select /*+ use_index_merge(t2, idx2, idx, idx3) */ * from t2 where json_overlaps(a, ?) and c=? and ? member of (b) and d=?`, `json-signed`, `int`, `int`, `int`) - verifyPlanCacheForMVIndex(t, tk, false, + verifyPlanCacheForMVIndex(t, tk, false, true, `select /*+ use_index_merge(t2, idx2, idx) */ * from t2 where ? member of (a) and c=? and c=?`, `int`, `int`, `int`) @@ -1453,7 +1453,7 @@ func TestPlanCacheMVIndexRandomly(t *testing.T) { tk.MustExec(`drop table if exists t`) tk.MustExec("create table t(pk varbinary(255) NOT NULL, domains json null, image_signatures json null, canonical_links json null, fpi json null, KEY `domains` ((cast(`domains` as char(253) array))), KEY `image_signatures` ((cast(`image_signatures` as char(32) array))),KEY `canonical_links` ((cast(`canonical_links` as char(1000) array))), KEY `fpi` ((cast(`fpi` as signed array))))") tk.MustExec(fmt.Sprintf("insert into t values %v", insertValuesForMVIndex(100, "string", "json-string", "json-string", "json-string", "json-signed"))) - verifyPlanCacheForMVIndex(t, tk, true, + verifyPlanCacheForMVIndex(t, tk, false, false, `SELECT /*+ use_index_merge(t, domains, image_signatures, canonical_links, fpi) */ pk FROM t WHERE ? member of (domains) OR ? member of (image_signatures) OR ? member of (canonical_links) OR json_contains(fpi, "[69236881]") LIMIT 100`, `string`, `string`, `string`) @@ -1461,7 +1461,7 @@ func TestPlanCacheMVIndexRandomly(t *testing.T) { tk.MustExec(`DROP table if exists t`) tk.MustExec("CREATE TABLE `t` (`pk` varbinary(255) NOT NULL,`nslc` json DEFAULT NULL,`fpi` json DEFAULT NULL,`point_of_sale_country` varchar(2) DEFAULT NULL,KEY `fpi` ((cast(`fpi` as signed array))),KEY `nslc` ((cast(`nslc` as char(1000) array)),`point_of_sale_country`),KEY `nslc_old` ((cast(`nslc` as char(1000) array))))") tk.MustExec(fmt.Sprintf("insert into t values %v", insertValuesForMVIndex(100, "string", "json-string", "json-signed", "string"))) - verifyPlanCacheForMVIndex(t, tk, true, + verifyPlanCacheForMVIndex(t, tk, true, true, "SELECT /*+ use_index_merge(t, fpi, nslc_old, nslc) */ * FROM t WHERE ? member of (fpi) AND ? member of (nslc) LIMIT 100", "int", "string") @@ -1469,10 +1469,10 @@ func TestPlanCacheMVIndexRandomly(t *testing.T) { tk.MustExec(`DROP table if exists t`) tk.MustExec("CREATE TABLE t (nslc json DEFAULT NULL,fpi json DEFAULT NULL,point_of_sale_country int,KEY nslc ((cast(nslc as char(1000) array)),point_of_sale_country),KEY fpi ((cast(fpi as signed array))))") tk.MustExec(fmt.Sprintf("insert into t values %v", insertValuesForMVIndex(100, "json-string", "json-signed", "int"))) - verifyPlanCacheForMVIndex(t, tk, true, + verifyPlanCacheForMVIndex(t, tk, true, true, "SELECT /*+ use_index_merge(t, nslc) */ * FROM t WHERE ? member of (fpi) AND ? member of (nslc) LIMIT 1", "int", "string") - verifyPlanCacheForMVIndex(t, tk, true, + verifyPlanCacheForMVIndex(t, tk, true, true, "SELECT /*+ use_index_merge(t, fpi) */ * FROM t WHERE ? member of (fpi) AND ? member of (nslc) LIMIT 1", "int", "string") } @@ -1498,7 +1498,8 @@ func TestPlanCacheMVIndexManually(t *testing.T) { output[i].SQL = input[i] }) if strings.HasPrefix(strings.ToLower(input[i]), "select") || - strings.HasPrefix(strings.ToLower(input[i]), "execute") { + strings.HasPrefix(strings.ToLower(input[i]), "execute") || + strings.HasPrefix(strings.ToLower(input[i]), "show") { result := tk.MustQuery(input[i]) testdata.OnRecord(func() { output[i].Result = testdata.ConvertRowsToStrings(result.Rows()) diff --git a/pkg/planner/core/testdata/plan_cache_suite_in.json b/pkg/planner/core/testdata/plan_cache_suite_in.json index c4ecff861116d..c3da13f882128 100644 --- a/pkg/planner/core/testdata/plan_cache_suite_in.json +++ b/pkg/planner/core/testdata/plan_cache_suite_in.json @@ -91,6 +91,41 @@ "execute st using @a", "set @a=13.21", "execute st using @a", + "select @@last_plan_from_cache", + // some cases for json_contains and json_overlaps with parameters + // queries with json_contains or overlaps with parameters that might affect index selection cannot be cached + "drop table if exists tx", + "create table tx (a json, b json, c json, d int, key ka ((cast(a as signed array))), key kb ((cast(b as signed array))))", + "prepare st from 'select /*+ use_index_merge(tx, ka, kb) */ * from tx where (? member of (a)) and (json_contains(b, ?))'", + "set @a=1, @b='[1,2]'", + "execute st using @a, @b", + "show warnings", + "execute st using @a, @b", + "select @@last_plan_from_cache", + "prepare st from 'select /*+ use_index_merge(tx, ka, kb) */ * from tx where (? member of (a)) and (json_overlaps(b, ?))'", + "set @a=1, @b='[1,2]'", + "execute st using @a, @b", + "show warnings", + "execute st using @a, @b", + "select @@last_plan_from_cache", + // if json_contains/overlaps don't affect index selection, then the query can be cached + "prepare st from 'select /*+ use_index_merge(tx, ka, kb) */ * from tx where (? member of (a)) and (d = 1 or json_overlaps(b, ?))'", + "set @a=1, @b='[1,2]'", + "execute st using @a, @b", + "show warnings", + "execute st using @a, @b", + "select @@last_plan_from_cache", + "prepare st from 'select /*+ use_index_merge(tx, ka) */ * from tx where (? member of (a)) and (json_overlaps(c, ?))'", + "set @a=1, @c='[1,2]'", + "execute st using @a, @b", + "show warnings", + "execute st using @a, @b", + "select @@last_plan_from_cache", + "prepare st from 'select /*+ use_index_merge(tx, ka) */ * from tx where (? member of (a)) and (json_contains(c, ?))'", + "set @a=1, @c='[1,2]'", + "execute st using @a, @b", + "show warnings", + "execute st using @a, @b", "select @@last_plan_from_cache" ] } diff --git a/pkg/planner/core/testdata/plan_cache_suite_out.json b/pkg/planner/core/testdata/plan_cache_suite_out.json index 7471836dab449..fed347a96f558 100644 --- a/pkg/planner/core/testdata/plan_cache_suite_out.json +++ b/pkg/planner/core/testdata/plan_cache_suite_out.json @@ -422,6 +422,148 @@ "Result": [ "0" ] + }, + { + "SQL": "drop table if exists tx", + "Result": null + }, + { + "SQL": "create table tx (a json, b json, c json, d int, key ka ((cast(a as signed array))), key kb ((cast(b as signed array))))", + "Result": null + }, + { + "SQL": "prepare st from 'select /*+ use_index_merge(tx, ka, kb) */ * from tx where (? member of (a)) and (json_contains(b, ?))'", + "Result": null + }, + { + "SQL": "set @a=1, @b='[1,2]'", + "Result": null + }, + { + "SQL": "execute st using @a, @b", + "Result": null + }, + { + "SQL": "show warnings", + "Result": [ + "Warning 1105 skip prepared plan-cache: json_contains function with immutable parameters can affect index selection" + ] + }, + { + "SQL": "execute st using @a, @b", + "Result": null + }, + { + "SQL": "select @@last_plan_from_cache", + "Result": [ + "0" + ] + }, + { + "SQL": "prepare st from 'select /*+ use_index_merge(tx, ka, kb) */ * from tx where (? member of (a)) and (json_overlaps(b, ?))'", + "Result": null + }, + { + "SQL": "set @a=1, @b='[1,2]'", + "Result": null + }, + { + "SQL": "execute st using @a, @b", + "Result": null + }, + { + "SQL": "show warnings", + "Result": [ + "Warning 1105 skip prepared plan-cache: json_overlaps function with immutable parameters can affect index selection" + ] + }, + { + "SQL": "execute st using @a, @b", + "Result": null + }, + { + "SQL": "select @@last_plan_from_cache", + "Result": [ + "0" + ] + }, + { + "SQL": "prepare st from 'select /*+ use_index_merge(tx, ka, kb) */ * from tx where (? member of (a)) and (d = 1 or json_overlaps(b, ?))'", + "Result": null + }, + { + "SQL": "set @a=1, @b='[1,2]'", + "Result": null + }, + { + "SQL": "execute st using @a, @b", + "Result": null + }, + { + "SQL": "show warnings", + "Result": null + }, + { + "SQL": "execute st using @a, @b", + "Result": null + }, + { + "SQL": "select @@last_plan_from_cache", + "Result": [ + "1" + ] + }, + { + "SQL": "prepare st from 'select /*+ use_index_merge(tx, ka) */ * from tx where (? member of (a)) and (json_overlaps(c, ?))'", + "Result": null + }, + { + "SQL": "set @a=1, @c='[1,2]'", + "Result": null + }, + { + "SQL": "execute st using @a, @b", + "Result": null + }, + { + "SQL": "show warnings", + "Result": null + }, + { + "SQL": "execute st using @a, @b", + "Result": null + }, + { + "SQL": "select @@last_plan_from_cache", + "Result": [ + "1" + ] + }, + { + "SQL": "prepare st from 'select /*+ use_index_merge(tx, ka) */ * from tx where (? member of (a)) and (json_contains(c, ?))'", + "Result": null + }, + { + "SQL": "set @a=1, @c='[1,2]'", + "Result": null + }, + { + "SQL": "execute st using @a, @b", + "Result": null + }, + { + "SQL": "show warnings", + "Result": null + }, + { + "SQL": "execute st using @a, @b", + "Result": null + }, + { + "SQL": "select @@last_plan_from_cache", + "Result": [ + "1" + ] } ] }