From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: From: Vladimir Davydov Subject: [PATCH v2] schema: rework index sequence API Date: Tue, 28 May 2019 17:17:37 +0300 Message-Id: To: tarantool-patches@freelists.org List-ID: Rather than passing 'sequence_part' along with 'sequence' on index create/alter, pass a table with the following fields: - id: sequence id or name - field: auto increment field id or name or path in case of json index If id is omitted, the sequence will be auto-generated (equivalent to 'sequence = true'). If field is omitted, the first indexed field is used. Old format, i.e. passing false/true or sequence name/id instead of a table is still supported. Follow-up #4009 --- https://github.com/tarantool/tarantool/commits/dv/rework-sequence-api Changes in v2: - Store field no and path in _space_sequence as well. - Add tests checking index.alter behavior in presence of an auto-increment field. v1: https://www.freelists.org/post/tarantool-patches/PATCH-schema-rework-index-sequence-API src/box/alter.cc | 86 +++++++-- src/box/bootstrap.snap | Bin 4379 -> 4393 bytes src/box/lua/schema.lua | 306 +++++++++++++++++++++----------- src/box/lua/space.cc | 12 +- src/box/lua/upgrade.lua | 21 ++- src/box/request.c | 12 +- src/box/schema_def.h | 3 +- src/box/space.h | 9 +- src/box/sql/build.c | 36 ++-- src/box/sql/insert.c | 6 +- test/box-py/bootstrap.result | 2 +- test/box/access.result | 9 + test/box/access.test.lua | 3 + test/box/access_misc.result | 2 +- test/box/alter.result | 8 +- test/box/sequence.result | 176 +++++++++++++----- test/box/sequence.test.lua | 80 ++++++--- test/box/stat.result | 32 ++-- test/replication/autobootstrap.result | 6 + test/replication/autobootstrap.test.lua | 2 + test/vinyl/ddl.result | 6 +- 21 files changed, 563 insertions(+), 254 deletions(-) diff --git a/src/box/alter.cc b/src/box/alter.cc index 965945af..ed9e5590 100644 --- a/src/box/alter.cc +++ b/src/box/alter.cc @@ -124,14 +124,30 @@ 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, uint32_t sequence_part, - const char *space_name) +index_def_check_sequence(struct index_def *index_def, uint32_t sequence_fieldno, + const char *sequence_path, const char *space_name) { - if (sequence_part >= index_def->key_def->part_count) { + struct key_def *key_def = index_def->key_def; + struct key_part *sequence_part = NULL; + for (uint32_t i = 0; i < key_def->part_count; ++i) { + struct key_part *part = &key_def->parts[i]; + if (part->fieldno != sequence_fieldno) + continue; + if ((part->path == NULL && sequence_path == NULL) || + (part->path != NULL && sequence_path != NULL && + json_path_cmp(part->path, part->path_len, + sequence_path, strlen(sequence_path), + TUPLE_INDEX_BASE) == 0)) { + sequence_part = part; + break; + } + } + if (sequence_part == NULL) { tnt_raise(ClientError, ER_MODIFY_INDEX, index_def->name, - space_name, "sequence part is out of bounds"); + space_name, "sequence field must be a part of " + "the index"); } - enum field_type type = index_def->key_def->parts[sequence_part].type; + enum field_type type = 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 " @@ -284,8 +300,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->sequence_part, - space_name(space)); + index_def_check_sequence(index_def, space->sequence_fieldno, + space->sequence_path, space_name(space)); index_def_guard.is_active = false; return index_def; } @@ -861,7 +877,8 @@ 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; + alter->new_space->sequence_fieldno = alter->old_space->sequence_fieldno; + alter->new_space->sequence_path = alter->old_space->sequence_path; memcpy(alter->new_space->access, alter->old_space->access, sizeof(alter->old_space->access)); @@ -3340,12 +3357,6 @@ 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); @@ -3378,21 +3389,58 @@ 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, sequence_part, + + /* Sequence field was added in 2.2.1. */ + uint32_t sequence_fieldno; + const char *sequence_path_raw; + uint32_t sequence_path_len; + if (tuple_field_count(tuple) > BOX_SPACE_SEQUENCE_FIELD_FIELDNO) { + sequence_fieldno = tuple_field_u32_xc(tuple, + BOX_SPACE_SEQUENCE_FIELD_FIELDNO); + sequence_path_raw = tuple_field_str_xc(tuple, + BOX_SPACE_SEQUENCE_FIELD_PATH, + &sequence_path_len); + } else { + struct key_part *part = &pk->def->key_def->parts[0]; + sequence_fieldno = part->fieldno; + sequence_path_raw = part->path; + sequence_path_len = part->path_len; + } + char *sequence_path = NULL; + if (sequence_path_len > 0) { + sequence_path = (char *)malloc(sequence_path_len + 1); + if (sequence_path == NULL) + tnt_raise(OutOfMemory, sequence_path_len + 1, + "malloc", "sequence path"); + memcpy(sequence_path, sequence_path_raw, + sequence_path_len); + sequence_path[sequence_path_len] = 0; + } + auto sequence_path_guard = make_scoped_guard([=] { + free(sequence_path); + }); + + index_def_check_sequence(pk->def, sequence_fieldno, + sequence_path, space_name(space)); - if (seq->is_generated && seq != space->sequence) { + if (seq->is_generated) { 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; + space->sequence_fieldno = sequence_fieldno; + free(space->sequence_path); + space->sequence_path = sequence_path; + sequence_path_guard.is_active = false; } else { /* DELETE */ assert(space->sequence == seq); - assert(space->sequence_part == sequence_part); + seq->is_generated = false; space->sequence = NULL; - space->sequence_part = 0; + space->sequence_fieldno = 0; + free(space->sequence_path); + space->sequence_path = NULL; } } diff --git a/src/box/bootstrap.snap b/src/box/bootstrap.snap index 8c3b10e8417494edba07e6eba78377226ee5a31a..bb8fbeba114b1e72a5585548fb7f22796931d90f 100644 GIT binary patch literal 4393 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 literal 4379 zcmV+$5#;VuPC-x#FfK7O3RY!ub7^mGIv_GGGA=MJGB`3VXE8KoIbt+3WC}@cb97;D zV`VxZF=jV3Ib<+7Eiq*?Vl6Z>H!&?@H8?XZWnnX8Wn(ZgF=b*k3RXjGZ)0mZAbWiZ z3e~y`y3G)x0M0u^{P?8+00000D77#B08q_@06LTGAVv_7*f{_U01N0TTsFq3z#aICk^=b7=ejK7YHaR4@Sr z_exOZ-Uue(S4jeXlOo`kUkH@$rIK{-6bSB3AD}|>yV(?)-^m`J`4C6*JEYmH>qsW+ zasa7`38{&xOUluVD~ba$t_Gx#9Q@8N3zqMOTO{YSyE!($?{YzjVnYTRu%QtY8``jl z0owvbHk$%PHd_J+B~5NOA12-_S>k;X zAMcSU>HSEO-i;vXy~vTW1i(mH5-C!aHXQ=MObC&1bMX2sS&$*3twQpjXulMQ5L>r9 z({w#S%G}MX41RB5Kzd-WC|kGr_xrK%ANTFwW1Vp`<_Z^Fy&(tlKCdcO?5C#(_KGs& zhKv{{FkGzY>fL->y{KY8J+N0)g~2w@zF+ieP3?ibqVm*Um_I06>ZD>m_4UHzg9c2{ z6rMS#V8QeR5ZDV%Ukff~b+Xt(RtF0$P%kIEW#{7U--r4-S7hP66`DTTx8WJrq)Ok0*-myYE5q>Pmvm_rYzgE{=Z?CltFC;K9f$esuzvd?~qBCZ#Li0gwG z;`-}>5W@PdAgs3og!R^Qb@5)u4)1ihc;|gi9c}(VN1Hy((WW-Hez0Z_K3G%EmNl&z zr-#mWayn1rbly3fUCFi5lD~md@{(t1VZGsgUuE(9#`&ENYv-=^w$km5%h7g~6>al? zqix!DQ}uV?;eQ?6{GY=P9S0qHzafX-ZNNddxk2k1Y|xrb8?vOTVTuh|lI0pI6jd{Y zO07ZBG;02*K`Nagjjl>%pkeD6X4o1A8MfZ~g&16bLAGn*1sBhB#8@W>O+@JKQ>!3=McC7iIyO_E@OCPyei zlQ$AHsY%-iBnfpR$+Tu8tH>0EvQB5sI(^hSStoz`+`rFH-e;f9K4$(j=53C7%q6#B zy=|B*ZPbe)irFM40VB_++~e zLa14c3-)TxX&SU$3|cVvyZt@~Wp^%d{f_tVd}ZG6t=jB+K3)H1RUx6;&8WBeEA+dn zK?QtUb}s5htHs>kCkMY!-z;kFJnpMw^XV!O0j4c+{+ry&f?VrN+wXVRc&>_S^Zc#W z9Q@wYpP)Ne)2!C#{(RQDr?zO$wkX*07+45r0e^=F~_H6Gq zceQ!`-1Zt5-aTf&PpoU&zEzmN3tH1--O_F=G-_&?Kl9{$#5(i3nqv~QnwE9{)i?aY z+xr!7_-WhD=trm4+G7&~8sJBOCIOuI#^h&76CWm*TD088hvpBZLUP1sQ^GK!o5MzV!r| z!D_GeWVQBsY_?bJ)!r-*k1F<4d$n5n$E)^gsULY}yFUJq?fCG+)^N7A+oKPT>cIy` z^Vox<9K}PAjNXw)M(w~OqZzH^j(*Z%M?dGNqo4hhgO2lzV~+ELLyj|jBaYdY8*s?B z+IWL^R*mNt8*cQ!MjQRB!AAf6r?JNWXsGf38EO2Vnt{guWt{PUN`@I#=@@0;F9sR- zhcO2J_=6$F|6hdhzZYQqpLX$u{akoq{}x@?Zv#(w-Ag?stczA6k(`Wj3=fSpNQC{= zUJiEaK1D28or)#ZUYcG<6j=6+5cEoy*YXY6YRnC}Pv*HIo-DAe1m=Zp{YfADLci7sY50NEZ7jro~{^ znST9g_5D4^TzyT&jy_--^95JAxU-ag{V7EX6sP@A>r|}BgBq5R3dvyA4w^XVvO(Q4 zW=m6D(qKu9QW&KSlrWIe!IHqdk}l|xQj_!r$qP~!BrZo=jx0FRVwA-QOB$mqMOKQK z(iAPlDv*j%KvYGFXo4XLh9VeJ!ro#hNkR|^Q4yLF^gz%oNCtxeAyug~M^vCAAO~vw zk@6GeC6(sRJ)iPEb!YDF-|FR4dsH!^2DCH>-`LPRH_yW0w`I;}t$n*SvqGIQ*yitS z`}doJA1Qvj8bYxO@3;N#JAOXH(OMIyHZhQsy5EBP?ElNEU5wO>WA6XMs#}UqE2-~K z4Sra4X#q5MQ%QmG>Wt=YW?5}Pugm&Ac%5im>t6o1?c)`iyZPm6QUX)``V$EO{7*a8 zuRp(BJvv|wxK9!Nbk)d!^IyLP+^6{EYEg^|{jO?^s9Q6aaa2$@-1*a0q9<+8?@b*V z`ZoM_)rpfGhDfOMH zH2?!3KtX^a0A;`~U7StOx=ROho1%faPSLj%8r>SqcakZxL7{X>z>UCB;cEamh=Jzk5 zaq~p7n41kdhNv?sCPuwh`hqmb3eN+|C(TU6)}Uf>oq*Dt)&2oU0}gdzr4`fWY6kh;7*E;z+>d3s!NifJm3 zx5s+Z=xjaZdFvof3{MiB!#WC-E_379zvGr8Gg;`lnJsR0mg7cqQ5a|D$v6#;rpwdgg3~@1&5k$j?@7P>#(^Pn`)6-b5ww8llsj+*DIbZHN1*(?Dg3gw84rSE zA=S$uxyVgv$=AGo!OY=0_56oR8B+1e@&i)EgN=>c5-;|IFa}{BU7wSCpO(95l+>85q@fBa1i=^ux4Oqb+77=VJqXh7O6$H*OK=~$g#AQf|n7g0`_FcPN zDFT$xzIg8pV9`;RO8!)yEnxUk2ba1>|Bxn1KDVis$nc6b0XT@N)kI09Hs%!CFiIK} zL`Iu;>MD~UeJHvWjebf?nv1m)oYbPakVCUn3b(aKvArWky(hs!ohezT(~E4Bc1oQA?rv(B&>}Wv7qZv9^PKk zjh1Vu>@?8Q@csyML4qOv6>PYc2YEz_WN%9hF86f^q$>|ga=&lP*XkqJqp7J9+Tz74 z^htSRsk=OhN3`iG{stZWwdJZwLnN$?7qPc=lSlnX5$@T<$m3oMfS^B)1hinFv{wz5 zG+tCl|CsXga2Q2|LZ6cY#50qMb?mK(I(w;fqW9+F^oUQE#C2x<~mBpO_(CvFnqmGo}` zT{Z1BR38U{ZR_X-fNn`)8ev-?g5=}gOf?QhWnerAM})}sMq=c7uLnT= zA&`Ep`oB>Jf8BX2Ul5#zNb&|rQ3#9&`G^$B-jf(y?zI4@p9IpcRrxpQ;IBJxMHhsp z5INo;DGGt{p&t<<+j|m&%e@u=^%KR|sEED18aZZO2Y{=$YDZ_2EkrTyz^@QzXXS z_0A>wNTEB5$$O7KUu%((OOHPsk1?&~(=aJ~UVY>E_~Y2Q)e58;OvzA@G@Eq5pwg8l zfn0dF&=8jBq6k0LM@8Qt0Qq=T1+%J-BLnJ{bBf z&d@}0&1sZPC2Aav{0s39r9EF5>l31xA zi&zl+CRuoliGx66G}L33qD-OjEe9lN`^X36es%%}N75y3T=S!O0e5ulkyA*&O| zLJ0v{nF|Y(9|*8)*=e>B`m1FaQjKNTGoCvy{=3aCe9J@N#*rYoER5zVT*Z(Bu|!

