Convert numeric to Codec

query-exec-mode
Jack Christensen 2022-01-22 09:31:59 -06:00
parent 0056156904
commit b9b5e35d0f
7 changed files with 625 additions and 1957 deletions

View File

@ -901,7 +901,7 @@ func TestDomainType(t *testing.T) {
if err != nil {
t.Fatalf("did not find uint64 OID, %v", err)
}
conn.ConnInfo().RegisterDataType(pgtype.DataType{Value: &pgtype.Numeric{}, Name: "uint64", OID: uint64OID})
conn.ConnInfo().RegisterDataType(pgtype.DataType{Name: "uint64", OID: uint64OID, Codec: pgtype.NumericCodec{}})
// String is still an acceptable argument after registration
err = conn.QueryRow(context.Background(), "select $1::uint64", "7").Scan(&n)

View File

@ -273,6 +273,20 @@ func (w float32Wrapper) Int64Value() (Int8, error) {
return Int8{Int: int64(w), Valid: true}, nil
}
func (w *float32Wrapper) ScanFloat64(v Float8) error {
if !v.Valid {
return fmt.Errorf("cannot scan NULL into *float32")
}
*w = float32Wrapper(v.Float)
return nil
}
func (w float32Wrapper) Float64Value() (Float8, error) {
return Float8{Float: float64(w), Valid: true}, nil
}
type float64Wrapper float64
func (w float64Wrapper) SkipUnderlyingTypePlan() {}
@ -295,6 +309,20 @@ func (w float64Wrapper) Int64Value() (Int8, error) {
return Int8{Int: int64(w), Valid: true}, nil
}
func (w *float64Wrapper) ScanFloat64(v Float8) error {
if !v.Valid {
return fmt.Errorf("cannot scan NULL into *float64")
}
*w = float64Wrapper(v.Float)
return nil
}
func (w float64Wrapper) Float64Value() (Float8, error) {
return Float8{Float: float64(w), Valid: true}, nil
}
type stringWrapper string
func (w stringWrapper) SkipUnderlyingTypePlan() {}

File diff suppressed because it is too large Load Diff

View File

@ -1,672 +0,0 @@
// Code generated by erb. DO NOT EDIT.
package pgtype
import (
"database/sql/driver"
"encoding/binary"
"fmt"
"reflect"
"github.com/jackc/pgio"
)
type NumericArray struct {
Elements []Numeric
Dimensions []ArrayDimension
Valid bool
}
func (dst *NumericArray) Set(src interface{}) error {
// untyped nil and typed nil interfaces are different
if src == nil {
*dst = NumericArray{}
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 []float32:
if value == nil {
*dst = NumericArray{}
} else if len(value) == 0 {
*dst = NumericArray{Valid: true}
} else {
elements := make([]Numeric, len(value))
for i := range value {
if err := elements[i].Set(value[i]); err != nil {
return err
}
}
*dst = NumericArray{
Elements: elements,
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
Valid: true,
}
}
case []*float32:
if value == nil {
*dst = NumericArray{}
} else if len(value) == 0 {
*dst = NumericArray{Valid: true}
} else {
elements := make([]Numeric, len(value))
for i := range value {
if err := elements[i].Set(value[i]); err != nil {
return err
}
}
*dst = NumericArray{
Elements: elements,
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
Valid: true,
}
}
case []float64:
if value == nil {
*dst = NumericArray{}
} else if len(value) == 0 {
*dst = NumericArray{Valid: true}
} else {
elements := make([]Numeric, len(value))
for i := range value {
if err := elements[i].Set(value[i]); err != nil {
return err
}
}
*dst = NumericArray{
Elements: elements,
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
Valid: true,
}
}
case []*float64:
if value == nil {
*dst = NumericArray{}
} else if len(value) == 0 {
*dst = NumericArray{Valid: true}
} else {
elements := make([]Numeric, len(value))
for i := range value {
if err := elements[i].Set(value[i]); err != nil {
return err
}
}
*dst = NumericArray{
Elements: elements,
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
Valid: true,
}
}
case []int64:
if value == nil {
*dst = NumericArray{}
} else if len(value) == 0 {
*dst = NumericArray{Valid: true}
} else {
elements := make([]Numeric, len(value))
for i := range value {
if err := elements[i].Set(value[i]); err != nil {
return err
}
}
*dst = NumericArray{
Elements: elements,
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
Valid: true,
}
}
case []*int64:
if value == nil {
*dst = NumericArray{}
} else if len(value) == 0 {
*dst = NumericArray{Valid: true}
} else {
elements := make([]Numeric, len(value))
for i := range value {
if err := elements[i].Set(value[i]); err != nil {
return err
}
}
*dst = NumericArray{
Elements: elements,
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
Valid: true,
}
}
case []uint64:
if value == nil {
*dst = NumericArray{}
} else if len(value) == 0 {
*dst = NumericArray{Valid: true}
} else {
elements := make([]Numeric, len(value))
for i := range value {
if err := elements[i].Set(value[i]); err != nil {
return err
}
}
*dst = NumericArray{
Elements: elements,
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
Valid: true,
}
}
case []*uint64:
if value == nil {
*dst = NumericArray{}
} else if len(value) == 0 {
*dst = NumericArray{Valid: true}
} else {
elements := make([]Numeric, len(value))
for i := range value {
if err := elements[i].Set(value[i]); err != nil {
return err
}
}
*dst = NumericArray{
Elements: elements,
Dimensions: []ArrayDimension{{Length: int32(len(elements)), LowerBound: 1}},
Valid: true,
}
}
case []Numeric:
if value == nil {
*dst = NumericArray{}
} else if len(value) == 0 {
*dst = NumericArray{Valid: true}
} else {
*dst = NumericArray{
Elements: value,
Dimensions: []ArrayDimension{{Length: int32(len(value)), LowerBound: 1}},
Valid: true,
}
}
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 = NumericArray{}
return nil
}
dimensions, elementsLength, ok := findDimensionsFromValue(reflectedValue, nil, 0)
if !ok {
return fmt.Errorf("cannot find dimensions of %v for NumericArray", src)
}
if elementsLength == 0 {
*dst = NumericArray{Valid: true}
return nil
}
if len(dimensions) == 0 {
if originalSrc, ok := underlyingSliceType(src); ok {
return dst.Set(originalSrc)
}
return fmt.Errorf("cannot convert %v to NumericArray", src)
}
*dst = NumericArray{
Elements: make([]Numeric, elementsLength),
Dimensions: dimensions,
Valid: true,
}
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([]Numeric, 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 NumericArray, expected %d dst.Elements, but got %d instead", src, len(dst.Elements), elementCount)
}
}
return nil
}
func (dst *NumericArray) 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 NumericArray")
}
if err := dst.Elements[index].Set(value.Interface()); err != nil {
return 0, fmt.Errorf("%v in NumericArray", err)
}
index++
return index, nil
}
func (dst NumericArray) Get() interface{} {
if !dst.Valid {
return nil
}
return dst
}
func (src *NumericArray) AssignTo(dst interface{}) error {
if !src.Valid {
return NullAssignTo(dst)
}
if len(src.Dimensions) <= 1 {
// Attempt to match to select common types:
switch v := dst.(type) {
case *[]float32:
*v = make([]float32, len(src.Elements))
for i := range src.Elements {
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
return err
}
}
return nil
case *[]*float32:
*v = make([]*float32, len(src.Elements))
for i := range src.Elements {
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
return err
}
}
return nil
case *[]float64:
*v = make([]float64, len(src.Elements))
for i := range src.Elements {
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
return err
}
}
return nil
case *[]*float64:
*v = make([]*float64, len(src.Elements))
for i := range src.Elements {
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
return err
}
}
return nil
case *[]int64:
*v = make([]int64, len(src.Elements))
for i := range src.Elements {
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
return err
}
}
return nil
case *[]*int64:
*v = make([]*int64, len(src.Elements))
for i := range src.Elements {
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
return err
}
}
return nil
case *[]uint64:
*v = make([]uint64, len(src.Elements))
for i := range src.Elements {
if err := src.Elements[i].AssignTo(&((*v)[i])); err != nil {
return err
}
}
return nil
case *[]*uint64:
*v = make([]*uint64, 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
}
func (src *NumericArray) 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 NumericArray")
}
addr := value.Addr()
if !addr.CanInterface() {
return 0, fmt.Errorf("cannot assign all values from NumericArray")
}
if err := src.Elements[index].AssignTo(addr.Interface()); err != nil {
return 0, err
}
index++
return index, nil
}
func (dst *NumericArray) DecodeText(ci *ConnInfo, src []byte) error {
if src == nil {
*dst = NumericArray{}
return nil
}
uta, err := ParseUntypedTextArray(string(src))
if err != nil {
return err
}
var elements []Numeric
if len(uta.Elements) > 0 {
elements = make([]Numeric, len(uta.Elements))
for i, s := range uta.Elements {
var elem Numeric
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 = NumericArray{Elements: elements, Dimensions: uta.Dimensions, Valid: true}
return nil
}
func (dst *NumericArray) DecodeBinary(ci *ConnInfo, src []byte) error {
if src == nil {
*dst = NumericArray{}
return nil
}
var arrayHeader ArrayHeader
rp, err := arrayHeader.DecodeBinary(ci, src)
if err != nil {
return err
}
if len(arrayHeader.Dimensions) == 0 {
*dst = NumericArray{Dimensions: arrayHeader.Dimensions, Valid: true}
return nil
}
elementCount := arrayHeader.Dimensions[0].Length
for _, d := range arrayHeader.Dimensions[1:] {
elementCount *= d.Length
}
elements := make([]Numeric, 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 = NumericArray{Elements: elements, Dimensions: arrayHeader.Dimensions, Valid: true}
return nil
}
func (src NumericArray) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
if !src.Valid {
return nil, nil
}
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 NumericArray) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
if !src.Valid {
return nil, nil
}
arrayHeader := ArrayHeader{
Dimensions: src.Dimensions,
}
if dt, ok := ci.DataTypeForName("numeric"); ok {
arrayHeader.ElementOID = int32(dt.OID)
} else {
return nil, fmt.Errorf("unable to find oid for type name %v", "numeric")
}
for i := range src.Elements {
if !src.Elements[i].Valid {
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 *NumericArray) 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 NumericArray) 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
}

View File

@ -1,305 +0,0 @@
package pgtype_test
import (
"math"
"math/big"
"reflect"
"testing"
"github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgtype/testutil"
)
func TestNumericArrayTranscode(t *testing.T) {
testutil.TestSuccessfulTranscode(t, "numeric[]", []interface{}{
&pgtype.NumericArray{
Elements: nil,
Dimensions: nil,
Valid: true,
},
&pgtype.NumericArray{
Elements: []pgtype.Numeric{
{Int: big.NewInt(1), Valid: true},
{},
},
Dimensions: []pgtype.ArrayDimension{{Length: 2, LowerBound: 1}},
Valid: true,
},
&pgtype.NumericArray{},
&pgtype.NumericArray{
Elements: []pgtype.Numeric{
{Int: big.NewInt(1), Valid: true},
{Int: big.NewInt(2), Valid: true},
{Int: big.NewInt(3), Valid: true},
{Int: big.NewInt(4), Valid: true},
{},
{Int: big.NewInt(6), Valid: true},
},
Dimensions: []pgtype.ArrayDimension{{Length: 3, LowerBound: 1}, {Length: 2, LowerBound: 1}},
Valid: true,
},
&pgtype.NumericArray{
Elements: []pgtype.Numeric{
{Int: big.NewInt(1), Valid: true},
{Int: big.NewInt(2), Valid: true},
{Int: big.NewInt(3), Valid: true},
{Int: big.NewInt(4), Valid: true},
},
Dimensions: []pgtype.ArrayDimension{
{Length: 2, LowerBound: 4},
{Length: 2, LowerBound: 2},
},
Valid: true,
},
})
}
func TestNumericArraySet(t *testing.T) {
successfulTests := []struct {
source interface{}
result pgtype.NumericArray
}{
{
source: []float32{1},
result: pgtype.NumericArray{
Elements: []pgtype.Numeric{{Int: big.NewInt(1), Valid: true}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
Valid: true},
},
{
source: []float32{float32(math.Copysign(0, -1))},
result: pgtype.NumericArray{
Elements: []pgtype.Numeric{{Int: big.NewInt(0), Valid: true}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
Valid: true},
},
{
source: []float64{1},
result: pgtype.NumericArray{
Elements: []pgtype.Numeric{{Int: big.NewInt(1), Valid: true}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
Valid: true},
},
{
source: []float64{math.Copysign(0, -1)},
result: pgtype.NumericArray{
Elements: []pgtype.Numeric{{Int: big.NewInt(0), Valid: true}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
Valid: true},
},
{
source: (([]float32)(nil)),
result: pgtype.NumericArray{},
},
{
source: [][]float32{{1}, {2}},
result: pgtype.NumericArray{
Elements: []pgtype.Numeric{{Int: big.NewInt(1), Valid: true}, {Int: big.NewInt(2), Valid: true}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
Valid: true},
},
{
source: [][][][]float32{{{{1, 2, 3}}}, {{{4, 5, 6}}}},
result: pgtype.NumericArray{
Elements: []pgtype.Numeric{
{Int: big.NewInt(1), Valid: true},
{Int: big.NewInt(2), Valid: true},
{Int: big.NewInt(3), Valid: true},
{Int: big.NewInt(4), Valid: true},
{Int: big.NewInt(5), Valid: true},
{Int: big.NewInt(6), Valid: true}},
Dimensions: []pgtype.ArrayDimension{
{LowerBound: 1, Length: 2},
{LowerBound: 1, Length: 1},
{LowerBound: 1, Length: 1},
{LowerBound: 1, Length: 3}},
Valid: true},
},
{
source: [2][1]float32{{1}, {2}},
result: pgtype.NumericArray{
Elements: []pgtype.Numeric{{Int: big.NewInt(1), Valid: true}, {Int: big.NewInt(2), Valid: true}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
Valid: true},
},
{
source: [2][1][1][3]float32{{{{1, 2, 3}}}, {{{4, 5, 6}}}},
result: pgtype.NumericArray{
Elements: []pgtype.Numeric{
{Int: big.NewInt(1), Valid: true},
{Int: big.NewInt(2), Valid: true},
{Int: big.NewInt(3), Valid: true},
{Int: big.NewInt(4), Valid: true},
{Int: big.NewInt(5), Valid: true},
{Int: big.NewInt(6), Valid: true}},
Dimensions: []pgtype.ArrayDimension{
{LowerBound: 1, Length: 2},
{LowerBound: 1, Length: 1},
{LowerBound: 1, Length: 1},
{LowerBound: 1, Length: 3}},
Valid: true},
},
}
for i, tt := range successfulTests {
var r pgtype.NumericArray
err := r.Set(tt.source)
if err != nil {
t.Errorf("%d: %v", i, err)
}
if !reflect.DeepEqual(r, tt.result) {
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
}
}
}
func TestNumericArrayAssignTo(t *testing.T) {
var float32Slice []float32
var float64Slice []float64
var float32SliceDim2 [][]float32
var float32SliceDim4 [][][][]float32
var float32ArrayDim2 [2][1]float32
var float32ArrayDim4 [2][1][1][3]float32
simpleTests := []struct {
src pgtype.NumericArray
dst interface{}
expected interface{}
}{
{
src: pgtype.NumericArray{
Elements: []pgtype.Numeric{{Int: big.NewInt(1), Valid: true}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
Valid: true,
},
dst: &float32Slice,
expected: []float32{1},
},
{
src: pgtype.NumericArray{
Elements: []pgtype.Numeric{{Int: big.NewInt(1), Valid: true}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
Valid: true,
},
dst: &float64Slice,
expected: []float64{1},
},
{
src: pgtype.NumericArray{},
dst: &float32Slice,
expected: (([]float32)(nil)),
},
{
src: pgtype.NumericArray{Valid: true},
dst: &float32Slice,
expected: []float32{},
},
{
src: pgtype.NumericArray{
Elements: []pgtype.Numeric{{Int: big.NewInt(1), Valid: true}, {Int: big.NewInt(2), Valid: true}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
Valid: true},
dst: &float32SliceDim2,
expected: [][]float32{{1}, {2}},
},
{
src: pgtype.NumericArray{
Elements: []pgtype.Numeric{
{Int: big.NewInt(1), Valid: true},
{Int: big.NewInt(2), Valid: true},
{Int: big.NewInt(3), Valid: true},
{Int: big.NewInt(4), Valid: true},
{Int: big.NewInt(5), Valid: true},
{Int: big.NewInt(6), Valid: true}},
Dimensions: []pgtype.ArrayDimension{
{LowerBound: 1, Length: 2},
{LowerBound: 1, Length: 1},
{LowerBound: 1, Length: 1},
{LowerBound: 1, Length: 3}},
Valid: true},
dst: &float32SliceDim4,
expected: [][][][]float32{{{{1, 2, 3}}}, {{{4, 5, 6}}}},
},
{
src: pgtype.NumericArray{
Elements: []pgtype.Numeric{{Int: big.NewInt(1), Valid: true}, {Int: big.NewInt(2), Valid: true}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
Valid: true},
dst: &float32ArrayDim2,
expected: [2][1]float32{{1}, {2}},
},
{
src: pgtype.NumericArray{
Elements: []pgtype.Numeric{
{Int: big.NewInt(1), Valid: true},
{Int: big.NewInt(2), Valid: true},
{Int: big.NewInt(3), Valid: true},
{Int: big.NewInt(4), Valid: true},
{Int: big.NewInt(5), Valid: true},
{Int: big.NewInt(6), Valid: true}},
Dimensions: []pgtype.ArrayDimension{
{LowerBound: 1, Length: 2},
{LowerBound: 1, Length: 1},
{LowerBound: 1, Length: 1},
{LowerBound: 1, Length: 3}},
Valid: true},
dst: &float32ArrayDim4,
expected: [2][1][1][3]float32{{{{1, 2, 3}}}, {{{4, 5, 6}}}},
},
}
for i, tt := range simpleTests {
err := tt.src.AssignTo(tt.dst)
if err != nil {
t.Errorf("%d: %v", i, err)
}
if dst := reflect.ValueOf(tt.dst).Elem().Interface(); !reflect.DeepEqual(dst, tt.expected) {
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
}
}
errorTests := []struct {
src pgtype.NumericArray
dst interface{}
}{
{
src: pgtype.NumericArray{
Elements: []pgtype.Numeric{{}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}},
Valid: true,
},
dst: &float32Slice,
},
{
src: pgtype.NumericArray{
Elements: []pgtype.Numeric{{Int: big.NewInt(1), Valid: true}, {Int: big.NewInt(2), Valid: true}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}, {LowerBound: 1, Length: 2}},
Valid: true},
dst: &float32ArrayDim2,
},
{
src: pgtype.NumericArray{
Elements: []pgtype.Numeric{{Int: big.NewInt(1), Valid: true}, {Int: big.NewInt(2), Valid: true}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 1}, {LowerBound: 1, Length: 2}},
Valid: true},
dst: &float32Slice,
},
{
src: pgtype.NumericArray{
Elements: []pgtype.Numeric{{Int: big.NewInt(1), Valid: true}, {Int: big.NewInt(2), Valid: true}},
Dimensions: []pgtype.ArrayDimension{{LowerBound: 1, Length: 2}, {LowerBound: 1, Length: 1}},
Valid: true},
dst: &float32ArrayDim4,
},
}
for i, tt := range errorTests {
err := tt.src.AssignTo(tt.dst)
if err == nil {
t.Errorf("%d: expected error but none was returned (%v -> %v)", i, tt.src, tt.dst)
}
}
}

View File

@ -6,7 +6,7 @@ import (
"math"
"math/big"
"math/rand"
"reflect"
"strconv"
"testing"
"github.com/jackc/pgx/v5/pgtype"
@ -14,34 +14,6 @@ import (
"github.com/stretchr/testify/require"
)
// For test purposes only. Note that it does not normalize values. e.g. (Int: 1, Exp: 3) will not equal (Int: 1000, Exp: 0)
func numericEqual(left, right *pgtype.Numeric) bool {
return left.Valid == right.Valid &&
left.Exp == right.Exp &&
((left.Int == nil && right.Int == nil) || (left.Int != nil && right.Int != nil && left.Int.Cmp(right.Int) == 0)) &&
left.NaN == right.NaN
}
// For test purposes only.
func numericNormalizedEqual(left, right *pgtype.Numeric) bool {
if left.Valid != right.Valid {
return false
}
normLeft := &pgtype.Numeric{Int: (&big.Int{}).Set(left.Int), Valid: left.Valid}
normRight := &pgtype.Numeric{Int: (&big.Int{}).Set(right.Int), Valid: right.Valid}
if left.Exp < right.Exp {
mul := (&big.Int{}).Exp(big.NewInt(10), big.NewInt(int64(right.Exp-left.Exp)), nil)
normRight.Int.Mul(normRight.Int, mul)
} else if left.Exp > right.Exp {
mul := (&big.Int{}).Exp(big.NewInt(10), big.NewInt(int64(left.Exp-right.Exp)), nil)
normLeft.Int.Mul(normLeft.Int, mul)
}
return normLeft.Int.Cmp(normRight.Int) == 0
}
func mustParseBigInt(t *testing.T, src string) *big.Int {
i := &big.Int{}
if _, ok := i.SetString(src, 10); !ok {
@ -50,368 +22,122 @@ func mustParseBigInt(t *testing.T, src string) *big.Int {
return i
}
func TestNumericNormalize(t *testing.T) {
testutil.TestSuccessfulNormalize(t, []testutil.NormalizeTest{
{
SQL: "select '0'::numeric",
Value: &pgtype.Numeric{Int: big.NewInt(0), Exp: 0, Valid: true},
},
{
SQL: "select '1'::numeric",
Value: &pgtype.Numeric{Int: big.NewInt(1), Exp: 0, Valid: true},
},
{
SQL: "select '10.00'::numeric",
Value: &pgtype.Numeric{Int: big.NewInt(1000), Exp: -2, Valid: true},
},
{
SQL: "select '1e-3'::numeric",
Value: &pgtype.Numeric{Int: big.NewInt(1), Exp: -3, Valid: true},
},
{
SQL: "select '-1'::numeric",
Value: &pgtype.Numeric{Int: big.NewInt(-1), Exp: 0, Valid: true},
},
{
SQL: "select '10000'::numeric",
Value: &pgtype.Numeric{Int: big.NewInt(1), Exp: 4, Valid: true},
},
{
SQL: "select '3.14'::numeric",
Value: &pgtype.Numeric{Int: big.NewInt(314), Exp: -2, Valid: true},
},
{
SQL: "select '1.1'::numeric",
Value: &pgtype.Numeric{Int: big.NewInt(11), Exp: -1, Valid: true},
},
{
SQL: "select '100010001'::numeric",
Value: &pgtype.Numeric{Int: big.NewInt(100010001), Exp: 0, Valid: true},
},
{
SQL: "select '100010001.0001'::numeric",
Value: &pgtype.Numeric{Int: big.NewInt(1000100010001), Exp: -4, Valid: true},
},
{
SQL: "select '4237234789234789289347892374324872138321894178943189043890124832108934.43219085471578891547854892438945012347981'::numeric",
Value: &pgtype.Numeric{
Int: mustParseBigInt(t, "423723478923478928934789237432487213832189417894318904389012483210893443219085471578891547854892438945012347981"),
Exp: -41,
Valid: true,
},
},
{
SQL: "select '0.8925092023480223478923478978978937897879595901237890234789243679037419057877231734823098432903527585734549035904590854890345905434578345789347890402348952348905890489054234237489234987723894789234'::numeric",
Value: &pgtype.Numeric{
Int: mustParseBigInt(t, "8925092023480223478923478978978937897879595901237890234789243679037419057877231734823098432903527585734549035904590854890345905434578345789347890402348952348905890489054234237489234987723894789234"),
Exp: -196,
Valid: true,
},
},
{
SQL: "select '0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000123'::numeric",
Value: &pgtype.Numeric{
Int: mustParseBigInt(t, "123"),
Exp: -186,
Valid: true,
},
},
})
func isExpectedEqNumeric(a interface{}) func(interface{}) bool {
return func(v interface{}) bool {
aa := a.(pgtype.Numeric)
vv := v.(pgtype.Numeric)
if aa.Valid != vv.Valid {
return false
}
// If NULL doesn't matter what the rest of the values are.
if !aa.Valid {
return true
}
if !(aa.NaN == vv.NaN && aa.InfinityModifier == vv.InfinityModifier) {
return false
}
// If NaN or InfinityModifier are set then Int and Exp don't matter.
if aa.NaN || aa.InfinityModifier != pgtype.None {
return true
}
aaInt := (&big.Int{}).Set(aa.Int)
vvInt := (&big.Int{}).Set(vv.Int)
if aa.Exp < vv.Exp {
mul := (&big.Int{}).Exp(big.NewInt(10), big.NewInt(int64(vv.Exp-aa.Exp)), nil)
vvInt.Mul(vvInt, mul)
} else if aa.Exp > vv.Exp {
mul := (&big.Int{}).Exp(big.NewInt(10), big.NewInt(int64(aa.Exp-vv.Exp)), nil)
aaInt.Mul(aaInt, mul)
}
return aaInt.Cmp(vvInt) == 0
}
}
func TestNumericTranscode(t *testing.T) {
func mustParseNumeric(t *testing.T, src string) pgtype.Numeric {
var n pgtype.Numeric
plan := pgtype.NumericCodec{}.PlanScan(nil, pgtype.NumericOID, pgtype.TextFormatCode, &n, false)
require.NotNil(t, plan)
err := plan.Scan(nil, pgtype.NumericOID, pgtype.TextFormatCode, []byte(src), &n)
require.NoError(t, err)
return n
}
func TestNumericCodec(t *testing.T) {
max := new(big.Int).Exp(big.NewInt(10), big.NewInt(147454), nil)
max.Add(max, big.NewInt(1))
longestNumeric := &pgtype.Numeric{Int: max, Exp: -16383, Valid: true}
longestNumeric := pgtype.Numeric{Int: max, Exp: -16383, Valid: true}
testutil.TestSuccessfulTranscodeEqFunc(t, "numeric", []interface{}{
&pgtype.Numeric{NaN: true, Valid: true},
&pgtype.Numeric{InfinityModifier: pgtype.Infinity, Valid: true},
&pgtype.Numeric{InfinityModifier: pgtype.NegativeInfinity, Valid: true},
&pgtype.Numeric{Int: big.NewInt(0), Exp: 0, Valid: true},
&pgtype.Numeric{Int: big.NewInt(1), Exp: 0, Valid: true},
&pgtype.Numeric{Int: big.NewInt(-1), Exp: 0, Valid: true},
&pgtype.Numeric{Int: big.NewInt(1), Exp: 6, Valid: true},
// preserves significant zeroes
&pgtype.Numeric{Int: big.NewInt(10000000), Exp: -1, Valid: true},
&pgtype.Numeric{Int: big.NewInt(10000000), Exp: -2, Valid: true},
&pgtype.Numeric{Int: big.NewInt(10000000), Exp: -3, Valid: true},
&pgtype.Numeric{Int: big.NewInt(10000000), Exp: -4, Valid: true},
&pgtype.Numeric{Int: big.NewInt(10000000), Exp: -5, Valid: true},
&pgtype.Numeric{Int: big.NewInt(10000000), Exp: -6, Valid: true},
&pgtype.Numeric{Int: big.NewInt(314), Exp: -2, Valid: true},
&pgtype.Numeric{Int: big.NewInt(123), Exp: -7, Valid: true},
&pgtype.Numeric{Int: big.NewInt(123), Exp: -8, Valid: true},
&pgtype.Numeric{Int: big.NewInt(123), Exp: -9, Valid: true},
&pgtype.Numeric{Int: big.NewInt(123), Exp: -1500, Valid: true},
&pgtype.Numeric{Int: mustParseBigInt(t, "2437"), Exp: 23790, Valid: true},
&pgtype.Numeric{Int: mustParseBigInt(t, "243723409723490243842378942378901237502734019231380123"), Exp: 23790, Valid: true},
&pgtype.Numeric{Int: mustParseBigInt(t, "43723409723490243842378942378901237502734019231380123"), Exp: 80, Valid: true},
&pgtype.Numeric{Int: mustParseBigInt(t, "3723409723490243842378942378901237502734019231380123"), Exp: 81, Valid: true},
&pgtype.Numeric{Int: mustParseBigInt(t, "723409723490243842378942378901237502734019231380123"), Exp: 82, Valid: true},
&pgtype.Numeric{Int: mustParseBigInt(t, "23409723490243842378942378901237502734019231380123"), Exp: 83, Valid: true},
&pgtype.Numeric{Int: mustParseBigInt(t, "3409723490243842378942378901237502734019231380123"), Exp: 84, Valid: true},
&pgtype.Numeric{Int: mustParseBigInt(t, "913423409823409243892349028349023482934092340892390101"), Exp: -14021, Valid: true},
&pgtype.Numeric{Int: mustParseBigInt(t, "13423409823409243892349028349023482934092340892390101"), Exp: -90, Valid: true},
&pgtype.Numeric{Int: mustParseBigInt(t, "3423409823409243892349028349023482934092340892390101"), Exp: -91, Valid: true},
&pgtype.Numeric{Int: mustParseBigInt(t, "423409823409243892349028349023482934092340892390101"), Exp: -92, Valid: true},
&pgtype.Numeric{Int: mustParseBigInt(t, "23409823409243892349028349023482934092340892390101"), Exp: -93, Valid: true},
&pgtype.Numeric{Int: mustParseBigInt(t, "3409823409243892349028349023482934092340892390101"), Exp: -94, Valid: true},
longestNumeric,
&pgtype.Numeric{},
}, func(aa, bb interface{}) bool {
a := aa.(pgtype.Numeric)
b := bb.(pgtype.Numeric)
return numericEqual(&a, &b)
testPgxCodec(t, "numeric", []PgxTranscodeTestCase{
{mustParseNumeric(t, "1"), new(pgtype.Numeric), isExpectedEqNumeric(mustParseNumeric(t, "1"))},
{mustParseNumeric(t, "3.14159"), new(pgtype.Numeric), isExpectedEqNumeric(mustParseNumeric(t, "3.14159"))},
{mustParseNumeric(t, "100010001"), new(pgtype.Numeric), isExpectedEqNumeric(mustParseNumeric(t, "100010001"))},
{mustParseNumeric(t, "100010001.0001"), new(pgtype.Numeric), isExpectedEqNumeric(mustParseNumeric(t, "100010001.0001"))},
{mustParseNumeric(t, "4237234789234789289347892374324872138321894178943189043890124832108934.43219085471578891547854892438945012347981"), new(pgtype.Numeric), isExpectedEqNumeric(mustParseNumeric(t, "4237234789234789289347892374324872138321894178943189043890124832108934.43219085471578891547854892438945012347981"))},
{mustParseNumeric(t, "0.8925092023480223478923478978978937897879595901237890234789243679037419057877231734823098432903527585734549035904590854890345905434578345789347890402348952348905890489054234237489234987723894789234"), new(pgtype.Numeric), isExpectedEqNumeric(mustParseNumeric(t, "0.8925092023480223478923478978978937897879595901237890234789243679037419057877231734823098432903527585734549035904590854890345905434578345789347890402348952348905890489054234237489234987723894789234"))},
{mustParseNumeric(t, "0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000123"), new(pgtype.Numeric), isExpectedEqNumeric(mustParseNumeric(t, "0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000123"))},
{pgtype.Numeric{Int: mustParseBigInt(t, "243723409723490243842378942378901237502734019231380123"), Exp: 23790, Valid: true}, new(pgtype.Numeric), isExpectedEqNumeric(pgtype.Numeric{Int: mustParseBigInt(t, "243723409723490243842378942378901237502734019231380123"), Exp: 23790, Valid: true})},
{pgtype.Numeric{Int: mustParseBigInt(t, "2437"), Exp: 23790, Valid: true}, new(pgtype.Numeric), isExpectedEqNumeric(pgtype.Numeric{Int: mustParseBigInt(t, "2437"), Exp: 23790, Valid: true})},
{pgtype.Numeric{Int: mustParseBigInt(t, "43723409723490243842378942378901237502734019231380123"), Exp: 80, Valid: true}, new(pgtype.Numeric), isExpectedEqNumeric(pgtype.Numeric{Int: mustParseBigInt(t, "43723409723490243842378942378901237502734019231380123"), Exp: 80, Valid: true})},
{pgtype.Numeric{Int: mustParseBigInt(t, "43723409723490243842378942378901237502734019231380123"), Exp: 81, Valid: true}, new(pgtype.Numeric), isExpectedEqNumeric(pgtype.Numeric{Int: mustParseBigInt(t, "43723409723490243842378942378901237502734019231380123"), Exp: 81, Valid: true})},
{pgtype.Numeric{Int: mustParseBigInt(t, "43723409723490243842378942378901237502734019231380123"), Exp: 82, Valid: true}, new(pgtype.Numeric), isExpectedEqNumeric(pgtype.Numeric{Int: mustParseBigInt(t, "43723409723490243842378942378901237502734019231380123"), Exp: 82, Valid: true})},
{pgtype.Numeric{Int: mustParseBigInt(t, "43723409723490243842378942378901237502734019231380123"), Exp: 83, Valid: true}, new(pgtype.Numeric), isExpectedEqNumeric(pgtype.Numeric{Int: mustParseBigInt(t, "43723409723490243842378942378901237502734019231380123"), Exp: 83, Valid: true})},
{pgtype.Numeric{Int: mustParseBigInt(t, "43723409723490243842378942378901237502734019231380123"), Exp: 84, Valid: true}, new(pgtype.Numeric), isExpectedEqNumeric(pgtype.Numeric{Int: mustParseBigInt(t, "43723409723490243842378942378901237502734019231380123"), Exp: 84, Valid: true})},
{pgtype.Numeric{Int: mustParseBigInt(t, "913423409823409243892349028349023482934092340892390101"), Exp: -14021, Valid: true}, new(pgtype.Numeric), isExpectedEqNumeric(pgtype.Numeric{Int: mustParseBigInt(t, "913423409823409243892349028349023482934092340892390101"), Exp: -14021, Valid: true})},
{pgtype.Numeric{Int: mustParseBigInt(t, "13423409823409243892349028349023482934092340892390101"), Exp: -90, Valid: true}, new(pgtype.Numeric), isExpectedEqNumeric(pgtype.Numeric{Int: mustParseBigInt(t, "13423409823409243892349028349023482934092340892390101"), Exp: -90, Valid: true})},
{pgtype.Numeric{Int: mustParseBigInt(t, "13423409823409243892349028349023482934092340892390101"), Exp: -91, Valid: true}, new(pgtype.Numeric), isExpectedEqNumeric(pgtype.Numeric{Int: mustParseBigInt(t, "13423409823409243892349028349023482934092340892390101"), Exp: -91, Valid: true})},
{pgtype.Numeric{Int: mustParseBigInt(t, "13423409823409243892349028349023482934092340892390101"), Exp: -92, Valid: true}, new(pgtype.Numeric), isExpectedEqNumeric(pgtype.Numeric{Int: mustParseBigInt(t, "13423409823409243892349028349023482934092340892390101"), Exp: -92, Valid: true})},
{pgtype.Numeric{Int: mustParseBigInt(t, "13423409823409243892349028349023482934092340892390101"), Exp: -93, Valid: true}, new(pgtype.Numeric), isExpectedEqNumeric(pgtype.Numeric{Int: mustParseBigInt(t, "13423409823409243892349028349023482934092340892390101"), Exp: -93, Valid: true})},
{pgtype.Numeric{NaN: true, Valid: true}, new(pgtype.Numeric), isExpectedEqNumeric(pgtype.Numeric{NaN: true, Valid: true})},
{pgtype.Numeric{InfinityModifier: pgtype.Infinity, Valid: true}, new(pgtype.Numeric), isExpectedEqNumeric(pgtype.Numeric{InfinityModifier: pgtype.Infinity, Valid: true})},
{pgtype.Numeric{InfinityModifier: pgtype.NegativeInfinity, Valid: true}, new(pgtype.Numeric), isExpectedEqNumeric(pgtype.Numeric{InfinityModifier: pgtype.NegativeInfinity, Valid: true})},
{longestNumeric, new(pgtype.Numeric), isExpectedEqNumeric(longestNumeric)},
{mustParseNumeric(t, "1"), new(int64), isExpectedEq(int64(1))},
{math.NaN(), new(float64), func(a interface{}) bool { return math.IsNaN(a.(float64)) }},
{float32(math.NaN()), new(float32), func(a interface{}) bool { return math.IsNaN(float64(a.(float32))) }},
{math.Inf(1), new(float64), isExpectedEq(math.Inf(1))},
{float32(math.Inf(1)), new(float32), isExpectedEq(float32(math.Inf(1)))},
{math.Inf(-1), new(float64), isExpectedEq(math.Inf(-1))},
{float32(math.Inf(-1)), new(float32), isExpectedEq(float32(math.Inf(-1)))},
{int64(-1), new(pgtype.Numeric), isExpectedEqNumeric(mustParseNumeric(t, "-1"))},
{int64(0), new(pgtype.Numeric), isExpectedEqNumeric(mustParseNumeric(t, "0"))},
{int64(1), new(pgtype.Numeric), isExpectedEqNumeric(mustParseNumeric(t, "1"))},
{int64(math.MinInt64), new(pgtype.Numeric), isExpectedEqNumeric(mustParseNumeric(t, strconv.FormatInt(math.MinInt64, 10)))},
{int64(math.MinInt64 + 1), new(pgtype.Numeric), isExpectedEqNumeric(mustParseNumeric(t, strconv.FormatInt(math.MinInt64+1, 10)))},
{int64(math.MaxInt64), new(pgtype.Numeric), isExpectedEqNumeric(mustParseNumeric(t, strconv.FormatInt(math.MaxInt64, 10)))},
{int64(math.MaxInt64 - 1), new(pgtype.Numeric), isExpectedEqNumeric(mustParseNumeric(t, strconv.FormatInt(math.MaxInt64-1, 10)))},
{pgtype.Numeric{}, new(pgtype.Numeric), isExpectedEq(pgtype.Numeric{})},
{nil, new(pgtype.Numeric), isExpectedEq(pgtype.Numeric{})},
})
}
func TestNumericTranscodeFuzz(t *testing.T) {
func TestNumericCodecFuzz(t *testing.T) {
r := rand.New(rand.NewSource(0))
max := &big.Int{}
max.SetString("9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999", 10)
values := make([]interface{}, 0, 2000)
tests := make([]PgxTranscodeTestCase, 0, 2000)
for i := 0; i < 10; i++ {
for j := -50; j < 50; j++ {
num := (&big.Int{}).Rand(r, max)
n := pgtype.Numeric{Int: num, Exp: int32(j), Valid: true}
tests = append(tests, PgxTranscodeTestCase{n, new(pgtype.Numeric), isExpectedEqNumeric(n)})
negNum := &big.Int{}
negNum.Neg(num)
values = append(values, &pgtype.Numeric{Int: num, Exp: int32(j), Valid: true})
values = append(values, &pgtype.Numeric{Int: negNum, Exp: int32(j), Valid: true})
n = pgtype.Numeric{Int: negNum, Exp: int32(j), Valid: true}
tests = append(tests, PgxTranscodeTestCase{n, new(pgtype.Numeric), isExpectedEqNumeric(n)})
}
}
testutil.TestSuccessfulTranscodeEqFunc(t, "numeric", values,
func(aa, bb interface{}) bool {
a := aa.(pgtype.Numeric)
b := bb.(pgtype.Numeric)
return numericNormalizedEqual(&a, &b)
})
}
func TestNumericSet(t *testing.T) {
successfulTests := []struct {
source interface{}
result *pgtype.Numeric
}{
{source: float32(1), result: &pgtype.Numeric{Int: big.NewInt(1), Valid: true}},
{source: float32(math.Copysign(0, -1)), result: &pgtype.Numeric{Int: big.NewInt(0), Valid: true}},
{source: float64(1), result: &pgtype.Numeric{Int: big.NewInt(1), Valid: true}},
{source: float64(math.Copysign(0, -1)), result: &pgtype.Numeric{Int: big.NewInt(0), Valid: true}},
{source: int8(1), result: &pgtype.Numeric{Int: big.NewInt(1), Valid: true}},
{source: int16(1), result: &pgtype.Numeric{Int: big.NewInt(1), Valid: true}},
{source: int32(1), result: &pgtype.Numeric{Int: big.NewInt(1), Valid: true}},
{source: int64(1), result: &pgtype.Numeric{Int: big.NewInt(1), Valid: true}},
{source: int8(-1), result: &pgtype.Numeric{Int: big.NewInt(-1), Valid: true}},
{source: int16(-1), result: &pgtype.Numeric{Int: big.NewInt(-1), Valid: true}},
{source: int32(-1), result: &pgtype.Numeric{Int: big.NewInt(-1), Valid: true}},
{source: int64(-1), result: &pgtype.Numeric{Int: big.NewInt(-1), Valid: true}},
{source: uint8(1), result: &pgtype.Numeric{Int: big.NewInt(1), Valid: true}},
{source: uint16(1), result: &pgtype.Numeric{Int: big.NewInt(1), Valid: true}},
{source: uint32(1), result: &pgtype.Numeric{Int: big.NewInt(1), Valid: true}},
{source: uint64(1), result: &pgtype.Numeric{Int: big.NewInt(1), Valid: true}},
{source: "1", result: &pgtype.Numeric{Int: big.NewInt(1), Valid: true}},
{source: _int8(1), result: &pgtype.Numeric{Int: big.NewInt(1), Valid: true}},
{source: float64(1000), result: &pgtype.Numeric{Int: big.NewInt(1), Exp: 3, Valid: true}},
{source: float64(1234), result: &pgtype.Numeric{Int: big.NewInt(1234), Exp: 0, Valid: true}},
{source: float64(12345678900), result: &pgtype.Numeric{Int: big.NewInt(123456789), Exp: 2, Valid: true}},
{source: float64(12345.678901), result: &pgtype.Numeric{Int: big.NewInt(12345678901), Exp: -6, Valid: true}},
{source: math.NaN(), result: &pgtype.Numeric{Int: nil, Exp: 0, Valid: true, NaN: true}},
{source: float32(math.NaN()), result: &pgtype.Numeric{Int: nil, Exp: 0, Valid: true, NaN: true}},
{source: pgtype.Infinity, result: &pgtype.Numeric{InfinityModifier: pgtype.Infinity, Valid: true}},
{source: math.Inf(1), result: &pgtype.Numeric{Valid: true, InfinityModifier: pgtype.Infinity}},
{source: float32(math.Inf(1)), result: &pgtype.Numeric{Valid: true, InfinityModifier: pgtype.Infinity}},
{source: pgtype.NegativeInfinity, result: &pgtype.Numeric{InfinityModifier: pgtype.NegativeInfinity, Valid: true}},
{source: math.Inf(-1), result: &pgtype.Numeric{Valid: true, InfinityModifier: pgtype.NegativeInfinity}},
{source: float32(math.Inf(1)), result: &pgtype.Numeric{Valid: true, InfinityModifier: pgtype.Infinity}},
}
for i, tt := range successfulTests {
r := &pgtype.Numeric{}
err := r.Set(tt.source)
if err != nil {
t.Errorf("%d: %v", i, err)
}
if !numericEqual(r, tt.result) {
t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r)
}
}
}
func TestNumericAssignTo(t *testing.T) {
var i8 int8
var i16 int16
var i32 int32
var i64 int64
var i int
var ui8 uint8
var ui16 uint16
var ui32 uint32
var ui64 uint64
var ui uint
var pi8 *int8
var _i8 _int8
var _pi8 *_int8
var f32 float32
var f64 float64
var pf32 *float32
var pf64 *float64
simpleTests := []struct {
src *pgtype.Numeric
dst interface{}
expected interface{}
}{
{src: &pgtype.Numeric{Int: big.NewInt(42), Valid: true}, dst: &f32, expected: float32(42)},
{src: &pgtype.Numeric{Int: big.NewInt(42), Valid: true}, dst: &f64, expected: float64(42)},
{src: &pgtype.Numeric{Int: big.NewInt(42), Exp: -1, Valid: true}, dst: &f32, expected: float32(4.2)},
{src: &pgtype.Numeric{Int: big.NewInt(42), Exp: -1, Valid: true}, dst: &f64, expected: float64(4.2)},
{src: &pgtype.Numeric{Int: big.NewInt(42), Valid: true}, dst: &i16, expected: int16(42)},
{src: &pgtype.Numeric{Int: big.NewInt(42), Valid: true}, dst: &i32, expected: int32(42)},
{src: &pgtype.Numeric{Int: big.NewInt(42), Valid: true}, dst: &i64, expected: int64(42)},
{src: &pgtype.Numeric{Int: big.NewInt(42), Exp: 3, Valid: true}, dst: &i64, expected: int64(42000)},
{src: &pgtype.Numeric{Int: big.NewInt(42), Valid: true}, dst: &i, expected: int(42)},
{src: &pgtype.Numeric{Int: big.NewInt(42), Valid: true}, dst: &ui8, expected: uint8(42)},
{src: &pgtype.Numeric{Int: big.NewInt(42), Valid: true}, dst: &ui16, expected: uint16(42)},
{src: &pgtype.Numeric{Int: big.NewInt(42), Valid: true}, dst: &ui32, expected: uint32(42)},
{src: &pgtype.Numeric{Int: big.NewInt(42), Valid: true}, dst: &ui64, expected: uint64(42)},
{src: &pgtype.Numeric{Int: big.NewInt(42), Valid: true}, dst: &ui, expected: uint(42)},
{src: &pgtype.Numeric{Int: big.NewInt(42), Valid: true}, dst: &_i8, expected: _int8(42)},
{src: &pgtype.Numeric{Int: big.NewInt(0)}, dst: &pi8, expected: ((*int8)(nil))},
{src: &pgtype.Numeric{Int: big.NewInt(0)}, dst: &_pi8, expected: ((*_int8)(nil))},
{src: &pgtype.Numeric{Int: big.NewInt(1006), Exp: -2, Valid: true}, dst: &f64, expected: float64(10.06)}, // https://github.com/jackc/pgx/v5/pgtype/issues/27
{src: &pgtype.Numeric{Valid: true, NaN: true}, dst: &f64, expected: math.NaN()},
{src: &pgtype.Numeric{Valid: true, NaN: true}, dst: &f32, expected: float32(math.NaN())},
{src: &pgtype.Numeric{Valid: true, InfinityModifier: pgtype.Infinity}, dst: &f64, expected: math.Inf(1)},
{src: &pgtype.Numeric{Valid: true, InfinityModifier: pgtype.Infinity}, dst: &f32, expected: float32(math.Inf(1))},
{src: &pgtype.Numeric{Valid: true, InfinityModifier: pgtype.NegativeInfinity}, dst: &f64, expected: math.Inf(-1)},
{src: &pgtype.Numeric{Valid: true, InfinityModifier: pgtype.NegativeInfinity}, dst: &f32, expected: float32(math.Inf(-1))},
}
for i, tt := range simpleTests {
err := tt.src.AssignTo(tt.dst)
if err != nil {
t.Errorf("%d: %v", i, err)
}
dst := reflect.ValueOf(tt.dst).Elem().Interface()
switch dstTyped := dst.(type) {
case float32:
nanExpected := math.IsNaN(float64(tt.expected.(float32)))
if nanExpected && !math.IsNaN(float64(dstTyped)) {
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
} else if !nanExpected && dst != tt.expected {
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
}
case float64:
nanExpected := math.IsNaN(tt.expected.(float64))
if nanExpected && !math.IsNaN(dstTyped) {
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
} else if !nanExpected && dst != tt.expected {
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
}
default:
if dst != tt.expected {
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
}
}
}
pointerAllocTests := []struct {
src *pgtype.Numeric
dst interface{}
expected interface{}
}{
{src: &pgtype.Numeric{Int: big.NewInt(42), Valid: true}, dst: &pf32, expected: float32(42)},
{src: &pgtype.Numeric{Int: big.NewInt(42), Valid: true}, dst: &pf64, expected: float64(42)},
}
for i, tt := range pointerAllocTests {
err := tt.src.AssignTo(tt.dst)
if err != nil {
t.Errorf("%d: %v", i, err)
}
if dst := reflect.ValueOf(tt.dst).Elem().Elem().Interface(); dst != tt.expected {
t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst)
}
}
errorTests := []struct {
src *pgtype.Numeric
dst interface{}
}{
{src: &pgtype.Numeric{Int: big.NewInt(150), Valid: true}, dst: &i8},
{src: &pgtype.Numeric{Int: big.NewInt(40000), Valid: true}, dst: &i16},
{src: &pgtype.Numeric{Int: big.NewInt(-1), Valid: true}, dst: &ui8},
{src: &pgtype.Numeric{Int: big.NewInt(-1), Valid: true}, dst: &ui16},
{src: &pgtype.Numeric{Int: big.NewInt(-1), Valid: true}, dst: &ui32},
{src: &pgtype.Numeric{Int: big.NewInt(-1), Valid: true}, dst: &ui64},
{src: &pgtype.Numeric{Int: big.NewInt(-1), Valid: true}, dst: &ui},
{src: &pgtype.Numeric{Int: big.NewInt(0)}, dst: &i32},
}
for i, tt := range errorTests {
err := tt.src.AssignTo(tt.dst)
if err == nil {
t.Errorf("%d: expected error but none was returned (%v -> %v)", i, tt.src, tt.dst)
}
}
}
func TestNumericEncodeDecodeBinary(t *testing.T) {
ci := pgtype.NewConnInfo()
tests := []interface{}{
123,
0.000012345,
1.00002345,
math.NaN(),
float32(math.NaN()),
math.Inf(1),
float32(math.Inf(1)),
math.Inf(-1),
float32(math.Inf(-1)),
}
for i, tt := range tests {
toString := func(n *pgtype.Numeric) string {
ci := pgtype.NewConnInfo()
text, err := n.EncodeText(ci, nil)
if err != nil {
t.Errorf("%d (EncodeText): %v", i, err)
}
return string(text)
}
numeric := &pgtype.Numeric{}
numeric.Set(tt)
encoded, err := numeric.EncodeBinary(ci, nil)
if err != nil {
t.Errorf("%d (EncodeBinary): %v", i, err)
}
decoded := &pgtype.Numeric{}
err = decoded.DecodeBinary(ci, encoded)
if err != nil {
t.Errorf("%d (DecodeBinary): %v", i, err)
}
text0 := toString(numeric)
text1 := toString(decoded)
if text0 != text1 {
t.Errorf("%d: expected %v to equal to %v, but doesn't", i, text0, text1)
}
}
testPgxCodec(t, "numeric", tests)
}
func TestNumericMarshalJSON(t *testing.T) {

View File

@ -290,7 +290,7 @@ func NewConnInfo() *ConnInfo {
ci.RegisterDataType(DataType{Name: "_polygon", OID: PolygonArrayOID, Codec: &ArrayCodec{ElementCodec: PolygonCodec{}, ElementOID: PolygonOID}})
ci.RegisterDataType(DataType{Name: "_name", OID: NameArrayOID, Codec: &ArrayCodec{ElementCodec: TextCodec{}, ElementOID: NameOID}})
ci.RegisterDataType(DataType{Name: "_char", OID: QCharArrayOID, Codec: &ArrayCodec{ElementCodec: QCharCodec{}, ElementOID: QCharOID}})
ci.RegisterDataType(DataType{Value: &NumericArray{}, Name: "_numeric", OID: NumericArrayOID})
ci.RegisterDataType(DataType{Name: "_numeric", OID: NumericArrayOID, Codec: &ArrayCodec{ElementCodec: NumericCodec{}, ElementOID: NumericOID}})
ci.RegisterDataType(DataType{Name: "_text", OID: TextArrayOID, Codec: &ArrayCodec{ElementCodec: TextCodec{}, ElementOID: TextOID}})
ci.RegisterDataType(DataType{Name: "_timestamp", OID: TimestampArrayOID, Codec: &ArrayCodec{ElementCodec: TimestampCodec{}, ElementOID: TimestampOID}})
ci.RegisterDataType(DataType{Name: "_timestamptz", OID: TimestamptzArrayOID, Codec: &ArrayCodec{ElementCodec: TimestamptzCodec{}, ElementOID: TimestamptzOID}})
@ -333,7 +333,7 @@ func NewConnInfo() *ConnInfo {
ci.RegisterDataType(DataType{Name: "lseg", OID: LsegOID, Codec: LsegCodec{}})
ci.RegisterDataType(DataType{Name: "macaddr", OID: MacaddrOID, Codec: MacaddrCodec{}})
ci.RegisterDataType(DataType{Name: "name", OID: NameOID, Codec: TextCodec{}})
ci.RegisterDataType(DataType{Value: &Numeric{}, Name: "numeric", OID: NumericOID})
ci.RegisterDataType(DataType{Name: "numeric", OID: NumericOID, Codec: NumericCodec{}})
// ci.RegisterDataType(DataType{Value: &Numrange{}, Name: "numrange", OID: NumrangeOID})
ci.RegisterDataType(DataType{Name: "oid", OID: OIDOID, Codec: Uint32Codec{}})
ci.RegisterDataType(DataType{Name: "path", OID: PathOID, Codec: PathCodec{}})