Build / rewrite / port multirange support
This commit is contained in:
@@ -0,0 +1,443 @@
|
|||||||
|
package pgtype
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"database/sql/driver"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/internal/pgio"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MultirangeGetter is a type that can be converted into a PostgreSQL multirange.
|
||||||
|
type MultirangeGetter interface {
|
||||||
|
// IsNull returns true if the value is SQL NULL.
|
||||||
|
IsNull() bool
|
||||||
|
|
||||||
|
// Len returns the number of elements in the multirange.
|
||||||
|
Len() int
|
||||||
|
|
||||||
|
// Index returns the element at i.
|
||||||
|
Index(i int) any
|
||||||
|
|
||||||
|
// IndexType returns a non-nil scan target of the type Index will return. This is used by MultirangeCodec.PlanEncode.
|
||||||
|
IndexType() any
|
||||||
|
}
|
||||||
|
|
||||||
|
// MultirangeSetter is a type can be set from a PostgreSQL multirange.
|
||||||
|
type MultirangeSetter interface {
|
||||||
|
// ScanNull sets the value to SQL NULL.
|
||||||
|
ScanNull() error
|
||||||
|
|
||||||
|
// SetLen prepares the value such that ScanIndex can be called for each element. This will remove any existing
|
||||||
|
// elements.
|
||||||
|
SetLen(n int) error
|
||||||
|
|
||||||
|
// ScanIndex returns a value usable as a scan target for i. SetLen must be called before ScanIndex.
|
||||||
|
ScanIndex(i int) any
|
||||||
|
|
||||||
|
// ScanIndexType returns a non-nil scan target of the type ScanIndex will return. This is used by
|
||||||
|
// MultirangeCodec.PlanScan.
|
||||||
|
ScanIndexType() any
|
||||||
|
}
|
||||||
|
|
||||||
|
// MultirangeCodec is a codec for any multirange type.
|
||||||
|
type MultirangeCodec struct {
|
||||||
|
ElementType *Type
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MultirangeCodec) FormatSupported(format int16) bool {
|
||||||
|
return c.ElementType.Codec.FormatSupported(format)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MultirangeCodec) PreferredFormat() int16 {
|
||||||
|
return c.ElementType.Codec.PreferredFormat()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MultirangeCodec) PlanEncode(m *Map, oid uint32, format int16, value any) EncodePlan {
|
||||||
|
multirangeValuer, ok := value.(MultirangeGetter)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
elementType := multirangeValuer.IndexType()
|
||||||
|
|
||||||
|
elementEncodePlan := m.PlanEncode(c.ElementType.OID, format, elementType)
|
||||||
|
if elementEncodePlan == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch format {
|
||||||
|
case BinaryFormatCode:
|
||||||
|
return &encodePlanMultirangeCodecBinary{ac: c, m: m, oid: oid}
|
||||||
|
case TextFormatCode:
|
||||||
|
return &encodePlanMultirangeCodecText{ac: c, m: m, oid: oid}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type encodePlanMultirangeCodecText struct {
|
||||||
|
ac *MultirangeCodec
|
||||||
|
m *Map
|
||||||
|
oid uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *encodePlanMultirangeCodecText) Encode(value any, buf []byte) (newBuf []byte, err error) {
|
||||||
|
multirange := value.(MultirangeGetter)
|
||||||
|
|
||||||
|
if multirange.IsNull() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
elementCount := multirange.Len()
|
||||||
|
|
||||||
|
buf = append(buf, '{')
|
||||||
|
|
||||||
|
var encodePlan EncodePlan
|
||||||
|
var lastElemType reflect.Type
|
||||||
|
inElemBuf := make([]byte, 0, 32)
|
||||||
|
for i := 0; i < elementCount; i++ {
|
||||||
|
if i > 0 {
|
||||||
|
buf = append(buf, ',')
|
||||||
|
}
|
||||||
|
|
||||||
|
elem := multirange.Index(i)
|
||||||
|
var elemBuf []byte
|
||||||
|
if elem != nil {
|
||||||
|
elemType := reflect.TypeOf(elem)
|
||||||
|
if lastElemType != elemType {
|
||||||
|
lastElemType = elemType
|
||||||
|
encodePlan = p.m.PlanEncode(p.ac.ElementType.OID, TextFormatCode, elem)
|
||||||
|
if encodePlan == nil {
|
||||||
|
return nil, fmt.Errorf("unable to encode %v", multirange.Index(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elemBuf, err = encodePlan.Encode(elem, inElemBuf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if elemBuf == nil {
|
||||||
|
return nil, fmt.Errorf("multirange cannot contain NULL element")
|
||||||
|
} else {
|
||||||
|
buf = append(buf, elemBuf...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = append(buf, '}')
|
||||||
|
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type encodePlanMultirangeCodecBinary struct {
|
||||||
|
ac *MultirangeCodec
|
||||||
|
m *Map
|
||||||
|
oid uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *encodePlanMultirangeCodecBinary) Encode(value any, buf []byte) (newBuf []byte, err error) {
|
||||||
|
multirange := value.(MultirangeGetter)
|
||||||
|
|
||||||
|
if multirange.IsNull() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
elementCount := multirange.Len()
|
||||||
|
|
||||||
|
buf = pgio.AppendInt32(buf, int32(elementCount))
|
||||||
|
|
||||||
|
var encodePlan EncodePlan
|
||||||
|
var lastElemType reflect.Type
|
||||||
|
for i := 0; i < elementCount; i++ {
|
||||||
|
sp := len(buf)
|
||||||
|
buf = pgio.AppendInt32(buf, -1)
|
||||||
|
|
||||||
|
elem := multirange.Index(i)
|
||||||
|
var elemBuf []byte
|
||||||
|
if elem != nil {
|
||||||
|
elemType := reflect.TypeOf(elem)
|
||||||
|
if lastElemType != elemType {
|
||||||
|
lastElemType = elemType
|
||||||
|
encodePlan = p.m.PlanEncode(p.ac.ElementType.OID, BinaryFormatCode, elem)
|
||||||
|
if encodePlan == nil {
|
||||||
|
return nil, fmt.Errorf("unable to encode %v", multirange.Index(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elemBuf, err = encodePlan.Encode(elem, buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if elemBuf == nil {
|
||||||
|
return nil, fmt.Errorf("multirange cannot contain NULL element")
|
||||||
|
} else {
|
||||||
|
buf = elemBuf
|
||||||
|
pgio.SetInt32(buf[sp:], int32(len(buf[sp:])-4))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MultirangeCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan {
|
||||||
|
multirangeScanner, ok := target.(MultirangeSetter)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
elementType := multirangeScanner.ScanIndexType()
|
||||||
|
|
||||||
|
elementScanPlan := m.PlanScan(c.ElementType.OID, format, elementType)
|
||||||
|
if _, ok := elementScanPlan.(*scanPlanFail); ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &scanPlanMultirangeCodec{
|
||||||
|
multirangeCodec: c,
|
||||||
|
m: m,
|
||||||
|
oid: oid,
|
||||||
|
formatCode: format,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MultirangeCodec) decodeBinary(m *Map, multirangeOID uint32, src []byte, multirange MultirangeSetter) error {
|
||||||
|
rp := 0
|
||||||
|
|
||||||
|
elementCount := int(binary.BigEndian.Uint32(src[rp:]))
|
||||||
|
rp += 4
|
||||||
|
|
||||||
|
err := multirange.SetLen(elementCount)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if elementCount == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
elementScanPlan := c.ElementType.Codec.PlanScan(m, c.ElementType.OID, BinaryFormatCode, multirange.ScanIndex(0))
|
||||||
|
if elementScanPlan == nil {
|
||||||
|
elementScanPlan = m.PlanScan(c.ElementType.OID, BinaryFormatCode, multirange.ScanIndex(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < elementCount; i++ {
|
||||||
|
elem := multirange.ScanIndex(i)
|
||||||
|
elemLen := int(int32(binary.BigEndian.Uint32(src[rp:])))
|
||||||
|
rp += 4
|
||||||
|
var elemSrc []byte
|
||||||
|
if elemLen >= 0 {
|
||||||
|
elemSrc = src[rp : rp+elemLen]
|
||||||
|
rp += elemLen
|
||||||
|
}
|
||||||
|
err = elementScanPlan.Scan(elemSrc, elem)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to scan multirange element %d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MultirangeCodec) decodeText(m *Map, multirangeOID uint32, src []byte, multirange MultirangeSetter) error {
|
||||||
|
elements, err := parseUntypedTextMultirange(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = multirange.SetLen(len(elements))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(elements) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
elementScanPlan := c.ElementType.Codec.PlanScan(m, c.ElementType.OID, TextFormatCode, multirange.ScanIndex(0))
|
||||||
|
if elementScanPlan == nil {
|
||||||
|
elementScanPlan = m.PlanScan(c.ElementType.OID, TextFormatCode, multirange.ScanIndex(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, s := range elements {
|
||||||
|
elem := multirange.ScanIndex(i)
|
||||||
|
err = elementScanPlan.Scan([]byte(s), elem)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type scanPlanMultirangeCodec struct {
|
||||||
|
multirangeCodec *MultirangeCodec
|
||||||
|
m *Map
|
||||||
|
oid uint32
|
||||||
|
formatCode int16
|
||||||
|
elementScanPlan ScanPlan
|
||||||
|
}
|
||||||
|
|
||||||
|
func (spac *scanPlanMultirangeCodec) Scan(src []byte, dst any) error {
|
||||||
|
c := spac.multirangeCodec
|
||||||
|
m := spac.m
|
||||||
|
oid := spac.oid
|
||||||
|
formatCode := spac.formatCode
|
||||||
|
|
||||||
|
multirange := dst.(MultirangeSetter)
|
||||||
|
|
||||||
|
if src == nil {
|
||||||
|
return multirange.ScanNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch formatCode {
|
||||||
|
case BinaryFormatCode:
|
||||||
|
return c.decodeBinary(m, oid, src, multirange)
|
||||||
|
case TextFormatCode:
|
||||||
|
return c.decodeText(m, oid, src, multirange)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown format code %d", formatCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MultirangeCodec) DecodeDatabaseSQLValue(m *Map, oid uint32, format int16, src []byte) (driver.Value, error) {
|
||||||
|
if src == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch format {
|
||||||
|
case TextFormatCode:
|
||||||
|
return string(src), nil
|
||||||
|
case BinaryFormatCode:
|
||||||
|
buf := make([]byte, len(src))
|
||||||
|
copy(buf, src)
|
||||||
|
return buf, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown format code %d", format)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MultirangeCodec) DecodeValue(m *Map, oid uint32, format int16, src []byte) (any, error) {
|
||||||
|
if src == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var multirange Multirange[Range[any]]
|
||||||
|
err := m.PlanScan(oid, format, &multirange).Scan(src, &multirange)
|
||||||
|
return multirange, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseUntypedTextMultirange(src []byte) ([]string, error) {
|
||||||
|
elements := make([]string, 0)
|
||||||
|
|
||||||
|
buf := bytes.NewBuffer(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)
|
||||||
|
}
|
||||||
|
elements = append(elements, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
skipWhitespace(buf)
|
||||||
|
|
||||||
|
if buf.Len() > 0 {
|
||||||
|
return nil, fmt.Errorf("unexpected trailing data: %v", buf.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return elements, 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multirange is a generic multirange type.
|
||||||
|
//
|
||||||
|
// T should implement RangeValuer and *T should implement RangeScanner. However, there does not appear to be a way to
|
||||||
|
// enforce the RangeScanner constraint.
|
||||||
|
type Multirange[T RangeValuer] []T
|
||||||
|
|
||||||
|
func (r Multirange[T]) IsNull() bool {
|
||||||
|
return r == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Multirange[T]) Len() int {
|
||||||
|
return len(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Multirange[T]) Index(i int) any {
|
||||||
|
return r[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Multirange[T]) IndexType() any {
|
||||||
|
var zero T
|
||||||
|
return zero
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Multirange[T]) ScanNull() error {
|
||||||
|
*r = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Multirange[T]) SetLen(n int) error {
|
||||||
|
*r = make([]T, n)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Multirange[T]) ScanIndex(i int) any {
|
||||||
|
return &r[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Multirange[T]) ScanIndexType() any {
|
||||||
|
return new(T)
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
package pgtype_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
pgx "github.com/jackc/pgx/v5"
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
"github.com/jackc/pgx/v5/pgxtest"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMultirangeCodecTranscode(t *testing.T) {
|
||||||
|
skipCockroachDB(t, "Server does not support range types (see https://github.com/cockroachdb/cockroach/issues/27791)")
|
||||||
|
|
||||||
|
pgxtest.RunValueRoundTripTests(context.Background(), t, defaultConnTestRunner, nil, "int4multirange", []pgxtest.ValueRoundTripTest{
|
||||||
|
{
|
||||||
|
pgtype.Multirange[pgtype.Range[pgtype.Int4]](nil),
|
||||||
|
new(pgtype.Multirange[pgtype.Range[pgtype.Int4]]),
|
||||||
|
func(a any) bool { return reflect.DeepEqual(pgtype.Multirange[pgtype.Range[pgtype.Int4]](nil), a) },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pgtype.Multirange[pgtype.Range[pgtype.Int4]]{},
|
||||||
|
new(pgtype.Multirange[pgtype.Range[pgtype.Int4]]),
|
||||||
|
func(a any) bool { return reflect.DeepEqual(pgtype.Multirange[pgtype.Range[pgtype.Int4]]{}, a) },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
new(pgtype.Multirange[pgtype.Range[pgtype.Int4]]),
|
||||||
|
func(a any) bool {
|
||||||
|
return reflect.DeepEqual(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,
|
||||||
|
},
|
||||||
|
}, a)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultirangeCodecDecodeValue(t *testing.T) {
|
||||||
|
skipCockroachDB(t, "Server does not support range types (see https://github.com/cockroachdb/cockroach/issues/27791)")
|
||||||
|
|
||||||
|
defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, _ testing.TB, conn *pgx.Conn) {
|
||||||
|
|
||||||
|
for _, tt := range []struct {
|
||||||
|
sql string
|
||||||
|
expected any
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
sql: `select int4multirange(int4range(1, 5), int4range(7,9))`,
|
||||||
|
expected: pgtype.Multirange[pgtype.Range[any]]{
|
||||||
|
{
|
||||||
|
Lower: int32(1),
|
||||||
|
Upper: int32(5),
|
||||||
|
LowerType: pgtype.Inclusive,
|
||||||
|
UpperType: pgtype.Exclusive,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Lower: int32(7),
|
||||||
|
Upper: int32(9),
|
||||||
|
LowerType: pgtype.Inclusive,
|
||||||
|
UpperType: pgtype.Exclusive,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tt.sql, func(t *testing.T) {
|
||||||
|
rows, err := conn.Query(ctx, tt.sql)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
values, err := rows.Values()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, values, 1)
|
||||||
|
require.Equal(t, tt.expected, values[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, rows.Err())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
+122
-92
@@ -12,97 +12,109 @@ import (
|
|||||||
|
|
||||||
// PostgreSQL oids for common types
|
// PostgreSQL oids for common types
|
||||||
const (
|
const (
|
||||||
BoolOID = 16
|
BoolOID = 16
|
||||||
ByteaOID = 17
|
ByteaOID = 17
|
||||||
QCharOID = 18
|
QCharOID = 18
|
||||||
NameOID = 19
|
NameOID = 19
|
||||||
Int8OID = 20
|
Int8OID = 20
|
||||||
Int2OID = 21
|
Int2OID = 21
|
||||||
Int4OID = 23
|
Int4OID = 23
|
||||||
TextOID = 25
|
TextOID = 25
|
||||||
OIDOID = 26
|
OIDOID = 26
|
||||||
TIDOID = 27
|
TIDOID = 27
|
||||||
XIDOID = 28
|
XIDOID = 28
|
||||||
CIDOID = 29
|
CIDOID = 29
|
||||||
JSONOID = 114
|
JSONOID = 114
|
||||||
JSONArrayOID = 199
|
JSONArrayOID = 199
|
||||||
PointOID = 600
|
PointOID = 600
|
||||||
LsegOID = 601
|
LsegOID = 601
|
||||||
PathOID = 602
|
PathOID = 602
|
||||||
BoxOID = 603
|
BoxOID = 603
|
||||||
PolygonOID = 604
|
PolygonOID = 604
|
||||||
LineOID = 628
|
LineOID = 628
|
||||||
LineArrayOID = 629
|
LineArrayOID = 629
|
||||||
CIDROID = 650
|
CIDROID = 650
|
||||||
CIDRArrayOID = 651
|
CIDRArrayOID = 651
|
||||||
Float4OID = 700
|
Float4OID = 700
|
||||||
Float8OID = 701
|
Float8OID = 701
|
||||||
CircleOID = 718
|
CircleOID = 718
|
||||||
CircleArrayOID = 719
|
CircleArrayOID = 719
|
||||||
UnknownOID = 705
|
UnknownOID = 705
|
||||||
MacaddrOID = 829
|
MacaddrOID = 829
|
||||||
InetOID = 869
|
InetOID = 869
|
||||||
BoolArrayOID = 1000
|
BoolArrayOID = 1000
|
||||||
QCharArrayOID = 1003
|
QCharArrayOID = 1003
|
||||||
NameArrayOID = 1003
|
NameArrayOID = 1003
|
||||||
Int2ArrayOID = 1005
|
Int2ArrayOID = 1005
|
||||||
Int4ArrayOID = 1007
|
Int4ArrayOID = 1007
|
||||||
TextArrayOID = 1009
|
TextArrayOID = 1009
|
||||||
TIDArrayOID = 1010
|
TIDArrayOID = 1010
|
||||||
ByteaArrayOID = 1001
|
ByteaArrayOID = 1001
|
||||||
XIDArrayOID = 1011
|
XIDArrayOID = 1011
|
||||||
CIDArrayOID = 1012
|
CIDArrayOID = 1012
|
||||||
BPCharArrayOID = 1014
|
BPCharArrayOID = 1014
|
||||||
VarcharArrayOID = 1015
|
VarcharArrayOID = 1015
|
||||||
Int8ArrayOID = 1016
|
Int8ArrayOID = 1016
|
||||||
PointArrayOID = 1017
|
PointArrayOID = 1017
|
||||||
LsegArrayOID = 1018
|
LsegArrayOID = 1018
|
||||||
PathArrayOID = 1019
|
PathArrayOID = 1019
|
||||||
BoxArrayOID = 1020
|
BoxArrayOID = 1020
|
||||||
Float4ArrayOID = 1021
|
Float4ArrayOID = 1021
|
||||||
Float8ArrayOID = 1022
|
Float8ArrayOID = 1022
|
||||||
PolygonArrayOID = 1027
|
PolygonArrayOID = 1027
|
||||||
OIDArrayOID = 1028
|
OIDArrayOID = 1028
|
||||||
ACLItemOID = 1033
|
ACLItemOID = 1033
|
||||||
ACLItemArrayOID = 1034
|
ACLItemArrayOID = 1034
|
||||||
MacaddrArrayOID = 1040
|
MacaddrArrayOID = 1040
|
||||||
InetArrayOID = 1041
|
InetArrayOID = 1041
|
||||||
BPCharOID = 1042
|
BPCharOID = 1042
|
||||||
VarcharOID = 1043
|
VarcharOID = 1043
|
||||||
DateOID = 1082
|
DateOID = 1082
|
||||||
TimeOID = 1083
|
TimeOID = 1083
|
||||||
TimestampOID = 1114
|
TimestampOID = 1114
|
||||||
TimestampArrayOID = 1115
|
TimestampArrayOID = 1115
|
||||||
DateArrayOID = 1182
|
DateArrayOID = 1182
|
||||||
TimeArrayOID = 1183
|
TimeArrayOID = 1183
|
||||||
TimestamptzOID = 1184
|
TimestamptzOID = 1184
|
||||||
TimestamptzArrayOID = 1185
|
TimestamptzArrayOID = 1185
|
||||||
IntervalOID = 1186
|
IntervalOID = 1186
|
||||||
IntervalArrayOID = 1187
|
IntervalArrayOID = 1187
|
||||||
NumericArrayOID = 1231
|
NumericArrayOID = 1231
|
||||||
BitOID = 1560
|
BitOID = 1560
|
||||||
BitArrayOID = 1561
|
BitArrayOID = 1561
|
||||||
VarbitOID = 1562
|
VarbitOID = 1562
|
||||||
VarbitArrayOID = 1563
|
VarbitArrayOID = 1563
|
||||||
NumericOID = 1700
|
NumericOID = 1700
|
||||||
RecordOID = 2249
|
RecordOID = 2249
|
||||||
RecordArrayOID = 2287
|
RecordArrayOID = 2287
|
||||||
UUIDOID = 2950
|
UUIDOID = 2950
|
||||||
UUIDArrayOID = 2951
|
UUIDArrayOID = 2951
|
||||||
JSONBOID = 3802
|
JSONBOID = 3802
|
||||||
JSONBArrayOID = 3807
|
JSONBArrayOID = 3807
|
||||||
DaterangeOID = 3912
|
DaterangeOID = 3912
|
||||||
DaterangeArrayOID = 3913
|
DaterangeArrayOID = 3913
|
||||||
Int4rangeOID = 3904
|
Int4rangeOID = 3904
|
||||||
Int4rangeArrayOID = 3905
|
Int4rangeArrayOID = 3905
|
||||||
NumrangeOID = 3906
|
NumrangeOID = 3906
|
||||||
NumrangeArrayOID = 3907
|
NumrangeArrayOID = 3907
|
||||||
TsrangeOID = 3908
|
TsrangeOID = 3908
|
||||||
TsrangeArrayOID = 3909
|
TsrangeArrayOID = 3909
|
||||||
TstzrangeOID = 3910
|
TstzrangeOID = 3910
|
||||||
TstzrangeArrayOID = 3911
|
TstzrangeArrayOID = 3911
|
||||||
Int8rangeOID = 3926
|
Int8rangeOID = 3926
|
||||||
Int8rangeArrayOID = 3927
|
Int8rangeArrayOID = 3927
|
||||||
|
Int4multirangeOID = 4451
|
||||||
|
NummultirangeOID = 4532
|
||||||
|
TsmultirangeOID = 4533
|
||||||
|
TstzmultirangeOID = 4534
|
||||||
|
DatemultirangeOID = 4535
|
||||||
|
Int8multirangeOID = 4536
|
||||||
|
Int4multirangeArrayOID = 6150
|
||||||
|
NummultirangeArrayOID = 6151
|
||||||
|
TsmultirangeArrayOID = 6152
|
||||||
|
TstzmultirangeArrayOID = 6153
|
||||||
|
DatemultirangeArrayOID = 6155
|
||||||
|
Int8multirangeArrayOID = 6157
|
||||||
)
|
)
|
||||||
|
|
||||||
type InfinityModifier int8
|
type InfinityModifier int8
|
||||||
@@ -222,6 +234,7 @@ func NewMap() *Map {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Base types
|
||||||
m.RegisterType(&Type{Name: "aclitem", OID: ACLItemOID, Codec: &TextFormatOnlyCodec{TextCodec{}}})
|
m.RegisterType(&Type{Name: "aclitem", OID: ACLItemOID, Codec: &TextFormatOnlyCodec{TextCodec{}}})
|
||||||
m.RegisterType(&Type{Name: "bit", OID: BitOID, Codec: BitsCodec{}})
|
m.RegisterType(&Type{Name: "bit", OID: BitOID, Codec: BitsCodec{}})
|
||||||
m.RegisterType(&Type{Name: "bool", OID: BoolOID, Codec: BoolCodec{}})
|
m.RegisterType(&Type{Name: "bool", OID: BoolOID, Codec: BoolCodec{}})
|
||||||
@@ -263,6 +276,7 @@ func NewMap() *Map {
|
|||||||
m.RegisterType(&Type{Name: "varchar", OID: VarcharOID, Codec: TextCodec{}})
|
m.RegisterType(&Type{Name: "varchar", OID: VarcharOID, Codec: TextCodec{}})
|
||||||
m.RegisterType(&Type{Name: "xid", OID: XIDOID, Codec: Uint32Codec{}})
|
m.RegisterType(&Type{Name: "xid", OID: XIDOID, Codec: Uint32Codec{}})
|
||||||
|
|
||||||
|
// Range types
|
||||||
m.RegisterType(&Type{Name: "daterange", OID: DaterangeOID, Codec: &RangeCodec{ElementType: m.oidToType[DateOID]}})
|
m.RegisterType(&Type{Name: "daterange", OID: DaterangeOID, Codec: &RangeCodec{ElementType: m.oidToType[DateOID]}})
|
||||||
m.RegisterType(&Type{Name: "int4range", OID: Int4rangeOID, Codec: &RangeCodec{ElementType: m.oidToType[Int4OID]}})
|
m.RegisterType(&Type{Name: "int4range", OID: Int4rangeOID, Codec: &RangeCodec{ElementType: m.oidToType[Int4OID]}})
|
||||||
m.RegisterType(&Type{Name: "int8range", OID: Int8rangeOID, Codec: &RangeCodec{ElementType: m.oidToType[Int8OID]}})
|
m.RegisterType(&Type{Name: "int8range", OID: Int8rangeOID, Codec: &RangeCodec{ElementType: m.oidToType[Int8OID]}})
|
||||||
@@ -270,6 +284,15 @@ func NewMap() *Map {
|
|||||||
m.RegisterType(&Type{Name: "tsrange", OID: TsrangeOID, Codec: &RangeCodec{ElementType: m.oidToType[TimestampOID]}})
|
m.RegisterType(&Type{Name: "tsrange", OID: TsrangeOID, Codec: &RangeCodec{ElementType: m.oidToType[TimestampOID]}})
|
||||||
m.RegisterType(&Type{Name: "tstzrange", OID: TstzrangeOID, Codec: &RangeCodec{ElementType: m.oidToType[TimestamptzOID]}})
|
m.RegisterType(&Type{Name: "tstzrange", OID: TstzrangeOID, Codec: &RangeCodec{ElementType: m.oidToType[TimestamptzOID]}})
|
||||||
|
|
||||||
|
// Multirange types
|
||||||
|
m.RegisterType(&Type{Name: "datemultirange", OID: DatemultirangeOID, Codec: &MultirangeCodec{ElementType: m.oidToType[DaterangeOID]}})
|
||||||
|
m.RegisterType(&Type{Name: "int4multirange", OID: Int4multirangeOID, Codec: &MultirangeCodec{ElementType: m.oidToType[Int4rangeOID]}})
|
||||||
|
m.RegisterType(&Type{Name: "int8multirange", OID: Int8multirangeOID, Codec: &MultirangeCodec{ElementType: m.oidToType[Int8rangeOID]}})
|
||||||
|
m.RegisterType(&Type{Name: "nummultirange", OID: NummultirangeOID, Codec: &MultirangeCodec{ElementType: m.oidToType[NumrangeOID]}})
|
||||||
|
m.RegisterType(&Type{Name: "tsmultirange", OID: TsmultirangeOID, Codec: &MultirangeCodec{ElementType: m.oidToType[TsrangeOID]}})
|
||||||
|
m.RegisterType(&Type{Name: "tstzmultirange", OID: TstzmultirangeOID, Codec: &MultirangeCodec{ElementType: m.oidToType[TstzrangeOID]}})
|
||||||
|
|
||||||
|
// Array types
|
||||||
m.RegisterType(&Type{Name: "_aclitem", OID: ACLItemArrayOID, Codec: &ArrayCodec{ElementType: m.oidToType[ACLItemOID]}})
|
m.RegisterType(&Type{Name: "_aclitem", OID: ACLItemArrayOID, Codec: &ArrayCodec{ElementType: m.oidToType[ACLItemOID]}})
|
||||||
m.RegisterType(&Type{Name: "_bit", OID: BitArrayOID, Codec: &ArrayCodec{ElementType: m.oidToType[BitOID]}})
|
m.RegisterType(&Type{Name: "_bit", OID: BitArrayOID, Codec: &ArrayCodec{ElementType: m.oidToType[BitOID]}})
|
||||||
m.RegisterType(&Type{Name: "_bool", OID: BoolArrayOID, Codec: &ArrayCodec{ElementType: m.oidToType[BoolOID]}})
|
m.RegisterType(&Type{Name: "_bool", OID: BoolArrayOID, Codec: &ArrayCodec{ElementType: m.oidToType[BoolOID]}})
|
||||||
@@ -349,20 +372,25 @@ func NewMap() *Map {
|
|||||||
registerDefaultPgTypeVariants[Circle](m, "circle")
|
registerDefaultPgTypeVariants[Circle](m, "circle")
|
||||||
registerDefaultPgTypeVariants[Date](m, "date")
|
registerDefaultPgTypeVariants[Date](m, "date")
|
||||||
registerDefaultPgTypeVariants[Range[Date]](m, "daterange")
|
registerDefaultPgTypeVariants[Range[Date]](m, "daterange")
|
||||||
|
registerDefaultPgTypeVariants[Multirange[Range[Date]]](m, "datemultirange")
|
||||||
registerDefaultPgTypeVariants[Float4](m, "float4")
|
registerDefaultPgTypeVariants[Float4](m, "float4")
|
||||||
registerDefaultPgTypeVariants[Float8](m, "float8")
|
registerDefaultPgTypeVariants[Float8](m, "float8")
|
||||||
registerDefaultPgTypeVariants[Range[Float8]](m, "numrange") // There is no PostgreSQL builtin float8range so map it to numrange.
|
registerDefaultPgTypeVariants[Range[Float8]](m, "numrange") // There is no PostgreSQL builtin float8range so map it to numrange.
|
||||||
|
registerDefaultPgTypeVariants[Multirange[Range[Float8]]](m, "nummultirange") // There is no PostgreSQL builtin float8multirange so map it to nummultirange.
|
||||||
registerDefaultPgTypeVariants[Inet](m, "inet")
|
registerDefaultPgTypeVariants[Inet](m, "inet")
|
||||||
registerDefaultPgTypeVariants[Int2](m, "int2")
|
registerDefaultPgTypeVariants[Int2](m, "int2")
|
||||||
registerDefaultPgTypeVariants[Int4](m, "int4")
|
registerDefaultPgTypeVariants[Int4](m, "int4")
|
||||||
registerDefaultPgTypeVariants[Range[Int4]](m, "int4range")
|
registerDefaultPgTypeVariants[Range[Int4]](m, "int4range")
|
||||||
|
registerDefaultPgTypeVariants[Multirange[Range[Int4]]](m, "int4multirange")
|
||||||
registerDefaultPgTypeVariants[Int8](m, "int8")
|
registerDefaultPgTypeVariants[Int8](m, "int8")
|
||||||
registerDefaultPgTypeVariants[Range[Int8]](m, "int8range")
|
registerDefaultPgTypeVariants[Range[Int8]](m, "int8range")
|
||||||
|
registerDefaultPgTypeVariants[Multirange[Range[Int8]]](m, "int8multirange")
|
||||||
registerDefaultPgTypeVariants[Interval](m, "interval")
|
registerDefaultPgTypeVariants[Interval](m, "interval")
|
||||||
registerDefaultPgTypeVariants[Line](m, "line")
|
registerDefaultPgTypeVariants[Line](m, "line")
|
||||||
registerDefaultPgTypeVariants[Lseg](m, "lseg")
|
registerDefaultPgTypeVariants[Lseg](m, "lseg")
|
||||||
registerDefaultPgTypeVariants[Numeric](m, "numeric")
|
registerDefaultPgTypeVariants[Numeric](m, "numeric")
|
||||||
registerDefaultPgTypeVariants[Range[Numeric]](m, "numrange")
|
registerDefaultPgTypeVariants[Range[Numeric]](m, "numrange")
|
||||||
|
registerDefaultPgTypeVariants[Multirange[Range[Numeric]]](m, "nummultirange")
|
||||||
registerDefaultPgTypeVariants[Path](m, "path")
|
registerDefaultPgTypeVariants[Path](m, "path")
|
||||||
registerDefaultPgTypeVariants[Point](m, "point")
|
registerDefaultPgTypeVariants[Point](m, "point")
|
||||||
registerDefaultPgTypeVariants[Polygon](m, "polygon")
|
registerDefaultPgTypeVariants[Polygon](m, "polygon")
|
||||||
@@ -372,7 +400,9 @@ func NewMap() *Map {
|
|||||||
registerDefaultPgTypeVariants[Timestamp](m, "timestamp")
|
registerDefaultPgTypeVariants[Timestamp](m, "timestamp")
|
||||||
registerDefaultPgTypeVariants[Timestamptz](m, "timestamptz")
|
registerDefaultPgTypeVariants[Timestamptz](m, "timestamptz")
|
||||||
registerDefaultPgTypeVariants[Range[Timestamp]](m, "tsrange")
|
registerDefaultPgTypeVariants[Range[Timestamp]](m, "tsrange")
|
||||||
|
registerDefaultPgTypeVariants[Multirange[Range[Timestamp]]](m, "tsmultirange")
|
||||||
registerDefaultPgTypeVariants[Range[Timestamptz]](m, "tstzrange")
|
registerDefaultPgTypeVariants[Range[Timestamptz]](m, "tstzrange")
|
||||||
|
registerDefaultPgTypeVariants[Multirange[Range[Timestamptz]]](m, "tstzmultirange")
|
||||||
registerDefaultPgTypeVariants[UUID](m, "uuid")
|
registerDefaultPgTypeVariants[UUID](m, "uuid")
|
||||||
|
|
||||||
return m
|
return m
|
||||||
|
|||||||
Reference in New Issue
Block a user