diff --git a/spanner/value.go b/spanner/value.go index a9f82ae81c7..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{}) error { +func decodeValue(v *proto3.Value, t *sppb.Type, ptr interface{}, opts ...decodeOptions) error { if v == nil { return errNilSrc() } @@ -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 } } @@ -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. @@ -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. @@ -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") } @@ -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. 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"`},