diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 34219a3b0..cba02c6ab 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -241,7 +241,7 @@ jobs: - macos-12 tarantool: - brew - - 1.10.14 + - 1.10.15 env: # Make sense only for non-brew jobs. diff --git a/CHANGELOG.md b/CHANGELOG.md index 36f5c08db..d0f696cc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,15 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release. in requests instead of their IDs. - `GetSchema` function to get the actual schema (#7) - Support connection via an existing socket fd (#321) +- `Header` struct for the response header (#237). It can be accessed via + `Header()` method of the `Response` interface. +- `Response` method added to the `Request` interface (#237) +- New `LogAppendPushFailed` connection log constant (#237). + It is logged when connection fails to append a push response. +- `ErrorNo` constant that indicates that no error has occurred while getting + the response (#237) +- Ability to mock connections for tests (#237). Added new types `MockDoer`, + `MockRequest` to `test_helpers`. ### Changed @@ -67,6 +76,23 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release. it (#321) - Rename `pool.GetPoolInfo` to `pool.GetInfo`. Change return type to `map[string]ConnectionInfo` (#321) +- `Response` is now an interface (#237) +- All responses are now implementations of the `Response` interface (#237). + `SelectResponse`, `ExecuteResponse`, `PrepareResponse`, `PushResponse` are part + of a public API. `Pos()`, `MetaData()`, `SQLInfo()` methods created for them + to get specific info. + Special types of responses are used with special requests. +- `IsPush()` method is added to the response iterator (#237). It returns + the information if the current response is a `PushResponse`. + `PushCode` constant is removed. +- Method `Get` for `Future` now returns response data (#237). To get the actual + response new `GetResponse` method has been added. Methods `AppendPush` and + `SetResponse` accept response `Header` and data as their arguments. +- `Future` constructors now accept `Request` as their argument (#237) +- Operations `Ping`, `Select`, `Insert`, `Replace`, `Delete`, `Update`, `Upsert`, + `Call`, `Call16`, `Call17`, `Eval`, `Execute` of a `Connector` and `Pooler` + return response data instead of an actual responses (#237) +- Renamed `StrangerResponse` to `MockResponse` (#237) ### Deprecated @@ -89,6 +115,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release. - IPROTO constants (#158) - Code() method from the Request interface (#158) - `Schema` field from the `Connection` struct (#7) +- `OkCode` and `PushCode` constants (#237) ### Fixed diff --git a/README.md b/README.md index b1d47ee84..5472e50a2 100644 --- a/README.md +++ b/README.md @@ -128,12 +128,13 @@ func main() { if err != nil { fmt.Println("Connection refused:", err) } - resp, err := conn.Do(tarantool.NewInsertRequest(999). + data, err := conn.Do(tarantool.NewInsertRequest(999). Tuple([]interface{}{99999, "BB"}), ).Get() if err != nil { fmt.Println("Error", err) - fmt.Println("Code", resp.Code) + } else { + fmt.Printf("Data: %v", data) } } ``` @@ -201,12 +202,19 @@ The subpackage has been deleted. You could use `pool` instead. unique string ID, which allows them to be distinguished. * `pool.GetPoolInfo` has been renamed to `pool.GetInfo`. Return type has been changed to `map[string]ConnectionInfo`. +* Operations `Ping`, `Select`, `Insert`, `Replace`, `Delete`, `Update`, `Upsert`, + `Call`, `Call16`, `Call17`, `Eval`, `Execute` of a `Pooler` return + response data instead of an actual responses. #### crud package * `crud` operations `Timeout` option has `crud.OptFloat64` type instead of `crud.OptUint`. +#### test_helpers package + +Renamed `StrangerResponse` to `MockResponse`. + #### msgpack.v5 Most function names and argument types in `msgpack.v5` and `msgpack.v2` @@ -241,7 +249,11 @@ of the requests is an array instead of array of arrays. #### IPROTO constants -IPROTO constants have been moved to a separate package [go-iproto](https://github.com/tarantool/go-iproto). +* IPROTO constants have been moved to a separate package [go-iproto](https://github.com/tarantool/go-iproto). +* `PushCode` constant is removed. To check whether the current response is + a push response, use `IsPush()` method of the response iterator instead. +* `ErrorNo` constant is added to indicate that no error has occurred while + getting the response. It should be used instead of the removed `OkCode`. #### Request changes @@ -254,6 +266,35 @@ 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. +* `Response` method added to the `Request` interface. + +#### Response changes + +* `Response` is now an interface. +* Response header stored in a new `Header` struct. It could be accessed via + `Header()` method. +* `ResponseIterator` interface now has `IsPush()` method. + It returns true if the current response is a push response. +* For each request type, a different response type is created. They all + implement a `Response` interface. `SelectResponse`, `PrepareResponse`, + `ExecuteResponse`, `PushResponse` are a part of a public API. + `Pos()`, `MetaData()`, `SQLInfo()` methods created for them to get specific info. + Special types of responses are used with special requests. + +#### Future changes + +* Method `Get` now returns response data instead of the actual response. +* New method `GetResponse` added to get an actual response. +* `Future` constructors now accept `Request` as their argument. +* Methods `AppendPush` and `SetResponse` accepts response `Header` and data + as their arguments. + +#### Connector changes + +* Operations `Ping`, `Select`, `Insert`, `Replace`, `Delete`, `Update`, `Upsert`, + `Call`, `Call16`, `Call17`, `Eval`, `Execute` of a `Connector` return + response data instead of an actual responses. +* New interface `Doer` is added as a child-interface instead of a `Do` method. #### Connect function @@ -270,8 +311,8 @@ for creating a connection are now stored in corresponding `Dialer`, not in `Opts #### Connection schema * Removed `Schema` field from the `Connection` struct. Instead, new -`GetSchema(Connector)` function was added to get the actual connection -schema on demand. + `GetSchema(Doer)` function was added to get the actual connection + schema on demand. * `OverrideSchema(*Schema)` method replaced with the `SetSchema(Schema)`. #### Protocol changes diff --git a/box_error_test.go b/box_error_test.go index b08b6cc52..c48303bba 100644 --- a/box_error_test.go +++ b/box_error_test.go @@ -304,11 +304,11 @@ func TestErrorTypeEval(t *testing.T) { for name, testcase := range tupleCases { t.Run(name, func(t *testing.T) { - resp, err := conn.Eval("return ...", []interface{}{&testcase.tuple.val}) + data, err := conn.Eval("return ...", []interface{}{&testcase.tuple.val}) require.Nil(t, err) - require.NotNil(t, resp.Data) - require.Equal(t, len(resp.Data), 1) - actual, ok := resp.Data[0].(*BoxError) + require.NotNil(t, data) + require.Equal(t, len(data), 1) + actual, ok := data[0].(*BoxError) require.Truef(t, ok, "Response data has valid type") require.Equal(t, testcase.tuple.val, *actual) }) @@ -436,15 +436,14 @@ func TestErrorTypeSelect(t *testing.T) { _, err := conn.Eval(insertEval, []interface{}{}) require.Nilf(t, err, "Tuple has been successfully inserted") - var resp *Response var offset uint32 = 0 var limit uint32 = 1 - resp, err = conn.Select(space, index, offset, limit, IterEq, + data, err := conn.Select(space, index, offset, limit, IterEq, []interface{}{testcase.tuple.pk}) require.Nil(t, err) - require.NotNil(t, resp.Data) - require.Equalf(t, len(resp.Data), 1, "Exactly one tuple had been found") - tpl, ok := resp.Data[0].([]interface{}) + require.NotNil(t, data) + require.Equalf(t, len(data), 1, "Exactly one tuple had been found") + tpl, ok := data[0].([]interface{}) require.Truef(t, ok, "Tuple has valid type") require.Equal(t, testcase.tuple.pk, tpl[0]) actual, ok := tpl[1].(*BoxError) diff --git a/connection.go b/connection.go index bf9f1554e..8f8631a31 100644 --- a/connection.go +++ b/connection.go @@ -61,6 +61,8 @@ const ( LogUnexpectedResultId // LogWatchEventReadFailed is logged when failed to read a watch event. LogWatchEventReadFailed + // LogAppendPushFailed is logged when failed to append a push response. + LogAppendPushFailed ) // ConnEvent is sent throw Notify channel specified in Opts. @@ -97,12 +99,15 @@ func (d defaultLogger) Report(event ConnLogKind, conn *Connection, v ...interfac log.Printf("tarantool: last reconnect to %s failed: %s, giving it up", conn.Addr(), err) case LogUnexpectedResultId: - resp := v[0].(*Response) + header := v[0].(Header) log.Printf("tarantool: connection %s got unexpected resultId (%d) in response", - conn.Addr(), resp.RequestId) + conn.Addr(), header.RequestId) case LogWatchEventReadFailed: err := v[0].(error) log.Printf("tarantool: unable to parse watch event: %s", err) + case LogAppendPushFailed: + err := v[0].(error) + log.Printf("tarantool: unable to append a push response: %s", err) default: args := append([]interface{}{"tarantool: unexpected event ", event, conn}, v...) log.Print(args...) @@ -807,8 +812,8 @@ func (conn *Connection) reader(r io.Reader, c Conn) { conn.reconnect(err, c) return } - resp := &Response{buf: smallBuf{b: respBytes}} - err = resp.decodeHeader(conn.dec) + buf := smallBuf{b: respBytes} + header, code, err := decodeHeader(conn.dec, &buf) if err != nil { err = ClientError{ ErrProtocolError, @@ -819,8 +824,8 @@ func (conn *Connection) reader(r io.Reader, c Conn) { } var fut *Future = nil - if iproto.Type(resp.Code) == iproto.IPROTO_EVENT { - if event, err := readWatchEvent(&resp.buf); err == nil { + if code == iproto.IPROTO_EVENT { + if event, err := readWatchEvent(&buf); err == nil { events <- event } else { err = ClientError{ @@ -830,19 +835,27 @@ func (conn *Connection) reader(r io.Reader, c Conn) { conn.opts.Logger.Report(LogWatchEventReadFailed, conn, err) } continue - } else if resp.Code == PushCode { - if fut = conn.peekFuture(resp.RequestId); fut != nil { - fut.AppendPush(resp) + } else if code == iproto.IPROTO_CHUNK { + if fut = conn.peekFuture(header.RequestId); fut != nil { + if err := fut.AppendPush(header, &buf); err != nil { + err = ClientError{ + ErrProtocolError, + fmt.Sprintf("failed to append push response: %s", err), + } + conn.opts.Logger.Report(LogAppendPushFailed, conn, err) + } } } else { - if fut = conn.fetchFuture(resp.RequestId); fut != nil { - fut.SetResponse(resp) + if fut = conn.fetchFuture(header.RequestId); fut != nil { + if err := fut.SetResponse(header, &buf); err != nil { + fut.SetError(fmt.Errorf("failed to set response: %w", err)) + } conn.markDone(fut) } } if fut == nil { - conn.opts.Logger.Report(LogUnexpectedResultId, conn, resp) + conn.opts.Logger.Report(LogUnexpectedResultId, conn, header) } } } @@ -872,8 +885,9 @@ func (conn *Connection) eventer(events <-chan connWatchEvent) { } } -func (conn *Connection) newFuture(ctx context.Context) (fut *Future) { - fut = NewFuture() +func (conn *Connection) newFuture(req Request) (fut *Future) { + ctx := req.Ctx() + fut = NewFuture(req) if conn.rlimit != nil && conn.opts.RLimitAction == RLimitDrop { select { case conn.rlimit <- struct{}{}: @@ -983,7 +997,7 @@ func (conn *Connection) decrementRequestCnt() { func (conn *Connection) send(req Request, streamId uint64) *Future { conn.incrementRequestCnt() - fut := conn.newFuture(req.Ctx()) + fut := conn.newFuture(req) if fut.ready == nil { conn.decrementRequestCnt() return fut @@ -1052,11 +1066,11 @@ func (conn *Connection) putFuture(fut *Future, req Request, streamId uint64) { if req.Async() { if fut = conn.fetchFuture(reqid); fut != nil { - resp := &Response{ + header := Header{ RequestId: reqid, - Code: OkCode, + Error: ErrorNo, } - fut.SetResponse(resp) + fut.SetResponse(header, nil) conn.markDone(fut) } } @@ -1202,7 +1216,7 @@ func (conn *Connection) nextRequestId(context bool) (requestId uint32) { func (conn *Connection) Do(req Request) *Future { if connectedReq, ok := req.(ConnectedRequest); ok { if connectedReq.Conn() != conn { - fut := NewFuture() + fut := NewFuture(req) fut.SetError(errUnknownRequest) return fut } @@ -1236,7 +1250,7 @@ func (conn *Connection) SetSchema(s Schema) { // NewPrepared passes a sql statement to Tarantool for preparation synchronously. func (conn *Connection) NewPrepared(expr string) (*Prepared, error) { req := NewPrepareRequest(expr) - resp, err := conn.Do(req).Get() + resp, err := conn.Do(req).GetResponse() if err != nil { return nil, err } diff --git a/connector.go b/connector.go index 9536116d7..9917cff0f 100644 --- a/connector.go +++ b/connector.go @@ -2,52 +2,58 @@ package tarantool import "time" +// Doer is an interface that performs requests asynchronously. +type Doer interface { + // Do performs a request asynchronously. + Do(req Request) (fut *Future) +} + type Connector interface { + Doer ConnectedNow() bool Close() error ConfiguredTimeout() time.Duration NewPrepared(expr string) (*Prepared, error) NewStream() (*Stream, error) NewWatcher(key string, callback WatchCallback) (Watcher, error) - Do(req Request) (fut *Future) // Deprecated: the method will be removed in the next major version, // use a PingRequest object + Do() instead. - Ping() (*Response, error) + Ping() ([]interface{}, error) // Deprecated: the method will be removed in the next major version, // use a SelectRequest object + Do() instead. Select(space, index interface{}, offset, limit uint32, iterator Iter, - key interface{}) (*Response, error) + key interface{}) ([]interface{}, error) // Deprecated: the method will be removed in the next major version, // use an InsertRequest object + Do() instead. - Insert(space interface{}, tuple interface{}) (*Response, error) + Insert(space interface{}, tuple interface{}) ([]interface{}, error) // Deprecated: the method will be removed in the next major version, // use a ReplicaRequest object + Do() instead. - Replace(space interface{}, tuple interface{}) (*Response, error) + Replace(space interface{}, tuple interface{}) ([]interface{}, error) // Deprecated: the method will be removed in the next major version, // use a DeleteRequest object + Do() instead. - Delete(space, index interface{}, key interface{}) (*Response, error) + Delete(space, index interface{}, key interface{}) ([]interface{}, error) // Deprecated: the method will be removed in the next major version, // use a UpdateRequest object + Do() instead. - Update(space, index interface{}, key interface{}, ops *Operations) (*Response, error) + Update(space, index interface{}, key interface{}, ops *Operations) ([]interface{}, error) // Deprecated: the method will be removed in the next major version, // use a UpsertRequest object + Do() instead. - Upsert(space interface{}, tuple interface{}, ops *Operations) (*Response, error) + Upsert(space interface{}, tuple interface{}, ops *Operations) ([]interface{}, 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) + Call(functionName string, args interface{}) ([]interface{}, error) // Deprecated: the method will be removed in the next major version, // use a Call16Request object + Do() instead. - Call16(functionName string, args interface{}) (*Response, error) + Call16(functionName string, args interface{}) ([]interface{}, error) // Deprecated: the method will be removed in the next major version, // use a Call17Request object + Do() instead. - Call17(functionName string, args interface{}) (*Response, error) + Call17(functionName string, args interface{}) ([]interface{}, error) // Deprecated: the method will be removed in the next major version, // use an EvalRequest object + Do() instead. - Eval(expr string, args interface{}) (*Response, error) + Eval(expr string, args interface{}) ([]interface{}, error) // Deprecated: the method will be removed in the next major version, // use an ExecuteRequest object + Do() instead. - Execute(expr string, args interface{}) (*Response, error) + Execute(expr string, args interface{}) ([]interface{}, error) // Deprecated: the method will be removed in the next major version, // use a SelectRequest object + Do() instead. diff --git a/const.go b/const.go index 62650b5be..e8f389253 100644 --- a/const.go +++ b/const.go @@ -9,6 +9,7 @@ const ( ) const ( - OkCode = uint32(iproto.IPROTO_OK) - PushCode = uint32(iproto.IPROTO_CHUNK) + // ErrorNo indicates that no error has occurred. It could be used to + // check that a response has an error without the response body decoding. + ErrorNo = iproto.ER_UNKNOWN ) diff --git a/crud/common.go b/crud/common.go index 7a6f9e03b..061818487 100644 --- a/crud/common.go +++ b/crud/common.go @@ -55,6 +55,7 @@ package crud import ( "context" + "io" "github.com/tarantool/go-iproto" @@ -84,6 +85,12 @@ func (req baseRequest) Async() bool { return req.impl.Async() } +// Response creates a response for the baseRequest. +func (req baseRequest) Response(header tarantool.Header, + body io.Reader) (tarantool.Response, error) { + return req.impl.Response(header, body) +} + type spaceRequest struct { baseRequest space string diff --git a/crud/tarantool_test.go b/crud/tarantool_test.go index dfc8d064e..8ee28cf09 100644 --- a/crud/tarantool_test.go +++ b/crud/tarantool_test.go @@ -517,39 +517,32 @@ func testSelectGeneratedData(t *testing.T, conn tarantool.Connector, Limit(20). Iterator(tarantool.IterGe). Key([]interface{}{uint(1010)}) - resp, err := conn.Do(req).Get() + data, err := conn.Do(req).Get() if err != nil { t.Fatalf("Failed to Select: %s", err.Error()) } - if resp == nil { - t.Fatalf("Response is nil after Select") - } - if len(resp.Data) != expectedTuplesCount { - t.Fatalf("Response Data len %d != %d", len(resp.Data), expectedTuplesCount) + if len(data) != expectedTuplesCount { + t.Fatalf("Response Data len %d != %d", len(data), expectedTuplesCount) } } func testCrudRequestCheck(t *testing.T, req tarantool.Request, - resp *tarantool.Response, err error, expectedLen int) { + data []interface{}, err error, expectedLen int) { t.Helper() if err != nil { t.Fatalf("Failed to Do CRUD request: %s", err.Error()) } - if resp == nil { - t.Fatalf("Response is nil after Do CRUD request") - } - - if len(resp.Data) < expectedLen { + if len(data) < expectedLen { t.Fatalf("Response Body len < %#v, actual len %#v", - expectedLen, len(resp.Data)) + expectedLen, len(data)) } // resp.Data[0] - CRUD res. // resp.Data[1] - CRUD err. - if expectedLen >= 2 && resp.Data[1] != nil { - if crudErr, err := getCrudError(req, resp.Data[1]); err != nil { + if expectedLen >= 2 && data[1] != nil { + if crudErr, err := getCrudError(req, data[1]); err != nil { t.Fatalf("Failed to get CRUD error: %#v", err) } else if crudErr != nil { t.Fatalf("Failed to perform CRUD request on CRUD side: %#v", crudErr) @@ -569,8 +562,8 @@ func TestCrudGenerateData(t *testing.T) { conn.Do(req).Get() } - resp, err := conn.Do(testCase.req).Get() - testCrudRequestCheck(t, testCase.req, resp, + data, err := conn.Do(testCase.req).Get() + testCrudRequestCheck(t, testCase.req, data, err, testCase.expectedRespLen) testSelectGeneratedData(t, conn, testCase.expectedTuplesCount) @@ -591,8 +584,8 @@ func TestCrudProcessData(t *testing.T) { for _, testCase := range testProcessDataCases { t.Run(testCase.name, func(t *testing.T) { testCrudRequestPrepareData(t, conn) - resp, err := conn.Do(testCase.req).Get() - testCrudRequestCheck(t, testCase.req, resp, + data, err := conn.Do(testCase.req).Get() + testCrudRequestCheck(t, testCase.req, data, err, testCase.expectedRespLen) for i := 1010; i < 1020; i++ { req := tarantool.NewDeleteRequest(spaceName). @@ -623,8 +616,8 @@ func TestCrudUpdateSplice(t *testing.T) { Opts(simpleOperationOpts) testCrudRequestPrepareData(t, conn) - resp, err := conn.Do(req).Get() - testCrudRequestCheck(t, req, resp, + data, err := conn.Do(req).Get() + testCrudRequestCheck(t, req, data, err, 2) } @@ -648,8 +641,8 @@ func TestCrudUpsertSplice(t *testing.T) { Opts(simpleOperationOpts) testCrudRequestPrepareData(t, conn) - resp, err := conn.Do(req).Get() - testCrudRequestCheck(t, req, resp, + data, err := conn.Do(req).Get() + testCrudRequestCheck(t, req, data, err, 2) } @@ -673,8 +666,8 @@ func TestCrudUpsertObjectSplice(t *testing.T) { Opts(simpleOperationOpts) testCrudRequestPrepareData(t, conn) - resp, err := conn.Do(req).Get() - testCrudRequestCheck(t, req, resp, + data, err := conn.Do(req).Get() + testCrudRequestCheck(t, req, data, err, 2) } @@ -719,11 +712,11 @@ func TestUnflattenRows(t *testing.T) { req := crud.MakeReplaceRequest(spaceName). Tuple(tuple). Opts(simpleOperationOpts) - resp, err := conn.Do(req).Get() - testCrudRequestCheck(t, req, resp, err, 2) + data, err := conn.Do(req).Get() + testCrudRequestCheck(t, req, data, err, 2) - if res, ok = resp.Data[0].(map[interface{}]interface{}); !ok { - t.Fatalf("Unexpected CRUD result: %#v", resp.Data[0]) + if res, ok = data[0].(map[interface{}]interface{}); !ok { + t.Fatalf("Unexpected CRUD result: %#v", data[0]) } if rawMetadata, ok := res["metadata"]; !ok { @@ -1293,21 +1286,21 @@ func TestNoreturnOption(t *testing.T) { conn.Do(req).Get() } - resp, err := conn.Do(testCase.req).Get() + data, err := conn.Do(testCase.req).Get() if err != nil { t.Fatalf("Failed to Do CRUD request: %s", err) } - if len(resp.Data) == 0 { + if len(data) == 0 { t.Fatalf("Expected explicit nil") } - if resp.Data[0] != nil { - t.Fatalf("Expected nil result, got %v", resp.Data[0]) + if data[0] != nil { + t.Fatalf("Expected nil result, got %v", data[0]) } - if len(resp.Data) >= 2 && resp.Data[1] != nil { - t.Fatalf("Expected no returned errors, got %v", resp.Data[1]) + if len(data) >= 2 && data[1] != nil { + t.Fatalf("Expected no returned errors, got %v", data[1]) } for i := 1010; i < 1020; i++ { diff --git a/datetime/datetime_test.go b/datetime/datetime_test.go index 630d5f062..8be50e0e7 100644 --- a/datetime/datetime_test.go +++ b/datetime/datetime_test.go @@ -405,12 +405,12 @@ func TestDatetimeTarantoolInterval(t *testing.T) { func(t *testing.T) { req := NewCallRequest("call_datetime_interval"). Args([]interface{}{dti, dtj}) - resp, err := conn.Do(req).Get() + data, err := conn.Do(req).Get() if err != nil { t.Fatalf("Unable to call call_datetime_interval: %s", err) } ival := dti.Interval(dtj) - ret := resp.Data[0].(Interval) + ret := data[0].(Interval) if !reflect.DeepEqual(ival, ret) { t.Fatalf("%v != %v", ival, ret) } @@ -540,13 +540,13 @@ func TestCustomTimezone(t *testing.T) { } req := NewReplaceRequest(spaceTuple1).Tuple([]interface{}{dt, "payload"}) - resp, err := conn.Do(req).Get() + data, err := conn.Do(req).Get() if err != nil { t.Fatalf("Datetime replace failed %s", err.Error()) } - assertDatetimeIsEqual(t, resp.Data, tm) + assertDatetimeIsEqual(t, data, tm) - tpl := resp.Data[0].([]interface{}) + tpl := data[0].([]interface{}) if respDt, ok := tpl[0].(Datetime); ok { zone := respDt.ToTime().Location().String() _, offset := respDt.ToTime().Zone() @@ -592,25 +592,19 @@ func tupleInsertSelectDelete(t *testing.T, conn *Connection, tm time.Time) { Limit(limit). Iterator(IterEq). Key([]interface{}{dt}) - resp, err := conn.Do(sel).Get() + data, err := conn.Do(sel).Get() if err != nil { t.Fatalf("Datetime select failed: %s", err.Error()) } - if resp == nil { - t.Fatalf("Response is nil after Select") - } - assertDatetimeIsEqual(t, resp.Data, tm) + assertDatetimeIsEqual(t, data, tm) // Delete tuple with datetime. del := NewDeleteRequest(spaceTuple1).Index(index).Key([]interface{}{dt}) - resp, err = conn.Do(del).Get() + data, err = conn.Do(del).Get() if err != nil { t.Fatalf("Datetime delete failed: %s", err.Error()) } - if resp == nil { - t.Fatalf("Response is nil after Delete") - } - assertDatetimeIsEqual(t, resp.Data, tm) + assertDatetimeIsEqual(t, data, tm) } var datetimeSample = []struct { @@ -747,28 +741,22 @@ func TestDatetimeReplace(t *testing.T) { t.Fatalf("Unable to create Datetime from %s: %s", tm, err) } rep := NewReplaceRequest(spaceTuple1).Tuple([]interface{}{dt, "payload"}) - resp, err := conn.Do(rep).Get() + data, err := conn.Do(rep).Get() if err != nil { t.Fatalf("Datetime replace failed: %s", err) } - if resp == nil { - t.Fatalf("Response is nil after Replace") - } - assertDatetimeIsEqual(t, resp.Data, tm) + assertDatetimeIsEqual(t, data, tm) sel := NewSelectRequest(spaceTuple1). Index(index). Limit(1). Iterator(IterEq). Key([]interface{}{dt}) - resp, err = conn.Do(sel).Get() + data, err = conn.Do(sel).Get() if err != nil { t.Fatalf("Datetime select failed: %s", err) } - if resp == nil { - t.Fatalf("Response is nil after Select") - } - assertDatetimeIsEqual(t, resp.Data, tm) + assertDatetimeIsEqual(t, data, tm) // Delete tuple with datetime. del := NewDeleteRequest(spaceTuple1).Index(index).Key([]interface{}{dt}) @@ -923,15 +911,15 @@ func TestCustomEncodeDecodeTuple1(t *testing.T) { }, } rep := NewReplaceRequest(spaceTuple2).Tuple(&tuple) - resp, err := conn.Do(rep).Get() - if err != nil || resp.Code != 0 { + data, err := conn.Do(rep).Get() + if err != nil { t.Fatalf("Failed to replace: %s", err.Error()) } - if len(resp.Data) != 1 { + if len(data) != 1 { t.Fatalf("Response Body len != 1") } - tpl, ok := resp.Data[0].([]interface{}) + tpl, ok := data[0].([]interface{}) if !ok { t.Fatalf("Unexpected body of Replace") } @@ -1033,11 +1021,11 @@ func TestCustomEncodeDecodeTuple5(t *testing.T) { Limit(1). Iterator(IterEq). Key([]interface{}{dt}) - resp, errSel := conn.Do(sel).Get() + data, errSel := conn.Do(sel).Get() if errSel != nil { t.Errorf("Failed to Select: %s", errSel.Error()) } - if tpl, ok := resp.Data[0].([]interface{}); !ok { + if tpl, ok := data[0].([]interface{}); !ok { t.Errorf("Unexpected body of Select") } else { if val, ok := tpl[0].(Datetime); !ok || !val.ToTime().Equal(tm) { diff --git a/datetime/example_test.go b/datetime/example_test.go index 72d3448c2..ac5f40500 100644 --- a/datetime/example_test.go +++ b/datetime/example_test.go @@ -50,22 +50,21 @@ func Example() { index := "primary" // Replace a tuple with datetime. - resp, err := conn.Do(tarantool.NewReplaceRequest(space). + data, err := conn.Do(tarantool.NewReplaceRequest(space). Tuple([]interface{}{dt}), ).Get() if err != nil { fmt.Printf("Error in replace is %v", err) return } - respDt := resp.Data[0].([]interface{})[0].(Datetime) + respDt := data[0].([]interface{})[0].(Datetime) fmt.Println("Datetime tuple replace") - fmt.Printf("Code: %d\n", resp.Code) fmt.Printf("Data: %v\n", respDt.ToTime()) // Select a tuple with datetime. var offset uint32 = 0 var limit uint32 = 1 - resp, err = conn.Do(tarantool.NewSelectRequest(space). + data, err = conn.Do(tarantool.NewSelectRequest(space). Index(index). Offset(offset). Limit(limit). @@ -76,13 +75,12 @@ func Example() { fmt.Printf("Error in select is %v", err) return } - respDt = resp.Data[0].([]interface{})[0].(Datetime) + respDt = data[0].([]interface{})[0].(Datetime) fmt.Println("Datetime tuple select") - fmt.Printf("Code: %d\n", resp.Code) fmt.Printf("Data: %v\n", respDt.ToTime()) // Delete a tuple with datetime. - resp, err = conn.Do(tarantool.NewDeleteRequest(space). + data, err = conn.Do(tarantool.NewDeleteRequest(space). Index(index). Key([]interface{}{dt}), ).Get() @@ -90,9 +88,8 @@ func Example() { fmt.Printf("Error in delete is %v", err) return } - respDt = resp.Data[0].([]interface{})[0].(Datetime) + respDt = data[0].([]interface{})[0].(Datetime) fmt.Println("Datetime tuple delete") - fmt.Printf("Code: %d\n", resp.Code) fmt.Printf("Data: %v\n", respDt.ToTime()) } diff --git a/datetime/interval_test.go b/datetime/interval_test.go index 4e3cf5ab1..95142fe47 100644 --- a/datetime/interval_test.go +++ b/datetime/interval_test.go @@ -121,12 +121,12 @@ func TestIntervalTarantoolEncoding(t *testing.T) { t.Run(fmt.Sprintf("%v", tc), func(t *testing.T) { req := tarantool.NewCallRequest("call_interval_testdata"). Args([]interface{}{tc}) - resp, err := conn.Do(req).Get() + data, err := conn.Do(req).Get() if err != nil { t.Fatalf("Unexpected error: %s", err.Error()) } - ret := resp.Data[0].(Interval) + ret := data[0].(Interval) if !reflect.DeepEqual(ret, tc) { t.Fatalf("Unexpected response: %v, expected %v", ret, tc) } diff --git a/decimal/decimal_test.go b/decimal/decimal_test.go index 14ce05b2a..573daa8f6 100644 --- a/decimal/decimal_test.go +++ b/decimal/decimal_test.go @@ -538,14 +538,11 @@ func TestSelect(t *testing.T) { } ins := NewInsertRequest(space).Tuple([]interface{}{MakeDecimal(number)}) - resp, err := conn.Do(ins).Get() + data, err := conn.Do(ins).Get() if err != nil { t.Fatalf("Decimal insert failed: %s", err) } - if resp == nil { - t.Fatalf("Response is nil after Replace") - } - tupleValueIsDecimal(t, resp.Data, number) + tupleValueIsDecimal(t, data, number) var offset uint32 = 0 var limit uint32 = 1 @@ -555,21 +552,18 @@ func TestSelect(t *testing.T) { Limit(limit). Iterator(IterEq). Key([]interface{}{MakeDecimal(number)}) - resp, err = conn.Do(sel).Get() + data, err = conn.Do(sel).Get() if err != nil { t.Fatalf("Decimal select failed: %s", err.Error()) } - if resp == nil { - t.Fatalf("Response is nil after Select") - } - tupleValueIsDecimal(t, resp.Data, number) + tupleValueIsDecimal(t, data, number) del := NewDeleteRequest(space).Index(index).Key([]interface{}{MakeDecimal(number)}) - resp, err = conn.Do(del).Get() + data, err = conn.Do(del).Get() if err != nil { t.Fatalf("Decimal delete failed: %s", err) } - tupleValueIsDecimal(t, resp.Data, number) + tupleValueIsDecimal(t, data, number) } func TestUnmarshal_from_decimal_new(t *testing.T) { @@ -591,14 +585,11 @@ func TestUnmarshal_from_decimal_new(t *testing.T) { call := NewEvalRequest("return require('decimal').new(...)"). Args([]interface{}{str}) - resp, err := conn.Do(call).Get() + data, 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) + tupleValueIsDecimal(t, []interface{}{data}, number) }) } } @@ -610,21 +601,18 @@ func assertInsert(t *testing.T, conn *Connection, numString string) { } ins := NewInsertRequest(space).Tuple([]interface{}{MakeDecimal(number)}) - resp, err := conn.Do(ins).Get() + data, err := conn.Do(ins).Get() if err != nil { t.Fatalf("Decimal insert failed: %s", err) } - if resp == nil { - t.Fatalf("Response is nil after Replace") - } - tupleValueIsDecimal(t, resp.Data, number) + tupleValueIsDecimal(t, data, number) del := NewDeleteRequest(space).Index(index).Key([]interface{}{MakeDecimal(number)}) - resp, err = conn.Do(del).Get() + data, err = conn.Do(del).Get() if err != nil { t.Fatalf("Decimal delete failed: %s", err) } - tupleValueIsDecimal(t, resp.Data, number) + tupleValueIsDecimal(t, data, number) } func TestInsert(t *testing.T) { @@ -654,28 +642,22 @@ func TestReplace(t *testing.T) { } rep := NewReplaceRequest(space).Tuple([]interface{}{MakeDecimal(number)}) - respRep, errRep := conn.Do(rep).Get() + dataRep, errRep := conn.Do(rep).Get() if errRep != nil { t.Fatalf("Decimal replace failed: %s", errRep) } - if respRep == nil { - t.Fatalf("Response is nil after Replace") - } - tupleValueIsDecimal(t, respRep.Data, number) + tupleValueIsDecimal(t, dataRep, number) sel := NewSelectRequest(space). Index(index). Limit(1). Iterator(IterEq). Key([]interface{}{MakeDecimal(number)}) - respSel, errSel := conn.Do(sel).Get() + dataSel, errSel := conn.Do(sel).Get() if errSel != nil { t.Fatalf("Decimal select failed: %s", errSel) } - if respSel == nil { - t.Fatalf("Response is nil after Select") - } - tupleValueIsDecimal(t, respSel.Data, number) + tupleValueIsDecimal(t, dataSel, number) } // runTestMain is a body of TestMain function diff --git a/decimal/example_test.go b/decimal/example_test.go index 3f7d4de06..5597590dc 100644 --- a/decimal/example_test.go +++ b/decimal/example_test.go @@ -44,18 +44,14 @@ func Example() { log.Fatalf("Failed to prepare test decimal: %s", err) } - resp, err := client.Do(tarantool.NewReplaceRequest(spaceNo). + data, err := client.Do(tarantool.NewReplaceRequest(spaceNo). Tuple([]interface{}{number}), ).Get() if err != nil { log.Fatalf("Decimal replace failed: %s", err) } - if resp == nil { - log.Fatalf("Response is nil after Replace") - } log.Println("Decimal tuple replace") log.Println("Error", err) - log.Println("Code", resp.Code) - log.Println("Data", resp.Data) + log.Println("Data", data) } diff --git a/dial.go b/dial.go index eae8e1283..ff5419760 100644 --- a/dial.go +++ b/dial.go @@ -398,21 +398,25 @@ func identify(w writeFlusher, r io.Reader) (ProtocolInfo, error) { return info, err } - resp, err := readResponse(r) + resp, err := readResponse(r, req) if err != nil { - if iproto.Error(resp.Code) == iproto.ER_UNKNOWN_REQUEST_TYPE { + if resp != nil && + resp.Header().Error == iproto.ER_UNKNOWN_REQUEST_TYPE { // IPROTO_ID requests are not supported by server. return info, nil } - + return info, err + } + data, err := resp.Decode() + if err != nil { return info, err } - if len(resp.Data) == 0 { + if len(data) == 0 { return info, errors.New("unexpected response: no data") } - info, ok := resp.Data[0].(ProtocolInfo) + info, ok := data[0].(ProtocolInfo) if !ok { return info, errors.New("unexpected response: wrong data") } @@ -474,7 +478,7 @@ func authenticate(c Conn, auth Auth, user string, pass string, salt string) erro if err = writeRequest(c, req); err != nil { return err } - if _, err = readResponse(c); err != nil { + if _, err = readResponse(c, req); err != nil { return err } return nil @@ -498,21 +502,24 @@ func writeRequest(w writeFlusher, req Request) error { } // readResponse reads a response from the reader. -func readResponse(r io.Reader) (Response, error) { +func readResponse(r io.Reader, req Request) (Response, error) { var lenbuf [packetLengthBytes]byte respBytes, err := read(r, lenbuf[:]) if err != nil { - return Response{}, fmt.Errorf("read error: %w", err) + return nil, fmt.Errorf("read error: %w", err) } - resp := Response{buf: smallBuf{b: respBytes}} - err = resp.decodeHeader(msgpack.NewDecoder(&smallBuf{})) + buf := smallBuf{b: respBytes} + header, _, err := decodeHeader(msgpack.NewDecoder(&smallBuf{}), &buf) if err != nil { - return resp, fmt.Errorf("decode response header error: %w", err) + return nil, fmt.Errorf("decode response header error: %w", err) } - - err = resp.decodeBody() + resp, err := req.Response(header, &buf) + if err != nil { + return nil, fmt.Errorf("creating response error: %w", err) + } + _, err = resp.Decode() if err != nil { switch err.(type) { case Error: diff --git a/dial_test.go b/dial_test.go index 17f037e00..88a582b07 100644 --- a/dial_test.go +++ b/dial_test.go @@ -303,9 +303,8 @@ func TestConn_ReadWrite(t *testing.T) { 0x80, // Empty map. }, dialer.conn.writebuf.Bytes()) - resp, err := fut.Get() + _, err := fut.Get() assert.Nil(t, err) - assert.NotNil(t, resp) } func TestConn_ContextCancel(t *testing.T) { diff --git a/example_custom_unpacking_test.go b/example_custom_unpacking_test.go index 316e0086f..a2706f3f6 100644 --- a/example_custom_unpacking_test.go +++ b/example_custom_unpacking_test.go @@ -97,13 +97,12 @@ func Example_customUnpacking() { tuple := Tuple2{Cid: 777, Orig: "orig", Members: []Member{{"lol", "", 1}, {"wut", "", 3}}} // Insert a structure itself. initReq := tarantool.NewReplaceRequest(spaceNo).Tuple(&tuple) - resp, err := conn.Do(initReq).Get() + data, err := conn.Do(initReq).Get() if err != nil { log.Fatalf("Failed to insert: %s", err.Error()) return } - fmt.Println("Data", resp.Data) - fmt.Println("Code", resp.Code) + fmt.Println("Data", data) var tuples1 []Tuple2 selectReq := tarantool.NewSelectRequest(spaceNo). @@ -139,7 +138,6 @@ func Example_customUnpacking() { // Output: // Data [[777 orig [[lol 1] [wut 3]]]] - // Code 0 // Tuples (tuples1) [{777 orig [{lol 1} {wut 3}]}] // Tuples (tuples2): [{{} 777 orig [{lol 1} {wut 3}]}] // Tuples (tuples3): [[{{} 221 [{Moscow 34} {Minsk 23} {Kiev 31}]}]] diff --git a/example_test.go b/example_test.go index 6500e18f4..965c25a82 100644 --- a/example_test.go +++ b/example_test.go @@ -149,12 +149,10 @@ func ExamplePingRequest() { defer conn.Close() // Ping a Tarantool instance to check connection. - resp, err := conn.Do(tarantool.NewPingRequest()).Get() - fmt.Println("Ping Code", resp.Code) - fmt.Println("Ping Data", resp.Data) + data, err := conn.Do(tarantool.NewPingRequest()).Get() + fmt.Println("Ping Data", data) fmt.Println("Ping Error", err) // Output: - // Ping Code 0 // Ping Data [] // Ping Error } @@ -181,11 +179,11 @@ func ExamplePingRequest_Context() { req := tarantool.NewPingRequest().Context(ctx) // Ping a Tarantool instance to check connection. - resp, err := conn.Do(req).Get() - fmt.Println("Ping Resp", resp) + data, err := conn.Do(req).Get() + fmt.Println("Ping Resp data", data) fmt.Println("Ping Error", err) // Output: - // Ping Resp + // Ping Resp data [] // Ping Error context is done } @@ -204,13 +202,31 @@ func ExampleSelectRequest() { Limit(100). Iterator(tarantool.IterEq). Key(key), - ).Get() + ).GetResponse() if err != nil { fmt.Printf("error in select is %v", err) return } - fmt.Printf("response is %#v\n", resp.Data) + selResp, ok := resp.(*tarantool.SelectResponse) + if !ok { + fmt.Print("wrong response type") + return + } + + pos, err := selResp.Pos() + if err != nil { + fmt.Printf("error in Pos: %v", err) + return + } + fmt.Printf("pos for Select is %v\n", pos) + + data, err := resp.Decode() + if err != nil { + fmt.Printf("error while decoding: %v", err) + return + } + fmt.Printf("response is %#v\n", data) var res []Tuple err = conn.Do(tarantool.NewSelectRequest("test"). @@ -226,6 +242,7 @@ func ExampleSelectRequest() { fmt.Printf("response is %v\n", res) // Output: + // pos for Select is [] // response is []interface {}{[]interface {}{0x457, "hello", "world"}} // response is [{{} 1111 hello world}] } @@ -236,12 +253,12 @@ func ExampleSelectRequest_spaceAndIndexNames() { req := tarantool.NewSelectRequest(spaceName) req.Index(indexName) - resp, err := conn.Do(req).Get() + data, err := conn.Do(req).Get() if err != nil { fmt.Printf("Failed to execute the request: %s\n", err) } else { - fmt.Println(resp.Data) + fmt.Println(data) } } @@ -250,21 +267,19 @@ func ExampleInsertRequest() { defer conn.Close() // Insert a new tuple { 31, 1 }. - resp, err := conn.Do(tarantool.NewInsertRequest(spaceNo). + data, err := conn.Do(tarantool.NewInsertRequest(spaceNo). Tuple([]interface{}{uint(31), "test", "one"}), ).Get() fmt.Println("Insert 31") fmt.Println("Error", err) - fmt.Println("Code", resp.Code) - fmt.Println("Data", resp.Data) + fmt.Println("Data", data) // Insert a new tuple { 32, 1 }. - resp, err = conn.Do(tarantool.NewInsertRequest("test"). + data, err = conn.Do(tarantool.NewInsertRequest("test"). Tuple(&Tuple{Id: 32, Msg: "test", Name: "one"}), ).Get() fmt.Println("Insert 32") fmt.Println("Error", err) - fmt.Println("Code", resp.Code) - fmt.Println("Data", resp.Data) + fmt.Println("Data", data) // Delete tuple with primary key { 31 }. conn.Do(tarantool.NewDeleteRequest("test"). @@ -279,11 +294,9 @@ func ExampleInsertRequest() { // Output: // Insert 31 // Error - // Code 0 // Data [[31 test one]] // Insert 32 // Error - // Code 0 // Data [[32 test one]] } @@ -292,12 +305,12 @@ func ExampleInsertRequest_spaceAndIndexNames() { defer conn.Close() req := tarantool.NewInsertRequest(spaceName) - resp, err := conn.Do(req).Get() + data, err := conn.Do(req).Get() if err != nil { fmt.Printf("Failed to execute the request: %s\n", err) } else { - fmt.Println(resp.Data) + fmt.Println(data) } } @@ -315,32 +328,28 @@ func ExampleDeleteRequest() { ).Get() // Delete tuple with primary key { 35 }. - resp, err := conn.Do(tarantool.NewDeleteRequest(spaceNo). + data, err := conn.Do(tarantool.NewDeleteRequest(spaceNo). Index(indexNo). Key([]interface{}{uint(35)}), ).Get() fmt.Println("Delete 35") fmt.Println("Error", err) - fmt.Println("Code", resp.Code) - fmt.Println("Data", resp.Data) + fmt.Println("Data", data) // Delete tuple with primary key { 36 }. - resp, err = conn.Do(tarantool.NewDeleteRequest("test"). + data, err = conn.Do(tarantool.NewDeleteRequest("test"). Index("primary"). Key([]interface{}{uint(36)}), ).Get() fmt.Println("Delete 36") fmt.Println("Error", err) - fmt.Println("Code", resp.Code) - fmt.Println("Data", resp.Data) + fmt.Println("Data", data) // Output: // Delete 35 // Error - // Code 0 // Data [[35 test one]] // Delete 36 // Error - // Code 0 // Data [[36 test one]] } @@ -350,12 +359,12 @@ func ExampleDeleteRequest_spaceAndIndexNames() { req := tarantool.NewDeleteRequest(spaceName) req.Index(indexName) - resp, err := conn.Do(req).Get() + data, err := conn.Do(req).Get() if err != nil { fmt.Printf("Failed to execute the request: %s\n", err) } else { - fmt.Println(resp.Data) + fmt.Println(data) } } @@ -371,50 +380,42 @@ func ExampleReplaceRequest() { // Replace a tuple with primary key 13. // Note, Tuple is defined within tests, and has EncdodeMsgpack and // DecodeMsgpack methods. - resp, err := conn.Do(tarantool.NewReplaceRequest(spaceNo). + data, err := conn.Do(tarantool.NewReplaceRequest(spaceNo). Tuple([]interface{}{uint(13), 1}), ).Get() fmt.Println("Replace 13") fmt.Println("Error", err) - fmt.Println("Code", resp.Code) - fmt.Println("Data", resp.Data) - resp, err = conn.Do(tarantool.NewReplaceRequest("test"). + fmt.Println("Data", data) + data, err = conn.Do(tarantool.NewReplaceRequest("test"). Tuple([]interface{}{uint(13), 1}), ).Get() fmt.Println("Replace 13") fmt.Println("Error", err) - fmt.Println("Code", resp.Code) - fmt.Println("Data", resp.Data) - resp, err = conn.Do(tarantool.NewReplaceRequest("test"). + fmt.Println("Data", data) + data, err = conn.Do(tarantool.NewReplaceRequest("test"). Tuple(&Tuple{Id: 13, Msg: "test", Name: "eleven"}), ).Get() fmt.Println("Replace 13") fmt.Println("Error", err) - fmt.Println("Code", resp.Code) - fmt.Println("Data", resp.Data) - resp, err = conn.Do(tarantool.NewReplaceRequest("test"). + fmt.Println("Data", data) + data, err = conn.Do(tarantool.NewReplaceRequest("test"). Tuple(&Tuple{Id: 13, Msg: "test", Name: "twelve"}), ).Get() fmt.Println("Replace 13") fmt.Println("Error", err) - fmt.Println("Code", resp.Code) - fmt.Println("Data", resp.Data) + fmt.Println("Data", data) // Output: // Replace 13 // Error - // Code 0 // Data [[13 1]] // Replace 13 // Error - // Code 0 // Data [[13 1]] // Replace 13 // Error - // Code 0 // Data [[13 test eleven]] // Replace 13 // Error - // Code 0 // Data [[13 test twelve]] } @@ -423,12 +424,12 @@ func ExampleReplaceRequest_spaceAndIndexNames() { defer conn.Close() req := tarantool.NewReplaceRequest(spaceName) - resp, err := conn.Do(req).Get() + data, err := conn.Do(req).Get() if err != nil { fmt.Printf("Failed to execute the request: %s\n", err) } else { - fmt.Println(resp.Data) + fmt.Println(data) } } @@ -453,12 +454,12 @@ func ExampleUpdateRequest() { Splice(1, 1, 2, "!!"). Insert(7, "new"). Assign(7, "updated")) - resp, err := conn.Do(req).Get() + data, 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) + fmt.Printf("response is %#v\n", data) // Output: // response is []interface {}{[]interface {}{0x457, "t!!t", 2, 0, 1, 1, 0, "updated"}} } @@ -469,12 +470,12 @@ func ExampleUpdateRequest_spaceAndIndexNames() { req := tarantool.NewUpdateRequest(spaceName) req.Index(indexName) - resp, err := conn.Do(req).Get() + data, err := conn.Do(req).Get() if err != nil { fmt.Printf("Failed to execute the request: %s\n", err) } else { - fmt.Println(resp.Data) + fmt.Println(data) } } @@ -486,33 +487,33 @@ func ExampleUpsertRequest() { req = tarantool.NewUpsertRequest(617). Tuple([]interface{}{uint(1113), "first", "first"}). Operations(tarantool.NewOperations().Assign(1, "updated")) - resp, err := conn.Do(req).Get() + data, err := conn.Do(req).Get() if err != nil { fmt.Printf("error in do select upsert is %v", err) return } - fmt.Printf("response is %#v\n", resp.Data) + fmt.Printf("response is %#v\n", data) req = tarantool.NewUpsertRequest("test"). Tuple([]interface{}{uint(1113), "second", "second"}). Operations(tarantool.NewOperations().Assign(2, "updated")) fut := conn.Do(req) - resp, err = fut.Get() + data, err = fut.Get() if err != nil { fmt.Printf("error in do async upsert request is %v", err) return } - fmt.Printf("response is %#v\n", resp.Data) + fmt.Printf("response is %#v\n", data) req = tarantool.NewSelectRequest(617). Limit(100). Key(tarantool.IntKey{1113}) - resp, err = conn.Do(req).Get() + data, err = conn.Do(req).Get() if err != nil { fmt.Printf("error in do select request is %v", err) return } - fmt.Printf("response is %#v\n", resp.Data) + fmt.Printf("response is %#v\n", data) // Output: // response is []interface {}{} // response is []interface {}{} @@ -524,12 +525,12 @@ func ExampleUpsertRequest_spaceAndIndexNames() { defer conn.Close() req := tarantool.NewUpsertRequest(spaceName) - resp, err := conn.Do(req).Get() + data, err := conn.Do(req).Get() if err != nil { fmt.Printf("Failed to execute the request: %s\n", err) } else { - fmt.Println(resp.Data) + fmt.Println(data) } } @@ -538,17 +539,15 @@ func ExampleCallRequest() { defer conn.Close() // Call a function 'simple_concat' with arguments. - resp, err := conn.Do(tarantool.NewCallRequest("simple_concat"). + data, err := conn.Do(tarantool.NewCallRequest("simple_concat"). Args([]interface{}{"1"}), ).Get() fmt.Println("Call simple_concat()") fmt.Println("Error", err) - fmt.Println("Code", resp.Code) - fmt.Println("Data", resp.Data) + fmt.Println("Data", data) // Output: // Call simple_concat() // Error - // Code 0 // Data [11] } @@ -557,15 +556,13 @@ func ExampleEvalRequest() { defer conn.Close() // Run raw Lua code. - resp, err := conn.Do(tarantool.NewEvalRequest("return 1 + 2")).Get() + data, err := conn.Do(tarantool.NewEvalRequest("return 1 + 2")).Get() fmt.Println("Eval 'return 1 + 2'") fmt.Println("Error", err) - fmt.Println("Code", resp.Code) - fmt.Println("Data", resp.Data) + fmt.Println("Data", data) // Output: // Eval 'return 1 + 2' // Error - // Code 0 // Data [3] } @@ -586,13 +583,27 @@ func ExampleExecuteRequest() { req := tarantool.NewExecuteRequest( "CREATE TABLE SQL_TEST (id INTEGER PRIMARY KEY, name STRING)") - resp, err := conn.Do(req).Get() + resp, err := conn.Do(req).GetResponse() fmt.Println("Execute") fmt.Println("Error", err) - fmt.Println("Code", resp.Code) - fmt.Println("Data", resp.Data) - fmt.Println("MetaData", resp.MetaData) - fmt.Println("SQL Info", resp.SQLInfo) + + data, err := resp.Decode() + fmt.Println("Error", err) + fmt.Println("Data", data) + + exResp, ok := resp.(*tarantool.ExecuteResponse) + if !ok { + fmt.Printf("wrong response type") + return + } + + metaData, err := exResp.MetaData() + fmt.Println("MetaData", metaData) + fmt.Println("Error", err) + + sqlInfo, err := exResp.SQLInfo() + fmt.Println("SQL Info", sqlInfo) + fmt.Println("Error", err) // There are 4 options to pass named parameters to an SQL query: // 1) The simple map; @@ -623,55 +634,105 @@ func ExampleExecuteRequest() { req = tarantool.NewExecuteRequest( "CREATE TABLE SQL_TEST (id INTEGER PRIMARY KEY, name STRING)") req = req.Args(sqlBind1) - resp, err = conn.Do(req).Get() + resp, err = conn.Do(req).GetResponse() fmt.Println("Execute") fmt.Println("Error", err) - fmt.Println("Code", resp.Code) - fmt.Println("Data", resp.Data) - fmt.Println("MetaData", resp.MetaData) - fmt.Println("SQL Info", resp.SQLInfo) + data, err = resp.Decode() + fmt.Println("Error", err) + fmt.Println("Data", data) + exResp, ok = resp.(*tarantool.ExecuteResponse) + if !ok { + fmt.Printf("wrong response type") + return + } + metaData, err = exResp.MetaData() + fmt.Println("MetaData", metaData) + fmt.Println("Error", err) + sqlInfo, err = exResp.SQLInfo() + fmt.Println("SQL Info", sqlInfo) + fmt.Println("Error", err) // 2) req = req.Args(sqlBind2) - resp, err = conn.Do(req).Get() + resp, err = conn.Do(req).GetResponse() fmt.Println("Execute") fmt.Println("Error", err) - fmt.Println("Code", resp.Code) - fmt.Println("Data", resp.Data) - fmt.Println("MetaData", resp.MetaData) - fmt.Println("SQL Info", resp.SQLInfo) + data, err = resp.Decode() + fmt.Println("Error", err) + fmt.Println("Data", data) + exResp, ok = resp.(*tarantool.ExecuteResponse) + if !ok { + fmt.Printf("wrong response type") + return + } + metaData, err = exResp.MetaData() + fmt.Println("MetaData", metaData) + fmt.Println("Error", err) + sqlInfo, err = exResp.SQLInfo() + fmt.Println("SQL Info", sqlInfo) + fmt.Println("Error", err) // 3) req = req.Args(sqlBind3) - resp, err = conn.Do(req).Get() + resp, err = conn.Do(req).GetResponse() fmt.Println("Execute") fmt.Println("Error", err) - fmt.Println("Code", resp.Code) - fmt.Println("Data", resp.Data) - fmt.Println("MetaData", resp.MetaData) - fmt.Println("SQL Info", resp.SQLInfo) + data, err = resp.Decode() + fmt.Println("Error", err) + fmt.Println("Data", data) + exResp, ok = resp.(*tarantool.ExecuteResponse) + if !ok { + fmt.Printf("wrong response type") + return + } + metaData, err = exResp.MetaData() + fmt.Println("MetaData", metaData) + fmt.Println("Error", err) + sqlInfo, err = exResp.SQLInfo() + fmt.Println("SQL Info", sqlInfo) + fmt.Println("Error", err) // 4) req = req.Args(sqlBind4) - resp, err = conn.Do(req).Get() + resp, err = conn.Do(req).GetResponse() fmt.Println("Execute") fmt.Println("Error", err) - fmt.Println("Code", resp.Code) - fmt.Println("Data", resp.Data) - fmt.Println("MetaData", resp.MetaData) - fmt.Println("SQL Info", resp.SQLInfo) + data, err = resp.Decode() + fmt.Println("Error", err) + fmt.Println("Data", data) + exResp, ok = resp.(*tarantool.ExecuteResponse) + if !ok { + fmt.Printf("wrong response type") + return + } + metaData, err = exResp.MetaData() + fmt.Println("MetaData", metaData) + fmt.Println("Error", err) + sqlInfo, err = exResp.SQLInfo() + fmt.Println("SQL Info", sqlInfo) + fmt.Println("Error", err) // The way to pass positional arguments to an SQL query. req = tarantool.NewExecuteRequest( "SELECT id FROM SQL_TEST WHERE id=? AND name=?"). Args([]interface{}{2, "test"}) - resp, err = conn.Do(req).Get() + resp, err = conn.Do(req).GetResponse() fmt.Println("Execute") fmt.Println("Error", err) - fmt.Println("Code", resp.Code) - fmt.Println("Data", resp.Data) - fmt.Println("MetaData", resp.MetaData) - fmt.Println("SQL Info", resp.SQLInfo) + data, err = resp.Decode() + fmt.Println("Error", err) + fmt.Println("Data", data) + exResp, ok = resp.(*tarantool.ExecuteResponse) + if !ok { + fmt.Printf("wrong response type") + return + } + metaData, err = exResp.MetaData() + fmt.Println("MetaData", metaData) + fmt.Println("Error", err) + sqlInfo, err = exResp.SQLInfo() + fmt.Println("SQL Info", sqlInfo) + fmt.Println("Error", err) // The way to pass SQL expression with using custom packing/unpacking for // a type. @@ -690,13 +751,23 @@ func ExampleExecuteRequest() { req = tarantool.NewExecuteRequest( "SELECT id FROM SQL_TEST WHERE id=? AND name=?"). Args([]interface{}{tarantool.KeyValueBind{"id", 1}, "test"}) - resp, err = conn.Do(req).Get() + resp, err = conn.Do(req).GetResponse() fmt.Println("Execute") fmt.Println("Error", err) - fmt.Println("Code", resp.Code) - fmt.Println("Data", resp.Data) - fmt.Println("MetaData", resp.MetaData) - fmt.Println("SQL Info", resp.SQLInfo) + data, err = resp.Decode() + fmt.Println("Error", err) + fmt.Println("Data", data) + exResp, ok = resp.(*tarantool.ExecuteResponse) + if !ok { + fmt.Printf("wrong response type") + return + } + metaData, err = exResp.MetaData() + fmt.Println("MetaData", metaData) + fmt.Println("Error", err) + sqlInfo, err = exResp.SQLInfo() + fmt.Println("SQL Info", sqlInfo) + fmt.Println("Error", err) } func getTestTxnDialer() tarantool.Dialer { @@ -716,7 +787,6 @@ func getTestTxnDialer() tarantool.Dialer { func ExampleCommitRequest() { var req tarantool.Request - var resp *tarantool.Response var err error // Tarantool supports streams and interactive transactions since version 2.10.0 @@ -733,22 +803,22 @@ func ExampleCommitRequest() { // Begin transaction req = tarantool.NewBeginRequest() - resp, err = stream.Do(req).Get() + data, err := stream.Do(req).Get() if err != nil { fmt.Printf("Failed to Begin: %s", err.Error()) return } - fmt.Printf("Begin transaction: response is %#v\n", resp.Code) + fmt.Printf("Begin transaction: response is %#v\n", data) // Insert in stream req = tarantool.NewInsertRequest(spaceName). Tuple([]interface{}{uint(1001), "commit_hello", "commit_world"}) - resp, err = stream.Do(req).Get() + data, err = stream.Do(req).Get() if err != nil { fmt.Printf("Failed to Insert: %s", err.Error()) return } - fmt.Printf("Insert in stream: response is %#v\n", resp.Code) + fmt.Printf("Insert in stream: response is %#v\n", data) // Select not related to the transaction // while transaction is not committed @@ -758,42 +828,41 @@ func ExampleCommitRequest() { Limit(1). Iterator(tarantool.IterEq). Key([]interface{}{uint(1001)}) - resp, err = conn.Do(selectReq).Get() + data, err = conn.Do(selectReq).Get() if err != nil { fmt.Printf("Failed to Select: %s", err.Error()) return } - fmt.Printf("Select out of stream before commit: response is %#v\n", resp.Data) + fmt.Printf("Select out of stream before commit: response is %#v\n", data) // Select in stream - resp, err = stream.Do(selectReq).Get() + data, err = stream.Do(selectReq).Get() if err != nil { fmt.Printf("Failed to Select: %s", err.Error()) return } - fmt.Printf("Select in stream: response is %#v\n", resp.Data) + fmt.Printf("Select in stream: response is %#v\n", data) // Commit transaction req = tarantool.NewCommitRequest() - resp, err = stream.Do(req).Get() + data, err = stream.Do(req).Get() if err != nil { fmt.Printf("Failed to Commit: %s", err.Error()) return } - fmt.Printf("Commit transaction: response is %#v\n", resp.Code) + fmt.Printf("Commit transaction: response is %#v\n", data) // Select outside of transaction - resp, err = conn.Do(selectReq).Get() + data, err = conn.Do(selectReq).Get() if err != nil { fmt.Printf("Failed to Select: %s", err.Error()) return } - fmt.Printf("Select after commit: response is %#v\n", resp.Data) + fmt.Printf("Select after commit: response is %#v\n", data) } func ExampleRollbackRequest() { var req tarantool.Request - var resp *tarantool.Response var err error // Tarantool supports streams and interactive transactions since version 2.10.0 @@ -810,22 +879,22 @@ func ExampleRollbackRequest() { // Begin transaction req = tarantool.NewBeginRequest() - resp, err = stream.Do(req).Get() + data, err := stream.Do(req).Get() if err != nil { fmt.Printf("Failed to Begin: %s", err.Error()) return } - fmt.Printf("Begin transaction: response is %#v\n", resp.Code) + fmt.Printf("Begin transaction: response is %#v\n", data) // Insert in stream req = tarantool.NewInsertRequest(spaceName). Tuple([]interface{}{uint(2001), "rollback_hello", "rollback_world"}) - resp, err = stream.Do(req).Get() + data, err = stream.Do(req).Get() if err != nil { fmt.Printf("Failed to Insert: %s", err.Error()) return } - fmt.Printf("Insert in stream: response is %#v\n", resp.Code) + fmt.Printf("Insert in stream: response is %#v\n", data) // Select not related to the transaction // while transaction is not committed @@ -835,42 +904,41 @@ func ExampleRollbackRequest() { Limit(1). Iterator(tarantool.IterEq). Key([]interface{}{uint(2001)}) - resp, err = conn.Do(selectReq).Get() + data, err = conn.Do(selectReq).Get() if err != nil { fmt.Printf("Failed to Select: %s", err.Error()) return } - fmt.Printf("Select out of stream: response is %#v\n", resp.Data) + fmt.Printf("Select out of stream: response is %#v\n", data) // Select in stream - resp, err = stream.Do(selectReq).Get() + data, err = stream.Do(selectReq).Get() if err != nil { fmt.Printf("Failed to Select: %s", err.Error()) return } - fmt.Printf("Select in stream: response is %#v\n", resp.Data) + fmt.Printf("Select in stream: response is %#v\n", data) // Rollback transaction req = tarantool.NewRollbackRequest() - resp, err = stream.Do(req).Get() + data, err = stream.Do(req).Get() if err != nil { fmt.Printf("Failed to Rollback: %s", err.Error()) return } - fmt.Printf("Rollback transaction: response is %#v\n", resp.Code) + fmt.Printf("Rollback transaction: response is %#v\n", data) // Select outside of transaction - resp, err = conn.Do(selectReq).Get() + data, err = conn.Do(selectReq).Get() if err != nil { fmt.Printf("Failed to Select: %s", err.Error()) return } - fmt.Printf("Select after Rollback: response is %#v\n", resp.Data) + fmt.Printf("Select after Rollback: response is %#v\n", data) } func ExampleBeginRequest_TxnIsolation() { var req tarantool.Request - var resp *tarantool.Response var err error // Tarantool supports streams and interactive transactions since version 2.10.0 @@ -889,22 +957,22 @@ func ExampleBeginRequest_TxnIsolation() { req = tarantool.NewBeginRequest(). TxnIsolation(tarantool.ReadConfirmedLevel). Timeout(500 * time.Millisecond) - resp, err = stream.Do(req).Get() + data, err := stream.Do(req).Get() if err != nil { fmt.Printf("Failed to Begin: %s", err.Error()) return } - fmt.Printf("Begin transaction: response is %#v\n", resp.Code) + fmt.Printf("Begin transaction: response is %#v\n", data) // Insert in stream req = tarantool.NewInsertRequest(spaceName). Tuple([]interface{}{uint(2001), "rollback_hello", "rollback_world"}) - resp, err = stream.Do(req).Get() + data, err = stream.Do(req).Get() if err != nil { fmt.Printf("Failed to Insert: %s", err.Error()) return } - fmt.Printf("Insert in stream: response is %#v\n", resp.Code) + fmt.Printf("Insert in stream: response is %#v\n", data) // Select not related to the transaction // while transaction is not committed @@ -914,37 +982,57 @@ func ExampleBeginRequest_TxnIsolation() { Limit(1). Iterator(tarantool.IterEq). Key([]interface{}{uint(2001)}) - resp, err = conn.Do(selectReq).Get() + data, err = conn.Do(selectReq).Get() if err != nil { fmt.Printf("Failed to Select: %s", err.Error()) return } - fmt.Printf("Select out of stream: response is %#v\n", resp.Data) + fmt.Printf("Select out of stream: response is %#v\n", data) // Select in stream - resp, err = stream.Do(selectReq).Get() + data, err = stream.Do(selectReq).Get() if err != nil { fmt.Printf("Failed to Select: %s", err.Error()) return } - fmt.Printf("Select in stream: response is %#v\n", resp.Data) + fmt.Printf("Select in stream: response is %#v\n", data) // Rollback transaction req = tarantool.NewRollbackRequest() - resp, err = stream.Do(req).Get() + data, err = stream.Do(req).Get() if err != nil { fmt.Printf("Failed to Rollback: %s", err.Error()) return } - fmt.Printf("Rollback transaction: response is %#v\n", resp.Code) + fmt.Printf("Rollback transaction: response is %#v\n", data) // Select outside of transaction - resp, err = conn.Do(selectReq).Get() + data, err = conn.Do(selectReq).Get() if err != nil { fmt.Printf("Failed to Select: %s", err.Error()) return } - fmt.Printf("Select after Rollback: response is %#v\n", resp.Data) + fmt.Printf("Select after Rollback: response is %#v\n", data) +} + +func ExampleErrorNo() { + conn := exampleConnect(dialer, opts) + defer conn.Close() + + req := tarantool.NewPingRequest() + resp, err := conn.Do(req).GetResponse() + if err != nil { + fmt.Printf("error getting the response: %s\n", err) + return + } + + if resp.Header().Error != tarantool.ErrorNo { + fmt.Printf("response error code: %s\n", resp.Header().Error) + } else { + fmt.Println("Success.") + } + // Output: + // Success. } func ExampleFuture_GetIterator() { @@ -959,14 +1047,15 @@ func ExampleFuture_GetIterator() { var it tarantool.ResponseIterator for it = fut.GetIterator().WithTimeout(timeout); it.Next(); { resp := it.Value() - if resp.Code == tarantool.PushCode { + data, _ := resp.Decode() + if it.IsPush() { // It is a push message. - fmt.Printf("push message: %v\n", resp.Data[0]) - } else if resp.Code == tarantool.OkCode { + fmt.Printf("push message: %v\n", data[0]) + } else if resp.Header().Error == tarantool.ErrorNo { // It is a regular response. - fmt.Printf("response: %v", resp.Data[0]) + fmt.Printf("response: %v", data[0]) } else { - fmt.Printf("an unexpected response code %d", resp.Code) + fmt.Printf("an unexpected response code %d", resp.Header().Error) } } if err := it.Err(); err != nil { @@ -1148,11 +1237,11 @@ func ExampleConnection_Do() { // When the future receives the response, the result of the Future is set // and becomes available. We could wait for that moment with Future.Get() // or Future.GetTyped() methods. - resp, err := future.Get() + data, err := future.Get() if err != nil { fmt.Printf("Failed to execute the request: %s\n", err) } else { - fmt.Println(resp.Data) + fmt.Println(data) } // Output: @@ -1172,9 +1261,23 @@ func ExampleConnection_Do_failure() { future := conn.Do(req) // When the future receives the response, the result of the Future is set - // and becomes available. We could wait for that moment with Future.Get() - // or Future.GetTyped() methods. - resp, err := future.Get() + // and becomes available. We could wait for that moment with Future.Get(), + // Future.GetResponse() or Future.GetTyped() methods. + resp, err := future.GetResponse() + if err != nil { + fmt.Printf("Error in the future: %s\n", err) + } + // Optional step: check a response error. + // It allows checking that response has or hasn't an error without decoding. + if resp.Header().Error != tarantool.ErrorNo { + fmt.Printf("Response error: %s\n", resp.Header().Error) + } + + data, err := future.Get() + if err != nil { + fmt.Printf("Data: %v\n", data) + } + if err != nil { // We don't print the error here to keep the example reproducible. // fmt.Printf("Failed to execute the request: %s\n", err) @@ -1184,8 +1287,8 @@ func ExampleConnection_Do_failure() { } else { // Response exist. So it could be a Tarantool error or a decode // error. We need to check the error code. - fmt.Printf("Error code from the response: %d\n", resp.Code) - if resp.Code == tarantool.OkCode { + fmt.Printf("Error code from the response: %d\n", resp.Header().Error) + if resp.Header().Error == tarantool.ErrorNo { fmt.Printf("Decode error: %s\n", err) } else { code := err.(tarantool.Error).Code @@ -1196,6 +1299,8 @@ func ExampleConnection_Do_failure() { } // Output: + // Response error: ER_NO_SUCH_PROC + // Data: [] // Error code from the response: 33 // Error code from the error: 33 // Error short from the error: ER_NO_SUCH_PROC @@ -1326,7 +1431,7 @@ func ExampleConnection_CloseGraceful_force() { // Force Connection.Close()! // Connection.CloseGraceful() done! // Result: - // connection closed by client (0x4001) + // [] connection closed by client (0x4001) } func ExampleWatchOnceRequest() { @@ -1344,11 +1449,11 @@ func ExampleWatchOnceRequest() { conn.Do(tarantool.NewBroadcastRequest(key).Value(value)).Get() - resp, err := conn.Do(tarantool.NewWatchOnceRequest(key)).Get() + data, err := conn.Do(tarantool.NewWatchOnceRequest(key)).Get() if err != nil { fmt.Printf("Failed to execute the request: %s\n", err) } else { - fmt.Println(resp.Data) + fmt.Println(data) } } @@ -1377,8 +1482,8 @@ func ExampleFdDialer() { fmt.Printf("connect error: %v\n", err) return } - resp, err := conn.Do(tarantool.NewPingRequest()).Get() - fmt.Println(resp.Code, err) + _, err = conn.Do(tarantool.NewPingRequest()).Get() + fmt.Println(err) // Output: - // 0 + // } diff --git a/future.go b/future.go index e7f7dae19..1b9f2ed14 100644 --- a/future.go +++ b/future.go @@ -1,6 +1,7 @@ package tarantool import ( + "io" "sync" "time" ) @@ -8,11 +9,12 @@ import ( // Future is a handle for asynchronous request. type Future struct { requestId uint32 + req Request next *Future timeout time.Duration mutex sync.Mutex - pushes []*Response - resp *Response + pushes []Response + resp Response err error ready chan struct{} done chan struct{} @@ -40,7 +42,7 @@ func (fut *Future) isDone() bool { type asyncResponseIterator struct { fut *Future timeout time.Duration - resp *Response + resp Response err error curPos int done bool @@ -77,24 +79,24 @@ func (it *asyncResponseIterator) Next() bool { return false } - if it.err = it.resp.decodeBody(); it.err != nil { - it.resp = nil - return false - } - if last { it.done = true } else { + it.err = nil it.curPos += 1 } return true } -func (it *asyncResponseIterator) Value() *Response { +func (it *asyncResponseIterator) Value() Response { return it.resp } +func (it *asyncResponseIterator) IsPush() bool { + return !it.done +} + func (it *asyncResponseIterator) Err() error { return it.err } @@ -104,7 +106,7 @@ func (it *asyncResponseIterator) WithTimeout(timeout time.Duration) TimeoutRespo return it } -func (it *asyncResponseIterator) nextResponse() (resp *Response) { +func (it *asyncResponseIterator) nextResponse() (resp Response) { fut := it.fut pushesLen := len(fut.pushes) @@ -117,12 +119,26 @@ func (it *asyncResponseIterator) nextResponse() (resp *Response) { return resp } -// NewFuture creates a new empty Future. -func NewFuture() (fut *Future) { +// PushResponse is used for push requests for the Future. +type PushResponse struct { + baseResponse +} + +func createPushResponse(header Header, body io.Reader) (Response, error) { + resp, err := createBaseResponse(header, body) + if err != nil { + return nil, err + } + return &PushResponse{resp}, nil +} + +// NewFuture creates a new empty Future for a given Request. +func NewFuture(req Request) (fut *Future) { fut = &Future{} fut.ready = make(chan struct{}, 1000000000) fut.done = make(chan struct{}) - fut.pushes = make([]*Response, 0) + fut.pushes = make([]Response, 0) + fut.req = req return fut } @@ -131,31 +147,41 @@ func NewFuture() (fut *Future) { // // Deprecated: the method will be removed in the next major version, // use Connector.NewWatcher() instead of box.session.push(). -func (fut *Future) AppendPush(resp *Response) { +func (fut *Future) AppendPush(header Header, body io.Reader) error { fut.mutex.Lock() defer fut.mutex.Unlock() if fut.isDone() { - return + return nil + } + resp, err := createPushResponse(header, body) + if err != nil { + return err } - resp.Code = PushCode fut.pushes = append(fut.pushes, resp) fut.ready <- struct{}{} + return nil } // SetResponse sets a response for the future and finishes the future. -func (fut *Future) SetResponse(resp *Response) { +func (fut *Future) SetResponse(header Header, body io.Reader) error { fut.mutex.Lock() defer fut.mutex.Unlock() if fut.isDone() { - return + return nil + } + + resp, err := fut.req.Response(header, body) + if err != nil { + return err } fut.resp = resp close(fut.ready) close(fut.done) + return nil } // SetError sets an error for the future and finishes the future. @@ -172,22 +198,29 @@ func (fut *Future) SetError(err error) { close(fut.done) } -// Get waits for Future to be filled and returns Response and error. -// -// Response will contain deserialized result in Data field. -// It will be []interface{}, so if you want more performance, use GetTyped method. +// GetResponse waits for Future to be filled and returns Response and error. // // Note: Response could be equal to nil if ClientError is returned in error. // // "error" could be Error, if it is error returned by Tarantool, // or ClientError, if something bad happens in a client process. -func (fut *Future) Get() (*Response, error) { +func (fut *Future) GetResponse() (Response, error) { + fut.wait() + return fut.resp, fut.err +} + +// Get waits for Future to be filled and returns the data of the Response and error. +// +// The data will be []interface{}, so if you want more performance, use GetTyped method. +// +// "error" could be Error, if it is error returned by Tarantool, +// or ClientError, if something bad happens in a client process. +func (fut *Future) Get() ([]interface{}, error) { fut.wait() if fut.err != nil { - return fut.resp, fut.err + return nil, fut.err } - err := fut.resp.decodeBody() - return fut.resp, err + return fut.resp.Decode() } // GetTyped waits for Future and calls msgpack.Decoder.Decode(result) if no error happens. @@ -199,8 +232,7 @@ func (fut *Future) GetTyped(result interface{}) error { if fut.err != nil { return fut.err } - err := fut.resp.decodeBodyTyped(result) - return err + return fut.resp.DecodeTyped(result) } // GetIterator returns an iterator for iterating through push messages diff --git a/future_test.go b/future_test.go index 274bee7d5..6efda10a1 100644 --- a/future_test.go +++ b/future_test.go @@ -1,16 +1,88 @@ package tarantool_test import ( + "bytes" + "context" "errors" + "io" + "io/ioutil" "sync" "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/tarantool/go-iproto" . "github.com/tarantool/go-tarantool/v2" + "github.com/tarantool/go-tarantool/v2/test_helpers" + "github.com/vmihailenco/msgpack/v5" ) +type futureMockRequest struct { +} + +func (req *futureMockRequest) Type() iproto.Type { + return iproto.Type(0) +} + +func (req *futureMockRequest) Async() bool { + return false +} + +func (req *futureMockRequest) Body(resolver SchemaResolver, enc *msgpack.Encoder) error { + return nil +} + +func (req *futureMockRequest) Conn() *Connection { + return &Connection{} +} + +func (req *futureMockRequest) Ctx() context.Context { + return nil +} + +func (req *futureMockRequest) Response(header Header, + body io.Reader) (Response, error) { + resp, err := createFutureMockResponse(header, body) + return resp, err +} + +type futureMockResponse struct { + header Header + data []byte + + decodeCnt int + decodeTypedCnt int +} + +func (resp *futureMockResponse) Header() Header { + return resp.header +} + +func (resp *futureMockResponse) Decode() ([]interface{}, error) { + resp.decodeCnt++ + + dataInt := make([]interface{}, len(resp.data)) + for i := range resp.data { + dataInt[i] = resp.data[i] + } + return dataInt, nil +} + +func (resp *futureMockResponse) DecodeTyped(res interface{}) error { + resp.decodeTypedCnt++ + return nil +} + +func createFutureMockResponse(header Header, body io.Reader) (Response, error) { + data, err := ioutil.ReadAll(body) + if err != nil { + return nil, err + } + return &futureMockResponse{header: header, data: data}, nil +} + func assertResponseIteratorValue(t testing.TB, it ResponseIterator, - code uint32, resp *Response) { + isPush bool, resp Response) { t.Helper() if it.Err() != nil { @@ -19,13 +91,15 @@ func assertResponseIteratorValue(t testing.TB, it ResponseIterator, if it.Value() == nil { t.Errorf("An unexpected nil value") - } else if it.Value().Code != code { - t.Errorf("An unexpected response code %d, expected %d", it.Value().Code, code) + } else if it.IsPush() != isPush { + if isPush { + t.Errorf("An unexpected response type, expected to be push") + } else { + t.Errorf("An unexpected response type, expected not to be push") + } } - if it.Value() != resp { - t.Errorf("An unexpected response %v, expected %v", it.Value(), resp) - } + assert.Equalf(t, it.Value(), resp, "An unexpected response %v, expected %v", it.Value(), resp) } func assertResponseIteratorFinished(t testing.TB, it ResponseIterator) { @@ -40,7 +114,7 @@ func assertResponseIteratorFinished(t testing.TB, it ResponseIterator) { } func TestFutureGetIteratorNoItems(t *testing.T) { - fut := NewFuture() + fut := NewFuture(test_helpers.NewMockRequest()) it := fut.GetIterator() if it.Next() { @@ -51,12 +125,13 @@ func TestFutureGetIteratorNoItems(t *testing.T) { } func TestFutureGetIteratorNoResponse(t *testing.T) { - push := &Response{} - fut := NewFuture() - fut.AppendPush(push) + pushHeader := Header{} + push := &PushResponse{} + fut := NewFuture(test_helpers.NewMockRequest()) + fut.AppendPush(pushHeader, nil) if it := fut.GetIterator(); it.Next() { - assertResponseIteratorValue(t, it, PushCode, push) + assertResponseIteratorValue(t, it, true, push) if it.Next() == true { t.Errorf("An unexpected next value.") } @@ -67,12 +142,13 @@ func TestFutureGetIteratorNoResponse(t *testing.T) { } func TestFutureGetIteratorNoResponseTimeout(t *testing.T) { - push := &Response{} - fut := NewFuture() - fut.AppendPush(push) + pushHeader := Header{} + push := &PushResponse{} + fut := NewFuture(test_helpers.NewMockRequest()) + fut.AppendPush(pushHeader, nil) if it := fut.GetIterator().WithTimeout(1 * time.Nanosecond); it.Next() { - assertResponseIteratorValue(t, it, PushCode, push) + assertResponseIteratorValue(t, it, true, push) if it.Next() == true { t.Errorf("An unexpected next value.") } @@ -83,10 +159,12 @@ func TestFutureGetIteratorNoResponseTimeout(t *testing.T) { } func TestFutureGetIteratorResponseOnTimeout(t *testing.T) { - push := &Response{} - resp := &Response{} - fut := NewFuture() - fut.AppendPush(push) + pushHeader := Header{} + respHeader := Header{} + push := &PushResponse{} + resp := &test_helpers.MockResponse{} + fut := NewFuture(test_helpers.NewMockRequest()) + fut.AppendPush(pushHeader, nil) var done sync.WaitGroup var wait sync.WaitGroup @@ -99,13 +177,14 @@ func TestFutureGetIteratorResponseOnTimeout(t *testing.T) { var it ResponseIterator var cnt = 0 for it = fut.GetIterator().WithTimeout(5 * time.Second); it.Next(); { - code := PushCode - r := push + var r Response + isPush := true + r = push if cnt == 1 { - code = OkCode + isPush = false r = resp } - assertResponseIteratorValue(t, it, code, r) + assertResponseIteratorValue(t, it, isPush, r) cnt += 1 if cnt == 1 { wait.Done() @@ -119,19 +198,19 @@ func TestFutureGetIteratorResponseOnTimeout(t *testing.T) { }() wait.Wait() - fut.SetResponse(resp) + + fut.SetResponse(respHeader, nil) done.Wait() } func TestFutureGetIteratorFirstResponse(t *testing.T) { - resp1 := &Response{} - resp2 := &Response{} - fut := NewFuture() - fut.SetResponse(resp1) - fut.SetResponse(resp2) + resp := &test_helpers.MockResponse{} + fut := NewFuture(test_helpers.NewMockRequest()) + fut.SetResponse(Header{}, nil) + fut.SetResponse(Header{}, nil) if it := fut.GetIterator(); it.Next() { - assertResponseIteratorValue(t, it, OkCode, resp1) + assertResponseIteratorValue(t, it, false, resp) if it.Next() == true { t.Errorf("An unexpected next value.") } @@ -145,7 +224,7 @@ func TestFutureGetIteratorFirstError(t *testing.T) { const errMsg1 = "error1" const errMsg2 = "error2" - fut := NewFuture() + fut := NewFuture(test_helpers.NewMockRequest()) fut.SetError(errors.New(errMsg1)) fut.SetError(errors.New(errMsg2)) @@ -160,17 +239,18 @@ func TestFutureGetIteratorFirstError(t *testing.T) { } func TestFutureGetIteratorResponse(t *testing.T) { - responses := []*Response{ - {}, - {}, - {Code: OkCode}, + responses := []Response{ + &PushResponse{}, + &PushResponse{}, + &test_helpers.MockResponse{}, } - fut := NewFuture() - for i, resp := range responses { + header := Header{} + fut := NewFuture(test_helpers.NewMockRequest()) + for i := range responses { if i == len(responses)-1 { - fut.SetResponse(resp) + fut.SetResponse(header, nil) } else { - fut.AppendPush(resp) + fut.AppendPush(header, nil) } } @@ -181,11 +261,11 @@ func TestFutureGetIteratorResponse(t *testing.T) { for _, it := range its { var cnt = 0 for it.Next() { - code := PushCode + isPush := true if cnt == len(responses)-1 { - code = OkCode + isPush = false } - assertResponseIteratorValue(t, it, code, responses[cnt]) + assertResponseIteratorValue(t, it, isPush, responses[cnt]) cnt += 1 } assertResponseIteratorFinished(t, it) @@ -198,14 +278,14 @@ func TestFutureGetIteratorResponse(t *testing.T) { func TestFutureGetIteratorError(t *testing.T) { const errMsg = "error message" - responses := []*Response{ + responses := []*PushResponse{ {}, {}, } err := errors.New(errMsg) - fut := NewFuture() - for _, resp := range responses { - fut.AppendPush(resp) + fut := NewFuture(test_helpers.NewMockRequest()) + for range responses { + fut.AppendPush(Header{}, nil) } fut.SetError(err) @@ -216,8 +296,7 @@ func TestFutureGetIteratorError(t *testing.T) { for _, it := range its { var cnt = 0 for it.Next() { - code := PushCode - assertResponseIteratorValue(t, it, code, responses[cnt]) + assertResponseIteratorValue(t, it, true, responses[cnt]) cnt += 1 } if err = it.Err(); err != nil { @@ -236,19 +315,17 @@ func TestFutureGetIteratorError(t *testing.T) { func TestFutureSetStateRaceCondition(t *testing.T) { err := errors.New("any error") - resp := &Response{} for i := 0; i < 1000; i++ { - fut := NewFuture() + fut := NewFuture(test_helpers.NewMockRequest()) for j := 0; j < 9; j++ { go func(opt int) { if opt%3 == 0 { - respAppend := &Response{} - fut.AppendPush(respAppend) + fut.AppendPush(Header{}, nil) } else if opt%3 == 1 { fut.SetError(err) } else { - fut.SetResponse(resp) + fut.SetResponse(Header{}, nil) } }(j) } @@ -256,3 +333,67 @@ func TestFutureSetStateRaceCondition(t *testing.T) { // It may be false-positive, but very rarely - it's ok for such very // simple race conditions tests. } + +func TestFutureGetIteratorIsPush(t *testing.T) { + fut := NewFuture(test_helpers.NewMockRequest()) + fut.AppendPush(Header{}, nil) + fut.SetResponse(Header{}, nil) + it := fut.GetIterator() + + it.Next() + assert.True(t, it.IsPush()) + it.Next() + assert.False(t, it.IsPush()) +} + +func TestFuture_Get(t *testing.T) { + fut := NewFuture(&futureMockRequest{}) + fut.SetResponse(Header{}, bytes.NewReader([]byte{'v', '2'})) + + resp, err := fut.GetResponse() + assert.NoError(t, err) + mockResp, ok := resp.(*futureMockResponse) + assert.True(t, ok) + + data, err := fut.Get() + assert.NoError(t, err) + assert.Equal(t, []interface{}{uint8('v'), uint8('2')}, data) + assert.Equal(t, 1, mockResp.decodeCnt) + assert.Equal(t, 0, mockResp.decodeTypedCnt) +} + +func TestFuture_GetTyped(t *testing.T) { + fut := NewFuture(&futureMockRequest{}) + fut.SetResponse(Header{}, bytes.NewReader([]byte{'v', '2'})) + + resp, err := fut.GetResponse() + assert.NoError(t, err) + mockResp, ok := resp.(*futureMockResponse) + assert.True(t, ok) + + var data []byte + + err = fut.GetTyped(&data) + assert.NoError(t, err) + assert.Equal(t, 0, mockResp.decodeCnt) + assert.Equal(t, 1, mockResp.decodeTypedCnt) +} + +func TestFuture_GetResponse(t *testing.T) { + mockResp, err := createFutureMockResponse(Header{}, + bytes.NewReader([]byte{'v', '2'})) + assert.NoError(t, err) + + fut := NewFuture(&futureMockRequest{}) + fut.SetResponse(Header{}, bytes.NewReader([]byte{'v', '2'})) + + resp, err := fut.GetResponse() + assert.NoError(t, err) + respConv, ok := resp.(*futureMockResponse) + assert.True(t, ok) + assert.Equal(t, mockResp, respConv) + + data, err := resp.Decode() + assert.NoError(t, err) + assert.Equal(t, []interface{}{uint8('v'), uint8('2')}, data) +} diff --git a/header.go b/header.go new file mode 100644 index 000000000..20a4a465f --- /dev/null +++ b/header.go @@ -0,0 +1,14 @@ +package tarantool + +import "github.com/tarantool/go-iproto" + +// Header is a response header. +type Header struct { + // RequestId is an id of a corresponding request. + RequestId uint32 + // Error is a response error. It could be used + // to check that response has or hasn't an error without decoding. + // Error == ErrorNo (iproto.ER_UNKNOWN) if there is no error. + // Otherwise, it contains an error code from iproto.Error enumeration. + Error iproto.Error +} diff --git a/pool/connection_pool.go b/pool/connection_pool.go index c272310bd..861221290 100644 --- a/pool/connection_pool.go +++ b/pool/connection_pool.go @@ -373,7 +373,7 @@ func (p *ConnectionPool) GetInfo() map[string]ConnectionInfo { // // Deprecated: the method will be removed in the next major version, // use a PingRequest object + Do() instead. -func (p *ConnectionPool) Ping(userMode Mode) (*tarantool.Response, error) { +func (p *ConnectionPool) Ping(userMode Mode) ([]interface{}, error) { conn, err := p.getNextConnection(userMode) if err != nil { return nil, err @@ -388,7 +388,7 @@ func (p *ConnectionPool) Ping(userMode Mode) (*tarantool.Response, error) { // use a SelectRequest object + Do() instead. func (p *ConnectionPool) Select(space, index interface{}, offset, limit uint32, - iterator tarantool.Iter, key interface{}, userMode ...Mode) (*tarantool.Response, error) { + iterator tarantool.Iter, key interface{}, userMode ...Mode) ([]interface{}, error) { conn, err := p.getConnByMode(ANY, userMode) if err != nil { return nil, err @@ -403,7 +403,7 @@ func (p *ConnectionPool) Select(space, index interface{}, // Deprecated: the method will be removed in the next major version, // use an InsertRequest object + Do() instead. func (p *ConnectionPool) Insert(space interface{}, tuple interface{}, - userMode ...Mode) (*tarantool.Response, error) { + userMode ...Mode) ([]interface{}, error) { conn, err := p.getConnByMode(RW, userMode) if err != nil { return nil, err @@ -418,7 +418,7 @@ func (p *ConnectionPool) Insert(space interface{}, tuple interface{}, // Deprecated: the method will be removed in the next major version, // use a ReplaceRequest object + Do() instead. func (p *ConnectionPool) Replace(space interface{}, tuple interface{}, - userMode ...Mode) (*tarantool.Response, error) { + userMode ...Mode) ([]interface{}, error) { conn, err := p.getConnByMode(RW, userMode) if err != nil { return nil, err @@ -433,7 +433,7 @@ func (p *ConnectionPool) Replace(space interface{}, tuple interface{}, // Deprecated: the method will be removed in the next major version, // use a DeleteRequest object + Do() instead. func (p *ConnectionPool) Delete(space, index interface{}, key interface{}, - userMode ...Mode) (*tarantool.Response, error) { + userMode ...Mode) ([]interface{}, error) { conn, err := p.getConnByMode(RW, userMode) if err != nil { return nil, err @@ -448,7 +448,7 @@ 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 interface{}, - ops *tarantool.Operations, userMode ...Mode) (*tarantool.Response, error) { + ops *tarantool.Operations, userMode ...Mode) ([]interface{}, error) { conn, err := p.getConnByMode(RW, userMode) if err != nil { return nil, err @@ -463,7 +463,7 @@ func (p *ConnectionPool) Update(space, index interface{}, key 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 interface{}, - ops *tarantool.Operations, userMode ...Mode) (*tarantool.Response, error) { + ops *tarantool.Operations, userMode ...Mode) ([]interface{}, error) { conn, err := p.getConnByMode(RW, userMode) if err != nil { return nil, err @@ -478,7 +478,7 @@ func (p *ConnectionPool) Upsert(space interface{}, tuple interface{}, // Deprecated: the method will be removed in the next major version, // use a CallRequest object + Do() instead. func (p *ConnectionPool) Call(functionName string, args interface{}, - userMode Mode) (*tarantool.Response, error) { + userMode Mode) ([]interface{}, error) { conn, err := p.getNextConnection(userMode) if err != nil { return nil, err @@ -494,7 +494,7 @@ func (p *ConnectionPool) Call(functionName string, args interface{}, // Deprecated: the method will be removed in the next major version, // use a Call16Request object + Do() instead. func (p *ConnectionPool) Call16(functionName string, args interface{}, - userMode Mode) (*tarantool.Response, error) { + userMode Mode) ([]interface{}, error) { conn, err := p.getNextConnection(userMode) if err != nil { return nil, err @@ -509,7 +509,7 @@ func (p *ConnectionPool) Call16(functionName string, args interface{}, // Deprecated: the method will be removed in the next major version, // use a Call17Request object + Do() instead. func (p *ConnectionPool) Call17(functionName string, args interface{}, - userMode Mode) (*tarantool.Response, error) { + userMode Mode) ([]interface{}, error) { conn, err := p.getNextConnection(userMode) if err != nil { return nil, err @@ -523,7 +523,7 @@ func (p *ConnectionPool) Call17(functionName string, args interface{}, // Deprecated: the method will be removed in the next major version, // use an EvalRequest object + Do() instead. func (p *ConnectionPool) Eval(expr string, args interface{}, - userMode Mode) (*tarantool.Response, error) { + userMode Mode) ([]interface{}, error) { conn, err := p.getNextConnection(userMode) if err != nil { return nil, err @@ -537,7 +537,7 @@ func (p *ConnectionPool) Eval(expr string, args interface{}, // Deprecated: the method will be removed in the next major version, // use an ExecuteRequest object + Do() instead. func (p *ConnectionPool) Execute(expr string, args interface{}, - userMode Mode) (*tarantool.Response, error) { + userMode Mode) ([]interface{}, error) { conn, err := p.getNextConnection(userMode) if err != nil { return nil, err @@ -980,18 +980,15 @@ func (p *ConnectionPool) Do(req tarantool.Request, userMode Mode) *tarantool.Fut // func (p *ConnectionPool) getConnectionRole(conn *tarantool.Connection) (Role, error) { - resp, err := conn.Do(tarantool.NewCallRequest("box.info")).Get() + data, err := conn.Do(tarantool.NewCallRequest("box.info")).Get() if err != nil { return UnknownRole, err } - if resp == nil { - return UnknownRole, ErrIncorrectResponse - } - if len(resp.Data) < 1 { + if len(data) < 1 { return UnknownRole, ErrIncorrectResponse } - instanceStatus, ok := resp.Data[0].(map[interface{}]interface{})["status"] + instanceStatus, ok := data[0].(map[interface{}]interface{})["status"] if !ok { return UnknownRole, ErrIncorrectResponse } @@ -999,7 +996,7 @@ func (p *ConnectionPool) getConnectionRole(conn *tarantool.Connection) (Role, er return UnknownRole, ErrIncorrectStatus } - replicaRole, ok := resp.Data[0].(map[interface{}]interface{})["ro"] + replicaRole, ok := data[0].(map[interface{}]interface{})["ro"] if !ok { return UnknownRole, ErrIncorrectResponse } @@ -1463,7 +1460,7 @@ func (p *ConnectionPool) getConnByMode(defaultMode Mode, } func newErrorFuture(err error) *tarantool.Future { - fut := tarantool.NewFuture() + fut := tarantool.NewFuture(nil) fut.SetError(err) return fut } diff --git a/pool/connection_pool_test.go b/pool/connection_pool_test.go index 15e67b59d..acdc756d3 100644 --- a/pool/connection_pool_test.go +++ b/pool/connection_pool_test.go @@ -814,9 +814,8 @@ func TestCloseGraceful(t *testing.T) { require.ErrorContains(t, err, "can't find healthy instance in pool") // Check that a previous request was successful. - resp, err := fut.Get() + _, err = fut.Get() require.Nilf(t, err, "sleep request no error") - require.NotNilf(t, resp, "sleep response exists") args = test_helpers.CheckStatusesArgs{ ConnPool: connPool, @@ -1141,45 +1140,45 @@ func TestCall(t *testing.T) { defer connPool.Close() // PreferRO - resp, err := connPool.Call("box.info", []interface{}{}, pool.PreferRO) + data, err := connPool.Call("box.info", []interface{}{}, pool.PreferRO) require.Nilf(t, err, "failed to Call") - require.NotNilf(t, resp, "response is nil after Call") - require.GreaterOrEqualf(t, len(resp.Data), 1, "response.Data is empty after Call") + require.NotNilf(t, data, "response is nil after Call") + require.GreaterOrEqualf(t, len(data), 1, "response.Data is empty after Call") - val := resp.Data[0].(map[interface{}]interface{})["ro"] + val := data[0].(map[interface{}]interface{})["ro"] ro, ok := val.(bool) require.Truef(t, ok, "expected `true` with mode `PreferRO`") require.Truef(t, ro, "expected `true` with mode `PreferRO`") // PreferRW - resp, err = connPool.Call("box.info", []interface{}{}, pool.PreferRW) + data, err = connPool.Call("box.info", []interface{}{}, pool.PreferRW) require.Nilf(t, err, "failed to Call") - require.NotNilf(t, resp, "response is nil after Call") - require.GreaterOrEqualf(t, len(resp.Data), 1, "response.Data is empty after Call") + require.NotNilf(t, data, "response is nil after Call") + require.GreaterOrEqualf(t, len(data), 1, "response.Data is empty after Call") - val = resp.Data[0].(map[interface{}]interface{})["ro"] + val = data[0].(map[interface{}]interface{})["ro"] ro, ok = val.(bool) require.Truef(t, ok, "expected `false` with mode `PreferRW`") require.Falsef(t, ro, "expected `false` with mode `PreferRW`") // RO - resp, err = connPool.Call("box.info", []interface{}{}, pool.RO) + data, err = connPool.Call("box.info", []interface{}{}, pool.RO) require.Nilf(t, err, "failed to Call") - require.NotNilf(t, resp, "response is nil after Call") - require.GreaterOrEqualf(t, len(resp.Data), 1, "response.Data is empty after Call") + require.NotNilf(t, data, "response is nil after Call") + require.GreaterOrEqualf(t, len(data), 1, "response.Data is empty after Call") - val = resp.Data[0].(map[interface{}]interface{})["ro"] + val = data[0].(map[interface{}]interface{})["ro"] ro, ok = val.(bool) require.Truef(t, ok, "expected `true` with mode `RO`") require.Truef(t, ro, "expected `true` with mode `RO`") // RW - resp, err = connPool.Call("box.info", []interface{}{}, pool.RW) + data, err = connPool.Call("box.info", []interface{}{}, pool.RW) require.Nilf(t, err, "failed to Call") - require.NotNilf(t, resp, "response is nil after Call") - require.GreaterOrEqualf(t, len(resp.Data), 1, "response.Data is empty after Call") + require.NotNilf(t, data, "response is nil after Call") + require.GreaterOrEqualf(t, len(data), 1, "response.Data is empty after Call") - val = resp.Data[0].(map[interface{}]interface{})["ro"] + val = data[0].(map[interface{}]interface{})["ro"] ro, ok = val.(bool) require.Truef(t, ok, "expected `false` with mode `RW`") require.Falsef(t, ro, "expected `false` with mode `RW`") @@ -1200,45 +1199,45 @@ func TestCall16(t *testing.T) { defer connPool.Close() // PreferRO - resp, err := connPool.Call16("box.info", []interface{}{}, pool.PreferRO) + data, err := connPool.Call16("box.info", []interface{}{}, pool.PreferRO) require.Nilf(t, err, "failed to Call") - require.NotNilf(t, resp, "response is nil after Call") - require.GreaterOrEqualf(t, len(resp.Data), 1, "response.Data is empty after Call") + require.NotNilf(t, data, "response is nil after Call") + require.GreaterOrEqualf(t, len(data), 1, "response.Data is empty after Call") - val := resp.Data[0].([]interface{})[0].(map[interface{}]interface{})["ro"] + val := data[0].([]interface{})[0].(map[interface{}]interface{})["ro"] ro, ok := val.(bool) require.Truef(t, ok, "expected `true` with mode `PreferRO`") require.Truef(t, ro, "expected `true` with mode `PreferRO`") // PreferRW - resp, err = connPool.Call16("box.info", []interface{}{}, pool.PreferRW) + data, err = connPool.Call16("box.info", []interface{}{}, pool.PreferRW) require.Nilf(t, err, "failed to Call") - require.NotNilf(t, resp, "response is nil after Call") - require.GreaterOrEqualf(t, len(resp.Data), 1, "response.Data is empty after Call") + require.NotNilf(t, data, "response is nil after Call") + require.GreaterOrEqualf(t, len(data), 1, "response.Data is empty after Call") - val = resp.Data[0].([]interface{})[0].(map[interface{}]interface{})["ro"] + val = data[0].([]interface{})[0].(map[interface{}]interface{})["ro"] ro, ok = val.(bool) require.Truef(t, ok, "expected `false` with mode `PreferRW`") require.Falsef(t, ro, "expected `false` with mode `PreferRW`") // RO - resp, err = connPool.Call16("box.info", []interface{}{}, pool.RO) + data, err = connPool.Call16("box.info", []interface{}{}, pool.RO) require.Nilf(t, err, "failed to Call") - require.NotNilf(t, resp, "response is nil after Call") - require.GreaterOrEqualf(t, len(resp.Data), 1, "response.Data is empty after Call") + require.NotNilf(t, data, "response is nil after Call") + require.GreaterOrEqualf(t, len(data), 1, "response.Data is empty after Call") - val = resp.Data[0].([]interface{})[0].(map[interface{}]interface{})["ro"] + val = data[0].([]interface{})[0].(map[interface{}]interface{})["ro"] ro, ok = val.(bool) require.Truef(t, ok, "expected `true` with mode `RO`") require.Truef(t, ro, "expected `true` with mode `RO`") // RW - resp, err = connPool.Call16("box.info", []interface{}{}, pool.RW) + data, err = connPool.Call16("box.info", []interface{}{}, pool.RW) require.Nilf(t, err, "failed to Call") - require.NotNilf(t, resp, "response is nil after Call") - require.GreaterOrEqualf(t, len(resp.Data), 1, "response.Data is empty after Call") + require.NotNilf(t, data, "response is nil after Call") + require.GreaterOrEqualf(t, len(data), 1, "response.Data is empty after Call") - val = resp.Data[0].([]interface{})[0].(map[interface{}]interface{})["ro"] + val = data[0].([]interface{})[0].(map[interface{}]interface{})["ro"] ro, ok = val.(bool) require.Truef(t, ok, "expected `false` with mode `RW`") require.Falsef(t, ro, "expected `false` with mode `RW`") @@ -1259,45 +1258,45 @@ func TestCall17(t *testing.T) { defer connPool.Close() // PreferRO - resp, err := connPool.Call17("box.info", []interface{}{}, pool.PreferRO) + data, err := connPool.Call17("box.info", []interface{}{}, pool.PreferRO) require.Nilf(t, err, "failed to Call") - require.NotNilf(t, resp, "response is nil after Call") - require.GreaterOrEqualf(t, len(resp.Data), 1, "response.Data is empty after Call") + require.NotNilf(t, data, "response is nil after Call") + require.GreaterOrEqualf(t, len(data), 1, "response.Data is empty after Call") - val := resp.Data[0].(map[interface{}]interface{})["ro"] + val := data[0].(map[interface{}]interface{})["ro"] ro, ok := val.(bool) require.Truef(t, ok, "expected `true` with mode `PreferRO`") require.Truef(t, ro, "expected `true` with mode `PreferRO`") // PreferRW - resp, err = connPool.Call17("box.info", []interface{}{}, pool.PreferRW) + data, err = connPool.Call17("box.info", []interface{}{}, pool.PreferRW) require.Nilf(t, err, "failed to Call") - require.NotNilf(t, resp, "response is nil after Call") - require.GreaterOrEqualf(t, len(resp.Data), 1, "response.Data is empty after Call") + require.NotNilf(t, data, "response is nil after Call") + require.GreaterOrEqualf(t, len(data), 1, "response.Data is empty after Call") - val = resp.Data[0].(map[interface{}]interface{})["ro"] + val = data[0].(map[interface{}]interface{})["ro"] ro, ok = val.(bool) require.Truef(t, ok, "expected `false` with mode `PreferRW`") require.Falsef(t, ro, "expected `false` with mode `PreferRW`") // RO - resp, err = connPool.Call17("box.info", []interface{}{}, pool.RO) + data, err = connPool.Call17("box.info", []interface{}{}, pool.RO) require.Nilf(t, err, "failed to Call") - require.NotNilf(t, resp, "response is nil after Call") - require.GreaterOrEqualf(t, len(resp.Data), 1, "response.Data is empty after Call") + require.NotNilf(t, data, "response is nil after Call") + require.GreaterOrEqualf(t, len(data), 1, "response.Data is empty after Call") - val = resp.Data[0].(map[interface{}]interface{})["ro"] + val = data[0].(map[interface{}]interface{})["ro"] ro, ok = val.(bool) require.Truef(t, ok, "expected `true` with mode `RO`") require.Truef(t, ro, "expected `true` with mode `RO`") // RW - resp, err = connPool.Call17("box.info", []interface{}{}, pool.RW) + data, err = connPool.Call17("box.info", []interface{}{}, pool.RW) require.Nilf(t, err, "failed to Call") - require.NotNilf(t, resp, "response is nil after Call") - require.GreaterOrEqualf(t, len(resp.Data), 1, "response.Data is empty after Call") + require.NotNilf(t, data, "response is nil after Call") + require.GreaterOrEqualf(t, len(data), 1, "response.Data is empty after Call") - val = resp.Data[0].(map[interface{}]interface{})["ro"] + val = data[0].(map[interface{}]interface{})["ro"] ro, ok = val.(bool) require.Truef(t, ok, "expected `false` with mode `RW`") require.Falsef(t, ro, "expected `false` with mode `RW`") @@ -1318,42 +1317,42 @@ func TestEval(t *testing.T) { defer connPool.Close() // PreferRO - resp, err := connPool.Eval("return box.info().ro", []interface{}{}, pool.PreferRO) + data, err := connPool.Eval("return box.info().ro", []interface{}{}, pool.PreferRO) require.Nilf(t, err, "failed to Eval") - require.NotNilf(t, resp, "response is nil after Eval") - require.GreaterOrEqualf(t, len(resp.Data), 1, "response.Data is empty after Eval") + require.NotNilf(t, data, "response is nil after Eval") + require.GreaterOrEqualf(t, len(data), 1, "response.Data is empty after Eval") - val, ok := resp.Data[0].(bool) + val, ok := data[0].(bool) require.Truef(t, ok, "expected `true` with mode `PreferRO`") require.Truef(t, val, "expected `true` with mode `PreferRO`") // PreferRW - resp, err = connPool.Eval("return box.info().ro", []interface{}{}, pool.PreferRW) + data, err = connPool.Eval("return box.info().ro", []interface{}{}, pool.PreferRW) require.Nilf(t, err, "failed to Eval") - require.NotNilf(t, resp, "response is nil after Eval") - require.GreaterOrEqualf(t, len(resp.Data), 1, "response.Data is empty after Eval") + require.NotNilf(t, data, "response is nil after Eval") + require.GreaterOrEqualf(t, len(data), 1, "response.Data is empty after Eval") - val, ok = resp.Data[0].(bool) + val, ok = data[0].(bool) require.Truef(t, ok, "expected `false` with mode `PreferRW`") require.Falsef(t, val, "expected `false` with mode `PreferRW`") // RO - resp, err = connPool.Eval("return box.info().ro", []interface{}{}, pool.RO) + data, err = connPool.Eval("return box.info().ro", []interface{}{}, pool.RO) require.Nilf(t, err, "failed to Eval") - require.NotNilf(t, resp, "response is nil after Eval") - require.GreaterOrEqualf(t, len(resp.Data), 1, "response.Data is empty after Eval") + require.NotNilf(t, data, "response is nil after Eval") + require.GreaterOrEqualf(t, len(data), 1, "response.Data is empty after Eval") - val, ok = resp.Data[0].(bool) + val, ok = data[0].(bool) require.Truef(t, ok, "expected `true` with mode `RO`") require.Truef(t, val, "expected `true` with mode `RO`") // RW - resp, err = connPool.Eval("return box.info().ro", []interface{}{}, pool.RW) + data, err = connPool.Eval("return box.info().ro", []interface{}{}, pool.RW) require.Nilf(t, err, "failed to Eval") - require.NotNilf(t, resp, "response is nil after Eval") - require.GreaterOrEqualf(t, len(resp.Data), 1, "response.Data is empty after Eval") + require.NotNilf(t, data, "response is nil after Eval") + require.GreaterOrEqualf(t, len(data), 1, "response.Data is empty after Eval") - val, ok = resp.Data[0].(bool) + val, ok = data[0].(bool) require.Truef(t, ok, "expected `false` with mode `RW`") require.Falsef(t, val, "expected `false` with mode `RW`") } @@ -1399,11 +1398,11 @@ func TestExecute(t *testing.T) { request := "SELECT NAME0, NAME1 FROM SQL_TEST WHERE NAME0 == 1;" // Execute - resp, err := connPool.Execute(request, []interface{}{}, pool.ANY) + data, err := connPool.Execute(request, []interface{}{}, pool.ANY) require.Nilf(t, err, "failed to Execute") - require.NotNilf(t, resp, "response is nil after Execute") - require.GreaterOrEqualf(t, len(resp.Data), 1, "response.Data is empty after Execute") - require.Equalf(t, len(resp.Data[0].([]interface{})), 2, "unexpected response") + require.NotNilf(t, data, "response is nil after Execute") + require.GreaterOrEqualf(t, len(data), 1, "response.Data is empty after Execute") + require.Equalf(t, len(data[0].([]interface{})), 2, "unexpected response") // ExecuteTyped mem := []Member{} @@ -1414,11 +1413,10 @@ func TestExecute(t *testing.T) { // ExecuteAsync fut := connPool.ExecuteAsync(request, []interface{}{}, pool.ANY) - resp, err = fut.Get() + data, err = fut.Get() require.Nilf(t, err, "failed to ExecuteAsync") - require.NotNilf(t, resp, "response is nil after ExecuteAsync") - require.GreaterOrEqualf(t, len(resp.Data), 1, "response.Data is empty after ExecuteAsync") - require.Equalf(t, len(resp.Data[0].([]interface{})), 2, "unexpected response") + require.GreaterOrEqualf(t, len(data), 1, "response.Data is empty after ExecuteAsync") + require.Equalf(t, len(data[0].([]interface{})), 2, "unexpected response") } func TestRoundRobinStrategy(t *testing.T) { @@ -1840,12 +1838,12 @@ func TestInsert(t *testing.T) { defer connPool.Close() // Mode is `RW` by default, we have only one RW instance (servers[2]) - resp, err := connPool.Insert(spaceName, []interface{}{"rw_insert_key", "rw_insert_value"}) + data, err := connPool.Insert(spaceName, []interface{}{"rw_insert_key", "rw_insert_value"}) require.Nilf(t, err, "failed to Insert") - require.NotNilf(t, resp, "response is nil after Insert") - require.Equalf(t, len(resp.Data), 1, "response Body len != 1 after Insert") + require.NotNilf(t, data, "response is nil after Insert") + require.Equalf(t, len(data), 1, "response Body len != 1 after Insert") - tpl, ok := resp.Data[0].([]interface{}) + tpl, ok := data[0].([]interface{}) require.Truef(t, ok, "unexpected body of Insert") require.Equalf(t, 2, len(tpl), "unexpected body of Insert") @@ -1867,12 +1865,11 @@ func TestInsert(t *testing.T) { Limit(1). Iterator(tarantool.IterEq). Key([]interface{}{"rw_insert_key"}) - resp, err = conn.Do(sel).Get() + data, err = conn.Do(sel).Get() require.Nilf(t, err, "failed to Select") - require.NotNilf(t, resp, "response is nil after Select") - require.Equalf(t, len(resp.Data), 1, "response Body len != 1 after Select") + require.Equalf(t, len(data), 1, "response Body len != 1 after Select") - tpl, ok = resp.Data[0].([]interface{}) + tpl, ok = data[0].([]interface{}) require.Truef(t, ok, "unexpected body of Select") require.Equalf(t, 2, len(tpl), "unexpected body of Select") @@ -1885,13 +1882,13 @@ func TestInsert(t *testing.T) { require.Equalf(t, "rw_insert_value", value, "unexpected body of Select (1)") // PreferRW - resp, err = connPool.Insert(spaceName, + data, err = connPool.Insert(spaceName, []interface{}{"preferRW_insert_key", "preferRW_insert_value"}) require.Nilf(t, err, "failed to Insert") - require.NotNilf(t, resp, "response is nil after Insert") - require.Equalf(t, len(resp.Data), 1, "response Body len != 1 after Insert") + require.NotNilf(t, data, "response is nil after Insert") + require.Equalf(t, len(data), 1, "response Body len != 1 after Insert") - tpl, ok = resp.Data[0].([]interface{}) + tpl, ok = data[0].([]interface{}) require.Truef(t, ok, "unexpected body of Insert") require.Equalf(t, 2, len(tpl), "unexpected body of Insert") @@ -1908,12 +1905,11 @@ func TestInsert(t *testing.T) { Limit(1). Iterator(tarantool.IterEq). Key([]interface{}{"preferRW_insert_key"}) - resp, err = conn.Do(sel).Get() + data, err = conn.Do(sel).Get() require.Nilf(t, err, "failed to Select") - require.NotNilf(t, resp, "response is nil after Select") - require.Equalf(t, len(resp.Data), 1, "response Body len != 1 after Select") + require.Equalf(t, len(data), 1, "response Body len != 1 after Select") - tpl, ok = resp.Data[0].([]interface{}) + tpl, ok = data[0].([]interface{}) require.Truef(t, ok, "unexpected body of Select") require.Equalf(t, 2, len(tpl), "unexpected body of Select") @@ -1946,12 +1942,11 @@ func TestDelete(t *testing.T) { defer conn.Close() ins := tarantool.NewInsertRequest(spaceNo).Tuple([]interface{}{"delete_key", "delete_value"}) - resp, err := conn.Do(ins).Get() + data, err := conn.Do(ins).Get() require.Nilf(t, err, "failed to Insert") - require.NotNilf(t, resp, "response is nil after Insert") - require.Equalf(t, len(resp.Data), 1, "response Body len != 1 after Insert") + require.Equalf(t, len(data), 1, "response Body len != 1 after Insert") - tpl, ok := resp.Data[0].([]interface{}) + tpl, ok := data[0].([]interface{}) require.Truef(t, ok, "unexpected body of Insert") require.Equalf(t, 2, len(tpl), "unexpected body of Insert") @@ -1964,12 +1959,12 @@ func TestDelete(t *testing.T) { require.Equalf(t, "delete_value", value, "unexpected body of Insert (1)") // Mode is `RW` by default, we have only one RW instance (servers[2]) - resp, err = connPool.Delete(spaceName, indexNo, []interface{}{"delete_key"}) + data, err = connPool.Delete(spaceName, indexNo, []interface{}{"delete_key"}) require.Nilf(t, err, "failed to Delete") - require.NotNilf(t, resp, "response is nil after Delete") - require.Equalf(t, len(resp.Data), 1, "response Body len != 1 after Delete") + require.NotNilf(t, data, "response is nil after Delete") + require.Equalf(t, len(data), 1, "response Body len != 1 after Delete") - tpl, ok = resp.Data[0].([]interface{}) + tpl, ok = data[0].([]interface{}) require.Truef(t, ok, "unexpected body of Delete") require.Equalf(t, 2, len(tpl), "unexpected body of Delete") @@ -1986,10 +1981,9 @@ func TestDelete(t *testing.T) { Limit(1). Iterator(tarantool.IterEq). Key([]interface{}{"delete_key"}) - resp, err = conn.Do(sel).Get() + data, err = conn.Do(sel).Get() require.Nilf(t, err, "failed to Select") - require.NotNilf(t, resp, "response is nil after Select") - require.Equalf(t, 0, len(resp.Data), "response Body len != 0 after Select") + require.Equalf(t, 0, len(data), "response Body len != 0 after Select") } func TestUpsert(t *testing.T) { @@ -2012,23 +2006,22 @@ func TestUpsert(t *testing.T) { defer conn.Close() // Mode is `RW` by default, we have only one RW instance (servers[2]) - resp, err := connPool.Upsert(spaceName, + data, err := connPool.Upsert(spaceName, []interface{}{"upsert_key", "upsert_value"}, tarantool.NewOperations().Assign(1, "new_value")) require.Nilf(t, err, "failed to Upsert") - require.NotNilf(t, resp, "response is nil after Upsert") + require.NotNilf(t, data, "response is nil after Upsert") sel := tarantool.NewSelectRequest(spaceNo). Index(indexNo). Limit(1). Iterator(tarantool.IterEq). Key([]interface{}{"upsert_key"}) - resp, err = conn.Do(sel).Get() + data, err = conn.Do(sel).Get() require.Nilf(t, err, "failed to Select") - require.NotNilf(t, resp, "response is nil after Select") - require.Equalf(t, len(resp.Data), 1, "response Body len != 1 after Select") + require.Equalf(t, len(data), 1, "response Body len != 1 after Select") - tpl, ok := resp.Data[0].([]interface{}) + tpl, ok := data[0].([]interface{}) require.Truef(t, ok, "unexpected body of Select") require.Equalf(t, 2, len(tpl), "unexpected body of Select") @@ -2041,19 +2034,18 @@ func TestUpsert(t *testing.T) { require.Equalf(t, "upsert_value", value, "unexpected body of Select (1)") // PreferRW - resp, err = connPool.Upsert( + data, err = connPool.Upsert( spaceName, []interface{}{"upsert_key", "upsert_value"}, tarantool.NewOperations().Assign(1, "new_value"), pool.PreferRW) require.Nilf(t, err, "failed to Upsert") - require.NotNilf(t, resp, "response is nil after Upsert") + require.NotNilf(t, data, "response is nil after Upsert") - resp, err = conn.Do(sel).Get() + data, err = conn.Do(sel).Get() require.Nilf(t, err, "failed to Select") - require.NotNilf(t, resp, "response is nil after Select") - require.Equalf(t, len(resp.Data), 1, "response Body len != 1 after Select") + require.Equalf(t, len(data), 1, "response Body len != 1 after Select") - tpl, ok = resp.Data[0].([]interface{}) + tpl, ok = data[0].([]interface{}) require.Truef(t, ok, "unexpected body of Select") require.Equalf(t, 2, len(tpl), "unexpected body of Select") @@ -2087,12 +2079,11 @@ func TestUpdate(t *testing.T) { ins := tarantool.NewInsertRequest(spaceNo). Tuple([]interface{}{"update_key", "update_value"}) - resp, err := conn.Do(ins).Get() + data, err := conn.Do(ins).Get() require.Nilf(t, err, "failed to Insert") - require.NotNilf(t, resp, "response is nil after Insert") - require.Equalf(t, len(resp.Data), 1, "response Body len != 1 after Insert") + require.Equalf(t, len(data), 1, "response Body len != 1 after Insert") - tpl, ok := resp.Data[0].([]interface{}) + tpl, ok := data[0].([]interface{}) require.Truef(t, ok, "unexpected body of Insert") require.Equalf(t, 2, len(tpl), "unexpected body of Insert") @@ -2105,7 +2096,7 @@ func TestUpdate(t *testing.T) { require.Equalf(t, "update_value", value, "unexpected body of Insert (1)") // Mode is `RW` by default, we have only one RW instance (servers[2]) - resp, err = connPool.Update(spaceName, indexNo, + resp, err := connPool.Update(spaceName, indexNo, []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") @@ -2115,12 +2106,11 @@ func TestUpdate(t *testing.T) { Limit(1). Iterator(tarantool.IterEq). Key([]interface{}{"update_key"}) - resp, err = conn.Do(sel).Get() + data, err = conn.Do(sel).Get() require.Nilf(t, err, "failed to Select") - require.NotNilf(t, resp, "response is nil after Select") - require.Equalf(t, len(resp.Data), 1, "response Body len != 1 after Select") + require.Equalf(t, len(data), 1, "response Body len != 1 after Select") - tpl, ok = resp.Data[0].([]interface{}) + tpl, ok = data[0].([]interface{}) require.Truef(t, ok, "unexpected body of Select") require.Equalf(t, 2, len(tpl), "unexpected body of Select") @@ -2140,12 +2130,11 @@ func TestUpdate(t *testing.T) { require.Nilf(t, err, "failed to Update") require.NotNilf(t, resp, "response is nil after Update") - resp, err = conn.Do(sel).Get() + data, err = conn.Do(sel).Get() require.Nilf(t, err, "failed to Select") - require.NotNilf(t, resp, "response is nil after Select") - require.Equalf(t, len(resp.Data), 1, "response Body len != 1 after Select") + require.Equalf(t, len(data), 1, "response Body len != 1 after Select") - tpl, ok = resp.Data[0].([]interface{}) + tpl, ok = data[0].([]interface{}) require.Truef(t, ok, "unexpected body of Select") require.Equalf(t, 2, len(tpl), "unexpected body of Select") @@ -2179,12 +2168,11 @@ func TestReplace(t *testing.T) { ins := tarantool.NewInsertRequest(spaceNo). Tuple([]interface{}{"replace_key", "replace_value"}) - resp, err := conn.Do(ins).Get() + data, err := conn.Do(ins).Get() require.Nilf(t, err, "failed to Insert") - require.NotNilf(t, resp, "response is nil after Insert") - require.Equalf(t, len(resp.Data), 1, "response Body len != 1 after Insert") + require.Equalf(t, len(data), 1, "response Body len != 1 after Insert") - tpl, ok := resp.Data[0].([]interface{}) + tpl, ok := data[0].([]interface{}) require.Truef(t, ok, "unexpected body of Insert") require.Equalf(t, 2, len(tpl), "unexpected body of Insert") @@ -2197,7 +2185,7 @@ func TestReplace(t *testing.T) { require.Equalf(t, "replace_value", value, "unexpected body of Insert (1)") // Mode is `RW` by default, we have only one RW instance (servers[2]) - resp, err = connPool.Replace(spaceNo, []interface{}{"new_key", "new_value"}) + resp, err := connPool.Replace(spaceNo, []interface{}{"new_key", "new_value"}) require.Nilf(t, err, "failed to Replace") require.NotNilf(t, resp, "response is nil after Replace") @@ -2206,12 +2194,11 @@ func TestReplace(t *testing.T) { Limit(1). Iterator(tarantool.IterEq). Key([]interface{}{"new_key"}) - resp, err = conn.Do(sel).Get() + data, err = conn.Do(sel).Get() require.Nilf(t, err, "failed to Select") - require.NotNilf(t, resp, "response is nil after Select") - require.Equalf(t, len(resp.Data), 1, "response Body len != 1 after Select") + require.Equalf(t, len(data), 1, "response Body len != 1 after Select") - tpl, ok = resp.Data[0].([]interface{}) + tpl, ok = data[0].([]interface{}) require.Truef(t, ok, "unexpected body of Select") require.Equalf(t, 2, len(tpl), "unexpected body of Select") @@ -2228,12 +2215,11 @@ func TestReplace(t *testing.T) { require.Nilf(t, err, "failed to Replace") require.NotNilf(t, resp, "response is nil after Replace") - resp, err = conn.Do(sel).Get() + data, err = conn.Do(sel).Get() require.Nilf(t, err, "failed to Select") - require.NotNilf(t, resp, "response is nil after Select") - require.Equalf(t, len(resp.Data), 1, "response Body len != 1 after Select") + require.Equalf(t, len(data), 1, "response Body len != 1 after Select") - tpl, ok = resp.Data[0].([]interface{}) + tpl, ok = data[0].([]interface{}) require.Truef(t, ok, "unexpected body of Select") require.Equalf(t, 2, len(tpl), "unexpected body of Select") @@ -2282,12 +2268,12 @@ func TestSelect(t *testing.T) { require.Nil(t, err) //default: ANY - resp, err := connPool.Select(spaceNo, indexNo, 0, 1, tarantool.IterEq, anyKey) + data, err := connPool.Select(spaceNo, indexNo, 0, 1, tarantool.IterEq, anyKey) require.Nilf(t, err, "failed to Select") - require.NotNilf(t, resp, "response is nil after Select") - require.Equalf(t, len(resp.Data), 1, "response Body len != 1 after Select") + require.NotNilf(t, data, "response is nil after Select") + require.Equalf(t, len(data), 1, "response Body len != 1 after Select") - tpl, ok := resp.Data[0].([]interface{}) + tpl, ok := data[0].([]interface{}) require.Truef(t, ok, "unexpected body of Select") require.Equalf(t, 2, len(tpl), "unexpected body of Select") @@ -2300,12 +2286,12 @@ func TestSelect(t *testing.T) { require.Equalf(t, "any_select_value", value, "unexpected body of Select (1)") // PreferRO - resp, err = connPool.Select(spaceNo, indexNo, 0, 1, tarantool.IterEq, roKey, pool.PreferRO) + data, err = connPool.Select(spaceNo, indexNo, 0, 1, tarantool.IterEq, roKey, pool.PreferRO) require.Nilf(t, err, "failed to Select") - require.NotNilf(t, resp, "response is nil after Select") - require.Equalf(t, len(resp.Data), 1, "response Body len != 1 after Select") + require.NotNilf(t, data, "response is nil after Select") + require.Equalf(t, len(data), 1, "response Body len != 1 after Select") - tpl, ok = resp.Data[0].([]interface{}) + tpl, ok = data[0].([]interface{}) require.Truef(t, ok, "unexpected body of Select") require.Equalf(t, 2, len(tpl), "unexpected body of Select") @@ -2314,12 +2300,12 @@ func TestSelect(t *testing.T) { require.Equalf(t, "ro_select_key", key, "unexpected body of Select (0)") // PreferRW - resp, err = connPool.Select(spaceNo, indexNo, 0, 1, tarantool.IterEq, rwKey, pool.PreferRW) + data, err = connPool.Select(spaceNo, indexNo, 0, 1, tarantool.IterEq, rwKey, pool.PreferRW) require.Nilf(t, err, "failed to Select") - require.NotNilf(t, resp, "response is nil after Select") - require.Equalf(t, len(resp.Data), 1, "response Body len != 1 after Select") + require.NotNilf(t, data, "response is nil after Select") + require.Equalf(t, len(data), 1, "response Body len != 1 after Select") - tpl, ok = resp.Data[0].([]interface{}) + tpl, ok = data[0].([]interface{}) require.Truef(t, ok, "unexpected body of Select") require.Equalf(t, 2, len(tpl), "unexpected body of Select") @@ -2332,12 +2318,12 @@ func TestSelect(t *testing.T) { require.Equalf(t, "rw_select_value", value, "unexpected body of Select (1)") // RO - resp, err = connPool.Select(spaceNo, indexNo, 0, 1, tarantool.IterEq, roKey, pool.RO) + data, err = connPool.Select(spaceNo, indexNo, 0, 1, tarantool.IterEq, roKey, pool.RO) require.Nilf(t, err, "failed to Select") - require.NotNilf(t, resp, "response is nil after Select") - require.Equalf(t, len(resp.Data), 1, "response Body len != 1 after Select") + require.NotNilf(t, data, "response is nil after Select") + require.Equalf(t, len(data), 1, "response Body len != 1 after Select") - tpl, ok = resp.Data[0].([]interface{}) + tpl, ok = data[0].([]interface{}) require.Truef(t, ok, "unexpected body of Select") require.Equalf(t, 2, len(tpl), "unexpected body of Select") @@ -2350,12 +2336,12 @@ func TestSelect(t *testing.T) { require.Equalf(t, "ro_select_value", value, "unexpected body of Select (1)") // RW - resp, err = connPool.Select(spaceNo, indexNo, 0, 1, tarantool.IterEq, rwKey, pool.RW) + data, err = connPool.Select(spaceNo, indexNo, 0, 1, tarantool.IterEq, rwKey, pool.RW) require.Nilf(t, err, "failed to Select") - require.NotNilf(t, resp, "response is nil after Select") - require.Equalf(t, len(resp.Data), 1, "response Body len != 1 after Select") + require.NotNilf(t, data, "response is nil after Select") + require.Equalf(t, len(data), 1, "response Body len != 1 after Select") - tpl, ok = resp.Data[0].([]interface{}) + tpl, ok = data[0].([]interface{}) require.Truef(t, ok, "unexpected body of Select") require.Equalf(t, 2, len(tpl), "unexpected body of Select") @@ -2383,29 +2369,28 @@ func TestPing(t *testing.T) { defer connPool.Close() // ANY - resp, err := connPool.Ping(pool.ANY) + data, err := connPool.Ping(pool.ANY) require.Nilf(t, err, "failed to Ping") - require.NotNilf(t, resp, "response is nil after Ping") + require.Nilf(t, data, "response data is not nil after Ping") // RW - resp, err = connPool.Ping(pool.RW) + data, err = connPool.Ping(pool.RW) require.Nilf(t, err, "failed to Ping") - require.NotNilf(t, resp, "response is nil after Ping") + require.Nilf(t, data, "response data is not nil after Ping") // RO - resp, err = connPool.Ping(pool.RO) + _, err = connPool.Ping(pool.RO) require.Nilf(t, err, "failed to Ping") - require.NotNilf(t, resp, "response is nil after Ping") // PreferRW - resp, err = connPool.Ping(pool.PreferRW) + data, err = connPool.Ping(pool.PreferRW) require.Nilf(t, err, "failed to Ping") - require.NotNilf(t, resp, "response is nil after Ping") + require.Nilf(t, data, "response data is not nil after Ping") // PreferRO - resp, err = connPool.Ping(pool.PreferRO) + data, err = connPool.Ping(pool.PreferRO) require.Nilf(t, err, "failed to Ping") - require.NotNilf(t, resp, "response is nil after Ping") + require.Nilf(t, data, "response data is not nil after Ping") } func TestDo(t *testing.T) { @@ -2424,29 +2409,24 @@ func TestDo(t *testing.T) { req := tarantool.NewPingRequest() // ANY - resp, err := connPool.Do(req, pool.ANY).Get() + _, err = connPool.Do(req, pool.ANY).Get() require.Nilf(t, err, "failed to Ping") - require.NotNilf(t, resp, "response is nil after Ping") // RW - resp, err = connPool.Do(req, pool.RW).Get() + _, err = connPool.Do(req, pool.RW).Get() require.Nilf(t, err, "failed to Ping") - require.NotNilf(t, resp, "response is nil after Ping") // RO - resp, err = connPool.Do(req, pool.RO).Get() + _, err = connPool.Do(req, pool.RO).Get() require.Nilf(t, err, "failed to Ping") - require.NotNilf(t, resp, "response is nil after Ping") // PreferRW - resp, err = connPool.Do(req, pool.PreferRW).Get() + _, err = connPool.Do(req, pool.PreferRW).Get() require.Nilf(t, err, "failed to Ping") - require.NotNilf(t, resp, "response is nil after Ping") // PreferRO - resp, err = connPool.Do(req, pool.PreferRO).Get() + _, err = connPool.Do(req, pool.PreferRO).Get() require.Nilf(t, err, "failed to Ping") - require.NotNilf(t, resp, "response is nil after Ping") } func TestDo_concurrent(t *testing.T) { @@ -2503,34 +2483,40 @@ func TestNewPrepared(t *testing.T) { executeReq := tarantool.NewExecutePreparedRequest(stmt) unprepareReq := tarantool.NewUnprepareRequest(stmt) - resp, err := connPool.Do(executeReq.Args([]interface{}{1, "test"}), pool.ANY).Get() + resp, err := connPool.Do(executeReq.Args([]interface{}{1, "test"}), pool.ANY).GetResponse() if err != nil { t.Fatalf("failed to execute prepared: %v", err) } if resp == nil { t.Fatalf("nil response") } - if resp.Code != tarantool.OkCode { - t.Fatalf("failed to execute prepared: code %d", resp.Code) + data, err := resp.Decode() + if err != nil { + t.Fatalf("failed to Decode: %s", err.Error()) } - if reflect.DeepEqual(resp.Data[0], []interface{}{1, "test"}) { + if reflect.DeepEqual(data[0], []interface{}{1, "test"}) { t.Error("Select with named arguments failed") } - if resp.MetaData[0].FieldType != "unsigned" || - resp.MetaData[0].FieldName != "NAME0" || - resp.MetaData[1].FieldType != "string" || - resp.MetaData[1].FieldName != "NAME1" { + prepResp, ok := resp.(*tarantool.ExecuteResponse) + if !ok { + t.Fatalf("Not a Prepare response") + } + metaData, err := prepResp.MetaData() + if err != nil { + t.Errorf("Error while getting MetaData: %s", err.Error()) + } + if metaData[0].FieldType != "unsigned" || + metaData[0].FieldName != "NAME0" || + metaData[1].FieldType != "string" || + metaData[1].FieldName != "NAME1" { t.Error("Wrong metadata") } // the second argument for unprepare request is unused - it already belongs to some connection - resp, err = connPool.Do(unprepareReq, pool.ANY).Get() + _, err = connPool.Do(unprepareReq, pool.ANY).Get() if err != nil { t.Errorf("failed to unprepare prepared statement: %v", err) } - if resp.Code != tarantool.OkCode { - t.Errorf("failed to unprepare prepared statement: code %d", resp.Code) - } _, err = connPool.Do(unprepareReq, pool.ANY).Get() if err == nil { @@ -2562,7 +2548,7 @@ func TestDoWithStrangerConn(t *testing.T) { defer connPool.Close() - req := test_helpers.NewStrangerRequest() + req := test_helpers.NewMockRequest() _, err = connPool.Do(req, pool.ANY).Get() if err == nil { @@ -2575,7 +2561,6 @@ func TestDoWithStrangerConn(t *testing.T) { func TestStream_Commit(t *testing.T) { var req tarantool.Request - var resp *tarantool.Response var err error test_helpers.SkipIfStreamsUnsupported(t) @@ -2598,18 +2583,14 @@ func TestStream_Commit(t *testing.T) { // Begin transaction req = tarantool.NewBeginRequest() - resp, err = stream.Do(req).Get() + _, err = stream.Do(req).Get() require.Nilf(t, err, "failed to Begin") - require.NotNilf(t, resp, "response is nil after Begin") - require.Equalf(t, tarantool.OkCode, resp.Code, "failed to Begin: wrong code returned") // Insert in stream req = tarantool.NewInsertRequest(spaceName). Tuple([]interface{}{"commit_key", "commit_value"}) - resp, err = stream.Do(req).Get() + _, err = stream.Do(req).Get() require.Nilf(t, err, "failed to Insert") - require.NotNilf(t, resp, "response is nil after Insert") - require.Equalf(t, tarantool.OkCode, resp.Code, "failed to Insert: wrong code returned") // Connect to servers[2] to check if tuple // was inserted outside of stream on RW instance @@ -2625,18 +2606,16 @@ func TestStream_Commit(t *testing.T) { Limit(1). Iterator(tarantool.IterEq). Key([]interface{}{"commit_key"}) - resp, err = conn.Do(selectReq).Get() + data, err := conn.Do(selectReq).Get() require.Nilf(t, err, "failed to Select") - require.NotNilf(t, resp, "response is nil after Select") - require.Equalf(t, 0, len(resp.Data), "response Data len != 0") + require.Equalf(t, 0, len(data), "response Data len != 0") // Select in stream - resp, err = stream.Do(selectReq).Get() + data, err = stream.Do(selectReq).Get() require.Nilf(t, err, "failed to Select") - require.NotNilf(t, resp, "response is nil after Select") - require.Equalf(t, 1, len(resp.Data), "response Body len != 1 after Select") + require.Equalf(t, 1, len(data), "response Body len != 1 after Select") - tpl, ok := resp.Data[0].([]interface{}) + tpl, ok := data[0].([]interface{}) require.Truef(t, ok, "unexpected body of Select") require.Equalf(t, 2, len(tpl), "unexpected body of Select") @@ -2650,18 +2629,15 @@ func TestStream_Commit(t *testing.T) { // Commit transaction req = tarantool.NewCommitRequest() - resp, err = stream.Do(req).Get() + _, err = stream.Do(req).Get() require.Nilf(t, err, "failed to Commit") - require.NotNilf(t, resp, "response is nil after Commit") - require.Equalf(t, tarantool.OkCode, resp.Code, "failed to Commit: wrong code returned") // Select outside of transaction - resp, err = conn.Do(selectReq).Get() + data, err = conn.Do(selectReq).Get() require.Nilf(t, err, "failed to Select") - require.NotNilf(t, resp, "response is nil after Select") - require.Equalf(t, len(resp.Data), 1, "response Body len != 1 after Select") + require.Equalf(t, len(data), 1, "response Body len != 1 after Select") - tpl, ok = resp.Data[0].([]interface{}) + tpl, ok = data[0].([]interface{}) require.Truef(t, ok, "unexpected body of Select") require.Equalf(t, 2, len(tpl), "unexpected body of Select") @@ -2676,7 +2652,6 @@ func TestStream_Commit(t *testing.T) { func TestStream_Rollback(t *testing.T) { var req tarantool.Request - var resp *tarantool.Response var err error test_helpers.SkipIfStreamsUnsupported(t) @@ -2699,18 +2674,14 @@ func TestStream_Rollback(t *testing.T) { // Begin transaction req = tarantool.NewBeginRequest() - resp, err = stream.Do(req).Get() + _, err = stream.Do(req).Get() require.Nilf(t, err, "failed to Begin") - require.NotNilf(t, resp, "response is nil after Begin") - require.Equalf(t, tarantool.OkCode, resp.Code, "failed to Begin: wrong code returned") // Insert in stream req = tarantool.NewInsertRequest(spaceName). Tuple([]interface{}{"rollback_key", "rollback_value"}) - resp, err = stream.Do(req).Get() + _, err = stream.Do(req).Get() require.Nilf(t, err, "failed to Insert") - require.NotNilf(t, resp, "response is nil after Insert") - require.Equalf(t, tarantool.OkCode, resp.Code, "failed to Insert: wrong code returned") // Connect to servers[2] to check if tuple // was not inserted outside of stream on RW instance @@ -2725,18 +2696,16 @@ func TestStream_Rollback(t *testing.T) { Limit(1). Iterator(tarantool.IterEq). Key([]interface{}{"rollback_key"}) - resp, err = conn.Do(selectReq).Get() + data, err := conn.Do(selectReq).Get() require.Nilf(t, err, "failed to Select") - require.NotNilf(t, resp, "response is nil after Select") - require.Equalf(t, 0, len(resp.Data), "response Data len != 0") + require.Equalf(t, 0, len(data), "response Data len != 0") // Select in stream - resp, err = stream.Do(selectReq).Get() + data, err = stream.Do(selectReq).Get() require.Nilf(t, err, "failed to Select") - require.NotNilf(t, resp, "response is nil after Select") - require.Equalf(t, 1, len(resp.Data), "response Body len != 1 after Select") + require.Equalf(t, 1, len(data), "response Body len != 1 after Select") - tpl, ok := resp.Data[0].([]interface{}) + tpl, ok := data[0].([]interface{}) require.Truef(t, ok, "unexpected body of Select") require.Equalf(t, 2, len(tpl), "unexpected body of Select") @@ -2750,27 +2719,22 @@ func TestStream_Rollback(t *testing.T) { // Rollback transaction req = tarantool.NewRollbackRequest() - resp, err = stream.Do(req).Get() + _, err = stream.Do(req).Get() require.Nilf(t, err, "failed to Rollback") - require.NotNilf(t, resp, "response is nil after Rollback") - require.Equalf(t, tarantool.OkCode, resp.Code, "failed to Rollback: wrong code returned") // Select outside of transaction - resp, err = conn.Do(selectReq).Get() + data, err = conn.Do(selectReq).Get() require.Nilf(t, err, "failed to Select") - require.NotNilf(t, resp, "response is nil after Select") - require.Equalf(t, 0, len(resp.Data), "response Body len != 0 after Select") + require.Equalf(t, 0, len(data), "response Body len != 0 after Select") // Select inside of stream after rollback - resp, err = stream.Do(selectReq).Get() + data, err = stream.Do(selectReq).Get() require.Nilf(t, err, "failed to Select") - require.NotNilf(t, resp, "response is nil after Select") - require.Equalf(t, 0, len(resp.Data), "response Body len != 0 after Select") + require.Equalf(t, 0, len(data), "response Body len != 0 after Select") } func TestStream_TxnIsolationLevel(t *testing.T) { var req tarantool.Request - var resp *tarantool.Response var err error txnIsolationLevels := []tarantool.TxnIsolationLevel{ @@ -2806,18 +2770,14 @@ func TestStream_TxnIsolationLevel(t *testing.T) { for _, level := range txnIsolationLevels { // Begin transaction req = tarantool.NewBeginRequest().TxnIsolation(level).Timeout(500 * time.Millisecond) - resp, err = stream.Do(req).Get() + _, err = stream.Do(req).Get() require.Nilf(t, err, "failed to Begin") - require.NotNilf(t, resp, "response is nil after Begin") - require.Equalf(t, tarantool.OkCode, resp.Code, "wrong code returned") // Insert in stream req = tarantool.NewInsertRequest(spaceName). Tuple([]interface{}{"level_key", "level_value"}) - resp, err = stream.Do(req).Get() + _, err = stream.Do(req).Get() require.Nilf(t, err, "failed to Insert") - require.NotNilf(t, resp, "response is nil after Insert") - require.Equalf(t, tarantool.OkCode, resp.Code, "wrong code returned") // Select not related to the transaction // while transaction is not committed @@ -2827,18 +2787,16 @@ func TestStream_TxnIsolationLevel(t *testing.T) { Limit(1). Iterator(tarantool.IterEq). Key([]interface{}{"level_key"}) - resp, err = conn.Do(selectReq).Get() + data, err := conn.Do(selectReq).Get() require.Nilf(t, err, "failed to Select") - require.NotNilf(t, resp, "response is nil after Select") - require.Equalf(t, 0, len(resp.Data), "response Data len != 0") + require.Equalf(t, 0, len(data), "response Data len != 0") // Select in stream - resp, err = stream.Do(selectReq).Get() + data, err = stream.Do(selectReq).Get() require.Nilf(t, err, "failed to Select") - require.NotNilf(t, resp, "response is nil after Select") - require.Equalf(t, 1, len(resp.Data), "response Body len != 1 after Select") + require.Equalf(t, 1, len(data), "response Body len != 1 after Select") - tpl, ok := resp.Data[0].([]interface{}) + tpl, ok := data[0].([]interface{}) require.Truef(t, ok, "unexpected body of Select") require.Equalf(t, 2, len(tpl), "unexpected body of Select") @@ -2852,22 +2810,18 @@ func TestStream_TxnIsolationLevel(t *testing.T) { // Rollback transaction req = tarantool.NewRollbackRequest() - resp, err = stream.Do(req).Get() + _, err = stream.Do(req).Get() require.Nilf(t, err, "failed to Rollback") - require.NotNilf(t, resp, "response is nil after Rollback") - require.Equalf(t, tarantool.OkCode, resp.Code, "wrong code returned") // Select outside of transaction - resp, err = conn.Do(selectReq).Get() + data, err = conn.Do(selectReq).Get() require.Nilf(t, err, "failed to Select") - require.NotNilf(t, resp, "response is nil after Select") - require.Equalf(t, 0, len(resp.Data), "response Data len != 0") + require.Equalf(t, 0, len(data), "response Data len != 0") // Select inside of stream after rollback - resp, err = stream.Do(selectReq).Get() + data, err = stream.Do(selectReq).Get() require.Nilf(t, err, "failed to Select") - require.NotNilf(t, resp, "response is nil after Select") - require.Equalf(t, 0, len(resp.Data), "response Data len != 0") + require.Equalf(t, 0, len(data), "response Data len != 0") test_helpers.DeleteRecordByKey(t, conn, spaceNo, indexNo, []interface{}{"level_key"}) } diff --git a/pool/connector.go b/pool/connector.go index 604e3921f..23cc7275d 100644 --- a/pool/connector.go +++ b/pool/connector.go @@ -60,7 +60,7 @@ func (c *ConnectorAdapter) ConfiguredTimeout() time.Duration { // // Deprecated: the method will be removed in the next major version, // use a PingRequest object + Do() instead. -func (c *ConnectorAdapter) Ping() (*tarantool.Response, error) { +func (c *ConnectorAdapter) Ping() ([]interface{}, error) { return c.pool.Ping(c.mode) } @@ -70,7 +70,7 @@ func (c *ConnectorAdapter) Ping() (*tarantool.Response, error) { // use a SelectRequest object + Do() instead. func (c *ConnectorAdapter) Select(space, index interface{}, offset, limit uint32, iterator tarantool.Iter, - key interface{}) (*tarantool.Response, error) { + key interface{}) ([]interface{}, error) { return c.pool.Select(space, index, offset, limit, iterator, key, c.mode) } @@ -79,7 +79,7 @@ func (c *ConnectorAdapter) Select(space, index interface{}, // Deprecated: the method will be removed in the next major version, // use an InsertRequest object + Do() instead. func (c *ConnectorAdapter) Insert(space interface{}, - tuple interface{}) (*tarantool.Response, error) { + tuple interface{}) ([]interface{}, error) { return c.pool.Insert(space, tuple, c.mode) } @@ -88,7 +88,7 @@ func (c *ConnectorAdapter) Insert(space interface{}, // Deprecated: the method will be removed in the next major version, // use a ReplaceRequest object + Do() instead. func (c *ConnectorAdapter) Replace(space interface{}, - tuple interface{}) (*tarantool.Response, error) { + tuple interface{}) ([]interface{}, error) { return c.pool.Replace(space, tuple, c.mode) } @@ -97,7 +97,7 @@ func (c *ConnectorAdapter) Replace(space interface{}, // Deprecated: the method will be removed in the next major version, // use a DeleteRequest object + Do() instead. func (c *ConnectorAdapter) Delete(space, index interface{}, - key interface{}) (*tarantool.Response, error) { + key interface{}) ([]interface{}, error) { return c.pool.Delete(space, index, key, c.mode) } @@ -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 interface{}, ops *tarantool.Operations) (*tarantool.Response, error) { + key interface{}, ops *tarantool.Operations) ([]interface{}, error) { return c.pool.Update(space, index, key, ops, c.mode) } @@ -115,7 +115,7 @@ 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, tuple interface{}, - ops *tarantool.Operations) (*tarantool.Response, error) { + ops *tarantool.Operations) ([]interface{}, error) { return c.pool.Upsert(space, tuple, ops, c.mode) } @@ -125,7 +125,7 @@ func (c *ConnectorAdapter) Upsert(space, tuple interface{}, // Deprecated: the method will be removed in the next major version, // use a CallRequest object + Do() instead. func (c *ConnectorAdapter) Call(functionName string, - args interface{}) (*tarantool.Response, error) { + args interface{}) ([]interface{}, error) { return c.pool.Call(functionName, args, c.mode) } @@ -136,7 +136,7 @@ func (c *ConnectorAdapter) Call(functionName string, // Deprecated: the method will be removed in the next major version, // use a Call16Request object + Do() instead. func (c *ConnectorAdapter) Call16(functionName string, - args interface{}) (*tarantool.Response, error) { + args interface{}) ([]interface{}, error) { return c.pool.Call16(functionName, args, c.mode) } @@ -146,7 +146,7 @@ func (c *ConnectorAdapter) Call16(functionName string, // Deprecated: the method will be removed in the next major version, // use a Call17Request object + Do() instead. func (c *ConnectorAdapter) Call17(functionName string, - args interface{}) (*tarantool.Response, error) { + args interface{}) ([]interface{}, error) { return c.pool.Call17(functionName, args, c.mode) } @@ -155,7 +155,7 @@ func (c *ConnectorAdapter) Call17(functionName string, // Deprecated: the method will be removed in the next major version, // use an EvalRequest object + Do() instead. func (c *ConnectorAdapter) Eval(expr string, - args interface{}) (*tarantool.Response, error) { + args interface{}) ([]interface{}, error) { return c.pool.Eval(expr, args, c.mode) } @@ -164,7 +164,7 @@ func (c *ConnectorAdapter) Eval(expr string, // Deprecated: the method will be removed in the next major version, // use an ExecuteRequest object + Do() instead. func (c *ConnectorAdapter) Execute(expr string, - args interface{}) (*tarantool.Response, error) { + args interface{}) ([]interface{}, error) { return c.pool.Execute(expr, args, c.mode) } diff --git a/pool/connector_test.go b/pool/connector_test.go index fa107cc58..87bebbd53 100644 --- a/pool/connector_test.go +++ b/pool/connector_test.go @@ -135,7 +135,7 @@ type baseRequestMock struct { mode Mode } -var reqResp *tarantool.Response = &tarantool.Response{} +var reqData []interface{} var errReq error = errors.New("response error") var reqFuture *tarantool.Future = &tarantool.Future{} @@ -190,7 +190,7 @@ type selectMock struct { func (m *selectMock) Select(space, index interface{}, offset, limit uint32, iterator tarantool.Iter, key interface{}, - mode ...Mode) (*tarantool.Response, error) { + mode ...Mode) ([]interface{}, error) { m.called++ m.space = space m.index = index @@ -199,7 +199,7 @@ func (m *selectMock) Select(space, index interface{}, m.iterator = iterator m.key = key m.mode = mode[0] - return reqResp, errReq + return reqData, errReq } func TestConnectorSelect(t *testing.T) { @@ -208,7 +208,7 @@ func TestConnectorSelect(t *testing.T) { resp, err := c.Select(reqSpace, reqIndex, reqOffset, reqLimit, reqIterator, reqKey) - require.Equalf(t, reqResp, resp, "unexpected response") + require.Equalf(t, reqData, resp, "unexpected response") require.Equalf(t, errReq, err, "unexpected error") require.Equalf(t, 1, m.called, "should be called only once") require.Equalf(t, reqSpace, m.space, "unexpected space was passed") @@ -299,12 +299,12 @@ type insertMock struct { } func (m *insertMock) Insert(space, tuple interface{}, - mode ...Mode) (*tarantool.Response, error) { + mode ...Mode) ([]interface{}, error) { m.called++ m.space = space m.tuple = tuple m.mode = mode[0] - return reqResp, errReq + return reqData, errReq } func TestConnectorInsert(t *testing.T) { @@ -313,7 +313,7 @@ func TestConnectorInsert(t *testing.T) { resp, err := c.Insert(reqSpace, reqTuple) - require.Equalf(t, reqResp, resp, "unexpected response") + require.Equalf(t, reqData, resp, "unexpected response") require.Equalf(t, errReq, err, "unexpected error") require.Equalf(t, 1, m.called, "should be called only once") require.Equalf(t, reqSpace, m.space, "unexpected space was passed") @@ -380,12 +380,12 @@ type replaceMock struct { } func (m *replaceMock) Replace(space, tuple interface{}, - mode ...Mode) (*tarantool.Response, error) { + mode ...Mode) ([]interface{}, error) { m.called++ m.space = space m.tuple = tuple m.mode = mode[0] - return reqResp, errReq + return reqData, errReq } func TestConnectorReplace(t *testing.T) { @@ -394,7 +394,7 @@ func TestConnectorReplace(t *testing.T) { resp, err := c.Replace(reqSpace, reqTuple) - require.Equalf(t, reqResp, resp, "unexpected response") + require.Equalf(t, reqData, resp, "unexpected response") require.Equalf(t, errReq, err, "unexpected error") require.Equalf(t, 1, m.called, "should be called only once") require.Equalf(t, reqSpace, m.space, "unexpected space was passed") @@ -461,13 +461,13 @@ type deleteMock struct { } func (m *deleteMock) Delete(space, index, key interface{}, - mode ...Mode) (*tarantool.Response, error) { + mode ...Mode) ([]interface{}, error) { m.called++ m.space = space m.index = index m.key = key m.mode = mode[0] - return reqResp, errReq + return reqData, errReq } func TestConnectorDelete(t *testing.T) { @@ -476,7 +476,7 @@ func TestConnectorDelete(t *testing.T) { resp, err := c.Delete(reqSpace, reqIndex, reqKey) - require.Equalf(t, reqResp, resp, "unexpected response") + require.Equalf(t, reqData, resp, "unexpected response") require.Equalf(t, errReq, err, "unexpected error") require.Equalf(t, 1, m.called, "should be called only once") require.Equalf(t, reqSpace, m.space, "unexpected space was passed") @@ -548,14 +548,14 @@ type updateMock struct { } func (m *updateMock) Update(space, index, key interface{}, - ops *tarantool.Operations, mode ...Mode) (*tarantool.Response, error) { + ops *tarantool.Operations, mode ...Mode) ([]interface{}, error) { m.called++ m.space = space m.index = index m.key = key m.ops = ops m.mode = mode[0] - return reqResp, errReq + return reqData, errReq } func TestConnectorUpdate(t *testing.T) { @@ -564,7 +564,7 @@ func TestConnectorUpdate(t *testing.T) { resp, err := c.Update(reqSpace, reqIndex, reqKey, reqOps) - require.Equalf(t, reqResp, resp, "unexpected response") + require.Equalf(t, reqData, resp, "unexpected response") require.Equalf(t, errReq, err, "unexpected error") require.Equalf(t, 1, m.called, "should be called only once") require.Equalf(t, reqSpace, m.space, "unexpected space was passed") @@ -641,13 +641,13 @@ type upsertMock struct { } func (m *upsertMock) Upsert(space, tuple interface{}, ops *tarantool.Operations, - mode ...Mode) (*tarantool.Response, error) { + mode ...Mode) ([]interface{}, error) { m.called++ m.space = space m.tuple = tuple m.ops = ops m.mode = mode[0] - return reqResp, errReq + return reqData, errReq } func TestConnectorUpsert(t *testing.T) { @@ -656,7 +656,7 @@ func TestConnectorUpsert(t *testing.T) { resp, err := c.Upsert(reqSpace, reqTuple, reqOps) - require.Equalf(t, reqResp, resp, "unexpected response") + require.Equalf(t, reqData, resp, "unexpected response") require.Equalf(t, errReq, err, "unexpected error") require.Equalf(t, 1, m.called, "should be called only once") require.Equalf(t, reqSpace, m.space, "unexpected space was passed") @@ -698,12 +698,12 @@ type baseCallMock struct { } func (m *baseCallMock) call(functionName string, args interface{}, - mode Mode) (*tarantool.Response, error) { + mode Mode) ([]interface{}, error) { m.called++ m.functionName = functionName m.args = args m.mode = mode - return reqResp, errReq + return reqData, errReq } func (m *baseCallMock) callTyped(functionName string, args interface{}, @@ -730,7 +730,7 @@ type callMock struct { } func (m *callMock) Call(functionName string, args interface{}, - mode Mode) (*tarantool.Response, error) { + mode Mode) ([]interface{}, error) { return m.call(functionName, args, mode) } @@ -740,7 +740,7 @@ func TestConnectorCall(t *testing.T) { resp, err := c.Call(reqFunctionName, reqArgs) - require.Equalf(t, reqResp, resp, "unexpected response") + require.Equalf(t, reqData, resp, "unexpected response") require.Equalf(t, errReq, err, "unexpected error") require.Equalf(t, 1, m.called, "should be called only once") require.Equalf(t, reqFunctionName, m.functionName, @@ -801,7 +801,7 @@ type call16Mock struct { } func (m *call16Mock) Call16(functionName string, args interface{}, - mode Mode) (*tarantool.Response, error) { + mode Mode) ([]interface{}, error) { return m.call(functionName, args, mode) } @@ -811,7 +811,7 @@ func TestConnectorCall16(t *testing.T) { resp, err := c.Call16(reqFunctionName, reqArgs) - require.Equalf(t, reqResp, resp, "unexpected response") + require.Equalf(t, reqData, resp, "unexpected response") require.Equalf(t, errReq, err, "unexpected error") require.Equalf(t, 1, m.called, "should be called only once") require.Equalf(t, reqFunctionName, m.functionName, @@ -872,7 +872,7 @@ type call17Mock struct { } func (m *call17Mock) Call17(functionName string, args interface{}, - mode Mode) (*tarantool.Response, error) { + mode Mode) ([]interface{}, error) { return m.call(functionName, args, mode) } @@ -882,7 +882,7 @@ func TestConnectorCall17(t *testing.T) { resp, err := c.Call17(reqFunctionName, reqArgs) - require.Equalf(t, reqResp, resp, "unexpected response") + require.Equalf(t, reqData, resp, "unexpected response") require.Equalf(t, errReq, err, "unexpected error") require.Equalf(t, 1, m.called, "should be called only once") require.Equalf(t, reqFunctionName, m.functionName, @@ -943,7 +943,7 @@ type evalMock struct { } func (m *evalMock) Eval(functionName string, args interface{}, - mode Mode) (*tarantool.Response, error) { + mode Mode) ([]interface{}, error) { return m.call(functionName, args, mode) } @@ -953,7 +953,7 @@ func TestConnectorEval(t *testing.T) { resp, err := c.Eval(reqFunctionName, reqArgs) - require.Equalf(t, reqResp, resp, "unexpected response") + require.Equalf(t, reqData, resp, "unexpected response") require.Equalf(t, errReq, err, "unexpected error") require.Equalf(t, 1, m.called, "should be called only once") require.Equalf(t, reqFunctionName, m.functionName, @@ -1014,7 +1014,7 @@ type executeMock struct { } func (m *executeMock) Execute(functionName string, args interface{}, - mode Mode) (*tarantool.Response, error) { + mode Mode) ([]interface{}, error) { return m.call(functionName, args, mode) } @@ -1024,7 +1024,7 @@ func TestConnectorExecute(t *testing.T) { resp, err := c.Execute(reqFunctionName, reqArgs) - require.Equalf(t, reqResp, resp, "unexpected response") + require.Equalf(t, reqData, resp, "unexpected response") require.Equalf(t, errReq, err, "unexpected error") require.Equalf(t, 1, m.called, "should be called only once") require.Equalf(t, reqFunctionName, m.functionName, diff --git a/pool/example_test.go b/pool/example_test.go index c4a919a93..7eb480177 100644 --- a/pool/example_test.go +++ b/pool/example_test.go @@ -157,7 +157,6 @@ func getTestTxnProtocol() tarantool.ProtocolInfo { func ExampleCommitRequest() { var req tarantool.Request - var resp *tarantool.Response var err error // Tarantool supports streams and interactive transactions since version 2.10.0 @@ -182,22 +181,22 @@ func ExampleCommitRequest() { // Begin transaction req = tarantool.NewBeginRequest() - resp, err = stream.Do(req).Get() + data, err := stream.Do(req).Get() if err != nil { fmt.Printf("Failed to Begin: %s", err.Error()) return } - fmt.Printf("Begin transaction: response is %#v\n", resp.Code) + fmt.Printf("Begin transaction: response is %#v\n", data) // Insert in stream req = tarantool.NewInsertRequest(spaceName). Tuple([]interface{}{"example_commit_key", "example_commit_value"}) - resp, err = stream.Do(req).Get() + data, err = stream.Do(req).Get() if err != nil { fmt.Printf("Failed to Insert: %s", err.Error()) return } - fmt.Printf("Insert in stream: response is %#v\n", resp.Code) + fmt.Printf("Insert in stream: response is %#v\n", data) // Select not related to the transaction // while transaction is not committed @@ -207,43 +206,42 @@ func ExampleCommitRequest() { Limit(1). Iterator(tarantool.IterEq). Key([]interface{}{"example_commit_key"}) - resp, err = connPool.Do(selectReq, pool.RW).Get() + data, err = connPool.Do(selectReq, pool.RW).Get() if err != nil { fmt.Printf("Failed to Select: %s", err.Error()) return } - fmt.Printf("Select out of stream before commit: response is %#v\n", resp.Data) + fmt.Printf("Select out of stream before commit: response is %#v\n", data) // Select in stream - resp, err = stream.Do(selectReq).Get() + data, err = stream.Do(selectReq).Get() if err != nil { fmt.Printf("Failed to Select: %s", err.Error()) return } - fmt.Printf("Select in stream: response is %#v\n", resp.Data) + fmt.Printf("Select in stream: response is %#v\n", data) // Commit transaction req = tarantool.NewCommitRequest() - resp, err = stream.Do(req).Get() + data, err = stream.Do(req).Get() if err != nil { fmt.Printf("Failed to Commit: %s", err.Error()) return } - fmt.Printf("Commit transaction: response is %#v\n", resp.Code) + fmt.Printf("Commit transaction: response is %#v\n", data) // Select outside of transaction // example pool has only one rw instance - resp, err = connPool.Do(selectReq, pool.RW).Get() + data, err = connPool.Do(selectReq, pool.RW).Get() if err != nil { fmt.Printf("Failed to Select: %s", err.Error()) return } - fmt.Printf("Select after commit: response is %#v\n", resp.Data) + fmt.Printf("Select after commit: response is %#v\n", data) } func ExampleRollbackRequest() { var req tarantool.Request - var resp *tarantool.Response var err error // Tarantool supports streams and interactive transactions since version 2.10.0 @@ -268,22 +266,22 @@ func ExampleRollbackRequest() { // Begin transaction req = tarantool.NewBeginRequest() - resp, err = stream.Do(req).Get() + data, err := stream.Do(req).Get() if err != nil { fmt.Printf("Failed to Begin: %s", err.Error()) return } - fmt.Printf("Begin transaction: response is %#v\n", resp.Code) + fmt.Printf("Begin transaction: response is %#v\n", data) // Insert in stream req = tarantool.NewInsertRequest(spaceName). Tuple([]interface{}{"example_rollback_key", "example_rollback_value"}) - resp, err = stream.Do(req).Get() + data, err = stream.Do(req).Get() if err != nil { fmt.Printf("Failed to Insert: %s", err.Error()) return } - fmt.Printf("Insert in stream: response is %#v\n", resp.Code) + fmt.Printf("Insert in stream: response is %#v\n", data) // Select not related to the transaction // while transaction is not committed @@ -293,43 +291,42 @@ func ExampleRollbackRequest() { Limit(1). Iterator(tarantool.IterEq). Key([]interface{}{"example_rollback_key"}) - resp, err = connPool.Do(selectReq, pool.RW).Get() + data, err = connPool.Do(selectReq, pool.RW).Get() if err != nil { fmt.Printf("Failed to Select: %s", err.Error()) return } - fmt.Printf("Select out of stream: response is %#v\n", resp.Data) + fmt.Printf("Select out of stream: response is %#v\n", data) // Select in stream - resp, err = stream.Do(selectReq).Get() + data, err = stream.Do(selectReq).Get() if err != nil { fmt.Printf("Failed to Select: %s", err.Error()) return } - fmt.Printf("Select in stream: response is %#v\n", resp.Data) + fmt.Printf("Select in stream: response is %#v\n", data) // Rollback transaction req = tarantool.NewRollbackRequest() - resp, err = stream.Do(req).Get() + data, err = stream.Do(req).Get() if err != nil { fmt.Printf("Failed to Rollback: %s", err.Error()) return } - fmt.Printf("Rollback transaction: response is %#v\n", resp.Code) + fmt.Printf("Rollback transaction: response is %#v\n", data) // Select outside of transaction // example pool has only one rw instance - resp, err = connPool.Do(selectReq, pool.RW).Get() + data, err = connPool.Do(selectReq, pool.RW).Get() if err != nil { fmt.Printf("Failed to Select: %s", err.Error()) return } - fmt.Printf("Select after Rollback: response is %#v\n", resp.Data) + fmt.Printf("Select after Rollback: response is %#v\n", data) } func ExampleBeginRequest_TxnIsolation() { var req tarantool.Request - var resp *tarantool.Response var err error // Tarantool supports streams and interactive transactions since version 2.10.0 @@ -356,22 +353,22 @@ func ExampleBeginRequest_TxnIsolation() { req = tarantool.NewBeginRequest(). TxnIsolation(tarantool.ReadConfirmedLevel). Timeout(500 * time.Millisecond) - resp, err = stream.Do(req).Get() + data, err := stream.Do(req).Get() if err != nil { fmt.Printf("Failed to Begin: %s", err.Error()) return } - fmt.Printf("Begin transaction: response is %#v\n", resp.Code) + fmt.Printf("Begin transaction: response is %#v\n", data) // Insert in stream req = tarantool.NewInsertRequest(spaceName). Tuple([]interface{}{"isolation_level_key", "isolation_level_value"}) - resp, err = stream.Do(req).Get() + data, err = stream.Do(req).Get() if err != nil { fmt.Printf("Failed to Insert: %s", err.Error()) return } - fmt.Printf("Insert in stream: response is %#v\n", resp.Code) + fmt.Printf("Insert in stream: response is %#v\n", data) // Select not related to the transaction // while transaction is not committed @@ -381,38 +378,38 @@ func ExampleBeginRequest_TxnIsolation() { Limit(1). Iterator(tarantool.IterEq). Key([]interface{}{"isolation_level_key"}) - resp, err = connPool.Do(selectReq, pool.RW).Get() + data, err = connPool.Do(selectReq, pool.RW).Get() if err != nil { fmt.Printf("Failed to Select: %s", err.Error()) return } - fmt.Printf("Select out of stream: response is %#v\n", resp.Data) + fmt.Printf("Select out of stream: response is %#v\n", data) // Select in stream - resp, err = stream.Do(selectReq).Get() + data, err = stream.Do(selectReq).Get() if err != nil { fmt.Printf("Failed to Select: %s", err.Error()) return } - fmt.Printf("Select in stream: response is %#v\n", resp.Data) + fmt.Printf("Select in stream: response is %#v\n", data) // Rollback transaction req = tarantool.NewRollbackRequest() - resp, err = stream.Do(req).Get() + data, err = stream.Do(req).Get() if err != nil { fmt.Printf("Failed to Rollback: %s", err.Error()) return } - fmt.Printf("Rollback transaction: response is %#v\n", resp.Code) + fmt.Printf("Rollback transaction: response is %#v\n", data) // Select outside of transaction // example pool has only one rw instance - resp, err = connPool.Do(selectReq, pool.RW).Get() + data, err = connPool.Do(selectReq, pool.RW).Get() if err != nil { fmt.Printf("Failed to Select: %s", err.Error()) return } - fmt.Printf("Select after Rollback: response is %#v\n", resp.Data) + fmt.Printf("Select after Rollback: response is %#v\n", data) } func ExampleConnectorAdapter() { @@ -426,12 +423,10 @@ func ExampleConnectorAdapter() { var connector tarantool.Connector = adapter // Ping an RW instance to check connection. - resp, err := connector.Do(tarantool.NewPingRequest()).Get() - fmt.Println("Ping Code", resp.Code) - fmt.Println("Ping Data", resp.Data) + data, err := connector.Do(tarantool.NewPingRequest()).Get() + fmt.Println("Ping Data", data) fmt.Println("Ping Error", err) // Output: - // Ping Code 0 // Ping Data [] // Ping Error } @@ -473,5 +468,5 @@ func ExampleConnectionPool_CloseGraceful_force() { // Force ConnectionPool.Close()! // ConnectionPool.CloseGraceful() done! // Result: - // connection closed by client (0x4001) + // [] connection closed by client (0x4001) } diff --git a/pool/pooler.go b/pool/pooler.go index 0ff945bbb..975162d37 100644 --- a/pool/pooler.go +++ b/pool/pooler.go @@ -19,51 +19,51 @@ type Pooler interface { // Deprecated: the method will be removed in the next major version, // use a PingRequest object + Do() instead. - Ping(mode Mode) (*tarantool.Response, error) + Ping(mode Mode) ([]interface{}, error) // Deprecated: the method will be removed in the next major version, // use a SelectRequest object + Do() instead. Select(space, index interface{}, offset, limit uint32, iterator tarantool.Iter, - key interface{}, mode ...Mode) (*tarantool.Response, error) + key interface{}, mode ...Mode) ([]interface{}, error) // Deprecated: the method will be removed in the next major version, // use an InsertRequest object + Do() instead. Insert(space interface{}, tuple interface{}, - mode ...Mode) (*tarantool.Response, error) + mode ...Mode) ([]interface{}, error) // Deprecated: the method will be removed in the next major version, // use a ReplaceRequest object + Do() instead. Replace(space interface{}, tuple interface{}, - mode ...Mode) (*tarantool.Response, error) + mode ...Mode) ([]interface{}, error) // Deprecated: the method will be removed in the next major version, // use a DeleteRequest object + Do() instead. Delete(space, index interface{}, key interface{}, - mode ...Mode) (*tarantool.Response, error) + mode ...Mode) ([]interface{}, error) // Deprecated: the method will be removed in the next major version, // use a UpdateRequest object + Do() instead. Update(space, index interface{}, key interface{}, ops *tarantool.Operations, - mode ...Mode) (*tarantool.Response, error) + mode ...Mode) ([]interface{}, error) // Deprecated: the method will be removed in the next major version, // use a UpsertRequest object + Do() instead. Upsert(space interface{}, tuple interface{}, ops *tarantool.Operations, - mode ...Mode) (*tarantool.Response, error) + mode ...Mode) ([]interface{}, error) // Deprecated: the method will be removed in the next major version, // use a CallRequest object + Do() instead. Call(functionName string, args interface{}, - mode Mode) (*tarantool.Response, error) + mode Mode) ([]interface{}, error) // Deprecated: the method will be removed in the next major version, // use a Call16Request object + Do() instead. Call16(functionName string, args interface{}, - mode Mode) (*tarantool.Response, error) + mode Mode) ([]interface{}, error) // Deprecated: the method will be removed in the next major version, // use a Call17Request object + Do() instead. Call17(functionName string, args interface{}, - mode Mode) (*tarantool.Response, error) + mode Mode) ([]interface{}, error) // Deprecated: the method will be removed in the next major version, // use an EvalRequest object + Do() instead. Eval(expr string, args interface{}, - mode Mode) (*tarantool.Response, error) + mode Mode) ([]interface{}, error) // Deprecated: the method will be removed in the next major version, // use an ExecuteRequest object + Do() instead. Execute(expr string, args interface{}, - mode Mode) (*tarantool.Response, error) + mode Mode) ([]interface{}, error) // Deprecated: the method will be removed in the next major version, // use a SelectRequest object + Do() instead. diff --git a/prepared.go b/prepared.go index 8f2d95519..3a03d740f 100644 --- a/prepared.go +++ b/prepared.go @@ -3,6 +3,7 @@ package tarantool import ( "context" "fmt" + "io" "github.com/tarantool/go-iproto" "github.com/vmihailenco/msgpack/v5" @@ -42,17 +43,21 @@ func fillExecutePrepared(enc *msgpack.Encoder, stmt Prepared, args interface{}) } // NewPreparedFromResponse constructs a Prepared object. -func NewPreparedFromResponse(conn *Connection, resp *Response) (*Prepared, error) { +func NewPreparedFromResponse(conn *Connection, resp Response) (*Prepared, error) { if resp == nil { return nil, fmt.Errorf("passed nil response") } - if resp.Data == nil { + data, err := resp.Decode() + if err != nil { + return nil, fmt.Errorf("decode response body error: %s", err.Error()) + } + if data == nil { return nil, fmt.Errorf("response Data is nil") } - if len(resp.Data) == 0 { + if len(data) == 0 { return nil, fmt.Errorf("response Data format is wrong") } - stmt, ok := resp.Data[0].(*Prepared) + stmt, ok := data[0].(*Prepared) if !ok { return nil, fmt.Errorf("response Data format is wrong") } @@ -91,6 +96,15 @@ func (req *PrepareRequest) Context(ctx context.Context) *PrepareRequest { return req } +// Response creates a response for the PrepareRequest. +func (req *PrepareRequest) Response(header Header, body io.Reader) (Response, error) { + baseResp, err := createBaseResponse(header, body) + if err != nil { + return nil, err + } + return &PrepareResponse{baseResponse: baseResp}, nil +} + // UnprepareRequest helps you to create an unprepare request object for // execution by a Connection. type UnprepareRequest struct { @@ -171,3 +185,12 @@ func (req *ExecutePreparedRequest) Context(ctx context.Context) *ExecutePrepared req.ctx = ctx return req } + +// Response creates a response for the ExecutePreparedRequest. +func (req *ExecutePreparedRequest) Response(header Header, body io.Reader) (Response, error) { + baseResp, err := createBaseResponse(header, body) + if err != nil { + return nil, err + } + return &ExecuteResponse{baseResponse: baseResp}, nil +} diff --git a/queue/queue.go b/queue/queue.go index 5c0506f3e..99b1a722f 100644 --- a/queue/queue.go +++ b/queue/queue.go @@ -213,14 +213,14 @@ func (q *queue) Cfg(opts CfgOpts) error { // Exists checks existence of a tube. func (q *queue) Exists() (bool, error) { cmd := "local name = ... ; return queue.tube[name] ~= nil" - resp, err := q.conn.Do(tarantool.NewEvalRequest(cmd). + data, err := q.conn.Do(tarantool.NewEvalRequest(cmd). Args([]string{q.name}), ).Get() if err != nil { return false, err } - exist := len(resp.Data) != 0 && resp.Data[0].(bool) + exist := len(data) != 0 && data[0].(bool) return exist, nil } @@ -244,11 +244,11 @@ func (q *queue) Identify(u *uuid.UUID) (uuid.UUID, error) { } req := tarantool.NewCallRequest(q.cmds.identify).Args(args) - if resp, err := q.conn.Do(req).Get(); err == nil { - if us, ok := resp.Data[0].(string); ok { + if data, err := q.conn.Do(req).Get(); err == nil { + if us, ok := data[0].(string); ok { return uuid.FromBytes([]byte(us)) } else { - return uuid.UUID{}, fmt.Errorf("unexpected response: %v", resp.Data) + return uuid.UUID{}, fmt.Errorf("unexpected response: %v", data) } } else { return uuid.UUID{}, err @@ -411,31 +411,31 @@ func (q *queue) Delete(taskId uint64) error { // State returns a current queue state. func (q *queue) State() (State, error) { - resp, err := q.conn.Do(tarantool.NewCallRequest(q.cmds.state)).Get() + data, err := q.conn.Do(tarantool.NewCallRequest(q.cmds.state)).Get() if err != nil { return UnknownState, err } - if respState, ok := resp.Data[0].(string); ok { + if respState, ok := data[0].(string); ok { if state, ok := strToState[respState]; ok { return state, nil } - return UnknownState, fmt.Errorf("unknown state: %v", resp.Data[0]) + return UnknownState, fmt.Errorf("unknown state: %v", data[0]) } - return UnknownState, fmt.Errorf("unexpected response: %v", resp.Data) + return UnknownState, fmt.Errorf("unexpected response: %v", data) } // Return the number of tasks in a queue broken down by task_state, and the // number of requests broken down by the type of request. func (q *queue) Statistic() (interface{}, error) { req := tarantool.NewCallRequest(q.cmds.statistics).Args([]interface{}{q.name}) - resp, err := q.conn.Do(req).Get() + data, err := q.conn.Do(req).Get() if err != nil { return nil, err } - if len(resp.Data) != 0 { - return resp.Data[0], nil + if len(data) != 0 { + return data[0], nil } return nil, nil diff --git a/request.go b/request.go index 8c4f4acd2..8dbe250b5 100644 --- a/request.go +++ b/request.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "io" "reflect" "strings" "sync" @@ -260,7 +261,7 @@ func fillWatchOnce(enc *msgpack.Encoder, key string) error { // // Deprecated: the method will be removed in the next major version, // use a PingRequest object + Do() instead. -func (conn *Connection) Ping() (*Response, error) { +func (conn *Connection) Ping() ([]interface{}, error) { return conn.Do(NewPingRequest()).Get() } @@ -271,7 +272,7 @@ func (conn *Connection) Ping() (*Response, error) { // Deprecated: the method will be removed in the next major version, // use a SelectRequest object + Do() instead. func (conn *Connection) Select(space, index interface{}, offset, limit uint32, iterator Iter, - key interface{}) (*Response, error) { + key interface{}) ([]interface{}, error) { return conn.SelectAsync(space, index, offset, limit, iterator, key).Get() } @@ -282,7 +283,7 @@ func (conn *Connection) Select(space, index interface{}, offset, limit uint32, i // // Deprecated: the method will be removed in the next major version, // use an InsertRequest object + Do() instead. -func (conn *Connection) Insert(space interface{}, tuple interface{}) (*Response, error) { +func (conn *Connection) Insert(space interface{}, tuple interface{}) ([]interface{}, error) { return conn.InsertAsync(space, tuple).Get() } @@ -293,7 +294,7 @@ func (conn *Connection) Insert(space interface{}, tuple interface{}) (*Response, // // Deprecated: the method will be removed in the next major version, // use a ReplaceRequest object + Do() instead. -func (conn *Connection) Replace(space interface{}, tuple interface{}) (*Response, error) { +func (conn *Connection) Replace(space interface{}, tuple interface{}) ([]interface{}, error) { return conn.ReplaceAsync(space, tuple).Get() } @@ -304,7 +305,7 @@ func (conn *Connection) Replace(space interface{}, tuple interface{}) (*Response // // Deprecated: the method will be removed in the next major version, // use a DeleteRequest object + Do() instead. -func (conn *Connection) Delete(space, index interface{}, key interface{}) (*Response, error) { +func (conn *Connection) Delete(space, index interface{}, key interface{}) ([]interface{}, error) { return conn.DeleteAsync(space, index, key).Get() } @@ -315,7 +316,8 @@ 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, key interface{}, ops *Operations) (*Response, error) { +func (conn *Connection) Update(space, index, key interface{}, + ops *Operations) ([]interface{}, error) { return conn.UpdateAsync(space, index, key, ops).Get() } @@ -326,7 +328,7 @@ func (conn *Connection) Update(space, index, key interface{}, ops *Operations) ( // // Deprecated: the method will be removed in the next major version, // use a UpsertRequest object + Do() instead. -func (conn *Connection) Upsert(space, tuple interface{}, ops *Operations) (*Response, error) { +func (conn *Connection) Upsert(space, tuple interface{}, ops *Operations) ([]interface{}, error) { return conn.UpsertAsync(space, tuple, ops).Get() } @@ -337,7 +339,7 @@ func (conn *Connection) Upsert(space, tuple interface{}, ops *Operations) (*Resp // // Deprecated: the method will be removed in the next major version, // use a CallRequest object + Do() instead. -func (conn *Connection) Call(functionName string, args interface{}) (*Response, error) { +func (conn *Connection) Call(functionName string, args interface{}) ([]interface{}, error) { return conn.CallAsync(functionName, args).Get() } @@ -349,7 +351,7 @@ func (conn *Connection) Call(functionName string, args interface{}) (*Response, // // Deprecated: the method will be removed in the next major version, // use a Call16Request object + Do() instead. -func (conn *Connection) Call16(functionName string, args interface{}) (*Response, error) { +func (conn *Connection) Call16(functionName string, args interface{}) ([]interface{}, error) { return conn.Call16Async(functionName, args).Get() } @@ -360,7 +362,7 @@ func (conn *Connection) Call16(functionName string, args interface{}) (*Response // // Deprecated: the method will be removed in the next major version, // use a Call17Request object + Do() instead. -func (conn *Connection) Call17(functionName string, args interface{}) (*Response, error) { +func (conn *Connection) Call17(functionName string, args interface{}) ([]interface{}, error) { return conn.Call17Async(functionName, args).Get() } @@ -370,7 +372,7 @@ func (conn *Connection) Call17(functionName string, args interface{}) (*Response // // Deprecated: the method will be removed in the next major version, // use an EvalRequest object + Do() instead. -func (conn *Connection) Eval(expr string, args interface{}) (*Response, error) { +func (conn *Connection) Eval(expr string, args interface{}) ([]interface{}, error) { return conn.EvalAsync(expr, args).Get() } @@ -381,7 +383,7 @@ func (conn *Connection) Eval(expr string, args interface{}) (*Response, error) { // // Deprecated: the method will be removed in the next major version, // use an ExecuteRequest object + Do() instead. -func (conn *Connection) Execute(expr string, args interface{}) (*Response, error) { +func (conn *Connection) Execute(expr string, args interface{}) ([]interface{}, error) { return conn.ExecuteAsync(expr, args).Get() } @@ -532,9 +534,20 @@ func (conn *Connection) EvalTyped(expr string, args interface{}, result interfac // use an ExecuteRequest object + Do() instead. func (conn *Connection) ExecuteTyped(expr string, args interface{}, result interface{}) (SQLInfo, []ColumnMetaData, error) { + var ( + sqlInfo SQLInfo + metaData []ColumnMetaData + ) + fut := conn.ExecuteAsync(expr, args) err := fut.GetTyped(&result) - return fut.resp.SQLInfo, fut.resp.MetaData, err + if resp, ok := fut.resp.(*ExecuteResponse); ok { + sqlInfo = resp.sqlInfo + metaData = resp.metaData + } else if err == nil { + err = fmt.Errorf("unexpected response type %T, want: *ExecuteResponse", fut.resp) + } + return sqlInfo, metaData, err } // SelectAsync sends select request to Tarantool and returns Future. @@ -807,6 +820,8 @@ type Request interface { Ctx() context.Context // Async returns true if the request does not expect response. Async() bool + // Response creates a response for current request type. + Response(header Header, body io.Reader) (Response, error) } // ConnectedRequest is an interface that provides the info about a Connection @@ -838,6 +853,15 @@ func (req *baseRequest) Ctx() context.Context { return req.ctx } +// Response creates a response for the baseRequest. +func (req *baseRequest) Response(header Header, body io.Reader) (Response, error) { + resp, err := createBaseResponse(header, body) + if err != nil { + return nil, err + } + return &resp, nil +} + type spaceRequest struct { baseRequest space interface{} @@ -928,6 +952,15 @@ func (req authRequest) Body(res SchemaResolver, enc *msgpack.Encoder) error { return nil } +// Response creates a response for the authRequest. +func (req authRequest) Response(header Header, body io.Reader) (Response, error) { + resp, err := createBaseResponse(header, body) + if err != nil { + return nil, err + } + return &resp, nil +} + // PingRequest helps you to create an execute request object for execution // by a Connection. type PingRequest struct { @@ -1070,6 +1103,15 @@ func (req *SelectRequest) Context(ctx context.Context) *SelectRequest { return req } +// Response creates a response for the SelectRequest. +func (req *SelectRequest) Response(header Header, body io.Reader) (Response, error) { + baseResp, err := createBaseResponse(header, body) + if err != nil { + return nil, err + } + return &SelectResponse{baseResponse: baseResp}, nil +} + // InsertRequest helps you to create an insert request object for execution // by a Connection. type InsertRequest struct { @@ -1469,6 +1511,15 @@ func (req *ExecuteRequest) Context(ctx context.Context) *ExecuteRequest { return req } +// Response creates a response for the ExecuteRequest. +func (req *ExecuteRequest) Response(header Header, body io.Reader) (Response, error) { + baseResp, err := createBaseResponse(header, body) + if err != nil { + return nil, err + } + return &ExecuteResponse{baseResponse: baseResp}, nil +} + // WatchOnceRequest synchronously fetches the value currently associated with a // specified notification key without subscribing to changes. type WatchOnceRequest struct { diff --git a/request_test.go b/request_test.go index 580259702..84ba23ef6 100644 --- a/request_test.go +++ b/request_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "errors" + "fmt" "testing" "time" @@ -317,58 +318,6 @@ func TestRequestsCtx_setter(t *testing.T) { } } -func TestResolverCalledWithoutNameSupport(t *testing.T) { - resolver.nameUseSupported = false - resolver.spaceResolverCalls = 0 - resolver.indexResolverCalls = 0 - - req := NewSelectRequest("valid") - req.Index("valid") - - var reqBuf bytes.Buffer - reqEnc := msgpack.NewEncoder(&reqBuf) - - err := req.Body(&resolver, reqEnc) - if err != nil { - t.Errorf("An unexpected Response.Body() error: %q", err.Error()) - } - - if resolver.spaceResolverCalls != 1 { - t.Errorf("ResolveSpace was called %d times instead of 1.", - resolver.spaceResolverCalls) - } - if resolver.indexResolverCalls != 1 { - t.Errorf("ResolveIndex was called %d times instead of 1.", - resolver.indexResolverCalls) - } -} - -func TestResolverNotCalledWithNameSupport(t *testing.T) { - resolver.nameUseSupported = true - resolver.spaceResolverCalls = 0 - resolver.indexResolverCalls = 0 - - req := NewSelectRequest("valid") - req.Index("valid") - - var reqBuf bytes.Buffer - reqEnc := msgpack.NewEncoder(&reqBuf) - - err := req.Body(&resolver, reqEnc) - if err != nil { - t.Errorf("An unexpected Response.Body() error: %q", err.Error()) - } - - if resolver.spaceResolverCalls != 0 { - t.Errorf("ResolveSpace was called %d times instead of 0.", - resolver.spaceResolverCalls) - } - if resolver.indexResolverCalls != 0 { - t.Errorf("ResolveIndex was called %d times instead of 0.", - resolver.indexResolverCalls) - } -} - func TestPingRequestDefaultValues(t *testing.T) { var refBuf bytes.Buffer @@ -1007,3 +956,110 @@ func TestWatchOnceRequestDefaultValues(t *testing.T) { req := NewWatchOnceRequest(validKey) assertBodyEqual(t, refBuf.Bytes(), req) } + +func TestResponseDecode(t *testing.T) { + header := Header{} + data := bytes.NewBuffer([]byte{'v', '2'}) + baseExample, err := NewPingRequest().Response(header, data) + assert.NoError(t, err) + + tests := []struct { + req Request + expected Response + }{ + {req: NewSelectRequest(validSpace), expected: &SelectResponse{}}, + {req: NewUpdateRequest(validSpace), expected: baseExample}, + {req: NewUpsertRequest(validSpace), expected: baseExample}, + {req: NewInsertRequest(validSpace), expected: baseExample}, + {req: NewReplaceRequest(validSpace), expected: baseExample}, + {req: NewDeleteRequest(validSpace), expected: baseExample}, + {req: NewCallRequest(validExpr), expected: baseExample}, + {req: NewCall16Request(validExpr), expected: baseExample}, + {req: NewCall17Request(validExpr), expected: baseExample}, + {req: NewEvalRequest(validExpr), expected: baseExample}, + {req: NewExecuteRequest(validExpr), expected: &ExecuteResponse{}}, + {req: NewPingRequest(), expected: baseExample}, + {req: NewPrepareRequest(validExpr), expected: &PrepareResponse{}}, + {req: NewUnprepareRequest(validStmt), expected: baseExample}, + {req: NewExecutePreparedRequest(validStmt), expected: &ExecuteResponse{}}, + {req: NewBeginRequest(), expected: baseExample}, + {req: NewCommitRequest(), expected: baseExample}, + {req: NewRollbackRequest(), expected: baseExample}, + {req: NewIdRequest(validProtocolInfo), expected: baseExample}, + {req: NewBroadcastRequest(validKey), expected: baseExample}, + {req: NewWatchOnceRequest(validKey), expected: baseExample}, + } + + for _, test := range tests { + buf := bytes.NewBuffer([]byte{}) + enc := msgpack.NewEncoder(buf) + + enc.EncodeMapLen(1) + enc.EncodeUint8(uint8(iproto.IPROTO_DATA)) + enc.Encode([]interface{}{'v', '2'}) + + resp, err := test.req.Response(header, bytes.NewBuffer(buf.Bytes())) + assert.NoError(t, err) + assert.True(t, fmt.Sprintf("%T", resp) == + fmt.Sprintf("%T", test.expected)) + assert.Equal(t, header, resp.Header()) + + decodedInterface, err := resp.Decode() + assert.NoError(t, err) + assert.Equal(t, []interface{}{'v', '2'}, decodedInterface) + } +} + +func TestResponseDecodeTyped(t *testing.T) { + header := Header{} + data := bytes.NewBuffer([]byte{'v', '2'}) + baseExample, err := NewPingRequest().Response(header, data) + assert.NoError(t, err) + + tests := []struct { + req Request + expected Response + }{ + {req: NewSelectRequest(validSpace), expected: &SelectResponse{}}, + {req: NewUpdateRequest(validSpace), expected: baseExample}, + {req: NewUpsertRequest(validSpace), expected: baseExample}, + {req: NewInsertRequest(validSpace), expected: baseExample}, + {req: NewReplaceRequest(validSpace), expected: baseExample}, + {req: NewDeleteRequest(validSpace), expected: baseExample}, + {req: NewCallRequest(validExpr), expected: baseExample}, + {req: NewCall16Request(validExpr), expected: baseExample}, + {req: NewCall17Request(validExpr), expected: baseExample}, + {req: NewEvalRequest(validExpr), expected: baseExample}, + {req: NewExecuteRequest(validExpr), expected: &ExecuteResponse{}}, + {req: NewPingRequest(), expected: baseExample}, + {req: NewPrepareRequest(validExpr), expected: &PrepareResponse{}}, + {req: NewUnprepareRequest(validStmt), expected: baseExample}, + {req: NewExecutePreparedRequest(validStmt), expected: &ExecuteResponse{}}, + {req: NewBeginRequest(), expected: baseExample}, + {req: NewCommitRequest(), expected: baseExample}, + {req: NewRollbackRequest(), expected: baseExample}, + {req: NewIdRequest(validProtocolInfo), expected: baseExample}, + {req: NewBroadcastRequest(validKey), expected: baseExample}, + {req: NewWatchOnceRequest(validKey), expected: baseExample}, + } + + for _, test := range tests { + buf := bytes.NewBuffer([]byte{}) + enc := msgpack.NewEncoder(buf) + + enc.EncodeMapLen(1) + enc.EncodeUint8(uint8(iproto.IPROTO_DATA)) + enc.EncodeBytes([]byte{'v', '2'}) + + resp, err := test.req.Response(header, bytes.NewBuffer(buf.Bytes())) + assert.NoError(t, err) + assert.True(t, fmt.Sprintf("%T", resp) == + fmt.Sprintf("%T", test.expected)) + assert.Equal(t, header, resp.Header()) + + var decoded []byte + err = resp.DecodeTyped(&decoded) + assert.NoError(t, err) + assert.Equal(t, []byte{'v', '2'}, decoded) + } +} diff --git a/response.go b/response.go index 0d6d062b8..db88c743c 100644 --- a/response.go +++ b/response.go @@ -2,23 +2,76 @@ package tarantool import ( "fmt" + "io" + "io/ioutil" "github.com/tarantool/go-iproto" "github.com/vmihailenco/msgpack/v5" ) -type Response struct { - RequestId uint32 - Code uint32 - // Error contains an error message. - Error string - // Data contains deserialized data for untyped requests. - Data []interface{} - // Pos contains a position descriptor of last selected tuple. - Pos []byte - MetaData []ColumnMetaData - SQLInfo SQLInfo - buf smallBuf +// Response is an interface with operations for the server responses. +type Response interface { + // Header returns a response header. + Header() Header + // Decode decodes a response. + Decode() ([]interface{}, error) + // DecodeTyped decodes a response into a given container res. + DecodeTyped(res interface{}) error +} + +type baseResponse struct { + // header is a response header. + header Header + // data contains deserialized data for untyped requests. + data []interface{} + buf smallBuf + // Was the Decode() func called for this response. + decoded bool + // Was the DecodeTyped() func called for this response. + decodedTyped bool + err error +} + +func createBaseResponse(header Header, body io.Reader) (baseResponse, error) { + if body == nil { + return baseResponse{header: header}, nil + } + if buf, ok := body.(*smallBuf); ok { + return baseResponse{header: header, buf: *buf}, nil + } + data, err := ioutil.ReadAll(body) + if err != nil { + return baseResponse{}, err + } + return baseResponse{header: header, buf: smallBuf{b: data}}, nil +} + +// SelectResponse is used for the select requests. +// It might contain a position descriptor of the last selected tuple. +// +// You need to cast to SelectResponse a response from SelectRequest. +type SelectResponse struct { + baseResponse + // pos contains a position descriptor of last selected tuple. + pos []byte +} + +// PrepareResponse is used for the prepare requests. +// It might contain meta-data and sql info. +// +// Be careful: now this is an alias for `ExecuteResponse`, +// but it could be changed in the future. +// You need to cast to PrepareResponse a response from PrepareRequest. +type PrepareResponse ExecuteResponse + +// ExecuteResponse is used for the execute requests. +// It might contain meta-data and sql info. +// +// You need to cast to ExecuteResponse a response from ExecuteRequest. +type ExecuteResponse struct { + baseResponse + metaData []ColumnMetaData + sqlInfo SQLInfo } type ColumnMetaData struct { @@ -103,61 +156,173 @@ func (info *SQLInfo) DecodeMsgpack(d *msgpack.Decoder) error { return nil } -func (resp *Response) smallInt(d *msgpack.Decoder) (i int, err error) { - b, err := resp.buf.ReadByte() +func smallInt(d *msgpack.Decoder, buf *smallBuf) (i int, err error) { + b, err := buf.ReadByte() if err != nil { return } if b <= 127 { return int(b), nil } - resp.buf.UnreadByte() + buf.UnreadByte() return d.DecodeInt() } -func (resp *Response) decodeHeader(d *msgpack.Decoder) (err error) { +func decodeHeader(d *msgpack.Decoder, buf *smallBuf) (Header, iproto.Type, error) { var l int - d.Reset(&resp.buf) + var code int + var err error + d.Reset(buf) if l, err = d.DecodeMapLen(); err != nil { - return + return Header{}, 0, err } + decodedHeader := Header{Error: ErrorNo} for ; l > 0; l-- { var cd int - if cd, err = resp.smallInt(d); err != nil { - return + if cd, err = smallInt(d, buf); err != nil { + return Header{}, 0, err } switch iproto.Key(cd) { case iproto.IPROTO_SYNC: var rid uint64 if rid, err = d.DecodeUint64(); err != nil { - return + return Header{}, 0, err } - resp.RequestId = uint32(rid) + decodedHeader.RequestId = uint32(rid) case iproto.IPROTO_REQUEST_TYPE: - var rcode uint64 - if rcode, err = d.DecodeUint64(); err != nil { - return + if code, err = d.DecodeInt(); err != nil { + return Header{}, 0, err + } + if code&int(iproto.IPROTO_TYPE_ERROR) != 0 { + decodedHeader.Error = iproto.Error(code &^ int(iproto.IPROTO_TYPE_ERROR)) + } else { + decodedHeader.Error = ErrorNo } - resp.Code = uint32(rcode) default: if err = d.Skip(); err != nil { - return + return Header{}, 0, err } } } + return decodedHeader, iproto.Type(code), nil +} + +type decodeInfo struct { + stmtID uint64 + bindCount uint64 + serverProtocolInfo ProtocolInfo + errorExtendedInfo *BoxError + + decodedError string +} + +func (info *decodeInfo) parseData(resp *baseResponse) error { + if info.stmtID != 0 { + stmt := &Prepared{ + StatementID: PreparedID(info.stmtID), + ParamCount: info.bindCount, + } + resp.data = []interface{}{stmt} + return nil + } + + // Tarantool may send only version >= 1. + if info.serverProtocolInfo.Version != ProtocolVersion(0) || + info.serverProtocolInfo.Features != nil { + if info.serverProtocolInfo.Version == ProtocolVersion(0) { + return fmt.Errorf("no protocol version provided in Id response") + } + if info.serverProtocolInfo.Features == nil { + return fmt.Errorf("no features provided in Id response") + } + resp.data = []interface{}{info.serverProtocolInfo} + return nil + } return nil } -func (resp *Response) decodeBody() (err error) { +func decodeCommonField(d *msgpack.Decoder, cd int, data *[]interface{}, + info *decodeInfo) (bool, error) { + var feature iproto.Feature + var err error + + switch iproto.Key(cd) { + case iproto.IPROTO_DATA: + var res interface{} + var ok bool + if res, err = d.DecodeInterface(); err != nil { + return false, err + } + if *data, ok = res.([]interface{}); !ok { + return false, fmt.Errorf("result is not array: %v", res) + } + case iproto.IPROTO_ERROR: + if info.errorExtendedInfo, err = decodeBoxError(d); err != nil { + return false, err + } + case iproto.IPROTO_ERROR_24: + if info.decodedError, err = d.DecodeString(); err != nil { + return false, err + } + case iproto.IPROTO_STMT_ID: + if info.stmtID, err = d.DecodeUint64(); err != nil { + return false, err + } + case iproto.IPROTO_BIND_COUNT: + if info.bindCount, err = d.DecodeUint64(); err != nil { + return false, err + } + case iproto.IPROTO_VERSION: + if err = d.Decode(&info.serverProtocolInfo.Version); err != nil { + return false, err + } + case iproto.IPROTO_FEATURES: + var larr int + if larr, err = d.DecodeArrayLen(); err != nil { + return false, err + } + + info.serverProtocolInfo.Features = make([]iproto.Feature, larr) + for i := 0; i < larr; i++ { + if err = d.Decode(&feature); err != nil { + return false, err + } + info.serverProtocolInfo.Features[i] = feature + } + case iproto.IPROTO_AUTH_TYPE: + var auth string + if auth, err = d.DecodeString(); err != nil { + return false, err + } + found := false + for _, a := range [...]Auth{ChapSha1Auth, PapSha256Auth} { + if auth == a.String() { + info.serverProtocolInfo.Auth = a + found = true + } + } + if !found { + return false, fmt.Errorf("unknown auth type %s", auth) + } + default: + return false, nil + } + return true, nil +} + +func (resp *baseResponse) Decode() ([]interface{}, error) { + if resp.decoded { + return resp.data, resp.err + } + + resp.decoded = true + var err error if resp.buf.Len() > 2 { offset := resp.buf.Offset() defer resp.buf.Seek(offset) - var l, larr int - var stmtID, bindCount uint64 - var serverProtocolInfo ProtocolInfo - var feature iproto.Feature - var errorExtendedInfo *BoxError = nil + var l int + info := &decodeInfo{} d := msgpack.NewDecoder(&resp.buf) d.SetMapDecoder(func(dec *msgpack.Decoder) (interface{}, error) { @@ -165,123 +330,204 @@ func (resp *Response) decodeBody() (err error) { }) if l, err = d.DecodeMapLen(); err != nil { - return err + resp.err = err + return nil, resp.err } for ; l > 0; l-- { var cd int - if cd, err = resp.smallInt(d); err != nil { - return err + if cd, err = smallInt(d, &resp.buf); err != nil { + resp.err = err + return nil, resp.err } - switch iproto.Key(cd) { - case iproto.IPROTO_DATA: - var res interface{} - var ok bool - if res, err = d.DecodeInterface(); err != nil { - return err - } - if resp.Data, ok = res.([]interface{}); !ok { - return fmt.Errorf("result is not array: %v", res) - } - case iproto.IPROTO_ERROR: - if errorExtendedInfo, err = decodeBoxError(d); err != nil { - return err - } - case iproto.IPROTO_ERROR_24: - if resp.Error, err = d.DecodeString(); err != nil { - return err - } - case iproto.IPROTO_SQL_INFO: - if err = d.Decode(&resp.SQLInfo); err != nil { - return err - } - case iproto.IPROTO_METADATA: - if err = d.Decode(&resp.MetaData); err != nil { - return err - } - case iproto.IPROTO_STMT_ID: - if stmtID, err = d.DecodeUint64(); err != nil { - return err - } - case iproto.IPROTO_BIND_COUNT: - if bindCount, err = d.DecodeUint64(); err != nil { - return err - } - case iproto.IPROTO_VERSION: - if err = d.Decode(&serverProtocolInfo.Version); err != nil { - return err - } - case iproto.IPROTO_FEATURES: - if larr, err = d.DecodeArrayLen(); err != nil { - return err + decoded, err := decodeCommonField(d, cd, &resp.data, info) + if err != nil { + resp.err = err + return nil, resp.err + } + if !decoded { + if err = d.Skip(); err != nil { + resp.err = err + return nil, resp.err } + } + } + err = info.parseData(resp) + if err != nil { + resp.err = err + return nil, resp.err + } - serverProtocolInfo.Features = make([]iproto.Feature, larr) - for i := 0; i < larr; i++ { - if err = d.Decode(&feature); err != nil { - return err + if info.decodedError != "" { + resp.err = Error{resp.header.Error, info.decodedError, + info.errorExtendedInfo} + } + } + return resp.data, resp.err +} + +func (resp *SelectResponse) Decode() ([]interface{}, error) { + if resp.decoded { + return resp.data, resp.err + } + + resp.decoded = true + var err error + if resp.buf.Len() > 2 { + offset := resp.buf.Offset() + defer resp.buf.Seek(offset) + + var l int + info := &decodeInfo{} + + d := msgpack.NewDecoder(&resp.buf) + d.SetMapDecoder(func(dec *msgpack.Decoder) (interface{}, error) { + return dec.DecodeUntypedMap() + }) + + if l, err = d.DecodeMapLen(); err != nil { + resp.err = err + return nil, resp.err + } + for ; l > 0; l-- { + var cd int + if cd, err = smallInt(d, &resp.buf); err != nil { + resp.err = err + return nil, resp.err + } + decoded, err := decodeCommonField(d, cd, &resp.data, info) + if err != nil { + resp.err = err + return nil, err + } + if !decoded { + switch iproto.Key(cd) { + case iproto.IPROTO_POSITION: + if resp.pos, err = d.DecodeBytes(); err != nil { + resp.err = err + return nil, fmt.Errorf("unable to decode a position: %w", resp.err) } - serverProtocolInfo.Features[i] = feature - } - case iproto.IPROTO_AUTH_TYPE: - var auth string - if auth, err = d.DecodeString(); err != nil { - return err - } - found := false - for _, a := range [...]Auth{ChapSha1Auth, PapSha256Auth} { - if auth == a.String() { - serverProtocolInfo.Auth = a - found = true + default: + if err = d.Skip(); err != nil { + resp.err = err + return nil, resp.err } } - if !found { - return fmt.Errorf("unknown auth type %s", auth) - } - case iproto.IPROTO_POSITION: - if resp.Pos, err = d.DecodeBytes(); err != nil { - return fmt.Errorf("unable to decode a position: %w", err) - } - default: - if err = d.Skip(); err != nil { - return err - } } } - if stmtID != 0 { - stmt := &Prepared{ - StatementID: PreparedID(stmtID), - ParamCount: bindCount, - MetaData: resp.MetaData, - } - resp.Data = []interface{}{stmt} + err = info.parseData(&resp.baseResponse) + if err != nil { + resp.err = err + return nil, resp.err } - // Tarantool may send only version >= 1 - if serverProtocolInfo.Version != ProtocolVersion(0) || serverProtocolInfo.Features != nil { - if serverProtocolInfo.Version == ProtocolVersion(0) { - return fmt.Errorf("no protocol version provided in Id response") + if info.decodedError != "" { + resp.err = Error{resp.header.Error, info.decodedError, + info.errorExtendedInfo} + } + } + return resp.data, resp.err +} + +func (resp *ExecuteResponse) Decode() ([]interface{}, error) { + if resp.decoded { + return resp.data, resp.err + } + + resp.decoded = true + var err error + if resp.buf.Len() > 2 { + offset := resp.buf.Offset() + defer resp.buf.Seek(offset) + + var l int + info := &decodeInfo{} + + d := msgpack.NewDecoder(&resp.buf) + d.SetMapDecoder(func(dec *msgpack.Decoder) (interface{}, error) { + return dec.DecodeUntypedMap() + }) + + if l, err = d.DecodeMapLen(); err != nil { + resp.err = err + return nil, resp.err + } + for ; l > 0; l-- { + var cd int + if cd, err = smallInt(d, &resp.buf); err != nil { + resp.err = err + return nil, resp.err } - if serverProtocolInfo.Features == nil { - return fmt.Errorf("no features provided in Id response") + decoded, err := decodeCommonField(d, cd, &resp.data, info) + if err != nil { + resp.err = err + return nil, resp.err + } + if !decoded { + switch iproto.Key(cd) { + case iproto.IPROTO_SQL_INFO: + if err = d.Decode(&resp.sqlInfo); err != nil { + resp.err = err + return nil, resp.err + } + case iproto.IPROTO_METADATA: + if err = d.Decode(&resp.metaData); err != nil { + resp.err = err + return nil, resp.err + } + default: + if err = d.Skip(); err != nil { + resp.err = err + return nil, resp.err + } + } } - resp.Data = []interface{}{serverProtocolInfo} + } + err = info.parseData(&resp.baseResponse) + if err != nil { + resp.err = err + return nil, resp.err } - if resp.Code != OkCode && resp.Code != PushCode { - resp.Code &^= uint32(iproto.IPROTO_TYPE_ERROR) - err = Error{iproto.Error(resp.Code), resp.Error, errorExtendedInfo} + if info.decodedError != "" { + resp.err = Error{resp.header.Error, info.decodedError, + info.errorExtendedInfo} } } - return + return resp.data, resp.err } -func (resp *Response) decodeBodyTyped(res interface{}) (err error) { +func decodeTypedCommonField(d *msgpack.Decoder, res interface{}, cd int, + info *decodeInfo) (bool, error) { + var err error + + switch iproto.Key(cd) { + case iproto.IPROTO_DATA: + if err = d.Decode(res); err != nil { + return false, err + } + case iproto.IPROTO_ERROR: + if info.errorExtendedInfo, err = decodeBoxError(d); err != nil { + return false, err + } + case iproto.IPROTO_ERROR_24: + if info.decodedError, err = d.DecodeString(); err != nil { + return false, err + } + default: + return false, nil + } + return true, nil +} + +func (resp *baseResponse) DecodeTyped(res interface{}) error { + resp.decodedTyped = true + + var err error if resp.buf.Len() > 0 { offset := resp.buf.Offset() defer resp.buf.Seek(offset) - var errorExtendedInfo *BoxError = nil - + info := &decodeInfo{} var l int d := msgpack.NewDecoder(&resp.buf) @@ -294,67 +540,161 @@ func (resp *Response) decodeBodyTyped(res interface{}) (err error) { } for ; l > 0; l-- { var cd int - if cd, err = resp.smallInt(d); err != nil { + if cd, err = smallInt(d, &resp.buf); err != nil { return err } - switch iproto.Key(cd) { - case iproto.IPROTO_DATA: - if err = d.Decode(res); err != nil { - return err - } - case iproto.IPROTO_ERROR: - if errorExtendedInfo, err = decodeBoxError(d); err != nil { - return err - } - case iproto.IPROTO_ERROR_24: - if resp.Error, err = d.DecodeString(); err != nil { - return err - } - case iproto.IPROTO_SQL_INFO: - if err = d.Decode(&resp.SQLInfo); err != nil { - return err - } - case iproto.IPROTO_METADATA: - if err = d.Decode(&resp.MetaData); err != nil { - return err - } - case iproto.IPROTO_POSITION: - if resp.Pos, err = d.DecodeBytes(); err != nil { - return fmt.Errorf("unable to decode a position: %w", err) - } - default: + decoded, err := decodeTypedCommonField(d, res, cd, info) + if err != nil { + return err + } + if !decoded { if err = d.Skip(); err != nil { return err } } } - if resp.Code != OkCode && resp.Code != PushCode { - resp.Code &^= uint32(iproto.IPROTO_TYPE_ERROR) - err = Error{iproto.Error(resp.Code), resp.Error, errorExtendedInfo} + if info.decodedError != "" { + err = Error{resp.header.Error, info.decodedError, info.errorExtendedInfo} } } - return + return err } -// String implements Stringer interface. -func (resp *Response) String() (str string) { - if resp.Code == OkCode { - return fmt.Sprintf("<%d OK %v>", resp.RequestId, resp.Data) +func (resp *SelectResponse) DecodeTyped(res interface{}) error { + resp.decodedTyped = true + + var err error + if resp.buf.Len() > 0 { + offset := resp.buf.Offset() + defer resp.buf.Seek(offset) + + info := &decodeInfo{} + var l int + + d := msgpack.NewDecoder(&resp.buf) + d.SetMapDecoder(func(dec *msgpack.Decoder) (interface{}, error) { + return dec.DecodeUntypedMap() + }) + + if l, err = d.DecodeMapLen(); err != nil { + return err + } + for ; l > 0; l-- { + var cd int + if cd, err = smallInt(d, &resp.buf); err != nil { + return err + } + decoded, err := decodeTypedCommonField(d, res, cd, info) + if err != nil { + return err + } + if !decoded { + switch iproto.Key(cd) { + case iproto.IPROTO_POSITION: + if resp.pos, err = d.DecodeBytes(); err != nil { + return fmt.Errorf("unable to decode a position: %w", err) + } + default: + if err = d.Skip(); err != nil { + return err + } + } + } + } + if info.decodedError != "" { + err = Error{resp.header.Error, info.decodedError, info.errorExtendedInfo} + } } - return fmt.Sprintf("<%d ERR 0x%x %s>", resp.RequestId, resp.Code, resp.Error) + return err } -// Tuples converts result of Eval and Call to same format -// as other actions returns (i.e. array of arrays). -func (resp *Response) Tuples() (res [][]interface{}) { - res = make([][]interface{}, len(resp.Data)) - for i, t := range resp.Data { - switch t := t.(type) { - case []interface{}: - res[i] = t - default: - res[i] = []interface{}{t} +func (resp *ExecuteResponse) DecodeTyped(res interface{}) error { + resp.decodedTyped = true + + var err error + if resp.buf.Len() > 0 { + offset := resp.buf.Offset() + defer resp.buf.Seek(offset) + + info := &decodeInfo{} + var l int + + d := msgpack.NewDecoder(&resp.buf) + d.SetMapDecoder(func(dec *msgpack.Decoder) (interface{}, error) { + return dec.DecodeUntypedMap() + }) + + if l, err = d.DecodeMapLen(); err != nil { + return err } + for ; l > 0; l-- { + var cd int + if cd, err = smallInt(d, &resp.buf); err != nil { + return err + } + decoded, err := decodeTypedCommonField(d, res, cd, info) + if err != nil { + return err + } + if !decoded { + switch iproto.Key(cd) { + case iproto.IPROTO_SQL_INFO: + if err = d.Decode(&resp.sqlInfo); err != nil { + return err + } + case iproto.IPROTO_METADATA: + if err = d.Decode(&resp.metaData); err != nil { + return err + } + default: + if err = d.Skip(); err != nil { + return err + } + } + } + } + if info.decodedError != "" { + err = Error{resp.header.Error, info.decodedError, info.errorExtendedInfo} + } + } + return err +} + +func (resp *baseResponse) Header() Header { + return resp.header +} + +// Pos returns a position descriptor of the last selected tuple for the SelectResponse. +// If the response was not decoded, this method will call Decode(). +func (resp *SelectResponse) Pos() ([]byte, error) { + if !resp.decoded && !resp.decodedTyped { + resp.Decode() + } + return resp.pos, resp.err +} + +// MetaData returns ExecuteResponse meta-data. +// If the response was not decoded, this method will call Decode(). +func (resp *ExecuteResponse) MetaData() ([]ColumnMetaData, error) { + if !resp.decoded && !resp.decodedTyped { + resp.Decode() + } + return resp.metaData, resp.err +} + +// SQLInfo returns ExecuteResponse sql info. +// If the response was not decoded, this method will call Decode(). +func (resp *ExecuteResponse) SQLInfo() (SQLInfo, error) { + if !resp.decoded && !resp.decodedTyped { + resp.Decode() + } + return resp.sqlInfo, resp.err +} + +// String implements Stringer interface. +func (resp *baseResponse) String() (str string) { + if resp.header.Error == ErrorNo { + return fmt.Sprintf("<%d OK %v>", resp.header.RequestId, resp.data) } - return res + return fmt.Sprintf("<%d ERR %s %v>", resp.header.RequestId, resp.header.Error, resp.err) } diff --git a/response_it.go b/response_it.go index 404c68a51..f5a4517e0 100644 --- a/response_it.go +++ b/response_it.go @@ -12,7 +12,9 @@ type ResponseIterator interface { // Next tries to switch to a next Response and returns true if it exists. Next() bool // Value returns a current Response if it exists, nil otherwise. - Value() *Response + Value() Response + // IsPush returns true if the current response is a push response. + IsPush() bool // Err returns error if it happens. Err() error } diff --git a/schema.go b/schema.go index 6066adf49..72b5e397f 100644 --- a/schema.go +++ b/schema.go @@ -380,15 +380,18 @@ func (indexField *IndexField) DecodeMsgpack(d *msgpack.Decoder) error { return errors.New("unexpected schema format (index fields)") } -// GetSchema returns the actual schema for the connection. -func GetSchema(conn Connector) (Schema, error) { +// GetSchema returns the actual schema for the Doer. +func GetSchema(doer Doer) (Schema, error) { schema := Schema{} schema.SpacesById = make(map[uint32]Space) schema.Spaces = make(map[string]Space) // Reload spaces. var spaces []Space - err := conn.SelectTyped(vspaceSpId, 0, 0, maxSchemas, IterAll, []interface{}{}, &spaces) + req := NewSelectRequest(vspaceSpId). + Index(0). + Limit(maxSchemas) + err := doer.Do(req).GetTyped(&spaces) if err != nil { return Schema{}, err } @@ -399,7 +402,10 @@ func GetSchema(conn Connector) (Schema, error) { // Reload indexes. var indexes []Index - err = conn.SelectTyped(vindexSpId, 0, 0, maxSchemas, IterAll, []interface{}{}, &indexes) + req = NewSelectRequest(vindexSpId). + Index(0). + Limit(maxSchemas) + err = doer.Do(req).GetTyped(&indexes) if err != nil { return Schema{}, err } diff --git a/schema_test.go b/schema_test.go new file mode 100644 index 000000000..631591cb2 --- /dev/null +++ b/schema_test.go @@ -0,0 +1,162 @@ +package tarantool_test + +import ( + "bytes" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + "github.com/vmihailenco/msgpack/v5" + + "github.com/tarantool/go-tarantool/v2" + "github.com/tarantool/go-tarantool/v2/test_helpers" +) + +func TestGetSchema_ok(t *testing.T) { + space1 := tarantool.Space{ + Id: 1, + Name: "name1", + Indexes: make(map[string]tarantool.Index), + IndexesById: make(map[uint32]tarantool.Index), + Fields: make(map[string]tarantool.Field), + FieldsById: make(map[uint32]tarantool.Field), + } + index := tarantool.Index{ + Id: 1, + SpaceId: 2, + Name: "index_name", + Type: "index_type", + Unique: true, + Fields: make([]tarantool.IndexField, 0), + } + space2 := tarantool.Space{ + Id: 2, + Name: "name2", + Indexes: map[string]tarantool.Index{ + "index_name": index, + }, + IndexesById: map[uint32]tarantool.Index{ + 1: index, + }, + Fields: make(map[string]tarantool.Field), + FieldsById: make(map[uint32]tarantool.Field), + } + + mockDoer := test_helpers.NewMockDoer(t, + test_helpers.NewMockResponse(t, [][]interface{}{ + { + uint32(1), + "skip", + "name1", + "", + 0, + }, + { + uint32(2), + "skip", + "name2", + "", + 0, + }, + }), + test_helpers.NewMockResponse(t, [][]interface{}{ + { + uint32(2), + uint32(1), + "index_name", + "index_type", + uint8(1), + uint8(0), + }, + }), + ) + + expectedSchema := tarantool.Schema{ + SpacesById: map[uint32]tarantool.Space{ + 1: space1, + 2: space2, + }, + Spaces: map[string]tarantool.Space{ + "name1": space1, + "name2": space2, + }, + } + + schema, err := tarantool.GetSchema(&mockDoer) + require.NoError(t, err) + require.Equal(t, expectedSchema, schema) +} + +func TestGetSchema_spaces_select_error(t *testing.T) { + mockDoer := test_helpers.NewMockDoer(t, fmt.Errorf("some error")) + + schema, err := tarantool.GetSchema(&mockDoer) + require.EqualError(t, err, "some error") + require.Equal(t, tarantool.Schema{}, schema) +} + +func TestGetSchema_index_select_error(t *testing.T) { + mockDoer := test_helpers.NewMockDoer(t, + test_helpers.NewMockResponse(t, [][]interface{}{ + { + uint32(1), + "skip", + "name1", + "", + 0, + }, + }), + fmt.Errorf("some error")) + + schema, err := tarantool.GetSchema(&mockDoer) + require.EqualError(t, err, "some error") + require.Equal(t, tarantool.Schema{}, schema) +} + +func TestResolverCalledWithoutNameSupport(t *testing.T) { + resolver := ValidSchemeResolver{nameUseSupported: false} + + req := tarantool.NewSelectRequest("valid") + req.Index("valid") + + var reqBuf bytes.Buffer + reqEnc := msgpack.NewEncoder(&reqBuf) + + err := req.Body(&resolver, reqEnc) + if err != nil { + t.Errorf("An unexpected Response.Body() error: %q", err.Error()) + } + + if resolver.spaceResolverCalls != 1 { + t.Errorf("ResolveSpace was called %d times instead of 1.", + resolver.spaceResolverCalls) + } + if resolver.indexResolverCalls != 1 { + t.Errorf("ResolveIndex was called %d times instead of 1.", + resolver.indexResolverCalls) + } +} + +func TestResolverNotCalledWithNameSupport(t *testing.T) { + resolver := ValidSchemeResolver{nameUseSupported: true} + + req := tarantool.NewSelectRequest("valid") + req.Index("valid") + + var reqBuf bytes.Buffer + reqEnc := msgpack.NewEncoder(&reqBuf) + + err := req.Body(&resolver, reqEnc) + if err != nil { + t.Errorf("An unexpected Response.Body() error: %q", err.Error()) + } + + if resolver.spaceResolverCalls != 0 { + t.Errorf("ResolveSpace was called %d times instead of 0.", + resolver.spaceResolverCalls) + } + if resolver.indexResolverCalls != 0 { + t.Errorf("ResolveIndex was called %d times instead of 0.", + resolver.indexResolverCalls) + } +} diff --git a/settings/example_test.go b/settings/example_test.go index 47f8e8c43..e51cadef0 100644 --- a/settings/example_test.go +++ b/settings/example_test.go @@ -31,7 +31,7 @@ func example_connect(dialer tarantool.Dialer, opts tarantool.Opts) *tarantool.Co } func Example_sqlFullColumnNames() { - var resp *tarantool.Response + var resp tarantool.Response var err error var isLess bool @@ -69,13 +69,25 @@ func Example_sqlFullColumnNames() { // Get some data with SQL query. req = tarantool.NewExecuteRequest("SELECT x FROM example WHERE id = 1;") - _, err = conn.Do(req).Get() + resp, err = conn.Do(req).GetResponse() if err != nil { fmt.Printf("error on select: %v\n", err) return } + + exResp, ok := resp.(*tarantool.ExecuteResponse) + if !ok { + fmt.Printf("wrong response type") + return + } + + metaData, err := exResp.MetaData() + if err != nil { + fmt.Printf("error on getting MetaData: %v\n", err) + return + } // Show response metadata. - fmt.Printf("full column name: %v\n", resp.MetaData[0].FieldName) + fmt.Printf("full column name: %v\n", metaData[0].FieldName) // Disable showing full column names in SQL responses. _, err = conn.Do(settings.NewSQLFullColumnNamesSetRequest(false)).Get() @@ -85,11 +97,21 @@ func Example_sqlFullColumnNames() { } // Get some data with SQL query. - _, err = conn.Do(req).Get() + resp, err = conn.Do(req).GetResponse() if err != nil { fmt.Printf("error on select: %v\n", err) return } + exResp, ok = resp.(*tarantool.ExecuteResponse) + if !ok { + fmt.Printf("wrong response type") + return + } + metaData, err = exResp.MetaData() + if err != nil { + fmt.Printf("error on getting MetaData: %v\n", err) + return + } // Show response metadata. - fmt.Printf("short column name: %v\n", resp.MetaData[0].FieldName) + fmt.Printf("short column name: %v\n", metaData[0].FieldName) } diff --git a/settings/request.go b/settings/request.go index 02252fe47..1c106dc8d 100644 --- a/settings/request.go +++ b/settings/request.go @@ -60,6 +60,7 @@ package settings import ( "context" + "io" "github.com/tarantool/go-iproto" "github.com/vmihailenco/msgpack/v5" @@ -107,6 +108,12 @@ func (req *SetRequest) Async() bool { return req.impl.Async() } +// Response creates a response for the SetRequest. +func (req *SetRequest) Response(header tarantool.Header, + body io.Reader) (tarantool.Response, error) { + return req.impl.Response(header, body) +} + // GetRequest helps to get session settings. type GetRequest struct { impl *tarantool.SelectRequest @@ -147,6 +154,12 @@ func (req *GetRequest) Async() bool { return req.impl.Async() } +// Response creates a response for the GetRequest. +func (req *GetRequest) Response(header tarantool.Header, + body io.Reader) (tarantool.Response, error) { + return req.impl.Response(header, body) +} + // NewErrorMarshalingEnabledSetRequest creates a request to // update current session ErrorMarshalingEnabled setting. func NewErrorMarshalingEnabledSetRequest(value bool) *SetRequest { diff --git a/settings/tarantool_test.go b/settings/tarantool_test.go index 272693243..56cee33ce 100644 --- a/settings/tarantool_test.go +++ b/settings/tarantool_test.go @@ -52,48 +52,41 @@ func skipIfSQLDeferForeignKeysSettingUnsupported(t *testing.T) { func TestErrorMarshalingEnabledSetting(t *testing.T) { skipIfErrorMarshalingEnabledSettingUnsupported(t) - var resp *tarantool.Response var err error conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() // Disable receiving box.error as MP_EXT 3. - resp, err = conn.Do(NewErrorMarshalingEnabledSetRequest(false)).Get() + data, err := conn.Do(NewErrorMarshalingEnabledSetRequest(false)).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, []interface{}{[]interface{}{"error_marshaling_enabled", false}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"error_marshaling_enabled", false}}, data) // Fetch current setting value. - resp, err = conn.Do(NewErrorMarshalingEnabledGetRequest()).Get() + data, err = conn.Do(NewErrorMarshalingEnabledGetRequest()).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, []interface{}{[]interface{}{"error_marshaling_enabled", false}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"error_marshaling_enabled", false}}, data) // Get a box.Error value. eval := tarantool.NewEvalRequest("return box.error.new(box.error.UNKNOWN)") - resp, err = conn.Do(eval).Get() + data, err = conn.Do(eval).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.IsType(t, "string", resp.Data[0]) + require.IsType(t, "string", data[0]) // Enable receiving box.error as MP_EXT 3. - resp, err = conn.Do(NewErrorMarshalingEnabledSetRequest(true)).Get() + data, err = conn.Do(NewErrorMarshalingEnabledSetRequest(true)).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, []interface{}{[]interface{}{"error_marshaling_enabled", true}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"error_marshaling_enabled", true}}, data) // Fetch current setting value. - resp, err = conn.Do(NewErrorMarshalingEnabledGetRequest()).Get() + data, err = conn.Do(NewErrorMarshalingEnabledGetRequest()).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, []interface{}{[]interface{}{"error_marshaling_enabled", true}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"error_marshaling_enabled", true}}, data) // Get a box.Error value. - resp, err = conn.Do(eval).Get() + data, err = conn.Do(eval).Get() require.Nil(t, err) - require.NotNil(t, resp) - _, ok := resp.Data[0].(*tarantool.BoxError) + _, ok := data[0].(*tarantool.BoxError) require.True(t, ok) } @@ -101,70 +94,71 @@ func TestSQLDefaultEngineSetting(t *testing.T) { // https://github.com/tarantool/tarantool/blob/680990a082374e4790539215f69d9e9ee39c3307/test/sql/engine.test.lua skipIfSettingsUnsupported(t) - var resp *tarantool.Response var err error conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() // Set default SQL "CREATE TABLE" engine to "vinyl". - resp, err = conn.Do(NewSQLDefaultEngineSetRequest("vinyl")).Get() + data, err := conn.Do(NewSQLDefaultEngineSetRequest("vinyl")).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.EqualValues(t, []interface{}{[]interface{}{"sql_default_engine", "vinyl"}}, resp.Data) + require.EqualValues(t, []interface{}{[]interface{}{"sql_default_engine", "vinyl"}}, data) // Fetch current setting value. - resp, err = conn.Do(NewSQLDefaultEngineGetRequest()).Get() + data, err = conn.Do(NewSQLDefaultEngineGetRequest()).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, []interface{}{[]interface{}{"sql_default_engine", "vinyl"}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"sql_default_engine", "vinyl"}}, data) // Create a space with "CREATE TABLE". exec := tarantool.NewExecuteRequest("CREATE TABLE T1_VINYL(a INT PRIMARY KEY, b INT, c INT);") - resp, err = conn.Do(exec).Get() + resp, err := conn.Do(exec).GetResponse() require.Nil(t, err) require.NotNil(t, resp) - require.Equal(t, uint64(1), resp.SQLInfo.AffectedCount) + exResp, ok := resp.(*tarantool.ExecuteResponse) + require.True(t, ok, "wrong response type") + sqlInfo, err := exResp.SQLInfo() + require.Nil(t, err) + require.Equal(t, uint64(1), sqlInfo.AffectedCount) // Check new space engine. eval := tarantool.NewEvalRequest("return box.space['T1_VINYL'].engine") - resp, err = conn.Do(eval).Get() + data, err = conn.Do(eval).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, "vinyl", resp.Data[0]) + require.Equal(t, "vinyl", data[0]) // Set default SQL "CREATE TABLE" engine to "memtx". - resp, err = conn.Do(NewSQLDefaultEngineSetRequest("memtx")).Get() + data, err = conn.Do(NewSQLDefaultEngineSetRequest("memtx")).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, []interface{}{[]interface{}{"sql_default_engine", "memtx"}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"sql_default_engine", "memtx"}}, data) // Fetch current setting value. - resp, err = conn.Do(NewSQLDefaultEngineGetRequest()).Get() + data, err = conn.Do(NewSQLDefaultEngineGetRequest()).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, []interface{}{[]interface{}{"sql_default_engine", "memtx"}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"sql_default_engine", "memtx"}}, data) // Create a space with "CREATE TABLE". exec = tarantool.NewExecuteRequest("CREATE TABLE T2_MEMTX(a INT PRIMARY KEY, b INT, c INT);") - resp, err = conn.Do(exec).Get() + resp, err = conn.Do(exec).GetResponse() require.Nil(t, err) require.NotNil(t, resp) - require.Equal(t, uint64(1), resp.SQLInfo.AffectedCount) + exResp, ok = resp.(*tarantool.ExecuteResponse) + sqlInfo, err = exResp.SQLInfo() + require.Nil(t, err) + require.True(t, ok, "wrong response type") + require.Equal(t, uint64(1), sqlInfo.AffectedCount) // Check new space engine. eval = tarantool.NewEvalRequest("return box.space['T2_MEMTX'].engine") - resp, err = conn.Do(eval).Get() + data, err = conn.Do(eval).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, "memtx", resp.Data[0]) + require.Equal(t, "memtx", data[0]) } func TestSQLDeferForeignKeysSetting(t *testing.T) { // https://github.com/tarantool/tarantool/blob/eafadc13425f14446d7aaa49dea67dfc1d5f45e9/test/sql/transitive-transactions.result skipIfSQLDeferForeignKeysSettingUnsupported(t) - var resp *tarantool.Response + var resp tarantool.Response var err error conn := test_helpers.ConnectWithValidation(t, dialer, opts) @@ -172,18 +166,26 @@ func TestSQLDeferForeignKeysSetting(t *testing.T) { // Create a parent space. exec := tarantool.NewExecuteRequest("CREATE TABLE parent(id INT PRIMARY KEY, y INT UNIQUE);") - resp, err = conn.Do(exec).Get() + resp, err = conn.Do(exec).GetResponse() require.Nil(t, err) require.NotNil(t, resp) - require.Equal(t, uint64(1), resp.SQLInfo.AffectedCount) + exResp, ok := resp.(*tarantool.ExecuteResponse) + require.True(t, ok, "wrong response type") + sqlInfo, err := exResp.SQLInfo() + require.Nil(t, err) + require.Equal(t, uint64(1), sqlInfo.AffectedCount) // Create a space with reference to the parent space. exec = tarantool.NewExecuteRequest( "CREATE TABLE child(id INT PRIMARY KEY, x INT REFERENCES parent(y));") - resp, err = conn.Do(exec).Get() + resp, err = conn.Do(exec).GetResponse() require.Nil(t, err) require.NotNil(t, resp) - require.Equal(t, uint64(1), resp.SQLInfo.AffectedCount) + exResp, ok = resp.(*tarantool.ExecuteResponse) + require.True(t, ok, "wrong response type") + sqlInfo, err = exResp.SQLInfo() + require.Nil(t, err) + require.Equal(t, uint64(1), sqlInfo.AffectedCount) deferEval := ` box.begin() @@ -198,16 +200,14 @@ func TestSQLDeferForeignKeysSetting(t *testing.T) { ` // Disable foreign key constraint checks before commit. - resp, err = conn.Do(NewSQLDeferForeignKeysSetRequest(false)).Get() + data, err := conn.Do(NewSQLDeferForeignKeysSetRequest(false)).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, []interface{}{[]interface{}{"sql_defer_foreign_keys", false}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"sql_defer_foreign_keys", false}}, data) // Fetch current setting value. - resp, err = conn.Do(NewSQLDeferForeignKeysGetRequest()).Get() + data, err = conn.Do(NewSQLDeferForeignKeysGetRequest()).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, []interface{}{[]interface{}{"sql_defer_foreign_keys", false}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"sql_defer_foreign_keys", false}}, data) // Evaluate a scenario when foreign key not exists // on INSERT, but exists on commit. @@ -215,29 +215,26 @@ func TestSQLDeferForeignKeysSetting(t *testing.T) { require.NotNil(t, err) require.ErrorContains(t, err, "Failed to execute SQL statement: FOREIGN KEY constraint failed") - resp, err = conn.Do(NewSQLDeferForeignKeysSetRequest(true)).Get() + data, err = conn.Do(NewSQLDeferForeignKeysSetRequest(true)).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, []interface{}{[]interface{}{"sql_defer_foreign_keys", true}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"sql_defer_foreign_keys", true}}, data) // Fetch current setting value. - resp, err = conn.Do(NewSQLDeferForeignKeysGetRequest()).Get() + data, err = conn.Do(NewSQLDeferForeignKeysGetRequest()).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, []interface{}{[]interface{}{"sql_defer_foreign_keys", true}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"sql_defer_foreign_keys", true}}, data) // Evaluate a scenario when foreign key not exists // on INSERT, but exists on commit. - resp, err = conn.Do(tarantool.NewEvalRequest(deferEval)).Get() + data, err = conn.Do(tarantool.NewEvalRequest(deferEval)).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, true, resp.Data[0]) + require.Equal(t, true, data[0]) } func TestSQLFullColumnNamesSetting(t *testing.T) { skipIfSettingsUnsupported(t) - var resp *tarantool.Response + var resp tarantool.Response var err error conn := test_helpers.ConnectWithValidation(t, dialer, opts) @@ -245,61 +242,73 @@ func TestSQLFullColumnNamesSetting(t *testing.T) { // Create a space. exec := tarantool.NewExecuteRequest("CREATE TABLE FKNAME(ID INT PRIMARY KEY, X INT);") - resp, err = conn.Do(exec).Get() + resp, err = conn.Do(exec).GetResponse() require.Nil(t, err) require.NotNil(t, resp) - require.Equal(t, uint64(1), resp.SQLInfo.AffectedCount) + exResp, ok := resp.(*tarantool.ExecuteResponse) + require.True(t, ok, "wrong response type") + sqlInfo, err := exResp.SQLInfo() + require.Nil(t, err) + require.Equal(t, uint64(1), sqlInfo.AffectedCount) // Fill it with some data. exec = tarantool.NewExecuteRequest("INSERT INTO FKNAME VALUES (1, 1);") - resp, err = conn.Do(exec).Get() + resp, err = conn.Do(exec).GetResponse() require.Nil(t, err) require.NotNil(t, resp) - require.Equal(t, uint64(1), resp.SQLInfo.AffectedCount) + exResp, ok = resp.(*tarantool.ExecuteResponse) + require.True(t, ok, "wrong response type") + sqlInfo, err = exResp.SQLInfo() + require.Nil(t, err) + require.Equal(t, uint64(1), sqlInfo.AffectedCount) // Disable displaying full column names in metadata. - resp, err = conn.Do(NewSQLFullColumnNamesSetRequest(false)).Get() + data, err := conn.Do(NewSQLFullColumnNamesSetRequest(false)).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, []interface{}{[]interface{}{"sql_full_column_names", false}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"sql_full_column_names", false}}, data) // Fetch current setting value. - resp, err = conn.Do(NewSQLFullColumnNamesGetRequest()).Get() + data, err = conn.Do(NewSQLFullColumnNamesGetRequest()).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, []interface{}{[]interface{}{"sql_full_column_names", false}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"sql_full_column_names", false}}, data) // Get a data with short column names in metadata. exec = tarantool.NewExecuteRequest("SELECT X FROM FKNAME WHERE ID = 1;") - resp, err = conn.Do(exec).Get() + resp, err = conn.Do(exec).GetResponse() require.Nil(t, err) require.NotNil(t, resp) - require.Equal(t, "X", resp.MetaData[0].FieldName) + exResp, ok = resp.(*tarantool.ExecuteResponse) + require.True(t, ok, "wrong response type") + metaData, err := exResp.MetaData() + require.Nil(t, err) + require.Equal(t, "X", metaData[0].FieldName) // Enable displaying full column names in metadata. - resp, err = conn.Do(NewSQLFullColumnNamesSetRequest(true)).Get() + data, err = conn.Do(NewSQLFullColumnNamesSetRequest(true)).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, []interface{}{[]interface{}{"sql_full_column_names", true}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"sql_full_column_names", true}}, data) // Fetch current setting value. - resp, err = conn.Do(NewSQLFullColumnNamesGetRequest()).Get() + data, err = conn.Do(NewSQLFullColumnNamesGetRequest()).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, []interface{}{[]interface{}{"sql_full_column_names", true}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"sql_full_column_names", true}}, data) // Get a data with full column names in metadata. exec = tarantool.NewExecuteRequest("SELECT X FROM FKNAME WHERE ID = 1;") - resp, err = conn.Do(exec).Get() + resp, err = conn.Do(exec).GetResponse() require.Nil(t, err) require.NotNil(t, resp) - require.Equal(t, "FKNAME.X", resp.MetaData[0].FieldName) + exResp, ok = resp.(*tarantool.ExecuteResponse) + require.True(t, ok, "wrong response type") + metaData, err = exResp.MetaData() + require.Nil(t, err) + require.Equal(t, "FKNAME.X", metaData[0].FieldName) } func TestSQLFullMetadataSetting(t *testing.T) { skipIfSettingsUnsupported(t) - var resp *tarantool.Response + var resp tarantool.Response var err error conn := test_helpers.ConnectWithValidation(t, dialer, opts) @@ -307,88 +316,96 @@ func TestSQLFullMetadataSetting(t *testing.T) { // Create a space. exec := tarantool.NewExecuteRequest("CREATE TABLE fmt(id INT PRIMARY KEY, x INT);") - resp, err = conn.Do(exec).Get() + resp, err = conn.Do(exec).GetResponse() require.Nil(t, err) require.NotNil(t, resp) - require.Equal(t, uint64(1), resp.SQLInfo.AffectedCount) + exResp, ok := resp.(*tarantool.ExecuteResponse) + require.True(t, ok, "wrong response type") + sqlInfo, err := exResp.SQLInfo() + require.Nil(t, err) + require.Equal(t, uint64(1), sqlInfo.AffectedCount) // Fill it with some data. exec = tarantool.NewExecuteRequest("INSERT INTO fmt VALUES (1, 1);") - resp, err = conn.Do(exec).Get() + resp, err = conn.Do(exec).GetResponse() require.Nil(t, err) require.NotNil(t, resp) - require.Equal(t, uint64(1), resp.SQLInfo.AffectedCount) + exResp, ok = resp.(*tarantool.ExecuteResponse) + require.True(t, ok, "wrong response type") + sqlInfo, err = exResp.SQLInfo() + require.Nil(t, err) + require.Equal(t, uint64(1), sqlInfo.AffectedCount) // Disable displaying additional fields in metadata. - resp, err = conn.Do(NewSQLFullMetadataSetRequest(false)).Get() + data, err := conn.Do(NewSQLFullMetadataSetRequest(false)).Get() require.Nil(t, err) - require.Equal(t, []interface{}{[]interface{}{"sql_full_metadata", false}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"sql_full_metadata", false}}, data) // Fetch current setting value. - resp, err = conn.Do(NewSQLFullMetadataGetRequest()).Get() + data, err = conn.Do(NewSQLFullMetadataGetRequest()).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, []interface{}{[]interface{}{"sql_full_metadata", false}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"sql_full_metadata", false}}, data) // Get a data without additional fields in metadata. exec = tarantool.NewExecuteRequest("SELECT x FROM fmt WHERE id = 1;") - resp, err = conn.Do(exec).Get() + resp, err = conn.Do(exec).GetResponse() require.Nil(t, err) require.NotNil(t, resp) - require.Equal(t, "", resp.MetaData[0].FieldSpan) + exResp, ok = resp.(*tarantool.ExecuteResponse) + require.True(t, ok, "wrong response type") + metaData, err := exResp.MetaData() + require.Nil(t, err) + require.Equal(t, "", metaData[0].FieldSpan) // Enable displaying full column names in metadata. - resp, err = conn.Do(NewSQLFullMetadataSetRequest(true)).Get() + data, err = conn.Do(NewSQLFullMetadataSetRequest(true)).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, []interface{}{[]interface{}{"sql_full_metadata", true}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"sql_full_metadata", true}}, data) // Fetch current setting value. - resp, err = conn.Do(NewSQLFullMetadataGetRequest()).Get() + data, err = conn.Do(NewSQLFullMetadataGetRequest()).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, []interface{}{[]interface{}{"sql_full_metadata", true}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"sql_full_metadata", true}}, data) // Get a data with additional fields in metadata. exec = tarantool.NewExecuteRequest("SELECT x FROM fmt WHERE id = 1;") - resp, err = conn.Do(exec).Get() + resp, err = conn.Do(exec).GetResponse() require.Nil(t, err) require.NotNil(t, resp) - require.Equal(t, "x", resp.MetaData[0].FieldSpan) + exResp, ok = resp.(*tarantool.ExecuteResponse) + require.True(t, ok, "wrong response type") + metaData, err = exResp.MetaData() + require.Nil(t, err) + require.Equal(t, "x", metaData[0].FieldSpan) } func TestSQLParserDebugSetting(t *testing.T) { skipIfSettingsUnsupported(t) - var resp *tarantool.Response var err error conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() // Disable parser debug mode. - resp, err = conn.Do(NewSQLParserDebugSetRequest(false)).Get() + data, err := conn.Do(NewSQLParserDebugSetRequest(false)).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, []interface{}{[]interface{}{"sql_parser_debug", false}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"sql_parser_debug", false}}, data) // Fetch current setting value. - resp, err = conn.Do(NewSQLParserDebugGetRequest()).Get() + data, err = conn.Do(NewSQLParserDebugGetRequest()).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, []interface{}{[]interface{}{"sql_parser_debug", false}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"sql_parser_debug", false}}, data) // Enable parser debug mode. - resp, err = conn.Do(NewSQLParserDebugSetRequest(true)).Get() + data, err = conn.Do(NewSQLParserDebugSetRequest(true)).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, []interface{}{[]interface{}{"sql_parser_debug", true}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"sql_parser_debug", true}}, data) // Fetch current setting value. - resp, err = conn.Do(NewSQLParserDebugGetRequest()).Get() + data, err = conn.Do(NewSQLParserDebugGetRequest()).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, []interface{}{[]interface{}{"sql_parser_debug", true}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"sql_parser_debug", true}}, data) // To test real effect we need a Tarantool instance built with // `-DCMAKE_BUILD_TYPE=Debug`. @@ -398,7 +415,7 @@ func TestSQLRecursiveTriggersSetting(t *testing.T) { // https://github.com/tarantool/tarantool/blob/d11fb3061e15faf4e0eb5375fb8056b4e64348ae/test/sql-tap/triggerC.test.lua skipIfSettingsUnsupported(t) - var resp *tarantool.Response + var resp tarantool.Response var err error conn := test_helpers.ConnectWithValidation(t, dialer, opts) @@ -406,39 +423,49 @@ func TestSQLRecursiveTriggersSetting(t *testing.T) { // Create a space. exec := tarantool.NewExecuteRequest("CREATE TABLE rec(id INTEGER PRIMARY KEY, a INT, b INT);") - resp, err = conn.Do(exec).Get() + resp, err = conn.Do(exec).GetResponse() require.Nil(t, err) require.NotNil(t, resp) - require.Equal(t, uint64(1), resp.SQLInfo.AffectedCount) + exResp, ok := resp.(*tarantool.ExecuteResponse) + require.True(t, ok, "wrong response type") + sqlInfo, err := exResp.SQLInfo() + require.Nil(t, err) + require.Equal(t, uint64(1), sqlInfo.AffectedCount) // Fill it with some data. exec = tarantool.NewExecuteRequest("INSERT INTO rec VALUES(1, 1, 2);") - resp, err = conn.Do(exec).Get() + resp, err = conn.Do(exec).GetResponse() require.Nil(t, err) require.NotNil(t, resp) - require.Equal(t, uint64(1), resp.SQLInfo.AffectedCount) + exResp, ok = resp.(*tarantool.ExecuteResponse) + require.True(t, ok, "wrong response type") + sqlInfo, err = exResp.SQLInfo() + require.Nil(t, err) + require.Equal(t, uint64(1), sqlInfo.AffectedCount) // Create a recursive trigger (with infinite depth). exec = tarantool.NewExecuteRequest(` CREATE TRIGGER tr12 AFTER UPDATE ON rec FOR EACH ROW BEGIN UPDATE rec SET a=new.a+1, b=new.b+1; END;`) - resp, err = conn.Do(exec).Get() + resp, err = conn.Do(exec).GetResponse() require.Nil(t, err) require.NotNil(t, resp) - require.Equal(t, uint64(1), resp.SQLInfo.AffectedCount) + exResp, ok = resp.(*tarantool.ExecuteResponse) + require.True(t, ok, "wrong response type") + sqlInfo, err = exResp.SQLInfo() + require.Nil(t, err) + require.Equal(t, uint64(1), sqlInfo.AffectedCount) // Enable SQL recursive triggers. - resp, err = conn.Do(NewSQLRecursiveTriggersSetRequest(true)).Get() + data, err := conn.Do(NewSQLRecursiveTriggersSetRequest(true)).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, []interface{}{[]interface{}{"sql_recursive_triggers", true}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"sql_recursive_triggers", true}}, data) // Fetch current setting value. - resp, err = conn.Do(NewSQLRecursiveTriggersGetRequest()).Get() + data, err = conn.Do(NewSQLRecursiveTriggersGetRequest()).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, []interface{}{[]interface{}{"sql_recursive_triggers", true}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"sql_recursive_triggers", true}}, data) // Trigger the recursion. exec = tarantool.NewExecuteRequest("UPDATE rec SET a=a+1, b=b+1;") @@ -448,29 +475,31 @@ func TestSQLRecursiveTriggersSetting(t *testing.T) { "Failed to execute SQL statement: too many levels of trigger recursion") // Disable SQL recursive triggers. - resp, err = conn.Do(NewSQLRecursiveTriggersSetRequest(false)).Get() + data, err = conn.Do(NewSQLRecursiveTriggersSetRequest(false)).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, []interface{}{[]interface{}{"sql_recursive_triggers", false}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"sql_recursive_triggers", false}}, data) // Fetch current setting value. - resp, err = conn.Do(NewSQLRecursiveTriggersGetRequest()).Get() + data, err = conn.Do(NewSQLRecursiveTriggersGetRequest()).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, []interface{}{[]interface{}{"sql_recursive_triggers", false}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"sql_recursive_triggers", false}}, data) // Trigger the recursion. exec = tarantool.NewExecuteRequest("UPDATE rec SET a=a+1, b=b+1;") - resp, err = conn.Do(exec).Get() + resp, err = conn.Do(exec).GetResponse() require.Nil(t, err) require.NotNil(t, resp) - require.Equal(t, uint64(1), resp.SQLInfo.AffectedCount) + exResp, ok = resp.(*tarantool.ExecuteResponse) + require.True(t, ok, "wrong response type") + sqlInfo, err = exResp.SQLInfo() + require.Nil(t, err) + require.Equal(t, uint64(1), sqlInfo.AffectedCount) } func TestSQLReverseUnorderedSelectsSetting(t *testing.T) { skipIfSettingsUnsupported(t) - var resp *tarantool.Response + var resp tarantool.Response var err error conn := test_helpers.ConnectWithValidation(t, dialer, opts) @@ -478,37 +507,47 @@ func TestSQLReverseUnorderedSelectsSetting(t *testing.T) { // Create a space. exec := tarantool.NewExecuteRequest("CREATE TABLE data(id STRING PRIMARY KEY);") - resp, err = conn.Do(exec).Get() + resp, err = conn.Do(exec).GetResponse() require.Nil(t, err) require.NotNil(t, resp) - require.Equal(t, uint64(1), resp.SQLInfo.AffectedCount) + exResp, ok := resp.(*tarantool.ExecuteResponse) + require.True(t, ok, "wrong response type") + sqlInfo, err := exResp.SQLInfo() + require.Nil(t, err) + require.Equal(t, uint64(1), sqlInfo.AffectedCount) // Fill it with some data. exec = tarantool.NewExecuteRequest("INSERT INTO data VALUES('1');") - resp, err = conn.Do(exec).Get() + resp, err = conn.Do(exec).GetResponse() require.Nil(t, err) require.NotNil(t, resp) - require.Equal(t, uint64(1), resp.SQLInfo.AffectedCount) + exResp, ok = resp.(*tarantool.ExecuteResponse) + require.True(t, ok, "wrong response type") + sqlInfo, err = exResp.SQLInfo() + require.Nil(t, err) + require.Equal(t, uint64(1), sqlInfo.AffectedCount) exec = tarantool.NewExecuteRequest("INSERT INTO data VALUES('2');") - resp, err = conn.Do(exec).Get() + resp, err = conn.Do(exec).GetResponse() require.Nil(t, err) require.NotNil(t, resp) - require.Equal(t, uint64(1), resp.SQLInfo.AffectedCount) + exResp, ok = resp.(*tarantool.ExecuteResponse) + require.True(t, ok, "wrong response type") + sqlInfo, err = exResp.SQLInfo() + require.Nil(t, err) + require.Equal(t, uint64(1), sqlInfo.AffectedCount) // Disable reverse order in unordered selects. - resp, err = conn.Do(NewSQLReverseUnorderedSelectsSetRequest(false)).Get() + data, err := conn.Do(NewSQLReverseUnorderedSelectsSetRequest(false)).Get() require.Nil(t, err) - require.NotNil(t, resp) require.Equal(t, []interface{}{[]interface{}{"sql_reverse_unordered_selects", false}}, - resp.Data) + data) // Fetch current setting value. - resp, err = conn.Do(NewSQLReverseUnorderedSelectsGetRequest()).Get() + data, err = conn.Do(NewSQLReverseUnorderedSelectsGetRequest()).Get() require.Nil(t, err) - require.NotNil(t, resp) require.Equal(t, []interface{}{[]interface{}{"sql_reverse_unordered_selects", false}}, - resp.Data) + data) // Select multiple records. query := "SELECT * FROM seqscan data;" @@ -518,66 +557,57 @@ func TestSQLReverseUnorderedSelectsSetting(t *testing.T) { query = "SELECT * FROM data;" } - resp, err = conn.Do(tarantool.NewExecuteRequest(query)).Get() + data, err = conn.Do(tarantool.NewExecuteRequest(query)).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.EqualValues(t, []interface{}{"1"}, resp.Data[0]) - require.EqualValues(t, []interface{}{"2"}, resp.Data[1]) + require.EqualValues(t, []interface{}{"1"}, data[0]) + require.EqualValues(t, []interface{}{"2"}, data[1]) // Enable reverse order in unordered selects. - resp, err = conn.Do(NewSQLReverseUnorderedSelectsSetRequest(true)).Get() + data, err = conn.Do(NewSQLReverseUnorderedSelectsSetRequest(true)).Get() require.Nil(t, err) - require.NotNil(t, resp) require.Equal(t, []interface{}{[]interface{}{"sql_reverse_unordered_selects", true}}, - resp.Data) + data) // Fetch current setting value. - resp, err = conn.Do(NewSQLReverseUnorderedSelectsGetRequest()).Get() + data, err = conn.Do(NewSQLReverseUnorderedSelectsGetRequest()).Get() require.Nil(t, err) - require.NotNil(t, resp) require.Equal(t, []interface{}{[]interface{}{"sql_reverse_unordered_selects", true}}, - resp.Data) + data) // Select multiple records. - resp, err = conn.Do(tarantool.NewExecuteRequest(query)).Get() + data, err = conn.Do(tarantool.NewExecuteRequest(query)).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.EqualValues(t, []interface{}{"2"}, resp.Data[0]) - require.EqualValues(t, []interface{}{"1"}, resp.Data[1]) + require.EqualValues(t, []interface{}{"2"}, data[0]) + require.EqualValues(t, []interface{}{"1"}, data[1]) } func TestSQLSelectDebugSetting(t *testing.T) { skipIfSettingsUnsupported(t) - var resp *tarantool.Response var err error conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() // Disable select debug mode. - resp, err = conn.Do(NewSQLSelectDebugSetRequest(false)).Get() + data, err := conn.Do(NewSQLSelectDebugSetRequest(false)).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, []interface{}{[]interface{}{"sql_select_debug", false}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"sql_select_debug", false}}, data) // Fetch current setting value. - resp, err = conn.Do(NewSQLSelectDebugGetRequest()).Get() + data, err = conn.Do(NewSQLSelectDebugGetRequest()).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, []interface{}{[]interface{}{"sql_select_debug", false}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"sql_select_debug", false}}, data) // Enable select debug mode. - resp, err = conn.Do(NewSQLSelectDebugSetRequest(true)).Get() + data, err = conn.Do(NewSQLSelectDebugSetRequest(true)).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, []interface{}{[]interface{}{"sql_select_debug", true}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"sql_select_debug", true}}, data) // Fetch current setting value. - resp, err = conn.Do(NewSQLSelectDebugGetRequest()).Get() + data, err = conn.Do(NewSQLSelectDebugGetRequest()).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, []interface{}{[]interface{}{"sql_select_debug", true}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"sql_select_debug", true}}, data) // To test real effect we need a Tarantool instance built with // `-DCMAKE_BUILD_TYPE=Debug`. @@ -586,35 +616,30 @@ func TestSQLSelectDebugSetting(t *testing.T) { func TestSQLVDBEDebugSetting(t *testing.T) { skipIfSettingsUnsupported(t) - var resp *tarantool.Response var err error conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() // Disable VDBE debug mode. - resp, err = conn.Do(NewSQLVDBEDebugSetRequest(false)).Get() + data, err := conn.Do(NewSQLVDBEDebugSetRequest(false)).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, []interface{}{[]interface{}{"sql_vdbe_debug", false}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"sql_vdbe_debug", false}}, data) // Fetch current setting value. - resp, err = conn.Do(NewSQLVDBEDebugGetRequest()).Get() + data, err = conn.Do(NewSQLVDBEDebugGetRequest()).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, []interface{}{[]interface{}{"sql_vdbe_debug", false}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"sql_vdbe_debug", false}}, data) // Enable VDBE debug mode. - resp, err = conn.Do(NewSQLVDBEDebugSetRequest(true)).Get() + data, err = conn.Do(NewSQLVDBEDebugSetRequest(true)).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, []interface{}{[]interface{}{"sql_vdbe_debug", true}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"sql_vdbe_debug", true}}, data) // Fetch current setting value. - resp, err = conn.Do(NewSQLVDBEDebugGetRequest()).Get() + data, err = conn.Do(NewSQLVDBEDebugGetRequest()).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, []interface{}{[]interface{}{"sql_vdbe_debug", true}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"sql_vdbe_debug", true}}, data) // To test real effect we need a Tarantool instance built with // `-DCMAKE_BUILD_TYPE=Debug`. @@ -623,28 +648,24 @@ func TestSQLVDBEDebugSetting(t *testing.T) { func TestSessionSettings(t *testing.T) { skipIfSettingsUnsupported(t) - var resp *tarantool.Response var err error conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() // Set some settings values. - resp, err = conn.Do(NewSQLDefaultEngineSetRequest("memtx")).Get() + data, err := conn.Do(NewSQLDefaultEngineSetRequest("memtx")).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, []interface{}{[]interface{}{"sql_default_engine", "memtx"}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"sql_default_engine", "memtx"}}, data) - resp, err = conn.Do(NewSQLFullColumnNamesSetRequest(true)).Get() + data, err = conn.Do(NewSQLFullColumnNamesSetRequest(true)).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, []interface{}{[]interface{}{"sql_full_column_names", true}}, resp.Data) + require.Equal(t, []interface{}{[]interface{}{"sql_full_column_names", true}}, data) // Fetch current settings values. - resp, err = conn.Do(NewSessionSettingsGetRequest()).Get() + data, err = conn.Do(NewSessionSettingsGetRequest()).Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Subset(t, resp.Data, + require.Subset(t, data, []interface{}{ []interface{}{"sql_default_engine", "memtx"}, []interface{}{"sql_full_column_names", true}, diff --git a/shutdown_test.go b/shutdown_test.go index 80996e3a9..b3a09eff0 100644 --- a/shutdown_test.go +++ b/shutdown_test.go @@ -47,7 +47,6 @@ var evalBody = ` ` func testGracefulShutdown(t *testing.T, conn *Connection, inst *test_helpers.TarantoolInstance) { - var resp *Response var err error // Set a big timeout so it would be easy to differ @@ -102,10 +101,9 @@ func testGracefulShutdown(t *testing.T, conn *Connection, inst *test_helpers.Tar require.Nil(t, err) // Check that requests started before the shutdown finish successfully. - resp, err = fut.Get() + data, err := fut.Get() require.Nil(t, err) - require.NotNil(t, resp) - require.Equal(t, resp.Data, []interface{}{evalMsg}) + require.Equal(t, data, []interface{}{evalMsg}) // Wait until server go down. // Server will go down only when it process all requests from our connection diff --git a/stream.go b/stream.go index 5144ea6f1..43e80fc28 100644 --- a/stream.go +++ b/stream.go @@ -199,7 +199,7 @@ func (req *RollbackRequest) Context(ctx context.Context) *RollbackRequest { func (s *Stream) Do(req Request) *Future { if connectedReq, ok := req.(ConnectedRequest); ok { if connectedReq.Conn() != s.Conn { - fut := NewFuture() + fut := NewFuture(req) fut.SetError(errUnknownStreamRequest) return fut } diff --git a/tarantool_test.go b/tarantool_test.go index 4d3f193f1..c3f6b4c0b 100644 --- a/tarantool_test.go +++ b/tarantool_test.go @@ -699,6 +699,35 @@ func BenchmarkSQLSerial(b *testing.B) { } } +type mockRequest struct { + conn *Connection +} + +func (req *mockRequest) Type() iproto.Type { + return iproto.Type(0) +} + +func (req *mockRequest) Async() bool { + return false +} + +func (req *mockRequest) Body(resolver SchemaResolver, enc *msgpack.Encoder) error { + return nil +} + +func (req *mockRequest) Conn() *Connection { + return req.conn +} + +func (req *mockRequest) Ctx() context.Context { + return nil +} + +func (req *mockRequest) Response(header Header, + body io.Reader) (Response, error) { + return nil, fmt.Errorf("some error") +} + func TestNetDialer(t *testing.T) { assert := assert.New(t) require := require.New(t) @@ -759,12 +788,12 @@ func TestFutureMultipleGetGetTyped(t *testing.T) { } if get { - resp, err := fut.Get() + data, err := fut.Get() if err != nil { t.Errorf("Failed to call Get(): %s", err) } - if val, ok := resp.Data[0].(string); !ok || val != "11" { - t.Errorf("Wrong Get() result: %v", resp.Data) + if val, ok := data[0].(string); !ok || val != "11" { + t.Errorf("Wrong Get() result: %v", data) } } else { tpl := struct { @@ -821,39 +850,29 @@ func TestFutureMultipleGetTypedWithError(t *testing.T) { /////////////////// func TestClient(t *testing.T) { - var resp *Response var err error conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() // Ping - resp, err = conn.Ping() + data, err := conn.Ping() if err != nil { - t.Fatalf("Failed to Ping: %s", err.Error()) + t.Fatalf("Failed to Ping: %s", err) } - if resp == nil { - t.Fatalf("Response is nil after Ping") - } - if resp.Pos != nil { - t.Errorf("Response should not have a position") + if data != nil { + t.Fatalf("Response data is not nil after Ping") } // Insert - resp, err = conn.Insert(spaceNo, []interface{}{uint(1), "hello", "world"}) + data, err = conn.Insert(spaceNo, []interface{}{uint(1), "hello", "world"}) if err != nil { - t.Fatalf("Failed to Insert: %s", err.Error()) - } - if resp == nil { - t.Fatalf("Response is nil after Insert") - } - if resp.Pos != nil { - t.Errorf("Response should not have a position") + t.Fatalf("Failed to Insert: %s", err) } - if len(resp.Data) != 1 { + if len(data) != 1 { t.Errorf("Response Body len != 1") } - if tpl, ok := resp.Data[0].([]interface{}); !ok { + if tpl, ok := data[0].([]interface{}); !ok { t.Errorf("Unexpected body of Insert") } else { if len(tpl) != 3 { @@ -866,29 +885,23 @@ func TestClient(t *testing.T) { t.Errorf("Unexpected body of Insert (1)") } } - resp, err = conn.Insert(spaceNo, &Tuple{Id: 1, Msg: "hello", Name: "world"}) + data, err = conn.Insert(spaceNo, &Tuple{Id: 1, Msg: "hello", Name: "world"}) if tntErr, ok := err.(Error); !ok || tntErr.Code != iproto.ER_TUPLE_FOUND { t.Errorf("Expected %s but got: %v", iproto.ER_TUPLE_FOUND, err) } - if len(resp.Data) != 0 { + if len(data) != 0 { t.Errorf("Response Body len != 0") } // Delete - resp, err = conn.Delete(spaceNo, indexNo, []interface{}{uint(1)}) + data, err = conn.Delete(spaceNo, indexNo, []interface{}{uint(1)}) if err != nil { - t.Fatalf("Failed to Delete: %s", err.Error()) + t.Fatalf("Failed to Delete: %s", err) } - if resp == nil { - t.Fatalf("Response is nil after Delete") - } - if resp.Pos != nil { - t.Errorf("Response should not have a position") - } - if len(resp.Data) != 1 { + if len(data) != 1 { t.Errorf("Response Body len != 1") } - if tpl, ok := resp.Data[0].([]interface{}); !ok { + if tpl, ok := data[0].([]interface{}); !ok { t.Errorf("Unexpected body of Delete") } else { if len(tpl) != 3 { @@ -901,42 +914,30 @@ func TestClient(t *testing.T) { t.Errorf("Unexpected body of Delete (1)") } } - resp, err = conn.Delete(spaceNo, indexNo, []interface{}{uint(101)}) + data, err = conn.Delete(spaceNo, indexNo, []interface{}{uint(101)}) if err != nil { - t.Fatalf("Failed to Delete: %s", err.Error()) - } - if resp == nil { - t.Fatalf("Response is nil after Delete") - } - if resp.Pos != nil { - t.Errorf("Response should not have a position") + t.Fatalf("Failed to Delete: %s", err) } - if len(resp.Data) != 0 { + if len(data) != 0 { t.Errorf("Response Data len != 0") } // Replace - resp, err = conn.Replace(spaceNo, []interface{}{uint(2), "hello", "world"}) + data, err = conn.Replace(spaceNo, []interface{}{uint(2), "hello", "world"}) if err != nil { - t.Fatalf("Failed to Replace: %s", err.Error()) + t.Fatalf("Failed to Replace: %s", err) } - if resp == nil { + if data == nil { t.Fatalf("Response is nil after Replace") } - if resp.Pos != nil { - t.Errorf("Response should not have a position") - } - resp, err = conn.Replace(spaceNo, []interface{}{uint(2), "hi", "planet"}) + data, err = conn.Replace(spaceNo, []interface{}{uint(2), "hi", "planet"}) if err != nil { - t.Fatalf("Failed to Replace (duplicate): %s", err.Error()) + t.Fatalf("Failed to Replace (duplicate): %s", err) } - if resp == nil { - t.Fatalf("Response is nil after Replace (duplicate)") - } - if len(resp.Data) != 1 { + if len(data) != 1 { t.Errorf("Response Data len != 1") } - if tpl, ok := resp.Data[0].([]interface{}); !ok { + if tpl, ok := data[0].([]interface{}); !ok { t.Errorf("Unexpected body of Replace") } else { if len(tpl) != 3 { @@ -951,21 +952,15 @@ func TestClient(t *testing.T) { } // Update - resp, err = conn.Update(spaceNo, indexNo, []interface{}{uint(2)}, + data, err = conn.Update(spaceNo, indexNo, []interface{}{uint(2)}, NewOperations().Assign(1, "bye").Delete(2, 1)) if err != nil { - t.Fatalf("Failed to Update: %s", err.Error()) - } - if resp == nil { - t.Fatalf("Response is nil after Update") + t.Fatalf("Failed to Update: %s", err) } - if resp.Pos != nil { - t.Errorf("Response should not have a position") - } - if len(resp.Data) != 1 { + if len(data) != 1 { t.Errorf("Response Data len != 1") } - if tpl, ok := resp.Data[0].([]interface{}); !ok { + if tpl, ok := data[0].([]interface{}); !ok { t.Errorf("Unexpected body of Update") } else { if len(tpl) != 2 { @@ -980,53 +975,41 @@ func TestClient(t *testing.T) { } // Upsert - resp, err = conn.Upsert(spaceNo, []interface{}{uint(3), 1}, + data, err = conn.Upsert(spaceNo, []interface{}{uint(3), 1}, NewOperations().Add(1, 1)) if err != nil { - t.Fatalf("Failed to Upsert (insert): %s", err.Error()) + t.Fatalf("Failed to Upsert (insert): %s", err) } - if resp == nil { + if data == nil { t.Fatalf("Response is nil after Upsert (insert)") } - if resp.Pos != nil { - t.Errorf("Response should not have a position") - } - resp, err = conn.Upsert(spaceNo, []interface{}{uint(3), 1}, + data, err = conn.Upsert(spaceNo, []interface{}{uint(3), 1}, NewOperations().Add(1, 1)) if err != nil { - t.Fatalf("Failed to Upsert (update): %s", err.Error()) + t.Fatalf("Failed to Upsert (update): %s", err) } - if resp == nil { + if data == nil { t.Errorf("Response is nil after Upsert (update)") } // Select for i := 10; i < 20; i++ { - resp, err = conn.Replace(spaceNo, []interface{}{uint(i), fmt.Sprintf("val %d", i), "bla"}) + data, err = conn.Replace(spaceNo, []interface{}{uint(i), fmt.Sprintf("val %d", i), "bla"}) if err != nil { - t.Fatalf("Failed to Replace: %s", err.Error()) - } - if resp.Pos != nil { - t.Errorf("Response should not have a position") + t.Fatalf("Failed to Replace: %s", err) } - if resp.Code != 0 { - t.Errorf("Failed to replace") + if data == nil { + t.Errorf("Response is nil after Replace") } } - resp, err = conn.Select(spaceNo, indexNo, 0, 1, IterEq, []interface{}{uint(10)}) + data, err = conn.Select(spaceNo, indexNo, 0, 1, IterEq, []interface{}{uint(10)}) if err != nil { - t.Fatalf("Failed to Select: %s", err.Error()) - } - if resp == nil { - t.Fatalf("Response is nil after Select") - } - if resp.Pos != nil { - t.Errorf("Response should not have a position") + t.Fatalf("Failed to Select: %s", err) } - if len(resp.Data) != 1 { - t.Errorf("Response Data len != 1") + if len(data) != 1 { + t.Fatalf("Response Data len != 1") } - if tpl, ok := resp.Data[0].([]interface{}); !ok { + if tpl, ok := data[0].([]interface{}); !ok { t.Errorf("Unexpected body of Select") } else { if id, err := test_helpers.ConvertUint64(tpl[0]); err != nil || id != 10 { @@ -1038,17 +1021,11 @@ func TestClient(t *testing.T) { } // Select empty - resp, err = conn.Select(spaceNo, indexNo, 0, 1, IterEq, []interface{}{uint(30)}) + data, err = conn.Select(spaceNo, indexNo, 0, 1, IterEq, []interface{}{uint(30)}) if err != nil { - t.Fatalf("Failed to Select: %s", err.Error()) - } - if resp == nil { - t.Fatalf("Response is nil after Select") - } - if resp.Pos != nil { - t.Errorf("Response should not have a position") + t.Fatalf("Failed to Select: %s", err) } - if len(resp.Data) != 0 { + if len(data) != 0 { t.Errorf("Response Data len != 0") } @@ -1056,7 +1033,7 @@ func TestClient(t *testing.T) { var tpl []Tuple err = conn.SelectTyped(spaceNo, indexNo, 0, 1, IterEq, []interface{}{uint(10)}, &tpl) if err != nil { - t.Fatalf("Failed to SelectTyped: %s", err.Error()) + t.Fatalf("Failed to SelectTyped: %s", err) } if len(tpl) != 1 { t.Errorf("Result len of SelectTyped != 1") @@ -1068,7 +1045,7 @@ func TestClient(t *testing.T) { var singleTpl = Tuple{} err = conn.GetTyped(spaceNo, indexNo, []interface{}{uint(10)}, &singleTpl) if err != nil { - t.Fatalf("Failed to GetTyped: %s", err.Error()) + t.Fatalf("Failed to GetTyped: %s", err) } if singleTpl.Id != 10 { t.Errorf("Bad value loaded from GetTyped") @@ -1078,7 +1055,7 @@ func TestClient(t *testing.T) { var tpl1 [1]Tuple err = conn.SelectTyped(spaceNo, indexNo, 0, 1, IterEq, []interface{}{uint(10)}, &tpl1) if err != nil { - t.Fatalf("Failed to SelectTyped: %s", err.Error()) + t.Fatalf("Failed to SelectTyped: %s", err) } if len(tpl) != 1 { t.Errorf("Result len of SelectTyped != 1") @@ -1090,7 +1067,7 @@ func TestClient(t *testing.T) { var singleTpl2 Tuple err = conn.GetTyped(spaceNo, indexNo, []interface{}{uint(30)}, &singleTpl2) if err != nil { - t.Fatalf("Failed to GetTyped: %s", err.Error()) + t.Fatalf("Failed to GetTyped: %s", err) } if singleTpl2.Id != 0 { t.Errorf("Bad value loaded from GetTyped") @@ -1100,65 +1077,47 @@ func TestClient(t *testing.T) { var tpl2 []Tuple err = conn.SelectTyped(spaceNo, indexNo, 0, 1, IterEq, []interface{}{uint(30)}, &tpl2) if err != nil { - t.Fatalf("Failed to SelectTyped: %s", err.Error()) + t.Fatalf("Failed to SelectTyped: %s", err) } if len(tpl2) != 0 { t.Errorf("Result len of SelectTyped != 1") } // Call16 - resp, err = conn.Call16("box.info", []interface{}{"box.schema.SPACE_ID"}) + data, err = conn.Call16("box.info", []interface{}{"box.schema.SPACE_ID"}) if err != nil { - t.Fatalf("Failed to Call16: %s", err.Error()) - } - if resp == nil { - t.Fatalf("Response is nil after Call16") + t.Fatalf("Failed to Call16: %s", err) } - if resp.Pos != nil { - t.Errorf("Response should not have a position") - } - if len(resp.Data) < 1 { + if len(data) < 1 { t.Errorf("Response.Data is empty after Eval") } // Call16 vs Call17 - resp, err = conn.Call16("simple_concat", []interface{}{"1"}) + data, err = conn.Call16("simple_concat", []interface{}{"1"}) if err != nil { t.Errorf("Failed to use Call16") } - if resp.Pos != nil { - t.Errorf("Response should not have a position") - } - if val, ok := resp.Data[0].([]interface{})[0].(string); !ok || val != "11" { - t.Errorf("result is not {{1}} : %v", resp.Data) + if val, ok := data[0].([]interface{})[0].(string); !ok || val != "11" { + t.Errorf("result is not {{1}} : %v", data) } - resp, err = conn.Call17("simple_concat", []interface{}{"1"}) + data, err = conn.Call17("simple_concat", []interface{}{"1"}) if err != nil { t.Errorf("Failed to use Call") } - if resp.Pos != nil { - t.Errorf("Response should not have a position") - } - if val, ok := resp.Data[0].(string); !ok || val != "11" { - t.Errorf("result is not {{1}} : %v", resp.Data) + if val, ok := data[0].(string); !ok || val != "11" { + t.Errorf("result is not {{1}} : %v", data) } // Eval - resp, err = conn.Eval("return 5 + 6", []interface{}{}) + data, err = conn.Eval("return 5 + 6", []interface{}{}) if err != nil { - t.Fatalf("Failed to Eval: %s", err.Error()) - } - if resp == nil { - t.Fatalf("Response is nil after Eval") - } - if resp.Pos != nil { - t.Errorf("Response should not have a position") + t.Fatalf("Failed to Eval: %s", err) } - if len(resp.Data) < 1 { + if len(data) < 1 { t.Errorf("Response.Data is empty after Eval") } - if val, err := test_helpers.ConvertUint64(resp.Data[0]); err != nil || val != 11 { + if val, err := test_helpers.ConvertUint64(data[0]); err != nil || val != 11 { t.Errorf("5 + 6 == 11, but got %v", val) } } @@ -1196,15 +1155,13 @@ func TestClientSessionPush(t *testing.T) { // It will wait a response before iteration. fut1 := conn.Call17Async("push_func", []interface{}{pushMax}) // Future.Get ignores push messages. - resp, err := fut1.Get() + data, err := fut1.Get() if err != nil { - t.Errorf("Failed to Call17: %s", err.Error()) - } else if resp == nil { - t.Errorf("Response is nil after CallAsync") - } else if len(resp.Data) < 1 { + t.Errorf("Failed to Call17: %s", err) + } else if len(data) < 1 { t.Errorf("Response.Data is empty after Call17Async") - } else if val, err := test_helpers.ConvertUint64(resp.Data[0]); err != nil || val != pushMax { - t.Errorf("Result is not %d: %v", pushMax, resp.Data) + } else if val, err := test_helpers.ConvertUint64(data[0]); err != nil || val != pushMax { + t.Errorf("Result is not %d: %v", pushMax, data) } // It will will be iterated with a timeout. @@ -1221,29 +1178,31 @@ func TestClientSessionPush(t *testing.T) { it = its[i] for it.Next() { - resp = it.Value() + resp := it.Value() if resp == nil { t.Errorf("Response is empty after it.Next() == true") break } - if resp.Pos != nil { - t.Errorf("Response should not have a position") + data, err := resp.Decode() + if err != nil { + t.Errorf("Failed to Decode: %s", err) + break } - if len(resp.Data) < 1 { + if len(data) < 1 { t.Errorf("Response.Data is empty after CallAsync") break } - if resp.Code == PushCode { + if it.IsPush() { pushCnt += 1 - val, err := test_helpers.ConvertUint64(resp.Data[0]) + val, err := test_helpers.ConvertUint64(data[0]) if err != nil || val != pushCnt { - t.Errorf("Unexpected push data = %v", resp.Data) + t.Errorf("Unexpected push data = %v", data) } } else { respCnt += 1 - val, err := test_helpers.ConvertUint64(resp.Data[0]) + val, err := test_helpers.ConvertUint64(data[0]) if err != nil || val != pushMax { - t.Errorf("Result is not %d: %v", pushMax, resp.Data) + t.Errorf("Result is not %d: %v", pushMax, data) } } } @@ -1263,13 +1222,13 @@ func TestClientSessionPush(t *testing.T) { // We can collect original responses after iterations. for _, fut := range []*Future{fut0, fut1, fut2} { - resp, err := fut.Get() + data, err := fut.Get() if err != nil { t.Errorf("Unable to call fut.Get(): %s", err) } - val, err := test_helpers.ConvertUint64(resp.Data[0]) + val, err := test_helpers.ConvertUint64(data[0]) if err != nil || val != pushMax { - t.Errorf("Result is not %d: %v", pushMax, resp.Data) + t.Errorf("Result is not %d: %v", pushMax, data) } tpl := struct { @@ -1311,9 +1270,11 @@ func TestSQL(t *testing.T) { test_helpers.SkipIfSQLUnsupported(t) type testCase struct { - Query string - Args interface{} - Resp Response + Query string + Args interface{} + sqlInfo SQLInfo + data []interface{} + metaData []ColumnMetaData } selectSpanDifQuery := selectSpanDifQueryNew @@ -1327,20 +1288,16 @@ func TestSQL(t *testing.T) { { createTableQuery, []interface{}{}, - Response{ - SQLInfo: SQLInfo{AffectedCount: 1}, - Data: []interface{}{}, - MetaData: nil, - }, + SQLInfo{AffectedCount: 1}, + []interface{}{}, + nil, }, { insertQuery, []interface{}{"1", "test"}, - Response{ - SQLInfo: SQLInfo{AffectedCount: 1}, - Data: []interface{}{}, - MetaData: nil, - }, + SQLInfo{AffectedCount: 1}, + []interface{}{}, + nil, }, { selectNamedQuery, @@ -1348,119 +1305,100 @@ func TestSQL(t *testing.T) { "ID": "1", "NAME": "test", }, - Response{ - SQLInfo: SQLInfo{AffectedCount: 0}, - Data: []interface{}{[]interface{}{"1", "test"}}, - MetaData: []ColumnMetaData{ - {FieldType: "string", FieldName: "ID"}, - {FieldType: "string", FieldName: "NAME"}}, - }, + SQLInfo{AffectedCount: 0}, + []interface{}{[]interface{}{"1", "test"}}, + []ColumnMetaData{ + {FieldType: "string", FieldName: "ID"}, + {FieldType: "string", FieldName: "NAME"}}, }, { selectPosQuery, []interface{}{"1", "test"}, - Response{ - SQLInfo: SQLInfo{AffectedCount: 0}, - Data: []interface{}{[]interface{}{"1", "test"}}, - MetaData: []ColumnMetaData{ - {FieldType: "string", FieldName: "ID"}, - {FieldType: "string", FieldName: "NAME"}}, - }, + SQLInfo{AffectedCount: 0}, + []interface{}{[]interface{}{"1", "test"}}, + []ColumnMetaData{ + {FieldType: "string", FieldName: "ID"}, + {FieldType: "string", FieldName: "NAME"}}, }, { updateQuery, []interface{}{"test_test", "1"}, - Response{ - SQLInfo: SQLInfo{AffectedCount: 1}, - Data: []interface{}{}, - MetaData: nil, - }, + SQLInfo{AffectedCount: 1}, + []interface{}{}, + nil, }, { enableFullMetaDataQuery, []interface{}{}, - Response{ - SQLInfo: SQLInfo{AffectedCount: 1}, - Data: []interface{}{}, - MetaData: nil, - }, + SQLInfo{AffectedCount: 1}, + []interface{}{}, + nil, }, { selectSpanDifQuery, []interface{}{"test_test"}, - Response{ - SQLInfo: SQLInfo{AffectedCount: 0}, - Data: []interface{}{[]interface{}{"11", "test_test", "1"}}, - MetaData: []ColumnMetaData{ - { - FieldType: "string", - FieldName: "COLUMN_1", - FieldIsNullable: false, - FieldIsAutoincrement: false, - FieldSpan: "ID||ID", - }, - { - FieldType: "string", - FieldName: "NAME", - FieldIsNullable: true, - FieldIsAutoincrement: false, - FieldSpan: "NAME", - FieldCollation: "unicode", - }, - { - FieldType: "string", - FieldName: "ID", - FieldIsNullable: false, - FieldIsAutoincrement: false, - FieldSpan: "ID", - FieldCollation: "", - }, - }}, + SQLInfo{AffectedCount: 0}, + []interface{}{[]interface{}{"11", "test_test", "1"}}, + []ColumnMetaData{ + { + FieldType: "string", + FieldName: "COLUMN_1", + FieldIsNullable: false, + FieldIsAutoincrement: false, + FieldSpan: "ID||ID", + }, + { + FieldType: "string", + FieldName: "NAME", + FieldIsNullable: true, + FieldIsAutoincrement: false, + FieldSpan: "NAME", + FieldCollation: "unicode", + }, + { + FieldType: "string", + FieldName: "ID", + FieldIsNullable: false, + FieldIsAutoincrement: false, + FieldSpan: "ID", + FieldCollation: "", + }, + }, }, { alterTableQuery, []interface{}{}, - Response{ - SQLInfo: SQLInfo{AffectedCount: 0}, - Data: []interface{}{}, - MetaData: nil, - }, + SQLInfo{AffectedCount: 0}, + []interface{}{}, + nil, }, { insertIncrQuery, []interface{}{"2", "test_2"}, - Response{ - SQLInfo: SQLInfo{AffectedCount: 1, InfoAutoincrementIds: []uint64{1}}, - Data: []interface{}{}, - MetaData: nil, - }, + SQLInfo{AffectedCount: 1, InfoAutoincrementIds: []uint64{1}}, + []interface{}{}, + nil, }, { deleteQuery, []interface{}{"test_2"}, - Response{ - SQLInfo: SQLInfo{AffectedCount: 1}, - Data: []interface{}{}, - MetaData: nil, - }, + SQLInfo{AffectedCount: 1}, + []interface{}{}, + nil, }, { dropQuery, []interface{}{}, - Response{ - SQLInfo: SQLInfo{AffectedCount: 1}, - Data: []interface{}{}, - MetaData: nil, - }, + SQLInfo{AffectedCount: 1}, + []interface{}{}, + nil, }, { disableFullMetaDataQuery, []interface{}{}, - Response{ - SQLInfo: SQLInfo{AffectedCount: 1}, - Data: []interface{}{}, - MetaData: nil, - }, + SQLInfo{AffectedCount: 1}, + []interface{}{}, + nil, }, } @@ -1468,26 +1406,27 @@ func TestSQL(t *testing.T) { defer conn.Close() for i, test := range testCases { - resp, err := conn.Execute(test.Query, test.Args) + req := NewExecuteRequest(test.Query).Args(test.Args) + resp, err := conn.Do(req).GetResponse() assert.NoError(t, err, "Failed to Execute, query: %s", test.Query) assert.NotNil(t, resp, "Response is nil after Execute\nQuery number: %d", i) - for j := range resp.Data { - assert.Equal(t, resp.Data[j], test.Resp.Data[j], "Response data is wrong") - } - assert.Equal(t, resp.SQLInfo.AffectedCount, test.Resp.SQLInfo.AffectedCount, + data, err := resp.Decode() + assert.NoError(t, err, "Failed to Decode") + for j := range data { + assert.Equal(t, data[j], test.data[j], "Response data is wrong") + } + exResp, ok := resp.(*ExecuteResponse) + assert.True(t, ok, "Got wrong response type") + sqlInfo, err := exResp.SQLInfo() + assert.NoError(t, err, "Error while getting SQLInfo") + assert.Equal(t, sqlInfo.AffectedCount, test.sqlInfo.AffectedCount, "Affected count is wrong") errorMsg := "Response Metadata is wrong" - for j := range resp.MetaData { - assert.Equal(t, resp.MetaData[j].FieldIsAutoincrement, - test.Resp.MetaData[j].FieldIsAutoincrement, errorMsg) - assert.Equal(t, resp.MetaData[j].FieldIsNullable, - test.Resp.MetaData[j].FieldIsNullable, errorMsg) - assert.Equal(t, resp.MetaData[j].FieldCollation, - test.Resp.MetaData[j].FieldCollation, errorMsg) - assert.Equal(t, resp.MetaData[j].FieldName, test.Resp.MetaData[j].FieldName, errorMsg) - assert.Equal(t, resp.MetaData[j].FieldSpan, test.Resp.MetaData[j].FieldSpan, errorMsg) - assert.Equal(t, resp.MetaData[j].FieldType, test.Resp.MetaData[j].FieldType, errorMsg) + metaData, err := exResp.MetaData() + assert.NoError(t, err, "Error while getting MetaData") + for j := range metaData { + assert.Equal(t, metaData[j], test.metaData[j], errorMsg) } } } @@ -1522,7 +1461,7 @@ func TestSQLBindings(t *testing.T) { 1: "test", } - var resp *Response + var resp Response conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() @@ -1568,55 +1507,82 @@ func TestSQLBindings(t *testing.T) { } for _, bind := range namedSQLBinds { - resp, err := conn.Execute(selectNamedQuery2, bind) + req := NewExecuteRequest(selectNamedQuery2).Args(bind) + resp, err := conn.Do(req).GetResponse() if err != nil { - t.Fatalf("Failed to Execute: %s", err.Error()) + t.Fatalf("Failed to Execute: %s", err) } if resp == nil { t.Fatal("Response is nil after Execute") } - if reflect.DeepEqual(resp.Data[0], []interface{}{1, testData[1]}) { + data, err := resp.Decode() + if err != nil { + t.Errorf("Failed to Decode: %s", err) + } + if reflect.DeepEqual(data[0], []interface{}{1, testData[1]}) { t.Error("Select with named arguments failed") } - if resp.MetaData[0].FieldType != "unsigned" || - resp.MetaData[0].FieldName != "NAME0" || - resp.MetaData[1].FieldType != "string" || - resp.MetaData[1].FieldName != "NAME1" { + exResp, ok := resp.(*ExecuteResponse) + assert.True(t, ok, "Got wrong response type") + metaData, err := exResp.MetaData() + assert.NoError(t, err, "Error while getting MetaData") + if metaData[0].FieldType != "unsigned" || + metaData[0].FieldName != "NAME0" || + metaData[1].FieldType != "string" || + metaData[1].FieldName != "NAME1" { t.Error("Wrong metadata") } } - resp, err := conn.Execute(selectPosQuery2, sqlBind5) + req := NewExecuteRequest(selectPosQuery2).Args(sqlBind5) + resp, err := conn.Do(req).GetResponse() if err != nil { - t.Fatalf("Failed to Execute: %s", err.Error()) + t.Fatalf("Failed to Execute: %s", err) } if resp == nil { t.Fatal("Response is nil after Execute") } - if reflect.DeepEqual(resp.Data[0], []interface{}{1, testData[1]}) { + data, err := resp.Decode() + if err != nil { + t.Errorf("Failed to Decode: %s", err) + } + if reflect.DeepEqual(data[0], []interface{}{1, testData[1]}) { t.Error("Select with positioned arguments failed") } - if resp.MetaData[0].FieldType != "unsigned" || - resp.MetaData[0].FieldName != "NAME0" || - resp.MetaData[1].FieldType != "string" || - resp.MetaData[1].FieldName != "NAME1" { + exResp, ok := resp.(*ExecuteResponse) + assert.True(t, ok, "Got wrong response type") + metaData, err := exResp.MetaData() + assert.NoError(t, err, "Error while getting MetaData") + if metaData[0].FieldType != "unsigned" || + metaData[0].FieldName != "NAME0" || + metaData[1].FieldType != "string" || + metaData[1].FieldName != "NAME1" { t.Error("Wrong metadata") } - resp, err = conn.Execute(mixedQuery, sqlBind6) + req = NewExecuteRequest(mixedQuery).Args(sqlBind6) + resp, err = conn.Do(req).GetResponse() if err != nil { - t.Fatalf("Failed to Execute: %s", err.Error()) + t.Fatalf("Failed to Execute: %s", err) } if resp == nil { t.Fatal("Response is nil after Execute") } - if reflect.DeepEqual(resp.Data[0], []interface{}{1, testData[1]}) { + data, err = resp.Decode() + if err != nil { + t.Errorf("Failed to Decode: %s", err) + } + if reflect.DeepEqual(data[0], []interface{}{1, testData[1]}) { t.Error("Select with positioned arguments failed") } - if resp.MetaData[0].FieldType != "unsigned" || - resp.MetaData[0].FieldName != "NAME0" || - resp.MetaData[1].FieldType != "string" || - resp.MetaData[1].FieldName != "NAME1" { + exResp, ok = resp.(*ExecuteResponse) + assert.True(t, ok, "Got wrong response type") + metaData, err = exResp.MetaData() + assert.NoError(t, err, "Error while getting MetaData") + if metaData[0].FieldType != "unsigned" || + metaData[0].FieldName != "NAME0" || + metaData[1].FieldType != "string" || + metaData[1].FieldName != "NAME1" { t.Error("Wrong metadata") } } @@ -1624,99 +1590,134 @@ func TestSQLBindings(t *testing.T) { func TestStressSQL(t *testing.T) { test_helpers.SkipIfSQLUnsupported(t) - var resp *Response - conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() - resp, err := conn.Execute(createTableQuery, []interface{}{}) + req := NewExecuteRequest(createTableQuery) + resp, err := conn.Do(req).GetResponse() if err != nil { - t.Fatalf("Failed to Execute: %s", err.Error()) + t.Fatalf("Failed to create an Execute: %s", err) } if resp == nil { t.Fatal("Response is nil after Execute") } - if resp.Code != 0 { - t.Fatalf("Failed to Execute: %d", resp.Code) - } - if resp.SQLInfo.AffectedCount != 1 { - t.Errorf("Incorrect count of created spaces: %d", resp.SQLInfo.AffectedCount) + exResp, ok := resp.(*ExecuteResponse) + assert.True(t, ok, "Got wrong response type") + sqlInfo, err := exResp.SQLInfo() + assert.NoError(t, err, "Error while getting SQLInfo") + if sqlInfo.AffectedCount != 1 { + t.Errorf("Incorrect count of created spaces: %d", sqlInfo.AffectedCount) } // create table with the same name - resp, err = conn.Execute(createTableQuery, []interface{}{}) - if err == nil { - t.Fatal("Unexpected lack of error") + req = NewExecuteRequest(createTableQuery) + resp, err = conn.Do(req).GetResponse() + if err != nil { + t.Fatalf("Failed to create an Execute: %s", err) } if resp == nil { t.Fatal("Response is nil after Execute") } + _, err = resp.Decode() + assert.NotNil(t, err, "Expected error while decoding") - if iproto.Error(resp.Code) != iproto.ER_SPACE_EXISTS { - t.Fatalf("Unexpected response code: %d", resp.Code) + tntErr, ok := err.(Error) + assert.True(t, ok) + assert.Equal(t, iproto.ER_SPACE_EXISTS, tntErr.Code) + if resp.Header().Error != iproto.ER_SPACE_EXISTS { + t.Fatalf("Unexpected response error: %d", resp.Header().Error) } - if resp.SQLInfo.AffectedCount != 0 { - t.Errorf("Incorrect count of created spaces: %d", resp.SQLInfo.AffectedCount) + prevErr := err + + exResp, ok = resp.(*ExecuteResponse) + assert.True(t, ok, "Got wrong response type") + sqlInfo, err = exResp.SQLInfo() + assert.Equal(t, prevErr, err) + if sqlInfo.AffectedCount != 0 { + t.Errorf("Incorrect count of created spaces: %d", sqlInfo.AffectedCount) } // execute with nil argument - resp, err = conn.Execute(createTableQuery, nil) - if err == nil { - t.Fatal("Unexpected lack of error") + req = NewExecuteRequest(createTableQuery).Args(nil) + resp, err = conn.Do(req).GetResponse() + if err != nil { + t.Fatalf("Failed to create an Execute: %s", err) } if resp == nil { t.Fatal("Response is nil after Execute") } - if resp.Code == 0 { - t.Fatalf("Unexpected response code: %d", resp.Code) + if resp.Header().Error == ErrorNo { + t.Fatal("Unexpected successful Execute") } - if resp.SQLInfo.AffectedCount != 0 { - t.Errorf("Incorrect count of created spaces: %d", resp.SQLInfo.AffectedCount) + exResp, ok = resp.(*ExecuteResponse) + assert.True(t, ok, "Got wrong response type") + sqlInfo, err = exResp.SQLInfo() + assert.NotNil(t, err, "Expected an error") + if sqlInfo.AffectedCount != 0 { + t.Errorf("Incorrect count of created spaces: %d", sqlInfo.AffectedCount) } // execute with zero string - resp, err = conn.Execute("", []interface{}{}) - if err == nil { - t.Fatal("Unexpected lack of error") + req = NewExecuteRequest("") + resp, err = conn.Do(req).GetResponse() + if err != nil { + t.Fatalf("Failed to create an Execute: %s", err) } if resp == nil { t.Fatal("Response is nil after Execute") } - if resp.Code == 0 { - t.Fatalf("Unexpected response code: %d", resp.Code) + if resp.Header().Error == ErrorNo { + t.Fatal("Unexpected successful Execute") } - if resp.SQLInfo.AffectedCount != 0 { - t.Errorf("Incorrect count of created spaces: %d", resp.SQLInfo.AffectedCount) + exResp, ok = resp.(*ExecuteResponse) + assert.True(t, ok, "Got wrong response type") + sqlInfo, err = exResp.SQLInfo() + assert.NotNil(t, err, "Expected an error") + if sqlInfo.AffectedCount != 0 { + t.Errorf("Incorrect count of created spaces: %d", sqlInfo.AffectedCount) } // drop table query - resp, err = conn.Execute(dropQuery2, []interface{}{}) + req = NewExecuteRequest(dropQuery2) + resp, err = conn.Do(req).GetResponse() if err != nil { - t.Fatalf("Failed to Execute: %s", err.Error()) + t.Fatalf("Failed to Execute: %s", err) } if resp == nil { t.Fatal("Response is nil after Execute") } - if resp.Code != 0 { - t.Fatalf("Failed to Execute: %d", resp.Code) - } - if resp.SQLInfo.AffectedCount != 1 { - t.Errorf("Incorrect count of dropped spaces: %d", resp.SQLInfo.AffectedCount) + exResp, ok = resp.(*ExecuteResponse) + assert.True(t, ok, "Got wrong response type") + sqlInfo, err = exResp.SQLInfo() + assert.NoError(t, err, "Error while getting SQLInfo") + if sqlInfo.AffectedCount != 1 { + t.Errorf("Incorrect count of dropped spaces: %d", sqlInfo.AffectedCount) } // drop the same table - resp, err = conn.Execute(dropQuery2, []interface{}{}) - if err == nil { - t.Fatal("Unexpected lack of error") + req = NewExecuteRequest(dropQuery2) + resp, err = conn.Do(req).GetResponse() + if err != nil { + t.Fatalf("Failed to create an Execute: %s", err) } if resp == nil { t.Fatal("Response is nil after Execute") } - if resp.Code == 0 { - t.Fatalf("Unexpected response code: %d", resp.Code) + if resp.Header().Error == ErrorNo { + t.Fatal("Unexpected successful Execute") } - if resp.SQLInfo.AffectedCount != 0 { - t.Errorf("Incorrect count of created spaces: %d", resp.SQLInfo.AffectedCount) + _, err = resp.Decode() + if err == nil { + t.Fatal("Unexpected lack of error") + } + exResp, ok = resp.(*ExecuteResponse) + assert.True(t, ok, "Got wrong response type") + sqlInfo, err = exResp.SQLInfo() + if err == nil { + t.Fatal("Unexpected lack of error") + } + if sqlInfo.AffectedCount != 0 { + t.Errorf("Incorrect count of created spaces: %d", sqlInfo.AffectedCount) } } @@ -1734,30 +1735,32 @@ func TestNewPrepared(t *testing.T) { executeReq := NewExecutePreparedRequest(stmt) unprepareReq := NewUnprepareRequest(stmt) - resp, err := conn.Do(executeReq.Args([]interface{}{1, "test"})).Get() + resp, err := conn.Do(executeReq.Args([]interface{}{1, "test"})).GetResponse() if err != nil { t.Errorf("failed to execute prepared: %v", err) } - if resp.Code != OkCode { - t.Errorf("failed to execute prepared: code %d", resp.Code) + data, err := resp.Decode() + if err != nil { + t.Errorf("Failed to Decode: %s", err) } - if reflect.DeepEqual(resp.Data[0], []interface{}{1, "test"}) { + if reflect.DeepEqual(data[0], []interface{}{1, "test"}) { t.Error("Select with named arguments failed") } - if resp.MetaData[0].FieldType != "unsigned" || - resp.MetaData[0].FieldName != "NAME0" || - resp.MetaData[1].FieldType != "string" || - resp.MetaData[1].FieldName != "NAME1" { + prepResp, ok := resp.(*ExecuteResponse) + assert.True(t, ok, "Got wrong response type") + metaData, err := prepResp.MetaData() + assert.NoError(t, err, "Error while getting MetaData") + if metaData[0].FieldType != "unsigned" || + metaData[0].FieldName != "NAME0" || + metaData[1].FieldType != "string" || + metaData[1].FieldName != "NAME1" { t.Error("Wrong metadata") } - resp, err = conn.Do(unprepareReq).Get() + _, err = conn.Do(unprepareReq).Get() if err != nil { t.Errorf("failed to unprepare prepared statement: %v", err) } - if resp.Code != OkCode { - t.Errorf("failed to unprepare prepared statement: code %d", resp.Code) - } _, err = conn.Do(unprepareReq).Get() if err == nil { @@ -1772,21 +1775,18 @@ func TestNewPrepared(t *testing.T) { require.Contains(t, err.Error(), "Prepared statement with id") prepareReq := NewPrepareRequest(selectNamedQuery2) - resp, err = conn.Do(prepareReq).Get() + data, err = conn.Do(prepareReq).Get() if err != nil { t.Errorf("failed to prepare: %v", err) } - if resp.Data == nil { + if data == nil { t.Errorf("failed to prepare: Data is nil") } - if resp.Code != OkCode { - t.Errorf("failed to unprepare prepared statement: code %d", resp.Code) - } - if len(resp.Data) == 0 { + if len(data) == 0 { t.Errorf("failed to prepare: response Data has no elements") } - stmt, ok := resp.Data[0].(*Prepared) + stmt, ok = data[0].(*Prepared) if !ok { t.Errorf("failed to prepare: failed to cast the response Data to Prepared object") } @@ -1800,7 +1800,7 @@ func TestConnection_DoWithStrangerConn(t *testing.T) { " connection or connection pool") conn1 := &Connection{} - req := test_helpers.NewStrangerRequest() + req := test_helpers.NewMockRequest() _, err := conn1.Do(req).Get() if err == nil { @@ -1811,6 +1811,18 @@ func TestConnection_DoWithStrangerConn(t *testing.T) { } } +func TestConnection_SetResponse_failed(t *testing.T) { + conn := test_helpers.ConnectWithValidation(t, dialer, opts) + defer conn.Close() + + req := mockRequest{conn} + fut := conn.Do(&req) + + data, err := fut.Get() + assert.EqualError(t, err, "failed to set response: some error") + assert.Nil(t, data) +} + func TestGetSchema(t *testing.T) { conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() @@ -1830,12 +1842,9 @@ func TestConnection_SetSchema_Changes(t *testing.T) { req := NewInsertRequest(spaceName) req.Tuple([]interface{}{uint(1010), "Tarantool"}) - resp, err := conn.Do(req).Get() + _, err := conn.Do(req).Get() if err != nil { - t.Fatalf("Failed to Insert: %s", err.Error()) - } - if resp.Code != OkCode { - t.Errorf("Failed to Insert: wrong code returned %d", resp.Code) + t.Fatalf("Failed to Insert: %s", err) } s, err := GetSchema(conn) @@ -1850,41 +1859,12 @@ func TestConnection_SetSchema_Changes(t *testing.T) { reqS := NewSelectRequest(spaceName) reqS.Key([]interface{}{uint(1010)}) - resp, err = conn.Do(reqS).Get() + data, err := conn.Do(reqS).Get() if err != nil { - t.Fatalf("failed to Select: %s", err.Error()) - } - if resp.Code != OkCode { - t.Errorf("failed to Select: wrong code returned %d", resp.Code) + t.Fatalf("failed to Select: %s", err) } - if resp.Data[0].([]interface{})[1] != "Tarantool" { - t.Errorf("wrong Select body: %v", resp.Data) - } -} - -func TestNewPreparedFromResponse(t *testing.T) { - var ( - ErrNilResponsePassed = fmt.Errorf("passed nil response") - ErrNilResponseData = fmt.Errorf("response Data is nil") - ErrWrongDataFormat = fmt.Errorf("response Data format is wrong") - ) - testConn := &Connection{} - testCases := []struct { - name string - resp *Response - expectedError error - }{ - {"ErrNilResponsePassed", nil, ErrNilResponsePassed}, - {"ErrNilResponseData", &Response{Data: nil}, ErrNilResponseData}, - {"ErrWrongDataFormat", &Response{Data: []interface{}{}}, ErrWrongDataFormat}, - {"ErrWrongDataFormat", &Response{Data: []interface{}{"test"}}, ErrWrongDataFormat}, - {"nil", &Response{Data: []interface{}{&Prepared{}}}, nil}, - } - for _, testCase := range testCases { - t.Run("Expecting error "+testCase.name, func(t *testing.T) { - _, err := NewPreparedFromResponse(testConn, testCase.resp) - assert.Equal(t, err, testCase.expectedError) - }) + if data[0].([]interface{})[1] != "Tarantool" { + t.Errorf("wrong Select body: %v", data) } } @@ -2074,86 +2054,112 @@ func TestSchema_IsNullable(t *testing.T) { } } -func TestClientNamed(t *testing.T) { - var resp *Response - var err error +func TestNewPreparedFromResponse(t *testing.T) { + var ( + ErrNilResponsePassed = fmt.Errorf("passed nil response") + ErrNilResponseData = fmt.Errorf("response Data is nil") + ErrWrongDataFormat = fmt.Errorf("response Data format is wrong") + ) + + testConn := &Connection{} + testCases := []struct { + name string + resp Response + expectedError error + }{ + {"ErrNilResponsePassed", nil, ErrNilResponsePassed}, + {"ErrNilResponseData", test_helpers.NewMockResponse(t, nil), + ErrNilResponseData}, + {"ErrWrongDataFormat", test_helpers.NewMockResponse(t, []interface{}{}), + ErrWrongDataFormat}, + {"ErrWrongDataFormat", test_helpers.NewMockResponse(t, []interface{}{"test"}), + ErrWrongDataFormat}, + } + for _, testCase := range testCases { + t.Run("Expecting error "+testCase.name, func(t *testing.T) { + _, err := NewPreparedFromResponse(testConn, testCase.resp) + assert.Equal(t, testCase.expectedError, err) + }) + } +} +func TestClientNamed(t *testing.T) { conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() // Insert - resp, err = conn.Insert(spaceName, []interface{}{uint(1001), "hello2", "world2"}) + data, err := conn.Insert(spaceName, []interface{}{uint(1001), "hello2", "world2"}) if err != nil { - t.Fatalf("Failed to Insert: %s", err.Error()) + t.Fatalf("Failed to Insert: %s", err) } - if resp.Code != 0 { - t.Errorf("Failed to Insert: wrong code returned %d", resp.Code) + if data == nil { + t.Errorf("Response is nil after Insert") } // Delete - resp, err = conn.Delete(spaceName, indexName, []interface{}{uint(1001)}) + data, err = conn.Delete(spaceName, indexName, []interface{}{uint(1001)}) if err != nil { - t.Fatalf("Failed to Delete: %s", err.Error()) + t.Fatalf("Failed to Delete: %s", err) } - if resp == nil { + if data == nil { t.Errorf("Response is nil after Delete") } // Replace - resp, err = conn.Replace(spaceName, []interface{}{uint(1002), "hello", "world"}) + data, err = conn.Replace(spaceName, []interface{}{uint(1002), "hello", "world"}) if err != nil { - t.Fatalf("Failed to Replace: %s", err.Error()) + t.Fatalf("Failed to Replace: %s", err) } - if resp == nil { + if data == nil { t.Errorf("Response is nil after Replace") } // Update - resp, err = conn.Update(spaceName, indexName, + data, err = conn.Update(spaceName, indexName, []interface{}{ uint(1002)}, NewOperations().Assign(1, "buy").Delete(2, 1)) if err != nil { - t.Fatalf("Failed to Update: %s", err.Error()) + t.Fatalf("Failed to Update: %s", err) } - if resp == nil { + if data == nil { t.Errorf("Response is nil after Update") } // Upsert - resp, err = conn.Upsert(spaceName, + data, err = conn.Upsert(spaceName, []interface{}{uint(1003), 1}, NewOperations().Add(1, 1)) if err != nil { - t.Fatalf("Failed to Upsert (insert): %s", err.Error()) + t.Fatalf("Failed to Upsert (insert): %s", err) } - if resp == nil { + if data == nil { t.Errorf("Response is nil after Upsert (insert)") } - resp, err = conn.Upsert(spaceName, + data, err = conn.Upsert(spaceName, []interface{}{uint(1003), 1}, NewOperations().Add(1, 1)) if err != nil { - t.Fatalf("Failed to Upsert (update): %s", err.Error()) + t.Fatalf("Failed to Upsert (update): %s", err) } - if resp == nil { + if data == nil { t.Errorf("Response is nil after Upsert (update)") } // Select for i := 1010; i < 1020; i++ { - resp, err = conn.Replace(spaceName, + data, err = conn.Replace(spaceName, []interface{}{uint(i), fmt.Sprintf("val %d", i), "bla"}) if err != nil { - t.Fatalf("Failed to Replace: %s", err.Error()) + t.Fatalf("Failed to Replace: %s", err) } - if resp.Code != 0 { - t.Errorf("Failed to Replace: wrong code returned %d", resp.Code) + if data == nil { + t.Errorf("Response is nil after Replace") } } - resp, err = conn.Select(spaceName, indexName, 0, 1, IterEq, []interface{}{uint(1010)}) + data, err = conn.Select(spaceName, indexName, 0, 1, IterEq, []interface{}{uint(1010)}) if err != nil { - t.Fatalf("Failed to Select: %s", err.Error()) + t.Fatalf("Failed to Select: %s", err) } - if resp == nil { + if data == nil { t.Errorf("Response is nil after Select") } @@ -2161,7 +2167,7 @@ func TestClientNamed(t *testing.T) { var tpl []Tuple err = conn.SelectTyped(spaceName, indexName, 0, 1, IterEq, []interface{}{uint(1010)}, &tpl) if err != nil { - t.Fatalf("Failed to SelectTyped: %s", err.Error()) + t.Fatalf("Failed to SelectTyped: %s", err) } if len(tpl) != 1 { t.Errorf("Result len of SelectTyped != 1") @@ -2170,9 +2176,8 @@ func TestClientNamed(t *testing.T) { func TestClientRequestObjects(t *testing.T) { var ( - req Request - resp *Response - err error + req Request + err error ) conn := test_helpers.ConnectWithValidation(t, dialer, opts) @@ -2180,14 +2185,11 @@ func TestClientRequestObjects(t *testing.T) { // Ping req = NewPingRequest() - resp, err = conn.Do(req).Get() + data, err := conn.Do(req).Get() if err != nil { - t.Fatalf("Failed to Ping: %s", err.Error()) + t.Fatalf("Failed to Ping: %s", err) } - if resp == nil { - t.Fatalf("Response is nil after Ping") - } - if len(resp.Data) != 0 { + if len(data) != 0 { t.Errorf("Response Body len != 0") } @@ -2200,23 +2202,14 @@ func TestClientRequestObjects(t *testing.T) { for i := 1010; i < 1020; i++ { req = NewInsertRequest(spaceName). Tuple([]interface{}{uint(i), fmt.Sprintf("val %d", i), "bla"}) - resp, err = conn.Do(req).Get() + data, err = conn.Do(req).Get() if err != nil { - t.Fatalf("Failed to Insert: %s", err.Error()) - } - if resp == nil { - t.Fatalf("Response is nil after Insert") - } - if resp.Pos != nil { - t.Errorf("Response should not have a position") + t.Fatalf("Failed to Insert: %s", err) } - if resp.Data == nil { - t.Fatalf("Response data is nil after Insert") - } - if len(resp.Data) != 1 { + if len(data) != 1 { t.Fatalf("Response Body len != 1") } - if tpl, ok := resp.Data[0].([]interface{}); !ok { + if tpl, ok := data[0].([]interface{}); !ok { t.Errorf("Unexpected body of Insert") } else { if len(tpl) != 3 { @@ -2238,23 +2231,14 @@ func TestClientRequestObjects(t *testing.T) { for i := 1015; i < 1020; i++ { req = NewReplaceRequest(spaceName). Tuple([]interface{}{uint(i), fmt.Sprintf("val %d", i), "blar"}) - resp, err = conn.Do(req).Get() + data, err = conn.Do(req).Get() if err != nil { - t.Fatalf("Failed to Replace: %s", err.Error()) - } - if resp == nil { - t.Fatalf("Response is nil after Replace") - } - if resp.Pos != nil { - t.Errorf("Response should not have a position") + t.Fatalf("Failed to Decode: %s", err) } - if resp.Data == nil { - t.Fatalf("Response data is nil after Replace") - } - if len(resp.Data) != 1 { + if len(data) != 1 { t.Fatalf("Response Body len != 1") } - if tpl, ok := resp.Data[0].([]interface{}); !ok { + if tpl, ok := data[0].([]interface{}); !ok { t.Errorf("Unexpected body of Replace") } else { if len(tpl) != 3 { @@ -2275,23 +2259,17 @@ func TestClientRequestObjects(t *testing.T) { // Delete req = NewDeleteRequest(spaceName). Key([]interface{}{uint(1016)}) - resp, err = conn.Do(req).Get() + data, err = conn.Do(req).Get() if err != nil { - t.Fatalf("Failed to Delete: %s", err.Error()) - } - if resp == nil { - t.Fatalf("Response is nil after Delete") - } - if resp.Pos != nil { - t.Errorf("Response should not have a position") + t.Fatalf("Failed to Delete: %s", err) } - if resp.Data == nil { + if data == nil { t.Fatalf("Response data is nil after Delete") } - if len(resp.Data) != 1 { + if len(data) != 1 { t.Fatalf("Response Body len != 1") } - if tpl, ok := resp.Data[0].([]interface{}); !ok { + if tpl, ok := data[0].([]interface{}); !ok { t.Errorf("Unexpected body of Delete") } else { if len(tpl) != 3 { @@ -2312,23 +2290,17 @@ func TestClientRequestObjects(t *testing.T) { req = NewUpdateRequest(spaceName). Index(indexName). Key([]interface{}{uint(1010)}) - resp, err = conn.Do(req).Get() + data, err = conn.Do(req).Get() if err != nil { - t.Errorf("Failed to Update: %s", err.Error()) + t.Errorf("Failed to Update: %s", err) } - if resp == nil { - t.Fatalf("Response is nil after Update") - } - if resp.Pos != nil { - t.Errorf("Response should not have a position") - } - if resp.Data == nil { + if data == nil { t.Fatalf("Response data is nil after Update") } - if len(resp.Data) != 1 { + if len(data) != 1 { t.Fatalf("Response Data len != 1") } - if tpl, ok := resp.Data[0].([]interface{}); !ok { + if tpl, ok := data[0].([]interface{}); !ok { t.Errorf("Unexpected body of Update") } else { if id, err := test_helpers.ConvertUint64(tpl[0]); err != nil || id != uint64(1010) { @@ -2347,23 +2319,14 @@ func TestClientRequestObjects(t *testing.T) { Index(indexName). Key([]interface{}{uint(1010)}). Operations(NewOperations().Assign(1, "bye").Insert(2, 1)) - resp, err = conn.Do(req).Get() + data, err = conn.Do(req).Get() if err != nil { - t.Errorf("Failed to Update: %s", err.Error()) - } - if resp == nil { - t.Fatalf("Response is nil after Update") - } - if resp.Pos != nil { - t.Errorf("Response should not have a position") - } - if resp.Data == nil { - t.Fatalf("Response data is nil after Update") + t.Errorf("Failed to Update: %s", err) } - if len(resp.Data) != 1 { + if len(data) != 1 { t.Fatalf("Response Data len != 1") } - if tpl, ok := resp.Data[0].([]interface{}); !ok { + if tpl, ok := data[0].([]interface{}); !ok { t.Errorf("Unexpected body of Select") } else { if id, err := test_helpers.ConvertUint64(tpl[0]); err != nil || id != 1010 { @@ -2380,20 +2343,11 @@ func TestClientRequestObjects(t *testing.T) { // Upsert without operations. req = NewUpsertRequest(spaceNo). Tuple([]interface{}{uint(1010), "hi", "hi"}) - resp, err = conn.Do(req).Get() + data, err = conn.Do(req).Get() if err != nil { - t.Errorf("Failed to Upsert (update): %s", err.Error()) - } - if resp == nil { - t.Fatalf("Response is nil after Upsert (update)") - } - if resp.Pos != nil { - t.Errorf("Response should not have a position") - } - if resp.Data == nil { - t.Fatalf("Response data is nil after Upsert") + t.Errorf("Failed to Upsert (update): %s", err) } - if len(resp.Data) != 0 { + if len(data) != 0 { t.Fatalf("Response Data len != 0") } @@ -2401,65 +2355,44 @@ func TestClientRequestObjects(t *testing.T) { req = NewUpsertRequest(spaceNo). Tuple([]interface{}{uint(1010), "hi", "hi"}). Operations(NewOperations().Assign(2, "bye")) - resp, err = conn.Do(req).Get() + data, err = conn.Do(req).Get() if err != nil { - t.Errorf("Failed to Upsert (update): %s", err.Error()) + t.Errorf("Failed to Upsert (update): %s", err) } - if resp == nil { - t.Fatalf("Response is nil after Upsert (update)") - } - if resp.Pos != nil { - t.Errorf("Response should not have a position") - } - if resp.Data == nil { - t.Fatalf("Response data is nil after Upsert") - } - if len(resp.Data) != 0 { + if len(data) != 0 { t.Fatalf("Response Data len != 0") } // Call16 vs Call17 req = NewCall16Request("simple_concat").Args([]interface{}{"1"}) - resp, err = conn.Do(req).Get() + data, err = conn.Do(req).Get() if err != nil { t.Errorf("Failed to use Call") } - if resp.Pos != nil { - t.Errorf("Response should not have a position") - } - if val, ok := resp.Data[0].([]interface{})[0].(string); !ok || val != "11" { - t.Errorf("result is not {{1}} : %v", resp.Data) + if val, ok := data[0].([]interface{})[0].(string); !ok || val != "11" { + t.Errorf("result is not {{1}} : %v", data) } // Call17 req = NewCall17Request("simple_concat").Args([]interface{}{"1"}) - resp, err = conn.Do(req).Get() + data, err = conn.Do(req).Get() if err != nil { t.Errorf("Failed to use Call17") } - if resp.Pos != nil { - t.Errorf("Response should not have a position") - } - if val, ok := resp.Data[0].(string); !ok || val != "11" { - t.Errorf("result is not {{1}} : %v", resp.Data) + if val, ok := data[0].(string); !ok || val != "11" { + t.Errorf("result is not {{1}} : %v", data) } // Eval req = NewEvalRequest("return 5 + 6") - resp, err = conn.Do(req).Get() + data, err = conn.Do(req).Get() if err != nil { - t.Fatalf("Failed to Eval: %s", err.Error()) - } - if resp == nil { - t.Fatalf("Response is nil after Eval") - } - if resp.Pos != nil { - t.Errorf("Response should not have a position") + t.Fatalf("Failed to Eval: %s", err) } - if len(resp.Data) < 1 { + if len(data) < 1 { t.Errorf("Response.Data is empty after Eval") } - if val, err := test_helpers.ConvertUint64(resp.Data[0]); err != nil || val != 11 { + if val, err := test_helpers.ConvertUint64(data[0]); err != nil || val != 11 { t.Errorf("5 + 6 == 11, but got %v", val) } @@ -2473,45 +2406,49 @@ func TestClientRequestObjects(t *testing.T) { } req = NewExecuteRequest(createTableQuery) - resp, err = conn.Do(req).Get() + resp, err := conn.Do(req).GetResponse() if err != nil { - t.Fatalf("Failed to Execute: %s", err.Error()) + t.Fatalf("Failed to Execute: %s", err) } if resp == nil { t.Fatal("Response is nil after Execute") } - if resp.Pos != nil { - t.Errorf("Response should not have a position") + data, err = resp.Decode() + if err != nil { + t.Fatalf("Failed to Decode: %s", err) } - if len(resp.Data) != 0 { + if len(data) != 0 { t.Fatalf("Response Body len != 0") } - if resp.Code != OkCode { - t.Fatalf("Failed to Execute: %d", resp.Code) - } - if resp.SQLInfo.AffectedCount != 1 { - t.Errorf("Incorrect count of created spaces: %d", resp.SQLInfo.AffectedCount) + exResp, ok := resp.(*ExecuteResponse) + assert.True(t, ok, "Got wrong response type") + sqlInfo, err := exResp.SQLInfo() + assert.NoError(t, err, "Error while getting SQLInfo") + if sqlInfo.AffectedCount != 1 { + t.Errorf("Incorrect count of created spaces: %d", sqlInfo.AffectedCount) } req = NewExecuteRequest(dropQuery2) - resp, err = conn.Do(req).Get() + resp, err = conn.Do(req).GetResponse() if err != nil { - t.Fatalf("Failed to Execute: %s", err.Error()) + t.Fatalf("Failed to Execute: %s", err) } if resp == nil { t.Fatal("Response is nil after Execute") } - if resp.Pos != nil { - t.Errorf("Response should not have a position") + data, err = resp.Decode() + if err != nil { + t.Fatalf("Failed to Decode: %s", err) } - if len(resp.Data) != 0 { + if len(data) != 0 { t.Fatalf("Response Body len != 0") } - if resp.Code != OkCode { - t.Fatalf("Failed to Execute: %d", resp.Code) - } - if resp.SQLInfo.AffectedCount != 1 { - t.Errorf("Incorrect count of dropped spaces: %d", resp.SQLInfo.AffectedCount) + exResp, ok = resp.(*ExecuteResponse) + assert.True(t, ok, "Got wrong response type") + sqlInfo, err = exResp.SQLInfo() + assert.NoError(t, err, "Error while getting SQLInfo") + if sqlInfo.AffectedCount != 1 { + t.Errorf("Incorrect count of dropped spaces: %d", sqlInfo.AffectedCount) } } @@ -2528,27 +2465,35 @@ func testConnectionDoSelectRequestPrepare(t *testing.T, conn Connector) { } func testConnectionDoSelectRequestCheck(t *testing.T, - resp *Response, err error, pos bool, dataLen int, firstKey uint64) { + resp *SelectResponse, err error, pos bool, dataLen int, firstKey uint64) { t.Helper() if err != nil { - t.Fatalf("Failed to Select: %s", err.Error()) + t.Fatalf("Failed to Select: %s", err) } if resp == nil { t.Fatalf("Response is nil after Select") } - if !pos && resp.Pos != nil { + respPos, err := resp.Pos() + if err != nil { + t.Errorf("Error while getting Pos: %s", err) + } + if !pos && respPos != nil { t.Errorf("Response should not have a position descriptor") } - if pos && resp.Pos == nil { + if pos && respPos == nil { t.Fatalf("A response must have a position descriptor") } - if len(resp.Data) != dataLen { - t.Fatalf("Response Data len %d != %d", len(resp.Data), dataLen) + data, err := resp.Decode() + if err != nil { + t.Fatalf("Failed to Decode: %s", err) + } + if len(data) != dataLen { + t.Fatalf("Response Data len %d != %d", len(data), dataLen) } for i := 0; i < dataLen; i++ { key := firstKey + uint64(i) - if tpl, ok := resp.Data[i].([]interface{}); !ok { + if tpl, ok := data[i].([]interface{}); !ok { t.Errorf("Unexpected body of Select") } else { if id, err := test_helpers.ConvertUint64(tpl[0]); err != nil || id != key { @@ -2579,9 +2524,12 @@ func TestConnectionDoSelectRequest(t *testing.T) { Limit(20). Iterator(IterGe). Key([]interface{}{uint(1010)}) - resp, err := conn.Do(req).Get() + resp, err := conn.Do(req).GetResponse() + + selResp, ok := resp.(*SelectResponse) + assert.True(t, ok, "Got wrong response type") - testConnectionDoSelectRequestCheck(t, resp, err, false, 10, 1010) + testConnectionDoSelectRequestCheck(t, selResp, err, false, 10, 1010) } func TestConnectionDoWatchOnceRequest(t *testing.T) { @@ -2595,15 +2543,12 @@ func TestConnectionDoWatchOnceRequest(t *testing.T) { t.Fatalf("Failed to create a broadcast : %s", err.Error()) } - resp, err := conn.Do(NewWatchOnceRequest("hello")).Get() + data, err := conn.Do(NewWatchOnceRequest("hello")).Get() if err != nil { t.Fatalf("Failed to WatchOnce: %s", err.Error()) } - if resp.Code != OkCode { - t.Errorf("Failed to WatchOnce: wrong code returned %d", resp.Code) - } - if len(resp.Data) < 1 || resp.Data[0] != "world" { - t.Errorf("Failed to WatchOnce: wrong value returned %v", resp.Data) + if len(data) < 1 || data[0] != "world" { + t.Errorf("Failed to WatchOnce: wrong value returned %v", data) } } @@ -2619,15 +2564,12 @@ func TestConnectionDoWatchOnceOnEmptyKey(t *testing.T) { conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() - resp, err := conn.Do(NewWatchOnceRequest("notexists!")).Get() + data, err := conn.Do(NewWatchOnceRequest("notexists!")).Get() if err != nil { t.Fatalf("Failed to WatchOnce: %s", err.Error()) } - if resp.Code != OkCode { - t.Errorf("Failed to WatchOnce: wrong code returned %d", resp.Code) - } - if len(resp.Data) > 0 { - t.Errorf("Failed to WatchOnce: wrong value returned %v", resp.Data) + if len(data) > 0 { + t.Errorf("Failed to WatchOnce: wrong value returned %v", data) } } @@ -2645,9 +2587,12 @@ func TestConnectionDoSelectRequest_fetch_pos(t *testing.T) { Iterator(IterGe). FetchPos(true). Key([]interface{}{uint(1010)}) - resp, err := conn.Do(req).Get() + resp, err := conn.Do(req).GetResponse() + + selResp, ok := resp.(*SelectResponse) + assert.True(t, ok, "Got wrong response type") - testConnectionDoSelectRequestCheck(t, resp, err, true, 2, 1010) + testConnectionDoSelectRequestCheck(t, selResp, err, true, 2, 1010) } func TestConnectDoSelectRequest_after_tuple(t *testing.T) { @@ -2665,9 +2610,12 @@ func TestConnectDoSelectRequest_after_tuple(t *testing.T) { FetchPos(true). Key([]interface{}{uint(1010)}). After([]interface{}{uint(1012)}) - resp, err := conn.Do(req).Get() + resp, err := conn.Do(req).GetResponse() + + selResp, ok := resp.(*SelectResponse) + assert.True(t, ok, "Got wrong response type") - testConnectionDoSelectRequestCheck(t, resp, err, true, 2, 1013) + testConnectionDoSelectRequestCheck(t, selResp, err, true, 2, 1013) } func TestConnectionDoSelectRequest_pagination_pos(t *testing.T) { @@ -2684,45 +2632,49 @@ func TestConnectionDoSelectRequest_pagination_pos(t *testing.T) { Iterator(IterGe). FetchPos(true). Key([]interface{}{uint(1010)}) - resp, err := conn.Do(req).Get() + resp, err := conn.Do(req).GetResponse() + + selResp, ok := resp.(*SelectResponse) + assert.True(t, ok, "Got wrong response type") - testConnectionDoSelectRequestCheck(t, resp, err, true, 2, 1010) + testConnectionDoSelectRequestCheck(t, selResp, err, true, 2, 1010) - resp, err = conn.Do(req.After(resp.Pos)).Get() + selPos, err := selResp.Pos() + assert.NoError(t, err, "Error while getting Pos") - testConnectionDoSelectRequestCheck(t, resp, err, true, 2, 1012) + resp, err = conn.Do(req.After(selPos)).GetResponse() + selResp, ok = resp.(*SelectResponse) + assert.True(t, ok, "Got wrong response type") + + testConnectionDoSelectRequestCheck(t, selResp, err, true, 2, 1012) } func TestConnection_Call(t *testing.T) { - var resp *Response - var err error - conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() - resp, err = conn.Call("simple_concat", []interface{}{"1"}) + data, err := conn.Call("simple_concat", []interface{}{"1"}) if err != nil { t.Errorf("Failed to use Call") } - if val, ok := resp.Data[0].(string); !ok || val != "11" { - t.Errorf("result is not {{1}} : %v", resp.Data) + if val, ok := data[0].(string); !ok || val != "11" { + t.Errorf("result is not {{1}} : %v", data) } } func TestCallRequest(t *testing.T) { - var resp *Response var err error conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() req := NewCallRequest("simple_concat").Args([]interface{}{"1"}) - resp, err = conn.Do(req).Get() + data, err := conn.Do(req).Get() if err != nil { t.Errorf("Failed to use Call") } - if val, ok := resp.Data[0].(string); !ok || val != "11" { - t.Errorf("result is not {{1}} : %v", resp.Data) + if val, ok := data[0].(string); !ok || val != "11" { + t.Errorf("result is not {{1}} : %v", data) } } @@ -2730,14 +2682,11 @@ func TestClientRequestObjectsWithNilContext(t *testing.T) { conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() req := NewPingRequest().Context(nil) //nolint - resp, err := conn.Do(req).Get() + data, err := conn.Do(req).Get() if err != nil { - t.Fatalf("Failed to Ping: %s", err.Error()) - } - if resp == nil { - t.Fatalf("Response is nil after Ping") + t.Fatalf("Failed to Ping: %s", err) } - if len(resp.Data) != 0 { + if len(data) != 0 { t.Errorf("Response Body len != 0") } } @@ -2783,6 +2732,11 @@ func (req *waitCtxRequest) Async() bool { return NewPingRequest().Async() } +func (req *waitCtxRequest) Response(header Header, body io.Reader) (Response, error) { + resp, err := test_helpers.CreateMockResponse(header, body) + return resp, err +} + func TestClientRequestObjectsWithContext(t *testing.T) { var err error conn := test_helpers.ConnectWithValidation(t, dialer, opts) @@ -2830,13 +2784,13 @@ func TestComplexStructs(t *testing.T) { tuple := Tuple2{Cid: 777, Orig: "orig", Members: []Member{{"lol", "", 1}, {"wut", "", 3}}} _, err = conn.Replace(spaceNo, &tuple) if err != nil { - t.Fatalf("Failed to insert: %s", err.Error()) + t.Fatalf("Failed to insert: %s", err) } var tuples [1]Tuple2 err = conn.SelectTyped(spaceNo, indexNo, 0, 1, IterEq, []interface{}{777}, &tuples) if err != nil { - t.Fatalf("Failed to selectTyped: %s", err.Error()) + t.Fatalf("Failed to selectTyped: %s", err) } if len(tuples) != 1 { @@ -2878,7 +2832,7 @@ func TestStream_IdValues(t *testing.T) { stream.Id = id _, err := stream.Do(req).Get() if err != nil { - t.Fatalf("Failed to Ping: %s", err.Error()) + t.Fatalf("Failed to Ping: %s", err) } }) } @@ -2886,7 +2840,6 @@ func TestStream_IdValues(t *testing.T) { func TestStream_Commit(t *testing.T) { var req Request - var resp *Response var err error var conn *Connection @@ -2899,23 +2852,17 @@ func TestStream_Commit(t *testing.T) { // Begin transaction req = NewBeginRequest() - resp, err = stream.Do(req).Get() + _, err = stream.Do(req).Get() if err != nil { - t.Fatalf("Failed to Begin: %s", err.Error()) - } - if resp.Code != OkCode { - t.Fatalf("Failed to Begin: wrong code returned %d", resp.Code) + t.Fatalf("Failed to Begin: %s", err) } // Insert in stream req = NewInsertRequest(spaceName). Tuple([]interface{}{uint(1001), "hello2", "world2"}) - resp, err = stream.Do(req).Get() + _, err = stream.Do(req).Get() if err != nil { - t.Fatalf("Failed to Insert: %s", err.Error()) - } - if resp.Code != OkCode { - t.Errorf("Failed to Insert: wrong code returned %d", resp.Code) + t.Fatalf("Failed to Insert: %s", err) } defer test_helpers.DeleteRecordByKey(t, conn, spaceNo, indexNo, []interface{}{uint(1001)}) @@ -2927,29 +2874,23 @@ func TestStream_Commit(t *testing.T) { Limit(1). Iterator(IterEq). Key([]interface{}{uint(1001)}) - resp, err = conn.Do(selectReq).Get() + data, err := conn.Do(selectReq).Get() if err != nil { - t.Fatalf("Failed to Select: %s", err.Error()) + t.Fatalf("Failed to Select: %s", err) } - if resp == nil { - t.Fatalf("Response is nil after Select") - } - if len(resp.Data) != 0 { + if len(data) != 0 { t.Fatalf("Response Data len != 0") } // Select in stream - resp, err = stream.Do(selectReq).Get() + data, err = stream.Do(selectReq).Get() if err != nil { - t.Fatalf("Failed to Select: %s", err.Error()) - } - if resp == nil { - t.Fatalf("Response is nil after Select") + t.Fatalf("Failed to Select: %s", err) } - if len(resp.Data) != 1 { + if len(data) != 1 { t.Fatalf("Response Data len != 1") } - if tpl, ok := resp.Data[0].([]interface{}); !ok { + if tpl, ok := data[0].([]interface{}); !ok { t.Fatalf("Unexpected body of Select") } else { if id, err := test_helpers.ConvertUint64(tpl[0]); err != nil || id != 1001 { @@ -2965,26 +2906,20 @@ func TestStream_Commit(t *testing.T) { // Commit transaction req = NewCommitRequest() - resp, err = stream.Do(req).Get() + _, err = stream.Do(req).Get() if err != nil { - t.Fatalf("Failed to Commit: %s", err.Error()) - } - if resp.Code != OkCode { - t.Fatalf("Failed to Commit: wrong code returned %d", resp.Code) + t.Fatalf("Failed to Commit: %s", err) } // Select outside of transaction - resp, err = conn.Do(selectReq).Get() + data, err = conn.Do(selectReq).Get() if err != nil { - t.Fatalf("Failed to Select: %s", err.Error()) - } - if resp == nil { - t.Fatalf("Response is nil after Select") + t.Fatalf("Failed to Select: %s", err) } - if len(resp.Data) != 1 { + if len(data) != 1 { t.Fatalf("Response Data len != 1") } - if tpl, ok := resp.Data[0].([]interface{}); !ok { + if tpl, ok := data[0].([]interface{}); !ok { t.Fatalf("Unexpected body of Select") } else { if id, err := test_helpers.ConvertUint64(tpl[0]); err != nil || id != 1001 { @@ -3001,7 +2936,6 @@ func TestStream_Commit(t *testing.T) { func TestStream_Rollback(t *testing.T) { var req Request - var resp *Response var err error var conn *Connection @@ -3014,23 +2948,17 @@ func TestStream_Rollback(t *testing.T) { // Begin transaction req = NewBeginRequest() - resp, err = stream.Do(req).Get() + _, err = stream.Do(req).Get() if err != nil { - t.Fatalf("Failed to Begin: %s", err.Error()) - } - if resp.Code != OkCode { - t.Fatalf("Failed to Begin: wrong code returned %d", resp.Code) + t.Fatalf("Failed to Begin: %s", err) } // Insert in stream req = NewInsertRequest(spaceName). Tuple([]interface{}{uint(1001), "hello2", "world2"}) - resp, err = stream.Do(req).Get() + _, err = stream.Do(req).Get() if err != nil { - t.Fatalf("Failed to Insert: %s", err.Error()) - } - if resp.Code != OkCode { - t.Errorf("Failed to Insert: wrong code returned %d", resp.Code) + t.Fatalf("Failed to Insert: %s", err) } defer test_helpers.DeleteRecordByKey(t, conn, spaceNo, indexNo, []interface{}{uint(1001)}) @@ -3042,29 +2970,23 @@ func TestStream_Rollback(t *testing.T) { Limit(1). Iterator(IterEq). Key([]interface{}{uint(1001)}) - resp, err = conn.Do(selectReq).Get() + data, err := conn.Do(selectReq).Get() if err != nil { - t.Fatalf("Failed to Select: %s", err.Error()) + t.Fatalf("Failed to Select: %s", err) } - if resp == nil { - t.Fatalf("Response is nil after Select") - } - if len(resp.Data) != 0 { + if len(data) != 0 { t.Fatalf("Response Data len != 0") } // Select in stream - resp, err = stream.Do(selectReq).Get() + data, err = stream.Do(selectReq).Get() if err != nil { - t.Fatalf("Failed to Select: %s", err.Error()) - } - if resp == nil { - t.Fatalf("Response is nil after Select") + t.Fatalf("Failed to Select: %s", err) } - if len(resp.Data) != 1 { + if len(data) != 1 { t.Fatalf("Response Data len != 1") } - if tpl, ok := resp.Data[0].([]interface{}); !ok { + if tpl, ok := data[0].([]interface{}); !ok { t.Fatalf("Unexpected body of Select") } else { if id, err := test_helpers.ConvertUint64(tpl[0]); err != nil || id != 1001 { @@ -3080,42 +3002,32 @@ func TestStream_Rollback(t *testing.T) { // Rollback transaction req = NewRollbackRequest() - resp, err = stream.Do(req).Get() + _, err = stream.Do(req).Get() if err != nil { - t.Fatalf("Failed to Rollback: %s", err.Error()) - } - if resp.Code != OkCode { - t.Fatalf("Failed to Rollback: wrong code returned %d", resp.Code) + t.Fatalf("Failed to Rollback: %s", err) } // Select outside of transaction - resp, err = conn.Do(selectReq).Get() + data, err = conn.Do(selectReq).Get() if err != nil { - t.Fatalf("Failed to Select: %s", err.Error()) - } - if resp == nil { - t.Fatalf("Response is nil after Select") + t.Fatalf("Failed to Select: %s", err) } - if len(resp.Data) != 0 { + if len(data) != 0 { t.Fatalf("Response Data len != 0") } // Select inside of stream after rollback - resp, err = stream.Do(selectReq).Get() + _, err = stream.Do(selectReq).Get() if err != nil { - t.Fatalf("Failed to Select: %s", err.Error()) - } - if resp == nil { - t.Fatalf("Response is nil after Select") + t.Fatalf("Failed to Select: %s", err) } - if len(resp.Data) != 0 { + if len(data) != 0 { t.Fatalf("Response Data len != 0") } } func TestStream_TxnIsolationLevel(t *testing.T) { var req Request - var resp *Response var err error var conn *Connection @@ -3136,18 +3048,14 @@ func TestStream_TxnIsolationLevel(t *testing.T) { for _, level := range txnIsolationLevels { // Begin transaction req = NewBeginRequest().TxnIsolation(level).Timeout(500 * time.Millisecond) - resp, err = stream.Do(req).Get() + _, err = stream.Do(req).Get() require.Nilf(t, err, "failed to Begin") - require.NotNilf(t, resp, "response is nil after Begin") - require.Equalf(t, OkCode, resp.Code, "wrong code returned") // Insert in stream req = NewInsertRequest(spaceName). Tuple([]interface{}{uint(1001), "hello2", "world2"}) - resp, err = stream.Do(req).Get() + _, err = stream.Do(req).Get() require.Nilf(t, err, "failed to Insert") - require.NotNilf(t, resp, "response is nil after Insert") - require.Equalf(t, OkCode, resp.Code, "wrong code returned") // Select not related to the transaction // while transaction is not committed @@ -3157,18 +3065,16 @@ func TestStream_TxnIsolationLevel(t *testing.T) { Limit(1). Iterator(IterEq). Key([]interface{}{uint(1001)}) - resp, err = conn.Do(selectReq).Get() + data, err := conn.Do(selectReq).Get() require.Nilf(t, err, "failed to Select") - require.NotNilf(t, resp, "response is nil after Select") - require.Equalf(t, 0, len(resp.Data), "response Data len != 0") + require.Equalf(t, 0, len(data), "response Data len != 0") // Select in stream - resp, err = stream.Do(selectReq).Get() + data, err = stream.Do(selectReq).Get() require.Nilf(t, err, "failed to Select") - require.NotNilf(t, resp, "response is nil after Select") - require.Equalf(t, 1, len(resp.Data), "response Body len != 1 after Select") + require.Equalf(t, 1, len(data), "response Body len != 1 after Select") - tpl, ok := resp.Data[0].([]interface{}) + tpl, ok := data[0].([]interface{}) require.Truef(t, ok, "unexpected body of Select") require.Equalf(t, 3, len(tpl), "unexpected body of Select") @@ -3186,22 +3092,18 @@ func TestStream_TxnIsolationLevel(t *testing.T) { // Rollback transaction req = NewRollbackRequest() - resp, err = stream.Do(req).Get() + _, err = stream.Do(req).Get() require.Nilf(t, err, "failed to Rollback") - require.NotNilf(t, resp, "response is nil after Rollback") - require.Equalf(t, OkCode, resp.Code, "wrong code returned") // Select outside of transaction - resp, err = conn.Do(selectReq).Get() + data, err = conn.Do(selectReq).Get() require.Nilf(t, err, "failed to Select") - require.NotNilf(t, resp, "response is nil after Select") - require.Equalf(t, 0, len(resp.Data), "response Data len != 0") + require.Equalf(t, 0, len(data), "response Data len != 0") // Select inside of stream after rollback - resp, err = stream.Do(selectReq).Get() + data, err = stream.Do(selectReq).Get() require.Nilf(t, err, "failed to Select") - require.NotNilf(t, resp, "response is nil after Select") - require.Equalf(t, 0, len(resp.Data), "response Data len != 0") + require.Equalf(t, 0, len(data), "response Data len != 0") test_helpers.DeleteRecordByKey(t, conn, spaceNo, indexNo, []interface{}{uint(1001)}) } @@ -3213,7 +3115,7 @@ func TestStream_DoWithStrangerConn(t *testing.T) { conn := &Connection{} stream, _ := conn.NewStream() - req := test_helpers.NewStrangerRequest() + req := test_helpers.NewMockRequest() _, err := stream.Do(req).Get() if err == nil { @@ -3298,13 +3200,12 @@ func TestClientIdRequestObject(t *testing.T) { Version: ProtocolVersion(1), Features: []iproto.Feature{iproto.IPROTO_FEATURE_STREAMS}, }) - resp, err := conn.Do(req).Get() + data, err := conn.Do(req).Get() require.Nilf(t, err, "No errors on Id request execution") - require.NotNilf(t, resp, "Response not empty") - require.NotNilf(t, resp.Data, "Response data not empty") - require.Equal(t, len(resp.Data), 1, "Response data contains exactly one object") + require.NotNilf(t, data, "Response data not empty") + require.Equal(t, len(data), 1, "Response data contains exactly one object") - serverProtocolInfo, ok := resp.Data[0].(ProtocolInfo) + serverProtocolInfo, ok := data[0].(ProtocolInfo) require.Truef(t, ok, "Response Data object is an ProtocolInfo object") require.GreaterOrEqual(t, serverProtocolInfo.Version, @@ -3334,13 +3235,12 @@ func TestClientIdRequestObjectWithNilContext(t *testing.T) { Version: ProtocolVersion(1), Features: []iproto.Feature{iproto.IPROTO_FEATURE_STREAMS}, }).Context(nil) //nolint - resp, err := conn.Do(req).Get() + data, err := conn.Do(req).Get() require.Nilf(t, err, "No errors on Id request execution") - require.NotNilf(t, resp, "Response not empty") - require.NotNilf(t, resp.Data, "Response data not empty") - require.Equal(t, len(resp.Data), 1, "Response data contains exactly one object") + require.NotNilf(t, data, "Response data not empty") + require.Equal(t, len(data), 1, "Response data contains exactly one object") - serverProtocolInfo, ok := resp.Data[0].(ProtocolInfo) + serverProtocolInfo, ok := data[0].(ProtocolInfo) require.Truef(t, ok, "Response Data object is an ProtocolInfo object") require.GreaterOrEqual(t, serverProtocolInfo.Version, @@ -3697,15 +3597,12 @@ func TestBroadcastRequest(t *testing.T) { conn := test_helpers.ConnectWithValidation(t, dialer, opts) defer conn.Close() - resp, err := conn.Do(NewBroadcastRequest(key).Value(value)).Get() + data, err := conn.Do(NewBroadcastRequest(key).Value(value)).Get() if err != nil { t.Fatalf("Got broadcast error: %s", err) } - if resp.Code != OkCode { - t.Errorf("Got unexpected broadcast response code: %d", resp.Code) - } - if !reflect.DeepEqual(resp.Data, []interface{}{}) { - t.Errorf("Got unexpected broadcast response data: %v", resp.Data) + if !reflect.DeepEqual(data, []interface{}{}) { + t.Errorf("Got unexpected broadcast response data: %v", data) } events := make(chan WatchEvent) diff --git a/test_helpers/doer.go b/test_helpers/doer.go new file mode 100644 index 000000000..c33ff0e69 --- /dev/null +++ b/test_helpers/doer.go @@ -0,0 +1,69 @@ +package test_helpers + +import ( + "bytes" + "testing" + + "github.com/tarantool/go-tarantool/v2" +) + +type doerResponse struct { + resp *MockResponse + err error +} + +// MockDoer is an implementation of the Doer interface +// used for testing purposes. +type MockDoer struct { + // Requests is a slice of received requests. + // It could be used to compare incoming requests with expected. + Requests []tarantool.Request + responses []doerResponse + t *testing.T +} + +// NewMockDoer creates a MockDoer by given responses. +// Each response could be one of two types: MockResponse or error. +func NewMockDoer(t *testing.T, responses ...interface{}) MockDoer { + t.Helper() + + mockDoer := MockDoer{t: t} + for _, response := range responses { + doerResp := doerResponse{} + + switch resp := response.(type) { + case *MockResponse: + doerResp.resp = resp + case error: + doerResp.err = resp + default: + t.Fatalf("unsupported type: %T", response) + } + + mockDoer.responses = append(mockDoer.responses, doerResp) + } + return mockDoer +} + +// Do returns a future with the current response or an error. +// It saves the current request into MockDoer.Requests. +func (doer *MockDoer) Do(req tarantool.Request) *tarantool.Future { + doer.Requests = append(doer.Requests, req) + + mockReq := NewMockRequest() + fut := tarantool.NewFuture(mockReq) + + if len(doer.responses) == 0 { + doer.t.Fatalf("list of responses is empty") + } + response := doer.responses[0] + + if response.err != nil { + fut.SetError(response.err) + } else { + fut.SetResponse(response.resp.header, bytes.NewBuffer(response.resp.data)) + } + doer.responses = doer.responses[1:] + + return fut +} diff --git a/test_helpers/example_test.go b/test_helpers/example_test.go new file mode 100644 index 000000000..6272d737d --- /dev/null +++ b/test_helpers/example_test.go @@ -0,0 +1,36 @@ +package test_helpers_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/tarantool/go-tarantool/v2" + "github.com/tarantool/go-tarantool/v2/test_helpers" +) + +func TestExampleMockDoer(t *testing.T) { + mockDoer := test_helpers.NewMockDoer(t, + test_helpers.NewMockResponse(t, []interface{}{"some data"}), + fmt.Errorf("some error"), + test_helpers.NewMockResponse(t, "some typed data"), + fmt.Errorf("some error"), + ) + + data, err := mockDoer.Do(tarantool.NewPingRequest()).Get() + assert.NoError(t, err) + assert.Equal(t, []interface{}{"some data"}, data) + + data, err = mockDoer.Do(tarantool.NewSelectRequest("foo")).Get() + assert.EqualError(t, err, "some error") + assert.Nil(t, data) + + var stringData string + err = mockDoer.Do(tarantool.NewInsertRequest("space")).GetTyped(&stringData) + assert.NoError(t, err) + assert.Equal(t, "some typed data", stringData) + + err = mockDoer.Do(tarantool.NewPrepareRequest("expr")).GetTyped(&stringData) + assert.EqualError(t, err, "some error") + assert.Nil(t, data) +} diff --git a/test_helpers/main.go b/test_helpers/main.go index d81ce3d1b..200c3f474 100644 --- a/test_helpers/main.go +++ b/test_helpers/main.go @@ -83,7 +83,6 @@ type TarantoolInstance struct { func isReady(dialer tarantool.Dialer, opts *tarantool.Opts) error { var err error var conn *tarantool.Connection - var resp *tarantool.Response ctx, cancel := GetConnectContext() defer cancel() @@ -96,13 +95,10 @@ func isReady(dialer tarantool.Dialer, opts *tarantool.Opts) error { } defer conn.Close() - resp, err = conn.Do(tarantool.NewPingRequest()).Get() + _, err = conn.Do(tarantool.NewPingRequest()).Get() if err != nil { return err } - if resp == nil { - return errors.New("response is nil after ping") - } return nil } diff --git a/test_helpers/pool_helper.go b/test_helpers/pool_helper.go index fb59418d5..729a69e44 100644 --- a/test_helpers/pool_helper.go +++ b/test_helpers/pool_helper.go @@ -84,18 +84,15 @@ func ProcessListenOnInstance(args interface{}) error { for i := 0; i < listenArgs.ServersNumber; i++ { req := tarantool.NewEvalRequest("return box.cfg.listen") - resp, err := listenArgs.ConnPool.Do(req, listenArgs.Mode).Get() + data, err := listenArgs.ConnPool.Do(req, listenArgs.Mode).Get() if err != nil { return fmt.Errorf("fail to Eval: %s", err.Error()) } - if resp == nil { - return fmt.Errorf("response is nil after Eval") - } - if len(resp.Data) < 1 { + if len(data) < 1 { return fmt.Errorf("response.Data is empty after Eval") } - port, ok := resp.Data[0].(string) + port, ok := data[0].(string) if !ok { return fmt.Errorf("response.Data is incorrect after Eval") } @@ -142,17 +139,14 @@ func InsertOnInstance(ctx context.Context, dialer tarantool.Dialer, connOpts tar } defer conn.Close() - resp, err := conn.Do(tarantool.NewInsertRequest(space).Tuple(tuple)).Get() + data, err := conn.Do(tarantool.NewInsertRequest(space).Tuple(tuple)).Get() if err != nil { return fmt.Errorf("failed to Insert: %s", err.Error()) } - if resp == nil { - return fmt.Errorf("response is nil after Insert") - } - if len(resp.Data) != 1 { + if len(data) != 1 { return fmt.Errorf("response Body len != 1") } - if tpl, ok := resp.Data[0].([]interface{}); !ok { + if tpl, ok := data[0].([]interface{}); !ok { return fmt.Errorf("unexpected body of Insert") } else { expectedTpl, ok := tuple.([]interface{}) diff --git a/test_helpers/request.go b/test_helpers/request.go new file mode 100644 index 000000000..3756a2b54 --- /dev/null +++ b/test_helpers/request.go @@ -0,0 +1,52 @@ +package test_helpers + +import ( + "context" + "io" + + "github.com/tarantool/go-iproto" + "github.com/vmihailenco/msgpack/v5" + + "github.com/tarantool/go-tarantool/v2" +) + +// MockRequest is an empty mock request used for testing purposes. +type MockRequest struct { +} + +// NewMockRequest creates an empty MockRequest. +func NewMockRequest() *MockRequest { + return &MockRequest{} +} + +// Type returns an iproto type for MockRequest. +func (req *MockRequest) Type() iproto.Type { + return iproto.Type(0) +} + +// Async returns if MockRequest expects a response. +func (req *MockRequest) Async() bool { + return false +} + +// Body fills an msgpack.Encoder with the watch request body. +func (req *MockRequest) Body(resolver tarantool.SchemaResolver, enc *msgpack.Encoder) error { + return nil +} + +// Conn returns the Connection object the request belongs to. +func (req *MockRequest) Conn() *tarantool.Connection { + return &tarantool.Connection{} +} + +// Ctx returns a context of the MockRequest. +func (req *MockRequest) Ctx() context.Context { + return nil +} + +// Response creates a response for the MockRequest. +func (req *MockRequest) Response(header tarantool.Header, + body io.Reader) (tarantool.Response, error) { + resp, err := CreateMockResponse(header, body) + return resp, err +} diff --git a/test_helpers/request_mock.go b/test_helpers/request_mock.go deleted file mode 100644 index 80ca1b541..000000000 --- a/test_helpers/request_mock.go +++ /dev/null @@ -1,37 +0,0 @@ -package test_helpers - -import ( - "context" - - "github.com/tarantool/go-iproto" - "github.com/vmihailenco/msgpack/v5" - - "github.com/tarantool/go-tarantool/v2" -) - -type StrangerRequest struct { -} - -func NewStrangerRequest() *StrangerRequest { - return &StrangerRequest{} -} - -func (sr *StrangerRequest) Type() iproto.Type { - return iproto.Type(0) -} - -func (sr *StrangerRequest) Async() bool { - return false -} - -func (sr *StrangerRequest) Body(resolver tarantool.SchemaResolver, enc *msgpack.Encoder) error { - return nil -} - -func (sr *StrangerRequest) Conn() *tarantool.Connection { - return &tarantool.Connection{} -} - -func (sr *StrangerRequest) Ctx() context.Context { - return nil -} diff --git a/test_helpers/response.go b/test_helpers/response.go new file mode 100644 index 000000000..4a28400c0 --- /dev/null +++ b/test_helpers/response.go @@ -0,0 +1,74 @@ +package test_helpers + +import ( + "bytes" + "io" + "io/ioutil" + "testing" + + "github.com/vmihailenco/msgpack/v5" + + "github.com/tarantool/go-tarantool/v2" +) + +// MockResponse is a mock response used for testing purposes. +type MockResponse struct { + // header contains response header + header tarantool.Header + // data contains data inside a response. + data []byte +} + +// NewMockResponse creates a new MockResponse with an empty header and the given data. +// body should be passed as a structure to be encoded. +// The encoded body is served as response data and will be decoded once the +// response is decoded. +func NewMockResponse(t *testing.T, body interface{}) *MockResponse { + t.Helper() + + buf := bytes.NewBuffer([]byte{}) + enc := msgpack.NewEncoder(buf) + + err := enc.Encode(body) + if err != nil { + t.Errorf("unexpected error while encoding: %s", err) + } + + return &MockResponse{data: buf.Bytes()} +} + +// CreateMockResponse creates a MockResponse from the header and a data, +// packed inside an io.Reader. +func CreateMockResponse(header tarantool.Header, body io.Reader) (*MockResponse, error) { + if body == nil { + return &MockResponse{header: header, data: nil}, nil + } + data, err := ioutil.ReadAll(body) + if err != nil { + return nil, err + } + return &MockResponse{header: header, data: data}, nil +} + +// Header returns a header for the MockResponse. +func (resp *MockResponse) Header() tarantool.Header { + return resp.header +} + +// Decode returns the result of decoding the response data as slice. +func (resp *MockResponse) Decode() ([]interface{}, error) { + if resp.data == nil { + return nil, nil + } + dec := msgpack.NewDecoder(bytes.NewBuffer(resp.data)) + return dec.DecodeSlice() +} + +// DecodeTyped returns the result of decoding the response data. +func (resp *MockResponse) DecodeTyped(res interface{}) error { + if resp.data == nil { + return nil + } + dec := msgpack.NewDecoder(bytes.NewBuffer(resp.data)) + return dec.Decode(res) +} diff --git a/uuid/example_test.go b/uuid/example_test.go index ba90ea905..c79dc35be 100644 --- a/uuid/example_test.go +++ b/uuid/example_test.go @@ -46,12 +46,11 @@ func Example() { log.Fatalf("Failed to prepare uuid: %s", uuidErr) } - resp, err := client.Do(tarantool.NewReplaceRequest(spaceNo). + data, err := client.Do(tarantool.NewReplaceRequest(spaceNo). Tuple([]interface{}{id}), ).Get() fmt.Println("UUID tuple replace") fmt.Println("Error", err) - fmt.Println("Code", resp.Code) - fmt.Println("Data", resp.Data) + fmt.Println("Data", data) } diff --git a/uuid/uuid_test.go b/uuid/uuid_test.go index fdbf0cd82..0ce317979 100644 --- a/uuid/uuid_test.go +++ b/uuid/uuid_test.go @@ -89,14 +89,11 @@ func TestSelect(t *testing.T) { Limit(1). Iterator(IterEq). Key([]interface{}{id}) - resp, errSel := conn.Do(sel).Get() + data, errSel := conn.Do(sel).Get() if errSel != nil { t.Fatalf("UUID select failed: %s", errSel.Error()) } - if resp == nil { - t.Fatalf("Response is nil after Select") - } - tupleValueIsId(t, resp.Data, id) + tupleValueIsId(t, data, id) var tuples []TupleUUID errTyp := conn.Do(sel).GetTyped(&tuples) @@ -125,28 +122,22 @@ func TestReplace(t *testing.T) { } rep := NewReplaceRequest(space).Tuple([]interface{}{id}) - respRep, errRep := conn.Do(rep).Get() + dataRep, errRep := conn.Do(rep).Get() if errRep != nil { t.Errorf("UUID replace failed: %s", errRep) } - if respRep == nil { - t.Fatalf("Response is nil after Replace") - } - tupleValueIsId(t, respRep.Data, id) + tupleValueIsId(t, dataRep, id) sel := NewSelectRequest(space). Index(index). Limit(1). Iterator(IterEq). Key([]interface{}{id}) - respSel, errSel := conn.Do(sel).Get() + dataSel, errSel := conn.Do(sel).Get() if errSel != nil { t.Errorf("UUID select failed: %s", errSel) } - if respSel == nil { - t.Fatalf("Response is nil after Select") - } - tupleValueIsId(t, respSel.Data, id) + tupleValueIsId(t, dataSel, id) } // runTestMain is a body of TestMain function diff --git a/watch.go b/watch.go index 0508899f0..c147b0399 100644 --- a/watch.go +++ b/watch.go @@ -2,6 +2,7 @@ package tarantool import ( "context" + "io" "github.com/tarantool/go-iproto" "github.com/vmihailenco/msgpack/v5" @@ -55,6 +56,15 @@ func (req *BroadcastRequest) Async() bool { return req.call.Async() } +// Response creates a response for a BroadcastRequest. +func (req *BroadcastRequest) Response(header Header, body io.Reader) (Response, error) { + resp, err := createBaseResponse(header, body) + if err != nil { + return nil, err + } + return &resp, nil +} + // watchRequest subscribes to the updates of a specified key defined on the // server. After receiving the notification, you should send a new // watchRequest to acknowledge the notification.