From b3e1355a466d62bdb678216ce292a14115641f81 Mon Sep 17 00:00:00 2001 From: Jack Christensen Date: Tue, 12 May 2020 16:58:16 -0500 Subject: [PATCH] CompositeType can assign to struct via reflection --- composite_type.go | 76 +++++++++++++++++++++++++++++++++++------- composite_type_test.go | 17 ++++++++++ 2 files changed, 81 insertions(+), 12 deletions(-) diff --git a/composite_type.go b/composite_type.go index c37aef27..7f5ae694 100644 --- a/composite_type.go +++ b/composite_type.go @@ -2,6 +2,7 @@ package pgtype import ( "encoding/binary" + "reflect" "strings" "github.com/jackc/pgio" @@ -102,24 +103,19 @@ func (src CompositeType) AssignTo(dst interface{}) error { continue } - assignToErr := src.fields[i].AssignTo(v[i]) - if assignToErr != nil { - // Try to use get / set instead -- this avoids every type having to be able to AssignTo type of self. - setSucceeded := false - if setter, ok := v[i].(Value); ok { - err := setter.Set(src.fields[i].Get()) - setSucceeded = err == nil - } - if !setSucceeded { - return errors.Errorf("unable to assign to dst[%d]: %v", i, assignToErr) - } + err := assignToOrSet(src.fields[i], v[i]) + if err != nil { + return errors.Errorf("unable to assign to dst[%d]: %v", i, err) } - } return nil case *[]interface{}: return src.AssignTo(*v) default: + if isPtrStruct, err := src.assignToPtrStruct(dst); isPtrStruct { + return err + } + if nextDst, retry := GetAssignToDstType(dst); retry { return src.AssignTo(nextDst) } @@ -131,6 +127,62 @@ func (src CompositeType) AssignTo(dst interface{}) error { return errors.Errorf("cannot decode %#v into %T", src, dst) } +func assignToOrSet(src Value, dst interface{}) error { + assignToErr := src.AssignTo(dst) + if assignToErr != nil { + // Try to use get / set instead -- this avoids every type having to be able to AssignTo type of self. + setSucceeded := false + if setter, ok := dst.(Value); ok { + err := setter.Set(src.Get()) + setSucceeded = err == nil + } + if !setSucceeded { + return assignToErr + } + } + + return nil +} + +func (src CompositeType) assignToPtrStruct(dst interface{}) (bool, error) { + dstValue := reflect.ValueOf(dst) + if dstValue.Kind() != reflect.Ptr { + return false, nil + } + + if dstValue.IsNil() { + return false, nil + } + + dstElemValue := dstValue.Elem() + dstElemType := dstElemValue.Type() + + if dstElemType.Kind() != reflect.Struct { + return false, nil + } + + exportedFields := make([]int, 0, dstElemType.NumField()) + for i := 0; i < dstElemType.NumField(); i++ { + sf := dstElemType.Field(i) + if sf.PkgPath == "" { + exportedFields = append(exportedFields, i) + } + } + + if len(exportedFields) != len(src.fields) { + return false, nil + } + + for i := range exportedFields { + err := assignToOrSet(src.fields[i], dstElemValue.Field(exportedFields[i]).Addr().Interface()) + if err != nil { + return true, errors.Errorf("unable to assign to field %s: %v", dstElemType.Field(exportedFields[i]).Name, err) + } + } + + return true, nil +} + func (src CompositeType) EncodeBinary(ci *ConnInfo, buf []byte) (newBuf []byte, err error) { switch src.status { case Null: diff --git a/composite_type_test.go b/composite_type_test.go index 17d34251..0225e443 100644 --- a/composite_type_test.go +++ b/composite_type_test.go @@ -130,6 +130,23 @@ func TestCompositeTypeAssignTo(t *testing.T) { assert.Equal(t, pgtype.Text{String: "foo", Status: pgtype.Present}, a) assert.Equal(t, pgtype.Int4{Int: 42, Status: pgtype.Present}, b) } + + // Struct fields positionally via reflection + { + err := ct.Set([]interface{}{"foo", int32(42)}) + assert.NoError(t, err) + + s := struct { + A string + B int32 + }{} + + err = ct.AssignTo(&s) + if assert.NoError(t, err) { + assert.Equal(t, "foo", s.A) + assert.Equal(t, int32(42), s.B) + } + } } func TestCompositeTypeTranscode(t *testing.T) {