Skip to content

Commit

Permalink
Remove Response type from public API (#8)
Browse files Browse the repository at this point in the history
  • Loading branch information
FZambia authored Jan 16, 2022
1 parent a624568 commit 7aeb1a1
Show file tree
Hide file tree
Showing 11 changed files with 245 additions and 242 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
22 changes: 22 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -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
======

Expand Down
19 changes: 10 additions & 9 deletions connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
}

Expand All @@ -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)
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down
6 changes: 3 additions & 3 deletions const.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ const (
RLimitWait = 2
)

// Response related const.
// response related const.
const (
OkCode = uint32(0)
ErrorCodeBit = 0x8000
okCode = uint32(0)
errorCodeBit = 0x8000
)
88 changes: 34 additions & 54 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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 <nil>
// Ping Result []
// Insert Error <nil>
// 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 <nil>
// Delete Code 0
// Delete Data [[10 test one]]
// Delete Result [[10 test one]]
// Replace Error <nil>
// Replace Code 0
// Replace Data [[13 1]]
// Replace Result [[13 1]]
// Update Error <nil>
// Update Code 0
// Update Data [[13 4]]
// Update Result [[13 4]]
// Select Error <nil>
// Select Code 0
// Select Data [[15 val 15 bla]]
// Select Result [[15 val 15 bla]]
// Call Error <nil>
// Call Code 0
// Call Data [2]
// Call Result [2]
// Eval Error <nil>
// Eval Code 0
// Eval Data [3]
// Eval Result [3]
}
44 changes: 25 additions & 19 deletions future.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand All @@ -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.
Expand All @@ -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 {
Expand All @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 7aeb1a1

Please sign in to comment.