Add arrays to all other pgtypes

v3-numeric-wip
Jack Christensen 2017-03-04 11:48:53 -06:00
parent aabf43a725
commit ffb949054d
18 changed files with 2614 additions and 33 deletions

19
conn.go
View File

@ -279,13 +279,18 @@ func (c *Conn) connect(config ConnConfig, network, address string, tlsConfig *tl
c.closedChan = make(chan error)
c.oidPgtypeValues = map[OID]pgtype.Value{
BoolOID: &pgtype.Bool{},
DateOID: &pgtype.Date{},
Int2OID: &pgtype.Int2{},
Int2ArrayOID: &pgtype.Int2Array{},
Int4OID: &pgtype.Int4{},
Int8OID: &pgtype.Int8{},
TimestampTzOID: &pgtype.Timestamptz{},
BoolOID: &pgtype.Bool{},
BoolArrayOID: &pgtype.BoolArray{},
DateOID: &pgtype.Date{},
DateArrayOID: &pgtype.DateArray{},
Int2OID: &pgtype.Int2{},
Int2ArrayOID: &pgtype.Int2Array{},
Int4OID: &pgtype.Int4{},
Int4ArrayOID: &pgtype.Int4Array{},
Int8OID: &pgtype.Int8{},
Int8ArrayOID: &pgtype.Int8Array{},
TimestampTzOID: &pgtype.Timestamptz{},
TimestampTzArrayOID: &pgtype.TimestamptzArray{},
}
if tlsConfig != nil {

286
pgtype/boolarray.go Normal file
View File

@ -0,0 +1,286 @@
package pgtype
import (
"bytes"
"fmt"
"io"
"github.com/jackc/pgx/pgio"
)
type BoolArray struct {
Elements []Bool
Dimensions []ArrayDimension
Status Status
}
func (dst *BoolArray) ConvertFrom(src interface{}) error {
switch value := src.(type) {
case BoolArray:
*dst = value
case []bool:
if value == nil {
*dst = BoolArray{Status: Null}
} else if len(value) == 0 {
*dst = BoolArray{Status: Present}
} else {
elements := make([]Bool, len(value))
for i := range value {
if err := elements[i].ConvertFrom(value[i]); err != nil {
return err
}
}
*dst = BoolArray{
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 Bool", value)
}
return nil
}
func (src *BoolArray) AssignTo(dst interface{}) error {
switch v := dst.(type) {
case *[]bool:
if src.Status == Present {
*v = make([]bool, 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 *BoolArray) DecodeText(r io.Reader) error {
size, err := pgio.ReadInt32(r)
if err != nil {
return err
}
if size == -1 {
*dst = BoolArray{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 []Bool
if len(uta.Elements) > 0 {
elements = make([]Bool, len(uta.Elements))
for i, s := range uta.Elements {
var elem Bool
textElementReader.Reset(s)
err = elem.DecodeText(textElementReader)
if err != nil {
return err
}
elements[i] = elem
}
}
*dst = BoolArray{Elements: elements, Dimensions: uta.Dimensions, Status: Present}
return nil
}
func (dst *BoolArray) DecodeBinary(r io.Reader) error {
size, err := pgio.ReadInt32(r)
if err != nil {
return err
}
if size == -1 {
*dst = BoolArray{Status: Null}
return nil
}
var arrayHeader ArrayHeader
err = arrayHeader.DecodeBinary(r)
if err != nil {
return err
}
if len(arrayHeader.Dimensions) == 0 {
*dst = BoolArray{Dimensions: arrayHeader.Dimensions, Status: Present}
return nil
}
elementCount := arrayHeader.Dimensions[0].Length
for _, d := range arrayHeader.Dimensions[1:] {
elementCount *= d.Length
}
elements := make([]Bool, elementCount)
for i := range elements {
err = elements[i].DecodeBinary(r)
if err != nil {
return err
}
}
*dst = BoolArray{Elements: elements, Dimensions: arrayHeader.Dimensions, Status: Present}
return nil
}
func (src *BoolArray) 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 *BoolArray) 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 = BoolOID
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
}

152
pgtype/boolarray_test.go Normal file
View File

@ -0,0 +1,152 @@
package pgtype_test
import (
"reflect"
"testing"
"github.com/jackc/pgx/pgtype"
)
func TestBoolArrayTranscode(t *testing.T) {
testSuccessfulTranscode(t, "bool[]", []interface{}{
&pgtype.BoolArray{
Elements: nil,
Dimensions: nil,
Status: pgtype.Present,
},
&pgtype.BoolArray{
Elements: []pgtype.Bool{
pgtype.Bool{Bool: true, Status: pgtype.Present},
pgtype.Bool{Status: pgtype.Null},
},
Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}},
Status: pgtype.Present,
},
&pgtype.BoolArray{Status: pgtype.Null},
&pgtype.BoolArray{
Elements: []pgtype.Bool{
pgtype.Bool{Bool: true, Status: pgtype.Present},
pgtype.Bool{Bool: true, Status: pgtype.Present},
pgtype.Bool{Bool: false, Status: pgtype.Present},
pgtype.Bool{Bool: true, Status: pgtype.Present},
pgtype.Bool{Status: pgtype.Null},
pgtype.Bool{Bool: false, Status: pgtype.Present},
},
Dimensions: []pgtype.ArrayDimension{{Length: 3, LowerBound: 1}, {Length: 2, LowerBound: 1}},
Status: pgtype.Present,
},
&pgtype.BoolArray{
Elements: []pgtype.Bool{
pgtype.Bool{Bool: true, Status: pgtype.Present},
pgtype.Bool{Bool: false, Status: pgtype.Present},
pgtype.Bool{Bool: true, Status: pgtype.Present},
pgtype.Bool{Bool: false, Status: pgtype.Present},
},
Dimensions: []pgtype.ArrayDimension{
{Length: 2, LowerBound: 4},
{Length: 2, LowerBound: 2},
},
Status: pgtype.Present,
},
})
}
func TestBoolArrayConvertFrom(t *testing.T) {
successfulTests := []struct {
source interface{}
result pgtype.BoolArray
}{
{
source: []bool{true},
result: pgtype.BoolArray{
Elements: []pgtype.Bool{{Bool: true, Status: pgtype.Present}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
Status: pgtype.Present},
},
{
source: (([]bool)(nil)),
result: pgtype.BoolArray{Status: pgtype.Null},
},
}
for i, tt := range successfulTests {
var r pgtype.BoolArray
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 TestBoolArrayAssignTo(t *testing.T) {
var boolSlice []bool
type _boolSlice []bool
var namedBoolSlice _boolSlice
simpleTests := []struct {
src pgtype.BoolArray
dst interface{}
expected interface{}
}{
{
src: pgtype.BoolArray{
Elements: []pgtype.Bool{{Bool: true, Status: pgtype.Present}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
Status: pgtype.Present,
},
dst: &boolSlice,
expected: []bool{true},
},
{
src: pgtype.BoolArray{
Elements: []pgtype.Bool{{Bool: true, Status: pgtype.Present}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
Status: pgtype.Present,
},
dst: &namedBoolSlice,
expected: _boolSlice{true},
},
{
src: pgtype.BoolArray{Status: pgtype.Null},
dst: &boolSlice,
expected: (([]bool)(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.BoolArray
dst interface{}
}{
{
src: pgtype.BoolArray{
Elements: []pgtype.Bool{{Status: pgtype.Null}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
Status: pgtype.Present,
},
dst: &boolSlice,
},
}
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)
}
}
}

287
pgtype/datearray.go Normal file
View File

@ -0,0 +1,287 @@
package pgtype
import (
"bytes"
"fmt"
"io"
"time"
"github.com/jackc/pgx/pgio"
)
type DateArray struct {
Elements []Date
Dimensions []ArrayDimension
Status Status
}
func (dst *DateArray) ConvertFrom(src interface{}) error {
switch value := src.(type) {
case DateArray:
*dst = value
case []time.Time:
if value == nil {
*dst = DateArray{Status: Null}
} else if len(value) == 0 {
*dst = DateArray{Status: Present}
} else {
elements := make([]Date, len(value))
for i := range value {
if err := elements[i].ConvertFrom(value[i]); err != nil {
return err
}
}
*dst = DateArray{
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 Date", value)
}
return nil
}
func (src *DateArray) AssignTo(dst interface{}) error {
switch v := dst.(type) {
case *[]time.Time:
if src.Status == Present {
*v = make([]time.Time, 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 *DateArray) DecodeText(r io.Reader) error {
size, err := pgio.ReadInt32(r)
if err != nil {
return err
}
if size == -1 {
*dst = DateArray{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 []Date
if len(uta.Elements) > 0 {
elements = make([]Date, len(uta.Elements))
for i, s := range uta.Elements {
var elem Date
textElementReader.Reset(s)
err = elem.DecodeText(textElementReader)
if err != nil {
return err
}
elements[i] = elem
}
}
*dst = DateArray{Elements: elements, Dimensions: uta.Dimensions, Status: Present}
return nil
}
func (dst *DateArray) DecodeBinary(r io.Reader) error {
size, err := pgio.ReadInt32(r)
if err != nil {
return err
}
if size == -1 {
*dst = DateArray{Status: Null}
return nil
}
var arrayHeader ArrayHeader
err = arrayHeader.DecodeBinary(r)
if err != nil {
return err
}
if len(arrayHeader.Dimensions) == 0 {
*dst = DateArray{Dimensions: arrayHeader.Dimensions, Status: Present}
return nil
}
elementCount := arrayHeader.Dimensions[0].Length
for _, d := range arrayHeader.Dimensions[1:] {
elementCount *= d.Length
}
elements := make([]Date, elementCount)
for i := range elements {
err = elements[i].DecodeBinary(r)
if err != nil {
return err
}
}
*dst = DateArray{Elements: elements, Dimensions: arrayHeader.Dimensions, Status: Present}
return nil
}
func (src *DateArray) 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 *DateArray) 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 = DateOID
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
}

142
pgtype/datearray_test.go Normal file
View File

@ -0,0 +1,142 @@
package pgtype_test
import (
"reflect"
"testing"
"time"
"github.com/jackc/pgx/pgtype"
)
func TestDateArrayTranscode(t *testing.T) {
testSuccessfulTranscode(t, "date[]", []interface{}{
&pgtype.DateArray{
Elements: nil,
Dimensions: nil,
Status: pgtype.Present,
},
&pgtype.DateArray{
Elements: []pgtype.Date{
pgtype.Date{Time: time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
pgtype.Date{Status: pgtype.Null},
},
Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}},
Status: pgtype.Present,
},
&pgtype.DateArray{Status: pgtype.Null},
&pgtype.DateArray{
Elements: []pgtype.Date{
pgtype.Date{Time: time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
pgtype.Date{Time: time.Date(2016, 2, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
pgtype.Date{Time: time.Date(2017, 2, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
pgtype.Date{Time: time.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
pgtype.Date{Status: pgtype.Null},
pgtype.Date{Time: time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
},
Dimensions: []pgtype.ArrayDimension{{Length: 3, LowerBound: 1}, {Length: 2, LowerBound: 1}},
Status: pgtype.Present,
},
&pgtype.DateArray{
Elements: []pgtype.Date{
pgtype.Date{Time: time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
pgtype.Date{Time: time.Date(2015, 2, 2, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
pgtype.Date{Time: time.Date(2015, 2, 3, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
pgtype.Date{Time: time.Date(2015, 2, 4, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
},
Dimensions: []pgtype.ArrayDimension{
{Length: 2, LowerBound: 4},
{Length: 2, LowerBound: 2},
},
Status: pgtype.Present,
},
})
}
func TestDateArrayConvertFrom(t *testing.T) {
successfulTests := []struct {
source interface{}
result pgtype.DateArray
}{
{
source: []time.Time{time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC)},
result: pgtype.DateArray{
Elements: []pgtype.Date{{Time: time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
Status: pgtype.Present},
},
{
source: (([]time.Time)(nil)),
result: pgtype.DateArray{Status: pgtype.Null},
},
}
for i, tt := range successfulTests {
var r pgtype.DateArray
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 TestDateArrayAssignTo(t *testing.T) {
var timeSlice []time.Time
simpleTests := []struct {
src pgtype.DateArray
dst interface{}
expected interface{}
}{
{
src: pgtype.DateArray{
Elements: []pgtype.Date{{Time: time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
Status: pgtype.Present,
},
dst: &timeSlice,
expected: []time.Time{time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC)},
},
{
src: pgtype.DateArray{Status: pgtype.Null},
dst: &timeSlice,
expected: (([]time.Time)(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.DateArray
dst interface{}
}{
{
src: pgtype.DateArray{
Elements: []pgtype.Date{{Status: pgtype.Null}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
Status: pgtype.Present,
},
dst: &timeSlice,
},
}
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)
}
}
}

View File

@ -18,6 +18,7 @@ func (dst *Int2Array) ConvertFrom(src interface{}) error {
switch value := src.(type) {
case Int2Array:
*dst = value
case []int16:
if value == nil {
*dst = Int2Array{Status: Null}
@ -36,6 +37,7 @@ func (dst *Int2Array) ConvertFrom(src interface{}) error {
Status: Present,
}
}
case []uint16:
if value == nil {
*dst = Int2Array{Status: Null}
@ -54,6 +56,7 @@ func (dst *Int2Array) ConvertFrom(src interface{}) error {
Status: Present,
}
}
default:
if originalSrc, ok := underlyingSliceType(src); ok {
return dst.ConvertFrom(originalSrc)
@ -66,6 +69,7 @@ func (dst *Int2Array) ConvertFrom(src interface{}) error {
func (src *Int2Array) AssignTo(dst interface{}) error {
switch v := dst.(type) {
case *[]int16:
if src.Status == Present {
*v = make([]int16, len(src.Elements))
@ -77,6 +81,7 @@ func (src *Int2Array) AssignTo(dst interface{}) error {
} else {
*v = nil
}
case *[]uint16:
if src.Status == Present {
*v = make([]uint16, len(src.Elements))
@ -88,6 +93,7 @@ func (src *Int2Array) AssignTo(dst interface{}) error {
} else {
*v = nil
}
default:
if originalDst, ok := underlyingPtrSliceType(dst); ok {
return src.AssignTo(originalDst)

317
pgtype/int4array.go Normal file
View File

@ -0,0 +1,317 @@
package pgtype
import (
"bytes"
"fmt"
"io"
"github.com/jackc/pgx/pgio"
)
type Int4Array struct {
Elements []Int4
Dimensions []ArrayDimension
Status Status
}
func (dst *Int4Array) ConvertFrom(src interface{}) error {
switch value := src.(type) {
case Int4Array:
*dst = value
case []int32:
if value == nil {
*dst = Int4Array{Status: Null}
} else if len(value) == 0 {
*dst = Int4Array{Status: Present}
} else {
elements := make([]Int4, len(value))
for i := range value {
if err := elements[i].ConvertFrom(value[i]); err != nil {
return err
}
}
*dst = Int4Array{
Elements: elements,
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
Status: Present,
}
}
case []uint32:
if value == nil {
*dst = Int4Array{Status: Null}
} else if len(value) == 0 {
*dst = Int4Array{Status: Present}
} else {
elements := make([]Int4, len(value))
for i := range value {
if err := elements[i].ConvertFrom(value[i]); err != nil {
return err
}
}
*dst = Int4Array{
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 Int4", value)
}
return nil
}
func (src *Int4Array) AssignTo(dst interface{}) error {
switch v := dst.(type) {
case *[]int32:
if src.Status == Present {
*v = make([]int32, len(src.Elements))
for i := range src.Elements {
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
return err
}
}
} else {
*v = nil
}
case *[]uint32:
if src.Status == Present {
*v = make([]uint32, 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 *Int4Array) DecodeText(r io.Reader) error {
size, err := pgio.ReadInt32(r)
if err != nil {
return err
}
if size == -1 {
*dst = Int4Array{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 []Int4
if len(uta.Elements) > 0 {
elements = make([]Int4, len(uta.Elements))
for i, s := range uta.Elements {
var elem Int4
textElementReader.Reset(s)
err = elem.DecodeText(textElementReader)
if err != nil {
return err
}
elements[i] = elem
}
}
*dst = Int4Array{Elements: elements, Dimensions: uta.Dimensions, Status: Present}
return nil
}
func (dst *Int4Array) DecodeBinary(r io.Reader) error {
size, err := pgio.ReadInt32(r)
if err != nil {
return err
}
if size == -1 {
*dst = Int4Array{Status: Null}
return nil
}
var arrayHeader ArrayHeader
err = arrayHeader.DecodeBinary(r)
if err != nil {
return err
}
if len(arrayHeader.Dimensions) == 0 {
*dst = Int4Array{Dimensions: arrayHeader.Dimensions, Status: Present}
return nil
}
elementCount := arrayHeader.Dimensions[0].Length
for _, d := range arrayHeader.Dimensions[1:] {
elementCount *= d.Length
}
elements := make([]Int4, elementCount)
for i := range elements {
err = elements[i].DecodeBinary(r)
if err != nil {
return err
}
}
*dst = Int4Array{Elements: elements, Dimensions: arrayHeader.Dimensions, Status: Present}
return nil
}
func (src *Int4Array) 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 *Int4Array) 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 = Int4OID
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
}

176
pgtype/int4array_test.go Normal file
View File

@ -0,0 +1,176 @@
package pgtype_test
import (
"reflect"
"testing"
"github.com/jackc/pgx/pgtype"
)
func TestInt4ArrayTranscode(t *testing.T) {
testSuccessfulTranscode(t, "int4[]", []interface{}{
&pgtype.Int4Array{
Elements: nil,
Dimensions: nil,
Status: pgtype.Present,
},
&pgtype.Int4Array{
Elements: []pgtype.Int4{
pgtype.Int4{Int: 1, Status: pgtype.Present},
pgtype.Int4{Status: pgtype.Null},
},
Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}},
Status: pgtype.Present,
},
&pgtype.Int4Array{Status: pgtype.Null},
&pgtype.Int4Array{
Elements: []pgtype.Int4{
pgtype.Int4{Int: 1, Status: pgtype.Present},
pgtype.Int4{Int: 2, Status: pgtype.Present},
pgtype.Int4{Int: 3, Status: pgtype.Present},
pgtype.Int4{Int: 4, Status: pgtype.Present},
pgtype.Int4{Status: pgtype.Null},
pgtype.Int4{Int: 6, Status: pgtype.Present},
},
Dimensions: []pgtype.ArrayDimension{{Length: 3, LowerBound: 1}, {Length: 2, LowerBound: 1}},
Status: pgtype.Present,
},
&pgtype.Int4Array{
Elements: []pgtype.Int4{
pgtype.Int4{Int: 1, Status: pgtype.Present},
pgtype.Int4{Int: 2, Status: pgtype.Present},
pgtype.Int4{Int: 3, Status: pgtype.Present},
pgtype.Int4{Int: 4, Status: pgtype.Present},
},
Dimensions: []pgtype.ArrayDimension{
{Length: 2, LowerBound: 4},
{Length: 2, LowerBound: 2},
},
Status: pgtype.Present,
},
})
}
func TestInt4ArrayConvertFrom(t *testing.T) {
successfulTests := []struct {
source interface{}
result pgtype.Int4Array
}{
{
source: []int32{1},
result: pgtype.Int4Array{
Elements: []pgtype.Int4{{Int: 1, Status: pgtype.Present}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
Status: pgtype.Present},
},
{
source: []uint32{1},
result: pgtype.Int4Array{
Elements: []pgtype.Int4{{Int: 1, Status: pgtype.Present}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
Status: pgtype.Present},
},
{
source: (([]int32)(nil)),
result: pgtype.Int4Array{Status: pgtype.Null},
},
}
for i, tt := range successfulTests {
var r pgtype.Int4Array
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 TestInt4ArrayAssignTo(t *testing.T) {
var int32Slice []int32
var uint32Slice []uint32
var namedInt32Slice _int32Slice
simpleTests := []struct {
src pgtype.Int4Array
dst interface{}
expected interface{}
}{
{
src: pgtype.Int4Array{
Elements: []pgtype.Int4{{Int: 1, Status: pgtype.Present}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
Status: pgtype.Present,
},
dst: &int32Slice,
expected: []int32{1},
},
{
src: pgtype.Int4Array{
Elements: []pgtype.Int4{{Int: 1, Status: pgtype.Present}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
Status: pgtype.Present,
},
dst: &uint32Slice,
expected: []uint32{1},
},
{
src: pgtype.Int4Array{
Elements: []pgtype.Int4{{Int: 1, Status: pgtype.Present}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
Status: pgtype.Present,
},
dst: &namedInt32Slice,
expected: _int32Slice{1},
},
{
src: pgtype.Int4Array{Status: pgtype.Null},
dst: &int32Slice,
expected: (([]int32)(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.Int4Array
dst interface{}
}{
{
src: pgtype.Int4Array{
Elements: []pgtype.Int4{{Status: pgtype.Null}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
Status: pgtype.Present,
},
dst: &int32Slice,
},
{
src: pgtype.Int4Array{
Elements: []pgtype.Int4{{Int: -1, Status: pgtype.Present}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
Status: pgtype.Present,
},
dst: &uint32Slice,
},
}
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)
}
}
}

317
pgtype/int8array.go Normal file
View File

@ -0,0 +1,317 @@
package pgtype
import (
"bytes"
"fmt"
"io"
"github.com/jackc/pgx/pgio"
)
type Int8Array struct {
Elements []Int8
Dimensions []ArrayDimension
Status Status
}
func (dst *Int8Array) ConvertFrom(src interface{}) error {
switch value := src.(type) {
case Int8Array:
*dst = value
case []int64:
if value == nil {
*dst = Int8Array{Status: Null}
} else if len(value) == 0 {
*dst = Int8Array{Status: Present}
} else {
elements := make([]Int8, len(value))
for i := range value {
if err := elements[i].ConvertFrom(value[i]); err != nil {
return err
}
}
*dst = Int8Array{
Elements: elements,
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
Status: Present,
}
}
case []uint64:
if value == nil {
*dst = Int8Array{Status: Null}
} else if len(value) == 0 {
*dst = Int8Array{Status: Present}
} else {
elements := make([]Int8, len(value))
for i := range value {
if err := elements[i].ConvertFrom(value[i]); err != nil {
return err
}
}
*dst = Int8Array{
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 Int8", value)
}
return nil
}
func (src *Int8Array) AssignTo(dst interface{}) error {
switch v := dst.(type) {
case *[]int64:
if src.Status == Present {
*v = make([]int64, len(src.Elements))
for i := range src.Elements {
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
return err
}
}
} else {
*v = nil
}
case *[]uint64:
if src.Status == Present {
*v = make([]uint64, 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 *Int8Array) DecodeText(r io.Reader) error {
size, err := pgio.ReadInt32(r)
if err != nil {
return err
}
if size == -1 {
*dst = Int8Array{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 []Int8
if len(uta.Elements) > 0 {
elements = make([]Int8, len(uta.Elements))
for i, s := range uta.Elements {
var elem Int8
textElementReader.Reset(s)
err = elem.DecodeText(textElementReader)
if err != nil {
return err
}
elements[i] = elem
}
}
*dst = Int8Array{Elements: elements, Dimensions: uta.Dimensions, Status: Present}
return nil
}
func (dst *Int8Array) DecodeBinary(r io.Reader) error {
size, err := pgio.ReadInt32(r)
if err != nil {
return err
}
if size == -1 {
*dst = Int8Array{Status: Null}
return nil
}
var arrayHeader ArrayHeader
err = arrayHeader.DecodeBinary(r)
if err != nil {
return err
}
if len(arrayHeader.Dimensions) == 0 {
*dst = Int8Array{Dimensions: arrayHeader.Dimensions, Status: Present}
return nil
}
elementCount := arrayHeader.Dimensions[0].Length
for _, d := range arrayHeader.Dimensions[1:] {
elementCount *= d.Length
}
elements := make([]Int8, elementCount)
for i := range elements {
err = elements[i].DecodeBinary(r)
if err != nil {
return err
}
}
*dst = Int8Array{Elements: elements, Dimensions: arrayHeader.Dimensions, Status: Present}
return nil
}
func (src *Int8Array) 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 *Int8Array) 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 = Int8OID
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
}

176
pgtype/int8array_test.go Normal file
View File

@ -0,0 +1,176 @@
package pgtype_test
import (
"reflect"
"testing"
"github.com/jackc/pgx/pgtype"
)
func TestInt8ArrayTranscode(t *testing.T) {
testSuccessfulTranscode(t, "int8[]", []interface{}{
&pgtype.Int8Array{
Elements: nil,
Dimensions: nil,
Status: pgtype.Present,
},
&pgtype.Int8Array{
Elements: []pgtype.Int8{
pgtype.Int8{Int: 1, Status: pgtype.Present},
pgtype.Int8{Status: pgtype.Null},
},
Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}},
Status: pgtype.Present,
},
&pgtype.Int8Array{Status: pgtype.Null},
&pgtype.Int8Array{
Elements: []pgtype.Int8{
pgtype.Int8{Int: 1, Status: pgtype.Present},
pgtype.Int8{Int: 2, Status: pgtype.Present},
pgtype.Int8{Int: 3, Status: pgtype.Present},
pgtype.Int8{Int: 4, Status: pgtype.Present},
pgtype.Int8{Status: pgtype.Null},
pgtype.Int8{Int: 6, Status: pgtype.Present},
},
Dimensions: []pgtype.ArrayDimension{{Length: 3, LowerBound: 1}, {Length: 2, LowerBound: 1}},
Status: pgtype.Present,
},
&pgtype.Int8Array{
Elements: []pgtype.Int8{
pgtype.Int8{Int: 1, Status: pgtype.Present},
pgtype.Int8{Int: 2, Status: pgtype.Present},
pgtype.Int8{Int: 3, Status: pgtype.Present},
pgtype.Int8{Int: 4, Status: pgtype.Present},
},
Dimensions: []pgtype.ArrayDimension{
{Length: 2, LowerBound: 4},
{Length: 2, LowerBound: 2},
},
Status: pgtype.Present,
},
})
}
func TestInt8ArrayConvertFrom(t *testing.T) {
successfulTests := []struct {
source interface{}
result pgtype.Int8Array
}{
{
source: []int64{1},
result: pgtype.Int8Array{
Elements: []pgtype.Int8{{Int: 1, Status: pgtype.Present}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
Status: pgtype.Present},
},
{
source: []uint64{1},
result: pgtype.Int8Array{
Elements: []pgtype.Int8{{Int: 1, Status: pgtype.Present}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
Status: pgtype.Present},
},
{
source: (([]int64)(nil)),
result: pgtype.Int8Array{Status: pgtype.Null},
},
}
for i, tt := range successfulTests {
var r pgtype.Int8Array
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 TestInt8ArrayAssignTo(t *testing.T) {
var int64Slice []int64
var uint64Slice []uint64
var namedInt64Slice _int64Slice
simpleTests := []struct {
src pgtype.Int8Array
dst interface{}
expected interface{}
}{
{
src: pgtype.Int8Array{
Elements: []pgtype.Int8{{Int: 1, Status: pgtype.Present}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
Status: pgtype.Present,
},
dst: &int64Slice,
expected: []int64{1},
},
{
src: pgtype.Int8Array{
Elements: []pgtype.Int8{{Int: 1, Status: pgtype.Present}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
Status: pgtype.Present,
},
dst: &uint64Slice,
expected: []uint64{1},
},
{
src: pgtype.Int8Array{
Elements: []pgtype.Int8{{Int: 1, Status: pgtype.Present}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
Status: pgtype.Present,
},
dst: &namedInt64Slice,
expected: _int64Slice{1},
},
{
src: pgtype.Int8Array{Status: pgtype.Null},
dst: &int64Slice,
expected: (([]int64)(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.Int8Array
dst interface{}
}{
{
src: pgtype.Int8Array{
Elements: []pgtype.Int8{{Status: pgtype.Null}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
Status: pgtype.Present,
},
dst: &int64Slice,
},
{
src: pgtype.Int8Array{
Elements: []pgtype.Int8{{Int: -1, Status: pgtype.Present}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
Status: pgtype.Present,
},
dst: &uint64Slice,
},
}
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)
}
}
}

View File

@ -44,8 +44,9 @@ const (
DateOID = 1082
TimestampOID = 1114
TimestampArrayOID = 1115
TimestampTzOID = 1184
TimestampTzArrayOID = 1185
DateArrayOID = 1182
TimestamptzOID = 1184
TimestamptzArrayOID = 1185
RecordOID = 2249
UUIDOID = 2950
JSONBOID = 3802

View File

@ -16,6 +16,8 @@ type _bool bool
type _int8 int8
type _int16 int16
type _int16Slice []int16
type _int32Slice []int32
type _int64Slice []int64
func mustConnectPgx(t testing.TB) *pgx.Conn {
config, err := pgx.ParseURI(os.Getenv("DATABASE_URL"))

287
pgtype/timestamptzarray.go Normal file
View File

@ -0,0 +1,287 @@
package pgtype
import (
"bytes"
"fmt"
"io"
"time"
"github.com/jackc/pgx/pgio"
)
type TimestamptzArray struct {
Elements []Timestamptz
Dimensions []ArrayDimension
Status Status
}
func (dst *TimestamptzArray) ConvertFrom(src interface{}) error {
switch value := src.(type) {
case TimestamptzArray:
*dst = value
case []time.Time:
if value == nil {
*dst = TimestamptzArray{Status: Null}
} else if len(value) == 0 {
*dst = TimestamptzArray{Status: Present}
} else {
elements := make([]Timestamptz, len(value))
for i := range value {
if err := elements[i].ConvertFrom(value[i]); err != nil {
return err
}
}
*dst = TimestamptzArray{
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 Timestamptz", value)
}
return nil
}
func (src *TimestamptzArray) AssignTo(dst interface{}) error {
switch v := dst.(type) {
case *[]time.Time:
if src.Status == Present {
*v = make([]time.Time, 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 *TimestamptzArray) DecodeText(r io.Reader) error {
size, err := pgio.ReadInt32(r)
if err != nil {
return err
}
if size == -1 {
*dst = TimestamptzArray{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 []Timestamptz
if len(uta.Elements) > 0 {
elements = make([]Timestamptz, len(uta.Elements))
for i, s := range uta.Elements {
var elem Timestamptz
textElementReader.Reset(s)
err = elem.DecodeText(textElementReader)
if err != nil {
return err
}
elements[i] = elem
}
}
*dst = TimestamptzArray{Elements: elements, Dimensions: uta.Dimensions, Status: Present}
return nil
}
func (dst *TimestamptzArray) DecodeBinary(r io.Reader) error {
size, err := pgio.ReadInt32(r)
if err != nil {
return err
}
if size == -1 {
*dst = TimestamptzArray{Status: Null}
return nil
}
var arrayHeader ArrayHeader
err = arrayHeader.DecodeBinary(r)
if err != nil {
return err
}
if len(arrayHeader.Dimensions) == 0 {
*dst = TimestamptzArray{Dimensions: arrayHeader.Dimensions, Status: Present}
return nil
}
elementCount := arrayHeader.Dimensions[0].Length
for _, d := range arrayHeader.Dimensions[1:] {
elementCount *= d.Length
}
elements := make([]Timestamptz, elementCount)
for i := range elements {
err = elements[i].DecodeBinary(r)
if err != nil {
return err
}
}
*dst = TimestamptzArray{Elements: elements, Dimensions: arrayHeader.Dimensions, Status: Present}
return nil
}
func (src *TimestamptzArray) 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 *TimestamptzArray) 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 = TimestamptzOID
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
}

View File

@ -0,0 +1,158 @@
package pgtype_test
import (
"reflect"
"testing"
"time"
"github.com/jackc/pgx/pgtype"
)
func TestTimestamptzArrayTranscode(t *testing.T) {
testSuccessfulTranscodeEqFunc(t, "timestamptz[]", []interface{}{
&pgtype.TimestamptzArray{
Elements: nil,
Dimensions: nil,
Status: pgtype.Present,
},
&pgtype.TimestamptzArray{
Elements: []pgtype.Timestamptz{
pgtype.Timestamptz{Time: time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
pgtype.Timestamptz{Status: pgtype.Null},
},
Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}},
Status: pgtype.Present,
},
&pgtype.TimestamptzArray{Status: pgtype.Null},
&pgtype.TimestamptzArray{
Elements: []pgtype.Timestamptz{
pgtype.Timestamptz{Time: time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
pgtype.Timestamptz{Time: time.Date(2016, 2, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
pgtype.Timestamptz{Time: time.Date(2017, 2, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
pgtype.Timestamptz{Time: time.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
pgtype.Timestamptz{Status: pgtype.Null},
pgtype.Timestamptz{Time: time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
},
Dimensions: []pgtype.ArrayDimension{{Length: 3, LowerBound: 1}, {Length: 2, LowerBound: 1}},
Status: pgtype.Present,
},
&pgtype.TimestamptzArray{
Elements: []pgtype.Timestamptz{
pgtype.Timestamptz{Time: time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
pgtype.Timestamptz{Time: time.Date(2015, 2, 2, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
pgtype.Timestamptz{Time: time.Date(2015, 2, 3, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
pgtype.Timestamptz{Time: time.Date(2015, 2, 4, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
},
Dimensions: []pgtype.ArrayDimension{
{Length: 2, LowerBound: 4},
{Length: 2, LowerBound: 2},
},
Status: pgtype.Present,
},
}, func(a, b interface{}) bool {
ata := a.(pgtype.TimestamptzArray)
bta := b.(pgtype.TimestamptzArray)
if len(ata.Elements) != len(bta.Elements) || ata.Status != bta.Status {
return false
}
for i := range ata.Elements {
ae, be := ata.Elements[i], bta.Elements[i]
if !(ae.Time.Equal(be.Time) && ae.Status == be.Status && ae.InfinityModifier == be.InfinityModifier) {
return false
}
}
return true
})
}
func TestTimestamptzArrayConvertFrom(t *testing.T) {
successfulTests := []struct {
source interface{}
result pgtype.TimestamptzArray
}{
{
source: []time.Time{time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC)},
result: pgtype.TimestamptzArray{
Elements: []pgtype.Timestamptz{{Time: time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
Status: pgtype.Present},
},
{
source: (([]time.Time)(nil)),
result: pgtype.TimestamptzArray{Status: pgtype.Null},
},
}
for i, tt := range successfulTests {
var r pgtype.TimestamptzArray
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 TestTimestamptzArrayAssignTo(t *testing.T) {
var timeSlice []time.Time
simpleTests := []struct {
src pgtype.TimestamptzArray
dst interface{}
expected interface{}
}{
{
src: pgtype.TimestamptzArray{
Elements: []pgtype.Timestamptz{{Time: time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
Status: pgtype.Present,
},
dst: &timeSlice,
expected: []time.Time{time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC)},
},
{
src: pgtype.TimestamptzArray{Status: pgtype.Null},
dst: &timeSlice,
expected: (([]time.Time)(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.TimestamptzArray
dst interface{}
}{
{
src: pgtype.TimestamptzArray{
Elements: []pgtype.Timestamptz{{Status: pgtype.Null}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
Status: pgtype.Present,
},
dst: &timeSlice,
},
}
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)
}
}
}

286
pgtype/typed_array.go.erb Normal file
View File

@ -0,0 +1,286 @@
package pgtype
import (
"bytes"
"fmt"
"io"
"github.com/jackc/pgx/pgio"
)
type <%= pgtype_array_type %> struct {
Elements []<%= pgtype_element_type %>
Dimensions []ArrayDimension
Status Status
}
func (dst *<%= pgtype_array_type %>) ConvertFrom(src interface{}) error {
switch value := src.(type) {
case <%= pgtype_array_type %>:
*dst = value
<% go_array_types.split(",").each do |t| %>
case <%= t %>:
if value == nil {
*dst = <%= pgtype_array_type %>{Status: Null}
} else if len(value) == 0 {
*dst = <%= pgtype_array_type %>{Status: Present}
} else {
elements := make([]<%= pgtype_element_type %>, len(value))
for i := range value {
if err := elements[i].ConvertFrom(value[i]); err != nil {
return err
}
}
*dst = <%= pgtype_array_type %>{
Elements: elements,
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
Status: Present,
}
}
<% end %>
default:
if originalSrc, ok := underlyingSliceType(src); ok {
return dst.ConvertFrom(originalSrc)
}
return fmt.Errorf("cannot convert %v to <%= pgtype_element_type %>", value)
}
return nil
}
func (src *<%= pgtype_array_type %>) AssignTo(dst interface{}) error {
switch v := dst.(type) {
<% go_array_types.split(",").each do |t| %>
case *<%= t %>:
if src.Status == Present {
*v = make(<%= t %>, len(src.Elements))
for i := range src.Elements {
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
return err
}
}
} else {
*v = nil
}
<% end %>
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 *<%= pgtype_array_type %>) DecodeText(r io.Reader) error {
size, err := pgio.ReadInt32(r)
if err != nil {
return err
}
if size == -1 {
*dst = <%= pgtype_array_type %>{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 []<%= pgtype_element_type %>
if len(uta.Elements) > 0 {
elements = make([]<%= pgtype_element_type %>, len(uta.Elements))
for i, s := range uta.Elements {
var elem <%= pgtype_element_type %>
textElementReader.Reset(s)
err = elem.DecodeText(textElementReader)
if err != nil {
return err
}
elements[i] = elem
}
}
*dst = <%= pgtype_array_type %>{Elements: elements, Dimensions: uta.Dimensions, Status: Present}
return nil
}
func (dst *<%= pgtype_array_type %>) DecodeBinary(r io.Reader) error {
size, err := pgio.ReadInt32(r)
if err != nil {
return err
}
if size == -1 {
*dst = <%= pgtype_array_type %>{Status: Null}
return nil
}
var arrayHeader ArrayHeader
err = arrayHeader.DecodeBinary(r)
if err != nil {
return err
}
if len(arrayHeader.Dimensions) == 0 {
*dst = <%= pgtype_array_type %>{Dimensions: arrayHeader.Dimensions, Status: Present}
return nil
}
elementCount := arrayHeader.Dimensions[0].Length
for _, d := range arrayHeader.Dimensions[1:] {
elementCount *= d.Length
}
elements := make([]<%= pgtype_element_type %>, elementCount)
for i := range elements {
err = elements[i].DecodeBinary(r)
if err != nil {
return err
}
}
*dst = <%= pgtype_array_type %>{Elements: elements, Dimensions: arrayHeader.Dimensions, Status: Present}
return nil
}
func (src *<%= pgtype_array_type %>) 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 *<%= pgtype_array_type %>) 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 = <%= element_oid %>
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
}

View File

@ -0,0 +1,6 @@
erb pgtype_array_type=Int2Array pgtype_element_type=Int2 go_array_types=[]int16,[]uint16 element_oid=Int2OID typed_array.go.erb > int2array.go
erb pgtype_array_type=Int4Array pgtype_element_type=Int4 go_array_types=[]int32,[]uint32 element_oid=Int4OID typed_array.go.erb > int4array.go
erb pgtype_array_type=Int8Array pgtype_element_type=Int8 go_array_types=[]int64,[]uint64 element_oid=Int8OID typed_array.go.erb > int2array.go
erb pgtype_array_type=BoolArray pgtype_element_type=Bool go_array_types=[]bool element_oid=BoolOID typed_array.go.erb > boolarray.go
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

View File

@ -1063,9 +1063,6 @@ func TestQueryRowCoreInt32Slice(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)
}
@ -1110,9 +1107,6 @@ func TestQueryRowCoreInt64Slice(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)
}

View File

@ -54,6 +54,7 @@ const (
DateOID = 1082
TimestampOID = 1114
TimestampArrayOID = 1115
DateArrayOID = 1182
TimestampTzOID = 1184
TimestampTzArrayOID = 1185
RecordOID = 2249
@ -1087,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 []int32:
return encodeInt32Slice(wbuf, oid, arg)
case []uint32:
return encodeUInt32Slice(wbuf, oid, arg)
case []int64:
return encodeInt64Slice(wbuf, oid, arg)
case []uint64:
return encodeUInt64Slice(wbuf, oid, arg)
case float32:
return encodeFloat32(wbuf, oid, arg)
case []float32:
@ -1212,16 +1205,6 @@ func Decode(vr *ValueReader, d interface{}) error {
*v = decodeFloat8(vr)
case *[]AclItem:
*v = decodeAclItemArray(vr)
case *[]bool:
*v = decodeBoolArray(vr)
case *[]int32:
*v = decodeInt4Array(vr)
case *[]uint32:
*v = decodeInt4ArrayToUInt(vr)
case *[]int64:
*v = decodeInt8Array(vr)
case *[]uint64:
*v = decodeInt8ArrayToUInt(vr)
case *[]float32:
*v = decodeFloat4Array(vr)
case *[]float64: