Tarantool development patches archive
 help / color / mirror / Atom feed
From: Kirill Shcherbatov <kshcherbatov@tarantool.org>
To: tarantool-patches@freelists.org, vdavydov.dev@gmail.com
Cc: kostja@tarantool.org, Kirill Shcherbatov <kshcherbatov@tarantool.org>
Subject: [PATCH v8 5/5] box: specify indexes in user-friendly form
Date: Wed, 16 Jan 2019 16:44:43 +0300	[thread overview]
Message-ID: <a9e76ac945ba8dde15c1fa1bd170baf61c3835f6.1547645795.git.kshcherbatov@tarantool.org> (raw)
In-Reply-To: <cover.1547645795.git.kshcherbatov@tarantool.org>

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 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    | 60 ++++++++++++++++++++++++++++++++++-----
 test/engine/json.result   | 42 +++++++++++++++++++++++++++
 test/engine/json.test.lua | 12 ++++++++
 3 files changed, 107 insertions(+), 7 deletions(-)

diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index 8a804f0ba..a55ef5b22 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -575,6 +575,49 @@ local function update_index_parts_1_6_0(parts)
     return result
 end
 
+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
+
+local function format_field_resolve(format, name)
+    local idx = nil
+    local field_name = nil
+    -- try resolve whole name
+    idx = format_field_index_by_name(format, name)
+    if idx ~= nil then
+        print("Case 1 "..idx)
+        return idx, nil
+    end
+    -- try resolve [%d]
+    field_name = string.match(name, "^%[(%d+)%]")
+    idx = tonumber(field_name)
+    if idx ~= nil then
+        print("Case 2 "..idx..string.sub(name, string.len(field_name) + 3))
+        return idx, string.sub(name, string.len(field_name) + 3)
+    end
+    -- try resolve ["%s"] or ['%s']
+    field_name = string.match(name, "^%[\"(.*)\"%]") or
+                 string.match(name, "^%[\'(.*)\'%]")
+    idx = format_field_index_by_name(format, field_name)
+    if idx ~= nil then
+        print("Case 3 "..idx..string.sub(name, string.len(field_name) + 5))
+        return idx, string.sub(name, string.len(field_name) + 5)
+    end
+    -- try to resolve .*[ and .*.
+    field_name = string.match(name, "^([^.%[]-)[.%[]")
+    idx = format_field_index_by_name(format, field_name)
+    if idx ~= nil then
+        print("Case 4 "..idx..string.sub(name, string.len(field_name) + 1))
+        return idx, string.sub(name, string.len(field_name) + 1)
+    end
+    return nil, nil
+end
+
 local function update_index_parts(format, parts)
     if type(parts) ~= "table" then
         box.error(box.error.ILLEGAL_PARAMS,
@@ -626,16 +669,19 @@ 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
+            local idx, path = format_field_resolve(format, part.field)
+            if idx == nil then
+               box.error(box.error.ILLEGAL_PARAMS,
+                         "options.parts[" .. i .. "]: field was not found by name '" .. part.field .. "'")
             end
-            if type(part.field) == 'string' then
+            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 '"..
+                          path.." doesn't math the path '" ..part.path.."'")
             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")
diff --git a/test/engine/json.result b/test/engine/json.result
index 9e5accb0b..32f787a3d 100644
--- a/test/engine/json.result
+++ b/test/engine/json.result
@@ -70,6 +70,48 @@ s:create_index('test2', {parts = {{2, 'number'}, {3, 'number', path = 'FIO.fname
 - error: Field [3]["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]: field was not found by name '']sad.FIO["fname"]'''
+...
+s:create_index('test3', {parts = {{2, 'number'}, {'[3].FIO["fname"]', 'str', path = "FIO[\"sname\"]"}}})
+---
+- error: 'Illegal parameters, options.parts[2]: field path ''.FIO["fname"] doesn''t
+    math the path ''FIO["sname"]'''
+...
+idx3 = s:create_index('test3', {parts = {{2, 'number'}, {'[3].FIO["fname"]', 'str'}}})
+---
+...
+idx3 ~= nil
+---
+- true
+...
+idx3.parts[2].path == ".FIO[\"fname\"]"
+---
+- true
+...
+s:create_index('test4', {parts = {{2, 'number'}, {'invalid.FIO["fname"]', 'str'}}})
+---
+- error: 'Illegal parameters, options.parts[2]: field was not found by name ''invalid.FIO["fname"]'''
+...
+idx4 = s:create_index('test4', {parts = {{2, 'number'}, {'data.FIO["fname"]', 'str'}}})
+---
+...
+idx4 ~= nil
+---
+- true
+...
+idx4.parts[2].path == ".FIO[\"fname\"]"
+---
+- true
+...
+-- Vinyl has optimizations that omit index checks, so errors could differ.
+idx3:drop()
+---
+...
+idx4:drop()
+---
+...
 s:insert{7, 7, {town = 'London', FIO = 666}, 4, 5}
 ---
 - error: 'Tuple field [3]["FIO"] type does not match one required by operation: expected
diff --git a/test/engine/json.test.lua b/test/engine/json.test.lua
index a876ff1ed..cfed275b0 100644
--- a/test/engine/json.test.lua
+++ b/test/engine/json.test.lua
@@ -19,6 +19,18 @@ 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'}, {'[3].FIO["fname"]', 'str', path = "FIO[\"sname\"]"}}})
+idx3 = s:create_index('test3', {parts = {{2, 'number'}, {'[3].FIO["fname"]', 'str'}}})
+idx3 ~= nil
+idx3.parts[2].path == ".FIO[\"fname\"]"
+s:create_index('test4', {parts = {{2, 'number'}, {'invalid.FIO["fname"]', 'str'}}})
+idx4 = s:create_index('test4', {parts = {{2, 'number'}, {'data.FIO["fname"]', 'str'}}})
+idx4 ~= nil
+idx4.parts[2].path == ".FIO[\"fname\"]"
+-- Vinyl has optimizations that omit index checks, so errors could differ.
+idx3:drop()
+idx4: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

  parent reply	other threads:[~2019-01-16 13:44 UTC|newest]

Thread overview: 13+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-01-16 13:44 [PATCH v8 0/5] box: Indexes by JSON path Kirill Shcherbatov
2019-01-16 13:44 ` [PATCH v8 1/5] lib: updated msgpuck library version Kirill Shcherbatov
2019-01-23 12:53   ` Vladimir Davydov
2019-01-16 13:44 ` [PATCH v8 2/5] box: refactor tuple_field_raw_by_path routine Kirill Shcherbatov
2019-01-23 13:15   ` Vladimir Davydov
2019-01-16 13:44 ` [PATCH v8 3/5] box: introduce JSON Indexes Kirill Shcherbatov
2019-01-23 13:49   ` Vladimir Davydov
2019-01-16 13:44 ` [PATCH v8 4/5] box: introduce offset_slot cache in key_part Kirill Shcherbatov
2019-01-23 14:23   ` Vladimir Davydov
2019-01-16 13:44 ` Kirill Shcherbatov [this message]
2019-01-23 15:29   ` [PATCH v8 5/5] box: specify indexes in user-friendly form Vladimir Davydov
2019-01-16 15:35 ` [tarantool-patches] [PATCH v8 6/6] box: introduce has_json_paths flag in templates Kirill Shcherbatov
2019-01-23 14:15   ` 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=a9e76ac945ba8dde15c1fa1bd170baf61c3835f6.1547645795.git.kshcherbatov@tarantool.org \
    --to=kshcherbatov@tarantool.org \
    --cc=kostja@tarantool.org \
    --cc=tarantool-patches@freelists.org \
    --cc=vdavydov.dev@gmail.com \
    --subject='Re: [PATCH v8 5/5] 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