1package numeric_test 2 3import ( 4 "fmt" 5 "math/big" 6 "math/rand" 7 "reflect" 8 "testing" 9 10 "github.com/jackc/pgtype" 11 shopspring "github.com/jackc/pgtype/ext/shopspring-numeric" 12 "github.com/jackc/pgtype/testutil" 13 "github.com/shopspring/decimal" 14 "github.com/stretchr/testify/require" 15) 16 17func mustParseDecimal(t *testing.T, src string) decimal.Decimal { 18 dec, err := decimal.NewFromString(src) 19 if err != nil { 20 t.Fatal(err) 21 } 22 return dec 23} 24 25func TestNumericNormalize(t *testing.T) { 26 testutil.TestSuccessfulNormalizeEqFunc(t, []testutil.NormalizeTest{ 27 { 28 SQL: "select '0'::numeric", 29 Value: &shopspring.Numeric{Decimal: mustParseDecimal(t, "0"), Status: pgtype.Present}, 30 }, 31 { 32 SQL: "select '1'::numeric", 33 Value: &shopspring.Numeric{Decimal: mustParseDecimal(t, "1"), Status: pgtype.Present}, 34 }, 35 { 36 SQL: "select '10.00'::numeric", 37 Value: &shopspring.Numeric{Decimal: mustParseDecimal(t, "10.00"), Status: pgtype.Present}, 38 }, 39 { 40 SQL: "select '1e-3'::numeric", 41 Value: &shopspring.Numeric{Decimal: mustParseDecimal(t, "0.001"), Status: pgtype.Present}, 42 }, 43 { 44 SQL: "select '-1'::numeric", 45 Value: &shopspring.Numeric{Decimal: mustParseDecimal(t, "-1"), Status: pgtype.Present}, 46 }, 47 { 48 SQL: "select '10000'::numeric", 49 Value: &shopspring.Numeric{Decimal: mustParseDecimal(t, "10000"), Status: pgtype.Present}, 50 }, 51 { 52 SQL: "select '3.14'::numeric", 53 Value: &shopspring.Numeric{Decimal: mustParseDecimal(t, "3.14"), Status: pgtype.Present}, 54 }, 55 { 56 SQL: "select '1.1'::numeric", 57 Value: &shopspring.Numeric{Decimal: mustParseDecimal(t, "1.1"), Status: pgtype.Present}, 58 }, 59 { 60 SQL: "select '100010001'::numeric", 61 Value: &shopspring.Numeric{Decimal: mustParseDecimal(t, "100010001"), Status: pgtype.Present}, 62 }, 63 { 64 SQL: "select '100010001.0001'::numeric", 65 Value: &shopspring.Numeric{Decimal: mustParseDecimal(t, "100010001.0001"), Status: pgtype.Present}, 66 }, 67 { 68 SQL: "select '4237234789234789289347892374324872138321894178943189043890124832108934.43219085471578891547854892438945012347981'::numeric", 69 Value: &shopspring.Numeric{ 70 Decimal: mustParseDecimal(t, "4237234789234789289347892374324872138321894178943189043890124832108934.43219085471578891547854892438945012347981"), 71 Status: pgtype.Present, 72 }, 73 }, 74 { 75 SQL: "select '0.8925092023480223478923478978978937897879595901237890234789243679037419057877231734823098432903527585734549035904590854890345905434578345789347890402348952348905890489054234237489234987723894789234'::numeric", 76 Value: &shopspring.Numeric{ 77 Decimal: mustParseDecimal(t, "0.8925092023480223478923478978978937897879595901237890234789243679037419057877231734823098432903527585734549035904590854890345905434578345789347890402348952348905890489054234237489234987723894789234"), 78 Status: pgtype.Present, 79 }, 80 }, 81 { 82 SQL: "select '0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000123'::numeric", 83 Value: &shopspring.Numeric{ 84 Decimal: mustParseDecimal(t, "0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000123"), 85 Status: pgtype.Present, 86 }, 87 }, 88 }, func(aa, bb interface{}) bool { 89 a := aa.(shopspring.Numeric) 90 b := bb.(shopspring.Numeric) 91 92 return a.Status == b.Status && a.Decimal.Equal(b.Decimal) 93 }) 94} 95 96func TestNumericTranscode(t *testing.T) { 97 testutil.TestSuccessfulTranscodeEqFunc(t, "numeric", []interface{}{ 98 &shopspring.Numeric{Decimal: mustParseDecimal(t, "0"), Status: pgtype.Present}, 99 &shopspring.Numeric{Decimal: mustParseDecimal(t, "1"), Status: pgtype.Present}, 100 &shopspring.Numeric{Decimal: mustParseDecimal(t, "-1"), Status: pgtype.Present}, 101 &shopspring.Numeric{Decimal: mustParseDecimal(t, "100000"), Status: pgtype.Present}, 102 103 &shopspring.Numeric{Decimal: mustParseDecimal(t, "0.1"), Status: pgtype.Present}, 104 &shopspring.Numeric{Decimal: mustParseDecimal(t, "0.01"), Status: pgtype.Present}, 105 &shopspring.Numeric{Decimal: mustParseDecimal(t, "0.001"), Status: pgtype.Present}, 106 &shopspring.Numeric{Decimal: mustParseDecimal(t, "0.0001"), Status: pgtype.Present}, 107 &shopspring.Numeric{Decimal: mustParseDecimal(t, "0.00001"), Status: pgtype.Present}, 108 &shopspring.Numeric{Decimal: mustParseDecimal(t, "0.000001"), Status: pgtype.Present}, 109 110 &shopspring.Numeric{Decimal: mustParseDecimal(t, "3.14"), Status: pgtype.Present}, 111 &shopspring.Numeric{Decimal: mustParseDecimal(t, "0.00000123"), Status: pgtype.Present}, 112 &shopspring.Numeric{Decimal: mustParseDecimal(t, "0.000000123"), Status: pgtype.Present}, 113 &shopspring.Numeric{Decimal: mustParseDecimal(t, "0.0000000123"), Status: pgtype.Present}, 114 &shopspring.Numeric{Decimal: mustParseDecimal(t, "0.00000000123"), Status: pgtype.Present}, 115 &shopspring.Numeric{Decimal: mustParseDecimal(t, "0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001234567890123456789"), Status: pgtype.Present}, 116 &shopspring.Numeric{Decimal: mustParseDecimal(t, "4309132809320932980457137401234890237489238912983572189348951289375283573984571892758234678903467889512893489128589347891272139.8489235871258912789347891235879148795891238915678189467128957812395781238579189025891238901583915890128973578957912385798125789012378905238905471598123758923478294374327894237892234"), Status: pgtype.Present}, 117 &shopspring.Numeric{Status: pgtype.Null}, 118 }, func(aa, bb interface{}) bool { 119 a := aa.(shopspring.Numeric) 120 b := bb.(shopspring.Numeric) 121 122 return a.Status == b.Status && a.Decimal.Equal(b.Decimal) 123 }) 124 125} 126 127func TestNumericTranscodeFuzz(t *testing.T) { 128 r := rand.New(rand.NewSource(0)) 129 max := &big.Int{} 130 max.SetString("9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999", 10) 131 132 values := make([]interface{}, 0, 2000) 133 for i := 0; i < 500; i++ { 134 num := fmt.Sprintf("%s.%s", (&big.Int{}).Rand(r, max).String(), (&big.Int{}).Rand(r, max).String()) 135 negNum := "-" + num 136 values = append(values, &shopspring.Numeric{Decimal: mustParseDecimal(t, num), Status: pgtype.Present}) 137 values = append(values, &shopspring.Numeric{Decimal: mustParseDecimal(t, negNum), Status: pgtype.Present}) 138 } 139 140 testutil.TestSuccessfulTranscodeEqFunc(t, "numeric", values, 141 func(aa, bb interface{}) bool { 142 a := aa.(shopspring.Numeric) 143 b := bb.(shopspring.Numeric) 144 145 return a.Status == b.Status && a.Decimal.Equal(b.Decimal) 146 }) 147} 148 149func TestNumericSet(t *testing.T) { 150 type _int8 int8 151 152 successfulTests := []struct { 153 source interface{} 154 result *shopspring.Numeric 155 }{ 156 {source: float32(1), result: &shopspring.Numeric{Decimal: mustParseDecimal(t, "1"), Status: pgtype.Present}}, 157 {source: float64(1), result: &shopspring.Numeric{Decimal: mustParseDecimal(t, "1"), Status: pgtype.Present}}, 158 {source: int8(1), result: &shopspring.Numeric{Decimal: mustParseDecimal(t, "1"), Status: pgtype.Present}}, 159 {source: int16(1), result: &shopspring.Numeric{Decimal: mustParseDecimal(t, "1"), Status: pgtype.Present}}, 160 {source: int32(1), result: &shopspring.Numeric{Decimal: mustParseDecimal(t, "1"), Status: pgtype.Present}}, 161 {source: int64(1), result: &shopspring.Numeric{Decimal: mustParseDecimal(t, "1"), Status: pgtype.Present}}, 162 {source: int8(-1), result: &shopspring.Numeric{Decimal: mustParseDecimal(t, "-1"), Status: pgtype.Present}}, 163 {source: int16(-1), result: &shopspring.Numeric{Decimal: mustParseDecimal(t, "-1"), Status: pgtype.Present}}, 164 {source: int32(-1), result: &shopspring.Numeric{Decimal: mustParseDecimal(t, "-1"), Status: pgtype.Present}}, 165 {source: int64(-1), result: &shopspring.Numeric{Decimal: mustParseDecimal(t, "-1"), Status: pgtype.Present}}, 166 {source: uint8(1), result: &shopspring.Numeric{Decimal: mustParseDecimal(t, "1"), Status: pgtype.Present}}, 167 {source: uint16(1), result: &shopspring.Numeric{Decimal: mustParseDecimal(t, "1"), Status: pgtype.Present}}, 168 {source: uint32(1), result: &shopspring.Numeric{Decimal: mustParseDecimal(t, "1"), Status: pgtype.Present}}, 169 {source: uint64(1), result: &shopspring.Numeric{Decimal: mustParseDecimal(t, "1"), Status: pgtype.Present}}, 170 {source: "1", result: &shopspring.Numeric{Decimal: mustParseDecimal(t, "1"), Status: pgtype.Present}}, 171 {source: _int8(1), result: &shopspring.Numeric{Decimal: mustParseDecimal(t, "1"), Status: pgtype.Present}}, 172 {source: float64(1000), result: &shopspring.Numeric{Decimal: mustParseDecimal(t, "1000"), Status: pgtype.Present}}, 173 {source: float64(1234), result: &shopspring.Numeric{Decimal: mustParseDecimal(t, "1234"), Status: pgtype.Present}}, 174 {source: float64(12345678900), result: &shopspring.Numeric{Decimal: mustParseDecimal(t, "12345678900"), Status: pgtype.Present}}, 175 {source: float64(1.25), result: &shopspring.Numeric{Decimal: mustParseDecimal(t, "1.25"), Status: pgtype.Present}}, 176 } 177 178 for i, tt := range successfulTests { 179 r := &shopspring.Numeric{} 180 err := r.Set(tt.source) 181 if err != nil { 182 t.Errorf("%d: %v", i, err) 183 } 184 185 if !(r.Status == tt.result.Status && r.Decimal.Equal(tt.result.Decimal)) { 186 t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r) 187 } 188 } 189} 190 191func TestNumericAssignTo(t *testing.T) { 192 type _int8 int8 193 194 var i8 int8 195 var i16 int16 196 var i32 int32 197 var i64 int64 198 var i int 199 var ui8 uint8 200 var ui16 uint16 201 var ui32 uint32 202 var ui64 uint64 203 var ui uint 204 var pi8 *int8 205 var _i8 _int8 206 var _pi8 *_int8 207 var f32 float32 208 var f64 float64 209 var pf32 *float32 210 var pf64 *float64 211 212 simpleTests := []struct { 213 src *shopspring.Numeric 214 dst interface{} 215 expected interface{} 216 }{ 217 {src: &shopspring.Numeric{Decimal: mustParseDecimal(t, "42"), Status: pgtype.Present}, dst: &f32, expected: float32(42)}, 218 {src: &shopspring.Numeric{Decimal: mustParseDecimal(t, "42"), Status: pgtype.Present}, dst: &f64, expected: float64(42)}, 219 {src: &shopspring.Numeric{Decimal: mustParseDecimal(t, "4.2"), Status: pgtype.Present}, dst: &f32, expected: float32(4.2)}, 220 {src: &shopspring.Numeric{Decimal: mustParseDecimal(t, "4.2"), Status: pgtype.Present}, dst: &f64, expected: float64(4.2)}, 221 {src: &shopspring.Numeric{Decimal: mustParseDecimal(t, "42"), Status: pgtype.Present}, dst: &i16, expected: int16(42)}, 222 {src: &shopspring.Numeric{Decimal: mustParseDecimal(t, "42"), Status: pgtype.Present}, dst: &i32, expected: int32(42)}, 223 {src: &shopspring.Numeric{Decimal: mustParseDecimal(t, "42"), Status: pgtype.Present}, dst: &i64, expected: int64(42)}, 224 {src: &shopspring.Numeric{Decimal: mustParseDecimal(t, "42000"), Status: pgtype.Present}, dst: &i64, expected: int64(42000)}, 225 {src: &shopspring.Numeric{Decimal: mustParseDecimal(t, "42"), Status: pgtype.Present}, dst: &i, expected: int(42)}, 226 {src: &shopspring.Numeric{Decimal: mustParseDecimal(t, "42"), Status: pgtype.Present}, dst: &ui8, expected: uint8(42)}, 227 {src: &shopspring.Numeric{Decimal: mustParseDecimal(t, "42"), Status: pgtype.Present}, dst: &ui16, expected: uint16(42)}, 228 {src: &shopspring.Numeric{Decimal: mustParseDecimal(t, "42"), Status: pgtype.Present}, dst: &ui32, expected: uint32(42)}, 229 {src: &shopspring.Numeric{Decimal: mustParseDecimal(t, "42"), Status: pgtype.Present}, dst: &ui64, expected: uint64(42)}, 230 {src: &shopspring.Numeric{Decimal: mustParseDecimal(t, "42"), Status: pgtype.Present}, dst: &ui, expected: uint(42)}, 231 {src: &shopspring.Numeric{Decimal: mustParseDecimal(t, "42"), Status: pgtype.Present}, dst: &_i8, expected: _int8(42)}, 232 {src: &shopspring.Numeric{Status: pgtype.Null}, dst: &pi8, expected: ((*int8)(nil))}, 233 {src: &shopspring.Numeric{Status: pgtype.Null}, dst: &_pi8, expected: ((*_int8)(nil))}, 234 } 235 236 for i, tt := range simpleTests { 237 err := tt.src.AssignTo(tt.dst) 238 if err != nil { 239 t.Errorf("%d: %v", i, err) 240 } 241 242 if dst := reflect.ValueOf(tt.dst).Elem().Interface(); dst != tt.expected { 243 t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst) 244 } 245 } 246 247 pointerAllocTests := []struct { 248 src *shopspring.Numeric 249 dst interface{} 250 expected interface{} 251 }{ 252 {src: &shopspring.Numeric{Decimal: mustParseDecimal(t, "42"), Status: pgtype.Present}, dst: &pf32, expected: float32(42)}, 253 {src: &shopspring.Numeric{Decimal: mustParseDecimal(t, "42"), Status: pgtype.Present}, dst: &pf64, expected: float64(42)}, 254 } 255 256 for i, tt := range pointerAllocTests { 257 err := tt.src.AssignTo(tt.dst) 258 if err != nil { 259 t.Errorf("%d: %v", i, err) 260 } 261 262 if dst := reflect.ValueOf(tt.dst).Elem().Elem().Interface(); dst != tt.expected { 263 t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst) 264 } 265 } 266 267 errorTests := []struct { 268 src *shopspring.Numeric 269 dst interface{} 270 }{ 271 {src: &shopspring.Numeric{Decimal: mustParseDecimal(t, "150"), Status: pgtype.Present}, dst: &i8}, 272 {src: &shopspring.Numeric{Decimal: mustParseDecimal(t, "40000"), Status: pgtype.Present}, dst: &i16}, 273 {src: &shopspring.Numeric{Decimal: mustParseDecimal(t, "-1"), Status: pgtype.Present}, dst: &ui8}, 274 {src: &shopspring.Numeric{Decimal: mustParseDecimal(t, "-1"), Status: pgtype.Present}, dst: &ui16}, 275 {src: &shopspring.Numeric{Decimal: mustParseDecimal(t, "-1"), Status: pgtype.Present}, dst: &ui32}, 276 {src: &shopspring.Numeric{Decimal: mustParseDecimal(t, "-1"), Status: pgtype.Present}, dst: &ui64}, 277 {src: &shopspring.Numeric{Decimal: mustParseDecimal(t, "-1"), Status: pgtype.Present}, dst: &ui}, 278 {src: &shopspring.Numeric{Status: pgtype.Null}, dst: &i32}, 279 } 280 281 for i, tt := range errorTests { 282 err := tt.src.AssignTo(tt.dst) 283 if err == nil { 284 t.Errorf("%d: expected error but none was returned (%v -> %v)", i, tt.src, tt.dst) 285 } 286 } 287} 288 289func BenchmarkDecode(b *testing.B) { 290 benchmarks := []struct { 291 name string 292 numberStr string 293 }{ 294 {"Zero", "0"}, 295 {"Small", "12345"}, 296 {"Medium", "12345.12345"}, 297 {"Large", "123457890.1234567890"}, 298 {"Huge", "123457890123457890123457890.1234567890123457890123457890"}, 299 } 300 301 for _, bm := range benchmarks { 302 src := &shopspring.Numeric{} 303 err := src.Set(bm.numberStr) 304 require.NoError(b, err) 305 textFormat, err := src.EncodeText(nil, nil) 306 require.NoError(b, err) 307 binaryFormat, err := src.EncodeBinary(nil, nil) 308 require.NoError(b, err) 309 310 b.Run(fmt.Sprintf("%s-Text", bm.name), func(b *testing.B) { 311 dst := &shopspring.Numeric{} 312 for i := 0; i < b.N; i++ { 313 err := dst.DecodeText(nil, textFormat) 314 if err != nil { 315 b.Fatal(err) 316 } 317 } 318 }) 319 320 b.Run(fmt.Sprintf("%s-Binary", bm.name), func(b *testing.B) { 321 dst := &shopspring.Numeric{} 322 for i := 0; i < b.N; i++ { 323 err := dst.DecodeBinary(nil, binaryFormat) 324 if err != nil { 325 b.Fatal(err) 326 } 327 } 328 }) 329 } 330} 331