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