1 /*****************************************************************************
2 
3 Copyright (c) 2005, 2019, Oracle and/or its affiliates. All Rights Reserved.
4 Copyright (c) 2013, 2021, MariaDB Corporation.
5 
6 This program is free software; you can redistribute it and/or modify it under
7 the terms of the GNU General Public License as published by the Free Software
8 Foundation; version 2 of the License.
9 
10 This program is distributed in the hope that it will be useful, but WITHOUT
11 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
13 
14 You should have received a copy of the GNU General Public License along with
15 this program; if not, write to the Free Software Foundation, Inc.,
16 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA
17 
18 *****************************************************************************/
19 
20 /**************************************************//**
21 @file handler/handler0alter.cc
22 Smart ALTER TABLE
23 *******************************************************/
24 
25 /* Include necessary SQL headers */
26 #include "univ.i"
27 #include <debug_sync.h>
28 #include <log.h>
29 #include <sql_lex.h>
30 #include <sql_class.h>
31 #include <sql_table.h>
32 #include <mysql/plugin.h>
33 
34 /* Include necessary InnoDB headers */
35 #include "btr0sea.h"
36 #include "dict0crea.h"
37 #include "dict0dict.h"
38 #include "dict0priv.h"
39 #include "dict0stats.h"
40 #include "dict0stats_bg.h"
41 #include "log0log.h"
42 #include "rem0types.h"
43 #include "row0log.h"
44 #include "row0merge.h"
45 #include "row0ins.h"
46 #include "row0row.h"
47 #include "row0upd.h"
48 #include "trx0trx.h"
49 #include "handler0alter.h"
50 #include "srv0mon.h"
51 #include "srv0srv.h"
52 #include "fts0priv.h"
53 #include "fts0plugin.h"
54 #include "pars0pars.h"
55 #include "row0sel.h"
56 #include "ha_innodb.h"
57 #include "ut0stage.h"
58 #include "span.h"
59 #include <thread>
60 #include <sstream>
61 
62 using st_::span;
63 /** File format constraint for ALTER TABLE */
64 extern ulong innodb_instant_alter_column_allowed;
65 
66 static const char *MSG_UNSUPPORTED_ALTER_ONLINE_ON_VIRTUAL_COLUMN=
67 			"INPLACE ADD or DROP of virtual columns cannot be "
68 			"combined with other ALTER TABLE actions";
69 
70 /** Operations for creating secondary indexes (no rebuild needed) */
71 static const alter_table_operations INNOBASE_ONLINE_CREATE
72 	= ALTER_ADD_NON_UNIQUE_NON_PRIM_INDEX
73 	| ALTER_ADD_UNIQUE_INDEX;
74 
75 /** Operations that require filling in default values for columns */
76 static const alter_table_operations INNOBASE_DEFAULTS
77 	= ALTER_COLUMN_NOT_NULLABLE
78 	| ALTER_ADD_STORED_BASE_COLUMN;
79 
80 
81 /** Operations that require knowledge about row_start, row_end values */
82 static const alter_table_operations INNOBASE_ALTER_VERSIONED_REBUILD
83 	= ALTER_ADD_SYSTEM_VERSIONING
84 	| ALTER_DROP_SYSTEM_VERSIONING;
85 
86 /** Operations for rebuilding a table in place */
87 static const alter_table_operations INNOBASE_ALTER_REBUILD
88 	= ALTER_ADD_PK_INDEX
89 	| ALTER_DROP_PK_INDEX
90 	| ALTER_OPTIONS
91 	/* ALTER_OPTIONS needs to check alter_options_need_rebuild() */
92 	| ALTER_COLUMN_NULLABLE
93 	| INNOBASE_DEFAULTS
94 	| ALTER_STORED_COLUMN_ORDER
95 	| ALTER_DROP_STORED_COLUMN
96 	| ALTER_RECREATE_TABLE
97 	/*
98 	| ALTER_STORED_COLUMN_TYPE
99 	*/
100 	| INNOBASE_ALTER_VERSIONED_REBUILD
101 	;
102 
103 /** Operations that require changes to data */
104 static const alter_table_operations INNOBASE_ALTER_DATA
105 	= INNOBASE_ONLINE_CREATE | INNOBASE_ALTER_REBUILD;
106 
107 /** Operations for altering a table that InnoDB does not care about */
108 static const alter_table_operations INNOBASE_INPLACE_IGNORE
109 	= ALTER_COLUMN_DEFAULT
110 	| ALTER_PARTITIONED
111 	| ALTER_COLUMN_COLUMN_FORMAT
112 	| ALTER_COLUMN_STORAGE_TYPE
113 	| ALTER_CONVERT_TO
114 	| ALTER_VIRTUAL_GCOL_EXPR
115 	| ALTER_DROP_CHECK_CONSTRAINT
116 	| ALTER_RENAME
117 	| ALTER_INDEX_ORDER
118 	| ALTER_COLUMN_INDEX_LENGTH
119 	| ALTER_CHANGE_INDEX_COMMENT;
120 
121 /** Operations on foreign key definitions (changing the schema only) */
122 static const alter_table_operations INNOBASE_FOREIGN_OPERATIONS
123 	= ALTER_DROP_FOREIGN_KEY
124 	| ALTER_ADD_FOREIGN_KEY;
125 
126 /** Operations that InnoDB cares about and can perform without creating data */
127 static const alter_table_operations INNOBASE_ALTER_NOCREATE
128 	= ALTER_DROP_NON_UNIQUE_NON_PRIM_INDEX
129 	| ALTER_DROP_UNIQUE_INDEX;
130 
131 /** Operations that InnoDB cares about and can perform without validation */
132 static const alter_table_operations INNOBASE_ALTER_NOVALIDATE
133 	= INNOBASE_ALTER_NOCREATE
134 	| ALTER_VIRTUAL_COLUMN_ORDER
135 	| ALTER_COLUMN_NAME
136 	| INNOBASE_FOREIGN_OPERATIONS
137 	| ALTER_COLUMN_UNVERSIONED
138 	| ALTER_DROP_VIRTUAL_COLUMN;
139 
140 /** Operations that InnoDB cares about and can perform without rebuild */
141 static const alter_table_operations INNOBASE_ALTER_NOREBUILD
142 	= INNOBASE_ONLINE_CREATE
143 	| INNOBASE_ALTER_NOCREATE;
144 
145 /** Operations that can be performed instantly, without inplace_alter_table() */
146 static const alter_table_operations INNOBASE_ALTER_INSTANT
147 	= ALTER_VIRTUAL_COLUMN_ORDER
148 	| ALTER_COLUMN_NAME
149 	| ALTER_ADD_VIRTUAL_COLUMN
150 	| INNOBASE_FOREIGN_OPERATIONS
151 	| ALTER_COLUMN_TYPE_CHANGE_BY_ENGINE
152 	| ALTER_COLUMN_UNVERSIONED
153 	| ALTER_RENAME_INDEX
154 	| ALTER_DROP_VIRTUAL_COLUMN;
155 
156 /** Initialize instant->field_map.
157 @param[in]	table	table definition to copy from */
init_instant(const dict_table_t & table)158 inline void dict_table_t::init_instant(const dict_table_t& table)
159 {
160 	const dict_index_t& oindex __attribute__((unused))= *table.indexes.start;
161 	dict_index_t& index = *indexes.start;
162 	const unsigned u = index.first_user_field();
163 	DBUG_ASSERT(u == oindex.first_user_field());
164 	DBUG_ASSERT(index.n_fields >= oindex.n_fields);
165 
166 	field_map_element_t* field_map_it = static_cast<field_map_element_t*>(
167 		mem_heap_zalloc(heap, (index.n_fields - u)
168 				* sizeof *field_map_it));
169 	instant->field_map = field_map_it;
170 
171 	ut_d(unsigned n_drop = 0);
172 	ut_d(unsigned n_nullable = 0);
173 	for (unsigned i = u; i < index.n_fields; i++) {
174 		auto& f = index.fields[i];
175 		ut_d(n_nullable += f.col->is_nullable());
176 
177 		if (!f.col->is_dropped()) {
178 			(*field_map_it++).set_ind(f.col->ind);
179 			continue;
180 		}
181 
182 		auto fixed_len = dict_col_get_fixed_size(
183 			f.col, not_redundant());
184 		field_map_it->set_dropped();
185 		if (!f.col->is_nullable()) {
186 			field_map_it->set_not_null();
187 		}
188 		field_map_it->set_ind(fixed_len
189 				      ? uint16_t(fixed_len + 1)
190 				      : DATA_BIG_COL(f.col));
191 		field_map_it++;
192 		ut_ad(f.col >= table.instant->dropped);
193 		ut_ad(f.col < table.instant->dropped
194 		      + table.instant->n_dropped);
195 		ut_d(n_drop++);
196 		size_t d = f.col - table.instant->dropped;
197 		ut_ad(f.col == &table.instant->dropped[d]);
198 		ut_ad(d <= instant->n_dropped);
199 		f.col = &instant->dropped[d];
200 	}
201 	ut_ad(n_drop == n_dropped());
202 	ut_ad(field_map_it == &instant->field_map[index.n_fields - u]);
203 	ut_ad(index.n_nullable == n_nullable);
204 }
205 
206 /** Set is_instant() before instant_column().
207 @param[in]	old		previous table definition
208 @param[in]	col_map		map from old.cols[] and old.v_cols[] to this
209 @param[out]	first_alter_pos	0, or 1 + first changed column position */
prepare_instant(const dict_table_t & old,const ulint * col_map,unsigned & first_alter_pos)210 inline void dict_table_t::prepare_instant(const dict_table_t& old,
211 					  const ulint* col_map,
212 					  unsigned& first_alter_pos)
213 {
214 	DBUG_ASSERT(!is_instant());
215 	DBUG_ASSERT(n_dropped() == 0);
216 	DBUG_ASSERT(old.n_cols == old.n_def);
217 	DBUG_ASSERT(n_cols == n_def);
218 	DBUG_ASSERT(old.supports_instant());
219 	DBUG_ASSERT(not_redundant() == old.not_redundant());
220 	DBUG_ASSERT(DICT_TF_HAS_ATOMIC_BLOBS(flags)
221 		    == DICT_TF_HAS_ATOMIC_BLOBS(old.flags));
222 	DBUG_ASSERT(!persistent_autoinc
223 		    || persistent_autoinc == old.persistent_autoinc);
224 	/* supports_instant() does not necessarily hold here,
225 	in case ROW_FORMAT=COMPRESSED according to the
226 	MariaDB data dictionary, and ALTER_OPTIONS was not set.
227 	If that is the case, the instant ALTER TABLE would keep
228 	the InnoDB table in its current format. */
229 
230 	const dict_index_t& oindex = *old.indexes.start;
231 	dict_index_t& index = *indexes.start;
232 	first_alter_pos = 0;
233 
234 	for (unsigned i = 0; i + DATA_N_SYS_COLS < old.n_cols; i++) {
235 		if (col_map[i] != i) {
236 			first_alter_pos = 1 + i;
237 			goto add_metadata;
238 		}
239 	}
240 
241 	if (!old.instant) {
242 		/* Columns were not dropped or reordered.
243 		Therefore columns must have been added at the end,
244 		or modified instantly in place. */
245 		DBUG_ASSERT(index.n_fields >= oindex.n_fields);
246 		DBUG_ASSERT(index.n_fields > oindex.n_fields
247 			    || !not_redundant());
248 #ifdef UNIV_DEBUG
249 		if (index.n_fields == oindex.n_fields) {
250 			ut_ad(!not_redundant());
251 			for (unsigned i = index.n_fields; i--; ) {
252 				ut_ad(index.fields[i].col->same_format(
253 					      *oindex.fields[i].col));
254 			}
255 		}
256 #endif
257 set_core_fields:
258 		index.n_core_fields = oindex.n_core_fields;
259 		index.n_core_null_bytes = oindex.n_core_null_bytes;
260 	} else {
261 add_metadata:
262 		const unsigned n_old_drop = old.n_dropped();
263 		unsigned n_drop = n_old_drop;
264 		for (unsigned i = old.n_cols; i--; ) {
265 			if (col_map[i] == ULINT_UNDEFINED) {
266 				DBUG_ASSERT(i + DATA_N_SYS_COLS
267 					    < uint(old.n_cols));
268 				n_drop++;
269 			}
270 		}
271 
272 		instant = new (mem_heap_alloc(heap, sizeof(dict_instant_t)))
273 			dict_instant_t();
274 		instant->n_dropped = n_drop;
275 		if (n_drop) {
276 			instant->dropped
277 				= static_cast<dict_col_t*>(
278 					mem_heap_alloc(heap, n_drop
279 						       * sizeof(dict_col_t)));
280 			if (n_old_drop) {
281 				memcpy(instant->dropped, old.instant->dropped,
282 				       n_old_drop * sizeof(dict_col_t));
283 			}
284 		} else {
285 			instant->dropped = NULL;
286 		}
287 
288 		for (unsigned i = 0, d = n_old_drop; i < old.n_cols; i++) {
289 			if (col_map[i] == ULINT_UNDEFINED) {
290 				(new (&instant->dropped[d++])
291 				 dict_col_t(old.cols[i]))->set_dropped();
292 			}
293 		}
294 #ifndef DBUG_OFF
295 		for (unsigned i = 0; i < n_drop; i++) {
296 			DBUG_ASSERT(instant->dropped[i].is_dropped());
297 		}
298 #endif
299 		const unsigned n_fields = index.n_fields + n_dropped();
300 
301 		DBUG_ASSERT(n_fields >= oindex.n_fields);
302 		dict_field_t* fields = static_cast<dict_field_t*>(
303 			mem_heap_zalloc(heap, n_fields * sizeof *fields));
304 		unsigned i = 0, j = 0, n_nullable = 0;
305 		ut_d(uint core_null = 0);
306 		for (; i < oindex.n_fields; i++) {
307 			DBUG_ASSERT(j <= i);
308 			dict_field_t&f = fields[i] = oindex.fields[i];
309 			if (f.col->is_dropped()) {
310 				/* The column has been instantly
311 				dropped earlier. */
312 				DBUG_ASSERT(f.col >= old.instant->dropped);
313 				{
314 					size_t d = f.col
315 						- old.instant->dropped;
316 					DBUG_ASSERT(d < n_old_drop);
317 					DBUG_ASSERT(&old.instant->dropped[d]
318 						    == f.col);
319 					DBUG_ASSERT(!f.name);
320 					f.col = instant->dropped + d;
321 				}
322 				if (f.col->is_nullable()) {
323 found_nullable:
324 					n_nullable++;
325 					ut_d(core_null
326 					     += i < oindex.n_core_fields);
327 				}
328 				continue;
329 			}
330 
331 			const ulint col_ind = col_map[f.col->ind];
332 			if (col_ind != ULINT_UNDEFINED) {
333 				if (index.fields[j].col->ind != col_ind) {
334 					/* The fields for instantly
335 					added columns must be placed
336 					last in the clustered index.
337 					Keep pre-existing fields in
338 					the same position. */
339 					uint k;
340 					for (k = j + 1; k < index.n_fields;
341 					     k++) {
342 						if (index.fields[k].col->ind
343 						    == col_ind) {
344 							goto found_j;
345 						}
346 					}
347 					DBUG_ASSERT("no such col" == 0);
348 found_j:
349 					std::swap(index.fields[j],
350 						  index.fields[k]);
351 				}
352 				DBUG_ASSERT(index.fields[j].col->ind
353 					    == col_ind);
354 				fields[i] = index.fields[j++];
355 				DBUG_ASSERT(!fields[i].col->is_dropped());
356 				DBUG_ASSERT(fields[i].name
357 					    == fields[i].col->name(*this));
358 				if (fields[i].col->is_nullable()) {
359 					goto found_nullable;
360 				}
361 				continue;
362 			}
363 
364 			/* This column is being dropped. */
365 			unsigned d = n_old_drop;
366 			for (unsigned c = 0; c < f.col->ind; c++) {
367 				d += col_map[c] == ULINT_UNDEFINED;
368 			}
369 			DBUG_ASSERT(d < n_drop);
370 			f.col = &instant->dropped[d];
371 			f.name = NULL;
372 			if (f.col->is_nullable()) {
373 				goto found_nullable;
374 			}
375 		}
376 		/* The n_core_null_bytes only matters for
377 		ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC tables. */
378 		ut_ad(UT_BITS_IN_BYTES(core_null) == oindex.n_core_null_bytes
379 		      || !not_redundant());
380 		DBUG_ASSERT(i >= oindex.n_core_fields);
381 		DBUG_ASSERT(j <= i);
382 		DBUG_ASSERT(n_fields - (i - j) == index.n_fields);
383 		std::sort(index.fields + j, index.fields + index.n_fields,
384 			  [](const dict_field_t& a, const dict_field_t& b)
385 			  { return a.col->ind < b.col->ind; });
386 		for (; i < n_fields; i++) {
387 			fields[i] = index.fields[j++];
388 			n_nullable += fields[i].col->is_nullable();
389 			DBUG_ASSERT(!fields[i].col->is_dropped());
390 			DBUG_ASSERT(fields[i].name
391 				    == fields[i].col->name(*this));
392 		}
393 		DBUG_ASSERT(j == index.n_fields);
394 		index.n_fields = index.n_def = n_fields
395 			& dict_index_t::MAX_N_FIELDS;
396 		index.fields = fields;
397 		DBUG_ASSERT(n_nullable >= index.n_nullable);
398 		DBUG_ASSERT(n_nullable >= oindex.n_nullable);
399 		index.n_nullable = n_nullable & dict_index_t::MAX_N_FIELDS;
400 		goto set_core_fields;
401 	}
402 
403 	DBUG_ASSERT(n_cols + n_dropped() >= old.n_cols + old.n_dropped());
404 	DBUG_ASSERT(n_dropped() >= old.n_dropped());
405 	DBUG_ASSERT(index.n_core_fields == oindex.n_core_fields);
406 	DBUG_ASSERT(index.n_core_null_bytes == oindex.n_core_null_bytes);
407 }
408 
409 /** Adjust index metadata for instant ADD/DROP/reorder COLUMN.
410 @param[in]	clustered index definition after instant ALTER TABLE */
instant_add_field(const dict_index_t & instant)411 inline void dict_index_t::instant_add_field(const dict_index_t& instant)
412 {
413 	DBUG_ASSERT(is_primary());
414 	DBUG_ASSERT(instant.is_primary());
415 	DBUG_ASSERT(!has_virtual());
416 	DBUG_ASSERT(!instant.has_virtual());
417 	DBUG_ASSERT(instant.n_core_fields <= instant.n_fields);
418 	DBUG_ASSERT(n_def == n_fields);
419 	DBUG_ASSERT(instant.n_def == instant.n_fields);
420 	DBUG_ASSERT(type == instant.type);
421 	DBUG_ASSERT(trx_id_offset == instant.trx_id_offset);
422 	DBUG_ASSERT(n_user_defined_cols == instant.n_user_defined_cols);
423 	DBUG_ASSERT(n_uniq == instant.n_uniq);
424 	DBUG_ASSERT(instant.n_fields >= n_fields);
425 	DBUG_ASSERT(instant.n_nullable >= n_nullable);
426 	DBUG_ASSERT(instant.n_core_fields == n_core_fields);
427 	DBUG_ASSERT(instant.n_core_null_bytes == n_core_null_bytes);
428 
429 	/* instant will have all fields (including ones for columns
430 	that have been or are being instantly dropped) in the same position
431 	as this index. Fields for any added columns are appended at the end. */
432 #ifndef DBUG_OFF
433 	for (unsigned i = 0; i < n_fields; i++) {
434 		DBUG_ASSERT(fields[i].same(instant.fields[i]));
435 		DBUG_ASSERT(instant.fields[i].col->same_format(*fields[i]
436 							       .col));
437 		/* Instant conversion from NULL to NOT NULL is not allowed. */
438 		DBUG_ASSERT(!fields[i].col->is_nullable()
439 			    || instant.fields[i].col->is_nullable());
440 		DBUG_ASSERT(fields[i].col->is_nullable()
441 			    == instant.fields[i].col->is_nullable()
442 			    || !table->not_redundant());
443 	}
444 #endif
445 	n_fields = instant.n_fields;
446 	n_def = instant.n_def;
447 	n_nullable = instant.n_nullable;
448 	fields = static_cast<dict_field_t*>(
449 		mem_heap_dup(heap, instant.fields, n_fields * sizeof *fields));
450 
451 	ut_d(unsigned n_null = 0);
452 	ut_d(unsigned n_dropped = 0);
453 
454 	for (unsigned i = 0; i < n_fields; i++) {
455 		const dict_col_t* icol = instant.fields[i].col;
456 		dict_field_t& f = fields[i];
457 		ut_d(n_null += icol->is_nullable());
458 		DBUG_ASSERT(!icol->is_virtual());
459 		if (icol->is_dropped()) {
460 			ut_d(n_dropped++);
461 			f.col->set_dropped();
462 			f.name = NULL;
463 		} else {
464 			f.col = &table->cols[icol - instant.table->cols];
465 			f.name = f.col->name(*table);
466 		}
467 	}
468 
469 	ut_ad(n_null == n_nullable);
470 	ut_ad(n_dropped == instant.table->n_dropped());
471 }
472 
473 /** Adjust table metadata for instant ADD/DROP/reorder COLUMN.
474 @param[in]	table	altered table (with dropped columns)
475 @param[in]	col_map	mapping from cols[] and v_cols[] to table
476 @return		whether the metadata record must be updated */
instant_column(const dict_table_t & table,const ulint * col_map)477 inline bool dict_table_t::instant_column(const dict_table_t& table,
478 					 const ulint* col_map)
479 {
480 	DBUG_ASSERT(!table.cached);
481 	DBUG_ASSERT(table.n_def == table.n_cols);
482 	DBUG_ASSERT(table.n_t_def == table.n_t_cols);
483 	DBUG_ASSERT(n_def == n_cols);
484 	DBUG_ASSERT(n_t_def == n_t_cols);
485 	DBUG_ASSERT(n_v_def == n_v_cols);
486 	DBUG_ASSERT(table.n_v_def == table.n_v_cols);
487 	DBUG_ASSERT(table.n_cols + table.n_dropped() >= n_cols + n_dropped());
488 	DBUG_ASSERT(!table.persistent_autoinc
489 		    || persistent_autoinc == table.persistent_autoinc);
490 	ut_ad(mutex_own(&dict_sys.mutex));
491 
492 	{
493 		const char* end = table.col_names;
494 		for (unsigned i = table.n_cols; i--; ) end += strlen(end) + 1;
495 
496 		col_names = static_cast<char*>(
497 			mem_heap_dup(heap, table.col_names,
498 				     ulint(end - table.col_names)));
499 	}
500 	const dict_col_t* const old_cols = cols;
501 	cols = static_cast<dict_col_t*>(mem_heap_dup(heap, table.cols,
502 						     table.n_cols
503 						     * sizeof *cols));
504 
505 	/* Preserve the default values of previously instantly added
506 	columns, or copy the new default values to this->heap. */
507 	for (uint16_t i = 0; i < table.n_cols; i++) {
508 		dict_col_t& c = cols[i];
509 
510 		if (const dict_col_t* o = find(old_cols, col_map, n_cols, i)) {
511 			c.def_val = o->def_val;
512 			DBUG_ASSERT(!((c.prtype ^ o->prtype)
513 				      & ~(DATA_NOT_NULL | DATA_VERSIONED
514 					  | CHAR_COLL_MASK << 16
515 					  | DATA_LONG_TRUE_VARCHAR)));
516 			DBUG_ASSERT(c.same_type(*o));
517 			DBUG_ASSERT(c.len >= o->len);
518 
519 			if (o->vers_sys_start()) {
520 				ut_ad(o->ind == vers_start);
521 				vers_start = i & dict_index_t::MAX_N_FIELDS;
522 			} else if (o->vers_sys_end()) {
523 				ut_ad(o->ind == vers_end);
524 				vers_end = i & dict_index_t::MAX_N_FIELDS;
525 			}
526 			continue;
527 		}
528 
529 		DBUG_ASSERT(c.is_added());
530 		if (c.def_val.len <= UNIV_PAGE_SIZE_MAX
531 		    && (!c.def_val.len
532 			|| !memcmp(c.def_val.data, field_ref_zero,
533 				   c.def_val.len))) {
534 			c.def_val.data = field_ref_zero;
535 		} else if (const void*& d = c.def_val.data) {
536 			d = mem_heap_dup(heap, d, c.def_val.len);
537 		} else {
538 			DBUG_ASSERT(c.def_val.len == UNIV_SQL_NULL);
539 		}
540 	}
541 
542 	n_t_def = (n_t_def + (table.n_cols - n_cols))
543 		& dict_index_t::MAX_N_FIELDS;
544 	n_t_cols = (n_t_cols + (table.n_cols - n_cols))
545 		& dict_index_t::MAX_N_FIELDS;
546 	n_def = table.n_cols;
547 
548 	const dict_v_col_t* const old_v_cols = v_cols;
549 
550 	if (const char* end = table.v_col_names) {
551 		for (unsigned i = table.n_v_cols; i--; ) {
552 			end += strlen(end) + 1;
553 		}
554 
555 		v_col_names = static_cast<char*>(
556 			mem_heap_dup(heap, table.v_col_names,
557 				     ulint(end - table.v_col_names)));
558 		v_cols = static_cast<dict_v_col_t*>(
559 			mem_heap_alloc(heap, table.n_v_cols * sizeof(*v_cols)));
560 		for (ulint i = table.n_v_cols; i--; ) {
561 			new (&v_cols[i]) dict_v_col_t(table.v_cols[i]);
562 			v_cols[i].v_indexes.clear();
563 		}
564 	} else {
565 		ut_ad(table.n_v_cols == 0);
566 		v_col_names = NULL;
567 		v_cols = NULL;
568 	}
569 
570 	n_t_def = (n_t_def + (table.n_v_cols - n_v_cols))
571 		& dict_index_t::MAX_N_FIELDS;
572 	n_t_cols = (n_t_cols + (table.n_v_cols - n_v_cols))
573 		& dict_index_t::MAX_N_FIELDS;
574 	n_v_def = table.n_v_cols;
575 
576 	for (unsigned i = 0; i < n_v_def; i++) {
577 		dict_v_col_t& v = v_cols[i];
578 		DBUG_ASSERT(v.v_indexes.empty());
579 		v.base_col = static_cast<dict_col_t**>(
580 			mem_heap_dup(heap, v.base_col,
581 				     v.num_base * sizeof *v.base_col));
582 
583 		for (ulint n = v.num_base; n--; ) {
584 			dict_col_t*& base = v.base_col[n];
585 			if (base->is_virtual()) {
586 			} else if (base >= table.cols
587 				   && base < table.cols + table.n_cols) {
588 				/* The base column was instantly added. */
589 				size_t c = base - table.cols;
590 				DBUG_ASSERT(base == &table.cols[c]);
591 				base = &cols[c];
592 			} else {
593 				DBUG_ASSERT(base >= old_cols);
594 				size_t c = base - old_cols;
595 				DBUG_ASSERT(c + DATA_N_SYS_COLS < n_cols);
596 				DBUG_ASSERT(base == &old_cols[c]);
597 				DBUG_ASSERT(col_map[c] + DATA_N_SYS_COLS
598 					    < n_cols);
599 				base = &cols[col_map[c]];
600 			}
601 		}
602 	}
603 
604 	dict_index_t* index = dict_table_get_first_index(this);
605 	bool metadata_changed;
606 	{
607 		const dict_index_t& i = *dict_table_get_first_index(&table);
608 		metadata_changed = i.n_fields > index->n_fields;
609 		ut_ad(i.n_fields >= index->n_fields);
610 		index->instant_add_field(i);
611 	}
612 
613 	if (instant || table.instant) {
614 		const auto old_instant = instant;
615 		/* FIXME: add instant->heap, and transfer ownership here */
616 		if (!instant) {
617 			instant = new (mem_heap_zalloc(heap, sizeof *instant))
618 				dict_instant_t();
619 			goto dup_dropped;
620 		} else if (n_dropped() < table.n_dropped()) {
621 dup_dropped:
622 			instant->dropped = static_cast<dict_col_t*>(
623 				mem_heap_dup(heap, table.instant->dropped,
624 					     table.instant->n_dropped
625 					     * sizeof *instant->dropped));
626 			instant->n_dropped = table.instant->n_dropped;
627 		} else if (table.instant->n_dropped) {
628 			memcpy(instant->dropped, table.instant->dropped,
629 			       table.instant->n_dropped
630 			       * sizeof *instant->dropped);
631 		}
632 
633 		const field_map_element_t* field_map = old_instant
634 			? old_instant->field_map : NULL;
635 
636 		init_instant(table);
637 
638 		if (!metadata_changed) {
639 			metadata_changed = !field_map
640 				|| memcmp(field_map,
641 					  instant->field_map,
642 					  (index->n_fields
643 					   - index->first_user_field())
644 					  * sizeof *field_map);
645 		}
646 	}
647 
648 	while ((index = dict_table_get_next_index(index)) != NULL) {
649 		if (index->to_be_dropped) {
650 			continue;
651 		}
652 		for (unsigned i = 0; i < index->n_fields; i++) {
653 			dict_field_t& f = index->fields[i];
654 			if (f.col >= table.cols
655 			    && f.col < table.cols + table.n_cols) {
656 				/* This is an instantly added column
657 				in a newly added index. */
658 				DBUG_ASSERT(!f.col->is_virtual());
659 				size_t c = f.col - table.cols;
660 				DBUG_ASSERT(f.col == &table.cols[c]);
661 				f.col = &cols[c];
662 			} else if (f.col >= &table.v_cols->m_col
663 				   && f.col < &table.v_cols[n_v_cols].m_col) {
664 				/* This is an instantly added virtual column
665 				in a newly added index. */
666 				DBUG_ASSERT(f.col->is_virtual());
667 				size_t c = reinterpret_cast<dict_v_col_t*>(
668 					f.col) - table.v_cols;
669 				DBUG_ASSERT(f.col == &table.v_cols[c].m_col);
670 				f.col = &v_cols[c].m_col;
671 			} else if (f.col < old_cols
672 				   || f.col >= old_cols + n_cols) {
673 				DBUG_ASSERT(f.col->is_virtual());
674 				f.col = &v_cols[col_map[
675 						reinterpret_cast<dict_v_col_t*>(
676 							f.col)
677 						- old_v_cols + n_cols]].m_col;
678 			} else {
679 				f.col = &cols[col_map[f.col - old_cols]];
680 				DBUG_ASSERT(!f.col->is_virtual());
681 			}
682 			f.name = f.col->name(*this);
683 			if (f.col->is_virtual()) {
684 				dict_v_col_t* v_col = reinterpret_cast
685 					<dict_v_col_t*>(f.col);
686 				v_col->v_indexes.push_front(
687 					dict_v_idx_t(index, i));
688 			}
689 		}
690 	}
691 
692 	n_cols = table.n_cols;
693 	n_v_cols = table.n_v_cols;
694 	return metadata_changed;
695 }
696 
697 /** Find the old column number for the given new column position.
698 @param[in]	col_map	column map from old column to new column
699 @param[in]	pos	new column position
700 @param[in]	n	number of columns present in the column map
701 @return old column position for the given new column position. */
find_old_col_no(const ulint * col_map,ulint pos,ulint n)702 static ulint find_old_col_no(const ulint* col_map, ulint pos, ulint n)
703 {
704 	do {
705 		ut_ad(n);
706 	} while (col_map[--n] != pos);
707 	return n;
708 }
709 
710 /** Roll back instant_column().
711 @param[in]	old_n_cols		original n_cols
712 @param[in]	old_cols		original cols
713 @param[in]	old_col_names		original col_names
714 @param[in]	old_instant		original instant structure
715 @param[in]	old_fields		original fields
716 @param[in]	old_n_fields		original number of fields
717 @param[in]	old_n_core_fields	original number of core fields
718 @param[in]	old_n_v_cols		original n_v_cols
719 @param[in]	old_v_cols		original v_cols
720 @param[in]	old_v_col_names		original v_col_names
721 @param[in]	col_map			column map */
rollback_instant(unsigned old_n_cols,dict_col_t * old_cols,const char * old_col_names,dict_instant_t * old_instant,dict_field_t * old_fields,unsigned old_n_fields,unsigned old_n_core_fields,unsigned old_n_v_cols,dict_v_col_t * old_v_cols,const char * old_v_col_names,const ulint * col_map)722 inline void dict_table_t::rollback_instant(
723 	unsigned	old_n_cols,
724 	dict_col_t*	old_cols,
725 	const char*	old_col_names,
726 	dict_instant_t*	old_instant,
727 	dict_field_t*	old_fields,
728 	unsigned	old_n_fields,
729 	unsigned	old_n_core_fields,
730 	unsigned	old_n_v_cols,
731 	dict_v_col_t*	old_v_cols,
732 	const char*	old_v_col_names,
733 	const ulint*	col_map)
734 {
735 	ut_d(dict_sys.assert_locked());
736 
737 	if (cols == old_cols) {
738 		/* Alter fails before instant operation happens.
739 		So there is no need to do rollback instant operation */
740 		return;
741 	}
742 
743 	dict_index_t* index = indexes.start;
744 	/* index->is_instant() does not necessarily hold here, because
745 	the table may have been emptied */
746 	DBUG_ASSERT(old_n_cols >= DATA_N_SYS_COLS);
747 	DBUG_ASSERT(n_cols == n_def);
748 	DBUG_ASSERT(index->n_def == index->n_fields);
749 	DBUG_ASSERT(index->n_core_fields <= index->n_fields);
750 	DBUG_ASSERT(old_n_core_fields <= old_n_fields);
751 	DBUG_ASSERT(instant || !old_instant);
752 
753 	instant = old_instant;
754 
755 	index->n_nullable = 0;
756 
757 	for (unsigned i = old_n_fields; i--; ) {
758 		if (old_fields[i].col->is_nullable()) {
759 			index->n_nullable++;
760 		}
761 	}
762 
763 	for (unsigned i = n_v_cols; i--; ) {
764 		v_cols[i].~dict_v_col_t();
765 	}
766 
767 	index->n_core_fields = ((index->n_fields == index->n_core_fields)
768 				? old_n_fields
769 				: old_n_core_fields)
770 		& dict_index_t::MAX_N_FIELDS;
771 	index->n_def = index->n_fields = old_n_fields
772 		& dict_index_t::MAX_N_FIELDS;
773 	index->n_core_null_bytes = static_cast<uint8_t>(
774 		UT_BITS_IN_BYTES(index->get_n_nullable(index->n_core_fields)));
775 
776 	const dict_col_t* const new_cols = cols;
777 	const dict_col_t* const new_cols_end __attribute__((unused)) = cols + n_cols;
778 	const dict_v_col_t* const new_v_cols = v_cols;
779 	const dict_v_col_t* const new_v_cols_end __attribute__((unused))= v_cols + n_v_cols;
780 
781 	cols = old_cols;
782 	col_names = old_col_names;
783 	v_cols = old_v_cols;
784 	v_col_names = old_v_col_names;
785 	n_def = n_cols = old_n_cols & dict_index_t::MAX_N_FIELDS;
786 	n_v_def = n_v_cols = old_n_v_cols & dict_index_t::MAX_N_FIELDS;
787 	n_t_def = n_t_cols = (n_cols + n_v_cols) & dict_index_t::MAX_N_FIELDS;
788 
789 	if (versioned()) {
790 		for (unsigned i = 0; i < n_cols; ++i) {
791 			if (cols[i].vers_sys_start()) {
792 				vers_start = i & dict_index_t::MAX_N_FIELDS;
793 			} else if (cols[i].vers_sys_end()) {
794 				vers_end = i & dict_index_t::MAX_N_FIELDS;
795 			}
796 		}
797 	}
798 
799 	index->fields = old_fields;
800 
801 	while ((index = dict_table_get_next_index(index)) != NULL) {
802 		if (index->to_be_dropped) {
803 			/* instant_column() did not adjust these indexes. */
804 			continue;
805 		}
806 
807 		for (unsigned i = 0; i < index->n_fields; i++) {
808 			dict_field_t& f = index->fields[i];
809 			if (f.col->is_virtual()) {
810 				DBUG_ASSERT(f.col >= &new_v_cols->m_col);
811 				DBUG_ASSERT(f.col < &new_v_cols_end->m_col);
812 				size_t n = size_t(
813 					reinterpret_cast<dict_v_col_t*>(f.col)
814 					- new_v_cols);
815 				DBUG_ASSERT(n <= n_v_cols);
816 
817 				ulint old_col_no = find_old_col_no(
818 					col_map + n_cols, n, n_v_cols);
819 				DBUG_ASSERT(old_col_no <= n_v_cols);
820 				f.col = &v_cols[old_col_no].m_col;
821 				DBUG_ASSERT(f.col->is_virtual());
822 			} else {
823 				DBUG_ASSERT(f.col >= new_cols);
824 				DBUG_ASSERT(f.col < new_cols_end);
825 				size_t n = size_t(f.col - new_cols);
826 				DBUG_ASSERT(n <= n_cols);
827 
828 				ulint old_col_no = find_old_col_no(col_map,
829 								   n, n_cols);
830 				DBUG_ASSERT(old_col_no < n_cols);
831 				f.col = &cols[old_col_no];
832 				DBUG_ASSERT(!f.col->is_virtual());
833 			}
834 			f.name = f.col->name(*this);
835 		}
836 	}
837 }
838 
839 struct ha_innobase_inplace_ctx : public inplace_alter_handler_ctx
840 {
841 	/** Dummy query graph */
842 	que_thr_t*	thr;
843 	/** The prebuilt struct of the creating instance */
844 	row_prebuilt_t*&	prebuilt;
845 	/** InnoDB indexes being created */
846 	dict_index_t**	add_index;
847 	/** MySQL key numbers for the InnoDB indexes that are being created */
848 	const ulint*	add_key_numbers;
849 	/** number of InnoDB indexes being created */
850 	ulint		num_to_add_index;
851 	/** InnoDB indexes being dropped */
852 	dict_index_t**	drop_index;
853 	/** number of InnoDB indexes being dropped */
854 	const ulint	num_to_drop_index;
855 	/** InnoDB foreign key constraints being dropped */
856 	dict_foreign_t** drop_fk;
857 	/** number of InnoDB foreign key constraints being dropped */
858 	const ulint	num_to_drop_fk;
859 	/** InnoDB foreign key constraints being added */
860 	dict_foreign_t** add_fk;
861 	/** number of InnoDB foreign key constraints being dropped */
862 	const ulint	num_to_add_fk;
863 	/** whether to create the indexes online */
864 	bool		online;
865 	/** memory heap */
866 	mem_heap_t*	heap;
867 	/** dictionary transaction */
868 	trx_t*		trx;
869 	/** original table (if rebuilt, differs from indexed_table) */
870 	dict_table_t*	old_table;
871 	/** table where the indexes are being created or dropped */
872 	dict_table_t*	new_table;
873 	/** table definition for instant ADD/DROP/reorder COLUMN */
874 	dict_table_t*	instant_table;
875 	/** mapping of old column numbers to new ones, or NULL */
876 	const ulint*	col_map;
877 	/** new column names, or NULL if nothing was renamed */
878 	const char**	col_names;
879 	/** added AUTO_INCREMENT column position, or ULINT_UNDEFINED */
880 	const ulint	add_autoinc;
881 	/** default values of ADD and CHANGE COLUMN, or NULL */
882 	const dtuple_t*	defaults;
883 	/** autoinc sequence to use */
884 	ib_sequence_t	sequence;
885 	/** temporary table name to use for old table when renaming tables */
886 	const char*	tmp_name;
887 	/** whether the order of the clustered index is unchanged */
888 	bool		skip_pk_sort;
889 	/** number of virtual columns to be added */
890 	unsigned	num_to_add_vcol;
891 	/** virtual columns to be added */
892 	dict_v_col_t*	add_vcol;
893 	const char**	add_vcol_name;
894 	/** number of virtual columns to be dropped */
895 	unsigned	num_to_drop_vcol;
896 	/** virtual columns to be dropped */
897 	dict_v_col_t*	drop_vcol;
898 	const char**	drop_vcol_name;
899 	/** ALTER TABLE stage progress recorder */
900 	ut_stage_alter_t* m_stage;
901 	/** original number of user columns in the table */
902 	const unsigned	old_n_cols;
903 	/** original columns of the table */
904 	dict_col_t* const old_cols;
905 	/** original column names of the table */
906 	const char* const old_col_names;
907 	/** original instantly dropped or reordered columns */
908 	dict_instant_t*	const	old_instant;
909 	/** original index fields */
910 	dict_field_t* const	old_fields;
911 	/** size of old_fields */
912 	const unsigned		old_n_fields;
913 	/** original old_table->n_core_fields */
914 	const unsigned		old_n_core_fields;
915 	/** original number of virtual columns in the table */
916 	const unsigned		old_n_v_cols;
917 	/** original virtual columns of the table */
918 	dict_v_col_t* const old_v_cols;
919 	/** original virtual column names of the table */
920 	const char* const old_v_col_names;
921 	/** 0, or 1 + first column whose position changes in instant ALTER */
922 	unsigned	first_alter_pos;
923 	/** Allow non-null conversion.
924 	(1) Alter ignore should allow the conversion
925 	irrespective of sql mode.
926 	(2) Don't allow the conversion in strict mode
927 	(3) Allow the conversion only in non-strict mode. */
928 	const bool	allow_not_null;
929 
930 	/** The page_compression_level attribute, or 0 */
931 	const uint	page_compression_level;
932 
ha_innobase_inplace_ctxha_innobase_inplace_ctx933 	ha_innobase_inplace_ctx(row_prebuilt_t*& prebuilt_arg,
934 				dict_index_t** drop_arg,
935 				ulint num_to_drop_arg,
936 				dict_foreign_t** drop_fk_arg,
937 				ulint num_to_drop_fk_arg,
938 				dict_foreign_t** add_fk_arg,
939 				ulint num_to_add_fk_arg,
940 				bool online_arg,
941 				mem_heap_t* heap_arg,
942 				dict_table_t* new_table_arg,
943 				const char** col_names_arg,
944 				ulint add_autoinc_arg,
945 				ulonglong autoinc_col_min_value_arg,
946 				ulonglong autoinc_col_max_value_arg,
947 				bool allow_not_null_flag,
948 				bool page_compressed,
949 				ulonglong page_compression_level_arg) :
950 		inplace_alter_handler_ctx(),
951 		prebuilt (prebuilt_arg),
952 		add_index (0), add_key_numbers (0), num_to_add_index (0),
953 		drop_index (drop_arg), num_to_drop_index (num_to_drop_arg),
954 		drop_fk (drop_fk_arg), num_to_drop_fk (num_to_drop_fk_arg),
955 		add_fk (add_fk_arg), num_to_add_fk (num_to_add_fk_arg),
956 		online (online_arg), heap (heap_arg), trx (0),
957 		old_table (prebuilt_arg->table),
958 		new_table (new_table_arg), instant_table (0),
959 		col_map (0), col_names (col_names_arg),
960 		add_autoinc (add_autoinc_arg),
961 		defaults (0),
962 		sequence(prebuilt->trx->mysql_thd,
963 			 autoinc_col_min_value_arg, autoinc_col_max_value_arg),
964 		tmp_name (0),
965 		skip_pk_sort(false),
966 		num_to_add_vcol(0),
967 		add_vcol(0),
968 		add_vcol_name(0),
969 		num_to_drop_vcol(0),
970 		drop_vcol(0),
971 		drop_vcol_name(0),
972 		m_stage(NULL),
973 		old_n_cols(prebuilt_arg->table->n_cols),
974 		old_cols(prebuilt_arg->table->cols),
975 		old_col_names(prebuilt_arg->table->col_names),
976 		old_instant(prebuilt_arg->table->instant),
977 		old_fields(prebuilt_arg->table->indexes.start->fields),
978 		old_n_fields(prebuilt_arg->table->indexes.start->n_fields),
979 		old_n_core_fields(prebuilt_arg->table->indexes.start
980 				  ->n_core_fields),
981 		old_n_v_cols(prebuilt_arg->table->n_v_cols),
982 		old_v_cols(prebuilt_arg->table->v_cols),
983 		old_v_col_names(prebuilt_arg->table->v_col_names),
984 		first_alter_pos(0),
985 		allow_not_null(allow_not_null_flag),
986 		page_compression_level(page_compressed
987 				       ? (page_compression_level_arg
988 					  ? uint(page_compression_level_arg)
989 					  : page_zip_level)
990 				       : 0)
991 	{
992 		ut_ad(old_n_cols >= DATA_N_SYS_COLS);
993 		ut_ad(page_compression_level <= 9);
994 #ifdef UNIV_DEBUG
995 		for (ulint i = 0; i < num_to_add_index; i++) {
996 			ut_ad(!add_index[i]->to_be_dropped);
997 		}
998 		for (ulint i = 0; i < num_to_drop_index; i++) {
999 			ut_ad(drop_index[i]->to_be_dropped);
1000 		}
1001 #endif /* UNIV_DEBUG */
1002 
1003 		thr = pars_complete_graph_for_exec(NULL, prebuilt->trx, heap,
1004 			prebuilt);
1005 	}
1006 
~ha_innobase_inplace_ctxha_innobase_inplace_ctx1007 	~ha_innobase_inplace_ctx()
1008 	{
1009 		UT_DELETE(m_stage);
1010 		if (instant_table) {
1011 			ut_ad(!instant_table->id);
1012 			while (dict_index_t* index
1013 			       = UT_LIST_GET_LAST(instant_table->indexes)) {
1014 				UT_LIST_REMOVE(instant_table->indexes, index);
1015 				rw_lock_free(&index->lock);
1016 				dict_mem_index_free(index);
1017 			}
1018 			for (unsigned i = old_n_v_cols; i--; ) {
1019 				old_v_cols[i].~dict_v_col_t();
1020 			}
1021 			if (instant_table->fts) {
1022 				fts_free(instant_table);
1023 			}
1024 			dict_mem_table_free(instant_table);
1025 		}
1026 		mem_heap_free(heap);
1027 	}
1028 
1029 	/** Determine if the table will be rebuilt.
1030 	@return whether the table will be rebuilt */
need_rebuildha_innobase_inplace_ctx1031 	bool need_rebuild () const { return(old_table != new_table); }
1032 
1033 	/** Convert table-rebuilding ALTER to instant ALTER. */
prepare_instantha_innobase_inplace_ctx1034 	void prepare_instant()
1035 	{
1036 		DBUG_ASSERT(need_rebuild());
1037 		DBUG_ASSERT(!is_instant());
1038 		DBUG_ASSERT(old_table->n_cols == old_n_cols);
1039 
1040 		instant_table = new_table;
1041 		new_table = old_table;
1042 		export_vars.innodb_instant_alter_column++;
1043 
1044 		instant_table->prepare_instant(*old_table, col_map,
1045 					       first_alter_pos);
1046 	}
1047 
1048 	/** Adjust table metadata for instant ADD/DROP/reorder COLUMN.
1049 	@return whether the metadata record must be updated */
instant_columnha_innobase_inplace_ctx1050 	bool instant_column()
1051 	{
1052 		DBUG_ASSERT(is_instant());
1053 		DBUG_ASSERT(old_n_fields
1054 			    == old_table->indexes.start->n_fields);
1055 		return old_table->instant_column(*instant_table, col_map);
1056 	}
1057 
1058 	/** Revert prepare_instant() if the transaction is rolled back. */
rollback_instantha_innobase_inplace_ctx1059 	void rollback_instant()
1060 	{
1061 		if (!is_instant()) return;
1062 		old_table->rollback_instant(old_n_cols,
1063 					    old_cols, old_col_names,
1064 					    old_instant,
1065 					    old_fields, old_n_fields,
1066 					    old_n_core_fields,
1067 					    old_n_v_cols, old_v_cols,
1068 					    old_v_col_names,
1069 					    col_map);
1070 	}
1071 
1072 	/** @return whether this is instant ALTER TABLE */
is_instantha_innobase_inplace_ctx1073 	bool is_instant() const
1074 	{
1075 		DBUG_ASSERT(!instant_table || !instant_table->can_be_evicted);
1076 		return instant_table;
1077 	}
1078 
1079 	/** Create an index table where indexes are ordered as follows:
1080 
1081 	IF a new primary key is defined for the table THEN
1082 
1083 		1) New primary key
1084 		2) The remaining keys in key_info
1085 
1086 	ELSE
1087 
1088 		1) All new indexes in the order they arrive from MySQL
1089 
1090 	ENDIF
1091 
1092 	@return key definitions */
1093 	MY_ATTRIBUTE((nonnull, warn_unused_result, malloc))
1094 	inline index_def_t*
1095 	create_key_defs(
1096 		const Alter_inplace_info*	ha_alter_info,
1097 				/*!< in: alter operation */
1098 		const TABLE*			altered_table,
1099 				/*!< in: MySQL table that is being altered */
1100 		ulint&				n_fts_add,
1101 				/*!< out: number of FTS indexes to be created */
1102 		ulint&				fts_doc_id_col,
1103 				/*!< in: The column number for Doc ID */
1104 		bool&				add_fts_doc_id,
1105 				/*!< in: whether we need to add new DOC ID
1106 				column for FTS index */
1107 		bool&				add_fts_doc_idx,
1108 				/*!< in: whether we need to add new DOC ID
1109 				index for FTS index */
1110 		const TABLE*			table);
1111 				/*!< in: MySQL table that is being altered */
1112 
1113 	/** Share context between partitions.
1114 	@param[in] ctx	context from another partition of the table */
set_shared_dataha_innobase_inplace_ctx1115 	void set_shared_data(const inplace_alter_handler_ctx& ctx)
1116 	{
1117 		if (add_autoinc != ULINT_UNDEFINED) {
1118 			const ha_innobase_inplace_ctx& ha_ctx =
1119 				static_cast<const ha_innobase_inplace_ctx&>
1120 				(ctx);
1121 			/* When adding an AUTO_INCREMENT column to a
1122 			partitioned InnoDB table, we must share the
1123 			sequence for all partitions. */
1124 			ut_ad(ha_ctx.add_autoinc == add_autoinc);
1125 			ut_ad(ha_ctx.sequence.last());
1126 			sequence = ha_ctx.sequence;
1127 		}
1128 	}
1129 
1130    /** @return whether the given column is being added */
is_new_vcolha_innobase_inplace_ctx1131    bool is_new_vcol(const dict_v_col_t &v_col) const
1132    {
1133      for (ulint i= 0; i < num_to_add_vcol; i++)
1134        if (&add_vcol[i] == &v_col)
1135          return true;
1136      return false;
1137    }
1138 
1139   /** During rollback, make newly added indexes point to
1140   newly added virtual columns. */
clean_new_vcol_indexha_innobase_inplace_ctx1141   void clean_new_vcol_index()
1142   {
1143     ut_ad(old_table == new_table);
1144     const dict_index_t *index= dict_table_get_first_index(old_table);
1145     while ((index= dict_table_get_next_index(index)) != NULL)
1146     {
1147       if (!index->has_virtual() || index->is_committed())
1148         continue;
1149       ulint n_drop_new_vcol= index->get_new_n_vcol();
1150       for (ulint i= 0; n_drop_new_vcol && i < index->n_fields; i++)
1151       {
1152         dict_col_t *col= index->fields[i].col;
1153         /* Skip the non-virtual and old virtual columns */
1154         if (!col->is_virtual())
1155           continue;
1156         dict_v_col_t *vcol= reinterpret_cast<dict_v_col_t*>(col);
1157         if (!is_new_vcol(*vcol))
1158           continue;
1159 
1160         index->fields[i].col= &index->new_vcol_info->
1161           add_drop_v_col(index->heap, vcol, --n_drop_new_vcol)->m_col;
1162       }
1163     }
1164   }
1165 
1166 private:
1167 	// Disable copying
1168 	ha_innobase_inplace_ctx(const ha_innobase_inplace_ctx&);
1169 	ha_innobase_inplace_ctx& operator=(const ha_innobase_inplace_ctx&);
1170 };
1171 
1172 /********************************************************************//**
1173 Get the upper limit of the MySQL integral and floating-point type.
1174 @return maximum allowed value for the field */
1175 UNIV_INTERN
1176 ulonglong
1177 innobase_get_int_col_max_value(
1178 /*===========================*/
1179 	const Field*	field);	/*!< in: MySQL field */
1180 
1181 /* Report an InnoDB error to the client by invoking my_error(). */
1182 static ATTRIBUTE_COLD __attribute__((nonnull))
1183 void
my_error_innodb(dberr_t error,const char * table,ulint flags)1184 my_error_innodb(
1185 /*============*/
1186 	dberr_t		error,	/*!< in: InnoDB error code */
1187 	const char*	table,	/*!< in: table name */
1188 	ulint		flags)	/*!< in: table flags */
1189 {
1190 	switch (error) {
1191 	case DB_MISSING_HISTORY:
1192 		my_error(ER_TABLE_DEF_CHANGED, MYF(0));
1193 		break;
1194 	case DB_RECORD_NOT_FOUND:
1195 		my_error(ER_KEY_NOT_FOUND, MYF(0), table);
1196 		break;
1197 	case DB_DEADLOCK:
1198 		my_error(ER_LOCK_DEADLOCK, MYF(0));
1199 		break;
1200 	case DB_LOCK_WAIT_TIMEOUT:
1201 		my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0));
1202 		break;
1203 	case DB_INTERRUPTED:
1204 		my_error(ER_QUERY_INTERRUPTED, MYF(0));
1205 		break;
1206 	case DB_OUT_OF_MEMORY:
1207 		my_error(ER_OUT_OF_RESOURCES, MYF(0));
1208 		break;
1209 	case DB_OUT_OF_FILE_SPACE:
1210 		my_error(ER_RECORD_FILE_FULL, MYF(0), table);
1211 		break;
1212 	case DB_TEMP_FILE_WRITE_FAIL:
1213 		my_error(ER_TEMP_FILE_WRITE_FAILURE, MYF(0));
1214 		break;
1215 	case DB_TOO_BIG_INDEX_COL:
1216 		my_error(ER_INDEX_COLUMN_TOO_LONG, MYF(0),
1217 			 (ulong) DICT_MAX_FIELD_LEN_BY_FORMAT_FLAG(flags));
1218 		break;
1219 	case DB_TOO_MANY_CONCURRENT_TRXS:
1220 		my_error(ER_TOO_MANY_CONCURRENT_TRXS, MYF(0));
1221 		break;
1222 	case DB_LOCK_TABLE_FULL:
1223 		my_error(ER_LOCK_TABLE_FULL, MYF(0));
1224 		break;
1225 	case DB_UNDO_RECORD_TOO_BIG:
1226 		my_error(ER_UNDO_RECORD_TOO_BIG, MYF(0));
1227 		break;
1228 	case DB_CORRUPTION:
1229 		my_error(ER_NOT_KEYFILE, MYF(0), table);
1230 		break;
1231 	case DB_TOO_BIG_RECORD: {
1232 		/* Note that in page0zip.ic page_zip_rec_needs_ext() rec_size
1233 		is limited to COMPRESSED_REC_MAX_DATA_SIZE (16K) or
1234 		REDUNDANT_REC_MAX_DATA_SIZE (16K-1). */
1235 		bool comp = !!(flags & DICT_TF_COMPACT);
1236 		ulint free_space = page_get_free_space_of_empty(comp) / 2;
1237 
1238 		if (free_space >= ulint(comp ? COMPRESSED_REC_MAX_DATA_SIZE :
1239 					  REDUNDANT_REC_MAX_DATA_SIZE)) {
1240 			free_space = (comp ? COMPRESSED_REC_MAX_DATA_SIZE :
1241 				REDUNDANT_REC_MAX_DATA_SIZE) - 1;
1242 		}
1243 
1244 		my_error(ER_TOO_BIG_ROWSIZE, MYF(0), free_space);
1245 		break;
1246 	}
1247 	case DB_INVALID_NULL:
1248 		/* TODO: report the row, as we do for DB_DUPLICATE_KEY */
1249 		my_error(ER_INVALID_USE_OF_NULL, MYF(0));
1250 		break;
1251 	case DB_CANT_CREATE_GEOMETRY_OBJECT:
1252 		my_error(ER_CANT_CREATE_GEOMETRY_OBJECT, MYF(0));
1253 		break;
1254 	case DB_TABLESPACE_EXISTS:
1255 		my_error(ER_TABLESPACE_EXISTS, MYF(0), table);
1256 		break;
1257 
1258 #ifdef UNIV_DEBUG
1259 	case DB_SUCCESS:
1260 	case DB_DUPLICATE_KEY:
1261 	case DB_ONLINE_LOG_TOO_BIG:
1262 		/* These codes should not be passed here. */
1263 		ut_error;
1264 #endif /* UNIV_DEBUG */
1265 	default:
1266 		my_error(ER_GET_ERRNO, MYF(0), error, "InnoDB");
1267 		break;
1268 	}
1269 }
1270 
1271 /** Determine if fulltext indexes exist in a given table.
1272 @param table MySQL table
1273 @return number of fulltext indexes */
innobase_fulltext_exist(const TABLE * table)1274 static uint innobase_fulltext_exist(const TABLE* table)
1275 {
1276 	uint count = 0;
1277 
1278 	for (uint i = 0; i < table->s->keys; i++) {
1279 		if (table->key_info[i].flags & HA_FULLTEXT) {
1280 			count++;
1281 		}
1282 	}
1283 
1284 	return count;
1285 }
1286 
1287 /** Determine whether indexed virtual columns exist in a table.
1288 @param[in]	table	table definition
1289 @return	whether indexes exist on virtual columns */
innobase_indexed_virtual_exist(const TABLE * table)1290 static bool innobase_indexed_virtual_exist(const TABLE* table)
1291 {
1292 	const KEY* const end = &table->key_info[table->s->keys];
1293 
1294 	for (const KEY* key = table->key_info; key < end; key++) {
1295 		const KEY_PART_INFO* const key_part_end = key->key_part
1296 			+ key->user_defined_key_parts;
1297 		for (const KEY_PART_INFO* key_part = key->key_part;
1298 		     key_part < key_part_end; key_part++) {
1299 			if (!key_part->field->stored_in_db())
1300 				return true;
1301 		}
1302 	}
1303 
1304 	return false;
1305 }
1306 
1307 /** Determine if spatial indexes exist in a given table.
1308 @param table MySQL table
1309 @return whether spatial indexes exist on the table */
1310 static
1311 bool
innobase_spatial_exist(const TABLE * table)1312 innobase_spatial_exist(
1313 /*===================*/
1314 	const   TABLE*  table)
1315 {
1316 	for (uint i = 0; i < table->s->keys; i++) {
1317 	       if (table->key_info[i].flags & HA_SPATIAL) {
1318 		       return(true);
1319 	       }
1320 	}
1321 
1322 	return(false);
1323 }
1324 
1325 /** Determine if ALTER_OPTIONS requires rebuilding the table.
1326 @param[in] ha_alter_info	the ALTER TABLE operation
1327 @param[in] table		metadata before ALTER TABLE
1328 @return whether it is mandatory to rebuild the table */
alter_options_need_rebuild(const Alter_inplace_info * ha_alter_info,const TABLE * table)1329 static bool alter_options_need_rebuild(
1330 	const Alter_inplace_info*	ha_alter_info,
1331 	const TABLE*			table)
1332 {
1333 	DBUG_ASSERT(ha_alter_info->handler_flags & ALTER_OPTIONS);
1334 
1335 	if (ha_alter_info->create_info->used_fields
1336 	    & (HA_CREATE_USED_ROW_FORMAT
1337 	       | HA_CREATE_USED_KEY_BLOCK_SIZE)) {
1338 		/* Specifying ROW_FORMAT or KEY_BLOCK_SIZE requires
1339 		rebuilding the table. (These attributes in the .frm
1340 		file may disagree with the InnoDB data dictionary, and
1341 		the interpretation of thse attributes depends on
1342 		InnoDB parameters. That is why we for now always
1343 		require a rebuild when these attributes are specified.) */
1344 		return true;
1345 	}
1346 
1347 	const ha_table_option_struct& alt_opt=
1348 			*ha_alter_info->create_info->option_struct;
1349 	const ha_table_option_struct& opt= *table->s->option_struct;
1350 
1351 	/* Allow an instant change to enable page_compressed,
1352 	and any change of page_compression_level. */
1353 	if ((!alt_opt.page_compressed && opt.page_compressed)
1354 	    || alt_opt.encryption != opt.encryption
1355 	    || alt_opt.encryption_key_id != opt.encryption_key_id) {
1356 		return(true);
1357 	}
1358 
1359 	return false;
1360 }
1361 
1362 /** Determine if ALTER TABLE needs to rebuild the table
1363 (or perform instant operation).
1364 @param[in] ha_alter_info	the ALTER TABLE operation
1365 @param[in] table		metadata before ALTER TABLE
1366 @return whether it is necessary to rebuild the table or to alter columns */
1367 static MY_ATTRIBUTE((nonnull, warn_unused_result))
1368 bool
innobase_need_rebuild(const Alter_inplace_info * ha_alter_info,const TABLE * table)1369 innobase_need_rebuild(
1370 	const Alter_inplace_info*	ha_alter_info,
1371 	const TABLE*			table)
1372 {
1373 	if ((ha_alter_info->handler_flags & ~(INNOBASE_INPLACE_IGNORE
1374 					      | INNOBASE_ALTER_NOREBUILD
1375 					      | INNOBASE_ALTER_INSTANT))
1376 	    == ALTER_OPTIONS) {
1377 		return alter_options_need_rebuild(ha_alter_info, table);
1378 	}
1379 
1380 	return !!(ha_alter_info->handler_flags & INNOBASE_ALTER_REBUILD);
1381 }
1382 
1383 /** Check if virtual column in old and new table are in order, excluding
1384 those dropped column. This is needed because when we drop a virtual column,
1385 ALTER_VIRTUAL_COLUMN_ORDER is also turned on, so we can't decide if this
1386 is a real ORDER change or just DROP COLUMN
1387 @param[in]	table		old TABLE
1388 @param[in]	altered_table	new TABLE
1389 @param[in]	ha_alter_info	Structure describing changes to be done
1390 by ALTER TABLE and holding data used during in-place alter.
1391 @return	true is all columns in order, false otherwise. */
1392 static
1393 bool
check_v_col_in_order(const TABLE * table,const TABLE * altered_table,Alter_inplace_info * ha_alter_info)1394 check_v_col_in_order(
1395 	const TABLE*		table,
1396 	const TABLE*		altered_table,
1397 	Alter_inplace_info*	ha_alter_info)
1398 {
1399 	ulint	j = 0;
1400 
1401 	/* We don't support any adding new virtual column before
1402 	existed virtual column. */
1403 	if (ha_alter_info->handler_flags
1404               & ALTER_ADD_VIRTUAL_COLUMN) {
1405 		bool			has_new = false;
1406 
1407 		for (const Create_field& new_field :
1408 		     ha_alter_info->alter_info->create_list) {
1409 			if (new_field.stored_in_db()) {
1410 				continue;
1411 			}
1412 
1413 			/* Found a new added virtual column. */
1414 			if (!new_field.field) {
1415 				has_new = true;
1416 				continue;
1417 			}
1418 
1419 			/* If there's any old virtual column
1420 			after the new added virtual column,
1421 			order must be changed. */
1422 			if (has_new) {
1423 				return(false);
1424 			}
1425 		}
1426 	}
1427 
1428 	/* directly return true if ALTER_VIRTUAL_COLUMN_ORDER is not on */
1429 	if (!(ha_alter_info->handler_flags
1430               & ALTER_VIRTUAL_COLUMN_ORDER)) {
1431 		return(true);
1432 	}
1433 
1434 	for (ulint i = 0; i < table->s->fields; i++) {
1435 		Field*		field = table->field[i];
1436 
1437 		if (field->stored_in_db()) {
1438 			continue;
1439 		}
1440 
1441 		if (field->flags & FIELD_IS_DROPPED) {
1442 			continue;
1443 		}
1444 
1445 		/* Now check if the next virtual column in altered table
1446 		matches this column */
1447 		while (j < altered_table->s->fields) {
1448 			 Field*  new_field = altered_table->s->field[j];
1449 
1450 			if (new_field->stored_in_db()) {
1451 				j++;
1452 				continue;
1453 			}
1454 
1455 			if (my_strcasecmp(system_charset_info,
1456 					  field->field_name.str,
1457 					  new_field->field_name.str) != 0) {
1458 				/* different column */
1459 				return(false);
1460 			} else {
1461 				j++;
1462 				break;
1463 			}
1464 		}
1465 
1466 		if (j > altered_table->s->fields) {
1467 			/* there should not be less column in new table
1468 			without them being in drop list */
1469 			ut_ad(0);
1470 			return(false);
1471 		}
1472 	}
1473 
1474 	return(true);
1475 }
1476 
1477 /** Determine if an instant operation is possible for altering columns.
1478 @param[in]	ib_table	InnoDB table definition
1479 @param[in]	ha_alter_info	the ALTER TABLE operation
1480 @param[in]	table		table definition before ALTER TABLE
1481 @param[in]	altered_table	table definition after ALTER TABLE
1482 @param[in]	strict		whether to ensure that user records fit */
1483 static
1484 bool
instant_alter_column_possible(const dict_table_t & ib_table,const Alter_inplace_info * ha_alter_info,const TABLE * table,const TABLE * altered_table,bool strict)1485 instant_alter_column_possible(
1486 	const dict_table_t&		ib_table,
1487 	const Alter_inplace_info*	ha_alter_info,
1488 	const TABLE*			table,
1489 	const TABLE*			altered_table,
1490 	bool				strict)
1491 {
1492 	const dict_index_t* const pk = ib_table.indexes.start;
1493 	ut_ad(pk->is_primary());
1494 	ut_ad(!pk->has_virtual());
1495 
1496 	if (ha_alter_info->handler_flags
1497 	    & (ALTER_STORED_COLUMN_ORDER | ALTER_DROP_STORED_COLUMN
1498 	       | ALTER_ADD_STORED_BASE_COLUMN)) {
1499 #if 1 // MDEV-17459: adjust fts_fetch_doc_from_rec() and friends; remove this
1500 		if (ib_table.fts || innobase_fulltext_exist(altered_table))
1501 			return false;
1502 #endif
1503 #if 1 // MDEV-17468: fix bugs with indexed virtual columns & remove this
1504 		for (const dict_index_t* index = ib_table.indexes.start;
1505 		     index; index = index->indexes.next) {
1506 			if (index->has_virtual()) {
1507 				ut_ad(ib_table.n_v_cols
1508 				      || index->is_corrupted());
1509 				return false;
1510 			}
1511 		}
1512 #endif
1513 		uint n_add = 0, n_nullable = 0, lenlen = 0;
1514 		const uint blob_prefix = dict_table_has_atomic_blobs(&ib_table)
1515 			? 0
1516 			: REC_ANTELOPE_MAX_INDEX_COL_LEN;
1517 		const uint min_local_len = blob_prefix
1518 			? blob_prefix + FIELD_REF_SIZE
1519 			: 2 * FIELD_REF_SIZE;
1520 		size_t min_size = 0, max_size = 0;
1521 		Field** af = altered_table->field;
1522 		Field** const end = altered_table->field
1523 			+ altered_table->s->fields;
1524 		List_iterator_fast<Create_field> cf_it(
1525 			ha_alter_info->alter_info->create_list);
1526 
1527 		for (; af < end; af++) {
1528 			const Create_field* cf = cf_it++;
1529 			if (!(*af)->stored_in_db() || cf->field) {
1530 				/* Virtual or pre-existing column */
1531 				continue;
1532 			}
1533 			const bool nullable = (*af)->real_maybe_null();
1534 			const bool is_null = (*af)->is_real_null();
1535 			ut_ad(!is_null || nullable);
1536 			n_nullable += nullable;
1537 			n_add++;
1538 			uint l;
1539 			switch ((*af)->type()) {
1540 			case MYSQL_TYPE_VARCHAR:
1541 				l = reinterpret_cast<const Field_varstring*>
1542 					(*af)->get_length();
1543 			variable_length:
1544 				if (l >= min_local_len) {
1545 					max_size += blob_prefix
1546 						+ FIELD_REF_SIZE;
1547 					if (!is_null) {
1548 						min_size += blob_prefix
1549 							+ FIELD_REF_SIZE;
1550 					}
1551 					lenlen += 2;
1552 				} else {
1553 					if (!is_null) {
1554 						min_size += l;
1555 					}
1556 					l = (*af)->pack_length();
1557 					max_size += l;
1558 					lenlen += l > 255 ? 2 : 1;
1559 				}
1560 				break;
1561 			case MYSQL_TYPE_GEOMETRY:
1562 			case MYSQL_TYPE_TINY_BLOB:
1563 			case MYSQL_TYPE_MEDIUM_BLOB:
1564 			case MYSQL_TYPE_BLOB:
1565 			case MYSQL_TYPE_LONG_BLOB:
1566 				l = reinterpret_cast<const Field_blob*>
1567 					((*af))->get_length();
1568 				goto variable_length;
1569 			default:
1570 				l = (*af)->pack_length();
1571 				if (l > 255 && ib_table.not_redundant()) {
1572 					goto variable_length;
1573 				}
1574 				max_size += l;
1575 				if (!is_null) {
1576 					min_size += l;
1577 				}
1578 			}
1579 		}
1580 
1581 		ulint n_fields = pk->n_fields + n_add;
1582 
1583 		if (n_fields >= REC_MAX_N_USER_FIELDS + DATA_N_SYS_COLS) {
1584 			return false;
1585 		}
1586 
1587 		if (pk->is_gen_clust()) {
1588 			min_size += DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN
1589 				+ DATA_ROW_ID_LEN;
1590 			max_size += DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN
1591 				+ DATA_ROW_ID_LEN;
1592 		} else {
1593 			min_size += DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN;
1594 			max_size += DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN;
1595 		}
1596 
1597 		uint i = pk->n_fields;
1598 		while (i-- > pk->n_core_fields) {
1599 			const dict_field_t& f = pk->fields[i];
1600 			if (f.col->is_nullable()) {
1601 				n_nullable++;
1602 				if (!f.col->is_dropped()
1603 				    && f.col->def_val.data) {
1604 					goto instantly_added_column;
1605 				}
1606 			} else if (f.fixed_len
1607 				   && (f.fixed_len <= 255
1608 				       || !ib_table.not_redundant())) {
1609 				if (ib_table.not_redundant()
1610 				    || !f.col->is_dropped()) {
1611 					min_size += f.fixed_len;
1612 					max_size += f.fixed_len;
1613 				}
1614 			} else if (f.col->is_dropped() || !f.col->is_added()) {
1615 				lenlen++;
1616 				goto set_max_size;
1617 			} else {
1618 instantly_added_column:
1619 				ut_ad(f.col->is_added());
1620 				if (f.col->def_val.len >= min_local_len) {
1621 					min_size += blob_prefix
1622 						+ FIELD_REF_SIZE;
1623 					lenlen += 2;
1624 				} else {
1625 					min_size += f.col->def_val.len;
1626 					lenlen += f.col->def_val.len
1627 						> 255 ? 2 : 1;
1628 				}
1629 set_max_size:
1630 				if (f.fixed_len
1631 				    && (f.fixed_len <= 255
1632 					|| !ib_table.not_redundant())) {
1633 					max_size += f.fixed_len;
1634 				} else if (f.col->len >= min_local_len) {
1635 					max_size += blob_prefix
1636 						+ FIELD_REF_SIZE;
1637 				} else {
1638 					max_size += f.col->len;
1639 				}
1640 			}
1641 		}
1642 
1643 		do {
1644 			const dict_field_t& f = pk->fields[i];
1645 			if (f.col->is_nullable()) {
1646 				n_nullable++;
1647 			} else if (f.fixed_len) {
1648 				min_size += f.fixed_len;
1649 			} else {
1650 				lenlen++;
1651 			}
1652 		} while (i--);
1653 
1654 		if (ib_table.instant
1655 		    || (ha_alter_info->handler_flags
1656 			& (ALTER_STORED_COLUMN_ORDER
1657 			   | ALTER_DROP_STORED_COLUMN))) {
1658 			n_fields++;
1659 			lenlen += 2;
1660 			min_size += FIELD_REF_SIZE;
1661 		}
1662 
1663 		if (ib_table.not_redundant()) {
1664 			min_size += REC_N_NEW_EXTRA_BYTES
1665 				+ UT_BITS_IN_BYTES(n_nullable)
1666 				+ lenlen;
1667 		} else {
1668 			min_size += (n_fields > 255 || min_size > 255)
1669 				? n_fields * 2 : n_fields;
1670 			min_size += REC_N_OLD_EXTRA_BYTES;
1671 		}
1672 
1673 		if (page_zip_rec_needs_ext(min_size, ib_table.not_redundant(),
1674 					   0, 0)) {
1675 			return false;
1676 		}
1677 
1678 		if (strict && page_zip_rec_needs_ext(max_size,
1679 						     ib_table.not_redundant(),
1680 						     0, 0)) {
1681 			return false;
1682 		}
1683 	}
1684 	// Making table system-versioned instantly is not implemented yet.
1685 	if (ha_alter_info->handler_flags & ALTER_ADD_SYSTEM_VERSIONING) {
1686 		return false;
1687 	}
1688 
1689 	static constexpr alter_table_operations avoid_rebuild
1690 		= ALTER_ADD_STORED_BASE_COLUMN
1691 		| ALTER_DROP_STORED_COLUMN
1692 		| ALTER_STORED_COLUMN_ORDER
1693 		| ALTER_COLUMN_NULLABLE;
1694 
1695 	if (!(ha_alter_info->handler_flags & avoid_rebuild)) {
1696 		alter_table_operations flags = ha_alter_info->handler_flags
1697 			& ~avoid_rebuild;
1698 		/* None of the flags are set that we can handle
1699 		specially to avoid rebuild. In this case, we can
1700 		allow ALGORITHM=INSTANT, except if some requested
1701 		operation requires that the table be rebuilt. */
1702 		if (flags & INNOBASE_ALTER_REBUILD) {
1703 			return false;
1704 		}
1705 		if ((flags & ALTER_OPTIONS)
1706 		    && alter_options_need_rebuild(ha_alter_info, table)) {
1707 			return false;
1708 		}
1709 	} else if (!ib_table.supports_instant()) {
1710 		return false;
1711 	}
1712 
1713 	/* At the moment, we disallow ADD [UNIQUE] INDEX together with
1714 	instant ADD COLUMN.
1715 
1716 	The main reason is that the work of instant ADD must be done
1717 	in commit_inplace_alter_table().  For the rollback_instant()
1718 	to work, we must add the columns to dict_table_t beforehand,
1719 	and roll back those changes in case the transaction is rolled
1720 	back.
1721 
1722 	If we added the columns to the dictionary cache already in the
1723 	prepare_inplace_alter_table(), we would have to deal with
1724 	column number mismatch in ha_innobase::open(), write_row() and
1725 	other functions. */
1726 
1727 	/* FIXME: allow instant ADD COLUMN together with
1728 	INNOBASE_ONLINE_CREATE (ADD [UNIQUE] INDEX) on pre-existing
1729 	columns. */
1730 	if (ha_alter_info->handler_flags
1731 	    & ((INNOBASE_ALTER_REBUILD | INNOBASE_ONLINE_CREATE)
1732 	       & ~ALTER_DROP_STORED_COLUMN
1733 	       & ~ALTER_STORED_COLUMN_ORDER
1734 	       & ~ALTER_ADD_STORED_BASE_COLUMN
1735 	       & ~ALTER_COLUMN_NULLABLE
1736 	       & ~ALTER_OPTIONS)) {
1737 		return false;
1738 	}
1739 
1740 	if ((ha_alter_info->handler_flags & ALTER_OPTIONS)
1741 	    && alter_options_need_rebuild(ha_alter_info, table)) {
1742 		return false;
1743 	}
1744 
1745 	if (ha_alter_info->handler_flags & ALTER_COLUMN_NULLABLE) {
1746 		if (ib_table.not_redundant()) {
1747 			/* Instantaneous removal of NOT NULL is
1748 			only supported for ROW_FORMAT=REDUNDANT. */
1749 			return false;
1750 		}
1751 		if (ib_table.fts_doc_id_index
1752 		    && !innobase_fulltext_exist(altered_table)) {
1753 			/* Removing hidden FTS_DOC_ID_INDEX(FTS_DOC_ID)
1754 			requires that the table be rebuilt. */
1755 			return false;
1756 		}
1757 
1758 		Field** af = altered_table->field;
1759 		Field** const end = altered_table->field
1760 			+ altered_table->s->fields;
1761 		for (unsigned c = 0; af < end; af++) {
1762 			if (!(*af)->stored_in_db()) {
1763 				continue;
1764 			}
1765 
1766 			const dict_col_t* col = dict_table_get_nth_col(
1767 				&ib_table, c++);
1768 
1769 			if (!col->ord_part || col->is_nullable()
1770 			    || !(*af)->real_maybe_null()) {
1771 				continue;
1772 			}
1773 
1774 			/* The column would be changed from NOT NULL.
1775 			Ensure that it is not a clustered index key. */
1776 			for (auto i = pk->n_uniq; i--; ) {
1777 				if (pk->fields[i].col == col) {
1778 					return false;
1779 				}
1780 			}
1781 		}
1782 	}
1783 
1784 	return true;
1785 }
1786 
1787 /** Check whether the non-const default value for the field
1788 @param[in]	field	field which could be added or changed
1789 @return true if the non-const default is present. */
is_non_const_value(Field * field)1790 static bool is_non_const_value(Field* field)
1791 {
1792 	return field->default_value
1793 		&& field->default_value->flags
1794 		& uint(~(VCOL_SESSION_FUNC | VCOL_TIME_FUNC));
1795 }
1796 
1797 /** Set default value for the field.
1798 @param[in]	field	field which could be added or changed
1799 @return true if the default value is set. */
set_default_value(Field * field)1800 static bool set_default_value(Field* field)
1801 {
1802 	/* The added/changed NOT NULL column lacks a DEFAULT value,
1803 	   or the DEFAULT is the same for all rows.
1804 	   (Time functions, such as CURRENT_TIMESTAMP(),
1805 	   are evaluated from a timestamp that is assigned
1806 	   at the start of the statement. Session
1807 	   functions, such as USER(), always evaluate the
1808 	   same within a statement.) */
1809 
1810 	ut_ad(!is_non_const_value(field));
1811 
1812 	/* Compute the DEFAULT values of non-constant columns
1813 	   (VCOL_SESSION_FUNC | VCOL_TIME_FUNC). */
1814 	switch (field->set_default()) {
1815 	case 0: /* OK */
1816 	case 3: /* DATETIME to TIME or DATE conversion */
1817 		return true;
1818 	case -1: /* OOM, or GEOMETRY type mismatch */
1819 	case 1:  /* A number adjusted to the min/max value */
1820 	case 2:  /* String truncation, or conversion problem */
1821 		break;
1822 	}
1823 
1824 	return false;
1825 }
1826 
1827 /** Check whether the table has the FTS_DOC_ID column
1828 @param[in]	table		InnoDB table with fulltext index
1829 @param[in]	altered_table	MySQL table with fulltext index
1830 @param[out]	fts_doc_col_no	The column number for Doc ID,
1831 				or ULINT_UNDEFINED if it is of wrong type
1832 @param[out]	num_v		Number of virtual column
1833 @param[in]	check_only	check only whether fts doc id exist.
1834 @return whether there exists an FTS_DOC_ID column */
1835 static
1836 bool
innobase_fts_check_doc_id_col(const dict_table_t * table,const TABLE * altered_table,ulint * fts_doc_col_no,ulint * num_v,bool check_only=false)1837 innobase_fts_check_doc_id_col(
1838 	const dict_table_t*	table,
1839 	const TABLE*		altered_table,
1840 	ulint*			fts_doc_col_no,
1841 	ulint*			num_v,
1842 	bool			check_only=false)
1843 {
1844 	*fts_doc_col_no = ULINT_UNDEFINED;
1845 
1846 	const uint n_cols = altered_table->s->fields;
1847 	ulint	i;
1848 	int	err = 0;
1849 	*num_v = 0;
1850 
1851 	for (i = 0; i < n_cols; i++) {
1852 		const Field*	field = altered_table->field[i];
1853 
1854 		if (!field->stored_in_db()) {
1855 			(*num_v)++;
1856 		}
1857 
1858 		if (my_strcasecmp(system_charset_info,
1859 				  field->field_name.str, FTS_DOC_ID_COL_NAME)) {
1860 			continue;
1861 		}
1862 
1863 		if (strcmp(field->field_name.str, FTS_DOC_ID_COL_NAME)) {
1864 			err = ER_WRONG_COLUMN_NAME;
1865 		} else if (field->type() != MYSQL_TYPE_LONGLONG
1866 			   || field->pack_length() != 8
1867 			   || field->real_maybe_null()
1868 			   || !(field->flags & UNSIGNED_FLAG)
1869 			   || !field->stored_in_db()) {
1870 			err = ER_INNODB_FT_WRONG_DOCID_COLUMN;
1871 		} else {
1872 			*fts_doc_col_no = i - *num_v;
1873 		}
1874 
1875 		if (err && !check_only) {
1876 			my_error(err, MYF(0), field->field_name.str);
1877 		}
1878 
1879 		return(true);
1880 	}
1881 
1882 	if (!table) {
1883 		return(false);
1884 	}
1885 
1886 	/* Not to count the virtual columns */
1887 	i -= *num_v;
1888 
1889 	for (; i + DATA_N_SYS_COLS < (uint) table->n_cols; i++) {
1890 		const char*     name = dict_table_get_col_name(table, i);
1891 
1892 		if (strcmp(name, FTS_DOC_ID_COL_NAME) == 0) {
1893 #ifdef UNIV_DEBUG
1894 			const dict_col_t*       col;
1895 
1896 			col = dict_table_get_nth_col(table, i);
1897 
1898 			/* Because the FTS_DOC_ID does not exist in
1899 			the MySQL data dictionary, this must be the
1900 			internally created FTS_DOC_ID column. */
1901 			ut_ad(col->mtype == DATA_INT);
1902 			ut_ad(col->len == 8);
1903 			ut_ad(col->prtype & DATA_NOT_NULL);
1904 			ut_ad(col->prtype & DATA_UNSIGNED);
1905 #endif /* UNIV_DEBUG */
1906 			*fts_doc_col_no = i;
1907 			return(true);
1908 		}
1909 	}
1910 
1911 	return(false);
1912 }
1913 
1914 /** Check whether the table is empty.
1915 @param[in]	table			table to be checked
1916 @param[in]	ignore_delete_marked	Ignore the delete marked
1917 					flag record
1918 @return true if table is empty */
innobase_table_is_empty(const dict_table_t * table,bool ignore_delete_marked=true)1919 static bool innobase_table_is_empty(const dict_table_t *table,
1920 				    bool ignore_delete_marked=true)
1921 {
1922   if (!table->space)
1923     return false;
1924   dict_index_t *clust_index= dict_table_get_first_index(table);
1925   mtr_t mtr;
1926   btr_pcur_t pcur;
1927   buf_block_t *block;
1928   page_cur_t *cur;
1929   const rec_t *rec;
1930   bool next_page= false;
1931 
1932   mtr.start();
1933   btr_pcur_open_at_index_side(true, clust_index, BTR_SEARCH_LEAF,
1934                               &pcur, true, 0, &mtr);
1935   btr_pcur_move_to_next_user_rec(&pcur, &mtr);
1936   if (!rec_is_metadata(btr_pcur_get_rec(&pcur), *clust_index))
1937     btr_pcur_move_to_prev_on_page(&pcur);
1938 scan_leaf:
1939   cur= btr_pcur_get_page_cur(&pcur);
1940   page_cur_move_to_next(cur);
1941 next_page:
1942   if (next_page)
1943   {
1944     uint32_t next_page_no= btr_page_get_next(page_cur_get_page(cur));
1945     if (next_page_no == FIL_NULL)
1946     {
1947       mtr.commit();
1948       return true;
1949     }
1950 
1951     next_page= false;
1952     block= page_cur_get_block(cur);
1953     block= btr_block_get(*clust_index, next_page_no, BTR_SEARCH_LEAF, false,
1954                          &mtr);
1955     btr_leaf_page_release(page_cur_get_block(cur), BTR_SEARCH_LEAF, &mtr);
1956     page_cur_set_before_first(block, cur);
1957     page_cur_move_to_next(cur);
1958   }
1959 
1960   rec= page_cur_get_rec(cur);
1961   if (rec_get_deleted_flag(rec, dict_table_is_comp(table)))
1962   {
1963     if (ignore_delete_marked)
1964       goto scan_leaf;
1965 non_empty:
1966     mtr.commit();
1967     return false;
1968   }
1969   else if (!page_rec_is_supremum(rec))
1970     goto non_empty;
1971   else
1972   {
1973     next_page= true;
1974     goto next_page;
1975   }
1976   goto scan_leaf;
1977 }
1978 
1979 /** Check if InnoDB supports a particular alter table in-place
1980 @param altered_table TABLE object for new version of table.
1981 @param ha_alter_info Structure describing changes to be done
1982 by ALTER TABLE and holding data used during in-place alter.
1983 
1984 @retval HA_ALTER_INPLACE_NOT_SUPPORTED Not supported
1985 @retval HA_ALTER_INPLACE_INSTANT
1986 MDL_EXCLUSIVE is needed for executing prepare_inplace_alter_table()
1987 and commit_inplace_alter_table(). inplace_alter_table() will not be called.
1988 @retval HA_ALTER_INPLACE_COPY_NO_LOCK
1989 MDL_EXCLUSIVE in prepare_inplace_alter_table(), which can be downgraded to
1990 LOCK=NONE for rebuilding the table in inplace_alter_table()
1991 @retval HA_ALTER_INPLACE_COPY_LOCK
1992 MDL_EXCLUSIVE in prepare_inplace_alter_table(), which can be downgraded to
1993 LOCK=SHARED for rebuilding the table in inplace_alter_table()
1994 @retval HA_ALTER_INPLACE_NOCOPY_NO_LOCK
1995 MDL_EXCLUSIVE in prepare_inplace_alter_table(), which can be downgraded to
1996 LOCK=NONE for inplace_alter_table() which will not rebuild the table
1997 @retval HA_ALTER_INPLACE_NOCOPY_LOCK
1998 MDL_EXCLUSIVE in prepare_inplace_alter_table(), which can be downgraded to
1999 LOCK=SHARED for inplace_alter_table() which will not rebuild the table
2000 */
2001 
2002 enum_alter_inplace_result
check_if_supported_inplace_alter(TABLE * altered_table,Alter_inplace_info * ha_alter_info)2003 ha_innobase::check_if_supported_inplace_alter(
2004 	TABLE*			altered_table,
2005 	Alter_inplace_info*	ha_alter_info)
2006 {
2007 	DBUG_ENTER("check_if_supported_inplace_alter");
2008 
2009 	if ((ha_alter_info->handler_flags
2010 	     & INNOBASE_ALTER_VERSIONED_REBUILD)
2011 	    && altered_table->versioned(VERS_TIMESTAMP)) {
2012 		ha_alter_info->unsupported_reason =
2013 			"Not implemented for system-versioned timestamp tables";
2014 		DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
2015 	}
2016 
2017 	/* Before 10.2.2 information about virtual columns was not stored in
2018 	system tables. We need to do a full alter to rebuild proper 10.2.2+
2019 	metadata with the information about virtual columns */
2020 	if (omits_virtual_cols(*table_share)) {
2021 		DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
2022 	}
2023 
2024 	if (high_level_read_only) {
2025 		ha_alter_info->unsupported_reason =
2026 			my_get_err_msg(ER_READ_ONLY_MODE);
2027 
2028 		DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
2029 	}
2030 
2031 	if (altered_table->s->fields > REC_MAX_N_USER_FIELDS) {
2032 		/* Deny the inplace ALTER TABLE. MySQL will try to
2033 		re-create the table and ha_innobase::create() will
2034 		return an error too. This is how we effectively
2035 		deny adding too many columns to a table. */
2036 		ha_alter_info->unsupported_reason =
2037 			my_get_err_msg(ER_TOO_MANY_FIELDS);
2038 		DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
2039 	}
2040 
2041 	update_thd();
2042 
2043 	if (ha_alter_info->handler_flags
2044 	    & ~(INNOBASE_INPLACE_IGNORE
2045 		| INNOBASE_ALTER_INSTANT
2046 		| INNOBASE_ALTER_NOREBUILD
2047 		| INNOBASE_ALTER_REBUILD)) {
2048 
2049 		if (ha_alter_info->handler_flags
2050 		    & ALTER_STORED_COLUMN_TYPE) {
2051 			ha_alter_info->unsupported_reason = my_get_err_msg(
2052 				ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_COLUMN_TYPE);
2053 		}
2054 
2055 		DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
2056 	}
2057 
2058 	/* Only support online add foreign key constraint when
2059 	check_foreigns is turned off */
2060 	if ((ha_alter_info->handler_flags & ALTER_ADD_FOREIGN_KEY)
2061 	    && m_prebuilt->trx->check_foreigns) {
2062 		ha_alter_info->unsupported_reason = my_get_err_msg(
2063 			ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_CHECK);
2064 		DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
2065 	}
2066 
2067 	const char* reason_rebuild = NULL;
2068 
2069 	switch (innodb_instant_alter_column_allowed) {
2070 	case 0: /* never */
2071 		if ((ha_alter_info->handler_flags
2072 		     & (ALTER_ADD_STORED_BASE_COLUMN
2073 			| ALTER_STORED_COLUMN_ORDER
2074 			| ALTER_DROP_STORED_COLUMN))
2075 		    || m_prebuilt->table->is_instant()) {
2076 			reason_rebuild =
2077 				"innodb_instant_alter_column_allowed=never";
2078 innodb_instant_alter_column_allowed_reason:
2079 			if (ha_alter_info->handler_flags
2080 			    & ALTER_RECREATE_TABLE) {
2081 				reason_rebuild = NULL;
2082 			} else {
2083 				ha_alter_info->handler_flags
2084 					|= ALTER_RECREATE_TABLE;
2085 				ha_alter_info->unsupported_reason
2086 					= reason_rebuild;
2087 			}
2088 		}
2089 		break;
2090 	case 1: /* add_last */
2091 		if ((ha_alter_info->handler_flags
2092 		     & (ALTER_STORED_COLUMN_ORDER | ALTER_DROP_STORED_COLUMN))
2093 		    || m_prebuilt->table->instant) {
2094 			reason_rebuild = "innodb_instant_atler_column_allowed="
2095 				"add_last";
2096 			goto innodb_instant_alter_column_allowed_reason;
2097 		}
2098 	}
2099 
2100 	switch (ha_alter_info->handler_flags & ~INNOBASE_INPLACE_IGNORE) {
2101 	case ALTER_OPTIONS:
2102 		if (alter_options_need_rebuild(ha_alter_info, table)) {
2103 			reason_rebuild = my_get_err_msg(
2104 				ER_ALTER_OPERATION_TABLE_OPTIONS_NEED_REBUILD);
2105 			ha_alter_info->unsupported_reason = reason_rebuild;
2106 			break;
2107 		}
2108 		/* fall through */
2109 	case 0:
2110 		DBUG_RETURN(HA_ALTER_INPLACE_INSTANT);
2111 	}
2112 
2113 	/* InnoDB cannot IGNORE when creating unique indexes. IGNORE
2114 	should silently delete some duplicate rows. Our inplace_alter
2115 	code will not delete anything from existing indexes. */
2116 	if (ha_alter_info->ignore
2117 	    && (ha_alter_info->handler_flags
2118 		& (ALTER_ADD_PK_INDEX | ALTER_ADD_UNIQUE_INDEX))) {
2119 		ha_alter_info->unsupported_reason = my_get_err_msg(
2120 			ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_IGNORE);
2121 		DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
2122 	}
2123 
2124 	/* DROP PRIMARY KEY is only allowed in combination with ADD
2125 	PRIMARY KEY. */
2126 	if ((ha_alter_info->handler_flags
2127 	     & (ALTER_ADD_PK_INDEX | ALTER_DROP_PK_INDEX))
2128 	    == ALTER_DROP_PK_INDEX) {
2129 		ha_alter_info->unsupported_reason = my_get_err_msg(
2130 			ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOPK);
2131 		DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
2132 	}
2133 
2134 	if (ha_alter_info->handler_flags & ALTER_COLUMN_NULLABLE) {
2135 		/* If a NOT NULL attribute is going to be removed and
2136 		a UNIQUE INDEX on the column had been promoted to an
2137 		implicit PRIMARY KEY, the table should be rebuilt by
2138 		ALGORITHM=COPY. (Theoretically, we could support
2139 		rebuilding by ALGORITHM=INPLACE if a PRIMARY KEY is
2140 		going to be added, either explicitly or by promoting
2141 		another UNIQUE KEY.) */
2142 		const uint my_primary_key = altered_table->s->primary_key;
2143 
2144 		if (UNIV_UNLIKELY(my_primary_key >= MAX_KEY)
2145 		    && !dict_index_is_auto_gen_clust(
2146 			    dict_table_get_first_index(m_prebuilt->table))) {
2147 			ha_alter_info->unsupported_reason = my_get_err_msg(
2148 				ER_PRIMARY_CANT_HAVE_NULL);
2149 			DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
2150 		}
2151 	}
2152 
2153 	/*
2154 	  InnoDB in different MariaDB versions was generating different mtype
2155 	  codes for certain types. In some cases the signed/unsigned bit was
2156 	  generated differently too.
2157 
2158 	  Inplace ALTER would change the mtype/unsigned_flag (to what the
2159 	  current code generates) without changing the underlying data
2160 	  represenation, and it might result in data corruption.
2161 
2162 	  Don't do inplace ALTER if mtype/unsigned_flag are wrong.
2163 	*/
2164 	for (ulint i = 0, icol= 0; i < table->s->fields; i++) {
2165 		const Field*		field = table->field[i];
2166 		const dict_col_t*	col = dict_table_get_nth_col(
2167 			m_prebuilt->table, icol);
2168 		unsigned unsigned_flag;
2169 
2170 		if (!field->stored_in_db()) {
2171 			continue;
2172 		}
2173 
2174 		icol++;
2175 
2176 		if (col->mtype != get_innobase_type_from_mysql_type(
2177 			    &unsigned_flag, field)) {
2178 
2179 			DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
2180 		}
2181 
2182 		if ((col->prtype & DATA_UNSIGNED) != unsigned_flag) {
2183 
2184 			DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
2185 		}
2186 	}
2187 
2188 	ulint n_indexes = UT_LIST_GET_LEN((m_prebuilt->table)->indexes);
2189 
2190 	/* If InnoDB dictionary and MySQL frm file are not consistent
2191 	use "Copy" method. */
2192 	if (m_prebuilt->table->dict_frm_mismatch) {
2193 
2194 		ha_alter_info->unsupported_reason = my_get_err_msg(
2195 			ER_NO_SUCH_INDEX);
2196 		ib_push_frm_error(m_user_thd, m_prebuilt->table, altered_table,
2197 			n_indexes, true);
2198 
2199 		DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
2200 	}
2201 
2202 	/* '0000-00-00' value isn't allowed for datetime datatype
2203 	for newly added column when table is not empty */
2204 	if (ha_alter_info->error_if_not_empty
2205 	    && m_prebuilt->table->space
2206 	    && !innobase_table_is_empty(m_prebuilt->table)) {
2207 		DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
2208 	}
2209 
2210 	const bool add_drop_v_cols = !!(ha_alter_info->handler_flags
2211 					& (ALTER_ADD_VIRTUAL_COLUMN
2212 					   | ALTER_DROP_VIRTUAL_COLUMN
2213 					   | ALTER_VIRTUAL_COLUMN_ORDER));
2214 
2215 	/* We should be able to do the operation in-place.
2216 	See if we can do it online (LOCK=NONE) or without rebuild. */
2217 	bool online = true, need_rebuild = false;
2218 	const uint fulltext_indexes = innobase_fulltext_exist(altered_table);
2219 
2220 	/* Fix the key parts. */
2221 	for (KEY* new_key = ha_alter_info->key_info_buffer;
2222 	     new_key < ha_alter_info->key_info_buffer
2223 		     + ha_alter_info->key_count;
2224 	     new_key++) {
2225 
2226 		/* Do not support adding/droping a virtual column, while
2227 		there is a table rebuild caused by adding a new FTS_DOC_ID */
2228 		if ((new_key->flags & HA_FULLTEXT) && add_drop_v_cols
2229 		    && !DICT_TF2_FLAG_IS_SET(m_prebuilt->table,
2230 					     DICT_TF2_FTS_HAS_DOC_ID)) {
2231 			ha_alter_info->unsupported_reason =
2232 				MSG_UNSUPPORTED_ALTER_ONLINE_ON_VIRTUAL_COLUMN;
2233 			DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
2234 		}
2235 
2236 		for (KEY_PART_INFO* key_part = new_key->key_part;
2237 		     key_part < (new_key->key_part
2238 				 + new_key->user_defined_key_parts);
2239 		     key_part++) {
2240 			DBUG_ASSERT(key_part->fieldnr
2241 				    < altered_table->s->fields);
2242 
2243 			const Create_field* new_field
2244 				= ha_alter_info->alter_info->create_list.elem(
2245 					key_part->fieldnr);
2246 
2247 			DBUG_ASSERT(new_field);
2248 
2249 			key_part->field = altered_table->field[
2250 				key_part->fieldnr];
2251 
2252 			/* In some special cases InnoDB emits "false"
2253 			duplicate key errors with NULL key values. Let
2254 			us play safe and ensure that we can correctly
2255 			print key values even in such cases. */
2256 			key_part->null_offset = key_part->field->null_offset();
2257 			key_part->null_bit = key_part->field->null_bit;
2258 
2259 			if (new_field->field) {
2260 				/* This is an existing column. */
2261 				continue;
2262 			}
2263 
2264 			/* This is an added column. */
2265 			DBUG_ASSERT(ha_alter_info->handler_flags
2266 				    & ALTER_ADD_COLUMN);
2267 
2268 			/* We cannot replace a hidden FTS_DOC_ID
2269 			with a user-visible FTS_DOC_ID. */
2270 			if (fulltext_indexes && m_prebuilt->table->fts
2271 			    && !my_strcasecmp(
2272 				    system_charset_info,
2273 				    key_part->field->field_name.str,
2274 				    FTS_DOC_ID_COL_NAME)) {
2275 				ha_alter_info->unsupported_reason = my_get_err_msg(
2276 					ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_HIDDEN_FTS);
2277 				DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
2278 			}
2279 
2280 			DBUG_ASSERT((MTYP_TYPENR(key_part->field->unireg_check)
2281 				     == Field::NEXT_NUMBER)
2282 				    == !!(key_part->field->flags
2283 					  & AUTO_INCREMENT_FLAG));
2284 
2285 			if (key_part->field->flags & AUTO_INCREMENT_FLAG) {
2286 				/* We cannot assign AUTO_INCREMENT values
2287 				during online or instant ALTER. */
2288 				DBUG_ASSERT(key_part->field == altered_table
2289 					    -> found_next_number_field);
2290 
2291 				if (ha_alter_info->online) {
2292 					ha_alter_info->unsupported_reason = my_get_err_msg(
2293 						ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_AUTOINC);
2294 				}
2295 
2296 				online = false;
2297 				need_rebuild = true;
2298 			}
2299 
2300 			if (!key_part->field->stored_in_db()) {
2301 				/* Do not support adding index on newly added
2302 				virtual column, while there is also a drop
2303 				virtual column in the same clause */
2304 				if (ha_alter_info->handler_flags
2305 				    & ALTER_DROP_VIRTUAL_COLUMN) {
2306 					ha_alter_info->unsupported_reason =
2307 						MSG_UNSUPPORTED_ALTER_ONLINE_ON_VIRTUAL_COLUMN;
2308 
2309 					DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
2310 				}
2311 
2312 				if (ha_alter_info->online
2313 				    && !ha_alter_info->unsupported_reason) {
2314 					ha_alter_info->unsupported_reason =
2315 						MSG_UNSUPPORTED_ALTER_ONLINE_ON_VIRTUAL_COLUMN;
2316 				}
2317 
2318 				online = false;
2319 			}
2320 		}
2321 	}
2322 
2323 	DBUG_ASSERT(!m_prebuilt->table->fts
2324 		    || (m_prebuilt->table->fts->doc_col <= table->s->fields));
2325 
2326 	DBUG_ASSERT(!m_prebuilt->table->fts
2327 		    || (m_prebuilt->table->fts->doc_col
2328 		        < dict_table_get_n_user_cols(m_prebuilt->table)));
2329 
2330 	if (fulltext_indexes && m_prebuilt->table->fts) {
2331 		/* FULLTEXT indexes are supposed to remain. */
2332 		/* Disallow DROP INDEX FTS_DOC_ID_INDEX */
2333 
2334 		for (uint i = 0; i < ha_alter_info->index_drop_count; i++) {
2335 			if (!my_strcasecmp(
2336 				    system_charset_info,
2337 				    ha_alter_info->index_drop_buffer[i]->name.str,
2338 				    FTS_DOC_ID_INDEX_NAME)) {
2339 				ha_alter_info->unsupported_reason = my_get_err_msg(
2340 					ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_CHANGE_FTS);
2341 				DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
2342 			}
2343 		}
2344 
2345 		/* InnoDB can have a hidden FTS_DOC_ID_INDEX on a
2346 		visible FTS_DOC_ID column as well. Prevent dropping or
2347 		renaming the FTS_DOC_ID. */
2348 
2349 		for (Field** fp = table->field; *fp; fp++) {
2350 			if (!((*fp)->flags
2351 			      & (FIELD_IS_RENAMED | FIELD_IS_DROPPED))) {
2352 				continue;
2353 			}
2354 
2355 			if (!my_strcasecmp(
2356 				    system_charset_info,
2357 				    (*fp)->field_name.str,
2358 				    FTS_DOC_ID_COL_NAME)) {
2359 				ha_alter_info->unsupported_reason = my_get_err_msg(
2360 					ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_CHANGE_FTS);
2361 				DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
2362 			}
2363 		}
2364 	}
2365 
2366 	m_prebuilt->trx->will_lock = true;
2367 
2368 	/* When changing a NULL column to NOT NULL and specifying a
2369 	DEFAULT value, ensure that the DEFAULT expression is a constant.
2370 	Also, in ADD COLUMN, for now we only support a
2371 	constant DEFAULT expression. */
2372 	Field **af = altered_table->field;
2373 	bool fts_need_rebuild = false;
2374 	need_rebuild = need_rebuild
2375 		|| innobase_need_rebuild(ha_alter_info, table);
2376 
2377 	for (Create_field& cf : ha_alter_info->alter_info->create_list) {
2378 		DBUG_ASSERT(cf.field
2379 			    || (ha_alter_info->handler_flags
2380 				& ALTER_ADD_COLUMN));
2381 
2382 		if (const Field* f = cf.field) {
2383 			/* An AUTO_INCREMENT attribute can only
2384 			be added to an existing column by ALGORITHM=COPY,
2385 			but we can remove the attribute. */
2386 			ut_ad((MTYP_TYPENR((*af)->unireg_check)
2387 			       != Field::NEXT_NUMBER)
2388 			      || (MTYP_TYPENR(f->unireg_check)
2389 				  == Field::NEXT_NUMBER));
2390 			if (!f->real_maybe_null() || (*af)->real_maybe_null())
2391 				goto next_column;
2392 			/* We are changing an existing column
2393 			from NULL to NOT NULL. */
2394 			DBUG_ASSERT(ha_alter_info->handler_flags
2395 				    & ALTER_COLUMN_NOT_NULLABLE);
2396 			/* Virtual columns are never NOT NULL. */
2397 			DBUG_ASSERT(f->stored_in_db());
2398 			switch ((*af)->type()) {
2399 			case MYSQL_TYPE_TIMESTAMP:
2400 			case MYSQL_TYPE_TIMESTAMP2:
2401 				/* Inserting NULL into a TIMESTAMP column
2402 				would cause the DEFAULT value to be
2403 				replaced. Ensure that the DEFAULT
2404 				expression is not changing during
2405 				ALTER TABLE. */
2406 				if (!(*af)->default_value
2407 				    && (*af)->is_real_null()) {
2408 					/* No DEFAULT value is
2409 					specified. We can report
2410 					errors for any NULL values for
2411 					the TIMESTAMP. */
2412 					goto next_column;
2413 				}
2414 				break;
2415 			default:
2416 				/* For any other data type, NULL
2417 				values are not converted. */
2418 				goto next_column;
2419 			}
2420 
2421 			ha_alter_info->unsupported_reason = my_get_err_msg(
2422 				ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOT_NULL);
2423 		} else if (!is_non_const_value(*af)
2424 			   && set_default_value(*af)) {
2425 			if (fulltext_indexes > 1
2426 			    && !my_strcasecmp(system_charset_info,
2427 					      (*af)->field_name.str,
2428 					      FTS_DOC_ID_COL_NAME)) {
2429 				/* If a hidden FTS_DOC_ID column exists
2430 				(because of FULLTEXT INDEX), it cannot
2431 				be replaced with a user-created one
2432 				except when using ALGORITHM=COPY. */
2433 				ha_alter_info->unsupported_reason =
2434 					my_get_err_msg(ER_INNODB_FT_LIMIT);
2435 				DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
2436 			}
2437 			goto next_column;
2438 		}
2439 
2440 		DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
2441 
2442 next_column:
2443 		af++;
2444 	}
2445 
2446 	const bool supports_instant = instant_alter_column_possible(
2447 		*m_prebuilt->table, ha_alter_info, table, altered_table,
2448 		is_innodb_strict_mode());
2449 	if (add_drop_v_cols) {
2450 		ulonglong flags = ha_alter_info->handler_flags;
2451 
2452 		/* TODO: uncomment the flags below, once we start to
2453 		support them */
2454 
2455 		flags &= ~(ALTER_ADD_VIRTUAL_COLUMN
2456 			   | ALTER_DROP_VIRTUAL_COLUMN
2457 			   | ALTER_VIRTUAL_COLUMN_ORDER
2458 		           | ALTER_VIRTUAL_GCOL_EXPR
2459 		           | ALTER_COLUMN_VCOL
2460 		/*
2461 			   | ALTER_ADD_STORED_BASE_COLUMN
2462 			   | ALTER_DROP_STORED_COLUMN
2463 			   | ALTER_STORED_COLUMN_ORDER
2464 			   | ALTER_ADD_UNIQUE_INDEX
2465 		*/
2466 			   | ALTER_ADD_NON_UNIQUE_NON_PRIM_INDEX
2467 			   | ALTER_DROP_NON_UNIQUE_NON_PRIM_INDEX
2468 			   | ALTER_INDEX_ORDER);
2469 		if (supports_instant) {
2470 			flags &= ~(ALTER_DROP_STORED_COLUMN
2471 #if 0 /* MDEV-17468: remove check_v_col_in_order() and fix the code */
2472 				   | ALTER_ADD_STORED_BASE_COLUMN
2473 #endif
2474 				   | ALTER_STORED_COLUMN_ORDER);
2475 		}
2476 		if (flags != 0
2477 		    || IF_PARTITIONING((altered_table->s->partition_info_str
2478 			&& altered_table->s->partition_info_str_len), 0)
2479 		    || (!check_v_col_in_order(
2480 			this->table, altered_table, ha_alter_info))) {
2481 			ha_alter_info->unsupported_reason =
2482 				MSG_UNSUPPORTED_ALTER_ONLINE_ON_VIRTUAL_COLUMN;
2483 			DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
2484 		}
2485 	}
2486 
2487 	if (supports_instant && !(ha_alter_info->handler_flags
2488 				  & INNOBASE_ALTER_NOREBUILD)) {
2489 		DBUG_RETURN(HA_ALTER_INPLACE_INSTANT);
2490 	}
2491 
2492 	if (need_rebuild
2493 	    && (fulltext_indexes
2494 		|| innobase_spatial_exist(altered_table)
2495 		|| innobase_indexed_virtual_exist(altered_table))) {
2496 		/* If the table already contains fulltext indexes,
2497 		refuse to rebuild the table natively altogether. */
2498 		if (fulltext_indexes > 1) {
2499 cannot_create_many_fulltext_index:
2500 			ha_alter_info->unsupported_reason =
2501 				my_get_err_msg(ER_INNODB_FT_LIMIT);
2502 			DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
2503 		}
2504 
2505 		if (!online || !ha_alter_info->online
2506 		    || ha_alter_info->unsupported_reason != reason_rebuild) {
2507 			/* Either LOCK=NONE was not requested, or we already
2508 			gave specific reason to refuse it. */
2509 		} else if (fulltext_indexes) {
2510 			ha_alter_info->unsupported_reason = my_get_err_msg(
2511 				ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FTS);
2512 		} else if (innobase_spatial_exist(altered_table)) {
2513 			ha_alter_info->unsupported_reason = my_get_err_msg(
2514 				ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_GIS);
2515 		} else {
2516 			/* MDEV-14341 FIXME: Remove this limitation. */
2517 			ha_alter_info->unsupported_reason =
2518 				"online rebuild with indexed virtual columns";
2519 		}
2520 
2521 		online = false;
2522 	}
2523 
2524 	if (ha_alter_info->handler_flags
2525 		& ALTER_ADD_NON_UNIQUE_NON_PRIM_INDEX) {
2526 		/* ADD FULLTEXT|SPATIAL INDEX requires a lock.
2527 
2528 		We could do ADD FULLTEXT INDEX without a lock if the
2529 		table already contains an FTS_DOC_ID column, but in
2530 		that case we would have to apply the modification log
2531 		to the full-text indexes.
2532 
2533 		We could also do ADD SPATIAL INDEX by implementing
2534 		row_log_apply() for it. */
2535 		bool add_fulltext = false;
2536 
2537 		for (uint i = 0; i < ha_alter_info->index_add_count; i++) {
2538 			const KEY* key =
2539 				&ha_alter_info->key_info_buffer[
2540 					ha_alter_info->index_add_buffer[i]];
2541 			if (key->flags & HA_FULLTEXT) {
2542 				DBUG_ASSERT(!(key->flags & HA_KEYFLAG_MASK
2543 					      & ~(HA_FULLTEXT
2544 						  | HA_PACK_KEY
2545 						  | HA_GENERATED_KEY
2546 						  | HA_BINARY_PACK_KEY)));
2547 				if (add_fulltext) {
2548 					goto cannot_create_many_fulltext_index;
2549 				}
2550 
2551 				add_fulltext = true;
2552 				if (ha_alter_info->online
2553 				    && !ha_alter_info->unsupported_reason) {
2554 					ha_alter_info->unsupported_reason = my_get_err_msg(
2555 						ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FTS);
2556 				}
2557 
2558 				online = false;
2559 
2560 				/* Full text search index exists, check
2561 				whether the table already has DOC ID column.
2562 				If not, InnoDB have to rebuild the table to
2563 				add a Doc ID hidden column and change
2564 				primary index. */
2565 				ulint	fts_doc_col_no;
2566 				ulint	num_v = 0;
2567 
2568 				fts_need_rebuild =
2569 					!innobase_fts_check_doc_id_col(
2570 						m_prebuilt->table,
2571 						altered_table,
2572 						&fts_doc_col_no, &num_v, true);
2573 			}
2574 
2575 			if (online && (key->flags & HA_SPATIAL)) {
2576 
2577 				if (ha_alter_info->online) {
2578 					ha_alter_info->unsupported_reason = my_get_err_msg(
2579 						ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_GIS);
2580 				}
2581 
2582 				online = false;
2583 			}
2584 		}
2585 	}
2586 
2587 	// FIXME: implement Online DDL for system-versioned operations
2588 	if (ha_alter_info->handler_flags & INNOBASE_ALTER_VERSIONED_REBUILD) {
2589 
2590 		if (ha_alter_info->online) {
2591 			ha_alter_info->unsupported_reason =
2592 				"Not implemented for system-versioned operations";
2593 		}
2594 
2595 		online = false;
2596 	}
2597 
2598 	if ((need_rebuild && !supports_instant) || fts_need_rebuild) {
2599 		ha_alter_info->handler_flags |= ALTER_RECREATE_TABLE;
2600 		DBUG_RETURN(online
2601 			    ? HA_ALTER_INPLACE_COPY_NO_LOCK
2602 			    : HA_ALTER_INPLACE_COPY_LOCK);
2603 	}
2604 
2605 	if (ha_alter_info->unsupported_reason) {
2606 	} else if (ha_alter_info->handler_flags & INNOBASE_ONLINE_CREATE) {
2607 		ha_alter_info->unsupported_reason = "ADD INDEX";
2608 	} else {
2609 		ha_alter_info->unsupported_reason = "DROP INDEX";
2610 	}
2611 
2612 	DBUG_RETURN(online
2613 		    ? HA_ALTER_INPLACE_NOCOPY_NO_LOCK
2614 		    : HA_ALTER_INPLACE_NOCOPY_LOCK);
2615 }
2616 
2617 /*************************************************************//**
2618 Initialize the dict_foreign_t structure with supplied info
2619 @return true if added, false if duplicate foreign->id */
2620 static MY_ATTRIBUTE((nonnull(1,3,5,7)))
2621 bool
innobase_init_foreign(dict_foreign_t * foreign,const char * constraint_name,dict_table_t * table,dict_index_t * index,const char ** column_names,ulint num_field,const char * referenced_table_name,dict_table_t * referenced_table,dict_index_t * referenced_index,const char ** referenced_column_names,ulint referenced_num_field)2622 innobase_init_foreign(
2623 /*==================*/
2624 	dict_foreign_t*	foreign,		/*!< in/out: structure to
2625 						initialize */
2626 	const char*	constraint_name,	/*!< in/out: constraint name if
2627 						exists */
2628 	dict_table_t*	table,			/*!< in: foreign table */
2629 	dict_index_t*	index,			/*!< in: foreign key index */
2630 	const char**	column_names,		/*!< in: foreign key column
2631 						names */
2632 	ulint		num_field,		/*!< in: number of columns */
2633 	const char*	referenced_table_name,	/*!< in: referenced table
2634 						name */
2635 	dict_table_t*	referenced_table,	/*!< in: referenced table */
2636 	dict_index_t*	referenced_index,	/*!< in: referenced index */
2637 	const char**	referenced_column_names,/*!< in: referenced column
2638 						names */
2639 	ulint		referenced_num_field)	/*!< in: number of referenced
2640 						columns */
2641 {
2642 	ut_ad(mutex_own(&dict_sys.mutex));
2643 
2644         if (constraint_name) {
2645                 ulint   db_len;
2646 
2647                 /* Catenate 'databasename/' to the constraint name specified
2648                 by the user: we conceive the constraint as belonging to the
2649                 same MySQL 'database' as the table itself. We store the name
2650                 to foreign->id. */
2651 
2652                 db_len = dict_get_db_name_len(table->name.m_name);
2653 
2654                 foreign->id = static_cast<char*>(mem_heap_alloc(
2655                         foreign->heap, db_len + strlen(constraint_name) + 2));
2656 
2657                 memcpy(foreign->id, table->name.m_name, db_len);
2658                 foreign->id[db_len] = '/';
2659                 strcpy(foreign->id + db_len + 1, constraint_name);
2660 
2661 		/* Check if any existing foreign key has the same id,
2662 		this is needed only if user supplies the constraint name */
2663 
2664 		if (table->foreign_set.find(foreign)
2665 		    != table->foreign_set.end()) {
2666 			return(false);
2667 		}
2668         }
2669 
2670         foreign->foreign_table = table;
2671         foreign->foreign_table_name = mem_heap_strdup(
2672                 foreign->heap, table->name.m_name);
2673         dict_mem_foreign_table_name_lookup_set(foreign, TRUE);
2674 
2675         foreign->foreign_index = index;
2676         foreign->n_fields = static_cast<unsigned>(num_field)
2677 		& dict_index_t::MAX_N_FIELDS;
2678 
2679         foreign->foreign_col_names = static_cast<const char**>(
2680                 mem_heap_alloc(foreign->heap, num_field * sizeof(void*)));
2681 
2682         for (ulint i = 0; i < foreign->n_fields; i++) {
2683                 foreign->foreign_col_names[i] = mem_heap_strdup(
2684                         foreign->heap, column_names[i]);
2685         }
2686 
2687 	foreign->referenced_index = referenced_index;
2688 	foreign->referenced_table = referenced_table;
2689 
2690 	foreign->referenced_table_name = mem_heap_strdup(
2691 		foreign->heap, referenced_table_name);
2692         dict_mem_referenced_table_name_lookup_set(foreign, TRUE);
2693 
2694         foreign->referenced_col_names = static_cast<const char**>(
2695                 mem_heap_alloc(foreign->heap,
2696 			       referenced_num_field * sizeof(void*)));
2697 
2698         for (ulint i = 0; i < foreign->n_fields; i++) {
2699                 foreign->referenced_col_names[i]
2700                         = mem_heap_strdup(foreign->heap,
2701 					  referenced_column_names[i]);
2702         }
2703 
2704 	return(true);
2705 }
2706 
2707 /*************************************************************//**
2708 Check whether the foreign key options is legit
2709 @return true if it is */
2710 static MY_ATTRIBUTE((nonnull, warn_unused_result))
2711 bool
innobase_check_fk_option(const dict_foreign_t * foreign)2712 innobase_check_fk_option(
2713 /*=====================*/
2714 	const dict_foreign_t*	foreign)	/*!< in: foreign key */
2715 {
2716 	if (!foreign->foreign_index) {
2717 		return(true);
2718 	}
2719 
2720 	if (foreign->type & (DICT_FOREIGN_ON_UPDATE_SET_NULL
2721 			     | DICT_FOREIGN_ON_DELETE_SET_NULL)) {
2722 
2723 		for (ulint j = 0; j < foreign->n_fields; j++) {
2724 			if ((dict_index_get_nth_col(
2725 				     foreign->foreign_index, j)->prtype)
2726 			    & DATA_NOT_NULL) {
2727 
2728 				/* It is not sensible to define
2729 				SET NULL if the column is not
2730 				allowed to be NULL! */
2731 				return(false);
2732 			}
2733 		}
2734 	}
2735 
2736 	return(true);
2737 }
2738 
2739 /*************************************************************//**
2740 Set foreign key options
2741 @return true if successfully set */
2742 static MY_ATTRIBUTE((nonnull, warn_unused_result))
2743 bool
innobase_set_foreign_key_option(dict_foreign_t * foreign,Foreign_key * fk_key)2744 innobase_set_foreign_key_option(
2745 /*============================*/
2746 	dict_foreign_t*	foreign,	/*!< in:InnoDB Foreign key */
2747 	Foreign_key*	fk_key)		/*!< in: Foreign key info from
2748 					MySQL */
2749 {
2750 	ut_ad(!foreign->type);
2751 
2752 	switch (fk_key->delete_opt) {
2753 	case FK_OPTION_NO_ACTION:
2754 	case FK_OPTION_RESTRICT:
2755 	case FK_OPTION_SET_DEFAULT:
2756 		foreign->type = DICT_FOREIGN_ON_DELETE_NO_ACTION;
2757 		break;
2758 	case FK_OPTION_CASCADE:
2759 		foreign->type = DICT_FOREIGN_ON_DELETE_CASCADE;
2760 		break;
2761 	case FK_OPTION_SET_NULL:
2762 		foreign->type = DICT_FOREIGN_ON_DELETE_SET_NULL;
2763 		break;
2764 	case FK_OPTION_UNDEF:
2765 		break;
2766 	}
2767 
2768 	switch (fk_key->update_opt) {
2769 	case FK_OPTION_NO_ACTION:
2770 	case FK_OPTION_RESTRICT:
2771 	case FK_OPTION_SET_DEFAULT:
2772 		foreign->type |= DICT_FOREIGN_ON_UPDATE_NO_ACTION;
2773 		break;
2774 	case FK_OPTION_CASCADE:
2775 		foreign->type |= DICT_FOREIGN_ON_UPDATE_CASCADE;
2776 		break;
2777 	case FK_OPTION_SET_NULL:
2778 		foreign->type |= DICT_FOREIGN_ON_UPDATE_SET_NULL;
2779 		break;
2780 	case FK_OPTION_UNDEF:
2781 		break;
2782 	}
2783 
2784 	return(innobase_check_fk_option(foreign));
2785 }
2786 
2787 /*******************************************************************//**
2788 Check if a foreign key constraint can make use of an index
2789 that is being created.
2790 @param[in]	col_names	column names
2791 @param[in]	n_cols		number of columns
2792 @param[in]	keys		index information
2793 @param[in]	add		indexes being created
2794 @return useable index, or NULL if none found */
2795 static MY_ATTRIBUTE((nonnull, warn_unused_result))
2796 const KEY*
innobase_find_equiv_index(const char * const * col_names,uint n_cols,const KEY * keys,span<uint> add)2797 innobase_find_equiv_index(
2798 	const char*const*	col_names,
2799 	uint			n_cols,
2800 	const KEY*		keys,
2801 	span<uint>		add)
2802 {
2803 	for (span<uint>::iterator it = add.begin(), end = add.end(); it != end;
2804 	     ++it) {
2805 		const KEY*	key = &keys[*it];
2806 
2807 		if (key->user_defined_key_parts < n_cols
2808 		    || key->flags & HA_SPATIAL) {
2809 no_match:
2810 			continue;
2811 		}
2812 
2813 		for (uint j = 0; j < n_cols; j++) {
2814 			const KEY_PART_INFO&	key_part = key->key_part[j];
2815 			uint32			col_len
2816 				= key_part.field->pack_length();
2817 
2818 			/* Any index on virtual columns cannot be used
2819 			for reference constaint */
2820 			if (!key_part.field->stored_in_db()) {
2821 				goto no_match;
2822 			}
2823 
2824 			/* The MySQL pack length contains 1 or 2 bytes
2825 			length field for a true VARCHAR. */
2826 
2827 			if (key_part.field->type() == MYSQL_TYPE_VARCHAR) {
2828 				col_len -= static_cast<const Field_varstring*>(
2829 					key_part.field)->length_bytes;
2830 			}
2831 
2832 			if (key_part.length < col_len) {
2833 
2834 				/* Column prefix indexes cannot be
2835 				used for FOREIGN KEY constraints. */
2836 				goto no_match;
2837 			}
2838 
2839 			if (innobase_strcasecmp(col_names[j],
2840 						key_part.field->field_name.str)) {
2841 				/* Name mismatch */
2842 				goto no_match;
2843 			}
2844 		}
2845 
2846 		return(key);
2847 	}
2848 
2849 	return(NULL);
2850 }
2851 
2852 /*************************************************************//**
2853 Find an index whose first fields are the columns in the array
2854 in the same order and is not marked for deletion
2855 @return matching index, NULL if not found */
2856 static MY_ATTRIBUTE((nonnull(1,4), warn_unused_result))
2857 dict_index_t*
innobase_find_fk_index(dict_table_t * table,const char ** col_names,span<dict_index_t * > drop_index,const char ** columns,ulint n_cols)2858 innobase_find_fk_index(
2859 /*===================*/
2860 	dict_table_t*		table,	/*!< in: table */
2861 	const char**		col_names,
2862 					/*!< in: column names, or NULL
2863 					to use table->col_names */
2864 	span<dict_index_t*>	drop_index,
2865 					/*!< in: indexes to be dropped */
2866 	const char**		columns,/*!< in: array of column names */
2867 	ulint			n_cols) /*!< in: number of columns */
2868 {
2869 	dict_index_t*	index;
2870 
2871 	index = dict_table_get_first_index(table);
2872 
2873 	while (index != NULL) {
2874 		if (dict_foreign_qualify_index(table, col_names, columns,
2875 					       n_cols, index, NULL, true, 0,
2876 					       NULL, NULL, NULL)
2877 		    && std::find(drop_index.begin(), drop_index.end(), index)
2878 			   == drop_index.end()) {
2879 			return index;
2880 		}
2881 
2882 		index = dict_table_get_next_index(index);
2883 	}
2884 
2885 	return(NULL);
2886 }
2887 
2888 /** Check whether given column is a base of stored column.
2889 @param[in]	col_name	column name
2890 @param[in]	table		table
2891 @param[in]	s_cols		list of stored columns
2892 @return true if the given column is a base of stored column,else false. */
2893 static
2894 bool
innobase_col_check_fk(const char * col_name,const dict_table_t * table,dict_s_col_list * s_cols)2895 innobase_col_check_fk(
2896 	const char*		col_name,
2897 	const dict_table_t*	table,
2898 	dict_s_col_list*	s_cols)
2899 {
2900 	dict_s_col_list::const_iterator	it;
2901 
2902 	for (it = s_cols->begin(); it != s_cols->end(); ++it) {
2903 		for (ulint j = it->num_base; j--; ) {
2904 			if (!strcmp(col_name, dict_table_get_col_name(
2905 					    table, it->base_col[j]->ind))) {
2906 				return(true);
2907 			}
2908 		}
2909 	}
2910 
2911 	return(false);
2912 }
2913 
2914 /** Check whether the foreign key constraint is on base of any stored columns.
2915 @param[in]	foreign	Foriegn key constraing information
2916 @param[in]	table	table to which the foreign key objects
2917 to be added
2918 @param[in]	s_cols	list of stored column information in the table.
2919 @return true if yes, otherwise false. */
2920 static
2921 bool
innobase_check_fk_stored(const dict_foreign_t * foreign,const dict_table_t * table,dict_s_col_list * s_cols)2922 innobase_check_fk_stored(
2923 	const dict_foreign_t*	foreign,
2924 	const dict_table_t*	table,
2925 	dict_s_col_list*	s_cols)
2926 {
2927 	ulint	type = foreign->type;
2928 
2929 	type &= ~(DICT_FOREIGN_ON_DELETE_NO_ACTION
2930 		  | DICT_FOREIGN_ON_UPDATE_NO_ACTION);
2931 
2932 	if (type == 0 || s_cols == NULL) {
2933 		return(false);
2934 	}
2935 
2936 	for (ulint i = 0; i < foreign->n_fields; i++) {
2937 		if (innobase_col_check_fk(
2938 			foreign->foreign_col_names[i], table, s_cols)) {
2939 			return(true);
2940 		}
2941 	}
2942 
2943 	return(false);
2944 }
2945 
2946 /** Create InnoDB foreign key structure from MySQL alter_info
2947 @param[in]	ha_alter_info	alter table info
2948 @param[in]	table_share	TABLE_SHARE
2949 @param[in]	table		table object
2950 @param[in]	col_names	column names, or NULL to use
2951 table->col_names
2952 @param[in]	drop_index	indexes to be dropped
2953 @param[in]	n_drop_index	size of drop_index
2954 @param[out]	add_fk		foreign constraint added
2955 @param[out]	n_add_fk	number of foreign constraints
2956 added
2957 @param[in]	trx		user transaction
2958 @param[in]	s_cols		list of stored column information
2959 @retval true if successful
2960 @retval false on error (will call my_error()) */
2961 static MY_ATTRIBUTE((nonnull(1,2,3,7,8), warn_unused_result))
2962 bool
innobase_get_foreign_key_info(Alter_inplace_info * ha_alter_info,const TABLE_SHARE * table_share,dict_table_t * table,const char ** col_names,dict_index_t ** drop_index,ulint n_drop_index,dict_foreign_t ** add_fk,ulint * n_add_fk,const trx_t * trx,dict_s_col_list * s_cols)2963 innobase_get_foreign_key_info(
2964 	Alter_inplace_info*
2965 			ha_alter_info,
2966 	const TABLE_SHARE*
2967 			table_share,
2968 	dict_table_t*	table,
2969 	const char**	col_names,
2970 	dict_index_t**	drop_index,
2971 	ulint		n_drop_index,
2972 	dict_foreign_t**add_fk,
2973 	ulint*		n_add_fk,
2974 	const trx_t*	trx,
2975 	dict_s_col_list*s_cols)
2976 {
2977 	dict_table_t*	referenced_table = NULL;
2978 	char*		referenced_table_name = NULL;
2979 	ulint		num_fk = 0;
2980 	Alter_info*	alter_info = ha_alter_info->alter_info;
2981 	const CHARSET_INFO*	cs = thd_charset(trx->mysql_thd);
2982 
2983 	DBUG_ENTER("innobase_get_foreign_key_info");
2984 
2985 	*n_add_fk = 0;
2986 
2987 	for (Key& key : alter_info->key_list) {
2988 		if (key.type != Key::FOREIGN_KEY) {
2989 			continue;
2990 		}
2991 
2992 		const char*	column_names[MAX_NUM_FK_COLUMNS];
2993 		dict_index_t*	index = NULL;
2994 		const char*	referenced_column_names[MAX_NUM_FK_COLUMNS];
2995 		dict_index_t*	referenced_index = NULL;
2996 		ulint		num_col = 0;
2997 		ulint		referenced_num_col = 0;
2998 		bool		correct_option;
2999 
3000 		Foreign_key* fk_key = static_cast<Foreign_key*>(&key);
3001 
3002 		if (fk_key->columns.elements > 0) {
3003 			ulint	i = 0;
3004 
3005 			/* Get all the foreign key column info for the
3006 			current table */
3007 			for (const Key_part_spec& column : fk_key->columns) {
3008 				column_names[i] = column.field_name.str;
3009 				ut_ad(i < MAX_NUM_FK_COLUMNS);
3010 				i++;
3011 			}
3012 
3013 			index = innobase_find_fk_index(
3014 				table, col_names,
3015 				span<dict_index_t*>(drop_index, n_drop_index),
3016 				column_names, i);
3017 
3018 			/* MySQL would add a index in the creation
3019 			list if no such index for foreign table,
3020 			so we have to use DBUG_EXECUTE_IF to simulate
3021 			the scenario */
3022 			DBUG_EXECUTE_IF("innodb_test_no_foreign_idx",
3023 					index = NULL;);
3024 
3025 			/* Check whether there exist such
3026 			index in the the index create clause */
3027 			if (!index && !innobase_find_equiv_index(
3028 				    column_names, static_cast<uint>(i),
3029 				    ha_alter_info->key_info_buffer,
3030 				    span<uint>(ha_alter_info->index_add_buffer,
3031 					       ha_alter_info->index_add_count))) {
3032 				my_error(
3033 					ER_FK_NO_INDEX_CHILD,
3034 					MYF(0),
3035 					fk_key->name.str
3036 					? fk_key->name.str : "",
3037 					table_share->table_name.str);
3038 				goto err_exit;
3039 			}
3040 
3041 			num_col = i;
3042 		}
3043 
3044 		add_fk[num_fk] = dict_mem_foreign_create();
3045 
3046 		mutex_enter(&dict_sys.mutex);
3047 
3048 		referenced_table_name = dict_get_referenced_table(
3049 			table->name.m_name,
3050 			LEX_STRING_WITH_LEN(fk_key->ref_db),
3051 			LEX_STRING_WITH_LEN(fk_key->ref_table),
3052 			&referenced_table,
3053 			add_fk[num_fk]->heap, cs);
3054 
3055 		/* Test the case when referenced_table failed to
3056 		open, if trx->check_foreigns is not set, we should
3057 		still be able to add the foreign key */
3058 		DBUG_EXECUTE_IF("innodb_test_open_ref_fail",
3059 				referenced_table = NULL;);
3060 
3061 		if (!referenced_table && trx->check_foreigns) {
3062 			mutex_exit(&dict_sys.mutex);
3063 			my_error(ER_FK_CANNOT_OPEN_PARENT,
3064 				 MYF(0), fk_key->ref_table.str);
3065 
3066 			goto err_exit;
3067 		}
3068 
3069 		if (fk_key->ref_columns.elements > 0) {
3070 			ulint	i = 0;
3071 
3072 			for (Key_part_spec &column : fk_key->ref_columns) {
3073 				referenced_column_names[i] =
3074 					column.field_name.str;
3075 				ut_ad(i < MAX_NUM_FK_COLUMNS);
3076 				i++;
3077 			}
3078 
3079 			if (referenced_table) {
3080 				referenced_index =
3081 					dict_foreign_find_index(
3082 						referenced_table, 0,
3083 						referenced_column_names,
3084 						i, index,
3085 						TRUE, FALSE,
3086 						NULL, NULL, NULL);
3087 
3088 				DBUG_EXECUTE_IF(
3089 					"innodb_test_no_reference_idx",
3090 					referenced_index = NULL;);
3091 
3092 				/* Check whether there exist such
3093 				index in the the index create clause */
3094 				if (!referenced_index) {
3095 					mutex_exit(&dict_sys.mutex);
3096 					my_error(ER_FK_NO_INDEX_PARENT, MYF(0),
3097 						 fk_key->name.str
3098 						 ? fk_key->name.str : "",
3099 						 fk_key->ref_table.str);
3100 					goto err_exit;
3101 				}
3102 			} else {
3103 				ut_a(!trx->check_foreigns);
3104 			}
3105 
3106 			referenced_num_col = i;
3107 		} else {
3108 			/* Not possible to add a foreign key without a
3109 			referenced column */
3110 			mutex_exit(&dict_sys.mutex);
3111 			my_error(ER_CANNOT_ADD_FOREIGN, MYF(0),
3112 				 fk_key->ref_table.str);
3113 			goto err_exit;
3114 		}
3115 
3116 		if (!innobase_init_foreign(
3117 			    add_fk[num_fk], fk_key->name.str,
3118 			    table, index, column_names,
3119 			    num_col, referenced_table_name,
3120 			    referenced_table, referenced_index,
3121 			    referenced_column_names, referenced_num_col)) {
3122 			mutex_exit(&dict_sys.mutex);
3123 			my_error(
3124 				ER_DUP_CONSTRAINT_NAME,
3125 				MYF(0),
3126                                 "FOREIGN KEY", add_fk[num_fk]->id);
3127 			goto err_exit;
3128 		}
3129 
3130 		mutex_exit(&dict_sys.mutex);
3131 
3132 		correct_option = innobase_set_foreign_key_option(
3133 			add_fk[num_fk], fk_key);
3134 
3135 		DBUG_EXECUTE_IF("innodb_test_wrong_fk_option",
3136 				correct_option = false;);
3137 
3138 		if (!correct_option) {
3139 			my_error(ER_FK_INCORRECT_OPTION,
3140 				 MYF(0),
3141 				 table_share->table_name.str,
3142 				 add_fk[num_fk]->id);
3143 			goto err_exit;
3144 		}
3145 
3146 		if (innobase_check_fk_stored(
3147 			add_fk[num_fk], table, s_cols)) {
3148 			my_printf_error(
3149 				HA_ERR_UNSUPPORTED,
3150 				"Cannot add foreign key on the base column "
3151 				"of stored column", MYF(0));
3152 			goto err_exit;
3153 		}
3154 
3155 		num_fk++;
3156 	}
3157 
3158 	*n_add_fk = num_fk;
3159 
3160 	DBUG_RETURN(true);
3161 err_exit:
3162 	for (ulint i = 0; i <= num_fk; i++) {
3163 		if (add_fk[i]) {
3164 			dict_foreign_free(add_fk[i]);
3165 		}
3166 	}
3167 
3168 	DBUG_RETURN(false);
3169 }
3170 
3171 /*************************************************************//**
3172 Copies an InnoDB column to a MySQL field.  This function is
3173 adapted from row_sel_field_store_in_mysql_format(). */
3174 static
3175 void
innobase_col_to_mysql(const dict_col_t * col,const uchar * data,ulint len,Field * field)3176 innobase_col_to_mysql(
3177 /*==================*/
3178 	const dict_col_t*	col,	/*!< in: InnoDB column */
3179 	const uchar*		data,	/*!< in: InnoDB column data */
3180 	ulint			len,	/*!< in: length of data, in bytes */
3181 	Field*			field)	/*!< in/out: MySQL field */
3182 {
3183 	uchar*	ptr;
3184 	uchar*	dest	= field->ptr;
3185 	ulint	flen	= field->pack_length();
3186 
3187 	switch (col->mtype) {
3188 	case DATA_INT:
3189 		ut_ad(len == flen);
3190 
3191 		/* Convert integer data from Innobase to little-endian
3192 		format, sign bit restored to normal */
3193 
3194 		for (ptr = dest + len; ptr != dest; ) {
3195 			*--ptr = *data++;
3196 		}
3197 
3198 		if (!(col->prtype & DATA_UNSIGNED)) {
3199 			((byte*) dest)[len - 1] ^= 0x80;
3200 		}
3201 
3202 		break;
3203 
3204 	case DATA_VARCHAR:
3205 	case DATA_VARMYSQL:
3206 	case DATA_BINARY:
3207 		field->reset();
3208 
3209 		if (field->type() == MYSQL_TYPE_VARCHAR) {
3210 			/* This is a >= 5.0.3 type true VARCHAR. Store the
3211 			length of the data to the first byte or the first
3212 			two bytes of dest. */
3213 
3214 			dest = row_mysql_store_true_var_len(
3215 				dest, len, flen - field->key_length());
3216 		}
3217 
3218 		/* Copy the actual data */
3219 		memcpy(dest, data, len);
3220 		break;
3221 
3222 	case DATA_GEOMETRY:
3223 	case DATA_BLOB:
3224 		/* Skip MySQL BLOBs when reporting an erroneous row
3225 		during index creation or table rebuild. */
3226 		field->set_null();
3227 		break;
3228 
3229 #ifdef UNIV_DEBUG
3230 	case DATA_MYSQL:
3231 		ut_ad(flen >= len);
3232 		ut_ad(col->mbmaxlen >= col->mbminlen);
3233 		memcpy(dest, data, len);
3234 		break;
3235 
3236 	default:
3237 	case DATA_SYS_CHILD:
3238 	case DATA_SYS:
3239 		/* These column types should never be shipped to MySQL. */
3240 		ut_ad(0);
3241 		/* fall through */
3242 	case DATA_FLOAT:
3243 	case DATA_DOUBLE:
3244 	case DATA_DECIMAL:
3245 		/* Above are the valid column types for MySQL data. */
3246 		ut_ad(flen == len);
3247 		/* fall through */
3248 	case DATA_FIXBINARY:
3249 	case DATA_CHAR:
3250 		/* We may have flen > len when there is a shorter
3251 		prefix on the CHAR and BINARY column. */
3252 		ut_ad(flen >= len);
3253 #else /* UNIV_DEBUG */
3254 	default:
3255 #endif /* UNIV_DEBUG */
3256 		memcpy(dest, data, len);
3257 	}
3258 }
3259 
3260 /*************************************************************//**
3261 Copies an InnoDB record to table->record[0]. */
3262 void
innobase_rec_to_mysql(struct TABLE * table,const rec_t * rec,const dict_index_t * index,const rec_offs * offsets)3263 innobase_rec_to_mysql(
3264 /*==================*/
3265 	struct TABLE*		table,	/*!< in/out: MySQL table */
3266 	const rec_t*		rec,	/*!< in: record */
3267 	const dict_index_t*	index,	/*!< in: index */
3268 	const rec_offs*		offsets)/*!< in: rec_get_offsets(
3269 					rec, index, ...) */
3270 {
3271 	uint	n_fields	= table->s->fields;
3272 
3273 	ut_ad(n_fields == dict_table_get_n_user_cols(index->table)
3274 	      - !!(DICT_TF2_FLAG_IS_SET(index->table,
3275 					DICT_TF2_FTS_HAS_DOC_ID)));
3276 
3277 	for (uint i = 0; i < n_fields; i++) {
3278 		Field*		field	= table->field[i];
3279 		ulint		ipos;
3280 		ulint		ilen;
3281 		const uchar*	ifield;
3282 		ulint prefix_col;
3283 
3284 		field->reset();
3285 
3286 		ipos = dict_index_get_nth_col_or_prefix_pos(
3287 			index, i, true, false, &prefix_col);
3288 
3289 		if (ipos == ULINT_UNDEFINED
3290 		    || rec_offs_nth_extern(offsets, ipos)) {
3291 null_field:
3292 			field->set_null();
3293 			continue;
3294 		}
3295 
3296 		ifield = rec_get_nth_cfield(rec, index, offsets, ipos, &ilen);
3297 
3298 		/* Assign the NULL flag */
3299 		if (ilen == UNIV_SQL_NULL) {
3300 			ut_ad(field->real_maybe_null());
3301 			goto null_field;
3302 		}
3303 
3304 		field->set_notnull();
3305 
3306 		innobase_col_to_mysql(
3307 			dict_field_get_col(
3308 				dict_index_get_nth_field(index, ipos)),
3309 			ifield, ilen, field);
3310 	}
3311 }
3312 
3313 /*************************************************************//**
3314 Copies an InnoDB index entry to table->record[0].
3315 This is used in preparation for print_keydup_error() from
3316 inline add index */
3317 void
innobase_fields_to_mysql(struct TABLE * table,const dict_index_t * index,const dfield_t * fields)3318 innobase_fields_to_mysql(
3319 /*=====================*/
3320 	struct TABLE*		table,	/*!< in/out: MySQL table */
3321 	const dict_index_t*	index,	/*!< in: InnoDB index */
3322 	const dfield_t*		fields)	/*!< in: InnoDB index fields */
3323 {
3324 	uint	n_fields	= table->s->fields;
3325 	ulint	num_v 		= 0;
3326 
3327 	ut_ad(n_fields == dict_table_get_n_user_cols(index->table)
3328 	      + dict_table_get_n_v_cols(index->table)
3329 	      - !!(DICT_TF2_FLAG_IS_SET(index->table,
3330 					DICT_TF2_FTS_HAS_DOC_ID)));
3331 
3332 	for (uint i = 0; i < n_fields; i++) {
3333 		Field*		field	= table->field[i];
3334 		ulint		ipos;
3335 		ulint		prefix_col;
3336 
3337 		field->reset();
3338 
3339 		const bool is_v = !field->stored_in_db();
3340 		const ulint col_n = is_v ? num_v++ : i - num_v;
3341 
3342 		ipos = dict_index_get_nth_col_or_prefix_pos(
3343 			index, col_n, true, is_v, &prefix_col);
3344 
3345 		if (ipos == ULINT_UNDEFINED
3346 		    || dfield_is_ext(&fields[ipos])
3347 		    || dfield_is_null(&fields[ipos])) {
3348 
3349 			field->set_null();
3350 		} else {
3351 			field->set_notnull();
3352 
3353 			const dfield_t*	df	= &fields[ipos];
3354 
3355 			innobase_col_to_mysql(
3356 				dict_field_get_col(
3357 					dict_index_get_nth_field(index, ipos)),
3358 				static_cast<const uchar*>(dfield_get_data(df)),
3359 				dfield_get_len(df), field);
3360 		}
3361 	}
3362 }
3363 
3364 /*************************************************************//**
3365 Copies an InnoDB row to table->record[0].
3366 This is used in preparation for print_keydup_error() from
3367 row_log_table_apply() */
3368 void
innobase_row_to_mysql(struct TABLE * table,const dict_table_t * itab,const dtuple_t * row)3369 innobase_row_to_mysql(
3370 /*==================*/
3371 	struct TABLE*		table,	/*!< in/out: MySQL table */
3372 	const dict_table_t*	itab,	/*!< in: InnoDB table */
3373 	const dtuple_t*		row)	/*!< in: InnoDB row */
3374 {
3375 	uint	n_fields = table->s->fields;
3376 	ulint	num_v = 0;
3377 
3378 	/* The InnoDB row may contain an extra FTS_DOC_ID column at the end. */
3379 	ut_ad(row->n_fields == dict_table_get_n_cols(itab));
3380 	ut_ad(n_fields == row->n_fields - DATA_N_SYS_COLS
3381 	      + dict_table_get_n_v_cols(itab)
3382 	      - !!(DICT_TF2_FLAG_IS_SET(itab, DICT_TF2_FTS_HAS_DOC_ID)));
3383 
3384 	for (uint i = 0; i < n_fields; i++) {
3385 		Field*		field	= table->field[i];
3386 
3387 		field->reset();
3388 
3389 		if (!field->stored_in_db()) {
3390 			/* Virtual column are not stored in InnoDB table, so
3391 			skip it */
3392 			num_v++;
3393 			continue;
3394 		}
3395 
3396 		const dfield_t*	df	= dtuple_get_nth_field(row, i - num_v);
3397 
3398 		if (dfield_is_ext(df) || dfield_is_null(df)) {
3399 			field->set_null();
3400 		} else {
3401 			field->set_notnull();
3402 
3403 			innobase_col_to_mysql(
3404 				dict_table_get_nth_col(itab, i - num_v),
3405 				static_cast<const uchar*>(dfield_get_data(df)),
3406 				dfield_get_len(df), field);
3407 		}
3408 	}
3409 	if (table->vfield) {
3410 		MY_BITMAP*	old_read_set = tmp_use_all_columns(table, &table->read_set);
3411 		table->update_virtual_fields(table->file, VCOL_UPDATE_FOR_READ);
3412 		tmp_restore_column_map(&table->read_set, old_read_set);
3413 	}
3414 }
3415 
3416 /*******************************************************************//**
3417 This function checks that index keys are sensible.
3418 @return 0 or error number */
3419 static MY_ATTRIBUTE((nonnull, warn_unused_result))
3420 int
innobase_check_index_keys(const Alter_inplace_info * info,const dict_table_t * innodb_table)3421 innobase_check_index_keys(
3422 /*======================*/
3423 	const Alter_inplace_info*	info,
3424 				/*!< in: indexes to be created or dropped */
3425 	const dict_table_t*		innodb_table)
3426 				/*!< in: Existing indexes */
3427 {
3428 	for (uint key_num = 0; key_num < info->index_add_count;
3429 	     key_num++) {
3430 		const KEY&	key = info->key_info_buffer[
3431 			info->index_add_buffer[key_num]];
3432 
3433 		/* Check that the same index name does not appear
3434 		twice in indexes to be created. */
3435 
3436 		for (ulint i = 0; i < key_num; i++) {
3437 			const KEY&	key2 = info->key_info_buffer[
3438 				info->index_add_buffer[i]];
3439 
3440 			if (0 == strcmp(key.name.str, key2.name.str)) {
3441 				my_error(ER_WRONG_NAME_FOR_INDEX, MYF(0),
3442 					 key.name.str);
3443 
3444 				return(ER_WRONG_NAME_FOR_INDEX);
3445 			}
3446 		}
3447 
3448 		/* Check that the same index name does not already exist. */
3449 
3450 		const dict_index_t* index;
3451 
3452 		for (index = dict_table_get_first_index(innodb_table);
3453 		     index; index = dict_table_get_next_index(index)) {
3454 
3455 			if (index->is_committed()
3456 			    && !strcmp(key.name.str, index->name)) {
3457 				break;
3458 			}
3459 		}
3460 
3461 		/* Now we are in a situation where we have "ADD INDEX x"
3462 		and an index by the same name already exists. We have 4
3463 		possible cases:
3464 		1. No further clauses for an index x are given. Should reject
3465 		the operation.
3466 		2. "DROP INDEX x" is given. Should allow the operation.
3467 		3. "RENAME INDEX x TO y" is given. Should allow the operation.
3468 		4. "DROP INDEX x, RENAME INDEX x TO y" is given. Should allow
3469 		the operation, since no name clash occurs. In this particular
3470 		case MySQL cancels the operation without calling InnoDB
3471 		methods. */
3472 
3473 		if (index) {
3474 			/* If a key by the same name is being created and
3475 			dropped, the name clash is OK. */
3476 			for (uint i = 0; i < info->index_drop_count;
3477 			     i++) {
3478 				const KEY*	drop_key
3479 					= info->index_drop_buffer[i];
3480 
3481 				if (0 == strcmp(key.name.str,
3482                                                 drop_key->name.str)) {
3483 					goto name_ok;
3484 				}
3485 			}
3486 
3487 			for (const Alter_inplace_info::Rename_key_pair& pair :
3488 			     info->rename_keys) {
3489 				if (0 == strcmp(key.name.str,
3490                                                 pair.old_key->name.str)) {
3491 					goto name_ok;
3492 				}
3493 			}
3494 
3495 			my_error(ER_WRONG_NAME_FOR_INDEX, MYF(0),
3496                                  key.name.str);
3497 			return(ER_WRONG_NAME_FOR_INDEX);
3498 		}
3499 
3500 name_ok:
3501 		for (ulint i = 0; i < key.user_defined_key_parts; i++) {
3502 			const KEY_PART_INFO&	key_part1
3503 				= key.key_part[i];
3504 			const Field*		field
3505 				= key_part1.field;
3506 			unsigned		is_unsigned;
3507 
3508 			switch (get_innobase_type_from_mysql_type(
3509 					&is_unsigned, field)) {
3510 			default:
3511 				break;
3512 			case DATA_INT:
3513 			case DATA_FLOAT:
3514 			case DATA_DOUBLE:
3515 			case DATA_DECIMAL:
3516 				/* Check that MySQL does not try to
3517 				create a column prefix index field on
3518 				an inappropriate data type. */
3519 
3520 				if (field->type() == MYSQL_TYPE_VARCHAR) {
3521 					if (key_part1.length
3522 					    >= field->pack_length()
3523 					    - ((Field_varstring*) field)
3524 					    ->length_bytes) {
3525 						break;
3526 					}
3527 				} else {
3528 					if (key_part1.length
3529 					    >= field->pack_length()) {
3530 						break;
3531 					}
3532 				}
3533 
3534 				my_error(ER_WRONG_KEY_COLUMN, MYF(0), "InnoDB",
3535 					 field->field_name.str);
3536 				return(ER_WRONG_KEY_COLUMN);
3537 			}
3538 
3539 			/* Check that the same column does not appear
3540 			twice in the index. */
3541 
3542 			for (ulint j = 0; j < i; j++) {
3543 				const KEY_PART_INFO&	key_part2
3544 					= key.key_part[j];
3545 
3546 				if (key_part1.fieldnr != key_part2.fieldnr) {
3547 					continue;
3548 				}
3549 
3550 				my_error(ER_WRONG_KEY_COLUMN, MYF(0), "InnoDB",
3551 					 field->field_name.str);
3552 				return(ER_WRONG_KEY_COLUMN);
3553 			}
3554 		}
3555 	}
3556 
3557 	return(0);
3558 }
3559 
3560 /** Create index field definition for key part
3561 @param[in]	new_clustered	true if alter is generating a new clustered
3562 index
3563 @param[in]	altered_table	MySQL table that is being altered
3564 @param[in]	key_part	MySQL key definition
3565 @param[out]	index_field	index field definition for key_part */
3566 static MY_ATTRIBUTE((nonnull))
3567 void
innobase_create_index_field_def(bool new_clustered,const TABLE * altered_table,const KEY_PART_INFO * key_part,index_field_t * index_field)3568 innobase_create_index_field_def(
3569 	bool			new_clustered,
3570 	const TABLE*		altered_table,
3571 	const KEY_PART_INFO*	key_part,
3572 	index_field_t*		index_field)
3573 {
3574 	const Field*	field;
3575 	unsigned	is_unsigned;
3576 	unsigned	num_v = 0;
3577 
3578 	DBUG_ENTER("innobase_create_index_field_def");
3579 
3580 	field = new_clustered
3581 		? altered_table->field[key_part->fieldnr]
3582 		: key_part->field;
3583 
3584 	for (ulint i = 0; i < key_part->fieldnr; i++) {
3585 		if (!altered_table->field[i]->stored_in_db()) {
3586 			num_v++;
3587 		}
3588 	}
3589 
3590 	auto col_type = get_innobase_type_from_mysql_type(
3591 		&is_unsigned, field);
3592 
3593 	if ((index_field->is_v_col = !field->stored_in_db())) {
3594 		index_field->col_no = num_v;
3595 	} else {
3596 		index_field->col_no = key_part->fieldnr - num_v;
3597 	}
3598 
3599 	if (DATA_LARGE_MTYPE(col_type)
3600 	    || (key_part->length < field->pack_length()
3601 		&& field->type() != MYSQL_TYPE_VARCHAR)
3602 	    || (field->type() == MYSQL_TYPE_VARCHAR
3603 		&& key_part->length < field->pack_length()
3604 			- ((Field_varstring*) field)->length_bytes)) {
3605 
3606 		index_field->prefix_len = key_part->length;
3607 	} else {
3608 		index_field->prefix_len = 0;
3609 	}
3610 
3611 	DBUG_VOID_RETURN;
3612 }
3613 
3614 /** Create index definition for key
3615 @param[in]	altered_table		MySQL table that is being altered
3616 @param[in]	keys			key definitions
3617 @param[in]	key_number		MySQL key number
3618 @param[in]	new_clustered		true if generating a new clustered
3619 index on the table
3620 @param[in]	key_clustered		true if this is the new clustered index
3621 @param[out]	index			index definition
3622 @param[in]	heap			heap where memory is allocated */
3623 static MY_ATTRIBUTE((nonnull))
3624 void
innobase_create_index_def(const TABLE * altered_table,const KEY * keys,ulint key_number,bool new_clustered,bool key_clustered,index_def_t * index,mem_heap_t * heap)3625 innobase_create_index_def(
3626 	const TABLE*		altered_table,
3627 	const KEY*		keys,
3628 	ulint			key_number,
3629 	bool			new_clustered,
3630 	bool			key_clustered,
3631 	index_def_t*		index,
3632 	mem_heap_t*		heap)
3633 {
3634 	const KEY*	key = &keys[key_number];
3635 	ulint		i;
3636 	ulint		n_fields = key->user_defined_key_parts;
3637 
3638 	DBUG_ENTER("innobase_create_index_def");
3639 	DBUG_ASSERT(!key_clustered || new_clustered);
3640 
3641 	index->fields = static_cast<index_field_t*>(
3642 		mem_heap_alloc(heap, n_fields * sizeof *index->fields));
3643 
3644 	index->parser = NULL;
3645 	index->key_number = key_number;
3646 	index->n_fields = n_fields;
3647 	index->name = mem_heap_strdup(heap, key->name.str);
3648 	index->rebuild = new_clustered;
3649 
3650 	if (key_clustered) {
3651 		DBUG_ASSERT(!(key->flags & (HA_FULLTEXT | HA_SPATIAL)));
3652 		DBUG_ASSERT(key->flags & HA_NOSAME);
3653 		index->ind_type = DICT_CLUSTERED | DICT_UNIQUE;
3654 	} else if (key->flags & HA_FULLTEXT) {
3655 		DBUG_ASSERT(!(key->flags & (HA_SPATIAL | HA_NOSAME)));
3656 		DBUG_ASSERT(!(key->flags & HA_KEYFLAG_MASK
3657 			      & ~(HA_FULLTEXT
3658 				  | HA_PACK_KEY
3659 				  | HA_BINARY_PACK_KEY)));
3660 		index->ind_type = DICT_FTS;
3661 
3662 		/* Note: key->parser is only parser name,
3663 			 we need to get parser from altered_table instead */
3664 
3665 		if (key->flags & HA_USES_PARSER) {
3666 			for (ulint j = 0; j < altered_table->s->keys; j++) {
3667 				if (!strcmp(altered_table->key_info[j].name.str,
3668 					    key->name.str)) {
3669 					ut_ad(altered_table->key_info[j].flags
3670 					      & HA_USES_PARSER);
3671 
3672 					plugin_ref	parser =
3673 						altered_table->key_info[j].parser;
3674 					index->parser =
3675 						static_cast<st_mysql_ftparser*>(
3676 						plugin_decl(parser)->info);
3677 
3678 					break;
3679 				}
3680 			}
3681 
3682 			DBUG_EXECUTE_IF("fts_instrument_use_default_parser",
3683 				index->parser = &fts_default_parser;);
3684 			ut_ad(index->parser);
3685 		}
3686 	} else if (key->flags & HA_SPATIAL) {
3687 		DBUG_ASSERT(!(key->flags & HA_NOSAME));
3688 		index->ind_type = DICT_SPATIAL;
3689 		ut_ad(n_fields == 1);
3690 		ulint	num_v = 0;
3691 
3692 		/* Need to count the virtual fields before this spatial
3693 		indexed field */
3694 		for (ulint i = 0; i < key->key_part->fieldnr; i++) {
3695 			num_v += !altered_table->field[i]->stored_in_db();
3696 		}
3697 		index->fields[0].col_no = key->key_part[0].fieldnr - num_v;
3698 		index->fields[0].prefix_len = 0;
3699 		index->fields[0].is_v_col = false;
3700 
3701 		/* Currently, the spatial index cannot be created
3702 		on virtual columns. It is blocked in the SQL layer. */
3703 		DBUG_ASSERT(key->key_part[0].field->stored_in_db());
3704 	} else {
3705 		index->ind_type = (key->flags & HA_NOSAME) ? DICT_UNIQUE : 0;
3706 	}
3707 
3708 	if (!(key->flags & HA_SPATIAL)) {
3709 		for (i = 0; i < n_fields; i++) {
3710 			innobase_create_index_field_def(
3711 				new_clustered, altered_table,
3712 				&key->key_part[i], &index->fields[i]);
3713 
3714 			if (index->fields[i].is_v_col) {
3715 				index->ind_type |= DICT_VIRTUAL;
3716 			}
3717 		}
3718 	}
3719 
3720 	DBUG_VOID_RETURN;
3721 }
3722 
3723 /*******************************************************************//**
3724 Check whether the table has a unique index with FTS_DOC_ID_INDEX_NAME
3725 on the Doc ID column.
3726 @return the status of the FTS_DOC_ID index */
3727 enum fts_doc_id_index_enum
innobase_fts_check_doc_id_index(const dict_table_t * table,const TABLE * altered_table,ulint * fts_doc_col_no)3728 innobase_fts_check_doc_id_index(
3729 /*============================*/
3730 	const dict_table_t*	table,		/*!< in: table definition */
3731 	const TABLE*		altered_table,	/*!< in: MySQL table
3732 						that is being altered */
3733 	ulint*			fts_doc_col_no)	/*!< out: The column number for
3734 						Doc ID, or ULINT_UNDEFINED
3735 						if it is being created in
3736 						ha_alter_info */
3737 {
3738 	const dict_index_t*	index;
3739 	const dict_field_t*	field;
3740 
3741 	if (altered_table) {
3742 		/* Check if a unique index with the name of
3743 		FTS_DOC_ID_INDEX_NAME is being created. */
3744 
3745 		for (uint i = 0; i < altered_table->s->keys; i++) {
3746 			const KEY& key = altered_table->key_info[i];
3747 
3748 			if (innobase_strcasecmp(
3749 				    key.name.str, FTS_DOC_ID_INDEX_NAME)) {
3750 				continue;
3751 			}
3752 
3753 			if ((key.flags & HA_NOSAME)
3754 			    && key.user_defined_key_parts == 1
3755 			    && !strcmp(key.name.str, FTS_DOC_ID_INDEX_NAME)
3756 			    && !strcmp(key.key_part[0].field->field_name.str,
3757 				       FTS_DOC_ID_COL_NAME)) {
3758 				if (fts_doc_col_no) {
3759 					*fts_doc_col_no = ULINT_UNDEFINED;
3760 				}
3761 				return(FTS_EXIST_DOC_ID_INDEX);
3762 			} else {
3763 				return(FTS_INCORRECT_DOC_ID_INDEX);
3764 			}
3765 		}
3766 	}
3767 
3768 	if (!table) {
3769 		return(FTS_NOT_EXIST_DOC_ID_INDEX);
3770 	}
3771 
3772 	for (index = dict_table_get_first_index(table);
3773 	     index; index = dict_table_get_next_index(index)) {
3774 
3775 
3776 		/* Check if there exists a unique index with the name of
3777 		FTS_DOC_ID_INDEX_NAME and ignore the corrupted index */
3778 		if (index->type & DICT_CORRUPT
3779 		    || innobase_strcasecmp(index->name, FTS_DOC_ID_INDEX_NAME)) {
3780 			continue;
3781 		}
3782 
3783 		if (!dict_index_is_unique(index)
3784 		    || dict_index_get_n_unique(index) > 1
3785 		    || strcmp(index->name, FTS_DOC_ID_INDEX_NAME)) {
3786 			return(FTS_INCORRECT_DOC_ID_INDEX);
3787 		}
3788 
3789 		/* Check whether the index has FTS_DOC_ID as its
3790 		first column */
3791 		field = dict_index_get_nth_field(index, 0);
3792 
3793 		/* The column would be of a BIGINT data type */
3794 		if (strcmp(field->name, FTS_DOC_ID_COL_NAME) == 0
3795 		    && field->col->mtype == DATA_INT
3796 		    && field->col->len == 8
3797 		    && field->col->prtype & DATA_NOT_NULL
3798 		    && !field->col->is_virtual()) {
3799 			if (fts_doc_col_no) {
3800 				*fts_doc_col_no = dict_col_get_no(field->col);
3801 			}
3802 			return(FTS_EXIST_DOC_ID_INDEX);
3803 		} else {
3804 			return(FTS_INCORRECT_DOC_ID_INDEX);
3805 		}
3806 	}
3807 
3808 
3809 	/* Not found */
3810 	return(FTS_NOT_EXIST_DOC_ID_INDEX);
3811 }
3812 /*******************************************************************//**
3813 Check whether the table has a unique index with FTS_DOC_ID_INDEX_NAME
3814 on the Doc ID column in MySQL create index definition.
3815 @return FTS_EXIST_DOC_ID_INDEX if there exists the FTS_DOC_ID index,
3816 FTS_INCORRECT_DOC_ID_INDEX if the FTS_DOC_ID index is of wrong format */
3817 enum fts_doc_id_index_enum
innobase_fts_check_doc_id_index_in_def(ulint n_key,const KEY * key_info)3818 innobase_fts_check_doc_id_index_in_def(
3819 /*===================================*/
3820 	ulint		n_key,		/*!< in: Number of keys */
3821 	const KEY*	key_info)	/*!< in: Key definition */
3822 {
3823 	/* Check whether there is a "FTS_DOC_ID_INDEX" in the to be built index
3824 	list */
3825 	for (ulint j = 0; j < n_key; j++) {
3826 		const KEY*	key = &key_info[j];
3827 
3828 		if (innobase_strcasecmp(key->name.str, FTS_DOC_ID_INDEX_NAME)) {
3829 			continue;
3830 		}
3831 
3832 		/* Do a check on FTS DOC ID_INDEX, it must be unique,
3833 		named as "FTS_DOC_ID_INDEX" and on column "FTS_DOC_ID" */
3834 		if (!(key->flags & HA_NOSAME)
3835 		    || key->user_defined_key_parts != 1
3836 		    || strcmp(key->name.str, FTS_DOC_ID_INDEX_NAME)
3837 		    || strcmp(key->key_part[0].field->field_name.str,
3838 			      FTS_DOC_ID_COL_NAME)) {
3839 			return(FTS_INCORRECT_DOC_ID_INDEX);
3840 		}
3841 
3842 		return(FTS_EXIST_DOC_ID_INDEX);
3843 	}
3844 
3845 	return(FTS_NOT_EXIST_DOC_ID_INDEX);
3846 }
3847 
3848 /** Create an index table where indexes are ordered as follows:
3849 
3850 IF a new primary key is defined for the table THEN
3851 
3852 	1) New primary key
3853 	2) The remaining keys in key_info
3854 
3855 ELSE
3856 
3857 	1) All new indexes in the order they arrive from MySQL
3858 
3859 ENDIF
3860 
3861 @return key definitions */
3862 MY_ATTRIBUTE((nonnull, warn_unused_result, malloc))
3863 inline index_def_t*
create_key_defs(const Alter_inplace_info * ha_alter_info,const TABLE * altered_table,ulint & n_fts_add,ulint & fts_doc_id_col,bool & add_fts_doc_id,bool & add_fts_doc_idx,const TABLE * table)3864 ha_innobase_inplace_ctx::create_key_defs(
3865 	const Alter_inplace_info*	ha_alter_info,
3866 			/*!< in: alter operation */
3867 	const TABLE*			altered_table,
3868 			/*!< in: MySQL table that is being altered */
3869 	ulint&				n_fts_add,
3870 			/*!< out: number of FTS indexes to be created */
3871 	ulint&				fts_doc_id_col,
3872 			/*!< in: The column number for Doc ID */
3873 	bool&				add_fts_doc_id,
3874 			/*!< in: whether we need to add new DOC ID
3875 			column for FTS index */
3876 	bool&				add_fts_doc_idx,
3877 			/*!< in: whether we need to add new DOC ID
3878 			index for FTS index */
3879 	const TABLE*			table)
3880 			/*!< in: MySQL table that is being altered */
3881 {
3882 	ulint&			n_add = num_to_add_index;
3883 	const bool got_default_clust = new_table->indexes.start->is_gen_clust();
3884 
3885 	index_def_t*		indexdef;
3886 	index_def_t*		indexdefs;
3887 	bool			new_primary;
3888 	const uint*const	add
3889 		= ha_alter_info->index_add_buffer;
3890 	const KEY*const		key_info
3891 		= ha_alter_info->key_info_buffer;
3892 
3893 	DBUG_ENTER("ha_innobase_inplace_ctx::create_key_defs");
3894 	DBUG_ASSERT(!add_fts_doc_id || add_fts_doc_idx);
3895 	DBUG_ASSERT(ha_alter_info->index_add_count == n_add);
3896 
3897 	/* If there is a primary key, it is always the first index
3898 	defined for the innodb_table. */
3899 
3900 	new_primary = n_add > 0
3901 		&& !my_strcasecmp(system_charset_info,
3902 				  key_info[*add].name.str, "PRIMARY");
3903 	n_fts_add = 0;
3904 
3905 	/* If there is a UNIQUE INDEX consisting entirely of NOT NULL
3906 	columns and if the index does not contain column prefix(es)
3907 	(only prefix/part of the column is indexed), MySQL will treat the
3908 	index as a PRIMARY KEY unless the table already has one. */
3909 
3910 	ut_ad(altered_table->s->primary_key == 0
3911 	      || altered_table->s->primary_key == MAX_KEY);
3912 
3913 	if (got_default_clust && !new_primary) {
3914 		new_primary = (altered_table->s->primary_key != MAX_KEY);
3915 	}
3916 
3917 	const bool rebuild = new_primary || add_fts_doc_id
3918 		|| innobase_need_rebuild(ha_alter_info, table);
3919 
3920 	/* Reserve one more space if new_primary is true, and we might
3921 	need to add the FTS_DOC_ID_INDEX */
3922 	indexdef = indexdefs = static_cast<index_def_t*>(
3923 		mem_heap_alloc(
3924 			heap, sizeof *indexdef
3925 			* (ha_alter_info->key_count
3926 			   + rebuild
3927 			   + got_default_clust)));
3928 
3929 	if (rebuild) {
3930 		ulint	primary_key_number;
3931 
3932 		if (new_primary) {
3933 			DBUG_ASSERT(n_add || got_default_clust);
3934 			DBUG_ASSERT(n_add || !altered_table->s->primary_key);
3935 			primary_key_number = altered_table->s->primary_key;
3936 		} else if (got_default_clust) {
3937 			/* Create the GEN_CLUST_INDEX */
3938 			index_def_t*	index = indexdef++;
3939 
3940 			index->fields = NULL;
3941 			index->n_fields = 0;
3942 			index->ind_type = DICT_CLUSTERED;
3943 			index->name = innobase_index_reserve_name;
3944 			index->rebuild = true;
3945 			index->key_number = ~0U;
3946 			primary_key_number = ULINT_UNDEFINED;
3947 			goto created_clustered;
3948 		} else {
3949 			primary_key_number = 0;
3950 		}
3951 
3952 		/* Create the PRIMARY key index definition */
3953 		innobase_create_index_def(
3954 			altered_table, key_info, primary_key_number,
3955 			true, true, indexdef++, heap);
3956 
3957 created_clustered:
3958 		n_add = 1;
3959 
3960 		for (ulint i = 0; i < ha_alter_info->key_count; i++) {
3961 			if (i == primary_key_number) {
3962 				continue;
3963 			}
3964 			/* Copy the index definitions. */
3965 			innobase_create_index_def(
3966 				altered_table, key_info, i, true,
3967 				false, indexdef, heap);
3968 
3969 			if (indexdef->ind_type & DICT_FTS) {
3970 				n_fts_add++;
3971 			}
3972 
3973 			indexdef++;
3974 			n_add++;
3975 		}
3976 
3977 		if (n_fts_add > 0) {
3978 			ulint	num_v = 0;
3979 
3980 			if (!add_fts_doc_id
3981 			    && !innobase_fts_check_doc_id_col(
3982 				    NULL, altered_table,
3983 				    &fts_doc_id_col, &num_v)) {
3984 				fts_doc_id_col = altered_table->s->fields - num_v;
3985 				add_fts_doc_id = true;
3986 			}
3987 
3988 			if (!add_fts_doc_idx) {
3989 				fts_doc_id_index_enum	ret;
3990 				ulint			doc_col_no;
3991 
3992 				ret = innobase_fts_check_doc_id_index(
3993 					NULL, altered_table, &doc_col_no);
3994 
3995 				/* This should have been checked before */
3996 				ut_ad(ret != FTS_INCORRECT_DOC_ID_INDEX);
3997 
3998 				if (ret == FTS_NOT_EXIST_DOC_ID_INDEX) {
3999 					add_fts_doc_idx = true;
4000 				} else {
4001 					ut_ad(ret == FTS_EXIST_DOC_ID_INDEX);
4002 					ut_ad(doc_col_no == ULINT_UNDEFINED
4003 					      || doc_col_no == fts_doc_id_col);
4004 				}
4005 			}
4006 		}
4007 	} else {
4008 		/* Create definitions for added secondary indexes. */
4009 
4010 		for (ulint i = 0; i < n_add; i++) {
4011 			innobase_create_index_def(
4012 				altered_table, key_info, add[i],
4013 				false, false, indexdef, heap);
4014 
4015 			if (indexdef->ind_type & DICT_FTS) {
4016 				n_fts_add++;
4017 			}
4018 
4019 			indexdef++;
4020 		}
4021 	}
4022 
4023 	DBUG_ASSERT(indexdefs + n_add == indexdef);
4024 
4025 	if (add_fts_doc_idx) {
4026 		index_def_t*	index = indexdef++;
4027 
4028 		index->fields = static_cast<index_field_t*>(
4029 			mem_heap_alloc(heap, sizeof *index->fields));
4030 		index->n_fields = 1;
4031 		index->fields->col_no = fts_doc_id_col;
4032 		index->fields->prefix_len = 0;
4033 		index->fields->is_v_col = false;
4034 		index->ind_type = DICT_UNIQUE;
4035 		ut_ad(!rebuild
4036 		      || !add_fts_doc_id
4037 		      || fts_doc_id_col <= altered_table->s->fields);
4038 
4039 		index->name = FTS_DOC_ID_INDEX_NAME;
4040 		index->rebuild = rebuild;
4041 
4042 		/* TODO: assign a real MySQL key number for this */
4043 		index->key_number = ULINT_UNDEFINED;
4044 		n_add++;
4045 	}
4046 
4047 	DBUG_ASSERT(indexdef > indexdefs);
4048 	DBUG_ASSERT((ulint) (indexdef - indexdefs)
4049 		    <= ha_alter_info->key_count
4050 		    + add_fts_doc_idx + got_default_clust);
4051 	DBUG_ASSERT(ha_alter_info->index_add_count <= n_add);
4052 	DBUG_RETURN(indexdefs);
4053 }
4054 
4055 MY_ATTRIBUTE((warn_unused_result))
too_big_key_part_length(size_t max_field_len,const KEY & key)4056 bool too_big_key_part_length(size_t max_field_len, const KEY& key)
4057 {
4058 	for (ulint i = 0; i < key.user_defined_key_parts; i++) {
4059 		if (key.key_part[i].length > max_field_len) {
4060 			return true;
4061 		}
4062 	}
4063 	return false;
4064 }
4065 
4066 /********************************************************************//**
4067 Drop any indexes that we were not able to free previously due to
4068 open table handles. */
4069 static
4070 void
online_retry_drop_indexes_low(dict_table_t * table,trx_t * trx)4071 online_retry_drop_indexes_low(
4072 /*==========================*/
4073 	dict_table_t*	table,	/*!< in/out: table */
4074 	trx_t*		trx)	/*!< in/out: transaction */
4075 {
4076 	ut_ad(mutex_own(&dict_sys.mutex));
4077 	ut_ad(trx->dict_operation_lock_mode == RW_X_LATCH);
4078 	ut_ad(trx_get_dict_operation(trx) == TRX_DICT_OP_INDEX);
4079 
4080 	/* We can have table->n_ref_count > 1, because other threads
4081 	may have prebuilt->table pointing to the table. However, these
4082 	other threads should be between statements, waiting for the
4083 	next statement to execute, or for a meta-data lock. */
4084 	ut_ad(table->get_ref_count() >= 1);
4085 
4086 	if (table->drop_aborted) {
4087 		row_merge_drop_indexes(trx, table, true);
4088 	}
4089 }
4090 
4091 /********************************************************************//**
4092 Drop any indexes that we were not able to free previously due to
4093 open table handles. */
4094 static MY_ATTRIBUTE((nonnull))
4095 void
online_retry_drop_indexes(dict_table_t * table,THD * user_thd)4096 online_retry_drop_indexes(
4097 /*======================*/
4098 	dict_table_t*	table,		/*!< in/out: table */
4099 	THD*		user_thd)	/*!< in/out: MySQL connection */
4100 {
4101 	if (table->drop_aborted) {
4102 		trx_t*	trx = innobase_trx_allocate(user_thd);
4103 
4104 		trx_start_for_ddl(trx, TRX_DICT_OP_INDEX);
4105 
4106 		row_mysql_lock_data_dictionary(trx);
4107 		online_retry_drop_indexes_low(table, trx);
4108 		trx_commit_for_mysql(trx);
4109 		row_mysql_unlock_data_dictionary(trx);
4110 		trx->free();
4111 	}
4112 
4113 	ut_d(mutex_enter(&dict_sys.mutex));
4114 	ut_d(dict_table_check_for_dup_indexes(table, CHECK_ALL_COMPLETE));
4115 	ut_d(mutex_exit(&dict_sys.mutex));
4116 	ut_ad(!table->drop_aborted);
4117 }
4118 
4119 /********************************************************************//**
4120 Commit a dictionary transaction and drop any indexes that we were not
4121 able to free previously due to open table handles. */
4122 static MY_ATTRIBUTE((nonnull))
4123 void
online_retry_drop_indexes_with_trx(dict_table_t * table,trx_t * trx)4124 online_retry_drop_indexes_with_trx(
4125 /*===============================*/
4126 	dict_table_t*	table,	/*!< in/out: table */
4127 	trx_t*		trx)	/*!< in/out: transaction */
4128 {
4129 	ut_ad(trx_state_eq(trx, TRX_STATE_NOT_STARTED));
4130 
4131 	ut_ad(trx->dict_operation_lock_mode == RW_X_LATCH);
4132 
4133 	/* Now that the dictionary is being locked, check if we can
4134 	drop any incompletely created indexes that may have been left
4135 	behind in rollback_inplace_alter_table() earlier. */
4136 	if (table->drop_aborted) {
4137 
4138 		trx->table_id = 0;
4139 
4140 		trx_start_for_ddl(trx, TRX_DICT_OP_INDEX);
4141 
4142 		online_retry_drop_indexes_low(table, trx);
4143 		trx_commit_for_mysql(trx);
4144 	}
4145 }
4146 
4147 /** Determines if InnoDB is dropping a foreign key constraint.
4148 @param foreign the constraint
4149 @param drop_fk constraints being dropped
4150 @param n_drop_fk number of constraints that are being dropped
4151 @return whether the constraint is being dropped */
4152 MY_ATTRIBUTE((pure, nonnull(1), warn_unused_result))
4153 inline
4154 bool
innobase_dropping_foreign(const dict_foreign_t * foreign,dict_foreign_t ** drop_fk,ulint n_drop_fk)4155 innobase_dropping_foreign(
4156 	const dict_foreign_t*	foreign,
4157 	dict_foreign_t**	drop_fk,
4158 	ulint			n_drop_fk)
4159 {
4160 	while (n_drop_fk--) {
4161 		if (*drop_fk++ == foreign) {
4162 			return(true);
4163 		}
4164 	}
4165 
4166 	return(false);
4167 }
4168 
4169 /** Determines if an InnoDB FOREIGN KEY constraint depends on a
4170 column that is being dropped or modified to NOT NULL.
4171 @param user_table InnoDB table as it is before the ALTER operation
4172 @param col_name Name of the column being altered
4173 @param drop_fk constraints being dropped
4174 @param n_drop_fk number of constraints that are being dropped
4175 @param drop true=drop column, false=set NOT NULL
4176 @retval true Not allowed (will call my_error())
4177 @retval false Allowed
4178 */
4179 MY_ATTRIBUTE((pure, nonnull(1,4), warn_unused_result))
4180 static
4181 bool
innobase_check_foreigns_low(const dict_table_t * user_table,dict_foreign_t ** drop_fk,ulint n_drop_fk,const char * col_name,bool drop)4182 innobase_check_foreigns_low(
4183 	const dict_table_t*	user_table,
4184 	dict_foreign_t**	drop_fk,
4185 	ulint			n_drop_fk,
4186 	const char*		col_name,
4187 	bool			drop)
4188 {
4189 	dict_foreign_t*	foreign;
4190 	ut_ad(mutex_own(&dict_sys.mutex));
4191 
4192 	/* Check if any FOREIGN KEY constraints are defined on this
4193 	column. */
4194 
4195 	for (dict_foreign_set::const_iterator it = user_table->foreign_set.begin();
4196 	     it != user_table->foreign_set.end();
4197 	     ++it) {
4198 
4199 		foreign = *it;
4200 
4201 		if (!drop && !(foreign->type
4202 			       & (DICT_FOREIGN_ON_DELETE_SET_NULL
4203 				  | DICT_FOREIGN_ON_UPDATE_SET_NULL))) {
4204 			continue;
4205 		}
4206 
4207 		if (innobase_dropping_foreign(foreign, drop_fk, n_drop_fk)) {
4208 			continue;
4209 		}
4210 
4211 		for (unsigned f = 0; f < foreign->n_fields; f++) {
4212 			if (!strcmp(foreign->foreign_col_names[f],
4213 				    col_name)) {
4214 				my_error(drop
4215 					 ? ER_FK_COLUMN_CANNOT_DROP
4216 					 : ER_FK_COLUMN_NOT_NULL, MYF(0),
4217 					 col_name, foreign->id);
4218 				return(true);
4219 			}
4220 		}
4221 	}
4222 
4223 	if (!drop) {
4224 		/* SET NULL clauses on foreign key constraints of
4225 		child tables affect the child tables, not the parent table.
4226 		The column can be NOT NULL in the parent table. */
4227 		return(false);
4228 	}
4229 
4230 	/* Check if any FOREIGN KEY constraints in other tables are
4231 	referring to the column that is being dropped. */
4232 	for (dict_foreign_set::const_iterator it
4233 		= user_table->referenced_set.begin();
4234 	     it != user_table->referenced_set.end();
4235 	     ++it) {
4236 
4237 		foreign = *it;
4238 
4239 		if (innobase_dropping_foreign(foreign, drop_fk, n_drop_fk)) {
4240 			continue;
4241 		}
4242 
4243 		for (unsigned f = 0; f < foreign->n_fields; f++) {
4244 			char display_name[FN_REFLEN];
4245 
4246 			if (strcmp(foreign->referenced_col_names[f],
4247 				   col_name)) {
4248 				continue;
4249 			}
4250 
4251 			char* buf_end = innobase_convert_name(
4252 				display_name, (sizeof display_name) - 1,
4253 				foreign->foreign_table_name,
4254 				strlen(foreign->foreign_table_name),
4255 				NULL);
4256 			*buf_end = '\0';
4257 			my_error(ER_FK_COLUMN_CANNOT_DROP_CHILD,
4258 				 MYF(0), col_name, foreign->id,
4259 				 display_name);
4260 
4261 			return(true);
4262 		}
4263 	}
4264 
4265 	return(false);
4266 }
4267 
4268 /** Determines if an InnoDB FOREIGN KEY constraint depends on a
4269 column that is being dropped or modified to NOT NULL.
4270 @param ha_alter_info Data used during in-place alter
4271 @param altered_table MySQL table that is being altered
4272 @param old_table MySQL table as it is before the ALTER operation
4273 @param user_table InnoDB table as it is before the ALTER operation
4274 @param drop_fk constraints being dropped
4275 @param n_drop_fk number of constraints that are being dropped
4276 @retval true Not allowed (will call my_error())
4277 @retval false Allowed
4278 */
4279 MY_ATTRIBUTE((pure, nonnull(1,2,3), warn_unused_result))
4280 static
4281 bool
innobase_check_foreigns(Alter_inplace_info * ha_alter_info,const TABLE * old_table,const dict_table_t * user_table,dict_foreign_t ** drop_fk,ulint n_drop_fk)4282 innobase_check_foreigns(
4283 	Alter_inplace_info*	ha_alter_info,
4284 	const TABLE*		old_table,
4285 	const dict_table_t*	user_table,
4286 	dict_foreign_t**	drop_fk,
4287 	ulint			n_drop_fk)
4288 {
4289 	for (Field** fp = old_table->field; *fp; fp++) {
4290 		ut_ad(!(*fp)->real_maybe_null()
4291 		      == !!((*fp)->flags & NOT_NULL_FLAG));
4292 
4293 		auto end = ha_alter_info->alter_info->create_list.end();
4294 		auto it = std::find_if(
4295 			ha_alter_info->alter_info->create_list.begin(), end,
4296 			[fp](const Create_field& field) {
4297 				return field.field == *fp;
4298 			});
4299 
4300 		if (it == end || (it->flags & NOT_NULL_FLAG)) {
4301 			if (innobase_check_foreigns_low(
4302 				    user_table, drop_fk, n_drop_fk,
4303 				    (*fp)->field_name.str, it == end)) {
4304 				return(true);
4305 			}
4306 		}
4307 	}
4308 
4309 	return(false);
4310 }
4311 
4312 /** Convert a default value for ADD COLUMN.
4313 @param[in,out]	heap		Memory heap where allocated
4314 @param[out]	dfield		InnoDB data field to copy to
4315 @param[in]	field		MySQL value for the column
4316 @param[in]	old_field	Old column if altering; NULL for ADD COLUMN
4317 @param[in]	comp		nonzero if in compact format. */
innobase_build_col_map_add(mem_heap_t * heap,dfield_t * dfield,const Field * field,const Field * old_field,ulint comp)4318 static void innobase_build_col_map_add(
4319 	mem_heap_t*	heap,
4320 	dfield_t*	dfield,
4321 	const Field*	field,
4322 	const Field*	old_field,
4323 	ulint		comp)
4324 {
4325 	if (old_field && old_field->real_maybe_null()
4326 	    && field->real_maybe_null()) {
4327 		return;
4328 	}
4329 
4330 	if (field->is_real_null()) {
4331 		dfield_set_null(dfield);
4332 		return;
4333 	}
4334 
4335 	const Field& from = old_field ? *old_field : *field;
4336 	ulint	size	= from.pack_length();
4337 
4338 	byte*	buf	= static_cast<byte*>(mem_heap_alloc(heap, size));
4339 
4340 	row_mysql_store_col_in_innobase_format(
4341 		dfield, buf, true, from.ptr, size, comp);
4342 }
4343 
4344 /** Construct the translation table for reordering, dropping or
4345 adding columns.
4346 
4347 @param ha_alter_info Data used during in-place alter
4348 @param altered_table MySQL table that is being altered
4349 @param table MySQL table as it is before the ALTER operation
4350 @param new_table InnoDB table corresponding to MySQL altered_table
4351 @param old_table InnoDB table corresponding to MYSQL table
4352 @param defaults Default values for ADD COLUMN, or NULL if no ADD COLUMN
4353 @param heap Memory heap where allocated
4354 @return array of integers, mapping column numbers in the table
4355 to column numbers in altered_table */
4356 static MY_ATTRIBUTE((nonnull(1,2,3,4,5,7), warn_unused_result))
4357 const ulint*
innobase_build_col_map(Alter_inplace_info * ha_alter_info,const TABLE * altered_table,const TABLE * table,dict_table_t * new_table,const dict_table_t * old_table,dtuple_t * defaults,mem_heap_t * heap)4358 innobase_build_col_map(
4359 /*===================*/
4360 	Alter_inplace_info*	ha_alter_info,
4361 	const TABLE*		altered_table,
4362 	const TABLE*		table,
4363 	dict_table_t*		new_table,
4364 	const dict_table_t*	old_table,
4365 	dtuple_t*		defaults,
4366 	mem_heap_t*		heap)
4367 {
4368 	DBUG_ENTER("innobase_build_col_map");
4369 	DBUG_ASSERT(altered_table != table);
4370 	DBUG_ASSERT(new_table != old_table);
4371 	DBUG_ASSERT(dict_table_get_n_cols(new_table)
4372 		    + dict_table_get_n_v_cols(new_table)
4373 		    >= altered_table->s->fields + DATA_N_SYS_COLS);
4374 	DBUG_ASSERT(dict_table_get_n_cols(old_table)
4375 		    + dict_table_get_n_v_cols(old_table)
4376 		    >= table->s->fields + DATA_N_SYS_COLS
4377 		    || ha_innobase::omits_virtual_cols(*table->s));
4378 	DBUG_ASSERT(!!defaults == !!(ha_alter_info->handler_flags
4379 				     & INNOBASE_DEFAULTS));
4380 	DBUG_ASSERT(!defaults || dtuple_get_n_fields(defaults)
4381 		    == dict_table_get_n_cols(new_table));
4382 
4383 	const uint old_n_v_cols = uint(table->s->fields
4384 				       - table->s->stored_fields);
4385 	DBUG_ASSERT(old_n_v_cols == old_table->n_v_cols
4386 		    || table->s->frm_version < FRM_VER_EXPRESSSIONS);
4387 	DBUG_ASSERT(!old_n_v_cols || table->s->virtual_fields);
4388 
4389 	ulint*	col_map = static_cast<ulint*>(
4390 		mem_heap_alloc(
4391 			heap, (size_t(old_table->n_cols) + old_n_v_cols)
4392 			* sizeof *col_map));
4393 
4394 	uint	i = 0;
4395 	uint	num_v = 0;
4396 
4397 	/* Any dropped columns will map to ULINT_UNDEFINED. */
4398 	for (uint old_i = 0; old_i + DATA_N_SYS_COLS < old_table->n_cols;
4399 	     old_i++) {
4400 		col_map[old_i] = ULINT_UNDEFINED;
4401 	}
4402 
4403 	for (uint old_i = 0; old_i < old_n_v_cols; old_i++) {
4404 		col_map[old_i + old_table->n_cols] = ULINT_UNDEFINED;
4405 	}
4406 
4407 	const bool omits_virtual = ha_innobase::omits_virtual_cols(*table->s);
4408 
4409 	for (const Create_field& new_field :
4410 	     ha_alter_info->alter_info->create_list) {
4411 		bool	is_v = !new_field.stored_in_db();
4412 		ulint	num_old_v = 0;
4413 
4414 		for (uint old_i = 0; table->field[old_i]; old_i++) {
4415 			const Field* field = table->field[old_i];
4416 			if (!field->stored_in_db()) {
4417 				if (is_v && new_field.field == field) {
4418 					if (!omits_virtual) {
4419 						col_map[old_table->n_cols
4420 							+ num_v]
4421 							= num_old_v;
4422 					}
4423 					num_old_v++;
4424 					goto found_col;
4425 				}
4426 				num_old_v++;
4427 				continue;
4428 			}
4429 
4430 			if (new_field.field == field) {
4431 
4432 				const Field* altered_field =
4433 					altered_table->field[i + num_v];
4434 
4435 				if (defaults) {
4436 					innobase_build_col_map_add(
4437 						heap,
4438 						dtuple_get_nth_field(
4439 							defaults, i),
4440 						altered_field,
4441 						field,
4442 						dict_table_is_comp(
4443 							new_table));
4444 				}
4445 
4446 				col_map[old_i - num_old_v] = i;
4447 				if (!old_table->versioned()
4448 				    || !altered_table->versioned()) {
4449 				} else if (old_i == old_table->vers_start) {
4450 					new_table->vers_start = (i + num_v)
4451 						& dict_index_t::MAX_N_FIELDS;
4452 				} else if (old_i == old_table->vers_end) {
4453 					new_table->vers_end = (i + num_v)
4454 						& dict_index_t::MAX_N_FIELDS;
4455 				}
4456 				goto found_col;
4457 			}
4458 		}
4459 
4460 		if (!is_v) {
4461 			innobase_build_col_map_add(
4462 				heap, dtuple_get_nth_field(defaults, i),
4463 				altered_table->field[i + num_v],
4464 				NULL,
4465 				dict_table_is_comp(new_table));
4466 		}
4467 found_col:
4468 		if (is_v) {
4469 			num_v++;
4470 		} else {
4471 			i++;
4472 		}
4473 	}
4474 
4475 	DBUG_ASSERT(i == altered_table->s->fields - num_v);
4476 
4477 	i = table->s->fields - old_n_v_cols;
4478 
4479 	/* Add the InnoDB hidden FTS_DOC_ID column, if any. */
4480 	if (i + DATA_N_SYS_COLS < old_table->n_cols) {
4481 		/* There should be exactly one extra field,
4482 		the FTS_DOC_ID. */
4483 		DBUG_ASSERT(DICT_TF2_FLAG_IS_SET(old_table,
4484 						 DICT_TF2_FTS_HAS_DOC_ID));
4485 		DBUG_ASSERT(i + DATA_N_SYS_COLS + 1 == old_table->n_cols);
4486 		DBUG_ASSERT(!strcmp(dict_table_get_col_name(
4487 					    old_table, i),
4488 				    FTS_DOC_ID_COL_NAME));
4489 		if (altered_table->s->fields + DATA_N_SYS_COLS
4490 		    - new_table->n_v_cols
4491 		    < new_table->n_cols) {
4492 			DBUG_ASSERT(DICT_TF2_FLAG_IS_SET(
4493 					    new_table,
4494 					    DICT_TF2_FTS_HAS_DOC_ID));
4495 			DBUG_ASSERT(altered_table->s->fields
4496 				    + DATA_N_SYS_COLS + 1
4497 				    == static_cast<ulint>(
4498 					new_table->n_cols
4499 					+ new_table->n_v_cols));
4500 			col_map[i] = altered_table->s->fields
4501 				     - new_table->n_v_cols;
4502 		} else {
4503 			DBUG_ASSERT(!DICT_TF2_FLAG_IS_SET(
4504 					    new_table,
4505 					    DICT_TF2_FTS_HAS_DOC_ID));
4506 			col_map[i] = ULINT_UNDEFINED;
4507 		}
4508 
4509 		i++;
4510 	} else {
4511 		DBUG_ASSERT(!DICT_TF2_FLAG_IS_SET(
4512 				    old_table,
4513 				    DICT_TF2_FTS_HAS_DOC_ID));
4514 	}
4515 
4516 	for (; i < old_table->n_cols; i++) {
4517 		col_map[i] = i + new_table->n_cols - old_table->n_cols;
4518 	}
4519 
4520 	DBUG_RETURN(col_map);
4521 }
4522 
4523 /** Drop newly create FTS index related auxiliary table during
4524 FIC create index process, before fts_add_index is called
4525 @param table table that was being rebuilt online
4526 @param trx transaction
4527 @return DB_SUCCESS if successful, otherwise last error code
4528 */
4529 static
4530 dberr_t
innobase_drop_fts_index_table(dict_table_t * table,trx_t * trx)4531 innobase_drop_fts_index_table(
4532 /*==========================*/
4533         dict_table_t*   table,
4534 	trx_t*		trx)
4535 {
4536 	dberr_t		ret_err = DB_SUCCESS;
4537 
4538 	for (dict_index_t* index = dict_table_get_first_index(table);
4539 	     index != NULL;
4540 	     index = dict_table_get_next_index(index)) {
4541 		if (index->type & DICT_FTS) {
4542 			dberr_t	err;
4543 
4544 			err = fts_drop_index_tables(trx, index);
4545 
4546 			if (err != DB_SUCCESS) {
4547 				ret_err = err;
4548 			}
4549 		}
4550 	}
4551 
4552 	return(ret_err);
4553 }
4554 
4555 /** Get the new non-virtual column names if any columns were renamed
4556 @param ha_alter_info	Data used during in-place alter
4557 @param altered_table	MySQL table that is being altered
4558 @param table		MySQL table as it is before the ALTER operation
4559 @param user_table	InnoDB table as it is before the ALTER operation
4560 @param heap		Memory heap for the allocation
4561 @return array of new column names in rebuilt_table, or NULL if not renamed */
4562 static MY_ATTRIBUTE((nonnull, warn_unused_result))
4563 const char**
innobase_get_col_names(Alter_inplace_info * ha_alter_info,const TABLE * altered_table,const TABLE * table,const dict_table_t * user_table,mem_heap_t * heap)4564 innobase_get_col_names(
4565 	Alter_inplace_info*	ha_alter_info,
4566 	const TABLE*		altered_table,
4567 	const TABLE*		table,
4568 	const dict_table_t*	user_table,
4569 	mem_heap_t*		heap)
4570 {
4571 	const char**		cols;
4572 	uint			i;
4573 
4574 	DBUG_ENTER("innobase_get_col_names");
4575 	DBUG_ASSERT(user_table->n_t_def > table->s->fields);
4576 	DBUG_ASSERT(ha_alter_info->handler_flags
4577 		    & ALTER_COLUMN_NAME);
4578 
4579 	cols = static_cast<const char**>(
4580 		mem_heap_zalloc(heap, user_table->n_def * sizeof *cols));
4581 
4582 	i = 0;
4583 	for (const Create_field& new_field :
4584 	     ha_alter_info->alter_info->create_list) {
4585 		ulint	num_v = 0;
4586 		DBUG_ASSERT(i < altered_table->s->fields);
4587 
4588 		if (!new_field.stored_in_db()) {
4589 			continue;
4590 		}
4591 
4592 		for (uint old_i = 0; table->field[old_i]; old_i++) {
4593 			num_v += !table->field[old_i]->stored_in_db();
4594 
4595 			if (new_field.field == table->field[old_i]) {
4596 				cols[old_i - num_v] = new_field.field_name.str;
4597 				break;
4598 			}
4599 		}
4600 
4601 		i++;
4602 	}
4603 
4604 	/* Copy the internal column names. */
4605 	i = table->s->fields - user_table->n_v_def;
4606 	cols[i] = dict_table_get_col_name(user_table, i);
4607 
4608 	while (++i < user_table->n_def) {
4609 		cols[i] = cols[i - 1] + strlen(cols[i - 1]) + 1;
4610 	}
4611 
4612 	DBUG_RETURN(cols);
4613 }
4614 
4615 /** Check whether the column prefix is increased, decreased, or unchanged.
4616 @param[in]	new_prefix_len	new prefix length
4617 @param[in]	old_prefix_len	new prefix length
4618 @retval	1	prefix is increased
4619 @retval	0	prefix is unchanged
4620 @retval	-1	prefix is decreased */
4621 static inline
4622 lint
innobase_pk_col_prefix_compare(ulint new_prefix_len,ulint old_prefix_len)4623 innobase_pk_col_prefix_compare(
4624 	ulint	new_prefix_len,
4625 	ulint	old_prefix_len)
4626 {
4627 	ut_ad(new_prefix_len < COMPRESSED_REC_MAX_DATA_SIZE);
4628 	ut_ad(old_prefix_len < COMPRESSED_REC_MAX_DATA_SIZE);
4629 
4630 	if (new_prefix_len == old_prefix_len) {
4631 		return(0);
4632 	}
4633 
4634 	if (new_prefix_len == 0) {
4635 		new_prefix_len = ULINT_MAX;
4636 	}
4637 
4638 	if (old_prefix_len == 0) {
4639 		old_prefix_len = ULINT_MAX;
4640 	}
4641 
4642 	if (new_prefix_len > old_prefix_len) {
4643 		return(1);
4644 	} else {
4645 		return(-1);
4646 	}
4647 }
4648 
4649 /** Check whether the column is existing in old table.
4650 @param[in]	new_col_no	new column no
4651 @param[in]	col_map		mapping of old column numbers to new ones
4652 @param[in]	col_map_size	the column map size
4653 @return true if the column is existing, otherwise false. */
4654 static inline
4655 bool
innobase_pk_col_is_existing(const ulint new_col_no,const ulint * col_map,const ulint col_map_size)4656 innobase_pk_col_is_existing(
4657 	const ulint	new_col_no,
4658 	const ulint*	col_map,
4659 	const ulint	col_map_size)
4660 {
4661 	for (ulint i = 0; i < col_map_size; i++) {
4662 		if (col_map[i] == new_col_no) {
4663 			return(true);
4664 		}
4665 	}
4666 
4667 	return(false);
4668 }
4669 
4670 /** Determine whether both the indexes have same set of primary key
4671 fields arranged in the same order.
4672 
4673 Rules when we cannot skip sorting:
4674 (1) Removing existing PK columns somewhere else than at the end of the PK;
4675 (2) Adding existing columns to the PK, except at the end of the PK when no
4676 columns are removed from the PK;
4677 (3) Changing the order of existing PK columns;
4678 (4) Decreasing the prefix length just like removing existing PK columns
4679 follows rule(1), Increasing the prefix length just like adding existing
4680 PK columns follows rule(2).
4681 @param[in]	col_map		mapping of old column numbers to new ones
4682 @param[in]	ha_alter_info	Data used during in-place alter
4683 @param[in]	old_clust_index	index to be compared
4684 @param[in]	new_clust_index index to be compared
4685 @retval true if both indexes have same order.
4686 @retval false. */
4687 static MY_ATTRIBUTE((warn_unused_result))
4688 bool
innobase_pk_order_preserved(const ulint * col_map,const dict_index_t * old_clust_index,const dict_index_t * new_clust_index)4689 innobase_pk_order_preserved(
4690 	const ulint*		col_map,
4691 	const dict_index_t*	old_clust_index,
4692 	const dict_index_t*	new_clust_index)
4693 {
4694 	ulint	old_n_uniq
4695 		= dict_index_get_n_ordering_defined_by_user(
4696 			old_clust_index);
4697 	ulint	new_n_uniq
4698 		= dict_index_get_n_ordering_defined_by_user(
4699 			new_clust_index);
4700 
4701 	ut_ad(dict_index_is_clust(old_clust_index));
4702 	ut_ad(dict_index_is_clust(new_clust_index));
4703 	ut_ad(old_clust_index->table != new_clust_index->table);
4704 	ut_ad(col_map != NULL);
4705 
4706 	if (old_n_uniq == 0) {
4707 		/* There was no PRIMARY KEY in the table.
4708 		If there is no PRIMARY KEY after the ALTER either,
4709 		no sorting is needed. */
4710 		return(new_n_uniq == old_n_uniq);
4711 	}
4712 
4713 	/* DROP PRIMARY KEY is only allowed in combination with
4714 	ADD PRIMARY KEY. */
4715 	ut_ad(new_n_uniq > 0);
4716 
4717 	/* The order of the last processed new_clust_index key field,
4718 	not counting ADD COLUMN, which are constant. */
4719 	lint	last_field_order = -1;
4720 	ulint	existing_field_count = 0;
4721 	ulint	old_n_cols = dict_table_get_n_cols(old_clust_index->table);
4722 	for (ulint new_field = 0; new_field < new_n_uniq; new_field++) {
4723 		ulint	new_col_no =
4724 			new_clust_index->fields[new_field].col->ind;
4725 
4726 		/* Check if there is a match in old primary key. */
4727 		ulint	old_field = 0;
4728 		while (old_field < old_n_uniq) {
4729 			ulint	old_col_no =
4730 				old_clust_index->fields[old_field].col->ind;
4731 
4732 			if (col_map[old_col_no] == new_col_no) {
4733 				break;
4734 			}
4735 
4736 			old_field++;
4737 		}
4738 
4739 		/* The order of key field in the new primary key.
4740 		1. old PK column:      idx in old primary key
4741 		2. existing column:    old_n_uniq + sequence no
4742 		3. newly added column: no order */
4743 		lint		new_field_order;
4744 		const bool	old_pk_column = old_field < old_n_uniq;
4745 
4746 		if (old_pk_column) {
4747 			new_field_order = lint(old_field);
4748 		} else if (innobase_pk_col_is_existing(new_col_no, col_map,
4749 						       old_n_cols)
4750 			   || new_clust_index->table->persistent_autoinc
4751 			   == new_field + 1) {
4752 			/* Adding an existing column or an AUTO_INCREMENT
4753 			column may change the existing ordering. */
4754 			new_field_order = lint(old_n_uniq
4755 					       + existing_field_count++);
4756 		} else {
4757 			/* Skip newly added column. */
4758 			continue;
4759 		}
4760 
4761 		if (last_field_order + 1 != new_field_order) {
4762 			/* Old PK order is not kept, or existing column
4763 			is not added at the end of old PK. */
4764 			return(false);
4765 		}
4766 
4767 		last_field_order = new_field_order;
4768 
4769 		if (!old_pk_column) {
4770 			continue;
4771 		}
4772 
4773 		/* Check prefix length change. */
4774 		const lint	prefix_change = innobase_pk_col_prefix_compare(
4775 			new_clust_index->fields[new_field].prefix_len,
4776 			old_clust_index->fields[old_field].prefix_len);
4777 
4778 		if (prefix_change < 0) {
4779 			/* If a column's prefix length is decreased, it should
4780 			be the last old PK column in new PK.
4781 			Note: we set last_field_order to -2, so that if	there
4782 			are any old PK colmns or existing columns after it in
4783 			new PK, the comparison to new_field_order will fail in
4784 			the next round.*/
4785 			last_field_order = -2;
4786 		} else if (prefix_change > 0) {
4787 			/* If a column's prefix length is increased, it	should
4788 			be the last PK column in old PK. */
4789 			if (old_field != old_n_uniq - 1) {
4790 				return(false);
4791 			}
4792 		}
4793 	}
4794 
4795 	return(true);
4796 }
4797 
4798 /** Update the mtype from DATA_BLOB to DATA_GEOMETRY for a specified
4799 GIS column of a table. This is used when we want to create spatial index
4800 on legacy GIS columns coming from 5.6, where we store GIS data as DATA_BLOB
4801 in innodb layer.
4802 @param[in]	table_id	table id
4803 @param[in]	col_name	column name
4804 @param[in]	trx		data dictionary transaction
4805 @retval true Failure
4806 @retval false Success */
4807 static
4808 bool
innobase_update_gis_column_type(table_id_t table_id,const char * col_name,trx_t * trx)4809 innobase_update_gis_column_type(
4810 	table_id_t	table_id,
4811 	const char*	col_name,
4812 	trx_t*		trx)
4813 {
4814 	pars_info_t*	info;
4815 	dberr_t		error;
4816 
4817 	DBUG_ENTER("innobase_update_gis_column_type");
4818 
4819 	DBUG_ASSERT(trx_get_dict_operation(trx) == TRX_DICT_OP_INDEX);
4820 	ut_ad(trx->dict_operation_lock_mode == RW_X_LATCH);
4821 	ut_d(dict_sys.assert_locked());
4822 
4823 	info = pars_info_create();
4824 
4825 	pars_info_add_ull_literal(info, "tableid", table_id);
4826 	pars_info_add_str_literal(info, "name", col_name);
4827 	pars_info_add_int4_literal(info, "mtype", DATA_GEOMETRY);
4828 
4829 	trx->op_info = "update column type to DATA_GEOMETRY";
4830 
4831 	error = que_eval_sql(
4832 		info,
4833 		"PROCEDURE UPDATE_SYS_COLUMNS_PROC () IS\n"
4834 		"BEGIN\n"
4835 		"UPDATE SYS_COLUMNS SET MTYPE=:mtype\n"
4836 		"WHERE TABLE_ID=:tableid AND NAME=:name;\n"
4837 		"END;\n",
4838 		false, trx);
4839 
4840 	trx->error_state = DB_SUCCESS;
4841 	trx->op_info = "";
4842 
4843 	DBUG_RETURN(error != DB_SUCCESS);
4844 }
4845 
4846 /** Check if we are creating spatial indexes on GIS columns, which are
4847 legacy columns from earlier MySQL, such as 5.6. If so, we have to update
4848 the mtypes of the old GIS columns to DATA_GEOMETRY.
4849 In 5.6, we store GIS columns as DATA_BLOB in InnoDB layer, it will introduce
4850 confusion when we run latest server on older data. That's why we need to
4851 do the upgrade.
4852 @param[in] ha_alter_info	Data used during in-place alter
4853 @param[in] table		Table on which we want to add indexes
4854 @param[in] trx			Transaction
4855 @return DB_SUCCESS if update successfully or no columns need to be updated,
4856 otherwise DB_ERROR, which means we can't update the mtype for some
4857 column, and creating spatial index on it should be dangerous */
4858 static
4859 dberr_t
innobase_check_gis_columns(Alter_inplace_info * ha_alter_info,dict_table_t * table,trx_t * trx)4860 innobase_check_gis_columns(
4861 	Alter_inplace_info*	ha_alter_info,
4862 	dict_table_t*		table,
4863 	trx_t*			trx)
4864 {
4865 	DBUG_ENTER("innobase_check_gis_columns");
4866 
4867 	for (uint key_num = 0;
4868 	     key_num < ha_alter_info->index_add_count;
4869 	     key_num++) {
4870 
4871 		const KEY&	key = ha_alter_info->key_info_buffer[
4872 			ha_alter_info->index_add_buffer[key_num]];
4873 
4874 		if (!(key.flags & HA_SPATIAL)) {
4875 			continue;
4876 		}
4877 
4878 		ut_ad(key.user_defined_key_parts == 1);
4879 		const KEY_PART_INFO&    key_part = key.key_part[0];
4880 
4881 		/* Does not support spatial index on virtual columns */
4882 		if (!key_part.field->stored_in_db()) {
4883 			DBUG_RETURN(DB_UNSUPPORTED);
4884 		}
4885 
4886 		ulint col_nr = dict_table_has_column(
4887 			table,
4888 			key_part.field->field_name.str,
4889 			key_part.fieldnr);
4890 		ut_ad(col_nr != table->n_def);
4891 		dict_col_t*	col = &table->cols[col_nr];
4892 
4893 		if (col->mtype != DATA_BLOB) {
4894 			ut_ad(DATA_GEOMETRY_MTYPE(col->mtype));
4895 			continue;
4896 		}
4897 
4898 		const char* col_name = dict_table_get_col_name(
4899 			table, col_nr);
4900 
4901 		if (innobase_update_gis_column_type(
4902 			table->id, col_name, trx)) {
4903 
4904 			DBUG_RETURN(DB_ERROR);
4905 		} else {
4906 			col->mtype = DATA_GEOMETRY;
4907 
4908 			ib::info() << "Updated mtype of column" << col_name
4909 				<< " in table " << table->name
4910 				<< ", whose id is " << table->id
4911 				<< " to DATA_GEOMETRY";
4912 		}
4913 	}
4914 
4915 	DBUG_RETURN(DB_SUCCESS);
4916 }
4917 
4918 /** Collect virtual column info for its addition
4919 @param[in] ha_alter_info	Data used during in-place alter
4920 @param[in] altered_table	MySQL table that is being altered to
4921 @param[in] table		MySQL table as it is before the ALTER operation
4922 @retval true Failure
4923 @retval false Success */
4924 static
4925 bool
prepare_inplace_add_virtual(Alter_inplace_info * ha_alter_info,const TABLE * altered_table,const TABLE * table)4926 prepare_inplace_add_virtual(
4927 	Alter_inplace_info*	ha_alter_info,
4928 	const TABLE*		altered_table,
4929 	const TABLE*		table)
4930 {
4931 	ha_innobase_inplace_ctx*	ctx;
4932 	uint16_t i = 0, j = 0;
4933 
4934 	ctx = static_cast<ha_innobase_inplace_ctx*>
4935 		(ha_alter_info->handler_ctx);
4936 
4937 	ctx->num_to_add_vcol = altered_table->s->virtual_fields
4938 		+ ctx->num_to_drop_vcol - table->s->virtual_fields;
4939 
4940 	ctx->add_vcol = static_cast<dict_v_col_t*>(
4941 		 mem_heap_zalloc(ctx->heap, ctx->num_to_add_vcol
4942 				 * sizeof *ctx->add_vcol));
4943 	ctx->add_vcol_name = static_cast<const char**>(
4944 		 mem_heap_alloc(ctx->heap, ctx->num_to_add_vcol
4945 				* sizeof *ctx->add_vcol_name));
4946 
4947 	for (const Create_field& new_field :
4948 	     ha_alter_info->alter_info->create_list) {
4949 		const Field* field = altered_table->field[i++];
4950 
4951 		if (new_field.field || field->stored_in_db()) {
4952 			continue;
4953 		}
4954 
4955 		unsigned is_unsigned;
4956 		auto col_type = get_innobase_type_from_mysql_type(
4957 			&is_unsigned, field);
4958 
4959 		auto col_len = field->pack_length();
4960 		unsigned field_type = field->type() | is_unsigned;
4961 
4962 		if (!field->real_maybe_null()) {
4963 			field_type |= DATA_NOT_NULL;
4964 		}
4965 
4966 		if (field->binary()) {
4967 			field_type |= DATA_BINARY_TYPE;
4968 		}
4969 
4970 		unsigned charset_no;
4971 
4972 		if (dtype_is_string_type(col_type)) {
4973 			charset_no = field->charset()->number;
4974 
4975 			DBUG_EXECUTE_IF(
4976 				"ib_alter_add_virtual_fail",
4977 				charset_no += MAX_CHAR_COLL_NUM;);
4978 
4979 			if (charset_no > MAX_CHAR_COLL_NUM) {
4980 				my_error(ER_WRONG_KEY_COLUMN, MYF(0), "InnoDB",
4981 					 field->field_name.str);
4982 				return(true);
4983 			}
4984 		} else {
4985 			charset_no = 0;
4986 		}
4987 
4988 		if (field->type() == MYSQL_TYPE_VARCHAR) {
4989 			uint32  length_bytes
4990 				= static_cast<const Field_varstring*>(
4991 					field)->length_bytes;
4992 
4993 			col_len -= length_bytes;
4994 
4995 			if (length_bytes == 2) {
4996 				field_type |= DATA_LONG_TRUE_VARCHAR;
4997 			}
4998 		}
4999 
5000 		new (&ctx->add_vcol[j]) dict_v_col_t();
5001 		ctx->add_vcol[j].m_col.prtype = dtype_form_prtype(
5002 						field_type, charset_no);
5003 
5004 		ctx->add_vcol[j].m_col.prtype |= DATA_VIRTUAL;
5005 
5006 		ctx->add_vcol[j].m_col.mtype = col_type;
5007 
5008 		ctx->add_vcol[j].m_col.len = static_cast<uint16_t>(col_len);
5009 
5010 		ctx->add_vcol[j].m_col.ind = (i - 1)
5011 			& dict_index_t::MAX_N_FIELDS;
5012 		ctx->add_vcol[j].num_base = 0;
5013 		ctx->add_vcol_name[j] = field->field_name.str;
5014 		ctx->add_vcol[j].base_col = NULL;
5015 		ctx->add_vcol[j].v_pos = (ctx->old_table->n_v_cols
5016 					  - ctx->num_to_drop_vcol + j)
5017 			& dict_index_t::MAX_N_FIELDS;
5018 
5019 		/* MDEV-17468: Do this on ctx->instant_table later */
5020 		innodb_base_col_setup(ctx->old_table, field, &ctx->add_vcol[j]);
5021 		j++;
5022 	}
5023 
5024 	return(false);
5025 }
5026 
5027 /** Collect virtual column info for its addition
5028 @param[in] ha_alter_info	Data used during in-place alter
5029 @param[in] table		MySQL table as it is before the ALTER operation
5030 @retval true Failure
5031 @retval false Success */
5032 static
5033 bool
prepare_inplace_drop_virtual(Alter_inplace_info * ha_alter_info,const TABLE * table)5034 prepare_inplace_drop_virtual(
5035 	Alter_inplace_info*	ha_alter_info,
5036 	const TABLE*		table)
5037 {
5038 	ha_innobase_inplace_ctx*	ctx;
5039 	unsigned i = 0, j = 0;
5040 
5041 	ctx = static_cast<ha_innobase_inplace_ctx*>
5042 		(ha_alter_info->handler_ctx);
5043 
5044 	ctx->num_to_drop_vcol = 0;
5045 	for (i = 0; table->field[i]; i++) {
5046 		const Field* field = table->field[i];
5047 		if (field->flags & FIELD_IS_DROPPED && !field->stored_in_db()) {
5048 			ctx->num_to_drop_vcol++;
5049 		}
5050 	}
5051 
5052 	ctx->drop_vcol = static_cast<dict_v_col_t*>(
5053 		 mem_heap_alloc(ctx->heap, ctx->num_to_drop_vcol
5054 				* sizeof *ctx->drop_vcol));
5055 	ctx->drop_vcol_name = static_cast<const char**>(
5056 		 mem_heap_alloc(ctx->heap, ctx->num_to_drop_vcol
5057 				* sizeof *ctx->drop_vcol_name));
5058 
5059 	for (i = 0; table->field[i]; i++) {
5060 		Field *field =  table->field[i];
5061 		if (!(field->flags & FIELD_IS_DROPPED) || field->stored_in_db()) {
5062 			continue;
5063 		}
5064 
5065 		unsigned is_unsigned;
5066 
5067 		auto col_type = get_innobase_type_from_mysql_type(
5068 			&is_unsigned, field);
5069 
5070 		auto col_len = field->pack_length();
5071 		unsigned field_type = field->type() | is_unsigned;
5072 
5073 		if (!field->real_maybe_null()) {
5074 			field_type |= DATA_NOT_NULL;
5075 		}
5076 
5077 		if (field->binary()) {
5078 			field_type |= DATA_BINARY_TYPE;
5079 		}
5080 
5081 		unsigned charset_no = 0;
5082 
5083 		if (dtype_is_string_type(col_type)) {
5084 			charset_no = field->charset()->number;
5085 
5086 			DBUG_EXECUTE_IF(
5087 				"ib_alter_add_virtual_fail",
5088 				charset_no += MAX_CHAR_COLL_NUM;);
5089 
5090 			if (charset_no > MAX_CHAR_COLL_NUM) {
5091 				my_error(ER_WRONG_KEY_COLUMN, MYF(0), "InnoDB",
5092 					 field->field_name.str);
5093 				return(true);
5094 			}
5095 		} else {
5096 			charset_no = 0;
5097 		}
5098 
5099 		if (field->type() == MYSQL_TYPE_VARCHAR) {
5100 			uint32  length_bytes
5101 				= static_cast<const Field_varstring*>(
5102 					field)->length_bytes;
5103 
5104 			col_len -= length_bytes;
5105 
5106 			if (length_bytes == 2) {
5107 				field_type |= DATA_LONG_TRUE_VARCHAR;
5108 			}
5109 		}
5110 
5111 
5112 		ctx->drop_vcol[j].m_col.prtype = dtype_form_prtype(
5113 						field_type, charset_no);
5114 
5115 		ctx->drop_vcol[j].m_col.prtype |= DATA_VIRTUAL;
5116 
5117 		ctx->drop_vcol[j].m_col.mtype = col_type;
5118 
5119 		ctx->drop_vcol[j].m_col.len = static_cast<uint16_t>(col_len);
5120 
5121 		ctx->drop_vcol[j].m_col.ind = i & dict_index_t::MAX_N_FIELDS;
5122 
5123 		ctx->drop_vcol_name[j] = field->field_name.str;
5124 
5125 		dict_v_col_t*	v_col = dict_table_get_nth_v_col_mysql(
5126 					ctx->old_table, i);
5127 		ctx->drop_vcol[j].v_pos = v_col->v_pos;
5128 		j++;
5129 	}
5130 
5131 	return(false);
5132 }
5133 
5134 /** Insert a new record to INNODB SYS_VIRTUAL
5135 @param[in] table	InnoDB table
5136 @param[in] pos		virtual column column no
5137 @param[in] base_pos	base column pos
5138 @param[in] trx		transaction
5139 @retval	false	on success
5140 @retval	true	on failure (my_error() will have been called) */
innobase_insert_sys_virtual(const dict_table_t * table,ulint pos,ulint base_pos,trx_t * trx)5141 static bool innobase_insert_sys_virtual(
5142 	const dict_table_t*	table,
5143 	ulint			pos,
5144 	ulint			base_pos,
5145 	trx_t*			trx)
5146 {
5147 	pars_info_t*    info = pars_info_create();
5148 	pars_info_add_ull_literal(info, "id", table->id);
5149 	pars_info_add_int4_literal(info, "pos", pos);
5150 	pars_info_add_int4_literal(info, "base_pos", base_pos);
5151 
5152 	if (DB_SUCCESS != que_eval_sql(
5153 		    info,
5154 		    "PROCEDURE P () IS\n"
5155 		    "BEGIN\n"
5156 		    "INSERT INTO SYS_VIRTUAL VALUES (:id, :pos, :base_pos);\n"
5157 		    "END;\n",
5158 		    FALSE, trx)) {
5159 		my_error(ER_INTERNAL_ERROR, MYF(0),
5160 			 "InnoDB: ADD COLUMN...VIRTUAL");
5161 		return true;
5162 	}
5163 
5164 	return false;
5165 }
5166 
5167 /** Insert a record to the SYS_COLUMNS dictionary table.
5168 @param[in]	table_id	table id
5169 @param[in]	pos		position of the column
5170 @param[in]	field_name	field name
5171 @param[in]	mtype		main type
5172 @param[in]	prtype		precise type
5173 @param[in]	len		fixed length in bytes, or 0
5174 @param[in]	n_base		number of base columns of virtual columns, or 0
5175 @param[in]	update		whether to update instead of inserting
5176 @retval	false	on success
5177 @retval	true	on failure (my_error() will have been called) */
innodb_insert_sys_columns(table_id_t table_id,ulint pos,const char * field_name,ulint mtype,ulint prtype,ulint len,ulint n_base,trx_t * trx,bool update=false)5178 static bool innodb_insert_sys_columns(
5179 	table_id_t	table_id,
5180 	ulint		pos,
5181 	const char*	field_name,
5182 	ulint		mtype,
5183 	ulint		prtype,
5184 	ulint		len,
5185 	ulint		n_base,
5186 	trx_t*		trx,
5187 	bool		update = false)
5188 {
5189 	pars_info_t*	info = pars_info_create();
5190 	pars_info_add_ull_literal(info, "id", table_id);
5191 	pars_info_add_int4_literal(info, "pos", pos);
5192 	pars_info_add_str_literal(info, "name", field_name);
5193 	pars_info_add_int4_literal(info, "mtype", mtype);
5194 	pars_info_add_int4_literal(info, "prtype", prtype);
5195 	pars_info_add_int4_literal(info, "len", len);
5196 	pars_info_add_int4_literal(info, "base", n_base);
5197 
5198 	if (update) {
5199 		if (DB_SUCCESS != que_eval_sql(
5200 			    info,
5201 			    "PROCEDURE UPD_COL () IS\n"
5202 			    "BEGIN\n"
5203 			    "UPDATE SYS_COLUMNS SET\n"
5204 			    "NAME=:name, MTYPE=:mtype, PRTYPE=:prtype, "
5205 			    "LEN=:len, PREC=:base\n"
5206 			    "WHERE TABLE_ID=:id AND POS=:pos;\n"
5207 			    "END;\n", FALSE, trx)) {
5208 			my_error(ER_INTERNAL_ERROR, MYF(0),
5209 				 "InnoDB: Updating SYS_COLUMNS failed");
5210 			return true;
5211 		}
5212 
5213 		return false;
5214 	}
5215 
5216 	if (DB_SUCCESS != que_eval_sql(
5217 		    info,
5218 		    "PROCEDURE ADD_COL () IS\n"
5219 		    "BEGIN\n"
5220 		    "INSERT INTO SYS_COLUMNS VALUES"
5221 		    "(:id,:pos,:name,:mtype,:prtype,:len,:base);\n"
5222 		    "END;\n", FALSE, trx)) {
5223 		my_error(ER_INTERNAL_ERROR, MYF(0),
5224 			 "InnoDB: Insert into SYS_COLUMNS failed");
5225 		return true;
5226 	}
5227 
5228 	return false;
5229 }
5230 
5231 /** Update INNODB SYS_COLUMNS on new virtual columns
5232 @param[in] table	InnoDB table
5233 @param[in] col_name	column name
5234 @param[in] vcol		virtual column
5235 @param[in] trx		transaction
5236 @retval	false	on success
5237 @retval	true	on failure (my_error() will have been called) */
innobase_add_one_virtual(const dict_table_t * table,const char * col_name,dict_v_col_t * vcol,trx_t * trx)5238 static bool innobase_add_one_virtual(
5239 	const dict_table_t*	table,
5240 	const char*		col_name,
5241 	dict_v_col_t*		vcol,
5242 	trx_t*			trx)
5243 {
5244 	ulint		pos = dict_create_v_col_pos(vcol->v_pos,
5245 						    vcol->m_col.ind);
5246 
5247 	if (innodb_insert_sys_columns(table->id, pos, col_name,
5248 				      vcol->m_col.mtype, vcol->m_col.prtype,
5249 				      vcol->m_col.len, vcol->num_base, trx)) {
5250 		return true;
5251 	}
5252 
5253 	for (unsigned i = 0; i < vcol->num_base; i++) {
5254 		if (innobase_insert_sys_virtual(
5255 			    table, pos, vcol->base_col[i]->ind, trx)) {
5256 			return true;
5257 		}
5258 	}
5259 
5260 	return false;
5261 }
5262 
5263 /** Update SYS_TABLES.N_COLS in the data dictionary.
5264 @param[in] user_table	InnoDB table
5265 @param[in] n		the new value of SYS_TABLES.N_COLS
5266 @param[in] trx		transaction
5267 @return whether the operation failed */
innodb_update_cols(const dict_table_t * table,ulint n,trx_t * trx)5268 static bool innodb_update_cols(const dict_table_t* table, ulint n, trx_t* trx)
5269 {
5270 	pars_info_t*    info = pars_info_create();
5271 
5272 	pars_info_add_int4_literal(info, "n", n);
5273 	pars_info_add_ull_literal(info, "id", table->id);
5274 
5275 	if (DB_SUCCESS != que_eval_sql(info,
5276 				       "PROCEDURE UPDATE_N_COLS () IS\n"
5277 				       "BEGIN\n"
5278 				       "UPDATE SYS_TABLES SET N_COLS = :n"
5279 				       " WHERE ID = :id;\n"
5280 				       "END;\n", FALSE, trx)) {
5281 		my_error(ER_INTERNAL_ERROR, MYF(0),
5282 			 "InnoDB: Updating SYS_TABLES.N_COLS failed");
5283 		return true;
5284 	}
5285 
5286 	return false;
5287 }
5288 
5289 /** Update system table for adding virtual column(s)
5290 @param[in]	ha_alter_info	Data used during in-place alter
5291 @param[in]	user_table	InnoDB table
5292 @param[in]	trx		transaction
5293 @retval true Failure
5294 @retval false Success */
5295 static
5296 bool
innobase_add_virtual_try(const Alter_inplace_info * ha_alter_info,const dict_table_t * user_table,trx_t * trx)5297 innobase_add_virtual_try(
5298 	const Alter_inplace_info*	ha_alter_info,
5299 	const dict_table_t*		user_table,
5300 	trx_t*				trx)
5301 {
5302 	ha_innobase_inplace_ctx* ctx = static_cast<ha_innobase_inplace_ctx*>(
5303 		ha_alter_info->handler_ctx);
5304 
5305 	for (ulint i = 0; i < ctx->num_to_add_vcol; i++) {
5306 		if (innobase_add_one_virtual(
5307 			    user_table, ctx->add_vcol_name[i],
5308 			    &ctx->add_vcol[i], trx)) {
5309 			return true;
5310 		}
5311 	}
5312 
5313 	return false;
5314 }
5315 
5316 /** Delete metadata from SYS_COLUMNS and SYS_VIRTUAL.
5317 @param[in]	id	table id
5318 @param[in]	pos	first SYS_COLUMNS.POS
5319 @param[in,out]	trx	data dictionary transaction
5320 @retval true Failure
5321 @retval false Success. */
innobase_instant_drop_cols(table_id_t id,ulint pos,trx_t * trx)5322 static bool innobase_instant_drop_cols(table_id_t id, ulint pos, trx_t* trx)
5323 {
5324 	pars_info_t*	info = pars_info_create();
5325 	pars_info_add_ull_literal(info, "id", id);
5326 	pars_info_add_int4_literal(info, "pos", pos);
5327 
5328 	dberr_t err = que_eval_sql(
5329 			info,
5330 			"PROCEDURE DELETE_COL () IS\n"
5331 			"BEGIN\n"
5332 			"DELETE FROM SYS_COLUMNS WHERE\n"
5333 			"TABLE_ID = :id AND POS >= :pos;\n"
5334 			"DELETE FROM SYS_VIRTUAL WHERE TABLE_ID = :id;\n"
5335 			"END;\n", FALSE, trx);
5336 	if (err != DB_SUCCESS) {
5337 		my_error(ER_INTERNAL_ERROR, MYF(0),
5338 			 "InnoDB: DELETE from SYS_COLUMNS/SYS_VIRTUAL failed");
5339 		return true;
5340 	}
5341 
5342 	return false;
5343 }
5344 
5345 /** Update INNODB SYS_COLUMNS on new virtual column's position
5346 @param[in]	table	InnoDB table
5347 @param[in]	old_pos	old position
5348 @param[in]	new_pos	new position
5349 @param[in]	trx	transaction
5350 @return DB_SUCCESS if successful, otherwise error code */
5351 static
5352 dberr_t
innobase_update_v_pos_sys_columns(const dict_table_t * table,ulint old_pos,ulint new_pos,trx_t * trx)5353 innobase_update_v_pos_sys_columns(
5354 	const dict_table_t*	table,
5355 	ulint			old_pos,
5356 	ulint			new_pos,
5357 	trx_t*			trx)
5358 {
5359 	pars_info_t*    info = pars_info_create();
5360 
5361 	pars_info_add_int4_literal(info, "pos", old_pos);
5362 	pars_info_add_int4_literal(info, "val", new_pos);
5363 	pars_info_add_ull_literal(info, "id", table->id);
5364 
5365 	dberr_t error = que_eval_sql(
5366 			info,
5367 			"PROCEDURE P () IS\n"
5368 			"BEGIN\n"
5369 			"UPDATE SYS_COLUMNS\n"
5370 			"SET POS = :val\n"
5371 			"WHERE POS = :pos\n"
5372 			"AND TABLE_ID = :id;\n"
5373 			"END;\n",
5374 			FALSE, trx);
5375 
5376 	return(error);
5377 }
5378 
5379 /** Update INNODB SYS_VIRTUAL table with new virtual column position
5380 @param[in]	table		InnoDB table
5381 @param[in]	old_pos		old position
5382 @param[in]	new_pos		new position
5383 @param[in]	trx		transaction
5384 @return DB_SUCCESS if successful, otherwise error code */
5385 static
5386 dberr_t
innobase_update_v_pos_sys_virtual(const dict_table_t * table,ulint old_pos,ulint new_pos,trx_t * trx)5387 innobase_update_v_pos_sys_virtual(
5388 	const dict_table_t*	table,
5389 	ulint			old_pos,
5390 	ulint			new_pos,
5391 	trx_t*			trx)
5392 {
5393 	pars_info_t*    info = pars_info_create();
5394 
5395 	pars_info_add_int4_literal(info, "pos", old_pos);
5396 	pars_info_add_int4_literal(info, "val", new_pos);
5397 	pars_info_add_ull_literal(info, "id", table->id);
5398 
5399 	dberr_t error = que_eval_sql(
5400 			info,
5401 			"PROCEDURE P () IS\n"
5402 			"BEGIN\n"
5403 			"UPDATE SYS_VIRTUAL\n"
5404 			"SET POS = :val\n"
5405 			"WHERE POS = :pos\n"
5406 			"AND TABLE_ID = :id;\n"
5407 			"END;\n",
5408 			FALSE, trx);
5409 
5410 	return(error);
5411 }
5412 
5413 /** Update InnoDB system tables on dropping a virtual column
5414 @param[in]	table		InnoDB table
5415 @param[in]	col_name	column name of the dropping column
5416 @param[in]	drop_col	col information for the dropping column
5417 @param[in]	n_prev_dropped	number of previously dropped columns in the
5418 				same alter clause
5419 @param[in]	trx		transaction
5420 @return DB_SUCCESS if successful, otherwise error code */
5421 static
5422 dberr_t
innobase_drop_one_virtual_sys_columns(const dict_table_t * table,const char * col_name,dict_col_t * drop_col,ulint n_prev_dropped,trx_t * trx)5423 innobase_drop_one_virtual_sys_columns(
5424 	const dict_table_t*	table,
5425 	const char*		col_name,
5426 	dict_col_t*		drop_col,
5427 	ulint			n_prev_dropped,
5428 	trx_t*			trx)
5429 {
5430 	pars_info_t*    info = pars_info_create();
5431 	pars_info_add_ull_literal(info, "id", table->id);
5432 
5433 	pars_info_add_str_literal(info, "name", col_name);
5434 
5435 	dberr_t error = que_eval_sql(
5436 			info,
5437 			"PROCEDURE P () IS\n"
5438 			"BEGIN\n"
5439 			"DELETE FROM SYS_COLUMNS\n"
5440 			"WHERE TABLE_ID = :id\n"
5441 			"AND NAME = :name;\n"
5442 			"END;\n",
5443 			FALSE, trx);
5444 
5445 	if (error != DB_SUCCESS) {
5446 		return(error);
5447 	}
5448 
5449 	dict_v_col_t*	v_col = dict_table_get_nth_v_col_mysql(
5450 				table, drop_col->ind);
5451 
5452 	/* Adjust column positions for all subsequent columns */
5453 	for (ulint i = v_col->v_pos + 1; i < table->n_v_cols; i++) {
5454 		dict_v_col_t*   t_col = dict_table_get_nth_v_col(table, i);
5455 		ulint		old_p = dict_create_v_col_pos(
5456 			t_col->v_pos - n_prev_dropped,
5457 			t_col->m_col.ind - n_prev_dropped);
5458 		ulint		new_p = dict_create_v_col_pos(
5459 			t_col->v_pos - 1 - n_prev_dropped,
5460 			ulint(t_col->m_col.ind) - 1 - n_prev_dropped);
5461 
5462 		error = innobase_update_v_pos_sys_columns(
5463 			table, old_p, new_p, trx);
5464 		if (error != DB_SUCCESS) {
5465 			return(error);
5466 		}
5467 		error = innobase_update_v_pos_sys_virtual(
5468 			table, old_p, new_p, trx);
5469 		if (error != DB_SUCCESS) {
5470 			return(error);
5471 		}
5472 	}
5473 
5474 	return(error);
5475 }
5476 
5477 /** Delete virtual column's info from INNODB SYS_VIRTUAL
5478 @param[in]	table	InnoDB table
5479 @param[in]	pos	position of the virtual column to be deleted
5480 @param[in]	trx	transaction
5481 @return DB_SUCCESS if successful, otherwise error code */
5482 static
5483 dberr_t
innobase_drop_one_virtual_sys_virtual(const dict_table_t * table,ulint pos,trx_t * trx)5484 innobase_drop_one_virtual_sys_virtual(
5485 	const dict_table_t*	table,
5486 	ulint			pos,
5487 	trx_t*			trx)
5488 {
5489 	pars_info_t*    info = pars_info_create();
5490 	pars_info_add_ull_literal(info, "id", table->id);
5491 
5492 	pars_info_add_int4_literal(info, "pos", pos);
5493 
5494 	dberr_t error = que_eval_sql(
5495 			info,
5496 			"PROCEDURE P () IS\n"
5497 			"BEGIN\n"
5498 			"DELETE FROM SYS_VIRTUAL\n"
5499 			"WHERE TABLE_ID = :id\n"
5500 			"AND POS = :pos;\n"
5501 			"END;\n",
5502 			FALSE, trx);
5503 
5504 	return(error);
5505 }
5506 
5507 /** Update system table for dropping virtual column(s)
5508 @param[in]	ha_alter_info	Data used during in-place alter
5509 @param[in]	user_table	InnoDB table
5510 @param[in]	trx		transaction
5511 @retval true Failure
5512 @retval false Success */
5513 static
5514 bool
innobase_drop_virtual_try(const Alter_inplace_info * ha_alter_info,const dict_table_t * user_table,trx_t * trx)5515 innobase_drop_virtual_try(
5516 	const Alter_inplace_info*	ha_alter_info,
5517 	const dict_table_t*	     user_table,
5518 	trx_t*				trx)
5519 {
5520 	ha_innobase_inplace_ctx*	ctx;
5521 	dberr_t				err = DB_SUCCESS;
5522 
5523 	ctx = static_cast<ha_innobase_inplace_ctx*>
5524 		(ha_alter_info->handler_ctx);
5525 
5526 	for (unsigned i = 0; i < ctx->num_to_drop_vcol; i++) {
5527 
5528 		ulint	pos = dict_create_v_col_pos(
5529 			ctx->drop_vcol[i].v_pos - i,
5530 			ctx->drop_vcol[i].m_col.ind - i);
5531 		err = innobase_drop_one_virtual_sys_virtual(
5532 			user_table, pos, trx);
5533 
5534 		if (err != DB_SUCCESS) {
5535 			my_error(ER_INTERNAL_ERROR, MYF(0),
5536 				 "InnoDB: DROP COLUMN...VIRTUAL");
5537 			return(true);
5538 		}
5539 
5540 		err = innobase_drop_one_virtual_sys_columns(
5541 			user_table, ctx->drop_vcol_name[i],
5542 			&(ctx->drop_vcol[i].m_col), i, trx);
5543 
5544 		if (err != DB_SUCCESS) {
5545 			my_error(ER_INTERNAL_ERROR, MYF(0),
5546 				 "InnoDB: DROP COLUMN...VIRTUAL");
5547 			return(true);
5548 		}
5549 	}
5550 
5551 	return false;
5552 }
5553 
5554 /** Serialise metadata of dropped or reordered columns.
5555 @param[in,out]	heap	memory heap for allocation
5556 @param[out]	field	data field with the metadata */
5557 inline
serialise_columns(mem_heap_t * heap,dfield_t * field) const5558 void dict_table_t::serialise_columns(mem_heap_t* heap, dfield_t* field) const
5559 {
5560 	DBUG_ASSERT(instant);
5561 	const dict_index_t& index = *UT_LIST_GET_FIRST(indexes);
5562 	unsigned n_fixed = index.first_user_field();
5563 	unsigned num_non_pk_fields = index.n_fields - n_fixed;
5564 
5565 	ulint len = 4 + num_non_pk_fields * 2;
5566 
5567 	byte* data = static_cast<byte*>(mem_heap_alloc(heap, len));
5568 
5569 	dfield_set_data(field, data, len);
5570 
5571 	mach_write_to_4(data, num_non_pk_fields);
5572 
5573 	data += 4;
5574 
5575 	for (ulint i = n_fixed; i < index.n_fields; i++) {
5576 		mach_write_to_2(data, instant->field_map[i - n_fixed]);
5577 		data += 2;
5578 	}
5579 }
5580 
5581 /** Construct the metadata record for instant ALTER TABLE.
5582 @param[in]	row	dummy or default values for existing columns
5583 @param[in,out]	heap	memory heap for allocations
5584 @return	metadata record */
5585 inline
5586 dtuple_t*
instant_metadata(const dtuple_t & row,mem_heap_t * heap) const5587 dict_index_t::instant_metadata(const dtuple_t& row, mem_heap_t* heap) const
5588 {
5589 	ut_ad(is_primary());
5590 	dtuple_t* entry;
5591 
5592 	if (!table->instant) {
5593 		entry = row_build_index_entry(&row, NULL, this, heap);
5594 		entry->info_bits = REC_INFO_METADATA_ADD;
5595 		return entry;
5596 	}
5597 
5598 	entry = dtuple_create(heap, n_fields + 1);
5599 	entry->n_fields_cmp = n_uniq;
5600 	entry->info_bits = REC_INFO_METADATA_ALTER;
5601 
5602 	const dict_field_t* field = fields;
5603 
5604 	for (uint i = 0; i <= n_fields; i++, field++) {
5605 		dfield_t* dfield = dtuple_get_nth_field(entry, i);
5606 
5607 		if (i == first_user_field()) {
5608 			table->serialise_columns(heap, dfield);
5609 			dfield->type.metadata_blob_init();
5610 			field--;
5611 			continue;
5612 		}
5613 
5614 		ut_ad(!field->col->is_virtual());
5615 
5616 		if (field->col->is_dropped()) {
5617 			dict_col_copy_type(field->col, &dfield->type);
5618 			if (field->col->is_nullable()) {
5619 				dfield_set_null(dfield);
5620 			} else {
5621 				dfield_set_data(dfield, field_ref_zero,
5622 						field->fixed_len);
5623 			}
5624 			continue;
5625 		}
5626 
5627 		const dfield_t* s = dtuple_get_nth_field(&row, field->col->ind);
5628 		ut_ad(dict_col_type_assert_equal(field->col, &s->type));
5629 		*dfield = *s;
5630 
5631 		if (dfield_is_null(dfield)) {
5632 			continue;
5633 		}
5634 
5635 		if (dfield_is_ext(dfield)) {
5636 			ut_ad(i > first_user_field());
5637 			ut_ad(!field->prefix_len);
5638 			ut_ad(dfield->len >= FIELD_REF_SIZE);
5639 			dfield_set_len(dfield, dfield->len - FIELD_REF_SIZE);
5640 		}
5641 
5642 		if (!field->prefix_len) {
5643 			continue;
5644 		}
5645 
5646 		ut_ad(field->col->ord_part);
5647 		ut_ad(i < n_uniq);
5648 
5649 		ulint len = dtype_get_at_most_n_mbchars(
5650 			field->col->prtype,
5651 			field->col->mbminlen, field->col->mbmaxlen,
5652 			field->prefix_len, dfield->len,
5653 			static_cast<char*>(dfield_get_data(dfield)));
5654 		dfield_set_len(dfield, len);
5655 	}
5656 
5657 	return entry;
5658 }
5659 
5660 /** Insert or update SYS_COLUMNS and the hidden metadata record
5661 for instant ALTER TABLE.
5662 @param[in]	ha_alter_info	ALTER TABLE context
5663 @param[in,out]	ctx		ALTER TABLE context for the current partition
5664 @param[in]	altered_table	MySQL table that is being altered
5665 @param[in]	table		MySQL table as it is before the ALTER operation
5666 @param[in,out]	trx		dictionary transaction
5667 @retval	true	failure
5668 @retval	false	success */
innobase_instant_try(const Alter_inplace_info * ha_alter_info,ha_innobase_inplace_ctx * ctx,const TABLE * altered_table,const TABLE * table,trx_t * trx)5669 static bool innobase_instant_try(
5670 	const Alter_inplace_info*	ha_alter_info,
5671 	ha_innobase_inplace_ctx*	ctx,
5672 	const TABLE*			altered_table,
5673 	const TABLE*			table,
5674 	trx_t*				trx)
5675 {
5676 	DBUG_ASSERT(!ctx->need_rebuild());
5677 	DBUG_ASSERT(ctx->is_instant());
5678 
5679 	dict_table_t* user_table = ctx->old_table;
5680 
5681 	dict_index_t* index = dict_table_get_first_index(user_table);
5682 	const unsigned n_old_fields = index->n_fields;
5683 	const dict_col_t* old_cols = user_table->cols;
5684 	DBUG_ASSERT(user_table->n_cols == ctx->old_n_cols);
5685 
5686 	const bool metadata_changed = ctx->instant_column();
5687 
5688 	DBUG_ASSERT(index->n_fields >= n_old_fields);
5689 	/* The table may have been emptied and may have lost its
5690 	'instantness' during this ALTER TABLE. */
5691 
5692 	/* Construct a table row of default values for the stored columns. */
5693 	dtuple_t* row = dtuple_create(ctx->heap, user_table->n_cols);
5694 	dict_table_copy_types(row, user_table);
5695 	Field** af = altered_table->field;
5696 	Field** const end = altered_table->field + altered_table->s->fields;
5697 	ut_d(List_iterator_fast<Create_field> cf_it(
5698 		     ha_alter_info->alter_info->create_list));
5699 	if (ctx->first_alter_pos
5700 	    && innobase_instant_drop_cols(user_table->id,
5701 					  ctx->first_alter_pos - 1, trx)) {
5702 		return true;
5703 	}
5704 	for (uint i = 0; af < end; af++) {
5705 		if (!(*af)->stored_in_db()) {
5706 			ut_d(cf_it++);
5707 			continue;
5708 		}
5709 
5710 		const dict_col_t* old = dict_table_t::find(old_cols,
5711 							   ctx->col_map,
5712 							   ctx->old_n_cols, i);
5713 		DBUG_ASSERT(!old || i >= ctx->old_n_cols - DATA_N_SYS_COLS
5714 			    || old->ind == i
5715 			    || (ctx->first_alter_pos
5716 				&& old->ind >= ctx->first_alter_pos - 1));
5717 
5718 		dfield_t* d = dtuple_get_nth_field(row, i);
5719 		const dict_col_t* col = dict_table_get_nth_col(user_table, i);
5720 		DBUG_ASSERT(!col->is_virtual());
5721 		DBUG_ASSERT(!col->is_dropped());
5722 		DBUG_ASSERT(col->mtype != DATA_SYS);
5723 		DBUG_ASSERT(!strcmp((*af)->field_name.str,
5724 				    dict_table_get_col_name(user_table, i)));
5725 		DBUG_ASSERT(old || col->is_added());
5726 
5727 		ut_d(const Create_field* new_field = cf_it++);
5728 		/* new_field->field would point to an existing column.
5729 		If it is NULL, the column was added by this ALTER TABLE. */
5730 		ut_ad(!new_field->field == !old);
5731 
5732 		if (col->is_added()) {
5733 			dfield_set_data(d, col->def_val.data,
5734 					col->def_val.len);
5735 		} else if ((*af)->real_maybe_null()) {
5736 			/* Store NULL for nullable 'core' columns. */
5737 			dfield_set_null(d);
5738 		} else {
5739 			switch ((*af)->type()) {
5740 			case MYSQL_TYPE_VARCHAR:
5741 			case MYSQL_TYPE_GEOMETRY:
5742 			case MYSQL_TYPE_TINY_BLOB:
5743 			case MYSQL_TYPE_MEDIUM_BLOB:
5744 			case MYSQL_TYPE_BLOB:
5745 			case MYSQL_TYPE_LONG_BLOB:
5746 			variable_length:
5747 				/* Store the empty string for 'core'
5748 				variable-length NOT NULL columns. */
5749 				dfield_set_data(d, field_ref_zero, 0);
5750 				break;
5751 			case MYSQL_TYPE_STRING:
5752 				if (col->mbminlen != col->mbmaxlen
5753 				    && user_table->not_redundant()) {
5754 					goto variable_length;
5755 				}
5756 				/* fall through */
5757 			default:
5758 				/* For fixed-length NOT NULL 'core' columns,
5759 				get a dummy default value from SQL. Note that
5760 				we will preserve the old values of these
5761 				columns when updating the metadata
5762 				record, to avoid unnecessary updates. */
5763 				ulint len = (*af)->pack_length();
5764 				DBUG_ASSERT(d->type.mtype != DATA_INT
5765 					    || len <= 8);
5766 				row_mysql_store_col_in_innobase_format(
5767 					d, d->type.mtype == DATA_INT
5768 					? static_cast<byte*>(
5769 						mem_heap_alloc(ctx->heap, len))
5770 					: NULL, true, (*af)->ptr, len,
5771 					dict_table_is_comp(user_table));
5772 				ut_ad(new_field->field->pack_length() == len);
5773 			}
5774 		}
5775 
5776 		bool update = old && (!ctx->first_alter_pos
5777 				      || i < ctx->first_alter_pos - 1);
5778 		DBUG_ASSERT(!old || col->same_format(*old));
5779 		if (update
5780 		    && old->prtype == d->type.prtype) {
5781 			/* The record is already present in SYS_COLUMNS. */
5782 		} else if (innodb_insert_sys_columns(user_table->id, i,
5783 						     (*af)->field_name.str,
5784 						     d->type.mtype,
5785 						     d->type.prtype,
5786 						     d->type.len, 0, trx,
5787 						     update)) {
5788 			return true;
5789 		}
5790 
5791 		i++;
5792 	}
5793 
5794 	if (innodb_update_cols(user_table, dict_table_encode_n_col(
5795 				       unsigned(user_table->n_cols)
5796 				       - DATA_N_SYS_COLS,
5797 				       user_table->n_v_cols)
5798 			       | (user_table->flags & DICT_TF_COMPACT) << 31,
5799 			       trx)) {
5800 		return true;
5801 	}
5802 
5803 	if (ctx->first_alter_pos) {
5804 add_all_virtual:
5805 		for (uint i = 0; i < user_table->n_v_cols; i++) {
5806 			if (innobase_add_one_virtual(
5807 				    user_table,
5808 				    dict_table_get_v_col_name(user_table, i),
5809 				    &user_table->v_cols[i], trx)) {
5810 				return true;
5811 			}
5812 		}
5813 	} else if (ha_alter_info->handler_flags & ALTER_DROP_VIRTUAL_COLUMN) {
5814 		if (innobase_instant_drop_cols(user_table->id, 65536, trx)) {
5815 			return true;
5816 		}
5817 		goto add_all_virtual;
5818 	} else if ((ha_alter_info->handler_flags & ALTER_ADD_VIRTUAL_COLUMN)
5819 		   && innobase_add_virtual_try(ha_alter_info, user_table,
5820 					       trx)) {
5821 		return true;
5822         }
5823 
5824 	if (!user_table->space) {
5825 		/* In case of ALTER TABLE...DISCARD TABLESPACE,
5826 		update only the metadata and transform the dictionary
5827 		cache entry to the canonical format. */
5828 		index->clear_instant_alter();
5829 		return false;
5830 	}
5831 
5832 	unsigned i = unsigned(user_table->n_cols) - DATA_N_SYS_COLS;
5833 	DBUG_ASSERT(i >= altered_table->s->stored_fields);
5834 	DBUG_ASSERT(i <= altered_table->s->stored_fields + 1);
5835 	if (i > altered_table->s->fields) {
5836 		const dict_col_t& fts_doc_id = user_table->cols[i - 1];
5837 		DBUG_ASSERT(!strcmp(fts_doc_id.name(*user_table),
5838 				    FTS_DOC_ID_COL_NAME));
5839 		DBUG_ASSERT(!fts_doc_id.is_nullable());
5840 		DBUG_ASSERT(fts_doc_id.len == 8);
5841 		dfield_set_data(dtuple_get_nth_field(row, i - 1),
5842 				field_ref_zero, fts_doc_id.len);
5843 	}
5844 	byte trx_id[DATA_TRX_ID_LEN], roll_ptr[DATA_ROLL_PTR_LEN];
5845 	dfield_set_data(dtuple_get_nth_field(row, i++), field_ref_zero,
5846 			DATA_ROW_ID_LEN);
5847 	dfield_set_data(dtuple_get_nth_field(row, i++), trx_id, sizeof trx_id);
5848 	dfield_set_data(dtuple_get_nth_field(row, i),roll_ptr,sizeof roll_ptr);
5849 	DBUG_ASSERT(i + 1 == user_table->n_cols);
5850 
5851 	trx_write_trx_id(trx_id, trx->id);
5852 	/* The DB_ROLL_PTR will be assigned later, when allocating undo log.
5853 	Silence a Valgrind warning in dtuple_validate() when
5854 	row_ins_clust_index_entry_low() searches for the insert position. */
5855 	memset(roll_ptr, 0, sizeof roll_ptr);
5856 
5857 	dtuple_t* entry = index->instant_metadata(*row, ctx->heap);
5858 	mtr_t	mtr;
5859 	mtr.start();
5860 	index->set_modified(mtr);
5861 	btr_pcur_t pcur;
5862 	btr_pcur_open_at_index_side(true, index, BTR_MODIFY_TREE, &pcur, true,
5863 				    0, &mtr);
5864 	ut_ad(btr_pcur_is_before_first_on_page(&pcur));
5865 	btr_pcur_move_to_next_on_page(&pcur);
5866 
5867 	buf_block_t* block = btr_pcur_get_block(&pcur);
5868 	ut_ad(page_is_leaf(block->frame));
5869 	ut_ad(!page_has_prev(block->frame));
5870 	ut_ad(!buf_block_get_page_zip(block));
5871 	const rec_t* rec = btr_pcur_get_rec(&pcur);
5872 	que_thr_t* thr = pars_complete_graph_for_exec(
5873 		NULL, trx, ctx->heap, NULL);
5874 	const bool is_root = block->page.id().page_no() == index->page;
5875 
5876 	dberr_t err = DB_SUCCESS;
5877 	if (rec_is_metadata(rec, *index)) {
5878 		ut_ad(page_rec_is_user_rec(rec));
5879 		if (is_root
5880 		    && !rec_is_alter_metadata(rec, *index)
5881 		    && !index->table->instant
5882 		    && !page_has_next(block->frame)
5883 		    && page_rec_is_last(rec, block->frame)) {
5884 			goto empty_table;
5885 		}
5886 
5887 		if (!metadata_changed) {
5888 			goto func_exit;
5889 		}
5890 
5891 		/* Ensure that the root page is in the correct format. */
5892 		buf_block_t* root = btr_root_block_get(index, RW_X_LATCH,
5893 						       &mtr);
5894 		DBUG_ASSERT(root);
5895 		if (fil_page_get_type(root->frame) != FIL_PAGE_TYPE_INSTANT) {
5896 			DBUG_ASSERT("wrong page type" == 0);
5897 			err = DB_CORRUPTION;
5898 			goto func_exit;
5899 		}
5900 
5901 		btr_set_instant(root, *index, &mtr);
5902 
5903 		/* Extend the record with any added columns. */
5904 		uint n = uint(index->n_fields) - n_old_fields;
5905 		/* Reserve room for DB_TRX_ID,DB_ROLL_PTR and any
5906 		non-updated off-page columns in case they are moved off
5907 		page as a result of the update. */
5908 		const uint16_t f = user_table->instant != NULL;
5909 		upd_t* update = upd_create(index->n_fields + f, ctx->heap);
5910 		update->n_fields = n + f;
5911 		update->info_bits = f
5912 			? REC_INFO_METADATA_ALTER
5913 			: REC_INFO_METADATA_ADD;
5914 		if (f) {
5915 			upd_field_t* uf = upd_get_nth_field(update, 0);
5916 			uf->field_no = index->first_user_field();
5917 			uf->new_val = entry->fields[uf->field_no];
5918 			DBUG_ASSERT(!dfield_is_ext(&uf->new_val));
5919 			DBUG_ASSERT(!dfield_is_null(&uf->new_val));
5920 		}
5921 
5922 		/* Add the default values for instantly added columns */
5923 		unsigned j = f;
5924 
5925 		for (unsigned k = n_old_fields; k < index->n_fields; k++) {
5926 			upd_field_t* uf = upd_get_nth_field(update, j++);
5927 			uf->field_no = static_cast<uint16_t>(k + f);
5928 			uf->new_val = entry->fields[k + f];
5929 
5930 			ut_ad(j <= n + f);
5931 		}
5932 
5933 		ut_ad(j == n + f);
5934 
5935 		rec_offs* offsets = NULL;
5936 		mem_heap_t* offsets_heap = NULL;
5937 		big_rec_t* big_rec;
5938 		err = btr_cur_pessimistic_update(
5939 			BTR_NO_LOCKING_FLAG | BTR_KEEP_POS_FLAG,
5940 			btr_pcur_get_btr_cur(&pcur),
5941 			&offsets, &offsets_heap, ctx->heap,
5942 			&big_rec, update, UPD_NODE_NO_ORD_CHANGE,
5943 			thr, trx->id, &mtr);
5944 
5945 		offsets = rec_get_offsets(
5946 			btr_pcur_get_rec(&pcur), index, offsets,
5947 			index->n_core_fields, ULINT_UNDEFINED, &offsets_heap);
5948 		if (big_rec) {
5949 			if (err == DB_SUCCESS) {
5950 				err = btr_store_big_rec_extern_fields(
5951 					&pcur, offsets, big_rec, &mtr,
5952 					BTR_STORE_UPDATE);
5953 			}
5954 
5955 			dtuple_big_rec_free(big_rec);
5956 		}
5957 		if (offsets_heap) {
5958 			mem_heap_free(offsets_heap);
5959 		}
5960 		btr_pcur_close(&pcur);
5961 		goto func_exit;
5962 	} else if (is_root && page_rec_is_supremum(rec)
5963 		   && !index->table->instant) {
5964 empty_table:
5965 		/* The table is empty. */
5966 		ut_ad(fil_page_index_page_check(block->frame));
5967 		ut_ad(!page_has_siblings(block->frame));
5968 		ut_ad(block->page.id().page_no() == index->page);
5969 		/* MDEV-17383: free metadata BLOBs! */
5970 		btr_page_empty(block, NULL, index, 0, &mtr);
5971 		if (index->is_instant()) {
5972 			index->clear_instant_add();
5973 		}
5974 		goto func_exit;
5975 	} else if (!user_table->is_instant()) {
5976 		ut_ad(!user_table->not_redundant());
5977 		goto func_exit;
5978 	}
5979 
5980 	/* Convert the table to the instant ALTER TABLE format. */
5981 	mtr.commit();
5982 	mtr.start();
5983 	index->set_modified(mtr);
5984 	if (buf_block_t* root = btr_root_block_get(index, RW_SX_LATCH, &mtr)) {
5985 		if (fil_page_get_type(root->frame) != FIL_PAGE_INDEX) {
5986 			DBUG_ASSERT("wrong page type" == 0);
5987 			goto err_exit;
5988 		}
5989 
5990 		btr_set_instant(root, *index, &mtr);
5991 		mtr.commit();
5992 		mtr.start();
5993 		index->set_modified(mtr);
5994 		err = row_ins_clust_index_entry_low(
5995 			BTR_NO_LOCKING_FLAG, BTR_MODIFY_TREE, index,
5996 			index->n_uniq, entry, 0, thr);
5997 	} else {
5998 err_exit:
5999 		err = DB_CORRUPTION;
6000 	}
6001 
6002 func_exit:
6003 	mtr.commit();
6004 
6005 	if (err != DB_SUCCESS) {
6006 		my_error_innodb(err, table->s->table_name.str,
6007 				user_table->flags);
6008 		return true;
6009 	}
6010 
6011 	return false;
6012 }
6013 
6014 /** Adjust the create index column number from "New table" to
6015 "old InnoDB table" while we are doing dropping virtual column. Since we do
6016 not create separate new table for the dropping/adding virtual columns.
6017 To correctly find the indexed column, we will need to find its col_no
6018 in the "Old Table", not the "New table".
6019 @param[in]	ha_alter_info	Data used during in-place alter
6020 @param[in]	old_table	MySQL table as it is before the ALTER operation
6021 @param[in]	num_v_dropped	number of virtual column dropped
6022 @param[in,out]	index_def	index definition */
6023 static
6024 void
innodb_v_adjust_idx_col(const Alter_inplace_info * ha_alter_info,const TABLE * old_table,ulint num_v_dropped,index_def_t * index_def)6025 innodb_v_adjust_idx_col(
6026 	const Alter_inplace_info*	ha_alter_info,
6027 	const TABLE*			old_table,
6028 	ulint				num_v_dropped,
6029 	index_def_t*			index_def)
6030 {
6031 	for (ulint i = 0; i < index_def->n_fields; i++) {
6032 #ifdef UNIV_DEBUG
6033 		bool	col_found = false;
6034 #endif /* UNIV_DEBUG */
6035 		ulint	num_v = 0;
6036 
6037 		index_field_t*	index_field = &index_def->fields[i];
6038 
6039 		/* Only adjust virtual column col_no, since non-virtual
6040 		column position (in non-vcol list) won't change unless
6041 		table rebuild */
6042 		if (!index_field->is_v_col) {
6043 			continue;
6044 		}
6045 
6046 		const Field*	field = NULL;
6047 
6048 		/* Found the field in the new table */
6049 		for (const Create_field& new_field :
6050 		     ha_alter_info->alter_info->create_list) {
6051 			if (new_field.stored_in_db()) {
6052 				continue;
6053 			}
6054 
6055 			field = new_field.field;
6056 
6057 			if (num_v == index_field->col_no) {
6058 				break;
6059 			}
6060 			num_v++;
6061 		}
6062 
6063 		if (!field) {
6064 			/* this means the field is a newly added field, this
6065 			should have been blocked when we drop virtual column
6066 			at the same time */
6067 			ut_ad(num_v_dropped > 0);
6068 			ut_a(0);
6069 		}
6070 
6071 		ut_ad(!field->stored_in_db());
6072 
6073 		num_v = 0;
6074 
6075 		/* Look for its position in old table */
6076 		for (uint old_i = 0; old_table->field[old_i]; old_i++) {
6077 			if (old_table->field[old_i] == field) {
6078 				/* Found it, adjust its col_no to its position
6079 				in old table */
6080 				index_def->fields[i].col_no = num_v;
6081 				ut_d(col_found = true);
6082 				break;
6083 			}
6084 
6085 			num_v += !old_table->field[old_i]->stored_in_db();
6086 		}
6087 
6088 		ut_ad(col_found);
6089 	}
6090 }
6091 
6092 /** Create index metadata in the data dictionary.
6093 @param[in,out]	trx	dictionary transaction
6094 @param[in,out]	index	index being created
6095 @param[in]	add_v	virtual columns that are being added, or NULL
6096 @return the created index */
6097 MY_ATTRIBUTE((nonnull(1,2), warn_unused_result))
6098 static
6099 dict_index_t*
create_index_dict(trx_t * trx,dict_index_t * index,const dict_add_v_col_t * add_v)6100 create_index_dict(
6101 	trx_t*			trx,
6102 	dict_index_t*		index,
6103 	const dict_add_v_col_t* add_v)
6104 {
6105 	DBUG_ENTER("create_index_dict");
6106 
6107 	mem_heap_t* heap = mem_heap_create(512);
6108 	ind_node_t* node = ind_create_graph_create(
6109 		index, index->table->name.m_name, heap, add_v);
6110 	que_thr_t* thr = pars_complete_graph_for_exec(node, trx, heap, NULL);
6111 
6112 	que_fork_start_command(
6113 		static_cast<que_fork_t*>(que_node_get_parent(thr)));
6114 
6115 	que_run_threads(thr);
6116 
6117 	DBUG_ASSERT(trx->error_state != DB_SUCCESS || index != node->index);
6118 	DBUG_ASSERT(trx->error_state != DB_SUCCESS || node->index);
6119 	index = node->index;
6120 
6121 	que_graph_free((que_t*) que_node_get_parent(thr));
6122 
6123 	DBUG_RETURN(index);
6124 }
6125 
6126 /** Update internal structures with concurrent writes blocked,
6127 while preparing ALTER TABLE.
6128 
6129 @param ha_alter_info Data used during in-place alter
6130 @param altered_table MySQL table that is being altered
6131 @param old_table MySQL table as it is before the ALTER operation
6132 @param table_name Table name in MySQL
6133 @param flags Table and tablespace flags
6134 @param flags2 Additional table flags
6135 @param fts_doc_id_col The column number of FTS_DOC_ID
6136 @param add_fts_doc_id Flag: add column FTS_DOC_ID?
6137 @param add_fts_doc_id_idx Flag: add index FTS_DOC_ID_INDEX (FTS_DOC_ID)?
6138 
6139 @retval true Failure
6140 @retval false Success
6141 */
6142 static MY_ATTRIBUTE((warn_unused_result, nonnull(1,2,3,4)))
6143 bool
prepare_inplace_alter_table_dict(Alter_inplace_info * ha_alter_info,const TABLE * altered_table,const TABLE * old_table,const char * table_name,ulint flags,ulint flags2,ulint fts_doc_id_col,bool add_fts_doc_id,bool add_fts_doc_id_idx)6144 prepare_inplace_alter_table_dict(
6145 /*=============================*/
6146 	Alter_inplace_info*	ha_alter_info,
6147 	const TABLE*		altered_table,
6148 	const TABLE*		old_table,
6149 	const char*		table_name,
6150 	ulint			flags,
6151 	ulint			flags2,
6152 	ulint			fts_doc_id_col,
6153 	bool			add_fts_doc_id,
6154 	bool			add_fts_doc_id_idx)
6155 {
6156 	bool			dict_locked	= false;
6157 	ulint*			add_key_nums;	/* MySQL key numbers */
6158 	index_def_t*		index_defs;	/* index definitions */
6159 	dict_table_t*		user_table;
6160 	dict_index_t*		fts_index	= NULL;
6161 	bool			new_clustered	= false;
6162 	dberr_t			error;
6163 	ulint			num_fts_index;
6164 	dict_add_v_col_t*	add_v = NULL;
6165 	ha_innobase_inplace_ctx*ctx;
6166 
6167 	DBUG_ENTER("prepare_inplace_alter_table_dict");
6168 
6169 	ctx = static_cast<ha_innobase_inplace_ctx*>
6170 		(ha_alter_info->handler_ctx);
6171 
6172 	DBUG_ASSERT((ctx->add_autoinc != ULINT_UNDEFINED)
6173 		    == (ctx->sequence.max_value() > 0));
6174 	DBUG_ASSERT(!ctx->num_to_drop_index == !ctx->drop_index);
6175 	DBUG_ASSERT(!ctx->num_to_drop_fk == !ctx->drop_fk);
6176 	DBUG_ASSERT(!add_fts_doc_id || add_fts_doc_id_idx);
6177 	DBUG_ASSERT(!add_fts_doc_id_idx
6178 		    || innobase_fulltext_exist(altered_table));
6179 	DBUG_ASSERT(!ctx->defaults);
6180 	DBUG_ASSERT(!ctx->add_index);
6181 	DBUG_ASSERT(!ctx->add_key_numbers);
6182 	DBUG_ASSERT(!ctx->num_to_add_index);
6183 
6184 	user_table = ctx->new_table;
6185 
6186 	switch (ha_alter_info->inplace_supported) {
6187 	default: break;
6188 	case HA_ALTER_INPLACE_INSTANT:
6189 	case HA_ALTER_INPLACE_NOCOPY_LOCK:
6190 	case HA_ALTER_INPLACE_NOCOPY_NO_LOCK:
6191 		/* If we promised ALGORITHM=NOCOPY or ALGORITHM=INSTANT,
6192 		we must retain the original ROW_FORMAT of the table. */
6193 		flags = (user_table->flags & (DICT_TF_MASK_COMPACT
6194 					      | DICT_TF_MASK_ATOMIC_BLOBS))
6195 			| (flags & ~(DICT_TF_MASK_COMPACT
6196 				     | DICT_TF_MASK_ATOMIC_BLOBS));
6197 	}
6198 
6199 	trx_start_if_not_started_xa(ctx->prebuilt->trx, true);
6200 
6201 	if (ha_alter_info->handler_flags
6202 	    & ALTER_DROP_VIRTUAL_COLUMN) {
6203 		if (prepare_inplace_drop_virtual(ha_alter_info, old_table)) {
6204 			DBUG_RETURN(true);
6205 		}
6206 	}
6207 
6208 	if (ha_alter_info->handler_flags
6209 	    & ALTER_ADD_VIRTUAL_COLUMN) {
6210 		if (prepare_inplace_add_virtual(
6211 			    ha_alter_info, altered_table, old_table)) {
6212 			DBUG_RETURN(true);
6213 		}
6214 
6215 		/* Need information for newly added virtual columns
6216 		for create index */
6217 
6218 		if (ha_alter_info->handler_flags
6219 		    & ALTER_ADD_NON_UNIQUE_NON_PRIM_INDEX) {
6220 			for (ulint i = 0; i < ctx->num_to_add_vcol; i++) {
6221 				/* Set mbminmax for newly added column */
6222 				dict_col_t& col = ctx->add_vcol[i].m_col;
6223 				unsigned mbminlen, mbmaxlen;
6224 				dtype_get_mblen(col.mtype, col.prtype,
6225 						&mbminlen, &mbmaxlen);
6226 				col.mbminlen = mbminlen & 7;
6227 				col.mbmaxlen = mbmaxlen & 7;
6228 			}
6229 			add_v = static_cast<dict_add_v_col_t*>(
6230 				mem_heap_alloc(ctx->heap, sizeof *add_v));
6231 			add_v->n_v_col = ctx->num_to_add_vcol;
6232 			add_v->v_col = ctx->add_vcol;
6233 			add_v->v_col_name = ctx->add_vcol_name;
6234 		}
6235 	}
6236 
6237 	/* There should be no order change for virtual columns coming in
6238 	here */
6239 	ut_ad(check_v_col_in_order(old_table, altered_table, ha_alter_info));
6240 
6241 	/* Create table containing all indexes to be built in this
6242 	ALTER TABLE ADD INDEX so that they are in the correct order
6243 	in the table. */
6244 
6245 	ctx->num_to_add_index = ha_alter_info->index_add_count;
6246 
6247 	ut_ad(ctx->prebuilt->trx->mysql_thd != NULL);
6248 	const char*	path = thd_innodb_tmpdir(
6249 		ctx->prebuilt->trx->mysql_thd);
6250 
6251 	index_defs = ctx->create_key_defs(
6252 		ha_alter_info, altered_table,
6253 		num_fts_index,
6254 		fts_doc_id_col, add_fts_doc_id, add_fts_doc_id_idx,
6255 		old_table);
6256 
6257 	new_clustered = (DICT_CLUSTERED & index_defs[0].ind_type) != 0;
6258 
6259 	create_table_info_t info(ctx->prebuilt->trx->mysql_thd, altered_table,
6260 				 ha_alter_info->create_info, NULL, NULL,
6261 				 srv_file_per_table);
6262 	ut_d(bool stats_wait = false);
6263 
6264 	/* The primary index would be rebuilt if a FTS Doc ID
6265 	column is to be added, and the primary index definition
6266 	is just copied from old table and stored in indexdefs[0] */
6267 	DBUG_ASSERT(!add_fts_doc_id || new_clustered);
6268 	DBUG_ASSERT(!!new_clustered ==
6269 		    (innobase_need_rebuild(ha_alter_info, old_table)
6270 		     || add_fts_doc_id));
6271 
6272 	/* Allocate memory for dictionary index definitions */
6273 
6274 	ctx->add_index = static_cast<dict_index_t**>(
6275 		mem_heap_zalloc(ctx->heap, ctx->num_to_add_index
6276 			       * sizeof *ctx->add_index));
6277 	ctx->add_key_numbers = add_key_nums = static_cast<ulint*>(
6278 		mem_heap_alloc(ctx->heap, ctx->num_to_add_index
6279 			       * sizeof *ctx->add_key_numbers));
6280 
6281 	/* Acquire a lock on the table before creating any indexes. */
6282 
6283 	if (ctx->online) {
6284 		error = DB_SUCCESS;
6285 	} else {
6286 		error = row_merge_lock_table(
6287 			ctx->prebuilt->trx, ctx->new_table, LOCK_S);
6288 
6289 		if (error != DB_SUCCESS) {
6290 
6291 			goto error_handling;
6292 		}
6293 	}
6294 
6295 	/* Create a background transaction for the operations on
6296 	the data dictionary tables. */
6297 	ctx->trx = innobase_trx_allocate(ctx->prebuilt->trx->mysql_thd);
6298 
6299 	trx_start_for_ddl(ctx->trx, TRX_DICT_OP_INDEX);
6300 
6301 	/* Latch the InnoDB data dictionary exclusively so that no deadlocks
6302 	or lock waits can happen in it during an index create operation. */
6303 
6304 	row_mysql_lock_data_dictionary(ctx->trx);
6305 	dict_locked = true;
6306 
6307 	/* Wait for background stats processing to stop using the table that
6308 	we are going to alter. We know bg stats will not start using it again
6309 	until we are holding the data dict locked and we are holding it here
6310 	at least until checking ut_ad(user_table->n_ref_count == 1) below.
6311 	XXX what may happen if bg stats opens the table after we
6312 	have unlocked data dictionary below? */
6313 	dict_stats_wait_bg_to_stop_using_table(user_table, ctx->trx);
6314 	ut_d(stats_wait = true);
6315 
6316 	online_retry_drop_indexes_low(ctx->new_table, ctx->trx);
6317 
6318 	ut_d(dict_table_check_for_dup_indexes(
6319 		     ctx->new_table, CHECK_ABORTED_OK));
6320 
6321 	DBUG_EXECUTE_IF("innodb_OOM_prepare_inplace_alter",
6322 			error = DB_OUT_OF_MEMORY;
6323 			goto error_handling;);
6324 
6325 	/* If a new clustered index is defined for the table we need
6326 	to rebuild the table with a temporary name. */
6327 
6328 	if (new_clustered) {
6329 		if (innobase_check_foreigns(
6330 			    ha_alter_info, old_table,
6331 			    user_table, ctx->drop_fk, ctx->num_to_drop_fk)) {
6332 new_clustered_failed:
6333 			DBUG_ASSERT(ctx->trx != ctx->prebuilt->trx);
6334 			ctx->trx->rollback();
6335 
6336 			ut_ad(user_table->get_ref_count() == 1);
6337 
6338 			online_retry_drop_indexes_with_trx(
6339 				user_table, ctx->trx);
6340 
6341 			if (ctx->need_rebuild()) {
6342 				if (ctx->new_table) {
6343 					ut_ad(!ctx->new_table->cached);
6344 					dict_mem_table_free(ctx->new_table);
6345 				}
6346 				ctx->new_table = ctx->old_table;
6347 			}
6348 
6349 			while (ctx->num_to_add_index--) {
6350 				if (dict_index_t*& i = ctx->add_index[
6351 					    ctx->num_to_add_index]) {
6352 					dict_mem_index_free(i);
6353 					i = NULL;
6354 				}
6355 			}
6356 
6357 			goto err_exit;
6358 		}
6359 
6360 		size_t	prefixlen= strlen(mysql_data_home);
6361                 if (mysql_data_home[prefixlen-1] != FN_LIBCHAR)
6362                   prefixlen++;
6363 		size_t	tablen = altered_table->s->path.length - prefixlen;
6364 		const char* part = ctx->old_table->name.part();
6365 		size_t	partlen = part ? strlen(part) : 0;
6366 		char*	new_table_name = static_cast<char*>(
6367 			mem_heap_alloc(ctx->heap, tablen + partlen + 1));
6368 		memcpy(new_table_name,
6369 		       altered_table->s->path.str + prefixlen, tablen);
6370 #ifdef _WIN32
6371                 {
6372                   char *sep= strchr(new_table_name, FN_LIBCHAR);
6373                   sep[0]= '/';
6374                 }
6375 #endif
6376 		memcpy(new_table_name + tablen, part ? part : "", partlen + 1);
6377 		ulint		n_cols = 0;
6378 		ulint		n_v_cols = 0;
6379 		dtuple_t*	defaults;
6380 		ulint		z = 0;
6381 
6382 		for (uint i = 0; i < altered_table->s->fields; i++) {
6383 			const Field*	field = altered_table->field[i];
6384 
6385 			if (!field->stored_in_db()) {
6386 				n_v_cols++;
6387 			} else {
6388 				n_cols++;
6389 			}
6390 		}
6391 
6392 		ut_ad(n_cols + n_v_cols == altered_table->s->fields);
6393 
6394 		if (add_fts_doc_id) {
6395 			n_cols++;
6396 			DBUG_ASSERT(flags2 & DICT_TF2_FTS);
6397 			DBUG_ASSERT(add_fts_doc_id_idx);
6398 			flags2 |= DICT_TF2_FTS_ADD_DOC_ID
6399 				| DICT_TF2_FTS_HAS_DOC_ID
6400 				| DICT_TF2_FTS;
6401 		}
6402 
6403 		DBUG_ASSERT(!add_fts_doc_id_idx || (flags2 & DICT_TF2_FTS));
6404 
6405 		ctx->new_table = dict_mem_table_create(
6406 			new_table_name, NULL, n_cols + n_v_cols, n_v_cols,
6407 			flags, flags2);
6408 
6409 		/* The rebuilt indexed_table will use the renamed
6410 		column names. */
6411 		ctx->col_names = NULL;
6412 
6413 		if (DICT_TF_HAS_DATA_DIR(flags)) {
6414 			ctx->new_table->data_dir_path =
6415 				mem_heap_strdup(ctx->new_table->heap,
6416 				user_table->data_dir_path);
6417 		}
6418 
6419 		for (uint i = 0; i < altered_table->s->fields; i++) {
6420 			const Field*	field = altered_table->field[i];
6421 			unsigned is_unsigned;
6422 			auto col_type = get_innobase_type_from_mysql_type(
6423 				&is_unsigned, field);
6424 			unsigned field_type = field->type() | is_unsigned;
6425 			const bool is_virtual = !field->stored_in_db();
6426 
6427 			/* we assume in dtype_form_prtype() that this
6428 			fits in two bytes */
6429 			ut_a(field_type <= MAX_CHAR_COLL_NUM);
6430 
6431 			if (!field->real_maybe_null()) {
6432 				field_type |= DATA_NOT_NULL;
6433 			}
6434 
6435 			if (field->binary()) {
6436 				field_type |= DATA_BINARY_TYPE;
6437 			}
6438 
6439 			if (altered_table->versioned()) {
6440 				if (i == altered_table->s->vers.start_fieldno) {
6441 					field_type |= DATA_VERS_START;
6442 				} else if (i ==
6443 					   altered_table->s->vers.end_fieldno) {
6444 					field_type |= DATA_VERS_END;
6445 				} else if (!(field->flags
6446 					     & VERS_UPDATE_UNVERSIONED_FLAG)) {
6447 					field_type |= DATA_VERSIONED;
6448 				}
6449 			}
6450 
6451 			unsigned charset_no;
6452 
6453 			if (dtype_is_string_type(col_type)) {
6454 				charset_no = field->charset()->number;
6455 
6456 				if (charset_no > MAX_CHAR_COLL_NUM) {
6457 					my_error(ER_WRONG_KEY_COLUMN, MYF(0), "InnoDB",
6458 						 field->field_name.str);
6459 					goto new_clustered_failed;
6460 				}
6461 			} else {
6462 				charset_no = 0;
6463 			}
6464 
6465 			auto col_len = field->pack_length();
6466 
6467 			/* The MySQL pack length contains 1 or 2 bytes
6468 			length field for a true VARCHAR. Let us
6469 			subtract that, so that the InnoDB column
6470 			length in the InnoDB data dictionary is the
6471 			real maximum byte length of the actual data. */
6472 
6473 			if (field->type() == MYSQL_TYPE_VARCHAR) {
6474 				uint32	length_bytes
6475 					= static_cast<const Field_varstring*>(
6476 						field)->length_bytes;
6477 
6478 				col_len -= length_bytes;
6479 
6480 				if (length_bytes == 2) {
6481 					field_type |= DATA_LONG_TRUE_VARCHAR;
6482 				}
6483 
6484 			}
6485 
6486 			if (dict_col_name_is_reserved(field->field_name.str)) {
6487 wrong_column_name:
6488 				dict_mem_table_free(ctx->new_table);
6489 				ctx->new_table = ctx->old_table;
6490 				my_error(ER_WRONG_COLUMN_NAME, MYF(0),
6491 					 field->field_name.str);
6492 				goto new_clustered_failed;
6493 			}
6494 
6495 			/** Note the FTS_DOC_ID name is case sensitive due
6496 			 to internal query parser.
6497 			 FTS_DOC_ID column must be of BIGINT NOT NULL type
6498 			 and it should be in all capitalized characters */
6499 			if (!innobase_strcasecmp(field->field_name.str,
6500 						 FTS_DOC_ID_COL_NAME)) {
6501 				if (col_type != DATA_INT
6502 				    || field->real_maybe_null()
6503 				    || col_len != sizeof(doc_id_t)
6504 				    || strcmp(field->field_name.str,
6505 					      FTS_DOC_ID_COL_NAME)) {
6506 					goto wrong_column_name;
6507 				}
6508 			}
6509 
6510 			if (is_virtual) {
6511 				dict_mem_table_add_v_col(
6512 					ctx->new_table, ctx->heap,
6513 					field->field_name.str,
6514 					col_type,
6515 					dtype_form_prtype(
6516 						field_type, charset_no)
6517 					| DATA_VIRTUAL,
6518 					col_len, i, 0);
6519 			} else {
6520 				dict_mem_table_add_col(
6521 					ctx->new_table, ctx->heap,
6522 					field->field_name.str,
6523 					col_type,
6524 					dtype_form_prtype(
6525 						field_type, charset_no),
6526 					col_len);
6527 			}
6528 		}
6529 
6530 		if (n_v_cols) {
6531 			for (uint i = 0; i < altered_table->s->fields; i++) {
6532 				dict_v_col_t*	v_col;
6533 				const Field*	field = altered_table->field[i];
6534 
6535 				if (!!field->stored_in_db()) {
6536 					continue;
6537 				}
6538 				v_col = dict_table_get_nth_v_col(
6539 					ctx->new_table, z);
6540 				z++;
6541 				innodb_base_col_setup(
6542 					ctx->new_table, field, v_col);
6543 			}
6544 		}
6545 
6546 		if (add_fts_doc_id) {
6547 			fts_add_doc_id_column(ctx->new_table, ctx->heap);
6548 			ctx->new_table->fts->doc_col = fts_doc_id_col;
6549 			ut_ad(fts_doc_id_col
6550 			      == altered_table->s->fields - n_v_cols);
6551 		} else if (ctx->new_table->fts) {
6552 			ctx->new_table->fts->doc_col = fts_doc_id_col;
6553 		}
6554 
6555 		dict_table_add_system_columns(ctx->new_table, ctx->heap);
6556 
6557 		if (ha_alter_info->handler_flags & INNOBASE_DEFAULTS) {
6558 			defaults = dtuple_create_with_vcol(
6559 				ctx->heap,
6560 				dict_table_get_n_cols(ctx->new_table),
6561 				dict_table_get_n_v_cols(ctx->new_table));
6562 
6563 			dict_table_copy_types(defaults, ctx->new_table);
6564 		} else {
6565 			defaults = NULL;
6566 		}
6567 
6568 		ctx->col_map = innobase_build_col_map(
6569 			ha_alter_info, altered_table, old_table,
6570 			ctx->new_table, user_table, defaults, ctx->heap);
6571 		ctx->defaults = defaults;
6572 	} else {
6573 		DBUG_ASSERT(!innobase_need_rebuild(ha_alter_info, old_table));
6574 		DBUG_ASSERT(old_table->s->primary_key
6575 			    == altered_table->s->primary_key);
6576 
6577 		for (dict_index_t* index
6578 			     = dict_table_get_first_index(user_table);
6579 		     index != NULL;
6580 		     index = dict_table_get_next_index(index)) {
6581 			if (!index->to_be_dropped && index->is_corrupted()) {
6582 				my_error(ER_CHECK_NO_SUCH_TABLE, MYF(0));
6583 				goto error_handled;
6584 			}
6585 		}
6586 
6587 		for (dict_index_t* index
6588 			     = dict_table_get_first_index(user_table);
6589 		     index != NULL;
6590 		     index = dict_table_get_next_index(index)) {
6591 			if (!index->to_be_dropped && index->is_corrupted()) {
6592 				my_error(ER_CHECK_NO_SUCH_TABLE, MYF(0));
6593 				goto error_handled;
6594 			}
6595 		}
6596 
6597 		if (!ctx->new_table->fts
6598 		    && innobase_fulltext_exist(altered_table)) {
6599 			ctx->new_table->fts = fts_create(
6600 				ctx->new_table);
6601 			ctx->new_table->fts->doc_col = fts_doc_id_col;
6602 		}
6603 
6604 		/* Check if we need to update mtypes of legacy GIS columns.
6605 		This check is only needed when we don't have to rebuild
6606 		the table, since rebuild would update all mtypes for GIS
6607 		columns */
6608 		error = innobase_check_gis_columns(
6609 			ha_alter_info, ctx->new_table, ctx->trx);
6610 		if (error != DB_SUCCESS) {
6611 			ut_ad(error == DB_ERROR);
6612 			error = DB_UNSUPPORTED;
6613 			goto error_handling;
6614 		}
6615 	}
6616 
6617 	ut_ad(new_clustered == ctx->need_rebuild());
6618 
6619 	/* Create the index metadata. */
6620 	for (ulint a = 0; a < ctx->num_to_add_index; a++) {
6621 		if (index_defs[a].ind_type & DICT_VIRTUAL
6622 		    && ctx->num_to_drop_vcol > 0 && !new_clustered) {
6623 			innodb_v_adjust_idx_col(ha_alter_info, old_table,
6624 						ctx->num_to_drop_vcol,
6625 						&index_defs[a]);
6626 		}
6627 
6628 		ctx->add_index[a] = row_merge_create_index(
6629 			ctx->new_table, &index_defs[a], add_v);
6630 
6631 		add_key_nums[a] = index_defs[a].key_number;
6632 
6633 		DBUG_ASSERT(ctx->add_index[a]->is_committed()
6634 			    == !!new_clustered);
6635 	}
6636 
6637 	DBUG_ASSERT(!ctx->need_rebuild()
6638 		    || !ctx->new_table->persistent_autoinc);
6639 
6640 	if (ctx->need_rebuild() && instant_alter_column_possible(
6641 		    *user_table, ha_alter_info, old_table, altered_table,
6642 		    ha_innobase::is_innodb_strict_mode(ctx->trx->mysql_thd))) {
6643 		for (uint a = 0; a < ctx->num_to_add_index; a++) {
6644 			ctx->add_index[a]->table = ctx->new_table;
6645 			error = dict_index_add_to_cache(
6646 				ctx->add_index[a], FIL_NULL, add_v);
6647 			ut_a(error == DB_SUCCESS);
6648 		}
6649 
6650 		DBUG_ASSERT(ha_alter_info->key_count
6651 			    /* hidden GEN_CLUST_INDEX in InnoDB */
6652 			    + dict_index_is_auto_gen_clust(
6653 				    dict_table_get_first_index(ctx->new_table))
6654 			    /* hidden FTS_DOC_ID_INDEX in InnoDB */
6655 			    + (ctx->old_table->fts_doc_id_index
6656 			       && innobase_fts_check_doc_id_index_in_def(
6657 				       altered_table->s->keys,
6658 				       altered_table->key_info)
6659 			       != FTS_EXIST_DOC_ID_INDEX)
6660 			    == ctx->num_to_add_index);
6661 
6662 		ctx->num_to_add_index = 0;
6663 		ctx->add_index = NULL;
6664 
6665 		uint i = 0; // index of stored columns ctx->new_table->cols[]
6666 		Field **af = altered_table->field;
6667 
6668 		for (const Create_field& new_field :
6669 		     ha_alter_info->alter_info->create_list) {
6670 			DBUG_ASSERT(!new_field.field
6671 				    || std::find(old_table->field,
6672 						 old_table->field
6673 						 + old_table->s->fields,
6674 						 new_field.field) !=
6675 				    old_table->field + old_table->s->fields);
6676 			DBUG_ASSERT(new_field.field
6677 				    || !strcmp(new_field.field_name.str,
6678 					       (*af)->field_name.str));
6679 
6680 			if (!(*af)->stored_in_db()) {
6681 				af++;
6682 				continue;
6683 			}
6684 
6685 			dict_col_t* col = dict_table_get_nth_col(
6686 				ctx->new_table, i);
6687 			DBUG_ASSERT(!strcmp((*af)->field_name.str,
6688 				    dict_table_get_col_name(ctx->new_table,
6689 							    i)));
6690 			DBUG_ASSERT(!col->is_added());
6691 
6692 			if (new_field.field) {
6693 				/* This is a pre-existing column,
6694 				possibly at a different position. */
6695 			} else if ((*af)->is_real_null()) {
6696 				/* DEFAULT NULL */
6697 				col->def_val.len = UNIV_SQL_NULL;
6698 			} else {
6699 				switch ((*af)->type()) {
6700 				case MYSQL_TYPE_VARCHAR:
6701 					col->def_val.len = reinterpret_cast
6702 						<const Field_varstring*>
6703 						((*af))->get_length();
6704 					col->def_val.data = reinterpret_cast
6705 						<const Field_varstring*>
6706 						((*af))->get_data();
6707 					break;
6708 				case MYSQL_TYPE_GEOMETRY:
6709 				case MYSQL_TYPE_TINY_BLOB:
6710 				case MYSQL_TYPE_MEDIUM_BLOB:
6711 				case MYSQL_TYPE_BLOB:
6712 				case MYSQL_TYPE_LONG_BLOB:
6713 					col->def_val.len = reinterpret_cast
6714 						<const Field_blob*>
6715 						((*af))->get_length();
6716 					col->def_val.data = reinterpret_cast
6717 						<const Field_blob*>
6718 						((*af))->get_ptr();
6719 					break;
6720 				default:
6721 					dfield_t d;
6722 					dict_col_copy_type(col, &d.type);
6723 					ulint len = (*af)->pack_length();
6724 					DBUG_ASSERT(len <= 8
6725 						    || d.type.mtype
6726 						    != DATA_INT);
6727 					row_mysql_store_col_in_innobase_format(
6728 						&d,
6729 						d.type.mtype == DATA_INT
6730 						? static_cast<byte*>(
6731 							mem_heap_alloc(
6732 								ctx->heap,
6733 								len))
6734 						: NULL,
6735 						true, (*af)->ptr, len,
6736 						dict_table_is_comp(
6737 							user_table));
6738 					col->def_val.len = d.len;
6739 					col->def_val.data = d.data;
6740 				}
6741 			}
6742 
6743 			i++;
6744 			af++;
6745 		}
6746 
6747 		DBUG_ASSERT(af == altered_table->field
6748 			    + altered_table->s->fields);
6749 		/* There might exist a hidden FTS_DOC_ID column for
6750 		FULLTEXT INDEX. If it exists, the columns should have
6751 		been implicitly added by ADD FULLTEXT INDEX together
6752 		with instant ADD COLUMN. (If a hidden FTS_DOC_ID pre-existed,
6753 		then the ctx->col_map[] check should have prevented
6754 		adding visible user columns after that.) */
6755 		DBUG_ASSERT(DATA_N_SYS_COLS + i == ctx->new_table->n_cols
6756 			    || (1 + DATA_N_SYS_COLS + i
6757 				== ctx->new_table->n_cols
6758 				&& !strcmp(dict_table_get_col_name(
6759 						   ctx->new_table, i),
6760 				   FTS_DOC_ID_COL_NAME)));
6761 
6762 		if (altered_table->found_next_number_field) {
6763 			ctx->new_table->persistent_autoinc
6764 				= ctx->old_table->persistent_autoinc;
6765 		}
6766 
6767 		ctx->prepare_instant();
6768 	}
6769 
6770 	if (ctx->need_rebuild()) {
6771 		DBUG_ASSERT(ctx->need_rebuild());
6772 		DBUG_ASSERT(!ctx->is_instant());
6773 		DBUG_ASSERT(num_fts_index <= 1);
6774 		DBUG_ASSERT(!ctx->online || num_fts_index == 0);
6775 		DBUG_ASSERT(!ctx->online
6776 			    || !ha_alter_info->mdl_exclusive_after_prepare
6777 			    || ctx->add_autoinc == ULINT_UNDEFINED);
6778 		DBUG_ASSERT(!ctx->online
6779 			    || !innobase_need_rebuild(ha_alter_info, old_table)
6780 			    || !innobase_fulltext_exist(altered_table));
6781 
6782 		uint32_t		key_id	= FIL_DEFAULT_ENCRYPTION_KEY;
6783 		fil_encryption_t	mode	= FIL_ENCRYPTION_DEFAULT;
6784 
6785 		if (fil_space_t* s = user_table->space) {
6786 			if (const fil_space_crypt_t* c = s->crypt_data) {
6787 				key_id = c->key_id;
6788 				mode = c->encryption;
6789 			}
6790 		}
6791 
6792 		if (ha_alter_info->handler_flags & ALTER_OPTIONS) {
6793 			const ha_table_option_struct& alt_opt=
6794 				*ha_alter_info->create_info->option_struct;
6795 			const ha_table_option_struct& opt=
6796 				*old_table->s->option_struct;
6797 			if (alt_opt.encryption != opt.encryption
6798 			    || alt_opt.encryption_key_id
6799 			    != opt.encryption_key_id) {
6800 				key_id = uint32_t(alt_opt.encryption_key_id);
6801 				mode = fil_encryption_t(alt_opt.encryption);
6802 			}
6803 		}
6804 
6805 		if (dict_table_get_low(ctx->new_table->name.m_name)) {
6806 			my_error(ER_TABLE_EXISTS_ERROR, MYF(0),
6807 				 ctx->new_table->name.m_name);
6808 			goto new_clustered_failed;
6809 		}
6810 
6811 		/* Create the table. */
6812 		trx_set_dict_operation(ctx->trx, TRX_DICT_OP_TABLE);
6813 
6814 		error = row_create_table_for_mysql(
6815 			ctx->new_table, ctx->trx, mode, key_id);
6816 
6817 		switch (error) {
6818 			dict_table_t*	temp_table;
6819 		case DB_SUCCESS:
6820 			/* We need to bump up the table ref count and
6821 			before we can use it we need to open the
6822 			table. The new_table must be in the data
6823 			dictionary cache, because we are still holding
6824 			the dict_sys.mutex. */
6825 			ut_ad(mutex_own(&dict_sys.mutex));
6826 			temp_table = dict_table_open_on_name(
6827 				ctx->new_table->name.m_name, TRUE, FALSE,
6828 				DICT_ERR_IGNORE_NONE);
6829 			ut_a(ctx->new_table == temp_table);
6830 			/* n_ref_count must be 1, because purge cannot
6831 			be executing on this very table as we are
6832 			holding dict_sys.latch X-latch. */
6833 			DBUG_ASSERT(ctx->new_table->get_ref_count() == 1);
6834 			DBUG_ASSERT(ctx->new_table->id != 0);
6835 			DBUG_ASSERT(ctx->new_table->id == ctx->trx->table_id);
6836 			break;
6837 		case DB_TABLESPACE_EXISTS:
6838 			my_error(ER_TABLESPACE_EXISTS, MYF(0),
6839 				 altered_table->s->table_name.str);
6840 			goto new_table_failed;
6841 		case DB_DUPLICATE_KEY:
6842 			my_error(HA_ERR_TABLE_EXIST, MYF(0),
6843 				 altered_table->s->table_name.str);
6844 			goto new_table_failed;
6845 		case DB_UNSUPPORTED:
6846 			my_error(ER_UNSUPPORTED_EXTENSION, MYF(0),
6847 				 altered_table->s->table_name.str);
6848 			goto new_table_failed;
6849 		default:
6850 			my_error_innodb(error, table_name, flags);
6851 new_table_failed:
6852 			DBUG_ASSERT(ctx->trx != ctx->prebuilt->trx);
6853 			ctx->new_table = NULL;
6854 			goto new_clustered_failed;
6855 		}
6856 
6857 		for (ulint a = 0; a < ctx->num_to_add_index; a++) {
6858 			dict_index_t* index = ctx->add_index[a];
6859 			const ulint n_v_col = index->get_new_n_vcol();
6860 			index = create_index_dict(ctx->trx, index, add_v);
6861 			error = ctx->trx->error_state;
6862 			if (error != DB_SUCCESS) {
6863 				if (index) {
6864 					dict_mem_index_free(index);
6865 				}
6866 error_handling_drop_uncached_1:
6867 				while (++a < ctx->num_to_add_index) {
6868 					dict_mem_index_free(ctx->add_index[a]);
6869 				}
6870 				goto error_handling;
6871 			} else {
6872 				DBUG_ASSERT(index != ctx->add_index[a]);
6873 			}
6874 
6875 			ctx->add_index[a] = index;
6876 			/* For ALTER TABLE...FORCE or OPTIMIZE TABLE,
6877 			we may only issue warnings, because there will
6878 			be no schema change from the user perspective. */
6879 			if (!info.row_size_is_acceptable(
6880 				    *index,
6881 				    !!(ha_alter_info->handler_flags
6882 				       & ~(INNOBASE_INPLACE_IGNORE
6883 					   | INNOBASE_ALTER_NOVALIDATE
6884 					   | ALTER_RECREATE_TABLE)))) {
6885 				error = DB_TOO_BIG_RECORD;
6886 				goto error_handling_drop_uncached_1;
6887 			}
6888 			index->parser = index_defs[a].parser;
6889 			if (n_v_col) {
6890 				index->assign_new_v_col(n_v_col);
6891 			}
6892 			/* Note the id of the transaction that created this
6893 			index, we use it to restrict readers from accessing
6894 			this index, to ensure read consistency. */
6895 			ut_ad(index->trx_id == ctx->trx->id);
6896 
6897 			if (index->type & DICT_FTS) {
6898 				DBUG_ASSERT(num_fts_index == 1);
6899 				DBUG_ASSERT(!fts_index);
6900 				DBUG_ASSERT(index->type == DICT_FTS);
6901 				fts_index = ctx->add_index[a];
6902 			}
6903 		}
6904 
6905 		dict_index_t*	clust_index = dict_table_get_first_index(
6906 			user_table);
6907 		dict_index_t*	new_clust_index = dict_table_get_first_index(
6908 			ctx->new_table);
6909 		ut_ad(!new_clust_index->is_instant());
6910 		/* row_merge_build_index() depends on the correct value */
6911 		ut_ad(new_clust_index->n_core_null_bytes
6912 		      == UT_BITS_IN_BYTES(new_clust_index->n_nullable));
6913 
6914 		if (const Field* ai = altered_table->found_next_number_field) {
6915 			const unsigned	col_no = innodb_col_no(ai);
6916 
6917 			ctx->new_table->persistent_autoinc =
6918 				(dict_table_get_nth_col_pos(
6919 					ctx->new_table, col_no, NULL) + 1)
6920 				& dict_index_t::MAX_N_FIELDS;
6921 
6922 			/* Initialize the AUTO_INCREMENT sequence
6923 			to the rebuilt table from the old one. */
6924 			if (!old_table->found_next_number_field
6925 			    || !user_table->space) {
6926 			} else if (ib_uint64_t autoinc
6927 				   = btr_read_autoinc(clust_index)) {
6928 				btr_write_autoinc(new_clust_index, autoinc);
6929 			}
6930 		}
6931 
6932 		ctx->skip_pk_sort = innobase_pk_order_preserved(
6933 			ctx->col_map, clust_index, new_clust_index);
6934 
6935 		DBUG_EXECUTE_IF("innodb_alter_table_pk_assert_no_sort",
6936 			DBUG_ASSERT(ctx->skip_pk_sort););
6937 
6938 		if (ctx->online) {
6939 			/* Allocate a log for online table rebuild. */
6940 			rw_lock_x_lock(&clust_index->lock);
6941 			bool ok = row_log_allocate(
6942 				ctx->prebuilt->trx,
6943 				clust_index, ctx->new_table,
6944 				!(ha_alter_info->handler_flags
6945 				  & ALTER_ADD_PK_INDEX),
6946 				ctx->defaults, ctx->col_map, path,
6947 				old_table,
6948 				ctx->allow_not_null);
6949 			rw_lock_x_unlock(&clust_index->lock);
6950 
6951 			if (!ok) {
6952 				error = DB_OUT_OF_MEMORY;
6953 				goto error_handling;
6954 			}
6955 		}
6956 	} else if (ctx->num_to_add_index) {
6957 		ut_ad(!ctx->is_instant());
6958 		ctx->trx->table_id = user_table->id;
6959 
6960 		for (ulint a = 0; a < ctx->num_to_add_index; a++) {
6961 			dict_index_t* index = ctx->add_index[a];
6962 			const ulint n_v_col = index->get_new_n_vcol();
6963 			DBUG_EXECUTE_IF(
6964 				"create_index_metadata_fail",
6965 				if (a + 1 == ctx->num_to_add_index) {
6966 					ctx->trx->error_state =
6967 						DB_OUT_OF_FILE_SPACE;
6968 					goto index_created;
6969 				});
6970 			index = create_index_dict(ctx->trx, index, add_v);
6971 #ifndef DBUG_OFF
6972 index_created:
6973 #endif
6974 			error = ctx->trx->error_state;
6975 			if (error != DB_SUCCESS) {
6976 				if (index) {
6977 					dict_mem_index_free(index);
6978 				}
6979 error_handling_drop_uncached:
6980 				while (++a < ctx->num_to_add_index) {
6981 					dict_mem_index_free(ctx->add_index[a]);
6982 				}
6983 				goto error_handling;
6984 			} else {
6985 				DBUG_ASSERT(index != ctx->add_index[a]);
6986 			}
6987 			ctx->add_index[a]= index;
6988 			if (!info.row_size_is_acceptable(*index, true)) {
6989 				error = DB_TOO_BIG_RECORD;
6990 				goto error_handling_drop_uncached;
6991 			}
6992 
6993 			index->parser = index_defs[a].parser;
6994 			if (n_v_col) {
6995 				index->assign_new_v_col(n_v_col);
6996 			}
6997 			/* Note the id of the transaction that created this
6998 			index, we use it to restrict readers from accessing
6999 			this index, to ensure read consistency. */
7000 			ut_ad(index->trx_id == ctx->trx->id);
7001 
7002 			/* If ADD INDEX with LOCK=NONE has been
7003 			requested, allocate a modification log. */
7004 			if (index->type & DICT_FTS) {
7005 				DBUG_ASSERT(num_fts_index == 1);
7006 				DBUG_ASSERT(!fts_index);
7007 				DBUG_ASSERT(index->type == DICT_FTS);
7008 				fts_index = ctx->add_index[a];
7009 				/* Fulltext indexes are not covered
7010 				by a modification log. */
7011 			} else if (!ctx->online
7012 				   || !user_table->is_readable()
7013 				   || !user_table->space) {
7014 				/* No need to allocate a modification log. */
7015 				DBUG_ASSERT(!index->online_log);
7016 			} else {
7017 				rw_lock_x_lock(&ctx->add_index[a]->lock);
7018 
7019 				bool ok = row_log_allocate(
7020 					ctx->prebuilt->trx,
7021 					index,
7022 					NULL, true, NULL, NULL,
7023 					path, old_table,
7024 					ctx->allow_not_null);
7025 
7026 				rw_lock_x_unlock(&index->lock);
7027 
7028 				DBUG_EXECUTE_IF(
7029 					"innodb_OOM_prepare_add_index",
7030 					if (ok && a == 1) {
7031 						row_log_free(
7032 							index->online_log);
7033 						index->online_log = NULL;
7034 						ok = false;
7035 					});
7036 
7037 				if (!ok) {
7038 					error = DB_OUT_OF_MEMORY;
7039 					goto error_handling_drop_uncached;
7040 				}
7041 			}
7042 		}
7043 	} else if (ctx->is_instant()
7044 		   && !info.row_size_is_acceptable(*user_table, true)) {
7045 		error = DB_TOO_BIG_RECORD;
7046 		goto error_handling;
7047 	}
7048 
7049 	if (ctx->online && ctx->num_to_add_index) {
7050 		/* Assign a consistent read view for
7051 		row_merge_read_clustered_index(). */
7052 		ctx->prebuilt->trx->read_view.open(ctx->prebuilt->trx);
7053 	}
7054 
7055 	if (fts_index) {
7056 		/* Ensure that the dictionary operation mode will
7057 		not change while creating the auxiliary tables. */
7058 		trx_dict_op_t	op = trx_get_dict_operation(ctx->trx);
7059 
7060 #ifdef UNIV_DEBUG
7061 		switch (op) {
7062 		case TRX_DICT_OP_NONE:
7063 			break;
7064 		case TRX_DICT_OP_TABLE:
7065 		case TRX_DICT_OP_INDEX:
7066 			goto op_ok;
7067 		}
7068 		ut_error;
7069 op_ok:
7070 #endif /* UNIV_DEBUG */
7071 		ut_ad(ctx->trx->dict_operation_lock_mode == RW_X_LATCH);
7072 		ut_d(dict_sys.assert_locked());
7073 
7074 		DICT_TF2_FLAG_SET(ctx->new_table, DICT_TF2_FTS);
7075 		if (ctx->need_rebuild()) {
7076 			/* For !ctx->need_rebuild(), this will be set at
7077 			commit_cache_norebuild(). */
7078 			ctx->new_table->fts_doc_id_index
7079 				= dict_table_get_index_on_name(
7080 					ctx->new_table, FTS_DOC_ID_INDEX_NAME);
7081 			DBUG_ASSERT(ctx->new_table->fts_doc_id_index != NULL);
7082 		}
7083 
7084 		error = fts_create_index_tables(ctx->trx, fts_index,
7085 						ctx->new_table->id);
7086 
7087 		DBUG_EXECUTE_IF("innodb_test_fail_after_fts_index_table",
7088 				error = DB_LOCK_WAIT_TIMEOUT;
7089 				goto error_handling;);
7090 
7091 		if (error != DB_SUCCESS) {
7092 			goto error_handling;
7093 		}
7094 
7095 		ctx->trx->commit();
7096 		trx_start_for_ddl(ctx->trx, op);
7097 
7098 		if (!ctx->new_table->fts
7099 		    || ib_vector_size(ctx->new_table->fts->indexes) == 0) {
7100 			error = fts_create_common_tables(
7101 				ctx->trx, ctx->new_table, true);
7102 
7103 			DBUG_EXECUTE_IF(
7104 				"innodb_test_fail_after_fts_common_table",
7105 				error = DB_LOCK_WAIT_TIMEOUT;);
7106 
7107 			if (error != DB_SUCCESS) {
7108 				goto error_handling;
7109 			}
7110 
7111 			ctx->new_table->fts->dict_locked = true;
7112 
7113 			error = innobase_fts_load_stopword(
7114 				ctx->new_table, ctx->trx,
7115 				ctx->prebuilt->trx->mysql_thd)
7116 				? DB_SUCCESS : DB_ERROR;
7117 			ctx->new_table->fts->dict_locked = false;
7118 
7119 			if (error != DB_SUCCESS) {
7120 				goto error_handling;
7121 			}
7122 		}
7123 
7124 		ut_ad(trx_get_dict_operation(ctx->trx) == op);
7125 	}
7126 
7127 	DBUG_ASSERT(error == DB_SUCCESS);
7128 
7129 	/* Commit the data dictionary transaction in order to release
7130 	the table locks on the system tables.  This means that if
7131 	MySQL crashes while creating a new primary key inside
7132 	row_merge_build_indexes(), ctx->new_table will not be dropped
7133 	by trx_rollback_active().  It will have to be recovered or
7134 	dropped by the database administrator. */
7135 	trx_commit_for_mysql(ctx->trx);
7136 
7137 	row_mysql_unlock_data_dictionary(ctx->trx);
7138 	dict_locked = false;
7139 
7140 	ut_ad(!ctx->trx->lock.n_active_thrs);
7141 
7142 	if (ctx->old_table->fts) {
7143 		fts_sync_during_ddl(ctx->old_table);
7144 	}
7145 
7146 error_handling:
7147 	/* After an error, remove all those index definitions from the
7148 	dictionary which were defined. */
7149 
7150 	switch (error) {
7151 	case DB_SUCCESS:
7152 		ut_a(!dict_locked);
7153 
7154 		ut_d(mutex_enter(&dict_sys.mutex));
7155 		ut_d(dict_table_check_for_dup_indexes(
7156 			     user_table, CHECK_PARTIAL_OK));
7157 		ut_d(mutex_exit(&dict_sys.mutex));
7158 		DBUG_RETURN(false);
7159 	case DB_TABLESPACE_EXISTS:
7160 		my_error(ER_TABLESPACE_EXISTS, MYF(0), "(unknown)");
7161 		break;
7162 	case DB_DUPLICATE_KEY:
7163 		my_error(ER_DUP_KEY, MYF(0), "SYS_INDEXES");
7164 		break;
7165 	case DB_UNSUPPORTED:
7166 		my_error(ER_TABLE_CANT_HANDLE_SPKEYS, MYF(0), "SYS_COLUMNS");
7167 		break;
7168 	default:
7169 		my_error_innodb(error, table_name, user_table->flags);
7170 	}
7171 
7172 error_handled:
7173 
7174 	ctx->prebuilt->trx->error_info = NULL;
7175 
7176 	if (!ctx->trx) {
7177 		goto err_exit;
7178 	}
7179 
7180 	ctx->trx->error_state = DB_SUCCESS;
7181 
7182 	if (!dict_locked) {
7183 		row_mysql_lock_data_dictionary(ctx->trx);
7184 	}
7185 
7186 	if (new_clustered) {
7187 		if (ctx->need_rebuild()) {
7188 
7189 			if (DICT_TF2_FLAG_IS_SET(
7190 				    ctx->new_table, DICT_TF2_FTS)) {
7191 				innobase_drop_fts_index_table(
7192 					ctx->new_table, ctx->trx);
7193 			}
7194 
7195 			dict_table_close_and_drop(ctx->trx, ctx->new_table);
7196 
7197 			/* Free the log for online table rebuild, if
7198 			one was allocated. */
7199 
7200 			dict_index_t* clust_index = dict_table_get_first_index(
7201 				user_table);
7202 
7203 			rw_lock_x_lock(&clust_index->lock);
7204 
7205 			if (clust_index->online_log) {
7206 				ut_ad(ctx->online);
7207 				row_log_abort_sec(clust_index);
7208 				clust_index->online_status
7209 					= ONLINE_INDEX_COMPLETE;
7210 			}
7211 
7212 			rw_lock_x_unlock(&clust_index->lock);
7213 		}
7214 
7215 		trx_commit_for_mysql(ctx->trx);
7216 		/* n_ref_count must be 1, because purge cannot
7217 		be executing on this very table as we are
7218 		holding dict_sys.latch X-latch. */
7219 		ut_ad(!stats_wait || ctx->online
7220 		      || user_table->get_ref_count() == 1);
7221 
7222 		online_retry_drop_indexes_with_trx(user_table, ctx->trx);
7223 	} else {
7224 		ut_ad(!ctx->need_rebuild());
7225 		row_merge_drop_indexes(ctx->trx, user_table, true);
7226 		trx_commit_for_mysql(ctx->trx);
7227 	}
7228 
7229 	ut_d(dict_table_check_for_dup_indexes(user_table, CHECK_ALL_COMPLETE));
7230 	ut_ad(!user_table->drop_aborted);
7231 
7232 err_exit:
7233 	/* Clear the to_be_dropped flag in the data dictionary cache. */
7234 	for (ulint i = 0; i < ctx->num_to_drop_index; i++) {
7235 		DBUG_ASSERT(ctx->drop_index[i]->is_committed());
7236 		DBUG_ASSERT(ctx->drop_index[i]->to_be_dropped);
7237 		ctx->drop_index[i]->to_be_dropped = 0;
7238 	}
7239 
7240 	if (ctx->trx) {
7241 		row_mysql_unlock_data_dictionary(ctx->trx);
7242 
7243 		ctx->trx->free();
7244 	}
7245 	trx_commit_for_mysql(ctx->prebuilt->trx);
7246 
7247 	for (uint i = 0; i < ctx->num_to_add_fk; i++) {
7248 		if (ctx->add_fk[i]) {
7249 			dict_foreign_free(ctx->add_fk[i]);
7250 		}
7251 	}
7252 
7253 	delete ctx;
7254 	ha_alter_info->handler_ctx = NULL;
7255 
7256 	DBUG_RETURN(true);
7257 }
7258 
7259 /* Check whether an index is needed for the foreign key constraint.
7260 If so, if it is dropped, is there an equivalent index can play its role.
7261 @return true if the index is needed and can't be dropped */
7262 static MY_ATTRIBUTE((nonnull(1,2,3,5), warn_unused_result))
7263 bool
innobase_check_foreign_key_index(Alter_inplace_info * ha_alter_info,dict_index_t * index,dict_table_t * indexed_table,const char ** col_names,trx_t * trx,dict_foreign_t ** drop_fk,ulint n_drop_fk)7264 innobase_check_foreign_key_index(
7265 /*=============================*/
7266 	Alter_inplace_info*	ha_alter_info,	/*!< in: Structure describing
7267 						changes to be done by ALTER
7268 						TABLE */
7269 	dict_index_t*		index,		/*!< in: index to check */
7270 	dict_table_t*		indexed_table,	/*!< in: table that owns the
7271 						foreign keys */
7272 	const char**		col_names,	/*!< in: column names, or NULL
7273 						for indexed_table->col_names */
7274 	trx_t*			trx,		/*!< in/out: transaction */
7275 	dict_foreign_t**	drop_fk,	/*!< in: Foreign key constraints
7276 						to drop */
7277 	ulint			n_drop_fk)	/*!< in: Number of foreign keys
7278 						to drop */
7279 {
7280 	const dict_foreign_set*	fks = &indexed_table->referenced_set;
7281 
7282 	/* Check for all FK references from other tables to the index. */
7283 	for (dict_foreign_set::const_iterator it = fks->begin();
7284 	     it != fks->end(); ++it) {
7285 
7286 		dict_foreign_t*	foreign = *it;
7287 		if (foreign->referenced_index != index) {
7288 			continue;
7289 		}
7290 		ut_ad(indexed_table == foreign->referenced_table);
7291 
7292 		if (NULL == dict_foreign_find_index(
7293 			    indexed_table, col_names,
7294 			    foreign->referenced_col_names,
7295 			    foreign->n_fields, index,
7296 			    /*check_charsets=*/TRUE,
7297 			    /*check_null=*/FALSE,
7298 			    NULL, NULL, NULL)
7299 		    && NULL == innobase_find_equiv_index(
7300 			    foreign->referenced_col_names,
7301 			    foreign->n_fields,
7302 			    ha_alter_info->key_info_buffer,
7303 			    span<uint>(ha_alter_info->index_add_buffer,
7304 				       ha_alter_info->index_add_count))) {
7305 
7306 			/* Index cannot be dropped. */
7307 			trx->error_info = index;
7308 			return(true);
7309 		}
7310 	}
7311 
7312 	fks = &indexed_table->foreign_set;
7313 
7314 	/* Check for all FK references in current table using the index. */
7315 	for (dict_foreign_set::const_iterator it = fks->begin();
7316 	     it != fks->end(); ++it) {
7317 
7318 		dict_foreign_t*	foreign = *it;
7319 		if (foreign->foreign_index != index) {
7320 			continue;
7321 		}
7322 
7323 		ut_ad(indexed_table == foreign->foreign_table);
7324 
7325 		if (!innobase_dropping_foreign(
7326 			    foreign, drop_fk, n_drop_fk)
7327 		    && NULL == dict_foreign_find_index(
7328 			    indexed_table, col_names,
7329 			    foreign->foreign_col_names,
7330 			    foreign->n_fields, index,
7331 			    /*check_charsets=*/TRUE,
7332 			    /*check_null=*/FALSE,
7333 			    NULL, NULL, NULL)
7334 		    && NULL == innobase_find_equiv_index(
7335 			    foreign->foreign_col_names,
7336 			    foreign->n_fields,
7337 			    ha_alter_info->key_info_buffer,
7338 			    span<uint>(ha_alter_info->index_add_buffer,
7339 				       ha_alter_info->index_add_count))) {
7340 
7341 			/* Index cannot be dropped. */
7342 			trx->error_info = index;
7343 			return(true);
7344 		}
7345 	}
7346 
7347 	return(false);
7348 }
7349 
7350 /**
7351 Rename a given index in the InnoDB data dictionary.
7352 
7353 @param index index to rename
7354 @param new_name new name of the index
7355 @param[in,out] trx dict transaction to use, not going to be committed here
7356 
7357 @retval true Failure
7358 @retval false Success */
7359 static MY_ATTRIBUTE((warn_unused_result))
7360 bool
rename_index_try(const dict_index_t * index,const char * new_name,trx_t * trx)7361 rename_index_try(
7362 	const dict_index_t*	index,
7363 	const char*		new_name,
7364 	trx_t*			trx)
7365 {
7366 	DBUG_ENTER("rename_index_try");
7367 	ut_d(dict_sys.assert_locked());
7368 	ut_ad(trx->dict_operation_lock_mode == RW_X_LATCH);
7369 
7370 	pars_info_t*	pinfo;
7371 	dberr_t		err;
7372 
7373 	pinfo = pars_info_create();
7374 
7375 	pars_info_add_ull_literal(pinfo, "table_id", index->table->id);
7376 	pars_info_add_ull_literal(pinfo, "index_id", index->id);
7377 	pars_info_add_str_literal(pinfo, "new_name", new_name);
7378 
7379 	trx->op_info = "Renaming an index in SYS_INDEXES";
7380 
7381 	DBUG_EXECUTE_IF(
7382 		"ib_rename_index_fail1",
7383 		DBUG_SET("+d,innodb_report_deadlock");
7384 	);
7385 
7386 	err = que_eval_sql(
7387 		pinfo,
7388 		"PROCEDURE RENAME_INDEX_IN_SYS_INDEXES () IS\n"
7389 		"BEGIN\n"
7390 		"UPDATE SYS_INDEXES SET\n"
7391 		"NAME = :new_name\n"
7392 		"WHERE\n"
7393 		"ID = :index_id AND\n"
7394 		"TABLE_ID = :table_id;\n"
7395 		"END;\n",
7396 		FALSE, trx); /* pinfo is freed by que_eval_sql() */
7397 
7398 	DBUG_EXECUTE_IF(
7399 		"ib_rename_index_fail1",
7400 		DBUG_SET("-d,innodb_report_deadlock");
7401 	);
7402 
7403 	trx->op_info = "";
7404 
7405 	if (err != DB_SUCCESS) {
7406 		my_error_innodb(err, index->table->name.m_name, 0);
7407 		DBUG_RETURN(true);
7408 	}
7409 
7410 	DBUG_RETURN(false);
7411 }
7412 
7413 
7414 /**
7415 Rename a given index in the InnoDB data dictionary cache.
7416 
7417 @param[in,out] index index to rename
7418 @param new_name new index name
7419 */
7420 static
7421 void
innobase_rename_index_cache(dict_index_t * index,const char * new_name)7422 innobase_rename_index_cache(dict_index_t* index, const char* new_name)
7423 {
7424 	DBUG_ENTER("innobase_rename_index_cache");
7425 	ut_d(dict_sys.assert_locked());
7426 
7427 	size_t	old_name_len = strlen(index->name);
7428 	size_t	new_name_len = strlen(new_name);
7429 
7430 	if (old_name_len < new_name_len) {
7431 		index->name = static_cast<char*>(
7432 		    mem_heap_alloc(index->heap, new_name_len + 1));
7433 	}
7434 
7435 	memcpy(const_cast<char*>(index->name()), new_name, new_name_len + 1);
7436 
7437 	DBUG_VOID_RETURN;
7438 }
7439 
7440 
7441 /** Rename the index name in cache.
7442 @param[in]	ctx		alter context
7443 @param[in]	ha_alter_info	Data used during inplace alter. */
7444 static void
innobase_rename_indexes_cache(const ha_innobase_inplace_ctx * ctx,const Alter_inplace_info * ha_alter_info)7445 innobase_rename_indexes_cache(const ha_innobase_inplace_ctx *ctx,
7446                               const Alter_inplace_info *ha_alter_info)
7447 {
7448   DBUG_ASSERT(ha_alter_info->handler_flags & ALTER_RENAME_INDEX);
7449 
7450   std::vector<std::pair<dict_index_t *, const char *>> rename_info;
7451   rename_info.reserve(ha_alter_info->rename_keys.size());
7452 
7453   for (const Alter_inplace_info::Rename_key_pair &pair :
7454        ha_alter_info->rename_keys)
7455   {
7456     dict_index_t *index=
7457         dict_table_get_index_on_name(ctx->old_table, pair.old_key->name.str);
7458     ut_ad(index);
7459 
7460     rename_info.emplace_back(index, pair.new_key->name.str);
7461   }
7462 
7463   for (const auto &pair : rename_info)
7464     innobase_rename_index_cache(pair.first, pair.second);
7465 }
7466 
7467 /** Fill the stored column information in s_cols list.
7468 @param[in]	altered_table	mysql table object
7469 @param[in]	table		innodb table object
7470 @param[out]	s_cols		list of stored column
7471 @param[out]	s_heap		heap for storing stored
7472 column information. */
7473 static
7474 void
alter_fill_stored_column(const TABLE * altered_table,dict_table_t * table,dict_s_col_list ** s_cols,mem_heap_t ** s_heap)7475 alter_fill_stored_column(
7476 	const TABLE*		altered_table,
7477 	dict_table_t*		table,
7478 	dict_s_col_list**	s_cols,
7479 	mem_heap_t**		s_heap)
7480 {
7481 	ulint	n_cols = altered_table->s->fields;
7482 	ulint	stored_col_no = 0;
7483 
7484 	for (ulint i = 0; i < n_cols; i++) {
7485 		Field* field = altered_table->field[i];
7486 		dict_s_col_t	s_col;
7487 
7488 		if (field->stored_in_db()) {
7489 			stored_col_no++;
7490 		}
7491 
7492 		if (!innobase_is_s_fld(field)) {
7493 			continue;
7494 		}
7495 
7496 		ulint	num_base = 0;
7497 		dict_col_t*	col = dict_table_get_nth_col(table,
7498 							     stored_col_no);
7499 
7500 		s_col.m_col = col;
7501 		s_col.s_pos = i;
7502 
7503 		if (*s_cols == NULL) {
7504 			*s_cols = UT_NEW_NOKEY(dict_s_col_list());
7505 			*s_heap = mem_heap_create(1000);
7506 		}
7507 
7508 		if (num_base != 0) {
7509 			s_col.base_col = static_cast<dict_col_t**>(mem_heap_zalloc(
7510 						*s_heap, num_base * sizeof(dict_col_t*)));
7511 		} else {
7512 			s_col.base_col = NULL;
7513 		}
7514 
7515 		s_col.num_base = num_base;
7516 		innodb_base_col_setup_for_stored(table, field, &s_col);
7517 		(*s_cols)->push_front(s_col);
7518 	}
7519 }
7520 
7521 static bool alter_templ_needs_rebuild(const TABLE* altered_table,
7522                                       const Alter_inplace_info* ha_alter_info,
7523                                       const dict_table_t* table);
7524 
7525 
7526 /** Allows InnoDB to update internal structures with concurrent
7527 writes blocked (provided that check_if_supported_inplace_alter()
7528 did not return HA_ALTER_INPLACE_NO_LOCK).
7529 This will be invoked before inplace_alter_table().
7530 
7531 @param altered_table TABLE object for new version of table.
7532 @param ha_alter_info Structure describing changes to be done
7533 by ALTER TABLE and holding data used during in-place alter.
7534 
7535 @retval true Failure
7536 @retval false Success
7537 */
7538 
7539 bool
prepare_inplace_alter_table(TABLE * altered_table,Alter_inplace_info * ha_alter_info)7540 ha_innobase::prepare_inplace_alter_table(
7541 /*=====================================*/
7542 	TABLE*			altered_table,
7543 	Alter_inplace_info*	ha_alter_info)
7544 {
7545 	dict_index_t**	drop_index;	/*!< Index to be dropped */
7546 	ulint		n_drop_index;	/*!< Number of indexes to drop */
7547 	dict_foreign_t**drop_fk;	/*!< Foreign key constraints to drop */
7548 	ulint		n_drop_fk;	/*!< Number of foreign keys to drop */
7549 	dict_foreign_t**add_fk = NULL;	/*!< Foreign key constraints to drop */
7550 	ulint		n_add_fk;	/*!< Number of foreign keys to drop */
7551 	dict_table_t*	indexed_table;	/*!< Table where indexes are created */
7552 	mem_heap_t*	heap;
7553 	const char**	col_names;
7554 	int		error;
7555 	ulint		add_autoinc_col_no	= ULINT_UNDEFINED;
7556 	ulonglong	autoinc_col_max_value	= 0;
7557 	ulint		fts_doc_col_no		= ULINT_UNDEFINED;
7558 	bool		add_fts_doc_id		= false;
7559 	bool		add_fts_doc_id_idx	= false;
7560 	bool		add_fts_idx		= false;
7561 	dict_s_col_list*s_cols			= NULL;
7562 	mem_heap_t*	s_heap			= NULL;
7563 
7564 	DBUG_ENTER("prepare_inplace_alter_table");
7565 	DBUG_ASSERT(!ha_alter_info->handler_ctx);
7566 	DBUG_ASSERT(ha_alter_info->create_info);
7567 	DBUG_ASSERT(!srv_read_only_mode);
7568 
7569 	/* Init online ddl status variables */
7570 	onlineddl_rowlog_rows = 0;
7571 	onlineddl_rowlog_pct_used = 0;
7572 	onlineddl_pct_progress = 0;
7573 
7574 	MONITOR_ATOMIC_INC(MONITOR_PENDING_ALTER_TABLE);
7575 
7576 #ifdef UNIV_DEBUG
7577 	for (dict_index_t* index = dict_table_get_first_index(m_prebuilt->table);
7578 	     index;
7579 	     index = dict_table_get_next_index(index)) {
7580 		ut_ad(!index->to_be_dropped);
7581 	}
7582 #endif /* UNIV_DEBUG */
7583 
7584 	ut_d(mutex_enter(&dict_sys.mutex));
7585 	ut_d(dict_table_check_for_dup_indexes(
7586 		     m_prebuilt->table, CHECK_ABORTED_OK));
7587 	ut_d(mutex_exit(&dict_sys.mutex));
7588 
7589 	if (!(ha_alter_info->handler_flags & ~INNOBASE_INPLACE_IGNORE)) {
7590 		/* Nothing to do */
7591 		DBUG_ASSERT(m_prebuilt->trx->dict_operation_lock_mode == 0);
7592 		DBUG_RETURN(false);
7593 	}
7594 
7595 #ifdef WITH_PARTITION_STORAGE_ENGINE
7596 	if (table->part_info == NULL) {
7597 #endif
7598 	/* Ignore the MDL downgrade when table is empty.
7599 	This optimization is disabled for partition table. */
7600 	ha_alter_info->mdl_exclusive_after_prepare =
7601 		innobase_table_is_empty(m_prebuilt->table, false);
7602 	if (ha_alter_info->online
7603 	    && ha_alter_info->mdl_exclusive_after_prepare) {
7604 		ha_alter_info->online = false;
7605 	}
7606 #ifdef WITH_PARTITION_STORAGE_ENGINE
7607 	}
7608 #endif
7609 	indexed_table = m_prebuilt->table;
7610 
7611 	/* ALTER TABLE will not implicitly move a table from a single-table
7612 	tablespace to the system tablespace when innodb_file_per_table=OFF.
7613 	But it will implicitly move a table from the system tablespace to a
7614 	single-table tablespace if innodb_file_per_table = ON. */
7615 
7616 	create_table_info_t	info(m_user_thd,
7617 				     altered_table,
7618 				     ha_alter_info->create_info,
7619 				     NULL,
7620 				     NULL,
7621 				     srv_file_per_table);
7622 
7623 	info.set_tablespace_type(indexed_table->space != fil_system.sys_space);
7624 
7625 	if (ha_alter_info->handler_flags & ALTER_ADD_NON_UNIQUE_NON_PRIM_INDEX) {
7626 		if (info.gcols_in_fulltext_or_spatial()) {
7627 			goto err_exit_no_heap;
7628 		}
7629 	}
7630 
7631 	if (indexed_table->is_readable()) {
7632 	} else {
7633 		if (indexed_table->corrupted) {
7634 			/* Handled below */
7635 		} else {
7636 			if (const fil_space_t* space = indexed_table->space) {
7637 				String str;
7638 				const char* engine= table_type();
7639 
7640 				push_warning_printf(
7641 					m_user_thd,
7642 					Sql_condition::WARN_LEVEL_WARN,
7643 					HA_ERR_DECRYPTION_FAILED,
7644 					"Table %s in file %s is encrypted but encryption service or"
7645 					" used key_id is not available. "
7646 					" Can't continue reading table.",
7647 					table_share->table_name.str,
7648 					space->chain.start->name);
7649 
7650 				my_error(ER_GET_ERRMSG, MYF(0), HA_ERR_DECRYPTION_FAILED, str.c_ptr(), engine);
7651 				DBUG_RETURN(true);
7652 			}
7653 		}
7654 	}
7655 
7656 	if (indexed_table->corrupted
7657 	    || dict_table_get_first_index(indexed_table) == NULL
7658 	    || dict_table_get_first_index(indexed_table)->is_corrupted()) {
7659 		/* The clustered index is corrupted. */
7660 		my_error(ER_CHECK_NO_SUCH_TABLE, MYF(0));
7661 		DBUG_RETURN(true);
7662 	} else {
7663 		const char* invalid_opt = info.create_options_are_invalid();
7664 
7665 		/* Check engine specific table options */
7666 		if (const char* invalid_tbopt = info.check_table_options()) {
7667 			my_error(ER_ILLEGAL_HA_CREATE_OPTION, MYF(0),
7668 				 table_type(), invalid_tbopt);
7669 			goto err_exit_no_heap;
7670 		}
7671 
7672 		if (invalid_opt) {
7673 			my_error(ER_ILLEGAL_HA_CREATE_OPTION, MYF(0),
7674 				 table_type(), invalid_opt);
7675 			goto err_exit_no_heap;
7676 		}
7677 	}
7678 
7679 	/* Check if any index name is reserved. */
7680 	if (innobase_index_name_is_reserved(
7681 		    m_user_thd,
7682 		    ha_alter_info->key_info_buffer,
7683 		    ha_alter_info->key_count)) {
7684 err_exit_no_heap:
7685 		DBUG_ASSERT(m_prebuilt->trx->dict_operation_lock_mode == 0);
7686 		online_retry_drop_indexes(m_prebuilt->table, m_user_thd);
7687 		DBUG_RETURN(true);
7688 	}
7689 
7690 	indexed_table = m_prebuilt->table;
7691 
7692 	/* Check that index keys are sensible */
7693 	error = innobase_check_index_keys(ha_alter_info, indexed_table);
7694 
7695 	if (error) {
7696 		goto err_exit_no_heap;
7697 	}
7698 
7699 	/* Prohibit renaming a column to something that the table
7700 	already contains. */
7701 	if (ha_alter_info->handler_flags
7702 	    & ALTER_COLUMN_NAME) {
7703 		for (Field** fp = table->field; *fp; fp++) {
7704 			if (!((*fp)->flags & FIELD_IS_RENAMED)) {
7705 				continue;
7706 			}
7707 
7708 			const char* name = 0;
7709 
7710 			for (const Create_field& cf :
7711 			     ha_alter_info->alter_info->create_list) {
7712 				if (cf.field == *fp) {
7713 					name = cf.field_name.str;
7714 					goto check_if_ok_to_rename;
7715 				}
7716 			}
7717 
7718 			ut_error;
7719 check_if_ok_to_rename:
7720 			/* Prohibit renaming a column from FTS_DOC_ID
7721 			if full-text indexes exist. */
7722 			if (!my_strcasecmp(system_charset_info,
7723 					   (*fp)->field_name.str,
7724 					   FTS_DOC_ID_COL_NAME)
7725 			    && innobase_fulltext_exist(altered_table)) {
7726 				my_error(ER_INNODB_FT_WRONG_DOCID_COLUMN,
7727 					 MYF(0), name);
7728 				goto err_exit_no_heap;
7729 			}
7730 
7731 			/* Prohibit renaming a column to an internal column. */
7732 			const char*	s = m_prebuilt->table->col_names;
7733 			unsigned j;
7734 			/* Skip user columns.
7735 			MySQL should have checked these already.
7736 			We want to allow renaming of c1 to c2, c2 to c1. */
7737 			for (j = 0; j < table->s->fields; j++) {
7738 				if (table->field[j]->stored_in_db()) {
7739 					s += strlen(s) + 1;
7740 				}
7741 			}
7742 
7743 			for (; j < m_prebuilt->table->n_def; j++) {
7744 				if (!my_strcasecmp(
7745 					    system_charset_info, name, s)) {
7746 					my_error(ER_WRONG_COLUMN_NAME, MYF(0),
7747 						 s);
7748 					goto err_exit_no_heap;
7749 				}
7750 
7751 				s += strlen(s) + 1;
7752 			}
7753 		}
7754 	}
7755 
7756 	if (!info.innobase_table_flags()) {
7757 		my_error(ER_ILLEGAL_HA_CREATE_OPTION, MYF(0),
7758 			 table_type(), "PAGE_COMPRESSED");
7759 		goto err_exit_no_heap;
7760 	}
7761 
7762 	if (info.flags2() & DICT_TF2_USE_FILE_PER_TABLE) {
7763 		/* Preserve the DATA DIRECTORY attribute, because it
7764 		currently cannot be changed during ALTER TABLE. */
7765 		info.flags_set(m_prebuilt->table->flags
7766 			       & 1U << DICT_TF_POS_DATA_DIR);
7767 	}
7768 
7769 
7770 	/* ALGORITHM=INPLACE without rebuild (10.3+ ALGORITHM=NOCOPY)
7771 	must use the current ROW_FORMAT of the table. */
7772 	const ulint max_col_len = DICT_MAX_FIELD_LEN_BY_FORMAT_FLAG(
7773 		innobase_need_rebuild(ha_alter_info, this->table)
7774 		? info.flags()
7775 		: m_prebuilt->table->flags);
7776 
7777 	/* Check each index's column length to make sure they do not
7778 	exceed limit */
7779 	for (ulint i = 0; i < ha_alter_info->key_count; i++) {
7780 		const KEY* key = &ha_alter_info->key_info_buffer[i];
7781 
7782 		if (key->flags & HA_FULLTEXT) {
7783 			/* The column length does not matter for
7784 			fulltext search indexes. But, UNIQUE
7785 			fulltext indexes are not supported. */
7786 			DBUG_ASSERT(!(key->flags & HA_NOSAME));
7787 			DBUG_ASSERT(!(key->flags & HA_KEYFLAG_MASK
7788 				      & ~(HA_FULLTEXT
7789 					  | HA_PACK_KEY
7790 					  | HA_BINARY_PACK_KEY)));
7791 			add_fts_idx = true;
7792 			continue;
7793 		}
7794 
7795 		if (too_big_key_part_length(max_col_len, *key)) {
7796 			my_error(ER_INDEX_COLUMN_TOO_LONG, MYF(0),
7797 				 max_col_len);
7798 			goto err_exit_no_heap;
7799 		}
7800 	}
7801 
7802 	/* We won't be allowed to add fts index to a table with
7803 	fts indexes already but without AUX_HEX_NAME set.
7804 	This means the aux tables of the table failed to
7805 	rename to hex format but new created aux tables
7806 	shall be in hex format, which is contradictory. */
7807 	if (!DICT_TF2_FLAG_IS_SET(indexed_table, DICT_TF2_FTS_AUX_HEX_NAME)
7808 	    && indexed_table->fts != NULL && add_fts_idx) {
7809 		my_error(ER_INNODB_FT_AUX_NOT_HEX_ID, MYF(0));
7810 		goto err_exit_no_heap;
7811 	}
7812 
7813 	/* Check existing index definitions for too-long column
7814 	prefixes as well, in case max_col_len shrunk. */
7815 	for (const dict_index_t* index
7816 		     = dict_table_get_first_index(indexed_table);
7817 	     index;
7818 	     index = dict_table_get_next_index(index)) {
7819 		if (index->type & DICT_FTS) {
7820 			DBUG_ASSERT(index->type == DICT_FTS
7821 				    || (index->type & DICT_CORRUPT));
7822 
7823 			/* We need to drop any corrupted fts indexes
7824 			before we add a new fts index. */
7825 			if (add_fts_idx && index->type & DICT_CORRUPT) {
7826 				ib_errf(m_user_thd, IB_LOG_LEVEL_ERROR,
7827 					ER_INNODB_INDEX_CORRUPT,
7828 					"Fulltext index '%s' is corrupt. "
7829 					"you should drop this index first.",
7830 					index->name());
7831 
7832 				goto err_exit_no_heap;
7833 			}
7834 
7835 			continue;
7836 		}
7837 
7838 		for (ulint i = 0; i < dict_index_get_n_fields(index); i++) {
7839 			const dict_field_t* field
7840 				= dict_index_get_nth_field(index, i);
7841 			if (field->prefix_len > max_col_len) {
7842 				my_error(ER_INDEX_COLUMN_TOO_LONG, MYF(0),
7843 					 max_col_len);
7844 				goto err_exit_no_heap;
7845 			}
7846 		}
7847 	}
7848 
7849 	n_drop_index = 0;
7850 	n_drop_fk = 0;
7851 
7852 	if (ha_alter_info->handler_flags
7853 	    & (INNOBASE_ALTER_NOREBUILD | INNOBASE_ALTER_REBUILD
7854 	       | INNOBASE_ALTER_INSTANT)) {
7855 		heap = mem_heap_create(1024);
7856 
7857 		if (ha_alter_info->handler_flags
7858 		    & ALTER_COLUMN_NAME) {
7859 			col_names = innobase_get_col_names(
7860 				ha_alter_info, altered_table, table,
7861 				indexed_table, heap);
7862 		} else {
7863 			col_names = NULL;
7864 		}
7865 	} else {
7866 		heap = NULL;
7867 		col_names = NULL;
7868 	}
7869 
7870 	if (ha_alter_info->handler_flags
7871 	    & ALTER_DROP_FOREIGN_KEY) {
7872 		DBUG_ASSERT(ha_alter_info->alter_info->drop_list.elements > 0);
7873 
7874 		drop_fk = static_cast<dict_foreign_t**>(
7875 			mem_heap_alloc(
7876 				heap,
7877 				ha_alter_info->alter_info->drop_list.elements
7878 				* sizeof(dict_foreign_t*)));
7879 
7880 		for (Alter_drop& drop : ha_alter_info->alter_info->drop_list) {
7881 			if (drop.type != Alter_drop::FOREIGN_KEY) {
7882 				continue;
7883 			}
7884 
7885 			dict_foreign_t* foreign;
7886 
7887 			for (dict_foreign_set::iterator it
7888 				= m_prebuilt->table->foreign_set.begin();
7889 			     it != m_prebuilt->table->foreign_set.end();
7890 			     ++it) {
7891 
7892 				foreign = *it;
7893 				const char* fid = strchr(foreign->id, '/');
7894 
7895 				DBUG_ASSERT(fid);
7896 				/* If no database/ prefix was present in
7897 				the FOREIGN KEY constraint name, compare
7898 				to the full constraint name. */
7899 				fid = fid ? fid + 1 : foreign->id;
7900 
7901 				if (!my_strcasecmp(system_charset_info,
7902 						   fid, drop.name)) {
7903 					goto found_fk;
7904 				}
7905 			}
7906 
7907 			my_error(ER_CANT_DROP_FIELD_OR_KEY, MYF(0),
7908 				drop.type_name(), drop.name);
7909 			goto err_exit;
7910 found_fk:
7911 			for (ulint i = n_drop_fk; i--; ) {
7912 				if (drop_fk[i] == foreign) {
7913 					goto dup_fk;
7914 				}
7915 			}
7916 			drop_fk[n_drop_fk++] = foreign;
7917 dup_fk:
7918 			continue;
7919 		}
7920 
7921 		DBUG_ASSERT(n_drop_fk > 0);
7922 
7923 		DBUG_ASSERT(n_drop_fk
7924 			    <= ha_alter_info->alter_info->drop_list.elements);
7925 	} else {
7926 		drop_fk = NULL;
7927 	}
7928 
7929 	if (ha_alter_info->index_drop_count) {
7930 		dict_index_t*	drop_primary = NULL;
7931 
7932 		DBUG_ASSERT(ha_alter_info->handler_flags
7933 			    & (ALTER_DROP_NON_UNIQUE_NON_PRIM_INDEX
7934 			       | ALTER_DROP_UNIQUE_INDEX
7935 			       | ALTER_DROP_PK_INDEX));
7936 		/* Check which indexes to drop. */
7937 		drop_index = static_cast<dict_index_t**>(
7938 			mem_heap_alloc(
7939 				heap, (ha_alter_info->index_drop_count + 1)
7940 				* sizeof *drop_index));
7941 
7942 		for (uint i = 0; i < ha_alter_info->index_drop_count; i++) {
7943 			const KEY*	key
7944 				= ha_alter_info->index_drop_buffer[i];
7945 			dict_index_t*	index
7946 				= dict_table_get_index_on_name(
7947 					indexed_table, key->name.str);
7948 
7949 			if (!index) {
7950 				push_warning_printf(
7951 					m_user_thd,
7952 					Sql_condition::WARN_LEVEL_WARN,
7953 					HA_ERR_WRONG_INDEX,
7954 					"InnoDB could not find key"
7955 					" with name %s", key->name.str);
7956 			} else {
7957 				ut_ad(!index->to_be_dropped);
7958 				if (!index->is_primary()) {
7959 					drop_index[n_drop_index++] = index;
7960 				} else {
7961 					drop_primary = index;
7962 				}
7963 			}
7964 		}
7965 
7966 		/* If all FULLTEXT indexes were removed, drop an
7967 		internal FTS_DOC_ID_INDEX as well, unless it exists in
7968 		the table. */
7969 
7970 		if (innobase_fulltext_exist(table)
7971 		    && !innobase_fulltext_exist(altered_table)
7972 		    && !DICT_TF2_FLAG_IS_SET(
7973 			indexed_table, DICT_TF2_FTS_HAS_DOC_ID)) {
7974 			dict_index_t*	fts_doc_index
7975 				= indexed_table->fts_doc_id_index;
7976 			ut_ad(fts_doc_index);
7977 
7978 			// Add some fault tolerance for non-debug builds.
7979 			if (fts_doc_index == NULL) {
7980 				goto check_if_can_drop_indexes;
7981 			}
7982 
7983 			DBUG_ASSERT(!fts_doc_index->to_be_dropped);
7984 
7985 			for (uint i = 0; i < table->s->keys; i++) {
7986 				if (!my_strcasecmp(
7987 					    system_charset_info,
7988 					    FTS_DOC_ID_INDEX_NAME,
7989 					    table->key_info[i].name.str)) {
7990 					/* The index exists in the MySQL
7991 					data dictionary. Do not drop it,
7992 					even though it is no longer needed
7993 					by InnoDB fulltext search. */
7994 					goto check_if_can_drop_indexes;
7995 				}
7996 			}
7997 
7998 			drop_index[n_drop_index++] = fts_doc_index;
7999 		}
8000 
8001 check_if_can_drop_indexes:
8002 		/* Check if the indexes can be dropped. */
8003 
8004 		/* Prevent a race condition between DROP INDEX and
8005 		CREATE TABLE adding FOREIGN KEY constraints. */
8006 		row_mysql_lock_data_dictionary(m_prebuilt->trx);
8007 
8008 		if (!n_drop_index) {
8009 			drop_index = NULL;
8010 		} else {
8011 			/* Flag all indexes that are to be dropped. */
8012 			for (ulint i = 0; i < n_drop_index; i++) {
8013 				ut_ad(!drop_index[i]->to_be_dropped);
8014 				drop_index[i]->to_be_dropped = 1;
8015 			}
8016 		}
8017 
8018 		if (m_prebuilt->trx->check_foreigns) {
8019 			for (uint i = 0; i < n_drop_index; i++) {
8020 				dict_index_t*	index = drop_index[i];
8021 
8022 				if (innobase_check_foreign_key_index(
8023 						ha_alter_info, index,
8024 						indexed_table, col_names,
8025 						m_prebuilt->trx, drop_fk, n_drop_fk)) {
8026 					row_mysql_unlock_data_dictionary(
8027 						m_prebuilt->trx);
8028 					m_prebuilt->trx->error_info = index;
8029 					print_error(HA_ERR_DROP_INDEX_FK,
8030 						MYF(0));
8031 					goto err_exit;
8032 				}
8033 			}
8034 
8035 			/* If a primary index is dropped, need to check
8036 			any depending foreign constraints get affected */
8037 			if (drop_primary
8038 				&& innobase_check_foreign_key_index(
8039 					ha_alter_info, drop_primary,
8040 					indexed_table, col_names,
8041 					m_prebuilt->trx, drop_fk, n_drop_fk)) {
8042 				row_mysql_unlock_data_dictionary(m_prebuilt->trx);
8043 				print_error(HA_ERR_DROP_INDEX_FK, MYF(0));
8044 				goto err_exit;
8045 			}
8046 		}
8047 
8048 		row_mysql_unlock_data_dictionary(m_prebuilt->trx);
8049 	} else {
8050 		drop_index = NULL;
8051 	}
8052 
8053 	/* Check if any of the existing indexes are marked as corruption
8054 	and if they are, refuse adding more indexes. */
8055 	if (ha_alter_info->handler_flags & ALTER_ADD_NON_UNIQUE_NON_PRIM_INDEX) {
8056 		for (dict_index_t* index = dict_table_get_first_index(indexed_table);
8057 		     index != NULL; index = dict_table_get_next_index(index)) {
8058 
8059 			if (!index->to_be_dropped && index->is_committed()
8060 			    && index->is_corrupted()) {
8061 				my_error(ER_INDEX_CORRUPT, MYF(0), index->name());
8062 				goto err_exit;
8063 			}
8064 		}
8065 	}
8066 
8067 	n_add_fk = 0;
8068 
8069 	if (ha_alter_info->handler_flags
8070 	    & ALTER_ADD_FOREIGN_KEY) {
8071 		ut_ad(!m_prebuilt->trx->check_foreigns);
8072 
8073 		alter_fill_stored_column(altered_table, m_prebuilt->table,
8074 					 &s_cols, &s_heap);
8075 
8076 		add_fk = static_cast<dict_foreign_t**>(
8077 			mem_heap_zalloc(
8078 				heap,
8079 				ha_alter_info->alter_info->key_list.elements
8080 				* sizeof(dict_foreign_t*)));
8081 
8082 		if (!innobase_get_foreign_key_info(
8083 			    ha_alter_info, table_share,
8084 			    m_prebuilt->table, col_names,
8085 			    drop_index, n_drop_index,
8086 			    add_fk, &n_add_fk, m_prebuilt->trx, s_cols)) {
8087 err_exit:
8088 			if (n_drop_index) {
8089 				row_mysql_lock_data_dictionary(m_prebuilt->trx);
8090 
8091 				/* Clear the to_be_dropped flags, which might
8092 				have been set at this point. */
8093 				for (ulint i = 0; i < n_drop_index; i++) {
8094 					ut_ad(drop_index[i]->is_committed());
8095 					drop_index[i]->to_be_dropped = 0;
8096 				}
8097 
8098 				row_mysql_unlock_data_dictionary(
8099 					m_prebuilt->trx);
8100 			}
8101 
8102 			if (heap) {
8103 				mem_heap_free(heap);
8104 			}
8105 
8106 			if (s_cols != NULL) {
8107 				UT_DELETE(s_cols);
8108 				mem_heap_free(s_heap);
8109 			}
8110 
8111 			goto err_exit_no_heap;
8112 		}
8113 
8114 		if (s_cols != NULL) {
8115 			UT_DELETE(s_cols);
8116 			mem_heap_free(s_heap);
8117 		}
8118 	}
8119 
8120 	if (ha_alter_info->handler_flags & ALTER_RENAME_INDEX) {
8121 		for (const Alter_inplace_info::Rename_key_pair& pair :
8122 		     ha_alter_info->rename_keys) {
8123 			dict_index_t* index = dict_table_get_index_on_name(
8124 			    indexed_table, pair.old_key->name.str);
8125 
8126 			if (!index || index->is_corrupted()) {
8127 				my_error(ER_INDEX_CORRUPT, MYF(0),
8128 					 index->name());
8129 				goto err_exit;
8130 			}
8131 		}
8132 	}
8133 
8134 	const ha_table_option_struct& alt_opt=
8135 		*ha_alter_info->create_info->option_struct;
8136 
8137 	if (!(ha_alter_info->handler_flags & INNOBASE_ALTER_DATA)
8138 	    || ((ha_alter_info->handler_flags & ~(INNOBASE_INPLACE_IGNORE
8139 						  | INNOBASE_ALTER_NOCREATE
8140 						  | INNOBASE_ALTER_INSTANT))
8141 		== ALTER_OPTIONS
8142 		&& !alter_options_need_rebuild(ha_alter_info, table))) {
8143 
8144 		ha_innobase_inplace_ctx *ctx = NULL;
8145 		if (heap) {
8146 			ctx = new ha_innobase_inplace_ctx(
8147 					m_prebuilt,
8148 					drop_index, n_drop_index,
8149 					drop_fk, n_drop_fk,
8150 					add_fk, n_add_fk,
8151 					ha_alter_info->online,
8152 					heap, indexed_table,
8153 					col_names, ULINT_UNDEFINED, 0, 0,
8154 					(ha_alter_info->ignore
8155 					 || !thd_is_strict_mode(m_user_thd)),
8156 					alt_opt.page_compressed,
8157 					alt_opt.page_compression_level);
8158 			ha_alter_info->handler_ctx = ctx;
8159 		}
8160 
8161 		DBUG_ASSERT(m_prebuilt->trx->dict_operation_lock_mode == 0);
8162 		online_retry_drop_indexes(m_prebuilt->table, m_user_thd);
8163 
8164 		if ((ha_alter_info->handler_flags
8165 		     & ALTER_DROP_VIRTUAL_COLUMN)
8166 		    && prepare_inplace_drop_virtual(ha_alter_info, table)) {
8167 			DBUG_RETURN(true);
8168 		}
8169 
8170 		if ((ha_alter_info->handler_flags
8171 		     & ALTER_ADD_VIRTUAL_COLUMN)
8172 		    && prepare_inplace_add_virtual(
8173 			    ha_alter_info, altered_table, table)) {
8174 			DBUG_RETURN(true);
8175 		}
8176 
8177 		if (!(ha_alter_info->handler_flags & INNOBASE_ALTER_DATA)
8178 		    && alter_templ_needs_rebuild(altered_table, ha_alter_info,
8179 						 ctx->new_table)
8180 		    && ctx->new_table->n_v_cols > 0) {
8181 			/* Changing maria record structure may end up here only
8182 			if virtual columns were altered. In this case, however,
8183 			vc_templ should be rebuilt. Since we don't actually
8184 			change any stored data, we can just dispose vc_templ;
8185 			it will be recreated on next ha_innobase::open(). */
8186 
8187 			DBUG_ASSERT(ctx->new_table == ctx->old_table);
8188 
8189 			dict_free_vc_templ(ctx->new_table->vc_templ);
8190 			UT_DELETE(ctx->new_table->vc_templ);
8191 
8192 			ctx->new_table->vc_templ = NULL;
8193 		}
8194 
8195 		DBUG_RETURN(false);
8196 	}
8197 
8198 	/* If we are to build a full-text search index, check whether
8199 	the table already has a DOC ID column.  If not, we will need to
8200 	add a Doc ID hidden column and rebuild the primary index */
8201 	if (innobase_fulltext_exist(altered_table)) {
8202 		ulint	doc_col_no;
8203 		ulint	num_v = 0;
8204 
8205 		if (!innobase_fts_check_doc_id_col(
8206 			    m_prebuilt->table,
8207 			    altered_table, &fts_doc_col_no, &num_v)) {
8208 
8209 			fts_doc_col_no = altered_table->s->fields - num_v;
8210 			add_fts_doc_id = true;
8211 			add_fts_doc_id_idx = true;
8212 
8213 		} else if (fts_doc_col_no == ULINT_UNDEFINED) {
8214 			goto err_exit;
8215 		}
8216 
8217 		switch (innobase_fts_check_doc_id_index(
8218 				m_prebuilt->table, altered_table,
8219 				&doc_col_no)) {
8220 		case FTS_NOT_EXIST_DOC_ID_INDEX:
8221 			add_fts_doc_id_idx = true;
8222 			break;
8223 		case FTS_INCORRECT_DOC_ID_INDEX:
8224 			my_error(ER_INNODB_FT_WRONG_DOCID_INDEX, MYF(0),
8225 				 FTS_DOC_ID_INDEX_NAME);
8226 			goto err_exit;
8227 		case FTS_EXIST_DOC_ID_INDEX:
8228 			DBUG_ASSERT(
8229 				doc_col_no == fts_doc_col_no
8230 				|| doc_col_no == ULINT_UNDEFINED
8231 				|| (ha_alter_info->handler_flags
8232 				    & (ALTER_STORED_COLUMN_ORDER
8233 				       | ALTER_DROP_STORED_COLUMN
8234 				       | ALTER_ADD_STORED_BASE_COLUMN)));
8235 		}
8236 	}
8237 
8238 	/* See if an AUTO_INCREMENT column was added. */
8239 	uint	i = 0;
8240 	ulint	num_v = 0;
8241 	for (const Create_field& new_field :
8242 	     ha_alter_info->alter_info->create_list) {
8243 		const Field*	field;
8244 
8245 		DBUG_ASSERT(i < altered_table->s->fields);
8246 
8247 		for (uint old_i = 0; table->field[old_i]; old_i++) {
8248 			if (new_field.field == table->field[old_i]) {
8249 				goto found_col;
8250 			}
8251 		}
8252 
8253 		/* This is an added column. */
8254 		DBUG_ASSERT(!new_field.field);
8255 		DBUG_ASSERT(ha_alter_info->handler_flags
8256 			    & ALTER_ADD_COLUMN);
8257 
8258 		field = altered_table->field[i];
8259 
8260 		DBUG_ASSERT((MTYP_TYPENR(field->unireg_check)
8261 			     == Field::NEXT_NUMBER)
8262 			    == !!(field->flags & AUTO_INCREMENT_FLAG));
8263 
8264 		if (field->flags & AUTO_INCREMENT_FLAG) {
8265 			if (add_autoinc_col_no != ULINT_UNDEFINED) {
8266 				/* This should have been blocked earlier. */
8267 				ut_ad(0);
8268 				my_error(ER_WRONG_AUTO_KEY, MYF(0));
8269 				goto err_exit;
8270 			}
8271 
8272 			/* Get the col no of the old table non-virtual column array */
8273 			add_autoinc_col_no = i - num_v;
8274 
8275 			autoinc_col_max_value = innobase_get_int_col_max_value(field);
8276 		}
8277 found_col:
8278 		num_v += !new_field.stored_in_db();
8279 		i++;
8280 	}
8281 
8282 	DBUG_ASSERT(heap);
8283 	DBUG_ASSERT(m_user_thd == m_prebuilt->trx->mysql_thd);
8284 	DBUG_ASSERT(!ha_alter_info->handler_ctx);
8285 
8286 	ha_alter_info->handler_ctx = new ha_innobase_inplace_ctx(
8287 		m_prebuilt,
8288 		drop_index, n_drop_index,
8289 		drop_fk, n_drop_fk, add_fk, n_add_fk,
8290 		ha_alter_info->online,
8291 		heap, m_prebuilt->table, col_names,
8292 		add_autoinc_col_no,
8293 		ha_alter_info->create_info->auto_increment_value,
8294 		autoinc_col_max_value,
8295 		ha_alter_info->ignore || !thd_is_strict_mode(m_user_thd),
8296 		alt_opt.page_compressed, alt_opt.page_compression_level);
8297 
8298 	DBUG_RETURN(prepare_inplace_alter_table_dict(
8299 			    ha_alter_info, altered_table, table,
8300 			    table_share->table_name.str,
8301 			    info.flags(), info.flags2(),
8302 			    fts_doc_col_no, add_fts_doc_id,
8303 			    add_fts_doc_id_idx));
8304 }
8305 
8306 /* Check whether a columnn length change alter operation requires
8307 to rebuild the template.
8308 @param[in]	altered_table	TABLE object for new version of table.
8309 @param[in]	ha_alter_info	Structure describing changes to be done
8310 				by ALTER TABLE and holding data used
8311 				during in-place alter.
8312 @param[in]	table		table being altered
8313 @return TRUE if needs rebuild. */
8314 static
8315 bool
alter_templ_needs_rebuild(const TABLE * altered_table,const Alter_inplace_info * ha_alter_info,const dict_table_t * table)8316 alter_templ_needs_rebuild(
8317 	const TABLE*            altered_table,
8318 	const Alter_inplace_info*     ha_alter_info,
8319 	const dict_table_t*		table)
8320 {
8321         ulint	i = 0;
8322 
8323 	for (Field** fp = altered_table->field; *fp; fp++, i++) {
8324 		for (const Create_field& cf :
8325 		     ha_alter_info->alter_info->create_list) {
8326 			for (ulint j=0; j < table->n_cols; j++) {
8327 				dict_col_t* cols
8328                                    = dict_table_get_nth_col(table, j);
8329 				if (cf.length > cols->len) {
8330 					return(true);
8331 				}
8332 			}
8333 		}
8334 	}
8335 
8336 	return(false);
8337 }
8338 
8339 /** Get the name of an erroneous key.
8340 @param[in]	error_key_num	InnoDB number of the erroneus key
8341 @param[in]	ha_alter_info	changes that were being performed
8342 @param[in]	table		InnoDB table
8343 @return	the name of the erroneous key */
8344 static
8345 const char*
get_error_key_name(ulint error_key_num,const Alter_inplace_info * ha_alter_info,const dict_table_t * table)8346 get_error_key_name(
8347 	ulint				error_key_num,
8348 	const Alter_inplace_info*	ha_alter_info,
8349 	const dict_table_t*		table)
8350 {
8351 	if (error_key_num == ULINT_UNDEFINED) {
8352 		return(FTS_DOC_ID_INDEX_NAME);
8353 	} else if (ha_alter_info->key_count == 0) {
8354 		return(dict_table_get_first_index(table)->name);
8355 	} else {
8356 		return(ha_alter_info->key_info_buffer[error_key_num].name.str);
8357 	}
8358 }
8359 
8360 /** Alter the table structure in-place with operations
8361 specified using Alter_inplace_info.
8362 The level of concurrency allowed during this operation depends
8363 on the return value from check_if_supported_inplace_alter().
8364 
8365 @param altered_table TABLE object for new version of table.
8366 @param ha_alter_info Structure describing changes to be done
8367 by ALTER TABLE and holding data used during in-place alter.
8368 
8369 @retval true Failure
8370 @retval false Success
8371 */
8372 
8373 bool
inplace_alter_table(TABLE * altered_table,Alter_inplace_info * ha_alter_info)8374 ha_innobase::inplace_alter_table(
8375 /*=============================*/
8376 	TABLE*			altered_table,
8377 	Alter_inplace_info*	ha_alter_info)
8378 {
8379 	dberr_t			error;
8380 	dict_add_v_col_t*	add_v = NULL;
8381 	dict_vcol_templ_t*	s_templ = NULL;
8382 	dict_vcol_templ_t*	old_templ = NULL;
8383 	struct TABLE*		eval_table = altered_table;
8384 	bool			rebuild_templ = false;
8385 	DBUG_ENTER("inplace_alter_table");
8386 	DBUG_ASSERT(!srv_read_only_mode);
8387 	ut_ad(!sync_check_iterate(sync_check()));
8388 	ut_ad(!rw_lock_own_flagged(&dict_sys.latch,
8389 				   RW_LOCK_FLAG_X | RW_LOCK_FLAG_S));
8390 
8391 	DEBUG_SYNC(m_user_thd, "innodb_inplace_alter_table_enter");
8392 
8393 	/* Ignore the inplace alter phase when table is empty */
8394 	if (!(ha_alter_info->handler_flags & INNOBASE_ALTER_DATA)
8395 	    || ha_alter_info->mdl_exclusive_after_prepare) {
8396 ok_exit:
8397 		DEBUG_SYNC(m_user_thd, "innodb_after_inplace_alter_table");
8398 		DBUG_RETURN(false);
8399 	}
8400 
8401 	if ((ha_alter_info->handler_flags & ~(INNOBASE_INPLACE_IGNORE
8402 					      | INNOBASE_ALTER_NOCREATE
8403 					      | INNOBASE_ALTER_INSTANT))
8404 	    == ALTER_OPTIONS
8405 	    && !alter_options_need_rebuild(ha_alter_info, table)) {
8406 		goto ok_exit;
8407 	}
8408 
8409 	ha_innobase_inplace_ctx*	ctx
8410 		= static_cast<ha_innobase_inplace_ctx*>
8411 		(ha_alter_info->handler_ctx);
8412 
8413 	DBUG_ASSERT(ctx);
8414 	DBUG_ASSERT(ctx->trx);
8415 	DBUG_ASSERT(ctx->prebuilt == m_prebuilt);
8416 
8417 	if (ctx->is_instant()) goto ok_exit;
8418 
8419 	dict_index_t*	pk = dict_table_get_first_index(m_prebuilt->table);
8420 	ut_ad(pk != NULL);
8421 
8422 	/* For partitioned tables this could be already allocated from a
8423 	previous partition invocation. For normal tables this is NULL. */
8424 	UT_DELETE(ctx->m_stage);
8425 
8426 	ctx->m_stage = UT_NEW_NOKEY(ut_stage_alter_t(pk));
8427 
8428 	if (!m_prebuilt->table->is_readable()) {
8429 		goto all_done;
8430 	}
8431 
8432 	/* If we are doing a table rebuilding or having added virtual
8433 	columns in the same clause, we will need to build a table template
8434 	that carries translation information between MySQL TABLE and InnoDB
8435 	table, which indicates the virtual columns and their base columns
8436 	info. This is used to do the computation callback, so that the
8437 	data in base columns can be extracted send to server.
8438 	If the Column length changes and it is a part of virtual
8439 	index then we need to rebuild the template. */
8440 	rebuild_templ
8441 	     = ctx->need_rebuild()
8442 	       || ((ha_alter_info->handler_flags
8443 		& ALTER_COLUMN_TYPE_CHANGE_BY_ENGINE)
8444 		&& alter_templ_needs_rebuild(
8445 		   altered_table, ha_alter_info, ctx->new_table));
8446 
8447 	if ((ctx->new_table->n_v_cols > 0) && rebuild_templ) {
8448 		/* Save the templ if isn't NULL so as to restore the
8449 		original state in case of alter operation failures. */
8450 		if (ctx->new_table->vc_templ != NULL && !ctx->need_rebuild()) {
8451 			old_templ = ctx->new_table->vc_templ;
8452 		}
8453 		s_templ = UT_NEW_NOKEY(dict_vcol_templ_t());
8454 
8455 		innobase_build_v_templ(
8456 			altered_table, ctx->new_table, s_templ, NULL, false);
8457 
8458 		ctx->new_table->vc_templ = s_templ;
8459 	} else if (ctx->num_to_add_vcol > 0 && ctx->num_to_drop_vcol == 0) {
8460 		/* if there is ongoing drop virtual column, then we disallow
8461 		inplace add index on newly added virtual column, so it does
8462 		not need to come in here to rebuild template with add_v.
8463 		Please also see the assertion in innodb_v_adjust_idx_col() */
8464 
8465 		s_templ = UT_NEW_NOKEY(dict_vcol_templ_t());
8466 
8467 		add_v = static_cast<dict_add_v_col_t*>(
8468 			mem_heap_alloc(ctx->heap, sizeof *add_v));
8469 		add_v->n_v_col = ctx->num_to_add_vcol;
8470 		add_v->v_col = ctx->add_vcol;
8471 		add_v->v_col_name = ctx->add_vcol_name;
8472 
8473 		innobase_build_v_templ(
8474 			altered_table, ctx->new_table, s_templ, add_v, false);
8475 		old_templ = ctx->new_table->vc_templ;
8476 		ctx->new_table->vc_templ = s_templ;
8477 	}
8478 
8479 	/* Drop virtual column without rebuild will keep dict table
8480 	unchanged, we use old table to evaluate virtual column value
8481 	in innobase_get_computed_value(). */
8482 	if (!ctx->need_rebuild() && ctx->num_to_drop_vcol > 0) {
8483 		eval_table = table;
8484 	}
8485 
8486 	/* Read the clustered index of the table and build
8487 	indexes based on this information using temporary
8488 	files and merge sort. */
8489 	DBUG_EXECUTE_IF("innodb_OOM_inplace_alter",
8490 			error = DB_OUT_OF_MEMORY; goto oom;);
8491 
8492 	error = row_merge_build_indexes(
8493 		m_prebuilt->trx,
8494 		m_prebuilt->table, ctx->new_table,
8495 		ctx->online,
8496 		ctx->add_index, ctx->add_key_numbers, ctx->num_to_add_index,
8497 		altered_table, ctx->defaults, ctx->col_map,
8498 		ctx->add_autoinc, ctx->sequence, ctx->skip_pk_sort,
8499 		ctx->m_stage, add_v, eval_table, ctx->allow_not_null);
8500 
8501 #ifndef DBUG_OFF
8502 oom:
8503 #endif /* !DBUG_OFF */
8504 	if (error == DB_SUCCESS && ctx->online && ctx->need_rebuild()) {
8505 		DEBUG_SYNC_C("row_log_table_apply1_before");
8506 		error = row_log_table_apply(
8507 			ctx->thr, m_prebuilt->table, altered_table,
8508 			ctx->m_stage, ctx->new_table);
8509 	}
8510 
8511 	/* Init online ddl status variables */
8512 	onlineddl_rowlog_rows = 0;
8513 	onlineddl_rowlog_pct_used = 0;
8514 	onlineddl_pct_progress = 0;
8515 
8516 	if (s_templ) {
8517 		ut_ad(ctx->need_rebuild() || ctx->num_to_add_vcol > 0
8518 		      || rebuild_templ);
8519 		dict_free_vc_templ(s_templ);
8520 		UT_DELETE(s_templ);
8521 
8522 		ctx->new_table->vc_templ = old_templ;
8523 	}
8524 
8525 	DEBUG_SYNC_C("inplace_after_index_build");
8526 
8527 	DBUG_EXECUTE_IF("create_index_fail",
8528 			error = DB_DUPLICATE_KEY;
8529 			m_prebuilt->trx->error_key_num = ULINT_UNDEFINED;);
8530 
8531 	/* After an error, remove all those index definitions
8532 	from the dictionary which were defined. */
8533 
8534 	switch (error) {
8535 		KEY*	dup_key;
8536 	all_done:
8537 	case DB_SUCCESS:
8538 		ut_d(mutex_enter(&dict_sys.mutex));
8539 		ut_d(dict_table_check_for_dup_indexes(
8540 			     m_prebuilt->table, CHECK_PARTIAL_OK));
8541 		ut_d(mutex_exit(&dict_sys.mutex));
8542 		/* prebuilt->table->n_ref_count can be anything here,
8543 		given that we hold at most a shared lock on the table. */
8544 		goto ok_exit;
8545 	case DB_DUPLICATE_KEY:
8546 		if (m_prebuilt->trx->error_key_num == ULINT_UNDEFINED
8547 		    || ha_alter_info->key_count == 0) {
8548 			/* This should be the hidden index on
8549 			FTS_DOC_ID, or there is no PRIMARY KEY in the
8550 			table. Either way, we should be seeing and
8551 			reporting a bogus duplicate key error. */
8552 			dup_key = NULL;
8553 		} else {
8554 			DBUG_ASSERT(m_prebuilt->trx->error_key_num
8555 				    < ha_alter_info->key_count);
8556 			dup_key = &ha_alter_info->key_info_buffer[
8557 				m_prebuilt->trx->error_key_num];
8558 		}
8559 		print_keydup_error(altered_table, dup_key, MYF(0));
8560 		break;
8561 	case DB_ONLINE_LOG_TOO_BIG:
8562 		DBUG_ASSERT(ctx->online);
8563 		my_error(ER_INNODB_ONLINE_LOG_TOO_BIG, MYF(0),
8564 			 get_error_key_name(m_prebuilt->trx->error_key_num,
8565 					    ha_alter_info, m_prebuilt->table));
8566 		break;
8567 	case DB_INDEX_CORRUPT:
8568 		my_error(ER_INDEX_CORRUPT, MYF(0),
8569 			 get_error_key_name(m_prebuilt->trx->error_key_num,
8570 					    ha_alter_info, m_prebuilt->table));
8571 		break;
8572 	case DB_DECRYPTION_FAILED: {
8573 		String str;
8574 		const char* engine= table_type();
8575 		get_error_message(HA_ERR_DECRYPTION_FAILED, &str);
8576 		my_error(ER_GET_ERRMSG, MYF(0), HA_ERR_DECRYPTION_FAILED, str.c_ptr(), engine);
8577 		break;
8578 	}
8579 	default:
8580 		my_error_innodb(error,
8581 				table_share->table_name.str,
8582 				m_prebuilt->table->flags);
8583 	}
8584 
8585 	/* prebuilt->table->n_ref_count can be anything here, given
8586 	that we hold at most a shared lock on the table. */
8587 	m_prebuilt->trx->error_info = NULL;
8588 	ctx->trx->error_state = DB_SUCCESS;
8589 
8590 	DBUG_RETURN(true);
8591 }
8592 
8593 /** Free the modification log for online table rebuild.
8594 @param table table that was being rebuilt online */
8595 static
8596 void
innobase_online_rebuild_log_free(dict_table_t * table)8597 innobase_online_rebuild_log_free(
8598 /*=============================*/
8599 	dict_table_t*	table)
8600 {
8601 	dict_index_t* clust_index = dict_table_get_first_index(table);
8602 	ut_d(dict_sys.assert_locked());
8603 	rw_lock_x_lock(&clust_index->lock);
8604 
8605 	if (clust_index->online_log) {
8606 		ut_ad(dict_index_get_online_status(clust_index)
8607 		      == ONLINE_INDEX_CREATION);
8608 		clust_index->online_status = ONLINE_INDEX_COMPLETE;
8609 		row_log_free(clust_index->online_log);
8610 		clust_index->online_log = NULL;
8611 		DEBUG_SYNC_C("innodb_online_rebuild_log_free_aborted");
8612 	}
8613 
8614 	DBUG_ASSERT(dict_index_get_online_status(clust_index)
8615 		    == ONLINE_INDEX_COMPLETE);
8616 	rw_lock_x_unlock(&clust_index->lock);
8617 }
8618 
8619 /** For each user column, which is part of an index which is not going to be
8620 dropped, it checks if the column number of the column is same as col_no
8621 argument passed.
8622 @param[in]	table		table
8623 @param[in]	col_no		column number
8624 @param[in]	is_v		if this is a virtual column
8625 @param[in]	only_committed	whether to consider only committed indexes
8626 @retval true column exists
8627 @retval false column does not exist, true if column is system column or
8628 it is in the index. */
8629 static
8630 bool
check_col_exists_in_indexes(const dict_table_t * table,ulint col_no,bool is_v,bool only_committed=false)8631 check_col_exists_in_indexes(
8632 	const dict_table_t*	table,
8633 	ulint			col_no,
8634 	bool			is_v,
8635 	bool			only_committed = false)
8636 {
8637 	/* This function does not check system columns */
8638 	if (!is_v && dict_table_get_nth_col(table, col_no)->mtype == DATA_SYS) {
8639 		return(true);
8640 	}
8641 
8642 	for (const dict_index_t* index = dict_table_get_first_index(table);
8643 	     index;
8644 	     index = dict_table_get_next_index(index)) {
8645 
8646 		if (only_committed
8647 		    ? !index->is_committed()
8648 		    : index->to_be_dropped) {
8649 			continue;
8650 		}
8651 
8652 		for (ulint i = 0; i < index->n_user_defined_cols; i++) {
8653 			const dict_col_t* idx_col
8654 				= dict_index_get_nth_col(index, i);
8655 
8656 			if (is_v && idx_col->is_virtual()) {
8657 				const dict_v_col_t*   v_col = reinterpret_cast<
8658 					const dict_v_col_t*>(idx_col);
8659 				if (v_col->v_pos == col_no) {
8660 					return(true);
8661 				}
8662 			}
8663 
8664 			if (!is_v && !idx_col->is_virtual()
8665 			    && dict_col_get_no(idx_col) == col_no) {
8666 				return(true);
8667 			}
8668 		}
8669 	}
8670 
8671 	return(false);
8672 }
8673 
8674 /** Rollback a secondary index creation, drop the indexes with
8675 temparary index prefix
8676 @param user_table InnoDB table
8677 @param table the TABLE
8678 @param locked TRUE=table locked, FALSE=may need to do a lazy drop
8679 @param trx the transaction
8680 @param alter_trx transaction which takes S-lock on the table
8681                  while creating the index */
8682 static
8683 void
innobase_rollback_sec_index(dict_table_t * user_table,const TABLE * table,bool locked,trx_t * trx,const trx_t * alter_trx=NULL)8684 innobase_rollback_sec_index(
8685         dict_table_t*   user_table,
8686         const TABLE*    table,
8687         bool            locked,
8688         trx_t*          trx,
8689         const trx_t*    alter_trx=NULL)
8690 {
8691 	row_merge_drop_indexes(trx, user_table, locked, alter_trx);
8692 
8693 	/* Free the table->fts only if there is no FTS_DOC_ID
8694 	in the table */
8695 	if (user_table->fts
8696 	    && !DICT_TF2_FLAG_IS_SET(user_table,
8697 				     DICT_TF2_FTS_HAS_DOC_ID)
8698 	    && !innobase_fulltext_exist(table)) {
8699 		fts_free(user_table);
8700 	}
8701 }
8702 
8703 /* Get the number of uncommitted fts index during rollback
8704 operation.
8705 @param[in]	table	table which undergoes rollback for alter
8706 @return number of uncommitted fts indexes. */
8707 static
innobase_get_uncommitted_fts_indexes(const dict_table_t * table)8708 ulint innobase_get_uncommitted_fts_indexes(const dict_table_t* table)
8709 {
8710   ut_ad(mutex_own(&dict_sys.mutex));
8711   dict_index_t*	index = dict_table_get_first_index(table);
8712   ulint n_uncommitted_fts = 0;
8713 
8714   for (; index ; index = dict_table_get_next_index(index))
8715   {
8716     if (index->type & DICT_FTS && !index->is_committed())
8717       n_uncommitted_fts++;
8718   }
8719 
8720   return n_uncommitted_fts;
8721 }
8722 
8723 /** Roll back the changes made during prepare_inplace_alter_table()
8724 and inplace_alter_table() inside the storage engine. Note that the
8725 allowed level of concurrency during this operation will be the same as
8726 for inplace_alter_table() and thus might be higher than during
8727 prepare_inplace_alter_table(). (E.g concurrent writes were blocked
8728 during prepare, but might not be during commit).
8729 
8730 @param ha_alter_info Data used during in-place alter.
8731 @param table the TABLE
8732 @param prebuilt the prebuilt struct
8733 @retval true Failure
8734 @retval false Success
8735 */
8736 inline MY_ATTRIBUTE((nonnull, warn_unused_result))
8737 bool
rollback_inplace_alter_table(Alter_inplace_info * ha_alter_info,const TABLE * table,row_prebuilt_t * prebuilt)8738 rollback_inplace_alter_table(
8739 /*=========================*/
8740 	Alter_inplace_info*	ha_alter_info,
8741 	const TABLE*		table,
8742 	row_prebuilt_t*		prebuilt)
8743 {
8744 	bool	fail	= false;
8745 
8746 	ha_innobase_inplace_ctx*	ctx
8747 		= static_cast<ha_innobase_inplace_ctx*>
8748 		(ha_alter_info->handler_ctx);
8749 
8750 	DBUG_ENTER("rollback_inplace_alter_table");
8751 
8752 	if (!ctx || !ctx->trx) {
8753 		/* If we have not started a transaction yet,
8754 		(almost) nothing has been or needs to be done. */
8755 		goto func_exit;
8756 	}
8757 
8758 	trx_start_for_ddl(ctx->trx, ctx->need_rebuild()
8759 			  ? TRX_DICT_OP_TABLE : TRX_DICT_OP_INDEX);
8760 	row_mysql_lock_data_dictionary(ctx->trx);
8761 
8762 	if (ctx->need_rebuild()) {
8763 		/* DML threads can access ctx->new_table via the
8764 		online rebuild log. Free it first. */
8765 		innobase_online_rebuild_log_free(prebuilt->table);
8766 	}
8767 
8768 	if (!ctx->new_table) {
8769 		ut_ad(ctx->need_rebuild());
8770 	} else if (ctx->need_rebuild()) {
8771 		dberr_t	err= DB_SUCCESS;
8772 		ulint	flags	= ctx->new_table->flags;
8773 
8774 		/* Since the FTS index specific auxiliary tables has
8775 		not yet registered with "table->fts" by fts_add_index(),
8776 		we will need explicitly delete them here */
8777 		if (dict_table_has_fts_index(ctx->new_table)) {
8778 
8779 			err = innobase_drop_fts_index_table(
8780 				ctx->new_table, ctx->trx);
8781 
8782 			if (err != DB_SUCCESS) {
8783 				my_error_innodb(
8784 					err, table->s->table_name.str,
8785 					flags);
8786 				fail = true;
8787 			}
8788 		}
8789 
8790 		dict_table_close_and_drop(ctx->trx, ctx->new_table);
8791 
8792 		switch (err) {
8793 		case DB_SUCCESS:
8794 			break;
8795 		default:
8796 			my_error_innodb(err, table->s->table_name.str,
8797 					flags);
8798 			fail = true;
8799 		}
8800 	} else {
8801 		DBUG_ASSERT(!(ha_alter_info->handler_flags
8802 			      & ALTER_ADD_PK_INDEX));
8803 		DBUG_ASSERT(ctx->new_table == prebuilt->table);
8804 
8805 		/* Remove the fts table from fts_optimize_wq if
8806 		there is only one fts index exist. */
8807 		if (prebuilt->table->fts
8808 		    && innobase_get_uncommitted_fts_indexes(
8809 					prebuilt->table) == 1
8810 		    && (ib_vector_is_empty(prebuilt->table->fts->indexes)
8811 			|| ib_vector_size(prebuilt->table->fts->indexes)
8812 			   == 1)) {
8813 			row_mysql_unlock_data_dictionary(ctx->trx);
8814 			fts_optimize_remove_table(prebuilt->table);
8815 			row_mysql_lock_data_dictionary(ctx->trx);
8816 		}
8817 
8818 		innobase_rollback_sec_index(
8819 			prebuilt->table, table,
8820 			(ha_alter_info->alter_info->requested_lock
8821                          == Alter_info::ALTER_TABLE_LOCK_EXCLUSIVE),
8822 			ctx->trx, prebuilt->trx);
8823 
8824 		ctx->clean_new_vcol_index();
8825 	}
8826 
8827 	trx_commit_for_mysql(ctx->trx);
8828 	row_mysql_unlock_data_dictionary(ctx->trx);
8829 	ctx->trx->free();
8830 	ctx->trx = NULL;
8831 
8832 func_exit:
8833 #ifndef DBUG_OFF
8834 	dict_index_t* clust_index = dict_table_get_first_index(
8835 		prebuilt->table);
8836 	DBUG_ASSERT(!clust_index->online_log);
8837 	DBUG_ASSERT(dict_index_get_online_status(clust_index)
8838 		    == ONLINE_INDEX_COMPLETE);
8839 #endif /* !DBUG_OFF */
8840 
8841 	if (ctx) {
8842 		DBUG_ASSERT(ctx->prebuilt == prebuilt);
8843 
8844 		if (ctx->num_to_add_fk) {
8845 			for (ulint i = 0; i < ctx->num_to_add_fk; i++) {
8846 				dict_foreign_free(ctx->add_fk[i]);
8847 			}
8848 		}
8849 
8850 		if (ctx->num_to_drop_index) {
8851 			row_mysql_lock_data_dictionary(prebuilt->trx);
8852 
8853 			/* Clear the to_be_dropped flags
8854 			in the data dictionary cache.
8855 			The flags may already have been cleared,
8856 			in case an error was detected in
8857 			commit_inplace_alter_table(). */
8858 			for (ulint i = 0; i < ctx->num_to_drop_index; i++) {
8859 				dict_index_t*	index = ctx->drop_index[i];
8860 				DBUG_ASSERT(index->is_committed());
8861 				index->to_be_dropped = 0;
8862 			}
8863 
8864 			row_mysql_unlock_data_dictionary(prebuilt->trx);
8865 		}
8866 
8867 		if (ctx->add_vcol) {
8868 			for (ulint i = 0; i < ctx->num_to_add_vcol; i++) {
8869 				ctx->add_vcol[i].~dict_v_col_t();
8870 			}
8871 			ctx->num_to_add_vcol = 0;
8872 			ctx->add_vcol = nullptr;
8873 		}
8874 	}
8875 
8876 	/* Reset dict_col_t::ord_part for those columns fail to be indexed,
8877 	we do this by checking every existing column, if any current
8878 	index would index them */
8879 	for (ulint i = 0; i < dict_table_get_n_cols(prebuilt->table); i++) {
8880 		dict_col_t& col = prebuilt->table->cols[i];
8881 		if (!col.ord_part) {
8882 			continue;
8883 		}
8884 		if (!check_col_exists_in_indexes(prebuilt->table, i, false,
8885 						 true)) {
8886 			col.ord_part = 0;
8887 		}
8888 	}
8889 
8890 	for (ulint i = 0; i < dict_table_get_n_v_cols(prebuilt->table); i++) {
8891 		dict_col_t& col = prebuilt->table->v_cols[i].m_col;
8892 		if (!col.ord_part) {
8893 			continue;
8894 		}
8895 		if (!check_col_exists_in_indexes(prebuilt->table, i, true,
8896 						 true)) {
8897 			col.ord_part = 0;
8898 		}
8899 	}
8900 
8901 	trx_commit_for_mysql(prebuilt->trx);
8902 	MONITOR_ATOMIC_DEC(MONITOR_PENDING_ALTER_TABLE);
8903 	DBUG_RETURN(fail);
8904 }
8905 
8906 /** Drop a FOREIGN KEY constraint from the data dictionary tables.
8907 @param trx data dictionary transaction
8908 @param table_name Table name in MySQL
8909 @param foreign_id Foreign key constraint identifier
8910 @retval true Failure
8911 @retval false Success */
8912 static MY_ATTRIBUTE((nonnull, warn_unused_result))
8913 bool
innobase_drop_foreign_try(trx_t * trx,const char * table_name,const char * foreign_id)8914 innobase_drop_foreign_try(
8915 /*======================*/
8916 	trx_t*			trx,
8917 	const char*		table_name,
8918 	const char*		foreign_id)
8919 {
8920 	DBUG_ENTER("innobase_drop_foreign_try");
8921 
8922 	DBUG_ASSERT(trx_get_dict_operation(trx) == TRX_DICT_OP_INDEX);
8923 	ut_ad(trx->dict_operation_lock_mode == RW_X_LATCH);
8924 	ut_d(dict_sys.assert_locked());
8925 
8926 	/* Drop the constraint from the data dictionary. */
8927 	static const char sql[] =
8928 		"PROCEDURE DROP_FOREIGN_PROC () IS\n"
8929 		"BEGIN\n"
8930 		"DELETE FROM SYS_FOREIGN WHERE ID=:id;\n"
8931 		"DELETE FROM SYS_FOREIGN_COLS WHERE ID=:id;\n"
8932 		"END;\n";
8933 
8934 	dberr_t		error;
8935 	pars_info_t*	info;
8936 
8937 	info = pars_info_create();
8938 	pars_info_add_str_literal(info, "id", foreign_id);
8939 
8940 	trx->op_info = "dropping foreign key constraint from dictionary";
8941 	error = que_eval_sql(info, sql, FALSE, trx);
8942 	trx->op_info = "";
8943 
8944 	DBUG_EXECUTE_IF("ib_drop_foreign_error",
8945 			error = DB_OUT_OF_FILE_SPACE;);
8946 
8947 	if (error != DB_SUCCESS) {
8948 		my_error_innodb(error, table_name, 0);
8949 		trx->error_state = DB_SUCCESS;
8950 		DBUG_RETURN(true);
8951 	}
8952 
8953 	DBUG_RETURN(false);
8954 }
8955 
8956 /** Rename a column in the data dictionary tables.
8957 @param[in] ctx			ALTER TABLE context
8958 @param[in,out] trx		Data dictionary transaction
8959 @param[in] table_name		Table name in MySQL
8960 @param[in] from			old column name
8961 @param[in] to			new column name
8962 @retval true Failure
8963 @retval false Success */
8964 static MY_ATTRIBUTE((nonnull, warn_unused_result))
8965 bool
innobase_rename_column_try(const ha_innobase_inplace_ctx & ctx,trx_t * trx,const char * table_name,const char * from,const char * to)8966 innobase_rename_column_try(
8967 	const ha_innobase_inplace_ctx&	ctx,
8968 	trx_t*				trx,
8969 	const char*			table_name,
8970 	const char*			from,
8971 	const char*			to)
8972 {
8973 	dberr_t		error;
8974 	bool clust_has_prefixes = false;
8975 
8976 	DBUG_ENTER("innobase_rename_column_try");
8977 
8978 	DBUG_ASSERT(trx_get_dict_operation(trx) == TRX_DICT_OP_INDEX);
8979 	ut_ad(trx->dict_operation_lock_mode == RW_X_LATCH);
8980 	ut_d(dict_sys.assert_locked());
8981 
8982 	if (ctx.need_rebuild()) {
8983 		goto rename_foreign;
8984 	}
8985 
8986 	error = DB_SUCCESS;
8987 
8988 	trx->op_info = "renaming column in SYS_FIELDS";
8989 
8990 	for (const dict_index_t* index = dict_table_get_first_index(
8991 		     ctx.old_table);
8992 	     index != NULL;
8993 	     index = dict_table_get_next_index(index)) {
8994 
8995 		bool has_prefixes = false;
8996 		for (size_t i = 0; i < dict_index_get_n_fields(index); i++) {
8997 			if (dict_index_get_nth_field(index, i)->prefix_len) {
8998 				has_prefixes = true;
8999 				break;
9000 			}
9001 		}
9002 
9003 		for (ulint i = 0; i < dict_index_get_n_fields(index); i++) {
9004 			const dict_field_t& f = index->fields[i];
9005 			DBUG_ASSERT(!f.name == f.col->is_dropped());
9006 
9007 			if (!f.name || my_strcasecmp(system_charset_info,
9008 						     f.name, from)) {
9009 				continue;
9010 			}
9011 
9012 			pars_info_t* info = pars_info_create();
9013 			ulint pos = has_prefixes ? i << 16 | f.prefix_len : i;
9014 
9015 			pars_info_add_ull_literal(info, "indexid", index->id);
9016 			pars_info_add_int4_literal(info, "nth", pos);
9017 			pars_info_add_str_literal(info, "new", to);
9018 
9019 			error = que_eval_sql(
9020 				info,
9021 				"PROCEDURE RENAME_SYS_FIELDS_PROC () IS\n"
9022 				"BEGIN\n"
9023 				"UPDATE SYS_FIELDS SET COL_NAME=:new\n"
9024 				"WHERE INDEX_ID=:indexid\n"
9025 				"AND POS=:nth;\n"
9026 				"END;\n",
9027 				FALSE, trx);
9028 			DBUG_EXECUTE_IF("ib_rename_column_error",
9029 					error = DB_OUT_OF_FILE_SPACE;);
9030 
9031 			if (error != DB_SUCCESS) {
9032 				goto err_exit;
9033 			}
9034 
9035 			if (!has_prefixes || !clust_has_prefixes
9036 			    || f.prefix_len) {
9037 				continue;
9038 			}
9039 
9040 			/* For secondary indexes, the
9041 			has_prefixes check can be 'polluted'
9042 			by PRIMARY KEY column prefix. Try also
9043 			the simpler encoding of SYS_FIELDS.POS. */
9044 			info = pars_info_create();
9045 
9046 			pars_info_add_ull_literal(info, "indexid", index->id);
9047 			pars_info_add_int4_literal(info, "nth", i);
9048 			pars_info_add_str_literal(info, "new", to);
9049 
9050 			error = que_eval_sql(
9051 				info,
9052 				"PROCEDURE RENAME_SYS_FIELDS_PROC () IS\n"
9053 				"BEGIN\n"
9054 				"UPDATE SYS_FIELDS SET COL_NAME=:new\n"
9055 				"WHERE INDEX_ID=:indexid\n"
9056 				"AND POS=:nth;\n"
9057 				"END;\n",
9058 				FALSE, trx);
9059 
9060 			if (error != DB_SUCCESS) {
9061 				goto err_exit;
9062 			}
9063 		}
9064 
9065 		if (index == dict_table_get_first_index(ctx.old_table)) {
9066 			clust_has_prefixes = has_prefixes;
9067 		}
9068 	}
9069 
9070 	if (error != DB_SUCCESS) {
9071 err_exit:
9072 		my_error_innodb(error, table_name, 0);
9073 		trx->error_state = DB_SUCCESS;
9074 		trx->op_info = "";
9075 		DBUG_RETURN(true);
9076 	}
9077 
9078 rename_foreign:
9079 	trx->op_info = "renaming column in SYS_FOREIGN_COLS";
9080 
9081 	std::set<dict_foreign_t*> fk_evict;
9082 	bool		foreign_modified;
9083 
9084 	for (dict_foreign_set::const_iterator it = ctx.old_table->foreign_set.begin();
9085 	     it != ctx.old_table->foreign_set.end();
9086 	     ++it) {
9087 
9088 		dict_foreign_t*	foreign = *it;
9089 		foreign_modified = false;
9090 
9091 		for (unsigned i = 0; i < foreign->n_fields; i++) {
9092 			if (my_strcasecmp(system_charset_info,
9093 					  foreign->foreign_col_names[i],
9094 					  from)) {
9095 				continue;
9096 			}
9097 
9098 			/* Ignore the foreign key rename if fk info
9099 			is being dropped. */
9100 			if (innobase_dropping_foreign(
9101 				    foreign, ctx.drop_fk,
9102 				    ctx.num_to_drop_fk)) {
9103 				continue;
9104 			}
9105 
9106 			pars_info_t* info = pars_info_create();
9107 
9108 			pars_info_add_str_literal(info, "id", foreign->id);
9109 			pars_info_add_int4_literal(info, "nth", i);
9110 			pars_info_add_str_literal(info, "new", to);
9111 
9112 			error = que_eval_sql(
9113 				info,
9114 				"PROCEDURE RENAME_SYS_FOREIGN_F_PROC () IS\n"
9115 				"BEGIN\n"
9116 				"UPDATE SYS_FOREIGN_COLS\n"
9117 				"SET FOR_COL_NAME=:new\n"
9118 				"WHERE ID=:id AND POS=:nth;\n"
9119 				"END;\n",
9120 				FALSE, trx);
9121 
9122 			if (error != DB_SUCCESS) {
9123 				goto err_exit;
9124 			}
9125 			foreign_modified = true;
9126 		}
9127 
9128 		if (foreign_modified) {
9129 			fk_evict.insert(foreign);
9130 		}
9131 	}
9132 
9133 	for (dict_foreign_set::const_iterator it
9134 		= ctx.old_table->referenced_set.begin();
9135 	     it != ctx.old_table->referenced_set.end();
9136 	     ++it) {
9137 
9138 		foreign_modified = false;
9139 		dict_foreign_t*	foreign = *it;
9140 
9141 		for (unsigned i = 0; i < foreign->n_fields; i++) {
9142 			if (my_strcasecmp(system_charset_info,
9143 					  foreign->referenced_col_names[i],
9144 					  from)) {
9145 				continue;
9146 			}
9147 
9148 			pars_info_t* info = pars_info_create();
9149 
9150 			pars_info_add_str_literal(info, "id", foreign->id);
9151 			pars_info_add_int4_literal(info, "nth", i);
9152 			pars_info_add_str_literal(info, "new", to);
9153 
9154 			error = que_eval_sql(
9155 				info,
9156 				"PROCEDURE RENAME_SYS_FOREIGN_R_PROC () IS\n"
9157 				"BEGIN\n"
9158 				"UPDATE SYS_FOREIGN_COLS\n"
9159 				"SET REF_COL_NAME=:new\n"
9160 				"WHERE ID=:id AND POS=:nth;\n"
9161 				"END;\n",
9162 				FALSE, trx);
9163 
9164 			if (error != DB_SUCCESS) {
9165 				goto err_exit;
9166 			}
9167 			foreign_modified = true;
9168 		}
9169 
9170 		if (foreign_modified) {
9171 			fk_evict.insert(foreign);
9172 		}
9173 	}
9174 
9175 	/* Reload the foreign key info for instant table too. */
9176 	if (ctx.need_rebuild() || ctx.is_instant()) {
9177 		std::for_each(fk_evict.begin(), fk_evict.end(),
9178 			      dict_foreign_remove_from_cache);
9179 	}
9180 
9181 	trx->op_info = "";
9182 	DBUG_RETURN(false);
9183 }
9184 
9185 /** Rename columns in the data dictionary tables.
9186 @param ha_alter_info Data used during in-place alter.
9187 @param ctx In-place ALTER TABLE context
9188 @param table the TABLE
9189 @param trx data dictionary transaction
9190 @param table_name Table name in MySQL
9191 @retval true Failure
9192 @retval false Success */
9193 static MY_ATTRIBUTE((nonnull, warn_unused_result))
9194 bool
innobase_rename_columns_try(Alter_inplace_info * ha_alter_info,ha_innobase_inplace_ctx * ctx,const TABLE * table,trx_t * trx,const char * table_name)9195 innobase_rename_columns_try(
9196 /*========================*/
9197 	Alter_inplace_info*	ha_alter_info,
9198 	ha_innobase_inplace_ctx*ctx,
9199 	const TABLE*		table,
9200 	trx_t*			trx,
9201 	const char*		table_name)
9202 {
9203 	uint	i = 0;
9204 	ulint	num_v = 0;
9205 
9206 	DBUG_ASSERT(ctx->need_rebuild());
9207 	DBUG_ASSERT(ha_alter_info->handler_flags
9208 		    & ALTER_COLUMN_NAME);
9209 
9210 	for (Field** fp = table->field; *fp; fp++, i++) {
9211 		const bool is_virtual = !(*fp)->stored_in_db();
9212 		if (!((*fp)->flags & FIELD_IS_RENAMED)) {
9213 			goto processed_field;
9214 		}
9215 
9216 		for (const Create_field& cf :
9217 		     ha_alter_info->alter_info->create_list) {
9218 			if (cf.field == *fp) {
9219 				if (innobase_rename_column_try(
9220 					    *ctx, trx, table_name,
9221 					    cf.field->field_name.str,
9222 					    cf.field_name.str)) {
9223 					return(true);
9224 				}
9225 				goto processed_field;
9226 			}
9227 		}
9228 
9229 		ut_error;
9230 processed_field:
9231 		if (is_virtual) {
9232 			num_v++;
9233 		}
9234 
9235 		continue;
9236 	}
9237 
9238 	return(false);
9239 }
9240 
9241 /** Convert field type and length to InnoDB format */
get_type(const Field & f,uint & prtype,uint8_t & mtype,uint16_t & len)9242 static void get_type(const Field& f, uint& prtype, uint8_t& mtype,
9243                      uint16_t& len)
9244 {
9245 	mtype = get_innobase_type_from_mysql_type(&prtype, &f);
9246 	len = static_cast<uint16_t>(f.pack_length());
9247 	prtype |= f.type();
9248 	if (f.type() == MYSQL_TYPE_VARCHAR) {
9249 		auto l = static_cast<const Field_varstring&>(f).length_bytes;
9250 		len = static_cast<uint16_t>(len - l);
9251 		if (l == 2) prtype |= DATA_LONG_TRUE_VARCHAR;
9252 	}
9253 	if (!f.real_maybe_null()) prtype |= DATA_NOT_NULL;
9254 	if (f.binary()) prtype |= DATA_BINARY_TYPE;
9255 	if (f.table->versioned()) {
9256 		if (&f == f.table->field[f.table->s->vers.start_fieldno]) {
9257 			prtype |= DATA_VERS_START;
9258 		} else if (&f == f.table->field[f.table->s->vers.end_fieldno]) {
9259 			prtype |= DATA_VERS_END;
9260 		} else if (!(f.flags & VERS_UPDATE_UNVERSIONED_FLAG)) {
9261 			prtype |= DATA_VERSIONED;
9262 		}
9263 	}
9264 	if (!f.stored_in_db()) prtype |= DATA_VIRTUAL;
9265 
9266 	if (dtype_is_string_type(mtype)) {
9267 		prtype |= f.charset()->number << 16;
9268 	}
9269 }
9270 
9271 /** Enlarge a column in the data dictionary tables.
9272 @param ctx In-place ALTER TABLE context
9273 @param trx data dictionary transaction
9274 @param table_name Table name in MySQL
9275 @param pos 0-based index to user_table->cols[] or user_table->v_cols[]
9276 @param f new column
9277 @param is_v if it's a virtual column
9278 @retval true Failure
9279 @retval false Success */
9280 static MY_ATTRIBUTE((nonnull, warn_unused_result))
9281 bool
innobase_rename_or_enlarge_column_try(ha_innobase_inplace_ctx * ctx,trx_t * trx,const char * table_name,ulint pos,const Field & f,bool is_v)9282 innobase_rename_or_enlarge_column_try(
9283 	ha_innobase_inplace_ctx*ctx,
9284 	trx_t*			trx,
9285 	const char*		table_name,
9286 	ulint			pos,
9287 	const Field&		f,
9288 	bool			is_v)
9289 {
9290 	dict_col_t*	col;
9291 	dict_table_t* user_table = ctx->old_table;
9292 
9293 	DBUG_ENTER("innobase_rename_or_enlarge_column_try");
9294 	DBUG_ASSERT(!ctx->need_rebuild());
9295 
9296 	DBUG_ASSERT(trx_get_dict_operation(trx) == TRX_DICT_OP_INDEX);
9297 	ut_ad(trx->dict_operation_lock_mode == RW_X_LATCH);
9298 	ut_d(dict_sys.assert_locked());
9299 
9300 	ulint n_base;
9301 
9302 	if (is_v) {
9303 		dict_v_col_t* v_col= dict_table_get_nth_v_col(user_table, pos);
9304 		pos = dict_create_v_col_pos(v_col->v_pos, v_col->m_col.ind);
9305 		col = &v_col->m_col;
9306 		n_base = v_col->num_base;
9307 	} else {
9308 		col = dict_table_get_nth_col(user_table, pos);
9309 		n_base = 0;
9310 	}
9311 
9312 	unsigned prtype;
9313 	uint8_t mtype;
9314 	uint16_t len;
9315 	get_type(f, prtype, mtype, len);
9316 	DBUG_ASSERT(!dtype_is_string_type(col->mtype)
9317 		    || col->mbminlen == f.charset()->mbminlen);
9318 	DBUG_ASSERT(col->len <= len);
9319 
9320 #ifdef UNIV_DEBUG
9321 	ut_ad(col->mbminlen <= col->mbmaxlen);
9322 	switch (mtype) {
9323 	case DATA_MYSQL:
9324 		if (!(prtype & DATA_BINARY_TYPE) || user_table->not_redundant()
9325 		    || col->mbminlen != col->mbmaxlen) {
9326 			/* NOTE: we could allow this when !(prtype &
9327 			DATA_BINARY_TYPE) and ROW_FORMAT is not REDUNDANT and
9328 			mbminlen<mbmaxlen. That is, we treat a UTF-8 CHAR(n)
9329 			column somewhat like a VARCHAR. */
9330 			break;
9331 		}
9332 		/* fall through */
9333 	case DATA_FIXBINARY:
9334 	case DATA_CHAR:
9335 		ut_ad(col->len == len);
9336 		break;
9337 	case DATA_BINARY:
9338 	case DATA_VARCHAR:
9339 	case DATA_VARMYSQL:
9340 	case DATA_DECIMAL:
9341 	case DATA_BLOB:
9342 		break;
9343 	default:
9344 		ut_ad(!((col->prtype ^ prtype) & ~DATA_VERSIONED));
9345 		ut_ad(col->mtype == mtype);
9346 		ut_ad(col->len == len);
9347 	}
9348 #endif /* UNIV_DEBUG */
9349 
9350 	const char* col_name = col->name(*user_table);
9351 	const bool same_name = !strcmp(col_name, f.field_name.str);
9352 
9353 	if (!same_name
9354 	    && innobase_rename_column_try(*ctx, trx, table_name,
9355 					  col_name, f.field_name.str)) {
9356 		DBUG_RETURN(true);
9357 	}
9358 
9359 	if (same_name
9360 	    && col->prtype == prtype && col->mtype == mtype
9361 	    && col->len == len) {
9362 		DBUG_RETURN(false);
9363 	}
9364 
9365 	DBUG_RETURN(innodb_insert_sys_columns(user_table->id, pos,
9366 					      f.field_name.str,
9367 					      mtype, prtype, len,
9368 					      n_base, trx, true));
9369 }
9370 
9371 /** Rename or enlarge columns in the data dictionary cache
9372 as part of commit_try_norebuild().
9373 @param ha_alter_info Data used during in-place alter.
9374 @param ctx In-place ALTER TABLE context
9375 @param altered_table metadata after ALTER TABLE
9376 @param table metadata before ALTER TABLE
9377 @param trx data dictionary transaction
9378 @param table_name Table name in MySQL
9379 @retval true Failure
9380 @retval false Success */
9381 static MY_ATTRIBUTE((nonnull, warn_unused_result))
9382 bool
innobase_rename_or_enlarge_columns_try(Alter_inplace_info * ha_alter_info,ha_innobase_inplace_ctx * ctx,const TABLE * altered_table,const TABLE * table,trx_t * trx,const char * table_name)9383 innobase_rename_or_enlarge_columns_try(
9384 	Alter_inplace_info*	ha_alter_info,
9385 	ha_innobase_inplace_ctx*ctx,
9386 	const TABLE*		altered_table,
9387 	const TABLE*		table,
9388 	trx_t*			trx,
9389 	const char*		table_name)
9390 {
9391 	DBUG_ENTER("innobase_rename_or_enlarge_columns_try");
9392 
9393 	if (!(ha_alter_info->handler_flags
9394 	      & (ALTER_COLUMN_TYPE_CHANGE_BY_ENGINE
9395 		 | ALTER_COLUMN_NAME))) {
9396 		DBUG_RETURN(false);
9397 	}
9398 
9399 	ulint	i = 0;
9400 	ulint	num_v = 0;
9401 
9402 	for (Field** fp = table->field; *fp; fp++, i++) {
9403 		const bool is_v = !(*fp)->stored_in_db();
9404 		ulint idx = is_v ? num_v++ : i - num_v;
9405 
9406 		Field** af = altered_table->field;
9407 		for (const Create_field& cf :
9408 		     ha_alter_info->alter_info->create_list) {
9409 			if (cf.field == *fp) {
9410 				if (innobase_rename_or_enlarge_column_try(
9411 					    ctx, trx, table_name,
9412 					    idx, **af, is_v)) {
9413 					DBUG_RETURN(true);
9414 				}
9415 				break;
9416 			}
9417 			af++;
9418 		}
9419 	}
9420 
9421 	DBUG_RETURN(false);
9422 }
9423 
9424 /** Rename or enlarge columns in the data dictionary cache
9425 as part of commit_cache_norebuild().
9426 @param ha_alter_info Data used during in-place alter.
9427 @param altered_table metadata after ALTER TABLE
9428 @param table metadata before ALTER TABLE
9429 @param user_table InnoDB table that was being altered */
9430 static MY_ATTRIBUTE((nonnull))
9431 void
innobase_rename_or_enlarge_columns_cache(Alter_inplace_info * ha_alter_info,const TABLE * altered_table,const TABLE * table,dict_table_t * user_table)9432 innobase_rename_or_enlarge_columns_cache(
9433 /*=====================================*/
9434 	Alter_inplace_info*	ha_alter_info,
9435 	const TABLE*		altered_table,
9436 	const TABLE*		table,
9437 	dict_table_t*		user_table)
9438 {
9439 	if (!(ha_alter_info->handler_flags
9440 	      & (ALTER_COLUMN_TYPE_CHANGE_BY_ENGINE
9441 		 | ALTER_COLUMN_NAME))) {
9442 		return;
9443 	}
9444 
9445 	uint	i = 0;
9446 	ulint	num_v = 0;
9447 
9448 	for (Field** fp = table->field; *fp; fp++, i++) {
9449 		const bool is_virtual = !(*fp)->stored_in_db();
9450 
9451 		Field** af = altered_table->field;
9452 		for (Create_field& cf :
9453 		     ha_alter_info->alter_info->create_list) {
9454 			if (cf.field != *fp) {
9455 				af++;
9456 				continue;
9457 			}
9458 
9459 			ulint	col_n = is_virtual ? num_v : i - num_v;
9460 			dict_col_t *col = is_virtual
9461 				? &dict_table_get_nth_v_col(user_table, col_n)
9462 				->m_col
9463 				: dict_table_get_nth_col(user_table, col_n);
9464 			const bool is_string= dtype_is_string_type(col->mtype);
9465 			DBUG_ASSERT(col->mbminlen
9466 				    == (is_string
9467 					? (*af)->charset()->mbminlen : 0));
9468 			unsigned prtype;
9469 			uint8_t mtype;
9470 			uint16_t len;
9471 			get_type(**af, prtype, mtype, len);
9472 			DBUG_ASSERT(is_string == dtype_is_string_type(mtype));
9473 
9474 			col->prtype = prtype;
9475 			col->mtype = mtype;
9476 			col->len = len;
9477 			col->mbmaxlen = is_string
9478 				? (*af)->charset()->mbmaxlen & 7: 0;
9479 
9480 			if ((*fp)->flags & FIELD_IS_RENAMED) {
9481 				dict_mem_table_col_rename(
9482 					user_table, col_n,
9483 					cf.field->field_name.str,
9484 					(*af)->field_name.str, is_virtual);
9485 			}
9486 
9487 			break;
9488 		}
9489 
9490 		if (is_virtual) {
9491 			num_v++;
9492 		}
9493 	}
9494 }
9495 
9496 /** Set the auto-increment value of the table on commit.
9497 @param ha_alter_info Data used during in-place alter
9498 @param ctx In-place ALTER TABLE context
9499 @param altered_table MySQL table that is being altered
9500 @param old_table MySQL table as it is before the ALTER operation
9501 @return whether the operation failed (and my_error() was called) */
9502 static MY_ATTRIBUTE((nonnull))
9503 bool
commit_set_autoinc(Alter_inplace_info * ha_alter_info,ha_innobase_inplace_ctx * ctx,const TABLE * altered_table,const TABLE * old_table)9504 commit_set_autoinc(
9505 	Alter_inplace_info*	ha_alter_info,
9506 	ha_innobase_inplace_ctx*ctx,
9507 	const TABLE*		altered_table,
9508 	const TABLE*		old_table)
9509 {
9510 	DBUG_ENTER("commit_set_autoinc");
9511 
9512 	if (!altered_table->found_next_number_field) {
9513 		/* There is no AUTO_INCREMENT column in the table
9514 		after the ALTER operation. */
9515 	} else if (ctx->add_autoinc != ULINT_UNDEFINED) {
9516 		ut_ad(ctx->need_rebuild());
9517 		/* An AUTO_INCREMENT column was added. Get the last
9518 		value from the sequence, which may be based on a
9519 		supplied AUTO_INCREMENT value. */
9520 		ib_uint64_t autoinc = ctx->sequence.last();
9521 		ctx->new_table->autoinc = autoinc;
9522 		/* Bulk index creation does not update
9523 		PAGE_ROOT_AUTO_INC, so we must persist the "last used"
9524 		value here. */
9525 		btr_write_autoinc(dict_table_get_first_index(ctx->new_table),
9526 				  autoinc - 1, true);
9527 	} else if ((ha_alter_info->handler_flags
9528 		    & ALTER_CHANGE_CREATE_OPTION)
9529 		   && (ha_alter_info->create_info->used_fields
9530 		       & HA_CREATE_USED_AUTO)) {
9531 
9532 		if (!ctx->old_table->space) {
9533 			my_error(ER_TABLESPACE_DISCARDED, MYF(0),
9534 				 old_table->s->table_name.str);
9535 			DBUG_RETURN(true);
9536 		}
9537 
9538 		/* An AUTO_INCREMENT value was supplied by the user.
9539 		It must be persisted to the data file. */
9540 		const Field*	ai	= old_table->found_next_number_field;
9541 		ut_ad(!strcmp(dict_table_get_col_name(ctx->old_table,
9542 						      innodb_col_no(ai)),
9543 			      ai->field_name.str));
9544 
9545 		ib_uint64_t	autoinc
9546 			= ha_alter_info->create_info->auto_increment_value;
9547 		if (autoinc == 0) {
9548 			autoinc = 1;
9549 		}
9550 
9551 		if (autoinc >= ctx->old_table->autoinc) {
9552 			/* Persist the predecessor of the
9553 			AUTO_INCREMENT value as the last used one. */
9554 			ctx->new_table->autoinc = autoinc--;
9555 		} else {
9556 			/* Mimic ALGORITHM=COPY in the following scenario:
9557 
9558 			CREATE TABLE t (a SERIAL);
9559 			INSERT INTO t SET a=100;
9560 			ALTER TABLE t AUTO_INCREMENT = 1;
9561 			INSERT INTO t SET a=NULL;
9562 			SELECT * FROM t;
9563 
9564 			By default, ALGORITHM=INPLACE would reset the
9565 			sequence to 1, while after ALGORITHM=COPY, the
9566 			last INSERT would use a value larger than 100.
9567 
9568 			We could only search the tree to know current
9569 			max counter in the table and compare. */
9570 			const dict_col_t*	autoinc_col
9571 				= dict_table_get_nth_col(ctx->old_table,
9572 							 innodb_col_no(ai));
9573 			dict_index_t*		index
9574 				= dict_table_get_first_index(ctx->old_table);
9575 			while (index != NULL
9576 			       && index->fields[0].col != autoinc_col) {
9577 				index = dict_table_get_next_index(index);
9578 			}
9579 
9580 			ut_ad(index);
9581 
9582 			ib_uint64_t	max_in_table = index
9583 				? row_search_max_autoinc(index)
9584 				: 0;
9585 
9586 			if (autoinc <= max_in_table) {
9587 				ctx->new_table->autoinc = innobase_next_autoinc(
9588 					max_in_table, 1,
9589 					ctx->prebuilt->autoinc_increment,
9590 					ctx->prebuilt->autoinc_offset,
9591 					innobase_get_int_col_max_value(ai));
9592 				/* Persist the maximum value as the
9593 				last used one. */
9594 				autoinc = max_in_table;
9595 			} else {
9596 				/* Persist the predecessor of the
9597 				AUTO_INCREMENT value as the last used one. */
9598 				ctx->new_table->autoinc = autoinc--;
9599 			}
9600 		}
9601 
9602 		btr_write_autoinc(dict_table_get_first_index(ctx->new_table),
9603 				  autoinc, true);
9604 	} else if (ctx->need_rebuild()) {
9605 		/* No AUTO_INCREMENT value was specified.
9606 		Copy it from the old table. */
9607 		ctx->new_table->autoinc = ctx->old_table->autoinc;
9608 		/* The persistent value was already copied in
9609 		prepare_inplace_alter_table_dict() when ctx->new_table
9610 		was created. If this was a LOCK=NONE operation, the
9611 		AUTO_INCREMENT values would be updated during
9612 		row_log_table_apply(). If this was LOCK!=NONE,
9613 		the table contents could not possibly have changed
9614 		between prepare_inplace and commit_inplace. */
9615 	}
9616 
9617 	DBUG_RETURN(false);
9618 }
9619 
9620 /** Add or drop foreign key constraints to the data dictionary tables,
9621 but do not touch the data dictionary cache.
9622 @param ha_alter_info Data used during in-place alter
9623 @param ctx In-place ALTER TABLE context
9624 @param trx Data dictionary transaction
9625 @param table_name Table name in MySQL
9626 @retval true Failure
9627 @retval false Success
9628 */
9629 static MY_ATTRIBUTE((nonnull, warn_unused_result))
9630 bool
innobase_update_foreign_try(ha_innobase_inplace_ctx * ctx,trx_t * trx,const char * table_name)9631 innobase_update_foreign_try(
9632 /*========================*/
9633 	ha_innobase_inplace_ctx*ctx,
9634 	trx_t*			trx,
9635 	const char*		table_name)
9636 {
9637 	ulint	foreign_id;
9638 	ulint	i;
9639 
9640 	DBUG_ENTER("innobase_update_foreign_try");
9641 
9642 	foreign_id = dict_table_get_highest_foreign_id(ctx->new_table);
9643 
9644 	foreign_id++;
9645 
9646 	for (i = 0; i < ctx->num_to_add_fk; i++) {
9647 		dict_foreign_t*		fk = ctx->add_fk[i];
9648 
9649 		ut_ad(fk->foreign_table == ctx->new_table
9650 		      || fk->foreign_table == ctx->old_table);
9651 
9652 		dberr_t error = dict_create_add_foreign_id(
9653 			&foreign_id, ctx->old_table->name.m_name, fk);
9654 
9655 		if (error != DB_SUCCESS) {
9656 			my_error(ER_TOO_LONG_IDENT, MYF(0),
9657 				 fk->id);
9658 			DBUG_RETURN(true);
9659 		}
9660 
9661 		if (!fk->foreign_index) {
9662 			fk->foreign_index = dict_foreign_find_index(
9663 				ctx->new_table, ctx->col_names,
9664 				fk->foreign_col_names,
9665 				fk->n_fields, fk->referenced_index, TRUE,
9666 				fk->type
9667 				& (DICT_FOREIGN_ON_DELETE_SET_NULL
9668 					| DICT_FOREIGN_ON_UPDATE_SET_NULL),
9669 				NULL, NULL, NULL);
9670 			if (!fk->foreign_index) {
9671 				my_error(ER_FK_INCORRECT_OPTION,
9672 					 MYF(0), table_name, fk->id);
9673 				DBUG_RETURN(true);
9674 			}
9675 		}
9676 
9677 		/* The fk->foreign_col_names[] uses renamed column
9678 		names, while the columns in ctx->old_table have not
9679 		been renamed yet. */
9680 		error = dict_create_add_foreign_to_dictionary(
9681 			ctx->old_table->name.m_name, fk, trx);
9682 
9683 		DBUG_EXECUTE_IF(
9684 			"innodb_test_cannot_add_fk_system",
9685 			error = DB_ERROR;);
9686 
9687 		if (error != DB_SUCCESS) {
9688 			my_error(ER_FK_FAIL_ADD_SYSTEM, MYF(0),
9689 				 fk->id);
9690 			DBUG_RETURN(true);
9691 		}
9692 	}
9693 
9694 	for (i = 0; i < ctx->num_to_drop_fk; i++) {
9695 		dict_foreign_t* fk = ctx->drop_fk[i];
9696 
9697 		DBUG_ASSERT(fk->foreign_table == ctx->old_table);
9698 
9699 		if (innobase_drop_foreign_try(trx, table_name, fk->id)) {
9700 			DBUG_RETURN(true);
9701 		}
9702 	}
9703 
9704 	DBUG_RETURN(false);
9705 }
9706 
9707 /** Update the foreign key constraint definitions in the data dictionary cache
9708 after the changes to data dictionary tables were committed.
9709 @param ctx	In-place ALTER TABLE context
9710 @param user_thd	MySQL connection
9711 @return		InnoDB error code (should always be DB_SUCCESS) */
9712 static MY_ATTRIBUTE((nonnull, warn_unused_result))
9713 dberr_t
innobase_update_foreign_cache(ha_innobase_inplace_ctx * ctx,THD * user_thd)9714 innobase_update_foreign_cache(
9715 /*==========================*/
9716 	ha_innobase_inplace_ctx*	ctx,
9717 	THD*				user_thd)
9718 {
9719 	dict_table_t*	user_table;
9720 	dberr_t		err = DB_SUCCESS;
9721 
9722 	DBUG_ENTER("innobase_update_foreign_cache");
9723 
9724 	ut_ad(mutex_own(&dict_sys.mutex));
9725 
9726 	user_table = ctx->old_table;
9727 
9728 	/* Discard the added foreign keys, because we will
9729 	load them from the data dictionary. */
9730 	for (ulint i = 0; i < ctx->num_to_add_fk; i++) {
9731 		dict_foreign_t*	fk = ctx->add_fk[i];
9732 		dict_foreign_free(fk);
9733 	}
9734 
9735 	if (ctx->need_rebuild()) {
9736 		/* The rebuilt table is already using the renamed
9737 		column names. No need to pass col_names or to drop
9738 		constraints from the data dictionary cache. */
9739 		DBUG_ASSERT(!ctx->col_names);
9740 		DBUG_ASSERT(user_table->foreign_set.empty());
9741 		DBUG_ASSERT(user_table->referenced_set.empty());
9742 		user_table = ctx->new_table;
9743 	} else {
9744 		/* Drop the foreign key constraints if the
9745 		table was not rebuilt. If the table is rebuilt,
9746 		there would not be any foreign key contraints for
9747 		it yet in the data dictionary cache. */
9748 		for (ulint i = 0; i < ctx->num_to_drop_fk; i++) {
9749 			dict_foreign_t* fk = ctx->drop_fk[i];
9750 			dict_foreign_remove_from_cache(fk);
9751 		}
9752 	}
9753 
9754 	/* Load the old or added foreign keys from the data dictionary
9755 	and prevent the table from being evicted from the data
9756 	dictionary cache (work around the lack of WL#6049). */
9757 	dict_names_t	fk_tables;
9758 
9759 	err = dict_load_foreigns(user_table->name.m_name,
9760 				 ctx->col_names, false, true,
9761 				 DICT_ERR_IGNORE_NONE,
9762 				 fk_tables);
9763 
9764 	if (err == DB_CANNOT_ADD_CONSTRAINT) {
9765 		fk_tables.clear();
9766 
9767 		/* It is possible there are existing foreign key are
9768 		loaded with "foreign_key checks" off,
9769 		so let's retry the loading with charset_check is off */
9770 		err = dict_load_foreigns(user_table->name.m_name,
9771 					 ctx->col_names, false, false,
9772 					 DICT_ERR_IGNORE_NONE,
9773 					 fk_tables);
9774 
9775 		/* The load with "charset_check" off is successful, warn
9776 		the user that the foreign key has loaded with mis-matched
9777 		charset */
9778 		if (err == DB_SUCCESS) {
9779 			push_warning_printf(
9780 				user_thd,
9781 				Sql_condition::WARN_LEVEL_WARN,
9782 				ER_ALTER_INFO,
9783 				"Foreign key constraints for table '%s'"
9784 				" are loaded with charset check off",
9785 				user_table->name.m_name);
9786 		}
9787 	}
9788 
9789 	/* For complete loading of foreign keys, all associated tables must
9790 	also be loaded. */
9791 	while (err == DB_SUCCESS && !fk_tables.empty()) {
9792 		dict_table_t*	table = dict_load_table(
9793 			fk_tables.front(), DICT_ERR_IGNORE_NONE);
9794 
9795 		if (table == NULL) {
9796 			err = DB_TABLE_NOT_FOUND;
9797 			ib::error()
9798 				<< "Failed to load table '"
9799 				<< table_name_t(const_cast<char*>
9800 						(fk_tables.front()))
9801 				<< "' which has a foreign key constraint with"
9802 				<< " table '" << user_table->name << "'.";
9803 			break;
9804 		}
9805 
9806 		fk_tables.pop_front();
9807 	}
9808 
9809 	DBUG_RETURN(err);
9810 }
9811 
9812 /** Changes SYS_COLUMNS.PRTYPE for one column.
9813 @param[in,out]	trx	transaction
9814 @param[in]	table_name	table name
9815 @param[in]	tableid	table ID as in SYS_TABLES
9816 @param[in]	pos	column position
9817 @param[in]	prtype	new precise type
9818 @return		boolean flag
9819 @retval	true	on failure
9820 @retval false	on success */
9821 static
9822 bool
vers_change_field_try(trx_t * trx,const char * table_name,const table_id_t tableid,const ulint pos,const ulint prtype)9823 vers_change_field_try(
9824 	trx_t* trx,
9825 	const char* table_name,
9826 	const table_id_t tableid,
9827 	const ulint pos,
9828 	const ulint prtype)
9829 {
9830 	DBUG_ENTER("vers_change_field_try");
9831 
9832 	pars_info_t* info = pars_info_create();
9833 
9834 	pars_info_add_int4_literal(info, "prtype", prtype);
9835 	pars_info_add_ull_literal(info,"tableid", tableid);
9836 	pars_info_add_int4_literal(info, "pos", pos);
9837 
9838 	dberr_t error = que_eval_sql(info,
9839 				     "PROCEDURE CHANGE_COLUMN_MTYPE () IS\n"
9840 				     "BEGIN\n"
9841 				     "UPDATE SYS_COLUMNS SET PRTYPE=:prtype\n"
9842 				     "WHERE TABLE_ID=:tableid AND POS=:pos;\n"
9843 				     "END;\n",
9844 				     false, trx);
9845 
9846 	if (error != DB_SUCCESS) {
9847 		my_error_innodb(error, table_name, 0);
9848 		trx->error_state = DB_SUCCESS;
9849 		trx->op_info = "";
9850 		DBUG_RETURN(true);
9851 	}
9852 
9853 	DBUG_RETURN(false);
9854 }
9855 
9856 /** Changes fields WITH/WITHOUT SYSTEM VERSIONING property in SYS_COLUMNS.
9857 @param[in]	ha_alter_info	alter info
9858 @param[in]	ctx	alter inplace context
9859 @param[in]	trx	transaction
9860 @param[in]	table	old table
9861 @return		boolean flag
9862 @retval	true	on failure
9863 @retval false	on success */
9864 static
9865 bool
vers_change_fields_try(const Alter_inplace_info * ha_alter_info,const ha_innobase_inplace_ctx * ctx,trx_t * trx,const TABLE * table)9866 vers_change_fields_try(
9867 	const Alter_inplace_info* ha_alter_info,
9868 	const ha_innobase_inplace_ctx* ctx,
9869 	trx_t* trx,
9870 	const TABLE* table)
9871 {
9872 	DBUG_ENTER("vers_change_fields_try");
9873 
9874 	DBUG_ASSERT(ha_alter_info);
9875 	DBUG_ASSERT(ctx);
9876 
9877 	for (const Create_field& create_field : ha_alter_info->alter_info->create_list) {
9878 		if (!create_field.field) {
9879 			continue;
9880 		}
9881 		if (create_field.versioning
9882 		    == Column_definition::VERSIONING_NOT_SET) {
9883 			continue;
9884 		}
9885 
9886 		const dict_table_t* new_table = ctx->new_table;
9887 		const uint pos = innodb_col_no(create_field.field);
9888 		const dict_col_t* col = dict_table_get_nth_col(new_table, pos);
9889 
9890 		DBUG_ASSERT(!col->vers_sys_start());
9891 		DBUG_ASSERT(!col->vers_sys_end());
9892 
9893 		ulint new_prtype
9894 		    = create_field.versioning
9895 			      == Column_definition::WITHOUT_VERSIONING
9896 			  ? col->prtype & ~DATA_VERSIONED
9897 			  : col->prtype | DATA_VERSIONED;
9898 
9899 		if (vers_change_field_try(trx, table->s->table_name.str,
9900 					  new_table->id, pos,
9901 					  new_prtype)) {
9902 			DBUG_RETURN(true);
9903 		}
9904 	}
9905 
9906 	DBUG_RETURN(false);
9907 }
9908 
9909 /** Changes WITH/WITHOUT SYSTEM VERSIONING for fields
9910 in the data dictionary cache.
9911 @param ha_alter_info Data used during in-place alter
9912 @param ctx In-place ALTER TABLE context
9913 @param table MySQL table as it is before the ALTER operation */
9914 static
9915 void
vers_change_fields_cache(Alter_inplace_info * ha_alter_info,const ha_innobase_inplace_ctx * ctx,const TABLE * table)9916 vers_change_fields_cache(
9917 	Alter_inplace_info*		ha_alter_info,
9918 	const ha_innobase_inplace_ctx*	ctx,
9919 	const TABLE*			table)
9920 {
9921 	DBUG_ENTER("vers_change_fields_cache");
9922 
9923 	DBUG_ASSERT(ha_alter_info);
9924 	DBUG_ASSERT(ctx);
9925 	DBUG_ASSERT(ha_alter_info->handler_flags & ALTER_COLUMN_UNVERSIONED);
9926 
9927 	for (const Create_field& create_field :
9928 	     ha_alter_info->alter_info->create_list) {
9929 		if (!create_field.field || create_field.field->vcol_info) {
9930 			continue;
9931 		}
9932 		dict_col_t* col = dict_table_get_nth_col(
9933 		    ctx->new_table, innodb_col_no(create_field.field));
9934 
9935 		if (create_field.versioning
9936 		    == Column_definition::WITHOUT_VERSIONING) {
9937 
9938 			DBUG_ASSERT(!col->vers_sys_start());
9939 			DBUG_ASSERT(!col->vers_sys_end());
9940 			col->prtype &= ~DATA_VERSIONED;
9941 		} else if (create_field.versioning
9942 			   == Column_definition::WITH_VERSIONING) {
9943 
9944 			DBUG_ASSERT(!col->vers_sys_start());
9945 			DBUG_ASSERT(!col->vers_sys_end());
9946 			col->prtype |= DATA_VERSIONED;
9947 		}
9948 	}
9949 
9950 	DBUG_VOID_RETURN;
9951 }
9952 
9953 /** Commit the changes made during prepare_inplace_alter_table()
9954 and inplace_alter_table() inside the data dictionary tables,
9955 when rebuilding the table.
9956 @param ha_alter_info Data used during in-place alter
9957 @param ctx In-place ALTER TABLE context
9958 @param altered_table MySQL table that is being altered
9959 @param old_table MySQL table as it is before the ALTER operation
9960 @param trx Data dictionary transaction
9961 @param table_name Table name in MySQL
9962 @retval true Failure
9963 @retval false Success
9964 */
9965 inline MY_ATTRIBUTE((nonnull, warn_unused_result))
9966 bool
commit_try_rebuild(Alter_inplace_info * ha_alter_info,ha_innobase_inplace_ctx * ctx,TABLE * altered_table,const TABLE * old_table,trx_t * trx,const char * table_name)9967 commit_try_rebuild(
9968 /*===============*/
9969 	Alter_inplace_info*	ha_alter_info,
9970 	ha_innobase_inplace_ctx*ctx,
9971 	TABLE*			altered_table,
9972 	const TABLE*		old_table,
9973 	trx_t*			trx,
9974 	const char*		table_name)
9975 {
9976 	dict_table_t*	rebuilt_table	= ctx->new_table;
9977 	dict_table_t*	user_table	= ctx->old_table;
9978 
9979 	DBUG_ENTER("commit_try_rebuild");
9980 	DBUG_ASSERT(ctx->need_rebuild());
9981 	DBUG_ASSERT(trx->dict_operation_lock_mode == RW_X_LATCH);
9982 	DBUG_ASSERT(!(ha_alter_info->handler_flags
9983 		      & ALTER_DROP_FOREIGN_KEY)
9984 		    || ctx->num_to_drop_fk > 0);
9985 	DBUG_ASSERT(ctx->num_to_drop_fk
9986 		    <= ha_alter_info->alter_info->drop_list.elements);
9987 
9988 	for (dict_index_t* index = dict_table_get_first_index(rebuilt_table);
9989 	     index;
9990 	     index = dict_table_get_next_index(index)) {
9991 		DBUG_ASSERT(dict_index_get_online_status(index)
9992 			    == ONLINE_INDEX_COMPLETE);
9993 		DBUG_ASSERT(index->is_committed());
9994 		if (index->is_corrupted()) {
9995 			my_error(ER_INDEX_CORRUPT, MYF(0), index->name());
9996 			DBUG_RETURN(true);
9997 		}
9998 	}
9999 
10000 	if (innobase_update_foreign_try(ctx, trx, table_name)) {
10001 		DBUG_RETURN(true);
10002 	}
10003 
10004 	dberr_t	error;
10005 
10006 	/* Clear the to_be_dropped flag in the data dictionary cache
10007 	of user_table. */
10008 	for (ulint i = 0; i < ctx->num_to_drop_index; i++) {
10009 		dict_index_t*	index = ctx->drop_index[i];
10010 		DBUG_ASSERT(index->table == user_table);
10011 		DBUG_ASSERT(index->is_committed());
10012 		DBUG_ASSERT(index->to_be_dropped);
10013 		index->to_be_dropped = 0;
10014 	}
10015 
10016 	if ((ha_alter_info->handler_flags
10017 	     & ALTER_COLUMN_NAME)
10018 	    && innobase_rename_columns_try(ha_alter_info, ctx, old_table,
10019 					   trx, table_name)) {
10020 		DBUG_RETURN(true);
10021 	}
10022 
10023 	DBUG_EXECUTE_IF("ib_ddl_crash_before_rename", DBUG_SUICIDE(););
10024 
10025 	/* The new table must inherit the flag from the
10026 	"parent" table. */
10027 	if (!user_table->space) {
10028 		rebuilt_table->file_unreadable = true;
10029 		rebuilt_table->flags2 |= DICT_TF2_DISCARDED;
10030 	}
10031 
10032 	/* We can now rename the old table as a temporary table,
10033 	rename the new temporary table as the old table and drop the
10034 	old table. */
10035 	char* old_name= mem_heap_strdup(ctx->heap, user_table->name.m_name);
10036 
10037 	error = row_rename_table_for_mysql(user_table->name.m_name,
10038 					   ctx->tmp_name, trx, false, false);
10039 	if (error == DB_SUCCESS) {
10040 		error = row_rename_table_for_mysql(rebuilt_table->name.m_name,
10041 						   old_name, trx,
10042 						   false, false);
10043 	}
10044 
10045 	/* We must be still holding a table handle. */
10046 	DBUG_ASSERT(user_table->get_ref_count() == 1);
10047 
10048 	DBUG_EXECUTE_IF("ib_ddl_crash_after_rename", DBUG_SUICIDE(););
10049 	DBUG_EXECUTE_IF("ib_rebuild_cannot_rename", error = DB_ERROR;);
10050 
10051 	switch (error) {
10052 	case DB_SUCCESS:
10053 		DBUG_RETURN(false);
10054 	case DB_TABLESPACE_EXISTS:
10055 		ut_a(rebuilt_table->get_ref_count() == 1);
10056 		my_error(ER_TABLESPACE_EXISTS, MYF(0), ctx->tmp_name);
10057 		DBUG_RETURN(true);
10058 	case DB_DUPLICATE_KEY:
10059 		ut_a(rebuilt_table->get_ref_count() == 1);
10060 		my_error(ER_TABLE_EXISTS_ERROR, MYF(0), ctx->tmp_name);
10061 		DBUG_RETURN(true);
10062 	default:
10063 		my_error_innodb(error, table_name, user_table->flags);
10064 		DBUG_RETURN(true);
10065 	}
10066 }
10067 
10068 /** Rename indexes in dictionary.
10069 @param[in]	ctx		alter info context
10070 @param[in]	ha_alter_info	Operation used during inplace alter
10071 @param[out]	trx		transaction to change the index name
10072 				in dictionary
10073 @return true if it failed to rename
10074 @return false if it is success. */
10075 static
10076 bool
rename_indexes_try(const ha_innobase_inplace_ctx * ctx,const Alter_inplace_info * ha_alter_info,trx_t * trx)10077 rename_indexes_try(
10078 	const ha_innobase_inplace_ctx*	ctx,
10079 	const Alter_inplace_info*	ha_alter_info,
10080 	trx_t*				trx)
10081 {
10082 	DBUG_ASSERT(ha_alter_info->handler_flags & ALTER_RENAME_INDEX);
10083 
10084 	for (const Alter_inplace_info::Rename_key_pair& pair :
10085 	     ha_alter_info->rename_keys) {
10086 		dict_index_t* index = dict_table_get_index_on_name(
10087 		    ctx->old_table, pair.old_key->name.str);
10088 		// This was checked previously in
10089 		// ha_innobase::prepare_inplace_alter_table()
10090 		ut_ad(index);
10091 
10092 		if (rename_index_try(index, pair.new_key->name.str, trx)) {
10093 			return true;
10094 		}
10095 	}
10096 
10097 	return false;
10098 }
10099 
10100 /** Set of column numbers */
10101 typedef std::set<ulint, std::less<ulint>, ut_allocator<ulint> >	col_set;
10102 
10103 /** Collect (not instantly dropped) columns from dropped indexes
10104 @param[in]	ctx		In-place ALTER TABLE context
10105 @param[in, out]	drop_col_list	list which will be set, containing columns
10106 				which is part of index being dropped
10107 @param[in, out]	drop_v_col_list	list which will be set, containing
10108 				virtual columns which is part of index
10109 				being dropped */
10110 static
10111 void
collect_columns_from_dropped_indexes(const ha_innobase_inplace_ctx * ctx,col_set & drop_col_list,col_set & drop_v_col_list)10112 collect_columns_from_dropped_indexes(
10113 	const ha_innobase_inplace_ctx*	ctx,
10114 	col_set&			drop_col_list,
10115 	col_set&			drop_v_col_list)
10116 {
10117 	for (ulint index_count = 0; index_count < ctx->num_to_drop_index;
10118 	     index_count++) {
10119 		const dict_index_t*	index = ctx->drop_index[index_count];
10120 
10121 		for (ulint col = 0; col < index->n_user_defined_cols; col++) {
10122 			const dict_col_t*	idx_col
10123 				= dict_index_get_nth_col(index, col);
10124 
10125 			if (idx_col->is_virtual()) {
10126 				const dict_v_col_t*	v_col
10127 					= reinterpret_cast<
10128 						const dict_v_col_t*>(idx_col);
10129 				drop_v_col_list.insert(v_col->v_pos);
10130 
10131 			} else {
10132 				ulint	col_no = dict_col_get_no(idx_col);
10133 				if (ctx->col_map
10134 				    && ctx->col_map[col_no]
10135 					   == ULINT_UNDEFINED) {
10136 					// this column was instantly dropped
10137 					continue;
10138 				}
10139 				drop_col_list.insert(col_no);
10140 			}
10141 		}
10142 	}
10143 }
10144 
10145 /** Change PAGE_COMPRESSED to ON or change the PAGE_COMPRESSION_LEVEL.
10146 @param[in]	level		PAGE_COMPRESSION_LEVEL
10147 @param[in]	table		table before the change
10148 @param[in,out]	trx		data dictionary transaction
10149 @param[in]	table_name	table name in MariaDB
10150 @return	whether the operation succeeded */
10151 MY_ATTRIBUTE((nonnull, warn_unused_result))
10152 static
10153 bool
innobase_page_compression_try(uint level,const dict_table_t * table,trx_t * trx,const char * table_name)10154 innobase_page_compression_try(
10155 	uint			level,
10156 	const dict_table_t*	table,
10157 	trx_t*			trx,
10158 	const char*		table_name)
10159 {
10160 	DBUG_ENTER("innobase_page_compression_try");
10161 	DBUG_ASSERT(level >= 1);
10162 	DBUG_ASSERT(level <= 9);
10163 
10164 	unsigned flags = table->flags
10165 		& ~(0xFU << DICT_TF_POS_PAGE_COMPRESSION_LEVEL);
10166 	flags |= 1U << DICT_TF_POS_PAGE_COMPRESSION
10167 		| level << DICT_TF_POS_PAGE_COMPRESSION_LEVEL;
10168 
10169 	if (table->flags == flags) {
10170 		DBUG_RETURN(false);
10171 	}
10172 
10173 	pars_info_t* info = pars_info_create();
10174 
10175 	pars_info_add_ull_literal(info, "id", table->id);
10176 	pars_info_add_int4_literal(info, "type",
10177 				   dict_tf_to_sys_tables_type(flags));
10178 
10179 	dberr_t error = que_eval_sql(info,
10180 				     "PROCEDURE CHANGE_COMPRESSION () IS\n"
10181 				     "BEGIN\n"
10182 				     "UPDATE SYS_TABLES SET TYPE=:type\n"
10183 				     "WHERE ID=:id;\n"
10184 				     "END;\n",
10185 				     false, trx);
10186 
10187 	if (error != DB_SUCCESS) {
10188 		my_error_innodb(error, table_name, 0);
10189 		trx->error_state = DB_SUCCESS;
10190 		trx->op_info = "";
10191 		DBUG_RETURN(true);
10192 	}
10193 
10194 	DBUG_RETURN(false);
10195 }
10196 
10197 static
10198 void
dict_stats_try_drop_table(THD * thd,const table_name_t & name,const LEX_CSTRING & table_name)10199 dict_stats_try_drop_table(THD *thd, const table_name_t &name,
10200                           const LEX_CSTRING &table_name)
10201 {
10202   char errstr[1024];
10203   if (dict_stats_drop_table(name.m_name, errstr, sizeof(errstr)) != DB_SUCCESS)
10204   {
10205     push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_ALTER_INFO,
10206                         "Deleting persistent statistics"
10207                         " for table '%s' in InnoDB failed: %s",
10208                         table_name.str,
10209                         errstr);
10210   }
10211 }
10212 
10213 /** Evict the table from cache and reopen it. Drop outdated statistics.
10214 @param thd           mariadb THD entity
10215 @param table         innodb table
10216 @param table_name    user-friendly table name for errors
10217 @param ctx           ALTER TABLE context
10218 @return newly opened table */
innobase_reload_table(THD * thd,dict_table_t * table,const LEX_CSTRING & table_name,ha_innobase_inplace_ctx & ctx)10219 static dict_table_t *innobase_reload_table(THD *thd, dict_table_t *table,
10220                                            const LEX_CSTRING &table_name,
10221                                            ha_innobase_inplace_ctx &ctx)
10222 {
10223   char *tb_name= strdup(table->name.m_name);
10224   dict_table_close(table, true, false);
10225 
10226   if (ctx.is_instant())
10227   {
10228     for (auto i = ctx.old_n_v_cols; i--; )
10229     {
10230       ctx.old_v_cols[i].~dict_v_col_t();
10231       const_cast<unsigned&>(ctx.old_n_v_cols) = 0;
10232     }
10233   }
10234 
10235   dict_sys.remove(table);
10236   table= dict_table_open_on_name(tb_name, TRUE, TRUE,
10237                                  DICT_ERR_IGNORE_FK_NOKEY);
10238 
10239   /* Drop outdated table stats. */
10240   dict_stats_try_drop_table(thd, table->name, table_name);
10241   free(tb_name);
10242   return table;
10243 }
10244 
10245 /** Commit the changes made during prepare_inplace_alter_table()
10246 and inplace_alter_table() inside the data dictionary tables,
10247 when not rebuilding the table.
10248 @param ha_alter_info Data used during in-place alter
10249 @param ctx In-place ALTER TABLE context
10250 @param old_table MySQL table as it is before the ALTER operation
10251 @param trx Data dictionary transaction
10252 @param table_name Table name in MySQL
10253 @retval true Failure
10254 @retval false Success
10255 */
10256 inline MY_ATTRIBUTE((nonnull, warn_unused_result))
10257 bool
commit_try_norebuild(Alter_inplace_info * ha_alter_info,ha_innobase_inplace_ctx * ctx,TABLE * altered_table,const TABLE * old_table,trx_t * trx,const char * table_name)10258 commit_try_norebuild(
10259 /*=================*/
10260 	Alter_inplace_info*	ha_alter_info,
10261 	ha_innobase_inplace_ctx*ctx,
10262 	TABLE*			altered_table,
10263 	const TABLE*		old_table,
10264 	trx_t*			trx,
10265 	const char*		table_name)
10266 {
10267 	DBUG_ENTER("commit_try_norebuild");
10268 	DBUG_ASSERT(!ctx->need_rebuild());
10269 	DBUG_ASSERT(trx->dict_operation_lock_mode == RW_X_LATCH);
10270 	DBUG_ASSERT(!(ha_alter_info->handler_flags
10271 		      & ALTER_DROP_FOREIGN_KEY)
10272 		    || ctx->num_to_drop_fk > 0);
10273 	DBUG_ASSERT(ctx->num_to_drop_fk
10274 		    <= ha_alter_info->alter_info->drop_list.elements
10275 		    || ctx->num_to_drop_vcol
10276 		       == ha_alter_info->alter_info->drop_list.elements);
10277 
10278 	if (ctx->page_compression_level
10279 	    && innobase_page_compression_try(ctx->page_compression_level,
10280 					     ctx->new_table, trx,
10281 					     table_name)) {
10282 		DBUG_RETURN(true);
10283 	}
10284 
10285 	for (ulint i = 0; i < ctx->num_to_add_index; i++) {
10286 		dict_index_t*	index = ctx->add_index[i];
10287 		DBUG_ASSERT(dict_index_get_online_status(index)
10288 			    == ONLINE_INDEX_COMPLETE);
10289 		DBUG_ASSERT(!index->is_committed());
10290 		if (index->is_corrupted()) {
10291 			/* Report a duplicate key
10292 			error for the index that was
10293 			flagged corrupted, most likely
10294 			because a duplicate value was
10295 			inserted (directly or by
10296 			rollback) after
10297 			ha_innobase::inplace_alter_table()
10298 			completed.
10299 			TODO: report this as a corruption
10300 			with a detailed reason once
10301 			WL#6379 has been implemented. */
10302 			my_error(ER_DUP_UNKNOWN_IN_INDEX,
10303 				 MYF(0), index->name());
10304 			DBUG_RETURN(true);
10305 		}
10306 	}
10307 
10308 	if (innobase_update_foreign_try(ctx, trx, table_name)) {
10309 		DBUG_RETURN(true);
10310 	}
10311 
10312 	if ((ha_alter_info->handler_flags & ALTER_COLUMN_UNVERSIONED)
10313 	    && vers_change_fields_try(ha_alter_info, ctx, trx, old_table)) {
10314 		DBUG_RETURN(true);
10315 	}
10316 
10317 	dberr_t	error;
10318 
10319 	/* We altered the table in place. Mark the indexes as committed. */
10320 	for (ulint i = 0; i < ctx->num_to_add_index; i++) {
10321 		dict_index_t*	index = ctx->add_index[i];
10322 		DBUG_ASSERT(dict_index_get_online_status(index)
10323 			    == ONLINE_INDEX_COMPLETE);
10324 		DBUG_ASSERT(!index->is_committed());
10325 		error = row_merge_rename_index_to_add(
10326 			trx, ctx->new_table->id, index->id);
10327 		switch (error) {
10328 		case DB_SUCCESS:
10329 			break;
10330 		case DB_TOO_MANY_CONCURRENT_TRXS:
10331 			/* If we wrote some undo log here, then the
10332 			persistent data dictionary for this table may
10333 			probably be corrupted. This is because a
10334 			'trigger' on SYS_INDEXES could already have invoked
10335 			btr_free_if_exists(), which cannot be rolled back. */
10336 			DBUG_ASSERT(trx->undo_no == 0);
10337 			my_error(ER_TOO_MANY_CONCURRENT_TRXS, MYF(0));
10338 			DBUG_RETURN(true);
10339 		default:
10340 			sql_print_error(
10341 				"InnoDB: rename index to add: %lu\n",
10342 				(ulong) error);
10343 			DBUG_ASSERT(0);
10344 			my_error(ER_INTERNAL_ERROR, MYF(0),
10345 				 "rename index to add");
10346 			DBUG_RETURN(true);
10347 		}
10348 	}
10349 
10350 	/* Drop any indexes that were requested to be dropped.
10351 	Flag them in the data dictionary first. */
10352 
10353 	for (ulint i = 0; i < ctx->num_to_drop_index; i++) {
10354 		dict_index_t*	index = ctx->drop_index[i];
10355 		DBUG_ASSERT(index->is_committed());
10356 		DBUG_ASSERT(index->table == ctx->new_table);
10357 		DBUG_ASSERT(index->to_be_dropped);
10358 
10359 		error = row_merge_rename_index_to_drop(
10360 			trx, index->table->id, index->id);
10361 		if (error != DB_SUCCESS) {
10362 			sql_print_error(
10363 				"InnoDB: rename index to drop: %lu\n",
10364 				(ulong) error);
10365 			DBUG_ASSERT(0);
10366 			my_error(ER_INTERNAL_ERROR, MYF(0),
10367 				 "rename index to drop");
10368 			DBUG_RETURN(true);
10369 		}
10370 	}
10371 
10372 	if (innobase_rename_or_enlarge_columns_try(ha_alter_info, ctx,
10373 						   altered_table, old_table,
10374 						   trx, table_name)) {
10375 		DBUG_RETURN(true);
10376 	}
10377 
10378 	if ((ha_alter_info->handler_flags & ALTER_RENAME_INDEX)
10379 	    && rename_indexes_try(ctx, ha_alter_info, trx)) {
10380 		DBUG_RETURN(true);
10381 	}
10382 
10383 	if (ctx->is_instant()) {
10384 		DBUG_RETURN(innobase_instant_try(ha_alter_info, ctx,
10385 						 altered_table, old_table,
10386 						 trx));
10387 	}
10388 
10389 	if (ha_alter_info->handler_flags
10390 	    & (ALTER_DROP_VIRTUAL_COLUMN | ALTER_ADD_VIRTUAL_COLUMN)) {
10391 		if ((ha_alter_info->handler_flags & ALTER_DROP_VIRTUAL_COLUMN)
10392 		    && innobase_drop_virtual_try(ha_alter_info, ctx->old_table,
10393 						 trx)) {
10394 			DBUG_RETURN(true);
10395 		}
10396 
10397 		if ((ha_alter_info->handler_flags & ALTER_ADD_VIRTUAL_COLUMN)
10398 		    && innobase_add_virtual_try(ha_alter_info, ctx->old_table,
10399 						trx)) {
10400 			DBUG_RETURN(true);
10401 		}
10402 
10403 		unsigned n_col = ctx->old_table->n_cols
10404 			- DATA_N_SYS_COLS;
10405 		unsigned n_v_col = ctx->old_table->n_v_cols
10406 			+ ctx->num_to_add_vcol - ctx->num_to_drop_vcol;
10407 
10408 		if (innodb_update_cols(
10409 			    ctx->old_table,
10410 			    dict_table_encode_n_col(n_col, n_v_col)
10411 			    | unsigned(ctx->old_table->flags & DICT_TF_COMPACT)
10412 			    << 31, trx)) {
10413 			DBUG_RETURN(true);
10414 		}
10415 	}
10416 
10417 	DBUG_RETURN(false);
10418 }
10419 
10420 /** Commit the changes to the data dictionary cache
10421 after a successful commit_try_norebuild() call.
10422 @param ha_alter_info algorithm=inplace context
10423 @param ctx In-place ALTER TABLE context for the current partition
10424 @param altered_table the TABLE after the ALTER
10425 @param table the TABLE before the ALTER
10426 @param trx Data dictionary transaction
10427 (will be started and committed, for DROP INDEX)
10428 @return whether all replacements were found for dropped indexes */
10429 inline MY_ATTRIBUTE((nonnull))
10430 bool
commit_cache_norebuild(Alter_inplace_info * ha_alter_info,ha_innobase_inplace_ctx * ctx,const TABLE * altered_table,const TABLE * table,trx_t * trx)10431 commit_cache_norebuild(
10432 /*===================*/
10433 	Alter_inplace_info*	ha_alter_info,
10434 	ha_innobase_inplace_ctx*ctx,
10435 	const TABLE*		altered_table,
10436 	const TABLE*		table,
10437 	trx_t*			trx)
10438 {
10439 	DBUG_ENTER("commit_cache_norebuild");
10440 	DBUG_ASSERT(!ctx->need_rebuild());
10441 	DBUG_ASSERT(ctx->new_table->space != fil_system.temp_space);
10442 	DBUG_ASSERT(!ctx->new_table->is_temporary());
10443 
10444 	bool found = true;
10445 
10446 	if (ctx->page_compression_level) {
10447 		DBUG_ASSERT(ctx->new_table->space != fil_system.sys_space);
10448 #if defined __GNUC__ && !defined __clang__ && __GNUC__ < 6
10449 # pragma GCC diagnostic push
10450 # pragma GCC diagnostic ignored "-Wconversion" /* GCC 4 and 5 need this here */
10451 #endif
10452 		ctx->new_table->flags
10453 			= static_cast<uint16_t>(
10454 				(ctx->new_table->flags
10455 				 & ~(0xFU
10456 				     << DICT_TF_POS_PAGE_COMPRESSION_LEVEL))
10457 				| 1 << DICT_TF_POS_PAGE_COMPRESSION
10458 				| (ctx->page_compression_level & 0xF)
10459 				<< DICT_TF_POS_PAGE_COMPRESSION_LEVEL)
10460 			& ((1U << DICT_TF_BITS) - 1);
10461 #if defined __GNUC__ && !defined __clang__ && __GNUC__ < 6
10462 # pragma GCC diagnostic pop
10463 #endif
10464 
10465 		if (fil_space_t* space = ctx->new_table->space) {
10466 			bool update = !(space->flags
10467 					& FSP_FLAGS_MASK_PAGE_COMPRESSION);
10468 			mutex_enter(&fil_system.mutex);
10469 			space->flags &= ~FSP_FLAGS_MASK_MEM_COMPRESSION_LEVEL;
10470 			space->flags |= ctx->page_compression_level
10471 				<< FSP_FLAGS_MEM_COMPRESSION_LEVEL;
10472 			if (!space->full_crc32()) {
10473 				space->flags
10474 					|= FSP_FLAGS_MASK_PAGE_COMPRESSION;
10475 			} else if (!space->is_compressed()) {
10476 				space->flags
10477 					|= innodb_compression_algorithm
10478 					<< FSP_FLAGS_FCRC32_POS_COMPRESSED_ALGO;
10479 			}
10480 			mutex_exit(&fil_system.mutex);
10481 
10482 			if (update) {
10483 				/* Maybe we should introduce an undo
10484 				log record for updating tablespace
10485 				flags, and perform the update already
10486 				in innobase_page_compression_try().
10487 
10488 				If the server is killed before the
10489 				following mini-transaction commit
10490 				becomes durable, fsp_flags_try_adjust()
10491 				will perform the equivalent adjustment
10492 				and warn "adjusting FSP_SPACE_FLAGS". */
10493 				mtr_t	mtr;
10494 				mtr.start();
10495 				if (buf_block_t* b = buf_page_get(
10496 					    page_id_t(space->id, 0),
10497 					    space->zip_size(),
10498 					    RW_X_LATCH, &mtr)) {
10499 					byte* f = FSP_HEADER_OFFSET
10500 						+ FSP_SPACE_FLAGS + b->frame;
10501 					const auto sf = space->flags
10502 						& ~FSP_FLAGS_MEM_MASK;
10503 					if (mach_read_from_4(f) != sf) {
10504 						mtr.set_named_space(space);
10505 						mtr.write<4,mtr_t::FORCED>(
10506 							*b, f, sf);
10507 					}
10508 				}
10509 				mtr.commit();
10510 			}
10511 		}
10512 	}
10513 
10514 	col_set			drop_list;
10515 	col_set			v_drop_list;
10516 
10517 	/* Check if the column, part of an index to be dropped is part of any
10518 	other index which is not being dropped. If it so, then set the ord_part
10519 	of the column to 0. */
10520 	collect_columns_from_dropped_indexes(ctx, drop_list, v_drop_list);
10521 
10522 	for (ulint col : drop_list) {
10523 		if (!check_col_exists_in_indexes(ctx->new_table, col, false)) {
10524 			ctx->new_table->cols[col].ord_part = 0;
10525 		}
10526 	}
10527 
10528 	for (ulint col : v_drop_list) {
10529 		if (!check_col_exists_in_indexes(ctx->new_table, col, true)) {
10530 			ctx->new_table->v_cols[col].m_col.ord_part = 0;
10531 		}
10532 	}
10533 
10534 	for (ulint i = 0; i < ctx->num_to_add_index; i++) {
10535 		dict_index_t*	index = ctx->add_index[i];
10536 		DBUG_ASSERT(dict_index_get_online_status(index)
10537 			    == ONLINE_INDEX_COMPLETE);
10538 		DBUG_ASSERT(!index->is_committed());
10539 		index->set_committed(true);
10540 	}
10541 
10542 	if (ctx->num_to_drop_index) {
10543 		/* Really drop the indexes that were dropped.
10544 		The transaction had to be committed first
10545 		(after renaming the indexes), so that in the
10546 		event of a crash, crash recovery will drop the
10547 		indexes, because it drops all indexes whose
10548 		names start with TEMP_INDEX_PREFIX_STR. Once we
10549 		have started dropping an index tree, there is
10550 		no way to roll it back. */
10551 
10552 		for (ulint i = 0; i < ctx->num_to_drop_index; i++) {
10553 			dict_index_t*	index = ctx->drop_index[i];
10554 			DBUG_ASSERT(index->is_committed());
10555 			DBUG_ASSERT(index->table == ctx->new_table);
10556 			DBUG_ASSERT(index->to_be_dropped);
10557 
10558 			/* Replace the indexes in foreign key
10559 			constraints if needed. */
10560 
10561 			if (!dict_foreign_replace_index(
10562 				    index->table, ctx->col_names, index)) {
10563 				found = false;
10564 			}
10565 
10566 			/* Mark the index dropped
10567 			in the data dictionary cache. */
10568 			rw_lock_x_lock(dict_index_get_lock(index));
10569 			index->page = FIL_NULL;
10570 			rw_lock_x_unlock(dict_index_get_lock(index));
10571 		}
10572 
10573 		trx_start_for_ddl(trx, TRX_DICT_OP_INDEX);
10574 		row_merge_drop_indexes_dict(trx, ctx->new_table->id);
10575 
10576 		for (ulint i = 0; i < ctx->num_to_drop_index; i++) {
10577 			dict_index_t*	index = ctx->drop_index[i];
10578 			DBUG_ASSERT(index->is_committed());
10579 			DBUG_ASSERT(index->table == ctx->new_table);
10580 
10581 			if (index->type & DICT_FTS) {
10582 				DBUG_ASSERT(index->type == DICT_FTS
10583 					    || (index->type
10584 						& DICT_CORRUPT));
10585 				DBUG_ASSERT(index->table->fts);
10586 				DEBUG_SYNC_C("norebuild_fts_drop");
10587 				fts_drop_index(index->table, index, trx);
10588 			}
10589 
10590 			dict_index_remove_from_cache(index->table, index);
10591 		}
10592 
10593 		fts_clear_all(ctx->old_table, trx);
10594 		trx_commit_for_mysql(trx);
10595 	}
10596 
10597 	if (!ctx->is_instant()) {
10598 		innobase_rename_or_enlarge_columns_cache(
10599 			ha_alter_info, altered_table, table, ctx->new_table);
10600 	} else {
10601 		ut_ad(ctx->col_map);
10602 
10603 		if (fts_t* fts = ctx->new_table->fts) {
10604 			ut_ad(fts->doc_col != ULINT_UNDEFINED);
10605 			ut_ad(ctx->new_table->n_cols > DATA_N_SYS_COLS);
10606 			const ulint c = ctx->col_map[fts->doc_col];
10607 			ut_ad(c < ulint(ctx->new_table->n_cols)
10608 			      - DATA_N_SYS_COLS);
10609 			ut_d(const dict_col_t& col = ctx->new_table->cols[c]);
10610 			ut_ad(!col.is_nullable());
10611 			ut_ad(!col.is_virtual());
10612 			ut_ad(!col.is_added());
10613 			ut_ad(col.prtype & DATA_UNSIGNED);
10614 			ut_ad(col.mtype == DATA_INT);
10615 			ut_ad(col.len == 8);
10616 			ut_ad(col.ord_part);
10617 			fts->doc_col = c;
10618 		}
10619 
10620 		if (ha_alter_info->handler_flags & ALTER_DROP_STORED_COLUMN) {
10621 			const dict_index_t* index = ctx->new_table->indexes.start;
10622 
10623 			for (const dict_field_t* f = index->fields,
10624 				     * const end = f + index->n_fields;
10625 			     f != end; f++) {
10626 				dict_col_t& c = *f->col;
10627 				if (c.is_dropped()) {
10628 					c.set_dropped(!c.is_nullable(),
10629 						      DATA_LARGE_MTYPE(c.mtype)
10630 						      || (!f->fixed_len
10631 							  && c.len > 255),
10632 						      f->fixed_len);
10633 				}
10634 			}
10635 		}
10636 
10637 		if (!ctx->instant_table->persistent_autoinc) {
10638 			ctx->new_table->persistent_autoinc = 0;
10639 		}
10640 	}
10641 
10642 	if (ha_alter_info->handler_flags & ALTER_COLUMN_UNVERSIONED) {
10643 		vers_change_fields_cache(ha_alter_info, ctx, table);
10644 	}
10645 
10646 	if (ha_alter_info->handler_flags & ALTER_RENAME_INDEX) {
10647 		innobase_rename_indexes_cache(ctx, ha_alter_info);
10648 	}
10649 
10650 	ctx->new_table->fts_doc_id_index
10651 		= ctx->new_table->fts
10652 		? dict_table_get_index_on_name(
10653 			ctx->new_table, FTS_DOC_ID_INDEX_NAME)
10654 		: NULL;
10655 	DBUG_ASSERT((ctx->new_table->fts == NULL)
10656 		    == (ctx->new_table->fts_doc_id_index == NULL));
10657 	DBUG_RETURN(found);
10658 }
10659 
10660 /** Adjust the persistent statistics after non-rebuilding ALTER TABLE.
10661 Remove statistics for dropped indexes, add statistics for created indexes
10662 and rename statistics for renamed indexes.
10663 @param ha_alter_info Data used during in-place alter
10664 @param ctx In-place ALTER TABLE context
10665 @param thd MySQL connection
10666 */
10667 static
10668 void
alter_stats_norebuild(Alter_inplace_info * ha_alter_info,ha_innobase_inplace_ctx * ctx,THD * thd)10669 alter_stats_norebuild(
10670 /*==================*/
10671 	Alter_inplace_info*		ha_alter_info,
10672 	ha_innobase_inplace_ctx*	ctx,
10673 	THD*				thd)
10674 {
10675 	ulint	i;
10676 
10677 	DBUG_ENTER("alter_stats_norebuild");
10678 	DBUG_ASSERT(!ctx->need_rebuild());
10679 
10680 	if (!dict_stats_is_persistent_enabled(ctx->new_table)) {
10681 		DBUG_VOID_RETURN;
10682 	}
10683 
10684 	/* Delete corresponding rows from the stats table. We do this
10685 	in a separate transaction from trx, because lock waits are not
10686 	allowed in a data dictionary transaction. (Lock waits are possible
10687 	on the statistics table, because it is directly accessible by users,
10688 	not covered by the dict_sys.latch.)
10689 
10690 	Because the data dictionary changes were already committed, orphaned
10691 	rows may be left in the statistics table if the system crashes.
10692 
10693 	FIXME: each change to the statistics tables is being committed in a
10694 	separate transaction, meaning that the operation is not atomic
10695 
10696 	FIXME: This will not drop the (unused) statistics for
10697 	FTS_DOC_ID_INDEX if it was a hidden index, dropped together
10698 	with the last renamining FULLTEXT index. */
10699 	for (i = 0; i < ha_alter_info->index_drop_count; i++) {
10700 		const KEY* key = ha_alter_info->index_drop_buffer[i];
10701 
10702 		if (key->flags & HA_FULLTEXT) {
10703 			/* There are no index cardinality
10704 			statistics for FULLTEXT indexes. */
10705 			continue;
10706 		}
10707 
10708 		char	errstr[1024];
10709 
10710 		if (dict_stats_drop_index(
10711 			    ctx->new_table->name.m_name, key->name.str,
10712 			    errstr, sizeof errstr) != DB_SUCCESS) {
10713 			push_warning(thd,
10714 				     Sql_condition::WARN_LEVEL_WARN,
10715 				     ER_LOCK_WAIT_TIMEOUT, errstr);
10716 		}
10717 	}
10718 
10719 	for (size_t i = 0; i < ha_alter_info->rename_keys.size(); i++) {
10720 		const Alter_inplace_info::Rename_key_pair& pair
10721 			= ha_alter_info->rename_keys[i];
10722 
10723 		std::stringstream ss;
10724 		ss << TEMP_FILE_PREFIX_INNODB << std::this_thread::get_id()
10725 		   << i;
10726 		auto tmp_name = ss.str();
10727 
10728 		dberr_t err = dict_stats_rename_index(ctx->new_table,
10729 						      pair.old_key->name.str,
10730 						      tmp_name.c_str());
10731 
10732 		if (err != DB_SUCCESS) {
10733 			push_warning_printf(
10734 				thd,
10735 				Sql_condition::WARN_LEVEL_WARN,
10736 				ER_ERROR_ON_RENAME,
10737 				"Error renaming an index of table '%s'"
10738 				" from '%s' to '%s' in InnoDB persistent"
10739 				" statistics storage: %s",
10740 				ctx->new_table->name.m_name,
10741 				pair.old_key->name.str,
10742 				tmp_name.c_str(),
10743 				ut_strerr(err));
10744 		}
10745 	}
10746 
10747 	for (size_t i = 0; i < ha_alter_info->rename_keys.size(); i++) {
10748 		const Alter_inplace_info::Rename_key_pair& pair
10749 			= ha_alter_info->rename_keys[i];
10750 
10751 		std::stringstream ss;
10752 		ss << TEMP_FILE_PREFIX_INNODB << std::this_thread::get_id()
10753 		   << i;
10754 		auto tmp_name = ss.str();
10755 
10756 		dberr_t err = dict_stats_rename_index(ctx->new_table,
10757 						      tmp_name.c_str(),
10758 						      pair.new_key->name.str);
10759 
10760 		if (err != DB_SUCCESS) {
10761 			push_warning_printf(
10762 				thd,
10763 				Sql_condition::WARN_LEVEL_WARN,
10764 				ER_ERROR_ON_RENAME,
10765 				"Error renaming an index of table '%s'"
10766 				" from '%s' to '%s' in InnoDB persistent"
10767 				" statistics storage: %s",
10768 				ctx->new_table->name.m_name,
10769 				tmp_name.c_str(),
10770 				pair.new_key->name.str,
10771 				ut_strerr(err));
10772 		}
10773 	}
10774 
10775 	for (i = 0; i < ctx->num_to_add_index; i++) {
10776 		dict_index_t*	index = ctx->add_index[i];
10777 		DBUG_ASSERT(index->table == ctx->new_table);
10778 
10779 		if (!(index->type & DICT_FTS)) {
10780 			dict_stats_init(ctx->new_table);
10781 			dict_stats_update_for_index(index);
10782 		}
10783 	}
10784 
10785 	DBUG_VOID_RETURN;
10786 }
10787 
10788 /** Adjust the persistent statistics after rebuilding ALTER TABLE.
10789 Remove statistics for dropped indexes, add statistics for created indexes
10790 and rename statistics for renamed indexes.
10791 @param table InnoDB table that was rebuilt by ALTER TABLE
10792 @param table_name Table name in MySQL
10793 @param thd MySQL connection
10794 */
10795 static
10796 void
alter_stats_rebuild(dict_table_t * table,const char * table_name,THD * thd)10797 alter_stats_rebuild(
10798 /*================*/
10799 	dict_table_t*	table,
10800 	const char*	table_name,
10801 	THD*		thd)
10802 {
10803 	DBUG_ENTER("alter_stats_rebuild");
10804 
10805 	if (!table->space
10806 	    || !dict_stats_is_persistent_enabled(table)) {
10807 		DBUG_VOID_RETURN;
10808 	}
10809 
10810 	dberr_t	ret = dict_stats_update(table, DICT_STATS_RECALC_PERSISTENT);
10811 
10812 	if (ret != DB_SUCCESS) {
10813 		push_warning_printf(
10814 			thd,
10815 			Sql_condition::WARN_LEVEL_WARN,
10816 			ER_ALTER_INFO,
10817 			"Error updating stats for table '%s'"
10818 			" after table rebuild: %s",
10819 			table_name, ut_strerr(ret));
10820 	}
10821 
10822 	DBUG_VOID_RETURN;
10823 }
10824 
10825 #ifndef DBUG_OFF
10826 # define DBUG_INJECT_CRASH(prefix, count)			\
10827 do {								\
10828 	char buf[32];						\
10829 	snprintf(buf, sizeof buf, prefix "_%u", count);	\
10830 	DBUG_EXECUTE_IF(buf, DBUG_SUICIDE(););			\
10831 } while (0)
10832 #else
10833 # define DBUG_INJECT_CRASH(prefix, count)
10834 #endif
10835 
10836 /** Apply the log for the table rebuild operation.
10837 @param[in]	ctx		Inplace Alter table context
10838 @param[in]	altered_table	MySQL table that is being altered
10839 @return true Failure, else false. */
alter_rebuild_apply_log(ha_innobase_inplace_ctx * ctx,Alter_inplace_info * ha_alter_info,TABLE * altered_table)10840 static bool alter_rebuild_apply_log(
10841 	ha_innobase_inplace_ctx*	ctx,
10842 	Alter_inplace_info*		ha_alter_info,
10843 	TABLE*				altered_table)
10844 {
10845 	DBUG_ENTER("alter_rebuild_apply_log");
10846 
10847 	if (!ctx->online) {
10848 		DBUG_RETURN(false);
10849 	}
10850 
10851 	/* We copied the table. Any indexes that were requested to be
10852 	dropped were not created in the copy of the table. Apply any
10853 	last bit of the rebuild log and then rename the tables. */
10854 	dict_table_t*	user_table = ctx->old_table;
10855 	dict_table_t*	rebuilt_table = ctx->new_table;
10856 
10857 	DEBUG_SYNC_C("row_log_table_apply2_before");
10858 
10859 	dict_vcol_templ_t* s_templ  = NULL;
10860 
10861 	if (ctx->new_table->n_v_cols > 0) {
10862 		s_templ = UT_NEW_NOKEY(
10863 				dict_vcol_templ_t());
10864 		s_templ->vtempl = NULL;
10865 
10866 		innobase_build_v_templ(altered_table, ctx->new_table, s_templ,
10867 				       NULL, true);
10868 		ctx->new_table->vc_templ = s_templ;
10869 	}
10870 
10871 	dberr_t error = row_log_table_apply(
10872 		ctx->thr, user_table, altered_table,
10873 		static_cast<ha_innobase_inplace_ctx*>(
10874 			ha_alter_info->handler_ctx)->m_stage,
10875 		ctx->new_table);
10876 
10877 	if (s_templ) {
10878 		ut_ad(ctx->need_rebuild());
10879 		dict_free_vc_templ(s_templ);
10880 		UT_DELETE(s_templ);
10881 		ctx->new_table->vc_templ = NULL;
10882 	}
10883 
10884 	ulint	err_key = thr_get_trx(ctx->thr)->error_key_num;
10885 
10886 	switch (error) {
10887 		KEY*	dup_key;
10888 	case DB_SUCCESS:
10889 		break;
10890 	case DB_DUPLICATE_KEY:
10891 		if (err_key == ULINT_UNDEFINED) {
10892 			/* This should be the hidden index on
10893 			   FTS_DOC_ID. */
10894 			dup_key = NULL;
10895 		} else {
10896 			DBUG_ASSERT(err_key < ha_alter_info->key_count);
10897 			dup_key = &ha_alter_info->key_info_buffer[err_key];
10898 		}
10899 
10900 		print_keydup_error(altered_table, dup_key, MYF(0));
10901 		DBUG_RETURN(true);
10902 	case DB_ONLINE_LOG_TOO_BIG:
10903 		my_error(ER_INNODB_ONLINE_LOG_TOO_BIG, MYF(0),
10904 			 get_error_key_name(err_key, ha_alter_info,
10905 					    rebuilt_table));
10906 		DBUG_RETURN(true);
10907 	case DB_INDEX_CORRUPT:
10908 		my_error(ER_INDEX_CORRUPT, MYF(0),
10909 			 get_error_key_name(err_key, ha_alter_info,
10910 					    rebuilt_table));
10911 		DBUG_RETURN(true);
10912 	default:
10913 		my_error_innodb(error, ctx->old_table->name.m_name,
10914 				user_table->flags);
10915 		DBUG_RETURN(true);
10916 	}
10917 
10918 	DBUG_RETURN(false);
10919 }
10920 
10921 /** Commit or rollback the changes made during
10922 prepare_inplace_alter_table() and inplace_alter_table() inside
10923 the storage engine. Note that the allowed level of concurrency
10924 during this operation will be the same as for
10925 inplace_alter_table() and thus might be higher than during
10926 prepare_inplace_alter_table(). (E.g concurrent writes were
10927 blocked during prepare, but might not be during commit).
10928 @param altered_table TABLE object for new version of table.
10929 @param ha_alter_info Structure describing changes to be done
10930 by ALTER TABLE and holding data used during in-place alter.
10931 @param commit true => Commit, false => Rollback.
10932 @retval true Failure
10933 @retval false Success
10934 */
10935 
10936 bool
commit_inplace_alter_table(TABLE * altered_table,Alter_inplace_info * ha_alter_info,bool commit)10937 ha_innobase::commit_inplace_alter_table(
10938 /*====================================*/
10939 	TABLE*			altered_table,
10940 	Alter_inplace_info*	ha_alter_info,
10941 	bool			commit)
10942 {
10943 	ha_innobase_inplace_ctx*ctx0;
10944 
10945 	ctx0 = static_cast<ha_innobase_inplace_ctx*>
10946 		(ha_alter_info->handler_ctx);
10947 
10948 #ifndef DBUG_OFF
10949 	uint	crash_inject_count	= 1;
10950 	uint	crash_fail_inject_count	= 1;
10951 	uint	failure_inject_count	= 1;
10952 #endif /* DBUG_OFF */
10953 
10954 	DBUG_ENTER("commit_inplace_alter_table");
10955 	DBUG_ASSERT(!srv_read_only_mode);
10956 	DBUG_ASSERT(!ctx0 || ctx0->prebuilt == m_prebuilt);
10957 	DBUG_ASSERT(!ctx0 || ctx0->old_table == m_prebuilt->table);
10958 
10959 	DEBUG_SYNC_C("innodb_commit_inplace_alter_table_enter");
10960 
10961 	DEBUG_SYNC_C("innodb_commit_inplace_alter_table_wait");
10962 
10963 	if (ctx0 != NULL && ctx0->m_stage != NULL) {
10964 		ctx0->m_stage->begin_phase_end();
10965 	}
10966 
10967 	if (!commit) {
10968 		/* A rollback is being requested. So far we may at
10969 		most have created some indexes. If any indexes were to
10970 		be dropped, they would actually be dropped in this
10971 		method if commit=true. */
10972 		const bool	ret = rollback_inplace_alter_table(
10973 			ha_alter_info, table, m_prebuilt);
10974 		DBUG_RETURN(ret);
10975 	}
10976 
10977 	if (!(ha_alter_info->handler_flags & ~INNOBASE_INPLACE_IGNORE)) {
10978 		DBUG_ASSERT(!ctx0);
10979 		MONITOR_ATOMIC_DEC(MONITOR_PENDING_ALTER_TABLE);
10980 		ha_alter_info->group_commit_ctx = NULL;
10981 		DBUG_RETURN(false);
10982 	}
10983 
10984 	DBUG_ASSERT(ctx0);
10985 
10986 	inplace_alter_handler_ctx**	ctx_array;
10987 	inplace_alter_handler_ctx*	ctx_single[2];
10988 
10989 	if (ha_alter_info->group_commit_ctx) {
10990 		ctx_array = ha_alter_info->group_commit_ctx;
10991 	} else {
10992 		ctx_single[0] = ctx0;
10993 		ctx_single[1] = NULL;
10994 		ctx_array = ctx_single;
10995 	}
10996 
10997 	DBUG_ASSERT(ctx0 == ctx_array[0]);
10998 	ut_ad(m_prebuilt->table == ctx0->old_table);
10999 	ha_alter_info->group_commit_ctx = NULL;
11000 
11001 	trx_start_if_not_started_xa(m_prebuilt->trx, true);
11002 
11003 	for (inplace_alter_handler_ctx** pctx = ctx_array; *pctx; pctx++) {
11004 		ha_innobase_inplace_ctx*	ctx
11005 			= static_cast<ha_innobase_inplace_ctx*>(*pctx);
11006 		DBUG_ASSERT(ctx->prebuilt->trx == m_prebuilt->trx);
11007 
11008 		/* If decryption failed for old table or new table
11009 		fail here. */
11010 		if ((!ctx->old_table->is_readable()
11011 		     && ctx->old_table->space)
11012 		    || (!ctx->new_table->is_readable()
11013 			&& ctx->new_table->space)) {
11014 			String str;
11015 			const char* engine= table_type();
11016 			get_error_message(HA_ERR_DECRYPTION_FAILED, &str);
11017 			my_error(ER_GET_ERRMSG, MYF(0), HA_ERR_DECRYPTION_FAILED, str.c_ptr(), engine);
11018 			DBUG_RETURN(true);
11019 		}
11020 
11021 		/* Exclusively lock the table, to ensure that no other
11022 		transaction is holding locks on the table while we
11023 		change the table definition. The MySQL meta-data lock
11024 		should normally guarantee that no conflicting locks
11025 		exist. However, FOREIGN KEY constraints checks and any
11026 		transactions collected during crash recovery could be
11027 		holding InnoDB locks only, not MySQL locks. */
11028 
11029 		dberr_t error = row_merge_lock_table(
11030 			m_prebuilt->trx, ctx->old_table, LOCK_X);
11031 
11032 		if (error != DB_SUCCESS) {
11033 			my_error_innodb(
11034 				error, table_share->table_name.str, 0);
11035 			DBUG_RETURN(true);
11036 		}
11037 	}
11038 
11039 	DEBUG_SYNC(m_user_thd, "innodb_alter_commit_after_lock_table");
11040 
11041 	const bool	new_clustered	= ctx0->need_rebuild();
11042 	trx_t*		trx		= ctx0->trx;
11043 	bool		fail		= false;
11044 
11045 	/* Stop background FTS operations. */
11046 	for (inplace_alter_handler_ctx** pctx = ctx_array;
11047 			 *pctx; pctx++) {
11048 		ha_innobase_inplace_ctx*	ctx
11049 			= static_cast<ha_innobase_inplace_ctx*>(*pctx);
11050 
11051 		DBUG_ASSERT(new_clustered == ctx->need_rebuild());
11052 
11053 		if (new_clustered) {
11054 			if (ctx->old_table->fts) {
11055 				ut_ad(!ctx->old_table->fts->add_wq);
11056 				fts_optimize_remove_table(ctx->old_table);
11057 			}
11058 		}
11059 
11060 		if (ctx->new_table->fts) {
11061 			ut_ad(!ctx->new_table->fts->add_wq);
11062 			fts_optimize_remove_table(ctx->new_table);
11063 			fts_sync_during_ddl(ctx->new_table);
11064 		}
11065 
11066 		/* Apply the online log of the table before acquiring
11067 		data dictionary latches. Here alter thread already acquired
11068 		MDL_EXCLUSIVE on the table. So there can't be anymore DDLs, DMLs
11069 		for the altered table. By applying the log here, InnoDB
11070 		makes sure that concurrent DDLs, purge thread or any other
11071 		background thread doesn't wait for the dict_operation_lock
11072 		for longer time. */
11073 		if (new_clustered && commit
11074 		    && alter_rebuild_apply_log(
11075 				ctx, ha_alter_info, altered_table)) {
11076 			DBUG_RETURN(true);
11077 		}
11078 	}
11079 
11080 	if (!trx) {
11081 		DBUG_ASSERT(!new_clustered);
11082 		trx = innobase_trx_allocate(m_user_thd);
11083 	}
11084 
11085 	trx_start_for_ddl(trx, TRX_DICT_OP_INDEX);
11086 	/* Latch the InnoDB data dictionary exclusively so that no deadlocks
11087 	or lock waits can happen in it during the data dictionary operation. */
11088 	row_mysql_lock_data_dictionary(trx);
11089 
11090 	/* Prevent the background statistics collection from accessing
11091 	the tables. */
11092 	for (;;) {
11093 		bool	retry = false;
11094 
11095 		for (inplace_alter_handler_ctx** pctx = ctx_array;
11096 		     *pctx; pctx++) {
11097 			ha_innobase_inplace_ctx*	ctx
11098 				= static_cast<ha_innobase_inplace_ctx*>(*pctx);
11099 
11100 			DBUG_ASSERT(new_clustered == ctx->need_rebuild());
11101 
11102 			if (new_clustered
11103 			    && !dict_stats_stop_bg(ctx->old_table)) {
11104 				retry = true;
11105 			}
11106 
11107 			if (!dict_stats_stop_bg(ctx->new_table)) {
11108 				retry = true;
11109 			}
11110 		}
11111 
11112 		if (!retry) {
11113 			break;
11114 		}
11115 
11116 		DICT_BG_YIELD(trx);
11117 	}
11118 
11119 	/* Apply the changes to the data dictionary tables, for all
11120 	partitions. */
11121 
11122 	for (inplace_alter_handler_ctx** pctx = ctx_array;
11123 	     *pctx && !fail; pctx++) {
11124 		ha_innobase_inplace_ctx*	ctx
11125 			= static_cast<ha_innobase_inplace_ctx*>(*pctx);
11126 
11127 		DBUG_ASSERT(new_clustered == ctx->need_rebuild());
11128 		if (ctx->need_rebuild() && !ctx->old_table->space) {
11129 			my_error(ER_TABLESPACE_DISCARDED, MYF(0),
11130 				 table->s->table_name.str);
11131 			fail = true;
11132 		} else {
11133 			fail = commit_set_autoinc(ha_alter_info, ctx,
11134 						  altered_table, table);
11135 		}
11136 
11137 		if (fail) {
11138 		} else if (ctx->need_rebuild()) {
11139 			ctx->tmp_name = dict_mem_create_temporary_tablename(
11140 				ctx->heap, ctx->new_table->name.m_name,
11141 				ctx->new_table->id);
11142 
11143 			fail = commit_try_rebuild(
11144 				ha_alter_info, ctx, altered_table, table,
11145 				trx, table_share->table_name.str);
11146 		} else {
11147 			fail = commit_try_norebuild(
11148 				ha_alter_info, ctx, altered_table, table, trx,
11149 				table_share->table_name.str);
11150 		}
11151 		DBUG_INJECT_CRASH("ib_commit_inplace_crash",
11152 				  crash_inject_count++);
11153 #ifndef DBUG_OFF
11154 		{
11155 			/* Generate a dynamic dbug text. */
11156 			char buf[32];
11157 
11158 			snprintf(buf, sizeof buf,
11159 				    "ib_commit_inplace_fail_%u",
11160 				    failure_inject_count++);
11161 
11162 			DBUG_EXECUTE_IF(buf,
11163 					my_error(ER_INTERNAL_ERROR, MYF(0),
11164 						 "Injected error!");
11165 					fail = true;
11166 			);
11167 		}
11168 #endif
11169 	}
11170 
11171 	/* Commit or roll back the changes to the data dictionary. */
11172 	DEBUG_SYNC(m_user_thd, "innodb_alter_inplace_before_commit");
11173 
11174 	if (fail) {
11175 		trx_rollback_for_mysql(trx);
11176 		for (inplace_alter_handler_ctx** pctx = ctx_array;
11177 		     *pctx; pctx++) {
11178 			ha_innobase_inplace_ctx*	ctx
11179 				= static_cast<ha_innobase_inplace_ctx*>(*pctx);
11180 			ctx->rollback_instant();
11181 		}
11182 	} else if (!new_clustered) {
11183 		trx_commit_for_mysql(trx);
11184 	} else {
11185 		/* Test what happens on crash if the redo logs
11186 		are flushed to disk here. The log records
11187 		about the rename should not be committed, and
11188 		the data dictionary transaction should be
11189 		rolled back, restoring the old table. */
11190 		DBUG_EXECUTE_IF("innodb_alter_commit_crash_before_commit",
11191 				log_buffer_flush_to_disk();
11192 				DBUG_SUICIDE(););
11193 		ut_ad(!trx->fts_trx);
11194 
11195 		if (fail) {
11196 			trx_rollback_for_mysql(trx);
11197 		} else {
11198 			ut_ad(trx_state_eq(trx, TRX_STATE_ACTIVE));
11199 			ut_ad(trx->has_logged());
11200 			trx->commit();
11201 		}
11202 
11203 		/* If server crashes here, the dictionary in
11204 		InnoDB and MySQL will differ.  The .ibd files
11205 		and the .frm files must be swapped manually by
11206 		the administrator. No loss of data. */
11207 		DBUG_EXECUTE_IF("innodb_alter_commit_crash_after_commit",
11208 				log_buffer_flush_to_disk();
11209 				DBUG_SUICIDE(););
11210 	}
11211 
11212 	/* Flush the log to reduce probability that the .frm files and
11213 	the InnoDB data dictionary get out-of-sync if the user runs
11214 	with innodb_flush_log_at_trx_commit = 0 */
11215 
11216 	log_buffer_flush_to_disk();
11217 
11218 	/* At this point, the changes to the persistent storage have
11219 	been committed or rolled back. What remains to be done is to
11220 	update the in-memory structures, close some handles, release
11221 	temporary files, and (unless we rolled back) update persistent
11222 	statistics. */
11223 	for (inplace_alter_handler_ctx** pctx = ctx_array;
11224 	     *pctx; pctx++) {
11225 		ha_innobase_inplace_ctx*	ctx
11226 			= static_cast<ha_innobase_inplace_ctx*>(*pctx);
11227 
11228 		DBUG_ASSERT(ctx->need_rebuild() == new_clustered);
11229 
11230 		if (new_clustered) {
11231 			innobase_online_rebuild_log_free(ctx->old_table);
11232 		}
11233 
11234 		if (fail) {
11235 			if (new_clustered) {
11236 				trx_start_for_ddl(trx, TRX_DICT_OP_TABLE);
11237 
11238 				dict_table_close_and_drop(trx, ctx->new_table);
11239 
11240 				trx_commit_for_mysql(trx);
11241 				ctx->new_table = NULL;
11242 			} else {
11243 				/* We failed, but did not rebuild the table.
11244 				Roll back any ADD INDEX, or get rid of garbage
11245 				ADD INDEX that was left over from a previous
11246 				ALTER TABLE statement. */
11247 				trx_start_for_ddl(trx, TRX_DICT_OP_INDEX);
11248 				innobase_rollback_sec_index(
11249 					ctx->new_table, table, TRUE, trx);
11250 				trx_commit_for_mysql(trx);
11251 			}
11252 			DBUG_INJECT_CRASH("ib_commit_inplace_crash_fail",
11253 					  crash_fail_inject_count++);
11254 
11255 			continue;
11256 		}
11257 
11258 		innobase_copy_frm_flags_from_table_share(
11259 			ctx->new_table, altered_table->s);
11260 
11261 		if (new_clustered) {
11262 			/* We will reload and refresh the
11263 			in-memory foreign key constraint
11264 			metadata. This is a rename operation
11265 			in preparing for dropping the old
11266 			table. Set the table to_be_dropped bit
11267 			here, so to make sure DML foreign key
11268 			constraint check does not use the
11269 			stale dict_foreign_t. This is done
11270 			because WL#6049 (FK MDL) has not been
11271 			implemented yet. */
11272 			ctx->old_table->to_be_dropped = true;
11273 
11274 			DBUG_PRINT("to_be_dropped",
11275 				   ("table: %s", ctx->old_table->name.m_name));
11276 
11277 			if (innobase_update_foreign_cache(ctx, m_user_thd)
11278 			    != DB_SUCCESS
11279 			    && m_prebuilt->trx->check_foreigns) {
11280 foreign_fail:
11281 				push_warning_printf(
11282 					m_user_thd,
11283 					Sql_condition::WARN_LEVEL_WARN,
11284 					ER_ALTER_INFO,
11285 					"failed to load FOREIGN KEY"
11286 					" constraints");
11287 			}
11288 		} else {
11289 			bool fk_fail = innobase_update_foreign_cache(
11290 				ctx, m_user_thd) != DB_SUCCESS;
11291 
11292 			if (!commit_cache_norebuild(ha_alter_info, ctx,
11293 						    altered_table, table,
11294 						    trx)) {
11295 				fk_fail = true;
11296 			}
11297 
11298 			if (fk_fail && m_prebuilt->trx->check_foreigns) {
11299 				goto foreign_fail;
11300 			}
11301 		}
11302 
11303 		dict_mem_table_free_foreign_vcol_set(ctx->new_table);
11304 		dict_mem_table_fill_foreign_vcol_set(ctx->new_table);
11305 
11306 		DBUG_INJECT_CRASH("ib_commit_inplace_crash",
11307 				  crash_inject_count++);
11308 	}
11309 
11310 	if (fail) {
11311 		for (inplace_alter_handler_ctx** pctx = ctx_array;
11312 		     *pctx; pctx++) {
11313 			ha_innobase_inplace_ctx*	ctx
11314 				= static_cast<ha_innobase_inplace_ctx*>
11315 				(*pctx);
11316 			DBUG_ASSERT(ctx->need_rebuild() == new_clustered);
11317 
11318 			ut_d(dict_table_check_for_dup_indexes(
11319 				     ctx->old_table,
11320 				     CHECK_ABORTED_OK));
11321 			ut_a(fts_check_cached_index(ctx->old_table));
11322 			DBUG_INJECT_CRASH("ib_commit_inplace_crash_fail",
11323 					  crash_fail_inject_count++);
11324 
11325 			/* Restart the FTS background operations. */
11326 			if (ctx->old_table->fts) {
11327 				fts_optimize_add_table(ctx->old_table);
11328 			}
11329 		}
11330 
11331 		row_mysql_unlock_data_dictionary(trx);
11332 		if (trx != ctx0->trx) {
11333 			trx->free();
11334 		}
11335 		DBUG_RETURN(true);
11336 	}
11337 
11338 	if (trx == ctx0->trx) {
11339 		ctx0->trx = NULL;
11340 	}
11341 
11342 	/* Free the ctx->trx of other partitions, if any. We will only
11343 	use the ctx0->trx here. Others may have been allocated in
11344 	the prepare stage. */
11345 
11346 	for (inplace_alter_handler_ctx** pctx = &ctx_array[1]; *pctx;
11347 	     pctx++) {
11348 		ha_innobase_inplace_ctx*	ctx
11349 			= static_cast<ha_innobase_inplace_ctx*>(*pctx);
11350 
11351 		if (ctx->trx) {
11352 			ctx->trx->free();
11353 			ctx->trx = NULL;
11354 		}
11355 	}
11356 
11357 	/* MDEV-17468: Avoid this at least when ctx->is_instant().
11358 	Currently dict_load_column_low() is the only place where
11359 	num_base for virtual columns is assigned to nonzero. */
11360 	if (ctx0->num_to_drop_vcol || ctx0->num_to_add_vcol
11361 	    || (ctx0->new_table->n_v_cols && !new_clustered
11362 		&& (ha_alter_info->alter_info->drop_list.elements
11363 		    || ha_alter_info->alter_info->create_list.elements))
11364 	    || (ctx0->is_instant()
11365 		&& m_prebuilt->table->n_v_cols
11366 		&& ha_alter_info->handler_flags & ALTER_STORED_COLUMN_ORDER)) {
11367 		DBUG_ASSERT(ctx0->old_table->get_ref_count() == 1);
11368 		ut_ad(ctx0->prebuilt == m_prebuilt);
11369 		trx_commit_for_mysql(m_prebuilt->trx);
11370 
11371 		for (inplace_alter_handler_ctx** pctx = ctx_array; *pctx;
11372 		     pctx++) {
11373 			auto ctx= static_cast<ha_innobase_inplace_ctx*>(*pctx);
11374 			ctx->prebuilt->table = innobase_reload_table(
11375 				m_user_thd, ctx->prebuilt->table,
11376 				table->s->table_name, *ctx);
11377 			innobase_copy_frm_flags_from_table_share(
11378 				ctx->prebuilt->table, altered_table->s);
11379 		}
11380 
11381 		row_mysql_unlock_data_dictionary(trx);
11382 		trx->free();
11383 		MONITOR_ATOMIC_DEC(MONITOR_PENDING_ALTER_TABLE);
11384 		DBUG_RETURN(false);
11385 	}
11386 
11387 	/* Release the table locks. */
11388 	trx_commit_for_mysql(m_prebuilt->trx);
11389 
11390 	DBUG_EXECUTE_IF("ib_ddl_crash_after_user_trx_commit", DBUG_SUICIDE(););
11391 
11392 	for (inplace_alter_handler_ctx** pctx = ctx_array;
11393 	     *pctx; pctx++) {
11394 		ha_innobase_inplace_ctx*	ctx
11395 			= static_cast<ha_innobase_inplace_ctx*>
11396 			(*pctx);
11397 		DBUG_ASSERT(ctx->need_rebuild() == new_clustered);
11398 
11399 		/* Publish the created fulltext index, if any.
11400 		Note that a fulltext index can be created without
11401 		creating the clustered index, if there already exists
11402 		a suitable FTS_DOC_ID column. If not, one will be
11403 		created, implying new_clustered */
11404 		for (ulint i = 0; i < ctx->num_to_add_index; i++) {
11405 			dict_index_t*	index = ctx->add_index[i];
11406 
11407 			if (index->type & DICT_FTS) {
11408 				DBUG_ASSERT(index->type == DICT_FTS);
11409 				/* We reset DICT_TF2_FTS here because the bit
11410 				is left unset when a drop proceeds the add. */
11411 				DICT_TF2_FLAG_SET(ctx->new_table, DICT_TF2_FTS);
11412 				fts_add_index(index, ctx->new_table);
11413 			}
11414 		}
11415 
11416 		ut_d(dict_table_check_for_dup_indexes(
11417 			     ctx->new_table, CHECK_ALL_COMPLETE));
11418 
11419 		/* Start/Restart the FTS background operations. */
11420 		if (ctx->new_table->fts) {
11421 			fts_optimize_add_table(ctx->new_table);
11422 		}
11423 
11424 		ut_d(dict_table_check_for_dup_indexes(
11425 			     ctx->new_table, CHECK_ABORTED_OK));
11426 
11427 #ifdef UNIV_DEBUG
11428 		if (!(ctx->new_table->fts != NULL
11429 			&& ctx->new_table->fts->cache->sync->in_progress)) {
11430 			ut_a(fts_check_cached_index(ctx->new_table));
11431 		}
11432 #endif
11433 		if (new_clustered) {
11434 			/* Since the table has been rebuilt, we remove
11435 			all persistent statistics corresponding to the
11436 			old copy of the table (which was renamed to
11437 			ctx->tmp_name). */
11438 
11439 			DBUG_ASSERT(0 == strcmp(ctx->old_table->name.m_name,
11440 						ctx->tmp_name));
11441 
11442 			dict_stats_try_drop_table(m_user_thd,
11443                                                   ctx->new_table->name,
11444                                                   table->s->table_name);
11445 
11446 			DBUG_EXECUTE_IF("ib_ddl_crash_before_commit",
11447 					DBUG_SUICIDE(););
11448 
11449 			ut_ad(m_prebuilt != ctx->prebuilt
11450 			      || ctx == ctx0);
11451 			bool update_own_prebuilt =
11452 				(m_prebuilt == ctx->prebuilt);
11453 			trx_t* const	user_trx = m_prebuilt->trx;
11454 
11455 			row_prebuilt_free(ctx->prebuilt, TRUE);
11456 
11457 			/* Drop the copy of the old table, which was
11458 			renamed to ctx->tmp_name at the atomic DDL
11459 			transaction commit.  If the system crashes
11460 			before this is completed, some orphan tables
11461 			with ctx->tmp_name may be recovered. */
11462 			trx_start_for_ddl(trx, TRX_DICT_OP_TABLE);
11463 			dberr_t error = row_merge_drop_table(trx, ctx->old_table);
11464 
11465 			if (UNIV_UNLIKELY(error != DB_SUCCESS)) {
11466 				ib::error() << "Inplace alter table " << ctx->old_table->name
11467 					    << " dropping copy of the old table failed error "
11468 					    << error
11469 					    << ". tmp_name " << (ctx->tmp_name ? ctx->tmp_name : "N/A")
11470 					    << " new_table " << ctx->new_table->name;
11471 			}
11472 
11473 			trx_commit_for_mysql(trx);
11474 
11475 			/* Rebuild the prebuilt object. */
11476 			ctx->prebuilt = row_create_prebuilt(
11477 				ctx->new_table, altered_table->s->reclength);
11478 			if (update_own_prebuilt) {
11479 				m_prebuilt = ctx->prebuilt;
11480 			}
11481 			trx_start_if_not_started(user_trx, true);
11482 			m_prebuilt->trx = user_trx;
11483 		}
11484 		DBUG_INJECT_CRASH("ib_commit_inplace_crash",
11485 				  crash_inject_count++);
11486 	}
11487 
11488 	row_mysql_unlock_data_dictionary(trx);
11489 	trx->free();
11490 
11491 	/* TODO: The following code could be executed
11492 	while allowing concurrent access to the table
11493 	(MDL downgrade). */
11494 
11495 	if (new_clustered) {
11496 		for (inplace_alter_handler_ctx** pctx = ctx_array;
11497 		     *pctx; pctx++) {
11498 			ha_innobase_inplace_ctx*	ctx
11499 				= static_cast<ha_innobase_inplace_ctx*>
11500 				(*pctx);
11501 			DBUG_ASSERT(ctx->need_rebuild());
11502 
11503 			alter_stats_rebuild(
11504 				ctx->new_table, table->s->table_name.str,
11505 				m_user_thd);
11506 			DBUG_INJECT_CRASH("ib_commit_inplace_crash",
11507 					  crash_inject_count++);
11508 		}
11509 	} else {
11510 		for (inplace_alter_handler_ctx** pctx = ctx_array;
11511 		     *pctx; pctx++) {
11512 			ha_innobase_inplace_ctx*	ctx
11513 				= static_cast<ha_innobase_inplace_ctx*>
11514 				(*pctx);
11515 			DBUG_ASSERT(!ctx->need_rebuild());
11516 
11517 			alter_stats_norebuild(ha_alter_info, ctx, m_user_thd);
11518 			DBUG_INJECT_CRASH("ib_commit_inplace_crash",
11519 					  crash_inject_count++);
11520 		}
11521 	}
11522 
11523 	innobase_parse_hint_from_comment(
11524 		m_user_thd, m_prebuilt->table, altered_table->s);
11525 
11526 	/* TODO: Also perform DROP TABLE and DROP INDEX after
11527 	the MDL downgrade. */
11528 
11529 #ifndef DBUG_OFF
11530 	dict_index_t* clust_index = dict_table_get_first_index(
11531 		ctx0->prebuilt->table);
11532 	DBUG_ASSERT(!clust_index->online_log);
11533 	DBUG_ASSERT(dict_index_get_online_status(clust_index)
11534 		    == ONLINE_INDEX_COMPLETE);
11535 
11536 	for (dict_index_t* index = clust_index;
11537 	     index;
11538 	     index = dict_table_get_next_index(index)) {
11539 		DBUG_ASSERT(!index->to_be_dropped);
11540 	}
11541 #endif /* DBUG_OFF */
11542 	MONITOR_ATOMIC_DEC(MONITOR_PENDING_ALTER_TABLE);
11543 	DBUG_RETURN(false);
11544 }
11545 
11546 /**
11547 @param thd the session
11548 @param start_value the lower bound
11549 @param max_value the upper bound (inclusive) */
11550 
ib_sequence_t(THD * thd,ulonglong start_value,ulonglong max_value)11551 ib_sequence_t::ib_sequence_t(
11552 	THD*		thd,
11553 	ulonglong	start_value,
11554 	ulonglong	max_value)
11555 	:
11556 	m_max_value(max_value),
11557 	m_increment(0),
11558 	m_offset(0),
11559 	m_next_value(start_value),
11560 	m_eof(false)
11561 {
11562 	if (thd != 0 && m_max_value > 0) {
11563 
11564 		thd_get_autoinc(thd, &m_offset, &m_increment);
11565 
11566 		if (m_increment > 1 || m_offset > 1) {
11567 
11568 			/* If there is an offset or increment specified
11569 			then we need to work out the exact next value. */
11570 
11571 			m_next_value = innobase_next_autoinc(
11572 				start_value, 1,
11573 				m_increment, m_offset, m_max_value);
11574 
11575 		} else if (start_value == 0) {
11576 			/* The next value can never be 0. */
11577 			m_next_value = 1;
11578 		}
11579 	} else {
11580 		m_eof = true;
11581 	}
11582 }
11583 
11584 /**
11585 Postfix increment
11586 @return the next value to insert */
11587 
11588 ulonglong
operator ++(int)11589 ib_sequence_t::operator++(int) UNIV_NOTHROW
11590 {
11591 	ulonglong	current = m_next_value;
11592 
11593 	ut_ad(!m_eof);
11594 	ut_ad(m_max_value > 0);
11595 
11596 	m_next_value = innobase_next_autoinc(
11597 		current, 1, m_increment, m_offset, m_max_value);
11598 
11599 	if (m_next_value == m_max_value && current == m_next_value) {
11600 		m_eof = true;
11601 	}
11602 
11603 	return(current);
11604 }
11605