From 92cff1d96155b53b88a02bd0524bdbc20c3afe35 Mon Sep 17 00:00:00 2001 From: Jack Christensen Date: Sat, 18 Mar 2017 14:42:36 -0500 Subject: [PATCH] Simplify []byte scanning --- doc.go | 5 +---- pgtype/text.go | 10 ++++++++++ pgtype/text_test.go | 27 +++++++++++++++++++++++++-- query.go | 15 +-------------- query_test.go | 1 - v3.md | 2 ++ values.go | 18 ------------------ 7 files changed, 39 insertions(+), 39 deletions(-) diff --git a/doc.go b/doc.go index 2d782c5e..0921242a 100644 --- a/doc.go +++ b/doc.go @@ -169,10 +169,7 @@ and database/sql/driver.Valuer interfaces. Raw Bytes Mapping []byte passed as arguments to Query, QueryRow, and Exec are passed unmodified -to PostgreSQL. In like manner, a *[]byte passed to Scan will be filled with -the raw bytes returned by PostgreSQL. This can be especially useful for reading -varchar, text, json, and jsonb values directly into a []byte and avoiding the -type conversion from string. +to PostgreSQL. Transactions diff --git a/pgtype/text.go b/pgtype/text.go index f1a76b6e..af7f16fc 100644 --- a/pgtype/text.go +++ b/pgtype/text.go @@ -49,6 +49,16 @@ func (src *Text) AssignTo(dst interface{}) error { return fmt.Errorf("cannot assign %v to %T", src, dst) } *v = src.String + case *[]byte: + switch src.Status { + case Present: + *v = make([]byte, len(src.String)) + copy(*v, src.String) + case Null: + *v = nil + default: + return fmt.Errorf("unknown status") + } default: if v := reflect.ValueOf(dst); v.Kind() == reflect.Ptr { el := v.Elem() diff --git a/pgtype/text_test.go b/pgtype/text_test.go index 39348bcc..34b6a784 100644 --- a/pgtype/text_test.go +++ b/pgtype/text_test.go @@ -1,6 +1,7 @@ package pgtype_test import ( + "bytes" "reflect" "testing" @@ -44,7 +45,7 @@ func TestTextAssignTo(t *testing.T) { var s string var ps *string - simpleTests := []struct { + stringTests := []struct { src pgtype.Text dst interface{} expected interface{} @@ -53,7 +54,7 @@ func TestTextAssignTo(t *testing.T) { {src: pgtype.Text{Status: pgtype.Null}, dst: &ps, expected: ((*string)(nil))}, } - for i, tt := range simpleTests { + for i, tt := range stringTests { err := tt.src.AssignTo(tt.dst) if err != nil { t.Errorf("%d: %v", i, err) @@ -64,6 +65,28 @@ func TestTextAssignTo(t *testing.T) { } } + var buf []byte + + bytesTests := []struct { + src pgtype.Text + dst *[]byte + expected []byte + }{ + {src: pgtype.Text{String: "foo", Status: pgtype.Present}, dst: &buf, expected: []byte("foo")}, + {src: pgtype.Text{Status: pgtype.Null}, dst: &buf, expected: nil}, + } + + for i, tt := range bytesTests { + err := tt.src.AssignTo(tt.dst) + if err != nil { + t.Errorf("%d: %v", i, err) + } + + if bytes.Compare(*tt.dst, tt.expected) != 0 { + t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, tt.dst) + } + } + pointerAllocTests := []struct { src pgtype.Text dst interface{} diff --git a/query.go b/query.go index a76a99bc..0b5cc911 100644 --- a/query.go +++ b/query.go @@ -198,20 +198,7 @@ func (rows *Rows) Scan(dest ...interface{}) (err error) { continue } - // Check for []byte first as we allow sidestepping the decoding process and retrieving the raw bytes - if b, ok := d.(*[]byte); ok { - // If it actually is a bytea then pass it through decodeBytea (so it can be decoded if it is in text format) - // Otherwise read the bytes directly regardless of what the actual type is. - if vr.Type().DataType == ByteaOid { - *b = decodeBytea(vr) - } else { - if vr.Len() != -1 { - *b = vr.ReadBytes(vr.Len()) - } else { - *b = nil - } - } - } else if s, ok := d.(pgtype.BinaryDecoder); ok && vr.Type().FormatCode == BinaryFormatCode { + if s, ok := d.(pgtype.BinaryDecoder); ok && vr.Type().FormatCode == BinaryFormatCode { err = s.DecodeBinary(rows.conn.ConnInfo, vr.bytes()) if err != nil { rows.Fatal(scanArgError{col: i, err: err}) diff --git a/query_test.go b/query_test.go index 480959e8..b053e26d 100644 --- a/query_test.go +++ b/query_test.go @@ -684,7 +684,6 @@ func TestQueryRowCoreByteSlice(t *testing.T) { }{ {"select $1::text", "Jack", []byte("Jack")}, {"select $1::text", []byte("Jack"), []byte("Jack")}, - {"select $1::int4", int32(239023409), []byte{14, 63, 53, 49}}, {"select $1::varchar", []byte("Jack"), []byte("Jack")}, {"select $1::bytea", []byte{0, 15, 255, 17}, []byte{0, 15, 255, 17}}, } diff --git a/v3.md b/v3.md index 6f5fd412..8fe30bf4 100644 --- a/v3.md +++ b/v3.md @@ -28,6 +28,8 @@ Reject scanning binary format values into a string (e.g. binary encoded timestam Remove CopyTo +No longer can read raw bytes of any value into a []byte. Use pgtype.GenericBinary if this functionality is needed. + ## TODO / Possible / Investigate Organize errors better diff --git a/values.go b/values.go index 8bfcb2ec..734e1fa5 100644 --- a/values.go +++ b/values.go @@ -217,21 +217,3 @@ func stripNamedType(val *reflect.Value) (interface{}, bool) { return nil, false } - -func decodeBytea(vr *ValueReader) []byte { - if vr.Len() == -1 { - return nil - } - - if vr.Type().DataType != ByteaOid { - vr.Fatal(ProtocolError(fmt.Sprintf("Cannot decode oid %v into []byte", vr.Type().DataType))) - return nil - } - - if vr.Type().FormatCode != BinaryFormatCode { - vr.Fatal(ProtocolError(fmt.Sprintf("Unknown field description format code: %v", vr.Type().FormatCode))) - return nil - } - - return vr.ReadBytes(vr.Len()) -}