[PATCH 12/12] vinyl: allow to modify key definition if it does not require rebuild

Vladimir Davydov vdavydov.dev at gmail.com
Mon Apr 2 11:46:49 MSK 2018


On Sun, Apr 01, 2018 at 12:05:39PM +0300, Vladimir Davydov wrote:
> @@ -945,11 +973,19 @@ vinyl_space_prepare_alter(struct space *old_space, struct space *new_space)
>  	 */
>  	if (env->status != VINYL_ONLINE)
>  		return 0;
> -	/*
> -	 * Regardless of the space emptyness, key definition of an
> -	 * existing index can not be changed, because key
> -	 * definition is already in vylog. See #3169.
> -	 */
> +	/* The space is empty. Allow alter. */
> +	if (pk->stat.disk.count.rows == 0 &&
> +	    pk->stat.memory.count.rows == 0)
> +		return 0;

> +	if (space_def_check_compatibility(old_space->def, new_space->def,
> +					  false) != 0)
> +		return -1;
> +	if (old_space->index_count < new_space->index_count) {
> +		diag_set(ClientError, ER_UNSUPPORTED, "Vinyl",
> +			 "adding an index to a non-empty space");
> +		return -1;
> +	}
> +

These two checks got added by git-revert. They are pointless as we
already have them in vinyl_space_prepare_alter(). I removed them on
the branch. Here's the updated diff:

diff --git a/src/box/key_def.cc b/src/box/key_def.cc
index 2f157419..50786913 100644
--- a/src/box/key_def.cc
+++ b/src/box/key_def.cc
@@ -107,6 +107,15 @@ key_def_dup(const struct key_def *src)
 }
 
 void
+key_def_swap(struct key_def *old_def, struct key_def *new_def)
+{
+	assert(old_def->part_count == new_def->part_count);
+	for (uint32_t i = 0; i < new_def->part_count; i++)
+		SWAP(old_def->parts[i], new_def->parts[i]);
+	SWAP(*old_def, *new_def);
+}
+
+void
 key_def_delete(struct key_def *def)
 {
 	free(def);
diff --git a/src/box/key_def.h b/src/box/key_def.h
index fe9ee56f..84e3e1e5 100644
--- a/src/box/key_def.h
+++ b/src/box/key_def.h
@@ -150,6 +150,13 @@ struct key_def *
 key_def_dup(const struct key_def *src);
 
 /**
+ * Swap content of two key definitions in memory.
+ * The two key definitions must have the same size.
+ */
+void
+key_def_swap(struct key_def *old_def, struct key_def *new_def);
+
+/**
  * Delete @a key_def.
  * @param def Key_def to delete.
  */
diff --git a/src/box/vinyl.c b/src/box/vinyl.c
index 145fde70..d503704e 100644
--- a/src/box/vinyl.c
+++ b/src/box/vinyl.c
@@ -803,7 +803,7 @@ vinyl_index_commit_create(struct index *index, int64_t lsn)
 		 * the index isn't in the recovery context and we
 		 * need to retry to log it now.
 		 */
-		if (lsm->is_committed) {
+		if (lsm->commit_lsn >= 0) {
 			vy_scheduler_add_lsm(&env->scheduler, lsm);
 			return;
 		}
@@ -828,8 +828,8 @@ vinyl_index_commit_create(struct index *index, int64_t lsn)
 	if (lsm->opts.lsn != 0)
 		lsn = lsm->opts.lsn;
 
-	assert(!lsm->is_committed);
-	lsm->is_committed = true;
+	assert(lsm->commit_lsn < 0);
+	lsm->commit_lsn = lsn;
 
 	assert(lsm->range_count == 1);
 	struct vy_range *range = vy_range_tree_first(lsm->tree);
@@ -854,6 +854,34 @@ vinyl_index_commit_create(struct index *index, int64_t lsn)
 	vy_scheduler_add_lsm(&env->scheduler, lsm);
 }
 
+static void
+vinyl_index_commit_modify(struct index *index, int64_t lsn)
+{
+	struct vy_env *env = vy_env(index->engine);
+	struct vy_lsm *lsm = vy_lsm(index);
+
+	(void)env;
+	assert(env->status == VINYL_ONLINE ||
+	       env->status == VINYL_FINAL_RECOVERY_LOCAL ||
+	       env->status == VINYL_FINAL_RECOVERY_REMOTE);
+
+	if (lsn <= lsm->commit_lsn) {
+		/*
+		 * This must be local recovery from WAL, when
+		 * the operation has already been committed to
+		 * vylog.
+		 */
+		assert(env->status == VINYL_FINAL_RECOVERY_LOCAL);
+		return;
+	}
+
+	lsm->commit_lsn = lsn;
+
+	vy_log_tx_begin();
+	vy_log_modify_lsm(lsm->id, lsm->key_def, lsn);
+	vy_log_tx_try_commit();
+}
+
 /*
  * Delete all runs, ranges, and slices of a given LSM tree
  * from the metadata log.
@@ -945,11 +973,10 @@ vinyl_space_prepare_alter(struct space *old_space, struct space *new_space)
 	 */
 	if (env->status != VINYL_ONLINE)
 		return 0;
-	/*
-	 * Regardless of the space emptyness, key definition of an
-	 * existing index can not be changed, because key
-	 * definition is already in vylog. See #3169.
-	 */
+	/* The space is empty. Allow alter. */
+	if (pk->stat.disk.count.rows == 0 &&
+	    pk->stat.memory.count.rows == 0)
+		return 0;
 	if (old_space->index_count == new_space->index_count) {
 		/* Check index_defs to be unchanged. */
 		for (uint32_t i = 0; i < old_space->index_count; ++i) {
@@ -961,25 +988,14 @@ vinyl_space_prepare_alter(struct space *old_space, struct space *new_space)
 			 * vinyl yet.
 			 */
 			if (index_def_change_requires_rebuild(old_def,
-							      new_def) ||
-			    key_part_cmp(old_def->key_def->parts,
-					 old_def->key_def->part_count,
-					 new_def->key_def->parts,
-					 new_def->key_def->part_count) != 0) {
+							      new_def)) {
 				diag_set(ClientError, ER_UNSUPPORTED, "Vinyl",
-					 "changing the definition of an index");
+					 "changing the definition of "
+					 "a non-empty index");
 				return -1;
 			}
 		}
 	}
