1package pgtype_test 2 3import ( 4 "math" 5 "math/big" 6 "math/rand" 7 "reflect" 8 "testing" 9 10 "github.com/jackc/pgtype" 11 "github.com/jackc/pgtype/testutil" 12) 13 14// For test purposes only. Note that it does not normalize values. e.g. (Int: 1, Exp: 3) will not equal (Int: 1000, Exp: 0) 15func numericEqual(left, right *pgtype.Numeric) bool { 16 return left.Status == right.Status && 17 left.Exp == right.Exp && 18 ((left.Int == nil && right.Int == nil) || (left.Int != nil && right.Int != nil && left.Int.Cmp(right.Int) == 0)) 19} 20 21// For test purposes only. 22func numericNormalizedEqual(left, right *pgtype.Numeric) bool { 23 if left.Status != right.Status { 24 return false 25 } 26 27 normLeft := &pgtype.Numeric{Int: (&big.Int{}).Set(left.Int), Status: left.Status} 28 normRight := &pgtype.Numeric{Int: (&big.Int{}).Set(right.Int), Status: right.Status} 29 30 if left.Exp < right.Exp { 31 mul := (&big.Int{}).Exp(big.NewInt(10), big.NewInt(int64(right.Exp-left.Exp)), nil) 32 normRight.Int.Mul(normRight.Int, mul) 33 } else if left.Exp > right.Exp { 34 mul := (&big.Int{}).Exp(big.NewInt(10), big.NewInt(int64(left.Exp-right.Exp)), nil) 35 normLeft.Int.Mul(normLeft.Int, mul) 36 } 37 38 return normLeft.Int.Cmp(normRight.Int) == 0 39} 40 41func mustParseBigInt(t *testing.T, src string) *big.Int { 42 i := &big.Int{} 43 if _, ok := i.SetString(src, 10); !ok { 44 t.Fatalf("could not parse big.Int: %s", src) 45 } 46 return i 47} 48 49func TestNumericNormalize(t *testing.T) { 50 testutil.TestSuccessfulNormalize(t, []testutil.NormalizeTest{ 51 { 52 SQL: "select '0'::numeric", 53 Value: &pgtype.Numeric{Int: big.NewInt(0), Exp: 0, Status: pgtype.Present}, 54 }, 55 { 56 SQL: "select '1'::numeric", 57 Value: &pgtype.Numeric{Int: big.NewInt(1), Exp: 0, Status: pgtype.Present}, 58 }, 59 { 60 SQL: "select '10.00'::numeric", 61 Value: &pgtype.Numeric{Int: big.NewInt(1000), Exp: -2, Status: pgtype.Present}, 62 }, 63 { 64 SQL: "select '1e-3'::numeric", 65 Value: &pgtype.Numeric{Int: big.NewInt(1), Exp: -3, Status: pgtype.Present}, 66 }, 67 { 68 SQL: "select '-1'::numeric", 69 Value: &pgtype.Numeric{Int: big.NewInt(-1), Exp: 0, Status: pgtype.Present}, 70 }, 71 { 72 SQL: "select '10000'::numeric", 73 Value: &pgtype.Numeric{Int: big.NewInt(1), Exp: 4, Status: pgtype.Present}, 74 }, 75 { 76 SQL: "select '3.14'::numeric", 77 Value: &pgtype.Numeric{Int: big.NewInt(314), Exp: -2, Status: pgtype.Present}, 78 }, 79 { 80 SQL: "select '1.1'::numeric", 81 Value: &pgtype.Numeric{Int: big.NewInt(11), Exp: -1, Status: pgtype.Present}, 82 }, 83 { 84 SQL: "select '100010001'::numeric", 85 Value: &pgtype.Numeric{Int: big.NewInt(100010001), Exp: 0, Status: pgtype.Present}, 86 }, 87 { 88 SQL: "select '100010001.0001'::numeric", 89 Value: &pgtype.Numeric{Int: big.NewInt(1000100010001), Exp: -4, Status: pgtype.Present}, 90 }, 91 { 92 SQL: "select '4237234789234789289347892374324872138321894178943189043890124832108934.43219085471578891547854892438945012347981'::numeric", 93 Value: &pgtype.Numeric{ 94 Int: mustParseBigInt(t, "423723478923478928934789237432487213832189417894318904389012483210893443219085471578891547854892438945012347981"), 95 Exp: -41, 96 Status: pgtype.Present, 97 }, 98 }, 99 { 100 SQL: "select '0.8925092023480223478923478978978937897879595901237890234789243679037419057877231734823098432903527585734549035904590854890345905434578345789347890402348952348905890489054234237489234987723894789234'::numeric", 101 Value: &pgtype.Numeric{ 102 Int: mustParseBigInt(t, "8925092023480223478923478978978937897879595901237890234789243679037419057877231734823098432903527585734549035904590854890345905434578345789347890402348952348905890489054234237489234987723894789234"), 103 Exp: -196, 104 Status: pgtype.Present, 105 }, 106 }, 107 { 108 SQL: "select '0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000123'::numeric", 109 Value: &pgtype.Numeric{ 110 Int: mustParseBigInt(t, "123"), 111 Exp: -186, 112 Status: pgtype.Present, 113 }, 114 }, 115 }) 116} 117 118func TestNumericTranscode(t *testing.T) { 119 testutil.TestSuccessfulTranscodeEqFunc(t, "numeric", []interface{}{ 120 &pgtype.Numeric{Int: big.NewInt(0), Exp: 0, Status: pgtype.Present}, 121 &pgtype.Numeric{Int: big.NewInt(1), Exp: 0, Status: pgtype.Present}, 122 &pgtype.Numeric{Int: big.NewInt(-1), Exp: 0, Status: pgtype.Present}, 123 &pgtype.Numeric{Int: big.NewInt(1), Exp: 6, Status: pgtype.Present}, 124 125 // preserves significant zeroes 126 &pgtype.Numeric{Int: big.NewInt(10000000), Exp: -1, Status: pgtype.Present}, 127 &pgtype.Numeric{Int: big.NewInt(10000000), Exp: -2, Status: pgtype.Present}, 128 &pgtype.Numeric{Int: big.NewInt(10000000), Exp: -3, Status: pgtype.Present}, 129 &pgtype.Numeric{Int: big.NewInt(10000000), Exp: -4, Status: pgtype.Present}, 130 &pgtype.Numeric{Int: big.NewInt(10000000), Exp: -5, Status: pgtype.Present}, 131 &pgtype.Numeric{Int: big.NewInt(10000000), Exp: -6, Status: pgtype.Present}, 132 133 &pgtype.Numeric{Int: big.NewInt(314), Exp: -2, Status: pgtype.Present}, 134 &pgtype.Numeric{Int: big.NewInt(123), Exp: -7, Status: pgtype.Present}, 135 &pgtype.Numeric{Int: big.NewInt(123), Exp: -8, Status: pgtype.Present}, 136 &pgtype.Numeric{Int: big.NewInt(123), Exp: -9, Status: pgtype.Present}, 137 &pgtype.Numeric{Int: big.NewInt(123), Exp: -1500, Status: pgtype.Present}, 138 &pgtype.Numeric{Int: mustParseBigInt(t, "2437"), Exp: 23790, Status: pgtype.Present}, 139 &pgtype.Numeric{Int: mustParseBigInt(t, "243723409723490243842378942378901237502734019231380123"), Exp: 23790, Status: pgtype.Present}, 140 &pgtype.Numeric{Int: mustParseBigInt(t, "43723409723490243842378942378901237502734019231380123"), Exp: 80, Status: pgtype.Present}, 141 &pgtype.Numeric{Int: mustParseBigInt(t, "3723409723490243842378942378901237502734019231380123"), Exp: 81, Status: pgtype.Present}, 142 &pgtype.Numeric{Int: mustParseBigInt(t, "723409723490243842378942378901237502734019231380123"), Exp: 82, Status: pgtype.Present}, 143 &pgtype.Numeric{Int: mustParseBigInt(t, "23409723490243842378942378901237502734019231380123"), Exp: 83, Status: pgtype.Present}, 144 &pgtype.Numeric{Int: mustParseBigInt(t, "3409723490243842378942378901237502734019231380123"), Exp: 84, Status: pgtype.Present}, 145 &pgtype.Numeric{Int: mustParseBigInt(t, "913423409823409243892349028349023482934092340892390101"), Exp: -14021, Status: pgtype.Present}, 146 &pgtype.Numeric{Int: mustParseBigInt(t, "13423409823409243892349028349023482934092340892390101"), Exp: -90, Status: pgtype.Present}, 147 &pgtype.Numeric{Int: mustParseBigInt(t, "3423409823409243892349028349023482934092340892390101"), Exp: -91, Status: pgtype.Present}, 148 &pgtype.Numeric{Int: mustParseBigInt(t, "423409823409243892349028349023482934092340892390101"), Exp: -92, Status: pgtype.Present}, 149 &pgtype.Numeric{Int: mustParseBigInt(t, "23409823409243892349028349023482934092340892390101"), Exp: -93, Status: pgtype.Present}, 150 &pgtype.Numeric{Int: mustParseBigInt(t, "3409823409243892349028349023482934092340892390101"), Exp: -94, Status: pgtype.Present}, 151 &pgtype.Numeric{Status: pgtype.Null}, 152 }, func(aa, bb interface{}) bool { 153 a := aa.(pgtype.Numeric) 154 b := bb.(pgtype.Numeric) 155 156 return numericEqual(&a, &b) 157 }) 158 159} 160 161func TestNumericTranscodeFuzz(t *testing.T) { 162 r := rand.New(rand.NewSource(0)) 163 max := &big.Int{} 164 max.SetString("9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999", 10) 165 166 values := make([]interface{}, 0, 2000) 167 for i := 0; i < 10; i++ { 168 for j := -50; j < 50; j++ { 169 num := (&big.Int{}).Rand(r, max) 170 negNum := &big.Int{} 171 negNum.Neg(num) 172 values = append(values, &pgtype.Numeric{Int: num, Exp: int32(j), Status: pgtype.Present}) 173 values = append(values, &pgtype.Numeric{Int: negNum, Exp: int32(j), Status: pgtype.Present}) 174 } 175 } 176 177 testutil.TestSuccessfulTranscodeEqFunc(t, "numeric", values, 178 func(aa, bb interface{}) bool { 179 a := aa.(pgtype.Numeric) 180 b := bb.(pgtype.Numeric) 181 182 return numericNormalizedEqual(&a, &b) 183 }) 184} 185 186func TestNumericSet(t *testing.T) { 187 successfulTests := []struct { 188 source interface{} 189 result *pgtype.Numeric 190 }{ 191 {source: float32(1), result: &pgtype.Numeric{Int: big.NewInt(1), Status: pgtype.Present}}, 192 {source: float32(math.Copysign(0, -1)), result: &pgtype.Numeric{Int: big.NewInt(0), Status: pgtype.Present}}, 193 {source: float64(1), result: &pgtype.Numeric{Int: big.NewInt(1), Status: pgtype.Present}}, 194 {source: float64(math.Copysign(0, -1)), result: &pgtype.Numeric{Int: big.NewInt(0), Status: pgtype.Present}}, 195 {source: int8(1), result: &pgtype.Numeric{Int: big.NewInt(1), Status: pgtype.Present}}, 196 {source: int16(1), result: &pgtype.Numeric{Int: big.NewInt(1), Status: pgtype.Present}}, 197 {source: int32(1), result: &pgtype.Numeric{Int: big.NewInt(1), Status: pgtype.Present}}, 198 {source: int64(1), result: &pgtype.Numeric{Int: big.NewInt(1), Status: pgtype.Present}}, 199 {source: int8(-1), result: &pgtype.Numeric{Int: big.NewInt(-1), Status: pgtype.Present}}, 200 {source: int16(-1), result: &pgtype.Numeric{Int: big.NewInt(-1), Status: pgtype.Present}}, 201 {source: int32(-1), result: &pgtype.Numeric{Int: big.NewInt(-1), Status: pgtype.Present}}, 202 {source: int64(-1), result: &pgtype.Numeric{Int: big.NewInt(-1), Status: pgtype.Present}}, 203 {source: uint8(1), result: &pgtype.Numeric{Int: big.NewInt(1), Status: pgtype.Present}}, 204 {source: uint16(1), result: &pgtype.Numeric{Int: big.NewInt(1), Status: pgtype.Present}}, 205 {source: uint32(1), result: &pgtype.Numeric{Int: big.NewInt(1), Status: pgtype.Present}}, 206 {source: uint64(1), result: &pgtype.Numeric{Int: big.NewInt(1), Status: pgtype.Present}}, 207 {source: "1", result: &pgtype.Numeric{Int: big.NewInt(1), Status: pgtype.Present}}, 208 {source: _int8(1), result: &pgtype.Numeric{Int: big.NewInt(1), Status: pgtype.Present}}, 209 {source: float64(1000), result: &pgtype.Numeric{Int: big.NewInt(1), Exp: 3, Status: pgtype.Present}}, 210 {source: float64(1234), result: &pgtype.Numeric{Int: big.NewInt(1234), Exp: 0, Status: pgtype.Present}}, 211 {source: float64(12345678900), result: &pgtype.Numeric{Int: big.NewInt(123456789), Exp: 2, Status: pgtype.Present}}, 212 {source: float64(12345.678901), result: &pgtype.Numeric{Int: big.NewInt(12345678901), Exp: -6, Status: pgtype.Present}}, 213 {source: math.NaN(), result: &pgtype.Numeric{Int: nil, Exp: 0, Status: pgtype.Present, NaN: true}}, 214 {source: float32(math.NaN()), result: &pgtype.Numeric{Int: nil, Exp: 0, Status: pgtype.Present, NaN: true}}, 215 } 216 217 for i, tt := range successfulTests { 218 r := &pgtype.Numeric{} 219 err := r.Set(tt.source) 220 if err != nil { 221 t.Errorf("%d: %v", i, err) 222 } 223 224 if !numericEqual(r, tt.result) { 225 t.Errorf("%d: expected %v to convert to %v, but it was %v", i, tt.source, tt.result, r) 226 } 227 } 228} 229 230func TestNumericAssignTo(t *testing.T) { 231 var i8 int8 232 var i16 int16 233 var i32 int32 234 var i64 int64 235 var i int 236 var ui8 uint8 237 var ui16 uint16 238 var ui32 uint32 239 var ui64 uint64 240 var ui uint 241 var pi8 *int8 242 var _i8 _int8 243 var _pi8 *_int8 244 var f32 float32 245 var f64 float64 246 var pf32 *float32 247 var pf64 *float64 248 249 simpleTests := []struct { 250 src *pgtype.Numeric 251 dst interface{} 252 expected interface{} 253 }{ 254 {src: &pgtype.Numeric{Int: big.NewInt(42), Status: pgtype.Present}, dst: &f32, expected: float32(42)}, 255 {src: &pgtype.Numeric{Int: big.NewInt(42), Status: pgtype.Present}, dst: &f64, expected: float64(42)}, 256 {src: &pgtype.Numeric{Int: big.NewInt(42), Exp: -1, Status: pgtype.Present}, dst: &f32, expected: float32(4.2)}, 257 {src: &pgtype.Numeric{Int: big.NewInt(42), Exp: -1, Status: pgtype.Present}, dst: &f64, expected: float64(4.2)}, 258 {src: &pgtype.Numeric{Int: big.NewInt(42), Status: pgtype.Present}, dst: &i16, expected: int16(42)}, 259 {src: &pgtype.Numeric{Int: big.NewInt(42), Status: pgtype.Present}, dst: &i32, expected: int32(42)}, 260 {src: &pgtype.Numeric{Int: big.NewInt(42), Status: pgtype.Present}, dst: &i64, expected: int64(42)}, 261 {src: &pgtype.Numeric{Int: big.NewInt(42), Exp: 3, Status: pgtype.Present}, dst: &i64, expected: int64(42000)}, 262 {src: &pgtype.Numeric{Int: big.NewInt(42), Status: pgtype.Present}, dst: &i, expected: int(42)}, 263 {src: &pgtype.Numeric{Int: big.NewInt(42), Status: pgtype.Present}, dst: &ui8, expected: uint8(42)}, 264 {src: &pgtype.Numeric{Int: big.NewInt(42), Status: pgtype.Present}, dst: &ui16, expected: uint16(42)}, 265 {src: &pgtype.Numeric{Int: big.NewInt(42), Status: pgtype.Present}, dst: &ui32, expected: uint32(42)}, 266 {src: &pgtype.Numeric{Int: big.NewInt(42), Status: pgtype.Present}, dst: &ui64, expected: uint64(42)}, 267 {src: &pgtype.Numeric{Int: big.NewInt(42), Status: pgtype.Present}, dst: &ui, expected: uint(42)}, 268 {src: &pgtype.Numeric{Int: big.NewInt(42), Status: pgtype.Present}, dst: &_i8, expected: _int8(42)}, 269 {src: &pgtype.Numeric{Int: big.NewInt(0), Status: pgtype.Null}, dst: &pi8, expected: ((*int8)(nil))}, 270 {src: &pgtype.Numeric{Int: big.NewInt(0), Status: pgtype.Null}, dst: &_pi8, expected: ((*_int8)(nil))}, 271 {src: &pgtype.Numeric{Int: big.NewInt(1006), Exp: -2, Status: pgtype.Present}, dst: &f64, expected: float64(10.06)}, // https://github.com/jackc/pgtype/issues/27 272 {src: &pgtype.Numeric{Status: pgtype.Present, NaN: true}, dst: &f64, expected: math.NaN()}, 273 {src: &pgtype.Numeric{Status: pgtype.Present, NaN: true}, dst: &f32, expected: float32(math.NaN())}, 274 } 275 276 for i, tt := range simpleTests { 277 err := tt.src.AssignTo(tt.dst) 278 if err != nil { 279 t.Errorf("%d: %v", i, err) 280 } 281 282 dst := reflect.ValueOf(tt.dst).Elem().Interface() 283 switch dstTyped := dst.(type) { 284 case float32: 285 nanExpected := math.IsNaN(float64(tt.expected.(float32))) 286 if nanExpected && !math.IsNaN(float64(dstTyped)) { 287 t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst) 288 } else if !nanExpected && dst != tt.expected { 289 t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst) 290 } 291 case float64: 292 nanExpected := math.IsNaN(tt.expected.(float64)) 293 if nanExpected && !math.IsNaN(dstTyped) { 294 t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst) 295 } else if !nanExpected && dst != tt.expected { 296 t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst) 297 } 298 default: 299 if dst != tt.expected { 300 t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst) 301 } 302 } 303 } 304 305 pointerAllocTests := []struct { 306 src *pgtype.Numeric 307 dst interface{} 308 expected interface{} 309 }{ 310 {src: &pgtype.Numeric{Int: big.NewInt(42), Status: pgtype.Present}, dst: &pf32, expected: float32(42)}, 311 {src: &pgtype.Numeric{Int: big.NewInt(42), Status: pgtype.Present}, dst: &pf64, expected: float64(42)}, 312 } 313 314 for i, tt := range pointerAllocTests { 315 err := tt.src.AssignTo(tt.dst) 316 if err != nil { 317 t.Errorf("%d: %v", i, err) 318 } 319 320 if dst := reflect.ValueOf(tt.dst).Elem().Elem().Interface(); dst != tt.expected { 321 t.Errorf("%d: expected %v to assign %v, but result was %v", i, tt.src, tt.expected, dst) 322 } 323 } 324 325 errorTests := []struct { 326 src *pgtype.Numeric 327 dst interface{} 328 }{ 329 {src: &pgtype.Numeric{Int: big.NewInt(150), Status: pgtype.Present}, dst: &i8}, 330 {src: &pgtype.Numeric{Int: big.NewInt(40000), Status: pgtype.Present}, dst: &i16}, 331 {src: &pgtype.Numeric{Int: big.NewInt(-1), Status: pgtype.Present}, dst: &ui8}, 332 {src: &pgtype.Numeric{Int: big.NewInt(-1), Status: pgtype.Present}, dst: &ui16}, 333 {src: &pgtype.Numeric{Int: big.NewInt(-1), Status: pgtype.Present}, dst: &ui32}, 334 {src: &pgtype.Numeric{Int: big.NewInt(-1), Status: pgtype.Present}, dst: &ui64}, 335 {src: &pgtype.Numeric{Int: big.NewInt(-1), Status: pgtype.Present}, dst: &ui}, 336 {src: &pgtype.Numeric{Int: big.NewInt(0), Status: pgtype.Null}, dst: &i32}, 337 } 338 339 for i, tt := range errorTests { 340 err := tt.src.AssignTo(tt.dst) 341 if err == nil { 342 t.Errorf("%d: expected error but none was returned (%v -> %v)", i, tt.src, tt.dst) 343 } 344 } 345} 346 347func TestNumericEncodeDecodeBinary(t *testing.T) { 348 ci := pgtype.NewConnInfo() 349 tests := []interface{}{ 350 123, 351 0.000012345, 352 1.00002345, 353 math.NaN(), 354 float32(math.NaN()), 355 } 356 357 for i, tt := range tests { 358 toString := func(n *pgtype.Numeric) string { 359 ci := pgtype.NewConnInfo() 360 text, err := n.EncodeText(ci, nil) 361 if err != nil { 362 t.Errorf("%d (EncodeText): %v", i, err) 363 } 364 return string(text) 365 } 366 numeric := &pgtype.Numeric{} 367 numeric.Set(tt) 368 369 encoded, err := numeric.EncodeBinary(ci, nil) 370 if err != nil { 371 t.Errorf("%d (EncodeBinary): %v", i, err) 372 } 373 decoded := &pgtype.Numeric{} 374 err = decoded.DecodeBinary(ci, encoded) 375 if err != nil { 376 t.Errorf("%d (DecodeBinary): %v", i, err) 377 } 378 379 text0 := toString(numeric) 380 text1 := toString(decoded) 381 382 if text0 != text1 { 383 t.Errorf("%d: expected %v to equal to %v, but doesn't", i, text0, text1) 384 } 385 } 386} 387