Skip to content

Commit

Permalink
[release-18.0] tuple: serialized form (#14392) (#14394)
Browse files Browse the repository at this point in the history
Signed-off-by: Vicent Marti <vmg@strn.cat>
Co-authored-by: vitess-bot[bot] <108069721+vitess-bot[bot]@users.noreply.github.com>
  • Loading branch information
vitess-bot[bot] authored Oct 30, 2023
1 parent dbc352b commit 5476052
Show file tree
Hide file tree
Showing 15 changed files with 1,045 additions and 1,205 deletions.
15 changes: 9 additions & 6 deletions go/sqltypes/bind_variables.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,21 @@ var (
NullBindVariable = &querypb.BindVariable{Type: querypb.Type_NULL_TYPE}
)

func TupleToProto(v []Value) *querypb.Value {
return &querypb.Value{
Type: querypb.Type_TUPLE,
Value: encodeTuple(v),
}
}

// ValueToProto converts Value to a *querypb.Value.
func ValueToProto(v Value) *querypb.Value {
var protoValues []*querypb.Value
for _, value := range v.values {
protoValues = append(protoValues, ValueToProto(value))
}
return &querypb.Value{Type: v.typ, Value: v.val, Values: protoValues}
return &querypb.Value{Type: v.typ, Value: v.val}
}

// ProtoToValue converts a *querypb.Value to a Value.
func ProtoToValue(v *querypb.Value) Value {
return MakeTrustedValues(v.Type, v.Value, v.Values)
return MakeTrusted(v.Type, v.Value)
}

// BuildBindVariables builds a map[string]*querypb.BindVariable from a map[string]any
Expand Down
68 changes: 14 additions & 54 deletions go/sqltypes/bind_variables_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
"testing"

"github.com/stretchr/testify/assert"

"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"

Expand All @@ -42,67 +41,28 @@ func TestProtoConversions(t *testing.T) {
protoVal: &querypb.Value{Type: Int64, Value: []byte("1")},
}, {
name: "tuple value",
val: Value{
typ: Tuple,
values: []Value{
TestValue(VarChar, "1"),
TestValue(Int64, "3"),
},
},
protoVal: &querypb.Value{
Type: Tuple,
Values: []*querypb.Value{
{
Type: VarChar,
Value: []byte("1"),
}, {
Type: Int64,
Value: []byte("3"),
},
},
},
val: TestTuple(TestValue(VarChar, "1"), TestValue(Int64, "3")),
}, {
name: "tuple of tuple as a value",
val: Value{
typ: Tuple,
values: []Value{
{
typ: Tuple,
values: []Value{
TestValue(VarChar, "1"),
TestValue(Int64, "3"),
},
},
TestValue(Int64, "5"),
},
},
protoVal: &querypb.Value{
Type: Tuple,
Values: []*querypb.Value{
{
Type: Tuple,
Values: []*querypb.Value{
{
Type: VarChar,
Value: []byte("1"),
}, {
Type: Int64,
Value: []byte("3"),
},
},
}, {
Type: Int64,
Value: []byte("5"),
},
},
},
val: TestTuple(
TestTuple(
TestValue(VarChar, "1"),
TestValue(Int64, "3"),
),
TestValue(Int64, "5"),
),
},
}