wQM0kY6SY-tc^}nc!iq>hXb$_>cXbc~l`z5({!ka5 zYzHHuw-%I`>XoV>(8fxAacD6g#cWZI(L4 z0p+4v4aE%_4lBn>W}S*UG#sWbi`Y7?WZ3Cg<_N+F_gf~Rq7jI?EMm)fjf94 #parts then - box.error(box.error.MODIFY_INDEX, index_name, space_name, - "sequence part is out of bounds") +-- +-- Raise an error if a sequence isn't compatible with a given +-- index definition. +-- +local function space_sequence_check(sequence, parts, space_name, index_name) + local sequence_part = nil + if sequence.field ~= nil then + sequence.path = sequence.path or '' + -- Look up the index part corresponding to the given field. + for _, part in ipairs(parts) do + local field = part.field or part[1] + local path = part.path or '' + if sequence.field == field and sequence.path == path then + sequence_part = part + break + end + end + if sequence_part == nil then + box.error(box.error.MODIFY_INDEX, index_name, space_name, + "sequence field must be a part of the index") + end + else + -- If the sequence field is omitted, use the first + -- indexed field. + sequence_part = parts[1] + sequence.field = sequence_part.field or sequence_part[1] + sequence.path = sequence_part.path or '' 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 + -- Check the type of the auto-increment field. + local t = sequence_part.type or sequence_part[2] + if t ~= 'integer' and t ~= 'unsigned' then box.error(box.error.MODIFY_INDEX, index_name, space_name, "sequence cannot be used with a non-integer key") end end +-- +-- The first stage of a space sequence modification operation. +-- Called before altering the space definition. Checks sequence +-- options and detaches the old sequence from the space. +-- Returns a proxy object that is supposed to be passed to +-- space_sequence_alter_commit() to complete the operation. +-- +local function space_sequence_alter_prepare(format, parts, options, + space_id, index_id, + space_name, index_name) + local _space_sequence = box.space[box.schema.SPACE_SEQUENCE_ID] + + -- A sequence can only be attached to a primary index. + if index_id ~= 0 then + -- Ignore 'sequence = false' for secondary indexes. + if not options.sequence then + return nil + end + box.error(box.error.MODIFY_INDEX, index_name, space_name, + "sequence cannot be used with a secondary key") + end + + -- Look up the currently attached sequence, if any. + local old_sequence + local tuple = _space_sequence:get(space_id) + if tuple ~= nil then + old_sequence = { + id = tuple.sequence_id, + is_generated = tuple.is_generated, + field = tuple.field, + path = tuple.path, + } + else + old_sequence = nil + end + + if options.sequence == nil then + -- No sequence option, just check that the old sequence + -- is compatible with the new index definition. + if old_sequence ~= nil and old_sequence.field ~= nil then + space_sequence_check(old_sequence, parts, space_name, index_name) + end + return nil + end + + -- Convert the provided option to the table format. + local new_sequence + if type(options.sequence) == 'table' then + -- Sequence is given as a table, just copy it. + -- Silently ignore unknown fields. + new_sequence = { + id = options.sequence.id, + field = options.sequence.field, + } + elseif options.sequence == true then + -- Create an auto-generated sequence. + new_sequence = {} + elseif options.sequence == false then + -- Drop the currently attached sequence. + new_sequence = nil + else + -- Attach a sequence with the given id. + new_sequence = {id = options.sequence} + end + + if new_sequence ~= nil then + -- Resolve the sequence name. + if new_sequence.id ~= nil then + local id = sequence_resolve(new_sequence.id) + if id == nil then + box.error(box.error.NO_SUCH_SEQUENCE, new_sequence.id) + end + local tuple = _space_sequence.index.sequence:select(id)[1] + if tuple ~= nil and tuple.is_generated then + box.error(box.error.ALTER_SPACE, space_name, + "can not attach generated sequence") + end + new_sequence.id = id + end + -- Resolve the sequence field. + if new_sequence.field ~= nil then + local field, path = format_field_resolve(format, new_sequence.field, + "sequence field") + new_sequence.field = field + new_sequence.path = path + end + -- Inherit omitted options from the attached sequence. + if old_sequence ~= nil then + if new_sequence.id == nil and old_sequence.is_generated then + new_sequence.id = old_sequence.id + new_sequence.is_generated = true + end + if new_sequence.field == nil then + new_sequence.field = old_sequence.field + new_sequence.path = old_sequence.path + end + end + -- Check that the sequence is compatible with + -- the index definition. + space_sequence_check(new_sequence, parts, space_name, index_name) + -- If sequence id is omitted, we are supposed to create + -- a new auto-generated sequence for the given space. + if new_sequence.id == nil then + local seq = box.schema.sequence.create(space_name .. '_seq') + new_sequence.id = seq.id + new_sequence.is_generated = true + end + new_sequence.is_generated = new_sequence.is_generated or false + end + + if old_sequence ~= nil then + -- Detach the old sequence before altering the space. + _space_sequence:delete(space_id) + end + + return { + space_id = space_id, + new_sequence = new_sequence, + old_sequence = old_sequence, + } +end + +-- +-- The second stage of a space sequence modification operation. +-- Called after altering the space definition. Attaches the sequence +-- to the space and drops the old sequence if required. 'proxy' is +-- an object returned by space_sequence_alter_prepare(). +-- +local function space_sequence_alter_commit(proxy) + local _space_sequence = box.space[box.schema.SPACE_SEQUENCE_ID] + + if proxy == nil then + -- No sequence option, nothing to do. + return + end + + local space_id = proxy.space_id + local old_sequence = proxy.old_sequence + local new_sequence = proxy.new_sequence + + if new_sequence ~= nil then + -- Attach the new sequence. + _space_sequence:insert{space_id, new_sequence.id, + new_sequence.is_generated, + new_sequence.field, new_sequence.path} + end + + if old_sequence ~= nil and old_sequence.is_generated and + (new_sequence == nil or old_sequence.id ~= new_sequence.id) then + -- Drop automatically generated sequence. + box.schema.sequence.drop(old_sequence.id) + end +end + -- Historically, some properties of an index -- are stored as tuple fields, others in a -- single field containing msgpack map. @@ -787,8 +963,7 @@ local alter_index_template = { name = 'string', type = 'string', parts = 'table', - sequence = 'boolean, number, string', - sequence_part = 'number', + sequence = 'boolean, number, string, table', } for k, v in pairs(index_options) do alter_index_template[k] = v @@ -898,41 +1073,15 @@ box.schema.index.create = function(space_id, name, options) "please use '%s' instead", field_type, part.type) end end - 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 - 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 - sequence_is_generated = true - else - sequence = sequence_resolve(sequence) - if sequence == nil then - box.error(box.error.NO_SUCH_SEQUENCE, options.sequence) - end - end - end -- save parts in old format if possible if parts_can_be_simplified then parts = simplify_index_parts(parts) end + local sequence_proxy = space_sequence_alter_prepare(format, parts, options, + space_id, iid, + space.name, name) _index:insert{space_id, iid, name, options.type, index_opts, parts} - if sequence ~= nil then - _space_sequence:insert{space_id, sequence, sequence_is_generated, - sequence_part - 1} - end + space_sequence_alter_commit(sequence_proxy) return space.index[name] end @@ -1044,71 +1193,12 @@ box.schema.index.alter = function(space_id, index_id, options) parts = simplify_index_parts(parts) end end - 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 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 - end - if sequence ~= nil or sequence_part ~= nil then - sequence_tuple = _space_sequence:get(space_id) - 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 - else - -- Space already has an automatically generated sequence. - sequence = sequence_tuple.sequence_id - end - sequence_is_generated = true - elseif sequence then - sequence = sequence_resolve(sequence) - if sequence == nil then - box.error(box.error.NO_SUCH_SEQUENCE, options.sequence) - end - end - if sequence == false then - _space_sequence:delete(space_id) - end + local sequence_proxy = space_sequence_alter_prepare(format, parts, options, + space_id, index_id, + space.name, options.name) _index:replace{space_id, index_id, options.name, options.type, index_opts, parts} - 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 - sequence_tuple.sequence_id ~= sequence then - -- Delete automatically generated sequence. - box.schema.sequence.drop(sequence_tuple.sequence_id) - end + space_sequence_alter_commit(sequence_proxy) end -- a static box_tuple_t ** instance for calling box_index_* API diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc index e342bfcc..87adaeb1 100644 --- a/src/box/lua/space.cc +++ b/src/box/lua/space.cc @@ -309,9 +309,17 @@ lbox_fillspace(struct lua_State *L, struct space *space, int i) */ lua_rawset(L, -3); - lua_pushstring(L, "sequence_part"); + lua_pushstring(L, "sequence_fieldno"); if (k == 0 && space->sequence != NULL) - lua_pushnumber(L, space->sequence_part + 1); + lua_pushnumber(L, space->sequence_fieldno + + TUPLE_INDEX_BASE); + else + lua_pushnil(L); + lua_rawset(L, -3); + + lua_pushstring(L, "sequence_path"); + if (k == 0 && space->sequence_path != NULL) + lua_pushstring(L, space->sequence_path); else lua_pushnil(L); lua_rawset(L, -3); diff --git a/src/box/lua/upgrade.lua b/src/box/lua/upgrade.lua index 23f4df01..07066269 100644 --- a/src/box/lua/upgrade.lua +++ b/src/box/lua/upgrade.lua @@ -749,17 +749,28 @@ end -- Tarantool 2.2.1 -------------------------------------------------------------------------------- --- Add sequence part field to _space_sequence table +-- Add sequence field to _space_sequence table local function upgrade_sequence_to_2_2_1() - log.info("add key part field to space _space_sequence") + log.info("add sequence field to space _space_sequence") + local _index = box.space[box.schema.INDEX_ID] 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}}) + if #v > 3 then + -- Must be a sequence created after upgrade. + -- It doesn't need to get updated. + goto continue end + -- Explicitly attach the sequence to the first index part. + local pk = _index:get{v[1], 0} + local part = pk[6][1] + local field = part.field or part[1] + local path = part.path or '' + _space_sequence:update(v[1], {{'!', 4, field}, {'!', 5, path}}) + ::continue:: end local format = _space_sequence:format() - format[4] = {name = 'part', type = 'unsigned'} + format[4] = {name = 'field', type = 'unsigned'} + format[5] = {name = 'path', type = 'string'} _space_sequence:format(format) end diff --git a/src/box/request.c b/src/box/request.c index e2a98fdf..82232a15 100644 --- a/src/box/request.c +++ b/src/box/request.c @@ -158,13 +158,14 @@ request_handle_sequence(struct request *request, struct space *space) return 0; /* - * Look up the first field of the primary key. + * Look up the auto-increment field. */ + int fieldno = space->sequence_fieldno; + const char *path = space->sequence_path; + const char *data = request->tuple; const char *data_end = request->tuple_end; int len = mp_decode_array(&data); - struct key_part *part = &pk->def->key_def->parts[space->sequence_part]; - int fieldno = part->fieldno; if (unlikely(len < fieldno + 1)) return 0; @@ -175,9 +176,8 @@ 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 (path != NULL) { + tuple_go_to_path(&key, path, strlen(path), MULTIKEY_NONE); if (key == NULL) return 0; /* field not found */ } diff --git a/src/box/schema_def.h b/src/box/schema_def.h index dea3fad1..b817b49f 100644 --- a/src/box/schema_def.h +++ b/src/box/schema_def.h @@ -216,7 +216,8 @@ 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, + BOX_SPACE_SEQUENCE_FIELD_FIELDNO = 3, + BOX_SPACE_SEQUENCE_FIELD_PATH = 4, }; /** _trigger fields. */ diff --git a/src/box/space.h b/src/box/space.h index c3eef71c..be1178b9 100644 --- a/src/box/space.h +++ b/src/box/space.h @@ -176,11 +176,10 @@ 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; + /** Auto increment field number. */ + uint32_t sequence_fieldno; + /** Path to data in the auto-increment field. */ + char *sequence_path; /** Enable/disable triggers. */ bool run_triggers; /** diff --git a/src/box/sql/build.c b/src/box/sql/build.c index 91b977de..e2353d8c 100644 --- a/src/box/sql/build.c +++ b/src/box/sql/build.c @@ -939,15 +939,26 @@ emitNewSysSequenceRecord(Parse *pParse, int reg_seq_id, const char *seq_name) return first_col; } -int -emitNewSysSpaceSequenceRecord(Parse *pParse, int space_id, const char reg_seq_id) +static int +emitNewSysSpaceSequenceRecord(Parse *pParse, int reg_space_id, int reg_seq_id, + struct index_def *idx_def) { + struct key_part *part = &idx_def->key_def->parts[0]; + int fieldno = part->fieldno; + char *path = NULL; + if (part->path != NULL) { + path = sqlDbStrNDup(pParse->db, part->path, part->path_len); + if (path == NULL) + return -1; + path[part->path_len] = 0; + } + Vdbe *v = sqlGetVdbe(pParse); int first_col = pParse->nMem + 1; - pParse->nMem += 4; /* 3 fields + new record pointer */ + pParse->nMem += 6; /* 5 fields + new record pointer */ /* 1. Space id */ - sqlVdbeAddOp2(v, OP_SCopy, space_id, first_col + 1); + sqlVdbeAddOp2(v, OP_SCopy, reg_space_id, first_col + 1); /* 2. Sequence id */ sqlVdbeAddOp2(v, OP_IntCopy, reg_seq_id, first_col + 2); @@ -955,10 +966,15 @@ emitNewSysSpaceSequenceRecord(Parse *pParse, int space_id, const char reg_seq_id /* 3. Autogenerated. */ sqlVdbeAddOp2(v, OP_Bool, true, first_col + 3); - /* 4. Part id. */ - sqlVdbeAddOp2(v, OP_Integer, 0, first_col + 4); + /* 4. Field id. */ + sqlVdbeAddOp2(v, OP_Integer, fieldno, first_col + 4); - sqlVdbeAddOp3(v, OP_MakeRecord, first_col + 1, 4, first_col); + /* 5. Field path. */ + sqlVdbeAddOp4(v, OP_String8, 0, first_col + 5, 0, + path != NULL ? path : "", + path != NULL ? P4_DYNAMIC : P4_STATIC ); + + sqlVdbeAddOp3(v, OP_MakeRecord, first_col + 1, 5, first_col); return first_col; } @@ -1173,9 +1189,9 @@ sqlEndTable(struct Parse *pParse) save_record(pParse, BOX_SEQUENCE_ID, reg_seq_record + 1, 1, v->nOp - 1); /* Do an insertion into _space_sequence. */ - int reg_space_seq_record = - emitNewSysSpaceSequenceRecord(pParse, reg_space_id, - reg_seq_id); + int reg_space_seq_record = emitNewSysSpaceSequenceRecord(pParse, + reg_space_id, reg_seq_id, + new_space->index[0]->def); sqlVdbeAddOp3(v, OP_SInsert, BOX_SPACE_SEQUENCE_ID, 0, reg_space_seq_record); save_record(pParse, BOX_SPACE_SEQUENCE_ID, diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c index 2813415b..f84507e5 100644 --- a/src/box/sql/insert.c +++ b/src/box/sql/insert.c @@ -82,11 +82,9 @@ static uint32_t sql_space_autoinc_fieldno(struct space *space) { assert(space != NULL); - struct index *pk = space_index(space, 0); - if (pk == NULL || pk->def->key_def->part_count != 1 || - space->sequence == NULL) + if (space->sequence == NULL) return UINT32_MAX; - return pk->def->key_def->parts[space->sequence_part].fieldno; + return space->sequence_fieldno; } /** diff --git a/test/box-py/bootstrap.result b/test/box-py/bootstrap.result index de90beee..0684914c 100644 --- a/test/box-py/bootstrap.result +++ b/test/box-py/bootstrap.result @@ -73,7 +73,7 @@ box.space._space:select{} 'type': 'unsigned'}]] - [340, 1, '_space_sequence', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'sequence_id', 'type': 'unsigned'}, {'name': 'is_generated', 'type': 'boolean'}, - {'name': 'part', 'type': 'unsigned'}]] + {'name': 'field', 'type': 'unsigned'}, {'name': 'path', 'type': 'string'}]] - [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.result b/test/box/access.result index 9c190240..44a74e17 100644 --- a/test/box/access.result +++ b/test/box/access.result @@ -610,6 +610,9 @@ box.schema.user.grant('twostep', 'write', 'space', '_space') box.schema.user.grant('twostep', 'write', 'space', '_index') --- ... +box.schema.user.grant('twostep', 'read', 'space', '_space_sequence') +--- +... box.schema.user.grant('twostep', 'read,write', 'space', '_func') --- ... @@ -1481,6 +1484,9 @@ box.schema.user.grant('tester', 'create' , 'sequence') box.schema.user.grant('tester', 'read', 'space', '_sequence') --- ... +box.schema.user.grant('tester', 'read', 'space', '_space_sequence') +--- +... box.schema.user.grant('tester', 'read', 'space', '_trigger') --- ... @@ -1922,6 +1928,9 @@ box.schema.user.grant("test", "read", "space", "space1") box.schema.user.grant("test", "write", "space", "_index") --- ... +box.schema.user.grant("test", "read", "space", "_space_sequence") +--- +... box.session.su("test") --- ... diff --git a/test/box/access.test.lua b/test/box/access.test.lua index 4baeb2ef..ee408f53 100644 --- a/test/box/access.test.lua +++ b/test/box/access.test.lua @@ -250,6 +250,7 @@ box.schema.user.grant('twostep', 'create', 'function') box.schema.user.grant('twostep', 'write', 'space', '_schema') box.schema.user.grant('twostep', 'write', 'space', '_space') box.schema.user.grant('twostep', 'write', 'space', '_index') +box.schema.user.grant('twostep', 'read', 'space', '_space_sequence') box.schema.user.grant('twostep', 'read,write', 'space', '_func') box.session.su('twostep') twostep = box.schema.space.create('twostep') @@ -552,6 +553,7 @@ box.schema.user.grant('tester', 'create', 'space') box.schema.user.grant('tester', 'create', 'function') box.schema.user.grant('tester', 'create' , 'sequence') box.schema.user.grant('tester', 'read', 'space', '_sequence') +box.schema.user.grant('tester', 'read', 'space', '_space_sequence') box.schema.user.grant('tester', 'read', 'space', '_trigger') box.schema.user.grant('tester', 'read', 'space', '_fk_constraint') box.session.su("tester") @@ -746,6 +748,7 @@ box.schema.user.create("test") _ = box.schema.space.create("space1") box.schema.user.grant("test", "read", "space", "space1") box.schema.user.grant("test", "write", "space", "_index") +box.schema.user.grant("test", "read", "space", "_space_sequence") box.session.su("test") box.space.space1:create_index("pk") box.session.su("admin") diff --git a/test/box/access_misc.result b/test/box/access_misc.result index 877a9b53..24bdd9d6 100644 --- a/test/box/access_misc.result +++ b/test/box/access_misc.result @@ -813,7 +813,7 @@ box.space._space:select() 'type': 'unsigned'}]] - [340, 1, '_space_sequence', 'memtx', 0, {}, [{'name': 'id', 'type': 'unsigned'}, {'name': 'sequence_id', 'type': 'unsigned'}, {'name': 'is_generated', 'type': 'boolean'}, - {'name': 'part', 'type': 'unsigned'}]] + {'name': 'field', 'type': 'unsigned'}, {'name': 'path', 'type': 'string'}]] - [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/alter.result b/test/box/alter.result index 75d6dae2..e83c0b7e 100644 --- a/test/box/alter.result +++ b/test/box/alter.result @@ -674,10 +674,10 @@ s.index.pk - type: unsigned is_nullable: false fieldno: 2 + type: TREE id: 0 space_id: 731 name: pk - type: TREE ... s:select{} --- @@ -704,10 +704,10 @@ s.index.pk - type: unsigned is_nullable: false fieldno: 1 - id: 0 space_id: 731 - name: pk + id: 0 type: TREE + name: pk ... s.index.secondary --- @@ -716,10 +716,10 @@ s.index.secondary - type: unsigned is_nullable: false fieldno: 2 + type: TREE id: 1 space_id: 731 name: secondary - type: TREE ... s.index.secondary:select{} --- diff --git a/test/box/sequence.result b/test/box/sequence.result index 3cac4dc9..990d15db 100644 --- a/test/box/sequence.result +++ b/test/box/sequence.result @@ -590,17 +590,20 @@ 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 +s:create_index('pk', {sequence = {id = 'no_such_sequence'}}) -- error --- -- error: 'Can''t create or modify index ''nil'' in space ''test'': sequence part cannot - be used without sequence' +- error: Sequence 'no_such_sequence' does not exist ... -s:create_index('pk', {sequence = true, sequence_part = 2}) -- error +s:create_index('pk', {sequence = {field = 2}}) -- error --- -- error: 'Can''t create or modify index ''pk'' in space ''test'': sequence part is - out of bounds' +- error: 'Can''t create or modify index ''pk'' in space ''test'': sequence field must + be a part of the index' ... -s:create_index('pk', {parts = {1, 'unsigned', 2, 'string'}, sequence = true, sequence_part = 2}) -- error +s:create_index('pk', {sequence = {field = 'no.such.field'}}) -- error +--- +- error: 'Illegal parameters, sequence field: field was not found by name ''no.such.field''' +... +s:create_index('pk', {parts = {1, 'unsigned', 2, 'string'}, sequence = {field = 2}}) -- error --- - error: 'Can''t create or modify index ''pk'' in space ''test'': sequence cannot be used with a non-integer key' @@ -608,23 +611,33 @@ s:create_index('pk', {parts = {1, 'unsigned', 2, 'string'}, sequence = true, seq pk = s:create_index('pk', {parts = {1, 'string', 2, 'unsigned'}}) -- ok --- ... -pk:alter{sequence_part = 1} -- error +pk:alter{sequence = {id = 'no_such_sequence', field = 2}} -- error --- -- error: 'Can''t create or modify index ''pk'' in space ''test'': sequence part cannot - be used without sequence' +- error: Sequence 'no_such_sequence' does not exist ... -pk:alter{sequence = true, sequence_part = 1} -- error +pk:alter{sequence = {id = 'test', field = 2}} -- ok +--- +... +pk:alter{sequence = {id = 'test', field = 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 = false} -- ok +--- +... +pk:alter{sequence = {field = 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 = {field = 2}} -- ok --- ... -pk:alter{sequence = false, sequence_part = 2} -- error +pk:alter{sequence = {field = 1}} -- error --- -- error: 'Can''t create or modify index ''pk'' in space ''test'': sequence part cannot - be used without sequence' +- error: 'Can''t create or modify index ''pk'' in space ''test'': sequence cannot + be used with a non-integer key' ... pk:alter{sequence = false} -- ok --- @@ -657,10 +670,10 @@ 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 +s:create_index('secondary', {parts = {2, 'unsigned'}, sequence = {field = 2}}) -- error --- -- error: 'Can''t create or modify index ''nil'' in space ''test'': sequence part cannot - be used without sequence' +- error: 'Can''t create or modify index ''secondary'' in space ''test'': sequence + cannot be used with a secondary key' ... sk = s:create_index('secondary', {parts = {2, 'unsigned'}}) -- ok --- @@ -747,15 +760,15 @@ 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, 0} -- 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 +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' +- error: 'Can''t create or modify index ''pk'' in space ''test'': sequence field must + be a part of the index' ... sk:drop() --- @@ -763,14 +776,15 @@ sk:drop() pk:drop() --- ... -box.space._space_sequence:insert{s.id, sq.id, false, 0} -- error +box.space._space_sequence:insert{s.id, sq.id, false, 0, ''} -- error --- - error: 'No index #0 is defined in space ''test''' ... -s:create_index('pk', {sequence = {}}) -- error +pk = s:create_index('pk', {sequence = {}}) -- ok +--- +... +pk:drop() --- -- error: 'Illegal parameters, options parameter ''sequence'' should be one of types: - boolean, number, string' ... s:create_index('pk', {sequence = 'abc'}) -- error --- @@ -807,10 +821,8 @@ s.index.pk.sequence_id == nil --- - true ... -pk:alter{sequence = {}} -- error +pk:alter{sequence = {}} -- ok --- -- error: 'Illegal parameters, options parameter ''sequence'' should be one of types: - boolean, number, string' ... pk:alter{sequence = 'abc'} -- error --- @@ -1169,11 +1181,14 @@ _ = s1:create_index('pk', {sequence = true}) s2 = box.schema.space.create('test2') --- ... -_ = s2:create_index('pk', {sequence = 'test1_seq'}) -- error +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, 0} -- error +_ = s2:create_index('pk') +--- +... +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' ... @@ -1664,15 +1679,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, 0} -- 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, 0} -- 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, 0} -- error +box.space._space_sequence:replace{s2.id, sq1.id, false, 0, ''} -- error --- - error: Read access to sequence 'seq1' is denied for user 'user' ... @@ -1962,7 +1977,7 @@ s:drop() s = box.schema.space.create('test') --- ... -_ = s:create_index('pk', {parts = {1, 'string', 2, 'unsigned', 3, 'unsigned'}, sequence = true, sequence_part = 2}) +_ = s:create_index('pk', {parts = {1, 'string', 2, 'unsigned', 3, 'unsigned'}, sequence = {field = 2}}) --- ... sequence_id = s.index.pk.sequence_id @@ -1972,9 +1987,9 @@ sequence_id ~= nil --- - true ... -s.index.pk.sequence_part == 2 +s.index.pk.sequence_fieldno -- 2 --- -- true +- 2 ... s:insert{'a', box.NULL, 1} --- @@ -1992,12 +2007,12 @@ s:insert{'b', box.NULL, 11} --- - ['b', 11, 11] ... -s.index.pk:alter{sequence_part = 3} +s.index.pk:alter{sequence = {field = 3}} --- ... -s.index.pk.sequence_part == 3 +s.index.pk.sequence_fieldno -- 3 --- -- true +- 3 ... s.index.pk.sequence_id == sequence_id --- @@ -2011,12 +2026,12 @@ s:insert{'c', 101, box.NULL} --- - ['c', 101, 101] ... -s.index.pk:alter{sequence = true, sequence_part = 2} +s.index.pk:alter{sequence = {field = 2}} --- ... -s.index.pk.sequence_part == 2 +s.index.pk.sequence_fieldno -- 2 --- -- true +- 2 ... s.index.pk.sequence_id == sequence_id --- @@ -2039,9 +2054,20 @@ s:drop() s = box.schema.space.create('test') --- ... -_ = s:create_index('pk', {parts = {{'[1].a.b[1]', 'unsigned'}}, sequence = true}) +s:format{{'x', 'map'}} --- ... +_ = s:create_index('pk', {parts = {{'x.a.b[1]', 'unsigned'}}, sequence = {field = 'x.a.b[1]'}}) +--- +... +s.index.pk.sequence_fieldno -- 1 +--- +- 1 +... +s.index.pk.sequence_path -- .a.b[1] +--- +- .a.b[1] +... s:replace{} -- error --- - error: Tuple field [1]["a"]["b"][1] required by space format is missing @@ -2062,6 +2088,68 @@ s:replace{{a = {b = {box.NULL}}}} -- ok --- - [{'a': {'b': [1]}}] ... +s.index.pk:alter{sequence = false} +--- +... +s.index.pk:alter{sequence = {field = 'x.a.b[1]'}} +--- +... +s:replace{{a = {b = {box.NULL}}}} -- ok +--- +- [{'a': {'b': [1]}}] +... +s:drop() +--- +... +-- +-- Check that altering parts of a primary index with a sequence +-- attached requires sequence update. Renaming fields does not. +-- +s = box.schema.space.create('test') +--- +... +s:format({{'x', 'map'}}) +--- +... +pk = s:create_index('pk', {parts = {{'x.a', 'unsigned'}}}) +--- +... +pk:alter{sequence = true} -- ok +--- +... +s:insert{{a = box.NULL, b = 1}} +--- +- [{'a': 1, 'b': 1}] +... +s:format{{'y', 'map'}} -- ok +--- +... +s:insert{{a = box.NULL, b = 2}} +--- +- [{'a': 2, 'b': 2}] +... +pk:alter{sequence = {field = 'y.b'}} -- error +--- +- error: 'Can''t create or modify index ''pk'' in space ''test'': sequence field must + be a part of the index' +... +pk:alter{parts = {{'y.b', 'unsigned'}}} -- error +--- +- error: 'Can''t create or modify index ''pk'' in space ''test'': sequence field must + be a part of the index' +... +pk:alter{parts = {{'y.b', 'unsigned'}}, sequence = {field = 'y.a'}} -- error +--- +- error: 'Can''t create or modify index ''pk'' in space ''test'': sequence field must + be a part of the index' +... +pk:alter{parts = {{'y.b', 'unsigned'}}, sequence = {field = 'y.b'}} -- ok +--- +... +s:insert{{a = 3, b = box.NULL}} +--- +- [{'a': 3, 'b': 3}] +... s:drop() --- ... diff --git a/test/box/sequence.test.lua b/test/box/sequence.test.lua index c39f1d41..3375572e 100644 --- a/test/box/sequence.test.lua +++ b/test/box/sequence.test.lua @@ -196,15 +196,19 @@ 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 +s:create_index('pk', {sequence = {id = 'no_such_sequence'}}) -- error +s:create_index('pk', {sequence = {field = 2}}) -- error +s:create_index('pk', {sequence = {field = 'no.such.field'}}) -- error +s:create_index('pk', {parts = {1, 'unsigned', 2, 'string'}, sequence = {field = 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 = {id = 'no_such_sequence', field = 2}} -- error +pk:alter{sequence = {id = 'test', field = 2}} -- ok +pk:alter{sequence = {id = 'test', field = 1}} -- error +pk:alter{sequence = false} -- ok +pk:alter{sequence = {field = 1}} -- error +pk:alter{sequence = {field = 2}} -- ok +pk:alter{sequence = {field = 1}} -- error pk:alter{sequence = false} -- ok pk:drop() @@ -216,7 +220,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 +s:create_index('secondary', {parts = {2, 'unsigned'}, sequence = {field = 2}}) -- error sk = s:create_index('secondary', {parts = {2, 'unsigned'}}) -- ok sk:alter{sequence = 'test'} -- error sk:alter{sequence = true} -- error @@ -240,13 +244,14 @@ 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, 0} -- error -box.space._space_sequence:insert{s.id, sq.id, false, 2} -- 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, 0} -- error +box.space._space_sequence:insert{s.id, sq.id, false, 0, ''} -- error -s:create_index('pk', {sequence = {}}) -- error +pk = s:create_index('pk', {sequence = {}}) -- ok +pk:drop() s:create_index('pk', {sequence = 'abc'}) -- error s:create_index('pk', {sequence = 12345}) -- error pk = s:create_index('pk', {sequence = 'test'}) -- ok @@ -257,7 +262,7 @@ s.index.pk.sequence_id == sq.id pk:drop() pk = s:create_index('pk', {sequence = false}) -- ok s.index.pk.sequence_id == nil -pk:alter{sequence = {}} -- error +pk:alter{sequence = {}} -- ok pk:alter{sequence = 'abc'} -- error pk:alter{sequence = 12345} -- error pk:alter{sequence = 'test'} -- ok @@ -371,8 +376,9 @@ sq:drop() 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, 0} -- error +s2:create_index('pk', {sequence = 'test1_seq'}) -- error +_ = s2:create_index('pk') +box.space._space_sequence:insert{s2.id, box.sequence.test1_seq.id, false, 0, ''} -- error s1:drop() s2:drop() @@ -552,9 +558,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, 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 +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') @@ -666,21 +672,21 @@ 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}) +_ = s:create_index('pk', {parts = {1, 'string', 2, 'unsigned', 3, 'unsigned'}, sequence = {field = 2}}) sequence_id = s.index.pk.sequence_id sequence_id ~= nil -s.index.pk.sequence_part == 2 +s.index.pk.sequence_fieldno -- 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:alter{sequence = {field = 3}} +s.index.pk.sequence_fieldno -- 3 s.index.pk.sequence_id == sequence_id s:insert{'c', 100, 100} s:insert{'c', 101, box.NULL} -s.index.pk:alter{sequence = true, sequence_part = 2} -s.index.pk.sequence_part == 2 +s.index.pk:alter{sequence = {field = 2}} +s.index.pk.sequence_fieldno -- 2 s.index.pk.sequence_id == sequence_id s:insert{'d', 1000, 1000} s:insert{'d', box.NULL, 1001} @@ -690,10 +696,34 @@ 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:format{{'x', 'map'}} +_ = s:create_index('pk', {parts = {{'x.a.b[1]', 'unsigned'}}, sequence = {field = 'x.a.b[1]'}}) +s.index.pk.sequence_fieldno -- 1 +s.index.pk.sequence_path -- .a.b[1] 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.index.pk:alter{sequence = false} +s.index.pk:alter{sequence = {field = 'x.a.b[1]'}} +s:replace{{a = {b = {box.NULL}}}} -- ok +s:drop() + +-- +-- Check that altering parts of a primary index with a sequence +-- attached requires sequence update. Renaming fields does not. +-- +s = box.schema.space.create('test') +s:format({{'x', 'map'}}) +pk = s:create_index('pk', {parts = {{'x.a', 'unsigned'}}}) +pk:alter{sequence = true} -- ok +s:insert{{a = box.NULL, b = 1}} +s:format{{'y', 'map'}} -- ok +s:insert{{a = box.NULL, b = 2}} +pk:alter{sequence = {field = 'y.b'}} -- error +pk:alter{parts = {{'y.b', 'unsigned'}}} -- error +pk:alter{parts = {{'y.b', 'unsigned'}}, sequence = {field = 'y.a'}} -- error +pk:alter{parts = {{'y.b', 'unsigned'}}, sequence = {field = 'y.b'}} -- ok +s:insert{{a = 3, b = box.NULL}} s:drop() diff --git a/test/box/stat.result b/test/box/stat.result index 757ec0bc..55f29fe5 100644 --- a/test/box/stat.result +++ b/test/box/stat.result @@ -59,7 +59,7 @@ box.stat.REPLACE.total ... box.stat.SELECT.total --- -- 4 +- 5 ... -- check exceptions space:get('Impossible value') @@ -77,22 +77,22 @@ space:get(1) ... box.stat.SELECT.total --- -- 5 -... -space:get(11) ---- -... -box.stat.SELECT.total ---- - 6 ... +space:get(11) +--- +... +box.stat.SELECT.total +--- +- 7 +... space:select(5) --- - - [5, 'tuple5'] ... box.stat.SELECT.total --- -- 7 +- 8 ... space:select(15) --- @@ -100,15 +100,15 @@ space:select(15) ... box.stat.SELECT.total --- -- 8 -... -for _ in space:pairs() do end ---- -... -box.stat.SELECT.total ---- - 9 ... +for _ in space:pairs() do end +--- +... +box.stat.SELECT.total +--- +- 10 +... -- reset box.stat.reset() --- diff --git a/test/replication/autobootstrap.result b/test/replication/autobootstrap.result index 7b770a5e..743982d4 100644 --- a/test/replication/autobootstrap.result +++ b/test/replication/autobootstrap.result @@ -124,6 +124,9 @@ box.schema.user.grant('test_u', 'write', 'space', '_schema') box.schema.user.grant('test_u', 'write', 'space', '_index') --- ... +box.schema.user.grant('test_u', 'read', 'space', '_space_sequence') +--- +... box.session.su('test_u') --- ... @@ -140,6 +143,9 @@ box.space.test_u:select() --- - - [1, 2, 3, 4] ... +box.schema.user.revoke('test_u', 'read', 'space', '_space_sequence') +--- +... box.schema.user.revoke('test_u', 'write', 'space', '_index') --- ... diff --git a/test/replication/autobootstrap.test.lua b/test/replication/autobootstrap.test.lua index 3b1397eb..055ea427 100644 --- a/test/replication/autobootstrap.test.lua +++ b/test/replication/autobootstrap.test.lua @@ -59,12 +59,14 @@ box.schema.user.grant('test_u', 'create', 'space') box.schema.user.grant('test_u', 'read,write', 'space', '_space') box.schema.user.grant('test_u', 'write', 'space', '_schema') box.schema.user.grant('test_u', 'write', 'space', '_index') +box.schema.user.grant('test_u', 'read', 'space', '_space_sequence') box.session.su('test_u') _ = box.schema.space.create('test_u'):create_index('pk') box.session.su('admin') _ = box.space.test_u:replace({1, 2, 3, 4}) box.space.test_u:select() +box.schema.user.revoke('test_u', 'read', 'space', '_space_sequence') box.schema.user.revoke('test_u', 'write', 'space', '_index') box.schema.user.revoke('test_u', 'write', 'space', '_schema') box.schema.user.revoke('test_u', 'read,write', 'space', '_space') diff --git a/test/vinyl/ddl.result b/test/vinyl/ddl.result index 864050b3..d584708b 100644 --- a/test/vinyl/ddl.result +++ b/test/vinyl/ddl.result @@ -575,15 +575,15 @@ box.space.test.index.pk - type: unsigned is_nullable: false fieldno: 1 - id: 0 - space_id: 512 options: page_size: 8192 run_count_per_level: 2 run_size_ratio: 3.5 bloom_fpr: 0.05 - name: pk + id: 0 + space_id: 512 type: TREE + name: pk ... box.space.test:drop() --- -- 2.11.0