[patches] [PATCH 1/2] alter: allow to restrict space format of a non-empty space
Vladislav Shpilevoy
v.shpilevoy at tarantool.org
Wed Feb 14 21:27:51 MSK 2018
Now if a space is not empty, its format can not be restricted by
nullability removal, or by restriction of a field type.
Allow format restriction followed by space format checking.
Do not rebuild index, if it is not empty. A rebuild is not needed
for such change, because types can not be altered on a not empty space
in such way, that index order is changed.
But do rebuild, if an index is not unique, and a primary index parts
have been changed. It changes cmp_def of the secondary index, and
possibly sort order.
Needed for #3008
Signed-off-by: Vladislav Shpilevoy <v.shpilevoy at tarantool.org>
---
src/box/alter.cc | 121 +++++++++++++++++-------------------
src/box/key_def.cc | 2 -
src/box/memtx_hash.c | 9 ++-
src/box/space.c | 12 +---
src/box/tuple_format.c | 35 +++++++++++
src/box/tuple_format.h | 16 +++++
src/box/vinyl.c | 7 +++
test/box/alter.result | 162 ++++++++++++++++++++++++++++++++++++++++++++----
test/box/alter.test.lua | 63 ++++++++++++++++++-
test/vinyl/ddl.result | 95 ++++++++++++++++++++++++++--
test/vinyl/ddl.test.lua | 26 ++++++++
test/vinyl/gh.result | 2 +-
12 files changed, 454 insertions(+), 96 deletions(-)
diff --git a/src/box/alter.cc b/src/box/alter.cc
index 46c90c883..ba043210a 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -871,6 +871,12 @@ class ModifySpaceFormat: public AlterSpaceOp
* names into an old dictionary and deletes new one.
*/
struct tuple_dictionary *new_dict;
+ /**
+ * Old tuple dictionary stored to rollback in destructor,
+ * if an exception had been raised after alter_def(), but
+ * before alter().
+ */
+ struct tuple_dictionary *old_dict;
/**
* New space definition. It can not be got from alter,
* because alter_def() is called before
@@ -879,14 +885,12 @@ class ModifySpaceFormat: public AlterSpaceOp
struct space_def *new_def;
public:
ModifySpaceFormat(struct alter_space *alter, struct space_def *new_def)
- :AlterSpaceOp(alter), new_dict(NULL), new_def(new_def) {}
+ : AlterSpaceOp(alter), new_dict(NULL), old_dict(NULL),
+ new_def(new_def) {}
+ virtual void alter(struct alter_space *alter);
virtual void alter_def(struct alter_space *alter);
- virtual void rollback(struct alter_space *alter);
- virtual ~ModifySpaceFormat()
- {
- if (new_dict != NULL)
- tuple_dictionary_unref(new_dict);
- }
+ virtual void commit(struct alter_space *alter, int64_t lsn);
+ virtual ~ModifySpaceFormat();
};
void
@@ -898,18 +902,43 @@ ModifySpaceFormat::alter_def(struct alter_space *alter)
* object is deleted later, in destructor.
*/
new_dict = new_def->dict;
- struct tuple_dictionary *old_dict = alter->old_space->def->dict;
+ old_dict = alter->old_space->def->dict;
tuple_dictionary_swap(new_dict, old_dict);
new_def->dict = old_dict;
tuple_dictionary_ref(old_dict);
}
void
-ModifySpaceFormat::rollback(struct alter_space *alter)
+ModifySpaceFormat::alter(struct alter_space *alter)
{
- /* Return old names into the old dict. */
- struct tuple_dictionary *old_dict = alter->old_space->def->dict;
- tuple_dictionary_swap(new_dict, old_dict);
+ struct space *new_space = alter->new_space;
+ struct space *old_space = alter->old_space;
+ struct tuple_format *new_format = new_space->format;
+ struct tuple_format *old_format = old_space->format;
+ if (old_format != NULL) {
+ assert(new_format != NULL);
+ if (! tuple_format1_can_store_format2_tuples(new_format,
+ old_format))
+ space_check_format_xc(new_space, old_space);
+ }
+}
+
+void
+ModifySpaceFormat::commit(struct alter_space *alter, int64_t lsn)
+{
+ (void) alter;
+ (void) lsn;
+ old_dict = NULL;
+}
+
+ModifySpaceFormat::~ModifySpaceFormat()
+{
+ if (new_dict != NULL) {
+ /* Return old names into the old dict. */
+ if (old_dict != NULL)
+ tuple_dictionary_swap(new_dict, old_dict);
+ tuple_dictionary_unref(new_dict);
+ }
}
/** Change non-essential properties of a space. */
@@ -921,7 +950,6 @@ public:
/* New space definition. */
struct space_def *def;
virtual void alter_def(struct alter_space *alter);
- virtual void alter(struct alter_space *alter);
virtual ~ModifySpace();
};
@@ -935,51 +963,6 @@ ModifySpace::alter_def(struct alter_space *alter)
def = NULL;
}
-void
-ModifySpace::alter(struct alter_space *alter)
-{
- struct space *new_space = alter->new_space;
- struct space *old_space = alter->old_space;
- uint32_t old_field_count = old_space->def->field_count;
- uint32_t new_field_count = new_space->def->field_count;
- if (old_field_count >= new_field_count) {
- /* Is checked by space_def_check_compatibility. */
- return;
- }
- struct tuple_format *new_format = new_space->format;
- struct tuple_format *old_format = old_space->format;
- /*
- * A tuples validation can be skipped if fields between
- * old_space->def->field_count and
- * new_space->def->field_count are indexed or have type
- * ANY. If they are indexed, then their type is already
- * checked. Type ANY can store any values.
- * Optimization is inapplicable if
- * new_def->def->field_count > old_format->field_count.
- */
- if (old_format != NULL && new_field_count <= old_format->field_count) {
- assert(new_field_count <= new_format->field_count);
- struct tuple_field *fields = new_format->fields;
- bool are_new_fields_checked = true;
- for (uint32_t i = old_field_count; i < new_field_count; ++i) {
- if (!fields[i].is_key_part &&
- fields[i].type != FIELD_TYPE_ANY) {
- are_new_fields_checked = false;
- break;
- }
- }
- if (are_new_fields_checked) {
- /*
- * If the new space fields are already
- * used by existing indexes, then tuples
- * already are validated by them.
- */
- return;
- }
- }
- space_check_format_xc(new_space, old_space);
-}
-
ModifySpace::~ModifySpace() {
if (def != NULL)
space_def_delete(def);
@@ -1078,9 +1061,20 @@ public:
ModifyIndex(struct alter_space *alter,
struct index_def *new_index_def_arg,
struct index_def *old_index_def_arg)
- :AlterSpaceOp(alter),
- new_index_def(new_index_def_arg),
- old_index_def(old_index_def_arg) {}
+ : AlterSpaceOp(alter), new_index_def(new_index_def_arg),
+ old_index_def(old_index_def_arg) {
+ if (new_index_def->iid == 0 &&
+ key_part_cmp(new_index_def->key_def->parts,
+ new_index_def->key_def->part_count,
+ old_index_def->key_def->parts,
+ old_index_def->key_def->part_count) != 0) {
+ /*
+ * Primary parts have been changed -
+ * update non-unique secondary indexes.
+ */
+ alter->pk_def = new_index_def->key_def;
+ }
+ }
struct index_def *new_index_def;
struct index_def *old_index_def;
virtual void alter_def(struct alter_space *alter);
@@ -1676,8 +1670,8 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
if (index_def_cmp(index_def, old_index->def) == 0) {
/* Index is not changed so just move it. */
(void) new MoveIndex(alter, old_index->def->iid);
- }
- else if (index_def_change_requires_rebuild(old_index->def, index_def)) {
+ } else if (index_def_change_requires_rebuild(old_index->def,
+ index_def)) {
/*
* Operation demands an index rebuild.
*/
@@ -1685,6 +1679,7 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
old_index->def);
index_def_guard.is_active = false;
} else {
+ (void) new ModifySpaceFormat(alter, old_space->def);
/*
* Operation can be done without index rebuild.
*/
diff --git a/src/box/key_def.cc b/src/box/key_def.cc
index 5c91fb9c7..54784b624 100644
--- a/src/box/key_def.cc
+++ b/src/box/key_def.cc
@@ -225,8 +225,6 @@ key_part_check_compatibility(const struct key_part *old_parts,
const struct key_part *old_part = &old_parts[i];
if (old_part->fieldno != new_part->fieldno)
return false;
- if (! field_type1_contains_type2(new_part->type, old_part->type))
- return false;
if (old_part->coll != new_part->coll)
return false;
if (old_part->is_nullable != new_part->is_nullable)
diff --git a/src/box/memtx_hash.c b/src/box/memtx_hash.c
index 78f55eb7d..9d769cb4b 100644
--- a/src/box/memtx_hash.c
+++ b/src/box/memtx_hash.c
@@ -138,6 +138,13 @@ memtx_hash_index_destroy(struct index *base)
free(index);
}
+static void
+memtx_hash_index_update_def(struct index *base)
+{
+ struct memtx_hash_index *index = (struct memtx_hash_index *)base;
+ index->hash_table->arg = index->base.def->key_def;
+}
+
static ssize_t
memtx_hash_index_size(struct index *base)
{
@@ -376,7 +383,7 @@ static const struct index_vtab memtx_hash_index_vtab = {
/* .destroy = */ memtx_hash_index_destroy,
/* .commit_create = */ generic_index_commit_create,
/* .commit_drop = */ generic_index_commit_drop,
- /* .update_def = */ generic_index_update_def,
+ /* .update_def = */ memtx_hash_index_update_def,
/* .size = */ memtx_hash_index_size,
/* .bsize = */ memtx_hash_index_bsize,
/* .min = */ generic_index_min,
diff --git a/src/box/space.c b/src/box/space.c
index 4fbc0607e..168202271 100644
--- a/src/box/space.c
+++ b/src/box/space.c
@@ -296,7 +296,8 @@ space_def_check_compatibility(const struct space_def *old_def,
for (uint32_t i = 0; i < field_count; ++i) {
enum field_type old_type = old_def->fields[i].type;
enum field_type new_type = new_def->fields[i].type;
- if (! field_type1_contains_type2(new_type, old_type)) {
+ if (!field_type1_contains_type2(new_type, old_type) &&
+ !field_type1_contains_type2(old_type, new_type)) {
const char *msg =
tt_sprintf("Can not change a field type from "\
"%s to %s on a not empty space",
@@ -306,15 +307,6 @@ space_def_check_compatibility(const struct space_def *old_def,
msg);
return -1;
}
- if (old_def->fields[i].is_nullable &&
- !new_def->fields[i].is_nullable) {
- const char *msg =
- tt_sprintf("Can not disable is_nullable "\
- "on a not empty space");
- diag_set(ClientError, ER_ALTER_SPACE, old_def->name,
- msg);
- return -1;
- }
}
return 0;
}
diff --git a/src/box/tuple_format.c b/src/box/tuple_format.c
index 3e2c8bf57..1d6499748 100644
--- a/src/box/tuple_format.c
+++ b/src/box/tuple_format.c
@@ -277,6 +277,41 @@ tuple_format_new(struct tuple_format_vtab *vtab, struct key_def * const *keys,
return format;
}
+bool
+tuple_format1_can_store_format2_tuples(const struct tuple_format *format1,
+ const struct tuple_format *format2)
+{
+ if (format1->exact_field_count != format2->exact_field_count)
+ return false;
+ for (uint32_t i = 0; i < format1->field_count; ++i) {
+ const struct tuple_field *field1 = &format1->fields[i];
+ /*
+ * The field is formatted in format1, but not
+ * formatted in format2.
+ */
+ if (i >= format2->field_count) {
+ /*
+ * The field can be defined with no type,
+ * but with a name - it is not
+ * restriction. Nullability is necessary
+ * if a field is absend in some tuples.
+ */
+ if (field1->type == FIELD_TYPE_ANY &&
+ field1->is_nullable)
+ continue;
+ else
+ return false;
+ }
+ const struct tuple_field *field2 = &format2->fields[i];
+ if (! field_type1_contains_type2(field1->type, field2->type))
+ return false;
+ /* Nullability removal - format is restricted. */
+ if (field2->is_nullable && !field1->is_nullable)
+ return false;
+ }
+ return true;
+}
+
bool
tuple_format_eq(const struct tuple_format *a, const struct tuple_format *b)
{
diff --git a/src/box/tuple_format.h b/src/box/tuple_format.h
index d33c77ae6..c047cdb65 100644
--- a/src/box/tuple_format.h
+++ b/src/box/tuple_format.h
@@ -199,6 +199,22 @@ tuple_format_new(struct tuple_format_vtab *vtab, struct key_def * const *keys,
const struct field_def *space_fields,
uint32_t space_field_count, struct tuple_dictionary *dict);
+/**
+ * Check, if @a format1 can store ANY!!! tuples of @a format2. For
+ * example, if a field is not nullable in the format1 and the same
+ * field is nullable in the format2, or the field type is integer
+ * in the format1 and unsigned in the format2, then the format1
+ * can not store the format2 tuples.
+ * @param format1 Tuple format, that possibly can store tuples of
+ * @a format2.
+ * @param format2 Tuple format 2.
+ *
+ * @retval True, if @a format1 can store any tuples of @a format2.
+ */
+bool
+tuple_format1_can_store_format2_tuples(const struct tuple_format *format1,
+ const struct tuple_format *format2);
+
/**
* Check that two tuple formats are identical.
* @param a format a
diff --git a/src/box/vinyl.c b/src/box/vinyl.c
index 94d0d038a..712f278b4 100644
--- a/src/box/vinyl.c
+++ b/src/box/vinyl.c
@@ -1036,6 +1036,13 @@ vinyl_space_prepare_alter(struct space *old_space, struct space *new_space)
return -1;
}
+ if (! tuple_format1_can_store_format2_tuples(new_space->format,
+ old_space->format)) {
+ diag_set(ClientError, ER_UNSUPPORTED, "Vinyl",
+ "non-empty space format incompatible change");
+ return -1;
+ }
+
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) {
diff --git a/test/box/alter.result b/test/box/alter.result
index fdd48419b..0f49c44bf 100644
--- a/test/box/alter.result
+++ b/test/box/alter.result
@@ -1295,7 +1295,7 @@ s = box.schema.space.create('test', {format = format})
pk = s:create_index('pk')
---
...
-t = s:replace{1, 2, 3, '4', 5.5, -6, true, 8, {9, 9}, {val = 10}}
+t = s:replace{1, {2}, 3, '4', 5.5, -6, true, -8, {9, 9}, {val = 10}}
---
...
test_run:cmd("setopt delimiter ';'")
@@ -1329,8 +1329,7 @@ test_run:cmd("setopt delimiter ''");
-- any --X--> unsigned
fail_format_change(2, 'unsigned')
---
-- 'Can''t modify space ''test'': Can not change a field type from any to unsigned
- on a not empty space'
+- 'Tuple field 2 type does not match one required by operation: expected unsigned'
...
-- unsigned -----> any
ok_format_change(3, 'any')
@@ -1385,8 +1384,7 @@ ok_format_change(5, 'scalar')
-- number --X--> integer
fail_format_change(5, 'integer')
---
-- 'Can''t modify space ''test'': Can not change a field type from number to integer
- on a not empty space'
+- 'Tuple field 5 type does not match one required by operation: expected integer'
...
-- integer -----> any
ok_format_change(6, 'any')
@@ -1403,8 +1401,7 @@ ok_format_change(6, 'scalar')
-- integer --X--> unsigned
fail_format_change(6, 'unsigned')
---
-- 'Can''t modify space ''test'': Can not change a field type from integer to unsigned
- on a not empty space'
+- 'Tuple field 6 type does not match one required by operation: expected unsigned'
...
-- boolean -----> any
ok_format_change(7, 'any')
@@ -1427,8 +1424,7 @@ ok_format_change(8, 'any')
-- scalar --X--> unsigned
fail_format_change(8, 'unsigned')
---
-- 'Can''t modify space ''test'': Can not change a field type from scalar to unsigned
- on a not empty space'
+- 'Tuple field 8 type does not match one required by operation: expected unsigned'
...
-- array -----> any
ok_format_change(9, 'any')
@@ -1574,7 +1570,7 @@ format[2] = {name = 'field2', type = 'unsigned'}
...
s:format(format)
---
-- error: Vinyl does not support adding new fields to a non-empty space
+- error: Vinyl does not support non-empty space format incompatible change
...
s:drop()
---
@@ -1647,8 +1643,7 @@ format[2].is_nullable = false
...
s:format(format)
---
-- error: 'Can''t modify space ''test'': Can not disable is_nullable on a not empty
- space'
+- error: 'Tuple field 2 type does not match one required by operation: expected unsigned'
...
s:delete(1)
---
@@ -1658,6 +1653,38 @@ s:delete(1)
s:format(format)
---
...
+-- Disable is_nullable on a non-empty space.
+format[2].is_nullable = true
+---
+...
+s:format(format)
+---
+...
+s:replace{1, 1}
+---
+- [1, 1]
+...
+format[2].is_nullable = false
+---
+...
+s:format(format)
+---
+...
+-- Enable is_nullable on a non-empty space.
+format[2].is_nullable = true
+---
+...
+s:format(format)
+---
+...
+s:replace{1, box.NULL}
+---
+- [1, null]
+...
+s:delete{1}
+---
+- [1, null]
+...
s:format({})
---
...
@@ -1789,6 +1816,65 @@ s:drop()
---
...
--
+-- Allow to restrict space format, if corresponding restrictions
+-- already are defined in indexes.
+--
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+function check_format_restriction(engine, name)
+ local s = box.schema.create_space(name, {engine = engine})
+ local pk = s:create_index('pk')
+ local format = {}
+ format[1] = {name = 'field1'}
+ s:replace{1}
+ s:replace{100}
+ s:replace{0}
+ s:format(format)
+ s:format()
+ format[1].type = 'unsigned'
+ s:format(format)
+end;
+---
+...
+test_run:cmd("setopt delimiter ''");
+---
+- true
+...
+check_format_restriction('memtx', 'test1')
+---
+...
+check_format_restriction('vinyl', 'test2')
+---
+...
+box.space.test1:format()
+---
+- [{'name': 'field1', 'type': 'unsigned'}]
+...
+box.space.test1:select{}
+---
+- - [0]
+ - [1]
+ - [100]
+...
+box.space.test2:format()
+---
+- [{'name': 'field1', 'type': 'unsigned'}]
+...
+box.space.test2:select{}
+---
+- - [0]
+ - [1]
+ - [100]
+...
+box.space.test1:drop()
+---
+...
+box.space.test2:drop()
+---
+...
+--
-- gh-2914: Allow any space name which consists of printable characters
--
identifier = require("identifier")
@@ -1933,3 +2019,55 @@ t3.field_1
s:drop()
---
...
+--
+-- gh-3008. Ensure the change of hash index parts updates hash
+-- key_def.
+--
+s = box.schema.create_space('test')
+---
+...
+pk = s:create_index('pk', {type = 'hash'})
+---
+...
+pk:alter{parts = {{1, 'string'}}}
+---
+...
+s:replace{'1', '1'}
+---
+- ['1', '1']
+...
+s:replace{'1', '2'}
+---
+- ['1', '2']
+...
+pk:select{}
+---
+- - ['1', '2']
+...
+pk:select{'1'}
+---
+- - ['1', '2']
+...
+s:drop()
+---
+...
+--
+-- Ensure that incompatible key parts change validates format.
+--
+s = box.schema.create_space('test')
+---
+...
+pk = s:create_index('pk')
+---
+...
+s:replace{1}
+---
+- [1]
+...
+pk:alter{parts = {{1, 'string'}}} -- Must fail.
+---
+- error: 'Tuple field 1 type does not match one required by operation: expected string'
+...
+s:drop()
+---
+...
diff --git a/test/box/alter.test.lua b/test/box/alter.test.lua
index 08db0ec25..775407fe5 100644
--- a/test/box/alter.test.lua
+++ b/test/box/alter.test.lua
@@ -474,7 +474,7 @@ format[9] = {name = 'field9', type = 'array'}
format[10] = {name = 'field10', type = 'map'}
s = box.schema.space.create('test', {format = format})
pk = s:create_index('pk')
-t = s:replace{1, 2, 3, '4', 5.5, -6, true, 8, {9, 9}, {val = 10}}
+t = s:replace{1, {2}, 3, '4', 5.5, -6, true, -8, {9, 9}, {val = 10}}
test_run:cmd("setopt delimiter ';'")
function fail_format_change(fieldno, new_type)
@@ -635,6 +635,17 @@ s:format(format)
s:delete(1)
-- Disable is_nullable on empty space
s:format(format)
+-- Disable is_nullable on a non-empty space.
+format[2].is_nullable = true
+s:format(format)
+s:replace{1, 1}
+format[2].is_nullable = false
+s:format(format)
+-- Enable is_nullable on a non-empty space.
+format[2].is_nullable = true
+s:format(format)
+s:replace{1, box.NULL}
+s:delete{1}
s:format({})
s:create_index('secondary', { parts = {{2, 'string', is_nullable = true}} })
@@ -685,6 +696,34 @@ s:replace{-2}
s:select{}
s:drop()
+--
+-- Allow to restrict space format, if corresponding restrictions
+-- already are defined in indexes.
+--
+test_run:cmd("setopt delimiter ';'")
+function check_format_restriction(engine, name)
+ local s = box.schema.create_space(name, {engine = engine})
+ local pk = s:create_index('pk')
+ local format = {}
+ format[1] = {name = 'field1'}
+ s:replace{1}
+ s:replace{100}
+ s:replace{0}
+ s:format(format)
+ s:format()
+ format[1].type = 'unsigned'
+ s:format(format)
+end;
+test_run:cmd("setopt delimiter ''");
+check_format_restriction('memtx', 'test1')
+check_format_restriction('vinyl', 'test2')
+box.space.test1:format()
+box.space.test1:select{}
+box.space.test2:format()
+box.space.test2:select{}
+box.space.test1:drop()
+box.space.test2:drop()
+
--
-- gh-2914: Allow any space name which consists of printable characters
--
@@ -756,3 +795,25 @@ t2.field_1
t3.field1
t3.field_1
s:drop()
+
+--
+-- gh-3008. Ensure the change of hash index parts updates hash
+-- key_def.
+--
+s = box.schema.create_space('test')
+pk = s:create_index('pk', {type = 'hash'})
+pk:alter{parts = {{1, 'string'}}}
+s:replace{'1', '1'}
+s:replace{'1', '2'}
+pk:select{}
+pk:select{'1'}
+s:drop()
+
+--
+-- Ensure that incompatible key parts change validates format.
+--
+s = box.schema.create_space('test')
+pk = s:create_index('pk')
+s:replace{1}
+pk:alter{parts = {{1, 'string'}}} -- Must fail.
+s:drop()
diff --git a/test/vinyl/ddl.result b/test/vinyl/ddl.result
index 45a383442..a17455856 100644
--- a/test/vinyl/ddl.result
+++ b/test/vinyl/ddl.result
@@ -47,7 +47,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 a non-empty index
+- error: Vinyl does not support non-empty space format incompatible change
...
#box.space._index:select({space.id})
---
@@ -76,7 +76,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 a non-empty index
+- error: Vinyl does not support non-empty space format incompatible change
...
#box.space._index:select({space.id})
---
@@ -105,7 +105,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 a non-empty index
+- error: Vinyl does not support non-empty space format incompatible change
...
#box.space._index:select({space.id})
---
@@ -125,7 +125,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 a non-empty index
+- error: Vinyl does not support non-empty space format incompatible change
...
box.snapshot()
---
@@ -196,7 +196,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 a non-empty index
+- error: Vinyl does not support non-empty space format incompatible change
...
-- After compaction the REPLACE + DELETE + DELETE = nothing, so
-- the space is now empty and can be altered.
@@ -251,7 +251,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 a non-empty index
+- error: Vinyl does not support non-empty space format incompatible change
...
space:select{}
---
@@ -656,6 +656,89 @@ index = space:create_index('test', { type = 'tree', parts = { 2, 'map' }})
space:drop()
---
...
+--
+-- Allow compatible changes of a non-empty vinyl space.
+--
+space = box.schema.create_space('test', { engine = 'vinyl' })
+---
+...
+pk = space:create_index('primary')
+---
+...
+space:replace{1}
+---
+- [1]
+...
+space:replace{2}
+---
+- [2]
+...
+format = {}
+---
+...
+format[1] = {name = 'field1'}
+---
+...
+format[2] = {name = 'field2', is_nullable = true}
+---
+...
+format[3] = {name = 'field3', is_nullable = true}
+---
+...
+space:format(format)
+---
+...
+t1 = space:replace{3,4,5}
+---
+...
+t2 = space:replace{4,5}
+---
+...
+t1.field1, t1.field2, t1.field3
+---
+- 3
+- 4
+- 5
+...
+t2.field1, t2.field2, t2.field3
+---
+- 4
+- 5
+- null
+...
+t1 = pk:get{1}
+---
+...
+t1.field1, t1.field2, t1.field3
+---
+- 1
+- null
+- null
+...
+box.snapshot()
+---
+- ok
+...
+t1 = pk:get{2}
+---
+...
+t1.field1, t1.field2, t1.field3
+---
+- 2
+- null
+- null
+...
+-- Forbid incompatible change.
+format[2].is_nullable = false
+---
+...
+space:format(format)
+---
+- error: Vinyl does not support non-empty space format incompatible change
+...
+space:drop()
+---
+...
-- gh-3019 default index options
box.space._space:insert{512, 1, 'test', 'vinyl', 0, setmetatable({}, {__serialize = 'map'}), {}}
---
diff --git a/test/vinyl/ddl.test.lua b/test/vinyl/ddl.test.lua
index 53205df99..aa408dc5c 100644
--- a/test/vinyl/ddl.test.lua
+++ b/test/vinyl/ddl.test.lua
@@ -249,6 +249,32 @@ index = space:create_index('test', { type = 'tree', parts = { 2, 'array' }})
index = space:create_index('test', { type = 'tree', parts = { 2, 'map' }})
space:drop()
+--
+-- Allow compatible changes of a non-empty vinyl space.
+--
+space = box.schema.create_space('test', { engine = 'vinyl' })
+pk = space:create_index('primary')
+space:replace{1}
+space:replace{2}
+format = {}
+format[1] = {name = 'field1'}
+format[2] = {name = 'field2', is_nullable = true}
+format[3] = {name = 'field3', is_nullable = true}
+space:format(format)
+t1 = space:replace{3,4,5}
+t2 = space:replace{4,5}
+t1.field1, t1.field2, t1.field3
+t2.field1, t2.field2, t2.field3
+t1 = pk:get{1}
+t1.field1, t1.field2, t1.field3
+box.snapshot()
+t1 = pk:get{2}
+t1.field1, t1.field2, t1.field3
+-- Forbid incompatible change.
+format[2].is_nullable = false
+space:format(format)
+space:drop()
+
-- gh-3019 default index options
box.space._space:insert{512, 1, 'test', 'vinyl', 0, setmetatable({}, {__serialize = 'map'}), {}}
box.space._index:insert{512, 0, 'pk', 'tree', {unique = true}, {{0, 'unsigned'}}}
diff --git a/test/vinyl/gh.result b/test/vinyl/gh.result
index 9c72acc81..fdfd9a0f7 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 a non-empty index
+- error: Vinyl does not support non-empty space format incompatible change
...
s:drop()
---
--
2.14.3 (Apple Git-98)
More information about the Tarantool-patches
mailing list