Add infinity to pgtype.Date

pgxtype-experiment2
Jack Christensen 2017-02-25 18:19:38 -06:00
parent 7bf783ae20
commit 7d9f954de4
7 changed files with 128 additions and 50 deletions

View File

@ -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))

View File

@ -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
}

View File

@ -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 {

View File

@ -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

View File

@ -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())
}
}

View File

@ -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}},
}

View File

@ -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}}},
}