From: Vladimir Davydov <vdavydov.dev@gmail.com>
To: Kirill Shcherbatov <kshcherbatov@tarantool.org>
Cc: tarantool-patches@freelists.org, kostja@tarantool.org
Subject: Re: [PATCH v6 8/8] box: specify indexes in user-friendly form
Date: Tue, 18 Dec 2018 23:58:42 +0300 [thread overview]
Message-ID: <20181218205842.utabeqkxyt2mpl7f@esperanza> (raw)
In-Reply-To: <0cab9a9f155f9b2b3bf982acd083c4db132ba4a4.1544995259.git.kshcherbatov@tarantool.org>
https://www.freelists.org/post/tarantool-patches/PATCH-v5-99-box-specify-indexes-in-userfriendly-form,1
On Mon, Dec 17, 2018 at 09:52:52AM +0300, Kirill Shcherbatov wrote:
> Implemented a more convenient interface for creating an index
> by JSON path. Instead of specifying fieldno and relative path
> it comes possible to pass full JSON path to data.
>
> Closes #1012
>
> @TarantoolBot document
> Title: Indexes by JSON path
> Sometimes field data could have complex document structure.
> When this structure is consistent across whole document,
> you are able to create an index by JSON path.
>
> Example:
> s = box.schema.space.create('sample')
> format = {{'id', 'unsigned'}, {'data', 'map'}}
> s:format(format)
> -- explicit JSON index creation
> age_idx = s:create_index('age', {{2, 'number', path = ".age"}})
> -- user-friendly syntax for JSON index creation
> parts = {{'data.FIO["fname"]', 'str'}, {'data.FIO["sname"]', 'str'},
> {'data.age', 'number'}}
> info_idx = s:create_index('info', {parts = parts}})
> s:insert({1, {FIO={fname="James", sname="Bond"}, age=35}})
> ---
> src/box/lua/index.c | 64 +++++++++++++++++++++++++++++++++++++++
> src/box/lua/schema.lua | 22 +++++++-------
> test/engine/json.result | 28 +++++++++++++++++
> test/engine/json.test.lua | 8 +++++
> 4 files changed, 111 insertions(+), 11 deletions(-)
>
> diff --git a/src/box/lua/index.c b/src/box/lua/index.c
> index 6265c044a..9b04c5d9a 100644
> --- a/src/box/lua/index.c
> +++ b/src/box/lua/index.c
> @@ -35,6 +35,9 @@
> #include "box/box.h"
> #include "box/index.h"
> #include "box/lua/tuple.h"
> +#include "box/schema.h"
> +#include "box/tuple_format.h"
> +#include "json/json.h"
> #include "box/lua/misc.h" /* lbox_encode_tuple_on_gc() */
>
> /** {{{ box.index Lua library: access to spaces and indexes
> @@ -328,6 +331,66 @@ lbox_index_compact(lua_State *L)
> return 0;
> }
>
> +/**
> + * Resolve field index by absolute JSON path first component and
> + * return relative JSON path.
> + */
> +static int
> +lbox_index_path_resolve(struct lua_State *L)
> +{
> + if (lua_gettop(L) != 3 ||
> + !lua_isnumber(L, 1) || !lua_isnumber(L, 2) || !lua_isstring(L, 3)) {
> + return luaL_error(L, "Usage box.internal."
> + "path_resolve(part_id, space_id, path)");
> + }
> + uint32_t part_id = lua_tonumber(L, 1);
> + uint32_t space_id = lua_tonumber(L, 2);
> + size_t path_len;
> + const char *path = lua_tolstring(L, 3, &path_len);
> + struct space *space = space_cache_find(space_id);
> + if (space == NULL)
> + return luaT_error(L);
> + struct json_lexer lexer;
> + struct json_token token;
> + json_lexer_create(&lexer, path, path_len, TUPLE_INDEX_BASE);
> + int rc = json_lexer_next_token(&lexer, &token);
> + if (rc != 0) {
> + const char *err_msg =
> + tt_sprintf("options.parts[%d]: error in path on "
> + "position %d", part_id, rc);
> + diag_set(ClientError, ER_ILLEGAL_PARAMS, err_msg);
> + return luaT_error(L);
> + }
> + assert(space->format != NULL && space->format->dict != NULL);
> + uint32_t fieldno = token.num;
> + uint32_t field_count = tuple_format_field_count(space->format);
> + if (token.type == JSON_TOKEN_NUM && fieldno >= field_count) {
> + const char *err_msg =
> + tt_sprintf("options.parts[%d]: field '%d' referenced "
> + "in path is greater than format field "
> + "count %d", part_id,
> + fieldno + TUPLE_INDEX_BASE, field_count);
> + diag_set(ClientError, ER_ILLEGAL_PARAMS, err_msg);
> + return luaT_error(L);
> + } else if (token.type == JSON_TOKEN_STR &&
> + tuple_fieldno_by_name(space->format->dict, token.str,
> + token.len,
> + field_name_hash(token.str, token.len),
> + &fieldno) != 0) {
> + const char *err_msg =
> + tt_sprintf("options.parts[%d]: field was not found by "
> + "name '%.*s'", part_id, token.len,
> + token.str);
> + diag_set(ClientError, ER_ILLEGAL_PARAMS, err_msg);
> + return luaT_error(L);
> + }
> + fieldno += TUPLE_INDEX_BASE;
> + path += lexer.offset;
> + lua_pushnumber(L, fieldno);
> + lua_pushstring(L, path);
> + return 2;
> +}
> +
> /* }}} */
>
> void
> @@ -365,6 +428,7 @@ box_lua_index_init(struct lua_State *L)
> {"truncate", lbox_truncate},
> {"stat", lbox_index_stat},
> {"compact", lbox_index_compact},
> + {"path_resolve", lbox_index_path_resolve},
> {NULL, NULL}
> };
>
> diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
> index 8a804f0ba..497cf197c 100644
> --- a/src/box/lua/schema.lua
> +++ b/src/box/lua/schema.lua
> @@ -575,7 +575,7 @@ local function update_index_parts_1_6_0(parts)
> return result
> end
>
> -local function update_index_parts(format, parts)
> +local function update_index_parts(format, parts, space_id)
> if type(parts) ~= "table" then
> box.error(box.error.ILLEGAL_PARAMS,
> "options.parts parameter should be a table")
> @@ -626,16 +626,16 @@ local function update_index_parts(format, parts)
> box.error(box.error.ILLEGAL_PARAMS,
> "options.parts[" .. i .. "]: field (name or number) is expected")
> elseif type(part.field) == 'string' then
> - for k,v in pairs(format) do
> - if v.name == part.field then
> - part.field = k
> - break
> - end
> - end
> - if type(part.field) == 'string' then
> + local idx, path = box.internal.path_resolve(i, space_id, part.field)
> + if part.path ~= nil and part.path ~= path then
> box.error(box.error.ILLEGAL_PARAMS,
> - "options.parts[" .. i .. "]: field was not found by name '" .. part.field .. "'")
> + "options.parts[" .. i .. "]: field path '"..
> + part.path.." doesn't math the path '" ..
> + part.field .. "'")
> end
> + parts_can_be_simplified = parts_can_be_simplified and path == nil
> + part.field = idx
> + part.path = path or part.path
> elseif part.field == 0 then
> box.error(box.error.ILLEGAL_PARAMS,
> "options.parts[" .. i .. "]: field (number) must be one-based")
> @@ -792,7 +792,7 @@ box.schema.index.create = function(space_id, name, options)
> end
> end
> local parts, parts_can_be_simplified =
> - update_index_parts(format, options.parts)
> + update_index_parts(format, options.parts, space_id)
> -- create_index() options contains type, parts, etc,
> -- stored separately. Remove these members from index_opts
> local index_opts = {
> @@ -959,7 +959,7 @@ box.schema.index.alter = function(space_id, index_id, options)
> if options.parts then
> local parts_can_be_simplified
> parts, parts_can_be_simplified =
> - update_index_parts(format, options.parts)
> + update_index_parts(format, options.parts, space_id)
> -- save parts in old format if possible
> if parts_can_be_simplified then
> parts = simplify_index_parts(parts)
> diff --git a/test/engine/json.result b/test/engine/json.result
> index c33e568b3..60d71bdfa 100644
> --- a/test/engine/json.result
> +++ b/test/engine/json.result
> @@ -71,6 +71,34 @@ s:create_index('test2', {parts = {{2, 'number'}, {3, 'number', path = '.FIO.fnam
> - error: Field ["data"]["FIO"]["fname"] has type 'string' in one index, but type 'number'
> in another
> ...
> +s:create_index('test3', {parts = {{2, 'number'}, {']sad.FIO["fname"]', 'str'}}})
> +---
> +- error: 'Illegal parameters, options.parts[2]: error in path on position 1'
> +...
> +s:create_index('test3', {parts = {{2, 'number'}, {'[666].FIO["fname"]', 'str'}}})
> +---
> +- error: 'Illegal parameters, options.parts[2]: field ''666'' referenced in path is
> + greater than format field count 5'
> +...
> +s:create_index('test3', {parts = {{2, 'number'}, {'invalid.FIO["fname"]', 'str'}}})
> +---
> +- error: 'Illegal parameters, options.parts[2]: field was not found by name ''invalid'''
> +...
> +idx3 = s:create_index('test3', {parts = {{2, 'number'}, {'data.FIO["fname"]', 'str'}}})
> +---
> +...
> +assert(idx3 ~= nil)
> +---
> +- true
> +...
> +assert(idx3.parts[2].path == ".FIO[\"fname\"]")
> +---
> +- true
> +...
> +-- Vinyl has optimizations that omit index checks, so errors could differ.
> +idx3:drop()
> +---
> +...
> s:insert{7, 7, {town = 'London', FIO = 666}, 4, 5}
> ---
> - error: 'Tuple field ["data"]["FIO"] type does not match one required by operation:
> diff --git a/test/engine/json.test.lua b/test/engine/json.test.lua
> index 45153743d..f91846d79 100644
> --- a/test/engine/json.test.lua
> +++ b/test/engine/json.test.lua
> @@ -19,6 +19,14 @@ s:format(format)
> format = {{'id', 'unsigned'}, {'meta', 'unsigned'}, {'data', 'map'}, {'age', 'unsigned'}, {'level', 'unsigned'}}
> s:format(format)
> s:create_index('test2', {parts = {{2, 'number'}, {3, 'number', path = '.FIO.fname'}, {3, 'str', path = '["FIO"]["sname"]'}}})
> +s:create_index('test3', {parts = {{2, 'number'}, {']sad.FIO["fname"]', 'str'}}})
> +s:create_index('test3', {parts = {{2, 'number'}, {'[666].FIO["fname"]', 'str'}}})
> +s:create_index('test3', {parts = {{2, 'number'}, {'invalid.FIO["fname"]', 'str'}}})
> +idx3 = s:create_index('test3', {parts = {{2, 'number'}, {'data.FIO["fname"]', 'str'}}})
> +assert(idx3 ~= nil)
> +assert(idx3.parts[2].path == ".FIO[\"fname\"]")
> +-- Vinyl has optimizations that omit index checks, so errors could differ.
> +idx3:drop()
> s:insert{7, 7, {town = 'London', FIO = 666}, 4, 5}
> s:insert{7, 7, {town = 'London', FIO = {fname = 666, sname = 'Bond'}}, 4, 5}
> s:insert{7, 7, {town = 'London', FIO = {fname = "James"}}, 4, 5}
> --
> 2.19.2
>
next prev parent reply other threads:[~2018-12-18 20:58 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2018-12-17 6:52 [PATCH v6 0/8] box: Indexes by JSON path Kirill Shcherbatov
2018-12-17 6:52 ` [PATCH v6 1/8] box: refactor tuple_validate_raw Kirill Shcherbatov
2018-12-17 6:52 ` [PATCH v6 2/8] box: refactor ER_{FIELD_TYPE, ACTION_MISMATCH} Kirill Shcherbatov
2018-12-17 6:52 ` [PATCH v6 3/8] box: build path to field string uniformly on error Kirill Shcherbatov
2018-12-17 6:52 ` [PATCH v6 4/8] box: introduce JSON Indexes Kirill Shcherbatov
2018-12-17 6:52 ` [PATCH v6 5/8] box: introduce has_json_paths flag in templates Kirill Shcherbatov
2018-12-27 11:51 ` [tarantool-patches] " Konstantin Osipov
2018-12-27 11:52 ` Konstantin Osipov
2018-12-27 11:57 ` [tarantool-patches] " Konstantin Osipov
2018-12-17 6:52 ` [PATCH v6 6/8] box: tune tuple_field_raw_by_path for indexed data Kirill Shcherbatov
2018-12-17 6:52 ` [PATCH v6 7/8] box: introduce offset_slot cache in key_part Kirill Shcherbatov
2018-12-17 6:52 ` [PATCH v6 8/8] box: specify indexes in user-friendly form Kirill Shcherbatov
2018-12-18 20:58 ` Vladimir Davydov [this message]
2018-12-18 20:46 ` [PATCH v6 0/8] box: Indexes by JSON path Vladimir Davydov
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20181218205842.utabeqkxyt2mpl7f@esperanza \
--to=vdavydov.dev@gmail.com \
--cc=kostja@tarantool.org \
--cc=kshcherbatov@tarantool.org \
--cc=tarantool-patches@freelists.org \
--subject='Re: [PATCH v6 8/8] box: specify indexes in user-friendly form' \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox