diff --git a/aclitem_array.go b/aclitem_array.go index f4b14433..501074d6 100644 --- a/aclitem_array.go +++ b/aclitem_array.go @@ -317,7 +317,7 @@ func (dst *ACLItemArray) DecodeText(ci *ConnInfo, src []byte) error { for i, s := range uta.Elements { var elem ACLItem var elemSrc []byte - if s != "NULL" { + if s != "NULL" || uta.Quoted[i] { elemSrc = []byte(s) } err = elem.DecodeText(ci, elemSrc) diff --git a/array.go b/array.go index b779cd9d..93c91897 100644 --- a/array.go +++ b/array.go @@ -82,6 +82,7 @@ func (src ArrayHeader) EncodeBinary(ci *ConnInfo, buf []byte) []byte { type UntypedTextArray struct { Elements []string + Quoted []bool Dimensions []ArrayDimension } @@ -196,13 +197,14 @@ func ParseUntypedTextArray(src string) (*UntypedTextArray, error) { } default: buf.UnreadRune() - value, err := arrayParseValue(buf) + value, quoted, err := arrayParseValue(buf) if err != nil { return nil, errors.Errorf("invalid array value: %v", err) } if currentDim == counterDim { implicitDimensions[currentDim].Length++ } + dst.Quoted = append(dst.Quoted, quoted) dst.Elements = append(dst.Elements, value) } @@ -239,10 +241,10 @@ func skipWhitespace(buf *bytes.Buffer) { } } -func arrayParseValue(buf *bytes.Buffer) (string, error) { +func arrayParseValue(buf *bytes.Buffer) (string, bool, error) { r, _, err := buf.ReadRune() if err != nil { - return "", err + return "", false, err } if r == '"' { return arrayParseQuotedValue(buf) @@ -254,41 +256,41 @@ func arrayParseValue(buf *bytes.Buffer) (string, error) { for { r, _, err := buf.ReadRune() if err != nil { - return "", err + return "", false, err } switch r { case ',', '}': buf.UnreadRune() - return s.String(), nil + return s.String(), false, nil } s.WriteRune(r) } } -func arrayParseQuotedValue(buf *bytes.Buffer) (string, error) { +func arrayParseQuotedValue(buf *bytes.Buffer) (string, bool, error) { s := &bytes.Buffer{} for { r, _, err := buf.ReadRune() if err != nil { - return "", err + return "", false, err } switch r { case '\\': r, _, err = buf.ReadRune() if err != nil { - return "", err + return "", false, err } case '"': r, _, err = buf.ReadRune() if err != nil { - return "", err + return "", false, err } buf.UnreadRune() - return s.String(), nil + return s.String(), true, nil } s.WriteRune(r) } diff --git a/array_test.go b/array_test.go index 486171b8..2f3b9237 100644 --- a/array_test.go +++ b/array_test.go @@ -16,6 +16,7 @@ func TestParseUntypedTextArray(t *testing.T) { source: "{}", result: pgtype.UntypedTextArray{ Elements: nil, + Quoted: nil, Dimensions: nil, }, }, @@ -23,6 +24,7 @@ func TestParseUntypedTextArray(t *testing.T) { source: "{1}", result: pgtype.UntypedTextArray{ Elements: []string{"1"}, + Quoted: []bool{false}, Dimensions: []pgtype.ArrayDimension{{Length: 1, LowerBound: 1}}, }, }, @@ -30,6 +32,7 @@ func TestParseUntypedTextArray(t *testing.T) { source: "{a,b}", result: pgtype.UntypedTextArray{ Elements: []string{"a", "b"}, + Quoted: []bool{false, false}, Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}}, }, }, @@ -37,6 +40,7 @@ func TestParseUntypedTextArray(t *testing.T) { source: `{"NULL"}`, result: pgtype.UntypedTextArray{ Elements: []string{"NULL"}, + Quoted: []bool{true}, Dimensions: []pgtype.ArrayDimension{{Length: 1, LowerBound: 1}}, }, }, @@ -44,6 +48,7 @@ func TestParseUntypedTextArray(t *testing.T) { source: `{""}`, result: pgtype.UntypedTextArray{ Elements: []string{""}, + Quoted: []bool{true}, Dimensions: []pgtype.ArrayDimension{{Length: 1, LowerBound: 1}}, }, }, @@ -51,6 +56,7 @@ func TestParseUntypedTextArray(t *testing.T) { source: `{"He said, \"Hello.\""}`, result: pgtype.UntypedTextArray{ Elements: []string{`He said, "Hello."`}, + Quoted: []bool{true}, Dimensions: []pgtype.ArrayDimension{{Length: 1, LowerBound: 1}}, }, }, @@ -58,6 +64,7 @@ func TestParseUntypedTextArray(t *testing.T) { source: "{{a,b},{c,d},{e,f}}", result: pgtype.UntypedTextArray{ Elements: []string{"a", "b", "c", "d", "e", "f"}, + Quoted: []bool{false, false, false, false, false, false}, Dimensions: []pgtype.ArrayDimension{{Length: 3, LowerBound: 1}, {Length: 2, LowerBound: 1}}, }, }, @@ -65,6 +72,7 @@ func TestParseUntypedTextArray(t *testing.T) { source: "{{{a,b},{c,d},{e,f}},{{a,b},{c,d},{e,f}}}", result: pgtype.UntypedTextArray{ Elements: []string{"a", "b", "c", "d", "e", "f", "a", "b", "c", "d", "e", "f"}, + Quoted: []bool{false, false, false, false, false, false, false, false, false, false, false, false}, Dimensions: []pgtype.ArrayDimension{ {Length: 2, LowerBound: 1}, {Length: 3, LowerBound: 1}, @@ -76,6 +84,7 @@ func TestParseUntypedTextArray(t *testing.T) { source: "[4:4]={1}", result: pgtype.UntypedTextArray{ Elements: []string{"1"}, + Quoted: []bool{false}, Dimensions: []pgtype.ArrayDimension{{Length: 1, LowerBound: 4}}, }, }, @@ -83,6 +92,7 @@ func TestParseUntypedTextArray(t *testing.T) { source: "[4:5][2:3]={{a,b},{c,d}}", result: pgtype.UntypedTextArray{ Elements: []string{"a", "b", "c", "d"}, + Quoted: []bool{false, false, false, false}, Dimensions: []pgtype.ArrayDimension{ {Length: 2, LowerBound: 4}, {Length: 2, LowerBound: 2}, diff --git a/bool_array.go b/bool_array.go index 41c6deda..232863ec 100644 --- a/bool_array.go +++ b/bool_array.go @@ -319,7 +319,7 @@ func (dst *BoolArray) DecodeText(ci *ConnInfo, src []byte) error { for i, s := range uta.Elements { var elem Bool var elemSrc []byte - if s != "NULL" { + if s != "NULL" || uta.Quoted[i] { elemSrc = []byte(s) } err = elem.DecodeText(ci, elemSrc) diff --git a/bpchar_array.go b/bpchar_array.go index 5fd7381a..aad7c144 100644 --- a/bpchar_array.go +++ b/bpchar_array.go @@ -319,7 +319,7 @@ func (dst *BPCharArray) DecodeText(ci *ConnInfo, src []byte) error { for i, s := range uta.Elements { var elem BPChar var elemSrc []byte - if s != "NULL" { + if s != "NULL" || uta.Quoted[i] { elemSrc = []byte(s) } err = elem.DecodeText(ci, elemSrc) diff --git a/bytea_array.go b/bytea_array.go index 9b5c9ee9..1dee05fa 100644 --- a/bytea_array.go +++ b/bytea_array.go @@ -291,7 +291,7 @@ func (dst *ByteaArray) DecodeText(ci *ConnInfo, src []byte) error { for i, s := range uta.Elements { var elem Bytea var elemSrc []byte - if s != "NULL" { + if s != "NULL" || uta.Quoted[i] { elemSrc = []byte(s) } err = elem.DecodeText(ci, elemSrc) diff --git a/cidr_array.go b/cidr_array.go index 06192ddd..645c641a 100644 --- a/cidr_array.go +++ b/cidr_array.go @@ -348,7 +348,7 @@ func (dst *CIDRArray) DecodeText(ci *ConnInfo, src []byte) error { for i, s := range uta.Elements { var elem CIDR var elemSrc []byte - if s != "NULL" { + if s != "NULL" || uta.Quoted[i] { elemSrc = []byte(s) } err = elem.DecodeText(ci, elemSrc) diff --git a/date_array.go b/date_array.go index 1961bf20..a546a854 100644 --- a/date_array.go +++ b/date_array.go @@ -320,7 +320,7 @@ func (dst *DateArray) DecodeText(ci *ConnInfo, src []byte) error { for i, s := range uta.Elements { var elem Date var elemSrc []byte - if s != "NULL" { + if s != "NULL" || uta.Quoted[i] { elemSrc = []byte(s) } err = elem.DecodeText(ci, elemSrc) diff --git a/enum_array.go b/enum_array.go index ebe838ad..d497dead 100644 --- a/enum_array.go +++ b/enum_array.go @@ -317,7 +317,7 @@ func (dst *EnumArray) DecodeText(ci *ConnInfo, src []byte) error { for i, s := range uta.Elements { var elem GenericText var elemSrc []byte - if s != "NULL" { + if s != "NULL" || uta.Quoted[i] { elemSrc = []byte(s) } err = elem.DecodeText(ci, elemSrc) diff --git a/float4_array.go b/float4_array.go index 44ba1fee..c399697d 100644 --- a/float4_array.go +++ b/float4_array.go @@ -319,7 +319,7 @@ func (dst *Float4Array) DecodeText(ci *ConnInfo, src []byte) error { for i, s := range uta.Elements { var elem Float4 var elemSrc []byte - if s != "NULL" { + if s != "NULL" || uta.Quoted[i] { elemSrc = []byte(s) } err = elem.DecodeText(ci, elemSrc) diff --git a/float8_array.go b/float8_array.go index 1065190d..9a961c2f 100644 --- a/float8_array.go +++ b/float8_array.go @@ -319,7 +319,7 @@ func (dst *Float8Array) DecodeText(ci *ConnInfo, src []byte) error { for i, s := range uta.Elements { var elem Float8 var elemSrc []byte - if s != "NULL" { + if s != "NULL" || uta.Quoted[i] { elemSrc = []byte(s) } err = elem.DecodeText(ci, elemSrc) diff --git a/hstore_array.go b/hstore_array.go index 3899ae49..0be072cc 100644 --- a/hstore_array.go +++ b/hstore_array.go @@ -291,7 +291,7 @@ func (dst *HstoreArray) DecodeText(ci *ConnInfo, src []byte) error { for i, s := range uta.Elements { var elem Hstore var elemSrc []byte - if s != "NULL" { + if s != "NULL" || uta.Quoted[i] { elemSrc = []byte(s) } err = elem.DecodeText(ci, elemSrc) diff --git a/inet_array.go b/inet_array.go index 5de138c0..d5d0a665 100644 --- a/inet_array.go +++ b/inet_array.go @@ -348,7 +348,7 @@ func (dst *InetArray) DecodeText(ci *ConnInfo, src []byte) error { for i, s := range uta.Elements { var elem Inet var elemSrc []byte - if s != "NULL" { + if s != "NULL" || uta.Quoted[i] { elemSrc = []byte(s) } err = elem.DecodeText(ci, elemSrc) diff --git a/int2_array.go b/int2_array.go index 6b4e4c8a..8aeb7d46 100644 --- a/int2_array.go +++ b/int2_array.go @@ -711,7 +711,7 @@ func (dst *Int2Array) DecodeText(ci *ConnInfo, src []byte) error { for i, s := range uta.Elements { var elem Int2 var elemSrc []byte - if s != "NULL" { + if s != "NULL" || uta.Quoted[i] { elemSrc = []byte(s) } err = elem.DecodeText(ci, elemSrc) diff --git a/int4_array.go b/int4_array.go index 8801947d..76ca811e 100644 --- a/int4_array.go +++ b/int4_array.go @@ -711,7 +711,7 @@ func (dst *Int4Array) DecodeText(ci *ConnInfo, src []byte) error { for i, s := range uta.Elements { var elem Int4 var elemSrc []byte - if s != "NULL" { + if s != "NULL" || uta.Quoted[i] { elemSrc = []byte(s) } err = elem.DecodeText(ci, elemSrc) diff --git a/int8_array.go b/int8_array.go index 13e20fca..45d8447f 100644 --- a/int8_array.go +++ b/int8_array.go @@ -711,7 +711,7 @@ func (dst *Int8Array) DecodeText(ci *ConnInfo, src []byte) error { for i, s := range uta.Elements { var elem Int8 var elemSrc []byte - if s != "NULL" { + if s != "NULL" || uta.Quoted[i] { elemSrc = []byte(s) } err = elem.DecodeText(ci, elemSrc) diff --git a/jsonb_array.go b/jsonb_array.go index f44f7fa5..c8ef1fcd 100644 --- a/jsonb_array.go +++ b/jsonb_array.go @@ -319,7 +319,7 @@ func (dst *JSONBArray) DecodeText(ci *ConnInfo, src []byte) error { for i, s := range uta.Elements { var elem JSONB var elemSrc []byte - if s != "NULL" { + if s != "NULL" || uta.Quoted[i] { elemSrc = []byte(s) } err = elem.DecodeText(ci, elemSrc) diff --git a/macaddr_array.go b/macaddr_array.go index 5a27046f..7f78c304 100644 --- a/macaddr_array.go +++ b/macaddr_array.go @@ -320,7 +320,7 @@ func (dst *MacaddrArray) DecodeText(ci *ConnInfo, src []byte) error { for i, s := range uta.Elements { var elem Macaddr var elemSrc []byte - if s != "NULL" { + if s != "NULL" || uta.Quoted[i] { elemSrc = []byte(s) } err = elem.DecodeText(ci, elemSrc) diff --git a/numeric_array.go b/numeric_array.go index c281bfb3..49c70855 100644 --- a/numeric_array.go +++ b/numeric_array.go @@ -487,7 +487,7 @@ func (dst *NumericArray) DecodeText(ci *ConnInfo, src []byte) error { for i, s := range uta.Elements { var elem Numeric var elemSrc []byte - if s != "NULL" { + if s != "NULL" || uta.Quoted[i] { elemSrc = []byte(s) } err = elem.DecodeText(ci, elemSrc) diff --git a/text_array.go b/text_array.go index 599764d8..d7125237 100644 --- a/text_array.go +++ b/text_array.go @@ -319,7 +319,7 @@ func (dst *TextArray) DecodeText(ci *ConnInfo, src []byte) error { for i, s := range uta.Elements { var elem Text var elemSrc []byte - if s != "NULL" { + if s != "NULL" || uta.Quoted[i] { elemSrc = []byte(s) } err = elem.DecodeText(ci, elemSrc) diff --git a/text_array_test.go b/text_array_test.go index 5a2317e3..a5d050f6 100644 --- a/text_array_test.go +++ b/text_array_test.go @@ -6,8 +6,20 @@ import ( "github.com/jackc/pgtype" "github.com/jackc/pgtype/testutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) +// https://github.com/jackc/pgtype/issues/78 +func TestTextArrayDecodeTextNull(t *testing.T) { + textArray := &pgtype.TextArray{} + err := textArray.DecodeText(nil, []byte(`{abc,"NULL",NULL,def}`)) + require.NoError(t, err) + require.Len(t, textArray.Elements, 4) + assert.Equal(t, pgtype.Present, textArray.Elements[1].Status) + assert.Equal(t, pgtype.Null, textArray.Elements[2].Status) +} + func TestTextArrayTranscode(t *testing.T) { testutil.TestSuccessfulTranscode(t, "text[]", []interface{}{ &pgtype.TextArray{ diff --git a/timestamp_array.go b/timestamp_array.go index 2f7176b8..bb819017 100644 --- a/timestamp_array.go +++ b/timestamp_array.go @@ -320,7 +320,7 @@ func (dst *TimestampArray) DecodeText(ci *ConnInfo, src []byte) error { for i, s := range uta.Elements { var elem Timestamp var elemSrc []byte - if s != "NULL" { + if s != "NULL" || uta.Quoted[i] { elemSrc = []byte(s) } err = elem.DecodeText(ci, elemSrc) diff --git a/timestamptz_array.go b/timestamptz_array.go index a10aaa8b..f028e0f9 100644 --- a/timestamptz_array.go +++ b/timestamptz_array.go @@ -320,7 +320,7 @@ func (dst *TimestamptzArray) DecodeText(ci *ConnInfo, src []byte) error { for i, s := range uta.Elements { var elem Timestamptz var elemSrc []byte - if s != "NULL" { + if s != "NULL" || uta.Quoted[i] { elemSrc = []byte(s) } err = elem.DecodeText(ci, elemSrc) diff --git a/tstzrange_array.go b/tstzrange_array.go index 7e57acfe..4f03d838 100644 --- a/tstzrange_array.go +++ b/tstzrange_array.go @@ -272,7 +272,7 @@ func (dst *TstzrangeArray) DecodeText(ci *ConnInfo, src []byte) error { for i, s := range uta.Elements { var elem Tstzrange var elemSrc []byte - if s != "NULL" { + if s != "NULL" || uta.Quoted[i] { elemSrc = []byte(s) } err = elem.DecodeText(ci, elemSrc) diff --git a/typed_array.go.erb b/typed_array.go.erb index eb1a642e..60665270 100644 --- a/typed_array.go.erb +++ b/typed_array.go.erb @@ -292,7 +292,7 @@ func (dst *<%= pgtype_array_type %>) DecodeText(ci *ConnInfo, src []byte) error for i, s := range uta.Elements { var elem <%= pgtype_element_type %> var elemSrc []byte - if s != "NULL" { + if s != "NULL" || uta.Quoted[i] { elemSrc = []byte(s) } err = elem.DecodeText(ci, elemSrc) diff --git a/uuid_array.go b/uuid_array.go index fc1ea3b3..894bbd40 100644 --- a/uuid_array.go +++ b/uuid_array.go @@ -375,7 +375,7 @@ func (dst *UUIDArray) DecodeText(ci *ConnInfo, src []byte) error { for i, s := range uta.Elements { var elem UUID var elemSrc []byte - if s != "NULL" { + if s != "NULL" || uta.Quoted[i] { elemSrc = []byte(s) } err = elem.DecodeText(ci, elemSrc) diff --git a/varchar_array.go b/varchar_array.go index 9326c72d..d515c2a4 100644 --- a/varchar_array.go +++ b/varchar_array.go @@ -319,7 +319,7 @@ func (dst *VarcharArray) DecodeText(ci *ConnInfo, src []byte) error { for i, s := range uta.Elements { var elem Varchar var elemSrc []byte - if s != "NULL" { + if s != "NULL" || uta.Quoted[i] { elemSrc = []byte(s) } err = elem.DecodeText(ci, elemSrc)