From ed2b3b3b49cd876ed9c83fc7a06e14a52c6cfda2 Mon Sep 17 00:00:00 2001 From: Jack Christensen Date: Sat, 12 Jul 2014 09:33:49 -0500 Subject: [PATCH] Add timestamp support But not to NullTime because of text vs binary encoding difficulties. You really should never use timestamp anyway. --- conn.go | 4 +++- query.go | 13 +++++++++++-- query_test.go | 1 + values.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 3 deletions(-) diff --git a/conn.go b/conn.go index ee30d15f..b5f0dc5d 100644 --- a/conn.go +++ b/conn.go @@ -438,7 +438,7 @@ func (c *Conn) sendPreparedQuery(ps *PreparedStatement, arguments ...interface{} switch oid { case BoolOid, ByteaOid, Int2Oid, Int4Oid, Int8Oid, Float4Oid, Float8Oid, TimestampTzOid: wbuf.WriteInt16(BinaryFormatCode) - case TextOid, VarcharOid, DateOid: + case TextOid, VarcharOid, DateOid, TimestampOid: wbuf.WriteInt16(TextFormatCode) default: return SerializationError(fmt.Sprintf("Parameter %d oid %d is not a core type and argument type %T does not implement TextEncoder or BinaryEncoder", i, oid, arg)) @@ -488,6 +488,8 @@ func (c *Conn) sendPreparedQuery(ps *PreparedStatement, arguments ...interface{} err = encodeDate(wbuf, arguments[i]) case TimestampTzOid: err = encodeTimestampTz(wbuf, arguments[i]) + case TimestampOid: + err = encodeTimestamp(wbuf, arguments[i]) default: return SerializationError(fmt.Sprintf("%T is not a core type and it does not implement TextEncoder or BinaryEncoder", arg)) } diff --git a/query.go b/query.go index 3c23d52c..d6b91e95 100644 --- a/query.go +++ b/query.go @@ -199,10 +199,17 @@ func (rows *Rows) Scan(dest ...interface{}) (err error) { case *float64: *d = decodeFloat8(rows, fd, size) case *time.Time: - if fd.DataType == DateOid { + switch fd.DataType { + case DateOid: *d = decodeDate(rows, fd, size) - } else { + case TimestampTzOid: *d = decodeTimestampTz(rows, fd, size) + case TimestampOid: + *d = decodeTimestamp(rows, fd, size) + default: + err = fmt.Errorf("Can't convert OID %v to time.Time", fd.DataType) + rows.Fatal(err) + return err } case Scanner: @@ -254,6 +261,8 @@ func (rows *Rows) Values() ([]interface{}, error) { values = append(values, decodeDate(rows, fd, size)) case TimestampTzOid: values = append(values, decodeTimestampTz(rows, fd, size)) + case TimestampOid: + values = append(values, decodeTimestamp(rows, fd, size)) default: // if it is not an intrinsic type then return the text switch fd.FormatCode { diff --git a/query_test.go b/query_test.go index 067947c4..040b5c06 100644 --- a/query_test.go +++ b/query_test.go @@ -387,6 +387,7 @@ func TestQueryRowCoreTypes(t *testing.T) { {"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::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)}}, } diff --git a/values.go b/values.go index 94932851..1b8d6eab 100644 --- a/values.go +++ b/values.go @@ -22,6 +22,7 @@ const ( Float8Oid = 701 VarcharOid = 1043 DateOid = 1082 + TimestampOid = 1114 TimestampTzOid = 1184 ) @@ -366,10 +367,15 @@ type NullTime struct { } func (n *NullTime) Scan(rows *Rows, fd *FieldDescription, size int32) error { + if fd.DataType != TimestampTzOid { + return SerializationError(fmt.Sprintf("NullTime.EncodeBinary cannot encode into OID %d", fd.DataType)) + } + if size == -1 { n.Time, n.Valid = time.Time{}, false return nil } + n.Valid = true n.Time = decodeTimestampTz(rows, fd, size) @@ -962,3 +968,41 @@ func encodeTimestampTz(w *WriteBuf, value interface{}) error { return nil } + +func decodeTimestamp(rows *Rows, fd *FieldDescription, size int32) time.Time { + var zeroTime time.Time + + if fd.DataType != TimestampOid { + rows.Fatal(ProtocolError(fmt.Sprintf("Expected type oid %v but received type oid %v", TimestampOid, fd.DataType))) + return zeroTime + } + + switch fd.FormatCode { + case TextFormatCode: + s := rows.mr.ReadString(size) + t, err := time.ParseInLocation("2006-01-02 15:04:05.999999", s, time.Local) + if err != nil { + rows.Fatal(ProtocolError(fmt.Sprintf("Can't decode timestamp: %v - %v", err, s))) + return zeroTime + } + return t + case BinaryFormatCode: + rows.Fatal(ProtocolError("Can't decode binary timestamp")) + return zeroTime + default: + rows.Fatal(ProtocolError(fmt.Sprintf("Unknown field description format code: %v", fd.FormatCode))) + return zeroTime + } +} + +func encodeTimestamp(w *WriteBuf, value interface{}) error { + t, ok := value.(time.Time) + if !ok { + return fmt.Errorf("Expected time.Time, received %T", value) + } + + s := t.Format("2006-01-02 15:04:05.999999") + return encodeText(w, s) + + return nil +}