2
0

CompositeType can assign to struct via reflection

This commit is contained in:
Jack Christensen
2020-05-12 16:58:16 -05:00
parent 9a3923b6e0
commit b3e1355a46
2 changed files with 81 additions and 12 deletions
+64 -12
View File
@@ -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:
+17
View File
@@ -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) {