diff options
Diffstat (limited to 'storage/innobase/handler/handler0alter.cc')
-rw-r--r-- | storage/innobase/handler/handler0alter.cc | 2887 |
1 files changed, 2185 insertions, 702 deletions
diff --git a/storage/innobase/handler/handler0alter.cc b/storage/innobase/handler/handler0alter.cc index 70f2c1940fc..403dae8334d 100644 --- a/storage/innobase/handler/handler0alter.cc +++ b/storage/innobase/handler/handler0alter.cc @@ -104,6 +104,7 @@ static const alter_table_operations INNOBASE_INPLACE_IGNORE | ALTER_PARTITIONED | ALTER_COLUMN_COLUMN_FORMAT | ALTER_COLUMN_STORAGE_TYPE + | ALTER_CONVERT_TO | ALTER_VIRTUAL_GCOL_EXPR | ALTER_DROP_CHECK_CONSTRAINT | ALTER_RENAME @@ -132,8 +133,721 @@ static const alter_table_operations INNOBASE_ALTER_INSTANT | INNOBASE_FOREIGN_OPERATIONS | ALTER_COLUMN_EQUAL_PACK_LENGTH | ALTER_COLUMN_UNVERSIONED + | ALTER_RENAME_INDEX | ALTER_DROP_VIRTUAL_COLUMN; +/** Acquire a page latch on the possible metadata record, +to prevent concurrent invocation of dict_index_t::clear_instant_alter() +by purge when the table turns out to be empty. +@param[in,out] index clustered index +@param[in,out] mtr mini-transaction */ +static void instant_metadata_lock(dict_index_t& index, mtr_t& mtr) +{ + DBUG_ASSERT(index.is_primary()); + + if (!index.is_instant()) { + /* dict_index_t::clear_instant_alter() cannot be called. + No need for a latch. */ + return; + } + + btr_cur_t btr_cur; + btr_cur_open_at_index_side(true, &index, BTR_SEARCH_LEAF, + &btr_cur, 0, &mtr); + ut_ad(page_cur_is_before_first(btr_cur_get_page_cur(&btr_cur))); + ut_ad(page_is_leaf(btr_cur_get_page(&btr_cur))); + ut_ad(!page_has_prev(btr_cur_get_page(&btr_cur))); + ut_ad(!buf_block_get_page_zip(btr_cur_get_block(&btr_cur))); +} + +/** Initialize instant->field_map. +@tparam replace_dropped whether to point clustered index fields +to instant->dropped[] +@param[in] table table definition to copy from */ +template<bool replace_dropped> +inline void dict_table_t::init_instant(const dict_table_t& table) +{ + const dict_index_t& oindex __attribute__((unused))= *table.indexes.start; + dict_index_t& index = *indexes.start; + const unsigned u = index.first_user_field(); + DBUG_ASSERT(u == oindex.first_user_field()); + DBUG_ASSERT(index.n_fields >= oindex.n_fields); + + field_map_element_t* field_map_it = static_cast<field_map_element_t*>( + mem_heap_zalloc(heap, (index.n_fields - u) + * sizeof *field_map_it)); + instant->field_map = field_map_it; + + ut_d(unsigned n_drop = 0); + ut_d(unsigned n_nullable = 0); + for (unsigned i = u; i < index.n_fields; i++) { + auto& f = index.fields[i]; + DBUG_ASSERT(dict_col_get_fixed_size(f.col, not_redundant()) + <= DICT_MAX_FIXED_COL_LEN); + ut_d(n_nullable += f.col->is_nullable()); + + if (!f.col->is_dropped()) { + (*field_map_it++).set_ind(f.col->ind); + continue; + } + + auto fixed_len = dict_col_get_fixed_size( + f.col, not_redundant()); + field_map_it->set_dropped(); + if (!f.col->is_nullable()) { + field_map_it->set_not_null(); + } + field_map_it->set_ind(fixed_len + ? uint16_t(fixed_len + 1) + : DATA_BIG_COL(f.col)); + field_map_it++; + ut_ad(f.col >= table.instant->dropped); + ut_ad(f.col < table.instant->dropped + + table.instant->n_dropped); + ut_d(n_drop++); + if (replace_dropped) { + size_t d = f.col - table.instant->dropped; + ut_ad(f.col == &table.instant->dropped[d]); + ut_ad(d <= instant->n_dropped); + f.col = &instant->dropped[d]; + } + } + ut_ad(n_drop == n_dropped()); + ut_ad(field_map_it == &instant->field_map[index.n_fields - u]); + ut_ad(index.n_nullable == n_nullable); +} + +/** Set is_instant() before instant_column(). +@param[in] old previous table definition +@param[in] col_map map from old.cols[] and old.v_cols[] to this +@param[out] first_alter_pos 0, or 1 + first changed column position */ +inline void dict_table_t::prepare_instant(const dict_table_t& old, + const ulint* col_map, + unsigned& first_alter_pos) +{ + DBUG_ASSERT(!is_instant()); + DBUG_ASSERT(n_dropped() == 0); + DBUG_ASSERT(old.n_cols == old.n_def); + DBUG_ASSERT(n_cols == n_def); + DBUG_ASSERT(old.supports_instant()); + DBUG_ASSERT(!persistent_autoinc + || persistent_autoinc == old.persistent_autoinc); + /* supports_instant() does not necessarily hold here, + in case ROW_FORMAT=COMPRESSED according to the + MariaDB data dictionary, and ALTER_OPTIONS was not set. + If that is the case, the instant ALTER TABLE would keep + the InnoDB table in its current format. */ + + dict_index_t& oindex = *old.indexes.start; + dict_index_t& index = *indexes.start; + first_alter_pos = 0; + + mtr_t mtr; + mtr.start(); + /* Protect oindex.n_core_fields and others, so that + purge cannot invoke dict_index_t::clear_instant_alter(). */ + instant_metadata_lock(oindex, mtr); + + for (unsigned i = 0; i + DATA_N_SYS_COLS < old.n_cols; i++) { + if (col_map[i] != i) { + first_alter_pos = 1 + i; + goto add_metadata; + } + } + + if (!old.instant) { + /* Columns were not dropped or reordered. + Therefore columns must have been added at the end, + or modified instantly in place. */ + DBUG_ASSERT(index.n_fields >= oindex.n_fields); + DBUG_ASSERT(index.n_fields > oindex.n_fields + || !not_redundant()); +#ifdef UNIV_DEBUG + if (index.n_fields == oindex.n_fields) { + ut_ad(!not_redundant()); + for (unsigned i = index.n_fields; i--; ) { + ut_ad(index.fields[i].col->same_format( + *oindex.fields[i].col)); + } + } +#endif +set_core_fields: + index.n_core_fields = oindex.n_core_fields; + index.n_core_null_bytes = oindex.n_core_null_bytes; + } else { +add_metadata: + const unsigned n_old_drop = old.n_dropped(); + unsigned n_drop = n_old_drop; + for (unsigned i = old.n_cols; i--; ) { + if (col_map[i] == ULINT_UNDEFINED) { + DBUG_ASSERT(i + DATA_N_SYS_COLS + < uint(old.n_cols)); + n_drop++; + } + } + + instant = new (mem_heap_alloc(heap, sizeof(dict_instant_t))) + dict_instant_t(); + instant->n_dropped = n_drop; + if (n_drop) { + instant->dropped + = static_cast<dict_col_t*>( + mem_heap_alloc(heap, n_drop + * sizeof(dict_col_t))); + if (n_old_drop) { + memcpy(instant->dropped, old.instant->dropped, + n_old_drop * sizeof(dict_col_t)); + } + } else { + instant->dropped = NULL; + } + + for (unsigned i = 0, d = n_old_drop; i < old.n_cols; i++) { + if (col_map[i] == ULINT_UNDEFINED) { + (new (&instant->dropped[d++]) + dict_col_t(old.cols[i]))->set_dropped(); + } + } +#ifndef DBUG_OFF + for (unsigned i = 0; i < n_drop; i++) { + DBUG_ASSERT(instant->dropped[i].is_dropped()); + } +#endif + const uint n_fields = index.n_fields + n_dropped(); + + DBUG_ASSERT(n_fields >= oindex.n_fields); + dict_field_t* fields = static_cast<dict_field_t*>( + mem_heap_zalloc(heap, n_fields * sizeof *fields)); + uint i = 0, j = 0, n_nullable = 0; + ut_d(uint core_null = 0); + for (; i < oindex.n_fields; i++) { + DBUG_ASSERT(j <= i); + dict_field_t&f = fields[i] = oindex.fields[i]; + if (f.col->is_dropped()) { + /* The column has been instantly + dropped earlier. */ + DBUG_ASSERT(f.col >= old.instant->dropped); + { + size_t d = f.col + - old.instant->dropped; + DBUG_ASSERT(d < n_old_drop); + DBUG_ASSERT(&old.instant->dropped[d] + == f.col); + DBUG_ASSERT(!f.name); + f.col = instant->dropped + d; + } + if (f.col->is_nullable()) { +found_nullable: + n_nullable++; + ut_d(core_null + += i < oindex.n_core_fields); + } + continue; + } + + const ulint col_ind = col_map[f.col->ind]; + if (col_ind != ULINT_UNDEFINED) { + if (index.fields[j].col->ind != col_ind) { + /* The fields for instantly + added columns must be placed + last in the clustered index. + Keep pre-existing fields in + the same position. */ + uint k; + for (k = j + 1; k < index.n_fields; + k++) { + if (index.fields[k].col->ind + == col_ind) { + goto found_j; + } + } + DBUG_ASSERT(!"no such col"); +found_j: + std::swap(index.fields[j], + index.fields[k]); + } + DBUG_ASSERT(index.fields[j].col->ind + == col_ind); + fields[i] = index.fields[j++]; + DBUG_ASSERT(!fields[i].col->is_dropped()); + DBUG_ASSERT(fields[i].name + == fields[i].col->name(*this)); + if (fields[i].col->is_nullable()) { + goto found_nullable; + } + continue; + } + + /* This column is being dropped. */ + unsigned d = n_old_drop; + for (unsigned c = 0; c < f.col->ind; c++) { + d += col_map[c] == ULINT_UNDEFINED; + } + DBUG_ASSERT(d < n_drop); + f.col = &instant->dropped[d]; + f.name = NULL; + if (f.col->is_nullable()) { + goto found_nullable; + } + } + /* The n_core_null_bytes only matters for + ROW_FORMAT=COMPACT and ROW_FORMAT=DYNAMIC tables. */ + ut_ad(UT_BITS_IN_BYTES(core_null) == oindex.n_core_null_bytes + || !not_redundant()); + DBUG_ASSERT(i >= oindex.n_core_fields); + DBUG_ASSERT(j <= i); + DBUG_ASSERT(n_fields - (i - j) == index.n_fields); + std::sort(index.fields + j, index.fields + index.n_fields, + [](const dict_field_t& a, const dict_field_t& b) + { return a.col->ind < b.col->ind; }); + for (; i < n_fields; i++) { + fields[i] = index.fields[j++]; + n_nullable += fields[i].col->is_nullable(); + DBUG_ASSERT(!fields[i].col->is_dropped()); + DBUG_ASSERT(fields[i].name + == fields[i].col->name(*this)); + } + DBUG_ASSERT(j == index.n_fields); + index.n_fields = index.n_def = n_fields; + index.fields = fields; + DBUG_ASSERT(n_nullable >= index.n_nullable); + DBUG_ASSERT(n_nullable >= oindex.n_nullable); + index.n_nullable = n_nullable; + goto set_core_fields; + } + + DBUG_ASSERT(n_cols + n_dropped() >= old.n_cols + old.n_dropped()); + DBUG_ASSERT(n_dropped() >= old.n_dropped()); + DBUG_ASSERT(index.n_core_fields == oindex.n_core_fields); + DBUG_ASSERT(index.n_core_null_bytes == oindex.n_core_null_bytes); + mtr.commit(); +} + +/** Adjust index metadata for instant ADD/DROP/reorder COLUMN. +@param[in] clustered index definition after instant ALTER TABLE */ +inline void dict_index_t::instant_add_field(const dict_index_t& instant) +{ + DBUG_ASSERT(is_primary()); + DBUG_ASSERT(instant.is_primary()); + DBUG_ASSERT(!has_virtual()); + DBUG_ASSERT(!instant.has_virtual()); + DBUG_ASSERT(instant.n_core_fields <= instant.n_fields); + DBUG_ASSERT(n_def == n_fields); + DBUG_ASSERT(instant.n_def == instant.n_fields); + DBUG_ASSERT(type == instant.type); + DBUG_ASSERT(trx_id_offset == instant.trx_id_offset); + DBUG_ASSERT(n_user_defined_cols == instant.n_user_defined_cols); + DBUG_ASSERT(n_uniq == instant.n_uniq); + DBUG_ASSERT(instant.n_fields >= n_fields); + DBUG_ASSERT(instant.n_nullable >= n_nullable); + /* dict_table_t::prepare_instant() initialized n_core_fields + to be equal. However, after that purge could have emptied the + table and invoked dict_index_t::clear_instant_alter(). */ + DBUG_ASSERT(instant.n_core_fields <= n_core_fields); + DBUG_ASSERT(instant.n_core_null_bytes <= n_core_null_bytes); + DBUG_ASSERT(instant.n_core_fields == n_core_fields + || (!is_instant() && instant.is_instant())); + DBUG_ASSERT(instant.n_core_null_bytes == n_core_null_bytes + || (!is_instant() && instant.is_instant())); + + /* instant will have all fields (including ones for columns + that have been or are being instantly dropped) in the same position + as this index. Fields for any added columns are appended at the end. */ +#ifndef DBUG_OFF + for (unsigned i = 0; i < n_fields; i++) { + DBUG_ASSERT(fields[i].same(instant.fields[i])); + DBUG_ASSERT(instant.fields[i].col->same_format(*fields[i] + .col)); + /* Instant conversion from NULL to NOT NULL is not allowed. */ + DBUG_ASSERT(!fields[i].col->is_nullable() + || instant.fields[i].col->is_nullable()); + DBUG_ASSERT(fields[i].col->is_nullable() + == instant.fields[i].col->is_nullable() + || !table->not_redundant()); + } +#endif + n_fields = instant.n_fields; + n_def = instant.n_def; + n_nullable = instant.n_nullable; + fields = static_cast<dict_field_t*>( + mem_heap_dup(heap, instant.fields, n_fields * sizeof *fields)); + + ut_d(unsigned n_null = 0); + ut_d(unsigned n_dropped = 0); + + for (unsigned i = 0; i < n_fields; i++) { + const dict_col_t* icol = instant.fields[i].col; + dict_field_t& f = fields[i]; + ut_d(n_null += icol->is_nullable()); + DBUG_ASSERT(!icol->is_virtual()); + if (icol->is_dropped()) { + ut_d(n_dropped++); + f.col->set_dropped(); + f.name = NULL; + } else { + f.col = &table->cols[icol - instant.table->cols]; + f.name = f.col->name(*table); + } + } + + ut_ad(n_null == n_nullable); + ut_ad(n_dropped == instant.table->n_dropped()); +} + +/** Adjust table metadata for instant ADD/DROP/reorder COLUMN. +@param[in] table altered table (with dropped columns) +@param[in] col_map mapping from cols[] and v_cols[] to table +@return whether the metadata record must be updated */ +inline bool dict_table_t::instant_column(const dict_table_t& table, + const ulint* col_map) +{ + DBUG_ASSERT(!table.cached); + DBUG_ASSERT(table.n_def == table.n_cols); + DBUG_ASSERT(table.n_t_def == table.n_t_cols); + DBUG_ASSERT(n_def == n_cols); + DBUG_ASSERT(n_t_def == n_t_cols); + DBUG_ASSERT(n_v_def == n_v_cols); + DBUG_ASSERT(table.n_v_def == table.n_v_cols); + DBUG_ASSERT(table.n_cols + table.n_dropped() >= n_cols + n_dropped()); + DBUG_ASSERT(!table.persistent_autoinc + || persistent_autoinc == table.persistent_autoinc); + ut_ad(mutex_own(&dict_sys->mutex)); + + { + const char* end = table.col_names; + for (unsigned i = table.n_cols; i--; ) end += strlen(end) + 1; + + col_names = static_cast<char*>( + mem_heap_dup(heap, table.col_names, + ulint(end - table.col_names))); + } + const dict_col_t* const old_cols = cols; + cols = static_cast<dict_col_t*>(mem_heap_dup(heap, table.cols, + table.n_cols + * sizeof *cols)); + + /* Preserve the default values of previously instantly added + columns, or copy the new default values to this->heap. */ + for (ulint i = 0; i < ulint(table.n_cols); i++) { + dict_col_t& c = cols[i]; + + if (const dict_col_t* o = find(old_cols, col_map, n_cols, i)) { + c.def_val = o->def_val; + DBUG_ASSERT(!((c.prtype ^ o->prtype) + & ~(DATA_NOT_NULL | DATA_VERSIONED + | DATA_LONG_TRUE_VARCHAR))); + DBUG_ASSERT(c.mtype == o->mtype); + DBUG_ASSERT(c.len >= o->len); + + if (o->vers_sys_start()) { + ut_ad(o->ind == vers_start); + vers_start = i; + } else if (o->vers_sys_end()) { + ut_ad(o->ind == vers_end); + vers_end = i; + } + continue; + } + + DBUG_ASSERT(c.is_added()); + if (c.def_val.len <= sizeof field_ref_zero + && !memcmp(c.def_val.data, field_ref_zero, + c.def_val.len)) { + c.def_val.data = field_ref_zero; + } else if (const void*& d = c.def_val.data) { + d = mem_heap_dup(heap, d, c.def_val.len); + } else { + DBUG_ASSERT(c.def_val.len == UNIV_SQL_NULL); + } + } + + n_t_def += table.n_cols - n_cols; + n_t_cols += table.n_cols - n_cols; + n_def = table.n_cols; + + const dict_v_col_t* const old_v_cols = v_cols; + + if (const char* end = table.v_col_names) { + for (unsigned i = table.n_v_cols; i--; ) { + end += strlen(end) + 1; + } + + v_col_names = static_cast<char*>( + mem_heap_dup(heap, table.v_col_names, + ulint(end - table.v_col_names))); + v_cols = static_cast<dict_v_col_t*>( + mem_heap_dup(heap, table.v_cols, + table.n_v_cols * sizeof *v_cols)); + } else { + ut_ad(table.n_v_cols == 0); + v_col_names = NULL; + v_cols = NULL; + } + + n_t_def += table.n_v_cols - n_v_cols; + n_t_cols += table.n_v_cols - n_v_cols; + n_v_def = table.n_v_cols; + + for (unsigned i = 0; i < n_v_def; i++) { + dict_v_col_t& v = v_cols[i]; + v.v_indexes = UT_NEW_NOKEY(dict_v_idx_list()); + v.base_col = static_cast<dict_col_t**>( + mem_heap_dup(heap, v.base_col, + v.num_base * sizeof *v.base_col)); + + for (ulint n = v.num_base; n--; ) { + dict_col_t*& base = v.base_col[n]; + if (base->is_virtual()) { + } else if (base >= table.cols + && base < table.cols + table.n_cols) { + /* The base column was instantly added. */ + size_t c = base - table.cols; + DBUG_ASSERT(base == &table.cols[c]); + base = &cols[c]; + } else { + DBUG_ASSERT(base >= old_cols); + size_t c = base - old_cols; + DBUG_ASSERT(c + DATA_N_SYS_COLS < n_cols); + DBUG_ASSERT(base == &old_cols[c]); + DBUG_ASSERT(col_map[c] + DATA_N_SYS_COLS + < n_cols); + base = &cols[col_map[c]]; + } + } + } + + dict_index_t* index = dict_table_get_first_index(this); + bool metadata_changed; + { + const dict_index_t& i = *dict_table_get_first_index(&table); + metadata_changed = i.n_fields > index->n_fields; + ut_ad(i.n_fields >= index->n_fields); + index->instant_add_field(i); + } + + if (instant || table.instant) { + const auto old_instant = instant; + /* FIXME: add instant->heap, and transfer ownership here */ + if (!instant) { + instant = new (mem_heap_zalloc(heap, sizeof *instant)) + dict_instant_t(); + goto dup_dropped; + } else if (n_dropped() < table.n_dropped()) { +dup_dropped: + instant->dropped = static_cast<dict_col_t*>( + mem_heap_dup(heap, table.instant->dropped, + table.instant->n_dropped + * sizeof *instant->dropped)); + instant->n_dropped = table.instant->n_dropped; + } else if (table.instant->n_dropped) { + memcpy(instant->dropped, table.instant->dropped, + table.instant->n_dropped + * sizeof *instant->dropped); + } + + const field_map_element_t* field_map = old_instant + ? old_instant->field_map : NULL; + + init_instant<true>(table); + + if (!metadata_changed) { + metadata_changed = !field_map + || memcmp(field_map, + instant->field_map, + (index->n_fields + - index->first_user_field()) + * sizeof *field_map); + } + } + + while ((index = dict_table_get_next_index(index)) != NULL) { + if (index->to_be_dropped) { + continue; + } + for (unsigned i = 0; i < index->n_fields; i++) { + dict_field_t& f = index->fields[i]; + if (f.col >= table.cols + && f.col < table.cols + table.n_cols) { + /* This is an instantly added column + in a newly added index. */ + DBUG_ASSERT(!f.col->is_virtual()); + size_t c = f.col - table.cols; + DBUG_ASSERT(f.col == &table.cols[c]); + f.col = &cols[c]; + } else if (f.col >= &table.v_cols->m_col + && f.col < &table.v_cols[n_v_cols].m_col) { + /* This is an instantly added virtual column + in a newly added index. */ + DBUG_ASSERT(f.col->is_virtual()); + size_t c = reinterpret_cast<dict_v_col_t*>( + f.col) - table.v_cols; + DBUG_ASSERT(f.col == &table.v_cols[c].m_col); + f.col = &v_cols[c].m_col; + } else if (f.col < old_cols + || f.col >= old_cols + n_cols) { + DBUG_ASSERT(f.col->is_virtual()); + f.col = &v_cols[col_map[ + reinterpret_cast<dict_v_col_t*>( + f.col) + - old_v_cols + n_cols]].m_col; + } else { + f.col = &cols[col_map[f.col - old_cols]]; + DBUG_ASSERT(!f.col->is_virtual()); + } + f.name = f.col->name(*this); + if (f.col->is_virtual()) { + reinterpret_cast<dict_v_col_t*>(f.col) + ->v_indexes->push_back( + dict_v_idx_t(index, i)); + } + } + } + + n_cols = table.n_cols; + n_v_cols = table.n_v_cols; + return metadata_changed; +} + +/** Find the old column number for the given new column position. +@param[in] col_map column map from old column to new column +@param[in] pos new column position +@param[in] n number of columns present in the column map +@return old column position for the given new column position. */ +static ulint find_old_col_no(const ulint* col_map, ulint pos, ulint n) +{ + do { + ut_ad(n); + } while (col_map[--n] != pos); + return n; +} + +/** Roll back instant_column(). +@param[in] old_n_cols original n_cols +@param[in] old_cols original cols +@param[in] old_col_names original col_names +@param[in] old_instant original instant structure +@param[in] old_fields original fields +@param[in] old_n_fields original number of fields +@param[in] old_n_core_fields original number of core fields +@param[in] old_n_v_cols original n_v_cols +@param[in] old_v_cols original v_cols +@param[in] old_v_col_names original v_col_names +@param[in] col_map column map */ +inline void dict_table_t::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) +{ + ut_ad(mutex_own(&dict_sys->mutex)); + ut_ad(rw_lock_own(dict_operation_lock, RW_LOCK_X)); + dict_index_t* index = indexes.start; + mtr_t mtr; + mtr.start(); + /* Prevent concurrent execution of dict_index_t::clear_instant_alter() + by acquiring a latch on the leftmost leaf page. */ + instant_metadata_lock(*index, mtr); + /* index->is_instant() does not necessarily hold here, because + the table may have been emptied */ + DBUG_ASSERT(old_n_cols >= DATA_N_SYS_COLS); + DBUG_ASSERT(n_cols == n_def); + DBUG_ASSERT(index->n_def == index->n_fields); + DBUG_ASSERT(index->n_core_fields <= index->n_fields); + DBUG_ASSERT(old_n_core_fields <= old_n_fields); + DBUG_ASSERT(instant || !old_instant); + + instant = old_instant; + + index->n_nullable = 0; + + for (unsigned i = old_n_fields; i--; ) { + if (old_fields[i].col->is_nullable()) { + index->n_nullable++; + } + } + + for (unsigned i = n_v_cols; i--; ) { + UT_DELETE(v_cols[i].v_indexes); + } + + index->n_core_fields = (index->n_fields == index->n_core_fields) + ? old_n_fields + : old_n_core_fields; + index->n_def = index->n_fields = old_n_fields; + index->n_core_null_bytes = UT_BITS_IN_BYTES( + index->get_n_nullable(index->n_core_fields)); + + const dict_col_t* const new_cols = cols; + const dict_col_t* const new_cols_end __attribute__((unused)) = cols + n_cols; + const dict_v_col_t* const new_v_cols = v_cols; + const dict_v_col_t* const new_v_cols_end __attribute__((unused))= v_cols + n_v_cols; + + cols = old_cols; + col_names = old_col_names; + v_cols = old_v_cols; + v_col_names = old_v_col_names; + n_def = n_cols = old_n_cols; + n_v_def = n_v_cols = old_n_v_cols; + n_t_def = n_t_cols = n_cols + n_v_cols; + + if (versioned()) { + for (unsigned i = 0; i < n_cols; ++i) { + if (cols[i].vers_sys_start()) { + vers_start = i; + } else if (cols[i].vers_sys_end()) { + vers_end = i; + } + } + } + + index->fields = old_fields; + mtr.commit(); + + while ((index = dict_table_get_next_index(index)) != NULL) { + if (index->to_be_dropped) { + /* instant_column() did not adjust these indexes. */ + continue; + } + + for (unsigned i = 0; i < index->n_fields; i++) { + dict_field_t& f = index->fields[i]; + if (f.col->is_virtual()) { + DBUG_ASSERT(f.col >= &new_v_cols->m_col); + DBUG_ASSERT(f.col < &new_v_cols_end->m_col); + size_t n = size_t( + reinterpret_cast<dict_v_col_t*>(f.col) + - new_v_cols); + DBUG_ASSERT(n <= n_v_cols); + + ulint old_col_no = find_old_col_no( + col_map + n_cols, n, n_v_cols); + DBUG_ASSERT(old_col_no <= n_v_cols); + f.col = &v_cols[old_col_no].m_col; + DBUG_ASSERT(f.col->is_virtual()); + } else { + DBUG_ASSERT(f.col >= new_cols); + DBUG_ASSERT(f.col < new_cols_end); + size_t n = size_t(f.col - new_cols); + DBUG_ASSERT(n <= n_cols); + + ulint old_col_no = find_old_col_no(col_map, + n, n_cols); + DBUG_ASSERT(old_col_no < n_cols); + f.col = &cols[old_col_no]; + DBUG_ASSERT(!f.col->is_virtual()); + } + f.name = f.col->name(*this); + } + } +} + struct ha_innobase_inplace_ctx : public inplace_alter_handler_ctx { /** Dummy query graph */ @@ -150,10 +864,6 @@ struct ha_innobase_inplace_ctx : public inplace_alter_handler_ctx dict_index_t** drop_index; /** number of InnoDB indexes being dropped */ const ulint num_to_drop_index; - /** InnoDB indexes being renamed */ - dict_index_t** rename; - /** number of InnoDB indexes being renamed */ - const ulint num_to_rename; /** InnoDB foreign key constraints being dropped */ dict_foreign_t** drop_fk; /** number of InnoDB foreign key constraints being dropped */ @@ -172,7 +882,7 @@ struct ha_innobase_inplace_ctx : public inplace_alter_handler_ctx dict_table_t* old_table; /** table where the indexes are being created or dropped */ dict_table_t* new_table; - /** table definition for instant ADD COLUMN */ + /** table definition for instant ADD/DROP/reorder COLUMN */ dict_table_t* instant_table; /** mapping of old column numbers to new ones, or NULL */ const ulint* col_map; @@ -206,7 +916,22 @@ struct ha_innobase_inplace_ctx : public inplace_alter_handler_ctx dict_col_t* const old_cols; /** original column names of the table */ const char* const old_col_names; - + /** original instantly dropped or reordered columns */ + dict_instant_t* const old_instant; + /** original index fields */ + dict_field_t* const old_fields; + /** size of old_fields */ + const unsigned old_n_fields; + /** original old_table->n_core_fields */ + const unsigned old_n_core_fields; + /** original number of virtual columns in the table */ + const unsigned old_n_v_cols; + /** original virtual columns of the table */ + dict_v_col_t* const old_v_cols; + /** original virtual column names of the table */ + const char* const old_v_col_names; + /** 0, or 1 + first column whose position changes in instant ALTER */ + unsigned first_alter_pos; /** Allow non-null conversion. (1) Alter ignore should allow the conversion irrespective of sql mode. @@ -220,8 +945,6 @@ struct ha_innobase_inplace_ctx : public inplace_alter_handler_ctx ha_innobase_inplace_ctx(row_prebuilt_t*& prebuilt_arg, dict_index_t** drop_arg, ulint num_to_drop_arg, - dict_index_t** rename_arg, - ulint num_to_rename_arg, dict_foreign_t** drop_fk_arg, ulint num_to_drop_fk_arg, dict_foreign_t** add_fk_arg, @@ -240,7 +963,6 @@ struct ha_innobase_inplace_ctx : public inplace_alter_handler_ctx prebuilt (prebuilt_arg), add_index (0), add_key_numbers (0), num_to_add_index (0), drop_index (drop_arg), num_to_drop_index (num_to_drop_arg), - rename (rename_arg), num_to_rename (num_to_rename_arg), drop_fk (drop_fk_arg), num_to_drop_fk (num_to_drop_fk_arg), add_fk (add_fk_arg), num_to_add_fk (num_to_add_fk_arg), online (online_arg), heap (heap_arg), trx (0), @@ -263,6 +985,15 @@ struct ha_innobase_inplace_ctx : public inplace_alter_handler_ctx old_n_cols(prebuilt_arg->table->n_cols), old_cols(prebuilt_arg->table->cols), old_col_names(prebuilt_arg->table->col_names), + old_instant(prebuilt_arg->table->instant), + old_fields(prebuilt_arg->table->indexes.start->fields), + old_n_fields(prebuilt_arg->table->indexes.start->n_fields), + old_n_core_fields(prebuilt_arg->table->indexes.start + ->n_core_fields), + old_n_v_cols(prebuilt_arg->table->n_v_cols), + old_v_cols(prebuilt_arg->table->v_cols), + old_v_col_names(prebuilt_arg->table->v_col_names), + first_alter_pos(0), allow_not_null(allow_not_null_flag), page_compression_level(page_compressed ? (page_compression_level_arg @@ -295,6 +1026,9 @@ struct ha_innobase_inplace_ctx : public inplace_alter_handler_ctx rw_lock_free(&index->lock); dict_mem_index_free(index); } + for (unsigned i = old_n_v_cols; i--; ) { + UT_DELETE(old_v_cols[i].v_indexes); + } dict_mem_table_free(instant_table); } mem_heap_free(heap); @@ -319,14 +1053,24 @@ struct ha_innobase_inplace_ctx : public inplace_alter_handler_ctx { DBUG_ASSERT(need_rebuild()); DBUG_ASSERT(!is_instant()); - DBUG_ASSERT(old_table->n_cols == old_table->n_def); - DBUG_ASSERT(new_table->n_cols == new_table->n_def); DBUG_ASSERT(old_table->n_cols == old_n_cols); - DBUG_ASSERT(new_table->n_cols > old_table->n_cols); - instant_table = new_table; + instant_table = new_table; new_table = old_table; export_vars.innodb_instant_alter_column++; + + instant_table->prepare_instant(*old_table, col_map, + first_alter_pos); + } + + /** Adjust table metadata for instant ADD/DROP/reorder COLUMN. + @return whether the metadata record must be updated */ + bool instant_column() + { + DBUG_ASSERT(is_instant()); + DBUG_ASSERT(old_n_fields + == old_table->indexes.start->n_fields); + return old_table->instant_column(*instant_table, col_map); } /** Revert prepare_instant() if the transaction is rolled back. */ @@ -334,7 +1078,13 @@ struct ha_innobase_inplace_ctx : public inplace_alter_handler_ctx { if (!is_instant()) return; old_table->rollback_instant(old_n_cols, - old_cols, old_col_names); + old_cols, old_col_names, + old_instant, + old_fields, old_n_fields, + old_n_core_fields, + old_n_v_cols, old_v_cols, + old_v_col_names, + col_map); } /** @return whether this is instant ALTER TABLE */ @@ -344,6 +1094,40 @@ struct ha_innobase_inplace_ctx : public inplace_alter_handler_ctx return instant_table; } + /** Create an index table where indexes are ordered as follows: + + IF a new primary key is defined for the table THEN + + 1) New primary key + 2) The remaining keys in key_info + + ELSE + + 1) All new indexes in the order they arrive from MySQL + + ENDIF + + @return key definitions */ + MY_ATTRIBUTE((nonnull, warn_unused_result, malloc)) + inline index_def_t* + create_key_defs( + const Alter_inplace_info* ha_alter_info, + /*!< in: alter operation */ + const TABLE* altered_table, + /*!< in: MySQL table that is being altered */ + ulint& n_fts_add, + /*!< out: number of FTS indexes to be created */ + ulint& fts_doc_id_col, + /*!< in: The column number for Doc ID */ + bool& add_fts_doc_id, + /*!< in: whether we need to add new DOC ID + column for FTS index */ + bool& add_fts_doc_idx, + /*!< in: whether we need to add new DOC ID + index for FTS index */ + const TABLE* table); + /*!< in: MySQL table that is being altered */ + private: // Disable copying ha_innobase_inplace_ctx(const ha_innobase_inplace_ctx&); @@ -660,20 +1444,237 @@ check_v_col_in_order( } /** Determine if an instant operation is possible for altering columns. +@param[in] ib_table InnoDB table definition @param[in] ha_alter_info the ALTER TABLE operation -@param[in] table table definition before ALTER TABLE */ +@param[in] table table definition before ALTER TABLE +@param[in] altered_table table definition after ALTER TABLE +@param[in] strict whether to ensure that user records fit */ static bool instant_alter_column_possible( + const dict_table_t& ib_table, const Alter_inplace_info* ha_alter_info, - const TABLE* table) + const TABLE* table, + const TABLE* altered_table, + bool strict) { + const dict_index_t* const pk = ib_table.indexes.start; + ut_ad(pk->is_primary()); + ut_ad(!pk->has_virtual()); + + if (ha_alter_info->handler_flags + & (ALTER_STORED_COLUMN_ORDER | ALTER_DROP_STORED_COLUMN + | ALTER_ADD_STORED_BASE_COLUMN)) { +#if 1 // MDEV-17459: adjust fts_fetch_doc_from_rec() and friends; remove this + if (ib_table.fts || innobase_fulltext_exist(altered_table)) + return false; +#endif +#if 1 // MDEV-17468: fix bugs with indexed virtual columns & remove this + for (const dict_index_t* index = ib_table.indexes.start; + index; index = index->indexes.next) { + if (index->has_virtual()) { + ut_ad(ib_table.n_v_cols); + return false; + } + } +#endif + uint n_add = 0, n_nullable = 0, lenlen = 0; + const uint blob_prefix = dict_table_has_atomic_blobs(&ib_table) + ? 0 + : REC_ANTELOPE_MAX_INDEX_COL_LEN; + const uint min_local_len = blob_prefix + ? blob_prefix + FIELD_REF_SIZE + : 2 * FIELD_REF_SIZE; + size_t min_size = 0, max_size = 0; + Field** af = altered_table->field; + Field** const end = altered_table->field + + altered_table->s->fields; + List_iterator_fast<Create_field> cf_it( + ha_alter_info->alter_info->create_list); + + for (; af < end; af++) { + const Create_field* cf = cf_it++; + if (!(*af)->stored_in_db() || cf->field) { + /* Virtual or pre-existing column */ + continue; + } + const bool nullable = (*af)->real_maybe_null(); + const bool is_null = (*af)->is_real_null(); + ut_ad(!is_null || nullable); + n_nullable += nullable; + n_add++; + uint l; + switch ((*af)->type()) { + case MYSQL_TYPE_VARCHAR: + l = reinterpret_cast<const Field_varstring*> + (*af)->get_length(); + variable_length: + if (l >= min_local_len) { + max_size += blob_prefix + + FIELD_REF_SIZE; + if (!is_null) { + min_size += blob_prefix + + FIELD_REF_SIZE; + } + lenlen += 2; + } else { + if (!is_null) { + min_size += l; + } + l = (*af)->pack_length(); + max_size += l; + lenlen += l > 255 ? 2 : 1; + } + break; + case MYSQL_TYPE_GEOMETRY: + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_LONG_BLOB: + l = reinterpret_cast<const Field_blob*> + ((*af))->get_length(); + goto variable_length; + default: + l = (*af)->pack_length(); + if (l > 255 && ib_table.not_redundant()) { + goto variable_length; + } + max_size += l; + if (!is_null) { + min_size += l; + } + } + } + + ulint n_fields = pk->n_fields + n_add; + + if (n_fields >= REC_MAX_N_USER_FIELDS + DATA_N_SYS_COLS) { + return false; + } + + if (pk->is_gen_clust()) { + min_size += DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN + + DATA_ROW_ID_LEN; + max_size += DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN + + DATA_ROW_ID_LEN; + } else { + min_size += DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN; + max_size += DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN; + } + + uint i = pk->n_fields; + while (i-- > pk->n_core_fields) { + const dict_field_t& f = pk->fields[i]; + if (f.col->is_nullable()) { + n_nullable++; + if (!f.col->is_dropped() + && f.col->def_val.data) { + goto instantly_added_column; + } + } else if (f.fixed_len + && (f.fixed_len <= 255 + || !ib_table.not_redundant())) { + if (ib_table.not_redundant() + || !f.col->is_dropped()) { + min_size += f.fixed_len; + max_size += f.fixed_len; + } + } else if (f.col->is_dropped() || !f.col->is_added()) { + lenlen++; + goto set_max_size; + } else { +instantly_added_column: + ut_ad(f.col->is_added()); + if (f.col->def_val.len >= min_local_len) { + min_size += blob_prefix + + FIELD_REF_SIZE; + lenlen += 2; + } else { + min_size += f.col->def_val.len; + lenlen += f.col->def_val.len + > 255 ? 2 : 1; + } +set_max_size: + if (f.fixed_len + && (f.fixed_len <= 255 + || !ib_table.not_redundant())) { + max_size += f.fixed_len; + } else if (f.col->len >= min_local_len) { + max_size += blob_prefix + + FIELD_REF_SIZE; + } else { + max_size += f.col->len; + } + } + } + + do { + const dict_field_t& f = pk->fields[i]; + if (f.col->is_nullable()) { + n_nullable++; + } else if (f.fixed_len) { + min_size += f.fixed_len; + } else { + lenlen++; + } + } while (i--); + + if (ib_table.instant + || (ha_alter_info->handler_flags + & (ALTER_STORED_COLUMN_ORDER + | ALTER_DROP_STORED_COLUMN))) { + n_fields++; + lenlen += 2; + min_size += FIELD_REF_SIZE; + } + + if (ib_table.not_redundant()) { + min_size += REC_N_NEW_EXTRA_BYTES + + UT_BITS_IN_BYTES(n_nullable) + + lenlen; + } else { + min_size += (n_fields > 255 || min_size > 255) + ? n_fields * 2 : n_fields; + min_size += REC_N_OLD_EXTRA_BYTES; + } + + if (page_zip_rec_needs_ext(min_size, ib_table.not_redundant(), + 0, 0)) { + return false; + } + + if (strict && page_zip_rec_needs_ext(max_size, + ib_table.not_redundant(), + 0, 0)) { + return false; + } + } // Making table system-versioned instantly is not implemented yet. if (ha_alter_info->handler_flags & ALTER_ADD_SYSTEM_VERSIONING) { return false; } - if (~ha_alter_info->handler_flags & ALTER_ADD_STORED_BASE_COLUMN) { + static constexpr alter_table_operations avoid_rebuild + = ALTER_ADD_STORED_BASE_COLUMN + | ALTER_DROP_STORED_COLUMN + | ALTER_STORED_COLUMN_ORDER + | ALTER_COLUMN_NULLABLE; + + if (!(ha_alter_info->handler_flags & avoid_rebuild)) { + alter_table_operations flags = ha_alter_info->handler_flags + & ~avoid_rebuild; + /* None of the flags are set that we can handle + specially to avoid rebuild. In this case, we can + allow ALGORITHM=INSTANT, except if some requested + operation requires that the table be rebuilt. */ + if (flags & INNOBASE_ALTER_REBUILD) { + return false; + } + if ((flags & ALTER_OPTIONS) + && alter_options_need_rebuild(ha_alter_info, table)) { + return false; + } + } else if (!ib_table.supports_instant()) { return false; } @@ -696,12 +1697,59 @@ instant_alter_column_possible( columns. */ if (ha_alter_info->handler_flags & ((INNOBASE_ALTER_REBUILD | INNOBASE_ONLINE_CREATE) - & ~ALTER_ADD_STORED_BASE_COLUMN & ~ALTER_OPTIONS)) { + & ~ALTER_DROP_STORED_COLUMN + & ~ALTER_STORED_COLUMN_ORDER + & ~ALTER_ADD_STORED_BASE_COLUMN + & ~ALTER_COLUMN_NULLABLE + & ~ALTER_OPTIONS)) { + return false; + } + + if ((ha_alter_info->handler_flags & ALTER_OPTIONS) + && alter_options_need_rebuild(ha_alter_info, table)) { return false; } - return !(ha_alter_info->handler_flags & ALTER_OPTIONS) - || !alter_options_need_rebuild(ha_alter_info, table); + if (ha_alter_info->handler_flags & ALTER_COLUMN_NULLABLE) { + if (ib_table.not_redundant()) { + /* Instantaneous removal of NOT NULL is + only supported for ROW_FORMAT=REDUNDANT. */ + return false; + } + if (ib_table.fts_doc_id_index + && !innobase_fulltext_exist(altered_table)) { + /* Removing hidden FTS_DOC_ID_INDEX(FTS_DOC_ID) + requires that the table be rebuilt. */ + return false; + } + + Field** af = altered_table->field; + Field** const end = altered_table->field + + altered_table->s->fields; + for (unsigned c = 0; af < end; af++) { + if (!(*af)->stored_in_db()) { + continue; + } + + const dict_col_t* col = dict_table_get_nth_col( + &ib_table, c++); + + if (!col->ord_part || col->is_nullable() + || !(*af)->real_maybe_null()) { + continue; + } + + /* The column would be changed from NOT NULL. + Ensure that it is not a clustered index key. */ + for (auto i = pk->n_uniq; i--; ) { + if (pk->fields[i].col == col) { + return false; + } + } + } + } + + return true; } /** Check whether the non-const default value for the field @@ -1023,46 +2071,10 @@ ha_innobase::check_if_supported_inplace_alter( DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); } - bool add_drop_v_cols = false; - - /* If there is add or drop virtual columns, we will support operations - with these 2 options alone with inplace interface for now */ - - if (ha_alter_info->handler_flags - & (ALTER_ADD_VIRTUAL_COLUMN - | ALTER_DROP_VIRTUAL_COLUMN - | ALTER_VIRTUAL_COLUMN_ORDER)) { - ulonglong flags = ha_alter_info->handler_flags; - - /* TODO: uncomment the flags below, once we start to - support them */ - - flags &= ~(ALTER_ADD_VIRTUAL_COLUMN - | ALTER_DROP_VIRTUAL_COLUMN - | ALTER_VIRTUAL_COLUMN_ORDER - | ALTER_VIRTUAL_GCOL_EXPR - | ALTER_COLUMN_VCOL - /* - | ALTER_ADD_STORED_BASE_COLUMN - | ALTER_DROP_STORED_COLUMN - | ALTER_STORED_COLUMN_ORDER - | ALTER_ADD_UNIQUE_INDEX - */ - | ALTER_ADD_NON_UNIQUE_NON_PRIM_INDEX - | ALTER_DROP_NON_UNIQUE_NON_PRIM_INDEX); - - if (flags != 0 - || IF_PARTITIONING((altered_table->s->partition_info_str - && altered_table->s->partition_info_str_len), 0) - || (!check_v_col_in_order( - this->table, altered_table, ha_alter_info))) { - ha_alter_info->unsupported_reason = - MSG_UNSUPPORTED_ALTER_ONLINE_ON_VIRTUAL_COLUMN; - DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); - } - - add_drop_v_cols = true; - } + const bool add_drop_v_cols = !!(ha_alter_info->handler_flags + & (ALTER_ADD_VIRTUAL_COLUMN + | ALTER_DROP_VIRTUAL_COLUMN + | ALTER_VIRTUAL_COLUMN_ORDER)); /* We should be able to do the operation in-place. See if we can do it online (LOCK=NONE) or without rebuild. */ @@ -1232,8 +2244,9 @@ ha_innobase::check_if_supported_inplace_alter( constant DEFAULT expression. */ cf_it.rewind(); Field **af = altered_table->field; - bool add_column_not_last = false; - uint n_stored_cols = 0, n_add_cols = 0; + bool fts_need_rebuild = false; + need_rebuild = need_rebuild + || innobase_need_rebuild(ha_alter_info, table); while (Create_field* cf = cf_it++) { DBUG_ASSERT(cf->field @@ -1282,43 +2295,73 @@ ha_innobase::check_if_supported_inplace_alter( ha_alter_info->unsupported_reason = my_get_err_msg( ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOT_NULL); - } else if (!is_non_const_value(*af)) { - - n_add_cols++; - - if (af < &altered_table->field[table_share->fields]) { - add_column_not_last = true; - } - - if (set_default_value(*af)) { - goto next_column; + } else if (!is_non_const_value(*af) + && set_default_value(*af)) { + if (fulltext_indexes > 1 + && !my_strcasecmp(system_charset_info, + (*af)->field_name.str, + FTS_DOC_ID_COL_NAME)) { + /* If a hidden FTS_DOC_ID column exists + (because of FULLTEXT INDEX), it cannot + be replaced with a user-created one + except when using ALGORITHM=COPY. */ + ha_alter_info->unsupported_reason = + my_get_err_msg(ER_INNODB_FT_LIMIT); + DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); } + goto next_column; } DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); next_column: - n_stored_cols += (*af++)->stored_in_db(); + af++; } - if (!add_column_not_last - && uint(m_prebuilt->table->n_cols) - DATA_N_SYS_COLS + n_add_cols - == n_stored_cols - && m_prebuilt->table->supports_instant() - && instant_alter_column_possible(ha_alter_info, table)) { + const bool supports_instant = instant_alter_column_possible( + *m_prebuilt->table, ha_alter_info, table, altered_table, + trx_is_strict(m_prebuilt->trx)); + if (add_drop_v_cols) { + ulonglong flags = ha_alter_info->handler_flags; - DBUG_RETURN(HA_ALTER_INPLACE_INSTANT); + /* TODO: uncomment the flags below, once we start to + support them */ + + flags &= ~(ALTER_ADD_VIRTUAL_COLUMN + | ALTER_DROP_VIRTUAL_COLUMN + | ALTER_VIRTUAL_COLUMN_ORDER + | ALTER_VIRTUAL_GCOL_EXPR + | ALTER_COLUMN_VCOL + /* + | ALTER_ADD_STORED_BASE_COLUMN + | ALTER_DROP_STORED_COLUMN + | ALTER_STORED_COLUMN_ORDER + | ALTER_ADD_UNIQUE_INDEX + */ + | ALTER_ADD_NON_UNIQUE_NON_PRIM_INDEX + | ALTER_DROP_NON_UNIQUE_NON_PRIM_INDEX); + if (supports_instant) { + flags &= ~(ALTER_DROP_STORED_COLUMN +#if 0 /* MDEV-17468: remove check_v_col_in_order() and fix the code */ + | ALTER_ADD_STORED_BASE_COLUMN +#endif + | ALTER_STORED_COLUMN_ORDER); + } + if (flags != 0 + || IF_PARTITIONING((altered_table->s->partition_info_str + && altered_table->s->partition_info_str_len), 0) + || (!check_v_col_in_order( + this->table, altered_table, ha_alter_info))) { + ha_alter_info->unsupported_reason = + MSG_UNSUPPORTED_ALTER_ONLINE_ON_VIRTUAL_COLUMN; + DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); + } } - if (!(ha_alter_info->handler_flags & ~(INNOBASE_ALTER_INSTANT - | INNOBASE_INPLACE_IGNORE))) { + if (supports_instant) { DBUG_RETURN(HA_ALTER_INPLACE_INSTANT); } - bool fts_need_rebuild = false; - need_rebuild = need_rebuild - || innobase_need_rebuild(ha_alter_info, table); - if (need_rebuild && (fulltext_indexes || innobase_spatial_exist(altered_table) @@ -2287,9 +3330,9 @@ innobase_row_to_mysql( } } if (table->vfield) { - my_bitmap_map* old_vcol_set = tmp_use_all_columns(table, table->vcol_set); + my_bitmap_map* old_read_set = tmp_use_all_columns(table, table->read_set); table->update_virtual_fields(table->file, VCOL_UPDATE_FOR_READ); - tmp_restore_column_map(table->vcol_set, old_vcol_set); + tmp_restore_column_map(table->read_set, old_read_set); } } @@ -2364,7 +3407,6 @@ innobase_check_index_keys( } } - my_error(ER_WRONG_NAME_FOR_INDEX, MYF(0), key.name.str); return(ER_WRONG_NAME_FOR_INDEX); @@ -2717,8 +3759,7 @@ innobase_fts_check_doc_id_index_in_def( return(FTS_NOT_EXIST_DOC_ID_INDEX); } -/*******************************************************************//** -Create an index table where indexes are ordered as follows: +/** Create an index table where indexes are ordered as follows: IF a new primary key is defined for the table THEN @@ -2732,23 +3773,15 @@ ELSE ENDIF @return key definitions */ -static MY_ATTRIBUTE((nonnull, warn_unused_result, malloc)) -index_def_t* -innobase_create_key_defs( -/*=====================*/ - mem_heap_t* heap, - /*!< in/out: memory heap where space for key - definitions are allocated */ +MY_ATTRIBUTE((nonnull, warn_unused_result, malloc)) +inline index_def_t* +ha_innobase_inplace_ctx::create_key_defs( const Alter_inplace_info* ha_alter_info, /*!< in: alter operation */ const TABLE* altered_table, /*!< in: MySQL table that is being altered */ - ulint& n_add, - /*!< in/out: number of indexes to be created */ ulint& n_fts_add, /*!< out: number of FTS indexes to be created */ - bool got_default_clust, - /*!< in: whether the table lacks a primary key */ ulint& fts_doc_id_col, /*!< in: The column number for Doc ID */ bool& add_fts_doc_id, @@ -2760,6 +3793,9 @@ innobase_create_key_defs( const TABLE* table) /*!< in: MySQL table that is being altered */ { + ulint& n_add = num_to_add_index; + const bool got_default_clust = new_table->indexes.start->is_gen_clust(); + index_def_t* indexdef; index_def_t* indexdefs; bool new_primary; @@ -2768,7 +3804,7 @@ innobase_create_key_defs( const KEY*const key_info = ha_alter_info->key_info_buffer; - DBUG_ENTER("innobase_create_key_defs"); + DBUG_ENTER("ha_innobase_inplace_ctx::create_key_defs"); DBUG_ASSERT(!add_fts_doc_id || add_fts_doc_idx); DBUG_ASSERT(ha_alter_info->index_add_count == n_add); @@ -3203,7 +4239,7 @@ innobase_check_foreigns( @param[in,out] heap Memory heap where allocated @param[out] dfield InnoDB data field to copy to @param[in] field MySQL value for the column -@param[in] old_field Old field or NULL if new col is added +@param[in] old_field Old column if altering; NULL for ADD COLUMN @param[in] comp nonzero if in compact format. */ static void innobase_build_col_map_add( mem_heap_t* heap, @@ -3222,14 +4258,13 @@ static void innobase_build_col_map_add( return; } - ulint size = field->pack_length(); + const Field& from = old_field ? *old_field : *field; + ulint size = from.pack_length(); byte* buf = static_cast<byte*>(mem_heap_alloc(heap, size)); - const byte* mysql_data = old_field ? old_field->ptr : field->ptr; - row_mysql_store_col_in_innobase_format( - dfield, buf, true, mysql_data, size, comp); + dfield, buf, true, from.ptr, size, comp); } /** Construct the translation table for reordering, dropping or @@ -3251,7 +4286,7 @@ innobase_build_col_map( Alter_inplace_info* ha_alter_info, const TABLE* altered_table, const TABLE* table, - const dict_table_t* new_table, + dict_table_t* new_table, const dict_table_t* old_table, dtuple_t* defaults, mem_heap_t* heap) @@ -3336,16 +4371,25 @@ innobase_build_col_map( } col_map[old_i - num_old_v] = i; + if (old_table->versioned() + && altered_table->versioned()) { + if (old_i == old_table->vers_start) { + new_table->vers_start = i + num_v; + } else if (old_i == old_table->vers_end) { + new_table->vers_end = i + num_v; + } + } goto found_col; } } - ut_ad(!is_v); - innobase_build_col_map_add( - heap, dtuple_get_nth_field(defaults, i), - altered_table->field[i + num_v], - NULL, - dict_table_is_comp(new_table)); + if (!is_v) { + innobase_build_col_map_add( + heap, dtuple_get_nth_field(defaults, i), + altered_table->field[i + num_v], + NULL, + dict_table_is_comp(new_table)); + } found_col: if (is_v) { num_v++; @@ -3811,13 +4855,12 @@ prepare_inplace_add_virtual( ha_innobase_inplace_ctx* ctx; ulint i = 0; ulint j = 0; - const Create_field* new_field; ctx = static_cast<ha_innobase_inplace_ctx*> (ha_alter_info->handler_ctx); - ctx->num_to_add_vcol = altered_table->s->fields - + ctx->num_to_drop_vcol - table->s->fields; + ctx->num_to_add_vcol = altered_table->s->virtual_fields + + ctx->num_to_drop_vcol - table->s->virtual_fields; ctx->add_vcol = static_cast<dict_v_col_t*>( mem_heap_zalloc(ctx->heap, ctx->num_to_add_vcol @@ -3829,43 +4872,21 @@ prepare_inplace_add_virtual( List_iterator_fast<Create_field> cf_it( ha_alter_info->alter_info->create_list); - while ((new_field = (cf_it++)) != NULL) { - const Field* field = new_field->field; - ulint old_i; - - for (old_i = 0; table->field[old_i]; old_i++) { - const Field* n_field = table->field[old_i]; - if (field == n_field) { - break; - } - } - - i++; + while (const Create_field* new_field = cf_it++) { + const Field* field = altered_table->field[i++]; - if (table->field[old_i]) { + if (new_field->field || field->stored_in_db()) { continue; } - ut_ad(!field); - - ulint col_len; ulint is_unsigned; - ulint field_type; ulint charset_no; - - field = altered_table->field[i - 1]; - ulint col_type = get_innobase_type_from_mysql_type( &is_unsigned, field); - - if (field->stored_in_db()) { - continue; - } - - col_len = field->pack_length(); - field_type = (ulint) field->type(); + ulint col_len = field->pack_length(); + ulint field_type = (ulint) field->type(); if (!field->real_maybe_null()) { field_type |= DATA_NOT_NULL; @@ -3907,7 +4928,6 @@ prepare_inplace_add_virtual( } } - ctx->add_vcol[j].m_col.prtype = dtype_form_prtype( field_type, charset_no); @@ -3926,6 +4946,7 @@ prepare_inplace_add_virtual( /* No need to track the list */ ctx->add_vcol[j].v_indexes = NULL; + /* MDEV-17468: Do this on ctx->instant_table later */ innodb_base_col_setup(ctx->old_table, field, &ctx->add_vcol[j]); j++; } @@ -4052,33 +5073,96 @@ prepare_inplace_drop_virtual( @param[in] pos virtual column column no @param[in] base_pos base column pos @param[in] trx transaction -@return DB_SUCCESS if successful, otherwise error code */ -static -dberr_t -innobase_insert_sys_virtual( +@retval false on success +@retval true on failure (my_error() will have been called) */ +static bool innobase_insert_sys_virtual( const dict_table_t* table, ulint pos, ulint base_pos, trx_t* trx) { pars_info_t* info = pars_info_create(); - pars_info_add_ull_literal(info, "id", table->id); + pars_info_add_int4_literal(info, "pos", pos); + pars_info_add_int4_literal(info, "base_pos", base_pos); + + if (DB_SUCCESS != que_eval_sql( + info, + "PROCEDURE P () IS\n" + "BEGIN\n" + "INSERT INTO SYS_VIRTUAL VALUES (:id, :pos, :base_pos);\n" + "END;\n", + FALSE, trx)) { + my_error(ER_INTERNAL_ERROR, MYF(0), + "InnoDB: ADD COLUMN...VIRTUAL"); + return true; + } + return false; +} + +/** Insert a record to the SYS_COLUMNS dictionary table. +@param[in] table_id table id +@param[in] pos position of the column +@param[in] field_name field name +@param[in] mtype main type +@param[in] prtype precise type +@param[in] len fixed length in bytes, or 0 +@param[in] n_base number of base columns of virtual columns, or 0 +@param[in] update whether to update instead of inserting +@retval false on success +@retval true on failure (my_error() will have been called) */ +static bool 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) +{ + pars_info_t* info = pars_info_create(); + pars_info_add_ull_literal(info, "id", table_id); pars_info_add_int4_literal(info, "pos", pos); + pars_info_add_str_literal(info, "name", field_name); + pars_info_add_int4_literal(info, "mtype", mtype); + pars_info_add_int4_literal(info, "prtype", prtype); + pars_info_add_int4_literal(info, "len", len); + pars_info_add_int4_literal(info, "base", n_base); + + if (update) { + if (DB_SUCCESS != que_eval_sql( + info, + "PROCEDURE UPD_COL () IS\n" + "BEGIN\n" + "UPDATE SYS_COLUMNS SET\n" + "NAME=:name, MTYPE=:mtype, PRTYPE=:prtype, " + "LEN=:len, PREC=:base\n" + "WHERE TABLE_ID=:id AND POS=:pos;\n" + "END;\n", FALSE, trx)) { + my_error(ER_INTERNAL_ERROR, MYF(0), + "InnoDB: Updating SYS_COLUMNS failed"); + return true; + } - pars_info_add_int4_literal(info, "base_pos", base_pos); + return false; + } - dberr_t error = que_eval_sql( - info, - "PROCEDURE P () IS\n" - "BEGIN\n" - "INSERT INTO SYS_VIRTUAL VALUES" - "(:id, :pos, :base_pos);\n" - "END;\n", - FALSE, trx); + if (DB_SUCCESS != que_eval_sql( + info, + "PROCEDURE ADD_COL () IS\n" + "BEGIN\n" + "INSERT INTO SYS_COLUMNS VALUES" + "(:id,:pos,:name,:mtype,:prtype,:len,:base);\n" + "END;\n", FALSE, trx)) { + my_error(ER_INTERNAL_ERROR, MYF(0), + "InnoDB: Insert into SYS_COLUMNS failed"); + return true; + } - return(error); + return false; } /** Update INNODB SYS_COLUMNS on new virtual columns @@ -4086,10 +5170,9 @@ innobase_insert_sys_virtual( @param[in] col_name column name @param[in] vcol virtual column @param[in] trx transaction -@return DB_SUCCESS if successful, otherwise error code */ -static -dberr_t -innobase_add_one_virtual( +@retval false on success +@retval true on failure (my_error() will have been called) */ +static bool innobase_add_one_virtual( const dict_table_t* table, const char* col_name, dict_v_col_t* vcol, @@ -4097,67 +5180,41 @@ innobase_add_one_virtual( { ulint pos = dict_create_v_col_pos(vcol->v_pos, vcol->m_col.ind); - ulint mtype = vcol->m_col.mtype; - ulint prtype = vcol->m_col.prtype; - ulint len = vcol->m_col.len; - pars_info_t* info = pars_info_create(); - - pars_info_add_ull_literal(info, "id", table->id); - - pars_info_add_int4_literal(info, "pos", pos); - - pars_info_add_str_literal(info, "name", col_name); - pars_info_add_int4_literal(info, "mtype", mtype); - pars_info_add_int4_literal(info, "prtype", prtype); - pars_info_add_int4_literal(info, "len", len); - pars_info_add_int4_literal(info, "prec", vcol->num_base); - - dberr_t error = que_eval_sql( - info, - "PROCEDURE P () IS\n" - "BEGIN\n" - "INSERT INTO SYS_COLUMNS VALUES" - "(:id, :pos, :name, :mtype, :prtype, :len, :prec);\n" - "END;\n", - FALSE, trx); - if (error != DB_SUCCESS) { - return(error); + if (innodb_insert_sys_columns(table->id, pos, col_name, + vcol->m_col.mtype, vcol->m_col.prtype, + vcol->m_col.len, vcol->num_base, trx)) { + return true; } for (ulint i = 0; i < vcol->num_base; i++) { - error = innobase_insert_sys_virtual( - table, pos, vcol->base_col[i]->ind, trx); - if (error != DB_SUCCESS) { - return(error); + if (innobase_insert_sys_virtual( + table, pos, vcol->base_col[i]->ind, trx)) { + return true; } } - return(error); + return false; } /** Update SYS_TABLES.N_COLS in the data dictionary. @param[in] user_table InnoDB table -@param[in] n_cols the new value of SYS_TABLES.N_COLS +@param[in] n the new value of SYS_TABLES.N_COLS @param[in] trx transaction @return whether the operation failed */ -static -bool -innodb_update_n_cols(const dict_table_t* table, ulint n_cols, trx_t* trx) +static bool innodb_update_cols(const dict_table_t* table, ulint n, trx_t* trx) { pars_info_t* info = pars_info_create(); - pars_info_add_int4_literal(info, "n", n_cols); + pars_info_add_int4_literal(info, "n", n); pars_info_add_ull_literal(info, "id", table->id); - dberr_t err = que_eval_sql(info, - "PROCEDURE UPDATE_N_COLS () IS\n" - "BEGIN\n" - "UPDATE SYS_TABLES SET N_COLS = :n" - " WHERE ID = :id;\n" - "END;\n", FALSE, trx); - - if (err != DB_SUCCESS) { + if (DB_SUCCESS != que_eval_sql(info, + "PROCEDURE UPDATE_N_COLS () IS\n" + "BEGIN\n" + "UPDATE SYS_TABLES SET N_COLS = :n" + " WHERE ID = :id;\n" + "END;\n", FALSE, trx)) { my_error(ER_INTERNAL_ERROR, MYF(0), "InnoDB: Updating SYS_TABLES.N_COLS failed"); return true; @@ -4175,283 +5232,47 @@ innodb_update_n_cols(const dict_table_t* table, ulint n_cols, trx_t* trx) static bool innobase_add_virtual_try( - Alter_inplace_info* ha_alter_info, - const dict_table_t* user_table, - trx_t* trx) + const Alter_inplace_info* ha_alter_info, + const dict_table_t* user_table, + trx_t* trx) { - ha_innobase_inplace_ctx* ctx; - dberr_t err = DB_SUCCESS; - - ctx = static_cast<ha_innobase_inplace_ctx*>( + ha_innobase_inplace_ctx* ctx = static_cast<ha_innobase_inplace_ctx*>( ha_alter_info->handler_ctx); for (ulint i = 0; i < ctx->num_to_add_vcol; i++) { - - err = innobase_add_one_virtual( - user_table, ctx->add_vcol_name[i], - &ctx->add_vcol[i], trx); - - if (err != DB_SUCCESS) { - my_error(ER_INTERNAL_ERROR, MYF(0), - "InnoDB: ADD COLUMN...VIRTUAL"); - return(true); + if (innobase_add_one_virtual( + user_table, ctx->add_vcol_name[i], + &ctx->add_vcol[i], trx)) { + return true; } } - - ulint n_col = unsigned(user_table->n_cols) - DATA_N_SYS_COLS; - ulint n_v_col = unsigned(user_table->n_v_cols) - + ctx->num_to_add_vcol - ctx->num_to_drop_vcol; - ulint new_n = dict_table_encode_n_col(n_col, n_v_col) - + (unsigned(user_table->flags & DICT_TF_COMPACT) << 31); - - return innodb_update_n_cols(user_table, new_n, trx); + return false; } -/** Insert into SYS_COLUMNS and insert/update the hidden metadata record -for instant ADD COLUMN. -@param[in,out] ctx ALTER TABLE context for the current partition -@param[in] altered_table MySQL table that is being altered -@param[in] table MySQL table as it is before the ALTER operation -@param[in,out] trx dictionary transaction -@retval true failure -@retval false success */ -static -bool -innobase_add_instant_try( - ha_innobase_inplace_ctx*ctx, - const TABLE* altered_table, - const TABLE* table, - trx_t* trx) +/** Delete metadata from SYS_COLUMNS and SYS_VIRTUAL. +@param[in] id table id +@param[in] pos first SYS_COLUMNS.POS +@param[in,out] trx data dictionary transaction +@retval true Failure +@retval false Success. */ +static bool innobase_instant_drop_cols(table_id_t id, ulint pos, trx_t* trx) { - DBUG_ASSERT(!ctx->need_rebuild()); - - if (!ctx->is_instant()) return false; - - DBUG_ASSERT(altered_table->s->fields > table->s->fields); - DBUG_ASSERT(ctx->old_table->n_cols == ctx->old_n_cols); - - dict_table_t* user_table = ctx->old_table; - user_table->instant_add_column(*ctx->instant_table); - dict_index_t* index = dict_table_get_first_index(user_table); - /* The table may have been emptied and may have lost its - 'instant-add-ness' during this instant ADD COLUMN. */ - - /* Construct a table row of default values for the stored columns. */ - dtuple_t* row = dtuple_create(ctx->heap, user_table->n_cols); - dict_table_copy_types(row, user_table); - Field** af = altered_table->field; - Field** const end = altered_table->field + altered_table->s->fields; - - for (uint i = 0; af < end; af++) { - if (!(*af)->stored_in_db()) { - continue; - } - - dict_col_t* col = dict_table_get_nth_col(user_table, i); - DBUG_ASSERT(!strcmp((*af)->field_name.str, - dict_table_get_col_name(user_table, i))); - - dfield_t* d = dtuple_get_nth_field(row, i); - - if (col->is_instant()) { - dfield_set_data(d, col->def_val.data, - col->def_val.len); - } else if ((*af)->real_maybe_null()) { - /* Store NULL for nullable 'core' columns. */ - dfield_set_null(d); - } else { - switch ((*af)->type()) { - case MYSQL_TYPE_VARCHAR: - case MYSQL_TYPE_GEOMETRY: - case MYSQL_TYPE_TINY_BLOB: - case MYSQL_TYPE_MEDIUM_BLOB: - case MYSQL_TYPE_BLOB: - case MYSQL_TYPE_LONG_BLOB: - /* Store the empty string for 'core' - variable-length NOT NULL columns. */ - dfield_set_data(d, field_ref_zero, 0); - break; - default: - /* For fixed-length NOT NULL 'core' columns, - get a dummy default value from SQL. Note that - we will preserve the old values of these - columns when updating the metadata - record, to avoid unnecessary updates. */ - ulint len = (*af)->pack_length(); - DBUG_ASSERT(d->type.mtype != DATA_INT - || len <= 8); - row_mysql_store_col_in_innobase_format( - d, d->type.mtype == DATA_INT - ? static_cast<byte*>( - mem_heap_alloc(ctx->heap, len)) - : NULL, true, (*af)->ptr, len, - dict_table_is_comp(user_table)); - } - } - - if (i + DATA_N_SYS_COLS < ctx->old_n_cols) { - i++; - continue; - } - - pars_info_t* info = pars_info_create(); - pars_info_add_ull_literal(info, "id", user_table->id); - pars_info_add_int4_literal(info, "pos", i); - pars_info_add_str_literal(info, "name", (*af)->field_name.str); - pars_info_add_int4_literal(info, "mtype", d->type.mtype); - pars_info_add_int4_literal(info, "prtype", d->type.prtype); - pars_info_add_int4_literal(info, "len", d->type.len); + pars_info_t* info = pars_info_create(); + pars_info_add_ull_literal(info, "id", id); + pars_info_add_int4_literal(info, "pos", pos); - dberr_t err = que_eval_sql( + dberr_t err = que_eval_sql( info, - "PROCEDURE ADD_COL () IS\n" + "PROCEDURE DELETE_COL () IS\n" "BEGIN\n" - "INSERT INTO SYS_COLUMNS VALUES" - "(:id,:pos,:name,:mtype,:prtype,:len,0);\n" + "DELETE FROM SYS_COLUMNS WHERE\n" + "TABLE_ID = :id AND POS >= :pos;\n" + "DELETE FROM SYS_VIRTUAL WHERE TABLE_ID = :id;\n" "END;\n", FALSE, trx); - if (err != DB_SUCCESS) { - my_error(ER_INTERNAL_ERROR, MYF(0), - "InnoDB: Insert into SYS_COLUMNS failed"); - return(true); - } - - i++; - } - - if (innodb_update_n_cols(user_table, dict_table_encode_n_col( - unsigned(user_table->n_cols) - - DATA_N_SYS_COLS, - user_table->n_v_cols) - | (user_table->flags & DICT_TF_COMPACT) << 31, - trx)) { - return true; - } - - unsigned i = unsigned(user_table->n_cols) - DATA_N_SYS_COLS; - byte trx_id[DATA_TRX_ID_LEN], roll_ptr[DATA_ROLL_PTR_LEN]; - dfield_set_data(dtuple_get_nth_field(row, i++), field_ref_zero, - DATA_ROW_ID_LEN); - dfield_set_data(dtuple_get_nth_field(row, i++), trx_id, sizeof trx_id); - dfield_set_data(dtuple_get_nth_field(row, i),roll_ptr,sizeof roll_ptr); - DBUG_ASSERT(i + 1 == user_table->n_cols); - - trx_write_trx_id(trx_id, trx->id); - /* The DB_ROLL_PTR will be assigned later, when allocating undo log. - Silence a Valgrind warning in dtuple_validate() when - row_ins_clust_index_entry_low() searches for the insert position. */ - memset(roll_ptr, 0, sizeof roll_ptr); - - dtuple_t* entry = row_build_index_entry(row, NULL, index, ctx->heap); - entry->info_bits = REC_INFO_METADATA; - - mtr_t mtr; - mtr.start(); - index->set_modified(mtr); - btr_pcur_t pcur; - btr_pcur_open_at_index_side(true, index, BTR_MODIFY_TREE, &pcur, true, - 0, &mtr); - ut_ad(btr_pcur_is_before_first_on_page(&pcur)); - btr_pcur_move_to_next_on_page(&pcur); - - buf_block_t* block = btr_pcur_get_block(&pcur); - ut_ad(page_is_leaf(block->frame)); - ut_ad(!page_has_prev(block->frame)); - ut_ad(!buf_block_get_page_zip(block)); - const rec_t* rec = btr_pcur_get_rec(&pcur); - que_thr_t* thr = pars_complete_graph_for_exec( - NULL, trx, ctx->heap, NULL); - - dberr_t err; - if (rec_is_metadata(rec, index)) { - ut_ad(page_rec_is_user_rec(rec)); - if (!page_has_next(block->frame) - && page_rec_is_last(rec, block->frame)) { - goto empty_table; - } - /* Extend the record with the instantly added columns. */ - const unsigned n = user_table->n_cols - ctx->old_n_cols; - /* Reserve room for DB_TRX_ID,DB_ROLL_PTR and any - non-updated off-page columns in case they are moved off - page as a result of the update. */ - upd_t* update = upd_create(index->n_fields, ctx->heap); - update->n_fields = n; - update->info_bits = REC_INFO_METADATA; - /* Add the default values for instantly added columns */ - for (unsigned i = 0; i < n; i++) { - upd_field_t* uf = upd_get_nth_field(update, i); - unsigned f = index->n_fields - n + i; - uf->field_no = f; - uf->new_val = entry->fields[f]; - } - ulint* offsets = NULL; - mem_heap_t* offsets_heap = NULL; - big_rec_t* big_rec; - err = btr_cur_pessimistic_update( - BTR_NO_LOCKING_FLAG | BTR_KEEP_POS_FLAG, - btr_pcur_get_btr_cur(&pcur), - &offsets, &offsets_heap, ctx->heap, - &big_rec, update, UPD_NODE_NO_ORD_CHANGE, - thr, trx->id, &mtr); - if (big_rec) { - if (err == DB_SUCCESS) { - err = btr_store_big_rec_extern_fields( - &pcur, offsets, big_rec, &mtr, - BTR_STORE_UPDATE); - } - - dtuple_big_rec_free(big_rec); - } - if (offsets_heap) { - mem_heap_free(offsets_heap); - } - btr_pcur_close(&pcur); - goto func_exit; - } else if (page_rec_is_supremum(rec)) { -empty_table: - /* The table is empty. */ - ut_ad(fil_page_index_page_check(block->frame)); - ut_ad(!page_has_siblings(block->frame)); - ut_ad(block->page.id.page_no() == index->page); - btr_page_empty(block, NULL, index, 0, &mtr); - index->remove_instant(); - err = DB_SUCCESS; - goto func_exit; - } - - /* Convert the table to the instant ADD COLUMN format. */ - ut_ad(user_table->is_instant()); - mtr.commit(); - mtr.start(); - index->set_modified(mtr); - if (page_t* root = btr_root_get(index, &mtr)) { - if (fil_page_get_type(root) != FIL_PAGE_INDEX) { - DBUG_ASSERT(!"wrong page type"); - goto err_exit; - } - - DBUG_ASSERT(!page_is_comp(root) || !page_get_instant(root)); - mlog_write_ulint(root + FIL_PAGE_TYPE, - FIL_PAGE_TYPE_INSTANT, MLOG_2BYTES, - &mtr); - page_set_instant(root, index->n_core_fields, &mtr); - mtr.commit(); - mtr.start(); - index->set_modified(mtr); - err = row_ins_clust_index_entry_low( - BTR_NO_LOCKING_FLAG, BTR_MODIFY_TREE, index, - index->n_uniq, entry, 0, thr, false); - } else { -err_exit: - err = DB_CORRUPTION; - } - -func_exit: - mtr.commit(); - if (err != DB_SUCCESS) { - my_error_innodb(err, table->s->table_name.str, - user_table->flags); + my_error(ER_INTERNAL_ERROR, MYF(0), + "InnoDB: DELETE from SYS_COLUMNS/SYS_VIRTUAL failed"); return true; } @@ -4629,9 +5450,9 @@ innobase_drop_one_virtual_sys_virtual( static bool innobase_drop_virtual_try( - Alter_inplace_info* ha_alter_info, - const dict_table_t* user_table, - trx_t* trx) + const Alter_inplace_info* ha_alter_info, + const dict_table_t* user_table, + trx_t* trx) { ha_innobase_inplace_ctx* ctx; dberr_t err = DB_SUCCESS; @@ -4664,14 +5485,458 @@ innobase_drop_virtual_try( } } + return false; +} + +/** Serialise metadata of dropped or reordered columns. +@param[in,out] heap memory heap for allocation +@param[out] field data field with the metadata */ +inline +void dict_table_t::serialise_columns(mem_heap_t* heap, dfield_t* field) const +{ + DBUG_ASSERT(instant); + const dict_index_t& index = *UT_LIST_GET_FIRST(indexes); + unsigned n_fixed = index.first_user_field(); + unsigned num_non_pk_fields = index.n_fields - n_fixed; + + ulint len = 4 + num_non_pk_fields * 2; + + byte* data = static_cast<byte*>(mem_heap_alloc(heap, len)); + + dfield_set_data(field, data, len); + + mach_write_to_4(data, num_non_pk_fields); - ulint n_col = unsigned(user_table->n_cols) - DATA_N_SYS_COLS; - ulint n_v_col = unsigned(user_table->n_v_cols) - - ctx->num_to_drop_vcol; - ulint new_n = dict_table_encode_n_col(n_col, n_v_col) - | ((user_table->flags & DICT_TF_COMPACT) << 31); + data += 4; - return innodb_update_n_cols(user_table, new_n, trx); + for (ulint i = n_fixed; i < index.n_fields; i++) { + mach_write_to_2(data, instant->field_map[i - n_fixed]); + data += 2; + } +} + +/** Construct the metadata record for instant ALTER TABLE. +@param[in] row dummy or default values for existing columns +@param[in,out] heap memory heap for allocations +@return metadata record */ +inline +dtuple_t* +dict_index_t::instant_metadata(const dtuple_t& row, mem_heap_t* heap) const +{ + ut_ad(is_primary()); + dtuple_t* entry; + + if (!table->instant) { + entry = row_build_index_entry(&row, NULL, this, heap); + entry->info_bits = REC_INFO_METADATA_ADD; + return entry; + } + + entry = dtuple_create(heap, n_fields + 1); + entry->n_fields_cmp = n_uniq; + entry->info_bits = REC_INFO_METADATA_ALTER; + + const dict_field_t* field = fields; + + for (uint i = 0; i <= n_fields; i++, field++) { + dfield_t* dfield = dtuple_get_nth_field(entry, i); + + if (i == first_user_field()) { + table->serialise_columns(heap, dfield); + dfield->type.metadata_blob_init(); + field--; + continue; + } + + ut_ad(!field->col->is_virtual()); + + if (field->col->is_dropped()) { + dict_col_copy_type(field->col, &dfield->type); + if (field->col->is_nullable()) { + dfield_set_null(dfield); + } else { + dfield_set_data(dfield, field_ref_zero, + field->fixed_len); + } + continue; + } + + const dfield_t* s = dtuple_get_nth_field(&row, field->col->ind); + ut_ad(dict_col_type_assert_equal(field->col, &s->type)); + *dfield = *s; + + if (dfield_is_null(dfield)) { + continue; + } + + if (dfield_is_ext(dfield)) { + ut_ad(i > first_user_field()); + ut_ad(!field->prefix_len); + ut_ad(dfield->len >= FIELD_REF_SIZE); + dfield_set_len(dfield, dfield->len - FIELD_REF_SIZE); + } + + if (!field->prefix_len) { + continue; + } + + ut_ad(field->col->ord_part); + ut_ad(i < n_uniq); + + ulint len = dtype_get_at_most_n_mbchars( + field->col->prtype, + field->col->mbminlen, field->col->mbmaxlen, + field->prefix_len, dfield->len, + static_cast<char*>(dfield_get_data(dfield))); + dfield_set_len(dfield, len); + } + + return entry; +} + +/** Insert or update SYS_COLUMNS and the hidden metadata record +for instant ALTER TABLE. +@param[in] ha_alter_info ALTER TABLE context +@param[in,out] ctx ALTER TABLE context for the current partition +@param[in] altered_table MySQL table that is being altered +@param[in] table MySQL table as it is before the ALTER operation +@param[in,out] trx dictionary transaction +@retval true failure +@retval false success */ +static bool 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) +{ + DBUG_ASSERT(!ctx->need_rebuild()); + DBUG_ASSERT(ctx->is_instant()); + + dict_table_t* user_table = ctx->old_table; + + dict_index_t* index = dict_table_get_first_index(user_table); + mtr_t mtr; + mtr.start(); + /* Prevent purge from calling dict_index_t::clear_instant_alter(), + to protect index->n_core_fields, index->table->instant and others + from changing during ctx->instant_column(). */ + instant_metadata_lock(*index, mtr); + const unsigned n_old_fields = index->n_fields; + const dict_col_t* old_cols = user_table->cols; + DBUG_ASSERT(user_table->n_cols == ctx->old_n_cols); + + const bool metadata_changed = ctx->instant_column(); + + DBUG_ASSERT(index->n_fields >= n_old_fields); + /* Release the page latch. Between this and the next + btr_pcur_open_at_index_side(), data fields such as + index->n_core_fields and index->table->instant could change, + but we would handle that in empty_table: below. */ + mtr.commit(); + /* The table may have been emptied and may have lost its + 'instantness' during this ALTER TABLE. */ + + /* Construct a table row of default values for the stored columns. */ + dtuple_t* row = dtuple_create(ctx->heap, user_table->n_cols); + dict_table_copy_types(row, user_table); + Field** af = altered_table->field; + Field** const end = altered_table->field + altered_table->s->fields; + ut_d(List_iterator_fast<Create_field> cf_it( + ha_alter_info->alter_info->create_list)); + if (ctx->first_alter_pos + && innobase_instant_drop_cols(user_table->id, + ctx->first_alter_pos - 1, trx)) { + return true; + } + for (uint i = 0; af < end; af++) { + if (!(*af)->stored_in_db()) { + ut_d(cf_it++); + continue; + } + + const dict_col_t* old = dict_table_t::find(old_cols, + ctx->col_map, + ctx->old_n_cols, i); + DBUG_ASSERT(!old || i >= ctx->old_n_cols - DATA_N_SYS_COLS + || old->ind == i + || (ctx->first_alter_pos + && old->ind >= ctx->first_alter_pos - 1)); + + dfield_t* d = dtuple_get_nth_field(row, i); + const dict_col_t* col = dict_table_get_nth_col(user_table, i); + DBUG_ASSERT(!col->is_virtual()); + DBUG_ASSERT(!col->is_dropped()); + DBUG_ASSERT(col->mtype != DATA_SYS); + DBUG_ASSERT(!strcmp((*af)->field_name.str, + dict_table_get_col_name(user_table, i))); + DBUG_ASSERT(old || col->is_added()); + + ut_d(const Create_field* new_field = cf_it++); + /* new_field->field would point to an existing column. + If it is NULL, the column was added by this ALTER TABLE. */ + ut_ad(!new_field->field == !old); + + if (col->is_added()) { + dfield_set_data(d, col->def_val.data, + col->def_val.len); + } else if ((*af)->real_maybe_null()) { + /* Store NULL for nullable 'core' columns. */ + dfield_set_null(d); + } else { + switch ((*af)->type()) { + case MYSQL_TYPE_VARCHAR: + case MYSQL_TYPE_GEOMETRY: + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_LONG_BLOB: + /* Store the empty string for 'core' + variable-length NOT NULL columns. */ + dfield_set_data(d, field_ref_zero, 0); + break; + default: + /* For fixed-length NOT NULL 'core' columns, + get a dummy default value from SQL. Note that + we will preserve the old values of these + columns when updating the metadata + record, to avoid unnecessary updates. */ + ulint len = (*af)->pack_length(); + DBUG_ASSERT(d->type.mtype != DATA_INT + || len <= 8); + row_mysql_store_col_in_innobase_format( + d, d->type.mtype == DATA_INT + ? static_cast<byte*>( + mem_heap_alloc(ctx->heap, len)) + : NULL, true, (*af)->ptr, len, + dict_table_is_comp(user_table)); + ut_ad(new_field->field->pack_length() == len); + + } + } + + bool update = old && (!ctx->first_alter_pos + || i < ctx->first_alter_pos - 1); + DBUG_ASSERT(!old || col->same_format(*old)); + if (update + && old->prtype == d->type.prtype) { + /* The record is already present in SYS_COLUMNS. */ + } else if (innodb_insert_sys_columns(user_table->id, i, + (*af)->field_name.str, + d->type.mtype, + d->type.prtype, + d->type.len, 0, trx, + update)) { + return true; + } + + i++; + } + + if (innodb_update_cols(user_table, dict_table_encode_n_col( + unsigned(user_table->n_cols) + - DATA_N_SYS_COLS, + user_table->n_v_cols) + | (user_table->flags & DICT_TF_COMPACT) << 31, + trx)) { + return true; + } + + if (ctx->first_alter_pos) { +add_all_virtual: + for (uint i = 0; i < user_table->n_v_cols; i++) { + if (innobase_add_one_virtual( + user_table, + dict_table_get_v_col_name(user_table, i), + &user_table->v_cols[i], trx)) { + return true; + } + } + } else if (ha_alter_info->handler_flags & ALTER_DROP_VIRTUAL_COLUMN) { + if (innobase_instant_drop_cols(user_table->id, 65536, trx)) { + return true; + } + goto add_all_virtual; + } else if ((ha_alter_info->handler_flags & ALTER_ADD_VIRTUAL_COLUMN) + && innobase_add_virtual_try(ha_alter_info, user_table, + trx)) { + return true; + } + + unsigned i = unsigned(user_table->n_cols) - DATA_N_SYS_COLS; + DBUG_ASSERT(i >= altered_table->s->stored_fields); + DBUG_ASSERT(i <= altered_table->s->stored_fields + 1); + if (i > altered_table->s->fields) { + const dict_col_t& fts_doc_id = user_table->cols[i - 1]; + DBUG_ASSERT(!strcmp(fts_doc_id.name(*user_table), + FTS_DOC_ID_COL_NAME)); + DBUG_ASSERT(!fts_doc_id.is_nullable()); + DBUG_ASSERT(fts_doc_id.len == 8); + dfield_set_data(dtuple_get_nth_field(row, i - 1), + field_ref_zero, fts_doc_id.len); + } + byte trx_id[DATA_TRX_ID_LEN], roll_ptr[DATA_ROLL_PTR_LEN]; + dfield_set_data(dtuple_get_nth_field(row, i++), field_ref_zero, + DATA_ROW_ID_LEN); + dfield_set_data(dtuple_get_nth_field(row, i++), trx_id, sizeof trx_id); + dfield_set_data(dtuple_get_nth_field(row, i),roll_ptr,sizeof roll_ptr); + DBUG_ASSERT(i + 1 == user_table->n_cols); + + trx_write_trx_id(trx_id, trx->id); + /* The DB_ROLL_PTR will be assigned later, when allocating undo log. + Silence a Valgrind warning in dtuple_validate() when + row_ins_clust_index_entry_low() searches for the insert position. */ + memset(roll_ptr, 0, sizeof roll_ptr); + + dtuple_t* entry = index->instant_metadata(*row, ctx->heap); + mtr.start(); + index->set_modified(mtr); + btr_pcur_t pcur; + btr_pcur_open_at_index_side(true, index, BTR_MODIFY_TREE, &pcur, true, + 0, &mtr); + ut_ad(btr_pcur_is_before_first_on_page(&pcur)); + btr_pcur_move_to_next_on_page(&pcur); + + buf_block_t* block = btr_pcur_get_block(&pcur); + ut_ad(page_is_leaf(block->frame)); + ut_ad(!page_has_prev(block->frame)); + ut_ad(!buf_block_get_page_zip(block)); + const rec_t* rec = btr_pcur_get_rec(&pcur); + que_thr_t* thr = pars_complete_graph_for_exec( + NULL, trx, ctx->heap, NULL); + + dberr_t err = DB_SUCCESS; + if (rec_is_metadata(rec, *index)) { + ut_ad(page_rec_is_user_rec(rec)); + if (!page_has_next(block->frame) + && page_rec_is_last(rec, block->frame)) { + goto empty_table; + } + + if (!metadata_changed) { + goto func_exit; + } + + /* Ensure that the root page is in the correct format. */ + buf_block_t* root = btr_root_block_get(index, RW_X_LATCH, + &mtr); + DBUG_ASSERT(root); + DBUG_ASSERT(!root->page.encrypted); + if (fil_page_get_type(root->frame) != FIL_PAGE_TYPE_INSTANT) { + DBUG_ASSERT(!"wrong page type"); + err = DB_CORRUPTION; + goto func_exit; + } + + btr_set_instant(root, *index, &mtr); + + /* Extend the record with any added columns. */ + uint n = uint(index->n_fields) - n_old_fields; + /* Reserve room for DB_TRX_ID,DB_ROLL_PTR and any + non-updated off-page columns in case they are moved off + page as a result of the update. */ + const unsigned f = user_table->instant != NULL; + upd_t* update = upd_create(index->n_fields + f, ctx->heap); + update->n_fields = n + f; + update->info_bits = f + ? REC_INFO_METADATA_ALTER + : REC_INFO_METADATA_ADD; + if (f) { + upd_field_t* uf = upd_get_nth_field(update, 0); + uf->field_no = index->first_user_field(); + uf->new_val = entry->fields[uf->field_no]; + DBUG_ASSERT(!dfield_is_ext(&uf->new_val)); + DBUG_ASSERT(!dfield_is_null(&uf->new_val)); + } + + /* Add the default values for instantly added columns */ + unsigned j = f; + + for (unsigned k = n_old_fields; k < index->n_fields; k++) { + upd_field_t* uf = upd_get_nth_field(update, j++); + uf->field_no = k + f; + uf->new_val = entry->fields[k + f]; + + ut_ad(j <= n + f); + } + + ut_ad(j == n + f); + + ulint* offsets = NULL; + mem_heap_t* offsets_heap = NULL; + big_rec_t* big_rec; + err = btr_cur_pessimistic_update( + BTR_NO_LOCKING_FLAG | BTR_KEEP_POS_FLAG, + btr_pcur_get_btr_cur(&pcur), + &offsets, &offsets_heap, ctx->heap, + &big_rec, update, UPD_NODE_NO_ORD_CHANGE, + thr, trx->id, &mtr); + + offsets = rec_get_offsets( + btr_pcur_get_rec(&pcur), index, offsets, + true, ULINT_UNDEFINED, &offsets_heap); + if (big_rec) { + if (err == DB_SUCCESS) { + err = btr_store_big_rec_extern_fields( + &pcur, offsets, big_rec, &mtr, + BTR_STORE_UPDATE); + } + + dtuple_big_rec_free(big_rec); + } + if (offsets_heap) { + mem_heap_free(offsets_heap); + } + btr_pcur_close(&pcur); + goto func_exit; + } else if (page_rec_is_supremum(rec)) { +empty_table: + /* The table is empty. */ + ut_ad(fil_page_index_page_check(block->frame)); + ut_ad(!page_has_siblings(block->frame)); + ut_ad(block->page.id.page_no() == index->page); + /* MDEV-17383: free metadata BLOBs! */ + btr_page_empty(block, NULL, index, 0, &mtr); + index->clear_instant_alter(); + goto func_exit; + } else if (!user_table->is_instant()) { + ut_ad(!user_table->not_redundant()); + goto func_exit; + } + + /* Convert the table to the instant ALTER TABLE format. */ + mtr.commit(); + mtr.start(); + index->set_modified(mtr); + if (buf_block_t* root = btr_root_block_get(index, RW_SX_LATCH, &mtr)) { + if (root->page.encrypted + || fil_page_get_type(root->frame) != FIL_PAGE_INDEX) { + DBUG_ASSERT(!"wrong page type"); + goto err_exit; + } + + btr_set_instant(root, *index, &mtr); + mtr.commit(); + mtr.start(); + index->set_modified(mtr); + err = row_ins_clust_index_entry_low( + BTR_NO_LOCKING_FLAG, BTR_MODIFY_TREE, index, + index->n_uniq, entry, 0, thr, false); + } else { +err_exit: + err = DB_CORRUPTION; + } + +func_exit: + mtr.commit(); + + if (err != DB_SUCCESS) { + my_error_innodb(err, table->s->table_name.str, + user_table->flags); + return true; + } + + return false; } /** Adjust the create index column number from "New table" to @@ -4901,11 +6166,9 @@ prepare_inplace_alter_table_dict( const char* path = thd_innodb_tmpdir( ctx->prebuilt->trx->mysql_thd); - index_defs = innobase_create_key_defs( - ctx->heap, ha_alter_info, altered_table, ctx->num_to_add_index, + index_defs = ctx->create_key_defs( + ha_alter_info, altered_table, num_fts_index, - dict_index_is_auto_gen_clust(dict_table_get_first_index( - ctx->new_table)), fts_doc_id_col, add_fts_doc_id, add_fts_doc_id_idx, old_table); @@ -5094,10 +6357,10 @@ new_clustered_failed: } if (altered_table->versioned()) { - if (i == altered_table->s->row_start_field) { + if (i == altered_table->s->vers.start_fieldno) { field_type |= DATA_VERS_START; } else if (i == - altered_table->s->row_end_field) { + altered_table->s->vers.end_fieldno) { field_type |= DATA_VERS_END; } else if (!(field->flags & VERS_UPDATE_UNVERSIONED_FLAG)) { @@ -5273,20 +6536,12 @@ new_clustered_failed: == !!new_clustered); } - if (ctx->need_rebuild() && user_table->supports_instant()) { - if (!instant_alter_column_possible(ha_alter_info, old_table)) { - goto not_instant_add_column; - } - - for (uint i = uint(ctx->old_table->n_cols) - DATA_N_SYS_COLS; - i--; ) { - if (ctx->col_map[i] != i) { - goto not_instant_add_column; - } - } - - DBUG_ASSERT(ctx->new_table->n_cols > ctx->old_table->n_cols); + DBUG_ASSERT(!ctx->need_rebuild() + || !ctx->new_table->persistent_autoinc); + if (ctx->need_rebuild() && instant_alter_column_possible( + *user_table, ha_alter_info, old_table, altered_table, + trx_is_strict(ctx->trx))) { for (uint a = 0; a < ctx->num_to_add_index; a++) { ctx->add_index[a]->table = ctx->new_table; ctx->add_index[a] = dict_index_add_to_cache( @@ -5294,6 +6549,7 @@ new_clustered_failed: &error, add_v); ut_a(error == DB_SUCCESS); } + DBUG_ASSERT(ha_alter_info->key_count /* hidden GEN_CLUST_INDEX in InnoDB */ + dict_index_is_auto_gen_clust( @@ -5305,6 +6561,7 @@ new_clustered_failed: altered_table->key_info) != FTS_EXIST_DOC_ID_INDEX) == ctx->num_to_add_index); + ctx->num_to_add_index = 0; ctx->add_index = NULL; @@ -5335,26 +6592,11 @@ new_clustered_failed: DBUG_ASSERT(!strcmp((*af)->field_name.str, dict_table_get_col_name(ctx->new_table, i))); - DBUG_ASSERT(!col->is_instant()); + DBUG_ASSERT(!col->is_added()); if (new_field->field) { - ut_d(const dict_col_t* old_col - = dict_table_get_nth_col(user_table, i)); - ut_d(const dict_index_t* index - = user_table->indexes.start); - DBUG_SLOW_ASSERT(col->mtype == old_col->mtype); - ut_ad(col->prtype == old_col->prtype - || col->prtype - == (old_col->prtype & ~DATA_VERSIONED)); - DBUG_SLOW_ASSERT(col->mbminlen - == old_col->mbminlen); - DBUG_SLOW_ASSERT(col->mbmaxlen - == old_col->mbmaxlen); - DBUG_SLOW_ASSERT(col->len >= old_col->len); - DBUG_SLOW_ASSERT(old_col->is_instant() - == (dict_col_get_clust_pos( - old_col, index) - >= index->n_core_fields)); + /* This is a pre-existing column, + possibly at a different position. */ } else if ((*af)->is_real_null()) { /* DEFAULT NULL */ col->def_val.len = UNIV_SQL_NULL; @@ -5422,11 +6664,15 @@ new_clustered_failed: ctx->new_table, i), FTS_DOC_ID_COL_NAME))); + if (altered_table->found_next_number_field) { + ctx->new_table->persistent_autoinc + = ctx->old_table->persistent_autoinc; + } + ctx->prepare_instant(); } if (ctx->need_rebuild()) { -not_instant_add_column: DBUG_ASSERT(ctx->need_rebuild()); DBUG_ASSERT(!ctx->is_instant()); DBUG_ASSERT(num_fts_index <= 1); @@ -5560,7 +6806,6 @@ new_table_failed: ut_ad(new_clust_index->n_core_null_bytes == UT_BITS_IN_BYTES(new_clust_index->n_nullable)); - DBUG_ASSERT(!ctx->new_table->persistent_autoinc); if (const Field* ai = altered_table->found_next_number_field) { const unsigned col_no = innodb_col_no(ai); @@ -5971,6 +7216,120 @@ innobase_check_foreign_key_index( return(false); } +/** +Rename a given index in the InnoDB data dictionary. + +@param index index to rename +@param new_name new name of the index +@param[in,out] trx dict transaction to use, not going to be committed here + +@retval true Failure +@retval false Success */ +static MY_ATTRIBUTE((warn_unused_result)) +bool +rename_index_try( + const dict_index_t* index, + const char* new_name, + trx_t* trx) +{ + DBUG_ENTER("rename_index_try"); + + ut_ad(mutex_own(&dict_sys->mutex)); + ut_ad(rw_lock_own(dict_operation_lock, RW_LOCK_X)); + ut_ad(trx->dict_operation_lock_mode == RW_X_LATCH); + + pars_info_t* pinfo; + dberr_t err; + + pinfo = pars_info_create(); + + pars_info_add_ull_literal(pinfo, "table_id", index->table->id); + pars_info_add_ull_literal(pinfo, "index_id", index->id); + pars_info_add_str_literal(pinfo, "new_name", new_name); + + trx->op_info = "Renaming an index in SYS_INDEXES"; + + DBUG_EXECUTE_IF( + "ib_rename_index_fail1", + DBUG_SET("+d,innodb_report_deadlock"); + ); + + err = que_eval_sql( + pinfo, + "PROCEDURE RENAME_INDEX_IN_SYS_INDEXES () IS\n" + "BEGIN\n" + "UPDATE SYS_INDEXES SET\n" + "NAME = :new_name\n" + "WHERE\n" + "ID = :index_id AND\n" + "TABLE_ID = :table_id;\n" + "END;\n", + FALSE, trx); /* pinfo is freed by que_eval_sql() */ + + DBUG_EXECUTE_IF( + "ib_rename_index_fail1", + DBUG_SET("-d,innodb_report_deadlock"); + ); + + trx->op_info = ""; + + if (err != DB_SUCCESS) { + my_error_innodb(err, index->table->name.m_name, 0); + DBUG_RETURN(true); + } + + DBUG_RETURN(false); +} + + +/** +Rename a given index in the InnoDB data dictionary cache. + +@param[in,out] index index to rename +@param new_name new index name +*/ +static +void +innobase_rename_index_cache(dict_index_t* index, const char* new_name) +{ + DBUG_ENTER("innobase_rename_index_cache"); + + ut_ad(mutex_own(&dict_sys->mutex)); + ut_ad(rw_lock_own(dict_operation_lock, RW_LOCK_X)); + + size_t old_name_len = strlen(index->name); + size_t new_name_len = strlen(new_name); + + if (old_name_len < new_name_len) { + index->name = static_cast<char*>( + mem_heap_alloc(index->heap, new_name_len + 1)); + } + + memcpy(const_cast<char*>(index->name()), new_name, new_name_len + 1); + + DBUG_VOID_RETURN; +} + + +/** Rename the index name in cache. +@param[in] ctx alter context +@param[in] ha_alter_info Data used during inplace alter. */ +static void innobase_rename_indexes_cache( + const ha_innobase_inplace_ctx* ctx, + const Alter_inplace_info* ha_alter_info) +{ + DBUG_ASSERT(ha_alter_info->handler_flags & ALTER_RENAME_INDEX); + + for (const Alter_inplace_info::Rename_key_pair& pair : + ha_alter_info->rename_keys) { + dict_index_t* index = dict_table_get_index_on_name( + ctx->old_table, pair.old_key->name.str); + ut_ad(index); + + innobase_rename_index_cache(index, pair.new_key->name.str); + } +} + /** Fill the stored column information in s_cols list. @param[in] altered_table mysql table object @@ -6048,8 +7407,6 @@ ha_innobase::prepare_inplace_alter_table( { dict_index_t** drop_index; /*!< Index to be dropped */ ulint n_drop_index; /*!< Number of indexes to drop */ - dict_index_t** rename_index; /*!< Indexes to be dropped */ - ulint n_rename_index; /*!< Number of indexes to rename */ dict_foreign_t**drop_fk; /*!< Foreign key constraints to drop */ ulint n_drop_fk; /*!< Number of foreign keys to drop */ dict_foreign_t**add_fk = NULL; /*!< Foreign key constraints to drop */ @@ -6565,9 +7922,6 @@ check_if_can_drop_indexes: } } - n_rename_index = 0; - rename_index = NULL; - n_add_fk = 0; if (ha_alter_info->handler_flags @@ -6621,6 +7975,20 @@ err_exit: } } + if (ha_alter_info->handler_flags & ALTER_RENAME_INDEX) { + for (const Alter_inplace_info::Rename_key_pair& pair : + ha_alter_info->rename_keys) { + dict_index_t* index = dict_table_get_index_on_name( + indexed_table, pair.old_key->name.str); + + if (!index || index->is_corrupted()) { + my_error(ER_INDEX_CORRUPT, MYF(0), + index->name()); + goto err_exit; + } + } + } + const ha_table_option_struct& alt_opt= *ha_alter_info->create_info->option_struct; @@ -6636,7 +8004,6 @@ err_exit: = new ha_innobase_inplace_ctx( m_prebuilt, drop_index, n_drop_index, - rename_index, n_rename_index, drop_fk, n_drop_fk, add_fk, n_add_fk, ha_alter_info->online, @@ -6764,7 +8131,6 @@ found_col: ha_alter_info->handler_ctx = new ha_innobase_inplace_ctx( m_prebuilt, drop_index, n_drop_index, - rename_index, n_rename_index, drop_fk, n_drop_fk, add_fk, n_add_fk, ha_alter_info->online, heap, m_prebuilt->table, col_names, @@ -7423,7 +8789,6 @@ innobase_drop_foreign_try( @param[in] user_table InnoDB table that was being altered @param[in] trx data dictionary transaction @param[in] table_name Table name in MySQL -@param[in] nth_col 0-based index of the column @param[in] from old column name @param[in] to new column name @param[in] new_clustered whether the table has been rebuilt @@ -7436,12 +8801,10 @@ innobase_rename_column_try( const dict_table_t* user_table, trx_t* trx, const char* table_name, - ulint nth_col, const char* from, const char* to, bool new_clustered) { - pars_info_t* info; dberr_t error; DBUG_ENTER("innobase_rename_column_try"); @@ -7455,34 +8818,7 @@ innobase_rename_column_try( goto rename_foreign; } - info = pars_info_create(); - - pars_info_add_ull_literal(info, "tableid", user_table->id); - pars_info_add_int4_literal(info, "nth", nth_col); - pars_info_add_str_literal(info, "new", to); - - trx->op_info = "renaming column in SYS_COLUMNS"; - - error = que_eval_sql( - info, - "PROCEDURE RENAME_SYS_COLUMNS_PROC () IS\n" - "BEGIN\n" - "UPDATE SYS_COLUMNS SET NAME=:new\n" - "WHERE TABLE_ID=:tableid\n" - "AND POS=:nth;\n" - "END;\n", - FALSE, trx); - - DBUG_EXECUTE_IF("ib_rename_column_error", - error = DB_OUT_OF_FILE_SPACE;); - - if (error != DB_SUCCESS) { -err_exit: - my_error_innodb(error, table_name, 0); - trx->error_state = DB_SUCCESS; - trx->op_info = ""; - DBUG_RETURN(true); - } + error = DB_SUCCESS; trx->op_info = "renaming column in SYS_FIELDS"; @@ -7500,19 +8836,16 @@ err_exit: } for (ulint i = 0; i < dict_index_get_n_fields(index); i++) { - const dict_field_t* field - = dict_index_get_nth_field(index, i); - if (my_strcasecmp(system_charset_info, field->name, - from)) { + const dict_field_t& f = index->fields[i]; + DBUG_ASSERT(!f.name == f.col->is_dropped()); + + if (!f.name || my_strcasecmp(system_charset_info, + f.name, from)) { continue; } - info = pars_info_create(); - - ulint pos = i; - if (has_prefixes) { - pos = (pos << 16) + field->prefix_len; - } + pars_info_t* info = pars_info_create(); + ulint pos = has_prefixes ? i << 16 | f.prefix_len : i; pars_info_add_ull_literal(info, "indexid", index->id); pars_info_add_int4_literal(info, "nth", pos); @@ -7527,6 +8860,8 @@ err_exit: "AND POS=:nth;\n" "END;\n", FALSE, trx); + DBUG_EXECUTE_IF("ib_rename_column_error", + error = DB_OUT_OF_FILE_SPACE;); if (error != DB_SUCCESS) { goto err_exit; @@ -7534,6 +8869,14 @@ err_exit: } } + if (error != DB_SUCCESS) { +err_exit: + my_error_innodb(error, table_name, 0); + trx->error_state = DB_SUCCESS; + trx->op_info = ""; + DBUG_RETURN(true); + } + rename_foreign: trx->op_info = "renaming column in SYS_FOREIGN_COLS"; @@ -7554,7 +8897,7 @@ rename_foreign: continue; } - info = pars_info_create(); + pars_info_t* info = pars_info_create(); pars_info_add_str_literal(info, "id", foreign->id); pars_info_add_int4_literal(info, "nth", i); @@ -7596,7 +8939,7 @@ rename_foreign: continue; } - info = pars_info_create(); + pars_info_t* info = pars_info_create(); pars_info_add_str_literal(info, "id", foreign->id); pars_info_add_int4_literal(info, "nth", i); @@ -7655,6 +8998,7 @@ innobase_rename_columns_try( uint i = 0; ulint num_v = 0; + DBUG_ASSERT(ctx->need_rebuild()); DBUG_ASSERT(ha_alter_info->handler_flags & ALTER_COLUMN_NAME); @@ -7668,17 +9012,10 @@ innobase_rename_columns_try( while (Create_field* cf = cf_it++) { if (cf->field == *fp) { - ulint col_n = is_virtual - ? dict_create_v_col_pos( - num_v, i) - : i - num_v; - if (innobase_rename_column_try( ctx->old_table, trx, table_name, - col_n, cf->field->field_name.str, - cf->field_name.str, - ctx->need_rebuild())) { + cf->field_name.str, true)) { return(true); } goto processed_field; @@ -7697,63 +9034,92 @@ processed_field: return(false); } +/** Convert field type and length to InnoDB format */ +static void get_type(const Field& f, ulint& prtype, ulint& mtype, ulint& len) +{ + mtype = get_innobase_type_from_mysql_type(&prtype, &f); + len = f.pack_length(); + prtype |= f.type(); + if (f.type() == MYSQL_TYPE_VARCHAR) { + auto l = static_cast<const Field_varstring&>(f).length_bytes; + len -= l; + if (l == 2) prtype |= DATA_LONG_TRUE_VARCHAR; + } + if (!f.real_maybe_null()) prtype |= DATA_NOT_NULL; + if (f.binary()) prtype |= DATA_BINARY_TYPE; + if (f.table->versioned()) { + if (&f == f.table->field[f.table->s->vers.start_fieldno]) { + prtype |= DATA_VERS_START; + } else if (&f == f.table->field[f.table->s->vers.end_fieldno]) { + prtype |= DATA_VERS_END; + } else if (!(f.flags & VERS_UPDATE_UNVERSIONED_FLAG)) { + prtype |= DATA_VERSIONED; + } + } + if (!f.stored_in_db()) prtype |= DATA_VIRTUAL; + + if (dtype_is_string_type(mtype)) { + prtype |= ulint(f.charset()->number) << 16; + } +} + /** Enlarge a column in the data dictionary tables. @param user_table InnoDB table that was being altered @param trx data dictionary transaction @param table_name Table name in MySQL -@param nth_col 0-based index of the column -@param new_len new column length, in bytes +@param pos 0-based index to user_table->cols[] or user_table->v_cols[] +@param f new column @param is_v if it's a virtual column @retval true Failure @retval false Success */ static MY_ATTRIBUTE((nonnull, warn_unused_result)) bool -innobase_enlarge_column_try( -/*========================*/ +innobase_rename_or_enlarge_column_try( const dict_table_t* user_table, trx_t* trx, const char* table_name, - ulint nth_col, - ulint new_len, + ulint pos, + const Field& f, bool is_v) { - pars_info_t* info; - dberr_t error; -#ifdef UNIV_DEBUG dict_col_t* col; -#endif /* UNIV_DEBUG */ - dict_v_col_t* v_col; - ulint pos; - DBUG_ENTER("innobase_enlarge_column_try"); + DBUG_ENTER("innobase_rename_or_enlarge_column_try"); DBUG_ASSERT(trx_get_dict_operation(trx) == TRX_DICT_OP_INDEX); ut_ad(trx->dict_operation_lock_mode == RW_X_LATCH); ut_ad(mutex_own(&dict_sys->mutex)); ut_ad(rw_lock_own(dict_operation_lock, RW_LOCK_X)); + ulint n_base; + if (is_v) { - v_col = dict_table_get_nth_v_col(user_table, nth_col); + dict_v_col_t* v_col= dict_table_get_nth_v_col(user_table, pos); pos = dict_create_v_col_pos(v_col->v_pos, v_col->m_col.ind); -#ifdef UNIV_DEBUG col = &v_col->m_col; -#endif /* UNIV_DEBUG */ + n_base = v_col->num_base; } else { -#ifdef UNIV_DEBUG - col = dict_table_get_nth_col(user_table, nth_col); -#endif /* UNIV_DEBUG */ - pos = nth_col; + col = dict_table_get_nth_col(user_table, pos); + n_base = 0; } + ulint prtype, mtype, len; + get_type(f, prtype, mtype, len); + DBUG_ASSERT(!dtype_is_string_type(col->mtype) + || col->mbminlen == f.charset()->mbminlen); + DBUG_ASSERT(col->len <= len); + #ifdef UNIV_DEBUG - ut_ad(col->len < new_len); - switch (col->mtype) { + switch (mtype) { + case DATA_FIXBINARY: + case DATA_CHAR: case DATA_MYSQL: /* NOTE: we could allow this when !(prtype & DATA_BINARY_TYPE) and ROW_FORMAT is not REDUNDANT and mbminlen<mbmaxlen. That is, we treat a UTF-8 CHAR(n) column somewhat like a VARCHAR. */ - ut_error; + ut_ad(col->len == len); + break; case DATA_BINARY: case DATA_VARCHAR: case DATA_VARMYSQL: @@ -7761,58 +9127,62 @@ innobase_enlarge_column_try( case DATA_BLOB: break; default: - ut_error; + ut_ad(col->prtype == prtype); + ut_ad(col->mtype == mtype); + ut_ad(col->len == len); } #endif /* UNIV_DEBUG */ - info = pars_info_create(); - - pars_info_add_ull_literal(info, "tableid", user_table->id); - pars_info_add_int4_literal(info, "nth", pos); - pars_info_add_int4_literal(info, "new", new_len); - - trx->op_info = "resizing column in SYS_COLUMNS"; - error = que_eval_sql( - info, - "PROCEDURE RESIZE_SYS_COLUMNS_PROC () IS\n" - "BEGIN\n" - "UPDATE SYS_COLUMNS SET LEN=:new\n" - "WHERE TABLE_ID=:tableid AND POS=:nth;\n" - "END;\n", - FALSE, trx); - - DBUG_EXECUTE_IF("ib_resize_column_error", - error = DB_OUT_OF_FILE_SPACE;); - - trx->op_info = ""; - trx->error_state = DB_SUCCESS; + const char* col_name = col->name(*user_table); + const bool same_name = !strcmp(col_name, f.field_name.str); - if (error != DB_SUCCESS) { - my_error_innodb(error, table_name, 0); + if (!same_name + && innobase_rename_column_try(user_table, trx, table_name, + col_name, f.field_name.str, + false)) { DBUG_RETURN(true); } - DBUG_RETURN(false); + if (same_name + && col->prtype == prtype && col->mtype == mtype + && col->len == len) { + DBUG_RETURN(false); + } + + DBUG_RETURN(innodb_insert_sys_columns(user_table->id, pos, + f.field_name.str, + mtype, prtype, len, + n_base, trx, true)); } -/** Enlarge columns in the data dictionary tables. +/** Rename or enlarge columns in the data dictionary cache +as part of commit_try_norebuild(). @param ha_alter_info Data used during in-place alter. -@param table the TABLE -@param user_table InnoDB table that was being altered +@param ctx In-place ALTER TABLE context +@param altered_table metadata after ALTER TABLE +@param table metadata before ALTER TABLE @param trx data dictionary transaction @param table_name Table name in MySQL @retval true Failure @retval false Success */ static MY_ATTRIBUTE((nonnull, warn_unused_result)) bool -innobase_enlarge_columns_try( -/*=========================*/ +innobase_rename_or_enlarge_columns_try( Alter_inplace_info* ha_alter_info, + ha_innobase_inplace_ctx*ctx, + const TABLE* altered_table, const TABLE* table, - const dict_table_t* user_table, trx_t* trx, const char* table_name) { + DBUG_ENTER("innobase_rename_or_enlarge_columns_try"); + + if (!(ha_alter_info->handler_flags + & (ALTER_COLUMN_EQUAL_PACK_LENGTH + | ALTER_COLUMN_NAME))) { + DBUG_RETURN(false); + } + List_iterator_fast<Create_field> cf_it( ha_alter_info->alter_info->create_list); ulint i = 0; @@ -7823,34 +9193,35 @@ innobase_enlarge_columns_try( ulint idx = is_v ? num_v++ : i - num_v; cf_it.rewind(); + Field** af = altered_table->field; while (Create_field* cf = cf_it++) { if (cf->field == *fp) { - if ((*fp)->is_equal(cf) - == IS_EQUAL_PACK_LENGTH - && innobase_enlarge_column_try( - user_table, trx, table_name, - idx, static_cast<ulint>(cf->length), is_v)) { - return(true); + if (innobase_rename_or_enlarge_column_try( + ctx->old_table, trx, table_name, + idx, **af, is_v)) { + DBUG_RETURN(true); } - break; } + af++; } } - return(false); + DBUG_RETURN(false); } /** Rename or enlarge columns in the data dictionary cache as part of commit_cache_norebuild(). @param ha_alter_info Data used during in-place alter. -@param table the TABLE +@param altered_table metadata after ALTER TABLE +@param table metadata before ALTER TABLE @param user_table InnoDB table that was being altered */ static MY_ATTRIBUTE((nonnull)) 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) { @@ -7869,30 +9240,37 @@ innobase_rename_or_enlarge_columns_cache( const bool is_virtual = !(*fp)->stored_in_db(); cf_it.rewind(); + Field** af = altered_table->field; while (Create_field* cf = cf_it++) { if (cf->field != *fp) { + af++; continue; } ulint col_n = is_virtual ? num_v : i - num_v; - - if ((*fp)->is_equal(cf) == IS_EQUAL_PACK_LENGTH) { - if (is_virtual) { - dict_table_get_nth_v_col( - user_table, col_n)->m_col.len - = cf->length; - } else { - dict_table_get_nth_col( - user_table, col_n)->len - = cf->length; - } - } + dict_col_t *col = is_virtual + ? &dict_table_get_nth_v_col(user_table, col_n) + ->m_col + : dict_table_get_nth_col(user_table, col_n); + const bool is_string= dtype_is_string_type(col->mtype); + DBUG_ASSERT(col->mbminlen + == (is_string + ? (*af)->charset()->mbminlen : 0)); + ulint prtype, mtype, len; + get_type(**af, prtype, mtype, len); + DBUG_ASSERT(is_string == dtype_is_string_type(mtype)); + + col->prtype = prtype; + col->mtype = mtype; + col->len = len; + col->mbmaxlen = is_string + ? (*af)->charset()->mbmaxlen : 0; if ((*fp)->flags & FIELD_IS_RENAMED) { dict_mem_table_col_rename( user_table, col_n, cf->field->field_name.str, - cf->field_name.str, is_virtual); + (*af)->field_name.str, is_virtual); } break; @@ -8546,6 +9924,38 @@ commit_try_rebuild( } } +/** Rename indexes in dictionary. +@param[in] ctx alter info context +@param[in] ha_alter_info Operation used during inplace alter +@param[out] trx transaction to change the index name + in dictionary +@return true if it failed to rename +@return false if it is success. */ +static +bool +rename_indexes_try( + const ha_innobase_inplace_ctx* ctx, + const Alter_inplace_info* ha_alter_info, + trx_t* trx) +{ + DBUG_ASSERT(ha_alter_info->handler_flags & ALTER_RENAME_INDEX); + + for (const Alter_inplace_info::Rename_key_pair& pair : + ha_alter_info->rename_keys) { + dict_index_t* index = dict_table_get_index_on_name( + ctx->old_table, pair.old_key->name.str); + // This was checked previously in + // ha_innobase::prepare_inplace_alter_table() + ut_ad(index); + + if (rename_index_try(index, pair.new_key->name.str, trx)) { + return true; + } + } + + return false; +} + /** Apply the changes made during commit_try_rebuild(), to the data dictionary cache and the file system. @param ctx In-place ALTER TABLE context */ @@ -8797,34 +10207,49 @@ commit_try_norebuild( } } - if ((ha_alter_info->handler_flags - & ALTER_COLUMN_NAME) - && innobase_rename_columns_try(ha_alter_info, ctx, old_table, - trx, table_name)) { + if (innobase_rename_or_enlarge_columns_try(ha_alter_info, ctx, + altered_table, old_table, + trx, table_name)) { DBUG_RETURN(true); } - if ((ha_alter_info->handler_flags - & ALTER_COLUMN_EQUAL_PACK_LENGTH) - && innobase_enlarge_columns_try(ha_alter_info, old_table, - ctx->old_table, trx, table_name)) { + if ((ha_alter_info->handler_flags & ALTER_RENAME_INDEX) + && rename_indexes_try(ctx, ha_alter_info, trx)) { DBUG_RETURN(true); } - if ((ha_alter_info->handler_flags - & ALTER_DROP_VIRTUAL_COLUMN) - && innobase_drop_virtual_try(ha_alter_info, ctx->old_table, trx)) { - DBUG_RETURN(true); + if (ctx->is_instant()) { + DBUG_RETURN(innobase_instant_try(ha_alter_info, ctx, + altered_table, old_table, + trx)); } - if ((ha_alter_info->handler_flags - & ALTER_ADD_VIRTUAL_COLUMN) - && innobase_add_virtual_try(ha_alter_info, ctx->old_table, trx)) { - DBUG_RETURN(true); - } + if (ha_alter_info->handler_flags + & (ALTER_DROP_VIRTUAL_COLUMN | ALTER_ADD_VIRTUAL_COLUMN)) { + if ((ha_alter_info->handler_flags & ALTER_DROP_VIRTUAL_COLUMN) + && innobase_drop_virtual_try(ha_alter_info, ctx->old_table, + trx)) { + DBUG_RETURN(true); + } - if (innobase_add_instant_try(ctx, altered_table, old_table, trx)) { - DBUG_RETURN(true); + if ((ha_alter_info->handler_flags & ALTER_ADD_VIRTUAL_COLUMN) + && innobase_add_virtual_try(ha_alter_info, ctx->old_table, + trx)) { + DBUG_RETURN(true); + } + + ulint n_col = unsigned(ctx->old_table->n_cols) + - DATA_N_SYS_COLS; + ulint n_v_col = unsigned(ctx->old_table->n_v_cols) + + ctx->num_to_add_vcol - ctx->num_to_drop_vcol; + + if (innodb_update_cols( + ctx->old_table, + dict_table_encode_n_col(n_col, n_v_col) + | unsigned(ctx->old_table->flags & DICT_TF_COMPACT) + << 31, trx)) { + DBUG_RETURN(true); + } } DBUG_RETURN(false); @@ -8834,6 +10259,7 @@ commit_try_norebuild( after a successful commit_try_norebuild() call. @param ha_alter_info algorithm=inplace context @param ctx In-place ALTER TABLE context for the current partition +@param altered_table the TABLE after the ALTER @param table the TABLE before the ALTER @param trx Data dictionary transaction (will be started and committed, for DROP INDEX) @@ -8844,6 +10270,7 @@ commit_cache_norebuild( /*===================*/ Alter_inplace_info* ha_alter_info, ha_innobase_inplace_ctx*ctx, + const TABLE* altered_table, const TABLE* table, trx_t* trx) { @@ -8866,11 +10293,17 @@ commit_cache_norebuild( bool update = !(space->flags & FSP_FLAGS_MASK_PAGE_COMPRESSION); mutex_enter(&fil_system.mutex); - space->flags = (~FSP_FLAGS_MASK_MEM_COMPRESSION_LEVEL - & (space->flags - | FSP_FLAGS_MASK_PAGE_COMPRESSION)) - | ctx->page_compression_level + space->flags &= ~FSP_FLAGS_MASK_MEM_COMPRESSION_LEVEL; + space->flags |= ctx->page_compression_level << FSP_FLAGS_MEM_COMPRESSION_LEVEL; + if (!space->full_crc32()) { + space->flags + |= FSP_FLAGS_MASK_PAGE_COMPRESSION; + } else if (!space->is_compressed()) { + space->flags + |= innodb_compression_algorithm + << FSP_FLAGS_FCRC32_POS_COMPRESSED_ALGO; + } mutex_exit(&fil_system.mutex); if (update) { @@ -8888,7 +10321,7 @@ commit_cache_norebuild( mtr.start(); if (buf_block_t* b = buf_page_get( page_id_t(space->id, 0), - page_size_t(space->flags), + space->zip_size(), RW_X_LATCH, &mtr)) { mtr.set_named_space(space); mlog_write_ulint( @@ -8990,13 +10423,57 @@ commit_cache_norebuild( if (!ctx->is_instant()) { innobase_rename_or_enlarge_columns_cache( - ha_alter_info, table, ctx->new_table); + ha_alter_info, altered_table, table, ctx->new_table); + } else { + ut_ad(ctx->col_map); + + if (fts_t* fts = ctx->new_table->fts) { + ut_ad(fts->doc_col != ULINT_UNDEFINED); + ut_ad(ctx->new_table->n_cols > DATA_N_SYS_COLS); + const ulint c = ctx->col_map[fts->doc_col]; + ut_ad(c < ulint(ctx->new_table->n_cols) + - DATA_N_SYS_COLS); + ut_d(const dict_col_t& col = ctx->new_table->cols[c]); + ut_ad(!col.is_nullable()); + ut_ad(!col.is_virtual()); + ut_ad(!col.is_added()); + ut_ad(col.prtype & DATA_UNSIGNED); + ut_ad(col.mtype == DATA_INT); + ut_ad(col.len == 8); + ut_ad(col.ord_part); + fts->doc_col = c; + } + + if (ha_alter_info->handler_flags & ALTER_DROP_STORED_COLUMN) { + const dict_index_t* index = ctx->new_table->indexes.start; + + for (const dict_field_t* f = index->fields, + * const end = f + index->n_fields; + f != end; f++) { + dict_col_t& c = *f->col; + if (c.is_dropped()) { + c.set_dropped(!c.is_nullable(), + DATA_LARGE_MTYPE(c.mtype) + || (!f->fixed_len + && c.len > 255), + f->fixed_len); + } + } + } + + if (!ctx->instant_table->persistent_autoinc) { + ctx->new_table->persistent_autoinc = 0; + } } if (ha_alter_info->handler_flags & ALTER_COLUMN_UNVERSIONED) { vers_change_fields_cache(ha_alter_info, ctx, table); } + if (ha_alter_info->handler_flags & ALTER_RENAME_INDEX) { + innobase_rename_indexes_cache(ctx, ha_alter_info); + } + ctx->new_table->fts_doc_id_index = ctx->new_table->fts ? dict_table_get_index_on_name( @@ -9066,6 +10543,27 @@ alter_stats_norebuild( } } + for (const Alter_inplace_info::Rename_key_pair& pair : + ha_alter_info->rename_keys) { + dberr_t err = dict_stats_rename_index(ctx->new_table, + pair.old_key->name.str, + pair.new_key->name.str); + + if (err != DB_SUCCESS) { + push_warning_printf( + thd, + Sql_condition::WARN_LEVEL_WARN, + ER_ERROR_ON_RENAME, + "Error renaming an index of table '%s'" + " from '%s' to '%s' in InnoDB persistent" + " statistics storage: %s", + ctx->new_table->name.m_name, + pair.old_key->name.str, + pair.new_key->name.str, + ut_strerr(err)); + } + } + for (i = 0; i < ctx->num_to_add_index; i++) { dict_index_t* index = ctx->add_index[i]; DBUG_ASSERT(index->table == ctx->new_table); @@ -9101,23 +10599,8 @@ alter_stats_rebuild( DBUG_VOID_RETURN; } -#ifndef DBUG_OFF - bool file_unreadable_orig = false; -#endif /* DBUG_OFF */ - - DBUG_EXECUTE_IF( - "ib_rename_index_fail2", - file_unreadable_orig = table->file_unreadable; - table->file_unreadable = true; - ); - dberr_t ret = dict_stats_update(table, DICT_STATS_RECALC_PERSISTENT); - DBUG_EXECUTE_IF( - "ib_rename_index_fail2", - table->file_unreadable = file_unreadable_orig; - ); - if (ret != DB_SUCCESS) { push_warning_printf( thd, @@ -9593,7 +11076,8 @@ foreign_fail: bool fk_fail = innobase_update_foreign_cache( ctx, m_user_thd) != DB_SUCCESS; - if (!commit_cache_norebuild(ha_alter_info, ctx, table, + if (!commit_cache_norebuild(ha_alter_info, ctx, + altered_table, table, trx)) { fk_fail = true; } @@ -9663,6 +11147,9 @@ foreign_fail: } } + /* MDEV-17468: Avoid this at least when ctx->is_instant(). + Currently dict_load_column_low() is the only place where + num_base for virtual columns is assigned to nonzero. */ if (ctx0->num_to_drop_vcol || ctx0->num_to_add_vcol) { DBUG_ASSERT(ctx0->old_table->get_ref_count() == 1); @@ -9680,6 +11167,12 @@ foreign_fail: tb_name[strlen(m_prebuilt->table->name.m_name)] = 0; dict_table_close(m_prebuilt->table, true, false); + if (ctx0->is_instant()) { + for (unsigned i = ctx0->old_n_v_cols; i--; ) { + UT_DELETE(ctx0->old_v_cols[i].v_indexes); + } + const_cast<unsigned&>(ctx0->old_n_v_cols) = 0; + } dict_table_remove_from_cache(m_prebuilt->table); m_prebuilt->table = dict_table_open_on_name( tb_name, TRUE, TRUE, DICT_ERR_IGNORE_NONE); @@ -9764,11 +11257,6 @@ foreign_fail: DBUG_ASSERT(0 == strcmp(ctx->old_table->name.m_name, ctx->tmp_name)); - DBUG_EXECUTE_IF( - "ib_rename_index_fail3", - DBUG_SET("+d,innodb_report_deadlock"); - ); - if (dict_stats_drop_table( ctx->new_table->name.m_name, errstr, sizeof(errstr)) @@ -9784,11 +11272,6 @@ foreign_fail: errstr); } - DBUG_EXECUTE_IF( - "ib_rename_index_fail3", - DBUG_SET("-d,innodb_report_deadlock"); - ); - DBUG_EXECUTE_IF("ib_ddl_crash_before_commit", DBUG_SUICIDE();); |