Skip to content

Commit 8a30693

Browse files
committed
Fix binary column trailing zero stripping for non-key columns
MySQL's binlog strips trailing 0x00 bytes from binary(N) columns. PR #915 fixed this for unique key columns only, but the same issue affects all binary columns in INSERT/UPDATE operations. Remove the isUniqueKeyColumn condition so all binary(N) columns are padded to their declared length. Fixes a variation of #909 where the affected column is not a primary key.
1 parent a1e9c9d commit 8a30693

4 files changed

Lines changed: 105 additions & 1 deletion

File tree

go/sql/types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ func (this *Column) convertArg(arg interface{}, isUniqueKeyColumn bool) interfac
7777
}
7878
}
7979

80-
if this.Type == BinaryColumnType && isUniqueKeyColumn {
80+
if this.Type == BinaryColumnType {
8181
size := len(arg2Bytes)
8282
if uint(size) < this.BinaryOctetLength {
8383
buf := bytes.NewBuffer(arg2Bytes)

go/sql/types_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,68 @@ func TestConvertArgCharsetDecoding(t *testing.T) {
6565
str := col.convertArg(latin1Bytes, false)
6666
require.Equal(t, "Garçon !", str)
6767
}
68+
69+
func TestConvertArgBinaryColumnPadding(t *testing.T) {
70+
// Test that binary columns are padded with trailing zeros to their declared length.
71+
// This is needed because MySQL's binlog strips trailing 0x00 bytes from binary values.
72+
// See https://github.com/github/gh-ost/issues/909
73+
74+
// Simulates a binary(20) column where binlog delivered only 18 bytes
75+
// (trailing zeros were stripped)
76+
truncatedValue := []uint8{
77+
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
78+
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
79+
0x11, 0x12, // 18 bytes, missing 2 trailing zeros
80+
}
81+
82+
col := Column{
83+
Name: "bin_col",
84+
Type: BinaryColumnType,
85+
BinaryOctetLength: 20,
86+
}
87+
88+
// Test with isUniqueKeyColumn=false (the bug was that non-key columns weren't padded)
89+
result := col.convertArg(truncatedValue, false)
90+
resultBytes := []byte(result.(string))
91+
92+
require.Equal(t, 20, len(resultBytes), "binary column should be padded to declared length")
93+
// First 18 bytes should be unchanged
94+
require.Equal(t, truncatedValue, resultBytes[:18])
95+
// Last 2 bytes should be zeros
96+
require.Equal(t, []byte{0x00, 0x00}, resultBytes[18:])
97+
98+
// Test with isUniqueKeyColumn=true (this already worked before the fix)
99+
resultKey := col.convertArg(truncatedValue, true)
100+
resultKeyBytes := []byte(resultKey.(string))
101+
require.Equal(t, 20, len(resultKeyBytes), "binary key column should be padded to declared length")
102+
}
103+
104+
func TestConvertArgBinaryColumnNoPaddingWhenFull(t *testing.T) {
105+
// When binary value is already at full length, no padding should occur
106+
fullValue := []uint8{
107+
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
108+
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
109+
0x11, 0x12, 0x13, 0x14, // 20 bytes
110+
}
111+
112+
col := Column{
113+
Name: "bin_col",
114+
Type: BinaryColumnType,
115+
BinaryOctetLength: 20,
116+
}
117+
118+
result := col.convertArg(fullValue, false)
119+
// When no padding needed, result may be []uint8 or string depending on code path
120+
var resultBytes []byte
121+
switch v := result.(type) {
122+
case string:
123+
resultBytes = []byte(v)
124+
case []uint8:
125+
resultBytes = v
126+
default:
127+
t.Fatalf("unexpected type %T", result)
128+
}
129+
130+
require.Equal(t, 20, len(resultBytes))
131+
require.Equal(t, []byte(fullValue), resultBytes)
132+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
-- Test for https://github.com/github/gh-ost/issues/909 variant:
2+
-- Binary columns with trailing zeros should preserve their values
3+
-- when migrating from binary(N) to varbinary(M), even for rows
4+
-- modified during migration via binlog events.
5+
6+
drop table if exists gh_ost_test;
7+
create table gh_ost_test (
8+
id int NOT NULL AUTO_INCREMENT,
9+
info varchar(255) NOT NULL,
10+
data binary(20) NOT NULL,
11+
PRIMARY KEY (id)
12+
) auto_increment=1;
13+
14+
drop event if exists gh_ost_test;
15+
delimiter ;;
16+
create event gh_ost_test
17+
on schedule every 1 second
18+
starts current_timestamp
19+
ends current_timestamp + interval 60 second
20+
on completion not preserve
21+
enable
22+
do
23+
begin
24+
-- Insert rows where data has trailing zeros (will be stripped by binlog)
25+
INSERT INTO gh_ost_test (info, data) VALUES ('insert-during-1', X'aabbccdd00000000000000000000000000000000');
26+
INSERT INTO gh_ost_test (info, data) VALUES ('insert-during-2', X'11223344556677889900000000000000000000ee');
27+
28+
-- Update existing rows to values with trailing zeros
29+
UPDATE gh_ost_test SET data = X'ffeeddcc00000000000000000000000000000000' WHERE info = 'update-target-1';
30+
UPDATE gh_ost_test SET data = X'aabbccdd11111111111111111100000000000000' WHERE info = 'update-target-2';
31+
end ;;
32+
33+
-- Pre-existing rows (copied via rowcopy, not binlog - these should work fine)
34+
INSERT INTO gh_ost_test (info, data) VALUES
35+
('pre-existing-1', X'01020304050607080910111213141516171819ff'),
36+
('pre-existing-2', X'0102030405060708091011121314151617181900'),
37+
('update-target-1', X'ffffffffffffffffffffffffffffffffffffffff'),
38+
('update-target-2', X'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee');
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
--alter="MODIFY data varbinary(32)"

0 commit comments

Comments
 (0)