New Upstream Snapshot - golang-github-jackc-pgtype

Ready changes

Summary

Merged new upstream version: 1.13.0+git20221215.1.69882d2 (was: 1.12.0+git20221008.1.7294846).

Resulting package

Built on 2022-12-16T19:38 (took 4m13s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-snapshots golang-github-jackc-pgtype-dev

Lintian Result

Diff

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
deleted file mode 100644
index 4b5a72f..0000000
--- a/.github/workflows/ci.yml
+++ /dev/null
@@ -1,52 +0,0 @@
-name: CI
-
-on:
-  push:
-    branches: [ master ]
-  pull_request:
-    branches: [ master ]
-
-jobs:
-
-  test:
-    name: Test
-    runs-on: ubuntu-latest
-
-    services:
-      postgres:
-        image: postgres
-        env:
-          POSTGRES_PASSWORD: secret
-        options: >-
-          --health-cmd pg_isready
-          --health-interval 10s
-          --health-timeout 5s
-          --health-retries 5
-        ports:
-          - 5432:5432
-
-    steps:
-
-    - name: Set up Go 1.x
-      uses: actions/setup-go@v2
-      with:
-        go-version: ^1.13
-
-    - name: Check out code into the Go module directory
-      uses: actions/checkout@v2
-
-    - name: Create hstore extension
-      run: psql -c 'create extension hstore'
-      env:
-        PGHOST: localhost
-        PGUSER: postgres
-        PGPASSWORD: secret
-        PGSSLMODE: disable
-
-    - name: Test
-      run: go test -v ./...
-      env:
-        PGHOST: localhost
-        PGUSER: postgres
-        PGPASSWORD: secret
-        PGSSLMODE: disable
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 73126cf..8c04d8d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,33 @@
+# 1.13.0 (December 1, 2022)
+
+* Fix: Reset jsonb before unmarshal (Tomas Odinas)
+* Fix: return correct zero value when UUID conversion fails (ndrpnt)
+* Fix: EncodeText for Lseg includes [ and ]
+* Support sql Value and Scan for custom date type (Hubert Krauze)
+* Support Ltree binary encoding (AmineChikhaoui)
+* Fix: dates with "BC" (jozeflami)
+
+# 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/README.md b/README.md
index 77d59b3..72dadcf 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,12 @@
 [![](https://godoc.org/github.com/jackc/pgtype?status.svg)](https://godoc.org/github.com/jackc/pgtype)
 ![CI](https://github.com/jackc/pgtype/workflows/CI/badge.svg)
 
+---
+
+This version is used with pgx `v4`. In pgx `v5` it is part of the https://github.com/jackc/pgx repository.
+
+---
+
 # pgtype
 
 pgtype implements Go types for over 70 PostgreSQL types. pgtype is the type system underlying the
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/convert.go b/convert.go
index f7219bd..377fe3e 100644
--- a/convert.go
+++ b/convert.go
@@ -172,7 +172,7 @@ func underlyingUUIDType(val interface{}) (interface{}, bool) {
 	switch refVal.Kind() {
 	case reflect.Ptr:
 		if refVal.IsNil() {
-			return time.Time{}, false
+			return nil, false
 		}
 		convVal := refVal.Elem().Interface()
 		return convVal, true
diff --git a/date.go b/date.go
index e8d21a7..e68abf0 100644
--- a/date.go
+++ b/date.go
@@ -1,10 +1,12 @@
 package pgtype
 
 import (
+	"database/sql"
 	"database/sql/driver"
 	"encoding/binary"
 	"encoding/json"
 	"fmt"
+	"strings"
 	"time"
 
 	"github.com/jackc/pgio"
@@ -34,17 +36,25 @@ func (dst *Date) Set(src interface{}) error {
 		}
 	}
 
+	if value, ok := src.(interface{ Value() (driver.Value, error) }); ok {
+		v, err := value.Value()
+		if err != nil {
+			return fmt.Errorf("cannot get value %v for Date: %v", value, err)
+		}
+		return dst.Set(v)
+	}
+
 	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}
@@ -76,6 +86,24 @@ func (dst Date) Get() interface{} {
 }
 
 func (src *Date) AssignTo(dst interface{}) error {
+	if scanner, ok := dst.(sql.Scanner); ok {
+		var err error
+		switch src.Status {
+		case Present:
+			if src.InfinityModifier != None {
+				err = scanner.Scan(src.InfinityModifier.String())
+			} else {
+				err = scanner.Scan(src.Time)
+			}
+		case Null:
+			err = scanner.Scan(nil)
+		}
+		if err != nil {
+			return fmt.Errorf("unable assign %v to %T: %s", src, dst, err)
+		}
+		return nil
+	}
+
 	switch src.Status {
 	case Present:
 		switch v := dst.(type) {
@@ -111,6 +139,15 @@ func (dst *Date) DecodeText(ci *ConnInfo, src []byte) error {
 	case "-infinity":
 		*dst = Date{Status: Present, InfinityModifier: -Infinity}
 	default:
+		if strings.HasSuffix(sbuf, " BC") {
+			t, err := time.ParseInLocation("2006-01-02", strings.TrimRight(sbuf, " BC"), time.UTC)
+			t2 := time.Date(1-t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), t.Location())
+			if err != nil {
+				return err
+			}
+			*dst = Date{Time: t2, Status: Present}
+			return nil
+		}
 		t, err := time.ParseInLocation("2006-01-02", sbuf, time.UTC)
 		if err != nil {
 			return err
diff --git a/date_array_test.go b/date_array_test.go
index 4458abf..ec26eb4 100644
--- a/date_array_test.go
+++ b/date_array_test.go
@@ -65,6 +65,14 @@ func TestDateArraySet(t *testing.T) {
 				Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
 				Status:     pgtype.Present},
 		},
+		{
+			source: []customDate{{t: time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC)}},
+			result: pgtype.DateArray{
+				Elements:   []pgtype.Date{{Time: time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present}},
+				Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
+				Status:     pgtype.Present,
+			},
+		},
 		{
 			source: (([]time.Time)(nil)),
 			result: pgtype.DateArray{Status: pgtype.Null},
@@ -162,6 +170,7 @@ func TestDateArrayAssignTo(t *testing.T) {
 	var timeSliceDim4 [][][][]time.Time
 	var timeArrayDim2 [2][1]time.Time
 	var timeArrayDim4 [2][1][1][3]time.Time
+	var customDateSlice []customDate
 
 	simpleTests := []struct {
 		src      pgtype.DateArray
@@ -177,6 +186,15 @@ func TestDateArrayAssignTo(t *testing.T) {
 			dst:      &timeSlice,
 			expected: []time.Time{time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC)},
 		},
+		{
+			src: pgtype.DateArray{
+				Elements:   []pgtype.Date{{Time: time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present}},
+				Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
+				Status:     pgtype.Present,
+			},
+			dst:      &customDateSlice,
+			expected: []customDate{{t: time.Date(2015, 2, 1, 0, 0, 0, 0, time.UTC)}},
+		},
 		{
 			src:      pgtype.DateArray{Status: pgtype.Null},
 			dst:      &timeSlice,
diff --git a/date_test.go b/date_test.go
index 5c38e7a..f90d60f 100644
--- a/date_test.go
+++ b/date_test.go
@@ -1,6 +1,8 @@
 package pgtype_test
 
 import (
+	"database/sql/driver"
+	"fmt"
 	"reflect"
 	"testing"
 	"time"
@@ -9,6 +11,37 @@ import (
 	"github.com/jackc/pgtype/testutil"
 )
 
+type customDate struct {
+	t time.Time
+}
+
+func (d customDate) Value() (driver.Value, error) {
+	return d.t.Format("2006-01-02"), nil
+}
+
+func (d *customDate) Scan(src interface{}) (err error) {
+	if src == nil {
+		d.t = time.Time{}
+		return nil
+	}
+
+	switch v := src.(type) {
+	case int64:
+		d.t = time.Unix(v, 0).UTC()
+	case float64:
+		d.t = time.Unix(int64(v), 0).UTC()
+	case string:
+		d.t, err = time.Parse("2006-01-02", v)
+	case []byte:
+		d.t, err = time.Parse("2006-01-02", string(v))
+	case time.Time:
+		d.t = v
+	default:
+		err = fmt.Errorf("failed to scan type '%T' into date", src)
+	}
+	return err
+}
+
 func TestDateTranscode(t *testing.T) {
 	testutil.TestSuccessfulTranscodeEqFunc(t, "date", []interface{}{
 		&pgtype.Date{Time: time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present},
@@ -43,6 +76,8 @@ func TestDateSet(t *testing.T) {
 		{source: time.Date(2200, 1, 1, 0, 0, 0, 0, time.UTC), result: pgtype.Date{Time: time.Date(2200, 1, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present}},
 		{source: _time(time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)), result: pgtype.Date{Time: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present}},
 		{source: "1999-12-31", result: pgtype.Date{Time: time.Date(1999, 12, 31, 0, 0, 0, 0, time.UTC), Status: pgtype.Present}},
+		{source: "0001-01-01 BC", result: pgtype.Date{Time: time.Date(0000, 01, 01, 0, 0, 0, 0, time.UTC), Status: pgtype.Present}},
+		{source: customDate{t: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)}, result: pgtype.Date{Time: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC), Status: pgtype.Present}},
 	}
 
 	for i, tt := range successfulTests {
@@ -68,6 +103,7 @@ func TestDateAssignTo(t *testing.T) {
 		expected interface{}
 	}{
 		{src: pgtype.Date{Time: time.Date(2015, 1, 1, 0, 0, 0, 0, time.Local), Status: pgtype.Present}, dst: &tim, expected: time.Date(2015, 1, 1, 0, 0, 0, 0, time.Local)},
+		{src: pgtype.Date{Time: time.Date(2015, 1, 1, 0, 0, 0, 0, time.Local), Status: pgtype.Present}, dst: &customDate{}, expected: customDate{t: time.Date(2015, 1, 1, 0, 0, 0, 0, time.Local)}},
 		{src: pgtype.Date{Time: time.Time{}, Status: pgtype.Null}, dst: &ptim, expected: ((*time.Time)(nil))},
 	}
 
diff --git a/debian/changelog b/debian/changelog
index 0e8faa7..fb74263 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+golang-github-jackc-pgtype (1.13.0+git20221215.1.69882d2-1) UNRELEASED; urgency=low
+
+  * New upstream snapshot.
+  * New upstream snapshot.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Fri, 16 Dec 2022 19:36:27 -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.go b/json.go
index 32bef5e..a9508bd 100644
--- a/json.go
+++ b/json.go
@@ -5,6 +5,7 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	"reflect"
 )
 
 type JSON struct {
@@ -107,6 +108,9 @@ func (src *JSON) AssignTo(dst interface{}) error {
 			data = []byte("null")
 		}
 
+		p := reflect.ValueOf(dst).Elem()
+		p.Set(reflect.Zero(p.Type()))
+
 		return json.Unmarshal(data, dst)
 	}
 
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/lseg.go b/lseg.go
index 5c4babb..894dae8 100644
--- a/lseg.go
+++ b/lseg.go
@@ -115,7 +115,7 @@ func (src Lseg) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
 		return nil, errUndefined
 	}
 
-	buf = append(buf, fmt.Sprintf(`(%s,%s),(%s,%s)`,
+	buf = append(buf, fmt.Sprintf(`[(%s,%s),(%s,%s)]`,
 		strconv.FormatFloat(src.P[0].X, 'f', -1, 64),
 		strconv.FormatFloat(src.P[0].Y, 'f', -1, 64),
 		strconv.FormatFloat(src.P[1].X, 'f', -1, 64),
diff --git a/ltree.go b/ltree.go
new file mode 100644
index 0000000..8c8d421
--- /dev/null
+++ b/ltree.go
@@ -0,0 +1,72 @@
+package pgtype
+
+import (
+	"database/sql/driver"
+	"fmt"
+)
+
+type Ltree Text
+
+func (dst *Ltree) Set(src interface{}) error {
+	return (*Text)(dst).Set(src)
+}
+
+func (dst Ltree) Get() interface{} {
+	return (Text)(dst).Get()
+}
+
+func (src *Ltree) AssignTo(dst interface{}) error {
+	return (*Text)(src).AssignTo(dst)
+}
+
+func (src Ltree) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
+	return (Text)(src).EncodeText(ci, buf)
+}
+
+func (src Ltree) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
+	switch src.Status {
+	case Null:
+		return nil, nil
+	case Undefined:
+		return nil, errUndefined
+	}
+	buf = append(buf, 1)
+	return append(buf, src.String...), nil
+}
+
+func (Ltree) PreferredResultFormat() int16 {
+	return TextFormatCode
+}
+
+func (dst *Ltree) DecodeText(ci *ConnInfo, src []byte) error {
+	return (*Text)(dst).DecodeText(ci, src)
+}
+
+func (dst *Ltree) DecodeBinary(ci *ConnInfo, src []byte) error {
+	if src == nil {
+		*dst = Ltree{Status: Null}
+		return nil
+	}
+
+	// Get Ltree version, only 1 is allowed
+	version := src[0]
+	if version != 1 {
+		return fmt.Errorf("unsupported ltree version %d", version)
+	}
+
+	ltreeStr := string(src[1:])
+	*dst = Ltree{String: ltreeStr, Status: Present}
+	return nil
+}
+
+func (Ltree) PreferredParamFormat() int16 {
+	return TextFormatCode
+}
+
+func (dst *Ltree) Scan(src interface{}) error {
+	return (*Text)(dst).Scan(src)
+}
+
+func (src Ltree) Value() (driver.Value, error) {
+	return (Text)(src).Value()
+}
diff --git a/ltree_test.go b/ltree_test.go
new file mode 100644
index 0000000..7a1e6cb
--- /dev/null
+++ b/ltree_test.go
@@ -0,0 +1,50 @@
+package pgtype_test
+
+import (
+	"reflect"
+	"testing"
+
+	"github.com/jackc/pgtype"
+	"github.com/jackc/pgtype/testutil"
+)
+
+func TestLtreeTranscode(t *testing.T) {
+	values := []interface{}{
+		&pgtype.Ltree{String: "", Status: pgtype.Present},
+		&pgtype.Ltree{String: "All.foo.one", Status: pgtype.Present},
+		&pgtype.Ltree{Status: pgtype.Null},
+	}
+
+	testutil.TestSuccessfulTranscodeEqFunc(
+		t, "ltree", values, func(ai, bi interface{}) bool {
+			a := ai.(pgtype.Ltree)
+			b := bi.(pgtype.Ltree)
+
+			if a.String != b.String || a.Status != b.Status {
+				return false
+			}
+			return true
+		},
+	)
+
+}
+
+func TestLtreeSet(t *testing.T) {
+	successfulTests := []struct {
+		src    interface{}
+		result pgtype.Ltree
+	}{
+		{src: "All.foo.bar", result: pgtype.Ltree{String: "All.foo.bar", Status: pgtype.Present}},
+		{src: (*string)(nil), result: pgtype.Ltree{Status: pgtype.Null}},
+	}
+	for i, tt := range successfulTests {
+		var dst pgtype.Ltree
+		err := dst.Set(tt.src)
+		if err != nil {
+			t.Errorf("%d: %v", i, err)
+		}
+		if !reflect.DeepEqual(dst, tt.result) {
+			t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.src, tt.result, dst)
+		}
+	}
+}
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..3eb82fb 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,77 @@ 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{},
+		"ltree":          &Ltree{},
+		"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..fce490c 100644
--- a/timestamp.go
+++ b/timestamp.go
@@ -4,6 +4,7 @@ import (
 	"database/sql/driver"
 	"encoding/binary"
 	"fmt"
+	"strings"
 	"time"
 
 	"github.com/jackc/pgio"
@@ -46,6 +47,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:
@@ -110,6 +119,15 @@ func (dst *Timestamp) DecodeText(ci *ConnInfo, src []byte) error {
 	case "-infinity":
 		*dst = Timestamp{Status: Present, InfinityModifier: -Infinity}
 	default:
+		if strings.HasSuffix(sbuf, " BC") {
+			t, err := time.Parse(pgTimestampFormat, strings.TrimRight(sbuf, " BC"))
+			t2 := time.Date(1-t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), t.Location())
+			if err != nil {
+				return err
+			}
+			*dst = Timestamp{Time: t2, Status: Present}
+			return nil
+		}
 		tim, err := time.Parse(pgTimestampFormat, sbuf)
 		if err != nil {
 			return err
diff --git a/timestamp_test.go b/timestamp_test.go
index ea7ef57..a7eff26 100644
--- a/timestamp_test.go
+++ b/timestamp_test.go
@@ -123,6 +123,8 @@ 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}},
+		{source: "0001-01-01 00:00:00.000000000 BC", result: pgtype.Timestamp{Time: time.Date(0000, 01, 01, 0, 0, 0, 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/ltree.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/jackc/pgtype/ltree_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

More details

Full run details