Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release 1.12.1 #325

Merged
merged 6 commits into from
Aug 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,26 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.

### Fixed

## [1.12.1] - 2023-08-03

The patch release with fixes from the master branch.

### Added

### Changed

- 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)

### Fixed

- Flaky decimal/TestSelect (#300)
- Race condition at roundRobinStrategy.GetNextConnection() (#309)
- Incorrect decoding of an MP_DECIMAL when the `scale` value is negative (#314)
- Incorrect options (`after`, `batch_size` and `force_map_call`) setup for
crud.SelectRequest (#320)

## [1.12.0] - 2023-06-07

The release introduces the ability to gracefully close Connection
Expand Down
29 changes: 29 additions & 0 deletions connection_pool/connection_pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2065,6 +2065,35 @@ func TestDo(t *testing.T) {
require.NotNilf(t, resp, "response is nil after Ping")
}

func TestDo_concurrent(t *testing.T) {
roles := []bool{true, true, false, true, false}

err := test_helpers.SetClusterRO(servers, connOpts, roles)
require.Nilf(t, err, "fail to set roles for cluster")

connPool, err := connection_pool.Connect(servers, connOpts)
require.Nilf(t, err, "failed to connect")
require.NotNilf(t, connPool, "conn is nil after Connect")

defer connPool.Close()

req := tarantool.NewPingRequest()
const concurrency = 100
var wg sync.WaitGroup
wg.Add(concurrency)

for i := 0; i < concurrency; i++ {
go func() {
defer wg.Done()

_, err := connPool.Do(req, connection_pool.ANY).Get()
assert.Nil(t, err)
}()
}

wg.Wait()
}

func TestNewPrepared(t *testing.T) {
test_helpers.SkipIfSQLUnsupported(t)

Expand Down
14 changes: 7 additions & 7 deletions connection_pool/round_robin.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package connection_pool

import (
"sync"
"sync/atomic"

"github.com/tarantool/go-tarantool"
)
Expand All @@ -10,8 +11,8 @@ type RoundRobinStrategy struct {
conns []*tarantool.Connection
indexByAddr map[string]uint
mutex sync.RWMutex
size uint
current uint
size uint64
current uint64
}

func NewEmptyRoundRobin(size int) *RoundRobinStrategy {
Expand Down Expand Up @@ -98,13 +99,12 @@ func (r *RoundRobinStrategy) AddConn(addr string, conn *tarantool.Connection) {
r.conns[idx] = conn
} else {
r.conns = append(r.conns, conn)
r.indexByAddr[addr] = r.size
r.indexByAddr[addr] = uint(r.size)
r.size += 1
}
}

func (r *RoundRobinStrategy) nextIndex() uint {
ret := r.current % r.size
r.current++
return ret
func (r *RoundRobinStrategy) nextIndex() uint64 {
next := atomic.AddUint64(&r.current, 1)
return (next - 1) % r.size
}
38 changes: 38 additions & 0 deletions crud/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,41 @@ func ExampleResult_errorMany() {
// Output:
// Failed to execute request: CallError:
}

func ExampleSelectRequest_pagination() {
conn := exampleConnect()

const (
fromTuple = 5
allTuples = 10
)
var tuple interface{}
for i := 0; i < allTuples; i++ {
req := crud.MakeReplaceRequest(exampleSpace).
Tuple([]interface{}{uint(3000 + i), nil, "bla"})
ret := crud.Result{}
if err := conn.Do(req).GetTyped(&ret); err != nil {
fmt.Printf("Failed to initialize the example: %s\n", err)
return
}
if i == fromTuple {
tuple = ret.Rows.([]interface{})[0]
}
}

req := crud.MakeSelectRequest(exampleSpace).
Opts(crud.SelectOpts{
First: crud.MakeOptInt(2),
After: crud.MakeOptTuple(tuple),
})
ret := crud.Result{}
if err := conn.Do(req).GetTyped(&ret); err != nil {
fmt.Printf("Failed to execute request: %s", err)
return
}
fmt.Println(ret.Metadata)
fmt.Println(ret.Rows)
// Output:
// [{id unsigned false} {bucket_id unsigned true} {name string false}]
// [[3006 32 bla] [3007 33 bla]]
}
6 changes: 3 additions & 3 deletions crud/select.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@ func (opts SelectOpts) EncodeMsgpack(enc *encoder) error {
values[6], exists[6] = opts.Balance.Get()
values[7], exists[7] = opts.First.Get()
values[8], exists[8] = opts.After.Get()
values[8], exists[8] = opts.BatchSize.Get()
values[8], exists[8] = opts.ForceMapCall.Get()
values[8], exists[8] = opts.Fullscan.Get()
values[9], exists[9] = opts.BatchSize.Get()
values[10], exists[10] = opts.ForceMapCall.Get()
values[11], exists[11] = opts.Fullscan.Get()

return encodeOptions(enc, names[:], values[:], exists[:])
}
Expand Down
44 changes: 17 additions & 27 deletions decimal/bcd.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,11 @@ package decimal
// * An implementation in C language https://github.com/tarantool/decNumber/blob/master/decPacked.c

import (
"bytes"
"fmt"
"strings"

"github.com/vmihailenco/msgpack/v5"
)

const (
Expand Down Expand Up @@ -183,13 +186,17 @@ func encodeStringToBCD(buf string) ([]byte, error) {
// ended by a 4-bit sign nibble in the least significant four bits of the final
// byte. The scale is used (negated) as the exponent of the decimal number.
// Note that zeroes may have a sign and/or a scale.
func decodeStringFromBCD(bcdBuf []byte) (string, error) {
// Index of a byte with scale.
const scaleIdx = 0
scale := int(bcdBuf[scaleIdx])
func decodeStringFromBCD(bcdBuf []byte) (string, int, error) {
// Read scale.
buf := bytes.NewBuffer(bcdBuf)
dec := msgpack.NewDecoder(buf)
scale, err := dec.DecodeInt()
if err != nil {
return "", 0, fmt.Errorf("unable to decode the decimal scale: %w", err)
}

// Get a BCD buffer without scale.
bcdBuf = bcdBuf[scaleIdx+1:]
// Get the data without the scale.
bcdBuf = buf.Bytes()
bufLen := len(bcdBuf)

// Every nibble contains a digit, and the last low nibble contains a
Expand All @@ -204,10 +211,6 @@ func decodeStringFromBCD(bcdBuf []byte) (string, error) {

// Reserve bytes for dot and sign.
numLen := ndigits + 2
// Reserve bytes for zeroes.
if scale >= ndigits {
numLen += scale - ndigits
}

var bld strings.Builder
bld.Grow(numLen)
Expand All @@ -219,26 +222,10 @@ func decodeStringFromBCD(bcdBuf []byte) (string, error) {
bld.WriteByte('-')
}

// Add missing zeroes to the left side when scale is bigger than a
// number of digits and a single missed zero to the right side when
// equal.
if scale > ndigits {
bld.WriteByte('0')
bld.WriteByte('.')
for diff := scale - ndigits; diff > 0; diff-- {
bld.WriteByte('0')
}
} else if scale == ndigits {
bld.WriteByte('0')
}

const MaxDigit = 0x09
// Builds a buffer with symbols of decimal number (digits, dot and sign).
processNibble := func(nibble byte) {
if nibble <= MaxDigit {
if ndigits == scale {
bld.WriteByte('.')
}
bld.WriteByte(nibble + '0')
ndigits--
}
Expand All @@ -254,5 +241,8 @@ func decodeStringFromBCD(bcdBuf []byte) (string, error) {
processNibble(lowNibble)
}

return bld.String(), nil
if bld.Len() == 0 || isNegative[sign] && bld.Len() == 1 {
bld.WriteByte('0')
}
return bld.String(), -1 * scale, nil
}
8 changes: 6 additions & 2 deletions decimal/decimal.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,19 @@ func (decNum *Decimal) UnmarshalMsgpack(b []byte) error {
// +--------+-------------------+------------+===============+
// | MP_EXT | length (optional) | MP_DECIMAL | PackedDecimal |
// +--------+-------------------+------------+===============+
digits, err := decodeStringFromBCD(b)
digits, exp, err := decodeStringFromBCD(b)
if err != nil {
return fmt.Errorf("msgpack: can't decode string from BCD buffer (%x): %w", b, err)
}

dec, err := decimal.NewFromString(digits)
*decNum = *NewDecimal(dec)
if err != nil {
return fmt.Errorf("msgpack: can't encode string (%s) to a decimal number: %w", digits, err)
}

if exp != 0 {
dec = dec.Shift(int32(exp))
}
*decNum = *NewDecimal(dec)
return nil
}
51 changes: 49 additions & 2 deletions decimal/decimal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ var isDecimalSupported = false

var server = "127.0.0.1:3013"
var opts = Opts{
Timeout: 500 * time.Millisecond,
Timeout: 5 * time.Second,
User: "test",
Pass: "test",
}
Expand Down Expand Up @@ -112,6 +112,18 @@ var correctnessSamples = []struct {
{"-1234567891234567890.0987654321987654321", "c7150113012345678912345678900987654321987654321d", false},
}

var correctnessDecodeSamples = []struct {
numString string
mpBuf string
fixExt bool
}{
{"1e2", "d501fe1c", true},
{"1e33", "c70301d0df1c", false},
{"1.1e31", "c70301e2011c", false},
{"13e-2", "c7030102013c", false},
{"-1e3", "d501fd1d", true},
}

// There is a difference between encoding result from a raw string and from
// decimal.Decimal. It's expected because decimal.Decimal simplifies decimals:
// 0.00010000 -> 0.0001
Expand Down Expand Up @@ -384,17 +396,21 @@ func TestEncodeStringToBCD(t *testing.T) {

func TestDecodeStringFromBCD(t *testing.T) {
samples := append(correctnessSamples, rawSamples...)
samples = append(samples, correctnessDecodeSamples...)
samples = append(samples, benchmarkSamples...)
for _, testcase := range samples {
t.Run(testcase.numString, func(t *testing.T) {
b, _ := hex.DecodeString(testcase.mpBuf)
bcdBuf := trimMPHeader(b, testcase.fixExt)
s, err := DecodeStringFromBCD(bcdBuf)
s, exp, err := DecodeStringFromBCD(bcdBuf)
if err != nil {
t.Fatalf("Failed to decode BCD '%x' to decimal: %s", bcdBuf, err)
}

decActual, err := decimal.NewFromString(s)
if exp != 0 {
decActual = decActual.Shift(int32(exp))
}
if err != nil {
t.Fatalf("Failed to encode string ('%s') to decimal", s)
}
Expand Down Expand Up @@ -525,6 +541,37 @@ func TestSelect(t *testing.T) {
tupleValueIsDecimal(t, resp.Data, number)
}

func TestUnmarshal_from_decimal_new(t *testing.T) {
skipIfDecimalUnsupported(t)

conn := test_helpers.ConnectWithValidation(t, server, opts)
defer conn.Close()

samples := correctnessSamples
samples = append(samples, correctnessDecodeSamples...)
samples = append(samples, benchmarkSamples...)
for _, testcase := range samples {
str := testcase.numString
t.Run(str, func(t *testing.T) {
number, err := decimal.NewFromString(str)
if err != nil {
t.Fatalf("Failed to prepare test decimal: %s", err)
}

call := NewEvalRequest("return require('decimal').new(...)").
Args([]interface{}{str})
resp, err := conn.Do(call).Get()
if err != nil {
t.Fatalf("Decimal create failed: %s", err)
}
if resp == nil {
t.Fatalf("Response is nil after Call")
}
tupleValueIsDecimal(t, []interface{}{resp.Data}, number)
})
}
}

func assertInsert(t *testing.T, conn *Connection, numString string) {
number, err := decimal.NewFromString(numString)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion decimal/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ func EncodeStringToBCD(buf string) ([]byte, error) {
return encodeStringToBCD(buf)
}

func DecodeStringFromBCD(bcdBuf []byte) (string, error) {
func DecodeStringFromBCD(bcdBuf []byte) (string, int, error) {
return decodeStringFromBCD(bcdBuf)
}

Expand Down
11 changes: 7 additions & 4 deletions decimal/fuzzing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ import (
. "github.com/tarantool/go-tarantool/decimal"
)

func strToDecimal(t *testing.T, buf string) decimal.Decimal {
func strToDecimal(t *testing.T, buf string, exp int) decimal.Decimal {
decNum, err := decimal.NewFromString(buf)
if err != nil {
t.Fatal(err)
}
if exp != 0 {
decNum = decNum.Shift(int32(exp))
}
return decNum
}

Expand All @@ -33,13 +36,13 @@ func FuzzEncodeDecodeBCD(f *testing.F) {
if err != nil {
t.Skip("Only correct requests are interesting: %w", err)
}
var dec string
dec, err = DecodeStringFromBCD(bcdBuf)

dec, exp, err := DecodeStringFromBCD(bcdBuf)
if err != nil {
t.Fatalf("Failed to decode encoded value ('%s')", orig)
}

if !strToDecimal(t, dec).Equal(strToDecimal(t, orig)) {
if !strToDecimal(t, dec, exp).Equal(strToDecimal(t, orig, 0)) {
t.Fatal("Decimal numbers are not equal")
}
})
Expand Down
Loading
Loading