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