Tarantool development patches archive
 help / color / mirror / Atom feed
* [tarantool-patches] [PATCH v2 0/3] JSON Paths support
@ 2018-04-06 11:08 Kirill Shcherbatov
  2018-04-06 11:08 ` [tarantool-patches] [PATCH v2 1/3] Allow gcov on Mac Kirill Shcherbatov
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: Kirill Shcherbatov @ 2018-04-06 11:08 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy, Kirill Shcherbatov

Branch: http://github.com/tarantool/tarantool/tree/gh-1285-tuple-field-by-json-icu
Issue: https://github.com/tarantool/tarantool/issues/1285

Kirill Shcherbatov (2):
  Introduce json_path_parser with Unicode support.
  Lua: implement json path access to tuple fields

Vladislav Shpilevoy (1):
  Allow gcov on Mac

 cmake/profile.cmake         |   9 --
 src/box/CMakeLists.txt      |   4 +-
 src/box/lua/tuple.c         |  63 ++++++++----
 src/box/lua/tuple.lua       |  52 +++-------
 src/box/tuple.h             |  21 ++++
 src/box/tuple_format.c      | 164 ++++++++++++++++++++++++++++++
 src/box/tuple_format.h      |  19 ++++
 src/lib/CMakeLists.txt      |   1 +
 src/lib/json/CMakeLists.txt |   6 ++
 src/lib/json/path.c         | 242 ++++++++++++++++++++++++++++++++++++++++++++
 src/lib/json/path.h         | 114 +++++++++++++++++++++
 test/engine/tuple.result    | 225 ++++++++++++++++++++++++++++++++++++++++
 test/engine/tuple.test.lua  |  66 ++++++++++++
 test/unit/CMakeLists.txt    |   3 +
 test/unit/json_path.c       | 165 ++++++++++++++++++++++++++++++
 test/unit/json_path.result  |  84 +++++++++++++++
 16 files changed, 1171 insertions(+), 67 deletions(-)
 create mode 100644 src/lib/json/CMakeLists.txt
 create mode 100644 src/lib/json/path.c
 create mode 100644 src/lib/json/path.h
 create mode 100644 test/unit/json_path.c
 create mode 100644 test/unit/json_path.result

-- 
2.7.4

^ permalink raw reply	[flat|nested] 4+ messages in thread

* [tarantool-patches] [PATCH v2 1/3] Allow gcov on Mac
  2018-04-06 11:08 [tarantool-patches] [PATCH v2 0/3] JSON Paths support Kirill Shcherbatov
@ 2018-04-06 11:08 ` Kirill Shcherbatov
  2018-04-06 11:08 ` [tarantool-patches] [PATCH v2 2/3] Introduce json_path_parser with Unicode support Kirill Shcherbatov
  2018-04-06 11:08 ` [tarantool-patches] [PATCH v2 3/3] Lua: implement json path access to tuple fields Kirill Shcherbatov
  2 siblings, 0 replies; 4+ messages in thread
From: Kirill Shcherbatov @ 2018-04-06 11:08 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy

From: Vladislav Shpilevoy <v.shpilevoy@tarantool.org>

---
 cmake/profile.cmake | 9 ---------
 1 file changed, 9 deletions(-)

diff --git a/cmake/profile.cmake b/cmake/profile.cmake
index 2783991..c82fc01 100644
--- a/cmake/profile.cmake
+++ b/cmake/profile.cmake
@@ -1,14 +1,7 @@
-check_library_exists (gcov __gcov_flush  ""  HAVE_GCOV)
-
 set(ENABLE_GCOV_DEFAULT OFF)
 option(ENABLE_GCOV "Enable integration with gcov, a code coverage program" ${ENABLE_GCOV_DEFAULT})
 
 if (ENABLE_GCOV)
