Repository: pingcap/tidb
File: pkg/planner/core/plan_cacheable_checker.go (Go)

Code before:
			checker.withScopeOffset = append(checker.withScopeOffset, len(checker.cteCanUsed))
		}
	case *ast.InsertStmt:
		if node.Select == nil {
			nRows := len(node.Lists)
			nCols := 0
			if len(node.Lists) > 0 { // avoid index-out-of-range
				nCols = len(node.Lists[0])
			}
			if nRows*nCols > checker.maxNumParam { // to save memory
				checker.cacheable = false
				checker.reason = "too many values in the insert statement"
				return in, true
			}
		}
	case *ast.PatternInExpr:
		checker.sumInListLen += len(node.List)
		if checker.sumInListLen > checker.maxNumParam { // to save memory
			checker.cacheable = false
			checker.reason = "too many values in in-list"
			return in, true
		}
	case *ast.VariableExpr:
		checker.cacheable = false
		checker.reason = "query has user-defined variables is un-cacheable"
		return in, true
	case *ast.ExistsSubqueryExpr:
		return in, checker.skipForSubqueryDisabled()
	case *ast.CommonTableExpression:
		checker.cteOffset = append(checker.cteOffset, len(checker.cteCanUsed))
		if node.IsRecursive {
			checker.cteCanUsed = append(checker.cteCanUsed, node.Name.L)
		}
		return in, false
	case *ast.SubqueryExpr:
		checker.cteOffset = append(checker.cteOffset, len(checker.cteCanUsed))
		return in, checker.skipForSubqueryDisabled()
	case *ast.FuncCallExpr:
		if _, found := expression.UnCacheableFunctions[node.FnName.L]; found {
			checker.cacheable = false
			checker.reason = fmt.Sprintf("query has '%v' is un-cacheable", node.FnName.L)
			return in, true
		}
	case *ast.OrderByClause:
		for _, item := range node.Items {
			if _, isParamMarker := item.Expr.(*driver.ParamMarkerExpr); isParamMarker {
				checker.cacheable = false
				checker.reason = "query has 'order by ?' is un-cacheable"
				return in, true
			}
		}

Review comment (suggestion):
I think we don't need this.
e.g.
<img width="1193" height="457" alt="Image" src="https://github.com/user-attachments/assets/04926b5a-f383-4743-8284-0151ecff0cf1" />
The `*ast.CommonTableExpression` is the definition of one CTE. There's no need to maintain the stack when we enter/leave it.

Code after:
				checker.cacheable = false
				checker.reason = "too many values in the insert statement"
				return in, true
			}
		}
	case *ast.PatternInExpr:
		checker.sumInListLen += len(node.List)
		if checker.sumInListLen > checker.maxNumParam { // to save memory
			checker.cacheable = false
			checker.reason = "too many values in in-list"
			return in, true
		}
	case *ast.VariableExpr:
		checker.cacheable = false
		checker.reason = "query has user-defined variables is un-cacheable"
		return in, true
	case *ast.ExistsSubqueryExpr:
		return in, checker.skipForSubqueryDisabled()
	case *ast.CommonTableExpression:
		if node.IsRecursive {
			// Recursive CTE can reference itself, so expose the name before traversing Query.
			checker.cteCanUsed = append(checker.cteCanUsed, node.Name.L)
		}
		return in, false
	case *ast.SubqueryExpr:
		return in, checker.skipForSubqueryDisabled()
	case *ast.FuncCallExpr:
		if _, found := expression.UnCacheableFunctions[node.FnName.L]; found {
			checker.cacheable = false
			checker.reason = fmt.Sprintf("query has '%v' is un-cacheable", node.FnName.L)
			return in, true
		}
	case *ast.OrderByClause:
		for _, item := range node.Items {
			if _, isParamMarker := item.Expr.(*driver.ParamMarkerExpr); isParamMarker {
				checker.cacheable = false
				checker.reason = "query has 'order by ?' is un-cacheable"
				return in, true
			}
		}
	case *ast.GroupByClause:
		for _, item := range node.Items {
			if _, isParamMarker := item.Expr.(*driver.ParamMarkerExpr); isParamMarker {
				checker.cacheable = false
				checker.reason = "query has 'group by ?' is un-cacheable"
				return in, true
			}
		}
	case *ast.Limit:
		if node.Count != nil {
			if _, isParamMarker := node.Count.(*driver.ParamMarkerExpr); isParamMarker && !checker.sctx.GetSessionVars().EnablePlanCacheForParamLimit {