Tarantool development patches archive
 help / color / mirror / Atom feed
* [PATCH 0/4] A few fixes/improvements for autoincrement indexes
@ 2019-05-15 10:33 Vladimir Davydov
  2019-05-15 10:33 ` [PATCH 1/4] schema: use tuple field names in Lua Vladimir Davydov
                   ` (4 more replies)
  0 siblings, 5 replies; 17+ messages in thread
From: Vladimir Davydov @ 2019-05-15 10:33 UTC (permalink / raw)
  To: tarantool-patches

See comments to individual patches for more details.

https://github.com/tarantool/tarantool/issues/4009
https://github.com/tarantool/tarantool/issues/4210
https://github.com/tarantool/tarantool/issues/4214
https://github.com/tarantool/tarantool/commits/dv/sequence-fixes

Vladimir Davydov (4):
  schema: use tuple field names in Lua
  schema: fix error while altering index with sequence
  schema: allow to set sequence for any index part, not just the first
  schema: explicitly forbid setting sequence for json path key part

 src/box/alter.cc             |  33 +++++-
 src/box/bootstrap.snap       | Bin 4374 -> 4379 bytes
 src/box/lua/schema.lua       | 171 ++++++++++++++++++------------
 src/box/lua/space.cc         |   7 ++
 src/box/lua/upgrade.lua      |  35 ++++++-
 src/box/request.c            |   2 +-
 src/box/schema_def.h         |   1 +
 src/box/space.h              |   5 +
 src/box/sql/build.c          |   7 +-
 src/box/sql/insert.c         |   2 +-
 test/box-py/bootstrap.result |   5 +-
 test/box/access_misc.result  |   3 +-
 test/box/alter.result        |  15 ---
 test/box/alter.test.lua      |   6 --
 test/box/sequence.result     | 243 +++++++++++++++++++++++++++++++++++++++++--
 test/box/sequence.test.lua   |  83 +++++++++++++--
 16 files changed, 507 insertions(+), 111 deletions(-)

-- 
2.11.0

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

* [PATCH 1/4] schema: use tuple field names in Lua
  2019-05-15 10:33 [PATCH 0/4] A few fixes/improvements for autoincrement indexes Vladimir Davydov
@ 2019-05-15 10:33 ` Vladimir Davydov
  2019-05-15 10:33 ` [PATCH 2/4] schema: fix error while altering index with sequence Vladimir Davydov
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 17+ messages in thread
From: Vladimir Davydov @ 2019-05-15 10:33 UTC (permalink / raw)
  To: tarantool-patches

When schema.lua was introduced, there was no such thing as space format
and we had to access tuple fields by no. Now we can use human readable
names. Let's do it - this should improve code readability.

A note about box/alter.test.lua: for some reason it clears format of
_space and _index system spaces, which apparently breaks our assumption
about field names. Let's zap those pointless test cases.
---
 src/box/lua/schema.lua  | 98 ++++++++++++++++++++++++-------------------------
 test/box/alter.result   | 15 --------
 test/box/alter.test.lua |  6 ---
 3 files changed, 49 insertions(+), 70 deletions(-)

diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index f31cf7f2..036e5f2d 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -155,7 +155,7 @@ local function user_or_role_resolve(user)
     if tuple == nil then
         return nil
     end
-    return tuple[1]
+    return tuple.id
 end
 
 local function role_resolve(name_or_id)
@@ -166,10 +166,10 @@ local function role_resolve(name_or_id)
     elseif type(name_or_id) ~= 'nil' then
         tuple = _vuser:get{name_or_id}
     end
-    if tuple == nil or tuple[4] ~= 'role' then
+    if tuple == nil or tuple.type ~= 'role' then
         return nil
     else
-        return tuple[1]
+        return tuple.id
     end
 end
 
@@ -181,10 +181,10 @@ local function user_resolve(name_or_id)
     elseif type(name_or_id) ~= 'nil' then
         tuple = _vuser:get{name_or_id}
     end
-    if tuple == nil or tuple[4] ~= 'user' then
+    if tuple == nil or tuple.type ~= 'user' then
         return nil
     else
-        return tuple[1]
+        return tuple.id
     end
 end
 
@@ -197,7 +197,7 @@ local function sequence_resolve(name_or_id)
         tuple = _vsequence:get{name_or_id}
     end
     if tuple ~= nil then
-        return tuple[1], tuple
+        return tuple.id, tuple
     else
         return nil
     end
@@ -209,7 +209,7 @@ local function revoke_object_privs(object_type, object_id)
     local _priv = box.space[box.schema.PRIV_ID]
     local privs = _vpriv.index.object:select{object_type, object_id}
     for k, tuple in pairs(privs) do
-        local uid = tuple[2]
+        local uid = tuple.grantee
         _priv:delete{uid, object_type, object_id}
     end
 end
@@ -453,13 +453,13 @@ box.schema.space.create = function(name, options)
         local _schema = box.space._schema
         local max_id = _schema:update({'max_id'}, {{'+', 2, 1}})
         if max_id == nil then
-            id = _space.index.primary:max()[1]
+            id = _space.index.primary:max().id
             if id < box.schema.SYSTEM_ID_MAX then
                 id = box.schema.SYSTEM_ID_MAX
             end
             max_id = _schema:insert{'max_id', id + 1}
         end
-        id = max_id[2]
+        id = max_id.value
     end
     local uid = session.euid()
     if options.user then
@@ -492,7 +492,7 @@ function box.schema.space.format(id, format)
         if tuple == nil then
             box.error(box.error.NO_SUCH_SPACE, '#' .. tostring(id))
         end
-        return tuple[7]
+        return tuple.format
     else
         check_param(format, 'format', 'table')
         format = update_format(format)
@@ -514,9 +514,9 @@ box.schema.space.drop = function(space_id, space_name, opts)
     local _space_sequence = box.space[box.schema.SPACE_SEQUENCE_ID]
     local _fk_constraint = box.space[box.schema.FK_CONSTRAINT_ID]
     local sequence_tuple = _space_sequence:delete{space_id}
-    if sequence_tuple ~= nil and sequence_tuple[3] == true then
+    if sequence_tuple ~= nil and sequence_tuple.is_generated == true then
         -- Delete automatically generated sequence.
-        box.schema.sequence.drop(sequence_tuple[2])
+        box.schema.sequence.drop(sequence_tuple.sequence_id)
     end
     for _, t in _trigger.index.space_id:pairs({space_id}) do
         _trigger:delete({t.name})
@@ -527,7 +527,7 @@ box.schema.space.drop = function(space_id, space_name, opts)
     local keys = _vindex:select(space_id)
     for i = #keys, 1, -1 do
         local v = keys[i]
-        _index:delete{v[1], v[2]}
+        _index:delete{v.id, v.iid}
     end
     revoke_object_privs('space', space_id)
     _truncate:delete{space_id}
@@ -847,9 +847,9 @@ box.schema.index.create = function(space_id, name, options)
         local tuple = _vindex.index[0]
             :select(space_id, { limit = 1, iterator = 'LE' })[1]
         if tuple then
-            local id = tuple[1]
+            local id = tuple.id
             if id == space_id then
-                iid = tuple[2] + 1
+                iid = tuple.iid + 1
             end
         end
     end
@@ -923,9 +923,9 @@ box.schema.index.drop = function(space_id, index_id)
     if index_id == 0 then
         local _space_sequence = box.space[box.schema.SPACE_SEQUENCE_ID]
         local sequence_tuple = _space_sequence:delete{space_id}
-        if sequence_tuple ~= nil and sequence_tuple[3] == true then
+        if sequence_tuple ~= nil and sequence_tuple.is_generated == true then
             -- Delete automatically generated sequence.
-            box.schema.sequence.drop(sequence_tuple[2])
+            box.schema.sequence.drop(sequence_tuple.sequence_id)
         end
     end
     local _index = box.space[box.schema.INDEX_ID]
@@ -993,25 +993,23 @@ box.schema.index.alter = function(space_id, index_id, options)
     local tuple = _index:get{space_id, index_id }
     local parts = {}
     local index_opts = {}
-    local OPTS = 5
-    local PARTS = 6
-    if type(tuple[OPTS]) == 'number' then
+    if type(tuple.opts) == 'number' then
         -- old format
-        index_opts.unique = tuple[OPTS] == 1
-        local part_count = tuple[PARTS]
+        index_opts.unique = tuple[5] == 1
+        local part_count = tuple[6]
         for i = 1, part_count do
             table.insert(parts, {tuple[2 * i + 4], tuple[2 * i + 5]});
         end
     else
         -- new format
-        index_opts = tuple[OPTS]
-        parts = tuple[PARTS]
+        index_opts = tuple.opts
+        parts = tuple.parts
     end
     if options.name == nil then
-        options.name = tuple[3]
+        options.name = tuple.name
     end
     if options.type == nil then
-        options.type = tuple[4]
+        options.type = tuple.type
     end
     for k, t in pairs(index_options) do
         if options[k] ~= nil then
@@ -1048,7 +1046,7 @@ box.schema.index.alter = function(space_id, index_id, options)
         end
     end
     if sequence == true then
-        if sequence_tuple == nil or sequence_tuple[3] == false then
+        if sequence_tuple == nil or sequence_tuple.is_generated == false then
             sequence = box.schema.sequence.create(space.name .. '_seq')
             sequence = sequence.id
             sequence_is_generated = true
@@ -1070,10 +1068,10 @@ box.schema.index.alter = function(space_id, index_id, options)
     if sequence then
         _space_sequence:replace{space_id, sequence, sequence_is_generated}
     end
-    if sequence_tuple ~= nil and sequence_tuple[3] == true and
-       sequence_tuple[2] ~= sequence then
+    if sequence_tuple ~= nil and sequence_tuple.is_generated == true and
+       sequence_tuple.sequence_id ~= sequence then
         -- Delete automatically generated sequence.
-        box.schema.sequence.drop(sequence_tuple[2])
+        box.schema.sequence.drop(sequence_tuple.sequence_id)
     end
 end
 
@@ -1662,13 +1660,13 @@ end
 
 local function sequence_on_alter(old_tuple, new_tuple)
     if old_tuple and not new_tuple then
-        local old_name = old_tuple[3]
+        local old_name = old_tuple.name
         box.sequence[old_name] = nil
     elseif not old_tuple and new_tuple then
         local seq = sequence_new(new_tuple)
         box.sequence[seq.name] = seq
     else
-        local old_name = old_tuple[3]
+        local old_name = old_tuple.name
         local seq = box.sequence[old_name]
         if not seq then
             seq = sequence_new(seq, new_tuple)
@@ -1919,7 +1917,7 @@ local function object_resolve(object_type, object_name)
             func = _vfunc:get{object_name}
         end
         if func then
-            return func[1]
+            return func.id
         else
             box.error(box.error.NO_SUCH_FUNCTION, object_name)
         end
@@ -1945,8 +1943,8 @@ local function object_resolve(object_type, object_name)
         else
             role_or_user = _vuser:get{object_name}
         end
-        if role_or_user and role_or_user[4] == object_type then
-            return role_or_user[1]
+        if role_or_user and role_or_user.type == object_type then
+            return role_or_user.id
         elseif object_type == 'role' then
             box.error(box.error.NO_SUCH_ROLE, object_name)
         else
@@ -1973,7 +1971,7 @@ local function object_name(object_type, object_id)
     else
         box.error(box.error.UNKNOWN_SCHEMA_OBJECT, object_type)
     end
-    return space:get{object_id}[3]
+    return space:get{object_id}.name
 end
 
 box.schema.func = {}
@@ -2010,7 +2008,7 @@ box.schema.func.drop = function(name, opts)
         tuple = _vfunc:get{name}
     end
     if tuple then
-        fid = tuple[1]
+        fid = tuple.id
     end
     if fid == nil then
         if not opts.if_exists then
@@ -2096,7 +2094,7 @@ end
 box.internal.collation.id_by_name = function(name)
     local _coll = box.space[box.schema.COLLATION_ID]
     local coll = _coll.index.name:get{name}
-    return coll[1]
+    return coll.id
 end
 
 box.schema.user = {}
@@ -2148,7 +2146,7 @@ box.schema.user.create = function(name, opts)
         auth_mech_list["chap-sha1"] = box.schema.user.password(opts.password)
     end
     local _user = box.space[box.schema.USER_ID]
-    uid = _user:auto_increment{session.euid(), name, 'user', auth_mech_list}[1]
+    uid = _user:auto_increment{session.euid(), name, 'user', auth_mech_list}.id
     -- grant role 'public' to the user
     box.schema.user.grant(uid, 'public')
     -- Grant privilege 'alter' on itself, so that it can
@@ -2201,7 +2199,7 @@ local function grant(uid, name, privilege, object_type,
     local tuple = _vpriv:get{uid, object_type, oid}
     local old_privilege
     if tuple ~= nil then
-        old_privilege = tuple[5]
+        old_privilege = tuple.privilege
     else
         old_privilege = 0
     end
@@ -2255,8 +2253,8 @@ local function revoke(uid, name, privilege, object_type, object_name, options)
                       object_type, object_name)
         end
     end
-    local old_privilege = tuple[5]
-    local grantor = tuple[1]
+    local old_privilege = tuple.privilege
+    local grantor = tuple.grantor
     -- sic:
     -- a user may revoke more than he/she granted
     -- (erroneous user input)
@@ -2282,25 +2280,25 @@ local function drop(uid, opts)
     local _vpriv = box.space[box.schema.VPRIV_ID]
     local spaces = box.space[box.schema.VSPACE_ID].index.owner:select{uid}
     for k, tuple in pairs(spaces) do
-        box.space[tuple[1]]:drop()
+        box.space[tuple.id]:drop()
     end
     local funcs = box.space[box.schema.VFUNC_ID].index.owner:select{uid}
     for k, tuple in pairs(funcs) do
-        box.schema.func.drop(tuple[1])
+        box.schema.func.drop(tuple.id)
     end
     -- if this is a role, revoke this role from whoever it was granted to
     local grants = _vpriv.index.object:select{'role', uid}
     for k, tuple in pairs(grants) do
-        revoke(tuple[2], tuple[2], uid)
+        revoke(tuple.grantee, tuple.grantee, uid)
     end
     local sequences = box.space[box.schema.VSEQUENCE_ID].index.owner:select{uid}
     for k, tuple in pairs(sequences) do
-        box.schema.sequence.drop(tuple[1])
+        box.schema.sequence.drop(tuple.id)
     end
     -- xxx: hack, we have to revoke session and usage privileges
     -- of a user using a setuid function in absence of create/drop
     -- privileges and grant option
-    if box.space._vuser:get{uid}[4] == 'user' then
+    if box.space._vuser:get{uid}.type == 'user' then
         box.session.su('admin', box.schema.user.revoke, uid,
                        'session,usage', 'universe', nil, {if_exists = true})
     end
@@ -2309,7 +2307,8 @@ local function drop(uid, opts)
     for k, tuple in pairs(privs) do
         -- we need an additional box.session.su() here, because of
         -- unnecessary check for privilege PRIV_REVOKE in priv_def_check()
-        box.session.su("admin", revoke, uid, uid, tuple[5], tuple[3], tuple[4])
+        box.session.su("admin", revoke, uid, uid, tuple.privilege,
+                       tuple.object_type, tuple.object_id)
     end
     box.space[box.schema.USER_ID]:delete{uid}
 end
@@ -2369,7 +2368,8 @@ local function info(id)
     for _, v in pairs(_priv:select{id}) do
         table.insert(
             privs,
-            {privilege_name(v[5]), v[3], object_name(v[3], v[4])}
+            {privilege_name(v.privilege), v.object_type,
+             object_name(v.object_type, v.object_id)}
         )
     end
     return privs
diff --git a/test/box/alter.result b/test/box/alter.result
index c1b1de13..75d6dae2 100644
--- a/test/box/alter.result
+++ b/test/box/alter.result
@@ -52,25 +52,10 @@ _space:insert{_space.id, ADMIN, '_space', 'memtx', 0, EMPTY_MAP, {}}
 ---
 - error: Duplicate key exists in unique index 'primary' in space '_space'
 ...
-_space:replace{_space.id, ADMIN, '_space', 'memtx', 0, EMPTY_MAP, {}}
----
-- [280, 1, '_space', 'memtx', 0, {}, []]
-...
 _space:insert{_index.id, ADMIN, '_index', 'memtx', 0, EMPTY_MAP, {}}
 ---
 - error: Duplicate key exists in unique index 'primary' in space '_space'
 ...
-_space:replace{_index.id, ADMIN, '_index', 'memtx', 0, EMPTY_MAP, {}}
----
-- [288, 1, '_index', 'memtx', 0, {}, []]
-...
---
--- Can't change properties of a space
---
-_space:replace{_space.id, ADMIN, '_space', 'memtx', 0, EMPTY_MAP, {}}
----
-- [280, 1, '_space', 'memtx', 0, {}, []]
-...
 --
 -- Can't drop a system space
 --
diff --git a/test/box/alter.test.lua b/test/box/alter.test.lua
index 733d27c5..3cb0c4f8 100644
--- a/test/box/alter.test.lua
+++ b/test/box/alter.test.lua
@@ -24,13 +24,7 @@ _space:insert{_space.id, ADMIN, 'test', 'world', 0, EMPTY_MAP, {}}
 -- There is already a tuple for the system space
 --
 _space:insert{_space.id, ADMIN, '_space', 'memtx', 0, EMPTY_MAP, {}}
-_space:replace{_space.id, ADMIN, '_space', 'memtx', 0, EMPTY_MAP, {}}
 _space:insert{_index.id, ADMIN, '_index', 'memtx', 0, EMPTY_MAP, {}}
-_space:replace{_index.id, ADMIN, '_index', 'memtx', 0, EMPTY_MAP, {}}
---
--- Can't change properties of a space
---
-_space:replace{_space.id, ADMIN, '_space', 'memtx', 0, EMPTY_MAP, {}}
 --
 -- Can't drop a system space
 --
-- 
2.11.0

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

* [PATCH 2/4] schema: fix error while altering index with sequence
  2019-05-15 10:33 [PATCH 0/4] A few fixes/improvements for autoincrement indexes Vladimir Davydov
  2019-05-15 10:33 ` [PATCH 1/4] schema: use tuple field names in Lua Vladimir Davydov
@ 2019-05-15 10:33 ` Vladimir Davydov
  2019-05-16  7:45   ` [tarantool-patches] " Konstantin Osipov
  2019-05-15 10:33 ` [PATCH 3/4] schema: allow to set sequence for any index part, not just the first Vladimir Davydov
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 17+ messages in thread
From: Vladimir Davydov @ 2019-05-15 10:33 UTC (permalink / raw)
  To: tarantool-patches

A check was missing in index.alter. This resulted in an attempt to drop
the sequence attached to the altered index even if the sequence was not
modified.

Closes #4214
---
 src/box/lua/schema.lua     |  3 ++-
 test/box/sequence.result   | 33 +++++++++++++++++++++++++++++++++
 test/box/sequence.test.lua | 13 +++++++++++++
 3 files changed, 48 insertions(+), 1 deletion(-)

diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index 036e5f2d..14ad4de1 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -1068,7 +1068,8 @@ box.schema.index.alter = function(space_id, index_id, options)
     if sequence then
         _space_sequence:replace{space_id, sequence, sequence_is_generated}
     end
-    if sequence_tuple ~= nil and sequence_tuple.is_generated == true and
+    if sequence ~= nil and sequence_tuple ~= nil and
+       sequence_tuple.is_generated == true and
        sequence_tuple.sequence_id ~= sequence then
         -- Delete automatically generated sequence.
         box.schema.sequence.drop(sequence_tuple.sequence_id)
diff --git a/test/box/sequence.result b/test/box/sequence.result
index b3907659..5eed0ef4 100644
--- a/test/box/sequence.result
+++ b/test/box/sequence.result
@@ -1871,3 +1871,36 @@ test_run:cmd("setopt delimiter ''");
 ---
 - true
 ...
+--
+-- gh-4214: error while altering an index with attached sequence.
+--
+s = box.schema.space.create('test')
+---
+...
+_ = s:create_index('pk', {sequence = true})
+---
+...
+sequence_id = s.index.pk.sequence_id
+---
+...
+sequence_id ~= nil
+---
+- true
+...
+s.index.pk:alter{parts = {1, 'integer'}}
+---
+...
+s.index.pk.parts[1].type
+---
+- integer
+...
+s.index.pk:alter{sequence = true}
+---
+...
+sequence_id == s.index.pk.sequence_id
+---
+- true
+...
+s:drop()
+---
+...
diff --git a/test/box/sequence.test.lua b/test/box/sequence.test.lua
index 96297d6f..6459419e 100644
--- a/test/box/sequence.test.lua
+++ b/test/box/sequence.test.lua
@@ -634,3 +634,16 @@ identifier.run_test(
 	function (identifier) box.schema.sequence.drop(identifier) end
 );
 test_run:cmd("setopt delimiter ''");
+
+--
+-- gh-4214: error while altering an index with attached sequence.
+--
+s = box.schema.space.create('test')
+_ = s:create_index('pk', {sequence = true})
+sequence_id = s.index.pk.sequence_id
+sequence_id ~= nil
+s.index.pk:alter{parts = {1, 'integer'}}
+s.index.pk.parts[1].type
+s.index.pk:alter{sequence = true}
+sequence_id == s.index.pk.sequence_id
+s:drop()
-- 
2.11.0

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

* [PATCH 3/4] schema: allow to set sequence for any index part, not just the first
  2019-05-15 10:33 [PATCH 0/4] A few fixes/improvements for autoincrement indexes Vladimir Davydov
  2019-05-15 10:33 ` [PATCH 1/4] schema: use tuple field names in Lua Vladimir Davydov
  2019-05-15 10:33 ` [PATCH 2/4] schema: fix error while altering index with sequence Vladimir Davydov
@ 2019-05-15 10:33 ` Vladimir Davydov
  2019-05-16  7:45   ` [tarantool-patches] " Konstantin Osipov
  2019-05-15 10:33 ` [PATCH 4/4] schema: explicitly forbid setting sequence for json path key part Vladimir Davydov
  2019-05-21 10:42 ` [tarantool-patches] Re: [PATCH 0/4] A few fixes/improvements for autoincrement indexes Kirill Yukhin
  4 siblings, 1 reply; 17+ messages in thread
From: Vladimir Davydov @ 2019-05-15 10:33 UTC (permalink / raw)
  To: tarantool-patches

Closes #4009

@TarantoolBot document
Title: Sequence can now be set for an index part other than the first

Initially one could attach a sequence (aka autoincrement) only to the
first index part. Now it's possible to attach a sequence to any primary
index part. The part still must be integer though.

Syntax:

```
box.schema.space.create('test')
box.space.test:create_index('primary', {
    parts = {{1, 'string'}, {2, 'unsigned'}, {3, 'unsigned'}},
    sequence = true, sequence_part = 2
})
box.space.test:insert{'a', box.null, 1} -- inserts {'a', 1, 1}
```

Note, `sequence_part` option is 1-base.

If `sequence_part` is omitted, 1 is used, which assures backward
compatibility with the original behavior.

One can also attach a sequence to another index part using
`index.alter` (the code below continues the example above):

```
box.space.test.index.primary:alter{sequence_part = 3}
box.space.test:insert{'a', 1, box.null, 'x'} -- inserts {'a', 1, 2, 'x'}
```
---
 src/box/alter.cc             |  27 +++++++--
 src/box/bootstrap.snap       | Bin 4374 -> 4379 bytes
 src/box/lua/schema.lua       |  68 +++++++++++++++------
 src/box/lua/space.cc         |   7 +++
 src/box/lua/upgrade.lua      |  35 ++++++++++-
 src/box/request.c            |   2 +-
 src/box/schema_def.h         |   1 +
 src/box/space.h              |   5 ++
 src/box/sql/build.c          |   7 ++-
 src/box/sql/insert.c         |   2 +-
 test/box-py/bootstrap.result |   5 +-
 test/box/access_misc.result  |   3 +-
 test/box/sequence.result     | 141 +++++++++++++++++++++++++++++++++++++++++--
 test/box/sequence.test.lua   |  50 +++++++++++++--
 14 files changed, 312 insertions(+), 41 deletions(-)

diff --git a/src/box/alter.cc b/src/box/alter.cc
index 9279426d..2d43a9d2 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -124,9 +124,14 @@ access_check_ddl(const char *name, uint32_t object_id, uint32_t owner_uid,
  * is incompatible with a sequence.
  */
 static void
-index_def_check_sequence(struct index_def *index_def, const char *space_name)
+index_def_check_sequence(struct index_def *index_def, uint32_t sequence_part,
+			 const char *space_name)
 {
-	enum field_type type = index_def->key_def->parts[0].type;
+	if (sequence_part >= index_def->key_def->part_count) {
+		tnt_raise(ClientError, ER_MODIFY_INDEX, index_def->name,
+			  space_name, "sequence part is out of bounds");
+	}
+	enum field_type type = index_def->key_def->parts[sequence_part].type;
 	if (type != FIELD_TYPE_UNSIGNED && type != FIELD_TYPE_INTEGER) {
 		tnt_raise(ClientError, ER_MODIFY_INDEX, index_def->name,
 			  space_name, "sequence cannot be used with "
@@ -279,7 +284,8 @@ index_def_new_from_tuple(struct tuple *tuple, struct space *space)
 	index_def_check_xc(index_def, space_name(space));
 	space_check_index_def_xc(space, index_def);
 	if (index_def->iid == 0 && space->sequence != NULL)
-		index_def_check_sequence(index_def, space_name(space));
+		index_def_check_sequence(index_def, space->sequence_part,
+					 space_name(space));
 	index_def_guard.is_active = false;
 	return index_def;
 }
@@ -855,6 +861,7 @@ alter_space_do(struct txn *txn, struct alter_space *alter)
 	space_prepare_alter_xc(alter->old_space, alter->new_space);
 
 	alter->new_space->sequence = alter->old_space->sequence;
+	alter->new_space->sequence_part = alter->old_space->sequence_part;
 	memcpy(alter->new_space->access, alter->old_space->access,
 	       sizeof(alter->old_space->access));
 
@@ -3333,6 +3340,12 @@ on_replace_dd_space_sequence(struct trigger * /* trigger */, void *event)
 				BOX_SPACE_SEQUENCE_FIELD_SEQUENCE_ID);
 	bool is_generated = tuple_field_bool_xc(tuple,
 				BOX_SPACE_SEQUENCE_FIELD_IS_GENERATED);
+	/* Sequence part was added in 2.2.1. */
+	uint32_t sequence_part = 0;
+	if (tuple_field_count(tuple) > BOX_SPACE_SEQUENCE_FIELD_PART) {
+		sequence_part = tuple_field_u32_xc(tuple,
+					BOX_SPACE_SEQUENCE_FIELD_PART);
+	}
 
 	struct space *space = space_cache_find_xc(space_id);
 	struct sequence *seq = sequence_cache_find(sequence_id);
@@ -3365,17 +3378,21 @@ on_replace_dd_space_sequence(struct trigger * /* trigger */, void *event)
 
 	if (stmt->new_tuple != NULL) {			/* INSERT, UPDATE */
 		struct index *pk = index_find_xc(space, 0);
-		index_def_check_sequence(pk->def, space_name(space));
-		if (seq->is_generated) {
+		index_def_check_sequence(pk->def, sequence_part,
+					 space_name(space));
+		if (seq->is_generated && seq != space->sequence) {
 			tnt_raise(ClientError, ER_ALTER_SPACE,
 				  space_name(space),
 				  "can not attach generated sequence");
 		}
 		seq->is_generated = is_generated;
 		space->sequence = seq;
+		space->sequence_part = sequence_part;
 	} else {					/* DELETE */
 		assert(space->sequence == seq);
+		assert(space->sequence_part == sequence_part);
 		space->sequence = NULL;
+		space->sequence_part = 0;
 	}
 }
 
diff --git a/src/box/bootstrap.snap b/src/box/bootstrap.snap
index 871a93f9856f97636ad55b7f5260e5c6957fef36..8c3b10e8417494edba07e6eba78377226ee5a31a 100644
GIT binary patch
delta 3671
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

delta 3647
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
Rfj#7n<+o@q*9X-Qt?gkf;xqsN

diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index 14ad4de1..91900395 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -748,6 +748,20 @@ local function simplify_index_parts(parts)
     return new_parts
 end
 
+local function check_sequence_part(parts, sequence_part,
+                                   index_name, space_name)
+    if sequence_part <= 0 or sequence_part > #parts then
+        box.error(box.error.MODIFY_INDEX, index_name, space_name,
+                  "sequence part is out of bounds")
+    end
+    sequence_part = parts[sequence_part]
+    local sequence_part_type = sequence_part.type or sequence_part[2]
+    if sequence_part_type ~= 'integer' and sequence_part_type ~= 'unsigned' then
+        box.error(box.error.MODIFY_INDEX, index_name, space_name,
+                  "sequence cannot be used with a non-integer key")
+    end
+end
+
 -- Historically, some properties of an index
 -- are stored as tuple fields, others in a
 -- single field containing msgpack map.
@@ -773,6 +787,7 @@ local alter_index_template = {
     type = 'string',
     parts = 'table',
     sequence = 'boolean, number, string',
+    sequence_part = 'number',
 }
 for k, v in pairs(index_options) do
     alter_index_template[k] = v
@@ -885,16 +900,18 @@ box.schema.index.create = function(space_id, name, options)
     local _space_sequence = box.space[box.schema.SPACE_SEQUENCE_ID]
     local sequence_is_generated = false
     local sequence = options.sequence or nil -- ignore sequence = false
+    local sequence_part = options.sequence_part
+    if sequence_part ~= nil and sequence == nil then
+        box.error(box.error.MODIFY_INDEX, options.name, space.name,
+                  "sequence part cannot be used without sequence")
+    end
     if sequence ~= nil then
         if iid ~= 0 then
             box.error(box.error.MODIFY_INDEX, name, space.name,
                       "sequence cannot be used with a secondary key")
         end
-        if #parts >= 1 and parts[1].type ~= 'integer' and
-                           parts[1].type ~= 'unsigned' then
-            box.error(box.error.MODIFY_INDEX, name, space.name,
-                      "sequence cannot be used with a non-integer key")
-        end
+        sequence_part = sequence_part or 1
+        check_sequence_part(parts, sequence_part, name, space.name)
         if sequence == true then
             sequence = box.schema.sequence.create(space.name .. '_seq')
             sequence = sequence.id
@@ -912,7 +929,8 @@ box.schema.index.create = function(space_id, name, options)
     end
     _index:insert{space_id, iid, name, options.type, index_opts, parts}
     if sequence ~= nil then
-        _space_sequence:insert{space_id, sequence, sequence_is_generated}
+        _space_sequence:insert{space_id, sequence, sequence_is_generated,
+                               sequence_part - 1}
     end
     return space.index[name]
 end
@@ -1028,32 +1046,45 @@ box.schema.index.alter = function(space_id, index_id, options)
     local _space_sequence = box.space[box.schema.SPACE_SEQUENCE_ID]
     local sequence_is_generated = false
     local sequence = options.sequence
+    local sequence_part = options.sequence_part
     local sequence_tuple
     if index_id ~= 0 then
-        if sequence then
+        if sequence or sequence_part ~= nil then
             box.error(box.error.MODIFY_INDEX, options.name, space.name,
                       "sequence cannot be used with a secondary key")
         end
         -- ignore 'sequence = false' for secondary indexes
         sequence = nil
-    else
+    end
+    if sequence ~= nil or sequence_part ~= nil then
         sequence_tuple = _space_sequence:get(space_id)
-        if (sequence or (sequence ~= false and sequence_tuple ~= nil)) and
-           #parts >= 1 and (parts[1].type or parts[1][2]) ~= 'integer' and
-                           (parts[1].type or parts[1][2]) ~= 'unsigned' then
-            box.error(box.error.MODIFY_INDEX, options.name, space.name,
-                      "sequence cannot be used with a non-integer key")
+        if sequence_tuple ~= nil then
+            -- Inherit omitted options from the attached sequence.
+            if sequence == nil then
+                sequence = sequence_tuple.sequence_id
+                sequence_is_generated = sequence_tuple.is_generated
+            end
+            if sequence and sequence_part == nil then
+                sequence_part = sequence_tuple.sequence_part
+            end
         end
     end
+    if sequence then
+        sequence_part = sequence_part or 1
+        check_sequence_part(parts, sequence_part, options.name, space.name)
+    elseif sequence_part ~= nil then
+        box.error(box.error.MODIFY_INDEX, options.name, space.name,
+                  "sequence part cannot be used without sequence")
+    end
     if sequence == true then
         if sequence_tuple == nil or sequence_tuple.is_generated == false then
             sequence = box.schema.sequence.create(space.name .. '_seq')
             sequence = sequence.id
-            sequence_is_generated = true
         else
             -- Space already has an automatically generated sequence.
-            sequence = nil
+            sequence = sequence_tuple.sequence_id
         end
+        sequence_is_generated = true
     elseif sequence then
         sequence = sequence_resolve(sequence)
         if sequence == nil then
@@ -1065,8 +1096,11 @@ box.schema.index.alter = function(space_id, index_id, options)
     end
     _index:replace{space_id, index_id, options.name, options.type,
                    index_opts, parts}
-    if sequence then
-        _space_sequence:replace{space_id, sequence, sequence_is_generated}
+    if sequence and (sequence_tuple == nil or
+                     sequence_tuple.sequence_id ~= sequence or
+                     sequence_tuple.sequence_part ~= sequence_part) then
+        _space_sequence:replace{space_id, sequence, sequence_is_generated,
+                                sequence_part - 1}
     end
     if sequence ~= nil and sequence_tuple ~= nil and
        sequence_tuple.is_generated == true and
diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc
index 100da0a7..e342bfcc 100644
--- a/src/box/lua/space.cc
+++ b/src/box/lua/space.cc
@@ -309,6 +309,13 @@ lbox_fillspace(struct lua_State *L, struct space *space, int i)
 		 */
 		lua_rawset(L, -3);
 
+		lua_pushstring(L, "sequence_part");
+		if (k == 0 && space->sequence != NULL)
+			lua_pushnumber(L, space->sequence_part + 1);
+		else
+			lua_pushnil(L);
+		lua_rawset(L, -3);
+
 		if (space_is_vinyl(space)) {
 			lua_pushstring(L, "options");
 			lua_newtable(L);
diff --git a/src/box/lua/upgrade.lua b/src/box/lua/upgrade.lua
index 89d6e3d5..23f4df01 100644
--- a/src/box/lua/upgrade.lua
+++ b/src/box/lua/upgrade.lua
@@ -625,8 +625,12 @@ local function upgrade_to_2_1_2()
     update_collation_strength_field()
 end
 
+--------------------------------------------------------------------------------
+-- Tarantool 2.1.3
+--------------------------------------------------------------------------------
+
 -- Add new collations
-local function upgrade_to_2_1_3()
+local function upgrade_collation_to_2_1_3()
     local coll_lst = {
         {name="af", loc_str="af"},  -- Afrikaans
         {name="am", loc_str="am"},  -- Amharic (no character changes, just re-ordering)
@@ -737,6 +741,34 @@ local function upgrade_to_2_1_3()
     end
 end
 
+local function upgrade_to_2_1_3()
+    upgrade_collation_to_2_1_3()
+end
+
+--------------------------------------------------------------------------------
+-- Tarantool 2.2.1
+--------------------------------------------------------------------------------
+
+-- Add sequence part field to _space_sequence table
+local function upgrade_sequence_to_2_2_1()
+    log.info("add key part field to space _space_sequence")
+    local _space_sequence = box.space[box.schema.SPACE_SEQUENCE_ID]
+    for _, v in _space_sequence:pairs() do
+        if #v == 3 then
+            _space_sequence:update(v[1], {{'!', 4, 0}})
+        end
+    end
+    local format = _space_sequence:format()
+    format[4] = {name = 'part', type = 'unsigned'}
+    _space_sequence:format(format)
+end
+
+local function upgrade_to_2_2_1()
+    upgrade_sequence_to_2_2_1()
+end
+
+--------------------------------------------------------------------------------
+
 local function get_version()
     local version = box.space._schema:get{'version'}
     if version == nil then
@@ -768,6 +800,7 @@ local function upgrade(options)
         {version = mkversion(2, 1, 1), func = upgrade_to_2_1_1, auto = true},
         {version = mkversion(2, 1, 2), func = upgrade_to_2_1_2, auto = true},
         {version = mkversion(2, 1, 3), func = upgrade_to_2_1_3, auto = true},
+        {version = mkversion(2, 2, 1), func = upgrade_to_2_2_1, auto = true},
     }
 
     for _, handler in ipairs(handlers) do
diff --git a/src/box/request.c b/src/box/request.c
index 44a43ee1..9d3287f9 100644
--- a/src/box/request.c
+++ b/src/box/request.c
@@ -163,7 +163,7 @@ request_handle_sequence(struct request *request, struct space *space)
 	const char *data = request->tuple;
 	const char *data_end = request->tuple_end;
 	int len = mp_decode_array(&data);
-	int fieldno = pk->def->key_def->parts[0].fieldno;
+	int fieldno = pk->def->key_def->parts[space->sequence_part].fieldno;
 	if (unlikely(len < fieldno + 1))
 		return 0;
 
diff --git a/src/box/schema_def.h b/src/box/schema_def.h
index eeeeb950..dea3fad1 100644
--- a/src/box/schema_def.h
+++ b/src/box/schema_def.h
@@ -216,6 +216,7 @@ enum {
 	BOX_SPACE_SEQUENCE_FIELD_ID = 0,
 	BOX_SPACE_SEQUENCE_FIELD_SEQUENCE_ID = 1,
 	BOX_SPACE_SEQUENCE_FIELD_IS_GENERATED = 2,
+	BOX_SPACE_SEQUENCE_FIELD_PART = 3,
 };
 
 /** _trigger fields. */
diff --git a/src/box/space.h b/src/box/space.h
index 13a220d1..c3eef71c 100644
--- a/src/box/space.h
+++ b/src/box/space.h
@@ -176,6 +176,11 @@ struct space {
 	struct space_def *def;
 	/** Sequence attached to this space or NULL. */
 	struct sequence *sequence;
+	/**
+	 * Auto increment part of the primary index.
+	 * Makes sense only if sequence is set.
+	 */
+	uint32_t sequence_part;
 	/** Enable/disable triggers. */
 	bool run_triggers;
 	/**
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index 6051a252..91b977de 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -951,9 +951,14 @@ emitNewSysSpaceSequenceRecord(Parse *pParse, int space_id, const char reg_seq_id
 	
 	/* 2. Sequence id  */
 	sqlVdbeAddOp2(v, OP_IntCopy, reg_seq_id, first_col + 2);
+
+	/* 3. Autogenerated. */
 	sqlVdbeAddOp2(v, OP_Bool, true, first_col + 3);
-	sqlVdbeAddOp3(v, OP_MakeRecord, first_col + 1, 3, first_col);
 
+	/* 4. Part id. */
+	sqlVdbeAddOp2(v, OP_Integer, 0, first_col + 4);
+
+	sqlVdbeAddOp3(v, OP_MakeRecord, first_col + 1, 4, first_col);
 	return first_col;
 }
 
diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index c2aac553..1261ab9c 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -98,7 +98,7 @@ sql_space_autoinc_fieldno(struct space *space)
 	if (pk == NULL || pk->def->key_def->part_count != 1 ||
 	    space->sequence == NULL)
 		return UINT32_MAX;
-	return pk->def->key_def->parts[0].fieldno;
+	return pk->def->key_def->parts[space->sequence_part].fieldno;
 }
 
 /**
diff --git a/test/box-py/bootstrap.result b/test/box-py/bootstrap.result
index 379f6c51..de90beee 100644
--- a/test/box-py/bootstrap.result
+++ b/test/box-py/bootstrap.result
@@ -4,7 +4,7 @@ box.internal.bootstrap()
 box.space._schema:select{}
 ---
 - - ['max_id', 511]
-  - ['version', 2, 1, 3]
+  - ['version', 2, 2, 1]
 ...
 box.space._cluster:select{}
 ---
@@ -72,7 +72,8 @@ box.space._space:select{}
   - [330, 1, '_truncate', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'count',
         'type': 'unsigned'}]]
   - [340, 1, '_space_sequence', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'},
-      {'name': 'sequence_id', 'type': 'unsigned'}, {'name': 'is_generated', 'type': 'boolean'}]]
+      {'name': 'sequence_id', 'type': 'unsigned'}, {'name': 'is_generated', 'type': 'boolean'},
+      {'name': 'part', 'type': 'unsigned'}]]
   - [356, 1, '_fk_constraint', 'memtx', 0, {}, [{'name': 'name', 'type': 'string'},
       {'name': 'child_id', 'type': 'unsigned'}, {'name': 'parent_id', 'type': 'unsigned'},
       {'name': 'is_deferred', 'type': 'boolean'}, {'name': 'match', 'type': 'string'},
diff --git a/test/box/access_misc.result b/test/box/access_misc.result
index 36ebfae0..877a9b53 100644
--- a/test/box/access_misc.result
+++ b/test/box/access_misc.result
@@ -812,7 +812,8 @@ box.space._space:select()
   - [330, 1, '_truncate', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'count',
         'type': 'unsigned'}]]
   - [340, 1, '_space_sequence', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'},
-      {'name': 'sequence_id', 'type': 'unsigned'}, {'name': 'is_generated', 'type': 'boolean'}]]
+      {'name': 'sequence_id', 'type': 'unsigned'}, {'name': 'is_generated', 'type': 'boolean'},
+      {'name': 'part', 'type': 'unsigned'}]]
   - [356, 1, '_fk_constraint', 'memtx', 0, {}, [{'name': 'name', 'type': 'string'},
       {'name': 'child_id', 'type': 'unsigned'}, {'name': 'parent_id', 'type': 'unsigned'},
       {'name': 'is_deferred', 'type': 'boolean'}, {'name': 'match', 'type': 'string'},
diff --git a/test/box/sequence.result b/test/box/sequence.result
index 5eed0ef4..4f962347 100644
--- a/test/box/sequence.result
+++ b/test/box/sequence.result
@@ -590,6 +590,48 @@ s:create_index('pk', {parts = {1, 'number'}, sequence = 'test'}) -- error
 - error: 'Can''t create or modify index ''pk'' in space ''test'': sequence cannot
     be used with a non-integer key'
 ...
+s:create_index('pk', {sequence_part = 1}) -- error
+---
+- error: 'Can''t create or modify index ''nil'' in space ''test'': sequence part cannot
+    be used without sequence'
+...
+s:create_index('pk', {sequence = true, sequence_part = 2}) -- error
+---
+- error: 'Can''t create or modify index ''pk'' in space ''test'': sequence part is
+    out of bounds'
+...
+s:create_index('pk', {parts = {1, 'unsigned', 2, 'string'}, sequence = true, sequence_part = 2}) -- error
+---
+- error: 'Can''t create or modify index ''pk'' in space ''test'': sequence cannot
+    be used with a non-integer key'
+...
+pk = s:create_index('pk', {parts = {1, 'string', 2, 'unsigned'}}) -- ok
+---
+...
+pk:alter{sequence_part = 1} -- error
+---
+- error: 'Can''t create or modify index ''pk'' in space ''test'': sequence part cannot
+    be used without sequence'
+...
+pk:alter{sequence = true, sequence_part = 1} -- error
+---
+- error: 'Can''t create or modify index ''pk'' in space ''test'': sequence cannot
+    be used with a non-integer key'
+...
+pk:alter{sequence = true, sequence_part = 2} -- ok
+---
+...
+pk:alter{sequence = false, sequence_part = 2} -- error
+---
+- error: 'Can''t create or modify index ''pk'' in space ''test'': sequence part cannot
+    be used without sequence'
+...
+pk:alter{sequence = false} -- ok
+---
+...
+pk:drop()
+---
+...
 pk = s:create_index('pk', {parts = {1, 'integer'}, sequence = 'test'}) -- ok
 ---
 ...
@@ -615,6 +657,11 @@ s:create_index('secondary', {parts = {2, 'unsigned'}, sequence = true}) -- error
 - error: 'Can''t create or modify index ''secondary'' in space ''test'': sequence
     cannot be used with a secondary key'
 ...
+s:create_index('secondary', {parts = {2, 'unsigned'}, sequence_part = 1}) -- error
+---
+- error: 'Can''t create or modify index ''nil'' in space ''test'': sequence part cannot
+    be used without sequence'
+...
 sk = s:create_index('secondary', {parts = {2, 'unsigned'}}) -- ok
 ---
 ...
@@ -700,18 +747,23 @@ sk:alter{sequence = 'test'} -- error
 - error: 'Can''t create or modify index ''sk'' in space ''test'': sequence cannot
     be used with a secondary key'
 ...
-box.space._space_sequence:insert{s.id, sq.id, false} -- error
+box.space._space_sequence:insert{s.id, sq.id, false, 0} -- error
 ---
 - error: 'Can''t create or modify index ''pk'' in space ''test'': sequence cannot
     be used with a non-integer key'
 ...
+box.space._space_sequence:insert{s.id, sq.id, false, 2} -- error
+---
+- error: 'Can''t create or modify index ''pk'' in space ''test'': sequence part is
+    out of bounds'
+...
 sk:drop()
 ---
 ...
 pk:drop()
 ---
 ...
-box.space._space_sequence:insert{s.id, sq.id, false} -- error
+box.space._space_sequence:insert{s.id, sq.id, false, 0} -- error
 ---
 - error: 'No index #0 is defined in space ''test'''
 ...
@@ -1121,7 +1173,7 @@ _ = s2:create_index('pk', {sequence = 'test1_seq'}) -- error
 ---
 - error: 'Can''t modify space ''test2'': can not attach generated sequence'
 ...
-box.space._space_sequence:insert{s2.id, box.sequence.test1_seq.id, false} -- error
+box.space._space_sequence:insert{s2.id, box.sequence.test1_seq.id, false, 0} -- error
 ---
 - error: 'Can''t modify space ''test2'': can not attach generated sequence'
 ...
@@ -1612,15 +1664,15 @@ s1.index.pk:alter({sequence = 'seq1'}) -- error
 ---
 - error: Alter access to space 'space1' is denied for user 'user'
 ...
-box.space._space_sequence:replace{s1.id, sq1.id, false} -- error
+box.space._space_sequence:replace{s1.id, sq1.id, false, 0} -- error
 ---
 - error: Read access to sequence 'seq1' is denied for user 'user'
 ...
-box.space._space_sequence:replace{s1.id, sq2.id, false} -- error
+box.space._space_sequence:replace{s1.id, sq2.id, false, 0} -- error
 ---
 - error: Alter access to space 'space1' is denied for user 'user'
 ...
-box.space._space_sequence:replace{s2.id, sq1.id, false} -- error
+box.space._space_sequence:replace{s2.id, sq1.id, false, 0} -- error
 ---
 - error: Read access to sequence 'seq1' is denied for user 'user'
 ...
@@ -1904,3 +1956,80 @@ sequence_id == s.index.pk.sequence_id
 s:drop()
 ---
 ...
+--
+-- gh-4009: setting sequence for an index part other than the first.
+--
+s = box.schema.space.create('test')
+---
+...
+_ = s:create_index('pk', {parts = {1, 'string', 2, 'unsigned', 3, 'unsigned'}, sequence = true, sequence_part = 2})
+---
+...
+sequence_id = s.index.pk.sequence_id
+---
+...
+sequence_id ~= nil
+---
+- true
+...
+s.index.pk.sequence_part == 2
+---
+- true
+...
+s:insert{'a', box.null, 1}
+---
+- ['a', 1, 1]
+...
+s:insert{'a', box.null, 2}
+---
+- ['a', 2, 2]
+...
+s:insert{'b', 10, 10}
+---
+- ['b', 10, 10]
+...
+s:insert{'b', box.null, 11}
+---
+- ['b', 11, 11]
+...
+s.index.pk:alter{sequence_part = 3}
+---
+...
+s.index.pk.sequence_part == 3
+---
+- true
+...
+s.index.pk.sequence_id == sequence_id
+---
+- true
+...
+s:insert{'c', 100, 100, 'x'}
+---
+- ['c', 100, 100, 'x']
+...
+s:insert{'c', 101, box.null, 'y'}
+---
+- ['c', 101, 101, 'y']
+...
+s.index.pk:alter{sequence = true, sequence_part = 2}
+---
+...
+s.index.pk.sequence_part == 2
+---
+- true
+...
+s.index.pk.sequence_id == sequence_id
+---
+- true
+...
+s:insert{'d', 1000, 1000}
+---
+- ['d', 1000, 1000]
+...
+s:insert{'d', box.null, 1001}
+---
+- ['d', 1001, 1001]
+...
+s:drop()
+---
+...
diff --git a/test/box/sequence.test.lua b/test/box/sequence.test.lua
index 6459419e..d419e369 100644
--- a/test/box/sequence.test.lua
+++ b/test/box/sequence.test.lua
@@ -196,6 +196,18 @@ s:create_index('pk', {parts = {1, 'string'}, sequence = 'test'}) -- error
 s:create_index('pk', {parts = {1, 'scalar'}, sequence = 'test'}) -- error
 s:create_index('pk', {parts = {1, 'number'}, sequence = 'test'}) -- error
 
+s:create_index('pk', {sequence_part = 1}) -- error
+s:create_index('pk', {sequence = true, sequence_part = 2}) -- error
+s:create_index('pk', {parts = {1, 'unsigned', 2, 'string'}, sequence = true, sequence_part = 2}) -- error
+
+pk = s:create_index('pk', {parts = {1, 'string', 2, 'unsigned'}}) -- ok
+pk:alter{sequence_part = 1} -- error
+pk:alter{sequence = true, sequence_part = 1} -- error
+pk:alter{sequence = true, sequence_part = 2} -- ok
+pk:alter{sequence = false, sequence_part = 2} -- error
+pk:alter{sequence = false} -- ok
+pk:drop()
+
 pk = s:create_index('pk', {parts = {1, 'integer'}, sequence = 'test'}) -- ok
 pk:drop()
 pk = s:create_index('pk', {parts = {1, 'unsigned'}, sequence = 'test'}) -- ok
@@ -204,6 +216,7 @@ pk:drop()
 pk = s:create_index('pk') -- ok
 s:create_index('secondary', {parts = {2, 'unsigned'}, sequence = 'test'}) -- error
 s:create_index('secondary', {parts = {2, 'unsigned'}, sequence = true}) -- error
+s:create_index('secondary', {parts = {2, 'unsigned'}, sequence_part = 1}) -- error
 sk = s:create_index('secondary', {parts = {2, 'unsigned'}}) -- ok
 sk:alter{sequence = 'test'} -- error
 sk:alter{sequence = true} -- error
@@ -227,10 +240,11 @@ box.space._index:delete{s.id, pk.id} -- error
 pk:alter{parts = {1, 'string'}, sequence = false} -- ok
 sk = s:create_index('sk', {parts = {2, 'unsigned'}})
 sk:alter{sequence = 'test'} -- error
-box.space._space_sequence:insert{s.id, sq.id, false} -- error
+box.space._space_sequence:insert{s.id, sq.id, false, 0} -- error
+box.space._space_sequence:insert{s.id, sq.id, false, 2} -- error
 sk:drop()
 pk:drop()
-box.space._space_sequence:insert{s.id, sq.id, false} -- error
+box.space._space_sequence:insert{s.id, sq.id, false, 0} -- error
 
 s:create_index('pk', {sequence = {}}) -- error
 s:create_index('pk', {sequence = 'abc'}) -- error
@@ -358,7 +372,7 @@ s1 = box.schema.space.create('test1')
 _ = s1:create_index('pk', {sequence = true})
 s2 = box.schema.space.create('test2')
 _ = s2:create_index('pk', {sequence = 'test1_seq'}) -- error
-box.space._space_sequence:insert{s2.id, box.sequence.test1_seq.id, false} -- error
+box.space._space_sequence:insert{s2.id, box.sequence.test1_seq.id, false, 0} -- error
 
 s1:drop()
 s2:drop()
@@ -538,9 +552,9 @@ box.schema.user.grant('user', 'read', 'space', '_space_sequence')
 box.session.su('user')
 _ = s2:create_index('pk', {sequence = 'seq1'}) -- error
 s1.index.pk:alter({sequence = 'seq1'}) -- error
-box.space._space_sequence:replace{s1.id, sq1.id, false} -- error
-box.space._space_sequence:replace{s1.id, sq2.id, false} -- error
-box.space._space_sequence:replace{s2.id, sq1.id, false} -- error
+box.space._space_sequence:replace{s1.id, sq1.id, false, 0} -- error
+box.space._space_sequence:replace{s1.id, sq2.id, false, 0} -- error
+box.space._space_sequence:replace{s2.id, sq1.id, false, 0} -- error
 s2.index.pk:alter({sequence = 'seq2'}) -- ok
 box.session.su('admin')
 
@@ -647,3 +661,27 @@ s.index.pk.parts[1].type
 s.index.pk:alter{sequence = true}
 sequence_id == s.index.pk.sequence_id
 s:drop()
+
+--
+-- gh-4009: setting sequence for an index part other than the first.
+--
+s = box.schema.space.create('test')
+_ = s:create_index('pk', {parts = {1, 'string', 2, 'unsigned', 3, 'unsigned'}, sequence = true, sequence_part = 2})
+sequence_id = s.index.pk.sequence_id
+sequence_id ~= nil
+s.index.pk.sequence_part == 2
+s:insert{'a', box.null, 1}
+s:insert{'a', box.null, 2}
+s:insert{'b', 10, 10}
+s:insert{'b', box.null, 11}
+s.index.pk:alter{sequence_part = 3}
+s.index.pk.sequence_part == 3
+s.index.pk.sequence_id == sequence_id
+s:insert{'c', 100, 100, 'x'}
+s:insert{'c', 101, box.null, 'y'}
+s.index.pk:alter{sequence = true, sequence_part = 2}
+s.index.pk.sequence_part == 2
+s.index.pk.sequence_id == sequence_id
+s:insert{'d', 1000, 1000}
+s:insert{'d', box.null, 1001}
+s:drop()
-- 
2.11.0

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

* [PATCH 4/4] schema: explicitly forbid setting sequence for json path key part
  2019-05-15 10:33 [PATCH 0/4] A few fixes/improvements for autoincrement indexes Vladimir Davydov
                   ` (2 preceding siblings ...)
  2019-05-15 10:33 ` [PATCH 3/4] schema: allow to set sequence for any index part, not just the first Vladimir Davydov
@ 2019-05-15 10:33 ` Vladimir Davydov
  2019-05-15 13:00   ` [tarantool-patches] " Konstantin Osipov
  2019-05-21 10:42 ` [tarantool-patches] Re: [PATCH 0/4] A few fixes/improvements for autoincrement indexes Kirill Yukhin
  4 siblings, 1 reply; 17+ messages in thread
From: Vladimir Davydov @ 2019-05-15 10:33 UTC (permalink / raw)
  To: tarantool-patches

When a space has a sequence, we substitute NULL in the corresponding
primary index part with the next value generated by the sequence. We do
this by patching raw msgpack, see request_handle_sequence. The problem
is we can't do it easily if the field is nested. For example, consider
field [1].a.b. In Lua it is impossible to create tuple {{a = {b = nil}}}
so we would need to restore the whole path from an empty tuple. This
isn't trivial to do. Let's forbid it for now as nobody has requested
this feature yet.

See #4210

@TarantoolBot document
Title: Document that a sequence can't be used with json path key part

This will result in index create/alter error:

```
box.schema.space.create('test')
box.space.test:create_index('primary', {
    parts = {{'[1][1]', 'unsigned'}},
    sequence = true
})
```

One can set a sequence for a plain part of a json path index though,
i.e. this is okay:

```
box.schema.space.create('test')
box.space.test:create_index('primary', {
    parts = {{'[1][1]', 'unsigned'}, {2, 'unsigned}},
    sequence = true, sequence_part = 2
})
```
---
 src/box/alter.cc           |  8 +++++-
 src/box/lua/schema.lua     |  4 +++
 test/box/sequence.result   | 69 ++++++++++++++++++++++++++++++++++++++++++++++
 test/box/sequence.test.lua | 20 ++++++++++++++
 4 files changed, 100 insertions(+), 1 deletion(-)

diff --git a/src/box/alter.cc b/src/box/alter.cc
index 2d43a9d2..a07a0845 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -131,7 +131,13 @@ index_def_check_sequence(struct index_def *index_def, uint32_t sequence_part,
 		tnt_raise(ClientError, ER_MODIFY_INDEX, index_def->name,
 			  space_name, "sequence part is out of bounds");
 	}
-	enum field_type type = index_def->key_def->parts[sequence_part].type;
+	struct key_part *part = &index_def->key_def->parts[sequence_part];
+	if (part->path != NULL) {
+		tnt_raise(ClientError, ER_MODIFY_INDEX, index_def->name,
+			  space_name, "sequence cannot be used with "
+			  "a json path key");
+	}
+	enum field_type type = part->type;
 	if (type != FIELD_TYPE_UNSIGNED && type != FIELD_TYPE_INTEGER) {
 		tnt_raise(ClientError, ER_MODIFY_INDEX, index_def->name,
 			  space_name, "sequence cannot be used with "
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index 91900395..8f54f444 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -755,6 +755,10 @@ local function check_sequence_part(parts, sequence_part,
                   "sequence part is out of bounds")
     end
     sequence_part = parts[sequence_part]
+    if sequence_part.path ~= nil then
+        box.error(box.error.MODIFY_INDEX, index_name, space_name,
+                  "sequence cannot be used with a json path key")
+    end
     local sequence_part_type = sequence_part.type or sequence_part[2]
     if sequence_part_type ~= 'integer' and sequence_part_type ~= 'unsigned' then
         box.error(box.error.MODIFY_INDEX, index_name, space_name,
diff --git a/test/box/sequence.result b/test/box/sequence.result
index 4f962347..5d2d8a4f 100644
--- a/test/box/sequence.result
+++ b/test/box/sequence.result
@@ -2033,3 +2033,72 @@ s:insert{'d', box.null, 1001}
 s:drop()
 ---
 ...
+--
+-- gh-4210: sequence cannot be used with json path indexes.
+--
+sq = box.schema.sequence.create('test')
+---
+...
+s = box.schema.space.create('test')
+---
+...
+s:create_index('pk', {parts = {{'[1][1]', 'unsigned'}}, sequence = true}) -- error
+---
+- error: 'Can''t create or modify index ''pk'' in space ''test'': sequence cannot
+    be used with a json path key'
+...
+s:create_index('pk', {parts = {{1, 'unsigned', path = '[1]'}}, sequence = true}) -- error
+---
+- error: 'Can''t create or modify index ''pk'' in space ''test'': sequence cannot
+    be used with a json path key'
+...
+_ = s:create_index('pk', {parts = {{1, 'unsigned'}, {'[2][1]', 'unsigned'}}, sequence = true}) -- ok
+---
+...
+s.index.pk:alter{sequence_part = 2} -- error
+---
+- error: 'Can''t create or modify index ''pk'' in space ''test'': sequence cannot
+    be used with a json path key'
+...
+box.space._space_sequence:update(s.id, {{'=', 4, 1}}) -- error
+---
+- error: 'Can''t create or modify index ''pk'' in space ''test'': sequence cannot
+    be used with a json path key'
+...
+s.index.pk:alter{parts = {{'[1][1]', 'unsigned'}}} -- error
+---
+- error: 'Can''t create or modify index ''pk'' in space ''test'': sequence cannot
+    be used with a json path key'
+...
+s.index.pk:alter{parts = {{1, 'unsigned', path = '[1]'}}} -- error
+---
+- error: 'Can''t create or modify index ''pk'' in space ''test'': sequence cannot
+    be used with a json path key'
+...
+box.space._index:update({s.id, 0}, {{'=', 6, {{field = 1, type = 'unsigned', path = '[1]'}}}}) -- error
+---
+- error: 'Can''t create or modify index ''pk'' in space ''test'': sequence cannot
+    be used with a json path key'
+...
+s.index.pk:alter{sequence = false} -- ok
+---
+...
+s.index.pk:alter{sequence = true, sequence_part = 2} -- error
+---
+- error: 'Can''t create or modify index ''pk'' in space ''test'': sequence cannot
+    be used with a json path key'
+...
+box.space._space_sequence:insert{s.id, sq.id, false, 1} -- error
+---
+- error: 'Can''t create or modify index ''pk'' in space ''test'': sequence cannot
+    be used with a json path key'
+...
+s.index.pk:alter{sequence = true} -- ok
+---
+...
+s:drop()
+---
+...
+sq:drop()
+---
+...
diff --git a/test/box/sequence.test.lua b/test/box/sequence.test.lua
index d419e369..3fa0bef7 100644
--- a/test/box/sequence.test.lua
+++ b/test/box/sequence.test.lua
@@ -685,3 +685,23 @@ s.index.pk.sequence_id == sequence_id
 s:insert{'d', 1000, 1000}
 s:insert{'d', box.null, 1001}
 s:drop()
+
+--
+-- gh-4210: sequence cannot be used with json path indexes.
+--
+sq = box.schema.sequence.create('test')
+s = box.schema.space.create('test')
+s:create_index('pk', {parts = {{'[1][1]', 'unsigned'}}, sequence = true}) -- error
+s:create_index('pk', {parts = {{1, 'unsigned', path = '[1]'}}, sequence = true}) -- error
+_ = s:create_index('pk', {parts = {{1, 'unsigned'}, {'[2][1]', 'unsigned'}}, sequence = true}) -- ok
+s.index.pk:alter{sequence_part = 2} -- error
+box.space._space_sequence:update(s.id, {{'=', 4, 1}}) -- error
+s.index.pk:alter{parts = {{'[1][1]', 'unsigned'}}} -- error
+s.index.pk:alter{parts = {{1, 'unsigned', path = '[1]'}}} -- error
+box.space._index:update({s.id, 0}, {{'=', 6, {{field = 1, type = 'unsigned', path = '[1]'}}}}) -- error
+s.index.pk:alter{sequence = false} -- ok
+s.index.pk:alter{sequence = true, sequence_part = 2} -- error
+box.space._space_sequence:insert{s.id, sq.id, false, 1} -- error
+s.index.pk:alter{sequence = true} -- ok
+s:drop()
+sq:drop()
-- 
2.11.0

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

* [tarantool-patches] Re: [PATCH 4/4] schema: explicitly forbid setting sequence for json path key part
  2019-05-15 10:33 ` [PATCH 4/4] schema: explicitly forbid setting sequence for json path key part Vladimir Davydov
@ 2019-05-15 13:00   ` Konstantin Osipov
  2019-05-15 13:11     ` Vladimir Davydov
  0 siblings, 1 reply; 17+ messages in thread
From: Konstantin Osipov @ 2019-05-15 13:00 UTC (permalink / raw)
  To: tarantool-patches

* Vladimir Davydov <vdavydov.dev@gmail.com> [19/05/15 14:16]:
> When a space has a sequence, we substitute NULL in the corresponding
> primary index part with the next value generated by the sequence. We do
> this by patching raw msgpack, see request_handle_sequence. The problem
> is we can't do it easily if the field is nested. For example, consider
> field [1].a.b. In Lua it is impossible to create tuple {{a = {b = nil}}}

It is trivial to do, use msgpack.NULL

> so we would need to restore the whole path from an empty tuple.

No, it's not the job of the sequence to fill in the entire path.
You can expect the right prefix to be there.

-- 
Konstantin Osipov, Moscow, Russia, +7 903 626 22 32

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

* Re: [tarantool-patches] Re: [PATCH 4/4] schema: explicitly forbid setting sequence for json path key part
  2019-05-15 13:00   ` [tarantool-patches] " Konstantin Osipov
@ 2019-05-15 13:11     ` Vladimir Davydov
  2019-05-15 13:16       ` Vladimir Davydov
  0 siblings, 1 reply; 17+ messages in thread
From: Vladimir Davydov @ 2019-05-15 13:11 UTC (permalink / raw)
  To: Konstantin Osipov; +Cc: tarantool-patches

On Wed, May 15, 2019 at 04:00:26PM +0300, Konstantin Osipov wrote:
> * Vladimir Davydov <vdavydov.dev@gmail.com> [19/05/15 14:16]:
> > When a space has a sequence, we substitute NULL in the corresponding
> > primary index part with the next value generated by the sequence. We do
> > this by patching raw msgpack, see request_handle_sequence. The problem
> > is we can't do it easily if the field is nested. For example, consider
> > field [1].a.b. In Lua it is impossible to create tuple {{a = {b = nil}}}
> 
> It is trivial to do, use msgpack.NULL

Hmm, I tried box.NULL and it didn't work out.

msgpack.NULL seems to be different.

> 
> > so we would need to restore the whole path from an empty tuple.
> 
> No, it's not the job of the sequence to fill in the entire path.
> You can expect the right prefix to be there.

Okay, will try to address.

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

* Re: [tarantool-patches] Re: [PATCH 4/4] schema: explicitly forbid setting sequence for json path key part
  2019-05-15 13:11     ` Vladimir Davydov
@ 2019-05-15 13:16       ` Vladimir Davydov
  2019-05-15 13:44         ` [PATCH] box: fix autoincrement for json path indexes Vladimir Davydov
  0 siblings, 1 reply; 17+ messages in thread
From: Vladimir Davydov @ 2019-05-15 13:16 UTC (permalink / raw)
  To: Konstantin Osipov; +Cc: tarantool-patches

On Wed, May 15, 2019 at 04:11:21PM +0300, Vladimir Davydov wrote:
> On Wed, May 15, 2019 at 04:00:26PM +0300, Konstantin Osipov wrote:
> > * Vladimir Davydov <vdavydov.dev@gmail.com> [19/05/15 14:16]:
> > > When a space has a sequence, we substitute NULL in the corresponding
> > > primary index part with the next value generated by the sequence. We do
> > > this by patching raw msgpack, see request_handle_sequence. The problem
> > > is we can't do it easily if the field is nested. For example, consider
> > > field [1].a.b. In Lua it is impossible to create tuple {{a = {b = nil}}}
> > 
> > It is trivial to do, use msgpack.NULL
> 
> Hmm, I tried box.NULL and it didn't work out.

Oops, box.NULL is fine, too. It's just I tried box.null :)

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

* [PATCH] box: fix autoincrement for json path indexes
  2019-05-15 13:16       ` Vladimir Davydov
@ 2019-05-15 13:44         ` Vladimir Davydov
  2019-05-16  7:42           ` [tarantool-patches] " Konstantin Osipov
  0 siblings, 1 reply; 17+ messages in thread
From: Vladimir Davydov @ 2019-05-15 13:44 UTC (permalink / raw)
  To: tarantool-patches

The autoincrement code was written when there were no nested field.
Now, it isn't enough to just skip to the autoincrement field - we also
need to descend deeper if key_part->path is set.

Note, the code expects the nested field to be present and set to NULL.
That is, if field path is [1].a.b, the tuple must have all intermediate
fields set: {{a = {b = box.NULL}}} (usage of box.NULL is mandatory to
create a tuple like that in Lua).

Closes #4210
---
 src/box/request.c          | 10 +++++++++-
 test/box/sequence.result   | 32 ++++++++++++++++++++++++++++++++
 test/box/sequence.test.lua | 12 ++++++++++++
 3 files changed, 53 insertions(+), 1 deletion(-)

diff --git a/src/box/request.c b/src/box/request.c
index 9d3287f9..041a8d54 100644
--- a/src/box/request.c
+++ b/src/box/request.c
@@ -163,7 +163,8 @@ request_handle_sequence(struct request *request, struct space *space)
 	const char *data = request->tuple;
 	const char *data_end = request->tuple_end;
 	int len = mp_decode_array(&data);
-	int fieldno = pk->def->key_def->parts[space->sequence_part].fieldno;
+	struct key_part *part = &pk->def->key_def->parts[space->sequence_part];
+	int fieldno = part->fieldno;
 	if (unlikely(len < fieldno + 1))
 		return 0;
 
@@ -174,6 +175,13 @@ request_handle_sequence(struct request *request, struct space *space)
 		} while (--fieldno > 0);
 	}
 
+	if (part->path != NULL) {
+		tuple_go_to_path(&key, part->path, part->path_len,
+				 MULTIKEY_NONE);
+		if (key == NULL)
+			return 0; /* field not found */
+	}
+
 	int64_t value;
 	if (mp_typeof(*key) == MP_NIL) {
 		/*
diff --git a/test/box/sequence.result b/test/box/sequence.result
index 8a7c349c..3cac4dc9 100644
--- a/test/box/sequence.result
+++ b/test/box/sequence.result
@@ -2033,3 +2033,35 @@ s:insert{'d', box.NULL, 1001}
 s:drop()
 ---
 ...
+--
+-- gh-4210: using sequence with a json path key part.
+--
+s = box.schema.space.create('test')
+---
+...
+_ = s:create_index('pk', {parts = {{'[1].a.b[1]', 'unsigned'}}, sequence = true})
+---
+...
+s:replace{} -- error
+---
+- error: Tuple field [1]["a"]["b"][1] required by space format is missing
+...
+s:replace{{c = {}}} -- error
+---
+- error: Tuple field [1]["a"]["b"][1] required by space format is missing
+...
+s:replace{{a = {c = {}}}} -- error
+---
+- error: Tuple field [1]["a"]["b"][1] required by space format is missing
+...
+s:replace{{a = {b = {}}}} -- error
+---
+- error: Tuple field [1]["a"]["b"][1] required by space format is missing
+...
+s:replace{{a = {b = {box.NULL}}}} -- ok
+---
+- [{'a': {'b': [1]}}]
+...
+s:drop()
+---
+...
diff --git a/test/box/sequence.test.lua b/test/box/sequence.test.lua
index d931a94e..c39f1d41 100644
--- a/test/box/sequence.test.lua
+++ b/test/box/sequence.test.lua
@@ -685,3 +685,15 @@ s.index.pk.sequence_id == sequence_id
 s:insert{'d', 1000, 1000}
 s:insert{'d', box.NULL, 1001}
 s:drop()
+
+--
+-- gh-4210: using sequence with a json path key part.
+--
+s = box.schema.space.create('test')
+_ = s:create_index('pk', {parts = {{'[1].a.b[1]', 'unsigned'}}, sequence = true})
+s:replace{} -- error
+s:replace{{c = {}}} -- error
+s:replace{{a = {c = {}}}} -- error
+s:replace{{a = {b = {}}}} -- error
+s:replace{{a = {b = {box.NULL}}}} -- ok
+s:drop()
-- 
2.11.0

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

* [tarantool-patches] Re: [PATCH] box: fix autoincrement for json path indexes
  2019-05-15 13:44         ` [PATCH] box: fix autoincrement for json path indexes Vladimir Davydov
@ 2019-05-16  7:42           ` Konstantin Osipov
  2019-05-21 13:28             ` Vladimir Davydov
  0 siblings, 1 reply; 17+ messages in thread
From: Konstantin Osipov @ 2019-05-16  7:42 UTC (permalink / raw)
  To: tarantool-patches

* Vladimir Davydov <vdavydov.dev@gmail.com> [19/05/15 16:50]:
> The autoincrement code was written when there were no nested field.
> Now, it isn't enough to just skip to the autoincrement field - we also
> need to descend deeper if key_part->path is set.
> 
> Note, the code expects the nested field to be present and set to NULL.
> That is, if field path is [1].a.b, the tuple must have all intermediate
> fields set: {{a = {b = box.NULL}}} (usage of box.NULL is mandatory to
> create a tuple like that in Lua).

Please write a request to docs since this part deserves a spetial
mention in the sequences docs. The patch itself is OK to push.


-- 
Konstantin Osipov, Moscow, Russia, +7 903 626 22 32

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

* [tarantool-patches] Re: [PATCH 3/4] schema: allow to set sequence for any index part, not just the first
  2019-05-15 10:33 ` [PATCH 3/4] schema: allow to set sequence for any index part, not just the first Vladimir Davydov
@ 2019-05-16  7:45   ` Konstantin Osipov
  2019-05-16  8:02     ` Vladimir Davydov
  0 siblings, 1 reply; 17+ messages in thread
From: Konstantin Osipov @ 2019-05-16  7:45 UTC (permalink / raw)
  To: tarantool-patches

* Vladimir Davydov <vdavydov.dev@gmail.com> [19/05/15 14:16]:
> Closes #4009
> 
> @TarantoolBot document
> Title: Sequence can now be set for an index part other than the first
> 
> Initially one could attach a sequence (aka autoincrement) only to the
> first index part. Now it's possible to attach a sequence to any primary
> index part. The part still must be integer though.
> 
> Syntax:
> 
> ```
> box.schema.space.create('test')
> box.space.test:create_index('primary', {
>     parts = {{1, 'string'}, {2, 'unsigned'}, {3, 'unsigned'}},
>     sequence = true, sequence_part = 2
> })
> box.space.test:insert{'a', box.null, 1} -- inserts {'a', 1, 1}


How about allowing column names? We already allow column names in
the parts definition.

Do we really need a separate sequence_part option, why not make
a scalar (bool = true/false, numeric, string - column number or
id).

-- 
Konstantin Osipov, Moscow, Russia, +7 903 626 22 32

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

* [tarantool-patches] Re: [PATCH 2/4] schema: fix error while altering index with sequence
  2019-05-15 10:33 ` [PATCH 2/4] schema: fix error while altering index with sequence Vladimir Davydov
@ 2019-05-16  7:45   ` Konstantin Osipov
  0 siblings, 0 replies; 17+ messages in thread
From: Konstantin Osipov @ 2019-05-16  7:45 UTC (permalink / raw)
  To: tarantool-patches

* Vladimir Davydov <vdavydov.dev@gmail.com> [19/05/15 14:16]:
> A check was missing in index.alter. This resulted in an attempt to drop
> the sequence attached to the altered index even if the sequence was not
> modified.
> 
> Closes #4214

OK to push.

-- 
Konstantin Osipov, Moscow, Russia, +7 903 626 22 32

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

* Re: [tarantool-patches] Re: [PATCH 3/4] schema: allow to set sequence for any index part, not just the first
  2019-05-16  7:45   ` [tarantool-patches] " Konstantin Osipov
@ 2019-05-16  8:02     ` Vladimir Davydov
  0 siblings, 0 replies; 17+ messages in thread
From: Vladimir Davydov @ 2019-05-16  8:02 UTC (permalink / raw)
  To: Konstantin Osipov; +Cc: tarantool-patches

On Thu, May 16, 2019 at 10:45:29AM +0300, Konstantin Osipov wrote:
> * Vladimir Davydov <vdavydov.dev@gmail.com> [19/05/15 14:16]:
> > Closes #4009
> > 
> > @TarantoolBot document
> > Title: Sequence can now be set for an index part other than the first
> > 
> > Initially one could attach a sequence (aka autoincrement) only to the
> > first index part. Now it's possible to attach a sequence to any primary
> > index part. The part still must be integer though.
> > 
> > Syntax:
> > 
> > ```
> > box.schema.space.create('test')
> > box.space.test:create_index('primary', {
> >     parts = {{1, 'string'}, {2, 'unsigned'}, {3, 'unsigned'}},
> >     sequence = true, sequence_part = 2
> > })
> > box.space.test:insert{'a', box.null, 1} -- inserts {'a', 1, 1}
> 
> 
> How about allowing column names? We already allow column names in
> the parts definition.

I don't understand. Do you suggest to match column name with a part
number? What for? The user knows index parts - they are right here in
the index definition - he can choose one for autoincrement. Actually,
this is what the solution team asked for.

> 
> Do we really need a separate sequence_part option, why not make
> a scalar (bool = true/false, numeric, string - column number or
> id).

We do need 'sequence_part', because 'sequence' is already a scalar:

  int - sequence id
  string - sequence name
  true/false - autogenerated sequence

An alternative approach would be attaching sequences to columns rather
than indexes (i.e. adding them to tuple_format), but that would require
massive rework of autoincrement with a lot of backward compatibility
hacks. It isn't quite clear to me that we need to do it now.

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

* [tarantool-patches] Re: [PATCH 0/4] A few fixes/improvements for autoincrement indexes
  2019-05-15 10:33 [PATCH 0/4] A few fixes/improvements for autoincrement indexes Vladimir Davydov
                   ` (3 preceding siblings ...)
  2019-05-15 10:33 ` [PATCH 4/4] schema: explicitly forbid setting sequence for json path key part Vladimir Davydov
@ 2019-05-21 10:42 ` Kirill Yukhin
  2019-05-21 14:58   ` Konstantin Osipov
  4 siblings, 1 reply; 17+ messages in thread
From: Kirill Yukhin @ 2019-05-21 10:42 UTC (permalink / raw)
  To: tarantool-patches

Hello,

On 15 May 13:33, Vladimir Davydov wrote:
> See comments to individual patches for more details.
> 
> https://github.com/tarantool/tarantool/issues/4009
> https://github.com/tarantool/tarantool/issues/4210
> https://github.com/tarantool/tarantool/issues/4214
> https://github.com/tarantool/tarantool/commits/dv/sequence-fixes
> 
> Vladimir Davydov (4):
>   schema: use tuple field names in Lua

Checked into master.

>   schema: fix error while altering index with sequence

Checked into 1.10, 2.1 and master.

>   schema: allow to set sequence for any index part, not just the first

Checked into master.

>   schema: explicitly forbid setting sequence for json path key part

Checked into 2.1 and master.

--
Regards, Kirill Yukhin

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

* Re: [tarantool-patches] Re: [PATCH] box: fix autoincrement for json path indexes
  2019-05-16  7:42           ` [tarantool-patches] " Konstantin Osipov
@ 2019-05-21 13:28             ` Vladimir Davydov
  0 siblings, 0 replies; 17+ messages in thread
From: Vladimir Davydov @ 2019-05-21 13:28 UTC (permalink / raw)
  To: Konstantin Osipov; +Cc: tarantool-patches

On Thu, May 16, 2019 at 10:42:56AM +0300, Konstantin Osipov wrote:
> * Vladimir Davydov <vdavydov.dev@gmail.com> [19/05/15 16:50]:
> > The autoincrement code was written when there were no nested field.
> > Now, it isn't enough to just skip to the autoincrement field - we also
> > need to descend deeper if key_part->path is set.
> > 
> > Note, the code expects the nested field to be present and set to NULL.
> > That is, if field path is [1].a.b, the tuple must have all intermediate
> > fields set: {{a = {b = box.NULL}}} (usage of box.NULL is mandatory to
> > create a tuple like that in Lua).
> 
> Please write a request to docs since this part deserves a spetial
> mention in the sequences docs.

Done: https://github.com/tarantool/doc/issues/782

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

* [tarantool-patches] Re: [PATCH 0/4] A few fixes/improvements for autoincrement indexes
  2019-05-21 10:42 ` [tarantool-patches] Re: [PATCH 0/4] A few fixes/improvements for autoincrement indexes Kirill Yukhin
@ 2019-05-21 14:58   ` Konstantin Osipov
  2019-05-21 16:02     ` Kirill Yukhin
  0 siblings, 1 reply; 17+ messages in thread
From: Konstantin Osipov @ 2019-05-21 14:58 UTC (permalink / raw)
  To: tarantool-patches

* Kirill Yukhin <kyukhin@tarantool.org> [19/05/21 13:43]:
> Hello,

> >   schema: explicitly forbid setting sequence for json path key part

I asked to use an existing option and allow field names in the
patch, e.g. sequece = path.to.field.

I don't see that this is done in the patch.

Why did you check it in?


-- 
Konstantin Osipov, Moscow, Russia

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

* [tarantool-patches] Re: [PATCH 0/4] A few fixes/improvements for autoincrement indexes
  2019-05-21 14:58   ` Konstantin Osipov
@ 2019-05-21 16:02     ` Kirill Yukhin
  0 siblings, 0 replies; 17+ messages in thread
From: Kirill Yukhin @ 2019-05-21 16:02 UTC (permalink / raw)
  To: tarantool-patches

Hello,

On 21 май 17:58, Konstantin Osipov wrote:
> * Kirill Yukhin <kyukhin@tarantool.org> [19/05/21 13:43]:
> > Hello,
> 
> > >   schema: explicitly forbid setting sequence for json path key part
> 
> I asked to use an existing option and allow field names in the
> patch, e.g. sequece = path.to.field.

Do you suggest to allow assigning a sequence to JSON path?

> I don't see that this is done in the patch.
> 
> Why did you check it in?

Vova replied you here [1], this might be useful follow-up, but
right now looks like this is out of scope of the request. They
just need to be able to set sequence to an arbitrary index part.
Since five days pass w/o answer I've checked it into master.

[1] - https://www.freelists.org/post/tarantool-patches/PATCH-34-schema-allow-to-set-sequence-for-any-index-part-not-just-the-first,2

--
Regards, Kirill Yukhin

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

end of thread, other threads:[~2019-05-21 16:02 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-05-15 10:33 [PATCH 0/4] A few fixes/improvements for autoincrement indexes Vladimir Davydov
2019-05-15 10:33 ` [PATCH 1/4] schema: use tuple field names in Lua Vladimir Davydov
2019-05-15 10:33 ` [PATCH 2/4] schema: fix error while altering index with sequence Vladimir Davydov
2019-05-16  7:45   ` [tarantool-patches] " Konstantin Osipov
2019-05-15 10:33 ` [PATCH 3/4] schema: allow to set sequence for any index part, not just the first Vladimir Davydov
2019-05-16  7:45   ` [tarantool-patches] " Konstantin Osipov
2019-05-16  8:02     ` Vladimir Davydov
2019-05-15 10:33 ` [PATCH 4/4] schema: explicitly forbid setting sequence for json path key part Vladimir Davydov
2019-05-15 13:00   ` [tarantool-patches] " Konstantin Osipov
2019-05-15 13:11     ` Vladimir Davydov
2019-05-15 13:16       ` Vladimir Davydov
2019-05-15 13:44         ` [PATCH] box: fix autoincrement for json path indexes Vladimir Davydov
2019-05-16  7:42           ` [tarantool-patches] " Konstantin Osipov
2019-05-21 13:28             ` Vladimir Davydov
2019-05-21 10:42 ` [tarantool-patches] Re: [PATCH 0/4] A few fixes/improvements for autoincrement indexes Kirill Yukhin
2019-05-21 14:58   ` Konstantin Osipov
2019-05-21 16:02     ` Kirill Yukhin

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