From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: From: Vladislav Shpilevoy Subject: [PATCH 5/5] tuple: implement update by field name Date: Sun, 14 Jul 2019 00:11:08 +0200 Message-Id: <8ffb22759c7c9d6d60b74b1bf280d16344afeefc.1563054879.git.v.shpilevoy@tarantool.org> In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit To: tarantool-patches@freelists.org Cc: vdavydov.dev@gmail.com List-ID: 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 be8dab27d..ccbc43b02 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 the space '%s' can not contain nullable parts") \ - /*153 */_(ER_NO_SUCH_FIELD_NAME, "Field '%s' was not found in the space '%s' format") \ + /*153 */_(ER_NO_SUCH_FIELD_NAME_IN_SPACE, "Field '%s' was not found in the 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 54b379c43..d6286924e 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 e881aaf32..d4ac90ad9 100644 --- a/src/box/space.c +++ b/src/box/space.c @@ -359,7 +359,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; @@ -382,6 +383,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; @@ -390,8 +392,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 b35314855..c8ad2f7f7 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 d77bee051..e9262de2e 100644 --- a/src/box/sql/update.c +++ b/src/box/sql/update.c @@ -192,7 +192,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 1e5b2fb24..f1225d3d5 100644 --- a/src/box/tuple.c +++ b/src/box/tuple.c @@ -705,15 +705,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); @@ -727,16 +727,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 #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 d82cae766..3be999caf 100644 --- a/src/box/vinyl.c +++ b/src/box/vinyl.c @@ -1938,7 +1938,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; @@ -2117,7 +2118,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) { @@ -2175,7 +2177,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 dab8549bd..02482ef6d 100644 --- a/test/box/misc.result +++ b/test/box/misc.result @@ -482,13 +482,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)