[PATCH 7/7] vinyl: implement multikey index support

Vladimir Davydov vdavydov.dev at gmail.com
Wed May 8 20:22:39 MSK 2019


In case of multikey indexes, we use vy_entry.hint to store multikey
array entry index instead of a comparison hint. So all we need to do is
patch all places where a statement is inserted so that in case the key
definition is multikey we iterate over all multikey indexes and insert
an entry for each of them. The rest will be done automatically as vinyl
stores and compares vy_entry objects, which have hints built-in, while
comparators and other generic functions have already been patched to
treat hints as multikey indexes.

There are just a few places we need to patch:

 - vy_tx_set, which inserts a statement into a transaction write set.
 - vy_build_insert_stmt, which is used to fill the new index on index
   creation and DDL recovery.
 - vy_build_on_replace, which forwards modifications done to the space
   during index creation to the new index.
 - vy_check_is_unique_secondary, which checks a secondary index for
   conflicts on insertion of a new statement.
 - vy_tx_handle_deferred_delete, which generates deferred DELETE
   statements if the old tuple is found in memory or in cache.
 - vy_deferred_delete_on_replace, which applies deferred DELETEs on
   compaction.

Plus, we need to teach vy_get_by_secondary_tuple to match a full
multikey tuple to a partial multikey tuple or a key, which implies
iterating over all multikey indexes of the full tuple and comparing
them to the corresponding entries to the partial tuple.

We already have tests that check the functionality for memtx. Enable and
tweak it a little so that it can be used for vinyl as well.
---
 src/box/vinyl.c               | 143 ++++++++++++++++++++++++------------------
 src/box/vy_stmt.h             |  25 ++++++++
 src/box/vy_tx.c               |  43 +++++++++----
 src/box/vy_tx.h               |  19 +-----
 test/box/bitset.result        |  15 +++++
 test/box/bitset.test.lua      |   6 ++
 test/box/hash.result          |  15 +++++
 test/box/hash.test.lua        |   6 ++
 test/box/rtree_misc.result    |  15 +++++
 test/box/rtree_misc.test.lua  |   6 ++
 test/engine/engine.cfg        |   3 -
 test/engine/multikey.result   |  40 +-----------
 test/engine/multikey.test.lua |  16 +----
 13 files changed, 209 insertions(+), 143 deletions(-)

diff --git a/src/box/vinyl.c b/src/box/vinyl.c
index eddbdbed..5be10d59 100644
--- a/src/box/vinyl.c
+++ b/src/box/vinyl.c
@@ -701,11 +701,6 @@ vinyl_space_check_index_def(struct space *space, struct index_def *index_def)
 			return -1;
 		}
 	}
-	if (key_def_is_multikey(index_def->key_def)) {
-		diag_set(ClientError, ER_UNSUPPORTED,
-			 "Vinyl", "multikey indexes");
-		return -1;
-	}
 	return 0;
 }
 
