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