From 1061b1f978ed615ebb05f9b800cc7bcaa0e600c1 Mon Sep 17 00:00:00 2001 From: Manni Wood Date: Sat, 3 Sep 2016 18:04:55 -0400 Subject: [PATCH] Adds Xid type --- messages.go | 6 ++++ values.go | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++ values_test.go | 4 +++ 3 files changed, 100 insertions(+) diff --git a/messages.go b/messages.go index 7e5c3b54..053a4c13 100644 --- a/messages.go +++ b/messages.go @@ -138,6 +138,12 @@ func (wb *WriteBuf) WriteInt32(n int32) { wb.buf = append(wb.buf, b...) } +func (wb *WriteBuf) WriteUint32(n uint32) { + b := make([]byte, 4) + binary.BigEndian.PutUint32(b, uint32(n)) + wb.buf = append(wb.buf, b...) +} + func (wb *WriteBuf) WriteInt64(n int64) { b := make([]byte, 8) binary.BigEndian.PutUint64(b, uint64(n)) diff --git a/values.go b/values.go index ee43813b..d2c56b92 100644 --- a/values.go +++ b/values.go @@ -22,6 +22,7 @@ const ( Int4Oid = 23 TextOid = 25 OidOid = 26 + XidOid = 28 JsonOid = 114 CidrOid = 650 CidrArrayOid = 651 @@ -93,6 +94,7 @@ func init() { "int4": BinaryFormatCode, "int8": BinaryFormatCode, "oid": BinaryFormatCode, + "xid": BinaryFormatCode, "record": BinaryFormatCode, "text": BinaryFormatCode, "timestamp": BinaryFormatCode, @@ -327,6 +329,47 @@ func (n NullInt32) Encode(w *WriteBuf, oid Oid) error { return encodeInt32(w, oid, n.Int32) } +type Xid uint32 + +// NullXid represents a transaction ID (Xid) that may be null. NullXid implements the +// Scanner and Encoder interfaces so it may be used both as an argument to +// Query[Row] and a destination for Scan. +// +// If Valid is false then the value is NULL. +type NullXid struct { + Xid Xid + Valid bool // Valid is true if Int32 is not NULL +} + +func (n *NullXid) Scan(vr *ValueReader) error { + if vr.Type().DataType != XidOid { + return SerializationError(fmt.Sprintf("NullXid.Scan cannot decode OID %d", vr.Type().DataType)) + } + + if vr.Len() == -1 { + n.Xid, n.Valid = 0, false + return nil + } + n.Valid = true + n.Xid = decodeXid(vr) + return vr.Err() +} + +func (n NullXid) FormatCode() int16 { return BinaryFormatCode } + +func (n NullXid) Encode(w *WriteBuf, oid Oid) error { + if oid != XidOid { + return SerializationError(fmt.Sprintf("NullXid.Encode cannot encode into OID %d", oid)) + } + + if !n.Valid { + w.WriteInt32(-1) + return nil + } + + return encodeXid(w, oid, n.Xid) +} + // NullInt64 represents an bigint that may be null. NullInt64 implements the // Scanner and Encoder interfaces so it may be used both as an argument to // Query[Row] and a destination for Scan. @@ -691,6 +734,8 @@ func Encode(wbuf *WriteBuf, oid Oid, arg interface{}) error { return encodeIPNetSlice(wbuf, oid, arg) case Oid: return encodeOid(wbuf, oid, arg) + case Xid: + return encodeXid(wbuf, oid, arg) default: if strippedArg, ok := stripNamedType(&refVal); ok { return Encode(wbuf, oid, strippedArg) @@ -815,6 +860,8 @@ func Decode(vr *ValueReader, d interface{}) error { *v = uint64(n) case *Oid: *v = decodeOid(vr) + case *Xid: + *v = decodeXid(vr) case *string: *v = decodeText(vr) case *float32: @@ -1339,6 +1386,49 @@ func encodeOid(w *WriteBuf, oid Oid, value Oid) error { return nil } +func decodeXid(vr *ValueReader) Xid { + if vr.Len() == -1 { + vr.Fatal(ProtocolError("Cannot decode null into Xid")) + return Xid(0) + } + + if vr.Type().DataType != XidOid { + vr.Fatal(ProtocolError(fmt.Sprintf("Cannot decode oid %v into pgx.Xid", vr.Type().DataType))) + return Xid(0) + } + + // Unlikely Xid will ever go over the wire as text format, but who knows? + switch vr.Type().FormatCode { + case TextFormatCode: + s := vr.ReadString(vr.Len()) + n, err := strconv.ParseUint(s, 10, 32) + if err != nil { + vr.Fatal(ProtocolError(fmt.Sprintf("Received invalid Oid: %v", s))) + } + return Xid(n) + case BinaryFormatCode: + if vr.Len() != 4 { + vr.Fatal(ProtocolError(fmt.Sprintf("Received an invalid size for an Oid: %d", vr.Len()))) + return Xid(0) + } + return Xid(vr.ReadUint32()) + default: + vr.Fatal(ProtocolError(fmt.Sprintf("Unknown field description format code: %v", vr.Type().FormatCode))) + return Xid(0) + } +} + +func encodeXid(w *WriteBuf, oid Oid, value Xid) error { + if oid != XidOid { + return fmt.Errorf("cannot encode Go %s into oid %d", "pgx.Xid", oid) + } + + w.WriteInt32(4) + w.WriteUint32(uint32(value)) + + return nil +} + func decodeFloat4(vr *ValueReader) float32 { if vr.Len() == -1 { vr.Fatal(ProtocolError("Cannot decode null into float32")) diff --git a/values_test.go b/values_test.go index 3e650b61..c7474a1c 100644 --- a/values_test.go +++ b/values_test.go @@ -594,6 +594,7 @@ func TestNullX(t *testing.T) { s pgx.NullString i16 pgx.NullInt16 i32 pgx.NullInt32 + xid pgx.NullXid i64 pgx.NullInt64 f32 pgx.NullFloat32 f64 pgx.NullFloat64 @@ -615,6 +616,9 @@ func TestNullX(t *testing.T) { {"select $1::int2", []interface{}{pgx.NullInt16{Int16: 1, Valid: false}}, []interface{}{&actual.i16}, allTypes{i16: pgx.NullInt16{Int16: 0, Valid: false}}}, {"select $1::int4", []interface{}{pgx.NullInt32{Int32: 1, Valid: true}}, []interface{}{&actual.i32}, allTypes{i32: pgx.NullInt32{Int32: 1, Valid: true}}}, {"select $1::int4", []interface{}{pgx.NullInt32{Int32: 1, Valid: false}}, []interface{}{&actual.i32}, allTypes{i32: pgx.NullInt32{Int32: 0, Valid: false}}}, + {"select $1::xid", []interface{}{pgx.NullXid{Xid: 1, Valid: true}}, []interface{}{&actual.xid}, allTypes{xid: pgx.NullXid{Xid: 1, Valid: true}}}, + {"select $1::xid", []interface{}{pgx.NullXid{Xid: 1, Valid: false}}, []interface{}{&actual.xid}, allTypes{xid: pgx.NullXid{Xid: 0, Valid: false}}}, + {"select $1::xid", []interface{}{pgx.NullXid{Xid: 4294967295, Valid: true}}, []interface{}{&actual.xid}, allTypes{xid: pgx.NullXid{Xid: 4294967295, Valid: true}}}, {"select $1::int8", []interface{}{pgx.NullInt64{Int64: 1, Valid: true}}, []interface{}{&actual.i64}, allTypes{i64: pgx.NullInt64{Int64: 1, Valid: true}}}, {"select $1::int8", []interface{}{pgx.NullInt64{Int64: 1, Valid: false}}, []interface{}{&actual.i64}, allTypes{i64: pgx.NullInt64{Int64: 0, Valid: false}}}, {"select $1::float4", []interface{}{pgx.NullFloat32{Float32: 1.23, Valid: true}}, []interface{}{&actual.f32}, allTypes{f32: pgx.NullFloat32{Float32: 1.23, Valid: true}}},