From 54d9cbc743e9294fe1720f67533dfbfc229d1956 Mon Sep 17 00:00:00 2001 From: Jack Christensen Date: Wed, 5 Apr 2017 07:54:41 -0500 Subject: [PATCH] Add pgtype.Varbit --- pgtype/pgtype.go | 1 + pgtype/varbit.go | 141 ++++++++++++++++++++++++++++++++++++++++++ pgtype/varbit_test.go | 25 ++++++++ v3.md | 7 --- 4 files changed, 167 insertions(+), 7 deletions(-) create mode 100644 pgtype/varbit.go create mode 100644 pgtype/varbit_test.go diff --git a/pgtype/pgtype.go b/pgtype/pgtype.go index 5de07b7d..338afc9b 100644 --- a/pgtype/pgtype.go +++ b/pgtype/pgtype.go @@ -263,6 +263,7 @@ func init() { "tstzrange": &Tstzrange{}, "unknown": &Unknown{}, "uuid": &Uuid{}, + "varbit": &Varbit{}, "varchar": &Varchar{}, "xid": &Xid{}, } diff --git a/pgtype/varbit.go b/pgtype/varbit.go new file mode 100644 index 00000000..d28e95cd --- /dev/null +++ b/pgtype/varbit.go @@ -0,0 +1,141 @@ +package pgtype + +import ( + "database/sql/driver" + "encoding/binary" + "fmt" + "io" + + "github.com/jackc/pgx/pgio" +) + +type Varbit struct { + Bytes []byte + Len int32 // Number of bits + Status Status +} + +func (dst *Varbit) Set(src interface{}) error { + return fmt.Errorf("cannot convert %v to Varbit", src) +} + +func (dst *Varbit) Get() interface{} { + switch dst.Status { + case Present: + return dst + case Null: + return nil + default: + return dst.Status + } +} + +func (src *Varbit) AssignTo(dst interface{}) error { + return fmt.Errorf("cannot assign %v to %T", src, dst) +} + +func (dst *Varbit) DecodeText(ci *ConnInfo, src []byte) error { + if src == nil { + *dst = Varbit{Status: Null} + return nil + } + + bitLen := len(src) + byteLen := bitLen / 8 + if bitLen%8 > 0 { + byteLen++ + } + buf := make([]byte, byteLen) + + for i, b := range src { + if b == '1' { + byteIdx := i / 8 + bitIdx := uint(i % 8) + buf[byteIdx] = buf[byteIdx] | (128 >> bitIdx) + } + } + + *dst = Varbit{Bytes: buf, Len: int32(bitLen), Status: Present} + return nil +} + +func (dst *Varbit) DecodeBinary(ci *ConnInfo, src []byte) error { + if src == nil { + *dst = Varbit{Status: Null} + return nil + } + + if len(src) < 4 { + return fmt.Errorf("invalid length for varbit: %v", len(src)) + } + + bitLen := int32(binary.BigEndian.Uint32(src)) + rp := 4 + + buf := make([]byte, len(src[rp:])) + copy(buf, src[rp:]) + + *dst = Varbit{Bytes: buf, Len: bitLen, Status: Present} + return nil +} + +func (src *Varbit) EncodeText(ci *ConnInfo, w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined + } + + buf := make([]byte, int(src.Len)) + for i, _ := range buf { + byteIdx := i / 8 + bitMask := byte(128 >> byte(i%8)) + char := byte('0') + if src.Bytes[byteIdx]&bitMask > 0 { + char = '1' + } + buf[i] = char + } + + _, err := w.Write(buf) + return false, err +} + +func (src *Varbit) EncodeBinary(ci *ConnInfo, w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined + } + + if _, err := pgio.WriteInt32(w, src.Len); err != nil { + return false, err + } + + _, err := w.Write(src.Bytes) + return false, err +} + +// Scan implements the database/sql Scanner interface. +func (dst *Varbit) Scan(src interface{}) error { + if src == nil { + *dst = Varbit{Status: Null} + return nil + } + + switch src := src.(type) { + case string: + return dst.DecodeText(nil, []byte(src)) + case []byte: + return dst.DecodeText(nil, src) + } + + return fmt.Errorf("cannot scan %T", src) +} + +// Value implements the database/sql/driver Valuer interface. +func (src *Varbit) Value() (driver.Value, error) { + return encodeValueText(src) +} diff --git a/pgtype/varbit_test.go b/pgtype/varbit_test.go new file mode 100644 index 00000000..cd146d26 --- /dev/null +++ b/pgtype/varbit_test.go @@ -0,0 +1,25 @@ +package pgtype_test + +import ( + "testing" + + "github.com/jackc/pgx/pgtype" +) + +func TestVarbitTranscode(t *testing.T) { + testSuccessfulTranscode(t, "varbit", []interface{}{ + &pgtype.Varbit{Bytes: []byte{}, Len: 0, Status: pgtype.Present}, + &pgtype.Varbit{Bytes: []byte{0, 1, 128, 254, 255}, Len: 40, Status: pgtype.Present}, + &pgtype.Varbit{Bytes: []byte{0, 1, 128, 254, 128}, Len: 33, Status: pgtype.Present}, + &pgtype.Varbit{Status: pgtype.Null}, + }) +} + +func TestVarbitNormalize(t *testing.T) { + testSuccessfulNormalize(t, []normalizeTest{ + { + sql: "select B'111111111'", + value: &pgtype.Varbit{Bytes: []byte{255, 128}, Len: 9, Status: pgtype.Present}, + }, + }) +} diff --git a/v3.md b/v3.md index f522825d..20038938 100644 --- a/v3.md +++ b/v3.md @@ -42,14 +42,10 @@ Or maybe double-down on conn/pool coupling and improve connpool Add auto-idle pinging to conns in pool -Extract types Null* and Hstore to separate package - Remove names from prepared statements - use database/sql style objects Better way of handling text/binary protocol choice than pgx.DefaultTypeFormats or manually editing a PreparedStatement. Possibly an optional part of preparing a statement is specifying the format and/or a decoder. Or maybe it is part of a QueryEx call... Could be very interesting to make encoding and decoding possible without being a method of the type. This could drastically clean up those huge type switches. -Also maybe support binary and text for everything possible - dValueReader / msgReader cleanup Make easier / possible to mock Conn or ConnPool (https://github.com/jackc/pgx/pull/162) @@ -66,6 +62,3 @@ Keep ability to change logging while running consider test to ensure that AssignTo makes copy of reference types something like: select array[1,2,3], array[4,5,6,7] - -pgtype TODO: -varbit