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: