diff --git a/CHANGELOG.md b/CHANGELOG.md index 63ed00b39..6a2fcd51f 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 instead + of their IDs ### Changed diff --git a/README.md b/README.md index 2c7de68c4..828eb148c 100644 --- a/README.md +++ b/README.md @@ -239,6 +239,12 @@ 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 usage of space and index names is supported. + ## Contributing See [the contributing guide](CONTRIBUTING.md) for detailed instructions on how diff --git a/crud/request_test.go b/crud/request_test.go index 4c49a9214..a156f2e15 100644 --- a/crud/request_test.go +++ b/crud/request_test.go @@ -12,6 +12,7 @@ import ( "github.com/tarantool/go-tarantool/v2" "github.com/tarantool/go-tarantool/v2/crud" + "github.com/tarantool/go-tarantool/v2/test_helpers" ) const invalidSpaceMsg = "invalid space" @@ -72,25 +73,38 @@ var expectedOpts = map[string]interface{}{ type ValidSchemeResolver struct { } -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 indexNo, nil +} + +func (*ValidSchemeResolver) NamesUseSupported() bool { + isLess, err := test_helpers.IsTarantoolVersionLess(3, 0, 0) + if err != nil { + return false } - return spaceNo, indexNo, nil + return !isLess } var resolver ValidSchemeResolver diff --git a/example_test.go b/example_test.go index f85f532d0..2cd5f7b79 100644 --- a/example_test.go +++ b/example_test.go @@ -634,6 +634,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..b881399c6 100644 --- a/export_test.go +++ b/export_test.go @@ -5,9 +5,24 @@ import ( "net" "time" + "github.com/tarantool/go-iproto" "github.com/vmihailenco/msgpack/v5" ) +func getSpaceKey(space interface{}) iproto.Key { + if _, ok := space.(string); ok { + return iproto.IPROTO_SPACE_NAME + } + return iproto.IPROTO_SPACE_ID +} + +func getIndexKey(index interface{}) iproto.Key { + if _, ok := index.(string); ok { + return iproto.IPROTO_INDEX_NAME + } + return iproto.IPROTO_INDEX_ID +} + func SslDialContext(ctx context.Context, network, address string, opts SslOpts) (connection net.Conn, err error) { return sslDialContext(ctx, network, address, opts) @@ -25,39 +40,49 @@ 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, space, index interface{}, + offset, limit uint32, iterator Iter, key, after interface{}, fetchPos bool) error { + iprotoSpaceKey := getSpaceKey(space) + iprotoIndexKey := getIndexKey(index) + return fillSelect(enc, space, index, offset, limit, iterator, key, after, + fetchPos, iprotoSpaceKey, iprotoIndexKey) } // 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, space, tuple interface{}) error { + iprotoSpaceKey := getSpaceKey(space) + return fillInsert(enc, space, tuple, iprotoSpaceKey) } // 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, space, tuple interface{}) error { + iprotoSpaceKey := getSpaceKey(space) + return fillInsert(enc, space, tuple, iprotoSpaceKey) } // 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, space, index, key interface{}) error { + iprotoSpaceKey := getSpaceKey(space) + iprotoIndexKey := getIndexKey(index) + return fillDelete(enc, space, index, key, iprotoSpaceKey, iprotoIndexKey) } // 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, space, index, key, ops interface{}) error { + iprotoSpaceKey := getSpaceKey(space) + iprotoIndexKey := getIndexKey(index) + return fillUpdate(enc, space, index, key, ops, iprotoSpaceKey, iprotoIndexKey) } // 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, space, tuple, ops interface{}) error { + iprotoSpaceKey := getSpaceKey(space) + return fillUpsert(enc, space, tuple, ops, iprotoSpaceKey) } // 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..564ab82f6 100644 --- a/request.go +++ b/request.go @@ -12,20 +12,36 @@ 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 { +func fillSearch(enc *msgpack.Encoder, spaceInfo, indexInfo interface{}, + key interface{}, iprotoSpaceKey, iprotoIndexKey iproto.Key) error { + var err error + + if err = enc.EncodeUint(uint64(iprotoSpaceKey)); err != nil { return err } - if err := enc.EncodeUint(uint64(spaceNo)); err != nil { + + if iprotoSpaceKey == iproto.IPROTO_SPACE_NAME { + err = enc.EncodeString(spaceInfo.(string)) + } else { + err = enc.EncodeUint(uint64(spaceInfo.(uint32))) + } + if err != nil { return err } - if err := enc.EncodeUint(uint64(iproto.IPROTO_INDEX_ID)); err != nil { + + if err = enc.EncodeUint(uint64(iprotoIndexKey)); err != nil { return err } - if err := enc.EncodeUint(uint64(indexNo)); err != nil { + if iprotoIndexKey == iproto.IPROTO_INDEX_NAME { + err = enc.EncodeString(indexInfo.(string)) + } else { + err = enc.EncodeUint(uint64(indexInfo.(uint32))) + } + if 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 +66,34 @@ 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 { +func fillInsert(enc *msgpack.Encoder, spaceInfo interface{}, tuple interface{}, + iprotoKey iproto.Key) error { + var err error + if err = enc.EncodeMapLen(2); err != nil { return err } - if err := enc.EncodeUint(uint64(iproto.IPROTO_SPACE_ID)); err != nil { + if err = enc.EncodeUint(uint64(iprotoKey)); err != nil { return err } - if err := enc.EncodeUint(uint64(spaceNo)); err != nil { + + if iprotoKey == iproto.IPROTO_SPACE_NAME { + err = enc.EncodeString(spaceInfo.(string)) + } else { + err = enc.EncodeUint(uint64(spaceInfo.(uint32))) + } + if 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, spaceInfo, indexInfo interface{}, + offset, limit uint32, iterator Iter, key, after interface{}, fetchPos bool, + iprotoSpaceKey, iprotoIndexKey iproto.Key) error { mapLen := 6 if fetchPos { mapLen += 1 @@ -81,7 +107,8 @@ 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, spaceInfo, indexInfo, key, + iprotoSpaceKey, iprotoIndexKey); err != nil { return err } if fetchPos { @@ -112,19 +139,28 @@ 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, spaceInfo, indexInfo, key, ops interface{}, + iprotoSpaceKey, iprotoIndexKey iproto.Key) error { enc.EncodeMapLen(4) - if err := fillSearch(enc, spaceNo, indexNo, key); err != nil { + if err := fillSearch(enc, spaceInfo, indexInfo, key, + iprotoSpaceKey, iprotoIndexKey); 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, spaceInfo, tuple, ops interface{}, + iprotoKey iproto.Key) error { enc.EncodeMapLen(3) - enc.EncodeUint(uint64(iproto.IPROTO_SPACE_ID)) - enc.EncodeUint(uint64(spaceNo)) + enc.EncodeUint(uint64(iprotoKey)) + + if iprotoKey == iproto.IPROTO_SPACE_NAME { + enc.EncodeString(spaceInfo.(string)) + } else { + enc.EncodeUint(uint64(spaceInfo.(uint32))) + } + enc.EncodeUint(uint64(iproto.IPROTO_TUPLE)) if err := enc.Encode(tuple); err != nil { return err @@ -133,9 +169,11 @@ 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, spaceInfo, indexInfo, key interface{}, + iprotoSpaceKey, iprotoIndexKey iproto.Key) error { enc.EncodeMapLen(3) - return fillSearch(enc, spaceNo, indexNo, key) + return fillSearch(enc, spaceInfo, indexInfo, key, + iprotoSpaceKey, iprotoIndexKey) } func fillCall(enc *msgpack.Encoder, functionName string, args interface{}) error { @@ -716,6 +754,38 @@ func encodeSQLBind(enc *msgpack.Encoder, from interface{}) error { return nil } +type resolveResponse struct { + name string + id uint32 +} + +func resolveSpace(res SchemaResolver, + spaceInfo interface{}) (resolveResponse, iproto.Key, error) { + if res.NamesUseSupported() { + if spaceName, ok := spaceInfo.(string); ok { + return resolveResponse{name: spaceName}, iproto.IPROTO_SPACE_NAME, nil + } + } + spaceNo, err := res.ResolveSpace(spaceInfo) + return resolveResponse{id: spaceNo}, iproto.IPROTO_SPACE_ID, err +} + +func resolveIndex(res SchemaResolver, indexInfo, + spaceNo interface{}) (resolveResponse, iproto.Key, error) { + if res.NamesUseSupported() { + if indexName, ok := indexInfo.(string); ok { + return resolveResponse{name: indexName}, iproto.IPROTO_INDEX_NAME, nil + } + // indexInfo is not a string, so spaceNo will not be used in the + // ResolveIndex call. + indexNo, err := res.ResolveIndex(indexInfo, 0) + return resolveResponse{id: indexNo}, iproto.IPROTO_INDEX_ID, err + } + // If names usage is not supported, spaceNo is returned as uint32. + indexNo, err := res.ResolveIndex(indexInfo, spaceNo.(uint32)) + return resolveResponse{id: indexNo}, iproto.IPROTO_INDEX_ID, err +} + // Request is an interface that provides the necessary data to create a request // that will be sent to a tarantool instance. type Request interface { @@ -948,13 +1018,26 @@ 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) + spaceResp, iprotoSpaceKey, err := resolveSpace(res, req.space) + if err != nil { + return err + } + var spaceNo interface{} = spaceResp.id + indexResp, iprotoIndexKey, err := resolveIndex(res, req.index, spaceNo) if err != nil { return err } + var indexNo interface{} = indexResp.id + + if iprotoSpaceKey == iproto.IPROTO_SPACE_NAME { + spaceNo = spaceResp.name + } + if iprotoIndexKey == iproto.IPROTO_INDEX_NAME { + indexNo = indexResp.name + } return fillSelect(enc, spaceNo, indexNo, req.offset, req.limit, req.iterator, - req.key, req.after, req.fetchPos) + req.key, req.after, req.fetchPos, iprotoSpaceKey, iprotoIndexKey) } // Context sets a passed context to the request. @@ -993,12 +1076,17 @@ 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) + spaceResp, iprotoSpaceKey, err := resolveSpace(res, req.space) if err != nil { return err } + var spaceNo interface{} = spaceResp.id - return fillInsert(enc, spaceNo, req.tuple) + if iprotoSpaceKey == iproto.IPROTO_SPACE_NAME { + spaceNo = spaceResp.name + } + + return fillInsert(enc, spaceNo, req.tuple, iprotoSpaceKey) } // Context sets a passed context to the request. @@ -1037,12 +1125,17 @@ 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) + spaceResp, iprotoSpaceKey, err := resolveSpace(res, req.space) if err != nil { return err } + var spaceNo interface{} = spaceResp.id + + if iprotoSpaceKey == iproto.IPROTO_SPACE_NAME { + spaceNo = spaceResp.name + } - return fillInsert(enc, spaceNo, req.tuple) + return fillInsert(enc, spaceNo, req.tuple, iprotoSpaceKey) } // Context sets a passed context to the request. @@ -1088,12 +1181,25 @@ 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) + spaceResp, iprotoSpaceKey, err := resolveSpace(res, req.space) if err != nil { return err } + var spaceNo interface{} = spaceResp.id + indexResp, iprotoIndexKey, err := resolveIndex(res, req.index, spaceNo) + if err != nil { + return err + } + var indexNo interface{} = indexResp.id + + if iprotoSpaceKey == iproto.IPROTO_SPACE_NAME { + spaceNo = spaceResp.name + } + if iprotoIndexKey == iproto.IPROTO_INDEX_NAME { + indexNo = indexResp.name + } - return fillDelete(enc, spaceNo, indexNo, req.key) + return fillDelete(enc, spaceNo, indexNo, req.key, iprotoSpaceKey, iprotoIndexKey) } // Context sets a passed context to the request. @@ -1150,12 +1256,26 @@ 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) + spaceResp, iprotoSpaceKey, err := resolveSpace(res, req.space) + if err != nil { + return err + } + var spaceNo interface{} = spaceResp.id + indexResp, iprotoIndexKey, err := resolveIndex(res, req.index, spaceNo) if err != nil { return err } + var indexNo interface{} = indexResp.id + + if iprotoSpaceKey == iproto.IPROTO_SPACE_NAME { + spaceNo = spaceResp.name + } + if iprotoIndexKey == iproto.IPROTO_INDEX_NAME { + indexNo = indexResp.name + } - return fillUpdate(enc, spaceNo, indexNo, req.key, req.ops) + return fillUpdate(enc, spaceNo, indexNo, req.key, req.ops, + iprotoSpaceKey, iprotoIndexKey) } // Context sets a passed context to the request. @@ -1205,12 +1325,17 @@ 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) + spaceResp, iprotoSpaceKey, err := resolveSpace(res, req.space) if err != nil { return err } + var spaceNo interface{} = spaceResp.id + + if iprotoSpaceKey == iproto.IPROTO_SPACE_NAME { + spaceNo = spaceResp.name + } - return fillUpsert(enc, spaceNo, req.tuple, req.ops) + return fillUpsert(enc, spaceNo, req.tuple, req.ops, iprotoSpaceKey) } // Context sets a passed context to the request. diff --git a/request_test.go b/request_test.go index 6d08533d3..e2bce4114 100644 --- a/request_test.go +++ b/request_test.go @@ -12,19 +12,20 @@ import ( "github.com/vmihailenco/msgpack/v5" . "github.com/tarantool/go-tarantool/v2" + "github.com/tarantool/go-tarantool/v2/test_helpers" ) 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 @@ -41,25 +42,38 @@ var validProtocolInfo ProtocolInfo = ProtocolInfo{ type ValidSchemeResolver struct { } -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 indexNo, nil +} + +func (*ValidSchemeResolver) NamesUseSupported() bool { + isLess, err := test_helpers.IsTarantoolVersionLess(3, 0, 0) + if err != nil { + return false } - return spaceNo, indexNo, nil + return !isLess } var resolver ValidSchemeResolver @@ -342,6 +356,41 @@ func TestSelectRequestDefaultValues(t *testing.T) { assertBodyEqual(t, refBuf.Bytes(), req) } +func TestSelectRequestSpaceByName(t *testing.T) { + test_helpers.SkipIfNameUsageUnsupported(t) + + var refBuf bytes.Buffer + + refEnc := msgpack.NewEncoder(&refBuf) + err := RefImplSelectBody(refEnc, "valid", defaultIndex, 0, 0xFFFFFFFF, + IterAll, []interface{}{}, nil, false) + if err != nil { + t.Errorf("An unexpected RefImplSelectBody() error %q", err.Error()) + return + } + + req := NewSelectRequest("valid") + assertBodyEqual(t, refBuf.Bytes(), req) +} + +func TestSelectRequestIndexByName(t *testing.T) { + test_helpers.SkipIfNameUsageUnsupported(t) + + var refBuf bytes.Buffer + + refEnc := msgpack.NewEncoder(&refBuf) + err := RefImplSelectBody(refEnc, defaultSpace, "valid", 0, 0xFFFFFFFF, + IterAll, []interface{}{}, nil, false) + if err != nil { + t.Errorf("An unexpected RefImplSelectBody() error %q", err.Error()) + return + } + + req := NewSelectRequest(validSpace) + req.Index("valid") + assertBodyEqual(t, refBuf.Bytes(), req) +} + func TestSelectRequestDefaultIteratorEqIfKey(t *testing.T) { var refBuf bytes.Buffer key := []interface{}{uint(18)} @@ -438,6 +487,22 @@ func TestInsertRequestDefaultValues(t *testing.T) { assertBodyEqual(t, refBuf.Bytes(), req) } +func TestInsertRequestSpaceByName(t *testing.T) { + test_helpers.SkipIfNameUsageUnsupported(t) + + var refBuf bytes.Buffer + + refEnc := msgpack.NewEncoder(&refBuf) + err := RefImplInsertBody(refEnc, "valid", []interface{}{}) + if err != nil { + t.Errorf("An unexpected RefImplInsertBody() error: %q", err.Error()) + return + } + + req := NewInsertRequest("valid") + assertBodyEqual(t, refBuf.Bytes(), req) +} + func TestInsertRequestSetters(t *testing.T) { tuple := []interface{}{uint(24)} var refBuf bytes.Buffer @@ -468,6 +533,22 @@ func TestReplaceRequestDefaultValues(t *testing.T) { assertBodyEqual(t, refBuf.Bytes(), req) } +func TestReplaceRequestSpaceByName(t *testing.T) { + test_helpers.SkipIfNameUsageUnsupported(t) + + var refBuf bytes.Buffer + + refEnc := msgpack.NewEncoder(&refBuf) + err := RefImplReplaceBody(refEnc, "valid", []interface{}{}) + if err != nil { + t.Errorf("An unexpected RefImplReplaceBody() error: %q", err.Error()) + return + } + + req := NewReplaceRequest("valid") + assertBodyEqual(t, refBuf.Bytes(), req) +} + func TestReplaceRequestSetters(t *testing.T) { tuple := []interface{}{uint(99)} var refBuf bytes.Buffer @@ -498,6 +579,39 @@ func TestDeleteRequestDefaultValues(t *testing.T) { assertBodyEqual(t, refBuf.Bytes(), req) } +func TestDeleteRequestSpaceByName(t *testing.T) { + test_helpers.SkipIfNameUsageUnsupported(t) + + var refBuf bytes.Buffer + + refEnc := msgpack.NewEncoder(&refBuf) + err := RefImplDeleteBody(refEnc, "valid", defaultIndex, []interface{}{}) + if err != nil { + t.Errorf("An unexpected RefImplDeleteBody() error: %q", err.Error()) + return + } + + req := NewDeleteRequest("valid") + assertBodyEqual(t, refBuf.Bytes(), req) +} + +func TestDeleteRequestIndexByName(t *testing.T) { + test_helpers.SkipIfNameUsageUnsupported(t) + + var refBuf bytes.Buffer + + refEnc := msgpack.NewEncoder(&refBuf) + err := RefImplDeleteBody(refEnc, defaultSpace, "valid", []interface{}{}) + if err != nil { + t.Errorf("An unexpected RefImplDeleteBody() error: %q", err.Error()) + return + } + + req := NewDeleteRequest(defaultSpace) + req.Index("valid") + assertBodyEqual(t, refBuf.Bytes(), req) +} + func TestDeleteRequestSetters(t *testing.T) { key := []interface{}{uint(923)} var refBuf bytes.Buffer @@ -529,6 +643,39 @@ func TestUpdateRequestDefaultValues(t *testing.T) { assertBodyEqual(t, refBuf.Bytes(), req) } +func TestUpdateRequestSpaceByName(t *testing.T) { + test_helpers.SkipIfNameUsageUnsupported(t) + + var refBuf bytes.Buffer + + refEnc := msgpack.NewEncoder(&refBuf) + err := RefImplUpdateBody(refEnc, "valid", defaultIndex, []interface{}{}, []Op{}) + if err != nil { + t.Errorf("An unexpected RefImplUpdateBody() error: %q", err.Error()) + return + } + + req := NewUpdateRequest("valid") + assertBodyEqual(t, refBuf.Bytes(), req) +} + +func TestUpdateRequestIndexByName(t *testing.T) { + test_helpers.SkipIfNameUsageUnsupported(t) + + var refBuf bytes.Buffer + + refEnc := msgpack.NewEncoder(&refBuf) + err := RefImplUpdateBody(refEnc, defaultSpace, "valid", []interface{}{}, []Op{}) + if err != nil { + t.Errorf("An unexpected RefImplUpdateBody() error: %q", err.Error()) + return + } + + req := NewUpdateRequest(defaultSpace) + req.Index("valid") + assertBodyEqual(t, refBuf.Bytes(), req) +} + func TestUpdateRequestSetters(t *testing.T) { key := []interface{}{uint(44)} refOps, reqOps := getTestOps() @@ -562,6 +709,22 @@ func TestUpsertRequestDefaultValues(t *testing.T) { assertBodyEqual(t, refBuf.Bytes(), req) } +func TestUpsertRequestSpaceByName(t *testing.T) { + test_helpers.SkipIfNameUsageUnsupported(t) + + var refBuf bytes.Buffer + + refEnc := msgpack.NewEncoder(&refBuf) + err := RefImplUpsertBody(refEnc, "valid", []interface{}{}, []Op{}) + if err != nil { + t.Errorf("An unexpected RefImplUpsertBody() error: %q", err.Error()) + return + } + + req := NewUpsertRequest("valid") + assertBodyEqual(t, refBuf.Bytes(), req) +} + func TestUpsertRequestSetters(t *testing.T) { tuple := []interface{}{uint(64)} refOps, reqOps := getTestOps() diff --git a/schema.go b/schema.go index 714f51c5b..43fdba424 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 the use of space and index names instead of + // IDs supported. It depends on Tarantool version, if it supports + // iproto.IPROTO_FEATURE_SPACE_AND_INDEX_NAMES. + 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,6 +381,8 @@ func (conn *Connection) loadSchema() (err error) { } } + checkNamesSupported(conn, schema) + conn.lockShards() conn.Schema = schema conn.unlockShards() @@ -371,24 +390,22 @@ func (conn *Connection) loadSchema() (err error) { 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 +436,69 @@ 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 { + if schema == nil { + return false + } + return schema.SpaceAndIndexNamesSupported } diff --git a/settings/request_test.go b/settings/request_test.go index 2438f81cc..c890c71a4 100644 --- a/settings/request_test.go +++ b/settings/request_test.go @@ -16,8 +16,8 @@ import ( type ValidSchemeResolver struct { } -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,13 +27,22 @@ 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 (*ValidSchemeResolver) NamesUseSupported() bool { + return false } var resolver ValidSchemeResolver diff --git a/tarantool_test.go b/tarantool_test.go index 0ad365286..987a71e93 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") + } + rIndexNo, err = schema.ResolveIndex("secondary", rSpaceNo) + if err != nil || rIndexNo != 3 { + t.Errorf("symbolic index param not resolved") } - rSpaceNo, _, err = schema.ResolveSpaceIndex("schematest", nil) + 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") } @@ -3302,6 +3315,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 +3434,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: