From 9ee5cf82ae579d62543a4bb335dd1e99b00dacf6 Mon Sep 17 00:00:00 2001
From: Vladislav Shpilevoy <v.shpilevoy@tarantool.org>
Date: Tue, 29 May 2018 15:33:46 +0300
Subject: [PATCH] tuple: implement update by field name

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           |   3 +-
 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(+), 97 deletions(-)

diff --git a/src/box/errcode.h b/src/box/errcode.h
index 46b0b365ad..e9990340fd 100644
--- a/src/box/errcode.h
+++ b/src/box/errcode.h
@@ -205,7 +205,7 @@ 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") \
@@ -253,6 +253,7 @@ struct errcode_record {
 	/*198 */_(ER_FUNC_INDEX_FUNC,		"Failed to build a key for functional index '%s' of space '%s': %s") \
 	/*199 */_(ER_FUNC_INDEX_FORMAT,		"Key format doesn't match one defined in functional index '%s' of space '%s': %s") \
 	/*200 */_(ER_FUNC_INDEX_PARTS,		"Wrong functional index definition: %s") \
+	/*201 */_(ER_NO_SUCH_FIELD_NAME,	"Field '%s' was not found in the tuple") \
 
 /*
  * !IMPORTANT! Please follow instructions at start of the file
diff --git a/src/box/lua/tuple.c b/src/box/lua/tuple.c
index 2fbfb1473d..3902288bf7 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 73bdff911e..487cfdadd9 100644
--- a/src/box/memtx_space.c
+++ b/src/box/memtx_space.c
@@ -388,15 +388,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;
@@ -442,6 +443,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
@@ -460,11 +462,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;
@@ -482,12 +484,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 31a31dce68..042be042c0 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 1c956567f6..42b8391667 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 207f57ba68..d81f69ca47 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 7f9358c05a..2d7ebf8cd0 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 beb290a9f8..bf4ea711d4 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 a247d59b3b..dd1d78997d 100644
--- a/src/box/tuple_update.c
+++ b/src/box/tuple_update.c
@@ -43,6 +43,8 @@
 #include "mp_extension_types.h"
 #include "mp_decimal.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
@@ -1006,12 +1008,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
@@ -1025,7 +1102,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,
@@ -1051,59 +1129,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 != '!')
@@ -1261,24 +1294,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;
@@ -1291,14 +1325,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))
@@ -1312,14 +1346,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 37faa19180..b6210dd384 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 bc4d0ecd13..7455c2c865 100644
--- a/src/box/vinyl.c
+++ b/src/box/vinyl.c
@@ -1885,7 +1885,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;
@@ -2064,7 +2065,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) {
@@ -2122,7 +2124,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 1c55f86c20..817d29cfd8 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 287d84e5bb..9e93d8fbf1 100644
--- a/test/box/misc.result
+++ b/test/box/misc.result
@@ -483,7 +483,7 @@ 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
@@ -530,6 +530,7 @@ t;
   198: box.error.FUNC_INDEX_FUNC
   199: box.error.FUNC_INDEX_FORMAT
   200: box.error.FUNC_INDEX_PARTS
+  201: box.error.NO_SUCH_FIELD_NAME
 ...
 test_run:cmd("setopt delimiter ''");
 ---
diff --git a/test/box/update.result b/test/box/update.result
index cc8fa7ad0c..6c7bf09df9 100644
--- a/test/box/update.result
+++ b/test/box/update.result
@@ -834,7 +834,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 69293e468e..f181924f3a 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 51263f577b..4ca2589e49 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 b355455886..47da307fa3 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 3ffd351ac6..5ee8b73326 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");
-- 
GitLab