From: Kirill Shcherbatov <kshcherbatov@tarantool.org> To: tarantool-patches@freelists.org, vdavydov.dev@gmail.com Cc: Kirill Shcherbatov <kshcherbatov@tarantool.org> Subject: [PATCH v9 6/6] box: specify indexes in user-friendly form Date: Sun, 3 Feb 2019 13:20:26 +0300 [thread overview] Message-ID: <a9476fdc808d1e6b9fe02bf4740f3362f910b3e6.1549187339.git.kshcherbatov@tarantool.org> (raw) In-Reply-To: <cover.1549187339.git.kshcherbatov@tarantool.org> Implemented a more convenient interface for creating an index by JSON path. Instead of specifying fieldno and relative path it is now 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 space, 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/schema.lua | 100 ++++++++++++++++++++++++++++++-------- test/engine/json.result | 94 +++++++++++++++++++++++++++++++++++ test/engine/json.test.lua | 27 ++++++++++ 3 files changed, 202 insertions(+), 19 deletions(-) diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua index 8a804f0ba..aa9fd4b96 100644 --- a/src/box/lua/schema.lua +++ b/src/box/lua/schema.lua @@ -575,6 +575,78 @@ local function update_index_parts_1_6_0(parts) return result end +-- +-- Get field index by format field name. +-- +local function format_field_index_by_name(format, name) + for k, v in pairs(format) do + if v.name == name then + return k + end + end + return nil +end + +-- +-- Get field 0-based index and relative JSON path to data by +-- field 1-based index or full JSON path. A particular case of a +-- full JSON path is the format field name. +-- +local function format_field_resolve(format, path, part_idx) + assert(type(path) == 'number' or type(path) == 'string') + local idx = nil + local relative_path = nil + local field_name = nil + -- Path doesn't require resolve. + if type(path) == 'number' then + idx = path + goto done + end + -- An attempt to interpret a path as the full field name. + idx = format_field_index_by_name(format, path) + if idx ~= nil then + relative_path = nil + goto done + end + -- Check if the initial part of the JSON path is a token of + -- the form [%d]. + field_name = string.match(path, "^%[(%d+)%]") + idx = tonumber(field_name) + if idx ~= nil then + relative_path = string.sub(path, string.len(field_name) + 3) + goto done + end + -- Check if the initial part of the JSON path is a token of + -- the form ["%s"] or ['%s']. + field_name = string.match(path, '^%[\"([^%]]+)\"%].*') or + string.match(path, "^%[\'([^%]]+)\'%].*") + idx = format_field_index_by_name(format, field_name) + if idx ~= nil then + relative_path = string.sub(path, string.len(field_name) + 5) + goto done + end + -- Check if the initial part of the JSON path is a string + -- token: assume that it ends with .*[ or .*. + field_name = string.match(path, "^([^.[]+)") + idx = format_field_index_by_name(format, field_name) + if idx ~= nil then + relative_path = string.sub(path, string.len(field_name) + 1) + goto done + end + -- Can't resolve field index by path. + assert(idx == nil) + box.error(box.error.ILLEGAL_PARAMS, "options.parts[" .. part_idx .. "]: " .. + "field was not found by name '" .. path .. "'") + +::done:: + if idx <= 0 then + box.error(box.error.ILLEGAL_PARAMS, + "options.parts[" .. part_idx .. "]: " .. + "field (number) must be one-based") + end + return idx - 1, relative_path +end + local function update_index_parts(format, parts) if type(parts) ~= "table" then box.error(box.error.ILLEGAL_PARAMS, @@ -622,25 +694,16 @@ local function update_index_parts(format, parts) end end end - if type(part.field) ~= 'number' and type(part.field) ~= 'string' then - 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 - box.error(box.error.ILLEGAL_PARAMS, - "options.parts[" .. i .. "]: field was not found by name '" .. part.field .. "'") - end - elseif part.field == 0 then - box.error(box.error.ILLEGAL_PARAMS, - "options.parts[" .. i .. "]: field (number) must be one-based") + if type(part.field) == 'number' or type(part.field) == 'string' then + local idx, path = format_field_resolve(format, part.field, i) + part.field = idx + part.path = path or part.path + parts_can_be_simplified = parts_can_be_simplified and part.path == nil + else + box.error(box.error.ILLEGAL_PARAMS, "options.parts[" .. i .. "]: " .. + "field (name or number) is expected") end - local fmt = format[part.field] + local fmt = format[part.field + 1] if part.type == nil then if fmt and fmt.type then part.type = fmt.type @@ -666,7 +729,6 @@ local function update_index_parts(format, parts) parts_can_be_simplified = false end end - part.field = part.field - 1 table.insert(result, part) end return result, parts_can_be_simplified diff --git a/test/engine/json.result b/test/engine/json.result index 3a5f472bc..2d50976e3 100644 --- a/test/engine/json.result +++ b/test/engine/json.result @@ -122,6 +122,100 @@ idx:max() s:drop() --- ... +-- Test user-friendly index creation interface. +s = box.schema.space.create('withdata', {engine = engine}) +--- +... +format = {{'data', 'map'}, {'meta', 'str'}} +--- +... +s:format(format) +--- +... +s:create_index('pk_invalid', {parts = {{']sad.FIO["sname"]', 'str'}}}) +--- +- error: 'Illegal parameters, options.parts[1]: field was not found by name '']sad.FIO["sname"]''' +... +s:create_index('pk_unexistent', {parts = {{'unexistent.FIO["sname"]', 'str'}}}) +--- +- error: 'Illegal parameters, options.parts[1]: field was not found by name ''unexistent.FIO["sname"]''' +... +pk = s:create_index('pk', {parts = {{'data.FIO["sname"]', 'str'}}}) +--- +... +pk ~= nil +--- +- true +... +sk2 = s:create_index('sk2', {parts = {{'["data"]FIO["sname"]', 'str'}}}) +--- +... +sk2 ~= nil +--- +- true +... +sk3 = s:create_index('sk3', {parts = {{'[\'data\']FIO["sname"]', 'str'}}}) +--- +... +sk3 ~= nil +--- +- true +... +sk4 = s:create_index('sk4', {parts = {{'[1]FIO["sname"]', 'str'}}}) +--- +... +sk4 ~= nil +--- +- true +... +pk.fieldno == sk2.fieldno +--- +- true +... +sk2.fieldno == sk3.fieldno +--- +- true +... +sk3.fieldno == sk4.fieldno +--- +- true +... +pk.path == sk2.path +--- +- true +... +sk2.path == sk3.path +--- +- true +... +sk3.path == sk4.path +--- +- true +... +s:insert{{town = 'London', FIO = {fname = 'James', sname = 'Bond'}}, "mi6"} +--- +- [{'town': 'London', 'FIO': {'fname': 'James', 'sname': 'Bond'}}, 'mi6'] +... +s:insert{{town = 'Moscow', FIO = {fname = 'Max', sname = 'Isaev', data = "extra"}}, "test"} +--- +- [{'town': 'Moscow', 'FIO': {'fname': 'Max', 'data': 'extra', 'sname': 'Isaev'}}, + 'test'] +... +pk:get({'Bond'}) == sk2:get({'Bond'}) +--- +- true +... +sk2:get({'Bond'}) == sk3:get({'Bond'}) +--- +- true +... +sk3:get({'Bond'}) == sk4:get({'Bond'}) +--- +- true +... +s:drop() +--- +... -- Test upsert of JSON-indexed data. s = box.schema.create_space('withdata', {engine = engine}) --- diff --git a/test/engine/json.test.lua b/test/engine/json.test.lua index 181eae02c..7a67e341e 100644 --- a/test/engine/json.test.lua +++ b/test/engine/json.test.lua @@ -34,6 +34,33 @@ idx:min() idx:max() s:drop() +-- Test user-friendly index creation interface. +s = box.schema.space.create('withdata', {engine = engine}) +format = {{'data', 'map'}, {'meta', 'str'}} +s:format(format) +s:create_index('pk_invalid', {parts = {{']sad.FIO["sname"]', 'str'}}}) +s:create_index('pk_unexistent', {parts = {{'unexistent.FIO["sname"]', 'str'}}}) +pk = s:create_index('pk', {parts = {{'data.FIO["sname"]', 'str'}}}) +pk ~= nil +sk2 = s:create_index('sk2', {parts = {{'["data"]FIO["sname"]', 'str'}}}) +sk2 ~= nil +sk3 = s:create_index('sk3', {parts = {{'[\'data\']FIO["sname"]', 'str'}}}) +sk3 ~= nil +sk4 = s:create_index('sk4', {parts = {{'[1]FIO["sname"]', 'str'}}}) +sk4 ~= nil +pk.fieldno == sk2.fieldno +sk2.fieldno == sk3.fieldno +sk3.fieldno == sk4.fieldno +pk.path == sk2.path +sk2.path == sk3.path +sk3.path == sk4.path +s:insert{{town = 'London', FIO = {fname = 'James', sname = 'Bond'}}, "mi6"} +s:insert{{town = 'Moscow', FIO = {fname = 'Max', sname = 'Isaev', data = "extra"}}, "test"} +pk:get({'Bond'}) == sk2:get({'Bond'}) +sk2:get({'Bond'}) == sk3:get({'Bond'}) +sk3:get({'Bond'}) == sk4:get({'Bond'}) +s:drop() + -- Test upsert of JSON-indexed data. s = box.schema.create_space('withdata', {engine = engine}) parts = {} -- 2.20.1
next prev parent reply other threads:[~2019-02-03 10:20 UTC|newest] Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top 2019-02-03 10:20 [PATCH v9 0/6] box: Indexes by JSON path Kirill Shcherbatov 2019-02-03 10:20 ` [PATCH v9 1/6] lib: update msgpuck library Kirill Shcherbatov 2019-02-04 9:48 ` Vladimir Davydov 2019-02-03 10:20 ` [PATCH v9 2/6] box: introduce tuple_field_raw_by_path routine Kirill Shcherbatov 2019-02-04 10:37 ` Vladimir Davydov 2019-02-03 10:20 ` [PATCH v9 3/6] box: introduce JSON Indexes Kirill Shcherbatov 2019-02-04 12:26 ` Vladimir Davydov 2019-02-03 10:20 ` [PATCH v9 4/6] box: introduce has_json_paths flag in templates Kirill Shcherbatov 2019-02-04 12:31 ` Vladimir Davydov 2019-02-03 10:20 ` [PATCH v9 5/6] box: introduce offset_slot cache in key_part Kirill Shcherbatov 2019-02-04 12:56 ` Vladimir Davydov 2019-02-04 13:02 ` [tarantool-patches] " Kirill Shcherbatov 2019-02-04 15:10 ` Vladimir Davydov 2019-02-03 10:20 ` Kirill Shcherbatov [this message] 2019-02-04 15:30 ` [PATCH v9 6/6] box: specify indexes in user-friendly form 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=a9476fdc808d1e6b9fe02bf4740f3362f910b3e6.1549187339.git.kshcherbatov@tarantool.org \ --to=kshcherbatov@tarantool.org \ --cc=tarantool-patches@freelists.org \ --cc=vdavydov.dev@gmail.com \ --subject='Re: [PATCH v9 6/6] 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