Repository: pingcap/dumpling
Pull request #150 (2020-09-09 09:04:36)

fix scan null string error
<!--
Thank you for contributing to Dumpling! Please read the [CONTRIBUTING](https://github.com/pingcap/dumpling/blob/master/CONTRIBUTING.md) document **BEFORE** filing this PR.
-->

### What problem does this PR solve? <!--add issue link with summary if exists-->
Dumpling use `[]string` to parse metadata variables, which can't handle null variable.
```log
[2020/09/09 15:46:07.651 +08:00] [INFO] [log.go:70] ["get global metadata failed"] [task=coa-sync-prod] [unit=dump] [error="sql: Scan error on column index 43, name \"SQL_Remaining_Delay\": converting NULL to string is unsupported"] [errorVerbose="err = sql: Scan error on column index 43, name \"SQL_Remaining_Delay\": converting NULL to string is unsupported
```

### What is changed and how it works?
Use `[]sql.NullString` instead of `[]string`.

### Check List <!--REMOVE the items that are not applicable-->

Tests <!-- At least one of them must be included. -->

 - Unit test
 - Integration test

Related changes

 - Need to cherry-pick to the release branch
 
### Release note

<!-- bugfixes or new feature need a release note, must in the form of a list, such as

- support -T/--tables-list argument

or if no need to be included in the release note, just add the following line

- No release note
-->
- fix the problem that dumpling fails to parse metadata when some variables are NULL

lance6716: LGTM

Patch:
diff --git a/v4/export/metadata.go b/v4/export/metadata.go
index c8e27cd..a0eadc2 100644
--- a/v4/export/metadata.go
+++ b/v4/export/metadata.go
@@ -152,7 +152,7 @@ func (m *globalMetadata) recordGlobalMetaData(db *sql.Conn, serverType ServerTyp
 		if err != nil {
 			return err
 		}
-		data := make([]string, len(cols))
+		data := make([]sql.NullString, len(cols))
 		args := make([]interface{}, 0, len(cols))
 		for i := range data {
 			args = append(args, &data[i])
@@ -162,18 +162,20 @@ func (m *globalMetadata) recordGlobalMetaData(db *sql.Conn, serverType ServerTyp
 		}
 		var connName, pos, logFile, host, gtidSet string
 		for i, col := range cols {
-			col = strings.ToLower(col)
-			switch col {
-			case "connection_name":
-				connName = data[i]
-			case "exec_master_log_pos":
-				pos = data[i]
-			case "relay_master_log_file":
-				logFile = data[i]
-			case "master_host":
-				host = data[i]
-			case "executed_gtid_set":
-				gtidSet = data[i]
+			if data[i].Valid {
+				col = strings.ToLower(col)
+				switch col {
+				case "connection_name":
+					connName = data[i].String
+				case "exec_master_log_pos":
+					pos = data[i].String
+				case "relay_master_log_file":
+					logFile = data[i].String
+				case "master_host":
+					host = data[i].String
+				case "executed_gtid_set":
+					gtidSet = data[i].String
+				}
 			}
 		}
 		if len(host) > 0 {


Test patch:
diff --git a/v4/export/metadata_test.go b/v4/export/metadata_test.go
index 3bee987..cf11e83 100644
--- a/v4/export/metadata_test.go
+++ b/v4/export/metadata_test.go
@@ -115,6 +115,34 @@ func (s *testMetaDataSuite) TestMysqlWithFollowersMetaData(c *C) {
 	c.Assert(mock.ExpectationsWereMet(), IsNil)
 }
 
+func (s *testMetaDataSuite) TestMysqlWithNullFollowersMetaData(c *C) {
+	db, mock, err := sqlmock.New()
+	c.Assert(err, IsNil)
+	defer db.Close()
+	conn, err := db.Conn(context.Background())
+	c.Assert(err, IsNil)
+
+	logFile := "ON.000001"
+	pos := "7502"
+	gtidSet := "6ce40be3-e359-11e9-87e0-36933cb0ca5a:1-29"
+	rows := sqlmock.NewRows([]string{"File", "Position", "Binlog_Do_DB", "Binlog_Ignore_DB", "Executed_Gtid_Set"}).
+		AddRow(logFile, pos, "", "", gtidSet)
+	mock.ExpectQuery("SHOW MASTER STATUS").WillReturnRows(rows)
+	mock.ExpectQuery("SELECT @@default_master_connection").WillReturnError(fmt.Errorf("mock error"))
+	mock.ExpectQuery("SHOW SLAVE STATUS").WillReturnRows(sqlmock.NewRows([]string{"SQL_Remaining_Delay"}).AddRow(nil))
+
+	testFilePath := "/test"
+	m := newGlobalMetadata(testFilePath)
+	c.Assert(m.recordGlobalMetaData(conn, ServerTypeMySQL, false), IsNil)
+	c.Assert(m.filePath, Equals, path.Join(testFilePath, metadataPath))
+
+	c.Assert(m.buffer.String(), Equals, "SHOW MASTER STATUS:\n"+
+		"\tLog: ON.000001\n"+
+		"\tPos: 7502\n"+
+		"\tGTID:6ce40be3-e359-11e9-87e0-36933cb0ca5a:1-29\n\n")
+	c.Assert(mock.ExpectationsWereMet(), IsNil)
+}
+
 func (s *testMetaDataSuite) TestMariaDBMetaData(c *C) {
 	db, mock, err := sqlmock.New()
 	c.Assert(err, IsNil)