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