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