summaryrefslogtreecommitdiff
path: root/storage/innobase/handler/handler0alter.cc
diff options
context:
space:
mode:
Diffstat (limited to 'storage/innobase/handler/handler0alter.cc')
-rw-r--r--storage/innobase/handler/handler0alter.cc1224
1 files changed, 1224 insertions, 0 deletions
diff --git a/storage/innobase/handler/handler0alter.cc b/storage/innobase/handler/handler0alter.cc
new file mode 100644
index 00000000000..a5008991400
--- /dev/null
+++ b/storage/innobase/handler/handler0alter.cc
@@ -0,0 +1,1224 @@
+/*****************************************************************************
+
+Copyright (c) 2005, 2009, Innobase Oy. All Rights Reserved.
+
+This program is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free Software
+Foundation; version 2 of the License.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+Place, Suite 330, Boston, MA 02111-1307 USA
+
+*****************************************************************************/
+
+/**************************************************//**
+@file handler/handler0alter.cc
+Smart ALTER TABLE
+*******************************************************/
+
+#include <mysql_priv.h>
+#include <mysqld_error.h>
+
+extern "C" {
+#include "log0log.h"
+#include "row0merge.h"
+#include "srv0srv.h"
+#include "trx0trx.h"
+#include "trx0roll.h"
+#include "ha_prototypes.h"
+#include "handler0alter.h"
+}
+
+#include "ha_innodb.h"
+
+/*************************************************************//**
+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++;
+ }
+
+ if (!(field->flags & UNSIGNED_FLAG)) {
+ ((byte*) dest)[len - 1] ^= 0x80;
+ }
+
+ break;
+
+ 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
+ length of the data to the first byte or the first
+ 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_BLOB:
+ /* Store a pointer to the BLOB buffer to dest: the BLOB was
+ already copied to the buffer in row_sel_store_mysql_rec */
+
+ row_mysql_store_blob_ref(dest, flen, data, len);
+ break;
+
+#ifdef UNIV_DEBUG
+ case DATA_MYSQL:
+ ut_ad(flen >= len);
+ ut_ad(col->mbmaxlen >= col->mbminlen);
+ ut_ad(col->mbmaxlen > col->mbminlen || flen == len);
+ memcpy(dest, data, len);
+ break;
+
+ default:
+ case DATA_SYS_CHILD:
+ case DATA_SYS:
+ /* These column types should never be shipped to MySQL. */
+ ut_ad(0);
+
+ case DATA_CHAR:
+ case DATA_FIXBINARY:
+ case DATA_FLOAT:
+ case DATA_DOUBLE:
+ case DATA_DECIMAL:
+ /* Above are the valid column types for MySQL data. */
+ ut_ad(flen == len);
+#else /* UNIV_DEBUG */
+ default:
+#endif /* UNIV_DEBUG */
+ memcpy(dest, data, len);
+ }
+}
+
+/*************************************************************//**
+Copies an InnoDB record to table->record[0]. */
+extern "C" UNIV_INTERN
+void
+innobase_rec_to_mysql(
+/*==================*/
+ TABLE* table, /*!< in/out: MySQL table */
+ const rec_t* rec, /*!< in: record */
+ const dict_index_t* index, /*!< in: index */
+ const ulint* offsets) /*!< in: rec_get_offsets(
+ rec, index, ...) */
+{
+ uint n_fields = table->s->fields;
+ uint i;
+
+ ut_ad(n_fields == dict_table_get_n_user_cols(index->table));
+
+ for (i = 0; i < n_fields; i++) {
+ Field* field = table->field[i];
+ ulint ipos;
+ ulint ilen;
+ const uchar* ifield;
+
+ field->reset();
+
+ ipos = dict_index_get_nth_col_pos(index, i);
+
+ if (UNIV_UNLIKELY(ipos == ULINT_UNDEFINED)) {
+null_field:
+ field->set_null();
+ continue;
+ }
+
+ ifield = rec_get_nth_field(rec, offsets, ipos, &ilen);
+
+ /* Assign the NULL flag */
+ if (ilen == UNIV_SQL_NULL) {
+ ut_ad(field->real_maybe_null());
+ goto null_field;
+ }
+
+ field->set_notnull();
+
+ innobase_col_to_mysql(
+ dict_field_get_col(
+ dict_index_get_nth_field(index, ipos)),
+ ifield, ilen, field);
+ }
+}
+
+/*************************************************************//**
+Resets table->record[0]. */
+extern "C" UNIV_INTERN
+void
+innobase_rec_reset(
+/*===============*/
+ TABLE* table) /*!< in/out: MySQL table */
+{
+ uint n_fields = table->s->fields;
+ uint i;
+
+ for (i = 0; i < n_fields; i++) {
+ table->field[i]->set_default();
+ }
+}
+
+/******************************************************************//**
+Removes the filename encoding of a database and table name. */
+static
+void
+innobase_convert_tablename(
+/*=======================*/
+ char* s) /*!< in: identifier; out: decoded identifier */
+{
+ uint errors;
+
+ char* slash = strchr(s, '/');
+
+ if (slash) {
+ char* t;
+ /* Temporarily replace the '/' with NUL. */
+ *slash = 0;
+ /* Convert the database name. */
+ strconvert(&my_charset_filename, s, system_charset_info,
+ s, slash - s + 1, &errors);
+
+ t = s + strlen(s);
+ ut_ad(slash >= t);
+ /* Append a '.' after the database name. */
+ *t++ = '.';
+ slash++;
+ /* Convert the table name. */
+ strconvert(&my_charset_filename, slash, system_charset_info,
+ t, slash - t + strlen(slash), &errors);
+ } else {
+ strconvert(&my_charset_filename, s,
+ system_charset_info, s, strlen(s), &errors);
+ }
+}
+
+/*******************************************************************//**
+This function checks that index keys are sensible.
+@return 0 or error number */
+static
+int
+innobase_check_index_keys(
+/*======================*/
+ const KEY* key_info, /*!< in: Indexes to be created */
+ ulint num_of_keys) /*!< in: Number of indexes to
+ be created */
+{
+ ulint key_num;
+
+ ut_ad(key_info);
+ ut_ad(num_of_keys);
+
+ for (key_num = 0; key_num < num_of_keys; key_num++) {
+ const KEY& key = key_info[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 = key_info[i];
+
+ if (0 == strcmp(key.name, key2.name)) {
+ sql_print_error("InnoDB: key name `%s` appears"
+ " twice in CREATE INDEX\n",
+ key.name);
+
+ return(ER_WRONG_NAME_FOR_INDEX);
+ }
+ }
+
+ /* Check that MySQL does not try to create a column
+ prefix index field on an inappropriate data type and
+ that the same colum does not appear twice in the index. */
+
+ for (ulint i = 0; i < key.key_parts; i++) {
+ const KEY_PART_INFO& key_part1
+ = key.key_part[i];
+ const Field* field
+ = key_part1.field;
+ ibool 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:
+ 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;
+ }
+ }
+
+ sql_print_error("InnoDB: MySQL is trying to"
+ " create a column prefix"
+ " index field on an"
+ " inappropriate data type."
+ " column `%s`,"
+ " index `%s`.\n",
+ field->field_name,
+ key.name);
+ return(ER_WRONG_KEY_COLUMN);
+ }
+
+ for (ulint j = 0; j < i; j++) {
+ const KEY_PART_INFO& key_part2
+ = key.key_part[j];
+
+ if (strcmp(key_part1.field->field_name,
+ key_part2.field->field_name)) {
+ continue;
+ }
+
+ sql_print_error("InnoDB: column `%s`"
+ " is not allowed to occur"
+ " twice in index `%s`.\n",
+ key_part1.field->field_name,
+ key.name);
+ return(ER_WRONG_KEY_COLUMN);
+ }
+ }
+ }
+
+ return(0);
+}
+
+/*******************************************************************//**
+Create index field definition for key part */
+static
+void
+innobase_create_index_field_def(
+/*============================*/
+ KEY_PART_INFO* key_part, /*!< in: MySQL key definition */
+ mem_heap_t* heap, /*!< in: memory heap */
+ merge_index_field_t* index_field) /*!< out: index field
+ definition for key_part */
+{
+ Field* field;
+ ibool is_unsigned;
+ ulint col_type;
+
+ DBUG_ENTER("innobase_create_index_field_def");
+
+ ut_ad(key_part);
+ ut_ad(index_field);
+
+ field = key_part->field;
+ ut_a(field);
+
+ col_type = get_innobase_type_from_mysql_type(&is_unsigned, field);
+
+ if (DATA_BLOB == col_type
+ || (key_part->length < field->pack_length()
+ && field->type() != MYSQL_TYPE_VARCHAR)
+ || (field->type() == MYSQL_TYPE_VARCHAR
+ && key_part->length < field->pack_length()
+ - ((Field_varstring*)field)->length_bytes)) {
+
+ index_field->prefix_len = key_part->length;
+ } else {
+ index_field->prefix_len = 0;
+ }
+
+ index_field->field_name = mem_heap_strdup(heap, field->field_name);
+
+ DBUG_VOID_RETURN;
+}
+
+/*******************************************************************//**
+Create index definition for key */
+static
+void
+innobase_create_index_def(
+/*======================*/
+ KEY* key, /*!< in: key definition */
+ bool new_primary, /*!< in: TRUE=generating
+ a new primary key
+ on the table */
+ bool key_primary, /*!< in: TRUE if this key
+ is a primary key */
+ merge_index_def_t* index, /*!< out: index definition */
+ mem_heap_t* heap) /*!< in: heap where memory
+ is allocated */
+{
+ ulint i;
+ ulint len;
+ ulint n_fields = key->key_parts;
+ char* index_name;
+
+ DBUG_ENTER("innobase_create_index_def");
+
+ index->fields = (merge_index_field_t*) mem_heap_alloc(
+ heap, n_fields * sizeof *index->fields);
+
+ index->ind_type = 0;
+ index->n_fields = n_fields;
+ len = strlen(key->name) + 1;
+ index->name = index_name = (char*) mem_heap_alloc(heap,
+ len + !new_primary);
+
+ if (UNIV_LIKELY(!new_primary)) {
+ *index_name++ = TEMP_INDEX_PREFIX;
+ }
+
+ memcpy(index_name, key->name, len);
+
+ if (key->flags & HA_NOSAME) {
+ index->ind_type |= DICT_UNIQUE;
+ }
+
+ if (key_primary) {
+ index->ind_type |= DICT_CLUSTERED;
+ }
+
+ for (i = 0; i < n_fields; i++) {
+ innobase_create_index_field_def(&key->key_part[i], heap,
+ &index->fields[i]);
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+/*******************************************************************//**
+Copy index field definition */
+static
+void
+innobase_copy_index_field_def(
+/*==========================*/
+ const dict_field_t* field, /*!< in: definition to copy */
+ merge_index_field_t* index_field) /*!< out: copied definition */
+{
+ DBUG_ENTER("innobase_copy_index_field_def");
+ DBUG_ASSERT(field != NULL);
+ DBUG_ASSERT(index_field != NULL);
+
+ index_field->field_name = field->name;
+ index_field->prefix_len = field->prefix_len;
+
+ DBUG_VOID_RETURN;
+}
+
+/*******************************************************************//**
+Copy index definition for the index */
+static
+void
+innobase_copy_index_def(
+/*====================*/
+ const dict_index_t* index, /*!< in: index definition to copy */
+ merge_index_def_t* new_index,/*!< out: Index definition */
+ mem_heap_t* heap) /*!< in: heap where allocated */
+{
+ ulint n_fields;
+ ulint i;
+
+ DBUG_ENTER("innobase_copy_index_def");
+
+ /* Note that we take only those fields that user defined to be
+ in the index. In the internal representation more colums were
+ added and those colums are not copied .*/
+
+ n_fields = index->n_user_defined_cols;
+
+ new_index->fields = (merge_index_field_t*) mem_heap_alloc(
+ heap, n_fields * sizeof *new_index->fields);
+
+ /* When adding a PRIMARY KEY, we may convert a previous
+ clustered index to a secondary index (UNIQUE NOT NULL). */
+ new_index->ind_type = index->type & ~DICT_CLUSTERED;
+ new_index->n_fields = n_fields;
+ new_index->name = index->name;
+
+ for (i = 0; i < n_fields; i++) {
+ innobase_copy_index_field_def(&index->fields[i],
+ &new_index->fields[i]);
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+/*******************************************************************//**
+Create an index table where indexes are ordered as follows:
+
+IF a new primary key is defined for the table THEN
+
+ 1) New primary key
+ 2) Original secondary indexes
+ 3) New secondary indexes
+
+ELSE
+
+ 1) All new indexes in the order they arrive from MySQL
+
+ENDIF
+
+
+@return key definitions or NULL */
+static
+merge_index_def_t*
+innobase_create_key_def(
+/*====================*/
+ trx_t* trx, /*!< in: trx */
+ const dict_table_t*table, /*!< in: table definition */
+ mem_heap_t* heap, /*!< in: heap where space for key
+ definitions are allocated */
+ KEY* key_info, /*!< in: Indexes to be created */
+ ulint& n_keys) /*!< in/out: Number of indexes to
+ be created */
+{
+ ulint i = 0;
+ merge_index_def_t* indexdef;
+ merge_index_def_t* indexdefs;
+ bool new_primary;
+
+ DBUG_ENTER("innobase_create_key_def");
+
+ indexdef = indexdefs = (merge_index_def_t*)
+ mem_heap_alloc(heap, sizeof *indexdef
+ * (n_keys + UT_LIST_GET_LEN(table->indexes)));
+
+ /* If there is a primary key, it is always the first index
+ defined for the table. */
+
+ new_primary = !my_strcasecmp(system_charset_info,
+ key_info->name, "PRIMARY");
+
+ /* If there is a UNIQUE INDEX consisting entirely of NOT NULL
+ columns, MySQL will treat it as a PRIMARY KEY unless the
+ table already has one. */
+
+ if (!new_primary && (key_info->flags & HA_NOSAME)
+ && row_table_got_default_clust_index(table)) {
+ uint key_part = key_info->key_parts;
+
+ new_primary = TRUE;
+
+ while (key_part--) {
+ if (key_info->key_part[key_part].key_type
+ & FIELDFLAG_MAYBE_NULL) {
+ new_primary = FALSE;
+ break;
+ }
+ }
+ }
+
+ if (new_primary) {
+ const dict_index_t* index;
+
+ /* Create the PRIMARY key index definition */
+ innobase_create_index_def(&key_info[i++], TRUE, TRUE,
+ indexdef++, heap);
+
+ row_mysql_lock_data_dictionary(trx);
+
+ index = dict_table_get_first_index(table);
+
+ /* Copy the index definitions of the old table. Skip
+ the old clustered index if it is a generated clustered
+ index or a PRIMARY KEY. If the clustered index is a
+ UNIQUE INDEX, it must be converted to a secondary index. */
+
+ if (dict_index_get_nth_col(index, 0)->mtype == DATA_SYS
+ || !my_strcasecmp(system_charset_info,
+ index->name, "PRIMARY")) {
+ index = dict_table_get_next_index(index);
+ }
+
+ while (index) {
+ innobase_copy_index_def(index, indexdef++, heap);
+ index = dict_table_get_next_index(index);
+ }
+
+ row_mysql_unlock_data_dictionary(trx);
+ }
+
+ /* Create definitions for added secondary indexes. */
+
+ while (i < n_keys) {
+ innobase_create_index_def(&key_info[i++], new_primary, FALSE,
+ indexdef++, heap);
+ }
+
+ n_keys = indexdef - indexdefs;
+
+ DBUG_RETURN(indexdefs);
+}
+
+/*******************************************************************//**
+Create a temporary tablename using query id, thread id, and id
+@return temporary tablename */
+static
+char*
+innobase_create_temporary_tablename(
+/*================================*/
+ mem_heap_t* heap, /*!< in: memory heap */
+ char id, /*!< in: identifier [0-9a-zA-Z] */
+ const char* table_name) /*!< in: table name */
+{
+ char* name;
+ ulint len;
+ static const char suffix[] = "@0023 "; /* "# " */
+
+ len = strlen(table_name);
+
+ name = (char*) mem_heap_alloc(heap, len + sizeof suffix);
+ memcpy(name, table_name, len);
+ memcpy(name + len, suffix, sizeof suffix);
+ name[len + (sizeof suffix - 2)] = id;
+
+ return(name);
+}
+
+/*******************************************************************//**
+Create indexes.
+@return 0 or error number */
+UNIV_INTERN
+int
+ha_innobase::add_index(
+/*===================*/
+ TABLE* table, /*!< in: Table where indexes are created */
+ KEY* key_info, /*!< in: Indexes to be created */
+ uint num_of_keys) /*!< in: Number of indexes to be created */
+{
+ dict_index_t** index; /*!< Index to be created */
+ dict_table_t* innodb_table; /*!< InnoDB table in dictionary */
+ dict_table_t* indexed_table; /*!< Table where indexes are created */
+ merge_index_def_t* index_defs; /*!< Index definitions */
+ mem_heap_t* heap; /*!< Heap for index definitions */
+ trx_t* trx; /*!< Transaction */
+ ulint num_of_idx;
+ ulint num_created = 0;
+ ibool dict_locked = FALSE;
+ ulint new_primary;
+ int error;
+
+ DBUG_ENTER("ha_innobase::add_index");
+ ut_a(table);
+ ut_a(key_info);
+ ut_a(num_of_keys);
+
+ if (srv_created_new_raw || srv_force_recovery) {
+ DBUG_RETURN(HA_ERR_WRONG_COMMAND);
+ }
+
+ update_thd();
+
+ heap = mem_heap_create(1024);
+
+ /* In case MySQL calls this in the middle of a SELECT query, release
+ possible adaptive hash latch to avoid deadlocks of threads. */
+ trx_search_latch_release_if_reserved(prebuilt->trx);
+ trx_start_if_not_started(prebuilt->trx);
+
+ /* Create a background transaction for the operations on
+ the data dictionary tables. */
+ trx = innobase_trx_allocate(user_thd);
+ trx_start_if_not_started(trx);
+
+ innodb_table = indexed_table
+ = dict_table_get(prebuilt->table->name, FALSE);
+
+ /* Check if the index name is reserved. */
+ if (innobase_index_name_is_reserved(trx, key_info, num_of_keys)) {
+ error = -1;
+ } else {
+ /* Check that index keys are sensible */
+ error = innobase_check_index_keys(key_info, num_of_keys);
+ }
+
+ if (UNIV_UNLIKELY(error)) {
+err_exit:
+ mem_heap_free(heap);
+ trx_general_rollback_for_mysql(trx, NULL);
+ trx_free_for_mysql(trx);
+ trx_commit_for_mysql(prebuilt->trx);
+ DBUG_RETURN(error);
+ }
+
+ /* 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. */
+
+ num_of_idx = num_of_keys;
+
+ index_defs = innobase_create_key_def(
+ trx, innodb_table, heap, key_info, num_of_idx);
+
+ new_primary = DICT_CLUSTERED & index_defs[0].ind_type;
+
+ /* Allocate memory for dictionary index definitions */
+
+ index = (dict_index_t**) mem_heap_alloc(
+ heap, num_of_idx * sizeof *index);
+
+ /* Flag this transaction as a dictionary operation, so that
+ the data dictionary will be locked in crash recovery. */
+ trx_set_dict_operation(trx, TRX_DICT_OP_INDEX);
+
+ /* Acquire a lock on the table before creating any indexes. */
+ error = row_merge_lock_table(prebuilt->trx, innodb_table,
+ new_primary ? LOCK_X : LOCK_S);
+
+ if (UNIV_UNLIKELY(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(trx);
+ dict_locked = TRUE;
+
+ /* If a new primary key is defined for the table we need
+ to drop the original table and rebuild all indexes. */
+
+ if (UNIV_UNLIKELY(new_primary)) {
+ /* This transaction should be the only one
+ operating on the table. */
+ ut_a(innodb_table->n_mysql_handles_opened == 1);
+
+ char* new_table_name = innobase_create_temporary_tablename(
+ heap, '1', innodb_table->name);
+
+ /* Clone the table. */
+ trx_set_dict_operation(trx, TRX_DICT_OP_TABLE);
+ indexed_table = row_merge_create_temporary_table(
+ new_table_name, index_defs, innodb_table, trx);
+
+ if (!indexed_table) {
+
+ switch (trx->error_state) {
+ case DB_TABLESPACE_ALREADY_EXISTS:
+ case DB_DUPLICATE_KEY:
+ innobase_convert_tablename(new_table_name);
+ my_error(HA_ERR_TABLE_EXIST, MYF(0),
+ new_table_name);
+ error = HA_ERR_TABLE_EXIST;
+ break;
+ default:
+ error = convert_error_code_to_mysql(
+ trx->error_state, innodb_table->flags,
+ user_thd);
+ }
+
+ row_mysql_unlock_data_dictionary(trx);
+ goto err_exit;
+ }
+
+ trx->table_id = indexed_table->id;
+ }
+
+ /* Create the indexes in SYS_INDEXES and load into dictionary. */
+
+ for (ulint i = 0; i < num_of_idx; i++) {
+
+ index[i] = row_merge_create_index(trx, indexed_table,
+ &index_defs[i]);
+
+ if (!index[i]) {
+ error = trx->error_state;
+ goto error_handling;
+ }
+
+ num_created++;
+ }
+
+ ut_ad(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(), indexed_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(trx);
+
+ row_mysql_unlock_data_dictionary(trx);
+ dict_locked = FALSE;
+
+ ut_a(trx->n_active_thrs == 0);
+ ut_a(UT_LIST_GET_LEN(trx->signals) == 0);
+
+ if (UNIV_UNLIKELY(new_primary)) {
+ /* A primary key is to be built. Acquire an exclusive
+ table lock also on the table that is being created. */
+ ut_ad(indexed_table != innodb_table);
+
+ error = row_merge_lock_table(prebuilt->trx, indexed_table,
+ LOCK_X);
+
+ if (UNIV_UNLIKELY(error != DB_SUCCESS)) {
+
+ goto error_handling;
+ }
+ }
+
+ /* Read the clustered index of the table and build indexes
+ based on this information using temporary files and merge sort. */
+ error = row_merge_build_indexes(prebuilt->trx,
+ innodb_table, indexed_table,
+ index, num_of_idx, table);
+
+error_handling:
+#ifdef UNIV_DEBUG
+ /* TODO: At the moment we can't handle the following statement
+ in our debugging code below:
+
+ alter table t drop index b, add index (b);
+
+ The fix will have to parse the SQL and note that the index
+ being added has the same name as the one being dropped and
+ ignore that in the dup index check.*/
+ //dict_table_check_for_dup_indexes(prebuilt->table);
+#endif
+
+ /* After an error, remove all those index definitions from the
+ dictionary which were defined. */
+
+ switch (error) {
+ const char* old_name;
+ char* tmp_name;
+ case DB_SUCCESS:
+ ut_a(!dict_locked);
+ row_mysql_lock_data_dictionary(trx);
+ dict_locked = TRUE;
+
+ if (!new_primary) {
+ error = row_merge_rename_indexes(trx, indexed_table);
+
+ if (error != DB_SUCCESS) {
+ row_merge_drop_indexes(trx, indexed_table,
+ index, num_created);
+ }
+
+ goto convert_error;
+ }
+
+ /* If a new primary key was defined for the table and
+ there was no error at this point, we can now rename
+ the old table as a temporary table, rename the new
+ temporary table as the old table and drop the old table. */
+ old_name = innodb_table->name;
+ tmp_name = innobase_create_temporary_tablename(heap, '2',
+ old_name);
+
+ error = row_merge_rename_tables(innodb_table, indexed_table,
+ tmp_name, trx);
+
+ if (error != DB_SUCCESS) {
+
+ row_merge_drop_table(trx, indexed_table);
+
+ switch (error) {
+ case DB_TABLESPACE_ALREADY_EXISTS:
+ case DB_DUPLICATE_KEY:
+ innobase_convert_tablename(tmp_name);
+ my_error(HA_ERR_TABLE_EXIST, MYF(0), tmp_name);
+ error = HA_ERR_TABLE_EXIST;
+ break;
+ default:
+ goto convert_error;
+ }
+ break;
+ }
+
+ trx_commit_for_mysql(prebuilt->trx);
+ row_prebuilt_free(prebuilt, TRUE);
+ prebuilt = row_create_prebuilt(indexed_table);
+
+ indexed_table->n_mysql_handles_opened++;
+
+ error = row_merge_drop_table(trx, innodb_table);
+ innodb_table = indexed_table;
+ goto convert_error;
+
+ case DB_TOO_BIG_RECORD:
+ my_error(HA_ERR_TO_BIG_ROW, MYF(0));
+ goto error;
+ case DB_PRIMARY_KEY_IS_NULL:
+ my_error(ER_PRIMARY_CANT_HAVE_NULL, MYF(0));
+ /* fall through */
+ case DB_DUPLICATE_KEY:
+error:
+ prebuilt->trx->error_info = NULL;
+ /* fall through */
+ default:
+ if (new_primary) {
+ if (indexed_table != innodb_table) {
+ row_merge_drop_table(trx, indexed_table);
+ }
+ } else {
+ if (!dict_locked) {
+ row_mysql_lock_data_dictionary(trx);
+ dict_locked = TRUE;
+ }
+
+ row_merge_drop_indexes(trx, indexed_table,
+ index, num_created);
+ }
+
+convert_error:
+ error = convert_error_code_to_mysql(error,
+ innodb_table->flags,
+ user_thd);
+ }
+
+ mem_heap_free(heap);
+ trx_commit_for_mysql(trx);
+ if (prebuilt->trx) {
+ trx_commit_for_mysql(prebuilt->trx);
+ }
+
+ if (dict_locked) {
+ row_mysql_unlock_data_dictionary(trx);
+ }
+
+ trx_free_for_mysql(trx);
+
+ /* There might be work for utility threads.*/
+ srv_active_wake_master_thread();
+
+ DBUG_RETURN(error);
+}
+
+/*******************************************************************//**
+Prepare to drop some indexes of a table.
+@return 0 or error number */
+UNIV_INTERN
+int
+ha_innobase::prepare_drop_index(
+/*============================*/
+ TABLE* table, /*!< in: Table where indexes are dropped */
+ uint* key_num, /*!< in: Key nums to be dropped */
+ uint num_of_keys) /*!< in: Number of keys to be dropped */
+{
+ trx_t* trx;
+ int err = 0;
+ uint n_key;
+
+ DBUG_ENTER("ha_innobase::prepare_drop_index");
+ ut_ad(table);
+ ut_ad(key_num);
+ ut_ad(num_of_keys);
+ if (srv_created_new_raw || srv_force_recovery) {
+ DBUG_RETURN(HA_ERR_WRONG_COMMAND);
+ }
+
+ update_thd();
+
+ trx_search_latch_release_if_reserved(prebuilt->trx);
+ trx = prebuilt->trx;
+
+ /* Test and mark all the indexes to be dropped */
+
+ row_mysql_lock_data_dictionary(trx);
+
+ /* Check that none of the indexes have previously been flagged
+ for deletion. */
+ {
+ const dict_index_t* index
+ = dict_table_get_first_index(prebuilt->table);
+ do {
+ ut_a(!index->to_be_dropped);
+ index = dict_table_get_next_index(index);
+ } while (index);
+ }
+
+ for (n_key = 0; n_key < num_of_keys; n_key++) {
+ const KEY* key;
+ dict_index_t* index;
+
+ key = table->key_info + key_num[n_key];
+ index = dict_table_get_index_on_name_and_min_id(
+ prebuilt->table, key->name);
+
+ if (!index) {
+ sql_print_error("InnoDB could not find key n:o %u "
+ "with name %s for table %s",
+ key_num[n_key],
+ key ? key->name : "NULL",
+ prebuilt->table->name);
+
+ err = HA_ERR_KEY_NOT_FOUND;
+ goto func_exit;
+ }
+
+ /* Refuse to drop the clustered index. It would be
+ better to automatically generate a clustered index,
+ but mysql_alter_table() will call this method only
+ after ha_innobase::add_index(). */
+
+ if (dict_index_is_clust(index)) {
+ my_error(ER_REQUIRES_PRIMARY_KEY, MYF(0));
+ err = -1;
+ goto func_exit;
+ }
+
+ index->to_be_dropped = TRUE;
+ }
+
+ /* If FOREIGN_KEY_CHECK = 1 you may not drop an index defined
+ for a foreign key constraint because InnoDB requires that both
+ tables contain indexes for the constraint. Note that CREATE
+ INDEX id ON table does a CREATE INDEX and DROP INDEX, and we
+ can ignore here foreign keys because a new index for the
+ foreign key has already been created.
+
+ We check for the foreign key constraints after marking the
+ candidate indexes for deletion, because when we check for an
+ equivalent foreign index we don't want to select an index that
+ is later deleted. */
+
+ if (trx->check_foreigns
+ && thd_sql_command(user_thd) != SQLCOM_CREATE_INDEX) {
+ dict_index_t* index;
+
+ for (index = dict_table_get_first_index(prebuilt->table);
+ index;
+ index = dict_table_get_next_index(index)) {
+ dict_foreign_t* foreign;
+
+ if (!index->to_be_dropped) {
+
+ continue;
+ }
+
+ /* Check if the index is referenced. */
+ foreign = dict_table_get_referenced_constraint(
+ prebuilt->table, index);
+
+ if (foreign) {
+index_needed:
+ trx_set_detailed_error(
+ trx,
+ "Index needed in foreign key "
+ "constraint");
+
+ trx->error_info = index;
+
+ err = HA_ERR_DROP_INDEX_FK;
+ break;
+ } else {
+ /* Check if this index references some
+ other table */
+ foreign = dict_table_get_foreign_constraint(
+ prebuilt->table, index);
+
+ if (foreign) {
+ ut_a(foreign->foreign_index == index);
+
+ /* Search for an equivalent index that
+ the foreign key constraint could use
+ if this index were to be deleted. */
+ if (!dict_foreign_find_equiv_index(
+ foreign)) {
+
+ goto index_needed;
+ }
+ }
+ }
+ }
+ } else if (thd_sql_command(user_thd) == SQLCOM_CREATE_INDEX) {
+ /* This is a drop of a foreign key constraint index that
+ was created by MySQL when the constraint was added. MySQL
+ does this when the user creates an index explicitly which
+ can be used in place of the automatically generated index. */
+
+ dict_index_t* index;
+
+ for (index = dict_table_get_first_index(prebuilt->table);
+ index;
+ index = dict_table_get_next_index(index)) {
+ dict_foreign_t* foreign;
+
+ if (!index->to_be_dropped) {
+
+ continue;
+ }
+
+ /* Check if this index references some other table */
+ foreign = dict_table_get_foreign_constraint(
+ prebuilt->table, index);
+
+ if (foreign == NULL) {
+
+ continue;
+ }
+
+ ut_a(foreign->foreign_index == index);
+
+ /* Search for an equivalent index that the
+ foreign key constraint could use if this index
+ were to be deleted. */
+
+ if (!dict_foreign_find_equiv_index(foreign)) {
+ trx_set_detailed_error(
+ trx,
+ "Index needed in foreign key "
+ "constraint");
+
+ trx->error_info = foreign->foreign_index;
+
+ err = HA_ERR_DROP_INDEX_FK;
+ break;
+ }
+ }
+ }
+
+func_exit:
+ if (err) {
+ /* Undo our changes since there was some sort of error. */
+ dict_index_t* index
+ = dict_table_get_first_index(prebuilt->table);
+
+ do {
+ index->to_be_dropped = FALSE;
+ index = dict_table_get_next_index(index);
+ } while (index);
+ }
+
+ row_mysql_unlock_data_dictionary(trx);
+
+ DBUG_RETURN(err);
+}
+
+/*******************************************************************//**
+Drop the indexes that were passed to a successful prepare_drop_index().
+@return 0 or error number */
+UNIV_INTERN
+int
+ha_innobase::final_drop_index(
+/*==========================*/
+ TABLE* table) /*!< in: Table where indexes are dropped */
+{
+ dict_index_t* index; /*!< Index to be dropped */
+ trx_t* trx; /*!< Transaction */
+ int err;
+
+ DBUG_ENTER("ha_innobase::final_drop_index");
+ ut_ad(table);
+
+ if (srv_created_new_raw || srv_force_recovery) {
+ DBUG_RETURN(HA_ERR_WRONG_COMMAND);
+ }
+
+ update_thd();
+
+ trx_search_latch_release_if_reserved(prebuilt->trx);
+ trx_start_if_not_started(prebuilt->trx);
+
+ /* Create a background transaction for the operations on
+ the data dictionary tables. */
+ trx = innobase_trx_allocate(user_thd);
+ trx_start_if_not_started(trx);
+
+ /* Flag this transaction as a dictionary operation, so that
+ the data dictionary will be locked in crash recovery. */
+ trx_set_dict_operation(trx, TRX_DICT_OP_INDEX);
+
+ /* Lock the table exclusively, to ensure that no active
+ transaction depends on an index that is being dropped. */
+ err = convert_error_code_to_mysql(
+ row_merge_lock_table(prebuilt->trx, prebuilt->table, LOCK_X),
+ prebuilt->table->flags, user_thd);
+
+ row_mysql_lock_data_dictionary(trx);
+
+ if (UNIV_UNLIKELY(err)) {
+
+ /* Unmark the indexes to be dropped. */
+ for (index = dict_table_get_first_index(prebuilt->table);
+ index; index = dict_table_get_next_index(index)) {
+
+ index->to_be_dropped = FALSE;
+ }
+
+ goto func_exit;
+ }
+
+ /* Drop indexes marked to be dropped */
+
+ index = dict_table_get_first_index(prebuilt->table);
+
+ while (index) {
+ dict_index_t* next_index;
+
+ next_index = dict_table_get_next_index(index);
+
+ if (index->to_be_dropped) {
+
+ row_merge_drop_index(index, prebuilt->table, trx);
+ }
+
+ index = next_index;
+ }
+
+ /* Check that all flagged indexes were dropped. */
+ for (index = dict_table_get_first_index(prebuilt->table);
+ index; index = dict_table_get_next_index(index)) {
+ ut_a(!index->to_be_dropped);
+ }
+
+#ifdef UNIV_DEBUG
+ dict_table_check_for_dup_indexes(prebuilt->table);
+#endif
+
+func_exit:
+ trx_commit_for_mysql(trx);
+ trx_commit_for_mysql(prebuilt->trx);
+ row_mysql_unlock_data_dictionary(trx);
+
+ /* Flush the log to reduce probability that the .frm files and
+ the InnoDB data dictionary get out-of-sync if the user runs
+ with innodb_flush_log_at_trx_commit = 0 */
+
+ log_buffer_flush_to_disk();
+
+ trx_free_for_mysql(trx);
+
+ /* Tell the InnoDB server that there might be work for
+ utility threads: */
+
+ srv_active_wake_master_thread();
+
+ DBUG_RETURN(err);
+}