mirror of https://github.com/jackc/pgx.git
Add float4, float8 and arrays
parent
3179e2debc
commit
2010bea555
4
conn.go
4
conn.go
|
@ -283,6 +283,10 @@ func (c *Conn) connect(config ConnConfig, network, address string, tlsConfig *tl
|
|||
BoolOID: &pgtype.Bool{},
|
||||
DateArrayOID: &pgtype.DateArray{},
|
||||
DateOID: &pgtype.Date{},
|
||||
Float4ArrayOID: &pgtype.Float4Array{},
|
||||
Float4OID: &pgtype.Float4{},
|
||||
Float8ArrayOID: &pgtype.Float8Array{},
|
||||
Float8OID: &pgtype.Float8{},
|
||||
Int2ArrayOID: &pgtype.Int2Array{},
|
||||
Int2OID: &pgtype.Int2{},
|
||||
Int4ArrayOID: &pgtype.Int4Array{},
|
||||
|
|
|
@ -11,8 +11,8 @@ const maxUint = ^uint(0)
|
|||
const maxInt = int(maxUint >> 1)
|
||||
const minInt = -maxInt - 1
|
||||
|
||||
// underlyingIntType gets the underlying type that can be converted to Int2, Int4, or Int8
|
||||
func underlyingIntType(val interface{}) (interface{}, bool) {
|
||||
// underlyingNumberType gets the underlying type that can be converted to Int2, Int4, Int8, Float4, or Float8
|
||||
func underlyingNumberType(val interface{}) (interface{}, bool) {
|
||||
refVal := reflect.ValueOf(val)
|
||||
|
||||
switch refVal.Kind() {
|
||||
|
@ -52,6 +52,12 @@ func underlyingIntType(val interface{}) (interface{}, bool) {
|
|||
case reflect.Uint64:
|
||||
convVal := uint64(refVal.Uint())
|
||||
return convVal, reflect.TypeOf(convVal) != refVal.Type()
|
||||
case reflect.Float32:
|
||||
convVal := float32(refVal.Float())
|
||||
return convVal, reflect.TypeOf(convVal) != refVal.Type()
|
||||
case reflect.Float64:
|
||||
convVal := refVal.Float()
|
||||
return convVal, reflect.TypeOf(convVal) != refVal.Type()
|
||||
case reflect.String:
|
||||
convVal := refVal.String()
|
||||
return convVal, reflect.TypeOf(convVal) != refVal.Type()
|
||||
|
@ -259,3 +265,45 @@ func int64AssignTo(srcVal int64, srcStatus Status, dst interface{}) error {
|
|||
|
||||
return fmt.Errorf("cannot assign %v %v into %T", srcVal, srcStatus, dst)
|
||||
}
|
||||
|
||||
func float64AssignTo(srcVal float64, srcStatus Status, dst interface{}) error {
|
||||
if srcStatus == Present {
|
||||
switch v := dst.(type) {
|
||||
case *float32:
|
||||
*v = float32(srcVal)
|
||||
case *float64:
|
||||
*v = srcVal
|
||||
default:
|
||||
if v := reflect.ValueOf(dst); v.Kind() == reflect.Ptr {
|
||||
el := v.Elem()
|
||||
switch el.Kind() {
|
||||
// if dst is a pointer to pointer, strip the pointer and try again
|
||||
case reflect.Ptr:
|
||||
if el.IsNil() {
|
||||
// allocate destination
|
||||
el.Set(reflect.New(el.Type().Elem()))
|
||||
}
|
||||
return float64AssignTo(srcVal, srcStatus, el.Interface())
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
i64 := int64(srcVal)
|
||||
if float64(i64) == srcVal {
|
||||
return int64AssignTo(i64, srcStatus, dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("cannot assign %v into %T", srcVal, dst)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// if dst is a pointer to pointer and srcStatus is not Present, nil it out
|
||||
if v := reflect.ValueOf(dst); v.Kind() == reflect.Ptr {
|
||||
el := v.Elem()
|
||||
if el.Kind() == reflect.Ptr {
|
||||
el.Set(reflect.Zero(el.Type()))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot assign %v %v into %T", srcVal, srcStatus, dst)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,171 @@
|
|||
package pgtype
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"github.com/jackc/pgx/pgio"
|
||||
)
|
||||
|
||||
type Float4 struct {
|
||||
Float float32
|
||||
Status Status
|
||||
}
|
||||
|
||||
func (dst *Float4) ConvertFrom(src interface{}) error {
|
||||
switch value := src.(type) {
|
||||
case Float4:
|
||||
*dst = value
|
||||
case float32:
|
||||
*dst = Float4{Float: value, Status: Present}
|
||||
case float64:
|
||||
*dst = Float4{Float: float32(value), Status: Present}
|
||||
case int8:
|
||||
*dst = Float4{Float: float32(value), Status: Present}
|
||||
case uint8:
|
||||
*dst = Float4{Float: float32(value), Status: Present}
|
||||
case int16:
|
||||
*dst = Float4{Float: float32(value), Status: Present}
|
||||
case uint16:
|
||||
*dst = Float4{Float: float32(value), Status: Present}
|
||||
case int32:
|
||||
f32 := float32(value)
|
||||
if int32(f32) == value {
|
||||
*dst = Float4{Float: f32, Status: Present}
|
||||
} else {
|
||||
return fmt.Errorf("%v cannot be exactly represented as float32", value)
|
||||
}
|
||||
case uint32:
|
||||
f32 := float32(value)
|
||||
if uint32(f32) == value {
|
||||
*dst = Float4{Float: f32, Status: Present}
|
||||
} else {
|
||||
return fmt.Errorf("%v cannot be exactly represented as float32", value)
|
||||
}
|
||||
case int64:
|
||||
f32 := float32(value)
|
||||
if int64(f32) == value {
|
||||
*dst = Float4{Float: f32, Status: Present}
|
||||
} else {
|
||||
return fmt.Errorf("%v cannot be exactly represented as float32", value)
|
||||
}
|
||||
case uint64:
|
||||
f32 := float32(value)
|
||||
if uint64(f32) == value {
|
||||
*dst = Float4{Float: f32, Status: Present}
|
||||
} else {
|
||||
return fmt.Errorf("%v cannot be exactly represented as float32", value)
|
||||
}
|
||||
case int:
|
||||
f32 := float32(value)
|
||||
if int(f32) == value {
|
||||
*dst = Float4{Float: f32, Status: Present}
|
||||
} else {
|
||||
return fmt.Errorf("%v cannot be exactly represented as float32", value)
|
||||
}
|
||||
case uint:
|
||||
f32 := float32(value)
|
||||
if uint(f32) == value {
|
||||
*dst = Float4{Float: f32, Status: Present}
|
||||
} else {
|
||||
return fmt.Errorf("%v cannot be exactly represented as float32", value)
|
||||
}
|
||||
case string:
|
||||
num, err := strconv.ParseFloat(value, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*dst = Float4{Float: float32(num), Status: Present}
|
||||
default:
|
||||
if originalSrc, ok := underlyingNumberType(src); ok {
|
||||
return dst.ConvertFrom(originalSrc)
|
||||
}
|
||||
return fmt.Errorf("cannot convert %v to Float8", value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src *Float4) AssignTo(dst interface{}) error {
|
||||
return float64AssignTo(float64(src.Float), src.Status, dst)
|
||||
}
|
||||
|
||||
func (dst *Float4) DecodeText(r io.Reader) error {
|
||||
size, err := pgio.ReadInt32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if size == -1 {
|
||||
*dst = Float4{Status: Null}
|
||||
return nil
|
||||
}
|
||||
|
||||
buf := make([]byte, int(size))
|
||||
_, err = r.Read(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n, err := strconv.ParseFloat(string(buf), 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*dst = Float4{Float: float32(n), Status: Present}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *Float4) DecodeBinary(r io.Reader) error {
|
||||
size, err := pgio.ReadInt32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if size == -1 {
|
||||
*dst = Float4{Status: Null}
|
||||
return nil
|
||||
}
|
||||
|
||||
if size != 4 {
|
||||
return fmt.Errorf("invalid length for float4: %v", size)
|
||||
}
|
||||
|
||||
n, err := pgio.ReadInt32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*dst = Float4{Float: math.Float32frombits(uint32(n)), Status: Present}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src Float4) EncodeText(w io.Writer) error {
|
||||
if done, err := encodeNotPresent(w, src.Status); done {
|
||||
return err
|
||||
}
|
||||
|
||||
s := strconv.FormatFloat(float64(src.Float), 'f', -1, 32)
|
||||
_, err := pgio.WriteInt32(w, int32(len(s)))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
_, err = w.Write([]byte(s))
|
||||
return err
|
||||
}
|
||||
|
||||
func (src Float4) EncodeBinary(w io.Writer) error {
|
||||
if done, err := encodeNotPresent(w, src.Status); done {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := pgio.WriteInt32(w, 4)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = pgio.WriteInt32(w, int32(math.Float32bits(src.Float)))
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
package pgtype_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgx/pgtype"
|
||||
)
|
||||
|
||||
func TestFloat4Transcode(t *testing.T) {
|
||||
testSuccessfulTranscode(t, "float4", []interface{}{
|
||||
pgtype.Float4{Float: -1, Status: pgtype.Present},
|
||||
pgtype.Float4{Float: 0, Status: pgtype.Present},
|
||||
pgtype.Float4{Float: 0.00001, Status: pgtype.Present},
|
||||
pgtype.Float4{Float: 1, Status: pgtype.Present},
|
||||
pgtype.Float4{Float: 9999.99, Status: pgtype.Present},
|
||||
pgtype.Float4{Float: 0, Status: pgtype.Null},
|
||||
})
|
||||
}
|
||||
|
||||
func TestFloat4ConvertFrom(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source interface{}
|
||||
result pgtype.Float4
|
||||
}{
|
||||
{source: float32(1), result: pgtype.Float4{Float: 1, Status: pgtype.Present}},
|
||||
{source: float64(1), result: pgtype.Float4{Float: 1, Status: pgtype.Present}},
|
||||
{source: int8(1), result: pgtype.Float4{Float: 1, Status: pgtype.Present}},
|
||||
{source: int16(1), result: pgtype.Float4{Float: 1, Status: pgtype.Present}},
|
||||
{source: int32(1), result: pgtype.Float4{Float: 1, Status: pgtype.Present}},
|
||||
{source: int64(1), result: pgtype.Float4{Float: 1, Status: pgtype.Present}},
|
||||
{source: int8(-1), result: pgtype.Float4{Float: -1, Status: pgtype.Present}},
|
||||
{source: int16(-1), result: pgtype.Float4{Float: -1, Status: pgtype.Present}},
|
||||
{source: int32(-1), result: pgtype.Float4{Float: -1, Status: pgtype.Present}},
|
||||
{source: int64(-1), result: pgtype.Float4{Float: -1, Status: pgtype.Present}},
|
||||
{source: uint8(1), result: pgtype.Float4{Float: 1, Status: pgtype.Present}},
|
||||
{source: uint16(1), result: pgtype.Float4{Float: 1, Status: pgtype.Present}},
|
||||
{source: uint32(1), result: pgtype.Float4{Float: 1, Status: pgtype.Present}},
|
||||
{source: uint64(1), result: pgtype.Float4{Float: 1, Status: pgtype.Present}},
|
||||
{source: "1", result: pgtype.Float4{Float: 1, Status: pgtype.Present}},
|
||||
{source: _int8(1), result: pgtype.Float4{Float: 1, Status: pgtype.Present}},
|
||||
}
|
||||
|
||||
for i, tt := range successfulTests {
|
||||
var r pgtype.Float4
|
||||
err := r.ConvertFrom(tt.source)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if r != tt.result {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFloat4AssignTo(t *testing.T) {
|
||||
var i8 int8
|
||||
var i16 int16
|
||||
var i32 int32
|
||||
var i64 int64
|
||||
var i int
|
||||
var ui8 uint8
|
||||
var ui16 uint16
|
||||
var ui32 uint32
|
||||
var ui64 uint64
|
||||
var ui uint
|
||||
var pi8 *int8
|
||||
var _i8 _int8
|
||||
var _pi8 *_int8
|
||||
var f32 float32
|
||||
var f64 float64
|
||||
var pf32 *float32
|
||||
var pf64 *float64
|
||||
|
||||
simpleTests := []struct {
|
||||
src pgtype.Float4
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{src: pgtype.Float4{Float: 42, Status: pgtype.Present}, dst: &f32, expected: float32(42)},
|
||||
{src: pgtype.Float4{Float: 42, Status: pgtype.Present}, dst: &f64, expected: float64(42)},
|
||||
{src: pgtype.Float4{Float: 42, Status: pgtype.Present}, dst: &i16, expected: int16(42)},
|
||||
{src: pgtype.Float4{Float: 42, Status: pgtype.Present}, dst: &i32, expected: int32(42)},
|
||||
{src: pgtype.Float4{Float: 42, Status: pgtype.Present}, dst: &i64, expected: int64(42)},
|
||||
{src: pgtype.Float4{Float: 42, Status: pgtype.Present}, dst: &i, expected: int(42)},
|
||||
{src: pgtype.Float4{Float: 42, Status: pgtype.Present}, dst: &ui8, expected: uint8(42)},
|
||||
{src: pgtype.Float4{Float: 42, Status: pgtype.Present}, dst: &ui16, expected: uint16(42)},
|
||||
{src: pgtype.Float4{Float: 42, Status: pgtype.Present}, dst: &ui32, expected: uint32(42)},
|
||||
{src: pgtype.Float4{Float: 42, Status: pgtype.Present}, dst: &ui64, expected: uint64(42)},
|
||||
{src: pgtype.Float4{Float: 42, Status: pgtype.Present}, dst: &ui, expected: uint(42)},
|
||||
{src: pgtype.Float4{Float: 42, Status: pgtype.Present}, dst: &_i8, expected: _int8(42)},
|
||||
{src: pgtype.Float4{Float: 0, Status: pgtype.Null}, dst: &pi8, expected: ((*int8)(nil))},
|
||||
{src: pgtype.Float4{Float: 0, Status: pgtype.Null}, dst: &_pi8, expected: ((*_int8)(nil))},
|
||||
}
|
||||
|
||||
for i, tt := range simpleTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Interface(); dst != tt.expected {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
pointerAllocTests := []struct {
|
||||
src pgtype.Float4
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{src: pgtype.Float4{Float: 42, Status: pgtype.Present}, dst: &pf32, expected: float32(42)},
|
||||
{src: pgtype.Float4{Float: 42, Status: pgtype.Present}, dst: &pf64, expected: float64(42)},
|
||||
}
|
||||
|
||||
for i, tt := range pointerAllocTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Elem().Interface(); dst != tt.expected {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
errorTests := []struct {
|
||||
src pgtype.Float4
|
||||
dst interface{}
|
||||
}{
|
||||
{src: pgtype.Float4{Float: 150, Status: pgtype.Present}, dst: &i8},
|
||||
{src: pgtype.Float4{Float: 40000, Status: pgtype.Present}, dst: &i16},
|
||||
{src: pgtype.Float4{Float: -1, Status: pgtype.Present}, dst: &ui8},
|
||||
{src: pgtype.Float4{Float: -1, Status: pgtype.Present}, dst: &ui16},
|
||||
{src: pgtype.Float4{Float: -1, Status: pgtype.Present}, dst: &ui32},
|
||||
{src: pgtype.Float4{Float: -1, Status: pgtype.Present}, dst: &ui64},
|
||||
{src: pgtype.Float4{Float: -1, Status: pgtype.Present}, dst: &ui},
|
||||
{src: pgtype.Float4{Float: 0, Status: pgtype.Null}, dst: &i32},
|
||||
}
|
||||
|
||||
for i, tt := range errorTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err == nil {
|
||||
t.Errorf("%d: expected error but none was returned (%v -> %v)", i, tt.src, tt.dst)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,286 @@
|
|||
package pgtype
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/jackc/pgx/pgio"
|
||||
)
|
||||
|
||||
type Float4Array struct {
|
||||
Elements []Float4
|
||||
Dimensions []ArrayDimension
|
||||
Status Status
|
||||
}
|
||||
|
||||
func (dst *Float4Array) ConvertFrom(src interface{}) error {
|
||||
switch value := src.(type) {
|
||||
case Float4Array:
|
||||
*dst = value
|
||||
|
||||
case []float32:
|
||||
if value == nil {
|
||||
*dst = Float4Array{Status: Null}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Float4Array{Status: Present}
|
||||
} else {
|
||||
elements := make([]Float4, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].ConvertFrom(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Float4Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Status: Present,
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
if originalSrc, ok := underlyingSliceType(src); ok {
|
||||
return dst.ConvertFrom(originalSrc)
|
||||
}
|
||||
return fmt.Errorf("cannot convert %v to Float4", value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src *Float4Array) AssignTo(dst interface{}) error {
|
||||
switch v := dst.(type) {
|
||||
|
||||
case *[]float32:
|
||||
if src.Status == Present {
|
||||
*v = make([]float32, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
*v = nil
|
||||
}
|
||||
|
||||
default:
|
||||
if originalDst, ok := underlyingPtrSliceType(dst); ok {
|
||||
return src.AssignTo(originalDst)
|
||||
}
|
||||
return fmt.Errorf("cannot put decode %v into %T", src, dst)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *Float4Array) DecodeText(r io.Reader) error {
|
||||
size, err := pgio.ReadInt32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if size == -1 {
|
||||
*dst = Float4Array{Status: Null}
|
||||
return nil
|
||||
}
|
||||
|
||||
buf := make([]byte, int(size))
|
||||
_, err = io.ReadFull(r, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
uta, err := ParseUntypedTextArray(string(buf))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
textElementReader := NewTextElementReader(r)
|
||||
var elements []Float4
|
||||
|
||||
if len(uta.Elements) > 0 {
|
||||
elements = make([]Float4, len(uta.Elements))
|
||||
|
||||
for i, s := range uta.Elements {
|
||||
var elem Float4
|
||||
textElementReader.Reset(s)
|
||||
err = elem.DecodeText(textElementReader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
elements[i] = elem
|
||||
}
|
||||
}
|
||||
|
||||
*dst = Float4Array{Elements: elements, Dimensions: uta.Dimensions, Status: Present}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *Float4Array) DecodeBinary(r io.Reader) error {
|
||||
size, err := pgio.ReadInt32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if size == -1 {
|
||||
*dst = Float4Array{Status: Null}
|
||||
return nil
|
||||
}
|
||||
|
||||
var arrayHeader ArrayHeader
|
||||
err = arrayHeader.DecodeBinary(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(arrayHeader.Dimensions) == 0 {
|
||||
*dst = Float4Array{Dimensions: arrayHeader.Dimensions, Status: Present}
|
||||
return nil
|
||||
}
|
||||
|
||||
elementCount := arrayHeader.Dimensions[0].Length
|
||||
for _, d := range arrayHeader.Dimensions[1:] {
|
||||
elementCount *= d.Length
|
||||
}
|
||||
|
||||
elements := make([]Float4, elementCount)
|
||||
|
||||
for i := range elements {
|
||||
err = elements[i].DecodeBinary(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
*dst = Float4Array{Elements: elements, Dimensions: arrayHeader.Dimensions, Status: Present}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src *Float4Array) EncodeText(w io.Writer) error {
|
||||
if done, err := encodeNotPresent(w, src.Status); done {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(src.Dimensions) == 0 {
|
||||
_, err := pgio.WriteInt32(w, 2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.Write([]byte("{}"))
|
||||
return err
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
err := EncodeTextArrayDimensions(buf, src.Dimensions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// dimElemCounts is the multiples of elements that each array lies on. For
|
||||
// example, a single dimension array of length 4 would have a dimElemCounts of
|
||||
// [4]. A multi-dimensional array of lengths [3,5,2] would have a
|
||||
// dimElemCounts of [30,10,2]. This is used to simplify when to render a '{'
|
||||
// or '}'.
|
||||
dimElemCounts := make([]int, len(src.Dimensions))
|
||||
dimElemCounts[len(src.Dimensions)-1] = int(src.Dimensions[len(src.Dimensions)-1].Length)
|
||||
for i := len(src.Dimensions) - 2; i > -1; i-- {
|
||||
dimElemCounts[i] = int(src.Dimensions[i].Length) * dimElemCounts[i+1]
|
||||
}
|
||||
|
||||
textElementWriter := NewTextElementWriter(buf)
|
||||
|
||||
for i, elem := range src.Elements {
|
||||
if i > 0 {
|
||||
err = pgio.WriteByte(buf, ',')
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, dec := range dimElemCounts {
|
||||
if i%dec == 0 {
|
||||
err = pgio.WriteByte(buf, '{')
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
textElementWriter.Reset()
|
||||
err = elem.EncodeText(textElementWriter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, dec := range dimElemCounts {
|
||||
if (i+1)%dec == 0 {
|
||||
err = pgio.WriteByte(buf, '}')
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, err = pgio.WriteInt32(w, int32(buf.Len()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = buf.WriteTo(w)
|
||||
return err
|
||||
}
|
||||
|
||||
func (src *Float4Array) EncodeBinary(w io.Writer) error {
|
||||
if done, err := encodeNotPresent(w, src.Status); done {
|
||||
return err
|
||||
}
|
||||
|
||||
var arrayHeader ArrayHeader
|
||||
|
||||
// TODO - consider how to avoid having to buffer array before writing length -
|
||||
// or how not pay allocations for the byte order conversions.
|
||||
elemBuf := &bytes.Buffer{}
|
||||
|
||||
for i := range src.Elements {
|
||||
err := src.Elements[i].EncodeBinary(elemBuf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if src.Elements[i].Status == Null {
|
||||
arrayHeader.ContainsNull = true
|
||||
}
|
||||
}
|
||||
|
||||
arrayHeader.ElementOID = Float4OID
|
||||
arrayHeader.Dimensions = src.Dimensions
|
||||
|
||||
// TODO - consider how to avoid having to buffer array before writing length -
|
||||
// or how not pay allocations for the byte order conversions.
|
||||
headerBuf := &bytes.Buffer{}
|
||||
err := arrayHeader.EncodeBinary(headerBuf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = pgio.WriteInt32(w, int32(headerBuf.Len()+elemBuf.Len()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = headerBuf.WriteTo(w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = elemBuf.WriteTo(w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
package pgtype_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgx/pgtype"
|
||||
)
|
||||
|
||||
func TestFloat4ArrayTranscode(t *testing.T) {
|
||||
testSuccessfulTranscode(t, "float4[]", []interface{}{
|
||||
&pgtype.Float4Array{
|
||||
Elements: nil,
|
||||
Dimensions: nil,
|
||||
Status: pgtype.Present,
|
||||
},
|
||||
&pgtype.Float4Array{
|
||||
Elements: []pgtype.Float4{
|
||||
pgtype.Float4{Float: 1, Status: pgtype.Present},
|
||||
pgtype.Float4{Status: pgtype.Null},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}},
|
||||
Status: pgtype.Present,
|
||||
},
|
||||
&pgtype.Float4Array{Status: pgtype.Null},
|
||||
&pgtype.Float4Array{
|
||||
Elements: []pgtype.Float4{
|
||||
pgtype.Float4{Float: 1, Status: pgtype.Present},
|
||||
pgtype.Float4{Float: 2, Status: pgtype.Present},
|
||||
pgtype.Float4{Float: 3, Status: pgtype.Present},
|
||||
pgtype.Float4{Float: 4, Status: pgtype.Present},
|
||||
pgtype.Float4{Status: pgtype.Null},
|
||||
pgtype.Float4{Float: 6, Status: pgtype.Present},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 3, LowerBound: 1}, {Length: 2, LowerBound: 1}},
|
||||
Status: pgtype.Present,
|
||||
},
|
||||
&pgtype.Float4Array{
|
||||
Elements: []pgtype.Float4{
|
||||
pgtype.Float4{Float: 1, Status: pgtype.Present},
|
||||
pgtype.Float4{Float: 2, Status: pgtype.Present},
|
||||
pgtype.Float4{Float: 3, Status: pgtype.Present},
|
||||
pgtype.Float4{Float: 4, Status: pgtype.Present},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{Length: 2, LowerBound: 4},
|
||||
{Length: 2, LowerBound: 2},
|
||||
},
|
||||
Status: pgtype.Present,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestFloat4ArrayConvertFrom(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source interface{}
|
||||
result pgtype.Float4Array
|
||||
}{
|
||||
{
|
||||
source: []float32{1},
|
||||
result: pgtype.Float4Array{
|
||||
Elements: []pgtype.Float4{{Float: 1, Status: pgtype.Present}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Status: pgtype.Present},
|
||||
},
|
||||
{
|
||||
source: (([]float32)(nil)),
|
||||
result: pgtype.Float4Array{Status: pgtype.Null},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range successfulTests {
|
||||
var r pgtype.Float4Array
|
||||
err := r.ConvertFrom(tt.source)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(r, tt.result) {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFloat4ArrayAssignTo(t *testing.T) {
|
||||
var float32Slice []float32
|
||||
var namedFloat32Slice _float32Slice
|
||||
|
||||
simpleTests := []struct {
|
||||
src pgtype.Float4Array
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
src: pgtype.Float4Array{
|
||||
Elements: []pgtype.Float4{{Float: 1.23, Status: pgtype.Present}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Status: pgtype.Present,
|
||||
},
|
||||
dst: &float32Slice,
|
||||
expected: []float32{1.23},
|
||||
},
|
||||
{
|
||||
src: pgtype.Float4Array{
|
||||
Elements: []pgtype.Float4{{Float: 1.23, Status: pgtype.Present}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Status: pgtype.Present,
|
||||
},
|
||||
dst: &namedFloat32Slice,
|
||||
expected: _float32Slice{1.23},
|
||||
},
|
||||
{
|
||||
src: pgtype.Float4Array{Status: pgtype.Null},
|
||||
dst: &float32Slice,
|
||||
expected: (([]float32)(nil)),
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range simpleTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Interface(); !reflect.DeepEqual(dst, tt.expected) {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
errorTests := []struct {
|
||||
src pgtype.Float4Array
|
||||
dst interface{}
|
||||
}{
|
||||
{
|
||||
src: pgtype.Float4Array{
|
||||
Elements: []pgtype.Float4{{Status: pgtype.Null}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Status: pgtype.Present,
|
||||
},
|
||||
dst: &float32Slice,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range errorTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err == nil {
|
||||
t.Errorf("%d: expected error but none was returned (%v -> %v)", i, tt.src, tt.dst)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
package pgtype
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"github.com/jackc/pgx/pgio"
|
||||
)
|
||||
|
||||
type Float8 struct {
|
||||
Float float64
|
||||
Status Status
|
||||
}
|
||||
|
||||
func (dst *Float8) ConvertFrom(src interface{}) error {
|
||||
switch value := src.(type) {
|
||||
case Float8:
|
||||
*dst = value
|
||||
case float32:
|
||||
*dst = Float8{Float: float64(value), Status: Present}
|
||||
case float64:
|
||||
*dst = Float8{Float: value, Status: Present}
|
||||
case int8:
|
||||
*dst = Float8{Float: float64(value), Status: Present}
|
||||
case uint8:
|
||||
*dst = Float8{Float: float64(value), Status: Present}
|
||||
case int16:
|
||||
*dst = Float8{Float: float64(value), Status: Present}
|
||||
case uint16:
|
||||
*dst = Float8{Float: float64(value), Status: Present}
|
||||
case int32:
|
||||
*dst = Float8{Float: float64(value), Status: Present}
|
||||
case uint32:
|
||||
*dst = Float8{Float: float64(value), Status: Present}
|
||||
case int64:
|
||||
f64 := float64(value)
|
||||
if int64(f64) == value {
|
||||
*dst = Float8{Float: f64, Status: Present}
|
||||
} else {
|
||||
return fmt.Errorf("%v cannot be exactly represented as float64", value)
|
||||
}
|
||||
case uint64:
|
||||
f64 := float64(value)
|
||||
if uint64(f64) == value {
|
||||
*dst = Float8{Float: f64, Status: Present}
|
||||
} else {
|
||||
return fmt.Errorf("%v cannot be exactly represented as float64", value)
|
||||
}
|
||||
case int:
|
||||
f64 := float64(value)
|
||||
if int(f64) == value {
|
||||
*dst = Float8{Float: f64, Status: Present}
|
||||
} else {
|
||||
return fmt.Errorf("%v cannot be exactly represented as float64", value)
|
||||
}
|
||||
case uint:
|
||||
f64 := float64(value)
|
||||
if uint(f64) == value {
|
||||
*dst = Float8{Float: f64, Status: Present}
|
||||
} else {
|
||||
return fmt.Errorf("%v cannot be exactly represented as float64", value)
|
||||
}
|
||||
case string:
|
||||
num, err := strconv.ParseFloat(value, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*dst = Float8{Float: float64(num), Status: Present}
|
||||
default:
|
||||
if originalSrc, ok := underlyingNumberType(src); ok {
|
||||
return dst.ConvertFrom(originalSrc)
|
||||
}
|
||||
return fmt.Errorf("cannot convert %v to Float8", value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src *Float8) AssignTo(dst interface{}) error {
|
||||
return float64AssignTo(src.Float, src.Status, dst)
|
||||
}
|
||||
|
||||
func (dst *Float8) DecodeText(r io.Reader) error {
|
||||
size, err := pgio.ReadInt32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if size == -1 {
|
||||
*dst = Float8{Status: Null}
|
||||
return nil
|
||||
}
|
||||
|
||||
buf := make([]byte, int(size))
|
||||
_, err = r.Read(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n, err := strconv.ParseFloat(string(buf), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*dst = Float8{Float: n, Status: Present}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *Float8) DecodeBinary(r io.Reader) error {
|
||||
size, err := pgio.ReadInt32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if size == -1 {
|
||||
*dst = Float8{Status: Null}
|
||||
return nil
|
||||
}
|
||||
|
||||
if size != 8 {
|
||||
return fmt.Errorf("invalid length for float4: %v", size)
|
||||
}
|
||||
|
||||
n, err := pgio.ReadInt64(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*dst = Float8{Float: math.Float64frombits(uint64(n)), Status: Present}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src Float8) EncodeText(w io.Writer) error {
|
||||
if done, err := encodeNotPresent(w, src.Status); done {
|
||||
return err
|
||||
}
|
||||
|
||||
s := strconv.FormatFloat(float64(src.Float), 'f', -1, 64)
|
||||
_, err := pgio.WriteInt32(w, int32(len(s)))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
_, err = w.Write([]byte(s))
|
||||
return err
|
||||
}
|
||||
|
||||
func (src Float8) EncodeBinary(w io.Writer) error {
|
||||
if done, err := encodeNotPresent(w, src.Status); done {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := pgio.WriteInt32(w, 8)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = pgio.WriteInt64(w, int64(math.Float64bits(src.Float)))
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
package pgtype_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgx/pgtype"
|
||||
)
|
||||
|
||||
func TestFloat8Transcode(t *testing.T) {
|
||||
testSuccessfulTranscode(t, "float8", []interface{}{
|
||||
pgtype.Float8{Float: -1, Status: pgtype.Present},
|
||||
pgtype.Float8{Float: 0, Status: pgtype.Present},
|
||||
pgtype.Float8{Float: 0.00001, Status: pgtype.Present},
|
||||
pgtype.Float8{Float: 1, Status: pgtype.Present},
|
||||
pgtype.Float8{Float: 9999.99, Status: pgtype.Present},
|
||||
pgtype.Float8{Float: 0, Status: pgtype.Null},
|
||||
})
|
||||
}
|
||||
|
||||
func TestFloat8ConvertFrom(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source interface{}
|
||||
result pgtype.Float8
|
||||
}{
|
||||
{source: float32(1), result: pgtype.Float8{Float: 1, Status: pgtype.Present}},
|
||||
{source: float64(1), result: pgtype.Float8{Float: 1, Status: pgtype.Present}},
|
||||
{source: int8(1), result: pgtype.Float8{Float: 1, Status: pgtype.Present}},
|
||||
{source: int16(1), result: pgtype.Float8{Float: 1, Status: pgtype.Present}},
|
||||
{source: int32(1), result: pgtype.Float8{Float: 1, Status: pgtype.Present}},
|
||||
{source: int64(1), result: pgtype.Float8{Float: 1, Status: pgtype.Present}},
|
||||
{source: int8(-1), result: pgtype.Float8{Float: -1, Status: pgtype.Present}},
|
||||
{source: int16(-1), result: pgtype.Float8{Float: -1, Status: pgtype.Present}},
|
||||
{source: int32(-1), result: pgtype.Float8{Float: -1, Status: pgtype.Present}},
|
||||
{source: int64(-1), result: pgtype.Float8{Float: -1, Status: pgtype.Present}},
|
||||
{source: uint8(1), result: pgtype.Float8{Float: 1, Status: pgtype.Present}},
|
||||
{source: uint16(1), result: pgtype.Float8{Float: 1, Status: pgtype.Present}},
|
||||
{source: uint32(1), result: pgtype.Float8{Float: 1, Status: pgtype.Present}},
|
||||
{source: uint64(1), result: pgtype.Float8{Float: 1, Status: pgtype.Present}},
|
||||
{source: "1", result: pgtype.Float8{Float: 1, Status: pgtype.Present}},
|
||||
{source: _int8(1), result: pgtype.Float8{Float: 1, Status: pgtype.Present}},
|
||||
}
|
||||
|
||||
for i, tt := range successfulTests {
|
||||
var r pgtype.Float8
|
||||
err := r.ConvertFrom(tt.source)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if r != tt.result {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFloat8AssignTo(t *testing.T) {
|
||||
var i8 int8
|
||||
var i16 int16
|
||||
var i32 int32
|
||||
var i64 int64
|
||||
var i int
|
||||
var ui8 uint8
|
||||
var ui16 uint16
|
||||
var ui32 uint32
|
||||
var ui64 uint64
|
||||
var ui uint
|
||||
var pi8 *int8
|
||||
var _i8 _int8
|
||||
var _pi8 *_int8
|
||||
var f32 float32
|
||||
var f64 float64
|
||||
var pf32 *float32
|
||||
var pf64 *float64
|
||||
|
||||
simpleTests := []struct {
|
||||
src pgtype.Float8
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{src: pgtype.Float8{Float: 42, Status: pgtype.Present}, dst: &f32, expected: float32(42)},
|
||||
{src: pgtype.Float8{Float: 42, Status: pgtype.Present}, dst: &f64, expected: float64(42)},
|
||||
{src: pgtype.Float8{Float: 42, Status: pgtype.Present}, dst: &i16, expected: int16(42)},
|
||||
{src: pgtype.Float8{Float: 42, Status: pgtype.Present}, dst: &i32, expected: int32(42)},
|
||||
{src: pgtype.Float8{Float: 42, Status: pgtype.Present}, dst: &i64, expected: int64(42)},
|
||||
{src: pgtype.Float8{Float: 42, Status: pgtype.Present}, dst: &i, expected: int(42)},
|
||||
{src: pgtype.Float8{Float: 42, Status: pgtype.Present}, dst: &ui8, expected: uint8(42)},
|
||||
{src: pgtype.Float8{Float: 42, Status: pgtype.Present}, dst: &ui16, expected: uint16(42)},
|
||||
{src: pgtype.Float8{Float: 42, Status: pgtype.Present}, dst: &ui32, expected: uint32(42)},
|
||||
{src: pgtype.Float8{Float: 42, Status: pgtype.Present}, dst: &ui64, expected: uint64(42)},
|
||||
{src: pgtype.Float8{Float: 42, Status: pgtype.Present}, dst: &ui, expected: uint(42)},
|
||||
{src: pgtype.Float8{Float: 42, Status: pgtype.Present}, dst: &_i8, expected: _int8(42)},
|
||||
{src: pgtype.Float8{Float: 0, Status: pgtype.Null}, dst: &pi8, expected: ((*int8)(nil))},
|
||||
{src: pgtype.Float8{Float: 0, Status: pgtype.Null}, dst: &_pi8, expected: ((*_int8)(nil))},
|
||||
}
|
||||
|
||||
for i, tt := range simpleTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Interface(); dst != tt.expected {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
pointerAllocTests := []struct {
|
||||
src pgtype.Float8
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{src: pgtype.Float8{Float: 42, Status: pgtype.Present}, dst: &pf32, expected: float32(42)},
|
||||
{src: pgtype.Float8{Float: 42, Status: pgtype.Present}, dst: &pf64, expected: float64(42)},
|
||||
}
|
||||
|
||||
for i, tt := range pointerAllocTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Elem().Interface(); dst != tt.expected {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
errorTests := []struct {
|
||||
src pgtype.Float8
|
||||
dst interface{}
|
||||
}{
|
||||
{src: pgtype.Float8{Float: 150, Status: pgtype.Present}, dst: &i8},
|
||||
{src: pgtype.Float8{Float: 40000, Status: pgtype.Present}, dst: &i16},
|
||||
{src: pgtype.Float8{Float: -1, Status: pgtype.Present}, dst: &ui8},
|
||||
{src: pgtype.Float8{Float: -1, Status: pgtype.Present}, dst: &ui16},
|
||||
{src: pgtype.Float8{Float: -1, Status: pgtype.Present}, dst: &ui32},
|
||||
{src: pgtype.Float8{Float: -1, Status: pgtype.Present}, dst: &ui64},
|
||||
{src: pgtype.Float8{Float: -1, Status: pgtype.Present}, dst: &ui},
|
||||
{src: pgtype.Float8{Float: 0, Status: pgtype.Null}, dst: &i32},
|
||||
}
|
||||
|
||||
for i, tt := range errorTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err == nil {
|
||||
t.Errorf("%d: expected error but none was returned (%v -> %v)", i, tt.src, tt.dst)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,286 @@
|
|||
package pgtype
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/jackc/pgx/pgio"
|
||||
)
|
||||
|
||||
type Float8Array struct {
|
||||
Elements []Float8
|
||||
Dimensions []ArrayDimension
|
||||
Status Status
|
||||
}
|
||||
|
||||
func (dst *Float8Array) ConvertFrom(src interface{}) error {
|
||||
switch value := src.(type) {
|
||||
case Float8Array:
|
||||
*dst = value
|
||||
|
||||
case []float64:
|
||||
if value == nil {
|
||||
*dst = Float8Array{Status: Null}
|
||||
} else if len(value) == 0 {
|
||||
*dst = Float8Array{Status: Present}
|
||||
} else {
|
||||
elements := make([]Float8, len(value))
|
||||
for i := range value {
|
||||
if err := elements[i].ConvertFrom(value[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*dst = Float8Array{
|
||||
Elements: elements,
|
||||
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
|
||||
Status: Present,
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
if originalSrc, ok := underlyingSliceType(src); ok {
|
||||
return dst.ConvertFrom(originalSrc)
|
||||
}
|
||||
return fmt.Errorf("cannot convert %v to Float8", value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src *Float8Array) AssignTo(dst interface{}) error {
|
||||
switch v := dst.(type) {
|
||||
|
||||
case *[]float64:
|
||||
if src.Status == Present {
|
||||
*v = make([]float64, len(src.Elements))
|
||||
for i := range src.Elements {
|
||||
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
*v = nil
|
||||
}
|
||||
|
||||
default:
|
||||
if originalDst, ok := underlyingPtrSliceType(dst); ok {
|
||||
return src.AssignTo(originalDst)
|
||||
}
|
||||
return fmt.Errorf("cannot put decode %v into %T", src, dst)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *Float8Array) DecodeText(r io.Reader) error {
|
||||
size, err := pgio.ReadInt32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if size == -1 {
|
||||
*dst = Float8Array{Status: Null}
|
||||
return nil
|
||||
}
|
||||
|
||||
buf := make([]byte, int(size))
|
||||
_, err = io.ReadFull(r, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
uta, err := ParseUntypedTextArray(string(buf))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
textElementReader := NewTextElementReader(r)
|
||||
var elements []Float8
|
||||
|
||||
if len(uta.Elements) > 0 {
|
||||
elements = make([]Float8, len(uta.Elements))
|
||||
|
||||
for i, s := range uta.Elements {
|
||||
var elem Float8
|
||||
textElementReader.Reset(s)
|
||||
err = elem.DecodeText(textElementReader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
elements[i] = elem
|
||||
}
|
||||
}
|
||||
|
||||
*dst = Float8Array{Elements: elements, Dimensions: uta.Dimensions, Status: Present}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *Float8Array) DecodeBinary(r io.Reader) error {
|
||||
size, err := pgio.ReadInt32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if size == -1 {
|
||||
*dst = Float8Array{Status: Null}
|
||||
return nil
|
||||
}
|
||||
|
||||
var arrayHeader ArrayHeader
|
||||
err = arrayHeader.DecodeBinary(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(arrayHeader.Dimensions) == 0 {
|
||||
*dst = Float8Array{Dimensions: arrayHeader.Dimensions, Status: Present}
|
||||
return nil
|
||||
}
|
||||
|
||||
elementCount := arrayHeader.Dimensions[0].Length
|
||||
for _, d := range arrayHeader.Dimensions[1:] {
|
||||
elementCount *= d.Length
|
||||
}
|
||||
|
||||
elements := make([]Float8, elementCount)
|
||||
|
||||
for i := range elements {
|
||||
err = elements[i].DecodeBinary(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
*dst = Float8Array{Elements: elements, Dimensions: arrayHeader.Dimensions, Status: Present}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src *Float8Array) EncodeText(w io.Writer) error {
|
||||
if done, err := encodeNotPresent(w, src.Status); done {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(src.Dimensions) == 0 {
|
||||
_, err := pgio.WriteInt32(w, 2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.Write([]byte("{}"))
|
||||
return err
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
err := EncodeTextArrayDimensions(buf, src.Dimensions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// dimElemCounts is the multiples of elements that each array lies on. For
|
||||
// example, a single dimension array of length 4 would have a dimElemCounts of
|
||||
// [4]. A multi-dimensional array of lengths [3,5,2] would have a
|
||||
// dimElemCounts of [30,10,2]. This is used to simplify when to render a '{'
|
||||
// or '}'.
|
||||
dimElemCounts := make([]int, len(src.Dimensions))
|
||||
dimElemCounts[len(src.Dimensions)-1] = int(src.Dimensions[len(src.Dimensions)-1].Length)
|
||||
for i := len(src.Dimensions) - 2; i > -1; i-- {
|
||||
dimElemCounts[i] = int(src.Dimensions[i].Length) * dimElemCounts[i+1]
|
||||
}
|
||||
|
||||
textElementWriter := NewTextElementWriter(buf)
|
||||
|
||||
for i, elem := range src.Elements {
|
||||
if i > 0 {
|
||||
err = pgio.WriteByte(buf, ',')
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, dec := range dimElemCounts {
|
||||
if i%dec == 0 {
|
||||
err = pgio.WriteByte(buf, '{')
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
textElementWriter.Reset()
|
||||
err = elem.EncodeText(textElementWriter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, dec := range dimElemCounts {
|
||||
if (i+1)%dec == 0 {
|
||||
err = pgio.WriteByte(buf, '}')
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, err = pgio.WriteInt32(w, int32(buf.Len()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = buf.WriteTo(w)
|
||||
return err
|
||||
}
|
||||
|
||||
func (src *Float8Array) EncodeBinary(w io.Writer) error {
|
||||
if done, err := encodeNotPresent(w, src.Status); done {
|
||||
return err
|
||||
}
|
||||
|
||||
var arrayHeader ArrayHeader
|
||||
|
||||
// TODO - consider how to avoid having to buffer array before writing length -
|
||||
// or how not pay allocations for the byte order conversions.
|
||||
elemBuf := &bytes.Buffer{}
|
||||
|
||||
for i := range src.Elements {
|
||||
err := src.Elements[i].EncodeBinary(elemBuf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if src.Elements[i].Status == Null {
|
||||
arrayHeader.ContainsNull = true
|
||||
}
|
||||
}
|
||||
|
||||
arrayHeader.ElementOID = Float8OID
|
||||
arrayHeader.Dimensions = src.Dimensions
|
||||
|
||||
// TODO - consider how to avoid having to buffer array before writing length -
|
||||
// or how not pay allocations for the byte order conversions.
|
||||
headerBuf := &bytes.Buffer{}
|
||||
err := arrayHeader.EncodeBinary(headerBuf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = pgio.WriteInt32(w, int32(headerBuf.Len()+elemBuf.Len()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = headerBuf.WriteTo(w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = elemBuf.WriteTo(w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
package pgtype_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgx/pgtype"
|
||||
)
|
||||
|
||||
func TestFloat8ArrayTranscode(t *testing.T) {
|
||||
testSuccessfulTranscode(t, "float8[]", []interface{}{
|
||||
&pgtype.Float8Array{
|
||||
Elements: nil,
|
||||
Dimensions: nil,
|
||||
Status: pgtype.Present,
|
||||
},
|
||||
&pgtype.Float8Array{
|
||||
Elements: []pgtype.Float8{
|
||||
pgtype.Float8{Float: 1, Status: pgtype.Present},
|
||||
pgtype.Float8{Status: pgtype.Null},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}},
|
||||
Status: pgtype.Present,
|
||||
},
|
||||
&pgtype.Float8Array{Status: pgtype.Null},
|
||||
&pgtype.Float8Array{
|
||||
Elements: []pgtype.Float8{
|
||||
pgtype.Float8{Float: 1, Status: pgtype.Present},
|
||||
pgtype.Float8{Float: 2, Status: pgtype.Present},
|
||||
pgtype.Float8{Float: 3, Status: pgtype.Present},
|
||||
pgtype.Float8{Float: 4, Status: pgtype.Present},
|
||||
pgtype.Float8{Status: pgtype.Null},
|
||||
pgtype.Float8{Float: 6, Status: pgtype.Present},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{{Length: 3, LowerBound: 1}, {Length: 2, LowerBound: 1}},
|
||||
Status: pgtype.Present,
|
||||
},
|
||||
&pgtype.Float8Array{
|
||||
Elements: []pgtype.Float8{
|
||||
pgtype.Float8{Float: 1, Status: pgtype.Present},
|
||||
pgtype.Float8{Float: 2, Status: pgtype.Present},
|
||||
pgtype.Float8{Float: 3, Status: pgtype.Present},
|
||||
pgtype.Float8{Float: 4, Status: pgtype.Present},
|
||||
},
|
||||
Dimensions: []pgtype.ArrayDimension{
|
||||
{Length: 2, LowerBound: 4},
|
||||
{Length: 2, LowerBound: 2},
|
||||
},
|
||||
Status: pgtype.Present,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestFloat8ArrayConvertFrom(t *testing.T) {
|
||||
successfulTests := []struct {
|
||||
source interface{}
|
||||
result pgtype.Float8Array
|
||||
}{
|
||||
{
|
||||
source: []float64{1},
|
||||
result: pgtype.Float8Array{
|
||||
Elements: []pgtype.Float8{{Float: 1, Status: pgtype.Present}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Status: pgtype.Present},
|
||||
},
|
||||
{
|
||||
source: (([]float64)(nil)),
|
||||
result: pgtype.Float8Array{Status: pgtype.Null},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range successfulTests {
|
||||
var r pgtype.Float8Array
|
||||
err := r.ConvertFrom(tt.source)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(r, tt.result) {
|
||||
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFloat8ArrayAssignTo(t *testing.T) {
|
||||
var float64Slice []float64
|
||||
var namedFloat64Slice _float64Slice
|
||||
|
||||
simpleTests := []struct {
|
||||
src pgtype.Float8Array
|
||||
dst interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
src: pgtype.Float8Array{
|
||||
Elements: []pgtype.Float8{{Float: 1.23, Status: pgtype.Present}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Status: pgtype.Present,
|
||||
},
|
||||
dst: &float64Slice,
|
||||
expected: []float64{1.23},
|
||||
},
|
||||
{
|
||||
src: pgtype.Float8Array{
|
||||
Elements: []pgtype.Float8{{Float: 1.23, Status: pgtype.Present}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Status: pgtype.Present,
|
||||
},
|
||||
dst: &namedFloat64Slice,
|
||||
expected: _float64Slice{1.23},
|
||||
},
|
||||
{
|
||||
src: pgtype.Float8Array{Status: pgtype.Null},
|
||||
dst: &float64Slice,
|
||||
expected: (([]float64)(nil)),
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range simpleTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
}
|
||||
|
||||
if dst := reflect.ValueOf(tt.dst).Elem().Interface(); !reflect.DeepEqual(dst, tt.expected) {
|
||||
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
|
||||
}
|
||||
}
|
||||
|
||||
errorTests := []struct {
|
||||
src pgtype.Float8Array
|
||||
dst interface{}
|
||||
}{
|
||||
{
|
||||
src: pgtype.Float8Array{
|
||||
Elements: []pgtype.Float8{{Status: pgtype.Null}},
|
||||
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
|
||||
Status: pgtype.Present,
|
||||
},
|
||||
dst: &float64Slice,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range errorTests {
|
||||
err := tt.src.AssignTo(tt.dst)
|
||||
if err == nil {
|
||||
t.Errorf("%d: expected error but none was returned (%v -> %v)", i, tt.src, tt.dst)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -75,7 +75,7 @@ func (dst *Int2) ConvertFrom(src interface{}) error {
|
|||
}
|
||||
*dst = Int2{Int: int16(num), Status: Present}
|
||||
default:
|
||||
if originalSrc, ok := underlyingIntType(src); ok {
|
||||
if originalSrc, ok := underlyingNumberType(src); ok {
|
||||
return dst.ConvertFrom(originalSrc)
|
||||
}
|
||||
return fmt.Errorf("cannot convert %v to Int2", value)
|
||||
|
|
|
@ -66,7 +66,7 @@ func (dst *Int4) ConvertFrom(src interface{}) error {
|
|||
}
|
||||
*dst = Int4{Int: int32(num), Status: Present}
|
||||
default:
|
||||
if originalSrc, ok := underlyingIntType(src); ok {
|
||||
if originalSrc, ok := underlyingNumberType(src); ok {
|
||||
return dst.ConvertFrom(originalSrc)
|
||||
}
|
||||
return fmt.Errorf("cannot convert %v to Int8", value)
|
||||
|
|
|
@ -57,7 +57,7 @@ func (dst *Int8) ConvertFrom(src interface{}) error {
|
|||
}
|
||||
*dst = Int8{Int: num, Status: Present}
|
||||
default:
|
||||
if originalSrc, ok := underlyingIntType(src); ok {
|
||||
if originalSrc, ok := underlyingNumberType(src); ok {
|
||||
return dst.ConvertFrom(originalSrc)
|
||||
}
|
||||
return fmt.Errorf("cannot convert %v to Int8", value)
|
||||
|
|
|
@ -18,6 +18,8 @@ type _int16 int16
|
|||
type _int16Slice []int16
|
||||
type _int32Slice []int32
|
||||
type _int64Slice []int64
|
||||
type _float32Slice []float32
|
||||
type _float64Slice []float64
|
||||
|
||||
func mustConnectPgx(t testing.TB) *pgx.Conn {
|
||||
config, err := pgx.ParseURI(os.Getenv("DATABASE_URL"))
|
||||
|
|
|
@ -5,3 +5,5 @@ erb pgtype_array_type=BoolArray pgtype_element_type=Bool go_array_types=[]bool e
|
|||
erb pgtype_array_type=DateArray pgtype_element_type=Date go_array_types=[]time.Time element_oid=DateOID typed_array.go.erb > datearray.go
|
||||
erb pgtype_array_type=TimestamptzArray pgtype_element_type=Timestamptz go_array_types=[]time.Time element_oid=TimestamptzOID typed_array.go.erb > timestamptzarray.go
|
||||
erb pgtype_array_type=TimestampArray pgtype_element_type=Timestamp go_array_types=[]time.Time element_oid=TimestampOID typed_array.go.erb > timestamparray.go
|
||||
erb pgtype_array_type=Float4Array pgtype_element_type=Float4 go_array_types=[]float32 element_oid=Float4OID typed_array.go.erb > float4array.go
|
||||
erb pgtype_array_type=Float8Array pgtype_element_type=Float8 go_array_types=[]float64 element_oid=Float8OID typed_array.go.erb > float8array.go
|
||||
|
|
|
@ -1151,9 +1151,6 @@ func TestQueryRowCoreFloat32Slice(t *testing.T) {
|
|||
if err == nil {
|
||||
t.Error("Expected null to cause error when scanned into slice, but it didn't")
|
||||
}
|
||||
if err != nil && !strings.Contains(err.Error(), "Cannot decode null") {
|
||||
t.Errorf(`Expected null to cause error "Cannot decode null..." but it was %v`, err)
|
||||
}
|
||||
|
||||
ensureConnValid(t, conn)
|
||||
}
|
||||
|
@ -1198,9 +1195,6 @@ func TestQueryRowCoreFloat64Slice(t *testing.T) {
|
|||
if err == nil {
|
||||
t.Error("Expected null to cause error when scanned into slice, but it didn't")
|
||||
}
|
||||
if err != nil && !strings.Contains(err.Error(), "Cannot decode null") {
|
||||
t.Errorf(`Expected null to cause error "Cannot decode null..." but it was %v`, err)
|
||||
}
|
||||
|
||||
ensureConnValid(t, conn)
|
||||
}
|
||||
|
|
16
values.go
16
values.go
|
@ -1088,14 +1088,6 @@ func Encode(wbuf *WriteBuf, oid OID, arg interface{}) error {
|
|||
// The name data type goes over the wire using the same format as string,
|
||||
// so just cast to string and use encodeString
|
||||
return encodeString(wbuf, oid, string(arg))
|
||||
case float32:
|
||||
return encodeFloat32(wbuf, oid, arg)
|
||||
case []float32:
|
||||
return encodeFloat32Slice(wbuf, oid, arg)
|
||||
case float64:
|
||||
return encodeFloat64(wbuf, oid, arg)
|
||||
case []float64:
|
||||
return encodeFloat64Slice(wbuf, oid, arg)
|
||||
case net.IP:
|
||||
return encodeIP(wbuf, oid, arg)
|
||||
case []net.IP:
|
||||
|
@ -1195,16 +1187,8 @@ func Decode(vr *ValueReader, d interface{}) error {
|
|||
*v = decodeCid(vr)
|
||||
case *string:
|
||||
*v = decodeText(vr)
|
||||
case *float32:
|
||||
*v = decodeFloat4(vr)
|
||||
case *float64:
|
||||
*v = decodeFloat8(vr)
|
||||
case *[]AclItem:
|
||||
*v = decodeAclItemArray(vr)
|
||||
case *[]float32:
|
||||
*v = decodeFloat4Array(vr)
|
||||
case *[]float64:
|
||||
*v = decodeFloat8Array(vr)
|
||||
case *[]string:
|
||||
*v = decodeTextArray(vr)
|
||||
case *[][]byte:
|
||||
|
|
Loading…
Reference in New Issue