From f24facdcb80ae8b08dedcd6a989b9342ef8cfaa0 Mon Sep 17 00:00:00 2001
From: Konstantin Osipov <kostja@tarantool.org>
Date: Tue, 20 Feb 2018 21:13:36 +0300
Subject: [PATCH] alter: a follow up on the fix for gh-3008

Update comments and move space_def_check_compatibility to
space_def.[hc].
---
 src/box/space.c        | 48 ----------------------------------------
 src/box/space.h        | 25 ---------------------
 src/box/space_def.c    | 50 ++++++++++++++++++++++++++++++++++++++++++
 src/box/space_def.h    | 26 ++++++++++++++++++++++
 src/box/tuple_format.c | 20 +++++++++++------
 src/box/tuple_format.h | 15 ++++++-------
 src/box/vinyl.c        |  2 +-
 test/box/alter.result  |  2 +-
 test/vinyl/ddl.result  |  2 +-
 9 files changed, 99 insertions(+), 91 deletions(-)

diff --git a/src/box/space.c b/src/box/space.c
index 1682022716..3f0097e650 100644
--- a/src/box/space.c
+++ b/src/box/space.c
@@ -263,54 +263,6 @@ index_name_by_id(struct space *space, uint32_t id)
 	return NULL;
 }
 
-int
-space_def_check_compatibility(const struct space_def *old_def,
-			      const struct space_def *new_def,
-			      bool is_space_empty)
-{
-	if (strcmp(new_def->engine_name, old_def->engine_name) != 0) {
-		diag_set(ClientError, ER_ALTER_SPACE, old_def->name,
-			 "can not change space engine");
-		return -1;
-	}
-	if (new_def->id != old_def->id) {
-		diag_set(ClientError, ER_ALTER_SPACE, old_def->name,
-			 "space id is immutable");
-		return -1;
-	}
-	if (is_space_empty)
-		return 0;
-
-	if (new_def->exact_field_count != 0 &&
-	    new_def->exact_field_count != old_def->exact_field_count) {
-		diag_set(ClientError, ER_ALTER_SPACE, old_def->name,
-			 "can not change field count on a non-empty space");
-		return -1;
-	}
-	if (new_def->opts.temporary != old_def->opts.temporary) {
-		diag_set(ClientError, ER_ALTER_SPACE, old_def->name,
-			 "can not switch temporary flag on a non-empty space");
-		return -1;
-	}
-	uint32_t field_count = MIN(new_def->field_count, old_def->field_count);
-	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) &&
-		    !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",
-					   field_type_strs[old_type],
-					   field_type_strs[new_type]);
-			diag_set(ClientError, ER_ALTER_SPACE, old_def->name,
-				 msg);
-			return -1;
-		}
-	}
-	return 0;
-}
-
 /**
  * Run BEFORE triggers registered for a space. If a trigger
  * changes the current statement, this function updates the
diff --git a/src/box/space.h b/src/box/space.h
index 8918dbd8df..6408eedcb7 100644
--- a/src/box/space.h
+++ b/src/box/space.h
@@ -287,21 +287,6 @@ space_index_def(struct space *space, int n);
 const char *
 index_name_by_id(struct space *space, uint32_t id);
 
-/**
- * Check that a space with @an old_def can be altered to have
- * @a new_def.
- * @param old_def Old space definition.
- * @param new_def New space definition.
- * @param is_space_empty True, if a space is empty.
- *
- * @retval  0 Space definition can be altered to @a new_def.
- * @retval -1 Client error.
- */
-int
-space_def_check_compatibility(const struct space_def *old_def,
-			      const struct space_def *new_def,
-			      bool is_space_empty);
-
 /**
  * Check whether or not the current user can be granted
  * the requested access to the space.
@@ -442,16 +427,6 @@ space_fill_index_map(struct space *space);
 #if defined(__cplusplus)
 } /* extern "C" */
 
-static inline void
-space_def_check_compatibility_xc(const struct space_def *old_def,
-				 const struct space_def *new_def,
-				 bool is_space_empty)
-{
-	if (space_def_check_compatibility(old_def, new_def,
-					  is_space_empty) != 0)
-		diag_raise();
-}
-
 static inline struct space *
 space_new_xc(struct space_def *space_def, struct rlist *key_list)
 {
diff --git a/src/box/space_def.c b/src/box/space_def.c
index ce5872ffb2..ecb5ad723c 100644
--- a/src/box/space_def.c
+++ b/src/box/space_def.c
@@ -31,6 +31,7 @@
 
 #include "space_def.h"
 #include "diag.h"
+#include "error.h"
 
 const struct space_opts space_opts_default = {
 	/* .temporary = */ false,
@@ -141,3 +142,52 @@ space_def_new(uint32_t id, uint32_t uid, uint32_t exact_field_count,
 	}
 	return def;
 }
+
+int
+space_def_check_compatibility(const struct space_def *old_def,
+			      const struct space_def *new_def,
+			      bool is_space_empty)
+{
+	if (strcmp(new_def->engine_name, old_def->engine_name) != 0) {
+		diag_set(ClientError, ER_ALTER_SPACE, old_def->name,
+			 "can not change space engine");
+		return -1;
+	}
+	if (new_def->id != old_def->id) {
+		diag_set(ClientError, ER_ALTER_SPACE, old_def->name,
+			 "space id is immutable");
+		return -1;
+	}
+	if (is_space_empty)
+		return 0;
+
+	if (new_def->exact_field_count != 0 &&
+	    new_def->exact_field_count != old_def->exact_field_count) {
+		diag_set(ClientError, ER_ALTER_SPACE, old_def->name,
+			 "can not change field count on a non-empty space");
+		return -1;
+	}
+	if (new_def->opts.temporary != old_def->opts.temporary) {
+		diag_set(ClientError, ER_ALTER_SPACE, old_def->name,
+			 "can not switch temporary flag on a non-empty space");
+		return -1;
+	}
+	uint32_t field_count = MIN(new_def->field_count, old_def->field_count);
+	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) &&
+		    !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",
+					   field_type_strs[old_type],
+					   field_type_strs[new_type]);
+			diag_set(ClientError, ER_ALTER_SPACE, old_def->name,
+				 msg);
+			return -1;
+		}
+	}
+	return 0;
+}
+
diff --git a/src/box/space_def.h b/src/box/space_def.h
index 97c7e13809..54fe2c712c 100644
--- a/src/box/space_def.h
+++ b/src/box/space_def.h
@@ -133,6 +133,22 @@ space_def_new(uint32_t id, uint32_t uid, uint32_t exact_field_count,
 	      const struct space_opts *opts, const struct field_def *fields,
 	      uint32_t field_count);
 
