mirror of https://github.com/jackc/pgx.git
Add timestamp to pgtype
parent
ffb949054d
commit
3179e2debc
14
conn.go
14
conn.go
|
@ -279,18 +279,20 @@ func (c *Conn) connect(config ConnConfig, network, address string, tlsConfig *tl
|
||||||
c.closedChan = make(chan error)
|
c.closedChan = make(chan error)
|
||||||
|
|
||||||
c.oidPgtypeValues = map[OID]pgtype.Value{
|
c.oidPgtypeValues = map[OID]pgtype.Value{
|
||||||
BoolOID: &pgtype.Bool{},
|
|
||||||
BoolArrayOID: &pgtype.BoolArray{},
|
BoolArrayOID: &pgtype.BoolArray{},
|
||||||
DateOID: &pgtype.Date{},
|
BoolOID: &pgtype.Bool{},
|
||||||
DateArrayOID: &pgtype.DateArray{},
|
DateArrayOID: &pgtype.DateArray{},
|
||||||
Int2OID: &pgtype.Int2{},
|
DateOID: &pgtype.Date{},
|
||||||
Int2ArrayOID: &pgtype.Int2Array{},
|
Int2ArrayOID: &pgtype.Int2Array{},
|
||||||
Int4OID: &pgtype.Int4{},
|
Int2OID: &pgtype.Int2{},
|
||||||
Int4ArrayOID: &pgtype.Int4Array{},
|
Int4ArrayOID: &pgtype.Int4Array{},
|
||||||
Int8OID: &pgtype.Int8{},
|
Int4OID: &pgtype.Int4{},
|
||||||
Int8ArrayOID: &pgtype.Int8Array{},
|
Int8ArrayOID: &pgtype.Int8Array{},
|
||||||
TimestampTzOID: &pgtype.Timestamptz{},
|
Int8OID: &pgtype.Int8{},
|
||||||
|
TimestampArrayOID: &pgtype.TimestampArray{},
|
||||||
|
TimestampOID: &pgtype.Timestamp{},
|
||||||
TimestampTzArrayOID: &pgtype.TimestamptzArray{},
|
TimestampTzArrayOID: &pgtype.TimestamptzArray{},
|
||||||
|
TimestampTzOID: &pgtype.Timestamptz{},
|
||||||
}
|
}
|
||||||
|
|
||||||
if tlsConfig != nil {
|
if tlsConfig != nil {
|
||||||
|
|
|
@ -0,0 +1,204 @@
|
||||||
|
package pgtype
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/pgio"
|
||||||
|
)
|
||||||
|
|
||||||
|
const pgTimestampFormat = "2006-01-02 15:04:05.999999999"
|
||||||
|
|
||||||
|
// Timestamp represents the PostgreSQL timestamp type. The PostgreSQL
|
||||||
|
// timestamp does not have a time zone. This presents a problem when
|
||||||
|
// translating to and from time.Time which requires a time zone. It is highly
|
||||||
|
// recommended to use timestamptz whenever possible. Timestamp methods either
|
||||||
|
// convert to UTC or return an error on non-UTC times.
|
||||||
|
type Timestamp struct {
|
||||||
|
Time time.Time // Time must always be in UTC.
|
||||||
|
Status Status
|
||||||
|
InfinityModifier
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertFrom converts src into a Timestamp and stores in dst. If src is a
|
||||||
|
// time.Time in a non-UTC time zone, the time zone is discarded.
|
||||||
|
func (dst *Timestamp) ConvertFrom(src interface{}) error {
|
||||||
|
switch value := src.(type) {
|
||||||
|
case Timestamp:
|
||||||
|
*dst = value
|
||||||
|
case time.Time:
|
||||||
|
*dst = Timestamp{Time: time.Date(value.Year(), value.Month(), value.Day(), value.Hour(), value.Minute(), value.Second(), value.Nanosecond(), time.UTC), Status: Present}
|
||||||
|
default:
|
||||||
|
if originalSrc, ok := underlyingTimeType(src); ok {
|
||||||
|
return dst.ConvertFrom(originalSrc)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("cannot convert %v to Timestamp", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (src *Timestamp) AssignTo(dst interface{}) error {
|
||||||
|
switch v := dst.(type) {
|
||||||
|
case *time.Time:
|
||||||
|
if src.Status != Present || src.InfinityModifier != None {
|
||||||
|
return fmt.Errorf("cannot assign %v to %T", src, dst)
|
||||||
|
}
|
||||||
|
*v = src.Time
|
||||||
|
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 src.Status == Null {
|
||||||
|
el.Set(reflect.Zero(el.Type()))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if el.IsNil() {
|
||||||
|
// allocate destination
|
||||||
|
el.Set(reflect.New(el.Type().Elem()))
|
||||||
|
}
|
||||||
|
return src.AssignTo(el.Interface())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("cannot assign %v into %T", src, dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeText decodes from src into dst. The decoded time is considered to
|
||||||
|
// be in UTC.
|
||||||
|
func (dst *Timestamp) DecodeText(r io.Reader) error {
|
||||||
|
size, err := pgio.ReadInt32(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if size == -1 {
|
||||||
|
*dst = Timestamp{Status: Null}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, int(size))
|
||||||
|
_, err = r.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sbuf := string(buf)
|
||||||
|
switch sbuf {
|
||||||
|
case "infinity":
|
||||||
|
*dst = Timestamp{Status: Present, InfinityModifier: Infinity}
|
||||||
|
case "-infinity":
|
||||||
|
*dst = Timestamp{Status: Present, InfinityModifier: -Infinity}
|
||||||
|
default:
|
||||||
|
tim, err := time.Parse(pgTimestampFormat, sbuf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*dst = Timestamp{Time: tim, Status: Present}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeBinary decodes from src into dst. The decoded time is considered to
|
||||||
|
// be in UTC.
|
||||||
|
func (dst *Timestamp) DecodeBinary(r io.Reader) error {
|
||||||
|
size, err := pgio.ReadInt32(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if size == -1 {
|
||||||
|
*dst = Timestamp{Status: Null}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if size != 8 {
|
||||||
|
return fmt.Errorf("invalid length for timestamp: %v", size)
|
||||||
|
}
|
||||||
|
|
||||||
|
microsecSinceY2K, err := pgio.ReadInt64(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch microsecSinceY2K {
|
||||||
|
case infinityMicrosecondOffset:
|
||||||
|
*dst = Timestamp{Status: Present, InfinityModifier: Infinity}
|
||||||
|
case negativeInfinityMicrosecondOffset:
|
||||||
|
*dst = Timestamp{Status: Present, InfinityModifier: -Infinity}
|
||||||
|
default:
|
||||||
|
microsecSinceUnixEpoch := microsecFromUnixEpochToY2K + microsecSinceY2K
|
||||||
|
tim := time.Unix(microsecSinceUnixEpoch/1000000, (microsecSinceUnixEpoch%1000000)*1000).UTC()
|
||||||
|
*dst = Timestamp{Time: tim, Status: Present}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeText writes the text encoding of src into w. If src.Time is not in
|
||||||
|
// the UTC time zone it returns an error.
|
||||||
|
func (src Timestamp) EncodeText(w io.Writer) error {
|
||||||
|
if done, err := encodeNotPresent(w, src.Status); done {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if src.Time.Location() != time.UTC {
|
||||||
|
return fmt.Errorf("cannot encode non-UTC time into timestamp")
|
||||||
|
}
|
||||||
|
|
||||||
|
var s string
|
||||||
|
|
||||||
|
switch src.InfinityModifier {
|
||||||
|
case None:
|
||||||
|
s = src.Time.Format(pgTimestampFormat)
|
||||||
|
case Infinity:
|
||||||
|
s = "infinity"
|
||||||
|
case NegativeInfinity:
|
||||||
|
s = "-infinity"
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := pgio.WriteInt32(w, int32(len(s)))
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = w.Write([]byte(s))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeBinary writes the binary encoding of src into w. If src.Time is not in
|
||||||
|
// the UTC time zone it returns an error.
|
||||||
|
func (src Timestamp) EncodeBinary(w io.Writer) error {
|
||||||
|
if done, err := encodeNotPresent(w, src.Status); done {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if src.Time.Location() != time.UTC {
|
||||||
|
return fmt.Errorf("cannot encode non-UTC time into timestamp")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := pgio.WriteInt32(w, 8)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var microsecSinceY2K int64
|
||||||
|
switch src.InfinityModifier {
|
||||||
|
case None:
|
||||||
|
microsecSinceUnixEpoch := src.Time.Unix()*1000000 + int64(src.Time.Nanosecond())/1000
|
||||||
|
microsecSinceY2K = microsecSinceUnixEpoch - microsecFromUnixEpochToY2K
|
||||||
|
case Infinity:
|
||||||
|
microsecSinceY2K = infinityMicrosecondOffset
|
||||||
|
case NegativeInfinity:
|
||||||
|
microsecSinceY2K = negativeInfinityMicrosecondOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = pgio.WriteInt64(w, microsecSinceY2K)
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,123 @@
|
||||||
|
package pgtype_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTimestampTranscode(t *testing.T) {
|
||||||
|
testSuccessfulTranscodeEqFunc(t, "timestamp", []interface{}{
|
||||||
|
pgtype.Timestamp{Time: time.Date(1800, 1, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
|
||||||
|
pgtype.Timestamp{Time: time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
|
||||||
|
pgtype.Timestamp{Time: time.Date(1905, 1, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
|
||||||
|
pgtype.Timestamp{Time: time.Date(1940, 1, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
|
||||||
|
pgtype.Timestamp{Time: time.Date(1960, 1, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
|
||||||
|
pgtype.Timestamp{Time: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
|
||||||
|
pgtype.Timestamp{Time: time.Date(1999, 12, 31, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
|
||||||
|
pgtype.Timestamp{Time: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
|
||||||
|
pgtype.Timestamp{Time: time.Date(2000, 1, 2, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
|
||||||
|
pgtype.Timestamp{Time: time.Date(2200, 1, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
|
||||||
|
pgtype.Timestamp{Status: pgtype.Null},
|
||||||
|
pgtype.Timestamp{Status: pgtype.Present, InfinityModifier: pgtype.Infinity},
|
||||||
|
pgtype.Timestamp{Status: pgtype.Present, InfinityModifier: -pgtype.Infinity},
|
||||||
|
}, func(a, b interface{}) bool {
|
||||||
|
at := a.(pgtype.Timestamp)
|
||||||
|
bt := b.(pgtype.Timestamp)
|
||||||
|
|
||||||
|
return at.Time.Equal(bt.Time) && at.Status == bt.Status && at.InfinityModifier == bt.InfinityModifier
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTimestampConvertFrom(t *testing.T) {
|
||||||
|
type _time time.Time
|
||||||
|
|
||||||
|
successfulTests := []struct {
|
||||||
|
source interface{}
|
||||||
|
result pgtype.Timestamp
|
||||||
|
}{
|
||||||
|
{source: pgtype.Timestamp{Time: time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present}, result: pgtype.Timestamp{Time: time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present}},
|
||||||
|
{source: time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC), result: pgtype.Timestamp{Time: time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present}},
|
||||||
|
{source: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC), result: pgtype.Timestamp{Time: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present}},
|
||||||
|
{source: time.Date(1999, 12, 31, 12, 59, 59, 0, time.UTC), result: pgtype.Timestamp{Time: time.Date(1999, 12, 31, 12, 59, 59, 0, time.UTC), Status: pgtype.Present}},
|
||||||
|
{source: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), result: pgtype.Timestamp{Time: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present}},
|
||||||
|
{source: time.Date(2000, 1, 1, 0, 0, 1, 0, time.UTC), result: pgtype.Timestamp{Time: time.Date(2000, 1, 1, 0, 0, 1, 0, time.UTC), Status: pgtype.Present}},
|
||||||
|
{source: time.Date(2200, 1, 1, 0, 0, 0, 0, time.UTC), result: pgtype.Timestamp{Time: time.Date(2200, 1, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present}},
|
||||||
|
{source: time.Date(2015, 1, 1, 0, 0, 0, 0, time.Local), result: pgtype.Timestamp{Time: time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present}},
|
||||||
|
{source: _time(time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)), result: pgtype.Timestamp{Time: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range successfulTests {
|
||||||
|
var r pgtype.Timestamp
|
||||||
|
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 TestTimestampAssignTo(t *testing.T) {
|
||||||
|
var tim time.Time
|
||||||
|
var ptim *time.Time
|
||||||
|
|
||||||
|
simpleTests := []struct {
|
||||||
|
src pgtype.Timestamp
|
||||||
|
dst interface{}
|
||||||
|
expected interface{}
|
||||||
|
}{
|
||||||
|
{src: pgtype.Timestamp{Time: time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present}, dst: &tim, expected: time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)},
|
||||||
|
{src: pgtype.Timestamp{Time: time.Time{}, Status: pgtype.Null}, dst: &ptim, 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(); 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.Timestamp
|
||||||
|
dst interface{}
|
||||||
|
expected interface{}
|
||||||
|
}{
|
||||||
|
{src: pgtype.Timestamp{Time: time.Date(2015, 1, 1, 0, 0, 0, 0, time.Local), Status: pgtype.Present}, dst: &ptim, expected: time.Date(2015, 1, 1, 0, 0, 0, 0, time.Local)},
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Timestamp
|
||||||
|
dst interface{}
|
||||||
|
}{
|
||||||
|
{src: pgtype.Timestamp{Time: time.Date(2015, 1, 1, 0, 0, 0, 0, time.Local), InfinityModifier: pgtype.Infinity, Status: pgtype.Present}, dst: &tim},
|
||||||
|
{src: pgtype.Timestamp{Time: time.Date(2015, 1, 1, 0, 0, 0, 0, time.Local), InfinityModifier: pgtype.NegativeInfinity, Status: pgtype.Present}, dst: &tim},
|
||||||
|
{src: pgtype.Timestamp{Time: time.Date(2015, 1, 1, 0, 0, 0, 0, time.Local), Status: pgtype.Null}, dst: &tim},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range errorTests {
|
||||||
|
err := tt.src.AssignTo(tt.dst)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("%d: expected error but none was returned (%v -> %v)", i, tt.src, tt.dst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,287 @@
|
||||||
|
package pgtype
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/pgio"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TimestampArray struct {
|
||||||
|
Elements []Timestamp
|
||||||
|
Dimensions []ArrayDimension
|
||||||
|
Status Status
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dst *TimestampArray) ConvertFrom(src interface{}) error {
|
||||||
|
switch value := src.(type) {
|
||||||
|
case TimestampArray:
|
||||||
|
*dst = value
|
||||||
|
|
||||||
|
case []time.Time:
|
||||||
|
if value == nil {
|
||||||
|
*dst = TimestampArray{Status: Null}
|
||||||
|
} else if len(value) == 0 {
|
||||||
|
*dst = TimestampArray{Status: Present}
|
||||||
|
} else {
|
||||||
|
elements := make([]Timestamp, len(value))
|
||||||
|
for i := range value {
|
||||||
|
if err := elements[i].ConvertFrom(value[i]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*dst = TimestampArray{
|
||||||
|
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 Timestamp", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (src *TimestampArray) 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 *TimestampArray) DecodeText(r io.Reader) error {
|
||||||
|
size, err := pgio.ReadInt32(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if size == -1 {
|
||||||
|
*dst = TimestampArray{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 []Timestamp
|
||||||
|
|
||||||
|
if len(uta.Elements) > 0 {
|
||||||
|
elements = make([]Timestamp, len(uta.Elements))
|
||||||
|
|
||||||
|
for i, s := range uta.Elements {
|
||||||
|
var elem Timestamp
|
||||||
|
textElementReader.Reset(s)
|
||||||
|
err = elem.DecodeText(textElementReader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
elements[i] = elem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*dst = TimestampArray{Elements: elements, Dimensions: uta.Dimensions, Status: Present}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dst *TimestampArray) DecodeBinary(r io.Reader) error {
|
||||||
|
size, err := pgio.ReadInt32(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if size == -1 {
|
||||||
|
*dst = TimestampArray{Status: Null}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var arrayHeader ArrayHeader
|
||||||
|
err = arrayHeader.DecodeBinary(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(arrayHeader.Dimensions) == 0 {
|
||||||
|
*dst = TimestampArray{Dimensions: arrayHeader.Dimensions, Status: Present}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
elementCount := arrayHeader.Dimensions[0].Length
|
||||||
|
for _, d := range arrayHeader.Dimensions[1:] {
|
||||||
|
elementCount *= d.Length
|
||||||
|
}
|
||||||
|
|
||||||
|
elements := make([]Timestamp, elementCount)
|
||||||
|
|
||||||
|
for i := range elements {
|
||||||
|
err = elements[i].DecodeBinary(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*dst = TimestampArray{Elements: elements, Dimensions: arrayHeader.Dimensions, Status: Present}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (src *TimestampArray) 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 *TimestampArray) 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 = TimestampOID
|
||||||
|
arrayHeader.Dimensions = src.Dimensions
|
||||||
|
|
||||||
|
// TODO - consider how to avoid having to buffer array before writing length -
|
||||||
|
// or how not pay allocations for the byte order conversions.
|
||||||
|
headerBuf := &bytes.Buffer{}
|
||||||
|
err := arrayHeader.EncodeBinary(headerBuf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = pgio.WriteInt32(w, int32(headerBuf.Len()+elemBuf.Len()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = headerBuf.WriteTo(w)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = elemBuf.WriteTo(w)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,158 @@
|
||||||
|
package pgtype_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTimestampArrayTranscode(t *testing.T) {
|
||||||
|
testSuccessfulTranscodeEqFunc(t, "timestamp[]", []interface{}{
|
||||||
|
&pgtype.TimestampArray{
|
||||||
|
Elements: nil,
|
||||||
|
Dimensions: nil,
|
||||||
|
Status: pgtype.Present,
|
||||||
|
},
|
||||||
|
&pgtype.TimestampArray{
|
||||||
|
Elements: []pgtype.Timestamp{
|
||||||
|
pgtype.Timestamp{Time: time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
|
||||||
|
pgtype.Timestamp{Status: pgtype.Null},
|
||||||
|
},
|
||||||
|
Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}},
|
||||||
|
Status: pgtype.Present,
|
||||||
|
},
|
||||||
|
&pgtype.TimestampArray{Status: pgtype.Null},
|
||||||
|
&pgtype.TimestampArray{
|
||||||
|
Elements: []pgtype.Timestamp{
|
||||||
|
pgtype.Timestamp{Time: time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
|
||||||
|
pgtype.Timestamp{Time: time.Date(2016, 2, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
|
||||||
|
pgtype.Timestamp{Time: time.Date(2017, 2, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
|
||||||
|
pgtype.Timestamp{Time: time.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
|
||||||
|
pgtype.Timestamp{Status: pgtype.Null},
|
||||||
|
pgtype.Timestamp{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.TimestampArray{
|
||||||
|
Elements: []pgtype.Timestamp{
|
||||||
|
pgtype.Timestamp{Time: time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
|
||||||
|
pgtype.Timestamp{Time: time.Date(2015, 2, 2, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
|
||||||
|
pgtype.Timestamp{Time: time.Date(2015, 2, 3, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
|
||||||
|
pgtype.Timestamp{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.TimestampArray)
|
||||||
|
bta := b.(pgtype.TimestampArray)
|
||||||
|
|
||||||
|
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 TestTimestampArrayConvertFrom(t *testing.T) {
|
||||||
|
successfulTests := []struct {
|
||||||
|
source interface{}
|
||||||
|
result pgtype.TimestampArray
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
source: []time.Time{time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC)},
|
||||||
|
result: pgtype.TimestampArray{
|
||||||
|
Elements: []pgtype.Timestamp{{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.TimestampArray{Status: pgtype.Null},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range successfulTests {
|
||||||
|
var r pgtype.TimestampArray
|
||||||
|
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 TestTimestampArrayAssignTo(t *testing.T) {
|
||||||
|
var timeSlice []time.Time
|
||||||
|
|
||||||
|
simpleTests := []struct {
|
||||||
|
src pgtype.TimestampArray
|
||||||
|
dst interface{}
|
||||||
|
expected interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
src: pgtype.TimestampArray{
|
||||||
|
Elements: []pgtype.Timestamp{{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.TimestampArray{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.TimestampArray
|
||||||
|
dst interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
src: pgtype.TimestampArray{
|
||||||
|
Elements: []pgtype.Timestamp{{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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -4,3 +4,4 @@ erb pgtype_array_type=Int8Array pgtype_element_type=Int8 go_array_types=[]int64,
|
||||||
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=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=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=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
|
||||||
|
|
|
@ -516,7 +516,7 @@ func TestQueryRowCoreTypes(t *testing.T) {
|
||||||
{"select $1::float8", []interface{}{float64(1.23)}, []interface{}{&actual.f64}, allTypes{f64: 1.23}},
|
{"select $1::float8", []interface{}{float64(1.23)}, []interface{}{&actual.f64}, allTypes{f64: 1.23}},
|
||||||
{"select $1::bool", []interface{}{true}, []interface{}{&actual.b}, allTypes{b: true}},
|
{"select $1::bool", []interface{}{true}, []interface{}{&actual.b}, allTypes{b: true}},
|
||||||
{"select $1::timestamptz", []interface{}{time.Unix(123, 5000)}, []interface{}{&actual.t}, allTypes{t: time.Unix(123, 5000)}},
|
{"select $1::timestamptz", []interface{}{time.Unix(123, 5000)}, []interface{}{&actual.t}, allTypes{t: time.Unix(123, 5000)}},
|
||||||
{"select $1::timestamp", []interface{}{time.Date(2010, 1, 2, 3, 4, 5, 0, time.Local)}, []interface{}{&actual.t}, allTypes{t: time.Date(2010, 1, 2, 3, 4, 5, 0, time.Local)}},
|
{"select $1::timestamp", []interface{}{time.Date(2010, 1, 2, 3, 4, 5, 0, time.UTC)}, []interface{}{&actual.t}, allTypes{t: time.Date(2010, 1, 2, 3, 4, 5, 0, time.UTC)}},
|
||||||
{"select $1::date", []interface{}{time.Date(1987, 1, 2, 0, 0, 0, 0, time.UTC)}, []interface{}{&actual.t}, allTypes{t: time.Date(1987, 1, 2, 0, 0, 0, 0, time.UTC)}},
|
{"select $1::date", []interface{}{time.Date(1987, 1, 2, 0, 0, 0, 0, time.UTC)}, []interface{}{&actual.t}, allTypes{t: time.Date(1987, 1, 2, 0, 0, 0, 0, time.UTC)}},
|
||||||
{"select $1::oid", []interface{}{pgx.OID(42)}, []interface{}{&actual.oid}, allTypes{oid: 42}},
|
{"select $1::oid", []interface{}{pgx.OID(42)}, []interface{}{&actual.oid}, allTypes{oid: 42}},
|
||||||
}
|
}
|
||||||
|
|
13
values.go
13
values.go
|
@ -1096,10 +1096,6 @@ func Encode(wbuf *WriteBuf, oid OID, arg interface{}) error {
|
||||||
return encodeFloat64(wbuf, oid, arg)
|
return encodeFloat64(wbuf, oid, arg)
|
||||||
case []float64:
|
case []float64:
|
||||||
return encodeFloat64Slice(wbuf, oid, arg)
|
return encodeFloat64Slice(wbuf, oid, arg)
|
||||||
case time.Time:
|
|
||||||
return encodeTime(wbuf, oid, arg)
|
|
||||||
case []time.Time:
|
|
||||||
return encodeTimeSlice(wbuf, oid, arg)
|
|
||||||
case net.IP:
|
case net.IP:
|
||||||
return encodeIP(wbuf, oid, arg)
|
return encodeIP(wbuf, oid, arg)
|
||||||
case []net.IP:
|
case []net.IP:
|
||||||
|
@ -1211,19 +1207,10 @@ func Decode(vr *ValueReader, d interface{}) error {
|
||||||
*v = decodeFloat8Array(vr)
|
*v = decodeFloat8Array(vr)
|
||||||
case *[]string:
|
case *[]string:
|
||||||
*v = decodeTextArray(vr)
|
*v = decodeTextArray(vr)
|
||||||
case *[]time.Time:
|
|
||||||
*v = decodeTimestampArray(vr)
|
|
||||||
case *[][]byte:
|
case *[][]byte:
|
||||||
*v = decodeByteaArray(vr)
|
*v = decodeByteaArray(vr)
|
||||||
case *[]interface{}:
|
case *[]interface{}:
|
||||||
*v = decodeRecord(vr)
|
*v = decodeRecord(vr)
|
||||||
case *time.Time:
|
|
||||||
switch vr.Type().DataType {
|
|
||||||
case TimestampOID:
|
|
||||||
*v = decodeTimestamp(vr)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("Can't convert OID %v to time.Time", vr.Type().DataType)
|
|
||||||
}
|
|
||||||
case *net.IP:
|
case *net.IP:
|
||||||
ipnet := decodeInet(vr)
|
ipnet := decodeInet(vr)
|
||||||
if oneCount, bitCount := ipnet.Mask.Size(); oneCount != bitCount {
|
if oneCount, bitCount := ipnet.Mask.Size(); oneCount != bitCount {
|
||||||
|
|
|
@ -772,14 +772,6 @@ func TestArrayDecoding(t *testing.T) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"select $1::timestamp[]", []time.Time{time.Unix(323232, 0), time.Unix(3239949334, 00)}, &[]time.Time{},
|
|
||||||
func(t *testing.T, query, scan interface{}) {
|
|
||||||
if !reflect.DeepEqual(query, *(scan.(*[]time.Time))) {
|
|
||||||
t.Errorf("failed to encode time.Time[] to timestamp[]")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"select $1::timestamptz[]", []time.Time{time.Unix(323232, 0), time.Unix(3239949334, 00)}, &[]time.Time{},
|
"select $1::timestamptz[]", []time.Time{time.Unix(323232, 0), time.Unix(3239949334, 00)}, &[]time.Time{},
|
||||||
func(t *testing.T, query, scan interface{}) {
|
func(t *testing.T, query, scan interface{}) {
|
||||||
|
@ -1003,8 +995,6 @@ func TestPointerPointer(t *testing.T) {
|
||||||
{"select $1::bool", []interface{}{zero.b}, []interface{}{&actual.b}, allTypes{}},
|
{"select $1::bool", []interface{}{zero.b}, []interface{}{&actual.b}, allTypes{}},
|
||||||
{"select $1::timestamptz", []interface{}{expected.t}, []interface{}{&actual.t}, allTypes{t: expected.t}},
|
{"select $1::timestamptz", []interface{}{expected.t}, []interface{}{&actual.t}, allTypes{t: expected.t}},
|
||||||
{"select $1::timestamptz", []interface{}{zero.t}, []interface{}{&actual.t}, allTypes{}},
|
{"select $1::timestamptz", []interface{}{zero.t}, []interface{}{&actual.t}, allTypes{}},
|
||||||
{"select $1::timestamp", []interface{}{expected.t}, []interface{}{&actual.t}, allTypes{t: expected.t}},
|
|
||||||
{"select $1::timestamp", []interface{}{zero.t}, []interface{}{&actual.t}, allTypes{}},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, tt := range tests {
|
for i, tt := range tests {
|
||||||
|
|
Loading…
Reference in New Issue