diff --git a/go/mysql/collations/integration/coercion_test.go b/go/mysql/collations/integration/coercion_test.go index fd1f8ef0998..ad0e930b7e2 100644 --- a/go/mysql/collations/integration/coercion_test.go +++ b/go/mysql/collations/integration/coercion_test.go @@ -23,6 +23,8 @@ import ( "strings" "testing" + "github.com/stretchr/testify/require" + "vitess.io/vitess/go/mysql/collations" "vitess.io/vitess/go/mysql/collations/remote" "vitess.io/vitess/go/sqltypes" @@ -80,12 +82,14 @@ func (tc *testConcat) Test(t *testing.T, remote *RemoteCoercionResult, local col concat.Write(leftText) concat.Write(rightText) - if !bytes.Equal(concat.Bytes(), remote.Expr.ToBytes()) { + rEBytes, err := remote.Expr.ToBytes() + require.NoError(t, err) + if !bytes.Equal(concat.Bytes(), rEBytes) { t.Errorf("failed to concatenate text;\n\tCONCAT(%v COLLATE %s, %v COLLATE %s) = \n\tCONCAT(%v, %v) COLLATE %s = \n\t\t%v\n\n\texpected: %v", tc.left.Text, tc.left.Collation.Name(), tc.right.Text, tc.right.Collation.Name(), leftText, rightText, localCollation.Name(), - concat.Bytes(), remote.Expr.ToBytes(), + concat.Bytes(), rEBytes, ) } } @@ -102,6 +106,7 @@ func (tc *testComparison) Expression() string { } func (tc *testComparison) Test(t *testing.T, remote *RemoteCoercionResult, local collations.TypedCollation, coerce1, coerce2 collations.Coercion) { + localCollation := defaultenv.LookupByID(local.Collation) leftText, err := coerce1(nil, tc.left.Text) if err != nil { t.Errorf("failed to transcode left: %v", err) @@ -113,9 +118,9 @@ func (tc *testComparison) Test(t *testing.T, remote *RemoteCoercionResult, local t.Errorf("failed to transcode right: %v", err) return } - - remoteEquals := remote.Expr.ToBytes()[0] == '1' - localCollation := defaultenv.LookupByID(local.Collation) + rEBytes, err := remote.Expr.ToBytes() + require.NoError(t, err) + remoteEquals := rEBytes[0] == '1' localEquals := localCollation.Collate(leftText, rightText, false) == 0 if remoteEquals != localEquals { t.Errorf("failed to collate %#v = %#v with collation %s (expected %v, got %v)", diff --git a/go/mysql/collations/integration/collations_test.go b/go/mysql/collations/integration/collations_test.go index 1868141dba9..61672ccb663 100644 --- a/go/mysql/collations/integration/collations_test.go +++ b/go/mysql/collations/integration/collations_test.go @@ -28,6 +28,7 @@ import ( "strings" "testing" + "github.com/stretchr/testify/require" "golang.org/x/text/encoding/unicode/utf32" "vitess.io/vitess/go/mysql" @@ -113,14 +114,17 @@ func (u *uca900CollationTest) Test(t *testing.T, result *sqltypes.Result) { if row[1].Len() == 0 { continue } - utf8Input := parseUtf32cp(row[0].ToBytes()) + rowBytes, err := row[0].ToBytes() + require.NoError(t, err) + utf8Input := parseUtf32cp(rowBytes) if utf8Input == nil { t.Errorf("[%s] failed to parse UTF32-encoded codepoint: %s (%s)", u.collation, row[0], row[2].ToString()) errors++ continue } - - expectedWeightString := parseWeightString(row[1].ToBytes()) + rowBytes, err = row[1].ToBytes() + require.NoError(t, err) + expectedWeightString := parseWeightString(rowBytes) if expectedWeightString == nil { t.Errorf("[%s] failed to parse weight string: %s (%s)", u.collation, row[1], row[2].ToString()) errors++ diff --git a/go/mysql/collations/remote/charset.go b/go/mysql/collations/remote/charset.go index 6d2f8fb81c6..01b485403bb 100644 --- a/go/mysql/collations/remote/charset.go +++ b/go/mysql/collations/remote/charset.go @@ -92,7 +92,10 @@ func (c *Charset) performConversion(dst []byte, dstCharset string, src []byte, s if len(res.Rows) != 1 { return nil, fmt.Errorf("unexpected result from MySQL: %d rows returned", len(res.Rows)) } - result := res.Rows[0][0].ToBytes() + result, err := res.Rows[0][0].ToBytes() + if err != nil { + return nil, err + } if dst != nil { return append(dst, result...), nil } diff --git a/go/mysql/collations/remote/collation.go b/go/mysql/collations/remote/collation.go index ed66344744d..2dcc19bb161 100644 --- a/go/mysql/collations/remote/collation.go +++ b/go/mysql/collations/remote/collation.go @@ -160,10 +160,11 @@ func (c *Collation) WeightString(dst, src []byte, numCodepoints int) []byte { c.sql.WriteString(")") if result := c.performRemoteQuery(); result != nil { + resultBytes, _ := result[0].ToBytes() if dst == nil { - dst = result[0].ToBytes() + dst = resultBytes } else { - dst = append(dst, result[0].ToBytes()...) + dst = append(dst, resultBytes...) } } return dst diff --git a/go/mysql/endtoend/replication_test.go b/go/mysql/endtoend/replication_test.go index 8e0d302c6cb..96f8fae06ab 100644 --- a/go/mysql/endtoend/replication_test.go +++ b/go/mysql/endtoend/replication_test.go @@ -25,6 +25,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "vitess.io/vitess/go/vt/vtgate/evalengine" "context" @@ -1025,7 +1027,12 @@ func TestRowReplicationTypes(t *testing.T) { sql.WriteString(", ") sql.WriteString(tcase.name) sql.WriteString(" = ") - if values[i+1].Type() == querypb.Type_TIMESTAMP && !bytes.HasPrefix(values[i+1].ToBytes(), mysql.ZeroTimestamp) { + valueBytes, err := values[i+1].ToBytes() + // Expression values are not supported with ToBytes + if values[i+1].Type() != querypb.Type_EXPRESSION { + require.NoError(t, err) + } + if values[i+1].Type() == querypb.Type_TIMESTAMP && !bytes.HasPrefix(valueBytes, mysql.ZeroTimestamp) { // Values in the binary log are UTC. Let's convert them // to whatever timezone the connection is using, // so MySQL properly converts them back to UTC. diff --git a/go/sqltypes/plan_value.go b/go/sqltypes/plan_value.go index d3e962d6543..c9d157710c1 100644 --- a/go/sqltypes/plan_value.go +++ b/go/sqltypes/plan_value.go @@ -150,7 +150,7 @@ func (pv PlanValue) MarshalJSON() ([]byte, error) { return json.Marshal(":" + pv.Key) case !pv.Value.IsNull(): if pv.Value.IsIntegral() { - return pv.Value.ToBytes(), nil + return pv.Value.ToBytes() } return json.Marshal(pv.Value.ToString()) case pv.ListKey != "": diff --git a/go/sqltypes/result.go b/go/sqltypes/result.go index 602b9fa5c8e..09518215986 100644 --- a/go/sqltypes/result.go +++ b/go/sqltypes/result.go @@ -221,9 +221,9 @@ func saveRowsAnalysis(r Result, allRows map[string]int, totalRows *int, incremen for _, row := range r.Rows { newHash := hashCodeForRow(row) if increment { - allRows[newHash] += 1 + allRows[newHash]++ } else { - allRows[newHash] -= 1 + allRows[newHash]-- } } if increment { diff --git a/go/sqltypes/value.go b/go/sqltypes/value.go index 5d1b5591167..8aabe66a1ae 100644 --- a/go/sqltypes/value.go +++ b/go/sqltypes/value.go @@ -19,9 +19,11 @@ package sqltypes import ( "encoding/base64" + "encoding/hex" "encoding/json" "errors" "fmt" + "regexp" "strconv" "strings" @@ -29,6 +31,9 @@ import ( "vitess.io/vitess/go/hack" querypb "vitess.io/vitess/go/vt/proto/query" + "vitess.io/vitess/go/vt/proto/vtrpc" + vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc" + "vitess.io/vitess/go/vt/vterrors" ) var ( @@ -236,13 +241,21 @@ func (v Value) RawStr() string { // ToBytes returns the value as MySQL would return it as []byte. // In contrast, Raw returns the internal representation of the Value, which may not -// match MySQL's representation for newer types. -// If the value is not convertible like in the case of Expression, it returns nil. -func (v Value) ToBytes() []byte { +// match MySQL's representation for hex encoded binary data or newer types. +// If the value is not convertible like in the case of Expression, it returns an error. +func (v Value) ToBytes() ([]byte, error) { if v.typ == Expression { - return nil + return nil, vterrors.New(vtrpcpb.Code_INVALID_ARGUMENT, "expression cannot be converted to bytes") } - return v.val + if v.typ == HexVal { + dv, err := v.decodeHexVal() + return dv, err + } + if v.typ == HexNum { + dv, err := v.decodeHexNum() + return dv, err + } + return v.val, nil } // Len returns the length. @@ -469,6 +482,38 @@ func (v *Value) UnmarshalJSON(b []byte) error { return err } +// decodeHexVal decodes the SQL hex value of the form x'A1' into a byte +// array matching what MySQL would return when querying the column where +// an INSERT was performed with x'A1' having been specified as a value +func (v *Value) decodeHexVal() ([]byte, error) { + match, err := regexp.Match("^x'.*'$", v.val) + if !match || err != nil { + return nil, vterrors.Errorf(vtrpc.Code_INVALID_ARGUMENT, "invalid hex value: %v", v.val) + } + hexBytes := v.val[2 : len(v.val)-1] + decodedHexBytes, err := hex.DecodeString(string(hexBytes)) + if err != nil { + return nil, err + } + return decodedHexBytes, nil +} + +// decodeHexNum decodes the SQL hex value of the form 0xA1 into a byte +// array matching what MySQL would return when querying the column where +// an INSERT was performed with 0xA1 having been specified as a value +func (v *Value) decodeHexNum() ([]byte, error) { + match, err := regexp.Match("^0x.*$", v.val) + if !match || err != nil { + return nil, vterrors.Errorf(vtrpc.Code_INVALID_ARGUMENT, "invalid hex number: %v", v.val) + } + hexBytes := v.val[2:] + decodedHexBytes, err := hex.DecodeString(string(hexBytes)) + if err != nil { + return nil, err + } + return decodedHexBytes, nil +} + func encodeBytesSQL(val []byte, b BinWriter) { buf := &bytes2.Buffer{} encodeBytesSQLBytes2(val, buf) diff --git a/go/sqltypes/value_test.go b/go/sqltypes/value_test.go index 362581302a0..8b54428e90b 100644 --- a/go/sqltypes/value_test.go +++ b/go/sqltypes/value_test.go @@ -22,6 +22,8 @@ import ( "strings" "testing" + "github.com/stretchr/testify/require" + querypb "vitess.io/vitess/go/vt/proto/query" ) @@ -403,7 +405,9 @@ func TestToBytesAndString(t *testing.T) { TestValue(Int64, "1"), TestValue(Int64, "12"), } { - if b := v.ToBytes(); !bytes.Equal(b, v.Raw()) { + vBytes, err := v.ToBytes() + require.NoError(t, err) + if b := vBytes; !bytes.Equal(b, v.Raw()) { t.Errorf("%v.ToBytes: %s, want %s", v, b, v.Raw()) } if s := v.ToString(); s != string(v.Raw()) { @@ -412,7 +416,9 @@ func TestToBytesAndString(t *testing.T) { } tv := TestValue(Expression, "aa") - if b := tv.ToBytes(); b != nil { + tvBytes, err := tv.ToBytes() + require.EqualError(t, err, "expression cannot be converted to bytes") + if b := tvBytes; b != nil { t.Errorf("%v.ToBytes: %s, want nil", tv, b) } if s := tv.ToString(); s != "" { diff --git a/go/vt/binlog/binlog_connection.go b/go/vt/binlog/binlog_connection.go index ded68cfb27f..2681875b14a 100644 --- a/go/vt/binlog/binlog_connection.go +++ b/go/vt/binlog/binlog_connection.go @@ -41,6 +41,7 @@ var ( // connecting for replication. Each such connection must identify itself to // mysqld with a server ID that is unique both among other BinlogConnections and // among actual replicas in the topology. +//revive:disable because I'm not trying to refactor the entire code base right now type BinlogConnection struct { *mysql.Conn cp dbconfigs.Connector diff --git a/go/vt/binlog/binlog_streamer.go b/go/vt/binlog/binlog_streamer.go index 51f9d84022c..3c054a485a3 100644 --- a/go/vt/binlog/binlog_streamer.go +++ b/go/vt/binlog/binlog_streamer.go @@ -758,7 +758,11 @@ func writeValuesAsSQL(sql *sqlparser.TrackedBuffer, tce *tableCacheEntry, rs *my if err != nil { return keyspaceIDCell, nil, err } - if value.Type() == querypb.Type_TIMESTAMP && !bytes.HasPrefix(value.ToBytes(), mysql.ZeroTimestamp) { + vBytes, err := value.ToBytes() + if err != nil { + return sqltypes.Value{}, nil, err + } + if value.Type() == querypb.Type_TIMESTAMP && !bytes.HasPrefix(vBytes, mysql.ZeroTimestamp) { // Values in the binary log are UTC. Let's convert them // to whatever timezone the connection is using, // so MySQL properly converts them back to UTC. @@ -819,7 +823,11 @@ func writeIdentifiersAsSQL(sql *sqlparser.TrackedBuffer, tce *tableCacheEntry, r if err != nil { return keyspaceIDCell, nil, err } - if value.Type() == querypb.Type_TIMESTAMP && !bytes.HasPrefix(value.ToBytes(), mysql.ZeroTimestamp) { + vBytes, err := value.ToBytes() + if err != nil { + return keyspaceIDCell, nil, err + } + if value.Type() == querypb.Type_TIMESTAMP && !bytes.HasPrefix(vBytes, mysql.ZeroTimestamp) { // Values in the binary log are UTC. Let's convert them // to whatever timezone the connection is using, // so MySQL properly converts them back to UTC. diff --git a/go/vt/binlog/keyspace_id_resolver.go b/go/vt/binlog/keyspace_id_resolver.go index d48fbc186a7..d5c93e09778 100644 --- a/go/vt/binlog/keyspace_id_resolver.go +++ b/go/vt/binlog/keyspace_id_resolver.go @@ -103,7 +103,11 @@ type keyspaceIDResolverFactoryV2 struct { func (r *keyspaceIDResolverFactoryV2) keyspaceID(v sqltypes.Value) ([]byte, error) { switch r.shardingColumnType { case topodatapb.KeyspaceIdType_BYTES: - return v.ToBytes(), nil + vBytes, err := v.ToBytes() + if err != nil { + return nil, err + } + return vBytes, nil case topodatapb.KeyspaceIdType_UINT64: i, err := evalengine.ToUint64(v) if err != nil { diff --git a/go/vt/mysqlctl/tmutils/permissions.go b/go/vt/mysqlctl/tmutils/permissions.go index 2770491f8c3..57c8922adfa 100644 --- a/go/vt/mysqlctl/tmutils/permissions.go +++ b/go/vt/mysqlctl/tmutils/permissions.go @@ -68,7 +68,8 @@ func NewUserPermission(fields []*querypb.Field, values []sqltypes.Value) *tablet case "user": up.User = values[i].ToString() case "password": - up.PasswordChecksum = crc64.Checksum(values[i].ToBytes(), hashTable) + vBytes, _ := values[i].ToBytes() + up.PasswordChecksum = crc64.Checksum(vBytes, hashTable) case "password_last_changed": // we skip this one, as the value may be // different on primary and replicas. diff --git a/go/vt/sqlparser/ast_funcs.go b/go/vt/sqlparser/ast_funcs.go index 0e254d42cca..684fdaa0903 100644 --- a/go/vt/sqlparser/ast_funcs.go +++ b/go/vt/sqlparser/ast_funcs.go @@ -20,6 +20,7 @@ import ( "bytes" "encoding/hex" "encoding/json" + "regexp" "strings" "vitess.io/vitess/go/hack" @@ -481,7 +482,7 @@ func (node *Literal) HexDecode() ([]byte, error) { return hex.DecodeString(node.Val) } -// EncodeHexValToMySQLQueryFormat encodes the hexval back into the query format +// encodeHexValToMySQLQueryFormat encodes the hexval back into the query format // for passing on to MySQL as a bind var func (node *Literal) encodeHexValToMySQLQueryFormat() ([]byte, error) { nb := node.Bytes() @@ -490,7 +491,11 @@ func (node *Literal) encodeHexValToMySQLQueryFormat() ([]byte, error) { } // Let's make this idempotent in case it's called more than once - if nb[0] == 'x' && nb[1] == '0' && nb[len(nb)-1] == '\'' { + match, err := regexp.Match("^x'.*'$", nb) + if err != nil { + return nb, err + } + if match { return nb, nil } diff --git a/go/vt/vtctl/workflow/server.go b/go/vt/vtctl/workflow/server.go index 9b355ce5d76..aa40969a5d5 100644 --- a/go/vt/vtctl/workflow/server.go +++ b/go/vt/vtctl/workflow/server.go @@ -100,7 +100,11 @@ func (s *Server) CheckReshardingJournalExistsOnTablet(ctx context.Context, table if len(p3qr.Rows) != 0 { qr := sqltypes.Proto3ToResult(p3qr) - if err := prototext.Unmarshal(qr.Rows[0][0].ToBytes(), &journal); err != nil { + qrBytes, err := qr.Rows[0][0].ToBytes() + if err != nil { + return nil, false, err + } + if err := prototext.Unmarshal(qrBytes, &journal); err != nil { return nil, false, err } @@ -334,7 +338,11 @@ func (s *Server) GetWorkflows(ctx context.Context, req *vtctldatapb.GetWorkflows } var bls binlogdatapb.BinlogSource - if err := prototext.Unmarshal(row[2].ToBytes(), &bls); err != nil { + rowBytes, err := row[2].ToBytes() + if err != nil { + return err + } + if err := prototext.Unmarshal(rowBytes, &bls); err != nil { return err } diff --git a/go/vt/vtctl/workflow/stream_migrator.go b/go/vt/vtctl/workflow/stream_migrator.go index 323835edcb4..1b23b52503d 100644 --- a/go/vt/vtctl/workflow/stream_migrator.go +++ b/go/vt/vtctl/workflow/stream_migrator.go @@ -233,7 +233,11 @@ func (sm *StreamMigrator) readTabletStreams(ctx context.Context, ti *topo.Tablet } var bls binlogdatapb.BinlogSource - if err := prototext.Unmarshal(row[2].ToBytes(), &bls); err != nil { + rowBytes, err := row[2].ToBytes() + if err != nil { + return nil, err + } + if err := prototext.Unmarshal(rowBytes, &bls); err != nil { return nil, err } diff --git a/go/vt/vtctl/workflow/traffic_switcher.go b/go/vt/vtctl/workflow/traffic_switcher.go index c4378a231b3..6f038e610df 100644 --- a/go/vt/vtctl/workflow/traffic_switcher.go +++ b/go/vt/vtctl/workflow/traffic_switcher.go @@ -246,7 +246,11 @@ func BuildTargets(ctx context.Context, ts *topo.Server, tmc tmclient.TabletManag } var bls binlogdatapb.BinlogSource - if err := prototext.Unmarshal(row[1].ToBytes(), &bls); err != nil { + rowBytes, err := row[1].ToBytes() + if err != nil { + return nil, err + } + if err := prototext.Unmarshal(rowBytes, &bls); err != nil { return nil, err } diff --git a/go/vt/vtgate/engine/ordered_aggregate.go b/go/vt/vtgate/engine/ordered_aggregate.go index c3ed40c5ea6..bd69a48ce3d 100644 --- a/go/vt/vtgate/engine/ordered_aggregate.go +++ b/go/vt/vtgate/engine/ordered_aggregate.go @@ -461,7 +461,11 @@ func (oa *OrderedAggregate) merge(fields []*querypb.Field, row1, row2 []sqltypes result[aggr.Col], err = evalengine.NullSafeAdd(row1[aggr.Col], row2[aggr.Col], OpcodeType[aggr.Opcode]) case AggregateGtid: vgtid := &binlogdatapb.VGtid{} - err = proto.Unmarshal(row1[aggr.Col].ToBytes(), vgtid) + rowBytes, err := row1[aggr.Col].ToBytes() + if err != nil { + return nil, nil, err + } + err = proto.Unmarshal(rowBytes, vgtid) if err != nil { return nil, nil, err } @@ -544,7 +548,11 @@ func (oa *OrderedAggregate) convertFinal(current []sqltypes.Value) ([]sqltypes.V switch aggr.Opcode { case AggregateGtid: vgtid := &binlogdatapb.VGtid{} - err := proto.Unmarshal(current[aggr.Col].ToBytes(), vgtid) + currentBytes, err := current[aggr.Col].ToBytes() + if err != nil { + return nil, err + } + err = proto.Unmarshal(currentBytes, vgtid) if err != nil { return nil, err } diff --git a/go/vt/vtgate/evalengine/arithmetic.go b/go/vt/vtgate/evalengine/arithmetic.go index ed73e997520..9f17d50f579 100644 --- a/go/vt/vtgate/evalengine/arithmetic.go +++ b/go/vt/vtgate/evalengine/arithmetic.go @@ -218,7 +218,15 @@ func NullsafeCompare(v1, v2 sqltypes.Value, collationID collations.ID) (int, err } if isByteComparable(v1.Type()) && isByteComparable(v2.Type()) { - return bytes.Compare(v1.ToBytes(), v2.ToBytes()), nil + v1Bytes, err1 := v1.ToBytes() + if err1 != nil { + return 0, err1 + } + v2Bytes, err2 := v2.ToBytes() + if err2 != nil { + return 0, err2 + } + return bytes.Compare(v1Bytes, v2Bytes), nil } typ, err := CoerceTo(v1.Type(), v2.Type()) // TODO systay we should add a method where this decision is done at plantime @@ -249,7 +257,15 @@ func NullsafeCompare(v1, v2 sqltypes.Value, collationID collations.ID) (int, err ID: collationID, } } - switch result := collation.Collate(v1.ToBytes(), v2.ToBytes(), false); { + v1Bytes, err1 := v1.ToBytes() + if err1 != nil { + return 0, err1 + } + v2Bytes, err2 := v2.ToBytes() + if err2 != nil { + return 0, err2 + } + switch result := collation.Collate(v1Bytes, v2Bytes, false); { case result < 0: return -1, nil case result > 0: diff --git a/go/vt/vtgate/evalengine/arithmetic_test.go b/go/vt/vtgate/evalengine/arithmetic_test.go index f2e06a0c8d7..fc705041ffd 100644 --- a/go/vt/vtgate/evalengine/arithmetic_test.go +++ b/go/vt/vtgate/evalengine/arithmetic_test.go @@ -782,7 +782,7 @@ func TestCast(t *testing.T) { }, { typ: querypb.Type_VARCHAR, v: TestValue(sqltypes.Expression, "bad string"), - err: vterrors.New(vtrpcpb.Code_INVALID_ARGUMENT, "EXPRESSION(bad string) cannot be cast to VARCHAR"), + err: vterrors.New(vtrpcpb.Code_INVALID_ARGUMENT, "expression cannot be converted to bytes"), }} for _, tcase := range tcases { got, err := Cast(tcase.v, tcase.typ) @@ -1558,7 +1558,8 @@ func TestMaxCollate(t *testing.T) { } func printValue(v sqltypes.Value) string { - return fmt.Sprintf("%v:%q", v.Type(), v.ToBytes()) + vBytes, _ := v.ToBytes() + return fmt.Sprintf("%v:%q", v.Type(), vBytes) } // These benchmarks show that using existing ASCII representations diff --git a/go/vt/vtgate/evalengine/evalengine.go b/go/vt/vtgate/evalengine/evalengine.go index f05e7cc7780..8b47b05e034 100644 --- a/go/vt/vtgate/evalengine/evalengine.go +++ b/go/vt/vtgate/evalengine/evalengine.go @@ -35,17 +35,21 @@ func Cast(v sqltypes.Value, typ querypb.Type) (sqltypes.Value, error) { if v.Type() == typ || v.IsNull() { return v, nil } + vBytes, err := v.ToBytes() + if err != nil { + return v, err + } if sqltypes.IsSigned(typ) && v.IsSigned() { - return sqltypes.MakeTrusted(typ, v.ToBytes()), nil + return sqltypes.MakeTrusted(typ, vBytes), nil } if sqltypes.IsUnsigned(typ) && v.IsUnsigned() { - return sqltypes.MakeTrusted(typ, v.ToBytes()), nil + return sqltypes.MakeTrusted(typ, vBytes), nil } if (sqltypes.IsFloat(typ) || typ == sqltypes.Decimal) && (v.IsIntegral() || v.IsFloat() || v.Type() == sqltypes.Decimal) { - return sqltypes.MakeTrusted(typ, v.ToBytes()), nil + return sqltypes.MakeTrusted(typ, vBytes), nil } if sqltypes.IsQuoted(typ) && (v.IsIntegral() || v.IsFloat() || v.Type() == sqltypes.Decimal || v.IsQuoted()) { - return sqltypes.MakeTrusted(typ, v.ToBytes()), nil + return sqltypes.MakeTrusted(typ, vBytes), nil } // Explicitly disallow Expression. @@ -55,7 +59,7 @@ func Cast(v sqltypes.Value, typ querypb.Type) (sqltypes.Value, error) { // If the above fast-paths were not possible, // go through full validation. - return sqltypes.NewValue(typ, v.ToBytes()) + return sqltypes.NewValue(typ, vBytes) } // ToUint64 converts Value to uint64. @@ -136,7 +140,7 @@ func ToNative(v sqltypes.Value) (interface{}, error) { case v.IsFloat(): return ToFloat64(v) case v.IsQuoted() || v.Type() == sqltypes.Bit || v.Type() == sqltypes.Decimal: - out = v.ToBytes() + out, err = v.ToBytes() case v.Type() == sqltypes.Expression: err = vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "%v cannot be converted to a go type", v) } diff --git a/go/vt/vtgate/vindexes/binary.go b/go/vt/vtgate/vindexes/binary.go index 95c8208a69d..7a2ed815e4c 100644 --- a/go/vt/vtgate/vindexes/binary.go +++ b/go/vt/vtgate/vindexes/binary.go @@ -63,7 +63,11 @@ func (vind *Binary) NeedsVCursor() bool { func (vind *Binary) Verify(_ VCursor, ids []sqltypes.Value, ksids [][]byte) ([]bool, error) { out := make([]bool, len(ids)) for i := range ids { - out[i] = bytes.Equal(ids[i].ToBytes(), ksids[i]) + idBytes, err := ids[i].ToBytes() + if err != nil { + return out, err + } + out[i] = bytes.Equal(idBytes, ksids[i]) } return out, nil } @@ -72,7 +76,11 @@ func (vind *Binary) Verify(_ VCursor, ids []sqltypes.Value, ksids [][]byte) ([]b func (vind *Binary) Map(cursor VCursor, ids []sqltypes.Value) ([]key.Destination, error) { out := make([]key.Destination, len(ids)) for i, id := range ids { - out[i] = key.DestinationKeyspaceID(id.ToBytes()) + idBytes, err := id.ToBytes() + if err != nil { + return out, err + } + out[i] = key.DestinationKeyspaceID(idBytes) } return out, nil } diff --git a/go/vt/vtgate/vindexes/binary_test.go b/go/vt/vtgate/vindexes/binary_test.go index 167a05e8ef2..0399c1687bc 100644 --- a/go/vt/vtgate/vindexes/binary_test.go +++ b/go/vt/vtgate/vindexes/binary_test.go @@ -18,6 +18,8 @@ package vindexes import ( "bytes" + "encoding/hex" + "fmt" "reflect" "testing" @@ -69,13 +71,17 @@ func TestBinaryMap(t *testing.T) { } func TestBinaryVerify(t *testing.T) { - ids := []sqltypes.Value{sqltypes.NewVarBinary("1"), sqltypes.NewVarBinary("2")} - ksids := [][]byte{[]byte("1"), []byte("1")} + hexValStr := "8a1e" + hexValStrSQL := fmt.Sprintf("x'%s'", hexValStr) + hexNumStrSQL := fmt.Sprintf("0x%s", hexValStr) + hexBytes, _ := hex.DecodeString(hexValStr) + ids := []sqltypes.Value{sqltypes.NewVarBinary("1"), sqltypes.NewVarBinary("2"), sqltypes.NewHexVal([]byte(hexValStrSQL)), sqltypes.NewHexNum([]byte(hexNumStrSQL))} + ksids := [][]byte{[]byte("1"), []byte("1"), hexBytes, hexBytes} got, err := binOnlyVindex.Verify(nil, ids, ksids) if err != nil { t.Fatal(err) } - want := []bool{true, false} + want := []bool{true, false, true, true} if !reflect.DeepEqual(got, want) { t.Errorf("binary.Verify: %v, want %v", got, want) } diff --git a/go/vt/vtgate/vindexes/binarymd5.go b/go/vt/vtgate/vindexes/binarymd5.go index 7cd99626919..7bf46cf6f47 100644 --- a/go/vt/vtgate/vindexes/binarymd5.go +++ b/go/vt/vtgate/vindexes/binarymd5.go @@ -62,7 +62,11 @@ func (vind *BinaryMD5) NeedsVCursor() bool { func (vind *BinaryMD5) Verify(_ VCursor, ids []sqltypes.Value, ksids [][]byte) ([]bool, error) { out := make([]bool, len(ids)) for i := range ids { - out[i] = bytes.Equal(vMD5Hash(ids[i].ToBytes()), ksids[i]) + idBytes, err := ids[i].ToBytes() + if err != nil { + return out, err + } + out[i] = bytes.Equal(vMD5Hash(idBytes), ksids[i]) } return out, nil } @@ -71,7 +75,11 @@ func (vind *BinaryMD5) Verify(_ VCursor, ids []sqltypes.Value, ksids [][]byte) ( func (vind *BinaryMD5) Map(cursor VCursor, ids []sqltypes.Value) ([]key.Destination, error) { out := make([]key.Destination, len(ids)) for i, id := range ids { - out[i] = key.DestinationKeyspaceID(vMD5Hash(id.ToBytes())) + idBytes, err := id.ToBytes() + if err != nil { + return out, err + } + out[i] = key.DestinationKeyspaceID(vMD5Hash(idBytes)) } return out, nil } diff --git a/go/vt/vtgate/vindexes/binarymd5_test.go b/go/vt/vtgate/vindexes/binarymd5_test.go index 5dfd3ae770f..58fb485abe3 100644 --- a/go/vt/vtgate/vindexes/binarymd5_test.go +++ b/go/vt/vtgate/vindexes/binarymd5_test.go @@ -17,6 +17,7 @@ limitations under the License. package vindexes import ( + "encoding/hex" "fmt" "reflect" "testing" @@ -72,13 +73,17 @@ func TestBinaryMD5Map(t *testing.T) { } func TestBinaryMD5Verify(t *testing.T) { - ids := []sqltypes.Value{sqltypes.NewVarBinary("Test"), sqltypes.NewVarBinary("TEst")} - ksids := [][]byte{[]byte("\f\xbcf\x11\xf5T\vЀ\x9a8\x8d\xc9Za["), []byte("\f\xbcf\x11\xf5T\vЀ\x9a8\x8d\xc9Za[")} + hexValStr := "21cf" + hexValStrSQL := fmt.Sprintf("x'%s'", hexValStr) + hexNumStrSQL := fmt.Sprintf("0x%s", hexValStr) + hexBytes, _ := hex.DecodeString(hexValStr) + ids := []sqltypes.Value{sqltypes.NewVarBinary("Test"), sqltypes.NewVarBinary("TEst"), sqltypes.NewHexVal([]byte(hexValStrSQL)), sqltypes.NewHexNum([]byte(hexNumStrSQL))} + ksids := [][]byte{[]byte("\f\xbcf\x11\xf5T\vЀ\x9a8\x8d\xc9Za["), []byte("\f\xbcf\x11\xf5T\vЀ\x9a8\x8d\xc9Za["), vMD5Hash(hexBytes), vMD5Hash(hexBytes)} got, err := binVindex.Verify(nil, ids, ksids) if err != nil { t.Fatal(err) } - want := []bool{true, false} + want := []bool{true, false, true, true} if !reflect.DeepEqual(got, want) { t.Errorf("binaryMD5.Verify: %v, want %v", got, want) } diff --git a/go/vt/vtgate/vindexes/cfc.go b/go/vt/vtgate/vindexes/cfc.go index 9cd79abcc83..ee03af48d46 100644 --- a/go/vt/vtgate/vindexes/cfc.go +++ b/go/vt/vtgate/vindexes/cfc.go @@ -207,7 +207,11 @@ func (vind *CFC) computeKsid(v []byte, prefix bool) ([]byte, error) { func (vind *CFC) Verify(_ VCursor, ids []sqltypes.Value, ksids [][]byte) ([]bool, error) { out := make([]bool, len(ids)) for i := range ids { - v, err := vind.computeKsid(ids[i].ToBytes(), false) + idBytes, err := ids[i].ToBytes() + if err != nil { + return out, err + } + v, err := vind.computeKsid(idBytes, false) if err != nil { return nil, err } @@ -220,7 +224,11 @@ func (vind *CFC) Verify(_ VCursor, ids []sqltypes.Value, ksids [][]byte) ([]bool func (vind *CFC) Map(cursor VCursor, ids []sqltypes.Value) ([]key.Destination, error) { out := make([]key.Destination, len(ids)) for i, id := range ids { - v, err := vind.computeKsid(id.ToBytes(), false) + idBytes, err := id.ToBytes() + if err != nil { + return out, err + } + v, err := vind.computeKsid(idBytes, false) if err != nil { return nil, err } @@ -292,7 +300,10 @@ func (vind *prefixCFC) IsUnique() bool { func (vind *prefixCFC) Map(cursor VCursor, ids []sqltypes.Value) ([]key.Destination, error) { out := make([]key.Destination, len(ids)) for i, id := range ids { - value := id.ToBytes() + value, err := id.ToBytes() + if err != nil { + return out, err + } prefix := findPrefix(value) begin, err := vind.computeKsid(prefix, true) if err != nil { diff --git a/go/vt/vtgate/vindexes/consistent_lookup.go b/go/vt/vtgate/vindexes/consistent_lookup.go index 863910ee0f0..ea9dd141082 100644 --- a/go/vt/vtgate/vindexes/consistent_lookup.go +++ b/go/vt/vtgate/vindexes/consistent_lookup.go @@ -111,7 +111,11 @@ func (lu *ConsistentLookup) Map(vcursor VCursor, ids []sqltypes.Value) ([]key.De } ksids := make([][]byte, 0, len(result.Rows)) for _, row := range result.Rows { - ksids = append(ksids, row[0].ToBytes()) + rowBytes, err := row[0].ToBytes() + if err != nil { + return nil, err + } + ksids = append(ksids, rowBytes) } out = append(out, key.DestinationKeyspaceIDs(ksids)) } @@ -174,7 +178,11 @@ func (lu *ConsistentLookupUnique) Map(vcursor VCursor, ids []sqltypes.Value) ([] case 0: out = append(out, key.DestinationNone{}) case 1: - out = append(out, key.DestinationKeyspaceID(result.Rows[0][0].ToBytes())) + rowBytes, err := result.Rows[0][0].ToBytes() + if err != nil { + return out, err + } + out = append(out, key.DestinationKeyspaceID(rowBytes)) default: return nil, fmt.Errorf("Lookup.Map: unexpected multiple results from vindex %s: %v", lu.lkp.Table, ids[i]) } @@ -285,7 +293,10 @@ func (lu *clCommon) handleDup(vcursor VCursor, values []sqltypes.Value, ksid []b return err } case 1: - existingksid := qr.Rows[0][0].ToBytes() + existingksid, err := qr.Rows[0][0].ToBytes() + if err != nil { + return err + } // Lock the target row using normal transaction priority. qr, err = vcursor.ExecuteKeyspaceID(lu.keyspace, existingksid, lu.lockOwnerQuery, bindVars, false /* rollbackOnError */, false /* autocommit */) if err != nil { diff --git a/go/vt/vtgate/vindexes/lookup.go b/go/vt/vtgate/vindexes/lookup.go index d06881e8a12..081b9a9aab7 100644 --- a/go/vt/vtgate/vindexes/lookup.go +++ b/go/vt/vtgate/vindexes/lookup.go @@ -95,7 +95,11 @@ func (ln *LookupNonUnique) Map(vcursor VCursor, ids []sqltypes.Value) ([]key.Des } ksids := make([][]byte, 0, len(result.Rows)) for _, row := range result.Rows { - ksids = append(ksids, row[0].ToBytes()) + rowBytes, err := row[0].ToBytes() + if err != nil { + return nil, err + } + ksids = append(ksids, rowBytes) } out = append(out, key.DestinationKeyspaceIDs(ksids)) } @@ -247,7 +251,11 @@ func (lu *LookupUnique) Map(vcursor VCursor, ids []sqltypes.Value) ([]key.Destin case 0: out = append(out, key.DestinationNone{}) case 1: - out = append(out, key.DestinationKeyspaceID(result.Rows[0][0].ToBytes())) + rowBytes, err := result.Rows[0][0].ToBytes() + if err != nil { + return nil, err + } + out = append(out, key.DestinationKeyspaceID(rowBytes)) default: return nil, fmt.Errorf("Lookup.Map: unexpected multiple results from vindex %s: %v", lu.lkp.Table, ids[i]) } diff --git a/go/vt/vtgate/vindexes/lookup_internal.go b/go/vt/vtgate/vindexes/lookup_internal.go index 7097e605a2a..87186e02fd9 100644 --- a/go/vt/vtgate/vindexes/lookup_internal.go +++ b/go/vt/vtgate/vindexes/lookup_internal.go @@ -174,7 +174,9 @@ func (v *sorter) Less(i, j int) bool { rightRow := v.rowsColValues[j] for cell, left := range leftRow { right := rightRow[cell] - compare := bytes.Compare(left.ToBytes(), right.ToBytes()) + lBytes, _ := left.ToBytes() + rBytes, _ := right.ToBytes() + compare := bytes.Compare(lBytes, rBytes) if compare < 0 { return true } @@ -182,7 +184,9 @@ func (v *sorter) Less(i, j int) bool { return false } } - return bytes.Compare(v.toValues[i].ToBytes(), v.toValues[j].ToBytes()) < 0 + iBytes, _ := v.toValues[i].ToBytes() + jBytes, _ := v.toValues[j].ToBytes() + return bytes.Compare(iBytes, jBytes) < 0 } func (v *sorter) Swap(i, j int) { diff --git a/go/vt/vtgate/vindexes/unicode.go b/go/vt/vtgate/vindexes/unicode.go index 9c274495cbb..da28f972568 100644 --- a/go/vt/vtgate/vindexes/unicode.go +++ b/go/vt/vtgate/vindexes/unicode.go @@ -35,7 +35,11 @@ func unicodeHash(hashFunc func([]byte) []byte, key sqltypes.Value) ([]byte, erro collator := collatorPool.Get().(*pooledCollator) defer collatorPool.Put(collator) - norm, err := normalize(collator.col, collator.buf, key.ToBytes()) + keyBytes, err := key.ToBytes() + if err != nil { + return nil, err + } + norm, err := normalize(collator.col, collator.buf, keyBytes) if err != nil { return nil, err } diff --git a/go/vt/vtgate/vindexes/xxhash.go b/go/vt/vtgate/vindexes/xxhash.go index 8f177301163..defafd55b34 100644 --- a/go/vt/vtgate/vindexes/xxhash.go +++ b/go/vt/vtgate/vindexes/xxhash.go @@ -65,8 +65,11 @@ func (vind *XXHash) NeedsVCursor() bool { func (vind *XXHash) Map(cursor VCursor, ids []sqltypes.Value) ([]key.Destination, error) { out := make([]key.Destination, len(ids)) for i := range ids { - id := ids[i].ToBytes() - out[i] = key.DestinationKeyspaceID(vXXHash(id)) + idBytes, err := ids[i].ToBytes() + if err != nil { + return out, err + } + out[i] = key.DestinationKeyspaceID(vXXHash(idBytes)) } return out, nil } @@ -75,8 +78,11 @@ func (vind *XXHash) Map(cursor VCursor, ids []sqltypes.Value) ([]key.Destination func (vind *XXHash) Verify(_ VCursor, ids []sqltypes.Value, ksids [][]byte) ([]bool, error) { out := make([]bool, len(ids)) for i := range ids { - id := ids[i].ToBytes() - out[i] = bytes.Equal(vXXHash(id), ksids[i]) + idBytes, err := ids[i].ToBytes() + if err != nil { + return out, err + } + out[i] = bytes.Equal(vXXHash(idBytes), ksids[i]) } return out, nil } diff --git a/go/vt/vtgate/vindexes/xxhash_test.go b/go/vt/vtgate/vindexes/xxhash_test.go index 1586f9a4d89..7ccfdc5da6d 100644 --- a/go/vt/vtgate/vindexes/xxhash_test.go +++ b/go/vt/vtgate/vindexes/xxhash_test.go @@ -18,6 +18,7 @@ package vindexes import ( "bytes" + "encoding/hex" "fmt" "reflect" "testing" @@ -94,13 +95,17 @@ func TestXXHashMap(t *testing.T) { } func TestXXHashVerify(t *testing.T) { - ids := []sqltypes.Value{sqltypes.NewUint64(1), sqltypes.NewUint64(2)} - ksids := [][]byte{{0xd4, 0x64, 0x5, 0x36, 0x76, 0x12, 0xb4, 0xb7}, {0xd4, 0x64, 0x5, 0x36, 0x76, 0x12, 0xb4, 0xb7}} + hexValStr := "9efa" + hexValStrSQL := fmt.Sprintf("x'%s'", hexValStr) + hexNumStrSQL := fmt.Sprintf("0x%s", hexValStr) + hexBytes, _ := hex.DecodeString(hexValStr) + ids := []sqltypes.Value{sqltypes.NewUint64(1), sqltypes.NewUint64(2), sqltypes.NewHexVal([]byte(hexValStrSQL)), sqltypes.NewHexNum([]byte(hexNumStrSQL))} + ksids := [][]byte{{0xd4, 0x64, 0x5, 0x36, 0x76, 0x12, 0xb4, 0xb7}, {0xd4, 0x64, 0x5, 0x36, 0x76, 0x12, 0xb4, 0xb7}, vXXHash(hexBytes), vXXHash(hexBytes)} got, err := xxHash.Verify(nil, ids, ksids) if err != nil { t.Fatal(err) } - want := []bool{true, false} + want := []bool{true, false, true, true} if !reflect.DeepEqual(got, want) { t.Errorf("xxHash.Verify: %v, want %v", got, want) } diff --git a/go/vt/vttablet/tabletserver/schema/historian.go b/go/vt/vttablet/tabletserver/schema/historian.go index 118079f7bad..bec7bfed727 100644 --- a/go/vt/vttablet/tabletserver/schema/historian.go +++ b/go/vt/vttablet/tabletserver/schema/historian.go @@ -184,17 +184,29 @@ func (h *historian) loadFromDB(ctx context.Context) error { // readRow converts a row from the schema_version table to a trackedSchema func (h *historian) readRow(row []sqltypes.Value) (*trackedSchema, int64, error) { id, _ := evalengine.ToInt64(row[0]) - pos, err := mysql.DecodePosition(string(row[1].ToBytes())) + rowBytes, err := row[1].ToBytes() if err != nil { return nil, 0, err } - ddl := string(row[2].ToBytes()) + pos, err := mysql.DecodePosition(string(rowBytes)) + if err != nil { + return nil, 0, err + } + rowBytes, err = row[2].ToBytes() + if err != nil { + return nil, 0, err + } + ddl := string(rowBytes) timeUpdated, err := evalengine.ToInt64(row[3]) if err != nil { return nil, 0, err } sch := &binlogdatapb.MinimalSchema{} - if err := proto.Unmarshal(row[4].ToBytes(), sch); err != nil { + rowBytes, err = row[4].ToBytes() + if err != nil { + return nil, 0, err + } + if err := proto.Unmarshal(rowBytes, sch); err != nil { return nil, 0, err } log.V(vl).Infof("Read tracked schema from db: id %d, pos %v, ddl %s, schema len %d, time_updated %d \n", diff --git a/go/vt/vttablet/tabletserver/vstreamer/vstreamer.go b/go/vt/vttablet/tabletserver/vstreamer/vstreamer.go index 2eb946006ad..7b3a39c0f5b 100644 --- a/go/vt/vttablet/tabletserver/vstreamer/vstreamer.go +++ b/go/vt/vttablet/tabletserver/vstreamer/vstreamer.go @@ -801,7 +801,11 @@ nextrow: continue } journal := &binlogdatapb.Journal{} - if err := prototext.Unmarshal(afterValues[i].ToBytes(), journal); err != nil { + avBytes, err := afterValues[i].ToBytes() + if err != nil { + return nil, err + } + if err := prototext.Unmarshal(avBytes, journal); err != nil { return nil, err } vevents = append(vevents, &binlogdatapb.VEvent{ @@ -913,7 +917,11 @@ func (vs *vstreamer) extractRowAndFilter(plan *streamerPlan, data []byte, dataCo if maxBytesPerChar > 1 { maxCharLen := plan.Table.Fields[colNum].ColumnLength / maxBytesPerChar if uint32(value.Len()) > maxCharLen { - originalVal := value.ToBytes() + ovBytes, err := value.ToBytes() + if err != nil { + return false, nil, err + } + originalVal := ovBytes // Let's be sure that we're not going to be trimming non-null bytes firstNullBytePos := bytes.IndexByte(originalVal, byte(0)) diff --git a/go/vt/worker/key_resolver.go b/go/vt/worker/key_resolver.go index b58fa09087f..dd7b5e43800 100644 --- a/go/vt/worker/key_resolver.go +++ b/go/vt/worker/key_resolver.go @@ -77,7 +77,11 @@ func (r *v2Resolver) keyspaceID(row []sqltypes.Value) ([]byte, error) { v := row[r.shardingColumnIndex] switch r.keyspaceInfo.ShardingColumnType { case topodatapb.KeyspaceIdType_BYTES: - return v.ToBytes(), nil + vBytes, err := v.ToBytes() + if err != nil { + return nil, err + } + return vBytes, nil case topodatapb.KeyspaceIdType_UINT64: i, err := evalengine.ToUint64(v) if err != nil { diff --git a/go/vt/wrangler/resharder.go b/go/vt/wrangler/resharder.go index e16b9445b24..93265fe2ace 100644 --- a/go/vt/wrangler/resharder.go +++ b/go/vt/wrangler/resharder.go @@ -211,7 +211,11 @@ func (rs *resharder) readRefStreams(ctx context.Context) error { return fmt.Errorf("VReplication streams must have named workflows for migration: shard: %s:%s", source.Keyspace(), source.ShardName()) } var bls binlogdatapb.BinlogSource - if err := prototext.Unmarshal(row[1].ToBytes(), &bls); err != nil { + rowBytes, err := row[1].ToBytes() + if err != nil { + return err + } + if err := prototext.Unmarshal(rowBytes, &bls); err != nil { return vterrors.Wrapf(err, "prototext.Unmarshal: %v", row) } isReference, err := rs.blsIsReference(&bls) diff --git a/go/vt/wrangler/vdiff.go b/go/vt/wrangler/vdiff.go index c3a23d355d5..0667a000f6d 100644 --- a/go/vt/wrangler/vdiff.go +++ b/go/vt/wrangler/vdiff.go @@ -640,7 +640,11 @@ func (df *vdiff) stopTargets(ctx context.Context) error { for _, row := range qr.Rows { var bls binlogdatapb.BinlogSource - if err := prototext.Unmarshal(row[0].ToBytes(), &bls); err != nil { + rowBytes, err := row[0].ToBytes() + if err != nil { + return err + } + if err := prototext.Unmarshal(rowBytes, &bls); err != nil { return err } pos, err := binlogplayer.DecodePosition(row[1].ToString()) @@ -1057,7 +1061,15 @@ func (td *tableDiffer) compare(sourceRow, targetRow []sqltypes.Value, cols []com var c int var err error if sourceRow[compareIndex].IsText() && targetRow[compareIndex].IsText() { - c = bytes.Compare(sourceRow[compareIndex].ToBytes(), targetRow[compareIndex].ToBytes()) + srowBytes, err := sourceRow[compareIndex].ToBytes() + if err != nil { + return 0, err + } + trowBytes, err := targetRow[compareIndex].ToBytes() + if err != nil { + return 0, err + } + c = bytes.Compare(srowBytes, trowBytes) } else { // TODO(king-11) make collation aware c, err = evalengine.NullsafeCompare(sourceRow[compareIndex], targetRow[compareIndex], collations.Unknown) diff --git a/go/vt/wrangler/vexec.go b/go/vt/wrangler/vexec.go index 2be1bce18b6..ff5f52a806f 100644 --- a/go/vt/wrangler/vexec.go +++ b/go/vt/wrangler/vexec.go @@ -414,7 +414,11 @@ func (wr *Wrangler) getReplicationStatusFromRow(ctx context.Context, row []sqlty if err != nil { return nil, "", err } - if err := prototext.Unmarshal(row[1].ToBytes(), &bls); err != nil { + rowBytes, err := row[1].ToBytes() + if err != nil { + return nil, "", err + } + if err := prototext.Unmarshal(rowBytes, &bls); err != nil { return nil, "", err }