Skip to content

Commit 63b1024

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 3aeb8c2 commit 63b1024

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
@@ -24,6 +24,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
2424
- Change encoding of the queue.Identify() UUID argument from binary blob to
2525
plain string. Needed for upgrade to Tarantool 3.0, where a binary blob is
2626
decoded to a varbinary object (#313).
27+
- Use objects of the Decimal type instead of pointers (#238)
2728

2829
### Deprecated
2930

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
@@ -21,6 +21,7 @@ package decimal
2121

2222
import (
2323
"fmt"
24+
"reflect"
2425

2526
"github.com/shopspring/decimal"
2627
"github.com/vmihailenco/msgpack/v5"
@@ -42,54 +43,67 @@ const (
4243
decimalPrecision = 38
4344
)
4445

46+
var (
47+
one decimal.Decimal = decimal.NewFromInt(1)
48+
// -10^decimalPrecision - 1
49+
minSupportedDecimal decimal.Decimal = maxSupportedDecimal.Neg().Sub(one)
50+
// 10^decimalPrecision - 1
51+
maxSupportedDecimal decimal.Decimal = decimal.New(1, decimalPrecision).Sub(one)
52+
)
53+
4554
type Decimal struct {
4655
decimal.Decimal
4756
}
4857

49-
// NewDecimal creates a new Decimal from a decimal.Decimal.
50-
func NewDecimal(decimal decimal.Decimal) *Decimal {
51-
return &Decimal{Decimal: decimal}
58+
// MakeDecimal creates a new Decimal from a decimal.Decimal.
59+
func MakeDecimal(decimal decimal.Decimal) Decimal {
60+
return Decimal{Decimal: decimal}
5261
}
5362

54-
// NewDecimalFromString creates a new Decimal from a string.
55-
func NewDecimalFromString(src string) (result *Decimal, err error) {
63+
// MakeDecimalFromString creates a new Decimal from a string.
64+
func MakeDecimalFromString(src string) (Decimal, error) {
65+
result := Decimal{}
5666
dec, err := decimal.NewFromString(src)
5767
if err != nil {
58-
return
68+
return result, err
5969
}
60-
result = NewDecimal(dec)
61-
return
70+
result = MakeDecimal(dec)
71+
return result, nil
6272
}
6373

64-
// MarshalMsgpack serializes the Decimal into a MessagePack representation.
65-
func (decNum *Decimal) MarshalMsgpack() ([]byte, error) {
66-
one := decimal.NewFromInt(1)
67-
maxSupportedDecimal := decimal.New(1, decimalPrecision).Sub(one) // 10^decimalPrecision - 1
68-
minSupportedDecimal := maxSupportedDecimal.Neg().Sub(one) // -10^decimalPrecision - 1
69-
if decNum.GreaterThan(maxSupportedDecimal) {
74+
func decimalEncoder(e *msgpack.Encoder, v reflect.Value) ([]byte, error) {
75+
dec := v.Interface().(Decimal)
76+
if dec.GreaterThan(maxSupportedDecimal) {
7077
return nil,
7178
fmt.Errorf(
7279
"msgpack: decimal number is bigger than maximum supported number (10^%d - 1)",
7380
decimalPrecision)
7481
}
75-
if decNum.LessThan(minSupportedDecimal) {
82+
if dec.LessThan(minSupportedDecimal) {
7683
return nil,
7784
fmt.Errorf(
7885
"msgpack: decimal number is lesser than minimum supported number (-10^%d - 1)",
7986
decimalPrecision)
8087
}
8188

82-
strBuf := decNum.String()
89+
strBuf := dec.String()
8390
bcdBuf, err := encodeStringToBCD(strBuf)
8491
if err != nil {
8592
return nil, fmt.Errorf("msgpack: can't encode string (%s) to a BCD buffer: %w", strBuf, err)
8693
}
8794
return bcdBuf, nil
8895
}
8996

90-
// UnmarshalMsgpack deserializes a Decimal value from a MessagePack
91-
// representation.
92-
func (decNum *Decimal) UnmarshalMsgpack(b []byte) error {
97+
func decimalDecoder(d *msgpack.Decoder, v reflect.Value, extLen int) error {
98+
b := make([]byte, extLen)
99+
n, err := d.Buffered().Read(b)
100+
if err != nil {
101+
return err
102+
}
103+
if n < extLen {
104+
return fmt.Errorf("msgpack: unexpected end of stream after %d decimal bytes", n)
105+
}
106+
93107
// Decimal values can be encoded to fixext MessagePack, where buffer
94108
// has a fixed length encoded by first byte, and ext MessagePack, where
95109
// buffer length is not fixed and encoded by a number in a separate
@@ -111,10 +125,12 @@ func (decNum *Decimal) UnmarshalMsgpack(b []byte) error {
111125
if exp != 0 {
112126
dec = dec.Shift(int32(exp))
113127
}
114-
*decNum = *NewDecimal(dec)
128+
ptr := v.Addr().Interface().(*Decimal)
129+
*ptr = MakeDecimal(dec)
115130
return nil
116131
}
117132

118133
func init() {
119-
msgpack.RegisterExt(decimalExtID, (*Decimal)(nil))
134+
msgpack.RegisterExtDecoder(decimalExtID, Decimal{}, decimalDecoder)
135+
msgpack.RegisterExtEncoder(decimalExtID, Decimal{}, decimalEncoder)
120136
}

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
}
@@ -160,12 +160,12 @@ var decimalSamples = []struct {
160160
func TestMPEncodeDecode(t *testing.T) {
161161
for _, testcase := range benchmarkSamples {
162162
t.Run(testcase.numString, func(t *testing.T) {
163-
decNum, err := NewDecimalFromString(testcase.numString)
163+
decNum, err := MakeDecimalFromString(testcase.numString)
164164
if err != nil {
165165
t.Fatal(err)
166166
}
167167
var buf []byte
168-
tuple := TupleDecimal{number: *decNum}
168+
tuple := TupleDecimal{number: decNum}
169169
if buf, err = msgpack.Marshal(&tuple); err != nil {
170170
t.Fatalf(
171171
"Failed to msgpack.Encoder decimal number '%s' to a MessagePack buffer: %s",
@@ -270,7 +270,7 @@ func TestEncodeMaxNumber(t *testing.T) {
270270
referenceErrMsg := "msgpack: decimal number is bigger than maximum " +
271271
"supported number (10^38 - 1)"
272272
decNum := decimal.New(1, DecimalPrecision) // // 10^DecimalPrecision
273-
tuple := TupleDecimal{number: *NewDecimal(decNum)}
273+
tuple := TupleDecimal{number: MakeDecimal(decNum)}
274274
_, err := msgpack.Marshal(&tuple)
275275
if err == nil {
276276
t.Fatalf("It is possible to msgpack.Encoder a number unsupported by Tarantool")
@@ -285,7 +285,7 @@ func TestEncodeMinNumber(t *testing.T) {
285285
"supported number (-10^38 - 1)"
286286
two := decimal.NewFromInt(2)
287287
decNum := decimal.New(1, DecimalPrecision).Neg().Sub(two) // -10^DecimalPrecision - 2
288-
tuple := TupleDecimal{number: *NewDecimal(decNum)}
288+
tuple := TupleDecimal{number: MakeDecimal(decNum)}
289289
_, err := msgpack.Marshal(&tuple)
290290
if err == nil {
291291
t.Fatalf("It is possible to msgpack.Encoder a number unsupported by Tarantool")
@@ -302,7 +302,7 @@ func benchmarkMPEncodeDecode(b *testing.B, src decimal.Decimal, dst interface{})
302302
var buf []byte
303303
var err error
304304
for i := 0; i < b.N; i++ {
305-
tuple := TupleDecimal{number: *NewDecimal(src)}
305+
tuple := TupleDecimal{number: MakeDecimal(src)}
306306
if buf, err = msgpack.Marshal(&tuple); err != nil {
307307
b.Fatal(err)
308308
}
@@ -327,13 +327,15 @@ func BenchmarkMPEncodeDecodeDecimal(b *testing.B) {
327327
func BenchmarkMPEncodeDecimal(b *testing.B) {
328328
for _, testcase := range benchmarkSamples {
329329
b.Run(testcase.numString, func(b *testing.B) {
330-
decNum, err := NewDecimalFromString(testcase.numString)
330+
decNum, err := MakeDecimalFromString(testcase.numString)
331331
if err != nil {
332332
b.Fatal(err)
333333
}
334334
b.ResetTimer()
335335
for i := 0; i < b.N; i++ {
336-
msgpack.Marshal(decNum)
336+
if _, err := msgpack.Marshal(decNum); err != nil {
337+
b.Fatal(err)
338+
}
337339
}
338340
})
339341
}
@@ -342,20 +344,20 @@ func BenchmarkMPEncodeDecimal(b *testing.B) {
342344
func BenchmarkMPDecodeDecimal(b *testing.B) {
343345
for _, testcase := range benchmarkSamples {
344346
b.Run(testcase.numString, func(b *testing.B) {
345-
decNum, err := NewDecimalFromString(testcase.numString)
347+
decNum, err := MakeDecimalFromString(testcase.numString)
346348
if err != nil {
347349
b.Fatal(err)
348350
}
349-
var buf []byte
350-
if buf, err = msgpack.Marshal(decNum); err != nil {
351+
buf, err := msgpack.Marshal(decNum)
352+
if err != nil {
351353
b.Fatal(err)
352354
}
353355
b.ResetTimer()
354-
var v TupleDecimal
355356
for i := 0; i < b.N; i++ {
356-
msgpack.Unmarshal(buf, &v)
357+
if err := msgpack.Unmarshal(buf, &decNum); err != nil {
358+
b.Fatal(err)
359+
}
357360
}
358-
359361
})
360362
}
361363
}
@@ -371,7 +373,7 @@ func tupleValueIsDecimal(t *testing.T, tuples []interface{}, number decimal.Deci
371373
if len(tpl) != 1 {
372374
t.Fatalf("Unexpected return value body (tuple len)")
373375
}
374-
if val, ok := tpl[0].(*Decimal); !ok || !val.Equal(number) {
376+
if val, ok := tpl[0].(Decimal); !ok || !val.Equal(number) {
375377
t.Fatalf("Unexpected return value body (tuple 0 field)")
376378
}
377379
}
@@ -447,9 +449,9 @@ func TestMPEncode(t *testing.T) {
447449
samples = append(samples, benchmarkSamples...)
448450
for _, testcase := range samples {
449451
t.Run(testcase.numString, func(t *testing.T) {
450-
dec, err := NewDecimalFromString(testcase.numString)
452+
dec, err := MakeDecimalFromString(testcase.numString)
451453
if err != nil {
452-
t.Fatalf("NewDecimalFromString() failed: %s", err.Error())
454+
t.Fatalf("MakeDecimalFromString() failed: %s", err.Error())
453455
}
454456
buf, err := msgpack.Marshal(dec)
455457
if err != nil {
@@ -481,7 +483,7 @@ func TestMPDecode(t *testing.T) {
481483
if err != nil {
482484
t.Fatalf("Unmsgpack.Marshalling failed: %s", err.Error())
483485
}
484-
decActual, ok := v.(*Decimal)
486+
decActual, ok := v.(Decimal)
485487
if !ok {
486488
t.Fatalf("Unable to convert to Decimal")
487489
}
@@ -532,7 +534,7 @@ func TestSelect(t *testing.T) {
532534
t.Fatalf("Failed to prepare test decimal: %s", err)
533535
}
534536

535-
ins := NewInsertRequest(space).Tuple([]interface{}{NewDecimal(number)})
537+
ins := NewInsertRequest(space).Tuple([]interface{}{MakeDecimal(number)})
536538
resp, err := conn.Do(ins).Get()
537539
if err != nil {
538540
t.Fatalf("Decimal insert failed: %s", err)
@@ -549,7 +551,7 @@ func TestSelect(t *testing.T) {
549551
Offset(offset).
550552
Limit(limit).
551553
Iterator(IterEq).
552-
Key([]interface{}{NewDecimal(number)})
554+
Key([]interface{}{MakeDecimal(number)})
553555
resp, err = conn.Do(sel).Get()
554556
if err != nil {
555557
t.Fatalf("Decimal select failed: %s", err.Error())
@@ -559,7 +561,7 @@ func TestSelect(t *testing.T) {
559561
}
560562
tupleValueIsDecimal(t, resp.Data, number)
561563

562-
del := NewDeleteRequest(space).Index(index).Key([]interface{}{NewDecimal(number)})
564+
del := NewDeleteRequest(space).Index(index).Key([]interface{}{MakeDecimal(number)})
563565
resp, err = conn.Do(del).Get()
564566
if err != nil {
565567
t.Fatalf("Decimal delete failed: %s", err)
@@ -604,7 +606,7 @@ func assertInsert(t *testing.T, conn *Connection, numString string) {
604606
t.Fatalf("Failed to prepare test decimal: %s", err)
605607
}
606608

607-
ins := NewInsertRequest(space).Tuple([]interface{}{NewDecimal(number)})
609+
ins := NewInsertRequest(space).Tuple([]interface{}{MakeDecimal(number)})
608610
resp, err := conn.Do(ins).Get()
609611
if err != nil {
610612
t.Fatalf("Decimal insert failed: %s", err)
@@ -614,7 +616,7 @@ func assertInsert(t *testing.T, conn *Connection, numString string) {
614616
}
615617
tupleValueIsDecimal(t, resp.Data, number)
616618

617-
del := NewDeleteRequest(space).Index(index).Key([]interface{}{NewDecimal(number)})
619+
del := NewDeleteRequest(space).Index(index).Key([]interface{}{MakeDecimal(number)})
618620
resp, err = conn.Do(del).Get()
619621
if err != nil {
620622
t.Fatalf("Decimal delete failed: %s", err)
@@ -648,7 +650,7 @@ func TestReplace(t *testing.T) {
648650
t.Fatalf("Failed to prepare test decimal: %s", err)
649651
}
650652

651-
rep := NewReplaceRequest(space).Tuple([]interface{}{NewDecimal(number)})
653+
rep := NewReplaceRequest(space).Tuple([]interface{}{MakeDecimal(number)})
652654
respRep, errRep := conn.Do(rep).Get()
653655
if errRep != nil {
654656
t.Fatalf("Decimal replace failed: %s", errRep)
@@ -662,7 +664,7 @@ func TestReplace(t *testing.T) {
662664
Index(index).
663665
Limit(1).
664666
Iterator(IterEq).
665-
Key([]interface{}{NewDecimal(number)})
667+
Key([]interface{}{MakeDecimal(number)})
666668
respSel, errSel := conn.Do(sel).Get()
667669
if errSel != nil {
668670
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)