From 225b3ae30a1085ebcb19bfbdb44e5353bcd4aea5 Mon Sep 17 00:00:00 2001 From: Jack Yu Date: Tue, 9 Oct 2018 15:24:25 +0800 Subject: [PATCH] executor: print arguments in execute statement in log files (#7684) --- ast/misc.go | 8 +++ ddl/db_change_test.go | 2 +- executor/adapter.go | 5 +- executor/executor.go | 110 +++++++++++++++++++++++++++++ executor/prepared.go | 122 ++++----------------------------- expression/builtin_other.go | 3 +- planner/core/common_plans.go | 20 ++---- session/session.go | 7 +- session/session_test.go | 4 +- sessionctx/variable/session.go | 30 ++++++-- 10 files changed, 172 insertions(+), 139 deletions(-) diff --git a/ast/misc.go b/ast/misc.go index 620a9abf34c12..c4493a24ccd66 100644 --- a/ast/misc.go +++ b/ast/misc.go @@ -184,6 +184,14 @@ func (n *DeallocateStmt) Accept(v Visitor) (Node, bool) { return v.Leave(n) } +// Prepared represents a prepared statement. +type Prepared struct { + Stmt StmtNode + Params []*ParamMarkerExpr + SchemaVersion int64 + UseCache bool +} + // ExecuteStmt is a statement to execute PreparedStmt. // See https://dev.mysql.com/doc/refman/5.7/en/execute.html type ExecuteStmt struct { diff --git a/ddl/db_change_test.go b/ddl/db_change_test.go index b8550c4bcdd83..79837b9765ce9 100644 --- a/ddl/db_change_test.go +++ b/ddl/db_change_test.go @@ -296,7 +296,7 @@ func (t *testExecInfo) compileSQL(idx int) (err error) { ctx := context.TODO() se.PrepareTxnCtx(ctx) sctx := se.(sessionctx.Context) - if err = executor.ResetStmtCtx(sctx, c.rawStmt); err != nil { + if err = executor.ResetContextOfStmt(sctx, c.rawStmt); err != nil { return errors.Trace(err) } c.stmt, err = compiler.Compile(ctx, c.rawStmt) diff --git a/executor/adapter.go b/executor/adapter.go index 3b9d4cad470f5..0f7c73d9a83ee 100644 --- a/executor/adapter.go +++ b/executor/adapter.go @@ -323,7 +323,6 @@ func (a *ExecStmt) buildExecutor(ctx sessionctx.Context) (Executor, error) { if err != nil { return nil, errors.Trace(err) } - a.Text = executorExec.stmt.Text() a.isPreparedStmt = true a.Plan = executorExec.plan e = executorExec.stmtExec @@ -350,9 +349,9 @@ func (a *ExecStmt) LogSlowQuery(txnTS uint64, succ bool) { if len(sql) > int(cfg.Log.QueryLogMaxLen) { sql = fmt.Sprintf("%.*q(len:%d)", cfg.Log.QueryLogMaxLen, sql, len(a.Text)) } - sql = QueryReplacer.Replace(sql) - sessVars := a.Ctx.GetSessionVars() + sql = QueryReplacer.Replace(sql) + sessVars.GetExecuteArgumentsInfo() + connID := sessVars.ConnectionID currentDB := sessVars.CurrentDB var tableIDs, indexIDs string diff --git a/executor/executor.go b/executor/executor.go index fe543c4d7eebf..2ea2381db9ec8 100644 --- a/executor/executor.go +++ b/executor/executor.go @@ -22,6 +22,7 @@ import ( "github.com/cznic/mathutil" "github.com/pingcap/tidb/ast" + "github.com/pingcap/tidb/config" "github.com/pingcap/tidb/domain" "github.com/pingcap/tidb/expression" "github.com/pingcap/tidb/infoschema" @@ -30,6 +31,8 @@ import ( "github.com/pingcap/tidb/mysql" plannercore "github.com/pingcap/tidb/planner/core" "github.com/pingcap/tidb/sessionctx" + "github.com/pingcap/tidb/sessionctx/stmtctx" + "github.com/pingcap/tidb/sessionctx/variable" "github.com/pingcap/tidb/table" "github.com/pingcap/tidb/tablecodec" "github.com/pingcap/tidb/terror" @@ -37,6 +40,7 @@ import ( "github.com/pingcap/tidb/util/admin" "github.com/pingcap/tidb/util/chunk" "github.com/pingcap/tidb/util/execdetails" + "github.com/pingcap/tidb/util/memory" "github.com/pkg/errors" log "github.com/sirupsen/logrus" "golang.org/x/net/context" @@ -1161,3 +1165,109 @@ func (e *UnionExec) Close() error { e.resourcePools = nil return errors.Trace(e.baseExecutor.Close()) } + +// ResetContextOfStmt resets the StmtContext and session variables. +// Before every execution, we must clear statement context. +func ResetContextOfStmt(ctx sessionctx.Context, s ast.StmtNode) (err error) { + vars := ctx.GetSessionVars() + sc := new(stmtctx.StatementContext) + sc.TimeZone = vars.Location() + sc.MemTracker = memory.NewTracker(s.Text(), vars.MemQuotaQuery) + switch config.GetGlobalConfig().OOMAction { + case config.OOMActionCancel: + sc.MemTracker.SetActionOnExceed(&memory.PanicOnExceed{}) + case config.OOMActionLog: + sc.MemTracker.SetActionOnExceed(&memory.LogOnExceed{}) + default: + sc.MemTracker.SetActionOnExceed(&memory.LogOnExceed{}) + } + + if execStmt, ok := s.(*ast.ExecuteStmt); ok { + s, err = getPreparedStmt(execStmt, vars) + } + // TODO: Many same bool variables here. + // We should set only two variables ( + // IgnoreErr and StrictSQLMode) to avoid setting the same bool variables and + // pushing them down to TiKV as flags. + switch stmt := s.(type) { + case *ast.UpdateStmt: + sc.InUpdateOrDeleteStmt = true + sc.DupKeyAsWarning = stmt.IgnoreErr + sc.BadNullAsWarning = !vars.StrictSQLMode || stmt.IgnoreErr + sc.TruncateAsWarning = !vars.StrictSQLMode || stmt.IgnoreErr + sc.DividedByZeroAsWarning = !vars.StrictSQLMode || stmt.IgnoreErr + sc.IgnoreZeroInDate = !vars.StrictSQLMode || stmt.IgnoreErr + sc.Priority = stmt.Priority + case *ast.DeleteStmt: + sc.InUpdateOrDeleteStmt = true + sc.DupKeyAsWarning = stmt.IgnoreErr + sc.BadNullAsWarning = !vars.StrictSQLMode || stmt.IgnoreErr + sc.TruncateAsWarning = !vars.StrictSQLMode || stmt.IgnoreErr + sc.DividedByZeroAsWarning = !vars.StrictSQLMode || stmt.IgnoreErr + sc.IgnoreZeroInDate = !vars.StrictSQLMode || stmt.IgnoreErr + sc.Priority = stmt.Priority + case *ast.InsertStmt: + sc.InInsertStmt = true + sc.DupKeyAsWarning = stmt.IgnoreErr + sc.BadNullAsWarning = !vars.StrictSQLMode || stmt.IgnoreErr + sc.TruncateAsWarning = !vars.StrictSQLMode || stmt.IgnoreErr + sc.DividedByZeroAsWarning = !vars.StrictSQLMode || stmt.IgnoreErr + sc.IgnoreZeroInDate = !vars.StrictSQLMode || stmt.IgnoreErr + sc.Priority = stmt.Priority + case *ast.CreateTableStmt, *ast.AlterTableStmt: + // Make sure the sql_mode is strict when checking column default value. + case *ast.LoadDataStmt: + sc.DupKeyAsWarning = true + sc.BadNullAsWarning = true + sc.TruncateAsWarning = !vars.StrictSQLMode + case *ast.SelectStmt: + sc.InSelectStmt = true + + // see https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sql-mode-strict + // said "For statements such as SELECT that do not change data, invalid values + // generate a warning in strict mode, not an error." + // and https://dev.mysql.com/doc/refman/5.7/en/out-of-range-and-overflow.html + sc.OverflowAsWarning = true + + // Return warning for truncate error in selection. + sc.TruncateAsWarning = true + sc.IgnoreZeroInDate = true + if opts := stmt.SelectStmtOpts; opts != nil { + sc.Priority = opts.Priority + sc.NotFillCache = !opts.SQLCache + } + sc.PadCharToFullLength = ctx.GetSessionVars().SQLMode.HasPadCharToFullLengthMode() + case *ast.ShowStmt: + sc.IgnoreTruncate = true + sc.IgnoreZeroInDate = true + if stmt.Tp == ast.ShowWarnings || stmt.Tp == ast.ShowErrors { + sc.InShowWarning = true + sc.SetWarnings(vars.StmtCtx.GetWarnings()) + } + default: + sc.IgnoreTruncate = true + sc.IgnoreZeroInDate = true + } + vars.PreparedParams = vars.PreparedParams[:0] + if !vars.InRestrictedSQL { + if priority := mysql.PriorityEnum(atomic.LoadInt32(&variable.ForcePriority)); priority != mysql.NoPriority { + sc.Priority = priority + } + } + if vars.LastInsertID > 0 { + vars.PrevLastInsertID = vars.LastInsertID + vars.LastInsertID = 0 + } + vars.ResetPrevAffectedRows() + err = vars.SetSystemVar("warning_count", fmt.Sprintf("%d", vars.StmtCtx.NumWarnings(false))) + if err != nil { + return errors.Trace(err) + } + err = vars.SetSystemVar("error_count", fmt.Sprintf("%d", vars.StmtCtx.NumWarnings(true))) + if err != nil { + return errors.Trace(err) + } + vars.InsertID = 0 + vars.StmtCtx = sc + return +} diff --git a/executor/prepared.go b/executor/prepared.go index 2e629c7971661..962d35db6ae3c 100644 --- a/executor/prepared.go +++ b/executor/prepared.go @@ -16,22 +16,16 @@ package executor import ( "math" "sort" - "sync/atomic" - "fmt" "github.com/pingcap/tidb/ast" - "github.com/pingcap/tidb/config" "github.com/pingcap/tidb/expression" "github.com/pingcap/tidb/infoschema" - "github.com/pingcap/tidb/mysql" "github.com/pingcap/tidb/parser" plannercore "github.com/pingcap/tidb/planner/core" "github.com/pingcap/tidb/sessionctx" - "github.com/pingcap/tidb/sessionctx/stmtctx" "github.com/pingcap/tidb/sessionctx/variable" "github.com/pingcap/tidb/types" "github.com/pingcap/tidb/util/chunk" - "github.com/pingcap/tidb/util/memory" "github.com/pingcap/tidb/util/sqlexec" "github.com/pkg/errors" "golang.org/x/net/context" @@ -152,7 +146,7 @@ func (e *PrepareExec) Next(ctx context.Context, chk *chunk.Chunk) error { for i := 0; i < e.ParamCount; i++ { sorter.markers[i].Order = i } - prepared := &plannercore.Prepared{ + prepared := &ast.Prepared{ Stmt: stmt, Params: sorter.markers, SchemaVersion: e.is.SchemaMetaVersion(), @@ -219,9 +213,6 @@ func (e *ExecuteExec) Build() error { return errors.Trace(b.err) } e.stmtExec = stmtExec - if err = ResetStmtCtx(e.ctx, e.stmt); err != nil { - return err - } CountStmtNode(e.stmt, e.ctx.GetSessionVars().InRestrictedSQL) logExpensiveQuery(e.stmt, e.plan) return nil @@ -249,6 +240,9 @@ func (e *DeallocateExec) Next(ctx context.Context, chk *chunk.Chunk) error { // CompileExecutePreparedStmt compiles a session Execute command to a stmt.Statement. func CompileExecutePreparedStmt(ctx sessionctx.Context, ID uint32, args ...interface{}) (ast.Statement, error) { execStmt := &ast.ExecuteStmt{ExecID: ID} + if err := ResetContextOfStmt(ctx, execStmt); err != nil { + return nil, err + } execStmt.UsingVars = make([]ast.ExprNode, len(args)) for i, val := range args { execStmt.UsingVars[i] = ast.NewValueExpr(val) @@ -265,110 +259,22 @@ func CompileExecutePreparedStmt(ctx sessionctx.Context, ID uint32, args ...inter StmtNode: execStmt, Ctx: ctx, } - if prepared, ok := ctx.GetSessionVars().PreparedStmts[ID].(*plannercore.Prepared); ok { + if prepared, ok := ctx.GetSessionVars().PreparedStmts[ID]; ok { stmt.Text = prepared.Stmt.Text() } return stmt, nil } -// ResetStmtCtx resets the StmtContext. -// Before every execution, we must clear statement context. -func ResetStmtCtx(ctx sessionctx.Context, s ast.StmtNode) (err error) { - sessVars := ctx.GetSessionVars() - sc := new(stmtctx.StatementContext) - sc.TimeZone = sessVars.Location() - sc.MemTracker = memory.NewTracker(s.Text(), sessVars.MemQuotaQuery) - switch config.GetGlobalConfig().OOMAction { - case config.OOMActionCancel: - sc.MemTracker.SetActionOnExceed(&memory.PanicOnExceed{}) - case config.OOMActionLog: - sc.MemTracker.SetActionOnExceed(&memory.LogOnExceed{}) - default: - sc.MemTracker.SetActionOnExceed(&memory.LogOnExceed{}) - } - - // TODO: Many same bool variables here. - // We should set only two variables ( - // IgnoreErr and StrictSQLMode) to avoid setting the same bool variables and - // pushing them down to TiKV as flags. - switch stmt := s.(type) { - case *ast.UpdateStmt: - sc.InUpdateOrDeleteStmt = true - sc.DupKeyAsWarning = stmt.IgnoreErr - sc.BadNullAsWarning = !sessVars.StrictSQLMode || stmt.IgnoreErr - sc.TruncateAsWarning = !sessVars.StrictSQLMode || stmt.IgnoreErr - sc.DividedByZeroAsWarning = !sessVars.StrictSQLMode || stmt.IgnoreErr - sc.IgnoreZeroInDate = !sessVars.StrictSQLMode || stmt.IgnoreErr - sc.Priority = stmt.Priority - case *ast.DeleteStmt: - sc.InUpdateOrDeleteStmt = true - sc.DupKeyAsWarning = stmt.IgnoreErr - sc.BadNullAsWarning = !sessVars.StrictSQLMode || stmt.IgnoreErr - sc.TruncateAsWarning = !sessVars.StrictSQLMode || stmt.IgnoreErr - sc.DividedByZeroAsWarning = !sessVars.StrictSQLMode || stmt.IgnoreErr - sc.IgnoreZeroInDate = !sessVars.StrictSQLMode || stmt.IgnoreErr - sc.Priority = stmt.Priority - case *ast.InsertStmt: - sc.InInsertStmt = true - sc.DupKeyAsWarning = stmt.IgnoreErr - sc.BadNullAsWarning = !sessVars.StrictSQLMode || stmt.IgnoreErr - sc.TruncateAsWarning = !sessVars.StrictSQLMode || stmt.IgnoreErr - sc.DividedByZeroAsWarning = !sessVars.StrictSQLMode || stmt.IgnoreErr - sc.IgnoreZeroInDate = !sessVars.StrictSQLMode || stmt.IgnoreErr - sc.Priority = stmt.Priority - case *ast.CreateTableStmt, *ast.AlterTableStmt: - // Make sure the sql_mode is strict when checking column default value. - case *ast.LoadDataStmt: - sc.DupKeyAsWarning = true - sc.BadNullAsWarning = true - sc.TruncateAsWarning = !sessVars.StrictSQLMode - case *ast.SelectStmt: - sc.InSelectStmt = true - - // see https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sql-mode-strict - // said "For statements such as SELECT that do not change data, invalid values - // generate a warning in strict mode, not an error." - // and https://dev.mysql.com/doc/refman/5.7/en/out-of-range-and-overflow.html - sc.OverflowAsWarning = true - - // Return warning for truncate error in selection. - sc.TruncateAsWarning = true - sc.IgnoreZeroInDate = true - if opts := stmt.SelectStmtOpts; opts != nil { - sc.Priority = opts.Priority - sc.NotFillCache = !opts.SQLCache +func getPreparedStmt(stmt *ast.ExecuteStmt, vars *variable.SessionVars) (ast.StmtNode, error) { + execID := stmt.ExecID + ok := false + if stmt.Name != "" { + if execID, ok = vars.PreparedStmtNameToID[stmt.Name]; !ok { + return nil, plannercore.ErrStmtNotFound } - sc.PadCharToFullLength = ctx.GetSessionVars().SQLMode.HasPadCharToFullLengthMode() - case *ast.ShowStmt: - sc.IgnoreTruncate = true - sc.IgnoreZeroInDate = true - if stmt.Tp == ast.ShowWarnings || stmt.Tp == ast.ShowErrors { - sc.InShowWarning = true - sc.SetWarnings(sessVars.StmtCtx.GetWarnings()) - } - default: - sc.IgnoreTruncate = true - sc.IgnoreZeroInDate = true } - if !sessVars.InRestrictedSQL { - if priority := mysql.PriorityEnum(atomic.LoadInt32(&variable.ForcePriority)); priority != mysql.NoPriority { - sc.Priority = priority - } - } - if sessVars.LastInsertID > 0 { - sessVars.PrevLastInsertID = sessVars.LastInsertID - sessVars.LastInsertID = 0 - } - sessVars.ResetPrevAffectedRows() - err = sessVars.SetSystemVar("warning_count", fmt.Sprintf("%d", sessVars.StmtCtx.NumWarnings(false))) - if err != nil { - return errors.Trace(err) - } - err = sessVars.SetSystemVar("error_count", fmt.Sprintf("%d", sessVars.StmtCtx.NumWarnings(true))) - if err != nil { - return errors.Trace(err) + if prepared, ok := vars.PreparedStmts[execID]; ok { + return prepared.Stmt, nil } - sessVars.InsertID = 0 - sessVars.StmtCtx = sc - return + return nil, plannercore.ErrStmtNotFound } diff --git a/expression/builtin_other.go b/expression/builtin_other.go index fa8f9130f1c7d..8afc8e9c0cce6 100644 --- a/expression/builtin_other.go +++ b/expression/builtin_other.go @@ -771,8 +771,7 @@ func (b *builtinGetParamStringSig) evalString(row chunk.Row) (string, bool, erro } v := sessionVars.PreparedParams[idx] - dt := v.(types.Datum) - str, err := (&dt).ToString() + str, err := v.ToString() if err != nil { return "", true, nil } diff --git a/planner/core/common_plans.go b/planner/core/common_plans.go index d629d611cc70c..6a4db7fe3795b 100644 --- a/planner/core/common_plans.go +++ b/planner/core/common_plans.go @@ -126,14 +126,6 @@ type Prepare struct { SQLText string } -// Prepared represents a prepared statement. -type Prepared struct { - Stmt ast.StmtNode - Params []*ast.ParamMarkerExpr - SchemaVersion int64 - UseCache bool -} - // Execute represents prepare plan. type Execute struct { baseSchemaProducer @@ -150,26 +142,22 @@ func (e *Execute) optimizePreparedPlan(ctx sessionctx.Context, is infoschema.Inf if e.Name != "" { e.ExecID = vars.PreparedStmtNameToID[e.Name] } - v := vars.PreparedStmts[e.ExecID] - if v == nil { + prepared, ok := vars.PreparedStmts[e.ExecID] + if !ok { return errors.Trace(ErrStmtNotFound) } - prepared := v.(*Prepared) if len(prepared.Params) != len(e.UsingVars) { return errors.Trace(ErrWrongParamCount) } - if cap(vars.PreparedParams) < len(e.UsingVars) { - vars.PreparedParams = make([]interface{}, len(e.UsingVars)) - } for i, usingVar := range e.UsingVars { val, err := usingVar.Eval(chunk.Row{}) if err != nil { return errors.Trace(err) } prepared.Params[i].SetDatum(val) - vars.PreparedParams[i] = val + vars.PreparedParams = append(vars.PreparedParams, val) } if prepared.SchemaVersion != is.SchemaMetaVersion() { // If the schema version has changed we need to preprocess it again, @@ -189,7 +177,7 @@ func (e *Execute) optimizePreparedPlan(ctx sessionctx.Context, is infoschema.Inf return nil } -func (e *Execute) getPhysicalPlan(ctx sessionctx.Context, is infoschema.InfoSchema, prepared *Prepared) (Plan, error) { +func (e *Execute) getPhysicalPlan(ctx sessionctx.Context, is infoschema.InfoSchema, prepared *ast.Prepared) (Plan, error) { var cacheKey kvcache.Key sessionVars := ctx.GetSessionVars() sessionVars.StmtCtx.UseCache = prepared.UseCache diff --git a/session/session.go b/session/session.go index 20ab58c722808..953d024eaba07 100644 --- a/session/session.go +++ b/session/session.go @@ -54,7 +54,7 @@ import ( "github.com/pingcap/tidb/util/chunk" "github.com/pingcap/tidb/util/kvcache" "github.com/pingcap/tidb/util/timeutil" - binlog "github.com/pingcap/tipb/go-binlog" + "github.com/pingcap/tipb/go-binlog" "github.com/pkg/errors" log "github.com/sirupsen/logrus" "golang.org/x/net/context" @@ -796,7 +796,7 @@ func (s *session) execute(ctx context.Context, sql string) (recordSets []ast.Rec // Step2: Transform abstract syntax tree to a physical plan(stored in executor.ExecStmt). startTS = time.Now() // Some executions are done in compile stage, so we reset them before compile. - if err := executor.ResetStmtCtx(s, stmtNode); err != nil { + if err := executor.ResetContextOfStmt(s, stmtNode); err != nil { return nil, errors.Trace(err) } stmt, err := compiler.Compile(ctx, stmtNode) @@ -1431,6 +1431,7 @@ func logStmt(node ast.StmtNode, vars *variable.SessionVars) { func logQuery(query string, vars *variable.SessionVars) { if atomic.LoadUint32(&variable.ProcessGeneralLog) != 0 && !vars.InRestrictedSQL { query = executor.QueryReplacer.Replace(query) - log.Infof("[GENERAL_LOG] con:%d user:%s schema_ver:%d start_ts:%d sql:%s", vars.ConnectionID, vars.User, vars.TxnCtx.SchemaVersion, vars.TxnCtx.StartTS, query) + log.Infof("[GENERAL_LOG] con:%d user:%s schema_ver:%d start_ts:%d sql:%s%s", + vars.ConnectionID, vars.User, vars.TxnCtx.SchemaVersion, vars.TxnCtx.StartTS, query, vars.GetExecuteArgumentsInfo()) } } diff --git a/session/session_test.go b/session/session_test.go index f844a532a8706..30f0f7a745eec 100644 --- a/session/session_test.go +++ b/session/session_test.go @@ -42,7 +42,7 @@ import ( "github.com/pingcap/tidb/util/testkit" "github.com/pingcap/tidb/util/testleak" "github.com/pingcap/tidb/util/testutil" - binlog "github.com/pingcap/tipb/go-binlog" + "github.com/pingcap/tipb/go-binlog" "golang.org/x/net/context" "google.golang.org/grpc" ) @@ -423,7 +423,7 @@ func (s *testSessionSuite) TestRetryCleanTxn(c *C) { stmtNode, err := parser.New().ParseOneStmt("insert retrytxn values (2, 'a')", "", "") c.Assert(err, IsNil) stmt, _ := session.Compile(context.TODO(), tk.Se, stmtNode) - executor.ResetStmtCtx(tk.Se, stmtNode) + executor.ResetContextOfStmt(tk.Se, stmtNode) history.Add(0, stmt, tk.Se.GetSessionVars().StmtCtx) _, err = tk.Exec("commit") c.Assert(err, NotNil) diff --git a/sessionctx/variable/session.go b/sessionctx/variable/session.go index 40afea3b9a4d6..26c0fbefb031d 100644 --- a/sessionctx/variable/session.go +++ b/sessionctx/variable/session.go @@ -15,11 +15,13 @@ package variable import ( "crypto/tls" + "fmt" "strings" "sync" "sync/atomic" "time" + "github.com/pingcap/tidb/ast" "github.com/pingcap/tidb/config" "github.com/pingcap/tidb/kv" "github.com/pingcap/tidb/meta/autoid" @@ -164,12 +166,12 @@ type SessionVars struct { // systems variables, don't modify it directly, use GetSystemVar/SetSystemVar method. systems map[string]string // PreparedStmts stores prepared statement. - PreparedStmts map[uint32]interface{} + PreparedStmts map[uint32]*ast.Prepared PreparedStmtNameToID map[string]uint32 // preparedStmtID is id of prepared statement. preparedStmtID uint32 // params for prepared statements - PreparedParams []interface{} + PreparedParams []types.Datum // retry information RetryInfo *RetryInfo @@ -301,9 +303,9 @@ func NewSessionVars() *SessionVars { vars := &SessionVars{ Users: make(map[string]string), systems: make(map[string]string), - PreparedStmts: make(map[uint32]interface{}), + PreparedStmts: make(map[uint32]*ast.Prepared), PreparedStmtNameToID: make(map[string]uint32), - PreparedParams: make([]interface{}, 10), + PreparedParams: make([]types.Datum, 0, 10), TxnCtx: &TransactionContext{}, KVVars: kv.NewVariables(), RetryInfo: &RetryInfo{}, @@ -444,6 +446,26 @@ func (s *SessionVars) ResetPrevAffectedRows() { } } +// GetExecuteArgumentsInfo gets the argument list as a string of execute statement. +func (s *SessionVars) GetExecuteArgumentsInfo() string { + if len(s.PreparedParams) == 0 { + return "" + } + args := make([]string, 0, len(s.PreparedParams)) + for _, v := range s.PreparedParams { + if v.IsNull() { + args = append(args, "") + } else { + str, err := v.ToString() + if err != nil { + terror.Log(err) + } + args = append(args, str) + } + } + return fmt.Sprintf(" [arguments: %s]", strings.Join(args, ", ")) +} + // GetSystemVar gets the string value of a system variable. func (s *SessionVars) GetSystemVar(name string) (string, bool) { val, ok := s.systems[name]