Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions go/sql/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@ func (this *Column) convertArg(arg interface{}) interface{} {
if arg2Bytes != nil {
if this.Charset != "" && this.charsetConversion == nil {
arg = arg2Bytes
} else if this.Charset == "" && (strings.Contains(this.MySQLType, "binary") || strings.HasSuffix(this.MySQLType, "blob")) {
// varbinary/binary/blob column: no charset means binary storage. Return []byte so
// the MySQL driver sends MYSQL_TYPE_BLOB (binary) rather than MYSQL_TYPE_VAR_STRING
// (text with the connection's charset/collation metadata, often utf8mb4), which would
// cause MySQL to validate the bytes and emit Warning 1300 for byte sequences that are
// invalid in that charset.
arg = arg2Bytes
} else {
if encoding, ok := charsetEncodingMap[this.Charset]; ok {
decodedBytes, _ := encoding.NewDecoder().Bytes(arg2Bytes)
Expand Down
41 changes: 41 additions & 0 deletions go/sql/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,47 @@ func TestConvertArgBinaryColumnPadding(t *testing.T) {
require.Equal(t, []byte{0x00, 0x00}, resultBytes[18:])
}

func TestConvertArgVarbinaryStringWithInvalidUTF8Bytes(t *testing.T) {
// go-mysql returns varbinary binlog row values as Go `string` (not `[]uint8`).
// When convertArg receives a string for a column with no Charset (varbinary),
// it must return []byte — not the original string. The Go MySQL driver sends
// string args as MYSQL_TYPE_VAR_STRING with utf8mb4 charset metadata, which
// causes MySQL to validate the bytes and emit Warning 1300 for invalid sequences.
// gh-ost's panic-on-warnings then turns that warning into a fatal migration error.
// See: uuid varbinary(16) rows whose binary UUID bytes happen to be invalid utf8mb4.
rawBytes := []byte{0x91, 0xC3, 0xCD, 0x00, 0x01, 0x02}

col := Column{
Name: "uuid",
Charset: "", // varbinary has no character set
MySQLType: "varbinary(16)", // set by inspect.go from information_schema COLUMN_TYPE
}

result := col.convertArg(string(rawBytes))

require.IsType(t, []byte{}, result,
"varbinary value from binlog (Go string) must be returned as []byte, not string, "+
"to prevent MySQL driver from sending it with the connection's charset metadata")
require.Equal(t, rawBytes, result.([]byte))
}

func TestConvertArgVarbinaryBytesWithInvalidUTF8Bytes(t *testing.T) {
// When go-mysql returns varbinary values as []uint8 (rather than string),
// convertArg should also return []byte consistently.
rawBytes := []uint8{0x91, 0xC3, 0xCD, 0x00, 0x01, 0x02}

col := Column{
Name: "uuid",
Charset: "",
MySQLType: "varbinary(16)", // set by inspect.go from information_schema COLUMN_TYPE
}

result := col.convertArg(rawBytes)

require.IsType(t, []byte{}, result)
require.Equal(t, rawBytes, result.([]byte))
}

func TestConvertArgBinaryColumnNoPaddingWhenFull(t *testing.T) {
// When binary value is already at full length, no padding should occur
fullValue := []uint8{
Expand Down
Loading