mirror of https://github.com/jackc/pgx.git
Convert numeric to Codec
parent
0056156904
commit
b9b5e35d0f
|
@ -901,7 +901,7 @@ func TestDomainType(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("did not find uint64 OID, %v", err)
|
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
|
// String is still an acceptable argument after registration
|
||||||
err = conn.QueryRow(context.Background(), "select $1::uint64", "7").Scan(&n)
|
err = conn.QueryRow(context.Background(), "select $1::uint64", "7").Scan(&n)
|
||||||
|
|
|
@ -273,6 +273,20 @@ func (w float32Wrapper) Int64Value() (Int8, error) {
|
||||||
return Int8{Int: int64(w), Valid: true}, nil
|
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
|
type float64Wrapper float64
|
||||||
|
|
||||||
func (w float64Wrapper) SkipUnderlyingTypePlan() {}
|
func (w float64Wrapper) SkipUnderlyingTypePlan() {}
|
||||||
|
@ -295,6 +309,20 @@ func (w float64Wrapper) Int64Value() (Int8, error) {
|
||||||
return Int8{Int: int64(w), Valid: true}, nil
|
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
|
type stringWrapper string
|
||||||
|
|
||||||
func (w stringWrapper) SkipUnderlyingTypePlan() {}
|
func (w stringWrapper) SkipUnderlyingTypePlan() {}
|
||||||
|
|
1103
pgtype/numeric.go
1103
pgtype/numeric.go
File diff suppressed because it is too large
Load Diff
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"reflect"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
@ -14,34 +14,6 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"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 {
|
func mustParseBigInt(t *testing.T, src string) *big.Int {
|
||||||
i := &big.Int{}
|
i := &big.Int{}
|
||||||
if _, ok := i.SetString(src, 10); !ok {
|
if _, ok := i.SetString(src, 10); !ok {
|
||||||
|
@ -50,368 +22,122 @@ func mustParseBigInt(t *testing.T, src string) *big.Int {
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNumericNormalize(t *testing.T) {
|
func isExpectedEqNumeric(a interface{}) func(interface{}) bool {
|
||||||
testutil.TestSuccessfulNormalize(t, []testutil.NormalizeTest{
|
return func(v interface{}) bool {
|
||||||
{
|
aa := a.(pgtype.Numeric)
|
||||||
SQL: "select '0'::numeric",
|
vv := v.(pgtype.Numeric)
|
||||||
Value: &pgtype.Numeric{Int: big.NewInt(0), Exp: 0, Valid: true},
|
|
||||||
},
|
if aa.Valid != vv.Valid {
|
||||||
{
|
return false
|
||||||
SQL: "select '1'::numeric",
|
}
|
||||||
Value: &pgtype.Numeric{Int: big.NewInt(1), Exp: 0, Valid: true},
|
|
||||||
},
|
// If NULL doesn't matter what the rest of the values are.
|
||||||
{
|
if !aa.Valid {
|
||||||
SQL: "select '10.00'::numeric",
|
return true
|
||||||
Value: &pgtype.Numeric{Int: big.NewInt(1000), Exp: -2, Valid: true},
|
}
|
||||||
},
|
|
||||||
{
|
if !(aa.NaN == vv.NaN && aa.InfinityModifier == vv.InfinityModifier) {
|
||||||
SQL: "select '1e-3'::numeric",
|
return false
|
||||||
Value: &pgtype.Numeric{Int: big.NewInt(1), Exp: -3, Valid: true},
|
}
|
||||||
},
|
|
||||||
{
|
// If NaN or InfinityModifier are set then Int and Exp don't matter.
|
||||||
SQL: "select '-1'::numeric",
|
if aa.NaN || aa.InfinityModifier != pgtype.None {
|
||||||
Value: &pgtype.Numeric{Int: big.NewInt(-1), Exp: 0, Valid: true},
|
return true
|
||||||
},
|
}
|
||||||
{
|
|
||||||
SQL: "select '10000'::numeric",
|
aaInt := (&big.Int{}).Set(aa.Int)
|
||||||
Value: &pgtype.Numeric{Int: big.NewInt(1), Exp: 4, Valid: true},
|
vvInt := (&big.Int{}).Set(vv.Int)
|
||||||
},
|
|
||||||
{
|
if aa.Exp < vv.Exp {
|
||||||
SQL: "select '3.14'::numeric",
|
mul := (&big.Int{}).Exp(big.NewInt(10), big.NewInt(int64(vv.Exp-aa.Exp)), nil)
|
||||||
Value: &pgtype.Numeric{Int: big.NewInt(314), Exp: -2, Valid: true},
|
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)
|
||||||
SQL: "select '1.1'::numeric",
|
aaInt.Mul(aaInt, mul)
|
||||||
Value: &pgtype.Numeric{Int: big.NewInt(11), Exp: -1, Valid: true},
|
}
|
||||||
},
|
|
||||||
{
|
return aaInt.Cmp(vvInt) == 0
|
||||||
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 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 := new(big.Int).Exp(big.NewInt(10), big.NewInt(147454), nil)
|
||||||
max.Add(max, big.NewInt(1))
|
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{}{
|
testPgxCodec(t, "numeric", []PgxTranscodeTestCase{
|
||||||
&pgtype.Numeric{NaN: true, Valid: true},
|
{mustParseNumeric(t, "1"), new(pgtype.Numeric), isExpectedEqNumeric(mustParseNumeric(t, "1"))},
|
||||||
&pgtype.Numeric{InfinityModifier: pgtype.Infinity, Valid: true},
|
{mustParseNumeric(t, "3.14159"), new(pgtype.Numeric), isExpectedEqNumeric(mustParseNumeric(t, "3.14159"))},
|
||||||
&pgtype.Numeric{InfinityModifier: pgtype.NegativeInfinity, Valid: true},
|
{mustParseNumeric(t, "100010001"), new(pgtype.Numeric), isExpectedEqNumeric(mustParseNumeric(t, "100010001"))},
|
||||||
|
{mustParseNumeric(t, "100010001.0001"), new(pgtype.Numeric), isExpectedEqNumeric(mustParseNumeric(t, "100010001.0001"))},
|
||||||
&pgtype.Numeric{Int: big.NewInt(0), Exp: 0, Valid: true},
|
{mustParseNumeric(t, "4237234789234789289347892374324872138321894178943189043890124832108934.43219085471578891547854892438945012347981"), new(pgtype.Numeric), isExpectedEqNumeric(mustParseNumeric(t, "4237234789234789289347892374324872138321894178943189043890124832108934.43219085471578891547854892438945012347981"))},
|
||||||
&pgtype.Numeric{Int: big.NewInt(1), Exp: 0, Valid: true},
|
{mustParseNumeric(t, "0.8925092023480223478923478978978937897879595901237890234789243679037419057877231734823098432903527585734549035904590854890345905434578345789347890402348952348905890489054234237489234987723894789234"), new(pgtype.Numeric), isExpectedEqNumeric(mustParseNumeric(t, "0.8925092023480223478923478978978937897879595901237890234789243679037419057877231734823098432903527585734549035904590854890345905434578345789347890402348952348905890489054234237489234987723894789234"))},
|
||||||
&pgtype.Numeric{Int: big.NewInt(-1), Exp: 0, Valid: true},
|
{mustParseNumeric(t, "0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000123"), new(pgtype.Numeric), isExpectedEqNumeric(mustParseNumeric(t, "0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000123"))},
|
||||||
&pgtype.Numeric{Int: big.NewInt(1), Exp: 6, Valid: true},
|
{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})},
|
||||||
// preserves significant zeroes
|
{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: big.NewInt(10000000), Exp: -1, 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: big.NewInt(10000000), Exp: -2, 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: big.NewInt(10000000), Exp: -3, 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: big.NewInt(10000000), Exp: -4, 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: big.NewInt(10000000), Exp: -5, 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: big.NewInt(10000000), Exp: -6, 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: big.NewInt(314), Exp: -2, 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: big.NewInt(123), Exp: -7, 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{Int: big.NewInt(123), Exp: -8, Valid: true},
|
{pgtype.Numeric{NaN: true, Valid: true}, new(pgtype.Numeric), isExpectedEqNumeric(pgtype.Numeric{NaN: true, Valid: true})},
|
||||||
&pgtype.Numeric{Int: big.NewInt(123), Exp: -9, Valid: true},
|
{pgtype.Numeric{InfinityModifier: pgtype.Infinity, Valid: true}, new(pgtype.Numeric), isExpectedEqNumeric(pgtype.Numeric{InfinityModifier: pgtype.Infinity, Valid: true})},
|
||||||
&pgtype.Numeric{Int: big.NewInt(123), Exp: -1500, Valid: true},
|
{pgtype.Numeric{InfinityModifier: pgtype.NegativeInfinity, Valid: true}, new(pgtype.Numeric), isExpectedEqNumeric(pgtype.Numeric{InfinityModifier: pgtype.NegativeInfinity, Valid: true})},
|
||||||
&pgtype.Numeric{Int: mustParseBigInt(t, "2437"), Exp: 23790, Valid: true},
|
{longestNumeric, new(pgtype.Numeric), isExpectedEqNumeric(longestNumeric)},
|
||||||
&pgtype.Numeric{Int: mustParseBigInt(t, "243723409723490243842378942378901237502734019231380123"), Exp: 23790, Valid: true},
|
{mustParseNumeric(t, "1"), new(int64), isExpectedEq(int64(1))},
|
||||||
&pgtype.Numeric{Int: mustParseBigInt(t, "43723409723490243842378942378901237502734019231380123"), Exp: 80, Valid: true},
|
{math.NaN(), new(float64), func(a interface{}) bool { return math.IsNaN(a.(float64)) }},
|
||||||
&pgtype.Numeric{Int: mustParseBigInt(t, "3723409723490243842378942378901237502734019231380123"), Exp: 81, Valid: true},
|
{float32(math.NaN()), new(float32), func(a interface{}) bool { return math.IsNaN(float64(a.(float32))) }},
|
||||||
&pgtype.Numeric{Int: mustParseBigInt(t, "723409723490243842378942378901237502734019231380123"), Exp: 82, Valid: true},
|
{math.Inf(1), new(float64), isExpectedEq(math.Inf(1))},
|
||||||
&pgtype.Numeric{Int: mustParseBigInt(t, "23409723490243842378942378901237502734019231380123"), Exp: 83, Valid: true},
|
{float32(math.Inf(1)), new(float32), isExpectedEq(float32(math.Inf(1)))},
|
||||||
&pgtype.Numeric{Int: mustParseBigInt(t, "3409723490243842378942378901237502734019231380123"), Exp: 84, Valid: true},
|
{math.Inf(-1), new(float64), isExpectedEq(math.Inf(-1))},
|
||||||
&pgtype.Numeric{Int: mustParseBigInt(t, "913423409823409243892349028349023482934092340892390101"), Exp: -14021, Valid: true},
|
{float32(math.Inf(-1)), new(float32), isExpectedEq(float32(math.Inf(-1)))},
|
||||||
&pgtype.Numeric{Int: mustParseBigInt(t, "13423409823409243892349028349023482934092340892390101"), Exp: -90, Valid: true},
|
{int64(-1), new(pgtype.Numeric), isExpectedEqNumeric(mustParseNumeric(t, "-1"))},
|
||||||
&pgtype.Numeric{Int: mustParseBigInt(t, "3423409823409243892349028349023482934092340892390101"), Exp: -91, Valid: true},
|
{int64(0), new(pgtype.Numeric), isExpectedEqNumeric(mustParseNumeric(t, "0"))},
|
||||||
&pgtype.Numeric{Int: mustParseBigInt(t, "423409823409243892349028349023482934092340892390101"), Exp: -92, Valid: true},
|
{int64(1), new(pgtype.Numeric), isExpectedEqNumeric(mustParseNumeric(t, "1"))},
|
||||||
&pgtype.Numeric{Int: mustParseBigInt(t, "23409823409243892349028349023482934092340892390101"), Exp: -93, Valid: true},
|
{int64(math.MinInt64), new(pgtype.Numeric), isExpectedEqNumeric(mustParseNumeric(t, strconv.FormatInt(math.MinInt64, 10)))},
|
||||||
&pgtype.Numeric{Int: mustParseBigInt(t, "3409823409243892349028349023482934092340892390101"), Exp: -94, Valid: true},
|
{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)))},
|
||||||
longestNumeric,
|
{int64(math.MaxInt64 - 1), new(pgtype.Numeric), isExpectedEqNumeric(mustParseNumeric(t, strconv.FormatInt(math.MaxInt64-1, 10)))},
|
||||||
|
{pgtype.Numeric{}, new(pgtype.Numeric), isExpectedEq(pgtype.Numeric{})},
|
||||||
&pgtype.Numeric{},
|
{nil, new(pgtype.Numeric), isExpectedEq(pgtype.Numeric{})},
|
||||||
}, func(aa, bb interface{}) bool {
|
|
||||||
a := aa.(pgtype.Numeric)
|
|
||||||
b := bb.(pgtype.Numeric)
|
|
||||||
|
|
||||||
return numericEqual(&a, &b)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNumericTranscodeFuzz(t *testing.T) {
|
func TestNumericCodecFuzz(t *testing.T) {
|
||||||
r := rand.New(rand.NewSource(0))
|
r := rand.New(rand.NewSource(0))
|
||||||
max := &big.Int{}
|
max := &big.Int{}
|
||||||
max.SetString("9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999", 10)
|
max.SetString("9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999", 10)
|
||||||
|
|
||||||
values := make([]interface{}, 0, 2000)
|
tests := make([]PgxTranscodeTestCase, 0, 2000)
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
for j := -50; j < 50; j++ {
|
for j := -50; j < 50; j++ {
|
||||||
num := (&big.Int{}).Rand(r, max)
|
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 := &big.Int{}
|
||||||
negNum.Neg(num)
|
negNum.Neg(num)
|
||||||
values = append(values, &pgtype.Numeric{Int: num, Exp: int32(j), Valid: true})
|
n = pgtype.Numeric{Int: negNum, Exp: int32(j), Valid: true}
|
||||||
values = append(values, &pgtype.Numeric{Int: negNum, Exp: int32(j), Valid: true})
|
tests = append(tests, PgxTranscodeTestCase{n, new(pgtype.Numeric), isExpectedEqNumeric(n)})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
testutil.TestSuccessfulTranscodeEqFunc(t, "numeric", values,
|
testPgxCodec(t, "numeric", tests)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNumericMarshalJSON(t *testing.T) {
|
func TestNumericMarshalJSON(t *testing.T) {
|
||||||
|
|
|
@ -290,7 +290,7 @@ func NewConnInfo() *ConnInfo {
|
||||||
ci.RegisterDataType(DataType{Name: "_polygon", OID: PolygonArrayOID, Codec: &ArrayCodec{ElementCodec: PolygonCodec{}, ElementOID: PolygonOID}})
|
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: "_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{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: "_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: "_timestamp", OID: TimestampArrayOID, Codec: &ArrayCodec{ElementCodec: TimestampCodec{}, ElementOID: TimestampOID}})
|
||||||
ci.RegisterDataType(DataType{Name: "_timestamptz", OID: TimestamptzArrayOID, Codec: &ArrayCodec{ElementCodec: TimestamptzCodec{}, ElementOID: TimestamptzOID}})
|
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: "lseg", OID: LsegOID, Codec: LsegCodec{}})
|
||||||
ci.RegisterDataType(DataType{Name: "macaddr", OID: MacaddrOID, Codec: MacaddrCodec{}})
|
ci.RegisterDataType(DataType{Name: "macaddr", OID: MacaddrOID, Codec: MacaddrCodec{}})
|
||||||
ci.RegisterDataType(DataType{Name: "name", OID: NameOID, Codec: TextCodec{}})
|
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{Value: &Numrange{}, Name: "numrange", OID: NumrangeOID})
|
||||||
ci.RegisterDataType(DataType{Name: "oid", OID: OIDOID, Codec: Uint32Codec{}})
|
ci.RegisterDataType(DataType{Name: "oid", OID: OIDOID, Codec: Uint32Codec{}})
|
||||||
ci.RegisterDataType(DataType{Name: "path", OID: PathOID, Codec: PathCodec{}})
|
ci.RegisterDataType(DataType{Name: "path", OID: PathOID, Codec: PathCodec{}})
|
||||||
|
|
Loading…
Reference in New Issue