Skip to content

Commit bca8d50

Browse files
authored
feat(spanner): enable row.ToStructLenient to work with STRUCT data type (#5944)
* feat(spanner): enable row.ToStructLenient to work with STRUCT data type * incorporate requested changes * incorporate requested changes
1 parent 61e8f5b commit bca8d50

2 files changed

Lines changed: 92 additions & 5 deletions

File tree

spanner/value.go

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -986,7 +986,7 @@ func parseNullTime(v *proto3.Value, p *NullTime, code sppb.TypeCode, isNull bool
986986

987987
// decodeValue decodes a protobuf Value into a pointer to a Go value, as
988988
// specified by sppb.Type.
989-
func decodeValue(v *proto3.Value, t *sppb.Type, ptr interface{}) error {
989+
func decodeValue(v *proto3.Value, t *sppb.Type, ptr interface{}, opts ...decodeOptions) error {
990990
if v == nil {
991991
return errNilSrc()
992992
}
@@ -1891,7 +1891,13 @@ func decodeValue(v *proto3.Value, t *sppb.Type, ptr interface{}) error {
18911891
if err != nil {
18921892
return err
18931893
}
1894-
if err = decodeStructArray(t.ArrayElementType.StructType, x, p); err != nil {
1894+
s := decodeSetting{
1895+
Lenient: false,
1896+
}
1897+
for _, opt := range opts {
1898+
opt.Apply(&s)
1899+
}
1900+
if err = decodeStructArray(t.ArrayElementType.StructType, x, p, s.Lenient); err != nil {
18951901
return err
18961902
}
18971903
}
@@ -3043,6 +3049,22 @@ func errDecodeStructField(ty *sppb.StructType, f string, err error) error {
30433049
return se
30443050
}
30453051

3052+
// decodeSetting contains all the settings for decoding from spanner struct
3053+
type decodeSetting struct {
3054+
Lenient bool
3055+
}
3056+
3057+
// decodeOptions is the interface to change decode struct settings
3058+
type decodeOptions interface {
3059+
Apply(s *decodeSetting)
3060+
}
3061+
3062+
type withLenient struct{ lenient bool }
3063+
3064+
func (w withLenient) Apply(s *decodeSetting) {
3065+
s.Lenient = w.lenient
3066+
}
3067+
30463068
// decodeStruct decodes proto3.ListValue pb into struct referenced by pointer
30473069
// ptr, according to
30483070
// the structural information given in sppb.StructType ty.
@@ -3087,8 +3109,9 @@ func decodeStruct(ty *sppb.StructType, pb *proto3.ListValue, ptr interface{}, le
30873109
// We don't allow duplicated field name.
30883110
return errDupSpannerField(f.Name, ty)
30893111
}
3112+
opts := []decodeOptions{withLenient{lenient: lenient}}
30903113
// Try to decode a single field.
3091-
if err := decodeValue(pb.Values[i], f.Type, v.FieldByIndex(sf.Index).Addr().Interface()); err != nil {
3114+
if err := decodeValue(pb.Values[i], f.Type, v.FieldByIndex(sf.Index).Addr().Interface(), opts...); err != nil {
30923115
return errDecodeStructField(ty, f.Name, err)
30933116
}
30943117
// Mark field f.Name as processed.
@@ -3113,7 +3136,7 @@ func isPtrStructPtrSlice(t reflect.Type) bool {
31133136
// decodeStructArray decodes proto3.ListValue pb into struct slice referenced by
31143137
// pointer ptr, according to the
31153138
// structural information given in a sppb.StructType.
3116-
func decodeStructArray(ty *sppb.StructType, pb *proto3.ListValue, ptr interface{}) error {
3139+
func decodeStructArray(ty *sppb.StructType, pb *proto3.ListValue, ptr interface{}, lenient bool) error {
31173140
if pb == nil {
31183141
return errNilListValue("STRUCT")
31193142
}
@@ -3139,7 +3162,7 @@ func decodeStructArray(ty *sppb.StructType, pb *proto3.ListValue, ptr interface{
31393162
return errDecodeArrayElement(i, pv, "STRUCT", err)
31403163
}
31413164
// Decode proto3.ListValue l into struct referenced by s.Interface().
3142-
if err = decodeStruct(ty, l, s.Interface(), false); err != nil {
3165+
if err = decodeStruct(ty, l, s.Interface(), lenient); err != nil {
31433166
return errDecodeArrayElement(i, pv, "STRUCT", err)
31443167
}
31453168
// Append the decoded struct back into the slice.

spanner/value_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2273,6 +2273,70 @@ func TestDecodeStructWithPointers(t *testing.T) {
22732273
}
22742274
}
22752275

2276+
func TestDecodeStructArray(t *testing.T) {
2277+
stype := &sppb.StructType{Fields: []*sppb.StructType_Field{
2278+
{Name: "C", Type: &sppb.Type{Code: sppb.TypeCode_ARRAY,
2279+
ArrayElementType: &sppb.Type{
2280+
Code: sppb.TypeCode_STRUCT,
2281+
StructType: &sppb.StructType{Fields: []*sppb.StructType_Field{
2282+
{Name: "A", Type: intType()},
2283+
{Name: "B", Type: intType()},
2284+
}},
2285+
},
2286+
},
2287+
},
2288+
},
2289+
}
2290+
lv := listValueProto(listProto(listProto(intProto(1), intProto(2))))
2291+
2292+
type (
2293+
// inner struct
2294+
S2 struct {
2295+
A int64 `spanner:"A"`
2296+
}
2297+
2298+
S1 struct {
2299+
C []*S2 `spanner:"C"`
2300+
}
2301+
)
2302+
2303+
var (
2304+
test1 S1
2305+
test2 S1
2306+
)
2307+
for _, test := range []struct {
2308+
desc string
2309+
lenient bool
2310+
ptr interface{}
2311+
want interface{}
2312+
fail bool
2313+
}{
2314+
{
2315+
// when the Spanner returns more fields in inner struct compared to Go inner struct
2316+
desc: "decode to S1 with lenient enabled",
2317+
ptr: &test1,
2318+
want: &S1{C: []*S2{{A: 1}}},
2319+
lenient: true,
2320+
},
2321+
{
2322+
desc: "decode to S1 with lenient disabled",
2323+
ptr: &test2,
2324+
fail: true,
2325+
lenient: false,
2326+
},
2327+
} {
2328+
err := decodeStruct(stype, lv, test.ptr, test.lenient)
2329+
if (err != nil) != test.fail {
2330+
t.Errorf("%s: got error %v, wanted fail: %v", test.desc, err, test.fail)
2331+
}
2332+
if err == nil {
2333+
if !testutil.Equal(test.ptr, test.want) {
2334+
t.Errorf("%s: got %+v, want %+v", test.desc, test.ptr, test.want)
2335+
}
2336+
}
2337+
}
2338+
}
2339+
22762340
func TestEncodeStructValueDynamicStructs(t *testing.T) {
22772341
dynStructType := reflect.StructOf([]reflect.StructField{
22782342
{Name: "A", Type: reflect.TypeOf(0), Tag: `spanner:"a"`},

0 commit comments

Comments
 (0)