[tarantool-patches] [PATCH 05/13] tuple: implement update by field name

Vladislav Shpilevoy v.shpilevoy at tarantool.org
Tue Aug 13 02:05:15 MSK 2019


Tuple fields can be named, accessed by name, indexed by name, but
till this commit field names couldn't be used in update
operations. Now it is possible.

This patch is a teaser of updates by JSON path.

Part of #1261
---
 src/box/errcode.h           |   4 +-
 src/box/lua/tuple.c         |   5 +-
 src/box/memtx_space.c       |  17 ++--
 src/box/space.c             |   9 ++-
 src/box/sql/insert.c        |   3 +-
 src/box/sql/resolve.c       |   4 +-
 src/box/sql/update.c        |   2 +-
 src/box/tuple.c             |  12 +--
 src/box/tuple_update.c      | 152 ++++++++++++++++++++++--------------
 src/box/tuple_update.h      |  15 ++--
 src/box/vinyl.c             |   9 ++-
 src/box/vy_upsert.c         |   6 +-
 test/box/misc.result        |   3 +-
 test/box/update.result      |   2 +-
 test/engine/update.result   |  67 ++++++++++++++++
 test/engine/update.test.lua |  28 +++++++
 test/engine/upsert.result   |   2 +-
 test/unit/column_mask.c     |   5 +-
 18 files changed, 247 insertions(+), 98 deletions(-)

diff --git a/src/box/errcode.h b/src/box/errcode.h
index 817275b97..dd55bd42a 100644
--- a/src/box/errcode.h
+++ b/src/box/errcode.h
@@ -205,14 +205,14 @@ struct errcode_record {
 	/*150 */_(ER_CANT_CREATE_COLLATION,	"Failed to initialize collation: %s.") \
 	/*151 */_(ER_WRONG_COLLATION_OPTIONS,	"Wrong collation options (field %u): %s") \
 	/*152 */_(ER_NULLABLE_PRIMARY,		"Primary index of space '%s' can not contain nullable parts") \
-	/*153 */_(ER_NO_SUCH_FIELD_NAME,	"Field '%s' was not found in space '%s' format") \
+	/*153 */_(ER_NO_SUCH_FIELD_NAME_IN_SPACE,	"Field '%s' was not found in space '%s' format") \
 	/*154 */_(ER_TRANSACTION_YIELD,		"Transaction has been aborted by a fiber yield") \
 	/*155 */_(ER_NO_SUCH_GROUP,		"Replication group '%s' does not exist") \
 	/*156 */_(ER_SQL_BIND_VALUE,            "Bind value for parameter %s is out of range for type %s") \
 	/*157 */_(ER_SQL_BIND_TYPE,             "Bind value type %s for parameter %s is not supported") \
 	/*158 */_(ER_SQL_BIND_PARAMETER_MAX,    "SQL bind parameter limit reached: %d") \
 	/*159 */_(ER_SQL_EXECUTE,               "Failed to execute SQL statement: %s") \
-	/*160 */_(ER_UNUSED,			"") \
+	/*160 */_(ER_NO_SUCH_FIELD_NAME,	"Field '%s' was not found in the tuple") \
 	/*161 */_(ER_SQL_BIND_NOT_FOUND,	"Parameter %s was not found in the statement") \
 	/*162 */_(ER_ACTION_MISMATCH,		"Field %s contains %s on conflict action, but %s in index parts") \
 	/*163 */_(ER_VIEW_MISSING_SQL,		"Space declared as a view must have SQL statement") \
diff --git a/src/box/lua/tuple.c b/src/box/lua/tuple.c
index 2fbfb1473..3902288bf 100644
--- a/src/box/lua/tuple.c
+++ b/src/box/lua/tuple.c
@@ -439,6 +439,7 @@ lbox_tuple_transform(struct lua_State *L)
 	const char *old_data = tuple_data_range(tuple, &bsize);
 	struct region *region = &fiber()->gc;
 	size_t used = region_used(region);
+	struct tuple_format *format = tuple_format(tuple);
 	struct tuple *new_tuple = NULL;
 	/*
 	 * Can't use box_tuple_update() since transform must reset
@@ -449,8 +450,8 @@ lbox_tuple_transform(struct lua_State *L)
 	 */
 	const char *new_data =
 		tuple_update_execute(buf->buf, buf->buf + ibuf_used(buf),
-				     old_data, old_data + bsize, &new_size, 1,
-				     NULL);
+				     old_data, old_data + bsize, format->dict,
+				     &new_size, 1, NULL);
 	if (new_data != NULL)
 		new_tuple = tuple_new(box_tuple_format_default(),
 				      new_data, new_data + new_size);
