From 1f3e484ca15f6072173f866abb7b46b5d3ecd821 Mon Sep 17 00:00:00 2001 From: Jack Christensen Date: Sat, 11 Mar 2017 12:32:33 -0600 Subject: [PATCH] pgtype.Encode(Binary|Text) do not write length To aid in composability, these methods no longer write their own length. This is especially useful for text formatted arrays and may be useful for future database/sql compatibility. It also makes the code a little simpler as the types no longer have to compute their own size. Along with this, these methods cannot encode NULL. They now return a boolean if they are NULL. This also benefits text array encoding as numeric arrays require NULL to be exactly `NULL` while string arrays require NULL to be `"NULL"`. --- pgtype/bool.go | 38 ++++------ pgtype/boolarray.go | 148 +++++++++++++++++++------------------ pgtype/bytea.go | 44 +++++------ pgtype/cid.go | 4 +- pgtype/cidrarray.go | 4 +- pgtype/date.go | 36 ++++----- pgtype/datearray.go | 148 +++++++++++++++++++------------------ pgtype/float4.go | 36 ++++----- pgtype/float4array.go | 148 +++++++++++++++++++------------------ pgtype/float8.go | 36 ++++----- pgtype/float8array.go | 148 +++++++++++++++++++------------------ pgtype/inet.go | 46 +++++------- pgtype/inetarray.go | 148 +++++++++++++++++++------------------ pgtype/int2.go | 36 ++++----- pgtype/int2array.go | 148 +++++++++++++++++++------------------ pgtype/int4.go | 36 ++++----- pgtype/int4array.go | 148 +++++++++++++++++++------------------ pgtype/int8.go | 36 ++++----- pgtype/int8array.go | 148 +++++++++++++++++++------------------ pgtype/name.go | 4 +- pgtype/oid.go | 4 +- pgtype/pgtype.go | 29 ++++---- pgtype/pgtype_test.go | 4 +- pgtype/pguint32.go | 36 ++++----- pgtype/qchar.go | 16 ++-- pgtype/text.go | 22 +++--- pgtype/text_element.go | 112 ---------------------------- pgtype/textarray.go | 148 ++++++++++++++++++------------------- pgtype/timestamp.go | 40 +++++----- pgtype/timestamparray.go | 148 +++++++++++++++++++------------------ pgtype/timestamptz.go | 36 ++++----- pgtype/timestamptzarray.go | 148 +++++++++++++++++++------------------ pgtype/to-consider.txt | 9 --- pgtype/typed_array.go.erb | 148 +++++++++++++++++++------------------ pgtype/typed_array_gen.sh | 22 +++--- pgtype/varchararray.go | 4 +- pgtype/xid.go | 4 +- values.go | 120 +++++++++++++++++++++--------- 38 files changed, 1271 insertions(+), 1319 deletions(-) delete mode 100644 pgtype/text_element.go delete mode 100644 pgtype/to-consider.txt diff --git a/pgtype/bool.go b/pgtype/bool.go index b7bc14d0..9764fafe 100644 --- a/pgtype/bool.go +++ b/pgtype/bool.go @@ -5,8 +5,6 @@ import ( "io" "reflect" "strconv" - - "github.com/jackc/pgx/pgio" ) type Bool struct { @@ -100,14 +98,12 @@ func (dst *Bool) DecodeBinary(src []byte) error { return nil } -func (src Bool) EncodeText(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err - } - - _, err := pgio.WriteInt32(w, 1) - if err != nil { - return nil +func (src Bool) EncodeText(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } var buf []byte @@ -117,18 +113,16 @@ func (src Bool) EncodeText(w io.Writer) error { buf = []byte{'f'} } - _, err = w.Write(buf) - return err + _, err := w.Write(buf) + return false, err } -func (src Bool) EncodeBinary(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err - } - - _, err := pgio.WriteInt32(w, 1) - if err != nil { - return nil +func (src Bool) EncodeBinary(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } var buf []byte @@ -138,6 +132,6 @@ func (src Bool) EncodeBinary(w io.Writer) error { buf = []byte{0} } - _, err = w.Write(buf) - return err + _, err := w.Write(buf) + return false, err } diff --git a/pgtype/boolarray.go b/pgtype/boolarray.go index a9b8bf50..f7323281 100644 --- a/pgtype/boolarray.go +++ b/pgtype/boolarray.go @@ -152,26 +152,22 @@ func (dst *BoolArray) DecodeBinary(src []byte) error { return nil } -func (src *BoolArray) EncodeText(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src *BoolArray) EncodeText(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } if len(src.Dimensions) == 0 { - _, err := pgio.WriteInt32(w, 2) - if err != nil { - return err - } - - _, err = w.Write([]byte("{}")) - return err + _, err := io.WriteString(w, "{}") + return false, err } - buf := &bytes.Buffer{} - - err := EncodeTextArrayDimensions(buf, src.Dimensions) + err := EncodeTextArrayDimensions(w, src.Dimensions) if err != nil { - return err + return false, err } // dimElemCounts is the multiples of elements that each array lies on. For @@ -185,100 +181,112 @@ func (src *BoolArray) EncodeText(w io.Writer) error { dimElemCounts[i] = int(src.Dimensions[i].Length) * dimElemCounts[i+1] } - textElementWriter := NewTextElementWriter(buf) - for i, elem := range src.Elements { if i > 0 { - err = pgio.WriteByte(buf, ',') + err = pgio.WriteByte(w, ',') if err != nil { - return err + return false, err } } for _, dec := range dimElemCounts { if i%dec == 0 { - err = pgio.WriteByte(buf, '{') + err = pgio.WriteByte(w, '{') if err != nil { - return err + return false, err } } } - textElementWriter.Reset() - err = elem.EncodeText(textElementWriter) + elemBuf := &bytes.Buffer{} + null, err := elem.EncodeText(elemBuf) if err != nil { - return err + return false, err + } + if null { + _, err = io.WriteString(w, `NULL`) + if err != nil { + return false, err + } + } else if elemBuf.Len() == 0 { + _, err = io.WriteString(w, `""`) + if err != nil { + return false, err + } + } else { + _, err = elemBuf.WriteTo(w) + if err != nil { + return false, err + } } for _, dec := range dimElemCounts { if (i+1)%dec == 0 { - err = pgio.WriteByte(buf, '}') + err = pgio.WriteByte(w, '}') if err != nil { - return err + return false, err } } } } - _, err = pgio.WriteInt32(w, int32(buf.Len())) - if err != nil { - return err - } - - _, err = buf.WriteTo(w) - return err + return false, nil } -func (src *BoolArray) EncodeBinary(w io.Writer) error { +func (src *BoolArray) EncodeBinary(w io.Writer) (bool, error) { return src.encodeBinary(w, BoolOID) } -func (src *BoolArray) encodeBinary(w io.Writer, elementOID int32) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src *BoolArray) encodeBinary(w io.Writer, elementOID int32) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } - var arrayHeader ArrayHeader + arrayHeader := ArrayHeader{ + ElementOID: elementOID, + Dimensions: src.Dimensions, + } + + for i := range src.Elements { + if src.Elements[i].Status == Null { + arrayHeader.ContainsNull = true + break + } + } + + err := arrayHeader.EncodeBinary(w) + if err != nil { + return false, err + } - // TODO - consider how to avoid having to buffer array before writing length - - // or how not pay allocations for the byte order conversions. elemBuf := &bytes.Buffer{} for i := range src.Elements { - err := src.Elements[i].EncodeBinary(elemBuf) + elemBuf.Reset() + + null, err := src.Elements[i].EncodeBinary(elemBuf) if err != nil { - return err + return false, err } - if src.Elements[i].Status == Null { - arrayHeader.ContainsNull = true + if null { + _, err = pgio.WriteInt32(w, -1) + if err != nil { + return false, err + } + } else { + _, err = pgio.WriteInt32(w, int32(elemBuf.Len())) + if err != nil { + return false, err + } + _, err = elemBuf.WriteTo(w) + if err != nil { + return false, err + } } } - arrayHeader.ElementOID = elementOID - arrayHeader.Dimensions = src.Dimensions - - // TODO - consider how to avoid having to buffer array before writing length - - // or how not pay allocations for the byte order conversions. - headerBuf := &bytes.Buffer{} - err := arrayHeader.EncodeBinary(headerBuf) - if err != nil { - return err - } - - _, err = pgio.WriteInt32(w, int32(headerBuf.Len()+elemBuf.Len())) - if err != nil { - return err - } - - _, err = headerBuf.WriteTo(w) - if err != nil { - return err - } - - _, err = elemBuf.WriteTo(w) - if err != nil { - return err - } - - return err + return false, err } diff --git a/pgtype/bytea.go b/pgtype/bytea.go index db20482f..709499d2 100644 --- a/pgtype/bytea.go +++ b/pgtype/bytea.go @@ -5,8 +5,6 @@ import ( "fmt" "io" "reflect" - - "github.com/jackc/pgx/pgio" ) type Bytea struct { @@ -101,37 +99,31 @@ func (dst *Bytea) DecodeBinary(src []byte) error { return nil } -func (src Bytea) EncodeText(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src Bytea) EncodeText(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } - str := hex.EncodeToString(src.Bytes) - - _, err := pgio.WriteInt32(w, int32(len(str)+2)) + _, err := io.WriteString(w, `\x`) if err != nil { - return nil + return false, err } - _, err = io.WriteString(w, `\x`) - if err != nil { - return nil - } - - _, err = io.WriteString(w, str) - return err + _, err = io.WriteString(w, hex.EncodeToString(src.Bytes)) + return false, err } -func (src Bytea) EncodeBinary(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src Bytea) EncodeBinary(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } - _, err := pgio.WriteInt32(w, int32(len(src.Bytes))) - if err != nil { - return nil - } - - _, err = w.Write(src.Bytes) - return err + _, err := w.Write(src.Bytes) + return false, err } diff --git a/pgtype/cid.go b/pgtype/cid.go index f8d706d0..41b817bb 100644 --- a/pgtype/cid.go +++ b/pgtype/cid.go @@ -38,10 +38,10 @@ func (dst *CID) DecodeBinary(src []byte) error { return (*pguint32)(dst).DecodeBinary(src) } -func (src CID) EncodeText(w io.Writer) error { +func (src CID) EncodeText(w io.Writer) (bool, error) { return (pguint32)(src).EncodeText(w) } -func (src CID) EncodeBinary(w io.Writer) error { +func (src CID) EncodeBinary(w io.Writer) (bool, error) { return (pguint32)(src).EncodeBinary(w) } diff --git a/pgtype/cidrarray.go b/pgtype/cidrarray.go index d95eef4a..cb81d2b9 100644 --- a/pgtype/cidrarray.go +++ b/pgtype/cidrarray.go @@ -22,10 +22,10 @@ func (dst *CidrArray) DecodeBinary(src []byte) error { return (*InetArray)(dst).DecodeBinary(src) } -func (src *CidrArray) EncodeText(w io.Writer) error { +func (src *CidrArray) EncodeText(w io.Writer) (bool, error) { return (*InetArray)(src).EncodeText(w) } -func (src *CidrArray) EncodeBinary(w io.Writer) error { +func (src *CidrArray) EncodeBinary(w io.Writer) (bool, error) { return (*InetArray)(src).encodeBinary(w, CidrOID) } diff --git a/pgtype/date.go b/pgtype/date.go index 1bb81d35..b0d16e64 100644 --- a/pgtype/date.go +++ b/pgtype/date.go @@ -116,9 +116,12 @@ func (dst *Date) DecodeBinary(src []byte) error { return nil } -func (src Date) EncodeText(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src Date) EncodeText(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } var s string @@ -132,23 +135,16 @@ func (src Date) EncodeText(w io.Writer) error { s = "-infinity" } - _, err := pgio.WriteInt32(w, int32(len(s))) - if err != nil { - return nil - } - - _, err = w.Write([]byte(s)) - return err + _, err := io.WriteString(w, s) + return false, err } -func (src Date) 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 +func (src Date) EncodeBinary(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } var daysSinceDateEpoch int32 @@ -165,6 +161,6 @@ func (src Date) EncodeBinary(w io.Writer) error { daysSinceDateEpoch = negativeInfinityDayOffset } - _, err = pgio.WriteInt32(w, daysSinceDateEpoch) - return err + _, err := pgio.WriteInt32(w, daysSinceDateEpoch) + return false, err } diff --git a/pgtype/datearray.go b/pgtype/datearray.go index e9ad1f62..9552739b 100644 --- a/pgtype/datearray.go +++ b/pgtype/datearray.go @@ -153,26 +153,22 @@ func (dst *DateArray) DecodeBinary(src []byte) error { return nil } -func (src *DateArray) EncodeText(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src *DateArray) EncodeText(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } if len(src.Dimensions) == 0 { - _, err := pgio.WriteInt32(w, 2) - if err != nil { - return err - } - - _, err = w.Write([]byte("{}")) - return err + _, err := io.WriteString(w, "{}") + return false, err } - buf := &bytes.Buffer{} - - err := EncodeTextArrayDimensions(buf, src.Dimensions) + err := EncodeTextArrayDimensions(w, src.Dimensions) if err != nil { - return err + return false, err } // dimElemCounts is the multiples of elements that each array lies on. For @@ -186,100 +182,112 @@ func (src *DateArray) EncodeText(w io.Writer) error { dimElemCounts[i] = int(src.Dimensions[i].Length) * dimElemCounts[i+1] } - textElementWriter := NewTextElementWriter(buf) - for i, elem := range src.Elements { if i > 0 { - err = pgio.WriteByte(buf, ',') + err = pgio.WriteByte(w, ',') if err != nil { - return err + return false, err } } for _, dec := range dimElemCounts { if i%dec == 0 { - err = pgio.WriteByte(buf, '{') + err = pgio.WriteByte(w, '{') if err != nil { - return err + return false, err } } } - textElementWriter.Reset() - err = elem.EncodeText(textElementWriter) + elemBuf := &bytes.Buffer{} + null, err := elem.EncodeText(elemBuf) if err != nil { - return err + return false, err + } + if null { + _, err = io.WriteString(w, `NULL`) + if err != nil { + return false, err + } + } else if elemBuf.Len() == 0 { + _, err = io.WriteString(w, `""`) + if err != nil { + return false, err + } + } else { + _, err = elemBuf.WriteTo(w) + if err != nil { + return false, err + } } for _, dec := range dimElemCounts { if (i+1)%dec == 0 { - err = pgio.WriteByte(buf, '}') + err = pgio.WriteByte(w, '}') if err != nil { - return err + return false, err } } } } - _, err = pgio.WriteInt32(w, int32(buf.Len())) - if err != nil { - return err - } - - _, err = buf.WriteTo(w) - return err + return false, nil } -func (src *DateArray) EncodeBinary(w io.Writer) error { +func (src *DateArray) EncodeBinary(w io.Writer) (bool, error) { return src.encodeBinary(w, DateOID) } -func (src *DateArray) encodeBinary(w io.Writer, elementOID int32) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src *DateArray) encodeBinary(w io.Writer, elementOID int32) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } - var arrayHeader ArrayHeader + arrayHeader := ArrayHeader{ + ElementOID: elementOID, + Dimensions: src.Dimensions, + } + + for i := range src.Elements { + if src.Elements[i].Status == Null { + arrayHeader.ContainsNull = true + break + } + } + + err := arrayHeader.EncodeBinary(w) + if err != nil { + return false, err + } - // TODO - consider how to avoid having to buffer array before writing length - - // or how not pay allocations for the byte order conversions. elemBuf := &bytes.Buffer{} for i := range src.Elements { - err := src.Elements[i].EncodeBinary(elemBuf) + elemBuf.Reset() + + null, err := src.Elements[i].EncodeBinary(elemBuf) if err != nil { - return err + return false, err } - if src.Elements[i].Status == Null { - arrayHeader.ContainsNull = true + if null { + _, err = pgio.WriteInt32(w, -1) + if err != nil { + return false, err + } + } else { + _, err = pgio.WriteInt32(w, int32(elemBuf.Len())) + if err != nil { + return false, err + } + _, err = elemBuf.WriteTo(w) + if err != nil { + return false, err + } } } - arrayHeader.ElementOID = elementOID - arrayHeader.Dimensions = src.Dimensions - - // TODO - consider how to avoid having to buffer array before writing length - - // or how not pay allocations for the byte order conversions. - headerBuf := &bytes.Buffer{} - err := arrayHeader.EncodeBinary(headerBuf) - if err != nil { - return err - } - - _, err = pgio.WriteInt32(w, int32(headerBuf.Len()+elemBuf.Len())) - if err != nil { - return err - } - - _, err = headerBuf.WriteTo(w) - if err != nil { - return err - } - - _, err = elemBuf.WriteTo(w) - if err != nil { - return err - } - - return err + return false, err } diff --git a/pgtype/float4.go b/pgtype/float4.go index fb0415e5..26609ab2 100644 --- a/pgtype/float4.go +++ b/pgtype/float4.go @@ -124,30 +124,26 @@ func (dst *Float4) DecodeBinary(src []byte) error { return nil } -func (src Float4) EncodeText(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src Float4) EncodeText(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } - s := strconv.FormatFloat(float64(src.Float), 'f', -1, 32) - _, err := pgio.WriteInt32(w, int32(len(s))) - if err != nil { - return nil - } - _, err = w.Write([]byte(s)) - return err + _, err := io.WriteString(w, strconv.FormatFloat(float64(src.Float), 'f', -1, 32)) + return false, err } -func (src Float4) EncodeBinary(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src Float4) EncodeBinary(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } - _, err := pgio.WriteInt32(w, 4) - if err != nil { - return err - } - - _, err = pgio.WriteInt32(w, int32(math.Float32bits(src.Float))) - return err + _, err := pgio.WriteInt32(w, int32(math.Float32bits(src.Float))) + return false, err } diff --git a/pgtype/float4array.go b/pgtype/float4array.go index a4a72146..9ab08dcc 100644 --- a/pgtype/float4array.go +++ b/pgtype/float4array.go @@ -152,26 +152,22 @@ func (dst *Float4Array) DecodeBinary(src []byte) error { return nil } -func (src *Float4Array) EncodeText(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src *Float4Array) EncodeText(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } if len(src.Dimensions) == 0 { - _, err := pgio.WriteInt32(w, 2) - if err != nil { - return err - } - - _, err = w.Write([]byte("{}")) - return err + _, err := io.WriteString(w, "{}") + return false, err } - buf := &bytes.Buffer{} - - err := EncodeTextArrayDimensions(buf, src.Dimensions) + err := EncodeTextArrayDimensions(w, src.Dimensions) if err != nil { - return err + return false, err } // dimElemCounts is the multiples of elements that each array lies on. For @@ -185,100 +181,112 @@ func (src *Float4Array) EncodeText(w io.Writer) error { dimElemCounts[i] = int(src.Dimensions[i].Length) * dimElemCounts[i+1] } - textElementWriter := NewTextElementWriter(buf) - for i, elem := range src.Elements { if i > 0 { - err = pgio.WriteByte(buf, ',') + err = pgio.WriteByte(w, ',') if err != nil { - return err + return false, err } } for _, dec := range dimElemCounts { if i%dec == 0 { - err = pgio.WriteByte(buf, '{') + err = pgio.WriteByte(w, '{') if err != nil { - return err + return false, err } } } - textElementWriter.Reset() - err = elem.EncodeText(textElementWriter) + elemBuf := &bytes.Buffer{} + null, err := elem.EncodeText(elemBuf) if err != nil { - return err + return false, err + } + if null { + _, err = io.WriteString(w, `NULL`) + if err != nil { + return false, err + } + } else if elemBuf.Len() == 0 { + _, err = io.WriteString(w, `""`) + if err != nil { + return false, err + } + } else { + _, err = elemBuf.WriteTo(w) + if err != nil { + return false, err + } } for _, dec := range dimElemCounts { if (i+1)%dec == 0 { - err = pgio.WriteByte(buf, '}') + err = pgio.WriteByte(w, '}') if err != nil { - return err + return false, err } } } } - _, err = pgio.WriteInt32(w, int32(buf.Len())) - if err != nil { - return err - } - - _, err = buf.WriteTo(w) - return err + return false, nil } -func (src *Float4Array) EncodeBinary(w io.Writer) error { +func (src *Float4Array) EncodeBinary(w io.Writer) (bool, error) { return src.encodeBinary(w, Float4OID) } -func (src *Float4Array) encodeBinary(w io.Writer, elementOID int32) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src *Float4Array) encodeBinary(w io.Writer, elementOID int32) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } - var arrayHeader ArrayHeader + arrayHeader := ArrayHeader{ + ElementOID: elementOID, + Dimensions: src.Dimensions, + } + + for i := range src.Elements { + if src.Elements[i].Status == Null { + arrayHeader.ContainsNull = true + break + } + } + + err := arrayHeader.EncodeBinary(w) + if err != nil { + return false, err + } - // TODO - consider how to avoid having to buffer array before writing length - - // or how not pay allocations for the byte order conversions. elemBuf := &bytes.Buffer{} for i := range src.Elements { - err := src.Elements[i].EncodeBinary(elemBuf) + elemBuf.Reset() + + null, err := src.Elements[i].EncodeBinary(elemBuf) if err != nil { - return err + return false, err } - if src.Elements[i].Status == Null { - arrayHeader.ContainsNull = true + if null { + _, err = pgio.WriteInt32(w, -1) + if err != nil { + return false, err + } + } else { + _, err = pgio.WriteInt32(w, int32(elemBuf.Len())) + if err != nil { + return false, err + } + _, err = elemBuf.WriteTo(w) + if err != nil { + return false, err + } } } - arrayHeader.ElementOID = elementOID - arrayHeader.Dimensions = src.Dimensions - - // TODO - consider how to avoid having to buffer array before writing length - - // or how not pay allocations for the byte order conversions. - headerBuf := &bytes.Buffer{} - err := arrayHeader.EncodeBinary(headerBuf) - if err != nil { - return err - } - - _, err = pgio.WriteInt32(w, int32(headerBuf.Len()+elemBuf.Len())) - if err != nil { - return err - } - - _, err = headerBuf.WriteTo(w) - if err != nil { - return err - } - - _, err = elemBuf.WriteTo(w) - if err != nil { - return err - } - - return err + return false, err } diff --git a/pgtype/float8.go b/pgtype/float8.go index a53de5e3..9ec9a665 100644 --- a/pgtype/float8.go +++ b/pgtype/float8.go @@ -114,30 +114,26 @@ func (dst *Float8) DecodeBinary(src []byte) error { return nil } -func (src Float8) EncodeText(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src Float8) EncodeText(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } - s := strconv.FormatFloat(float64(src.Float), 'f', -1, 64) - _, err := pgio.WriteInt32(w, int32(len(s))) - if err != nil { - return nil - } - _, err = w.Write([]byte(s)) - return err + _, err := io.WriteString(w, strconv.FormatFloat(float64(src.Float), 'f', -1, 64)) + return false, err } -func (src Float8) EncodeBinary(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src Float8) EncodeBinary(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } - _, err := pgio.WriteInt32(w, 8) - if err != nil { - return err - } - - _, err = pgio.WriteInt64(w, int64(math.Float64bits(src.Float))) - return err + _, err := pgio.WriteInt64(w, int64(math.Float64bits(src.Float))) + return false, err } diff --git a/pgtype/float8array.go b/pgtype/float8array.go index 082e817d..ce7e3b90 100644 --- a/pgtype/float8array.go +++ b/pgtype/float8array.go @@ -152,26 +152,22 @@ func (dst *Float8Array) DecodeBinary(src []byte) error { return nil } -func (src *Float8Array) EncodeText(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src *Float8Array) EncodeText(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } if len(src.Dimensions) == 0 { - _, err := pgio.WriteInt32(w, 2) - if err != nil { - return err - } - - _, err = w.Write([]byte("{}")) - return err + _, err := io.WriteString(w, "{}") + return false, err } - buf := &bytes.Buffer{} - - err := EncodeTextArrayDimensions(buf, src.Dimensions) + err := EncodeTextArrayDimensions(w, src.Dimensions) if err != nil { - return err + return false, err } // dimElemCounts is the multiples of elements that each array lies on. For @@ -185,100 +181,112 @@ func (src *Float8Array) EncodeText(w io.Writer) error { dimElemCounts[i] = int(src.Dimensions[i].Length) * dimElemCounts[i+1] } - textElementWriter := NewTextElementWriter(buf) - for i, elem := range src.Elements { if i > 0 { - err = pgio.WriteByte(buf, ',') + err = pgio.WriteByte(w, ',') if err != nil { - return err + return false, err } } for _, dec := range dimElemCounts { if i%dec == 0 { - err = pgio.WriteByte(buf, '{') + err = pgio.WriteByte(w, '{') if err != nil { - return err + return false, err } } } - textElementWriter.Reset() - err = elem.EncodeText(textElementWriter) + elemBuf := &bytes.Buffer{} + null, err := elem.EncodeText(elemBuf) if err != nil { - return err + return false, err + } + if null { + _, err = io.WriteString(w, `NULL`) + if err != nil { + return false, err + } + } else if elemBuf.Len() == 0 { + _, err = io.WriteString(w, `""`) + if err != nil { + return false, err + } + } else { + _, err = elemBuf.WriteTo(w) + if err != nil { + return false, err + } } for _, dec := range dimElemCounts { if (i+1)%dec == 0 { - err = pgio.WriteByte(buf, '}') + err = pgio.WriteByte(w, '}') if err != nil { - return err + return false, err } } } } - _, err = pgio.WriteInt32(w, int32(buf.Len())) - if err != nil { - return err - } - - _, err = buf.WriteTo(w) - return err + return false, nil } -func (src *Float8Array) EncodeBinary(w io.Writer) error { +func (src *Float8Array) EncodeBinary(w io.Writer) (bool, error) { return src.encodeBinary(w, Float8OID) } -func (src *Float8Array) encodeBinary(w io.Writer, elementOID int32) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src *Float8Array) encodeBinary(w io.Writer, elementOID int32) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } - var arrayHeader ArrayHeader + arrayHeader := ArrayHeader{ + ElementOID: elementOID, + Dimensions: src.Dimensions, + } + + for i := range src.Elements { + if src.Elements[i].Status == Null { + arrayHeader.ContainsNull = true + break + } + } + + err := arrayHeader.EncodeBinary(w) + if err != nil { + return false, err + } - // TODO - consider how to avoid having to buffer array before writing length - - // or how not pay allocations for the byte order conversions. elemBuf := &bytes.Buffer{} for i := range src.Elements { - err := src.Elements[i].EncodeBinary(elemBuf) + elemBuf.Reset() + + null, err := src.Elements[i].EncodeBinary(elemBuf) if err != nil { - return err + return false, err } - if src.Elements[i].Status == Null { - arrayHeader.ContainsNull = true + if null { + _, err = pgio.WriteInt32(w, -1) + if err != nil { + return false, err + } + } else { + _, err = pgio.WriteInt32(w, int32(elemBuf.Len())) + if err != nil { + return false, err + } + _, err = elemBuf.WriteTo(w) + if err != nil { + return false, err + } } } - arrayHeader.ElementOID = elementOID - arrayHeader.Dimensions = src.Dimensions - - // TODO - consider how to avoid having to buffer array before writing length - - // or how not pay allocations for the byte order conversions. - headerBuf := &bytes.Buffer{} - err := arrayHeader.EncodeBinary(headerBuf) - if err != nil { - return err - } - - _, err = pgio.WriteInt32(w, int32(headerBuf.Len()+elemBuf.Len())) - if err != nil { - return err - } - - _, err = headerBuf.WriteTo(w) - if err != nil { - return err - } - - _, err = elemBuf.WriteTo(w) - if err != nil { - return err - } - - return err + return false, err } diff --git a/pgtype/inet.go b/pgtype/inet.go index 132a876a..f94622f4 100644 --- a/pgtype/inet.go +++ b/pgtype/inet.go @@ -144,61 +144,55 @@ func (dst *Inet) DecodeBinary(src []byte) error { return nil } -func (src Inet) EncodeText(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src Inet) EncodeText(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } - s := src.IPNet.String() - _, err := pgio.WriteInt32(w, int32(len(s))) - if err != nil { - return nil - } - _, err = w.Write([]byte(s)) - return err + _, err := io.WriteString(w, src.IPNet.String()) + return false, err } // EncodeBinary encodes src into w. -func (src Inet) EncodeBinary(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src Inet) EncodeBinary(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } - var size int32 var family byte switch len(src.IPNet.IP) { case net.IPv4len: - size = 8 family = defaultAFInet case net.IPv6len: - size = 20 family = defaultAFInet6 default: - return fmt.Errorf("Unexpected IP length: %v", len(src.IPNet.IP)) - } - - if _, err := pgio.WriteInt32(w, size); err != nil { - return err + return false, fmt.Errorf("Unexpected IP length: %v", len(src.IPNet.IP)) } if err := pgio.WriteByte(w, family); err != nil { - return err + return false, err } ones, _ := src.IPNet.Mask.Size() if err := pgio.WriteByte(w, byte(ones)); err != nil { - return err + return false, err } // is_cidr is ignored on server if err := pgio.WriteByte(w, 0); err != nil { - return err + return false, err } if err := pgio.WriteByte(w, byte(len(src.IPNet.IP))); err != nil { - return err + return false, err } _, err := w.Write(src.IPNet.IP) - return err + return false, err } diff --git a/pgtype/inetarray.go b/pgtype/inetarray.go index 28de736f..32cde554 100644 --- a/pgtype/inetarray.go +++ b/pgtype/inetarray.go @@ -184,26 +184,22 @@ func (dst *InetArray) DecodeBinary(src []byte) error { return nil } -func (src *InetArray) EncodeText(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src *InetArray) EncodeText(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } if len(src.Dimensions) == 0 { - _, err := pgio.WriteInt32(w, 2) - if err != nil { - return err - } - - _, err = w.Write([]byte("{}")) - return err + _, err := io.WriteString(w, "{}") + return false, err } - buf := &bytes.Buffer{} - - err := EncodeTextArrayDimensions(buf, src.Dimensions) + err := EncodeTextArrayDimensions(w, src.Dimensions) if err != nil { - return err + return false, err } // dimElemCounts is the multiples of elements that each array lies on. For @@ -217,100 +213,112 @@ func (src *InetArray) EncodeText(w io.Writer) error { dimElemCounts[i] = int(src.Dimensions[i].Length) * dimElemCounts[i+1] } - textElementWriter := NewTextElementWriter(buf) - for i, elem := range src.Elements { if i > 0 { - err = pgio.WriteByte(buf, ',') + err = pgio.WriteByte(w, ',') if err != nil { - return err + return false, err } } for _, dec := range dimElemCounts { if i%dec == 0 { - err = pgio.WriteByte(buf, '{') + err = pgio.WriteByte(w, '{') if err != nil { - return err + return false, err } } } - textElementWriter.Reset() - err = elem.EncodeText(textElementWriter) + elemBuf := &bytes.Buffer{} + null, err := elem.EncodeText(elemBuf) if err != nil { - return err + return false, err + } + if null { + _, err = io.WriteString(w, `NULL`) + if err != nil { + return false, err + } + } else if elemBuf.Len() == 0 { + _, err = io.WriteString(w, `""`) + if err != nil { + return false, err + } + } else { + _, err = elemBuf.WriteTo(w) + if err != nil { + return false, err + } } for _, dec := range dimElemCounts { if (i+1)%dec == 0 { - err = pgio.WriteByte(buf, '}') + err = pgio.WriteByte(w, '}') if err != nil { - return err + return false, err } } } } - _, err = pgio.WriteInt32(w, int32(buf.Len())) - if err != nil { - return err - } - - _, err = buf.WriteTo(w) - return err + return false, nil } -func (src *InetArray) EncodeBinary(w io.Writer) error { +func (src *InetArray) EncodeBinary(w io.Writer) (bool, error) { return src.encodeBinary(w, InetOID) } -func (src *InetArray) encodeBinary(w io.Writer, elementOID int32) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src *InetArray) encodeBinary(w io.Writer, elementOID int32) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } - var arrayHeader ArrayHeader + arrayHeader := ArrayHeader{ + ElementOID: elementOID, + Dimensions: src.Dimensions, + } + + for i := range src.Elements { + if src.Elements[i].Status == Null { + arrayHeader.ContainsNull = true + break + } + } + + err := arrayHeader.EncodeBinary(w) + if err != nil { + return false, err + } - // TODO - consider how to avoid having to buffer array before writing length - - // or how not pay allocations for the byte order conversions. elemBuf := &bytes.Buffer{} for i := range src.Elements { - err := src.Elements[i].EncodeBinary(elemBuf) + elemBuf.Reset() + + null, err := src.Elements[i].EncodeBinary(elemBuf) if err != nil { - return err + return false, err } - if src.Elements[i].Status == Null { - arrayHeader.ContainsNull = true + if null { + _, err = pgio.WriteInt32(w, -1) + if err != nil { + return false, err + } + } else { + _, err = pgio.WriteInt32(w, int32(elemBuf.Len())) + if err != nil { + return false, err + } + _, err = elemBuf.WriteTo(w) + if err != nil { + return false, err + } } } - arrayHeader.ElementOID = elementOID - arrayHeader.Dimensions = src.Dimensions - - // TODO - consider how to avoid having to buffer array before writing length - - // or how not pay allocations for the byte order conversions. - headerBuf := &bytes.Buffer{} - err := arrayHeader.EncodeBinary(headerBuf) - if err != nil { - return err - } - - _, err = pgio.WriteInt32(w, int32(headerBuf.Len()+elemBuf.Len())) - if err != nil { - return err - } - - _, err = headerBuf.WriteTo(w) - if err != nil { - return err - } - - _, err = elemBuf.WriteTo(w) - if err != nil { - return err - } - - return err + return false, err } diff --git a/pgtype/int2.go b/pgtype/int2.go index 51346a43..7bdbacfe 100644 --- a/pgtype/int2.go +++ b/pgtype/int2.go @@ -119,30 +119,26 @@ func (dst *Int2) DecodeBinary(src []byte) error { return nil } -func (src Int2) EncodeText(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src Int2) EncodeText(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } - s := strconv.FormatInt(int64(src.Int), 10) - _, err := pgio.WriteInt32(w, int32(len(s))) - if err != nil { - return nil - } - _, err = w.Write([]byte(s)) - return err + _, err := io.WriteString(w, strconv.FormatInt(int64(src.Int), 10)) + return false, err } -func (src Int2) EncodeBinary(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src Int2) EncodeBinary(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } - _, err := pgio.WriteInt32(w, 2) - if err != nil { - return err - } - - _, err = pgio.WriteInt16(w, src.Int) - return err + _, err := pgio.WriteInt16(w, src.Int) + return false, err } diff --git a/pgtype/int2array.go b/pgtype/int2array.go index 71760e1e..f7cc2492 100644 --- a/pgtype/int2array.go +++ b/pgtype/int2array.go @@ -183,26 +183,22 @@ func (dst *Int2Array) DecodeBinary(src []byte) error { return nil } -func (src *Int2Array) EncodeText(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src *Int2Array) EncodeText(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } if len(src.Dimensions) == 0 { - _, err := pgio.WriteInt32(w, 2) - if err != nil { - return err - } - - _, err = w.Write([]byte("{}")) - return err + _, err := io.WriteString(w, "{}") + return false, err } - buf := &bytes.Buffer{} - - err := EncodeTextArrayDimensions(buf, src.Dimensions) + err := EncodeTextArrayDimensions(w, src.Dimensions) if err != nil { - return err + return false, err } // dimElemCounts is the multiples of elements that each array lies on. For @@ -216,100 +212,112 @@ func (src *Int2Array) EncodeText(w io.Writer) error { dimElemCounts[i] = int(src.Dimensions[i].Length) * dimElemCounts[i+1] } - textElementWriter := NewTextElementWriter(buf) - for i, elem := range src.Elements { if i > 0 { - err = pgio.WriteByte(buf, ',') + err = pgio.WriteByte(w, ',') if err != nil { - return err + return false, err } } for _, dec := range dimElemCounts { if i%dec == 0 { - err = pgio.WriteByte(buf, '{') + err = pgio.WriteByte(w, '{') if err != nil { - return err + return false, err } } } - textElementWriter.Reset() - err = elem.EncodeText(textElementWriter) + elemBuf := &bytes.Buffer{} + null, err := elem.EncodeText(elemBuf) if err != nil { - return err + return false, err + } + if null { + _, err = io.WriteString(w, `NULL`) + if err != nil { + return false, err + } + } else if elemBuf.Len() == 0 { + _, err = io.WriteString(w, `""`) + if err != nil { + return false, err + } + } else { + _, err = elemBuf.WriteTo(w) + if err != nil { + return false, err + } } for _, dec := range dimElemCounts { if (i+1)%dec == 0 { - err = pgio.WriteByte(buf, '}') + err = pgio.WriteByte(w, '}') if err != nil { - return err + return false, err } } } } - _, err = pgio.WriteInt32(w, int32(buf.Len())) - if err != nil { - return err - } - - _, err = buf.WriteTo(w) - return err + return false, nil } -func (src *Int2Array) EncodeBinary(w io.Writer) error { +func (src *Int2Array) EncodeBinary(w io.Writer) (bool, error) { return src.encodeBinary(w, Int2OID) } -func (src *Int2Array) encodeBinary(w io.Writer, elementOID int32) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src *Int2Array) encodeBinary(w io.Writer, elementOID int32) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } - var arrayHeader ArrayHeader + arrayHeader := ArrayHeader{ + ElementOID: elementOID, + Dimensions: src.Dimensions, + } + + for i := range src.Elements { + if src.Elements[i].Status == Null { + arrayHeader.ContainsNull = true + break + } + } + + err := arrayHeader.EncodeBinary(w) + if err != nil { + return false, err + } - // TODO - consider how to avoid having to buffer array before writing length - - // or how not pay allocations for the byte order conversions. elemBuf := &bytes.Buffer{} for i := range src.Elements { - err := src.Elements[i].EncodeBinary(elemBuf) + elemBuf.Reset() + + null, err := src.Elements[i].EncodeBinary(elemBuf) if err != nil { - return err + return false, err } - if src.Elements[i].Status == Null { - arrayHeader.ContainsNull = true + if null { + _, err = pgio.WriteInt32(w, -1) + if err != nil { + return false, err + } + } else { + _, err = pgio.WriteInt32(w, int32(elemBuf.Len())) + if err != nil { + return false, err + } + _, err = elemBuf.WriteTo(w) + if err != nil { + return false, err + } } } - arrayHeader.ElementOID = elementOID - arrayHeader.Dimensions = src.Dimensions - - // TODO - consider how to avoid having to buffer array before writing length - - // or how not pay allocations for the byte order conversions. - headerBuf := &bytes.Buffer{} - err := arrayHeader.EncodeBinary(headerBuf) - if err != nil { - return err - } - - _, err = pgio.WriteInt32(w, int32(headerBuf.Len()+elemBuf.Len())) - if err != nil { - return err - } - - _, err = headerBuf.WriteTo(w) - if err != nil { - return err - } - - _, err = elemBuf.WriteTo(w) - if err != nil { - return err - } - - return err + return false, err } diff --git a/pgtype/int4.go b/pgtype/int4.go index 8a53d454..2d96ea48 100644 --- a/pgtype/int4.go +++ b/pgtype/int4.go @@ -110,30 +110,26 @@ func (dst *Int4) DecodeBinary(src []byte) error { return nil } -func (src Int4) EncodeText(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src Int4) EncodeText(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } - s := strconv.FormatInt(int64(src.Int), 10) - _, err := pgio.WriteInt32(w, int32(len(s))) - if err != nil { - return nil - } - _, err = w.Write([]byte(s)) - return err + _, err := io.WriteString(w, strconv.FormatInt(int64(src.Int), 10)) + return false, err } -func (src Int4) EncodeBinary(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src Int4) EncodeBinary(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } - _, err := pgio.WriteInt32(w, 4) - if err != nil { - return err - } - - _, err = pgio.WriteInt32(w, src.Int) - return err + _, err := pgio.WriteInt32(w, src.Int) + return false, err } diff --git a/pgtype/int4array.go b/pgtype/int4array.go index 6a202b08..fa710af7 100644 --- a/pgtype/int4array.go +++ b/pgtype/int4array.go @@ -183,26 +183,22 @@ func (dst *Int4Array) DecodeBinary(src []byte) error { return nil } -func (src *Int4Array) EncodeText(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src *Int4Array) EncodeText(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } if len(src.Dimensions) == 0 { - _, err := pgio.WriteInt32(w, 2) - if err != nil { - return err - } - - _, err = w.Write([]byte("{}")) - return err + _, err := io.WriteString(w, "{}") + return false, err } - buf := &bytes.Buffer{} - - err := EncodeTextArrayDimensions(buf, src.Dimensions) + err := EncodeTextArrayDimensions(w, src.Dimensions) if err != nil { - return err + return false, err } // dimElemCounts is the multiples of elements that each array lies on. For @@ -216,100 +212,112 @@ func (src *Int4Array) EncodeText(w io.Writer) error { dimElemCounts[i] = int(src.Dimensions[i].Length) * dimElemCounts[i+1] } - textElementWriter := NewTextElementWriter(buf) - for i, elem := range src.Elements { if i > 0 { - err = pgio.WriteByte(buf, ',') + err = pgio.WriteByte(w, ',') if err != nil { - return err + return false, err } } for _, dec := range dimElemCounts { if i%dec == 0 { - err = pgio.WriteByte(buf, '{') + err = pgio.WriteByte(w, '{') if err != nil { - return err + return false, err } } } - textElementWriter.Reset() - err = elem.EncodeText(textElementWriter) + elemBuf := &bytes.Buffer{} + null, err := elem.EncodeText(elemBuf) if err != nil { - return err + return false, err + } + if null { + _, err = io.WriteString(w, `NULL`) + if err != nil { + return false, err + } + } else if elemBuf.Len() == 0 { + _, err = io.WriteString(w, `""`) + if err != nil { + return false, err + } + } else { + _, err = elemBuf.WriteTo(w) + if err != nil { + return false, err + } } for _, dec := range dimElemCounts { if (i+1)%dec == 0 { - err = pgio.WriteByte(buf, '}') + err = pgio.WriteByte(w, '}') if err != nil { - return err + return false, err } } } } - _, err = pgio.WriteInt32(w, int32(buf.Len())) - if err != nil { - return err - } - - _, err = buf.WriteTo(w) - return err + return false, nil } -func (src *Int4Array) EncodeBinary(w io.Writer) error { +func (src *Int4Array) EncodeBinary(w io.Writer) (bool, error) { return src.encodeBinary(w, Int4OID) } -func (src *Int4Array) encodeBinary(w io.Writer, elementOID int32) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src *Int4Array) encodeBinary(w io.Writer, elementOID int32) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } - var arrayHeader ArrayHeader + arrayHeader := ArrayHeader{ + ElementOID: elementOID, + Dimensions: src.Dimensions, + } + + for i := range src.Elements { + if src.Elements[i].Status == Null { + arrayHeader.ContainsNull = true + break + } + } + + err := arrayHeader.EncodeBinary(w) + if err != nil { + return false, err + } - // TODO - consider how to avoid having to buffer array before writing length - - // or how not pay allocations for the byte order conversions. elemBuf := &bytes.Buffer{} for i := range src.Elements { - err := src.Elements[i].EncodeBinary(elemBuf) + elemBuf.Reset() + + null, err := src.Elements[i].EncodeBinary(elemBuf) if err != nil { - return err + return false, err } - if src.Elements[i].Status == Null { - arrayHeader.ContainsNull = true + if null { + _, err = pgio.WriteInt32(w, -1) + if err != nil { + return false, err + } + } else { + _, err = pgio.WriteInt32(w, int32(elemBuf.Len())) + if err != nil { + return false, err + } + _, err = elemBuf.WriteTo(w) + if err != nil { + return false, err + } } } - arrayHeader.ElementOID = elementOID - arrayHeader.Dimensions = src.Dimensions - - // TODO - consider how to avoid having to buffer array before writing length - - // or how not pay allocations for the byte order conversions. - headerBuf := &bytes.Buffer{} - err := arrayHeader.EncodeBinary(headerBuf) - if err != nil { - return err - } - - _, err = pgio.WriteInt32(w, int32(headerBuf.Len()+elemBuf.Len())) - if err != nil { - return err - } - - _, err = headerBuf.WriteTo(w) - if err != nil { - return err - } - - _, err = elemBuf.WriteTo(w) - if err != nil { - return err - } - - return err + return false, err } diff --git a/pgtype/int8.go b/pgtype/int8.go index c6bedaa6..91f5b877 100644 --- a/pgtype/int8.go +++ b/pgtype/int8.go @@ -102,30 +102,26 @@ func (dst *Int8) DecodeBinary(src []byte) error { return nil } -func (src Int8) EncodeText(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src Int8) EncodeText(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } - s := strconv.FormatInt(src.Int, 10) - _, err := pgio.WriteInt32(w, int32(len(s))) - if err != nil { - return nil - } - _, err = w.Write([]byte(s)) - return err + _, err := io.WriteString(w, strconv.FormatInt(src.Int, 10)) + return false, err } -func (src Int8) EncodeBinary(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src Int8) EncodeBinary(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } - _, err := pgio.WriteInt32(w, 8) - if err != nil { - return err - } - - _, err = pgio.WriteInt64(w, src.Int) - return err + _, err := pgio.WriteInt64(w, src.Int) + return false, err } diff --git a/pgtype/int8array.go b/pgtype/int8array.go index f621618e..65f42477 100644 --- a/pgtype/int8array.go +++ b/pgtype/int8array.go @@ -183,26 +183,22 @@ func (dst *Int8Array) DecodeBinary(src []byte) error { return nil } -func (src *Int8Array) EncodeText(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src *Int8Array) EncodeText(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } if len(src.Dimensions) == 0 { - _, err := pgio.WriteInt32(w, 2) - if err != nil { - return err - } - - _, err = w.Write([]byte("{}")) - return err + _, err := io.WriteString(w, "{}") + return false, err } - buf := &bytes.Buffer{} - - err := EncodeTextArrayDimensions(buf, src.Dimensions) + err := EncodeTextArrayDimensions(w, src.Dimensions) if err != nil { - return err + return false, err } // dimElemCounts is the multiples of elements that each array lies on. For @@ -216,100 +212,112 @@ func (src *Int8Array) EncodeText(w io.Writer) error { dimElemCounts[i] = int(src.Dimensions[i].Length) * dimElemCounts[i+1] } - textElementWriter := NewTextElementWriter(buf) - for i, elem := range src.Elements { if i > 0 { - err = pgio.WriteByte(buf, ',') + err = pgio.WriteByte(w, ',') if err != nil { - return err + return false, err } } for _, dec := range dimElemCounts { if i%dec == 0 { - err = pgio.WriteByte(buf, '{') + err = pgio.WriteByte(w, '{') if err != nil { - return err + return false, err } } } - textElementWriter.Reset() - err = elem.EncodeText(textElementWriter) + elemBuf := &bytes.Buffer{} + null, err := elem.EncodeText(elemBuf) if err != nil { - return err + return false, err + } + if null { + _, err = io.WriteString(w, `NULL`) + if err != nil { + return false, err + } + } else if elemBuf.Len() == 0 { + _, err = io.WriteString(w, `""`) + if err != nil { + return false, err + } + } else { + _, err = elemBuf.WriteTo(w) + if err != nil { + return false, err + } } for _, dec := range dimElemCounts { if (i+1)%dec == 0 { - err = pgio.WriteByte(buf, '}') + err = pgio.WriteByte(w, '}') if err != nil { - return err + return false, err } } } } - _, err = pgio.WriteInt32(w, int32(buf.Len())) - if err != nil { - return err - } - - _, err = buf.WriteTo(w) - return err + return false, nil } -func (src *Int8Array) EncodeBinary(w io.Writer) error { +func (src *Int8Array) EncodeBinary(w io.Writer) (bool, error) { return src.encodeBinary(w, Int8OID) } -func (src *Int8Array) encodeBinary(w io.Writer, elementOID int32) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src *Int8Array) encodeBinary(w io.Writer, elementOID int32) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } - var arrayHeader ArrayHeader + arrayHeader := ArrayHeader{ + ElementOID: elementOID, + Dimensions: src.Dimensions, + } + + for i := range src.Elements { + if src.Elements[i].Status == Null { + arrayHeader.ContainsNull = true + break + } + } + + err := arrayHeader.EncodeBinary(w) + if err != nil { + return false, err + } - // TODO - consider how to avoid having to buffer array before writing length - - // or how not pay allocations for the byte order conversions. elemBuf := &bytes.Buffer{} for i := range src.Elements { - err := src.Elements[i].EncodeBinary(elemBuf) + elemBuf.Reset() + + null, err := src.Elements[i].EncodeBinary(elemBuf) if err != nil { - return err + return false, err } - if src.Elements[i].Status == Null { - arrayHeader.ContainsNull = true + if null { + _, err = pgio.WriteInt32(w, -1) + if err != nil { + return false, err + } + } else { + _, err = pgio.WriteInt32(w, int32(elemBuf.Len())) + if err != nil { + return false, err + } + _, err = elemBuf.WriteTo(w) + if err != nil { + return false, err + } } } - arrayHeader.ElementOID = elementOID - arrayHeader.Dimensions = src.Dimensions - - // TODO - consider how to avoid having to buffer array before writing length - - // or how not pay allocations for the byte order conversions. - headerBuf := &bytes.Buffer{} - err := arrayHeader.EncodeBinary(headerBuf) - if err != nil { - return err - } - - _, err = pgio.WriteInt32(w, int32(headerBuf.Len()+elemBuf.Len())) - if err != nil { - return err - } - - _, err = headerBuf.WriteTo(w) - if err != nil { - return err - } - - _, err = elemBuf.WriteTo(w) - if err != nil { - return err - } - - return err + return false, err } diff --git a/pgtype/name.go b/pgtype/name.go index 4bbc43c1..513abfc7 100644 --- a/pgtype/name.go +++ b/pgtype/name.go @@ -35,10 +35,10 @@ func (dst *Name) DecodeBinary(src []byte) error { return (*Text)(dst).DecodeBinary(src) } -func (src Name) EncodeText(w io.Writer) error { +func (src Name) EncodeText(w io.Writer) (bool, error) { return (Text)(src).EncodeText(w) } -func (src Name) EncodeBinary(w io.Writer) error { +func (src Name) EncodeBinary(w io.Writer) (bool, error) { return (Text)(src).EncodeBinary(w) } diff --git a/pgtype/oid.go b/pgtype/oid.go index 2ea9c2d1..e1bee4cf 100644 --- a/pgtype/oid.go +++ b/pgtype/oid.go @@ -32,10 +32,10 @@ func (dst *OID) DecodeBinary(src []byte) error { return (*pguint32)(dst).DecodeBinary(src) } -func (src OID) EncodeText(w io.Writer) error { +func (src OID) EncodeText(w io.Writer) (bool, error) { return (pguint32)(src).EncodeText(w) } -func (src OID) EncodeBinary(w io.Writer) error { +func (src OID) EncodeBinary(w io.Writer) (bool, error) { return (pguint32)(src).EncodeBinary(w) } diff --git a/pgtype/pgtype.go b/pgtype/pgtype.go index 7928e1cc..d6cd53c1 100644 --- a/pgtype/pgtype.go +++ b/pgtype/pgtype.go @@ -3,8 +3,6 @@ package pgtype import ( "errors" "io" - - "github.com/jackc/pgx/pgio" ) // PostgreSQL oids for common types @@ -81,23 +79,24 @@ type TextDecoder interface { DecodeText(src []byte) error } +// BinaryEncoder is implemented by types that can encode themselves into the +// PostgreSQL binary wire format. type BinaryEncoder interface { - EncodeBinary(w io.Writer) error + // EncodeBinary should encode the binary format of self to w. If self is the + // SQL value NULL then write nothing and return (true, nil). The caller of + // EncodeBinary is responsible for writing the correct NULL value or the + // length of the data written. + EncodeBinary(w io.Writer) (null bool, err error) } +// TextEncoder is implemented by types that can encode themselves into the +// PostgreSQL text wire format. type TextEncoder interface { - EncodeText(w io.Writer) error + // EncodeText should encode the text format of self to w. If self is the SQL + // value NULL then write nothing and return (true, nil). The caller of + // EncodeText is responsible for writing the correct NULL value or the length + // of the data written. + EncodeText(w io.Writer) (null bool, err error) } var errUndefined = errors.New("cannot encode status undefined") - -func encodeNotPresent(w io.Writer, status Status) (done bool, err error) { - switch status { - case Undefined: - return true, errUndefined - case Null: - _, err = pgio.WriteInt32(w, -1) - return true, err - } - return false, nil -} diff --git a/pgtype/pgtype_test.go b/pgtype/pgtype_test.go index 6e173cbe..07a40160 100644 --- a/pgtype/pgtype_test.go +++ b/pgtype/pgtype_test.go @@ -60,7 +60,7 @@ type forceTextEncoder struct { e pgtype.TextEncoder } -func (f forceTextEncoder) EncodeText(w io.Writer) error { +func (f forceTextEncoder) EncodeText(w io.Writer) (bool, error) { return f.e.EncodeText(w) } @@ -68,7 +68,7 @@ type forceBinaryEncoder struct { e pgtype.BinaryEncoder } -func (f forceBinaryEncoder) EncodeBinary(w io.Writer) error { +func (f forceBinaryEncoder) EncodeBinary(w io.Writer) (bool, error) { return f.e.EncodeBinary(w) } diff --git a/pgtype/pguint32.go b/pgtype/pguint32.go index 9bf1eef6..df9e0d36 100644 --- a/pgtype/pguint32.go +++ b/pgtype/pguint32.go @@ -82,30 +82,26 @@ func (dst *pguint32) DecodeBinary(src []byte) error { return nil } -func (src pguint32) EncodeText(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src pguint32) EncodeText(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } - 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 + _, err := io.WriteString(w, strconv.FormatUint(uint64(src.Uint), 10)) + return false, err } -func (src pguint32) EncodeBinary(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src pguint32) EncodeBinary(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } - _, err := pgio.WriteInt32(w, 4) - if err != nil { - return err - } - - _, err = pgio.WriteUint32(w, src.Uint) - return err + _, err := pgio.WriteUint32(w, src.Uint) + return false, err } diff --git a/pgtype/qchar.go b/pgtype/qchar.go index 8abec935..0da1e88b 100644 --- a/pgtype/qchar.go +++ b/pgtype/qchar.go @@ -120,15 +120,13 @@ func (dst *QChar) DecodeBinary(src []byte) error { return nil } -func (src QChar) EncodeBinary(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src QChar) EncodeBinary(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } - _, err := pgio.WriteInt32(w, 1) - if err != nil { - return nil - } - - return pgio.WriteByte(w, byte(src.Int)) + return false, pgio.WriteByte(w, byte(src.Int)) } diff --git a/pgtype/text.go b/pgtype/text.go index 2951b5ad..baf62d1e 100644 --- a/pgtype/text.go +++ b/pgtype/text.go @@ -4,8 +4,6 @@ import ( "fmt" "io" "reflect" - - "github.com/jackc/pgx/pgio" ) type Text struct { @@ -85,20 +83,18 @@ func (dst *Text) DecodeBinary(src []byte) error { return dst.DecodeText(src) } -func (src Text) EncodeText(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src Text) EncodeText(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } - _, err := pgio.WriteInt32(w, int32(len(src.String))) - if err != nil { - return nil - } - - _, err = io.WriteString(w, src.String) - return err + _, err := io.WriteString(w, src.String) + return false, err } -func (src Text) EncodeBinary(w io.Writer) error { +func (src Text) EncodeBinary(w io.Writer) (bool, error) { return src.EncodeText(w) } diff --git a/pgtype/text_element.go b/pgtype/text_element.go deleted file mode 100644 index 1a585d08..00000000 --- a/pgtype/text_element.go +++ /dev/null @@ -1,112 +0,0 @@ -package pgtype - -import ( - "bytes" - "errors" - "io" - - "github.com/jackc/pgx/pgio" -) - -// TextElementWriter is a wrapper that makes TextEncoders composable into other -// TextEncoders. TextEncoder first writes the length of the subsequent value. -// This is not necessary when the value is part of another value such as an -// array. TextElementWriter requires one int32 to be written first which it -// ignores. No other integer writes are valid. -type TextElementWriter struct { - w io.Writer - lengthHeaderIgnored bool -} - -func NewTextElementWriter(w io.Writer) *TextElementWriter { - return &TextElementWriter{w: w} -} - -func (w *TextElementWriter) WriteUint16(n uint16) (int, error) { - return 0, errors.New("WriteUint16 should never be called on TextElementWriter") -} - -func (w *TextElementWriter) WriteUint32(n uint32) (int, error) { - if !w.lengthHeaderIgnored { - w.lengthHeaderIgnored = true - - if int32(n) == -1 { - return io.WriteString(w.w, "NULL") - } - - return 4, nil - } - - return 0, errors.New("WriteUint32 should only be called once on TextElementWriter") -} - -func (w *TextElementWriter) WriteUint64(n uint64) (int, error) { - if w.lengthHeaderIgnored { - return pgio.WriteUint64(w.w, n) - } - - return 0, errors.New("WriteUint64 should never be called on TextElementWriter") -} - -func (w *TextElementWriter) Write(buf []byte) (int, error) { - if w.lengthHeaderIgnored { - return w.w.Write(buf) - } - - return 0, errors.New("int32 must be written first") -} - -func (w *TextElementWriter) Reset() { - w.lengthHeaderIgnored = false -} - -// TextElementReader is a wrapper that makes TextDecoders composable into other -// TextDecoders. TextEncoders first read the length of the subsequent value. -// This length value is not present when the value is part of another value such -// as an array. TextElementReader provides a substitute length value from the -// length of the string. No other integer reads are valid. Each time DecodeText -// is called with a TextElementReader as the source the TextElementReader must -// first have Reset called with the new element string data. -type TextElementReader struct { - buf *bytes.Buffer - lengthHeaderIgnored bool -} - -func NewTextElementReader(r io.Reader) *TextElementReader { - return &TextElementReader{buf: &bytes.Buffer{}} -} - -func (r *TextElementReader) ReadUint16() (uint16, error) { - return 0, errors.New("ReadUint16 should never be called on TextElementReader") -} - -func (r *TextElementReader) ReadUint32() (uint32, error) { - if !r.lengthHeaderIgnored { - r.lengthHeaderIgnored = true - if r.buf.String() == "NULL" { - n32 := int32(-1) - return uint32(n32), nil - } - return uint32(r.buf.Len()), nil - } - - return 0, errors.New("ReadUint32 should only be called once on TextElementReader") -} - -func (r *TextElementReader) WriteUint64(n uint64) (int, error) { - return 0, errors.New("ReadUint64 should never be called on TextElementReader") -} - -func (r *TextElementReader) Read(buf []byte) (int, error) { - if r.lengthHeaderIgnored { - return r.buf.Read(buf) - } - - return 0, errors.New("int32 must be read first") -} - -func (r *TextElementReader) Reset(s string) { - r.lengthHeaderIgnored = false - r.buf.Reset() - r.buf.WriteString(s) -} diff --git a/pgtype/textarray.go b/pgtype/textarray.go index e7ca3578..c3e595e0 100644 --- a/pgtype/textarray.go +++ b/pgtype/textarray.go @@ -152,26 +152,22 @@ func (dst *TextArray) DecodeBinary(src []byte) error { return nil } -func (src *TextArray) EncodeText(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src *TextArray) EncodeText(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } if len(src.Dimensions) == 0 { - _, err := pgio.WriteInt32(w, 2) - if err != nil { - return err - } - - _, err = w.Write([]byte("{}")) - return err + _, err := io.WriteString(w, "{}") + return false, err } - buf := &bytes.Buffer{} - - err := EncodeTextArrayDimensions(buf, src.Dimensions) + err := EncodeTextArrayDimensions(w, src.Dimensions) if err != nil { - return err + return false, err } // dimElemCounts is the multiples of elements that each array lies on. For @@ -185,112 +181,112 @@ func (src *TextArray) EncodeText(w io.Writer) error { dimElemCounts[i] = int(src.Dimensions[i].Length) * dimElemCounts[i+1] } - textElementWriter := NewTextElementWriter(buf) - for i, elem := range src.Elements { if i > 0 { - err = pgio.WriteByte(buf, ',') + err = pgio.WriteByte(w, ',') if err != nil { - return err + return false, err } } for _, dec := range dimElemCounts { if i%dec == 0 { - err = pgio.WriteByte(buf, '{') + err = pgio.WriteByte(w, '{') if err != nil { - return err + return false, err } } } - textElementWriter.Reset() - if elem.Status == Null { - _, err := io.WriteString(buf, `"NULL"`) + elemBuf := &bytes.Buffer{} + null, err := elem.EncodeText(elemBuf) + if err != nil { + return false, err + } + if null { + _, err = io.WriteString(w, `"NULL"`) if err != nil { - return err + return false, err } - } else if elem.String == "" { - _, err := io.WriteString(buf, `""`) + } else if elemBuf.Len() == 0 { + _, err = io.WriteString(w, `""`) if err != nil { - return err + return false, err } } else { - err = elem.EncodeText(textElementWriter) + _, err = elemBuf.WriteTo(w) if err != nil { - return err + return false, err } } for _, dec := range dimElemCounts { if (i+1)%dec == 0 { - err = pgio.WriteByte(buf, '}') + err = pgio.WriteByte(w, '}') if err != nil { - return err + return false, err } } } } - _, err = pgio.WriteInt32(w, int32(buf.Len())) - if err != nil { - return err - } - - _, err = buf.WriteTo(w) - return err + return false, nil } -func (src *TextArray) EncodeBinary(w io.Writer) error { +func (src *TextArray) EncodeBinary(w io.Writer) (bool, error) { return src.encodeBinary(w, TextOID) } -func (src *TextArray) encodeBinary(w io.Writer, elementOID int32) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src *TextArray) encodeBinary(w io.Writer, elementOID int32) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } - var arrayHeader ArrayHeader + arrayHeader := ArrayHeader{ + ElementOID: elementOID, + Dimensions: src.Dimensions, + } + + for i := range src.Elements { + if src.Elements[i].Status == Null { + arrayHeader.ContainsNull = true + break + } + } + + err := arrayHeader.EncodeBinary(w) + if err != nil { + return false, err + } - // TODO - consider how to avoid having to buffer array before writing length - - // or how not pay allocations for the byte order conversions. elemBuf := &bytes.Buffer{} for i := range src.Elements { - err := src.Elements[i].EncodeBinary(elemBuf) + elemBuf.Reset() + + null, err := src.Elements[i].EncodeBinary(elemBuf) if err != nil { - return err + return false, err } - if src.Elements[i].Status == Null { - arrayHeader.ContainsNull = true + if null { + _, err = pgio.WriteInt32(w, -1) + if err != nil { + return false, err + } + } else { + _, err = pgio.WriteInt32(w, int32(elemBuf.Len())) + if err != nil { + return false, err + } + _, err = elemBuf.WriteTo(w) + if err != nil { + return false, err + } } } - arrayHeader.ElementOID = elementOID - arrayHeader.Dimensions = src.Dimensions - - // TODO - consider how to avoid having to buffer array before writing length - - // or how not pay allocations for the byte order conversions. - headerBuf := &bytes.Buffer{} - err := arrayHeader.EncodeBinary(headerBuf) - if err != nil { - return err - } - - _, err = pgio.WriteInt32(w, int32(headerBuf.Len()+elemBuf.Len())) - if err != nil { - return err - } - - _, err = headerBuf.WriteTo(w) - if err != nil { - return err - } - - _, err = elemBuf.WriteTo(w) - if err != nil { - return err - } - - return err + return false, err } diff --git a/pgtype/timestamp.go b/pgtype/timestamp.go index ca5eb738..a8b628e9 100644 --- a/pgtype/timestamp.go +++ b/pgtype/timestamp.go @@ -127,12 +127,15 @@ func (dst *Timestamp) DecodeBinary(src []byte) error { // EncodeText writes the text encoding of src into w. If src.Time is not in // the UTC time zone it returns an error. -func (src Timestamp) EncodeText(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src Timestamp) EncodeText(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } if src.Time.Location() != time.UTC { - return fmt.Errorf("cannot encode non-UTC time into timestamp") + return false, fmt.Errorf("cannot encode non-UTC time into timestamp") } var s string @@ -146,28 +149,21 @@ func (src Timestamp) EncodeText(w io.Writer) error { s = "-infinity" } - _, err := pgio.WriteInt32(w, int32(len(s))) - if err != nil { - return nil - } - - _, err = w.Write([]byte(s)) - return err + _, err := io.WriteString(w, s) + return false, err } // EncodeBinary writes the binary encoding of src into w. If src.Time is not in // the UTC time zone it returns an error. -func (src Timestamp) EncodeBinary(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src Timestamp) EncodeBinary(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } if src.Time.Location() != time.UTC { - return fmt.Errorf("cannot encode non-UTC time into timestamp") - } - - _, err := pgio.WriteInt32(w, 8) - if err != nil { - return err + return false, fmt.Errorf("cannot encode non-UTC time into timestamp") } var microsecSinceY2K int64 @@ -181,6 +177,6 @@ func (src Timestamp) EncodeBinary(w io.Writer) error { microsecSinceY2K = negativeInfinityMicrosecondOffset } - _, err = pgio.WriteInt64(w, microsecSinceY2K) - return err + _, err := pgio.WriteInt64(w, microsecSinceY2K) + return false, err } diff --git a/pgtype/timestamparray.go b/pgtype/timestamparray.go index 695559ac..21e4de98 100644 --- a/pgtype/timestamparray.go +++ b/pgtype/timestamparray.go @@ -153,26 +153,22 @@ func (dst *TimestampArray) DecodeBinary(src []byte) error { return nil } -func (src *TimestampArray) EncodeText(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src *TimestampArray) EncodeText(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } if len(src.Dimensions) == 0 { - _, err := pgio.WriteInt32(w, 2) - if err != nil { - return err - } - - _, err = w.Write([]byte("{}")) - return err + _, err := io.WriteString(w, "{}") + return false, err } - buf := &bytes.Buffer{} - - err := EncodeTextArrayDimensions(buf, src.Dimensions) + err := EncodeTextArrayDimensions(w, src.Dimensions) if err != nil { - return err + return false, err } // dimElemCounts is the multiples of elements that each array lies on. For @@ -186,100 +182,112 @@ func (src *TimestampArray) EncodeText(w io.Writer) error { dimElemCounts[i] = int(src.Dimensions[i].Length) * dimElemCounts[i+1] } - textElementWriter := NewTextElementWriter(buf) - for i, elem := range src.Elements { if i > 0 { - err = pgio.WriteByte(buf, ',') + err = pgio.WriteByte(w, ',') if err != nil { - return err + return false, err } } for _, dec := range dimElemCounts { if i%dec == 0 { - err = pgio.WriteByte(buf, '{') + err = pgio.WriteByte(w, '{') if err != nil { - return err + return false, err } } } - textElementWriter.Reset() - err = elem.EncodeText(textElementWriter) + elemBuf := &bytes.Buffer{} + null, err := elem.EncodeText(elemBuf) if err != nil { - return err + return false, err + } + if null { + _, err = io.WriteString(w, `NULL`) + if err != nil { + return false, err + } + } else if elemBuf.Len() == 0 { + _, err = io.WriteString(w, `""`) + if err != nil { + return false, err + } + } else { + _, err = elemBuf.WriteTo(w) + if err != nil { + return false, err + } } for _, dec := range dimElemCounts { if (i+1)%dec == 0 { - err = pgio.WriteByte(buf, '}') + err = pgio.WriteByte(w, '}') if err != nil { - return err + return false, err } } } } - _, err = pgio.WriteInt32(w, int32(buf.Len())) - if err != nil { - return err - } - - _, err = buf.WriteTo(w) - return err + return false, nil } -func (src *TimestampArray) EncodeBinary(w io.Writer) error { +func (src *TimestampArray) EncodeBinary(w io.Writer) (bool, error) { return src.encodeBinary(w, TimestampOID) } -func (src *TimestampArray) encodeBinary(w io.Writer, elementOID int32) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src *TimestampArray) encodeBinary(w io.Writer, elementOID int32) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } - var arrayHeader ArrayHeader + arrayHeader := ArrayHeader{ + ElementOID: elementOID, + Dimensions: src.Dimensions, + } + + for i := range src.Elements { + if src.Elements[i].Status == Null { + arrayHeader.ContainsNull = true + break + } + } + + err := arrayHeader.EncodeBinary(w) + if err != nil { + return false, err + } - // TODO - consider how to avoid having to buffer array before writing length - - // or how not pay allocations for the byte order conversions. elemBuf := &bytes.Buffer{} for i := range src.Elements { - err := src.Elements[i].EncodeBinary(elemBuf) + elemBuf.Reset() + + null, err := src.Elements[i].EncodeBinary(elemBuf) if err != nil { - return err + return false, err } - if src.Elements[i].Status == Null { - arrayHeader.ContainsNull = true + if null { + _, err = pgio.WriteInt32(w, -1) + if err != nil { + return false, err + } + } else { + _, err = pgio.WriteInt32(w, int32(elemBuf.Len())) + if err != nil { + return false, err + } + _, err = elemBuf.WriteTo(w) + if err != nil { + return false, err + } } } - arrayHeader.ElementOID = elementOID - arrayHeader.Dimensions = src.Dimensions - - // TODO - consider how to avoid having to buffer array before writing length - - // or how not pay allocations for the byte order conversions. - headerBuf := &bytes.Buffer{} - err := arrayHeader.EncodeBinary(headerBuf) - if err != nil { - return err - } - - _, err = pgio.WriteInt32(w, int32(headerBuf.Len()+elemBuf.Len())) - if err != nil { - return err - } - - _, err = headerBuf.WriteTo(w) - if err != nil { - return err - } - - _, err = elemBuf.WriteTo(w) - if err != nil { - return err - } - - return err + return false, err } diff --git a/pgtype/timestamptz.go b/pgtype/timestamptz.go index 7255bb06..f4c67b0b 100644 --- a/pgtype/timestamptz.go +++ b/pgtype/timestamptz.go @@ -131,9 +131,12 @@ func (dst *Timestamptz) DecodeBinary(src []byte) error { return nil } -func (src Timestamptz) EncodeText(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src Timestamptz) EncodeText(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } var s string @@ -147,23 +150,16 @@ func (src Timestamptz) EncodeText(w io.Writer) error { s = "-infinity" } - _, err := pgio.WriteInt32(w, int32(len(s))) - if err != nil { - return nil - } - - _, err = w.Write([]byte(s)) - return err + _, err := io.WriteString(w, s) + return false, err } -func (src Timestamptz) EncodeBinary(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err - } - - _, err := pgio.WriteInt32(w, 8) - if err != nil { - return err +func (src Timestamptz) EncodeBinary(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } var microsecSinceY2K int64 @@ -177,6 +173,6 @@ func (src Timestamptz) EncodeBinary(w io.Writer) error { microsecSinceY2K = negativeInfinityMicrosecondOffset } - _, err = pgio.WriteInt64(w, microsecSinceY2K) - return err + _, err := pgio.WriteInt64(w, microsecSinceY2K) + return false, err } diff --git a/pgtype/timestamptzarray.go b/pgtype/timestamptzarray.go index ca416c97..597b1842 100644 --- a/pgtype/timestamptzarray.go +++ b/pgtype/timestamptzarray.go @@ -153,26 +153,22 @@ func (dst *TimestamptzArray) DecodeBinary(src []byte) error { return nil } -func (src *TimestamptzArray) EncodeText(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src *TimestamptzArray) EncodeText(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } if len(src.Dimensions) == 0 { - _, err := pgio.WriteInt32(w, 2) - if err != nil { - return err - } - - _, err = w.Write([]byte("{}")) - return err + _, err := io.WriteString(w, "{}") + return false, err } - buf := &bytes.Buffer{} - - err := EncodeTextArrayDimensions(buf, src.Dimensions) + err := EncodeTextArrayDimensions(w, src.Dimensions) if err != nil { - return err + return false, err } // dimElemCounts is the multiples of elements that each array lies on. For @@ -186,100 +182,112 @@ func (src *TimestamptzArray) EncodeText(w io.Writer) error { dimElemCounts[i] = int(src.Dimensions[i].Length) * dimElemCounts[i+1] } - textElementWriter := NewTextElementWriter(buf) - for i, elem := range src.Elements { if i > 0 { - err = pgio.WriteByte(buf, ',') + err = pgio.WriteByte(w, ',') if err != nil { - return err + return false, err } } for _, dec := range dimElemCounts { if i%dec == 0 { - err = pgio.WriteByte(buf, '{') + err = pgio.WriteByte(w, '{') if err != nil { - return err + return false, err } } } - textElementWriter.Reset() - err = elem.EncodeText(textElementWriter) + elemBuf := &bytes.Buffer{} + null, err := elem.EncodeText(elemBuf) if err != nil { - return err + return false, err + } + if null { + _, err = io.WriteString(w, `NULL`) + if err != nil { + return false, err + } + } else if elemBuf.Len() == 0 { + _, err = io.WriteString(w, `""`) + if err != nil { + return false, err + } + } else { + _, err = elemBuf.WriteTo(w) + if err != nil { + return false, err + } } for _, dec := range dimElemCounts { if (i+1)%dec == 0 { - err = pgio.WriteByte(buf, '}') + err = pgio.WriteByte(w, '}') if err != nil { - return err + return false, err } } } } - _, err = pgio.WriteInt32(w, int32(buf.Len())) - if err != nil { - return err - } - - _, err = buf.WriteTo(w) - return err + return false, nil } -func (src *TimestamptzArray) EncodeBinary(w io.Writer) error { +func (src *TimestamptzArray) EncodeBinary(w io.Writer) (bool, error) { return src.encodeBinary(w, TimestamptzOID) } -func (src *TimestamptzArray) encodeBinary(w io.Writer, elementOID int32) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src *TimestamptzArray) encodeBinary(w io.Writer, elementOID int32) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } - var arrayHeader ArrayHeader + arrayHeader := ArrayHeader{ + ElementOID: elementOID, + Dimensions: src.Dimensions, + } + + for i := range src.Elements { + if src.Elements[i].Status == Null { + arrayHeader.ContainsNull = true + break + } + } + + err := arrayHeader.EncodeBinary(w) + if err != nil { + return false, err + } - // TODO - consider how to avoid having to buffer array before writing length - - // or how not pay allocations for the byte order conversions. elemBuf := &bytes.Buffer{} for i := range src.Elements { - err := src.Elements[i].EncodeBinary(elemBuf) + elemBuf.Reset() + + null, err := src.Elements[i].EncodeBinary(elemBuf) if err != nil { - return err + return false, err } - if src.Elements[i].Status == Null { - arrayHeader.ContainsNull = true + if null { + _, err = pgio.WriteInt32(w, -1) + if err != nil { + return false, err + } + } else { + _, err = pgio.WriteInt32(w, int32(elemBuf.Len())) + if err != nil { + return false, err + } + _, err = elemBuf.WriteTo(w) + if err != nil { + return false, err + } } } - arrayHeader.ElementOID = elementOID - arrayHeader.Dimensions = src.Dimensions - - // TODO - consider how to avoid having to buffer array before writing length - - // or how not pay allocations for the byte order conversions. - headerBuf := &bytes.Buffer{} - err := arrayHeader.EncodeBinary(headerBuf) - if err != nil { - return err - } - - _, err = pgio.WriteInt32(w, int32(headerBuf.Len()+elemBuf.Len())) - if err != nil { - return err - } - - _, err = headerBuf.WriteTo(w) - if err != nil { - return err - } - - _, err = elemBuf.WriteTo(w) - if err != nil { - return err - } - - return err + return false, err } diff --git a/pgtype/to-consider.txt b/pgtype/to-consider.txt deleted file mode 100644 index ba4f3511..00000000 --- a/pgtype/to-consider.txt +++ /dev/null @@ -1,9 +0,0 @@ -DecodeText and DecodeBinary take []byte instead of io.Reader -EncodeText and EncodeBinary do not write size -Add Nullable interface with IsNull() and SetNull() - -The above would keep types from needing to worry about writing their own size. Could make EncodeText and DecodeText easier to use with sql.Scanner and driver.Valuer. SetNull() could be removed as DecodeText and DecodeBinary could interpret a nil slice as null. - -EncodeText and EncodeBinary could return (null bool, err error). That would finish removing Nullable interface. - -Also, consider whether arrays and ranges could be represented as generic data types or more common code could be extracted instead of using code generation. diff --git a/pgtype/typed_array.go.erb b/pgtype/typed_array.go.erb index 316439ef..2e9b77ea 100644 --- a/pgtype/typed_array.go.erb +++ b/pgtype/typed_array.go.erb @@ -151,26 +151,22 @@ func (dst *<%= pgtype_array_type %>) DecodeBinary(src []byte) error { return nil } -func (src *<%= pgtype_array_type %>) EncodeText(w io.Writer) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src *<%= pgtype_array_type %>) EncodeText(w io.Writer) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } if len(src.Dimensions) == 0 { - _, err := pgio.WriteInt32(w, 2) - if err != nil { - return err - } - - _, err = w.Write([]byte("{}")) - return err + _, err := io.WriteString(w, "{}") + return false, err } - buf := &bytes.Buffer{} - - err := EncodeTextArrayDimensions(buf, src.Dimensions) + err := EncodeTextArrayDimensions(w, src.Dimensions) if err != nil { - return err + return false, err } // dimElemCounts is the multiples of elements that each array lies on. For @@ -184,100 +180,112 @@ func (src *<%= pgtype_array_type %>) EncodeText(w io.Writer) error { dimElemCounts[i] = int(src.Dimensions[i].Length) * dimElemCounts[i+1] } - textElementWriter := NewTextElementWriter(buf) - for i, elem := range src.Elements { if i > 0 { - err = pgio.WriteByte(buf, ',') + err = pgio.WriteByte(w, ',') if err != nil { - return err + return false, err } } for _, dec := range dimElemCounts { if i%dec == 0 { - err = pgio.WriteByte(buf, '{') + err = pgio.WriteByte(w, '{') if err != nil { - return err + return false, err } } } - textElementWriter.Reset() - err = elem.EncodeText(textElementWriter) + elemBuf := &bytes.Buffer{} + null, err := elem.EncodeText(elemBuf) if err != nil { - return err + return false, err + } + if null { + _, err = io.WriteString(w, `<%= text_null %>`) + if err != nil { + return false, err + } + } else if elemBuf.Len() == 0 { + _, err = io.WriteString(w, `""`) + if err != nil { + return false, err + } + } else { + _, err = elemBuf.WriteTo(w) + if err != nil { + return false, err + } } for _, dec := range dimElemCounts { if (i+1)%dec == 0 { - err = pgio.WriteByte(buf, '}') + err = pgio.WriteByte(w, '}') if err != nil { - return err + return false, err } } } } - _, err = pgio.WriteInt32(w, int32(buf.Len())) - if err != nil { - return err - } - - _, err = buf.WriteTo(w) - return err + return false, nil } -func (src *<%= pgtype_array_type %>) EncodeBinary(w io.Writer) error { +func (src *<%= pgtype_array_type %>) EncodeBinary(w io.Writer) (bool, error) { return src.encodeBinary(w, <%= element_oid %>) } -func (src *<%= pgtype_array_type %>) encodeBinary(w io.Writer, elementOID int32) error { - if done, err := encodeNotPresent(w, src.Status); done { - return err +func (src *<%= pgtype_array_type %>) encodeBinary(w io.Writer, elementOID int32) (bool, error) { + switch src.Status { + case Null: + return true, nil + case Undefined: + return false, errUndefined } - var arrayHeader ArrayHeader + arrayHeader := ArrayHeader{ + ElementOID: elementOID, + Dimensions: src.Dimensions, + } + + for i := range src.Elements { + if src.Elements[i].Status == Null { + arrayHeader.ContainsNull = true + break + } + } + + err := arrayHeader.EncodeBinary(w) + if err != nil { + return false, err + } - // TODO - consider how to avoid having to buffer array before writing length - - // or how not pay allocations for the byte order conversions. elemBuf := &bytes.Buffer{} for i := range src.Elements { - err := src.Elements[i].EncodeBinary(elemBuf) + elemBuf.Reset() + + null, err := src.Elements[i].EncodeBinary(elemBuf) if err != nil { - return err + return false, err } - if src.Elements[i].Status == Null { - arrayHeader.ContainsNull = true + if null { + _, err = pgio.WriteInt32(w, -1) + if err != nil { + return false, err + } + } else { + _, err = pgio.WriteInt32(w, int32(elemBuf.Len())) + if err != nil { + return false, err + } + _, err = elemBuf.WriteTo(w) + if err != nil { + return false, err + } } } - arrayHeader.ElementOID = elementOID - arrayHeader.Dimensions = src.Dimensions - - // TODO - consider how to avoid having to buffer array before writing length - - // or how not pay allocations for the byte order conversions. - headerBuf := &bytes.Buffer{} - err := arrayHeader.EncodeBinary(headerBuf) - if err != nil { - return err - } - - _, err = pgio.WriteInt32(w, int32(headerBuf.Len()+elemBuf.Len())) - if err != nil { - return err - } - - _, err = headerBuf.WriteTo(w) - if err != nil { - return err - } - - _, err = elemBuf.WriteTo(w) - if err != nil { - return err - } - - return err + return false, err } diff --git a/pgtype/typed_array_gen.sh b/pgtype/typed_array_gen.sh index 1e2dce64..43109700 100644 --- a/pgtype/typed_array_gen.sh +++ b/pgtype/typed_array_gen.sh @@ -1,11 +1,11 @@ -erb pgtype_array_type=Int2Array pgtype_element_type=Int2 go_array_types=[]int16,[]uint16 element_oid=Int2OID typed_array.go.erb > int2array.go -erb pgtype_array_type=Int4Array pgtype_element_type=Int4 go_array_types=[]int32,[]uint32 element_oid=Int4OID typed_array.go.erb > int4array.go -erb pgtype_array_type=Int8Array pgtype_element_type=Int8 go_array_types=[]int64,[]uint64 element_oid=Int8OID typed_array.go.erb > int8array.go -erb pgtype_array_type=BoolArray pgtype_element_type=Bool go_array_types=[]bool element_oid=BoolOID typed_array.go.erb > boolarray.go -erb pgtype_array_type=DateArray pgtype_element_type=Date go_array_types=[]time.Time element_oid=DateOID typed_array.go.erb > datearray.go -erb pgtype_array_type=TimestamptzArray pgtype_element_type=Timestamptz go_array_types=[]time.Time element_oid=TimestamptzOID typed_array.go.erb > timestamptzarray.go -erb pgtype_array_type=TimestampArray pgtype_element_type=Timestamp go_array_types=[]time.Time element_oid=TimestampOID typed_array.go.erb > timestamparray.go -erb pgtype_array_type=Float4Array pgtype_element_type=Float4 go_array_types=[]float32 element_oid=Float4OID typed_array.go.erb > float4array.go -erb pgtype_array_type=Float8Array pgtype_element_type=Float8 go_array_types=[]float64 element_oid=Float8OID typed_array.go.erb > float8array.go -erb pgtype_array_type=InetArray pgtype_element_type=Inet go_array_types=[]*net.IPNet,[]net.IP element_oid=InetOID typed_array.go.erb > inetarray.go -erb pgtype_array_type=TextArray pgtype_element_type=Text go_array_types=[]string element_oid=TextOID typed_array.go.erb > textarray.go +erb pgtype_array_type=Int2Array pgtype_element_type=Int2 go_array_types=[]int16,[]uint16 element_oid=Int2OID text_null=NULL typed_array.go.erb > int2array.go +erb pgtype_array_type=Int4Array pgtype_element_type=Int4 go_array_types=[]int32,[]uint32 element_oid=Int4OID text_null=NULL typed_array.go.erb > int4array.go +erb pgtype_array_type=Int8Array pgtype_element_type=Int8 go_array_types=[]int64,[]uint64 element_oid=Int8OID text_null=NULL typed_array.go.erb > int8array.go +erb pgtype_array_type=BoolArray pgtype_element_type=Bool go_array_types=[]bool element_oid=BoolOID text_null=NULL typed_array.go.erb > boolarray.go +erb pgtype_array_type=DateArray pgtype_element_type=Date go_array_types=[]time.Time element_oid=DateOID text_null=NULL typed_array.go.erb > datearray.go +erb pgtype_array_type=TimestamptzArray pgtype_element_type=Timestamptz go_array_types=[]time.Time element_oid=TimestamptzOID text_null=NULL typed_array.go.erb > timestamptzarray.go +erb pgtype_array_type=TimestampArray pgtype_element_type=Timestamp go_array_types=[]time.Time element_oid=TimestampOID text_null=NULL typed_array.go.erb > timestamparray.go +erb pgtype_array_type=Float4Array pgtype_element_type=Float4 go_array_types=[]float32 element_oid=Float4OID text_null=NULL typed_array.go.erb > float4array.go +erb pgtype_array_type=Float8Array pgtype_element_type=Float8 go_array_types=[]float64 element_oid=Float8OID text_null=NULL typed_array.go.erb > float8array.go +erb pgtype_array_type=InetArray pgtype_element_type=Inet go_array_types=[]*net.IPNet,[]net.IP element_oid=InetOID text_null=NULL typed_array.go.erb > inetarray.go +erb pgtype_array_type=TextArray pgtype_element_type=Text go_array_types=[]string element_oid=TextOID text_null='"NULL"' typed_array.go.erb > textarray.go diff --git a/pgtype/varchararray.go b/pgtype/varchararray.go index 3a5d8536..9c8829d0 100644 --- a/pgtype/varchararray.go +++ b/pgtype/varchararray.go @@ -22,10 +22,10 @@ func (dst *VarcharArray) DecodeBinary(src []byte) error { return (*TextArray)(dst).DecodeBinary(src) } -func (src *VarcharArray) EncodeText(w io.Writer) error { +func (src *VarcharArray) EncodeText(w io.Writer) (bool, error) { return (*TextArray)(src).EncodeText(w) } -func (src *VarcharArray) EncodeBinary(w io.Writer) error { +func (src *VarcharArray) EncodeBinary(w io.Writer) (bool, error) { return (*TextArray)(src).encodeBinary(w, VarcharOID) } diff --git a/pgtype/xid.go b/pgtype/xid.go index 389f93bc..6635b21e 100644 --- a/pgtype/xid.go +++ b/pgtype/xid.go @@ -41,10 +41,10 @@ func (dst *XID) DecodeBinary(src []byte) error { return (*pguint32)(dst).DecodeBinary(src) } -func (src XID) EncodeText(w io.Writer) error { +func (src XID) EncodeText(w io.Writer) (bool, error) { return (pguint32)(src).EncodeText(w) } -func (src XID) EncodeBinary(w io.Writer) error { +func (src XID) EncodeBinary(w io.Writer) (bool, error) { return (pguint32)(src).EncodeBinary(w) } diff --git a/values.go b/values.go index 796f2f3d..88bf13d2 100644 --- a/values.go +++ b/values.go @@ -408,7 +408,10 @@ func (n NullInt16) Encode(w *WriteBuf, oid OID) error { return nil } - return pgtype.Int2{Int: n.Int16, Status: pgtype.Present}.EncodeBinary(w) + w.WriteInt32(2) + + _, err := pgtype.Int2{Int: n.Int16, Status: pgtype.Present}.EncodeBinary(w) + return err } // NullInt32 represents an integer that may be null. NullInt32 implements the @@ -447,7 +450,10 @@ func (n NullInt32) Encode(w *WriteBuf, oid OID) error { return nil } - return pgtype.Int4{Int: n.Int32, Status: pgtype.Present}.EncodeBinary(w) + w.WriteInt32(4) + + _, err := pgtype.Int4{Int: n.Int32, Status: pgtype.Present}.EncodeBinary(w) + return err } // OID (Object Identifier Type) is, according to https://www.postgresql.org/docs/current/static/datatype-oid.html, @@ -484,24 +490,14 @@ func (dst *OID) DecodeBinary(src []byte) error { return nil } -func (src OID) EncodeText(w io.Writer) error { - s := strconv.FormatUint(uint64(src), 10) - _, err := pgio.WriteInt32(w, int32(len(s))) - if err != nil { - return nil - } - _, err = w.Write([]byte(s)) - return err +func (src OID) EncodeText(w io.Writer) (bool, error) { + _, err := io.WriteString(w, strconv.FormatUint(uint64(src), 10)) + return false, err } -func (src OID) EncodeBinary(w io.Writer) error { - _, err := pgio.WriteInt32(w, 4) - if err != nil { - return err - } - - _, err = pgio.WriteUint32(w, uint32(src)) - return err +func (src OID) EncodeBinary(w io.Writer) (bool, error) { + _, err := pgio.WriteUint32(w, uint32(src)) + return false, err } // Tid is PostgreSQL's Tuple Identifier type. @@ -595,7 +591,10 @@ func (n NullInt64) Encode(w *WriteBuf, oid OID) error { return nil } - return pgtype.Int8{Int: n.Int64, Status: pgtype.Present}.EncodeBinary(w) + w.WriteInt32(8) + + _, err := pgtype.Int8{Int: n.Int64, Status: pgtype.Present}.EncodeBinary(w) + return err } // NullBool represents an bool that may be null. NullBool implements the Scanner @@ -634,7 +633,10 @@ func (n NullBool) Encode(w *WriteBuf, oid OID) error { return nil } - return encodeBool(w, oid, n.Bool) + w.WriteInt32(1) + + _, err := pgtype.Bool{Bool: n.Bool, Status: pgtype.Present}.EncodeBinary(w) + return err } // NullTime represents an time.Time that may be null. NullTime implements the @@ -834,9 +836,31 @@ func Encode(wbuf *WriteBuf, oid OID, arg interface{}) error { case Encoder: return arg.Encode(wbuf, oid) case pgtype.BinaryEncoder: - return arg.EncodeBinary(wbuf) + buf := &bytes.Buffer{} + null, err := arg.EncodeBinary(buf) + if err != nil { + return err + } + if null { + wbuf.WriteInt32(-1) + } else { + wbuf.WriteInt32(int32(buf.Len())) + wbuf.WriteBytes(buf.Bytes()) + } + return nil case pgtype.TextEncoder: - return arg.EncodeText(wbuf) + buf := &bytes.Buffer{} + null, err := arg.EncodeText(buf) + if err != nil { + return err + } + if null { + wbuf.WriteInt32(-1) + } else { + wbuf.WriteInt32(int32(buf.Len())) + wbuf.WriteBytes(buf.Bytes()) + } + return nil case driver.Valuer: v, err := arg.Value() if err != nil { @@ -876,7 +900,19 @@ func Encode(wbuf *WriteBuf, oid OID, arg interface{}) error { if err != nil { return err } - return value.(pgtype.BinaryEncoder).EncodeBinary(wbuf) + + buf := &bytes.Buffer{} + null, err := value.(pgtype.BinaryEncoder).EncodeBinary(buf) + if err != nil { + return err + } + if null { + wbuf.WriteInt32(-1) + } else { + wbuf.WriteInt32(int32(buf.Len())) + wbuf.WriteBytes(buf.Bytes()) + } + return nil } switch arg := arg.(type) { @@ -1026,15 +1062,6 @@ func decodeBool(vr *ValueReader) bool { return b.Bool } -func encodeBool(w *WriteBuf, oid OID, value bool) error { - if oid != BoolOID { - return fmt.Errorf("cannot encode Go %s into oid %d", "bool", oid) - } - - b := pgtype.Bool{Bool: value, Status: pgtype.Present} - return b.EncodeBinary(w) -} - func decodeInt(vr *ValueReader) int64 { switch vr.Type().DataType { case Int2OID: @@ -1461,14 +1488,39 @@ func encodeTime(w *WriteBuf, oid OID, value time.Time) error { if err != nil { return err } - return d.EncodeBinary(w) + + buf := &bytes.Buffer{} + null, err := d.EncodeBinary(buf) + if err != nil { + return err + } + if null { + w.WriteInt32(-1) + } else { + w.WriteInt32(int32(buf.Len())) + w.WriteBytes(buf.Bytes()) + } + return nil + case TimestampTzOID, TimestampOID: var t pgtype.Timestamptz err := t.ConvertFrom(value) if err != nil { return err } - return t.EncodeBinary(w) + + buf := &bytes.Buffer{} + null, err := t.EncodeBinary(buf) + if err != nil { + return err + } + if null { + w.WriteInt32(-1) + } else { + w.WriteInt32(int32(buf.Len())) + w.WriteBytes(buf.Bytes()) + } + return nil default: return fmt.Errorf("cannot encode %s into oid %v", "time.Time", oid) }