-    if (NOT HAVE_GCOV)
-    message (FATAL_ERROR
-         "ENABLE_GCOV option requested but gcov library is not found")
-    endif()
-
     add_compile_flags("C;CXX"
         "-fprofile-arcs"
         "-ftest-coverage"
@@ -18,8 +11,6 @@ if (ENABLE_GCOV)
     set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -ftest-coverage")
     set (CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fprofile-arcs")
     set (CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -ftest-coverage")
-
-   # add_library(gcov SHARED IMPORTED)
 endif()
 
 if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
-- 
2.7.4

^ permalink raw reply	[flat|nested] 4+ messages in thread

* [tarantool-patches] [PATCH v2 2/3] Introduce json_path_parser with Unicode support.
  2018-04-06 11:08 [tarantool-patches] [PATCH v2 0/3] JSON Paths support Kirill Shcherbatov
  2018-04-06 11:08 ` [tarantool-patches] [PATCH v2 1/3] Allow gcov on Mac Kirill Shcherbatov
@ 2018-04-06 11:08 ` Kirill Shcherbatov
  2018-04-06 11:08 ` [tarantool-patches] [PATCH v2 3/3] Lua: implement json path access to tuple fields Kirill Shcherbatov
  2 siblings, 0 replies; 4+ messages in thread
From: Kirill Shcherbatov @ 2018-04-06 11:08 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy, Kirill Shcherbatov

Needed for #1285 and for #1261
---
 src/lib/CMakeLists.txt      |   1 +
 src/lib/json/CMakeLists.txt |   6 ++
 src/lib/json/path.c         | 242 ++++++++++++++++++++++++++++++++++++++++++++
 src/lib/json/path.h         | 114 +++++++++++++++++++++
 test/unit/CMakeLists.txt    |   3 +
 test/unit/json_path.c       | 165 ++++++++++++++++++++++++++++++
 test/unit/json_path.result  |  84 +++++++++++++++
 7 files changed, 615 insertions(+)
 create mode 100644 src/lib/json/CMakeLists.txt
 create mode 100644 src/lib/json/path.c
 create mode 100644 src/lib/json/path.h
 create mode 100644 test/unit/json_path.c
 create mode 100644 test/unit/json_path.result

diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt
index 0b274ca..98ff19b 100644
--- a/src/lib/CMakeLists.txt
+++ b/src/lib/CMakeLists.txt
@@ -4,6 +4,7 @@ set(SMALL_EMBEDDED ON)
 add_subdirectory(small)
 add_subdirectory(salad)
 add_subdirectory(csv)
+add_subdirectory(json)
 if(ENABLE_BUNDLED_MSGPUCK)
     add_subdirectory(msgpuck EXCLUDE_FROM_ALL)
 endif()
diff --git a/src/lib/json/CMakeLists.txt b/src/lib/json/CMakeLists.txt
new file mode 100644
index 0000000..203fe6f
--- /dev/null
+++ b/src/lib/json/CMakeLists.txt
@@ -0,0 +1,6 @@
+set(lib_sources
+    path.c
+)
+
+set_source_files_compile_flags(${lib_sources})
+add_library(json_path STATIC ${lib_sources})
diff --git a/src/lib/json/path.c b/src/lib/json/path.c
new file mode 100644
index 0000000..9f9cec3
--- /dev/null
+++ b/src/lib/json/path.c
@@ -0,0 +1,242 @@
+/*
+ * Copyright 2010-2016 Tarantool AUTHORS: please see AUTHORS file.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "path.h"
+#include <ctype.h>
+#include <unicode/uchar.h>
+#include "trivia/util.h"
+
+/**
+ * Parse string and update parser's state.
+ * @param parser JSON path parser. Upates pos, signs_read.
+ * @param[out] UChar32 to store result.
+ *
+ * @retval   0 Success.
+ * @retval > 0 1-based position of a syntax error.
+ */
+static inline int
+json_read_symbol(struct json_path_parser *parser, UChar32 *out)
+{
+	if (parser->offset == parser->src_len)
+		return parser->symbol_count + 1;
+	U8_NEXT_OR_FFFD(parser->src, parser->offset, parser->src_len, *out)
+	if (*out == 0xFFFD)
+		return parser->symbol_count + 1;
+	++parser->symbol_count;
+	return 0;
+}
+
+/**
+ * Reset parser state to previous one.
+ * @param parser JSON path parser.
+ * @param old parser read offset.
+ * @param signs to drop in signs_read counter.
+ */
+static inline void
+json_revert_symbol(struct json_path_parser *parser, int offset)
+{
+	parser->offset = offset;
+	--parser->symbol_count;
+}
+
+/** Fast forward when it is known that a symbol is 1-byte char. */
+static inline void
+json_propagate_char(struct json_path_parser *parser)
+{
+	++parser->offset;
+	++parser->symbol_count;
+}
+
+/** Get a current symbol as a 1-byte char. */
+static inline char
+json_current_char(struct json_path_parser *parser)
+{
+	return *(parser->src + parser->offset);
+}
+
+/**
+ * Parse string identifier in quotes. Parser either stops right
+ * after the closing quote, or returns an error position.
+ * @param parser JSON path parser.
+ * @param[out] node JSON node to store result.
+ * @param quote_type Quote by that a string must be terminated.
+ *
+ * @retval   0 Success.
+ * @retval > 0 1-based position of a syntax error.
+ */
+static inline int
+json_parse_string(struct json_path_parser *parser, struct json_path_node *node,
+		  UChar32 quote_type)
+{
+	assert(parser->offset < parser->src_len);
+	assert(quote_type == json_current_char(parser));
+	/* The first symbol is always char  - ' or ". */
+	json_propagate_char(parser);
+	int str_offset = parser->offset;
+	UChar32 c;
+	int rc;
+	while ((rc = json_read_symbol(parser, &c)) == 0) {
+		if (c == quote_type) {
+			int len = parser->offset - str_offset - 1;
+			if (len == 0)
+				return parser->symbol_count;
+			node->type = JSON_PATH_STR;
+			node->str = parser->src + str_offset;
+			node->len = len;
+			return 0;
+		}
+	}
+	return rc;
+}
+
+/**
+ * Parse digit sequence into integer until non-digit is met.
+ * Parser stops right after the last digit.
+ * @param parser JSON parser. Updates signs_read field.
+ * @param[out] node JSON node to store result.
+ *
+ * @retval   0 Success.
+ * @retval > 0 1-based position of a syntax error.
+ */
+static inline int
+json_parse_integer(struct json_path_parser *parser, struct json_path_node *node)
+{
+	const char *end = parser->src + parser->src_len;
+	const char *pos = parser->src + parser->offset;
+	assert(pos < end);
+	int len = 0;
+	uint64_t value = 0;
+	char c = *pos;
+	if (! isdigit(c))
+		return parser->symbol_count + 1;
+	do {
+		value = value * 10 + c - (int)'0';
+		++len;
+	} while (++pos < end && isdigit((c = *pos)));
+	parser->offset += len;
+	parser->symbol_count += len;
+	node->type = JSON_PATH_NUM;
+	node->num = value;
+	return 0;
+}
+
+/**
+ * Check that a symbol can be part of a JSON path not inside
+ * ["..."].
+ */
+static inline bool
+json_is_valid_identifier_sym(UChar32 c)
+{
+	return u_isUAlphabetic(c) || c == (UChar32)'_' || u_isdigit(c);
+}
+
+/**
+ * Parse identifier out of quotes. It can contain only alphas,
+ * digits and underscores. And can not contain digit at the first
+ * position. Parser is stoped right after the last non-digit,
+ * non-alpha and non-underscore symbol.
+ * @param parser JSON parser. Updates signs_read field.
+ * @param[out] node JSON node to store result.
+ *
+ * @retval   0 Success.
+ * @retval > 0 1-based position of a syntax error.
+ */
+static inline int
+json_parse_identifier(struct json_path_parser *parser,
+		      struct json_path_node *node)
+{
+	assert(parser->offset < parser->src_len);
+	int str_offset = parser->offset;
+	UChar32 c;
+	int rc = json_read_symbol(parser, &c);
+	if (rc != 0)
+		return rc;
+	/* First symbol can not be digit. */
+	if (!u_isalpha(c) && c != (UChar32)'_')
+		return parser->symbol_count;
+	int last_offset = parser->offset;
+	while ((rc = json_read_symbol(parser, &c)) == 0) {
+		if (! json_is_valid_identifier_sym(c)) {
+			json_revert_symbol(parser, last_offset);
+			break;
+		}
+		last_offset = parser->offset;
+	}
+	node->type = JSON_PATH_STR;
+	node->str = parser->src + str_offset;
+	node->len = parser->offset - str_offset;
+	return 0;
+}
+
+int
+json_path_next(struct json_path_parser *parser, struct json_path_node *node)
+{
+	UChar32 c;
+	int last_offset = parser->offset;
+	int rc = json_read_symbol(parser, &c);
+	if (rc != 0) {
+		if (parser->offset == parser->src_len) {
+			node->type = JSON_PATH_END;
+			return 0;
+		}
+		return rc;
+	}
+	switch(c) {
+	case (UChar32)'[':
+		/* Error for '['. */
+		if (parser->offset == parser->src_len)
+			return parser->symbol_count;
+		c = json_current_char(parser);
+		if (c == '"' || c == '\'')
+			rc = json_parse_string(parser, node, c);
+		else
+			rc = json_parse_integer(parser, node);
+		if (rc != 0)
+			return rc;
+		/*
+		 * Expression, started from [ must be finished
+		 * with ] regardless of its type.
+		 */
+		if (parser->offset == parser->src_len ||
+		    json_current_char(parser) != ']')
+			return parser->symbol_count + 1;
+		/* Skip ] - one byte char. */
+		json_propagate_char(parser);
+		return 0;
+	case (UChar32)'.':
+		if (parser->offset == parser->src_len)
+			return parser->symbol_count + 1;
+		return json_parse_identifier(parser, node);
+	default:
+		json_revert_symbol(parser, last_offset);
+		return json_parse_identifier(parser, node);
+	}
+}
diff --git a/src/lib/json/path.h b/src/lib/json/path.h
new file mode 100644
index 0000000..1775507
--- /dev/null
+++ b/src/lib/json/path.h
@@ -0,0 +1,114 @@
+#ifndef TARANTOOL_JSON_PATH_H_INCLUDED
+#define TARANTOOL_JSON_PATH_H_INCLUDED
+/*
+ * Copyright 2010-2016 Tarantool AUTHORS: please see AUTHORS file.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Parser for JSON paths:
+ * <field>, <.field>, <[123]>, <['field']> and their combinations.
+ */
+struct json_path_parser {
+	/** Source string. */
+	const char *src;
+	/** Length of string. */
+	int src_len;
+	/** Current parser's offset. */
+	int offset;
+	/** Successfully parsed signs count. */
+	int symbol_count;
+};
+
+enum json_path_type {
+	JSON_PATH_NUM,
+	JSON_PATH_STR,
+	/** Parser reached end of path. */
+	JSON_PATH_END,
+};
+
+/**
+ * Element of a JSON path. It can be either string or number.
+ * String idenfiers are in ["..."] and between dots. Numbers are
+ * indexes in [...].
+ */
+struct json_path_node {
+	enum json_path_type type;
+	union {
+		struct {
+			/** String identifier. */
+			const char *str;
+			/** Length of @a str. */
+			int len;
+		};
+		/** Index value. */
+		uint64_t num;
+	};
+};
+
+/**
+ * Init @a parser.
+ * @param[out] parser Parser to create.
+ * @param src Source string.
+ * @param src_len Length of @a src.
+ */
+static inline void
+json_path_parser_create(struct json_path_parser *parser, const char *src,
+                        int src_len)
+{
+	parser->src = src;
+	parser->src_len = src_len;
+	parser->offset = 0;
+	parser->symbol_count = 0;
+}
+
+/**
+ * Get a next path node.
+ * @param parser Parser.
+ * @param[out] node Node to store parsed result.
+ * @retval 0   Success. For result see @a node.str, node.len,
+ *             node.num.
+ * @retval > 0 Position of a syntax error. A position is 1-based
+ *             and starts from a beginning of a source string.
+ */
+int
+json_path_next(struct json_path_parser *parser, struct json_path_node *node);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TARANTOOL_JSON_PATH_H_INCLUDED */
diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt
index 943788b..667194c 100644
--- a/test/unit/CMakeLists.txt
+++ b/test/unit/CMakeLists.txt
@@ -129,6 +129,9 @@ add_executable(reflection_cxx.test reflection_cxx.cc unit.c
 add_executable(csv.test csv.c)
 target_link_libraries(csv.test csv)
 
+add_executable(json_path.test json_path.c)
+target_link_libraries(json_path.test json_path unit ${ICU_LIBRARIES})
+
 add_executable(rmean.test rmean.cc)
 target_link_libraries(rmean.test stat unit)
 add_executable(histogram.test histogram.c)
diff --git a/test/unit/json_path.c b/test/unit/json_path.c
new file mode 100644
index 0000000..336885b
--- /dev/null
+++ b/test/unit/json_path.c
@@ -0,0 +1,165 @@
+#include "json/path.h"
+#include "unit.h"
+#include "trivia/util.h"
+#include <string.h>
+
+#define reset_to_new_path(value) \
+	path = value; \
+	len = strlen(value); \
+	json_path_parser_create(&parser, path, len);
+
+#define is_next_index(value_len, value) \
+	path = parser.src + parser.offset; \
+	is(json_path_next(&parser, &node), 0, "parse <%." #value_len "s>", \
+	   path); \
+	is(node.type, JSON_PATH_NUM, "<%." #value_len "s> is num", path); \
+	is(node.num, value, "<%." #value_len "s> is " #value, path);
+
+#define is_next_key(value) \
+	len = strlen(value); \
+	is(json_path_next(&parser, &node), 0, "parse <" value ">"); \
+	is(node.type, JSON_PATH_STR, "<" value "> is str"); \
+	is(node.len, len, "len is %d", len); \
+	is(strncmp(node.str, value, len), 0, "str is " value);
+
+void
+test_basic()
+{
+	header();
+	plan(53);
+	const char *path;
+	int len;
+	struct json_path_parser parser;
+	struct json_path_node node;
+
+	reset_to_new_path("[0].field1.field2['field3'][5]");
+	is_next_index(3, 0);
+	is_next_key("field1");
+	is_next_key("field2");
+	is_next_key("field3");
+	is_next_index(3, 5);
+
+	reset_to_new_path("[3].field[2].field")
+	is_next_index(3, 3);
+	is_next_key("field");
+	is_next_index(3, 2);
+	is_next_key("field");
+
+	reset_to_new_path("[\"f1\"][\"f2'3'\"]");
+	is_next_key("f1");
+	is_next_key("f2'3'");
+
+	/* Support both '.field1...' and 'field1...'. */
+	reset_to_new_path(".field1");
+	is_next_key("field1");
+
+	/* Long number. */
+	reset_to_new_path("[1234]");
+	is_next_index(6, 1234);
+
+	/* Empty path. */
+	reset_to_new_path("");
+	is(json_path_next(&parser, &node), 0, "parse empty path");
+	is(node.type, JSON_PATH_END, "is str");
+
+	/* Path with no '.' at the beginning. */
+	reset_to_new_path("field1.field2");
+	is_next_key("field1");
+
+	check_plan();
+	footer();
+}
+
+#define check_new_path_on_error(value, errpos) \
+	reset_to_new_path(value); \
+	struct json_path_node node; \
+	is(json_path_next(&parser, &node), errpos, "error on position %d" \
+	   " for <%s>", errpos, path);
+
+struct path_and_errpos {
+	const char *path;
+	int errpos;
+};
+
+void
+test_errors()
+{
+	header();
+	plan(20);
+	const char *path;
+	int len;
+	struct json_path_parser parser;
+	const struct path_and_errpos errors[] = {
+		/* Double [[. */
+		{"[[", 2},
+		/* Not string inside []. */
+		{"[field]", 2},
+		/* String outside of []. */
+		{"'field1'.field2", 1},
+		/* Empty brackets. */
+		{"[]", 2},
+		/* Empty string. */
+		{"''", 1},
+		/* Spaces between identifiers. */
+		{" field1", 1},
+		/* Start from digit. */
+		{"1field", 1},
+		{".1field", 2},
+		/* Unfinished identifiers. */
+		{"['field", 8},
+		{"['field'", 9},
+		{"[123", 5},
+		{"['']", 3},
+		/*
+		 * Not trivial error: can not write
+		 * '[]' after '.'.
+		 */
+		{".[123]", 2},
+		/* Misc. */
+		{"[.]", 2},
+		/* Invalid UNICODE */
+		{"['aaa\xc2\xc2']", 6},
+		{".\xc2\xc2", 2},
+	};
+	for (size_t i = 0; i < lengthof(errors); ++i) {
+		reset_to_new_path(errors[i].path);
+		int errpos = errors[i].errpos;
+		struct json_path_node node;
+		is(json_path_next(&parser, &node), errpos,
+		   "error on position %d for <%s>", errpos, path);
+	}
+
+	reset_to_new_path("f.[2]")
+	struct json_path_node node;
+	json_path_next(&parser, &node);
+	is(json_path_next(&parser, &node), 3, "can not write <field.[index]>")
+
+	reset_to_new_path("f.")
+	json_path_next(&parser, &node);
+	is(json_path_next(&parser, &node), 3, "error in leading <.>");
+
+	reset_to_new_path("fiel d1")
+	json_path_next(&parser, &node);
+	is(json_path_next(&parser, &node), 5, "space inside identifier");
+
+	reset_to_new_path("field\t1")
+	json_path_next(&parser, &node);
+	is(json_path_next(&parser, &node), 6, "tab inside identifier");
+
+	check_plan();
+	footer();
+}
+
+int
+main()
+{
+	header();
+	plan(2);
+
+	test_basic();
+	test_errors();
+
+	int rc = check_plan();
+	footer();
+	return rc;
+}
diff --git a/test/unit/json_path.result b/test/unit/json_path.result
new file mode 100644
index 0000000..197316d
--- /dev/null
+++ b/test/unit/json_path.result
@@ -0,0 +1,84 @@
+	*** main ***
+1..2
+	*** test_basic ***
+    1..53
+    ok 1 - parse <[0]>
+    ok 2 - <[0]> is num
+    ok 3 - <[0]> is 0
+    ok 4 - parse <field1>
+    ok 5 - <field1> is str
+    ok 6 - len is 6
+    ok 7 - str is field1
+    ok 8 - parse <field2>
+    ok 9 - <field2> is str
+    ok 10 - len is 6
+    ok 11 - str is field2
+    ok 12 - parse <field3>
+    ok 13 - <field3> is str
+    ok 14 - len is 6
+    ok 15 - str is field3
+    ok 16 - parse <[5]>
+    ok 17 - <[5]> is num
+    ok 18 - <[5]> is 5
+    ok 19 - parse <[3]>
+    ok 20 - <[3]> is num
+    ok 21 - <[3]> is 3
+    ok 22 - parse <field>
+    ok 23 - <field> is str
+    ok 24 - len is 5
+    ok 25 - str is field
+    ok 26 - parse <[2]>
+    ok 27 - <[2]> is num
+    ok 28 - <[2]> is 2
+    ok 29 - parse <field>
+    ok 30 - <field> is str
+    ok 31 - len is 5
+    ok 32 - str is field
+    ok 33 - parse <f1>
+    ok 34 - <f1> is str
+    ok 35 - len is 2
+    ok 36 - str is f1
+    ok 37 - parse <f2'3'>
+    ok 38 - <f2'3'> is str
+    ok 39 - len is 5
+    ok 40 - str is f2'3'
+    ok 41 - parse <field1>
+    ok 42 - <field1> is str
+    ok 43 - len is 6
+    ok 44 - str is field1
+    ok 45 - parse <[1234]>
+    ok 46 - <[1234]> is num
+    ok 47 - <[1234]> is 1234
+    ok 48 - parse empty path
+    ok 49 - is str
+    ok 50 - parse <field1>
+    ok 51 - <field1> is str
+    ok 52 - len is 6
+    ok 53 - str is field1
+ok 1 - subtests
+	*** test_basic: done ***
+	*** test_errors ***
+    1..20
+    ok 1 - error on position 2 for <[[>
+    ok 2 - error on position 2 for <[field]>
+    ok 3 - error on position 1 for <'field1'.field2>
+    ok 4 - error on position 2 for <[]>
+    ok 5 - error on position 1 for <''>
+    ok 6 - error on position 1 for < field1>
+    ok 7 - error on position 1 for <1field>
+    ok 8 - error on position 2 for <.1field>
+    ok 9 - error on position 8 for <['field>
+    ok 10 - error on position 9 for <['field'>
+    ok 11 - error on position 5 for <[123>
+    ok 12 - error on position 3 for <['']>
+    ok 13 - error on position 2 for <.[123]>
+    ok 14 - error on position 2 for <[.]>
+    ok 15 - error on position 6 for <['aaa��']>
+    ok 16 - error on position 2 for <.��>
+    ok 17 - can not write <field.[index]>
+    ok 18 - error in leading <.>
+    ok 19 - space inside identifier
+    ok 20 - tab inside identifier
+ok 2 - subtests
+	*** test_errors: done ***
+	*** main: done ***
-- 
2.7.4

^ permalink raw reply	[flat|nested] 4+ messages in thread

* [tarantool-patches] [PATCH v2 3/3] Lua: implement json path access to tuple fields
  2018-04-06 11:08 [tarantool-patches] [PATCH v2 0/3] JSON Paths support Kirill Shcherbatov
  2018-04-06 11:08 ` [tarantool-patches] [PATCH v2 1/3] Allow gcov on Mac Kirill Shcherbatov
  2018-04-06 11:08 ` [tarantool-patches] [PATCH v2 2/3] Introduce json_path_parser with Unicode support Kirill Shcherbatov
@ 2018-04-06 11:08 ` Kirill Shcherbatov
  2 siblings, 0 replies; 4+ messages in thread
From: Kirill Shcherbatov @ 2018-04-06 11:08 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy, Kirill Shcherbatov

New tuple_field_raw_by_path and tuple_field_by_path APIs.

Resolves #1285
---
 src/box/CMakeLists.txt     |   4 +-
 src/box/lua/tuple.c        |  63 +++++++++----
 src/box/lua/tuple.lua      |  52 ++++-------
 src/box/tuple.h            |  21 +++++
 src/box/tuple_format.c     | 164 +++++++++++++++++++++++++++++++++
 src/box/tuple_format.h     |  19 ++++
 test/engine/tuple.result   | 225 +++++++++++++++++++++++++++++++++++++++++++++
 test/engine/tuple.test.lua |  66 +++++++++++++
 8 files changed, 556 insertions(+), 58 deletions(-)

diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt
index ad7f910..88c2c60 100644
--- a/src/box/CMakeLists.txt
+++ b/src/box/CMakeLists.txt
@@ -45,7 +45,7 @@ add_library(tuple STATIC
     field_def.c
     opt_def.c
 )
-target_link_libraries(tuple box_error core ${MSGPUCK_LIBRARIES} ${ICU_LIBRARIES} misc bit)
+target_link_libraries(tuple json_path box_error core ${MSGPUCK_LIBRARIES} ${ICU_LIBRARIES} misc bit)
 
 add_library(xlog STATIC xlog.c)
 target_link_libraries(xlog core box_error crc32 ${ZSTD_LIBRARIES})
@@ -131,5 +131,5 @@ add_library(box STATIC
     ${bin_sources})
 
 target_link_libraries(box box_error tuple stat xrow xlog vclock crc32 scramble
-                      ${common_libraries})
+                      json_path ${common_libraries})
 add_dependencies(box build_bundled_libs)
diff --git a/src/box/lua/tuple.c b/src/box/lua/tuple.c
index 7ca4299..6967da6 100644
--- a/src/box/lua/tuple.c
+++ b/src/box/lua/tuple.c
@@ -41,6 +41,7 @@
 #include "box/tuple_convert.h"
 #include "box/errcode.h"
 #include "box/memtx_tuple.h"
+#include "json/path.h"
 
 /** {{{ box.tuple Lua library
  *
@@ -402,36 +403,58 @@ lbox_tuple_transform(struct lua_State *L)
 }
 
 /**
- * Find a tuple field using its name.
+ * Find a tuple field by JSON path. If a field was not found and a
+ * path contains JSON syntax errors, then an exception is raised.
  * @param L Lua state.
- * @param tuple 1-th argument on lua stack, tuple to get field
+ * @param tuple 1-th argument on a lua stack, tuple to get field
  *        from.
- * @param field_name 2-th argument on lua stack, field name to
- *        get.
+ * @param path 2-th argument on lua stack. Can be field name,
+ *        JSON path to a field or a field number.
  *
- * @retval If a field was not found, return -1 and nil to lua else
- *         return 0 and decoded field.
+ * @retval not nil Found field value.
+ * @retval     nil A field is NULL or does not exist.
  */
 static int
-lbox_tuple_field_by_name(struct lua_State *L)
+lbox_tuple_field_by_path(struct lua_State *L)
 {
 	struct tuple *tuple = luaT_istuple(L, 1);
 	/* Is checked in Lua wrapper. */
 	assert(tuple != NULL);
-	assert(lua_isstring(L, 2));
-	size_t name_len;
-	const char *name = lua_tolstring(L, 2, &name_len);
-	uint32_t name_hash = lua_hashstring(L, 2);
-	const char *field =
-		tuple_field_by_name(tuple, name, name_len, name_hash);
-	if (field == NULL) {
-		lua_pushinteger(L, -1);
-		lua_pushnil(L);
-		return 2;
+	const char *field = NULL;
+	if (lua_isnumber(L, 2)) {
+		double dbl_index = lua_tonumber(L, 2);
+		if (dbl_index != floor(dbl_index))
+			goto usage_error;
+		int index = (int) floor(dbl_index) - TUPLE_INDEX_BASE;
+		if (index >= 0) {
+			field = tuple_field(tuple, index);
+			if (field == NULL) {
+				lua_pushnil(L);
+				return 1;
+			}
+		} else {
+			lua_pushnil(L);
+			return 1;
+		}
+	} else if (lua_isstring(L, 2)) {
+		size_t len;
+		const char *path = lua_tolstring(L, 2, &len);
+		if (len == 0)
+			goto usage_error;
+		if (tuple_field_by_path(tuple, path, (uint32_t) len,
+					lua_hashstring(L, 2), &field) != 0) {
+			return luaT_error(L);
+		} else if (field == NULL) {
+			lua_pushnil(L);
+			return 1;
+		}
+	} else {
+usage_error:
+		return luaL_error(L, "Usage: tuple[<path> or number >= 1]");
 	}
-	lua_pushinteger(L, 0);
+	assert(field != NULL);
 	luamp_decode(L, luaL_msgpack_default, &field);
-	return 2;
+	return 1;
 }
 
 static int
@@ -470,8 +493,8 @@ static const struct luaL_Reg lbox_tuple_meta[] = {
 	{"tostring", lbox_tuple_to_string},
 	{"slice", lbox_tuple_slice},
 	{"transform", lbox_tuple_transform},
-	{"tuple_field_by_name", lbox_tuple_field_by_name},
 	{"tuple_to_map", lbox_tuple_to_map},
+	{"tuple_field_by_path", lbox_tuple_field_by_path},
 	{NULL, NULL}
 };
 
diff --git a/src/box/lua/tuple.lua b/src/box/lua/tuple.lua
index 001971a..0eee325 100644
--- a/src/box/lua/tuple.lua
+++ b/src/box/lua/tuple.lua
@@ -9,16 +9,9 @@ local internal = require('box.internal')
 
 ffi.cdef[[
 /** \cond public */
-typedef struct tuple_format box_tuple_format_t;
-
-box_tuple_format_t *
-box_tuple_format_default(void);
 
 typedef struct tuple box_tuple_t;
 
-box_tuple_t *
-box_tuple_new(box_tuple_format_t *format, const char *data, const char *end);
-
 int
 box_tuple_ref(box_tuple_t *tuple);
 
@@ -34,9 +27,6 @@ box_tuple_bsize(const box_tuple_t *tuple);
 ssize_t
 box_tuple_to_buf(const box_tuple_t *tuple, char *buf, size_t size);
 
-box_tuple_format_t *
-box_tuple_format(const box_tuple_t *tuple);
-
 const char *
 box_tuple_field(const box_tuple_t *tuple, uint32_t i);
 
@@ -278,9 +268,9 @@ end
 
 msgpackffi.on_encode(const_tuple_ref_t, tuple_to_msgpack)
 
-local function tuple_field_by_name(tuple, name)
+local function tuple_field_by_path(tuple, path)
     tuple_check(tuple, "tuple['field_name']");
-    return internal.tuple.tuple_field_by_name(tuple, name)
+    return internal.tuple.tuple_field_by_path(tuple, path)
 end
 
 local methods = {
@@ -306,38 +296,28 @@ end
 
 methods["__serialize"] = tuple_totable -- encode hook for msgpack/yaml/json
 
-local tuple_field = function(tuple, field_n)
-    local field = builtin.box_tuple_field(tuple, field_n - 1)
-    if field == nil then
-        return nil
-    end
-    -- Use () to shrink stack to the first return value
-    return (msgpackffi.decode_unchecked(field))
-end
-
-
 ffi.metatype(tuple_t, {
     __len = function(tuple)
         return builtin.box_tuple_field_count(tuple)
     end;
     __tostring = internal.tuple.tostring;
     __index = function(tuple, key)
-        if type(key) == "number" then
-            return tuple_field(tuple, key)
-        elseif type(key) == "string" then
-            -- Try to get a field with a name = key. If it was not
-            -- found (rc ~= 0) then return a method from the
-            -- vtable. If a collision occurred, then fields have
-            -- higher priority. For example, if a tuple T has a
-            -- field with name 'bsize', then T.bsize returns field
-            -- value, not tuple_bsize function. To access hidden
-            -- methods use 'box.tuple.<method_name>(T, [args...])'.
-            local rc, field = tuple_field_by_name(tuple, key)
-            if rc == 0 then
-                return field
+        local res
+        if type(key) == "string" or type(key) == "number" then
+            -- Try to get a field by json path or by [index]. If
+            -- it was not found (rc ~= 0) then return a method
+            -- from the vtable. If a collision occurred, then
+            -- fields have higher priority. For example, if a
+            -- tuple T has a field with name 'bsize', then T.bsize
+            -- returns field value, not tuple_bsize function. To
+            -- access hidden methods use
+            -- 'box.tuple.<method_name>(T, [args...])'.
+            res = tuple_field_by_path(tuple, key)
+            if res ~= nil then
+                return res
             end
         end
-        return methods[key]
+        return methods[key] or res
     end;
     __eq = function(tuple_a, tuple_b)
         -- Two tuple are considered equal if they have same memory address
diff --git a/src/box/tuple.h b/src/box/tuple.h
index 6ebedf5..68ae9cd 100644
--- a/src/box/tuple.h
+++ b/src/box/tuple.h
@@ -514,6 +514,27 @@ tuple_field(const struct tuple *tuple, uint32_t fieldno)
 }
 
 /**
+ * Get tuple field by its JSON path.
+ * @param tuple.
+ * @param path Field path.
+ * @param path_len Length of @a path.
+ * @param path_hash Hash of @a path.
+ * @param[out] field Found field, or NULL, if not found.
+ *
+ * @retval  0 Success.
+ * @retval -1 Error in JSON path.
+ */
+static inline int
+tuple_field_by_path(struct tuple *tuple, const char *path,
+                    uint32_t path_len, uint32_t path_hash,
+                    const char **field)
+{
+	return tuple_field_raw_by_path(tuple_format(tuple), tuple_data(tuple),
+	                               tuple_field_map(tuple), path, path_len,
+	                               path_hash, field);
+}
+
+/**
  * Get tuple field by its name.
  * @param tuple Tuple to get field from.
  * @param name Field name.
diff --git a/src/box/tuple_format.c b/src/box/tuple_format.c
index e458f49..94342ea 100644
--- a/src/box/tuple_format.c
+++ b/src/box/tuple_format.c
@@ -28,6 +28,7 @@
  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
+#include "json/path.h"
 #include "tuple_format.h"
 
 /** Global table of tuple formats */
@@ -478,3 +479,166 @@ box_tuple_format_unref(box_tuple_format_t *format)
 {
 	tuple_format_unref(format);
 }
+
+/**
+ * Propagate @a field to MessagePack(field)[index].
+ * @param[in][out] field Field to propagate.
+ * @param index 1-based index to propagate to.
+ *
+ * @retval  0 Success, the index was found.
+ * @retval -1 Not found.
+ */
+static inline int
+tuple_field_go_to_index(const char **field, uint64_t index)
+{
+	enum mp_type type = mp_typeof(**field);
+	if (type == MP_ARRAY) {
+		if (index == 0)
+			return -1;
+		/* Make index 0-based. */
+		index -= TUPLE_INDEX_BASE;
+		uint32_t count = mp_decode_array(field);
+		if (index >= count)
+			return -1;
+		for (; index > 0; --index)
+			mp_next(field);
+		return 0;
+	} else if (type == MP_MAP) {
+		uint64_t count = mp_decode_map(field);
+		for (; count > 0; --count) {
+			type = mp_typeof(**field);
+			if (type == MP_UINT) {
+				uint64_t value = mp_decode_uint(field);
+				if (value == index)
+					return 0;
+			} else if (type == MP_INT) {
+				int64_t value = mp_decode_int(field);
+				if (value >= 0 && (uint64_t)value == index)
+					return 0;
+			} else {
+				/* Skip key. */
+				mp_next(field);
+			}
+			/* Skip value. */
+			mp_next(field);
+		}
+	}
+	return -1;
+}
+
+/**
+ * Propagate @a field to MessagePack(field)[key].
+ * @param[in][out] field Field to propagate.
+ * @param key Key to propagate to.
+ * @param len Length of @a key.
+ *
+ * @retval  0 Success, the index was found.
+ * @retval -1 Not found.
+ */
+static inline int
+tuple_field_go_to_key(const char **field, const char *key, int len)
+{
+	enum mp_type type = mp_typeof(**field);
+	if (type != MP_MAP)
+		return -1;
+	uint64_t count = mp_decode_map(field);
+	for (; count > 0; --count) {
+		type = mp_typeof(**field);
+		if (type == MP_STR) {
+			uint32_t value_len;
+			const char *value = mp_decode_str(field, &value_len);
+			if (value_len == (uint)len &&
+			    memcmp(value, key, len) == 0)
+				return 0;
+		} else {
+			/* Skip key. */
+			mp_next(field);
+		}
+		/* Skip value. */
+		mp_next(field);
+	}
+	return -1;
+}
+
+int
+tuple_field_raw_by_path(struct tuple_format *format, const char *tuple,
+                        const uint32_t *field_map, const char *path,
+                        uint32_t path_len, uint32_t path_hash,
+                        const char **field)
+{
+	assert(path_len > 0);
+	struct json_path_parser parser;
+	struct json_path_node node;
+	json_path_parser_create(&parser, path, path_len);
+	int rc = json_path_next(&parser, &node);
+	if (rc != 0)
+		goto best_effort;
+	switch(node.type) {
+	case JSON_PATH_NUM: {
+		int index = node.num;
+		if (index == 0)
+			goto best_effort;
+		index -= TUPLE_INDEX_BASE;
+		*field = tuple_field_raw(format, tuple, field_map, index);
+		if (*field == NULL)
+			goto best_effort;
+		break;
+	}
+	case JSON_PATH_STR: {
+		/* First part of a path is a field name. */
+		uint32_t name_hash;
+		if (path_len == (uint32_t) node.len) {
+			name_hash = path_hash;
+		} else {
+			/*
+			 * If a string is "field....", then its
+			 * precalculated juajit hash can not be
+			 * used. A tuple dictionary hashes only
+			 * name, not path.
+			 */
+			name_hash = field_name_hash(node.str, node.len);
+		}
+		*field = tuple_field_raw_by_name(format, tuple, field_map,
+						 node.str, node.len, name_hash);
+		if (*field == NULL)
+			goto best_effort;
+		break;
+	}
+	default:
+		assert(node.type == JSON_PATH_END);
+		*field = NULL;
+		return 0;
+	}
+	while (rc == 0 && (rc = json_path_next(&parser, &node)) == 0) {
+		switch(node.type) {
+		case JSON_PATH_NUM:
+			rc = tuple_field_go_to_index(field, node.num);
+			break;
+		case JSON_PATH_STR:
+			rc = tuple_field_go_to_key(field, node.str, node.len);
+			break;
+		default:
+			assert(node.type == JSON_PATH_END);
+			return 0;
+		}
+	}
+	assert(rc != 0);
+	/*
+	 * It is possible, that a field has a name as
+	 * well-formatted JSON. For example 'a.b.c.d' can be field
+	 * name. If a data was not found by such path, then try
+	 * to interpret the whole path as a field name.
+	 * The same is true for field names, that are not valid
+	 * JSON.
+	 */
+best_effort:
+	*field = tuple_field_raw_by_name(format, tuple, field_map, path,
+					 path_len, path_hash);
+	if (rc > 0 && *field == NULL) {
+		diag_set(ClientError, ER_ILLEGAL_PARAMS,
+			 tt_sprintf("error in path on position %d", rc));
+		return -1;
+	} else {
+		return 0;
+	}
+}
diff --git a/src/box/tuple_format.h b/src/box/tuple_format.h
index d35182d..a7dc9c7 100644
--- a/src/box/tuple_format.h
+++ b/src/box/tuple_format.h
@@ -377,6 +377,25 @@ tuple_field_raw_by_name(struct tuple_format *format, const char *tuple,
 	return tuple_field_raw(format, tuple, field_map, fieldno);
 }
 
+/**
+ * Get tuple field by its path.
+ * @param format Tuple format.
+ * @param tuple MessagePack tuple's body.
+ * @param field_map Tuple field map.
+ * @param path Field path.
+ * @param path_len Length of @a path.
+ * @param path_hash Hash of @a path.
+ * @param[out] field Found field, or NULL, if not found.
+ *
+ * @retval  0 Success.
+ * @retval -1 Error in JSON path.
+ */
+int
+tuple_field_raw_by_path(struct tuple_format *format, const char *tuple,
+                        const uint32_t *field_map, const char *path,
+                        uint32_t path_len, uint32_t path_hash,
+                        const char **field);
+
 #if defined(__cplusplus)
 } /* extern "C" */
 #endif /* defined(__cplusplus) */
diff --git a/test/engine/tuple.result b/test/engine/tuple.result
index b3b23b2..7aeb457 100644
--- a/test/engine/tuple.result
+++ b/test/engine/tuple.result
@@ -590,6 +590,231 @@ maplen(t1map), t1map[1], t1map[2], t1map[3]
 s:drop()
 ---
 ...
+format = {}
+---
+...
+format[1] = {name = 'field1', type = 'unsigned'}
+---
+...
+format[2] = {name = 'field2', type = 'array'}
+---
+...
+format[3] = {name = 'field3', type = 'map'}
+---
+...
+format[4] = {name = 'field4', type = 'string' }
+---
+...
+format[5] = {name = "[2][6]['привет中国world']['中国a']", type = 'string'}
+---
+...
+s = box.schema.space.create('test', {format = format})
+---
+...
+pk = s:create_index('pk')
+---
+...
+field2 = {1, 2, 3, "4", {5,6,7}, {привет中国world={中国="привет"}, key="value1", value="key1"}}
+---
+...
+field3 = {[10] = 100, k1 = 100, k2 = {1,2,3}, k3 = { {a=1, b=2}, {c=3, d=4} }, [-1] = 200}
+---
+...
+t = s:replace{1, field2, field3, "123456", "yes, this"}
+---
+...
+t[1]
+---
+- 1
+...
+t[2]
+---
+- [1, 2, 3, '4', [5, 6, 7], {'привет中国world': {'中国': 'привет'}, 'key': 'value1', 'value': 'key1'}]
+...
+t[3]
+---
+- {'k1': 100, 'k3': [{'a': 1, 'b': 2}, {'c': 3, 'd': 4}], -1: 200, 10: 100, 'k2': [
+    1, 2, 3]}
+...
+t[4]
+---
+- '123456'
+...
+t[2][1]
+---
+- 1
+...
+t["[2][1]"]
+---
+- 1
+...
+t[2][5]
+---
+- [5, 6, 7]
+...
+t["[2][5]"]
+---
+- [5, 6, 7]
+...
+t["[2][5][1]"]
+---
+- 5
+...
+t["[2][5][2]"]
+---
+- 6
+...
+t["[2][5][3]"]
+---
+- 7
+...
+t["[2][6].key"]
+---
+- value1
+...
+t["[2][6].value"]
+---
+- key1
+...
+t["[2][6]['key']"]
+---
+- value1
+...
+t["[2][6]['value']"]
+---
+- key1
+...
+t[2][6].привет中国world.中国
+---
+- привет
+...
+t["[2][6].привет中国world"].中国
+---
+- привет
+...
+t["[2][6].привет中国world.中国"]
+---
+- привет
+...
+t["[2][6]['привет中国world']"]["中国"]
+---
+- привет
+...
+t["[2][6]['привет中国world']['中国']"]
+---
+- привет
+...
+t["[2][6]['привет中国world']['中国a']"]
+---
+- yes, this
+...
+t["[3].k3[2].c"]
+---
+- 3
+...
+t["[4]"]
+---
+- '123456'
+...
+t.field1
+---
+- 1
+...
+t.field2[5]
+---
+- [5, 6, 7]
+...
+t[".field1"]
+---
+- 1
+...
+t["field1"]
+---
+- 1
+...
+t["[3][10]"]
+---
+- 100
+...
+-- Not found.
+t[0]
+---
+- null
+...
+t["[0]"]
+---
+- null
+...
+t["[1000]"]
+---
+- null
+...
+t.field1000
+---
+- null
+...
+t["not_found"]
+---
+- null
+...
+t["[2][5][10]"]
+---
+- null
+...
+t["[2][6].key100"]
+---
+- null
+...
+t["[2][0]"] -- 0-based index in array.
+---
+- null
+...
+t["[4][3]"] -- Can not index string.
+---
+- null
+...
+t["[4]['key']"]
+---
+- null
+...
+-- Not found 'a'. Return 'null' despite of syntax error on a
+-- next position.
+t["a.b.c d.e.f"]
+---
+- null
+...
+-- Sytax errors.
+t[""]
+---
+- error: 'builtin/box/tuple.lua:315: Usage: tuple[<path> or number >= 1]'
+...
+t["[2].[5]"]
+---
+- error: Illegal parameters, error in path on position 5
+...
+t["[-1]"]
+---
+- error: Illegal parameters, error in path on position 2
+...
+t[".."]
+---
+- error: Illegal parameters, error in path on position 2
+...
+t["[["]
+---
+- error: Illegal parameters, error in path on position 2
+...
+t["]]"]
+---
+- error: Illegal parameters, error in path on position 1
+...
+t["{"]
+---
+- error: Illegal parameters, error in path on position 1
+...
+s:drop()
+---
+...
 engine = nil
 ---
 ...
diff --git a/test/engine/tuple.test.lua b/test/engine/tuple.test.lua
index 6d7d254..90da8b2 100644
--- a/test/engine/tuple.test.lua
+++ b/test/engine/tuple.test.lua
@@ -200,5 +200,71 @@ t1map = t1:tomap()
 maplen(t1map), t1map[1], t1map[2], t1map[3]
 s:drop()
 
+format = {}
+format[1] = {name = 'field1', type = 'unsigned'}
+format[2] = {name = 'field2', type = 'array'}
+format[3] = {name = 'field3', type = 'map'}
+format[4] = {name = 'field4', type = 'string' }
+format[5] = {name = "[2][6]['привет中国world']['中国a']", type = 'string'}
+s = box.schema.space.create('test', {format = format})
+pk = s:create_index('pk')
+field2 = {1, 2, 3, "4", {5,6,7}, {привет中国world={中国="привет"}, key="value1", value="key1"}}
+field3 = {[10] = 100, k1 = 100, k2 = {1,2,3}, k3 = { {a=1, b=2}, {c=3, d=4} }, [-1] = 200}
+t = s:replace{1, field2, field3, "123456", "yes, this"}
+t[1]
+t[2]
+t[3]
+t[4]
+t[2][1]
+t["[2][1]"]
+t[2][5]
+t["[2][5]"]
+t["[2][5][1]"]
+t["[2][5][2]"]
+t["[2][5][3]"]
+t["[2][6].key"]
+t["[2][6].value"]
+t["[2][6]['key']"]
+t["[2][6]['value']"]
+t[2][6].привет中国world.中国
+t["[2][6].привет中国world"].中国
+t["[2][6].привет中国world.中国"]
+t["[2][6]['привет中国world']"]["中国"]
+t["[2][6]['привет中国world']['中国']"]
+t["[2][6]['привет中国world']['中国a']"]
+t["[3].k3[2].c"]
+t["[4]"]
+t.field1
+t.field2[5]
+t[".field1"]
+t["field1"]
+t["[3][10]"]
+
+-- Not found.
+t[0]
+t["[0]"]
+t["[1000]"]
+t.field1000
+t["not_found"]
+t["[2][5][10]"]
+t["[2][6].key100"]
+t["[2][0]"] -- 0-based index in array.
+t["[4][3]"] -- Can not index string.
+t["[4]['key']"]
+-- Not found 'a'. Return 'null' despite of syntax error on a
+-- next position.
+t["a.b.c d.e.f"]
+
+-- Sytax errors.
+t[""]
+t["[2].[5]"]
+t["[-1]"]
+t[".."]
+t["[["]
+t["]]"]
+t["{"]
+
+s:drop()
+
 engine = nil
 test_run = nil
-- 
2.7.4

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2018-04-06 11:09 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-04-06 11:08 [tarantool-patches] [PATCH v2 0/3] JSON Paths support Kirill Shcherbatov
2018-04-06 11:08 ` [tarantool-patches] [PATCH v2 1/3] Allow gcov on Mac Kirill Shcherbatov
2018-04-06 11:08 ` [tarantool-patches] [PATCH v2 2/3] Introduce json_path_parser with Unicode support Kirill Shcherbatov
2018-04-06 11:08 ` [tarantool-patches] [PATCH v2 3/3] Lua: implement json path access to tuple fields Kirill Shcherbatov

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox