pgx/record.go
2020-04-11 11:08:53 +01:00

185 lines
3.9 KiB
Go

package pgtype
import (
"encoding/binary"
"reflect"
errors "golang.org/x/xerrors"
)
// Record is the generic PostgreSQL record type such as is created with the
// "row" function. Record only implements BinaryEncoder and Value. The text
// format output format from PostgreSQL does not include type information and is
// therefore impossible to decode. No encoders are implemented because
// PostgreSQL does not support input of generic records.
type Record struct {
Fields []Value
Status Status
}
func (dst *Record) Set(src interface{}) error {
if src == nil {
*dst = Record{Status: Null}
return nil
}
if value, ok := src.(interface{ Get() interface{} }); ok {
value2 := value.Get()
if value2 != value {
return dst.Set(value2)
}
}
switch value := src.(type) {
case []Value:
*dst = Record{Fields: value, Status: Present}
default:
return errors.Errorf("cannot convert %v to Record", src)
}
return nil
}
func (dst Record) Get() interface{} {
switch dst.Status {
case Present:
return dst.Fields
case Null:
return nil
default:
return dst.Status
}
}
func (src *Record) AssignTo(dst interface{}) error {
switch src.Status {
case Present:
switch v := dst.(type) {
case *[]Value:
*v = make([]Value, len(src.Fields))
copy(*v, src.Fields)
return nil
case *[]interface{}:
*v = make([]interface{}, len(src.Fields))
for i := range *v {
(*v)[i] = src.Fields[i].Get()
}
return nil
default:
if nextDst, retry := GetAssignToDstType(dst); retry {
return src.AssignTo(nextDst)
}
return errors.Errorf("unable to assign to %T", dst)
}
case Null:
return NullAssignTo(dst)
}
return errors.Errorf("cannot decode %#v into %T", src, dst)
}
type fieldIter struct {
rp int
fieldCount int
src []byte
}
func newFieldIterator(src []byte) (fieldIter, error) {
rp := 0
if len(src[rp:]) < 4 {
return fieldIter{}, errors.Errorf("Record incomplete %v", src)
}
fieldCount := int(int32(binary.BigEndian.Uint32(src[rp:])))
rp += 4
return fieldIter{
rp: rp,
fieldCount: fieldCount,
src: src,
}, nil
}
func (fi *fieldIter) next() (fieldOID uint32, buf []byte, eof bool, err error) {
if fi.rp == len(fi.src) {
eof = true
return
}
if len(fi.src[fi.rp:]) < 8 {
err = errors.Errorf("Record incomplete %v", fi.src)
return
}
fieldOID = binary.BigEndian.Uint32(fi.src[fi.rp:])
fi.rp += 4
fieldLen := int(int32(binary.BigEndian.Uint32(fi.src[fi.rp:])))
fi.rp += 4
if fieldLen >= 0 {
if len(fi.src[fi.rp:]) < fieldLen {
err = errors.Errorf("Record incomplete rp=%d src=%v", fi.rp, fi.src)
return
}
buf = fi.src[fi.rp : fi.rp+fieldLen]
fi.rp += fieldLen
}
return
}
func prepareNewBinaryDecoder(ci *ConnInfo, fieldOID uint32, v *Value) (BinaryDecoder, error) {
var binaryDecoder BinaryDecoder
if dt, ok := ci.DataTypeForOID(fieldOID); ok {
binaryDecoder, _ = dt.Value.(BinaryDecoder)
} else {
return nil, errors.Errorf("unknown oid while decoding record: %v", fieldOID)
}
if binaryDecoder == nil {
return nil, errors.Errorf("no binary decoder registered for: %v", fieldOID)
}
// Duplicate struct to scan into
binaryDecoder = reflect.New(reflect.ValueOf(binaryDecoder).Elem().Type()).Interface().(BinaryDecoder)
*v = binaryDecoder.(Value)
return binaryDecoder, nil
}
func (dst *Record) DecodeBinary(ci *ConnInfo, src []byte) error {
if src == nil {
*dst = Record{Status: Null}
return nil
}
fieldIter, err := newFieldIterator(src)
if err != nil {
return err
}
fields := make([]Value, fieldIter.fieldCount)
fieldOID, fieldBytes, eof, err := fieldIter.next()
for i := 0; !eof; i++ {
if err != nil {
return err
}
binaryDecoder, err := prepareNewBinaryDecoder(ci, fieldOID, &fields[i])
if err != nil {
return err
}
if err = binaryDecoder.DecodeBinary(ci, fieldBytes); err != nil {
return err
}
fieldOID, fieldBytes, eof, err = fieldIter.next()
}
*dst = Record{Fields: fields, Status: Present}
return nil
}