Skip to content

Commit 5f76bc6

Browse files
committed
api: the Decimal type is immutable
The patch forces the use of objects of type Decimal instead of pointers. Part of #238
1 parent 337ca73 commit 5f76bc6

File tree

5 files changed

+73
-48
lines changed

5 files changed

+73
-48
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
1919
- connection_pool renamed to pool (#239)
2020
- Use msgpack/v5 instead of msgpack.v2 (#236)
2121
- Call/NewCallRequest = Call17/NewCall17Request (#235)
22+
- Use objects of the Decimal type instead of pointers (#238)
2223

2324
### Deprecated
2425

README.md

+6
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ faster than other packages according to public benchmarks.
2727
* [API reference](#api-reference)
2828
* [Walking\-through example](#walking-through-example)
2929
* [Migration to v2](#migration-to-v2)
30+
* [decimal package](#decimal-package)
3031
* [multi package](#multi-package)
3132
* [pool package](#pool-package)
3233
* [msgpack.v5](#msgpackv5)
@@ -148,6 +149,11 @@ by `Connect()`.
148149

149150
The article describes migration from go-tarantool to go-tarantool/v2.
150151

152+
#### decimal package
153+
154+
Now you need to use objects of the Decimal type instead of pointers to it. A
155+
new constructor `MakeDecimal` returns an object. `NewDecimal` has been removed.
156+
151157
#### multi package
152158

153159
The subpackage has been deleted. You could use `pool` instead.

decimal/decimal.go

+37-21
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package decimal
1717

1818
import (
1919
"fmt"
20+
"reflect"
2021

2122
"github.com/shopspring/decimal"
2223
"github.com/vmihailenco/msgpack/v5"
@@ -37,48 +38,61 @@ const (
3738
decimalPrecision = 38
3839
)
3940

41+
var (
42+
one decimal.Decimal = decimal.NewFromInt(1)
43+
// -10^decimalPrecision - 1
44+
minSupportedDecimal decimal.Decimal = maxSupportedDecimal.Neg().Sub(one)
45+
// 10^decimalPrecision - 1
46+
maxSupportedDecimal decimal.Decimal = decimal.New(1, decimalPrecision).Sub(one)
47+
)
48+
4049
type Decimal struct {
4150
decimal.Decimal
4251
}
4352

44-
// NewDecimal creates a new Decimal from a decimal.Decimal.
45-
func NewDecimal(decimal decimal.Decimal) *Decimal {
46-
return &Decimal{Decimal: decimal}
53+
// MakeDecimal creates a new Decimal from a decimal.Decimal.
54+
func MakeDecimal(decimal decimal.Decimal) Decimal {
55+
return Decimal{Decimal: decimal}
4756
}
4857

49-
// NewDecimalFromString creates a new Decimal from a string.
50-
func NewDecimalFromString(src string) (result *Decimal, err error) {
58+
// MakeDecimalFromString creates a new Decimal from a string.
59+
func MakeDecimalFromString(src string) (Decimal, error) {
60+
result := Decimal{}
5161
dec, err := decimal.NewFromString(src)
5262
if err != nil {
53-
return
63+
return result, err
5464
}
55-
result = NewDecimal(dec)
56-
return
65+
result = MakeDecimal(dec)
66+
return result, nil
5767
}
5868

59-
// MarshalMsgpack serializes the Decimal into a MessagePack representation.
60-
func (decNum *Decimal) MarshalMsgpack() ([]byte, error) {
61-
one := decimal.NewFromInt(1)
62-
maxSupportedDecimal := decimal.New(1, decimalPrecision).Sub(one) // 10^decimalPrecision - 1
63-
minSupportedDecimal := maxSupportedDecimal.Neg().Sub(one) // -10^decimalPrecision - 1
64-
if decNum.GreaterThan(maxSupportedDecimal) {
69+
func decimalEncoder(e *msgpack.Encoder, v reflect.Value) ([]byte, error) {
70+
dec := v.Interface().(Decimal)
71+
if dec.GreaterThan(maxSupportedDecimal) {
6572
return nil, fmt.Errorf("msgpack: decimal number is bigger than maximum supported number (10^%d - 1)", decimalPrecision)
6673
}
67-
if decNum.LessThan(minSupportedDecimal) {
74+
if dec.LessThan(minSupportedDecimal) {
6875
return nil, fmt.Errorf("msgpack: decimal number is lesser than minimum supported number (-10^%d - 1)", decimalPrecision)
6976
}
7077

71-
strBuf := decNum.String()
78+
strBuf := dec.String()
7279
bcdBuf, err := encodeStringToBCD(strBuf)
7380
if err != nil {
7481
return nil, fmt.Errorf("msgpack: can't encode string (%s) to a BCD buffer: %w", strBuf, err)
7582
}
7683
return bcdBuf, nil
7784
}
7885

79-
// UnmarshalMsgpack deserializes a Decimal value from a MessagePack
80-
// representation.
81-
func (decNum *Decimal) UnmarshalMsgpack(b []byte) error {
86+
func decimalDecoder(d *msgpack.Decoder, v reflect.Value, extLen int) error {
87+
b := make([]byte, extLen)
88+
n, err := d.Buffered().Read(b)
89+
if err != nil {
90+
return err
91+
}
92+
if n < extLen {
93+
return fmt.Errorf("msgpack: unexpected end of stream after %d decimal bytes", n)
94+
}
95+
8296
// Decimal values can be encoded to fixext MessagePack, where buffer
8397
// has a fixed length encoded by first byte, and ext MessagePack, where
8498
// buffer length is not fixed and encoded by a number in a separate
@@ -92,14 +106,16 @@ func (decNum *Decimal) UnmarshalMsgpack(b []byte) error {
92106
return fmt.Errorf("msgpack: can't decode string from BCD buffer (%x): %w", b, err)
93107
}
94108
dec, err := decimal.NewFromString(digits)
95-
*decNum = *NewDecimal(dec)
96109
if err != nil {
97110
return fmt.Errorf("msgpack: can't encode string (%s) to a decimal number: %w", digits, err)
98111
}
99112

113+
ptr := v.Addr().Interface().(*Decimal)
114+
*ptr = MakeDecimal(dec)
100115
return nil
101116
}
102117

103118
func init() {
104-
msgpack.RegisterExt(decimalExtID, (*Decimal)(nil))
119+
msgpack.RegisterExtDecoder(decimalExtID, Decimal{}, decimalDecoder)
120+
msgpack.RegisterExtEncoder(decimalExtID, Decimal{}, decimalEncoder)
105121
}

decimal/decimal_test.go

+28-26
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,10 @@ func (t *TupleDecimal) DecodeMsgpack(d *msgpack.Decoder) error {
6363
return err
6464
}
6565

66-
if dec, ok := res.(*Decimal); !ok {
66+
if dec, ok := res.(Decimal); !ok {
6767
return fmt.Errorf("decimal doesn't match")
6868
} else {
69-
t.number = *dec
69+
t.number = dec
7070
}
7171
return nil
7272
}
@@ -143,12 +143,12 @@ var decimalSamples = []struct {
143143
func TestMPEncodeDecode(t *testing.T) {
144144
for _, testcase := range benchmarkSamples {
145145
t.Run(testcase.numString, func(t *testing.T) {
146-
decNum, err := NewDecimalFromString(testcase.numString)
146+
decNum, err := MakeDecimalFromString(testcase.numString)
147147
if err != nil {
148148
t.Fatal(err)
149149
}
150150
var buf []byte
151-
tuple := TupleDecimal{number: *decNum}
151+
tuple := TupleDecimal{number: decNum}
152152
if buf, err = msgpack.Marshal(&tuple); err != nil {
153153
t.Fatalf("Failed to msgpack.Encoder decimal number '%s' to a MessagePack buffer: %s", testcase.numString, err)
154154
}
@@ -251,7 +251,7 @@ func TestEncodeStringToBCDIncorrectNumber(t *testing.T) {
251251
func TestEncodeMaxNumber(t *testing.T) {
252252
referenceErrMsg := "msgpack: decimal number is bigger than maximum supported number (10^38 - 1)"
253253
decNum := decimal.New(1, DecimalPrecision) // // 10^DecimalPrecision
254-
tuple := TupleDecimal{number: *NewDecimal(decNum)}
254+
tuple := TupleDecimal{number: MakeDecimal(decNum)}
255255
_, err := msgpack.Marshal(&tuple)
256256
if err == nil {
257257
t.Fatalf("It is possible to msgpack.Encoder a number unsupported by Tarantool")
@@ -265,7 +265,7 @@ func TestEncodeMinNumber(t *testing.T) {
265265
referenceErrMsg := "msgpack: decimal number is lesser than minimum supported number (-10^38 - 1)"
266266
two := decimal.NewFromInt(2)
267267
decNum := decimal.New(1, DecimalPrecision).Neg().Sub(two) // -10^DecimalPrecision - 2
268-
tuple := TupleDecimal{number: *NewDecimal(decNum)}
268+
tuple := TupleDecimal{number: MakeDecimal(decNum)}
269269
_, err := msgpack.Marshal(&tuple)
270270
if err == nil {
271271
t.Fatalf("It is possible to msgpack.Encoder a number unsupported by Tarantool")
@@ -284,7 +284,7 @@ func benchmarkMPEncodeDecode(b *testing.B, src decimal.Decimal, dst interface{})
284284
var buf []byte
285285
var err error
286286
for i := 0; i < b.N; i++ {
287-
tuple := TupleDecimal{number: *NewDecimal(src)}
287+
tuple := TupleDecimal{number: MakeDecimal(src)}
288288
if buf, err = msgpack.Marshal(&tuple); err != nil {
289289
b.Fatal(err)
290290
}
@@ -309,13 +309,15 @@ func BenchmarkMPEncodeDecodeDecimal(b *testing.B) {
309309
func BenchmarkMPEncodeDecimal(b *testing.B) {
310310
for _, testcase := range benchmarkSamples {
311311
b.Run(testcase.numString, func(b *testing.B) {
312-
decNum, err := NewDecimalFromString(testcase.numString)
312+
decNum, err := MakeDecimalFromString(testcase.numString)
313313
if err != nil {
314314
b.Fatal(err)
315315
}
316316
b.ResetTimer()
317317
for i := 0; i < b.N; i++ {
318-
msgpack.Marshal(decNum)
318+
if _, err := msgpack.Marshal(decNum); err != nil {
319+
b.Fatal(err)
320+
}
319321
}
320322
})
321323
}
@@ -324,20 +326,20 @@ func BenchmarkMPEncodeDecimal(b *testing.B) {
324326
func BenchmarkMPDecodeDecimal(b *testing.B) {
325327
for _, testcase := range benchmarkSamples {
326328
b.Run(testcase.numString, func(b *testing.B) {
327-
decNum, err := NewDecimalFromString(testcase.numString)
329+
decNum, err := MakeDecimalFromString(testcase.numString)
328330
if err != nil {
329331
b.Fatal(err)
330332
}
331-
var buf []byte
332-
if buf, err = msgpack.Marshal(decNum); err != nil {
333+
buf, err := msgpack.Marshal(decNum)
334+
if err != nil {
333335
b.Fatal(err)
334336
}
335337
b.ResetTimer()
336-
var v TupleDecimal
337338
for i := 0; i < b.N; i++ {
338-
msgpack.Unmarshal(buf, &v)
339+
if err := msgpack.Unmarshal(buf, &decNum); err != nil {
340+
b.Fatal(err)
341+
}
339342
}
340-
341343
})
342344
}
343345
}
@@ -353,7 +355,7 @@ func tupleValueIsDecimal(t *testing.T, tuples []interface{}, number decimal.Deci
353355
if len(tpl) != 1 {
354356
t.Fatalf("Unexpected return value body (tuple len)")
355357
}
356-
if val, ok := tpl[0].(*Decimal); !ok || !val.Equal(number) {
358+
if val, ok := tpl[0].(Decimal); !ok || !val.Equal(number) {
357359
t.Fatalf("Unexpected return value body (tuple 0 field)")
358360
}
359361
}
@@ -418,9 +420,9 @@ func TestMPEncode(t *testing.T) {
418420
samples = append(samples, benchmarkSamples...)
419421
for _, testcase := range samples {
420422
t.Run(testcase.numString, func(t *testing.T) {
421-
dec, err := NewDecimalFromString(testcase.numString)
423+
dec, err := MakeDecimalFromString(testcase.numString)
422424
if err != nil {
423-
t.Fatalf("NewDecimalFromString() failed: %s", err.Error())
425+
t.Fatalf("MakeDecimalFromString() failed: %s", err.Error())
424426
}
425427
buf, err := msgpack.Marshal(dec)
426428
if err != nil {
@@ -451,7 +453,7 @@ func TestMPDecode(t *testing.T) {
451453
if err != nil {
452454
t.Fatalf("Unmsgpack.Marshalling failed: %s", err.Error())
453455
}
454-
decActual, ok := v.(*Decimal)
456+
decActual, ok := v.(Decimal)
455457
if !ok {
456458
t.Fatalf("Unable to convert to Decimal")
457459
}
@@ -502,7 +504,7 @@ func TestSelect(t *testing.T) {
502504
t.Fatalf("Failed to prepare test decimal: %s", err)
503505
}
504506

505-
ins := NewInsertRequest(space).Tuple([]interface{}{NewDecimal(number)})
507+
ins := NewInsertRequest(space).Tuple([]interface{}{MakeDecimal(number)})
506508
resp, err := conn.Do(ins).Get()
507509
if err != nil {
508510
t.Fatalf("Decimal insert failed: %s", err)
@@ -519,7 +521,7 @@ func TestSelect(t *testing.T) {
519521
Offset(offset).
520522
Limit(limit).
521523
Iterator(IterEq).
522-
Key([]interface{}{NewDecimal(number)})
524+
Key([]interface{}{MakeDecimal(number)})
523525
resp, err = conn.Do(sel).Get()
524526
if err != nil {
525527
t.Fatalf("Decimal select failed: %s", err.Error())
@@ -529,7 +531,7 @@ func TestSelect(t *testing.T) {
529531
}
530532
tupleValueIsDecimal(t, resp.Data, number)
531533

532-
del := NewDeleteRequest(space).Index(index).Key([]interface{}{NewDecimal(number)})
534+
del := NewDeleteRequest(space).Index(index).Key([]interface{}{MakeDecimal(number)})
533535
resp, err = conn.Do(del).Get()
534536
if err != nil {
535537
t.Fatalf("Decimal delete failed: %s", err)
@@ -543,7 +545,7 @@ func assertInsert(t *testing.T, conn *Connection, numString string) {
543545
t.Fatalf("Failed to prepare test decimal: %s", err)
544546
}
545547

546-
ins := NewInsertRequest(space).Tuple([]interface{}{NewDecimal(number)})
548+
ins := NewInsertRequest(space).Tuple([]interface{}{MakeDecimal(number)})
547549
resp, err := conn.Do(ins).Get()
548550
if err != nil {
549551
t.Fatalf("Decimal insert failed: %s", err)
@@ -553,7 +555,7 @@ func assertInsert(t *testing.T, conn *Connection, numString string) {
553555
}
554556
tupleValueIsDecimal(t, resp.Data, number)
555557

556-
del := NewDeleteRequest(space).Index(index).Key([]interface{}{NewDecimal(number)})
558+
del := NewDeleteRequest(space).Index(index).Key([]interface{}{MakeDecimal(number)})
557559
resp, err = conn.Do(del).Get()
558560
if err != nil {
559561
t.Fatalf("Decimal delete failed: %s", err)
@@ -586,7 +588,7 @@ func TestReplace(t *testing.T) {
586588
t.Fatalf("Failed to prepare test decimal: %s", err)
587589
}
588590

589-
rep := NewReplaceRequest(space).Tuple([]interface{}{NewDecimal(number)})
591+
rep := NewReplaceRequest(space).Tuple([]interface{}{MakeDecimal(number)})
590592
respRep, errRep := conn.Do(rep).Get()
591593
if errRep != nil {
592594
t.Fatalf("Decimal replace failed: %s", errRep)
@@ -600,7 +602,7 @@ func TestReplace(t *testing.T) {
600602
Index(index).
601603
Limit(1).
602604
Iterator(IterEq).
603-
Key([]interface{}{NewDecimal(number)})
605+
Key([]interface{}{MakeDecimal(number)})
604606
respSel, errSel := conn.Do(sel).Get()
605607
if errSel != nil {
606608
t.Fatalf("Decimal select failed: %s", errSel)

decimal/example_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func Example() {
3535

3636
spaceNo := uint32(524)
3737

38-
number, err := NewDecimalFromString("-22.804")
38+
number, err := MakeDecimalFromString("-22.804")
3939
if err != nil {
4040
log.Fatalf("Failed to prepare test decimal: %s", err)
4141
}

0 commit comments

Comments
 (0)