From 2010bea5557fed90eb20d3a8baa767bf83fe869e Mon Sep 17 00:00:00 2001 From: Jack Christensen Date: Sat, 4 Mar 2017 13:29:04 -0600 Subject: [PATCH] Add float4, float8 and arrays --- conn.go | 4 + pgtype/convert.go | 52 ++++++- pgtype/float4.go | 171 ++++++++++++++++++++++ pgtype/float4_test.go | 148 +++++++++++++++++++ pgtype/float4array.go | 286 +++++++++++++++++++++++++++++++++++++ pgtype/float4array_test.go | 151 ++++++++++++++++++++ pgtype/float8.go | 161 +++++++++++++++++++++ pgtype/float8_test.go | 148 +++++++++++++++++++ pgtype/float8array.go | 286 +++++++++++++++++++++++++++++++++++++ pgtype/float8array_test.go | 151 ++++++++++++++++++++ pgtype/int2.go | 2 +- pgtype/int4.go | 2 +- pgtype/int8.go | 2 +- pgtype/pgtype_test.go | 2 + pgtype/typed_array_gen.sh | 2 + query_test.go | 6 - values.go | 16 --- 17 files changed, 1563 insertions(+), 27 deletions(-) create mode 100644 pgtype/float4.go create mode 100644 pgtype/float4_test.go create mode 100644 pgtype/float4array.go create mode 100644 pgtype/float4array_test.go create mode 100644 pgtype/float8.go create mode 100644 pgtype/float8_test.go create mode 100644 pgtype/float8array.go create mode 100644 pgtype/float8array_test.go diff --git a/conn.go b/conn.go index 5fd82ea0..1e277a0e 100644 --- a/conn.go +++ b/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{}, diff --git a/pgtype/convert.go b/pgtype/convert.go index e35e2310..c4b52322 100644 --- a/pgtype/convert.go +++ b/pgtype/convert.go @@ -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) +} diff --git a/pgtype/float4.go b/pgtype/float4.go new file mode 100644 index 00000000..a1e5aa18 --- /dev/null +++ b/pgtype/float4.go @@ -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 +} diff --git a/pgtype/float4_test.go b/pgtype/float4_test.go new file mode 100644 index 00000000..62420b8d --- /dev/null +++ b/pgtype/float4_test.go @@ -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) + } + } +} diff --git a/pgtype/float4array.go b/pgtype/float4array.go new file mode 100644 index 00000000..c06490cf --- /dev/null +++ b/pgtype/float4array.go @@ -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 +} diff --git a/pgtype/float4array_test.go b/pgtype/float4array_test.go new file mode 100644 index 00000000..b22f4fbc --- /dev/null +++ b/pgtype/float4array_test.go @@ -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) + } + } + +} diff --git a/pgtype/float8.go b/pgtype/float8.go new file mode 100644 index 00000000..c1347cb2 --- /dev/null +++ b/pgtype/float8.go @@ -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 +} diff --git a/pgtype/float8_test.go b/pgtype/float8_test.go new file mode 100644 index 00000000..748ffd25 --- /dev/null +++ b/pgtype/float8_test.go @@ -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) + } + } +} diff --git a/pgtype/float8array.go b/pgtype/float8array.go new file mode 100644 index 00000000..776fc1e6 --- /dev/null +++ b/pgtype/float8array.go @@ -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 +} diff --git a/pgtype/float8array_test.go b/pgtype/float8array_test.go new file mode 100644 index 00000000..d4402281 --- /dev/null +++ b/pgtype/float8array_test.go @@ -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) + } + } + +} diff --git a/pgtype/int2.go b/pgtype/int2.go index fb6a8ccc..8057550b 100644 --- a/pgtype/int2.go +++ b/pgtype/int2.go @@ -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) diff --git a/pgtype/int4.go b/pgtype/int4.go index 1a4733b0..43691bb6 100644 --- a/pgtype/int4.go +++ b/pgtype/int4.go @@ -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) diff --git a/pgtype/int8.go b/pgtype/int8.go index 7f307f18..b87bb85a 100644 --- a/pgtype/int8.go +++ b/pgtype/int8.go @@ -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) diff --git a/pgtype/pgtype_test.go b/pgtype/pgtype_test.go index 97afc249..a1dcd11b 100644 --- a/pgtype/pgtype_test.go +++ b/pgtype/pgtype_test.go @@ -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")) diff --git a/pgtype/typed_array_gen.sh b/pgtype/typed_array_gen.sh index 9f4e1ce0..4ce6c3b5 100644 --- a/pgtype/typed_array_gen.sh +++ b/pgtype/typed_array_gen.sh @@ -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 diff --git a/query_test.go b/query_test.go index 84b90d4b..9b6f9a06 100644 --- a/query_test.go +++ b/query_test.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) } diff --git a/values.go b/values.go index e4347a8b..d2ec9fc2 100644 --- a/values.go +++ b/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: