Skip to content

Commit

Permalink
feat(spanner): enable row.ToStructLenient to work with STRUCT data ty…
Browse files Browse the repository at this point in the history
…pe (#5944)

* feat(spanner): enable row.ToStructLenient to work with STRUCT data type

* incorporate requested changes

* incorporate requested changes
  • Loading branch information
rahul2393 committed Apr 27, 2022
1 parent 61e8f5b commit bca8d50
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 5 deletions.
33 changes: 28 additions & 5 deletions spanner/value.go
Expand Up @@ -986,7 +986,7 @@ func parseNullTime(v *proto3.Value, p *NullTime, code sppb.TypeCode, isNull bool

// decodeValue decodes a protobuf Value into a pointer to a Go value, as
// specified by sppb.Type.
func decodeValue(v *proto3.Value, t *sppb.Type, ptr interface{}) error {
func decodeValue(v *proto3.Value, t *sppb.Type, ptr interface{}, opts ...decodeOptions) error {
if v == nil {
return errNilSrc()
}
Expand Down Expand Up @@ -1891,7 +1891,13 @@ func decodeValue(v *proto3.Value, t *sppb.Type, ptr interface{}) error {
if err != nil {
return err
}
if err = decodeStructArray(t.ArrayElementType.StructType, x, p); err != nil {
s := decodeSetting{
Lenient: false,
}
for _, opt := range opts {
opt.Apply(&s)
}
if err = decodeStructArray(t.ArrayElementType.StructType, x, p, s.Lenient); err != nil {
return err
}
}
Expand Down Expand Up @@ -3043,6 +3049,22 @@ func errDecodeStructField(ty *sppb.StructType, f string, err error) error {
return se
}

// decodeSetting contains all the settings for decoding from spanner struct
type decodeSetting struct {
Lenient bool
}

// decodeOptions is the interface to change decode struct settings
type decodeOptions interface {
Apply(s *decodeSetting)
}

type withLenient struct{ lenient bool }

func (w withLenient) Apply(s *decodeSetting) {
s.Lenient = w.lenient
}

// decodeStruct decodes proto3.ListValue pb into struct referenced by pointer
// ptr, according to
// the structural information given in sppb.StructType ty.
Expand Down Expand Up @@ -3087,8 +3109,9 @@ func decodeStruct(ty *sppb.StructType, pb *proto3.ListValue, ptr interface{}, le
// We don't allow duplicated field name.
return errDupSpannerField(f.Name, ty)
}
opts := []decodeOptions{withLenient{lenient: lenient}}
// Try to decode a single field.
if err := decodeValue(pb.Values[i], f.Type, v.FieldByIndex(sf.Index).Addr().Interface()); err != nil {
if err := decodeValue(pb.Values[i], f.Type, v.FieldByIndex(sf.Index).Addr().Interface(), opts...); err != nil {
return errDecodeStructField(ty, f.Name, err)
}
// Mark field f.Name as processed.
Expand All @@ -3113,7 +3136,7 @@ func isPtrStructPtrSlice(t reflect.Type) bool {
// decodeStructArray decodes proto3.ListValue pb into struct slice referenced by
// pointer ptr, according to the
// structural information given in a sppb.StructType.
func decodeStructArray(ty *sppb.StructType, pb *proto3.ListValue, ptr interface{}) error {
func decodeStructArray(ty *sppb.StructType, pb *proto3.ListValue, ptr interface{}, lenient bool) error {
if pb == nil {
return errNilListValue("STRUCT")
}
Expand All @@ -3139,7 +3162,7 @@ func decodeStructArray(ty *sppb.StructType, pb *proto3.ListValue, ptr interface{
return errDecodeArrayElement(i, pv, "STRUCT", err)
}
// Decode proto3.ListValue l into struct referenced by s.Interface().
if err = decodeStruct(ty, l, s.Interface(), false); err != nil {
if err = decodeStruct(ty, l, s.Interface(), lenient); err != nil {
return errDecodeArrayElement(i, pv, "STRUCT", err)
}
// Append the decoded struct back into the slice.
Expand Down
64 changes: 64 additions & 0 deletions spanner/value_test.go
Expand Up @@ -2273,6 +2273,70 @@ func TestDecodeStructWithPointers(t *testing.T) {
}
}

func TestDecodeStructArray(t *testing.T) {
stype := &sppb.StructType{Fields: []*sppb.StructType_Field{
{Name: "C", Type: &sppb.Type{Code: sppb.TypeCode_ARRAY,
ArrayElementType: &sppb.Type{
Code: sppb.TypeCode_STRUCT,
StructType: &sppb.StructType{Fields: []*sppb.StructType_Field{
{Name: "A", Type: intType()},
{Name: "B", Type: intType()},
}},
},
},
},
},
}
lv := listValueProto(listProto(listProto(intProto(1), intProto(2))))

type (
// inner struct
S2 struct {
A int64 `spanner:"A"`
}

S1 struct {
C []*S2 `spanner:"C"`
}
)

var (
test1 S1
test2 S1
)
for _, test := range []struct {
desc string
lenient bool
ptr interface{}
want interface{}
fail bool
}{
{
// when the Spanner returns more fields in inner struct compared to Go inner struct
desc: "decode to S1 with lenient enabled",
ptr: &test1,
want: &S1{C: []*S2{{A: 1}}},
lenient: true,
},
{
desc: "decode to S1 with lenient disabled",
ptr: &test2,
fail: true,
lenient: false,
},
} {
err := decodeStruct(stype, lv, test.ptr, test.lenient)
if (err != nil) != test.fail {
t.Errorf("%s: got error %v, wanted fail: %v", test.desc, err, test.fail)
}
if err == nil {
if !testutil.Equal(test.ptr, test.want) {
t.Errorf("%s: got %+v, want %+v", test.desc, test.ptr, test.want)
}
}
}
}

func TestEncodeStructValueDynamicStructs(t *testing.T) {
dynStructType := reflect.StructOf([]reflect.StructField{
{Name: "A", Type: reflect.TypeOf(0), Tag: `spanner:"a"`},
Expand Down

0 comments on commit bca8d50

Please sign in to comment.