From 06d85e064d6bfc8244c85ccae622f8ebd6ebe018 Mon Sep 17 00:00:00 2001 From: Jack Christensen Date: Sun, 19 May 2024 07:37:32 -0500 Subject: [PATCH 1/5] Add Scan and Value methods to FlatArray and Array Support direct usage in database/sql. https://github.com/jackc/pgx/issues/1458 https://github.com/jackc/pgx/issues/1779 https://github.com/jackc/pgx/issues/1956 https://github.com/jackc/pgx/issues/1662 --- pgtype/array.go | 123 +++++++++++++++++++++++++++++++++++++++++++ pgtype/pgtype.go | 41 +++++++++------ stdlib/bench_test.go | 51 ++++++++++++++++++ stdlib/sql_test.go | 45 +++++++++++++--- 4 files changed, 237 insertions(+), 23 deletions(-) diff --git a/pgtype/array.go b/pgtype/array.go index 06b824ad0..df0d2be3f 100644 --- a/pgtype/array.go +++ b/pgtype/array.go @@ -2,6 +2,7 @@ package pgtype import ( "bytes" + "database/sql/driver" "encoding/binary" "fmt" "io" @@ -419,6 +420,53 @@ func (a Array[T]) ScanIndexType() any { return new(T) } +// Scan implements the database/sql Scanner interface. +// +// Array needs a *Map to decode the array elements. Unfortunately, the the Go type system does not support attaching +// data to a type and the Scanner interface does not have an argument that can be used to pass extra data to the Scan +// method. To work around this limitation, Scan uses a default *Map. This means Scan may only work properly with types +// that are supported by a default *Map. from a Map or Codec. +func (a *Array[T]) Scan(v any) error { + if v == nil { + *a = Array[T]{} + return nil + } + + m := databaseSQLMapPool.Get().(*Map) + defer databaseSQLMapPool.Put(m) + + switch v := v.(type) { + case string: + return m.Scan(0, 0, []byte(v), a) + case []byte: + return m.Scan(0, 0, v, a) + default: + return fmt.Errorf("unsupported type: %T", v) + } +} + +// Value implements the database/sql/driver Valuer interface. +// +// Array needs a *Map to encode the array elements. Unfortunately, the the Go type system does not support attaching +// data to a type and the Valuer interface does not have an argument that can be used to pass extra data to the Value +// method. To work around this limitation, Value uses a default *Map. This means Value may only work properly with types +// that are supported by a default *Map. from a Map or Codec. +func (src Array[T]) Value() (driver.Value, error) { + if !src.Valid { + return nil, nil + } + + m := databaseSQLMapPool.Get().(*Map) + defer databaseSQLMapPool.Put(m) + + buf, err := m.Encode(0, TextFormatCode, src, nil) + if err != nil { + return nil, err + } + + return string(buf), nil +} + // FlatArray implements the ArrayGetter and ArraySetter interfaces for any slice of T. It ignores PostgreSQL dimensions // and custom lower bounds. Use Array to preserve these. type FlatArray[T any] []T @@ -458,3 +506,78 @@ func (a FlatArray[T]) ScanIndex(i int) any { func (a FlatArray[T]) ScanIndexType() any { return new(T) } + +// Scan implements the database/sql Scanner interface. +// +// FlatArray needs a *Map to decode the array elements. Unfortunately, the the Go type system does not support attaching +// data to a type and the Scanner interface does not have an argument that can be used to pass extra data to the Scan +// method. To work around this limitation, Scan uses a default *Map. This means Scan may only work properly with types +// that are supported by a default *Map. from a Map or Codec. +func (a *FlatArray[T]) Scan(v any) error { + if v == nil { + *a = nil + return nil + } + + m := databaseSQLMapPool.Get().(*Map) + defer databaseSQLMapPool.Put(m) + + switch v := v.(type) { + case string: + return m.Scan(0, 0, []byte(v), a) + case []byte: + return m.Scan(0, 0, v, a) + default: + return fmt.Errorf("unsupported type: %T", v) + } +} + +// Value implements the database/sql/driver Valuer interface. +// +// FlatArray needs a *Map to encode the array elements. Unfortunately, the the Go type system does not support attaching +// data to a type and the Valuer interface does not have an argument that can be used to pass extra data to the Value +// method. To work around this limitation, Value uses a default *Map. This means Value may only work properly with types +// that are supported by a default *Map. from a Map or Codec. +func (src FlatArray[T]) Value() (driver.Value, error) { + if src == nil { + return nil, nil + } + + m := databaseSQLMapPool.Get().(*Map) + defer databaseSQLMapPool.Put(m) + + buf, err := m.Encode(0, TextFormatCode, src, nil) + if err != nil { + return nil, err + } + + return string(buf), nil +} + +// flatArrayForWrapper is FlatArray without the Scan and Value methods. The avoids a wrapped plan attempt always +// "succeeding". +type flatArrayForWrapper[T any] []T + +func (a flatArrayForWrapper[T]) Dimensions() []ArrayDimension { + return FlatArray[T](a).Dimensions() +} + +func (a flatArrayForWrapper[T]) Index(i int) any { + return FlatArray[T](a).Index(i) +} + +func (a flatArrayForWrapper[T]) IndexType() any { + return FlatArray[T](a).IndexType() +} + +func (a *flatArrayForWrapper[T]) SetDimensions(dimensions []ArrayDimension) error { + return (*FlatArray[T])(a).SetDimensions(dimensions) +} + +func (a flatArrayForWrapper[T]) ScanIndex(i int) any { + return FlatArray[T](a).ScanIndex(i) +} + +func (a flatArrayForWrapper[T]) ScanIndexType() any { + return FlatArray[T](a).ScanIndexType() +} diff --git a/pgtype/pgtype.go b/pgtype/pgtype.go index 2be11e820..d3b090f74 100644 --- a/pgtype/pgtype.go +++ b/pgtype/pgtype.go @@ -8,6 +8,7 @@ import ( "net" "net/netip" "reflect" + "sync" "time" ) @@ -123,6 +124,14 @@ const ( Int8multirangeArrayOID = 6157 ) +// databaseSQLMapPool is a sync.Pool that holds *Map instances used for implementing sql.Scanner and driver.Valuer on +// types that need a *Map to encode and decode such as FlatArray[T]. +var databaseSQLMapPool = sync.Pool{ + New: func() any { + return NewMap() + }, +} + type InfinityModifier int8 const ( @@ -932,19 +941,19 @@ func TryWrapPtrSliceScanPlan(target any) (plan WrappedScanPlanNextSetter, nextVa // Avoid using reflect path for common types. switch target := target.(type) { case *[]int16: - return &wrapPtrSliceScanPlan[int16]{}, (*FlatArray[int16])(target), true + return &wrapPtrSliceScanPlan[int16]{}, (*flatArrayForWrapper[int16])(target), true case *[]int32: - return &wrapPtrSliceScanPlan[int32]{}, (*FlatArray[int32])(target), true + return &wrapPtrSliceScanPlan[int32]{}, (*flatArrayForWrapper[int32])(target), true case *[]int64: - return &wrapPtrSliceScanPlan[int64]{}, (*FlatArray[int64])(target), true + return &wrapPtrSliceScanPlan[int64]{}, (*flatArrayForWrapper[int64])(target), true case *[]float32: - return &wrapPtrSliceScanPlan[float32]{}, (*FlatArray[float32])(target), true + return &wrapPtrSliceScanPlan[float32]{}, (*flatArrayForWrapper[float32])(target), true case *[]float64: - return &wrapPtrSliceScanPlan[float64]{}, (*FlatArray[float64])(target), true + return &wrapPtrSliceScanPlan[float64]{}, (*flatArrayForWrapper[float64])(target), true case *[]string: - return &wrapPtrSliceScanPlan[string]{}, (*FlatArray[string])(target), true + return &wrapPtrSliceScanPlan[string]{}, (*flatArrayForWrapper[string])(target), true case *[]time.Time: - return &wrapPtrSliceScanPlan[time.Time]{}, (*FlatArray[time.Time])(target), true + return &wrapPtrSliceScanPlan[time.Time]{}, (*flatArrayForWrapper[time.Time])(target), true } targetType := reflect.TypeOf(target) @@ -968,7 +977,7 @@ type wrapPtrSliceScanPlan[T any] struct { func (plan *wrapPtrSliceScanPlan[T]) SetNext(next ScanPlan) { plan.next = next } func (plan *wrapPtrSliceScanPlan[T]) Scan(src []byte, target any) error { - return plan.next.Scan(src, (*FlatArray[T])(target.(*[]T))) + return plan.next.Scan(src, (*flatArrayForWrapper[T])(target.(*[]T))) } type wrapPtrSliceReflectScanPlan struct { @@ -1773,19 +1782,19 @@ func TryWrapSliceEncodePlan(value any) (plan WrappedEncodePlanNextSetter, nextVa // Avoid using reflect path for common types. switch value := value.(type) { case []int16: - return &wrapSliceEncodePlan[int16]{}, (FlatArray[int16])(value), true + return &wrapSliceEncodePlan[int16]{}, (flatArrayForWrapper[int16])(value), true case []int32: - return &wrapSliceEncodePlan[int32]{}, (FlatArray[int32])(value), true + return &wrapSliceEncodePlan[int32]{}, (flatArrayForWrapper[int32])(value), true case []int64: - return &wrapSliceEncodePlan[int64]{}, (FlatArray[int64])(value), true + return &wrapSliceEncodePlan[int64]{}, (flatArrayForWrapper[int64])(value), true case []float32: - return &wrapSliceEncodePlan[float32]{}, (FlatArray[float32])(value), true + return &wrapSliceEncodePlan[float32]{}, (flatArrayForWrapper[float32])(value), true case []float64: - return &wrapSliceEncodePlan[float64]{}, (FlatArray[float64])(value), true + return &wrapSliceEncodePlan[float64]{}, (flatArrayForWrapper[float64])(value), true case []string: - return &wrapSliceEncodePlan[string]{}, (FlatArray[string])(value), true + return &wrapSliceEncodePlan[string]{}, (flatArrayForWrapper[string])(value), true case []time.Time: - return &wrapSliceEncodePlan[time.Time]{}, (FlatArray[time.Time])(value), true + return &wrapSliceEncodePlan[time.Time]{}, (flatArrayForWrapper[time.Time])(value), true } if valueType := reflect.TypeOf(value); valueType != nil && valueType.Kind() == reflect.Slice { @@ -1805,7 +1814,7 @@ type wrapSliceEncodePlan[T any] struct { func (plan *wrapSliceEncodePlan[T]) SetNext(next EncodePlan) { plan.next = next } func (plan *wrapSliceEncodePlan[T]) Encode(value any, buf []byte) (newBuf []byte, err error) { - return plan.next.Encode((FlatArray[T])(value.([]T)), buf) + return plan.next.Encode((flatArrayForWrapper[T])(value.([]T)), buf) } type wrapSliceEncodeReflectPlan struct { diff --git a/stdlib/bench_test.go b/stdlib/bench_test.go index 33734fc1b..141fc4eb5 100644 --- a/stdlib/bench_test.go +++ b/stdlib/bench_test.go @@ -8,6 +8,8 @@ import ( "strings" "testing" "time" + + "github.com/jackc/pgx/v5/pgtype" ) func getSelectRowsCounts(b *testing.B) []int64 { @@ -107,3 +109,52 @@ func BenchmarkSelectRowsScanNull(b *testing.B) { }) } } + +func BenchmarkFlatArrayEncodeArgument(b *testing.B) { + db := openDB(b) + defer closeDB(b, db) + + input := make(pgtype.FlatArray[string], 10) + for i := range input { + input[i] = fmt.Sprintf("String %d", i) + } + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + var n int64 + err := db.QueryRow("select cardinality($1::text[])", input).Scan(&n) + if err != nil { + b.Fatal(err) + } + if n != int64(len(input)) { + b.Fatalf("Expected %d, got %d", len(input), n) + } + } +} + +func BenchmarkFlatArrayScanResult(b *testing.B) { + db := openDB(b) + defer closeDB(b, db) + + var input string + for i := 0; i < 10; i++ { + if i > 0 { + input += "," + } + input += fmt.Sprintf(`'String %d'`, i) + } + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + var result pgtype.FlatArray[string] + err := db.QueryRow(fmt.Sprintf("select array[%s]::text[]", input)).Scan(&result) + if err != nil { + b.Fatal(err) + } + if len(result) != 10 { + b.Fatalf("Expected %d, got %d", len(result), 10) + } + } +} diff --git a/stdlib/sql_test.go b/stdlib/sql_test.go index 1e3db0d35..7f74cd9fb 100644 --- a/stdlib/sql_test.go +++ b/stdlib/sql_test.go @@ -509,18 +509,49 @@ func TestConnQueryScanGoArray(t *testing.T) { }) } -func TestConnQueryScanArray(t *testing.T) { +func TestPGTypeFlatArray(t *testing.T) { testWithAllQueryExecModes(t, func(t *testing.T, db *sql.DB) { - m := pgtype.NewMap() + var names pgtype.FlatArray[string] - var a pgtype.Array[int64] - err := db.QueryRow("select '{1,2,3}'::bigint[]").Scan(m.SQLScanner(&a)) + err := db.QueryRow("select array['John', 'Jane']::text[]").Scan(&names) + require.NoError(t, err) + require.Equal(t, pgtype.FlatArray[string]{"John", "Jane"}, names) + + var n int + err = db.QueryRow("select cardinality($1::text[])", names).Scan(&n) + require.NoError(t, err) + require.EqualValues(t, 2, n) + + err = db.QueryRow("select null::text[]").Scan(&names) + require.NoError(t, err) + require.Nil(t, names) + }) +} + +func TestPGTypeArray(t *testing.T) { + testWithAllQueryExecModes(t, func(t *testing.T, db *sql.DB) { + var matrix pgtype.Array[int64] + + err := db.QueryRow("select '{{1,2,3},{4,5,6}}'::bigint[]").Scan(&matrix) + require.NoError(t, err) + require.Equal(t, + pgtype.Array[int64]{ + Elements: []int64{1, 2, 3, 4, 5, 6}, + Dims: []pgtype.ArrayDimension{ + {Length: 2, LowerBound: 1}, + {Length: 3, LowerBound: 1}, + }, + Valid: true}, + matrix) + + var equal bool + err = db.QueryRow("select '{{1,2,3},{4,5,6}}'::bigint[] = $1::bigint[]", matrix).Scan(&equal) require.NoError(t, err) - assert.Equal(t, pgtype.Array[int64]{Elements: []int64{1, 2, 3}, Dims: []pgtype.ArrayDimension{{Length: 3, LowerBound: 1}}, Valid: true}, a) + require.Equal(t, true, equal) - err = db.QueryRow("select null::bigint[]").Scan(m.SQLScanner(&a)) + err = db.QueryRow("select null::bigint[]").Scan(&matrix) require.NoError(t, err) - assert.Equal(t, pgtype.Array[int64]{Elements: nil, Dims: nil, Valid: false}, a) + assert.Equal(t, pgtype.Array[int64]{Elements: nil, Dims: nil, Valid: false}, matrix) }) } From a07f17579d3b0a239cc5c7ae80b7be7ccde1c4ee Mon Sep 17 00:00:00 2001 From: Jack Christensen Date: Sun, 19 May 2024 08:03:34 -0500 Subject: [PATCH 2/5] Add Scan and Value to pgtype.Range[T] --- pgtype/range.go | 48 ++++++++++++++++++++++++++++++++++++++++++++++ stdlib/sql_test.go | 15 +++++++++++---- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/pgtype/range.go b/pgtype/range.go index 16427cccd..a00109e70 100644 --- a/pgtype/range.go +++ b/pgtype/range.go @@ -2,6 +2,7 @@ package pgtype import ( "bytes" + "database/sql/driver" "encoding/binary" "fmt" ) @@ -320,3 +321,50 @@ func (r *Range[T]) SetBoundTypes(lower, upper BoundType) error { r.Valid = true return nil } + +// Scan implements the database/sql Scanner interface. +// +// Range needs a *Map to decode the range elements. Unfortunately, the the Go type system does not support attaching +// data to a type and the Scanner interface does not have an argument that can be used to pass extra data to the Scan +// method. To work around this limitation, Scan uses a default *Map. This means Scan may only work properly with types +// that are supported by a default *Map. from a Map or Codec. +func (a *Range[T]) Scan(v any) error { + if v == nil { + *a = Range[T]{} + return nil + } + + m := databaseSQLMapPool.Get().(*Map) + defer databaseSQLMapPool.Put(m) + + switch v := v.(type) { + case string: + return m.Scan(0, 0, []byte(v), a) + case []byte: + return m.Scan(0, 0, v, a) + default: + return fmt.Errorf("unsupported type: %T", v) + } +} + +// Value implements the database/sql/driver Valuer interface. +// +// Range needs a *Map to encode the range elements. Unfortunately, the the Go type system does not support attaching +// data to a type and the Valuer interface does not have an argument that can be used to pass extra data to the Value +// method. To work around this limitation, Value uses a default *Map. This means Value may only work properly with types +// that are supported by a default *Map. from a Map or Codec. +func (src Range[T]) Value() (driver.Value, error) { + if !src.Valid { + return nil, nil + } + + m := databaseSQLMapPool.Get().(*Map) + defer databaseSQLMapPool.Put(m) + + buf, err := m.Encode(0, TextFormatCode, src, nil) + if err != nil { + return nil, err + } + + return string(buf), nil +} diff --git a/stdlib/sql_test.go b/stdlib/sql_test.go index 7f74cd9fb..7be496d58 100644 --- a/stdlib/sql_test.go +++ b/stdlib/sql_test.go @@ -555,14 +555,12 @@ func TestPGTypeArray(t *testing.T) { }) } -func TestConnQueryScanRange(t *testing.T) { +func TestConnQueryPGTypeRange(t *testing.T) { testWithAllQueryExecModes(t, func(t *testing.T, db *sql.DB) { skipCockroachDB(t, db, "Server does not support int4range") - m := pgtype.NewMap() - var r pgtype.Range[pgtype.Int4] - err := db.QueryRow("select int4range(1, 5)").Scan(m.SQLScanner(&r)) + err := db.QueryRow("select int4range(1, 5)").Scan(&r) require.NoError(t, err) assert.Equal( t, @@ -574,6 +572,15 @@ func TestConnQueryScanRange(t *testing.T) { Valid: true, }, r) + + var equal bool + err = db.QueryRow("select int4range(1, 5) = $1::int4range", r).Scan(&equal) + require.NoError(t, err) + require.Equal(t, true, equal) + + err = db.QueryRow("select null::int4range").Scan(&r) + require.NoError(t, err) + assert.Equal(t, pgtype.Range[pgtype.Int4]{}, r) }) } From 9316da55e50b2a49e5f6d85499a91c0aa91ab78d Mon Sep 17 00:00:00 2001 From: Jack Christensen Date: Sun, 19 May 2024 08:11:05 -0500 Subject: [PATCH 3/5] Add Scan and Value to pgtype.Multirange[T] --- pgtype/multirange.go | 47 ++++++++++++++++++++++++++++++++++++++++++++ stdlib/sql_test.go | 38 +++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/pgtype/multirange.go b/pgtype/multirange.go index e57637880..4ba71b1c1 100644 --- a/pgtype/multirange.go +++ b/pgtype/multirange.go @@ -441,3 +441,50 @@ func (r Multirange[T]) ScanIndex(i int) any { func (r Multirange[T]) ScanIndexType() any { return new(T) } + +// Scan implements the database/sql Scanner interface. +// +// Range needs a *Map to decode the range elements. Unfortunately, the the Go type system does not support attaching +// data to a type and the Scanner interface does not have an argument that can be used to pass extra data to the Scan +// method. To work around this limitation, Scan uses a default *Map. This means Scan may only work properly with types +// that are supported by a default *Map. from a Map or Codec. +func (r *Multirange[T]) Scan(v any) error { + if v == nil { + *r = nil + return nil + } + + m := databaseSQLMapPool.Get().(*Map) + defer databaseSQLMapPool.Put(m) + + switch v := v.(type) { + case string: + return m.Scan(0, 0, []byte(v), r) + case []byte: + return m.Scan(0, 0, v, r) + default: + return fmt.Errorf("unsupported type: %T", v) + } +} + +// Value implements the database/sql/driver Valuer interface. +// +// Range needs a *Map to encode the range elements. Unfortunately, the the Go type system does not support attaching +// data to a type and the Valuer interface does not have an argument that can be used to pass extra data to the Value +// method. To work around this limitation, Value uses a default *Map. This means Value may only work properly with types +// that are supported by a default *Map. from a Map or Codec. +func (src Multirange[T]) Value() (driver.Value, error) { + if src == nil { + return nil, nil + } + + m := databaseSQLMapPool.Get().(*Map) + defer databaseSQLMapPool.Put(m) + + buf, err := m.Encode(0, TextFormatCode, src, nil) + if err != nil { + return nil, err + } + + return string(buf), nil +} diff --git a/stdlib/sql_test.go b/stdlib/sql_test.go index 7be496d58..29fb40f55 100644 --- a/stdlib/sql_test.go +++ b/stdlib/sql_test.go @@ -584,6 +584,44 @@ func TestConnQueryPGTypeRange(t *testing.T) { }) } +func TestConnQueryPGTypeMultirange(t *testing.T) { + testWithAllQueryExecModes(t, func(t *testing.T, db *sql.DB) { + skipCockroachDB(t, db, "Server does not support int4range") + + var r pgtype.Multirange[pgtype.Range[pgtype.Int4]] + err := db.QueryRow("select int4multirange(int4range(1, 5), int4range(7,9))").Scan(&r) + require.NoError(t, err) + assert.Equal( + t, + pgtype.Multirange[pgtype.Range[pgtype.Int4]]{ + { + Lower: pgtype.Int4{Int32: 1, Valid: true}, + Upper: pgtype.Int4{Int32: 5, Valid: true}, + LowerType: pgtype.Inclusive, + UpperType: pgtype.Exclusive, + Valid: true, + }, + { + Lower: pgtype.Int4{Int32: 7, Valid: true}, + Upper: pgtype.Int4{Int32: 9, Valid: true}, + LowerType: pgtype.Inclusive, + UpperType: pgtype.Exclusive, + Valid: true, + }, + }, + r) + + var equal bool + err = db.QueryRow("select int4multirange(int4range(1, 5), int4range(7,9)) = $1::int4multirange", r).Scan(&equal) + require.NoError(t, err) + require.Equal(t, true, equal) + + err = db.QueryRow("select null::int4multirange").Scan(&r) + require.NoError(t, err) + require.Nil(t, r) + }) +} + // Test type that pgx would handle natively in binary, but since it is not a // database/sql native type should be passed through as a string func TestConnQueryRowPgxBinary(t *testing.T) { From b4f61c94a827ba464fe22353afbd745a3cee5124 Mon Sep 17 00:00:00 2001 From: Jack Christensen Date: Sun, 19 May 2024 08:20:28 -0500 Subject: [PATCH 4/5] Skip nested array test on CockroachDB --- stdlib/sql_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/stdlib/sql_test.go b/stdlib/sql_test.go index 29fb40f55..4c397bfd6 100644 --- a/stdlib/sql_test.go +++ b/stdlib/sql_test.go @@ -530,6 +530,8 @@ func TestPGTypeFlatArray(t *testing.T) { func TestPGTypeArray(t *testing.T) { testWithAllQueryExecModes(t, func(t *testing.T, db *sql.DB) { + skipCockroachDB(t, db, "Server does not support nested arrays") + var matrix pgtype.Array[int64] err := db.QueryRow("select '{{1,2,3},{4,5,6}}'::bigint[]").Scan(&matrix) From d0475cab1b92a4329e44626020dbe97d4eeff370 Mon Sep 17 00:00:00 2001 From: Jack Christensen Date: Sun, 19 May 2024 16:54:44 -0500 Subject: [PATCH 5/5] Skip multirange tests on PG < 14 --- stdlib/sql_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/stdlib/sql_test.go b/stdlib/sql_test.go index 4c397bfd6..18733718c 100644 --- a/stdlib/sql_test.go +++ b/stdlib/sql_test.go @@ -589,6 +589,7 @@ func TestConnQueryPGTypeRange(t *testing.T) { func TestConnQueryPGTypeMultirange(t *testing.T) { testWithAllQueryExecModes(t, func(t *testing.T, db *sql.DB) { skipCockroachDB(t, db, "Server does not support int4range") + skipPostgreSQLVersionLessThan(t, db, 14) var r pgtype.Multirange[pgtype.Range[pgtype.Int4]] err := db.QueryRow("select int4multirange(int4range(1, 5), int4range(7,9))").Scan(&r)