diff --git a/CHANGELOG.md b/CHANGELOG.md index 7385dd47c..f0cc0b40a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release. - Handle everything with `go test` (#115) - Use plain package instead of module for UUID submodule (#134) - Reset buffer if its average use size smaller than quater of capacity (#95) +- Update API documentation: comments and examples (#123). ## [1.5] - 2019-12-29 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..0d8423689 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,39 @@ +# Contribution Guide + +## First steps + +Clone the repository and install dependencies. + +```sh +$ git clone git@github.com:/tarantool/go-tarantool +$ cd go-tarantool +$ go get . +``` + +## Running tests + +You need to [install Tarantool](https://tarantool.io/en/download/) to run tests. +See the Installation section in the README for requirements. + +To install test dependencies (such as the +[tarantool/queue](https://github.com/tarantool/queue) module), run: +```bash +make deps +``` + +To run tests for the main package and each subpackage: +```bash +make test +``` + +The tests set up all required `tarantool` processes before run and clean up +afterwards. + +If you want to run the tests for a specific package: +```bash +make test- +``` +For example, for running tests in `multi`, `uuid` and `main` packages, call +```bash +make test-multi test-uuid test-main +``` diff --git a/LICENSE b/LICENSE index 9295b5220..4ec1f2574 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 2-Clause License -Copyright (c) 2014-2017, Tarantool AUTHORS +Copyright (c) 2014-2022, Tarantool AUTHORS Copyright (c) 2014-2017, Dmitry Smal Copyright (c) 2014-2017, Yura Sokolov aka funny_falcon All rights reserved. diff --git a/Makefile b/Makefile index 72959eeaf..5af3358d0 100644 --- a/Makefile +++ b/Makefile @@ -12,8 +12,32 @@ deps: clean .PHONY: test test: + go test ./... -v -p 1 + +.PHONY: test-multi +test-multi: + @echo "Running tests in multiconnection package" + go clean -testcache + go test ./multi/ -v -p 1 + +.PHONY: test-queue +test-queue: + @echo "Running tests in queue package" + cd ./queue/ && tarantool -e "require('queue')" + go clean -testcache + go test ./queue/ -v -p 1 + +.PHONY: test-uuid +test-uuid: + @echo "Running tests in UUID package" + go clean -testcache + go test ./uuid/ -v -p 1 + +.PHONY: test-main +test-main: + @echo "Running tests in main package" go clean -testcache - go test ./... -v -p 1 + go test . -v -p 1 .PHONY: coverage coverage: diff --git a/README.md b/README.md index 0cd576994..92d760315 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,18 @@ - -[![Coverage Status](https://coveralls.io/repos/github/tarantool/go-tarantool/badge.svg?branch=master)](https://coveralls.io/github/tarantool/go-tarantool?branch=master) +[![Go Reference][godoc-badge]][godoc-url] +[![Actions Status][actions-badge]][actions-url] +[![Code Coverage][coverage-badge]][coverage-url] +[![Telegram][telegram-badge]][telegram-url] +[![GitHub Discussions][discussions-badge]][discussions-url] +[![Stack Overflow][stackoverflow-badge]][stackoverflow-url] -# Client in Go for Tarantool 1.6+ +# Client in Go for Tarantool -The `go-tarantool` package has everything necessary for interfacing with -[Tarantool 1.6+](http://tarantool.org/). +The package `go-tarantool` contains everything you need to connect to +[Tarantool 1.6+][tarantool-site]. The advantage of integrating Go with Tarantool, which is an application server plus a DBMS, is that Go programmers can handle databases and perform on-the-fly @@ -20,694 +22,136 @@ faster than other packages according to public benchmarks. ## Table of contents * [Installation](#installation) -* [Hello World](#hello-world) -* [API reference](#api-reference) -* [Walking\-through example in Go](#walking-through-example-in-go) -* [Help](#help) -* [Usage](#usage) -* [Schema](#schema) -* [Custom (un)packing and typed selects and function calls](#custom-unpacking-and-typed-selects-and-function-calls) -* [Options](#options) -* [Working with queue](#working-with-queue) -* [Tests](#tests) +* [Documentation](#documentation) + * [API reference](#api-reference) + * [Walking\-through example](#walking-through-example) +* [Contributing](#contributing) * [Alternative connectors](#alternative-connectors) ## Installation -We assume that you have Tarantool version 1.6 and a modern Linux or BSD +We assume that you have Tarantool version 1.6+ and a modern Linux or BSD operating system. -You will need a current version of `go`, version 1.3 or later (use -`go version` to check the version number). Do not use `gccgo-go`. +You need a current version of `go`, version 1.13 or later (use `go version` to +check the version number). Do not use `gccgo-go`. -**Note:** If your `go` version is younger than 1.3, or if `go` is not installed, -download the latest tarball from [golang.org](https://golang.org/dl/) and say: +**Note:** If your `go` version is older than 1.3 or if `go` is not installed, +download and run the latest tarball from [golang.org][golang-dl]. -```bash -$ sudo tar -C /usr/local -xzf go1.7.5.linux-amd64.tar.gz -$ export PATH=$PATH:/usr/local/go/bin -$ export GOPATH="/usr/local/go/go-tarantool" -$ sudo chmod -R a+rwx /usr/local/go -``` - -The `go-tarantool` package is in -[tarantool/go-tarantool](github.com/tarantool/go-tarantool) repository. -To download and install, say: +The package `go-tarantool` is located in [tarantool/go-tarantool][go-tarantool] +repository. To download and install, say: ``` $ go get github.com/tarantool/go-tarantool ``` -This should bring source and binary files into subdirectories of `/usr/local/go`, -making it possible to access by adding `github.com/tarantool/go-tarantool` in -the `import {...}` section at the start of any Go program. - -

Hello World

+This should put the source and binary files in subdirectories of +`/usr/local/go`, so that you can access them by adding +`github.com/tarantool/go-tarantool` to the `import {...}` section at the start +of any Go program. -In the "[Connectors](https://www.tarantool.io/en/doc/latest/getting_started/getting_started_go/)" -chapter of the Tarantool manual, there is an explanation of a very short (18-line) -program written in Go. Follow the instructions at the start of the "Connectors" -chapter carefully. Then cut and paste the example into a file named `example.go`, -and run it. You should see: nothing. +## Documentation -If that is what you see, then you have successfully installed `go-tarantool` and -successfully executed a program that manipulated the contents of a Tarantool -database. +Read the [Tarantool documentation](tarantool-doc-data-model-url) +to find descriptions of terms such as "connect", "space", "index", and the +requests to create and manipulate database objects or Lua functions. -

API reference

+In general, connector methods can be divided into two main parts: -Read the [Tarantool manual](http://tarantool.org/doc.html) to find descriptions -of terms like "connect", "space", "index", and the requests for creating and -manipulating database objects or Lua functions. +* `Connect()` function and functions related to connecting, and +* Data manipulation functions and Lua invocations such as `Insert()` or `Call()`. -The source files for the requests library are: -* [connection.go](https://github.com/tarantool/go-tarantool/blob/master/connection.go) - for the `Connect()` function plus functions related to connecting, and -* [request.go](https://github.com/tarantool/go-tarantool/blob/master/request.go) - for data-manipulation functions and Lua invocations. - -See comments in those files for syntax details: -``` -Ping -closeConnection -Select -Insert -Replace -Delete -Update -Upsert -Call -Call17 -Eval -``` +The supported requests have parameters and results equivalent to requests in +the [Tarantool CRUD operations](tarantool-doc-box-space-url). +There are also Typed and Async versions of each data-manipulation function. -The supported requests have parameters and results equivalent to requests in the -Tarantool manual. There are also Typed and Async versions of each data-manipulation -function. +### API Reference -The source file for error-handling tools is -[errors.go](https://github.com/tarantool/go-tarantool/blob/master/errors.go), -which has structure definitions and constants whose names are equivalent to names -of errors that the Tarantool server returns. +Learn API documentation and examples at +[pkg.go.dev](https://pkg.go.dev/github.com/tarantool/go-tarantool). -## Walking-through example in Go +### Walking-through example -We can now have a closer look at the `example.go` program and make some observations +We can now have a closer look at the example and make some observations about what it does. ```go -package main +package tarantool import ( - "fmt" - "github.com/tarantool/go-tarantool" + "fmt" + "github.com/tarantool/go-tarantool" ) func main() { - opts := tarantool.Opts{User: "guest"} - conn, err := tarantool.Connect("127.0.0.1:3301", opts) - // conn, err := tarantool.Connect("/path/to/tarantool.socket", opts) - if err != nil { - fmt.Println("Connection refused:", err) - } - resp, err := conn.Insert(999, []interface{}{99999, "BB"}) - if err != nil { - fmt.Println("Error", err) - fmt.Println("Code", resp.Code) - } + opts := tarantool.Opts{User: "guest"} + conn, err := tarantool.Connect("127.0.0.1:3301", opts) + if err != nil { + fmt.Println("Connection refused:", err) + } + resp, err := conn.Insert(999, []interface{}{99999, "BB"}) + if err != nil { + fmt.Println("Error", err) + fmt.Println("Code", resp.Code) + } } ``` -**Observation 1:** the line "`github.com/tarantool/go-tarantool`" in the +**Observation 1:** The line "`github.com/tarantool/go-tarantool`" in the `import(...)` section brings in all Tarantool-related functions and structures. -**Observation 2:** the line beginning with "`Opts :=`" sets up the options for -`Connect()`. In this example, there is only one thing in the structure, a user -name. The structure can also contain: - -* `Pass` (password), -* `Timeout` (maximum number of milliseconds to wait before giving up), -* `Reconnect` (number of seconds to wait before retrying if a connection fails), -* `MaxReconnect` (maximum number of times to retry). +**Observation 2:** The line starting with "`Opts :=`" sets up the options for +`Connect()`. In this example, the structure contains only a single value, the +username. The structure may also contain other settings, see more in +[documentation][godoc-opts-url] for the "`Opts`" structure. -**Observation 3:** the line containing "`tarantool.Connect`" is essential for -beginning any session. There are two parameters: +**Observation 3:** The line containing "`tarantool.Connect`" is essential for +starting a session. There are two parameters: * a string with `host:port` format, and * the option structure that was set up earlier. -**Observation 4:** the `err` structure will be `nil` if there is no error, +**Observation 4:** The `err` structure will be `nil` if there is no error, otherwise it will have a description which can be retrieved with `err.Error()`. -**Observation 5:** the `Insert` request, like almost all requests, is preceded by +**Observation 5:** The `Insert` request, like almost all requests, is preceded by "`conn.`" which is the name of the object that was returned by `Connect()`. There are two parameters: * a space number (it could just as easily have been a space name), and * a tuple. -## Help - -To contact `go-tarantool` developers on any problems, create an issue at -[tarantool/go-tarantool](http://github.com/tarantool/go-tarantool/issues). - -The developers of the [Tarantool server](http://github.com/tarantool/tarantool) -will also be happy to provide advice or receive feedback. - -## Usage - -```go -package main - -import ( - "github.com/tarantool/go-tarantool" - "log" - "time" -) - -func main() { - spaceNo := uint32(512) - indexNo := uint32(0) - - server := "127.0.0.1:3013" - opts := tarantool.Opts{ - Timeout: 500 * time.Millisecond, - Reconnect: 1 * time.Second, - MaxReconnects: 3, - User: "test", - Pass: "test", - } - client, err := tarantool.Connect(server, opts) - if err != nil { - log.Fatalf("Failed to connect: %s", err.Error()) - } - - resp, err := client.Ping() - log.Println(resp.Code) - log.Println(resp.Data) - log.Println(err) - - // insert new tuple { 10, 1 } - resp, err = client.Insert(spaceNo, []interface{}{uint(10), 1}) - // or - resp, err = client.Insert("test", []interface{}{uint(10), 1}) - log.Println("Insert") - log.Println("Error", err) - log.Println("Code", resp.Code) - log.Println("Data", resp.Data) - - // delete tuple with primary key { 10 } - resp, err = client.Delete(spaceNo, indexNo, []interface{}{uint(10)}) - // or - resp, err = client.Delete("test", "primary", []interface{}{uint(10)}) - log.Println("Delete") - log.Println("Error", err) - log.Println("Code", resp.Code) - log.Println("Data", resp.Data) - - // replace tuple with { 13, 1 } - resp, err = client.Replace(spaceNo, []interface{}{uint(13), 1}) - // or - resp, err = client.Replace("test", []interface{}{uint(13), 1}) - log.Println("Replace") - log.Println("Error", err) - log.Println("Code", resp.Code) - log.Println("Data", resp.Data) - - // update tuple with primary key { 13 }, incrementing second field by 3 - resp, err = client.Update(spaceNo, indexNo, []interface{}{uint(13)}, []interface{}{[]interface{}{"+", 1, 3}}) - // or - resp, err = client.Update("test", "primary", []interface{}{uint(13)}, []interface{}{[]interface{}{"+", 1, 3}}) - log.Println("Update") - log.Println("Error", err) - log.Println("Code", resp.Code) - log.Println("Data", resp.Data) - - // insert tuple {15, 1} or increment second field by 1 - resp, err = client.Upsert(spaceNo, []interface{}{uint(15), 1}, []interface{}{[]interface{}{"+", 1, 1}}) - // or - resp, err = client.Upsert("test", []interface{}{uint(15), 1}, []interface{}{[]interface{}{"+", 1, 1}}) - log.Println("Upsert") - log.Println("Error", err) - log.Println("Code", resp.Code) - log.Println("Data", resp.Data) - - // select just one tuple with primay key { 15 } - resp, err = client.Select(spaceNo, indexNo, 0, 1, tarantool.IterEq, []interface{}{uint(15)}) - // or - resp, err = client.Select("test", "primary", 0, 1, tarantool.IterEq, []interface{}{uint(15)}) - log.Println("Select") - log.Println("Error", err) - log.Println("Code", resp.Code) - log.Println("Data", resp.Data) - - // select tuples by condition ( primay key > 15 ) with offset 7 limit 5 - // BTREE index supposed - resp, err = client.Select(spaceNo, indexNo, 7, 5, tarantool.IterGt, []interface{}{uint(15)}) - // or - resp, err = client.Select("test", "primary", 7, 5, tarantool.IterGt, []interface{}{uint(15)}) - log.Println("Select") - log.Println("Error", err) - log.Println("Code", resp.Code) - log.Println("Data", resp.Data) - - // call function 'func_name' with arguments - resp, err = client.Call("func_name", []interface{}{1, 2, 3}) - log.Println("Call") - log.Println("Error", err) - log.Println("Code", resp.Code) - log.Println("Data", resp.Data) - - // run raw lua code - resp, err = client.Eval("return 1 + 2", []interface{}{}) - log.Println("Eval") - log.Println("Error", err) - log.Println("Code", resp.Code) - log.Println("Data", resp.Data) -} -``` - -To enable support of UUID in msgpack with [google/uuid](https://github.com/google/uuid), -import tarantool/uuid submodule. -```go -package main - -import ( - "log" - "time" - - "github.com/tarantool/go-tarantool" - _ "github.com/tarantool/go-tarantool/uuid" - "github.com/google/uuid" -) - -func main() { - server := "127.0.0.1:3013" - opts := tarantool.Opts{ - Timeout: 500 * time.Millisecond, - Reconnect: 1 * time.Second, - MaxReconnects: 3, - User: "test", - Pass: "test", - } - client, err := tarantool.Connect(server, opts) - if err != nil { - log.Fatalf("Failed to connect: %s", err.Error()) - } - - spaceNo := uint32(524) - - id, uuidErr := uuid.Parse("c8f0fa1f-da29-438c-a040-393f1126ad39") - if uuidErr != nil { - log.Fatalf("Failed to prepare uuid: %s", uuidErr) - } - - resp, err := client.Replace(spaceNo, []interface{}{ id }) - - log.Println("UUID tuple replace") - log.Println("Error", err) - log.Println("Code", resp.Code) - log.Println("Data", resp.Data) -} -``` - -## Schema - -```go - // save Schema to local variable to avoid races - schema := client.Schema - - // access Space objects by name or id - space1 := schema.Spaces["some_space"] - space2 := schema.SpacesById[20] // it's a map - fmt.Printf("Space %d %s %s\n", space1.Id, space1.Name, space1.Engine) - fmt.Printf("Space %d %d\n", space1.FieldsCount, space1.Temporary) - - // access index information by name or id - index1 := space1.Indexes["some_index"] - index2 := space1.IndexesById[2] // it's a map - fmt.Printf("Index %d %s\n", index1.Id, index1.Name) - - // access index fields information by index - indexField1 := index1.Fields[0] // it's a slice - indexField2 := index1.Fields[1] // it's a slice - fmt.Printf("IndexFields %s %s\n", indexField1.Name, indexField1.Type) - - // access space fields information by name or id (index) - spaceField1 := space.Fields["some_field"] - spaceField2 := space.FieldsById[3] - fmt.Printf("SpaceField %s %s\n", spaceField1.Name, spaceField1.Type) -``` - -## Custom (un)packing and typed selects and function calls - -You can specify custom pack/unpack functions for your types. This will allow you -to store complex structures inside a tuple and may speed up you requests. - -Alternatively, you can just instruct the `msgpack` library to encode your -structure as an array. This is safe "magic". It will be easier to implement than -a custom packer/unpacker, but it will work slower. - -```go -import ( - "github.com/tarantool/go-tarantool" - "gopkg.in/vmihailenco/msgpack.v2" -) - -type Member struct { - Name string - Nonce string - Val uint -} - -type Tuple struct { - Cid uint - Orig string - Members []Member -} - -/* same effect in a "magic" way, but slower */ -type Tuple2 struct { - _msgpack struct{} `msgpack:",asArray"` - - Cid uint - Orig string - Members []Member -} - -func (m *Member) EncodeMsgpack(e *msgpack.Encoder) error { - if err := e.EncodeSliceLen(2); err != nil { - return err - } - if err := e.EncodeString(m.Name); err != nil { - return err - } - if err := e.EncodeUint(m.Val); err != nil { - return err - } - return nil -} - -func (m *Member) DecodeMsgpack(d *msgpack.Decoder) error { - var err error - var l int - if l, err = d.DecodeSliceLen(); err != nil { - return err - } - if l != 2 { - return fmt.Errorf("array len doesn't match: %d", l) - } - if m.Name, err = d.DecodeString(); err != nil { - return err - } - if m.Val, err = d.DecodeUint(); err != nil { - return err - } - return nil -} - -func (c *Tuple) EncodeMsgpack(e *msgpack.Encoder) error { - if err := e.EncodeSliceLen(3); err != nil { - return err - } - if err := e.EncodeUint(c.Cid); err != nil { - return err - } - if err := e.EncodeString(c.Orig); err != nil { - return err - } - if err := e.EncodeSliceLen(len(c.Members)); err != nil { - return err - } - for _, m := range c.Members { - e.Encode(m) - } - return nil -} - -func (c *Tuple) DecodeMsgpack(d *msgpack.Decoder) error { - var err error - var l int - if l, err = d.DecodeSliceLen(); err != nil { - return err - } - if l != 3 { - return fmt.Errorf("array len doesn't match: %d", l) - } - if c.Cid, err = d.DecodeUint(); err != nil { - return err - } - if c.Orig, err = d.DecodeString(); err != nil { - return err - } - if l, err = d.DecodeSliceLen(); err != nil { - return err - } - c.Members = make([]Member, l) - for i := 0; i < l; i++ { - d.Decode(&c.Members[i]) - } - return nil -} - -func main() { - // establish connection ... - - tuple := Tuple{777, "orig", []Member{{"lol", "", 1}, {"wut", "", 3}}} - _, err = conn.Replace(spaceNo, tuple) // NOTE: insert structure itself - if err != nil { - t.Errorf("Failed to insert: %s", err.Error()) - return - } - - var tuples []Tuple - err = conn.SelectTyped(spaceNo, indexNo, 0, 1, IterEq, []interface{}{777}, &tuples) - if err != nil { - t.Errorf("Failed to SelectTyped: %s", err.Error()) - return - } - - // same result in a "magic" way - var tuples2 []Tuple2 - err = conn.SelectTyped(spaceNo, indexNo, 0, 1, IterEq, []interface{}{777}, &tuples2) - if err != nil { - t.Errorf("Failed to SelectTyped: %s", err.Error()) - return - } - - // call function 'func_name' returning a table of custom tuples - var tuples3 []Tuple - err = client.CallTyped("func_name", []interface{}{1, 2, 3}, &tuples3) - if err != nil { - t.Errorf("Failed to CallTyped: %s", err.Error()) - return - } -} - -/* -// Old way to register types -func init() { - msgpack.Register(reflect.TypeOf(Tuple{}), encodeTuple, decodeTuple) - msgpack.Register(reflect.TypeOf(Member{}), encodeMember, decodeMember) -} - -func encodeMember(e *msgpack.Encoder, v reflect.Value) error { - m := v.Interface().(Member) - // same code as in EncodeMsgpack - return nil -} - -func decodeMember(d *msgpack.Decoder, v reflect.Value) error { - m := v.Addr().Interface().(*Member) - // same code as in DecodeMsgpack - return nil -} - -func encodeTuple(e *msgpack.Encoder, v reflect.Value) error { - c := v.Interface().(Tuple) - // same code as in EncodeMsgpack - return nil -} - -func decodeTuple(d *msgpack.Decoder, v reflect.Value) error { - c := v.Addr().Interface().(*Tuple) - // same code as in DecodeMsgpack - return nil -} -*/ - -``` - -## Options +## Contributing -* `Timeout` - timeout for any particular request. If `Timeout` is zero request, - any request may block infinitely. -* `Reconnect` - timeout between reconnect attempts. If `Reconnect` is zero, no - reconnects will be performed. -* `MaxReconnects` - maximal number of reconnect failures; after that we give it - up. If `MaxReconnects` is zero, the client will try to reconnect endlessly. -* `User` - user name to log into Tarantool. -* `Pass` - user password to log into Tarantool. - -## Working with queue -```go -package main -import ( - "gopkg.in/vmihailenco/msgpack.v2" - "github.com/tarantool/go-tarantool" - "github.com/tarantool/go-tarantool/queue" - "time" - "fmt" - "log" -) - -type customData struct{ - Dummy bool -} - -func (c *customData) DecodeMsgpack(d *msgpack.Decoder) error { - var err error - if c.Dummy, err = d.DecodeBool(); err != nil { - return err - } - return nil -} - -func (c *customData) EncodeMsgpack(e *msgpack.Encoder) error { - return e.EncodeBool(c.Dummy) -} - -func main() { - opts := tarantool.Opts{ - Timeout: time.Second, - Reconnect: time.Second, - MaxReconnects: 5, - User: "user", - Pass: "pass", - // ... - } - conn, err := tarantool.Connect("127.0.0.1:3301", opts) - - if err != nil { - log.Fatalf("connection: %s", err) - return - } - - cfg := queue.Cfg{ - Temporary: true, - IfNotExists: true, - Kind: queue.FIFO, - Opts: queue.Opts{ - Ttl: 10 * time.Second, - Ttr: 5 * time.Second, - Delay: 3 * time.Second, - Pri: 1, - }, - } - - que := queue.New(conn, "test_queue") - if err = que.Create(cfg); err != nil { - log.Fatalf("queue create: %s", err) - return - } - - // put data - task, err := que.Put("test_data") - if err != nil { - log.Fatalf("put task: %s", err) - } - fmt.Println("Task id is", task.Id()) - - // take data - task, err = que.Take() //blocking operation - if err != nil { - log.Fatalf("take task: %s", err) - } - fmt.Println("Data is", task.Data()) - task.Ack() - - // take typed example - putData := customData{} - // put data - task, err = que.Put(&putData) - if err != nil { - log.Fatalf("put typed task: %s", err) - } - fmt.Println("Task id is ", task.Id()) - - takeData := customData{} - //take data - task, err = que.TakeTyped(&takeData) //blocking operation - if err != nil { - log.Fatalf("take take typed: %s", err) - } - fmt.Println("Data is ", takeData) - // same data - fmt.Println("Data is ", task.Data()) - - task, err = que.Put([]int{1, 2, 3}) - task.Bury() - - task, err = que.TakeTimeout(2 * time.Second) - if task == nil { - fmt.Println("Task is nil") - } - - que.Drop() -} -``` -Features of the implementation: - -- If you use connection timeout and call `TakeWithTimeout` with parameter greater than the connection timeout then parameter reduced to it -- If you use connection timeout and call `Take` then we return a error if we can not take task from queue in a time equal to the connection timeout - -## Multi connections - -You can use multiple connections config with tarantool/multi. - -Main features: - -- Check active connection with configurable time interval and on connection fail switch to next in pool. -- Get addresses list from server and reconfigure to use in MultiConnection. - -Additional options (configurable via `ConnectWithOpts`): - -* `CheckTimeout` - time interval to check for connection timeout and try to switch connection -* `ClusterDiscoveryTime` - time interval to ask server for updated address list (works on with `NodesGetFunctionName` set) -* `NodesGetFunctionName` - server lua function name to call for getting address list - -## Tests - -You need to [install Tarantool](https://www.tarantool.io/en/download/) to run tests. -See [Installation](#installation) section for requirements. - -To install test dependencies (like [tarantool/queue](https://github.com/tarantool/queue) module), run -```bash -make deps -``` - -To run tests for the main package and each subpackage, call -```bash -make test -``` -Tests set up all required `tarantool` processes before run and clean up after. - -If you want to run a specific package tests, go to a package folder -```bash -cd multi -``` -and call -```bash -go clean -testcache && go test -v -``` -Use the same for main `tarantool` package and `queue` and `uuid` subpackages. -`uuid` tests require -[Tarantool 2.4.1 or newer](https://github.com/tarantool/tarantool/commit/d68fc29246714eee505bc9bbcd84a02de17972c5). +See [the contributing guide](CONTRIBUTING.md) for detailed instructions on how +to get started with our project. ## Alternative connectors -There are two more connectors from the open-source community available: +There are two other connectors available from the open source community: + * [viciious/go-tarantool](https://github.com/viciious/go-tarantool), * [FZambia/tarantool](https://github.com/FZambia/tarantool). -See feature comparison in [documentation](https://www.tarantool.io/en/doc/latest/book/connectors/#go-feature-comparison). +See feature comparison in the [documentation][tarantool-doc-connectors-comparison]. + +[tarantool-site]: https://tarantool.io/ +[godoc-badge]: https://pkg.go.dev/badge/github.com/tarantool/go-tarantool.svg +[godoc-url]: https://pkg.go.dev/github.com/tarantool/go-tarantool +[actions-badge]: https://github.com/tarantool/go-tarantool/actions/workflows/testing.yml/badge.svg +[actions-url]: https://github.com/tarantool/go-tarantool/actions/workflows/testing.yml +[coverage-badge]: https://coveralls.io/repos/github/tarantool/go-tarantool/badge.svg?branch=master +[coverage-url]: https://coveralls.io/github/tarantool/go-tarantool?branch=master +[telegram-badge]: https://img.shields.io/badge/Telegram-join%20chat-blue.svg +[telegram-url]: http://telegram.me/tarantool +[discussions-badge]: https://img.shields.io/github/discussions/tarantool/tarantool +[discussions-url]: https://github.com/tarantool/tarantool/discussions +[stackoverflow-badge]: https://img.shields.io/badge/stackoverflow-tarantool-orange.svg +[stackoverflow-url]: https://stackoverflow.com/questions/tagged/tarantool +[golang-dl]: https://go.dev/dl/ +[go-tarantool]: https://github.com/tarantool/go-tarantool +[tarantool-doc-data-model-url]: https://www.tarantool.io/en/doc/latest/book/box/data_model/ +[tarantool-doc-box-space-url]: https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_space/ +[godoc-opts-url]: https://pkg.go.dev/github.com/tarantool/go-tarantool#Opts +[tarantool-doc-connectors-comparison]: https://www.tarantool.io/en/doc/latest/book/connectors/#go-feature-comparison diff --git a/auth.go b/auth.go index 9f6c79157..60c219d69 100644 --- a/auth.go +++ b/auth.go @@ -25,7 +25,7 @@ func scramble(encodedSalt, pass string) (scramble []byte, err error) { } step1 := sha1.Sum([]byte(pass)) step2 := sha1.Sum(step1[0:]) - hash := sha1.New() // may be create it once per connection ? + hash := sha1.New() // May be create it once per connection? hash.Write(salt[0:scrambleSize]) hash.Write(step2[0:]) step3 := hash.Sum(nil) diff --git a/client_tools.go b/client_tools.go index ed76ea258..de10a366b 100644 --- a/client_tools.go +++ b/client_tools.go @@ -4,7 +4,7 @@ import ( "gopkg.in/vmihailenco/msgpack.v2" ) -// IntKey is utility type for passing integer key to Select*, Update* and Delete* +// IntKey is utility type for passing integer key to Select*, Update* and Delete*. // It serializes to array with single integer element. type IntKey struct { I int @@ -16,7 +16,7 @@ func (k IntKey) EncodeMsgpack(enc *msgpack.Encoder) error { return nil } -// UintKey is utility type for passing unsigned integer key to Select*, Update* and Delete* +// UintKey is utility type for passing unsigned integer key to Select*, Update* and Delete*. // It serializes to array with single integer element. type UintKey struct { I uint @@ -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* +// UintKey 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 @@ -40,8 +40,8 @@ func (k StringKey) EncodeMsgpack(enc *msgpack.Encoder) error { return nil } -// IntIntKey is utility type for passing two integer keys to Select*, Update* and Delete* -// It serializes to array with two integer elements +// IntIntKey is utility type for passing two integer keys to Select*, Update* and Delete*. +// It serializes to array with two integer elements. type IntIntKey struct { I1, I2 int } @@ -53,7 +53,7 @@ func (k IntIntKey) EncodeMsgpack(enc *msgpack.Encoder) error { return nil } -// Op - is update operation +// Op - is update operation. type Op struct { Op string Field int diff --git a/config.lua b/config.lua index 2528e7b81..c768cd746 100644 --- a/config.lua +++ b/config.lua @@ -51,6 +51,18 @@ box.once("init", function() box.schema.user.grant('test', 'read,write', 'space', 'schematest') end) +local function func_name() + return { + {221, "", { + {"Moscow", 34}, + {"Minsk", 23}, + {"Kiev", 31}, + } + } + } +end +rawset(_G, 'func_name', func_name) + local function simple_incr(a) return a + 1 end diff --git a/connection.go b/connection.go index 102b58d50..d8e381364 100644 --- a/connection.go +++ b/connection.go @@ -1,3 +1,5 @@ +// Package with implementation of methods and structures for work with +// Tarantool instance. package tarantool import ( @@ -27,26 +29,26 @@ type ConnEventKind int type ConnLogKind int const ( - // Connect signals that connection is established or reestablished + // Connected signals that connection is established or reestablished. Connected ConnEventKind = iota + 1 - // Disconnect signals that connection is broken + // Disconnected signals that connection is broken. Disconnected - // ReconnectFailed signals that attempt to reconnect has failed + // ReconnectFailed signals that attempt to reconnect has failed. ReconnectFailed - // Either reconnect attempts exhausted, or explicit Close is called + // Either reconnect attempts exhausted, or explicit Close is called. Closed - // LogReconnectFailed is logged when reconnect attempt failed + // LogReconnectFailed is logged when reconnect attempt failed. LogReconnectFailed ConnLogKind = iota + 1 // LogLastReconnectFailed is logged when last reconnect attempt failed, // connection will be closed after that. LogLastReconnectFailed - // LogUnexpectedResultId is logged when response with unknown id were received. + // LogUnexpectedResultId is logged when response with unknown id was received. // Most probably it is due to request timeout. LogUnexpectedResultId ) -// ConnEvent is sent throw Notify channel specified in Opts +// ConnEvent is sent throw Notify channel specified in Opts. type ConnEvent struct { Conn *Connection Kind ConnEventKind @@ -80,39 +82,39 @@ func (d defaultLogger) Report(event ConnLogKind, conn *Connection, v ...interfac } } -// Connection is a handle to Tarantool. +// Connection is a handle with a single connection to a Tarantool instance. // // It is created and configured with Connect function, and could not be // reconfigured later. // -// It is could be "Connected", "Disconnected", and "Closed". +// Connection could be in three possible states: // -// When "Connected" it sends queries to Tarantool. +// - In "Connected" state it sends queries to Tarantool. // -// When "Disconnected" it rejects queries with ClientError{Code: ErrConnectionNotReady} +// - In "Disconnected" state it rejects queries with ClientError{Code: +// ErrConnectionNotReady} // -// When "Closed" it rejects queries with ClientError{Code: ErrConnectionClosed} -// -// Connection could become "Closed" when Connection.Close() method called, -// or when Tarantool disconnected and Reconnect pause is not specified or -// MaxReconnects is specified and MaxReconnect reconnect attempts already performed. +// - In "Closed" state it rejects queries with ClientError{Code: +// ErrConnectionClosed}. Connection could become "Closed" when +// Connection.Close() method called, or when Tarantool disconnected and +// Reconnect pause is not specified or MaxReconnects is specified and +// MaxReconnect reconnect attempts already performed. // // You may perform data manipulation operation by calling its methods: // Call*, Insert*, Replace*, Update*, Upsert*, Call*, Eval*. // -// In any method that accepts `space` you my pass either space number or -// space name (in this case it will be looked up in schema). Same is true for `index`. +// In any method that accepts space you my pass either space number or space +// name (in this case it will be looked up in schema). Same is true for index. // -// ATTENTION: `tuple`, `key`, `ops` and `args` arguments for any method should be +// ATTENTION: tuple, key, ops and args arguments for any method should be // and array or should serialize to msgpack array. // -// ATTENTION: `result` argument for *Typed methods should deserialize from +// ATTENTION: result argument for *Typed methods should deserialize from // msgpack array, cause Tarantool always returns result as an array. // For all space related methods and Call* (but not Call17*) methods Tarantool // always returns array of array (array of tuples for space related methods). -// For Eval* and Call17* tarantool always returns array, but does not forces +// For Eval* and Call17* Tarantool always returns array, but does not forces // array of arrays. - type Connection struct { addr string c net.Conn @@ -120,7 +122,7 @@ type Connection struct { // Schema contains schema loaded on connection. Schema *Schema requestId uint32 - // Greeting contains first message sent by tarantool + // Greeting contains first message sent by Tarantool. Greeting *Greeting shard []connShard @@ -134,7 +136,7 @@ type Connection struct { lenbuf [PacketLengthBytes]byte } -var _ = Connector(&Connection{}) // check compatibility with connector interface +var _ = Connector(&Connection{}) // Check compatibility with connector interface. type connShard struct { rmut sync.Mutex @@ -148,7 +150,7 @@ type connShard struct { _pad [16]uint64 } -// Greeting is a message sent by tarantool on connect. +// Greeting is a message sent by Tarantool on connect. type Greeting struct { Version string auth string @@ -156,23 +158,27 @@ type Greeting struct { // Opts is a way to configure Connection type Opts struct { - // Timeout is requests timeout. - // Also used to setup net.TCPConn.Set(Read|Write)Deadline + // Timeout for any particular request. If Timeout is zero request, any + // request can be blocked infinitely. + // Also used to setup net.TCPConn.Set(Read|Write)Deadline. Timeout time.Duration - // Reconnect is a pause between reconnection attempts. - // If specified, then when tarantool is not reachable or disconnected, + // Timeout between reconnect attempts. If Reconnect is zero, no + // reconnect attempts will be made. + // If specified, then when Tarantool is not reachable or disconnected, // new connect attempt is performed after pause. // By default, no reconnection attempts are performed, // so once disconnected, connection becomes Closed. Reconnect time.Duration - // MaxReconnects is a maximum reconnect attempts. + // Maximum number of reconnect failures; after that we give it up to + // on. If MaxReconnects is zero, the client will try to reconnect + // endlessly. // After MaxReconnects attempts Connection becomes closed. MaxReconnects uint - // User name for authorization + // Username for logging in to Tarantool. User string - // Pass is password for authorization + // User password for logging in to Tarantool. Pass string - // RateLimit limits number of 'in-fly' request, ie already put into + // RateLimit limits number of 'in-fly' request, i.e. already put into // requests queue, but not yet answered by server or timeouted. // It is disabled by default. // See RLimitAction for possible actions when RateLimit.reached. @@ -191,42 +197,37 @@ type Opts struct { // By default it is runtime.GOMAXPROCS(-1) * 4 Concurrency uint32 // SkipSchema disables schema loading. Without disabling schema loading, - // there is no way to create Connection for currently not accessible tarantool. + // there is no way to create Connection for currently not accessible Tarantool. SkipSchema bool // Notify is a channel which receives notifications about Connection status // changes. Notify chan<- ConnEvent - // Handle is user specified value, that could be retrivied with Handle() method + // Handle is user specified value, that could be retrivied with + // Handle() method. Handle interface{} - // Logger is user specified logger used for error messages + // Logger is user specified logger used for error messages. Logger Logger } -// Connect creates and configures new Connection +// Connect creates and configures a new Connection. // // Address could be specified in following ways: // -// TCP connections: -// - tcp://192.168.1.1:3013 -// - tcp://my.host:3013 -// - tcp:192.168.1.1:3013 -// - tcp:my.host:3013 -// - 192.168.1.1:3013 -// - my.host:3013 -// Unix socket: -// - unix:///abs/path/tnt.sock -// - unix:path/tnt.sock -// - /abs/path/tnt.sock - first '/' indicates unix socket -// - ./rel/path/tnt.sock - first '.' indicates unix socket -// - unix/:path/tnt.sock - 'unix/' acts as a "host" and "/path..." as a port +// - TCP connections (tcp://192.168.1.1:3013, tcp://my.host:3013, +// tcp:192.168.1.1:3013, tcp:my.host:3013, 192.168.1.1:3013, my.host:3013) +// +// - Unix socket, first '/' or '.' indicates Unix socket +// (unix:///abs/path/tnt.sock, unix:path/tnt.sock, /abs/path/tnt.sock, +// ./rel/path/tnt.sock, unix/:path/tnt.sock) // -// Note: +// Notes: // // - If opts.Reconnect is zero (default), then connection either already connected // or error is returned. // -// - If opts.Reconnect is non-zero, then error will be returned only if authorization// fails. But if Tarantool is not reachable, then it will attempt to reconnect later -// and will not end attempts on authorization failures. +// - If opts.Reconnect is non-zero, then error will be returned only if authorization +// fails. But if Tarantool is not reachable, then it will make an attempt to reconnect later +// and will not finish to make attempts on authorization failures. func Connect(addr string, opts Opts) (conn *Connection, err error) { conn = &Connection{ addr: addr, @@ -272,10 +273,10 @@ func Connect(addr string, opts Opts) (conn *Connection, err error) { return nil, err } else if ok && (ter.Code == ErrNoSuchUser || ter.Code == ErrPasswordMismatch) { - /* reported auth errors immediatly */ + // Reported auth errors immediately. return nil, err } else { - // without SkipSchema it is useless + // Without SkipSchema it is useless. go func(conn *Connection) { conn.mutex.Lock() defer conn.mutex.Unlock() @@ -292,7 +293,7 @@ func Connect(addr string, opts Opts) (conn *Connection, err error) { go conn.timeouts() } - // TODO: reload schema after reconnect + // TODO: reload schema after reconnect. if !conn.opts.SkipSchema { if err = conn.loadSchema(); err != nil { conn.mutex.Lock() @@ -324,12 +325,12 @@ func (conn *Connection) Close() error { return conn.closeConnection(err, true) } -// Addr is configured address of Tarantool socket +// Addr returns a configured address of Tarantool socket. func (conn *Connection) Addr() string { return conn.addr } -// RemoteAddr is address of Tarantool socket +// RemoteAddr returns an address of Tarantool socket. func (conn *Connection) RemoteAddr() string { conn.mutex.Lock() defer conn.mutex.Unlock() @@ -339,7 +340,7 @@ func (conn *Connection) RemoteAddr() string { return conn.c.RemoteAddr().String() } -// LocalAddr is address of outgoing socket +// LocalAddr returns an address of outgoing socket. func (conn *Connection) LocalAddr() string { conn.mutex.Lock() defer conn.mutex.Unlock() @@ -349,7 +350,7 @@ func (conn *Connection) LocalAddr() string { return conn.c.LocalAddr().String() } -// Handle returns user specified handle from Opts +// Handle returns a user-specified handle from Opts. func (conn *Connection) Handle() interface{} { return conn.opts.Handle } @@ -416,7 +417,7 @@ func (conn *Connection) dial() (err error) { } } - // Only if connected and authenticated + // Only if connected and authenticated. conn.lockShards() conn.c = connection atomic.StoreUint32(&conn.state, connConnected) @@ -873,12 +874,12 @@ func (conn *Connection) nextRequestId() (requestId uint32) { return atomic.AddUint32(&conn.requestId, 1) } -// ConfiguredTimeout returns timeout from connection config +// ConfiguredTimeout returns a timeout from connection config. func (conn *Connection) ConfiguredTimeout() time.Duration { return conn.opts.Timeout } -// OverrideSchema sets Schema for the connection +// OverrideSchema sets Schema for the connection. func (conn *Connection) OverrideSchema(s *Schema) { if s != nil { conn.mutex.Lock() diff --git a/errors.go b/errors.go index 8eef0bfb4..a6895ccbc 100644 --- a/errors.go +++ b/errors.go @@ -4,32 +4,38 @@ import ( "fmt" ) -// Error is wrapper around error returned by Tarantool +// Error is wrapper around error returned by Tarantool. type Error struct { Code uint32 Msg string } +// Error converts an Error to a string. func (tnterr Error) Error() string { return fmt.Sprintf("%s (0x%x)", tnterr.Msg, tnterr.Code) } -// ClientError is connection produced by this client, -// ie connection failures or timeouts. +// ClientError is connection error produced by this client, +// i.e. connection failures or timeouts. type ClientError struct { Code uint32 Msg string } +// Error converts a ClientError to a string. func (clierr ClientError) Error() string { return fmt.Sprintf("%s (0x%x)", clierr.Msg, clierr.Code) } // Temporary returns true if next attempt to perform request may succeeed. +// // Currently it returns true when: -// - Connection is not connected at the moment, -// - or request is timeouted, -// - or request is aborted due to rate limit. +// +// - Connection is not connected at the moment +// +// - request is timeouted +// +// - request is aborted due to rate limit func (clierr ClientError) Temporary() bool { switch clierr.Code { case ErrConnectionNotReady, ErrTimeouted, ErrRateLimited: @@ -39,7 +45,7 @@ func (clierr ClientError) Temporary() bool { } } -// Tarantool client error codes +// Tarantool client error codes. const ( ErrConnectionNotReady = 0x4000 + iota ErrConnectionClosed = 0x4000 + iota @@ -48,7 +54,7 @@ const ( ErrRateLimited = 0x4000 + iota ) -// Tarantool server error codes +// Tarantool server error codes. const ( ErrUnknown = 0 // Unknown error ErrIllegalParams = 1 // Illegal parameters, %s diff --git a/example_custom_unpacking_test.go b/example_custom_unpacking_test.go new file mode 100644 index 000000000..772c9faf3 --- /dev/null +++ b/example_custom_unpacking_test.go @@ -0,0 +1,135 @@ +package tarantool_test + +import ( + "fmt" + "github.com/tarantool/go-tarantool" + "gopkg.in/vmihailenco/msgpack.v2" + "log" + "time" +) + +type Tuple2 struct { + Cid uint + Orig string + Members []Member +} + +// Same effect in a "magic" way, but slower. +type Tuple3 struct { + _msgpack struct{} `msgpack:",asArray"` + + Cid uint + Orig string + Members []Member +} + +func (c *Tuple2) EncodeMsgpack(e *msgpack.Encoder) error { + if err := e.EncodeSliceLen(3); err != nil { + return err + } + if err := e.EncodeUint(c.Cid); err != nil { + return err + } + if err := e.EncodeString(c.Orig); err != nil { + return err + } + e.Encode(c.Members) + return nil +} + +func (c *Tuple2) DecodeMsgpack(d *msgpack.Decoder) error { + var err error + var l int + if l, err = d.DecodeSliceLen(); err != nil { + return err + } + if l != 3 { + return fmt.Errorf("array len doesn't match: %d", l) + } + if c.Cid, err = d.DecodeUint(); err != nil { + return err + } + if c.Orig, err = d.DecodeString(); err != nil { + return err + } + if l, err = d.DecodeSliceLen(); err != nil { + return err + } + c.Members = make([]Member, l) + for i := 0; i < l; i++ { + d.Decode(&c.Members[i]) + } + return nil +} + +// Example demonstrates how to use custom (un)packing with typed selects and +// function calls. +// +// You can specify user-defined packing/unpacking functions for your types. +// This allows you to store complex structures within a tuple and may speed up +// your requests. +// +// Alternatively, you can just instruct the msgpack library to encode your +// structure as an array. This is safe "magic". It is easier to implement than +// a custom packer/unpacker, but it will work slower. +func Example_customUnpacking() { + // Establish a connection. + server := "127.0.0.1:3013" + opts := tarantool.Opts{ + Timeout: 500 * time.Millisecond, + Reconnect: 1 * time.Second, + MaxReconnects: 3, + User: "test", + Pass: "test", + } + conn, err := tarantool.Connect(server, opts) + if err != nil { + log.Fatalf("Failed to connect: %s", err.Error()) + } + + spaceNo := uint32(512) + indexNo := uint32(0) + + tuple := Tuple2{Cid: 777, Orig: "orig", Members: []Member{{"lol", "", 1}, {"wut", "", 3}}} + resp, err := conn.Replace(spaceNo, &tuple) // NOTE: insert a structure itself. + if err != nil { + log.Fatalf("Failed to insert: %s", err.Error()) + return + } + fmt.Println("Data", resp.Data) + fmt.Println("Code", resp.Code) + + var tuples1 []Tuple2 + err = conn.SelectTyped(spaceNo, indexNo, 0, 1, tarantool.IterEq, []interface{}{777}, &tuples1) + if err != nil { + log.Fatalf("Failed to SelectTyped: %s", err.Error()) + return + } + fmt.Println("Tuples (tuples1)", tuples1) + + // Same result in a "magic" way. + var tuples2 []Tuple3 + err = conn.SelectTyped(spaceNo, indexNo, 0, 1, tarantool.IterEq, []interface{}{777}, &tuples2) + if err != nil { + log.Fatalf("Failed to SelectTyped: %s", err.Error()) + return + } + fmt.Println("Tuples (tuples2):", tuples2) + + // Call a function "func_name" returning a table of custom tuples. + var tuples3 []Tuple3 + err = conn.CallTyped("func_name", []interface{}{}, &tuples3) + if err != nil { + log.Fatalf("Failed to CallTyped: %s", err.Error()) + return + } + fmt.Println("Tuples (tuples3):", tuples3) + + // 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 7b81bc28e..1e8c883ab 100644 --- a/example_test.go +++ b/example_test.go @@ -8,40 +8,29 @@ import ( ) type Tuple struct { - /* instruct msgpack to pack this struct as array, - * so no custom packer is needed */ + // Instruct msgpack to pack this struct as array, so no custom packer + // is needed. _msgpack struct{} `msgpack:",asArray"` Id uint Msg string Name string } -func example_connect() (*tarantool.Connection, error) { +func example_connect() *tarantool.Connection { conn, err := tarantool.Connect(server, opts) if err != nil { - return nil, err + panic("Connection is not established") } - _, err = conn.Replace(spaceNo, []interface{}{uint(1111), "hello", "world"}) - if err != nil { - conn.Close() - return nil, err - } - _, err = conn.Replace(spaceNo, []interface{}{uint(1112), "hallo", "werld"}) - if err != nil { - conn.Close() - return nil, err - } - return conn, nil + return conn } func ExampleConnection_Select() { - var conn *tarantool.Connection - conn, err := example_connect() - if err != nil { - fmt.Printf("error in prepare is %v", err) - return - } + conn := example_connect() defer conn.Close() + + conn.Replace(spaceNo, []interface{}{uint(1111), "hello", "world"}) + conn.Replace(spaceNo, []interface{}{uint(1112), "hallo", "werld"}) + resp, err := conn.Select(512, 0, 0, 100, tarantool.IterEq, []interface{}{uint(1111)}) if err != nil { fmt.Printf("error in select is %v", err) @@ -54,21 +43,17 @@ func ExampleConnection_Select() { return } fmt.Printf("response is %#v\n", resp.Data) + // Output: // response is []interface {}{[]interface {}{0x457, "hello", "world"}} // response is []interface {}{[]interface {}{0x457, "hello", "world"}} } func ExampleConnection_SelectTyped() { - var conn *tarantool.Connection - conn, err := example_connect() - if err != nil { - fmt.Printf("error in prepare is %v", err) - return - } + conn := example_connect() defer conn.Close() var res []Tuple - err = conn.SelectTyped(512, 0, 0, 100, tarantool.IterEq, tarantool.IntKey{1111}, &res) + err := conn.SelectTyped(512, 0, 0, 100, tarantool.IterEq, tarantool.IntKey{1111}, &res) if err != nil { fmt.Printf("error in select is %v", err) return @@ -85,140 +70,305 @@ func ExampleConnection_SelectTyped() { // response is [{{} 1111 hello world}] } -func Example() { - spaceNo := uint32(512) - indexNo := uint32(0) - - server := "127.0.0.1:3013" - opts := tarantool.Opts{ - Timeout: 50 * time.Millisecond, - Reconnect: 100 * time.Millisecond, - MaxReconnects: 3, - User: "test", - Pass: "test", - } - client, err := tarantool.Connect(server, opts) - if err != nil { - fmt.Errorf("Failed to connect: %s", err.Error()) - return - } - - resp, err := client.Ping() - fmt.Println("Ping Code", resp.Code) - fmt.Println("Ping Data", resp.Data) - fmt.Println("Ping Error", err) +func ExampleConnection_SelectAsync() { + conn := example_connect() + defer conn.Close() - // delete tuple for cleaning - client.Delete(spaceNo, indexNo, []interface{}{uint(10)}) - client.Delete(spaceNo, indexNo, []interface{}{uint(11)}) - - // insert new tuple { 10, 1 } - resp, err = client.Insert(spaceNo, []interface{}{uint(10), "test", "one"}) - fmt.Println("Insert Error", err) - fmt.Println("Insert Code", resp.Code) - fmt.Println("Insert Data", resp.Data) - - // insert new tuple { 11, 1 } - resp, err = client.Insert("test", &Tuple{Id: 11, Msg: "test", Name: "one"}) - fmt.Println("Insert Error", err) - fmt.Println("Insert Code", resp.Code) - fmt.Println("Insert Data", resp.Data) - - // delete tuple with primary key { 10 } - resp, err = client.Delete(spaceNo, indexNo, []interface{}{uint(10)}) - // or - // resp, err = client.Delete("test", "primary", UintKey{10}}) - fmt.Println("Delete Error", err) - fmt.Println("Delete Code", resp.Code) - fmt.Println("Delete Data", resp.Data) - - // replace tuple with primary key 13 - // note, Tuple is defined within tests, and has EncdodeMsgpack and DecodeMsgpack - // methods - resp, err = client.Replace(spaceNo, []interface{}{uint(13), 1}) - fmt.Println("Replace Error", err) - fmt.Println("Replace Code", resp.Code) - fmt.Println("Replace Data", resp.Data) - - // update tuple with primary key { 13 }, incrementing second field by 3 - resp, err = client.Update("test", "primary", tarantool.UintKey{13}, []tarantool.Op{{"+", 1, 3}}) - // or - // resp, err = client.Update(spaceNo, indexNo, []interface{}{uint(13)}, []interface{}{[]interface{}{"+", 1, 3}}) - fmt.Println("Update Error", err) - fmt.Println("Update Code", resp.Code) - fmt.Println("Update Data", resp.Data) - - // select just one tuple with primay key { 15 } - resp, err = client.Select(spaceNo, indexNo, 0, 1, tarantool.IterEq, []interface{}{uint(15)}) - // or - // resp, err = client.Select("test", "primary", 0, 1, tarantool.IterEq, tarantool.UintKey{15}) - fmt.Println("Select Error", err) - fmt.Println("Select Code", resp.Code) - fmt.Println("Select Data", resp.Data) - - // call function 'func_name' with arguments - resp, err = client.Call17("simple_incr", []interface{}{1}) - fmt.Println("Call17 Error", err) - fmt.Println("Call17 Code", resp.Code) - fmt.Println("Call17 Data", resp.Data) - - // run raw lua code - resp, err = client.Eval("return 1 + 2", []interface{}{}) - fmt.Println("Eval Error", err) - fmt.Println("Eval Code", resp.Code) - fmt.Println("Eval Data", resp.Data) - - resp, err = client.Replace("test", &Tuple{Id: 11, Msg: "test", Name: "eleven"}) - resp, err = client.Replace("test", &Tuple{Id: 12, Msg: "test", Name: "twelve"}) + conn.Insert(spaceNo, []interface{}{uint(16), "test", "one"}) + conn.Insert(spaceNo, []interface{}{uint(17), "test", "one"}) + conn.Insert(spaceNo, []interface{}{uint(18), "test", "one"}) var futs [3]*tarantool.Future - futs[0] = client.SelectAsync("test", "primary", 0, 2, tarantool.IterLe, tarantool.UintKey{12}) - futs[1] = client.SelectAsync("test", "primary", 0, 1, tarantool.IterEq, tarantool.UintKey{13}) - futs[2] = client.SelectAsync("test", "primary", 0, 1, tarantool.IterEq, tarantool.UintKey{15}) + futs[0] = conn.SelectAsync("test", "primary", 0, 2, tarantool.IterLe, tarantool.UintKey{16}) + futs[1] = conn.SelectAsync("test", "primary", 0, 1, tarantool.IterEq, tarantool.UintKey{17}) + futs[2] = conn.SelectAsync("test", "primary", 0, 1, tarantool.IterEq, tarantool.UintKey{18}) var t []Tuple - err = futs[0].GetTyped(&t) - fmt.Println("Fut", 0, "Error", err) - fmt.Println("Fut", 0, "Data", t) + err := futs[0].GetTyped(&t) + fmt.Println("Future", 0, "Error", err) + fmt.Println("Future", 0, "Data", t) - resp, err = futs[1].Get() - fmt.Println("Fut", 1, "Error", err) - fmt.Println("Fut", 1, "Data", resp.Data) + resp, err := futs[1].Get() + fmt.Println("Future", 1, "Error", err) + fmt.Println("Future", 1, "Data", resp.Data) resp, err = futs[2].Get() - fmt.Println("Fut", 2, "Error", err) - fmt.Println("Fut", 2, "Data", resp.Data) + fmt.Println("Future", 2, "Error", err) + fmt.Println("Future", 2, "Data", resp.Data) + // Output: + // Future 0 Error + // Future 0 Data [{{} 16 val 16 bla} {{} 15 val 15 bla}] + // Future 1 Error + // Future 1 Data [[17 val 17 bla]] + // Future 2 Error + // Future 2 Data [[18 val 18 bla]] +} + +func ExampleConnection_Ping() { + conn := example_connect() + defer conn.Close() + + // Ping a Tarantool instance to check connection. + resp, err := conn.Ping() + fmt.Println("Ping Code", resp.Code) + fmt.Println("Ping Data", resp.Data) + fmt.Println("Ping Error", err) // Output: // Ping Code 0 // Ping Data [] // Ping Error - // Insert Error - // Insert Code 0 - // Insert Data [[10 test one]] - // Insert Error - // Insert Code 0 - // Insert Data [[11 test one]] - // Delete Error - // Delete Code 0 - // Delete Data [[10 test one]] - // Replace Error - // Replace Code 0 - // Replace Data [[13 1]] - // Update Error - // Update Code 0 - // Update Data [[13 4]] - // Select Error - // Select Code 0 - // Select Data [[15 val 15 bla]] - // Call17 Error - // Call17 Code 0 - // Call17 Data [2] - // Eval Error - // Eval Code 0 - // Eval Data [3] - // Fut 0 Error - // Fut 0 Data [{{} 12 test twelve} {{} 11 test eleven}] - // Fut 1 Error - // Fut 1 Data [[13 4]] - // Fut 2 Error - // Fut 2 Data [[15 val 15 bla]] +} + +func ExampleConnection_Insert() { + conn := example_connect() + defer conn.Close() + + // Insert a new tuple { 31, 1 }. + resp, err := conn.Insert(spaceNo, []interface{}{uint(31), "test", "one"}) + fmt.Println("Insert 31") + fmt.Println("Error", err) + fmt.Println("Code", resp.Code) + fmt.Println("Data", resp.Data) + // Insert a new tuple { 32, 1 }. + resp, err = conn.Insert("test", &Tuple{Id: 32, Msg: "test", Name: "one"}) + fmt.Println("Insert 32") + fmt.Println("Error", err) + fmt.Println("Code", resp.Code) + fmt.Println("Data", resp.Data) + + // Delete tuple with primary key { 31 }. + conn.Delete("test", "primary", []interface{}{uint(31)}) + // Delete tuple with primary key { 32 }. + conn.Delete(spaceNo, indexNo, []interface{}{uint(32)}) + // Output: + // Insert 31 + // Error + // Code 0 + // Data [[31 test one]] + // Insert 32 + // Error + // Code 0 + // Data [[32 test one]] + +} + +func ExampleConnection_Delete() { + conn := example_connect() + defer conn.Close() + + // Insert a new tuple { 35, 1 }. + conn.Insert(spaceNo, []interface{}{uint(35), "test", "one"}) + // Insert a new tuple { 36, 1 }. + conn.Insert("test", &Tuple{Id: 36, Msg: "test", Name: "one"}) + + // Delete tuple with primary key { 35 }. + resp, err := conn.Delete(spaceNo, indexNo, []interface{}{uint(35)}) + fmt.Println("Delete 35") + fmt.Println("Error", err) + fmt.Println("Code", resp.Code) + fmt.Println("Data", resp.Data) + + // Delete tuple with primary key { 36 }. + resp, err = conn.Delete("test", "primary", []interface{}{uint(36)}) + fmt.Println("Delete 36") + fmt.Println("Error", err) + fmt.Println("Code", resp.Code) + fmt.Println("Data", resp.Data) + // Output: + // Delete 35 + // Error + // Code 0 + // Data [[35 test one]] + // Delete 36 + // Error + // Code 0 + // Data [[36 test one]] +} + +func ExampleConnection_Replace() { + conn := example_connect() + defer conn.Close() + + // Insert a new tuple { 13, 1 }. + conn.Insert(spaceNo, []interface{}{uint(13), "test", "one"}) + + // Replace a tuple with primary key 13. + // Note, Tuple is defined within tests, and has EncdodeMsgpack and + // DecodeMsgpack methods. + resp, err := conn.Replace(spaceNo, []interface{}{uint(13), 1}) + fmt.Println("Replace 13") + fmt.Println("Error", err) + fmt.Println("Code", resp.Code) + fmt.Println("Data", resp.Data) + resp, err = conn.Replace("test", []interface{}{uint(13), 1}) + fmt.Println("Replace 13") + fmt.Println("Error", err) + fmt.Println("Code", resp.Code) + fmt.Println("Data", resp.Data) + resp, err = conn.Replace("test", &Tuple{Id: 13, Msg: "test", Name: "eleven"}) + fmt.Println("Replace 13") + fmt.Println("Error", err) + fmt.Println("Code", resp.Code) + fmt.Println("Data", resp.Data) + resp, err = conn.Replace("test", &Tuple{Id: 13, Msg: "test", Name: "twelve"}) + fmt.Println("Replace 13") + fmt.Println("Error", err) + fmt.Println("Code", resp.Code) + fmt.Println("Data", resp.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]] +} + +func ExampleConnection_Update() { + conn := example_connect() + defer conn.Close() + + // Insert a new tuple { 14, 1 }. + conn.Insert(spaceNo, []interface{}{uint(14), "test", "one"}) + + // Update tuple with primary key { 14 }. + resp, err := conn.Update(spaceName, indexName, []interface{}{uint(14)}, []interface{}{[]interface{}{"=", 1, "bye"}}) + fmt.Println("Update 14") + fmt.Println("Error", err) + fmt.Println("Code", resp.Code) + fmt.Println("Data", resp.Data) + // Output: + // Update 14 + // Error + // Code 0 + // Data [[14 bye bla]] +} + +func ExampleConnection_Call17() { + conn := example_connect() + defer conn.Close() + + // Call a function 'simple_incr' with arguments. + resp, err := conn.Call17("simple_incr", []interface{}{1}) + fmt.Println("Call simple_incr()") + fmt.Println("Error", err) + fmt.Println("Code", resp.Code) + fmt.Println("Data", resp.Data) + // Output: + // Call simple_incr() + // Error + // Code 0 + // Data [2] +} + +func ExampleConnection_Eval() { + conn := example_connect() + defer conn.Close() + + // Run raw Lua code. + resp, err := conn.Eval("return 1 + 2", []interface{}{}) + fmt.Println("Eval 'return 1 + 2'") + fmt.Println("Error", err) + fmt.Println("Code", resp.Code) + fmt.Println("Data", resp.Data) + // Output: + // Eval 'return 1 + 2' + // Error + // Code 0 + // Data [3] +} + +func ExampleConnect() { + conn, err := tarantool.Connect("127.0.0.1:3013", tarantool.Opts{ + Timeout: 500 * time.Millisecond, + User: "test", + Pass: "test", + Concurrency: 32, + }) + if err != nil { + fmt.Println("No connection available") + return + } + defer conn.Close() + if conn != nil { + fmt.Println("Connection is ready") + } + // Output: + // Connection is ready +} + +// Example demonstrates how to retrieve information with space schema. +func ExampleSchema() { + conn := example_connect() + defer conn.Close() + + schema := conn.Schema + if schema.SpacesById == nil { + fmt.Println("schema.SpacesById is nil") + } + if schema.Spaces == nil { + fmt.Println("schema.Spaces is nil") + } + + space1 := schema.Spaces["test"] + space2 := schema.SpacesById[514] + fmt.Printf("Space 1 ID %d %s\n", space1.Id, space1.Name) + fmt.Printf("Space 2 ID %d %s\n", space2.Id, space2.Name) + // Output: + // Space 1 ID 512 test + // Space 2 ID 514 schematest +} + +// Example demonstrates how to retrieve information with space schema. +func ExampleSpace() { + conn := example_connect() + defer conn.Close() + + // Save Schema to a local variable to avoid races + schema := conn.Schema + if schema.SpacesById == nil { + fmt.Println("schema.SpacesById is nil") + } + if schema.Spaces == nil { + fmt.Println("schema.Spaces is nil") + } + + // Access Space objects by name or ID. + space1 := schema.Spaces["test"] + space2 := schema.SpacesById[514] // It's a map. + fmt.Printf("Space 1 ID %d %s %s\n", space1.Id, space1.Name, space1.Engine) + fmt.Printf("Space 1 ID %d %t\n", space1.FieldsCount, space1.Temporary) + + // Access index information by name or ID. + index1 := space1.Indexes["primary"] + index2 := space2.IndexesById[3] // It's a map. + fmt.Printf("Index %d %s\n", index1.Id, index1.Name) + + // Access index fields information by index. + indexField1 := index1.Fields[0] // It's a slice. + indexField2 := index2.Fields[1] // It's a slice. + fmt.Println(indexField1, indexField2) + + // Access space fields information by name or id (index). + spaceField1 := space2.Fields["name0"] + spaceField2 := space2.FieldsById[3] + fmt.Printf("SpaceField 1 %s %s\n", spaceField1.Name, spaceField1.Type) + fmt.Printf("SpaceField 2 %s %s\n", spaceField2.Name, spaceField2.Type) + + // Output: + // Space 1 ID 512 test memtx + // Space 1 ID 0 false + // Index 0 primary + // &{0 unsigned} &{2 string} + // SpaceField 1 name0 unsigned + // SpaceField 2 name3 unsigned } diff --git a/multi/example_test.go b/multi/example_test.go new file mode 100644 index 000000000..e095504a2 --- /dev/null +++ b/multi/example_test.go @@ -0,0 +1,38 @@ +package multi + +import ( + "fmt" + "github.com/tarantool/go-tarantool" + "time" +) + +func ExampleConnect() { + multiConn, err := Connect([]string{"127.0.0.1:3031", "127.0.0.1:3032"}, tarantool.Opts{ + Timeout: 500 * time.Millisecond, + User: "test", + Pass: "test", + }) + if err != nil { + fmt.Printf("error in connect is %v", err) + } + fmt.Println(multiConn) +} + +func ExampleConnectWithOpts() { + multiConn, err := ConnectWithOpts([]string{"127.0.0.1:3301", "127.0.0.1:3302"}, tarantool.Opts{ + Timeout: 500 * time.Millisecond, + User: "test", + Pass: "test", + }, OptsMulti{ + // Check for connection timeout every 1 second. + CheckTimeout: 1 * time.Second, + // Lua function name for getting address list. + NodesGetFunctionName: "get_cluster_nodes", + // Ask server for updated address list every 3 seconds. + ClusterDiscoveryTime: 3 * time.Second, + }) + if err != nil { + fmt.Printf("error in connect is %v", err) + } + fmt.Println(multiConn) +} diff --git a/multi/multi.go b/multi/multi.go index 0bf2b03c9..c83010c36 100644 --- a/multi/multi.go +++ b/multi/multi.go @@ -1,3 +1,14 @@ +// Package with methods to work with a Tarantool cluster. +// +// Main features: +// +// - Check the active connection with a configurable time interval and switch +// to the next connection in the pool if there is a connection failure. +// +// - Get the address list from the server and reconfigure it for use in +// MultiConnection. +// +// Since: 1.5 package multi import ( @@ -29,6 +40,10 @@ func indexOf(sstring string, data []string) int { return -1 } +// ConnectionMulti is a handle with connections to a number of Tarantool instances. +// +// It is created and configured with Connect function, and could not be +// reconfigured later. type ConnectionMulti struct { addrs []string connOpts tarantool.Opts @@ -42,14 +57,21 @@ type ConnectionMulti struct { fallback *tarantool.Connection } -var _ = tarantool.Connector(&ConnectionMulti{}) // check compatibility with connector interface +var _ = tarantool.Connector(&ConnectionMulti{}) // Check compatibility with connector interface. +// OptsMulti is a way to configure Connection with multiconnect-specific options. type OptsMulti struct { - CheckTimeout time.Duration + // CheckTimeout is a time interval to check for connection timeout and try to + // switch connection. + CheckTimeout time.Duration + // Lua function name of the server called to retrieve the address list. NodesGetFunctionName string + // Time interval to ask the server for an updated address list (works + // if NodesGetFunctionName is set). ClusterDiscoveryTime time.Duration } +// Connect creates and configures new ConnectionMulti with multiconnection options. func ConnectWithOpts(addrs []string, connOpts tarantool.Opts, opts OptsMulti) (connMulti *ConnectionMulti, err error) { if len(addrs) == 0 { return nil, ErrEmptyAddrs @@ -61,7 +83,7 @@ func ConnectWithOpts(addrs []string, connOpts tarantool.Opts, opts OptsMulti) (c opts.ClusterDiscoveryTime = 60 * time.Second } - notify := make(chan tarantool.ConnEvent, 10*len(addrs)) // x10 to accept disconnected and closed event (with a margin) + notify := make(chan tarantool.ConnEvent, 10*len(addrs)) // x10 to accept disconnected and closed event (with a margin). connOpts.Notify = notify connMulti = &ConnectionMulti{ addrs: addrs, @@ -81,6 +103,7 @@ func ConnectWithOpts(addrs []string, connOpts tarantool.Opts, opts OptsMulti) (c return connMulti, nil } +// Connect creates and configures new ConnectionMulti. func Connect(addrs []string, connOpts tarantool.Opts) (connMulti *ConnectionMulti, err error) { opts := OptsMulti{ CheckTimeout: 1 * time.Second, @@ -169,7 +192,7 @@ func (connMulti *ConnectionMulti) checker() { } if len(resp) > 0 && len(resp[0]) > 0 { addrs := resp[0] - // Fill pool with new connections + // Fill pool with new connections. for _, v := range addrs { if indexOf(v, connMulti.addrs) < 0 { conn, _ := tarantool.Connect(v, connMulti.connOpts) @@ -178,7 +201,7 @@ func (connMulti *ConnectionMulti) checker() { } } } - // Clear pool from obsolete connections + // Clear pool from obsolete connections. for _, v := range connMulti.addrs { if indexOf(v, addrs) < 0 { con, ok := connMulti.getConnectionFromPool(v) @@ -225,10 +248,13 @@ func (connMulti *ConnectionMulti) getCurrentConnection() *tarantool.Connection { return connMulti.fallback } +// ConnectedNow reports if connection is established at the moment. func (connMulti *ConnectionMulti) ConnectedNow() bool { return connMulti.getState() == connConnected && connMulti.getCurrentConnection().ConnectedNow() } +// Close closes Connection. +// After this method called, there is no way to reopen this Connection. func (connMulti *ConnectionMulti) Close() (err error) { connMulti.mutex.Lock() defer connMulti.mutex.Unlock() @@ -250,118 +276,174 @@ func (connMulti *ConnectionMulti) Close() (err error) { return } +// Ping sends empty request to Tarantool to check connection. func (connMulti *ConnectionMulti) Ping() (resp *tarantool.Response, err error) { return connMulti.getCurrentConnection().Ping() } +// ConfiguredTimeout returns a timeout from connection config. func (connMulti *ConnectionMulti) ConfiguredTimeout() time.Duration { return connMulti.getCurrentConnection().ConfiguredTimeout() } +// Select performs select to box space. func (connMulti *ConnectionMulti) Select(space, index interface{}, offset, limit, iterator uint32, key interface{}) (resp *tarantool.Response, err error) { return connMulti.getCurrentConnection().Select(space, index, offset, limit, iterator, key) } +// Insert performs insertion to box space. +// Tarantool will reject Insert when tuple with same primary key exists. func (connMulti *ConnectionMulti) Insert(space interface{}, tuple interface{}) (resp *tarantool.Response, err error) { return connMulti.getCurrentConnection().Insert(space, tuple) } +// Replace performs "insert or replace" action to box space. +// If tuple with same primary key exists, it will be replaced. func (connMulti *ConnectionMulti) Replace(space interface{}, tuple interface{}) (resp *tarantool.Response, err error) { return connMulti.getCurrentConnection().Replace(space, tuple) } +// Delete performs deletion of a tuple by key. +// Result will contain array with deleted tuple. func (connMulti *ConnectionMulti) Delete(space, index interface{}, key interface{}) (resp *tarantool.Response, err error) { return connMulti.getCurrentConnection().Delete(space, index, key) } +// Update performs update of a tuple by key. +// Result will contain array with updated tuple. func (connMulti *ConnectionMulti) Update(space, index interface{}, key, ops interface{}) (resp *tarantool.Response, err error) { return connMulti.getCurrentConnection().Update(space, index, key, ops) } +// Upsert performs "update or insert" action of a tuple by key. +// Result will not contain any tuple. func (connMulti *ConnectionMulti) Upsert(space interface{}, tuple, ops interface{}) (resp *tarantool.Response, err error) { return connMulti.getCurrentConnection().Upsert(space, tuple, ops) } +// Call calls registered Tarantool function. +// It uses request code for Tarantool 1.6, so result is converted to array of +// arrays. func (connMulti *ConnectionMulti) Call(functionName string, args interface{}) (resp *tarantool.Response, err error) { return connMulti.getCurrentConnection().Call(functionName, args) } +// Call17 calls registered Tarantool function. +// It uses request code for Tarantool 1.7, so result is not converted +// (though, keep in mind, result is always array). func (connMulti *ConnectionMulti) Call17(functionName string, args interface{}) (resp *tarantool.Response, err error) { return connMulti.getCurrentConnection().Call17(functionName, args) } +// Eval passes Lua expression for evaluation. func (connMulti *ConnectionMulti) Eval(expr string, args interface{}) (resp *tarantool.Response, err error) { return connMulti.getCurrentConnection().Eval(expr, args) } +// GetTyped performs select (with limit = 1 and offset = 0) to box space and +// fills typed result. func (connMulti *ConnectionMulti) GetTyped(space, index interface{}, key interface{}, result interface{}) (err error) { return connMulti.getCurrentConnection().GetTyped(space, index, key, result) } +// SelectTyped performs select to box space and fills typed result. func (connMulti *ConnectionMulti) SelectTyped(space, index interface{}, offset, limit, iterator uint32, key interface{}, result interface{}) (err error) { return connMulti.getCurrentConnection().SelectTyped(space, index, offset, limit, iterator, key, result) } +// InsertTyped performs insertion to box space. +// Tarantool will reject Insert when tuple with same primary key exists. func (connMulti *ConnectionMulti) InsertTyped(space interface{}, tuple interface{}, result interface{}) (err error) { return connMulti.getCurrentConnection().InsertTyped(space, tuple, result) } +// ReplaceTyped performs "insert or replace" action to box space. +// If tuple with same primary key exists, it will be replaced. func (connMulti *ConnectionMulti) ReplaceTyped(space interface{}, tuple interface{}, result interface{}) (err error) { return connMulti.getCurrentConnection().ReplaceTyped(space, tuple, result) } +// DeleteTyped performs deletion of a tuple by key and fills result with +// deleted tuple. func (connMulti *ConnectionMulti) DeleteTyped(space, index interface{}, key interface{}, result interface{}) (err error) { return connMulti.getCurrentConnection().DeleteTyped(space, index, key, result) } +// UpdateTyped performs update of a tuple by key and fills result with updated +// tuple. func (connMulti *ConnectionMulti) UpdateTyped(space, index interface{}, key, ops interface{}, result interface{}) (err error) { return connMulti.getCurrentConnection().UpdateTyped(space, index, key, ops, result) } +// CallTyped calls registered function. +// It uses request code for Tarantool 1.6, so result is converted to array of +// arrays. func (connMulti *ConnectionMulti) CallTyped(functionName string, args interface{}, result interface{}) (err error) { return connMulti.getCurrentConnection().CallTyped(functionName, args, result) } +// Call17Typed calls registered function. +// It uses request code for Tarantool 1.7, so result is not converted (though, +// keep in mind, result is always array) func (connMulti *ConnectionMulti) Call17Typed(functionName string, args interface{}, result interface{}) (err error) { return connMulti.getCurrentConnection().Call17Typed(functionName, args, result) } +// EvalTyped passes Lua expression for evaluation. func (connMulti *ConnectionMulti) EvalTyped(expr string, args interface{}, result interface{}) (err error) { return connMulti.getCurrentConnection().EvalTyped(expr, args, result) } +// SelectAsync sends select request to Tarantool and returns Future. func (connMulti *ConnectionMulti) SelectAsync(space, index interface{}, offset, limit, iterator uint32, key interface{}) *tarantool.Future { return connMulti.getCurrentConnection().SelectAsync(space, index, offset, limit, iterator, key) } +// InsertAsync sends insert action to Tarantool and returns Future. +// Tarantool will reject Insert when tuple with same primary key exists. func (connMulti *ConnectionMulti) InsertAsync(space interface{}, tuple interface{}) *tarantool.Future { return connMulti.getCurrentConnection().InsertAsync(space, tuple) } +// ReplaceAsync sends "insert or replace" action to Tarantool and returns Future. +// If tuple with same primary key exists, it will be replaced. func (connMulti *ConnectionMulti) ReplaceAsync(space interface{}, tuple interface{}) *tarantool.Future { return connMulti.getCurrentConnection().ReplaceAsync(space, tuple) } +// DeleteAsync sends deletion action to Tarantool and returns Future. +// Future's result will contain array with deleted tuple. func (connMulti *ConnectionMulti) DeleteAsync(space, index interface{}, key interface{}) *tarantool.Future { return connMulti.getCurrentConnection().DeleteAsync(space, index, key) } +// Update sends deletion of a tuple by key and returns Future. +// Future's result will contain array with updated tuple. func (connMulti *ConnectionMulti) UpdateAsync(space, index interface{}, key, ops interface{}) *tarantool.Future { return connMulti.getCurrentConnection().UpdateAsync(space, index, key, ops) } +// UpsertAsync sends "update or insert" action to Tarantool and returns Future. +// Future's sesult will not contain any tuple. func (connMulti *ConnectionMulti) UpsertAsync(space interface{}, tuple interface{}, ops interface{}) *tarantool.Future { return connMulti.getCurrentConnection().UpsertAsync(space, tuple, ops) } +// CallAsync sends a call to registered Tarantool function and returns Future. +// It uses request code for Tarantool 1.6, so future's result is always array +// of arrays. func (connMulti *ConnectionMulti) CallAsync(functionName string, args interface{}) *tarantool.Future { return connMulti.getCurrentConnection().CallAsync(functionName, args) } +// Call17Async sends a call to registered Tarantool function and returns Future. +// It uses request code for Tarantool 1.7, so future's result will not be converted +// (though, keep in mind, result is always array). func (connMulti *ConnectionMulti) Call17Async(functionName string, args interface{}) *tarantool.Future { return connMulti.getCurrentConnection().Call17Async(functionName, args) } +// EvalAsync passes Lua expression for evaluation. func (connMulti *ConnectionMulti) EvalAsync(expr string, args interface{}) *tarantool.Future { return connMulti.getCurrentConnection().EvalAsync(expr, args) } diff --git a/multi/multi_test.go b/multi/multi_test.go index 65ae439f8..aae659101 100644 --- a/multi/multi_test.go +++ b/multi/multi_test.go @@ -188,8 +188,8 @@ func TestRefresh(t *testing.T) { } curAddr := multiConn.addrs[0] - // wait for refresh timer - // scenario 1 nodeload, 1 refresh, 1 nodeload + // Wait for refresh timer. + // Scenario 1 nodeload, 1 refresh, 1 nodeload. time.Sleep(10 * time.Second) newAddr := multiConn.addrs[0] diff --git a/queue/example_msgpack_test.go b/queue/example_msgpack_test.go new file mode 100644 index 000000000..ecfb60a94 --- /dev/null +++ b/queue/example_msgpack_test.go @@ -0,0 +1,133 @@ +// Setup queue module and start Tarantool instance before execution: +// Terminal 1: +// $ make deps +// $ TEST_TNT_LISTEN=3013 tarantool queue/config.lua +// +// Terminal 2: +// $ cd queue +// $ go test -v example_msgpack_test.go +package queue_test + +import ( + "fmt" + "time" + + "github.com/tarantool/go-tarantool" + "github.com/tarantool/go-tarantool/queue" + "gopkg.in/vmihailenco/msgpack.v2" + "log" +) + +type dummyData struct { + Dummy bool +} + +func (c *dummyData) DecodeMsgpack(d *msgpack.Decoder) error { + var err error + if c.Dummy, err = d.DecodeBool(); err != nil { + return err + } + return nil +} + +func (c *dummyData) EncodeMsgpack(e *msgpack.Encoder) error { + return e.EncodeBool(c.Dummy) +} + +// Example demonstrates an operations like Put and Take with queue and custom +// MsgPack structure. +// +// Features of the implementation: +// +// - If you use the connection timeout and call TakeWithTimeout with a +// parameter greater than the connection timeout, the parameter is reduced to +// it. +// +// - If you use the connection timeout and call Take, we return an error if we +// cannot take the task out of the queue within the time corresponding to the +// connection timeout. +func Example_simpleQueueCustomMsgPack() { + opts := tarantool.Opts{ + Reconnect: time.Second, + Timeout: 2500 * time.Millisecond, + MaxReconnects: 5, + User: "test", + Pass: "test", + } + conn, err := tarantool.Connect("127.0.0.1:3013", opts) + if err != nil { + log.Fatalf("connection: %s", err) + return + } + defer conn.Close() + + cfg := queue.Cfg{ + Temporary: true, + IfNotExists: true, + Kind: queue.FIFO, + Opts: queue.Opts{ + Ttl: 10 * time.Second, + Ttr: 5 * time.Second, + Delay: 3 * time.Second, + Pri: 1, + }, + } + + que := queue.New(conn, "test_queue_msgpack") + if err = que.Create(cfg); err != nil { + log.Fatalf("queue create: %s", err) + return + } + + // Put data. + task, err := que.Put("test_data") + if err != nil { + log.Fatalf("put task: %s", err) + } + fmt.Println("Task id is", task.Id()) + + // Take data. + task, err = que.Take() // Blocking operation. + if err != nil { + log.Fatalf("take task: %s", err) + } + fmt.Println("Data is", task.Data()) + task.Ack() + + // Take typed example. + putData := dummyData{} + // Put data. + task, err = que.Put(&putData) + if err != nil { + log.Fatalf("put typed task: %s", err) + } + fmt.Println("Task id is ", task.Id()) + + takeData := dummyData{} + // Take data. + task, err = que.TakeTyped(&takeData) // Blocking operation. + if err != nil { + log.Fatalf("take take typed: %s", err) + } + fmt.Println("Data is ", takeData) + // Same data. + fmt.Println("Data is ", task.Data()) + + task, err = que.Put([]int{1, 2, 3}) + task.Bury() + + task, err = que.TakeTimeout(2 * time.Second) + if task == nil { + fmt.Println("Task is nil") + } + + que.Drop() + + // Unordered output: + // Task id is 0 + // Data is test_data + // Task id is 0 + // Data is {false} + // Data is &{false} + // Task is nil +} diff --git a/queue/example_test.go b/queue/example_test.go index cf3150260..cb0f62f1a 100644 --- a/queue/example_test.go +++ b/queue/example_test.go @@ -1,3 +1,11 @@ +// Setup queue module and start Tarantool instance before execution: +// Terminal 1: +// $ make deps +// $ TEST_TNT_LISTEN=3013 tarantool queue/config.lua +// +// Terminal 2: +// $ cd queue +// $ go test -v example_test.go package queue_test import ( @@ -8,7 +16,8 @@ import ( "github.com/tarantool/go-tarantool/queue" ) -func ExampleConnection_Queue() { +// Example demonstrates an operations like Put and Take with queue. +func Example_simpleQueue() { cfg := queue.Cfg{ Temporary: false, Kind: queue.FIFO, @@ -16,8 +25,13 @@ func ExampleConnection_Queue() { Ttl: 10 * time.Second, }, } + opts := tarantool.Opts{ + Timeout: 2500 * time.Millisecond, + User: "test", + Pass: "test", + } - conn, err := tarantool.Connect(server, opts) + conn, err := tarantool.Connect("127.0.0.1:3013", opts) if err != nil { fmt.Printf("error in prepare is %v", err) return @@ -64,4 +78,6 @@ func ExampleConnection_Queue() { fmt.Printf("Task should be nil, but %d", task.Id()) return } + + // Output: data_1: test_data_1 } diff --git a/queue/queue.go b/queue/queue.go index 69dcf8bcd..ce0eeb49f 100644 --- a/queue/queue.go +++ b/queue/queue.go @@ -1,3 +1,11 @@ +// Package with implementation of methods for work with a Tarantool's queue +// implementations. +// +// Since: 1.5. +// +// See also +// +// * Tarantool queue module https://github.com/tarantool/queue package queue import ( @@ -8,12 +16,12 @@ import ( msgpack "gopkg.in/vmihailenco/msgpack.v2" ) -// Queue is a handle to tarantool queue's tube +// Queue is a handle to Tarantool queue's tube. type Queue interface { - // Exists checks tube for existence - // Note: it uses Eval, so user needs 'execute universe' privilege + // Exists checks tube for existence. + // Note: it uses Eval, so user needs 'execute universe' privilege. Exists() (bool, error) - // Create creates new tube with configuration + // Create creates new tube with configuration. // Note: it uses Eval, so user needs 'execute universe' privilege // Note: you'd better not use this function in your application, cause it is // administrative task to create or delete queue. @@ -22,33 +30,38 @@ type Queue interface { // Note: you'd better not use this function in your application, cause it is // administrative task to create or delete queue. Drop() error - // Put creates new task in a tube + // Put creates new task in a tube. Put(data interface{}) (*Task, error) - // PutWithOpts creates new task with options different from tube's defaults + // PutWithOpts creates new task with options different from tube's defaults. PutWithOpts(data interface{}, cfg Opts) (*Task, error) - // Take takes 'ready' task from a tube and marks it as 'in progress' + // Take takes 'ready' task from a tube and marks it as 'in progress'. // Note: if connection has a request Timeout, then 0.9 * connection.Timeout is // used as a timeout. + // If you use a connection timeout and we can not take task from queue in + // a time equal to the connection timeout after calling `Take` then we + // return an error. Take() (*Task, error) - // TakeWithTimout takes 'ready' task from a tube and marks it as "in progress", + // TakeTimeout takes 'ready' task from a tube and marks it as "in progress", // or it is timeouted after "timeout" period. // Note: if connection has a request Timeout, and conn.Timeout * 0.9 < timeout - // then timeout = conn.Timeout*0.9 + // then timeout = conn.Timeout*0.9. + // If you use connection timeout and call `TakeTimeout` with parameter + // greater than the connection timeout then parameter reduced to it. TakeTimeout(timeout time.Duration) (*Task, error) - // Take takes 'ready' task from a tube and marks it as 'in progress' + // TakeTyped takes 'ready' task from a tube and marks it as 'in progress' // Note: if connection has a request Timeout, then 0.9 * connection.Timeout is // used as a timeout. - // Data will be unpacked to result + // Data will be unpacked to result. TakeTyped(interface{}) (*Task, error) - // TakeWithTimout takes 'ready' task from a tube and marks it as "in progress", + // TakeTypedTimeout takes 'ready' task from a tube and marks it as "in progress", // or it is timeouted after "timeout" period. // Note: if connection has a request Timeout, and conn.Timeout * 0.9 < timeout - // then timeout = conn.Timeout*0.9 - // data will be unpacked to result + // then timeout = conn.Timeout*0.9. + // Data will be unpacked to result. TakeTypedTimeout(timeout time.Duration, result interface{}) (*Task, error) // Peek returns task by its id. Peek(taskId uint64) (*Task, error) - // Kick reverts effect of Task.Bury() for `count` tasks. + // Kick reverts effect of Task.Bury() for count tasks. Kick(count uint64) (uint64, error) // Delete the task identified by its id. Delete(taskId uint64) error @@ -76,8 +89,8 @@ type cmd struct { } type Cfg struct { - Temporary bool // if true, the contents do not persist on disk - IfNotExists bool // if true, no error will be returned if the tube already exists + Temporary bool // If true, the contents do not persist on disk. + IfNotExists bool // If true, no error will be returned if the tube already exists. Kind queueType Opts } @@ -99,10 +112,10 @@ func (cfg Cfg) getType() string { } type Opts struct { - Pri int // task priorities - Ttl time.Duration // task time to live - Ttr time.Duration // task time to execute - Delay time.Duration // delayed execution + Pri int // Task priorities. + Ttl time.Duration // Task time to live. + Ttr time.Duration // Task time to execute. + Delay time.Duration // Delayed execution. Utube string } @@ -132,7 +145,7 @@ func (opts Opts) toMap() map[string]interface{} { return ret } -// New creates a queue handle +// New creates a queue handle. func New(conn tarantool.Connector, name string) Queue { q := &queue{ name: name, @@ -142,14 +155,14 @@ func New(conn tarantool.Connector, name string) Queue { return q } -// Create creates a new queue with config +// Create creates a new queue with config. func (q *queue) Create(cfg Cfg) error { cmd := "local name, type, cfg = ... ; queue.create_tube(name, type, cfg)" _, err := q.conn.Eval(cmd, []interface{}{q.name, cfg.getType(), cfg.toMap()}) return err } -// Exists checks existance of a tube +// Exists checks existance of a tube. func (q *queue) Exists() (bool, error) { cmd := "local name = ... ; return queue.tube[name] ~= nil" resp, err := q.conn.Eval(cmd, []string{q.name}) @@ -192,7 +205,8 @@ func (q *queue) Take() (*Task, error) { return q.take(params) } -// The take request searches for a task in the queue. Waits until a task becomes ready or the timeout expires. +// The take request searches for a task in the queue. Waits until a task +// becomes ready or the timeout expires. func (q *queue) TakeTimeout(timeout time.Duration) (*Task, error) { t := q.conn.ConfiguredTimeout() * 9 / 10 if t > 0 && timeout > t { @@ -211,7 +225,8 @@ func (q *queue) TakeTyped(result interface{}) (*Task, error) { return q.take(params, result) } -// The take request searches for a task in the queue. Waits until a task becomes ready or the timeout expires. +// The take request searches for a task in the queue. Waits until a task +// becomes ready or the timeout expires. func (q *queue) TakeTypedTimeout(timeout time.Duration, result interface{}) (*Task, error) { t := q.conn.ConfiguredTimeout() * 9 / 10 if t > 0 && timeout > t { @@ -285,7 +300,8 @@ func (q *queue) Delete(taskId uint64) error { return err } -// 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. +// 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) { resp, err := q.conn.Call(q.cmds.statistics, []interface{}{q.name}) if err != nil { diff --git a/queue/task.go b/queue/task.go index 6d8560bd7..fc16ffb88 100644 --- a/queue/task.go +++ b/queue/task.go @@ -6,7 +6,7 @@ import ( msgpack "gopkg.in/vmihailenco/msgpack.v2" ) -// Task represents a task from tarantool queue's tube +// Task represents a task from Tarantool queue's tube. type Task struct { id uint64 status string @@ -41,27 +41,27 @@ func (t *Task) DecodeMsgpack(d *msgpack.Decoder) error { return nil } -// Id is a getter for task id +// Id is a getter for task id. func (t *Task) Id() uint64 { return t.id } -// Data is a getter for task data +// Data is a getter for task data. func (t *Task) Data() interface{} { return t.data } -// Status is a getter for task status +// Status is a getter for task status. func (t *Task) Status() string { return t.status } -// Ack signals about task completion +// Ack signals about task completion. func (t *Task) Ack() error { return t.accept(t.q._ack(t.id)) } -// Delete task from queue +// Delete task from queue. func (t *Task) Delete() error { return t.accept(t.q._delete(t.id)) } @@ -93,27 +93,27 @@ func (t *Task) accept(newStatus string, err error) error { return err } -// IsReady returns if task is ready +// IsReady returns if task is ready. func (t *Task) IsReady() bool { return t.status == READY } -// IsTaken returns if task is taken +// IsTaken returns if task is taken. func (t *Task) IsTaken() bool { return t.status == TAKEN } -// IsDone returns if task is done +// IsDone returns if task is done. func (t *Task) IsDone() bool { return t.status == DONE } -// IsBurred returns if task is buried +// IsBurred returns if task is buried. func (t *Task) IsBuried() bool { return t.status == BURIED } -// IsDelayed returns if task is delayed +// IsDelayed returns if task is delayed. func (t *Task) IsDelayed() bool { return t.status == DELAYED } diff --git a/request.go b/request.go index ae42eb440..ff8a7e8a5 100644 --- a/request.go +++ b/request.go @@ -7,7 +7,7 @@ import ( "gopkg.in/vmihailenco/msgpack.v2" ) -// Future is a handle for asynchronous request +// Future is a handle for asynchronous request. type Future struct { requestId uint32 requestCode int32 @@ -51,7 +51,7 @@ func (req *Future) fillInsert(enc *msgpack.Encoder, spaceNo uint32, tuple interf // Select performs select to box space. // -// It is equal to conn.SelectAsync(...).Get() +// It is equal to conn.SelectAsync(...).Get(). func (conn *Connection) Select(space, index interface{}, offset, limit, iterator uint32, key interface{}) (resp *Response, err error) { return conn.SelectAsync(space, index, offset, limit, iterator, key).Get() } @@ -96,16 +96,16 @@ func (conn *Connection) Upsert(space interface{}, tuple, ops interface{}) (resp return conn.UpsertAsync(space, tuple, ops).Get() } -// Call calls registered tarantool function. -// It uses request code for tarantool 1.6, so result is converted to array of arrays +// Call calls registered Tarantool function. +// It uses request code for Tarantool 1.6, so result is converted to array of arrays // // It is equal to conn.CallAsync(functionName, args).Get(). func (conn *Connection) Call(functionName string, args interface{}) (resp *Response, err error) { return conn.CallAsync(functionName, args).Get() } -// Call17 calls registered tarantool function. -// It uses request code for tarantool 1.7, so result is not converted +// Call17 calls registered Tarantool function. +// It uses request code for Tarantool 1.7, so result is not converted // (though, keep in mind, result is always array) // // It is equal to conn.Call17Async(functionName, args).Get(). @@ -113,14 +113,14 @@ func (conn *Connection) Call17(functionName string, args interface{}) (resp *Res return conn.Call17Async(functionName, args).Get() } -// Eval passes lua expression for evaluation. +// Eval passes Lua expression for evaluation. // // It is equal to conn.EvalAsync(space, tuple).Get(). func (conn *Connection) Eval(expr string, args interface{}) (resp *Response, err error) { return conn.EvalAsync(expr, args).Get() } -// single used for conn.GetTyped for decode one tuple +// single used for conn.GetTyped for decode one tuple. type single struct { res interface{} found bool @@ -189,7 +189,7 @@ func (conn *Connection) UpdateTyped(space, index interface{}, key, ops interface } // CallTyped calls registered function. -// It uses request code for tarantool 1.6, so result is converted to array of arrays +// It uses request code for Tarantool 1.6, so result is converted to array of arrays // // It is equal to conn.CallAsync(functionName, args).GetTyped(&result). func (conn *Connection) CallTyped(functionName string, args interface{}, result interface{}) (err error) { @@ -197,7 +197,7 @@ func (conn *Connection) CallTyped(functionName string, args interface{}, result } // Call17Typed calls registered function. -// It uses request code for tarantool 1.7, so result is not converted +// It uses request code for Tarantool 1.7, so result is not converted // (though, keep in mind, result is always array) // // It is equal to conn.Call17Async(functionName, args).GetTyped(&result). @@ -205,14 +205,14 @@ func (conn *Connection) Call17Typed(functionName string, args interface{}, resul return conn.Call17Async(functionName, args).GetTyped(result) } -// EvalTyped passes lua expression for evaluation. +// EvalTyped passes Lua expression for evaluation. // // It is equal to conn.EvalAsync(space, tuple).GetTyped(&result). func (conn *Connection) EvalTyped(expr string, args interface{}, result interface{}) (err error) { return conn.EvalAsync(expr, args).GetTyped(result) } -// SelectAsync sends select request to tarantool and returns Future. +// SelectAsync sends select request to Tarantool and returns Future. func (conn *Connection) SelectAsync(space, index interface{}, offset, limit, iterator uint32, key interface{}) *Future { future := conn.newFuture(SelectRequest) spaceNo, indexNo, err := conn.Schema.resolveSpaceIndex(space, index) @@ -226,7 +226,7 @@ func (conn *Connection) SelectAsync(space, index interface{}, offset, limit, ite }) } -// InsertAsync sends insert action to tarantool and returns Future. +// InsertAsync sends insert action to Tarantool and returns Future. // Tarantool will reject Insert when tuple with same primary key exists. func (conn *Connection) InsertAsync(space interface{}, tuple interface{}) *Future { future := conn.newFuture(InsertRequest) @@ -240,7 +240,7 @@ func (conn *Connection) InsertAsync(space interface{}, tuple interface{}) *Futur }) } -// ReplaceAsync sends "insert or replace" action to tarantool and returns Future. +// ReplaceAsync sends "insert or replace" action to Tarantool and returns Future. // If tuple with same primary key exists, it will be replaced. func (conn *Connection) ReplaceAsync(space interface{}, tuple interface{}) *Future { future := conn.newFuture(ReplaceRequest) @@ -254,7 +254,7 @@ func (conn *Connection) ReplaceAsync(space interface{}, tuple interface{}) *Futu }) } -// DeleteAsync sends deletion action to tarantool and returns Future. +// DeleteAsync sends deletion action to Tarantool and returns Future. // Future's result will contain array with deleted tuple. func (conn *Connection) DeleteAsync(space, index interface{}, key interface{}) *Future { future := conn.newFuture(DeleteRequest) @@ -286,7 +286,7 @@ func (conn *Connection) UpdateAsync(space, index interface{}, key, ops interface }) } -// UpsertAsync sends "update or insert" action to tarantool and returns Future. +// UpsertAsync sends "update or insert" action to Tarantool and returns Future. // Future's sesult will not contain any tuple. func (conn *Connection) UpsertAsync(space interface{}, tuple interface{}, ops interface{}) *Future { future := conn.newFuture(UpsertRequest) @@ -307,8 +307,8 @@ func (conn *Connection) UpsertAsync(space interface{}, tuple interface{}, ops in }) } -// CallAsync sends a call to registered tarantool function and returns Future. -// It uses request code for tarantool 1.6, so future's result is always array of arrays +// CallAsync sends a call to registered Tarantool function and returns Future. +// It uses request code for Tarantool 1.6, so future's result is always array of arrays func (conn *Connection) CallAsync(functionName string, args interface{}) *Future { future := conn.newFuture(CallRequest) return future.send(conn, func(enc *msgpack.Encoder) error { @@ -320,8 +320,8 @@ func (conn *Connection) CallAsync(functionName string, args interface{}) *Future }) } -// Call17Async sends a call to registered tarantool function and returns Future. -// It uses request code for tarantool 1.7, so future's result will not be converted +// Call17Async sends a call to registered Tarantool function and returns Future. +// It uses request code for Tarantool 1.7, so future's result will not be converted // (though, keep in mind, result is always array) func (conn *Connection) Call17Async(functionName string, args interface{}) *Future { future := conn.newFuture(Call17Request) @@ -334,7 +334,7 @@ func (conn *Connection) Call17Async(functionName string, args interface{}) *Futu }) } -// EvalAsync sends a lua expression for evaluation and returns Future. +// EvalAsync sends a Lua expression for evaluation and returns Future. func (conn *Connection) EvalAsync(expr string, args interface{}) *Future { future := conn.newFuture(EvalRequest) return future.send(conn, func(enc *msgpack.Encoder) error { @@ -405,7 +405,7 @@ func (fut *Future) wait() { <-fut.ready } -// Get waits for Future to be filled and returns Response and error +// 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 performace, use GetTyped method. @@ -442,7 +442,7 @@ func init() { close(closedChan) } -// WaitChan returns channel which becomes closed when response arrived or error occured +// WaitChan returns channel which becomes closed when response arrived or error occured. func (fut *Future) WaitChan() <-chan struct{} { if fut.ready == nil { return closedChan diff --git a/response.go b/response.go index 6363a4fe4..2e0783659 100644 --- a/response.go +++ b/response.go @@ -9,8 +9,8 @@ import ( type Response struct { RequestId uint32 Code uint32 - Error string // error message - // Data contains deserialized data for untyped requests + Error string // Error message. + // Data contains deserialized data for untyped requests. Data []interface{} buf smallBuf } @@ -139,7 +139,7 @@ func (resp *Response) decodeBodyTyped(res interface{}) (err error) { return } -// String implements Stringer interface +// String implements Stringer interface. func (resp *Response) String() (str string) { if resp.Code == OkCode { return fmt.Sprintf("<%d OK %v>", resp.RequestId, resp.Data) @@ -148,7 +148,7 @@ func (resp *Response) String() (str string) { } // Tuples converts result of Eval and Call17 to same format -// as other actions returns (ie array of arrays). +// 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 { diff --git a/schema.go b/schema.go index 9b2dfae75..91c0faedd 100644 --- a/schema.go +++ b/schema.go @@ -7,25 +7,26 @@ import ( // Schema contains information about spaces and indexes. type Schema struct { Version uint - // Spaces is map from space names to spaces + // Spaces is map from space names to spaces. Spaces map[string]*Space - // SpacesById is map from space numbers to spaces + // SpacesById is map from space numbers to spaces. SpacesById map[uint32]*Space } -// Space contains information about tarantool space +// Space contains information about Tarantool's space. type Space struct { - Id uint32 - Name string + Id uint32 + Name string + // Could be "memtx" or "vinyl". Engine string - Temporary bool // Is this space temporaray? - // Field configuration is not mandatory and not checked by tarantool. + Temporary bool // Is this space temporary? + // Field configuration is not mandatory and not checked by Tarantool. FieldsCount uint32 Fields map[string]*Field FieldsById map[uint32]*Field - // Indexes is map from index names to indexes + // Indexes is map from index names to indexes. Indexes map[string]*Index - // IndexesById is map from index numbers to indexes + // IndexesById is map from index numbers to indexes. IndexesById map[uint32]*Index } @@ -35,7 +36,7 @@ type Field struct { Type string } -// Index contains information about index +// Index contains information about index. type Index struct { Id uint32 Name string @@ -64,7 +65,7 @@ func (conn *Connection) loadSchema() (err error) { schema.SpacesById = make(map[uint32]*Space) schema.Spaces = make(map[string]*Space) - // reload spaces + // Reload spaces. resp, err = conn.Select(vspaceSpId, 0, 0, maxSchemas, IterAll, []interface{}{}) if err != nil { return err @@ -117,7 +118,7 @@ func (conn *Connection) loadSchema() (err error) { schema.Spaces[space.Name] = space } - // reload indexes + // Reload indexes. resp, err = conn.Select(vindexSpId, 0, 0, maxSchemas, IterAll, []interface{}{}) if err != nil { return err @@ -135,7 +136,7 @@ func (conn *Connection) loadSchema() (err error) { opts := row[4].(map[interface{}]interface{}) var ok bool if index.Unique, ok = opts["unique"].(bool); !ok { - /* see bug https://github.com/tarantool/tarantool/issues/2060 */ + /* See bug https://github.com/tarantool/tarantool/issues/2060. */ index.Unique = true } default: diff --git a/tarantool_test.go b/tarantool_test.go index 41bdfe830..5f5078a8b 100644 --- a/tarantool_test.go +++ b/tarantool_test.go @@ -20,67 +20,34 @@ type Member struct { Val uint } -type Tuple2 struct { - Cid uint - Orig string - Members []Member -} - func (m *Member) EncodeMsgpack(e *msgpack.Encoder) error { - e.EncodeSliceLen(2) - e.EncodeString(m.Name) - e.EncodeUint(m.Val) - return nil -} - -func (m *Member) DecodeMsgpack(d *msgpack.Decoder) error { - var err error - var l int - if l, err = d.DecodeSliceLen(); err != nil { + if err := e.EncodeSliceLen(2); err != nil { return err } - if l != 2 { - return fmt.Errorf("array len doesn't match: %d", l) - } - if m.Name, err = d.DecodeString(); err != nil { + if err := e.EncodeString(m.Name); err != nil { return err } - if m.Val, err = d.DecodeUint(); err != nil { + if err := e.EncodeUint(m.Val); err != nil { return err } return nil } -func (c *Tuple2) EncodeMsgpack(e *msgpack.Encoder) error { - e.EncodeSliceLen(3) - e.EncodeUint(c.Cid) - e.EncodeString(c.Orig) - e.Encode(c.Members) - return nil -} - -func (c *Tuple2) DecodeMsgpack(d *msgpack.Decoder) error { +func (m *Member) DecodeMsgpack(d *msgpack.Decoder) error { var err error var l int if l, err = d.DecodeSliceLen(); err != nil { return err } - if l != 3 { + if l != 2 { return fmt.Errorf("array len doesn't match: %d", l) } - if c.Cid, err = d.DecodeUint(); err != nil { - return err - } - if c.Orig, err = d.DecodeString(); err != nil { + if m.Name, err = d.DecodeString(); err != nil { return err } - if l, err = d.DecodeSliceLen(); err != nil { + if m.Val, err = d.DecodeUint(); err != nil { return err } - c.Members = make([]Member, l) - for i := 0; i < l; i++ { - d.Decode(&c.Members[i]) - } return nil } diff --git a/test_helpers/main.go b/test_helpers/main.go index e5c73bfe5..ad45e00d9 100644 --- a/test_helpers/main.go +++ b/test_helpers/main.go @@ -1,3 +1,13 @@ +// Helpers for managing Tarantool process for testing purposes. +// +// Package introduces go helpers for starting a tarantool process and +// validating Tarantool version. Helpers are based on os/exec calls. +// Retries to connect test tarantool instance handled explicitly, +// see tarantool/go-tarantool#136. +// +// Tarantool's instance Lua scripts use environment variables to configure +// box.cfg. Listen port is set in the end of script so it is possible to +// connect only if every other thing was set up already. package test_helpers import ( diff --git a/uuid/example_test.go b/uuid/example_test.go new file mode 100644 index 000000000..ef0993e59 --- /dev/null +++ b/uuid/example_test.go @@ -0,0 +1,46 @@ +// Run Tarantool instance before example execution: +// Terminal 1: +// $ cd uuid +// $ TEST_TNT_LISTEN=3013 TEST_TNT_WORK_DIR=$(mktemp -d -t 'tarantool.XXX') tarantool config.lua +// +// Terminal 2: +// $ cd uuid +// $ go test -v example_test.go +package uuid_test + +import ( + "fmt" + "log" + + "github.com/google/uuid" + "github.com/tarantool/go-tarantool" + _ "github.com/tarantool/go-tarantool/uuid" +) + +// Example demonstrates how to use tuples with UUID. To enable UUID support +// in msgpack with google/uuid (https://github.com/google/uuid), import +// tarantool/uuid submodule. +func Example() { + opts := tarantool.Opts{ + User: "test", + Pass: "test", + } + client, err := tarantool.Connect("127.0.0.1:3013", opts) + if err != nil { + log.Fatalf("Failed to connect: %s", err.Error()) + } + + spaceNo := uint32(524) + + id, uuidErr := uuid.Parse("c8f0fa1f-da29-438c-a040-393f1126ad39") + if uuidErr != nil { + log.Fatalf("Failed to prepare uuid: %s", uuidErr) + } + + resp, err := client.Replace(spaceNo, []interface{}{id}) + + fmt.Println("UUID tuple replace") + fmt.Println("Error", err) + fmt.Println("Code", resp.Code) + fmt.Println("Data", resp.Data) +} diff --git a/uuid/uuid.go b/uuid/uuid.go index 11d3153e1..7d362c3d8 100644 --- a/uuid/uuid.go +++ b/uuid/uuid.go @@ -1,3 +1,16 @@ +// Package with support of Tarantool's UUID data type. +// +// UUID data type supported in Tarantool since 2.4.1. +// +// Since: 1.6.0. +// +// See also +// +// * Tarantool commit with UUID support https://github.com/tarantool/tarantool/commit/d68fc29246714eee505bc9bbcd84a02de17972c5 +// +// * Tarantool data model https://www.tarantool.io/en/doc/latest/book/box/data_model/ +// +// * Module UUID https://www.tarantool.io/en/doc/latest/reference/reference_lua/uuid/ package uuid import ( @@ -8,10 +21,7 @@ import ( "gopkg.in/vmihailenco/msgpack.v2" ) -// UUID external type -// Supported since Tarantool 2.4.1. See more in commit messages. -// https://github.com/tarantool/tarantool/commit/d68fc29246714eee505bc9bbcd84a02de17972c5 - +// UUID external type. const UUID_extId = 2 func encodeUUID(e *msgpack.Encoder, v reflect.Value) error {