// Copyright 2024 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 sessionexpr import ( "context" "math" "time" "github.com/pingcap/tidb/pkg/errctx" "github.com/pingcap/tidb/pkg/expression/exprctx" "github.com/pingcap/tidb/pkg/expression/expropt" "github.com/pingcap/tidb/pkg/expression/exprstatic" infoschema "github.com/pingcap/tidb/pkg/infoschema/context" "github.com/pingcap/tidb/pkg/parser/ast" "github.com/pingcap/tidb/pkg/parser/auth" "github.com/pingcap/tidb/pkg/parser/mysql" "github.com/pingcap/tidb/pkg/privilege" "github.com/pingcap/tidb/pkg/sessionctx" "github.com/pingcap/tidb/pkg/sessionctx/vardef" "github.com/pingcap/tidb/pkg/sessionctx/variable" "github.com/pingcap/tidb/pkg/types" "github.com/pingcap/tidb/pkg/util" contextutil "github.com/pingcap/tidb/pkg/util/context" "github.com/pingcap/tidb/pkg/util/intest" "github.com/pingcap/tidb/pkg/util/logutil" "github.com/pingcap/tidb/pkg/util/mathutil" "github.com/tikv/client-go/v2/oracle" "go.uber.org/zap" ) // ExprContext should implement the `ExprContext` interface. var _ exprctx.ExprContext = &ExprContext{} // ExprContext implements `ExprContext` type ExprContext struct { sctx sessionctx.Context *EvalContext } // NewExprContext creates a new ExprContext. func NewExprContext(sctx sessionctx.Context) *ExprContext { return &ExprContext{ sctx: sctx, EvalContext: NewEvalContext(sctx), } } // GetEvalCtx returns the EvalContext. func (ctx *ExprContext) GetEvalCtx() exprctx.EvalContext { return ctx.EvalContext } // GetCharsetInfo gets charset and collation for current context. func (ctx *ExprContext) GetCharsetInfo() (string, string) { return ctx.sctx.GetSessionVars().GetCharsetInfo() } // GetDefaultCollationForUTF8MB4 returns the default collation of UTF8MB4. func (ctx *ExprContext) GetDefaultCollationForUTF8MB4() string { return ctx.sctx.GetSessionVars().DefaultCollationForUTF8MB4 } // GetBlockEncryptionMode returns the variable block_encryption_mode func (ctx *ExprContext) GetBlockEncryptionMode() string { blockMode, ok := ctx.sctx.GetSessionVars().GetSystemVar(vardef.BlockEncryptionMode) if !ok { return vardef.DefBlockEncryptionMode } return blockMode } // GetSysdateIsNow returns a bool to determine whether Sysdate is an alias of Now function. // It is the value of variable `tidb_sysdate_is_now`. func (ctx *ExprContext) GetSysdateIsNow() bool { return ctx.sctx.GetSessionVars().SysdateIsNow } // GetNoopFuncsMode returns the noop function mode: OFF/ON/WARN values as 0/1/2. func (ctx *ExprContext) GetNoopFuncsMode() int { return ctx.sctx.GetSessionVars().NoopFuncsMode } // Rng is used to generate random values. func (ctx *ExprContext) Rng() *mathutil.MysqlRng { return ctx.sctx.GetSessionVars().Rng } // IsUseCache indicates whether to cache the build expression in plan cache. // If SetSkipPlanCache is invoked, it should return false. func (ctx *ExprContext) IsUseCache() bool { return ctx.sctx.GetSessionVars().StmtCtx.UseCache() } // SetSkipPlanCache sets to skip the plan cache and records the reason. func (ctx *ExprContext) SetSkipPlanCache(reason string) { ctx.sctx.GetSessionVars().StmtCtx.SetSkipPlanCache(reason) } // AllocPlanColumnID allocates column id for plan. func (ctx *ExprContext) AllocPlanColumnID() int64 { return ctx.sctx.GetSessionVars().AllocPlanColumnID() } // IsInNullRejectCheck returns whether the expression is in null reject check. func (ctx *ExprContext) IsInNullRejectCheck() bool { return false } // IsConstantPropagateCheck returns whether the ctx is in constant propagate check. func (ctx *ExprContext) IsConstantPropagateCheck() bool { return false } // GetWindowingUseHighPrecision determines whether to compute window operations without loss of precision. // see https://dev.mysql.com/doc/refman/8.0/en/window-function-optimization.html for more details. func (ctx *ExprContext) GetWindowingUseHighPrecision() bool { return ctx.sctx.GetSessionVars().WindowingUseHighPrecision } // GetGroupConcatMaxLen returns the value of the 'group_concat_max_len' system variable. func (ctx *ExprContext) GetGroupConcatMaxLen() uint64 { return ctx.sctx.GetSessionVars().GroupConcatMaxLen } // ConnectionID indicates the connection ID of the current session. // If the context is not in a session, it should return 0. func (ctx *ExprContext) ConnectionID() uint64 { return ctx.sctx.GetSessionVars().ConnectionID } // IsReadonlyUserVar checks whether the user variable is readonly. func (ctx *ExprContext) IsReadonlyUserVar(name string) bool { m := ctx.sctx.GetPlanCtx().GetReadonlyUserVarMap() _, ok := m[name] return ok } // IntoStatic turns the ExprContext into a ExprContext. func (ctx *ExprContext) IntoStatic() *exprstatic.ExprContext { return exprstatic.MakeExprContextStatic(ctx) } // EvalContext implements the `expression.EvalContext` interface to provide evaluation context in session. type EvalContext struct { sctx sessionctx.Context props expropt.OptionalEvalPropProviders } // NewEvalContext creates a new EvalContext. func NewEvalContext(sctx sessionctx.Context) *EvalContext { ctx := &EvalContext{sctx: sctx} // set all optional properties ctx.setOptionalProp(currentUserProp(sctx)) ctx.setOptionalProp(expropt.NewSessionVarsProvider(sctx)) ctx.setOptionalProp(infoSchemaProp(sctx)) ctx.setOptionalProp(expropt.KVStorePropProvider(sctx.GetStore)) ctx.setOptionalProp(sqlExecutorProp(sctx)) ctx.setOptionalProp(sequenceOperatorProp(sctx)) ctx.setOptionalProp(expropt.NewAdvisoryLockPropProvider(sctx)) ctx.setOptionalProp(expropt.DDLOwnerInfoProvider(sctx.IsDDLOwner)) ctx.setOptionalProp(expropt.PrivilegeCheckerProvider(func() expropt.PrivilegeChecker { return ctx })) // When EvalContext is created from a session, it should contain all the optional properties. intest.Assert(ctx.props.PropKeySet().IsFull()) return ctx } func (ctx *EvalContext) setOptionalProp(prop exprctx.OptionalEvalPropProvider) { intest.AssertFunc(func() bool { return !ctx.props.Contains(prop.Desc().Key()) }) ctx.props.Add(prop) } // Sctx returns the innert session context func (ctx *EvalContext) Sctx() sessionctx.Context { return ctx.sctx } // CtxID returns the context id. func (ctx *EvalContext) CtxID() uint64 { return ctx.sctx.GetSessionVars().StmtCtx.CtxID() } // SQLMode returns the sql mode func (ctx *EvalContext) SQLMode() mysql.SQLMode { return ctx.sctx.GetSessionVars().SQLMode } // TypeCtx returns the types.Context func (ctx *EvalContext) TypeCtx() (tc types.Context) { tc = ctx.sctx.GetSessionVars().StmtCtx.TypeCtx() if intest.EnableAssert { exprctx.AssertLocationWithSessionVars(tc.Location(), ctx.sctx.GetSessionVars()) } return } // ErrCtx returns the errctx.Context func (ctx *EvalContext) ErrCtx() errctx.Context { return ctx.sctx.GetSessionVars().StmtCtx.ErrCtx() } // Location returns the timezone info func (ctx *EvalContext) Location() *time.Location { tc := ctx.TypeCtx() return tc.Location() } // AppendWarning append warnings to the context. func (ctx *EvalContext) AppendWarning(err error) { ctx.sctx.GetSessionVars().StmtCtx.AppendWarning(err) } // AppendNote appends notes to the context. func (ctx *EvalContext) AppendNote(err error) { ctx.sctx.GetSessionVars().StmtCtx.AppendNote(err) } // WarningCount gets warning count. func (ctx *EvalContext) WarningCount() int { return int(ctx.sctx.GetSessionVars().StmtCtx.WarningCount()) } // TruncateWarnings truncates warnings begin from start and returns the truncated warnings. func (ctx *EvalContext) TruncateWarnings(start int) []contextutil.SQLWarn { return ctx.sctx.GetSessionVars().StmtCtx.TruncateWarnings(start) } // CopyWarnings copies the warnings to dst func (ctx *EvalContext) CopyWarnings(dst []contextutil.SQLWarn) []contextutil.SQLWarn { return ctx.sctx.GetSessionVars().StmtCtx.CopyWarnings(dst) } // CurrentDB returns the current database name func (ctx *EvalContext) CurrentDB() string { return ctx.sctx.GetSessionVars().CurrentDB } // CurrentTime returns the current time func (ctx *EvalContext) CurrentTime() (time.Time, error) { return getStmtTimestamp(ctx.sctx) } // GetMaxAllowedPacket returns the value of the 'max_allowed_packet' system variable. func (ctx *EvalContext) GetMaxAllowedPacket() uint64 { return ctx.sctx.GetSessionVars().MaxAllowedPacket } // GetTiDBRedactLog returns the value of the 'tidb_redact_log' system variable. func (ctx *EvalContext) GetTiDBRedactLog() string { return ctx.sctx.GetSessionVars().EnableRedactLog } // GetDefaultWeekFormatMode returns the value of the 'default_week_format' system variable. func (ctx *EvalContext) GetDefaultWeekFormatMode() string { mode, ok := ctx.sctx.GetSessionVars().GetSystemVar(vardef.DefaultWeekFormat) if !ok || mode == "" { return "0" } return mode } // GetDivPrecisionIncrement returns the specified value of DivPrecisionIncrement. func (ctx *EvalContext) GetDivPrecisionIncrement() int { return ctx.sctx.GetSessionVars().GetDivPrecisionIncrement() } // GetOptionalPropSet gets the optional property set from context func (ctx *EvalContext) GetOptionalPropSet() exprctx.OptionalEvalPropKeySet { return ctx.props.PropKeySet() } // GetOptionalPropProvider gets the optional property provider by key func (ctx *EvalContext) GetOptionalPropProvider(key exprctx.OptionalEvalPropKey) (exprctx.OptionalEvalPropProvider, bool) { return ctx.props.Get(key) } // RequestVerification verifies user privilege func (ctx *EvalContext) RequestVerification(db, table, column string, priv mysql.PrivilegeType) bool { checker := privilege.GetPrivilegeManager(ctx.sctx) if checker == nil { return true } return checker.RequestVerification(ctx.sctx.GetSessionVars().ActiveRoles, db, table, column, priv) } // RequestDynamicVerification verifies user privilege for a DYNAMIC privilege. func (ctx *EvalContext) RequestDynamicVerification(privName string, grantable bool) bool { checker := privilege.GetPrivilegeManager(ctx.sctx) if checker == nil { return true } return checker.RequestDynamicVerification(ctx.sctx.GetSessionVars().ActiveRoles, privName, grantable) } // GetParamValue returns the value of the parameter by index. func (ctx *EvalContext) GetParamValue(idx int) (types.Datum, error) { params := ctx.sctx.GetSessionVars().PlanCacheParams.AllParamValues() if idx >= len(params) { return types.Datum{}, exprctx.ErrParamIndexExceedParamCounts } return params[idx], nil } // GetUserVarsReader returns the user variables. func (ctx *EvalContext) GetUserVarsReader() variable.UserVarsReader { return ctx.sctx.GetSessionVars().UserVars } // IntoStatic turns the EvalContext into a EvalContext. func (ctx *EvalContext) IntoStatic() *exprstatic.EvalContext { return exprstatic.MakeEvalContextStatic(ctx) } func getStmtTimestamp(ctx sessionctx.Context) (time.Time, error) { if ctx != nil { staleTSO, err := ctx.GetSessionVars().StmtCtx.GetStaleTSO() if staleTSO != 0 && err == nil { return oracle.GetTimeFromTS(staleTSO), nil } else if err != nil { logutil.BgLogger().Error("get stale tso failed", zap.Error(err)) } } now := time.Now() if ctx == nil { return now, nil } sessionVars := ctx.GetSessionVars() timestampStr, err := sessionVars.GetSessionOrGlobalSystemVar(context.Background(), "timestamp") if err != nil { return now, err } timestamp, err := types.StrToFloat(sessionVars.StmtCtx.TypeCtx(), timestampStr, false) if err != nil { return time.Time{}, err } seconds, fractionalSeconds := math.Modf(timestamp) return time.Unix(int64(seconds), int64(fractionalSeconds*float64(time.Second))), nil } func currentUserProp(sctx sessionctx.Context) exprctx.OptionalEvalPropProvider { return expropt.CurrentUserPropProvider(func() (*auth.UserIdentity, []*auth.RoleIdentity) { vars := sctx.GetSessionVars() return vars.User, vars.ActiveRoles }) } func infoSchemaProp(sctx sessionctx.Context) expropt.InfoSchemaPropProvider { return func(isDomain bool) infoschema.MetaOnlyInfoSchema { if isDomain { return sctx.GetLatestInfoSchema() } return sctx.GetInfoSchema() } } func sqlExecutorProp(sctx sessionctx.Context) expropt.SQLExecutorPropProvider { return func() (expropt.SQLExecutor, error) { return sctx.GetRestrictedSQLExecutor(), nil } } type sequenceOperator struct { sctx sessionctx.Context db string name string tbl util.SequenceTable } func (s *sequenceOperator) GetSequenceID() int64 { return s.tbl.GetSequenceID() } func (s *sequenceOperator) GetSequenceNextVal() (int64, error) { return s.tbl.GetSequenceNextVal(s.sctx, s.db, s.name) } func (s *sequenceOperator) SetSequenceVal(newVal int64) (int64, bool, error) { return s.tbl.SetSequenceVal(s.sctx, newVal, s.db, s.name) } func sequenceOperatorProp(sctx sessionctx.Context) expropt.SequenceOperatorProvider { return func(db, name string) (expropt.SequenceOperator, error) { sequence, err := util.GetSequenceByName(sctx.GetInfoSchema(), ast.NewCIStr(db), ast.NewCIStr(name)) if err != nil { return nil, err } return &sequenceOperator{sctx, db, name, sequence}, nil } } var _ exprctx.StaticConvertibleExprContext = &ExprContext{} // GetStaticConvertibleEvalContext implements context.StaticConvertibleExprContext. func (ctx *ExprContext) GetStaticConvertibleEvalContext() exprctx.StaticConvertibleEvalContext { return ctx.EvalContext } // GetPlanCacheTracker implements context.StaticConvertibleExprContext. func (ctx *ExprContext) GetPlanCacheTracker() *contextutil.PlanCacheTracker { return &ctx.sctx.GetSessionVars().StmtCtx.PlanCacheTracker } // GetLastPlanColumnID implements context.StaticConvertibleExprContext. func (ctx *ExprContext) GetLastPlanColumnID() int64 { return ctx.sctx.GetSessionVars().PlanColumnID.Load() } var _ exprctx.StaticConvertibleEvalContext = &EvalContext{} // AllParamValues implements context.StaticConvertibleEvalContext. func (ctx *EvalContext) AllParamValues() []types.Datum { return ctx.sctx.GetSessionVars().PlanCacheParams.AllParamValues() } // GetWarnHandler implements context.StaticConvertibleEvalContext. func (ctx *EvalContext) GetWarnHandler() contextutil.WarnHandler { return ctx.sctx.GetSessionVars().StmtCtx.WarnHandler }