diff --git a/bindinfo/handle.go b/bindinfo/handle.go index 1f52bf5ed2dd7..77574e80c4a32 100644 --- a/bindinfo/handle.go +++ b/bindinfo/handle.go @@ -661,16 +661,16 @@ func (h *BindHandle) CaptureBaselines() { func getHintsForSQL(sctx sessionctx.Context, sql string) (string, error) { origVals := sctx.GetSessionVars().UsePlanBaselines sctx.GetSessionVars().UsePlanBaselines = false - recordSets, err := sctx.(sqlexec.SQLExecutor).ExecuteInternal(context.TODO(), fmt.Sprintf("explain format='hint' %s", sql)) + rs, err := sctx.(sqlexec.SQLExecutor).ExecuteInternal(context.TODO(), fmt.Sprintf("explain format='hint' %s", sql)) sctx.GetSessionVars().UsePlanBaselines = origVals - if len(recordSets) > 0 { - defer terror.Log(recordSets[0].Close()) + if rs != nil { + defer terror.Call(rs.Close) } if err != nil { return "", err } - chk := recordSets[0].NewChunk() - err = recordSets[0].Next(context.TODO(), chk) + chk := rs.NewChunk() + err = rs.Next(context.TODO(), chk) if err != nil { return "", err } @@ -873,23 +873,22 @@ func runSQL(ctx context.Context, sctx sessionctx.Context, sql string, resultChan resultChan <- fmt.Errorf("run sql panicked: %v", string(buf)) } }() - recordSets, err := sctx.(sqlexec.SQLExecutor).ExecuteInternal(ctx, sql) + rs, err := sctx.(sqlexec.SQLExecutor).ExecuteInternal(ctx, sql) if err != nil { - if len(recordSets) > 0 { - terror.Call(recordSets[0].Close) + if rs != nil { + terror.Call(rs.Close) } resultChan <- err return } - recordSet := recordSets[0] - chk := recordSets[0].NewChunk() + chk := rs.NewChunk() for { - err = recordSet.Next(ctx, chk) + err = rs.Next(ctx, chk) if err != nil || chk.NumRows() == 0 { break } } - terror.Call(recordSets[0].Close) + terror.Call(rs.Close) resultChan <- err } diff --git a/executor/analyze.go b/executor/analyze.go index a5ce368901f0d..2b8209d753d22 100644 --- a/executor/analyze.go +++ b/executor/analyze.go @@ -758,22 +758,18 @@ func (e *AnalyzeFastExec) calculateEstimateSampleStep() (err error) { if len(partition) > 0 { sql += partition } - var recordSets []sqlexec.RecordSet - recordSets, err = e.ctx.(sqlexec.SQLExecutor).ExecuteInternal(context.TODO(), sql) + var rs sqlexec.RecordSet + rs, err = e.ctx.(sqlexec.SQLExecutor).ExecuteInternal(context.TODO(), sql) if err != nil { return } - if len(recordSets) == 0 { + if rs == nil { err = errors.Trace(errors.Errorf("empty record set")) return } - defer func() { - for _, r := range recordSets { - terror.Call(r.Close) - } - }() - chk := recordSets[0].NewChunk() - err = recordSets[0].Next(context.TODO(), chk) + defer terror.Call(rs.Close) + chk := rs.NewChunk() + err = rs.Next(context.TODO(), chk) if err != nil { return } diff --git a/session/bootstrap.go b/session/bootstrap.go index 9d95fc9884bab..14f1e21c4904a 100644 --- a/session/bootstrap.go +++ b/session/bootstrap.go @@ -1325,8 +1325,8 @@ func upgradeToVer61(s Session, ver int64) { mustExecute(s, "COMMIT") }() mustExecute(s, h.LockBindInfoSQL()) - var recordSets []sqlexec.RecordSet - recordSets, err = s.ExecuteInternal(context.Background(), + var rs sqlexec.RecordSet + rs, err = s.ExecuteInternal(context.Background(), `SELECT bind_sql, default_db, status, create_time, charset, collation, source FROM mysql.bind_info WHERE source != 'builtin' @@ -1334,15 +1334,15 @@ func upgradeToVer61(s Session, ver int64) { if err != nil { logutil.BgLogger().Fatal("upgradeToVer61 error", zap.Error(err)) } - if len(recordSets) > 0 { - defer terror.Call(recordSets[0].Close) + if rs != nil { + defer terror.Call(rs.Close) } - req := recordSets[0].NewChunk() + req := rs.NewChunk() iter := chunk.NewIterator4Chunk(req) p := parser.New() now := types.NewTime(types.FromGoTime(time.Now()), mysql.TypeTimestamp, 3) for { - err = recordSets[0].Next(context.TODO(), req) + err = rs.Next(context.TODO(), req) if err != nil { logutil.BgLogger().Fatal("upgradeToVer61 error", zap.Error(err)) } diff --git a/session/session.go b/session/session.go index 395147cf74cec..fc4794fcb1a15 100644 --- a/session/session.go +++ b/session/session.go @@ -123,13 +123,14 @@ type Session interface { LastInsertID() uint64 // LastInsertID is the last inserted auto_increment ID. LastMessage() string // LastMessage is the info message that may be generated by last command AffectedRows() uint64 // Affected rows by latest executed stmt. - // Execute is deprecated, use ExecuteStmt() instead. + // Execute is deprecated, and only used by plugins. Use ExecuteStmt() instead. Execute(context.Context, string) ([]sqlexec.RecordSet, error) // Execute a sql statement. + // ExecuteStmt executes a parsed statement. ExecuteStmt(context.Context, ast.StmtNode) (sqlexec.RecordSet, error) // Parse is deprecated, use ParseWithParams() instead. Parse(ctx context.Context, sql string) ([]ast.StmtNode, error) // ExecuteInternal is a helper around ParseWithParams() and ExecuteStmt(). It is not allowed to execute multiple statements. - ExecuteInternal(context.Context, string, ...interface{}) ([]sqlexec.RecordSet, error) + ExecuteInternal(context.Context, string, ...interface{}) (sqlexec.RecordSet, error) String() string // String is used to debug. CommitTxn(context.Context) error RollbackTxn(context.Context) @@ -899,37 +900,22 @@ func (s *session) ExecRestrictedSQLWithSnapshot(sql string) ([]chunk.Row, []*ast func execRestrictedSQL(ctx context.Context, se *session, sql string) ([]chunk.Row, []*ast.ResultField, error) { ctx = context.WithValue(ctx, execdetails.StmtExecDetailKey, &execdetails.StmtExecDetails{}) startTime := time.Now() - recordSets, err := se.ExecuteInternal(ctx, sql) - defer func() { - for _, rs := range recordSets { - closeErr := rs.Close() - if closeErr != nil && err == nil { - err = closeErr - } - } - }() - if err != nil { + rs, err := se.ExecuteInternal(ctx, sql) + if rs != nil { + defer terror.Call(rs.Close) + } + if err != nil || rs == nil { return nil, nil, err } - var ( - rows []chunk.Row - fields []*ast.ResultField - ) // Execute all recordset, take out the first one as result. - for i, rs := range recordSets { - tmp, err := drainRecordSet(ctx, se, rs) - if err != nil { - return nil, nil, err - } - - if i == 0 { - rows = tmp - fields = rs.Fields() - } + rows, err := drainRecordSet(ctx, se, rs) + if err != nil { + return nil, nil, err } + metrics.QueryDurationHistogram.WithLabelValues(metrics.LblInternal).Observe(time.Since(startTime).Seconds()) - return rows, fields, err + return rows, rs.Fields(), err } func createSessionFunc(store kv.Storage) pools.Factory { @@ -1259,7 +1245,7 @@ func (s *session) SetProcessInfo(sql string, t time.Time, command byte, maxExecu s.processInfo.Store(&pi) } -func (s *session) ExecuteInternal(ctx context.Context, sql string, args ...interface{}) (recordSets []sqlexec.RecordSet, err error) { +func (s *session) ExecuteInternal(ctx context.Context, sql string, args ...interface{}) (rs sqlexec.RecordSet, err error) { origin := s.sessionVars.InRestrictedSQL s.sessionVars.InRestrictedSQL = true defer func() { @@ -1278,7 +1264,7 @@ func (s *session) ExecuteInternal(ctx context.Context, sql string, args ...inter return nil, err } - rs, err := s.ExecuteStmt(ctx, stmtNode) + rs, err = s.ExecuteStmt(ctx, stmtNode) if err != nil { s.sessionVars.StmtCtx.AppendError(err) } @@ -1286,9 +1272,10 @@ func (s *session) ExecuteInternal(ctx context.Context, sql string, args ...inter return nil, err } - return []sqlexec.RecordSet{rs}, err + return rs, err } +// Execute is deprecated, we can remove it as soon as plugins are migrated. func (s *session) Execute(ctx context.Context, sql string) (recordSets []sqlexec.RecordSet, err error) { if span := opentracing.SpanFromContext(ctx); span != nil && span.Tracer() != nil { span1 := span.Tracer().StartSpan("session.Execute", opentracing.ChildOf(span.Context())) @@ -1350,10 +1337,14 @@ func (s *session) Parse(ctx context.Context, sql string) ([]ast.StmtNode, error) } // ParseWithParams parses a query string, with arguments, to raw ast.StmtNode. +// Note that it will not do escaping if no variable arguments are passed. func (s *session) ParseWithParams(ctx context.Context, sql string, args ...interface{}) (ast.StmtNode, error) { - sql, err := EscapeSQL(sql, args...) - if err != nil { - return nil, err + var err error + if len(args) > 0 { + sql, err = sqlexec.EscapeSQL(sql, args...) + if err != nil { + return nil, err + } } internal := s.isInternal() @@ -2220,18 +2211,18 @@ var ( // loadParameter loads read-only parameter from mysql.tidb func loadParameter(se *session, name string) (string, error) { sql := "select variable_value from mysql.tidb where variable_name = '" + name + "'" - rss, errLoad := se.Execute(context.Background(), sql) + rs, errLoad := se.ExecuteInternal(context.Background(), sql) if errLoad != nil { return "", errLoad } // the record of mysql.tidb under where condition: variable_name = $name should shall only be one. defer func() { - if err := rss[0].Close(); err != nil { + if err := rs.Close(); err != nil { logutil.BgLogger().Error("close result set error", zap.Error(err)) } }() - req := rss[0].NewChunk() - if err := rss[0].Next(context.Background(), req); err != nil { + req := rs.NewChunk() + if err := rs.Next(context.Background(), req); err != nil { return "", err } if req.NumRows() == 0 { diff --git a/session/session_test.go b/session/session_test.go index bdb01ec475e1a..9d356dddf0e0e 100644 --- a/session/session_test.go +++ b/session/session_test.go @@ -4166,25 +4166,35 @@ func (s *testSessionSerialSuite) TestParseWithParams(c *C) { defer func() { se.GetSessionVars().InRestrictedSQL = origin }() - _, err := exec.ParseWithParams(context.Background(), "SELECT 4") + _, err := exec.ParseWithParams(context.TODO(), "SELECT 4") c.Assert(err, IsNil) // test charset attack - stmts, err := exec.ParseWithParams(context.Background(), "SELECT * FROM test WHERE name = %? LIMIT 1", "\xbf\x27 OR 1=1 /*") + stmt, err := exec.ParseWithParams(context.TODO(), "SELECT * FROM test WHERE name = %? LIMIT 1", "\xbf\x27 OR 1=1 /*") c.Assert(err, IsNil) var sb strings.Builder ctx := format.NewRestoreCtx(0, &sb) - err = stmts.Restore(ctx) + err = stmt.Restore(ctx) c.Assert(err, IsNil) // FIXME: well... so the restore function is vulnerable... c.Assert(sb.String(), Equals, "SELECT * FROM test WHERE name=_utf8mb4\xbf' OR 1=1 /* LIMIT 1") // test invalid sql - _, err = exec.ParseWithParams(context.Background(), "SELECT") + _, err = exec.ParseWithParams(context.TODO(), "SELECT") c.Assert(err, ErrorMatches, ".*You have an error in your SQL syntax.*") // test invalid arguments to escape - _, err = exec.ParseWithParams(context.Background(), "SELECT %?") + _, err = exec.ParseWithParams(context.TODO(), "SELECT %?, %?", 3) c.Assert(err, ErrorMatches, "missing arguments.*") + + // test noescape + stmt, err = exec.ParseWithParams(context.TODO(), "SELECT 3") + c.Assert(err, IsNil) + + sb.Reset() + ctx = format.NewRestoreCtx(0, &sb) + err = stmt.Restore(ctx) + c.Assert(err, IsNil) + c.Assert(sb.String(), Equals, "SELECT 3") } diff --git a/statistics/handle/bootstrap.go b/statistics/handle/bootstrap.go index 12e4ec8c67f3a..24a15c88fd491 100644 --- a/statistics/handle/bootstrap.go +++ b/statistics/handle/bootstrap.go @@ -23,7 +23,6 @@ import ( "github.com/pingcap/parser/mysql" "github.com/pingcap/parser/terror" "github.com/pingcap/tidb/infoschema" - "github.com/pingcap/tidb/sessionctx" "github.com/pingcap/tidb/statistics" "github.com/pingcap/tidb/types" "github.com/pingcap/tidb/util/chunk" @@ -85,7 +84,8 @@ func (h *Handle) initStatsMeta(is infoschema.InfoSchema) (statsCache, error) { func (h *Handle) initStatsHistograms4Chunk(is infoschema.InfoSchema, cache *statsCache, iter *chunk.Iterator4Chunk) { for row := iter.Begin(); row != iter.End(); row = iter.Next() { - table, ok := cache.tables[row.GetInt64(0)] + tblID, statsVer := row.GetInt64(0), row.GetInt64(8) + table, ok := cache.tables[tblID] if !ok { continue } @@ -114,7 +114,7 @@ func (h *Handle) initStatsHistograms4Chunk(is infoschema.InfoSchema, cache *stat CMSketch: cms, TopN: topN, Info: idxInfo, - StatsVer: row.GetInt64(8), + StatsVer: statsVer, Flag: row.GetInt64(10), } lastAnalyzePos.Copy(&index.LastAnalyzePos) @@ -130,15 +130,26 @@ func (h *Handle) initStatsHistograms4Chunk(is infoschema.InfoSchema, cache *stat if colInfo == nil { continue } + var topnCount int64 + // If this is stats of the Version2, we need to consider the topn's count as well. + // See the comments of Version2 for more details. + if statsVer == statistics.Version2 { + var err error + topnCount, err = h.initTopNCountSum(tblID, id) + if err != nil { + terror.Log(err) + } + } hist := statistics.NewHistogram(id, ndv, nullCount, version, &colInfo.FieldType, 0, totColSize) hist.Correlation = row.GetFloat64(9) col := &statistics.Column{ Histogram: *hist, PhysicalID: table.PhysicalID, Info: colInfo, - Count: nullCount, + Count: nullCount + topnCount, IsHandle: tbl.Meta().PKIsHandle && mysql.HasPriKeyFlag(colInfo.Flag), Flag: row.GetInt64(10), + StatsVer: statsVer, } lastAnalyzePos.Copy(&col.LastAnalyzePos) table.Columns[hist.ID] = col @@ -218,7 +229,7 @@ func (h *Handle) initStatsTopN(cache *statsCache) error { return nil } -func initStatsBuckets4Chunk(ctx sessionctx.Context, cache *statsCache, iter *chunk.Iterator4Chunk) { +func (h *Handle) initStatsBuckets4Chunk(cache *statsCache, iter *chunk.Iterator4Chunk) { for row := iter.Begin(); row != iter.End(); row = iter.Next() { tableID, isIndex, histID := row.GetInt64(0), row.GetInt64(1), row.GetInt64(2) table, ok := cache.tables[tableID] @@ -246,26 +257,53 @@ func initStatsBuckets4Chunk(ctx sessionctx.Context, cache *statsCache, iter *chu hist = &column.Histogram d := types.NewBytesDatum(row.GetBytes(5)) var err error - lower, err = d.ConvertTo(ctx.GetSessionVars().StmtCtx, &column.Info.FieldType) + lower, err = d.ConvertTo(h.mu.ctx.GetSessionVars().StmtCtx, &column.Info.FieldType) if err != nil { logutil.BgLogger().Debug("decode bucket lower bound failed", zap.Error(err)) delete(table.Columns, histID) continue } d = types.NewBytesDatum(row.GetBytes(6)) - upper, err = d.ConvertTo(ctx.GetSessionVars().StmtCtx, &column.Info.FieldType) + upper, err = d.ConvertTo(h.mu.ctx.GetSessionVars().StmtCtx, &column.Info.FieldType) if err != nil { logutil.BgLogger().Debug("decode bucket upper bound failed", zap.Error(err)) delete(table.Columns, histID) continue } } - hist.AppendBucket(&lower, &upper, row.GetInt64(3), row.GetInt64(4)) + hist.AppendBucketWithNDV(&lower, &upper, row.GetInt64(3), row.GetInt64(4), row.GetInt64(7)) + } +} + +func (h *Handle) initTopNCountSum(tableID, colID int64) (int64, error) { + // Before stats ver 2, histogram represents all data in this column. + // In stats ver 2, histogram + TopN represent all data in this column. + // So we need to add TopN total count here. + selSQL := fmt.Sprintf("select sum(count) from mysql.stats_top_n where table_id = %d and is_index = 0 and hist_id = %d", tableID, colID) + rs, err := h.mu.ctx.(sqlexec.SQLExecutor).Execute(context.TODO(), selSQL) + if len(rs) > 0 { + defer terror.Call(rs[0].Close) + } + if err != nil { + return 0, err + } + req := rs[0].NewChunk() + iter := chunk.NewIterator4Chunk(req) + for { + err := rs[0].Next(context.TODO(), req) + if err != nil { + return 0, err + } + if req.NumRows() == 0 { + break + } + return iter.Begin().GetMyDecimal(0).ToInt() } + return 0, nil } func (h *Handle) initStatsBuckets(cache *statsCache) error { - sql := "select HIGH_PRIORITY table_id, is_index, hist_id, count, repeats, lower_bound, upper_bound from mysql.stats_buckets order by table_id, is_index, hist_id, bucket_id" + sql := "select HIGH_PRIORITY table_id, is_index, hist_id, count, repeats, lower_bound, upper_bound, ndv from mysql.stats_buckets order by table_id, is_index, hist_id, bucket_id" rc, err := h.mu.ctx.(sqlexec.SQLExecutor).Execute(context.TODO(), sql) if len(rc) > 0 { defer terror.Call(rc[0].Close) @@ -283,7 +321,7 @@ func (h *Handle) initStatsBuckets(cache *statsCache) error { if req.NumRows() == 0 { break } - initStatsBuckets4Chunk(h.mu.ctx, cache, iter) + h.initStatsBuckets4Chunk(cache, iter) } lastVersion := uint64(0) for _, table := range cache.tables { diff --git a/statistics/handle/handle_test.go b/statistics/handle/handle_test.go index 6b145cca4d4ed..95417de2332b8 100644 --- a/statistics/handle/handle_test.go +++ b/statistics/handle/handle_test.go @@ -507,6 +507,36 @@ func (s *testStatsSuite) TestInitStats(c *C) { h.SetLease(0) } +func (s *testStatsSuite) TestInitStatsVer2(c *C) { + defer cleanEnv(c, s.store, s.do) + tk := testkit.NewTestKit(c, s.store) + tk.MustExec("use test") + tk.MustExec("set @@session.tidb_analyze_version=2") + tk.MustExec("create table t(a int, b int, c int, index idx(a), index idxab(a, b))") + tk.MustExec("insert into t values(1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (4, 4, 4), (4, 4, 4)") + tk.MustExec("analyze table t with 2 topn, 3 buckets") + h := s.do.StatsHandle() + is := s.do.InfoSchema() + tbl, err := is.TableByName(model.NewCIStr("test"), model.NewCIStr("t")) + c.Assert(err, IsNil) + // `Update` will not use load by need strategy when `Lease` is 0, and `InitStats` is only called when + // `Lease` is not 0, so here we just change it. + h.SetLease(time.Millisecond) + + h.Clear() + c.Assert(h.InitStats(is), IsNil) + table0 := h.GetTableStats(tbl.Meta()) + cols := table0.Columns + c.Assert(cols[1].LastAnalyzePos.GetBytes()[0], Equals, uint8(0x33)) + c.Assert(cols[2].LastAnalyzePos.GetBytes()[0], Equals, uint8(0x33)) + c.Assert(cols[3].LastAnalyzePos.GetBytes()[0], Equals, uint8(0x33)) + h.Clear() + c.Assert(h.Update(is), IsNil) + table1 := h.GetTableStats(tbl.Meta()) + assertTableEqual(c, table0, table1) + h.SetLease(0) +} + func (s *testStatsSuite) TestLoadStats(c *C) { defer cleanEnv(c, s.store, s.do) testKit := testkit.NewTestKit(c, s.store) diff --git a/store/tikv/gcworker/gc_worker.go b/store/tikv/gcworker/gc_worker.go index 4cde4b08a0cb6..43f50c9422748 100644 --- a/store/tikv/gcworker/gc_worker.go +++ b/store/tikv/gcworker/gc_worker.go @@ -1760,14 +1760,14 @@ func (w *GCWorker) loadValueFromSysTable(key string) (string, error) { se := createSession(w.store) defer se.Close() rs, err := se.ExecuteInternal(ctx, `SELECT HIGH_PRIORITY (variable_value) FROM mysql.tidb WHERE variable_name=%? FOR UPDATE`, key) - if len(rs) > 0 { - defer terror.Call(rs[0].Close) + if rs != nil { + defer terror.Call(rs.Close) } if err != nil { return "", errors.Trace(err) } - req := rs[0].NewChunk() - err = rs[0].Next(ctx, req) + req := rs.NewChunk() + err = rs.Next(ctx, req) if err != nil { return "", errors.Trace(err) } diff --git a/util/mock/context.go b/util/mock/context.go index daafb0c1a244c..8f87caefcc2c1 100644 --- a/util/mock/context.go +++ b/util/mock/context.go @@ -67,12 +67,12 @@ func (txn *wrapTxn) GetUnionStore() kv.UnionStore { // Execute implements sqlexec.SQLExecutor Execute interface. func (c *Context) Execute(ctx context.Context, sql string) ([]sqlexec.RecordSet, error) { - return nil, errors.Errorf("Not Support.") + return nil, errors.Errorf("Not Supported.") } // ExecuteInternal implements sqlexec.SQLExecutor ExecuteInternal interface. -func (c *Context) ExecuteInternal(ctx context.Context, sql string, args ...interface{}) ([]sqlexec.RecordSet, error) { - return nil, errors.Errorf("Not Support.") +func (c *Context) ExecuteInternal(ctx context.Context, sql string, args ...interface{}) (sqlexec.RecordSet, error) { + return nil, errors.Errorf("Not Supported.") } type mockDDLOwnerChecker struct{} diff --git a/util/sqlexec/restricted_sql_executor.go b/util/sqlexec/restricted_sql_executor.go index 5a07c6a04622f..a1d8d5421a803 100644 --- a/util/sqlexec/restricted_sql_executor.go +++ b/util/sqlexec/restricted_sql_executor.go @@ -86,9 +86,10 @@ func ExecOptionWithSnapshot(snapshot uint64) OptionFuncAlias { // For example, privilege/privileges package need execute SQL, if it use // session.Session.Execute, then privilege/privileges and tidb would become a circle. type SQLExecutor interface { + // Execute is only used by plugins. It can be removed soon. Execute(ctx context.Context, sql string) ([]RecordSet, error) // ExecuteInternal means execute sql as the internal sql. - ExecuteInternal(ctx context.Context, sql string, args ...interface{}) ([]RecordSet, error) + ExecuteInternal(ctx context.Context, sql string, args ...interface{}) (RecordSet, error) } // SQLParser is an interface provides parsing sql statement. diff --git a/session/utils.go b/util/sqlexec/utils.go similarity index 98% rename from session/utils.go rename to util/sqlexec/utils.go index 67788d5d53a4f..44262f40aba51 100644 --- a/session/utils.go +++ b/util/sqlexec/utils.go @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package session +package sqlexec import ( "encoding/json" @@ -187,7 +187,6 @@ func EscapeSQL(sql string, args ...interface{}) (string, error) { buf = escapeStringBackslash(buf, v) buf = append(buf, '\'') case []string: - buf = append(buf, '(') for i, k := range v { if i > 0 { buf = append(buf, ',') @@ -196,7 +195,6 @@ func EscapeSQL(sql string, args ...interface{}) (string, error) { buf = escapeStringBackslash(buf, k) buf = append(buf, '\'') } - buf = append(buf, ')') default: return "", errors.Errorf("unsupported %d-th argument: %v", argPos, arg) } diff --git a/session/utils_test.go b/util/sqlexec/utils_test.go similarity index 98% rename from session/utils_test.go rename to util/sqlexec/utils_test.go index f7d754418c019..accd25358d499 100644 --- a/session/utils_test.go +++ b/util/sqlexec/utils_test.go @@ -11,16 +11,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -package session +package sqlexec import ( "encoding/json" + "testing" "time" . "github.com/pingcap/check" "github.com/pingcap/tidb/util/hack" ) +func TestT(t *testing.T) { + TestingT(t) +} + var _ = Suite(&testUtilsSuite{}) type testUtilsSuite struct{} @@ -306,7 +311,7 @@ func (s *testUtilsSuite) TestEscapeSQL(c *C) { name: "string slice", input: "select %?", params: []interface{}{[]string{"33", "44"}}, - output: "select ('33','44')", + output: "select '33','44'", }, { name: "raw json",