diff --git a/README.md b/README.md index ded58ec..be37d07 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,8 @@ This is an opinionated modification of [github.com/tarantool/go-tarantool](https * API changed, some non-obvious (mostly to me personally) API removed. * This package uses the latest msgpack library [github.com/vmihailenco/msgpack/v5](https://github.com/vmihailenco/msgpack) instead of `v2` in original. -* Uses `enc.UseArrayEncodedStructs(true)` for `msgpack.Encoder` by default so there is no need to define `msgpack:",as_array"` struct tags. If you need to disable this (for example when using nested structs) then this behavior can be disabled using `DisableArrayEncodedStructs` option. +* Uses `UseArrayEncodedStructs(true)` for `msgpack.Encoder` by default so there is no need to define `msgpack:",as_array"` struct tags. If you need to disable this (for example when using nested structs) then this behavior can be disabled using `DisableArrayEncodedStructs` option. +* Uses `UseLooseInterfaceDecoding(true)` for `msgpack.Decoder` to decode response into untyped `[]interface{}` result. See [decoding rules](https://pkg.go.dev/github.com/vmihailenco/msgpack/v5#Decoder.DecodeInterfaceLoose). * Supports out-of-bound pushes (see [box.session.push](https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_session/#box-session-push)) * Adds optional support for `context.Context` (though performance will suffer a bit, if you want a maximum performance then use non-context methods which use per-connection timeout). Context cancellation does not cancel a query (Tarantool has no such functionality) - just stops waiting for request future resolving. * Uses sync.Pool for `*msgpack.Decoder` to reduce allocations on decoding stage a bit. Actually this package allocates a bit more than the original one, but allocations are small and overall performance is comparable to the original (based on observations from internal benchmarks). diff --git a/changelog.md b/changelog.md index 4b4fb8f..94c8c54 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,25 @@ +v0.3.0 +====== + +* Removing `Response` type from public API – see [#8](https://github.com/FZambia/tarantool/pull/8) for details. + +``` +> gorelease -base v0.2.3 -version v0.3.0 +# github.com/FZambia/tarantool +## incompatible changes +(*Connection).Exec: changed from func(*Request) (*Response, error) to func(*Request) ([]interface{}, error) +(*Connection).ExecContext: changed from func(context.Context, *Request) (*Response, error) to func(context.Context, *Request) ([]interface{}, error) +(*Request).WithPush: changed from func(func(*Response)) *Request to func(func([]interface{})) *Request +ErrorCodeBit: removed +Future.Get: changed from func() (*Response, error) to func() ([]interface{}, error) +FutureContext.GetContext: changed from func(context.Context) (*Response, error) to func(context.Context) ([]interface{}, error) +OkCode: removed +Response: removed + +# summary +v0.3.0 is a valid semantic version for this release. +``` + v0.2.3 ====== diff --git a/connection.go b/connection.go index 893bf61..94734b6 100644 --- a/connection.go +++ b/connection.go @@ -103,7 +103,7 @@ type Logger interface { // and array or should serialize to msgpack array. type Connection struct { state uint32 // Keep atomics on top to work on 32-bit architectures. - requestID uint32 + requestID uint32 // Keep atomics on top to work on 32-bit architectures. c net.Conn mutex sync.Mutex @@ -302,8 +302,9 @@ func (conn *Connection) NetConn() net.Conn { return conn.c } -// Exec Request on Tarantool server. -func (conn *Connection) Exec(req *Request) (*Response, error) { +// Exec Request on Tarantool server. Return untyped result and error. +// Use ExecTyped for more performance and convenience. +func (conn *Connection) Exec(req *Request) ([]interface{}, error) { return conn.newFuture(req, true).Get() } @@ -315,7 +316,7 @@ func (conn *Connection) ExecTyped(req *Request, result interface{}) error { // ExecContext execs Request with context.Context. Note, that context // cancellation/timeout won't result into ongoing request cancellation // on Tarantool side. -func (conn *Connection) ExecContext(ctx context.Context, req *Request) (*Response, error) { +func (conn *Connection) ExecContext(ctx context.Context, req *Request) ([]interface{}, error) { if _, ok := ctx.Deadline(); !ok && conn.opts.RequestTimeout > 0 { var cancel func() ctx, cancel = context.WithTimeout(ctx, conn.opts.RequestTimeout) @@ -587,7 +588,7 @@ func (conn *Connection) readAuthResponse(r io.Reader) (err error) { if err != nil { return errors.New("auth: read error " + err.Error()) } - resp := Response{buf: smallBuf{b: respBytes}} + resp := response{buf: smallBuf{b: respBytes}} err = resp.decodeHeader(conn.dec) if err != nil { return errors.New("auth: decode response header error " + err.Error()) @@ -775,14 +776,14 @@ func (conn *Connection) reader(r *bufio.Reader, c net.Conn) { conn.reconnect(err, c) return } - resp := &Response{buf: smallBuf{b: respBytes}} + resp := &response{buf: smallBuf{b: respBytes}} err = resp.decodeHeader(conn.dec) if err != nil { conn.reconnect(err, c) return } - if resp.Code == KeyPush { - if fut := conn.peekFuture(resp.RequestID); fut != nil { + if resp.code == KeyPush { + if fut := conn.peekFuture(resp.requestID); fut != nil { fut.markPushReady(resp) } else { if conn.opts.Logger != nil { @@ -791,7 +792,7 @@ func (conn *Connection) reader(r *bufio.Reader, c net.Conn) { } continue } - if fut := conn.fetchFuture(resp.RequestID); fut != nil { + if fut := conn.fetchFuture(resp.requestID); fut != nil { fut.resp = resp fut.markReady(conn) } else { diff --git a/const.go b/const.go index 257841e..85e5c85 100644 --- a/const.go +++ b/const.go @@ -54,8 +54,8 @@ const ( RLimitWait = 2 ) -// Response related const. +// response related const. const ( - OkCode = uint32(0) - ErrorCodeBit = 0x8000 + okCode = uint32(0) + errorCodeBit = 0x8000 ) diff --git a/example_test.go b/example_test.go index 1591bb0..a1e64e0 100644 --- a/example_test.go +++ b/example_test.go @@ -37,21 +37,21 @@ func ExampleConnection_Exec() { return } defer func() { _ = conn.Close() }() - resp, err := conn.Exec(Select(512, 0, 0, 100, IterEq, []interface{}{uint(1111)})) + result, err := conn.Exec(Select(512, 0, 0, 100, IterEq, []interface{}{uint(1111)})) if err != nil { fmt.Printf("error in select is %v", err) return } - fmt.Printf("response is %#v\n", resp.Data) - resp, err = conn.Exec(Select("test", "primary", 0, 100, IterEq, IntKey{1111})) + fmt.Printf("result is %#v\n", result) + result, err = conn.Exec(Select("test", "primary", 0, 100, IterEq, IntKey{1111})) if err != nil { fmt.Printf("error in select is %v", err) return } - fmt.Printf("response is %#v\n", resp.Data) + fmt.Printf("result is %#v\n", result) // Output: - // response is []interface {}{[]interface {}{0x457, "hello", "world"}} - // response is []interface {}{[]interface {}{0x457, "hello", "world"}} + // result is []interface {}{[]interface {}{0x457, "hello", "world"}} + // result is []interface {}{[]interface {}{0x457, "hello", "world"}} } func ExampleConnection_ExecTyped() { @@ -98,99 +98,79 @@ func Example() { return } - resp, err := client.Exec(Ping()) + result, err := client.Exec(Ping()) if err != nil { fmt.Printf("failed to ping: %s", err.Error()) return } - fmt.Println("Ping Code", resp.Code) - fmt.Println("Ping Data", resp.Data) - fmt.Println("Ping Error", err) + fmt.Println("Ping Result", result) // Delete tuple for cleaning. _, _ = client.Exec(Delete(spaceNo, indexNo, []interface{}{uint(10)})) _, _ = client.Exec(Delete(spaceNo, indexNo, []interface{}{uint(11)})) // Insert new tuple { 10, 1 }. - resp, err = client.Exec(Insert(spaceNo, []interface{}{uint(10), "test", "one"})) + result, err = client.Exec(Insert(spaceNo, []interface{}{uint(10), "test", "one"})) fmt.Println("Insert Error", err) - fmt.Println("Insert Code", resp.Code) - fmt.Println("Insert Data", resp.Data) + fmt.Println("Insert Result", result) // Insert new tuple { 11, 1 }. - resp, err = client.Exec(Insert("test", &Tuple{ID: 10, Msg: "test", Name: "one"})) + result, err = client.Exec(Insert("test", &Tuple{ID: 10, Msg: "test", Name: "one"})) fmt.Println("Insert Error", err) - fmt.Println("Insert Code", resp.Code) - fmt.Println("Insert Data", resp.Data) + fmt.Println("Insert Result", result) // Delete tuple with primary key { 10 }. - resp, err = client.Exec(Delete(spaceNo, indexNo, []interface{}{uint(10)})) + result, err = client.Exec(Delete(spaceNo, indexNo, []interface{}{uint(10)})) // or - // resp, err = client.Exec(Delete("test", "primary", UintKey{10}})) + // result, err = client.Exec(Delete("test", "primary", UintKey{10}})) fmt.Println("Delete Error", err) - fmt.Println("Delete Code", resp.Code) - fmt.Println("Delete Data", resp.Data) + fmt.Println("Delete Result", result) // Replace tuple with primary key 13. - resp, err = client.Exec(Replace(spaceNo, []interface{}{uint(13), 1})) + result, err = client.Exec(Replace(spaceNo, []interface{}{uint(13), 1})) fmt.Println("Replace Error", err) - fmt.Println("Replace Code", resp.Code) - fmt.Println("Replace Data", resp.Data) + fmt.Println("Replace Result", result) // Update tuple with primary key { 13 }, incrementing second field by 3. - resp, err = client.Exec(Update("test", "primary", UintKey{13}, []Op{OpAdd(1, 3)})) + result, err = client.Exec(Update("test", "primary", UintKey{13}, []Op{OpAdd(1, 3)})) // or // resp, err = client.Exec(Update(spaceNo, indexNo, []interface{}{uint(13)}, []Op{OpAdd(1, 3)})) fmt.Println("Update Error", err) - fmt.Println("Update Code", resp.Code) - fmt.Println("Update Data", resp.Data) + fmt.Println("Update Result", result) // Select just one tuple with primary key { 15 }. - resp, err = client.Exec(Select(spaceNo, indexNo, 0, 1, IterEq, []interface{}{uint(15)})) + result, err = client.Exec(Select(spaceNo, indexNo, 0, 1, IterEq, []interface{}{uint(15)})) // or // resp, err = client.Exec(Select("test", "primary", 0, 1, IterEq, UintKey{15})) fmt.Println("Select Error", err) - fmt.Println("Select Code", resp.Code) - fmt.Println("Select Data", resp.Data) + fmt.Println("Select Result", result) // Call function 'func_name' with arguments. - resp, err = client.Exec(Call("simple_incr", []interface{}{1})) + result, err = client.Exec(Call("simple_incr", []interface{}{1})) fmt.Println("Call Error", err) - fmt.Println("Call Code", resp.Code) - fmt.Println("Call Data", resp.Data) + fmt.Println("Call Result", result) // Run raw lua code. - resp, err = client.Exec(Eval("return 1 + 2", []interface{}{})) + result, err = client.Exec(Eval("return 1 + 2", []interface{}{})) fmt.Println("Eval Error", err) - fmt.Println("Eval Code", resp.Code) - fmt.Println("Eval Data", resp.Data) + fmt.Println("Eval Result", result) // Output: - // Ping Code 0 - // Ping Data [] - // Ping Error + // Ping Result [] // Insert Error - // Insert Code 0 - // Insert Data [[10 test one]] + // Insert Result [[10 test one]] // Insert Error Duplicate key exists in unique index 'primary' in space 'test' (0x3) - // Insert Code 3 - // Insert Data [] + // Insert Result [] // Delete Error - // Delete Code 0 - // Delete Data [[10 test one]] + // Delete Result [[10 test one]] // Replace Error - // Replace Code 0 - // Replace Data [[13 1]] + // Replace Result [[13 1]] // Update Error - // Update Code 0 - // Update Data [[13 4]] + // Update Result [[13 4]] // Select Error - // Select Code 0 - // Select Data [[15 val 15 bla]] + // Select Result [[15 val 15 bla]] // Call Error - // Call Code 0 - // Call Data [2] + // Call Result [2] // Eval Error - // Eval Code 0 - // Eval Data [3] + // Eval Result [3] } diff --git a/future.go b/future.go index 9f739b0..79ab3e8 100644 --- a/future.go +++ b/future.go @@ -9,13 +9,13 @@ import ( // Future allows to extract response from server as soon as it's ready. type Future interface { - Get() (*Response, error) + Get() ([]interface{}, error) GetTyped(result interface{}) error } // FutureContext allows extracting response from server as soon as it's ready with Context. type FutureContext interface { - GetContext(ctx context.Context) (*Response, error) + GetContext(ctx context.Context) ([]interface{}, error) GetTypedContext(ctx context.Context, result interface{}) error } @@ -25,28 +25,31 @@ type futureImpl struct { timeout time.Duration conn *Connection req *Request - resp *Response + resp *response err error ready chan struct{} next *futureImpl } -// Get waits for future to be filled and returns Response and error. +// Get waits for future to be filled and returns result and error. // -// Response will contain deserialized result in Data field. -// It will be []interface{}, so if you want more performance, use GetTyped method. +// Result will contain data deserialized into []interface{}. if you want more +// performance, use GetTyped method. // // 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 *futureImpl) Get() (*Response, error) { +// Error could be Error, if it is error returned by Tarantool, or ClientError, if +// something bad happens in a client process. +func (fut *futureImpl) Get() ([]interface{}, error) { fut.wait() if fut.err != nil { - return fut.resp, fut.err + return nil, fut.err } fut.err = fut.resp.decodeBody() - return fut.resp, fut.err + if fut.err != nil { + return nil, fut.err + } + return fut.resp.data, nil } // GetTyped waits for future and decodes response into result if no error happens. @@ -60,21 +63,24 @@ func (fut *futureImpl) GetTyped(result interface{}) error { return fut.err } -// GetContext waits for future to be filled and returns Response and error. -func (fut *futureImpl) GetContext(ctx context.Context) (*Response, error) { +// GetContext waits for future to be filled and returns result and error. +func (fut *futureImpl) GetContext(ctx context.Context) ([]interface{}, error) { fut.waitContext(ctx) if fut.err != nil { if fut.err == context.DeadlineExceeded || fut.err == context.Canceled { fut.conn.fetchFuture(fut.requestID) } - return fut.resp, fut.err + return nil, fut.err } fut.err = fut.resp.decodeBody() - return fut.resp, fut.err + if fut.err != nil { + return nil, fut.err + } + return fut.resp.data, nil } -// GetTypedContext waits for futureImpl and calls msgpack.Decoder.Decode(result) if no error happens. -// It could be much faster than GetContext() function. +// GetTypedContext waits for futureImpl and calls msgpack.Decoder.Decode(result) if +// no error happens. It could be much faster than GetContext() function. func (fut *futureImpl) GetTypedContext(ctx context.Context, result interface{}) error { fut.waitContext(ctx) if fut.err != nil { @@ -87,14 +93,14 @@ func (fut *futureImpl) GetTypedContext(ctx context.Context, result interface{}) return fut.err } -func (fut *futureImpl) markPushReady(resp *Response) { +func (fut *futureImpl) markPushReady(resp *response) { if fut.req.push == nil && fut.req.pushTyped == nil { return } if fut.req.push != nil { err := resp.decodeBody() if err == nil { - fut.req.push(resp) + fut.req.push(resp.data) } return } diff --git a/helpers.go b/helpers.go index 6b23048..1e038d8 100644 --- a/helpers.go +++ b/helpers.go @@ -28,7 +28,7 @@ func (k UintKey) EncodeMsgpack(enc *msgpack.Encoder) error { return nil } -// UintKey is utility type for passing string key to Select, Update and Delete. +// StringKey is utility type for passing string key to Select, Update and Delete. // It serializes to array with single string element. type StringKey struct { S string diff --git a/request.go b/request.go index 3a4b788..894406c 100644 --- a/request.go +++ b/request.go @@ -8,7 +8,7 @@ import ( type Request struct { requestCode int32 sendFunc func(conn *Connection) (func(enc *msgpack.Encoder) error, error) - push func(*Response) + push func([]interface{}) pushTyped func(func(interface{}) error) } @@ -20,7 +20,7 @@ func newRequest(requestCode int32, cb func(conn *Connection) (func(enc *msgpack. } // WithPush allows setting Push handler to Request. -func (req *Request) WithPush(pushCB func(*Response)) *Request { +func (req *Request) WithPush(pushCB func([]interface{})) *Request { req.push = pushCB return req } diff --git a/response.go b/response.go index 704178f..295be1f 100644 --- a/response.go +++ b/response.go @@ -6,17 +6,17 @@ import ( "github.com/vmihailenco/msgpack/v5" ) -type Response struct { - RequestID uint32 - Code uint32 - Error string - // Data contains deserialized data for untyped requests. - Data []interface{} +type response struct { + requestID uint32 + code uint32 + error string + buf smallBuf - buf smallBuf + // Deserialized data for untyped requests. + data []interface{} } -func (resp *Response) smallInt(d *msgpack.Decoder) (i int, err error) { +func (resp *response) smallInt(d *msgpack.Decoder) (i int, err error) { b, err := resp.buf.ReadByte() if err != nil { return @@ -31,7 +31,7 @@ func (resp *Response) smallInt(d *msgpack.Decoder) (i int, err error) { return d.DecodeInt() } -func (resp *Response) decodeHeader(d *msgpack.Decoder) (err error) { +func (resp *response) decodeHeader(d *msgpack.Decoder) (err error) { var l int d.Reset(&resp.buf) if l, err = d.DecodeMapLen(); err != nil { @@ -48,13 +48,13 @@ func (resp *Response) decodeHeader(d *msgpack.Decoder) (err error) { if rid, err = d.DecodeUint64(); err != nil { return } - resp.RequestID = uint32(rid) + resp.requestID = uint32(rid) case KeyCode: var respCode uint64 if respCode, err = d.DecodeUint64(); err != nil { return } - resp.Code = uint32(respCode) + resp.code = uint32(respCode) default: if err = d.Skip(); err != nil { return @@ -64,7 +64,7 @@ func (resp *Response) decodeHeader(d *msgpack.Decoder) (err error) { return nil } -func (resp *Response) decodeBody() (err error) { +func (resp *response) decodeBody() (err error) { if resp.buf.Len() > 2 { var l int d := getDecoder(&resp.buf) @@ -84,11 +84,11 @@ func (resp *Response) decodeBody() (err error) { if res, err = d.DecodeInterface(); err != nil { return err } - if resp.Data, ok = res.([]interface{}); !ok { + if resp.data, ok = res.([]interface{}); !ok { return fmt.Errorf("result is not array: %v", res) } case KeyError: - if resp.Error, err = d.DecodeString(); err != nil { + if resp.error, err = d.DecodeString(); err != nil { return err } default: @@ -97,18 +97,18 @@ func (resp *Response) decodeBody() (err error) { } } } - if resp.Code == KeyPush { + if resp.code == KeyPush { return } - if resp.Code != OkCode { - resp.Code &^= ErrorCodeBit - err = Error{resp.Code, resp.Error} + if resp.code != okCode { + resp.code &^= errorCodeBit + err = Error{resp.code, resp.error} } } return } -func (resp *Response) decodeBodyTyped(res interface{}) (err error) { +func (resp *response) decodeBodyTyped(res interface{}) (err error) { if resp.buf.Len() > 0 { var l int d := getDecoder(&resp.buf) @@ -127,7 +127,7 @@ func (resp *Response) decodeBodyTyped(res interface{}) (err error) { return err } case KeyError: - if resp.Error, err = d.DecodeString(); err != nil { + if resp.error, err = d.DecodeString(); err != nil { return err } default: @@ -136,23 +136,13 @@ func (resp *Response) decodeBodyTyped(res interface{}) (err error) { } } } - if resp.Code == KeyPush { + if resp.code == KeyPush { return } - if resp.Code != OkCode { - resp.Code &^= ErrorCodeBit - err = Error{resp.Code, resp.Error} + if resp.code != okCode { + resp.code &^= errorCodeBit + err = Error{resp.code, resp.error} } } return } - -// String implements Stringer interface. -func (resp *Response) String() (str string) { - if resp.Code == OkCode { - return fmt.Sprintf("<%d OK %v>", resp.RequestID, resp.Data) - } else if resp.Code == KeyPush { - return fmt.Sprintf("<%d PUSH %v>", resp.RequestID, resp.Data) - } - return fmt.Sprintf("<%d ERR 0x%x %s>", resp.RequestID, resp.Code, resp.Error) -} diff --git a/schema.go b/schema.go index cabb9c3..99e650b 100644 --- a/schema.go +++ b/schema.go @@ -59,8 +59,6 @@ const ( ) func (conn *Connection) loadSchema() (err error) { - var resp *Response - schema := new(Schema) schema.SpacesByID = make(map[uint32]*Space) schema.Spaces = make(map[string]*Space) @@ -68,11 +66,11 @@ func (conn *Connection) loadSchema() (err error) { // Reload spaces. ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() - resp, err = conn.ExecContext(ctx, Select(vSpaceSpID, 0, 0, maxSchemas, IterAll, []interface{}{})) + resp, err := conn.ExecContext(ctx, Select(vSpaceSpID, 0, 0, maxSchemas, IterAll, []interface{}{})) if err != nil { return err } - for _, row := range resp.Data { + for _, row := range resp { row := row.([]interface{}) space := new(Space) space.ID = uint32(row[0].(uint64)) @@ -127,7 +125,7 @@ func (conn *Connection) loadSchema() (err error) { if err != nil { return err } - for _, row := range resp.Data { + for _, row := range resp { row := row.([]interface{}) index := new(Index) index.ID = uint32(row[1].(int64)) diff --git a/tarantool_test.go b/tarantool_test.go index 5a5942c..52599ce 100644 --- a/tarantool_test.go +++ b/tarantool_test.go @@ -140,7 +140,7 @@ func BenchmarkClientSerialTyped(b *testing.B) { var r []Tuple b.ResetTimer() for i := 0; i < b.N; i++ { - err = conn.ExecTypedContext(context.Background(), Select(spaceNo, indexNo, 0, 1, IterEq, IntKey{I: 1111}), &r) + err = conn.ExecTyped(Select(spaceNo, indexNo, 0, 1, IterEq, IntKey{I: 1111}), &r) if err != nil { b.Errorf("No connection available") } @@ -420,7 +420,6 @@ func BenchmarkClientParallelMassiveUntyped(b *testing.B) { } func TestClientBoxInfoCall(t *testing.T) { - var resp *Response var err error var conn *Connection @@ -435,26 +434,21 @@ func TestClientBoxInfoCall(t *testing.T) { } defer func() { _ = conn.Close() }() - resp, err = conn.Exec(Call("box.info", []interface{}{"box.schema.SPACE_ID"})) + result, err := conn.Exec(Call("box.info", []interface{}{"box.schema.SPACE_ID"})) if err != nil { t.Errorf("Failed to Call: %s", err.Error()) return } - if resp == nil { - t.Errorf("Response is nil after Call") + if result == nil { + t.Errorf("Result is nil after Call") return } - if resp.Data == nil { - t.Errorf("Data is nil after Call") - return - } - if len(resp.Data) < 1 { - t.Errorf("Response.Data is empty after Call") + if len(result) < 1 { + t.Errorf("Result is empty after Call") } } func TestClient(t *testing.T) { - var resp *Response var err error var conn *Connection @@ -470,34 +464,26 @@ func TestClient(t *testing.T) { defer func() { _ = conn.Close() }() // Ping. - resp, err = conn.Exec(Ping()) + _, err = conn.Exec(Ping()) if err != nil { t.Errorf("Failed to Ping: %s", err.Error()) return } - if resp == nil { - t.Errorf("Response is nil after Ping") - return - } // Insert. - resp, err = conn.Exec(Insert(spaceNo, []interface{}{uint(1), "hello", "world"})) + result, err := conn.Exec(Insert(spaceNo, []interface{}{uint(1), "hello", "world"})) if err != nil { t.Errorf("Failed to Insert: %s", err.Error()) return } - if resp == nil { - t.Errorf("Response is nil after Insert") - return - } - if resp.Data == nil { + if result == nil { t.Errorf("Data is nil after Insert") return } - if len(resp.Data) != 1 { + if len(result) != 1 { t.Errorf("Response Body len != 1") } - if tpl, ok := resp.Data[0].([]interface{}); !ok { + if tpl, ok := result[0].([]interface{}); !ok { t.Errorf("Unexpected body of Insert") } else { if len(tpl) != 3 { @@ -517,23 +503,19 @@ func TestClient(t *testing.T) { } // Delete. - resp, err = conn.Exec(Delete(spaceNo, indexNo, []interface{}{uint(1)})) + result, err = conn.Exec(Delete(spaceNo, indexNo, []interface{}{uint(1)})) if err != nil { t.Errorf("Failed to Delete: %s", err.Error()) return } - if resp == nil { - t.Errorf("Response is nil after Delete") - return - } - if resp.Data == nil { + if result == nil { t.Errorf("Data is nil after Delete") return } - if len(resp.Data) != 1 { + if len(result) != 1 { t.Errorf("Response Body len != 1") } - if tpl, ok := resp.Data[0].([]interface{}); !ok { + if tpl, ok := result[0].([]interface{}); !ok { t.Errorf("Unexpected body of Delete") } else { if len(tpl) != 3 { @@ -546,48 +528,44 @@ func TestClient(t *testing.T) { t.Errorf("Unexpected body of Delete (1)") } } - resp, err = conn.Exec(Delete(spaceNo, indexNo, []interface{}{uint(101)})) + result, err = conn.Exec(Delete(spaceNo, indexNo, []interface{}{uint(101)})) if err != nil { t.Errorf("Failed to Delete: %s", err.Error()) return } - if resp == nil { - t.Errorf("Response is nil after Delete") - return - } - if resp.Data == nil { + if result == nil { t.Errorf("Data is nil after Insert") return } - if len(resp.Data) != 0 { + if len(result) != 0 { t.Errorf("Response Data len != 0") } // Replace. - resp, err = conn.Exec(Replace(spaceNo, []interface{}{uint(2), "hello", "world"})) + result, err = conn.Exec(Replace(spaceNo, []interface{}{uint(2), "hello", "world"})) if err != nil { t.Errorf("Failed to Replace: %s", err.Error()) } - if resp == nil { - t.Errorf("Response is nil after Replace") - } - resp, err = conn.Exec(Replace(spaceNo, []interface{}{uint(2), "hi", "planet"})) if err != nil { - t.Errorf("Failed to Replace (duplicate): %s", err.Error()) + t.Errorf("Failed to Replace: %s", err.Error()) return } - if resp == nil { - t.Errorf("Response is nil after Replace (duplicate)") + if result == nil { + t.Errorf("Result is nil after Replace") + } + result, err = conn.Exec(Replace(spaceNo, []interface{}{uint(2), "hi", "planet"})) + if err != nil { + t.Errorf("Failed to Replace (duplicate): %s", err.Error()) return } - if resp.Data == nil { + if result == nil { t.Errorf("Data is nil after Insert") return } - if len(resp.Data) != 1 { + if len(result) != 1 { t.Errorf("Response Data len != 1") } - if tpl, ok := resp.Data[0].([]interface{}); !ok { + if tpl, ok := result[0].([]interface{}); !ok { t.Errorf("Unexpected body of Replace") } else { if len(tpl) != 3 { @@ -602,23 +580,19 @@ func TestClient(t *testing.T) { } // Update. - resp, err = conn.Exec(Update(spaceNo, indexNo, []interface{}{uint(2)}, []Op{OpAssign(1, "bye"), OpDelete(2, 1)})) + result, err = conn.Exec(Update(spaceNo, indexNo, []interface{}{uint(2)}, []Op{OpAssign(1, "bye"), OpDelete(2, 1)})) if err != nil { t.Errorf("Failed to Update: %s", err.Error()) return } - if resp == nil { - t.Errorf("Response is nil after Update") - return - } - if resp.Data == nil { + if result == nil { t.Errorf("Data is nil after Insert") return } - if len(resp.Data) != 1 { + if len(result) != 1 { t.Errorf("Response Data len != 1") } - if tpl, ok := resp.Data[0].([]interface{}); !ok { + if tpl, ok := result[0].([]interface{}); !ok { t.Errorf("Unexpected body of Update") } else { if len(tpl) != 2 { @@ -634,19 +608,19 @@ func TestClient(t *testing.T) { // Upsert. if strings.Compare(conn.greeting.Version, "Tarantool 1.6.7") >= 0 { - resp, err = conn.Exec(Upsert(spaceNo, []interface{}{uint(3), 1}, []Op{OpAdd(1, 1)})) + result, err = conn.Exec(Upsert(spaceNo, []interface{}{uint(3), 1}, []Op{OpAdd(1, 1)})) if err != nil { t.Errorf("Failed to Upsert (insert): %s", err.Error()) } - if resp == nil { - t.Errorf("Response is nil after Upsert (insert)") + if result == nil { + t.Errorf("Result is nil after Upsert (insert)") } - resp, err = conn.Exec(Upsert(spaceNo, []interface{}{uint(3), 1}, []Op{OpAdd(1, 1)})) + result, err = conn.Exec(Upsert(spaceNo, []interface{}{uint(3), 1}, []Op{OpAdd(1, 1)})) if err != nil { t.Errorf("Failed to Upsert (update): %s", err.Error()) } - if resp == nil { - t.Errorf("Response is nil after Upsert (update)") + if result == nil { + t.Errorf("Result is nil after Upsert (update)") } } @@ -657,23 +631,19 @@ func TestClient(t *testing.T) { t.Errorf("Failed to Replace: %s", err.Error()) } } - resp, err = conn.Exec(Select(spaceNo, indexNo, 0, 1, IterEq, []interface{}{uint(10)})) + result, err = conn.Exec(Select(spaceNo, indexNo, 0, 1, IterEq, []interface{}{uint(10)})) if err != nil { t.Errorf("Failed to Select: %s", err.Error()) return } - if resp == nil { - t.Errorf("Response is nil after Select") - return - } - if resp.Data == nil { - t.Errorf("Data is nil after Select") + if result == nil { + t.Errorf("Result is nil after Select") return } - if len(resp.Data) != 1 { + if len(result) != 1 { t.Errorf("Response Data len != 1") } - if tpl, ok := resp.Data[0].([]interface{}); !ok { + if tpl, ok := result[0].([]interface{}); !ok { t.Errorf("Unexpected body of Select") } else { if id, ok := tpl[0].(int64); !ok || id != 10 { @@ -685,20 +655,16 @@ func TestClient(t *testing.T) { } // Select empty. - resp, err = conn.Exec(Select(spaceNo, indexNo, 0, 1, IterEq, []interface{}{uint(30)})) + result, err = conn.Exec(Select(spaceNo, indexNo, 0, 1, IterEq, []interface{}{uint(30)})) if err != nil { t.Errorf("Failed to Select: %s", err.Error()) return } - if resp == nil { - t.Errorf("Response is nil after Select") - return - } - if resp.Data == nil { - t.Errorf("Data is nil after Select") + if result == nil { + t.Errorf("Result is nil after Select") return } - if len(resp.Data) != 0 { + if len(result) != 0 { t.Errorf("Response Data len != 0") } @@ -741,33 +707,29 @@ func TestClient(t *testing.T) { } // Call. - resp, err = conn.Exec(Call("simple_incr", []interface{}{1})) + result, err = conn.Exec(Call("simple_incr", []interface{}{1})) if err != nil { t.Errorf("Failed to Call: %s", err.Error()) return } - if resp.Data[0].(int64) != 2 { - t.Errorf("result is not {{1}} : %v", resp.Data) + if result[0].(int64) != 2 { + t.Errorf("result is not {{1}} : %v", result) } // Eval. - resp, err = conn.Exec(Eval("return 5 + 6", []interface{}{})) + result, err = conn.Exec(Eval("return 5 + 6", []interface{}{})) if err != nil { t.Errorf("Failed to Eval: %s", err.Error()) return } - if resp == nil { - t.Errorf("Response is nil after Eval") + if result == nil { + t.Errorf("Result is nil after Eval") return } - if resp.Data == nil { - t.Errorf("Data is nil after Eval") - return - } - if len(resp.Data) < 1 { + if len(result) < 1 { t.Errorf("Response.Data is empty after Eval") } - val := resp.Data[0].(int64) + val := result[0].(int64) if val != 11 { t.Errorf("5 + 6 == 11, but got %v", val) } @@ -961,7 +923,6 @@ func TestSchema(t *testing.T) { } func TestClientNamed(t *testing.T) { - var resp *Response var err error var conn *Connection @@ -983,47 +944,47 @@ func TestClientNamed(t *testing.T) { } // Delete. - resp, err = conn.Exec(Delete(spaceName, indexName, []interface{}{uint(1001)})) + result, err := conn.Exec(Delete(spaceName, indexName, []interface{}{uint(1001)})) if err != nil { t.Errorf("Failed to Delete: %s", err.Error()) } - if resp == nil { - t.Errorf("Response is nil after Delete") + if result == nil { + t.Errorf("Result is nil after Delete") } // Replace. - resp, err = conn.Exec(Replace(spaceName, []interface{}{uint(1002), "hello", "world"})) + result, err = conn.Exec(Replace(spaceName, []interface{}{uint(1002), "hello", "world"})) if err != nil { t.Errorf("Failed to Replace: %s", err.Error()) } - if resp == nil { - t.Errorf("Response is nil after Replace") + if result == nil { + t.Errorf("Result is nil after Replace") } // Update. - resp, err = conn.Exec(Update(spaceName, indexName, []interface{}{uint(1002)}, []Op{OpAssign(1, "bye"), OpDelete(2, 1)})) + result, err = conn.Exec(Update(spaceName, indexName, []interface{}{uint(1002)}, []Op{OpAssign(1, "bye"), OpDelete(2, 1)})) if err != nil { t.Errorf("Failed to Update: %s", err.Error()) } - if resp == nil { - t.Errorf("Response is nil after Update") + if result == nil { + t.Errorf("Result is nil after Update") } // Upsert. if strings.Compare(conn.greeting.Version, "Tarantool 1.6.7") >= 0 { - resp, err = conn.Exec(Upsert(spaceName, []interface{}{uint(1003), 1}, []Op{OpAdd(1, 1)})) + result, err = conn.Exec(Upsert(spaceName, []interface{}{uint(1003), 1}, []Op{OpAdd(1, 1)})) if err != nil { t.Errorf("Failed to Upsert (insert): %s", err.Error()) } - if resp == nil { - t.Errorf("Response is nil after Upsert (insert)") + if result == nil { + t.Errorf("Result is nil after Upsert (insert)") } - resp, err = conn.Exec(Upsert(spaceName, []interface{}{uint(1003), 1}, []Op{OpAdd(1, 1)})) + result, err = conn.Exec(Upsert(spaceName, []interface{}{uint(1003), 1}, []Op{OpAdd(1, 1)})) if err != nil { t.Errorf("Failed to Upsert (update): %s", err.Error()) } - if resp == nil { - t.Errorf("Response is nil after Upsert (update)") + if result == nil { + t.Errorf("Result is nil after Upsert (update)") } } @@ -1034,12 +995,12 @@ func TestClientNamed(t *testing.T) { t.Errorf("Failed to Replace: %s", err.Error()) } } - resp, err = conn.Exec(Select(spaceName, indexName, 0, 1, IterEq, []interface{}{uint(1010)})) + result, err = conn.Exec(Select(spaceName, indexName, 0, 1, IterEq, []interface{}{uint(1010)})) if err != nil { t.Errorf("Failed to Select: %s", err.Error()) } - if resp == nil { - t.Errorf("Response is nil after Select") + if result == nil { + t.Errorf("Result is nil after Select") } // Select Typed. @@ -1140,12 +1101,13 @@ func TestExecContext(t *testing.T) { ctx, cancel = context.WithTimeout(context.Background(), 200*time.Millisecond) defer cancel() - _, err = connNoTimeout.ExecContext( + result, err = connNoTimeout.ExecContext( ctx, req, ) require.Error(t, err) require.True(t, errors.Is(err, context.DeadlineExceeded)) + require.Nil(t, result) // connection w/ request timeout connWithTimeout, err = Connect(server, Opts{ @@ -1197,3 +1159,46 @@ func TestExecContext(t *testing.T) { require.Error(t, err) require.True(t, errors.Is(err, context.DeadlineExceeded)) } + +func TestServerError(t *testing.T) { + var err error + var conn *Connection + + conn, err = Connect(server, opts) + if err != nil { + t.Errorf("Failed to connect: %s", err.Error()) + return + } + if conn == nil { + t.Errorf("conn is nil after Connect") + return + } + defer func() { _ = conn.Close() }() + + _, err = conn.Exec(Eval("error('boom')", []interface{}{})) + require.Error(t, err) + var e Error + ok := errors.As(err, &e) + require.True(t, ok) + require.EqualValues(t, ErrProcLua, e.Code) +} + +func TestErrorDecode(t *testing.T) { + var err error + var conn *Connection + + conn, err = Connect(server, opts) + if err != nil { + t.Errorf("Failed to connect: %s", err.Error()) + return + } + if conn == nil { + t.Errorf("conn is nil after Connect") + return + } + defer func() { _ = conn.Close() }() + + var s [][]string + err = conn.ExecTyped(Eval("return 1, 3", []interface{}{}), &s) + require.Error(t, err) +}