Skip to content

Commit

Permalink
fix(spanner): allow toStruct to decode value into struct with unequal…
Browse files Browse the repository at this point in the history
… number of fields compared to Spanner row fields
  • Loading branch information
rahul2393 committed Nov 18, 2021
1 parent 479c2f9 commit 9c05613
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 6 deletions.
79 changes: 74 additions & 5 deletions spanner/row_test.go
Expand Up @@ -556,19 +556,17 @@ func TestToStructInvalidDst(t *testing.T) {
PK1 string `spanner:"STRING"`
PK2 string `spanner:"STRING"`
}{},
errNoOrDupGoField(&struct {
errDupGoField(&struct {
PK1 string `spanner:"STRING"`
PK2 string `spanner:"STRING"`
}{}, "STRING"),
}{}),
},
{
"Decode row as STRUCT to Go struct with no field for the PK column",
&struct {
PK1 string `spanner:"_STRING"`
}{},
errNoOrDupGoField(&struct {
PK1 string `spanner:"_STRING"`
}{}, "STRING"),
nil,
},
{
"Decode row as STRUCT to Go struct with wrong type for the PK column",
Expand Down Expand Up @@ -1676,6 +1674,77 @@ func TestToStructEmbedded(t *testing.T) {
}
}

func TestToStructWithUnEqualFields(t *testing.T) {
type (
extraField struct {
F1 string
F2 string
F3 string
}
lessField struct {
F1 string
F3 string
}
)
testCases := []struct {
name string
want interface{}
row Row
}{
{
name: "destination struct has extra field",
row: Row{
[]*sppb.StructType_Field{
{Name: "F1", Type: stringType()},
{Name: "F2", Type: stringType()},
},
[]*proto3.Value{
stringProto("v1"),
stringProto("v2"),
},
},
want: extraField{F1: "v1", F2: "v2", F3: ""},
},
{
name: "destination struct has less field",
row: Row{
[]*sppb.StructType_Field{
{Name: "F1", Type: stringType()},
{Name: "F2", Type: stringType()},
{Name: "F3", Type: stringType()},
},
[]*proto3.Value{
stringProto("v1"),
stringProto("v2"),
stringProto("v3"),
},
},
want: lessField{F1: "v1", F3: "v3"},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var got interface{}
if reflect.TypeOf(tc.want).Name() == "extraField" {
var extraType extraField
if err := tc.row.ToStruct(&extraType); err != nil {
t.Fatal(err)
}
got = extraType
} else {
var lessType lessField
if err := tc.row.ToStruct(&lessType); err != nil {
t.Fatal(err)
}
got = lessType
}
if !testEqual(got, tc.want) {
t.Errorf("got %+v, want %+v", got, tc.want)
}
})
}
}

func TestRowToString(t *testing.T) {
r := Row{
[]*sppb.StructType_Field{
Expand Down
17 changes: 16 additions & 1 deletion spanner/value.go
Expand Up @@ -2871,6 +2871,11 @@ func errNilSpannerStructType() error {
return spannerErrorf(codes.FailedPrecondition, "unexpected nil StructType in decoding Cloud Spanner STRUCT")
}

// errDupGoField returns error for duplicated Go STRUCT field names
func errDupGoField(s interface{}) error {
return spannerErrorf(codes.InvalidArgument, "Go struct %+v(type %T) has duplicate fields for GO STRUCT field", s, s)
}

// errUnnamedField returns error for decoding a Cloud Spanner STRUCT with
// unnamed field into a Go struct.
func errUnnamedField(ty *sppb.StructType, i int) error {
Expand Down Expand Up @@ -2921,14 +2926,24 @@ func decodeStruct(ty *sppb.StructType, pb *proto3.ListValue, ptr interface{}) er
if err != nil {
return ToSpannerError(err)
}
for i := 0; i < t.NumField(); i++ {
name, keep, _, _ := spannerTagParser(t.Field(i).Tag)
if !keep {
continue
}
sf := fields.Match(name)
if sf == nil {
return errDupGoField(ptr)
}
}
seen := map[string]bool{}
for i, f := range ty.Fields {
if f.Name == "" {
return errUnnamedField(ty, i)
}
sf := fields.Match(f.Name)
if sf == nil {
return errNoOrDupGoField(ptr, f.Name)
continue
}
if seen[f.Name] {
// We don't allow duplicated field name.
Expand Down

0 comments on commit 9c05613

Please sign in to comment.