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