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