From e96f793c5abd5c11238f3cad752d4a8266c833f8 Mon Sep 17 00:00:00 2001 From: DerekBum <alextruewestern@gmail.com> Date: Wed, 1 Nov 2023 20:26:03 +0300 Subject: [PATCH] api: support `IPROTO_FEATURE_SPACE_AND_INDEX_NAMES` Support `IPROTO_FEATURE_SPACE_AND_INDEX_NAMES` for Tarantool version >= 3.0.0-alpha. It allows to use space and index names in requests instead of their IDs. `ResolveSpaceIndex` function for `SchemaResolver` interface split into two: `ResolveSpace` and `ResolveIndex`. `NamesUseSupported` function added into the interface to get information if usage of space and index names is supported. Closes #338 --- CHANGELOG.md | 3 + README.md | 7 + connection.go | 5 +- crud/request_test.go | 20 +- example_test.go | 88 +++++ export_test.go | 67 +++- protocol.go | 1 + request.go | 158 +++++++-- request_test.go | 722 ++++++++++++++++++++++++++++++--------- schema.go | 152 ++++++--- schema_test.go | 162 +++++++++ settings/request_test.go | 26 +- tarantool_test.go | 59 +++- test_helpers/utils.go | 8 + 14 files changed, 1187 insertions(+), 291 deletions(-) create mode 100644 schema_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 63ed00b39..7812e9fab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,9 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release. - Support `crud.schema` request (#336) - Support `IPROTO_WATCH_ONCE` request type for Tarantool version >= 3.0.0-alpha1 (#337) +- Support `IPROTO_FEATURE_SPACE_AND_INDEX_NAMES` for Tarantool + version >= 3.0.0-alpha1 (#338). It allows to use space and index names + in requests instead of their IDs ### Changed diff --git a/README.md b/README.md index 2c7de68c4..5af96053c 100644 --- a/README.md +++ b/README.md @@ -239,6 +239,13 @@ and user may cancel it in process. * `iproto.Feature` type used instead of `ProtocolFeature`. * `iproto.IPROTO_FEATURE_` constants used instead of local ones. +#### Schema changes + +`ResolveSpaceIndex` function for `SchemaResolver` interface split into two: +`ResolveSpace` and `ResolveIndex`. `NamesUseSupported` function added into the +interface to get information if the usage of space and index names in requests +is supported. + ## Contributing See [the contributing guide](CONTRIBUTING.md) for detailed instructions on how diff --git a/connection.go b/connection.go index 018df6caa..1f27462f1 100644 --- a/connection.go +++ b/connection.go @@ -162,6 +162,8 @@ type Connection struct { cond *sync.Cond // Schema contains schema loaded on connection. Schema *Schema + // schemaResolver contains a SchemaResolver implementation. + schemaResolver SchemaResolver // requestId contains the last request ID for requests with nil context. requestId uint32 // contextRequestId contains the last request ID for requests with context. @@ -384,6 +386,7 @@ func Connect(ctx context.Context, addr string, opts Opts) (conn *Connection, err control: make(chan struct{}), opts: opts.Clone(), dec: msgpack.NewDecoder(&smallBuf{}), + schemaResolver: &DefaultSchemaResolver{}, } maxprocs := uint32(runtime.GOMAXPROCS(-1)) if conn.opts.Concurrency == 0 || conn.opts.Concurrency > maxprocs*128 { @@ -1102,7 +1105,7 @@ func (conn *Connection) putFuture(fut *Future, req Request, streamId uint64) { } blen := shard.buf.Len() reqid := fut.requestId - if err := pack(&shard.buf, shard.enc, reqid, req, streamId, conn.Schema); err != nil { + if err := pack(&shard.buf, shard.enc, reqid, req, streamId, conn.schemaResolver); err != nil { shard.buf.Trunc(blen) shard.bufmut.Unlock() if f := conn.fetchFuture(reqid); f == fut { diff --git a/crud/request_test.go b/crud/request_test.go index 4c49a9214..4b087d5af 100644 --- a/crud/request_test.go +++ b/crud/request_test.go @@ -70,27 +70,33 @@ var expectedOpts = map[string]interface{}{ } type ValidSchemeResolver struct { + tarantool.SchemaResolver } -func (*ValidSchemeResolver) ResolveSpaceIndex(s, i interface{}) (uint32, uint32, error) { - var spaceNo, indexNo uint32 +func (*ValidSchemeResolver) ResolveSpace(s interface{}) (uint32, error) { + var spaceNo uint32 if s != nil { spaceNo = uint32(s.(int)) } else { spaceNo = defaultSpace } + if spaceNo == invalidSpace { + return 0, errors.New(invalidSpaceMsg) + } + return spaceNo, nil +} + +func (*ValidSchemeResolver) ResolveIndex(i interface{}, spaceNo uint32) (uint32, error) { + var indexNo uint32 if i != nil { indexNo = uint32(i.(int)) } else { indexNo = defaultIndex } - if spaceNo == invalidSpace { - return 0, 0, errors.New(invalidSpaceMsg) - } if indexNo == invalidIndex { - return 0, 0, errors.New(invalidIndexMsg) + return 0, errors.New(invalidIndexMsg) } - return spaceNo, indexNo, nil + return indexNo, nil } var resolver ValidSchemeResolver diff --git a/example_test.go b/example_test.go index f85f532d0..9b66ea29b 100644 --- a/example_test.go +++ b/example_test.go @@ -231,6 +231,21 @@ func ExampleSelectRequest() { // response is [{{} 1111 hello world}] } +func ExampleSelectRequest_spaceAndIndexNames() { + conn := exampleConnect(opts) + defer conn.Close() + + req := tarantool.NewSelectRequest(spaceName) + req.Index(indexName) + resp, err := conn.Do(req).Get() + + if err != nil { + fmt.Printf("Failed to execute the request: %s\n", err) + } else { + fmt.Println(resp.Data) + } +} + func ExampleInsertRequest() { conn := exampleConnect(opts) defer conn.Close() @@ -273,6 +288,20 @@ func ExampleInsertRequest() { // Data [[32 test one]] } +func ExampleInsertRequest_spaceAndIndexNames() { + conn := exampleConnect(opts) + defer conn.Close() + + req := tarantool.NewInsertRequest(spaceName) + resp, err := conn.Do(req).Get() + + if err != nil { + fmt.Printf("Failed to execute the request: %s\n", err) + } else { + fmt.Println(resp.Data) + } +} + func ExampleDeleteRequest() { conn := exampleConnect(opts) defer conn.Close() @@ -316,6 +345,21 @@ func ExampleDeleteRequest() { // Data [[36 test one]] } +func ExampleDeleteRequest_spaceAndIndexNames() { + conn := exampleConnect(opts) + defer conn.Close() + + req := tarantool.NewDeleteRequest(spaceName) + req.Index(indexName) + resp, err := conn.Do(req).Get() + + if err != nil { + fmt.Printf("Failed to execute the request: %s\n", err) + } else { + fmt.Println(resp.Data) + } +} + func ExampleReplaceRequest() { conn := exampleConnect(opts) defer conn.Close() @@ -375,6 +419,20 @@ func ExampleReplaceRequest() { // Data [[13 test twelve]] } +func ExampleReplaceRequest_spaceAndIndexNames() { + conn := exampleConnect(opts) + defer conn.Close() + + req := tarantool.NewReplaceRequest(spaceName) + resp, err := conn.Do(req).Get() + + if err != nil { + fmt.Printf("Failed to execute the request: %s\n", err) + } else { + fmt.Println(resp.Data) + } +} + func ExampleUpdateRequest() { conn := exampleConnect(opts) defer conn.Close() @@ -411,6 +469,21 @@ func ExampleUpdateRequest() { // response is []interface {}{[]interface {}{0x457, "hello", "world"}} } +func ExampleUpdateRequest_spaceAndIndexNames() { + conn := exampleConnect(opts) + defer conn.Close() + + req := tarantool.NewUpdateRequest(spaceName) + req.Index(indexName) + resp, err := conn.Do(req).Get() + + if err != nil { + fmt.Printf("Failed to execute the request: %s\n", err) + } else { + fmt.Println(resp.Data) + } +} + func ExampleUpsertRequest() { conn := exampleConnect(opts) defer conn.Close() @@ -452,6 +525,20 @@ func ExampleUpsertRequest() { // response is []interface {}{[]interface {}{0x459, "first", "updated"}} } +func ExampleUpsertRequest_spaceAndIndexNames() { + conn := exampleConnect(opts) + defer conn.Close() + + req := tarantool.NewUpsertRequest(spaceName) + resp, err := conn.Do(req).Get() + + if err != nil { + fmt.Printf("Failed to execute the request: %s\n", err) + } else { + fmt.Println(resp.Data) + } +} + func ExampleCallRequest() { conn := exampleConnect(opts) defer conn.Close() @@ -634,6 +721,7 @@ func ExampleProtocolVersion() { // Connector client protocol feature: IPROTO_FEATURE_ERROR_EXTENSION // Connector client protocol feature: IPROTO_FEATURE_WATCHERS // Connector client protocol feature: IPROTO_FEATURE_PAGINATION + // Connector client protocol feature: IPROTO_FEATURE_SPACE_AND_INDEX_NAMES // Connector client protocol feature: IPROTO_FEATURE_WATCH_ONCE } diff --git a/export_test.go b/export_test.go index 38211a4fc..a52ef5b26 100644 --- a/export_test.go +++ b/export_test.go @@ -25,39 +25,80 @@ func RefImplPingBody(enc *msgpack.Encoder) error { // RefImplSelectBody is reference implementation for filling of a select // request's body. -func RefImplSelectBody(enc *msgpack.Encoder, space, index, offset, limit uint32, iterator Iter, - key, after interface{}, fetchPos bool) error { - return fillSelect(enc, space, index, offset, limit, iterator, key, after, fetchPos) +func RefImplSelectBody(enc *msgpack.Encoder, res SchemaResolver, space, index interface{}, + offset, limit uint32, iterator Iter, key, after interface{}, fetchPos bool) error { + spaceEnc, err := newSpaceEncoder(res, space) + if err != nil { + return err + } + indexEnc, err := newIndexEncoder(res, index, spaceEnc.Id) + if err != nil { + return err + } + return fillSelect(enc, spaceEnc, indexEnc, offset, limit, iterator, key, after, fetchPos) } // RefImplInsertBody is reference implementation for filling of an insert // request's body. -func RefImplInsertBody(enc *msgpack.Encoder, space uint32, tuple interface{}) error { - return fillInsert(enc, space, tuple) +func RefImplInsertBody(enc *msgpack.Encoder, res SchemaResolver, space, + tuple interface{}) error { + spaceEnc, err := newSpaceEncoder(res, space) + if err != nil { + return err + } + return fillInsert(enc, spaceEnc, tuple) } // RefImplReplaceBody is reference implementation for filling of a replace // request's body. -func RefImplReplaceBody(enc *msgpack.Encoder, space uint32, tuple interface{}) error { - return fillInsert(enc, space, tuple) +func RefImplReplaceBody(enc *msgpack.Encoder, res SchemaResolver, space, + tuple interface{}) error { + spaceEnc, err := newSpaceEncoder(res, space) + if err != nil { + return err + } + return fillInsert(enc, spaceEnc, tuple) } // RefImplDeleteBody is reference implementation for filling of a delete // request's body. -func RefImplDeleteBody(enc *msgpack.Encoder, space, index uint32, key interface{}) error { - return fillDelete(enc, space, index, key) +func RefImplDeleteBody(enc *msgpack.Encoder, res SchemaResolver, space, index, + key interface{}) error { + spaceEnc, err := newSpaceEncoder(res, space) + if err != nil { + return err + } + indexEnc, err := newIndexEncoder(res, index, spaceEnc.Id) + if err != nil { + return err + } + return fillDelete(enc, spaceEnc, indexEnc, key) } // RefImplUpdateBody is reference implementation for filling of an update // request's body. -func RefImplUpdateBody(enc *msgpack.Encoder, space, index uint32, key, ops interface{}) error { - return fillUpdate(enc, space, index, key, ops) +func RefImplUpdateBody(enc *msgpack.Encoder, res SchemaResolver, space, index, + key, ops interface{}) error { + spaceEnc, err := newSpaceEncoder(res, space) + if err != nil { + return err + } + indexEnc, err := newIndexEncoder(res, index, spaceEnc.Id) + if err != nil { + return err + } + return fillUpdate(enc, spaceEnc, indexEnc, key, ops) } // RefImplUpsertBody is reference implementation for filling of an upsert // request's body. -func RefImplUpsertBody(enc *msgpack.Encoder, space uint32, tuple, ops interface{}) error { - return fillUpsert(enc, space, tuple, ops) +func RefImplUpsertBody(enc *msgpack.Encoder, res SchemaResolver, space, + tuple, ops interface{}) error { + spaceEnc, err := newSpaceEncoder(res, space) + if err != nil { + return err + } + return fillUpsert(enc, spaceEnc, tuple, ops) } // RefImplCallBody is reference implementation for filling of a call or call17 diff --git a/protocol.go b/protocol.go index 7de8b197e..9296943ce 100644 --- a/protocol.go +++ b/protocol.go @@ -56,6 +56,7 @@ var clientProtocolInfo ProtocolInfo = ProtocolInfo{ iproto.IPROTO_FEATURE_ERROR_EXTENSION, iproto.IPROTO_FEATURE_WATCHERS, iproto.IPROTO_FEATURE_PAGINATION, + iproto.IPROTO_FEATURE_SPACE_AND_INDEX_NAMES, iproto.IPROTO_FEATURE_WATCH_ONCE, }, } diff --git a/request.go b/request.go index 3332486f4..960e64744 100644 --- a/request.go +++ b/request.go @@ -12,20 +12,93 @@ import ( "github.com/vmihailenco/msgpack/v5" ) -func fillSearch(enc *msgpack.Encoder, spaceNo, indexNo uint32, key interface{}) error { - if err := enc.EncodeUint(uint64(iproto.IPROTO_SPACE_ID)); err != nil { +type spaceEncoder struct { + Id uint32 + Name string + IsId bool +} + +func newSpaceEncoder(res SchemaResolver, spaceInfo interface{}) (spaceEncoder, error) { + if res.NamesUseSupported() { + if spaceName, ok := spaceInfo.(string); ok { + return spaceEncoder{ + Id: 0, + Name: spaceName, + IsId: false, + }, nil + } + } + + spaceId, err := res.ResolveSpace(spaceInfo) + return spaceEncoder{ + Id: spaceId, + IsId: true, + }, err +} + +func (e spaceEncoder) Encode(enc *msgpack.Encoder) error { + if e.IsId { + if err := enc.EncodeUint(uint64(iproto.IPROTO_SPACE_ID)); err != nil { + return err + } + return enc.EncodeUint(uint64(e.Id)) + } + if err := enc.EncodeUint(uint64(iproto.IPROTO_SPACE_NAME)); err != nil { return err } - if err := enc.EncodeUint(uint64(spaceNo)); err != nil { + return enc.EncodeString(e.Name) +} + +type indexEncoder struct { + Id uint32 + Name string + IsId bool +} + +func newIndexEncoder(res SchemaResolver, indexInfo interface{}, + spaceNo uint32) (indexEncoder, error) { + if res.NamesUseSupported() { + if indexName, ok := indexInfo.(string); ok { + return indexEncoder{ + Name: indexName, + IsId: false, + }, nil + } + } + + indexId, err := res.ResolveIndex(indexInfo, spaceNo) + return indexEncoder{ + Id: indexId, + IsId: true, + }, err +} + +func (e indexEncoder) Encode(enc *msgpack.Encoder) error { + if e.IsId { + if err := enc.EncodeUint(uint64(iproto.IPROTO_INDEX_ID)); err != nil { + return err + } + return enc.EncodeUint(uint64(e.Id)) + } + if err := enc.EncodeUint(uint64(iproto.IPROTO_INDEX_NAME)); err != nil { return err } - if err := enc.EncodeUint(uint64(iproto.IPROTO_INDEX_ID)); err != nil { + return enc.EncodeString(e.Name) +} + +func fillSearch(enc *msgpack.Encoder, spaceEnc spaceEncoder, indexEnc indexEncoder, + key interface{}) error { + var err error + + if err = spaceEnc.Encode(enc); err != nil { return err } - if err := enc.EncodeUint(uint64(indexNo)); err != nil { + + if err = indexEnc.Encode(enc); err != nil { return err } - if err := enc.EncodeUint(uint64(iproto.IPROTO_KEY)); err != nil { + + if err = enc.EncodeUint(uint64(iproto.IPROTO_KEY)); err != nil { return err } return enc.Encode(key) @@ -50,24 +123,23 @@ func fillIterator(enc *msgpack.Encoder, offset, limit uint32, iterator Iter) err return enc.EncodeUint(uint64(limit)) } -func fillInsert(enc *msgpack.Encoder, spaceNo uint32, tuple interface{}) error { - if err := enc.EncodeMapLen(2); err != nil { - return err - } - if err := enc.EncodeUint(uint64(iproto.IPROTO_SPACE_ID)); err != nil { +func fillInsert(enc *msgpack.Encoder, spaceEnc spaceEncoder, tuple interface{}) error { + var err error + if err = enc.EncodeMapLen(2); err != nil { return err } - if err := enc.EncodeUint(uint64(spaceNo)); err != nil { + if err = spaceEnc.Encode(enc); err != nil { return err } - if err := enc.EncodeUint(uint64(iproto.IPROTO_TUPLE)); err != nil { + + if err = enc.EncodeUint(uint64(iproto.IPROTO_TUPLE)); err != nil { return err } return enc.Encode(tuple) } -func fillSelect(enc *msgpack.Encoder, spaceNo, indexNo, offset, limit uint32, iterator Iter, - key, after interface{}, fetchPos bool) error { +func fillSelect(enc *msgpack.Encoder, spaceEnc spaceEncoder, indexEnc indexEncoder, + offset, limit uint32, iterator Iter, key, after interface{}, fetchPos bool) error { mapLen := 6 if fetchPos { mapLen += 1 @@ -81,7 +153,7 @@ func fillSelect(enc *msgpack.Encoder, spaceNo, indexNo, offset, limit uint32, it if err := fillIterator(enc, offset, limit, iterator); err != nil { return err } - if err := fillSearch(enc, spaceNo, indexNo, key); err != nil { + if err := fillSearch(enc, spaceEnc, indexEnc, key); err != nil { return err } if fetchPos { @@ -112,19 +184,22 @@ func fillSelect(enc *msgpack.Encoder, spaceNo, indexNo, offset, limit uint32, it return nil } -func fillUpdate(enc *msgpack.Encoder, spaceNo, indexNo uint32, key, ops interface{}) error { +func fillUpdate(enc *msgpack.Encoder, spaceEnc spaceEncoder, indexEnc indexEncoder, + key, ops interface{}) error { enc.EncodeMapLen(4) - if err := fillSearch(enc, spaceNo, indexNo, key); err != nil { + if err := fillSearch(enc, spaceEnc, indexEnc, key); err != nil { return err } enc.EncodeUint(uint64(iproto.IPROTO_TUPLE)) return enc.Encode(ops) } -func fillUpsert(enc *msgpack.Encoder, spaceNo uint32, tuple, ops interface{}) error { +func fillUpsert(enc *msgpack.Encoder, spaceEnc spaceEncoder, tuple, ops interface{}) error { enc.EncodeMapLen(3) - enc.EncodeUint(uint64(iproto.IPROTO_SPACE_ID)) - enc.EncodeUint(uint64(spaceNo)) + if err := spaceEnc.Encode(enc); err != nil { + return err + } + enc.EncodeUint(uint64(iproto.IPROTO_TUPLE)) if err := enc.Encode(tuple); err != nil { return err @@ -133,9 +208,10 @@ func fillUpsert(enc *msgpack.Encoder, spaceNo uint32, tuple, ops interface{}) er return enc.Encode(ops) } -func fillDelete(enc *msgpack.Encoder, spaceNo, indexNo uint32, key interface{}) error { +func fillDelete(enc *msgpack.Encoder, spaceEnc spaceEncoder, indexEnc indexEncoder, + key interface{}) error { enc.EncodeMapLen(3) - return fillSearch(enc, spaceNo, indexNo, key) + return fillSearch(enc, spaceEnc, indexEnc, key) } func fillCall(enc *msgpack.Encoder, functionName string, args interface{}) error { @@ -948,12 +1024,16 @@ func (req *SelectRequest) After(after interface{}) *SelectRequest { // Body fills an encoder with the select request body. func (req *SelectRequest) Body(res SchemaResolver, enc *msgpack.Encoder) error { - spaceNo, indexNo, err := res.ResolveSpaceIndex(req.space, req.index) + spaceEnc, err := newSpaceEncoder(res, req.space) + if err != nil { + return err + } + indexEnc, err := newIndexEncoder(res, req.index, spaceEnc.Id) if err != nil { return err } - return fillSelect(enc, spaceNo, indexNo, req.offset, req.limit, req.iterator, + return fillSelect(enc, spaceEnc, indexEnc, req.offset, req.limit, req.iterator, req.key, req.after, req.fetchPos) } @@ -993,12 +1073,12 @@ func (req *InsertRequest) Tuple(tuple interface{}) *InsertRequest { // Body fills an msgpack.Encoder with the insert request body. func (req *InsertRequest) Body(res SchemaResolver, enc *msgpack.Encoder) error { - spaceNo, _, err := res.ResolveSpaceIndex(req.space, nil) + spaceEnc, err := newSpaceEncoder(res, req.space) if err != nil { return err } - return fillInsert(enc, spaceNo, req.tuple) + return fillInsert(enc, spaceEnc, req.tuple) } // Context sets a passed context to the request. @@ -1037,12 +1117,12 @@ func (req *ReplaceRequest) Tuple(tuple interface{}) *ReplaceRequest { // Body fills an msgpack.Encoder with the replace request body. func (req *ReplaceRequest) Body(res SchemaResolver, enc *msgpack.Encoder) error { - spaceNo, _, err := res.ResolveSpaceIndex(req.space, nil) + spaceEnc, err := newSpaceEncoder(res, req.space) if err != nil { return err } - return fillInsert(enc, spaceNo, req.tuple) + return fillInsert(enc, spaceEnc, req.tuple) } // Context sets a passed context to the request. @@ -1088,12 +1168,16 @@ func (req *DeleteRequest) Key(key interface{}) *DeleteRequest { // Body fills an msgpack.Encoder with the delete request body. func (req *DeleteRequest) Body(res SchemaResolver, enc *msgpack.Encoder) error { - spaceNo, indexNo, err := res.ResolveSpaceIndex(req.space, req.index) + spaceEnc, err := newSpaceEncoder(res, req.space) + if err != nil { + return err + } + indexEnc, err := newIndexEncoder(res, req.index, spaceEnc.Id) if err != nil { return err } - return fillDelete(enc, spaceNo, indexNo, req.key) + return fillDelete(enc, spaceEnc, indexEnc, req.key) } // Context sets a passed context to the request. @@ -1150,12 +1234,16 @@ func (req *UpdateRequest) Operations(ops *Operations) *UpdateRequest { // Body fills an msgpack.Encoder with the update request body. func (req *UpdateRequest) Body(res SchemaResolver, enc *msgpack.Encoder) error { - spaceNo, indexNo, err := res.ResolveSpaceIndex(req.space, req.index) + spaceEnc, err := newSpaceEncoder(res, req.space) + if err != nil { + return err + } + indexEnc, err := newIndexEncoder(res, req.index, spaceEnc.Id) if err != nil { return err } - return fillUpdate(enc, spaceNo, indexNo, req.key, req.ops) + return fillUpdate(enc, spaceEnc, indexEnc, req.key, req.ops) } // Context sets a passed context to the request. @@ -1205,12 +1293,12 @@ func (req *UpsertRequest) Operations(ops *Operations) *UpsertRequest { // Body fills an msgpack.Encoder with the upsert request body. func (req *UpsertRequest) Body(res SchemaResolver, enc *msgpack.Encoder) error { - spaceNo, _, err := res.ResolveSpaceIndex(req.space, nil) + spaceEnc, err := newSpaceEncoder(res, req.space) if err != nil { return err } - return fillUpsert(enc, spaceNo, req.tuple, req.ops) + return fillUpsert(enc, spaceEnc, req.tuple, req.ops) } // Context sets a passed context to the request. diff --git a/request_test.go b/request_test.go index 6d08533d3..03f2ef2b9 100644 --- a/request_test.go +++ b/request_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "errors" + "fmt" "testing" "time" @@ -17,14 +18,14 @@ import ( const invalidSpaceMsg = "invalid space" const invalidIndexMsg = "invalid index" -const invalidSpace = 2 -const invalidIndex = 2 -const validSpace = 1 // Any valid value != default. -const validIndex = 3 // Any valid value != default. +const invalidSpace uint32 = 2 +const invalidIndex uint32 = 2 +const validSpace uint32 = 1 // Any valid value != default. +const validIndex uint32 = 3 // Any valid value != default. const validExpr = "any string" // We don't check the value here. const validKey = "foo" // Any string. -const defaultSpace = 0 // And valid too. -const defaultIndex = 0 // And valid too. +const defaultSpace uint32 = 0 // And valid too. +const defaultIndex uint32 = 0 // And valid too. const defaultIsolationLevel = DefaultIsolationLevel const defaultTimeout = 0 @@ -39,27 +40,37 @@ var validProtocolInfo ProtocolInfo = ProtocolInfo{ } type ValidSchemeResolver struct { + nameUseSupported bool } -func (*ValidSchemeResolver) ResolveSpaceIndex(s, i interface{}) (uint32, uint32, error) { - var spaceNo, indexNo uint32 +func (*ValidSchemeResolver) ResolveSpace(s interface{}) (uint32, error) { + var spaceNo uint32 if s != nil { - spaceNo = uint32(s.(int)) + spaceNo = s.(uint32) } else { spaceNo = defaultSpace } + if spaceNo == invalidSpace { + return 0, errors.New(invalidSpaceMsg) + } + return spaceNo, nil +} + +func (*ValidSchemeResolver) ResolveIndex(i interface{}, spaceNo uint32) (uint32, error) { + var indexNo uint32 if i != nil { - indexNo = uint32(i.(int)) + indexNo = i.(uint32) } else { indexNo = defaultIndex } - if spaceNo == invalidSpace { - return 0, 0, errors.New(invalidSpaceMsg) - } if indexNo == invalidIndex { - return 0, 0, errors.New(invalidIndexMsg) + return 0, errors.New(invalidIndexMsg) } - return spaceNo, indexNo, nil + return indexNo, nil +} + +func (r *ValidSchemeResolver) NamesUseSupported() bool { + return r.nameUseSupported } var resolver ValidSchemeResolver @@ -130,45 +141,84 @@ func getTestOps() ([]Op, *Operations) { } func TestRequestsValidSpaceAndIndex(t *testing.T) { - requests := []Request{ - NewSelectRequest(validSpace), - NewSelectRequest(validSpace).Index(validIndex), - NewUpdateRequest(validSpace), - NewUpdateRequest(validSpace).Index(validIndex), - NewUpsertRequest(validSpace), - NewInsertRequest(validSpace), - NewReplaceRequest(validSpace), - NewDeleteRequest(validSpace), - NewDeleteRequest(validSpace).Index(validIndex), + tests := []struct { + supportNames bool + }{ + {false}, + {true}, } + for _, test := range tests { + resolver.nameUseSupported = test.supportNames + + requests := []Request{ + NewSelectRequest(validSpace), + NewSelectRequest(validSpace).Index(validIndex), + NewUpdateRequest(validSpace), + NewUpdateRequest(validSpace).Index(validIndex), + NewUpsertRequest(validSpace), + NewInsertRequest(validSpace), + NewReplaceRequest(validSpace), + NewDeleteRequest(validSpace), + NewDeleteRequest(validSpace).Index(validIndex), + } - assertBodyCall(t, requests, "") + t.Run(fmt.Sprintf("NamesSupported_%v", test.supportNames), + func(t *testing.T) { + assertBodyCall(t, requests, "") + }) + } } func TestRequestsInvalidSpace(t *testing.T) { - requests := []Request{ - NewSelectRequest(invalidSpace).Index(validIndex), - NewSelectRequest(invalidSpace), - NewUpdateRequest(invalidSpace).Index(validIndex), - NewUpdateRequest(invalidSpace), - NewUpsertRequest(invalidSpace), - NewInsertRequest(invalidSpace), - NewReplaceRequest(invalidSpace), - NewDeleteRequest(invalidSpace).Index(validIndex), - NewDeleteRequest(invalidSpace), + tests := []struct { + supportNames bool + }{ + {false}, + {true}, } + for _, test := range tests { + resolver.nameUseSupported = test.supportNames + + requests := []Request{ + NewSelectRequest(invalidSpace).Index(validIndex), + NewSelectRequest(invalidSpace), + NewUpdateRequest(invalidSpace).Index(validIndex), + NewUpdateRequest(invalidSpace), + NewUpsertRequest(invalidSpace), + NewInsertRequest(invalidSpace), + NewReplaceRequest(invalidSpace), + NewDeleteRequest(invalidSpace).Index(validIndex), + NewDeleteRequest(invalidSpace), + } - assertBodyCall(t, requests, invalidSpaceMsg) + t.Run(fmt.Sprintf("NamesSupported_%v", test.supportNames), + func(t *testing.T) { + assertBodyCall(t, requests, invalidSpaceMsg) + }) + } } func TestRequestsInvalidIndex(t *testing.T) { - requests := []Request{ - NewSelectRequest(validSpace).Index(invalidIndex), - NewUpdateRequest(validSpace).Index(invalidIndex), - NewDeleteRequest(validSpace).Index(invalidIndex), + tests := []struct { + supportNames bool + }{ + {false}, + {true}, } + for _, test := range tests { + resolver.nameUseSupported = test.supportNames + + requests := []Request{ + NewSelectRequest(validSpace).Index(invalidIndex), + NewUpdateRequest(validSpace).Index(invalidIndex), + NewDeleteRequest(validSpace).Index(invalidIndex), + } - assertBodyCall(t, requests, invalidIndexMsg) + t.Run(fmt.Sprintf("NamesSupported_%v", test.supportNames), + func(t *testing.T) { + assertBodyCall(t, requests, invalidIndexMsg) + }) + } } func TestRequestsTypes(t *testing.T) { @@ -328,256 +378,604 @@ func TestPingRequestDefaultValues(t *testing.T) { } func TestSelectRequestDefaultValues(t *testing.T) { + tests := []struct { + supportNames bool + }{ + {false}, + {true}, + } + + for _, test := range tests { + var refBuf bytes.Buffer + + resolver.nameUseSupported = test.supportNames + + t.Run(fmt.Sprintf("NamesSupported_%v", test.supportNames), func(t *testing.T) { + + refEnc := msgpack.NewEncoder(&refBuf) + err := RefImplSelectBody(refEnc, &resolver, validSpace, defaultIndex, 0, 0xFFFFFFFF, + IterAll, []interface{}{}, nil, false) + if err != nil { + t.Errorf("An unexpected RefImplSelectBody() error %q", err.Error()) + return + } + + req := NewSelectRequest(validSpace) + assertBodyEqual(t, refBuf.Bytes(), req) + }) + } +} + +func TestSelectRequestSpaceByName(t *testing.T) { var refBuf bytes.Buffer + resolver.nameUseSupported = true + refEnc := msgpack.NewEncoder(&refBuf) - err := RefImplSelectBody(refEnc, validSpace, defaultIndex, 0, 0xFFFFFFFF, + err := RefImplSelectBody(refEnc, &resolver, "valid", defaultIndex, 0, 0xFFFFFFFF, IterAll, []interface{}{}, nil, false) if err != nil { t.Errorf("An unexpected RefImplSelectBody() error %q", err.Error()) return } - req := NewSelectRequest(validSpace) + req := NewSelectRequest("valid") assertBodyEqual(t, refBuf.Bytes(), req) } -func TestSelectRequestDefaultIteratorEqIfKey(t *testing.T) { +func TestSelectRequestIndexByName(t *testing.T) { var refBuf bytes.Buffer - key := []interface{}{uint(18)} + + resolver.nameUseSupported = true refEnc := msgpack.NewEncoder(&refBuf) - err := RefImplSelectBody(refEnc, validSpace, defaultIndex, 0, 0xFFFFFFFF, - IterEq, key, nil, false) + err := RefImplSelectBody(refEnc, &resolver, defaultSpace, "valid", 0, 0xFFFFFFFF, + IterAll, []interface{}{}, nil, false) if err != nil { t.Errorf("An unexpected RefImplSelectBody() error %q", err.Error()) return } - req := NewSelectRequest(validSpace). - Key(key) + req := NewSelectRequest(defaultSpace) + req.Index("valid") assertBodyEqual(t, refBuf.Bytes(), req) } -func TestSelectRequestIteratorNotChangedIfKey(t *testing.T) { - var refBuf bytes.Buffer - key := []interface{}{uint(678)} - const iter = IterGe +func TestSelectRequestDefaultIteratorEqIfKey(t *testing.T) { + tests := []struct { + supportNames bool + }{ + {false}, + {true}, + } - refEnc := msgpack.NewEncoder(&refBuf) - err := RefImplSelectBody(refEnc, validSpace, defaultIndex, 0, 0xFFFFFFFF, - iter, key, nil, false) - if err != nil { - t.Errorf("An unexpected RefImplSelectBody() error %q", err.Error()) - return + for _, test := range tests { + var refBuf bytes.Buffer + key := []interface{}{uint(18)} + + resolver.nameUseSupported = test.supportNames + + t.Run(fmt.Sprintf("NamesSupported_%v", test.supportNames), func(t *testing.T) { + + refEnc := msgpack.NewEncoder(&refBuf) + err := RefImplSelectBody(refEnc, &resolver, validSpace, defaultIndex, 0, 0xFFFFFFFF, + IterEq, key, nil, false) + if err != nil { + t.Errorf("An unexpected RefImplSelectBody() error %q", err.Error()) + return + } + + req := NewSelectRequest(validSpace). + Key(key) + assertBodyEqual(t, refBuf.Bytes(), req) + }) } +} - req := NewSelectRequest(validSpace). - Iterator(iter). - Key(key) - assertBodyEqual(t, refBuf.Bytes(), req) +func TestSelectRequestIteratorNotChangedIfKey(t *testing.T) { + tests := []struct { + supportNames bool + }{ + {false}, + {true}, + } + + for _, test := range tests { + var refBuf bytes.Buffer + key := []interface{}{uint(678)} + const iter = IterGe + + resolver.nameUseSupported = test.supportNames + + t.Run(fmt.Sprintf("NamesSupported_%v", test.supportNames), func(t *testing.T) { + + refEnc := msgpack.NewEncoder(&refBuf) + err := RefImplSelectBody(refEnc, &resolver, validSpace, defaultIndex, 0, 0xFFFFFFFF, + iter, key, nil, false) + if err != nil { + t.Errorf("An unexpected RefImplSelectBody() error %q", err.Error()) + return + } + + req := NewSelectRequest(validSpace). + Iterator(iter). + Key(key) + assertBodyEqual(t, refBuf.Bytes(), req) + }) + } } func TestSelectRequestSetters(t *testing.T) { - const offset = 4 - const limit = 5 - const iter = IterLt - key := []interface{}{uint(36)} - afterBytes := []byte{0x1, 0x2, 0x3} - afterKey := []interface{}{uint(13)} - var refBufAfterBytes, refBufAfterKey bytes.Buffer - - refEncAfterBytes := msgpack.NewEncoder(&refBufAfterBytes) - err := RefImplSelectBody(refEncAfterBytes, validSpace, validIndex, offset, - limit, iter, key, afterBytes, true) - if err != nil { - t.Errorf("An unexpected RefImplSelectBody() error %s", err) - return + tests := []struct { + supportNames bool + }{ + {false}, + {true}, } - refEncAfterKey := msgpack.NewEncoder(&refBufAfterKey) - err = RefImplSelectBody(refEncAfterKey, validSpace, validIndex, offset, - limit, iter, key, afterKey, true) - if err != nil { - t.Errorf("An unexpected RefImplSelectBody() error %s", err) - return + for _, test := range tests { + const offset = 4 + const limit = 5 + const iter = IterLt + key := []interface{}{uint(36)} + afterBytes := []byte{0x1, 0x2, 0x3} + afterKey := []interface{}{uint(13)} + var refBufAfterBytes, refBufAfterKey bytes.Buffer + + resolver.nameUseSupported = test.supportNames + + t.Run(fmt.Sprintf("NamesSupported_%v", test.supportNames), func(t *testing.T) { + + refEncAfterBytes := msgpack.NewEncoder(&refBufAfterBytes) + err := RefImplSelectBody(refEncAfterBytes, &resolver, validSpace, validIndex, offset, + limit, iter, key, afterBytes, true) + if err != nil { + t.Errorf("An unexpected RefImplSelectBody() error %s", err) + return + } + + refEncAfterKey := msgpack.NewEncoder(&refBufAfterKey) + err = RefImplSelectBody(refEncAfterKey, &resolver, validSpace, validIndex, offset, + limit, iter, key, afterKey, true) + if err != nil { + t.Errorf("An unexpected RefImplSelectBody() error %s", err) + return + } + + reqAfterBytes := NewSelectRequest(validSpace). + Index(validIndex). + Offset(offset). + Limit(limit). + Iterator(iter). + Key(key). + After(afterBytes). + FetchPos(true) + reqAfterKey := NewSelectRequest(validSpace). + Index(validIndex). + Offset(offset). + Limit(limit). + Iterator(iter). + Key(key). + After(afterKey). + FetchPos(true) + + assertBodyEqual(t, refBufAfterBytes.Bytes(), reqAfterBytes) + assertBodyEqual(t, refBufAfterKey.Bytes(), reqAfterKey) + }) } +} - reqAfterBytes := NewSelectRequest(validSpace). - Index(validIndex). - Offset(offset). - Limit(limit). - Iterator(iter). - Key(key). - After(afterBytes). - FetchPos(true) - reqAfterKey := NewSelectRequest(validSpace). - Index(validIndex). - Offset(offset). - Limit(limit). - Iterator(iter). - Key(key). - After(afterKey). - FetchPos(true) +func TestInsertRequestDefaultValues(t *testing.T) { + tests := []struct { + supportNames bool + }{ + {false}, + {true}, + } - assertBodyEqual(t, refBufAfterBytes.Bytes(), reqAfterBytes) - assertBodyEqual(t, refBufAfterKey.Bytes(), reqAfterKey) + for _, test := range tests { + var refBuf bytes.Buffer + + resolver.nameUseSupported = test.supportNames + + t.Run(fmt.Sprintf("NamesSupported_%v", test.supportNames), func(t *testing.T) { + + refEnc := msgpack.NewEncoder(&refBuf) + err := RefImplInsertBody(refEnc, &resolver, validSpace, []interface{}{}) + if err != nil { + t.Errorf("An unexpected RefImplInsertBody() error: %q", err.Error()) + return + } + + req := NewInsertRequest(validSpace) + assertBodyEqual(t, refBuf.Bytes(), req) + }) + } } -func TestInsertRequestDefaultValues(t *testing.T) { +func TestInsertRequestSpaceByName(t *testing.T) { var refBuf bytes.Buffer + resolver.nameUseSupported = true + refEnc := msgpack.NewEncoder(&refBuf) - err := RefImplInsertBody(refEnc, validSpace, []interface{}{}) + err := RefImplInsertBody(refEnc, &resolver, "valid", []interface{}{}) if err != nil { t.Errorf("An unexpected RefImplInsertBody() error: %q", err.Error()) return } - req := NewInsertRequest(validSpace) + req := NewInsertRequest("valid") assertBodyEqual(t, refBuf.Bytes(), req) } func TestInsertRequestSetters(t *testing.T) { - tuple := []interface{}{uint(24)} - var refBuf bytes.Buffer - - refEnc := msgpack.NewEncoder(&refBuf) - err := RefImplInsertBody(refEnc, validSpace, tuple) - if err != nil { - t.Errorf("An unexpected RefImplInsertBody() error: %q", err.Error()) - return + tests := []struct { + supportNames bool + }{ + {false}, + {true}, } - req := NewInsertRequest(validSpace). - Tuple(tuple) - assertBodyEqual(t, refBuf.Bytes(), req) + for _, test := range tests { + tuple := []interface{}{uint(24)} + var refBuf bytes.Buffer + + resolver.nameUseSupported = test.supportNames + + t.Run(fmt.Sprintf("NamesSupported_%v", test.supportNames), func(t *testing.T) { + + refEnc := msgpack.NewEncoder(&refBuf) + err := RefImplInsertBody(refEnc, &resolver, validSpace, tuple) + if err != nil { + t.Errorf("An unexpected RefImplInsertBody() error: %q", err.Error()) + return + } + + req := NewInsertRequest(validSpace). + Tuple(tuple) + assertBodyEqual(t, refBuf.Bytes(), req) + }) + } } func TestReplaceRequestDefaultValues(t *testing.T) { + tests := []struct { + supportNames bool + }{ + {false}, + {true}, + } + + for _, test := range tests { + var refBuf bytes.Buffer + + resolver.nameUseSupported = test.supportNames + + t.Run(fmt.Sprintf("NamesSupported_%v", test.supportNames), func(t *testing.T) { + + refEnc := msgpack.NewEncoder(&refBuf) + err := RefImplReplaceBody(refEnc, &resolver, validSpace, []interface{}{}) + if err != nil { + t.Errorf("An unexpected RefImplReplaceBody() error: %q", err.Error()) + return + } + + req := NewReplaceRequest(validSpace) + assertBodyEqual(t, refBuf.Bytes(), req) + }) + } +} + +func TestReplaceRequestSpaceByName(t *testing.T) { var refBuf bytes.Buffer + resolver.nameUseSupported = true + refEnc := msgpack.NewEncoder(&refBuf) - err := RefImplReplaceBody(refEnc, validSpace, []interface{}{}) + err := RefImplReplaceBody(refEnc, &resolver, "valid", []interface{}{}) if err != nil { t.Errorf("An unexpected RefImplReplaceBody() error: %q", err.Error()) return } - req := NewReplaceRequest(validSpace) + req := NewReplaceRequest("valid") assertBodyEqual(t, refBuf.Bytes(), req) } func TestReplaceRequestSetters(t *testing.T) { - tuple := []interface{}{uint(99)} - var refBuf bytes.Buffer - - refEnc := msgpack.NewEncoder(&refBuf) - err := RefImplReplaceBody(refEnc, validSpace, tuple) - if err != nil { - t.Errorf("An unexpected RefImplReplaceBody() error: %q", err.Error()) - return + tests := []struct { + supportNames bool + }{ + {false}, + {true}, } - req := NewReplaceRequest(validSpace). - Tuple(tuple) - assertBodyEqual(t, refBuf.Bytes(), req) + for _, test := range tests { + tuple := []interface{}{uint(99)} + var refBuf bytes.Buffer + + resolver.nameUseSupported = test.supportNames + + t.Run(fmt.Sprintf("NamesSupported_%v", test.supportNames), func(t *testing.T) { + + refEnc := msgpack.NewEncoder(&refBuf) + err := RefImplReplaceBody(refEnc, &resolver, validSpace, tuple) + if err != nil { + t.Errorf("An unexpected RefImplReplaceBody() error: %q", err.Error()) + return + } + + req := NewReplaceRequest(validSpace). + Tuple(tuple) + assertBodyEqual(t, refBuf.Bytes(), req) + }) + } } func TestDeleteRequestDefaultValues(t *testing.T) { + tests := []struct { + supportNames bool + }{ + {false}, + {true}, + } + + for _, test := range tests { + var refBuf bytes.Buffer + + resolver.nameUseSupported = test.supportNames + + t.Run(fmt.Sprintf("NamesSupported_%v", test.supportNames), func(t *testing.T) { + + refEnc := msgpack.NewEncoder(&refBuf) + err := RefImplDeleteBody(refEnc, &resolver, validSpace, defaultIndex, []interface{}{}) + if err != nil { + t.Errorf("An unexpected RefImplDeleteBody() error: %q", err.Error()) + return + } + + req := NewDeleteRequest(validSpace) + assertBodyEqual(t, refBuf.Bytes(), req) + }) + } +} + +func TestDeleteRequestSpaceByName(t *testing.T) { var refBuf bytes.Buffer + resolver.nameUseSupported = true + refEnc := msgpack.NewEncoder(&refBuf) - err := RefImplDeleteBody(refEnc, validSpace, defaultIndex, []interface{}{}) + err := RefImplDeleteBody(refEnc, &resolver, "valid", defaultIndex, []interface{}{}) if err != nil { t.Errorf("An unexpected RefImplDeleteBody() error: %q", err.Error()) return } - req := NewDeleteRequest(validSpace) + req := NewDeleteRequest("valid") assertBodyEqual(t, refBuf.Bytes(), req) } -func TestDeleteRequestSetters(t *testing.T) { - key := []interface{}{uint(923)} +func TestDeleteRequestIndexByName(t *testing.T) { var refBuf bytes.Buffer + resolver.nameUseSupported = true + refEnc := msgpack.NewEncoder(&refBuf) - err := RefImplDeleteBody(refEnc, validSpace, validIndex, key) + err := RefImplDeleteBody(refEnc, &resolver, defaultSpace, "valid", []interface{}{}) if err != nil { t.Errorf("An unexpected RefImplDeleteBody() error: %q", err.Error()) return } - req := NewDeleteRequest(validSpace). - Index(validIndex). - Key(key) + req := NewDeleteRequest(defaultSpace) + req.Index("valid") assertBodyEqual(t, refBuf.Bytes(), req) } +func TestDeleteRequestSetters(t *testing.T) { + tests := []struct { + supportNames bool + }{ + {false}, + {true}, + } + + for _, test := range tests { + key := []interface{}{uint(923)} + var refBuf bytes.Buffer + + resolver.nameUseSupported = test.supportNames + + t.Run(fmt.Sprintf("NamesSupported_%v", test.supportNames), func(t *testing.T) { + + refEnc := msgpack.NewEncoder(&refBuf) + err := RefImplDeleteBody(refEnc, &resolver, validSpace, validIndex, key) + if err != nil { + t.Errorf("An unexpected RefImplDeleteBody() error: %q", err.Error()) + return + } + + req := NewDeleteRequest(validSpace). + Index(validIndex). + Key(key) + assertBodyEqual(t, refBuf.Bytes(), req) + }) + } +} + func TestUpdateRequestDefaultValues(t *testing.T) { + tests := []struct { + supportNames bool + }{ + {false}, + {true}, + } + + for _, test := range tests { + var refBuf bytes.Buffer + + resolver.nameUseSupported = test.supportNames + + t.Run(fmt.Sprintf("NamesSupported_%v", test.supportNames), func(t *testing.T) { + + refEnc := msgpack.NewEncoder(&refBuf) + err := RefImplUpdateBody(refEnc, &resolver, validSpace, defaultIndex, + []interface{}{}, []Op{}) + if err != nil { + t.Errorf("An unexpected RefImplUpdateBody() error: %q", err.Error()) + return + } + + req := NewUpdateRequest(validSpace) + assertBodyEqual(t, refBuf.Bytes(), req) + }) + } +} + +func TestUpdateRequestSpaceByName(t *testing.T) { var refBuf bytes.Buffer + resolver.nameUseSupported = true + refEnc := msgpack.NewEncoder(&refBuf) - err := RefImplUpdateBody(refEnc, validSpace, defaultIndex, []interface{}{}, []Op{}) + err := RefImplUpdateBody(refEnc, &resolver, "valid", defaultIndex, + []interface{}{}, []Op{}) if err != nil { t.Errorf("An unexpected RefImplUpdateBody() error: %q", err.Error()) return } - req := NewUpdateRequest(validSpace) + req := NewUpdateRequest("valid") assertBodyEqual(t, refBuf.Bytes(), req) } -func TestUpdateRequestSetters(t *testing.T) { - key := []interface{}{uint(44)} - refOps, reqOps := getTestOps() +func TestUpdateRequestIndexByName(t *testing.T) { var refBuf bytes.Buffer + resolver.nameUseSupported = true + refEnc := msgpack.NewEncoder(&refBuf) - err := RefImplUpdateBody(refEnc, validSpace, validIndex, key, refOps) + err := RefImplUpdateBody(refEnc, &resolver, defaultSpace, "valid", + []interface{}{}, []Op{}) if err != nil { t.Errorf("An unexpected RefImplUpdateBody() error: %q", err.Error()) return } - req := NewUpdateRequest(validSpace). - Index(validIndex). - Key(key). - Operations(reqOps) + req := NewUpdateRequest(defaultSpace) + req.Index("valid") assertBodyEqual(t, refBuf.Bytes(), req) } +func TestUpdateRequestSetters(t *testing.T) { + tests := []struct { + supportNames bool + }{ + {false}, + {true}, + } + + for _, test := range tests { + key := []interface{}{uint(44)} + refOps, reqOps := getTestOps() + var refBuf bytes.Buffer + + resolver.nameUseSupported = test.supportNames + + t.Run(fmt.Sprintf("NamesSupported_%v", test.supportNames), func(t *testing.T) { + + refEnc := msgpack.NewEncoder(&refBuf) + err := RefImplUpdateBody(refEnc, &resolver, validSpace, validIndex, key, refOps) + if err != nil { + t.Errorf("An unexpected RefImplUpdateBody() error: %q", err.Error()) + return + } + + req := NewUpdateRequest(validSpace). + Index(validIndex). + Key(key). + Operations(reqOps) + assertBodyEqual(t, refBuf.Bytes(), req) + }) + } +} + func TestUpsertRequestDefaultValues(t *testing.T) { + tests := []struct { + supportNames bool + }{ + {false}, + {true}, + } + + for _, test := range tests { + var refBuf bytes.Buffer + + resolver.nameUseSupported = test.supportNames + + t.Run(fmt.Sprintf("NamesSupported_%v", test.supportNames), func(t *testing.T) { + + refEnc := msgpack.NewEncoder(&refBuf) + err := RefImplUpsertBody(refEnc, &resolver, validSpace, []interface{}{}, []Op{}) + if err != nil { + t.Errorf("An unexpected RefImplUpsertBody() error: %q", err.Error()) + return + } + + req := NewUpsertRequest(validSpace) + assertBodyEqual(t, refBuf.Bytes(), req) + }) + } +} + +func TestUpsertRequestSpaceByName(t *testing.T) { var refBuf bytes.Buffer + resolver.nameUseSupported = true + refEnc := msgpack.NewEncoder(&refBuf) - err := RefImplUpsertBody(refEnc, validSpace, []interface{}{}, []Op{}) + err := RefImplUpsertBody(refEnc, &resolver, "valid", []interface{}{}, []Op{}) if err != nil { t.Errorf("An unexpected RefImplUpsertBody() error: %q", err.Error()) return } - req := NewUpsertRequest(validSpace) + req := NewUpsertRequest("valid") assertBodyEqual(t, refBuf.Bytes(), req) } func TestUpsertRequestSetters(t *testing.T) { - tuple := []interface{}{uint(64)} - refOps, reqOps := getTestOps() - var refBuf bytes.Buffer - - refEnc := msgpack.NewEncoder(&refBuf) - err := RefImplUpsertBody(refEnc, validSpace, tuple, refOps) - if err != nil { - t.Errorf("An unexpected RefImplUpsertBody() error: %q", err.Error()) - return + tests := []struct { + supportNames bool + }{ + {false}, + {true}, } - req := NewUpsertRequest(validSpace). - Tuple(tuple). - Operations(reqOps) - assertBodyEqual(t, refBuf.Bytes(), req) + for _, test := range tests { + tuple := []interface{}{uint(64)} + refOps, reqOps := getTestOps() + var refBuf bytes.Buffer + + resolver.nameUseSupported = test.supportNames + + t.Run(fmt.Sprintf("NamesSupported_%v", test.supportNames), func(t *testing.T) { + + refEnc := msgpack.NewEncoder(&refBuf) + err := RefImplUpsertBody(refEnc, &resolver, validSpace, tuple, refOps) + if err != nil { + t.Errorf("An unexpected RefImplUpsertBody() error: %q", err.Error()) + return + } + + req := NewUpsertRequest(validSpace). + Tuple(tuple). + Operations(reqOps) + assertBodyEqual(t, refBuf.Bytes(), req) + }) + } } func TestCallRequestsDefaultValues(t *testing.T) { diff --git a/schema.go b/schema.go index 714f51c5b..1d605b93d 100644 --- a/schema.go +++ b/schema.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" + "github.com/tarantool/go-iproto" "github.com/vmihailenco/msgpack/v5" "github.com/vmihailenco/msgpack/v5/msgpcode" ) @@ -41,9 +42,16 @@ func msgpackIsString(code byte) bool { // SchemaResolver is an interface for resolving schema details. type SchemaResolver interface { - // ResolveSpaceIndex returns resolved space and index numbers or an - // error if it cannot be resolved. - ResolveSpaceIndex(s interface{}, i interface{}) (spaceNo, indexNo uint32, err error) + // ResolveSpace returns resolved space number or an error + // if it cannot be resolved. + ResolveSpace(s interface{}) (spaceNo uint32, err error) + // ResolveIndex returns resolved index number or an error + // if it cannot be resolved. + ResolveIndex(i interface{}, spaceNo uint32) (indexNo uint32, err error) + // NamesUseSupported shows if usage of space and index names, instead of + // IDs, is supported. It must return true if + // iproto.IPROTO_FEATURE_SPACE_AND_INDEX_NAMES is supported. + NamesUseSupported() bool } // Schema contains information about spaces and indexes. @@ -53,6 +61,9 @@ type Schema struct { Spaces map[string]*Space // SpacesById is map from space numbers to spaces. SpacesById map[uint32]*Space + // SpaceAndIndexNamesSupported shows if current Tarantool version supports + // iproto.IPROTO_FEATURE_SPACE_AND_INDEX_NAMES. + SpaceAndIndexNamesSupported bool } // Space contains information about Tarantool's space. @@ -332,6 +343,12 @@ func (indexField *IndexField) DecodeMsgpack(d *msgpack.Decoder) error { return errors.New("unexpected schema format (index fields)") } +func checkNamesSupported(conn *Connection, schema *Schema) { + schema.SpaceAndIndexNamesSupported = + isFeatureInSlice(iproto.IPROTO_FEATURE_SPACE_AND_INDEX_NAMES, + conn.opts.RequiredProtocolInfo.Features) +} + func (conn *Connection) loadSchema() (err error) { schema := new(Schema) schema.SpacesById = make(map[uint32]*Space) @@ -364,31 +381,32 @@ func (conn *Connection) loadSchema() (err error) { } } + checkNamesSupported(conn, schema) + conn.lockShards() conn.Schema = schema + conn.schemaResolver = schema conn.unlockShards() return nil } -// ResolveSpaceIndex tries to resolve space and index numbers. +// ResolveSpace tries to resolve a space number. // Note: s can be a number, string, or an object of Space type. -// Note: i can be a number, string, or an object of Index type. -func (schema *Schema) ResolveSpaceIndex(s interface{}, i interface{}) (uint32, uint32, error) { +func (schema *Schema) ResolveSpace(s interface{}) (uint32, error) { var ( - spaceNo, indexNo uint32 - space *Space - index *Index - ok bool + spaceNo uint32 + space *Space + ok bool ) switch s := s.(type) { case string: if schema == nil { - return spaceNo, indexNo, fmt.Errorf("Schema is not loaded") + return spaceNo, fmt.Errorf("schema is not loaded") } if space, ok = schema.Spaces[s]; !ok { - return spaceNo, indexNo, fmt.Errorf("there is no space with name %s", s) + return spaceNo, fmt.Errorf("there is no space with name %s", s) } spaceNo = space.Id case uint: @@ -419,50 +437,74 @@ func (schema *Schema) ResolveSpaceIndex(s interface{}, i interface{}) (uint32, u panic("unexpected type of space param") } - if i != nil { - switch i := i.(type) { - case string: - if schema == nil { - return spaceNo, indexNo, fmt.Errorf("schema is not loaded") - } - if space == nil { - if space, ok = schema.SpacesById[spaceNo]; !ok { - return spaceNo, indexNo, fmt.Errorf("there is no space with id %d", spaceNo) - } - } - if index, ok = space.Indexes[i]; !ok { - err := fmt.Errorf("space %s has not index with name %s", space.Name, i) - return spaceNo, indexNo, err - } - indexNo = index.Id - case uint: - indexNo = uint32(i) - case uint64: - indexNo = uint32(i) - case uint32: - indexNo = i - case uint16: - indexNo = uint32(i) - case uint8: - indexNo = uint32(i) - case int: - indexNo = uint32(i) - case int64: - indexNo = uint32(i) - case int32: - indexNo = uint32(i) - case int16: - indexNo = uint32(i) - case int8: - indexNo = uint32(i) - case Index: - indexNo = i.Id - case *Index: - indexNo = i.Id - default: - panic("unexpected type of index param") + return spaceNo, nil +} + +// ResolveIndex tries to resolve an index number. +// Note: i can be a number, string, or an object of Index type. +func (schema *Schema) ResolveIndex(i interface{}, spaceNo uint32) (uint32, error) { + var ( + indexNo uint32 + space *Space + index *Index + ok bool + ) + + if i == nil { + return 0, nil + } + switch i := i.(type) { + case string: + if schema == nil { + return indexNo, fmt.Errorf("schema is not loaded") + } + if space, ok = schema.SpacesById[spaceNo]; !ok { + return indexNo, fmt.Errorf("there is no space with id %d", spaceNo) } + if index, ok = space.Indexes[i]; !ok { + err := fmt.Errorf("space %s has not index with name %s", space.Name, i) + return indexNo, err + } + indexNo = index.Id + case uint: + indexNo = uint32(i) + case uint64: + indexNo = uint32(i) + case uint32: + indexNo = i + case uint16: + indexNo = uint32(i) + case uint8: + indexNo = uint32(i) + case int: + indexNo = uint32(i) + case int64: + indexNo = uint32(i) + case int32: + indexNo = uint32(i) + case int16: + indexNo = uint32(i) + case int8: + indexNo = uint32(i) + case Index: + indexNo = i.Id + case *Index: + indexNo = i.Id + default: + panic("unexpected type of index param") } - return spaceNo, indexNo, nil + return indexNo, nil +} + +func (schema *Schema) NamesUseSupported() bool { + return schema.SpaceAndIndexNamesSupported +} + +type DefaultSchemaResolver struct { + Schema +} + +func (*DefaultSchemaResolver) NamesUseSupported() bool { + return false } diff --git a/schema_test.go b/schema_test.go new file mode 100644 index 000000000..15e470570 --- /dev/null +++ b/schema_test.go @@ -0,0 +1,162 @@ +package tarantool_test + +import ( + "fmt" + "testing" + + "github.com/tarantool/go-tarantool/v2" +) + +var nilSchema *tarantool.Schema +var schema = tarantool.Schema{ + Spaces: map[string]*tarantool.Space{}, + SpacesById: map[uint32]*tarantool.Space{}, +} + +func TestNamesUseSupported(t *testing.T) { + schema.SpaceAndIndexNamesSupported = false + if schema.NamesUseSupported() { + t.Errorf("unexpected value from NamesUseSupported") + } + + schema.SpaceAndIndexNamesSupported = true + if !schema.NamesUseSupported() { + t.Errorf("unexpected value from NamesUseSupported") + } +} + +func TestResolveSpace(t *testing.T) { + validSpace := tarantool.Space{ + Id: 1, + } + schema.Spaces["valid"] = &validSpace + + tests := []struct { + supportNames bool + }{ + {false}, + {true}, + } + + for _, test := range tests { + schema.SpaceAndIndexNamesSupported = test.supportNames + + correctCases := []interface{}{"valid", uint(1), uint64(1), uint32(1), + uint16(1), uint8(1), int(1), int64(1), int32(1), int16(1), int8(1), + validSpace, &validSpace} + + t.Run(fmt.Sprintf("NamesSupported_%v", test.supportNames), func(t *testing.T) { + + for _, space := range correctCases { + // The result of ResolveSpace does not depend on the + // SpaceAndIndexNamesSupported field. + spaceNo, err := schema.ResolveSpace(space) + if err != nil { + t.Errorf("unexpected error: %s", err.Error()) + } + if spaceNo != uint32(1) { + t.Errorf("unexpected value from ResolveSpace: %d", spaceNo) + } + } + + spaceNo, err := schema.ResolveSpace("invalid") + if err == nil { + t.Errorf("expected error") + } + if spaceNo != 0 { + t.Errorf("unexpected return value: %d", spaceNo) + } + if err.Error() != "there is no space with name invalid" { + t.Errorf("expected different error, got %s", err.Error()) + } + + spaceNo, err = nilSchema.ResolveSpace("valid") + if err == nil { + t.Errorf("expected error") + } + if spaceNo != 0 { + t.Errorf("unexpected return value: %d", spaceNo) + } + if err.Error() != "schema is not loaded" { + t.Errorf("expected different error, got %s", err.Error()) + } + }) + } +} + +func TestResolveIndex(t *testing.T) { + validSpace := tarantool.Space{ + Id: 1, + Name: "valid", + Indexes: map[string]*tarantool.Index{}, + } + validIndex := tarantool.Index{ + Id: 1, + } + validSpace.Indexes["valid"] = &validIndex + schema.Spaces["valid"] = &validSpace + schema.SpacesById[1] = &validSpace + + tests := []struct { + supportNames bool + }{ + {false}, + {true}, + } + + for _, test := range tests { + schema.SpaceAndIndexNamesSupported = test.supportNames + + correctCases := []interface{}{"valid", uint(1), uint64(1), uint32(1), + uint16(1), uint8(1), int(1), int64(1), int32(1), int16(1), int8(1), + validIndex, &validIndex} + + t.Run(fmt.Sprintf("NamesSupported_%v", test.supportNames), func(t *testing.T) { + + for _, index := range correctCases { + // The result of ResolveIndex does not depend on the + // SpaceAndIndexNamesSupported field. + indexNo, err := schema.ResolveIndex(index, uint32(1)) + if err != nil { + t.Errorf("unexpected error: %s", err.Error()) + } + if indexNo != uint32(1) { + t.Errorf("unexpected value from ResolveSpace: %d", indexNo) + } + } + + indexNo, err := schema.ResolveIndex("invalid", uint32(1)) + if err == nil { + t.Errorf("expected error") + } + if indexNo != 0 { + t.Errorf("unexpected return value: %d", indexNo) + } + if err.Error() != "space valid has not index with name invalid" { + t.Errorf("expected different error, got %s", err.Error()) + } + + indexNo, err = schema.ResolveIndex("invalid", uint32(2)) + if err == nil { + t.Errorf("expected error") + } + if indexNo != 0 { + t.Errorf("unexpected return value: %d", indexNo) + } + if err.Error() != "there is no space with id 2" { + t.Errorf("expected different error, got %s", err.Error()) + } + + indexNo, err = nilSchema.ResolveIndex("valid", uint32(1)) + if err == nil { + t.Errorf("expected error") + } + if indexNo != 0 { + t.Errorf("unexpected return value: %d", indexNo) + } + if err.Error() != "schema is not loaded" { + t.Errorf("expected different error, got %s", err.Error()) + } + }) + } +} diff --git a/settings/request_test.go b/settings/request_test.go index 2438f81cc..503a40444 100644 --- a/settings/request_test.go +++ b/settings/request_test.go @@ -14,10 +14,11 @@ import ( ) type ValidSchemeResolver struct { + nameUseSupported bool } -func (*ValidSchemeResolver) ResolveSpaceIndex(s, i interface{}) (uint32, uint32, error) { - var spaceNo, indexNo uint32 +func (*ValidSchemeResolver) ResolveSpace(s interface{}) (uint32, error) { + var spaceNo uint32 if s == nil { if s == "_session_settings" { spaceNo = 380 @@ -27,16 +28,28 @@ func (*ValidSchemeResolver) ResolveSpaceIndex(s, i interface{}) (uint32, uint32, } else { spaceNo = 0 } + + return spaceNo, nil +} + +func (*ValidSchemeResolver) ResolveIndex(i interface{}, spaceNo uint32) (uint32, error) { + var indexNo uint32 if i != nil { indexNo = uint32(i.(int)) } else { indexNo = 0 } + return indexNo, nil +} - return spaceNo, indexNo, nil +func (r *ValidSchemeResolver) NamesUseSupported() bool { + return r.nameUseSupported } -var resolver ValidSchemeResolver +var ( + resolverNames = ValidSchemeResolver{true} + resolverNoNames = ValidSchemeResolver{false} +) func TestRequestsAPI(t *testing.T) { tests := []struct { @@ -76,7 +89,10 @@ func TestRequestsAPI(t *testing.T) { var reqBuf bytes.Buffer enc := msgpack.NewEncoder(&reqBuf) - require.Nilf(t, test.req.Body(&resolver, enc), "No errors on fill") + require.Nilf(t, test.req.Body(&resolverNames, enc), + "No errors on fill, names supported") + require.Nilf(t, test.req.Body(&resolverNoNames, enc), + "No errors on fill, names unsupported") } } diff --git a/tarantool_test.go b/tarantool_test.go index 0ad365286..00f3233d1 100644 --- a/tarantool_test.go +++ b/tarantool_test.go @@ -585,7 +585,7 @@ func BenchmarkClientReplaceParallel(b *testing.B) { conn := test_helpers.ConnectWithValidation(b, server, opts) defer conn.Close() - rSpaceNo, _, err := conn.Schema.ResolveSpaceIndex("test_perf", "secondary") + rSpaceNo, err := conn.Schema.ResolveSpace("test_perf") if err != nil { b.Fatalf("Space is not resolved: %s", err.Error()) } @@ -606,9 +606,13 @@ func BenchmarkClientLargeSelectParallel(b *testing.B) { defer conn.Close() schema := conn.Schema - rSpaceNo, rIndexNo, err := schema.ResolveSpaceIndex("test_perf", "secondary") + rSpaceNo, err := schema.ResolveSpace("test_perf") if err != nil { - b.Fatalf("symbolic space and index params not resolved") + b.Fatalf("Space is not resolved: %s", err.Error()) + } + rIndexNo, err := schema.ResolveIndex("secondary", rSpaceNo) + if err != nil { + b.Fatalf("Index is not resolved: %s", err.Error()) } offset, limit := uint32(0), uint32(1000) @@ -2037,27 +2041,36 @@ func TestSchema(t *testing.T) { } var rSpaceNo, rIndexNo uint32 - rSpaceNo, rIndexNo, err = schema.ResolveSpaceIndex(616, 3) - if err != nil || rSpaceNo != 616 || rIndexNo != 3 { - t.Errorf("numeric space and index params not resolved as-is") + rSpaceNo, err = schema.ResolveSpace(616) + if err != nil || rSpaceNo != 616 { + t.Errorf("numeric space param not resolved as-is") } - rSpaceNo, _, err = schema.ResolveSpaceIndex(616, nil) + rIndexNo, err = schema.ResolveIndex(3, rSpaceNo) + if err != nil || rIndexNo != 3 { + t.Errorf("numeric index param not resolved as-is") + } + rSpaceNo, err = schema.ResolveSpace(616) if err != nil || rSpaceNo != 616 { t.Errorf("numeric space param not resolved as-is") } - rSpaceNo, rIndexNo, err = schema.ResolveSpaceIndex("schematest", "secondary") - if err != nil || rSpaceNo != 616 || rIndexNo != 3 { - t.Errorf("symbolic space and index params not resolved") + rSpaceNo, err = schema.ResolveSpace("schematest") + if err != nil || rSpaceNo != 616 { + t.Errorf("symbolic space param not resolved") } - rSpaceNo, _, err = schema.ResolveSpaceIndex("schematest", nil) + rIndexNo, err = schema.ResolveIndex("secondary", rSpaceNo) + if err != nil || rIndexNo != 3 { + t.Errorf("symbolic index param not resolved") + } + rSpaceNo, err = schema.ResolveSpace("schematest") if err != nil || rSpaceNo != 616 { t.Errorf("symbolic space param not resolved") } - _, _, err = schema.ResolveSpaceIndex("schematest22", "secondary") + _, err = schema.ResolveSpace("schematest22") if err == nil { t.Errorf("ResolveSpaceIndex didn't returned error with not existing space name") } - _, _, err = schema.ResolveSpaceIndex("schematest", "secondary22") + rSpaceNo, _ = schema.ResolveSpace("schematest") + _, err = schema.ResolveIndex("secondary22", rSpaceNo) if err == nil { t.Errorf("ResolveSpaceIndex didn't returned error with not existing index name") } @@ -2096,6 +2109,24 @@ func TestSchema_IsNullable(t *testing.T) { } } +func TestSchema_NamesUseSupported(t *testing.T) { + conn := test_helpers.ConnectWithValidation(t, server, opts) + defer conn.Close() + + schema := conn.Schema + + isLess, err := test_helpers.IsTarantoolVersionLess(3, 0, 0) + if err != nil { + t.Errorf("unexpected error: %s", err.Error()) + } + + // NamesUseSupported returns true if Tarantool version is >= 3.0.0 + if schema.NamesUseSupported() == isLess { + t.Errorf("unexpected value for NamesUseSupported, "+ + "expected %v, got %v", !isLess, schema.NamesUseSupported()) + } +} + func TestClientNamed(t *testing.T) { var resp *Response var err error @@ -3302,6 +3333,7 @@ func TestConnectionProtocolInfoSupported(t *testing.T) { iproto.IPROTO_FEATURE_ERROR_EXTENSION, iproto.IPROTO_FEATURE_WATCHERS, iproto.IPROTO_FEATURE_PAGINATION, + iproto.IPROTO_FEATURE_SPACE_AND_INDEX_NAMES, iproto.IPROTO_FEATURE_WATCH_ONCE, }, }) @@ -3420,6 +3452,7 @@ func TestConnectionProtocolInfoUnsupported(t *testing.T) { iproto.IPROTO_FEATURE_ERROR_EXTENSION, iproto.IPROTO_FEATURE_WATCHERS, iproto.IPROTO_FEATURE_PAGINATION, + iproto.IPROTO_FEATURE_SPACE_AND_INDEX_NAMES, iproto.IPROTO_FEATURE_WATCH_ONCE, }, }) diff --git a/test_helpers/utils.go b/test_helpers/utils.go index d2a941775..71793782d 100644 --- a/test_helpers/utils.go +++ b/test_helpers/utils.go @@ -200,6 +200,14 @@ func SkipIfWatchOnceUnsupported(t *testing.T) { SkipIfFeatureUnsupported(t, "watch once", 3, 0, 0) } +// SkipIfNameUsageUnsupported skips test run if Tarantool without support of +// space and index names in requests is used. +func SkipIfNameUsageUnsupported(t *testing.T) { + t.Helper() + + SkipIfFeatureUnsupported(t, "space and index names", 3, 0, 0) +} + // CheckEqualBoxErrors checks equivalence of tarantool.BoxError objects. // // Tarantool errors are not comparable by nature: