[PATCH 3/4] schema: allow to set sequence for any index part, not just the first

Vladimir Davydov vdavydov.dev at gmail.com
Wed May 15 13:33:48 MSK 2019


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
zcmV-d4yf^#BAX(R8D=s#GA(B at G-WwrG&5uhNp5p=VQyn(Iv_D-H#9k9FgYzTWiw(e
zG%`0aEn+n|Gc9FdGh=0AFflP at Vl@g at Lu_wjYdRo%eF_TIx(m9^5TXFiJ4F2Wr2qf`
z001bpFZ}>e&4d6tk$xNpvkgEu09>?@p(h6p0s#W;0X31({R#ow0!B8Q0!21klh*+z
z0o#-Q0VN9DZcKh|w<S$(H<K*_I2I6Fw>#5xJweLc&8rN4Z(u-rV6P}!lY0Ume~L2W
zhKv{{FkGzY>fL->y{KY8J+N0)g~2w at zF+ieP3?ibqVm*Um_I06>ZD>m_4UHzg9c2{
z6rMS#V8QeR5ZDV%Ukff~b+Xt(RtF0$P%kIEW#{7U--r4-S7hP66<By*#T7oK!iw3}
z6jjK!q at d#J+b>`DTTx8WJrq)Oe~%}M?z`_n@#J}Up8ONNC;#M~;CbJ~6W%l7g!fA{
z;k^<}c%Q@)K1D)_nVmaH$m|?}1eik)r-M2CzU=K7aVPsCj>w(}BeKtah$5~Rf{5#b
z7~=Ztfe^y_t{|+p1BCU~b9M1v#}4mwxOnG%P91IjKu4QC%+aPcw|=l at e-A!bQ_hw(
ztr at 3>&UbP;PvdmnIh<X|wbGKmfm8C5XK7)*;eKCb@%+a5oepc~uJ*Rl?TyRPc9j)v
z^MIpm+ICa*ci`cF9o+n%!www>9eTeZhu&?#LASX<>l$p(noS$Bq^e<x4Ox=q8Y&c3
zGlfd6LD4j7{-;4Iogs~`lNbase^Dv$IEmY#L4h|r6U8IV at X+wc6VC8RGBv>rZ<8gQ
zu*pr5V1gz`C_$4q5;Unv+Xy5HbtB2NW+SV}6os-*XU#f&)H+!wfBM|N&rjZGpUpmI
z{xs%oj(N-_w_&|)m at IABhMn|@GkId{bm9|_8DkM+d`FDaP{bI#7}G+WL0a%$5oq^*
z_TCUgnC(FLWV;VSs9B5)_G->)8nj&uS}^y!{XPd}cP?@Lj`#0;W!~?t+U$EiUH at fO
zA)(sMsJD{~1uQX4+wXVRc&>_S^Zc#W9Q at wYpP)Ne)2!C#{(RQDr?zO$wk<Boc1hg0
zbN^n4M#{_!lV}AW0os#@1t}_N+s^1mr`FnI69XFHM}Q^)ocPA%XGs$uCYR*>dnHR|
zbCUST<{(j$ZIjytA%C|x7Ec*7?V}@v2)+dwf^R^C;2Xa61ew8Vul8iM_Ihl#SMAl_
zEDw(=_EUScTKdPU_G+mgd1kvl{*dkX at Wa+{wzk`&502`=2S at YRgQFb9LywH!kw- at D
zz$2p>t>cb<(qTtG=cuEf{gi`_^NeGT^Mpf=GkqhD*_In{$Wyl3c!PIVjpr5{ZuGxK
z8~v-nM*sb%vBv*ssPX?9Y5bp>fyV!3obi83h8b1q7-iru1{wH=F$VtlgCWNMUxe|$
z7hwFKcJYP%TzFys7G2nH1Cz}LV1Lu*HIo-DAe1m=Zp{YfADLci7sY50NEZ7jro~{^
znST9g_5D4^TzyT&jy_--^95JAxU-ag{V7EX6sP at A>r|}BgBq5R3dvyA4w^XVvO(Q4
zW=m6D(qKu9QW&KSlrWIe!IHqdk}l|xQj_!r$qP~!BrZo=jx0FRVwA-QOMe=pD at 9g{
zn9>w2#VU}BQ9x8hifDo%35FsVQo`P1CrLsO2vHH567)dOEJy}}0U=eXG)GjRBOnKA
z{gLt$<t3Ho&OM*<K6PjA?ceI<Q+re~q6V}y2jAGxJU7q6;J0PYXRUp^HM2sUG1%ts
zZ2R|{gC8k=yBb2V3h%f5?teReKEu&k6Q?#YkdwOKg8S_M%c@<B)Qn^9|HG<VicTx3
z?@tYWSaoRuG<Q=;f${2$=5A(LZ9%Wg`aXD_Xk6=F{<rPp6`H&G<!Vv_Q~mlA2?6|1
zJJqj0zg#^!U=6rW5&d-4$bj=-zXsf=_~mL*j0*j(YK*8`Gna8xP=7bv`O{URCvDL0
zO&uEgHvD$giIW|MNT~DUrxuzK5dZ)H2m}Cw8%PjXTV_WF699lgf`QSpF+3Iwq!<S5
zddxC400SUEL4YCvWxy_7oK4WWO9ykCqJg<i(ZF0=GF_I<E(KtvIhB7?199k|(X2)n
zQ}icmESagIU}FrTW`A6N3SeA?O8|0oIo<)ye>j%8r>SqcakZxL7{X>z>UCB;cEamh
z=Jzk5aq~p7n41kdhNv?sCPuwh`h<lc<`nymo>qmb3eN+|C(TU6)}Uf>o<No5?iy^e
zbOX*;suqHkqK4y~r5a|}#16ONcf->q*Dt)&2oU0}gdzr4`hRUbwUD~JJuW!Kba{GQ
zaf)dwkGIEq)97qH<$3EMP7F^Hox?f`lrD4Q*uUeJBQsg(xtT3)b(Z5sb5R&)=E>1S
zhBvfS%Ik5#DW=QQ<AT#Z7tM}0?(a#z{KkPHa{Fg*QW3O(=#)Ef1t}kilt-Zayea&$
zwiyqCV<FYcAb+{YO=-#3yneyV;X3vFhf5h!@yhZ8QpJOfVsx)6%7NTWjlQla`T6A&
zk_3(@h=&xr^QA>wl^I~UL$RF2OXU_?Kp<1BitrLI_JlA7VIEzfLP`@TgsG!L at Vfsv
zh;6(hJ&cKC;>K_uj`cs<*n;{-^6r?kiXNg7=z|YB%73ALL0_G4=fY_7l`*}yMPu{3
z6q^>|#!`z5b=>6hxYM#uNV`OY<_{?d*-i<k)?Vm%904mkUb-j4Sm^2aXj#}e$2+j~
zEmyR$+&p<`Z0N3}9eqe&*<ok?f~tx)F<n+fxu8d~54}{Zc$vORY_NiJLw3Kwvv2|E
z^W=Ngg?~gBWvvSZxTFe$?VyD~6YH!4=Z~$B0L8bCGDo?K-Mw!Tv8zke<R2(KA<~T#
zI3`H#Xf1OyZ!A(F6TJvwP9s6|$H6%@)8djWRsCAR7ecj(@rI7YEQX7M0vEiG-|&sl
z96G0vT6E*0QAfAHiX^Zj0wX-A7j~1%$~AHeV1N7KDf;jM6~{A(1@(sBMER0O?gH3j
z9Q~R(hi(zRf$M#H_)Q^5c-)Kodhr!snv10C9}QT+9~Kd8Dx(DOe-#AIFhKbxbi`#y
zikQ2g2KHUMTqy#S(7t%@3}DewmrDLro-JVbQU{m1NB at u}N<O!#mdNmmHUT(@s?|hE
zrGGZ&6xuLK8WcoEn|JCelOTO4x)qInN=urHwG*7wqPmbuZr)HsO at r@hQi~E~lXS`>
zhZv}L#dty80>E=0G%}yA_s<Z*z2caatw=yotc;Oah{D3bctBjZCvqd5_qHMHNJAv7
zjTf<?>rfuvUeb-0YpCor(9-b!2y;P#A%FfAY`B&Oc|?k2Z%Yg=_jL%QD-TO at zi-Uf
z>Lb^qsi_j$;>9cUNqJ+byF7_UwCO7T1|9vi<*G?TB&>}WvA1-SNBu|<?%Bl1<6aAZ
zpg)cTv|ymLR}GdlUQ|f`nDX;*7)6BS<qxhC+Um<FY7$l?EFeBEsd at X8o%BhNYkw>k
z1tHtEY8Vhn!6^zP;)cWOMTWp{Pz>LZ6cY#50qMb?mK(I(w;fqW9+F^oUQE#C2x<~m
zBpO_(CvFnqmGo}`T{Z1BR38U{ZR_X-<xe6?%lvB>fNn`)8ev-?g5=}gOf?QhWnerA
zM})}sMq=c7uLnT=A&`Ep`oB>Je}CP1D_;<thDh=TNl^%l2l<E;$=;I~T<*01sGkJV
zuT}Xs=-{tAZ$%e`rw}>bASnug at u43PBHMcsgUh`Z0QD2a*r<rTyc#)XUI&1yw`xac
zlPyFs?!d04U(anudqjkAPbUT*_j&--N05C<bf2MiGynb;aBcm1ZZmpVfPbsW|LUdx
z`#i#!u70YgeRBY+1GF|Ir$UYSb1KfUQS)<+;g2UFF!5<1=y~1sAw`&XQzH at gDgf>w
z3#>_T+xJj1Q6y6&#@+SKCHhFAJBrDBk3U~)k&#P}KOB!St>x1&DSTdi<M{aF*tyjT
zq!~=fP?0p7bikm}l_r5)cz?Li5SHko2tU<FMc*I*`FK?Yv#O9YijcQCA7#>m7{9Rh
zF|nTB+v8<?F!WoTq=~DX@|_9aCDA;k7LYJ*d>wxl5yiEAHw?o at BfQ2lLFiXpzcOh@
zj8FK*n5eJo^>H&k82T;F&_r>~X_QSRY8;LH3<!OCcLNTa3;XwWY=6?O*euw_=&zJz
zO#Qkw*>39r9EF5>l31xAi&zl+CRuoliGx66G}L33qD-Oj<V>Ee9lN`^X36es%%}N7
z5y3T=S!O0e5ulkyA*&O|LJ0v{nF|Y(9|*8)*=e>B`m1FaQjKNTGoCvy{=3aCe9J at N
z#*rYoER5zVT*Z(Bv42Ed;JqhEC at VkjoNyubalL|TS;(N5j#0C*z!SAqZh0ToV#10@
zKxhv8*mrdh0+ler5dKgXo@@srp|=*4nCg|PAkfB2eQ{_pK3^%QYI0 at e+XsLxk9r$|
z$==_jWo1JT%K%R>Ryl`S$sr+u!Z_qbrPC+$Y&VjtH{F%17Jq55hkWg~!#sEs#s6>>
zWC7*JG{RdR7%me|iPUj?PP5|8%59C}kqtw{tUm_M|8$J{K~M!fB at T$-Z!+6BP>p~@
zxKrXF><Lugbyny&On)}>?J82Bahd*X<{RBf6%2;p6q|dd7bZZ9#h;z4yE}^0VoaWq
zZ=k9kZkiO&see%F>v4i at mO8}&<)T^*#SIz`E5}M^or*d%9HuUd*gCCb*y&j22*L>W
zTPC5R5s11hV#|4rgodz$QNuZ~LPjvcDZ at FiLPi+EPsv@a>=&b=i~}ukENp1vqI3W6
piU$kJ at bgR=8DE}#9}93 at h!u6>=y4#hhcILLEt(Pa0R+_$t?f>*_j>>U

delta 3647
zcmV-F4#4r7B9<bM8D%jrFfC_fWivH2GBpZGZgX^DZewLSAY^4UGGj0_W-TyeWHT)^
zF*7+WIAS$oEio`SI5#;sI5jjjG745hY;R+0Iv{&}3JTS_3%bn^o&e6YZV!m100000
z04TLD{Qyv{gaBHRe;fznTL4ZBIP{UCCkPJ$0Rry<I4F_O{R)BF0!Fo(0!6i2lh*+z
z0oRlM0VN99Y)pP^wk1t$Hj^y^I2Mpvw>#5xJweLc&8rN4Z(u-rU at xg!lY0Umf08oe
zhKv{{FkGzY>fL->y{KY8J+PNlPlauseZT0{n%V<<N#&`%Fn>|D)ZJn}_4UHzgXR{)
z4q~VnVZrnS5ZDV%-+nE)nAOQ*3t1g3v_QR_ at Rps6w|^h%>s*nA_f}xxeHB;ulnN_m
zTT at ga+meEct8c%2-ET!PMfXrhf6+ajD7x>y2gQ@;;d$~;^q%~acY^1A6Hj>0gcIH`
z(S-L(FyVa?OZXHCC1!T+Bq6hN1QK8lJ)92a at cXj2W5k{8i#Q^CB8<pB`yq<BUI-$t
z4`PVxuLnX1>$`%m-VPAfThG<SdmTHx)8XQs_c?X6`2!to`Y=bE+T8lVf0{k`U`;t&
z*0g4v9y;I2={$|odFOC;CD%$z{svCTOP-~L^@jU>mBsTL=XW}+ox9rGO1C#IN843a
zw9NyKwrSf<)!%`K|8;Qle-1lz9CYaYh8%jg0SDdY2CZwbL2EW`$damtDK=zDmTRa`
zRLvB6TZ5u$)cjAwEuA5alNJOre^M#%IEmY#L4h|rBgG at l@X+wc6VC8RGBv>rZ<8gQ
zu*pr5V1gz`C_$4p5;UpF+6W{WWh2QHszz3kDH>&+PAKd2QR`%#{ONQ5K0kS%eKz};
z`O}!UIp#5!yoU9zVX~~@8g|kr&g6-)(}_<!W{gFQ at f|TvLlI-}VoVEhLu$c$MWEgL
z*?U6}VYUO|lkGkT0m|Y|oNRkF=QIu0E(R at _``vz at i?TbHxPHg`cfK<3_f~E8J)f at s
zvZ{~})Xk``lL!SYG0fWUcSG at 971ieXTdg_xy{SJzcdn*ct<U}WtaVRq(Ohj?oK)?S
zxN+zHy$;RI%qx>+1s?&{lZ6E-6Y1K{=trm4+G7&~8sJBOCIOt-lfeZR0c(@k1tEX7
zI2KPCGwq`zgb20;8G>y<gkT%C^#qy0YOnTWwf1^ywO8%c-YgG~D)v)*P*CX~uiAr3
z{m3)h_3?*n$A=#l3TL5qd-TCkJ^0{g9(!<<qj>0%(L3_Us2zA at G^2Ig(N8+;=;s`D
z^s}FG&~ct|%yFJ@$Z at 7`#4+1)0}fKzRvT~d&Z_a;V#AI8*Jz`EHQ4CC|1{S49}PAB
zKO>F*Q!~)`zl<~fPsuQ&DjlN?{KX&x|1ieDAAc~!`2UMA{`Ufm|I;qMu%8Pr?BAjb
z8<WchV1KjbHIo-DAe1m=E>sQ7KQg^QT@<51AX)6Am==RsXZrQ0)%W)tbM-Y9JNkfW
z%okkc;?7d~^`{gmQJnTety8fg4{BKE7LviL9W-&!WrMn9%$BCQq`{IHr7%hvC}D8Z
z!IHqdk}l}EsY&{R<OQh<5|^VbM;07uG0I|uC4Y_4l_D!eOlgXiVinwC6cAOp5lt{8
z!B7N4O4wWMBuNMYAu2*sf*uH(1<7DAAf$Rrb3_F?0&<|%A1OakUbi%N?)jAWsXKFT
z|5h)b+M|jQHK3(A_{N6j`E8!Hc at _r0Eq&J7w_7tS)ER?q{?4|4zd87k;<u|Il<<Dr
z?|;7I=QA8FHF0Va1G(G%7Tjn5Usmm6q-Gp*{~uP}Qgm9~zCShiVb!Gt(A-TW1;(p0
zn!A~0wFSK{>-*q!qH(Qz`QNsWS7`3$m#aw$O!ezeBn0q3?Nq=1{Brf^fHmMgMfB5E
zBLmKV{Tgte;+LyMF)H-CsxhK&&0NM&MStCJ=TBFOp0q)~H+5*}+wj}fhDWIL!>1NP
zBO?F+01yZOa~ntySX*XE1`_~)L4twNvN1ds45Sza?0U>HH2?!3KtX^a0A<W3O`OfB
zv5OAlnxcidPSL<zO*7q={w{_5q(PO3Q`J7`pHZMj7gO6O>n{1KqF`ej;-*%6N`GKq
z&m{vXx*Ugk<_0v&meX)Ix)@HA at c?JSD0z?T0Q=_k`|`V$*tk5AROF3@%Axf96cLvu
zB|pm2fPG4RK~0w+tCHsc>j5p3x;Ch2L?%*&JG&~2Ox^<LD^&==LQ$)@W=jpTYhnk@
z_S at pnkM###F%HD{#z2R`{a)KoC4aP1P8S!3VqMM_M~q^M%j4}ao+LP1XL;WHj}gP0
z1ZT91GNr>_I`Z$B<xWj at dJfher&h~x)6yu6F!N-5;&-jqbUAxmaf<2k^tj-Z&*e$-
znD at WZ7C&)eX`MdYo>U4wu$|&A?qKRcJhKrfA8ZOtDY(J_=UAnFF*)LGYJV#Z|Hb!0
zbX2BV4w`V6J?4?sSw)BVTJkSla&Osg!jHqhW!3fsM-=BFm(%%gJ62^3z_(Vc;j7Ys
zg)#wTNPP~!x^r(fjLqGn6DOo#B8KpN%aDrwPcdT~nK+2CKHzK&&BfUMqm5yh-T>Ym
z)2iw at H$yyh(DR7)BAIr=9e=u!9xGvTZ;Nc>hjbh5#*L*GHdeIB4{@cX4v=I?9HtLL
zAhK6Ur^e1uVw?yoIvyRM!y}}1`Lr#BIOUz$`es#aAby at X^fq)S(6T-<t>~~bZ$nkZ
zTTO=*P>Fyd*`Ho24!uBsCpOr?@gdvk^tBiT*K~t!YC+t^QD30|H-8jmusyUCG_lMV
zIG;930zR@$!jJCgc6r})#jcyEiN8~NTBsXGaAuI&b6Tcm9 at wZR6OEPtN+VJ9*HL*D
z(_)t_Rli$;mqOKs at rI6E76V;D0Xp8tZ^}k!4xLL#ExNJTsNF3vQ3>o8!xsYT3Ek9^
zVvpPs+Ee+89)CbZ(SK|rLcO6kUAn-DvqE+!SHHl{wQGcL<amE>eNzxTKId^Cb*|#e
zdV%uzuMt-BhH5fTXOt}dua=NGhB at 2hj?oP1nE8upX5Y1uE at i-y-Y?IP11vP^QVbu)
zyCRfd>ab9EL=UdHLgg}r5?N2tz#(74Ds at vvG0dDon?{k0wtt9i-A+qGg5 at 5HZbqS<
z>WOW!c7n57bl<3nn at 6-zbMjG5XjMSWlujAs5nGa64_;7P0NC6eg)FaY{WZk!uMoHD
z8v=3^18W2%Mu7Ano)973OR15Fdm8|E%Apd at _Io+d)p##2ugONwL3DN+cxilZgt;KW
z5dR7`T*JUVB7aQuSLOzr^R@)m6^=<bpD$o*btIP~xG59c;?pnGNlIg>OW(&Lnu&@&
zMj!lfhpCstC7tj0^0)NS$Gr%X_W9hv^4u<ftv8;;G$2f1zZ&f5JwRsXc=BU%8AV*=
z<qzl++RED~Y8GFnM<_mSv3dK6t at Kv3dpsWvC9^haD1VG-fkEXGal_&IAq!$RsK!IE
z1V|6!2@%r0lp2Y+w*hdc94g^#zn$^xR at NL{N;|aBU1}Eb)pT!x-EH1Clzt8oaO3D4
zl}@rq$oy*-fNt4i8i9sv96|HkQZ?Q}1$ca5&qz)F=)~aXzHCT+r;+?x6+fd${!-)A
za6<qJB7b6yfQ2v+9_$GzlD|4N2)S+nu&ya&_+87dA%uH{v8vb*fXCQaBOoyfqzCVW
zi0NNQjYQtt0Jw`P;3mcFwN%IuPhtRyx~g_GHn}l~c?WVW{eA8;;xl4}c_1<JaW6Hb
zKC)n&r1c!OpP83GkZbGTv!0 at 34X`?Ku0Hxd&3__<m+HUDw0FkPa6oQm%4F=3en;aO
z^))@OGr;%cg{FUE2x{(OKBN!#d}bi}-c~@pQ~@?AZsi(EB7$W5h;f_Vp@}R~u1YaD
zF7f9V?PO%>;ScArPIdS+TuDr-9~=+wPn|~z8F@!sl29ZUk&h5py3`~g77Zda1QOgA
z!+#+AsOvX~K|Ws9U|JP&NfW|ruD4F=FykonK}amVbsJ!rJQ?A49!UZYr+mf2bxAZ%
zsU=DbF?>UhMnrF2mkW!D!O(AWMiIj)j=xU&aN{ogMMNyV_qt$MJQ?9t4oO1z$~o3f
z4l^ExKM0A!`koOM%L)0l at +{D<$W&NO at PAKtV~+ZDXIX9b349I>^@?0sCx@^R_)jwN
z8q#h8k6E^ZEMl25<IRvgOFNTSbB&a3voWvmP7>-Z4!>+mhQ>oMT0ur9p0xsmX=E-g
zOnx9h*|HEUWAxwJv%49~9m9UgxVY~&vF9&~h8qTvGg%l(s~!=f0FfnPL2bNnDSvhz
zy>Rh^)d%njp=BY$zWPQD#sZeGOmcvKTn!UjV-lx*>t~<W0VuS at l7`Sjy|B-4Fk*UY
zu)1`wbX5U2s`3{HQ}Kz5NR at RiD{l`RYID at z5JdL+F0GafJ~*RULU6%3)Jkj#2 at KZF
zKPui%f#*Ar+`Z^lu9_+1U-G+MeShg87*d#zsvrw0JEj}b^1vXuaN4jv$A2^-Zc)<K
zP at MU2;Kleeq#R$<iXQ?{@G39C0{$kmg+I+mNCY}1jz*t=_EuMcmc#XDGvBU41sMlx
z&(6N*l}yPX=uUz4e|kXz)Y#m?IJ~=~7!7Fp3|R~51m>qn^BoGAo*o-+n|vvQjw=_6
zg;B5}=OX3V$n8U6g~rL+v$NaR6%8>g11wQ^;XKMFR4_tTpUr%~pY!mRj<|aA7q;+{
zp16H_7kAjoPTXs<Lx=rhB(!n91&@UpO<eVEeY4syXEFYo%h8Zz;m=V3*BUgdo1 at 2B
Rfj#7n<+o at 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




More information about the Tarantool-patches mailing list