diff --git a/src/box/memtx_space.c b/src/box/memtx_space.c
index d63e8a5ca..29956c059 100644
--- a/src/box/memtx_space.c
+++ b/src/box/memtx_space.c
@@ -417,15 +417,16 @@ memtx_space_execute_update(struct space *space, struct txn *txn,
 
 	/* Update the tuple; legacy, request ops are in request->tuple */
 	uint32_t new_size = 0, bsize;
+	struct tuple_format *format = space->format;
 	const char *old_data = tuple_data_range(old_tuple, &bsize);
 	const char *new_data =
 		tuple_update_execute(request->tuple, request->tuple_end,
-				     old_data, old_data + bsize,
+				     old_data, old_data + bsize, format->dict,
 				     &new_size, request->index_base, NULL);
 	if (new_data == NULL)
 		return -1;
 
-	stmt->new_tuple = memtx_tuple_new(space->format, new_data,
+	stmt->new_tuple = memtx_tuple_new(format, new_data,
 					  new_data + new_size);
 	if (stmt->new_tuple == NULL)
 		return -1;
@@ -471,6 +472,7 @@ memtx_space_execute_upsert(struct space *space, struct txn *txn,
 	if (index_get(index, key, part_count, &old_tuple) != 0)
 		return -1;
 
+	struct tuple_format *format = space->format;
 	if (old_tuple == NULL) {
 		/**
 		 * Old tuple was not found. A write optimized
@@ -489,11 +491,11 @@ memtx_space_execute_upsert(struct space *space, struct txn *txn,
 		 * @sa https://github.com/tarantool/tarantool/issues/1156
 		 */
 		if (tuple_update_check_ops(request->ops, request->ops_end,
+					   format->dict,
 					   request->index_base) != 0) {
 			return -1;
 		}
-		stmt->new_tuple = memtx_tuple_new(space->format,
-						  request->tuple,
+		stmt->new_tuple = memtx_tuple_new(format, request->tuple,
 						  request->tuple_end);
 		if (stmt->new_tuple == NULL)
 			return -1;
@@ -511,12 +513,13 @@ memtx_space_execute_upsert(struct space *space, struct txn *txn,
 		const char *new_data =
 			tuple_upsert_execute(request->ops, request->ops_end,
 					     old_data, old_data + bsize,
-					     &new_size, request->index_base,
-					     false, &column_mask);
+					     format->dict, &new_size,
+					     request->index_base, false,
+					     &column_mask);
 		if (new_data == NULL)
 			return -1;
 
-		stmt->new_tuple = memtx_tuple_new(space->format, new_data,
+		stmt->new_tuple = memtx_tuple_new(format, new_data,
 						  new_data + new_size);
 		if (stmt->new_tuple == NULL)
 			return -1;
diff --git a/src/box/space.c b/src/box/space.c
index fffe03c41..b9a830e78 100644
--- a/src/box/space.c
+++ b/src/box/space.c
@@ -397,7 +397,8 @@ space_before_replace(struct space *space, struct txn *txn,
 		old_data_end = old_data + old_size;
 		new_data = tuple_update_execute(request->tuple,
 						request->tuple_end, old_data,
-						old_data_end, &new_size,
+						old_data_end,
+						space->format->dict, &new_size,
 						request->index_base, NULL);
 		if (new_data == NULL)
 			return -1;
@@ -420,6 +421,7 @@ space_before_replace(struct space *space, struct txn *txn,
 			new_data_end = request->tuple_end;
 			if (tuple_update_check_ops(request->ops,
 						   request->ops_end,
+						   space->format->dict,
 						   request->index_base) != 0)
 				return -1;
 			break;
@@ -428,8 +430,9 @@ space_before_replace(struct space *space, struct txn *txn,
 		old_data_end = old_data + old_size;
 		new_data = tuple_upsert_execute(request->ops, request->ops_end,
 						old_data, old_data_end,
-						&new_size, request->index_base,
-						false, NULL);
+						space->format->dict, &new_size,
+						request->index_base, false,
+						NULL);
 		new_data_end = new_data + new_size;
 		break;
 	default:
diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index 1c956567f..42b839166 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -373,7 +373,8 @@ sqlInsert(Parse * pParse,	/* Parser context */
 				}
 			}
 			if (j >= (int) space_def->field_count) {
-				diag_set(ClientError, ER_NO_SUCH_FIELD_NAME,
+				diag_set(ClientError,
+					 ER_NO_SUCH_FIELD_NAME_IN_SPACE,
 					 pColumn->a[i].zName,
 					 pTabList->a[0].zName);
 				pParse->is_aborted = true;
diff --git a/src/box/sql/resolve.c b/src/box/sql/resolve.c
index 0b90edd06..bb2e94e9e 100644
--- a/src/box/sql/resolve.c
+++ b/src/box/sql/resolve.c
@@ -431,8 +431,8 @@ lookupName(Parse * pParse,	/* The parsing context */
 		if (zTab == NULL) {
 			diag_set(ClientError, ER_SQL_CANT_RESOLVE_FIELD, zCol);
 		} else {
-			diag_set(ClientError, ER_NO_SUCH_FIELD_NAME, zCol,
-				 zTab);
+			diag_set(ClientError, ER_NO_SUCH_FIELD_NAME_IN_SPACE,
+				 zCol, zTab);
 		}
 		pParse->is_aborted = true;
 		pTopNC->nErr++;
diff --git a/src/box/sql/update.c b/src/box/sql/update.c
index 7f9358c05..2d7ebf8cd 100644
--- a/src/box/sql/update.c
+++ b/src/box/sql/update.c
@@ -167,7 +167,7 @@ sqlUpdate(Parse * pParse,		/* The parser context */
 			}
 		}
 		if (j >= (int)def->field_count) {
-			diag_set(ClientError, ER_NO_SUCH_FIELD_NAME,
+			diag_set(ClientError, ER_NO_SUCH_FIELD_NAME_IN_SPACE,
 				 pChanges->a[i].zName, def->name);
 			pParse->is_aborted = true;
 			goto update_cleanup;
diff --git a/src/box/tuple.c b/src/box/tuple.c
index beb290a9f..bf4ea711d 100644
--- a/src/box/tuple.c
+++ b/src/box/tuple.c
@@ -707,15 +707,15 @@ box_tuple_update(box_tuple_t *tuple, const char *expr, const char *expr_end)
 	const char *old_data = tuple_data_range(tuple, &bsize);
 	struct region *region = &fiber()->gc;
 	size_t used = region_used(region);
+	struct tuple_format *format = tuple_format(tuple);
 	const char *new_data =
 		tuple_update_execute(expr, expr_end, old_data, old_data + bsize,
-				     &new_size, 1, NULL);
+				     format->dict, &new_size, 1, NULL);
 	if (new_data == NULL) {
 		region_truncate(region, used);
 		return NULL;
 	}
-	struct tuple *ret = tuple_new(tuple_format(tuple), new_data,
-				      new_data + new_size);
+	struct tuple *ret = tuple_new(format, new_data, new_data + new_size);
 	region_truncate(region, used);
 	if (ret != NULL)
 		return tuple_bless(ret);
@@ -729,16 +729,16 @@ box_tuple_upsert(box_tuple_t *tuple, const char *expr, const char *expr_end)
 	const char *old_data = tuple_data_range(tuple, &bsize);
 	struct region *region = &fiber()->gc;
 	size_t used = region_used(region);
+	struct tuple_format *format = tuple_format(tuple);
 	const char *new_data =
 		tuple_upsert_execute(expr, expr_end, old_data, old_data + bsize,
-				     &new_size, 1, false, NULL);
+				     format->dict, &new_size, 1, false, NULL);
 	if (new_data == NULL) {
 		region_truncate(region, used);
 		return NULL;
 	}
 
-	struct tuple *ret = tuple_new(tuple_format(tuple),
-				      new_data, new_data + new_size);
+	struct tuple *ret = tuple_new(format, new_data, new_data + new_size);
 	region_truncate(region, used);
 	if (ret != NULL)
 		return tuple_bless(ret);
diff --git a/src/box/tuple_update.c b/src/box/tuple_update.c
index 21ea876c9..34e0e767f 100644
--- a/src/box/tuple_update.c
+++ b/src/box/tuple_update.c
@@ -41,6 +41,8 @@
 #include <bit/int96.h>
 #include "column_mask.h"
 #include "fiber.h"
+#include "tuple_dictionary.h"
+#include "tt_static.h"
 
 /** UPDATE request implementation.
  * UPDATE request is represented by a sequence of operations, each
@@ -929,12 +931,87 @@ update_op_by(char opcode)
 	}
 }
 
+/**
+ * Decode an update operation from MessagePack.
+ * @param[out] op Update operation.
+ * @param index_base Field numbers base: 0 or 1.
+ * @param dict Dictionary to lookup field number by a name.
+ * @param expr MessagePack.
+ *
+ * @retval 0 Success.
+ * @retval -1 Client error.
+ */
+static inline int
+update_op_decode(struct update_op *op, int index_base,
+		 struct tuple_dictionary *dict, const char **expr)
+{
+	if (mp_typeof(**expr) != MP_ARRAY) {
+		diag_set(ClientError, ER_ILLEGAL_PARAMS, "update operation "
+			 "must be an array {op,..}");
+		return -1;
+	}
+	uint32_t len, args = mp_decode_array(expr);
+	if (args < 1) {
+		diag_set(ClientError, ER_ILLEGAL_PARAMS, "update operation "\
+			 "must be an array {op,..}, got empty array");
+		return -1;
+	}
+	if (mp_typeof(**expr) != MP_STR) {
+		diag_set(ClientError, ER_ILLEGAL_PARAMS,
+			 "update operation name must be a string");
+		return -1;
+	}
+	op->opcode = *mp_decode_str(expr, &len);
+	op->meta = update_op_by(op->opcode);
+	if (op->meta == NULL)
+		return -1;
+	if (args != op->meta->args) {
+		diag_set(ClientError, ER_UNKNOWN_UPDATE_OP);
+		return -1;
+	}
+	int32_t field_no;
+	switch(mp_typeof(**expr)) {
+	case MP_INT:
+	case MP_UINT: {
+		if (mp_read_i32(index_base, op, expr, &field_no) != 0)
+			return -1;
+		if (field_no - index_base >= 0) {
+			op->field_no = field_no - index_base;
+		} else if (field_no < 0) {
+			op->field_no = field_no;
+		} else {
+			diag_set(ClientError, ER_NO_SUCH_FIELD_NO, field_no);
+			return -1;
+		}
+		break;
+	}
+	case MP_STR: {
+		const char *path = mp_decode_str(expr, &len);
+		uint32_t field_no, hash = field_name_hash(path, len);
+		if (tuple_fieldno_by_name(dict, path, len, hash,
+					  &field_no) != 0) {
+			diag_set(ClientError, ER_NO_SUCH_FIELD_NAME,
+				 tt_cstr(path, len));
+			return -1;
+		}
+		op->field_no = (int32_t) field_no;
+		break;
+	}
+	default:
+		diag_set(ClientError, ER_ILLEGAL_PARAMS,
+			 "field id must be a number or a string");
+		return -1;
+	}
+	return op->meta->read_arg(index_base, op, expr);
+}
+
 /**
  * Read and check update operations and fill column mask.
  *
  * @param[out] update Update meta.
  * @param expr MessagePack array of operations.
  * @param expr_end End of the @expr.
+ * @param dict Dictionary to lookup field number by a name.
  * @param field_count_hint Field count in the updated tuple. If
  *        there is no tuple at hand (for example, when we are
  *        reading UPSERT operations), then 0 for field count will
@@ -948,7 +1025,8 @@ update_op_by(char opcode)
  */
 static int
 update_read_ops(struct tuple_update *update, const char *expr,
-		const char *expr_end, int32_t field_count_hint)
+		const char *expr_end, struct tuple_dictionary *dict,
+		int32_t field_count_hint)
 {
 	if (mp_typeof(*expr) != MP_ARRAY) {
 		diag_set(ClientError, ER_ILLEGAL_PARAMS,
@@ -974,59 +1052,14 @@ update_read_ops(struct tuple_update *update, const char *expr,
 	struct update_op *op = update->ops;
 	struct update_op *ops_end = op + update->op_count;
 	for (; op < ops_end; op++) {
-		if (mp_typeof(*expr) != MP_ARRAY) {
-			diag_set(ClientError, ER_ILLEGAL_PARAMS,
-				 "update operation"
-				 " must be an array {op,..}");
-			return -1;
-		}
-		/* Read operation */
-		uint32_t args, len;
-		args = mp_decode_array(&expr);
-		if (args < 1) {
-			diag_set(ClientError, ER_ILLEGAL_PARAMS,
-				 "update operation must be an "
-				 "array {op,..}, got empty array");
-			return -1;
-		}
-		if (mp_typeof(*expr) != MP_STR) {
-			diag_set(ClientError, ER_ILLEGAL_PARAMS,
-				 "update operation name must be a string");
-			return -1;
-		}
-
-		op->opcode = *mp_decode_str(&expr, &len);
-		op->meta = update_op_by(op->opcode);
-		if (op->meta == NULL)
-			return -1;
-		if (args != op->meta->args) {
-			diag_set(ClientError, ER_UNKNOWN_UPDATE_OP);
-			return -1;
-		}
-		if (mp_typeof(*expr) != MP_INT && mp_typeof(*expr) != MP_UINT) {
-			diag_set(ClientError, ER_ILLEGAL_PARAMS,
-				 "field id must be a number");
-			return -1;
-		}
-		int32_t field_no = 0;
-		if (mp_read_i32(update->index_base, op, &expr, &field_no))
-			return -1;
-		if (field_no - update->index_base >= 0) {
-			op->field_no = field_no - update->index_base;
-		} else if (field_no < 0) {
-			op->field_no = field_no;
-		} else {
-			diag_set(ClientError, ER_NO_SUCH_FIELD_NO, field_no);
+		if (update_op_decode(op, update->index_base, dict, &expr) != 0)
 			return -1;
-		}
-		if (op->meta->read_arg(update->index_base, op, &expr))
-			return -1;
-
 		/*
 		 * Continue collecting the changed columns
 		 * only if there are unset bits in the mask.
 		 */
 		if (column_mask != COLUMN_MASK_FULL) {
+			int32_t field_no;
 			if (op->field_no >= 0)
 				field_no = op->field_no;
 			else if (op->opcode != '!')
@@ -1184,24 +1217,25 @@ update_finish(struct tuple_update *update, uint32_t *p_tuple_len)
 }
 
 int
-tuple_update_check_ops(const char *expr, const char *expr_end, int index_base)
+tuple_update_check_ops(const char *expr, const char *expr_end,
+		       struct tuple_dictionary *dict, int index_base)
 {
 	struct tuple_update update;
 	update_init(&update, index_base);
-	return update_read_ops(&update, expr, expr_end, 0);
+	return update_read_ops(&update, expr, expr_end, dict, 0);
 }
 
 const char *
 tuple_update_execute(const char *expr,const char *expr_end,
 		     const char *old_data, const char *old_data_end,
-		     uint32_t *p_tuple_len, int index_base,
-		     uint64_t *column_mask)
+		     struct tuple_dictionary *dict, uint32_t *p_tuple_len,
+		     int index_base, uint64_t *column_mask)
 {
 	struct tuple_update update;
 	update_init(&update, index_base);
 	uint32_t field_count = mp_decode_array(&old_data);
 
-	if (update_read_ops(&update, expr, expr_end, field_count) != 0)
+	if (update_read_ops(&update, expr, expr_end, dict, field_count) != 0)
 		return NULL;
 	if (update_do_ops(&update, old_data, old_data_end, field_count))
 		return NULL;
@@ -1214,14 +1248,14 @@ tuple_update_execute(const char *expr,const char *expr_end,
 const char *
 tuple_upsert_execute(const char *expr,const char *expr_end,
 		     const char *old_data, const char *old_data_end,
-		     uint32_t *p_tuple_len, int index_base, bool suppress_error,
-		     uint64_t *column_mask)
+		     struct tuple_dictionary *dict, uint32_t *p_tuple_len,
+		     int index_base, bool suppress_error, uint64_t *column_mask)
 {
 	struct tuple_update update;
 	update_init(&update, index_base);
 	uint32_t field_count = mp_decode_array(&old_data);
 
-	if (update_read_ops(&update, expr, expr_end, field_count) != 0)
+	if (update_read_ops(&update, expr, expr_end, dict, field_count) != 0)
 		return NULL;
 	if (upsert_do_ops(&update, old_data, old_data_end, field_count,
 			  suppress_error))
@@ -1235,14 +1269,16 @@ tuple_upsert_execute(const char *expr,const char *expr_end,
 const char *
 tuple_upsert_squash(const char *expr1, const char *expr1_end,
 		    const char *expr2, const char *expr2_end,
-		    size_t *result_size, int index_base)
+		    struct tuple_dictionary *dict, size_t *result_size,
+		    int index_base)
 {
 	const char *expr[2] = {expr1, expr2};
 	const char *expr_end[2] = {expr1_end, expr2_end};
 	struct tuple_update update[2];
 	for (int j = 0; j < 2; j++) {
 		update_init(&update[j], index_base);
-		if (update_read_ops(&update[j], expr[j], expr_end[j], 0))
+		if (update_read_ops(&update[j], expr[j], expr_end[j],
+				    dict, 0) != 0)
 			return NULL;
 		mp_decode_array(&expr[j]);
 		int32_t prev_field_no = index_base - 1;
diff --git a/src/box/tuple_update.h b/src/box/tuple_update.h
index 37faa1918..b6210dd38 100644
--- a/src/box/tuple_update.h
+++ b/src/box/tuple_update.h
@@ -44,19 +44,23 @@ enum {
 	BOX_UPDATE_OP_CNT_MAX = 4000,
 };
 
+struct tuple_dictionary;
+
 int
-tuple_update_check_ops(const char *expr, const char *expr_end, int index_base);
+tuple_update_check_ops(const char *expr, const char *expr_end,
+		       struct tuple_dictionary *dict, int index_base);
 
 const char *
 tuple_update_execute(const char *expr,const char *expr_end,
 		     const char *old_data, const char *old_data_end,
-		     uint32_t *p_new_size, int index_base,
-		     uint64_t *column_mask);
+		     struct tuple_dictionary *dict, uint32_t *p_new_size,
+		     int index_base, uint64_t *column_mask);
 
 const char *
 tuple_upsert_execute(const char *expr, const char *expr_end,
 		     const char *old_data, const char *old_data_end,
-		     uint32_t *p_new_size, int index_base, bool suppress_error,
+		     struct tuple_dictionary *dict, uint32_t *p_new_size,
+		     int index_base, bool suppress_error,
 		     uint64_t *column_mask);
 
 /**
@@ -72,7 +76,8 @@ tuple_upsert_execute(const char *expr, const char *expr_end,
 const char *
 tuple_upsert_squash(const char *expr1, const char *expr1_end,
 		    const char *expr2, const char *expr2_end,
-		    size_t *result_size, int index_base);
+		    struct tuple_dictionary *dict, size_t *result_size,
+		    int index_base);
 
 #if defined(__cplusplus)
 } /* extern "C" */
diff --git a/src/box/vinyl.c b/src/box/vinyl.c
index 4c5c08852..5fdfe0aa7 100644
--- a/src/box/vinyl.c
+++ b/src/box/vinyl.c
@@ -1943,7 +1943,8 @@ vy_update(struct vy_env *env, struct vy_tx *tx, struct txn_stmt *stmt,
 	const char *old_tuple = tuple_data_range(stmt->old_tuple, &old_size);
 	const char *old_tuple_end = old_tuple + old_size;
 	new_tuple = tuple_update_execute(request->tuple, request->tuple_end,
-					 old_tuple, old_tuple_end, &new_size,
+					 old_tuple, old_tuple_end,
+					 pk->mem_format->dict, &new_size,
 					 request->index_base, &column_mask);
 	if (new_tuple == NULL)
 		return -1;
@@ -2122,7 +2123,8 @@ vy_upsert(struct vy_env *env, struct vy_tx *tx, struct txn_stmt *stmt,
 		return 0;
 	/* Check update operations. */
 	if (tuple_update_check_ops(request->ops, request->ops_end,
-				   request->index_base)) {
+				   pk->mem_format->dict,
+				   request->index_base) != 0) {
 		return -1;
 	}
 	if (request->index_base != 0) {
@@ -2180,7 +2182,8 @@ vy_upsert(struct vy_env *env, struct vy_tx *tx, struct txn_stmt *stmt,
 
 	/* Apply upsert operations to the old tuple. */
 	new_tuple = tuple_upsert_execute(ops, ops_end, old_tuple, old_tuple_end,
-					 &new_size, 0, false, &column_mask);
+					 pk->mem_format->dict, &new_size, 0,
+					 false, &column_mask);
 	if (new_tuple == NULL)
 		return -1;
 	/*
diff --git a/src/box/vy_upsert.c b/src/box/vy_upsert.c
index 1c55f86c2..817d29cfd 100644
--- a/src/box/vy_upsert.c
+++ b/src/box/vy_upsert.c
@@ -58,7 +58,7 @@ vy_upsert_try_to_squash(struct tuple_format *format,
 	size_t squashed_size;
 	const char *squashed =
 		tuple_upsert_squash(old_serie, old_serie_end,
-				    new_serie, new_serie_end,
+				    new_serie, new_serie_end, format->dict,
 				    &squashed_size, 0);
 	if (squashed == NULL)
 		return 0;
@@ -119,8 +119,8 @@ vy_apply_upsert(struct tuple *new_stmt, struct tuple *old_stmt,
 	uint8_t old_type = vy_stmt_type(old_stmt);
 	uint64_t column_mask = COLUMN_MASK_FULL;
 	result_mp = tuple_upsert_execute(new_ops, new_ops_end, result_mp,
-					 result_mp_end, &mp_size, 0,
-					 suppress_error, &column_mask);
+					 result_mp_end, format->dict, &mp_size,
+					 0, suppress_error, &column_mask);
 	result_mp_end = result_mp + mp_size;
 	if (old_type != IPROTO_UPSERT) {
 		assert(old_type == IPROTO_INSERT ||
diff --git a/test/box/misc.result b/test/box/misc.result
index 7a15dabf0..f45e0514c 100644
--- a/test/box/misc.result
+++ b/test/box/misc.result
@@ -483,13 +483,14 @@ t;
   150: box.error.CANT_CREATE_COLLATION
   151: box.error.WRONG_COLLATION_OPTIONS
   152: box.error.NULLABLE_PRIMARY
-  153: box.error.NO_SUCH_FIELD_NAME
+  153: box.error.NO_SUCH_FIELD_NAME_IN_SPACE
   154: box.error.TRANSACTION_YIELD
   155: box.error.NO_SUCH_GROUP
   156: box.error.SQL_BIND_VALUE
   157: box.error.SQL_BIND_TYPE
   158: box.error.SQL_BIND_PARAMETER_MAX
   159: box.error.SQL_EXECUTE
+  160: box.error.NO_SUCH_FIELD_NAME
   161: box.error.SQL_BIND_NOT_FOUND
   162: box.error.ACTION_MISMATCH
   163: box.error.VIEW_MISSING_SQL
diff --git a/test/box/update.result b/test/box/update.result
index a3f731b55..f5de3bc09 100644
--- a/test/box/update.result
+++ b/test/box/update.result
@@ -794,7 +794,7 @@ s:update({0}, {{'+', 0}})
 ...
 s:update({0}, {{'+', '+', '+'}})
 ---
-- error: Illegal parameters, field id must be a number
+- error: Field '+' was not found in the tuple
 ...
 s:update({0}, {{0, 0, 0}})
 ---
diff --git a/test/engine/update.result b/test/engine/update.result
index 69293e468..f181924f3 100644
--- a/test/engine/update.result
+++ b/test/engine/update.result
@@ -788,3 +788,70 @@ sk:select()
 s:drop()
 ---
 ...
+--
+-- gh-1261: tuple update by JSON.
+-- At first, test tuple update by field names.
+--
+format = {}
+---
+...
+format[1] = {'field1', 'unsigned'}
+---
+...
+format[2] = {'field2', 'array'}
+---
+...
+format[3] = {'field3', 'map'}
+---
+...
+format[4] = {'field4', 'string'}
+---
+...
+format[5] = {'field5', 'any'}
+---
+...
+format[6] = {'field6', 'integer'}
+---
+...
+format[7] = {'[1]', 'unsigned'}
+---
+...
+s = box.schema.create_space('test', {format = format})
+---
+...
+pk = s:create_index('pk')
+---
+...
+t = s:replace{1, {10, 11, 12}, {a = 20, b = 21, c = 22}, 'abcdefgh', true, -100, 200}
+---
+...
+t:update({{'+', 'field1', 1}})
+---
+- [2, [10, 11, 12], {'b': 21, 'a': 20, 'c': 22}, 'abcdefgh', true, -100, 200]
+...
+t:update({{'=', 'field2', {13, 14, 15}}})
+---
+- [1, [13, 14, 15], {'b': 21, 'a': 20, 'c': 22}, 'abcdefgh', true, -100, 200]
+...
+t:update({{':', 'field4', 3, 3, 'bbccdd'}, {'+', 'field6', 50}, {'!', 7, 300}})
+---
+- [1, [10, 11, 12], {'b': 21, 'a': 20, 'c': 22}, 'abbbccddfgh', true, -50, 300, 200]
+...
+-- Any path is interpreted as a field name first. And only then
+-- as JSON.
+t:update({{'+', '[1]', 50}})
+---
+- [1, [10, 11, 12], {'b': 21, 'a': 20, 'c': 22}, 'abcdefgh', true, -100, 250]
+...
+-- JSON paths are not allowed yet.
+t:update({{'=', 'field2[1]', 13}})
+---
+- error: Field 'field2[1]' was not found in the tuple
+...
+s:update({1}, {{'=', 'field3', {d = 30, e = 31, f = 32}}})
+---
+- [1, [10, 11, 12], {'d': 30, 'f': 32, 'e': 31}, 'abcdefgh', true, -100, 200]
+...
+s:drop()
+---
+...
diff --git a/test/engine/update.test.lua b/test/engine/update.test.lua
index 51263f577..4ca2589e4 100644
--- a/test/engine/update.test.lua
+++ b/test/engine/update.test.lua
@@ -134,3 +134,31 @@ box.begin() s:update(1, {{'=', 2, 2}}) s:update(1, {{'=', 3, 2}}) box.commit()
 pk:select()
 sk:select()
 s:drop()
+
+--
+-- gh-1261: tuple update by JSON.
+-- At first, test tuple update by field names.
+--
+format = {}
+format[1] = {'field1', 'unsigned'}
+format[2] = {'field2', 'array'}
+format[3] = {'field3', 'map'}
+format[4] = {'field4', 'string'}
+format[5] = {'field5', 'any'}
+format[6] = {'field6', 'integer'}
+format[7] = {'[1]', 'unsigned'}
+s = box.schema.create_space('test', {format = format})
+pk = s:create_index('pk')
+t = s:replace{1, {10, 11, 12}, {a = 20, b = 21, c = 22}, 'abcdefgh', true, -100, 200}
+t:update({{'+', 'field1', 1}})
+t:update({{'=', 'field2', {13, 14, 15}}})
+t:update({{':', 'field4', 3, 3, 'bbccdd'}, {'+', 'field6', 50}, {'!', 7, 300}})
+-- Any path is interpreted as a field name first. And only then
+-- as JSON.
+t:update({{'+', '[1]', 50}})
+-- JSON paths are not allowed yet.
+t:update({{'=', 'field2[1]', 13}})
+
+s:update({1}, {{'=', 'field3', {d = 30, e = 31, f = 32}}})
+
+s:drop()
diff --git a/test/engine/upsert.result b/test/engine/upsert.result
index b35545588..47da307fa 100644
--- a/test/engine/upsert.result
+++ b/test/engine/upsert.result
@@ -2108,7 +2108,7 @@ test(t, {{'&', 3, 1}, {'&', 2, 1}, {'&', 4, 1}}, {{1, '1', 1, '1'}})
 ...
 test(t, {{'&', 'a', 3}, {'+', 3, 3}}, {{1, '1', 1, '1'}})
 ---
-- error: Illegal parameters, field id must be a number
+- error: Field 'a' was not found in the tuple
 ...
 test(t, {{'+', 3, 3}, {'&', 3, 'a'}}, {{1, '1', 1, '1'}})
 ---
diff --git a/test/unit/column_mask.c b/test/unit/column_mask.c
index 3ffd351ac..5ee8b7332 100644
--- a/test/unit/column_mask.c
+++ b/test/unit/column_mask.c
@@ -129,8 +129,9 @@ check_update_result(const struct tuple_template *original,
 	uint64_t column_mask;
 	struct region *region = &fiber()->gc;
 	const char *actual =
-		tuple_update_execute(ops, ops_end, old, old_end, &actual_len, 1,
-				     &column_mask);
+		tuple_update_execute(ops, ops_end, old, old_end,
+				     box_tuple_format_default()->dict,
+				     &actual_len, 1, &column_mask);
 	fail_if(actual == NULL);
 	is((int32_t)actual_len, new_end - new, "check result length");
 	is(memcmp(actual, new, actual_len), 0, "tuple update is correct");
-- 
2.20.1 (Apple Git-117)





More information about the Tarantool-patches mailing list