[PATCH v5 1/4] box: introduce tuple_format_iterator class

Kirill Shcherbatov kshcherbatov at tarantool.org
Mon May 6 14:57:38 MSK 2019


The similar code in tuple_field_map_create and
vy_stmt_new_surrogate_delete_raw that performs tuple decode
with tuple_format has been refactored as reusable
tuple_format_iterator class.

Being thus encapsulated, this code will be uniformly managed and
extended in the further patches in scope of multikey indexes.

Extended engine/json test with vy_stmt_new_surrogate_delete_raw
corner case test. There was no problem before this patch, but
small bug appeared during tuple_format_iterator_next
implementation was not covered.

Needed for #1257
---
 src/box/tuple_format.c    | 356 ++++++++++++++++++++------------------
 src/box/tuple_format.h    | 118 +++++++++++++
 src/box/vy_stmt.c         | 117 +++++--------
 test/engine/json.result   |  17 ++
 test/engine/json.test.lua |   5 +
 5 files changed, 371 insertions(+), 242 deletions(-)

diff --git a/src/box/tuple_format.c b/src/box/tuple_format.c
index 804a678a1..de48e9bb0 100644
--- a/src/box/tuple_format.c
+++ b/src/box/tuple_format.c
@@ -790,179 +790,29 @@ tuple_field_map_create(struct tuple_format *format, const char *tuple,
 		return -1;
 	}
 	*field_map = (uint32_t *)((char *)*field_map + *field_map_size);
-
-	const char *pos = tuple;
-	int rc = 0;
-	/* Check to see if the tuple has a sufficient number of fields. */
-	uint32_t field_count = mp_decode_array(&pos);
-	if (validate && format->exact_field_count > 0 &&
-	    format->exact_field_count != field_count) {
-		diag_set(ClientError, ER_EXACT_FIELD_COUNT,
-			 (unsigned) field_count,
-			 (unsigned) format->exact_field_count);
-		goto error;
-	}
-	/*
-	 * Allocate a field bitmap that will be used for checking
-	 * that all mandatory fields are present.
-	 */
-	void *required_fields = NULL;
-	size_t required_fields_sz = 0;
-	if (validate) {
-		required_fields_sz = bitmap_size(format->total_field_count);
-		required_fields = region_alloc(region, required_fields_sz);
-		if (required_fields == NULL) {
-			diag_set(OutOfMemory, required_fields_sz,
-				 "region", "required field bitmap");
-			goto error;
-		}
-		memcpy(required_fields, format->required_fields,
-		       required_fields_sz);
-	}
-	/*
-	 * Initialize the tuple field map and validate field types.
-	 */
-	if (field_count == 0) {
-		/* Empty tuple, nothing to do. */
-		goto finish;
-	}
-	uint32_t defined_field_count = MIN(field_count, validate ?
-					   tuple_format_field_count(format) :
-					   format->index_field_count);
 	/*
 	 * Nullify field map to be able to detect by 0,
 	 * which key fields are absent in tuple_field().
 	 */
 	memset((char *)*field_map - *field_map_size, 0, *field_map_size);
-	/*
-	 * Prepare mp stack of the size equal to the maximum depth
-	 * of the indexed field in the format::fields tree
-	 * (fields_depth) to carry out a simultaneous parsing of
-	 * the tuple and tree traversal to process type
-	 * validations and field map initialization.
-	 */
-	uint32_t frames_sz = format->fields_depth * sizeof(struct mp_frame);
-	struct mp_frame *frames = region_alloc(region, frames_sz);
-	if (frames == NULL) {
-		diag_set(OutOfMemory, frames_sz, "region", "frames");
-		goto error;
-	}
-	struct mp_stack stack;
-	mp_stack_create(&stack, format->fields_depth, frames);
-	mp_stack_push(&stack, MP_ARRAY, defined_field_count);
-	struct tuple_field *field;
-	struct json_token *parent = &format->fields.root;
-	while (true) {
-		struct mp_frame *frame = mp_stack_top(&stack);
-		while (!mp_frame_advance(frame)) {
-			/*
-			 * If the elements of the current frame
-			 * are over, pop this frame out of stack
-			 * and climb one position in the
-			 * format::fields tree to match the
-			 * changed JSON path to the data in the
-			 * tuple.
-			 */
-			mp_stack_pop(&stack);
-			if (mp_stack_is_empty(&stack))
-				goto finish;
-			frame = mp_stack_top(&stack);
-			parent = parent->parent;
-		}
-		/*
-		 * Use the top frame of the stack and the
-		 * current data offset to prepare the JSON token
-		 * for the subsequent format::fields lookup.
-		 */
-		struct json_token token;
-		switch (frame->type) {
-		case MP_ARRAY:
-			token.type = JSON_TOKEN_NUM;
-			token.num = frame->idx;
-			break;
-		case MP_MAP:
-			if (mp_typeof(*pos) != MP_STR) {
-				/*
-				 * JSON path support only string
-				 * keys for map. Skip this entry.
-				 */
-				mp_next(&pos);
-				mp_next(&pos);
-				continue;
-			}
-			token.type = JSON_TOKEN_STR;
-			token.str = mp_decode_str(&pos, (uint32_t *)&token.len);
-			break;
-		default:
-			unreachable();
-		}
-		/*
-		 * Perform lookup for a field in format::fields,
-		 * that represents the field metadata by JSON path
-		 * corresponding to the current position in the
-		 * tuple.
-		 */
-		enum mp_type type = mp_typeof(*pos);
-		assert(parent != NULL);
-		field = json_tree_lookup_entry(&format->fields, parent, &token,
-					       struct tuple_field, token);
-		if (field != NULL) {
-			bool is_nullable = tuple_field_is_nullable(field);
-			if (validate &&
-			    !field_mp_type_is_compatible(field->type, type,
-							 is_nullable) != 0) {
-				diag_set(ClientError, ER_FIELD_TYPE,
-					 tuple_field_path(field),
-					 field_type_strs[field->type]);
-				goto error;
-			}
-			if (field->offset_slot != TUPLE_OFFSET_SLOT_NIL)
-				(*field_map)[field->offset_slot] = pos - tuple;
-			if (required_fields != NULL)
-				bit_clear(required_fields, field->id);
-		}
-		/*
-		 * If the current position of the data in tuple
-		 * matches the container type (MP_MAP or MP_ARRAY)
-		 * and the format::fields tree has such a record,
-		 * prepare a new stack frame because it needs to
-		 * be analyzed in the next iterations.
-		 */
-		if ((type == MP_ARRAY || type == MP_MAP) &&
-		    !mp_stack_is_full(&stack) && field != NULL) {
-			uint32_t size = type == MP_ARRAY ?
-					mp_decode_array(&pos) :
-					mp_decode_map(&pos);
-			mp_stack_push(&stack, type, size);
-			parent = &field->token;
-		} else {
-			mp_next(&pos);
-		}
-	}
-finish:
-	/*
-	 * Check the required field bitmap for missing fields.
-	 */
-	if (required_fields != NULL) {
-		struct bit_iterator it;
-		bit_iterator_init(&it, required_fields,
-				  required_fields_sz, true);
-		size_t id = bit_iterator_next(&it);
-		if (id < SIZE_MAX) {
-			/* A field is missing, report an error. */
-			field = tuple_format_field_by_id(format, id);
-			assert(field != NULL);
-			diag_set(ClientError, ER_FIELD_MISSING,
-				 tuple_field_path(field));
-			goto error;
+	uint32_t field_count;
+	struct tuple_format_iterator it;
+	uint8_t flags = validate ? TUPLE_FORMAT_ITERATOR_VALIDATE : 0;
+	if (tuple_format_iterator_create(&it, format, tuple, flags,
+					 &field_count, region) != 0)
+		return -1;
+	struct tuple_format_iterator_entry entry;
+	while (tuple_format_iterator_next(&it, &entry) == 0 &&
+	       entry.data != NULL) {
+		if (entry.field == NULL)
+			continue;
+		if (entry.field->offset_slot != TUPLE_OFFSET_SLOT_NIL) {
+			(*field_map)[entry.field->offset_slot] =
+							entry.data - tuple;
 		}
 	}
-out:
 	*field_map = (uint32_t *)((char *)*field_map - *field_map_size);
-	return rc;
-error:
-	rc = -1;
-	goto out;
+	return entry.data == NULL ? 0 : -1;
 }
 
 uint32_t
@@ -1033,3 +883,179 @@ box_tuple_format_unref(box_tuple_format_t *format)
 	tuple_format_unref(format);
 }
 
+int
+tuple_format_iterator_create(struct tuple_format_iterator *it,
+			     struct tuple_format *format, const char *tuple,
+			     uint8_t flags, uint32_t *defined_field_count,
+			     struct region *region)
+{
+	assert(mp_typeof(*tuple) == MP_ARRAY);
+	*defined_field_count = mp_decode_array(&tuple);
+	bool validate = flags & TUPLE_FORMAT_ITERATOR_VALIDATE;
+	if (validate && format->exact_field_count > 0 &&
+	    format->exact_field_count != *defined_field_count) {
+		diag_set(ClientError, ER_EXACT_FIELD_COUNT,
+			 (unsigned) *defined_field_count,
+			 (unsigned) format->exact_field_count);
+		return -1;
+	}
+	it->parent = &format->fields.root;
+	it->format = format;
+	it->pos = tuple;
+	it->flags = flags;
+	it->required_fields = NULL;
+	it->required_fields_sz = 0;
+
+	uint32_t frames_sz = format->fields_depth * sizeof(struct mp_frame);
+	if (validate)
+		it->required_fields_sz = bitmap_size(format->total_field_count);
+	uint32_t total_sz = frames_sz + 2 * it->required_fields_sz;
+	struct mp_frame *frames = region_alloc(region, total_sz);
+	if (frames == NULL) {
+		diag_set(OutOfMemory, total_sz, "region",
+			 "tuple_format_iterator");
+		return -1;
+	}
+	mp_stack_create(&it->stack, format->fields_depth, frames);
+	*defined_field_count = MIN(*defined_field_count, validate ?
+				   tuple_format_field_count(format) :
+				   format->index_field_count);
+	mp_stack_push(&it->stack, MP_ARRAY, *defined_field_count);
+
+	if (validate) {
+		it->required_fields = (char *)frames + frames_sz;
+		memcpy(it->required_fields, format->required_fields,
+		       it->required_fields_sz);
+	}
+	return 0;
+}
+
+/**
+ * Scan required_fields bitmap and raise error when it is
+ * non-empty.
+ * @sa format:required_fields and field:multikey_required_fields
+ * definition.
+ */
+static int
+tuple_format_required_fields_validate(struct tuple_format *format,
+				      void *required_fields,
+				      uint32_t required_fields_sz)
+{
+	struct bit_iterator it;
+	bit_iterator_init(&it, required_fields, required_fields_sz, true);
+	size_t id = bit_iterator_next(&it);
+	if (id < SIZE_MAX) {
+		/* A field is missing, report an error. */
+		struct tuple_field *field =
+			tuple_format_field_by_id(format, id);
+		assert(field != NULL);
+		diag_set(ClientError, ER_FIELD_MISSING,
+			 tuple_field_path(field));
+		return -1;
+	}
+	return 0;
+}
+
+int
+tuple_format_iterator_next(struct tuple_format_iterator *it,
+			   struct tuple_format_iterator_entry *entry)
+{
+	entry->data = it->pos;
+	struct mp_frame *frame = mp_stack_top(&it->stack);
+	while (!mp_frame_advance(frame)) {
+		/*
+		 * If the elements of the current frame
+		 * are over, pop this frame out of stack
+		 * and climb one position in the format::fields
+		 * tree to match the changed JSON path to the
+		 * data in the tuple.
+		 */
+		mp_stack_pop(&it->stack);
+		if (mp_stack_is_empty(&it->stack))
+			goto eof;
+		frame = mp_stack_top(&it->stack);
+		it->parent = it->parent->parent;
+	}
+	entry->parent =
+		it->parent != &it->format->fields.root ?
+		json_tree_entry(it->parent, struct tuple_field, token) : NULL;
+	/*
+	 * Use the top frame of the stack and the
+	 * current data offset to prepare the JSON token
+	 * and perform subsequent format::fields lookup.
+	 */
+	struct json_token token;
+	switch (frame->type) {
+	case MP_ARRAY:
+		token.type = JSON_TOKEN_NUM;
+		token.num = frame->idx;
+		break;
+	case MP_MAP:
+		if (mp_typeof(*it->pos) != MP_STR) {
+			entry->data = it->pos;
+			entry->field = NULL;
+			mp_next(&it->pos);
+			mp_next(&it->pos);
+			return 0;
+		}
+		token.type = JSON_TOKEN_STR;
+		token.str = mp_decode_str(&it->pos, (uint32_t *)&token.len);
+		break;
+	default:
+		unreachable();
+	}
+	assert(it->parent != NULL);
+	struct tuple_field *field =
+		json_tree_lookup_entry(&it->format->fields, it->parent, &token,
+				       struct tuple_field, token);
+	if (it->flags & TUPLE_FORMAT_ITERATOR_KEY_PARTS_ONLY &&
+	    field != NULL && !field->is_key_part)
+		field = NULL;
+	entry->field = field;
+	entry->data = it->pos;
+	/*
+	 * If the current position of the data in tuple
+	 * matches the container type (MP_MAP or MP_ARRAY)
+	 * and the format::fields tree has such a record,
+	 * prepare a new stack frame because it needs to
+	 * be analyzed in the next iterations.
+	 */
+	enum mp_type type = mp_typeof(*it->pos);
+	if ((type == MP_ARRAY || type == MP_MAP) &&
+	    !mp_stack_is_full(&it->stack) && field != NULL) {
+		uint32_t size = type == MP_ARRAY ?
+				mp_decode_array(&it->pos) :
+				mp_decode_map(&it->pos);
+		entry->count = size;
+		mp_stack_push(&it->stack, type, size);
+		it->parent = &field->token;
+	} else {
+		entry->count = 0;
+		mp_next(&it->pos);
+	}
+	entry->data_end = it->pos;
+	if (field == NULL || (it->flags & TUPLE_FORMAT_ITERATOR_VALIDATE) == 0)
+		return 0;
+
+	/*
+	 * Check if field mp_type is compatible with type
+	 * defined in format.
+	 */
+	bool is_nullable = tuple_field_is_nullable(field);
+	if (!field_mp_type_is_compatible(field->type, mp_typeof(*entry->data),
+					 is_nullable) != 0) {
+		diag_set(ClientError, ER_FIELD_TYPE,
+			 tuple_field_path(field),
+			 field_type_strs[field->type]);
+		return -1;
+	}
+	bit_clear(it->required_fields, field->id);
+	return 0;
+eof:
+	if (it->flags & TUPLE_FORMAT_ITERATOR_VALIDATE &&
+	    tuple_format_required_fields_validate(it->format,
+			it->required_fields, it->required_fields_sz) != 0)
+		return -1;
+	entry->data = NULL;
+	return 0;
+}
diff --git a/src/box/tuple_format.h b/src/box/tuple_format.h
index 22a0fb232..244f60266 100644
--- a/src/box/tuple_format.h
+++ b/src/box/tuple_format.h
@@ -412,6 +412,124 @@ tuple_field_map_create(struct tuple_format *format, const char *tuple,
 int
 tuple_format_init();
 
+
+/** Tuple format iterator flags to configure parse mode. */
+enum {
+	/**
+	 * This flag is set for iterator that should perform tuple
+	 * validation to conform the specified format.
+	 */
+	TUPLE_FORMAT_ITERATOR_VALIDATE		= 1 << 0,
+	/**
+	 * This flag is set for iterator that should skip the
+	 * tuple fields that are not marked as key_parts in
+	 * format::fields tree.
+	 */
+	TUPLE_FORMAT_ITERATOR_KEY_PARTS_ONLY 	= 1 << 1,
+};
+
+/**
+ * A tuple msgpack iterator that decodes the tuple and returns
+ * only fields that are described in the tuple_format.
+ */
+struct tuple_format_iterator {
+	/** The current read position in msgpack. */
+	const char *pos;
+	/**
+	 * Tuple format is used to perform field lookups in
+	 * format::fields JSON tree.
+	 */
+	struct tuple_format *format;
+	/** The combination of tuple format iterator flags. */
+	uint8_t flags;
+	/**
+	 * Traversal stack of msgpack frames is used to determine
+	 * when the parsing of the current composite mp structure
+	 * (array or map) is completed to update to the parent
+	 * pointer accordingly.
+	 *
+	 * Stack has the size equal to the maximum depth of the
+	 * indexed field in the format::fields tree
+	 * (format::fields_depth).
+	 */
+	struct mp_stack stack;
+	/**
+	 * The pointer to the parent node in the format::fields
+	 * JSON tree. Is required for relative lookup for the
+	 * next field.
+	 */
+	struct json_token *parent;
+	/**
+	 * The size of validation bitmasks required_fields and
+	 * multikey_required_fields.
+	 */
+	uint32_t required_fields_sz;
+	/**
+	 * Field bitmap that is used for checking that all
+	 * mandatory fields are present.
+	 * Not NULL iff validate == true.
+	 */
+	void *required_fields;
+};
+
+/** Tuple format iterator next method's returning entry. */
+struct tuple_format_iterator_entry {
+	/** Pointer to the tuple field data. NULL if EOF. */
+	const char *data;
+	/**
+	 * Pointer to the end of tuple field data.
+	 * Is defined only for leaf fields
+	 * (json_token_is_leaf(&field->token) == true).
+	 */
+	const char *data_end;
+	/**
+	 * Format field metadata that represents the data field.
+	 * May be NULL if the field isn't present in the format.
+	 *
+	 * (All child entries of an array are returned present in
+	 * the format, no matter formatted or not)
+	 */
+	struct tuple_field *field;
+	/**
+	 * Format parent field metadata. NULL for top-level
+	 * fields.
+	 */
+	struct tuple_field *parent;
+	/**
+	 * Number of child entries of the analyzed field that has
+	 * container type. Is defined for intermediate fields.
+	 * (field->type in FIELD_TYPE_ARRAY, FIELD_TYPE_MAP).
+	 */
+	int count;
+};
+
+/**
+ * Initialize tuple decode iterator with tuple format and tuple
+ * data pointer.
+ *
+ * Function uses the region to allocate the traversal stack
+ * and required fields bitmasks.
+ *
+ * Returns 0 on success. In case of error sets diag message and
+ * returns -1.
+ */
+int
+tuple_format_iterator_create(struct tuple_format_iterator *it,
+			     struct tuple_format *format, const char *tuple,
+			     uint8_t flags, uint32_t *field_count,
+			     struct region *region);
+
+/**
+ * Perform tuple decode step and update iterator state.
+ * Update entry pointer with actual format parse context.
+ *
+ * Returns 0 on success. In case of error sets diag message and
+ * returns -1.
+ */
+int
+tuple_format_iterator_next(struct tuple_format_iterator *it,
+			   struct tuple_format_iterator_entry *entry);
+
 #if defined(__cplusplus)
 } /* extern "C" */
 #endif /* defined(__cplusplus) */
