Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkg/parser/ast/misc.go
Original file line number Diff line number Diff line change
Expand Up @@ -4102,7 +4102,7 @@ func (n *TableOptimizerHint) Restore(ctx *format.RestoreCtx) error {
}
// Hints without args except query block.
switch n.HintName.L {
case "mpp_1phase_agg", "mpp_2phase_agg", "hash_agg", "stream_agg", "agg_to_cop", "read_consistent_replica", "no_index_merge", "ignore_plan_cache", "limit_to_cop", "straight_join", "merge", "no_decorrelate":
case "mpp_1phase_agg", "mpp_2phase_agg", "hash_agg", "stream_agg", "agg_to_cop", "read_consistent_replica", "no_index_merge", "ignore_plan_cache", "use_plan_cache", "limit_to_cop", "straight_join", "merge", "no_decorrelate":
ctx.WritePlain(")")
return nil
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/planner/core/casetest/plancache/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ go_test(
"//pkg/planner/core:plan_clone_utils.go",
],
flaky = True,
shard_count = 45,
shard_count = 47,
deps = [
"//pkg/expression",
"//pkg/infoschema",
Expand Down
89 changes: 89 additions & 0 deletions pkg/planner/core/casetest/plancache/plan_cache_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1811,6 +1811,95 @@ func runPreparedPlanCacheForUpdateInTxn(t *testing.T, tk *testkit.TestKit) {
tk.MustExec(`deallocate prepare st`)
}

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

preparedCache := tk.MustQuery("select @@session.tidb_enable_prepared_plan_cache").Rows()[0][0]
planCachePolicy := tk.MustQuery("select @@session.tidb_plan_cache_policy").Rows()[0][0]
defer func() {
tk.MustExec(fmt.Sprintf("set @@session.tidb_enable_prepared_plan_cache=%v", preparedCache))
tk.MustExec(fmt.Sprintf("set @@session.tidb_plan_cache_policy=%q", planCachePolicy))
}()
tk.MustExec(`set @@session.tidb_enable_prepared_plan_cache=1`)
tk.MustExec(`set @@session.tidb_plan_cache_policy='hint_only'`)

tableName := "t_prepare_hint_only_without_use_hint"
tk.MustExec(fmt.Sprintf("drop table if exists %s", tableName))
tk.MustExec(fmt.Sprintf("create table %s (a int)", tableName))
tk.MustExec(fmt.Sprintf("insert into %s values (1)", tableName))

tk.MustExec(fmt.Sprintf("prepare st from 'select 1 from %s where a = ?'", tableName))
tk.MustExec("set @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_cache`).Check(testkit.Rows("0"))

tk.MustExec(fmt.Sprintf(
"create global binding for select 1 from %s where a = ? using select /*+ use_plan_cache() */ 1 from %s where a = ?",
tableName, tableName,
))
tk.MustExec("execute st using @a")
tk.MustQuery("select @@last_plan_from_binding, @@last_plan_from_cache").Check(testkit.Rows("1 0"))
tk.MustExec("execute st using @a")
tk.MustQuery("select @@last_plan_from_binding, @@last_plan_from_cache").Check(testkit.Rows("1 1"))
tk.MustExec("execute st using @a")
tk.MustQuery("select @@last_plan_from_binding, @@last_plan_from_cache").Check(testkit.Rows("1 1"))
}

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

preparedCache := tk.MustQuery("select @@session.tidb_enable_prepared_plan_cache").Rows()[0][0]
planCachePolicy := tk.MustQuery("select @@session.tidb_plan_cache_policy").Rows()[0][0]
defer func() {
tk.MustExec(fmt.Sprintf("set @@session.tidb_enable_prepared_plan_cache=%v", preparedCache))
tk.MustExec(fmt.Sprintf("set @@session.tidb_plan_cache_policy=%q", planCachePolicy))
}()
tk.MustExec(`set @@session.tidb_enable_prepared_plan_cache=1`)
tk.MustExec(`set @@session.tidb_plan_cache_policy='hint_only'`)

tableName := "t_prepare_hint_only_binding"
tk.MustExec(fmt.Sprintf("drop table if exists %s", tableName))
tk.MustExec(fmt.Sprintf("create table %s (pk int, a int, primary key(pk))", tableName))
tk.MustExec(fmt.Sprintf("insert into %s values (1, 1), (2, 2)", tableName))

tk.MustExec(fmt.Sprintf("prepare st from 'select * from %s where pk >= ?'", tableName))
tk.MustExec("set @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_cache`).Check(testkit.Rows("0"))
tk.MustExec("execute st using @a")
tk.MustQuery(`select @@last_plan_from_cache`).Check(testkit.Rows("0"))

tk.MustExec(fmt.Sprintf(
"CREATE BINDING FOR select * from %s where pk >= ? USING select /*+ use_plan_cache() */ * from %s where pk >= ?",
tableName, tableName,
))
tk.MustExec("execute st using @a")
tk.MustQuery("select @@last_plan_from_binding, @@last_plan_from_cache").Check(testkit.Rows("1 0"))
tk.MustExec("execute st using @a")
tk.MustQuery("select @@last_plan_from_binding, @@last_plan_from_cache").Check(testkit.Rows("1 1"))
tk.MustExec("execute st using @a")
tk.MustQuery(`select @@last_plan_from_cache`).Check(testkit.Rows("1"))

tk.MustExec(fmt.Sprintf(
"CREATE BINDING FOR select * from %s where pk >= ? USING select /*+ ignore_plan_cache() */ * from %s where pk >= ?",
tableName, tableName,
))
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_cache`).Check(testkit.Rows("0"))
tk.MustExec("execute st using @a")
tk.MustQuery(`select @@last_plan_from_cache`).Check(testkit.Rows("0"))
}

func TestNonPreparedPlanCacheSupportsFeatures(t *testing.T) {
store := testkit.CreateMockStore(t)
tk := testkit.NewTestKit(t, store)
Expand Down
40 changes: 38 additions & 2 deletions pkg/planner/core/plan_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"time"

"github.com/pingcap/errors"
"github.com/pingcap/tidb/pkg/bindinfo"
"github.com/pingcap/tidb/pkg/domain"
"github.com/pingcap/tidb/pkg/expression"
"github.com/pingcap/tidb/pkg/infoschema"
Expand Down Expand Up @@ -194,6 +195,7 @@ func GetPlanFromPlanCache(ctx context.Context, sctx sessionctx.Context,

sessVars := sctx.GetSessionVars()
stmtCtx := sessVars.StmtCtx
var matchedBinding *bindinfo.Binding
cacheEnabled := false
if isNonPrepared {
stmtCtx.SetCacheType(contextutil.SessionNonPrepared)
Expand All @@ -203,7 +205,14 @@ func GetPlanFromPlanCache(ctx context.Context, sctx sessionctx.Context,
cacheEnabled = sessVars.EnablePreparedPlanCache
}
if stmt.StmtCacheable && cacheEnabled {
stmtCtx.EnablePlanCache()
if sessVars.PlanCachePolicy == vardef.PlanCachePolicyHintOnly && !stmt.UsePlanCacheHint {
matchedBinding = matchSQLBindingWithCache(sctx, stmt)
}
if allowPlanCacheByPolicy(sctx, stmt, matchedBinding) {
stmtCtx.EnablePlanCache()
} else {
stmtCtx.WarnSkipPlanCache("the switch 'tidb_plan_cache_policy' is set to hint_only and no USE_PLAN_CACHE() hint is found")
}
}
if stmt.UncacheableReason != "" {
stmtCtx.WarnSkipPlanCache(stmt.UncacheableReason)
Expand All @@ -212,7 +221,10 @@ func GetPlanFromPlanCache(ctx context.Context, sctx sessionctx.Context,
var cacheKey, binding, reason string
var cacheable bool
if stmtCtx.UseCache() {
cacheKey, binding, cacheable, reason, err = NewPlanCacheKey(sctx, stmt)
if matchedBinding == nil {
matchedBinding = matchSQLBindingWithCache(sctx, stmt)
}
cacheKey, binding, cacheable, reason, err = newPlanCacheKeyWithMatchedBinding(sctx, stmt, matchedBinding)
if err != nil {
return nil, nil, err
}
Expand All @@ -238,6 +250,30 @@ func GetPlanFromPlanCache(ctx context.Context, sctx sessionctx.Context,
return generateNewPlan(ctx, sctx, isNonPrepared, is, stmt, cacheKey, binding, paramTypes)
}

func allowPlanCacheByPolicy(sctx sessionctx.Context, stmt *PlanCacheStmt, matchedBinding *bindinfo.Binding) bool {
if sctx.GetSessionVars().PlanCachePolicy != vardef.PlanCachePolicyHintOnly {
return true
}
if stmt.UsePlanCacheHint {
return true
}
return bindingHasUsePlanCacheHint(matchedBinding)
}

func matchSQLBindingWithCache(sctx sessionctx.Context, stmt *PlanCacheStmt) *bindinfo.Binding {
matchedBinding, matched, _ := bindinfo.MatchSQLBindingWithCache(sctx, stmt.PreparedAst.Stmt, &stmt.BindingInfo)
if !matched {
return nil
}
return matchedBinding
}

func bindingHasUsePlanCacheHint(matchedBinding *bindinfo.Binding) bool {
return matchedBinding != nil &&
matchedBinding.Hint != nil &&
matchedBinding.Hint.ContainTableHint(hint.HintUsePlanCache)
}

func clonePlanForInstancePlanCache(ctx context.Context, sctx sessionctx.Context,
stmt *PlanCacheStmt, plan base.Plan) (clonedPlan base.Plan, ok bool) {
defer func(begin time.Time) {
Expand Down
22 changes: 21 additions & 1 deletion pkg/planner/core/plan_cache_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ func (e *paramMarkerExtractor) Leave(in ast.Node) (ast.Node, bool) {
func GeneratePlanCacheStmtWithAST(ctx context.Context, sctx sessionctx.Context, isPrepStmt bool,
paramSQL string, paramStmt ast.StmtNode, is infoschema.InfoSchema) (*PlanCacheStmt, base.Plan, int, error) {
vars := sctx.GetSessionVars()
usePlanCacheHint := hasUsePlanCacheHint(paramStmt)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
var extractor paramMarkerExtractor
paramStmt.Accept(&extractor)

Expand Down Expand Up @@ -222,6 +223,7 @@ func GeneratePlanCacheStmtWithAST(ctx context.Context, sctx sessionctx.Context,
SnapshotTSEvaluator: ret.SnapshotTSEvaluator,
StmtCacheable: cacheable,
UncacheableReason: reason,
UsePlanCacheHint: usePlanCacheHint,
dbName: dbName,
tbls: tbls,
SchemaVersion: ret.InfoSchema.SchemaMetaVersion(),
Expand All @@ -238,6 +240,15 @@ func GeneratePlanCacheStmtWithAST(ctx context.Context, sctx sessionctx.Context,
return preparedObj, p, paramCount, nil
}

func hasUsePlanCacheHint(stmt ast.StmtNode) bool {
for _, h := range hint.ExtractTableHintsFromStmtNode(stmt, nil) {
if h.HintName.L == hint.HintUsePlanCache {
return true
}
}
return false
}

// tableIDSlicePool is a pool for int64 slices used in hashInt64Uint64Map.
var tableIDSlicePool = zeropool.New[[]int64](func() []int64 {
return make([]int64, 0, 8)
Expand Down Expand Up @@ -310,7 +321,15 @@ func hashInt64Uint64Map(b []byte, m map[int64]uint64) []byte {
// differentiate the cache key. In other cases, it will be 0.
// All information that might affect the plan should be considered in this function.
func NewPlanCacheKey(sctx sessionctx.Context, stmt *PlanCacheStmt) (key, binding string, cacheable bool, reason string, err error) {
if matchedBinding, matched, _ := bindinfo.MatchSQLBindingWithCache(sctx, stmt.PreparedAst.Stmt, &stmt.BindingInfo); matched {
matchedBinding, matched, _ := bindinfo.MatchSQLBindingWithCache(sctx, stmt.PreparedAst.Stmt, &stmt.BindingInfo)
if !matched {
matchedBinding = nil
}
return newPlanCacheKeyWithMatchedBinding(sctx, stmt, matchedBinding)
}

func newPlanCacheKeyWithMatchedBinding(sctx sessionctx.Context, stmt *PlanCacheStmt, matchedBinding *bindinfo.Binding) (key, binding string, cacheable bool, reason string, err error) {
if matchedBinding != nil {
// Record the matched binding SQL so the plan cache key reflects the effective hints.
binding = matchedBinding.BindSQL
}
Expand Down Expand Up @@ -736,6 +755,7 @@ type PlanCacheStmt struct {

StmtCacheable bool // Whether this stmt is cacheable.
UncacheableReason string // Why this stmt is uncacheable.
UsePlanCacheHint bool // Whether this stmt contains the USE_PLAN_CACHE() hint.

limits []*ast.Limit
hasSubquery bool
Expand Down
8 changes: 8 additions & 0 deletions pkg/sessionctx/vardef/tidb_vars.go
Original file line number Diff line number Diff line change
Expand Up @@ -953,6 +953,8 @@ const (
TiDBNonPreparedPlanCacheSize = "tidb_non_prepared_plan_cache_size"
// TiDBPlanCacheMaxPlanSize controls the maximum size of a plan that can be cached.
TiDBPlanCacheMaxPlanSize = "tidb_plan_cache_max_plan_size"
// TiDBPlanCachePolicy controls how plan cache is enabled.
TiDBPlanCachePolicy = "tidb_plan_cache_policy"
// TiDBPlanCacheInvalidationOnFreshStats controls if plan cache will be invalidated automatically when
// related stats are analyzed after the plan cache is generated.
TiDBPlanCacheInvalidationOnFreshStats = "tidb_plan_cache_invalidation_on_fresh_stats"
Expand Down Expand Up @@ -1670,6 +1672,7 @@ const (
DefTiDBEnableNonPreparedPlanCacheForDML = true
DefTiDBNonPreparedPlanCacheSize = 100
DefTiDBPlanCacheMaxPlanSize = 2 * size.MB
DefTiDBPlanCachePolicy = PlanCachePolicyAll
DefTiDBInstancePlanCacheMaxMemSize = 100 * size.MB
MinTiDBInstancePlanCacheMemSize = 100 * size.MB
DefTiDBInstancePlanCacheReservedPercentage = 0.1
Expand Down Expand Up @@ -2121,6 +2124,11 @@ const (
// StrategyCustom is a choice of variable TiDBPipelinedDmlResourcePolicy,
StrategyCustom = "custom"

// PlanCachePolicyAll means all cacheable statements can use plan cache.
PlanCachePolicyAll = "all"
// PlanCachePolicyHintOnly means only statements with the USE_PLAN_CACHE() hint can use plan cache.
PlanCachePolicyHintOnly = "hint_only"

// IndexLookUpPushDownPolicyHintOnly indicates only use the hint to decide whether to push down the index lookup or not.
IndexLookUpPushDownPolicyHintOnly = "hint-only"
// IndexLookUpPushDownPolicyAffinityForce indicates to force push down the index lookup for table with affinity options.
Expand Down
4 changes: 4 additions & 0 deletions pkg/sessionctx/variable/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -1642,6 +1642,9 @@ type SessionVars struct {
// PlanCacheMaxPlanSize controls the maximum size of a plan that can be cached.
PlanCacheMaxPlanSize uint64

// PlanCachePolicy controls how plan cache is enabled.
PlanCachePolicy string

// SessionPlanCacheSize controls the size of session plan cache.
SessionPlanCacheSize uint64

Expand Down Expand Up @@ -2455,6 +2458,7 @@ func NewSessionVars(hctx HookContext) *SessionVars {
RegardNULLAsPoint: vardef.DefTiDBRegardNULLAsPoint,
AllowProjectionPushDown: vardef.DefOptEnableProjectionPushDown,
SkipMissingPartitionStats: vardef.DefTiDBSkipMissingPartitionStats,
PlanCachePolicy: vardef.DefTiDBPlanCachePolicy,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read-only verification: find tests and related coverage for tidb_plan_cache_policy.
set -euo pipefail

echo "== References in implementation =="
rg -n -C2 'tidb_plan_cache_policy|PlanCachePolicy|DefTiDBPlanCachePolicy|hint_only|USE_PLAN_CACHE'

echo
echo "== References in Go tests =="
rg -n -C2 'tidb_plan_cache_policy|PlanCachePolicy|hint_only|USE_PLAN_CACHE' --iglob '*_test.go'

echo
echo "== References in SQL/integration test assets =="
rg -n -C2 'tidb_plan_cache_policy|hint_only|USE_PLAN_CACHE' --iglob '*.sql' --iglob '*.result' --iglob '*.test'

Repository: pingcap/tidb

Length of output: 9835


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Test function containing tidb_plan_cache_policy =="
sed -n '1810,1860p' pkg/planner/core/casetest/plancache/plan_cache_suite_test.go

echo
echo "== Looking for test function name around line 1820 =="
sed -n '1800,1830p' pkg/planner/core/casetest/plancache/plan_cache_suite_test.go | head -40

echo
echo "== Search for other test files that might test plan cache policy =="
fd -e go -e test -e sql -e result -e test | xargs grep -l 'tidb_plan_cache_policy' 2>/dev/null | head -20

echo
echo "== Check for default value test scenarios =="
rg -n 'DefTiDBPlanCachePolicy|PlanCachePolicyAll' --type go pkg/sessionctx/ | grep -i test

echo
echo "== Check if GLOBAL scope is tested for this variable =="
rg -n 'global.*tidb_plan_cache_policy|tidb_plan_cache_policy.*global' --type go -i

Repository: pingcap/tidb

Length of output: 4191


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== All test functions in plan_cache_suite_test.go =="
rg '^func Test' pkg/planner/core/casetest/plancache/plan_cache_suite_test.go | head -30

echo
echo "== Search for SQL/integration test files in plancache directory =="
fd -e sql -e result -e test . pkg/planner/core/casetest/plancache/ 2>/dev/null | head -20

echo
echo "== Check for any default value behavior test =="
rg -n "PlanCachePolicyAll|DefTiDBPlanCachePolicy" --type go pkg/planner/core/casetest/ -A 2 -B 2

echo
echo "== Check GLOBAL variable scope test in sysvar.go =="
sed -n '1580,1610p' pkg/sessionctx/variable/sysvar.go

echo
echo "== List all test files in plancache directory =="
ls -la pkg/planner/core/casetest/plancache/*.go | head -20

Repository: pingcap/tidb

Length of output: 1783


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Check the sysvar.go definition again for scope =="
sed -n '1585,1600p' pkg/sessionctx/variable/sysvar.go

echo
echo "== Search for tests that set DEFAULT value or test 'all' policy =="
rg -n "PlanCachePolicyAll|tidb_plan_cache_policy.*all" --type go -i pkg/planner/ | head -20

echo
echo "== Search for tests that use GLOBAL scope (@@global. prefix) =="
rg -n "@@global.*tidb_plan_cache_policy|@@global.*PlanCachePolicy" --type go -i

echo
echo "== Check what the default value actually is =="
rg -n "DefTiDBPlanCachePolicy.*=" pkg/sessionctx/vardef/tidb_vars.go

echo
echo "== Look for any test that doesn't set the variable (implicit default test) =="
rg -n "func Test" pkg/planner/core/casetest/plancache/plan_cache_suite_test.go -A 15 | grep -A 15 "TestInitLRUWithSystemVar"

echo
echo "== Search integration tests directory structure =="
find pkg -type d -name "*test*" | grep -i integ | head -10
find pkg -type f -name "*.test" | head -10
find pkg -type f -name "*.sql" | head -10

Repository: pingcap/tidb

Length of output: 1021


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Check for SetGlobal function in sysvar.go for tidb_plan_cache_policy =="
sed -n '1585,1610p' pkg/sessionctx/variable/sysvar.go | cat -n

echo
echo "== Search for how other variables define SetGlobal =="
rg -n "SetGlobal:" pkg/sessionctx/variable/sysvar.go | head -5

echo
echo "== Check if tidb_plan_cache_policy has SetGlobal defined =="
sed -n '1585,1600p' pkg/sessionctx/variable/sysvar.go | grep -i setglobal || echo "No SetGlobal found"

echo
echo "== Search for tests that verify default behavior at session startup =="
rg -n "NewSessionVars|NewSessionWithOpts" pkg/planner/core/casetest/plancache/plan_cache_suite_test.go | head -5

echo
echo "== Look at beginning of plan_cache_suite_test.go to see imports and setup =="
head -60 pkg/planner/core/casetest/plancache/plan_cache_suite_test.go

echo
echo "== Check if there are dedicated integration test suites =="
fd -type f -name '*.test' | head -20

Repository: pingcap/tidb

Length of output: 2649


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Check for default SetGlobal behavior when SetGlobal is not defined =="
rg -n "SetGlobal.*nil\|if.*SetGlobal.*==\|SetGlobal.*==" pkg/sessionctx/variable/sysvar.go | head -10

echo
echo "== Look for how SetGlobal is used/handled in the system =="
rg -n "s.SetGlobal" pkg/sessionctx/variable/ | head -10

echo
echo "== Check if SetSession alone works for both GLOBAL and SESSION =="
rg -n "SetSession:" pkg/sessionctx/variable/sysvar.go | head -5

echo
echo "== Check how other GLOBAL|SESSION scope variables handle this =="
sed -n '1400,1450p' pkg/sessionctx/variable/sysvar.go | grep -A 10 "ScopeGlobal.*ScopeSession"

echo
echo "== Find the actual test of hint_only to understand what behavior is verified =="
sed -n '1800,1900p' pkg/planner/core/casetest/plancache/plan_cache_suite_test.go

Repository: pingcap/tidb

Length of output: 128


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Search for any test that reads the default value without setting it =="
rg -n "tidb_plan_cache_policy" pkg/planner/core/casetest/plancache/plan_cache_suite_test.go -B 5 | head -40

echo
echo "== Check what TestInitLRUWithSystemVar does =="
sed -n '100,200p' pkg/planner/core/casetest/plancache/plan_cache_suite_test.go | grep -A 100 "func TestInitLRUWithSystemVar"

echo
echo "== Count how many times the variable is explicitly tested =="
rg -c "tidb_plan_cache_policy" pkg/planner/core/casetest/plancache/plan_cache_suite_test.go

echo
echo "== Check if setvar_affect.go (which flags variable for invalidation) has tests =="
fd -name '*setvar*test*' pkg/sessionctx/variable/

echo
echo "== Search for sql test files for session variables =="
fd -path '*/sessionctx/*' -name '*.test' -o -name '*.sql' 2>/dev/null | head -10

Repository: pingcap/tidb

Length of output: 852


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Check if variable supports GLOBAL SET by looking at SetGlobal in sysvar.go for this variable =="
sed -n '1585,1595p' pkg/sessionctx/variable/sysvar.go

echo
echo "== Search for any test that uses @@global.tidb_plan_cache_policy =="
rg '@@global.*tidb_plan_cache_policy' --type go

echo
echo "== Check integration/sql test files that might test plan cache =="
find . -path './tests' -prune -o -name '*.test' -type f -print 2>/dev/null | xargs grep -l 'tidb_plan_cache_policy' 2>/dev/null || echo "No .test files found with the variable"

echo
echo "== Check if there's an integration test suite for sessionctx =="
find . -path '*tests*' -name '*session*.test' -o -name '*variable*.test' 2>/dev/null | head -10

echo
echo "== Look at what the actual default value constant is =="
rg -n "DefTiDBPlanCachePolicy" pkg/sessionctx/vardef/tidb_vars.go -A 1 -B 1

echo
echo "== Check if there's documentation for test coverage patterns =="
cat AGENTS.md 2>/dev/null | grep -A 5 "session.*variable" || echo "No AGENTS.md guidance found"

Repository: pingcap/tidb

Length of output: 995


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Check how GLOBAL scope variables work without explicit SetGlobal =="
rg -n "func.*applySetGlobal\|SetGlobal.*nil\|SetGlobal.*func" pkg/sessionctx/variable/sysvar.go | head -10

echo
echo "== Check if ScopeGlobal without SetGlobal gets default behavior =="
grep -B 5 -A 5 "ScopeGlobal.*SetSession.*func" pkg/sessionctx/variable/sysvar.go | head -30

echo
echo "== Look for how variables with only SetSession handle global scope =="
rg -n "Scope:.*Global.*Session.*SetSession:" pkg/sessionctx/variable/sysvar.go -A 5 | head -50

echo
echo "== Verify the default value constant =="
grep -n "PlanCachePolicyAll\|DefTiDBPlanCachePolicy" pkg/sessionctx/vardef/tidb_vars.go | head -5

echo
echo "== Check if there are any other tests besides the one we found =="
rg -n "\.PlanCachePolicy\|tidb_plan_cache_policy" pkg/planner/core/casetest/plancache/plan_cache_suite_test.go

Repository: pingcap/tidb

Length of output: 126


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== List all occurrences of tidb_plan_cache_policy in the entire codebase =="
rg 'tidb_plan_cache_policy' --type go --type sql -i

echo
echo "== Check the sysvar definition structure more carefully =="
sed -n '1580,1600p' pkg/sessionctx/variable/sysvar.go | head -30

echo
echo "== Look for how SetGlobal falls back when not defined =="
grep -r "SetGlobal == nil\|SetGlobal.*nil\|if.*SetGlobal" pkg/sessionctx/variable/ | head -10

echo
echo "== Check if variable is mentioned in testing docs or test helpers =="
find . -name "*test*helper*" -o -name "*test*util*" | xargs grep -l "session.*variable" 2>/dev/null | head -5

Repository: pingcap/tidb

Length of output: 2581


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Check how SetGlobal is used in variable.go to understand the behavior =="
grep -B 5 -A 10 "if sv.SetGlobal != nil" pkg/sessionctx/variable/variable.go | head -30

echo
echo "== Check if GLOBAL scope for enum variables requires SetGlobal =="
rg -n "ScopeGlobal.*TypeEnum" pkg/sessionctx/variable/sysvar.go -A 5 | head -50

echo
echo "== Look for any integration test that might test this variable at GLOBAL scope =="
find . -name "*.test" -type f 2>/dev/null | xargs grep -l "set global tidb_plan_cache_policy" 2>/dev/null | head -5 || echo "No .test files found"

echo
echo "== Check documentation or comments about the scope limitation =="
sed -n '1645,1650p' pkg/sessionctx/variable/session.go

Repository: pingcap/tidb

Length of output: 6005


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Check what happens when SetGlobal is nil and variable has ScopeGlobal =="
sed -n '1,100p' pkg/sessionctx/variable/variable.go | grep -A 30 "SetGlobalFromHook"

echo
echo "== Verify the complete SetGlobalFromHook logic =="
sed -n '230,270p' pkg/sessionctx/variable/variable.go

echo
echo "== Check if there's a test that tries to set GLOBAL scope =="
rg -n "set.*global.*tidb_plan_cache_policy" --type go -i

echo
echo "== Check setvar_affect.go to understand why this var is listed there =="
sed -n '110,125p' pkg/sessionctx/variable/setvar_affect.go

echo
echo "== Check if the variable has Aliases =="
rg -n "TiDBPlanCachePolicy.*Aliases\|Aliases.*PlanCachePolicy" pkg/sessionctx/variable/sysvar.go

Repository: pingcap/tidb

Length of output: 131


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Read variable.go SetGlobalFromHook function =="
sed -n '85,125p' pkg/sessionctx/variable/variable.go

echo
echo "== Check if SetGlobal being nil is handled =="
sed -n '85,150p' pkg/sessionctx/variable/variable.go

echo
echo "== Check the complete definition of SysVar struct =="
sed -n '1,150p' pkg/sessionctx/variable/sysvar_definition.go | head -80

Repository: pingcap/tidb

Length of output: 4660


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Find SetGlobalFromHook implementation =="
rg -n "func.*SetGlobalFromHook" pkg/sessionctx/variable/ -A 20

echo
echo "== Check if SetGlobal being nil causes issues or is handled =="
rg -n "sv.SetGlobal" pkg/sessionctx/variable/variable.go -B 3 -A 3

echo
echo "== Look at how the variable is initialized in session =="
sed -n '2450,2470p' pkg/sessionctx/variable/session.go

Repository: pingcap/tidb

Length of output: 3942


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Verify what happens when GLOBAL SET is called on this variable =="
rg -n "SetGlobalSysVarOnly" pkg/sessionctx/variable/ | head -10

echo
echo "== Check if there are any tests that verify default value 'all' =="
rg -n "PlanCachePolicyAll\|'all'" pkg/planner/core/casetest/plancache/plan_cache_suite_test.go

echo
echo "== Check if any test tries to set GLOBAL scope =="
rg -n "@@global" pkg/planner/core/casetest/plancache/plan_cache_suite_test.go

echo
echo "== Verify if DefTiDBPlanCachePolicy equals PlanCachePolicyAll =="
rg -n "DefTiDBPlanCachePolicy\|PlanCachePolicyAll" pkg/sessionctx/vardef/tidb_vars.go | grep -A 1 -B 1 "1675\|2128"

Repository: pingcap/tidb

Length of output: 1671


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Get line 1675 from tidb_vars.go to confirm default value =="
sed -n '1673,1677p' pkg/sessionctx/vardef/tidb_vars.go

echo
echo "== Get line 2128 from tidb_vars.go to confirm policy values =="
sed -n '2125,2132p' pkg/sessionctx/vardef/tidb_vars.go

echo
echo "== Count test occurrences in plan_cache_suite_test.go =="
wc -l pkg/planner/core/casetest/plancache/plan_cache_suite_test.go

echo
echo "== Verify TestPreparedPlanCacheHintOnlyWithBinding is the ONLY test using the variable =="
rg -n "tidb_plan_cache_policy\|PlanCachePolicy" pkg/planner/core/casetest/plancache/plan_cache_suite_test.go | wc -l

echo
echo "== Check if there's a test that doesn't explicitly set the variable (uses default) =="
rg -n "TestNonPreparedPlanCacheBasically\|TestInitLRUWithSystemVar" pkg/planner/core/casetest/plancache/plan_cache_suite_test.go -A 30 | head -50

Repository: pingcap/tidb

Length of output: 1120


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Check if there are any .test files specifically for session variables =="
find . -path './tests' -name '*.test' -type f 2>/dev/null | xargs grep -l "tidb_plan_cache_policy" 2>/dev/null | head -5 || echo "No .test files found"

echo
echo "== Look for SQL integration test pattern in the repo =="
fd '.test' --max-depth 3 ./tests 2>/dev/null | head -10

echo
echo "== Confirm that TestPreparedPlanCacheHintOnlyWithBinding exists and what it covers =="
sed -n '1813,1900p' pkg/planner/core/casetest/plancache/plan_cache_suite_test.go

Repository: pingcap/tidb

Length of output: 5140


Confirm test coverage for tidb_plan_cache_policy session variable — GLOBAL scope and default value tests are missing.

Test coverage exists for SESSION scope (TestPreparedPlanCacheHintOnlyWithBinding sets and verifies 'hint_only' behavior with bindings), but verification gaps remain:

  • GLOBAL scope: No test for SET @@global.tidb_plan_cache_policy (variable is defined with ScopeGlobal|ScopeSession but lacks a SetGlobal handler, which may cause GLOBAL SET to silently succeed without effect).
  • Default value behavior: No explicit test for the default 'all' policy.
  • SQL integration tests: None found.

Per guidelines for session variables, add targeted package tests for default value and GLOBAL scope, plus SQL integration tests for user-visible behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/sessionctx/variable/session.go` at line 2461, Add tests to cover the
tidb_plan_cache_policy session variable: add unit tests that assert the default
value equals vardef.DefTiDBPlanCachePolicy (verify reading in a fresh session),
add a GLOBAL-scope test that attempts SET @@global.tidb_plan_cache_policy =
'hint_only' and then opens a new session to confirm whether global changes take
effect (and if SetGlobal handler is missing, update the session variable
registration to implement SetGlobal for PlanCachePolicy or assert that GLOBAL
SET is a no-op), and add SQL integration tests that exercise both 'all'
(default) and 'hint_only' behaviors (e.g., prepared statement + binding cases)
to validate runtime behavior; reference tidb_plan_cache_policy, PlanCachePolicy
and vardef.DefTiDBPlanCachePolicy when locating registration and tests to
update.

IndexLookUpPushDownPolicy: vardef.DefTiDBIndexLookUpPushDownPolicy,
OptPartialOrderedIndexForTopN: vardef.DefTiDBOptPartialOrderedIndexForTopN,
}
Expand Down
1 change: 1 addition & 0 deletions pkg/sessionctx/variable/setvar_affect.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ var isHintUpdatableVerified = map[string]struct{}{
"tidb_enable_prepared_plan_cache": {},
"tidb_enable_non_prepared_plan_cache": {},
"tidb_plan_cache_max_plan_size": {},
"tidb_plan_cache_policy": {},
"tidb_opt_range_max_size": {},
"tidb_opt_advanced_join_hint": {},
"tidb_opt_prefix_index_single_scan": {},
Expand Down
4 changes: 4 additions & 0 deletions pkg/sessionctx/variable/sysvar.go
Original file line number Diff line number Diff line change
Expand Up @@ -1587,6 +1587,10 @@ var defaultSysVars = []*SysVar{
}
return err
}},
{Scope: vardef.ScopeGlobal | vardef.ScopeSession, Name: vardef.TiDBPlanCachePolicy, Value: vardef.DefTiDBPlanCachePolicy, Type: vardef.TypeEnum, PossibleValues: []string{vardef.PlanCachePolicyAll, vardef.PlanCachePolicyHintOnly}, SetSession: func(s *SessionVars, val string) error {
s.PlanCachePolicy = val
return nil
}},
{Scope: vardef.ScopeGlobal | vardef.ScopeSession, Name: vardef.TiDBSessionPlanCacheSize, Aliases: []string{vardef.TiDBPrepPlanCacheSize}, Value: strconv.FormatUint(uint64(vardef.DefTiDBSessionPlanCacheSize), 10), Type: vardef.TypeUnsigned, MinValue: 1, MaxValue: 100000, SetSession: func(s *SessionVars, val string) error {
uVal, err := strconv.ParseUint(val, 10, 64)
if err == nil {
Expand Down
9 changes: 8 additions & 1 deletion pkg/util/hint/hint.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ const (
HintTimeRange = "time_range"
// HintIgnorePlanCache is a hint to enforce ignoring plan cache
HintIgnorePlanCache = "ignore_plan_cache"
// HintUsePlanCache is a hint to enforce enabling plan cache.
HintUsePlanCache = "use_plan_cache"
// HintLimitToCop is a hint enforce pushing limit or topn to coprocessor.
HintLimitToCop = "limit_to_cop"
// HintMerge is a hint which can switch turning inline for the CTE.
Expand Down Expand Up @@ -228,7 +230,9 @@ type StmtHints struct {
ResourceGroup string
// Do not store plan in either plan cache.
IgnorePlanCache bool
WriteSlowLog bool
// Force statement to use plan cache.
UsePlanCache bool
WriteSlowLog bool

// Hint flags
HasAllowInSubqToJoinAndAggHint bool
Expand Down Expand Up @@ -276,6 +280,7 @@ func (sh *StmtHints) Clone() *StmtHints {
ForceNthPlan: sh.ForceNthPlan,
ResourceGroup: sh.ResourceGroup,
IgnorePlanCache: sh.IgnorePlanCache,
UsePlanCache: sh.UsePlanCache,
WriteSlowLog: sh.WriteSlowLog,
HasAllowInSubqToJoinAndAggHint: sh.HasAllowInSubqToJoinAndAggHint,
HasMemQuotaHint: sh.HasMemQuotaHint,
Expand Down Expand Up @@ -409,6 +414,8 @@ func ParseStmtHints(hints []*ast.TableOptimizerHint,
setVarsOffs = append(setVarsOffs, i)
case HintIgnorePlanCache:
stmtHints.IgnorePlanCache = true
case HintUsePlanCache:
stmtHints.UsePlanCache = true
case HintWriteSlowLog:
stmtHints.WriteSlowLog = true
}
Expand Down
Loading