for _, tcase := range tcases {
t.Run(tcase.name, func(t *testing.T) {
got := ValueToProto(tcase.val)
require.True(t, proto.Equal(got, tcase.protoVal), "ValueToProto: %v, want %v", got, tcase.protoVal)
// If we have an expected protoVal, check that serialization matches.
// For nested tuples, we do not attempt to generate a protoVal, as it is binary data.
// We simply check that the roundtrip is correct.
if tcase.protoVal != nil {
require.True(t, proto.Equal(got, tcase.protoVal), "ValueToProto: %v, want %v", got, tcase.protoVal)
}
gotback := ProtoToValue(got)
require.EqualValues(t, tcase.val, gotback)
})
Expand Down
11 changes: 2 additions & 9 deletions go/sqltypes/cached_size.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions go/sqltypes/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,15 @@ func TestValue(typ querypb.Type, val string) Value {
return MakeTrusted(typ, []byte(val))
}

// TestTuple builds a tuple Value from a list of Values.
// This function should only be used for testing.
func TestTuple(vals ...Value) Value {
return Value{
typ: Tuple,
val: encodeTuple(vals),
}
}

// PrintResults prints []*Results into a string.
// This function should only be used for testing.
func PrintResults(results []*Result) string {
Expand Down
71 changes: 55 additions & 16 deletions go/sqltypes/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import (
"strconv"
"strings"

"google.golang.org/protobuf/encoding/protowire"

"vitess.io/vitess/go/bytes2"
"vitess.io/vitess/go/hack"
"vitess.io/vitess/go/mysql/decimal"
Expand Down Expand Up @@ -63,9 +65,8 @@ type (
// an integral type, the bytes are always stored as a canonical
// representation that matches how MySQL returns such values.
Value struct {
typ querypb.Type
val []byte
values []Value
typ querypb.Type
val []byte
}

Row = []Value
Expand Down Expand Up @@ -110,19 +111,10 @@ func NewValue(typ querypb.Type, val []byte) (v Value, err error) {
// comments. Other packages can also use the function to create
// VarBinary or VarChar values.
func MakeTrusted(typ querypb.Type, val []byte) Value {
return MakeTrustedValues(typ, val, nil)
}

func MakeTrustedValues(typ querypb.Type, val []byte, values []*querypb.Value) Value {
if typ == Null {
return NULL
}
var sqlValues []Value
for _, v := range values {
sqlValues = append(sqlValues,
MakeTrustedValues(v.Type, v.Value, v.Values))
}
return Value{typ: typ, val: val, values: sqlValues}
return Value{typ: typ, val: val}
}

// NewHexNum builds an Hex Value.
Expand Down Expand Up @@ -454,12 +446,14 @@ func (v Value) EncodeSQLStringBuilder(b *strings.Builder) {
encodeBytesSQLBits(v.val, b)
case v.typ == Tuple:
b.WriteByte('(')
for i, bv := range v.values {
if i != 0 {
var i int
_ = v.ForEachValue(func(bv Value) {
if i > 0 {
b.WriteString(", ")
}
bv.EncodeSQLStringBuilder(b)
}
i++
})
b.WriteByte(')')
default:
b.Write(v.val)
Expand Down Expand Up @@ -658,6 +652,51 @@ func (v *Value) decodeBitNum() ([]byte, error) {
return i.Bytes(), nil
}

var ErrBadTupleEncoding = errors.New("bad tuple encoding in sqltypes.Value")

func encodeTuple(tuple []Value) []byte {
var total int
for _, v := range tuple {
total += len(v.val) + 3
}

buf := make([]byte, 0, total)
for _, v := range tuple {
buf = protowire.AppendVarint(buf, uint64(v.typ))
buf = protowire.AppendVarint(buf, uint64(len(v.val)))
buf = append(buf, v.val...)
}
return buf
}

func (v *Value) ForEachValue(each func(bv Value)) error {
if v.typ != Tuple {
panic("Value.ForEachValue on non-tuple")
}

var sz, ty uint64
var varlen int
buf := v.val
for len(buf) > 0 {
ty, varlen = protowire.ConsumeVarint(buf)
if varlen < 0 {
return ErrBadTupleEncoding
}

buf = buf[varlen:]
sz, varlen = protowire.ConsumeVarint(buf)
if varlen < 0 {
return ErrBadTupleEncoding
}

buf = buf[varlen:]
each(Value{val: buf[:sz], typ: Type(ty)})

buf = buf[sz:]
}
return nil
}

func encodeBytesSQL(val []byte, b BinWriter) {
buf := &bytes2.Buffer{}
encodeBytesSQLBytes2(val, buf)
Expand Down
9 changes: 1 addition & 8 deletions go/vt/proto/query/cached_size.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 5476052

Please sign in to comment.