From 81505e6135d2095ea9d49eec93c26f82ceeb2cc3 Mon Sep 17 00:00:00 2001 From: Rahul Yadav Date: Tue, 26 Apr 2022 14:14:42 +0530 Subject: [PATCH 1/3] feat(spanner): enable row.ToStructLenient to work with STRUCT data type --- spanner/value.go | 15 ++++++---- spanner/value_test.go | 64 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 5 deletions(-) diff --git a/spanner/value.go b/spanner/value.go index a9f82ae81c7..1783d6ee11c 100644 --- a/spanner/value.go +++ b/spanner/value.go @@ -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 ...bool) error { if v == nil { return errNilSrc() } @@ -1891,7 +1891,12 @@ 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 { + lenient := false + if len(opts) > 0 { + // first bool option is to check if struct array decode should be lenient. + lenient = opts[0] + } + if err = decodeStructArray(t.ArrayElementType.StructType, x, p, lenient); err != nil { return err } } @@ -3088,7 +3093,7 @@ func decodeStruct(ty *sppb.StructType, pb *proto3.ListValue, ptr interface{}, le return errDupSpannerField(f.Name, ty) } // 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(), lenient); err != nil { return errDecodeStructField(ty, f.Name, err) } // Mark field f.Name as processed. @@ -3113,7 +3118,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") } @@ -3139,7 +3144,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. diff --git a/spanner/value_test.go b/spanner/value_test.go index 9b43aa1b456..01dd88eb3c0 100644 --- a/spanner/value_test.go +++ b/spanner/value_test.go @@ -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"`}, From b19d87fda11e42e89b86f2e93cf05e6d240008d3 Mon Sep 17 00:00:00 2001 From: Rahul Yadav Date: Tue, 26 Apr 2022 15:08:39 +0530 Subject: [PATCH 2/3] incorporate requested changes --- spanner/value.go | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/spanner/value.go b/spanner/value.go index 1783d6ee11c..5b6de97559d 100644 --- a/spanner/value.go +++ b/spanner/value.go @@ -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{}, opts ...bool) error { +func decodeValue(v *proto3.Value, t *sppb.Type, ptr interface{}, opts ...DecodeOptions) error { if v == nil { return errNilSrc() } @@ -1891,12 +1891,13 @@ func decodeValue(v *proto3.Value, t *sppb.Type, ptr interface{}, opts ...bool) e if err != nil { return err } - lenient := false - if len(opts) > 0 { - // first bool option is to check if struct array decode should be lenient. - lenient = opts[0] + s := DecodeSetting{ + Lenient: false, } - if err = decodeStructArray(t.ArrayElementType.StructType, x, p, lenient); err != nil { + for _, opt := range opts { + opt.Apply(&s) + } + if err = decodeStructArray(t.ArrayElementType.StructType, x, p, s.Lenient); err != nil { return err } } @@ -3048,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. @@ -3092,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(), lenient); 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. From f6328f8b0568d162e02b3d0c5eeeadfaf807d36e Mon Sep 17 00:00:00 2001 From: Rahul Yadav Date: Tue, 26 Apr 2022 20:50:54 +0530 Subject: [PATCH 3/3] incorporate requested changes --- spanner/value.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/spanner/value.go b/spanner/value.go index 5b6de97559d..70d7e7df7fd 100644 --- a/spanner/value.go +++ b/spanner/value.go @@ -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{}, opts ...DecodeOptions) error { +func decodeValue(v *proto3.Value, t *sppb.Type, ptr interface{}, opts ...decodeOptions) error { if v == nil { return errNilSrc() } @@ -1891,7 +1891,7 @@ func decodeValue(v *proto3.Value, t *sppb.Type, ptr interface{}, opts ...DecodeO if err != nil { return err } - s := DecodeSetting{ + s := decodeSetting{ Lenient: false, } for _, opt := range opts { @@ -3049,19 +3049,19 @@ 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 { +// 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) +// 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) { +func (w withLenient) Apply(s *decodeSetting) { s.Lenient = w.lenient } @@ -3109,7 +3109,7 @@ 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}} + 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(), opts...); err != nil { return errDecodeStructField(ty, f.Name, err)