From 30adcb517736d912dde34122178a1ead08913c7c Mon Sep 17 00:00:00 2001 From: meiji163 Date: Thu, 19 Dec 2024 11:20:43 -0800 Subject: [PATCH 1/6] reproduce bug in localtests --- localtests/bit-unique-key/create.sql | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 localtests/bit-unique-key/create.sql diff --git a/localtests/bit-unique-key/create.sql b/localtests/bit-unique-key/create.sql new file mode 100644 index 000000000..0497ae429 --- /dev/null +++ b/localtests/bit-unique-key/create.sql @@ -0,0 +1,9 @@ +drop table if exists gh_ost_test; +create table gh_ost_test ( + `id` bigint not null, + `bit_col` bit not null, + primary key (`id`, `bit_col`) +) DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; +insert into gh_ost_test values (1, b'1'); +insert into gh_ost_test values (2, b'1'); +insert into gh_ost_test values (3, b'1'); From 72a7b6e90be23740ce586cc6c94f6599715ca795 Mon Sep 17 00:00:00 2001 From: meiji163 Date: Thu, 19 Dec 2024 16:02:20 -0800 Subject: [PATCH 2/6] add localtest timeout 60s --- localtests/test.sh | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/localtests/test.sh b/localtests/test.sh index 0c8670cb6..589a7aa8c 100755 --- a/localtests/test.sh +++ b/localtests/test.sh @@ -18,6 +18,7 @@ ghost_structure_output_file=/tmp/gh-ost-test.ghost.structure.sql orig_content_output_file=/tmp/gh-ost-test.orig.content.csv ghost_content_output_file=/tmp/gh-ost-test.ghost.content.csv throttle_flag_file=/tmp/gh-ost-test.ghost.throttle.flag +timeout_duration="60s" master_host= master_port= @@ -67,7 +68,7 @@ verify_master_and_replica() { echo "Expecting test replica to have binlog_format=ROW" exit 1 fi - read replica_host replica_port <<< $(gh-ost-test-mysql-replica -e "select @@hostname, @@port" -ss) + read -r replica_host replica_port <<< "$(gh-ost-test-mysql-replica -e "select @@hostname, @@port" -ss)" [ "$replica_host" == "$(hostname)" ] && replica_host="127.0.0.1" echo "# replica verified at $replica_host:$replica_port" } @@ -190,9 +191,14 @@ test_single() { echo_dot echo $cmd > $exec_command_file echo_dot - bash $exec_command_file 1> $test_logfile 2>&1 + # run test in background so we can interrupt the "timeout" command + trap 'test -d /proc/$pid && kill -INT -$pid' INT + timeout "$timeout_duration" bash $exec_command_file 1> $test_logfile 2>&1 & + pid=$! + wait $pid execution_result=$? + trap - INT if [ -f $tests_path/$test_name/sql_mode ] ; then gh-ost-test-mysql-master --default-character-set=utf8mb4 test -e "set @@global.sql_mode='${original_sql_mode}'" @@ -203,6 +209,16 @@ test_single() { gh-ost-test-mysql-master --default-character-set=utf8mb4 test < $tests_path/$test_name/destroy.sql fi + if [ $execution_result -eq 124 ] ; then + echo + echo "ERROR $test_name execution exceeded timeout $timeout_duration" + return 1 + elif [ $execution_result -eq 130 ] ; then + echo + echo "$test_name execution interrupted" + return 130 + fi + if [ -f $tests_path/$test_name/expect_failure ] ; then if [ $execution_result -eq 0 ] ; then echo @@ -281,6 +297,10 @@ build_binary() { test_all() { build_binary test_dirs=$(find "$tests_path" -mindepth 1 -maxdepth 1 ! -path . -type d | grep "$test_pattern" | sort) + if [ -z "$test_dirs" ] ; then + echo "No tests found" + return 0 + fi while read -r test_dir; do test_name=$(basename "$test_dir") if ! test_single "$test_name" ; then From e6a4b0ce618fb7b52b5727ec5f5334805ebce192 Mon Sep 17 00:00:00 2001 From: meiji163 Date: Thu, 30 Apr 2026 12:53:48 -0700 Subject: [PATCH 3/6] convert BIT args in range comparisons --- go/logic/applier.go | 12 ++++++++++++ go/logic/inspect.go | 3 +++ go/sql/builder.go | 15 +++++++++------ go/sql/types.go | 11 ++++++++++- 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/go/logic/applier.go b/go/logic/applier.go index 47205c564..6e23d32a7 100644 --- a/go/logic/applier.go +++ b/go/logic/applier.go @@ -802,6 +802,10 @@ func (this *Applier) readMigrationMinValues(tx *gosql.Tx, uniqueKey *sql.UniqueK return err } } + abstractVals := this.migrationContext.MigrationRangeMinValues.AbstractValues() + for i, col := range uniqueKey.Columns.Columns() { + abstractVals[i] = col.ConvertArg(abstractVals[i]) + } this.migrationContext.Log.Infof("Migration min values: [%s]", this.migrationContext.MigrationRangeMinValues) return rows.Err() @@ -827,6 +831,10 @@ func (this *Applier) readMigrationMaxValues(tx *gosql.Tx, uniqueKey *sql.UniqueK return err } } + abstractVals := this.migrationContext.MigrationRangeMaxValues.AbstractValues() + for i, col := range uniqueKey.Columns.Columns() { + abstractVals[i] = col.ConvertArg(abstractVals[i]) + } this.migrationContext.Log.Infof("Migration max values: [%s]", this.migrationContext.MigrationRangeMaxValues) return rows.Err() @@ -911,6 +919,10 @@ func (this *Applier) CalculateNextIterationRangeEndValues() (hasFurtherRange boo return hasFurtherRange, err } if hasFurtherRange { + rangeMaxAbstractVals := iterationRangeMaxValues.AbstractValues() + for i, col := range this.migrationContext.UniqueKey.Columns.Columns() { + rangeMaxAbstractVals[i] = col.ConvertArg(rangeMaxAbstractVals[i]) + } this.migrationContext.MigrationIterationRangeMaxValues = iterationRangeMaxValues return hasFurtherRange, nil } diff --git a/go/logic/inspect.go b/go/logic/inspect.go index 97895890d..da8bc8fcc 100644 --- a/go/logic/inspect.go +++ b/go/logic/inspect.go @@ -748,6 +748,9 @@ func (this *Inspector) applyColumnTypes(databaseName, tableName string, columnsL column.Type = sql.BinaryColumnType column.BinaryOctetLength = columnOctetLength } + if strings.HasPrefix(columnType, "bit") { + column.Type = sql.BitColumnType + } if strings.Contains(extra, " GENERATED") { column.IsVirtual = true } diff --git a/go/sql/builder.go b/go/sql/builder.go index 940ca4ca3..2f7fdd0d6 100644 --- a/go/sql/builder.go +++ b/go/sql/builder.go @@ -58,6 +58,8 @@ func buildColumnsPreparedValues(columns *ColumnList) []string { token = fmt.Sprintf("ELT(?, %s)", column.EnumValues) } else if column.Type == JSONColumnType { token = "convert(? using utf8mb4)" + } else if column.Type == BitColumnType { + token = "cast(? as unsigned)" } else { token = "?" } @@ -169,11 +171,11 @@ func (b *CheckpointInsertQueryBuilder) BuildQuery(uniqueKeyArgs []interface{}) ( } convertedArgs := make([]interface{}, 0, 2*b.uniqueKeyColumns.Len()) for i, column := range b.uniqueKeyColumns.Columns() { - minArg := column.convertArg(uniqueKeyArgs[i]) + minArg := column.ConvertArg(uniqueKeyArgs[i]) convertedArgs = append(convertedArgs, minArg) } for i, column := range b.uniqueKeyColumns.Columns() { - minArg := column.convertArg(uniqueKeyArgs[i+b.uniqueKeyColumns.Len()]) + minArg := column.ConvertArg(uniqueKeyArgs[i+b.uniqueKeyColumns.Len()]) convertedArgs = append(convertedArgs, minArg) } return b.preparedStatement, convertedArgs, nil @@ -340,6 +342,7 @@ func BuildUniqueKeyRangeEndPreparedQueryViaOffset(databaseName, tableName string if includeRangeStartValues { startRangeComparisonSign = GreaterThanOrEqualsComparisonSign } + rangeStartComparison, rangeExplodedArgs, err := BuildRangePreparedComparison(uniqueKeyColumns, rangeStartArgs, startRangeComparisonSign) if err != nil { return "", explodedArgs, err @@ -533,7 +536,7 @@ func (b *DMLDeleteQueryBuilder) BuildQuery(args []interface{}) (string, []interf uniqueKeyArgs := make([]interface{}, 0, b.uniqueKeyColumns.Len()) for _, column := range b.uniqueKeyColumns.Columns() { tableOrdinal := b.tableColumns.Ordinals[column.Name] - arg := column.convertArg(args[tableOrdinal]) + arg := column.ConvertArg(args[tableOrdinal]) uniqueKeyArgs = append(uniqueKeyArgs, arg) } return b.preparedStatement, uniqueKeyArgs, nil @@ -595,7 +598,7 @@ func (b *DMLInsertQueryBuilder) BuildQuery(args []interface{}) (string, []interf sharedArgs := make([]interface{}, 0, b.sharedColumns.Len()) for _, column := range b.sharedColumns.Columns() { tableOrdinal := b.tableColumns.Ordinals[column.Name] - arg := column.convertArg(args[tableOrdinal]) + arg := column.ConvertArg(args[tableOrdinal]) sharedArgs = append(sharedArgs, arg) } return b.preparedStatement, sharedArgs, nil @@ -665,12 +668,12 @@ func (b *DMLUpdateQueryBuilder) BuildQuery(valueArgs, whereArgs []interface{}) ( args := make([]interface{}, 0, b.sharedColumns.Len()+b.uniqueKeyColumns.Len()) for _, column := range b.sharedColumns.Columns() { tableOrdinal := b.tableColumns.Ordinals[column.Name] - arg := column.convertArg(valueArgs[tableOrdinal]) + arg := column.ConvertArg(valueArgs[tableOrdinal]) args = append(args, arg) } for _, column := range b.uniqueKeyColumns.Columns() { tableOrdinal := b.tableColumns.Ordinals[column.Name] - arg := column.convertArg(whereArgs[tableOrdinal]) + arg := column.ConvertArg(whereArgs[tableOrdinal]) args = append(args, arg) } diff --git a/go/sql/types.go b/go/sql/types.go index 1a8f8a2e2..d90a00208 100644 --- a/go/sql/types.go +++ b/go/sql/types.go @@ -24,6 +24,7 @@ const ( JSONColumnType FloatColumnType BinaryColumnType + BitColumnType ) const maxMediumintUnsigned int32 = 16777215 @@ -57,7 +58,7 @@ type Column struct { MySQLType string } -func (this *Column) convertArg(arg interface{}) interface{} { +func (this *Column) ConvertArg(arg interface{}) interface{} { var arg2Bytes []byte if s, ok := arg.(string); ok { arg2Bytes = []byte(s) @@ -95,6 +96,14 @@ func (this *Column) convertArg(arg interface{}) interface{} { } } + if this.Type == BitColumnType { + var n uint64 + for _, b := range arg2Bytes { + n = (n << 8) | uint64(b) + } + arg = n + } + return arg } From c7e13a6da3a090c046557cc0e9c532132cbcf788 Mon Sep 17 00:00:00 2001 From: meiji163 Date: Thu, 30 Apr 2026 12:55:32 -0700 Subject: [PATCH 4/6] fix test --- go/sql/types_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/go/sql/types_test.go b/go/sql/types_test.go index 9275bbb85..fea8dfd78 100644 --- a/go/sql/types_test.go +++ b/go/sql/types_test.go @@ -62,7 +62,7 @@ func TestConvertArgCharsetDecoding(t *testing.T) { } // Should decode []uint8 - str := col.convertArg(latin1Bytes) + str := col.ConvertArg(latin1Bytes) require.Equal(t, "Garçon !", str) } @@ -85,7 +85,7 @@ func TestConvertArgBinaryColumnPadding(t *testing.T) { BinaryOctetLength: 20, } - result := col.convertArg(truncatedValue) + result := col.ConvertArg(truncatedValue) resultBytes := result.([]byte) require.Equal(t, 20, len(resultBytes), "binary column should be padded to declared length") @@ -111,7 +111,7 @@ func TestConvertArgVarbinaryStringWithInvalidUTF8Bytes(t *testing.T) { MySQLType: "varbinary(16)", // set by inspect.go from information_schema COLUMN_TYPE } - result := col.convertArg(string(rawBytes)) + result := col.ConvertArg(string(rawBytes)) require.IsType(t, []byte{}, result, "varbinary value from binlog (Go string) must be returned as []byte, not string, "+ @@ -130,7 +130,7 @@ func TestConvertArgVarbinaryBytesWithInvalidUTF8Bytes(t *testing.T) { MySQLType: "varbinary(16)", // set by inspect.go from information_schema COLUMN_TYPE } - result := col.convertArg(rawBytes) + result := col.ConvertArg(rawBytes) require.IsType(t, []byte{}, result) require.Equal(t, rawBytes, result.([]byte)) @@ -150,7 +150,7 @@ func TestConvertArgBinaryColumnNoPaddingWhenFull(t *testing.T) { BinaryOctetLength: 20, } - result := col.convertArg(fullValue) + result := col.ConvertArg(fullValue) resultBytes := result.([]byte) require.Equal(t, 20, len(resultBytes)) From 62a0038bdbf37d90297a71ff704c79c3dae69afb Mon Sep 17 00:00:00 2001 From: meiji163 Date: Thu, 30 Apr 2026 13:14:54 -0700 Subject: [PATCH 5/6] fix nil range column vals --- go/logic/applier.go | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/go/logic/applier.go b/go/logic/applier.go index 6e23d32a7..7a5dae8dd 100644 --- a/go/logic/applier.go +++ b/go/logic/applier.go @@ -802,13 +802,18 @@ func (this *Applier) readMigrationMinValues(tx *gosql.Tx, uniqueKey *sql.UniqueK return err } } - abstractVals := this.migrationContext.MigrationRangeMinValues.AbstractValues() - for i, col := range uniqueKey.Columns.Columns() { - abstractVals[i] = col.ConvertArg(abstractVals[i]) + if err := rows.Err(); err != nil { + return err + } + if this.migrationContext.MigrationRangeMinValues != nil { + abstractVals := this.migrationContext.MigrationRangeMinValues.AbstractValues() + for i, col := range uniqueKey.Columns.Columns() { + abstractVals[i] = col.ConvertArg(abstractVals[i]) + } } this.migrationContext.Log.Infof("Migration min values: [%s]", this.migrationContext.MigrationRangeMinValues) - return rows.Err() + return nil } // readMigrationMaxValues returns the maximum values to be iterated on rowcopy @@ -831,13 +836,18 @@ func (this *Applier) readMigrationMaxValues(tx *gosql.Tx, uniqueKey *sql.UniqueK return err } } - abstractVals := this.migrationContext.MigrationRangeMaxValues.AbstractValues() - for i, col := range uniqueKey.Columns.Columns() { - abstractVals[i] = col.ConvertArg(abstractVals[i]) + if err := rows.Err(); err != nil { + return err + } + if this.migrationContext.MigrationRangeMaxValues != nil { + abstractVals := this.migrationContext.MigrationRangeMaxValues.AbstractValues() + for i, col := range uniqueKey.Columns.Columns() { + abstractVals[i] = col.ConvertArg(abstractVals[i]) + } } - this.migrationContext.Log.Infof("Migration max values: [%s]", this.migrationContext.MigrationRangeMaxValues) - return rows.Err() + this.migrationContext.Log.Infof("Migration max values: [%s]", this.migrationContext.MigrationRangeMaxValues) + return nil } // ReadMigrationRangeValues reads min/max values that will be used for rowcopy. From a181283b7148018c236a944b7b240a159e9f9956 Mon Sep 17 00:00:00 2001 From: meiji163 Date: Thu, 30 Apr 2026 14:03:01 -0700 Subject: [PATCH 6/6] add test --- go/logic/applier.go | 19 +++++-------------- go/sql/builder.go | 12 ++++++------ go/sql/types.go | 9 ++++++++- go/sql/types_test.go | 20 +++++++++++++++----- 4 files changed, 34 insertions(+), 26 deletions(-) diff --git a/go/logic/applier.go b/go/logic/applier.go index 7a5dae8dd..2707b9d1e 100644 --- a/go/logic/applier.go +++ b/go/logic/applier.go @@ -805,13 +805,10 @@ func (this *Applier) readMigrationMinValues(tx *gosql.Tx, uniqueKey *sql.UniqueK if err := rows.Err(); err != nil { return err } + this.migrationContext.Log.Infof("Migration min values: [%s]", this.migrationContext.MigrationRangeMinValues) if this.migrationContext.MigrationRangeMinValues != nil { - abstractVals := this.migrationContext.MigrationRangeMinValues.AbstractValues() - for i, col := range uniqueKey.Columns.Columns() { - abstractVals[i] = col.ConvertArg(abstractVals[i]) - } + this.migrationContext.MigrationRangeMinValues.NormalizeValues(uniqueKey.Columns) } - this.migrationContext.Log.Infof("Migration min values: [%s]", this.migrationContext.MigrationRangeMinValues) return nil } @@ -839,14 +836,11 @@ func (this *Applier) readMigrationMaxValues(tx *gosql.Tx, uniqueKey *sql.UniqueK if err := rows.Err(); err != nil { return err } + this.migrationContext.Log.Infof("Migration max values: [%s]", this.migrationContext.MigrationRangeMaxValues) if this.migrationContext.MigrationRangeMaxValues != nil { - abstractVals := this.migrationContext.MigrationRangeMaxValues.AbstractValues() - for i, col := range uniqueKey.Columns.Columns() { - abstractVals[i] = col.ConvertArg(abstractVals[i]) - } + this.migrationContext.MigrationRangeMaxValues.NormalizeValues(uniqueKey.Columns) } - this.migrationContext.Log.Infof("Migration max values: [%s]", this.migrationContext.MigrationRangeMaxValues) return nil } @@ -929,11 +923,8 @@ func (this *Applier) CalculateNextIterationRangeEndValues() (hasFurtherRange boo return hasFurtherRange, err } if hasFurtherRange { - rangeMaxAbstractVals := iterationRangeMaxValues.AbstractValues() - for i, col := range this.migrationContext.UniqueKey.Columns.Columns() { - rangeMaxAbstractVals[i] = col.ConvertArg(rangeMaxAbstractVals[i]) - } this.migrationContext.MigrationIterationRangeMaxValues = iterationRangeMaxValues + this.migrationContext.MigrationIterationRangeMaxValues.NormalizeValues(this.migrationContext.UniqueKey.Columns) return hasFurtherRange, nil } } diff --git a/go/sql/builder.go b/go/sql/builder.go index 2f7fdd0d6..f9fc30627 100644 --- a/go/sql/builder.go +++ b/go/sql/builder.go @@ -171,11 +171,11 @@ func (b *CheckpointInsertQueryBuilder) BuildQuery(uniqueKeyArgs []interface{}) ( } convertedArgs := make([]interface{}, 0, 2*b.uniqueKeyColumns.Len()) for i, column := range b.uniqueKeyColumns.Columns() { - minArg := column.ConvertArg(uniqueKeyArgs[i]) + minArg := column.convertArg(uniqueKeyArgs[i]) convertedArgs = append(convertedArgs, minArg) } for i, column := range b.uniqueKeyColumns.Columns() { - minArg := column.ConvertArg(uniqueKeyArgs[i+b.uniqueKeyColumns.Len()]) + minArg := column.convertArg(uniqueKeyArgs[i+b.uniqueKeyColumns.Len()]) convertedArgs = append(convertedArgs, minArg) } return b.preparedStatement, convertedArgs, nil @@ -536,7 +536,7 @@ func (b *DMLDeleteQueryBuilder) BuildQuery(args []interface{}) (string, []interf uniqueKeyArgs := make([]interface{}, 0, b.uniqueKeyColumns.Len()) for _, column := range b.uniqueKeyColumns.Columns() { tableOrdinal := b.tableColumns.Ordinals[column.Name] - arg := column.ConvertArg(args[tableOrdinal]) + arg := column.convertArg(args[tableOrdinal]) uniqueKeyArgs = append(uniqueKeyArgs, arg) } return b.preparedStatement, uniqueKeyArgs, nil @@ -598,7 +598,7 @@ func (b *DMLInsertQueryBuilder) BuildQuery(args []interface{}) (string, []interf sharedArgs := make([]interface{}, 0, b.sharedColumns.Len()) for _, column := range b.sharedColumns.Columns() { tableOrdinal := b.tableColumns.Ordinals[column.Name] - arg := column.ConvertArg(args[tableOrdinal]) + arg := column.convertArg(args[tableOrdinal]) sharedArgs = append(sharedArgs, arg) } return b.preparedStatement, sharedArgs, nil @@ -668,12 +668,12 @@ func (b *DMLUpdateQueryBuilder) BuildQuery(valueArgs, whereArgs []interface{}) ( args := make([]interface{}, 0, b.sharedColumns.Len()+b.uniqueKeyColumns.Len()) for _, column := range b.sharedColumns.Columns() { tableOrdinal := b.tableColumns.Ordinals[column.Name] - arg := column.ConvertArg(valueArgs[tableOrdinal]) + arg := column.convertArg(valueArgs[tableOrdinal]) args = append(args, arg) } for _, column := range b.uniqueKeyColumns.Columns() { tableOrdinal := b.tableColumns.Ordinals[column.Name] - arg := column.ConvertArg(whereArgs[tableOrdinal]) + arg := column.convertArg(whereArgs[tableOrdinal]) args = append(args, arg) } diff --git a/go/sql/types.go b/go/sql/types.go index d90a00208..b93fe553c 100644 --- a/go/sql/types.go +++ b/go/sql/types.go @@ -58,7 +58,7 @@ type Column struct { MySQLType string } -func (this *Column) ConvertArg(arg interface{}) interface{} { +func (this *Column) convertArg(arg interface{}) interface{} { var arg2Bytes []byte if s, ok := arg.(string); ok { arg2Bytes = []byte(s) @@ -96,6 +96,7 @@ func (this *Column) ConvertArg(arg interface{}) interface{} { } } + // We convert BIT col to uint64 to force correct value comparison. if this.Type == BitColumnType { var n uint64 for _, b := range arg2Bytes { @@ -351,6 +352,12 @@ func (this *ColumnValues) AbstractValues() []interface{} { return this.abstractValues } +func (this *ColumnValues) NormalizeValues(columns ColumnList) { + for i, col := range columns.Columns() { + this.abstractValues[i] = col.convertArg(this.abstractValues[i]) + } +} + func (this *ColumnValues) StringColumn(index int) string { val := this.AbstractValues()[index] if ints, ok := val.([]uint8); ok { diff --git a/go/sql/types_test.go b/go/sql/types_test.go index fea8dfd78..cec2f2a74 100644 --- a/go/sql/types_test.go +++ b/go/sql/types_test.go @@ -62,7 +62,7 @@ func TestConvertArgCharsetDecoding(t *testing.T) { } // Should decode []uint8 - str := col.ConvertArg(latin1Bytes) + str := col.convertArg(latin1Bytes) require.Equal(t, "Garçon !", str) } @@ -85,7 +85,7 @@ func TestConvertArgBinaryColumnPadding(t *testing.T) { BinaryOctetLength: 20, } - result := col.ConvertArg(truncatedValue) + result := col.convertArg(truncatedValue) resultBytes := result.([]byte) require.Equal(t, 20, len(resultBytes), "binary column should be padded to declared length") @@ -111,7 +111,7 @@ func TestConvertArgVarbinaryStringWithInvalidUTF8Bytes(t *testing.T) { MySQLType: "varbinary(16)", // set by inspect.go from information_schema COLUMN_TYPE } - result := col.ConvertArg(string(rawBytes)) + result := col.convertArg(string(rawBytes)) require.IsType(t, []byte{}, result, "varbinary value from binlog (Go string) must be returned as []byte, not string, "+ @@ -130,7 +130,7 @@ func TestConvertArgVarbinaryBytesWithInvalidUTF8Bytes(t *testing.T) { MySQLType: "varbinary(16)", // set by inspect.go from information_schema COLUMN_TYPE } - result := col.ConvertArg(rawBytes) + result := col.convertArg(rawBytes) require.IsType(t, []byte{}, result) require.Equal(t, rawBytes, result.([]byte)) @@ -150,9 +150,19 @@ func TestConvertArgBinaryColumnNoPaddingWhenFull(t *testing.T) { BinaryOctetLength: 20, } - result := col.ConvertArg(fullValue) + result := col.convertArg(fullValue) resultBytes := result.([]byte) require.Equal(t, 20, len(resultBytes)) require.Equal(t, fullValue, resultBytes) } + +func TestConvertArgBitColumn(t *testing.T) { + b := []uint8{0x00, 0x00, 0xa3} + col := Column{ + Name: "bit_col", + Type: BitColumnType, + } + result := col.convertArg(b) + require.Equal(t, uint64(163), result) +}