static MY_ATTRIBUTE()

in storage/innobase/handler/handler0alter.cc [2558:3197]


static MY_ATTRIBUTE((warn_unused_result, nonnull(1,2,3,4)))
bool
prepare_inplace_alter_table_dict(
/*=============================*/
	Alter_inplace_info*	ha_alter_info,
	const TABLE*		altered_table,
	const TABLE*		old_table,
	const char*		table_name,
	ulint			flags,
	ulint			flags2,
	ulint			fts_doc_id_col,
	bool			add_fts_doc_id,
	bool			add_fts_doc_id_idx)
{
	bool			dict_locked	= false;
	ulint*			add_key_nums;	/* MySQL key numbers */
	index_def_t*		index_defs;	/* index definitions */
	dict_table_t*		user_table;
	dict_index_t*		fts_index	= NULL;
	ulint			new_clustered	= 0;
	dberr_t			error;
	ulint			num_fts_index;
	ha_innobase_inplace_ctx*ctx;

	DBUG_ENTER("prepare_inplace_alter_table_dict");

	ctx = static_cast<ha_innobase_inplace_ctx*>
		(ha_alter_info->handler_ctx);

	DBUG_ASSERT((ctx->add_autoinc != ULINT_UNDEFINED)
		    == (ctx->sequence.m_max_value > 0));
	DBUG_ASSERT(!ctx->num_to_drop_index == !ctx->drop_index);
	DBUG_ASSERT(!ctx->num_to_drop_fk == !ctx->drop_fk);
	DBUG_ASSERT(!add_fts_doc_id || add_fts_doc_id_idx);
	DBUG_ASSERT(!add_fts_doc_id_idx
		    || innobase_fulltext_exist(altered_table));
	DBUG_ASSERT(!ctx->add_cols);
	DBUG_ASSERT(!ctx->add_index);
	DBUG_ASSERT(!ctx->add_key_numbers);
	DBUG_ASSERT(!ctx->num_to_add_index);

	user_table = ctx->new_table;

	trx_start_if_not_started_xa(ctx->prebuilt->trx);

	/* Create a background transaction for the operations on
	the data dictionary tables. */
	ctx->trx = innobase_trx_allocate(ctx->prebuilt->trx->mysql_thd);

	trx_start_for_ddl(ctx->trx, TRX_DICT_OP_INDEX);

	/* Create table containing all indexes to be built in this
	ALTER TABLE ADD INDEX so that they are in the correct order
	in the table. */

	ctx->num_to_add_index = ha_alter_info->index_add_count;

	ut_ad(ctx->prebuilt->trx->mysql_thd != NULL);
	const char*	path = thd_innodb_tmpdir(
		ctx->prebuilt->trx->mysql_thd);

	index_defs = innobase_create_key_defs(
		ctx->heap, ha_alter_info, altered_table, ctx->num_to_add_index,
		num_fts_index,
		row_table_got_default_clust_index(ctx->new_table),
		fts_doc_id_col, add_fts_doc_id, add_fts_doc_id_idx);

	new_clustered = DICT_CLUSTERED & index_defs[0].ind_type;

	if (num_fts_index > 1) {
		my_error(ER_INNODB_FT_LIMIT, MYF(0));
		goto error_handled;
	}

	if (!ctx->online) {
		/* This is not an online operation (LOCK=NONE). */
	} else if (ctx->add_autoinc == ULINT_UNDEFINED
		   && num_fts_index == 0
		   && (!innobase_need_rebuild(ha_alter_info)
		       || !innobase_fulltext_exist(altered_table))) {
		/* InnoDB can perform an online operation (LOCK=NONE). */
	} else {
		/* This should have been blocked in
		check_if_supported_inplace_alter(). */
		ut_ad(0);
		my_error(ER_NOT_SUPPORTED_YET, MYF(0),
			 thd_query_string(ctx->prebuilt->trx->mysql_thd)->str);
		goto error_handled;
	}

	/* The primary index would be rebuilt if a FTS Doc ID
	column is to be added, and the primary index definition
	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)
		     || add_fts_doc_id));

	/* Allocate memory for dictionary index definitions */

	ctx->add_index = static_cast<dict_index_t**>(
		mem_heap_alloc(ctx->heap, ctx->num_to_add_index
			       * sizeof *ctx->add_index));
	ctx->add_key_numbers = add_key_nums = static_cast<ulint*>(
		mem_heap_alloc(ctx->heap, ctx->num_to_add_index
			       * sizeof *ctx->add_key_numbers));

	/* This transaction should be dictionary operation, so that
	the data dictionary will be locked during crash recovery. */

	ut_ad(ctx->trx->dict_operation == TRX_DICT_OP_INDEX);

	/* Acquire a lock on the table before creating any indexes. */

	if (ctx->online) {
		error = DB_SUCCESS;
	} else {
		error = row_merge_lock_table(
			ctx->prebuilt->trx, ctx->new_table, LOCK_S);

		if (error != DB_SUCCESS) {

			goto error_handling;
		}
	}

	/* Latch the InnoDB data dictionary exclusively so that no deadlocks
	or lock waits can happen in it during an index create operation. */

	row_mysql_lock_data_dictionary(ctx->trx);
	dict_locked = true;

	/* Wait for background stats processing to stop using the table that
	we are going to alter. We know bg stats will not start using it again
	until we are holding the data dict locked and we are holding it here
	at least until checking ut_ad(user_table->n_ref_count == 1) below.
	XXX what may happen if bg stats opens the table after we
	have unlocked data dictionary below? */
	dict_stats_wait_bg_to_stop_using_table(user_table, ctx->trx);

	online_retry_drop_indexes_low(ctx->new_table, ctx->trx);

	ut_d(dict_table_check_for_dup_indexes(
		     ctx->new_table, CHECK_ABORTED_OK));

	/* If a new clustered index is defined for the table we need
	to rebuild the table with a temporary name. */

	if (new_clustered) {
		const char*	new_table_name
			= dict_mem_create_temporary_tablename(
				ctx->heap,
				ctx->new_table->name,
				ctx->new_table->id);
		ulint		n_cols;
		dtuple_t*	add_cols;

		if (innobase_check_foreigns(
			    ha_alter_info, altered_table, old_table,
			    user_table, ctx->drop_fk, ctx->num_to_drop_fk)) {
			goto new_clustered_failed;
		}

		n_cols = altered_table->s->fields;

		if (add_fts_doc_id) {
			n_cols++;
			DBUG_ASSERT(flags2 & DICT_TF2_FTS);
			DBUG_ASSERT(add_fts_doc_id_idx);
			flags2 |= DICT_TF2_FTS_ADD_DOC_ID
				| DICT_TF2_FTS_HAS_DOC_ID
				| DICT_TF2_FTS;
		}

		DBUG_ASSERT(!add_fts_doc_id_idx || (flags2 & DICT_TF2_FTS));

		/* Create the table. */
		trx_set_dict_operation(ctx->trx, TRX_DICT_OP_TABLE);

		if (dict_table_get_low(new_table_name)) {
			my_error(ER_TABLE_EXISTS_ERROR, MYF(0),
				 new_table_name);
			goto new_clustered_failed;
		}

		/* The initial space id 0 may be overridden later. */
		ctx->new_table = dict_mem_table_create(
			new_table_name, 0, n_cols, flags, flags2);
		/* The rebuilt indexed_table will use the renamed
		column names. */
		ctx->col_names = NULL;

		if (DICT_TF_HAS_DATA_DIR(flags)) {
			ctx->new_table->data_dir_path =
				mem_heap_strdup(ctx->new_table->heap,
				user_table->data_dir_path);
		}

		for (uint i = 0; i < altered_table->s->fields; i++) {
			const Field*	field = altered_table->field[i];
			ulint		is_unsigned;
			ulint		field_type
				= (ulint) field->type();
			ulint		col_type
				= get_innobase_type_from_mysql_type(
					&is_unsigned, field);
			ulint		charset_no;
			ulint		col_len;

			/* we assume in dtype_form_prtype() that this
			fits in two bytes */
			ut_a(field_type <= MAX_CHAR_COLL_NUM);

			if (!field->real_maybe_null()) {
				field_type |= DATA_NOT_NULL;
			}

			if (field->binary()) {
				field_type |= DATA_BINARY_TYPE;
			}

			if (is_unsigned) {
				field_type |= DATA_UNSIGNED;
			}

			if (dtype_is_string_type(col_type)) {
				charset_no = (ulint) field->charset()->number;

				if (charset_no > MAX_CHAR_COLL_NUM) {
					dict_mem_table_free(
						ctx->new_table);
					my_error(ER_WRONG_KEY_COLUMN, MYF(0),
						 field->field_name);
					goto new_clustered_failed;
				}
			} else {
				charset_no = 0;
			}

			col_len = field->pack_length();

			/* The MySQL pack length contains 1 or 2 bytes
			length field for a true VARCHAR. Let us
			subtract that, so that the InnoDB column
			length in the InnoDB data dictionary is the
			real maximum byte length of the actual data. */

			if (field->type() == MYSQL_TYPE_VARCHAR) {
				uint32	length_bytes
					= static_cast<const Field_varstring*>(
						field)->length_bytes;

				col_len -= length_bytes;

				if (length_bytes == 2) {
					field_type |= DATA_LONG_TRUE_VARCHAR;
				}
			}

			if (dict_col_name_is_reserved(field->field_name)) {
				dict_mem_table_free(ctx->new_table);
				my_error(ER_WRONG_COLUMN_NAME, MYF(0),
					 field->field_name);
				goto new_clustered_failed;
			}

			dict_mem_table_add_col(
				ctx->new_table, ctx->heap,
				field->field_name,
				col_type,
				dtype_form_prtype(field_type, charset_no),
				col_len);
		}

		if (add_fts_doc_id) {
			fts_add_doc_id_column(ctx->new_table, ctx->heap);
			ctx->new_table->fts->doc_col = fts_doc_id_col;
			ut_ad(fts_doc_id_col == altered_table->s->fields);
		} else if (ctx->new_table->fts) {
			ctx->new_table->fts->doc_col = fts_doc_id_col;
		}

		error = row_create_table_for_mysql(
			ctx->new_table, ctx->trx, false);

		switch (error) {
			dict_table_t*	temp_table;
		case DB_SUCCESS:
			/* We need to bump up the table ref count and
			before we can use it we need to open the
			table. The new_table must be in the data
			dictionary cache, because we are still holding
			the dict_sys->mutex. */
			ut_ad(mutex_own(&dict_sys->mutex));
			temp_table = dict_table_open_on_name(
				ctx->new_table->name, TRUE, FALSE,
				DICT_ERR_IGNORE_NONE);
			ut_a(ctx->new_table == temp_table);
			/* n_ref_count must be 1, because purge cannot
			be executing on this very table as we are
			holding dict_operation_lock X-latch. */
			DBUG_ASSERT(ctx->new_table->n_ref_count == 1);
			break;
		case DB_TABLESPACE_EXISTS:
			my_error(ER_TABLESPACE_EXISTS, MYF(0),
				 new_table_name);
			goto new_clustered_failed;
		case DB_DUPLICATE_KEY:
			my_error(HA_ERR_TABLE_EXIST, MYF(0),
				 altered_table->s->table_name.str);
			goto new_clustered_failed;
		default:
			my_error_innodb(error, old_table->s->db.str,
					table_name, flags);
		new_clustered_failed:
			DBUG_ASSERT(ctx->trx != ctx->prebuilt->trx);
			trx_rollback_to_savepoint(ctx->trx, NULL);

			ut_ad(user_table->n_ref_count == 1);

			online_retry_drop_indexes_with_trx(
				user_table, ctx->trx);
			goto err_exit;
		}

		if (ha_alter_info->handler_flags
		    & Alter_inplace_info::ADD_COLUMN) {
			add_cols = dtuple_create(
				ctx->heap,
				dict_table_get_n_cols(ctx->new_table));

			dict_table_copy_types(add_cols, ctx->new_table);
		} else {
			add_cols = NULL;
		}

		ctx->col_map = innobase_build_col_map(
			ha_alter_info, altered_table, old_table,
			ctx->new_table, user_table,
			add_cols, ctx->heap);
		ctx->add_cols = add_cols;
	} else {
		DBUG_ASSERT(!innobase_need_rebuild(ha_alter_info));
		DBUG_ASSERT(old_table->s->primary_key
			    == altered_table->s->primary_key);

		if (!ctx->new_table->fts
		    && innobase_fulltext_exist(altered_table)) {
			ctx->new_table->fts = fts_create(
				ctx->new_table);
			ctx->new_table->fts->doc_col = fts_doc_id_col;
		}
	}

	/* Assign table_id, so that no table id of
	fts_create_index_tables() will be written to the undo logs. */
	DBUG_ASSERT(ctx->new_table->id != 0);
	ctx->trx->table_id = ctx->new_table->id;

	/* Create the indexes in SYS_INDEXES and load into dictionary. */

	for (ulint a = 0; a < ctx->num_to_add_index; a++) {

		ctx->add_index[a] = row_merge_create_index(
			ctx->trx, ctx->new_table,
			&index_defs[a]);

		add_key_nums[a] = index_defs[a].key_number;

		if (!ctx->add_index[a]) {
			error = ctx->trx->error_state;
			DBUG_ASSERT(error != DB_SUCCESS);
			goto error_handling;
		}

		if (ctx->add_index[a]->type & DICT_FTS) {
			DBUG_ASSERT(num_fts_index);
			DBUG_ASSERT(!fts_index);
			DBUG_ASSERT(ctx->add_index[a]->type == DICT_FTS);
			fts_index = ctx->add_index[a];
		}

		/* If only online ALTER TABLE operations have been
		requested, allocate a modification log. If the table
		will be locked anyway, the modification
		log is unnecessary. When rebuilding the table
		(new_clustered), we will allocate the log for the
		clustered index of the old table, later. */
		if (new_clustered
		    || !ctx->online
		    || user_table->ibd_file_missing
		    || dict_table_is_discarded(user_table)) {
			/* No need to allocate a modification log. */
			ut_ad(!ctx->add_index[a]->online_log);
		} else if (ctx->add_index[a]->type & DICT_FTS) {
			/* Fulltext indexes are not covered
			by a modification log. */
		} else {
			DBUG_EXECUTE_IF("innodb_OOM_prepare_inplace_alter",
					error = DB_OUT_OF_MEMORY;
					goto error_handling;);
			rw_lock_x_lock(&ctx->add_index[a]->lock);

			bool ok = row_log_allocate(ctx->add_index[a],
						   NULL, true, NULL,
						   NULL, path);
			rw_lock_x_unlock(&ctx->add_index[a]->lock);

			if (!ok) {
				error = DB_OUT_OF_MEMORY;
				goto error_handling;
			}
		}
	}

	ut_ad(new_clustered == ctx->need_rebuild());

	DBUG_EXECUTE_IF("innodb_OOM_prepare_inplace_alter",
			error = DB_OUT_OF_MEMORY;
			goto error_handling;);

	if (new_clustered && ctx->online) {
		/* Allocate a log for online table rebuild. */
		dict_index_t* clust_index = dict_table_get_first_index(
			user_table);

		rw_lock_x_lock(&clust_index->lock);
		bool ok = row_log_allocate(
			clust_index, ctx->new_table,
			!(ha_alter_info->handler_flags
			  & Alter_inplace_info::ADD_PK_INDEX),
			ctx->add_cols, ctx->col_map, path);
		rw_lock_x_unlock(&clust_index->lock);

		if (!ok) {
			error = DB_OUT_OF_MEMORY;
			goto error_handling;
		}
	}

	if (ctx->online) {
		/* Assign a consistent read view for
		row_merge_read_clustered_index(). */
		trx_assign_read_view(ctx->prebuilt->trx);
	}

	if (fts_index) {
		/* Ensure that the dictionary operation mode will
		not change while creating the auxiliary tables. */
		trx_dict_op_t	op = trx_get_dict_operation(ctx->trx);

#ifdef UNIV_DEBUG
		switch (op) {
		case TRX_DICT_OP_NONE:
			break;
		case TRX_DICT_OP_TABLE:
		case TRX_DICT_OP_INDEX:
			goto op_ok;
		}
		ut_error;
op_ok:
#endif /* UNIV_DEBUG */
		ut_ad(ctx->trx->dict_operation_lock_mode == RW_X_LATCH);
		ut_ad(mutex_own(&dict_sys->mutex));
#ifdef UNIV_SYNC_DEBUG
		ut_ad(rw_lock_own(&dict_operation_lock, RW_LOCK_EX));
#endif /* UNIV_SYNC_DEBUG */

		DICT_TF2_FLAG_SET(ctx->new_table, DICT_TF2_FTS);

		/* This function will commit the transaction and reset
		the trx_t::dict_operation flag on success. */

		error = fts_create_index_tables(ctx->trx, fts_index);

		DBUG_EXECUTE_IF("innodb_test_fail_after_fts_index_table",
				error = DB_LOCK_WAIT_TIMEOUT;
				goto error_handling;);

		if (error != DB_SUCCESS) {
			goto error_handling;
		}

		trx_start_for_ddl(ctx->trx, op);

		if (!ctx->new_table->fts
		    || ib_vector_size(ctx->new_table->fts->indexes) == 0) {
			error = fts_create_common_tables(
				ctx->trx, ctx->new_table,
				user_table->name, TRUE);

			DBUG_EXECUTE_IF(
				"innodb_test_fail_after_fts_common_table",
				error = DB_LOCK_WAIT_TIMEOUT;);

			if (error != DB_SUCCESS) {
				goto error_handling;
			}

			ctx->new_table->fts->fts_status
				|= TABLE_DICT_LOCKED;

			error = innobase_fts_load_stopword(
				ctx->new_table, ctx->trx,
				ctx->prebuilt->trx->mysql_thd)
				? DB_SUCCESS : DB_ERROR;
			ctx->new_table->fts->fts_status
				&= ~TABLE_DICT_LOCKED;

			if (error != DB_SUCCESS) {
				goto error_handling;
			}
		}

		ut_ad(trx_get_dict_operation(ctx->trx) == op);
	}

	DBUG_ASSERT(error == DB_SUCCESS);

	/* Commit the data dictionary transaction in order to release
	the table locks on the system tables.  This means that if
	MySQL crashes while creating a new primary key inside
	row_merge_build_indexes(), ctx->new_table will not be dropped
	by trx_rollback_active().  It will have to be recovered or
	dropped by the database administrator. */
	trx_commit_for_mysql(ctx->trx);

	row_mysql_unlock_data_dictionary(ctx->trx);
	dict_locked = false;

	ut_a(ctx->trx->lock.n_active_thrs == 0);

	DBUG_EXECUTE_IF("crash_innodb_add_index_after", DBUG_SUICIDE(););

error_handling:
	/* After an error, remove all those index definitions from the
	dictionary which were defined. */

	switch (error) {
	case DB_SUCCESS:
		ut_a(!dict_locked);

		ut_d(mutex_enter(&dict_sys->mutex));
		ut_d(dict_table_check_for_dup_indexes(
			     user_table, CHECK_PARTIAL_OK));
		ut_d(mutex_exit(&dict_sys->mutex));
		DBUG_RETURN(false);
	case DB_TABLESPACE_EXISTS:
		my_error(ER_TABLESPACE_EXISTS, MYF(0), "(unknown)");
		break;
	case DB_DUPLICATE_KEY:
		my_error(ER_DUP_KEY, MYF(0), "SYS_INDEXES");
		break;
	default:
		my_error_innodb(error, old_table->s->db.str,
				table_name, user_table->flags);
	}

error_handled:

	ctx->prebuilt->trx->error_info = NULL;
	ctx->trx->error_state = DB_SUCCESS;

	if (!dict_locked) {
		row_mysql_lock_data_dictionary(ctx->trx);
	}

	if (new_clustered) {
		if (ctx->need_rebuild()) {

			if (DICT_TF2_FLAG_IS_SET(
				    ctx->new_table, DICT_TF2_FTS)) {
				innobase_drop_fts_index_table(
					ctx->new_table, ctx->trx);
			}

			dict_table_close(ctx->new_table, TRUE, FALSE);

#if defined UNIV_DEBUG || defined UNIV_DDL_DEBUG
			/* Nobody should have initialized the stats of the
			newly created table yet. When this is the case, we
			know that it has not been added for background stats
			gathering. */
			ut_a(!ctx->new_table->stat_initialized);
#endif /* UNIV_DEBUG || UNIV_DDL_DEBUG */

			row_merge_drop_table(ctx->trx, ctx->new_table);

			/* Free the log for online table rebuild, if
			one was allocated. */

			dict_index_t* clust_index = dict_table_get_first_index(
				user_table);

			rw_lock_x_lock(&clust_index->lock);

			if (clust_index->online_log) {
				ut_ad(ctx->online);
				row_log_abort_sec(clust_index);
				clust_index->online_status
					= ONLINE_INDEX_COMPLETE;
			}

			rw_lock_x_unlock(&clust_index->lock);
		}

		trx_commit_for_mysql(ctx->trx);
		/* n_ref_count must be 1, because purge cannot
		be executing on this very table as we are
		holding dict_operation_lock X-latch. */
		DBUG_ASSERT(user_table->n_ref_count == 1 || ctx->online);

		online_retry_drop_indexes_with_trx(user_table, ctx->trx);
	} else {
		ut_ad(!ctx->need_rebuild());
		row_merge_drop_indexes(ctx->trx, user_table, TRUE);
		trx_commit_for_mysql(ctx->trx);
	}

	ut_d(dict_table_check_for_dup_indexes(user_table, CHECK_ALL_COMPLETE));
	ut_ad(!user_table->drop_aborted);

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]->name != TEMP_INDEX_PREFIX);
		DBUG_ASSERT(ctx->drop_index[i]->to_be_dropped);
		ctx->drop_index[i]->to_be_dropped = 0;
	}

	row_mysql_unlock_data_dictionary(ctx->trx);

	trx_free_for_mysql(ctx->trx);
	trx_commit_for_mysql(ctx->prebuilt->trx);

	delete ctx;
	ha_alter_info->handler_ctx = NULL;

	DBUG_RETURN(true);
}