Repository: pingcap/dumpling
Pull request #20 (2019-12-30 14:50:36)

add support for consistency control
Changes:
- refactor `Dump()`: we first list all the tables, then start dumping.
- add tests for `dumpTable()`.
- add three kinds of consistency control: 
  - lock all tables
  - flush tables with read lock
  - snapshot
- add some SQL convenient functions.

codecov-io: # [Codecov](https://codecov.io/gh/pingcap/dumpling/pull/20?src=pr&el=h1) Report
> Merging [#20](https://codecov.io/gh/pingcap/dumpling/pull/20?src=pr&el=desc) into [master](https://codecov.io/gh/pingcap/dumpling/commit/7caf19bec0a21e06853404f4b1a84ce591145eb3?src=pr&el=desc) will **decrease** coverage by `2.19%`.
> The diff coverage is `4.57%`.


```diff
@@           Coverage Diff            @@
##           master     #20     +/-   ##
========================================
- Coverage   41.69%   39.5%   -2.2%     
========================================
  Files          10      11      +1     
  Lines         530     643    +113     
========================================
+ Hits          221     254     +33     
- Misses        289     360     +71     
- Partials       20      29      +9
```


Patch:
diff --git a/go.mod b/go.mod
index 203a2dc..7a42d6f 100644
--- a/go.mod
+++ b/go.mod
@@ -5,6 +5,8 @@ require (
 	github.com/coreos/go-semver v0.3.0
 	github.com/go-sql-driver/mysql v1.4.1
 	github.com/pingcap/check v0.0.0-20191107115940-caf2b9e6ccf4
+	golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
+	google.golang.org/appengine v1.6.5 // indirect
 )
 
 go 1.13
diff --git a/go.sum b/go.sum
index 974d81a..b780bea 100644
--- a/go.sum
+++ b/go.sum
@@ -9,6 +9,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
 github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
@@ -51,11 +52,17 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl
 golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -63,6 +70,8 @@ golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtn
 golang.org/x/tools v0.0.0-20191107010934-f79515f33823 h1:akkRBeitX2EZP59KdtKw310CI4WGPCNPyrLbE7WZA8Y=
 golang.org/x/tools v0.0.0-20191107010934-f79515f33823/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
+google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/v4/export/config.go b/v4/export/config.go
index 40787b9..190c430 100644
--- a/v4/export/config.go
+++ b/v4/export/config.go
@@ -21,6 +21,8 @@ type Config struct {
 	OutputDirPath string
 	ServerInfo    ServerInfo
 	SortByPk      bool
+	Tables        DatabaseTables
+	Snapshot      string
 }
 
 func DefaultConfig() *Config {
@@ -36,6 +38,8 @@ func DefaultConfig() *Config {
 		OutputDirPath: ".",
 		ServerInfo:    ServerInfoUnknown,
 		SortByPk:      false,
+		Tables:        nil,
+		Snapshot:      "",
 	}
 }
 
@@ -96,3 +100,9 @@ const (
 
 	ServerTypeAll
 )
+
+type databaseName = string
+
+type tableName = string
+
+type DatabaseTables map[databaseName][]tableName
diff --git a/v4/export/dump.go b/v4/export/dump.go
index d63400c..6554204 100644
--- a/v4/export/dump.go
+++ b/v4/export/dump.go
@@ -3,112 +3,94 @@ package export
 import (
 	"context"
 	"database/sql"
-	"strings"
-	"sync"
+	"golang.org/x/sync/errgroup"
 
 	_ "github.com/go-sql-driver/mysql"
 )
 
-func Dump(conf *Config) error {
-	databases, serverInfo, err := prepareMeta(conf)
+func Dump(conf *Config) (err error) {
+	pool, err := sql.Open("mysql", conf.getDSN(""))
+	if err != nil {
+		return withStack(err)
+	}
+	defer pool.Close()
+
+	conf.ServerInfo, err = detectServerInfo(pool)
 	if err != nil {
 		return err
 	}
 
-	conf.ServerInfo = serverInfo
-	for _, database := range databases {
-		fsWriter, err := NewSimpleWriter(conf)
-		if err != nil {
-			return err
-		}
-		if err := dumpDatabase(context.Background(), conf, database, fsWriter); err != nil {
-			return err
-		}
+	databases, err := prepareDumpingDatabases(conf, pool)
+	if err != nil {
+		return err
 	}
-	return nil
-}
 
-func prepareMeta(conf *Config) (databases []string, info ServerInfo, err error) {
-	pool, err := sql.Open("mysql", conf.getDSN(""))
+	conf.Tables, err = listAllTables(pool, databases)
 	if err != nil {
-		return nil, ServerInfoUnknown, withStack(err)
+		return err
 	}
-	defer pool.Close()
 
-	if conf.Database == "" {
-		databases, err = ShowDatabases(pool)
-		if err != nil {
-			return nil, ServerInfoUnknown, withStack(err)
-		}
-	} else {
-		databases = strings.Split(conf.Database, ",")
+	var conCtrl ConsistencyController = &ConsistencyNone{}
+	if err = conCtrl.Setup(); err != nil {
+		return err
 	}
 
-	versionStr, err := SelectVersion(pool)
+	fsWriter, err := NewSimpleWriter(conf)
 	if err != nil {
-		return databases, ServerInfoUnknown, withStack(err)
+		return err
 	}
-	info = ParseServerInfo(versionStr)
-	return
+	if err = dumpDatabases(context.Background(), conf, pool, fsWriter); err != nil {
+		return err
+	}
+
+	return conCtrl.TearDown()
 }
 
-func dumpDatabase(ctx context.Context, conf *Config, dbName string, writer Writer) error {
-	dsn := conf.getDSN(dbName)
-	db, err := sql.Open("mysql", dsn)
-	if err != nil {
-		return withStack(err)
+func dumpDatabases(ctx context.Context, conf *Config, db *sql.DB, writer Writer) error {
+	allTables := conf.Tables
+	for dbName, tables := range allTables {
+		createDatabaseSQL, err := ShowCreateDatabase(db, dbName)
+		if err != nil {
+			return err
+		}
+		if err := writer.WriteDatabaseMeta(ctx, dbName, createDatabaseSQL); err != nil {
+			return err
+		}
+
+		rateLimit := newRateLimit(conf.Threads)
+		var g errgroup.Group
+		for _, table := range tables {
+			table := table
+			g.Go(func() error {
+				rateLimit.getToken()
+				defer rateLimit.putToken()
+				return dumpTable(ctx, conf, db, dbName, table, writer)
+			})
+		}
+		if err := g.Wait(); err != nil {
+			return err
+		}
+		return nil
 	}
-	defer db.Close()
+	return nil
+}
 
-	createDatabaseSQL, err := ShowCreateDatabase(db, dbName)
+func dumpTable(ctx context.Context, conf *Config, db *sql.DB, dbName, table string, writer Writer) error {
+	createTableSQL, err := ShowCreateTable(db, dbName, table)
 	if err != nil {
 		return err
 	}
-	if err := writer.WriteDatabaseMeta(ctx, dbName, createDatabaseSQL); err != nil {
+	if err := writer.WriteTableMeta(ctx, dbName, table, createTableSQL); err != nil {
 		return err
 	}
 
-	tables, err := ShowTables(db)
+	tableIR, err := SelectAllFromTable(conf, db, dbName, table)
 	if err != nil {
 		return err
 	}
 
-	rateLimit := newRateLimit(conf.Threads)
-	var wg sync.WaitGroup
-	wg.Add(len(tables))
-	res := make([]error, len(tables))
-	for i, table := range tables {
-		go func(ith int, table string, wg *sync.WaitGroup, res []error) {
-			defer wg.Done()
-			createTableSQL, err := ShowCreateTable(db, dbName, table)
-			if err != nil {
-				res[ith] = err
-				return
-			}
-			if err := writer.WriteTableMeta(ctx, dbName, table, createTableSQL); err != nil {
-				res[ith] = err
-				return
-			}
-
-			rateLimit.getToken()
-			tableIR, err := SelectAllFromTable(conf, db, dbName, table)
-			defer rateLimit.putToken()
-			if err != nil {
-				res[ith] = err
-				return
-			}
-
-			if err := writer.WriteTableData(ctx, tableIR); err != nil {
-				res[ith] = err
-				return
-			}
-		}(i, table, &wg, res)
-	}
-	wg.Wait()
-	for _, err := range res {
-		if err != nil {
-			return err
-		}
+	if err := writer.WriteTableData(ctx, tableIR); err != nil {
+		return err
 	}
 	return nil
 }
diff --git a/v4/export/error.go b/v4/export/error.go
index 3e987ae..7a94c9d 100644
--- a/v4/export/error.go
+++ b/v4/export/error.go
@@ -27,6 +27,10 @@ func (e errWithStack) Error() string {
 var stackErr errWithStack
 
 func withStack(err error) error {
+	if err == nil {
+		return nil
+	}
+
 	if errors.Is(err, stackErr) {
 		return err
 	}
diff --git a/v4/export/prepare.go b/v4/export/prepare.go
new file mode 100644
index 0000000..a21c503
--- /dev/null
+++ b/v4/export/prepare.go
@@ -0,0 +1,126 @@
+package export
+
+import (
+	"database/sql"
+	"errors"
+	"strings"
+)
+
+func detectServerInfo(db *sql.DB) (ServerInfo, error) {
+	versionStr, err := SelectVersion(db)
+	if err != nil {
+		return ServerInfoUnknown, err
+	}
+	return ParseServerInfo(versionStr), nil
+}
+
+func prepareDumpingDatabases(conf *Config, db *sql.DB) ([]string, error) {
+	if conf.Database == "" {
+		return ShowDatabases(db)
+	} else {
+		return strings.Split(conf.Database, ","), nil
+	}
+}
+
+func listAllTables(db *sql.DB, databaseNames []string) (DatabaseTables, error) {
+	dbTables := DatabaseTables{}
+	for _, dbName := range databaseNames {
+		err := UseDatabase(db, dbName)
+		if err != nil {
+			return nil, err
+		}
+		tables, err := ShowTables(db)
+		if err != nil {
+			return nil, err
+		}
+		dbTables[dbName] = tables
+	}
+	return dbTables, nil
+}
+
+type ConsistencyController interface {
+	Setup() error
+	TearDown() error
+}
+
+type ConsistencyNone struct{}
+
+func (c *ConsistencyNone) Setup() error {
+	return nil
+}
+
+func (c *ConsistencyNone) TearDown() error {
+	return nil
+}
+
+type ConsistencyFlushTableWithReadLock struct {
+	serverType ServerType
+	db         *sql.DB
+}
+
+func (c *ConsistencyFlushTableWithReadLock) Setup() error {
+	if c.serverType == ServerTypeTiDB {
+		return withStack(errors.New("'flush table with read lock' cannot be used to ensure the consistency in TiDB"))
+	}
+	return FlushTableWithReadLock(c.db)
+}
+
+func (c *ConsistencyFlushTableWithReadLock) TearDown() error {
+	err := c.db.Ping()
+	if err != nil {
+		return withStack(errors.New("ConsistencyFlushTableWithReadLock lost database connection"))
+	}
+	return UnlockTables(c.db)
+}
+
+type ConsistencyLockDumpingTables struct {
+	db        *sql.DB
+	allTables DatabaseTables
+}
+
+func (c *ConsistencyLockDumpingTables) Setup() error {
+	for dbName, tables := range c.allTables {
+		for _, table := range tables {
+			err := LockTables(c.db, dbName, table)
+			if err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
+
+func (c *ConsistencyLockDumpingTables) TearDown() error {
+	err := c.db.Ping()
+	if err != nil {
+		return withStack(errors.New("ConsistencyLockDumpingTables lost database connection"))
+	}
+	return UnlockTables(c.db)
+}
+
+type ConsistencySnapshot struct {
+	serverType ServerType
+	snapshot   string
+	db         *sql.DB
+}
+
+const showMasterStatusFieldNum = 5
+const snapshotFieldIndex = 1
+
+func (c *ConsistencySnapshot) Setup() error {
+	if c.serverType != ServerTypeTiDB {
+		return withStack(errors.New("snapshot consistency is not supported for this server"))
+	}
+	if c.snapshot == "" {
+		str, err := ShowMasterStatus(c.db, showMasterStatusFieldNum)
+		if err != nil {
+			return err
+		}
+		c.snapshot = str[snapshotFieldIndex]
+	}
+	return SetTiDBSnapshot(c.db, c.snapshot)
+}
+
+func (c *ConsistencySnapshot) TearDown() error {
+	return nil
+}
diff --git a/v4/export/sql.go b/v4/export/sql.go
index 0f7e26f..41b9635 100644
--- a/v4/export/sql.go
+++ b/v4/export/sql.go
@@ -62,7 +62,7 @@ func SelectVersion(db *sql.DB) (string, error) {
 }
 
 func SelectAllFromTable(conf *Config, db *sql.DB, database, table string) (TableDataIR, error) {
-	colTypes, err := GetColumnTypes(db, table)
+	colTypes, err := GetColumnTypes(db, database, table)
 	if err != nil {
 		return nil, err
 	}
@@ -140,8 +140,8 @@ func SelectTiDBRowID(db *sql.DB, database, table string) (bool, error) {
 	return true, nil
 }
 
-func GetColumnTypes(db *sql.DB, table string) ([]*sql.ColumnType, error) {
-	rows, err := db.Query(fmt.Sprintf("SELECT * FROM %s LIMIT 1", table))
+func GetColumnTypes(db *sql.DB, database, table string) ([]*sql.ColumnType, error) {
+	rows, err := db.Query(fmt.Sprintf("SELECT * FROM %s.%s LIMIT 1", database, table))
 	if err != nil {
 		return nil, withStack(err)
 	}
@@ -172,6 +172,47 @@ func GetPrimaryKeyName(db *sql.DB, database, table string) (string, error) {
 	return colName, nil
 }
 
+func FlushTableWithReadLock(db *sql.DB) error {
+	_, err := db.Exec("FLUSH TABLES WITH READ LOCK")
+	return withStack(err)
+}
+
+func LockTables(db *sql.DB, database, table string) error {
+	_, err := db.Exec(fmt.Sprintf("`%s`.`%s`", database, table))
+	return withStack(err)
+}
+
+func UnlockTables(db *sql.DB) error {
+	_, err := db.Exec("UNLOCK TABLES")
+	return withStack(err)
+}
+
+func UseDatabase(db *sql.DB, databaseName string) error {
+	_, err := db.Exec(fmt.Sprintf("USE %s", databaseName))
+	return withStack(err)
+}
+
+func ShowMasterStatus(db *sql.DB, fieldNum int) ([]string, error) {
+	oneRow := make([]string, fieldNum)
+	addr := make([]interface{}, fieldNum)
+	for i := range oneRow {
+		addr[i] = &oneRow[i]
+	}
+	handleOneRow := func(rows *sql.Rows) error {
+		return rows.Scan(addr...)
+	}
+	err := simpleQuery(db, "SHOW MASTER STATUS", handleOneRow)
+	if err != nil {
+		return nil, err
+	}
+	return oneRow, nil
+}
+
+func SetTiDBSnapshot(db *sql.DB, snapshot string) error {
+	_, err := db.Exec("SET SESSION tidb_snapshot = ?", snapshot)
+	return withStack(err)
+}
+
 type oneStrColumnTable struct {
 	data []string
 }


Test patch:
diff --git a/v4/export/dump_test.go b/v4/export/dump_test.go
new file mode 100644
index 0000000..3306a4c
--- /dev/null
+++ b/v4/export/dump_test.go
@@ -0,0 +1,68 @@
+package export
+
+import (
+	"context"
+	"fmt"
+	"github.com/DATA-DOG/go-sqlmock"
+	. "github.com/pingcap/check"
+)
+
+var _ = Suite(&testDumpSuite{})
+
+type testDumpSuite struct{}
+
+type mockWriter struct {
+	databaseMeta map[string]string
+	tableMeta    map[string]string
+	tableData    []TableDataIR
+}
+
+func newMockWriter() *mockWriter {
+	return &mockWriter{
+		databaseMeta: map[string]string{},
+		tableMeta:    map[string]string{},
+		tableData:    nil,
+	}
+}
+
+func (m *mockWriter) WriteDatabaseMeta(ctx context.Context, db, createSQL string) error {
+	m.databaseMeta[db] = createSQL
+	return nil
+}
+
+func (m *mockWriter) WriteTableMeta(ctx context.Context, db, table, createSQL string) error {
+	m.tableMeta[fmt.Sprintf("%s.%s", db, table)] = createSQL
+	return nil
+}
+
+func (m *mockWriter) WriteTableData(ctx context.Context, ir TableDataIR) error {
+	m.tableData = append(m.tableData, ir)
+	return nil
+}
+
+func (s *testDumpSuite) TestDumpTable(c *C) {
+	mockConfig := DefaultConfig()
+	db, mock, err := sqlmock.New()
+	c.Assert(err, IsNil)
+
+	showCreateTableResult := "CREATE TABLE t (a INT)"
+	rows := mock.NewRows([]string{"Table", "Create Table"}).AddRow("t", showCreateTableResult)
+	mock.ExpectQuery("SHOW CREATE TABLE test.t").WillReturnRows(rows)
+	rows = mock.NewRows([]string{"id"}).AddRow(1)
+	mock.ExpectQuery("SELECT (.) FROM test.t LIMIT 1").WillReturnRows(rows)
+	rows = mock.NewRows([]string{"id"}).AddRow(1).AddRow(2)
+	mock.ExpectQuery("SELECT (.) FROM test.t").WillReturnRows(rows)
+
+	mockWriter := newMockWriter()
+	err = dumpTable(context.Background(), mockConfig, db, "test", "t", mockWriter)
+	c.Assert(err, IsNil)
+
+	c.Assert(len(mockWriter.databaseMeta), Equals, 0)
+	c.Assert(mockWriter.tableMeta["test.t"], Equals, showCreateTableResult)
+	c.Assert(len(mockWriter.tableData), Equals, 1)
+	tbDataRes := mockWriter.tableData[0]
+	c.Assert(tbDataRes.DatabaseName(), Equals, "test")
+	c.Assert(tbDataRes.TableName(), Equals, "t")
+	c.Assert(tbDataRes.ColumnCount(), Equals, uint(1))
+	c.Assert(mock.ExpectationsWereMet(), IsNil)
+}
diff --git a/v4/export/sql_test.go b/v4/export/sql_test.go
index ac2070a..922e302 100644
--- a/v4/export/sql_test.go
+++ b/v4/export/sql_test.go
@@ -7,9 +7,9 @@ import (
 	. "github.com/pingcap/check"
 )
 
-var _ = Suite(&testDumpSuite{})
+var _ = Suite(&testSQLSuite{})
 
-type testDumpSuite struct{}
+type testSQLSuite struct{}
 
 func (s *testDumpSuite) TestDetectServerInfo(c *C) {
 	db, mock, err := sqlmock.New()