@@ -1355,19 +1350,24 @@ vy_get_by_secondary_tuple(struct vy_lsm *lsm, struct vy_tx *tx,
 
 	lsm->pk->stat.lookup++;
 
-	if (vy_point_lookup(lsm->pk, tx, rv, key, result) != 0) {
+	struct vy_entry pk_entry;
+	if (vy_point_lookup(lsm->pk, tx, rv, key, &pk_entry) != 0) {
 		rc = -1;
 		goto out;
 	}
 
-	/*
-	 * Note, result stores a hint computed for the primary
-	 * index while entry was read from a secondary index so
-	 * we must not use vy_entry_compare() here.
-	 */
-	if (result->stmt == NULL ||
-	    vy_stmt_compare(result->stmt, HINT_NONE, entry.stmt,
-			    HINT_NONE, lsm->cmp_def) != 0) {
+	bool match = false;
+	struct vy_entry full_entry;
+	if (pk_entry.stmt != NULL) {
+		vy_stmt_foreach_entry(full_entry, pk_entry.stmt, lsm->cmp_def) {
+			if (vy_entry_compare(full_entry, entry,
+					     lsm->cmp_def) == 0) {
+				match = true;
+				break;
+			}
+		}
+	}
+	if (!match) {
 		/*
 		 * If a tuple read from a secondary index doesn't
 		 * match the tuple corresponding to it in the
@@ -1377,11 +1377,10 @@ vy_get_by_secondary_tuple(struct vy_lsm *lsm, struct vy_tx *tx,
 		 * case silently skip this tuple.
 		 */
 		vy_stmt_counter_acct_tuple(&lsm->stat.skip, entry.stmt);
-		if (result->stmt != NULL) {
+		if (pk_entry.stmt != NULL) {
 			vy_stmt_counter_acct_tuple(&lsm->pk->stat.skip,
-						   result->stmt);
-			tuple_unref(result->stmt);
-			*result = vy_entry_none();
+						   pk_entry.stmt);
+			tuple_unref(pk_entry.stmt);
 		}
 		/*
 		 * We must purge stale tuples from the cache before
@@ -1390,6 +1389,7 @@ vy_get_by_secondary_tuple(struct vy_lsm *lsm, struct vy_tx *tx,
 		 * the tuple cache implementation.
 		 */
 		vy_cache_on_write(&lsm->cache, entry, NULL);
+		*result = vy_entry_none();
 		goto out;
 	}
 
@@ -1401,21 +1401,19 @@ vy_get_by_secondary_tuple(struct vy_lsm *lsm, struct vy_tx *tx,
 	 * the DELETE statement is not written to secondary indexes
 	 * immediately.
 	 */
-	if (tx != NULL && vy_tx_track_point(tx, lsm->pk, *result) != 0) {
-		tuple_unref(result->stmt);
+	if (tx != NULL && vy_tx_track_point(tx, lsm->pk, pk_entry) != 0) {
+		tuple_unref(pk_entry.stmt);
 		rc = -1;
 		goto out;
 	}
 
 	if ((*rv)->vlsn == INT64_MAX) {
-		vy_cache_add(&lsm->pk->cache, *result,
+		vy_cache_add(&lsm->pk->cache, pk_entry,
 			     vy_entry_none(), key, ITER_EQ);
 	}
 
-	/* Inherit the hint from the secondary index entry. */
-	result->hint = entry.hint;
-
-	vy_stmt_counter_acct_tuple(&lsm->pk->stat.get, result->stmt);
+	vy_stmt_counter_acct_tuple(&lsm->pk->stat.get, pk_entry.stmt);
+	*result = full_entry;
 out:
 	tuple_unref(key.stmt);
 	return rc;
@@ -1576,35 +1574,22 @@ vy_check_is_unique_primary(struct vy_tx *tx, const struct vy_read_view **rv,
 	return 0;
 }
 
-/**
- * Check if insertion of a new tuple violates unique constraint
- * of a secondary index.
- * @param tx         Current transaction.
- * @param rv         Read view.
- * @param space_name Space name.
- * @param index_name Index name.
- * @param lsm        LSM tree corresponding to the index.
- * @param stmt       New tuple.
- *
- * @retval  0 Success, unique constraint is satisfied.
- * @retval -1 Duplicate is found or read error occurred.
- */
 static int
-vy_check_is_unique_secondary(struct vy_tx *tx, const struct vy_read_view **rv,
-			     const char *space_name, const char *index_name,
-			     struct vy_lsm *lsm, struct tuple *stmt)
+vy_check_is_unique_secondary_one(struct vy_tx *tx, const struct vy_read_view **rv,
+				 const char *space_name, const char *index_name,
+				 struct vy_lsm *lsm, struct tuple *stmt,
+				 int multikey_idx)
 {
 	assert(lsm->index_id > 0);
 	assert(vy_stmt_type(stmt) == IPROTO_INSERT ||
 	       vy_stmt_type(stmt) == IPROTO_REPLACE);
 
-	if (!lsm->check_is_unique)
-		return 0;
 	if (lsm->key_def->is_nullable &&
-	    tuple_key_contains_null(stmt, lsm->key_def, -1))
+	    tuple_key_contains_null(stmt, lsm->key_def, multikey_idx))
 		return 0;
 	struct tuple *key = vy_stmt_extract_key(stmt, lsm->key_def,
-						lsm->env->key_format, -1);
+						lsm->env->key_format,
+						multikey_idx);
 	if (key == NULL)
 		return -1;
 	struct tuple *found;
@@ -1639,6 +1624,39 @@ vy_check_is_unique_secondary(struct vy_tx *tx, const struct vy_read_view **rv,
 
 /**
  * Check if insertion of a new tuple violates unique constraint
+ * of a secondary index.
+ * @param tx         Current transaction.
+ * @param rv         Read view.
+ * @param space_name Space name.
+ * @param index_name Index name.
+ * @param lsm        LSM tree corresponding to the index.
+ * @param stmt       New tuple.
+ *
+ * @retval  0 Success, unique constraint is satisfied.
+ * @retval -1 Duplicate is found or read error occurred.
+ */
+static int
+vy_check_is_unique_secondary(struct vy_tx *tx, const struct vy_read_view **rv,
+			     const char *space_name, const char *index_name,
+			     struct vy_lsm *lsm, struct tuple *stmt)
+{
+	if (!lsm->check_is_unique)
+		return 0;
+	if (!key_def_is_multikey(lsm->cmp_def)) {
+		return vy_check_is_unique_secondary_one(tx, rv,
+				space_name, index_name, lsm, stmt, -1);
+	}
+	int count = tuple_multikey_count(stmt, lsm->cmp_def);
+	for (int i = 0; i < count; ++i) {
+		if (vy_check_is_unique_secondary_one(tx, rv,
+				space_name, index_name, lsm, stmt, i) != 0)
+			return -1;
+	}
+	return 0;
+}
+
+/**
+ * Check if insertion of a new tuple violates unique constraint
  * of any index of the space.
  * @param env          Vinyl environment.
  * @param tx           Current transaction.
@@ -4063,11 +4081,10 @@ vy_build_on_replace(struct trigger *trigger, void *event)
 
 	/* Forward the statement to the new LSM tree. */
 	if (stmt->old_tuple != NULL) {
-		struct tuple *delete = vy_stmt_extract_key(stmt->old_tuple,
-					lsm->cmp_def, lsm->env->key_format, -1);
+		struct tuple *delete = vy_stmt_new_surrogate_delete(format,
+							stmt->old_tuple);
 		if (delete == NULL)
 			goto err;
-		vy_stmt_set_type(delete, IPROTO_DELETE);
 		int rc = vy_tx_set(tx, lsm, delete);
 		tuple_unref(delete);
 		if (rc != 0)
@@ -4114,12 +4131,13 @@ vy_build_insert_stmt(struct vy_lsm *lsm, struct vy_mem *mem,
 		return -1;
 	vy_stmt_set_lsn(region_stmt, lsn);
 	struct vy_entry entry;
-	entry.stmt = region_stmt;
-	entry.hint = vy_stmt_hint(region_stmt, lsm->cmp_def);
-	if (vy_mem_insert(mem, entry) != 0)
-		return -1;
-	vy_mem_commit_stmt(mem, entry);
-	vy_stmt_counter_acct_tuple(&lsm->stat.memory.count, region_stmt);
+	vy_stmt_foreach_entry(entry, region_stmt, lsm->cmp_def) {
+		if (vy_mem_insert(mem, entry) != 0)
+			return -1;
+		vy_mem_commit_stmt(mem, entry);
+		vy_stmt_counter_acct_tuple(&lsm->stat.memory.count,
+					   region_stmt);
+	}
 	return 0;
 }
 
@@ -4218,11 +4236,10 @@ vy_build_recover_stmt(struct vy_lsm *lsm, struct vy_lsm *pk,
 	struct tuple *insert = NULL;
 	struct tuple *old_tuple = old.stmt;
 	if (old_tuple != NULL) {
-		delete = vy_stmt_extract_key(old_tuple, lsm->cmp_def,
-					     lsm->env->key_format, -1);
+		delete = vy_stmt_new_surrogate_delete(lsm->mem_format,
+						      old_tuple);
 		if (delete == NULL)
 			return -1;
-		vy_stmt_set_type(delete, IPROTO_DELETE);
 	}
 	enum iproto_type type = vy_stmt_type(mem_stmt);
 	if (type == IPROTO_REPLACE || type == IPROTO_INSERT) {
@@ -4578,13 +4595,15 @@ vy_deferred_delete_on_replace(struct trigger *trigger, void *event)
 			mem = lsm->mem;
 		}
 		struct vy_entry entry;
-		entry.stmt = delete;
-		entry.hint = vy_stmt_hint(delete, lsm->cmp_def);
-		rc = vy_lsm_set(lsm, mem, entry, &region_stmt);
+		vy_stmt_foreach_entry(entry, delete, lsm->cmp_def) {
+			rc = vy_lsm_set(lsm, mem, entry, &region_stmt);
+			if (rc != 0)
+				break;
+			entry.stmt = region_stmt;
+			vy_lsm_commit_stmt(lsm, mem, entry);
+		}
 		if (rc != 0)
 			break;
-		entry.stmt = region_stmt;
-		vy_lsm_commit_stmt(lsm, mem, entry);
 
 		if (!is_first_statement)
 			continue;
diff --git a/src/box/vy_stmt.h b/src/box/vy_stmt.h
index b0731d3d..0026d614 100644
--- a/src/box/vy_stmt.h
+++ b/src/box/vy_stmt.h
@@ -750,6 +750,31 @@ vy_entry_compare_with_raw_key(struct vy_entry entry,
 					    key, key_hint, key_def);
 }
 
+/**
+ * Iterate over each key indexed in the given statement.
+ * @param entry    loop variable
+ * @param src_stmt source statement
+ * @param key_def  key definition
+ *
+ * For a multikey index, entry.hint is set to multikey entry offset
+ * and the loop iterates over each offset stored in the statement.
+ *
+ * For a unikey index, entry.hint is initialized with vy_stmt_hint()
+ * and the loop breaks after the first iteration.
+ *
+ * entry.stmt is set to src_stmt on each iteration.
+ */
+#define vy_stmt_foreach_entry(entry, src_stmt, key_def)			\
+	for (uint32_t multikey_idx = 0,					\
+	     multikey_count = !key_def_is_multikey((key_def)) ? 1 :	\
+			tuple_multikey_count((src_stmt), (key_def));	\
+	     multikey_idx < multikey_count &&				\
+	     (((entry).stmt = (src_stmt)),				\
+	      ((entry).hint = !key_def_is_multikey((key_def)) ?		\
+			vy_stmt_hint((src_stmt), (key_def)) :		\
+			multikey_idx), true);				\
+	     ++multikey_idx)
+
 #if defined(__cplusplus)
 } /* extern "C" */
 #endif /* defined(__cplusplus) */
diff --git a/src/box/vy_tx.c b/src/box/vy_tx.c
index 92d7c2a1..b1dee0fa 100644
--- a/src/box/vy_tx.c
+++ b/src/box/vy_tx.c
@@ -629,17 +629,21 @@ vy_tx_handle_deferred_delete(struct vy_tx *tx, struct txv *v)
 	int rc = 0;
 	for (uint32_t i = 1; i < space->index_count; i++) {
 		struct vy_lsm *lsm = vy_lsm(space->index[i]);
-		struct vy_entry delete_entry;
-		delete_entry.stmt = delete_stmt;
-		delete_entry.hint = vy_stmt_hint(delete_stmt, lsm->cmp_def);
-		struct txv *delete_txv = txv_new(tx, lsm, delete_entry,
-						 UINT64_MAX);
-		if (delete_txv == NULL) {
-			rc = -1;
+		struct vy_entry entry;
+		vy_stmt_foreach_entry(entry, delete_stmt, lsm->cmp_def) {
+			struct txv *delete_txv = txv_new(tx, lsm, entry,
+							 UINT64_MAX);
+			if (delete_txv == NULL) {
+				rc = -1;
+				break;
+			}
+			stailq_insert_entry(&tx->log, delete_txv, v,
+					    next_in_log);
+			vy_stmt_counter_acct_tuple(&lsm->stat.txw.count,
+						   entry.stmt);
+		}
+		if (rc != 0)
 			break;
-		}
-		stailq_insert_entry(&tx->log, delete_txv, v, next_in_log);
-		vy_stmt_counter_acct_tuple(&lsm->stat.txw.count, delete_stmt);
 	}
 	tuple_unref(delete_stmt);
 	return rc;
@@ -1028,7 +1032,12 @@ vy_tx_track_point(struct vy_tx *tx, struct vy_lsm *lsm, struct vy_entry entry)
 	return vy_tx_track(tx, lsm, entry, true, entry, true);
 }
 
-int
+/**
+ * Add one statement entry to a transaction. We add one entry
+ * for each index, and with multikey indexes it is possible there
+ * are multiple entries of a single statement in a single index.
+ */
+static int
 vy_tx_set_entry(struct vy_tx *tx, struct vy_lsm *lsm,
 		struct vy_entry entry, uint64_t column_mask)
 {
@@ -1123,6 +1132,18 @@ vy_tx_set_entry(struct vy_tx *tx, struct vy_lsm *lsm,
 	return 0;
 }
 
+int
+vy_tx_set_with_colmask(struct vy_tx *tx, struct vy_lsm *lsm,
+		       struct tuple *stmt, uint64_t column_mask)
+{
+	struct vy_entry entry;
+	vy_stmt_foreach_entry(entry, stmt, lsm->cmp_def) {
+		if (vy_tx_set_entry(tx, lsm, entry, column_mask) != 0)
+			return -1;
+	}
+	return 0;
+}
+
 void
 tx_manager_abort_writers_for_ddl(struct tx_manager *xm, struct space *space)
 {
diff --git a/src/box/vy_tx.h b/src/box/vy_tx.h
index a5c0856d..2712263b 100644
--- a/src/box/vy_tx.h
+++ b/src/box/vy_tx.h
@@ -393,15 +393,6 @@ int
 vy_tx_track_point(struct vy_tx *tx, struct vy_lsm *lsm, struct vy_entry entry);
 
 /**
- * Add one statement entry to a transaction. We add one entry
- * for each index, and with multikey indexes it is possible there
- * are multiple entries of a single statement in a single index.
- */
-int
-vy_tx_set_entry(struct vy_tx *tx, struct vy_lsm *lsm,
-		struct vy_entry entry, uint64_t column_mask);
-
-/**
  * Insert a statement into a transaction write set.
  * @param tx           Transaction.
  * @param lsm          LSM tree the statement is for.
@@ -411,15 +402,9 @@ vy_tx_set_entry(struct vy_tx *tx, struct vy_lsm *lsm,
  * @retval  0 Success
  * @retval -1 Memory allocation error.
  */
-static inline int
+int
 vy_tx_set_with_colmask(struct vy_tx *tx, struct vy_lsm *lsm,
-		       struct tuple *stmt, uint64_t column_mask)
-{
-	struct vy_entry entry;
-	entry.stmt = stmt;
-	entry.hint = vy_stmt_hint(stmt, lsm->cmp_def);
-	return vy_tx_set_entry(tx, lsm, entry, column_mask);
-}
+		       struct tuple *stmt, uint64_t column_mask);
 
 static inline int
 vy_tx_set(struct vy_tx *tx, struct vy_lsm *lsm, struct tuple *stmt)
diff --git a/test/box/bitset.result b/test/box/bitset.result
index a06207df..78f74ec3 100644
--- a/test/box/bitset.result
+++ b/test/box/bitset.result
@@ -1981,3 +1981,18 @@ s:drop()
 s = nil
 ---
 ...
+-- Bitset index cannot be multikey.
+s = box.schema.space.create('test')
+---
+...
+_ = s:create_index('primary')
+---
+...
+_ = s:create_index('bitset', {type = 'bitset', parts = {{'[2][*]', 'unsigned'}}})
+---
+- error: 'Can''t create or modify index ''bitset'' in space ''test'': BITSET index
+    cannot be multikey'
+...
+s:drop()
+---
+...
diff --git a/test/box/bitset.test.lua b/test/box/bitset.test.lua
index d9760e27..eb013a1c 100644
--- a/test/box/bitset.test.lua
+++ b/test/box/bitset.test.lua
@@ -147,3 +147,9 @@ for j=1,100 do check(math.random(9) - 1, {iterator = box.index.BITS_ALL_NOT_SET}
 good
 s:drop()
 s = nil
+
+-- Bitset index cannot be multikey.
+s = box.schema.space.create('test')
+_ = s:create_index('primary')
+_ = s:create_index('bitset', {type = 'bitset', parts = {{'[2][*]', 'unsigned'}}})
+s:drop()
diff --git a/test/box/hash.result b/test/box/hash.result
index f0c30ade..9f08c49b 100644
--- a/test/box/hash.result
+++ b/test/box/hash.result
@@ -832,3 +832,18 @@ s:get(-9007199254740994LL)
 s:drop()
 ---
 ...
+-- Hash index cannot be multikey.
+s = box.schema.space.create('test')
+---
+...
+_ = s:create_index('primary')
+---
+...
+_ = s:create_index('hash', {type = 'hash', parts = {{'[2][*]', 'unsigned'}}})
+---
+- error: 'Can''t create or modify index ''hash'' in space ''test'': HASH index cannot
+    be multikey'
+...
+s:drop()
+---
+...
diff --git a/test/box/hash.test.lua b/test/box/hash.test.lua
index d97312e3..9801873c 100644
--- a/test/box/hash.test.lua
+++ b/test/box/hash.test.lua
@@ -347,3 +347,9 @@ s:get(-1LL)
 s:get(9007199254740992LL)
 s:get(-9007199254740994LL)
 s:drop()
+
+-- Hash index cannot be multikey.
+s = box.schema.space.create('test')
+_ = s:create_index('primary')
+_ = s:create_index('hash', {type = 'hash', parts = {{'[2][*]', 'unsigned'}}})
+s:drop()
diff --git a/test/box/rtree_misc.result b/test/box/rtree_misc.result
index 36d5b8f5..6e48bacc 100644
--- a/test/box/rtree_misc.result
+++ b/test/box/rtree_misc.result
@@ -667,3 +667,18 @@ i:select({1, 2, 3, 4, 5, 6}, {iterator = 'BITS_ALL_SET' } )
 s:drop()
 ---
 ...
+-- Rtree index cannot be multikey.
+s = box.schema.space.create('test')
+---
+...
+_ = s:create_index('primary')
+---
+...
+_ = s:create_index('rtree', {type = 'rtree', parts = {{'[2][*]', 'array'}}})
+---
+- error: 'Can''t create or modify index ''rtree'' in space ''test'': RTREE index cannot
+    be multikey'
+...
+s:drop()
+---
+...
diff --git a/test/box/rtree_misc.test.lua b/test/box/rtree_misc.test.lua
index cef66fc1..000a928e 100644
--- a/test/box/rtree_misc.test.lua
+++ b/test/box/rtree_misc.test.lua
@@ -237,3 +237,9 @@ i:select({0, 0, 0}, {iterator = 'neighbor'})
 i:select({1, 2, 3, 4, 5, 6}, {iterator = 'BITS_ALL_SET' } )
 
 s:drop()
+
+-- Rtree index cannot be multikey.
+s = box.schema.space.create('test')
+_ = s:create_index('primary')
+_ = s:create_index('rtree', {type = 'rtree', parts = {{'[2][*]', 'array'}}})
+s:drop()
diff --git a/test/engine/engine.cfg b/test/engine/engine.cfg
index 8f34bae8..9f07629b 100644
--- a/test/engine/engine.cfg
+++ b/test/engine/engine.cfg
@@ -2,9 +2,6 @@
     "*": {
         "memtx": {"engine": "memtx"}, 
         "vinyl": {"engine": "vinyl"}
-    },
-    "multikey.test.lua": {
-        "memtx": {"engine": "memtx"}
     }
 }
 
diff --git a/test/engine/multikey.result b/test/engine/multikey.result
index 293e27f1..1d5d9e20 100644
--- a/test/engine/multikey.result
+++ b/test/engine/multikey.result
@@ -7,20 +7,6 @@ engine = test_run:get_cfg('engine')
 --
 -- gh-1260: Multikey indexes.
 --
-s = box.schema.space.create('withdata', {engine = 'vinyl'})
----
-...
-pk = s:create_index('pk')
----
-...
--- Vinyl's space can't be multikey (yet).
-_ = s:create_index('idx', {parts = {{3, 'str', path = '[*].fname'}, {3, 'str', path = '[*].sname'}}})
----
-- error: Vinyl does not support multikey indexes
-...
-s:drop()
----
-...
 s = box.schema.space.create('withdata', {engine = engine})
 ---
 ...
@@ -33,22 +19,6 @@ _ = s:create_index('idx', {parts = {{3, 'str', path = '[*].fname'}}})
 pk = s:create_index('pk')
 ---
 ...
--- Only tree index type may be mutlikey.
-_ = s:create_index('idx', {type = 'hash', unique = true, parts = {{3, 'str', path = '[*].fname'}}})
----
-- error: 'Can''t create or modify index ''idx'' in space ''withdata'': HASH index
-    cannot be multikey'
-...
-_ = s:create_index('idx', {type = 'bitset', unique = false, parts = {{3, 'str', path = '[*].fname'}}})
----
-- error: 'Can''t create or modify index ''idx'' in space ''withdata'': BITSET index
-    cannot be multikey'
-...
-_ = s:create_index('idx', {type = 'rtree', unique = false, parts = {{3, 'array', path = '[*].fname'}}})
----
-- error: 'Can''t create or modify index ''idx'' in space ''withdata'': RTREE index
-    cannot be multikey'
-...
 -- Test incompatible multikey index parts.
 _ = s:create_index('idx3', {parts = {{3, 'str', path = '[*].fname'}, {3, 'str', path = '["data"][*].sname'}}})
 ---
@@ -146,10 +116,8 @@ arr_idx:select({1})
   - [5, [1, 1, 1], [{'fname': 'A', 'sname': 'B'}, {'fname': 'C', 'sname': 'D'}, {
         'fname': 'A', 'sname': 'B'}]]
 ...
-s:delete(5)
+_ = s:delete(5)
 ---
-- [5, [1, 1, 1], [{'fname': 'A', 'sname': 'B'}, {'fname': 'C', 'sname': 'D'}, {'fname': 'A',
-      'sname': 'B'}]]
 ...
 -- Check that there is no garbage in index.
 arr_idx:select({1})
@@ -162,9 +130,8 @@ idx:get({'A', 'B'})
 idx:get({'C', 'D'})
 ---
 ...
-idx:delete({'Vasya', 'Pupkin'})
+_ = idx:delete({'Vasya', 'Pupkin'})
 ---
-- [1, [1, 2, 3], [{'fname': 'James', 'sname': 'Bond'}, {'fname': 'Vasya', 'sname': 'Pupkin'}]]
 ...
 s:insert({6, {1, 2}, {{fname='Vasya', sname='Pupkin'}}})
 ---
@@ -286,9 +253,8 @@ idx0:select()
 - - [1, [1, 1, 1]]
   - [2, [2, 2]]
 ...
-idx0:delete(2)
+_ = idx0:delete(2)
 ---
-- [2, [2, 2]]
 ...
 idx0:get(2)
 ---
diff --git a/test/engine/multikey.test.lua b/test/engine/multikey.test.lua
index 0916bf52..f32f49d2 100644
--- a/test/engine/multikey.test.lua
+++ b/test/engine/multikey.test.lua
@@ -4,20 +4,10 @@ engine = test_run:get_cfg('engine')
 --
 -- gh-1260: Multikey indexes.
 --
-s = box.schema.space.create('withdata', {engine = 'vinyl'})
-pk = s:create_index('pk')
--- Vinyl's space can't be multikey (yet).
-_ = s:create_index('idx', {parts = {{3, 'str', path = '[*].fname'}, {3, 'str', path = '[*].sname'}}})
-s:drop()
-
 s = box.schema.space.create('withdata', {engine = engine})
 -- Primary index must be unique so it can't be multikey.
 _ = s:create_index('idx', {parts = {{3, 'str', path = '[*].fname'}}})
 pk = s:create_index('pk')
--- Only tree index type may be mutlikey.
-_ = s:create_index('idx', {type = 'hash', unique = true, parts = {{3, 'str', path = '[*].fname'}}})
-_ = s:create_index('idx', {type = 'bitset', unique = false, parts = {{3, 'str', path = '[*].fname'}}})
-_ = s:create_index('idx', {type = 'rtree', unique = false, parts = {{3, 'array', path = '[*].fname'}}})
 -- Test incompatible multikey index parts.
 _ = s:create_index('idx3', {parts = {{3, 'str', path = '[*].fname'}, {3, 'str', path = '["data"][*].sname'}}})
 _ = s:create_index('idx2', {parts = {{3, 'str', path = '[*].fname'}, {3, 'str', path = '[*].sname[*].a'}}})
@@ -43,12 +33,12 @@ idx:select()
 -- Duplicates in multikey parts.
 s:insert({5, {1, 1, 1}, {{fname='A', sname='B'}, {fname='C', sname='D'}, {fname='A', sname='B'}}})
 arr_idx:select({1})
-s:delete(5)
+_ = s:delete(5)
 -- Check that there is no garbage in index.
 arr_idx:select({1})
 idx:get({'A', 'B'})
 idx:get({'C', 'D'})
-idx:delete({'Vasya', 'Pupkin'})
+_ = idx:delete({'Vasya', 'Pupkin'})
 s:insert({6, {1, 2}, {{fname='Vasya', sname='Pupkin'}}})
 s:insert({7, {1}, {{fname='James', sname='Bond'}}})
 arr_idx:select({1})
@@ -84,7 +74,7 @@ idx0:get(2)
 idx0:get(1)
 idx0:get(3)
 idx0:select()
-idx0:delete(2)
+_ = idx0:delete(2)
 idx0:get(2)
 idx0:select()
 s:drop()
-- 
2.11.0




More information about the Tarantool-patches mailing list