mirror of https://github.com/jackc/pgx.git
Add infinity to pgtype.Date
parent
7bf783ae20
commit
7d9f954de4
|
@ -26,7 +26,7 @@ func TestConnCopyToSmall(t *testing.T) {
|
|||
)`)
|
||||
|
||||
inputRows := [][]interface{}{
|
||||
{int16(0), int32(1), int64(2), "abc", "efg", time.Date(2000, 1, 1, 0, 0, 0, 0, time.Local), time.Date(2010, 2, 3, 4, 5, 6, 0, time.Local)},
|
||||
{int16(0), int32(1), int64(2), "abc", "efg", time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(2010, 2, 3, 4, 5, 6, 0, time.Local)},
|
||||
{nil, nil, nil, nil, nil, nil, nil},
|
||||
}
|
||||
|
||||
|
@ -83,7 +83,7 @@ func TestConnCopyToLarge(t *testing.T) {
|
|||
inputRows := [][]interface{}{}
|
||||
|
||||
for i := 0; i < 10000; i++ {
|
||||
inputRows = append(inputRows, []interface{}{int16(0), int32(1), int64(2), "abc", "efg", time.Date(2000, 1, 1, 0, 0, 0, 0, time.Local), time.Date(2010, 2, 3, 4, 5, 6, 0, time.Local), []byte{111, 111, 111, 111}})
|
||||
inputRows = append(inputRows, []interface{}{int16(0), int32(1), int64(2), "abc", "efg", time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(2010, 2, 3, 4, 5, 6, 0, time.Local), []byte{111, 111, 111, 111}})
|
||||
}
|
||||
|
||||
copyCount, err := conn.CopyTo("foo", []string{"a", "b", "c", "d", "e", "f", "g", "h"}, pgx.CopyToRows(inputRows))
|
||||
|
|
|
@ -9,17 +9,22 @@ import (
|
|||
)
|
||||
|
||||
type Date struct {
|
||||
// TODO handling Infinity and -Infinity
|
||||
Time time.Time
|
||||
Status Status
|
||||
InfinityModifier
|
||||
}
|
||||
|
||||
const (
|
||||
negativeInfinityDayOffset = -2147483648
|
||||
infinityDayOffset = 2147483647
|
||||
)
|
||||
|
||||
func (d *Date) ConvertFrom(src interface{}) error {
|
||||
switch value := src.(type) {
|
||||
case Date:
|
||||
*d = value
|
||||
case time.Time:
|
||||
*d = Date{Time: value}
|
||||
*d = Date{Time: value, Status: Present}
|
||||
default:
|
||||
if originalSrc, ok := underlyingTimeType(src); ok {
|
||||
return d.ConvertFrom(originalSrc)
|
||||
|
@ -51,12 +56,20 @@ func (d *Date) DecodeText(r io.Reader) error {
|
|||
return err
|
||||
}
|
||||
|
||||
t, err := time.ParseInLocation("2006-01-02", string(buf), time.UTC)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sbuf := string(buf)
|
||||
switch sbuf {
|
||||
case "infinity":
|
||||
*d = Date{Status: Present, InfinityModifier: Infinity}
|
||||
case "-infinity":
|
||||
*d = Date{Status: Present, InfinityModifier: -Infinity}
|
||||
default:
|
||||
t, err := time.ParseInLocation("2006-01-02", sbuf, time.UTC)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*d = Date{Time: t}
|
||||
*d = Date{Time: t, Status: Present}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -81,9 +94,15 @@ func (d *Date) DecodeBinary(r io.Reader) error {
|
|||
return err
|
||||
}
|
||||
|
||||
t := time.Date(2000, 1, int(1+dayOffset), 0, 0, 0, 0, time.UTC)
|
||||
|
||||
*d = Date{Time: t}
|
||||
switch dayOffset {
|
||||
case infinityDayOffset:
|
||||
*d = Date{Status: Present, InfinityModifier: Infinity}
|
||||
case negativeInfinityDayOffset:
|
||||
*d = Date{Status: Present, InfinityModifier: -Infinity}
|
||||
default:
|
||||
t := time.Date(2000, 1, int(1+dayOffset), 0, 0, 0, 0, time.UTC)
|
||||
*d = Date{Time: t, Status: Present}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -93,12 +112,23 @@ func (d Date) EncodeText(w io.Writer) error {
|
|||
return err
|
||||
}
|
||||
|
||||
_, err := pgio.WriteInt32(w, 10)
|
||||
var s string
|
||||
|
||||
switch d.InfinityModifier {
|
||||
case None:
|
||||
s = d.Time.Format("2006-01-02")
|
||||
case Infinity:
|
||||
s = "infinity"
|
||||
case NegativeInfinity:
|
||||
s = "-infinity"
|
||||
}
|
||||
|
||||
_, err := pgio.WriteInt32(w, int32(len(s)))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = w.Write([]byte(d.Time.Format("2006-01-02")))
|
||||
_, err = w.Write([]byte(s))
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -112,12 +142,20 @@ func (d Date) EncodeBinary(w io.Writer) error {
|
|||
return err
|
||||
}
|
||||
|
||||
tUnix := time.Date(d.Time.Year(), d.Time.Month(), d.Time.Day(), 0, 0, 0, 0, time.UTC).Unix()
|
||||
dateEpoch := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC).Unix()
|
||||
var daysSinceDateEpoch int32
|
||||
switch d.InfinityModifier {
|
||||
case None:
|
||||
tUnix := time.Date(d.Time.Year(), d.Time.Month(), d.Time.Day(), 0, 0, 0, 0, time.UTC).Unix()
|
||||
dateEpoch := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC).Unix()
|
||||
|
||||
secSinceDateEpoch := tUnix - dateEpoch
|
||||
daysSinceDateEpoch := secSinceDateEpoch / 86400
|
||||
secSinceDateEpoch := tUnix - dateEpoch
|
||||
daysSinceDateEpoch = int32(secSinceDateEpoch / 86400)
|
||||
case Infinity:
|
||||
daysSinceDateEpoch = infinityDayOffset
|
||||
case NegativeInfinity:
|
||||
daysSinceDateEpoch = negativeInfinityDayOffset
|
||||
}
|
||||
|
||||
_, err = pgio.WriteInt32(w, int32(daysSinceDateEpoch))
|
||||
_, err = pgio.WriteInt32(w, daysSinceDateEpoch)
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -16,6 +16,8 @@ func TestDateTranscode(t *testing.T) {
|
|||
pgtype.Date{Time: time.Date(2000, 1, 2, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
|
||||
pgtype.Date{Time: time.Date(2200, 1, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
|
||||
pgtype.Date{Status: pgtype.Null},
|
||||
pgtype.Date{Status: pgtype.Present, InfinityModifier: pgtype.Infinity},
|
||||
pgtype.Date{Status: pgtype.Present, InfinityModifier: -pgtype.Infinity},
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -26,13 +28,13 @@ func TestDateConvertFrom(t *testing.T) {
|
|||
source interface{}
|
||||
result *pgtype.Date
|
||||
}{
|
||||
{source: time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC), result: &pgtype.Date{Time: time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC)}},
|
||||
{source: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC), result: &pgtype.Date{Time: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)}},
|
||||
{source: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC), result: &pgtype.Date{Time: time.Date(1999, 12, 31, 0, 0, 0, 0, time.UTC)}},
|
||||
{source: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC), result: &pgtype.Date{Time: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)}},
|
||||
{source: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC), result: &pgtype.Date{Time: time.Date(2000, 1, 2, 0, 0, 0, 0, time.UTC)}},
|
||||
{source: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC), result: &pgtype.Date{Time: time.Date(2200, 1, 1, 0, 0, 0, 0, time.UTC)}},
|
||||
{source: _time(time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)), result: &pgtype.Date{Time: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)}},
|
||||
{source: time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC), result: &pgtype.Date{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.Date{Time: time.Date(1970, 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.Date{Time: time.Date(1999, 12, 31, 0, 0, 0, 0, time.UTC), Status: pgtype.Present}},
|
||||
{source: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC), result: &pgtype.Date{Time: time.Date(2000, 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.Date{Time: time.Date(2000, 1, 2, 0, 0, 0, 0, time.UTC), Status: pgtype.Present}},
|
||||
{source: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC), result: &pgtype.Date{Time: time.Date(2200, 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.Date{Time: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present}},
|
||||
}
|
||||
|
||||
for i, tt := range successfulTests {
|
||||
|
|
|
@ -15,6 +15,14 @@ const (
|
|||
Present
|
||||
)
|
||||
|
||||
type InfinityModifier int8
|
||||
|
||||
const (
|
||||
Infinity InfinityModifier = 1
|
||||
None InfinityModifier = 0
|
||||
NegativeInfinity InfinityModifier = -Infinity
|
||||
)
|
||||
|
||||
type Value interface {
|
||||
ConvertFrom(src interface{}) error
|
||||
AssignTo(dst interface{}) error
|
||||
|
|
|
@ -2,11 +2,13 @@ package pgtype_test
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgx"
|
||||
"github.com/jackc/pgx/pgtype"
|
||||
)
|
||||
|
||||
func mustConnectPgx(t testing.TB) *pgx.Conn {
|
||||
|
@ -32,6 +34,33 @@ func mustClose(t testing.TB, conn interface {
|
|||
}
|
||||
}
|
||||
|
||||
type forceTextEncoder struct {
|
||||
e pgtype.TextEncoder
|
||||
}
|
||||
|
||||
func (f forceTextEncoder) EncodeText(w io.Writer) error {
|
||||
return f.e.EncodeText(w)
|
||||
}
|
||||
|
||||
type forceBinaryEncoder struct {
|
||||
e pgtype.BinaryEncoder
|
||||
}
|
||||
|
||||
func (f forceBinaryEncoder) EncodeBinary(w io.Writer) error {
|
||||
return f.e.EncodeBinary(w)
|
||||
}
|
||||
|
||||
func forceEncoder(e interface{}, formatCode int16) interface{} {
|
||||
switch formatCode {
|
||||
case pgx.TextFormatCode:
|
||||
return forceTextEncoder{e: e.(pgtype.TextEncoder)}
|
||||
case pgx.BinaryFormatCode:
|
||||
return forceBinaryEncoder{e: e.(pgtype.BinaryEncoder)}
|
||||
default:
|
||||
panic("bad encoder")
|
||||
}
|
||||
}
|
||||
|
||||
func testSuccessfulTranscode(t testing.TB, pgTypeName string, values []interface{}) {
|
||||
conn := mustConnectPgx(t)
|
||||
defer mustClose(t, conn)
|
||||
|
@ -53,12 +82,12 @@ func testSuccessfulTranscode(t testing.TB, pgTypeName string, values []interface
|
|||
ps.FieldDescriptions[0].FormatCode = fc.formatCode
|
||||
for i, v := range values {
|
||||
result := reflect.New(reflect.TypeOf(v))
|
||||
err := conn.QueryRow("test", v).Scan(result.Interface())
|
||||
err := conn.QueryRow("test", forceEncoder(v, fc.formatCode)).Scan(result.Interface())
|
||||
if err != nil {
|
||||
t.Errorf("%v %d: %v", fc.name, i, err)
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(result.Interface(), v) {
|
||||
if !reflect.DeepEqual(result.Elem().Interface(), v) {
|
||||
t.Errorf("%v %d: expected %v, got %v", fc.name, i, v, result.Interface())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,11 +4,12 @@ import (
|
|||
"bytes"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"golang.org/x/net/context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/jackc/pgx"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
|
@ -518,7 +519,7 @@ func TestQueryRowCoreTypes(t *testing.T) {
|
|||
{"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::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::date", []interface{}{time.Date(1987, 1, 2, 0, 0, 0, 0, time.Local)}, []interface{}{&actual.t}, allTypes{t: time.Date(1987, 1, 2, 0, 0, 0, 0, time.Local)}},
|
||||
{"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}},
|
||||
}
|
||||
|
||||
|
|
|
@ -18,24 +18,24 @@ func TestDateTranscode(t *testing.T) {
|
|||
defer closeConn(t, conn)
|
||||
|
||||
dates := []time.Time{
|
||||
time.Date(1, 1, 1, 0, 0, 0, 0, time.Local),
|
||||
time.Date(1000, 1, 1, 0, 0, 0, 0, time.Local),
|
||||
time.Date(1600, 1, 1, 0, 0, 0, 0, time.Local),
|
||||
time.Date(1700, 1, 1, 0, 0, 0, 0, time.Local),
|
||||
time.Date(1800, 1, 1, 0, 0, 0, 0, time.Local),
|
||||
time.Date(1900, 1, 1, 0, 0, 0, 0, time.Local),
|
||||
time.Date(1990, 1, 1, 0, 0, 0, 0, time.Local),
|
||||
time.Date(1999, 12, 31, 0, 0, 0, 0, time.Local),
|
||||
time.Date(2000, 1, 1, 0, 0, 0, 0, time.Local),
|
||||
time.Date(2001, 1, 2, 0, 0, 0, 0, time.Local),
|
||||
time.Date(2004, 2, 29, 0, 0, 0, 0, time.Local),
|
||||
time.Date(2013, 7, 4, 0, 0, 0, 0, time.Local),
|
||||
time.Date(2013, 12, 25, 0, 0, 0, 0, time.Local),
|
||||
time.Date(2029, 1, 1, 0, 0, 0, 0, time.Local),
|
||||
time.Date(2081, 1, 1, 0, 0, 0, 0, time.Local),
|
||||
time.Date(2096, 2, 29, 0, 0, 0, 0, time.Local),
|
||||
time.Date(2550, 1, 1, 0, 0, 0, 0, time.Local),
|
||||
time.Date(9999, 12, 31, 0, 0, 0, 0, time.Local),
|
||||
time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(1000, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(1600, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(1700, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(1800, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(1990, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(1999, 12, 31, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(2001, 1, 2, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(2004, 2, 29, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(2013, 7, 4, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(2013, 12, 25, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(2029, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(2081, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(2096, 2, 29, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(2550, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(9999, 12, 31, 0, 0, 0, 0, time.UTC),
|
||||
}
|
||||
|
||||
for _, actualDate := range dates {
|
||||
|
@ -629,8 +629,8 @@ func TestNullX(t *testing.T) {
|
|||
{"select $1::timestamptz", []interface{}{pgx.NullTime{Time: time.Unix(123, 5000), Valid: false}}, []interface{}{&actual.t}, allTypes{t: pgx.NullTime{Time: time.Time{}, Valid: false}}},
|
||||
{"select $1::timestamp", []interface{}{pgx.NullTime{Time: time.Unix(123, 5000), Valid: true}}, []interface{}{&actual.t}, allTypes{t: pgx.NullTime{Time: time.Unix(123, 5000), Valid: true}}},
|
||||
{"select $1::timestamp", []interface{}{pgx.NullTime{Time: time.Unix(123, 5000), Valid: false}}, []interface{}{&actual.t}, allTypes{t: pgx.NullTime{Time: time.Time{}, Valid: false}}},
|
||||
{"select $1::date", []interface{}{pgx.NullTime{Time: time.Date(1990, 1, 1, 0, 0, 0, 0, time.Local), Valid: true}}, []interface{}{&actual.t}, allTypes{t: pgx.NullTime{Time: time.Date(1990, 1, 1, 0, 0, 0, 0, time.Local), Valid: true}}},
|
||||
{"select $1::date", []interface{}{pgx.NullTime{Time: time.Date(1990, 1, 1, 0, 0, 0, 0, time.Local), Valid: false}}, []interface{}{&actual.t}, allTypes{t: pgx.NullTime{Time: time.Time{}, Valid: false}}},
|
||||
{"select $1::date", []interface{}{pgx.NullTime{Time: time.Date(1990, 1, 1, 0, 0, 0, 0, time.UTC), Valid: true}}, []interface{}{&actual.t}, allTypes{t: pgx.NullTime{Time: time.Date(1990, 1, 1, 0, 0, 0, 0, time.UTC), Valid: true}}},
|
||||
{"select $1::date", []interface{}{pgx.NullTime{Time: time.Date(1990, 1, 1, 0, 0, 0, 0, time.UTC), Valid: false}}, []interface{}{&actual.t}, allTypes{t: pgx.NullTime{Time: time.Time{}, Valid: false}}},
|
||||
{"select 42::int4, $1::float8", []interface{}{pgx.NullFloat64{Float64: 1.23, Valid: true}}, []interface{}{&actual.i32, &actual.f64}, allTypes{i32: pgx.NullInt32{Int32: 42, Valid: true}, f64: pgx.NullFloat64{Float64: 1.23, Valid: true}}},
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue