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