/** File format constraint for ALTER TABLE */ extern ulong innodb_instant_alter_column_allowed;
staticconstchar *MSG_UNSUPPORTED_ALTER_ONLINE_ON_VIRTUAL_COLUMN= "INPLACE ADD or DROP of virtual columns cannot be " "combined with other ALTER TABLE actions";
/** Operations for creating secondary indexes (no rebuild needed) */ staticconst alter_table_operations INNOBASE_ONLINE_CREATE
= ALTER_ADD_NON_UNIQUE_NON_PRIM_INDEX
| ALTER_ADD_UNIQUE_INDEX;
/** Operations that require filling in default values for columns */ staticconst alter_table_operations INNOBASE_DEFAULTS
= ALTER_COLUMN_NOT_NULLABLE
| ALTER_ADD_STORED_BASE_COLUMN;
/** Operations that require knowledge about row_start, row_end values */ staticconst alter_table_operations INNOBASE_ALTER_VERSIONED_REBUILD
= ALTER_ADD_SYSTEM_VERSIONING
| ALTER_DROP_SYSTEM_VERSIONING;
/** Operations for rebuilding a table in place */ staticconst alter_table_operations INNOBASE_ALTER_REBUILD
= ALTER_ADD_PK_INDEX
| ALTER_DROP_PK_INDEX
| ALTER_OPTIONS /* ALTER_OPTIONS needs to check alter_options_need_rebuild() */
| ALTER_COLUMN_NULLABLE
| INNOBASE_DEFAULTS
| ALTER_STORED_COLUMN_ORDER
| ALTER_DROP_STORED_COLUMN
| ALTER_RECREATE_TABLE /* |ALTER_STORED_COLUMN_TYPE
*/
| INNOBASE_ALTER_VERSIONED_REBUILD
;
/** Operations that require changes to data */ staticconst alter_table_operations INNOBASE_ALTER_DATA
= INNOBASE_ONLINE_CREATE | INNOBASE_ALTER_REBUILD;
/** Operations for altering a table that InnoDB does not care about */ staticconst alter_table_operations INNOBASE_INPLACE_IGNORE
= ALTER_COLUMN_DEFAULT
| ALTER_PARTITIONED
| ALTER_COLUMN_COLUMN_FORMAT
| ALTER_COLUMN_STORAGE_TYPE
| ALTER_CONVERT_TO
| ALTER_VIRTUAL_GCOL_EXPR
| ALTER_DROP_CHECK_CONSTRAINT
| ALTER_RENAME
| ALTER_INDEX_ORDER
| ALTER_COLUMN_INDEX_LENGTH
| ALTER_CHANGE_INDEX_COMMENT
| ALTER_INDEX_IGNORABILITY;
/** Operations on foreign key definitions (changing the schema only) */ staticconst alter_table_operations INNOBASE_FOREIGN_OPERATIONS
= ALTER_DROP_FOREIGN_KEY
| ALTER_ADD_FOREIGN_KEY;
/** Operations that InnoDB cares about and can perform without creating data */ staticconst alter_table_operations INNOBASE_ALTER_NOCREATE
= ALTER_DROP_NON_UNIQUE_NON_PRIM_INDEX
| ALTER_DROP_UNIQUE_INDEX;
/** Operations that InnoDB cares about and can perform without validation */ staticconst alter_table_operations INNOBASE_ALTER_NOVALIDATE
= INNOBASE_ALTER_NOCREATE
| ALTER_VIRTUAL_COLUMN_ORDER
| ALTER_COLUMN_NAME
| INNOBASE_FOREIGN_OPERATIONS
| ALTER_COLUMN_UNVERSIONED
| ALTER_DROP_VIRTUAL_COLUMN;
/** Operations that InnoDB cares about and can perform without rebuild */ staticconst alter_table_operations INNOBASE_ALTER_NOREBUILD
= INNOBASE_ONLINE_CREATE
| INNOBASE_ALTER_NOCREATE;
/** Operations that can be performed instantly, without inplace_alter_table() */ staticconst alter_table_operations INNOBASE_ALTER_INSTANT
= ALTER_VIRTUAL_COLUMN_ORDER
| ALTER_COLUMN_NAME
| ALTER_ADD_VIRTUAL_COLUMN
| INNOBASE_FOREIGN_OPERATIONS
| ALTER_COLUMN_TYPE_CHANGE_BY_ENGINE
| ALTER_COLUMN_UNVERSIONED
| ALTER_RENAME_INDEX
| ALTER_DROP_VIRTUAL_COLUMN;
/** Initialize instant->field_map.
@param[in] table table definition to copy from */ inlinevoid 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; constunsigned u = index.first_user_field();
DBUG_ASSERT(u == oindex.first_user_field());
DBUG_ASSERT(index.n_fields >= oindex.n_fields);
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];
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());
/* Long fixed length can be treated as variable
length fields that needs external storage */ if (fixed_len > DICT_MAX_FIXED_COL_LEN) {
fixed_len = 0;
}
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 constunsigned 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)); unsigned 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 addedcolumnsmustbeplaced lastintheclusteredindex. Keeppre-existingfieldsin
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" == 0);
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).str); 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;
}
}
/* Discard tablespace doesn't remove the instantness fromthetabledefinition.ifn_core_null_byteswasn't
initialized then assign it based on nullable fields */ if (!oindex.table->space
&& oindex.n_core_null_bytes
== dict_index_t::NO_CORE_NULL_BYTES) {
oindex.n_core_null_bytes = static_cast<uint8_t>(
UT_BITS_IN_BYTES(unsigned(oindex.n_nullable)));
}
/* instant will have all fields (including ones for columns thathavebeenorarebeinginstantlydropped)inthesameposition
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));
/* Preserve the default values of previously instantly added
columns, or copy the new default values to this->heap. */ for (uint16_t i = 0; i < table.n_cols; i++) {
dict_col_t& c = cols[i];
/** Find the old column number for the given new column position. @param[in]col_mapcolumnmapfromoldcolumntonewcolumn @param[in]posnewcolumnposition @param[in]nnumberofcolumnspresentinthecolumnmap
@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;
}
if (cols == old_cols) { /* Alter fails before instant operation happens.
So there is no need to do rollback instant operation */ return;
}
dict_index_t* index = indexes.start; /* 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--; ) {
v_cols[i].~dict_v_col_t();
}
if (versioned()) { for (unsigned i = 0; i < n_cols; ++i) { if (cols[i].vers_sys_start()) {
vers_start = i & dict_index_t::MAX_N_FIELDS;
} elseif (cols[i].vers_sys_end()) {
vers_end = i & dict_index_t::MAX_N_FIELDS;
}
}
}
index->fields = old_fields;
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);
my_error(ER_TOO_BIG_ROWSIZE, MYF(0), free_space); break;
} case DB_INVALID_NULL: /* TODO: report the row, as we do for DB_DUPLICATE_KEY */
my_error(ER_INVALID_USE_OF_NULL, MYF(0)); break; case DB_CANT_CREATE_GEOMETRY_OBJECT:
my_error(ER_CANT_CREATE_GEOMETRY_OBJECT, MYF(0)); break; case DB_TABLESPACE_EXISTS:
my_error(ER_TABLESPACE_EXISTS, MYF(0), table); break;
#ifdef UNIV_DEBUG case DB_SUCCESS: case DB_DUPLICATE_KEY: case DB_ONLINE_LOG_TOO_BIG: /* These codes should not be passed here. */
ut_error; #endif/* UNIV_DEBUG */ default:
my_error(ER_GET_ERRNO, MYF(0), error, "InnoDB"); break;
}
}
/** Get the name of an erroneous key. @param[in]error_key_numInnoDBnumberoftheerroneouskey @param[in]ha_alter_infochangesthatwerebeingperformed @param[in]tableInnoDBtable
@return the name of the erroneous key */ static constchar*
get_error_key_name(
ulint error_key_num, const Alter_inplace_info* ha_alter_info, const dict_table_t* table)
{ if (error_key_num == ULINT_UNDEFINED) { return(FTS_DOC_ID_INDEX.str);
} elseif (ha_alter_info->key_count == 0) { return(dict_table_get_first_index(table)->name);
} else { return(ha_alter_info->key_info_buffer[error_key_num].name.str);
}
}
/** Convert field type and length to InnoDB format */ staticvoid get_type(const Field &f, uint &prtype, uint8_t &mtype,
uint16_t &len)
{
mtype= get_innobase_type_from_mysql_type(&prtype, &f);
len= static_cast<uint16_t>(f.pack_length());
prtype|= f.type(); if (f.type() == MYSQL_TYPE_VARCHAR)
{ auto l= static_cast<const Field_varstring&>(f).length_bytes;
len= static_cast<uint16_t>(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; elseif (&f == f.table->field[f.table->s->vers.end_fieldno])
prtype|= DATA_VERS_END; elseif (!(f.flags & VERS_UPDATE_UNVERSIONED_FLAG))
prtype|= DATA_VERSIONED;
}
if (!f.stored_in_db())
prtype|= DATA_VIRTUAL;
if (dtype_is_string_type(mtype))
prtype|= f.charset()->number << 16;
}
struct ha_innobase_inplace_ctx : public inplace_alter_handler_ctx
{ /** Dummy query graph */
que_thr_t*const thr; /** The prebuilt struct of the creating instance */
row_prebuilt_t*& prebuilt; /** InnoDB indexes being created */
dict_index_t** add_index; /** MySQL key numbers for the InnoDB indexes that are being created */ const ulint* add_key_numbers; /** number of InnoDB indexes being created */
ulint num_to_add_index; /** InnoDB indexes being dropped */
dict_index_t** drop_index; /** number of InnoDB indexes being dropped */ const ulint num_to_drop_index; /** InnoDB foreign key constraints being dropped */
dict_foreign_t** drop_fk; /** number of InnoDB foreign key constraints being dropped */ const ulint num_to_drop_fk; /** InnoDB foreign key constraints being added */
dict_foreign_t** add_fk; /** number of InnoDB foreign key constraints being dropped */ const ulint num_to_add_fk; /** whether to create the indexes online */ constbool online; /** memory heap */
mem_heap_t* const heap; /** dictionary transaction */
trx_t* trx; /** original table (if rebuilt, differs from indexed_table) */
dict_table_t* old_table; /** table where the indexes are being created or dropped */
dict_table_t* new_table; /** 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; /** new column names, or NULL if nothing was renamed */ constchar** col_names; /** added AUTO_INCREMENT column position, or ULINT_UNDEFINED */ const ulint add_autoinc; /** default values of ADD and CHANGE COLUMN, or NULL */ const dtuple_t* defaults; /** autoinc sequence to use */
ib_sequence_t sequence; /** temporary table name to use for old table when renaming tables */ constchar* tmp_name; /** whether the order of the clustered index is unchanged */ bool skip_pk_sort; /** number of virtual columns to be added */ unsigned num_to_add_vcol; /** virtual columns to be added */
dict_v_col_t* add_vcol; constchar** add_vcol_name; /** number of virtual columns to be dropped */ unsigned num_to_drop_vcol; /** virtual columns to be dropped */
dict_v_col_t* drop_vcol; constchar** drop_vcol_name; /** ALTER TABLE stage progress recorder */
ut_stage_alter_t* m_stage; /** original number of user columns in the table */ constunsigned old_n_cols; /** original columns of the table */
dict_col_t* const old_cols; /** original column names of the table */ constchar* 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 */ constunsigned old_n_fields; /** original old_table->n_core_fields */ constunsigned old_n_core_fields; /** original number of virtual columns in the table */ constunsigned 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 */ constchar* 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)Alterignoreshouldallowtheconversion irrespectiveofsqlmode. (2)Don'tallowtheconversioninstrictmode
(3) Allow the conversion only in non-strict mode. */ constbool allow_not_null;
/** The page_compression_level attribute, or 0 */ const uint page_compression_level;
/** Indexed columns whose charset-collation is changing
in a way that does not require the table to be rebuilt */
col_collations change_col_collate;
~ha_innobase_inplace_ctx() override
{
UT_DELETE(m_stage); if (instant_table) {
ut_ad(!instant_table->id); while (dict_index_t* index
= UT_LIST_GET_LAST(instant_table->indexes)) {
UT_LIST_REMOVE(instant_table->indexes, index);
index->lock.free();
dict_mem_index_free(index);
} for (unsigned i = old_n_v_cols; i--; ) {
old_v_cols[i].~dict_v_col_t();
} if (instant_table->fts) {
instant_table->fts->~fts_t();
instant_table->fts = nullptr;
}
dict_mem_table_free(instant_table);
}
mem_heap_free(heap);
}
/** Determine if the table will be rebuilt.
@return whether the table will be rebuilt */ bool need_rebuild () const { return(old_table != new_table); }
/** Convert table-rebuilding ALTER to instant ALTER. */ void prepare_instant()
{
DBUG_ASSERT(need_rebuild());
DBUG_ASSERT(!is_instant());
DBUG_ASSERT(old_table->n_cols == old_n_cols);
/** 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. */ void rollback_instant()
{ if (!is_instant()) return;
old_table->rollback_instant(old_n_cols,
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 */ bool is_instant() const
{
DBUG_ASSERT(!instant_table || !instant_table->can_be_evicted); return instant_table;
}
/** Create an index table where indexes are ordered as follows:
IFanewprimarykeyisdefinedforthetableTHEN
1)Newprimarykey 2)Theremainingkeysinkey_info
ELSE
1)AllnewindexesintheordertheyarrivefromMySQL
ENDIF
@return key definitions */
MY_ATTRIBUTE((nonnull(2,3), 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 ha_table_option_struct* opt); /*!< in: MySQL table that is being altered */
/** Share context between partitions.
@param[in] ctx context from another partition of the table */ void set_shared_data(const inplace_alter_handler_ctx& ctx) override
{ if (add_autoinc != ULINT_UNDEFINED) { const ha_innobase_inplace_ctx& ha_ctx = static_cast<const ha_innobase_inplace_ctx&>
(ctx); /* When adding an AUTO_INCREMENT column to a partitionedInnoDBtable,wemustsharethe
sequence for all partitions. */
ut_ad(ha_ctx.add_autoinc == add_autoinc);
ut_ad(ha_ctx.sequence.last());
sequence = ha_ctx.sequence;
}
}
/** @return whether the given column is being added */ bool is_new_vcol(const dict_v_col_t &v_col) const
{ for (ulint i= 0; i < num_to_add_vcol; i++) if (&add_vcol[i] == &v_col) returntrue; returnfalse;
}
/** During rollback, make newly added indexes point to
newly added virtual columns. */ void clean_new_vcol_index()
{
ut_ad(old_table == new_table); const dict_index_t *index= dict_table_get_first_index(old_table); while ((index= dict_table_get_next_index(index)) != NULL)
{ if (!index->has_virtual() || index->is_committed()) continue;
ulint n_drop_new_vcol= index->get_new_n_vcol(); for (ulint i= 0; n_drop_new_vcol && i < index->n_fields; i++)
{
dict_col_t *col= index->fields[i].col; /* Skip the non-virtual and old virtual columns */ if (!col->is_virtual()) continue;
dict_v_col_t *vcol= reinterpret_cast<dict_v_col_t*>(col); if (!is_new_vcol(*vcol)) continue;
/** @return whether a FULLTEXT INDEX is being added */ bool adding_fulltext_index() const
{ for (ulint a= 0; a < num_to_add_index; a++) if (add_index[a]->type & DICT_FTS) returntrue; returnfalse;
}
/** Handle the apply log failure for online DDL operation. @paramha_alter_infohandleralterinplaceinfo @paramaltered_tableMySQLtablethatisbeingaltered @paramerrorerrorcode @retvalfalseiferrorvalueisDB_SUCCESSor
TRUE in case of error */ bool log_failure(Alter_inplace_info *ha_alter_info,
TABLE *altered_table, dberr_t error)
{
ulint err_key= thr_get_trx(thr)->error_key_num; switch (error) {
KEY *dup_key; case DB_SUCCESS: returnfalse; case DB_DUPLICATE_KEY: if (err_key == ULINT_UNDEFINED) /* This should be the hidden index on FTS_DOC_ID */
dup_key= nullptr; else
{
DBUG_ASSERT(err_key < ha_alter_info->key_count);
dup_key= &ha_alter_info->key_info_buffer[err_key];
}
print_keydup_error(altered_table, dup_key, MYF(0)); break; case DB_ONLINE_LOG_TOO_BIG:
my_error(ER_INNODB_ONLINE_LOG_TOO_BIG, MYF(0),
get_error_key_name(err_key, ha_alter_info, new_table)); break; case DB_INDEX_CORRUPT:
my_error(ER_INDEX_CORRUPT, MYF(0),
get_error_key_name(err_key, ha_alter_info, new_table)); break; default:
my_error_innodb(error, old_table->name.m_name, old_table->flags);
} returntrue;
}
/** Check whether the column has any change in collation type. Ifitisthenstorethecolumninformationinheap @paramindexindexbeingadded(orrebuilt)
@param altered_table altered table definition */ void change_col_collation(dict_index_t *index, const TABLE &altered_table)
{
ut_ad(!need_rebuild());
ut_ad(!index->is_primary());
ut_ad(!index->is_committed());
unsigned n_cols= 0; for (unsigned i= 0; i < index->n_fields; i++)
{ constchar *field_name= index->fields[i].name(); if (!field_name || !dtype_is_string_type(index->fields[i].col->mtype) ||
index->fields[i].col->is_virtual()) continue; for (uint j= 0; j < altered_table.s->fields; j++)
{ const Field *altered_field= altered_table.field[j];
if (!altered_field->field_name.streq(Lex_cstring_strlen(field_name))) continue;
void cleanup_col_collation()
{
ut_ad(old_table == new_table); if (change_col_collate.empty()) return; const dict_index_t *index= dict_table_get_first_index(old_table); while ((index= dict_table_get_next_index(index)) != nullptr)
{ if (index->is_committed()) continue; auto collate_end= change_col_collate.end(); for (unsigned i= 0, j= 0; i < index->n_fields; i++)
{ const dict_col_t *col= index->fields[i].col; auto it= change_col_collate.find(col->ind); if (it != collate_end)
{
ut_ad(it->second == col);
index->fields[i].col=
index->change_col_info->add(index->heap, *col, j++);
}
}
}
}
};
/** Determine if fulltext indexes exist in a given table. @paramtableMySQLtable
@return number of fulltext indexes */ static uint innobase_fulltext_exist(const TABLE* table)
{
uint count = 0;
for (uint i = 0; i < table->s->keys; i++) { if (table->key_info[i].algorithm == HA_KEY_ALG_FULLTEXT) {
count++;
}
}
return count;
}
/** Determine whether indexed virtual columns exist in a table. @param[in]tabletabledefinition
@return whether indexes exist on virtual columns */ staticbool innobase_indexed_virtual_exist(const TABLE* table)
{ const KEY* const end = &table->key_info[table->s->keys];
/** Determine if spatial indexes exist in a given table. @paramtableMySQLtable
@return whether spatial indexes exist on the table */ static bool
innobase_spatial_exist( /*===================*/ const TABLE* table)
{ for (uint i = 0; i < table->s->keys; i++) { if (table->key_info[i].algorithm == HA_KEY_ALG_RTREE) { return(true);
}
}
return(false);
}
/** Determine if ALTER_OPTIONS requires rebuilding the table. @param[in]ha_alter_infotheALTERTABLEoperation @param[in]tablemetadatabeforeALTERTABLE
@return whether it is mandatory to rebuild the table */ staticbool alter_options_need_rebuild( const Alter_inplace_info* ha_alter_info, const ha_table_option_struct& opt)
{
DBUG_ASSERT(ha_alter_info->handler_flags & ALTER_OPTIONS);
if (ha_alter_info->create_info->used_fields
& (HA_CREATE_USED_ROW_FORMAT
| HA_CREATE_USED_KEY_BLOCK_SIZE)) { /* Specifying ROW_FORMAT or KEY_BLOCK_SIZE requires rebuildingthetable.(Theseattributesinthe.frm filemaydisagreewiththeInnoDBdatadictionary,and theinterpretationoftheseattributesdependson InnoDBparameters.Thatiswhywefornowalways
require a rebuild when these attributes are specified.) */ returntrue;
}
/* Allow an instant change of page_compression_level. */ if ((alt_opt.page_compressed != opt.page_compressed)
|| alt_opt.encryption != opt.encryption
|| alt_opt.encryption_key_id != opt.encryption_key_id) { return(true);
}
returnfalse;
}
/** Determine if ALTER TABLE needs to rebuild the table (orperforminstantoperation). @param[in]ha_alter_infotheALTERTABLEoperation @param[in]tablemetadatabeforeALTERTABLE
@return whether it is necessary to rebuild the table or to alter columns */ static MY_ATTRIBUTE((nonnull(1), warn_unused_result)) bool
innobase_need_rebuild( const Alter_inplace_info* ha_alter_info, const ha_table_option_struct* opt)
{ if ((ha_alter_info->handler_flags & ~(INNOBASE_INPLACE_IGNORE
| INNOBASE_ALTER_NOREBUILD
| INNOBASE_ALTER_INSTANT))
== ALTER_OPTIONS) { return alter_options_need_rebuild(ha_alter_info, *opt);
}
/** Check if virtual column in old and new table are in order, excluding thosedroppedcolumn.Thisisneededbecausewhenwedropavirtualcolumn, ALTER_VIRTUAL_COLUMN_ORDERisalsoturnedon,sowecan'tdecideifthis isarealORDERchangeorjustDROPCOLUMN @param[in]tableoldTABLE @param[in]altered_tablenewTABLE @param[in]ha_alter_infoStructuredescribingchangestobedone byALTERTABLEandholdingdatausedduringin-placealter.
@return true is all columns in order, false otherwise. */ static bool
check_v_col_in_order( const TABLE* table, const TABLE* altered_table,
Alter_inplace_info* ha_alter_info)
{
ulint j = 0;
/* We don't support any adding new virtual column before
existed virtual column. */ if (ha_alter_info->handler_flags
& ALTER_ADD_VIRTUAL_COLUMN) { bool has_new = false;
for (const Create_field& new_field :
ha_alter_info->alter_info->create_list) { if (new_field.stored_in_db()) { continue;
}
/* Found a new added virtual column. */ if (!new_field.field) {
has_new = true; continue;
}
/* If there's any old virtual column afterthenewaddedvirtualcolumn,
order must be changed. */ if (has_new) { return(false);
}
}
}
/* directly return true if ALTER_VIRTUAL_COLUMN_ORDER is not on */ if (!(ha_alter_info->handler_flags
& ALTER_VIRTUAL_COLUMN_ORDER)) { return(true);
}
for (ulint i = 0; i < table->s->fields; i++) {
Field* field = table->field[i];
if (field->stored_in_db()) { continue;
}
if (field->flags & FIELD_IS_DROPPED) { continue;
}
/* Now check if the next virtual column in altered table
matches this column */ while (j < altered_table->s->fields) {
Field* new_field = altered_table->s->field[j];
if (new_field->stored_in_db()) {
j++; continue;
}
if (!field->field_name.streq(new_field->field_name)) { /* different column */ return(false);
} else {
j++; break;
}
}
if (j > altered_table->s->fields) { /* there should not be less column in new table
without them being in drop list */
ut_ad(0); return(false);
}
}
return(true);
}
/** Determine if an instant operation is possible for altering columns. @param[in]ib_tableInnoDBtabledefinition @param[in]ha_alter_infotheALTERTABLEoperation @param[in]tabletabledefinitionbeforeALTERTABLE @param[in]altered_tabletabledefinitionafterALTERTABLE
@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 ha_table_option_struct* opt, 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)) { #if1// MDEV-17459: adjust fts_fetch_doc_from_rec() and friends; remove this if (ib_table.fts || innobase_fulltext_exist(altered_table)) returnfalse; #endif #if1// 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
|| index->is_corrupted()); returnfalse;
}
} #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;
} constbool nullable = (*af)->real_maybe_null(); constbool is_null = (*af)->is_real_null();
ut_ad(!is_null || nullable);
n_nullable += nullable;
n_add++;
uint l = (*af)->pack_length(); switch ((*af)->type()) { case MYSQL_TYPE_VARCHAR:
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;
}
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: 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) { returnfalse;
}
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 speciallytoavoidrebuild.Inthiscase,wecan allowALGORITHM=INSTANT,exceptifsomerequested
operation requires that the table be rebuilt. */ if (flags & INNOBASE_ALTER_REBUILD) { returnfalse;
} if ((flags & ALTER_OPTIONS)
&& alter_options_need_rebuild(ha_alter_info, *opt)) { returnfalse;
}
} elseif (!ib_table.supports_instant()) { returnfalse;
}
/* At the moment, we disallow ADD [UNIQUE] INDEX together with instantADDCOLUMN.
Ifweaddedthecolumnstothedictionarycachealreadyinthe prepare_inplace_alter_table(),wewouldhavetodealwith columnnumbermismatchinha_innobase::open(),write_row()and
other functions. */
/* FIXME: allow instant ADD COLUMN together with INNOBASE_ONLINE_CREATE(ADD[UNIQUE]INDEX)onpre-existing
columns. */ if (ha_alter_info->handler_flags
& ((INNOBASE_ALTER_REBUILD | INNOBASE_ONLINE_CREATE)
& ~ALTER_DROP_STORED_COLUMN
& ~ALTER_STORED_COLUMN_ORDER
& ~ALTER_ADD_STORED_BASE_COLUMN
& ~ALTER_COLUMN_NULLABLE
& ~ALTER_OPTIONS)) { returnfalse;
}
if ((ha_alter_info->handler_flags & ALTER_OPTIONS)
&& alter_options_need_rebuild(ha_alter_info, *opt)) { returnfalse;
}
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. */ returnfalse;
} 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. */ returnfalse;
}
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 (unsigned c = 0; af < end; af++) { const Create_field* cf = cf_it++; if (!cf->field || !(*af)->stored_in_db()) { /* Ignore virtual or newly created
column */ 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) { returnfalse;
}
}
}
}
returntrue;
}
/** Check whether the non-const default value for the field @param[in]fieldfieldwhichcouldbeaddedorchanged
@return true if the non-const default is present. */ staticbool is_non_const_value(Field* field)
{ return field->default_value
&& field->default_value->flags
& uint(~(VCOL_SESSION_FUNC | VCOL_TIME_FUNC));
}
/** Set default value for the field. @param[in]fieldfieldwhichcouldbeaddedorchanged
@return true if the default value is set. */ staticbool set_default_value(Field* field)
{ /* The added/changed NOT NULL column lacks a DEFAULT value, ortheDEFAULTisthesameforallrows. (Timefunctions,suchasCURRENT_TIMESTAMP(), areevaluatedfromatimestampthatisassigned atthestartofthestatement.Session functions,suchasUSER(),alwaysevaluatethe
same within a statement.) */
ut_ad(!is_non_const_value(field));
/* Compute the DEFAULT values of non-constant columns
(VCOL_SESSION_FUNC | VCOL_TIME_FUNC). */ switch (field->set_default()) { case0: /* OK */ case3: /* DATETIME to TIME or DATE conversion */ returntrue; case -1: /* OOM, or GEOMETRY type mismatch */ case1: /* A number adjusted to the min/max value */ case2: /* String truncation, or conversion problem */ break;
}
returnfalse;
}
/** Check whether the table has the FTS_DOC_ID column @param[in]tableInnoDBtablewithfulltextindex @param[in]altered_tableMySQLtablewithfulltextindex @param[out]fts_doc_col_noThecolumnnumberforDocID, orULINT_UNDEFINEDifitisofwrongtype @param[out]num_vNumberofvirtualcolumn @param[in]check_onlycheckonlywhetherftsdocidexist.
@return whether there exists an FTS_DOC_ID column */ static bool
innobase_fts_check_doc_id_col( const dict_table_t* table, const TABLE* altered_table,
ulint* fts_doc_col_no,
ulint* num_v, bool check_only=false)
{
*fts_doc_col_no = ULINT_UNDEFINED;
/** Check if InnoDB supports a particular alter table in-place @paramaltered_tableTABLEobjectfornewversionoftable. @paramha_alter_infoStructuredescribingchangestobedone byALTERTABLEandholdingdatausedduringin-placealter.
if ((ha_alter_info->handler_flags
& INNOBASE_ALTER_VERSIONED_REBUILD)
&& altered_table->versioned(VERS_TIMESTAMP)) {
ha_alter_info->unsupported_reason = "Not implemented for system-versioned timestamp tables";
DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
}
/* Before 10.2.2 information about virtual columns was not stored in systemtables.Weneedtodoafullaltertorebuildproper10.2.2+
metadata with the information about virtual columns */ if (omits_virtual_cols(*table_share)) {
DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
}
if (altered_table->s->fields > REC_MAX_N_USER_FIELDS) { /* Deny the inplace ALTER TABLE. MySQL will try to re-createthetableandha_innobase::create()will returnanerrortoo.Thisishowweeffectively
deny adding too many columns to a table. */
ha_alter_info->unsupported_reason =
my_get_err_msg(ER_TOO_MANY_FIELDS);
DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
}
if (ha_alter_info->create_info->used_fields
& HA_CREATE_USED_SEQUENCE) {
ha_alter_info->unsupported_reason = "SEQUENCE";
DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
}
update_thd();
if (!m_prebuilt->table->space) {
ib_senderrf(m_user_thd, IB_LOG_LEVEL_WARN,
ER_TABLESPACE_DISCARDED,
table->s->table_name.str);
}
if (ha_alter_info->handler_flags
& ALTER_STORED_COLUMN_TYPE) {
ha_alter_info->unsupported_reason = my_get_err_msg(
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_COLUMN_TYPE);
}
DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
}
ut_ad(dict_sys.sys_tables_exist());
/* Only support online add foreign key constraint when
check_foreigns is turned off */ if ((ha_alter_info->handler_flags & ALTER_ADD_FOREIGN_KEY)
&& m_prebuilt->trx->check_foreigns) {
ha_alter_info->unsupported_reason = my_get_err_msg(
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_CHECK);
DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
}
switch (ha_alter_info->handler_flags & ~INNOBASE_INPLACE_IGNORE) { case ALTER_OPTIONS: if ((ha_alter_info->file_per_table &&
!m_prebuilt->table->space_id)
|| alter_options_need_rebuild(ha_alter_info,
*option_struct)) {
reason_rebuild = my_get_err_msg(
ER_ALTER_OPERATION_TABLE_OPTIONS_NEED_REBUILD);
ha_alter_info->unsupported_reason = reason_rebuild;
need_rebuild= true; break;
} /* fall through */ case0:
DBUG_RETURN(HA_ALTER_INPLACE_INSTANT);
}
/* InnoDB cannot IGNORE when creating unique indexes. IGNORE shouldsilentlydeletesomeduplicaterows.Ourinplace_alter
code will not delete anything from existing indexes. */ if (ha_alter_info->ignore
&& (ha_alter_info->handler_flags
& (ALTER_ADD_PK_INDEX | ALTER_ADD_UNIQUE_INDEX))) {
ha_alter_info->unsupported_reason = my_get_err_msg(
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_IGNORE);
DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
}
/* DROP PRIMARY KEY is only allowed in combination with ADD
PRIMARY KEY. */ if ((ha_alter_info->handler_flags
& (ALTER_ADD_PK_INDEX | ALTER_DROP_PK_INDEX))
== ALTER_DROP_PK_INDEX) {
ha_alter_info->unsupported_reason = my_get_err_msg(
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOPK);
DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
}
if (ha_alter_info->handler_flags & ALTER_COLUMN_NULLABLE) { /* If a NOT NULL attribute is going to be removed and aUNIQUEINDEXonthecolumnhadbeenpromotedtoan implicitPRIMARYKEY,thetableshouldberebuiltby ALGORITHM=COPY.(Theoretically,wecouldsupport rebuildingbyALGORITHM=INPLACEifaPRIMARYKEYis goingtobeadded,eitherexplicitlyorbypromoting
another UNIQUE KEY.) */ const uint my_primary_key = altered_table->s->primary_key;
/* '0000-00-00' value isn't allowed for datetime datatype
for newly added column when table is not empty */ if (ha_alter_info->error_if_not_empty
&& m_prebuilt->table->space
&& !innobase_table_is_empty(m_prebuilt, true)) {
DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
}
/* We should be able to do the operation in-place.
See if we can do it online (LOCK=NONE) or without rebuild. */ bool online = true; const uint fulltext_indexes = innobase_fulltext_exist(altered_table);
/* Fix the key parts. */ for (KEY* new_key = ha_alter_info->key_info_buffer;
new_key < ha_alter_info->key_info_buffer
+ ha_alter_info->key_count;
new_key++) {
/* Do not support adding/droping a virtual column, while
there is a table rebuild caused by adding a new FTS_DOC_ID */ if ((new_key->algorithm == HA_KEY_ALG_FULLTEXT) && add_drop_v_cols
&& !DICT_TF2_FLAG_IS_SET(m_prebuilt->table,
DICT_TF2_FTS_HAS_DOC_ID)) {
ha_alter_info->unsupported_reason =
MSG_UNSUPPORTED_ALTER_ONLINE_ON_VIRTUAL_COLUMN;
DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
}
/* In some special cases InnoDB emits "false" duplicatekeyerrorswithNULLkeyvalues.Let usplaysafeandensurethatwecancorrectly
print key values even in such cases. */
key_part->null_offset = key_part->field->null_offset();
key_part->null_bit = key_part->field->null_bit;
if (new_field->field) { /* This is an existing column. */ continue;
}
/* This is an added column. */
DBUG_ASSERT(ha_alter_info->handler_flags
& ALTER_ADD_COLUMN);
/* We cannot replace a hidden FTS_DOC_ID
with a user-visible FTS_DOC_ID. */ if (fulltext_indexes && m_prebuilt->table->fts
&& key_part->field->field_name.
streq(FTS_DOC_ID)) {
ha_alter_info->unsupported_reason = my_get_err_msg(
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_HIDDEN_FTS);
DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
}
if (key_part->field->flags & AUTO_INCREMENT_FLAG) { /* We cannot assign AUTO_INCREMENT values
during online or instant ALTER. */
DBUG_ASSERT(key_part->field == altered_table
-> found_next_number_field);
if (ha_alter_info->online) {
ha_alter_info->unsupported_reason = my_get_err_msg(
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_AUTOINC);
}
online = false;
need_rebuild = true;
}
if (!key_part->field->stored_in_db()) { /* Do not support adding index on newly added virtualcolumn,whilethereisalsoadrop
virtual column in the same clause */ if (ha_alter_info->handler_flags
& ALTER_DROP_VIRTUAL_COLUMN) {
ha_alter_info->unsupported_reason =
MSG_UNSUPPORTED_ALTER_ONLINE_ON_VIRTUAL_COLUMN;
DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
}
if (ha_alter_info->online
&& !ha_alter_info->unsupported_reason) {
ha_alter_info->unsupported_reason =
MSG_UNSUPPORTED_ALTER_ONLINE_ON_VIRTUAL_COLUMN;
}
if (fulltext_indexes && m_prebuilt->table->fts) { /* FTS index of versioned table has row_end, need rebuild */ if (table->versioned() != altered_table->versioned()) {
need_rebuild= true;
}
/* FULLTEXT indexes are supposed to remain. */ /* Disallow DROP INDEX FTS_DOC_ID_INDEX */
for (uint i = 0; i < ha_alter_info->index_drop_count; i++) { if (ha_alter_info->index_drop_buffer[i]->name.streq(
FTS_DOC_ID_INDEX)) {
ha_alter_info->unsupported_reason = my_get_err_msg(
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_CHANGE_FTS);
DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
}
}
/* InnoDB can have a hidden FTS_DOC_ID_INDEX on a visibleFTS_DOC_IDcolumnaswell.Preventdroppingor
renaming the FTS_DOC_ID. */
for (Field** fp = table->field; *fp; fp++) { if (!((*fp)->flags
& (FIELD_IS_RENAMED | FIELD_IS_DROPPED))) { continue;
}
/* When changing a NULL column to NOT NULL and specifying a DEFAULTvalue,ensurethattheDEFAULTexpressionisaconstant. Also,inADDCOLUMN,fornowweonlysupporta
constant DEFAULT expression. */
Field **af = altered_table->field; bool fts_need_rebuild = false;
need_rebuild = need_rebuild
|| innobase_need_rebuild(ha_alter_info, option_struct);
if (const Field* f = cf.field) { /* An AUTO_INCREMENT attribute can only beaddedtoanexistingcolumnbyALGORITHM=COPY,
but we can remove the attribute. */
ut_ad((*af)->unireg_check != Field::NEXT_NUMBER
|| f->unireg_check == Field::NEXT_NUMBER); if (!f->real_maybe_null() || (*af)->real_maybe_null()) goto next_column; /* We are changing an existing column
from NULL to NOT NULL. */
DBUG_ASSERT(ha_alter_info->handler_flags
& ALTER_COLUMN_NOT_NULLABLE); /* Virtual columns are never NOT NULL. */
DBUG_ASSERT(f->stored_in_db()); switch ((*af)->type()) { case MYSQL_TYPE_TIMESTAMP: case MYSQL_TYPE_TIMESTAMP2: /* Inserting NULL into a TIMESTAMP column wouldcausetheDEFAULTvaluetobe replaced.EnsurethattheDEFAULT expressionisnotchangingduring
ALTER TABLE. */ if (!(*af)->default_value
&& (*af)->is_real_null()) { /* No DEFAULT value is specified.Wecanreport errorsforanyNULLvaluesfor
the TIMESTAMP. */ goto next_column;
} break; default: /* For any other data type, NULL
values are not converted. */ goto next_column;
}
ha_alter_info->unsupported_reason = my_get_err_msg(
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOT_NULL);
} elseif (!is_non_const_value(*af)
&& set_default_value(*af)) { if (fulltext_indexes > 1
&& (*af)->field_name.streq(FTS_DOC_ID)) { /* If a hidden FTS_DOC_ID column exists (becauseofFULLTEXTINDEX),itcannot bereplacedwithauser-createdone
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;
}
if (supports_instant && !(ha_alter_info->handler_flags
& INNOBASE_ALTER_NOREBUILD)) {
DBUG_RETURN(HA_ALTER_INPLACE_INSTANT);
}
if (need_rebuild
&& (fulltext_indexes
|| innobase_spatial_exist(altered_table)
|| innobase_indexed_virtual_exist(altered_table))) { /* If the table already contains fulltext indexes,
refuse to rebuild the table natively altogether. */ if (fulltext_indexes > 1) {
cannot_create_many_fulltext_index:
ha_alter_info->unsupported_reason =
my_get_err_msg(ER_INNODB_FT_LIMIT);
DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
}
if (!online || !ha_alter_info->online
|| ha_alter_info->unsupported_reason != reason_rebuild) { /* Either LOCK=NONE was not requested, or we already
gave specific reason to refuse it. */
} elseif (fulltext_indexes) {
ha_alter_info->unsupported_reason = my_get_err_msg(
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FTS);
} elseif (innobase_spatial_exist(altered_table)) {
ha_alter_info->unsupported_reason = my_get_err_msg(
ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_GIS);
} else { /* MDEV-14341 FIXME: Remove this limitation. */
ha_alter_info->unsupported_reason = "online rebuild with indexed virtual columns";
}
online = false;
}
static constexpr constchar *not_implemented
= "Not implemented for system-versioned operations";
if (ha_alter_info->handler_flags
& ALTER_ADD_NON_UNIQUE_NON_PRIM_INDEX) { /* ADD FULLTEXT|SPATIAL INDEX requires a lock.
for (ulint i = 0; i < foreign->n_fields; i++) {
foreign->referenced_col_names[i]
= mem_heap_strdup(foreign->heap,
referenced_column_names[i]);
}
return(true);
}
/*************************************************************//**
Check whether the foreign key options is legit
@returntrueif it is */ static MY_ATTRIBUTE((nonnull, warn_unused_result)) bool
innobase_check_fk_option( /*=====================*/ const dict_foreign_t* foreign) /*!< in: foreign key */
{ if (!foreign->foreign_index) { return(true);
}
if (foreign->type & (foreign->UPDATE_SET_NULL
| foreign->DELETE_SET_NULL)) {
for (ulint j = 0; j < foreign->n_fields; j++) { if ((dict_index_get_nth_col(
foreign->foreign_index, j)->prtype)
& DATA_NOT_NULL) {
/* It is not sensible to define SETNULLifthecolumnisnot
allowed to be NULL! */ return(false);
}
}
}
return(true);
}
/*************************************************************//**
Set foreign key options
@returntrueif successfully set */ static MY_ATTRIBUTE((nonnull, warn_unused_result)) bool
innobase_set_foreign_key_option( /*============================*/
dict_foreign_t* foreign, /*!< in:InnoDB Foreign key */
Foreign_key* fk_key) /*!< in: Foreign key info from
MySQL */
{
ut_ad(!foreign->type);
switch (fk_key->delete_opt) { case FK_OPTION_NO_ACTION: case FK_OPTION_RESTRICT: case FK_OPTION_SET_DEFAULT:
foreign->type = foreign->DELETE_NO_ACTION; break; case FK_OPTION_CASCADE:
foreign->type = foreign->DELETE_CASCADE; break; case FK_OPTION_SET_NULL:
foreign->type = foreign->DELETE_SET_NULL; break; case FK_OPTION_UNDEF: break;
}
#ifdefined __GNUC__ && !defined __clang__ && __GNUC__ < 6 # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wconversion" #endif switch (fk_key->update_opt) { case FK_OPTION_NO_ACTION: case FK_OPTION_RESTRICT: case FK_OPTION_SET_DEFAULT:
foreign->type |= foreign->UPDATE_NO_ACTION; break; case FK_OPTION_CASCADE:
foreign->type |= foreign->UPDATE_CASCADE; break; case FK_OPTION_SET_NULL:
foreign->type |= foreign->UPDATE_SET_NULL; break; case FK_OPTION_UNDEF: break;
}
/*******************************************************************//**
Check if a foreign key constraint can make use of an index
that is being created.
@param[in] col_names column names
@param[in] n_cols number of columns
@param[in] keys index information
@param[in] add indexes being created
@return useable index, or NULL if none found */ static MY_ATTRIBUTE((nonnull, warn_unused_result)) const KEY*
innobase_find_equiv_index( constchar*const* col_names,
uint n_cols, const KEY* keys,
span<uint> add)
{ for (span<uint>::iterator it = add.begin(), end = add.end(); it != end;
++it) { const KEY* key = &keys[*it];
/* Column prefix indexes cannot be
used for FOREIGN KEY constraints. */ goto no_match;
}
if (!key_part.field->field_name.
streq(Lex_cstring_strlen(col_names[j]))) { /* Name mismatch */ goto no_match;
}
}
return(key);
}
return(NULL);
}
/*************************************************************//**
Find an index whose first fields are the columns in the array
in the same order and is not marked for deletion
@return matching index, NULL ifnot found */ static MY_ATTRIBUTE((nonnull(1,4), warn_unused_result))
dict_index_t*
innobase_find_fk_index( /*===================*/
dict_table_t* table, /*!< in: table */ constchar** col_names, /*!< in: column names, or NULL
to use table->col_names */
span<dict_index_t*> drop_index, /*!< in: indexes to be dropped */ constchar** columns,/*!< in: array of column names */
ulint n_cols) /*!< in: number of columns */
{
dict_index_t* index;
/** Check whether given column is a base of stored column. @param[in]col_namecolumnname @param[in]tabletable @param[in]s_colslistofstoredcolumns
@return true if the given column is a base of stored column,else false. */ static bool
innobase_col_check_fk( constchar* col_name, const dict_table_t* table,
dict_s_col_list* s_cols)
{
dict_s_col_list::const_iterator it;
for (it = s_cols->begin(); it != s_cols->end(); ++it) { for (ulint j = it->num_base; j--; ) { if (!strcmp(col_name, dict_table_get_col_name(
table, it->base_col[j]->ind).str)) { return(true);
}
}
}
return(false);
}
/** Check whether the foreign key constraint is on base of any stored columns. @param[in]foreignForeignkeyconstraintinformation @param[in]tabletabletowhichtheforeignkeyobjects tobeadded @param[in]s_colslistofstoredcolumninformationinthetable.
@return true if yes, otherwise false. */ static bool
innobase_check_fk_stored( const dict_foreign_t* foreign, const dict_table_t* table,
dict_s_col_list* s_cols)
{
ulint type = foreign->type;
type &= ~(foreign->DELETE_NO_ACTION
| foreign->UPDATE_NO_ACTION);
/* Get all the foreign key column info for the
current table */ for (const Key_part_spec& column : fk_key->columns) {
column_names[i] = column.field_name.str;
ut_ad(i < MAX_NUM_FK_COLUMNS);
i++;
}
index = innobase_find_fk_index(
table, col_names,
span<dict_index_t*>(drop_index, n_drop_index),
column_names, i);
/* MySQL would add a index in the creation listifnosuchindexforforeigntable, sowehavetouseDBUG_EXECUTE_IFtosimulate
the scenario */
DBUG_EXECUTE_IF("innodb_test_no_foreign_idx",
index = NULL;);
/* Check whether there exist such
index in the the index create clause */ if (!index && !innobase_find_equiv_index(
column_names, static_cast<uint>(i),
ha_alter_info->key_info_buffer,
span<uint>(ha_alter_info->index_add_buffer,
ha_alter_info->index_add_count))) {
my_error(
ER_FK_NO_INDEX_CHILD,
MYF(0),
fk_key->name.str
? fk_key->name.str : "",
table_share->table_name.str); goto err_exit;
}
num_col = i;
}
dict_foreign_t* const fk = add_fk[num_fk]
= dict_mem_foreign_create();
LEX_CSTRING t = innodb_convert_name(cs, fk_key->ref_table,
t_name);
LEX_CSTRING d = fk_key->ref_db.str
? innodb_convert_name(cs, fk_key->ref_db, db_name)
: LEX_CSTRING{table->name.m_name, table->name.dblen()};
dict_sys.lock(SRW_LOCK_CALL);
/* Test the case when referenced_table failed to open,iftrx->check_foreignsisnotset,weshould
still be able to add the foreign key */
DBUG_EXECUTE_IF("innodb_test_open_ref_fail",
referenced_table = NULL;);
/* Check whether there exist such
index in the the index create clause */ if (!referenced_index) {
my_error(ER_FK_NO_INDEX_PARENT, MYF(0),
fk_key->name.str
? fk_key->name.str : "",
fk_key->ref_table.str); goto err_exit_unlock;
}
} else {
ut_a(!trx->check_foreigns);
}
referenced_num_col = i;
} else { /* Not possible to add a foreign key without a
referenced column */
my_error(ER_CANNOT_ADD_FOREIGN, MYF(0),
fk_key->ref_table.str); goto err_exit_unlock;
}
/* If fk_key->name.str==nullptr, we will end up with fk->id=nullptr.Inthecallstomy_error()below, passingnullptrto"%s"isfine;process_str_arg() willdisplay"(null)".
if (!correct_option) {
my_error(ER_FK_INCORRECT_OPTION,
MYF(0),
table_share->table_name.str,
fk_key->name.str); goto err_exit;
}
if (innobase_check_fk_stored(fk, table, s_cols)) {
my_printf_error(
HA_ERR_UNSUPPORTED, "Cannot add foreign key on the base column " "of stored column", MYF(0)); goto err_exit;
}
num_fk++;
}
*n_add_fk = num_fk;
DBUG_RETURN(true);
err_exit_unlock:
dict_sys.unlock();
err_exit: for (ulint i = 0; i <= num_fk; i++) { if (dict_foreign_t* fk = add_fk[i]) {
dict_foreign_free(fk);
}
}
DBUG_RETURN(false);
}
/*************************************************************//**
Copies an InnoDB column to a MySQL field. This function is
adapted from row_sel_field_store_in_mysql_format(). */ static void
innobase_col_to_mysql( /*==================*/ const dict_col_t* col, /*!< in: InnoDB column */ const uchar* data, /*!< in: InnoDB column data */
ulint len, /*!< in: length of data, in bytes */
Field* field) /*!< in/out: MySQL field */
{
uchar* ptr;
uchar* dest = field->ptr;
ulint flen = field->pack_length();
switch (col->mtype) { case DATA_INT:
ut_ad(len == flen);
/* Convert integer data from Innobase to little-endian
format, sign bit restored to normal */
for (ptr = dest + len; ptr != dest; ) {
*--ptr = *data++;
}
case DATA_VARCHAR: case DATA_VARMYSQL: case DATA_BINARY:
field->reset();
if (field->type() == MYSQL_TYPE_VARCHAR) { /* This is a >= 5.0.3 type true VARCHAR. Store the lengthofthedatatothefirstbyteorthefirst
two bytes of dest. */
dest = row_mysql_store_true_var_len(
dest, len, flen - field->key_length());
}
/* Copy the actual data */
memcpy(dest, data, len); break;
case DATA_GEOMETRY: case DATA_BLOB: /* Skip MySQL BLOBs when reporting an erroneous row
during index creation or table rebuild. */
field->set_null(); break;
default: case DATA_SYS_CHILD: case DATA_SYS: /* These column types should never be shipped to MySQL. */
ut_ad(0); /* fall through */ case DATA_FLOAT: case DATA_DOUBLE: case DATA_DECIMAL: /* Above are the valid column types for MySQL data. */
ut_ad(flen == len); /* fall through */ case DATA_FIXBINARY: case DATA_CHAR: /* We may have flen > len when there is a shorter
prefix on the CHAR and BINARY column. */
ut_ad(flen >= len); #else/* UNIV_DEBUG */ default: #endif/* UNIV_DEBUG */
memcpy(dest, data, len);
}
}
/*************************************************************//**
Copies an InnoDB record to table->record[0]. */ void
innobase_rec_to_mysql( /*==================*/ struct TABLE* table, /*!< in/out: MySQL table */ const rec_t* rec, /*!< in: record */ const dict_index_t* index, /*!< in: index */ const rec_offs* offsets)/*!< in: rec_get_offsets(
rec, index, ...) */
{
uint n_fields = table->s->fields;
/*************************************************************//**
Copies an InnoDB index entry to table->record[0]. This is used in preparation for print_keydup_error() from inline add index */ void
innobase_fields_to_mysql( /*=====================*/ struct TABLE* table, /*!< in/out: MySQL table */ const dict_index_t* index, /*!< in: InnoDB index */ const dfield_t* fields) /*!< in: InnoDB index fields */
{
uint n_fields = table->s->fields;
ulint num_v = 0;
/*************************************************************//**
Copies an InnoDB row to table->record[0]. This is used in preparation for print_keydup_error() from
row_log_table_apply() */ void
innobase_row_to_mysql( /*==================*/ struct TABLE* table, /*!< in/out: MySQL table */ const dict_table_t* itab, /*!< in: InnoDB table */ const dtuple_t* row) /*!< in: InnoDB row */
{
uint n_fields = table->s->fields;
ulint num_v = 0;
/* The InnoDB row may contain an extra FTS_DOC_ID column at the end. */
ut_ad(row->n_fields == dict_table_get_n_cols(itab));
ut_ad(row->n_fields == n_fields + DATA_N_SYS_COLS
- dict_table_get_n_v_cols(itab)
+ !!(DICT_TF2_FLAG_IS_SET(itab, DICT_TF2_FTS_HAS_DOC_ID)));
for (uint i = 0; i < n_fields; i++) {
Field* field = table->field[i];
field->reset();
if (!field->stored_in_db()) { /* Virtual column are not stored in InnoDB table, so
skip it */
num_v++; continue;
}
const dfield_t* df = dtuple_get_nth_field(row, i - num_v);
if (dfield_is_ext(df) || dfield_is_null(df)) {
field->set_null();
} else {
field->set_notnull();
/*******************************************************************//** This function checks that index keys are sensible.
@return0or error number */ static MY_ATTRIBUTE((nonnull, warn_unused_result)) int
innobase_check_index_keys( /*======================*/ const Alter_inplace_info* info, /*!< in: indexes to be created or dropped */ const dict_table_t* innodb_table) /*!< in: Existing indexes */
{ for (uint key_num = 0; key_num < info->index_add_count;
key_num++) { const KEY& key = info->key_info_buffer[
info->index_add_buffer[key_num]];
/* Check that the same index name does not appear
twice in indexes to be created. */
for (ulint i = 0; i < key_num; i++) { const KEY& key2 = info->key_info_buffer[
info->index_add_buffer[i]];
if (0 == strcmp(key.name.str, key2.name.str)) {
my_error(ER_WRONG_NAME_FOR_INDEX, MYF(0),
key.name.str);
return(ER_WRONG_NAME_FOR_INDEX);
}
}
/* Check that the same index name does not already exist. */
const dict_index_t* index;
for (index = dict_table_get_first_index(innodb_table);
index; index = dict_table_get_next_index(index)) {
if (index->is_committed()
&& !strcmp(key.name.str, index->name)) { break;
}
}
/* Now we are in a situation where we have "ADD INDEX x" andanindexbythesamenamealreadyexists.Wehave4 possiblecases: 1.Nofurtherclausesforanindexxaregiven.Shouldreject theoperation. 2."DROPINDEXx"isgiven.Shouldallowtheoperation. 3."RENAMEINDEXxTOy"isgiven.Shouldallowtheoperation. 4."DROPINDEXx,RENAMEINDEXxTOy"isgiven.Shouldallow theoperation,sincenonameclashoccurs.Inthisparticular caseMySQLcancelstheoperationwithoutcallingInnoDB
methods. */
if (index) { /* If a key by the same name is being created and
dropped, the name clash is OK. */ for (uint i = 0; i < info->index_drop_count;
i++) { const KEY* drop_key
= info->index_drop_buffer[i];
if (0 == strcmp(key.name.str,
drop_key->name.str)) { goto name_ok;
}
}
for (const Alter_inplace_info::Rename_key_pair& pair :
info->rename_keys) { if (0 == strcmp(key.name.str,
pair.old_key->name.str)) { goto name_ok;
}
}
name_ok: for (ulint i = 0; i < key.user_defined_key_parts; i++) { const KEY_PART_INFO& key_part1
= key.key_part[i]; const Field* field
= key_part1.field; unsigned is_unsigned;
switch (get_innobase_type_from_mysql_type(
&is_unsigned, field)) { default: break; case DATA_INT: case DATA_FLOAT: case DATA_DOUBLE: case DATA_DECIMAL: /* Check that MySQL does not try to createacolumnprefixindexfieldon
an inappropriate data type. */
if (field->type() == MYSQL_TYPE_VARCHAR) { if (key_part1.length
>= field->pack_length()
- ((Field_varstring*) field)
->length_bytes) { break;
}
} else { if (key_part1.length
>= field->pack_length()) { break;
}
}
/* Need to count the virtual fields before this spatial
indexed field */ for (ulint i = 0; i < key->key_part->fieldnr; i++) {
num_v += !altered_table->field[i]->stored_in_db();
}
index->fields[0].col_no = key->key_part[0].fieldnr - num_v;
index->fields[0].prefix_len = 0;
index->fields[0].is_v_col = false;
index->fields[0].descending = false;
/* Currently, the spatial index cannot be created
on virtual columns. It is blocked in the SQL layer. */
DBUG_ASSERT(key->key_part[0].field->stored_in_db());
} else {
index->ind_type = (key->flags & HA_NOSAME) ? DICT_UNIQUE : 0;
}
if (key->algorithm != HA_KEY_ALG_RTREE) { for (i = 0; i < n_fields; i++) {
innobase_create_index_field_def(
new_clustered, altered_table,
&key->key_part[i], &index->fields[i]);
if (index->fields[i].is_v_col) {
index->ind_type |= DICT_VIRTUAL;
}
}
}
DBUG_VOID_RETURN;
}
/*******************************************************************//**
Check whether the table has a unique index with FTS_DOC_ID_INDEX_NAME
on the Doc ID column.
@return the status of the FTS_DOC_ID index */ enum fts_doc_id_index_enum
innobase_fts_check_doc_id_index( /*============================*/ const dict_table_t* table, /*!< in: table definition */ const TABLE* altered_table, /*!< in: MySQL table
that is being altered */
ulint* fts_doc_col_no) /*!< out: The column number for DocID,orULINT_UNDEFINED ifitisbeingcreatedin
ha_alter_info */
{ const dict_index_t* index; const dict_field_t* field;
if (altered_table) { /* Check if a unique index with the name of
FTS_DOC_ID_INDEX_NAME is being created. */
if (!table) { return(FTS_NOT_EXIST_DOC_ID_INDEX);
}
for (index = dict_table_get_first_index(table);
index; index = dict_table_get_next_index(index)) {
/* Check if there exists a unique index with the name of
FTS_DOC_ID_INDEX_NAME and ignore the corrupted index */ const Lex_ident_column index_name = Lex_cstring_strlen(index->name); if (index->type & DICT_CORRUPT
|| !index_name.streq(FTS_DOC_ID_INDEX)) { continue;
}
/* Check whether the index has FTS_DOC_ID as its
first column */
field = dict_index_get_nth_field(index, 0);
/* The column would be of a BIGINT data type */ if (strcmp(field->name, FTS_DOC_ID.str) == 0
&& !field->descending
&& field->col->mtype == DATA_INT
&& field->col->len == 8
&& field->col->prtype & DATA_NOT_NULL
&& !field->col->is_virtual()) { if (fts_doc_col_no) {
*fts_doc_col_no = dict_col_get_no(field->col);
} return(FTS_EXIST_DOC_ID_INDEX);
} else { return(FTS_INCORRECT_DOC_ID_INDEX);
}
}
/* Not found */ return(FTS_NOT_EXIST_DOC_ID_INDEX);
} /*******************************************************************//**
Check whether the table has a unique index with FTS_DOC_ID_INDEX_NAME
on the Doc ID column in MySQL create index definition.
@return FTS_EXIST_DOC_ID_INDEX if there exists the FTS_DOC_ID index,
FTS_INCORRECT_DOC_ID_INDEX if the FTS_DOC_ID index is of wrong format */ enum fts_doc_id_index_enum
innobase_fts_check_doc_id_index_in_def( /*===================================*/
ulint n_key, /*!< in: Number of keys */ const KEY* key_info) /*!< in: Key definition */
{ /* Check whether there is a "FTS_DOC_ID_INDEX" in the to be built index
list */ const uint fts_n_uniq= key_info->table->versioned() ? 2 : 1; for (ulint j = 0; j < n_key; j++) { const KEY* key = &key_info[j];
if (!key->name.streq(FTS_DOC_ID_INDEX)) { continue;
}
/* Do a check on FTS DOC ID_INDEX, it must be unique,
named as "FTS_DOC_ID_INDEX" and on column "FTS_DOC_ID" */ if (!(key->flags & HA_NOSAME)
|| key->user_defined_key_parts != fts_n_uniq
|| (key->key_part[0].key_part_flag & HA_REVERSE_SORT)
|| strcmp(key->name.str, FTS_DOC_ID_INDEX.str)
|| strcmp(key->key_part[0].field->field_name.str,
FTS_DOC_ID.str)) { return(FTS_INCORRECT_DOC_ID_INDEX);
}
return(FTS_EXIST_DOC_ID_INDEX);
}
return(FTS_NOT_EXIST_DOC_ID_INDEX);
}
/** Create an index table where indexes are ordered as follows:
IFanewprimarykeyisdefinedforthetableTHEN
1)Newprimarykey 2)Theremainingkeysinkey_info
ELSE
1)AllnewindexesintheordertheyarrivefromMySQL
ENDIF
@return key definitions */
MY_ATTRIBUTE((nonnull(2,3), 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_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 ha_table_option_struct* opt) /*!< in: MySQL table that is being altered */
{
ulint& n_add = num_to_add_index; constbool got_default_clust = new_table->indexes.start->is_gen_clust();
/* If there is a UNIQUE INDEX consisting entirely of NOT NULL columnsandiftheindexdoesnotcontaincolumnprefix(es) (onlyprefix/partofthecolumnisindexed),MySQLwilltreatthe
index as a PRIMARY KEY unless the table already has one. */
/* Reserve one more space if new_primary is true, and we might
need to add the FTS_DOC_ID_INDEX */
indexdef = indexdefs = static_cast<index_def_t*>(
mem_heap_alloc(
heap, sizeof *indexdef
* (ha_alter_info->key_count
+ rebuild
+ got_default_clust)));
if (rebuild) {
ulint primary_key_number;
if (new_primary) {
DBUG_ASSERT(n_add || got_default_clust);
DBUG_ASSERT(n_add || !altered_table->s->primary_key);
primary_key_number = altered_table->s->primary_key;
} elseif (got_default_clust) { /* Create the GEN_CLUST_INDEX */
index_def_t* index = indexdef++;
/* Create the PRIMARY key index definition */
innobase_create_index_def(
altered_table, key_info, primary_key_number, true, true, indexdef++, heap);
created_clustered:
n_add = 1;
for (ulint i = 0; i < ha_alter_info->key_count; i++) { if (i == primary_key_number) { continue;
} /* Copy the index definitions. */
innobase_create_index_def(
altered_table, key_info, i, true, false, indexdef, heap);
if (indexdef->ind_type & DICT_FTS) {
n_fts_add++;
}
MY_ATTRIBUTE((warn_unused_result)) bool too_big_key_part_length(size_t max_field_len, const KEY& key)
{ for (ulint i = 0; i < key.user_defined_key_parts; i++) { if (key.key_part[i].length > max_field_len) { returntrue;
}
} returnfalse;
}
/********************************************************************//**
Drop any indexes that we were not able to free previously due to
open table handles. */ static void
online_retry_drop_indexes_low( /*==========================*/
dict_table_t* table, /*!< in/out: table */
trx_t* trx) /*!< in/out: transaction */
{
ut_ad(dict_sys.locked());
ut_ad(trx->dict_operation_lock_mode);
ut_ad(trx->dict_operation);
/* We can have table->n_ref_count > 1, because other threads mayhaveprebuilt->tablepointingtothetable.However,these otherthreadsshouldbebetweenstatements,waitingforthe
next statement to execute, or for a meta-data lock. */
ut_ad(table->get_ref_count() >= 1);
if (table->drop_aborted) {
row_merge_drop_indexes(trx, table, true);
}
}
/** After commit, unlock the data dictionary and close any deleted files. @paramdeletedhandlesofdeletedfiles
@param trx committed transaction */ staticvoid unlock_and_close_files(const std::vector<pfs_os_file_t> &deleted,
trx_t *trx)
{
row_mysql_unlock_data_dictionary(trx); for (pfs_os_file_t d : deleted)
os_file_close(d); if (lsn_t lsn= trx->commit_lsn)
{
trx->commit_lsn= 0;
log_write_up_to(lsn, true);
}
}
/** Commit a DDL transaction and unlink any deleted files. */ staticvoid commit_unlock_and_unlink(trx_t *trx)
{
std::vector<pfs_os_file_t> deleted;
trx->commit(deleted);
unlock_and_close_files(deleted, trx);
}
/* Changing from NULL to NOT NULL. So check referenced set */ if (new_field_flags & NOT_NULL_FLAG)
{ for (dict_foreign_t *foreign : user_table->foreign_set)
{ if (innobase_dropping_foreign(foreign, drop_fk, n_drop_fk)) continue;
if (strict_mode && foreign->on_update_cascade_null(col_name)) goto non_null_error;
/* Check if any FOREIGN KEY constraints are defined on this column. */ for (dict_foreign_t *foreign : user_table->foreign_set)
{ if (innobase_dropping_foreign(foreign, drop_fk, n_drop_fk)) continue;
for (unsigned f = 0; f < foreign->n_fields; f++) if (!strcmp(foreign->foreign_col_names[f], col_name))
{
my_error(ER_FK_COLUMN_CANNOT_DROP, MYF(0),
col_name, foreign->sql_id()); returntrue;
}
}
/* Check if any FOREIGN KEY constraints in other tables are
referring to the column that is being dropped. */ for (dict_foreign_t *foreign : user_table->referenced_set)
{ if (innobase_dropping_foreign(foreign, drop_fk, n_drop_fk)) continue;
auto end = ha_alter_info->alter_info->create_list.end(); auto it = std::find_if(
ha_alter_info->alter_info->create_list.begin(), end,
[fp](const Create_field& field) { return field.field == *fp;
});
for (; i < old_table->n_cols; i++) {
col_map[i] = i + new_table->n_cols - old_table->n_cols;
}
DBUG_RETURN(col_map);
}
/** Get the new non-virtual column names if any columns were renamed @paramha_alter_infoDatausedduringin-placealter @paramaltered_tableMySQLtablethatisbeingaltered @paramtableMySQLtableasitisbeforetheALTERoperation @paramuser_tableInnoDBtableasitisbeforetheALTERoperation @paramheapMemoryheapfortheallocation
@return array of new column names in rebuilt_table, or NULL if not renamed */ static MY_ATTRIBUTE((nonnull, warn_unused_result)) constchar**
innobase_get_col_names(
Alter_inplace_info* ha_alter_info, const TABLE* altered_table, const TABLE* table, const dict_table_t* user_table,
mem_heap_t* heap)
{ constchar** cols;
uint i;
/** Check whether the column is existing in old table. @param[in]new_col_nonewcolumnno @param[in]col_mapmappingofoldcolumnnumberstonewones @param[in]col_map_sizethecolumnmapsize
@return true if the column is existing, otherwise false. */ staticinline bool
innobase_pk_col_is_existing( const ulint new_col_no, const ulint* col_map, const ulint col_map_size)
{ for (ulint i = 0; i < col_map_size; i++) { if (col_map[i] == new_col_no) { return(true);
}
}
return(false);
}
/** Determine whether both the indexes have same set of primary key fieldsarrangedinthesameorder.
if (old_n_uniq == 0) { /* There was no PRIMARY KEY in the table. IfthereisnoPRIMARYKEYaftertheALTEReither,
no sorting is needed. */ return(new_n_uniq == old_n_uniq);
}
/* DROP PRIMARY KEY is only allowed in combination with
ADD PRIMARY KEY. */
ut_ad(new_n_uniq > 0);
/* The order of the last processed new_clust_index key field,
not counting ADD COLUMN, which are constant. */
lint last_field_order = -1;
ulint existing_field_count = 0;
ulint old_n_cols = dict_table_get_n_cols(old_clust_index->table); for (ulint new_field = 0; new_field < new_n_uniq; new_field++) {
ulint new_col_no =
new_clust_index->fields[new_field].col->ind;
/* Check if there is a match in old primary key. */
ulint old_field = 0; while (old_field < old_n_uniq) {
ulint old_col_no =
old_clust_index->fields[old_field].col->ind;
if (col_map[old_col_no] == new_col_no) { break;
}
old_field++;
}
/* The order of key field in the new primary key. 1.oldPKcolumn:idxinoldprimarykey 2.existingcolumn:old_n_uniq+sequenceno
3. newly added column: no order */
lint new_field_order; constbool old_pk_column = old_field < old_n_uniq;
if (old_pk_column) {
new_field_order = lint(old_field);
} elseif (innobase_pk_col_is_existing(new_col_no, col_map,
old_n_cols)
|| new_clust_index->table->persistent_autoinc
== new_field + 1) { /* Adding an existing column or an AUTO_INCREMENT
column may change the existing ordering. */
new_field_order = lint(old_n_uniq
+ existing_field_count++);
} else { /* Skip newly added column. */ continue;
}
if (last_field_order + 1 != new_field_order) { /* Old PK order is not kept, or existing column
is not added at the end of old PK. */ return(false);
}
if (prefix_change < 0) { /* If a column's prefix length is decreased, it should bethelastoldPKcolumninnewPK. Note:wesetlast_field_orderto-2,sothatifthere areanyoldPKcolmnsorexistingcolumnsafteritin newPK,thecomparisontonew_field_orderwillfailin
the next round.*/
last_field_order = -2;
} elseif (prefix_change > 0) { /* If a column's prefix length is increased, it should
be the last PK column in old PK. */ if (old_field != old_n_uniq - 1) { return(false);
}
}
}
return(true);
}
/** Update the mtype from DATA_BLOB to DATA_GEOMETRY for a specified GIScolumnofatable.Thisisusedwhenwewanttocreatespatialindex onlegacyGIScolumnscomingfrom5.6,wherewestoreGISdataasDATA_BLOB ininnodblayer. @param[in]table_idtableid @param[in]col_namecolumnname @param[in]trxdatadictionarytransaction @retvaltrueFailure
@retval false Success */ static bool
innobase_update_gis_column_type(
table_id_t table_id, constchar* col_name,
trx_t* trx)
{
pars_info_t* info;
dberr_t error;
trx->op_info = "update column type to DATA_GEOMETRY";
error = que_eval_sql(
info, "PROCEDURE UPDATE_SYS_COLUMNS_PROC () IS\n" "BEGIN\n" "UPDATE SYS_COLUMNS SET MTYPE=:mtype\n" "WHERE TABLE_ID=:tableid AND NAME=:name;\n" "END;\n", trx);
trx->error_state = DB_SUCCESS;
trx->op_info = "";
DBUG_RETURN(error != DB_SUCCESS);
}
/** Check if we are creating spatial indexes on GIS columns, which are legacycolumnsfromearlierMySQL,suchas5.6.Ifso,wehavetoupdate themtypesoftheoldGIScolumnstoDATA_GEOMETRY. In5.6,westoreGIScolumnsasDATA_BLOBinInnoDBlayer,itwillintroduce confusionwhenwerunlatestserveronolderdata.That'swhyweneedto dotheupgrade. @param[in]ha_alter_infoDatausedduringin-placealter @param[in]tableTableonwhichwewanttoaddindexes @param[in]trxTransaction @returnDB_SUCCESSifupdatesuccessfullyornocolumnsneedtobeupdated, otherwiseDB_ERROR,whichmeanswecan'tupdatethemtypeforsome
column, and creating spatial index on it should be dangerous */ static
dberr_t
innobase_check_gis_columns(
Alter_inplace_info* ha_alter_info,
dict_table_t* table,
trx_t* trx)
{
DBUG_ENTER("innobase_check_gis_columns");
for (uint key_num = 0;
key_num < ha_alter_info->index_add_count;
key_num++) {
for (unsigned i = 0; i < vcol->num_base; i++) { if (innobase_insert_sys_virtual(
table, pos, vcol->base_col[i]->ind, trx)) { returntrue;
}
}
returnfalse;
}
/** Update SYS_TABLES.N_COLS in the data dictionary. @param[in]user_tableInnoDBtable @param[in]nthenewvalueofSYS_TABLES.N_COLS @param[in]trxtransaction
@return whether the operation failed */ staticbool innodb_update_cols(const dict_table_t* table, ulint n, trx_t* trx)
{
pars_info_t* info = pars_info_create();
for (ulint i = 0; i < ctx->num_to_add_vcol; i++) { if (innobase_add_one_virtual(
user_table, ctx->add_vcol_name[i],
&ctx->add_vcol[i], trx)) { returntrue;
}
}
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);
} elseif ((*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:
variable_length: /* Store the empty string for 'core'
variable-length NOT NULL columns. */
dfield_set_data(d, field_ref_zero, 0); break; case MYSQL_TYPE_STRING: if (col->mbminlen != col->mbmaxlen
&& user_table->not_redundant()) { goto variable_length;
} /* fall through */ default: /* For fixed-length NOT NULL 'core' columns, getadummydefaultvaluefromSQL.Notethat wewillpreservetheoldvaluesofthese columnswhenupdatingthemetadata
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. */
} elseif (innodb_insert_sys_columns(user_table->id, i,
(*af)->field_name.str,
d->type.mtype,
d->type.prtype,
d->type.len, 0, trx,
update)) { returntrue;
}
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).str,
&user_table->v_cols[i], trx)) { returntrue;
}
}
} elseif (ha_alter_info->handler_flags & ALTER_DROP_VIRTUAL_COLUMN) { if (innobase_instant_drop_cols(user_table->id, 65536, trx)) { returntrue;
} goto add_all_virtual;
} elseif ((ha_alter_info->handler_flags & ALTER_ADD_VIRTUAL_COLUMN)
&& innobase_add_virtual_try(ha_alter_info, user_table,
trx)) { returntrue;
}
if (!user_table->space) { /* In case of ALTER TABLE...DISCARD TABLESPACE, updateonlythemetadataandtransformthedictionary
cache entry to the canonical format. */
index->clear_instant_alter(); returnfalse;
}
trx_write_trx_id(trx_id, trx->id); /* The DB_ROLL_PTR will be assigned later, when allocating undo log. SilenceaValgrindwarningindtuple_validate()when
row_ins_clust_index_entry_low() searches for the insert position. */
memset(roll_ptr, 0, sizeof roll_ptr);
/* Ensure that the root page is in the correct format. */
id.set_page_no(index->page);
buf_block_t* root = mtr.get_already_latched(
id, MTR_MEMO_PAGE_SX_FIX);
if (UNIV_UNLIKELY(!root)) {
err = DB_CORRUPTION; goto func_exit;
}
/* 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-updatedoff-pagecolumnsincasetheyaremovedoff
page as a result of the update. */ const uint16_t 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 = static_cast<uint16_t>(k + f);
uf->new_val = entry->fields[k + f];
/* Only adjust virtual column col_no, since non-virtual columnposition(innon-vcollist)won'tchangeunless
table rebuild */ if (!index_field->is_v_col) { continue;
}
const Field* field = NULL;
/* Found the field in the new table */ for (const Create_field& new_field :
ha_alter_info->alter_info->create_list) { if (new_field.stored_in_db()) { continue;
}
field = new_field.field;
if (num_v == index_field->col_no) { break;
}
num_v++;
}
if (!field) { /* this means the field is a newly added field, this shouldhavebeenblockedwhenwedropvirtualcolumn
at the same time */
ut_ad(num_v_dropped > 0);
ut_a(0);
}
ut_ad(!field->stored_in_db());
num_v = 0;
/* Look for its position in old table */ for (uint old_i = 0; old_table->field[old_i]; old_i++) { if (old_table->field[old_i] == field) { /* Found it, adjust its col_no to its position
in old table */
index_def->fields[i].col_no = num_v;
ut_d(col_found = true); break;
}
switch (ha_alter_info->inplace_supported) { default: break; case HA_ALTER_INPLACE_INSTANT: case HA_ALTER_INPLACE_NOCOPY_LOCK: case HA_ALTER_INPLACE_NOCOPY_NO_LOCK: /* If we promised ALGORITHM=NOCOPY or ALGORITHM=INSTANT,
we must retain the original ROW_FORMAT of the table. */
flags = (user_table->flags & (DICT_TF_MASK_COMPACT
| DICT_TF_MASK_ZIP_SSIZE
| DICT_TF_MASK_ATOMIC_BLOBS))
| (flags & ~(DICT_TF_MASK_COMPACT
| DICT_TF_MASK_ZIP_SSIZE
| DICT_TF_MASK_ATOMIC_BLOBS));
}
/* The primary index would be rebuilt if a FTS Doc ID columnistobeadded,andtheprimaryindexdefinition
is just copied from old table and stored in indexdefs[0] */
DBUG_ASSERT(!add_fts_doc_id || new_clustered);
DBUG_ASSERT(!!new_clustered ==
(innobase_need_rebuild(ha_alter_info, old_opt)
|| add_fts_doc_id));
/* Allocate memory for dictionary index definitions */
/* The MySQL pack length contains 1 or 2 bytes lengthfieldforatrueVARCHAR.Letus subtractthat,sothattheInnoDBcolumn lengthintheInnoDBdatadictionaryisthe
real maximum byte length of the actual data. */
/** Note the FTS_DOC_ID name is case sensitive due tointernalqueryparser. FTS_DOC_IDcolumnmustbeofBIGINTNOTNULLtype
and it should be in all capitalized characters */ if (field->field_name.streq(FTS_DOC_ID)) { if (col_type != DATA_INT
|| field->real_maybe_null()
|| col_len != sizeof(doc_id_t)
|| strcmp(field->field_name.str,
FTS_DOC_ID.str)) { goto wrong_column_name;
}
}
for (dict_index_t* index
= dict_table_get_first_index(user_table);
index != NULL;
index = dict_table_get_next_index(index)) { if (!index->to_be_dropped && index->is_corrupted()) {
my_error(ER_CHECK_NO_SUCH_TABLE, MYF(0)); goto error_handled;
}
}
for (dict_index_t* index
= dict_table_get_first_index(user_table);
index != NULL;
index = dict_table_get_next_index(index)) { if (!index->to_be_dropped && index->is_corrupted()) {
my_error(ER_CHECK_NO_SUCH_TABLE, MYF(0)); goto error_handled;
}
}
for (ulint a = 0; a < ctx->num_to_add_index; a++) {
dict_index_t* index = ctx->add_index[a]; const ulint n_v_col = index->get_new_n_vcol();
index = create_index_dict(ctx->trx, index,
mode, key_id, add_v);
error = ctx->trx->error_state; if (error != DB_SUCCESS) { if (index) {
dict_mem_index_free(index);
}
error_handling_drop_uncached_1: while (++a < ctx->num_to_add_index) {
dict_mem_index_free(ctx->add_index[a]);
} goto error_handling;
} else {
DBUG_ASSERT(index != ctx->add_index[a]);
}
ctx->add_index[a] = index; /* For ALTER TABLE...FORCE or OPTIMIZE TABLE, wemayonlyissuewarnings,becausetherewill
be no schema change from the user perspective. */ if (!info.row_size_is_acceptable(
*index,
!!(ha_alter_info->handler_flags
& ~(INNOBASE_INPLACE_IGNORE
| INNOBASE_ALTER_NOVALIDATE
| ALTER_RECREATE_TABLE)))) {
error = DB_TOO_BIG_RECORD; goto error_handling_drop_uncached_1;
}
index->parser = index_defs[a].parser; if (n_v_col) {
index->assign_new_v_col(n_v_col);
} /* Note the id of the transaction that created this index,weuseittorestrictreadersfromaccessing
this index, to ensure read consistency. */
ut_ad(index->trx_id == ctx->trx->id);
/* Initialize the AUTO_INCREMENT sequence
to the rebuilt table from the old one. */ if (!old_table->found_next_number_field
|| !user_table->space) {
} elseif (ib_uint64_t autoinc
= btr_read_autoinc(clust_index)) {
btr_write_autoinc(ctx->prebuilt->trx,
new_clust_index, autoinc);
}
}
for (ulint a = 0; a < ctx->num_to_add_index; a++) {
dict_index_t* index = ctx->add_index[a]; const ulint n_v_col = index->get_new_n_vcol();
DBUG_EXECUTE_IF( "create_index_metadata_fail", if (a + 1 == ctx->num_to_add_index) {
ctx->trx->error_state =
DB_OUT_OF_FILE_SPACE; goto index_created;
});
index = create_index_dict(ctx->trx, index,
FIL_ENCRYPTION_DEFAULT,
FIL_DEFAULT_ENCRYPTION_KEY,
add_v); #ifndef DBUG_OFF
index_created: #endif
error = ctx->trx->error_state; if (error != DB_SUCCESS) { if (index) {
dict_mem_index_free(index);
}
error_handling_drop_uncached: while (++a < ctx->num_to_add_index) {
dict_mem_index_free(ctx->add_index[a]);
} goto error_handling;
} else {
DBUG_ASSERT(index != ctx->add_index[a]);
}
ctx->add_index[a]= index; if (!info.row_size_is_acceptable(*index, true)) {
error = DB_TOO_BIG_RECORD; goto error_handling_drop_uncached;
}
index->parser = index_defs[a].parser; if (n_v_col) {
index->assign_new_v_col(n_v_col);
}
ctx->change_col_collation(index, *altered_table); /* Note the id of the transaction that created this index,weuseittorestrictreadersfromaccessing
this index, to ensure read consistency. */
ut_ad(index->trx_id == ctx->trx->id);
/* If ADD INDEX with LOCK=NONE has been
requested, allocate a modification log. */ if (index->type & DICT_FTS) {
DBUG_ASSERT(num_fts_index == 1);
DBUG_ASSERT(!fts_index);
DBUG_ASSERT(index->type == DICT_FTS);
fts_index = ctx->add_index[a]; /* Fulltext indexes are not covered
by a modification log. */
} elseif (!ctx->online
|| !user_table->is_readable()
|| !user_table->space) { /* No need to allocate a modification log. */
DBUG_ASSERT(!index->online_log);
} else {
index->lock.x_lock(SRW_LOCK_CALL);
if (ctx->online && ctx->num_to_add_index) { /* Assign a consistent read view for
row_merge_read_clustered_index(). */
ctx->prebuilt->trx->read_view.open(ctx->prebuilt->trx);
}
if (fts_index) {
ut_ad(ctx->trx->dict_operation);
ut_ad(ctx->trx->dict_operation_lock_mode);
ut_ad(dict_sys.locked());
DICT_TF2_FLAG_SET(ctx->new_table, DICT_TF2_FTS); if (ctx->need_rebuild()) { /* For !ctx->need_rebuild(), this will be set at
commit_cache_norebuild(). */
ctx->new_table->fts_doc_id_index
= dict_table_get_index_on_name(
ctx->new_table, FTS_DOC_ID_INDEX.str);
DBUG_ASSERT(ctx->new_table->fts_doc_id_index != NULL);
}
/* If table rebuild happens or fulltext index are beingaddedwhencommontablesdoesn'texistthen docreatenewfulltextcommontableselseusethe
existing fulltext common tables */ if (fts_instance_created) {
error = fts_create_common_tables(
ctx->trx, ctx->new_table, true);
if (error != DB_SUCCESS) { goto error_handling;
}
}
}
DBUG_ASSERT(error == DB_SUCCESS);
{ /* Commit the data dictionary transaction in order to release thetablelocksonthesystemtables.Thismeansthatif MariaDBiskilledwhilerebuildingthetableinside row_merge_build_indexes(),ctx->new_tablewillnotbedropped
by trx_rollback_active(). */
ut_d(dict_table_check_for_dup_indexes(user_table,
CHECK_PARTIAL_OK)); if (ctx->need_rebuild()) {
export_vars.innodb_bulk_operations++;
ctx->new_table->acquire();
}
/* fts_create_common_tables() may drop old common tables,
whose files would be deleted here. */
commit_unlock_and_unlink(ctx->trx); if (pause_purge) {
purge_sys.resume_FTS();
}
err_exit: /* Clear the to_be_dropped flag in the data dictionary cache. */ for (ulint i = 0; i < ctx->num_to_drop_index; i++) {
DBUG_ASSERT(ctx->drop_index[i]->is_committed());
DBUG_ASSERT(ctx->drop_index[i]->to_be_dropped);
ctx->drop_index[i]->to_be_dropped = 0;
}
if (ctx->trx) {
row_mysql_unlock_data_dictionary(ctx->trx);
ctx->trx->rollback();
ctx->trx->free();
}
trx_commit_for_mysql(ctx->prebuilt->trx); if (pause_purge) {
purge_sys.resume_FTS();
}
for (uint i = 0; i < ctx->num_to_add_fk; i++) { if (ctx->add_fk[i]) {
dict_foreign_free(ctx->add_fk[i]);
}
}
delete ctx;
ha_alter_info->handler_ctx = NULL;
DBUG_RETURN(true);
}
/* Check whether an index is needed for the foreign key constraint. Ifso,ifitisdropped,isthereanequivalentindexcanplayitsrole.
@return true if the index is needed and can't be dropped */ static MY_ATTRIBUTE((nonnull(1,2,3,5), warn_unused_result)) bool
innobase_check_foreign_key_index( /*=============================*/
Alter_inplace_info* ha_alter_info, /*!< in: Structure describing changestobedonebyALTER
TABLE */
dict_index_t* index, /*!< in: index to check */
dict_table_t* indexed_table, /*!< in: table that owns the
foreign keys */ constchar** col_names, /*!< in: column names, or NULL
for indexed_table->col_names */
trx_t* trx, /*!< in/out: transaction */
dict_foreign_t** drop_fk, /*!< in: Foreign key constraints
to drop */
ulint n_drop_fk) /*!< in: Number of foreign keys
to drop */
{ const dict_foreign_set* fks = &indexed_table->referenced_set;
/* Check for all FK references from other tables to the index. */ for (dict_foreign_set::const_iterator it = fks->begin();
it != fks->end(); ++it) {
/** Rename the index name in cache. @param[in]ctxaltercontext
@param[in] ha_alter_info Data used during inplace alter. */ staticvoid
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);
/** Check whether the column is present in table foreign key relations. @paramtabletablewhichhasforeignkeyrelation @paramcolcolumntobechecked @paramcol_namecolumnnametobedisplayduringerror @paramdrop_fkDropforeignkeyconstraint @paramn_drop_fknumberofdropforeignkeys @paramadd_fkNewlyaddedforeignkeyconstraint
@param n_add_fk number of newly added foreign constraint */ static bool check_col_is_in_fk_indexes( const dict_table_t *table, const dict_col_t *col, constchar* col_name,
span<const dict_foreign_t *> drop_fk,
span<const dict_foreign_t *> add_fk)
{ const dict_foreign_t *fk;
for (constauto f : table->foreign_set)
{ if (!f->foreign_index ||
std::find(drop_fk.begin(), drop_fk.end(), f) != drop_fk.end()) continue; for (ulint i= 0; i < f->n_fields; i++) if (f->foreign_index->fields[i].col == col)
{
fk= f; goto err_exit;
}
}
for (constauto a : add_fk)
{ if (!a->foreign_index) continue; for (ulint i= 0; i < a->n_fields; i++)
{ if (a->foreign_index->fields[i].col == col)
{
fk= a; goto err_exit;
}
}
}
for (constauto f : table->referenced_set)
{ if (!f->referenced_index) continue; for (ulint i= 0; i < f->n_fields; i++)
{ if (f->referenced_index->fields[i].col == col)
{
my_error(ER_FK_COLUMN_CANNOT_CHANGE_CHILD, MYF(0),
col_name, f->sql_id(), f->foreign_table_name); returntrue;
}
}
} returnfalse;
err_exit:
my_error(ER_FK_COLUMN_CANNOT_CHANGE, MYF(0), col_name,
fk->id ? fk->sql_id() : "0"); returntrue;
}
/** Allows InnoDB to update internal structures with concurrent writesblocked(providedthatcheck_if_supported_inplace_alter() didnotreturnHA_ALTER_INPLACE_NO_LOCK). Thiswillbeinvokedbeforeinplace_alter_table().
bool
ha_innobase::prepare_inplace_alter_table( /*=====================================*/
TABLE* altered_table,
Alter_inplace_info* ha_alter_info)
{
dict_index_t** drop_index; /*!< Index to be dropped */
ulint n_drop_index; /*!< Number of indexes to drop */
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 */
ulint n_add_fk= 0; /*!< Number of foreign keys to drop */
dict_table_t* indexed_table; /*!< Table where indexes are created */
mem_heap_t* heap; constchar** col_names; int error;
ulint add_autoinc_col_no = ULINT_UNDEFINED;
ulonglong autoinc_col_max_value = 0;
ulint fts_doc_col_no = ULINT_UNDEFINED; bool add_fts_doc_id = false; bool add_fts_doc_id_idx = false; bool add_fts_idx = false;
dict_s_col_list*s_cols = NULL;
mem_heap_t* s_heap = NULL;
if (!(ha_alter_info->handler_flags & ~INNOBASE_INPLACE_IGNORE)) { /* Nothing to do */
DBUG_ASSERT(!m_prebuilt->trx->dict_operation_lock_mode);
m_prebuilt->trx_id = 0;
DBUG_RETURN(false);
}
#ifdef WITH_PARTITION_STORAGE_ENGINE if (table->part_info == NULL) { #endif /* Ignore the MDL downgrade when table is empty.
This optimization is disabled for partition table. */
ha_alter_info->mdl_exclusive_after_prepare =
innobase_table_is_empty(m_prebuilt, false); if (ha_alter_info->online
&& ha_alter_info->mdl_exclusive_after_prepare) {
ha_alter_info->online = false;
} #ifdef WITH_PARTITION_STORAGE_ENGINE
} #endif
indexed_table = m_prebuilt->table;
/* ALTER TABLE will not implicitly move a table from a single-table tablespacetothesystemtablespacewheninnodb_file_per_table=OFF. Butitwillimplicitlymoveatablefromthesystemtablespacetoa
single-table tablespace if innodb_file_per_table = ON. */
if (ha_alter_info->handler_flags & ALTER_ADD_NON_UNIQUE_NON_PRIM_INDEX) { if (info.gcols_in_fulltext_or_spatial()) { goto err_exit_no_heap;
}
}
if (indexed_table->is_readable()) {
} else { if (indexed_table->corrupted) { /* Handled below */
} else { if (const fil_space_t* space = indexed_table->space) {
String str; constchar* engine= table_type();
push_warning_printf(
m_user_thd,
Sql_condition::WARN_LEVEL_WARN,
HA_ERR_DECRYPTION_FAILED, "Table %s in file %s is encrypted but encryption service or" " used key_id is not available. " " Can't continue reading table.",
table_share->table_name.str,
space->chain.start->name);
/* Check if any index name is reserved. */ if (innobase_index_name_is_reserved(
m_user_thd,
ha_alter_info->key_info_buffer,
ha_alter_info->key_count)) {
err_exit_no_heap:
DBUG_ASSERT(!m_prebuilt->trx->dict_operation_lock_mode);
online_retry_drop_indexes(m_prebuilt->table, m_user_thd);
DBUG_RETURN(true);
}
indexed_table = m_prebuilt->table;
/* Check that index keys are sensible */
error = innobase_check_index_keys(ha_alter_info, indexed_table);
if (error) { goto err_exit_no_heap;
}
/* Prohibit renaming a column to something that the table
already contains. */ if (ha_alter_info->handler_flags
& ALTER_COLUMN_NAME) { for (Field** fp = table->field; *fp; fp++) { if (!((*fp)->flags & FIELD_IS_RENAMED)) { continue;
}
constchar* name = 0;
for (const Create_field& cf :
ha_alter_info->alter_info->create_list) { if (cf.field == *fp) {
name = cf.field_name.str; goto check_if_ok_to_rename;
}
}
ut_error;
check_if_ok_to_rename: /* Prohibit renaming a column from FTS_DOC_ID
if full-text indexes exist. */ if ((*fp)->field_name.streq(FTS_DOC_ID)
&& innobase_fulltext_exist(altered_table)) {
my_error(ER_INNODB_FT_WRONG_DOCID_COLUMN,
MYF(0), name); goto err_exit_no_heap;
}
/* Prohibit renaming a column to an internal column. */ constchar* s = m_prebuilt->table->col_names; unsigned j; /* Skip user columns. MySQLshouldhavecheckedthesealready.
We want to allow renaming of c1 to c2, c2 to c1. */ for (j = 0; j < table->s->fields; j++) { if (table->field[j]->stored_in_db()) {
s += strlen(s) + 1;
}
}
for (; j < m_prebuilt->table->n_def; j++) { if (Lex_ident_column(Lex_cstring_strlen(name)).
streq(Lex_cstring_strlen(s))) {
my_error(ER_WRONG_COLUMN_NAME, MYF(0),
s); goto err_exit_no_heap;
}
s += strlen(s) + 1;
}
}
}
if (!info.innobase_table_flags()) {
my_error(ER_ILLEGAL_HA_CREATE_OPTION, MYF(0),
table_type(), "PAGE_COMPRESSED"); goto err_exit_no_heap;
}
if (info.flags2() & DICT_TF2_USE_FILE_PER_TABLE) { /* Preserve the DATA DIRECTORY attribute, because it
currently cannot be changed during ALTER TABLE. */
info.flags_set(m_prebuilt->table->flags
& 1U << DICT_TF_POS_DATA_DIR);
}
/* ALGORITHM=INPLACE without rebuild (10.3+ ALGORITHM=NOCOPY)
must use the current ROW_FORMAT of the table. */ const ulint max_col_len = DICT_MAX_FIELD_LEN_BY_FORMAT_FLAG(
innobase_need_rebuild(ha_alter_info, option_struct)
? info.flags()
: m_prebuilt->table->flags);
/* Check each index's column length to make sure they do not
exceed limit */ for (ulint i = 0; i < ha_alter_info->key_count; i++) { const KEY* key = &ha_alter_info->key_info_buffer[i];
if (key->algorithm == HA_KEY_ALG_FULLTEXT) { /* The column length does not matter for fulltextsearchindexes.But,UNIQUE
fulltext indexes are not supported. */
DBUG_ASSERT(!(key->flags & HA_NOSAME));
DBUG_ASSERT(!(key->flags & HA_KEYFLAG_MASK
& ~(HA_PACK_KEY
| HA_BINARY_PACK_KEY)));
add_fts_idx = true; continue;
}
/* We won't be allowed to add fts index to a table with ftsindexesalreadybutwithoutAUX_HEX_NAMEset. Thismeanstheauxtablesofthetablefailedto renametohexformatbutnewcreatedauxtables
shall be in hex format, which is contradictory. */ if (!DICT_TF2_FLAG_IS_SET(indexed_table, DICT_TF2_FTS_AUX_HEX_NAME)
&& indexed_table->fts != NULL && add_fts_idx) {
my_error(ER_INNODB_FT_AUX_NOT_HEX_ID, MYF(0)); goto err_exit_no_heap;
}
/* Check existing index definitions for too-long column
prefixes as well, in case max_col_len shrunk. */ for (const dict_index_t* index
= dict_table_get_first_index(indexed_table);
index;
index = dict_table_get_next_index(index)) { if (index->type & DICT_FTS) {
DBUG_ASSERT(index->type == DICT_FTS
|| (index->type & DICT_CORRUPT));
/* We need to drop any corrupted fts indexes
before we add a new fts index. */ if (add_fts_idx && index->type & DICT_CORRUPT) {
ib_errf(m_user_thd, IB_LOG_LEVEL_ERROR,
ER_INNODB_INDEX_CORRUPT, "Fulltext index '%s' is corrupt. " "you should drop this index first.",
index->name());
goto err_exit_no_heap;
}
continue;
}
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 (field->prefix_len > max_col_len) {
my_error(ER_INDEX_COLUMN_TOO_LONG, MYF(0),
max_col_len); goto err_exit_no_heap;
}
}
}
// Add some fault tolerance for non-debug builds. if (fts_doc_index == NULL) { goto check_if_can_drop_indexes;
}
DBUG_ASSERT(!fts_doc_index->to_be_dropped);
for (uint i = 0; i < table->s->keys; i++) { if (table->key_info[i].name.
streq(FTS_DOC_ID_INDEX)) { /* The index exists in the MySQL datadictionary.Donotdropit, eventhoughitisnolongerneeded
by InnoDB fulltext search. */ goto check_if_can_drop_indexes;
}
}
drop_index[n_drop_index++] = fts_doc_index;
}
check_if_can_drop_indexes: /* Check if the indexes can be dropped. */
/* Prevent a race condition between DROP INDEX and
CREATE TABLE adding FOREIGN KEY constraints. */
row_mysql_lock_data_dictionary(m_prebuilt->trx);
if (!n_drop_index) {
drop_index = NULL;
} else { /* Flag all indexes that are to be dropped. */ for (ulint i = 0; i < n_drop_index; i++) {
ut_ad(!drop_index[i]->to_be_dropped);
drop_index[i]->to_be_dropped = 1;
}
}
if (m_prebuilt->trx->check_foreigns) { for (uint i = 0; i < n_drop_index; i++) {
dict_index_t* index = drop_index[i];
/* If a primary index is dropped, need to check
any depending foreign constraints get affected */ if (drop_primary
&& innobase_check_foreign_key_index(
ha_alter_info, drop_primary,
indexed_table, col_names,
m_prebuilt->trx, drop_fk, n_drop_fk)) {
row_mysql_unlock_data_dictionary(m_prebuilt->trx);
print_error(HA_ERR_DROP_INDEX_FK, MYF(0)); goto err_exit;
}
}
/* Check if any of the existing indexes are marked as corruption
and if they are, refuse adding more indexes. */ if (ha_alter_info->handler_flags & ALTER_ADD_NON_UNIQUE_NON_PRIM_INDEX) { for (dict_index_t* index = dict_table_get_first_index(indexed_table);
index != NULL; index = dict_table_get_next_index(index)) {
if (!innobase_get_foreign_key_info(
ha_alter_info, table_share,
m_prebuilt->table, col_names,
drop_index, n_drop_index,
add_fk, &n_add_fk, m_prebuilt->trx, s_cols)) {
err_exit: if (n_drop_index) {
row_mysql_lock_data_dictionary(m_prebuilt->trx);
/* Clear the to_be_dropped flags, which might
have been set at this point. */ for (ulint i = 0; i < n_drop_index; i++) {
ut_ad(drop_index[i]->is_committed());
drop_index[i]->to_be_dropped = 0;
}
for (uint i = 0; i < n_add_fk; i++) { if (add_fk[i]) {
dict_foreign_free(add_fk[i]);
}
}
if (heap) {
mem_heap_free(heap);
}
if (s_cols != NULL) {
UT_DELETE(s_cols);
mem_heap_free(s_heap);
}
goto err_exit_no_heap;
}
if (s_cols != NULL) {
UT_DELETE(s_cols);
mem_heap_free(s_heap);
}
}
/** Alter shouldn't support if the foreign and referenced
index columns are modified */ if (ha_alter_info->handler_flags
& ALTER_COLUMN_TYPE_CHANGE_BY_ENGINE) {
for (uint i= 0, n_v_col= 0; i < table->s->fields;
i++) {
Field* field = table->field[i];
/* Skip the virtual columns. They are not presentinInnoDBdictionarycols[]array,so theymustnotshiftthefield-to-columnmapping below.Storedgeneratedcolumnsdohavean
InnoDB column and must be counted as stored. */ if (!field->stored_in_db()) {
n_v_col++; continue;
}
for (const Create_field& new_field :
ha_alter_info->alter_info->create_list) { if (new_field.field == field) { if (!field->is_equal(new_field)) { goto field_changed;
} break;
}
}
if (!(ha_alter_info->handler_flags & INNOBASE_ALTER_DATA)
&& alter_templ_needs_rebuild(altered_table, ha_alter_info,
ctx->new_table)
&& ctx->new_table->n_v_cols > 0) { /* Changing maria record structure may end up here only ifvirtualcolumnswerealtered.Inthiscase,however, vc_templshouldberebuilt.Sincewedon'tactually changeanystoreddata,wecanjustdisposevc_templ;
it will be recreated on next ha_innobase::open(). */
success: /* Memorize the future transaction ID for committing thedatadictionarychange,tobereportedby
ha_innobase::table_version(). */
m_prebuilt->trx_id = (ha_alter_info->handler_flags
& ~INNOBASE_INPLACE_IGNORE)
? static_cast<ha_innobase_inplace_ctx*>
(ha_alter_info->handler_ctx)->trx->id
: 0;
DBUG_RETURN(false);
}
/* If we are to build a full-text search index, check whether thetablealreadyhasaDOCIDcolumn.Ifnot,wewillneedto
add a Doc ID hidden column and rebuild the primary index */ if (innobase_fulltext_exist(altered_table)) {
ulint doc_col_no;
ulint num_v = 0;
if (!innobase_fts_check_doc_id_col(
m_prebuilt->table,
altered_table, &fts_doc_col_no, &num_v)) {
/* See if an AUTO_INCREMENT column was added. */
uint i = 0;
ulint num_v = 0; for (const Create_field& new_field :
ha_alter_info->alter_info->create_list) { const Field* field;
DBUG_ASSERT(i < altered_table->s->fields);
for (uint old_i = 0; table->field[old_i]; old_i++) { if (new_field.field == table->field[old_i]) { goto found_col;
}
}
/* This is an added column. */
DBUG_ASSERT(!new_field.field);
DBUG_ASSERT(ha_alter_info->handler_flags
& ALTER_ADD_COLUMN);
if (field->flags & AUTO_INCREMENT_FLAG) { if (add_autoinc_col_no != ULINT_UNDEFINED) { /* This should have been blocked earlier. */
ut_ad(0);
my_error(ER_WRONG_AUTO_KEY, MYF(0)); goto err_exit;
}
/* Get the col no of the old table non-virtual column array */
add_autoinc_col_no = i - num_v;
/** Alter the table structure in-place with operations specifiedusingAlter_inplace_info. Thelevelofconcurrencyallowedduringthisoperationdepends onthereturnvaluefromcheck_if_supported_inplace_alter().
/* Ignore the inplace alter phase when table is empty */ if (!(ha_alter_info->handler_flags & INNOBASE_ALTER_DATA)
|| ha_alter_info->mdl_exclusive_after_prepare) {
ok_exit:
DEBUG_SYNC(m_user_thd, "innodb_after_inplace_alter_table");
DBUG_RETURN(false);
}
/* For partitioned tables this could be already allocated from a
previous partition invocation. For normal tables this is NULL. */
UT_DELETE(ctx->m_stage);
if (!m_prebuilt->table->is_readable()) { goto all_done;
}
/* If we are doing a table rebuilding or having added virtual columnsinthesameclause,wewillneedtobuildatabletemplate thatcarriestranslationinformationbetweenMySQLTABLEandInnoDB table,whichindicatesthevirtualcolumnsandtheirbasecolumns info.Thisisusedtodothecomputationcallback,sothatthe datainbasecolumnscanbeextractedsendtoserver. IftheColumnlengthchangesanditisapartofvirtual
index then we need to rebuild the template. */
rebuild_templ
= ctx->need_rebuild()
|| ((ha_alter_info->handler_flags
& ALTER_COLUMN_TYPE_CHANGE_BY_ENGINE)
&& alter_templ_needs_rebuild(
altered_table, ha_alter_info, ctx->new_table));
if ((ctx->new_table->n_v_cols > 0) && rebuild_templ) { /* Save the templ if isn't NULL so as to restore the
original state in case of alter operation failures. */ if (ctx->new_table->vc_templ != NULL && !ctx->need_rebuild()) {
old_templ = ctx->new_table->vc_templ;
}
s_templ = UT_NEW_NOKEY(dict_vcol_templ_t());
ctx->new_table->lock_mutex_lock();
innobase_build_v_templ(
altered_table, ctx->new_table, s_templ);
ctx->new_table->vc_templ = s_templ;
ctx->new_table->lock_mutex_unlock();
} elseif (ctx->num_to_add_vcol > 0 && ctx->num_to_drop_vcol == 0) { /* if there is ongoing drop virtual column, then we disallow inplaceaddindexonnewlyaddedvirtualcolumn,soitdoes notneedtocomeinheretorebuildtemplatewithadd_v.
Please also see the assertion in innodb_v_adjust_idx_col() */
/* Drop virtual column without rebuild will keep dict table unchanged,weuseoldtabletoevaluatevirtualcolumnvalue
in innobase_get_computed_value(). */ if (!ctx->need_rebuild() && ctx->num_to_drop_vcol > 0) {
eval_table = table;
}
/* Read the clustered index of the table and build indexesbasedonthisinformationusingtemporary
files and merge sort. */
DBUG_EXECUTE_IF("innodb_OOM_inplace_alter",
error = DB_OUT_OF_MEMORY; goto oom;);
/* After an error, remove all those index definitions
from the dictionary which were defined. */
switch (error) {
KEY* dup_key; default:
my_error_innodb(error,
table_share->table_name.str,
m_prebuilt->table->flags); break;
all_done: case DB_SUCCESS:
ut_d(dict_sys.freeze(SRW_LOCK_CALL));
ut_d(dict_table_check_for_dup_indexes(
m_prebuilt->table, CHECK_PARTIAL_OK));
ut_d(dict_sys.unfreeze()); /* prebuilt->table->n_ref_count can be anything here,
given that we hold at most a shared lock on the table. */ goto ok_exit; case DB_DUPLICATE_KEY: if (m_prebuilt->trx->error_key_num == ULINT_UNDEFINED
|| ha_alter_info->key_count == 0) { /* This should be the hidden index on FTS_DOC_ID,orthereisnoPRIMARYKEYinthe table.Eitherway,weshouldbeseeingand
reporting a bogus duplicate key error. */
dup_key = NULL;
} else {
DBUG_ASSERT(m_prebuilt->trx->error_key_num
< ha_alter_info->key_count);
dup_key = &ha_alter_info->key_info_buffer[
m_prebuilt->trx->error_key_num];
}
print_keydup_error(altered_table, dup_key, MYF(0)); break; case DB_ONLINE_LOG_TOO_BIG:
DBUG_ASSERT(ctx->online);
my_error(ER_INNODB_ONLINE_LOG_TOO_BIG, MYF(0),
get_error_key_name(m_prebuilt->trx->error_key_num,
ha_alter_info, m_prebuilt->table)); break; case DB_INDEX_CORRUPT:
my_error(ER_INDEX_CORRUPT, MYF(0),
get_error_key_name(m_prebuilt->trx->error_key_num,
ha_alter_info, m_prebuilt->table)); break; case DB_DECRYPTION_FAILED:
String str; constchar* engine= table_type();
get_error_message(HA_ERR_DECRYPTION_FAILED, &str);
my_error(ER_GET_ERRMSG, MYF(0), HA_ERR_DECRYPTION_FAILED,
str.c_ptr(), engine); break;
}
/* prebuilt->table->n_ref_count can be anything here, given
that we hold at most a shared lock on the table. */
m_prebuilt->trx->error_info = NULL;
ctx->trx->error_state = DB_SUCCESS;
DBUG_RETURN(true);
}
/** Free the modification log for online table rebuild.
@param table table that was being rebuilt online */ static void
innobase_online_rebuild_log_free( /*=============================*/
dict_table_t* table)
{
dict_index_t* clust_index = dict_table_get_first_index(table);
ut_ad(dict_sys.locked());
clust_index->lock.x_lock(SRW_LOCK_CALL);
/** For each user column, which is part of an index which is not going to be dropped,itchecksifthecolumnnumberofthecolumnissameascol_no argumentpassed. @param[in]tabletable @param[in]col_nocolumnnumber @param[in]is_vifthisisavirtualcolumn @param[in]only_committedwhethertoconsideronlycommittedindexes @retvaltruecolumnexists @retvalfalsecolumndoesnotexist,trueifcolumnissystemcolumnor
it is in the index. */ static bool
check_col_exists_in_indexes( const dict_table_t* table,
ulint col_no, bool is_v, bool only_committed = false)
{ /* This function does not check system columns */ if (!is_v && dict_table_get_nth_col(table, col_no)->mtype == DATA_SYS) { return(true);
}
for (const dict_index_t* index = dict_table_get_first_index(table);
index;
index = dict_table_get_next_index(index)) {
if (only_committed
? !index->is_committed()
: index->to_be_dropped) { continue;
}
for (ulint i = 0; i < index->n_user_defined_cols; i++) { const dict_col_t* idx_col
= dict_index_get_nth_col(index, i);
if (is_v && idx_col->is_virtual()) { const dict_v_col_t* v_col = reinterpret_cast< const dict_v_col_t*>(idx_col); if (v_col->v_pos == col_no) { return(true);
}
}
/** Rollback a secondary index creation, drop the indexes with tempararyindexprefix @paramuser_tableInnoDBtable @paramtabletheTABLE @paramlockedTRUE=tablelocked,FALSE=mayneedtodoalazydrop @paramtrxthetransaction @paramalter_trxtransactionwhichtakesS-lockonthetable
while creating the index */ static void
innobase_rollback_sec_index(
dict_table_t* user_table, const TABLE* table, bool locked,
trx_t* trx, const trx_t* alter_trx=NULL)
{
row_merge_drop_indexes(trx, user_table, locked, alter_trx);
/* Free the table->fts only if there is no FTS_DOC_ID
in the table */ if (user_table->fts
&& !DICT_TF2_FLAG_IS_SET(user_table,
DICT_TF2_FTS_HAS_DOC_ID)
&& !innobase_fulltext_exist(table)) {
user_table->fts->~fts_t();
user_table->fts = nullptr;
}
}
MY_ATTRIBUTE((nonnull, warn_unused_result)) /** Roll back the changes made during prepare_inplace_alter_table() andinplace_alter_table()insidethestorageengine.Notethatthe allowedlevelofconcurrencyduringthisoperationwillbethesameas forinplace_alter_table()andthusmightbehigherthanduring prepare_inplace_alter_table().(E.gconcurrentwriteswereblocked duringprepare,butmightnotbeduringcommit).
DEBUG_SYNC_C("innodb_rollback_inplace_alter_table"); if (!ctx) /* If we have not started a transaction yet,
(almost) nothing has been or needs to be done. */
dict_sys.lock(SRW_LOCK_CALL); elseif (!ctx->trx->is_started()) goto free_and_exit; elseif (ctx->new_table)
{
ut_ad(ctx->trx->state == TRX_STATE_ACTIVE); constbool fts_exist= (ctx->new_table->flags2 &
(DICT_TF2_FTS_HAS_DOC_ID | DICT_TF2_FTS)) ||
ctx->adding_fulltext_index(); if (ctx->need_rebuild())
{ if (fts_exist)
{
fts_optimize_remove_table(ctx->new_table);
purge_sys.stop_FTS(*ctx->new_table);
}
dberr_t err= lock_table_for_trx(ctx->new_table, ctx->trx, LOCK_X); if (fts_exist)
{ if (err == DB_SUCCESS)
err= fts_lock_common_tables(ctx->trx, *ctx->new_table); for (const dict_index_t* index= ctx->new_table->indexes.start;
err == DB_SUCCESS && index; index= index->indexes.next) if (index->type & DICT_FTS)
err= fts_lock_index_tables(ctx->trx, *index);
} if (err == DB_SUCCESS)
err= lock_sys_tables(ctx->trx);
row_mysql_lock_data_dictionary(ctx->trx); /* Detach ctx->new_table from dict_index_t::online_log. */
innobase_online_rebuild_log_free(ctx->old_table);
ut_d(constbool last_handle=) ctx->new_table->release();
ut_ad(last_handle); if (err == DB_SUCCESS)
err= ctx->trx->drop_table(*ctx->new_table);
if (err == DB_SUCCESS) for (const dict_index_t* index= ctx->new_table->indexes.start; index;
index= index->indexes.next) if (index->type & DICT_FTS) if (dberr_t err2= fts_drop_index_tables(ctx->trx, *index))
err= err2;
/* Remove the fts table from fts_optimize_wq if there are
no FTS secondary index exist other than newly added one */ if (fts_index &&
(ib_vector_is_empty(prebuilt->table->fts->indexes) ||
(ib_vector_size(prebuilt->table->fts->indexes) == 1 &&
fts_index == static_cast<dict_index_t*>(
ib_vector_getp(prebuilt->table->fts->indexes, 0)))))
fts_optimize_remove_table(prebuilt->table);
if (ctx->add_vcol)
{ for (ulint i = 0; i < ctx->num_to_add_vcol; i++)
ctx->add_vcol[i].~dict_v_col_t();
ctx->num_to_add_vcol= 0;
ctx->add_vcol= nullptr;
}
for (ulint i= 0; i < ctx->num_to_add_fk; i++)
dict_foreign_free(ctx->add_fk[i]); /* Clear the to_be_dropped flags in the data dictionary cache. Theflagsmayalreadyhavebeencleared,incaseanerrorwas
detected in commit_inplace_alter_table(). */ for (ulint i= 0; i < ctx->num_to_drop_index; i++)
{
dict_index_t *index= ctx->drop_index[i];
DBUG_ASSERT(index->is_committed());
index->to_be_dropped= 0;
}
}
/* Drop the constraint from the data dictionary. */ staticconstchar sql[] = "PROCEDURE DROP_FOREIGN_PROC () IS\n" "BEGIN\n" "DELETE FROM SYS_FOREIGN WHERE ID=:id;\n" "DELETE FROM SYS_FOREIGN_COLS WHERE ID=:id;\n" "END;\n";
dberr_t error;
pars_info_t* info;
info = pars_info_create();
pars_info_add_str_literal(info, "id", foreign_id);
/* For secondary indexes, the wide_formatcheckcanbe'polluted' byPRIMARYKEYcolumnprefixordescending field.Tryalsothesimplerencoding
of SYS_FIELDS.POS. */
info = pars_info_create();
for (unsigned i = 0; i < foreign->n_fields; i++) { if (!Lex_ident_column(
Lex_cstring_strlen(foreign->foreign_col_names[i])).
streq(Lex_cstring_strlen(from))) { continue;
}
/* Ignore the foreign key rename if fk info
is being dropped. */ if (innobase_dropping_foreign(
foreign, ctx.drop_fk,
ctx.num_to_drop_fk)) { continue;
}
for (unsigned i = 0; i < foreign->n_fields; i++) { if (!Lex_ident_column(
Lex_cstring_strlen(foreign->referenced_col_names[i])).
streq(Lex_cstring_strlen(from))) { continue;
}
if (foreign_modified) {
fk_evict.insert(foreign);
}
}
/* Reload the foreign key info for instant table too. */ if (ctx.need_rebuild() || ctx.is_instant()) {
std::for_each(fk_evict.begin(), fk_evict.end(),
dict_foreign_remove_from_cache);
}
#ifdef UNIV_DEBUG
ut_ad(col->mbminlen <= col->mbmaxlen); switch (mtype) { case DATA_MYSQL: if (!(prtype & DATA_BINARY_TYPE) || user_table->not_redundant()
|| col->mbminlen != col->mbmaxlen) { /* NOTE: we could allow this when !(prtype & DATA_BINARY_TYPE)andROW_FORMATisnotREDUNDANTand mbminlen<mbmaxlen.Thatis,wetreataUTF-8CHAR(n)
column somewhat like a VARCHAR. */ break;
} /* fall through */ case DATA_FIXBINARY: case DATA_CHAR:
ut_ad(col->len == len); break; case DATA_BINARY: case DATA_VARCHAR: case DATA_VARMYSQL: case DATA_DECIMAL: case DATA_BLOB: break; default:
ut_ad(!((col->prtype ^ prtype) & ~DATA_VERSIONED));
ut_ad(col->mtype == mtype);
ut_ad(col->len == len);
} #endif/* UNIV_DEBUG */
/** Set the auto-increment value of the table on commit. @paramha_alter_infoDatausedduringin-placealter @paramctxIn-placeALTERTABLEcontext @paramaltered_tableMySQLtablethatisbeingaltered @paramold_tableMySQLtableasitisbeforetheALTERoperation
@return whether the operation failed (and my_error() was called) */ static MY_ATTRIBUTE((nonnull)) bool
commit_set_autoinc(
Alter_inplace_info* ha_alter_info,
ha_innobase_inplace_ctx*ctx, const TABLE* altered_table, const TABLE* old_table)
{
DBUG_ENTER("commit_set_autoinc");
if (!altered_table->found_next_number_field) { /* There is no AUTO_INCREMENT column in the table
after the ALTER operation. */
} elseif (ctx->add_autoinc != ULINT_UNDEFINED) {
ut_ad(ctx->need_rebuild()); /* An AUTO_INCREMENT column was added. Get the last valuefromthesequence,whichmaybebasedona
supplied AUTO_INCREMENT value. */
ib_uint64_t autoinc = ctx->sequence.last();
ctx->new_table->autoinc = autoinc; /* Bulk index creation does not update PAGE_ROOT_AUTO_INC,sowemustpersistthe"lastused"
value here. */
btr_write_autoinc(ctx->trx,
dict_table_get_first_index(ctx->new_table),
autoinc - 1, true);
} elseif ((ha_alter_info->handler_flags
& ALTER_CHANGE_CREATE_OPTION)
&& (ha_alter_info->create_info->used_fields
& HA_CREATE_USED_AUTO)) {
if (!ctx->old_table->space) {
my_error(ER_TABLESPACE_DISCARDED, MYF(0),
old_table->s->table_name.str);
DBUG_RETURN(true);
}
/* An AUTO_INCREMENT value was supplied by the user.
It must be persisted to the data file. */ const Field* ai = old_table->found_next_number_field;
ut_ad(!strcmp(dict_table_get_col_name(ctx->old_table,
innodb_col_no(ai)).str,
ai->field_name.str));
if (autoinc >= ctx->old_table->autoinc) { /* Persist the predecessor of the
AUTO_INCREMENT value as the last used one. */
ctx->new_table->autoinc = autoinc--;
} else { /* Mimic ALGORITHM=COPY in the following scenario:
Wecouldonlysearchthetreetoknowcurrent
max counter in the table and compare. */ const dict_col_t* autoinc_col
= dict_table_get_nth_col(ctx->old_table,
innodb_col_no(ai)); auto index = ctx->old_table->get_index(*autoinc_col);
ut_ad(index);
ib_uint64_t max_in_table = index
? row_search_max_autoinc(index)
: 0;
if (autoinc <= max_in_table) {
ctx->new_table->autoinc = innobase_next_autoinc(
max_in_table, 1,
ctx->prebuilt->autoinc_increment,
ctx->prebuilt->autoinc_offset,
innobase_get_int_col_max_value(ai)); /* Persist the maximum value as the
last used one. */
autoinc = max_in_table;
} else { /* Persist the predecessor of the
AUTO_INCREMENT value as the last used one. */
ctx->new_table->autoinc = autoinc--;
}
}
btr_write_autoinc(ctx->trx,
dict_table_get_first_index(ctx->new_table),
autoinc, true);
} elseif (ctx->need_rebuild()) { /* No AUTO_INCREMENT value was specified.
Copy it from the old table. */
ctx->new_table->autoinc = ctx->old_table->autoinc; /* The persistent value was already copied in prepare_inplace_alter_table_dict()whenctx->new_table wascreated.IfthiswasaLOCK=NONEoperation,the AUTO_INCREMENTvalueswouldbeupdatedduring row_log_table_apply().IfthiswasLOCK!=NONE, thetablecontentscouldnotpossiblyhavechanged
between prepare_inplace and commit_inplace. */
}
DBUG_RETURN(false);
}
/** Add or drop foreign key constraints to the data dictionary tables, butdonottouchthedatadictionarycache. @paramha_alter_infoDatausedduringin-placealter @paramctxIn-placeALTERTABLEcontext @paramtrxDatadictionarytransaction @paramtable_nameTablenameinMySQL @retvaltrueFailure @retvalfalseSuccess
*/ static MY_ATTRIBUTE((nonnull, warn_unused_result)) bool
innobase_update_foreign_try( /*========================*/
ha_innobase_inplace_ctx*ctx,
trx_t* trx, constchar* table_name)
{
ulint i;
for (i = 0; i < ctx->num_to_drop_fk; i++) {
dict_foreign_t* fk = ctx->drop_fk[i];
DBUG_ASSERT(fk->foreign_table == ctx->old_table);
if (innobase_drop_foreign_try(trx, table_name, fk->id)) {
DBUG_RETURN(true);
}
}
DBUG_RETURN(false);
}
/** Update the foreign key constraint definitions in the data dictionary cache afterthechangestodatadictionarytableswerecommitted. @paramctxIn-placeALTERTABLEcontext @paramuser_thdMySQLconnection
@return InnoDB error code (should always be DB_SUCCESS) */ static MY_ATTRIBUTE((nonnull, warn_unused_result))
dberr_t
innobase_update_foreign_cache( /*==========================*/
ha_innobase_inplace_ctx* ctx,
THD* user_thd)
{
dict_table_t* user_table;
dberr_t err = DB_SUCCESS;
DBUG_ENTER("innobase_update_foreign_cache");
ut_ad(dict_sys.locked());
user_table = ctx->old_table;
/* Discard the added foreign keys, because we will
load them from the data dictionary. */ for (ulint i = 0; i < ctx->num_to_add_fk; i++) {
dict_foreign_t* fk = ctx->add_fk[i];
dict_foreign_free(fk);
}
if (ctx->need_rebuild()) { /* The rebuilt table is already using the renamed columnnames.Noneedtopasscol_namesortodrop
constraints from the data dictionary cache. */
DBUG_ASSERT(!ctx->col_names);
user_table = ctx->new_table;
} else { /* Drop the foreign key constraints if the tablewasnotrebuilt.Ifthetableisrebuilt, therewouldnotbeanyforeignkeyconstraintsfor
it yet in the data dictionary cache. */ for (ulint i = 0; i < ctx->num_to_drop_fk; i++) {
dict_foreign_t* fk = ctx->drop_fk[i];
dict_foreign_remove_from_cache(fk);
}
}
/* Load the old or added foreign keys from the data dictionary andpreventthetablefrombeingevictedfromthedata
dictionary cache (work around the lack of WL#6049). */
dict_names_t fk_tables;
mtr_t mtr{ctx->trx};
if (err == DB_CANNOT_ADD_CONSTRAINT) {
fk_tables.clear();
/* It is possible there are existing foreign key are loadedwith"foreign_keychecks"off,
so let's retry the loading with charset_check is off */
err = dict_load_foreigns(mtr, user_table->name.m_name,
ctx->col_names, 1, false,
DICT_ERR_IGNORE_NONE,
fk_tables);
/* The load with "charset_check" off is successful, warn theuserthattheforeignkeyhasloadedwithmis-matched
charset */ if (err == DB_SUCCESS) {
push_warning_printf(
user_thd,
Sql_condition::WARN_LEVEL_WARN,
ER_ALTER_INFO, "Foreign key constraints for table '%s'" " are loaded with charset check off",
user_table->name.m_name);
}
}
/* For complete loading of foreign keys, all associated tables must
also be loaded. */ while (err == DB_SUCCESS && !fk_tables.empty()) { constchar *f = fk_tables.front(); if (!dict_sys.load_table({f, strlen(f)})) {
err = DB_TABLE_NOT_FOUND;
ib::error()
<< "Failed to load table "
<< table_name_t(const_cast<char*>(f))
<< " which has a foreign key constraint with"
<< user_table->name; break;
}
if (vers_change_field_try(trx, table->s->table_name.str,
new_table->id, pos,
new_prtype)) {
DBUG_RETURN(true);
}
}
DBUG_RETURN(false);
}
/** Changes WITH/WITHOUT SYSTEM VERSIONING for fields inthedatadictionarycache. @paramha_alter_infoDatausedduringin-placealter @paramctxIn-placeALTERTABLEcontext
@param table MySQL table as it is before the ALTER operation */ static void
vers_change_fields_cache(
Alter_inplace_info* ha_alter_info, const ha_innobase_inplace_ctx* ctx, const TABLE* table)
{
DBUG_ENTER("vers_change_fields_cache");
for (dict_index_t* index = dict_table_get_first_index(rebuilt_table);
index;
index = dict_table_get_next_index(index)) {
DBUG_ASSERT(dict_index_get_online_status(index)
== ONLINE_INDEX_COMPLETE);
DBUG_ASSERT(index->is_committed()); if (index->is_corrupted()) {
my_error(ER_INDEX_CORRUPT, MYF(0), index->name());
DBUG_RETURN(true);
}
}
if (innobase_update_foreign_try(ctx, trx, table_name)) {
DBUG_RETURN(true);
}
/* Clear the to_be_dropped flag in the data dictionary cache
of user_table. */ for (ulint i = 0; i < ctx->num_to_drop_index; i++) {
dict_index_t* index = ctx->drop_index[i];
DBUG_ASSERT(index->table == user_table);
DBUG_ASSERT(index->is_committed());
DBUG_ASSERT(index->to_be_dropped);
index->to_be_dropped = 0;
}
/* The new table must inherit the flag from the
"parent" table. */ if (!user_table->space) {
rebuilt_table->file_unreadable = true; #ifdefined __GNUC__ && !defined __clang__ # pragma GCC diagnostic push # if __GNUC__ < 12 || defined WITH_UBSAN # pragma GCC diagnostic ignored "-Wconversion" # endif #endif
rebuilt_table->flags2 |= DICT_TF2_DISCARDED; #ifdefined __GNUC__ && !defined __clang__ # pragma GCC diagnostic pop #endif
}
/* We can now rename the old table as a temporary table, renamethenewtemporarytableastheoldtableanddropthe
old table. */ char* old_name= mem_heap_strdup(ctx->heap, user_table->name.m_name);
dberr_t error = row_rename_table_for_mysql(user_table->name.m_name,
ctx->tmp_name, trx,
RENAME_REBUILD); if (error == DB_SUCCESS) {
error = row_rename_table_for_mysql(
rebuilt_table->name.m_name, old_name, trx,
RENAME_REBUILD); if (error == DB_SUCCESS) { /* The statistics for the surviving indexes will be
re-inserted in alter_stats_rebuild(). */ if (statistics_exist) {
error = trx->drop_table_statistics(old_name);
} if (error == DB_SUCCESS) {
error = trx->drop_table(*user_table);
}
}
}
/* We must be still holding a table handle. */
DBUG_ASSERT(user_table->get_ref_count() == 1);
rebuilt_table->row_id = uint64_t{user_table->row_id};
DBUG_EXECUTE_IF("ib_rebuild_cannot_rename", error = DB_ERROR;);
/** Rename indexes in dictionary. @param[in]ctxalterinfocontext @param[in]ha_alter_infoOperationusedduringinplacealter @param[out]trxtransactiontochangetheindexname indictionary @returntrueifitfailedtorename
@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)) { returntrue;
}
}
returnfalse;
}
/** Set of column numbers */ typedef std::set<ulint, std::less<ulint>, ut_allocator<ulint> > col_set;
/* We altered the table in place. Mark the indexes as committed. */ for (ulint i = 0; i < ctx->num_to_add_index; i++) {
index = ctx->add_index[i];
DBUG_ASSERT(dict_index_get_online_status(index)
== ONLINE_INDEX_COMPLETE);
DBUG_ASSERT(!index->is_committed());
error = row_merge_rename_index_to_add(
trx, ctx->new_table->id, index->id); if (error) { goto handle_error;
}
}
for (dict_index_t *index = UT_LIST_GET_FIRST(ctx->old_table->indexes);
index; index = UT_LIST_GET_NEXT(indexes, index)) { if (index->type & DICT_FTS) {
num_fts_index++;
}
}
for (ulint i = 0; i < ctx->num_to_drop_index; i++) {
index = ctx->drop_index[i];
DBUG_ASSERT(index->is_committed());
DBUG_ASSERT(index->table == ctx->new_table);
DBUG_ASSERT(index->to_be_dropped);
op = "DROP INDEX";
staticconstchar drop_index[] = "PROCEDURE DROP_INDEX_PROC () IS\n" "BEGIN\n" "DELETE FROM SYS_FIELDS WHERE INDEX_ID=:indexid;\n" "DELETE FROM SYS_INDEXES WHERE ID=:indexid;\n" "END;\n";
if (update) { /* Maybe we should introduce an undo logrecordforupdatingtablespace flags,andperformtheupdatealready ininnobase_page_compression_try().
Iftheserveriskilledbeforethe followingmini-transactioncommit becomesdurable,fsp_flags_try_adjust() willperformtheequivalentadjustment
and warn "adjusting FSP_SPACE_FLAGS". */
mtr_t mtr{trx};
mtr.start(); if (buf_block_t* b = buf_page_get(
page_id_t(space->id, 0),
space->zip_size(),
RW_X_LATCH, &mtr)) {
byte* f = FSP_HEADER_OFFSET
+ FSP_SPACE_FLAGS
+ b->page.frame; constauto sf = space->flags
& ~FSP_FLAGS_MEM_MASK; if (mach_read_from_4(f) != sf) {
mtr.set_named_space(space);
mtr.write<4,mtr_t::FORCED>(
*b, f, sf);
}
}
mtr.commit();
}
}
}
col_set drop_list;
col_set v_drop_list;
/* Check if the column, part of an index to be dropped is part of any otherindexwhichisnotbeingdropped.Ifitso,thensettheord_part
of the column to 0. */
collect_columns_from_dropped_indexes(ctx, drop_list, v_drop_list);
for (ulint col : drop_list) { if (!check_col_exists_in_indexes(ctx->new_table, col, false)) {
ctx->new_table->cols[col].ord_part = 0;
}
}
for (ulint col : v_drop_list) { if (!check_col_exists_in_indexes(ctx->new_table, col, true)) {
ctx->new_table->v_cols[col].m_col.ord_part = 0;
}
}
for (ulint i = 0; i < ctx->num_to_add_index; i++) {
dict_index_t* index = ctx->add_index[i];
DBUG_ASSERT(dict_index_get_online_status(index)
== ONLINE_INDEX_COMPLETE);
DBUG_ASSERT(!index->is_committed());
index->change_col_info = nullptr;
index->set_committed(true);
}
for (ulint i = 0; i < ctx->num_to_drop_index; i++) {
dict_index_t* index = ctx->drop_index[i];
DBUG_ASSERT(index->is_committed());
DBUG_ASSERT(index->table == ctx->new_table);
DBUG_ASSERT(index->to_be_dropped);
if (!dict_foreign_replace_index(index->table, ctx->col_names,
index)) {
found = false;
}
/** Adjust the persistent statistics after non-rebuilding ALTER TABLE. Removestatisticsfordroppedindexes,addstatisticsforcreatedindexes andrenamestatisticsforrenamedindexes. @paramha_alter_infoDatausedduringin-placealter @paramctxIn-placeALTERTABLEcontext @paramtrxusertransaction
*/ static void
alter_stats_norebuild( /*==================*/
Alter_inplace_info* ha_alter_info,
ha_innobase_inplace_ctx* ctx,
trx_t* trx)
{
DBUG_ENTER("alter_stats_norebuild");
DBUG_ASSERT(!ctx->need_rebuild());
auto stat = ctx->new_table->stat;
if (!dict_table_t::stat_initialized(stat)
|| !dict_table_t::stats_is_persistent(stat)) {
DBUG_VOID_RETURN;
}
for (ulint i = 0; i < ctx->num_to_add_index; i++) {
dict_index_t* index = ctx->add_index[i];
DBUG_ASSERT(index->table == ctx->new_table);
if (!(index->type & DICT_FTS)) {
dict_stats_update_for_index(trx, index);
}
}
DBUG_VOID_RETURN;
}
/** Apply the log for the table rebuild operation. @param[in]ctxInplaceAltertablecontext @param[in]altered_tableMySQLtablethatisbeingaltered
@return true Failure, else false. */ staticbool alter_rebuild_apply_log(
ha_innobase_inplace_ctx* ctx,
Alter_inplace_info* ha_alter_info,
TABLE* altered_table)
{
DBUG_ENTER("alter_rebuild_apply_log");
if (!ctx->online) {
DBUG_RETURN(false);
}
/* We copied the table. Any indexes that were requested to be droppedwerenotcreatedinthecopyofthetable.Applyany
last bit of the rebuild log and then rename the tables. */
dict_table_t* user_table = ctx->old_table;
if (!commit) { /* A rollback is being requested. So far we may at mosthavecreatedstubsforADDINDEXoracopyofthe
table for rebuild. */ #if0/* FIXME: is there a better way for innodb.innodb-index-online? */
lock_shared_ha_data(); auto share = static_cast<InnoDB_share*>(get_ha_share_ptr());
set_ha_share_ptr(nullptr);
unlock_shared_ha_data(); delete share; #endif
DBUG_RETURN(rollback_inplace_alter_table(
ha_alter_info, table, m_prebuilt));
}
if (!(ha_alter_info->handler_flags & ~INNOBASE_INPLACE_IGNORE)) {
DBUG_ASSERT(!ctx0);
MONITOR_ATOMIC_DEC(MONITOR_PENDING_ALTER_TABLE); if (table->found_next_number_field
&& !altered_table->found_next_number_field) {
m_prebuilt->table->persistent_autoinc = 0; /* Don't reset ha_alter_info->group_commit_ctx to make partitionsenginetocallthisfunctionforall
partitions. */
} else
ha_alter_info->group_commit_ctx = NULL;
DBUG_RETURN(false);
}
bool already_stopped= false; for (inplace_alter_handler_ctx** pctx = ctx_array; *pctx; pctx++) { auto ctx = static_cast<ha_innobase_inplace_ctx*>(*pctx);
dberr_t error = DB_SUCCESS;
if (fts_exist) {
purge_sys.stop_FTS(*ctx->old_table, already_stopped);
already_stopped = true;
}
if (new_clustered && ctx->old_table->fts) {
ut_ad(!ctx->old_table->fts->add_wq);
fts_optimize_remove_table(ctx->old_table);
}
error = lock_table_children(ctx->old_table, trx);
if (ctx->new_table->fts) {
ut_ad(!ctx->new_table->fts->add_wq);
fts_optimize_remove_table(ctx->new_table);
fts_sync_during_ddl(ctx->new_table, trx->mysql_thd);
}
/* Exclusively lock the table, to ensure that no other transactionisholdinglocksonthetablewhilewe changethetabledefinition.Anyrecoveredincomplete
transactions would be holding InnoDB locks only, not MDL. */ if (error == DB_SUCCESS) {
error = lock_table_for_trx(ctx->new_table, trx,
LOCK_X);
}
if (error != DB_SUCCESS) {
lock_fail:
my_error_innodb(
error, table_share->table_name.str, 0); if (fts_exist) {
purge_sys.resume_FTS();
}
/* Deadlock encountered and rollbacked the transaction.Sorestartthetransaction toremovethenewlycreatedtableor indexfromdatadictionaryandtablecache
in rollback_inplace_alter_table() */ if (!trx->is_started()) {
trx_start_for_ddl(trx);
}
if (new_clustered) { /* We are holding MDL_EXCLUSIVE as well as exclusive InnoDBtablelocks.Letusapplyanytablerebuildlog
before locking dict_sys. */ for (inplace_alter_handler_ctx** pctx= ctx_array; *pctx;
pctx++) { auto ctx= static_cast<ha_innobase_inplace_ctx*>(*pctx);
DBUG_ASSERT(ctx->need_rebuild()); if (alter_rebuild_apply_log(ctx, ha_alter_info,
altered_table)) { if (fts_exist) {
purge_sys.resume_FTS();
}
DBUG_RETURN(true);
}
}
} else {
dberr_t error= DB_SUCCESS; for (inplace_alter_handler_ctx** pctx= ctx_array; *pctx;
pctx++) { auto ctx= static_cast<ha_innobase_inplace_ctx*>(*pctx);
if (!ctx->online || !ctx->old_table->space
|| !ctx->old_table->is_readable()) { continue;
}
for (ulint i = 0; i < ctx->num_to_add_index; i++) {
dict_index_t *index= ctx->add_index[i];
if (error == DB_SUCCESS) {
error = lock_sys_tables(trx);
} if (error != DB_SUCCESS) { if (!stats_failed) {
stats.close();
} if (fts_exist) {
purge_sys.resume_FTS();
}
if (!trx->is_started()) { /* Transaction may have been rolled back duetoalockwaittimeout,deadlock, oraKILLstatement.Sorestartthe transactiontoremovethenewlycreated tableorindexstubsfromdatadictionary andtablecachein
rollback_inplace_alter_table() */
trx_start_for_ddl(trx);
}
/* Apply the changes to the data dictionary tables, for all
partitions. */ for (inplace_alter_handler_ctx** pctx = ctx_array; *pctx; pctx++) { auto ctx = static_cast<ha_innobase_inplace_ctx*>(*pctx);
DBUG_ASSERT(new_clustered == ctx->need_rebuild()); if (ctx->need_rebuild() && !ctx->old_table->space) {
my_error(ER_TABLESPACE_DISCARDED, MYF(0),
table->s->table_name.str);
fail:
trx->rollback();
ut_ad(!trx->fts_trx);
row_mysql_unlock_data_dictionary(trx); if (!stats_failed) {
stats.close();
} if (fts_exist) {
purge_sys.resume_FTS();
}
trx_start_for_ddl(trx);
DBUG_RETURN(true);
}
if (commit_set_autoinc(ha_alter_info, ctx,
altered_table, table)) { goto fail;
}
if (ctx->need_rebuild()) {
ctx->tmp_name = dict_mem_create_temporary_tablename(
ctx->heap, ctx->new_table->name.m_name,
ctx->new_table->id);
std::vector<pfs_os_file_t> deleted;
DBUG_EXECUTE_IF("innodb_alter_commit_crash_before_commit",
log_buffer_flush_to_disk(); DBUG_SUICIDE();); /* The SQL layer recovery of ALTER TABLE will invoke innodb_check_version()toknowwhetherourtrx->id,whichwe reportedviaha_innobase::table_version()after ha_innobase::prepare_inplace_alter_table(),wascommitted.
Duringrecovery,thepurgeofInnoDBtransactionhistorywill
not start until innodb_ddl_recovery_done(). */
ha_alter_info->inplace_alter_table_committed = purge_sys.resume_SYS;
purge_sys.stop_SYS();
trx->commit(deleted);
/* At this point, the changes to the persistent storage have beencommittedorrolledback.Whatremainstobedoneisto updatethein-memorystructures,closesomehandles,release temporaryfiles,and(unlesswerolledback)updatepersistent
statistics. */ for (inplace_alter_handler_ctx** pctx = ctx_array;
*pctx; pctx++) {
ha_innobase_inplace_ctx* ctx
= static_cast<ha_innobase_inplace_ctx*>(*pctx);
unlock_and_close_files(deleted, trx);
DBUG_EXECUTE_IF("innodb_alter_commit_crash_after_commit",
DBUG_SUICIDE(););
trx->free(); if (!stats_failed) {
stats.close();
} if (fts_exist) {
purge_sys.resume_FTS();
}
MONITOR_ATOMIC_DEC(MONITOR_PENDING_ALTER_TABLE); /* There is no need to reset dict_table_t::persistent_autoinc
as the table is reloaded */
DBUG_RETURN(false);
}
/* Publish the created fulltext index, if any. Notethatafulltextindexcanbecreatedwithout creatingtheclusteredindex,iftherealreadyexists asuitableFTS_DOC_IDcolumn.Ifnot,onewillbe
created, implying new_clustered */ for (ulint i = 0; i < ctx->num_to_add_index; i++) {
dict_index_t* index = ctx->add_index[i];
if (index->type & DICT_FTS) {
DBUG_ASSERT(index->type == DICT_FTS); /* We reset DICT_TF2_FTS here because the bit
is left unset when a drop proceeds the add. */
DICT_TF2_FLAG_SET(ctx->new_table, DICT_TF2_FTS);
fts_add_index(index, ctx->new_table);
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.