-	if (pk->stat.disk.count.rows == 0 &&
-	    pk->stat.memory.count.rows == 0)
-		return 0;
-	/*
-	 * Since space format is not persisted in vylog, it can be
-	 * altered on non-empty space to some state, compatible
-	 * with the old one.
-	 */
 	if (space_def_check_compatibility(old_space->def, new_space->def,
 					  false) != 0)
 		return -1;
@@ -1083,6 +1099,8 @@ vinyl_space_swap_index(struct space *old_space, struct space *new_space,
 	     new_lsm->mem_format_with_colmask);
 	SWAP(old_lsm->disk_format, new_lsm->disk_format);
 	SWAP(old_lsm->opts, new_lsm->opts);
+	key_def_swap(old_lsm->key_def, new_lsm->key_def);
+	key_def_swap(old_lsm->cmp_def, new_lsm->cmp_def);
 }
 
 static int
@@ -3931,7 +3949,7 @@ static const struct index_vtab vinyl_index_vtab = {
 	/* .destroy = */ vinyl_index_destroy,
 	/* .commit_create = */ vinyl_index_commit_create,
 	/* .abort_create = */ generic_index_abort_create,
-	/* .commit_modify = */ generic_index_commit_modify,
+	/* .commit_modify = */ vinyl_index_commit_modify,
 	/* .commit_drop = */ vinyl_index_commit_drop,
 	/* .update_def = */ generic_index_update_def,
 	/* .depends_on_pk = */ vinyl_index_depends_on_pk,
diff --git a/src/box/vy_log.c b/src/box/vy_log.c
index 5552065d..172a1b01 100644
--- a/src/box/vy_log.c
+++ b/src/box/vy_log.c
@@ -81,6 +81,7 @@ enum vy_log_key {
 	VY_LOG_KEY_GC_LSN		= 10,
 	VY_LOG_KEY_TRUNCATE_COUNT	= 11,
 	VY_LOG_KEY_CREATE_LSN		= 12,
+	VY_LOG_KEY_MODIFY_LSN		= 13,
 };
 
 /** vy_log_key -> human readable name. */
@@ -98,6 +99,7 @@ static const char *vy_log_key_name[] = {
 	[VY_LOG_KEY_GC_LSN]		= "gc_lsn",
 	[VY_LOG_KEY_TRUNCATE_COUNT]	= "truncate_count",
 	[VY_LOG_KEY_CREATE_LSN]		= "create_lsn",
+	[VY_LOG_KEY_MODIFY_LSN]		= "modify_lsn",
 };
 
 /** vy_log_type -> human readable name. */
@@ -115,6 +117,7 @@ static const char *vy_log_type_name[] = {
 	[VY_LOG_DUMP_LSM]		= "dump_lsm",
 	[VY_LOG_SNAPSHOT]		= "snapshot",
 	[VY_LOG_TRUNCATE_LSM]		= "truncate_lsm",
+	[VY_LOG_MODIFY_LSM]		= "modify_lsm",
 };
 
 /** Metadata log object. */
@@ -251,6 +254,10 @@ vy_log_record_snprint(char *buf, int size, const struct vy_log_record *record)
 		SNPRINT(total, snprintf, buf, size, "%s=%"PRIi64", ",
 			vy_log_key_name[VY_LOG_KEY_CREATE_LSN],
 			record->create_lsn);
+	if (record->modify_lsn > 0)
+		SNPRINT(total, snprintf, buf, size, "%s=%"PRIi64", ",
+			vy_log_key_name[VY_LOG_KEY_MODIFY_LSN],
+			record->modify_lsn);
 	if (record->dump_lsn > 0)
 		SNPRINT(total, snprintf, buf, size, "%s=%"PRIi64", ",
 			vy_log_key_name[VY_LOG_KEY_DUMP_LSN],
@@ -360,6 +367,11 @@ vy_log_record_encode(const struct vy_log_record *record,
 		size += mp_sizeof_uint(record->create_lsn);
 		n_keys++;
 	}
+	if (record->modify_lsn > 0) {
+		size += mp_sizeof_uint(VY_LOG_KEY_MODIFY_LSN);
+		size += mp_sizeof_uint(record->modify_lsn);
+		n_keys++;
+	}
 	if (record->dump_lsn > 0) {
 		size += mp_sizeof_uint(VY_LOG_KEY_DUMP_LSN);
 		size += mp_sizeof_uint(record->dump_lsn);
@@ -432,6 +444,10 @@ vy_log_record_encode(const struct vy_log_record *record,
 		pos = mp_encode_uint(pos, VY_LOG_KEY_CREATE_LSN);
 		pos = mp_encode_uint(pos, record->create_lsn);
 	}
+	if (record->modify_lsn > 0) {
+		pos = mp_encode_uint(pos, VY_LOG_KEY_MODIFY_LSN);
+		pos = mp_encode_uint(pos, record->modify_lsn);
+	}
 	if (record->dump_lsn > 0) {
 		pos = mp_encode_uint(pos, VY_LOG_KEY_DUMP_LSN);
 		pos = mp_encode_uint(pos, record->dump_lsn);
@@ -552,6 +568,9 @@ vy_log_record_decode(struct vy_log_record *record,
 		case VY_LOG_KEY_CREATE_LSN:
 			record->create_lsn = mp_decode_uint(&pos);
 			break;
+		case VY_LOG_KEY_MODIFY_LSN:
+			record->modify_lsn = mp_decode_uint(&pos);
+			break;
 		case VY_LOG_KEY_DUMP_LSN:
 			record->dump_lsn = mp_decode_uint(&pos);
 			break;
@@ -568,7 +587,7 @@ vy_log_record_decode(struct vy_log_record *record,
 			goto fail;
 		}
 	}
-	if (record->type == VY_LOG_CREATE_LSM && record->create_lsn == 0) {
+	if (record->type == VY_LOG_CREATE_LSM) {
 		/*
 		 * We used to use LSN as unique LSM tree identifier
 		 * and didn't store LSN separately so if there's
@@ -577,7 +596,14 @@ vy_log_record_decode(struct vy_log_record *record,
 		 * fact the LSN of the WAL record that committed
 		 * the LSM tree.
 		 */
-		record->create_lsn = record->lsm_id;
+		if (record->create_lsn == 0)
+			record->create_lsn = record->lsm_id;
+		/*
+		 * If the LSM tree has never been modified, initialize
+		 * 'modify_lsn' with 'create_lsn'.
+		 */
+		if (record->modify_lsn == 0)
+			record->modify_lsn = record->create_lsn;
 	}
 	return 0;
 fail:
@@ -1172,7 +1198,7 @@ vy_recovery_create_lsm(struct vy_recovery *recovery, int64_t id,
 		       uint32_t space_id, uint32_t index_id,
 		       const struct key_part_def *key_parts,
 		       uint32_t key_part_count, int64_t create_lsn,
-		       int64_t dump_lsn)
+		       int64_t modify_lsn, int64_t dump_lsn)
 {
 	struct vy_lsm_recovery_info *lsm;
 	struct key_part_def *key_parts_copy;
@@ -1259,6 +1285,7 @@ vy_recovery_create_lsm(struct vy_recovery *recovery, int64_t id,
 	lsm->key_part_count = key_part_count;
 	lsm->is_dropped = false;
 	lsm->create_lsn = create_lsn;
+	lsm->modify_lsn = modify_lsn;
 	lsm->dump_lsn = dump_lsn;
 
 	/*
@@ -1286,6 +1313,43 @@ vy_recovery_create_lsm(struct vy_recovery *recovery, int64_t id,
 }
 
 /**
+ * Handle a VY_LOG_MODIFY_LSM log record.
+ * This function updates key definition of the LSM tree with ID @id.
+ * Return 0 on success, -1 on failure.
+ */
+static int
+vy_recovery_modify_lsm(struct vy_recovery *recovery, int64_t id,
+		       const struct key_part_def *key_parts,
+		       uint32_t key_part_count, int64_t modify_lsn)
+{
+	struct vy_lsm_recovery_info *lsm;
+	lsm = vy_recovery_lookup_lsm(recovery, id);
+	if (lsm == NULL) {
+		diag_set(ClientError, ER_INVALID_VYLOG_FILE,
+			 tt_sprintf("Update of unregistered LSM tree %lld",
+				    (long long)id));
+		return -1;
+	}
+	if (lsm->is_dropped) {
+		diag_set(ClientError, ER_INVALID_VYLOG_FILE,
+			 tt_sprintf("Update of deleted LSM tree %lld",
+				    (long long)id));
+		return -1;
+	}
+	free(lsm->key_parts);
+	lsm->key_parts = malloc(sizeof(*key_parts) * key_part_count);
+	if (lsm->key_parts == NULL) {
+		diag_set(OutOfMemory, sizeof(*key_parts) * key_part_count,
+			 "malloc", "struct key_part_def");
+		return -1;
+	}
+	memcpy(lsm->key_parts, key_parts, sizeof(*key_parts) * key_part_count);
+	lsm->key_part_count = key_part_count;
+	lsm->modify_lsn = modify_lsn;
+	return 0;
+}
+
+/**
  * Handle a VY_LOG_DROP_LSM log record.
  * This function marks the LSM tree with ID @id as dropped.
  * All ranges and runs of the LSM tree must have been deleted by now.
@@ -1757,7 +1821,13 @@ vy_recovery_process_record(struct vy_recovery *recovery,
 		rc = vy_recovery_create_lsm(recovery, record->lsm_id,
 				record->space_id, record->index_id,
 				record->key_parts, record->key_part_count,
-				record->create_lsn, record->dump_lsn);
+				record->create_lsn, record->modify_lsn,
+				record->dump_lsn);
+		break;
+	case VY_LOG_MODIFY_LSM:
+		rc = vy_recovery_modify_lsm(recovery, record->lsm_id,
+				record->key_parts, record->key_part_count,
+				record->modify_lsn);
 		break;
 	case VY_LOG_DROP_LSM:
 		rc = vy_recovery_drop_lsm(recovery, record->lsm_id);
@@ -2007,6 +2077,7 @@ vy_log_append_lsm(struct xlog *xlog, struct vy_lsm_recovery_info *lsm)
 	record.key_parts = lsm->key_parts;
 	record.key_part_count = lsm->key_part_count;
 	record.create_lsn = lsm->create_lsn;
+	record.modify_lsn = lsm->modify_lsn;
 	record.dump_lsn = lsm->dump_lsn;
 	if (vy_log_append_record(xlog, &record) != 0)
 		return -1;
diff --git a/src/box/vy_log.h b/src/box/vy_log.h
index 702963d5..1b2b419f 100644
--- a/src/box/vy_log.h
+++ b/src/box/vy_log.h
@@ -164,6 +164,11 @@ enum vy_log_record_type {
 	 * WAL records written after truncation.
 	 */
 	VY_LOG_TRUNCATE_LSM		= 12,
+	/**
+	 * Modify key definition of an LSM tree.
+	 * Requires vy_log_record::lsm_id, key_def, modify_lsn.
+	 */
+	VY_LOG_MODIFY_LSM		= 13,
 
 	vy_log_record_type_MAX
 };
@@ -202,6 +207,8 @@ struct vy_log_record {
 	uint32_t key_part_count;
 	/** LSN of the WAL row that created the LSM tree. */
 	int64_t create_lsn;
+	/** LSN of the WAL row that last modified the LSM tree. */
+	int64_t modify_lsn;
 	/** Max LSN stored on disk. */
 	int64_t dump_lsn;
 	/**
@@ -255,6 +262,8 @@ struct vy_lsm_recovery_info {
 	bool is_dropped;
 	/** LSN of the WAL row that created the LSM tree. */
 	int64_t create_lsn;
+	/** LSN of the WAL row that last modified the LSM tree. */
+	int64_t modify_lsn;
 	/** LSN of the last LSM tree dump. */
 	int64_t dump_lsn;
 	/**
@@ -516,6 +525,19 @@ vy_log_create_lsm(int64_t id, uint32_t space_id, uint32_t index_id,
 	vy_log_write(&record);
 }
 
+/** Helper to log a vinyl LSM tree modification. */
+static inline void
+vy_log_modify_lsm(int64_t id, const struct key_def *key_def, int64_t modify_lsn)
+{
+	struct vy_log_record record;
+	vy_log_record_init(&record);
+	record.type = VY_LOG_MODIFY_LSM;
+	record.lsm_id = id;
+	record.key_def = key_def;
+	record.modify_lsn = modify_lsn;
+	vy_log_write(&record);
+}
+
 /** Helper to log a vinyl LSM tree drop. */
 static inline void
 vy_log_drop_lsm(int64_t id)
diff --git a/src/box/vy_lsm.c b/src/box/vy_lsm.c
index af6dde50..a8ed39ad 100644
--- a/src/box/vy_lsm.c
+++ b/src/box/vy_lsm.c
@@ -182,6 +182,7 @@ vy_lsm_new(struct vy_lsm_env *lsm_env, struct vy_cache_env *cache_env,
 	lsm->id = -1;
 	lsm->refs = 1;
 	lsm->dump_lsn = -1;
+	lsm->commit_lsn = -1;
 	vy_cache_create(&lsm->cache, cache_env, cmp_def);
 	rlist_create(&lsm->sealed);
 	vy_range_tree_new(lsm->tree);
@@ -479,7 +480,7 @@ vy_lsm_recover(struct vy_lsm *lsm, struct vy_recovery *recovery,
 		 bool is_checkpoint_recovery, bool force_recovery)
 {
 	assert(lsm->id < 0);
-	assert(!lsm->is_committed);
+	assert(lsm->commit_lsn < 0);
 	assert(lsm->range_count == 0);
 
 	/*
@@ -535,7 +536,7 @@ vy_lsm_recover(struct vy_lsm *lsm, struct vy_recovery *recovery,
 	}
 
 	lsm->id = lsm_info->id;
-	lsm->is_committed = true;
+	lsm->commit_lsn = lsm_info->modify_lsn;
 
 	if (lsn < lsm_info->create_lsn || lsm_info->is_dropped) {
 		/*
diff --git a/src/box/vy_lsm.h b/src/box/vy_lsm.h
index f5862a52..201b5a56 100644
--- a/src/box/vy_lsm.h
+++ b/src/box/vy_lsm.h
@@ -255,11 +255,13 @@ struct vy_lsm {
 	 * been dumped yet.
 	 */
 	int64_t dump_lsn;
-	/*
-	 * This flag is set if the LSM tree creation was
-	 * committed to the metadata log.
+	/**
+	 * LSN of the WAL row that created or last modified
+	 * this LSM tree. We store it in vylog so that during
+	 * local recovery we can replay vylog records we failed
+	 * to log before restart.
 	 */
-	bool is_committed;
+	int64_t commit_lsn;
 	/**
 	 * This flag is set if the LSM tree was dropped.
 	 * It is also set on local recovery if the LSM tree
diff --git a/test/engine/ddl.result b/test/engine/ddl.result
index c3cb1efc..308aefb0 100644
--- a/test/engine/ddl.result
+++ b/test/engine/ddl.result
@@ -391,9 +391,6 @@ s:drop()
 -- gh-3229: update optionality if a space format is changed too,
 -- not only when indexes are updated.
 --
-box.cfg{}
----
-...
 s = box.schema.create_space('test', {engine = engine})
 ---
 ...
@@ -466,3 +463,113 @@ sk:get({5})
 s:drop()
 ---
 ...
+--
+-- Modify key definition without index rebuild.
+--
+s = box.schema.create_space('test', {engine = engine})
+---
+...
+i1 = s:create_index('i1', {unique = true,  parts = {1, 'unsigned'}})
+---
+...
+i2 = s:create_index('i2', {unique = false, parts = {2, 'unsigned'}})
+---
+...
+i3 = s:create_index('i3', {unique = true,  parts = {3, 'unsigned'}})
+---
+...
+_ = s:insert{1, 2, 3}
+---
+...
+box.snapshot()
+---
+- ok
+...
+_ = s:insert{3, 2, 1}
+---
+...
+i1:alter{parts = {1, 'integer'}}
+---
+...
+_ = s:insert{-1, 2, 2}
+---
+...
+i1:select()
+---
+- - [-1, 2, 2]
+  - [1, 2, 3]
+  - [3, 2, 1]
+...
+i2:select()
+---
+- - [-1, 2, 2]
+  - [1, 2, 3]
+  - [3, 2, 1]
+...
+i3:select()
+---
+- - [3, 2, 1]
+  - [-1, 2, 2]
+  - [1, 2, 3]
+...
+i2:alter{parts = {2, 'integer'}}
+---
+...
+i3:alter{parts = {3, 'integer'}}
+---
+...
+_ = s:replace{-1, -1, -1}
+---
+...
+i1:select()
+---
+- - [-1, -1, -1]
+  - [1, 2, 3]
+  - [3, 2, 1]
+...
+i2:select()
+---
+- - [-1, -1, -1]
+  - [1, 2, 3]
+  - [3, 2, 1]
+...
+i3:select()
+---
+- - [-1, -1, -1]
+  - [3, 2, 1]
+  - [1, 2, 3]
+...
+box.snapshot()
+---
+- ok
+...
+_ = s:replace{-1, -2, -3}
+---
+...
+_ = s:replace{-3, -2, -1}
+---
+...
+i1:select()
+---
+- - [-3, -2, -1]
+  - [-1, -2, -3]
+  - [1, 2, 3]
+  - [3, 2, 1]
+...
+i2:select()
+---
+- - [-3, -2, -1]
+  - [-1, -2, -3]
+  - [1, 2, 3]
+  - [3, 2, 1]
+...
+i3:select()
+---
+- - [-1, -2, -3]
+  - [-3, -2, -1]
+  - [3, 2, 1]
+  - [1, 2, 3]
+...
+s:drop()
+---
+...
diff --git a/test/engine/ddl.test.lua b/test/engine/ddl.test.lua
index 25381a3c..019e18a1 100644
--- a/test/engine/ddl.test.lua
+++ b/test/engine/ddl.test.lua
@@ -141,7 +141,6 @@ s:drop()
 -- gh-3229: update optionality if a space format is changed too,
 -- not only when indexes are updated.
 --
-box.cfg{}
 s = box.schema.create_space('test', {engine = engine})
 format = {}
 format[1] = {'field1', 'unsigned'}
@@ -162,3 +161,37 @@ pk:get({4})
 sk:select({box.NULL})
 sk:get({5})
 s:drop()
+
+--
+-- Modify key definition without index rebuild.
+--
+s = box.schema.create_space('test', {engine = engine})
+i1 = s:create_index('i1', {unique = true,  parts = {1, 'unsigned'}})
+i2 = s:create_index('i2', {unique = false, parts = {2, 'unsigned'}})
+i3 = s:create_index('i3', {unique = true,  parts = {3, 'unsigned'}})
+
+_ = s:insert{1, 2, 3}
+box.snapshot()
+_ = s:insert{3, 2, 1}
+
+i1:alter{parts = {1, 'integer'}}
+_ = s:insert{-1, 2, 2}
+i1:select()
+i2:select()
+i3:select()
+
+i2:alter{parts = {2, 'integer'}}
+i3:alter{parts = {3, 'integer'}}
+_ = s:replace{-1, -1, -1}
+i1:select()
+i2:select()
+i3:select()
+
+box.snapshot()
+_ = s:replace{-1, -2, -3}
+_ = s:replace{-3, -2, -1}
+i1:select()
+i2:select()
+i3:select()
+
+s:drop()
diff --git a/test/engine/replica_join.result b/test/engine/replica_join.result
index a003c0d8..39d857fe 100644
--- a/test/engine/replica_join.result
+++ b/test/engine/replica_join.result
@@ -293,6 +293,12 @@ for k = 8, 1, -1 do box.space.test:update(k, {{'-', 2, 8}}) end
 for k = 9, 16 do box.space.test:delete(k) end
 ---
 ...
+box.space.test.index.primary:alter{parts = {1, 'integer'}}
+---
+...
+for k = -8, -1 do box.space.test:insert{k, 9 + k} end
+---
+...
 _ = box.space.test2:upsert({1, 'test1', 11}, {{'+', 3, 1}})
 ---
 ...
@@ -333,7 +339,15 @@ test_run:cmd('switch replica')
 ...
 box.space.test:select()
 ---
-- - [1, 8]
+- - [-8, 1]
+  - [-7, 2]
+  - [-6, 3]
+  - [-5, 4]
+  - [-4, 5]
+  - [-3, 6]
+  - [-2, 7]
+  - [-1, 8]
+  - [1, 8]
   - [2, 7]
   - [3, 6]
   - [4, 5]
@@ -344,13 +358,21 @@ box.space.test:select()
 ...
 box.space.test.index.secondary:select()
 ---
-- - [8, 1]
+- - [-8, 1]
+  - [8, 1]
+  - [-7, 2]
   - [7, 2]
+  - [-6, 3]
   - [6, 3]
+  - [-5, 4]
   - [5, 4]
+  - [-4, 5]
   - [4, 5]
+  - [-3, 6]
   - [3, 6]
+  - [-2, 7]
   - [2, 7]
+  - [-1, 8]
   - [1, 8]
 ...
 box.space.test2:select()
diff --git a/test/engine/replica_join.test.lua b/test/engine/replica_join.test.lua
index 5c6ed11a..1792281e 100644
--- a/test/engine/replica_join.test.lua
+++ b/test/engine/replica_join.test.lua
@@ -72,6 +72,8 @@ _ = test_run:cmd("cleanup server replica")
 -- new data
 for k = 8, 1, -1 do box.space.test:update(k, {{'-', 2, 8}}) end
 for k = 9, 16 do box.space.test:delete(k) end
+box.space.test.index.primary:alter{parts = {1, 'integer'}}
+for k = -8, -1 do box.space.test:insert{k, 9 + k} end
 _ = box.space.test2:upsert({1, 'test1', 11}, {{'+', 3, 1}})
 _ = box.space.test2:update({2, 'test2'}, {{'+', 3, 2}})
 _ = box.space.test2:delete{3, 'test3'}
diff --git a/test/engine/truncate.result b/test/engine/truncate.result
index b6e1a99a..2222804b 100644
--- a/test/engine/truncate.result
+++ b/test/engine/truncate.result
@@ -391,7 +391,7 @@ _ = s3:insert{789, 987}
 ...
 -- Check that index drop, create, and alter called after space
 -- truncate do not break recovery (gh-2615)
-s4 = box.schema.create_space('test4', {engine = 'memtx'})
+s4 = box.schema.create_space('test4', {engine = engine})
 ---
 ...
 _ = s4:create_index('i1', {parts = {1, 'string'}})
diff --git a/test/engine/truncate.test.lua b/test/engine/truncate.test.lua
index 66cbd156..df2797a1 100644
--- a/test/engine/truncate.test.lua
+++ b/test/engine/truncate.test.lua
@@ -175,7 +175,7 @@ s3:truncate()
 _ = s3:insert{789, 987}
 -- Check that index drop, create, and alter called after space
 -- truncate do not break recovery (gh-2615)
-s4 = box.schema.create_space('test4', {engine = 'memtx'})
+s4 = box.schema.create_space('test4', {engine = engine})
 _ = s4:create_index('i1', {parts = {1, 'string'}})
 _ = s4:create_index('i3', {parts = {3, 'string'}})
 _ = s4:insert{'zzz', 111, 'yyy'}
diff --git a/test/vinyl/ddl.result b/test/vinyl/ddl.result
index 6f393c69..456977e5 100644
--- a/test/vinyl/ddl.result
+++ b/test/vinyl/ddl.result
@@ -87,7 +87,7 @@ index2 = space:create_index('secondary', { parts = {2, 'unsigned'} })
 ...
 space.index.primary:alter({parts = {1, 'unsigned', 2, 'unsigned'}})
 ---
-- error: Vinyl does not support changing the definition of an index
+- error: Vinyl does not support changing the definition of a non-empty index
 ...
 #box.space._index:select({space.id})
 ---
@@ -116,7 +116,7 @@ index2 = space:create_index('secondary', { parts = {2, 'unsigned'} })
 ...
 space.index.primary:alter({parts = {1, 'unsigned', 2, 'unsigned'}})
 ---
-- error: Vinyl does not support changing the definition of an index
+- error: Vinyl does not support changing the definition of a non-empty index
 ...
 #box.space._index:select({space.id})
 ---
@@ -145,7 +145,7 @@ index2 = space:create_index('secondary', { parts = {2, 'unsigned'} })
 ...
 space.index.primary:alter({parts = {1, 'unsigned', 2, 'unsigned'}})
 ---
-- error: Vinyl does not support changing the definition of an index
+- error: Vinyl does not support changing the definition of a non-empty index
 ...
 #box.space._index:select({space.id})
 ---
@@ -165,7 +165,7 @@ index2 = space:create_index('secondary', { parts = {2, 'unsigned'} })
 ...
 space.index.primary:alter({parts = {1, 'unsigned', 2, 'unsigned'}})
 ---
-- error: Vinyl does not support changing the definition of an index
+- error: Vinyl does not support changing the definition of a non-empty index
 ...
 box.snapshot()
 ---
@@ -174,27 +174,33 @@ box.snapshot()
 while space.index.primary:info().rows ~= 0 do fiber.sleep(0.01) end
 ---
 ...
--- After a dump REPLACE + DELETE = nothing, so the space is empty
--- but an index can not be altered.
+-- after a dump REPLACE + DELETE = nothing, so the space is empty now and
+-- can be altered.
 index2 = space:create_index('secondary', { parts = {2, 'unsigned'} })
 ---
 ...
 space.index.primary:alter({parts = {1, 'unsigned', 2, 'unsigned'}})
 ---
-- error: Vinyl does not support changing the definition of an index
 ...
--- Space format still can be altered.
-format = {}
+#box.space._index:select({space.id})
 ---
+- 2
 ...
-format[1] = {name = 'field1', type = 'unsigned'}
+box.space._index:get{space.id, 0}[6]
 ---
+- [[0, 'unsigned'], [1, 'unsigned']]
 ...
-format[2] = {name = 'field2', type = 'unsigned'}
+space:insert({1, 2})
 ---
+- [1, 2]
 ...
-space:format(format)
+index:select{}
+---
+- - [1, 2]
+...
+index2:select{}
 ---
+- - [1, 2]
 ...
 space:drop()
 ---
@@ -230,7 +236,7 @@ index2 = space:create_index('secondary', { parts = {2, 'unsigned'} })
 ...
 space.index.primary:alter({parts = {1, 'unsigned', 2, 'unsigned'}})
 ---
-- error: Vinyl does not support changing the definition of an index
+- error: Vinyl does not support changing the definition of a non-empty index
 ...
 -- After compaction the REPLACE + DELETE + DELETE = nothing, so
 -- the space is now empty and can be altered.
@@ -256,10 +262,8 @@ while space.index.primary:info().rows ~= 0 do fiber.sleep(0.01) end
 index2 = space:create_index('secondary', { parts = {2, 'unsigned'} })
 ---
 ...
--- Can not alter an index even if it becames empty after dump.
 space.index.primary:alter({parts = {1, 'unsigned', 2, 'unsigned'}})
 ---
-- error: Vinyl does not support changing the definition of an index
 ...
 space:drop()
 ---
@@ -287,7 +291,7 @@ space:auto_increment{3}
 ...
 box.space._index:replace{space.id, 0, 'pk', 'tree', {unique=true}, {{0, 'unsigned'}, {1, 'unsigned'}}}
 ---
-- error: Vinyl does not support changing the definition of an index
+- error: Vinyl does not support changing the definition of a non-empty index
 ...
 space:select{}
 ---
@@ -828,7 +832,7 @@ s.index.secondary.unique
 ...
 s.index.secondary:alter{unique = true} -- error
 ---
-- error: Vinyl does not support changing the definition of an index
+- error: Vinyl does not support changing the definition of a non-empty index
 ...
 s.index.secondary.unique
 ---
@@ -846,43 +850,3 @@ s.index.secondary:select(10)
 s:drop()
 ---
 ...
---
--- gh-3169: vinyl index key definition can not be altered even if
--- the index is empty.
---
-s = box.schema.space.create('vinyl', {engine = 'vinyl'})
----
-...
-i = s:create_index('pk')
----
-...
-i:alter{parts = {1, 'integer'}}
----
-- error: Vinyl does not support changing the definition of an index
-...
-_ = s:replace{-1}
----
-- error: 'Tuple field 1 type does not match one required by operation: expected unsigned'
-...
-_ = s:replace{1}
----
-...
-_ = s:replace{-2}
----
-- error: 'Tuple field 1 type does not match one required by operation: expected unsigned'
-...
-_ = s:replace{3}
----
-...
-_ = s:replace{-3}
----
-- error: 'Tuple field 1 type does not match one required by operation: expected unsigned'
-...
-s:select{}
----
-- - [1]
-  - [3]
-...
-s:drop()
----
-...
diff --git a/test/vinyl/ddl.test.lua b/test/vinyl/ddl.test.lua
index f050799e..8ce8b920 100644
--- a/test/vinyl/ddl.test.lua
+++ b/test/vinyl/ddl.test.lua
@@ -59,15 +59,15 @@ space.index.primary:alter({parts = {1, 'unsigned', 2, 'unsigned'}})
 box.snapshot()
 while space.index.primary:info().rows ~= 0 do fiber.sleep(0.01) end
 
--- After a dump REPLACE + DELETE = nothing, so the space is empty
--- but an index can not be altered.
+-- after a dump REPLACE + DELETE = nothing, so the space is empty now and
+-- can be altered.
 index2 = space:create_index('secondary', { parts = {2, 'unsigned'} })
 space.index.primary:alter({parts = {1, 'unsigned', 2, 'unsigned'}})
--- Space format still can be altered.
-format = {}
-format[1] = {name = 'field1', type = 'unsigned'}
-format[2] = {name = 'field2', type = 'unsigned'}
-space:format(format)
+#box.space._index:select({space.id})
+box.space._index:get{space.id, 0}[6]
+space:insert({1, 2})
+index:select{}
+index2:select{}
 space:drop()
 
 space = box.schema.space.create('test', { engine = 'vinyl' })
@@ -91,7 +91,6 @@ box.snapshot()
 -- Wait until the dump is finished.
 while space.index.primary:info().rows ~= 0 do fiber.sleep(0.01) end
 index2 = space:create_index('secondary', { parts = {2, 'unsigned'} })
--- Can not alter an index even if it becames empty after dump.
 space.index.primary:alter({parts = {1, 'unsigned', 2, 'unsigned'}})
 
 space:drop()
@@ -305,18 +304,3 @@ s.index.secondary.unique
 s:insert{2, 10}
 s.index.secondary:select(10)
 s:drop()
-
---
--- gh-3169: vinyl index key definition can not be altered even if
--- the index is empty.
---
-s = box.schema.space.create('vinyl', {engine = 'vinyl'})
-i = s:create_index('pk')
-i:alter{parts = {1, 'integer'}}
-_ = s:replace{-1}
-_ = s:replace{1}
-_ = s:replace{-2}
-_ = s:replace{3}
-_ = s:replace{-3}
-s:select{}
-s:drop()
diff --git a/test/vinyl/gh.result b/test/vinyl/gh.result
index 32e4b6fb..9c72acc8 100644
--- a/test/vinyl/gh.result
+++ b/test/vinyl/gh.result
@@ -144,7 +144,7 @@ s:insert{5, 5}
 ...
 s.index.primary:alter({parts={2,'unsigned'}})
 ---
-- error: Vinyl does not support changing the definition of an index
+- error: Vinyl does not support changing the definition of a non-empty index
 ...
 s:drop()
 ---
diff --git a/test/vinyl/layout.result b/test/vinyl/layout.result
index 4af2c024..e6294d3c 100644
--- a/test/vinyl/layout.result
+++ b/test/vinyl/layout.result
@@ -20,7 +20,7 @@ space = box.schema.space.create('test', {engine='vinyl'})
 _ = space:create_index('pk', {parts = {{1, 'string', collation = 'unicode'}}, run_count_per_level=3})
 ---
 ...
-_ = space:create_index('sk', {parts = {{2, 'unsigned', is_nullable = true}}, run_count_per_level=3})
+_ = space:create_index('sk', {parts = {{2, 'unsigned'}}, run_count_per_level=3})
 ---
 ...
 -- Empty run
@@ -35,6 +35,9 @@ box.snapshot()
 ---
 - ok
 ...
+space.index.sk:alter{parts = {{2, 'unsigned', is_nullable = true}}}
+---
+...
 space:replace{'ЭЭЭ', box.NULL}
 ---
 - ['ЭЭЭ', null]
@@ -123,16 +126,16 @@ test_run:cmd("push filter 'offset: .*' to 'offset: <offset>'")
 ...
 result
 ---
-- - - 00000000000000000009.vylog
+- - - 00000000000000000010.vylog
     - - HEADER:
           type: INSERT
         BODY:
-          tuple: [0, {7: [{'field': 0, 'collation': 1, 'type': 'string'}], 9: 9, 12: 3,
-              6: 512}]
+          tuple: [0, {6: 512, 7: [{'field': 0, 'collation': 1, 'type': 'string'}],
+              9: 10, 12: 3, 13: 7}]
       - HEADER:
           type: INSERT
         BODY:
-          tuple: [5, {2: 8, 9: 9}]
+          tuple: [5, {2: 8, 9: 10}]
       - HEADER:
           type: INSERT
         BODY:
@@ -153,11 +156,11 @@ result
           type: INSERT
         BODY:
           tuple: [0, {0: 2, 5: 1, 6: 512, 7: [{'field': 1, 'is_nullable': true, 'type': 'unsigned'}],
-              9: 9, 12: 4}]
+              9: 10, 12: 4, 13: 7}]
       - HEADER:
           type: INSERT
         BODY:
-          tuple: [5, {0: 2, 2: 6, 9: 9}]
+          tuple: [5, {0: 2, 2: 6, 9: 10}]
       - HEADER:
           type: INSERT
         BODY:
@@ -197,7 +200,7 @@ result
           timestamp: <timestamp>
           type: INSERT
         BODY:
-          tuple: [5, {0: 2, 2: 10, 9: 12}]
+          tuple: [5, {0: 2, 2: 10, 9: 13}]
       - HEADER:
           timestamp: <timestamp>
           type: INSERT
@@ -207,7 +210,7 @@ result
           timestamp: <timestamp>
           type: INSERT
         BODY:
-          tuple: [10, {0: 2, 9: 12}]
+          tuple: [10, {0: 2, 9: 13}]
       - HEADER:
           timestamp: <timestamp>
           type: INSERT
@@ -217,7 +220,7 @@ result
           timestamp: <timestamp>
           type: INSERT
         BODY:
-          tuple: [5, {2: 12, 9: 12}]
+          tuple: [5, {2: 12, 9: 13}]
       - HEADER:
           timestamp: <timestamp>
           type: INSERT
@@ -227,16 +230,16 @@ result
           timestamp: <timestamp>
           type: INSERT
         BODY:
-          tuple: [10, {9: 12}]
+          tuple: [10, {9: 13}]
   - - 00000000000000000008.index
     - - HEADER:
           type: RUNINFO
         BODY:
-          min_lsn: 7
+          min_lsn: 8
           max_key: ['ЭЭЭ']
           page_count: 1
           bloom_filter: <bloom_filter>
-          max_lsn: 9
+          max_lsn: 10
           min_key: ['ёёё']
       - HEADER:
           type: PAGEINFO
@@ -249,17 +252,17 @@ result
           min_key: ['ёёё']
   - - 00000000000000000008.run
     - - HEADER:
-          lsn: 9
+          lsn: 10
           type: INSERT
         BODY:
           tuple: ['ёёё', null]
       - HEADER:
-          lsn: 8
+          lsn: 9
           type: INSERT
         BODY:
           tuple: ['эээ', null]
       - HEADER:
-          lsn: 7
+          lsn: 8
           type: INSERT
         BODY:
           tuple: ['ЭЭЭ', null]
@@ -271,11 +274,11 @@ result
     - - HEADER:
           type: RUNINFO
         BODY:
-          min_lsn: 10
+          min_lsn: 11
           max_key: ['ЮЮЮ']
           page_count: 1
           bloom_filter: <bloom_filter>
-          max_lsn: 12
+          max_lsn: 13
           min_key: ['ёёё']
       - HEADER:
           type: PAGEINFO
@@ -288,17 +291,17 @@ result
           min_key: ['ёёё']
   - - 00000000000000000012.run
     - - HEADER:
-          lsn: 10
+          lsn: 11
           type: REPLACE
         BODY:
           tuple: ['ёёё', 123]
       - HEADER:
-          lsn: 12
+          lsn: 13
           type: INSERT
         BODY:
           tuple: ['ююю', 789]
       - HEADER:
-          lsn: 11
+          lsn: 12
           type: INSERT
         BODY:
           tuple: ['ЮЮЮ', 456]
@@ -310,11 +313,11 @@ result
     - - HEADER:
           type: RUNINFO
         BODY:
-          min_lsn: 7
+          min_lsn: 8
           max_key: [null, 'ЭЭЭ']
           page_count: 1
           bloom_filter: <bloom_filter>
-          max_lsn: 9
+          max_lsn: 10
           min_key: [null, 'ёёё']
       - HEADER:
           type: PAGEINFO
@@ -327,17 +330,17 @@ result
           min_key: [null, 'ёёё']
   - - 00000000000000000006.run
     - - HEADER:
-          lsn: 9
+          lsn: 10
           type: INSERT
         BODY:
           tuple: [null, 'ёёё']
       - HEADER:
-          lsn: 8
+          lsn: 9
           type: INSERT
         BODY:
           tuple: [null, 'эээ']
       - HEADER:
-          lsn: 7
+          lsn: 8
           type: INSERT
         BODY:
           tuple: [null, 'ЭЭЭ']
@@ -349,11 +352,11 @@ result
     - - HEADER:
           type: RUNINFO
         BODY:
-          min_lsn: 10
+          min_lsn: 11
           max_key: [789, 'ююю']
           page_count: 1
           bloom_filter: <bloom_filter>
-          max_lsn: 12
+          max_lsn: 13
           min_key: [null, 'ёёё']
       - HEADER:
           type: PAGEINFO
@@ -366,22 +369,22 @@ result
           min_key: [null, 'ёёё']
   - - 00000000000000000010.run
     - - HEADER:
-          lsn: 10
+          lsn: 11
           type: DELETE
         BODY:
           key: [null, 'ёёё']
       - HEADER:
-          lsn: 10
+          lsn: 11
           type: REPLACE
         BODY:
           tuple: [123, 'ёёё']
       - HEADER:
-          lsn: 11
+          lsn: 12
           type: INSERT
         BODY:
           tuple: [456, 'ЮЮЮ']
       - HEADER:
-          lsn: 12
+          lsn: 13
           type: INSERT
         BODY:
           tuple: [789, 'ююю']
diff --git a/test/vinyl/layout.test.lua b/test/vinyl/layout.test.lua
index fd5787b7..27bf5d40 100644
--- a/test/vinyl/layout.test.lua
+++ b/test/vinyl/layout.test.lua
@@ -8,13 +8,15 @@ fun = require 'fun'
 
 space = box.schema.space.create('test', {engine='vinyl'})
 _ = space:create_index('pk', {parts = {{1, 'string', collation = 'unicode'}}, run_count_per_level=3})
-_ = space:create_index('sk', {parts = {{2, 'unsigned', is_nullable = true}}, run_count_per_level=3})
+_ = space:create_index('sk', {parts = {{2, 'unsigned'}}, run_count_per_level=3})
 
 -- Empty run
 space:insert{'ЁЁЁ', 777}
 space:delete{'ЁЁЁ'}
 box.snapshot()
 
+space.index.sk:alter{parts = {{2, 'unsigned', is_nullable = true}}}
+
 space:replace{'ЭЭЭ', box.NULL}
 space:replace{'эээ', box.NULL}
 space:replace{'ёёё', box.NULL}



More information about the Tarantool-patches mailing list