diff --git a/src/box/vy_stmt.c b/src/box/vy_stmt.c
index e1cdd293d..8d46a9eac 100644
--- a/src/box/vy_stmt.c
+++ b/src/box/vy_stmt.c
@@ -417,10 +417,17 @@ vy_stmt_new_surrogate_delete_raw(struct tuple_format *format,
 	}
 	char *field_map_begin = data + src_size;
 	uint32_t *field_map = (uint32_t *) (data + total_size);
-
-	const char *src_pos = src_data;
-	uint32_t src_count = mp_decode_array(&src_pos);
-	uint32_t field_count = MIN(src_count, format->index_field_count);
+	/*
+	 * Perform simultaneous parsing of the tuple and
+	 * format::fields tree traversal to copy indexed field
+	 * data and initialize field map.
+	 */
+	uint32_t field_count;
+	struct tuple_format_iterator it;
+	if (tuple_format_iterator_create(&it, format, src_data,
+			TUPLE_FORMAT_ITERATOR_KEY_PARTS_ONLY, &field_count,
+			region) != 0)
+		goto out;
 	/*
 	 * Nullify field map to be able to detect by 0, which key
 	 * fields are absent in tuple_field().
@@ -428,85 +435,41 @@ vy_stmt_new_surrogate_delete_raw(struct tuple_format *format,
 	memset((char *)field_map - format->field_map_size, 0,
 		format->field_map_size);
 	char *pos = mp_encode_array(data, field_count);
-	/*
-	 * Perform simultaneous parsing of the tuple and
-	 * format::fields tree traversal to copy indexed field
-	 * data and initialize field map. In many details the code
-	 * above works like tuple_field_map_create, read it's
-	 * comments for more details.
-	 */
-	uint32_t frames_sz = format->fields_depth * sizeof(struct mp_frame);
-	struct mp_frame *frames = region_alloc(region, frames_sz);
-	if (frames == NULL) {
-		diag_set(OutOfMemory, frames_sz, "region", "frames");
-		goto out;
-	}
-	struct mp_stack stack;
-	mp_stack_create(&stack, format->fields_depth, frames);
-	mp_stack_push(&stack, MP_ARRAY, field_count);
-	struct tuple_field *field;
-	struct json_token *parent = &format->fields.root;
-	while (true) {
-		struct mp_frame *frame = mp_stack_top(&stack);
-		while (!mp_frame_advance(frame)) {
-			mp_stack_pop(&stack);
-			if (mp_stack_is_empty(&stack))
-				goto finish;
-			frame = mp_stack_top(&stack);
-			parent = parent->parent;
-		}
-		struct json_token token;
-		switch (frame->type) {
-		case MP_ARRAY:
-			token.type = JSON_TOKEN_NUM;
-			token.num = frame->idx;
-			break;
-		case MP_MAP:
-			if (mp_typeof(*src_pos) != MP_STR) {
-				mp_next(&src_pos);
-				mp_next(&src_pos);
-				pos = mp_encode_nil(pos);
-				pos = mp_encode_nil(pos);
-				continue;
-			}
-			token.type = JSON_TOKEN_STR;
-			token.str = mp_decode_str(&src_pos, (uint32_t *)&token.len);
-			pos = mp_encode_str(pos, token.str, token.len);
-			break;
-		default:
-			unreachable();
-		}
-		assert(parent != NULL);
-		field = json_tree_lookup_entry(&format->fields, parent, &token,
-					       struct tuple_field, token);
-		if (field == NULL || !field->is_key_part) {
-			mp_next(&src_pos);
+
+	struct tuple_format_iterator_entry entry;
+	while (tuple_format_iterator_next(&it, &entry) == 0 &&
+	       entry.data != NULL) {
+		if (entry.field == NULL) {
+			/*
+			 * Instead of copying useless data having
+			 * no representation in tuple_format,
+			 * write nil.
+			 */
 			pos = mp_encode_nil(pos);
+			if (entry.parent != NULL &&
+			    entry.parent->type == FIELD_TYPE_MAP)
+				pos = mp_encode_nil(pos);
 			continue;
 		}
-		if (field->offset_slot != TUPLE_OFFSET_SLOT_NIL)
-			field_map[field->offset_slot] = pos - data;
-		enum mp_type type = mp_typeof(*src_pos);
-		if ((type == MP_ARRAY || type == MP_MAP) &&
-		    !mp_stack_is_full(&stack)) {
-			uint32_t size;
-			if (type == MP_ARRAY) {
-				size = mp_decode_array(&src_pos);
-				pos = mp_encode_array(pos, size);
-			} else {
-				size = mp_decode_map(&src_pos);
-				pos = mp_encode_map(pos, size);
-			}
-			mp_stack_push(&stack, type, size);
-			parent = &field->token;
+		if (entry.field->token.type == JSON_TOKEN_STR) {
+			pos = mp_encode_str(pos, entry.field->token.str,
+					    entry.field->token.len);
+		}
+		/* Initialize field_map with data offset. */
+		if (entry.field->offset_slot != TUPLE_OFFSET_SLOT_NIL)
+			field_map[entry.field->offset_slot] = pos - data;
+		/* Copy field data. */
+		if (entry.field->type == FIELD_TYPE_ARRAY) {
+			pos = mp_encode_array(pos, entry.count);
+		} else if (entry.field->type == FIELD_TYPE_MAP) {
+			pos = mp_encode_map(pos, entry.count);
 		} else {
-			const char *src_field = src_pos;
-			mp_next(&src_pos);
-			memcpy(pos, src_field, src_pos - src_field);
-			pos += src_pos - src_field;
+			memcpy(pos, entry.data, entry.data_end - entry.data);
+			pos += entry.data_end - entry.data;
 		}
 	}
-finish:
+	if (entry.data != NULL)
+		goto out;
 	assert(pos <= data + src_size);
 	uint32_t bsize = pos - data;
 	stmt = vy_stmt_alloc(format, bsize);
diff --git a/test/engine/json.result b/test/engine/json.result
index 09c704963..84b1309e1 100644
--- a/test/engine/json.result
+++ b/test/engine/json.result
@@ -702,6 +702,23 @@ s:replace({4, {"d1", name='D1'}, "test"})
 ---
 - [4, {1: 'd1', 'name': 'D1'}, 'test']
 ...
+idx0:drop()
+---
+...
+s:truncate()
+---
+...
+idx0 = s:create_index('idx2', {parts = {{3, 'str', path = '[1].fname'}, {3, 'str', path = '[1].sname'}}})
+---
+...
+s:insert({5, {1, 1, 1}, {{fname='A', sname='B'}, {fname='C', sname='D'}, {fname='A', sname='B'}}})
+---
+- [5, [1, 1, 1], [{'fname': 'A', 'sname': 'B'}, {'fname': 'C', 'sname': 'D'}, {'fname': 'A',
+      'sname': 'B'}]]
+...
+s:delete(5)
+---
+...
 s:drop()
 ---
 ...
diff --git a/test/engine/json.test.lua b/test/engine/json.test.lua
index 5c235e1ba..e864ec14d 100644
--- a/test/engine/json.test.lua
+++ b/test/engine/json.test.lua
@@ -200,6 +200,11 @@ pk = s:create_index('pk', {parts={{1, 'int'}}})
 idx0 = s:create_index('idx0', {parts = {{2, 'str', path = 'name'}, {3, "str"}}})
 s:insert({4, {"d", name='D'}, "test"})
 s:replace({4, {"d1", name='D1'}, "test"})
+idx0:drop()
+s:truncate()
+idx0 = s:create_index('idx2', {parts = {{3, 'str', path = '[1].fname'}, {3, 'str', path = '[1].sname'}}})
+s:insert({5, {1, 1, 1}, {{fname='A', sname='B'}, {fname='C', sname='D'}, {fname='A', sname='B'}}})
+s:delete(5)
 s:drop()
 
 --
-- 
2.21.0




More information about the Tarantool-patches mailing list