[PATCH v2] schema: rework index sequence API

Vladimir Davydov vdavydov.dev at gmail.com
Tue May 28 17:17:37 MSK 2019


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
zcmV+^5!UWgPC-x#FfK7O3RY!ub7^mGIv_GGGA=MJGc+?TXJjxpWiw%8G73p<b97;D
zV`VxZVPZBpV>vT8Eiy4TIW071H83q<WHMzfWo0omHe_aFWjQ%w3RXjGZ)0mZAbWiZ
z3e~y`y3G)<0M6I{4SJ;j00000D77#B08l-J0E(0NAVm<6*f{_U01N<p8IUZ>L;9HZ
zjmE{UXYp(U&<y|=Z7EYylPSr_mXef7<kUYsqF#qK7e_nBNEDs50(8i_YWG`8{x|Ip
z0s;c=0l1pQyAD3U*o+4lm+1iGlmj#8xIvk7)PRF?JV!Dc%;q#{Ae)l`nH&?AuZwfW
zG>M{^3`7xwF=Bdw7`+RFfox~N1v-nk9hfjPt}hm3(F>R?%E??^zGRV$mn>G$C5u^1
zw{Q_fEft7jmMx-~V$t&dl`Q`=VfjB at x-#pcfXT{IX#4j!j@>-}9NPZB&)=>p6ii^{
zUMf}Ym0$vXm8kHWBmuwtMxb;r1WET!iQwM!0SIV*H=9E9JJ|y?AL3|!hcugY9m!-}
z4j?r#AvG~|NjaKvMR7pJ)qoU|gWvgS$@1NBi{zYkH^=7pT`nk5Y{)<ZHZ+1_LmL(`
zU|Yb*W>cWZW=miJkAMQ+ACQ3e1|VR;G<JXmQz8Q_SY^TVNo=+?L?qjC^5jZ)8<s7h
zAzy$K?+Fm{KB#~VM8Mty1nn&V(4_ at 1z@_~NaA}v;B!JtE$<OV!q{;2(!^C?fOT16w
z<2 at 23y&p-^yAdS47dcXv02nDtB1Ov5rb7Uj2_X`04ql%n3o=BsRY?95?Uw=(V(WHi
znyx2EnY($F!4D1$NDu54W$QNoem at re<G%fSEHrP(VCjOZH{@X6=T)VO{q*#}UQuV>
zlo`Vmrppywy_;{V7gg-12lk4pFxckV_lsV!sXee)RG!)k^9N;1om9-HzFtrGq|t(p
zn3XJ4uwZfm2<(+6-+nE)nAyo<3z;1(v_Sow at Rps6w|^h%?Oc(C_f}xxeHB;ulnN_m
zTT at ga+meEct8c-4-ET!PMfXrh(ft!qbl-gsiYL!|;>kPFd-6}d37+>%IN|*gO?a;a
z6W%AWg!f1&;nO3MnBBpHgzSzHM}Rr>aXOg8 at 5}y95qPpE!iemLC?fmpg&^YkAcnXe
z2qCV&{)Zr}?*hX5J3m-&Jy#d+cJA<Ahl_XK>DbYx4|TNJ108K at bIS*7^3a1d=WJQi
znsRvPd^e}_R8Hre$Jv!!D=j&kI3+K6mKGKq?)Oy|&u^UH>9BV0YHusu-nblXS6R{a
z4mjGT?Kf3lhaLXU;m!Xz=+JM-p?4c_=)Hy;v>O|=rlAI{+qfZ1DjKHRkR at 5Np+eC$
zQ>YXh6jih4{~4rG8Pw>iG=>?rhCzm{Ux;DrtzCejg%@Ny7F^(w8b|8g3N6f3V1Y-b
zDXhRFQxsH~oxn>$7KH+jlek@)6L>Q;Q9SYtFAa}00S%8N(-O|`Hc5gBo806GC1_Fv
z5;S=mL6e%ai9nK2H<C<iHnNILQ7G$l)~wS<t&?@~r_cTS{N#Q1+3aKHPh;NZn8#dl
z8`j%~$<l^x*h!x_lPAVbCqD6*F%~h#cf>diMU26VF)hTY1 at 8@kcJF8J1tEml?t at RZ
z^8kdJ#Q{#7Y<o55G!5D=1}&KT-F}~gvOAZ!e#iTFzB2FkR&Mq^pRWJ1s*q6aX4KpK
z75ZJ(paQ-vI~R4M)ne}NlY?KVZx*$79{1I?`E(VC0MnK@|4nXXL9TVC?f1KDJXb}v
zdHz;w4t{XzPY|A~X;$lVe?Dv7Q(H7=+ZGpPyCiPhxqq)qBX#Bl^~+o4Y<V&0zpLs~
zd$xC*yV^W|ZhMUj?;f+?C)PD>-zv=C1+D3^ZfUnwnl&}dpLy~=Vxf6m%`pjDP0PCf
z>KlIH?fr^3{IqRn^s7^A?X`&k4e%pClK at V9WAd}4i4T)Y^8US&B{Mond}MTxD9N^+
zO*%)C%(#pok#Q9{vXddrWVDeB03)+HM2g6U)L!l3n;eU$jG6Y;5kdstf(*eoAVTmB
z-+F?~V6|6!vRZpRHruQAYHyZ at M-}_2y;?2(<5hdL)Q>!~T_1nQc6|6 at YdBlm?a>EE
z_27e}dF;Vaj^d$5M(@ZYqjunt(Tvt{M?dMXqn~rs(a(O$LC1N<F~@nrA;+1%5yx!H
z4LD?5ZM?xdtHyJS4LAB<qmBO6V59&3(^%twG}QS2j5PjF%|PS-GS2uvCBuxWbc{0a
z7lRD^!x#g9{J{|8|1ZM$-wQDQPrLZSelEPQe~T_`Pk7x+JtfSGRw9v{jB*SQjWtMw
z{nTC#cI!SxELfe2CDmS at Jb9j|Akgdje$l*7EE*8lt4_s|Y`^B|bM;9GVAkgCTD9l9
zns+NOYuah*Dr_otRC_y at q0QC$L6uR&rp;@nFIzw;V$9r{EzCbMztk>@Q6P{k_EAiW
z!7McW`qS$BdycvKnu;BHz(fWNu5xi_DgF9WiWDeL^P$$MSWyQxG$R$3!KxfIZQw<N
z%7qM<rn;2D(ikN%N*E|zAf<yPg?S}e&?B`b2 at KK~Brix^j<_6EaJ0opi%}LeMp%lj
z6fvnOT8dF16|+KAMT%&GAu2->3 at Kr6v6CdB2!tRIniBLt&@4y>g8?B`DK$n^pd%m$
zYW<P&6XhkP2G2d8@;-HF?(N^|<x_i9F`@>vGza0>&^*7*vo_Dd;J2mETKjfuW`#Oq
z(9Pf3_U|_bKT`a5HH2bKtMGo?@4n;ba~!QHIkky_oYegm+-Lt^R`p`EW*l?>A6Dg3
zbXrM$e`@f<s!I!?xtmG~j8|tgcQeas3wmAF_rdE#<68Ifzil6{(A>>0SCbN$>eru0
z2;hI(seb+W<?7J^Y`}er=%=ej2AuzTHsC(RFIS6VROokAV?^DWxs0QNy5Y{Bt`a?Q
zgMM)8(9pNxx2pk$O{nv;ry4>dBLDyZ5C{NM8%PkCV`fPP69B+L!hzAUF+3Iwgct_w
za?CO{00SUEL4YCv#fK_gq}mG7%VqzvbKAM?T=s5`NU`O<!$EM_!r+^s6%_q58rSGz
ziU9?;tFa0tZ32DK6g;RLH5|ICQO0G<v7y*tXBkSJ2B#a-Y=TGo97YN0$su4Tg1&1G
z&b1q#Cz6YmZFn(69liq5>K4<dEDSMO*|&ZAI7U@`6A&Mi6C&{jjqqPPRTgCz!%${-
z&c0|gVr(n4{CxJ-G27~Un5^H*=dZj?*$FR!Y-|q~AABpc-N{>N6KtQwR;Ep$e+FBT
zjuY_o|2lTAOv8!1clakp4LMf8zN;zJ_zbsyfnW4$D9}U2j}__T1a3O=gAu}>j8beI
z8z*g|?UVS5^@+AmVyg+AdFM^&_ufZSD8MY3e7ZZ?k263sMeUqH#_*|exSJn2nzoc@
zJ)i`va%Q6-Q- at -ULhgD0fh%vc^mavhMed`})&glQDvA~vuYh~xMQw`9!Pi_l`$#UB
zlfhIb48-C<d|Dgnh%!9n@;Z;C*Q&e$IM>A*AS%yWC{I9U?+x>5qVHqC*i=6{aY71K
zY6xEuhxEt)xEb4!&xaWE2=T^nE{^^2^;q%rX7KKqtx=CZ56Ip@$4}aeq_-13=|;H4
ziOIdKvW-u#Y~T?Qmbh%IX%oLbO-r3&$&$#@ABI3=&$UjC9nxYPF+w|DI^x4Aq;>zK
zSZMw7j#7OysW#9)&wPFxx?`wWAK8R<*qOJbta7ZT!<lw0xUcNDIu#RMpihYnOlsyJ
z3pCO!{GV%v&N`JB?jlQ9sF!P18QjLW1x at _(1@=vCB>|#r|M-y)x_#9*#n#mgY4RUh
zuLyM`Q|Jr|HK&qlPy%aVGSTP}<`m7wx*Ua6F(tOia+RqiTr5=KgE#TW<pXpD!qD+J
zeiH{kb98A!YU##ei{b7MMJ3P^VaYnPp5jhjN5qmVgs+{i;^l`-YX90**2|4PJ6R{o
zQLdSgS0qg|_$LDF at w`8GZ%{Z$9`|>5BcA at TeX+aKM&ldvgcZUlGp;fJr$$^VhMaAr
z#B at e!z`6^9)w;8jdnMpn?5x+!01KVE6w!yNub8iwy5`i46N*Hl;C)kOMZTKv<P(Xd
z`m5S0G%2UhtI^%-2x)&@Q`}7?#Y54ZDBbp-(7I?R#7>I7AUAL$7#a%oN!5XHrG+eh
zS{8yZk{AZz#nlDC)}FM8>8h^3(X>M^Vd_pIAcb-A#`jnZNf*lz5yZlo8(Cj$1y+}w
zEWzBuqy=4rChqkT-S|3AWT&haa1lnBi*^k0FJr@}HOv#jXoFX3AoAcx)Lmk-1a|{N
zwpMNmGlVsDp)GuToj%DnmQ0JD8l)So>fg}BLWS5hX*o$}19t2!-UJa3;>AIg8|ge)
z19Wu9HwY~luk2LAW{rm#>mIlK+#ZXIki7h1al%^(8_Ju52?_wkhbc;F!$6cC1GzA+
z^M=^NFg56iq;~j7M7)yNd<ZKY_KCsIiQTYu+#tP?ihd12Exd|dla@(1A21^fvy?SQ
zm(&9<bQZ2e#Dv{jV22HRCEcInJGXFj=kk+qPnjRx1-MvFOygjzkU%hx(P9nnVo4lc
zfCJLCK`Sxvd9XL?4jQDlQmL;&XoIg|tDum8##ph2<FOnpAK`=)(qWYvh`#s{bq^g<
zSgqC9Ahg5Bv^7vjKx3>}!|_-SmXB~k3hA&)4Mbo3h`Oh{Ku&2BGZDh`?-l^JlB!M5
zCT<i2cmiHmxgHPj5(e>1R{1pw?Qk(~4M{LY+6H)vGw^|itVVucndM-Z+fk7=&|8cH
zctUz%P$>bJ{)|H90^HDQ)1arjIiWyDEdF%Ph~|mlkMXGj8Z3Qw2xGNYUxUyNAJf)Q
z27oD<ZYLBh3duGSLo~fx5IIt=iehxGGEW%VC}f#p4 at YH~0!C at L;Kr-n`X3$~JB^wX
zl2%sImn65{N3T^zZ|@?p;T9Uw6aE)u!TY!iFf1oOUcZrA6~6xoMW25cER!Y!pc#rt
zG(5k9)-f^}gmhL|Vi%`0Wy^6%QJ#`@OnqYf%HQaOThbGt9fi?orL&45cq^QL+XNEh
zujwh3aCmx0rK2zzg>+W21mTtYY at 0%6JT!l$5)RMrpmh`&Y0|3*lw9$&B9Pe}@#$yc
zub=dW^PIFFJ6pC#B{M&gMJ{==vtcAY0~q634!7iGB<Gb_J?~WnPt8p!0V~FR^rb at l
zH9f=L83|>K!ffb1ohq#gSB%L-dI8c9O=6n at Cb#4y0^{({SW)@&ESJT$?L)F({(3gB
zNlawX%~jzmMr^zjp}Y5<FcFRXyl~=y%g6AFqGgd{hYUtn&QzALOmoS9Tn&>$LlUKR
z&SPKVdcl1T-Iu*g&DzXz7?Jwctkq9%Mf)*s&{%<kTKSYCr)sVxD{2g4rnKA75CY>q
z)qWmEKF+HjQON!4;bu7 at zc!6L9F at 81<R at x~ym)&zL~S?&y7_luZ0=!M6pLaW5IUL}
zOTm(Qkf3}hEwIRuOTQ8Y2)1fmfNaPzFaEqC?^{;s0>J<nsSKb<f0J$^fNr=Ky-|sW
z=XcOL#;5?pW$UnrGq-_;jDv5_LWaqeJfi_A&f>*?$_l~<*aacE(4B#pB3R-qX$Pqq
z^Q37V90{|~9t~`RRP*yC7l$X60_*gH%b}q^p;k2Z&7mfuzGN!Kf6!YRaa8t-xkG=H
z#1g5^oV#DP<dUh)oV#DP<dUh)oV#DP<dUh~T(Y at h_RDBCqn%F`YY>glkX&JBmEU3+
jhyU#84bJeRQ2?Bx)vDJ+kE69c#6Zn&=|QOv)ex=ia^6uF

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_ at 06LTGAVv_7*f{_U01N<nc}dWB4;W+G
zHyRhap2f2bKsNwfw53c*O{OFxTS`(UkyHQlh<Y8`T<*RD#^gTJ8t#%+7qdScy=~e7
z4gvuJ?Ey85cOHO%ahVS=CgTCdX$NJ_kpnX4xIqW!c#dN at n8|6{Kqg0nF*&9yUl-+w
zX%fXS8i-;BV#E}KFnR|j1KI8Z40IN8yDwm7TwX57VwW#jjFY*zc*)|HE?KmIOBS=3
zYS|)+Su7AmELub{y^`gBD_H(#y7GUzWM$S>0TTsFq3z#aICk^=b7=ejK7YHaR4 at Sr
z_exOZ-Uue(S4jeXlOo`kUkH@$rIK{-6bSB3AD}|>yV(?)-^m`J`4C6*JEYmH>qsW+
zasa7`38{&xOUluVD~ba$t_Gx#9Q at 8N3zqMOTO{YSyE!($?{YzjVnYTRu%QtY8``jl
z0owvbHk$%PHd_J<cmx#i{(uC$Hvj<(rm+Jom=YOa!72-;PhzvJAtKq9lP6cY+pcU0
z4fz6`cu#<k_dx}0AOiLtAZTv^fG#b70WR%FfJ?iyCIQ at TOnz>+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 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)Ok0*-myYE5q<av0W{1d$=|Ky$EdEdkn-ZSBZ_e(V4
zy%J1#pTrVAMM8<0ojXa$>>Pmvm_rYzgE{=Z?CltFC;K9f$esuzvd?~qBCZ#Li0gwG
z;`-}>5W at PdAgs3og!R^Qb at 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*1s<t!q~5XM!fb^Wcx0*q
z3p_GSVTBnAJQZY7DeyRn+o3^$H#-x>BhB#8 at W>O+ at JKQ>!3=McC7iIyO_E at OCPyei
zlQ$AHsY%-iBnfpR$+Tu8tH>0EvQB5sI(^hSStoz`+`rFH-e;f9K4$(j=53C7%q6#B
zy=|B*ZP<pL^ocWhV(fI{6OS2V5o3HujMGrW7`zzMLY!LgUJ+>be)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 at wYpP)Ne)2!C#{(RQDr?zO$wk<Boc1hg0bN^n4M#{_!>X*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*<o$akOJ;MD_{ioUQIc&tn{$pN
znQ<9GBI7D at WG6$K$!H at N07hnXh!l|xslD36w>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)#ZUY<O8 at Kg}!b$!2R-X|6f2<%m-VoA1N^Ypp;L<BHv^LDM;^Igrm
z6__>cG<6j=6+5cEoy*YXY6YRnC}Pv*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-QOB$mqMOKQK
z(iAPlDv*j%KvYGFXo4XLh9VeJ!ro#hNkR|^Q4yLF^gz%oNCtxeAyug~M^vCAAO~vw
zk at 6GeC6(sRJ)iPEb!YDF-|FR4dsH!^2DCH>-`LPRH_yW0w`I;}t$n*SvqGIQ*yitS
z`}doJA1Qvj8bYxO at 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_)rpfGhDfOM<EIvy5fK0Y00;yCgBwT?SX*XC1`_~)L4twNvN1ds45Sza?0U>H
zH2?!3KtX^a0A;`~U7StOx=ROho1%faPSL<zTQXgi%`OFCr8$*<Qv-46pV6#F7gO{n
zYb=?mqF`eTqGnuw3SeA?O8|0oIo<)ye>j%8r>SqcakZxL7{X>z>UCB;cEamh=Jzk5
zaq~p7n41kdhNv?sCPuwh`h<lc<`nymo>qmb3eN+|C(TU6)}Uf>o<No5?iy^ebOX*;
zsuqHkqK4y~r5a|}#16ONcf->q*Dt)&2oU0}gdzr4`fWY6kh;7*E;z+>d3s!NifJm3
zx5s+Z=xjaZdFvof3{MiB!#WC-E_379zvGr8Gg;`lnJsR0mg7cqQ5a|D$<aiHH?&mB
z>v6#;rpwdgg3~@1&5k$j?@7P>#(^Pn`)6-b5ww8llsj+*DIbZHN1*(?Dg3gw84rSE
zA=S$uxyVgv$=AGo!OY=0_56oR8B+1e@&i)EgN<TzuPVxc+)RzWt||HX<r9(wjwpzS
z6ua}KMO&2_V7WuFoW)D!7Fs|cQ>=>c5-;|IFa}{BU7<os6DWkKqeJkz|2T+kydyn~
ziDTl%a2}5JKib%W`bP5Zn6ru=q7mqW4?4=BenDTIaOc8k^OZ5Zw?$*~yA+!i;l@&n
z3w7M&^SINpPDr~%gys(^2-!{vr`BHRcpL#MJ6^gc!&vC)_-I+!ILAA%^(|MlvD`d)
zXl&@Nq#b=oU)f=2{(`ECH!)pSM7f|xvJbsftazEeN^G!#b3=B&z_V}x==0=z)rCYC
zWvvSZxTFe$?VyD~6YH!4=Z~$B0L8bCGDo?K-Mw!Tv8zke<R2(KA<~T#I3`H#Xf1Oy
zZ!A(F6TJvwP9s6|$H6%@)8djWRsCAR7ecj(@rI7YEQX7M0vEiG-|&sl96G0vT6E*0
zQAfAHiX^Zj0wX-A7j~1%$~AHeVEf`J`tSi2$1{ip^@iR=`I1NO0@!05{hB$4ZV|qL
z>wSCpO(95l+>85q at fBa1i=^ux4Oqb+77=VJqXh7O6$H*OK=~$g#AQf|n7g0`_FcPN
zDFT$xzIg8pV9`;RO8!)yEnxUk2ba1>|Bxn1KDVis$nc6b0XT at N)kI09Hs%!CFiIK}
zL`Iu;>MD~UeJHvWjebf?nv1m)oYbPakV<aeP(w|F?`l$u5 at eHf$|HvusCUJ9LEQqt
zb00J^pRV`M5W>CUn3b(aKvArWky(hs!ohezT(~E4Bc1oQA?rv(B&>}Wv7qZv9^PKk
zjh1Vu>@?8Q at csyML4qOv6>PYc2YEz_WN%9hF86f^q$>|ga=&lP*XkqJqp7J9+Tz74
z^htSRsk=OhN3`iG{stZWwdJZwLnN$?7qPc=lSlnX5$@T<$m3oMfS^B)1hinFv{wz5
zG+tCl|CsXga2Q2|<mC^p6WZ#_C~6W`BrG64E~$C at lb!TQkZUX#1tHtEY8Vhn!6^zP
z;)cWOMTWp{Pz>LZ6cY#50qMb?mK(I(w;fqW9+F^oUQE#C2x<~mBpO_(CvFnqmGo}`
zT{Z1BR38U{ZR_X-<xe6?%lvB>fNn`)8ev-?g5=}gOf?QhWnerAM})}sMq=c7uLnT=
zA&`Ep`oB>Jf8BX2Ul5#zNb&|rQ3#9&`G^$B-jf(y?zI4 at p9IpcRrxpQ;IBJxMHhsp
z5INo;DGGt{p&t<<+j|m&%e at u=^%KR|sEED18aZZO2Y{=$YDZ_2EkrTyz^<iV&uvC~
zM1*ipCk7t(dH~c%kbOyXpP_a$|Na(mZT)&~GkREntI7ZBrT_ap!kDgps;7N(0ICDD
zHYBG)jrnsb&aqMRbB*DTCm}HLX&~r%-Sr_wn0Hen5%($p?jZ}TNpaivP%=>@QzXXS
z_0A>wNTEB5$$O7KUu%((OOHPsk1?&~(=aJ~UVY>E_~Y2Q)e58;OvzA at G@Eq5pwg8l
zfn0dF&=8jBq6k0LM at 8Qt0Qq=T1+%J<Gm4P6IUi-xgBZWC_c5`a-rM74d@%G|oTQ1X
zobsIs-zCvJr52DdZhReo77 at j@eK!olMkBn&GePKAT)#4DM~qMS#h9qC>-BLnJ{bBf
z&d@}0&1sZPC2Aav{0s<vdUpd3n+yB*c5KqF*euw_=&zJzO#Qkw*>39r9EF5>l31xA
zi&zl+CRuoliGx66G}L33qD-Oj<V>Ee9lN`^X36es%%}N75y3T=S!O0e5ulkyA*&O|
zLJ0v{nF|Y(9|*8)*=e>B`m1FaQjKNTGoCvy{=3aCe9J at N#*rYoER5zVT*Z(Bu|!<p
zy(dU0D?jg?a3S__y at G35$e@>wQM0kY6SY-tc^}nc!iq>hXb$_>cXbc~l`z5({!ka5
zYzHHuw-%I`>XoV>(8fxAacD6<Un!_+a%JV)2Y at Y)dK-et-ruBUWkV0k08cPhIfq)w
zAt8anIOIj8(<k(7H<GJ2-Ic2rX|RWU?YF}`coW6{a1~?$<;OI_TOJrL6HbZLaePj*
z;?2r!jpC6FL&U5<2G0L<jQT-P1wADWh~RHB+c;2-fJC at c;vnn^RNr-0=r~M&HuLQ&
zQlN2}{%qzO-ANS;hTs&Nd!`pAK#RqnovXV$iqm3Do{?{$svd5d6wj$p>g#cWZI(L4
z0p+4v4aE%_4lBn>W}S*UG#sWbi`Y7?WZ3Cg<_N+F_gf~Rq7jI?EMm)fjf94<gi*se
zutG*K!YRW!utG){!cWOvt?U=0qKpGAaV%_T;-Yi^?}`Tt%kc9|85v)meIE;OU5FKR
V;^=W8u!k^X`7N3e^#Root?kdRMV<fv

diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index dcaa1365..39bd8da6 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -593,7 +593,7 @@ end
 -- field 1-based index or full JSON path. A particular case of a
 -- full JSON path is the format field name.
 --
-local function format_field_resolve(format, path, part_idx)
+local function format_field_resolve(format, path, what)
     assert(type(path) == 'number' or type(path) == 'string')
     local idx = nil
     local relative_path = nil
@@ -636,13 +636,12 @@ local function format_field_resolve(format, path, part_idx)
     end
     -- Can't resolve field index by path.
     assert(idx == nil)
-    box.error(box.error.ILLEGAL_PARAMS, "options.parts[" .. part_idx .. "]: " ..
+    box.error(box.error.ILLEGAL_PARAMS, what .. ": " ..
               "field was not found by name '" .. path .. "'")
 
 ::done::
     if idx <= 0 then
-        box.error(box.error.ILLEGAL_PARAMS,
-                  "options.parts[" .. part_idx .. "]: " ..
+        box.error(box.error.ILLEGAL_PARAMS, what .. ": " ..
                   "field (number) must be one-based")
     end
     return idx - 1, relative_path
@@ -696,7 +695,8 @@ local function update_index_parts(format, parts)
             end
         end
         if type(part.field) == 'number' or type(part.field) == 'string' then
-            local idx, path = format_field_resolve(format, part.field, i)
+            local idx, path = format_field_resolve(format, part.field,
+                                                   "options.parts[" .. i .. "]")
             part.field = idx
             part.path = path or part.path
             parts_can_be_simplified = parts_can_be_simplified and part.path == nil
@@ -749,20 +749,196 @@ 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")
+--
+-- 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




More information about the Tarantool-patches mailing list