tarantool/go-tarantool

Support IPROTO_FEATURE_SPACE_AND_INDEX_NAMES

oleg-jukovec opened this issue · 8 comments

We could use space/index names directly without a resolving to space/index id by schema for Tarantool 3.0.

	 // Using space [index] names instead of identifiers support:
	 // IPROTO_SPACE_NAME and IPROTO_INDEX_NAME fields in IPROTO_SELECT,
	 // IPROTO_UPDATE and IPROTO_DELETE request body;
	 // IPROTO_SPACE_NAME field in IPROTO_INSERT, IPROTO_REPLACE,
	 // IPROTO_UPDATE and IPROTO_UPSERT request body.
	IPROTO_FEATURE_SPACE_AND_INDEX_NAMES =  5

See:

  1. https://github.com/tarantool/tarantool/blame/05751e6c6869579446b34ae67e0605a68fc56b89/src/box/iproto_features.h#L52-L59
  2. https://www.notion.so/Schemafull-IPROTO-cc315ad6bdd641dea66ad854992d8cbf#f4d4b3fa2b3646f1949319866428b6c0
  3. tarantool/tarantool#8146
  4. tarantool/tarantool#8573

So, here is the steps I think I need to do to resolve this issue:

  1. Add IPROTO_FEATURE_SPACE_AND_INDEX_NAMES feature to the list of features in the protocol.go (and update protocol-related tests).
  2. Add new func to the SchemaResolver type that will return true if the IPROTO_FEATURE_SPACE_AND_INDEX_NAMES is supported in the current Tarantool version. Or we can add a new bool field to the Schema structure (and fill it in the loadSchema function).
  3. After that create separate functions-fillers (as example, instead of just fillInsert create fillInsertBySpaceID and fillInsertBySpaceName), that will use different iproto keys (IPROTO_SPACE_ID, IPROTO_SPACE_NAME or IPROTO_INDEX_NAME for select, update and delete requests). But in this case, for example, for deleteRequest we will need 4 functions (because we can pass space_id or space_name as well as index_id or index_name).
    Or we can leave just fillInsert, but let it accept spaceInfo interface{} as a second argument and correct iproto key as fourth. As example, this function

    go-tarantool/request.go

    Lines 53 to 67 in a664c6b

    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 {
    return err
    }
    if err := enc.EncodeUint(uint64(spaceNo)); err != nil {
    return err
    }
    if err := enc.EncodeUint(uint64(iproto.IPROTO_TUPLE)); err != nil {
    return err
    }
    return enc.Encode(tuple)
    }

    will look like this:
func fillInsert(enc *msgpack.Encoder, spaceInfo interface{}, tuple interface{}, iprotoKey iproto.Key) error {
	if err := enc.EncodeMapLen(2); err != nil {
		return err
	}
	if err := enc.EncodeUint(uint64(iprotoKey)); err != nil {
		return err
	}
	if err := enc.Encode(spaceInfo); err != nil {
		return err
	}
	if err := enc.EncodeUint(uint64(iproto.IPROTO_TUPLE)); err != nil {
		return err
	}
	return enc.Encode(tuple)
}

Or, if we want to use enc.EncodeUint or enc.EncodeString instead of enc.Encode, we could create an if statement, that will check iprotoKey:

if (iprotoKey == iproto.IPROTO_SPACE_NAME) { use EncodeString }
else { use EncodeUint }
  1. After that, in the Body call, we check, if req.space (or req.index) has type string, we will call corresponding functions, or pass corresponding keys to the fill... function (without call to the ResolveSpaceIndex). But right now ResolveSpaceIndex resolves both space and index. So make things easier we could split this function into two (resolver for space and index). It will help with the cases, when user provides space_id and index_name (or space_name and index_id), so we need to resolve only one (but not two) argument.
    We also need to check if Tarantool supports IPROTO_FEATURE_SPACE_AND_INDEX_NAMES (given the info from the step 2). If it is not supported, and we have space_name or index_name we will try and resolve them as we do right now:

    go-tarantool/schema.go

    Lines 377 to 468 in a664c6b

    func (schema *Schema) ResolveSpaceIndex(s interface{}, i interface{}) (uint32, uint32, error) {
    var (
    spaceNo, indexNo uint32
    space *Space
    index *Index
    ok bool
    )
    switch s := s.(type) {
    case string:
    if schema == nil {
    return spaceNo, indexNo, 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)
    }
    spaceNo = space.Id
    case uint:
    spaceNo = uint32(s)
    case uint64:
    spaceNo = uint32(s)
    case uint32:
    spaceNo = s
    case uint16:
    spaceNo = uint32(s)
    case uint8:
    spaceNo = uint32(s)
    case int:
    spaceNo = uint32(s)
    case int64:
    spaceNo = uint32(s)
    case int32:
    spaceNo = uint32(s)
    case int16:
    spaceNo = uint32(s)
    case int8:
    spaceNo = uint32(s)
    case Space:
    spaceNo = s.Id
    case *Space:
    spaceNo = s.Id
    default:
    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, indexNo, nil
    }
  2. Update current tests, if needed.
  3. Write new tests, update docs.

Thank you.

Add new func to the SchemaResolver type that will return true if the IPROTO_FEATURE_SPACE_AND_INDEX_NAMES is supported in the current Tarantool version.
But right now ResolveSpaceIndex resolves both space and index. So make things easier we could split this function into two (resolver for space and index).

Nice, let's see the final interface.

Or we can leave just fillInsert, but let it accept spaceInfo interface{} as a second argument and correct iproto key as fourth. As example, this function

I suggest first to try to run a benchmark with the change to see if there will be additional allocations because of this.

I checked the change for Insert function using benchmem and memprofile, and there was no difference in the number of allocations. So I believe there is no additional allocations because of this change.

I checked the change for Insert function using benchmem and memprofile, and there was no difference in the number of allocations. So I believe there is no additional allocations because of this change.

Does compiler with -mm (or something like this) report about escapes to heap?

If not, then make sure that nothing change in that benchmark with the change for select request:
#283

And let's do that way. It looks easier from the code side.

Does compiler with -mm (or something like this) report about escapes to heap?

After running go build -gcflags='-m=3' . |& grep escape I got this output: spaceNo does not escape (spaceNo was passed to the fillInsert function as an interface{} argument). I will also check a benchmark for select request soon.

Does compiler with -mm (or something like this) report about escapes to heap?

After running go build -gcflags='-m=3' . |& grep escape I got this output: spaceNo does not escape (spaceNo was passed to the fillInsert function as an interface{} argument). I will also check a benchmark for select request soon.

Thank you. Ok, let's do in that way than.

Benchmark for select request shows the same 15 allocations (after changes in fillSelect and fillSearch). With the spaceNo does not escape and indexNo does not escape messages from the compiler.