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