+/**
+ * Check that a space with @an old_def can be altered to have
+ * @a new_def.
+ * @param old_def Old space definition.
+ * @param new_def New space definition.
+ * @param is_space_empty True, if a space is empty.
+ *
+ * @retval  0 Space definition can be altered to @a new_def.
+ * @retval -1 Client error.
+ */
+int
+space_def_check_compatibility(const struct space_def *old_def,
+			      const struct space_def *new_def,
+			      bool is_space_empty);
+
+
 #if defined(__cplusplus)
 } /* extern "C" */
 
@@ -162,6 +178,16 @@ space_def_new_xc(uint32_t id, uint32_t uid, uint32_t exact_field_count,
 	return ret;
 }
 
+static inline void
+space_def_check_compatibility_xc(const struct space_def *old_def,
+				 const struct space_def *new_def,
+				 bool is_space_empty)
+{
+	if (space_def_check_compatibility(old_def, new_def,
+					  is_space_empty) != 0)
+		diag_raise();
+}
+
 #endif /* __cplusplus */
 
 #endif /* TARANTOOL_BOX_SPACE_DEF_H_INCLUDED */
diff --git a/src/box/tuple_format.c b/src/box/tuple_format.c
index f0eca24bb3..e42fc039ed 100644
--- a/src/box/tuple_format.c
+++ b/src/box/tuple_format.c
@@ -291,15 +291,18 @@ tuple_format1_can_store_format2_tuples(const struct tuple_format *format1,
 	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.
+		 * The field has a data type in format1, but has
+		 * no data type 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.
+			 * The field can get a name added
+			 * for it, and this doesn't require a data
+			 * check.
+			 * If the field is defined as not
+			 * nullable, however, we need a data
+			 * check, since old data may contain
+			 * NULLs or miss the subject field.
 			 */
 			if (field1->type == FIELD_TYPE_ANY &&
 			    field1->is_nullable)
@@ -310,7 +313,10 @@ tuple_format1_can_store_format2_tuples(const struct tuple_format *format1,
 		const struct tuple_field *field2 = &format2->fields[i];
 		if (! field_type1_contains_type2(field1->type, field2->type))
 			return false;
-		/* Nullability removal - format is restricted. */
+		/*
+		 * Do not allow transition from nullable to non-nullable:
+		 * it would require a check of all data in the space.
+		 */
 		if (field2->is_nullable && !field1->is_nullable)
 			return false;
 	}
diff --git a/src/box/tuple_format.h b/src/box/tuple_format.h
index c047cdb655..77c8e404f3 100644
--- a/src/box/tuple_format.h
+++ b/src/box/tuple_format.h
@@ -200,14 +200,13 @@ tuple_format_new(struct tuple_format_vtab *vtab, struct key_def * const *keys,
 		 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.
+ * Check, if @a format1 can store any tuples of @a format2. For
+ * example, if a field is not nullable in format1 and the same
+ * field is nullable in format2, or the field type is integer
+ * in format1 and unsigned in format2, then format1 can not store
+ * format2 tuples.
+ * @param format1 tuple format to check for compatibility of
+ * @param format2 tuple format to check compatibility with
  *
  * @retval True, if @a format1 can store any tuples of @a format2.
  */
diff --git a/src/box/vinyl.c b/src/box/vinyl.c
index 2dc11170d3..69db2c81d3 100644
--- a/src/box/vinyl.c
+++ b/src/box/vinyl.c
@@ -1068,7 +1068,7 @@ vinyl_space_prepare_alter(struct space *old_space, struct space *new_space)
 	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");
+			 "changing space format of a non-empty space");
 		return -1;
 	}
 	return 0;
diff --git a/test/box/alter.result b/test/box/alter.result
index eaf79f1d0a..00e1cb7912 100644
--- a/test/box/alter.result
+++ b/test/box/alter.result
@@ -1565,7 +1565,7 @@ format[2] = {name = 'field2', type = 'unsigned'}
 ...
 s:format(format)
 ---
-- error: Vinyl does not support non-empty space format incompatible change
+- error: Vinyl does not support changing space format of a non-empty space
 ...
 s:drop()
 ---
diff --git a/test/vinyl/ddl.result b/test/vinyl/ddl.result
index a2c68e428c..6f393c69e6 100644
--- a/test/vinyl/ddl.result
+++ b/test/vinyl/ddl.result
@@ -770,7 +770,7 @@ format[2].is_nullable = false
 ...
 space:format(format)
 ---
-- error: Vinyl does not support non-empty space format incompatible change
+- error: Vinyl does not support changing space format of a non-empty space
 ...
 space:drop()
 ---
-- 
GitLab