diff --git a/conn.go b/conn.go index 898eea94..580b23cc 100644 --- a/conn.go +++ b/conn.go @@ -557,7 +557,7 @@ func (c *Conn) sendPreparedQuery(ps *PreparedStatement, arguments ...interface{} wbuf.WriteInt16(TextFormatCode) default: switch oid { - case BoolOid, ByteaOid, Int2Oid, Int4Oid, Int8Oid, Float4Oid, Float8Oid, TimestampTzOid, TimestampTzArrayOid, TimestampArrayOid, BoolArrayOid, Int2ArrayOid, Int4ArrayOid, Int8ArrayOid, Float4ArrayOid, Float8ArrayOid, TextArrayOid, VarcharArrayOid, OidOid: + case BoolOid, ByteaOid, Int2Oid, Int4Oid, Int8Oid, Float4Oid, Float8Oid, TimestampTzOid, TimestampTzArrayOid, TimestampOid, TimestampArrayOid, BoolArrayOid, Int2ArrayOid, Int4ArrayOid, Int8ArrayOid, Float4ArrayOid, Float8ArrayOid, TextArrayOid, VarcharArrayOid, OidOid: wbuf.WriteInt16(BinaryFormatCode) default: wbuf.WriteInt16(TextFormatCode) diff --git a/values.go b/values.go index 1ba54b01..cf20fdee 100644 --- a/values.go +++ b/values.go @@ -71,6 +71,7 @@ func init() { DefaultTypeFormats["int4"] = BinaryFormatCode DefaultTypeFormats["int8"] = BinaryFormatCode DefaultTypeFormats["oid"] = BinaryFormatCode + DefaultTypeFormats["timestamp"] = BinaryFormatCode DefaultTypeFormats["timestamptz"] = BinaryFormatCode } @@ -388,7 +389,8 @@ type NullTime struct { } func (n *NullTime) Scan(vr *ValueReader) error { - if vr.Type().DataType != TimestampTzOid { + oid := vr.Type().DataType + if oid != TimestampTzOid && oid != TimestampOid { return SerializationError(fmt.Sprintf("NullTime.Scan cannot decode OID %d", vr.Type().DataType)) } @@ -398,7 +400,11 @@ func (n *NullTime) Scan(vr *ValueReader) error { } n.Valid = true - n.Time = decodeTimestampTz(vr) + if oid == TimestampTzOid { + n.Time = decodeTimestampTz(vr) + } else { + n.Time = decodeTimestamp(vr) + } return vr.Err() } @@ -406,7 +412,7 @@ func (n *NullTime) Scan(vr *ValueReader) error { func (n NullTime) FormatCode() int16 { return BinaryFormatCode } func (n NullTime) Encode(w *WriteBuf, oid Oid) error { - if oid != TimestampTzOid { + if oid != TimestampTzOid && oid != TimestampOid { return SerializationError(fmt.Sprintf("NullTime.Encode cannot encode into OID %d", oid)) } @@ -415,7 +421,11 @@ func (n NullTime) Encode(w *WriteBuf, oid Oid) error { return nil } - return encodeTimestampTz(w, n.Time) + if oid == TimestampTzOid { + return encodeTimestampTz(w, n.Time) + } else { + return encodeTimestamp(w, n.Time) + } } // Hstore represents an hstore column. It does not support a null column or null @@ -1159,8 +1169,12 @@ func decodeTimestamp(vr *ValueReader) time.Time { } return t case BinaryFormatCode: - vr.Fatal(ProtocolError("Can't decode binary timestamp")) - return zeroTime + if vr.Len() != 8 { + vr.Fatal(ProtocolError(fmt.Sprintf("Received an invalid size for an timestamp: %d", vr.Len()))) + } + microsecSinceY2K := vr.ReadInt64() + microsecSinceUnixEpoch := microsecFromUnixEpochToY2K + microsecSinceY2K + return time.Unix(microsecSinceUnixEpoch/1000000, (microsecSinceUnixEpoch%1000000)*1000) default: vr.Fatal(ProtocolError(fmt.Sprintf("Unknown field description format code: %v", vr.Type().FormatCode))) return zeroTime @@ -1168,13 +1182,7 @@ func decodeTimestamp(vr *ValueReader) time.Time { } 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 encodeTimestampTz(w, value) } func decode1dArrayHeader(vr *ValueReader) (length int32, err error) { diff --git a/values_test.go b/values_test.go index 3c5b35e6..1938e13d 100644 --- a/values_test.go +++ b/values_test.go @@ -130,6 +130,8 @@ func TestNullX(t *testing.T) { {"select $1::bool", []interface{}{pgx.NullBool{Bool: true, Valid: false}}, []interface{}{&actual.b}, allTypes{b: pgx.NullBool{Bool: false, Valid: false}}}, {"select $1::timestamptz", []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::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 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}}}, }