diff --git a/conn_test.go b/conn_test.go index 0d7bcb31..cedd6a7a 100644 --- a/conn_test.go +++ b/conn_test.go @@ -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) diff --git a/pgtype/builtin_wrappers.go b/pgtype/builtin_wrappers.go index 3020f9bb..3becf06b 100644 --- a/pgtype/builtin_wrappers.go +++ b/pgtype/builtin_wrappers.go @@ -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() {} diff --git a/pgtype/numeric.go b/pgtype/numeric.go index b24f433c..435c9618 100644 --- a/pgtype/numeric.go +++ b/pgtype/numeric.go @@ -55,422 +55,69 @@ var bigNBaseX2 *big.Int = big.NewInt(nbase * nbase) var bigNBaseX3 *big.Int = big.NewInt(nbase * nbase * nbase) var bigNBaseX4 *big.Int = big.NewInt(nbase * nbase * nbase * nbase) +type NumericScanner interface { + ScanNumeric(v Numeric) error +} + +type NumericValuer interface { + NumericValue() (Numeric, error) +} + type Numeric struct { Int *big.Int Exp int32 NaN bool InfinityModifier InfinityModifier Valid bool - - NumericDecoderWrapper func(interface{}) NumericDecoder - Getter func(Numeric) interface{} } -func (n *Numeric) NewTypeValue() Value { - return &Numeric{ - NumericDecoderWrapper: n.NumericDecoderWrapper, - Getter: n.Getter, - } -} - -func (n *Numeric) TypeName() string { - return "numeric" -} - -func (dst *Numeric) setNil() { - dst.Int = nil - dst.Exp = 0 - dst.NaN = false - dst.Valid = false -} - -func (dst *Numeric) setNaN() { - dst.Int = nil - dst.Exp = 0 - dst.NaN = true - dst.Valid = true -} - -func (dst *Numeric) setNumber(i *big.Int, exp int32) { - dst.Int = i - dst.Exp = exp - dst.NaN = false - dst.Valid = true -} - -func (dst *Numeric) Set(src interface{}) error { - if src == nil { - dst.setNil() - return nil - } - - if value, ok := src.(interface{ Get() interface{} }); ok { - value2 := value.Get() - if value2 != value { - return dst.Set(value2) - } - } - - switch value := src.(type) { - case float32: - if math.IsNaN(float64(value)) { - dst.setNaN() - return nil - } else if math.IsInf(float64(value), 1) { - *dst = Numeric{Valid: true, InfinityModifier: Infinity} - return nil - } else if math.IsInf(float64(value), -1) { - *dst = Numeric{Valid: true, InfinityModifier: NegativeInfinity} - return nil - } - num, exp, err := parseNumericString(strconv.FormatFloat(float64(value), 'f', -1, 64)) - if err != nil { - return err - } - dst.setNumber(num, exp) - case float64: - if math.IsNaN(value) { - dst.setNaN() - return nil - } else if math.IsInf(value, 1) { - *dst = Numeric{Valid: true, InfinityModifier: Infinity} - return nil - } else if math.IsInf(value, -1) { - *dst = Numeric{Valid: true, InfinityModifier: NegativeInfinity} - return nil - } - num, exp, err := parseNumericString(strconv.FormatFloat(value, 'f', -1, 64)) - if err != nil { - return err - } - dst.setNumber(num, exp) - case int8: - dst.setNumber(big.NewInt(int64(value)), 0) - case uint8: - dst.setNumber(big.NewInt(int64(value)), 0) - case int16: - dst.setNumber(big.NewInt(int64(value)), 0) - case uint16: - dst.setNumber(big.NewInt(int64(value)), 0) - case int32: - dst.setNumber(big.NewInt(int64(value)), 0) - case uint32: - dst.setNumber(big.NewInt(int64(value)), 0) - case int64: - dst.setNumber(big.NewInt(value), 0) - case uint64: - dst.setNumber((&big.Int{}).SetUint64(value), 0) - case int: - dst.setNumber(big.NewInt(int64(value)), 0) - case uint: - dst.setNumber((&big.Int{}).SetUint64(uint64(value)), 0) - case string: - num, exp, err := parseNumericString(value) - if err != nil { - return err - } - dst.setNumber(num, exp) - case *float64: - if value == nil { - dst.setNil() - } else { - return dst.Set(*value) - } - case *float32: - if value == nil { - dst.setNil() - } else { - return dst.Set(*value) - } - case *int8: - if value == nil { - dst.setNil() - } else { - return dst.Set(*value) - } - case *uint8: - if value == nil { - dst.setNil() - } else { - return dst.Set(*value) - } - case *int16: - if value == nil { - dst.setNil() - } else { - return dst.Set(*value) - } - case *uint16: - if value == nil { - dst.setNil() - } else { - return dst.Set(*value) - } - case *int32: - if value == nil { - dst.setNil() - } else { - return dst.Set(*value) - } - case *uint32: - if value == nil { - dst.setNil() - } else { - return dst.Set(*value) - } - case *int64: - if value == nil { - dst.setNil() - } else { - return dst.Set(*value) - } - case *uint64: - if value == nil { - dst.setNil() - } else { - return dst.Set(*value) - } - case *int: - if value == nil { - dst.setNil() - } else { - return dst.Set(*value) - } - case *uint: - if value == nil { - dst.setNil() - } else { - return dst.Set(*value) - } - case *string: - if value == nil { - dst.setNil() - } else { - return dst.Set(*value) - } - case InfinityModifier: - *dst = Numeric{InfinityModifier: value, Valid: true} - default: - if originalSrc, ok := underlyingNumberType(src); ok { - return dst.Set(originalSrc) - } - return fmt.Errorf("cannot convert %v to Numeric", value) - } - +func (n *Numeric) ScanNumeric(v Numeric) error { + *n = v return nil } -func (dst Numeric) Get() interface{} { - if dst.Getter != nil { - return dst.Getter(dst) - } - - if !dst.Valid { - return nil - } - - if dst.InfinityModifier != None { - return dst.InfinityModifier - } - return dst +func (n Numeric) NumericValue() (Numeric, error) { + return n, nil } -type NumericDecoder interface { - DecodeNumeric(*Numeric) error -} - -func (src *Numeric) AssignTo(dst interface{}) error { - if d, ok := dst.(NumericDecoder); ok { - return d.DecodeNumeric(src) - } else { - if src.NumericDecoderWrapper != nil { - d = src.NumericDecoderWrapper(dst) - if d != nil { - return d.DecodeNumeric(src) - } - } - } - - if !src.Valid { - return NullAssignTo(dst) - } - - switch v := dst.(type) { - case *float32: - f, err := src.toFloat64() - if err != nil { - return err - } - return float64AssignTo(f, src.Valid, dst) - case *float64: - f, err := src.toFloat64() - if err != nil { - return err - } - return float64AssignTo(f, src.Valid, dst) - case *int: - normalizedInt, err := src.toBigInt() - if err != nil { - return err - } - if normalizedInt.Cmp(bigMaxInt) > 0 { - return fmt.Errorf("%v is greater than maximum value for %T", normalizedInt, *v) - } - if normalizedInt.Cmp(bigMinInt) < 0 { - return fmt.Errorf("%v is less than minimum value for %T", normalizedInt, *v) - } - *v = int(normalizedInt.Int64()) - case *int8: - normalizedInt, err := src.toBigInt() - if err != nil { - return err - } - if normalizedInt.Cmp(bigMaxInt8) > 0 { - return fmt.Errorf("%v is greater than maximum value for %T", normalizedInt, *v) - } - if normalizedInt.Cmp(bigMinInt8) < 0 { - return fmt.Errorf("%v is less than minimum value for %T", normalizedInt, *v) - } - *v = int8(normalizedInt.Int64()) - case *int16: - normalizedInt, err := src.toBigInt() - if err != nil { - return err - } - if normalizedInt.Cmp(bigMaxInt16) > 0 { - return fmt.Errorf("%v is greater than maximum value for %T", normalizedInt, *v) - } - if normalizedInt.Cmp(bigMinInt16) < 0 { - return fmt.Errorf("%v is less than minimum value for %T", normalizedInt, *v) - } - *v = int16(normalizedInt.Int64()) - case *int32: - normalizedInt, err := src.toBigInt() - if err != nil { - return err - } - if normalizedInt.Cmp(bigMaxInt32) > 0 { - return fmt.Errorf("%v is greater than maximum value for %T", normalizedInt, *v) - } - if normalizedInt.Cmp(bigMinInt32) < 0 { - return fmt.Errorf("%v is less than minimum value for %T", normalizedInt, *v) - } - *v = int32(normalizedInt.Int64()) - case *int64: - normalizedInt, err := src.toBigInt() - if err != nil { - return err - } - if normalizedInt.Cmp(bigMaxInt64) > 0 { - return fmt.Errorf("%v is greater than maximum value for %T", normalizedInt, *v) - } - if normalizedInt.Cmp(bigMinInt64) < 0 { - return fmt.Errorf("%v is less than minimum value for %T", normalizedInt, *v) - } - *v = normalizedInt.Int64() - case *uint: - normalizedInt, err := src.toBigInt() - if err != nil { - return err - } - if normalizedInt.Cmp(big0) < 0 { - return fmt.Errorf("%d is less than zero for %T", normalizedInt, *v) - } else if normalizedInt.Cmp(bigMaxUint) > 0 { - return fmt.Errorf("%d is greater than maximum value for %T", normalizedInt, *v) - } - *v = uint(normalizedInt.Uint64()) - case *uint8: - normalizedInt, err := src.toBigInt() - if err != nil { - return err - } - if normalizedInt.Cmp(big0) < 0 { - return fmt.Errorf("%d is less than zero for %T", normalizedInt, *v) - } else if normalizedInt.Cmp(bigMaxUint8) > 0 { - return fmt.Errorf("%d is greater than maximum value for %T", normalizedInt, *v) - } - *v = uint8(normalizedInt.Uint64()) - case *uint16: - normalizedInt, err := src.toBigInt() - if err != nil { - return err - } - if normalizedInt.Cmp(big0) < 0 { - return fmt.Errorf("%d is less than zero for %T", normalizedInt, *v) - } else if normalizedInt.Cmp(bigMaxUint16) > 0 { - return fmt.Errorf("%d is greater than maximum value for %T", normalizedInt, *v) - } - *v = uint16(normalizedInt.Uint64()) - case *uint32: - normalizedInt, err := src.toBigInt() - if err != nil { - return err - } - if normalizedInt.Cmp(big0) < 0 { - return fmt.Errorf("%d is less than zero for %T", normalizedInt, *v) - } else if normalizedInt.Cmp(bigMaxUint32) > 0 { - return fmt.Errorf("%d is greater than maximum value for %T", normalizedInt, *v) - } - *v = uint32(normalizedInt.Uint64()) - case *uint64: - normalizedInt, err := src.toBigInt() - if err != nil { - return err - } - if normalizedInt.Cmp(big0) < 0 { - return fmt.Errorf("%d is less than zero for %T", normalizedInt, *v) - } else if normalizedInt.Cmp(bigMaxUint64) > 0 { - return fmt.Errorf("%d is greater than maximum value for %T", normalizedInt, *v) - } - *v = normalizedInt.Uint64() - default: - if nextDst, retry := GetAssignToDstType(dst); retry { - return src.AssignTo(nextDst) - } - return fmt.Errorf("unable to assign to %T", dst) - } - - return nil -} - -func (dst *Numeric) toBigInt() (*big.Int, error) { - if dst.Exp == 0 { - return dst.Int, nil +func (n *Numeric) toBigInt() (*big.Int, error) { + if n.Exp == 0 { + return n.Int, nil } num := &big.Int{} - num.Set(dst.Int) - if dst.Exp > 0 { + num.Set(n.Int) + if n.Exp > 0 { mul := &big.Int{} - mul.Exp(big10, big.NewInt(int64(dst.Exp)), nil) + mul.Exp(big10, big.NewInt(int64(n.Exp)), nil) num.Mul(num, mul) return num, nil } div := &big.Int{} - div.Exp(big10, big.NewInt(int64(-dst.Exp)), nil) + div.Exp(big10, big.NewInt(int64(-n.Exp)), nil) remainder := &big.Int{} num.DivMod(num, div, remainder) if remainder.Cmp(big0) != 0 { - return nil, fmt.Errorf("cannot convert %v to integer", dst) + return nil, fmt.Errorf("cannot convert %v to integer", n) } return num, nil } -func (src *Numeric) toFloat64() (float64, error) { - if src.NaN { +func (n *Numeric) toFloat64() (float64, error) { + if n.NaN { return math.NaN(), nil - } else if src.InfinityModifier == Infinity { + } else if n.InfinityModifier == Infinity { return math.Inf(1), nil - } else if src.InfinityModifier == NegativeInfinity { + } else if n.InfinityModifier == NegativeInfinity { return math.Inf(-1), nil } buf := make([]byte, 0, 32) - buf = append(buf, src.Int.String()...) + buf = append(buf, n.Int.String()...) buf = append(buf, 'e') - buf = append(buf, strconv.FormatInt(int64(src.Exp), 10)...) + buf = append(buf, strconv.FormatInt(int64(n.Exp), 10)...) f, err := strconv.ParseFloat(string(buf), 64) if err != nil { @@ -479,32 +126,6 @@ func (src *Numeric) toFloat64() (float64, error) { return f, nil } -func (dst *Numeric) DecodeText(ci *ConnInfo, src []byte) error { - if src == nil { - dst.setNil() - return nil - } - - if string(src) == "NaN" { - dst.setNaN() - return nil - } else if string(src) == "Infinity" { - *dst = Numeric{Valid: true, InfinityModifier: Infinity} - return nil - } else if string(src) == "-Infinity" { - *dst = Numeric{Valid: true, InfinityModifier: NegativeInfinity} - return nil - } - - num, exp, err := parseNumericString(string(src)) - if err != nil { - return err - } - - dst.setNumber(num, exp) - return nil -} - func parseNumericString(str string) (n *big.Int, exp int32, err error) { parts := strings.SplitN(str, ".", 2) digits := strings.Join(parts, "") @@ -526,12 +147,388 @@ func parseNumericString(str string) (n *big.Int, exp int32, err error) { return accum, exp, nil } -func (dst *Numeric) DecodeBinary(ci *ConnInfo, src []byte) error { +func nbaseDigitsToInt64(src []byte) (accum int64, bytesRead, digitsRead int) { + digits := len(src) / 2 + if digits > 4 { + digits = 4 + } + + rp := 0 + + for i := 0; i < digits; i++ { + if i > 0 { + accum *= nbase + } + accum += int64(binary.BigEndian.Uint16(src[rp:])) + rp += 2 + } + + return accum, rp, digits +} + +// Scan implements the database/sql Scanner interface. +func (n *Numeric) Scan(src interface{}) error { if src == nil { - dst.setNil() + *n = Numeric{} return nil } + switch src := src.(type) { + case string: + return scanPlanTextAnyToNumericScanner{}.Scan(nil, 0, TextFormatCode, []byte(src), n) + } + + return fmt.Errorf("cannot scan %T", src) +} + +// Value implements the database/sql/driver Valuer interface. +func (n Numeric) Value() (driver.Value, error) { + if !n.Valid { + return nil, nil + } + + buf, err := NumericCodec{}.PlanEncode(nil, 0, TextFormatCode, n).Encode(n, nil) + if err != nil { + return nil, err + } + return string(buf), err +} + +func (n Numeric) MarshalJSON() ([]byte, error) { + if !n.Valid { + return []byte("null"), nil + } + + if n.NaN { + return []byte(`"NaN"`), nil + } + + intStr := n.Int.String() + buf := &bytes.Buffer{} + exp := int(n.Exp) + if exp > 0 { + buf.WriteString(intStr) + for i := 0; i < exp; i++ { + buf.WriteByte('0') + } + } else if exp < 0 { + if len(intStr) <= -exp { + buf.WriteString("0.") + leadingZeros := -exp - len(intStr) + for i := 0; i < leadingZeros; i++ { + buf.WriteByte('0') + } + buf.WriteString(intStr) + } else if len(intStr) > -exp { + dpPos := len(intStr) + exp + buf.WriteString(intStr[:dpPos]) + buf.WriteByte('.') + buf.WriteString(intStr[dpPos:]) + } + } else { + buf.WriteString(intStr) + } + + return buf.Bytes(), nil +} + +type NumericCodec struct{} + +func (NumericCodec) FormatSupported(format int16) bool { + return format == TextFormatCode || format == BinaryFormatCode +} + +func (NumericCodec) PreferredFormat() int16 { + return BinaryFormatCode +} + +func (NumericCodec) PlanEncode(ci *ConnInfo, oid uint32, format int16, value interface{}) EncodePlan { + switch format { + case BinaryFormatCode: + switch value.(type) { + case NumericValuer: + return encodePlanNumericCodecBinaryNumericValuer{} + case Float64Valuer: + return encodePlanNumericCodecBinaryFloat64Valuer{} + case Int64Valuer: + return encodePlanNumericCodecBinaryInt64Valuer{} + } + case TextFormatCode: + switch value.(type) { + case NumericValuer: + return encodePlanNumericCodecTextNumericValuer{} + case Float64Valuer: + return encodePlanNumericCodecTextFloat64Valuer{} + case Int64Valuer: + return encodePlanNumericCodecTextInt64Valuer{} + } + } + + return nil +} + +type encodePlanNumericCodecBinaryNumericValuer struct{} + +func (encodePlanNumericCodecBinaryNumericValuer) Encode(value interface{}, buf []byte) (newBuf []byte, err error) { + n, err := value.(NumericValuer).NumericValue() + if err != nil { + return nil, err + } + + return encodeNumericBinary(n, buf) +} + +type encodePlanNumericCodecBinaryFloat64Valuer struct{} + +func (encodePlanNumericCodecBinaryFloat64Valuer) Encode(value interface{}, buf []byte) (newBuf []byte, err error) { + n, err := value.(Float64Valuer).Float64Value() + if err != nil { + return nil, err + } + + if !n.Valid { + return nil, nil + } + + if math.IsNaN(n.Float) { + return encodeNumericBinary(Numeric{NaN: true, Valid: true}, buf) + } else if math.IsInf(n.Float, 1) { + return encodeNumericBinary(Numeric{InfinityModifier: Infinity, Valid: true}, buf) + } else if math.IsInf(n.Float, -1) { + return encodeNumericBinary(Numeric{InfinityModifier: NegativeInfinity, Valid: true}, buf) + } + num, exp, err := parseNumericString(strconv.FormatFloat(n.Float, 'f', -1, 64)) + if err != nil { + return nil, err + } + + return encodeNumericBinary(Numeric{Int: num, Exp: exp, Valid: true}, buf) +} + +type encodePlanNumericCodecBinaryInt64Valuer struct{} + +func (encodePlanNumericCodecBinaryInt64Valuer) Encode(value interface{}, buf []byte) (newBuf []byte, err error) { + n, err := value.(Int64Valuer).Int64Value() + if err != nil { + return nil, err + } + + if !n.Valid { + return nil, nil + } + + return encodeNumericBinary(Numeric{Int: big.NewInt(n.Int), Valid: true}, buf) +} + +func encodeNumericBinary(n Numeric, buf []byte) (newBuf []byte, err error) { + if !n.Valid { + return nil, nil + } + + if n.NaN { + buf = pgio.AppendUint64(buf, pgNumericNaN) + return buf, nil + } else if n.InfinityModifier == Infinity { + buf = pgio.AppendUint64(buf, pgNumericPosInf) + return buf, nil + } else if n.InfinityModifier == NegativeInfinity { + buf = pgio.AppendUint64(buf, pgNumericNegInf) + return buf, nil + } + + var sign int16 + if n.Int.Cmp(big0) < 0 { + sign = 16384 + } + + absInt := &big.Int{} + wholePart := &big.Int{} + fracPart := &big.Int{} + remainder := &big.Int{} + absInt.Abs(n.Int) + + // Normalize absInt and exp to where exp is always a multiple of 4. This makes + // converting to 16-bit base 10,000 digits easier. + var exp int32 + switch n.Exp % 4 { + case 1, -3: + exp = n.Exp - 1 + absInt.Mul(absInt, big10) + case 2, -2: + exp = n.Exp - 2 + absInt.Mul(absInt, big100) + case 3, -1: + exp = n.Exp - 3 + absInt.Mul(absInt, big1000) + default: + exp = n.Exp + } + + if exp < 0 { + divisor := &big.Int{} + divisor.Exp(big10, big.NewInt(int64(-exp)), nil) + wholePart.DivMod(absInt, divisor, fracPart) + fracPart.Add(fracPart, divisor) + } else { + wholePart = absInt + } + + var wholeDigits, fracDigits []int16 + + for wholePart.Cmp(big0) != 0 { + wholePart.DivMod(wholePart, bigNBase, remainder) + wholeDigits = append(wholeDigits, int16(remainder.Int64())) + } + + if fracPart.Cmp(big0) != 0 { + for fracPart.Cmp(big1) != 0 { + fracPart.DivMod(fracPart, bigNBase, remainder) + fracDigits = append(fracDigits, int16(remainder.Int64())) + } + } + + buf = pgio.AppendInt16(buf, int16(len(wholeDigits)+len(fracDigits))) + + var weight int16 + if len(wholeDigits) > 0 { + weight = int16(len(wholeDigits) - 1) + if exp > 0 { + weight += int16(exp / 4) + } + } else { + weight = int16(exp/4) - 1 + int16(len(fracDigits)) + } + buf = pgio.AppendInt16(buf, weight) + + buf = pgio.AppendInt16(buf, sign) + + var dscale int16 + if n.Exp < 0 { + dscale = int16(-n.Exp) + } + buf = pgio.AppendInt16(buf, dscale) + + for i := len(wholeDigits) - 1; i >= 0; i-- { + buf = pgio.AppendInt16(buf, wholeDigits[i]) + } + + for i := len(fracDigits) - 1; i >= 0; i-- { + buf = pgio.AppendInt16(buf, fracDigits[i]) + } + + return buf, nil +} + +type encodePlanNumericCodecTextNumericValuer struct{} + +func (encodePlanNumericCodecTextNumericValuer) Encode(value interface{}, buf []byte) (newBuf []byte, err error) { + n, err := value.(NumericValuer).NumericValue() + if err != nil { + return nil, err + } + + return encodeNumericText(n, buf) +} + +type encodePlanNumericCodecTextFloat64Valuer struct{} + +func (encodePlanNumericCodecTextFloat64Valuer) Encode(value interface{}, buf []byte) (newBuf []byte, err error) { + n, err := value.(Float64Valuer).Float64Value() + if err != nil { + return nil, err + } + + if !n.Valid { + return nil, nil + } + + if math.IsNaN(n.Float) { + return encodeNumericBinary(Numeric{NaN: true, Valid: true}, buf) + } else if math.IsInf(n.Float, 1) { + return encodeNumericBinary(Numeric{InfinityModifier: Infinity, Valid: true}, buf) + } else if math.IsInf(n.Float, -1) { + return encodeNumericBinary(Numeric{InfinityModifier: NegativeInfinity, Valid: true}, buf) + } + num, exp, err := parseNumericString(strconv.FormatFloat(n.Float, 'f', -1, 64)) + if err != nil { + return nil, err + } + + return encodeNumericText(Numeric{Int: num, Exp: exp, Valid: true}, buf) +} + +type encodePlanNumericCodecTextInt64Valuer struct{} + +func (encodePlanNumericCodecTextInt64Valuer) Encode(value interface{}, buf []byte) (newBuf []byte, err error) { + n, err := value.(Int64Valuer).Int64Value() + if err != nil { + return nil, err + } + + if !n.Valid { + return nil, nil + } + + return encodeNumericText(Numeric{Int: big.NewInt(n.Int), Valid: true}, buf) +} + +func encodeNumericText(n Numeric, buf []byte) (newBuf []byte, err error) { + if !n.Valid { + return nil, nil + } + + if n.NaN { + buf = append(buf, "NaN"...) + return buf, nil + } else if n.InfinityModifier == Infinity { + buf = append(buf, "Infinity"...) + return buf, nil + } else if n.InfinityModifier == NegativeInfinity { + buf = append(buf, "-Infinity"...) + return buf, nil + } + + buf = append(buf, n.Int.String()...) + buf = append(buf, 'e') + buf = append(buf, strconv.FormatInt(int64(n.Exp), 10)...) + return buf, nil +} + +func (NumericCodec) PlanScan(ci *ConnInfo, oid uint32, format int16, target interface{}, actualTarget bool) ScanPlan { + + switch format { + case BinaryFormatCode: + switch target.(type) { + case NumericScanner: + return scanPlanBinaryNumericToNumericScanner{} + case Float64Scanner: + return scanPlanBinaryNumericToFloat64Scanner{} + case Int64Scanner: + return scanPlanBinaryNumericToInt64Scanner{} + } + case TextFormatCode: + switch target.(type) { + case NumericScanner: + return scanPlanTextAnyToNumericScanner{} + case Float64Scanner: + return scanPlanTextAnyToFloat64Scanner{} + case Int64Scanner: + return scanPlanTextAnyToInt64Scanner{} + } + } + + return nil +} + +type scanPlanBinaryNumericToNumericScanner struct{} + +func (scanPlanBinaryNumericToNumericScanner) Scan(ci *ConnInfo, oid uint32, formatCode int16, src []byte, dst interface{}) error { + scanner := (dst).(NumericScanner) + + if src == nil { + return scanner.ScanNumeric(Numeric{}) + } + if len(src) < 8 { return fmt.Errorf("numeric incomplete %v", src) } @@ -547,19 +544,15 @@ func (dst *Numeric) DecodeBinary(ci *ConnInfo, src []byte) error { rp += 2 if sign == pgNumericNaNSign { - dst.setNaN() - return nil + return scanner.ScanNumeric(Numeric{NaN: true, Valid: true}) } else if sign == pgNumericPosInfSign { - *dst = Numeric{Valid: true, InfinityModifier: Infinity} - return nil + return scanner.ScanNumeric(Numeric{InfinityModifier: Infinity, Valid: true}) } else if sign == pgNumericNegInfSign { - *dst = Numeric{Valid: true, InfinityModifier: NegativeInfinity} - return nil + return scanner.ScanNumeric(Numeric{InfinityModifier: NegativeInfinity, Valid: true}) } if ndigits == 0 { - dst.setNumber(big.NewInt(0), 0) - return nil + return scanner.ScanNumeric(Numeric{Int: big.NewInt(0), Valid: true}) } if len(src[rp:]) < int(ndigits)*2 { @@ -630,219 +623,117 @@ func (dst *Numeric) DecodeBinary(ci *ConnInfo, src []byte) error { accum.Neg(accum) } - dst.setNumber(accum, exp) - - return nil - + return scanner.ScanNumeric(Numeric{Int: accum, Exp: exp, Valid: true}) } -func nbaseDigitsToInt64(src []byte) (accum int64, bytesRead, digitsRead int) { - digits := len(src) / 2 - if digits > 4 { - digits = 4 - } +type scanPlanBinaryNumericToFloat64Scanner struct{} - rp := 0 +func (scanPlanBinaryNumericToFloat64Scanner) Scan(ci *ConnInfo, oid uint32, formatCode int16, src []byte, dst interface{}) error { + scanner := (dst).(Float64Scanner) - for i := 0; i < digits; i++ { - if i > 0 { - accum *= nbase - } - accum += int64(binary.BigEndian.Uint16(src[rp:])) - rp += 2 - } - - return accum, rp, digits -} - -func (src Numeric) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) { - if !src.Valid { - return nil, nil - } - - if src.NaN { - buf = append(buf, "NaN"...) - return buf, nil - } else if src.InfinityModifier == Infinity { - buf = append(buf, "Infinity"...) - return buf, nil - } else if src.InfinityModifier == NegativeInfinity { - buf = append(buf, "-Infinity"...) - return buf, nil - } - - buf = append(buf, src.Int.String()...) - buf = append(buf, 'e') - buf = append(buf, strconv.FormatInt(int64(src.Exp), 10)...) - return buf, nil -} - -func (src Numeric) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) { - if !src.Valid { - return nil, nil - } - - if src.NaN { - buf = pgio.AppendUint64(buf, pgNumericNaN) - return buf, nil - } else if src.InfinityModifier == Infinity { - buf = pgio.AppendUint64(buf, pgNumericPosInf) - return buf, nil - } else if src.InfinityModifier == NegativeInfinity { - buf = pgio.AppendUint64(buf, pgNumericNegInf) - return buf, nil - } - - var sign int16 - if src.Int.Cmp(big0) < 0 { - sign = 16384 - } - - absInt := &big.Int{} - wholePart := &big.Int{} - fracPart := &big.Int{} - remainder := &big.Int{} - absInt.Abs(src.Int) - - // Normalize absInt and exp to where exp is always a multiple of 4. This makes - // converting to 16-bit base 10,000 digits easier. - var exp int32 - switch src.Exp % 4 { - case 1, -3: - exp = src.Exp - 1 - absInt.Mul(absInt, big10) - case 2, -2: - exp = src.Exp - 2 - absInt.Mul(absInt, big100) - case 3, -1: - exp = src.Exp - 3 - absInt.Mul(absInt, big1000) - default: - exp = src.Exp - } - - if exp < 0 { - divisor := &big.Int{} - divisor.Exp(big10, big.NewInt(int64(-exp)), nil) - wholePart.DivMod(absInt, divisor, fracPart) - fracPart.Add(fracPart, divisor) - } else { - wholePart = absInt - } - - var wholeDigits, fracDigits []int16 - - for wholePart.Cmp(big0) != 0 { - wholePart.DivMod(wholePart, bigNBase, remainder) - wholeDigits = append(wholeDigits, int16(remainder.Int64())) - } - - if fracPart.Cmp(big0) != 0 { - for fracPart.Cmp(big1) != 0 { - fracPart.DivMod(fracPart, bigNBase, remainder) - fracDigits = append(fracDigits, int16(remainder.Int64())) - } - } - - buf = pgio.AppendInt16(buf, int16(len(wholeDigits)+len(fracDigits))) - - var weight int16 - if len(wholeDigits) > 0 { - weight = int16(len(wholeDigits) - 1) - if exp > 0 { - weight += int16(exp / 4) - } - } else { - weight = int16(exp/4) - 1 + int16(len(fracDigits)) - } - buf = pgio.AppendInt16(buf, weight) - - buf = pgio.AppendInt16(buf, sign) - - var dscale int16 - if src.Exp < 0 { - dscale = int16(-src.Exp) - } - buf = pgio.AppendInt16(buf, dscale) - - for i := len(wholeDigits) - 1; i >= 0; i-- { - buf = pgio.AppendInt16(buf, wholeDigits[i]) - } - - for i := len(fracDigits) - 1; i >= 0; i-- { - buf = pgio.AppendInt16(buf, fracDigits[i]) - } - - return buf, nil -} - -// Scan implements the database/sql Scanner interface. -func (dst *Numeric) Scan(src interface{}) error { if src == nil { - dst.setNil() - return nil + return scanner.ScanFloat64(Float8{}) } - 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) + var n Numeric + + err := scanPlanBinaryNumericToNumericScanner{}.Scan(ci, oid, formatCode, src, &n) + if err != nil { + return err } - return fmt.Errorf("cannot scan %T", src) + f64, err := n.toFloat64() + if err != nil { + return err + } + + return scanner.ScanFloat64(Float8{Float: f64, Valid: true}) } -// Value implements the database/sql/driver Valuer interface. -func (src Numeric) Value() (driver.Value, error) { - if !src.Valid { +type scanPlanBinaryNumericToInt64Scanner struct{} + +func (scanPlanBinaryNumericToInt64Scanner) Scan(ci *ConnInfo, oid uint32, formatCode int16, src []byte, dst interface{}) error { + scanner := (dst).(Int64Scanner) + + if src == nil { + return scanner.ScanInt64(Int8{}) + } + + var n Numeric + + err := scanPlanBinaryNumericToNumericScanner{}.Scan(ci, oid, formatCode, src, &n) + if err != nil { + return err + } + + bigInt, err := n.toBigInt() + if err != nil { + return err + } + + if !bigInt.IsInt64() { + return fmt.Errorf("%v is out of range for int64", bigInt) + } + + return scanner.ScanInt64(Int8{Int: bigInt.Int64(), Valid: true}) +} + +type scanPlanTextAnyToNumericScanner struct{} + +func (scanPlanTextAnyToNumericScanner) Scan(ci *ConnInfo, oid uint32, formatCode int16, src []byte, dst interface{}) error { + scanner := (dst).(NumericScanner) + + if src == nil { + return scanner.ScanNumeric(Numeric{}) + } + + if string(src) == "NaN" { + return scanner.ScanNumeric(Numeric{NaN: true, Valid: true}) + } else if string(src) == "Infinity" { + return scanner.ScanNumeric(Numeric{InfinityModifier: Infinity, Valid: true}) + } else if string(src) == "-Infinity" { + return scanner.ScanNumeric(Numeric{InfinityModifier: NegativeInfinity, Valid: true}) + } + + num, exp, err := parseNumericString(string(src)) + if err != nil { + return err + } + + return scanner.ScanNumeric(Numeric{Int: num, Exp: exp, Valid: true}) +} + +func (c NumericCodec) DecodeDatabaseSQLValue(ci *ConnInfo, oid uint32, format int16, src []byte) (driver.Value, error) { + if src == nil { return nil, nil } - buf, err := src.EncodeText(nil, nil) + if format == TextFormatCode { + return string(src), nil + } + + var n Numeric + err := codecScan(c, ci, oid, format, src, &n) if err != nil { return nil, err } + buf, err := ci.Encode(oid, TextFormatCode, n, nil) + if err != nil { + return nil, err + } return string(buf), nil } -func (src Numeric) MarshalJSON() ([]byte, error) { - if !src.Valid { - return []byte("null"), nil +func (c NumericCodec) DecodeValue(ci *ConnInfo, oid uint32, format int16, src []byte) (interface{}, error) { + if src == nil { + return nil, nil } - if src.NaN { - return []byte(`"NaN"`), nil + var n Numeric + err := codecScan(c, ci, oid, format, src, &n) + if err != nil { + return nil, err } - - intStr := src.Int.String() - buf := &bytes.Buffer{} - exp := int(src.Exp) - if exp > 0 { - buf.WriteString(intStr) - for i := 0; i < exp; i++ { - buf.WriteByte('0') - } - } else if exp < 0 { - if len(intStr) <= -exp { - buf.WriteString("0.") - leadingZeros := -exp - len(intStr) - for i := 0; i < leadingZeros; i++ { - buf.WriteByte('0') - } - buf.WriteString(intStr) - } else if len(intStr) > -exp { - dpPos := len(intStr) + exp - buf.WriteString(intStr[:dpPos]) - buf.WriteByte('.') - buf.WriteString(intStr[dpPos:]) - } - } else { - buf.WriteString(intStr) - } - - return buf.Bytes(), nil + return n, nil } diff --git a/pgtype/numeric_array.go b/pgtype/numeric_array.go deleted file mode 100644 index 3e9298b6..00000000 --- a/pgtype/numeric_array.go +++ /dev/null @@ -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 -} diff --git a/pgtype/numeric_array_test.go b/pgtype/numeric_array_test.go deleted file mode 100644 index 4542ed3e..00000000 --- a/pgtype/numeric_array_test.go +++ /dev/null @@ -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) - } - } - -} diff --git a/pgtype/numeric_test.go b/pgtype/numeric_test.go index ff53d92b..06d81ffe 100644 --- a/pgtype/numeric_test.go +++ b/pgtype/numeric_test.go @@ -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) { diff --git a/pgtype/pgtype.go b/pgtype/pgtype.go index a7c97a73..b1a9201f 100644 --- a/pgtype/pgtype.go +++ b/pgtype/pgtype.go @@ -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{}})