diff --git a/conn.go b/conn.go index 7bb26677..2b826dad 100644 --- a/conn.go +++ b/conn.go @@ -295,6 +295,7 @@ func (c *Conn) connect(config ConnConfig, network, address string, tlsConfig *tl TimestampTzOID: &pgtype.Timestamptz{}, VarcharArrayOID: &pgtype.VarcharArray{}, VarcharOID: &pgtype.Text{}, + XIDOID: &pgtype.XID{}, } if tlsConfig != nil { diff --git a/pgtype/pgtype.go b/pgtype/pgtype.go index 1200bf12..15c0cc76 100644 --- a/pgtype/pgtype.go +++ b/pgtype/pgtype.go @@ -19,7 +19,7 @@ const ( TextOID = 25 OIDOID = 26 TidOID = 27 - XidOID = 28 + XIDOID = 28 CIDOID = 29 JSONOID = 114 CidrOID = 650 diff --git a/pgtype/xid.go b/pgtype/xid.go new file mode 100644 index 00000000..f4d087a5 --- /dev/null +++ b/pgtype/xid.go @@ -0,0 +1,45 @@ +package pgtype + +import ( + "io" +) + +// Xid is PostgreSQL's Transaction ID type. +// +// In later versions of PostgreSQL, it is the type used for the backend_xid +// and backend_xmin columns of the pg_stat_activity system view. +// +// Also, when one does +// +// select xmin, xmax, * from some_table; +// +// it is the data type of the xmin and xmax hidden system columns. +// +// It is currently implemented as an unsigned four byte integer. +// Its definition can be found in src/include/postgres_ext.h as TransactionId +// in the PostgreSQL sources. +type XID CID + +func (dst *XID) ConvertFrom(src interface{}) error { + return (*CID)(dst).ConvertFrom(src) +} + +func (src *XID) AssignTo(dst interface{}) error { + return (*CID)(src).AssignTo(dst) +} + +func (dst *XID) DecodeText(r io.Reader) error { + return (*CID)(dst).DecodeText(r) +} + +func (dst *XID) DecodeBinary(r io.Reader) error { + return (*CID)(dst).DecodeBinary(r) +} + +func (src XID) EncodeText(w io.Writer) error { + return (CID)(src).EncodeText(w) +} + +func (src XID) EncodeBinary(w io.Writer) error { + return (CID)(src).EncodeBinary(w) +} diff --git a/pgtype/xid_test.go b/pgtype/xid_test.go new file mode 100644 index 00000000..664920bc --- /dev/null +++ b/pgtype/xid_test.go @@ -0,0 +1,94 @@ +package pgtype_test + +import ( + "reflect" + "testing" + + "github.com/jackc/pgx/pgtype" +) + +func TestXIDTranscode(t *testing.T) { + testSuccessfulTranscode(t, "xid", []interface{}{ + pgtype.XID{Uint: 42, Status: pgtype.Present}, + pgtype.XID{Status: pgtype.Null}, + }) +} + +func TestXIDConvertFrom(t *testing.T) { + successfulTests := []struct { + source interface{} + result pgtype.XID + }{ + {source: uint32(1), result: pgtype.XID{Uint: 1, Status: pgtype.Present}}, + } + + for i, tt := range successfulTests { + var r pgtype.XID + err := r.ConvertFrom(tt.source) + if err != nil { + t.Errorf("%d: %v", i, err) + } + + if r != tt.result { + t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r) + } + } +} + +func TestXIDAssignTo(t *testing.T) { + var ui32 uint32 + var pui32 *uint32 + + simpleTests := []struct { + src pgtype.XID + dst interface{} + expected interface{} + }{ + {src: pgtype.XID{Uint: 42, Status: pgtype.Present}, dst: &ui32, expected: uint32(42)}, + {src: pgtype.XID{Status: pgtype.Null}, dst: &pui32, expected: ((*uint32)(nil))}, + } + + for i, tt := range simpleTests { + err := tt.src.AssignTo(tt.dst) + if err != nil { + t.Errorf("%d: %v", i, err) + } + + if dst := reflect.ValueOf(tt.dst).Elem().Interface(); dst != tt.expected { + t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst) + } + } + + pointerAllocTests := []struct { + src pgtype.XID + dst interface{} + expected interface{} + }{ + {src: pgtype.XID{Uint: 42, Status: pgtype.Present}, dst: &pui32, expected: uint32(42)}, + } + + for i, tt := range pointerAllocTests { + err := tt.src.AssignTo(tt.dst) + if err != nil { + t.Errorf("%d: %v", i, err) + } + + if dst := reflect.ValueOf(tt.dst).Elem().Elem().Interface(); dst != tt.expected { + t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst) + } + } + + errorTests := []struct { + src pgtype.XID + dst interface{} + }{ + {src: pgtype.XID{Status: pgtype.Null}, dst: &ui32}, + } + + for i, tt := range errorTests { + err := tt.src.AssignTo(tt.dst) + if err == nil { + t.Errorf("%d: expected error but none was returned (%v -> %v)", i, tt.src, tt.dst) + } + } +} diff --git a/values.go b/values.go index b143ac1a..b6848cf5 100644 --- a/values.go +++ b/values.go @@ -28,7 +28,7 @@ const ( TextOID = 25 OIDOID = 26 TidOID = 27 - XidOID = 28 + XIDOID = 28 CIDOID = 29 JSONOID = 114 CidrOID = 650 @@ -590,61 +590,6 @@ func (n NullOID) Encode(w *WriteBuf, oid OID) error { return encodeOID(w, oid, n.OID) } -// Xid is PostgreSQL's Transaction ID type. -// -// In later versions of PostgreSQL, it is the type used for the backend_xid -// and backend_xmin columns of the pg_stat_activity system view. -// -// Also, when one does -// -// select xmin, xmax, * from some_table; -// -// it is the data type of the xmin and xmax hidden system columns. -// -// It is currently implemented as an unsigned four byte integer. -// Its definition can be found in src/include/postgres_ext.h as TransactionId -// in the PostgreSQL sources. -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 Xid 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) -} - // Tid is PostgreSQL's Tuple Identifier type. // // When one does @@ -1033,8 +978,6 @@ func Encode(wbuf *WriteBuf, oid OID, arg interface{}) error { return encodeString(wbuf, oid, string(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) @@ -1112,8 +1055,6 @@ func Decode(vr *ValueReader, d interface{}) error { *v = Name(decodeText(vr)) case *OID: *v = decodeOID(vr) - case *Xid: - *v = decodeXid(vr) case *Tid: *v = decodeTid(vr) case *string: @@ -1394,49 +1335,6 @@ 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 -} - // Note that we do not match negative numbers, because neither the // BlockNumber nor OffsetNumber of a Tid can be negative. var tidRegexp *regexp.Regexp = regexp.MustCompile(`^\((\d*),(\d*)\)$`) diff --git a/values_test.go b/values_test.go index ae3ecc84..0283f17d 100644 --- a/values_test.go +++ b/values_test.go @@ -572,7 +572,6 @@ func TestNullX(t *testing.T) { a pgx.NullAclItem n pgx.NullName oid pgx.NullOID - xid pgx.NullXid tid pgx.NullTid i64 pgx.NullInt64 f32 pgx.NullFloat32 @@ -598,9 +597,6 @@ func TestNullX(t *testing.T) { {"select $1::oid", []interface{}{pgx.NullOID{OID: 1, Valid: true}}, []interface{}{&actual.oid}, allTypes{oid: pgx.NullOID{OID: 1, Valid: true}}}, {"select $1::oid", []interface{}{pgx.NullOID{OID: 1, Valid: false}}, []interface{}{&actual.oid}, allTypes{oid: pgx.NullOID{OID: 0, Valid: false}}}, {"select $1::oid", []interface{}{pgx.NullOID{OID: 4294967295, Valid: true}}, []interface{}{&actual.oid}, allTypes{oid: pgx.NullOID{OID: 4294967295, Valid: true}}}, - {"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::\"char\"", []interface{}{pgx.NullChar{Char: 1, Valid: true}}, []interface{}{&actual.c}, allTypes{c: pgx.NullChar{Char: 1, Valid: true}}}, {"select $1::\"char\"", []interface{}{pgx.NullChar{Char: 1, Valid: false}}, []interface{}{&actual.c}, allTypes{c: pgx.NullChar{Char: 0, Valid: false}}}, {"select $1::\"char\"", []interface{}{pgx.NullChar{Char: 255, Valid: true}}, []interface{}{&actual.c}, allTypes{c: pgx.NullChar{Char: 255, Valid: true}}},