New Upstream Release - golang-github-jackc-pgtype
Ready changes
Summary
Merged new upstream version: 1.12.0 (was: 1.10.0).
Resulting package
Built on 2022-10-31T15:57 (took 4m36s)
The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:
apt install -t fresh-releases golang-github-jackc-pgtype-dev
Lintian Result
Diff
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 73126cf..f0c037e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,24 @@
+# 1.12.0 (August 6, 2022)
+
+* Add JSONArray (Jakob Ackermann)
+* Support Inet from fmt.Stringer and encoding.TextMarshaler (Ville Skyttä)
+* Support UUID from fmt.Stringer interface (Lasse Hyldahl Jensen)
+* Fix: shopspring-numeric extension does not panic on NaN
+* Numeric can be assigned to string
+* Fix: Do not send IPv4 networks as IPv4-mapped IPv6 (William Storey)
+* Fix: PlanScan for interface{}(nil) (James Hartig)
+* Fix: *sql.Scanner for NULL handling (James Hartig)
+* Timestamp[tz].Set() supports string (Harmen)
+* Fix: Hstore AssignTo with map of *string (Diego Becciolini)
+
+# 1.11.0 (April 21, 2022)
+
+* Add multirange for numeric, int4, and int8 (Vu)
+* JSONBArray now supports json.RawMessage (Jens Emil Schulz Østergaard)
+* Add RecordArray (WGH)
+* Add UnmarshalJSON to pgtype.Int2
+* Hstore.Set accepts map[string]Text
+
# 1.10.0 (February 7, 2022)
* Normalize UTC timestamps to comply with stdlib (Torkel Rogstad)
diff --git a/array_type.go b/array_type.go
index 1bd0244..7146655 100644
--- a/array_type.go
+++ b/array_type.go
@@ -11,7 +11,7 @@ import (
// ArrayType represents an array type. While it implements Value, this is only in service of its type conversion duties
// when registered as a data type in a ConnType. It should not be used directly as a Value. ArrayType is a convenience
-// type for types that do not have an concrete array type.
+// type for types that do not have a concrete array type.
type ArrayType struct {
elements []ValueTranscoder
dimensions []ArrayDimension
diff --git a/date.go b/date.go
index e8d21a7..ca84970 100644
--- a/date.go
+++ b/date.go
@@ -37,14 +37,14 @@ func (dst *Date) Set(src interface{}) error {
switch value := src.(type) {
case time.Time:
*dst = Date{Time: value, Status: Present}
- case string:
- return dst.DecodeText(nil, []byte(value))
case *time.Time:
if value == nil {
*dst = Date{Status: Null}
} else {
return dst.Set(*value)
}
+ case string:
+ return dst.DecodeText(nil, []byte(value))
case *string:
if value == nil {
*dst = Date{Status: Null}
diff --git a/debian/changelog b/debian/changelog
index 0e8faa7..57ea9f2 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+golang-github-jackc-pgtype (1.12.0-1) UNRELEASED; urgency=low
+
+ * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk> Mon, 31 Oct 2022 15:53:47 -0000
+
golang-github-jackc-pgtype (1.10.0-5) unstable; urgency=medium
[ Debian Janitor ]
diff --git a/enum_type.go b/enum_type.go
index d340320..5265782 100644
--- a/enum_type.go
+++ b/enum_type.go
@@ -2,7 +2,7 @@ package pgtype
import "fmt"
-// EnumType represents a enum type. While it implements Value, this is only in service of its type conversion duties
+// EnumType represents an enum type. While it implements Value, this is only in service of its type conversion duties
// when registered as a data type in a ConnType. It should not be used directly as a Value.
type EnumType struct {
value string
diff --git a/ext/shopspring-numeric/decimal.go b/ext/shopspring-numeric/decimal.go
index ef3ce20..c75efa3 100644
--- a/ext/shopspring-numeric/decimal.go
+++ b/ext/shopspring-numeric/decimal.go
@@ -263,6 +263,16 @@ func (dst *Numeric) DecodeBinary(ci *pgtype.ConnInfo, src []byte) error {
return err
}
+ if num.NaN {
+ return errors.New("cannot decode 'NaN'")
+ }
+ if num.InfinityModifier == pgtype.Infinity {
+ return errors.New("cannot decode 'Infinity'")
+ }
+ if num.InfinityModifier == pgtype.NegativeInfinity {
+ return errors.New("cannot decode '-Infinity'")
+ }
+
*dst = Numeric{Decimal: decimal.NewFromBigInt(num.Int, num.Exp), Status: pgtype.Present}
return nil
diff --git a/ext/shopspring-numeric/decimal_test.go b/ext/shopspring-numeric/decimal_test.go
index e635da4..e3c6d59 100644
--- a/ext/shopspring-numeric/decimal_test.go
+++ b/ext/shopspring-numeric/decimal_test.go
@@ -1,6 +1,7 @@
package numeric_test
import (
+ "context"
"fmt"
"math/big"
"math/rand"
@@ -93,6 +94,15 @@ func TestNumericNormalize(t *testing.T) {
})
}
+func TestNumericNaN(t *testing.T) {
+ conn := testutil.MustConnectPgx(t)
+ defer testutil.MustCloseContext(t, conn)
+
+ var n shopspring.Numeric
+ err := conn.QueryRow(context.Background(), `select 'NaN'::numeric`).Scan(&n)
+ require.EqualError(t, err, `can't scan into dest[0]: cannot decode 'NaN'`)
+}
+
func TestNumericTranscode(t *testing.T) {
testutil.TestSuccessfulTranscodeEqFunc(t, "numeric", []interface{}{
&shopspring.Numeric{Decimal: mustParseDecimal(t, "0"), Status: pgtype.Present},
diff --git a/hstore.go b/hstore.go
index f46eeaf..e42b755 100644
--- a/hstore.go
+++ b/hstore.go
@@ -50,6 +50,8 @@ func (dst *Hstore) Set(src interface{}) error {
}
}
*dst = Hstore{Map: m, Status: Present}
+ case map[string]Text:
+ *dst = Hstore{Map: value, Status: Present}
default:
return fmt.Errorf("cannot convert %v to Hstore", src)
}
@@ -88,7 +90,8 @@ func (src *Hstore) AssignTo(dst interface{}) error {
case Null:
(*v)[k] = nil
case Present:
- (*v)[k] = &val.String
+ str := val.String
+ (*v)[k] = &str
default:
return fmt.Errorf("cannot decode %#v into %T", src, dst)
}
@@ -411,7 +414,7 @@ func parseHstore(s string) (k []string, v []Text, err error) {
r, end = p.Consume()
switch {
case end:
- err = errors.New("Found EOS after ',', expcting space")
+ err = errors.New("Found EOS after ',', expecting space")
case (unicode.IsSpace(r)):
r, end = p.Consume()
state = hsKey
diff --git a/hstore_test.go b/hstore_test.go
index 73ee061..32a8f01 100644
--- a/hstore_test.go
+++ b/hstore_test.go
@@ -181,13 +181,14 @@ func TestHstoreAssignTo(t *testing.T) {
func TestHstoreAssignToNullable(t *testing.T) {
var m map[string]*string
+ strPtr := func(str string) *string { return &str }
simpleTests := []struct {
src pgtype.Hstore
dst *map[string]*string
expected map[string]*string
}{
- {src: pgtype.Hstore{Map: map[string]pgtype.Text{"foo": {Status: pgtype.Null}}, Status: pgtype.Present}, dst: &m, expected: map[string]*string{"foo": nil}},
+ {src: pgtype.Hstore{Map: map[string]pgtype.Text{"foo": {Status: pgtype.Null}, "bar": {String: "1", Status: pgtype.Present}, "baz": {String: "2", Status: pgtype.Present}}, Status: pgtype.Present}, dst: &m, expected: map[string]*string{"foo": nil, "bar": strPtr("1"), "baz": strPtr("2")}},
{src: pgtype.Hstore{Status: pgtype.Null}, dst: &m, expected: ((map[string]*string)(nil))},
}
diff --git a/inet.go b/inet.go
index f35f88b..976f0d7 100644
--- a/inet.go
+++ b/inet.go
@@ -2,8 +2,10 @@ package pgtype
import (
"database/sql/driver"
+ "encoding"
"fmt"
"net"
+ "strings"
)
// Network address family is dependent on server socket.h value for AF_INET.
@@ -47,17 +49,26 @@ func (dst *Inet) Set(src interface{}) error {
case string:
ip, ipnet, err := net.ParseCIDR(value)
if err != nil {
- ip = net.ParseIP(value)
+ ip := net.ParseIP(value)
if ip == nil {
return fmt.Errorf("unable to parse inet address: %s", value)
}
- ipnet = &net.IPNet{IP: ip, Mask: net.CIDRMask(128, 128)}
- if ipv4 := ip.To4(); ipv4 != nil {
- ip = ipv4
- ipnet.Mask = net.CIDRMask(32, 32)
+
+ if ipv4 := maybeGetIPv4(value, ip); ipv4 != nil {
+ ipnet = &net.IPNet{IP: ipv4, Mask: net.CIDRMask(32, 32)}
+ } else {
+ ipnet = &net.IPNet{IP: ip, Mask: net.CIDRMask(128, 128)}
+ }
+ } else {
+ ipnet.IP = ip
+ if ipv4 := maybeGetIPv4(value, ipnet.IP); ipv4 != nil {
+ ipnet.IP = ipv4
+ if len(ipnet.Mask) == 16 {
+ ipnet.Mask = ipnet.Mask[12:] // Not sure this is ever needed.
+ }
}
}
- ipnet.IP = ip
+
*dst = Inet{IPNet: ipnet, Status: Present}
case *net.IPNet:
if value == nil {
@@ -78,6 +89,16 @@ func (dst *Inet) Set(src interface{}) error {
return dst.Set(*value)
}
default:
+ if tv, ok := src.(encoding.TextMarshaler); ok {
+ text, err := tv.MarshalText()
+ if err != nil {
+ return fmt.Errorf("cannot marshal %v: %w", value, err)
+ }
+ return dst.Set(string(text))
+ }
+ if sv, ok := src.(fmt.Stringer); ok {
+ return dst.Set(sv.String())
+ }
if originalSrc, ok := underlyingPtrType(src); ok {
return dst.Set(originalSrc)
}
@@ -87,6 +108,25 @@ func (dst *Inet) Set(src interface{}) error {
return nil
}
+// Convert the net.IP to IPv4, if appropriate.
+//
+// When parsing a string to a net.IP using net.ParseIP() and the like, we get a
+// 16 byte slice for IPv4 addresses as well as IPv6 addresses. This function
+// calls To4() to convert them to a 4 byte slice. This is useful as it allows
+// users of the net.IP check for IPv4 addresses based on the length and makes
+// it clear we are handling IPv4 as opposed to IPv6 or IPv4-mapped IPv6
+// addresses.
+func maybeGetIPv4(input string, ip net.IP) net.IP {
+ // Do not do this if the provided input looks like IPv6. This is because
+ // To4() on IPv4-mapped IPv6 addresses converts them to IPv4, which behave
+ // different in some cases.
+ if strings.Contains(input, ":") {
+ return nil
+ }
+
+ return ip.To4()
+}
+
func (dst Inet) Get() interface{} {
switch dst.Status {
case Present:
@@ -118,6 +158,12 @@ func (src *Inet) AssignTo(dst interface{}) error {
copy(*v, src.IPNet.IP)
return nil
default:
+ if tv, ok := dst.(encoding.TextUnmarshaler); ok {
+ if err := tv.UnmarshalText([]byte(src.IPNet.String())); err != nil {
+ return fmt.Errorf("cannot unmarshal %v to %T: %w", src, dst, err)
+ }
+ return nil
+ }
if nextDst, retry := GetAssignToDstType(dst); retry {
return src.AssignTo(nextDst)
}
@@ -169,7 +215,7 @@ func (dst *Inet) DecodeBinary(ci *ConnInfo, src []byte) error {
}
if len(src) != 8 && len(src) != 20 {
- return fmt.Errorf("Received an invalid size for a inet: %d", len(src))
+ return fmt.Errorf("Received an invalid size for an inet: %d", len(src))
}
// ignore family
diff --git a/inet_test.go b/inet_test.go
index 09c6b21..0e1d0ca 100644
--- a/inet_test.go
+++ b/inet_test.go
@@ -1,8 +1,10 @@
package pgtype_test
import (
+ "fmt"
"net"
"reflect"
+ "strings"
"testing"
"github.com/jackc/pgtype"
@@ -44,6 +46,14 @@ func TestCidrTranscode(t *testing.T) {
})
}
+type textMarshaler struct {
+ Text string
+}
+
+func (t textMarshaler) MarshalText() (text []byte, err error) {
+ return []byte(t.Text), err
+}
+
func TestInetSet(t *testing.T) {
successfulTests := []struct {
source interface{}
@@ -52,10 +62,18 @@ func TestInetSet(t *testing.T) {
{source: mustParseCIDR(t, "127.0.0.1/32"), result: pgtype.Inet{IPNet: mustParseCIDR(t, "127.0.0.1/32"), Status: pgtype.Present}},
{source: mustParseCIDR(t, "127.0.0.1/32").IP, result: pgtype.Inet{IPNet: mustParseCIDR(t, "127.0.0.1/32"), Status: pgtype.Present}},
{source: "127.0.0.1/32", result: pgtype.Inet{IPNet: mustParseCIDR(t, "127.0.0.1/32"), Status: pgtype.Present}},
- {source: "1.2.3.4/24", result: pgtype.Inet{IPNet: &net.IPNet{IP: net.ParseIP("1.2.3.4"), Mask: net.CIDRMask(24, 32)}, Status: pgtype.Present}},
+ {source: "1.2.3.4/24", result: pgtype.Inet{IPNet: &net.IPNet{IP: net.ParseIP("1.2.3.4").To4(), Mask: net.CIDRMask(24, 32)}, Status: pgtype.Present}},
{source: "10.0.0.1", result: pgtype.Inet{IPNet: mustParseInet(t, "10.0.0.1"), Status: pgtype.Present}},
{source: "2607:f8b0:4009:80b::200e", result: pgtype.Inet{IPNet: mustParseInet(t, "2607:f8b0:4009:80b::200e"), Status: pgtype.Present}},
{source: net.ParseIP(""), result: pgtype.Inet{Status: pgtype.Null}},
+ {source: "0.0.0.0/8", result: pgtype.Inet{IPNet: mustParseInet(t, "0.0.0.0/8"), Status: pgtype.Present}},
+ {source: "::ffff:0.0.0.0/104", result: pgtype.Inet{IPNet: &net.IPNet{IP: net.ParseIP("::ffff:0.0.0.0"), Mask: net.CIDRMask(104, 128)}, Status: pgtype.Present}},
+ {source: textMarshaler{"127.0.0.1"}, result: pgtype.Inet{IPNet: mustParseInet(t, "127.0.0.1"), Status: pgtype.Present}},
+ {source: func(s string) fmt.Stringer {
+ var b strings.Builder
+ b.WriteString(s)
+ return &b
+ }("127.0.0.1"), result: pgtype.Inet{IPNet: mustParseInet(t, "127.0.0.1"), Status: pgtype.Present}},
}
for i, tt := range successfulTests {
@@ -68,17 +86,29 @@ func TestInetSet(t *testing.T) {
assert.Equalf(t, tt.result.Status, r.Status, "%d: Status", i)
if tt.result.Status == pgtype.Present {
- assert.Equalf(t, tt.result.IPNet.Mask, r.IPNet.Mask, "%d: IP", i)
- assert.Truef(t, tt.result.IPNet.IP.Equal(r.IPNet.IP), "%d: Mask", i)
+ assert.Equalf(t, tt.result.IPNet.Mask, r.IPNet.Mask, "%d: Mask", i)
+ assert.Truef(t, tt.result.IPNet.IP.Equal(r.IPNet.IP), "%d: IP", i)
+ assert.Equalf(t, len(tt.result.IPNet.IP), len(r.IPNet.IP), "%d: IP length", i)
}
}
}
+type textUnmarshaler struct {
+ Text string
+}
+
+func (u *textUnmarshaler) UnmarshalText(text []byte) error {
+ u.Text = string(text)
+ return nil
+}
+
func TestInetAssignTo(t *testing.T) {
var ipnet net.IPNet
var pipnet *net.IPNet
var ip net.IP
var pip *net.IP
+ var um textUnmarshaler
+ var pum *textUnmarshaler
simpleTests := []struct {
src pgtype.Inet
@@ -87,8 +117,10 @@ func TestInetAssignTo(t *testing.T) {
}{
{src: pgtype.Inet{IPNet: mustParseCIDR(t, "127.0.0.1/32"), Status: pgtype.Present}, dst: &ipnet, expected: *mustParseCIDR(t, "127.0.0.1/32")},
{src: pgtype.Inet{IPNet: mustParseCIDR(t, "127.0.0.1/32"), Status: pgtype.Present}, dst: &ip, expected: mustParseCIDR(t, "127.0.0.1/32").IP},
+ {src: pgtype.Inet{IPNet: mustParseCIDR(t, "127.0.0.1/32"), Status: pgtype.Present}, dst: &um, expected: textUnmarshaler{"127.0.0.1/32"}},
{src: pgtype.Inet{Status: pgtype.Null}, dst: &pipnet, expected: ((*net.IPNet)(nil))},
{src: pgtype.Inet{Status: pgtype.Null}, dst: &pip, expected: ((*net.IP)(nil))},
+ {src: pgtype.Inet{Status: pgtype.Null}, dst: &pum, expected: ((*textUnmarshaler)(nil))},
}
for i, tt := range simpleTests {
diff --git a/int2.go b/int2.go
index 3eb5aeb..0775882 100644
--- a/int2.go
+++ b/int2.go
@@ -3,6 +3,7 @@ package pgtype
import (
"database/sql/driver"
"encoding/binary"
+ "encoding/json"
"fmt"
"math"
"strconv"
@@ -302,3 +303,19 @@ func (src Int2) MarshalJSON() ([]byte, error) {
return nil, errBadStatus
}
+
+func (dst *Int2) UnmarshalJSON(b []byte) error {
+ var n *int16
+ err := json.Unmarshal(b, &n)
+ if err != nil {
+ return err
+ }
+
+ if n == nil {
+ *dst = Int2{Status: Null}
+ } else {
+ *dst = Int2{Int: *n, Status: Present}
+ }
+
+ return nil
+}
diff --git a/int4_multirange.go b/int4_multirange.go
new file mode 100644
index 0000000..c3432ce
--- /dev/null
+++ b/int4_multirange.go
@@ -0,0 +1,239 @@
+package pgtype
+
+import (
+ "database/sql/driver"
+ "encoding/binary"
+ "fmt"
+
+ "github.com/jackc/pgio"
+)
+
+type Int4multirange struct {
+ Ranges []Int4range
+ Status Status
+}
+
+func (dst *Int4multirange) Set(src interface{}) error {
+ //untyped nil and typed nil interfaces are different
+ if src == nil {
+ *dst = Int4multirange{Status: Null}
+ return nil
+ }
+
+ switch value := src.(type) {
+ case Int4multirange:
+ *dst = value
+ case *Int4multirange:
+ *dst = *value
+ case string:
+ return dst.DecodeText(nil, []byte(value))
+ case []Int4range:
+ if value == nil {
+ *dst = Int4multirange{Status: Null}
+ } else if len(value) == 0 {
+ *dst = Int4multirange{Status: Present}
+ } else {
+ elements := make([]Int4range, len(value))
+ for i := range value {
+ if err := elements[i].Set(value[i]); err != nil {
+ return err
+ }
+ }
+ *dst = Int4multirange{
+ Ranges: elements,
+ Status: Present,
+ }
+ }
+ case []*Int4range:
+ if value == nil {
+ *dst = Int4multirange{Status: Null}
+ } else if len(value) == 0 {
+ *dst = Int4multirange{Status: Present}
+ } else {
+ elements := make([]Int4range, len(value))
+ for i := range value {
+ if err := elements[i].Set(value[i]); err != nil {
+ return err
+ }
+ }
+ *dst = Int4multirange{
+ Ranges: elements,
+ Status: Present,
+ }
+ }
+ default:
+ return fmt.Errorf("cannot convert %v to Int4multirange", src)
+ }
+
+ return nil
+
+}
+
+func (dst Int4multirange) Get() interface{} {
+ switch dst.Status {
+ case Present:
+ return dst
+ case Null:
+ return nil
+ default:
+ return dst.Status
+ }
+}
+
+func (src *Int4multirange) AssignTo(dst interface{}) error {
+ return fmt.Errorf("cannot assign %v to %T", src, dst)
+}
+
+func (dst *Int4multirange) DecodeText(ci *ConnInfo, src []byte) error {
+ if src == nil {
+ *dst = Int4multirange{Status: Null}
+ return nil
+ }
+
+ utmr, err := ParseUntypedTextMultirange(string(src))
+ if err != nil {
+ return err
+ }
+
+ var elements []Int4range
+
+ if len(utmr.Elements) > 0 {
+ elements = make([]Int4range, len(utmr.Elements))
+
+ for i, s := range utmr.Elements {
+ var elem Int4range
+
+ elemSrc := []byte(s)
+
+ err = elem.DecodeText(ci, elemSrc)
+ if err != nil {
+ return err
+ }
+
+ elements[i] = elem
+ }
+ }
+
+ *dst = Int4multirange{Ranges: elements, Status: Present}
+
+ return nil
+}
+
+func (dst *Int4multirange) DecodeBinary(ci *ConnInfo, src []byte) error {
+ if src == nil {
+ *dst = Int4multirange{Status: Null}
+ return nil
+ }
+
+ rp := 0
+
+ numElems := int(binary.BigEndian.Uint32(src[rp:]))
+ rp += 4
+
+ if numElems == 0 {
+ *dst = Int4multirange{Status: Present}
+ return nil
+ }
+
+ elements := make([]Int4range, numElems)
+
+ for i := range elements {
+ elemLen := int(int32(binary.BigEndian.Uint32(src[rp:])))
+ rp += 4
+ var elemSrc []byte
+ if elemLen >= 0 {
+ elemSrc = src[rp : rp+elemLen]
+ rp += elemLen
+ }
+ err := elements[i].DecodeBinary(ci, elemSrc)
+ if err != nil {
+ return err
+ }
+ }
+
+ *dst = Int4multirange{Ranges: elements, Status: Present}
+ return nil
+}
+
+func (src Int4multirange) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
+ switch src.Status {
+ case Null:
+ return nil, nil
+ case Undefined:
+ return nil, errUndefined
+ }
+
+ buf = append(buf, '{')
+
+ inElemBuf := make([]byte, 0, 32)
+ for i, elem := range src.Ranges {
+ if i > 0 {
+ buf = append(buf, ',')
+ }
+
+ elemBuf, err := elem.EncodeText(ci, inElemBuf)
+ if err != nil {
+ return nil, err
+ }
+ if elemBuf == nil {
+ return nil, fmt.Errorf("multi-range does not allow null range")
+ } else {
+ buf = append(buf, string(elemBuf)...)
+ }
+
+ }
+
+ buf = append(buf, '}')
+
+ return buf, nil
+}
+
+func (src Int4multirange) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
+ switch src.Status {
+ case Null:
+ return nil, nil
+ case Undefined:
+ return nil, errUndefined
+ }
+
+ buf = pgio.AppendInt32(buf, int32(len(src.Ranges)))
+
+ for i := range src.Ranges {
+ sp := len(buf)
+ buf = pgio.AppendInt32(buf, -1)
+
+ elemBuf, err := src.Ranges[i].EncodeBinary(ci, buf)
+ if err != nil {
+ return nil, err
+ }
+ if elemBuf != nil {
+ buf = elemBuf
+ pgio.SetInt32(buf[sp:], int32(len(buf[sp:])-4))
+ }
+ }
+
+ return buf, nil
+}
+
+// Scan implements the database/sql Scanner interface.
+func (dst *Int4multirange) Scan(src interface{}) error {
+ if src == nil {
+ return dst.DecodeText(nil, nil)
+ }
+
+ switch src := src.(type) {
+ case string:
+ return dst.DecodeText(nil, []byte(src))
+ case []byte:
+ srcCopy := make([]byte, len(src))
+ copy(srcCopy, src)
+ return dst.DecodeText(nil, srcCopy)
+ }
+
+ return fmt.Errorf("cannot scan %T", src)
+}
+
+// Value implements the database/sql/driver Valuer interface.
+func (src Int4multirange) Value() (driver.Value, error) {
+ return EncodeValueText(src)
+}
diff --git a/int4_multirange_test.go b/int4_multirange_test.go
new file mode 100644
index 0000000..e123c40
--- /dev/null
+++ b/int4_multirange_test.go
@@ -0,0 +1,81 @@
+package pgtype_test
+
+import (
+ "testing"
+
+ "github.com/jackc/pgtype"
+ "github.com/jackc/pgtype/testutil"
+)
+
+func TestInt4multirangeTranscode(t *testing.T) {
+ testutil.TestSuccessfulTranscode(t, "int4multirange", []interface{}{
+ &pgtype.Int4multirange{
+ Ranges: nil,
+ Status: pgtype.Present,
+ },
+ &pgtype.Int4multirange{
+ Ranges: []pgtype.Int4range{
+ {
+ Lower: pgtype.Int4{Int: -543, Status: pgtype.Present},
+ Upper: pgtype.Int4{Int: 342, Status: pgtype.Present},
+ LowerType: pgtype.Inclusive,
+ UpperType: pgtype.Exclusive,
+ Status: pgtype.Present,
+ },
+ },
+ Status: pgtype.Present,
+ },
+ &pgtype.Int4multirange{
+ Ranges: []pgtype.Int4range{
+ {
+ Lower: pgtype.Int4{Int: -42, Status: pgtype.Present},
+ Upper: pgtype.Int4{Int: -5, Status: pgtype.Present},
+ LowerType: pgtype.Inclusive,
+ UpperType: pgtype.Exclusive,
+ Status: pgtype.Present,
+ },
+ {
+ Lower: pgtype.Int4{Int: 5, Status: pgtype.Present},
+ Upper: pgtype.Int4{Int: 42, Status: pgtype.Present},
+ LowerType: pgtype.Inclusive,
+ UpperType: pgtype.Exclusive,
+ Status: pgtype.Present,
+ },
+ {
+ Lower: pgtype.Int4{Int: 52, Status: pgtype.Present},
+ LowerType: pgtype.Inclusive,
+ UpperType: pgtype.Unbounded,
+ Status: pgtype.Present,
+ },
+ },
+ Status: pgtype.Present,
+ },
+ })
+}
+
+func TestInt4multirangeNormalize(t *testing.T) {
+ testutil.TestSuccessfulNormalize(t, []testutil.NormalizeTest{
+ {
+ SQL: "select int4multirange(int4range(1, 14, '(]'), int4range(20, 25, '()'))",
+ Value: pgtype.Int4multirange{
+ Ranges: []pgtype.Int4range{
+ {
+ Lower: pgtype.Int4{Int: 2, Status: pgtype.Present},
+ Upper: pgtype.Int4{Int: 15, Status: pgtype.Present},
+ LowerType: pgtype.Inclusive,
+ UpperType: pgtype.Exclusive,
+ Status: pgtype.Present,
+ },
+ {
+ Lower: pgtype.Int4{Int: 21, Status: pgtype.Present},
+ Upper: pgtype.Int4{Int: 25, Status: pgtype.Present},
+ LowerType: pgtype.Inclusive,
+ UpperType: pgtype.Exclusive,
+ Status: pgtype.Present,
+ },
+ },
+ Status: pgtype.Present,
+ },
+ },
+ })
+}
diff --git a/int8_multirange.go b/int8_multirange.go
new file mode 100644
index 0000000..e097642
--- /dev/null
+++ b/int8_multirange.go
@@ -0,0 +1,239 @@
+package pgtype
+
+import (
+ "database/sql/driver"
+ "encoding/binary"
+ "fmt"
+
+ "github.com/jackc/pgio"
+)
+
+type Int8multirange struct {
+ Ranges []Int8range
+ Status Status
+}
+
+func (dst *Int8multirange) Set(src interface{}) error {
+ //untyped nil and typed nil interfaces are different
+ if src == nil {
+ *dst = Int8multirange{Status: Null}
+ return nil
+ }
+
+ switch value := src.(type) {
+ case Int8multirange:
+ *dst = value
+ case *Int8multirange:
+ *dst = *value
+ case string:
+ return dst.DecodeText(nil, []byte(value))
+ case []Int8range:
+ if value == nil {
+ *dst = Int8multirange{Status: Null}
+ } else if len(value) == 0 {
+ *dst = Int8multirange{Status: Present}
+ } else {
+ elements := make([]Int8range, len(value))
+ for i := range value {
+ if err := elements[i].Set(value[i]); err != nil {
+ return err
+ }
+ }
+ *dst = Int8multirange{
+ Ranges: elements,
+ Status: Present,
+ }
+ }
+ case []*Int8range:
+ if value == nil {
+ *dst = Int8multirange{Status: Null}
+ } else if len(value) == 0 {
+ *dst = Int8multirange{Status: Present}
+ } else {
+ elements := make([]Int8range, len(value))
+ for i := range value {
+ if err := elements[i].Set(value[i]); err != nil {
+ return err
+ }
+ }
+ *dst = Int8multirange{
+ Ranges: elements,
+ Status: Present,
+ }
+ }
+ default:
+ return fmt.Errorf("cannot convert %v to Int8multirange", src)
+ }
+
+ return nil
+
+}
+
+func (dst Int8multirange) Get() interface{} {
+ switch dst.Status {
+ case Present:
+ return dst
+ case Null:
+ return nil
+ default:
+ return dst.Status
+ }
+}
+
+func (src *Int8multirange) AssignTo(dst interface{}) error {
+ return fmt.Errorf("cannot assign %v to %T", src, dst)
+}
+
+func (dst *Int8multirange) DecodeText(ci *ConnInfo, src []byte) error {
+ if src == nil {
+ *dst = Int8multirange{Status: Null}
+ return nil
+ }
+
+ utmr, err := ParseUntypedTextMultirange(string(src))
+ if err != nil {
+ return err
+ }
+
+ var elements []Int8range
+
+ if len(utmr.Elements) > 0 {
+ elements = make([]Int8range, len(utmr.Elements))
+
+ for i, s := range utmr.Elements {
+ var elem Int8range
+
+ elemSrc := []byte(s)
+
+ err = elem.DecodeText(ci, elemSrc)
+ if err != nil {
+ return err
+ }
+
+ elements[i] = elem
+ }
+ }
+
+ *dst = Int8multirange{Ranges: elements, Status: Present}
+
+ return nil
+}
+
+func (dst *Int8multirange) DecodeBinary(ci *ConnInfo, src []byte) error {
+ if src == nil {
+ *dst = Int8multirange{Status: Null}
+ return nil
+ }
+
+ rp := 0
+
+ numElems := int(binary.BigEndian.Uint32(src[rp:]))
+ rp += 4
+
+ if numElems == 0 {
+ *dst = Int8multirange{Status: Present}
+ return nil
+ }
+
+ elements := make([]Int8range, numElems)
+
+ for i := range elements {
+ elemLen := int(int32(binary.BigEndian.Uint32(src[rp:])))
+ rp += 4
+ var elemSrc []byte
+ if elemLen >= 0 {
+ elemSrc = src[rp : rp+elemLen]
+ rp += elemLen
+ }
+ err := elements[i].DecodeBinary(ci, elemSrc)
+ if err != nil {
+ return err
+ }
+ }
+
+ *dst = Int8multirange{Ranges: elements, Status: Present}
+ return nil
+}
+
+func (src Int8multirange) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
+ switch src.Status {
+ case Null:
+ return nil, nil
+ case Undefined:
+ return nil, errUndefined
+ }
+
+ buf = append(buf, '{')
+
+ inElemBuf := make([]byte, 0, 32)
+ for i, elem := range src.Ranges {
+ if i > 0 {
+ buf = append(buf, ',')
+ }
+
+ elemBuf, err := elem.EncodeText(ci, inElemBuf)
+ if err != nil {
+ return nil, err
+ }
+ if elemBuf == nil {
+ return nil, fmt.Errorf("multi-range does not allow null range")
+ } else {
+ buf = append(buf, string(elemBuf)...)
+ }
+
+ }
+
+ buf = append(buf, '}')
+
+ return buf, nil
+}
+
+func (src Int8multirange) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
+ switch src.Status {
+ case Null:
+ return nil, nil
+ case Undefined:
+ return nil, errUndefined
+ }
+
+ buf = pgio.AppendInt32(buf, int32(len(src.Ranges)))
+
+ for i := range src.Ranges {
+ sp := len(buf)
+ buf = pgio.AppendInt32(buf, -1)
+
+ elemBuf, err := src.Ranges[i].EncodeBinary(ci, buf)
+ if err != nil {
+ return nil, err
+ }
+ if elemBuf != nil {
+ buf = elemBuf
+ pgio.SetInt32(buf[sp:], int32(len(buf[sp:])-4))
+ }
+ }
+
+ return buf, nil
+}
+
+// Scan implements the database/sql Scanner interface.
+func (dst *Int8multirange) Scan(src interface{}) error {
+ if src == nil {
+ return dst.DecodeText(nil, nil)
+ }
+
+ switch src := src.(type) {
+ case string:
+ return dst.DecodeText(nil, []byte(src))
+ case []byte:
+ srcCopy := make([]byte, len(src))
+ copy(srcCopy, src)
+ return dst.DecodeText(nil, srcCopy)
+ }
+
+ return fmt.Errorf("cannot scan %T", src)
+}
+
+// Value implements the database/sql/driver Valuer interface.
+func (src Int8multirange) Value() (driver.Value, error) {
+ return EncodeValueText(src)
+}
diff --git a/int8_multirange_test.go b/int8_multirange_test.go
new file mode 100644
index 0000000..a423338
--- /dev/null
+++ b/int8_multirange_test.go
@@ -0,0 +1,81 @@
+package pgtype_test
+
+import (
+ "testing"
+
+ "github.com/jackc/pgtype"
+ "github.com/jackc/pgtype/testutil"
+)
+
+func TestInt8multirangeTranscode(t *testing.T) {
+ testutil.TestSuccessfulTranscode(t, "int8multirange", []interface{}{
+ &pgtype.Int8multirange{
+ Ranges: nil,
+ Status: pgtype.Present,
+ },
+ &pgtype.Int8multirange{
+ Ranges: []pgtype.Int8range{
+ {
+ Lower: pgtype.Int8{Int: -543, Status: pgtype.Present},
+ Upper: pgtype.Int8{Int: 342, Status: pgtype.Present},
+ LowerType: pgtype.Inclusive,
+ UpperType: pgtype.Exclusive,
+ Status: pgtype.Present,
+ },
+ },
+ Status: pgtype.Present,
+ },
+ &pgtype.Int8multirange{
+ Ranges: []pgtype.Int8range{
+ {
+ Lower: pgtype.Int8{Int: -42, Status: pgtype.Present},
+ Upper: pgtype.Int8{Int: -5, Status: pgtype.Present},
+ LowerType: pgtype.Inclusive,
+ UpperType: pgtype.Exclusive,
+ Status: pgtype.Present,
+ },
+ {
+ Lower: pgtype.Int8{Int: 5, Status: pgtype.Present},
+ Upper: pgtype.Int8{Int: 42, Status: pgtype.Present},
+ LowerType: pgtype.Inclusive,
+ UpperType: pgtype.Exclusive,
+ Status: pgtype.Present,
+ },
+ {
+ Lower: pgtype.Int8{Int: 52, Status: pgtype.Present},
+ LowerType: pgtype.Inclusive,
+ UpperType: pgtype.Unbounded,
+ Status: pgtype.Present,
+ },
+ },
+ Status: pgtype.Present,
+ },
+ })
+}
+
+func TestInt8multirangeNormalize(t *testing.T) {
+ testutil.TestSuccessfulNormalize(t, []testutil.NormalizeTest{
+ {
+ SQL: "select int8multirange(int8range(1, 14, '(]'), int8range(20, 25, '()'))",
+ Value: pgtype.Int8multirange{
+ Ranges: []pgtype.Int8range{
+ {
+ Lower: pgtype.Int8{Int: 2, Status: pgtype.Present},
+ Upper: pgtype.Int8{Int: 15, Status: pgtype.Present},
+ LowerType: pgtype.Inclusive,
+ UpperType: pgtype.Exclusive,
+ Status: pgtype.Present,
+ },
+ {
+ Lower: pgtype.Int8{Int: 21, Status: pgtype.Present},
+ Upper: pgtype.Int8{Int: 25, Status: pgtype.Present},
+ LowerType: pgtype.Inclusive,
+ UpperType: pgtype.Exclusive,
+ Status: pgtype.Present,
+ },
+ },
+ Status: pgtype.Present,
+ },
+ },
+ })
+}
diff --git a/interval.go b/interval.go
index b01fbb7..00ec47c 100644
--- a/interval.go
+++ b/interval.go
@@ -174,7 +174,7 @@ func (dst *Interval) DecodeBinary(ci *ConnInfo, src []byte) error {
}
if len(src) != 16 {
- return fmt.Errorf("Received an invalid size for a interval: %d", len(src))
+ return fmt.Errorf("Received an invalid size for an interval: %d", len(src))
}
microseconds := int64(binary.BigEndian.Uint64(src))
diff --git a/json_array.go b/json_array.go
new file mode 100644
index 0000000..8d68882
--- /dev/null
+++ b/json_array.go
@@ -0,0 +1,546 @@
+// Code generated by erb. DO NOT EDIT.
+
+package pgtype
+
+import (
+ "database/sql/driver"
+ "encoding/binary"
+ "encoding/json"
+ "fmt"
+ "reflect"
+
+ "github.com/jackc/pgio"
+)
+
+type JSONArray struct {
+ Elements []JSON
+ Dimensions []ArrayDimension
+ Status Status
+}
+
+func (dst *JSONArray) Set(src interface{}) error {
+ // untyped nil and typed nil interfaces are different
+ if src == nil {
+ *dst = JSONArray{Status: Null}
+ return nil
+ }
+
+ if value, ok := src.(interface{ Get() interface{} }); ok {
+ value2 := value.Get()
+ if value2 != value {
+ return dst.Set(value2)
+ }
+ }
+
+ // Attempt to match to select common types:
+ switch value := src.(type) {
+
+ case []string:
+ if value == nil {
+ *dst = JSONArray{Status: Null}
+ } else if len(value) == 0 {
+ *dst = JSONArray{Status: Present}
+ } else {
+ elements := make([]JSON, len(value))
+ for i := range value {
+ if err := elements[i].Set(value[i]); err != nil {
+ return err
+ }
+ }
+ *dst = JSONArray{
+ Elements: elements,
+ Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
+ Status: Present,
+ }
+ }
+
+ case [][]byte:
+ if value == nil {
+ *dst = JSONArray{Status: Null}
+ } else if len(value) == 0 {
+ *dst = JSONArray{Status: Present}
+ } else {
+ elements := make([]JSON, len(value))
+ for i := range value {
+ if err := elements[i].Set(value[i]); err != nil {
+ return err
+ }
+ }
+ *dst = JSONArray{
+ Elements: elements,
+ Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
+ Status: Present,
+ }
+ }
+
+ case []json.RawMessage:
+ if value == nil {
+ *dst = JSONArray{Status: Null}
+ } else if len(value) == 0 {
+ *dst = JSONArray{Status: Present}
+ } else {
+ elements := make([]JSON, len(value))
+ for i := range value {
+ if err := elements[i].Set(value[i]); err != nil {
+ return err
+ }
+ }
+ *dst = JSONArray{
+ Elements: elements,
+ Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
+ Status: Present,
+ }
+ }
+
+ case []JSON:
+ if value == nil {
+ *dst = JSONArray{Status: Null}
+ } else if len(value) == 0 {
+ *dst = JSONArray{Status: Present}
+ } else {
+ *dst = JSONArray{
+ Elements: value,
+ Dimensions: []ArrayDimension{{Length: int32(len(value)), LowerBound: 1}},
+ Status: Present,
+ }
+ }
+ default:
+ // Fallback to reflection if an optimised match was not found.
+ // The reflection is necessary for arrays and multidimensional slices,
+ // but it comes with a 20-50% performance penalty for large arrays/slices
+ reflectedValue := reflect.ValueOf(src)
+ if !reflectedValue.IsValid() || reflectedValue.IsZero() {
+ *dst = JSONArray{Status: Null}
+ return nil
+ }
+
+ dimensions, elementsLength, ok := findDimensionsFromValue(reflectedValue, nil, 0)
+ if !ok {
+ return fmt.Errorf("cannot find dimensions of %v for JSONArray", src)
+ }
+ if elementsLength == 0 {
+ *dst = JSONArray{Status: Present}
+ return nil
+ }
+ if len(dimensions) == 0 {
+ if originalSrc, ok := underlyingSliceType(src); ok {
+ return dst.Set(originalSrc)
+ }
+ return fmt.Errorf("cannot convert %v to JSONArray", src)
+ }
+
+ *dst = JSONArray{
+ Elements: make([]JSON, elementsLength),
+ Dimensions: dimensions,
+ Status: Present,
+ }
+ elementCount, err := dst.setRecursive(reflectedValue, 0, 0)
+ if err != nil {
+ // Maybe the target was one dimension too far, try again:
+ if len(dst.Dimensions) > 1 {
+ dst.Dimensions = dst.Dimensions[:len(dst.Dimensions)-1]
+ elementsLength = 0
+ for _, dim := range dst.Dimensions {
+ if elementsLength == 0 {
+ elementsLength = int(dim.Length)
+ } else {
+ elementsLength *= int(dim.Length)
+ }
+ }
+ dst.Elements = make([]JSON, elementsLength)
+ elementCount, err = dst.setRecursive(reflectedValue, 0, 0)
+ if err != nil {
+ return err
+ }
+ } else {
+ return err
+ }
+ }
+ if elementCount != len(dst.Elements) {
+ return fmt.Errorf("cannot convert %v to JSONArray, expected %d dst.Elements, but got %d instead", src, len(dst.Elements), elementCount)
+ }
+ }
+
+ return nil
+}
+
+func (dst *JSONArray) setRecursive(value reflect.Value, index, dimension int) (int, error) {
+ switch value.Kind() {
+ case reflect.Array:
+ fallthrough
+ case reflect.Slice:
+ if len(dst.Dimensions) == dimension {
+ break
+ }
+
+ valueLen := value.Len()
+ if int32(valueLen) != dst.Dimensions[dimension].Length {
+ return 0, fmt.Errorf("multidimensional arrays must have array expressions with matching dimensions")
+ }
+ for i := 0; i < valueLen; i++ {
+ var err error
+ index, err = dst.setRecursive(value.Index(i), index, dimension+1)
+ if err != nil {
+ return 0, err
+ }
+ }
+
+ return index, nil
+ }
+ if !value.CanInterface() {
+ return 0, fmt.Errorf("cannot convert all values to JSONArray")
+ }
+ if err := dst.Elements[index].Set(value.Interface()); err != nil {
+ return 0, fmt.Errorf("%v in JSONArray", err)
+ }
+ index++
+
+ return index, nil
+}
+
+func (dst JSONArray) Get() interface{} {
+ switch dst.Status {
+ case Present:
+ return dst
+ case Null:
+ return nil
+ default:
+ return dst.Status
+ }
+}
+
+func (src *JSONArray) AssignTo(dst interface{}) error {
+ switch src.Status {
+ case Present:
+ if len(src.Dimensions) <= 1 {
+ // Attempt to match to select common types:
+ switch v := dst.(type) {
+
+ case *[]string:
+ *v = make([]string, len(src.Elements))
+ for i := range src.Elements {
+ if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
+ return err
+ }
+ }
+ return nil
+
+ case *[][]byte:
+ *v = make([][]byte, len(src.Elements))
+ for i := range src.Elements {
+ if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
+ return err
+ }
+ }
+ return nil
+
+ case *[]json.RawMessage:
+ *v = make([]json.RawMessage, len(src.Elements))
+ for i := range src.Elements {
+ if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
+ return err
+ }
+ }
+ return nil
+
+ }
+ }
+
+ // Try to convert to something AssignTo can use directly.
+ if nextDst, retry := GetAssignToDstType(dst); retry {
+ return src.AssignTo(nextDst)
+ }
+
+ // Fallback to reflection if an optimised match was not found.
+ // The reflection is necessary for arrays and multidimensional slices,
+ // but it comes with a 20-50% performance penalty for large arrays/slices
+ value := reflect.ValueOf(dst)
+ if value.Kind() == reflect.Ptr {
+ value = value.Elem()
+ }
+
+ switch value.Kind() {
+ case reflect.Array, reflect.Slice:
+ default:
+ return fmt.Errorf("cannot assign %T to %T", src, dst)
+ }
+
+ if len(src.Elements) == 0 {
+ if value.Kind() == reflect.Slice {
+ value.Set(reflect.MakeSlice(value.Type(), 0, 0))
+ return nil
+ }
+ }
+
+ elementCount, err := src.assignToRecursive(value, 0, 0)
+ if err != nil {
+ return err
+ }
+ if elementCount != len(src.Elements) {
+ return fmt.Errorf("cannot assign %v, needed to assign %d elements, but only assigned %d", dst, len(src.Elements), elementCount)
+ }
+
+ return nil
+ case Null:
+ return NullAssignTo(dst)
+ }
+
+ return fmt.Errorf("cannot decode %#v into %T", src, dst)
+}
+
+func (src *JSONArray) assignToRecursive(value reflect.Value, index, dimension int) (int, error) {
+ switch kind := value.Kind(); kind {
+ case reflect.Array:
+ fallthrough
+ case reflect.Slice:
+ if len(src.Dimensions) == dimension {
+ break
+ }
+
+ length := int(src.Dimensions[dimension].Length)
+ if reflect.Array == kind {
+ typ := value.Type()
+ if typ.Len() != length {
+ return 0, fmt.Errorf("expected size %d array, but %s has size %d array", length, typ, typ.Len())
+ }
+ value.Set(reflect.New(typ).Elem())
+ } else {
+ value.Set(reflect.MakeSlice(value.Type(), length, length))
+ }
+
+ var err error
+ for i := 0; i < length; i++ {
+ index, err = src.assignToRecursive(value.Index(i), index, dimension+1)
+ if err != nil {
+ return 0, err
+ }
+ }
+
+ return index, nil
+ }
+ if len(src.Dimensions) != dimension {
+ return 0, fmt.Errorf("incorrect dimensions, expected %d, found %d", len(src.Dimensions), dimension)
+ }
+ if !value.CanAddr() {
+ return 0, fmt.Errorf("cannot assign all values from JSONArray")
+ }
+ addr := value.Addr()
+ if !addr.CanInterface() {
+ return 0, fmt.Errorf("cannot assign all values from JSONArray")
+ }
+ if err := src.Elements[index].AssignTo(addr.Interface()); err != nil {
+ return 0, err
+ }
+ index++
+ return index, nil
+}
+
+func (dst *JSONArray) DecodeText(ci *ConnInfo, src []byte) error {
+ if src == nil {
+ *dst = JSONArray{Status: Null}
+ return nil
+ }
+
+ uta, err := ParseUntypedTextArray(string(src))
+ if err != nil {
+ return err
+ }
+
+ var elements []JSON
+
+ if len(uta.Elements) > 0 {
+ elements = make([]JSON, len(uta.Elements))
+
+ for i, s := range uta.Elements {
+ var elem JSON
+ var elemSrc []byte
+ if s != "NULL" || uta.Quoted[i] {
+ elemSrc = []byte(s)
+ }
+ err = elem.DecodeText(ci, elemSrc)
+ if err != nil {
+ return err
+ }
+
+ elements[i] = elem
+ }
+ }
+
+ *dst = JSONArray{Elements: elements, Dimensions: uta.Dimensions, Status: Present}
+
+ return nil
+}
+
+func (dst *JSONArray) DecodeBinary(ci *ConnInfo, src []byte) error {
+ if src == nil {
+ *dst = JSONArray{Status: Null}
+ return nil
+ }
+
+ var arrayHeader ArrayHeader
+ rp, err := arrayHeader.DecodeBinary(ci, src)
+ if err != nil {
+ return err
+ }
+
+ if len(arrayHeader.Dimensions) == 0 {
+ *dst = JSONArray{Dimensions: arrayHeader.Dimensions, Status: Present}
+ return nil
+ }
+
+ elementCount := arrayHeader.Dimensions[0].Length
+ for _, d := range arrayHeader.Dimensions[1:] {
+ elementCount *= d.Length
+ }
+
+ elements := make([]JSON, elementCount)
+
+ for i := range elements {
+ elemLen := int(int32(binary.BigEndian.Uint32(src[rp:])))
+ rp += 4
+ var elemSrc []byte
+ if elemLen >= 0 {
+ elemSrc = src[rp : rp+elemLen]
+ rp += elemLen
+ }
+ err = elements[i].DecodeBinary(ci, elemSrc)
+ if err != nil {
+ return err
+ }
+ }
+
+ *dst = JSONArray{Elements: elements, Dimensions: arrayHeader.Dimensions, Status: Present}
+ return nil
+}
+
+func (src JSONArray) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
+ switch src.Status {
+ case Null:
+ return nil, nil
+ case Undefined:
+ return nil, errUndefined
+ }
+
+ if len(src.Dimensions) == 0 {
+ return append(buf, '{', '}'), nil
+ }
+
+ buf = EncodeTextArrayDimensions(buf, src.Dimensions)
+
+ // dimElemCounts is the multiples of elements that each array lies on. For
+ // example, a single dimension array of length 4 would have a dimElemCounts of
+ // [4]. A multi-dimensional array of lengths [3,5,2] would have a
+ // dimElemCounts of [30,10,2]. This is used to simplify when to render a '{'
+ // or '}'.
+ dimElemCounts := make([]int, len(src.Dimensions))
+ dimElemCounts[len(src.Dimensions)-1] = int(src.Dimensions[len(src.Dimensions)-1].Length)
+ for i := len(src.Dimensions) - 2; i > -1; i-- {
+ dimElemCounts[i] = int(src.Dimensions[i].Length) * dimElemCounts[i+1]
+ }
+
+ inElemBuf := make([]byte, 0, 32)
+ for i, elem := range src.Elements {
+ if i > 0 {
+ buf = append(buf, ',')
+ }
+
+ for _, dec := range dimElemCounts {
+ if i%dec == 0 {
+ buf = append(buf, '{')
+ }
+ }
+
+ elemBuf, err := elem.EncodeText(ci, inElemBuf)
+ if err != nil {
+ return nil, err
+ }
+ if elemBuf == nil {
+ buf = append(buf, `NULL`...)
+ } else {
+ buf = append(buf, QuoteArrayElementIfNeeded(string(elemBuf))...)
+ }
+
+ for _, dec := range dimElemCounts {
+ if (i+1)%dec == 0 {
+ buf = append(buf, '}')
+ }
+ }
+ }
+
+ return buf, nil
+}
+
+func (src JSONArray) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
+ switch src.Status {
+ case Null:
+ return nil, nil
+ case Undefined:
+ return nil, errUndefined
+ }
+
+ arrayHeader := ArrayHeader{
+ Dimensions: src.Dimensions,
+ }
+
+ if dt, ok := ci.DataTypeForName("json"); ok {
+ arrayHeader.ElementOID = int32(dt.OID)
+ } else {
+ return nil, fmt.Errorf("unable to find oid for type name %v", "json")
+ }
+
+ for i := range src.Elements {
+ if src.Elements[i].Status == Null {
+ arrayHeader.ContainsNull = true
+ break
+ }
+ }
+
+ buf = arrayHeader.EncodeBinary(ci, buf)
+
+ for i := range src.Elements {
+ sp := len(buf)
+ buf = pgio.AppendInt32(buf, -1)
+
+ elemBuf, err := src.Elements[i].EncodeBinary(ci, buf)
+ if err != nil {
+ return nil, err
+ }
+ if elemBuf != nil {
+ buf = elemBuf
+ pgio.SetInt32(buf[sp:], int32(len(buf[sp:])-4))
+ }
+ }
+
+ return buf, nil
+}
+
+// Scan implements the database/sql Scanner interface.
+func (dst *JSONArray) Scan(src interface{}) error {
+ if src == nil {
+ return dst.DecodeText(nil, nil)
+ }
+
+ switch src := src.(type) {
+ case string:
+ return dst.DecodeText(nil, []byte(src))
+ case []byte:
+ srcCopy := make([]byte, len(src))
+ copy(srcCopy, src)
+ return dst.DecodeText(nil, srcCopy)
+ }
+
+ return fmt.Errorf("cannot scan %T", src)
+}
+
+// Value implements the database/sql/driver Valuer interface.
+func (src JSONArray) Value() (driver.Value, error) {
+ buf, err := src.EncodeText(nil, nil)
+ if err != nil {
+ return nil, err
+ }
+ if buf == nil {
+ return nil, nil
+ }
+
+ return string(buf), nil
+}
diff --git a/json_array_test.go b/json_array_test.go
new file mode 100644
index 0000000..7400b54
--- /dev/null
+++ b/json_array_test.go
@@ -0,0 +1,88 @@
+package pgtype_test
+
+import (
+ "encoding/json"
+ "reflect"
+ "testing"
+
+ "github.com/jackc/pgtype"
+ "github.com/jackc/pgtype/testutil"
+)
+
+func TestJSONArrayTranscode(t *testing.T) {
+ testutil.TestSuccessfulTranscode(t, "json[]", []interface{}{
+ &pgtype.JSONArray{
+ Elements: nil,
+ Dimensions: nil,
+ Status: pgtype.Present,
+ },
+ &pgtype.JSONArray{
+ Elements: []pgtype.JSON{
+ {Bytes: []byte(`"foo"`), Status: pgtype.Present},
+ {Status: pgtype.Null},
+ },
+ Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}},
+ Status: pgtype.Present,
+ },
+ &pgtype.JSONArray{Status: pgtype.Null},
+ &pgtype.JSONArray{
+ Elements: []pgtype.JSON{
+ {Bytes: []byte(`"foo"`), Status: pgtype.Present},
+ {Bytes: []byte("null"), Status: pgtype.Present},
+ {Bytes: []byte("42"), Status: pgtype.Present},
+ },
+ Dimensions: []pgtype.ArrayDimension{{Length: 3, LowerBound: 1}},
+ Status: pgtype.Present,
+ },
+ })
+}
+
+func TestJSONArraySet(t *testing.T) {
+ successfulTests := []struct {
+ source interface{}
+ result pgtype.JSONArray
+ }{
+ {source: []string{"{}"}, result: pgtype.JSONArray{
+ Elements: []pgtype.JSON{{Bytes: []byte("{}"), Status: pgtype.Present}},
+ Dimensions: []pgtype.ArrayDimension{{Length: 1, LowerBound: 1}},
+ Status: pgtype.Present,
+ }},
+ {source: [][]byte{[]byte("{}")}, result: pgtype.JSONArray{
+ Elements: []pgtype.JSON{{Bytes: []byte("{}"), Status: pgtype.Present}},
+ Dimensions: []pgtype.ArrayDimension{{Length: 1, LowerBound: 1}},
+ Status: pgtype.Present,
+ }},
+ {source: [][]byte{[]byte(`{"foo":1}`), []byte(`{"bar":2}`)}, result: pgtype.JSONArray{
+ Elements: []pgtype.JSON{{Bytes: []byte(`{"foo":1}`), Status: pgtype.Present}, {Bytes: []byte(`{"bar":2}`), Status: pgtype.Present}},
+ Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}},
+ Status: pgtype.Present,
+ }},
+ {source: []json.RawMessage{json.RawMessage(`{"foo":1}`), json.RawMessage(`{"bar":2}`)}, result: pgtype.JSONArray{
+ Elements: []pgtype.JSON{{Bytes: []byte(`{"foo":1}`), Status: pgtype.Present}, {Bytes: []byte(`{"bar":2}`), Status: pgtype.Present}},
+ Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}},
+ Status: pgtype.Present,
+ }},
+ {source: []json.RawMessage{json.RawMessage(`{"foo":12}`), json.RawMessage(`{"bar":2}`)}, result: pgtype.JSONArray{
+ Elements: []pgtype.JSON{{Bytes: []byte(`{"foo":12}`), Status: pgtype.Present}, {Bytes: []byte(`{"bar":2}`), Status: pgtype.Present}},
+ Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}},
+ Status: pgtype.Present,
+ }},
+ {source: []json.RawMessage{json.RawMessage(`{"foo":1}`), json.RawMessage(`{"bar":{"x":2}}`)}, result: pgtype.JSONArray{
+ Elements: []pgtype.JSON{{Bytes: []byte(`{"foo":1}`), Status: pgtype.Present}, {Bytes: []byte(`{"bar":{"x":2}}`), Status: pgtype.Present}},
+ Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}},
+ Status: pgtype.Present,
+ }},
+ }
+
+ for i, tt := range successfulTests {
+ var d pgtype.JSONArray
+ err := d.Set(tt.source)
+ if err != nil {
+ t.Errorf("%d: %v", i, err)
+ }
+
+ if !reflect.DeepEqual(d, tt.result) {
+ t.Errorf("%d: expected %+v to convert to %+v, but it was %+v", i, tt.source, tt.result, d)
+ }
+ }
+}
diff --git a/jsonb_array.go b/jsonb_array.go
index c4b7cd3..e78ad37 100644
--- a/jsonb_array.go
+++ b/jsonb_array.go
@@ -5,6 +5,7 @@ package pgtype
import (
"database/sql/driver"
"encoding/binary"
+ "encoding/json"
"fmt"
"reflect"
@@ -72,6 +73,25 @@ func (dst *JSONBArray) Set(src interface{}) error {
}
}
+ case []json.RawMessage:
+ if value == nil {
+ *dst = JSONBArray{Status: Null}
+ } else if len(value) == 0 {
+ *dst = JSONBArray{Status: Present}
+ } else {
+ elements := make([]JSONB, len(value))
+ for i := range value {
+ if err := elements[i].Set(value[i]); err != nil {
+ return err
+ }
+ }
+ *dst = JSONBArray{
+ Elements: elements,
+ Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
+ Status: Present,
+ }
+ }
+
case []JSONB:
if value == nil {
*dst = JSONBArray{Status: Null}
@@ -214,6 +234,15 @@ func (src *JSONBArray) AssignTo(dst interface{}) error {
}
return nil
+ case *[]json.RawMessage:
+ *v = make([]json.RawMessage, len(src.Elements))
+ for i := range src.Elements {
+ if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
+ return err
+ }
+ }
+ return nil
+
}
}
diff --git a/jsonb_array_test.go b/jsonb_array_test.go
index 65f1777..e63d0c0 100644
--- a/jsonb_array_test.go
+++ b/jsonb_array_test.go
@@ -1,6 +1,8 @@
package pgtype_test
import (
+ "encoding/json"
+ "reflect"
"testing"
"github.com/jackc/pgtype"
@@ -34,3 +36,53 @@ func TestJSONBArrayTranscode(t *testing.T) {
},
})
}
+
+func TestJSONBArraySet(t *testing.T) {
+ successfulTests := []struct {
+ source interface{}
+ result pgtype.JSONBArray
+ }{
+ {source: []string{"{}"}, result: pgtype.JSONBArray{
+ Elements: []pgtype.JSONB{pgtype.JSONB{Bytes: []byte("{}"), Status: pgtype.Present}},
+ Dimensions: []pgtype.ArrayDimension{pgtype.ArrayDimension{Length: 1, LowerBound: 1}},
+ Status: pgtype.Present,
+ }},
+ {source: [][]byte{[]byte("{}")}, result: pgtype.JSONBArray{
+ Elements: []pgtype.JSONB{pgtype.JSONB{Bytes: []byte("{}"), Status: pgtype.Present}},
+ Dimensions: []pgtype.ArrayDimension{pgtype.ArrayDimension{Length: 1, LowerBound: 1}},
+ Status: pgtype.Present,
+ }},
+ {source: [][]byte{[]byte(`{"foo":1}`), []byte(`{"bar":2}`)}, result: pgtype.JSONBArray{
+ Elements: []pgtype.JSONB{pgtype.JSONB{Bytes: []byte(`{"foo":1}`), Status: pgtype.Present}, pgtype.JSONB{Bytes: []byte(`{"bar":2}`), Status: pgtype.Present}},
+ Dimensions: []pgtype.ArrayDimension{pgtype.ArrayDimension{Length: 2, LowerBound: 1}},
+ Status: pgtype.Present,
+ }},
+ {source: []json.RawMessage{json.RawMessage(`{"foo":1}`), json.RawMessage(`{"bar":2}`)}, result: pgtype.JSONBArray{
+ Elements: []pgtype.JSONB{pgtype.JSONB{Bytes: []byte(`{"foo":1}`), Status: pgtype.Present}, pgtype.JSONB{Bytes: []byte(`{"bar":2}`), Status: pgtype.Present}},
+ Dimensions: []pgtype.ArrayDimension{pgtype.ArrayDimension{Length: 2, LowerBound: 1}},
+ Status: pgtype.Present,
+ }},
+ {source: []json.RawMessage{json.RawMessage(`{"foo":12}`), json.RawMessage(`{"bar":2}`)}, result: pgtype.JSONBArray{
+ Elements: []pgtype.JSONB{pgtype.JSONB{Bytes: []byte(`{"foo":12}`), Status: pgtype.Present}, pgtype.JSONB{Bytes: []byte(`{"bar":2}`), Status: pgtype.Present}},
+ Dimensions: []pgtype.ArrayDimension{pgtype.ArrayDimension{Length: 2, LowerBound: 1}},
+ Status: pgtype.Present,
+ }},
+ {source: []json.RawMessage{json.RawMessage(`{"foo":1}`), json.RawMessage(`{"bar":{"x":2}}`)}, result: pgtype.JSONBArray{
+ Elements: []pgtype.JSONB{pgtype.JSONB{Bytes: []byte(`{"foo":1}`), Status: pgtype.Present}, pgtype.JSONB{Bytes: []byte(`{"bar":{"x":2}}`), Status: pgtype.Present}},
+ Dimensions: []pgtype.ArrayDimension{pgtype.ArrayDimension{Length: 2, LowerBound: 1}},
+ Status: pgtype.Present,
+ }},
+ }
+
+ for i, tt := range successfulTests {
+ var d pgtype.JSONBArray
+ err := d.Set(tt.source)
+ if err != nil {
+ t.Errorf("%d: %v", i, err)
+ }
+
+ if !reflect.DeepEqual(d, tt.result) {
+ t.Errorf("%d: expected %+v to convert to %+v, but it was %+v", i, tt.source, tt.result, d)
+ }
+ }
+}
diff --git a/multirange.go b/multirange.go
new file mode 100644
index 0000000..beb11f7
--- /dev/null
+++ b/multirange.go
@@ -0,0 +1,83 @@
+package pgtype
+
+import (
+ "bytes"
+ "fmt"
+)
+
+type UntypedTextMultirange struct {
+ Elements []string
+}
+
+func ParseUntypedTextMultirange(src string) (*UntypedTextMultirange, error) {
+ utmr := &UntypedTextMultirange{}
+ utmr.Elements = make([]string, 0)
+
+ buf := bytes.NewBufferString(src)
+
+ skipWhitespace(buf)
+
+ r, _, err := buf.ReadRune()
+ if err != nil {
+ return nil, fmt.Errorf("invalid array: %v", err)
+ }
+
+ if r != '{' {
+ return nil, fmt.Errorf("invalid multirange, expected '{': %v", err)
+ }
+
+parseValueLoop:
+ for {
+ r, _, err = buf.ReadRune()
+ if err != nil {
+ return nil, fmt.Errorf("invalid multirange: %v", err)
+ }
+
+ switch r {
+ case ',': // skip range separator
+ case '}':
+ break parseValueLoop
+ default:
+ buf.UnreadRune()
+ value, err := parseRange(buf)
+ if err != nil {
+ return nil, fmt.Errorf("invalid multirange value: %v", err)
+ }
+ utmr.Elements = append(utmr.Elements, value)
+ }
+ }
+
+ skipWhitespace(buf)
+
+ if buf.Len() > 0 {
+ return nil, fmt.Errorf("unexpected trailing data: %v", buf.String())
+ }
+
+ return utmr, nil
+
+}
+
+func parseRange(buf *bytes.Buffer) (string, error) {
+
+ s := &bytes.Buffer{}
+
+ boundSepRead := false
+ for {
+ r, _, err := buf.ReadRune()
+ if err != nil {
+ return "", err
+ }
+
+ switch r {
+ case ',', '}':
+ if r == ',' && !boundSepRead {
+ boundSepRead = true
+ break
+ }
+ buf.UnreadRune()
+ return s.String(), nil
+ }
+
+ s.WriteRune(r)
+ }
+}
diff --git a/multirange_test.go b/multirange_test.go
new file mode 100644
index 0000000..4991aec
--- /dev/null
+++ b/multirange_test.go
@@ -0,0 +1,51 @@
+package pgtype
+
+import (
+ "reflect"
+ "testing"
+)
+
+func TestParseUntypedTextMultirange(t *testing.T) {
+ tests := []struct {
+ src string
+ result UntypedTextMultirange
+ err error
+ }{
+ {
+ src: `{[1,2)}`,
+ result: UntypedTextMultirange{Elements: []string{`[1,2)`}},
+ err: nil,
+ },
+ {
+ src: `{[,),["foo", "bar"]}`,
+ result: UntypedTextMultirange{Elements: []string{`[,)`, `["foo", "bar"]`}},
+ err: nil,
+ },
+ {
+ src: `{}`,
+ result: UntypedTextMultirange{Elements: []string{}},
+ err: nil,
+ },
+ {
+ src: ` { (,) , [1,2] } `,
+ result: UntypedTextMultirange{Elements: []string{` (,) `, ` [1,2] `}},
+ err: nil,
+ },
+ {
+ src: `{["f""oo","b""ar")}`,
+ result: UntypedTextMultirange{Elements: []string{`["f""oo","b""ar")`}},
+ err: nil,
+ },
+ }
+ for i, tt := range tests {
+ r, err := ParseUntypedTextMultirange(tt.src)
+ if err != tt.err {
+ t.Errorf("%d. `%v`: expected err %v, got %v", i, tt.src, tt.err, err)
+ continue
+ }
+
+ if !reflect.DeepEqual(*r, tt.result) {
+ t.Errorf("%d: expected %+v to be parsed to %+v, but it was %+v", i, tt.src, tt.result, *r)
+ }
+ }
+}
diff --git a/num_multirange.go b/num_multirange.go
new file mode 100644
index 0000000..cbabc8a
--- /dev/null
+++ b/num_multirange.go
@@ -0,0 +1,239 @@
+package pgtype
+
+import (
+ "database/sql/driver"
+ "encoding/binary"
+ "fmt"
+
+ "github.com/jackc/pgio"
+)
+
+type Nummultirange struct {
+ Ranges []Numrange
+ Status Status
+}
+
+func (dst *Nummultirange) Set(src interface{}) error {
+ //untyped nil and typed nil interfaces are different
+ if src == nil {
+ *dst = Nummultirange{Status: Null}
+ return nil
+ }
+
+ switch value := src.(type) {
+ case Nummultirange:
+ *dst = value
+ case *Nummultirange:
+ *dst = *value
+ case string:
+ return dst.DecodeText(nil, []byte(value))
+ case []Numrange:
+ if value == nil {
+ *dst = Nummultirange{Status: Null}
+ } else if len(value) == 0 {
+ *dst = Nummultirange{Status: Present}
+ } else {
+ elements := make([]Numrange, len(value))
+ for i := range value {
+ if err := elements[i].Set(value[i]); err != nil {
+ return err
+ }
+ }
+ *dst = Nummultirange{
+ Ranges: elements,
+ Status: Present,
+ }
+ }
+ case []*Numrange:
+ if value == nil {
+ *dst = Nummultirange{Status: Null}
+ } else if len(value) == 0 {
+ *dst = Nummultirange{Status: Present}
+ } else {
+ elements := make([]Numrange, len(value))
+ for i := range value {
+ if err := elements[i].Set(value[i]); err != nil {
+ return err
+ }
+ }
+ *dst = Nummultirange{
+ Ranges: elements,
+ Status: Present,
+ }
+ }
+ default:
+ return fmt.Errorf("cannot convert %v to Nummultirange", src)
+ }
+
+ return nil
+
+}
+
+func (dst Nummultirange) Get() interface{} {
+ switch dst.Status {
+ case Present:
+ return dst
+ case Null:
+ return nil
+ default:
+ return dst.Status
+ }
+}
+
+func (src *Nummultirange) AssignTo(dst interface{}) error {
+ return fmt.Errorf("cannot assign %v to %T", src, dst)
+}
+
+func (dst *Nummultirange) DecodeText(ci *ConnInfo, src []byte) error {
+ if src == nil {
+ *dst = Nummultirange{Status: Null}
+ return nil
+ }
+
+ utmr, err := ParseUntypedTextMultirange(string(src))
+ if err != nil {
+ return err
+ }
+
+ var elements []Numrange
+
+ if len(utmr.Elements) > 0 {
+ elements = make([]Numrange, len(utmr.Elements))
+
+ for i, s := range utmr.Elements {
+ var elem Numrange
+
+ elemSrc := []byte(s)
+
+ err = elem.DecodeText(ci, elemSrc)
+ if err != nil {
+ return err
+ }
+
+ elements[i] = elem
+ }
+ }
+
+ *dst = Nummultirange{Ranges: elements, Status: Present}
+
+ return nil
+}
+
+func (dst *Nummultirange) DecodeBinary(ci *ConnInfo, src []byte) error {
+ if src == nil {
+ *dst = Nummultirange{Status: Null}
+ return nil
+ }
+
+ rp := 0
+
+ numElems := int(binary.BigEndian.Uint32(src[rp:]))
+ rp += 4
+
+ if numElems == 0 {
+ *dst = Nummultirange{Status: Present}
+ return nil
+ }
+
+ elements := make([]Numrange, numElems)
+
+ for i := range elements {
+ elemLen := int(int32(binary.BigEndian.Uint32(src[rp:])))
+ rp += 4
+ var elemSrc []byte
+ if elemLen >= 0 {
+ elemSrc = src[rp : rp+elemLen]
+ rp += elemLen
+ }
+ err := elements[i].DecodeBinary(ci, elemSrc)
+ if err != nil {
+ return err
+ }
+ }
+
+ *dst = Nummultirange{Ranges: elements, Status: Present}
+ return nil
+}
+
+func (src Nummultirange) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
+ switch src.Status {
+ case Null:
+ return nil, nil
+ case Undefined:
+ return nil, errUndefined
+ }
+
+ buf = append(buf, '{')
+
+ inElemBuf := make([]byte, 0, 32)
+ for i, elem := range src.Ranges {
+ if i > 0 {
+ buf = append(buf, ',')
+ }
+
+ elemBuf, err := elem.EncodeText(ci, inElemBuf)
+ if err != nil {
+ return nil, err
+ }
+ if elemBuf == nil {
+ return nil, fmt.Errorf("multi-range does not allow null range")
+ } else {
+ buf = append(buf, string(elemBuf)...)
+ }
+
+ }
+
+ buf = append(buf, '}')
+
+ return buf, nil
+}
+
+func (src Nummultirange) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
+ switch src.Status {
+ case Null:
+ return nil, nil
+ case Undefined:
+ return nil, errUndefined
+ }
+
+ buf = pgio.AppendInt32(buf, int32(len(src.Ranges)))
+
+ for i := range src.Ranges {
+ sp := len(buf)
+ buf = pgio.AppendInt32(buf, -1)
+
+ elemBuf, err := src.Ranges[i].EncodeBinary(ci, buf)
+ if err != nil {
+ return nil, err
+ }
+ if elemBuf != nil {
+ buf = elemBuf
+ pgio.SetInt32(buf[sp:], int32(len(buf[sp:])-4))
+ }
+ }
+
+ return buf, nil
+}
+
+// Scan implements the database/sql Scanner interface.
+func (dst *Nummultirange) Scan(src interface{}) error {
+ if src == nil {
+ return dst.DecodeText(nil, nil)
+ }
+
+ switch src := src.(type) {
+ case string:
+ return dst.DecodeText(nil, []byte(src))
+ case []byte:
+ srcCopy := make([]byte, len(src))
+ copy(srcCopy, src)
+ return dst.DecodeText(nil, srcCopy)
+ }
+
+ return fmt.Errorf("cannot scan %T", src)
+}
+
+// Value implements the database/sql/driver Valuer interface.
+func (src Nummultirange) Value() (driver.Value, error) {
+ return EncodeValueText(src)
+}
diff --git a/num_multirange_test.go b/num_multirange_test.go
new file mode 100644
index 0000000..f428979
--- /dev/null
+++ b/num_multirange_test.go
@@ -0,0 +1,55 @@
+package pgtype_test
+
+import (
+ "math/big"
+ "testing"
+
+ "github.com/jackc/pgtype"
+ "github.com/jackc/pgtype/testutil"
+)
+
+func TestNumericMultirangeTranscode(t *testing.T) {
+ testutil.TestSuccessfulTranscode(t, "nummultirange", []interface{}{
+ &pgtype.Nummultirange{
+ Ranges: nil,
+ Status: pgtype.Present,
+ },
+ &pgtype.Nummultirange{
+ Ranges: []pgtype.Numrange{
+ {
+ Lower: pgtype.Numeric{Int: big.NewInt(-543), Exp: 3, Status: pgtype.Present},
+ Upper: pgtype.Numeric{Int: big.NewInt(342), Exp: 1, Status: pgtype.Present},
+ LowerType: pgtype.Inclusive,
+ UpperType: pgtype.Exclusive,
+ Status: pgtype.Present,
+ },
+ },
+ Status: pgtype.Present,
+ },
+ &pgtype.Nummultirange{
+ Ranges: []pgtype.Numrange{
+ {
+ Lower: pgtype.Numeric{Int: big.NewInt(-42), Exp: 1, Status: pgtype.Present},
+ Upper: pgtype.Numeric{Int: big.NewInt(-5), Exp: 0, Status: pgtype.Present},
+ LowerType: pgtype.Inclusive,
+ UpperType: pgtype.Exclusive,
+ Status: pgtype.Present,
+ },
+ {
+ Lower: pgtype.Numeric{Int: big.NewInt(5), Exp: 1, Status: pgtype.Present},
+ Upper: pgtype.Numeric{Int: big.NewInt(42), Exp: 1, Status: pgtype.Present},
+ LowerType: pgtype.Inclusive,
+ UpperType: pgtype.Inclusive,
+ Status: pgtype.Present,
+ },
+ {
+ Lower: pgtype.Numeric{Int: big.NewInt(42), Exp: 2, Status: pgtype.Present},
+ LowerType: pgtype.Exclusive,
+ UpperType: pgtype.Unbounded,
+ Status: pgtype.Present,
+ },
+ },
+ Status: pgtype.Present,
+ },
+ })
+}
diff --git a/numeric.go b/numeric.go
index cd05774..1f32b36 100644
--- a/numeric.go
+++ b/numeric.go
@@ -1,6 +1,7 @@
package pgtype
import (
+ "bytes"
"database/sql/driver"
"encoding/binary"
"fmt"
@@ -375,6 +376,12 @@ func (src *Numeric) AssignTo(dst interface{}) error {
return err
}
v.Set(rat)
+ case *string:
+ buf, err := encodeNumericText(*src, nil)
+ if err != nil {
+ return err
+ }
+ *v = string(buf)
default:
if nextDst, retry := GetAssignToDstType(dst); retry {
return src.AssignTo(nextDst)
@@ -792,3 +799,55 @@ func (src Numeric) Value() (driver.Value, error) {
return nil, errUndefined
}
}
+
+func encodeNumericText(n Numeric, buf []byte) (newBuf []byte, err error) {
+ // if !n.Valid {
+ // return nil, nil
+ // }
+
+ if n.NaN {
+ buf = append(buf, "NaN"...)
+ return buf, nil
+ } else if n.InfinityModifier == Infinity {
+ buf = append(buf, "Infinity"...)
+ return buf, nil
+ } else if n.InfinityModifier == NegativeInfinity {
+ buf = append(buf, "-Infinity"...)
+ return buf, nil
+ }
+
+ buf = append(buf, n.numberTextBytes()...)
+
+ return buf, nil
+}
+
+// numberString returns a string of the number. undefined if NaN, infinite, or NULL
+func (n Numeric) numberTextBytes() []byte {
+ intStr := n.Int.String()
+ buf := &bytes.Buffer{}
+ exp := int(n.Exp)
+ if exp > 0 {
+ buf.WriteString(intStr)
+ for i := 0; i < exp; i++ {
+ buf.WriteByte('0')
+ }
+ } else if exp < 0 {
+ if len(intStr) <= -exp {
+ buf.WriteString("0.")
+ leadingZeros := -exp - len(intStr)
+ for i := 0; i < leadingZeros; i++ {
+ buf.WriteByte('0')
+ }
+ buf.WriteString(intStr)
+ } else if len(intStr) > -exp {
+ dpPos := len(intStr) + exp
+ buf.WriteString(intStr[:dpPos])
+ buf.WriteByte('.')
+ buf.WriteString(intStr[dpPos:])
+ }
+ } else {
+ buf.WriteString(intStr)
+ }
+
+ return buf.Bytes()
+}
diff --git a/pgtype.go b/pgtype.go
index 200fb56..addfc66 100644
--- a/pgtype.go
+++ b/pgtype.go
@@ -26,6 +26,7 @@ const (
XIDOID = 28
CIDOID = 29
JSONOID = 114
+ JSONArrayOID = 199
PointOID = 600
LsegOID = 601
PathOID = 602
@@ -74,12 +75,15 @@ const (
JSONBArrayOID = 3807
DaterangeOID = 3912
Int4rangeOID = 3904
+ Int4multirangeOID = 4451
NumrangeOID = 3906
+ NummultirangeOID = 4532
TsrangeOID = 3908
TsrangeArrayOID = 3909
TstzrangeOID = 3910
TstzrangeArrayOID = 3911
Int8rangeOID = 3926
+ Int8multirangeOID = 4536
)
type Status byte
@@ -288,10 +292,13 @@ func NewConnInfo() *ConnInfo {
ci.RegisterDataType(DataType{Value: &Int2{}, Name: "int2", OID: Int2OID})
ci.RegisterDataType(DataType{Value: &Int4{}, Name: "int4", OID: Int4OID})
ci.RegisterDataType(DataType{Value: &Int4range{}, Name: "int4range", OID: Int4rangeOID})
+ ci.RegisterDataType(DataType{Value: &Int4multirange{}, Name: "int4multirange", OID: Int4multirangeOID})
ci.RegisterDataType(DataType{Value: &Int8{}, Name: "int8", OID: Int8OID})
ci.RegisterDataType(DataType{Value: &Int8range{}, Name: "int8range", OID: Int8rangeOID})
+ ci.RegisterDataType(DataType{Value: &Int8multirange{}, Name: "int8multirange", OID: Int8multirangeOID})
ci.RegisterDataType(DataType{Value: &Interval{}, Name: "interval", OID: IntervalOID})
ci.RegisterDataType(DataType{Value: &JSON{}, Name: "json", OID: JSONOID})
+ ci.RegisterDataType(DataType{Value: &JSONArray{}, Name: "_json", OID: JSONArrayOID})
ci.RegisterDataType(DataType{Value: &JSONB{}, Name: "jsonb", OID: JSONBOID})
ci.RegisterDataType(DataType{Value: &JSONBArray{}, Name: "_jsonb", OID: JSONBArrayOID})
ci.RegisterDataType(DataType{Value: &Line{}, Name: "line", OID: LineOID})
@@ -300,6 +307,7 @@ func NewConnInfo() *ConnInfo {
ci.RegisterDataType(DataType{Value: &Name{}, Name: "name", OID: NameOID})
ci.RegisterDataType(DataType{Value: &Numeric{}, Name: "numeric", OID: NumericOID})
ci.RegisterDataType(DataType{Value: &Numrange{}, Name: "numrange", OID: NumrangeOID})
+ ci.RegisterDataType(DataType{Value: &Nummultirange{}, Name: "nummultirange", OID: NummultirangeOID})
ci.RegisterDataType(DataType{Value: &OIDValue{}, Name: "oid", OID: OIDOID})
ci.RegisterDataType(DataType{Value: &Path{}, Name: "path", OID: PathOID})
ci.RegisterDataType(DataType{Value: &Point{}, Name: "point", OID: PointOID})
@@ -527,8 +535,22 @@ type scanPlanDataTypeSQLScanner DataType
func (plan *scanPlanDataTypeSQLScanner) Scan(ci *ConnInfo, oid uint32, formatCode int16, src []byte, dst interface{}) error {
scanner, ok := dst.(sql.Scanner)
if !ok {
- newPlan := ci.PlanScan(oid, formatCode, dst)
- return newPlan.Scan(ci, oid, formatCode, src, dst)
+ dv := reflect.ValueOf(dst)
+ if dv.Kind() != reflect.Ptr || !dv.Type().Elem().Implements(scannerType) {
+ newPlan := ci.PlanScan(oid, formatCode, dst)
+ return newPlan.Scan(ci, oid, formatCode, src, dst)
+ }
+ if src == nil {
+ // Ensure the pointer points to a zero version of the value
+ dv.Elem().Set(reflect.Zero(dv.Type().Elem()))
+ return nil
+ }
+ dv = dv.Elem()
+ // If the pointer is to a nil pointer then set that before scanning
+ if dv.Kind() == reflect.Ptr && dv.IsNil() {
+ dv.Set(reflect.New(dv.Type().Elem()))
+ }
+ scanner = dv.Interface().(sql.Scanner)
}
dt := (*DataType)(plan)
@@ -587,7 +609,25 @@ func (plan *scanPlanDataTypeAssignTo) Scan(ci *ConnInfo, oid uint32, formatCode
type scanPlanSQLScanner struct{}
func (scanPlanSQLScanner) Scan(ci *ConnInfo, oid uint32, formatCode int16, src []byte, dst interface{}) error {
- scanner := dst.(sql.Scanner)
+ scanner, ok := dst.(sql.Scanner)
+ if !ok {
+ dv := reflect.ValueOf(dst)
+ if dv.Kind() != reflect.Ptr || !dv.Type().Elem().Implements(scannerType) {
+ newPlan := ci.PlanScan(oid, formatCode, dst)
+ return newPlan.Scan(ci, oid, formatCode, src, dst)
+ }
+ if src == nil {
+ // Ensure the pointer points to a zero version of the value
+ dv.Elem().Set(reflect.Zero(dv.Type()))
+ return nil
+ }
+ dv = dv.Elem()
+ // If the pointer is to a nil pointer then set that before scanning
+ if dv.Kind() == reflect.Ptr && dv.IsNil() {
+ dv.Set(reflect.New(dv.Type().Elem()))
+ }
+ scanner = dv.Interface().(sql.Scanner)
+ }
if src == nil {
// This is necessary because interface value []byte:nil does not equal nil:nil for the binary format path and the
// text format path would be converted to empty string.
@@ -755,6 +795,18 @@ func (scanPlanString) Scan(ci *ConnInfo, oid uint32, formatCode int16, src []byt
return newPlan.Scan(ci, oid, formatCode, src, dst)
}
+var scannerType = reflect.TypeOf((*sql.Scanner)(nil)).Elem()
+
+func isScanner(dst interface{}) bool {
+ if _, ok := dst.(sql.Scanner); ok {
+ return true
+ }
+ if t := reflect.TypeOf(dst); t != nil && t.Kind() == reflect.Ptr && t.Elem().Implements(scannerType) {
+ return true
+ }
+ return false
+}
+
// PlanScan prepares a plan to scan a value into dst.
func (ci *ConnInfo) PlanScan(oid uint32, formatCode int16, dst interface{}) ScanPlan {
switch formatCode {
@@ -819,13 +871,13 @@ func (ci *ConnInfo) PlanScan(oid uint32, formatCode int16, dst interface{}) Scan
}
if dt != nil {
- if _, ok := dst.(sql.Scanner); ok {
+ if isScanner(dst) {
return (*scanPlanDataTypeSQLScanner)(dt)
}
return (*scanPlanDataTypeAssignTo)(dt)
}
- if _, ok := dst.(sql.Scanner); ok {
+ if isScanner(dst) {
return scanPlanSQLScanner{}
}
@@ -873,72 +925,76 @@ var nameValues map[string]Value
func init() {
nameValues = map[string]Value{
- "_aclitem": &ACLItemArray{},
- "_bool": &BoolArray{},
- "_bpchar": &BPCharArray{},
- "_bytea": &ByteaArray{},
- "_cidr": &CIDRArray{},
- "_date": &DateArray{},
- "_float4": &Float4Array{},
- "_float8": &Float8Array{},
- "_inet": &InetArray{},
- "_int2": &Int2Array{},
- "_int4": &Int4Array{},
- "_int8": &Int8Array{},
- "_numeric": &NumericArray{},
- "_text": &TextArray{},
- "_timestamp": &TimestampArray{},
- "_timestamptz": &TimestamptzArray{},
- "_uuid": &UUIDArray{},
- "_varchar": &VarcharArray{},
- "_jsonb": &JSONBArray{},
- "aclitem": &ACLItem{},
- "bit": &Bit{},
- "bool": &Bool{},
- "box": &Box{},
- "bpchar": &BPChar{},
- "bytea": &Bytea{},
- "char": &QChar{},
- "cid": &CID{},
- "cidr": &CIDR{},
- "circle": &Circle{},
- "date": &Date{},
- "daterange": &Daterange{},
- "float4": &Float4{},
- "float8": &Float8{},
- "hstore": &Hstore{},
- "inet": &Inet{},
- "int2": &Int2{},
- "int4": &Int4{},
- "int4range": &Int4range{},
- "int8": &Int8{},
- "int8range": &Int8range{},
- "interval": &Interval{},
- "json": &JSON{},
- "jsonb": &JSONB{},
- "line": &Line{},
- "lseg": &Lseg{},
- "macaddr": &Macaddr{},
- "name": &Name{},
- "numeric": &Numeric{},
- "numrange": &Numrange{},
- "oid": &OIDValue{},
- "path": &Path{},
- "point": &Point{},
- "polygon": &Polygon{},
- "record": &Record{},
- "text": &Text{},
- "tid": &TID{},
- "timestamp": &Timestamp{},
- "timestamptz": &Timestamptz{},
- "tsrange": &Tsrange{},
- "_tsrange": &TsrangeArray{},
- "tstzrange": &Tstzrange{},
- "_tstzrange": &TstzrangeArray{},
- "unknown": &Unknown{},
- "uuid": &UUID{},
- "varbit": &Varbit{},
- "varchar": &Varchar{},
- "xid": &XID{},
+ "_aclitem": &ACLItemArray{},
+ "_bool": &BoolArray{},
+ "_bpchar": &BPCharArray{},
+ "_bytea": &ByteaArray{},
+ "_cidr": &CIDRArray{},
+ "_date": &DateArray{},
+ "_float4": &Float4Array{},
+ "_float8": &Float8Array{},
+ "_inet": &InetArray{},
+ "_int2": &Int2Array{},
+ "_int4": &Int4Array{},
+ "_int8": &Int8Array{},
+ "_numeric": &NumericArray{},
+ "_text": &TextArray{},
+ "_timestamp": &TimestampArray{},
+ "_timestamptz": &TimestamptzArray{},
+ "_uuid": &UUIDArray{},
+ "_varchar": &VarcharArray{},
+ "_json": &JSONArray{},
+ "_jsonb": &JSONBArray{},
+ "aclitem": &ACLItem{},
+ "bit": &Bit{},
+ "bool": &Bool{},
+ "box": &Box{},
+ "bpchar": &BPChar{},
+ "bytea": &Bytea{},
+ "char": &QChar{},
+ "cid": &CID{},
+ "cidr": &CIDR{},
+ "circle": &Circle{},
+ "date": &Date{},
+ "daterange": &Daterange{},
+ "float4": &Float4{},
+ "float8": &Float8{},
+ "hstore": &Hstore{},
+ "inet": &Inet{},
+ "int2": &Int2{},
+ "int4": &Int4{},
+ "int4range": &Int4range{},
+ "int4multirange": &Int4multirange{},
+ "int8": &Int8{},
+ "int8range": &Int8range{},
+ "int8multirange": &Int8multirange{},
+ "interval": &Interval{},
+ "json": &JSON{},
+ "jsonb": &JSONB{},
+ "line": &Line{},
+ "lseg": &Lseg{},
+ "macaddr": &Macaddr{},
+ "name": &Name{},
+ "numeric": &Numeric{},
+ "numrange": &Numrange{},
+ "nummultirange": &Nummultirange{},
+ "oid": &OIDValue{},
+ "path": &Path{},
+ "point": &Point{},
+ "polygon": &Polygon{},
+ "record": &Record{},
+ "text": &Text{},
+ "tid": &TID{},
+ "timestamp": &Timestamp{},
+ "timestamptz": &Timestamptz{},
+ "tsrange": &Tsrange{},
+ "_tsrange": &TsrangeArray{},
+ "tstzrange": &Tstzrange{},
+ "_tstzrange": &TstzrangeArray{},
+ "unknown": &Unknown{},
+ "uuid": &UUID{},
+ "varbit": &Varbit{},
+ "varchar": &Varchar{},
+ "xid": &XID{},
}
}
diff --git a/pgtype_test.go b/pgtype_test.go
index 85ca55e..67f3637 100644
--- a/pgtype_test.go
+++ b/pgtype_test.go
@@ -310,3 +310,54 @@ func BenchmarkScanPlanScanInt4IntoGoInt32(b *testing.B) {
}
}
}
+
+type pgCustomInt int64
+
+func (ci *pgCustomInt) Scan(src interface{}) error {
+ *ci = pgCustomInt(src.(int64))
+ return nil
+}
+
+func TestScanPlanBinaryInt32ScanScanner(t *testing.T) {
+ ci := pgtype.NewConnInfo()
+ src := []byte{0, 42}
+ var v pgCustomInt
+
+ plan := ci.PlanScan(pgtype.Int2OID, pgtype.BinaryFormatCode, &v)
+ err := plan.Scan(ci, pgtype.Int2OID, pgtype.BinaryFormatCode, src, &v)
+ require.NoError(t, err)
+ require.EqualValues(t, 42, v)
+
+ ptr := new(pgCustomInt)
+ plan = ci.PlanScan(pgtype.Int2OID, pgtype.BinaryFormatCode, &ptr)
+ err = plan.Scan(ci, pgtype.Int2OID, pgtype.BinaryFormatCode, src, &ptr)
+ require.NoError(t, err)
+ require.EqualValues(t, 42, *ptr)
+
+ ptr = new(pgCustomInt)
+ err = plan.Scan(ci, pgtype.Int2OID, pgtype.BinaryFormatCode, nil, &ptr)
+ require.NoError(t, err)
+ assert.Nil(t, ptr)
+
+ ptr = nil
+ plan = ci.PlanScan(pgtype.Int2OID, pgtype.BinaryFormatCode, &ptr)
+ err = plan.Scan(ci, pgtype.Int2OID, pgtype.BinaryFormatCode, src, &ptr)
+ require.NoError(t, err)
+ require.EqualValues(t, 42, *ptr)
+
+ ptr = nil
+ plan = ci.PlanScan(pgtype.Int2OID, pgtype.BinaryFormatCode, &ptr)
+ err = plan.Scan(ci, pgtype.Int2OID, pgtype.BinaryFormatCode, nil, &ptr)
+ require.NoError(t, err)
+ assert.Nil(t, ptr)
+}
+
+// Test for https://github.com/jackc/pgtype/issues/164
+func TestScanPlanInterface(t *testing.T) {
+ ci := pgtype.NewConnInfo()
+ src := []byte{0, 42}
+ var v interface{}
+ plan := ci.PlanScan(pgtype.Int2OID, pgtype.BinaryFormatCode, v)
+ err := plan.Scan(ci, pgtype.Int2OID, pgtype.BinaryFormatCode, src, v)
+ assert.Error(t, err)
+}
diff --git a/record.go b/record.go
index 718c357..5cf2c93 100644
--- a/record.go
+++ b/record.go
@@ -6,7 +6,7 @@ import (
)
// Record is the generic PostgreSQL record type such as is created with the
-// "row" function. Record only implements BinaryEncoder and Value. The text
+// "row" function. Record only implements BinaryDecoder and Value. The text
// format output format from PostgreSQL does not include type information and is
// therefore impossible to decode. No encoders are implemented because
// PostgreSQL does not support input of generic records.
diff --git a/record_array.go b/record_array.go
new file mode 100644
index 0000000..2271717
--- /dev/null
+++ b/record_array.go
@@ -0,0 +1,318 @@
+// Code generated by erb. DO NOT EDIT.
+
+package pgtype
+
+import (
+ "encoding/binary"
+ "fmt"
+ "reflect"
+)
+
+type RecordArray struct {
+ Elements []Record
+ Dimensions []ArrayDimension
+ Status Status
+}
+
+func (dst *RecordArray) Set(src interface{}) error {
+ // untyped nil and typed nil interfaces are different
+ if src == nil {
+ *dst = RecordArray{Status: Null}
+ return nil
+ }
+
+ if value, ok := src.(interface{ Get() interface{} }); ok {
+ value2 := value.Get()
+ if value2 != value {
+ return dst.Set(value2)
+ }
+ }
+
+ // Attempt to match to select common types:
+ switch value := src.(type) {
+
+ case [][]Value:
+ if value == nil {
+ *dst = RecordArray{Status: Null}
+ } else if len(value) == 0 {
+ *dst = RecordArray{Status: Present}
+ } else {
+ elements := make([]Record, len(value))
+ for i := range value {
+ if err := elements[i].Set(value[i]); err != nil {
+ return err
+ }
+ }
+ *dst = RecordArray{
+ Elements: elements,
+ Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
+ Status: Present,
+ }
+ }
+
+ case []Record:
+ if value == nil {
+ *dst = RecordArray{Status: Null}
+ } else if len(value) == 0 {
+ *dst = RecordArray{Status: Present}
+ } else {
+ *dst = RecordArray{
+ Elements: value,
+ Dimensions: []ArrayDimension{{Length: int32(len(value)), LowerBound: 1}},
+ Status: Present,
+ }
+ }
+ default:
+ // Fallback to reflection if an optimised match was not found.
+ // The reflection is necessary for arrays and multidimensional slices,
+ // but it comes with a 20-50% performance penalty for large arrays/slices
+ reflectedValue := reflect.ValueOf(src)
+ if !reflectedValue.IsValid() || reflectedValue.IsZero() {
+ *dst = RecordArray{Status: Null}
+ return nil
+ }
+
+ dimensions, elementsLength, ok := findDimensionsFromValue(reflectedValue, nil, 0)
+ if !ok {
+ return fmt.Errorf("cannot find dimensions of %v for RecordArray", src)
+ }
+ if elementsLength == 0 {
+ *dst = RecordArray{Status: Present}
+ return nil
+ }
+ if len(dimensions) == 0 {
+ if originalSrc, ok := underlyingSliceType(src); ok {
+ return dst.Set(originalSrc)
+ }
+ return fmt.Errorf("cannot convert %v to RecordArray", src)
+ }
+
+ *dst = RecordArray{
+ Elements: make([]Record, elementsLength),
+ Dimensions: dimensions,
+ Status: Present,
+ }
+ elementCount, err := dst.setRecursive(reflectedValue, 0, 0)
+ if err != nil {
+ // Maybe the target was one dimension too far, try again:
+ if len(dst.Dimensions) > 1 {
+ dst.Dimensions = dst.Dimensions[:len(dst.Dimensions)-1]
+ elementsLength = 0
+ for _, dim := range dst.Dimensions {
+ if elementsLength == 0 {
+ elementsLength = int(dim.Length)
+ } else {
+ elementsLength *= int(dim.Length)
+ }
+ }
+ dst.Elements = make([]Record, elementsLength)
+ elementCount, err = dst.setRecursive(reflectedValue, 0, 0)
+ if err != nil {
+ return err
+ }
+ } else {
+ return err
+ }
+ }
+ if elementCount != len(dst.Elements) {
+ return fmt.Errorf("cannot convert %v to RecordArray, expected %d dst.Elements, but got %d instead", src, len(dst.Elements), elementCount)
+ }
+ }
+
+ return nil
+}
+
+func (dst *RecordArray) setRecursive(value reflect.Value, index, dimension int) (int, error) {
+ switch value.Kind() {
+ case reflect.Array:
+ fallthrough
+ case reflect.Slice:
+ if len(dst.Dimensions) == dimension {
+ break
+ }
+
+ valueLen := value.Len()
+ if int32(valueLen) != dst.Dimensions[dimension].Length {
+ return 0, fmt.Errorf("multidimensional arrays must have array expressions with matching dimensions")
+ }
+ for i := 0; i < valueLen; i++ {
+ var err error
+ index, err = dst.setRecursive(value.Index(i), index, dimension+1)
+ if err != nil {
+ return 0, err
+ }
+ }
+
+ return index, nil
+ }
+ if !value.CanInterface() {
+ return 0, fmt.Errorf("cannot convert all values to RecordArray")
+ }
+ if err := dst.Elements[index].Set(value.Interface()); err != nil {
+ return 0, fmt.Errorf("%v in RecordArray", err)
+ }
+ index++
+
+ return index, nil
+}
+
+func (dst RecordArray) Get() interface{} {
+ switch dst.Status {
+ case Present:
+ return dst
+ case Null:
+ return nil
+ default:
+ return dst.Status
+ }
+}
+
+func (src *RecordArray) AssignTo(dst interface{}) error {
+ switch src.Status {
+ case Present:
+ if len(src.Dimensions) <= 1 {
+ // Attempt to match to select common types:
+ switch v := dst.(type) {
+
+ case *[][]Value:
+ *v = make([][]Value, len(src.Elements))
+ for i := range src.Elements {
+ if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
+ return err
+ }
+ }
+ return nil
+
+ }
+ }
+
+ // Try to convert to something AssignTo can use directly.
+ if nextDst, retry := GetAssignToDstType(dst); retry {
+ return src.AssignTo(nextDst)
+ }
+
+ // Fallback to reflection if an optimised match was not found.
+ // The reflection is necessary for arrays and multidimensional slices,
+ // but it comes with a 20-50% performance penalty for large arrays/slices
+ value := reflect.ValueOf(dst)
+ if value.Kind() == reflect.Ptr {
+ value = value.Elem()
+ }
+
+ switch value.Kind() {
+ case reflect.Array, reflect.Slice:
+ default:
+ return fmt.Errorf("cannot assign %T to %T", src, dst)
+ }
+
+ if len(src.Elements) == 0 {
+ if value.Kind() == reflect.Slice {
+ value.Set(reflect.MakeSlice(value.Type(), 0, 0))
+ return nil
+ }
+ }
+
+ elementCount, err := src.assignToRecursive(value, 0, 0)
+ if err != nil {
+ return err
+ }
+ if elementCount != len(src.Elements) {
+ return fmt.Errorf("cannot assign %v, needed to assign %d elements, but only assigned %d", dst, len(src.Elements), elementCount)
+ }
+
+ return nil
+ case Null:
+ return NullAssignTo(dst)
+ }
+
+ return fmt.Errorf("cannot decode %#v into %T", src, dst)
+}
+
+func (src *RecordArray) assignToRecursive(value reflect.Value, index, dimension int) (int, error) {
+ switch kind := value.Kind(); kind {
+ case reflect.Array:
+ fallthrough
+ case reflect.Slice:
+ if len(src.Dimensions) == dimension {
+ break
+ }
+
+ length := int(src.Dimensions[dimension].Length)
+ if reflect.Array == kind {
+ typ := value.Type()
+ if typ.Len() != length {
+ return 0, fmt.Errorf("expected size %d array, but %s has size %d array", length, typ, typ.Len())
+ }
+ value.Set(reflect.New(typ).Elem())
+ } else {
+ value.Set(reflect.MakeSlice(value.Type(), length, length))
+ }
+
+ var err error
+ for i := 0; i < length; i++ {
+ index, err = src.assignToRecursive(value.Index(i), index, dimension+1)
+ if err != nil {
+ return 0, err
+ }
+ }
+
+ return index, nil
+ }
+ if len(src.Dimensions) != dimension {
+ return 0, fmt.Errorf("incorrect dimensions, expected %d, found %d", len(src.Dimensions), dimension)
+ }
+ if !value.CanAddr() {
+ return 0, fmt.Errorf("cannot assign all values from RecordArray")
+ }
+ addr := value.Addr()
+ if !addr.CanInterface() {
+ return 0, fmt.Errorf("cannot assign all values from RecordArray")
+ }
+ if err := src.Elements[index].AssignTo(addr.Interface()); err != nil {
+ return 0, err
+ }
+ index++
+ return index, nil
+}
+
+func (dst *RecordArray) DecodeBinary(ci *ConnInfo, src []byte) error {
+ if src == nil {
+ *dst = RecordArray{Status: Null}
+ return nil
+ }
+
+ var arrayHeader ArrayHeader
+ rp, err := arrayHeader.DecodeBinary(ci, src)
+ if err != nil {
+ return err
+ }
+
+ if len(arrayHeader.Dimensions) == 0 {
+ *dst = RecordArray{Dimensions: arrayHeader.Dimensions, Status: Present}
+ return nil
+ }
+
+ elementCount := arrayHeader.Dimensions[0].Length
+ for _, d := range arrayHeader.Dimensions[1:] {
+ elementCount *= d.Length
+ }
+
+ elements := make([]Record, elementCount)
+
+ for i := range elements {
+ elemLen := int(int32(binary.BigEndian.Uint32(src[rp:])))
+ rp += 4
+ var elemSrc []byte
+ if elemLen >= 0 {
+ elemSrc = src[rp : rp+elemLen]
+ rp += elemLen
+ }
+ err = elements[i].DecodeBinary(ci, elemSrc)
+ if err != nil {
+ return err
+ }
+ }
+
+ *dst = RecordArray{Elements: elements, Dimensions: arrayHeader.Dimensions, Status: Present}
+ return nil
+}
diff --git a/record_array_test.go b/record_array_test.go
new file mode 100644
index 0000000..9c92e33
--- /dev/null
+++ b/record_array_test.go
@@ -0,0 +1,104 @@
+package pgtype_test
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+
+ "github.com/jackc/pgtype"
+ "github.com/jackc/pgtype/testutil"
+ "github.com/jackc/pgx/v4"
+)
+
+var recordArrayTests = []struct {
+ sql string
+ expected pgtype.RecordArray
+}{
+ {
+ sql: `select array_agg((x::int4, x+100::int8)) from generate_series(0, 1) x;`,
+ expected: pgtype.RecordArray{
+ Dimensions: []pgtype.ArrayDimension{
+ {LowerBound: 1, Length: 2},
+ },
+ Elements: []pgtype.Record{
+ {
+ Fields: []pgtype.Value{
+ &pgtype.Int4{Int: 0, Status: pgtype.Present},
+ &pgtype.Int8{Int: 100, Status: pgtype.Present},
+ },
+ Status: pgtype.Present,
+ },
+ {
+ Fields: []pgtype.Value{
+ &pgtype.Int4{Int: 1, Status: pgtype.Present},
+ &pgtype.Int8{Int: 101, Status: pgtype.Present},
+ },
+ Status: pgtype.Present,
+ },
+ },
+ Status: pgtype.Present,
+ },
+ },
+}
+
+func TestRecordArrayTranscode(t *testing.T) {
+ conn := testutil.MustConnectPgx(t)
+ defer testutil.MustCloseContext(t, conn)
+
+ for i, tt := range recordArrayTests {
+ psName := fmt.Sprintf("test%d", i)
+ _, err := conn.Prepare(context.Background(), psName, tt.sql)
+ require.NoError(t, err)
+
+ t.Run(tt.sql, func(t *testing.T) {
+ var result pgtype.RecordArray
+ err := conn.QueryRow(context.Background(), psName, pgx.QueryResultFormats{pgx.BinaryFormatCode}).Scan(&result)
+ require.NoError(t, err)
+
+ require.Equal(t, tt.expected, result)
+ })
+
+ }
+}
+
+func TestRecordArrayAssignTo(t *testing.T) {
+ src := pgtype.RecordArray{
+ Dimensions: []pgtype.ArrayDimension{
+ {LowerBound: 1, Length: 2},
+ },
+ Elements: []pgtype.Record{
+ {
+ Fields: []pgtype.Value{
+ &pgtype.Int4{Int: 0, Status: pgtype.Present},
+ &pgtype.Int8{Int: 100, Status: pgtype.Present},
+ },
+ Status: pgtype.Present,
+ },
+ {
+ Fields: []pgtype.Value{
+ &pgtype.Int4{Int: 1, Status: pgtype.Present},
+ &pgtype.Int8{Int: 101, Status: pgtype.Present},
+ },
+ Status: pgtype.Present,
+ },
+ },
+ Status: pgtype.Present,
+ }
+ dst := [][]pgtype.Value{}
+ err := src.AssignTo(&dst)
+ require.NoError(t, err)
+
+ expected := [][]pgtype.Value{
+ {
+ &pgtype.Int4{Int: 0, Status: pgtype.Present},
+ &pgtype.Int8{Int: 100, Status: pgtype.Present},
+ },
+ {
+ &pgtype.Int4{Int: 1, Status: pgtype.Present},
+ &pgtype.Int8{Int: 101, Status: pgtype.Present},
+ },
+ }
+ require.Equal(t, expected, dst)
+}
diff --git a/testutil/testutil.go b/testutil/testutil.go
index e7b64b5..52bff70 100644
--- a/testutil/testutil.go
+++ b/testutil/testutil.go
@@ -141,7 +141,7 @@ func TestPgxSuccessfulTranscodeEqFunc(t testing.TB, pgTypeName string, values []
}
}
- // Derefence value if it is a pointer
+ // Dereference value if it is a pointer
derefV := v
refVal := reflect.ValueOf(v)
if refVal.Kind() == reflect.Ptr {
@@ -173,7 +173,7 @@ func TestDatabaseSQLSuccessfulTranscodeEqFunc(t testing.TB, driverName, pgTypeNa
}
for i, v := range values {
- // Derefence value if it is a pointer
+ // Dereference value if it is a pointer
derefV := v
refVal := reflect.ValueOf(v)
if refVal.Kind() == reflect.Ptr {
@@ -235,7 +235,7 @@ func TestPgxSuccessfulNormalizeEqFunc(t testing.TB, tests []NormalizeTest, eqFun
t.Logf("Skipping: %#v does not implement %v", tt.Value, fc.name)
continue
}
- // Derefence value if it is a pointer
+ // Dereference value if it is a pointer
derefV := tt.Value
refVal := reflect.ValueOf(tt.Value)
if refVal.Kind() == reflect.Ptr {
@@ -266,7 +266,7 @@ func TestDatabaseSQLSuccessfulNormalizeEqFunc(t testing.TB, driverName string, t
continue
}
- // Derefence value if it is a pointer
+ // Dereference value if it is a pointer
derefV := tt.Value
refVal := reflect.ValueOf(tt.Value)
if refVal.Kind() == reflect.Ptr {
@@ -367,7 +367,7 @@ func TestPgxNullToGoZeroConversion(t testing.TB, pgTypeName string, zero interfa
}
}
- // Derefence value if it is a pointer
+ // Dereference value if it is a pointer
derefZero := zero
refVal := reflect.ValueOf(zero)
if refVal.Kind() == reflect.Ptr {
@@ -416,7 +416,7 @@ func TestDatabaseSQLNullToGoZeroConversion(t testing.TB, driverName, pgTypeName
t.Fatal(err)
}
- // Derefence value if it is a pointer
+ // Dereference value if it is a pointer
derefZero := zero
refVal := reflect.ValueOf(zero)
if refVal.Kind() == reflect.Ptr {
diff --git a/timestamp.go b/timestamp.go
index 5517acb..e043726 100644
--- a/timestamp.go
+++ b/timestamp.go
@@ -46,6 +46,14 @@ func (dst *Timestamp) Set(src interface{}) error {
} else {
return dst.Set(*value)
}
+ case string:
+ return dst.DecodeText(nil, []byte(value))
+ case *string:
+ if value == nil {
+ *dst = Timestamp{Status: Null}
+ } else {
+ return dst.Set(*value)
+ }
case InfinityModifier:
*dst = Timestamp{InfinityModifier: value, Status: Present}
default:
diff --git a/timestamp_test.go b/timestamp_test.go
index ea7ef57..d818d4f 100644
--- a/timestamp_test.go
+++ b/timestamp_test.go
@@ -123,6 +123,7 @@ func TestTimestampSet(t *testing.T) {
{source: _time(time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)), result: pgtype.Timestamp{Time: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present}},
{source: pgtype.Infinity, result: pgtype.Timestamp{InfinityModifier: pgtype.Infinity, Status: pgtype.Present}},
{source: pgtype.NegativeInfinity, result: pgtype.Timestamp{InfinityModifier: pgtype.NegativeInfinity, Status: pgtype.Present}},
+ {source: "2001-04-05 06:07:08", result: pgtype.Timestamp{Time: time.Date(2001, 4, 5, 6, 7, 8, 0, time.UTC), Status: pgtype.Present}},
}
for i, tt := range successfulTests {
diff --git a/timestamptz.go b/timestamptz.go
index 5870197..72ae499 100644
--- a/timestamptz.go
+++ b/timestamptz.go
@@ -48,6 +48,14 @@ func (dst *Timestamptz) Set(src interface{}) error {
} else {
return dst.Set(*value)
}
+ case string:
+ return dst.DecodeText(nil, []byte(value))
+ case *string:
+ if value == nil {
+ *dst = Timestamptz{Status: Null}
+ } else {
+ return dst.Set(*value)
+ }
case InfinityModifier:
*dst = Timestamptz{InfinityModifier: value, Status: Present}
default:
diff --git a/timestamptz_test.go b/timestamptz_test.go
index 2ff326b..d6a3f51 100644
--- a/timestamptz_test.go
+++ b/timestamptz_test.go
@@ -120,6 +120,7 @@ func TestTimestamptzSet(t *testing.T) {
{source: _time(time.Date(1970, 1, 1, 0, 0, 0, 0, time.Local)), result: pgtype.Timestamptz{Time: time.Date(1970, 1, 1, 0, 0, 0, 0, time.Local), Status: pgtype.Present}},
{source: pgtype.Infinity, result: pgtype.Timestamptz{InfinityModifier: pgtype.Infinity, Status: pgtype.Present}},
{source: pgtype.NegativeInfinity, result: pgtype.Timestamptz{InfinityModifier: pgtype.NegativeInfinity, Status: pgtype.Present}},
+ {source: "2020-04-05 06:07:08Z", result: pgtype.Timestamptz{Time: time.Date(2020, 4, 5, 6, 7, 8, 0, time.UTC), Status: pgtype.Present}},
}
for i, tt := range successfulTests {
diff --git a/typed_array.go.erb b/typed_array.go.erb
index 5788626..e8433c0 100644
--- a/typed_array.go.erb
+++ b/typed_array.go.erb
@@ -1,5 +1,17 @@
// Code generated by erb. DO NOT EDIT.
+<%
+ # defaults when not explicitly set on command line
+
+ binary_format ||= "true"
+ text_format ||= "true"
+
+ text_null ||= "NULL"
+
+ encode_binary ||= binary_format
+ decode_binary ||= binary_format
+%>
+
package pgtype
import (
@@ -279,6 +291,7 @@ func (src *<%= pgtype_array_type %>) assignToRecursive(value reflect.Value, inde
return index, nil
}
+<% if text_format == "true" %>
func (dst *<%= pgtype_array_type %>) DecodeText(ci *ConnInfo, src []byte) error {
if src == nil {
*dst = <%= pgtype_array_type %>{Status: Null}
@@ -314,8 +327,9 @@ func (dst *<%= pgtype_array_type %>) DecodeText(ci *ConnInfo, src []byte) error
return nil
}
+<% end %>
-<% if binary_format == "true" %>
+<% if decode_binary == "true" %>
func (dst *<%= pgtype_array_type %>) DecodeBinary(ci *ConnInfo, src []byte) error {
if src == nil {
*dst = <%= pgtype_array_type %>{Status: Null}
@@ -359,6 +373,7 @@ func (dst *<%= pgtype_array_type %>) DecodeBinary(ci *ConnInfo, src []byte) erro
}
<% end %>
+<% if text_format == "true" %>
func (src <%= pgtype_array_type %>) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
switch src.Status {
case Null:
@@ -415,8 +430,9 @@ func (src <%= pgtype_array_type %>) EncodeText(ci *ConnInfo, buf []byte) ([]byte
return buf, nil
}
+<% end %>
-<% if binary_format == "true" %>
+<% if encode_binary == "true" %>
func (src <%= pgtype_array_type %>) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
switch src.Status {
case Null:
@@ -462,6 +478,7 @@ func (src <%= pgtype_array_type %>) EncodeText(ci *ConnInfo, buf []byte) ([]byte
}
<% end %>
+<% if text_format == "true" %>
// Scan implements the database/sql Scanner interface.
func (dst *<%= pgtype_array_type %>) Scan(src interface{}) error {
if src == nil {
@@ -492,3 +509,4 @@ func (src <%= pgtype_array_type %>) Value() (driver.Value, error) {
return string(buf), nil
}
+<% end %>
diff --git a/typed_array_gen.sh b/typed_array_gen.sh
index ea28be0..9ec768b 100755
--- a/typed_array_gen.sh
+++ b/typed_array_gen.sh
@@ -1,28 +1,31 @@
-erb pgtype_array_type=Int2Array pgtype_element_type=Int2 go_array_types=[]int16,[]*int16,[]uint16,[]*uint16,[]int32,[]*int32,[]uint32,[]*uint32,[]int64,[]*int64,[]uint64,[]*uint64,[]int,[]*int,[]uint,[]*uint element_type_name=int2 text_null=NULL binary_format=true typed_array.go.erb > int2_array.go
-erb pgtype_array_type=Int4Array pgtype_element_type=Int4 go_array_types=[]int16,[]*int16,[]uint16,[]*uint16,[]int32,[]*int32,[]uint32,[]*uint32,[]int64,[]*int64,[]uint64,[]*uint64,[]int,[]*int,[]uint,[]*uint element_type_name=int4 text_null=NULL binary_format=true typed_array.go.erb > int4_array.go
-erb pgtype_array_type=Int8Array pgtype_element_type=Int8 go_array_types=[]int16,[]*int16,[]uint16,[]*uint16,[]int32,[]*int32,[]uint32,[]*uint32,[]int64,[]*int64,[]uint64,[]*uint64,[]int,[]*int,[]uint,[]*uint element_type_name=int8 text_null=NULL binary_format=true typed_array.go.erb > int8_array.go
-erb pgtype_array_type=BoolArray pgtype_element_type=Bool go_array_types=[]bool,[]*bool element_type_name=bool text_null=NULL binary_format=true typed_array.go.erb > bool_array.go
-erb pgtype_array_type=DateArray pgtype_element_type=Date go_array_types=[]time.Time,[]*time.Time element_type_name=date text_null=NULL binary_format=true typed_array.go.erb > date_array.go
-erb pgtype_array_type=TimestamptzArray pgtype_element_type=Timestamptz go_array_types=[]time.Time,[]*time.Time element_type_name=timestamptz text_null=NULL binary_format=true typed_array.go.erb > timestamptz_array.go
-erb pgtype_array_type=TstzrangeArray pgtype_element_type=Tstzrange go_array_types=[]Tstzrange element_type_name=tstzrange text_null=NULL binary_format=true typed_array.go.erb > tstzrange_array.go
-erb pgtype_array_type=TsrangeArray pgtype_element_type=Tsrange go_array_types=[]Tsrange element_type_name=tsrange text_null=NULL binary_format=true typed_array.go.erb > tsrange_array.go
-erb pgtype_array_type=TimestampArray pgtype_element_type=Timestamp go_array_types=[]time.Time,[]*time.Time element_type_name=timestamp text_null=NULL binary_format=true typed_array.go.erb > timestamp_array.go
-erb pgtype_array_type=Float4Array pgtype_element_type=Float4 go_array_types=[]float32,[]*float32 element_type_name=float4 text_null=NULL binary_format=true typed_array.go.erb > float4_array.go
-erb pgtype_array_type=Float8Array pgtype_element_type=Float8 go_array_types=[]float64,[]*float64 element_type_name=float8 text_null=NULL binary_format=true typed_array.go.erb > float8_array.go
-erb pgtype_array_type=InetArray pgtype_element_type=Inet go_array_types=[]*net.IPNet,[]net.IP,[]*net.IP element_type_name=inet text_null=NULL binary_format=true typed_array.go.erb > inet_array.go
-erb pgtype_array_type=MacaddrArray pgtype_element_type=Macaddr go_array_types=[]net.HardwareAddr,[]*net.HardwareAddr element_type_name=macaddr text_null=NULL binary_format=true typed_array.go.erb > macaddr_array.go
-erb pgtype_array_type=CIDRArray pgtype_element_type=CIDR go_array_types=[]*net.IPNet,[]net.IP,[]*net.IP element_type_name=cidr text_null=NULL binary_format=true typed_array.go.erb > cidr_array.go
-erb pgtype_array_type=TextArray pgtype_element_type=Text go_array_types=[]string,[]*string element_type_name=text text_null=NULL binary_format=true typed_array.go.erb > text_array.go
-erb pgtype_array_type=VarcharArray pgtype_element_type=Varchar go_array_types=[]string,[]*string element_type_name=varchar text_null=NULL binary_format=true typed_array.go.erb > varchar_array.go
-erb pgtype_array_type=BPCharArray pgtype_element_type=BPChar go_array_types=[]string,[]*string element_type_name=bpchar text_null=NULL binary_format=true typed_array.go.erb > bpchar_array.go
-erb pgtype_array_type=ByteaArray pgtype_element_type=Bytea go_array_types=[][]byte element_type_name=bytea text_null=NULL binary_format=true typed_array.go.erb > bytea_array.go
-erb pgtype_array_type=ACLItemArray pgtype_element_type=ACLItem go_array_types=[]string,[]*string element_type_name=aclitem text_null=NULL binary_format=false typed_array.go.erb > aclitem_array.go
-erb pgtype_array_type=HstoreArray pgtype_element_type=Hstore go_array_types=[]map[string]string element_type_name=hstore text_null=NULL binary_format=true typed_array.go.erb > hstore_array.go
-erb pgtype_array_type=NumericArray pgtype_element_type=Numeric go_array_types=[]float32,[]*float32,[]float64,[]*float64,[]int64,[]*int64,[]uint64,[]*uint64 element_type_name=numeric text_null=NULL binary_format=true typed_array.go.erb > numeric_array.go
-erb pgtype_array_type=UUIDArray pgtype_element_type=UUID go_array_types=[][16]byte,[][]byte,[]string,[]*string element_type_name=uuid text_null=NULL binary_format=true typed_array.go.erb > uuid_array.go
-erb pgtype_array_type=JSONBArray pgtype_element_type=JSONB go_array_types=[]string,[][]byte element_type_name=jsonb text_null=NULL binary_format=true typed_array.go.erb > jsonb_array.go
+erb pgtype_array_type=Int2Array pgtype_element_type=Int2 go_array_types=[]int16,[]*int16,[]uint16,[]*uint16,[]int32,[]*int32,[]uint32,[]*uint32,[]int64,[]*int64,[]uint64,[]*uint64,[]int,[]*int,[]uint,[]*uint element_type_name=int2 typed_array.go.erb > int2_array.go
+erb pgtype_array_type=Int4Array pgtype_element_type=Int4 go_array_types=[]int16,[]*int16,[]uint16,[]*uint16,[]int32,[]*int32,[]uint32,[]*uint32,[]int64,[]*int64,[]uint64,[]*uint64,[]int,[]*int,[]uint,[]*uint element_type_name=int4 typed_array.go.erb > int4_array.go
+erb pgtype_array_type=Int8Array pgtype_element_type=Int8 go_array_types=[]int16,[]*int16,[]uint16,[]*uint16,[]int32,[]*int32,[]uint32,[]*uint32,[]int64,[]*int64,[]uint64,[]*uint64,[]int,[]*int,[]uint,[]*uint element_type_name=int8 typed_array.go.erb > int8_array.go
+erb pgtype_array_type=BoolArray pgtype_element_type=Bool go_array_types=[]bool,[]*bool element_type_name=bool typed_array.go.erb > bool_array.go
+erb pgtype_array_type=DateArray pgtype_element_type=Date go_array_types=[]time.Time,[]*time.Time element_type_name=date typed_array.go.erb > date_array.go
+erb pgtype_array_type=TimestamptzArray pgtype_element_type=Timestamptz go_array_types=[]time.Time,[]*time.Time element_type_name=timestamptz typed_array.go.erb > timestamptz_array.go
+erb pgtype_array_type=TstzrangeArray pgtype_element_type=Tstzrange go_array_types=[]Tstzrange element_type_name=tstzrange typed_array.go.erb > tstzrange_array.go
+erb pgtype_array_type=TsrangeArray pgtype_element_type=Tsrange go_array_types=[]Tsrange element_type_name=tsrange typed_array.go.erb > tsrange_array.go
+erb pgtype_array_type=TimestampArray pgtype_element_type=Timestamp go_array_types=[]time.Time,[]*time.Time element_type_name=timestamp typed_array.go.erb > timestamp_array.go
+erb pgtype_array_type=Float4Array pgtype_element_type=Float4 go_array_types=[]float32,[]*float32 element_type_name=float4 typed_array.go.erb > float4_array.go
+erb pgtype_array_type=Float8Array pgtype_element_type=Float8 go_array_types=[]float64,[]*float64 element_type_name=float8 typed_array.go.erb > float8_array.go
+erb pgtype_array_type=InetArray pgtype_element_type=Inet go_array_types=[]*net.IPNet,[]net.IP,[]*net.IP element_type_name=inet typed_array.go.erb > inet_array.go
+erb pgtype_array_type=MacaddrArray pgtype_element_type=Macaddr go_array_types=[]net.HardwareAddr,[]*net.HardwareAddr element_type_name=macaddr typed_array.go.erb > macaddr_array.go
+erb pgtype_array_type=CIDRArray pgtype_element_type=CIDR go_array_types=[]*net.IPNet,[]net.IP,[]*net.IP element_type_name=cidr typed_array.go.erb > cidr_array.go
+erb pgtype_array_type=TextArray pgtype_element_type=Text go_array_types=[]string,[]*string element_type_name=text typed_array.go.erb > text_array.go
+erb pgtype_array_type=VarcharArray pgtype_element_type=Varchar go_array_types=[]string,[]*string element_type_name=varchar typed_array.go.erb > varchar_array.go
+erb pgtype_array_type=BPCharArray pgtype_element_type=BPChar go_array_types=[]string,[]*string element_type_name=bpchar typed_array.go.erb > bpchar_array.go
+erb pgtype_array_type=ByteaArray pgtype_element_type=Bytea go_array_types=[][]byte element_type_name=bytea typed_array.go.erb > bytea_array.go
+erb pgtype_array_type=ACLItemArray pgtype_element_type=ACLItem go_array_types=[]string,[]*string element_type_name=aclitem binary_format=false typed_array.go.erb > aclitem_array.go
+erb pgtype_array_type=HstoreArray pgtype_element_type=Hstore go_array_types=[]map[string]string element_type_name=hstore typed_array.go.erb > hstore_array.go
+erb pgtype_array_type=NumericArray pgtype_element_type=Numeric go_array_types=[]float32,[]*float32,[]float64,[]*float64,[]int64,[]*int64,[]uint64,[]*uint64 element_type_name=numeric typed_array.go.erb > numeric_array.go
+erb pgtype_array_type=UUIDArray pgtype_element_type=UUID go_array_types=[][16]byte,[][]byte,[]string,[]*string element_type_name=uuid typed_array.go.erb > uuid_array.go
+erb pgtype_array_type=JSONArray pgtype_element_type=JSON go_array_types=[]string,[][]byte,[]json.RawMessage element_type_name=json typed_array.go.erb > json_array.go
+erb pgtype_array_type=JSONBArray pgtype_element_type=JSONB go_array_types=[]string,[][]byte,[]json.RawMessage element_type_name=jsonb typed_array.go.erb > jsonb_array.go
# While the binary format is theoretically possible it is only practical to use the text format.
-erb pgtype_array_type=EnumArray pgtype_element_type=GenericText go_array_types=[]string,[]*string text_null=NULL binary_format=false typed_array.go.erb > enum_array.go
+erb pgtype_array_type=EnumArray pgtype_element_type=GenericText go_array_types=[]string,[]*string binary_format=false typed_array.go.erb > enum_array.go
+
+erb pgtype_array_type=RecordArray pgtype_element_type=Record go_array_types=[][]Value element_type_name=record text_null=NULL encode_binary=false text_format=false typed_array.go.erb > record_array.go
goimports -w *_array.go
diff --git a/typed_multirange.go.erb b/typed_multirange.go.erb
new file mode 100644
index 0000000..84c8299
--- /dev/null
+++ b/typed_multirange.go.erb
@@ -0,0 +1,239 @@
+package pgtype
+
+import (
+ "database/sql/driver"
+ "encoding/binary"
+ "fmt"
+
+ "github.com/jackc/pgio"
+)
+
+type <%= multirange_type %> struct {
+ Ranges []<%= range_type %>
+ Status Status
+}
+
+func (dst *<%= multirange_type %>) Set(src interface{}) error {
+ //untyped nil and typed nil interfaces are different
+ if src == nil {
+ *dst = <%= multirange_type %>{Status: Null}
+ return nil
+ }
+
+ switch value := src.(type) {
+ case <%= multirange_type %>:
+ *dst = value
+ case *<%= multirange_type %>:
+ *dst = *value
+ case string:
+ return dst.DecodeText(nil, []byte(value))
+ case []<%= range_type %>:
+ if value == nil {
+ *dst = <%= multirange_type %>{Status: Null}
+ } else if len(value) == 0 {
+ *dst = <%= multirange_type %>{Status: Present}
+ } else {
+ elements := make([]<%= range_type %>, len(value))
+ for i := range value {
+ if err := elements[i].Set(value[i]); err != nil {
+ return err
+ }
+ }
+ *dst = <%= multirange_type %>{
+ Ranges: elements,
+ Status: Present,
+ }
+ }
+ case []*<%= range_type %>:
+ if value == nil {
+ *dst = <%= multirange_type %>{Status: Null}
+ } else if len(value) == 0 {
+ *dst = <%= multirange_type %>{Status: Present}
+ } else {
+ elements := make([]<%= range_type %>, len(value))
+ for i := range value {
+ if err := elements[i].Set(value[i]); err != nil {
+ return err
+ }
+ }
+ *dst = <%= multirange_type %>{
+ Ranges: elements,
+ Status: Present,
+ }
+ }
+ default:
+ return fmt.Errorf("cannot convert %v to <%= multirange_type %>", src)
+ }
+
+ return nil
+
+}
+
+func (dst <%= multirange_type %>) Get() interface{} {
+ switch dst.Status {
+ case Present:
+ return dst
+ case Null:
+ return nil
+ default:
+ return dst.Status
+ }
+}
+
+func (src *<%= multirange_type %>) AssignTo(dst interface{}) error {
+ return fmt.Errorf("cannot assign %v to %T", src, dst)
+}
+
+func (dst *<%= multirange_type %>) DecodeText(ci *ConnInfo, src []byte) error {
+ if src == nil {
+ *dst = <%= multirange_type %>{Status: Null}
+ return nil
+ }
+
+ utmr, err := ParseUntypedTextMultirange(string(src))
+ if err != nil {
+ return err
+ }
+
+ var elements []<%= range_type %>
+
+ if len(utmr.Elements) > 0 {
+ elements = make([]<%= range_type %>, len(utmr.Elements))
+
+ for i, s := range utmr.Elements {
+ var elem <%= range_type %>
+
+ elemSrc := []byte(s)
+
+ err = elem.DecodeText(ci, elemSrc)
+ if err != nil {
+ return err
+ }
+
+ elements[i] = elem
+ }
+ }
+
+ *dst = <%= multirange_type %>{Ranges: elements, Status: Present}
+
+ return nil
+}
+
+func (dst *<%= multirange_type %>) DecodeBinary(ci *ConnInfo, src []byte) error {
+ if src == nil {
+ *dst = <%= multirange_type %>{Status: Null}
+ return nil
+ }
+
+ rp := 0
+
+ numElems := int(binary.BigEndian.Uint32(src[rp:]))
+ rp += 4
+
+ if numElems == 0 {
+ *dst = <%= multirange_type %>{Status: Present}
+ return nil
+ }
+
+ elements := make([]<%= range_type %>, numElems)
+
+ for i := range elements {
+ elemLen := int(int32(binary.BigEndian.Uint32(src[rp:])))
+ rp += 4
+ var elemSrc []byte
+ if elemLen >= 0 {
+ elemSrc = src[rp : rp+elemLen]
+ rp += elemLen
+ }
+ err := elements[i].DecodeBinary(ci, elemSrc)
+ if err != nil {
+ return err
+ }
+ }
+
+ *dst = <%= multirange_type %>{Ranges: elements, Status: Present}
+ return nil
+}
+
+func (src <%= multirange_type %>) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
+ switch src.Status {
+ case Null:
+ return nil, nil
+ case Undefined:
+ return nil, errUndefined
+ }
+
+ buf = append(buf, '{')
+
+ inElemBuf := make([]byte, 0, 32)
+ for i, elem := range src.Ranges {
+ if i > 0 {
+ buf = append(buf, ',')
+ }
+
+ elemBuf, err := elem.EncodeText(ci, inElemBuf)
+ if err != nil {
+ return nil, err
+ }
+ if elemBuf == nil {
+ return nil, fmt.Errorf("multi-range does not allow null range")
+ } else {
+ buf = append(buf, string(elemBuf)...)
+ }
+
+ }
+
+ buf = append(buf, '}')
+
+ return buf, nil
+}
+
+func (src <%= multirange_type %>) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
+ switch src.Status {
+ case Null:
+ return nil, nil
+ case Undefined:
+ return nil, errUndefined
+ }
+
+ buf = pgio.AppendInt32(buf, int32(len(src.Ranges)))
+
+ for i := range src.Ranges {
+ sp := len(buf)
+ buf = pgio.AppendInt32(buf, -1)
+
+ elemBuf, err := src.Ranges[i].EncodeBinary(ci, buf)
+ if err != nil {
+ return nil, err
+ }
+ if elemBuf != nil {
+ buf = elemBuf
+ pgio.SetInt32(buf[sp:], int32(len(buf[sp:])-4))
+ }
+ }
+
+ return buf, nil
+}
+
+// Scan implements the database/sql Scanner interface.
+func (dst *<%= multirange_type %>) Scan(src interface{}) error {
+ if src == nil {
+ return dst.DecodeText(nil, nil)
+ }
+
+ switch src := src.(type) {
+ case string:
+ return dst.DecodeText(nil, []byte(src))
+ case []byte:
+ srcCopy := make([]byte, len(src))
+ copy(srcCopy, src)
+ return dst.DecodeText(nil, srcCopy)
+ }
+
+ return fmt.Errorf("cannot scan %T", src)
+}
+
+// Value implements the database/sql/driver Valuer interface.
+func (src <%= multirange_type %>) Value() (driver.Value, error) {
+ return EncodeValueText(src)
+}
diff --git a/typed_multirange_gen.sh b/typed_multirange_gen.sh
new file mode 100755
index 0000000..610f40a
--- /dev/null
+++ b/typed_multirange_gen.sh
@@ -0,0 +1,8 @@
+erb range_type=Numrange multirange_type=Nummultirange typed_multirange.go.erb > num_multirange.go
+erb range_type=Int4range multirange_type=Int4multirange typed_multirange.go.erb > int4_multirange.go
+erb range_type=Int8range multirange_type=Int8multirange typed_multirange.go.erb > int8_multirange.go
+# TODO
+# erb range_type=Tsrange multirange_type=Tsmultirange typed_multirange.go.erb > ts_multirange.go
+# erb range_type=Tstzrange multirange_type=Tstzmultirange typed_multirange.go.erb > tstz_multirange.go
+# erb range_type=Daterange multirange_type=Datemultirange typed_multirange.go.erb > date_multirange.go
+goimports -w *multirange.go
\ No newline at end of file
diff --git a/uuid.go b/uuid.go
index fa0be07..6839c05 100644
--- a/uuid.go
+++ b/uuid.go
@@ -18,14 +18,15 @@ func (dst *UUID) Set(src interface{}) error {
return nil
}
- if value, ok := src.(interface{ Get() interface{} }); ok {
+ switch value := src.(type) {
+ case interface{ Get() interface{} }:
value2 := value.Get()
if value2 != value {
return dst.Set(value2)
}
- }
-
- switch value := src.(type) {
+ case fmt.Stringer:
+ value2 := value.String()
+ return dst.Set(value2)
case [16]byte:
*dst = UUID{Bytes: value, Status: Present}
case []byte:
diff --git a/uuid_test.go b/uuid_test.go
index 5a93ea8..f767300 100644
--- a/uuid_test.go
+++ b/uuid_test.go
@@ -21,6 +21,16 @@ type SomeUUIDWrapper struct {
}
type SomeUUIDType [16]byte
+type StringUUIDType string
+type GetterUUIDType string
+
+func (s StringUUIDType) String() string {
+ return string(s)
+}
+
+func (s GetterUUIDType) Get() interface{} {
+ return string(s)
+}
func TestUUIDSet(t *testing.T) {
successfulTests := []struct {
@@ -55,6 +65,13 @@ func TestUUIDSet(t *testing.T) {
source: "000102030405060708090a0b0c0d0e0f",
result: pgtype.UUID{Bytes: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, Status: pgtype.Present},
},
+ {
+ source: StringUUIDType("00010203-0405-0607-0809-0a0b0c0d0e0f"),
+ result: pgtype.UUID{Bytes: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, Status: pgtype.Present},
+ }, {
+ source: GetterUUIDType("00010203-0405-0607-0809-0a0b0c0d0e0f"),
+ result: pgtype.UUID{Bytes: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, Status: pgtype.Present},
+ },
}
for i, tt := range successfulTests {
Debdiff
[The following lists of changes regard files as different if they have different names, permissions or owners.]
Files in second set of .debs but not in first
-rw-r--r-- root/root /usr/share/gocode/src/github.com/jackc/pgtype/int4_multirange.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/jackc/pgtype/int4_multirange_test.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/jackc/pgtype/int8_multirange.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/jackc/pgtype/int8_multirange_test.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/jackc/pgtype/json_array.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/jackc/pgtype/json_array_test.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/jackc/pgtype/multirange.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/jackc/pgtype/multirange_test.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/jackc/pgtype/num_multirange.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/jackc/pgtype/num_multirange_test.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/jackc/pgtype/record_array.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/jackc/pgtype/record_array_test.go
No differences were encountered in the control files