mirror of https://github.com/jackc/pgx.git
Add int4range
parent
1a99c0e5c4
commit
7eae904eba
4
conn.go
4
conn.go
|
@ -366,8 +366,8 @@ func (c *Conn) initConnInfo() error {
|
|||
from pg_type t
|
||||
left join pg_type base_type on t.typelem=base_type.oid
|
||||
where (
|
||||
t.typtype in('b', 'p')
|
||||
and (base_type.oid is null or base_type.typtype in('b', 'p'))
|
||||
t.typtype in('b', 'p', 'r')
|
||||
and (base_type.oid is null or base_type.typtype in('b', 'p', 'r'))
|
||||
)`)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -0,0 +1,268 @@
|
|||
package pgtype
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/jackc/pgx/pgio"
|
||||
)
|
||||
|
||||
type Int4range struct {
|
||||
Lower Int4
|
||||
Upper Int4
|
||||
LowerType BoundType
|
||||
UpperType BoundType
|
||||
Status Status
|
||||
}
|
||||
|
||||
func (dst *Int4range) Set(src interface{}) error {
|
||||
return fmt.Errorf("cannot convert %v to Int4range", src)
|
||||
}
|
||||
|
||||
func (dst *Int4range) Get() interface{} {
|
||||
switch dst.Status {
|
||||
case Present:
|
||||
return dst
|
||||
case Null:
|
||||
return nil
|
||||
default:
|
||||
return dst.Status
|
||||
}
|
||||
}
|
||||
|
||||
func (src *Int4range) AssignTo(dst interface{}) error {
|
||||
return fmt.Errorf("cannot assign %v to %T", src, dst)
|
||||
}
|
||||
|
||||
func (dst *Int4range) DecodeText(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Int4range{Status: Null}
|
||||
return nil
|
||||
}
|
||||
|
||||
utr, err := ParseUntypedTextRange(string(src))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*dst = Int4range{Status: Present}
|
||||
|
||||
dst.LowerType = utr.LowerType
|
||||
dst.UpperType = utr.UpperType
|
||||
|
||||
if dst.LowerType == Empty {
|
||||
return nil
|
||||
}
|
||||
|
||||
if dst.LowerType == Inclusive || dst.LowerType == Exclusive {
|
||||
if err := dst.Lower.DecodeText(ci, []byte(utr.Lower)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if dst.UpperType == Inclusive || dst.UpperType == Exclusive {
|
||||
if err := dst.Upper.DecodeText(ci, []byte(utr.Upper)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dst *Int4range) DecodeBinary(ci *ConnInfo, src []byte) error {
|
||||
if src == nil {
|
||||
*dst = Int4range{Status: Null}
|
||||
return nil
|
||||
}
|
||||
|
||||
ubr, err := ParseUntypedBinaryRange(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*dst = Int4range{Status: Present}
|
||||
|
||||
dst.LowerType = ubr.LowerType
|
||||
dst.UpperType = ubr.UpperType
|
||||
|
||||
if dst.LowerType == Empty {
|
||||
return nil
|
||||
}
|
||||
|
||||
if dst.LowerType == Inclusive || dst.LowerType == Exclusive {
|
||||
if err := dst.Lower.DecodeBinary(ci, ubr.Lower); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if dst.UpperType == Inclusive || dst.UpperType == Exclusive {
|
||||
if err := dst.Upper.DecodeBinary(ci, ubr.Upper); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src Int4range) EncodeText(ci *ConnInfo, w io.Writer) (bool, error) {
|
||||
switch src.Status {
|
||||
case Null:
|
||||
return true, nil
|
||||
case Undefined:
|
||||
return false, errUndefined
|
||||
}
|
||||
|
||||
switch src.LowerType {
|
||||
case Exclusive, Unbounded:
|
||||
if err := pgio.WriteByte(w, '('); err != nil {
|
||||
return false, err
|
||||
}
|
||||
case Inclusive:
|
||||
if err := pgio.WriteByte(w, '['); err != nil {
|
||||
return false, err
|
||||
}
|
||||
case Empty:
|
||||
_, err := io.WriteString(w, "empty")
|
||||
return false, err
|
||||
default:
|
||||
return false, fmt.Errorf("unknown lower bound type %v", src.LowerType)
|
||||
}
|
||||
|
||||
if src.LowerType != Unbounded {
|
||||
if null, err := src.Lower.EncodeText(ci, w); err != nil {
|
||||
return false, err
|
||||
} else if null {
|
||||
return false, fmt.Errorf("Lower cannot be null unless LowerType is Unbounded")
|
||||
}
|
||||
}
|
||||
|
||||
if err := pgio.WriteByte(w, ','); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if src.UpperType != Unbounded {
|
||||
if null, err := src.Upper.EncodeText(ci, w); err != nil {
|
||||
return false, err
|
||||
} else if null {
|
||||
return false, fmt.Errorf("Upper cannot be null unless UpperType is Unbounded")
|
||||
}
|
||||
}
|
||||
|
||||
switch src.UpperType {
|
||||
case Exclusive, Unbounded:
|
||||
if err := pgio.WriteByte(w, ')'); err != nil {
|
||||
return false, err
|
||||
}
|
||||
case Inclusive:
|
||||
if err := pgio.WriteByte(w, ']'); err != nil {
|
||||
return false, err
|
||||
}
|
||||
default:
|
||||
return false, fmt.Errorf("unknown upper bound type %v", src.UpperType)
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (src Int4range) EncodeBinary(ci *ConnInfo, w io.Writer) (bool, error) {
|
||||
switch src.Status {
|
||||
case Null:
|
||||
return true, nil
|
||||
case Undefined:
|
||||
return false, errUndefined
|
||||
}
|
||||
|
||||
var rangeType byte
|
||||
switch src.LowerType {
|
||||
case Inclusive:
|
||||
rangeType |= lowerInclusiveMask
|
||||
case Unbounded:
|
||||
rangeType |= lowerUnboundedMask
|
||||
case Exclusive:
|
||||
case Empty:
|
||||
err := pgio.WriteByte(w, emptyMask)
|
||||
return false, err
|
||||
default:
|
||||
return false, fmt.Errorf("unknown LowerType: %v", src.LowerType)
|
||||
}
|
||||
|
||||
switch src.UpperType {
|
||||
case Inclusive:
|
||||
rangeType |= upperInclusiveMask
|
||||
case Unbounded:
|
||||
rangeType |= upperUnboundedMask
|
||||
case Exclusive:
|
||||
default:
|
||||
return false, fmt.Errorf("unknown UpperType: %v", src.UpperType)
|
||||
}
|
||||
|
||||
if err := pgio.WriteByte(w, rangeType); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
valBuf := &bytes.Buffer{}
|
||||
|
||||
if src.LowerType != Unbounded {
|
||||
null, err := src.Lower.EncodeBinary(ci, valBuf)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if null {
|
||||
return false, fmt.Errorf("Lower cannot be null unless LowerType is Unbounded")
|
||||
}
|
||||
|
||||
_, err = pgio.WriteInt32(w, int32(valBuf.Len()))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
_, err = valBuf.WriteTo(w)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
if src.UpperType != Unbounded {
|
||||
null, err := src.Upper.EncodeBinary(ci, valBuf)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if null {
|
||||
return false, fmt.Errorf("Upper cannot be null unless UpperType is Unbounded")
|
||||
}
|
||||
|
||||
_, err = pgio.WriteInt32(w, int32(valBuf.Len()))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
_, err = valBuf.WriteTo(w)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Scan implements the database/sql Scanner interface.
|
||||
func (dst *Int4range) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
*dst = Int4range{Status: Null}
|
||||
return nil
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return dst.DecodeText(nil, []byte(src))
|
||||
case []byte:
|
||||
return dst.DecodeText(nil, src)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot scan %T", src)
|
||||
}
|
||||
|
||||
// Value implements the database/sql/driver Valuer interface.
|
||||
func (src Int4range) Value() (driver.Value, error) {
|
||||
return encodeValueText(src)
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package pgtype_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgx/pgtype"
|
||||
)
|
||||
|
||||
func TestInt4rangeTranscode(t *testing.T) {
|
||||
testSuccessfulTranscode(t, "int4range", []interface{}{
|
||||
pgtype.Int4range{LowerType: pgtype.Empty, UpperType: pgtype.Empty, Status: pgtype.Present},
|
||||
pgtype.Int4range{Lower: pgtype.Int4{Int: 1, Status: pgtype.Present}, Upper: pgtype.Int4{Int: 10, Status: pgtype.Present}, LowerType: pgtype.Inclusive, UpperType: pgtype.Exclusive, Status: pgtype.Present},
|
||||
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},
|
||||
pgtype.Int4range{Status: pgtype.Null},
|
||||
})
|
||||
}
|
||||
|
||||
func TestInt4rangeNormalize(t *testing.T) {
|
||||
testSuccessfulNormalize(t, []normalizeTest{
|
||||
{
|
||||
sql: "select int4range(1, 10, '(]')",
|
||||
value: pgtype.Int4range{Lower: pgtype.Int4{Int: 2, Status: pgtype.Present}, Upper: pgtype.Int4{Int: 11, Status: pgtype.Present}, LowerType: pgtype.Inclusive, UpperType: pgtype.Exclusive, Status: pgtype.Present},
|
||||
},
|
||||
})
|
||||
}
|
|
@ -233,6 +233,7 @@ func init() {
|
|||
"inet": &Inet{},
|
||||
"int2": &Int2{},
|
||||
"int4": &Int4{},
|
||||
"int4range": &Int4range{},
|
||||
"int8": &Int8{},
|
||||
"json": &Json{},
|
||||
"jsonb": &Jsonb{},
|
||||
|
|
|
@ -189,6 +189,99 @@ func testDatabaseSQLSuccessfulTranscodeEqFunc(t testing.TB, driverName, pgTypeNa
|
|||
t.Errorf("%v %d: %v", driverName, i, err)
|
||||
}
|
||||
|
||||
if !eqFunc(result.Elem().Interface(), derefV) {
|
||||
t.Errorf("%v %d: expected %v, got %v", driverName, i, derefV, result.Elem().Interface())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type normalizeTest struct {
|
||||
sql string
|
||||
value interface{}
|
||||
}
|
||||
|
||||
func testSuccessfulNormalize(t testing.TB, tests []normalizeTest) {
|
||||
testSuccessfulNormalizeEqFunc(t, tests, func(a, b interface{}) bool {
|
||||
return reflect.DeepEqual(a, b)
|
||||
})
|
||||
}
|
||||
|
||||
func testSuccessfulNormalizeEqFunc(t testing.TB, tests []normalizeTest, eqFunc func(a, b interface{}) bool) {
|
||||
testPgxSuccessfulNormalizeEqFunc(t, tests, eqFunc)
|
||||
for _, driverName := range []string{"github.com/lib/pq", "github.com/jackc/pgx/stdlib"} {
|
||||
testDatabaseSQLSuccessfulNormalizeEqFunc(t, driverName, tests, eqFunc)
|
||||
}
|
||||
}
|
||||
|
||||
func testPgxSuccessfulNormalizeEqFunc(t testing.TB, tests []normalizeTest, eqFunc func(a, b interface{}) bool) {
|
||||
conn := mustConnectPgx(t)
|
||||
defer mustClose(t, conn)
|
||||
|
||||
formats := []struct {
|
||||
name string
|
||||
formatCode int16
|
||||
}{
|
||||
{name: "TextFormat", formatCode: pgx.TextFormatCode},
|
||||
{name: "BinaryFormat", formatCode: pgx.BinaryFormatCode},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
for _, fc := range formats {
|
||||
psName := fmt.Sprintf("test%d", i)
|
||||
ps, err := conn.Prepare(psName, tt.sql)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ps.FieldDescriptions[0].FormatCode = fc.formatCode
|
||||
if forceEncoder(tt.value, fc.formatCode) == nil {
|
||||
t.Logf("Skipping: %#v does not implement %v", tt.value, fc.name)
|
||||
continue
|
||||
}
|
||||
// Derefence value if it is a pointer
|
||||
derefV := tt.value
|
||||
refVal := reflect.ValueOf(tt.value)
|
||||
if refVal.Kind() == reflect.Ptr {
|
||||
derefV = refVal.Elem().Interface()
|
||||
}
|
||||
|
||||
result := reflect.New(reflect.TypeOf(derefV))
|
||||
err = conn.QueryRow(psName).Scan(result.Interface())
|
||||
if err != nil {
|
||||
t.Errorf("%v %d: %v", fc.name, i, err)
|
||||
}
|
||||
|
||||
if !eqFunc(result.Elem().Interface(), derefV) {
|
||||
t.Errorf("%v %d: expected %v, got %v", fc.name, i, derefV, result.Elem().Interface())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testDatabaseSQLSuccessfulNormalizeEqFunc(t testing.TB, driverName string, tests []normalizeTest, eqFunc func(a, b interface{}) bool) {
|
||||
conn := mustConnectDatabaseSQL(t, driverName)
|
||||
defer mustClose(t, conn)
|
||||
|
||||
for i, tt := range tests {
|
||||
ps, err := conn.Prepare(tt.sql)
|
||||
if err != nil {
|
||||
t.Errorf("%d. %v", i, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Derefence value if it is a pointer
|
||||
derefV := tt.value
|
||||
refVal := reflect.ValueOf(tt.value)
|
||||
if refVal.Kind() == reflect.Ptr {
|
||||
derefV = refVal.Elem().Interface()
|
||||
}
|
||||
|
||||
result := reflect.New(reflect.TypeOf(derefV))
|
||||
err = ps.QueryRow().Scan(result.Interface())
|
||||
if err != nil {
|
||||
t.Errorf("%v %d: %v", driverName, i, err)
|
||||
}
|
||||
|
||||
if !eqFunc(result.Elem().Interface(), derefV) {
|
||||
t.Errorf("%v %d: expected %v, got %v", driverName, i, derefV, result.Elem().Interface())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,273 @@
|
|||
package pgtype
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type BoundType byte
|
||||
|
||||
const (
|
||||
Inclusive = BoundType('i')
|
||||
Exclusive = BoundType('e')
|
||||
Unbounded = BoundType('U')
|
||||
Empty = BoundType('E')
|
||||
)
|
||||
|
||||
type UntypedTextRange struct {
|
||||
Lower string
|
||||
Upper string
|
||||
LowerType BoundType
|
||||
UpperType BoundType
|
||||
}
|
||||
|
||||
func ParseUntypedTextRange(src string) (*UntypedTextRange, error) {
|
||||
utr := &UntypedTextRange{}
|
||||
if src == "empty" {
|
||||
utr.LowerType = 'E'
|
||||
utr.UpperType = 'E'
|
||||
return utr, nil
|
||||
}
|
||||
|
||||
buf := bytes.NewBufferString(src)
|
||||
|
||||
skipWhitespace(buf)
|
||||
|
||||
r, _, err := buf.ReadRune()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid lower bound: %v", err)
|
||||
}
|
||||
switch r {
|
||||
case '(':
|
||||
utr.LowerType = Exclusive
|
||||
case '[':
|
||||
utr.LowerType = Inclusive
|
||||
default:
|
||||
return nil, fmt.Errorf("missing lower bound, instead got: %v", string(r))
|
||||
}
|
||||
|
||||
r, _, err = buf.ReadRune()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid lower value: %v", err)
|
||||
}
|
||||
buf.UnreadRune()
|
||||
|
||||
if r == ',' {
|
||||
utr.LowerType = Unbounded
|
||||
} else {
|
||||
utr.Lower, err = rangeParseValue(buf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid lower value: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
r, _, err = buf.ReadRune()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("missing range separator: %v", err)
|
||||
}
|
||||
if r != ',' {
|
||||
return nil, fmt.Errorf("missing range separator: %v", r)
|
||||
}
|
||||
|
||||
r, _, err = buf.ReadRune()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid upper value: %v", err)
|
||||
}
|
||||
buf.UnreadRune()
|
||||
|
||||
if r == ')' || r == ']' {
|
||||
utr.UpperType = Unbounded
|
||||
} else {
|
||||
utr.Upper, err = rangeParseValue(buf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid upper value: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
r, _, err = buf.ReadRune()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("missing upper bound: %v", err)
|
||||
}
|
||||
switch r {
|
||||
case ')':
|
||||
utr.UpperType = Exclusive
|
||||
case ']':
|
||||
utr.UpperType = Inclusive
|
||||
default:
|
||||
return nil, fmt.Errorf("missing upper bound, instead got: %v", string(r))
|
||||
}
|
||||
|
||||
skipWhitespace(buf)
|
||||
|
||||
if buf.Len() > 0 {
|
||||
return nil, fmt.Errorf("unexpected trailing data: %v", buf.String())
|
||||
}
|
||||
|
||||
return utr, nil
|
||||
}
|
||||
|
||||
func rangeParseValue(buf *bytes.Buffer) (string, error) {
|
||||
r, _, err := buf.ReadRune()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if r == '"' {
|
||||
return rangeParseQuotedValue(buf)
|
||||
}
|
||||
buf.UnreadRune()
|
||||
|
||||
s := &bytes.Buffer{}
|
||||
|
||||
for {
|
||||
r, _, err := buf.ReadRune()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
switch r {
|
||||
case '\\':
|
||||
r, _, err = buf.ReadRune()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
case ',', '[', ']', '(', ')':
|
||||
buf.UnreadRune()
|
||||
return s.String(), nil
|
||||
}
|
||||
|
||||
s.WriteRune(r)
|
||||
}
|
||||
}
|
||||
|
||||
func rangeParseQuotedValue(buf *bytes.Buffer) (string, error) {
|
||||
s := &bytes.Buffer{}
|
||||
|
||||
for {
|
||||
r, _, err := buf.ReadRune()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
switch r {
|
||||
case '\\':
|
||||
r, _, err = buf.ReadRune()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
case '"':
|
||||
r, _, err = buf.ReadRune()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if r != '"' {
|
||||
buf.UnreadRune()
|
||||
return s.String(), nil
|
||||
}
|
||||
}
|
||||
s.WriteRune(r)
|
||||
}
|
||||
}
|
||||
|
||||
type UntypedBinaryRange struct {
|
||||
Lower []byte
|
||||
Upper []byte
|
||||
LowerType BoundType
|
||||
UpperType BoundType
|
||||
}
|
||||
|
||||
// 0 = () = 00000
|
||||
// 1 = empty = 00001
|
||||
// 2 = [) = 00010
|
||||
// 4 = (] = 00100
|
||||
// 6 = [] = 00110
|
||||
// 8 = ) = 01000
|
||||
// 12 = ] = 01100
|
||||
// 16 = ( = 10000
|
||||
// 18 = [ = 10010
|
||||
// 24 = = 11000
|
||||
|
||||
const emptyMask = 1
|
||||
const lowerInclusiveMask = 2
|
||||
const upperInclusiveMask = 4
|
||||
const lowerUnboundedMask = 8
|
||||
const upperUnboundedMask = 16
|
||||
|
||||
func ParseUntypedBinaryRange(src []byte) (*UntypedBinaryRange, error) {
|
||||
ubr := &UntypedBinaryRange{}
|
||||
|
||||
if len(src) == 0 {
|
||||
return nil, fmt.Errorf("range too short: %v", len(src))
|
||||
}
|
||||
|
||||
rangeType := src[0]
|
||||
rp := 1
|
||||
|
||||
if rangeType&emptyMask > 0 {
|
||||
if len(src[rp:]) > 0 {
|
||||
return nil, fmt.Errorf("unexpected trailing bytes parsing empty range: %v", len(src[rp:]))
|
||||
}
|
||||
ubr.LowerType = Empty
|
||||
ubr.UpperType = Empty
|
||||
return ubr, nil
|
||||
}
|
||||
|
||||
if rangeType&lowerInclusiveMask > 0 {
|
||||
ubr.LowerType = Inclusive
|
||||
} else if rangeType&lowerUnboundedMask > 0 {
|
||||
ubr.LowerType = Unbounded
|
||||
} else {
|
||||
ubr.LowerType = Exclusive
|
||||
}
|
||||
|
||||
if rangeType&upperInclusiveMask > 0 {
|
||||
ubr.UpperType = Inclusive
|
||||
} else if rangeType&upperUnboundedMask > 0 {
|
||||
ubr.UpperType = Unbounded
|
||||
} else {
|
||||
ubr.UpperType = Exclusive
|
||||
}
|
||||
|
||||
if ubr.LowerType == Unbounded && ubr.UpperType == Unbounded {
|
||||
if len(src[rp:]) > 0 {
|
||||
return nil, fmt.Errorf("unexpected trailing bytes parsing unbounded range: %v", len(src[rp:]))
|
||||
}
|
||||
return ubr, nil
|
||||
}
|
||||
|
||||
if len(src[rp:]) < 4 {
|
||||
return nil, fmt.Errorf("too few bytes for size: %v", src[rp:])
|
||||
}
|
||||
valueLen := int(binary.BigEndian.Uint32(src[rp:]))
|
||||
rp += 4
|
||||
|
||||
val := src[rp : rp+valueLen]
|
||||
rp += valueLen
|
||||
|
||||
if ubr.LowerType != Unbounded {
|
||||
ubr.Lower = val
|
||||
} else {
|
||||
ubr.Upper = val
|
||||
if len(src[rp:]) > 0 {
|
||||
return nil, fmt.Errorf("unexpected trailing bytes parsing range: %v", len(src[rp:]))
|
||||
}
|
||||
return ubr, nil
|
||||
}
|
||||
|
||||
if ubr.UpperType != Unbounded {
|
||||
if len(src[rp:]) < 4 {
|
||||
return nil, fmt.Errorf("too few bytes for size: %v", src[rp:])
|
||||
}
|
||||
valueLen := int(binary.BigEndian.Uint32(src[rp:]))
|
||||
rp += 4
|
||||
ubr.Upper = src[rp : rp+valueLen]
|
||||
rp += valueLen
|
||||
}
|
||||
|
||||
if len(src[rp:]) > 0 {
|
||||
return nil, fmt.Errorf("unexpected trailing bytes parsing range: %v", len(src[rp:]))
|
||||
}
|
||||
|
||||
return ubr, nil
|
||||
|
||||
}
|
|
@ -0,0 +1,177 @@
|
|||
package pgtype
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseUntypedTextRange(t *testing.T) {
|
||||
tests := []struct {
|
||||
src string
|
||||
result UntypedTextRange
|
||||
err error
|
||||
}{
|
||||
{
|
||||
src: `[1,2)`,
|
||||
result: UntypedTextRange{Lower: "1", Upper: "2", LowerType: Inclusive, UpperType: Exclusive},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
src: `[1,2]`,
|
||||
result: UntypedTextRange{Lower: "1", Upper: "2", LowerType: Inclusive, UpperType: Inclusive},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
src: `(1,3)`,
|
||||
result: UntypedTextRange{Lower: "1", Upper: "3", LowerType: Exclusive, UpperType: Exclusive},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
src: ` [1,2) `,
|
||||
result: UntypedTextRange{Lower: "1", Upper: "2", LowerType: Inclusive, UpperType: Exclusive},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
src: `[ foo , bar )`,
|
||||
result: UntypedTextRange{Lower: " foo ", Upper: " bar ", LowerType: Inclusive, UpperType: Exclusive},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
src: `["foo","bar")`,
|
||||
result: UntypedTextRange{Lower: "foo", Upper: "bar", LowerType: Inclusive, UpperType: Exclusive},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
src: `["f""oo","b""ar")`,
|
||||
result: UntypedTextRange{Lower: `f"oo`, Upper: `b"ar`, LowerType: Inclusive, UpperType: Exclusive},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
src: `["f""oo","b""ar")`,
|
||||
result: UntypedTextRange{Lower: `f"oo`, Upper: `b"ar`, LowerType: Inclusive, UpperType: Exclusive},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
src: `["","bar")`,
|
||||
result: UntypedTextRange{Lower: ``, Upper: `bar`, LowerType: Inclusive, UpperType: Exclusive},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
src: `[f\"oo\,,b\\ar\))`,
|
||||
result: UntypedTextRange{Lower: `f"oo,`, Upper: `b\ar)`, LowerType: Inclusive, UpperType: Exclusive},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
src: `empty`,
|
||||
result: UntypedTextRange{Lower: "", Upper: "", LowerType: Empty, UpperType: Empty},
|
||||
err: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
r, err := ParseUntypedTextRange(tt.src)
|
||||
if err != tt.err {
|
||||
t.Errorf("%d. `%v`: expected err %v, got %v", i, tt.src, tt.err, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if r.LowerType != tt.result.LowerType {
|
||||
t.Errorf("%d. `%v`: expected result lower type %v, got %v", i, tt.src, string(tt.result.LowerType), string(r.LowerType))
|
||||
}
|
||||
|
||||
if r.UpperType != tt.result.UpperType {
|
||||
t.Errorf("%d. `%v`: expected result upper type %v, got %v", i, tt.src, string(tt.result.UpperType), string(r.UpperType))
|
||||
}
|
||||
|
||||
if r.Lower != tt.result.Lower {
|
||||
t.Errorf("%d. `%v`: expected result lower %v, got %v", i, tt.src, tt.result.Lower, r.Lower)
|
||||
}
|
||||
|
||||
if r.Upper != tt.result.Upper {
|
||||
t.Errorf("%d. `%v`: expected result upper %v, got %v", i, tt.src, tt.result.Upper, r.Upper)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseUntypedBinaryRange(t *testing.T) {
|
||||
tests := []struct {
|
||||
src []byte
|
||||
result UntypedBinaryRange
|
||||
err error
|
||||
}{
|
||||
{
|
||||
src: []byte{0, 0, 0, 0, 2, 0, 4, 0, 0, 0, 2, 0, 5},
|
||||
result: UntypedBinaryRange{Lower: []byte{0, 4}, Upper: []byte{0, 5}, LowerType: Exclusive, UpperType: Exclusive},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
src: []byte{1},
|
||||
result: UntypedBinaryRange{Lower: nil, Upper: nil, LowerType: Empty, UpperType: Empty},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
src: []byte{2, 0, 0, 0, 2, 0, 4, 0, 0, 0, 2, 0, 5},
|
||||
result: UntypedBinaryRange{Lower: []byte{0, 4}, Upper: []byte{0, 5}, LowerType: Inclusive, UpperType: Exclusive},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
src: []byte{4, 0, 0, 0, 2, 0, 4, 0, 0, 0, 2, 0, 5},
|
||||
result: UntypedBinaryRange{Lower: []byte{0, 4}, Upper: []byte{0, 5}, LowerType: Exclusive, UpperType: Inclusive},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
src: []byte{6, 0, 0, 0, 2, 0, 4, 0, 0, 0, 2, 0, 5},
|
||||
result: UntypedBinaryRange{Lower: []byte{0, 4}, Upper: []byte{0, 5}, LowerType: Inclusive, UpperType: Inclusive},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
src: []byte{8, 0, 0, 0, 2, 0, 5},
|
||||
result: UntypedBinaryRange{Lower: nil, Upper: []byte{0, 5}, LowerType: Unbounded, UpperType: Exclusive},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
src: []byte{12, 0, 0, 0, 2, 0, 5},
|
||||
result: UntypedBinaryRange{Lower: nil, Upper: []byte{0, 5}, LowerType: Unbounded, UpperType: Inclusive},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
src: []byte{16, 0, 0, 0, 2, 0, 4},
|
||||
result: UntypedBinaryRange{Lower: []byte{0, 4}, Upper: nil, LowerType: Exclusive, UpperType: Unbounded},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
src: []byte{18, 0, 0, 0, 2, 0, 4},
|
||||
result: UntypedBinaryRange{Lower: []byte{0, 4}, Upper: nil, LowerType: Inclusive, UpperType: Unbounded},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
src: []byte{24},
|
||||
result: UntypedBinaryRange{Lower: nil, Upper: nil, LowerType: Unbounded, UpperType: Unbounded},
|
||||
err: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
r, err := ParseUntypedBinaryRange(tt.src)
|
||||
if err != tt.err {
|
||||
t.Errorf("%d. `%v`: expected err %v, got %v", i, tt.src, tt.err, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if r.LowerType != tt.result.LowerType {
|
||||
t.Errorf("%d. `%v`: expected result lower type %v, got %v", i, tt.src, string(tt.result.LowerType), string(r.LowerType))
|
||||
}
|
||||
|
||||
if r.UpperType != tt.result.UpperType {
|
||||
t.Errorf("%d. `%v`: expected result upper type %v, got %v", i, tt.src, string(tt.result.UpperType), string(r.UpperType))
|
||||
}
|
||||
|
||||
if bytes.Compare(r.Lower, tt.result.Lower) != 0 {
|
||||
t.Errorf("%d. `%v`: expected result lower %v, got %v", i, tt.src, tt.result.Lower, r.Lower)
|
||||
}
|
||||
|
||||
if bytes.Compare(r.Upper, tt.result.Upper) != 0 {
|
||||
t.Errorf("%d. `%v`: expected result upper %v, got %v", i, tt.src, tt.result.Upper, r.Upper)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue