Tarantool development patches archive
 help / color / mirror / Atom feed
* [tarantool-patches] [PATCH 0/5] Expose ephemeral spaces into Lua
@ 2018-07-12 11:16 imeevma
  2018-07-12 11:16 ` [tarantool-patches] [PATCH 1/5] Create new methods for ephemeral spaces imeevma
                   ` (5 more replies)
  0 siblings, 6 replies; 12+ messages in thread
From: imeevma @ 2018-07-12 11:16 UTC (permalink / raw)
  To: tarantool-patches

Branch: https://github.com/tarantool/tarantool/compare/imeevma/gh-3375-lua-expose-ephemeral-spaces
Issue: https://github.com/tarantool/tarantool/issues/3375

Mergen Imeev (5):
  Create new methods for ephemeral spaces
  Move some decode functions from alter.cc
  Ephemeral space creation and deletion in Lua
  Primary index for ephemeral spaces
  Methods for ephemeral space and its index

 src/box/alter.cc                  |  203 +-
 src/box/box.cc                    |  170 +
 src/box/box.h                     |   51 +
 src/box/index.cc                  |  172 +
 src/box/index.h                   |  140 +
 src/box/index_def.c               |  102 +
 src/box/index_def.h               |   26 +
 src/box/lua/info.h                |    4 -
 src/box/lua/schema.lua            |  321 ++
 src/box/lua/space.cc              |  669 +++
 src/box/memtx_space.c             |  210 +-
 src/box/memtx_tree.c              |    5 +
 src/box/space.h                   |   17 -
 src/box/space_def.c               |  150 +
 src/box/space_def.h               |   15 +
 src/box/sql.c                     |   10 +-
 src/box/sysview_engine.c          |   22 -
 src/box/vinyl.c                   |   22 -
 test/box/ephemeral_space.result   | 8461 +++++++++++++++++++++++++++++++++++++
 test/box/ephemeral_space.test.lua | 1864 ++++++++
 test/engine/iterator.result       |    2 +-
 21 files changed, 12335 insertions(+), 301 deletions(-)
 create mode 100644 test/box/ephemeral_space.result
 create mode 100644 test/box/ephemeral_space.test.lua

-- 
2.7.4

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [tarantool-patches] [PATCH 1/5] Create new methods for ephemeral spaces
  2018-07-12 11:16 [tarantool-patches] [PATCH 0/5] Expose ephemeral spaces into Lua imeevma
@ 2018-07-12 11:16 ` imeevma
  2018-07-13 16:32   ` [tarantool-patches] " Vladislav Shpilevoy
  2018-07-12 11:16 ` [tarantool-patches] [PATCH 2/5] Move some decode functions from alter.cc imeevma
                   ` (4 subsequent siblings)
  5 siblings, 1 reply; 12+ messages in thread
From: imeevma @ 2018-07-12 11:16 UTC (permalink / raw)
  To: tarantool-patches

Up to this patch any space had two additional methods
that were methods of ephemeral spaces. In this patch
these methods deleted from vtab and from now on
ephemeral spaces are spaces with special vtab.

Part of #3375.
---
 src/box/box.cc           | 108 ++++++++++++++++++++++++
 src/box/box.h            |  42 ++++++++++
 src/box/memtx_space.c    | 210 ++++++++++++++++++++++++++++++++++++++++-------
 src/box/memtx_tree.c     |   5 ++
 src/box/space.h          |  17 ----
 src/box/sql.c            |  10 +--
 src/box/sysview_engine.c |  22 -----
 src/box/vinyl.c          |  22 -----
 8 files changed, 339 insertions(+), 97 deletions(-)

diff --git a/src/box/box.cc b/src/box/box.cc
index 481c2dd..795e3ee 100644
--- a/src/box/box.cc
+++ b/src/box/box.cc
@@ -1170,6 +1170,114 @@ box_upsert(uint32_t space_id, uint32_t index_id, const char *tuple,
 	return box_process1(&request, result);
 }
 
+int
+box_ephemeral_insert(struct space *space, const char *tuple,
+		     const char *tuple_end, box_tuple_t **result)
+{
+	mp_tuple_assert(tuple, tuple_end);
+	struct request request;
+	memset(&request, 0, sizeof(request));
+	request.type = IPROTO_INSERT;
+	request.space_id = space->def->id;
+	request.tuple = tuple;
+	request.tuple_end = tuple_end;
+	if(result != NULL)
+		return space_execute_dml(space, NULL, &request, result);
+	struct tuple *temp_result;
+	return space_execute_dml(space, NULL, &request, &temp_result);
+}
+
+int
+box_ephemeral_replace(struct space *space, const char *tuple,
+		      const char *tuple_end, box_tuple_t **result)
+{
+	mp_tuple_assert(tuple, tuple_end);
+	struct request request;
+	memset(&request, 0, sizeof(request));
+	request.type = IPROTO_REPLACE;
+	request.space_id = space->def->id;
+	request.tuple = tuple;
+	request.tuple_end = tuple_end;
+	if(result != NULL)
+		return space_execute_dml(space, NULL, &request, result);
+	struct tuple *temp_result;
+	return space_execute_dml(space, NULL, &request, &temp_result);
+}
+
+int
+box_ephemeral_delete(struct space *space, uint32_t index_id, const char *key,
+		     const char *key_end, box_tuple_t **result)
+{
+	mp_tuple_assert(key, key_end);
+	struct request request;
+	memset(&request, 0, sizeof(request));
+	request.type = IPROTO_DELETE;
+	request.space_id = space->def->id;
+	request.index_id = index_id;
+	request.key = key;
+	request.key_end = key_end;
+	if(result != NULL)
+		return space_execute_dml(space, NULL, &request, result);
+	struct tuple *temp_result;
+	int rc = space_execute_dml(space, NULL, &request, &temp_result);
+	if(temp_result != NULL)
+		tuple_unref(temp_result);
+	return rc;
+}
+
+int
+box_ephemeral_update(struct space *space, uint32_t index_id, const char *key,
+		     const char *key_end, const char *ops, const char *ops_end,
+		     int index_base, box_tuple_t **result)
+{
+	mp_tuple_assert(key, key_end);
+	mp_tuple_assert(ops, ops_end);
+	struct request request;
+	memset(&request, 0, sizeof(request));
+	request.type = IPROTO_UPDATE;
+	request.space_id = space->def->id;
+	request.index_id = index_id;
+	request.key = key;
+	request.key_end = key_end;
+	request.index_base = index_base;
+	/** Legacy: in case of update, ops are passed in in request tuple */
+	request.tuple = ops;
+	request.tuple_end = ops_end;
+	if(result != NULL)
+		return space_execute_dml(space, NULL, &request, result);
+	struct tuple *temp_result;
+	int rc = space_execute_dml(space, NULL, &request, &temp_result);
+	if(temp_result != NULL)
+		tuple_unref(temp_result);
+	return rc;
+}
+
+int
+box_ephemeral_upsert(struct space *space, uint32_t index_id, const char *tuple,
+		     const char *tuple_end, const char *ops,
+		     const char *ops_end, int index_base, box_tuple_t **result)
+{
+	mp_tuple_assert(ops, ops_end);
+	mp_tuple_assert(tuple, tuple_end);
+	struct request request;
+	memset(&request, 0, sizeof(request));
+	request.type = IPROTO_UPSERT;
+	request.space_id = space->def->id;
+	request.index_id = index_id;
+	request.ops = ops;
+	request.ops_end = ops_end;
+	request.tuple = tuple;
+	request.tuple_end = tuple_end;
+	request.index_base = index_base;
+	if(result != NULL)
+		return space_execute_dml(space, NULL, &request, result);
+	struct tuple *temp_result;
+	int rc = space_execute_dml(space, NULL, &request, &temp_result);
+	if(temp_result != NULL)
+		tuple_unref(temp_result);
+	return rc;
+}
+
 /**
  * Trigger space truncation by bumping a counter
  * in _truncate space.
diff --git a/src/box/box.h b/src/box/box.h
index d879847..a00a842 100644
--- a/src/box/box.h
+++ b/src/box/box.h
@@ -402,6 +402,48 @@ int
 box_process1(struct request *request, box_tuple_t **result);
 
 /**
+ * Used to prepare request for inserting tuple into
+ * ephemeral space and call box_process_rw().
+ */
+int
+box_ephemeral_insert(struct space *space, const char *tuple,
+		     const char *tuple_end, box_tuple_t **result);
+
+/**
+ * Used to prepare request for deleting tuple from
+ * ephemeral space and call box_process_rw().
+ */
+int
+box_ephemeral_replace(struct space *space, const char *tuple,
+		      const char *tuple_end, box_tuple_t **result);
+
+/**
+ * Used to prepare request for replacong tuple in
+ * ephemeral space and call box_process_rw().
+ */
+int
+box_ephemeral_delete(struct space *space, uint32_t index_id, const char *key,
+		     const char *key_end, box_tuple_t **result);
+
+/**
+ * Used to prepare request for updating tuple from
+ * ephemeral space and call box_process_rw().
+ */
+int
+box_ephemeral_update(struct space *space, uint32_t index_id, const char *key,
+		     const char *key_end, const char *ops, const char *ops_end,
+		     int index_base, box_tuple_t **result);
+
+/**
+ * Used to prepare request for inserting tuple into
+ * ephemeral space or updating tuple from ephemeral
+ * space and call box_process_rw().
+ */
+int
+box_ephemeral_upsert(struct space *space, uint32_t index_id, const char *tuple,
+		     const char *tuple_end, const char *ops,
+		     const char *ops_end, int index_base, box_tuple_t **result);
+/**
  * Execute request on given space.
  *
  * \param request Request to be executed
diff --git a/src/box/memtx_space.c b/src/box/memtx_space.c
index b94c4ab..e4781e3 100644
--- a/src/box/memtx_space.c
+++ b/src/box/memtx_space.c
@@ -543,56 +543,51 @@ memtx_space_execute_upsert(struct space *space, struct txn *txn,
 }
 
 /**
- * This function simply creates new memtx tuple, refs it and calls space's
- * replace function. In constrast to original memtx_space_execute_replace(), it
- * doesn't handle any transaction routine.
- * Ephemeral spaces shouldn't be involved in transaction routine, since
- * they are used only for internal purposes. Moreover, ephemeral spaces
- * can be created and destroyed within one transaction and rollback of already
- * destroyed space may lead to undefined behaviour. For this reason it
- * doesn't take txn as an argument.
+ * This function creates new memtx tuple, refs it and calls
+ * ephemeral space's replace function.
+ *
+ * This function isn't involved in any transaction routine.
  */
 static int
-memtx_space_ephemeral_replace(struct space *space, const char *tuple,
-				      const char *tuple_end)
+memtx_space_ephemeral_replace(struct space *space, struct txn *txn,
+			      struct request *request, struct tuple **result)
 {
+	(void)txn;
 	struct memtx_space *memtx_space = (struct memtx_space *)space;
-	struct tuple *new_tuple = memtx_tuple_new(space->format, tuple,
-						  tuple_end);
+	enum dup_replace_mode mode = dup_replace_mode(request->type);
+	struct tuple *new_tuple = memtx_tuple_new(space->format, request->tuple,
+						  request->tuple_end);
 	if (new_tuple == NULL)
 		return -1;
 	tuple_ref(new_tuple);
 	struct tuple *old_tuple = NULL;
 	if (memtx_space->replace(space, old_tuple, new_tuple,
-				 DUP_REPLACE_OR_INSERT, &old_tuple) != 0) {
+				 mode, &old_tuple) != 0) {
 		tuple_unref(new_tuple);
 		return -1;
 	}
+	*result = new_tuple;
 	if (old_tuple != NULL)
 		tuple_unref(old_tuple);
 	return 0;
 }
 
 /**
- * Delete tuple with given key from primary index. Tuple checking is omitted
- * due to the ability of ephemeral spaces to hold nulls in primary key.
- * Generally speaking, it is not correct behaviour owing to ambiguity when
- * fetching/deleting tuple from space with several tuples containing
- * nulls in PK. On the other hand, ephemeral spaces are used only for internal
- * needs, so if it is guaranteed that no such situation occur
- * (when several tuples with nulls in PK exist), it is OK to allow
- * insertion nulls in PK.
+ * Delete tuple with given key from primary index of
+ * ephemeral space.
  *
- * Similarly to ephemeral replace function,
- * it isn't involved in any transaction routine.
+ * This function isn't involved in any transaction routine.
  */
 static int
-memtx_space_ephemeral_delete(struct space *space, const char *key)
+memtx_space_ephemeral_delete(struct space *space, struct txn *txn,
+			     struct request *request, struct tuple **result)
 {
+	(void)txn;
 	struct memtx_space *memtx_space = (struct memtx_space *)space;
 	struct index *primary_index = space_index(space, 0 /* primary index*/);
 	if (primary_index == NULL)
 		return -1;
+	const char *key = request->key;
 	uint32_t part_count = mp_decode_array(&key);
 	struct tuple *old_tuple;
 	if (index_get(primary_index, key, part_count, &old_tuple) != 0)
@@ -601,7 +596,138 @@ memtx_space_ephemeral_delete(struct space *space, const char *key)
 	    memtx_space->replace(space, old_tuple, NULL,
 				 DUP_REPLACE, &old_tuple) != 0)
 		return -1;
-	tuple_unref(old_tuple);
+	*result = old_tuple;
+	return 0;
+}
+
+/**
+ * Replace tuple with given key from primary index of
+ * ephemeral space with new tuple.
+ *
+ * This function isn't involved in any transaction routine.
+ */
+static int
+memtx_space_ephemeral_update(struct space *space, struct txn *txn,
+			     struct request *request, struct tuple **result)
+{
+	(void)txn;
+	struct memtx_space *memtx_space = (struct memtx_space *)space;
+	struct index *pk = space_index(space, 0 /* primary index*/);
+	if (pk == NULL)
+		return -1;
+	const char *key = request->key;
+	uint32_t part_count = mp_decode_array(&key);
+	struct tuple *old_tuple;
+	if (exact_key_validate(pk->def->key_def, key, part_count) != 0)
+		return -1;
+	if (index_get(pk, key, part_count, &old_tuple) != 0)
+		return -1;
+	if (old_tuple == NULL) {
+		*result = NULL;
+		return 0;
+	}
+
+	/* Update the tuple; legacy, request ops are in request->tuple */
+	uint32_t new_size = 0, bsize;
+	const char *old_data = tuple_data_range(old_tuple, &bsize);
+	const char *new_data =
+		tuple_update_execute(region_aligned_alloc_cb, &fiber()->gc,
+				     request->tuple, request->tuple_end,
+				     old_data, old_data + bsize,
+				     &new_size, request->index_base, NULL);
+	if (new_data == NULL)
+		return -1;
+	struct tuple *new_tuple = memtx_tuple_new(space->format, new_data,
+						  new_data + new_size);
+	if (new_tuple == NULL)
+		return -1;
+	tuple_ref(new_tuple);
+	if (memtx_space->replace(space, old_tuple, new_tuple,
+				 DUP_REPLACE, &old_tuple) != 0) {
+		tuple_unref(new_tuple);
+		return -1;
+	}
+	*result = new_tuple;
+	return 0;
+}
+
+/**
+ * As ephemeral spaces doesn't works with transactions
+ * upsert becomes almost the same as update but it
+ * doesn't return any result.
+ *
+ * This function isn't involved in any transaction routine.
+ */
+static int
+memtx_space_ephemeral_upsert(struct space *space, struct txn *txn,
+			     struct request *request)
+{
+	(void)txn;
+	struct memtx_space *memtx_space = (struct memtx_space *)space;
+	if (tuple_validate_raw(space->format, request->tuple))
+		return -1;
+	struct index *index = index_find_unique(space, 0);
+	if (index == NULL)
+		return -1;
+	uint32_t part_count = index->def->key_def->part_count;
+	uint32_t key_size;
+	const char *key = tuple_extract_key_raw(request->tuple,
+						request->tuple_end,
+						index->def->key_def, &key_size);
+	if (key == NULL)
+		return -1;
+	mp_decode_array(&key);
+	struct tuple *old_tuple;
+	struct tuple *new_tuple;
+	if (index_get(index, key, part_count, &old_tuple) != 0)
+		return -1;
+	if (old_tuple == NULL) {
+		if (tuple_update_check_ops(region_aligned_alloc_cb,
+					   &fiber()->gc, request->ops,
+					   request->ops_end,
+					   request->index_base)) {
+			return -1;
+		}
+		new_tuple = memtx_tuple_new(space->format,
+					    request->tuple,
+					    request->tuple_end);
+		if (new_tuple == NULL)
+			return -1;
+		tuple_ref(new_tuple);
+	} else {
+		uint32_t new_size = 0, bsize;
+		const char *old_data = tuple_data_range(old_tuple, &bsize);
+		uint64_t column_mask = COLUMN_MASK_FULL;
+		const char *new_data =
+			tuple_upsert_execute(region_aligned_alloc_cb,
+					     &fiber()->gc, request->ops,
+					     request->ops_end, old_data,
+					     old_data + bsize, &new_size,
+					     request->index_base, false,
+					     &column_mask);
+		if (new_data == NULL)
+			return -1;
+		new_tuple = memtx_tuple_new(space->format, new_data,
+					    new_data + new_size);
+		if (new_tuple == NULL)
+			return -1;
+		tuple_ref(new_tuple);
+		if (!key_update_can_be_skipped(index->def->key_def->column_mask,
+					       column_mask) &&
+		    tuple_compare(old_tuple, new_tuple,
+				  index->def->key_def) != 0) {
+			diag_set(ClientError, ER_CANT_UPDATE_PRIMARY_KEY,
+				 index->def->name, space_name(space));
+			diag_log();
+			tuple_unref(new_tuple);
+			old_tuple = NULL;
+			new_tuple = NULL;
+		}
+	}
+	if (new_tuple != NULL &&
+	    memtx_space->replace(space, old_tuple, new_tuple,
+				 DUP_REPLACE_OR_INSERT, &old_tuple) != 0)
+		return -1;
 	return 0;
 }
 
@@ -842,10 +968,7 @@ memtx_init_system_space(struct space *space)
 }
 
 static void
-memtx_init_ephemeral_space(struct space *space)
-{
-	memtx_space_add_primary_key(space);
-}
+memtx_init_ephemeral_space(struct space *space);
 
 static int
 memtx_space_build_index(struct space *src_space, struct index *new_index,
@@ -943,8 +1066,6 @@ static const struct space_vtab memtx_space_vtab = {
 	/* .execute_delete = */ memtx_space_execute_delete,
 	/* .execute_update = */ memtx_space_execute_update,
 	/* .execute_upsert = */ memtx_space_execute_upsert,
-	/* .ephemeral_replace = */ memtx_space_ephemeral_replace,
-	/* .ephemeral_delete = */ memtx_space_ephemeral_delete,
 	/* .init_system_space = */ memtx_init_system_space,
 	/* .init_ephemeral_space = */ memtx_init_ephemeral_space,
 	/* .check_index_def = */ memtx_space_check_index_def,
@@ -957,6 +1078,33 @@ static const struct space_vtab memtx_space_vtab = {
 	/* .prepare_alter = */ memtx_space_prepare_alter,
 };
 
+static const struct space_vtab memtx_space_ephemeral_vtab = {
+	/* .destroy = */ memtx_space_destroy,
+	/* .bsize = */ memtx_space_bsize,
+	/* .apply_initial_join_row = */ memtx_space_apply_initial_join_row,
+	/* .execute_replace = */ memtx_space_ephemeral_replace,
+	/* .execute_delete = */ memtx_space_ephemeral_delete,
+	/* .execute_update = */ memtx_space_ephemeral_update,
+	/* .execute_upsert = */ memtx_space_ephemeral_upsert,
+	/* .init_system_space = */ memtx_init_system_space,
+	/* .init_ephemeral_space = */ memtx_init_ephemeral_space,
+	/* .check_index_def = */ memtx_space_check_index_def,
+	/* .create_index = */ memtx_space_create_index,
+	/* .add_primary_key = */ memtx_space_add_primary_key,
+	/* .drop_primary_key = */ memtx_space_drop_primary_key,
+	/* .check_format  = */ memtx_space_check_format,
+	/* .build_index = */ memtx_space_build_index,
+	/* .swap_index = */ generic_space_swap_index,
+	/* .prepare_alter = */ memtx_space_prepare_alter,
+};
+
+static void
+memtx_init_ephemeral_space(struct space *space)
+{
+	space->vtab = &memtx_space_ephemeral_vtab;
+	memtx_space_add_primary_key(space);
+}
+
 struct space *
 memtx_space_new(struct memtx_engine *memtx,
 		struct space_def *def, struct rlist *key_list)
diff --git a/src/box/memtx_tree.c b/src/box/memtx_tree.c
index f851fb8..b0634f9 100644
--- a/src/box/memtx_tree.c
+++ b/src/box/memtx_tree.c
@@ -461,6 +461,11 @@ memtx_tree_index_replace(struct index *base, struct tuple *old_tuple,
 			memtx_tree_delete(&index->tree, new_tuple);
 			if (dup_tuple)
 				memtx_tree_insert(&index->tree, dup_tuple, 0);
+			if (base->def->space_id == 0) {
+				diag_set(ClientError, errcode, base->def->name,
+					 "ephemeral");
+				return -1;
+			}
 			struct space *sp = space_cache_find(base->def->space_id);
 			if (sp != NULL)
 				diag_set(ClientError, errcode, base->def->name,
diff --git a/src/box/space.h b/src/box/space.h
index adf06b7..60fa603 100644
--- a/src/box/space.h
+++ b/src/box/space.h
@@ -67,10 +67,6 @@ struct space_vtab {
 			      struct request *, struct tuple **result);
 	int (*execute_upsert)(struct space *, struct txn *, struct request *);
 
-	int (*ephemeral_replace)(struct space *, const char *, const char *);
-
-	int (*ephemeral_delete)(struct space *, const char *);
-
 	void (*init_system_space)(struct space *);
 	/**
 	 * Initialize an ephemeral space instance.
@@ -310,19 +306,6 @@ int
 space_execute_dml(struct space *space, struct txn *txn,
 		  struct request *request, struct tuple **result);
 
-static inline int
-space_ephemeral_replace(struct space *space, const char *tuple,
-			const char *tuple_end)
-{
-	return space->vtab->ephemeral_replace(space, tuple, tuple_end);
-}
-
-static inline int
-space_ephemeral_delete(struct space *space, const char *key)
-{
-	return space->vtab->ephemeral_delete(space, key);
-}
-
 /**
  * Generic implementation of space_vtab::swap_index
  * that simply swaps the two indexes in index maps.
diff --git a/src/box/sql.c b/src/box/sql.c
index fdce224..5c1da72 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -451,7 +451,7 @@ int tarantoolSqlite3EphemeralInsert(struct space *space, const char *tuple,
 {
 	assert(space != NULL);
 	mp_tuple_assert(tuple, tuple_end);
-	if (space_ephemeral_replace(space, tuple, tuple_end) != 0)
+	if (box_ephemeral_replace(space, tuple, tuple_end, NULL) != 0)
 		return SQL_TARANTOOL_INSERT_FAIL;
 	return SQLITE_OK;
 }
@@ -515,9 +515,8 @@ int tarantoolSqlite3EphemeralDelete(BtCursor *pCur)
 				&key_size);
 	if (key == NULL)
 		return SQL_TARANTOOL_DELETE_FAIL;
-
-	int rc = space_ephemeral_delete(pCur->space, key);
-	if (rc != 0) {
+	if (box_ephemeral_delete(pCur->space, 0, key,
+				 key + key_size, NULL) != 0) {
 		diag_log();
 		return SQL_TARANTOOL_DELETE_FAIL;
 	}
@@ -600,7 +599,8 @@ int tarantoolSqlite3EphemeralClearTable(BtCursor *pCur)
 	while (iterator_next(it, &tuple) == 0 && tuple != NULL) {
 		key = tuple_extract_key(tuple, it->index->def->key_def,
 					&key_size);
-		if (space_ephemeral_delete(pCur->space, key) != 0) {
+		if (box_ephemeral_delete(pCur->space, 0, key,
+					 key + key_size, NULL) != 0) {
 			iterator_delete(it);
 			return SQL_TARANTOOL_DELETE_FAIL;
 		}
diff --git a/src/box/sysview_engine.c b/src/box/sysview_engine.c
index bd9432a..3067175 100644
--- a/src/box/sysview_engine.c
+++ b/src/box/sysview_engine.c
@@ -99,26 +99,6 @@ sysview_space_execute_upsert(struct space *space, struct txn *txn,
 	return -1;
 }
 
-static int
-sysview_space_ephemeral_replace(struct space *space, const char *tuple,
-				const char *tuple_end)
-{
-	(void)space;
-	(void)tuple;
-	(void)tuple_end;
-	unreachable();
-	return -1;
-}
-
-static int
-sysview_space_ephemeral_delete(struct space *space, const char *key)
-{
-	(void)key;
-	(void)space;
-	unreachable();
-	return -1;
-}
-
 static void
 sysview_init_system_space(struct space *space)
 {
@@ -197,8 +177,6 @@ static const struct space_vtab sysview_space_vtab = {
 	/* .execute_delete = */ sysview_space_execute_delete,
 	/* .execute_update = */ sysview_space_execute_update,
 	/* .execute_upsert = */ sysview_space_execute_upsert,
-	/* .ephemeral_replace = */ sysview_space_ephemeral_replace,
-	/* .ephemeral_delete = */ sysview_space_ephemeral_delete,
 	/* .init_system_space = */ sysview_init_system_space,
 	/* .init_ephemeral_space = */ sysview_init_ephemeral_space,
 	/* .check_index_def = */ sysview_space_check_index_def,
diff --git a/src/box/vinyl.c b/src/box/vinyl.c
index ba9cd3a..bfa9590 100644
--- a/src/box/vinyl.c
+++ b/src/box/vinyl.c
@@ -2349,26 +2349,6 @@ vinyl_space_execute_upsert(struct space *space, struct txn *txn,
 	return vy_upsert(env, tx, stmt, space, request);
 }
 
-static int
-vinyl_space_ephemeral_replace(struct space *space, const char *tuple,
-			      const char *tuple_end)
-{
-	(void)space;
-	(void)tuple;
-	(void)tuple_end;
-	unreachable();
-	return -1;
-}
-
-static int
-vinyl_space_ephemeral_delete(struct space *space, const char *key)
-{
-	(void)space;
-	(void)key;
-	unreachable();
-	return -1;
-}
-
 static inline void
 txn_stmt_unref_tuples(struct txn_stmt *stmt)
 {
@@ -4514,8 +4494,6 @@ static const struct space_vtab vinyl_space_vtab = {
 	/* .execute_delete = */ vinyl_space_execute_delete,
 	/* .execute_update = */ vinyl_space_execute_update,
 	/* .execute_upsert = */ vinyl_space_execute_upsert,
-	/* .ephemeral_replace = */ vinyl_space_ephemeral_replace,
-	/* .ephemeral_delete = */ vinyl_space_ephemeral_delete,
 	/* .init_system_space = */ vinyl_init_system_space,
 	/* .init_ephemeral_space = */ vinyl_init_ephemeral_space,
 	/* .check_index_def = */ vinyl_space_check_index_def,
-- 
2.7.4

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [tarantool-patches] [PATCH 2/5] Move some decode functions from alter.cc
  2018-07-12 11:16 [tarantool-patches] [PATCH 0/5] Expose ephemeral spaces into Lua imeevma
  2018-07-12 11:16 ` [tarantool-patches] [PATCH 1/5] Create new methods for ephemeral spaces imeevma
@ 2018-07-12 11:16 ` imeevma
  2018-07-13 16:32   ` [tarantool-patches] " Vladislav Shpilevoy
  2018-07-12 11:16 ` [tarantool-patches] [PATCH 3/5] Ephemeral space creation and deletion in Lua imeevma
                   ` (3 subsequent siblings)
  5 siblings, 1 reply; 12+ messages in thread
From: imeevma @ 2018-07-12 11:16 UTC (permalink / raw)
  To: tarantool-patches

Allow to use some format and options decode functions
outside of alter.cc.

Part of #3375.
---
 src/box/alter.cc    | 203 ++--------------------------------------------------
 src/box/index_def.c |  60 ++++++++++++++++
 src/box/index_def.h |   8 +++
 src/box/space_def.c | 150 ++++++++++++++++++++++++++++++++++++++
 src/box/space_def.h |  15 ++++
 5 files changed, 237 insertions(+), 199 deletions(-)

diff --git a/src/box/alter.cc b/src/box/alter.cc
index 7b6bd1a..15240c0 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -167,61 +167,6 @@ err:
 }
 
 /**
- * Fill index_opts structure from opts field in tuple of space _index
- * Throw an error is unrecognized option.
- */
-static void
-index_opts_decode(struct index_opts *opts, const char *map,
-		  struct region *region)
-{
-	index_opts_create(opts);
-	if (opts_decode(opts, index_opts_reg, &map, ER_WRONG_INDEX_OPTIONS,
-			BOX_INDEX_FIELD_OPTS, region) != 0)
-		diag_raise();
-	if (opts->distance == rtree_index_distance_type_MAX) {
-		tnt_raise(ClientError, ER_WRONG_INDEX_OPTIONS,
-			  BOX_INDEX_FIELD_OPTS, "distance must be either "\
-			  "'euclid' or 'manhattan'");
-	}
-	if (opts->sql != NULL) {
-		char *sql = strdup(opts->sql);
-		if (sql == NULL) {
-			opts->sql = NULL;
-			tnt_raise(OutOfMemory, strlen(opts->sql) + 1, "strdup",
-				  "sql");
-		}
-		opts->sql = sql;
-	}
-	if (opts->range_size <= 0) {
-		tnt_raise(ClientError, ER_WRONG_INDEX_OPTIONS,
-			  BOX_INDEX_FIELD_OPTS,
-			  "range_size must be greater than 0");
-	}
-	if (opts->page_size <= 0 || opts->page_size > opts->range_size) {
-		tnt_raise(ClientError, ER_WRONG_INDEX_OPTIONS,
-			  BOX_INDEX_FIELD_OPTS,
-			  "page_size must be greater than 0 and "
-			  "less than or equal to range_size");
-	}
-	if (opts->run_count_per_level <= 0) {
-		tnt_raise(ClientError, ER_WRONG_INDEX_OPTIONS,
-			  BOX_INDEX_FIELD_OPTS,
-			  "run_count_per_level must be greater than 0");
-	}
-	if (opts->run_size_ratio <= 1) {
-		tnt_raise(ClientError, ER_WRONG_INDEX_OPTIONS,
-			  BOX_INDEX_FIELD_OPTS,
-			  "run_size_ratio must be greater than 1");
-	}
-	if (opts->bloom_fpr <= 0 || opts->bloom_fpr > 1) {
-		tnt_raise(ClientError, ER_WRONG_INDEX_OPTIONS,
-			  BOX_INDEX_FIELD_OPTS,
-			  "bloom_fpr must be greater than 0 and "
-			  "less than or equal to 1");
-	}
-}
-
-/**
  * Create a index_def object from a record in _index
  * system space.
  *
@@ -250,7 +195,8 @@ index_def_new_from_tuple(struct tuple *tuple, struct space *space)
 	const char *opts_field =
 		tuple_field_with_type_xc(tuple, BOX_INDEX_FIELD_OPTS,
 					 MP_MAP);
-	index_opts_decode(&opts, opts_field, &fiber()->gc);
+	if(index_opts_decode(&opts, opts_field, &fiber()->gc) != 0)
+		diag_raise();
 	const char *parts = tuple_field(tuple, BOX_INDEX_FIELD_PARTS);
 	uint32_t part_count = mp_decode_array(&parts);
 	if (name_len > BOX_NAME_MAX) {
@@ -316,149 +262,6 @@ space_opts_decode(struct space_opts *opts, const char *map,
 }
 
 /**
- * Decode field definition from MessagePack map. Format:
- * {name: <string>, type: <string>}. Type is optional.
- * @param[out] field Field to decode to.
- * @param data MessagePack map to decode.
- * @param space_name Name of a space, from which the field is got.
- *        Used in error messages.
- * @param name_len Length of @a space_name.
- * @param errcode Error code to use for client errors. Either
- *        create or modify space errors.
- * @param fieldno Field number to decode. Used in error messages.
- * @param region Region to allocate field name.
- */
-static void
-field_def_decode(struct field_def *field, const char **data,
-		 const char *space_name, uint32_t name_len,
-		 uint32_t errcode, uint32_t fieldno, struct region *region)
-{
-	if (mp_typeof(**data) != MP_MAP) {
-		tnt_raise(ClientError, errcode, tt_cstr(space_name, name_len),
-			  tt_sprintf("field %d is not map",
-				     fieldno + TUPLE_INDEX_BASE));
-	}
-	int count = mp_decode_map(data);
-	*field = field_def_default;
-	bool is_action_missing = true;
-	uint32_t action_literal_len = strlen("nullable_action");
-	for (int i = 0; i < count; ++i) {
-		if (mp_typeof(**data) != MP_STR) {
-			tnt_raise(ClientError, errcode,
-				  tt_cstr(space_name, name_len),
-				  tt_sprintf("field %d format is not map"\
-					     " with string keys",
-					     fieldno + TUPLE_INDEX_BASE));
-		}
-		uint32_t key_len;
-		const char *key = mp_decode_str(data, &key_len);
-		if (opts_parse_key(field, field_def_reg, key, key_len, data,
-				   ER_WRONG_SPACE_FORMAT,
-				   fieldno + TUPLE_INDEX_BASE, region,
-				   true) != 0)
-			diag_raise();
-		if (is_action_missing &&
-		    key_len == action_literal_len &&
-		    memcmp(key, "nullable_action", action_literal_len) == 0)
-			is_action_missing = false;
-	}
-	if (is_action_missing) {
-		field->nullable_action = field->is_nullable ?
-			ON_CONFLICT_ACTION_NONE
-			: ON_CONFLICT_ACTION_DEFAULT;
-	}
-	if (field->name == NULL) {
-		tnt_raise(ClientError, errcode, tt_cstr(space_name, name_len),
-			  tt_sprintf("field %d name is not specified",
-				     fieldno + TUPLE_INDEX_BASE));
-	}
-	size_t field_name_len = strlen(field->name);
-	if (field_name_len > BOX_NAME_MAX) {
-		tnt_raise(ClientError, errcode, tt_cstr(space_name, name_len),
-			  tt_sprintf("field %d name is too long",
-				     fieldno + TUPLE_INDEX_BASE));
-	}
-	identifier_check_xc(field->name, field_name_len);
-	if (field->type == field_type_MAX) {
-		tnt_raise(ClientError, errcode, tt_cstr(space_name, name_len),
-			  tt_sprintf("field %d has unknown field type",
-				     fieldno + TUPLE_INDEX_BASE));
-	}
-	if (field->nullable_action == on_conflict_action_MAX) {
-		tnt_raise(ClientError, errcode, tt_cstr(space_name, name_len),
-			  tt_sprintf("field %d has unknown field on conflict "
-				     "nullable action",
-				     fieldno + TUPLE_INDEX_BASE));
-	}
-	if (!((field->is_nullable && field->nullable_action ==
-	       ON_CONFLICT_ACTION_NONE)
-	      || (!field->is_nullable
-		  && field->nullable_action != ON_CONFLICT_ACTION_NONE))) {
-		tnt_raise(ClientError, errcode, tt_cstr(space_name, name_len),
-			  tt_sprintf("field %d has conflicting nullability and "
-				     "nullable action properties", fieldno +
-				     TUPLE_INDEX_BASE));
-	}
-	if (field->coll_id != COLL_NONE &&
-	    field->type != FIELD_TYPE_STRING &&
-	    field->type != FIELD_TYPE_SCALAR &&
-	    field->type != FIELD_TYPE_ANY) {
-		tnt_raise(ClientError, errcode, tt_cstr(space_name, name_len),
-			  tt_sprintf("collation is reasonable only for "
-				     "string, scalar and any fields"));
-	}
-
-	const char *dv = field->default_value;
-	if (dv != NULL) {
-		field->default_value_expr = sql_expr_compile(sql_get(), dv,
-							     strlen(dv));
-		if (field->default_value_expr == NULL)
-			diag_raise();
-	}
-}
-
-/**
- * Decode MessagePack array of fields.
- * @param data MessagePack array of fields.
- * @param[out] out_count Length of a result array.
- * @param space_name Space name to use in error messages.
- * @param errcode Errcode for client errors.
- * @param region Region to allocate result array.
- *
- * @retval Array of fields.
- */
-static struct field_def *
-space_format_decode(const char *data, uint32_t *out_count,
-		    const char *space_name, uint32_t name_len,
-		    uint32_t errcode, struct region *region)
-{
-	/* Type is checked by _space format. */
-	assert(mp_typeof(*data) == MP_ARRAY);
-	uint32_t count = mp_decode_array(&data);
-	*out_count = count;
-	if (count == 0)
-		return NULL;
-	size_t size = count * sizeof(struct field_def);
-	struct field_def *region_defs =
-		(struct field_def *) region_alloc_xc(region, size);
-	/*
-	 * Nullify to prevent a case when decoding will fail in
-	 * the middle and space_def_destroy_fields() below will
-	 * work with garbage pointers.
-	 */
-	memset(region_defs, 0, size);
-	auto fields_guard = make_scoped_guard([=] {
-		space_def_destroy_fields(region_defs, count);
-	});
-	for (uint32_t i = 0; i < count; ++i) {
-		field_def_decode(&region_defs[i], &data, space_name, name_len,
-				 errcode, i, region);
-	}
-	fields_guard.is_active = false;
-	return region_defs;
-}
-
-/**
  * Fill space_def structure from struct tuple.
  */
 static struct space_def *
@@ -510,6 +313,8 @@ space_def_new_from_tuple(struct tuple *tuple, uint32_t errcode,
 					 MP_ARRAY);
 	fields = space_format_decode(format, &field_count, name,
 				     name_len, errcode, region);
+	if (field_count != 0 && fields == NULL)
+		diag_raise();
 	auto fields_guard = make_scoped_guard([=] {
 		space_def_destroy_fields(fields, field_count);
 	});
diff --git a/src/box/index_def.c b/src/box/index_def.c
index 45c74d9..e0d54c0 100644
--- a/src/box/index_def.c
+++ b/src/box/index_def.c
@@ -309,3 +309,63 @@ index_def_is_valid(struct index_def *index_def, const char *space_name)
 	}
 	return true;
 }
+
+int
+index_opts_decode(struct index_opts *opts, const char *map,
+		  struct region *region)
+{
+	index_opts_create(opts);
+	if (opts_decode(opts, index_opts_reg, &map, ER_WRONG_INDEX_OPTIONS,
+			BOX_INDEX_FIELD_OPTS, region) != 0) {
+		return -1;
+	}
+	if (opts->distance == rtree_index_distance_type_MAX) {
+		diag_set(ClientError, ER_WRONG_INDEX_OPTIONS,
+			  BOX_INDEX_FIELD_OPTS, "distance must be either "\
+			  "'euclid' or 'manhattan'");
+		return -1;
+	}
+	if (opts->sql != NULL) {
+		char *sql = strdup(opts->sql);
+		if (sql == NULL) {
+			opts->sql = NULL;
+			diag_set(OutOfMemory, strlen(opts->sql) + 1, "strdup",
+				 "sql");
+			return -1;
+		}
+		opts->sql = sql;
+	}
+	if (opts->range_size <= 0) {
+		diag_set(ClientError, ER_WRONG_INDEX_OPTIONS,
+			 BOX_INDEX_FIELD_OPTS,
+			 "range_size must be greater than 0");
+		return -1;
+	}
+	if (opts->page_size <= 0 || opts->page_size > opts->range_size) {
+		diag_set(ClientError, ER_WRONG_INDEX_OPTIONS,
+			 BOX_INDEX_FIELD_OPTS,
+			 "page_size must be greater than 0 and "
+			 "less than or equal to range_size");
+		return -1;
+	}
+	if (opts->run_count_per_level <= 0) {
+		diag_set(ClientError, ER_WRONG_INDEX_OPTIONS,
+			 BOX_INDEX_FIELD_OPTS,
+			 "run_count_per_level must be greater than 0");
+		return -1;
+	}
+	if (opts->run_size_ratio <= 1) {
+		diag_set(ClientError, ER_WRONG_INDEX_OPTIONS,
+			 BOX_INDEX_FIELD_OPTS,
+			 "run_size_ratio must be greater than 1");
+		return -1;
+	}
+	if (opts->bloom_fpr <= 0 || opts->bloom_fpr > 1) {
+		diag_set(ClientError, ER_WRONG_INDEX_OPTIONS,
+			 BOX_INDEX_FIELD_OPTS,
+			 "bloom_fpr must be greater than 0 and "
+			 "less than or equal to 1");
+		return -1;
+	}
+	return 0;
+}
diff --git a/src/box/index_def.h b/src/box/index_def.h
index 48a7820..4c3625b 100644
--- a/src/box/index_def.h
+++ b/src/box/index_def.h
@@ -358,6 +358,14 @@ index_def_cmp(const struct index_def *key1, const struct index_def *key2);
 bool
 index_def_is_valid(struct index_def *index_def, const char *space_name);
 
+/**
+ * Fill index_opts structure from opts field in tuple of space _index
+ * Throw an error is unrecognized option.
+ */
+int
+index_opts_decode(struct index_opts *opts, const char *map,
+		  struct region *region);
+
 #if defined(__cplusplus)
 } /* extern "C" */
 
diff --git a/src/box/space_def.c b/src/box/space_def.c
index f5ca0b5..3b31727 100644
--- a/src/box/space_def.c
+++ b/src/box/space_def.c
@@ -34,6 +34,10 @@
 #include "error.h"
 #include "sql.h"
 #include "msgpuck.h"
+#include "tuple_format.h"
+#include "schema_def.h"
+#include "identifier.h"
+#include "small/region.h"
 
 /**
  * Make checks from msgpack.
@@ -351,3 +355,149 @@ error:
 	sql_expr_list_delete(db, checks);
 	return  -1;
 }
+
+/**
+ * Decode field definition from MessagePack map. Format:
+ * {name: <string>, type: <string>}. Type is optional.
+ * @param[out] field Field to decode to.
+ * @param data MessagePack map to decode.
+ * @param space_name Name of a space, from which the field is got.
+ *        Used in error messages.
+ * @param name_len Length of @a space_name.
+ * @param errcode Error code to use for client errors. Either
+ *        create or modify space errors.
+ * @param fieldno Field number to decode. Used in error messages.
+ * @param region Region to allocate field name.
+ */
+static inline int
+field_def_decode(struct field_def *field, const char **data,
+		 const char *space_name, uint32_t name_len,
+		 uint32_t errcode, uint32_t fieldno, struct region *region)
+{
+	if (mp_typeof(**data) != MP_MAP) {
+		diag_set(ClientError, errcode, tt_cstr(space_name, name_len),
+			  tt_sprintf("field %d is not map",
+				     fieldno + TUPLE_INDEX_BASE));
+		return -1;
+	}
+	int count = mp_decode_map(data);
+	*field = field_def_default;
+	bool is_action_missing = true;
+	uint32_t action_literal_len = strlen("nullable_action");
+	for (int i = 0; i < count; ++i) {
+		if (mp_typeof(**data) != MP_STR) {
+			diag_set(ClientError, errcode,
+				  tt_cstr(space_name, name_len),
+				  tt_sprintf("field %d format is not map"\
+					     " with string keys",
+					     fieldno + TUPLE_INDEX_BASE));
+			return -1;
+		}
+		uint32_t key_len;
+		const char *key = mp_decode_str(data, &key_len);
+		if (opts_parse_key(field, field_def_reg, key, key_len, data,
+				   ER_WRONG_SPACE_FORMAT,
+				   fieldno + TUPLE_INDEX_BASE, region,
+				   true) != 0)
+			return -1;
+		if (is_action_missing &&
+		    key_len == action_literal_len &&
+		    memcmp(key, "nullable_action", action_literal_len) == 0)
+			is_action_missing = false;
+	}
+	if (is_action_missing) {
+		field->nullable_action = field->is_nullable ?
+			ON_CONFLICT_ACTION_NONE
+			: ON_CONFLICT_ACTION_DEFAULT;
+	}
+	if (field->name == NULL) {
+		diag_set(ClientError, errcode, tt_cstr(space_name, name_len),
+			  tt_sprintf("field %d name is not specified",
+				     fieldno + TUPLE_INDEX_BASE));
+		return -1;
+	}
+	size_t field_name_len = strlen(field->name);
+	if (field_name_len > BOX_NAME_MAX) {
+		diag_set(ClientError, errcode, tt_cstr(space_name, name_len),
+			  tt_sprintf("field %d name is too long",
+				     fieldno + TUPLE_INDEX_BASE));
+		return -1;
+	}
+	if(identifier_check(field->name, field_name_len))
+		return -1;
+	if (field->type == field_type_MAX) {
+		diag_set(ClientError, errcode, tt_cstr(space_name, name_len),
+			  tt_sprintf("field %d has unknown field type",
+				     fieldno + TUPLE_INDEX_BASE));
+		return -1;
+	}
+	if (field->nullable_action == on_conflict_action_MAX) {
+		diag_set(ClientError, errcode, tt_cstr(space_name, name_len),
+			  tt_sprintf("field %d has unknown field on conflict "
+				     "nullable action",
+				     fieldno + TUPLE_INDEX_BASE));
+		return -1;
+	}
+	if (!((field->is_nullable && field->nullable_action ==
+	       ON_CONFLICT_ACTION_NONE)
+	      || (!field->is_nullable
+		  && field->nullable_action != ON_CONFLICT_ACTION_NONE))) {
+		diag_set(ClientError, errcode, tt_cstr(space_name, name_len),
+			  tt_sprintf("field %d has conflicting nullability and "
+				     "nullable action properties", fieldno +
+				     TUPLE_INDEX_BASE));
+		return -1;
+	}
+	if (field->coll_id != COLL_NONE &&
+	    field->type != FIELD_TYPE_STRING &&
+	    field->type != FIELD_TYPE_SCALAR &&
+	    field->type != FIELD_TYPE_ANY) {
+		diag_set(ClientError, errcode, tt_cstr(space_name, name_len),
+			  tt_sprintf("collation is reasonable only for "
+				     "string, scalar and any fields"));
+		return -1;
+	}
+
+	const char *dv = field->default_value;
+	if (dv != NULL) {
+		field->default_value_expr = sql_expr_compile(sql_get(), dv,
+							     strlen(dv));
+		if (field->default_value_expr == NULL)
+			return -1;
+	}
+	return 0;
+}
+
+struct field_def *
+space_format_decode(const char *data, uint32_t *out_count,
+		    const char *space_name, uint32_t name_len,
+		    uint32_t errcode, struct region *region)
+{
+	/* Type is checked by _space format. */
+	assert(mp_typeof(*data) == MP_ARRAY);
+	uint32_t count = mp_decode_array(&data);
+	*out_count = count;
+	if (count == 0)
+		return NULL;
+	size_t size = count * sizeof(struct field_def);
+	struct field_def *region_defs =
+		(struct field_def *) region_alloc(region, size);
+	if(region_defs == NULL) {
+		diag_set(OutOfMemory, size, "region", "new slab");
+		return NULL;
+	}
+	/*
+	 * Nullify to prevent a case when decoding will fail in
+	 * the middle and space_def_destroy_fields() below will
+	 * work with garbage pointers.
+	 */
+	memset(region_defs, 0, size);
+	for (uint32_t i = 0; i < count; ++i) {
+		if(field_def_decode(&region_defs[i], &data, space_name,
+			name_len, errcode, i, region) != 0) {
+			space_def_destroy_fields(region_defs, count);
+			return NULL;
+		}
+	}
+	return region_defs;
+}
diff --git a/src/box/space_def.h b/src/box/space_def.h
index 0d1e902..16188c6 100644
--- a/src/box/space_def.h
+++ b/src/box/space_def.h
@@ -182,6 +182,21 @@ space_def_sizeof(uint32_t name_len, const struct field_def *fields,
 		 uint32_t field_count, uint32_t *names_offset,
 		 uint32_t *fields_offset, uint32_t *def_expr_offset);
 
+/**
+ * Decode MessagePack array of fields.
+ * @param data MessagePack array of fields.
+ * @param[out] out_count Length of a result array.
+ * @param space_name Space name to use in error messages.
+ * @param errcode Errcode for client errors.
+ * @param region Region to allocate result array.
+ *
+ * @retval Array of fields.
+ */
+struct field_def *
+space_format_decode(const char *data, uint32_t *out_count,
+		    const char *space_name, uint32_t name_len,
+		    uint32_t errcode, struct region *region);
+
 #if defined(__cplusplus)
 } /* extern "C" */
 
-- 
2.7.4

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [tarantool-patches] [PATCH 3/5] Ephemeral space creation and deletion in Lua
  2018-07-12 11:16 [tarantool-patches] [PATCH 0/5] Expose ephemeral spaces into Lua imeevma
  2018-07-12 11:16 ` [tarantool-patches] [PATCH 1/5] Create new methods for ephemeral spaces imeevma
  2018-07-12 11:16 ` [tarantool-patches] [PATCH 2/5] Move some decode functions from alter.cc imeevma
@ 2018-07-12 11:16 ` imeevma
  2018-07-13 16:32   ` [tarantool-patches] " Vladislav Shpilevoy
  2018-07-12 11:16 ` [tarantool-patches] [PATCH 4/5] Primary index for ephemeral spaces imeevma
                   ` (2 subsequent siblings)
  5 siblings, 1 reply; 12+ messages in thread
From: imeevma @ 2018-07-12 11:16 UTC (permalink / raw)
  To: tarantool-patches

Import functions to create ephemeral space in Lua and some
its methods that do not require index.

Part of #3375.
---
 src/box/lua/schema.lua            |  69 ++++++++++
 src/box/lua/space.cc              | 183 +++++++++++++++++++++++++
 test/box/ephemeral_space.result   | 271 ++++++++++++++++++++++++++++++++++++++
 test/box/ephemeral_space.test.lua |  96 ++++++++++++++
 test/engine/iterator.result       |   2 +-
 5 files changed, 620 insertions(+), 1 deletion(-)
 create mode 100644 test/box/ephemeral_space.result
 create mode 100644 test/box/ephemeral_space.test.lua

diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index e4c1b1d..81f0fa0 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -477,6 +477,64 @@ box.schema.space.create = function(name, options)
     return box.space[id], "created"
 end
 
+local space_new_ephemeral = box.internal.space.space_new_ephemeral
+box.internal.space.space_new_ephemeral = nil
+local space_delete_ephemeral = box.internal.space.space_delete_ephemeral
+box.internal.space.space_delete_ephemeral = nil
+local space_ephemeral_methods = box.schema.space_ephemeral_methods
+box.schema.space_ephemeral_methods = nil
+local space_ephemeral_mt = {}
+
+box.schema.space.create_ephemeral = function(options)
+    local options_template = {
+        engine = 'string',
+        field_count = 'number',
+        format = 'table',
+    }
+    local options_defaults = {
+        engine = 'memtx',
+        field_count = 0,
+    }
+    check_param_table(options, options_template)
+    options = update_param_table(options, options_defaults)
+
+    local format = options.format and options.format or {}
+    check_param(format, 'format', 'table')
+    format = update_format(format)
+    local packed_format = msgpack.encode(format)
+
+    local ephemeral_space = {}
+    ephemeral_space.space = space_new_ephemeral(options.engine,
+                                                options.field_count,
+                                                packed_format)
+    ephemeral_space.space_format = format
+    ephemeral_space.engine = options.engine
+    ephemeral_space.field_count = options.field_count
+    ephemeral_space.temporary = true
+    ephemeral_space.index = {}
+    -- Set GC for result
+    setmetatable(ephemeral_space, space_ephemeral_mt)
+    ephemeral_space.proxy = newproxy(true)
+    getmetatable(ephemeral_space.proxy).__gc = function(self)
+        box.schema.space.drop_ephemeral(ephemeral_space)
+    end
+    -- Return result
+    return ephemeral_space
+end
+
+box.schema.space.drop_ephemeral = function(ephemeral_space)
+    if ephemeral_space == nil or ephemeral_space.space == nil then
+        return
+    end
+    check_param(ephemeral_space.space, 'space', 'cdata')
+    space_delete_ephemeral(ephemeral_space)
+    for k,_ in pairs(ephemeral_space) do
+        ephemeral_space[k] = nil
+    end
+end
+
+box.schema.create_ephemeral_space = box.schema.space.create_ephemeral
+
 -- space format - the metadata about space fields
 function box.schema.space.format(id, format)
     local _space = box.space._space
@@ -1505,6 +1563,17 @@ end
 space_mt.frommap = box.internal.space.frommap
 space_mt.__index = space_mt
 
+-- Metatable for ephemeral space
+space_ephemeral_mt.format = function(ephemeral_space)
+    return ephemeral_space.space_format
+end
+space_ephemeral_mt.run_triggers = function(ephemeral_space, yesno)
+    builtin.space_run_triggers(ephemeral_space.space, yesno)
+end
+space_ephemeral_mt.frommap = space_ephemeral_methods.frommap
+space_ephemeral_mt.drop = box.schema.space.drop_ephemeral
+space_ephemeral_mt.__index = space_ephemeral_mt
+
 box.schema.index_mt = base_index_mt
 box.schema.memtx_index_mt = memtx_index_mt
 box.schema.vinyl_index_mt = vinyl_index_mt
diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc
index ca3fefc..955c465 100644
--- a/src/box/lua/space.cc
+++ b/src/box/lua/space.cc
@@ -33,6 +33,7 @@
 #include "box/sql/sqliteLimit.h"
 #include "lua/utils.h"
 #include "lua/trigger.h"
+#include "box/box.h"
 
 extern "C" {
 	#include <lua.h>
@@ -50,6 +51,8 @@ extern "C" {
 #include "box/coll_id_cache.h"
 #include "box/replication.h" /* GROUP_LOCAL */
 
+static uint32_t CTID_STRUCT_SPACE_POINTER = 0;
+
 /**
  * Trigger function for all spaces
  */
@@ -508,6 +511,169 @@ usage_error:
 	return luaL_error(L, "Usage: space:frommap(map, opts)");
 }
 
+/**
+ * Check if given argument have an appropriate type.
+ * @param Lua struct space *space - ephemeral space.
+ * @retval struct space *space - success.
+ * @retval NULL - error.
+ */
+static inline struct space *
+luaT_isspace(struct lua_State *L, int narg)
+{
+	uint32_t ctypeid = 0;
+	void *data;
+
+	if (lua_type(L, narg) != LUA_TCDATA)
+		return NULL;
+
+	data = luaL_checkcdata(L, narg, &ctypeid);
+	if (ctypeid != CTID_STRUCT_SPACE_POINTER)
+		return NULL;
+
+	return *(struct space **) data;
+}
+
+/**
+ * Puts space field of given argument in stack.
+ * onto Lua state stack and calls luaT_isspace
+ * @param Lua table with field space.
+ * @retval struct space *space - success.
+ * @retval NULL - error.
+ */
+static inline struct space *
+lua_checkephemeralspace(struct lua_State *L, int narg)
+{
+	lua_getfield(L, 1, "space");
+	struct space *space = luaT_isspace(L, -1);
+	lua_pop(L, 1);
+	if (space == NULL) {
+		luaL_error(L, "Invalid argument #%d (ephemeral space expected,"\
+			   "got %s)", narg, lua_typename(L, lua_type(L, narg)));
+	}
+	return space;
+}
+
+/**
+ * Create an ephemeral space.
+ * @param Lua const char *engine_name - name of engine.
+ * @param Lua uint32_t field_count - number of fields.
+ * @param Lua const char *format - format in msgpack.
+ * @retval not nil - ephemeral space created.
+ * @retval nil - error, A reason is returned in
+ *         the second value.
+ */
+static int
+lbox_space_new_ephemeral(struct lua_State *L)
+{
+	uint32_t argc = lua_gettop(L);
+	if (argc != 3)
+		return luaL_error(L, "Error with creating ephemeral space");
+	const char *engine_name = luaL_checkstring (L, 1);
+	uint32_t exact_field_count = luaL_checknumber (L, 2);
+	const char *format = luaL_checkstring (L, 3);
+	struct region *region = &fiber()->gc;
+	uint32_t field_count;
+	struct field_def *fields = space_format_decode(format, &field_count,
+		"ephemeral", strlen("ephemeral"), ER_CREATE_SPACE, region);
+	if (exact_field_count != 0 &&
+	    exact_field_count < field_count) {
+		return luaL_error(L, "exact_field_count must be either 0 or"\
+				  ">= formatted field count");
+	}
+	struct space_def *ephemeral_space_def =
+		space_def_new(0, 0, exact_field_count, "ephemeral",
+			      strlen("ephemeral"), engine_name,
+			      strlen(engine_name), &space_opts_default, fields,
+			      field_count);
+	if (ephemeral_space_def == NULL)
+		return luaL_error(L, "Error with creating space_def");
+	struct rlist key_list;
+	rlist_create(&key_list);
+	struct space *space = space_new_ephemeral(ephemeral_space_def,
+						  &key_list);
+	space_def_delete(ephemeral_space_def);
+	if (space == NULL)
+		return luaL_error(L, "Error with creating space");
+	struct space **ptr =
+		(struct space **) luaL_pushcdata(L, CTID_STRUCT_SPACE_POINTER);
+	*ptr = space;
+	return 1;
+}
+
+/**
+ * Delete an ephemeral space.
+ * @param Lua table with field space - ephemeral space.
+ */
+static int
+lbox_space_delete_ephemeral(struct lua_State *L)
+{
+	uint32_t argc = lua_gettop(L);
+	if (argc != 1 || !lua_istable(L, 1))
+		return luaL_error(L, "Usage: ephemeral_space:drop()");
+	struct space *space = (struct space *)lua_checkephemeralspace(L, 1);
+	space_delete(space);
+	return 0;
+}
+
+/**
+ * Make a tuple or a table Lua object by map.
+ * @param Lua space object.
+ * @param Lua map table object.
+ * @param Lua opts table object (optional).
+ * @retval not nil A tuple or a table conforming to a space
+ *         format.
+ * @retval nil, err Can not built a tuple. A reason is returned in
+ *         the second value.
+ */
+static int
+lbox_space_frommap_ephemeral(struct lua_State *L)
+{
+	struct tuple_dictionary *dict = NULL;
+	int argc = lua_gettop(L);
+	bool table = false;
+	if (argc < 2 || argc > 3 || !lua_istable(L, 1) || !lua_istable(L, 2))
+		return luaL_error(L, "Usage: ephemeral_space:"\
+				     "frommap(map, opts)");
+	if (argc == 3) {
+		if (!lua_istable(L, 3))
+			return luaL_error(L, "Usage: ephemeral_space:"\
+					     "frommap(map, opts)");
+		lua_getfield(L, 3, "table");
+		if (!lua_isboolean(L, -1) && !lua_isnil(L, -1))
+			return luaL_error(L, "Usage: ephemeral_space:"\
+					     "frommap(map, opts)");
+		table = lua_toboolean(L, -1);
+	}
+
+	struct space *space = (struct space *)lua_checkephemeralspace(L, 1);
+	assert(space->format != NULL);
+
+	dict = space->format->dict;
+	lua_createtable(L, space->def->field_count, 0);
+
+	lua_pushnil(L);
+	while (lua_next(L, 2) != 0) {
+		uint32_t fieldno;
+		size_t key_len;
+		const char *key = lua_tolstring(L, -2, &key_len);
+		uint32_t key_hash = lua_hashstring(L, -2);
+		if (tuple_fieldno_by_name(dict, key, key_len, key_hash,
+					  &fieldno)) {
+			lua_pushnil(L);
+			lua_pushstring(L, tt_sprintf("Unknown field '%s'",
+						     key));
+			return 2;
+		}
+		lua_rawseti(L, -3, fieldno+1);
+	}
+	if (table)
+		return 1;
+
+	lua_replace(L, 1);
+	lua_settop(L, 1);
+	return lbox_tuple_new(L);
+}
+
 void
 box_lua_space_init(struct lua_State *L)
 {
@@ -515,6 +681,12 @@ box_lua_space_init(struct lua_State *L)
 	on_alter_space_in_lua.data = L;
 	trigger_add(&on_alter_space, &on_alter_space_in_lua);
 
+	int rc = luaL_cdef(L, "struct space;");
+	assert(rc == 0);
+	(void) rc;
+	CTID_STRUCT_SPACE_POINTER = luaL_ctypeid(L, "struct space *");
+	assert(CTID_STRUCT_SPACE_POINTER != 0);
+
 	lua_getfield(L, LUA_GLOBALSINDEX, "box");
 	lua_newtable(L);
 	lua_setfield(L, -2, "schema");
@@ -595,12 +767,23 @@ box_lua_space_init(struct lua_State *L)
 	lua_setfield(L, -2, "REPLICA_MAX");
 	lua_pushnumber(L, SQL_BIND_PARAMETER_MAX);
 	lua_setfield(L, -2, "SQL_BIND_PARAMETER_MAX");
+	lua_newtable(L);
+	lua_setfield(L, -2, "space_ephemeral_methods");
 	lua_pop(L, 2); /* box, schema */
 
 	static const struct luaL_Reg space_internal_lib[] = {
 		{"frommap", lbox_space_frommap},
+		{"space_new_ephemeral", lbox_space_new_ephemeral},
+		{"space_delete_ephemeral", lbox_space_delete_ephemeral},
 		{NULL, NULL}
 	};
 	luaL_register(L, "box.internal.space", space_internal_lib);
 	lua_pop(L, 1);
+	static const struct luaL_Reg space_ephemeral_lib[] = {
+		{"frommap", lbox_space_frommap_ephemeral},
+		{NULL, NULL}
+	};
+	luaL_register(L, "box.schema.space_ephemeral_methods",
+		      space_ephemeral_lib);
+	lua_pop(L, 1);
 }
diff --git a/test/box/ephemeral_space.result b/test/box/ephemeral_space.result
new file mode 100644
index 0000000..135df3c
--- /dev/null
+++ b/test/box/ephemeral_space.result
@@ -0,0 +1,271 @@
+-- Ephemeral space: creation and dropping
+-- Simple creation
+s = box.schema.space.create_ephemeral()
+---
+...
+s.index
+---
+- []
+...
+s.temporary
+---
+- true
+...
+s.field_count
+---
+- 0
+...
+s.engine
+---
+- memtx
+...
+s:drop()
+---
+...
+-- Creation with defined engine (only memtx for now)
+s = box.schema.space.create_ephemeral({engine = 'memtx'})
+---
+...
+s.index
+---
+- []
+...
+s.temporary
+---
+- true
+...
+s.field_count
+---
+- 0
+...
+s.engine
+---
+- memtx
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral({engine = 'other'})
+---
+- error: 'builtin/box/schema.lua:507: Error with creating space'
+...
+-- Creation with defined field_count
+s = box.schema.space.create_ephemeral({field_count = 10})
+---
+...
+s.index
+---
+- []
+...
+s.temporary
+---
+- true
+...
+s.field_count
+---
+- 10
+...
+s.engine
+---
+- memtx
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral({field_count = 'asd'})
+---
+- error: Illegal parameters, options parameter 'field_count' should be of type number
+...
+-- Creation with defined format
+format = {{name='field1', type='unsigned'}, {name='field2', type='string'}}
+---
+...
+s = box.schema.space.create_ephemeral({format = format})
+---
+...
+s.index
+---
+- []
+...
+s.temporary
+---
+- true
+...
+s.field_count
+---
+- 0
+...
+s.engine
+---
+- memtx
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral({format = 'a'})
+---
+- error: Illegal parameters, options parameter 'format' should be of type table
+...
+-- All options defined
+format = {{name='field1', type='unsigned'}, {name='field2', type='string'}}
+---
+...
+options = {engine = 'memtx', field_count = 7, format = format}
+---
+...
+s = box.schema.space.create_ephemeral(options)
+---
+...
+s.index
+---
+- []
+...
+s.temporary
+---
+- true
+...
+s.field_count
+---
+- 7
+...
+s.engine
+---
+- memtx
+...
+s:drop()
+---
+...
+-- Multiple creation and drop
+for j = 1,10 do for i=1,10 do s = box.schema.space.create_ephemeral(); s:drop(); end; collectgarbage('collect'); end
+---
+...
+-- Multiple drop
+s = box.schema.space.create_ephemeral()
+---
+...
+s:drop()
+---
+...
+s:drop()
+---
+...
+s:drop()
+---
+...
+s:drop()
+---
+...
+s:drop()
+---
+...
+-- Drop using function from box.schema
+s = box.schema.space.create_ephemeral()
+---
+...
+s.index
+---
+- []
+...
+s.temporary
+---
+- true
+...
+s.field_count
+---
+- 0
+...
+s.engine
+---
+- memtx
+...
+box.schema.space.drop_ephemeral(s)
+---
+...
+s
+---
+- []
+...
+-- Ephemeral space: methods
+format = {{name='field1', type='unsigned'}, {name='field2', type='string'}}
+---
+...
+options = {engine = 'memtx', field_count = 7, format = format}
+---
+...
+s = box.schema.space.create_ephemeral(options)
+---
+...
+s:format()
+---
+- - name: field1
+    type: unsigned
+  - name: field2
+    type: string
+...
+s:run_triggers(true)
+---
+...
+s:drop()
+---
+...
+format = {}
+---
+...
+format[1] = {name = 'aaa', type = 'unsigned'}
+---
+...
+format[2] = {name = 'bbb', type = 'unsigned'}
+---
+...
+format[3] = {name = 'ccc', type = 'unsigned'}
+---
+...
+format[4] = {name = 'ddd', type = 'unsigned'}
+---
+...
+s = box.schema.space.create_ephemeral({format = format})
+---
+...
+s:frommap({ddd = 1, aaa = 2, ccc = 3, bbb = 4})
+---
+- [2, 4, 3, 1]
+...
+s:frommap({ddd = 1, aaa = 2, bbb = 3})
+---
+- [2, 3, null, 1]
+...
+s:frommap({ddd = 1, aaa = 2, ccc = 3, eee = 4})
+---
+- null
+- Unknown field 'eee'
+...
+s:frommap()
+---
+- error: 'Usage: ephemeral_space:frommap(map, opts)'
+...
+s:frommap({})
+---
+- []
+...
+s:frommap({ddd = 1, aaa = 2, ccc = 3, bbb = 4}, {table = true})
+---
+- - 2
+  - 4
+  - 3
+  - 1
+...
+s:frommap({ddd = 1, aaa = 2, ccc = 3, bbb = 4}, {table = false})
+---
+- [2, 4, 3, 1]
+...
+s:frommap({ddd = 1, aaa = 2, ccc = 3, bbb = box.NULL})
+---
+- [2, null, 3, 1]
+...
+s:frommap({ddd = 1, aaa = 2, ccc = 3, bbb = 4}, {dummy = true})
+---
+- [2, 4, 3, 1]
+...
+s:drop()
+---
+...
diff --git a/test/box/ephemeral_space.test.lua b/test/box/ephemeral_space.test.lua
new file mode 100644
index 0000000..3cc936f
--- /dev/null
+++ b/test/box/ephemeral_space.test.lua
@@ -0,0 +1,96 @@
+-- Ephemeral space: creation and dropping
+
+-- Simple creation
+s = box.schema.space.create_ephemeral()
+s.index
+s.temporary
+s.field_count
+s.engine
+s:drop()
+
+-- Creation with defined engine (only memtx for now)
+s = box.schema.space.create_ephemeral({engine = 'memtx'})
+s.index
+s.temporary
+s.field_count
+s.engine
+s:drop()
+
+s = box.schema.space.create_ephemeral({engine = 'other'})
+
+-- Creation with defined field_count
+s = box.schema.space.create_ephemeral({field_count = 10})
+s.index
+s.temporary
+s.field_count
+s.engine
+s:drop()
+
+s = box.schema.space.create_ephemeral({field_count = 'asd'})
+
+-- Creation with defined format
+format = {{name='field1', type='unsigned'}, {name='field2', type='string'}}
+s = box.schema.space.create_ephemeral({format = format})
+s.index
+s.temporary
+s.field_count
+s.engine
+s:drop()
+
+s = box.schema.space.create_ephemeral({format = 'a'})
+
+-- All options defined
+format = {{name='field1', type='unsigned'}, {name='field2', type='string'}}
+options = {engine = 'memtx', field_count = 7, format = format}
+s = box.schema.space.create_ephemeral(options)
+s.index
+s.temporary
+s.field_count
+s.engine
+s:drop()
+
+-- Multiple creation and drop
+for j = 1,10 do for i=1,10 do s = box.schema.space.create_ephemeral(); s:drop(); end; collectgarbage('collect'); end
+
+-- Multiple drop
+s = box.schema.space.create_ephemeral()
+s:drop()
+s:drop()
+s:drop()
+s:drop()
+s:drop()
+
+-- Drop using function from box.schema
+s = box.schema.space.create_ephemeral()
+s.index
+s.temporary
+s.field_count
+s.engine
+box.schema.space.drop_ephemeral(s)
+s
+
+
+-- Ephemeral space: methods
+format = {{name='field1', type='unsigned'}, {name='field2', type='string'}}
+options = {engine = 'memtx', field_count = 7, format = format}
+s = box.schema.space.create_ephemeral(options)
+s:format()
+s:run_triggers(true)
+s:drop()
+
+format = {}
+format[1] = {name = 'aaa', type = 'unsigned'}
+format[2] = {name = 'bbb', type = 'unsigned'}
+format[3] = {name = 'ccc', type = 'unsigned'}
+format[4] = {name = 'ddd', type = 'unsigned'}
+s = box.schema.space.create_ephemeral({format = format})
+s:frommap({ddd = 1, aaa = 2, ccc = 3, bbb = 4})
+s:frommap({ddd = 1, aaa = 2, bbb = 3})
+s:frommap({ddd = 1, aaa = 2, ccc = 3, eee = 4})
+s:frommap()
+s:frommap({})
+s:frommap({ddd = 1, aaa = 2, ccc = 3, bbb = 4}, {table = true})
+s:frommap({ddd = 1, aaa = 2, ccc = 3, bbb = 4}, {table = false})
+s:frommap({ddd = 1, aaa = 2, ccc = 3, bbb = box.NULL})
+s:frommap({ddd = 1, aaa = 2, ccc = 3, bbb = 4}, {dummy = true})
+s:drop()
diff --git a/test/engine/iterator.result b/test/engine/iterator.result
index 98b0b3e..f9da661 100644
--- a/test/engine/iterator.result
+++ b/test/engine/iterator.result
@@ -4213,7 +4213,7 @@ s:replace{35}
 ...
 state, value = gen(param,state)
 ---
-- error: 'builtin/box/schema.lua:1051: usage: next(param, state)'
+- error: 'builtin/box/schema.lua:1109: usage: next(param, state)'
 ...
 value
 ---
-- 
2.7.4

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [tarantool-patches] [PATCH 4/5] Primary index for ephemeral spaces
  2018-07-12 11:16 [tarantool-patches] [PATCH 0/5] Expose ephemeral spaces into Lua imeevma
                   ` (2 preceding siblings ...)
  2018-07-12 11:16 ` [tarantool-patches] [PATCH 3/5] Ephemeral space creation and deletion in Lua imeevma
@ 2018-07-12 11:16 ` imeevma
  2018-07-13 16:32   ` [tarantool-patches] " Vladislav Shpilevoy
  2018-07-12 11:16 ` [tarantool-patches] [PATCH 5/5] Methods for ephemeral space and its index imeevma
  2018-07-12 11:30 ` [tarantool-patches] Re: [PATCH 0/5] Expose ephemeral spaces into Lua Imeev Mergen
  5 siblings, 1 reply; 12+ messages in thread
From: imeevma @ 2018-07-12 11:16 UTC (permalink / raw)
  To: tarantool-patches

Functions for creation and deletion primary index
of ephemeral space added.

Part of #3375.
---
 src/box/index_def.c               |  42 +++++++
 src/box/index_def.h               |  18 +++
 src/box/lua/schema.lua            | 130 ++++++++++++++++++++++
 src/box/lua/space.cc              |  92 ++++++++++++++++
 test/box/ephemeral_space.result   | 225 ++++++++++++++++++++++++++++++++++++--
 test/box/ephemeral_space.test.lua |  88 +++++++++++++--
 test/engine/iterator.result       |   2 +-
 7 files changed, 582 insertions(+), 15 deletions(-)

diff --git a/src/box/index_def.c b/src/box/index_def.c
index e0d54c0..9622dce 100644
--- a/src/box/index_def.c
+++ b/src/box/index_def.c
@@ -31,6 +31,8 @@
 #include "index_def.h"
 #include "schema_def.h"
 #include "identifier.h"
+#include "fiber.h"
+#include "space.h"
 
 const char *index_type_strs[] = { "HASH", "TREE", "BITSET", "RTREE" };
 
@@ -369,3 +371,43 @@ index_opts_decode(struct index_opts *opts, const char *map,
 	}
 	return 0;
 }
+
+struct index_def *
+ephemeral_index_def(struct space *space, uint32_t index_id,
+		    const char *name, const char *type_field,
+		    const char *opts_field, const char *parts)
+{
+	const struct field_def *fields = space->def->fields;
+	uint32_t field_count = space->def->field_count;
+	struct index_opts opts;
+	struct key_def *key_def = NULL;
+	uint32_t part_count = mp_decode_array(&parts);
+	enum index_type type = STR2ENUM(index_type, type_field);
+	if(index_opts_decode(&opts, opts_field, &fiber()->gc) != 0)
+		return NULL;
+	if(identifier_check(name, strlen(name)) != 0)
+		return NULL;
+	struct key_part_def *part_def = (struct key_part_def *)
+			malloc(sizeof(*part_def) * part_count);
+	if (part_def == NULL) {
+		diag_set(OutOfMemory, sizeof(*part_def) * part_count,
+			 "malloc", "key_part_def");
+		return NULL;
+	}
+	if (key_def_decode_parts(part_def, part_count, &parts,
+				 fields, field_count) != 0) {
+		free(part_def);
+		return NULL;
+	}
+	key_def = key_def_new_with_parts(part_def, part_count);
+	free(part_def);
+	if (key_def == NULL)
+		return NULL;
+	struct index_def *index_def =
+		index_def_new(0, index_id, name, strlen(name),
+			      type, &opts, key_def, NULL);
+	key_def_delete(key_def);
+	if (index_def == NULL)
+		return NULL;
+	return index_def;
+}
diff --git a/src/box/index_def.h b/src/box/index_def.h
index 4c3625b..9725a8e 100644
--- a/src/box/index_def.h
+++ b/src/box/index_def.h
@@ -39,6 +39,8 @@
 extern "C" {
 #endif /* defined(__cplusplus) */
 
+struct space;
+
 enum index_type {
 	HASH = 0, /* HASH Index */
 	TREE,     /* TREE Index */
@@ -366,6 +368,22 @@ int
 index_opts_decode(struct index_opts *opts, const char *map,
 		  struct region *region);
 
+/**
+ * Create index definition for primary index
+ * of ephemeral space in Lua
+ *
+ * @param space - ephemeral space.
+ * @param index_id - index id.
+ * @param name - name of index.
+ * @param type_field - type of field as string.
+ * @param opts_field - options in msgpack format
+ * @param parts - parts in msgpack format
+ */
+struct index_def *
+ephemeral_index_def(struct space *space, uint32_t index_id,
+		    const char *name, const char *type_field,
+		    const char *opts_field, const char *parts);
+
 #if defined(__cplusplus)
 } /* extern "C" */
 
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index 81f0fa0..0fe109a 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -527,6 +527,9 @@ box.schema.space.drop_ephemeral = function(ephemeral_space)
         return
     end
     check_param(ephemeral_space.space, 'space', 'cdata')
+    if ephemeral_space.index ~= nil and ephemeral_space.index[0] ~= nil then
+        ephemeral_space.index[0]:drop()
+    end
     space_delete_ephemeral(ephemeral_space)
     for k,_ in pairs(ephemeral_space) do
         ephemeral_space[k] = nil
@@ -1084,6 +1087,128 @@ ffi.metatype(iterator_t, {
     end;
 })
 
+local index_new_ephemeral = box.internal.space.index_new_ephemeral
+box.internal.space.index_new_ephemeral = nil
+local index_delete_ephemeral = box.internal.space.index_delete_ephemeral
+box.internal.space.index_delete_ephemeral = nil
+local index_ephemeral_methods = box.schema.index_ephemeral_methods
+box.schema.index_ephemeral_mt = nil
+local index_ephemeral_mt = {}
+
+local index_ephemeral_create = function(ephemeral_space, name, options)
+    if type(ephemeral_space) ~= 'table' then
+        error("Usage: ephemeral_space:create_index(name, opts)")
+    end
+    check_param(name, 'name', 'string')
+    check_param_table(options, create_index_template)
+    local space = ephemeral_space
+    local format = space:format()
+
+    local options_defaults = {
+        type = 'tree',
+    }
+    options = update_param_table(options, options_defaults)
+    local type_dependent_defaults = {
+        rtree = {parts = { 2, 'array' }, unique = false},
+        bitset = {parts = { 2, 'unsigned' }, unique = false},
+        other = {parts = { 1, 'unsigned' }, unique = true},
+    }
+    options_defaults = type_dependent_defaults[options.type]
+            or type_dependent_defaults.other
+    if not options.parts then
+        local fieldno = options_defaults.parts[1]
+        if #format >= fieldno then
+            local t = format[fieldno].type
+            if t ~= 'any' then
+                options.parts = {{fieldno, format[fieldno].type}}
+            end
+        end
+    end
+    options = update_param_table(options, options_defaults)
+    if space.engine == 'vinyl' then
+        options_defaults = {
+            page_size = box.cfg.vinyl_page_size,
+            range_size = box.cfg.vinyl_range_size,
+            run_count_per_level = box.cfg.vinyl_run_count_per_level,
+            run_size_ratio = box.cfg.vinyl_run_size_ratio,
+            bloom_fpr = box.cfg.vinyl_bloom_fpr
+        }
+    else
+        options_defaults = {}
+    end
+    options = update_param_table(options, options_defaults)
+
+    if ephemeral_space.index ~= nil and ephemeral_space.index[0] then
+        if options.if_not_exists then
+            return space.index[0], "not created"
+        else
+            error("Primary index for this ephemeral index already exists")
+        end
+    end
+    local iid = 0
+    if options.id and options.id ~= 0 then
+        error("Ephemeral space can have only primary index.")
+    end
+    local parts, parts_can_be_simplified =
+        update_index_parts(format, options.parts)
+    -- create_index() options contains type, parts, etc,
+    -- stored separately. Remove these members from index_opts
+    local index_opts = {
+            dimension = options.dimension,
+            unique = options.unique,
+            distance = options.distance,
+            page_size = options.page_size,
+            range_size = options.range_size,
+            run_count_per_level = options.run_count_per_level,
+            run_size_ratio = options.run_size_ratio,
+            bloom_fpr = options.bloom_fpr,
+    }
+    local field_type_aliases = {
+        num = 'unsigned'; -- Deprecated since 1.7.2
+        uint = 'unsigned';
+        str = 'string';
+        int = 'integer';
+        ['*'] = 'any';
+    };
+    for _, part in pairs(parts) do
+        local field_type = part.type:lower()
+        part.type = field_type_aliases[field_type] or field_type
+        if field_type == 'num' then
+            log.warn("field type '%s' is deprecated since Tarantool 1.7, "..
+                     "please use '%s' instead", field_type, part.type)
+        end
+    end
+    if parts_can_be_simplified then
+        parts = simplify_index_parts(parts)
+    end
+    space.space = index_new_ephemeral(space, iid, name, options.type,
+                                      msgpack.encode(index_opts),
+                                      msgpack.encode(parts))
+    space.index[iid] = {}
+    space.index[iid].unique = index_opts.unique or true
+    space.index[iid].parts = parts
+    space.index[iid].id = iid
+    space.index[iid].name = name
+    space.index[iid].type = type
+    space.index[iid].options = options
+    space.index[iid].space = space.space
+    space.index[iid].ephemeral_space = space
+    space.index[name] = space.index[iid]
+    setmetatable(space.index[iid], index_ephemeral_mt)
+    return space.index[name]
+end
+
+local index_ephemeral_drop = function(ephemeral_index)
+    if type(ephemeral_index) ~= 'table' then
+        error("Usage: ephemeral_space.index[0]:drop()")
+    end
+    ephemeral_index.ephemeral_space.space = index_delete_ephemeral(ephemeral_index)
+    ephemeral_index.ephemeral_space.index = {}
+    for k,_ in pairs(ephemeral_index) do
+        ephemeral_index[k] = nil
+    end
+end
+
 local iterator_gen = function(param, state)
     --[[
         index:pairs() mostly conforms to the Lua for-in loop conventions and
@@ -1563,6 +1688,10 @@ end
 space_mt.frommap = box.internal.space.frommap
 space_mt.__index = space_mt
 
+-- Metatable for primary index of ephemeral space
+index_ephemeral_mt.drop = index_ephemeral_drop
+index_ephemeral_mt.__index = index_ephemeral_mt
+
 -- Metatable for ephemeral space
 space_ephemeral_mt.format = function(ephemeral_space)
     return ephemeral_space.space_format
@@ -1571,6 +1700,7 @@ space_ephemeral_mt.run_triggers = function(ephemeral_space, yesno)
     builtin.space_run_triggers(ephemeral_space.space, yesno)
 end
 space_ephemeral_mt.frommap = space_ephemeral_methods.frommap
+space_ephemeral_mt.create_index = index_ephemeral_create
 space_ephemeral_mt.drop = box.schema.space.drop_ephemeral
 space_ephemeral_mt.__index = space_ephemeral_mt
 
diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc
index 955c465..4374db9 100644
--- a/src/box/lua/space.cc
+++ b/src/box/lua/space.cc
@@ -34,6 +34,7 @@
 #include "lua/utils.h"
 #include "lua/trigger.h"
 #include "box/box.h"
+#include "box/lua/misc.h" /* lbox_encode_tuple_on_gc() */
 
 extern "C" {
 	#include <lua.h>
@@ -616,6 +617,93 @@ lbox_space_delete_ephemeral(struct lua_State *L)
 }
 
 /**
+ * Create an index for ephemeral space.
+ * @param Lua table - ephemeral space.
+ * @param Lua uint32_t id - id of index, for
+ * ephemeral spaces it is 0 as they can have only
+ * primary index.
+ * @param Lua const char *name - name of index.
+ * @param Lua const char *type_field - type of field.
+ * @param Lua const char *opts_field - options in
+ * msgpack format.
+ * @param Lua const char *parts - parts in msgpack
+ * format.
+ * @retval not nil - ephemeral space created.
+ * @retval nil - error, A reason is returned in
+ *         the second value.
+ */
+static int
+lbox_index_new_ephemeral(struct lua_State *L)
+{
+	uint32_t argc = lua_gettop(L);
+	if (argc != 6)
+		return luaL_error(L, "Using: ephemeral:create_index(opts");
+	struct space *space = (struct space *)lua_checkephemeralspace(L, 1);
+	uint32_t index_id = luaL_checknumber (L, 2);
+	const char *name = luaL_checkstring (L, 3);
+	const char *type_field = luaL_checkstring (L, 4);
+	const char *opts_field = luaL_checkstring (L, 5);
+	const char *parts = luaL_checkstring (L, 6);
+	assert(index_id == 0);
+	struct space_def *ephemeral_space_def = space_def_dup(space->def);
+	if (ephemeral_space_def == NULL)
+		return luaL_error(L, "Error with creating space_def");
+	struct index_def *index_def_ephemeral =
+		ephemeral_index_def(space, index_id, name, type_field,
+				    opts_field, parts);
+	if(index_def_ephemeral == NULL)
+		return luaT_error(L);
+	if(!index_def_is_valid(index_def_ephemeral, space_name(space)) ||
+	   space_check_index_def(space, index_def_ephemeral) != 0) {
+		index_def_delete(index_def_ephemeral);
+		return luaT_error(L);
+	}
+	struct rlist key_list;
+	rlist_create(&key_list);
+	rlist_add_entry(&key_list, index_def_ephemeral, link);
+	struct space *new_space = space_new_ephemeral(ephemeral_space_def,
+						      &key_list);
+	index_def_delete(index_def_ephemeral);
+	space_def_delete(ephemeral_space_def);
+	if(new_space == NULL) {
+		return luaL_error(L, "Error with creating index for "\
+				     "ephemeral space");
+	}
+	space_delete(space);
+	struct space **ptr =
+		(struct space **) luaL_pushcdata(L, CTID_STRUCT_SPACE_POINTER);
+	*ptr = new_space;
+	return 1;
+}
+
+/**
+ * Drop primary index of ephemeral space.
+ * @param Lua ephemeral space.
+ */
+static int
+lbox_index_drop_ephemeral(struct lua_State *L)
+{
+	struct space *space = (struct space *)lua_checkephemeralspace(L, 1);
+	struct space_def *ephemeral_space_def = space_def_dup(space->def);
+	if (ephemeral_space_def == NULL)
+		return luaL_error(L, "Error with creating space_def");
+	struct rlist key_list;
+	rlist_create(&key_list);
+	struct space *new_space = space_new_ephemeral(ephemeral_space_def,
+						      &key_list);
+	space_def_delete(ephemeral_space_def);
+	if(new_space == NULL) {
+		return luaL_error(L, "Error with creating index for "\
+				     "ephemeral space");
+	}
+	space_delete(space);
+	struct space **ptr =
+		(struct space **) luaL_pushcdata(L, CTID_STRUCT_SPACE_POINTER);
+	*ptr = new_space;
+	return 1;
+}
+
+/**
  * Make a tuple or a table Lua object by map.
  * @param Lua space object.
  * @param Lua map table object.
@@ -769,12 +857,16 @@ box_lua_space_init(struct lua_State *L)
 	lua_setfield(L, -2, "SQL_BIND_PARAMETER_MAX");
 	lua_newtable(L);
 	lua_setfield(L, -2, "space_ephemeral_methods");
+	lua_newtable(L);
+	lua_setfield(L, -2, "index_ephemeral_methods");
 	lua_pop(L, 2); /* box, schema */
 
 	static const struct luaL_Reg space_internal_lib[] = {
 		{"frommap", lbox_space_frommap},
 		{"space_new_ephemeral", lbox_space_new_ephemeral},
 		{"space_delete_ephemeral", lbox_space_delete_ephemeral},
+		{"index_new_ephemeral", lbox_index_new_ephemeral},
+		{"index_delete_ephemeral", lbox_index_drop_ephemeral},
 		{NULL, NULL}
 	};
 	luaL_register(L, "box.internal.space", space_internal_lib);
diff --git a/test/box/ephemeral_space.result b/test/box/ephemeral_space.result
index 135df3c..d958ffe 100644
--- a/test/box/ephemeral_space.result
+++ b/test/box/ephemeral_space.result
@@ -1,5 +1,5 @@
--- Ephemeral space: creation and dropping
--- Simple creation
+-- Ephemeral space: creation and dropping.
+-- Simple creation.
 s = box.schema.space.create_ephemeral()
 ---
 ...
@@ -22,7 +22,7 @@ s.engine
 s:drop()
 ---
 ...
--- Creation with defined engine (only memtx for now)
+-- Creation with defined engine (only memtx for now).
 s = box.schema.space.create_ephemeral({engine = 'memtx'})
 ---
 ...
@@ -49,7 +49,7 @@ s = box.schema.space.create_ephemeral({engine = 'other'})
 ---
 - error: 'builtin/box/schema.lua:507: Error with creating space'
 ...
--- Creation with defined field_count
+-- Creation with defined field_count.
 s = box.schema.space.create_ephemeral({field_count = 10})
 ---
 ...
@@ -76,7 +76,7 @@ s = box.schema.space.create_ephemeral({field_count = 'asd'})
 ---
 - error: Illegal parameters, options parameter 'field_count' should be of type number
 ...
--- Creation with defined format
+-- Creation with defined format.
 format = {{name='field1', type='unsigned'}, {name='field2', type='string'}}
 ---
 ...
@@ -106,7 +106,7 @@ s = box.schema.space.create_ephemeral({format = 'a'})
 ---
 - error: Illegal parameters, options parameter 'format' should be of type table
 ...
--- All options defined
+-- All options defined.
 format = {{name='field1', type='unsigned'}, {name='field2', type='string'}}
 ---
 ...
@@ -185,7 +185,218 @@ s
 ---
 - []
 ...
--- Ephemeral space: methods
+-- Ephemeral space: index creation and dropping.
+s = box.schema.space.create_ephemeral()
+---
+...
+-- Simple creation
+i = s:create_index('a')
+---
+...
+i.unique
+---
+- true
+...
+i.parts
+---
+- - - 0
+    - unsigned
+...
+i.id
+---
+- 0
+...
+i.name
+---
+- a
+...
+i:drop()
+---
+...
+-- Double creation of index for ephemeral space.
+i = s:create_index('a')
+---
+...
+i = s:create_index('a')
+---
+- error: 'builtin/box/schema.lua:1145: Primary index for this ephemeral index already
+    exists'
+...
+i:drop()
+---
+...
+-- Double creation of index for ephemeral space
+-- but with if_not_exists=true.
+i = s:create_index('a')
+---
+...
+i = s:create_index('a', {if_not_exists=true})
+---
+...
+i:drop()
+---
+...
+-- Ephemeral space can have only primary.
+-- index so its id == 0
+i = s:create_index('a', {id = 10})
+---
+- error: 'builtin/box/schema.lua:1150: Ephemeral space can have only primary index.'
+...
+-- Set parameters: parts.
+i = s:create_index('a', {parts={1}})
+---
+...
+i.unique
+---
+- true
+...
+i.parts
+---
+- - - 0
+    - scalar
+...
+i.id
+---
+- 0
+...
+i.name
+---
+- a
+...
+i:drop()
+---
+...
+i = s:create_index('a', {parts={1,"integer"}})
+---
+...
+i.unique
+---
+- true
+...
+i.parts
+---
+- - - 0
+    - integer
+...
+i.id
+---
+- 0
+...
+i.name
+---
+- a
+...
+i:drop()
+---
+...
+i = s:create_index('a', {parts={1,'string',collation='Unicode'}})
+---
+...
+i.unique
+---
+- true
+...
+i.parts
+---
+- - - 0
+    - string
+...
+i.id
+---
+- 0
+...
+i.name
+---
+- a
+...
+i:drop()
+---
+...
+i = s:create_index('a', {parts={1,'uint',2,'int',3,'str'}})
+---
+...
+i.unique
+---
+- true
+...
+i.parts
+---
+- - - 0
+    - unsigned
+  - - 1
+    - integer
+  - - 2
+    - string
+...
+i.id
+---
+- 0
+...
+i.name
+---
+- a
+...
+i:drop()
+---
+...
+i = s:create_index('a', {parts={{5,'string',collation='Unicode'}}})
+---
+...
+i.unique
+---
+- true
+...
+i.parts
+---
+- - field: 4
+    collation: 1
+    type: string
+...
+i.id
+---
+- 0
+...
+i.name
+---
+- a
+...
+i:drop()
+---
+...
+i = s:create_index('a', {parts={3,"scalar"}})
+---
+...
+i.unique
+---
+- true
+...
+i.parts
+---
+- - - 2
+    - scalar
+...
+i.id
+---
+- 0
+...
+i.name
+---
+- a
+...
+i:drop()
+---
+...
+i = s:create_index('a', {parts={{5,'uint',collation='Unicode'}}})
+---
+- error: 'Wrong index options (field 1): collation is reasonable only for string and
+    scalar parts'
+...
+i = s:create_index('a', {type='bitset',parts={1,'unsigned',2,'unsigned'}})
+---
+- error: 'Can''t create or modify index ''a'' in space ''ephemeral'': primary key
+    must be unique'
+...
+-- Ephemeral space: methods.
 format = {{name='field1', type='unsigned'}, {name='field2', type='string'}}
 ---
 ...
diff --git a/test/box/ephemeral_space.test.lua b/test/box/ephemeral_space.test.lua
index 3cc936f..211014c 100644
--- a/test/box/ephemeral_space.test.lua
+++ b/test/box/ephemeral_space.test.lua
@@ -1,6 +1,6 @@
--- Ephemeral space: creation and dropping
+-- Ephemeral space: creation and dropping.
 
--- Simple creation
+-- Simple creation.
 s = box.schema.space.create_ephemeral()
 s.index
 s.temporary
@@ -8,7 +8,7 @@ s.field_count
 s.engine
 s:drop()
 
--- Creation with defined engine (only memtx for now)
+-- Creation with defined engine (only memtx for now).
 s = box.schema.space.create_ephemeral({engine = 'memtx'})
 s.index
 s.temporary
@@ -18,7 +18,7 @@ s:drop()
 
 s = box.schema.space.create_ephemeral({engine = 'other'})
 
--- Creation with defined field_count
+-- Creation with defined field_count.
 s = box.schema.space.create_ephemeral({field_count = 10})
 s.index
 s.temporary
@@ -28,7 +28,7 @@ s:drop()
 
 s = box.schema.space.create_ephemeral({field_count = 'asd'})
 
--- Creation with defined format
+-- Creation with defined format.
 format = {{name='field1', type='unsigned'}, {name='field2', type='string'}}
 s = box.schema.space.create_ephemeral({format = format})
 s.index
@@ -39,7 +39,7 @@ s:drop()
 
 s = box.schema.space.create_ephemeral({format = 'a'})
 
--- All options defined
+-- All options defined.
 format = {{name='field1', type='unsigned'}, {name='field2', type='string'}}
 options = {engine = 'memtx', field_count = 7, format = format}
 s = box.schema.space.create_ephemeral(options)
@@ -70,7 +70,81 @@ box.schema.space.drop_ephemeral(s)
 s
 
 
--- Ephemeral space: methods
+-- Ephemeral space: index creation and dropping.
+s = box.schema.space.create_ephemeral()
+
+-- Simple creation
+i = s:create_index('a')
+i.unique
+i.parts
+i.id
+i.name
+i:drop()
+
+-- Double creation of index for ephemeral space.
+i = s:create_index('a')
+i = s:create_index('a')
+i:drop()
+
+-- Double creation of index for ephemeral space
+-- but with if_not_exists=true.
+i = s:create_index('a')
+i = s:create_index('a', {if_not_exists=true})
+i:drop()
+
+-- Ephemeral space can have only primary.
+-- index so its id == 0
+i = s:create_index('a', {id = 10})
+
+-- Set parameters: parts.
+i = s:create_index('a', {parts={1}})
+i.unique
+i.parts
+i.id
+i.name
+i:drop()
+
+i = s:create_index('a', {parts={1,"integer"}})
+i.unique
+i.parts
+i.id
+i.name
+i:drop()
+
+i = s:create_index('a', {parts={1,'string',collation='Unicode'}})
+i.unique
+i.parts
+i.id
+i.name
+i:drop()
+
+i = s:create_index('a', {parts={1,'uint',2,'int',3,'str'}})
+i.unique
+i.parts
+i.id
+i.name
+i:drop()
+
+i = s:create_index('a', {parts={{5,'string',collation='Unicode'}}})
+i.unique
+i.parts
+i.id
+i.name
+i:drop()
+
+i = s:create_index('a', {parts={3,"scalar"}})
+i.unique
+i.parts
+i.id
+i.name
+i:drop()
+
+i = s:create_index('a', {parts={{5,'uint',collation='Unicode'}}})
+
+i = s:create_index('a', {type='bitset',parts={1,'unsigned',2,'unsigned'}})
+
+
+-- Ephemeral space: methods.
 format = {{name='field1', type='unsigned'}, {name='field2', type='string'}}
 options = {engine = 'memtx', field_count = 7, format = format}
 s = box.schema.space.create_ephemeral(options)
diff --git a/test/engine/iterator.result b/test/engine/iterator.result
index f9da661..e1bc7ca 100644
--- a/test/engine/iterator.result
+++ b/test/engine/iterator.result
@@ -4213,7 +4213,7 @@ s:replace{35}
 ...
 state, value = gen(param,state)
 ---
-- error: 'builtin/box/schema.lua:1109: usage: next(param, state)'
+- error: 'builtin/box/schema.lua:1234: usage: next(param, state)'
 ...
 value
 ---
-- 
2.7.4

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [tarantool-patches] [PATCH 5/5] Methods for ephemeral space and its index
  2018-07-12 11:16 [tarantool-patches] [PATCH 0/5] Expose ephemeral spaces into Lua imeevma
                   ` (3 preceding siblings ...)
  2018-07-12 11:16 ` [tarantool-patches] [PATCH 4/5] Primary index for ephemeral spaces imeevma
@ 2018-07-12 11:16 ` imeevma
  2018-07-13 16:32   ` [tarantool-patches] " Vladislav Shpilevoy
  2018-07-12 11:30 ` [tarantool-patches] Re: [PATCH 0/5] Expose ephemeral spaces into Lua Imeev Mergen
  5 siblings, 1 reply; 12+ messages in thread
From: imeevma @ 2018-07-12 11:16 UTC (permalink / raw)
  To: tarantool-patches

This patch defines most methods for index of
ephemeral space and ephemeral space.

Closes #3375.
---
 src/box/box.cc                    |   62 +
 src/box/box.h                     |    9 +
 src/box/index.cc                  |  172 +
 src/box/index.h                   |  140 +
 src/box/lua/info.h                |    4 -
 src/box/lua/schema.lua            |  122 +
 src/box/lua/space.cc              |  396 +-
 test/box/ephemeral_space.result   | 7979 +++++++++++++++++++++++++++++++++++++
 test/box/ephemeral_space.test.lua | 1694 ++++++++
 9 files changed, 10573 insertions(+), 5 deletions(-)

diff --git a/src/box/box.cc b/src/box/box.cc
index 795e3ee..e825735 100644
--- a/src/box/box.cc
+++ b/src/box/box.cc
@@ -1171,6 +1171,68 @@ box_upsert(uint32_t space_id, uint32_t index_id, const char *tuple,
 }
 
 int
+box_ephemeral_select(struct space *space, uint32_t index_id,
+		     int iterator, uint32_t offset, uint32_t limit,
+		     const char *key, const char *key_end,
+		     struct port *port)
+{
+	(void)key_end;
+
+	rmean_collect(rmean_box, IPROTO_SELECT, 1);
+
+	if (iterator < 0 || iterator >= iterator_type_MAX) {
+		diag_set(ClientError, ER_ILLEGAL_PARAMS,
+			 "Invalid iterator type");
+		diag_log();
+		return -1;
+	}
+
+	struct index *index = index_find(space, index_id);
+	if (index == NULL)
+		return -1;
+
+	enum iterator_type type = (enum iterator_type) iterator;
+	uint32_t part_count = key ? mp_decode_array(&key) : 0;
+	if (key_validate(index->def, type, key, part_count))
+		return -1;
+
+	ERROR_INJECT(ERRINJ_TESTING, {
+		diag_set(ClientError, ER_INJECTION, "ERRINJ_TESTING");
+		return -1;
+	});
+
+	struct iterator *it = index_create_iterator(index, type,
+						    key, part_count);
+	if (it == NULL)
+		return -1;
+
+	int rc = 0;
+	uint32_t found = 0;
+	struct tuple *tuple;
+	port_tuple_create(port);
+	while (found < limit) {
+		rc = iterator_next(it, &tuple);
+		if (rc != 0 || tuple == NULL)
+			break;
+		if (offset > 0) {
+			offset--;
+			continue;
+		}
+		rc = port_tuple_add(port, tuple);
+		if (rc != 0)
+			break;
+		found++;
+	}
+	iterator_delete(it);
+
+	if (rc != 0) {
+		port_destroy(port);
+		return -1;
+	}
+	return 0;
+}
+
+int
 box_ephemeral_insert(struct space *space, const char *tuple,
 		     const char *tuple_end, box_tuple_t **result)
 {
diff --git a/src/box/box.h b/src/box/box.h
index a00a842..30f447b 100644
--- a/src/box/box.h
+++ b/src/box/box.h
@@ -402,6 +402,15 @@ int
 box_process1(struct request *request, box_tuple_t **result);
 
 /**
+ * Select all tuples from ephemeral space.
+ */
+int
+box_ephemeral_select(struct space *space, uint32_t index_id,
+		     int iterator, uint32_t offset, uint32_t limit,
+		     const char *key, const char *key_end,
+		     struct port *port);
+
+/**
  * Used to prepare request for inserting tuple into
  * ephemeral space and call box_process_rw().
  */
diff --git a/src/box/index.cc b/src/box/index.cc
index 188995e..d2d209f 100644
--- a/src/box/index.cc
+++ b/src/box/index.cc
@@ -354,6 +354,131 @@ box_index_count(uint32_t space_id, uint32_t index_id, int type,
 	return count;
 }
 
+ssize_t
+box_ephemeral_index_len(struct space *space, uint32_t index_id)
+{
+	struct index *index = space_index(space, index_id);
+	if (index == NULL)
+		return 0;
+	return index_size(index);
+}
+
+ssize_t
+box_ephemeral_index_bsize(struct space *space, uint32_t index_id)
+{
+	struct index *index = space_index(space, index_id);
+	if (index == NULL)
+		return 0;
+	return index_bsize(index);
+}
+
+int
+box_ephemeral_index_random(struct space *space, uint32_t index_id,
+			   uint32_t rnd, box_tuple_t **result)
+{
+	assert(result != NULL);
+	struct index *index = index_find(space, index_id);
+	if (index == NULL || index_random(index, rnd, result) != 0)
+		return -1;
+	if (*result != NULL)
+		tuple_bless(*result);
+	return 0;
+}
+
+int
+box_ephemeral_index_get(struct space *space, uint32_t index_id, const char *key,
+			const char *key_end, box_tuple_t **result)
+{
+	assert(key != NULL && key_end != NULL && result != NULL);
+	mp_tuple_assert(key, key_end);
+	struct index *index = index_find(space, index_id);
+	if (index == NULL)
+		return -1;
+	if (!index->def->opts.is_unique) {
+		diag_set(ClientError, ER_MORE_THAN_ONE_TUPLE);
+		return -1;
+	}
+	uint32_t part_count = mp_decode_array(&key);
+	if (exact_key_validate(index->def->key_def, key, part_count))
+		return -1;
+	if (index_get(index, key, part_count, result) != 0)
+		return -1;
+	rmean_collect(rmean_box, IPROTO_SELECT, 1);
+	if (*result != NULL)
+		tuple_bless(*result);
+	return 0;
+}
+
+int
+box_ephemeral_index_min(struct space *space, uint32_t index_id, const char *key,
+			const char *key_end, box_tuple_t **result)
+{
+	assert(key != NULL && key_end != NULL && result != NULL);
+	mp_tuple_assert(key, key_end);
+	struct index *index = index_find(space, index_id);
+	if (index == NULL)
+		return -1;
+	if (index->def->type != TREE) {
+		diag_set(UnsupportedIndexFeature, index->def, "min()");
+		return -1;
+	}
+	uint32_t part_count = mp_decode_array(&key);
+	if (key_validate(index->def, ITER_GE, key, part_count))
+		return -1;
+	if (index_min(index, key, part_count, result) != 0)
+		return -1;
+	if (*result != NULL)
+		tuple_bless(*result);
+	return 0;
+}
+
+int
+box_ephemeral_index_max(struct space *space, uint32_t index_id, const char *key,
+			const char *key_end, box_tuple_t **result)
+{
+	mp_tuple_assert(key, key_end);
+	assert(result != NULL);
+	struct index *index = index_find(space, index_id);
+	if (index == NULL)
+		return -1;
+	if (index->def->type != TREE) {
+		diag_set(UnsupportedIndexFeature, index->def, "max()");
+		return -1;
+	}
+	uint32_t part_count = mp_decode_array(&key);
+	if (key_validate(index->def, ITER_LE, key, part_count))
+		return -1;
+	if (index_max(index, key, part_count, result) != 0)
+		return -1;
+	if (*result != NULL)
+		tuple_bless(*result);
+	return 0;
+}
+
+ssize_t
+box_ephemeral_index_count(struct space *space, uint32_t index_id, int type,
+			  const char *key, const char *key_end)
+{
+	assert(key != NULL && key_end != NULL);
+	mp_tuple_assert(key, key_end);
+	if (type < 0 || type >= iterator_type_MAX) {
+		diag_set(ClientError, ER_ILLEGAL_PARAMS,
+			 "Invalid iterator type");
+		return -1;
+	}
+	enum iterator_type itype = (enum iterator_type) type;
+	struct index *index = index_find(space, index_id);
+	if (index == NULL)
+		return -1;
+	uint32_t part_count = mp_decode_array(&key);
+	if (key_validate(index->def, itype, key, part_count))
+		return -1;
+	ssize_t count = index_count(index, itype, key, part_count);
+	if (count < 0)
+		return -1;
+	return count;
+}
+
 /* }}} */
 
 /* {{{ Iterators ************************************************/
@@ -408,6 +533,32 @@ box_iterator_free(box_iterator_t *it)
 	iterator_delete(it);
 }
 
+box_iterator_t *
+box_ephemeral_index_iterator(struct space *space, uint32_t index_id, int type,
+			     const char *key, const char *key_end)
+{
+	assert(key != NULL && key_end != NULL);
+	mp_tuple_assert(key, key_end);
+	if (type < 0 || type >= iterator_type_MAX) {
+		diag_set(ClientError, ER_ILLEGAL_PARAMS,
+			 "Invalid iterator type");
+		return NULL;
+	}
+	struct index *index = index_find(space, index_id);
+	if (index == NULL)
+		return NULL;
+	enum iterator_type itype = (enum iterator_type) type;
+	assert(mp_typeof(*key) == MP_ARRAY); /* checked by Lua */
+	uint32_t part_count = mp_decode_array(&key);
+	if (key_validate(index->def, itype, key, part_count))
+		return NULL;
+	struct iterator *it = index_create_iterator(index, itype,
+						    key, part_count);
+	if (it == NULL)
+		return NULL;
+	return it;
+}
+
 /* }}} */
 
 /* {{{ Other index functions */
@@ -435,6 +586,27 @@ box_index_compact(uint32_t space_id, uint32_t index_id)
 	return 0;
 }
 
+int
+box_ephemeral_index_stat(struct space *space, uint32_t index_id,
+			 struct info_handler *info)
+{
+	struct index *index = index_find(space, index_id);
+	if(index == NULL)
+		return -1;
+	index_stat(index, info);
+	return 0;
+}
+
+int
+box_ephemeral_index_compact(struct space *space, uint32_t index_id)
+{
+	struct index *index = index_find(space, index_id);
+	if(index == NULL)
+		return -1;
+	index_compact(index);
+	return 0;
+}
+
 /* }}} */
 
 /* {{{ Internal API */
diff --git a/src/box/index.h b/src/box/index.h
index 686e7a1..ffcb9fc 100644
--- a/src/box/index.h
+++ b/src/box/index.h
@@ -235,6 +235,146 @@ box_index_stat(uint32_t space_id, uint32_t index_id,
 int
 box_index_compact(uint32_t space_id, uint32_t index_id);
 
+/**
+ * Return the number of element in the index.
+ * If space have no index it returns 0.
+ * 
+ * \param space - ephemeral space.
+ * \param index_id - index identifier.
+ * \retval >= 0.
+ */
+ssize_t
+box_ephemeral_index_len(struct space *space, uint32_t index_id);
+
+/**
+ * Return the number of bytes used in memory by the index.
+ * If space have no index it returns 0.
+ * 
+ * \param space - ephemeral space.
+ * \param index_id - index identifier.
+ * \retval >= 0.
+ */
+ssize_t
+box_ephemeral_index_bsize(struct space *space, uint32_t index_id);
+
+/**
+ * Return a random tuple from the index.
+ * 
+ * \param space - ephemeral space.
+ * \param index_id - index identifier.
+ * \param rnd - random seed.
+ * \param[out] - result a tuple or NULL if index is empty.
+ * \retval -1 on error.
+ * \retval 0 on success.
+ */
+int
+box_ephemeral_index_random(struct space *space, uint32_t index_id,
+			   uint32_t rnd, box_tuple_t **result);
+
+/**
+ * Get a tuple from index by the key.
+ * 
+ * \param space - ephemeral space.
+ * \param index_id - index identifier.
+ * \param key encoded key in MsgPack Array format.
+ * \param key_end - the end of encoded \a key.
+ * \param[out] result - a tuple or NULL if index is empty.
+ * \retval -1 on error.
+ * \retval 0 on success.
+ * \pre key != NULL
+ */
+int
+box_ephemeral_index_get(struct space *space, uint32_t index_id, const char *key,
+			const char *key_end, box_tuple_t **result);
+
+/**
+ * Return a first (minimal) tuple matched the provided key.
+ * 
+ * \param space - ephemeral space.
+ * \param index_id - index identifier.
+ * \param key - encoded key in MsgPack Array format.
+ * \param key_end - the end of encoded \a key.
+ * \param[out] - result a tuple or NULL if index is empty.
+ * \retval -1 on error.
+ * \retval 0 on success.
+ */
+int
+box_ephemeral_index_min(struct space *space, uint32_t index_id, const char *key,
+			const char *key_end, box_tuple_t **result);
+
+/**
+ * Count the number of tuple matched the provided key.
+ * 
+ * \param space - ephemeral space.
+ * \param index_id - index identifier.
+ * \param key - encoded key in MsgPack Array format.
+ * \param key_end - the end of encoded \a key.
+ * \param[out] - result a tuple or NULL if index is empty.
+ * \retval -1 on error.
+ * \retval 0 on success.
+ */
+int
+box_ephemeral_index_max(struct space *space, uint32_t index_id, const char *key,
+			const char *key_end, box_tuple_t **result);
+
+/**
+ * Return a last (maximal) tuple matched the provided key.
+ * 
+ * \param space - ephemeral space.
+ * \param index_id - index identifier.
+ * \param key - encoded key in MsgPack Array format.
+ * \param key_end - the end of encoded \a key.
+ * \param[out] - result a tuple or NULL if index is empty.
+ * \retval -1 on error.
+ * \retval 0 on success.
+ */
+ssize_t
+box_ephemeral_index_count(struct space *space, uint32_t index_id, int type,
+			  const char *key, const char *key_end);
+
+/**
+ * Allocate and initialize iterator for ephemeral space
+ *
+ * A returned iterator must be destroyed by box_iterator_free().
+ *
+ * \param space ephemeral space.
+ * \param index_id index identifier.
+ * \param type \link iterator_type iterator type \endlink
+ * \param key encoded key in MsgPack Array format ([part1, part2, ...]).
+ * \param key_end the end of encoded \a key
+ * \retval NULL on error (check box_error_last())
+ * \retval iterator otherwise
+ * \sa box_iterator_next()
+ * \sa box_iterator_free()
+ */
+box_iterator_t *
+box_ephemeral_index_iterator(struct space *space, uint32_t index_id, int type,
+			     const char *key, const char *key_end);
+
+/**
+ * Index statistics.
+ * 
+ * \param space - ephemeral space.
+ * \param index_id - index identifier.
+ * \param info - info handler
+ * \retval -1 on error.
+ * \retval 0 on success.
+ */
+int
+box_ephemeral_index_stat(struct space *space, uint32_t index_id,
+			 struct info_handler *info);
+
+/**
+ * Trigger index compaction.
+ * 
+ * \param space - ephemeral space.
+ * \param index_id - index identifier.
+ * \retval -1 on error.
+ * \retval 0 on success.
+ */
+int
+box_ephemeral_index_compact(struct space *space, uint32_t index_id);
+
 struct iterator {
 	/**
 	 * Iterate to the next tuple.
diff --git a/src/box/lua/info.h b/src/box/lua/info.h
index 78cd9e6..bf4c613 100644
--- a/src/box/lua/info.h
+++ b/src/box/lua/info.h
@@ -49,8 +49,4 @@ luaT_info_handler_create(struct info_handler *h, struct lua_State *L);
 } /* extern "C" */
 #endif /* defined(__cplusplus) */
 
-#if defined(__cplusplus)
-} /* extern "C" */
-#endif /* defined(__cplusplus) */
-
 #endif /* INCLUDES_TARANTOOL_LUA_INFO_H */
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index 0fe109a..24e033e 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -1689,6 +1689,70 @@ space_mt.frommap = box.internal.space.frommap
 space_mt.__index = space_mt
 
 -- Metatable for primary index of ephemeral space
+index_ephemeral_mt.len = function(index)
+    local ret = index_ephemeral_methods.len(index)
+    if ret == -1 then
+        box.error()
+    end
+    return tonumber(ret)
+end
+index_ephemeral_mt.bsize = function(index)
+    local ret = index_ephemeral_methods.bsize(index)
+    if ret == -1 then
+        box.error()
+    end
+    return tonumber(ret)
+end
+index_ephemeral_mt.__len = index_ephemeral_mt.len
+index_ephemeral_mt.min = function(index, key)
+    key = keify(key)
+    return index_ephemeral_methods.min(index, key);
+end
+index_ephemeral_mt.max = function(index, key)
+    key = keify(key)
+    return index_ephemeral_methods.max(index, key);
+end
+index_ephemeral_mt.random = function(index, rnd)
+    rnd = rnd or math.random()
+    return index_ephemeral_methods.random(index, rnd);
+end
+index_ephemeral_mt.pairs = function(index, key, opts)
+    key = keify(key)
+    local itype = check_iterator_type(opts, #key == 0);
+    local keymp = msgpack.encode(key)
+    local keybuf = ffi.string(keymp, #keymp)
+    local cdata = index_ephemeral_methods.iterator(index, itype, keymp);
+    return fun.wrap(iterator_gen_luac, keybuf,
+        ffi.gc(cdata, builtin.box_iterator_free))
+end
+index_ephemeral_mt.count = function(index, key, opts)
+    key = keify(key)
+    local itype = check_iterator_type(opts, #key == 0);
+    return index_ephemeral_methods.count(index, itype, key);
+end
+index_ephemeral_mt.select = function(index, key, opts)
+    local key = keify(key)
+    local iterator, offset, limit = check_select_opts(opts, #key == 0)
+    return index_ephemeral_methods.select(index, iterator, offset, limit, key)
+end
+index_ephemeral_mt.get = function(index, key)
+    key = keify(key)
+    return index_ephemeral_methods.get(index, key)
+end
+index_ephemeral_mt.update = function(index, key, ops)
+    key = keify(key)
+    return index_ephemeral_methods.update(index, key, ops)
+end
+index_ephemeral_mt.delete = function(index, key)
+    key = keify(key)
+    return index_ephemeral_methods.delete(index, key)
+end
+index_ephemeral_mt.stat = function(index)
+    return index_ephemeral_methods.stat(index)
+end
+index_ephemeral_mt.compact = function(index)
+    return index_ephemeral_methods.compact(index)
+end
 index_ephemeral_mt.drop = index_ephemeral_drop
 index_ephemeral_mt.__index = index_ephemeral_mt
 
@@ -1699,9 +1763,67 @@ end
 space_ephemeral_mt.run_triggers = function(ephemeral_space, yesno)
     builtin.space_run_triggers(ephemeral_space.space, yesno)
 end
+space_ephemeral_mt.auto_increment = function(ephemeral_space, tuple)
+    local max_tuple = check_primary_index(ephemeral_space):max()
+    local max = 0
+    if max_tuple ~= nil then
+        max = max_tuple[1]
+    end
+    table.insert(tuple, 1, max + 1)
+    return ephemeral_space:insert(tuple)
+end
+space_ephemeral_mt.insert = function(ephemeral_space, tuple)
+    return space_ephemeral_methods.insert(ephemeral_space, tuple);
+end
+space_ephemeral_mt.replace = function(ephemeral_space, tuple)
+    return space_ephemeral_methods.replace(ephemeral_space, tuple);
+end
+space_ephemeral_mt.upsert = function(ephemeral_space, tuple_key, ops, deprecated)
+    if deprecated ~= nil then
+        local msg = "Error: extra argument in upsert call: "
+        msg = msg .. tostring(deprecated)
+        msg = msg .. ". Usage :upsert(tuple, operations)"
+        box.error(box.error.PROC_LUA, msg)
+    end
+    return space_ephemeral_methods.upsert(ephemeral_space, tuple_key, ops);
+end
+space_ephemeral_mt.bsize = function(ephemeral_space)
+    if ephemeral_space == nil or ephemeral_space.space == nil then
+        box.error(box.error.NO_SUCH_SPACE, "ephemeral")
+    end
+    return builtin.space_bsize(ephemeral_space.space)
+end
+
+space_ephemeral_mt.pairs = function(ephemeral_space, key, opts)
+    local pk = ephemeral_space.index[0]
+    if pk == nil then
+        -- empty space without indexes, return empty iterator
+        return fun.iter({})
+    end
+    return pk:pairs(key, opts)
+end
+space_ephemeral_mt.__pairs = space_ephemeral_mt.pairs -- Lua 5.2 compatibility
+space_ephemeral_mt.__ipairs = space_ephemeral_mt.pairs -- Lua 5.2 compatibility
+space_ephemeral_mt.truncate = function(ephemeral_space)
+    if ephemeral_space.index == nil or ephemeral_space.index[0] == nil then
+        return
+    end
+    local index = ephemeral_space.index[0]
+    local name = index.name
+    local options = index.options
+    index:drop()
+    ephemeral_space:create_index(name, options)
+end
 space_ephemeral_mt.frommap = space_ephemeral_methods.frommap
 space_ephemeral_mt.create_index = index_ephemeral_create
 space_ephemeral_mt.drop = box.schema.space.drop_ephemeral
+space_ephemeral_mt.put = space_ephemeral_methods.replace
+space_ephemeral_mt.get = index_ephemeral_mt.get
+space_ephemeral_mt.select = index_ephemeral_mt.select
+space_ephemeral_mt.update = index_ephemeral_mt.update
+space_ephemeral_mt.delete = index_ephemeral_mt.delete
+space_ephemeral_mt.count = index_ephemeral_mt.count
+space_ephemeral_mt.len = index_ephemeral_mt.len
 space_ephemeral_mt.__index = space_ephemeral_mt
 
 box.schema.index_mt = base_index_mt
diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc
index 4374db9..81b9f3c 100644
--- a/src/box/lua/space.cc
+++ b/src/box/lua/space.cc
@@ -35,6 +35,8 @@
 #include "lua/trigger.h"
 #include "box/box.h"
 #include "box/lua/misc.h" /* lbox_encode_tuple_on_gc() */
+#include "box/info.h"
+#include "box/lua/info.h"
 
 extern "C" {
 	#include <lua.h>
@@ -51,8 +53,10 @@ extern "C" {
 #include "box/sequence.h"
 #include "box/coll_id_cache.h"
 #include "box/replication.h" /* GROUP_LOCAL */
+#include "box/port.h"
 
 static uint32_t CTID_STRUCT_SPACE_POINTER = 0;
+static uint32_t CTID_STRUCT_ITERATOR_REF = 0;
 
 /**
  * Trigger function for all spaces
@@ -704,6 +708,370 @@ lbox_index_drop_ephemeral(struct lua_State *L)
 }
 
 /**
+ * Insert tuple into ephemeral space.
+ * 
+ * @param Lua ephemeral space.
+ * @param Lua tuple.
+ * @retval not nil - tuple inserted.
+ * @retval nil - error, A reason is returned in
+ *         the second value.
+ */
+static int
+lbox_ephemeral_space_insert(lua_State *L)
+{
+	if (lua_gettop(L) != 2 || !lua_istable(L, 1))
+		return luaL_error(L, "Usage space:insert(tuple)");
+	struct space *space = (struct space *)lua_checkephemeralspace(L, 1);
+	size_t tuple_len;
+	const char *tuple = lbox_encode_tuple_on_gc(L, 2, &tuple_len);
+	struct tuple *result;
+	if (box_ephemeral_insert(space, tuple, tuple + tuple_len, &result) != 0)
+		return luaT_error(L);
+	return luaT_pushtupleornil(L, result);
+}
+
+/**
+ * Replace tuple from ephemeral space.
+ * 
+ * @param Lua ephemeral space.
+ * @param Lua tuple - tuple to insert.
+ * @retval not nil - tuple replaced.
+ * @retval nil - error, A reason is returned in
+ *         the second value.
+ */
+static int
+lbox_ephemeral_space_replace(lua_State *L)
+{
+	if (lua_gettop(L) != 2 || !lua_istable(L, 1))
+		return luaL_error(L, "Usage space:replace(tuple)");
+	struct space *space = (struct space *)lua_checkephemeralspace(L, 1);
+	size_t tuple_len;
+	const char *tuple = lbox_encode_tuple_on_gc(L, 2, &tuple_len);
+	struct tuple *result;
+	if (box_ephemeral_replace(space, tuple, tuple + tuple_len,
+				  &result) != 0)
+		return luaT_error(L);
+	return luaT_pushtupleornil(L, result);
+}
+
+/**
+ * Insert or update tuple from ephemeral space.
+ * 
+ * @param Lua ephemeral space.
+ * @param Lua tuple - key or tuple to insert.
+ * @param Lua tuple - operaions in case of update .
+ * @retval not nil - tuple upserted.
+ * @retval nil - error, A reason is returned in
+ *         the second value.
+ */
+static int
+lbox_ephemeral_space_upsert(lua_State *L)
+{
+	if (lua_gettop(L) != 3 || !lua_istable(L, 1) ||
+	    (lua_type(L, 2) != LUA_TTABLE && luaT_istuple(L, 2) == NULL) ||
+	    (lua_type(L, 3) != LUA_TTABLE && luaT_istuple(L, 3) == NULL))
+		return luaL_error(L, "Usage index:upsert(key, ops)");
+	struct space *space = (struct space *)lua_checkephemeralspace(L, 1);
+	size_t key_len;
+	const char *key = lbox_encode_tuple_on_gc(L, 2, &key_len);
+	size_t ops_len;
+	const char *ops = lbox_encode_tuple_on_gc(L, 3, &ops_len);
+	struct tuple *result;
+	if (box_ephemeral_upsert(space, 0, key, key + key_len,
+				 ops, ops + ops_len, 1, &result) != 0)
+		return luaT_error(L);
+	return luaT_pushtupleornil(L, result);
+}
+
+/**
+ * Return the number of element in the index.
+ * 
+ * @param Lua ephemeral space.
+ * @retval number - number of element in the index.
+ */
+static int
+lbox_ephemeral_index_len(struct lua_State *L)
+{
+	if (lua_gettop(L) != 1)
+		return luaL_error(L, "Usage: index:len()");
+	struct space *space = (struct space *)lua_checkephemeralspace(L, 1);
+	lua_pushnumber(L, box_ephemeral_index_len(space, 0));
+	return 1;
+}
+
+/**
+ * Return the number of bytes used in memory by the
+ * index.
+ * 
+ * @param Lua ephemeral space.
+ * @retval number - number of bytes used in memory
+ * by the index.
+ */
+static int
+lbox_ephemeral_index_bsize(struct lua_State *L)
+{
+	if (lua_gettop(L) != 1)
+		return luaL_error(L, "Usage: index:bsize()");
+	struct space *space = (struct space *)lua_checkephemeralspace(L, 1);
+	lua_pushnumber(L, box_ephemeral_index_bsize(space, 0));
+	return 1;
+}
+
+/**
+ * Return a random tuple from the index.
+ * 
+ * @param Lua ephemeral space.
+ * @param Lua number - seed.
+ * @retval tuple or nil.
+ */
+static int
+lbox_ephemeral_index_random(lua_State *L)
+{
+	if (lua_gettop(L) != 2 || !lua_istable(L, 1))
+		return luaL_error(L, "Usage index:random(seed)");
+	struct space *space = (struct space *)lua_checkephemeralspace(L, 1);
+	uint32_t rnd = luaL_checknumber (L, 2);
+	struct tuple *result;
+	if (box_ephemeral_index_random(space, 0, rnd, &result) != 0)
+		return luaT_error(L);
+	return luaT_pushtupleornil(L, result);
+}
+
+/**
+ * Return a tuple from the index by given key.
+ * 
+ * @param Lua ephemeral space.
+ * @param Lua tuple - key.
+ * @retval tuple or nil.
+ */
+static int
+lbox_ephemeral_index_get(lua_State *L)
+{
+	if (lua_gettop(L) != 2 || !lua_istable(L, 1))
+		return luaL_error(L, "Usage index:get(key)");
+	struct space *space = (struct space *)lua_checkephemeralspace(L, 1);
+	size_t key_len;
+	struct tuple *result;
+	const char *key = lbox_encode_tuple_on_gc(L, 2, &key_len);
+	if (box_ephemeral_index_get(space, 0, key, key + key_len, &result) != 0)
+		return luaT_error(L);
+	return luaT_pushtupleornil(L, result);
+}
+
+static inline void
+lbox_port_to_table(lua_State *L, struct port *port_base)
+{
+	struct port_tuple *port = port_tuple(port_base);
+	lua_createtable(L, port->size, 0);
+	struct port_tuple_entry *entry = port->first;
+	for (int i = 0 ; i < port->size; i++) {
+		luaT_pushtuple(L, entry->tuple);
+		lua_rawseti(L, -2, i + 1);
+		entry = entry->next;
+	}
+}
+
+static int
+lbox_ephemeral_index_select(lua_State *L)
+{
+	if (lua_gettop(L) != 5 || !lua_istable(L, 1)) {
+		return luaL_error(L, "Usage index:select(iterator, offset, "
+				  "limit, key)");
+	}
+	struct space *space = (struct space *)lua_checkephemeralspace(L, 1);
+	int iterator = luaL_checknumber(L, 2);
+	uint32_t offset = luaL_checknumber(L, 3);
+	uint32_t limit = luaL_checknumber(L, 4);
+	size_t key_len;
+	const char *key = lbox_encode_tuple_on_gc(L, 5, &key_len);
+	struct port port;
+	if (box_ephemeral_select(space, 0, iterator, offset, limit,
+				key, key + key_len, &port) != 0) {
+		return luaT_error(L);
+	}
+	lbox_port_to_table(L, &port);
+	port_destroy(&port);
+	return 1; /* lua table with tuples */
+}
+
+static int
+lbox_ephemeral_index_iterator(lua_State *L)
+{
+	if (lua_gettop(L) != 3 || !lua_istable(L, 1)) {
+		return luaL_error(L, "Usage index:iterator(type, key)");
+	}
+	struct space *space = (struct space *)lua_checkephemeralspace(L, 1);
+	int iterator = luaL_checknumber(L, 2);
+	size_t mpkey_len;
+	/* Key encoded by Lua */
+	const char *mpkey = lua_tolstring(L, 3, &mpkey_len);
+	struct iterator *it = box_ephemeral_index_iterator(space, 0, iterator,
+							   mpkey,
+							   mpkey + mpkey_len);
+	if (it == NULL)
+		return luaT_error(L);
+
+	assert(CTID_STRUCT_ITERATOR_REF != 0);
+	struct iterator **ptr = (struct iterator **) luaL_pushcdata(L,
+		CTID_STRUCT_ITERATOR_REF);
+	*ptr = it; /* NULL handled by Lua, gc also set by Lua */
+	return 1;
+}
+
+/**
+ * Return a first (minimal) tuple from the index
+ * matched provided key.
+ * 
+ * @param Lua ephemeral space.
+ * @param Lua tuple - key.
+ * @retval tuple or nil.
+ */
+static int
+lbox_ephemeral_index_min(lua_State *L)
+{
+	if (lua_gettop(L) != 2 || !lua_istable(L, 1))
+		return luaL_error(L, "Usage index:min(key)");
+	struct space *space = (struct space *)lua_checkephemeralspace(L, 1);
+	size_t key_len;
+	struct tuple *result;
+	const char *key = lbox_encode_tuple_on_gc(L, 2, &key_len);
+	if (box_ephemeral_index_min(space, 0, key, key + key_len, &result) != 0)
+		return luaT_error(L);
+	return luaT_pushtupleornil(L, result);
+}
+
+/**
+ * Return a last (maximal) tuple from the index
+ * matched provided key.
+ * 
+ * @param Lua ephemeral space.
+ * @param Lua tuple - key.
+ * @retval tuple or nil.
+ */
+static int
+lbox_ephemeral_index_max(lua_State *L)
+{
+	if (lua_gettop(L) != 2 || !lua_istable(L, 1))
+		return luaL_error(L, "Usage index:max(key)");
+	struct space *space = (struct space *)lua_checkephemeralspace(L, 1);
+	size_t key_len;
+	struct tuple *result;
+	const char *key = lbox_encode_tuple_on_gc(L, 2, &key_len);
+	if (box_ephemeral_index_max(space, 0, key, key + key_len, &result) != 0)
+		return luaT_error(L);
+	return luaT_pushtupleornil(L, result);
+}
+
+/**
+ * Count the number of tuple matched the provided key.
+ * 
+ * @param Lua ephemeral space.
+ * @param Lua number - iterator type.
+ * @param Lua tuple - key.
+ * @retval tuple or nil.
+ */
+static int
+lbox_ephemeral_index_count(lua_State *L)
+{
+	if (lua_gettop(L) != 3 || !lua_istable(L, 1))
+		return luaL_error(L, "Usage index:count(type, key)");
+	struct space *space = (struct space *)lua_checkephemeralspace(L, 1);
+	uint32_t type = lua_tonumber(L, 2);
+	size_t key_len;
+	const char *key = lbox_encode_tuple_on_gc(L, 3, &key_len);
+	ssize_t count = box_ephemeral_index_count(space, 0, type, key,
+						  key + key_len);
+	if (count < 0)
+		return luaT_error(L);
+	lua_pushnumber(L, count);
+	return 1;
+}
+
+/**
+ * Index statistics.
+ * 
+ * @param Lua ephemeral space.
+ * @retval info handler.
+ */
+static int
+lbox_ephemeral_index_stat(lua_State *L)
+{
+	if (lua_gettop(L) != 1 || !lua_istable(L, 1))
+		return luaL_error(L, "Usage index:stat()");
+	struct space *space = (struct space *)lua_checkephemeralspace(L, 1);
+	struct info_handler info;
+	luaT_info_handler_create(&info, L);
+	if (box_ephemeral_index_stat(space, 0, &info) != 0)
+		return luaT_error(L);
+	return 1;
+}
+
+/**
+ * Run index compaction.
+ * 
+ * @param Lua ephemeral space.
+ */
+static int
+lbox_ephemeral_index_compact(lua_State *L)
+{
+	if (lua_gettop(L) != 1 || !lua_istable(L, 1))
+		return luaL_error(L, "Usage index:compact()");
+	struct space *space = (struct space *)lua_checkephemeralspace(L, 1);
+	if (box_ephemeral_index_compact(space, 0) != 0)
+		return luaT_error(L);
+	return 0;
+}
+
+/**
+ * Update tuple matched the provided key.
+ * 
+ * @param Lua ephemeral space.
+ * @param Lua tuple - key.
+ * @param Lua tuple - operaions in case of update .
+ * @retval tuple or nil.
+ */
+static int
+lbox_ephemeral_index_update(lua_State *L)
+{
+	if (lua_gettop(L) != 3 || !lua_istable(L, 1) ||
+	    (lua_type(L, 2) != LUA_TTABLE && luaT_istuple(L, 2) == NULL) ||
+	    (lua_type(L, 3) != LUA_TTABLE && luaT_istuple(L, 3) == NULL))
+		return luaL_error(L, "Usage index:update(key, ops)");
+	struct space *space = (struct space *)lua_checkephemeralspace(L, 1);
+	size_t key_len;
+	const char *key = lbox_encode_tuple_on_gc(L, 2, &key_len);
+	size_t ops_len;
+	const char *ops = lbox_encode_tuple_on_gc(L, 3, &ops_len);
+	struct tuple *result;
+	if (box_ephemeral_update(space, 0, key, key + key_len,
+				 ops, ops + ops_len, 1, &result) != 0)
+		return luaT_error(L);
+	return luaT_pushtupleornil(L, result);
+}
+
+/**
+ * Delete tuple matched the provided key.
+ * 
+ * @param Lua ephemeral space.
+ * @param Lua tuple - key.
+ * @retval tuple or nil.
+ */
+static int
+lbox_ephemeral_index_delete(lua_State *L)
+{
+	if (lua_gettop(L) != 2 || !lua_istable(L, 1) ||
+	    (lua_type(L, 2) != LUA_TTABLE && luaT_istuple(L, 2) == NULL))
+		return luaL_error(L, "Usage index:delete(key)");
+	struct space *space = (struct space *)lua_checkephemeralspace(L, 1);
+	size_t key_len;
+	const char *key = lbox_encode_tuple_on_gc(L, 2, &key_len);
+	struct tuple *result;
+	if (box_ephemeral_delete(space, 0, key, key + key_len, &result) != 0)
+		return luaT_error(L);
+	return luaT_pushtupleornil(L, result);
+}
+
+/**
  * Make a tuple or a table Lua object by map.
  * @param Lua space object.
  * @param Lua map table object.
@@ -771,9 +1139,13 @@ box_lua_space_init(struct lua_State *L)
 
 	int rc = luaL_cdef(L, "struct space;");
 	assert(rc == 0);
-	(void) rc;
 	CTID_STRUCT_SPACE_POINTER = luaL_ctypeid(L, "struct space *");
 	assert(CTID_STRUCT_SPACE_POINTER != 0);
+	rc = luaL_cdef(L, "struct iterator;");
+	assert(rc == 0);
+	CTID_STRUCT_ITERATOR_REF = luaL_ctypeid(L, "struct iterator&");
+	assert(CTID_STRUCT_ITERATOR_REF != 0);
+	(void) rc;
 
 	lua_getfield(L, LUA_GLOBALSINDEX, "box");
 	lua_newtable(L);
@@ -873,9 +1245,31 @@ box_lua_space_init(struct lua_State *L)
 	lua_pop(L, 1);
 	static const struct luaL_Reg space_ephemeral_lib[] = {
 		{"frommap", lbox_space_frommap_ephemeral},
+		{"insert", lbox_ephemeral_space_insert},
+		{"replace", lbox_ephemeral_space_replace},
+		{"upsert", lbox_ephemeral_space_upsert},
 		{NULL, NULL}
 	};
 	luaL_register(L, "box.schema.space_ephemeral_methods",
 		      space_ephemeral_lib);
 	lua_pop(L, 1);
+	static const struct luaL_Reg index_ephemeral_lib[] = {
+		{"len", lbox_ephemeral_index_len},
+		{"bsize", lbox_ephemeral_index_bsize},
+		{"random", lbox_ephemeral_index_random},
+		{"get", lbox_ephemeral_index_get},
+		{"min", lbox_ephemeral_index_min},
+		{"max", lbox_ephemeral_index_max},
+		{"count", lbox_ephemeral_index_count},
+		{"iterator", lbox_ephemeral_index_iterator},
+		{"stat", lbox_ephemeral_index_stat},
+		{"compact", lbox_ephemeral_index_compact},
+		{"update", lbox_ephemeral_index_update},
+		{"delete", lbox_ephemeral_index_delete},
+		{"select", lbox_ephemeral_index_select},
+		{NULL, NULL}
+	};
+	luaL_register(L, "box.schema.index_ephemeral_methods",
+		      index_ephemeral_lib);
+	lua_pop(L, 1);
 }
diff --git a/test/box/ephemeral_space.result b/test/box/ephemeral_space.result
index d958ffe..0f80fa4 100644
--- a/test/box/ephemeral_space.result
+++ b/test/box/ephemeral_space.result
@@ -1,3 +1,6 @@
+test_run = require('test_run').new()
+---
+...
 -- Ephemeral space: creation and dropping.
 -- Simple creation.
 s = box.schema.space.create_ephemeral()
@@ -480,3 +483,7979 @@ s:frommap({ddd = 1, aaa = 2, ccc = 3, bbb = 4}, {dummy = true})
 s:drop()
 ---
 ...
+-- Ephemeral space: methods: insert
+s = box.schema.space.create_ephemeral({field_count = 3})
+---
+...
+i = s:create_index('a')
+---
+...
+s:insert{1}
+---
+- error: Tuple field count 1 does not match space field count 3
+...
+s:insert{2,2}
+---
+- error: Tuple field count 2 does not match space field count 3
+...
+s:insert{3,3,3}
+---
+- [3, 3, 3]
+...
+s:insert{4,4,4,4}
+---
+- error: Tuple field count 4 does not match space field count 3
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'string'} })
+---
+...
+for key = 1, 10 do s:insert({tostring(key)}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 10 do table.insert(t, s:get({tostring(key)})) end
+---
+...
+t
+---
+- - ['1']
+  - ['2']
+  - ['3']
+  - ['4']
+  - ['5']
+  - ['6']
+  - ['7']
+  - ['8']
+  - ['9']
+  - ['10']
+...
+s:insert({tostring(7)})
+---
+- error: Duplicate key exists in unique index 'a' in space 'ephemeral'
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+---
+...
+for key = 1, 10 do s:insert({key}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 10 do table.insert(t, s:get({key})) end
+---
+...
+t
+---
+- - [1]
+  - [2]
+  - [3]
+  - [4]
+  - [5]
+  - [6]
+  - [7]
+  - [8]
+  - [9]
+  - [10]
+...
+s:insert({7})
+---
+- error: Duplicate key exists in unique index 'a' in space 'ephemeral'
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned', 2, 'unsigned'} })
+---
+...
+for key = 1, 10 do s:insert({key, key}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 10 do table.insert(t, s:get({key, key})) end
+---
+...
+t
+---
+- - [1, 1]
+  - [2, 2]
+  - [3, 3]
+  - [4, 4]
+  - [5, 5]
+  - [6, 6]
+  - [7, 7]
+  - [8, 8]
+  - [9, 9]
+  - [10, 10]
+...
+s:insert({7, 7})
+---
+- error: Duplicate key exists in unique index 'a' in space 'ephemeral'
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'string'} })
+---
+...
+for key = 1, 10 do s:insert({tostring(key)}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 10 do table.insert(t, s:get({tostring(key)})) end
+---
+...
+t
+---
+- - ['1']
+  - ['2']
+  - ['3']
+  - ['4']
+  - ['5']
+  - ['6']
+  - ['7']
+  - ['8']
+  - ['9']
+  - ['10']
+...
+s:insert(box.tuple.new{tostring(7)})
+---
+- error: Duplicate key exists in unique index 'a' in space 'ephemeral'
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+index = s:create_index('a')
+---
+...
+s:insert(1)
+---
+- error: Tuple/Key must be MsgPack array
+...
+s:insert(1, 2)
+---
+- error: Tuple/Key must be MsgPack array
+...
+s:insert(1, 2, 3)
+---
+- error: Tuple/Key must be MsgPack array
+...
+s:insert{1}
+---
+- [1]
+...
+s:insert{2, 3}
+---
+- [2, 3]
+...
+tmp = s:delete(1, 2, 3)
+---
+...
+s:select{}
+---
+- - [2, 3]
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {3, 'unsigned'} })
+---
+...
+s:insert{1}
+---
+- error: Tuple field count 1 is less than required by space format or defined indexes
+    (expected at least 3)
+...
+s:insert{2, 3}
+---
+- error: Tuple field count 2 is less than required by space format or defined indexes
+    (expected at least 3)
+...
+s:insert{4, 5, 6}
+---
+- [4, 5, 6]
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+---
+...
+s:insert{"1"}
+---
+- error: 'Tuple field 1 type does not match one required by operation: expected unsigned'
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'string'} })
+---
+...
+s:insert{1}
+---
+- error: 'Tuple field 1 type does not match one required by operation: expected string'
+...
+s:drop()
+---
+...
+-- Ephemeral space: methods: replace
+s = box.schema.space.create_ephemeral({field_count = 3})
+---
+...
+i = s:create_index('a')
+---
+...
+s:replace{1}
+---
+- error: Tuple field count 1 does not match space field count 3
+...
+s:replace{2, 2}
+---
+- error: Tuple field count 2 does not match space field count 3
+...
+s:replace{3, 3, 3}
+---
+- [3, 3, 3]
+...
+s:replace{4, 4, 4, 4}
+---
+- error: Tuple field count 4 does not match space field count 3
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'string'} })
+---
+...
+for key = 1, 10 do s:replace({tostring(key)}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 10 do table.insert(t, s:get({tostring(key)})) end
+---
+...
+t
+---
+- - ['1']
+  - ['2']
+  - ['3']
+  - ['4']
+  - ['5']
+  - ['6']
+  - ['7']
+  - ['8']
+  - ['9']
+  - ['10']
+...
+_ = s:replace({tostring(7)})
+---
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+---
+...
+for key = 1, 10 do s:replace({key}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 10 do table.insert(t, s:get({key})) end
+---
+...
+t
+---
+- - [1]
+  - [2]
+  - [3]
+  - [4]
+  - [5]
+  - [6]
+  - [7]
+  - [8]
+  - [9]
+  - [10]
+...
+_ = s:replace({7})
+---
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned', 2, 'unsigned'} })
+---
+...
+for key = 1, 10 do s:replace({key, key}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 10 do table.insert(t, s:get({key, key})) end
+---
+...
+t
+---
+- - [1, 1]
+  - [2, 2]
+  - [3, 3]
+  - [4, 4]
+  - [5, 5]
+  - [6, 6]
+  - [7, 7]
+  - [8, 8]
+  - [9, 9]
+  - [10, 10]
+...
+s:replace({7, 7})
+---
+- [7, 7]
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'string'} })
+---
+...
+for key = 1, 10 do s:replace({tostring(key)}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 10 do table.insert(t, s:get({tostring(key)})) end
+---
+...
+t
+---
+- - ['1']
+  - ['2']
+  - ['3']
+  - ['4']
+  - ['5']
+  - ['6']
+  - ['7']
+  - ['8']
+  - ['9']
+  - ['10']
+...
+s:replace(box.tuple.new{tostring(7)})
+---
+- ['7']
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+index = s:create_index('a')
+---
+...
+s:replace(1)
+---
+- error: Tuple/Key must be MsgPack array
+...
+s:replace(1, 2)
+---
+- error: Tuple/Key must be MsgPack array
+...
+s:replace(1, 2, 3)
+---
+- error: Tuple/Key must be MsgPack array
+...
+s:replace{1}
+---
+- [1]
+...
+s:replace{2, 3}
+---
+- [2, 3]
+...
+tmp = s:delete(1, 2, 3)
+---
+...
+s:select{}
+---
+- - [2, 3]
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {3, 'unsigned'} })
+---
+...
+s:replace{1}
+---
+- error: Tuple field count 1 is less than required by space format or defined indexes
+    (expected at least 3)
+...
+s:replace{2, 3}
+---
+- error: Tuple field count 2 is less than required by space format or defined indexes
+    (expected at least 3)
+...
+s:replace{4, 5, 6}
+---
+- [4, 5, 6]
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+---
+...
+s:replace{"1"}
+---
+- error: 'Tuple field 1 type does not match one required by operation: expected unsigned'
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'string'} })
+---
+...
+s:replace{1}
+---
+- error: 'Tuple field 1 type does not match one required by operation: expected string'
+...
+s:drop()
+---
+...
+-- Ephemeral space: methods: upsert
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+---
+...
+s:upsert({1, 0}, {{'+', 2, 1}})
+---
+...
+s:get{1}
+---
+- [1, 0]
+...
+s:upsert({1, 0}, {{'+', 2, 1}})
+---
+...
+s:get{1}
+---
+- [1, 1]
+...
+s:upsert({1, 0}, {{'+', 1, 1}})
+---
+...
+s:get{1}
+---
+- [1, 1]
+...
+s:get{2}
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'string'} })
+---
+...
+for key = 1, 10 do s:upsert({tostring(key), 0}, {{'+', 2, 1}}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 10 do table.insert(t, s:get({tostring(key)})) end
+---
+...
+t
+---
+- - ['1', 0]
+  - ['2', 0]
+  - ['3', 0]
+  - ['4', 0]
+  - ['5', 0]
+  - ['6', 0]
+  - ['7', 0]
+  - ['8', 0]
+  - ['9', 0]
+  - ['10', 0]
+...
+for key = 1, 10 do s:upsert({tostring(key), 0}, {{'+', 2, 10}}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 10 do table.insert(t, s:get({tostring(key)})) end
+---
+...
+t
+---
+- - ['1', 10]
+  - ['2', 10]
+  - ['3', 10]
+  - ['4', 10]
+  - ['5', 10]
+  - ['6', 10]
+  - ['7', 10]
+  - ['8', 10]
+  - ['9', 10]
+  - ['10', 10]
+...
+for key = 1, 10 do s:delete({tostring(key)}) end
+---
+...
+for key = 1, 10 do s:upsert({tostring(key), 0}, {{'+', 2, 1}, {'=', 3, key}}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 10 do table.insert(t, s:get({tostring(key)})) end
+---
+...
+t
+---
+- - ['1', 0]
+  - ['2', 0]
+  - ['3', 0]
+  - ['4', 0]
+  - ['5', 0]
+  - ['6', 0]
+  - ['7', 0]
+  - ['8', 0]
+  - ['9', 0]
+  - ['10', 0]
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+---
+...
+for key = 1, 10 do s:upsert({key, 0}, {{'+', 2, 1}}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 10 do table.insert(t, s:get({key})) end
+---
+...
+t
+---
+- - [1, 0]
+  - [2, 0]
+  - [3, 0]
+  - [4, 0]
+  - [5, 0]
+  - [6, 0]
+  - [7, 0]
+  - [8, 0]
+  - [9, 0]
+  - [10, 0]
+...
+for key = 1, 10 do s:upsert({key, 0}, {{'+', 2, 10}}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 10 do table.insert(t, s:get({key})) end
+---
+...
+t
+---
+- - [1, 10]
+  - [2, 10]
+  - [3, 10]
+  - [4, 10]
+  - [5, 10]
+  - [6, 10]
+  - [7, 10]
+  - [8, 10]
+  - [9, 10]
+  - [10, 10]
+...
+for key = 1, 10 do s:delete({key}) end
+---
+...
+for key = 1, 10 do s:upsert({key, 0}, {{'+', 2, 1}, {'=', 3, key}}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 10 do table.insert(t, s:get({key})) end
+---
+...
+t
+---
+- - [1, 0]
+  - [2, 0]
+  - [3, 0]
+  - [4, 0]
+  - [5, 0]
+  - [6, 0]
+  - [7, 0]
+  - [8, 0]
+  - [9, 0]
+  - [10, 0]
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned', 2, 'unsigned'} })
+---
+...
+for key = 1, 10 do s:upsert({key, key, 0}, {{'+', 3, 1}}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 10 do table.insert(t, s:get({key, key})) end
+---
+...
+t
+---
+- - [1, 1, 0]
+  - [2, 2, 0]
+  - [3, 3, 0]
+  - [4, 4, 0]
+  - [5, 5, 0]
+  - [6, 6, 0]
+  - [7, 7, 0]
+  - [8, 8, 0]
+  - [9, 9, 0]
+  - [10, 10, 0]
+...
+for key = 1, 10 do s:upsert({key, key, 0}, {{'+', 3, 10}}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 10 do table.insert(t, s:get({key, key})) end
+---
+...
+t
+---
+- - [1, 1, 10]
+  - [2, 2, 10]
+  - [3, 3, 10]
+  - [4, 4, 10]
+  - [5, 5, 10]
+  - [6, 6, 10]
+  - [7, 7, 10]
+  - [8, 8, 10]
+  - [9, 9, 10]
+  - [10, 10, 10]
+...
+for key = 1, 10 do s:delete({key, key}) end
+---
+...
+for key = 1, 10 do s:upsert({key, key, 0}, {{'+', 3, 1}, {'=', 4, key}}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 10 do table.insert(t, s:get({key, key})) end
+---
+...
+t
+---
+- - [1, 1, 0]
+  - [2, 2, 0]
+  - [3, 3, 0]
+  - [4, 4, 0]
+  - [5, 5, 0]
+  - [6, 6, 0]
+  - [7, 7, 0]
+  - [8, 8, 0]
+  - [9, 9, 0]
+  - [10, 10, 0]
+...
+s:drop()
+---
+...
+test_run:cmd("setopt delimiter ';'");
+---
+- true
+...
+function less(a, b)
+    if type(a[2]) ~= type(b[2]) then
+        return type(a[2]) < type(b[2])
+    end
+    if a[2] == b[2] then
+        return a[1] < b[1]
+    end
+    if type(a[2]) == 'boolean' then
+        return a[2] == false and b[2] == true
+    end
+    return a[2] < b[2]
+end;
+---
+...
+test_run:cmd("setopt delimiter ''");
+---
+- true
+...
+function sort(t) table.sort(t, less) return t end
+---
+...
+-- upsert default tuple constraint
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned', 2, 'unsigned'} })
+---
+...
+s:upsert({0, 'key', 0}, {{'+', 3, 1}})
+---
+- error: 'Tuple field 2 type does not match one required by operation: expected unsigned'
+...
+s:drop()
+---
+...
+-- upsert primary key modify (skipped)
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+---
+...
+s:upsert({0, 0}, {{'+', 1, 1}, {'+', 2, 1}})
+---
+...
+s:get({0})
+---
+- [0, 0]
+...
+s:drop()
+---
+...
+-- upsert with box.tuple.new
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+---
+...
+s:upsert({0, 0}, {{'+', 1, 1}, {'+', 2, 1}})
+---
+...
+s:get({0})
+---
+- [0, 0]
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned', 2, 'unsigned'} })
+---
+...
+for key = 1, 10 do s:upsert(box.tuple.new{key, key, 0}, box.tuple.new{{'+', 3, 1}}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 10 do table.insert(t, s:get({key, key})) end
+---
+...
+t
+---
+- - [1, 1, 0]
+  - [2, 2, 0]
+  - [3, 3, 0]
+  - [4, 4, 0]
+  - [5, 5, 0]
+  - [6, 6, 0]
+  - [7, 7, 0]
+  - [8, 8, 0]
+  - [9, 9, 0]
+  - [10, 10, 0]
+...
+for key = 1, 10 do s:upsert(box.tuple.new{key, key, 0}, box.tuple.new{{'+', 3, 10}}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 10 do table.insert(t, s:get({key, key})) end
+---
+...
+t
+---
+- - [1, 1, 10]
+  - [2, 2, 10]
+  - [3, 3, 10]
+  - [4, 4, 10]
+  - [5, 5, 10]
+  - [6, 6, 10]
+  - [7, 7, 10]
+  - [8, 8, 10]
+  - [9, 9, 10]
+  - [10, 10, 10]
+...
+for key = 1, 10 do s:delete({key, key}) end
+---
+...
+for key = 1, 10 do s:upsert(box.tuple.new{key, key, 0}, box.tuple.new{{'+', 3, 1}, {'=', 4, key}}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 10 do table.insert(t, s:get({key, key})) end
+---
+...
+t
+---
+- - [1, 1, 0]
+  - [2, 2, 0]
+  - [3, 3, 0]
+  - [4, 4, 0]
+  - [5, 5, 0]
+  - [6, 6, 0]
+  - [7, 7, 0]
+  - [8, 8, 0]
+  - [9, 9, 0]
+  - [10, 10, 0]
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a')
+---
+...
+s:upsert({0, 0}, {{'+', 2, 2}})
+---
+...
+s:select{0}
+---
+- - [0, 0]
+...
+tmp = s:delete{0}
+---
+...
+s:upsert({0, 0, 0}, {{'+', 2, 2}})
+---
+...
+s:select{0}
+---
+- - [0, 0, 0]
+...
+tmp = s:delete{0}
+---
+...
+s:upsert({0}, {{'+', 2, 2}})
+---
+...
+s:select{0}
+---
+- - [0]
+...
+s:replace{0, 1, 2, 4}
+---
+- [0, 1, 2, 4]
+...
+s:upsert({0, 0, "you will not see it"}, {{'+', 2, 2}})
+---
+...
+s:select{0}
+---
+- - [0, 3, 2, 4]
+...
+s:replace{0, -0x4000000000000000ll}
+---
+- [0, -4611686018427387904]
+...
+s:upsert({0}, {{'+', 2, -0x4000000000000001ll}})  -- overflow
+---
+...
+s:select{0}
+---
+- - [0, -4611686018427387904]
+...
+s:replace{0, "thing"}
+---
+- [0, 'thing']
+...
+s:upsert({0, "nothing"}, {{'+', 2, 2}})
+---
+...
+s:select{0}
+---
+- - [0, 'thing']
+...
+tmp = s:delete{0}
+---
+...
+s:upsert({0, "thing"}, {{'+', 2, 2}})
+---
+...
+s:select{0}
+---
+- - [0, 'thing']
+...
+s:replace{0, 1, 2}
+---
+- [0, 1, 2]
+...
+s:upsert({0}, {{'!', 42, 42}})
+---
+...
+s:select{0}
+---
+- - [0, 1, 2]
+...
+s:upsert({0}, {{'#', 42, 42}})
+---
+...
+s:select{0}
+---
+- - [0, 1, 2]
+...
+s:upsert({0}, {{'=', 42, 42}})
+---
+...
+s:select{}
+---
+- - [0, 1, 2]
+...
+s:replace{0, 1.5}
+---
+- [0, 1.5]
+...
+s:select{}
+---
+- - [0, 1.5]
+...
+s:upsert({0}, {{'|', 1, 255}})
+---
+...
+s:select{0}
+---
+- - [0, 1.5]
+...
+s:replace{0, 1.5}
+---
+- [0, 1.5]
+...
+s:replace{0, 'something to splice'}
+---
+- [0, 'something to splice']
+...
+s:upsert({0}, {{':', 2, 1, 4, 'no'}})
+---
+...
+s:select{0}
+---
+- - [0, 'nothing to splice']
+...
+s:upsert({0}, {{':', 2, 1, 2, 'every'}})
+---
+...
+s:select{0}
+---
+- - [0, 'everything to splice']
+...
+s:upsert({0}, {{':', 2, -100, 2, 'every'}})
+---
+...
+s:select{0}
+---
+- - [0, 'everything to splice']
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral({ field_count = 1 })
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+---
+...
+s:insert({1})
+---
+- [1]
+...
+s:select{}
+---
+- - [1]
+...
+s:upsert({2, 2}, {{'+', 2, 1}})
+---
+- error: Tuple field count 2 does not match space field count 1
+...
+s:select{}
+---
+- - [1]
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral({ field_count = 2 })
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+---
+...
+s:insert({1, 1})
+---
+- [1, 1]
+...
+s:select{}
+---
+- - [1, 1]
+...
+s:upsert({2, 2, 2}, {{'+', 3, 1}})
+---
+- error: Tuple field count 3 does not match space field count 2
+...
+s:upsert({3, 3}, {{'+', 2, 1}})
+---
+...
+s:select{}
+---
+- - [1, 1]
+  - [3, 3]
+...
+s:drop()
+---
+...
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+function anything_to_string(tab)
+    if tab == nil then
+        return 'nil'
+    end
+    local str = '['
+    local first_route = true
+    local t = 0
+    for k,f in pairs(tab) do
+        if not first_route then str = str .. ',' end
+        first_route = false
+        t = t + 1
+        if k ~= t then
+            str = str .. k .. '='
+        end
+        if type(f) == 'string' then
+            str = str .. "'" .. f .. "'"
+        elseif type (f) == 'number' then
+            str = str .. tostring(f)
+        elseif type (f) == 'table' or type (f) == 'cdata' then
+            str = str .. anything_to_string(f)
+        else
+            str = str .. '?'
+        end
+    end
+    str = str .. ']'
+    return str
+end;
+---
+...
+function things_equal(var1, var2)
+    local type1 = type(var1) == 'cdata' and 'table' or type(var1)
+    local type2 = type(var2) == 'cdata' and 'table' or type(var2)
+    if type1 ~= type2 then
+        return false
+    end
+    if type1 ~= 'table' then
+        return var1 == var2
+    end
+    for k,v in pairs(var1) do
+        if not things_equal(v, var2[k]) then
+            return false
+        end
+    end
+    for k,v in pairs(var2) do
+        if not things_equal(v, var1[k]) then
+            return false
+        end
+    end
+    return true
+end;
+---
+...
+function copy_thing(t)
+    if type(t) ~= 'table' then
+        return t
+    end
+    local res = {}
+    for k,v in pairs(t) do
+        res[copy_thing(k)] = copy_thing(v)
+    end
+    return res
+end;
+---
+...
+function test(space, key_tuple, ops, expect)
+    space:upsert(key_tuple, ops)
+    if (things_equal(space:select{}, expect)) then
+        return 'upsert('.. anything_to_string(key_tuple) .. ', ' ..
+                anything_to_string(ops) .. ', '  ..
+                ') OK ' .. anything_to_string(space:select{})
+    end
+    return 'upsert('.. anything_to_string(key_tuple) .. ', ' ..
+            anything_to_string(ops) .. ', ' ..
+            ') FAILED, got ' .. anything_to_string(space:select{}) ..
+            ' expected ' .. anything_to_string(expect)
+end;
+---
+...
+test_run:cmd("setopt delimiter ''");
+---
+- true
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'string'} })
+---
+...
+s:upsert({1}, {{'!', 2, 100}}) -- must fail on checking tuple
+---
+- error: 'Tuple field 1 type does not match one required by operation: expected string'
+...
+s:upsert({'a'}, {{'a', 2, 100}}) -- must fail on checking ops
+---
+- error: Unknown UPDATE operation
+...
+s:upsert({'a'}, {{'!', 2, 'ups1'}}) -- 'fast' upsert via insert in one index
+---
+...
+s:upsert({'a', 'b'}, {{'!', 2, 'ups2'}}) -- 'fast' upsert via update in one index
+---
+...
+s:select{}
+---
+- - ['a', 'ups2']
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+---
+...
+s:insert({1, 1, 1})
+---
+- [1, 1, 1]
+...
+s:insert({2, 2, 2})
+---
+- [2, 2, 2]
+...
+s:insert({3, 3, 3})
+---
+- [3, 3, 3]
+...
+s:select{}
+---
+- - [1, 1, 1]
+  - [2, 2, 2]
+  - [3, 3, 3]
+...
+s:upsert({2, 18, 76}, {})
+---
+...
+s:upsert({4, 4, 4}, {})
+---
+...
+s:select{}
+---
+- - [1, 1, 1]
+  - [2, 2, 2]
+  - [3, 3, 3]
+  - [4, 4, 4]
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a')
+---
+...
+t = {1, '1', 1, 'qwerty'}
+---
+...
+s:insert(t)
+---
+- [1, '1', 1, 'qwerty']
+...
+-- all good operations, one op, equivalent to update
+test(s, t, {{'+', 3, 5}}, {{1, '1', 6, 'qwerty'}})
+---
+- upsert([1,'1',1,'qwerty'], [['+',3,5]], ) OK [[1,'1',6,'qwerty']]
+...
+test(s, t, {{'-', 3, 3}}, {{1, '1', 3, 'qwerty'}})
+---
+- upsert([1,'1',1,'qwerty'], [['-',3,3]], ) OK [[1,'1',3,'qwerty']]
+...
+test(s, t, {{'&', 3, 5}}, {{1, '1', 1, 'qwerty'}})
+---
+- upsert([1,'1',1,'qwerty'], [['&',3,5]], ) OK [[1,'1',1,'qwerty']]
+...
+test(s, t, {{'|', 3, 8}}, {{1, '1', 9, 'qwerty'}})
+---
+- upsert([1,'1',1,'qwerty'], [['|',3,8]], ) OK [[1,'1',9,'qwerty']]
+...
+test(s, t, {{'^', 3, 12}}, {{1, '1', 5, 'qwerty'}})
+---
+- upsert([1,'1',1,'qwerty'], [['^',3,12]], ) OK [[1,'1',5,'qwerty']]
+...
+test(s, t, {{':', 4, 2, 4, "uer"}}, {{1, '1', 5, 'query'}})
+---
+- upsert([1,'1',1,'qwerty'], [[':',4,2,4,'uer']], ) OK [[1,'1',5,'query']]
+...
+test(s, t, {{'!', 4, 'answer'}}, {{1, '1', 5, 'answer', 'query'}})
+---
+- upsert([1,'1',1,'qwerty'], [['!',4,'answer']], ) OK [[1,'1',5,'answer','query']]
+...
+test(s, t, {{'#', 5, 1}}, {{1, '1', 5, 'answer'}})
+---
+- upsert([1,'1',1,'qwerty'], [['#',5,1]], ) OK [[1,'1',5,'answer']]
+...
+test(s, t, {{'!', -1, 1}}, {{1, '1', 5, 'answer', 1}})
+---
+- upsert([1,'1',1,'qwerty'], [['!',-1,1]], ) OK [[1,'1',5,'answer',1]]
+...
+test(s, t, {{'!', -1, 2}}, {{1, '1', 5, 'answer', 1, 2}})
+---
+- upsert([1,'1',1,'qwerty'], [['!',-1,2]], ) OK [[1,'1',5,'answer',1,2]]
+...
+test(s, t, {{'!', -1, 3}}, {{1, '1', 5, 'answer', 1, 2 ,3}})
+---
+- upsert([1,'1',1,'qwerty'], [['!',-1,3]], ) OK [[1,'1',5,'answer',1,2,3]]
+...
+test(s, t, {{'#', 5, 100500}}, {{1, '1', 5, 'answer'}})
+---
+- upsert([1,'1',1,'qwerty'], [['#',5,100500]], ) OK [[1,'1',5,'answer']]
+...
+test(s, t, {{'=', 4, 'qwerty'}}, {{1, '1', 5, 'qwerty'}})
+---
+- upsert([1,'1',1,'qwerty'], [['=',4,'qwerty']], ) OK [[1,'1',5,'qwerty']]
+...
+-- same check for negative posistion
+test(s, t, {{'+', -2, 5}}, {{1, '1', 10, 'qwerty'}})
+---
+- upsert([1,'1',1,'qwerty'], [['+',-2,5]], ) OK [[1,'1',10,'qwerty']]
+...
+test(s, t, {{'-', -2, 3}}, {{1, '1', 7, 'qwerty'}})
+---
+- upsert([1,'1',1,'qwerty'], [['-',-2,3]], ) OK [[1,'1',7,'qwerty']]
+...
+test(s, t, {{'&', -2, 5}}, {{1, '1', 5, 'qwerty'}})
+---
+- upsert([1,'1',1,'qwerty'], [['&',-2,5]], ) OK [[1,'1',5,'qwerty']]
+...
+test(s, t, {{'|', -2, 8}}, {{1, '1', 13, 'qwerty'}})
+---
+- upsert([1,'1',1,'qwerty'], [['|',-2,8]], ) OK [[1,'1',13,'qwerty']]
+...
+test(s, t, {{'^', -2, 12}}, {{1, '1', 1, 'qwerty'}})
+---
+- upsert([1,'1',1,'qwerty'], [['^',-2,12]], ) OK [[1,'1',1,'qwerty']]
+...
+test(s, t, {{':', -1, 2, 4, "uer"}}, {{1, '1', 1, 'query'}})
+---
+- upsert([1,'1',1,'qwerty'], [[':',-1,2,4,'uer']], ) OK [[1,'1',1,'query']]
+...
+test(s, t, {{'!', -2, 'answer'}}, {{1, '1', 1, 'answer', 'query'}})
+---
+- upsert([1,'1',1,'qwerty'], [['!',-2,'answer']], ) OK [[1,'1',1,'answer','query']]
+...
+test(s, t, {{'#', -1, 1}}, {{1, '1', 1, 'answer'}})
+---
+- upsert([1,'1',1,'qwerty'], [['#',-1,1]], ) OK [[1,'1',1,'answer']]
+...
+test(s, t, {{'=', -1, 'answer!'}}, {{1, '1', 1, 'answer!'}})
+---
+- upsert([1,'1',1,'qwerty'], [['=',-1,'answer!']], ) OK [[1,'1',1,'answer!']]
+...
+-- selective test for good multiple ops
+test(s, t, {{'+', 3, 2}, {'!', 4, 42}}, {{1, '1', 3, 42, 'answer!'}})
+---
+- upsert([1,'1',1,'qwerty'], [['+',3,2],['!',4,42]], ) OK [[1,'1',3,42,'answer!']]
+...
+test(s, t, {{'!', 1, 666}, {'#', 1, 1}, {'+', 3, 2}}, {{1, '1', 5, 42, 'answer!'}})
+---
+- upsert([1,'1',1,'qwerty'], [['!',1,666],['#',1,1],['+',3,2]], ) OK [[1,'1',5,42,'answer!']]
+...
+test(s, t, {{'!', 3, 43}, {'+', 4, 2}}, {{1, '1', 43, 7, 42, 'answer!'}})
+---
+- upsert([1,'1',1,'qwerty'], [['!',3,43],['+',4,2]], ) OK [[1,'1',43,7,42,'answer!']]
+...
+test(s, t, {{'#', 3, 2}, {'=', 3, 1}, {'=', 4, '1'}}, {{1, '1', 1, '1'}})
+---
+- upsert([1,'1',1,'qwerty'], [['#',3,2],['=',3,1],['=',4,'1']], ) OK [[1,'1',1,'1']]
+...
+-- all bad operations, one op, equivalent to update but error is supressed
+test(s, t, {{'+', 4, 3}}, {{1, '1', 1, '1'}})
+---
+- upsert([1,'1',1,'qwerty'], [['+',4,3]], ) OK [[1,'1',1,'1']]
+...
+test(s, t, {{'-', 4, 3}}, {{1, '1', 1, '1'}})
+---
+- upsert([1,'1',1,'qwerty'], [['-',4,3]], ) OK [[1,'1',1,'1']]
+...
+test(s, t, {{'&', 4, 1}}, {{1, '1', 1, '1'}})
+---
+- upsert([1,'1',1,'qwerty'], [['&',4,1]], ) OK [[1,'1',1,'1']]
+...
+test(s, t, {{'|', 4, 1}}, {{1, '1', 1, '1'}})
+---
+- upsert([1,'1',1,'qwerty'], [['|',4,1]], ) OK [[1,'1',1,'1']]
+...
+test(s, t, {{'^', 4, 1}}, {{1, '1', 1, '1'}})
+---
+- upsert([1,'1',1,'qwerty'], [['^',4,1]], ) OK [[1,'1',1,'1']]
+...
+test(s, t, {{':', 3, 2, 4, "uer"}}, {{1, '1', 1, '1'}})
+---
+- upsert([1,'1',1,'qwerty'], [[':',3,2,4,'uer']], ) OK [[1,'1',1,'1']]
+...
+test(s, t, {{'!', 18, 'answer'}}, {{1, '1', 1, '1'}})
+---
+- upsert([1,'1',1,'qwerty'], [['!',18,'answer']], ) OK [[1,'1',1,'1']]
+...
+test(s, t, {{'#', 18, 1}}, {{1, '1', 1, '1'}})
+---
+- upsert([1,'1',1,'qwerty'], [['#',18,1]], ) OK [[1,'1',1,'1']]
+...
+test(s, t, {{'=', 18, 'qwerty'}}, {{1, '1', 1, '1'}})
+---
+- upsert([1,'1',1,'qwerty'], [['=',18,'qwerty']], ) OK [[1,'1',1,'1']]
+...
+-- selective test for good/bad multiple ops mix
+test(s, t, {{'+', 3, 1}, {'+', 4, 1}}, {{1, '1', 2, '1'}})
+---
+- upsert([1,'1',1,'qwerty'], [['+',3,1],['+',4,1]], ) OK [[1,'1',2,'1']]
+...
+test(s, t, {{'-', 4, 1}, {'-', 3, 1}}, {{1, '1', 1, '1'}})
+---
+- upsert([1,'1',1,'qwerty'], [['-',4,1],['-',3,1]], ) OK [[1,'1',1,'1']]
+...
+test(s, t, {{'#', 18, 1}, {'|', 3, 14}, {'!', 18, '!'}}, {{1, '1', 15, '1'}})
+---
+- upsert([1,'1',1,'qwerty'], [['#',18,1],['|',3,14],['!',18,'!']], ) OK [[1,'1',15,'1']]
+...
+test(s, t, {{'^', 42, 42}, {':', 1, 1, 1, ''}, {'^', 3, 8}}, {{1, '1', 7, '1'}})
+---
+- upsert([1,'1',1,'qwerty'], [['^',42,42],[':',1,1,1,''],['^',3,8]], ) OK [[1,'1',7,'1']]
+...
+test(s, t, {{'&', 3, 1}, {'&', 2, 1}, {'&', 4, 1}}, {{1, '1', 1, '1'}})
+---
+- upsert([1,'1',1,'qwerty'], [['&',3,1],['&',2,1],['&',4,1]], ) OK [[1,'1',1,'1']]
+...
+-- broken ops must raise an exception and discarded
+'dump ' .. anything_to_string(s:select{})
+---
+- dump [[1,'1',1,'1']]
+...
+test(s, t, {{'&', 'a', 3}, {'+', 3, 3}}, {{1, '1', 1, '1'}})
+---
+- error: Illegal parameters, field id must be a number
+...
+test(s, t, {{'+', 3, 3}, {'&', 3, 'a'}}, {{1, '1', 1, '1'}})
+---
+- error: 'Argument type in operation ''&'' on field 3 does not match field type: expected
+    a positive integer'
+...
+test(s, t, {{'+', 3}, {'&', 3, 'a'}}, {{1, '1', 1, '1'}})
+---
+- error: Unknown UPDATE operation
+...
+test(s, t, {{':', 3, 3}}, {{1, '1', 1, '1'}})
+---
+- error: Unknown UPDATE operation
+...
+test(s, t, {{':', 3, 3, 3}}, {{1, '1', 1, '1'}})
+---
+- error: Unknown UPDATE operation
+...
+test(s, t, {{'?', 3, 3}}, {{1, '1', 1, '1'}})
+---
+- error: Unknown UPDATE operation
+...
+'dump ' .. anything_to_string(s:select{})
+---
+- dump [[1,'1',1,'1']]
+...
+-- -- ignoring ops for insert upsert
+test(s, {2, '2', 2, '2'}, {{}}, {{1, '1', 1, '1'}})
+---
+- error: Illegal parameters, update operation must be an array {op,..}, got empty
+    array
+...
+test(s, {3, '3', 3, '3'}, {{'+', 3, 3}}, {{1, '1', 1, '1'}, {3, '3', 3, '3'}})
+---
+- upsert([3,'3',3,'3'], [['+',3,3]], ) OK [[1,'1',1,'1'],[3,'3',3,'3']]
+...
+t[1] = 1
+---
+...
+test(s, t, {{'+', 3, 3}, {'+', 4, 3}}, {{1, '1', 4, '1'}, {3, '3', 3, '3'}})
+---
+- upsert([1,'1',1,'qwerty'], [['+',3,3],['+',4,3]], ) OK [[1,'1',4,'1'],[3,'3',3,'3']]
+...
+t[1] = 2
+---
+...
+test(s, t, {{'-', 4, 1}}, {{1, '1', 4, '1'}, {2, '1', 1, 'qwerty'}, {3, '3', 3, '3'}})
+---
+- upsert([2,'1',1,'qwerty'], [['-',4,1]], ) OK [[1,'1',4,'1'],[2,'1',1,'qwerty'],[3,'3',3,'3']]
+...
+t[1] = 3
+---
+...
+test(s, t, {{':', 3, 3, 3, ''}, {'|', 3, 4}}, {{1, '1', 4, '1'}, {2, '1', 1, 'qwerty'}, {3, '3', 7, '3'}})
+---
+- upsert([3,'1',1,'qwerty'], [[':',3,3,3,''],['|',3,4]], ) OK [[1,'1',4,'1'],[2,'1',1,'qwerty'],[3,'3',7,'3']]
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a')
+---
+...
+s:replace({1, 1, 1})
+---
+- [1, 1, 1]
+...
+box.snapshot()
+---
+- ok
+...
+s:upsert({1, 1}, {{'+', 2, 2}})
+---
+...
+s:upsert({1, 1}, {{'+', 3, 4}})
+---
+...
+s:select()
+---
+- - [1, 3, 5]
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+s:upsert({1}, {})
+---
+- error: 'No index #0 is defined in space ''ephemeral'''
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', {parts = {1, 'unsigned', 3, 'unsigned'}})
+---
+...
+s:upsert({100, 100, 100}, {{'+', 2, 200}})
+---
+...
+s:upsert({200, 100, 200}, {{'+', 2, 300}})
+---
+...
+s:upsert({300, 100, 300}, {{'+', 2, 400}})
+---
+...
+i:select{}
+---
+- - [100, 100, 100]
+  - [200, 100, 200]
+  - [300, 100, 300]
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', {parts = {2, 'unsigned', 3, 'unsigned'}})
+---
+...
+s:upsert({100, 100, 100}, {{'+', 1, 200}})
+---
+...
+s:upsert({200, 100, 200}, {{'+', 1, 300}})
+---
+...
+s:upsert({300, 100, 300}, {{'+', 1, 400}})
+---
+...
+i:select{}
+---
+- - [100, 100, 100]
+  - [200, 100, 200]
+  - [300, 100, 300]
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', {parts = {3, 'unsigned', 2, 'unsigned'}})
+---
+...
+s:upsert({100, 100, 100}, {{'+', 1, 200}})
+---
+...
+s:upsert({200, 100, 200}, {{'+', 1, 300}})
+---
+...
+s:upsert({300, 100, 300}, {{'+', 1, 400}})
+---
+...
+i:select{}
+---
+- - [100, 100, 100]
+  - [200, 100, 200]
+  - [300, 100, 300]
+...
+s:drop()
+---
+...
+-- Ephemeral index: methods: update
+s = box.schema.space.create_ephemeral()
+---
+...
+-- test delete field
+i = s:create_index('a')
+---
+...
+s:insert{1000001, 1000002, 1000003, 1000004, 1000005}
+---
+- [1000001, 1000002, 1000003, 1000004, 1000005]
+...
+s:update({1000001}, {{'#', 1, 1}})
+---
+- error: Attempt to modify a tuple field which is part of index 'a' in space 'ephemeral'
+...
+s:update({1000001}, {{'#', 1, "only one record please"}})
+---
+- error: 'Argument type in operation ''#'' on field 1 does not match field type: expected
+    a number of fields to delete'
+...
+i:drop()
+---
+...
+-- test arithmetic
+i = s:create_index('a')
+---
+...
+s:insert{1, 0}
+---
+- [1, 0]
+...
+s:update(1, {{'+', 2, 10}})
+---
+- [1, 10]
+...
+s:update(1, {{'+', 2, 15}})
+---
+- [1, 25]
+...
+s:update(1, {{'-', 2, 5}})
+---
+- [1, 20]
+...
+s:update(1, {{'-', 2, 20}})
+---
+- [1, 0]
+...
+s:update(1, {{'|', 2, 0x9}})
+---
+- [1, 9]
+...
+s:update(1, {{'|', 2, 0x6}})
+---
+- [1, 15]
+...
+s:update(1, {{'&', 2, 0xabcde}})
+---
+- [1, 14]
+...
+s:update(1, {{'&', 2, 0x2}})
+---
+- [1, 2]
+...
+s:update(1, {{'^', 2, 0xa2}})
+---
+- [1, 160]
+...
+s:update(1, {{'^', 2, 0xa2}})
+---
+- [1, 2]
+...
+i:drop()
+---
+...
+-- test delete multiple fields
+i = s:create_index('a')
+---
+...
+s:insert{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
+---
+- [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
+...
+s:update({0}, {{'#', 42, 1}})
+---
+- error: Field 42 was not found in the tuple
+...
+s:update({0}, {{'#', 4, 'abirvalg'}})
+---
+- error: 'Argument type in operation ''#'' on field 4 does not match field type: expected
+    a number of fields to delete'
+...
+s:update({0}, {{'#', 2, 1}, {'#', 4, 2}, {'#', 6, 1}})
+---
+- [0, 2, 3, 6, 7, 9, 10, 11, 12, 13, 14, 15]
+...
+s:update({0}, {{'#', 4, 3}})
+---
+- [0, 2, 3, 10, 11, 12, 13, 14, 15]
+...
+s:update({0}, {{'#', 5, 123456}})
+---
+- [0, 2, 3, 10]
+...
+s:update({0}, {{'#', 3, 4294967295}})
+---
+- [0, 2]
+...
+s:update({0}, {{'#', 2, 0}})
+---
+- error: 'Field 2 UPDATE error: cannot delete 0 fields'
+...
+i:drop()
+---
+...
+-- test insert field
+i = s:create_index('a')
+---
+...
+s:insert{1, 3, 6, 9}
+---
+- [1, 3, 6, 9]
+...
+s:update({1}, {{'!', 2, 2}})
+---
+- [1, 2, 3, 6, 9]
+...
+s:update({1}, {{'!', 4, 4}, {'!', 4, 5}, {'!', 5, 7}, {'!', 5, 8}})
+---
+- [1, 2, 3, 5, 8, 7, 4, 6, 9]
+...
+s:update({1}, {{'!', 10, 10}, {'!', 10, 11}, {'!', 10, 12}})
+---
+- [1, 2, 3, 5, 8, 7, 4, 6, 9, 12, 11, 10]
+...
+i:drop()
+---
+...
+i = s:create_index('a')
+---
+...
+s:insert{1, 'tuple'}
+---
+- [1, 'tuple']
+...
+s:update({1}, {{'#', 2, 1}, {'!', 2, 'inserted tuple'}, {'=', 3, 'set tuple'}})
+---
+- [1, 'inserted tuple', 'set tuple']
+...
+i:drop()
+---
+...
+i = s:create_index('a')
+---
+...
+s:insert{1, 'tuple'}
+---
+- [1, 'tuple']
+...
+s:update({1}, {{'=', 2, 'set tuple'}, {'!', 2, 'inserted tuple'}, {'#', 3, 1}})
+---
+- [1, 'inserted tuple']
+...
+s:update({1}, {{'!', 1, 3}, {'!', 1, 2}})
+---
+- error: Attempt to modify a tuple field which is part of index 'a' in space 'ephemeral'
+...
+i:drop()
+---
+...
+-- test update's assign opearations
+i = s:create_index('a')
+---
+...
+s:replace{1, 'field string value'}
+---
+- [1, 'field string value']
+...
+s:update({1}, {{'=', 2, 'new field string value'}, {'=', 3, 42}, {'=', 4, 0xdeadbeef}})
+---
+- [1, 'new field string value', 42, 3735928559]
+...
+-- test multiple update opearations on the same field
+s:update({1}, {{'+', 3, 16}, {'&', 4, 0xffff0000}, {'|', 4, 0x0000a0a0}, {'^', 4, 0xffff00aa}})
+---
+- error: 'Field 4 UPDATE error: double update of the same field'
+...
+-- test update splice operation
+s:replace{1953719668, 'something to splice'}
+---
+- [1953719668, 'something to splice']
+...
+s:update(1953719668, {{':', 2, 1, 4, 'no'}})
+---
+- [1953719668, 'nothing to splice']
+...
+s:update(1953719668, {{':', 2, 1, 2, 'every'}})
+---
+- [1953719668, 'everything to splice']
+...
+-- check an incorrect offset
+s:update(1953719668, {{':', 2, 100, 2, 'every'}})
+---
+- [1953719668, 'everything to spliceevery']
+...
+s:update(1953719668, {{':', 2, -100, 2, 'every'}})
+---
+- error: 'SPLICE error on field 2: offset is out of bound'
+...
+i:drop()
+---
+...
+i = s:create_index('a')
+---
+...
+s:insert{1953719668, 'hello', 'october', '20th'}:unpack()
+---
+- 1953719668
+- hello
+- october
+- 20th
+...
+i:drop()
+---
+...
+i = s:create_index('a')
+---
+...
+s:insert{1953719668, 'hello world'}
+---
+- [1953719668, 'hello world']
+...
+s:update(1953719668, {{'=', 2, 'bye, world'}})
+---
+- [1953719668, 'bye, world']
+...
+s:delete{1953719668}
+---
+- [1953719668, 'bye, world']
+...
+s:replace({10, 'abcde'})
+---
+- [10, 'abcde']
+...
+s:update(10,  {{':', 2, 0, 0, '!'}})
+---
+- error: 'SPLICE error on field 2: offset is out of bound'
+...
+s:update(10,  {{':', 2, 1, 0, '('}})
+---
+- [10, '(abcde']
+...
+s:update(10,  {{':', 2, 2, 0, '({'}})
+---
+- [10, '(({abcde']
+...
+s:update(10,  {{':', 2, -1, 0, ')'}})
+---
+- [10, '(({abcde)']
+...
+s:update(10,  {{':', 2, -2, 0, '})'}})
+---
+- [10, '(({abcde}))']
+...
+-- test update delete operations
+s:update({1}, {{'#', 4, 1}, {'#', 3, 1}})
+---
+...
+-- test update insert operations
+s:update({1}, {{'!', 2, 1}, {'!', 2, 2}, {'!', 2, 3}, {'!', 2, 4}})
+---
+...
+-- s:update: zero field
+s:insert{48}
+---
+- [48]
+...
+s:update(48, {{'=', 0, 'hello'}})
+---
+- error: Field 0 was not found in the tuple
+...
+-- s:update: push/pop fields
+s:insert{1684234849}
+---
+- [1684234849]
+...
+s:update({1684234849}, {{'#', 2, 1}})
+---
+- error: Field 2 was not found in the tuple
+...
+s:update({1684234849}, {{'!', -1, 'push1'}})
+---
+- [1684234849, 'push1']
+...
+s:update({1684234849}, {{'!', -1, 'push2'}})
+---
+- [1684234849, 'push1', 'push2']
+...
+s:update({1684234849}, {{'!', -1, 'push3'}})
+---
+- [1684234849, 'push1', 'push2', 'push3']
+...
+s:update({1684234849}, {{'#', 2, 1}, {'!', -1, 'swap1'}})
+---
+- [1684234849, 'push2', 'push3', 'swap1']
+...
+s:update({1684234849}, {{'#', 2, 1}, {'!', -1, 'swap2'}})
+---
+- [1684234849, 'push3', 'swap1', 'swap2']
+...
+s:update({1684234849}, {{'#', 2, 1}, {'!', -1, 'swap3'}})
+---
+- [1684234849, 'swap1', 'swap2', 'swap3']
+...
+s:update({1684234849}, {{'#', -1, 1}, {'!', -1, 'noop1'}})
+---
+- [1684234849, 'swap1', 'swap2', 'noop1']
+...
+s:update({1684234849}, {{'#', -1, 1}, {'!', -1, 'noop2'}})
+---
+- [1684234849, 'swap1', 'swap2', 'noop2']
+...
+s:update({1684234849}, {{'#', -1, 1}, {'!', -1, 'noop3'}})
+---
+- [1684234849, 'swap1', 'swap2', 'noop3']
+...
+--
+-- negative indexes
+--
+box.tuple.new({1, 2, 3, 4, 5}):update({{'!', 0, 'Test'}})
+---
+- error: Field 0 was not found in the tuple
+...
+box.tuple.new({1, 2, 3, 4, 5}):update({{'!', -1, 'Test'}})
+---
+- [1, 2, 3, 4, 5, 'Test']
+...
+box.tuple.new({1, 2, 3, 4, 5}):update({{'!', -3, 'Test'}})
+---
+- [1, 2, 3, 'Test', 4, 5]
+...
+box.tuple.new({1, 2, 3, 4, 5}):update({{'!', -5, 'Test'}})
+---
+- [1, 'Test', 2, 3, 4, 5]
+...
+box.tuple.new({1, 2, 3, 4, 5}):update({{'!', -6, 'Test'}})
+---
+- ['Test', 1, 2, 3, 4, 5]
+...
+box.tuple.new({1, 2, 3, 4, 5}):update({{'!', -7, 'Test'}})
+---
+- error: Field -7 was not found in the tuple
+...
+box.tuple.new({1, 2, 3, 4, 5}):update({{'!', -100500, 'Test'}})
+---
+- error: Field -100500 was not found in the tuple
+...
+box.tuple.new({1, 2, 3, 4, 5}):update({{'=', 0, 'Test'}})
+---
+- error: Field 0 was not found in the tuple
+...
+box.tuple.new({1, 2, 3, 4, 5}):update({{'=', -1, 'Test'}})
+---
+- [1, 2, 3, 4, 'Test']
+...
+box.tuple.new({1, 2, 3, 4, 5}):update({{'=', -3, 'Test'}})
+---
+- [1, 2, 'Test', 4, 5]
+...
+box.tuple.new({1, 2, 3, 4, 5}):update({{'=', -5, 'Test'}})
+---
+- ['Test', 2, 3, 4, 5]
+...
+box.tuple.new({1, 2, 3, 4, 5}):update({{'=', -6, 'Test'}})
+---
+- error: Field -6 was not found in the tuple
+...
+box.tuple.new({1, 2, 3, 4, 5}):update({{'=', -100500, 'Test'}})
+---
+- error: Field -100500 was not found in the tuple
+...
+box.tuple.new({1, 2, 3, 4, 5}):update({{'+', 0, 100}})
+---
+- error: Field 0 was not found in the tuple
+...
+box.tuple.new({1, 2, 3, 4, 5}):update({{'+', -1, 100}})
+---
+- [1, 2, 3, 4, 105]
+...
+box.tuple.new({1, 2, 3, 4, 5}):update({{'+', -3, 100}})
+---
+- [1, 2, 103, 4, 5]
+...
+box.tuple.new({1, 2, 3, 4, 5}):update({{'+', -5, 100}})
+---
+- [101, 2, 3, 4, 5]
+...
+box.tuple.new({1, 2, 3, 4, 5}):update({{'+', -6, 100}})
+---
+- error: Field -6 was not found in the tuple
+...
+box.tuple.new({1, 2, 3, 4, 5}):update({{'+', -100500, 100}})
+---
+- error: Field -100500 was not found in the tuple
+...
+box.tuple.new({1, 2, 3, 4, 5}):update({{'|', 0, 100}})
+---
+- error: Field 0 was not found in the tuple
+...
+box.tuple.new({1, 2, 3, 4, 5}):update({{'|', -1, 100}})
+---
+- [1, 2, 3, 4, 101]
+...
+box.tuple.new({1, 2, 3, 4, 5}):update({{'|', -3, 100}})
+---
+- [1, 2, 103, 4, 5]
+...
+box.tuple.new({1, 2, 3, 4, 5}):update({{'|', -5, 100}})
+---
+- [101, 2, 3, 4, 5]
+...
+box.tuple.new({1, 2, 3, 4, 5}):update({{'|', -6, 100}})
+---
+- error: Field -6 was not found in the tuple
+...
+box.tuple.new({1, 2, 3, 4, 5}):update({{'|', -100500, 100}})
+---
+- error: Field -100500 was not found in the tuple
+...
+box.tuple.new({1, 2, 3, 4, 5}):update({{'#', 0, 1}})
+---
+- error: Field 0 was not found in the tuple
+...
+box.tuple.new({1, 2, 3, 4, 5}):update({{'#', -1, 1}})
+---
+- [1, 2, 3, 4]
+...
+box.tuple.new({1, 2, 3, 4, 5}):update({{'#', -3, 1}})
+---
+- [1, 2, 4, 5]
+...
+box.tuple.new({1, 2, 3, 4, 5}):update({{'#', -5, 1}})
+---
+- [2, 3, 4, 5]
+...
+box.tuple.new({1, 2, 3, 4, 5}):update({{'#', -6, 1}})
+---
+- error: Field -6 was not found in the tuple
+...
+box.tuple.new({1, 2, 3, 4, 5}):update({{'#', -100500, 1}})
+---
+- error: Field -100500 was not found in the tuple
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a')
+---
+...
+s:insert{1, 2, 3}
+---
+- [1, 2, 3]
+...
+s:update({1})
+---
+- error: Usage index:update(key, ops)
+...
+s:update({1}, {'=', 1, 1})
+---
+- error: Illegal parameters, update operation must be an array {op,..}
+...
+s:drop()
+---
+...
+ffi = require('ffi')
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a')
+---
+...
+s:insert{0, -1}
+---
+- [0, -1]
+...
+-- + --
+s:update({0}, {{'+', 2, "a"}}) -- err
+---
+- error: 'Argument type in operation ''+'' on field 2 does not match field type: expected
+    a number'
+...
+s:update({0}, {{'+', 2, 10}}) -- neg(ative) + pos(itive) = pos(itive) 9
+---
+- [0, 9]
+...
+s:update({0}, {{'+', 2, 5}}) -- pos + pos = pos 14
+---
+- [0, 14]
+...
+s:update({0}, {{'+', 2, -4}}) -- pos + neg = pos 10
+---
+- [0, 10]
+...
+s:update({0}, {{'+', 2, -22}}) -- pos + neg = neg -12
+---
+- [0, -12]
+...
+s:update({0}, {{'+', 2, -3}}) -- neg + neg = neg -15
+---
+- [0, -15]
+...
+s:update({0}, {{'+', 2, 7}}) -- neg + pos = neg -8
+---
+- [0, -8]
+...
+-- - --
+s:update({0}, {{'-', 2, "a"}}) -- err
+---
+- error: 'Argument type in operation ''-'' on field 2 does not match field type: expected
+    a number'
+...
+s:update({0}, {{'-', 2, 16}}) -- neg(ative) - pos(itive) = neg(ative) -24
+---
+- [0, -24]
+...
+s:update({0}, {{'-', 2, -4}}) -- neg - neg = neg 20
+---
+- [0, -20]
+...
+s:update({0}, {{'-', 2, -32}}) -- neg - neg = pos 12
+---
+- [0, 12]
+...
+s:update({0}, {{'-', 2, 3}}) -- pos - pos = pos 9
+---
+- [0, 9]
+...
+s:update({0}, {{'-', 2, -5}}) -- pos - neg = pos 14
+---
+- [0, 14]
+...
+s:update({0}, {{'-', 2, 17}}) -- pos - pos = neg -3
+---
+- [0, -3]
+...
+-- bit --
+s:replace{0, 0} -- 0
+---
+- [0, 0]
+...
+s:update({0}, {{'|', 2, 24}}) -- 24
+---
+- [0, 24]
+...
+s:update({0}, {{'|', 2, 2}}) -- 26
+---
+- [0, 26]
+...
+s:update({0}, {{'&', 2, 50}}) -- 18
+---
+- [0, 18]
+...
+s:update({0}, {{'^', 2, 6}}) -- 20
+---
+- [0, 20]
+...
+s:update({0}, {{'|', 2, -1}}) -- err
+---
+- error: 'Argument type in operation ''|'' on field 2 does not match field type: expected
+    a positive integer'
+...
+s:update({0}, {{'&', 2, -1}}) -- err
+---
+- error: 'Argument type in operation ''&'' on field 2 does not match field type: expected
+    a positive integer'
+...
+s:update({0}, {{'^', 2, -1}}) -- err
+---
+- error: 'Argument type in operation ''^'' on field 2 does not match field type: expected
+    a positive integer'
+...
+s:replace{0, -1} -- -1
+---
+- [0, -1]
+...
+s:update({0}, {{'|', 2, 2}}) -- err
+---
+- error: 'Argument type in operation ''|'' on field 2 does not match field type: expected
+    a positive integer'
+...
+s:update({0}, {{'&', 2, 40}}) -- err
+---
+- error: 'Argument type in operation ''&'' on field 2 does not match field type: expected
+    a positive integer'
+...
+s:update({0}, {{'^', 2, 6}}) -- err
+---
+- error: 'Argument type in operation ''^'' on field 2 does not match field type: expected
+    a positive integer'
+...
+s:replace{0, 1.5} -- 1.5
+---
+- [0, 1.5]
+...
+s:update({0}, {{'|', 2, 2}}) -- err
+---
+- error: 'Argument type in operation ''|'' on field 2 does not match field type: expected
+    a positive integer'
+...
+s:update({0}, {{'&', 2, 40}}) -- err
+---
+- error: 'Argument type in operation ''&'' on field 2 does not match field type: expected
+    a positive integer'
+...
+s:update({0}, {{'^', 2, 6}}) -- err
+---
+- error: 'Argument type in operation ''^'' on field 2 does not match field type: expected
+    a positive integer'
+...
+-- double
+s:replace{0, 5} -- 5
+---
+- [0, 5]
+...
+s:update({0}, {{'+', 2, 1.5}}) -- int + double = double 6.5
+---
+- [0, 6.5]
+...
+s:update({0}, {{'|', 2, 2}}) -- err (double!)
+---
+- error: 'Argument type in operation ''|'' on field 2 does not match field type: expected
+    a positive integer'
+...
+s:update({0}, {{'-', 2, 0.5}}) -- double - double = double 6
+---
+- [0, 6]
+...
+s:update({0}, {{'+', 2, 1.5}}) -- double + double = double 7.5
+---
+- [0, 7.5]
+...
+-- float
+s:replace{0, ffi.new("float", 1.5)} -- 1.5
+---
+- [0, 1.5]
+...
+s:update({0}, {{'+', 2, 2}}) -- float + int = float 3.5
+---
+- [0, 3.5]
+...
+s:update({0}, {{'+', 2, ffi.new("float", 3.5)}}) -- float + int = float 7
+---
+- [0, 7]
+...
+s:update({0}, {{'|', 2, 2}}) -- err (float!)
+---
+- error: 'Argument type in operation ''|'' on field 2 does not match field type: expected
+    a positive integer'
+...
+s:update({0}, {{'-', 2, ffi.new("float", 1.5)}}) -- float - float = float 5.5
+---
+- [0, 5.5]
+...
+s:update({0}, {{'+', 2, ffi.new("float", 3.5)}}) -- float + float = float 9
+---
+- [0, 9]
+...
+s:update({0}, {{'-', 2, ffi.new("float", 9)}}) -- float + float = float 0
+---
+- [0, 0]
+...
+s:update({0}, {{'+', 2, ffi.new("float", 1.2)}}) -- float + float = float 1.2
+---
+- [0, 1.2000000476837]
+...
+-- overflow --
+s:replace{0, 0xfffffffffffffffeull}
+---
+- [0, 18446744073709551614]
+...
+s:update({0}, {{'+', 2, 1}}) -- ok
+---
+- [0, 18446744073709551615]
+...
+s:update({0}, {{'+', 2, 1}}) -- overflow
+---
+- error: Integer overflow when performing '+' operation on field 2
+...
+s:update({0}, {{'+', 2, 100500}}) -- overflow
+---
+- error: Integer overflow when performing '+' operation on field 2
+...
+s:replace{0, 1}
+---
+- [0, 1]
+...
+s:update({0}, {{'+', 2, 0xffffffffffffffffull}})  -- overflow
+---
+- error: Integer overflow when performing '+' operation on field 2
+...
+s:replace{0, -1}
+---
+- [0, -1]
+...
+s:update({0}, {{'+', 2, 0xffffffffffffffffull}})  -- ok
+---
+- [0, 18446744073709551614]
+...
+s:replace{0, 0}
+---
+- [0, 0]
+...
+s:update({0}, {{'-', 2, 0x7fffffffffffffffull}})  -- ok
+---
+- [0, -9223372036854775807]
+...
+s:replace{0, -1}
+---
+- [0, -1]
+...
+s:update({0}, {{'-', 2, 0x7fffffffffffffffull}})  -- ok
+---
+- [0, -9223372036854775808]
+...
+s:replace{0, -2}
+---
+- [0, -2]
+...
+s:update({0}, {{'-', 2, 0x7fffffffffffffffull}})  -- overflow
+---
+- error: Integer overflow when performing '-' operation on field 2
+...
+s:replace{0, 1}
+---
+- [0, 1]
+...
+s:update({0}, {{'-', 2, 0xffffffffffffffffull}})  -- overflow
+---
+- error: Integer overflow when performing '-' operation on field 2
+...
+s:replace{0, 0xffffffffffffffefull}
+---
+- [0, 18446744073709551599]
+...
+s:update({0}, {{'-', 2, -16}})  -- ok
+---
+- [0, 18446744073709551615]
+...
+s:update({0}, {{'-', 2, -16}})  -- overflow
+---
+- error: Integer overflow when performing '-' operation on field 2
+...
+s:replace{0, -0x4000000000000000ll}
+---
+- [0, -4611686018427387904]
+...
+s:update({0}, {{'+', 2, -0x4000000000000000ll}})  -- ok
+---
+- [0, -9223372036854775808]
+...
+s:replace{0, -0x4000000000000000ll}
+---
+- [0, -4611686018427387904]
+...
+s:update({0}, {{'+', 2, -0x4000000000000001ll}})  -- overflow
+---
+- error: Integer overflow when performing '+' operation on field 2
+...
+-- some wrong updates --
+s:update({0}, 0)
+---
+- error: Usage index:update(key, ops)
+...
+s:update({0}, {'+', 2, 2})
+---
+- error: Illegal parameters, update operation must be an array {op,..}
+...
+s:update({0}, {{}})
+---
+- error: Illegal parameters, update operation must be an array {op,..}, got empty
+    array
+...
+s:update({0}, {{'+'}})
+---
+- error: Unknown UPDATE operation
+...
+s:update({0}, {{'+', 0}})
+---
+- error: Unknown UPDATE operation
+...
+s:update({0}, {{'+', '+', '+'}})
+---
+- error: Illegal parameters, field id must be a number
+...
+s:update({0}, {{0, 0, 0}})
+---
+- error: Illegal parameters, update operation name must be a string
+...
+ops = {}
+---
+...
+for i = 1,10 do table.insert(ops, {'=', 2, '1234567890'}) end
+---
+...
+s:upsert({0}, ops)
+---
+...
+s:get{0}
+---
+- [0, '1234567890']
+...
+s:update({0}, {})
+---
+- [0, '1234567890']
+...
+map = setmetatable({}, { __serialize = 'map' })
+---
+...
+t = box.tuple.new({1, 2, 3})
+---
+...
+s:replace({1, 2, 3})
+---
+- [1, 2, 3]
+...
+t:update({{'=', 3, map}})
+---
+- [1, 2, {}]
+...
+s:update(1, {{'=', 3, map}})
+---
+- [1, 2, {}]
+...
+s:drop()
+---
+...
+-- Ephemeral index: methods: pairs, count, select (iterators)
+-- iterator (str)
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'string'} })
+---
+...
+for key = 1, 100 do s:replace({tostring(key)}) end
+---
+...
+t = {} for state, v in i:pairs({}, {iterator = 'ALL'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - ['1']
+  - ['10']
+  - ['100']
+  - ['11']
+  - ['12']
+  - ['13']
+  - ['14']
+  - ['15']
+  - ['16']
+  - ['17']
+  - ['18']
+  - ['19']
+  - ['2']
+  - ['20']
+  - ['21']
+  - ['22']
+  - ['23']
+  - ['24']
+  - ['25']
+  - ['26']
+  - ['27']
+  - ['28']
+  - ['29']
+  - ['3']
+  - ['30']
+  - ['31']
+  - ['32']
+  - ['33']
+  - ['34']
+  - ['35']
+  - ['36']
+  - ['37']
+  - ['38']
+  - ['39']
+  - ['4']
+  - ['40']
+  - ['41']
+  - ['42']
+  - ['43']
+  - ['44']
+  - ['45']
+  - ['46']
+  - ['47']
+  - ['48']
+  - ['49']
+  - ['5']
+  - ['50']
+  - ['51']
+  - ['52']
+  - ['53']
+  - ['54']
+  - ['55']
+  - ['56']
+  - ['57']
+  - ['58']
+  - ['59']
+  - ['6']
+  - ['60']
+  - ['61']
+  - ['62']
+  - ['63']
+  - ['64']
+  - ['65']
+  - ['66']
+  - ['67']
+  - ['68']
+  - ['69']
+  - ['7']
+  - ['70']
+  - ['71']
+  - ['72']
+  - ['73']
+  - ['74']
+  - ['75']
+  - ['76']
+  - ['77']
+  - ['78']
+  - ['79']
+  - ['8']
+  - ['80']
+  - ['81']
+  - ['82']
+  - ['83']
+  - ['84']
+  - ['85']
+  - ['86']
+  - ['87']
+  - ['88']
+  - ['89']
+  - ['9']
+  - ['90']
+  - ['91']
+  - ['92']
+  - ['93']
+  - ['94']
+  - ['95']
+  - ['96']
+  - ['97']
+  - ['98']
+  - ['99']
+...
+t = {} for state, v in i:pairs({}, {iterator = 'GE'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - ['1']
+  - ['10']
+  - ['100']
+  - ['11']
+  - ['12']
+  - ['13']
+  - ['14']
+  - ['15']
+  - ['16']
+  - ['17']
+  - ['18']
+  - ['19']
+  - ['2']
+  - ['20']
+  - ['21']
+  - ['22']
+  - ['23']
+  - ['24']
+  - ['25']
+  - ['26']
+  - ['27']
+  - ['28']
+  - ['29']
+  - ['3']
+  - ['30']
+  - ['31']
+  - ['32']
+  - ['33']
+  - ['34']
+  - ['35']
+  - ['36']
+  - ['37']
+  - ['38']
+  - ['39']
+  - ['4']
+  - ['40']
+  - ['41']
+  - ['42']
+  - ['43']
+  - ['44']
+  - ['45']
+  - ['46']
+  - ['47']
+  - ['48']
+  - ['49']
+  - ['5']
+  - ['50']
+  - ['51']
+  - ['52']
+  - ['53']
+  - ['54']
+  - ['55']
+  - ['56']
+  - ['57']
+  - ['58']
+  - ['59']
+  - ['6']
+  - ['60']
+  - ['61']
+  - ['62']
+  - ['63']
+  - ['64']
+  - ['65']
+  - ['66']
+  - ['67']
+  - ['68']
+  - ['69']
+  - ['7']
+  - ['70']
+  - ['71']
+  - ['72']
+  - ['73']
+  - ['74']
+  - ['75']
+  - ['76']
+  - ['77']
+  - ['78']
+  - ['79']
+  - ['8']
+  - ['80']
+  - ['81']
+  - ['82']
+  - ['83']
+  - ['84']
+  - ['85']
+  - ['86']
+  - ['87']
+  - ['88']
+  - ['89']
+  - ['9']
+  - ['90']
+  - ['91']
+  - ['92']
+  - ['93']
+  - ['94']
+  - ['95']
+  - ['96']
+  - ['97']
+  - ['98']
+  - ['99']
+...
+t = {} for state, v in i:pairs(tostring(44), {iterator = 'GE'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - ['44']
+  - ['45']
+  - ['46']
+  - ['47']
+  - ['48']
+  - ['49']
+  - ['5']
+  - ['50']
+  - ['51']
+  - ['52']
+  - ['53']
+  - ['54']
+  - ['55']
+  - ['56']
+  - ['57']
+  - ['58']
+  - ['59']
+  - ['6']
+  - ['60']
+  - ['61']
+  - ['62']
+  - ['63']
+  - ['64']
+  - ['65']
+  - ['66']
+  - ['67']
+  - ['68']
+  - ['69']
+  - ['7']
+  - ['70']
+  - ['71']
+  - ['72']
+  - ['73']
+  - ['74']
+  - ['75']
+  - ['76']
+  - ['77']
+  - ['78']
+  - ['79']
+  - ['8']
+  - ['80']
+  - ['81']
+  - ['82']
+  - ['83']
+  - ['84']
+  - ['85']
+  - ['86']
+  - ['87']
+  - ['88']
+  - ['89']
+  - ['9']
+  - ['90']
+  - ['91']
+  - ['92']
+  - ['93']
+  - ['94']
+  - ['95']
+  - ['96']
+  - ['97']
+  - ['98']
+  - ['99']
+...
+t = {} for state, v in i:pairs(tostring(44), {iterator = 'GT'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - ['45']
+  - ['46']
+  - ['47']
+  - ['48']
+  - ['49']
+  - ['5']
+  - ['50']
+  - ['51']
+  - ['52']
+  - ['53']
+  - ['54']
+  - ['55']
+  - ['56']
+  - ['57']
+  - ['58']
+  - ['59']
+  - ['6']
+  - ['60']
+  - ['61']
+  - ['62']
+  - ['63']
+  - ['64']
+  - ['65']
+  - ['66']
+  - ['67']
+  - ['68']
+  - ['69']
+  - ['7']
+  - ['70']
+  - ['71']
+  - ['72']
+  - ['73']
+  - ['74']
+  - ['75']
+  - ['76']
+  - ['77']
+  - ['78']
+  - ['79']
+  - ['8']
+  - ['80']
+  - ['81']
+  - ['82']
+  - ['83']
+  - ['84']
+  - ['85']
+  - ['86']
+  - ['87']
+  - ['88']
+  - ['89']
+  - ['9']
+  - ['90']
+  - ['91']
+  - ['92']
+  - ['93']
+  - ['94']
+  - ['95']
+  - ['96']
+  - ['97']
+  - ['98']
+  - ['99']
+...
+t = {} for state, v in i:pairs({}, {iterator = 'LE'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - ['99']
+  - ['98']
+  - ['97']
+  - ['96']
+  - ['95']
+  - ['94']
+  - ['93']
+  - ['92']
+  - ['91']
+  - ['90']
+  - ['9']
+  - ['89']
+  - ['88']
+  - ['87']
+  - ['86']
+  - ['85']
+  - ['84']
+  - ['83']
+  - ['82']
+  - ['81']
+  - ['80']
+  - ['8']
+  - ['79']
+  - ['78']
+  - ['77']
+  - ['76']
+  - ['75']
+  - ['74']
+  - ['73']
+  - ['72']
+  - ['71']
+  - ['70']
+  - ['7']
+  - ['69']
+  - ['68']
+  - ['67']
+  - ['66']
+  - ['65']
+  - ['64']
+  - ['63']
+  - ['62']
+  - ['61']
+  - ['60']
+  - ['6']
+  - ['59']
+  - ['58']
+  - ['57']
+  - ['56']
+  - ['55']
+  - ['54']
+  - ['53']
+  - ['52']
+  - ['51']
+  - ['50']
+  - ['5']
+  - ['49']
+  - ['48']
+  - ['47']
+  - ['46']
+  - ['45']
+  - ['44']
+  - ['43']
+  - ['42']
+  - ['41']
+  - ['40']
+  - ['4']
+  - ['39']
+  - ['38']
+  - ['37']
+  - ['36']
+  - ['35']
+  - ['34']
+  - ['33']
+  - ['32']
+  - ['31']
+  - ['30']
+  - ['3']
+  - ['29']
+  - ['28']
+  - ['27']
+  - ['26']
+  - ['25']
+  - ['24']
+  - ['23']
+  - ['22']
+  - ['21']
+  - ['20']
+  - ['2']
+  - ['19']
+  - ['18']
+  - ['17']
+  - ['16']
+  - ['15']
+  - ['14']
+  - ['13']
+  - ['12']
+  - ['11']
+  - ['100']
+  - ['10']
+  - ['1']
+...
+t = {} for state, v in i:pairs(tostring(77), {iterator = 'LE'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - ['77']
+  - ['76']
+  - ['75']
+  - ['74']
+  - ['73']
+  - ['72']
+  - ['71']
+  - ['70']
+  - ['7']
+  - ['69']
+  - ['68']
+  - ['67']
+  - ['66']
+  - ['65']
+  - ['64']
+  - ['63']
+  - ['62']
+  - ['61']
+  - ['60']
+  - ['6']
+  - ['59']
+  - ['58']
+  - ['57']
+  - ['56']
+  - ['55']
+  - ['54']
+  - ['53']
+  - ['52']
+  - ['51']
+  - ['50']
+  - ['5']
+  - ['49']
+  - ['48']
+  - ['47']
+  - ['46']
+  - ['45']
+  - ['44']
+  - ['43']
+  - ['42']
+  - ['41']
+  - ['40']
+  - ['4']
+  - ['39']
+  - ['38']
+  - ['37']
+  - ['36']
+  - ['35']
+  - ['34']
+  - ['33']
+  - ['32']
+  - ['31']
+  - ['30']
+  - ['3']
+  - ['29']
+  - ['28']
+  - ['27']
+  - ['26']
+  - ['25']
+  - ['24']
+  - ['23']
+  - ['22']
+  - ['21']
+  - ['20']
+  - ['2']
+  - ['19']
+  - ['18']
+  - ['17']
+  - ['16']
+  - ['15']
+  - ['14']
+  - ['13']
+  - ['12']
+  - ['11']
+  - ['100']
+  - ['10']
+  - ['1']
+...
+t = {} for state, v in i:pairs({}, {iterator = 'LT'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - ['99']
+  - ['98']
+  - ['97']
+  - ['96']
+  - ['95']
+  - ['94']
+  - ['93']
+  - ['92']
+  - ['91']
+  - ['90']
+  - ['9']
+  - ['89']
+  - ['88']
+  - ['87']
+  - ['86']
+  - ['85']
+  - ['84']
+  - ['83']
+  - ['82']
+  - ['81']
+  - ['80']
+  - ['8']
+  - ['79']
+  - ['78']
+  - ['77']
+  - ['76']
+  - ['75']
+  - ['74']
+  - ['73']
+  - ['72']
+  - ['71']
+  - ['70']
+  - ['7']
+  - ['69']
+  - ['68']
+  - ['67']
+  - ['66']
+  - ['65']
+  - ['64']
+  - ['63']
+  - ['62']
+  - ['61']
+  - ['60']
+  - ['6']
+  - ['59']
+  - ['58']
+  - ['57']
+  - ['56']
+  - ['55']
+  - ['54']
+  - ['53']
+  - ['52']
+  - ['51']
+  - ['50']
+  - ['5']
+  - ['49']
+  - ['48']
+  - ['47']
+  - ['46']
+  - ['45']
+  - ['44']
+  - ['43']
+  - ['42']
+  - ['41']
+  - ['40']
+  - ['4']
+  - ['39']
+  - ['38']
+  - ['37']
+  - ['36']
+  - ['35']
+  - ['34']
+  - ['33']
+  - ['32']
+  - ['31']
+  - ['30']
+  - ['3']
+  - ['29']
+  - ['28']
+  - ['27']
+  - ['26']
+  - ['25']
+  - ['24']
+  - ['23']
+  - ['22']
+  - ['21']
+  - ['20']
+  - ['2']
+  - ['19']
+  - ['18']
+  - ['17']
+  - ['16']
+  - ['15']
+  - ['14']
+  - ['13']
+  - ['12']
+  - ['11']
+  - ['100']
+  - ['10']
+  - ['1']
+...
+t = {} for state, v in i:pairs(tostring(77), {iterator = 'LT'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - ['76']
+  - ['75']
+  - ['74']
+  - ['73']
+  - ['72']
+  - ['71']
+  - ['70']
+  - ['7']
+  - ['69']
+  - ['68']
+  - ['67']
+  - ['66']
+  - ['65']
+  - ['64']
+  - ['63']
+  - ['62']
+  - ['61']
+  - ['60']
+  - ['6']
+  - ['59']
+  - ['58']
+  - ['57']
+  - ['56']
+  - ['55']
+  - ['54']
+  - ['53']
+  - ['52']
+  - ['51']
+  - ['50']
+  - ['5']
+  - ['49']
+  - ['48']
+  - ['47']
+  - ['46']
+  - ['45']
+  - ['44']
+  - ['43']
+  - ['42']
+  - ['41']
+  - ['40']
+  - ['4']
+  - ['39']
+  - ['38']
+  - ['37']
+  - ['36']
+  - ['35']
+  - ['34']
+  - ['33']
+  - ['32']
+  - ['31']
+  - ['30']
+  - ['3']
+  - ['29']
+  - ['28']
+  - ['27']
+  - ['26']
+  - ['25']
+  - ['24']
+  - ['23']
+  - ['22']
+  - ['21']
+  - ['20']
+  - ['2']
+  - ['19']
+  - ['18']
+  - ['17']
+  - ['16']
+  - ['15']
+  - ['14']
+  - ['13']
+  - ['12']
+  - ['11']
+  - ['100']
+  - ['10']
+  - ['1']
+...
+s:drop()
+---
+...
+-- iterator (num)
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+---
+...
+for key = 1, 100 do s:replace({key}) end
+---
+...
+t = {} for state, v in i:pairs({}, {iterator = 'ALL'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [1]
+  - [2]
+  - [3]
+  - [4]
+  - [5]
+  - [6]
+  - [7]
+  - [8]
+  - [9]
+  - [10]
+  - [11]
+  - [12]
+  - [13]
+  - [14]
+  - [15]
+  - [16]
+  - [17]
+  - [18]
+  - [19]
+  - [20]
+  - [21]
+  - [22]
+  - [23]
+  - [24]
+  - [25]
+  - [26]
+  - [27]
+  - [28]
+  - [29]
+  - [30]
+  - [31]
+  - [32]
+  - [33]
+  - [34]
+  - [35]
+  - [36]
+  - [37]
+  - [38]
+  - [39]
+  - [40]
+  - [41]
+  - [42]
+  - [43]
+  - [44]
+  - [45]
+  - [46]
+  - [47]
+  - [48]
+  - [49]
+  - [50]
+  - [51]
+  - [52]
+  - [53]
+  - [54]
+  - [55]
+  - [56]
+  - [57]
+  - [58]
+  - [59]
+  - [60]
+  - [61]
+  - [62]
+  - [63]
+  - [64]
+  - [65]
+  - [66]
+  - [67]
+  - [68]
+  - [69]
+  - [70]
+  - [71]
+  - [72]
+  - [73]
+  - [74]
+  - [75]
+  - [76]
+  - [77]
+  - [78]
+  - [79]
+  - [80]
+  - [81]
+  - [82]
+  - [83]
+  - [84]
+  - [85]
+  - [86]
+  - [87]
+  - [88]
+  - [89]
+  - [90]
+  - [91]
+  - [92]
+  - [93]
+  - [94]
+  - [95]
+  - [96]
+  - [97]
+  - [98]
+  - [99]
+  - [100]
+...
+t = {} for state, v in i:pairs({}, {iterator = 'GE'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [1]
+  - [2]
+  - [3]
+  - [4]
+  - [5]
+  - [6]
+  - [7]
+  - [8]
+  - [9]
+  - [10]
+  - [11]
+  - [12]
+  - [13]
+  - [14]
+  - [15]
+  - [16]
+  - [17]
+  - [18]
+  - [19]
+  - [20]
+  - [21]
+  - [22]
+  - [23]
+  - [24]
+  - [25]
+  - [26]
+  - [27]
+  - [28]
+  - [29]
+  - [30]
+  - [31]
+  - [32]
+  - [33]
+  - [34]
+  - [35]
+  - [36]
+  - [37]
+  - [38]
+  - [39]
+  - [40]
+  - [41]
+  - [42]
+  - [43]
+  - [44]
+  - [45]
+  - [46]
+  - [47]
+  - [48]
+  - [49]
+  - [50]
+  - [51]
+  - [52]
+  - [53]
+  - [54]
+  - [55]
+  - [56]
+  - [57]
+  - [58]
+  - [59]
+  - [60]
+  - [61]
+  - [62]
+  - [63]
+  - [64]
+  - [65]
+  - [66]
+  - [67]
+  - [68]
+  - [69]
+  - [70]
+  - [71]
+  - [72]
+  - [73]
+  - [74]
+  - [75]
+  - [76]
+  - [77]
+  - [78]
+  - [79]
+  - [80]
+  - [81]
+  - [82]
+  - [83]
+  - [84]
+  - [85]
+  - [86]
+  - [87]
+  - [88]
+  - [89]
+  - [90]
+  - [91]
+  - [92]
+  - [93]
+  - [94]
+  - [95]
+  - [96]
+  - [97]
+  - [98]
+  - [99]
+  - [100]
+...
+t = {} for state, v in i:pairs(44, {iterator = 'GE'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [44]
+  - [45]
+  - [46]
+  - [47]
+  - [48]
+  - [49]
+  - [50]
+  - [51]
+  - [52]
+  - [53]
+  - [54]
+  - [55]
+  - [56]
+  - [57]
+  - [58]
+  - [59]
+  - [60]
+  - [61]
+  - [62]
+  - [63]
+  - [64]
+  - [65]
+  - [66]
+  - [67]
+  - [68]
+  - [69]
+  - [70]
+  - [71]
+  - [72]
+  - [73]
+  - [74]
+  - [75]
+  - [76]
+  - [77]
+  - [78]
+  - [79]
+  - [80]
+  - [81]
+  - [82]
+  - [83]
+  - [84]
+  - [85]
+  - [86]
+  - [87]
+  - [88]
+  - [89]
+  - [90]
+  - [91]
+  - [92]
+  - [93]
+  - [94]
+  - [95]
+  - [96]
+  - [97]
+  - [98]
+  - [99]
+  - [100]
+...
+t = {} for state, v in i:pairs(44, {iterator = 'GT'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [45]
+  - [46]
+  - [47]
+  - [48]
+  - [49]
+  - [50]
+  - [51]
+  - [52]
+  - [53]
+  - [54]
+  - [55]
+  - [56]
+  - [57]
+  - [58]
+  - [59]
+  - [60]
+  - [61]
+  - [62]
+  - [63]
+  - [64]
+  - [65]
+  - [66]
+  - [67]
+  - [68]
+  - [69]
+  - [70]
+  - [71]
+  - [72]
+  - [73]
+  - [74]
+  - [75]
+  - [76]
+  - [77]
+  - [78]
+  - [79]
+  - [80]
+  - [81]
+  - [82]
+  - [83]
+  - [84]
+  - [85]
+  - [86]
+  - [87]
+  - [88]
+  - [89]
+  - [90]
+  - [91]
+  - [92]
+  - [93]
+  - [94]
+  - [95]
+  - [96]
+  - [97]
+  - [98]
+  - [99]
+  - [100]
+...
+t = {} for state, v in i:pairs({}, {iterator = 'LE'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [100]
+  - [99]
+  - [98]
+  - [97]
+  - [96]
+  - [95]
+  - [94]
+  - [93]
+  - [92]
+  - [91]
+  - [90]
+  - [89]
+  - [88]
+  - [87]
+  - [86]
+  - [85]
+  - [84]
+  - [83]
+  - [82]
+  - [81]
+  - [80]
+  - [79]
+  - [78]
+  - [77]
+  - [76]
+  - [75]
+  - [74]
+  - [73]
+  - [72]
+  - [71]
+  - [70]
+  - [69]
+  - [68]
+  - [67]
+  - [66]
+  - [65]
+  - [64]
+  - [63]
+  - [62]
+  - [61]
+  - [60]
+  - [59]
+  - [58]
+  - [57]
+  - [56]
+  - [55]
+  - [54]
+  - [53]
+  - [52]
+  - [51]
+  - [50]
+  - [49]
+  - [48]
+  - [47]
+  - [46]
+  - [45]
+  - [44]
+  - [43]
+  - [42]
+  - [41]
+  - [40]
+  - [39]
+  - [38]
+  - [37]
+  - [36]
+  - [35]
+  - [34]
+  - [33]
+  - [32]
+  - [31]
+  - [30]
+  - [29]
+  - [28]
+  - [27]
+  - [26]
+  - [25]
+  - [24]
+  - [23]
+  - [22]
+  - [21]
+  - [20]
+  - [19]
+  - [18]
+  - [17]
+  - [16]
+  - [15]
+  - [14]
+  - [13]
+  - [12]
+  - [11]
+  - [10]
+  - [9]
+  - [8]
+  - [7]
+  - [6]
+  - [5]
+  - [4]
+  - [3]
+  - [2]
+  - [1]
+...
+t = {} for state, v in i:pairs(77, {iterator = 'LE'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [77]
+  - [76]
+  - [75]
+  - [74]
+  - [73]
+  - [72]
+  - [71]
+  - [70]
+  - [69]
+  - [68]
+  - [67]
+  - [66]
+  - [65]
+  - [64]
+  - [63]
+  - [62]
+  - [61]
+  - [60]
+  - [59]
+  - [58]
+  - [57]
+  - [56]
+  - [55]
+  - [54]
+  - [53]
+  - [52]
+  - [51]
+  - [50]
+  - [49]
+  - [48]
+  - [47]
+  - [46]
+  - [45]
+  - [44]
+  - [43]
+  - [42]
+  - [41]
+  - [40]
+  - [39]
+  - [38]
+  - [37]
+  - [36]
+  - [35]
+  - [34]
+  - [33]
+  - [32]
+  - [31]
+  - [30]
+  - [29]
+  - [28]
+  - [27]
+  - [26]
+  - [25]
+  - [24]
+  - [23]
+  - [22]
+  - [21]
+  - [20]
+  - [19]
+  - [18]
+  - [17]
+  - [16]
+  - [15]
+  - [14]
+  - [13]
+  - [12]
+  - [11]
+  - [10]
+  - [9]
+  - [8]
+  - [7]
+  - [6]
+  - [5]
+  - [4]
+  - [3]
+  - [2]
+  - [1]
+...
+t = {} for state, v in i:pairs({}, {iterator = 'LT'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [100]
+  - [99]
+  - [98]
+  - [97]
+  - [96]
+  - [95]
+  - [94]
+  - [93]
+  - [92]
+  - [91]
+  - [90]
+  - [89]
+  - [88]
+  - [87]
+  - [86]
+  - [85]
+  - [84]
+  - [83]
+  - [82]
+  - [81]
+  - [80]
+  - [79]
+  - [78]
+  - [77]
+  - [76]
+  - [75]
+  - [74]
+  - [73]
+  - [72]
+  - [71]
+  - [70]
+  - [69]
+  - [68]
+  - [67]
+  - [66]
+  - [65]
+  - [64]
+  - [63]
+  - [62]
+  - [61]
+  - [60]
+  - [59]
+  - [58]
+  - [57]
+  - [56]
+  - [55]
+  - [54]
+  - [53]
+  - [52]
+  - [51]
+  - [50]
+  - [49]
+  - [48]
+  - [47]
+  - [46]
+  - [45]
+  - [44]
+  - [43]
+  - [42]
+  - [41]
+  - [40]
+  - [39]
+  - [38]
+  - [37]
+  - [36]
+  - [35]
+  - [34]
+  - [33]
+  - [32]
+  - [31]
+  - [30]
+  - [29]
+  - [28]
+  - [27]
+  - [26]
+  - [25]
+  - [24]
+  - [23]
+  - [22]
+  - [21]
+  - [20]
+  - [19]
+  - [18]
+  - [17]
+  - [16]
+  - [15]
+  - [14]
+  - [13]
+  - [12]
+  - [11]
+  - [10]
+  - [9]
+  - [8]
+  - [7]
+  - [6]
+  - [5]
+  - [4]
+  - [3]
+  - [2]
+  - [1]
+...
+t = {} for state, v in i:pairs(77, {iterator = 'LT'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [76]
+  - [75]
+  - [74]
+  - [73]
+  - [72]
+  - [71]
+  - [70]
+  - [69]
+  - [68]
+  - [67]
+  - [66]
+  - [65]
+  - [64]
+  - [63]
+  - [62]
+  - [61]
+  - [60]
+  - [59]
+  - [58]
+  - [57]
+  - [56]
+  - [55]
+  - [54]
+  - [53]
+  - [52]
+  - [51]
+  - [50]
+  - [49]
+  - [48]
+  - [47]
+  - [46]
+  - [45]
+  - [44]
+  - [43]
+  - [42]
+  - [41]
+  - [40]
+  - [39]
+  - [38]
+  - [37]
+  - [36]
+  - [35]
+  - [34]
+  - [33]
+  - [32]
+  - [31]
+  - [30]
+  - [29]
+  - [28]
+  - [27]
+  - [26]
+  - [25]
+  - [24]
+  - [23]
+  - [22]
+  - [21]
+  - [20]
+  - [19]
+  - [18]
+  - [17]
+  - [16]
+  - [15]
+  - [14]
+  - [13]
+  - [12]
+  - [11]
+  - [10]
+  - [9]
+  - [8]
+  - [7]
+  - [6]
+  - [5]
+  - [4]
+  - [3]
+  - [2]
+  - [1]
+...
+s:drop()
+---
+...
+-- iterator multi-part (num, num)
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned', 2, 'unsigned'} })
+---
+...
+for key = 1, 100 do s:replace({key, key}) end
+---
+...
+t = {} for state, v in i:pairs({}, {iterator = 'ALL'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [1, 1]
+  - [2, 2]
+  - [3, 3]
+  - [4, 4]
+  - [5, 5]
+  - [6, 6]
+  - [7, 7]
+  - [8, 8]
+  - [9, 9]
+  - [10, 10]
+  - [11, 11]
+  - [12, 12]
+  - [13, 13]
+  - [14, 14]
+  - [15, 15]
+  - [16, 16]
+  - [17, 17]
+  - [18, 18]
+  - [19, 19]
+  - [20, 20]
+  - [21, 21]
+  - [22, 22]
+  - [23, 23]
+  - [24, 24]
+  - [25, 25]
+  - [26, 26]
+  - [27, 27]
+  - [28, 28]
+  - [29, 29]
+  - [30, 30]
+  - [31, 31]
+  - [32, 32]
+  - [33, 33]
+  - [34, 34]
+  - [35, 35]
+  - [36, 36]
+  - [37, 37]
+  - [38, 38]
+  - [39, 39]
+  - [40, 40]
+  - [41, 41]
+  - [42, 42]
+  - [43, 43]
+  - [44, 44]
+  - [45, 45]
+  - [46, 46]
+  - [47, 47]
+  - [48, 48]
+  - [49, 49]
+  - [50, 50]
+  - [51, 51]
+  - [52, 52]
+  - [53, 53]
+  - [54, 54]
+  - [55, 55]
+  - [56, 56]
+  - [57, 57]
+  - [58, 58]
+  - [59, 59]
+  - [60, 60]
+  - [61, 61]
+  - [62, 62]
+  - [63, 63]
+  - [64, 64]
+  - [65, 65]
+  - [66, 66]
+  - [67, 67]
+  - [68, 68]
+  - [69, 69]
+  - [70, 70]
+  - [71, 71]
+  - [72, 72]
+  - [73, 73]
+  - [74, 74]
+  - [75, 75]
+  - [76, 76]
+  - [77, 77]
+  - [78, 78]
+  - [79, 79]
+  - [80, 80]
+  - [81, 81]
+  - [82, 82]
+  - [83, 83]
+  - [84, 84]
+  - [85, 85]
+  - [86, 86]
+  - [87, 87]
+  - [88, 88]
+  - [89, 89]
+  - [90, 90]
+  - [91, 91]
+  - [92, 92]
+  - [93, 93]
+  - [94, 94]
+  - [95, 95]
+  - [96, 96]
+  - [97, 97]
+  - [98, 98]
+  - [99, 99]
+  - [100, 100]
+...
+t = {} for state, v in i:pairs({}, {iterator = 'GE'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [1, 1]
+  - [2, 2]
+  - [3, 3]
+  - [4, 4]
+  - [5, 5]
+  - [6, 6]
+  - [7, 7]
+  - [8, 8]
+  - [9, 9]
+  - [10, 10]
+  - [11, 11]
+  - [12, 12]
+  - [13, 13]
+  - [14, 14]
+  - [15, 15]
+  - [16, 16]
+  - [17, 17]
+  - [18, 18]
+  - [19, 19]
+  - [20, 20]
+  - [21, 21]
+  - [22, 22]
+  - [23, 23]
+  - [24, 24]
+  - [25, 25]
+  - [26, 26]
+  - [27, 27]
+  - [28, 28]
+  - [29, 29]
+  - [30, 30]
+  - [31, 31]
+  - [32, 32]
+  - [33, 33]
+  - [34, 34]
+  - [35, 35]
+  - [36, 36]
+  - [37, 37]
+  - [38, 38]
+  - [39, 39]
+  - [40, 40]
+  - [41, 41]
+  - [42, 42]
+  - [43, 43]
+  - [44, 44]
+  - [45, 45]
+  - [46, 46]
+  - [47, 47]
+  - [48, 48]
+  - [49, 49]
+  - [50, 50]
+  - [51, 51]
+  - [52, 52]
+  - [53, 53]
+  - [54, 54]
+  - [55, 55]
+  - [56, 56]
+  - [57, 57]
+  - [58, 58]
+  - [59, 59]
+  - [60, 60]
+  - [61, 61]
+  - [62, 62]
+  - [63, 63]
+  - [64, 64]
+  - [65, 65]
+  - [66, 66]
+  - [67, 67]
+  - [68, 68]
+  - [69, 69]
+  - [70, 70]
+  - [71, 71]
+  - [72, 72]
+  - [73, 73]
+  - [74, 74]
+  - [75, 75]
+  - [76, 76]
+  - [77, 77]
+  - [78, 78]
+  - [79, 79]
+  - [80, 80]
+  - [81, 81]
+  - [82, 82]
+  - [83, 83]
+  - [84, 84]
+  - [85, 85]
+  - [86, 86]
+  - [87, 87]
+  - [88, 88]
+  - [89, 89]
+  - [90, 90]
+  - [91, 91]
+  - [92, 92]
+  - [93, 93]
+  - [94, 94]
+  - [95, 95]
+  - [96, 96]
+  - [97, 97]
+  - [98, 98]
+  - [99, 99]
+  - [100, 100]
+...
+t = {} for state, v in i:pairs({44, 44}, {iterator = 'GE'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [44, 44]
+  - [45, 45]
+  - [46, 46]
+  - [47, 47]
+  - [48, 48]
+  - [49, 49]
+  - [50, 50]
+  - [51, 51]
+  - [52, 52]
+  - [53, 53]
+  - [54, 54]
+  - [55, 55]
+  - [56, 56]
+  - [57, 57]
+  - [58, 58]
+  - [59, 59]
+  - [60, 60]
+  - [61, 61]
+  - [62, 62]
+  - [63, 63]
+  - [64, 64]
+  - [65, 65]
+  - [66, 66]
+  - [67, 67]
+  - [68, 68]
+  - [69, 69]
+  - [70, 70]
+  - [71, 71]
+  - [72, 72]
+  - [73, 73]
+  - [74, 74]
+  - [75, 75]
+  - [76, 76]
+  - [77, 77]
+  - [78, 78]
+  - [79, 79]
+  - [80, 80]
+  - [81, 81]
+  - [82, 82]
+  - [83, 83]
+  - [84, 84]
+  - [85, 85]
+  - [86, 86]
+  - [87, 87]
+  - [88, 88]
+  - [89, 89]
+  - [90, 90]
+  - [91, 91]
+  - [92, 92]
+  - [93, 93]
+  - [94, 94]
+  - [95, 95]
+  - [96, 96]
+  - [97, 97]
+  - [98, 98]
+  - [99, 99]
+  - [100, 100]
+...
+t = {} for state, v in i:pairs({44, 44}, {iterator = 'GT'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [45, 45]
+  - [46, 46]
+  - [47, 47]
+  - [48, 48]
+  - [49, 49]
+  - [50, 50]
+  - [51, 51]
+  - [52, 52]
+  - [53, 53]
+  - [54, 54]
+  - [55, 55]
+  - [56, 56]
+  - [57, 57]
+  - [58, 58]
+  - [59, 59]
+  - [60, 60]
+  - [61, 61]
+  - [62, 62]
+  - [63, 63]
+  - [64, 64]
+  - [65, 65]
+  - [66, 66]
+  - [67, 67]
+  - [68, 68]
+  - [69, 69]
+  - [70, 70]
+  - [71, 71]
+  - [72, 72]
+  - [73, 73]
+  - [74, 74]
+  - [75, 75]
+  - [76, 76]
+  - [77, 77]
+  - [78, 78]
+  - [79, 79]
+  - [80, 80]
+  - [81, 81]
+  - [82, 82]
+  - [83, 83]
+  - [84, 84]
+  - [85, 85]
+  - [86, 86]
+  - [87, 87]
+  - [88, 88]
+  - [89, 89]
+  - [90, 90]
+  - [91, 91]
+  - [92, 92]
+  - [93, 93]
+  - [94, 94]
+  - [95, 95]
+  - [96, 96]
+  - [97, 97]
+  - [98, 98]
+  - [99, 99]
+  - [100, 100]
+...
+t = {} for state, v in i:pairs({}, {iterator = 'LE'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [100, 100]
+  - [99, 99]
+  - [98, 98]
+  - [97, 97]
+  - [96, 96]
+  - [95, 95]
+  - [94, 94]
+  - [93, 93]
+  - [92, 92]
+  - [91, 91]
+  - [90, 90]
+  - [89, 89]
+  - [88, 88]
+  - [87, 87]
+  - [86, 86]
+  - [85, 85]
+  - [84, 84]
+  - [83, 83]
+  - [82, 82]
+  - [81, 81]
+  - [80, 80]
+  - [79, 79]
+  - [78, 78]
+  - [77, 77]
+  - [76, 76]
+  - [75, 75]
+  - [74, 74]
+  - [73, 73]
+  - [72, 72]
+  - [71, 71]
+  - [70, 70]
+  - [69, 69]
+  - [68, 68]
+  - [67, 67]
+  - [66, 66]
+  - [65, 65]
+  - [64, 64]
+  - [63, 63]
+  - [62, 62]
+  - [61, 61]
+  - [60, 60]
+  - [59, 59]
+  - [58, 58]
+  - [57, 57]
+  - [56, 56]
+  - [55, 55]
+  - [54, 54]
+  - [53, 53]
+  - [52, 52]
+  - [51, 51]
+  - [50, 50]
+  - [49, 49]
+  - [48, 48]
+  - [47, 47]
+  - [46, 46]
+  - [45, 45]
+  - [44, 44]
+  - [43, 43]
+  - [42, 42]
+  - [41, 41]
+  - [40, 40]
+  - [39, 39]
+  - [38, 38]
+  - [37, 37]
+  - [36, 36]
+  - [35, 35]
+  - [34, 34]
+  - [33, 33]
+  - [32, 32]
+  - [31, 31]
+  - [30, 30]
+  - [29, 29]
+  - [28, 28]
+  - [27, 27]
+  - [26, 26]
+  - [25, 25]
+  - [24, 24]
+  - [23, 23]
+  - [22, 22]
+  - [21, 21]
+  - [20, 20]
+  - [19, 19]
+  - [18, 18]
+  - [17, 17]
+  - [16, 16]
+  - [15, 15]
+  - [14, 14]
+  - [13, 13]
+  - [12, 12]
+  - [11, 11]
+  - [10, 10]
+  - [9, 9]
+  - [8, 8]
+  - [7, 7]
+  - [6, 6]
+  - [5, 5]
+  - [4, 4]
+  - [3, 3]
+  - [2, 2]
+  - [1, 1]
+...
+t = {} for state, v in i:pairs({77, 77}, {iterator = 'LE'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [77, 77]
+  - [76, 76]
+  - [75, 75]
+  - [74, 74]
+  - [73, 73]
+  - [72, 72]
+  - [71, 71]
+  - [70, 70]
+  - [69, 69]
+  - [68, 68]
+  - [67, 67]
+  - [66, 66]
+  - [65, 65]
+  - [64, 64]
+  - [63, 63]
+  - [62, 62]
+  - [61, 61]
+  - [60, 60]
+  - [59, 59]
+  - [58, 58]
+  - [57, 57]
+  - [56, 56]
+  - [55, 55]
+  - [54, 54]
+  - [53, 53]
+  - [52, 52]
+  - [51, 51]
+  - [50, 50]
+  - [49, 49]
+  - [48, 48]
+  - [47, 47]
+  - [46, 46]
+  - [45, 45]
+  - [44, 44]
+  - [43, 43]
+  - [42, 42]
+  - [41, 41]
+  - [40, 40]
+  - [39, 39]
+  - [38, 38]
+  - [37, 37]
+  - [36, 36]
+  - [35, 35]
+  - [34, 34]
+  - [33, 33]
+  - [32, 32]
+  - [31, 31]
+  - [30, 30]
+  - [29, 29]
+  - [28, 28]
+  - [27, 27]
+  - [26, 26]
+  - [25, 25]
+  - [24, 24]
+  - [23, 23]
+  - [22, 22]
+  - [21, 21]
+  - [20, 20]
+  - [19, 19]
+  - [18, 18]
+  - [17, 17]
+  - [16, 16]
+  - [15, 15]
+  - [14, 14]
+  - [13, 13]
+  - [12, 12]
+  - [11, 11]
+  - [10, 10]
+  - [9, 9]
+  - [8, 8]
+  - [7, 7]
+  - [6, 6]
+  - [5, 5]
+  - [4, 4]
+  - [3, 3]
+  - [2, 2]
+  - [1, 1]
+...
+t = {} for state, v in i:pairs({}, {iterator = 'LT'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [100, 100]
+  - [99, 99]
+  - [98, 98]
+  - [97, 97]
+  - [96, 96]
+  - [95, 95]
+  - [94, 94]
+  - [93, 93]
+  - [92, 92]
+  - [91, 91]
+  - [90, 90]
+  - [89, 89]
+  - [88, 88]
+  - [87, 87]
+  - [86, 86]
+  - [85, 85]
+  - [84, 84]
+  - [83, 83]
+  - [82, 82]
+  - [81, 81]
+  - [80, 80]
+  - [79, 79]
+  - [78, 78]
+  - [77, 77]
+  - [76, 76]
+  - [75, 75]
+  - [74, 74]
+  - [73, 73]
+  - [72, 72]
+  - [71, 71]
+  - [70, 70]
+  - [69, 69]
+  - [68, 68]
+  - [67, 67]
+  - [66, 66]
+  - [65, 65]
+  - [64, 64]
+  - [63, 63]
+  - [62, 62]
+  - [61, 61]
+  - [60, 60]
+  - [59, 59]
+  - [58, 58]
+  - [57, 57]
+  - [56, 56]
+  - [55, 55]
+  - [54, 54]
+  - [53, 53]
+  - [52, 52]
+  - [51, 51]
+  - [50, 50]
+  - [49, 49]
+  - [48, 48]
+  - [47, 47]
+  - [46, 46]
+  - [45, 45]
+  - [44, 44]
+  - [43, 43]
+  - [42, 42]
+  - [41, 41]
+  - [40, 40]
+  - [39, 39]
+  - [38, 38]
+  - [37, 37]
+  - [36, 36]
+  - [35, 35]
+  - [34, 34]
+  - [33, 33]
+  - [32, 32]
+  - [31, 31]
+  - [30, 30]
+  - [29, 29]
+  - [28, 28]
+  - [27, 27]
+  - [26, 26]
+  - [25, 25]
+  - [24, 24]
+  - [23, 23]
+  - [22, 22]
+  - [21, 21]
+  - [20, 20]
+  - [19, 19]
+  - [18, 18]
+  - [17, 17]
+  - [16, 16]
+  - [15, 15]
+  - [14, 14]
+  - [13, 13]
+  - [12, 12]
+  - [11, 11]
+  - [10, 10]
+  - [9, 9]
+  - [8, 8]
+  - [7, 7]
+  - [6, 6]
+  - [5, 5]
+  - [4, 4]
+  - [3, 3]
+  - [2, 2]
+  - [1, 1]
+...
+t = {} for state, v in i:pairs({77, 77}, {iterator = 'LT'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - [76, 76]
+  - [75, 75]
+  - [74, 74]
+  - [73, 73]
+  - [72, 72]
+  - [71, 71]
+  - [70, 70]
+  - [69, 69]
+  - [68, 68]
+  - [67, 67]
+  - [66, 66]
+  - [65, 65]
+  - [64, 64]
+  - [63, 63]
+  - [62, 62]
+  - [61, 61]
+  - [60, 60]
+  - [59, 59]
+  - [58, 58]
+  - [57, 57]
+  - [56, 56]
+  - [55, 55]
+  - [54, 54]
+  - [53, 53]
+  - [52, 52]
+  - [51, 51]
+  - [50, 50]
+  - [49, 49]
+  - [48, 48]
+  - [47, 47]
+  - [46, 46]
+  - [45, 45]
+  - [44, 44]
+  - [43, 43]
+  - [42, 42]
+  - [41, 41]
+  - [40, 40]
+  - [39, 39]
+  - [38, 38]
+  - [37, 37]
+  - [36, 36]
+  - [35, 35]
+  - [34, 34]
+  - [33, 33]
+  - [32, 32]
+  - [31, 31]
+  - [30, 30]
+  - [29, 29]
+  - [28, 28]
+  - [27, 27]
+  - [26, 26]
+  - [25, 25]
+  - [24, 24]
+  - [23, 23]
+  - [22, 22]
+  - [21, 21]
+  - [20, 20]
+  - [19, 19]
+  - [18, 18]
+  - [17, 17]
+  - [16, 16]
+  - [15, 15]
+  - [14, 14]
+  - [13, 13]
+  - [12, 12]
+  - [11, 11]
+  - [10, 10]
+  - [9, 9]
+  - [8, 8]
+  - [7, 7]
+  - [6, 6]
+  - [5, 5]
+  - [4, 4]
+  - [3, 3]
+  - [2, 2]
+  - [1, 1]
+...
+s:drop()
+---
+...
+-- iterator with tuple.new
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'string'} })
+---
+...
+for key = 1, 100 do s:replace({tostring(key)}) end
+---
+...
+t = {} for state, v in i:pairs(box.tuple.new{}, {iterator = 'ALL'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - ['1']
+  - ['10']
+  - ['100']
+  - ['11']
+  - ['12']
+  - ['13']
+  - ['14']
+  - ['15']
+  - ['16']
+  - ['17']
+  - ['18']
+  - ['19']
+  - ['2']
+  - ['20']
+  - ['21']
+  - ['22']
+  - ['23']
+  - ['24']
+  - ['25']
+  - ['26']
+  - ['27']
+  - ['28']
+  - ['29']
+  - ['3']
+  - ['30']
+  - ['31']
+  - ['32']
+  - ['33']
+  - ['34']
+  - ['35']
+  - ['36']
+  - ['37']
+  - ['38']
+  - ['39']
+  - ['4']
+  - ['40']
+  - ['41']
+  - ['42']
+  - ['43']
+  - ['44']
+  - ['45']
+  - ['46']
+  - ['47']
+  - ['48']
+  - ['49']
+  - ['5']
+  - ['50']
+  - ['51']
+  - ['52']
+  - ['53']
+  - ['54']
+  - ['55']
+  - ['56']
+  - ['57']
+  - ['58']
+  - ['59']
+  - ['6']
+  - ['60']
+  - ['61']
+  - ['62']
+  - ['63']
+  - ['64']
+  - ['65']
+  - ['66']
+  - ['67']
+  - ['68']
+  - ['69']
+  - ['7']
+  - ['70']
+  - ['71']
+  - ['72']
+  - ['73']
+  - ['74']
+  - ['75']
+  - ['76']
+  - ['77']
+  - ['78']
+  - ['79']
+  - ['8']
+  - ['80']
+  - ['81']
+  - ['82']
+  - ['83']
+  - ['84']
+  - ['85']
+  - ['86']
+  - ['87']
+  - ['88']
+  - ['89']
+  - ['9']
+  - ['90']
+  - ['91']
+  - ['92']
+  - ['93']
+  - ['94']
+  - ['95']
+  - ['96']
+  - ['97']
+  - ['98']
+  - ['99']
+...
+t = {} for state, v in i:pairs(box.tuple.new{}, {iterator = 'GE'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - ['1']
+  - ['10']
+  - ['100']
+  - ['11']
+  - ['12']
+  - ['13']
+  - ['14']
+  - ['15']
+  - ['16']
+  - ['17']
+  - ['18']
+  - ['19']
+  - ['2']
+  - ['20']
+  - ['21']
+  - ['22']
+  - ['23']
+  - ['24']
+  - ['25']
+  - ['26']
+  - ['27']
+  - ['28']
+  - ['29']
+  - ['3']
+  - ['30']
+  - ['31']
+  - ['32']
+  - ['33']
+  - ['34']
+  - ['35']
+  - ['36']
+  - ['37']
+  - ['38']
+  - ['39']
+  - ['4']
+  - ['40']
+  - ['41']
+  - ['42']
+  - ['43']
+  - ['44']
+  - ['45']
+  - ['46']
+  - ['47']
+  - ['48']
+  - ['49']
+  - ['5']
+  - ['50']
+  - ['51']
+  - ['52']
+  - ['53']
+  - ['54']
+  - ['55']
+  - ['56']
+  - ['57']
+  - ['58']
+  - ['59']
+  - ['6']
+  - ['60']
+  - ['61']
+  - ['62']
+  - ['63']
+  - ['64']
+  - ['65']
+  - ['66']
+  - ['67']
+  - ['68']
+  - ['69']
+  - ['7']
+  - ['70']
+  - ['71']
+  - ['72']
+  - ['73']
+  - ['74']
+  - ['75']
+  - ['76']
+  - ['77']
+  - ['78']
+  - ['79']
+  - ['8']
+  - ['80']
+  - ['81']
+  - ['82']
+  - ['83']
+  - ['84']
+  - ['85']
+  - ['86']
+  - ['87']
+  - ['88']
+  - ['89']
+  - ['9']
+  - ['90']
+  - ['91']
+  - ['92']
+  - ['93']
+  - ['94']
+  - ['95']
+  - ['96']
+  - ['97']
+  - ['98']
+  - ['99']
+...
+t = {} for state, v in i:pairs(box.tuple.new(tostring(44)), {iterator = 'GE'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - ['44']
+  - ['45']
+  - ['46']
+  - ['47']
+  - ['48']
+  - ['49']
+  - ['5']
+  - ['50']
+  - ['51']
+  - ['52']
+  - ['53']
+  - ['54']
+  - ['55']
+  - ['56']
+  - ['57']
+  - ['58']
+  - ['59']
+  - ['6']
+  - ['60']
+  - ['61']
+  - ['62']
+  - ['63']
+  - ['64']
+  - ['65']
+  - ['66']
+  - ['67']
+  - ['68']
+  - ['69']
+  - ['7']
+  - ['70']
+  - ['71']
+  - ['72']
+  - ['73']
+  - ['74']
+  - ['75']
+  - ['76']
+  - ['77']
+  - ['78']
+  - ['79']
+  - ['8']
+  - ['80']
+  - ['81']
+  - ['82']
+  - ['83']
+  - ['84']
+  - ['85']
+  - ['86']
+  - ['87']
+  - ['88']
+  - ['89']
+  - ['9']
+  - ['90']
+  - ['91']
+  - ['92']
+  - ['93']
+  - ['94']
+  - ['95']
+  - ['96']
+  - ['97']
+  - ['98']
+  - ['99']
+...
+t = {} for state, v in i:pairs(box.tuple.new(tostring(44)), {iterator = 'GT'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - ['45']
+  - ['46']
+  - ['47']
+  - ['48']
+  - ['49']
+  - ['5']
+  - ['50']
+  - ['51']
+  - ['52']
+  - ['53']
+  - ['54']
+  - ['55']
+  - ['56']
+  - ['57']
+  - ['58']
+  - ['59']
+  - ['6']
+  - ['60']
+  - ['61']
+  - ['62']
+  - ['63']
+  - ['64']
+  - ['65']
+  - ['66']
+  - ['67']
+  - ['68']
+  - ['69']
+  - ['7']
+  - ['70']
+  - ['71']
+  - ['72']
+  - ['73']
+  - ['74']
+  - ['75']
+  - ['76']
+  - ['77']
+  - ['78']
+  - ['79']
+  - ['8']
+  - ['80']
+  - ['81']
+  - ['82']
+  - ['83']
+  - ['84']
+  - ['85']
+  - ['86']
+  - ['87']
+  - ['88']
+  - ['89']
+  - ['9']
+  - ['90']
+  - ['91']
+  - ['92']
+  - ['93']
+  - ['94']
+  - ['95']
+  - ['96']
+  - ['97']
+  - ['98']
+  - ['99']
+...
+t = {} for state, v in i:pairs(box.tuple.new{}, {iterator = 'LE'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - ['99']
+  - ['98']
+  - ['97']
+  - ['96']
+  - ['95']
+  - ['94']
+  - ['93']
+  - ['92']
+  - ['91']
+  - ['90']
+  - ['9']
+  - ['89']
+  - ['88']
+  - ['87']
+  - ['86']
+  - ['85']
+  - ['84']
+  - ['83']
+  - ['82']
+  - ['81']
+  - ['80']
+  - ['8']
+  - ['79']
+  - ['78']
+  - ['77']
+  - ['76']
+  - ['75']
+  - ['74']
+  - ['73']
+  - ['72']
+  - ['71']
+  - ['70']
+  - ['7']
+  - ['69']
+  - ['68']
+  - ['67']
+  - ['66']
+  - ['65']
+  - ['64']
+  - ['63']
+  - ['62']
+  - ['61']
+  - ['60']
+  - ['6']
+  - ['59']
+  - ['58']
+  - ['57']
+  - ['56']
+  - ['55']
+  - ['54']
+  - ['53']
+  - ['52']
+  - ['51']
+  - ['50']
+  - ['5']
+  - ['49']
+  - ['48']
+  - ['47']
+  - ['46']
+  - ['45']
+  - ['44']
+  - ['43']
+  - ['42']
+  - ['41']
+  - ['40']
+  - ['4']
+  - ['39']
+  - ['38']
+  - ['37']
+  - ['36']
+  - ['35']
+  - ['34']
+  - ['33']
+  - ['32']
+  - ['31']
+  - ['30']
+  - ['3']
+  - ['29']
+  - ['28']
+  - ['27']
+  - ['26']
+  - ['25']
+  - ['24']
+  - ['23']
+  - ['22']
+  - ['21']
+  - ['20']
+  - ['2']
+  - ['19']
+  - ['18']
+  - ['17']
+  - ['16']
+  - ['15']
+  - ['14']
+  - ['13']
+  - ['12']
+  - ['11']
+  - ['100']
+  - ['10']
+  - ['1']
+...
+t = {} for state, v in i:pairs(box.tuple.new(tostring(77)), {iterator = 'LE'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - ['77']
+  - ['76']
+  - ['75']
+  - ['74']
+  - ['73']
+  - ['72']
+  - ['71']
+  - ['70']
+  - ['7']
+  - ['69']
+  - ['68']
+  - ['67']
+  - ['66']
+  - ['65']
+  - ['64']
+  - ['63']
+  - ['62']
+  - ['61']
+  - ['60']
+  - ['6']
+  - ['59']
+  - ['58']
+  - ['57']
+  - ['56']
+  - ['55']
+  - ['54']
+  - ['53']
+  - ['52']
+  - ['51']
+  - ['50']
+  - ['5']
+  - ['49']
+  - ['48']
+  - ['47']
+  - ['46']
+  - ['45']
+  - ['44']
+  - ['43']
+  - ['42']
+  - ['41']
+  - ['40']
+  - ['4']
+  - ['39']
+  - ['38']
+  - ['37']
+  - ['36']
+  - ['35']
+  - ['34']
+  - ['33']
+  - ['32']
+  - ['31']
+  - ['30']
+  - ['3']
+  - ['29']
+  - ['28']
+  - ['27']
+  - ['26']
+  - ['25']
+  - ['24']
+  - ['23']
+  - ['22']
+  - ['21']
+  - ['20']
+  - ['2']
+  - ['19']
+  - ['18']
+  - ['17']
+  - ['16']
+  - ['15']
+  - ['14']
+  - ['13']
+  - ['12']
+  - ['11']
+  - ['100']
+  - ['10']
+  - ['1']
+...
+t = {} for state, v in i:pairs(box.tuple.new{}, {iterator = 'LT'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - ['99']
+  - ['98']
+  - ['97']
+  - ['96']
+  - ['95']
+  - ['94']
+  - ['93']
+  - ['92']
+  - ['91']
+  - ['90']
+  - ['9']
+  - ['89']
+  - ['88']
+  - ['87']
+  - ['86']
+  - ['85']
+  - ['84']
+  - ['83']
+  - ['82']
+  - ['81']
+  - ['80']
+  - ['8']
+  - ['79']
+  - ['78']
+  - ['77']
+  - ['76']
+  - ['75']
+  - ['74']
+  - ['73']
+  - ['72']
+  - ['71']
+  - ['70']
+  - ['7']
+  - ['69']
+  - ['68']
+  - ['67']
+  - ['66']
+  - ['65']
+  - ['64']
+  - ['63']
+  - ['62']
+  - ['61']
+  - ['60']
+  - ['6']
+  - ['59']
+  - ['58']
+  - ['57']
+  - ['56']
+  - ['55']
+  - ['54']
+  - ['53']
+  - ['52']
+  - ['51']
+  - ['50']
+  - ['5']
+  - ['49']
+  - ['48']
+  - ['47']
+  - ['46']
+  - ['45']
+  - ['44']
+  - ['43']
+  - ['42']
+  - ['41']
+  - ['40']
+  - ['4']
+  - ['39']
+  - ['38']
+  - ['37']
+  - ['36']
+  - ['35']
+  - ['34']
+  - ['33']
+  - ['32']
+  - ['31']
+  - ['30']
+  - ['3']
+  - ['29']
+  - ['28']
+  - ['27']
+  - ['26']
+  - ['25']
+  - ['24']
+  - ['23']
+  - ['22']
+  - ['21']
+  - ['20']
+  - ['2']
+  - ['19']
+  - ['18']
+  - ['17']
+  - ['16']
+  - ['15']
+  - ['14']
+  - ['13']
+  - ['12']
+  - ['11']
+  - ['100']
+  - ['10']
+  - ['1']
+...
+t = {} for state, v in i:pairs(box.tuple.new(tostring(77)), {iterator = 'LT'}) do table.insert(t, v) end
+---
+...
+t
+---
+- - ['76']
+  - ['75']
+  - ['74']
+  - ['73']
+  - ['72']
+  - ['71']
+  - ['70']
+  - ['7']
+  - ['69']
+  - ['68']
+  - ['67']
+  - ['66']
+  - ['65']
+  - ['64']
+  - ['63']
+  - ['62']
+  - ['61']
+  - ['60']
+  - ['6']
+  - ['59']
+  - ['58']
+  - ['57']
+  - ['56']
+  - ['55']
+  - ['54']
+  - ['53']
+  - ['52']
+  - ['51']
+  - ['50']
+  - ['5']
+  - ['49']
+  - ['48']
+  - ['47']
+  - ['46']
+  - ['45']
+  - ['44']
+  - ['43']
+  - ['42']
+  - ['41']
+  - ['40']
+  - ['4']
+  - ['39']
+  - ['38']
+  - ['37']
+  - ['36']
+  - ['35']
+  - ['34']
+  - ['33']
+  - ['32']
+  - ['31']
+  - ['30']
+  - ['3']
+  - ['29']
+  - ['28']
+  - ['27']
+  - ['26']
+  - ['25']
+  - ['24']
+  - ['23']
+  - ['22']
+  - ['21']
+  - ['20']
+  - ['2']
+  - ['19']
+  - ['18']
+  - ['17']
+  - ['16']
+  - ['15']
+  - ['14']
+  - ['13']
+  - ['12']
+  - ['11']
+  - ['100']
+  - ['10']
+  - ['1']
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'string'} })
+---
+...
+i:pairs({}, {iterator = 666 })
+---
+- error: Illegal parameters, Invalid iterator type
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a')
+---
+...
+s:replace({1})
+---
+- [1]
+...
+s:replace({2})
+---
+- [2]
+...
+s:replace({3})
+---
+- [3]
+...
+s:replace({4})
+---
+- [4]
+...
+s:pairs(2, { iterator = 'GE' }):totable()
+---
+- - [2]
+  - [3]
+  - [4]
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a')
+---
+...
+s:auto_increment{1}
+---
+- [1, 1]
+...
+s:auto_increment{2}
+---
+- [2, 2]
+...
+s:auto_increment{3}
+---
+- [3, 3]
+...
+s:auto_increment{4}
+---
+- [4, 4]
+...
+s:auto_increment{5}
+---
+- [5, 5]
+...
+s:pairs(3, 'GE'):totable()
+---
+- - [3, 3]
+  - [4, 4]
+  - [5, 5]
+...
+i:pairs(3, 'GE'):totable()
+---
+- - [3, 3]
+  - [4, 4]
+  - [5, 5]
+...
+s:pairs(3, {iterator = 'GE' }):totable()
+---
+- - [3, 3]
+  - [4, 4]
+  - [5, 5]
+...
+i:pairs(3, {iterator = 'GE' }):totable()
+---
+- - [3, 3]
+  - [4, 4]
+  - [5, 5]
+...
+s:pairs(3, 'EQ'):totable()
+---
+- - [3, 3]
+...
+i:pairs(3, 'EQ'):totable()
+---
+- - [3, 3]
+...
+s:pairs(3, {iterator = 'EQ' }):totable()
+---
+- - [3, 3]
+...
+i:pairs(3, {iterator = 'EQ' }):totable()
+---
+- - [3, 3]
+...
+s:pairs(3, 'GT'):totable()
+---
+- - [4, 4]
+  - [5, 5]
+...
+i:pairs(3, 'GT'):totable()
+---
+- - [4, 4]
+  - [5, 5]
+...
+s:pairs(3, {iterator = 'GT' }):totable()
+---
+- - [4, 4]
+  - [5, 5]
+...
+i:pairs(3, {iterator = 'GT' }):totable()
+---
+- - [4, 4]
+  - [5, 5]
+...
+i:select({3}, 'LE')
+---
+- - [3, 3]
+  - [2, 2]
+  - [1, 1]
+...
+s:select({3}, 'LE')
+---
+- - [3, 3]
+  - [2, 2]
+  - [1, 1]
+...
+i:count({3}, 'GT')
+---
+- 2
+...
+s:count({3}, 'GT')
+---
+- 2
+...
+s:drop()
+---
+...
+-- implement lazy iterator positioning
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned', 2, 'unsigned'} })
+---
+...
+for i = 1,3 do for j = 1,3 do s:replace{i, j} end end
+---
+...
+itr1,itr2,itr3 = s:pairs{2}
+---
+...
+_ = s:replace{1, 4}
+---
+...
+r = {}
+---
+...
+for k,v in itr1,itr2,itr3 do table.insert(r, v) end
+---
+...
+r
+---
+- - [2, 1]
+  - [2, 2]
+  - [2, 3]
+...
+itr1,itr2,itr3 = s:pairs({2}, {iterator = 'GE'})
+---
+...
+_ = s:replace{1, 5}
+---
+...
+r = {}
+---
+...
+for k,v in itr1,itr2,itr3 do table.insert(r, v) end
+---
+...
+r
+---
+- - [2, 1]
+  - [2, 2]
+  - [2, 3]
+  - [3, 1]
+  - [3, 2]
+  - [3, 3]
+...
+itr1,itr2,itr3 = s:pairs({2}, {iterator = 'REQ'})
+---
+...
+s:replace{2, 4}
+---
+- [2, 4]
+...
+r = {}
+---
+...
+for k,v in itr1,itr2,itr3 do table.insert(r, v) end
+---
+...
+r
+---
+- - [2, 4]
+  - [2, 3]
+  - [2, 2]
+  - [2, 1]
+...
+r = nil
+---
+...
+s:drop()
+---
+...
+-- make tree iterators stable
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+---
+...
+for i = 1,10 do s:replace{i} end
+---
+...
+r = {}
+---
+...
+for k,v in s:pairs{} do table.insert(r, v[1]) s:delete(v[1]) end
+---
+...
+r
+---
+- - 1
+  - 2
+  - 3
+  - 4
+  - 5
+  - 6
+  - 7
+  - 8
+  - 9
+  - 10
+...
+s:select{}
+---
+- []
+...
+for i = 1,10 do s:replace{i} end
+---
+...
+r = {}
+---
+...
+for k,v in s:pairs({}, {iterator = 'REQ'}) do table.insert(r, v[1]) s:delete(v[1]) end
+---
+...
+r
+---
+- - 10
+  - 9
+  - 8
+  - 7
+  - 6
+  - 5
+  - 4
+  - 3
+  - 2
+  - 1
+...
+s:select{}
+---
+- []
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned', 2, 'unsigned'} })
+---
+...
+for i = 1,3 do for j = 1,3 do s:replace{i, j} end end
+---
+...
+r = {}
+---
+...
+for k,v in s:pairs{2} do table.insert(r, v) s:delete{v[1], v[2]} end
+---
+...
+r
+---
+- - [2, 1]
+  - [2, 2]
+  - [2, 3]
+...
+s:select{}
+---
+- - [1, 1]
+  - [1, 2]
+  - [1, 3]
+  - [3, 1]
+  - [3, 2]
+  - [3, 3]
+...
+for i = 1,3 do for j = 1,3 do s:replace{i, j} end end
+---
+...
+r = {}
+---
+...
+for k,v in s:pairs({3}, {iterator = 'REQ'}) do table.insert(r, v) s:delete{v[1], v[2]} end
+---
+...
+r
+---
+- - [3, 3]
+  - [3, 2]
+  - [3, 1]
+...
+s:select{}
+---
+- - [1, 1]
+  - [1, 2]
+  - [1, 3]
+  - [2, 1]
+  - [2, 2]
+  - [2, 3]
+...
+r = nil
+---
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+---
+...
+s:replace{10} s:replace{20} s:replace{30} s:replace{40} s:replace{50} s:replace{60}
+---
+...
+gen,param,state = i:pairs({25})
+---
+...
+s:replace{25}
+---
+- [25]
+...
+state, value = gen(param,state)
+---
+...
+value
+---
+- [25]
+...
+state, value = gen(param,state)
+---
+...
+value
+---
+- null
+...
+gen,param,state = i:pairs({35})
+---
+...
+state, value = gen(param,state)
+---
+...
+value
+---
+- null
+...
+s:replace{35}
+---
+- [35]
+...
+state, value = gen(param,state)
+---
+- error: 'builtin/box/schema.lua:1247: usage: next(state)'
+...
+value
+---
+- null
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+---
+...
+s:replace{10} s:replace{20} s:replace{30} s:replace{40} s:replace{50} s:replace{60}
+---
+...
+gen,param,state = i:pairs({30}, {iterator = 'GE'})
+---
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [30]
+...
+s:replace{0}
+---
+- [0]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [40]
+...
+s:replace{42}
+---
+- [42]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [42]
+...
+s:replace{80}
+---
+- [80]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [50]
+...
+s:replace{15}
+---
+- [15]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [60]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [80]
+...
+state, value = gen(param, state)
+---
+...
+state
+---
+- null
+...
+value
+---
+- null
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+---
+...
+s:replace{10} s:replace{20} s:replace{30} s:replace{40} s:replace{50} s:replace{60}
+---
+...
+gen,param,state = i:pairs({40}, {iterator = 'LE'})
+---
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [40]
+...
+s:replace{0}
+---
+- [0]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [30]
+...
+s:replace{15}
+---
+- [15]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [20]
+...
+s:replace{42}
+---
+- [42]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [15]
+...
+s:replace{32}
+---
+- [32]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [10]
+...
+s:replace{80}
+---
+- [80]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [0]
+...
+state, value = gen(param, state)
+---
+...
+state
+---
+- null
+...
+value
+---
+- null
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+---
+...
+s:replace{10} s:replace{20} s:replace{30} s:replace{40} s:replace{50} s:replace{60}
+---
+...
+gen,param,state = i:pairs({28}, {iterator = 'GE'})
+---
+...
+s:replace{0}
+---
+- [0]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [30]
+...
+s:replace{15}
+---
+- [15]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [40]
+...
+s:replace{42}
+---
+- [42]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [42]
+...
+s:replace{32}
+---
+- [32]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [50]
+...
+s:replace{80}
+---
+- [80]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [60]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [80]
+...
+gen(param, state)
+---
+- null
+...
+-- test iterator dummy function, invoked when it's out of bounds
+gen(param, state)
+---
+- null
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+---
+...
+s:replace{10} s:replace{20} s:replace{30} s:replace{40} s:replace{50} s:replace{60}
+---
+...
+gen,param,state = i:pairs({42}, {iterator = 'LE'})
+---
+...
+s:replace{0}
+---
+- [0]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [40]
+...
+s:replace{42}
+---
+- [42]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [30]
+...
+s:replace{15}
+---
+- [15]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [20]
+...
+s:replace{32}
+---
+- [32]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [15]
+...
+s:replace{80}
+---
+- [80]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [10]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [0]
+...
+gen(param, state)
+---
+- null
+...
+-- test iterator dummy function, invoked when it's out of bounds
+gen(param, state)
+---
+- null
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+---
+...
+s:replace{10} s:replace{20} s:replace{30} s:replace{40} s:replace{50} s:replace{60}
+---
+...
+gen,param,state = i:pairs({20}, {iterator = 'GT'})
+---
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [30]
+...
+s:replace{0}
+---
+- [0]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [40]
+...
+s:replace{42}
+---
+- [42]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [42]
+...
+s:replace{80}
+---
+- [80]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [50]
+...
+s:replace{15}
+---
+- [15]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [60]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [80]
+...
+gen(param, state)
+---
+- null
+...
+-- test iterator dummy function, invoked when it's out of bounds
+gen(param, state)
+---
+- null
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+---
+...
+s:replace{10} s:replace{20} s:replace{30} s:replace{40} s:replace{50} s:replace{60}
+---
+...
+gen,param,state = i:pairs({50}, {iterator = 'LT'})
+---
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [40]
+...
+s:replace{0}
+---
+- [0]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [30]
+...
+s:replace{15}
+---
+- [15]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [20]
+...
+s:replace{42}
+---
+- [42]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [15]
+...
+s:replace{32}
+---
+- [32]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [10]
+...
+s:replace{80}
+---
+- [80]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [0]
+...
+gen(param, state)
+---
+- null
+...
+-- test iterator dummy function, invoked when it's out of bounds
+gen(param, state)
+---
+- null
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+---
+...
+s:replace{10} s:replace{20} s:replace{30} s:replace{40} s:replace{50} s:replace{60}
+---
+...
+gen,param,state = i:pairs({28}, {iterator = 'GT'})
+---
+...
+s:replace{0}
+---
+- [0]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [30]
+...
+s:replace{15}
+---
+- [15]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [40]
+...
+s:replace{42}
+---
+- [42]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [42]
+...
+s:replace{32}
+---
+- [32]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [50]
+...
+s:replace{80}
+---
+- [80]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [60]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [80]
+...
+gen(param, state)
+---
+- null
+...
+-- test iterator dummy function, invoked when it's out of bounds
+gen(param, state)
+---
+- null
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+---
+...
+s:replace{10} s:replace{20} s:replace{30} s:replace{40} s:replace{50} s:replace{60}
+---
+...
+gen,param,state = i:pairs({42}, {iterator = 'LT'})
+---
+...
+s:replace{0}
+---
+- [0]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [40]
+...
+s:replace{42}
+---
+- [42]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [30]
+...
+s:replace{15}
+---
+- [15]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [20]
+...
+s:replace{32}
+---
+- [32]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [15]
+...
+s:replace{80}
+---
+- [80]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [10]
+...
+state, value = gen(param, state)
+---
+...
+value
+---
+- [0]
+...
+gen(param, state)
+---
+- null
+...
+-- test iterator dummy function, invoked when it's out of bounds
+gen(param, state)
+---
+- null
+...
+s:drop()
+---
+...
+-- Ephemeral space: methods: delete
+-- delete (str)
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'string'} })
+---
+...
+for key = 1, 10 do s:replace({tostring(key)}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 10 do table.insert(t, s:get({tostring(key)})) end
+---
+...
+t
+---
+- - ['1']
+  - ['2']
+  - ['3']
+  - ['4']
+  - ['5']
+  - ['6']
+  - ['7']
+  - ['8']
+  - ['9']
+  - ['10']
+...
+for key = 1, 10 do s:delete({tostring(key)}) end
+---
+...
+for key = 1, 10 do assert(s:get({tostring(key)}) == nil) end
+---
+...
+s:delete({tostring(7)})
+---
+...
+s:drop()
+---
+...
+-- delete (num)
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+---
+...
+for key = 1, 10 do s:replace({key}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 10 do table.insert(t, s:get({key})) end
+---
+...
+t
+---
+- - [1]
+  - [2]
+  - [3]
+  - [4]
+  - [5]
+  - [6]
+  - [7]
+  - [8]
+  - [9]
+  - [10]
+...
+for key = 1, 10 do s:delete({key}) end
+---
+...
+for key = 1, 10 do assert(s:get({key}) == nil) end
+---
+...
+s:delete({7})
+---
+...
+s:drop()
+---
+...
+-- delete multi-part (num, num)
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned', 2, 'unsigned'} })
+---
+...
+for key = 1, 10 do s:replace({key, key}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 10 do table.insert(t, s:get({key, key})) end
+---
+...
+t
+---
+- - [1, 1]
+  - [2, 2]
+  - [3, 3]
+  - [4, 4]
+  - [5, 5]
+  - [6, 6]
+  - [7, 7]
+  - [8, 8]
+  - [9, 9]
+  - [10, 10]
+...
+for key = 1, 10 do s:delete({key, key}) end
+---
+...
+for key = 1, 10 do assert(s:get({key, key}) == nil) end
+---
+...
+s:delete({7, 7})
+---
+...
+s:drop()
+---
+...
+-- delete (str)
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'string'} })
+---
+...
+for key = 1, 10 do s:replace({tostring(key)}) end
+---
+...
+t = {}
+---
+...
+for key = 1, 10 do table.insert(t, s:get({tostring(key)})) end
+---
+...
+t
+---
+- - ['1']
+  - ['2']
+  - ['3']
+  - ['4']
+  - ['5']
+  - ['6']
+  - ['7']
+  - ['8']
+  - ['9']
+  - ['10']
+...
+for key = 1, 10 do s:delete(box.tuple.new{tostring(key)}) end
+---
+...
+for key = 1, 10 do assert(s:get({tostring(key)}) == nil) end
+---
+...
+s:delete(box.tuple.new{tostring(7)})
+---
+...
+s:drop()
+---
+...
+-- Ephemeral space: methods: bsize, len
+utils = dofile('utils.lua')
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+idx = s:create_index('a')
+---
+...
+for i = 1, 13 do s:insert{ i, string.rep('x', i) } end
+---
+...
+s:len()
+---
+- 13
+...
+s:bsize()
+---
+- 130
+...
+utils.space_bsize(s)
+---
+- 130
+...
+for i = 1, 13, 2 do s:delete{ i } end
+---
+...
+s:len()
+---
+- 6
+...
+s:bsize()
+---
+- 60
+...
+utils.space_bsize(s)
+---
+- 60
+...
+for i = 2, 13, 2 do s:update( { i }, {{ ":", 2, i, 0, string.rep('y', i) }} ) end
+---
+...
+s:len()
+---
+- 6
+...
+s:bsize()
+---
+- 102
+...
+utils.space_bsize(s)
+---
+- 102
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'INTEGER'} })
+---
+...
+s:insert({1, "AAAA"})
+---
+- [1, 'AAAA']
+...
+s:insert({2, "AAAA"})
+---
+- [2, 'AAAA']
+...
+s:insert({3, "AAAA"})
+---
+- [3, 'AAAA']
+...
+s:insert({4, "AAAA"})
+---
+- [4, 'AAAA']
+...
+i:select()
+---
+- - [1, 'AAAA']
+  - [2, 'AAAA']
+  - [3, 'AAAA']
+  - [4, 'AAAA']
+...
+i:max(2)
+---
+- [2, 'AAAA']
+...
+i:min(2)
+---
+- [2, 'AAAA']
+...
+i:count(2)
+---
+- 1
+...
+i:max()
+---
+- [4, 'AAAA']
+...
+i:min()
+---
+- [1, 'AAAA']
+...
+i:count()
+---
+- 4
+...
+s:insert({20, "AAAA"})
+---
+- [20, 'AAAA']
+...
+s:insert({30, "AAAA"})
+---
+- [30, 'AAAA']
+...
+s:insert({40, "AAAA"})
+---
+- [40, 'AAAA']
+...
+i:select()
+---
+- - [1, 'AAAA']
+  - [2, 'AAAA']
+  - [3, 'AAAA']
+  - [4, 'AAAA']
+  - [20, 'AAAA']
+  - [30, 'AAAA']
+  - [40, 'AAAA']
+...
+i:max(15)
+---
+...
+i:min(15)
+---
+...
+i:count(15)
+---
+- 0
+...
+i:max()
+---
+- [40, 'AAAA']
+...
+i:min()
+---
+- [1, 'AAAA']
+...
+i:count()
+---
+- 7
+...
+s:insert({-2, "AAAA"})
+---
+- [-2, 'AAAA']
+...
+s:insert({-3, "AAAA"})
+---
+- [-3, 'AAAA']
+...
+s:insert({-4, "AAAA"})
+---
+- [-4, 'AAAA']
+...
+i:select()
+---
+- - [-4, 'AAAA']
+  - [-3, 'AAAA']
+  - [-2, 'AAAA']
+  - [1, 'AAAA']
+  - [2, 'AAAA']
+  - [3, 'AAAA']
+  - [4, 'AAAA']
+  - [20, 'AAAA']
+  - [30, 'AAAA']
+  - [40, 'AAAA']
+...
+i:max(0)
+---
+...
+i:min(0)
+---
+...
+i:count(0)
+---
+- 0
+...
+i:max()
+---
+- [40, 'AAAA']
+...
+i:min()
+---
+- [-4, 'AAAA']
+...
+i:count()
+---
+- 10
+...
+s:drop()
+---
+...
+-- number type
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'number'} })
+---
+...
+s:insert({1, "AAAA"})
+---
+- [1, 'AAAA']
+...
+s:insert({2, "AAAA"})
+---
+- [2, 'AAAA']
+...
+s:insert({3, "AAAA"})
+---
+- [3, 'AAAA']
+...
+s:insert({4, "AAAA"})
+---
+- [4, 'AAAA']
+...
+i:select()
+---
+- - [1, 'AAAA']
+  - [2, 'AAAA']
+  - [3, 'AAAA']
+  - [4, 'AAAA']
+...
+i:max(2)
+---
+- [2, 'AAAA']
+...
+i:min(2)
+---
+- [2, 'AAAA']
+...
+i:count(2)
+---
+- 1
+...
+i:max()
+---
+- [4, 'AAAA']
+...
+i:min()
+---
+- [1, 'AAAA']
+...
+i:count()
+---
+- 4
+...
+s:insert({20, "AAAA"})
+---
+- [20, 'AAAA']
+...
+s:insert({30, "AAAA"})
+---
+- [30, 'AAAA']
+...
+s:insert({40, "AAAA"})
+---
+- [40, 'AAAA']
+...
+i:select()
+---
+- - [1, 'AAAA']
+  - [2, 'AAAA']
+  - [3, 'AAAA']
+  - [4, 'AAAA']
+  - [20, 'AAAA']
+  - [30, 'AAAA']
+  - [40, 'AAAA']
+...
+i:max(15)
+---
+...
+i:min(15)
+---
+...
+i:count(15)
+---
+- 0
+...
+i:max()
+---
+- [40, 'AAAA']
+...
+i:min()
+---
+- [1, 'AAAA']
+...
+i:count()
+---
+- 7
+...
+s:insert({-2, "AAAA"})
+---
+- [-2, 'AAAA']
+...
+s:insert({-3, "AAAA"})
+---
+- [-3, 'AAAA']
+...
+s:insert({-4, "AAAA"})
+---
+- [-4, 'AAAA']
+...
+i:select()
+---
+- - [-4, 'AAAA']
+  - [-3, 'AAAA']
+  - [-2, 'AAAA']
+  - [1, 'AAAA']
+  - [2, 'AAAA']
+  - [3, 'AAAA']
+  - [4, 'AAAA']
+  - [20, 'AAAA']
+  - [30, 'AAAA']
+  - [40, 'AAAA']
+...
+i:max(0)
+---
+...
+i:min(0)
+---
+...
+i:count(0)
+---
+- 0
+...
+i:max()
+---
+- [40, 'AAAA']
+...
+i:min()
+---
+- [-4, 'AAAA']
+...
+i:count()
+---
+- 10
+...
+s:insert({1.5, "AAAA"})
+---
+- [1.5, 'AAAA']
+...
+s:insert({2.5, "AAAA"})
+---
+- [2.5, 'AAAA']
+...
+s:insert({3.5, "AAAA"})
+---
+- [3.5, 'AAAA']
+...
+s:insert({4.5, "AAAA"})
+---
+- [4.5, 'AAAA']
+...
+i:select()
+---
+- - [-4, 'AAAA']
+  - [-3, 'AAAA']
+  - [-2, 'AAAA']
+  - [1, 'AAAA']
+  - [1.5, 'AAAA']
+  - [2, 'AAAA']
+  - [2.5, 'AAAA']
+  - [3, 'AAAA']
+  - [3.5, 'AAAA']
+  - [4, 'AAAA']
+  - [4.5, 'AAAA']
+  - [20, 'AAAA']
+  - [30, 'AAAA']
+  - [40, 'AAAA']
+...
+i:max(1)
+---
+- [1, 'AAAA']
+...
+i:min(1)
+---
+- [1, 'AAAA']
+...
+i:count(1)
+---
+- 1
+...
+i:max()
+---
+- [40, 'AAAA']
+...
+i:min()
+---
+- [-4, 'AAAA']
+...
+i:count()
+---
+- 14
+...
+s:drop()
+---
+...
+-- str type
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'string'} })
+---
+...
+s:insert({'1', "AAAA"})
+---
+- ['1', 'AAAA']
+...
+s:insert({'2', "AAAA"})
+---
+- ['2', 'AAAA']
+...
+s:insert({'3', "AAAA"})
+---
+- ['3', 'AAAA']
+...
+s:insert({'4', "AAAA"})
+---
+- ['4', 'AAAA']
+...
+i:select()
+---
+- - ['1', 'AAAA']
+  - ['2', 'AAAA']
+  - ['3', 'AAAA']
+  - ['4', 'AAAA']
+...
+i:max('2')
+---
+- ['2', 'AAAA']
+...
+i:min('2')
+---
+- ['2', 'AAAA']
+...
+i:count('2')
+---
+- 1
+...
+i:max()
+---
+- ['4', 'AAAA']
+...
+i:min()
+---
+- ['1', 'AAAA']
+...
+i:count()
+---
+- 4
+...
+s:insert({'20', "AAAA"})
+---
+- ['20', 'AAAA']
+...
+s:insert({'30', "AAAA"})
+---
+- ['30', 'AAAA']
+...
+s:insert({'40', "AAAA"})
+---
+- ['40', 'AAAA']
+...
+i:select()
+---
+- - ['1', 'AAAA']
+  - ['2', 'AAAA']
+  - ['20', 'AAAA']
+  - ['3', 'AAAA']
+  - ['30', 'AAAA']
+  - ['4', 'AAAA']
+  - ['40', 'AAAA']
+...
+i:max('15')
+---
+...
+i:min('15')
+---
+...
+i:count('15')
+---
+- 0
+...
+i:max()
+---
+- ['40', 'AAAA']
+...
+i:min()
+---
+- ['1', 'AAAA']
+...
+i:count()
+---
+- 7
+...
+s:insert({'-2', "AAAA"})
+---
+- ['-2', 'AAAA']
+...
+s:insert({'-3', "AAAA"})
+---
+- ['-3', 'AAAA']
+...
+s:insert({'-4', "AAAA"})
+---
+- ['-4', 'AAAA']
+...
+i:select()
+---
+- - ['-2', 'AAAA']
+  - ['-3', 'AAAA']
+  - ['-4', 'AAAA']
+  - ['1', 'AAAA']
+  - ['2', 'AAAA']
+  - ['20', 'AAAA']
+  - ['3', 'AAAA']
+  - ['30', 'AAAA']
+  - ['4', 'AAAA']
+  - ['40', 'AAAA']
+...
+i:max('0')
+---
+...
+i:min('0')
+---
+...
+i:count('0')
+---
+- 0
+...
+i:max()
+---
+- ['40', 'AAAA']
+...
+i:min()
+---
+- ['-2', 'AAAA']
+...
+i:count()
+---
+- 10
+...
+s:drop()
+---
+...
+-- num type
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+---
+...
+s:insert({1, "AAAA"})
+---
+- [1, 'AAAA']
+...
+s:insert({2, "AAAA"})
+---
+- [2, 'AAAA']
+...
+s:insert({3, "AAAA"})
+---
+- [3, 'AAAA']
+...
+s:insert({4, "AAAA"})
+---
+- [4, 'AAAA']
+...
+i:select()
+---
+- - [1, 'AAAA']
+  - [2, 'AAAA']
+  - [3, 'AAAA']
+  - [4, 'AAAA']
+...
+i:max(2)
+---
+- [2, 'AAAA']
+...
+i:min(2)
+---
+- [2, 'AAAA']
+...
+i:count(2)
+---
+- 1
+...
+i:max()
+---
+- [4, 'AAAA']
+...
+i:min()
+---
+- [1, 'AAAA']
+...
+i:count()
+---
+- 4
+...
+s:insert({20, "AAAA"})
+---
+- [20, 'AAAA']
+...
+s:insert({30, "AAAA"})
+---
+- [30, 'AAAA']
+...
+s:insert({40, "AAAA"})
+---
+- [40, 'AAAA']
+...
+i:select()
+---
+- - [1, 'AAAA']
+  - [2, 'AAAA']
+  - [3, 'AAAA']
+  - [4, 'AAAA']
+  - [20, 'AAAA']
+  - [30, 'AAAA']
+  - [40, 'AAAA']
+...
+i:max(15)
+---
+...
+i:min(15)
+---
+...
+i:count(15)
+---
+- 0
+...
+i:max()
+---
+- [40, 'AAAA']
+...
+i:min()
+---
+- [1, 'AAAA']
+...
+i:count()
+---
+- 7
+...
+s:drop()
+---
+...
+-- scalar type
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'scalar'} })
+---
+...
+s:insert({1, "AAAA"})
+---
+- [1, 'AAAA']
+...
+s:insert({2, "AAAA"})
+---
+- [2, 'AAAA']
+...
+s:insert({3, "AAAA"})
+---
+- [3, 'AAAA']
+...
+s:insert({4, "AAAA"})
+---
+- [4, 'AAAA']
+...
+i:select()
+---
+- - [1, 'AAAA']
+  - [2, 'AAAA']
+  - [3, 'AAAA']
+  - [4, 'AAAA']
+...
+i:max(2)
+---
+- [2, 'AAAA']
+...
+i:min(2)
+---
+- [2, 'AAAA']
+...
+i:count(2)
+---
+- 1
+...
+i:max()
+---
+- [4, 'AAAA']
+...
+i:min()
+---
+- [1, 'AAAA']
+...
+i:count()
+---
+- 4
+...
+s:insert({20, "AAAA"})
+---
+- [20, 'AAAA']
+...
+s:insert({30, "AAAA"})
+---
+- [30, 'AAAA']
+...
+s:insert({40, "AAAA"})
+---
+- [40, 'AAAA']
+...
+i:select()
+---
+- - [1, 'AAAA']
+  - [2, 'AAAA']
+  - [3, 'AAAA']
+  - [4, 'AAAA']
+  - [20, 'AAAA']
+  - [30, 'AAAA']
+  - [40, 'AAAA']
+...
+i:max(15)
+---
+...
+i:min(15)
+---
+...
+i:count(15)
+---
+- 0
+...
+i:max()
+---
+- [40, 'AAAA']
+...
+i:min()
+---
+- [1, 'AAAA']
+...
+i:count()
+---
+- 7
+...
+s:insert({'1', "AAAA"})
+---
+- ['1', 'AAAA']
+...
+s:insert({'2', "AAAA"})
+---
+- ['2', 'AAAA']
+...
+s:insert({'3', "AAAA"})
+---
+- ['3', 'AAAA']
+...
+s:insert({'4', "AAAA"})
+---
+- ['4', 'AAAA']
+...
+i:select()
+---
+- - [1, 'AAAA']
+  - [2, 'AAAA']
+  - [3, 'AAAA']
+  - [4, 'AAAA']
+  - [20, 'AAAA']
+  - [30, 'AAAA']
+  - [40, 'AAAA']
+  - ['1', 'AAAA']
+  - ['2', 'AAAA']
+  - ['3', 'AAAA']
+  - ['4', 'AAAA']
+...
+i:max('2')
+---
+- ['2', 'AAAA']
+...
+i:min('2')
+---
+- ['2', 'AAAA']
+...
+i:count('2')
+---
+- 1
+...
+i:max()
+---
+- ['4', 'AAAA']
+...
+i:min()
+---
+- [1, 'AAAA']
+...
+i:count()
+---
+- 11
+...
+s:insert({'20', "AAAA"})
+---
+- ['20', 'AAAA']
+...
+s:insert({'30', "AAAA"})
+---
+- ['30', 'AAAA']
+...
+s:insert({'40', "AAAA"})
+---
+- ['40', 'AAAA']
+...
+i:select()
+---
+- - [1, 'AAAA']
+  - [2, 'AAAA']
+  - [3, 'AAAA']
+  - [4, 'AAAA']
+  - [20, 'AAAA']
+  - [30, 'AAAA']
+  - [40, 'AAAA']
+  - ['1', 'AAAA']
+  - ['2', 'AAAA']
+  - ['20', 'AAAA']
+  - ['3', 'AAAA']
+  - ['30', 'AAAA']
+  - ['4', 'AAAA']
+  - ['40', 'AAAA']
+...
+i:max('15')
+---
+...
+i:min('15')
+---
+...
+i:count('15')
+---
+- 0
+...
+i:max()
+---
+- ['40', 'AAAA']
+...
+i:min()
+---
+- [1, 'AAAA']
+...
+i:count()
+---
+- 14
+...
+s:insert({'-2', "AAAA"})
+---
+- ['-2', 'AAAA']
+...
+s:insert({'-3', "AAAA"})
+---
+- ['-3', 'AAAA']
+...
+s:insert({'-4', "AAAA"})
+---
+- ['-4', 'AAAA']
+...
+i:select()
+---
+- - [1, 'AAAA']
+  - [2, 'AAAA']
+  - [3, 'AAAA']
+  - [4, 'AAAA']
+  - [20, 'AAAA']
+  - [30, 'AAAA']
+  - [40, 'AAAA']
+  - ['-2', 'AAAA']
+  - ['-3', 'AAAA']
+  - ['-4', 'AAAA']
+  - ['1', 'AAAA']
+  - ['2', 'AAAA']
+  - ['20', 'AAAA']
+  - ['3', 'AAAA']
+  - ['30', 'AAAA']
+  - ['4', 'AAAA']
+  - ['40', 'AAAA']
+...
+i:max('0')
+---
+...
+i:min('0')
+---
+...
+i:count('0')
+---
+- 0
+...
+i:max()
+---
+- ['40', 'AAAA']
+...
+i:min()
+---
+- [1, 'AAAA']
+...
+i:count()
+---
+- 17
+...
+s:insert({-2, "AAAA"})
+---
+- [-2, 'AAAA']
+...
+s:insert({-3, "AAAA"})
+---
+- [-3, 'AAAA']
+...
+s:insert({-4, "AAAA"})
+---
+- [-4, 'AAAA']
+...
+i:select()
+---
+- - [-4, 'AAAA']
+  - [-3, 'AAAA']
+  - [-2, 'AAAA']
+  - [1, 'AAAA']
+  - [2, 'AAAA']
+  - [3, 'AAAA']
+  - [4, 'AAAA']
+  - [20, 'AAAA']
+  - [30, 'AAAA']
+  - [40, 'AAAA']
+  - ['-2', 'AAAA']
+  - ['-3', 'AAAA']
+  - ['-4', 'AAAA']
+  - ['1', 'AAAA']
+  - ['2', 'AAAA']
+  - ['20', 'AAAA']
+  - ['3', 'AAAA']
+  - ['30', 'AAAA']
+  - ['4', 'AAAA']
+  - ['40', 'AAAA']
+...
+i:max(0)
+---
+...
+i:min(0)
+---
+...
+i:count(0)
+---
+- 0
+...
+i:max()
+---
+- ['40', 'AAAA']
+...
+i:min()
+---
+- [-4, 'AAAA']
+...
+i:count()
+---
+- 20
+...
+s:drop()
+---
+...
+-- multi filed indices
+-- scalar int
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'scalar', 2, 'INTEGER'} })
+---
+...
+s:insert({1, 1})
+---
+- [1, 1]
+...
+s:insert({1, 2})
+---
+- [1, 2]
+...
+s:insert({1, 3})
+---
+- [1, 3]
+...
+s:insert({1, -4})
+---
+- [1, -4]
+...
+i:select()
+---
+- - [1, -4]
+  - [1, 1]
+  - [1, 2]
+  - [1, 3]
+...
+i:max({1})
+---
+- [1, 3]
+...
+i:min({1})
+---
+- [1, -4]
+...
+i:count({1})
+---
+- 4
+...
+i:max({1, 0})
+---
+...
+i:min({1, 1})
+---
+- [1, 1]
+...
+i:count({1})
+---
+- 4
+...
+i:max()
+---
+- [1, 3]
+...
+i:min()
+---
+- [1, -4]
+...
+i:count()
+---
+- 4
+...
+s:insert({2, 1})
+---
+- [2, 1]
+...
+s:insert({2, 2})
+---
+- [2, 2]
+...
+s:insert({2, 3})
+---
+- [2, 3]
+...
+s:insert({2, -4})
+---
+- [2, -4]
+...
+i:select()
+---
+- - [1, -4]
+  - [1, 1]
+  - [1, 2]
+  - [1, 3]
+  - [2, -4]
+  - [2, 1]
+  - [2, 2]
+  - [2, 3]
+...
+i:max({2})
+---
+- [2, 3]
+...
+i:min({2})
+---
+- [2, -4]
+...
+i:count({2})
+---
+- 4
+...
+i:max({2, 0})
+---
+...
+i:min({2, 1})
+---
+- [2, 1]
+...
+i:count({2})
+---
+- 4
+...
+i:max()
+---
+- [2, 3]
+...
+i:min()
+---
+- [1, -4]
+...
+i:count()
+---
+- 8
+...
+s:drop()
+---
+...
+-- scalar str
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'scalar', 2, 'string'} })
+---
+...
+s:insert({1, '1'})
+---
+- [1, '1']
+...
+s:insert({1, '2'})
+---
+- [1, '2']
+...
+s:insert({1, '3'})
+---
+- [1, '3']
+...
+s:insert({1, '-4'})
+---
+- [1, '-4']
+...
+i:select()
+---
+- - [1, '-4']
+  - [1, '1']
+  - [1, '2']
+  - [1, '3']
+...
+i:max({1})
+---
+- [1, '3']
+...
+i:min({1})
+---
+- [1, '-4']
+...
+i:count({1})
+---
+- 4
+...
+i:max({1, '0'})
+---
+...
+i:min({1, '1'})
+---
+- [1, '1']
+...
+i:count({1})
+---
+- 4
+...
+i:max()
+---
+- [1, '3']
+...
+i:min()
+---
+- [1, '-4']
+...
+i:count()
+---
+- 4
+...
+s:insert({2, '1'})
+---
+- [2, '1']
+...
+s:insert({2, '2'})
+---
+- [2, '2']
+...
+s:insert({2, '3'})
+---
+- [2, '3']
+...
+s:insert({2, '-4'})
+---
+- [2, '-4']
+...
+i:select()
+---
+- - [1, '-4']
+  - [1, '1']
+  - [1, '2']
+  - [1, '3']
+  - [2, '-4']
+  - [2, '1']
+  - [2, '2']
+  - [2, '3']
+...
+i:max({2})
+---
+- [2, '3']
+...
+i:min({2})
+---
+- [2, '-4']
+...
+i:count({2})
+---
+- 4
+...
+i:max({2, '0'})
+---
+...
+i:min({2, '1'})
+---
+- [2, '1']
+...
+i:count({2})
+---
+- 4
+...
+i:max()
+---
+- [2, '3']
+...
+i:min()
+---
+- [1, '-4']
+...
+i:count()
+---
+- 8
+...
+s:drop()
+---
+...
+-- min max count after many inserts
+string = require('string')
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'scalar'} })
+---
+...
+long_string = string.rep('A', 650)
+---
+...
+for i = 1, 1000 do s:insert({i, long_string}) end
+---
+...
+i:max({100})
+---
+- [100, 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA']
+...
+i:max({700})
+---
+- [700, 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA']
+...
+i:min({100})
+---
+- [100, 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA']
+...
+i:min({700})
+---
+- [700, 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA']
+...
+i:count({2})
+---
+- 1
+...
+i:max()
+---
+- [1000, 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA']
+...
+i:min()
+---
+- [1, 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA']
+...
+i:count()
+---
+- 1000
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral()
+---
+...
+i = s:create_index('a', { type = 'tree', parts = {1, 'scalar', 2, 'INTEGER'} })
+---
+...
+for i = 1, 1000 do s:insert({i % 10, i, long_string}) end
+---
+...
+i:max({1, 100})
+---
+...
+i:max({2, 700})
+---
+...
+i:max({3})
+---
+- [3, 993, 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA']
+...
+i:min({1, 10})
+---
+...
+i:min({1, 700})
+---
+...
+i:min({3})
+---
+- [3, 3, 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA']
+...
+i:count({2})
+---
+- 100
+...
+i:max()
+---
+- [9, 999, 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA']
+...
+i:min()
+---
+- [0, 10, 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA']
+...
+i:count()
+---
+- 1000
+...
+s:drop()
+---
+...
+test_run:cmd("restart server default")
diff --git a/test/box/ephemeral_space.test.lua b/test/box/ephemeral_space.test.lua
index 211014c..ee03810 100644
--- a/test/box/ephemeral_space.test.lua
+++ b/test/box/ephemeral_space.test.lua
@@ -1,3 +1,4 @@
+test_run = require('test_run').new()
 -- Ephemeral space: creation and dropping.
 
 -- Simple creation.
@@ -168,3 +169,1696 @@ s:frommap({ddd = 1, aaa = 2, ccc = 3, bbb = 4}, {table = false})
 s:frommap({ddd = 1, aaa = 2, ccc = 3, bbb = box.NULL})
 s:frommap({ddd = 1, aaa = 2, ccc = 3, bbb = 4}, {dummy = true})
 s:drop()
+
+
+-- Ephemeral space: methods: insert
+s = box.schema.space.create_ephemeral({field_count = 3})
+i = s:create_index('a')
+s:insert{1}
+s:insert{2,2}
+s:insert{3,3,3}
+s:insert{4,4,4,4}
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'string'} })
+for key = 1, 10 do s:insert({tostring(key)}) end
+t = {}
+for key = 1, 10 do table.insert(t, s:get({tostring(key)})) end
+t
+s:insert({tostring(7)})
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+for key = 1, 10 do s:insert({key}) end
+t = {}
+for key = 1, 10 do table.insert(t, s:get({key})) end
+t
+s:insert({7})
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned', 2, 'unsigned'} })
+for key = 1, 10 do s:insert({key, key}) end
+t = {}
+for key = 1, 10 do table.insert(t, s:get({key, key})) end
+t
+s:insert({7, 7})
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'string'} })
+for key = 1, 10 do s:insert({tostring(key)}) end
+t = {}
+for key = 1, 10 do table.insert(t, s:get({tostring(key)})) end
+t
+s:insert(box.tuple.new{tostring(7)})
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+index = s:create_index('a')
+s:insert(1)
+s:insert(1, 2)
+s:insert(1, 2, 3)
+s:insert{1}
+s:insert{2, 3}
+tmp = s:delete(1, 2, 3)
+s:select{}
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {3, 'unsigned'} })
+s:insert{1}
+s:insert{2, 3}
+s:insert{4, 5, 6}
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+s:insert{"1"}
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'string'} })
+s:insert{1}
+s:drop()
+
+
+-- Ephemeral space: methods: replace
+s = box.schema.space.create_ephemeral({field_count = 3})
+i = s:create_index('a')
+s:replace{1}
+s:replace{2, 2}
+s:replace{3, 3, 3}
+s:replace{4, 4, 4, 4}
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'string'} })
+for key = 1, 10 do s:replace({tostring(key)}) end
+t = {}
+for key = 1, 10 do table.insert(t, s:get({tostring(key)})) end
+t
+_ = s:replace({tostring(7)})
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+for key = 1, 10 do s:replace({key}) end
+t = {}
+for key = 1, 10 do table.insert(t, s:get({key})) end
+t
+_ = s:replace({7})
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned', 2, 'unsigned'} })
+for key = 1, 10 do s:replace({key, key}) end
+t = {}
+for key = 1, 10 do table.insert(t, s:get({key, key})) end
+t
+s:replace({7, 7})
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'string'} })
+for key = 1, 10 do s:replace({tostring(key)}) end
+t = {}
+for key = 1, 10 do table.insert(t, s:get({tostring(key)})) end
+t
+s:replace(box.tuple.new{tostring(7)})
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+index = s:create_index('a')
+s:replace(1)
+s:replace(1, 2)
+s:replace(1, 2, 3)
+s:replace{1}
+s:replace{2, 3}
+tmp = s:delete(1, 2, 3)
+s:select{}
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {3, 'unsigned'} })
+s:replace{1}
+s:replace{2, 3}
+s:replace{4, 5, 6}
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+s:replace{"1"}
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'string'} })
+s:replace{1}
+s:drop()
+
+
+-- Ephemeral space: methods: upsert
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+s:upsert({1, 0}, {{'+', 2, 1}})
+s:get{1}
+s:upsert({1, 0}, {{'+', 2, 1}})
+s:get{1}
+s:upsert({1, 0}, {{'+', 1, 1}})
+s:get{1}
+s:get{2}
+
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'string'} })
+for key = 1, 10 do s:upsert({tostring(key), 0}, {{'+', 2, 1}}) end
+t = {}
+for key = 1, 10 do table.insert(t, s:get({tostring(key)})) end
+t
+for key = 1, 10 do s:upsert({tostring(key), 0}, {{'+', 2, 10}}) end
+t = {}
+for key = 1, 10 do table.insert(t, s:get({tostring(key)})) end
+t
+for key = 1, 10 do s:delete({tostring(key)}) end
+for key = 1, 10 do s:upsert({tostring(key), 0}, {{'+', 2, 1}, {'=', 3, key}}) end
+t = {}
+for key = 1, 10 do table.insert(t, s:get({tostring(key)})) end
+t
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+for key = 1, 10 do s:upsert({key, 0}, {{'+', 2, 1}}) end
+t = {}
+for key = 1, 10 do table.insert(t, s:get({key})) end
+t
+for key = 1, 10 do s:upsert({key, 0}, {{'+', 2, 10}}) end
+t = {}
+for key = 1, 10 do table.insert(t, s:get({key})) end
+t
+for key = 1, 10 do s:delete({key}) end
+for key = 1, 10 do s:upsert({key, 0}, {{'+', 2, 1}, {'=', 3, key}}) end
+t = {}
+for key = 1, 10 do table.insert(t, s:get({key})) end
+t
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned', 2, 'unsigned'} })
+for key = 1, 10 do s:upsert({key, key, 0}, {{'+', 3, 1}}) end
+t = {}
+for key = 1, 10 do table.insert(t, s:get({key, key})) end
+t
+for key = 1, 10 do s:upsert({key, key, 0}, {{'+', 3, 10}}) end
+t = {}
+for key = 1, 10 do table.insert(t, s:get({key, key})) end
+t
+for key = 1, 10 do s:delete({key, key}) end
+for key = 1, 10 do s:upsert({key, key, 0}, {{'+', 3, 1}, {'=', 4, key}}) end
+t = {}
+for key = 1, 10 do table.insert(t, s:get({key, key})) end
+t
+s:drop()
+
+test_run:cmd("setopt delimiter ';'");
+function less(a, b)
+    if type(a[2]) ~= type(b[2]) then
+        return type(a[2]) < type(b[2])
+    end
+    if a[2] == b[2] then
+        return a[1] < b[1]
+    end
+    if type(a[2]) == 'boolean' then
+        return a[2] == false and b[2] == true
+    end
+    return a[2] < b[2]
+end;
+test_run:cmd("setopt delimiter ''");
+function sort(t) table.sort(t, less) return t end
+
+-- upsert default tuple constraint
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned', 2, 'unsigned'} })
+s:upsert({0, 'key', 0}, {{'+', 3, 1}})
+s:drop()
+
+-- upsert primary key modify (skipped)
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+s:upsert({0, 0}, {{'+', 1, 1}, {'+', 2, 1}})
+s:get({0})
+s:drop()
+
+-- upsert with box.tuple.new
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+s:upsert({0, 0}, {{'+', 1, 1}, {'+', 2, 1}})
+s:get({0})
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned', 2, 'unsigned'} })
+for key = 1, 10 do s:upsert(box.tuple.new{key, key, 0}, box.tuple.new{{'+', 3, 1}}) end
+t = {}
+for key = 1, 10 do table.insert(t, s:get({key, key})) end
+t
+for key = 1, 10 do s:upsert(box.tuple.new{key, key, 0}, box.tuple.new{{'+', 3, 10}}) end
+t = {}
+for key = 1, 10 do table.insert(t, s:get({key, key})) end
+t
+for key = 1, 10 do s:delete({key, key}) end
+for key = 1, 10 do s:upsert(box.tuple.new{key, key, 0}, box.tuple.new{{'+', 3, 1}, {'=', 4, key}}) end
+t = {}
+for key = 1, 10 do table.insert(t, s:get({key, key})) end
+t
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a')
+s:upsert({0, 0}, {{'+', 2, 2}})
+s:select{0}
+tmp = s:delete{0}
+s:upsert({0, 0, 0}, {{'+', 2, 2}})
+s:select{0}
+tmp = s:delete{0}
+s:upsert({0}, {{'+', 2, 2}})
+s:select{0}
+s:replace{0, 1, 2, 4}
+s:upsert({0, 0, "you will not see it"}, {{'+', 2, 2}})
+s:select{0}
+s:replace{0, -0x4000000000000000ll}
+s:upsert({0}, {{'+', 2, -0x4000000000000001ll}})  -- overflow
+s:select{0}
+s:replace{0, "thing"}
+s:upsert({0, "nothing"}, {{'+', 2, 2}})
+s:select{0}
+tmp = s:delete{0}
+s:upsert({0, "thing"}, {{'+', 2, 2}})
+s:select{0}
+s:replace{0, 1, 2}
+s:upsert({0}, {{'!', 42, 42}})
+s:select{0}
+s:upsert({0}, {{'#', 42, 42}})
+s:select{0}
+s:upsert({0}, {{'=', 42, 42}})
+s:select{}
+s:replace{0, 1.5}
+s:select{}
+s:upsert({0}, {{'|', 1, 255}})
+s:select{0}
+s:replace{0, 1.5}
+s:replace{0, 'something to splice'}
+s:upsert({0}, {{':', 2, 1, 4, 'no'}})
+s:select{0}
+s:upsert({0}, {{':', 2, 1, 2, 'every'}})
+s:select{0}
+s:upsert({0}, {{':', 2, -100, 2, 'every'}})
+s:select{0}
+s:drop()
+
+s = box.schema.space.create_ephemeral({ field_count = 1 })
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+s:insert({1})
+s:select{}
+s:upsert({2, 2}, {{'+', 2, 1}})
+s:select{}
+s:drop()
+
+s = box.schema.space.create_ephemeral({ field_count = 2 })
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+s:insert({1, 1})
+s:select{}
+s:upsert({2, 2, 2}, {{'+', 3, 1}})
+s:upsert({3, 3}, {{'+', 2, 1}})
+s:select{}
+s:drop()
+
+test_run:cmd("setopt delimiter ';'")
+function anything_to_string(tab)
+    if tab == nil then
+        return 'nil'
+    end
+    local str = '['
+    local first_route = true
+    local t = 0
+    for k,f in pairs(tab) do
+        if not first_route then str = str .. ',' end
+        first_route = false
+        t = t + 1
+        if k ~= t then
+            str = str .. k .. '='
+        end
+        if type(f) == 'string' then
+            str = str .. "'" .. f .. "'"
+        elseif type (f) == 'number' then
+            str = str .. tostring(f)
+        elseif type (f) == 'table' or type (f) == 'cdata' then
+            str = str .. anything_to_string(f)
+        else
+            str = str .. '?'
+        end
+    end
+    str = str .. ']'
+    return str
+end;
+
+function things_equal(var1, var2)
+    local type1 = type(var1) == 'cdata' and 'table' or type(var1)
+    local type2 = type(var2) == 'cdata' and 'table' or type(var2)
+    if type1 ~= type2 then
+        return false
+    end
+    if type1 ~= 'table' then
+        return var1 == var2
+    end
+    for k,v in pairs(var1) do
+        if not things_equal(v, var2[k]) then
+            return false
+        end
+    end
+    for k,v in pairs(var2) do
+        if not things_equal(v, var1[k]) then
+            return false
+        end
+    end
+    return true
+end;
+
+function copy_thing(t)
+    if type(t) ~= 'table' then
+        return t
+    end
+    local res = {}
+    for k,v in pairs(t) do
+        res[copy_thing(k)] = copy_thing(v)
+    end
+    return res
+end;
+
+function test(space, key_tuple, ops, expect)
+    space:upsert(key_tuple, ops)
+    if (things_equal(space:select{}, expect)) then
+        return 'upsert('.. anything_to_string(key_tuple) .. ', ' ..
+                anything_to_string(ops) .. ', '  ..
+                ') OK ' .. anything_to_string(space:select{})
+    end
+    return 'upsert('.. anything_to_string(key_tuple) .. ', ' ..
+            anything_to_string(ops) .. ', ' ..
+            ') FAILED, got ' .. anything_to_string(space:select{}) ..
+            ' expected ' .. anything_to_string(expect)
+end;
+test_run:cmd("setopt delimiter ''");
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'string'} })
+s:upsert({1}, {{'!', 2, 100}}) -- must fail on checking tuple
+s:upsert({'a'}, {{'a', 2, 100}}) -- must fail on checking ops
+s:upsert({'a'}, {{'!', 2, 'ups1'}}) -- 'fast' upsert via insert in one index
+s:upsert({'a', 'b'}, {{'!', 2, 'ups2'}}) -- 'fast' upsert via update in one index
+s:select{}
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+s:insert({1, 1, 1})
+s:insert({2, 2, 2})
+s:insert({3, 3, 3})
+s:select{}
+s:upsert({2, 18, 76}, {})
+s:upsert({4, 4, 4}, {})
+s:select{}
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a')
+
+t = {1, '1', 1, 'qwerty'}
+s:insert(t)
+
+-- all good operations, one op, equivalent to update
+test(s, t, {{'+', 3, 5}}, {{1, '1', 6, 'qwerty'}})
+test(s, t, {{'-', 3, 3}}, {{1, '1', 3, 'qwerty'}})
+test(s, t, {{'&', 3, 5}}, {{1, '1', 1, 'qwerty'}})
+test(s, t, {{'|', 3, 8}}, {{1, '1', 9, 'qwerty'}})
+test(s, t, {{'^', 3, 12}}, {{1, '1', 5, 'qwerty'}})
+test(s, t, {{':', 4, 2, 4, "uer"}}, {{1, '1', 5, 'query'}})
+test(s, t, {{'!', 4, 'answer'}}, {{1, '1', 5, 'answer', 'query'}})
+test(s, t, {{'#', 5, 1}}, {{1, '1', 5, 'answer'}})
+test(s, t, {{'!', -1, 1}}, {{1, '1', 5, 'answer', 1}})
+test(s, t, {{'!', -1, 2}}, {{1, '1', 5, 'answer', 1, 2}})
+test(s, t, {{'!', -1, 3}}, {{1, '1', 5, 'answer', 1, 2 ,3}})
+test(s, t, {{'#', 5, 100500}}, {{1, '1', 5, 'answer'}})
+test(s, t, {{'=', 4, 'qwerty'}}, {{1, '1', 5, 'qwerty'}})
+
+-- same check for negative posistion
+test(s, t, {{'+', -2, 5}}, {{1, '1', 10, 'qwerty'}})
+test(s, t, {{'-', -2, 3}}, {{1, '1', 7, 'qwerty'}})
+test(s, t, {{'&', -2, 5}}, {{1, '1', 5, 'qwerty'}})
+test(s, t, {{'|', -2, 8}}, {{1, '1', 13, 'qwerty'}})
+test(s, t, {{'^', -2, 12}}, {{1, '1', 1, 'qwerty'}})
+test(s, t, {{':', -1, 2, 4, "uer"}}, {{1, '1', 1, 'query'}})
+test(s, t, {{'!', -2, 'answer'}}, {{1, '1', 1, 'answer', 'query'}})
+test(s, t, {{'#', -1, 1}}, {{1, '1', 1, 'answer'}})
+test(s, t, {{'=', -1, 'answer!'}}, {{1, '1', 1, 'answer!'}})
+
+-- selective test for good multiple ops
+test(s, t, {{'+', 3, 2}, {'!', 4, 42}}, {{1, '1', 3, 42, 'answer!'}})
+test(s, t, {{'!', 1, 666}, {'#', 1, 1}, {'+', 3, 2}}, {{1, '1', 5, 42, 'answer!'}})
+test(s, t, {{'!', 3, 43}, {'+', 4, 2}}, {{1, '1', 43, 7, 42, 'answer!'}})
+test(s, t, {{'#', 3, 2}, {'=', 3, 1}, {'=', 4, '1'}}, {{1, '1', 1, '1'}})
+
+-- all bad operations, one op, equivalent to update but error is supressed
+test(s, t, {{'+', 4, 3}}, {{1, '1', 1, '1'}})
+test(s, t, {{'-', 4, 3}}, {{1, '1', 1, '1'}})
+test(s, t, {{'&', 4, 1}}, {{1, '1', 1, '1'}})
+test(s, t, {{'|', 4, 1}}, {{1, '1', 1, '1'}})
+test(s, t, {{'^', 4, 1}}, {{1, '1', 1, '1'}})
+test(s, t, {{':', 3, 2, 4, "uer"}}, {{1, '1', 1, '1'}})
+test(s, t, {{'!', 18, 'answer'}}, {{1, '1', 1, '1'}})
+test(s, t, {{'#', 18, 1}}, {{1, '1', 1, '1'}})
+test(s, t, {{'=', 18, 'qwerty'}}, {{1, '1', 1, '1'}})
+
+-- selective test for good/bad multiple ops mix
+test(s, t, {{'+', 3, 1}, {'+', 4, 1}}, {{1, '1', 2, '1'}})
+test(s, t, {{'-', 4, 1}, {'-', 3, 1}}, {{1, '1', 1, '1'}})
+test(s, t, {{'#', 18, 1}, {'|', 3, 14}, {'!', 18, '!'}}, {{1, '1', 15, '1'}})
+test(s, t, {{'^', 42, 42}, {':', 1, 1, 1, ''}, {'^', 3, 8}}, {{1, '1', 7, '1'}})
+test(s, t, {{'&', 3, 1}, {'&', 2, 1}, {'&', 4, 1}}, {{1, '1', 1, '1'}})
+
+-- broken ops must raise an exception and discarded
+'dump ' .. anything_to_string(s:select{})
+test(s, t, {{'&', 'a', 3}, {'+', 3, 3}}, {{1, '1', 1, '1'}})
+test(s, t, {{'+', 3, 3}, {'&', 3, 'a'}}, {{1, '1', 1, '1'}})
+test(s, t, {{'+', 3}, {'&', 3, 'a'}}, {{1, '1', 1, '1'}})
+test(s, t, {{':', 3, 3}}, {{1, '1', 1, '1'}})
+test(s, t, {{':', 3, 3, 3}}, {{1, '1', 1, '1'}})
+test(s, t, {{'?', 3, 3}}, {{1, '1', 1, '1'}})
+'dump ' .. anything_to_string(s:select{})
+
+-- -- ignoring ops for insert upsert
+test(s, {2, '2', 2, '2'}, {{}}, {{1, '1', 1, '1'}})
+test(s, {3, '3', 3, '3'}, {{'+', 3, 3}}, {{1, '1', 1, '1'}, {3, '3', 3, '3'}})
+
+t[1] = 1
+test(s, t, {{'+', 3, 3}, {'+', 4, 3}}, {{1, '1', 4, '1'}, {3, '3', 3, '3'}})
+t[1] = 2
+test(s, t, {{'-', 4, 1}}, {{1, '1', 4, '1'}, {2, '1', 1, 'qwerty'}, {3, '3', 3, '3'}})
+t[1] = 3
+test(s, t, {{':', 3, 3, 3, ''}, {'|', 3, 4}}, {{1, '1', 4, '1'}, {2, '1', 1, 'qwerty'}, {3, '3', 7, '3'}})
+
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a')
+s:replace({1, 1, 1})
+box.snapshot()
+s:upsert({1, 1}, {{'+', 2, 2}})
+s:upsert({1, 1}, {{'+', 3, 4}})
+s:select()
+
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+s:upsert({1}, {})
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', {parts = {1, 'unsigned', 3, 'unsigned'}})
+s:upsert({100, 100, 100}, {{'+', 2, 200}})
+s:upsert({200, 100, 200}, {{'+', 2, 300}})
+s:upsert({300, 100, 300}, {{'+', 2, 400}})
+i:select{}
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', {parts = {2, 'unsigned', 3, 'unsigned'}})
+s:upsert({100, 100, 100}, {{'+', 1, 200}})
+s:upsert({200, 100, 200}, {{'+', 1, 300}})
+s:upsert({300, 100, 300}, {{'+', 1, 400}})
+i:select{}
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', {parts = {3, 'unsigned', 2, 'unsigned'}})
+s:upsert({100, 100, 100}, {{'+', 1, 200}})
+s:upsert({200, 100, 200}, {{'+', 1, 300}})
+s:upsert({300, 100, 300}, {{'+', 1, 400}})
+i:select{}
+s:drop()
+
+
+-- Ephemeral index: methods: update
+
+s = box.schema.space.create_ephemeral()
+
+-- test delete field
+i = s:create_index('a')
+s:insert{1000001, 1000002, 1000003, 1000004, 1000005}
+s:update({1000001}, {{'#', 1, 1}})
+s:update({1000001}, {{'#', 1, "only one record please"}})
+i:drop()
+
+-- test arithmetic
+i = s:create_index('a')
+s:insert{1, 0}
+s:update(1, {{'+', 2, 10}})
+s:update(1, {{'+', 2, 15}})
+s:update(1, {{'-', 2, 5}})
+s:update(1, {{'-', 2, 20}})
+s:update(1, {{'|', 2, 0x9}})
+s:update(1, {{'|', 2, 0x6}})
+s:update(1, {{'&', 2, 0xabcde}})
+s:update(1, {{'&', 2, 0x2}})
+s:update(1, {{'^', 2, 0xa2}})
+s:update(1, {{'^', 2, 0xa2}})
+i:drop()
+
+-- test delete multiple fields
+i = s:create_index('a')
+s:insert{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
+s:update({0}, {{'#', 42, 1}})
+s:update({0}, {{'#', 4, 'abirvalg'}})
+s:update({0}, {{'#', 2, 1}, {'#', 4, 2}, {'#', 6, 1}})
+s:update({0}, {{'#', 4, 3}})
+s:update({0}, {{'#', 5, 123456}})
+s:update({0}, {{'#', 3, 4294967295}})
+s:update({0}, {{'#', 2, 0}})
+i:drop()
+
+-- test insert field
+i = s:create_index('a')
+s:insert{1, 3, 6, 9}
+s:update({1}, {{'!', 2, 2}})
+s:update({1}, {{'!', 4, 4}, {'!', 4, 5}, {'!', 5, 7}, {'!', 5, 8}})
+s:update({1}, {{'!', 10, 10}, {'!', 10, 11}, {'!', 10, 12}})
+i:drop()
+
+i = s:create_index('a')
+s:insert{1, 'tuple'}
+s:update({1}, {{'#', 2, 1}, {'!', 2, 'inserted tuple'}, {'=', 3, 'set tuple'}})
+i:drop()
+
+i = s:create_index('a')
+s:insert{1, 'tuple'}
+s:update({1}, {{'=', 2, 'set tuple'}, {'!', 2, 'inserted tuple'}, {'#', 3, 1}})
+s:update({1}, {{'!', 1, 3}, {'!', 1, 2}})
+i:drop()
+
+-- test update's assign opearations
+i = s:create_index('a')
+s:replace{1, 'field string value'}
+s:update({1}, {{'=', 2, 'new field string value'}, {'=', 3, 42}, {'=', 4, 0xdeadbeef}})
+
+-- test multiple update opearations on the same field
+s:update({1}, {{'+', 3, 16}, {'&', 4, 0xffff0000}, {'|', 4, 0x0000a0a0}, {'^', 4, 0xffff00aa}})
+
+-- test update splice operation
+s:replace{1953719668, 'something to splice'}
+s:update(1953719668, {{':', 2, 1, 4, 'no'}})
+s:update(1953719668, {{':', 2, 1, 2, 'every'}})
+
+-- check an incorrect offset
+s:update(1953719668, {{':', 2, 100, 2, 'every'}})
+s:update(1953719668, {{':', 2, -100, 2, 'every'}})
+i:drop()
+
+i = s:create_index('a')
+s:insert{1953719668, 'hello', 'october', '20th'}:unpack()
+i:drop()
+
+i = s:create_index('a')
+s:insert{1953719668, 'hello world'}
+s:update(1953719668, {{'=', 2, 'bye, world'}})
+s:delete{1953719668}
+
+s:replace({10, 'abcde'})
+s:update(10,  {{':', 2, 0, 0, '!'}})
+s:update(10,  {{':', 2, 1, 0, '('}})
+s:update(10,  {{':', 2, 2, 0, '({'}})
+s:update(10,  {{':', 2, -1, 0, ')'}})
+s:update(10,  {{':', 2, -2, 0, '})'}})
+
+-- test update delete operations
+s:update({1}, {{'#', 4, 1}, {'#', 3, 1}})
+
+-- test update insert operations
+s:update({1}, {{'!', 2, 1}, {'!', 2, 2}, {'!', 2, 3}, {'!', 2, 4}})
+
+-- s:update: zero field
+s:insert{48}
+s:update(48, {{'=', 0, 'hello'}})
+
+-- s:update: push/pop fields
+s:insert{1684234849}
+s:update({1684234849}, {{'#', 2, 1}})
+s:update({1684234849}, {{'!', -1, 'push1'}})
+s:update({1684234849}, {{'!', -1, 'push2'}})
+s:update({1684234849}, {{'!', -1, 'push3'}})
+s:update({1684234849}, {{'#', 2, 1}, {'!', -1, 'swap1'}})
+s:update({1684234849}, {{'#', 2, 1}, {'!', -1, 'swap2'}})
+s:update({1684234849}, {{'#', 2, 1}, {'!', -1, 'swap3'}})
+s:update({1684234849}, {{'#', -1, 1}, {'!', -1, 'noop1'}})
+s:update({1684234849}, {{'#', -1, 1}, {'!', -1, 'noop2'}})
+s:update({1684234849}, {{'#', -1, 1}, {'!', -1, 'noop3'}})
+
+--
+-- negative indexes
+--
+
+box.tuple.new({1, 2, 3, 4, 5}):update({{'!', 0, 'Test'}})
+box.tuple.new({1, 2, 3, 4, 5}):update({{'!', -1, 'Test'}})
+box.tuple.new({1, 2, 3, 4, 5}):update({{'!', -3, 'Test'}})
+box.tuple.new({1, 2, 3, 4, 5}):update({{'!', -5, 'Test'}})
+box.tuple.new({1, 2, 3, 4, 5}):update({{'!', -6, 'Test'}})
+box.tuple.new({1, 2, 3, 4, 5}):update({{'!', -7, 'Test'}})
+box.tuple.new({1, 2, 3, 4, 5}):update({{'!', -100500, 'Test'}})
+
+box.tuple.new({1, 2, 3, 4, 5}):update({{'=', 0, 'Test'}})
+box.tuple.new({1, 2, 3, 4, 5}):update({{'=', -1, 'Test'}})
+box.tuple.new({1, 2, 3, 4, 5}):update({{'=', -3, 'Test'}})
+box.tuple.new({1, 2, 3, 4, 5}):update({{'=', -5, 'Test'}})
+box.tuple.new({1, 2, 3, 4, 5}):update({{'=', -6, 'Test'}})
+box.tuple.new({1, 2, 3, 4, 5}):update({{'=', -100500, 'Test'}})
+
+box.tuple.new({1, 2, 3, 4, 5}):update({{'+', 0, 100}})
+box.tuple.new({1, 2, 3, 4, 5}):update({{'+', -1, 100}})
+box.tuple.new({1, 2, 3, 4, 5}):update({{'+', -3, 100}})
+box.tuple.new({1, 2, 3, 4, 5}):update({{'+', -5, 100}})
+box.tuple.new({1, 2, 3, 4, 5}):update({{'+', -6, 100}})
+box.tuple.new({1, 2, 3, 4, 5}):update({{'+', -100500, 100}})
+
+box.tuple.new({1, 2, 3, 4, 5}):update({{'|', 0, 100}})
+box.tuple.new({1, 2, 3, 4, 5}):update({{'|', -1, 100}})
+box.tuple.new({1, 2, 3, 4, 5}):update({{'|', -3, 100}})
+box.tuple.new({1, 2, 3, 4, 5}):update({{'|', -5, 100}})
+box.tuple.new({1, 2, 3, 4, 5}):update({{'|', -6, 100}})
+box.tuple.new({1, 2, 3, 4, 5}):update({{'|', -100500, 100}})
+
+box.tuple.new({1, 2, 3, 4, 5}):update({{'#', 0, 1}})
+box.tuple.new({1, 2, 3, 4, 5}):update({{'#', -1, 1}})
+box.tuple.new({1, 2, 3, 4, 5}):update({{'#', -3, 1}})
+box.tuple.new({1, 2, 3, 4, 5}):update({{'#', -5, 1}})
+box.tuple.new({1, 2, 3, 4, 5}):update({{'#', -6, 1}})
+box.tuple.new({1, 2, 3, 4, 5}):update({{'#', -100500, 1}})
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a')
+s:insert{1, 2, 3}
+s:update({1})
+s:update({1}, {'=', 1, 1})
+s:drop()
+
+ffi = require('ffi')
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a')
+
+s:insert{0, -1}
+-- + --
+s:update({0}, {{'+', 2, "a"}}) -- err
+s:update({0}, {{'+', 2, 10}}) -- neg(ative) + pos(itive) = pos(itive) 9
+s:update({0}, {{'+', 2, 5}}) -- pos + pos = pos 14
+s:update({0}, {{'+', 2, -4}}) -- pos + neg = pos 10
+s:update({0}, {{'+', 2, -22}}) -- pos + neg = neg -12
+s:update({0}, {{'+', 2, -3}}) -- neg + neg = neg -15
+s:update({0}, {{'+', 2, 7}}) -- neg + pos = neg -8
+-- - --
+s:update({0}, {{'-', 2, "a"}}) -- err
+s:update({0}, {{'-', 2, 16}}) -- neg(ative) - pos(itive) = neg(ative) -24
+s:update({0}, {{'-', 2, -4}}) -- neg - neg = neg 20
+s:update({0}, {{'-', 2, -32}}) -- neg - neg = pos 12
+s:update({0}, {{'-', 2, 3}}) -- pos - pos = pos 9
+s:update({0}, {{'-', 2, -5}}) -- pos - neg = pos 14
+s:update({0}, {{'-', 2, 17}}) -- pos - pos = neg -3
+-- bit --
+s:replace{0, 0} -- 0
+s:update({0}, {{'|', 2, 24}}) -- 24
+s:update({0}, {{'|', 2, 2}}) -- 26
+s:update({0}, {{'&', 2, 50}}) -- 18
+s:update({0}, {{'^', 2, 6}}) -- 20
+s:update({0}, {{'|', 2, -1}}) -- err
+s:update({0}, {{'&', 2, -1}}) -- err
+s:update({0}, {{'^', 2, -1}}) -- err
+s:replace{0, -1} -- -1
+s:update({0}, {{'|', 2, 2}}) -- err
+s:update({0}, {{'&', 2, 40}}) -- err
+s:update({0}, {{'^', 2, 6}}) -- err
+s:replace{0, 1.5} -- 1.5
+s:update({0}, {{'|', 2, 2}}) -- err
+s:update({0}, {{'&', 2, 40}}) -- err
+s:update({0}, {{'^', 2, 6}}) -- err
+-- double
+s:replace{0, 5} -- 5
+s:update({0}, {{'+', 2, 1.5}}) -- int + double = double 6.5
+s:update({0}, {{'|', 2, 2}}) -- err (double!)
+s:update({0}, {{'-', 2, 0.5}}) -- double - double = double 6
+s:update({0}, {{'+', 2, 1.5}}) -- double + double = double 7.5
+-- float
+s:replace{0, ffi.new("float", 1.5)} -- 1.5
+s:update({0}, {{'+', 2, 2}}) -- float + int = float 3.5
+s:update({0}, {{'+', 2, ffi.new("float", 3.5)}}) -- float + int = float 7
+s:update({0}, {{'|', 2, 2}}) -- err (float!)
+s:update({0}, {{'-', 2, ffi.new("float", 1.5)}}) -- float - float = float 5.5
+s:update({0}, {{'+', 2, ffi.new("float", 3.5)}}) -- float + float = float 9
+s:update({0}, {{'-', 2, ffi.new("float", 9)}}) -- float + float = float 0
+s:update({0}, {{'+', 2, ffi.new("float", 1.2)}}) -- float + float = float 1.2
+-- overflow --
+s:replace{0, 0xfffffffffffffffeull}
+s:update({0}, {{'+', 2, 1}}) -- ok
+s:update({0}, {{'+', 2, 1}}) -- overflow
+s:update({0}, {{'+', 2, 100500}}) -- overflow
+s:replace{0, 1}
+s:update({0}, {{'+', 2, 0xffffffffffffffffull}})  -- overflow
+s:replace{0, -1}
+s:update({0}, {{'+', 2, 0xffffffffffffffffull}})  -- ok
+s:replace{0, 0}
+s:update({0}, {{'-', 2, 0x7fffffffffffffffull}})  -- ok
+s:replace{0, -1}
+s:update({0}, {{'-', 2, 0x7fffffffffffffffull}})  -- ok
+s:replace{0, -2}
+s:update({0}, {{'-', 2, 0x7fffffffffffffffull}})  -- overflow
+s:replace{0, 1}
+s:update({0}, {{'-', 2, 0xffffffffffffffffull}})  -- overflow
+s:replace{0, 0xffffffffffffffefull}
+s:update({0}, {{'-', 2, -16}})  -- ok
+s:update({0}, {{'-', 2, -16}})  -- overflow
+s:replace{0, -0x4000000000000000ll}
+s:update({0}, {{'+', 2, -0x4000000000000000ll}})  -- ok
+s:replace{0, -0x4000000000000000ll}
+s:update({0}, {{'+', 2, -0x4000000000000001ll}})  -- overflow
+-- some wrong updates --
+s:update({0}, 0)
+s:update({0}, {'+', 2, 2})
+s:update({0}, {{}})
+s:update({0}, {{'+'}})
+s:update({0}, {{'+', 0}})
+s:update({0}, {{'+', '+', '+'}})
+s:update({0}, {{0, 0, 0}})
+
+ops = {}
+for i = 1,10 do table.insert(ops, {'=', 2, '1234567890'}) end
+s:upsert({0}, ops)
+
+s:get{0}
+s:update({0}, {})
+
+map = setmetatable({}, { __serialize = 'map' })
+t = box.tuple.new({1, 2, 3})
+s:replace({1, 2, 3})
+
+t:update({{'=', 3, map}})
+s:update(1, {{'=', 3, map}})
+
+s:drop()
+
+-- Ephemeral index: methods: pairs, count, select (iterators)
+
+-- iterator (str)
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'string'} })
+for key = 1, 100 do s:replace({tostring(key)}) end
+t = {} for state, v in i:pairs({}, {iterator = 'ALL'}) do table.insert(t, v) end
+t
+t = {} for state, v in i:pairs({}, {iterator = 'GE'}) do table.insert(t, v) end
+t
+t = {} for state, v in i:pairs(tostring(44), {iterator = 'GE'}) do table.insert(t, v) end
+t
+t = {} for state, v in i:pairs(tostring(44), {iterator = 'GT'}) do table.insert(t, v) end
+t
+t = {} for state, v in i:pairs({}, {iterator = 'LE'}) do table.insert(t, v) end
+t
+t = {} for state, v in i:pairs(tostring(77), {iterator = 'LE'}) do table.insert(t, v) end
+t
+t = {} for state, v in i:pairs({}, {iterator = 'LT'}) do table.insert(t, v) end
+t
+t = {} for state, v in i:pairs(tostring(77), {iterator = 'LT'}) do table.insert(t, v) end
+t
+s:drop()
+
+-- iterator (num)
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+for key = 1, 100 do s:replace({key}) end
+t = {} for state, v in i:pairs({}, {iterator = 'ALL'}) do table.insert(t, v) end
+t
+t = {} for state, v in i:pairs({}, {iterator = 'GE'}) do table.insert(t, v) end
+t
+t = {} for state, v in i:pairs(44, {iterator = 'GE'}) do table.insert(t, v) end
+t
+t = {} for state, v in i:pairs(44, {iterator = 'GT'}) do table.insert(t, v) end
+t
+t = {} for state, v in i:pairs({}, {iterator = 'LE'}) do table.insert(t, v) end
+t
+t = {} for state, v in i:pairs(77, {iterator = 'LE'}) do table.insert(t, v) end
+t
+t = {} for state, v in i:pairs({}, {iterator = 'LT'}) do table.insert(t, v) end
+t
+t = {} for state, v in i:pairs(77, {iterator = 'LT'}) do table.insert(t, v) end
+t
+s:drop()
+
+-- iterator multi-part (num, num)
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned', 2, 'unsigned'} })
+for key = 1, 100 do s:replace({key, key}) end
+t = {} for state, v in i:pairs({}, {iterator = 'ALL'}) do table.insert(t, v) end
+t
+t = {} for state, v in i:pairs({}, {iterator = 'GE'}) do table.insert(t, v) end
+t
+t = {} for state, v in i:pairs({44, 44}, {iterator = 'GE'}) do table.insert(t, v) end
+t
+t = {} for state, v in i:pairs({44, 44}, {iterator = 'GT'}) do table.insert(t, v) end
+t
+t = {} for state, v in i:pairs({}, {iterator = 'LE'}) do table.insert(t, v) end
+t
+t = {} for state, v in i:pairs({77, 77}, {iterator = 'LE'}) do table.insert(t, v) end
+t
+t = {} for state, v in i:pairs({}, {iterator = 'LT'}) do table.insert(t, v) end
+t
+t = {} for state, v in i:pairs({77, 77}, {iterator = 'LT'}) do table.insert(t, v) end
+t
+s:drop()
+
+-- iterator with tuple.new
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'string'} })
+for key = 1, 100 do s:replace({tostring(key)}) end
+t = {} for state, v in i:pairs(box.tuple.new{}, {iterator = 'ALL'}) do table.insert(t, v) end
+t
+t = {} for state, v in i:pairs(box.tuple.new{}, {iterator = 'GE'}) do table.insert(t, v) end
+t
+t = {} for state, v in i:pairs(box.tuple.new(tostring(44)), {iterator = 'GE'}) do table.insert(t, v) end
+t
+t = {} for state, v in i:pairs(box.tuple.new(tostring(44)), {iterator = 'GT'}) do table.insert(t, v) end
+t
+t = {} for state, v in i:pairs(box.tuple.new{}, {iterator = 'LE'}) do table.insert(t, v) end
+t
+t = {} for state, v in i:pairs(box.tuple.new(tostring(77)), {iterator = 'LE'}) do table.insert(t, v) end
+t
+t = {} for state, v in i:pairs(box.tuple.new{}, {iterator = 'LT'}) do table.insert(t, v) end
+t
+t = {} for state, v in i:pairs(box.tuple.new(tostring(77)), {iterator = 'LT'}) do table.insert(t, v) end
+t
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'string'} })
+i:pairs({}, {iterator = 666 })
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a')
+s:replace({1})
+s:replace({2})
+s:replace({3})
+s:replace({4})
+s:pairs(2, { iterator = 'GE' }):totable()
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a')
+s:auto_increment{1}
+s:auto_increment{2}
+s:auto_increment{3}
+s:auto_increment{4}
+s:auto_increment{5}
+
+s:pairs(3, 'GE'):totable()
+i:pairs(3, 'GE'):totable()
+s:pairs(3, {iterator = 'GE' }):totable()
+i:pairs(3, {iterator = 'GE' }):totable()
+
+s:pairs(3, 'EQ'):totable()
+i:pairs(3, 'EQ'):totable()
+s:pairs(3, {iterator = 'EQ' }):totable()
+i:pairs(3, {iterator = 'EQ' }):totable()
+
+s:pairs(3, 'GT'):totable()
+i:pairs(3, 'GT'):totable()
+s:pairs(3, {iterator = 'GT' }):totable()
+i:pairs(3, {iterator = 'GT' }):totable()
+
+i:select({3}, 'LE')
+s:select({3}, 'LE')
+
+i:count({3}, 'GT')
+s:count({3}, 'GT')
+
+s:drop()
+
+-- implement lazy iterator positioning
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned', 2, 'unsigned'} })
+for i = 1,3 do for j = 1,3 do s:replace{i, j} end end
+
+itr1,itr2,itr3 = s:pairs{2}
+_ = s:replace{1, 4}
+r = {}
+for k,v in itr1,itr2,itr3 do table.insert(r, v) end
+r
+
+itr1,itr2,itr3 = s:pairs({2}, {iterator = 'GE'})
+_ = s:replace{1, 5}
+r = {}
+for k,v in itr1,itr2,itr3 do table.insert(r, v) end
+r
+
+itr1,itr2,itr3 = s:pairs({2}, {iterator = 'REQ'})
+s:replace{2, 4}
+r = {}
+for k,v in itr1,itr2,itr3 do table.insert(r, v) end
+r
+
+r = nil
+s:drop()
+
+-- make tree iterators stable
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+
+for i = 1,10 do s:replace{i} end
+r = {}
+for k,v in s:pairs{} do table.insert(r, v[1]) s:delete(v[1]) end
+r
+s:select{}
+
+for i = 1,10 do s:replace{i} end
+r = {}
+for k,v in s:pairs({}, {iterator = 'REQ'}) do table.insert(r, v[1]) s:delete(v[1]) end
+r
+s:select{}
+
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned', 2, 'unsigned'} })
+
+for i = 1,3 do for j = 1,3 do s:replace{i, j} end end
+r = {}
+for k,v in s:pairs{2} do table.insert(r, v) s:delete{v[1], v[2]} end
+r
+s:select{}
+
+for i = 1,3 do for j = 1,3 do s:replace{i, j} end end
+r = {}
+for k,v in s:pairs({3}, {iterator = 'REQ'}) do table.insert(r, v) s:delete{v[1], v[2]} end
+r
+s:select{}
+
+r = nil
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+s:replace{10} s:replace{20} s:replace{30} s:replace{40} s:replace{50} s:replace{60}
+
+gen,param,state = i:pairs({25})
+s:replace{25}
+state, value = gen(param,state)
+value
+state, value = gen(param,state)
+value
+
+gen,param,state = i:pairs({35})
+state, value = gen(param,state)
+value
+s:replace{35}
+state, value = gen(param,state)
+value
+
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+s:replace{10} s:replace{20} s:replace{30} s:replace{40} s:replace{50} s:replace{60}
+
+gen,param,state = i:pairs({30}, {iterator = 'GE'})
+state, value = gen(param, state)
+value
+s:replace{0}
+state, value = gen(param, state)
+value
+s:replace{42}
+state, value = gen(param, state)
+value
+s:replace{80}
+state, value = gen(param, state)
+value
+s:replace{15}
+state, value = gen(param, state)
+value
+state, value = gen(param, state)
+value
+state, value = gen(param, state)
+state
+value
+
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+s:replace{10} s:replace{20} s:replace{30} s:replace{40} s:replace{50} s:replace{60}
+
+gen,param,state = i:pairs({40}, {iterator = 'LE'})
+state, value = gen(param, state)
+value
+s:replace{0}
+state, value = gen(param, state)
+value
+s:replace{15}
+state, value = gen(param, state)
+value
+s:replace{42}
+state, value = gen(param, state)
+value
+s:replace{32}
+state, value = gen(param, state)
+value
+s:replace{80}
+state, value = gen(param, state)
+value
+state, value = gen(param, state)
+state
+value
+
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+s:replace{10} s:replace{20} s:replace{30} s:replace{40} s:replace{50} s:replace{60}
+
+gen,param,state = i:pairs({28}, {iterator = 'GE'})
+s:replace{0}
+state, value = gen(param, state)
+value
+s:replace{15}
+state, value = gen(param, state)
+value
+s:replace{42}
+state, value = gen(param, state)
+value
+s:replace{32}
+state, value = gen(param, state)
+value
+s:replace{80}
+state, value = gen(param, state)
+value
+state, value = gen(param, state)
+value
+gen(param, state)
+-- test iterator dummy function, invoked when it's out of bounds
+gen(param, state)
+
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+s:replace{10} s:replace{20} s:replace{30} s:replace{40} s:replace{50} s:replace{60}
+
+gen,param,state = i:pairs({42}, {iterator = 'LE'})
+s:replace{0}
+state, value = gen(param, state)
+value
+s:replace{42}
+state, value = gen(param, state)
+value
+s:replace{15}
+state, value = gen(param, state)
+value
+s:replace{32}
+state, value = gen(param, state)
+value
+s:replace{80}
+state, value = gen(param, state)
+value
+state, value = gen(param, state)
+value
+gen(param, state)
+-- test iterator dummy function, invoked when it's out of bounds
+gen(param, state)
+
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+s:replace{10} s:replace{20} s:replace{30} s:replace{40} s:replace{50} s:replace{60}
+
+gen,param,state = i:pairs({20}, {iterator = 'GT'})
+state, value = gen(param, state)
+value
+s:replace{0}
+state, value = gen(param, state)
+value
+s:replace{42}
+state, value = gen(param, state)
+value
+s:replace{80}
+state, value = gen(param, state)
+value
+s:replace{15}
+state, value = gen(param, state)
+value
+state, value = gen(param, state)
+value
+gen(param, state)
+-- test iterator dummy function, invoked when it's out of bounds
+gen(param, state)
+
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+s:replace{10} s:replace{20} s:replace{30} s:replace{40} s:replace{50} s:replace{60}
+
+gen,param,state = i:pairs({50}, {iterator = 'LT'})
+state, value = gen(param, state)
+value
+s:replace{0}
+state, value = gen(param, state)
+value
+s:replace{15}
+state, value = gen(param, state)
+value
+s:replace{42}
+state, value = gen(param, state)
+value
+s:replace{32}
+state, value = gen(param, state)
+value
+s:replace{80}
+state, value = gen(param, state)
+value
+gen(param, state)
+-- test iterator dummy function, invoked when it's out of bounds
+gen(param, state)
+
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+s:replace{10} s:replace{20} s:replace{30} s:replace{40} s:replace{50} s:replace{60}
+
+gen,param,state = i:pairs({28}, {iterator = 'GT'})
+s:replace{0}
+state, value = gen(param, state)
+value
+s:replace{15}
+state, value = gen(param, state)
+value
+s:replace{42}
+state, value = gen(param, state)
+value
+s:replace{32}
+state, value = gen(param, state)
+value
+s:replace{80}
+state, value = gen(param, state)
+value
+state, value = gen(param, state)
+value
+gen(param, state)
+-- test iterator dummy function, invoked when it's out of bounds
+gen(param, state)
+
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+s:replace{10} s:replace{20} s:replace{30} s:replace{40} s:replace{50} s:replace{60}
+
+gen,param,state = i:pairs({42}, {iterator = 'LT'})
+s:replace{0}
+state, value = gen(param, state)
+value
+s:replace{42}
+state, value = gen(param, state)
+value
+s:replace{15}
+state, value = gen(param, state)
+value
+s:replace{32}
+state, value = gen(param, state)
+value
+s:replace{80}
+state, value = gen(param, state)
+value
+state, value = gen(param, state)
+value
+gen(param, state)
+-- test iterator dummy function, invoked when it's out of bounds
+gen(param, state)
+
+s:drop()
+
+
+-- Ephemeral space: methods: delete
+
+-- delete (str)
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'string'} })
+for key = 1, 10 do s:replace({tostring(key)}) end
+t = {}
+for key = 1, 10 do table.insert(t, s:get({tostring(key)})) end
+t
+for key = 1, 10 do s:delete({tostring(key)}) end
+for key = 1, 10 do assert(s:get({tostring(key)}) == nil) end
+
+s:delete({tostring(7)})
+s:drop()
+
+-- delete (num)
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+for key = 1, 10 do s:replace({key}) end
+t = {}
+for key = 1, 10 do table.insert(t, s:get({key})) end
+t
+for key = 1, 10 do s:delete({key}) end
+for key = 1, 10 do assert(s:get({key}) == nil) end
+s:delete({7})
+s:drop()
+
+-- delete multi-part (num, num)
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned', 2, 'unsigned'} })
+for key = 1, 10 do s:replace({key, key}) end
+t = {}
+for key = 1, 10 do table.insert(t, s:get({key, key})) end
+t
+for key = 1, 10 do s:delete({key, key}) end
+for key = 1, 10 do assert(s:get({key, key}) == nil) end
+s:delete({7, 7})
+s:drop()
+
+-- delete (str)
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'string'} })
+for key = 1, 10 do s:replace({tostring(key)}) end
+t = {}
+for key = 1, 10 do table.insert(t, s:get({tostring(key)})) end
+t
+for key = 1, 10 do s:delete(box.tuple.new{tostring(key)}) end
+for key = 1, 10 do assert(s:get({tostring(key)}) == nil) end
+
+s:delete(box.tuple.new{tostring(7)})
+s:drop()
+
+-- Ephemeral space: methods: bsize, len
+
+utils = dofile('utils.lua')
+
+s = box.schema.space.create_ephemeral()
+idx = s:create_index('a')
+
+for i = 1, 13 do s:insert{ i, string.rep('x', i) } end
+
+s:len()
+s:bsize()
+utils.space_bsize(s)
+
+for i = 1, 13, 2 do s:delete{ i } end
+
+s:len()
+s:bsize()
+utils.space_bsize(s)
+
+for i = 2, 13, 2 do s:update( { i }, {{ ":", 2, i, 0, string.rep('y', i) }} ) end
+
+s:len()
+s:bsize()
+utils.space_bsize(s)
+
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'INTEGER'} })
+
+s:insert({1, "AAAA"})
+s:insert({2, "AAAA"})
+s:insert({3, "AAAA"})
+s:insert({4, "AAAA"})
+
+i:select()
+i:max(2)
+i:min(2)
+i:count(2)
+i:max()
+i:min()
+i:count()
+
+s:insert({20, "AAAA"})
+s:insert({30, "AAAA"})
+s:insert({40, "AAAA"})
+
+i:select()
+i:max(15)
+i:min(15)
+i:count(15)
+i:max()
+i:min()
+i:count()
+
+s:insert({-2, "AAAA"})
+s:insert({-3, "AAAA"})
+s:insert({-4, "AAAA"})
+
+i:select()
+i:max(0)
+i:min(0)
+i:count(0)
+i:max()
+i:min()
+i:count()
+
+s:drop()
+
+-- number type
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'number'} })
+
+s:insert({1, "AAAA"})
+s:insert({2, "AAAA"})
+s:insert({3, "AAAA"})
+s:insert({4, "AAAA"})
+
+i:select()
+i:max(2)
+i:min(2)
+i:count(2)
+i:max()
+i:min()
+i:count()
+
+s:insert({20, "AAAA"})
+s:insert({30, "AAAA"})
+s:insert({40, "AAAA"})
+
+i:select()
+i:max(15)
+i:min(15)
+i:count(15)
+i:max()
+i:min()
+i:count()
+
+s:insert({-2, "AAAA"})
+s:insert({-3, "AAAA"})
+s:insert({-4, "AAAA"})
+
+i:select()
+i:max(0)
+i:min(0)
+i:count(0)
+i:max()
+i:min()
+i:count()
+
+s:insert({1.5, "AAAA"})
+s:insert({2.5, "AAAA"})
+s:insert({3.5, "AAAA"})
+s:insert({4.5, "AAAA"})
+
+i:select()
+i:max(1)
+i:min(1)
+i:count(1)
+i:max()
+i:min()
+i:count()
+
+s:drop()
+
+-- str type
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'string'} })
+s:insert({'1', "AAAA"})
+s:insert({'2', "AAAA"})
+s:insert({'3', "AAAA"})
+s:insert({'4', "AAAA"})
+
+i:select()
+i:max('2')
+i:min('2')
+i:count('2')
+i:max()
+i:min()
+i:count()
+
+s:insert({'20', "AAAA"})
+s:insert({'30', "AAAA"})
+s:insert({'40', "AAAA"})
+
+i:select()
+i:max('15')
+i:min('15')
+i:count('15')
+i:max()
+i:min()
+i:count()
+
+s:insert({'-2', "AAAA"})
+s:insert({'-3', "AAAA"})
+s:insert({'-4', "AAAA"})
+
+i:select()
+i:max('0')
+i:min('0')
+i:count('0')
+i:max()
+i:min()
+i:count()
+
+s:drop()
+
+-- num type
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'unsigned'} })
+s:insert({1, "AAAA"})
+s:insert({2, "AAAA"})
+s:insert({3, "AAAA"})
+s:insert({4, "AAAA"})
+
+i:select()
+i:max(2)
+i:min(2)
+i:count(2)
+i:max()
+i:min()
+i:count()
+
+s:insert({20, "AAAA"})
+s:insert({30, "AAAA"})
+s:insert({40, "AAAA"})
+
+i:select()
+i:max(15)
+i:min(15)
+i:count(15)
+i:max()
+i:min()
+i:count()
+
+s:drop()
+
+-- scalar type
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'scalar'} })
+s:insert({1, "AAAA"})
+s:insert({2, "AAAA"})
+s:insert({3, "AAAA"})
+s:insert({4, "AAAA"})
+
+i:select()
+i:max(2)
+i:min(2)
+i:count(2)
+i:max()
+i:min()
+i:count()
+
+s:insert({20, "AAAA"})
+s:insert({30, "AAAA"})
+s:insert({40, "AAAA"})
+
+i:select()
+i:max(15)
+i:min(15)
+i:count(15)
+i:max()
+i:min()
+i:count()
+s:insert({'1', "AAAA"})
+s:insert({'2', "AAAA"})
+s:insert({'3', "AAAA"})
+s:insert({'4', "AAAA"})
+
+i:select()
+i:max('2')
+i:min('2')
+i:count('2')
+i:max()
+i:min()
+i:count()
+
+s:insert({'20', "AAAA"})
+s:insert({'30', "AAAA"})
+s:insert({'40', "AAAA"})
+
+i:select()
+i:max('15')
+i:min('15')
+i:count('15')
+i:max()
+i:min()
+i:count()
+
+s:insert({'-2', "AAAA"})
+s:insert({'-3', "AAAA"})
+s:insert({'-4', "AAAA"})
+
+i:select()
+i:max('0')
+i:min('0')
+i:count('0')
+i:max()
+i:min()
+i:count()
+
+s:insert({-2, "AAAA"})
+s:insert({-3, "AAAA"})
+s:insert({-4, "AAAA"})
+
+i:select()
+i:max(0)
+i:min(0)
+i:count(0)
+i:max()
+i:min()
+i:count()
+
+s:drop()
+
+-- multi filed indices
+
+-- scalar int
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'scalar', 2, 'INTEGER'} })
+
+s:insert({1, 1})
+s:insert({1, 2})
+s:insert({1, 3})
+s:insert({1, -4})
+
+i:select()
+i:max({1})
+i:min({1})
+i:count({1})
+i:max({1, 0})
+i:min({1, 1})
+i:count({1})
+i:max()
+i:min()
+i:count()
+
+s:insert({2, 1})
+s:insert({2, 2})
+s:insert({2, 3})
+s:insert({2, -4})
+
+i:select()
+i:max({2})
+i:min({2})
+i:count({2})
+i:max({2, 0})
+i:min({2, 1})
+i:count({2})
+i:max()
+i:min()
+i:count()
+
+s:drop()
+
+-- scalar str
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'scalar', 2, 'string'} })
+
+s:insert({1, '1'})
+s:insert({1, '2'})
+s:insert({1, '3'})
+s:insert({1, '-4'})
+
+i:select()
+i:max({1})
+i:min({1})
+i:count({1})
+i:max({1, '0'})
+i:min({1, '1'})
+i:count({1})
+i:max()
+i:min()
+i:count()
+
+s:insert({2, '1'})
+s:insert({2, '2'})
+s:insert({2, '3'})
+s:insert({2, '-4'})
+
+i:select()
+i:max({2})
+i:min({2})
+i:count({2})
+i:max({2, '0'})
+i:min({2, '1'})
+i:count({2})
+i:max()
+i:min()
+i:count()
+
+s:drop()
+
+-- min max count after many inserts
+
+string = require('string')
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'scalar'} })
+
+long_string = string.rep('A', 650)
+for i = 1, 1000 do s:insert({i, long_string}) end
+
+i:max({100})
+i:max({700})
+i:min({100})
+i:min({700})
+i:count({2})
+i:max()
+i:min()
+i:count()
+
+s:drop()
+
+s = box.schema.space.create_ephemeral()
+i = s:create_index('a', { type = 'tree', parts = {1, 'scalar', 2, 'INTEGER'} })
+
+for i = 1, 1000 do s:insert({i % 10, i, long_string}) end
+
+i:max({1, 100})
+i:max({2, 700})
+i:max({3})
+i:min({1, 10})
+i:min({1, 700})
+i:min({3})
+i:count({2})
+i:max()
+i:min()
+i:count()
+
+s:drop()
+
+test_run:cmd("restart server default")
-- 
2.7.4

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [tarantool-patches] Re: [PATCH 0/5] Expose ephemeral spaces into Lua
  2018-07-12 11:16 [tarantool-patches] [PATCH 0/5] Expose ephemeral spaces into Lua imeevma
                   ` (4 preceding siblings ...)
  2018-07-12 11:16 ` [tarantool-patches] [PATCH 5/5] Methods for ephemeral space and its index imeevma
@ 2018-07-12 11:30 ` Imeev Mergen
  5 siblings, 0 replies; 12+ messages in thread
From: Imeev Mergen @ 2018-07-12 11:30 UTC (permalink / raw)
  To: tarantool-patches

These patches allow us use ephemeral spaces in Lua. Ephemeral spaces are 
special spaces that in most cases work in the same way as usual spaces 
(for users), but they do not touch transactions and are local to one Lua 
session. The are deleted by GC when there is no references to them. They 
can be created and deleted but cannot be altered. Till now they were 
used to process some sql queries.


On 07/12/2018 02:16 PM, imeevma@tarantool.org wrote:
> Branch: https://github.com/tarantool/tarantool/compare/imeevma/gh-3375-lua-expose-ephemeral-spaces
> Issue: https://github.com/tarantool/tarantool/issues/3375
>
> Mergen Imeev (5):
>    Create new methods for ephemeral spaces
>    Move some decode functions from alter.cc
>    Ephemeral space creation and deletion in Lua
>    Primary index for ephemeral spaces
>    Methods for ephemeral space and its index
>
>   src/box/alter.cc                  |  203 +-
>   src/box/box.cc                    |  170 +
>   src/box/box.h                     |   51 +
>   src/box/index.cc                  |  172 +
>   src/box/index.h                   |  140 +
>   src/box/index_def.c               |  102 +
>   src/box/index_def.h               |   26 +
>   src/box/lua/info.h                |    4 -
>   src/box/lua/schema.lua            |  321 ++
>   src/box/lua/space.cc              |  669 +++
>   src/box/memtx_space.c             |  210 +-
>   src/box/memtx_tree.c              |    5 +
>   src/box/space.h                   |   17 -
>   src/box/space_def.c               |  150 +
>   src/box/space_def.h               |   15 +
>   src/box/sql.c                     |   10 +-
>   src/box/sysview_engine.c          |   22 -
>   src/box/vinyl.c                   |   22 -
>   test/box/ephemeral_space.result   | 8461 +++++++++++++++++++++++++++++++++++++
>   test/box/ephemeral_space.test.lua | 1864 ++++++++
>   test/engine/iterator.result       |    2 +-
>   21 files changed, 12335 insertions(+), 301 deletions(-)
>   create mode 100644 test/box/ephemeral_space.result
>   create mode 100644 test/box/ephemeral_space.test.lua
>

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [tarantool-patches] Re: [PATCH 5/5] Methods for ephemeral space and its index
  2018-07-12 11:16 ` [tarantool-patches] [PATCH 5/5] Methods for ephemeral space and its index imeevma
@ 2018-07-13 16:32   ` Vladislav Shpilevoy
  0 siblings, 0 replies; 12+ messages in thread
From: Vladislav Shpilevoy @ 2018-07-13 16:32 UTC (permalink / raw)
  To: tarantool-patches, imeevma

Thanks for the patch! See 10 comments below.

On 12/07/2018 14:16, imeevma@tarantool.org wrote:
> This patch defines most methods for index of
> ephemeral space and ephemeral space.
> 
> Closes #3375.
> ---
>   src/box/box.cc                    |   62 +
>   src/box/box.h                     |    9 +
>   src/box/index.cc                  |  172 +
>   src/box/index.h                   |  140 +
>   src/box/lua/info.h                |    4 -
>   src/box/lua/schema.lua            |  122 +
>   src/box/lua/space.cc              |  396 +-
>   test/box/ephemeral_space.result   | 7979 +++++++++++++++++++++++++++++++++++++
>   test/box/ephemeral_space.test.lua | 1694 ++++++++
>   9 files changed, 10573 insertions(+), 5 deletions(-)
> 
> diff --git a/src/box/box.cc b/src/box/box.cc
> index 795e3ee..e825735 100644
> --- a/src/box/box.cc
> +++ b/src/box/box.cc
> @@ -1171,6 +1171,68 @@ box_upsert(uint32_t space_id, uint32_t index_id, const char *tuple,
>   }
>   
>   int
> +box_ephemeral_select(struct space *space, uint32_t index_id,
> +		     int iterator, uint32_t offset, uint32_t limit,
> +		     const char *key, const char *key_end,
> +		     struct port *port)

1. This function is duplicate of box_select except transactions. Please,
remove this redundancy.

> diff --git a/src/box/index.cc b/src/box/index.cc
> index 188995e..d2d209f 100644
> --- a/src/box/index.cc
> +++ b/src/box/index.cc
> @@ -354,6 +354,131 @@ box_index_count(uint32_t space_id, uint32_t index_id, int type,
> +
> +int
> +box_ephemeral_index_get(struct space *space, uint32_t index_id, const char *key,
> +			const char *key_end, box_tuple_t **result)

2. Again duplication of a big function. Same about functions below.

> +{
> +	assert(key != NULL && key_end != NULL && result != NULL);
> +	mp_tuple_assert(key, key_end);
> +	struct index *index = index_find(space, index_id);
> +	if (index == NULL)
> +		return -1;
> +	if (!index->def->opts.is_unique) {
> +		diag_set(ClientError, ER_MORE_THAN_ONE_TUPLE);
> +		return -1;
> +	}
> +	uint32_t part_count = mp_decode_array(&key);
> +	if (exact_key_validate(index->def->key_def, key, part_count))
> +		return -1;
> +	if (index_get(index, key, part_count, result) != 0)
> +		return -1;
> +	rmean_collect(rmean_box, IPROTO_SELECT, 1);

3. I do not think we should track any ephemeral space things in statistics.
Same about rmean in other places.

> +	if (*result != NULL)
> +		tuple_bless(*result);
> +	return 0;
> +}
> +
> diff --git a/src/box/lua/info.h b/src/box/lua/info.h
> index 78cd9e6..bf4c613 100644
> --- a/src/box/lua/info.h
> +++ b/src/box/lua/info.h
> @@ -49,8 +49,4 @@ luaT_info_handler_create(struct info_handler *h, struct lua_State *L);
>   } /* extern "C" */
>   #endif /* defined(__cplusplus) */
>   
> -#if defined(__cplusplus)
> -} /* extern "C" */
> -#endif /* defined(__cplusplus) */

4. Please, rebase. This hunk is already in 2.0.

> -
>   #endif /* INCLUDES_TARANTOOL_LUA_INFO_H */
> diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc
> index 4374db9..81b9f3c 100644
> --- a/src/box/lua/space.cc
> +++ b/src/box/lua/space.cc
> @@ -704,6 +708,370 @@ lbox_index_drop_ephemeral(struct lua_State *L)
> +static int
> +lbox_ephemeral_index_iterator(lua_State *L)
> +{
> +	if (lua_gettop(L) != 3 || !lua_istable(L, 1)) {
> +		return luaL_error(L, "Usage index:iterator(type, key)");
> +	}

5. Extra '{' and '}'.

> +	struct space *space = (struct space *)lua_checkephemeralspace(L, 1);
> +	int iterator = luaL_checknumber(L, 2);

6. It is allowed to use string iterator types: 'EQ', 'GE' etc.

> +	size_t mpkey_len;
> +	/* Key encoded by Lua */
> +	const char *mpkey = lua_tolstring(L, 3, &mpkey_len);
> +	struct iterator *it = box_ephemeral_index_iterator(space, 0, iterator,
> +							   mpkey,
> +							   mpkey + mpkey_len);
> +	if (it == NULL)
> +		return luaT_error(L);
> +
> +	assert(CTID_STRUCT_ITERATOR_REF != 0);
> +	struct iterator **ptr = (struct iterator **) luaL_pushcdata(L,
> +		CTID_STRUCT_ITERATOR_REF);

7. Where do you set GC for the iterator?

> +	*ptr = it; /* NULL handled by Lua, gc also set by Lua */
> +	return 1;
> +}
> +
> +/**
> + * Update tuple matched the provided key.
> + *
> + * @param Lua ephemeral space.
> + * @param Lua tuple - key.
> + * @param Lua tuple - operaions in case of update .
> + * @retval tuple or nil.
> + */
> +static int
> +lbox_ephemeral_index_update(lua_State *L)
> +{
> +	if (lua_gettop(L) != 3 || !lua_istable(L, 1) ||
> +	    (lua_type(L, 2) != LUA_TTABLE && luaT_istuple(L, 2) == NULL) ||
> +	    (lua_type(L, 3) != LUA_TTABLE && luaT_istuple(L, 3) == NULL))
> +		return luaL_error(L, "Usage index:update(key, ops)");
> +	struct space *space = (struct space *)lua_checkephemeralspace(L, 1);
> +	size_t key_len;
> +	const char *key = lbox_encode_tuple_on_gc(L, 2, &key_len);
> +	size_t ops_len;
> +	const char *ops = lbox_encode_tuple_on_gc(L, 3, &ops_len);
> +	struct tuple *result;
> +	if (box_ephemeral_update(space, 0, key, key + key_len,
> +				 ops, ops + ops_len, 1, &result) != 0)
> +		return luaT_error(L);
> +	return luaT_pushtupleornil(L, result);
> +}

8. Looks like most of this functions are almost complete
duplicates of ones from index.c. Please, remove duplication.
And move the functions into index.c.

> diff --git a/test/box/ephemeral_space.result b/test/box/ephemeral_space.result
> index d958ffe..0f80fa4 100644
> --- a/test/box/ephemeral_space.result
> +++ b/test/box/ephemeral_space.result
> @@ -1,3 +1,6 @@
> +test_run = require('test_run').new()
> +---
> +...
>   -- Ephemeral space: creation and dropping.
>   -- Simple creation.
>   s = box.schema.space.create_ephemeral()
> @@ -480,3 +483,7979 @@ s:frommap({ddd = 1, aaa = 2, ccc = 3, bbb = 4}, {dummy = true})
>   s:drop()
>   ---
>   ...
> +-- Ephemeral space: methods: insert
> +s = box.schema.space.create_ephemeral({field_count = 3})
> +---
> +...
> +i = s:create_index('a')

9. Please, reduce the test size. It is really huge and full of
redundancy:
* you do not need to create/drop space to test each statement;
* you do not need to fill each space with 10 records to test only one of them;
* it is not necessary to the same functionality with a set of almost
   identical tests like three insertions below.

> +---
> +...
> +s:insert{1}
> +---
> +- error: Tuple field count 1 does not match space field count 3
> +...
> +s:insert{2,2}
> +---
> +- error: Tuple field count 2 does not match space field count 3
> +...
> +s:insert{3,3,3}
> +---
> +- [3, 3, 3]
> +...
> +s:insert{4,4,4,4}
> +---
> +- error: Tuple field count 4 does not match space field count 3
> +...
> +s:drop()
> +---
> +...
> +---
> +...
> +test_run:cmd("setopt delimiter ''");
> +---
> +- true
> +...
> +function sort(t) table.sort(t, less) return t end

10. Unused function.

General recommendation: please, try to make .result file be at most 1000 lines.

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [tarantool-patches] Re: [PATCH 3/5] Ephemeral space creation and deletion in Lua
  2018-07-12 11:16 ` [tarantool-patches] [PATCH 3/5] Ephemeral space creation and deletion in Lua imeevma
@ 2018-07-13 16:32   ` Vladislav Shpilevoy
  0 siblings, 0 replies; 12+ messages in thread
From: Vladislav Shpilevoy @ 2018-07-13 16:32 UTC (permalink / raw)
  To: tarantool-patches, imeevma

Thanks for the patch! See 18 comments below.

On 12/07/2018 14:16, imeevma@tarantool.org wrote:
> Import functions to create ephemeral space in Lua and some
> its methods that do not require index.
> 
> Part of #3375.
> ---
>   src/box/lua/schema.lua            |  69 ++++++++++
>   src/box/lua/space.cc              | 183 +++++++++++++++++++++++++
>   test/box/ephemeral_space.result   | 271 ++++++++++++++++++++++++++++++++++++++
>   test/box/ephemeral_space.test.lua |  96 ++++++++++++++
>   test/engine/iterator.result       |   2 +-
>   5 files changed, 620 insertions(+), 1 deletion(-)
>   create mode 100644 test/box/ephemeral_space.result
>   create mode 100644 test/box/ephemeral_space.test.lua
> 
> diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
> index e4c1b1d..81f0fa0 100644
> --- a/src/box/lua/schema.lua
> +++ b/src/box/lua/schema.lua
> @@ -477,6 +477,64 @@ box.schema.space.create = function(name, options)
>       return box.space[id], "created"
>   end
>   
> +local space_new_ephemeral = box.internal.space.space_new_ephemeral
> +box.internal.space.space_new_ephemeral = nil
> +local space_delete_ephemeral = box.internal.space.space_delete_ephemeral
> +box.internal.space.space_delete_ephemeral = nil
> +local space_ephemeral_methods = box.schema.space_ephemeral_methods
> +box.schema.space_ephemeral_methods = nil
> +local space_ephemeral_mt = {}
> +
> +box.schema.space.create_ephemeral = function(options)
> +    local options_template = {
> +        engine = 'string',
> +        field_count = 'number',
> +        format = 'table',
> +    }
> +    local options_defaults = {
> +        engine = 'memtx',
> +        field_count = 0,
> +    }
> +    check_param_table(options, options_template)
> +    options = update_param_table(options, options_defaults)
> +
> +    local format = options.format and options.format or {}
> +    check_param(format, 'format', 'table')
> +    format = update_format(format)
> +    local packed_format = msgpack.encode(format)

1. You can pass format as a Lua table into space_new_ephemeral
and encode it inside. It is strange to pass binary data into
Lua function, that does not work with network or disk.

> +
> +    local ephemeral_space = {}
> +    ephemeral_space.space = space_new_ephemeral(options.engine,
> +                                                options.field_count,
> +                                                packed_format)
> +    ephemeral_space.space_format = format
> +    ephemeral_space.engine = options.engine
> +    ephemeral_space.field_count = options.field_count
> +    ephemeral_space.temporary = true

2. I think, the space should not have this flag at all.
Neither true nor false.

> +    ephemeral_space.index = {}
> +    -- Set GC for result
> +    setmetatable(ephemeral_space, space_ephemeral_mt)
> +    ephemeral_space.proxy = newproxy(true)
> +    getmetatable(ephemeral_space.proxy).__gc = function(self)
> +        box.schema.space.drop_ephemeral(ephemeral_space)

3. Why not 'ephemeral_space:drop()' ?

> +    end
> +    -- Return result

4. Unhelpful comment.

> +    return ephemeral_space
> +end
> +
> +box.schema.space.drop_ephemeral = function(ephemeral_space)
> +    if ephemeral_space == nil or ephemeral_space.space == nil then
> +        return

5. How is it possible, that ephemeral_space == nil?

> +    end
> +    check_param(ephemeral_space.space, 'space', 'cdata')
> +    space_delete_ephemeral(ephemeral_space)

6. It is strange, that space_new_ephemeral returns space *, but
space_delete_ephemeral takes Lua table. Please, make space_delete_ephemeral
taking space * as well.

> +    for k,_ in pairs(ephemeral_space) do
> +        ephemeral_space[k] = nil
> +    end

7. After drop any DML/DQL method should throw an error that the
space is deleted. You can do it via a separate metatable for example.
For example, like this:

dropped_mt = {
	__index = function() error('The space is dropped and can not be used') end
}

setmetatable(space, dropped_mt)

> diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc
> index ca3fefc..955c465 100644
> --- a/src/box/lua/space.cc
> +++ b/src/box/lua/space.cc
> @@ -33,6 +33,7 @@
>   #include "box/sql/sqliteLimit.h"
>   #include "lua/utils.h"
>   #include "lua/trigger.h"
> +#include "box/box.h"

8. Why?

>   
>   extern "C" {
>   	#include <lua.h>
> @@ -508,6 +511,169 @@ usage_error:
>   	return luaL_error(L, "Usage: space:frommap(map, opts)");
>   }
>   
> +/**
> + * Check if given argument have an appropriate type.
> + * @param Lua struct space *space - ephemeral space.
> + * @retval struct space *space - success.
> + * @retval NULL - error.
> + */
> +static inline struct space *
> +luaT_isspace(struct lua_State *L, int narg)

9. 'narg' is rather about argument count. Lets better use 'idx' like
in Lua sources. Same about functions below.

10. Kostja asked to describe parameters for Lua C function in the
same way as for internal C functions. So lets describe it somehow
like this:

@param L Lua stack to get space from.
@param idx Index by which to get the space from @a L.

Same about comments of other functions.

> +{
> +	uint32_t ctypeid = 0;
> +	void *data;

11. You do not need to announce 'void *data' before its
assignment.

> +
> +	if (lua_type(L, narg) != LUA_TCDATA)
> +		return NULL;
> +
> +	data = luaL_checkcdata(L, narg, &ctypeid);
> +	if (ctypeid != CTID_STRUCT_SPACE_POINTER)
> +		return NULL;
> +
> +	return *(struct space **) data;
> +}
> +
> +/**
> + * Puts space field of given argument in stack.

12. It puts, but why is it mentioned in the comment, if
you pop the space just after checking? It actually
just checks with no after-effects.

> + * onto Lua state stack and calls luaT_isspace
> + * @param Lua table with field space.
> + * @retval struct space *space - success.
> + * @retval NULL - error.

13. It does not return NULL.

> + */
> +static inline struct space *
> +lua_checkephemeralspace(struct lua_State *L, int narg)

14. Why do you need 'narg' here, if it is ignored in getfield
and isspace, and used for error message only?

> +{
> +	lua_getfield(L, 1, "space");
> +	struct space *space = luaT_isspace(L, -1);
> +	lua_pop(L, 1);
> +	if (space == NULL) {
> +		luaL_error(L, "Invalid argument #%d (ephemeral space expected,"\
> +			   "got %s)", narg, lua_typename(L, lua_type(L, narg)));
> +	}
> +	return space;
> +}
> +
> +/**
> + * Create an ephemeral space.
> + * @param Lua const char *engine_name - name of engine.
> + * @param Lua uint32_t field_count - number of fields.
> + * @param Lua const char *format - format in msgpack.
> + * @retval not nil - ephemeral space created.
> + * @retval nil - error, A reason is returned in
> + *         the second value.
> + */
> +static int
> +lbox_space_new_ephemeral(struct lua_State *L)
> +{
> +	uint32_t argc = lua_gettop(L);
> +	if (argc != 3)
> +		return luaL_error(L, "Error with creating ephemeral space");
> +	const char *engine_name = luaL_checkstring (L, 1);
> +	uint32_t exact_field_count = luaL_checknumber (L, 2);
> +	const char *format = luaL_checkstring (L, 3);
> +	struct region *region = &fiber()->gc;
> +	uint32_t field_count;
> +	struct field_def *fields = space_format_decode(format, &field_count,
> +		"ephemeral", strlen("ephemeral"), ER_CREATE_SPACE, region);

15. You do not check space_format_decode on error.

> +	if (exact_field_count != 0 &&
> +	    exact_field_count < field_count) {
> +		return luaL_error(L, "exact_field_count must be either 0 or"\
> +				  ">= formatted field count");
> +	}
> +	struct space_def *ephemeral_space_def =
> +		space_def_new(0, 0, exact_field_count, "ephemeral",
> +			      strlen("ephemeral"), engine_name,
> +			      strlen(engine_name), &space_opts_default, fields,
> +			      field_count);
> +	if (ephemeral_space_def == NULL)
> +		return luaL_error(L, "Error with creating space_def");

16. Not luaL_error. You should rethrow the tarantool error to Lua. See
luaT_error. Same about errors below.

> +	struct rlist key_list;
> +	rlist_create(&key_list);
> +	struct space *space = space_new_ephemeral(ephemeral_space_def,
> +						  &key_list);
> +	space_def_delete(ephemeral_space_def);
> +	if (space == NULL)
> +		return luaL_error(L, "Error with creating space");
> +	struct space **ptr =
> +		(struct space **) luaL_pushcdata(L, CTID_STRUCT_SPACE_POINTER);
> +	*ptr = space;
> +	return 1;

17. Space_format_decode produces garbage on region, that you should clean.

> +
> +/**
> + * Make a tuple or a table Lua object by map.
> + * @param Lua space object.
> + * @param Lua map table object.
> + * @param Lua opts table object (optional).
> + * @retval not nil A tuple or a table conforming to a space
> + *         format.
> + * @retval nil, err Can not built a tuple. A reason is returned in
> + *         the second value.
> + */
> +static int
> +lbox_space_frommap_ephemeral(struct lua_State *L)

18. This function is almost completely the same as lbox_space_frommap.
Please, remove code duplication.

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [tarantool-patches] Re: [PATCH 1/5] Create new methods for ephemeral spaces
  2018-07-12 11:16 ` [tarantool-patches] [PATCH 1/5] Create new methods for ephemeral spaces imeevma
@ 2018-07-13 16:32   ` Vladislav Shpilevoy
  0 siblings, 0 replies; 12+ messages in thread
From: Vladislav Shpilevoy @ 2018-07-13 16:32 UTC (permalink / raw)
  To: tarantool-patches, imeevma

Hello. Thanks for the patch! See 11 comments below.

On 12/07/2018 14:16, imeevma@tarantool.org wrote:
> Up to this patch any space had two additional methods
> that were methods of ephemeral spaces. In this patch
> these methods deleted from vtab and from now on
> ephemeral spaces are spaces with special vtab.
> 
> Part of #3375.
> ---
>   src/box/box.cc           | 108 ++++++++++++++++++++++++
>   src/box/box.h            |  42 ++++++++++
>   src/box/memtx_space.c    | 210 ++++++++++++++++++++++++++++++++++++++++-------
>   src/box/memtx_tree.c     |   5 ++
>   src/box/space.h          |  17 ----
>   src/box/sql.c            |  10 +--
>   src/box/sysview_engine.c |  22 -----
>   src/box/vinyl.c          |  22 -----
>   8 files changed, 339 insertions(+), 97 deletions(-)
> 
> diff --git a/src/box/box.cc b/src/box/box.cc
> index 481c2dd..795e3ee 100644
> --- a/src/box/box.cc
> +++ b/src/box/box.cc
> @@ -1170,6 +1170,114 @@ box_upsert(uint32_t space_id, uint32_t index_id, const char *tuple,
>   	return box_process1(&request, result);
>   }
>   
> +int
> +box_ephemeral_insert(struct space *space, const char *tuple,
> +		     const char *tuple_end, box_tuple_t **result)
> +{
> +	mp_tuple_assert(tuple, tuple_end);
> +	struct request request;
> +	memset(&request, 0, sizeof(request));
> +	request.type = IPROTO_INSERT;
> +	request.space_id = space->def->id;

1. Do you really need this space_id filling? As I understand,
ephemeral space id is always 0, so it was nullified already
in memset above.

2. Please, extract request initialization into separate
functions, that will be called from box_insert and box_ephemeral_insert.
Same for other functions.

> +	request.tuple = tuple;
> +	request.tuple_end = tuple_end;
> +	if(result != NULL)
> +		return space_execute_dml(space, NULL, &request, result);
> +	struct tuple *temp_result;
> +	return space_execute_dml(space, NULL, &request, &temp_result);

3. Insert returns the tuple too, so here temp_result leaks, it is not?
I am not sure, just a question.

4. Lets make this API similar to other box methods: result is mandatory
non-NULL argument.

All the same comments for other functions.

> diff --git a/src/box/box.h b/src/box/box.h
> index d879847..a00a842 100644
> --- a/src/box/box.h
> +++ b/src/box/box.h
> @@ -402,6 +402,48 @@ int
>   box_process1(struct request *request, box_tuple_t **result);
>   
>   /**
> + * Used to prepare request for inserting tuple into

5. Lets use imperative for function comment to respect the
code style.

> + * ephemeral space and call box_process_rw().

6. But actually it does not call box_process_rw()... Same
about functions below.

> diff --git a/src/box/memtx_space.c b/src/box/memtx_space.c
> index b94c4ab..e4781e3 100644
> --- a/src/box/memtx_space.c
> +++ b/src/box/memtx_space.c
> @@ -543,56 +543,51 @@ memtx_space_execute_upsert(struct space *space, struct txn *txn,
>   }
>   
>   /**
> - * This function simply creates new memtx tuple, refs it and calls space's
> - * replace function. In constrast to original memtx_space_execute_replace(), it
> - * doesn't handle any transaction routine.
> - * Ephemeral spaces shouldn't be involved in transaction routine, since
> - * they are used only for internal purposes. Moreover, ephemeral spaces
> - * can be created and destroyed within one transaction and rollback of already
> - * destroyed space may lead to undefined behaviour. For this reason it
> - * doesn't take txn as an argument.
> + * This function creates new memtx tuple, refs it and calls
> + * ephemeral space's replace function.
> + *
> + * This function isn't involved in any transaction routine.
>    */
>   static int
> -memtx_space_ephemeral_replace(struct space *space, const char *tuple,
> -				      const char *tuple_end)
> +memtx_space_ephemeral_replace(struct space *space, struct txn *txn,
> +			      struct request *request, struct tuple **result)
>   {
> +	(void)txn;

7. Please, add an assertion that txn == NULL. Same for other functions.

>   	struct memtx_space *memtx_space = (struct memtx_space *)space;
> -	struct tuple *new_tuple = memtx_tuple_new(space->format, tuple,
> -						  tuple_end);
> +	enum dup_replace_mode mode = dup_replace_mode(request->type);
> +	struct tuple *new_tuple = memtx_tuple_new(space->format, request->tuple,
> +						  request->tuple_end);
>   	if (new_tuple == NULL)
>   		return -1;
>   	tuple_ref(new_tuple);
>   	struct tuple *old_tuple = NULL;
>   	if (memtx_space->replace(space, old_tuple, new_tuple,
> -				 DUP_REPLACE_OR_INSERT, &old_tuple) != 0) {
> +				 mode, &old_tuple) != 0) {
>   		tuple_unref(new_tuple);
>   		return -1;
>   	}
> +	*result = new_tuple;
>   	if (old_tuple != NULL)
>   		tuple_unref(old_tuple);
>   	return 0;
>   }
> @@ -601,7 +596,138 @@ memtx_space_ephemeral_delete(struct space *space, const char *key)
>   	    memtx_space->replace(space, old_tuple, NULL,
>   				 DUP_REPLACE, &old_tuple) != 0)
>   		return -1;
> -	tuple_unref(old_tuple);
> +	*result = old_tuple;
> +	return 0;
> +}
> +
> +/**
> + * Replace tuple with given key from primary index of
> + * ephemeral space with new tuple.
> + *
> + * This function isn't involved in any transaction routine.
> + */
> +static int
> +memtx_space_ephemeral_update(struct space *space, struct txn *txn,
> +			     struct request *request, struct tuple **result)
> +{
> +	(void)txn;
> +	struct memtx_space *memtx_space = (struct memtx_space *)space;
> +	struct index *pk = space_index(space, 0 /* primary index*/);

8. Lets better get the pk the same way as in non-ephemeral space:
via index_find_unique. When we will add alter, there are non-unique
indexes too.

9. Lets extract all these DML routines into _impl() functions
and call them from ephemeral and real DML. There are too many code
duplication.

> @@ -943,8 +1066,6 @@ static const struct space_vtab memtx_space_vtab = {
>   	/* .execute_delete = */ memtx_space_execute_delete,
>   	/* .execute_update = */ memtx_space_execute_update,
>   	/* .execute_upsert = */ memtx_space_execute_upsert,
> -	/* .ephemeral_replace = */ memtx_space_ephemeral_replace,
> -	/* .ephemeral_delete = */ memtx_space_ephemeral_delete,
>   	/* .init_system_space = */ memtx_init_system_space,
>   	/* .init_ephemeral_space = */ memtx_init_ephemeral_space,
>   	/* .check_index_def = */ memtx_space_check_index_def,
> @@ -957,6 +1078,33 @@ static const struct space_vtab memtx_space_vtab = {
>   	/* .prepare_alter = */ memtx_space_prepare_alter,
>   };
>   
> +static const struct space_vtab memtx_space_ephemeral_vtab = {
> +	/* .destroy = */ memtx_space_destroy,
> +	/* .bsize = */ memtx_space_bsize,
> +	/* .apply_initial_join_row = */ memtx_space_apply_initial_join_row,
> +	/* .execute_replace = */ memtx_space_ephemeral_replace,
> +	/* .execute_delete = */ memtx_space_ephemeral_delete,
> +	/* .execute_update = */ memtx_space_ephemeral_update,
> +	/* .execute_upsert = */ memtx_space_ephemeral_upsert,
> +	/* .init_system_space = */ memtx_init_system_space,
> +	/* .init_ephemeral_space = */ memtx_init_ephemeral_space,

10. I do not think that we should allow to call ephemeral space
initialization from ephemeral space. It should be NULL or
implemented as diag_set(ER_UNSUPPORTED).

> +	/* .check_index_def = */ memtx_space_check_index_def,
> +	/* .create_index = */ memtx_space_create_index,
> +	/* .add_primary_key = */ memtx_space_add_primary_key,
> +	/* .drop_primary_key = */ memtx_space_drop_primary_key,
> +	/* .check_format  = */ memtx_space_check_format,
> +	/* .build_index = */ memtx_space_build_index,
> +	/* .swap_index = */ generic_space_swap_index,
> +	/* .prepare_alter = */ memtx_space_prepare_alter,
> +};
> diff --git a/src/box/memtx_tree.c b/src/box/memtx_tree.c
> index f851fb8..b0634f9 100644
> --- a/src/box/memtx_tree.c
> +++ b/src/box/memtx_tree.c
> @@ -461,6 +461,11 @@ memtx_tree_index_replace(struct index *base, struct tuple *old_tuple,
>   			memtx_tree_delete(&index->tree, new_tuple);
>   			if (dup_tuple)
>   				memtx_tree_insert(&index->tree, dup_tuple, 0);
> +			if (base->def->space_id == 0) {
> +				diag_set(ClientError, errcode, base->def->name,
> +					 "ephemeral");
> +				return -1;
> +			}

11. Why? Can not understand why do you check here for ephemerality, and
why is it bad.

>   			struct space *sp = space_cache_find(base->def->space_id);
>   			if (sp != NULL)
>   				diag_set(ClientError, errcode, base->def->name,

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [tarantool-patches] Re: [PATCH 4/5] Primary index for ephemeral spaces
  2018-07-12 11:16 ` [tarantool-patches] [PATCH 4/5] Primary index for ephemeral spaces imeevma
@ 2018-07-13 16:32   ` Vladislav Shpilevoy
  0 siblings, 0 replies; 12+ messages in thread
From: Vladislav Shpilevoy @ 2018-07-13 16:32 UTC (permalink / raw)
  To: tarantool-patches, imeevma

Thanks for the patch! See 18 comments below.

On 12/07/2018 14:16, imeevma@tarantool.org wrote:
> Functions for creation and deletion primary index
> of ephemeral space added.
> 
> Part of #3375.
> ---
>   src/box/index_def.c               |  42 +++++++
>   src/box/index_def.h               |  18 +++
>   src/box/lua/schema.lua            | 130 ++++++++++++++++++++++
>   src/box/lua/space.cc              |  92 ++++++++++++++++
>   test/box/ephemeral_space.result   | 225 ++++++++++++++++++++++++++++++++++++--
>   test/box/ephemeral_space.test.lua |  88 +++++++++++++--
>   test/engine/iterator.result       |   2 +-
>   7 files changed, 582 insertions(+), 15 deletions(-)
> 
> diff --git a/src/box/index_def.c b/src/box/index_def.c
> index e0d54c0..9622dce 100644
> --- a/src/box/index_def.c
> +++ b/src/box/index_def.c
> @@ -31,6 +31,8 @@
>   #include "index_def.h"
>   #include "schema_def.h"
>   #include "identifier.h"
> +#include "fiber.h"
> +#include "space.h"

1. Please, do not make index_def depend on space.
Instead of passing struct space to ephemeral_index_def use
directly field_def * array and field_count parameters.

2. Can you find a way how to do not duplicate this code
from alter.cc index_def_new_from_tuple?

>   
>   const char *index_type_strs[] = { "HASH", "TREE", "BITSET", "RTREE" };
>   
> @@ -369,3 +371,43 @@ index_opts_decode(struct index_opts *opts, const char *map,
>   	}
>   	return 0;
>   }
> +
> +struct index_def *
> +ephemeral_index_def(struct space *space, uint32_t index_id,

3. Respect the code style:

_new, _delete names for allocating, initialization and deletion.

> +		    const char *name, const char *type_field,
> +		    const char *opts_field, const char *parts)
> +{
> +	const struct field_def *fields = space->def->fields;
> +	uint32_t field_count = space->def->field_count;
> +	struct index_opts opts;
> +	struct key_def *key_def = NULL;
> +	uint32_t part_count = mp_decode_array(&parts);
> +	enum index_type type = STR2ENUM(index_type, type_field);
> +	if(index_opts_decode(&opts, opts_field, &fiber()->gc) != 0)

4. + white space after 'if'. I will not mention it anymore, but please,
find other places yourself.

> +		return NULL;
> +	if(identifier_check(name, strlen(name)) != 0)
> +		return NULL;
> +	struct key_part_def *part_def = (struct key_part_def *)
> +			malloc(sizeof(*part_def) * part_count);

5. Wrong alignment. Rather this:

	struct key_part_def *part_def =
		(struct key_part_def *) malloc(sizeof(*part_def) * part_count);

6. You can use region here and do not free it now, but later using
region_truncate. It reduces count of malloc-free oscillation.

> +	if (part_def == NULL) {
> +		diag_set(OutOfMemory, sizeof(*part_def) * part_count,
> +			 "malloc", "key_part_def");
> +		return NULL;
> +	}
> +	if (key_def_decode_parts(part_def, part_count, &parts,
> +				 fields, field_count) != 0) {
> +		free(part_def);
> +		return NULL;
> +	}
> +	key_def = key_def_new_with_parts(part_def, part_count);
> +	free(part_def);
> +	if (key_def == NULL)
> +		return NULL;
> +	struct index_def *index_def =
> +		index_def_new(0, index_id, name, strlen(name),
> +			      type, &opts, key_def, NULL);
> +	key_def_delete(key_def);
> +	if (index_def == NULL)
> +		return NULL;
> +	return index_def;

7. These 3 lines can be replaced with one 'return index_def'.

> +}
> diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
> index 81f0fa0..0fe109a 100644
> --- a/src/box/lua/schema.lua
> +++ b/src/box/lua/schema.lua
> @@ -527,6 +527,9 @@ box.schema.space.drop_ephemeral = function(ephemeral_space)
>           return
>       end
>       check_param(ephemeral_space.space, 'space', 'cdata')
> +    if ephemeral_space.index ~= nil and ephemeral_space.index[0] ~= nil then

8. How ephemeral_space.index can be nil after the check above (about
ephemeral_space.space == nil)?

> +        ephemeral_space.index[0]:drop()
> +    end
>       space_delete_ephemeral(ephemeral_space)
>       for k,_ in pairs(ephemeral_space) do
>           ephemeral_space[k] = nil
> @@ -1084,6 +1087,128 @@ ffi.metatype(iterator_t, {
>       end;
>   })
>   
> +local index_new_ephemeral = box.internal.space.index_new_ephemeral
> +box.internal.space.index_new_ephemeral = nil
> +local index_delete_ephemeral = box.internal.space.index_delete_ephemeral
> +box.internal.space.index_delete_ephemeral = nil
> +local index_ephemeral_methods = box.schema.index_ephemeral_methods
> +box.schema.index_ephemeral_mt = nil
> +local index_ephemeral_mt = {}
> +
> +local index_ephemeral_create = function(ephemeral_space, name, options)

9. Lets do the same API as for non-ephemeral spaces:
create_index, not index_create.

> +    if type(ephemeral_space) ~= 'table' then
> +        error("Usage: ephemeral_space:create_index(name, opts)")
> +    end
> +    check_param(name, 'name', 'string')
> +    check_param_table(options, create_index_template)
> +    local space = ephemeral_space
> +    local format = space:format()
> +
> +    local options_defaults = {
> +        type = 'tree',
> +    }
> +    options = update_param_table(options, options_defaults)
> +    local type_dependent_defaults = {
> +        rtree = {parts = { 2, 'array' }, unique = false},
> +        bitset = {parts = { 2, 'unsigned' }, unique = false},
> +        other = {parts = { 1, 'unsigned' }, unique = true},
> +    }
> +    options_defaults = type_dependent_defaults[options.type]
> +            or type_dependent_defaults.other
> +    if not options.parts then
> +        local fieldno = options_defaults.parts[1]
> +        if #format >= fieldno then
> +            local t = format[fieldno].type
> +            if t ~= 'any' then
> +                options.parts = {{fieldno, format[fieldno].type}}
> +            end
> +        end
> +    end
> +    options = update_param_table(options, options_defaults)
> +    if space.engine == 'vinyl' then> +        options_defaults = {
> +            page_size = box.cfg.vinyl_page_size,
> +            range_size = box.cfg.vinyl_range_size,
> +            run_count_per_level = box.cfg.vinyl_run_count_per_level,
> +            run_size_ratio = box.cfg.vinyl_run_size_ratio,
> +            bloom_fpr = box.cfg.vinyl_bloom_fpr
> +        }
> +    else
> +        options_defaults = {}
> +    end
> +    options = update_param_table(options, options_defaults)
> +
> +    if ephemeral_space.index ~= nil and ephemeral_space.index[0] then
> +        if options.if_not_exists then
> +            return space.index[0], "not created"
> +        else
> +            error("Primary index for this ephemeral index already exists")
> +        end
> +    end
> +    local iid = 0
> +    if options.id and options.id ~= 0 then
> +        error("Ephemeral space can have only primary index.")
> +    end
> +    local parts, parts_can_be_simplified =
> +        update_index_parts(format, options.parts)
> +    -- create_index() options contains type, parts, etc,
> +    -- stored separately. Remove these members from index_opts
> +    local index_opts = {
> +            dimension = options.dimension,
> +            unique = options.unique,
> +            distance = options.distance,
> +            page_size = options.page_size,
> +            range_size = options.range_size,
> +            run_count_per_level = options.run_count_per_level,
> +            run_size_ratio = options.run_size_ratio,
> +            bloom_fpr = options.bloom_fpr,
> +    }
> +    local field_type_aliases = {
> +        num = 'unsigned'; -- Deprecated since 1.7.2
> +        uint = 'unsigned';
> +        str = 'string';
> +        int = 'integer';
> +        ['*'] = 'any';
> +    };
> +    for _, part in pairs(parts) do
> +        local field_type = part.type:lower()
> +        part.type = field_type_aliases[field_type] or field_type
> +        if field_type == 'num' then
> +            log.warn("field type '%s' is deprecated since Tarantool 1.7, "..
> +                     "please use '%s' instead", field_type, part.type)
> +        end
> +    end
> +    if parts_can_be_simplified then
> +        parts = simplify_index_parts(parts)
> +    end

10. This function again is almost complete duplicate of schema.index.create.
Please, remove code duplication.

> +    space.space = index_new_ephemeral(space, iid, name, options.type,
> +                                      msgpack.encode(index_opts),
> +                                      msgpack.encode(parts))
> +    space.index[iid] = {}
> +    space.index[iid].unique = index_opts.unique or true
> +    space.index[iid].parts = parts
> +    space.index[iid].id = iid
> +    space.index[iid].name = name
> +    space.index[iid].type = type
> +    space.index[iid].options = options
> +    space.index[iid].space = space.space
> +    space.index[iid].ephemeral_space = space
> +    space.index[name] = space.index[iid]
> +    setmetatable(space.index[iid], index_ephemeral_mt)
> +    return space.index[name]
> +end
> +
> +local index_ephemeral_drop = function(ephemeral_index)
> +    if type(ephemeral_index) ~= 'table' then
> +        error("Usage: ephemeral_space.index[0]:drop()")
> +    end
> +    ephemeral_index.ephemeral_space.space = index_delete_ephemeral(ephemeral_index)
> +    ephemeral_index.ephemeral_space.index = {}
> +    for k,_ in pairs(ephemeral_index) do
> +        ephemeral_index[k] = nil
> +    end

11. It will not work, when we will allow multiple indexes. You should not
delete and nullify all indexes when a one is deleted.

> +end
> +
>   local iterator_gen = function(param, state)
>       --[[
>           index:pairs() mostly conforms to the Lua for-in loop conventions and
> diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc
> index 955c465..4374db9 100644
> --- a/src/box/lua/space.cc
> +++ b/src/box/lua/space.cc
> @@ -34,6 +34,7 @@
>   #include "lua/utils.h"
>   #include "lua/trigger.h"
>   #include "box/box.h"
> +#include "box/lua/misc.h" /* lbox_encode_tuple_on_gc() */

12. Why? Please, go through your patches and check for unused
includes. I will not mention it anymore.

>   
>   extern "C" {
>   	#include <lua.h>
> @@ -616,6 +617,93 @@ lbox_space_delete_ephemeral(struct lua_State *L)
>   }
>   
>   /**
> + * Create an index for ephemeral space.
> + * @param Lua table - ephemeral space.
> + * @param Lua uint32_t id - id of index, for
> + * ephemeral spaces it is 0 as they can have only
> + * primary index.
> + * @param Lua const char *name - name of index.
> + * @param Lua const char *type_field - type of field.
> + * @param Lua const char *opts_field - options in
> + * msgpack format.
> + * @param Lua const char *parts - parts in msgpack
> + * format.
> + * @retval not nil - ephemeral space created.
> + * @retval nil - error, A reason is returned in
> + *         the second value.
> + */
> +static int
> +lbox_index_new_ephemeral(struct lua_State *L)
> +{
> +	uint32_t argc = lua_gettop(L);
> +	if (argc != 6)
> +		return luaL_error(L, "Using: ephemeral:create_index(opts");
> +	struct space *space = (struct space *)lua_checkephemeralspace(L, 1);
> +	uint32_t index_id = luaL_checknumber (L, 2);
> +	const char *name = luaL_checkstring (L, 3);
> +	const char *type_field = luaL_checkstring (L, 4);
> +	const char *opts_field = luaL_checkstring (L, 5);
> +	const char *parts = luaL_checkstring (L, 6);
> +	assert(index_id == 0);
> +	struct space_def *ephemeral_space_def = space_def_dup(space->def);

13. Please, remove '_ephemeral' suffix from variable names. They are too
long and it is hard to read this Java/C#-like code.

> +	if (ephemeral_space_def == NULL)
> +		return luaL_error(L, "Error with creating space_def");

14. Same as earlier - do not override Tarantool error with Lua one.
Use luaT_error. I will not mention it anymore, so, please, found and
fix other places.

> +	struct index_def *index_def_ephemeral =
> +		ephemeral_index_def(space, index_id, name, type_field,
> +				    opts_field, parts);
> +	if(index_def_ephemeral == NULL)
> +		return luaT_error(L);
> +	if(!index_def_is_valid(index_def_ephemeral, space_name(space)) ||
> +	   space_check_index_def(space, index_def_ephemeral) != 0) {
> +		index_def_delete(index_def_ephemeral);
> +		return luaT_error(L);
> +	}
> +	struct rlist key_list;
> +	rlist_create(&key_list);
> +	rlist_add_entry(&key_list, index_def_ephemeral, link);
> +	struct space *new_space = space_new_ephemeral(ephemeral_space_def,
> +						      &key_list);
> +	index_def_delete(index_def_ephemeral);
> +	space_def_delete(ephemeral_space_def);
> +	if(new_space == NULL) {
> +		return luaL_error(L, "Error with creating index for "\
> +				     "ephemeral space");
> +	}
> +	space_delete(space);
> +	struct space **ptr =
> +		(struct space **) luaL_pushcdata(L, CTID_STRUCT_SPACE_POINTER);
> +	*ptr = new_space;
> +	return 1;
> +}
> diff --git a/test/box/ephemeral_space.result b/test/box/ephemeral_space.result
> index 135df3c..d958ffe 100644
> --- a/test/box/ephemeral_space.result
> +++ b/test/box/ephemeral_space.result
> @@ -1,5 +1,5 @@
> --- Ephemeral space: creation and dropping
> --- Simple creation
> +-- Ephemeral space: creation and dropping.
> +-- Simple creation.

15. These spelling fixes should not be part of this patch.

>   s = box.schema.space.create_ephemeral()
>   ---
>   ...
> @@ -185,7 +185,218 @@ s
>   ---
>   - []
>   ...
> --- Ephemeral space: methods
> +-- Ephemeral space: index creation and dropping.
> +s = box.schema.space.create_ephemeral()
> +---
> +...
> +-- Simple creation
> +i = s:create_index('a')
> +---
> +...
> +i.unique
> +---
> +- true
> +...
> +i.parts
> +---
> +- - - 0
> +    - unsigned
> +...
> +i.id
> +---
> +- 0
> +...
> +i.name
> +---
> +- a
> +...
> +i:drop()
> +---
> +...
> +-- Double creation of index for ephemeral space.
> +i = s:create_index('a')
> +---
> +...
> +i = s:create_index('a')
> +---
> +- error: 'builtin/box/schema.lua:1145: Primary index for this ephemeral index already
> +    exists'

16. Please, use diag_set and ER_UNSUPPORTED for this type of errors.

> +...
> +i:drop()
> +---
> +...
> +-- Double creation of index for ephemeral space
> +-- but with if_not_exists=true.
> +i = s:create_index('a')
> +---
> +...
> +i = s:create_index('a', {if_not_exists=true})
> +---
> +...
> +i:drop()
> +---
> +...
> +-- Ephemeral space can have only primary.
> +-- index so its id == 0
> +i = s:create_index('a', {id = 10})
> +---
> +- error: 'builtin/box/schema.lua:1150: Ephemeral space can have only primary index.'
> +...
> +-- Set parameters: parts.
> +i = s:create_index('a', {parts={1}})
> +---
> +...
> +i.unique
> +---
> +- true

17. It is enough to just print the entire 'i'. Just write it alone on
the line. And it is not necessary to output the entire 'i' on each
'create_index'. Print just the attributes you have specified in the
options.

> +---
> +...
> +i = s:create_index('a', {parts={1,'uint',2,'int',3,'str'}})

18. Please, do not use 'uint' and 'str' - they are deprecated.

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [tarantool-patches] Re: [PATCH 2/5] Move some decode functions from alter.cc
  2018-07-12 11:16 ` [tarantool-patches] [PATCH 2/5] Move some decode functions from alter.cc imeevma
@ 2018-07-13 16:32   ` Vladislav Shpilevoy
  0 siblings, 0 replies; 12+ messages in thread
From: Vladislav Shpilevoy @ 2018-07-13 16:32 UTC (permalink / raw)
  To: tarantool-patches, imeevma

Thanks for the patch! See 7 comments below.

On 12/07/2018 14:16, imeevma@tarantool.org wrote:
> Allow to use some format and options decode functions
> outside of alter.cc.
> 
> Part of #3375.
> ---
>   src/box/alter.cc    | 203 ++--------------------------------------------------
>   src/box/index_def.c |  60 ++++++++++++++++
>   src/box/index_def.h |   8 +++
>   src/box/space_def.c | 150 ++++++++++++++++++++++++++++++++++++++
>   src/box/space_def.h |  15 ++++
>   5 files changed, 237 insertions(+), 199 deletions(-)
> 
> diff --git a/src/box/alter.cc b/src/box/alter.cc
> index 7b6bd1a..15240c0 100644
> --- a/src/box/alter.cc
> +++ b/src/box/alter.cc
> @@ -250,7 +195,8 @@ index_def_new_from_tuple(struct tuple *tuple, struct space *space)
>   	const char *opts_field =
>   		tuple_field_with_type_xc(tuple, BOX_INDEX_FIELD_OPTS,
>   					 MP_MAP);
> -	index_opts_decode(&opts, opts_field, &fiber()->gc);
> +	if(index_opts_decode(&opts, opts_field, &fiber()->gc) != 0)

1. Please, put white space after 'if'.

> +		diag_raise();
>   	const char *parts = tuple_field(tuple, BOX_INDEX_FIELD_PARTS);
>   	uint32_t part_count = mp_decode_array(&parts);
>   	if (name_len > BOX_NAME_MAX) {
> @@ -510,6 +313,8 @@ space_def_new_from_tuple(struct tuple *tuple, uint32_t errcode,
>   					 MP_ARRAY);
>   	fields = space_format_decode(format, &field_count, name,
>   				     name_len, errcode, region);
> +	if (field_count != 0 && fields == NULL)
> +		diag_raise();

2. You should not check more than 1 thing to test an error. When
a function can return NULL both on success and on error, you should
move the result into out parameter and return int: 0 on success, -1
on error.

>   	auto fields_guard = make_scoped_guard([=] {
>   		space_def_destroy_fields(fields, field_count);
>   	});
> diff --git a/src/box/space_def.c b/src/box/space_def.c
> index f5ca0b5..3b31727 100644
> --- a/src/box/space_def.c
> +++ b/src/box/space_def.c
> @@ -34,6 +34,10 @@
>   #include "error.h"
>   #include "sql.h"
>   #include "msgpuck.h"
> +#include "tuple_format.h"
> +#include "schema_def.h"
> +#include "identifier.h"
> +#include "small/region.h"

3. Can you please investigate is it possible to remove some of
these headers from alter.cc?

>   
>   /**
>    * Make checks from msgpack.
> @@ -351,3 +355,149 @@ error:
> +
> +struct field_def *
> +space_format_decode(const char *data, uint32_t *out_count,
> +		    const char *space_name, uint32_t name_len,
> +		    uint32_t errcode, struct region *region)
> +{
> +	/* Type is checked by _space format. */
> +	assert(mp_typeof(*data) == MP_ARRAY);
> +	uint32_t count = mp_decode_array(&data);
> +	*out_count = count;
> +	if (count == 0)
> +		return NULL;
> +	size_t size = count * sizeof(struct field_def);
> +	struct field_def *region_defs =
> +		(struct field_def *) region_alloc(region, size);
> +	if(region_defs == NULL) {

4. + white space after 'if'.

> +		diag_set(OutOfMemory, size, "region", "new slab");

5. Please, see what arguments OutOfMemory takes:

     size_t amount, const char *allocator,
     const char *object

You should specify here "region_alloc" instead of "region", and
"region_defs" instead of "new slab".

> +		return NULL;
> +	}
> +	/*
> +	 * Nullify to prevent a case when decoding will fail in
> +	 * the middle and space_def_destroy_fields() below will
> +	 * work with garbage pointers.
> +	 */
> +	memset(region_defs, 0, size);
> +	for (uint32_t i = 0; i < count; ++i) {
> +		if(field_def_decode(&region_defs[i], &data, space_name,
> +			name_len, errcode, i, region) != 0) {

6. Broken alignment.

> +			space_def_destroy_fields(region_defs, count);
> +			return NULL;
> +		}
> +	}
> +	return region_defs;
> +}
> diff --git a/src/box/space_def.h b/src/box/space_def.h
> index 0d1e902..16188c6 100644
> --- a/src/box/space_def.h
> +++ b/src/box/space_def.h
> @@ -182,6 +182,21 @@ space_def_sizeof(uint32_t name_len, const struct field_def *fields,
>   		 uint32_t field_count, uint32_t *names_offset,
>   		 uint32_t *fields_offset, uint32_t *def_expr_offset);
>   
> +/**
> + * Decode MessagePack array of fields.
> + * @param data MessagePack array of fields.
> + * @param[out] out_count Length of a result array.
> + * @param space_name Space name to use in error messages.

7. Missing 'name_len' parameter.

> + * @param errcode Errcode for client errors.
> + * @param region Region to allocate result array.
> + *
> + * @retval Array of fields.
> + */
> +struct field_def *
> +space_format_decode(const char *data, uint32_t *out_count,
> +		    const char *space_name, uint32_t name_len,
> +		    uint32_t errcode, struct region *region);
> +
>   #if defined(__cplusplus)
>   } /* extern "C" */
>   
> 

^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2018-07-13 16:32 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-07-12 11:16 [tarantool-patches] [PATCH 0/5] Expose ephemeral spaces into Lua imeevma
2018-07-12 11:16 ` [tarantool-patches] [PATCH 1/5] Create new methods for ephemeral spaces imeevma
2018-07-13 16:32   ` [tarantool-patches] " Vladislav Shpilevoy
2018-07-12 11:16 ` [tarantool-patches] [PATCH 2/5] Move some decode functions from alter.cc imeevma
2018-07-13 16:32   ` [tarantool-patches] " Vladislav Shpilevoy
2018-07-12 11:16 ` [tarantool-patches] [PATCH 3/5] Ephemeral space creation and deletion in Lua imeevma
2018-07-13 16:32   ` [tarantool-patches] " Vladislav Shpilevoy
2018-07-12 11:16 ` [tarantool-patches] [PATCH 4/5] Primary index for ephemeral spaces imeevma
2018-07-13 16:32   ` [tarantool-patches] " Vladislav Shpilevoy
2018-07-12 11:16 ` [tarantool-patches] [PATCH 5/5] Methods for ephemeral space and its index imeevma
2018-07-13 16:32   ` [tarantool-patches] " Vladislav Shpilevoy
2018-07-12 11:30 ` [tarantool-patches] Re: [PATCH 0/5] Expose ephemeral spaces into Lua Imeev Mergen

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox