From 0437c9f5d63d9f0569311f257485156c834317c1 Mon Sep 17 00:00:00 2001 From: Jack Christensen Date: Sat, 4 Mar 2017 22:12:03 -0600 Subject: [PATCH] Move cid to pgtype --- cid.go | 141 ++++++++++++++++++++++++++++++++++++++++++++++++++++ cid_test.go | 94 +++++++++++++++++++++++++++++++++++ pgtype.go | 2 +- 3 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 cid.go create mode 100644 cid_test.go diff --git a/cid.go b/cid.go new file mode 100644 index 00000000..9f8c87d8 --- /dev/null +++ b/cid.go @@ -0,0 +1,141 @@ +package pgtype + +import ( + "fmt" + "io" + "strconv" + + "github.com/jackc/pgx/pgio" +) + +// CID is PostgreSQL's Command Identifier type. +// +// When one does +// +// select cmin, cmax, * from some_table; +// +// it is the data type of the cmin and cmax hidden system columns. +// +// It is currently implemented as an unsigned four byte integer. +// Its definition can be found in src/include/c.h as CommandId +// in the PostgreSQL sources. +type CID struct { + Uint uint32 + Status Status +} + +// ConvertFrom converts from src to dst. Note that as CID is not a general +// number type ConvertFrom does not do automatic type conversion as other number +// types do. +func (dst *CID) ConvertFrom(src interface{}) error { + switch value := src.(type) { + case CID: + *dst = value + case uint32: + *dst = CID{Uint: value, Status: Present} + default: + return fmt.Errorf("cannot convert %v to CID", value) + } + + return nil +} + +// AssignTo assigns from src to dst. Note that as CID is not a general number +// type AssignTo does not do automatic type conversion as other number types do. +func (src *CID) AssignTo(dst interface{}) error { + switch v := dst.(type) { + case *uint32: + if src.Status == Present { + *v = src.Uint + } else { + return fmt.Errorf("cannot assign %v into %T", src, dst) + } + case **uint32: + if src.Status == Present { + n := src.Uint + *v = &n + } else { + *v = nil + } + } + + return nil +} + +func (dst *CID) DecodeText(r io.Reader) error { + size, err := pgio.ReadInt32(r) + if err != nil { + return err + } + + if size == -1 { + *dst = CID{Status: Null} + return nil + } + + buf := make([]byte, int(size)) + _, err = r.Read(buf) + if err != nil { + return err + } + + n, err := strconv.ParseUint(string(buf), 10, 32) + if err != nil { + return err + } + + *dst = CID{Uint: uint32(n), Status: Present} + return nil +} + +func (dst *CID) DecodeBinary(r io.Reader) error { + size, err := pgio.ReadInt32(r) + if err != nil { + return err + } + + if size == -1 { + *dst = CID{Status: Null} + return nil + } + + if size != 4 { + return fmt.Errorf("invalid length for cid: %v", size) + } + + n, err := pgio.ReadUint32(r) + if err != nil { + return err + } + + *dst = CID{Uint: n, Status: Present} + return nil +} + +func (src CID) EncodeText(w io.Writer) error { + if done, err := encodeNotPresent(w, src.Status); done { + return err + } + + s := strconv.FormatUint(uint64(src.Uint), 10) + _, err := pgio.WriteInt32(w, int32(len(s))) + if err != nil { + return nil + } + _, err = w.Write([]byte(s)) + return err +} + +func (src CID) EncodeBinary(w io.Writer) error { + if done, err := encodeNotPresent(w, src.Status); done { + return err + } + + _, err := pgio.WriteInt32(w, 4) + if err != nil { + return err + } + + _, err = pgio.WriteUint32(w, src.Uint) + return err +} diff --git a/cid_test.go b/cid_test.go new file mode 100644 index 00000000..72f5dfea --- /dev/null +++ b/cid_test.go @@ -0,0 +1,94 @@ +package pgtype_test + +import ( + "reflect" + "testing" + + "github.com/jackc/pgx/pgtype" +) + +func TestCIDTranscode(t *testing.T) { + testSuccessfulTranscode(t, "cid", []interface{}{ + pgtype.CID{Uint: 42, Status: pgtype.Present}, + pgtype.CID{Status: pgtype.Null}, + }) +} + +func TestCIDConvertFrom(t *testing.T) { + successfulTests := []struct { + source interface{} + result pgtype.CID + }{ + {source: uint32(1), result: pgtype.CID{Uint: 1, Status: pgtype.Present}}, + } + + for i, tt := range successfulTests { + var r pgtype.CID + 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 TestCIDAssignTo(t *testing.T) { + var ui32 uint32 + var pui32 *uint32 + + simpleTests := []struct { + src pgtype.CID + dst interface{} + expected interface{} + }{ + {src: pgtype.CID{Uint: 42, Status: pgtype.Present}, dst: &ui32, expected: uint32(42)}, + {src: pgtype.CID{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.CID + dst interface{} + expected interface{} + }{ + {src: pgtype.CID{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.CID + dst interface{} + }{ + {src: pgtype.CID{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/pgtype.go b/pgtype.go index 5722c8ab..1200bf12 100644 --- a/pgtype.go +++ b/pgtype.go @@ -20,7 +20,7 @@ const ( OIDOID = 26 TidOID = 27 XidOID = 28 - CidOID = 29 + CIDOID = 29 JSONOID = 114 CidrOID = 650 CidrArrayOID = 651