Skip to content

Commit

Permalink
api: the Decimal type is immutable
Browse files Browse the repository at this point in the history
The patch forces the use of objects of type Decimal instead
of pointers.

Part of #238
  • Loading branch information
oleg-jukovec committed Jun 30, 2023
1 parent 3aeb8c2 commit b38f619
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 48 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
- Change encoding of the queue.Identify() UUID argument from binary blob to
plain string. Needed for upgrade to Tarantool 3.0, where a binary blob is
decoded to a varbinary object (#313).
- Use objects of the Decimal type instead of pointers (#238)

### Deprecated

Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ faster than other packages according to public benchmarks.
* [API reference](#api-reference)
* [Walking\-through example](#walking-through-example)
* [Migration to v2](#migration-to-v2)
* [decimal package](#decimal-package)
* [multi package](#multi-package)
* [pool package](#pool-package)
* [msgpack.v5](#msgpackv5)
Expand Down Expand Up @@ -148,6 +149,11 @@ by `Connect()`.

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

#### decimal package

Now you need to use objects of the Decimal type instead of pointers to it. A
new constructor `MakeDecimal` returns an object. `NewDecimal` has been removed.

#### multi package

The subpackage has been deleted. You could use `pool` instead.
Expand Down
58 changes: 37 additions & 21 deletions decimal/decimal.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package decimal

import (
"fmt"
"reflect"

"github.com/shopspring/decimal"
"github.com/vmihailenco/msgpack/v5"
Expand All @@ -42,54 +43,67 @@ const (
decimalPrecision = 38
)

var (
one decimal.Decimal = decimal.NewFromInt(1)
// -10^decimalPrecision - 1
minSupportedDecimal decimal.Decimal = maxSupportedDecimal.Neg().Sub(one)
// 10^decimalPrecision - 1
maxSupportedDecimal decimal.Decimal = decimal.New(1, decimalPrecision).Sub(one)
)

type Decimal struct {
decimal.Decimal
}

// NewDecimal creates a new Decimal from a decimal.Decimal.
func NewDecimal(decimal decimal.Decimal) *Decimal {
return &Decimal{Decimal: decimal}
// MakeDecimal creates a new Decimal from a decimal.Decimal.
func MakeDecimal(decimal decimal.Decimal) Decimal {
return Decimal{Decimal: decimal}
}

// NewDecimalFromString creates a new Decimal from a string.
func NewDecimalFromString(src string) (result *Decimal, err error) {
// MakeDecimalFromString creates a new Decimal from a string.
func MakeDecimalFromString(src string) (Decimal, error) {
result := Decimal{}
dec, err := decimal.NewFromString(src)
if err != nil {
return
return result, err
}
result = NewDecimal(dec)
return
result = MakeDecimal(dec)
return result, nil
}

// MarshalMsgpack serializes the Decimal into a MessagePack representation.
func (decNum *Decimal) MarshalMsgpack() ([]byte, error) {
one := decimal.NewFromInt(1)
maxSupportedDecimal := decimal.New(1, decimalPrecision).Sub(one) // 10^decimalPrecision - 1
minSupportedDecimal := maxSupportedDecimal.Neg().Sub(one) // -10^decimalPrecision - 1
if decNum.GreaterThan(maxSupportedDecimal) {
func decimalEncoder(e *msgpack.Encoder, v reflect.Value) ([]byte, error) {
dec := v.Interface().(Decimal)
if dec.GreaterThan(maxSupportedDecimal) {
return nil,
fmt.Errorf(
"msgpack: decimal number is bigger than maximum supported number (10^%d - 1)",
decimalPrecision)
}
if decNum.LessThan(minSupportedDecimal) {
if dec.LessThan(minSupportedDecimal) {
return nil,
fmt.Errorf(
"msgpack: decimal number is lesser than minimum supported number (-10^%d - 1)",
decimalPrecision)
}

strBuf := decNum.String()
strBuf := dec.String()
bcdBuf, err := encodeStringToBCD(strBuf)
if err != nil {
return nil, fmt.Errorf("msgpack: can't encode string (%s) to a BCD buffer: %w", strBuf, err)
}
return bcdBuf, nil
}

// UnmarshalMsgpack deserializes a Decimal value from a MessagePack
// representation.
func (decNum *Decimal) UnmarshalMsgpack(b []byte) error {
func decimalDecoder(d *msgpack.Decoder, v reflect.Value, extLen int) error {
b := make([]byte, extLen)
n, err := d.Buffered().Read(b)
if err != nil {
return err
}
if n < extLen {
return fmt.Errorf("msgpack: unexpected end of stream after %d decimal bytes", n)
}

// Decimal values can be encoded to fixext MessagePack, where buffer
// has a fixed length encoded by first byte, and ext MessagePack, where
// buffer length is not fixed and encoded by a number in a separate
Expand All @@ -111,10 +125,12 @@ func (decNum *Decimal) UnmarshalMsgpack(b []byte) error {
if exp != 0 {
dec = dec.Shift(int32(exp))
}
*decNum = *NewDecimal(dec)
ptr := v.Addr().Interface().(*Decimal)
*ptr = MakeDecimal(dec)
return nil
}

func init() {
msgpack.RegisterExt(decimalExtID, (*Decimal)(nil))
msgpack.RegisterExtDecoder(decimalExtID, Decimal{}, decimalDecoder)
msgpack.RegisterExtEncoder(decimalExtID, Decimal{}, decimalEncoder)
}
54 changes: 28 additions & 26 deletions decimal/decimal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ func (t *TupleDecimal) DecodeMsgpack(d *msgpack.Decoder) error {
return err
}

if dec, ok := res.(*Decimal); !ok {
if dec, ok := res.(Decimal); !ok {
return fmt.Errorf("decimal doesn't match")
} else {
t.number = *dec
t.number = dec
}
return nil
}
Expand Down Expand Up @@ -160,12 +160,12 @@ var decimalSamples = []struct {
func TestMPEncodeDecode(t *testing.T) {
for _, testcase := range benchmarkSamples {
t.Run(testcase.numString, func(t *testing.T) {
decNum, err := NewDecimalFromString(testcase.numString)
decNum, err := MakeDecimalFromString(testcase.numString)
if err != nil {
t.Fatal(err)
}
var buf []byte
tuple := TupleDecimal{number: *decNum}
tuple := TupleDecimal{number: decNum}
if buf, err = msgpack.Marshal(&tuple); err != nil {
t.Fatalf(
"Failed to msgpack.Encoder decimal number '%s' to a MessagePack buffer: %s",
Expand Down Expand Up @@ -270,7 +270,7 @@ func TestEncodeMaxNumber(t *testing.T) {
referenceErrMsg := "msgpack: decimal number is bigger than maximum " +
"supported number (10^38 - 1)"
decNum := decimal.New(1, DecimalPrecision) // // 10^DecimalPrecision
tuple := TupleDecimal{number: *NewDecimal(decNum)}
tuple := TupleDecimal{number: MakeDecimal(decNum)}
_, err := msgpack.Marshal(&tuple)
if err == nil {
t.Fatalf("It is possible to msgpack.Encoder a number unsupported by Tarantool")
Expand All @@ -285,7 +285,7 @@ func TestEncodeMinNumber(t *testing.T) {
"supported number (-10^38 - 1)"
two := decimal.NewFromInt(2)
decNum := decimal.New(1, DecimalPrecision).Neg().Sub(two) // -10^DecimalPrecision - 2
tuple := TupleDecimal{number: *NewDecimal(decNum)}
tuple := TupleDecimal{number: MakeDecimal(decNum)}
_, err := msgpack.Marshal(&tuple)
if err == nil {
t.Fatalf("It is possible to msgpack.Encoder a number unsupported by Tarantool")
Expand All @@ -302,7 +302,7 @@ func benchmarkMPEncodeDecode(b *testing.B, src decimal.Decimal, dst interface{})
var buf []byte
var err error
for i := 0; i < b.N; i++ {
tuple := TupleDecimal{number: *NewDecimal(src)}
tuple := TupleDecimal{number: MakeDecimal(src)}
if buf, err = msgpack.Marshal(&tuple); err != nil {
b.Fatal(err)
}
Expand All @@ -327,13 +327,15 @@ func BenchmarkMPEncodeDecodeDecimal(b *testing.B) {
func BenchmarkMPEncodeDecimal(b *testing.B) {
for _, testcase := range benchmarkSamples {
b.Run(testcase.numString, func(b *testing.B) {
decNum, err := NewDecimalFromString(testcase.numString)
decNum, err := MakeDecimalFromString(testcase.numString)
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
msgpack.Marshal(decNum)
if _, err := msgpack.Marshal(decNum); err != nil {
b.Fatal(err)
}
}
})
}
Expand All @@ -342,20 +344,20 @@ func BenchmarkMPEncodeDecimal(b *testing.B) {
func BenchmarkMPDecodeDecimal(b *testing.B) {
for _, testcase := range benchmarkSamples {
b.Run(testcase.numString, func(b *testing.B) {
decNum, err := NewDecimalFromString(testcase.numString)
decNum, err := MakeDecimalFromString(testcase.numString)
if err != nil {
b.Fatal(err)
}
var buf []byte
if buf, err = msgpack.Marshal(decNum); err != nil {
buf, err := msgpack.Marshal(decNum)
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
var v TupleDecimal
for i := 0; i < b.N; i++ {
msgpack.Unmarshal(buf, &v)
if err := msgpack.Unmarshal(buf, &decNum); err != nil {
b.Fatal(err)
}
}

})
}
}
Expand All @@ -371,7 +373,7 @@ func tupleValueIsDecimal(t *testing.T, tuples []interface{}, number decimal.Deci
if len(tpl) != 1 {
t.Fatalf("Unexpected return value body (tuple len)")
}
if val, ok := tpl[0].(*Decimal); !ok || !val.Equal(number) {
if val, ok := tpl[0].(Decimal); !ok || !val.Equal(number) {
t.Fatalf("Unexpected return value body (tuple 0 field)")
}
}
Expand Down Expand Up @@ -447,9 +449,9 @@ func TestMPEncode(t *testing.T) {
samples = append(samples, benchmarkSamples...)
for _, testcase := range samples {
t.Run(testcase.numString, func(t *testing.T) {
dec, err := NewDecimalFromString(testcase.numString)
dec, err := MakeDecimalFromString(testcase.numString)
if err != nil {
t.Fatalf("NewDecimalFromString() failed: %s", err.Error())
t.Fatalf("MakeDecimalFromString() failed: %s", err.Error())
}
buf, err := msgpack.Marshal(dec)
if err != nil {
Expand Down Expand Up @@ -481,7 +483,7 @@ func TestMPDecode(t *testing.T) {
if err != nil {
t.Fatalf("Unmsgpack.Marshalling failed: %s", err.Error())
}
decActual, ok := v.(*Decimal)
decActual, ok := v.(Decimal)
if !ok {
t.Fatalf("Unable to convert to Decimal")
}
Expand Down Expand Up @@ -532,7 +534,7 @@ func TestSelect(t *testing.T) {
t.Fatalf("Failed to prepare test decimal: %s", err)
}

ins := NewInsertRequest(space).Tuple([]interface{}{NewDecimal(number)})
ins := NewInsertRequest(space).Tuple([]interface{}{MakeDecimal(number)})
resp, err := conn.Do(ins).Get()
if err != nil {
t.Fatalf("Decimal insert failed: %s", err)
Expand All @@ -549,7 +551,7 @@ func TestSelect(t *testing.T) {
Offset(offset).
Limit(limit).
Iterator(IterEq).
Key([]interface{}{NewDecimal(number)})
Key([]interface{}{MakeDecimal(number)})
resp, err = conn.Do(sel).Get()
if err != nil {
t.Fatalf("Decimal select failed: %s", err.Error())
Expand All @@ -559,7 +561,7 @@ func TestSelect(t *testing.T) {
}
tupleValueIsDecimal(t, resp.Data, number)

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

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

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

rep := NewReplaceRequest(space).Tuple([]interface{}{NewDecimal(number)})
rep := NewReplaceRequest(space).Tuple([]interface{}{MakeDecimal(number)})
respRep, errRep := conn.Do(rep).Get()
if errRep != nil {
t.Fatalf("Decimal replace failed: %s", errRep)
Expand All @@ -662,7 +664,7 @@ func TestReplace(t *testing.T) {
Index(index).
Limit(1).
Iterator(IterEq).
Key([]interface{}{NewDecimal(number)})
Key([]interface{}{MakeDecimal(number)})
respSel, errSel := conn.Do(sel).Get()
if errSel != nil {
t.Fatalf("Decimal select failed: %s", errSel)
Expand Down
2 changes: 1 addition & 1 deletion decimal/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func Example() {

spaceNo := uint32(524)

number, err := NewDecimalFromString("-22.804")
number, err := MakeDecimalFromString("-22.804")
if err != nil {
log.Fatalf("Failed to prepare test decimal: %s", err)
}
Expand Down

0 comments on commit b38f619

Please sign in to comment.