diff --git a/CHANGELOG.md b/CHANGELOG.md index 8500568ef..609e71380 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,8 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release. constants for `protocol` (#337) - Change `crud` operations `Timeout` option type to `crud.OptFloat64` instead of `crud.OptUint` (#342) +- Change all `Upsert` and `Update` requests to accept `*tarantool.Operations` + as `ops` parameters instead of `interface{}` (#348) ### Deprecated @@ -80,6 +82,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release. `prefer_replica`, `balance`) setup for crud.GetRequest (#335) - Tests with crud 1.4.0 (#336) - Tests with case sensitive SQL (#341) +- Splice update operation accepts 3 arguments instead of 5 (#348) ## [1.12.0] - 2023-06-07 diff --git a/README.md b/README.md index f8483dc34..a0a470a86 100644 --- a/README.md +++ b/README.md @@ -229,9 +229,17 @@ of the requests is an array instead of array of arrays. IPROTO constants have been moved to a separate package [go-iproto](https://github.com/tarantool/go-iproto). -#### Request interface +#### Request changes * The method `Code() uint32` replaced by the `Type() iproto.Type`. +* `Op` struct for update operations made private. +* Removed `OpSplice` struct. +* `Operations.Splice` method now accepts 5 arguments instead of 3. +* Requests `Update`, `UpdateAsync`, `UpdateTyped`, `Upsert`, `UpsertAsync` no +longer accept `ops` argument (operations) as an `interface{}`. `*Operations` +needs to be passed instead. +* `UpdateRequest` and `UpsertRequest` structs no longer accept `interface{}` +for an `ops` field. `*Operations` needs to be used instead. #### Connect function diff --git a/client_tools.go b/client_tools.go index 159f3d0ce..351b07cae 100644 --- a/client_tools.go +++ b/client_tools.go @@ -54,17 +54,43 @@ func (k IntIntKey) EncodeMsgpack(enc *msgpack.Encoder) error { return nil } -// Op - is update operation. -type Op struct { +// operation - is update operation. +type operation struct { Op string Field int Arg interface{} + // Pos, Len, Replace fields used in the Splice operation. + Pos int + Len int + Replace string } -func (o Op) EncodeMsgpack(enc *msgpack.Encoder) error { - enc.EncodeArrayLen(3) - enc.EncodeString(o.Op) - enc.EncodeInt(int64(o.Field)) +func (o operation) EncodeMsgpack(enc *msgpack.Encoder) error { + isSpliceOperation := o.Op == spliceOperator + argsLen := 3 + if isSpliceOperation { + argsLen = 5 + } + if err := enc.EncodeArrayLen(argsLen); err != nil { + return err + } + if err := enc.EncodeString(o.Op); err != nil { + return err + } + if err := enc.EncodeInt(int64(o.Field)); err != nil { + return err + } + + if isSpliceOperation { + if err := enc.EncodeInt(int64(o.Pos)); err != nil { + return err + } + if err := enc.EncodeInt(int64(o.Len)); err != nil { + return err + } + return enc.EncodeString(o.Replace) + } + return enc.Encode(o.Arg) } @@ -82,17 +108,26 @@ const ( // Operations is a collection of update operations. type Operations struct { - ops []Op + ops []operation +} + +// EncodeMsgpack encodes Operations as an array of operations. +func (ops *Operations) EncodeMsgpack(enc *msgpack.Encoder) error { + return enc.Encode(ops.ops) } // NewOperations returns a new empty collection of update operations. func NewOperations() *Operations { - ops := new(Operations) - return ops + return &Operations{[]operation{}} } func (ops *Operations) append(op string, field int, arg interface{}) *Operations { - ops.ops = append(ops.ops, Op{op, field, arg}) + ops.ops = append(ops.ops, operation{Op: op, Field: field, Arg: arg}) + return ops +} + +func (ops *Operations) appendSplice(op string, field, pos, len int, replace string) *Operations { + ops.ops = append(ops.ops, operation{Op: op, Field: field, Pos: pos, Len: len, Replace: replace}) return ops } @@ -122,8 +157,8 @@ func (ops *Operations) BitwiseXor(field int, arg interface{}) *Operations { } // Splice adds a splice operation to the collection of update operations. -func (ops *Operations) Splice(field int, arg interface{}) *Operations { - return ops.append(spliceOperator, field, arg) +func (ops *Operations) Splice(field, pos, len int, replace string) *Operations { + return ops.appendSplice(spliceOperator, field, pos, len, replace) } // Insert adds an insert operation to the collection of update operations. @@ -140,21 +175,3 @@ func (ops *Operations) Delete(field int, arg interface{}) *Operations { func (ops *Operations) Assign(field int, arg interface{}) *Operations { return ops.append(assignOperator, field, arg) } - -type OpSplice struct { - Op string - Field int - Pos int - Len int - Replace string -} - -func (o OpSplice) EncodeMsgpack(enc *msgpack.Encoder) error { - enc.EncodeArrayLen(5) - enc.EncodeString(o.Op) - enc.EncodeInt(int64(o.Field)) - enc.EncodeInt(int64(o.Pos)) - enc.EncodeInt(int64(o.Len)) - enc.EncodeString(o.Replace) - return nil -} diff --git a/client_tools_test.go b/client_tools_test.go new file mode 100644 index 000000000..fdd109152 --- /dev/null +++ b/client_tools_test.go @@ -0,0 +1,51 @@ +package tarantool_test + +import ( + "bytes" + "testing" + + "github.com/vmihailenco/msgpack/v5" + + "github.com/tarantool/go-tarantool/v2" +) + +func TestOperations_EncodeMsgpack(t *testing.T) { + ops := tarantool.NewOperations(). + Add(1, 2). + Subtract(1, 2). + BitwiseAnd(1, 2). + BitwiseOr(1, 2). + BitwiseXor(1, 2). + Splice(1, 2, 3, "a"). + Insert(1, 2). + Delete(1, 2). + Assign(1, 2) + refOps := []interface{}{ + []interface{}{"+", 1, 2}, + []interface{}{"-", 1, 2}, + []interface{}{"&", 1, 2}, + []interface{}{"|", 1, 2}, + []interface{}{"^", 1, 2}, + []interface{}{":", 1, 2, 3, "a"}, + []interface{}{"!", 1, 2}, + []interface{}{"#", 1, 2}, + []interface{}{"=", 1, 2}, + } + + var refBuf bytes.Buffer + encRef := msgpack.NewEncoder(&refBuf) + if err := encRef.Encode(refOps); err != nil { + t.Errorf("error while encoding: %v", err.Error()) + } + + var buf bytes.Buffer + enc := msgpack.NewEncoder(&buf) + + if err := enc.Encode(ops); err != nil { + t.Errorf("error while encoding: %v", err.Error()) + } + if !bytes.Equal(refBuf.Bytes(), buf.Bytes()) { + t.Errorf("encode response is wrong:\n expected %v\n got: %v", + refBuf, buf.Bytes()) + } +} diff --git a/connector.go b/connector.go index 5d4b5be39..9536116d7 100644 --- a/connector.go +++ b/connector.go @@ -29,10 +29,10 @@ type Connector interface { Delete(space, index interface{}, key interface{}) (*Response, error) // Deprecated: the method will be removed in the next major version, // use a UpdateRequest object + Do() instead. - Update(space, index interface{}, key, ops interface{}) (*Response, error) + Update(space, index interface{}, key interface{}, ops *Operations) (*Response, error) // Deprecated: the method will be removed in the next major version, // use a UpsertRequest object + Do() instead. - Upsert(space interface{}, tuple, ops interface{}) (*Response, error) + Upsert(space interface{}, tuple interface{}, ops *Operations) (*Response, error) // Deprecated: the method will be removed in the next major version, // use a CallRequest object + Do() instead. Call(functionName string, args interface{}) (*Response, error) @@ -67,7 +67,8 @@ type Connector interface { DeleteTyped(space, index interface{}, key interface{}, result interface{}) error // Deprecated: the method will be removed in the next major version, // use a UpdateRequest object + Do() instead. - UpdateTyped(space, index interface{}, key, ops interface{}, result interface{}) error + UpdateTyped(space, index interface{}, key interface{}, ops *Operations, + result interface{}) error // Deprecated: the method will be removed in the next major version, // use a CallRequest object + Do() instead. CallTyped(functionName string, args interface{}, result interface{}) error @@ -100,10 +101,10 @@ type Connector interface { DeleteAsync(space, index interface{}, key interface{}) *Future // Deprecated: the method will be removed in the next major version, // use a UpdateRequest object + Do() instead. - UpdateAsync(space, index interface{}, key, ops interface{}) *Future + UpdateAsync(space, index interface{}, key interface{}, ops *Operations) *Future // Deprecated: the method will be removed in the next major version, // use a UpsertRequest object + Do() instead. - UpsertAsync(space interface{}, tuple interface{}, ops interface{}) *Future + UpsertAsync(space interface{}, tuple interface{}, ops *Operations) *Future // Deprecated: the method will be removed in the next major version, // use a CallRequest object + Do() instead. CallAsync(functionName string, args interface{}) *Future diff --git a/crud/operations.go b/crud/operations.go index 5d55acc15..84953d590 100644 --- a/crud/operations.go +++ b/crud/operations.go @@ -1,5 +1,9 @@ package crud +import ( + "github.com/vmihailenco/msgpack/v5" +) + const ( // Add - operator for addition. Add Operator = "+" @@ -23,11 +27,44 @@ const ( // Operation describes CRUD operation as a table // {operator, field_identifier, value}. +// Splice operation described as a table +// {operator, field_identifier, position, length, replace_string}. type Operation struct { - // Instruct msgpack to pack this struct as array, so no custom packer - // is needed. - _msgpack struct{} `msgpack:",asArray"` //nolint: structcheck,unused Operator Operator Field interface{} // Number or string. Value interface{} + // Pos, Len, Replace fields used in the Splice operation. + Pos int + Len int + Replace string +} + +// EncodeMsgpack encodes Operation. +func (o Operation) EncodeMsgpack(enc *msgpack.Encoder) error { + isSpliceOperation := o.Operator == Splice + argsLen := 3 + if isSpliceOperation { + argsLen = 5 + } + if err := enc.EncodeArrayLen(argsLen); err != nil { + return err + } + if err := enc.EncodeString(string(o.Operator)); err != nil { + return err + } + if err := enc.Encode(o.Field); err != nil { + return err + } + + if isSpliceOperation { + if err := enc.EncodeInt(int64(o.Pos)); err != nil { + return err + } + if err := enc.EncodeInt(int64(o.Len)); err != nil { + return err + } + return enc.EncodeString(o.Replace) + } + + return enc.Encode(o.Value) } diff --git a/crud/operations_test.go b/crud/operations_test.go new file mode 100644 index 000000000..0ff3e818a --- /dev/null +++ b/crud/operations_test.go @@ -0,0 +1,123 @@ +package crud_test + +import ( + "bytes" + "testing" + + "github.com/vmihailenco/msgpack/v5" + + "github.com/tarantool/go-tarantool/v2/crud" +) + +func TestOperation_EncodeMsgpack(t *testing.T) { + testCases := []struct { + name string + op crud.Operation + ref []interface{} + }{ + { + "Add", + crud.Operation{ + Operator: crud.Add, + Field: 1, + Value: 2, + }, + []interface{}{"+", 1, 2}, + }, + { + "Sub", + crud.Operation{ + Operator: crud.Sub, + Field: 1, + Value: 2, + }, + []interface{}{"-", 1, 2}, + }, + { + "And", + crud.Operation{ + Operator: crud.And, + Field: 1, + Value: 2, + }, + []interface{}{"&", 1, 2}, + }, + { + "Or", + crud.Operation{ + Operator: crud.Or, + Field: 1, + Value: 2, + }, + []interface{}{"|", 1, 2}, + }, + { + "Xor", + crud.Operation{ + Operator: crud.Xor, + Field: 1, + Value: 2, + }, + []interface{}{"^", 1, 2}, + }, + { + "Splice", + crud.Operation{ + Operator: crud.Splice, + Field: 1, + Pos: 2, + Len: 3, + Replace: "a", + }, + []interface{}{":", 1, 2, 3, "a"}, + }, + { + "Insert", + crud.Operation{ + Operator: crud.Insert, + Field: 1, + Value: 2, + }, + []interface{}{"!", 1, 2}, + }, + { + "Delete", + crud.Operation{ + Operator: crud.Delete, + Field: 1, + Value: 2, + }, + []interface{}{"#", 1, 2}, + }, + { + "Assign", + crud.Operation{ + Operator: crud.Assign, + Field: 1, + Value: 2, + }, + []interface{}{"=", 1, 2}, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + var refBuf bytes.Buffer + encRef := msgpack.NewEncoder(&refBuf) + if err := encRef.Encode(test.ref); err != nil { + t.Errorf("error while encoding: %v", err.Error()) + } + + var buf bytes.Buffer + enc := msgpack.NewEncoder(&buf) + + if err := enc.Encode(test.op); err != nil { + t.Errorf("error while encoding: %v", err.Error()) + } + if !bytes.Equal(refBuf.Bytes(), buf.Bytes()) { + t.Errorf("encode response is wrong:\n expected %v\n got: %v", + refBuf, buf.Bytes()) + } + }) + } +} diff --git a/crud/tarantool_test.go b/crud/tarantool_test.go index 399b0d48f..f7a1e3a23 100644 --- a/crud/tarantool_test.go +++ b/crud/tarantool_test.go @@ -40,6 +40,63 @@ var startOpts test_helpers.StartOpts = test_helpers.StartOpts{ var timeout = float64(1.1) var operations = []crud.Operation{ + // Insert new fields, + // because double update of the same field results in an error. + { + Operator: crud.Insert, + Field: 4, + Value: 0, + }, + { + Operator: crud.Insert, + Field: 5, + Value: 0, + }, + { + Operator: crud.Insert, + Field: 6, + Value: 0, + }, + { + Operator: crud.Insert, + Field: 7, + Value: 0, + }, + { + Operator: crud.Insert, + Field: 8, + Value: 0, + }, + { + Operator: crud.Add, + Field: 4, + Value: 1, + }, + { + Operator: crud.Sub, + Field: 5, + Value: 1, + }, + { + Operator: crud.And, + Field: 6, + Value: 1, + }, + { + Operator: crud.Or, + Field: 7, + Value: 1, + }, + { + Operator: crud.Xor, + Field: 8, + Value: 1, + }, + { + Operator: crud.Delete, + Field: 4, + Value: 5, + }, { Operator: crud.Assign, Field: "name", @@ -532,6 +589,81 @@ func TestCrudProcessData(t *testing.T) { } } +func TestCrudUpdateSplice(t *testing.T) { + test_helpers.SkipIfCrudSpliceBroken(t) + + conn := connect(t) + defer conn.Close() + + req := crud.MakeUpdateRequest(spaceName). + Key(key). + Operations([]crud.Operation{ + { + Operator: crud.Splice, + Field: "name", + Pos: 1, + Len: 1, + Replace: "!!", + }, + }). + Opts(simpleOperationOpts) + + testCrudRequestPrepareData(t, conn) + resp, err := conn.Do(req).Get() + testCrudRequestCheck(t, req, resp, + err, 2) +} + +func TestCrudUpsertSplice(t *testing.T) { + test_helpers.SkipIfCrudSpliceBroken(t) + + conn := connect(t) + defer conn.Close() + + req := crud.MakeUpsertRequest(spaceName). + Tuple(tuple). + Operations([]crud.Operation{ + { + Operator: crud.Splice, + Field: "name", + Pos: 1, + Len: 1, + Replace: "!!", + }, + }). + Opts(simpleOperationOpts) + + testCrudRequestPrepareData(t, conn) + resp, err := conn.Do(req).Get() + testCrudRequestCheck(t, req, resp, + err, 2) +} + +func TestCrudUpsertObjectSplice(t *testing.T) { + test_helpers.SkipIfCrudSpliceBroken(t) + + conn := connect(t) + defer conn.Close() + + req := crud.MakeUpsertObjectRequest(spaceName). + Object(object). + Operations([]crud.Operation{ + { + Operator: crud.Splice, + Field: "name", + Pos: 1, + Len: 1, + Replace: "!!", + }, + }). + Opts(simpleOperationOpts) + + testCrudRequestPrepareData(t, conn) + resp, err := conn.Do(req).Get() + testCrudRequestCheck(t, req, resp, + err, 2) +} + func TestUnflattenRows_IncorrectParams(t *testing.T) { invalidMetadata := []interface{}{ map[interface{}]interface{}{ diff --git a/example_test.go b/example_test.go index 9b66ea29b..7e689bcf3 100644 --- a/example_test.go +++ b/example_test.go @@ -439,34 +439,29 @@ func ExampleUpdateRequest() { for i := 1111; i <= 1112; i++ { conn.Do(tarantool.NewReplaceRequest(spaceNo). - Tuple([]interface{}{uint(i), "hello", "world"}), + Tuple([]interface{}{uint(i), "text", 1, 1, 1, 1, 1}), ).Get() } req := tarantool.NewUpdateRequest(617). Key(tarantool.IntKey{1111}). - Operations(tarantool.NewOperations().Assign(1, "bye")) + Operations(tarantool.NewOperations(). + Add(2, 1). + Subtract(3, 1). + BitwiseAnd(4, 1). + BitwiseOr(5, 1). + BitwiseXor(6, 1). + Splice(1, 1, 2, "!!"). + Insert(7, "new"). + Assign(7, "updated")) resp, err := conn.Do(req).Get() if err != nil { fmt.Printf("error in do update request is %v", err) return } fmt.Printf("response is %#v\n", resp.Data) - - req = tarantool.NewUpdateRequest("test"). - Index("primary"). - Key(tarantool.IntKey{1111}). - Operations(tarantool.NewOperations().Assign(1, "hello")) - fut := conn.Do(req) - resp, err = fut.Get() - if err != nil { - fmt.Printf("error in do async update request is %v", err) - return - } - fmt.Printf("response is %#v\n", resp.Data) // Output: - // response is []interface {}{[]interface {}{0x457, "bye", "world"}} - // response is []interface {}{[]interface {}{0x457, "hello", "world"}} + // response is []interface {}{[]interface {}{0x457, "t!!t", 2, 0, 1, 1, 0, "updated"}} } func ExampleUpdateRequest_spaceAndIndexNames() { diff --git a/export_test.go b/export_test.go index a52ef5b26..2c28472b9 100644 --- a/export_test.go +++ b/export_test.go @@ -78,7 +78,7 @@ func RefImplDeleteBody(enc *msgpack.Encoder, res SchemaResolver, space, index, // RefImplUpdateBody is reference implementation for filling of an update // request's body. func RefImplUpdateBody(enc *msgpack.Encoder, res SchemaResolver, space, index, - key, ops interface{}) error { + key interface{}, ops *Operations) error { spaceEnc, err := newSpaceEncoder(res, space) if err != nil { return err @@ -93,7 +93,7 @@ func RefImplUpdateBody(enc *msgpack.Encoder, res SchemaResolver, space, index, // RefImplUpsertBody is reference implementation for filling of an upsert // request's body. func RefImplUpsertBody(enc *msgpack.Encoder, res SchemaResolver, space, - tuple, ops interface{}) error { + tuple interface{}, ops *Operations) error { spaceEnc, err := newSpaceEncoder(res, space) if err != nil { return err diff --git a/pool/connection_pool.go b/pool/connection_pool.go index aa84d8c24..6186683b3 100644 --- a/pool/connection_pool.go +++ b/pool/connection_pool.go @@ -462,8 +462,8 @@ func (p *ConnectionPool) Delete(space, index interface{}, key interface{}, // // Deprecated: the method will be removed in the next major version, // use a UpdateRequest object + Do() instead. -func (p *ConnectionPool) Update(space, index interface{}, key, ops interface{}, - userMode ...Mode) (*tarantool.Response, error) { +func (p *ConnectionPool) Update(space, index interface{}, key interface{}, + ops *tarantool.Operations, userMode ...Mode) (*tarantool.Response, error) { conn, err := p.getConnByMode(RW, userMode) if err != nil { return nil, err @@ -477,8 +477,8 @@ func (p *ConnectionPool) Update(space, index interface{}, key, ops interface{}, // // Deprecated: the method will be removed in the next major version, // use a UpsertRequest object + Do() instead. -func (p *ConnectionPool) Upsert(space interface{}, tuple, ops interface{}, - userMode ...Mode) (*tarantool.Response, error) { +func (p *ConnectionPool) Upsert(space interface{}, tuple interface{}, + ops *tarantool.Operations, userMode ...Mode) (*tarantool.Response, error) { conn, err := p.getConnByMode(RW, userMode) if err != nil { return nil, err @@ -639,8 +639,8 @@ func (p *ConnectionPool) DeleteTyped(space, index interface{}, key interface{}, // // Deprecated: the method will be removed in the next major version, // use a UpdateRequest object + Do() instead. -func (p *ConnectionPool) UpdateTyped(space, index interface{}, key, ops interface{}, - result interface{}, userMode ...Mode) error { +func (p *ConnectionPool) UpdateTyped(space, index interface{}, key interface{}, + ops *tarantool.Operations, result interface{}, userMode ...Mode) error { conn, err := p.getConnByMode(RW, userMode) if err != nil { return err @@ -788,8 +788,8 @@ func (p *ConnectionPool) DeleteAsync(space, index interface{}, key interface{}, // // Deprecated: the method will be removed in the next major version, // use a UpdateRequest object + Do() instead. -func (p *ConnectionPool) UpdateAsync(space, index interface{}, key, ops interface{}, - userMode ...Mode) *tarantool.Future { +func (p *ConnectionPool) UpdateAsync(space, index interface{}, key interface{}, + ops *tarantool.Operations, userMode ...Mode) *tarantool.Future { conn, err := p.getConnByMode(RW, userMode) if err != nil { return newErrorFuture(err) @@ -803,8 +803,8 @@ func (p *ConnectionPool) UpdateAsync(space, index interface{}, key, ops interfac // // Deprecated: the method will be removed in the next major version, // use a UpsertRequest object + Do() instead. -func (p *ConnectionPool) UpsertAsync(space interface{}, tuple interface{}, ops interface{}, - userMode ...Mode) *tarantool.Future { +func (p *ConnectionPool) UpsertAsync(space interface{}, tuple interface{}, + ops *tarantool.Operations, userMode ...Mode) *tarantool.Future { conn, err := p.getConnByMode(RW, userMode) if err != nil { return newErrorFuture(err) diff --git a/pool/connection_pool_test.go b/pool/connection_pool_test.go index 27310be23..8b19e671b 100644 --- a/pool/connection_pool_test.go +++ b/pool/connection_pool_test.go @@ -1964,7 +1964,7 @@ func TestUpsert(t *testing.T) { // Mode is `RW` by default, we have only one RW instance (servers[2]) resp, err := connPool.Upsert(spaceName, []interface{}{"upsert_key", "upsert_value"}, - []interface{}{[]interface{}{"=", 1, "new_value"}}) + tarantool.NewOperations().Assign(1, "new_value")) require.Nilf(t, err, "failed to Upsert") require.NotNilf(t, resp, "response is nil after Upsert") @@ -1993,7 +1993,7 @@ func TestUpsert(t *testing.T) { // PreferRW resp, err = connPool.Upsert( spaceName, []interface{}{"upsert_key", "upsert_value"}, - []interface{}{[]interface{}{"=", 1, "new_value"}}, pool.PreferRW) + tarantool.NewOperations().Assign(1, "new_value"), pool.PreferRW) require.Nilf(t, err, "failed to Upsert") require.NotNilf(t, resp, "response is nil after Upsert") @@ -2056,7 +2056,7 @@ func TestUpdate(t *testing.T) { // Mode is `RW` by default, we have only one RW instance (servers[2]) resp, err = connPool.Update(spaceName, indexNo, - []interface{}{"update_key"}, []interface{}{[]interface{}{"=", 1, "new_value"}}) + []interface{}{"update_key"}, tarantool.NewOperations().Assign(1, "new_value")) require.Nilf(t, err, "failed to Update") require.NotNilf(t, resp, "response is nil after Update") @@ -2085,7 +2085,7 @@ func TestUpdate(t *testing.T) { // PreferRW resp, err = connPool.Update( spaceName, indexNo, []interface{}{"update_key"}, - []interface{}{[]interface{}{"=", 1, "another_value"}}, pool.PreferRW) + tarantool.NewOperations().Assign(1, "another_value"), pool.PreferRW) require.Nilf(t, err, "failed to Update") require.NotNilf(t, resp, "response is nil after Update") diff --git a/pool/connector.go b/pool/connector.go index acb9a9187..604e3921f 100644 --- a/pool/connector.go +++ b/pool/connector.go @@ -106,7 +106,7 @@ func (c *ConnectorAdapter) Delete(space, index interface{}, // Deprecated: the method will be removed in the next major version, // use a UpdateRequest object + Do() instead. func (c *ConnectorAdapter) Update(space, index interface{}, - key, ops interface{}) (*tarantool.Response, error) { + key interface{}, ops *tarantool.Operations) (*tarantool.Response, error) { return c.pool.Update(space, index, key, ops, c.mode) } @@ -114,8 +114,8 @@ func (c *ConnectorAdapter) Update(space, index interface{}, // // Deprecated: the method will be removed in the next major version, // use a UpsertRequest object + Do() instead. -func (c *ConnectorAdapter) Upsert(space interface{}, - tuple, ops interface{}) (*tarantool.Response, error) { +func (c *ConnectorAdapter) Upsert(space, tuple interface{}, + ops *tarantool.Operations) (*tarantool.Response, error) { return c.pool.Upsert(space, tuple, ops, c.mode) } @@ -220,7 +220,7 @@ func (c *ConnectorAdapter) DeleteTyped(space, index interface{}, // Deprecated: the method will be removed in the next major version, // use a UpdateRequest object + Do() instead. func (c *ConnectorAdapter) UpdateTyped(space, index interface{}, - key, ops interface{}, result interface{}) error { + key interface{}, ops *tarantool.Operations, result interface{}) error { return c.pool.UpdateTyped(space, index, key, ops, result, c.mode) } @@ -314,7 +314,7 @@ func (c *ConnectorAdapter) DeleteAsync(space, index interface{}, // Deprecated: the method will be removed in the next major version, // use a UpdateRequest object + Do() instead. func (c *ConnectorAdapter) UpdateAsync(space, index interface{}, - key, ops interface{}) *tarantool.Future { + key interface{}, ops *tarantool.Operations) *tarantool.Future { return c.pool.UpdateAsync(space, index, key, ops, c.mode) } @@ -322,8 +322,8 @@ func (c *ConnectorAdapter) UpdateAsync(space, index interface{}, // // Deprecated: the method will be removed in the next major version, // use a UpsertRequest object + Do() instead. -func (c *ConnectorAdapter) UpsertAsync(space interface{}, tuple interface{}, - ops interface{}) *tarantool.Future { +func (c *ConnectorAdapter) UpsertAsync(space, tuple interface{}, + ops *tarantool.Operations) *tarantool.Future { return c.pool.UpsertAsync(space, tuple, ops, c.mode) } diff --git a/pool/connector_test.go b/pool/connector_test.go index b667b0d34..fa107cc58 100644 --- a/pool/connector_test.go +++ b/pool/connector_test.go @@ -148,7 +148,7 @@ var reqIndex interface{} = []interface{}{2} var reqArgs interface{} = []interface{}{3} var reqTuple interface{} = []interface{}{4} var reqKey interface{} = []interface{}{5} -var reqOps interface{} = []interface{}{6} +var reqOps = tarantool.NewOperations() var reqResult interface{} = []interface{}{7} var reqSqlInfo = tarantool.SQLInfo{AffectedCount: 3} @@ -547,8 +547,8 @@ type updateMock struct { baseRequestMock } -func (m *updateMock) Update(space, index, key, ops interface{}, - mode ...Mode) (*tarantool.Response, error) { +func (m *updateMock) Update(space, index, key interface{}, + ops *tarantool.Operations, mode ...Mode) (*tarantool.Response, error) { m.called++ m.space = space m.index = index @@ -578,8 +578,8 @@ type updateTypedMock struct { baseRequestMock } -func (m *updateTypedMock) UpdateTyped(space, index, key, ops interface{}, - result interface{}, mode ...Mode) error { +func (m *updateTypedMock) UpdateTyped(space, index, key interface{}, + ops *tarantool.Operations, result interface{}, mode ...Mode) error { m.called++ m.space = space m.index = index @@ -610,8 +610,8 @@ type updateAsyncMock struct { baseRequestMock } -func (m *updateAsyncMock) UpdateAsync(space, index, key, ops interface{}, - mode ...Mode) *tarantool.Future { +func (m *updateAsyncMock) UpdateAsync(space, index, key interface{}, + ops *tarantool.Operations, mode ...Mode) *tarantool.Future { m.called++ m.space = space m.index = index @@ -640,7 +640,7 @@ type upsertMock struct { baseRequestMock } -func (m *upsertMock) Upsert(space, tuple, ops interface{}, +func (m *upsertMock) Upsert(space, tuple interface{}, ops *tarantool.Operations, mode ...Mode) (*tarantool.Response, error) { m.called++ m.space = space @@ -669,8 +669,8 @@ type upsertAsyncMock struct { baseRequestMock } -func (m *upsertAsyncMock) UpsertAsync(space, tuple, ops interface{}, - mode ...Mode) *tarantool.Future { +func (m *upsertAsyncMock) UpsertAsync(space, tuple interface{}, + ops *tarantool.Operations, mode ...Mode) *tarantool.Future { m.called++ m.space = space m.tuple = tuple diff --git a/pool/pooler.go b/pool/pooler.go index a588d67f4..0ff945bbb 100644 --- a/pool/pooler.go +++ b/pool/pooler.go @@ -38,11 +38,11 @@ type Pooler interface { mode ...Mode) (*tarantool.Response, error) // Deprecated: the method will be removed in the next major version, // use a UpdateRequest object + Do() instead. - Update(space, index interface{}, key, ops interface{}, + Update(space, index interface{}, key interface{}, ops *tarantool.Operations, mode ...Mode) (*tarantool.Response, error) // Deprecated: the method will be removed in the next major version, // use a UpsertRequest object + Do() instead. - Upsert(space interface{}, tuple, ops interface{}, + Upsert(space interface{}, tuple interface{}, ops *tarantool.Operations, mode ...Mode) (*tarantool.Response, error) // Deprecated: the method will be removed in the next major version, // use a CallRequest object + Do() instead. @@ -87,8 +87,8 @@ type Pooler interface { mode ...Mode) error // Deprecated: the method will be removed in the next major version, // use a UpdateRequest object + Do() instead. - UpdateTyped(space, index interface{}, key, ops interface{}, - result interface{}, mode ...Mode) error + UpdateTyped(space, index interface{}, key interface{}, + ops *tarantool.Operations, result interface{}, mode ...Mode) error // Deprecated: the method will be removed in the next major version, // use a CallRequest object + Do() instead. CallTyped(functionName string, args interface{}, result interface{}, @@ -128,11 +128,11 @@ type Pooler interface { mode ...Mode) *tarantool.Future // Deprecated: the method will be removed in the next major version, // use a UpdateRequest object + Do() instead. - UpdateAsync(space, index interface{}, key, ops interface{}, - mode ...Mode) *tarantool.Future + UpdateAsync(space, index interface{}, key interface{}, + ops *tarantool.Operations, mode ...Mode) *tarantool.Future // Deprecated: the method will be removed in the next major version, // use a UpsertRequest object + Do() instead. - UpsertAsync(space interface{}, tuple interface{}, ops interface{}, + UpsertAsync(space interface{}, tuple interface{}, ops *tarantool.Operations, mode ...Mode) *tarantool.Future // Deprecated: the method will be removed in the next major version, // use a CallRequest object + Do() instead. diff --git a/request.go b/request.go index c2e7cf96d..ac0f7e858 100644 --- a/request.go +++ b/request.go @@ -182,16 +182,20 @@ func fillSelect(enc *msgpack.Encoder, spaceEnc spaceEncoder, indexEnc indexEncod } func fillUpdate(enc *msgpack.Encoder, spaceEnc spaceEncoder, indexEnc indexEncoder, - key, ops interface{}) error { + key interface{}, ops *Operations) error { enc.EncodeMapLen(4) if err := fillSearch(enc, spaceEnc, indexEnc, key); err != nil { return err } enc.EncodeUint(uint64(iproto.IPROTO_TUPLE)) + if ops == nil { + return enc.Encode([]interface{}{}) + } return enc.Encode(ops) } -func fillUpsert(enc *msgpack.Encoder, spaceEnc spaceEncoder, tuple, ops interface{}) error { +func fillUpsert(enc *msgpack.Encoder, spaceEnc spaceEncoder, tuple interface{}, + ops *Operations) error { enc.EncodeMapLen(3) if err := spaceEnc.Encode(enc); err != nil { return err @@ -202,6 +206,9 @@ func fillUpsert(enc *msgpack.Encoder, spaceEnc spaceEncoder, tuple, ops interfac return err } enc.EncodeUint(uint64(iproto.IPROTO_OPS)) + if ops == nil { + return enc.Encode([]interface{}{}) + } return enc.Encode(ops) } @@ -308,7 +315,7 @@ func (conn *Connection) Delete(space, index interface{}, key interface{}) (*Resp // // Deprecated: the method will be removed in the next major version, // use a UpdateRequest object + Do() instead. -func (conn *Connection) Update(space, index interface{}, key, ops interface{}) (*Response, error) { +func (conn *Connection) Update(space, index, key interface{}, ops *Operations) (*Response, error) { return conn.UpdateAsync(space, index, key, ops).Get() } @@ -319,7 +326,7 @@ func (conn *Connection) Update(space, index interface{}, key, ops interface{}) ( // // Deprecated: the method will be removed in the next major version, // use a UpsertRequest object + Do() instead. -func (conn *Connection) Upsert(space interface{}, tuple, ops interface{}) (*Response, error) { +func (conn *Connection) Upsert(space, tuple interface{}, ops *Operations) (*Response, error) { return conn.UpsertAsync(space, tuple, ops).Get() } @@ -464,8 +471,8 @@ func (conn *Connection) DeleteTyped(space, index interface{}, key interface{}, // // Deprecated: the method will be removed in the next major version, // use a UpdateRequest object + Do() instead. -func (conn *Connection) UpdateTyped(space, index interface{}, key, ops interface{}, - result interface{}) error { +func (conn *Connection) UpdateTyped(space, index interface{}, key interface{}, + ops *Operations, result interface{}) error { return conn.UpdateAsync(space, index, key, ops).GetTyped(result) } @@ -580,7 +587,8 @@ func (conn *Connection) DeleteAsync(space, index interface{}, key interface{}) * // // Deprecated: the method will be removed in the next major version, // use a UpdateRequest object + Do() instead. -func (conn *Connection) UpdateAsync(space, index interface{}, key, ops interface{}) *Future { +func (conn *Connection) UpdateAsync(space, index interface{}, key interface{}, + ops *Operations) *Future { req := NewUpdateRequest(space).Index(index).Key(key) req.ops = ops return conn.Do(req) @@ -591,8 +599,7 @@ func (conn *Connection) UpdateAsync(space, index interface{}, key, ops interface // // Deprecated: the method will be removed in the next major version, // use a UpsertRequest object + Do() instead. -func (conn *Connection) UpsertAsync(space interface{}, tuple interface{}, - ops interface{}) *Future { +func (conn *Connection) UpsertAsync(space, tuple interface{}, ops *Operations) *Future { req := NewUpsertRequest(space).Tuple(tuple) req.ops = ops return conn.Do(req) @@ -1193,7 +1200,7 @@ func (req *DeleteRequest) Context(ctx context.Context) *DeleteRequest { type UpdateRequest struct { spaceIndexRequest key interface{} - ops interface{} + ops *Operations } // NewUpdateRequest returns a new empty UpdateRequest. @@ -1202,7 +1209,6 @@ func NewUpdateRequest(space interface{}) *UpdateRequest { req.rtype = iproto.IPROTO_UPDATE req.setSpace(space) req.key = []interface{}{} - req.ops = []interface{}{} return req } @@ -1223,9 +1229,7 @@ func (req *UpdateRequest) Key(key interface{}) *UpdateRequest { // Operations sets operations to be performed on update. // Note: default value is empty. func (req *UpdateRequest) Operations(ops *Operations) *UpdateRequest { - if ops != nil { - req.ops = ops.ops - } + req.ops = ops return req } @@ -1259,7 +1263,7 @@ func (req *UpdateRequest) Context(ctx context.Context) *UpdateRequest { type UpsertRequest struct { spaceRequest tuple interface{} - ops interface{} + ops *Operations } // NewUpsertRequest returns a new empty UpsertRequest. @@ -1268,7 +1272,6 @@ func NewUpsertRequest(space interface{}) *UpsertRequest { req.rtype = iproto.IPROTO_UPSERT req.setSpace(space) req.tuple = []interface{}{} - req.ops = []interface{}{} return req } @@ -1282,9 +1285,7 @@ func (req *UpsertRequest) Tuple(tuple interface{}) *UpsertRequest { // Operations sets operations to be performed on update case by the upsert request. // Note: default value is empty. func (req *UpsertRequest) Operations(ops *Operations) *UpsertRequest { - if ops != nil { - req.ops = ops.ops - } + req.ops = ops return req } diff --git a/request_test.go b/request_test.go index 7c2e5e514..580259702 100644 --- a/request_test.go +++ b/request_test.go @@ -118,19 +118,7 @@ func assertBodyEqual(t testing.TB, reference []byte, req Request) { } } -func getTestOps() ([]Op, *Operations) { - ops := []Op{ - {"+", 1, 2}, - {"-", 3, 4}, - {"&", 5, 6}, - {"|", 7, 8}, - {"^", 9, 1}, - {"^", 9, 1}, // The duplication is for test purposes. - {":", 2, 3}, - {"!", 4, 5}, - {"#", 6, 7}, - {"=", 8, 9}, - } +func getTestOps() *Operations { operations := NewOperations(). Add(1, 2). Subtract(3, 4). @@ -138,11 +126,11 @@ func getTestOps() ([]Op, *Operations) { BitwiseOr(7, 8). BitwiseXor(9, 1). BitwiseXor(9, 1). // The duplication is for test purposes. - Splice(2, 3). + Splice(2, 3, 1, "!!"). Insert(4, 5). Delete(6, 7). Assign(8, 9) - return ops, operations + return operations } func TestRequestsValidSpaceAndIndex(t *testing.T) { @@ -670,7 +658,7 @@ func TestUpdateRequestDefaultValues(t *testing.T) { refEnc := msgpack.NewEncoder(&refBuf) err := RefImplUpdateBody(refEnc, &resolver, validSpace, defaultIndex, - []interface{}{}, []Op{}) + []interface{}{}, nil) if err != nil { t.Fatalf("An unexpected RefImplUpdateBody() error: %q", err.Error()) } @@ -686,7 +674,7 @@ func TestUpdateRequestSpaceByName(t *testing.T) { refEnc := msgpack.NewEncoder(&refBuf) err := RefImplUpdateBody(refEnc, &resolver, "valid", defaultIndex, - []interface{}{}, []Op{}) + []interface{}{}, nil) if err != nil { t.Fatalf("An unexpected RefImplUpdateBody() error: %q", err.Error()) } @@ -702,7 +690,7 @@ func TestUpdateRequestIndexByName(t *testing.T) { refEnc := msgpack.NewEncoder(&refBuf) err := RefImplUpdateBody(refEnc, &resolver, defaultSpace, "valid", - []interface{}{}, []Op{}) + []interface{}{}, nil) if err != nil { t.Fatalf("An unexpected RefImplUpdateBody() error: %q", err.Error()) } @@ -714,11 +702,11 @@ func TestUpdateRequestIndexByName(t *testing.T) { func TestUpdateRequestSetters(t *testing.T) { key := []interface{}{uint(44)} - refOps, reqOps := getTestOps() + reqOps := getTestOps() var refBuf bytes.Buffer refEnc := msgpack.NewEncoder(&refBuf) - err := RefImplUpdateBody(refEnc, &resolver, validSpace, validIndex, key, refOps) + err := RefImplUpdateBody(refEnc, &resolver, validSpace, validIndex, key, reqOps) if err != nil { t.Fatalf("An unexpected RefImplUpdateBody() error: %q", err.Error()) } @@ -734,7 +722,7 @@ func TestUpsertRequestDefaultValues(t *testing.T) { var refBuf bytes.Buffer refEnc := msgpack.NewEncoder(&refBuf) - err := RefImplUpsertBody(refEnc, &resolver, validSpace, []interface{}{}, []Op{}) + err := RefImplUpsertBody(refEnc, &resolver, validSpace, []interface{}{}, nil) if err != nil { t.Fatalf("An unexpected RefImplUpsertBody() error: %q", err.Error()) } @@ -749,7 +737,7 @@ func TestUpsertRequestSpaceByName(t *testing.T) { resolver.nameUseSupported = true refEnc := msgpack.NewEncoder(&refBuf) - err := RefImplUpsertBody(refEnc, &resolver, "valid", []interface{}{}, []Op{}) + err := RefImplUpsertBody(refEnc, &resolver, "valid", []interface{}{}, nil) if err != nil { t.Fatalf("An unexpected RefImplUpsertBody() error: %q", err.Error()) } @@ -760,11 +748,11 @@ func TestUpsertRequestSpaceByName(t *testing.T) { func TestUpsertRequestSetters(t *testing.T) { tuple := []interface{}{uint(64)} - refOps, reqOps := getTestOps() + reqOps := getTestOps() var refBuf bytes.Buffer refEnc := msgpack.NewEncoder(&refBuf) - err := RefImplUpsertBody(refEnc, &resolver, validSpace, tuple, refOps) + err := RefImplUpsertBody(refEnc, &resolver, validSpace, tuple, reqOps) if err != nil { t.Fatalf("An unexpected RefImplUpsertBody() error: %q", err.Error()) } diff --git a/tarantool_test.go b/tarantool_test.go index 8be963474..f8da2bdb5 100644 --- a/tarantool_test.go +++ b/tarantool_test.go @@ -992,7 +992,7 @@ func TestClient(t *testing.T) { // Update resp, err = conn.Update(spaceNo, indexNo, []interface{}{uint(2)}, - []interface{}{[]interface{}{"=", 1, "bye"}, []interface{}{"#", 2, 1}}) + NewOperations().Assign(1, "bye").Delete(2, 1)) if err != nil { t.Fatalf("Failed to Update: %s", err.Error()) } @@ -1021,7 +1021,7 @@ func TestClient(t *testing.T) { // Upsert resp, err = conn.Upsert(spaceNo, []interface{}{uint(3), 1}, - []interface{}{[]interface{}{"+", 1, 1}}) + NewOperations().Add(1, 1)) if err != nil { t.Fatalf("Failed to Upsert (insert): %s", err.Error()) } @@ -1032,7 +1032,7 @@ func TestClient(t *testing.T) { t.Errorf("Response should not have a position") } resp, err = conn.Upsert(spaceNo, []interface{}{uint(3), 1}, - []interface{}{[]interface{}{"+", 1, 1}}) + NewOperations().Add(1, 1)) if err != nil { t.Fatalf("Failed to Upsert (update): %s", err.Error()) } @@ -2095,7 +2095,7 @@ func TestClientNamed(t *testing.T) { resp, err = conn.Update(spaceName, indexName, []interface{}{ uint(1002)}, - []interface{}{[]interface{}{"=", 1, "bye"}, []interface{}{"#", 2, 1}}) + NewOperations().Assign(1, "buy").Delete(2, 1)) if err != nil { t.Fatalf("Failed to Update: %s", err.Error()) } @@ -2105,7 +2105,7 @@ func TestClientNamed(t *testing.T) { // Upsert resp, err = conn.Upsert(spaceName, - []interface{}{uint(1003), 1}, []interface{}{[]interface{}{"+", 1, 1}}) + []interface{}{uint(1003), 1}, NewOperations().Add(1, 1)) if err != nil { t.Fatalf("Failed to Upsert (insert): %s", err.Error()) } @@ -2113,7 +2113,7 @@ func TestClientNamed(t *testing.T) { t.Errorf("Response is nil after Upsert (insert)") } resp, err = conn.Upsert(spaceName, - []interface{}{uint(1003), 1}, []interface{}{[]interface{}{"+", 1, 1}}) + []interface{}{uint(1003), 1}, NewOperations().Add(1, 1)) if err != nil { t.Fatalf("Failed to Upsert (update): %s", err.Error()) } diff --git a/test_helpers/utils.go b/test_helpers/utils.go index d2a941775..52c078c90 100644 --- a/test_helpers/utils.go +++ b/test_helpers/utils.go @@ -200,6 +200,15 @@ func SkipIfWatchOnceUnsupported(t *testing.T) { SkipIfFeatureUnsupported(t, "watch once", 3, 0, 0) } +// SkipIfCrudSpliceBroken skips test run if splice operation is broken +// on the crud side. +// https://github.com/tarantool/crud/issues/397 +func SkipIfCrudSpliceBroken(t *testing.T) { + t.Helper() + + SkipIfFeatureUnsupported(t, "crud update splice", 2, 0, 0) +} + // CheckEqualBoxErrors checks equivalence of tarantool.BoxError objects. // // Tarantool errors are not comparable by nature: