summaryrefslogtreecommitdiff
path: root/sql/sql_table.cc
diff options
context:
space:
mode:
Diffstat (limited to 'sql/sql_table.cc')
-rw-r--r--sql/sql_table.cc4388
1 files changed, 2654 insertions, 1734 deletions
diff --git a/sql/sql_table.cc b/sql/sql_table.cc
index 1c064d612ba..962bc715764 100644
--- a/sql/sql_table.cc
+++ b/sql/sql_table.cc
@@ -33,6 +33,7 @@
#include "sql_partition.h" // mem_alloc_error,
// generate_partition_syntax,
// partition_info
+ // NOT_A_PARTITION_ID
#include "sql_db.h" // load_db_opt_by_name
#include "sql_time.h" // make_truncated_value_warning
#include "records.h" // init_read_record, end_read_record
@@ -64,16 +65,22 @@ const char *primary_key_name="PRIMARY";
static bool check_if_keyname_exists(const char *name,KEY *start, KEY *end);
static char *make_unique_key_name(const char *field_name,KEY *start,KEY *end);
-static int copy_data_between_tables(THD *thd, TABLE *,TABLE *,
- List<Create_field> &, bool,
- uint, ORDER *, ha_rows *,ha_rows *,
- enum enum_enable_or_disable, bool);
+static int copy_data_between_tables(THD *thd, TABLE *from,TABLE *to,
+ List<Create_field> &create, bool ignore,
+ uint order_num, ORDER *order,
+ ha_rows *copied,ha_rows *deleted,
+ Alter_info::enum_enable_or_disable keys_onoff,
+ Alter_table_ctx *alter_ctx);
static bool prepare_blob_field(THD *thd, Create_field *sql_field);
static bool check_engine(THD *, const char *, const char *, HA_CREATE_INFO *);
-static int mysql_prepare_create_table(THD *, HA_CREATE_INFO *, Alter_info *,
- bool, uint *, handler *, KEY **, uint *,
- int);
+static int
+mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info,
+ Alter_info *alter_info,
+ bool tmp_table,
+ uint *db_options,
+ handler *file, KEY **key_info_buffer,
+ uint *key_count, int create_table_mode);
/**
@brief Helper function for explain_filename
@@ -582,22 +589,19 @@ uint build_table_filename(char *buff, size_t bufflen, const char *db,
}
-/*
- Creates path to a file: mysql_tmpdir/#sql1234_12_1.ext
+/**
+ Create path to a temporary table mysql_tmpdir/#sql1234_12_1
+ (i.e. to its .FRM file but without an extension).
- SYNOPSIS
- build_tmptable_filename()
- thd The thread handle.
- buff Where to write result in my_charset_filename.
- bufflen buff size
-
- NOTES
+ @param thd The thread handle.
+ @param buff Where to write result in my_charset_filename.
+ @param bufflen buff size
+ @note
Uses current_pid, thread_id, and tmp_table counter to create
a file name in mysql_tmpdir.
- RETURN
- path length
+ @return Path length.
*/
uint build_tmptable_filename(THD* thd, char *buff, size_t bufflen)
@@ -646,7 +650,6 @@ uint build_tmptable_filename(THD* thd, char *buff, size_t bufflen)
--------------------------------------------------------------------------
*/
-
struct st_global_ddl_log
{
/*
@@ -702,6 +705,7 @@ static bool read_ddl_log_file_entry(uint entry_no)
uint io_size= global_ddl_log.io_size;
DBUG_ENTER("read_ddl_log_file_entry");
+ mysql_mutex_assert_owner(&LOCK_gdl);
if (mysql_file_pread(file_id, file_entry_buf, io_size, io_size * entry_no,
MYF(MY_WME)) != io_size)
error= TRUE;
@@ -723,9 +727,10 @@ static bool write_ddl_log_file_entry(uint entry_no)
{
bool error= FALSE;
File file_id= global_ddl_log.file_id;
- char *file_entry_buf= (char*)global_ddl_log.file_entry_buf;
+ uchar *file_entry_buf= (uchar*)global_ddl_log.file_entry_buf;
DBUG_ENTER("write_ddl_log_file_entry");
+ mysql_mutex_assert_owner(&LOCK_gdl);
if (mysql_file_pwrite(file_id, (uchar*)file_entry_buf,
IO_SIZE, IO_SIZE * entry_no, MYF(MY_WME)) != IO_SIZE)
error= TRUE;
@@ -733,19 +738,33 @@ static bool write_ddl_log_file_entry(uint entry_no)
}
-/*
- Write ddl log header
- SYNOPSIS
- write_ddl_log_header()
- RETURN VALUES
- TRUE Error
- FALSE Success
+/**
+ Sync the ddl log file.
+
+ @return Operation status
+ @retval FALSE Success
+ @retval TRUE Error
+*/
+
+
+static bool sync_ddl_log_file()
+{
+ DBUG_ENTER("sync_ddl_log_file");
+ DBUG_RETURN(mysql_file_sync(global_ddl_log.file_id, MYF(MY_WME)));
+}
+
+
+/**
+ Write ddl log header.
+
+ @return Operation status
+ @retval TRUE Error
+ @retval FALSE Success
*/
static bool write_ddl_log_header()
{
uint16 const_var;
- bool error= FALSE;
DBUG_ENTER("write_ddl_log_header");
int4store(&global_ddl_log.file_entry_buf[DDL_LOG_NUM_ENTRY_POS],
@@ -761,8 +780,7 @@ static bool write_ddl_log_header()
sql_print_error("Error writing ddl log header");
DBUG_RETURN(TRUE);
}
- (void) sync_ddl_log();
- DBUG_RETURN(error);
+ DBUG_RETURN(sync_ddl_log_file());
}
@@ -796,7 +814,7 @@ static inline void create_ddl_log_file_name(char *file_name)
static uint read_ddl_log_header()
{
- char *file_entry_buf= (char*)global_ddl_log.file_entry_buf;
+ uchar *file_entry_buf= (uchar*)global_ddl_log.file_entry_buf;
char file_name[FN_REFLEN];
uint entry_no;
bool successful_open= FALSE;
@@ -923,6 +941,108 @@ end:
}
+/**
+ Sync ddl log file.
+
+ @return Operation status
+ @retval TRUE Error
+ @retval FALSE Success
+*/
+
+static bool sync_ddl_log_no_lock()
+{
+ DBUG_ENTER("sync_ddl_log_no_lock");
+
+ mysql_mutex_assert_owner(&LOCK_gdl);
+ if ((!global_ddl_log.recovery_phase) &&
+ init_ddl_log())
+ {
+ DBUG_RETURN(TRUE);
+ }
+ DBUG_RETURN(sync_ddl_log_file());
+}
+
+
+/**
+ @brief Deactivate an individual entry.
+
+ @details For complex rename operations we need to deactivate individual
+ entries.
+
+ During replace operations where we start with an existing table called
+ t1 and a replacement table called t1#temp or something else and where
+ we want to delete t1 and rename t1#temp to t1 this is not possible to
+ do in a safe manner unless the ddl log is informed of the phases in
+ the change.
+
+ Delete actions are 1-phase actions that can be ignored immediately after
+ being executed.
+ Rename actions from x to y is also a 1-phase action since there is no
+ interaction with any other handlers named x and y.
+ Replace action where drop y and x -> y happens needs to be a two-phase
+ action. Thus the first phase will drop y and the second phase will
+ rename x -> y.
+
+ @param entry_no Entry position of record to change
+
+ @return Operation status
+ @retval TRUE Error
+ @retval FALSE Success
+*/
+
+static bool deactivate_ddl_log_entry_no_lock(uint entry_no)
+{
+ uchar *file_entry_buf= (uchar*)global_ddl_log.file_entry_buf;
+ DBUG_ENTER("deactivate_ddl_log_entry_no_lock");
+
+ mysql_mutex_assert_owner(&LOCK_gdl);
+ if (!read_ddl_log_file_entry(entry_no))
+ {
+ if (file_entry_buf[DDL_LOG_ENTRY_TYPE_POS] == DDL_LOG_ENTRY_CODE)
+ {
+ /*
+ Log entry, if complete mark it done (IGNORE).
+ Otherwise increase the phase by one.
+ */
+ if (file_entry_buf[DDL_LOG_ACTION_TYPE_POS] == DDL_LOG_DELETE_ACTION ||
+ file_entry_buf[DDL_LOG_ACTION_TYPE_POS] == DDL_LOG_RENAME_ACTION ||
+ (file_entry_buf[DDL_LOG_ACTION_TYPE_POS] == DDL_LOG_REPLACE_ACTION &&
+ file_entry_buf[DDL_LOG_PHASE_POS] == 1) ||
+ (file_entry_buf[DDL_LOG_ACTION_TYPE_POS] == DDL_LOG_EXCHANGE_ACTION &&
+ file_entry_buf[DDL_LOG_PHASE_POS] >= EXCH_PHASE_TEMP_TO_FROM))
+ file_entry_buf[DDL_LOG_ENTRY_TYPE_POS]= DDL_IGNORE_LOG_ENTRY_CODE;
+ else if (file_entry_buf[DDL_LOG_ACTION_TYPE_POS] == DDL_LOG_REPLACE_ACTION)
+ {
+ DBUG_ASSERT(file_entry_buf[DDL_LOG_PHASE_POS] == 0);
+ file_entry_buf[DDL_LOG_PHASE_POS]= 1;
+ }
+ else if (file_entry_buf[DDL_LOG_ACTION_TYPE_POS] == DDL_LOG_EXCHANGE_ACTION)
+ {
+ DBUG_ASSERT(file_entry_buf[DDL_LOG_PHASE_POS] <=
+ EXCH_PHASE_FROM_TO_NAME);
+ file_entry_buf[DDL_LOG_PHASE_POS]++;
+ }
+ else
+ {
+ DBUG_ASSERT(0);
+ }
+ if (write_ddl_log_file_entry(entry_no))
+ {
+ sql_print_error("Error in deactivating log entry. Position = %u",
+ entry_no);
+ DBUG_RETURN(TRUE);
+ }
+ }
+ }
+ else
+ {
+ sql_print_error("Failed in reading entry before deactivating it");
+ DBUG_RETURN(TRUE);
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
/*
Execute one action in a ddl log entry
SYNOPSIS
@@ -953,12 +1073,14 @@ static int execute_ddl_log_action(THD *thd, DDL_LOG_ENTRY *ddl_log_entry)
DBUG_RETURN(FALSE);
}
DBUG_PRINT("ddl_log",
- ("execute type %c next %u name '%s' from_name '%s' handler '%s'",
+ ("execute type %c next %u name '%s' from_name '%s' handler '%s'"
+ " tmp_name '%s'",
ddl_log_entry->action_type,
ddl_log_entry->next_entry,
ddl_log_entry->name,
ddl_log_entry->from_name,
- ddl_log_entry->handler_name));
+ ddl_log_entry->handler_name,
+ ddl_log_entry->tmp_name));
handler_name.str= (char*)ddl_log_entry->handler_name;
handler_name.length= strlen(ddl_log_entry->handler_name);
init_sql_alloc(&mem_root, TABLE_ALLOC_BLOCK_SIZE, 0, MYF(MY_THREAD_SPECIFIC));
@@ -1008,7 +1130,7 @@ static int execute_ddl_log_action(THD *thd, DDL_LOG_ENTRY *ddl_log_entry)
break;
}
}
- if ((deactivate_ddl_log_entry(ddl_log_entry->entry_pos)))
+ if ((deactivate_ddl_log_entry_no_lock(ddl_log_entry->entry_pos)))
break;
(void) sync_ddl_log();
error= FALSE;
@@ -1049,6 +1171,58 @@ static int execute_ddl_log_action(THD *thd, DDL_LOG_ENTRY *ddl_log_entry)
error= FALSE;
break;
}
+ case DDL_LOG_EXCHANGE_ACTION:
+ {
+ /* We hold LOCK_gdl, so we can alter global_ddl_log.file_entry_buf */
+ char *file_entry_buf= (char*)&global_ddl_log.file_entry_buf;
+ /* not yet implemented for frm */
+ DBUG_ASSERT(!frm_action);
+ /*
+ Using a case-switch here to revert all currently done phases,
+ since it will fall through until the first phase is undone.
+ */
+ switch (ddl_log_entry->phase) {
+ case EXCH_PHASE_TEMP_TO_FROM:
+ /* tmp_name -> from_name possibly done */
+ (void) file->ha_rename_table(ddl_log_entry->from_name,
+ ddl_log_entry->tmp_name);
+ /* decrease the phase and sync */
+ file_entry_buf[DDL_LOG_PHASE_POS]--;
+ if (write_ddl_log_file_entry(ddl_log_entry->entry_pos))
+ break;
+ if (sync_ddl_log_no_lock())
+ break;
+ /* fall through */
+ case EXCH_PHASE_FROM_TO_NAME:
+ /* from_name -> name possibly done */
+ (void) file->ha_rename_table(ddl_log_entry->name,
+ ddl_log_entry->from_name);
+ /* decrease the phase and sync */
+ file_entry_buf[DDL_LOG_PHASE_POS]--;
+ if (write_ddl_log_file_entry(ddl_log_entry->entry_pos))
+ break;
+ if (sync_ddl_log_no_lock())
+ break;
+ /* fall through */
+ case EXCH_PHASE_NAME_TO_TEMP:
+ /* name -> tmp_name possibly done */
+ (void) file->ha_rename_table(ddl_log_entry->tmp_name,
+ ddl_log_entry->name);
+ /* disable the entry and sync */
+ file_entry_buf[DDL_LOG_ENTRY_TYPE_POS]= DDL_IGNORE_LOG_ENTRY_CODE;
+ if (write_ddl_log_file_entry(ddl_log_entry->entry_pos))
+ break;
+ if (sync_ddl_log_no_lock())
+ break;
+ error= FALSE;
+ break;
+ default:
+ DBUG_ASSERT(0);
+ break;
+ }
+
+ break;
+ }
default:
DBUG_ASSERT(0);
break;
@@ -1656,15 +1830,12 @@ bool mysql_write_frm(ALTER_PARTITION_PARAM_TYPE *lpt, uint flags)
strxmov(shadow_frm_name, shadow_path, reg_ext, NullS);
if (flags & WFRM_WRITE_SHADOW)
{
- if (mysql_prepare_create_table(lpt->thd, lpt->create_info,
- lpt->alter_info,
+ if (mysql_prepare_create_table(lpt->thd, lpt->create_info, lpt->alter_info,
/*tmp_table*/ 1,
- &lpt->db_options,
- lpt->table->file,
- &lpt->key_info_buffer,
- &lpt->key_count,
- /*select_field_count*/ 0))
- {
+ &lpt->db_options, lpt->table->file,
+ &lpt->key_info_buffer, &lpt->key_count,
+ C_ALTER_TABLE))
+ {
DBUG_RETURN(TRUE);
}
#ifdef WITH_PARTITION_STORAGE_ENGINE
@@ -2213,7 +2384,7 @@ int mysql_rm_table_no_locks(THD *thd, TABLE_LIST *tables, bool if_exists,
goto err;
}
close_all_tables_for_name(thd, table->table->s,
- HA_EXTRA_PREPARE_FOR_DROP);
+ HA_EXTRA_PREPARE_FOR_DROP, NULL);
table->table= 0;
}
@@ -2277,7 +2448,7 @@ int mysql_rm_table_no_locks(THD *thd, TABLE_LIST *tables, bool if_exists,
. ./sql/datadict.cc +32 /Alfranio - TODO: We need to test this.
*/
if (if_exists)
- push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
ER_BAD_TABLE_ERROR, ER(ER_BAD_TABLE_ERROR),
table->table_name);
else
@@ -2446,22 +2617,20 @@ end:
}
-/*
+/**
Quickly remove a table.
- SYNOPSIS
- quick_rm_table()
- base The handlerton handle.
- db The database name.
- table_name The table name.
- flags flags for build_table_filename().
+ @param thd Thread context.
+ @param base The handlerton handle.
+ @param db The database name.
+ @param table_name The table name.
+ @param flags Flags for build_table_filename() as well as describing
+ if handler files / .FRM should be deleted as well.
- RETURN
- 0 OK
- != 0 Error
+ @return False in case of success, True otherwise.
*/
-bool quick_rm_table(handlerton *base,const char *db,
+bool quick_rm_table(THD *thd, handlerton *base, const char *db,
const char *table_name, uint flags)
{
char path[FN_REFLEN + 1];
@@ -2473,11 +2642,20 @@ bool quick_rm_table(handlerton *base,const char *db,
if (mysql_file_delete(key_file_frm, path, MYF(0)))
error= 1; /* purecov: inspected */
path[path_length - reg_ext_length]= '\0'; // Remove reg_ext
- if (!(flags & FRM_ONLY))
+ if (flags & NO_HA_TABLE)
+ {
+ handler *file= get_new_handler((TABLE_SHARE*) 0, thd->mem_root, base);
+ if (!file)
+ DBUG_RETURN(true);
+ (void) file->ha_create_handler_files(path, NULL, CHF_DELETE_FLAG, NULL);
+ delete file;
+ }
+ if (!(flags & (FRM_ONLY|NO_HA_TABLE)))
error|= ha_delete_table(current_thd, base, path, db, table_name, 0);
DBUG_RETURN(error);
}
+
/*
Sort keys in the following order:
- PRIMARY KEY
@@ -2571,7 +2749,7 @@ bool check_duplicates_in_interval(const char *set_or_name,
name, err.ptr(), set_or_name);
return 1;
}
- push_warning_printf(thd,MYSQL_ERROR::WARN_LEVEL_NOTE,
+ push_warning_printf(thd,Sql_condition::WARN_LEVEL_NOTE,
ER_DUPLICATED_VALUE_IN_TYPE,
ER(ER_DUPLICATED_VALUE_IN_TYPE),
name, err.ptr(), set_or_name);
@@ -2813,21 +2991,6 @@ CHARSET_INFO* get_sql_field_charset(Create_field *sql_field,
}
-bool check_duplicate_warning(THD *thd, char *msg, ulong length)
-{
- List_iterator_fast<MYSQL_ERROR> it(thd->warning_info->warn_list());
- MYSQL_ERROR *err;
- while ((err= it++))
- {
- if (strncmp(msg, err->get_message_text(), length) == 0)
- {
- return true;
- }
- }
- return false;
-}
-
-
/**
Modifies the first column definition whose SQL type is TIMESTAMP
by adding the features DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP.
@@ -2894,7 +3057,7 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info,
bool tmp_table,
uint *db_options,
handler *file, KEY **key_info_buffer,
- uint *key_count, int select_field_count)
+ uint *key_count, int create_table_mode)
{
const char *key_name;
Create_field *sql_field,*dup_field;
@@ -2907,6 +3070,7 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info,
List_iterator<Create_field> it(alter_info->create_list);
List_iterator<Create_field> it2(alter_info->create_list);
uint total_uneven_bit_length= 0;
+ int select_field_count= C_CREATE_SELECT(create_table_mode);
DBUG_ENTER("mysql_prepare_create_table");
select_field_pos= alter_info->create_list.elements - select_field_count;
@@ -2924,6 +3088,7 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info,
executing a prepared statement for the second time.
*/
sql_field->length= sql_field->char_length;
+ /* Set field charset. */
save_cs= sql_field->charset= get_sql_field_charset(sql_field,
create_info);
if ((sql_field->flags & BINCMP_FLAG) &&
@@ -3626,7 +3791,7 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info,
char warn_buff[MYSQL_ERRMSG_SIZE];
my_snprintf(warn_buff, sizeof(warn_buff), ER(ER_TOO_LONG_KEY),
length);
- push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
ER_TOO_LONG_KEY, warn_buff);
/* Align key length to multibyte char boundary */
length-= length % sql_field->charset->mbmaxlen;
@@ -3673,7 +3838,7 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info,
char warn_buff[MYSQL_ERRMSG_SIZE];
my_snprintf(warn_buff, sizeof(warn_buff), ER(ER_TOO_LONG_KEY),
length);
- push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
ER_TOO_LONG_KEY, warn_buff);
/* Align key length to multibyte char boundary */
length-= length % sql_field->charset->mbmaxlen;
@@ -3763,8 +3928,8 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info,
my_snprintf(warn_buff, sizeof(warn_buff), ER(ER_TOO_LONG_INDEX_COMMENT),
key_info->name, static_cast<ulong>(INDEX_COMMENT_MAXLEN));
/* do not push duplicate warnings */
- if (!check_duplicate_warning(thd, warn_buff, strlen(warn_buff)))
- push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
+ if (!thd->get_stmt_da()->has_sql_condition(warn_buff, strlen(warn_buff)))
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
ER_TOO_LONG_INDEX_COMMENT, warn_buff);
key->key_create_info.comment.length= tmp_len;
@@ -3903,7 +4068,7 @@ static bool prepare_blob_field(THD *thd, Create_field *sql_field)
my_snprintf(warn_buff, sizeof(warn_buff), ER(ER_AUTO_CONVERT), sql_field->field_name,
(sql_field->charset == &my_charset_bin) ? "VARBINARY" : "VARCHAR",
(sql_field->charset == &my_charset_bin) ? "BLOB" : "TEXT");
- push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, ER_AUTO_CONVERT,
+ push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, ER_AUTO_CONVERT,
warn_buff);
}
@@ -3974,47 +4139,6 @@ void sp_prepare_create_field(THD *thd, Create_field *sql_field)
}
-#ifdef WITH_PARTITION_STORAGE_ENGINE
-/**
- Auxiliary function which allows to check if freshly created .FRM
- file for table can be opened.
-
- @retval FALSE - Success.
- @retval TRUE - Failure.
-*/
-
-static bool check_if_created_table_can_be_opened(THD *thd,
- const char *path,
- const char *db,
- const char *table_name,
- HA_CREATE_INFO *create_info,
- handler *file)
-{
- TABLE table;
- TABLE_SHARE share;
- bool result;
-
- /*
- It is impossible to open definition of partitioned table without .par file.
- */
- if (file->ha_create_handler_files(path, NULL, CHF_CREATE_FLAG, create_info))
- return TRUE;
-
- init_tmp_table_share(thd, &share, db, 0, table_name, path);
-
- result= (open_table_def(thd, &share, 0) ||
- open_table_from_share(thd, &share, "", 0, (uint) READ_ALL,
- 0, &table, TRUE));
- if (! result)
- (void) closefrm(&table, 0);
-
- free_table_share(&share);
- (void) file->ha_create_handler_files(path, NULL, CHF_DELETE_FLAG, create_info);
- return result;
-}
-#endif
-
-
/**
Check that there is no frm file for given table
@@ -4077,57 +4201,59 @@ bool check_table_file_presence(char *old_path,
}
-/*
+/**
Create a table
- SYNOPSIS
- mysql_create_table_no_lock()
- thd Thread object
- db Database
- table_name Table name
- create_info Create information (like MAX_ROWS)
- fields List of fields to create
- keys List of keys to create
- internal_tmp_table Set to 1 if this is an internal temporary table
- (From ALTER TABLE)
- select_field_count
- is_trans identifies the type of engine where the table
- was created: either trans or non-trans.
-
- DESCRIPTION
- If one creates a temporary table, this is automatically opened
-
- Note that this function assumes that caller already have taken
- exclusive metadata lock on table being created or used some other
- way to ensure that concurrent operations won't intervene.
- mysql_create_table() is a wrapper that can be used for this.
-
- no_log is needed for the case of CREATE ... SELECT,
- as the logging will be done later in sql_insert.cc
- select_field_count is also used for CREATE ... SELECT,
- and must be zero for standard create of table.
-
- RETURN VALUES
- FALSE OK
- TRUE error
+ @param thd Thread object
+ @param db Database
+ @param table_name Table name
+ @param path Path to table (i.e. to its .FRM file without
+ the extension).
+ @param create_info Create information (like MAX_ROWS)
+ @param alter_info Description of fields and keys for new table
+ @param internal_tmp_table Set to true if this is an internal temporary table
+ (From ALTER TABLE)
+ @param select_field_count Number of fields coming from SELECT part of
+ CREATE TABLE ... SELECT statement. Must be zero
+ for standard create of table.
+ @param no_ha_table Indicates that only .FRM file (and PAR file if table
+ is partitioned) needs to be created and not a table
+ in the storage engine.
+ @param[out] is_trans Identifies the type of engine where the table
+ was created: either trans or non-trans.
+ @param[out] key_info Array of KEY objects describing keys in table
+ which was created.
+ @param[out] key_count Number of keys in table which was created.
+
+ If one creates a temporary table, this is automatically opened
+
+ Note that this function assumes that caller already have taken
+ exclusive metadata lock on table being created or used some other
+ way to ensure that concurrent operations won't intervene.
+ mysql_create_table() is a wrapper that can be used for this.
+
+ @retval false OK
+ @retval true error
*/
-bool mysql_create_table_no_lock(THD *thd,
- const char *db, const char *table_name,
- HA_CREATE_INFO *create_info,
- Alter_info *alter_info,
- bool internal_tmp_table,
- uint select_field_count,
- bool *is_trans)
+static
+bool create_table_impl(THD *thd,
+ const char *db, const char *table_name,
+ const char *path,
+ HA_CREATE_INFO *create_info,
+ Alter_info *alter_info,
+ bool internal_tmp_table,
+ bool no_ha_table,
+ bool *is_trans,
+ KEY **key_info,
+ uint *key_count,
+ int create_table_mode)
{
- char path[FN_REFLEN + 1];
- uint path_length;
const char *alias;
- uint db_options, key_count;
- KEY *key_info_buffer;
+ uint db_options;
handler *file;
bool error= TRUE;
- DBUG_ENTER("mysql_create_table_no_lock");
+ DBUG_ENTER("create_table_impl");
DBUG_PRINT("enter", ("db: '%s' table: '%s' tmp: %d",
db, table_name, internal_tmp_table));
@@ -4145,10 +4271,8 @@ bool mysql_create_table_no_lock(THD *thd,
set_table_default_charset(thd, create_info, (char*) db);
db_options= create_info->table_options;
- if (!create_info->frm_only &&
- create_info->row_type != ROW_TYPE_FIXED &&
- create_info->row_type != ROW_TYPE_DEFAULT)
- db_options|= HA_OPTION_PACK_RECORD;
+ if (create_info->row_type == ROW_TYPE_DYNAMIC)
+ db_options|=HA_OPTION_PACK_RECORD;
alias= table_case_name(create_info, table_name);
if (!(file= get_new_handler((TABLE_SHARE*) 0, thd->mem_root,
create_info->db_type)))
@@ -4186,11 +4310,7 @@ bool mysql_create_table_no_lock(THD *thd,
partitions also in the call to check_partition_info. We transport
this information in the default_db_type variable, it is either
DB_TYPE_DEFAULT or the engine set in the ALTER TABLE command.
-
- Check that we don't use foreign keys in the table since it won't
- work even with InnoDB beneath it.
*/
- List_iterator<Key> key_iterator(alter_info->key_list);
Key *key;
handlerton *part_engine_type= create_info->db_type;
char *part_syntax_buf;
@@ -4201,15 +4321,6 @@ bool mysql_create_table_no_lock(THD *thd,
my_error(ER_PARTITION_NO_TEMPORARY, MYF(0));
goto err;
}
- while ((key= key_iterator++))
- {
- if (key->type == Key::FOREIGN_KEY &&
- !part_info->is_auto_partitioned)
- {
- my_error(ER_FOREIGN_KEY_ON_PARTITIONED, MYF(0));
- goto err;
- }
- }
if ((part_engine_type == partition_hton) &&
part_info->default_engine_type)
{
@@ -4315,58 +4426,64 @@ bool mysql_create_table_no_lock(THD *thd,
DBUG_RETURN(TRUE);
}
}
+ /*
+ Unless table's storage engine supports partitioning natively
+ don't allow foreign keys on partitioned tables (they won't
+ work work even with InnoDB beneath of partitioning engine).
+ If storage engine handles partitioning natively (like NDB)
+ foreign keys support is possible, so we let the engine decide.
+ */
+ if (create_info->db_type == partition_hton)
+ {
+ List_iterator_fast<Key> key_iterator(alter_info->key_list);
+ while ((key= key_iterator++))
+ {
+ if (key->type == Key::FOREIGN_KEY)
+ {
+ my_error(ER_FOREIGN_KEY_ON_PARTITIONED, MYF(0));
+ goto err;
+ }
+ }
+ }
}
#endif
if (mysql_prepare_create_table(thd, create_info, alter_info,
internal_tmp_table,
&db_options, file,
- &key_info_buffer, &key_count,
- select_field_count))
+ key_info, key_count,
+ create_table_mode))
goto err;
- /* Check if table exists */
if (create_info->options & HA_LEX_CREATE_TMP_TABLE)
- {
- path_length= build_tmptable_filename(thd, path, sizeof(path));
create_info->table_options|=HA_CREATE_DELAY_KEY_WRITE;
- }
- else
- {
- path_length= build_table_filename(path, sizeof(path) - 1, db, alias, reg_ext,
- internal_tmp_table ? FN_IS_TMP : 0);
- }
/* Check if table already exists */
if ((create_info->options & HA_LEX_CREATE_TMP_TABLE) &&
find_temporary_table(thd, db, table_name))
{
if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS)
- goto warn;
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_TABLE_EXISTS_ERROR, ER(ER_TABLE_EXISTS_ERROR),
+ alias);
+ error= 0;
+ goto err;
+ }
my_error(ER_TABLE_EXISTS_ERROR, MYF(0), alias);
goto err;
}
- /* Give warnings for not supported table options */
-#if defined(WITH_ARIA_STORAGE_ENGINE)
- extern handlerton *maria_hton;
- if (file->ht != maria_hton)
-#endif
- if (create_info->transactional)
- push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
- ER_ILLEGAL_HA_CREATE_OPTION,
- ER(ER_ILLEGAL_HA_CREATE_OPTION),
- file->engine_name()->str,
- "TRANSACTIONAL=1");
-
if (!internal_tmp_table && !(create_info->options & HA_LEX_CREATE_TMP_TABLE))
{
- if (check_table_file_presence(NULL, path, db, table_name, table_name,
- !(create_info->options &
- HA_LEX_CREATE_IF_NOT_EXISTS)))
+ char frm_name[FN_REFLEN+1];
+ strxnmov(frm_name, sizeof(frm_name) - 1, path, reg_ext, NullS);
+
+ if (!access(frm_name, F_OK))
{
if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS)
goto warn;
+ my_error(ER_TABLE_EXISTS_ERROR,MYF(0),table_name);
goto err;
}
/*
@@ -4414,6 +4531,7 @@ bool mysql_create_table_no_lock(THD *thd,
goto warn;
my_error(ER_TABLE_EXISTS_ERROR,MYF(0),table_name);
goto err;
+ break;
default:
DBUG_PRINT("info", ("error: %u from storage engine", retcode));
my_error(retcode, MYF(0),table_name);
@@ -4423,7 +4541,6 @@ bool mysql_create_table_no_lock(THD *thd,
THD_STAGE_INFO(thd, stage_creating_table);
-#ifdef HAVE_READLINK
{
size_t dirlen;
char dirpath[FN_REFLEN];
@@ -4470,35 +4587,37 @@ bool mysql_create_table_no_lock(THD *thd,
}
#endif /* WITH_PARTITION_STORAGE_ENGINE */
- if (!my_use_symdir || (thd->variables.sql_mode & MODE_NO_DIR_IN_CREATE))
-#endif /* HAVE_READLINK */
+ if (thd->variables.sql_mode & MODE_NO_DIR_IN_CREATE)
{
if (create_info->data_file_name)
- push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
WARN_OPTION_IGNORED, ER(WARN_OPTION_IGNORED),
"DATA DIRECTORY");
if (create_info->index_file_name)
- push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
WARN_OPTION_IGNORED, ER(WARN_OPTION_IGNORED),
"INDEX DIRECTORY");
create_info->data_file_name= create_info->index_file_name= 0;
}
create_info->table_options=db_options;
- path[path_length - reg_ext_length]= '\0'; // Remove .frm extension
+ /*
+ Create .FRM (and .PAR file for partitioned table).
+ If "no_ha_table" is false also create table in storage engine.
+ */
if (rea_create_table(thd, path, db, table_name,
create_info, alter_info->create_list,
- key_count, key_info_buffer, file))
+ *key_count, *key_info, file, no_ha_table))
goto err;
- if (create_info->options & HA_LEX_CREATE_TMP_TABLE)
+ if (!no_ha_table && create_info->options & HA_LEX_CREATE_TMP_TABLE)
{
/*
Open a table (skipping table cache) and add it into
THD::temporary_tables list.
*/
- TABLE *table= open_table_uncached(thd, path, db, table_name, TRUE);
+ TABLE *table= open_table_uncached(thd, path, db, table_name, true, true);
if (!table)
{
@@ -4512,7 +4631,7 @@ bool mysql_create_table_no_lock(THD *thd,
thd->thread_specific_used= TRUE;
}
#ifdef WITH_PARTITION_STORAGE_ENGINE
- else if (part_info && create_info->frm_only)
+ else if (part_info && no_ha_table)
{
/*
For partitioned tables we can't find some problems with table
@@ -4524,12 +4643,26 @@ bool mysql_create_table_no_lock(THD *thd,
In cases when we create .FRM without SE part we have to open
table explicitly.
*/
- if (check_if_created_table_can_be_opened(thd, path, db, table_name,
- create_info, file))
+ TABLE table;
+ TABLE_SHARE share;
+
+ init_tmp_table_share(thd, &share, db, 0, table_name, path);
+
+ bool result= (open_table_def(thd, &share, 0) ||
+ open_table_from_share(thd, &share, "", 0, (uint) READ_ALL,
+ 0, &table, true));
+ if (!result)
+ (void) closefrm(&table, 0);
+
+ free_table_share(&share);
+
+ if (result)
{
- char frm_name[FN_REFLEN];
- strxmov(frm_name, path, reg_ext, NullS);
+ char frm_name[FN_REFLEN + 1];
+ strxnmov(frm_name, sizeof(frm_name) - 1, path, reg_ext, NullS);
(void) mysql_file_delete(key_file_frm, frm_name, MYF(0));
+ (void) file->ha_create_handler_files(path, NULL, CHF_DELETE_FLAG,
+ create_info);
goto err;
}
}
@@ -4543,7 +4676,7 @@ err:
warn:
error= FALSE;
- push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
ER_TABLE_EXISTS_ERROR, ER(ER_TABLE_EXISTS_ERROR),
alias);
goto err;
@@ -4551,6 +4684,42 @@ warn:
/**
+ Simple wrapper around create_table_impl() to be used
+ in various version of CREATE TABLE statement.
+*/
+bool mysql_create_table_no_lock(THD *thd,
+ const char *db, const char *table_name,
+ HA_CREATE_INFO *create_info,
+ Alter_info *alter_info, bool *is_trans,
+ int create_table_mode)
+{
+ KEY *not_used_1;
+ uint not_used_2;
+ char path[FN_REFLEN + 1];
+
+ if (create_info->options & HA_LEX_CREATE_TMP_TABLE)
+ build_tmptable_filename(thd, path, sizeof(path));
+ else
+ {
+ int length;
+ const char *alias= table_case_name(create_info, table_name);
+ length= build_table_filename(path, sizeof(path) - 1, db, alias,
+ "", 0);
+ // Check if we hit FN_REFLEN bytes along with file extension.
+ if (length+reg_ext_length > FN_REFLEN)
+ {
+ my_error(ER_IDENT_CAUSES_TOO_LONG_PATH, MYF(0), sizeof(path)-1, path);
+ return true;
+ }
+ }
+
+ return create_table_impl(thd, db, table_name, path, create_info, alter_info,
+ false, false, is_trans,
+ &not_used_1, &not_used_2, create_table_mode);
+}
+
+
+/**
Implementation of SQLCOM_CREATE_TABLE.
Take the metadata locks (including a shared lock on the affected
@@ -4566,6 +4735,7 @@ bool mysql_create_table(THD *thd, TABLE_LIST *create_table,
{
bool result;
bool is_trans= FALSE;
+ int create_table_mode;
DBUG_ENTER("mysql_create_table");
/*
@@ -4581,23 +4751,21 @@ bool mysql_create_table(THD *thd, TABLE_LIST *create_table,
/* Got lock. */
DEBUG_SYNC(thd, "locked_table_name");
+ if (alter_info->create_list.elements || alter_info->key_list.elements)
+ create_table_mode= C_ORDINARY_CREATE;
+ else
+ create_table_mode= C_ASSISTED_DISCOVERY;
+
promote_first_timestamp_column(&alter_info->create_list);
result= mysql_create_table_no_lock(thd, create_table->db,
create_table->table_name, create_info,
- alter_info, FALSE, 0, &is_trans);
+ alter_info, &is_trans, create_table_mode);
- /*
- Don't write statement if:
- - Table creation has failed
- - Row-based logging is used and we are creating a temporary table
- Otherwise, the statement shall be binlogged.
- */
- if (!result &&
- (!thd->is_current_stmt_binlog_format_row() ||
- (thd->is_current_stmt_binlog_format_row() &&
- !(create_info->options & HA_LEX_CREATE_TMP_TABLE))))
- result= write_bin_log(thd, TRUE, thd->query(), thd->query_length(), is_trans);
+ /* In RBR we don't need to log CREATE TEMPORARY TABLE */
+ if (thd->is_current_stmt_binlog_format_row() && create_info->tmp_table())
+ DBUG_RETURN(0);
+ result= write_bin_log(thd, TRUE, thd->query(), thd->query_length(), is_trans);
end:
DBUG_RETURN(result);
}
@@ -4647,25 +4815,23 @@ make_unique_key_name(const char *field_name,KEY *start,KEY *end)
****************************************************************************/
-/*
+/**
Rename a table.
- SYNOPSIS
- mysql_rename_table()
- base The handlerton handle.
- old_db The old database name.
- old_name The old table name.
- new_db The new database name.
- new_name The new table name.
- flags flags for build_table_filename().
- FN_FROM_IS_TMP old_name is temporary.
- FN_TO_IS_TMP new_name is temporary.
- NO_FRM_RENAME Don't rename the FRM file
- but only the table in the storage engine.
-
- RETURN
- FALSE OK
- TRUE Error
+ @param base The handlerton handle.
+ @param old_db The old database name.
+ @param old_name The old table name.
+ @param new_db The new database name.
+ @param new_name The new table name.
+ @param flags flags
+ FN_FROM_IS_TMP old_name is temporary.
+ FN_TO_IS_TMP new_name is temporary.
+ NO_FRM_RENAME Don't rename the FRM file
+ but only the table in the storage engine.
+ NO_HA_TABLE Don't rename table in engine.
+
+ @return false OK
+ @return true Error
*/
bool
@@ -4680,6 +4846,7 @@ mysql_rename_table(handlerton *base, const char *old_db,
char tmp_name[SAFE_NAME_LEN+1];
handler *file;
int error=0;
+ int length;
DBUG_ENTER("mysql_rename_table");
DBUG_PRINT("enter", ("old: '%s'.'%s' new: '%s'.'%s'",
old_db, old_name, new_db, new_name));
@@ -4689,8 +4856,14 @@ mysql_rename_table(handlerton *base, const char *old_db,
build_table_filename(from, sizeof(from) - 1, old_db, old_name, "",
flags & FN_FROM_IS_TMP);
- build_table_filename(to, sizeof(to) - 1, new_db, new_name, "",
- flags & FN_TO_IS_TMP);
+ length= build_table_filename(to, sizeof(to) - 1, new_db, new_name, "",
+ flags & FN_TO_IS_TMP);
+ // Check if we hit FN_REFLEN bytes along with file extension.
+ if (length+reg_ext_length > FN_REFLEN)
+ {
+ my_error(ER_IDENT_CAUSES_TOO_LONG_PATH, MYF(0), sizeof(to)-1, to);
+ DBUG_RETURN(TRUE);
+ }
/*
If lower_case_table_names == 2 (case-preserving but case-insensitive
@@ -4713,7 +4886,13 @@ mysql_rename_table(handlerton *base, const char *old_db,
to_base= lc_to;
}
- if (!file || !(error=file->ha_rename_table(from_base, to_base)))
+ if (flags & NO_HA_TABLE)
+ {
+ if (rename_file_ext(from,to,reg_ext))
+ error= my_errno;
+ (void) file->ha_create_handler_files(to, from, CHF_RENAME_FLAG, NULL);
+ }
+ else if (!file || !(error=file->ha_rename_table(from_base, to_base)))
{
if (!(flags & NO_FRM_RENAME) && rename_file_ext(from,to,reg_ext))
{
@@ -4729,6 +4908,7 @@ mysql_rename_table(handlerton *base, const char *old_db,
else if (error)
my_error(ER_ERROR_ON_RENAME, MYF(0), from, to, error);
+
#ifdef HAVE_PSI_TABLE_INTERFACE
/*
Remove the old table share from the pfs table share array. The new table
@@ -4737,8 +4917,8 @@ mysql_rename_table(handlerton *base, const char *old_db,
if (likely(error == 0))
{
my_bool temp_table= (my_bool)is_prefix(old_name, tmp_file_prefix);
- PSI_TABLE_CALL(drop_table_share)(temp_table, old_db, strlen(old_db),
- old_name, strlen(old_name));
+ PSI_TABLE_CALL(drop_table_share)
+ (temp_table, old_db, strlen(old_db), old_name, strlen(old_name));
}
#endif
@@ -4766,6 +4946,7 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table,
{
HA_CREATE_INFO local_create_info;
Alter_info local_alter_info;
+ Alter_table_ctx local_alter_ctx; // Not used
bool res= TRUE;
bool is_trans= FALSE;
uint not_used;
@@ -4797,7 +4978,7 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table,
local_create_info.db_type= src_table->table->s->db_type();
local_create_info.row_type= src_table->table->s->row_type;
if (mysql_prepare_alter_table(thd, src_table->table, &local_create_info,
- &local_alter_info))
+ &local_alter_info, &local_alter_ctx))
goto err;
#ifdef WITH_PARTITION_STORAGE_ENGINE
/* Partition info is not handled by mysql_prepare_alter_table() call. */
@@ -4829,7 +5010,7 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table,
if ((res= mysql_create_table_no_lock(thd, table->db, table->table_name,
&local_create_info, &local_alter_info,
- FALSE, 0, &is_trans)))
+ &is_trans, C_ORDINARY_CREATE)))
goto err;
/*
@@ -4922,13 +5103,11 @@ err:
/* table_list should contain just one table */
-static int
-mysql_discard_or_import_tablespace(THD *thd,
- TABLE_LIST *table_list,
- enum tablespace_op_type tablespace_op)
+int mysql_discard_or_import_tablespace(THD *thd,
+ TABLE_LIST *table_list,
+ bool discard)
{
- TABLE *table;
- my_bool discard;
+ Alter_table_prelocking_strategy alter_prelocking_strategy;
int error;
DBUG_ENTER("mysql_discard_or_import_tablespace");
@@ -4939,21 +5118,28 @@ mysql_discard_or_import_tablespace(THD *thd,
THD_STAGE_INFO(thd, stage_discard_or_import_tablespace);
- discard= test(tablespace_op == DISCARD_TABLESPACE);
-
/*
We set this flag so that ha_innobase::open and ::external_lock() do
not complain when we lock the table
*/
thd->tablespace_op= TRUE;
- table_list->mdl_request.set_type(MDL_SHARED_WRITE);
- if (!(table=open_ltable(thd, table_list, TL_WRITE, 0)))
+ /*
+ Adjust values of table-level and metadata which was set in parser
+ for the case general ALTER TABLE.
+ */
+ table_list->mdl_request.set_type(MDL_EXCLUSIVE);
+ table_list->lock_type= TL_WRITE;
+ /* Do not open views. */
+ table_list->required_type= FRMTYPE_TABLE;
+
+ if (open_and_lock_tables(thd, table_list, FALSE, 0,
+ &alter_prelocking_strategy))
{
thd->tablespace_op=FALSE;
DBUG_RETURN(-1);
}
- error= table->file->ha_discard_or_import_tablespace(discard);
+ error= table_list->table->file->ha_discard_or_import_tablespace(discard);
THD_STAGE_INFO(thd, stage_end);
@@ -4984,321 +5170,305 @@ err:
DBUG_RETURN(0);
}
- table->file->print_error(error, MYF(0));
+ table_list->table->file->print_error(error, MYF(0));
DBUG_RETURN(-1);
}
+
/**
- @brief Check if both DROP and CREATE are present for an index in ALTER TABLE
-
- @details Checks if any index is being modified (present as both DROP INDEX
- and ADD INDEX) in the current ALTER TABLE statement. Needed for disabling
- in-place ALTER TABLE.
-
- @param table The table being altered
- @param alter_info The ALTER TABLE structure
- @return presence of index being altered
- @retval FALSE No such index
- @retval TRUE Have at least 1 index modified
+ Check if key is a candidate key, i.e. a unique index with no index
+ fields partial or nullable.
*/
-static bool
-is_index_maintenance_unique (TABLE *table, Alter_info *alter_info)
+static bool is_candidate_key(KEY *key)
{
- List_iterator<Key> key_it(alter_info->key_list);
- List_iterator<Alter_drop> drop_it(alter_info->drop_list);
- Key *key;
+ KEY_PART_INFO *key_part;
+ KEY_PART_INFO *key_part_end= key->key_part + key->user_defined_key_parts;
- while ((key= key_it++))
- {
- if (key->name.str)
- {
- Alter_drop *drop;
+ if (!(key->flags & HA_NOSAME) || (key->flags & HA_NULL_PART_KEY))
+ return false;
- drop_it.rewind();
- while ((drop= drop_it++))
- {
- if (drop->type == Alter_drop::KEY &&
- !my_strcasecmp(system_charset_info, key->name.str, drop->name))
- return TRUE;
- }
- }
+ for (key_part= key->key_part; key_part < key_part_end; key_part++)
+ {
+ if (key_part->key_part_flag & HA_PART_KEY_SEG)
+ return false;
}
- return FALSE;
-}
-
-
-/*
- SYNOPSIS
- mysql_compare_tables()
- table The original table.
- alter_info Alter options, fields and keys for the new
- table.
- create_info Create options for the new table.
- order_num Number of order list elements.
- need_copy_table OUT Result of the comparison. Undefined if error.
- Otherwise is one of:
- ALTER_TABLE_METADATA_ONLY No copy needed
- ALTER_TABLE_DATA_CHANGED Data changes,
- copy needed
- ALTER_TABLE_INDEX_CHANGED Index changes,
- copy might be needed
- key_info_buffer OUT An array of KEY structs for new indexes
- index_drop_buffer OUT An array of offsets into table->key_info.
- index_drop_count OUT The number of elements in the array.
- index_add_buffer OUT An array of offsets into key_info_buffer.
- index_add_count OUT The number of elements in the array.
- candidate_key_count OUT The number of candidate keys in original table.
- DESCRIPTION
- 'table' (first argument) contains information of the original
- table, which includes all corresponding parts that the new
- table has in arguments create_list, key_list and create_info.
+ return true;
+}
- By comparing the changes between the original and new table
- we can determine how much it has changed after ALTER TABLE
- and whether we need to make a copy of the table, or just change
- the .frm file.
- If there are no data changes, but index changes, 'index_drop_buffer'
- and/or 'index_add_buffer' are populated with offsets into
- table->key_info or key_info_buffer respectively for the indexes
- that need to be dropped and/or (re-)created.
+/**
+ Get Create_field object for newly created table by field index.
- RETURN VALUES
- TRUE The tables are not compatible; We have to do a full alter table
- FALSE The tables are compatible; We only have to modify the .frm
+ @param alter_info Alter_info describing newly created table.
+ @param idx Field index.
*/
-bool
-mysql_compare_tables(TABLE *table,
- Alter_info *alter_info,
- HA_CREATE_INFO *create_info,
- uint order_num,
- enum_alter_table_change_level *need_copy_table,
- KEY **key_info_buffer,
- uint **index_drop_buffer, uint *index_drop_count,
- uint **index_add_buffer, uint *index_add_count,
- uint *candidate_key_count)
+static Create_field *get_field_by_index(Alter_info *alter_info, uint idx)
{
- Field **f_ptr, *field;
- uint changes= 0, tmp;
- uint key_count;
- List_iterator_fast<Create_field> new_field_it, tmp_new_field_it;
- Create_field *new_field, *tmp_new_field;
- KEY_PART_INFO *key_part;
- KEY_PART_INFO *end;
- THD *thd= table->in_use;
- uint i;
- /*
- Remember if the new definition has new VARCHAR column;
- create_info->varchar will be reset in mysql_prepare_create_table.
- */
- bool varchar= create_info->varchar;
- bool not_nullable= true;
- DBUG_ENTER("mysql_compare_tables");
+ List_iterator_fast<Create_field> field_it(alter_info->create_list);
+ uint field_idx= 0;
+ Create_field *field;
- /*
- Create a copy of alter_info.
- To compare the new and old table definitions, we need to "prepare"
- the new definition - transform it from parser output to a format
- that describes the final table layout (all column defaults are
- initialized, duplicate columns are removed). This is done by
- mysql_prepare_create_table. Unfortunately,
- mysql_prepare_create_table performs its transformations
- "in-place", that is, modifies the argument. Since we would
- like to keep mysql_compare_tables() idempotent (not altering any
- of the arguments) we create a copy of alter_info here and
- pass it to mysql_prepare_create_table, then use the result
- to evaluate possibility of in-place ALTER TABLE, and then
- destroy the copy.
- */
- Alter_info tmp_alter_info(*alter_info, thd->mem_root);
- uint db_options= 0; /* not used */
+ while ((field= field_it++) && field_idx < idx)
+ { field_idx++; }
- /* Set default value for return value (to ensure it's always set) */
- *need_copy_table= ALTER_TABLE_DATA_CHANGED;
+ return field;
+}
- /* Create the prepared information. */
- if (mysql_prepare_create_table(thd, create_info,
- &tmp_alter_info,
- (table->s->tmp_table != NO_TMP_TABLE),
- &db_options,
- table->file, key_info_buffer,
- &key_count, 0))
- DBUG_RETURN(1);
- /* Allocate result buffers. */
- if (! (*index_drop_buffer=
- (uint*) thd->alloc(sizeof(uint) * table->s->keys)) ||
- ! (*index_add_buffer=
- (uint*) thd->alloc(sizeof(uint) * tmp_alter_info.key_list.elements)))
- DBUG_RETURN(1);
-
- /*
- Some very basic checks. If number of fields changes, or the
- handler, we need to run full ALTER TABLE. In the future
- new fields can be added and old dropped without copy, but
- not yet.
- Test also that engine was not given during ALTER TABLE, or
- we are force to run regular alter table (copy).
- E.g. ALTER TABLE tbl_name ENGINE=MyISAM.
+static int compare_uint(const uint *s, const uint *t)
+{
+ return (*s < *t) ? -1 : ((*s > *t) ? 1 : 0);
+}
- For the following ones we also want to run regular alter table:
- ALTER TABLE tbl_name ORDER BY ..
- ALTER TABLE tbl_name CONVERT TO CHARACTER SET ..
- At the moment we can't handle altering temporary tables without a copy.
- We also test if OPTIMIZE TABLE was given and was mapped to alter table.
- In that case we always do full copy.
+/**
+ Compare original and new versions of a table and fill Alter_inplace_info
+ describing differences between those versions.
+
+ @param thd Thread
+ @param table The original table.
+ @param varchar Indicates that new definition has new
+ VARCHAR column.
+ @param[in/out] ha_alter_info Data structure which already contains
+ basic information about create options,
+ field and keys for the new version of
+ table and which should be completed with
+ more detailed information needed for
+ in-place ALTER.
+
+ First argument 'table' contains information of the original
+ table, which includes all corresponding parts that the new
+ table has in arguments create_list, key_list and create_info.
+
+ Compare the changes between the original and new table definitions.
+ The result of this comparison is then passed to SE which determines
+ whether it can carry out these changes in-place.
+
+ Mark any changes detected in the ha_alter_flags.
+ We generally try to specify handler flags only if there are real
+ changes. But in cases when it is cumbersome to determine if some
+ attribute has really changed we might choose to set flag
+ pessimistically, for example, relying on parser output only.
+
+ If there are no data changes, but index changes, 'index_drop_buffer'
+ and/or 'index_add_buffer' are populated with offsets into
+ table->key_info or key_info_buffer respectively for the indexes
+ that need to be dropped and/or (re-)created.
+
+ Note that this function assumes that it is OK to change Alter_info
+ and HA_CREATE_INFO which it gets. It is caller who is responsible
+ for creating copies for this structures if he needs them unchanged.
+
+ @retval true error
+ @retval false success
+*/
- There was a bug prior to mysql-4.0.25. Number of null fields was
- calculated incorrectly. As a result frm and data files gets out of
- sync after in-place alter table. There is no way to determine by which
- mysql version (in 4.0 and 4.1 branches) table was created, thus we
- disable in-place alter table for all tables created by mysql versions
- prior to 5.0 branch.
- See BUG#6236.
- */
- if (table->s->fields != alter_info->create_list.elements ||
- table->s->db_type() != create_info->db_type ||
- table->s->tmp_table ||
- create_info->used_fields & HA_CREATE_USED_ENGINE ||
- create_info->used_fields & HA_CREATE_USED_CHARSET ||
- create_info->used_fields & HA_CREATE_USED_DEFAULT_CHARSET ||
- (table->s->row_type != create_info->row_type) ||
- create_info->used_fields & HA_CREATE_USED_PAGE_CHECKSUM ||
- create_info->used_fields & HA_CREATE_USED_TRANSACTIONAL ||
- create_info->used_fields & HA_CREATE_USED_PACK_KEYS ||
- create_info->used_fields & HA_CREATE_USED_MAX_ROWS ||
- (alter_info->flags & (ALTER_RECREATE | ALTER_FOREIGN_KEY)) ||
- order_num ||
- !table->s->mysql_version ||
- (table->s->frm_version < FRM_VER_TRUE_VARCHAR && varchar))
- {
- DBUG_PRINT("info", ("Basic checks -> ALTER_TABLE_DATA_CHANGED"));
- DBUG_RETURN(0);
- }
+static bool fill_alter_inplace_info(THD *thd,
+ TABLE *table,
+ bool varchar,
+ Alter_inplace_info *ha_alter_info)
+{
+ Field **f_ptr, *field;
+ List_iterator_fast<Create_field> new_field_it;
+ Create_field *new_field;
+ KEY_PART_INFO *key_part, *new_part;
+ KEY_PART_INFO *end;
+ uint candidate_key_count= 0;
+ Alter_info *alter_info= ha_alter_info->alter_info;
+ DBUG_ENTER("fill_alter_inplace_info");
- if ((create_info->fields_option_struct= (ha_field_option_struct**)
- thd->calloc(sizeof(void*) * table->s->fields)) == NULL ||
- (create_info->indexes_option_struct= (ha_index_option_struct**)
- thd->calloc(sizeof(void*) * table->s->keys)) == NULL)
- DBUG_RETURN(1);
+ /* Allocate result buffers. */
+ if (! (ha_alter_info->index_drop_buffer=
+ (KEY**) thd->alloc(sizeof(KEY*) * table->s->keys)) ||
+ ! (ha_alter_info->index_add_buffer=
+ (uint*) thd->alloc(sizeof(uint) *
+ alter_info->key_list.elements)))
+ DBUG_RETURN(true);
- tmp_new_field_it.init(tmp_alter_info.create_list);
- for (i= 0, f_ptr= table->field, tmp_new_field= tmp_new_field_it++;
- (field= *f_ptr);
- i++, f_ptr++, tmp_new_field= tmp_new_field_it++)
- {
- if (field->is_equal(tmp_new_field) == IS_EQUAL_NO &&
- table->s->tmp_table == NO_TMP_TABLE)
- (void) delete_statistics_for_column(thd, table, field);
- else if (my_strcasecmp(system_charset_info,
- field->field_name,
- tmp_new_field->field_name))
- (void) rename_column_in_stat_tables(thd, table, field,
- tmp_new_field->field_name);
- }
+ /* First we setup ha_alter_flags based on what was detected by parser. */
+ if (alter_info->flags & Alter_info::ALTER_ADD_COLUMN)
+ ha_alter_info->handler_flags|= Alter_inplace_info::ADD_COLUMN;
+ if (alter_info->flags & Alter_info::ALTER_DROP_COLUMN)
+ ha_alter_info->handler_flags|= Alter_inplace_info::DROP_COLUMN;
+ /*
+ Comparing new and old default values of column is cumbersome.
+ So instead of using such a comparison for detecting if default
+ has really changed we rely on flags set by parser to get an
+ approximate value for storage engine flag.
+ */
+ if (alter_info->flags & (Alter_info::ALTER_CHANGE_COLUMN |
+ Alter_info::ALTER_CHANGE_COLUMN_DEFAULT))
+ ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_COLUMN_DEFAULT;
+ if (alter_info->flags & Alter_info::ADD_FOREIGN_KEY)
+ ha_alter_info->handler_flags|= Alter_inplace_info::ADD_FOREIGN_KEY;
+ if (alter_info->flags & Alter_info::DROP_FOREIGN_KEY)
+ ha_alter_info->handler_flags|= Alter_inplace_info::DROP_FOREIGN_KEY;
+ if (alter_info->flags & Alter_info::ALTER_OPTIONS)
+ ha_alter_info->handler_flags|= Alter_inplace_info::CHANGE_CREATE_OPTION;
+ if (alter_info->flags & Alter_info::ALTER_RENAME)
+ ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_RENAME;
+ /* Check partition changes */
+ if (alter_info->flags & Alter_info::ALTER_ADD_PARTITION)
+ ha_alter_info->handler_flags|= Alter_inplace_info::ADD_PARTITION;
+ if (alter_info->flags & Alter_info::ALTER_DROP_PARTITION)
+ ha_alter_info->handler_flags|= Alter_inplace_info::DROP_PARTITION;
+ if (alter_info->flags & Alter_info::ALTER_PARTITION)
+ ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_PARTITION;
+ if (alter_info->flags & Alter_info::ALTER_COALESCE_PARTITION)
+ ha_alter_info->handler_flags|= Alter_inplace_info::COALESCE_PARTITION;
+ if (alter_info->flags & Alter_info::ALTER_REORGANIZE_PARTITION)
+ ha_alter_info->handler_flags|= Alter_inplace_info::REORGANIZE_PARTITION;
+ if (alter_info->flags & Alter_info::ALTER_TABLE_REORG)
+ ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_TABLE_REORG;
+ if (alter_info->flags & Alter_info::ALTER_REMOVE_PARTITIONING)
+ ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_REMOVE_PARTITIONING;
+ if (alter_info->flags & Alter_info::ALTER_ALL_PARTITION)
+ ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_ALL_PARTITION;
/*
- Use transformed info to evaluate possibility of in-place ALTER TABLE
- but use the preserved field to persist modifications.
+ If we altering table with old VARCHAR fields we will be automatically
+ upgrading VARCHAR column types.
*/
- new_field_it.init(alter_info->create_list);
- tmp_new_field_it.init(tmp_alter_info.create_list);
+ if (table->s->frm_version < FRM_VER_TRUE_VARCHAR && varchar)
+ ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_COLUMN_TYPE;
/*
- Go through fields and check if the original ones are compatible
- with new table.
+ Go through fields in old version of table and detect changes to them.
+ We don't want to rely solely on Alter_info flags for this since:
+ a) new definition of column can be fully identical to the old one
+ despite the fact that this column is mentioned in MODIFY clause.
+ b) even if new column type differs from its old column from metadata
+ point of view, it might be identical from storage engine point
+ of view (e.g. when ENUM('a','b') is changed to ENUM('a','b',c')).
+ c) flags passed to storage engine contain more detailed information
+ about nature of changes than those provided from parser.
*/
- for (i= 0, f_ptr= table->field, new_field= new_field_it++,
- tmp_new_field= tmp_new_field_it++;
- (field= *f_ptr);
- i++, f_ptr++, new_field= new_field_it++,
- tmp_new_field= tmp_new_field_it++)
+ for (f_ptr= table->field; (field= *f_ptr); f_ptr++)
{
- DBUG_ASSERT(i < table->s->fields);
- create_info->fields_option_struct[i]= tmp_new_field->option_struct;
-
- /* reset common markers of how field changed */
- field->flags&= ~(FIELD_IS_RENAMED | FIELD_IN_ADD_INDEX);
+ /* Clear marker for renamed or dropped field
+ which we are going to set later. */
+ field->flags&= ~(FIELD_IS_RENAMED | FIELD_IS_DROPPED);
- /* Make sure we have at least the default charset in use. */
- if (!new_field->charset)
- new_field->charset= create_info->default_table_charset;
-
- /* Check that NULL behavior is same for old and new fields */
- if ((tmp_new_field->flags & NOT_NULL_FLAG) !=
- (uint) (field->flags & NOT_NULL_FLAG))
+ /* Use transformed info to evaluate flags for storage engine. */
+ uint new_field_index= 0;
+ new_field_it.init(alter_info->create_list);
+ while ((new_field= new_field_it++))
{
- DBUG_PRINT("info", ("NULL behaviour difference in field '%s' -> "
- "ALTER_TABLE_DATA_CHANGED", new_field->field_name));
- DBUG_RETURN(0);
+ if (new_field->field == field)
+ break;
+ new_field_index++;
}
- /*
- Check if the altered column is computed and either
- is stored or is used in the partitioning expression.
- TODO: Mark such a column with an alter flag only if
- the defining expression has changed.
- */
- if (field->vcol_info &&
- (field->stored_in_db || field->vcol_info->is_in_partitioning_expr()))
+ if (new_field)
{
- *need_copy_table= ALTER_TABLE_DATA_CHANGED;
- DBUG_RETURN(0);
- }
+ /* Field is not dropped. Evaluate changes bitmap for it. */
- /* Don't pack rows in old tables if the user has requested this. */
- if (create_info->row_type == ROW_TYPE_DYNAMIC ||
- (tmp_new_field->flags & BLOB_FLAG) ||
- (tmp_new_field->sql_type == MYSQL_TYPE_VARCHAR &&
- create_info->row_type != ROW_TYPE_FIXED))
- create_info->table_options|= HA_OPTION_PACK_RECORD;
+ /*
+ Check if type of column has changed to some incompatible type.
+ */
+ switch (field->is_equal(new_field))
+ {
+ case IS_EQUAL_NO:
+ /* New column type is incompatible with old one. */
+ ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_COLUMN_TYPE;
+ break;
+ case IS_EQUAL_YES:
+ /*
+ New column is the same as the old one or the fully compatible with
+ it (for example, ENUM('a','b') was changed to ENUM('a','b','c')).
+ Such a change if any can ALWAYS be carried out by simply updating
+ data-dictionary without even informing storage engine.
+ No flag is set in this case.
+ */
+ break;
+ case IS_EQUAL_PACK_LENGTH:
+ /*
+ New column type differs from the old one, but has compatible packed
+ data representation. Depending on storage engine, such a change can
+ be carried out by simply updating data dictionary without changing
+ actual data (for example, VARCHAR(300) is changed to VARCHAR(400)).
+ */
+ ha_alter_info->handler_flags|= Alter_inplace_info::
+ ALTER_COLUMN_EQUAL_PACK_LENGTH;
+ break;
+ default:
+ DBUG_ASSERT(0);
+ /* Safety. */
+ ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_COLUMN_TYPE;
+ }
- /* Check if field was renamed */
- if (my_strcasecmp(system_charset_info,
- field->field_name,
- tmp_new_field->field_name))
- {
- field->flags|= FIELD_IS_RENAMED;
- if (table->s->tmp_table == NO_TMP_TABLE)
- rename_column_in_stat_tables(thd, table, field,
- tmp_new_field->field_name);
- }
+ /* Check if field was renamed */
+ if (my_strcasecmp(system_charset_info, field->field_name,
+ new_field->field_name))
+ {
+ field->flags|= FIELD_IS_RENAMED;
+ ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_COLUMN_NAME;
+ }
- /* Evaluate changes bitmap and send to check_if_incompatible_data() */
- if (!(tmp= field->is_equal(tmp_new_field)))
- {
- if (table->s->tmp_table == NO_TMP_TABLE)
+ /* Check that NULL behavior is same for old and new fields */
+ if ((new_field->flags & NOT_NULL_FLAG) !=
+ (uint) (field->flags & NOT_NULL_FLAG))
{
- KEY *key_info= table->key_info;
- for (uint i=0; i < table->s->keys; i++, key_info++)
- {
- if (field->part_of_key.is_set(i))
- {
- uint key_parts= table->actual_n_key_parts(key_info);
- for (uint j= 0; j < key_parts; j++)
- {
- if (key_info->key_part[j].fieldnr-1 == field->field_index)
- {
- (void) delete_statistics_for_index(thd, table, key_info,
- j >= key_info->user_defined_key_parts);
- break;
- }
- }
- }
- }
+ if (new_field->flags & NOT_NULL_FLAG)
+ ha_alter_info->handler_flags|=
+ Alter_inplace_info::ALTER_COLUMN_NOT_NULLABLE;
+ else
+ ha_alter_info->handler_flags|=
+ Alter_inplace_info::ALTER_COLUMN_NULLABLE;
}
- DBUG_PRINT("info", ("!field_is_equal('%s') -> ALTER_TABLE_DATA_CHANGED",
- new_field->field_name));
- DBUG_RETURN(0);
+
+ /*
+ We do not detect changes to default values in this loop.
+ See comment above for more details.
+ */
+
+ /*
+ Detect changes in column order.
+ */
+ if (field->field_index != new_field_index)
+ ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_COLUMN_ORDER;
+
+ /* Detect changes in storage type of column */
+ if (new_field->field_storage_type() != field->field_storage_type())
+ ha_alter_info->handler_flags|=
+ Alter_inplace_info::ALTER_COLUMN_STORAGE_TYPE;
+
+ /* Detect changes in column format of column */
+ if (new_field->column_format() != field->column_format())
+ ha_alter_info->handler_flags|=
+ Alter_inplace_info::ALTER_COLUMN_COLUMN_FORMAT;
+ }
+ else
+ {
+ /*
+ Field is not present in new version of table and therefore was dropped.
+ Corresponding storage engine flag should be already set.
+ */
+ DBUG_ASSERT(ha_alter_info->handler_flags & Alter_inplace_info::DROP_COLUMN);
+ field->flags|= FIELD_IS_DROPPED;
}
- changes|= tmp;
}
+#ifndef DBUG_OFF
+ new_field_it.init(alter_info->create_list);
+ while ((new_field= new_field_it++))
+ {
+ if (! new_field->field)
+ {
+ /*
+ Field is not present in old version of table and therefore was added.
+ Again corresponding storage engine flag should be already set.
+ */
+ DBUG_ASSERT(ha_alter_info->handler_flags & Alter_inplace_info::ADD_COLUMN);
+ break;
+ }
+ }
+#endif /* DBUG_OFF */
+
/*
Go through keys and check if the original ones are compatible
with new table.
@@ -5306,106 +5476,86 @@ mysql_compare_tables(TABLE *table,
KEY *table_key;
KEY *table_key_end= table->key_info + table->s->keys;
KEY *new_key;
- KEY *new_key_end= *key_info_buffer + key_count;
+ KEY *new_key_end=
+ ha_alter_info->key_info_buffer + ha_alter_info->key_count;
DBUG_PRINT("info", ("index count old: %d new: %d",
- table->s->keys, key_count));
+ table->s->keys, ha_alter_info->key_count));
+
/*
Step through all keys of the old table and search matching new keys.
*/
- *index_drop_count= 0;
- *index_add_count= 0;
- *candidate_key_count= 0;
+ ha_alter_info->index_drop_count= 0;
+ ha_alter_info->index_add_count= 0;
for (table_key= table->key_info; table_key < table_key_end; table_key++)
{
- KEY_PART_INFO *table_part;
- KEY_PART_INFO *table_part_end= (table_key->key_part +
- table_key->user_defined_key_parts);
- KEY_PART_INFO *new_part;
-
- /*
- Check if key is a candidate key, i.e. a unique index with no index
- fields nullable, then key is either already primary key or could
- be promoted to primary key if the original primary key is dropped.
- Count all candidate keys.
- */
- not_nullable= true;
- for (table_part= table_key->key_part;
- table_part < table_part_end;
- table_part++)
- {
- not_nullable= not_nullable && (! table_part->field->maybe_null());
- }
- if ((table_key->flags & HA_NOSAME) && not_nullable)
- (*candidate_key_count)++;
-
/* Search a new key with the same name. */
- for (new_key= *key_info_buffer; new_key < new_key_end; new_key++)
+ for (new_key= ha_alter_info->key_info_buffer;
+ new_key < new_key_end;
+ new_key++)
{
if (! strcmp(table_key->name, new_key->name))
break;
}
if (new_key >= new_key_end)
{
- /* Key not found. Add the offset of the key to the drop buffer. */
- (*index_drop_buffer)[(*index_drop_count)++]= table_key - table->key_info;
+ /* Key not found. Add the key to the drop buffer. */
+ ha_alter_info->index_drop_buffer
+ [ha_alter_info->index_drop_count++]=
+ table_key;
DBUG_PRINT("info", ("index dropped: '%s'", table_key->name));
continue;
}
/* Check that the key types are compatible between old and new tables. */
if ((table_key->algorithm != new_key->algorithm) ||
- ((table_key->flags & HA_KEYFLAG_MASK) !=
+ ((table_key->flags & HA_KEYFLAG_MASK) !=
(new_key->flags & HA_KEYFLAG_MASK)) ||
- (table_key->user_defined_key_parts != new_key->user_defined_key_parts))
+ (table_key->user_defined_key_parts !=
+ new_key->user_defined_key_parts))
goto index_changed;
/*
Check that the key parts remain compatible between the old and
new tables.
*/
- for (table_part= table_key->key_part, new_part= new_key->key_part;
- table_part < table_part_end;
- table_part++, new_part++)
+ end= table_key->key_part + table_key->user_defined_key_parts;
+ for (key_part= table_key->key_part, new_part= new_key->key_part;
+ key_part < end;
+ key_part++, new_part++)
{
/*
- Key definition has changed if we are using a different field or
- if the used key part length is different. We know that the fields
- did not change. Comparing field numbers is sufficient.
+ Key definition has changed if we are using a different field or
+ if the used key part length is different. It makes sense to
+ check lengths first as in case when fields differ it is likely
+ that lengths differ too and checking fields is more expensive
+ in general case.
*/
- if ((table_part->length != new_part->length) ||
- (table_part->fieldnr - 1 != new_part->fieldnr))
- goto index_changed;
+ if (key_part->length != new_part->length)
+ goto index_changed;
+
+ new_field= get_field_by_index(alter_info, new_part->fieldnr);
+
+ /*
+ For prefix keys KEY_PART_INFO::field points to cloned Field
+ object with adjusted length. So below we have to check field
+ indexes instead of simply comparing pointers to Field objects.
+ */
+ if (! new_field->field ||
+ new_field->field->field_index != key_part->fieldnr - 1)
+ goto index_changed;
}
continue;
index_changed:
- /* Key modified. Add the offset of the key to both buffers. */
- (*index_drop_buffer)[(*index_drop_count)++]= table_key - table->key_info;
- (*index_add_buffer)[(*index_add_count)++]= new_key - *key_info_buffer;
- key_part= new_key->key_part;
- end= key_part + new_key->user_defined_key_parts;
- for(; key_part != end; key_part++)
- {
- // Mark field to be part of new key
- field= table->field[key_part->fieldnr];
- field->flags|= FIELD_IN_ADD_INDEX;
- }
- if (table->s->tmp_table == NO_TMP_TABLE)
- {
- (void) delete_statistics_for_index(thd, table, table_key, FALSE);
- if ((uint) (table_key - table->key_info) == table->s->primary_key)
- {
- KEY *tab_key_info= table->key_info;
- for (uint j=0; j < table->s->keys; j++, tab_key_info++)
- {
- if (tab_key_info->user_defined_key_parts != tab_key_info->ext_key_parts)
- (void) delete_statistics_for_index(thd, table, tab_key_info,
- TRUE);
- }
- }
- }
-
+ /* Key modified. Add the key / key offset to both buffers. */
+ ha_alter_info->index_drop_buffer
+ [ha_alter_info->index_drop_count++]=
+ table_key;
+ ha_alter_info->index_add_buffer
+ [ha_alter_info->index_add_count++]=
+ new_key - ha_alter_info->key_info_buffer;
+ /* Mark all old fields which are used in newly created index. */
DBUG_PRINT("info", ("index changed: '%s'", table_key->name));
}
/*end of for (; table_key < table_key_end;) */
@@ -5413,12 +5563,12 @@ mysql_compare_tables(TABLE *table,
/*
Step through all keys of the new table and find matching old keys.
*/
- for (new_key= *key_info_buffer; new_key < new_key_end; new_key++)
+ for (new_key= ha_alter_info->key_info_buffer;
+ new_key < new_key_end;
+ new_key++)
{
/* Search an old key with the same name. */
- for (i= 0, table_key= table->key_info;
- table_key < table_key_end;
- i++, table_key++)
+ for (table_key= table->key_info; table_key < table_key_end; table_key++)
{
if (! strcmp(table_key->name, new_key->name))
break;
@@ -5426,44 +5576,307 @@ mysql_compare_tables(TABLE *table,
if (table_key >= table_key_end)
{
/* Key not found. Add the offset of the key to the add buffer. */
- (*index_add_buffer)[(*index_add_count)++]= new_key - *key_info_buffer;
- key_part= new_key->key_part;
- end= key_part + new_key->user_defined_key_parts;
- for(; key_part != end; key_part++)
+ ha_alter_info->index_add_buffer
+ [ha_alter_info->index_add_count++]=
+ new_key - ha_alter_info->key_info_buffer;
+ DBUG_PRINT("info", ("index added: '%s'", new_key->name));
+ }
+ }
+
+ /*
+ Sort index_add_buffer according to how key_info_buffer is sorted.
+ I.e. with primary keys first - see sort_keys().
+ */
+ my_qsort(ha_alter_info->index_add_buffer,
+ ha_alter_info->index_add_count,
+ sizeof(uint), (qsort_cmp) compare_uint);
+
+ /* Now let us calculate flags for storage engine API. */
+
+ /* Count all existing candidate keys. */
+ for (table_key= table->key_info; table_key < table_key_end; table_key++)
+ {
+ /*
+ Check if key is a candidate key, This key is either already primary key
+ or could be promoted to primary key if the original primary key is
+ dropped.
+ In MySQL one is allowed to create primary key with partial fields (i.e.
+ primary key which is not considered candidate). For simplicity we count
+ such key as a candidate key here.
+ */
+ if (((uint) (table_key - table->key_info) == table->s->primary_key) ||
+ is_candidate_key(table_key))
+ candidate_key_count++;
+ }
+
+ /* Figure out what kind of indexes we are dropping. */
+ KEY **dropped_key;
+ KEY **dropped_key_end= ha_alter_info->index_drop_buffer +
+ ha_alter_info->index_drop_count;
+
+ for (dropped_key= ha_alter_info->index_drop_buffer;
+ dropped_key < dropped_key_end; dropped_key++)
+ {
+ table_key= *dropped_key;
+
+ if (table_key->flags & HA_NOSAME)
+ {
+ /*
+ Unique key. Check for PRIMARY KEY. Also see comment about primary
+ and candidate keys above.
+ */
+ if ((uint) (table_key - table->key_info) == table->s->primary_key)
{
- // Mark field to be part of new key
- field= table->field[key_part->fieldnr];
- field->flags|= FIELD_IN_ADD_INDEX;
+ ha_alter_info->handler_flags|= Alter_inplace_info::DROP_PK_INDEX;
+ candidate_key_count--;
+ }
+ else
+ {
+ ha_alter_info->handler_flags|= Alter_inplace_info::DROP_UNIQUE_INDEX;
+ if (is_candidate_key(table_key))
+ candidate_key_count--;
}
- DBUG_PRINT("info", ("index added: '%s'", new_key->name));
}
else
+ ha_alter_info->handler_flags|= Alter_inplace_info::DROP_INDEX;
+ }
+
+ /* Now figure out what kind of indexes we are adding. */
+ for (uint add_key_idx= 0; add_key_idx < ha_alter_info->index_add_count; add_key_idx++)
+ {
+ new_key= ha_alter_info->key_info_buffer + ha_alter_info->index_add_buffer[add_key_idx];
+
+ if (new_key->flags & HA_NOSAME)
{
- DBUG_ASSERT(i < table->s->keys);
- create_info->indexes_option_struct[i]= new_key->option_struct;
+ bool is_pk= !my_strcasecmp(system_charset_info, new_key->name, primary_key_name);
+
+ if ((!(new_key->flags & HA_KEY_HAS_PART_KEY_SEG) &&
+ !(new_key->flags & HA_NULL_PART_KEY)) ||
+ is_pk)
+ {
+ /* Candidate key or primary key! */
+ if (candidate_key_count == 0 || is_pk)
+ ha_alter_info->handler_flags|= Alter_inplace_info::ADD_PK_INDEX;
+ else
+ ha_alter_info->handler_flags|= Alter_inplace_info::ADD_UNIQUE_INDEX;
+ candidate_key_count++;
+ }
+ else
+ {
+ ha_alter_info->handler_flags|= Alter_inplace_info::ADD_UNIQUE_INDEX;
+ }
}
+ else
+ ha_alter_info->handler_flags|= Alter_inplace_info::ADD_INDEX;
}
- /* Check if changes are compatible with current handler without a copy */
+ DBUG_RETURN(false);
+}
+
+
+/**
+ Mark fields participating in newly added indexes in TABLE object which
+ corresponds to new version of altered table.
+
+ @param ha_alter_info Alter_inplace_info describing in-place ALTER.
+ @param altered_table TABLE object for new version of TABLE in which
+ fields should be marked.
+*/
+
+static void update_altered_table(const Alter_inplace_info &ha_alter_info,
+ TABLE *altered_table)
+{
+ uint field_idx, add_key_idx;
+ KEY *key;
+ KEY_PART_INFO *end, *key_part;
+
+ /*
+ Clear marker for all fields, as we are going to set it only
+ for fields which participate in new indexes.
+ */
+ for (field_idx= 0; field_idx < altered_table->s->fields; ++field_idx)
+ altered_table->field[field_idx]->flags&= ~FIELD_IN_ADD_INDEX;
+
+ /*
+ Go through array of newly added indexes and mark fields
+ participating in them.
+ */
+ for (add_key_idx= 0; add_key_idx < ha_alter_info.index_add_count;
+ add_key_idx++)
+ {
+ key= ha_alter_info.key_info_buffer +
+ ha_alter_info.index_add_buffer[add_key_idx];
+
+ end= key->key_part + key->user_defined_key_parts;
+ for (key_part= key->key_part; key_part < end; key_part++)
+ altered_table->field[key_part->fieldnr]->flags|= FIELD_IN_ADD_INDEX;
+ }
+}
+
+
+/**
+ Compare two tables to see if their metadata are compatible.
+ One table specified by a TABLE instance, the other using Alter_info
+ and HA_CREATE_INFO.
+
+ @param[in] table The first table.
+ @param[in] alter_info Alter options, fields and keys for the
+ second table.
+ @param[in] create_info Create options for the second table.
+ @param[out] metadata_equal Result of comparison.
+
+ @retval true error
+ @retval false success
+*/
+
+bool mysql_compare_tables(TABLE *table,
+ Alter_info *alter_info,
+ HA_CREATE_INFO *create_info,
+ bool *metadata_equal)
+{
+ DBUG_ENTER("mysql_compare_tables");
+
+ uint changes= IS_EQUAL_NO;
+ uint key_count;
+ List_iterator_fast<Create_field> tmp_new_field_it;
+ THD *thd= table->in_use;
+ *metadata_equal= false;
+
+ /*
+ Create a copy of alter_info.
+ To compare definitions, we need to "prepare" the definition - transform it
+ from parser output to a format that describes the table layout (all column
+ defaults are initialized, duplicate columns are removed). This is done by
+ mysql_prepare_create_table. Unfortunately, mysql_prepare_create_table
+ performs its transformations "in-place", that is, modifies the argument.
+ Since we would like to keep mysql_compare_tables() idempotent (not altering
+ any of the arguments) we create a copy of alter_info here and pass it to
+ mysql_prepare_create_table, then use the result to compare the tables, and
+ then destroy the copy.
+ */
+ Alter_info tmp_alter_info(*alter_info, thd->mem_root);
+ uint db_options= 0; /* not used */
+ KEY *key_info_buffer= NULL;
+
+ /* Create the prepared information. */
+ int create_table_mode= table->s->tmp_table == NO_TMP_TABLE ?
+ C_ORDINARY_CREATE : C_ALTER_TABLE;
+ if (mysql_prepare_create_table(thd, create_info, &tmp_alter_info,
+ (table->s->tmp_table != NO_TMP_TABLE),
+ &db_options, table->file, &key_info_buffer,
+ &key_count, create_table_mode))
+ DBUG_RETURN(true);
+
+ /* Some very basic checks. */
+ if (table->s->fields != alter_info->create_list.elements ||
+ table->s->db_type() != create_info->db_type ||
+ table->s->tmp_table ||
+ (table->s->row_type != create_info->row_type))
+ DBUG_RETURN(false);
+
+ /* Go through fields and check if they are compatible. */
+ tmp_new_field_it.init(tmp_alter_info.create_list);
+ for (Field **f_ptr= table->field; *f_ptr; f_ptr++)
+ {
+ Field *field= *f_ptr;
+ Create_field *tmp_new_field= tmp_new_field_it++;
+
+ /* Check that NULL behavior is the same. */
+ if ((tmp_new_field->flags & NOT_NULL_FLAG) !=
+ (uint) (field->flags & NOT_NULL_FLAG))
+ DBUG_RETURN(false);
+
+ /*
+ mysql_prepare_alter_table() clears HA_OPTION_PACK_RECORD bit when
+ preparing description of existing table. In ALTER TABLE it is later
+ updated to correct value by create_table_impl() call.
+ So to get correct value of this bit in this function we have to
+ mimic behavior of create_table_impl().
+ */
+ if (create_info->row_type == ROW_TYPE_DYNAMIC ||
+ (tmp_new_field->flags & BLOB_FLAG) ||
+ (tmp_new_field->sql_type == MYSQL_TYPE_VARCHAR &&
+ create_info->row_type != ROW_TYPE_FIXED))
+ create_info->table_options|= HA_OPTION_PACK_RECORD;
+
+ /* Check if field was renamed */
+ if (my_strcasecmp(system_charset_info,
+ field->field_name,
+ tmp_new_field->field_name))
+ DBUG_RETURN(false);
+
+ /* Evaluate changes bitmap and send to check_if_incompatible_data() */
+ uint field_changes= field->is_equal(tmp_new_field);
+ if (field_changes != IS_EQUAL_YES)
+ DBUG_RETURN(false);
+
+ changes|= field_changes;
+ }
+
+ /* Check if changes are compatible with current handler. */
if (table->file->check_if_incompatible_data(create_info, changes))
+ DBUG_RETURN(false);
+
+ /* Go through keys and check if they are compatible. */
+ KEY *table_key;
+ KEY *table_key_end= table->key_info + table->s->keys;
+ KEY *new_key;
+ KEY *new_key_end= key_info_buffer + key_count;
+
+ /* Step through all keys of the first table and search matching keys. */
+ for (table_key= table->key_info; table_key < table_key_end; table_key++)
{
- DBUG_PRINT("info", ("check_if_incompatible_data() -> "
- "ALTER_TABLE_DATA_CHANGED"));
- DBUG_RETURN(0);
+ /* Search a key with the same name. */
+ for (new_key= key_info_buffer; new_key < new_key_end; new_key++)
+ {
+ if (! strcmp(table_key->name, new_key->name))
+ break;
+ }
+ if (new_key >= new_key_end)
+ DBUG_RETURN(false);
+
+ /* Check that the key types are compatible. */
+ if ((table_key->algorithm != new_key->algorithm) ||
+ ((table_key->flags & HA_KEYFLAG_MASK) !=
+ (new_key->flags & HA_KEYFLAG_MASK)) ||
+ (table_key->user_defined_key_parts !=
+ new_key->user_defined_key_parts))
+ DBUG_RETURN(false);
+
+ /* Check that the key parts remain compatible. */
+ KEY_PART_INFO *table_part;
+ KEY_PART_INFO *table_part_end= table_key->key_part + table_key->user_defined_key_parts;
+ KEY_PART_INFO *new_part;
+ for (table_part= table_key->key_part, new_part= new_key->key_part;
+ table_part < table_part_end;
+ table_part++, new_part++)
+ {
+ /*
+ Key definition is different if we are using a different field or
+ if the used key part length is different. We know that the fields
+ are equal. Comparing field numbers is sufficient.
+ */
+ if ((table_part->length != new_part->length) ||
+ (table_part->fieldnr - 1 != new_part->fieldnr))
+ DBUG_RETURN(false);
+ }
}
- if (*index_drop_count || *index_add_count)
+ /* Step through all keys of the second table and find matching keys. */
+ for (new_key= key_info_buffer; new_key < new_key_end; new_key++)
{
- DBUG_PRINT("info", ("Index dropped=%u added=%u -> "
- "ALTER_TABLE_INDEX_CHANGED",
- *index_drop_count, *index_add_count));
- *need_copy_table= ALTER_TABLE_INDEX_CHANGED;
- DBUG_RETURN(0);
+ /* Search a key with the same name. */
+ for (table_key= table->key_info; table_key < table_key_end; table_key++)
+ {
+ if (! strcmp(table_key->name, new_key->name))
+ break;
+ }
+ if (table_key >= table_key_end)
+ DBUG_RETURN(false);
}
- DBUG_PRINT("info", (" -> ALTER_TABLE_METADATA_ONLY"));
- *need_copy_table= ALTER_TABLE_METADATA_ONLY; // Tables are compatible
- DBUG_RETURN(0);
+ *metadata_equal= true; // Tables are compatible
+ DBUG_RETURN(false);
}
@@ -5484,7 +5897,7 @@ mysql_compare_tables(TABLE *table,
static
bool alter_table_manage_keys(TABLE *table, int indexes_were_disabled,
- enum enum_enable_or_disable keys_onoff)
+ Alter_info::enum_enable_or_disable keys_onoff)
{
int error= 0;
DBUG_ENTER("alter_table_manage_keys");
@@ -5492,20 +5905,20 @@ bool alter_table_manage_keys(TABLE *table, int indexes_were_disabled,
table, indexes_were_disabled, keys_onoff));
switch (keys_onoff) {
- case ENABLE:
+ case Alter_info::ENABLE:
error= table->file->ha_enable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE);
break;
- case LEAVE_AS_IS:
+ case Alter_info::LEAVE_AS_IS:
if (!indexes_were_disabled)
break;
/* fall-through: disabled indexes */
- case DISABLE:
+ case Alter_info::DISABLE:
error= table->file->ha_disable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE);
}
if (error == HA_ERR_WRONG_COMMAND)
{
- push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
+ push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_NOTE,
ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA),
table->s->table_name.str);
error= 0;
@@ -5515,6 +5928,399 @@ bool alter_table_manage_keys(TABLE *table, int indexes_were_disabled,
DBUG_RETURN(error);
}
+
+/**
+ Check if the pending ALTER TABLE operations support the in-place
+ algorithm based on restrictions in the SQL layer or given the
+ nature of the operations themselves. If in-place isn't supported,
+ it won't be necessary to check with the storage engine.
+
+ @param table The original TABLE.
+ @param create_info Information from the parsing phase about new
+ table properties.
+ @param alter_info Data related to detected changes.
+
+ @return false In-place is possible, check with storage engine.
+ @return true Incompatible operations, must use table copy.
+*/
+
+static bool is_inplace_alter_impossible(TABLE *table,
+ HA_CREATE_INFO *create_info,
+ const Alter_info *alter_info)
+{
+ DBUG_ENTER("is_inplace_alter_impossible");
+
+ /* At the moment we can't handle altering temporary tables without a copy. */
+ if (table->s->tmp_table)
+ DBUG_RETURN(true);
+
+
+ /*
+ We also test if OPTIMIZE TABLE was given and was mapped to alter table.
+ In that case we always do full copy (ALTER_RECREATE is set in this case).
+
+ For the ALTER TABLE tbl_name ORDER BY ... we also always use copy
+ algorithm. In theory, this operation can be done in-place by some
+ engine, but since a) no current engine does this and b) our current
+ API lacks infrastructure for passing information about table ordering
+ to storage engine we simply always do copy now.
+
+ ENABLE/DISABLE KEYS is a MyISAM/Heap specific operation that is
+ not supported for in-place in combination with other operations.
+ Alone, it will be done by simple_rename_or_index_change().
+ */
+ if (alter_info->flags & (Alter_info::ALTER_RECREATE |
+ Alter_info::ALTER_ORDER |
+ Alter_info::ALTER_KEYS_ONOFF))
+ DBUG_RETURN(true);
+
+ /*
+ Test also that engine was not given during ALTER TABLE, or
+ we are force to run regular alter table (copy).
+ E.g. ALTER TABLE tbl_name ENGINE=MyISAM.
+ Note that in addition to checking flag in HA_CREATE_INFO we
+ also check HA_CREATE_INFO::db_type value. This is done
+ to cover cases in which engine is changed implicitly
+ (e.g. when non-partitioned table becomes partitioned).
+
+ Note that we do copy even if the table is already using the
+ given engine. Many users and tools depend on using ENGINE
+ to force a table rebuild.
+ */
+ if (create_info->db_type != table->s->db_type() ||
+ create_info->used_fields & HA_CREATE_USED_ENGINE)
+ DBUG_RETURN(true);
+
+ /*
+ There was a bug prior to mysql-4.0.25. Number of null fields was
+ calculated incorrectly. As a result frm and data files gets out of
+ sync after fast alter table. There is no way to determine by which
+ mysql version (in 4.0 and 4.1 branches) table was created, thus we
+ disable fast alter table for all tables created by mysql versions
+ prior to 5.0 branch.
+ See BUG#6236.
+ */
+ if (!table->s->mysql_version)
+ DBUG_RETURN(true);
+
+ DBUG_RETURN(false);
+}
+
+
+/**
+ Perform in-place alter table.
+
+ @param thd Thread handle.
+ @param table_list TABLE_LIST for the table to change.
+ @param table The original TABLE.
+ @param altered_table TABLE object for new version of the table.
+ @param ha_alter_info Structure describing ALTER TABLE to be carried
+ out and serving as a storage place for data
+ used during different phases.
+ @param inplace_supported Enum describing the locking requirements.
+ @param target_mdl_request Metadata request/lock on the target table name.
+ @param alter_ctx ALTER TABLE runtime context.
+
+ @retval true Error
+ @retval false Success
+
+ @note
+ If mysql_alter_table does not need to copy the table, it is
+ either an alter table where the storage engine does not
+ need to know about the change, only the frm will change,
+ or the storage engine supports performing the alter table
+ operation directly, in-place without mysql having to copy
+ the table.
+
+ @note This function frees the TABLE object associated with the new version of
+ the table and removes the .FRM file for it in case of both success and
+ failure.
+*/
+
+static bool mysql_inplace_alter_table(THD *thd,
+ TABLE_LIST *table_list,
+ TABLE *table,
+ TABLE *altered_table,
+ Alter_inplace_info *ha_alter_info,
+ enum_alter_inplace_result inplace_supported,
+ MDL_request *target_mdl_request,
+ Alter_table_ctx *alter_ctx)
+{
+ Open_table_context ot_ctx(thd, MYSQL_OPEN_REOPEN);
+ handlerton *db_type= table->s->db_type();
+ MDL_ticket *mdl_ticket= table->mdl_ticket;
+ HA_CREATE_INFO *create_info= ha_alter_info->create_info;
+ Alter_info *alter_info= ha_alter_info->alter_info;
+ bool reopen_tables= false;
+
+ DBUG_ENTER("mysql_inplace_alter_table");
+
+ /*
+ Upgrade to EXCLUSIVE lock if:
+ - This is requested by the storage engine
+ - Or the storage engine needs exclusive lock for just the prepare
+ phase
+ - Or requested by the user
+
+ Note that we handle situation when storage engine needs exclusive
+ lock for prepare phase under LOCK TABLES in the same way as when
+ exclusive lock is required for duration of the whole statement.
+ */
+ if (inplace_supported == HA_ALTER_INPLACE_EXCLUSIVE_LOCK ||
+ ((inplace_supported == HA_ALTER_INPLACE_SHARED_LOCK_AFTER_PREPARE ||
+ inplace_supported == HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE) &&
+ (thd->locked_tables_mode == LTM_LOCK_TABLES ||
+ thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES)) ||
+ alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_EXCLUSIVE)
+ {
+ if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
+ goto cleanup;
+ /*
+ Get rid of all TABLE instances belonging to this thread
+ except one to be used for in-place ALTER TABLE.
+
+ This is mostly needed to satisfy InnoDB assumptions/asserts.
+ */
+ close_all_tables_for_name(thd, table->s,
+ alter_ctx->is_table_renamed() ?
+ HA_EXTRA_PREPARE_FOR_RENAME :
+ HA_EXTRA_NOT_USED,
+ table);
+ /*
+ If we are under LOCK TABLES we will need to reopen tables which we
+ just have closed in case of error.
+ */
+ reopen_tables= true;
+ }
+ else if (inplace_supported == HA_ALTER_INPLACE_SHARED_LOCK_AFTER_PREPARE ||
+ inplace_supported == HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE)
+ {
+ /*
+ Storage engine has requested exclusive lock only for prepare phase
+ and we are not under LOCK TABLES.
+ Don't mark TABLE_SHARE as old in this case, as this won't allow opening
+ of table by other threads during main phase of in-place ALTER TABLE.
+ */
+ if (thd->mdl_context.upgrade_shared_lock(table->mdl_ticket, MDL_EXCLUSIVE,
+ thd->variables.lock_wait_timeout))
+ goto cleanup;
+
+ tdc_remove_table(thd, TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE,
+ table->s->db.str, table->s->table_name.str,
+ false);
+ }
+
+ /*
+ Upgrade to SHARED_NO_WRITE lock if:
+ - The storage engine needs writes blocked for the whole duration
+ - Or this is requested by the user
+ Note that under LOCK TABLES, we will already have SHARED_NO_READ_WRITE.
+ */
+ if ((inplace_supported == HA_ALTER_INPLACE_SHARED_LOCK ||
+ alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_SHARED) &&
+ thd->mdl_context.upgrade_shared_lock(table->mdl_ticket,
+ MDL_SHARED_NO_WRITE,
+ thd->variables.lock_wait_timeout))
+ {
+ goto cleanup;
+ }
+
+ // It's now safe to take the table level lock.
+ if (lock_tables(thd, table_list, alter_ctx->tables_opened, 0))
+ goto cleanup;
+
+ DEBUG_SYNC(thd, "alter_table_inplace_after_lock_upgrade");
+ THD_STAGE_INFO(thd, stage_alter_inplace_prepare);
+
+ switch (inplace_supported) {
+ case HA_ALTER_ERROR:
+ case HA_ALTER_INPLACE_NOT_SUPPORTED:
+ DBUG_ASSERT(0);
+ // fall through
+ case HA_ALTER_INPLACE_NO_LOCK:
+ case HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE:
+ switch (alter_info->requested_lock) {
+ case Alter_info::ALTER_TABLE_LOCK_DEFAULT:
+ case Alter_info::ALTER_TABLE_LOCK_NONE:
+ ha_alter_info->online= true;
+ break;
+ case Alter_info::ALTER_TABLE_LOCK_SHARED:
+ case Alter_info::ALTER_TABLE_LOCK_EXCLUSIVE:
+ break;
+ }
+ break;
+ case HA_ALTER_INPLACE_EXCLUSIVE_LOCK:
+ case HA_ALTER_INPLACE_SHARED_LOCK_AFTER_PREPARE:
+ case HA_ALTER_INPLACE_SHARED_LOCK:
+ break;
+ }
+
+ if (table->file->ha_prepare_inplace_alter_table(altered_table,
+ ha_alter_info))
+ {
+ goto rollback;
+ }
+
+ /*
+ Downgrade the lock if storage engine has told us that exclusive lock was
+ necessary only for prepare phase (unless we are not under LOCK TABLES) and
+ user has not explicitly requested exclusive lock.
+ */
+ if ((inplace_supported == HA_ALTER_INPLACE_SHARED_LOCK_AFTER_PREPARE ||
+ inplace_supported == HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE) &&
+ !(thd->locked_tables_mode == LTM_LOCK_TABLES ||
+ thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES) &&
+ (alter_info->requested_lock != Alter_info::ALTER_TABLE_LOCK_EXCLUSIVE))
+ {
+ /* If storage engine or user requested shared lock downgrade to SNW. */
+ if (inplace_supported == HA_ALTER_INPLACE_SHARED_LOCK_AFTER_PREPARE ||
+ alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_SHARED)
+ table->mdl_ticket->downgrade_lock(MDL_SHARED_NO_WRITE);
+ else
+ {
+ DBUG_ASSERT(inplace_supported == HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE);
+ table->mdl_ticket->downgrade_lock(MDL_SHARED_UPGRADABLE);
+ }
+ }
+
+ DEBUG_SYNC(thd, "alter_table_inplace_after_lock_downgrade");
+ THD_STAGE_INFO(thd, stage_alter_inplace);
+
+ if (table->file->ha_inplace_alter_table(altered_table,
+ ha_alter_info))
+ {
+ goto rollback;
+ }
+
+ // Upgrade to EXCLUSIVE before commit.
+ if (wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_RENAME))
+ goto rollback;
+
+ /*
+ If we are killed after this point, we should ignore and continue.
+ We have mostly completed the operation at this point, there should
+ be no long waits left.
+ */
+
+ DBUG_EXECUTE_IF("alter_table_rollback_new_index", {
+ table->file->ha_commit_inplace_alter_table(altered_table,
+ ha_alter_info,
+ false);
+ my_error(ER_UNKNOWN_ERROR, MYF(0));
+ goto cleanup;
+ });
+
+ DEBUG_SYNC(thd, "alter_table_inplace_before_commit");
+ THD_STAGE_INFO(thd, stage_alter_inplace_commit);
+
+ if (table->file->ha_commit_inplace_alter_table(altered_table,
+ ha_alter_info,
+ true))
+ {
+ goto rollback;
+ }
+
+ close_all_tables_for_name(thd, table->s,
+ alter_ctx->is_table_renamed() ?
+ HA_EXTRA_PREPARE_FOR_RENAME :
+ HA_EXTRA_NOT_USED,
+ NULL);
+ table_list->table= table= NULL;
+ close_temporary_table(thd, altered_table, true, false);
+
+ /*
+ Replace the old .FRM with the new .FRM, but keep the old name for now.
+ Rename to the new name (if needed) will be handled separately below.
+ */
+ if (mysql_rename_table(db_type, alter_ctx->new_db, alter_ctx->tmp_name,
+ alter_ctx->db, alter_ctx->alias,
+ FN_FROM_IS_TMP | NO_HA_TABLE))
+ {
+ // Since changes were done in-place, we can't revert them.
+ (void) quick_rm_table(thd, db_type,
+ alter_ctx->new_db, alter_ctx->tmp_name,
+ FN_IS_TMP | NO_HA_TABLE);
+ DBUG_RETURN(true);
+ }
+
+ table_list->mdl_request.ticket= mdl_ticket;
+ if (open_table(thd, table_list, thd->mem_root, &ot_ctx))
+ DBUG_RETURN(true);
+
+ /*
+ Tell the handler that the changed frm is on disk and table
+ has been re-opened
+ */
+ table_list->table->file->ha_notify_table_changed();
+
+ /*
+ We might be going to reopen table down on the road, so we have to
+ restore state of the TABLE object which we used for obtaining of
+ handler object to make it usable for later reopening.
+ */
+ close_thread_table(thd, &thd->open_tables);
+ table_list->table= NULL;
+
+ // Rename altered table if requested.
+ if (alter_ctx->is_table_renamed())
+ {
+ // Remove TABLE and TABLE_SHARE for old name from TDC.
+ tdc_remove_table(thd, TDC_RT_REMOVE_ALL,
+ alter_ctx->db, alter_ctx->table_name, false);
+
+ if (mysql_rename_table(db_type, alter_ctx->db, alter_ctx->table_name,
+ alter_ctx->new_db, alter_ctx->new_alias, 0))
+ {
+ /*
+ If the rename fails we will still have a working table
+ with the old name, but with other changes applied.
+ */
+ DBUG_RETURN(true);
+ }
+ if (Table_triggers_list::change_table_name(thd,
+ alter_ctx->db,
+ alter_ctx->alias,
+ alter_ctx->table_name,
+ alter_ctx->new_db,
+ alter_ctx->new_alias))
+ {
+ /*
+ If the rename of trigger files fails, try to rename the table
+ back so we at least have matching table and trigger files.
+ */
+ (void) mysql_rename_table(db_type,
+ alter_ctx->new_db, alter_ctx->new_alias,
+ alter_ctx->db, alter_ctx->alias, 0);
+ DBUG_RETURN(true);
+ }
+ }
+
+ DBUG_RETURN(false);
+
+ rollback:
+ table->file->ha_commit_inplace_alter_table(altered_table,
+ ha_alter_info,
+ false);
+ cleanup:
+ if (reopen_tables)
+ {
+ /* Close the only table instance which is still around. */
+ close_all_tables_for_name(thd, table->s,
+ alter_ctx->is_table_renamed() ?
+ HA_EXTRA_PREPARE_FOR_RENAME :
+ HA_EXTRA_NOT_USED,
+ NULL);
+ if (thd->locked_tables_list.reopen_tables(thd))
+ thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0);
+ /* QQ; do something about metadata locks ? */
+ }
+ close_temporary_table(thd, altered_table, true, false);
+ // Delete temporary .frm/.par
+ (void) quick_rm_table(thd, create_info->db_type, alter_ctx->new_db,
+ alter_ctx->tmp_name, FN_IS_TMP | NO_HA_TABLE);
+ DBUG_RETURN(true);
+}
+
/**
maximum possible length for certain blob types.
@@ -5573,6 +6379,7 @@ blob_length_by_type(enum_field_types type)
But since ALTER might end-up doing CREATE,
this distinction is gone and we just carry
around two structures.
+ @param[in,out] alter_ctx Runtime context for ALTER TABLE.
@return
Fills various create_info members based on information retrieved
@@ -5589,7 +6396,8 @@ blob_length_by_type(enum_field_types type)
bool
mysql_prepare_alter_table(THD *thd, TABLE *table,
HA_CREATE_INFO *create_info,
- Alter_info *alter_info)
+ Alter_info *alter_info,
+ Alter_table_ctx *alter_ctx)
{
/* New column definitions are added here */
List<Create_field> new_create_list;
@@ -5667,12 +6475,6 @@ mysql_prepare_alter_table(THD *thd, TABLE *table,
if (table->s->tmp_table == NO_TMP_TABLE)
(void) delete_statistics_for_column(thd, table, field);
drop_it.remove();
- /*
- ALTER TABLE DROP COLUMN always changes table data even in cases
- when new version of the table has the same structure as the old
- one.
- */
- alter_info->change_level= ALTER_TABLE_DATA_CHANGED;
continue;
}
/* Check if field is changed */
@@ -5746,45 +6548,58 @@ mysql_prepare_alter_table(THD *thd, TABLE *table,
if ((def->sql_type == MYSQL_TYPE_DATE ||
def->sql_type == MYSQL_TYPE_NEWDATE ||
def->sql_type == MYSQL_TYPE_DATETIME) &&
- !alter_info->datetime_field &&
+ !alter_ctx->datetime_field &&
!(~def->flags & (NO_DEFAULT_VALUE_FLAG | NOT_NULL_FLAG)) &&
thd->variables.sql_mode & MODE_NO_ZERO_DATE)
{
- alter_info->datetime_field= def;
- alter_info->error_if_not_empty= TRUE;
+ alter_ctx->datetime_field= def;
+ alter_ctx->error_if_not_empty= TRUE;
}
if (!def->after)
new_create_list.push_back(def);
- else if (def->after == first_keyword)
- {
- new_create_list.push_front(def);
- /*
- Re-ordering columns in table can't be done using in-place algorithm
- as it always changes table data.
- */
- alter_info->change_level= ALTER_TABLE_DATA_CHANGED;
- }
else
{
Create_field *find;
- find_it.rewind();
- while ((find=find_it++)) // Add new columns
+ if (def->change)
{
- if (!my_strcasecmp(system_charset_info,def->after, find->field_name))
- break;
+ find_it.rewind();
+ /*
+ For columns being modified with AFTER clause we should first remove
+ these columns from the list and then add them back at their correct
+ positions.
+ */
+ while ((find=find_it++))
+ {
+ /*
+ Create_fields representing changed columns are added directly
+ from Alter_info::create_list to new_create_list. We can therefore
+ safely use pointer equality rather than name matching here.
+ This prevents removing the wrong column in case of column rename.
+ */
+ if (find == def)
+ {
+ find_it.remove();
+ break;
+ }
+ }
}
- if (!find)
+ if (def->after == first_keyword)
+ new_create_list.push_front(def);
+ else
{
- my_error(ER_BAD_FIELD_ERROR, MYF(0), def->after,
- table->s->table_name.str);
- goto err;
+ find_it.rewind();
+ while ((find=find_it++))
+ {
+ if (!my_strcasecmp(system_charset_info, def->after, find->field_name))
+ break;
+ }
+ if (!find)
+ {
+ my_error(ER_BAD_FIELD_ERROR, MYF(0), def->after, table->s->table_name.str);
+ goto err;
+ }
+ find_it.after(def); // Put column after this
}
- find_it.after(def); // Put element after this
- /*
- Re-ordering columns in table can't be done using in-place algorithm
- as it always changes table data.
- */
- alter_info->change_level= ALTER_TABLE_DATA_CHANGED;
}
}
if (alter_info->alter_list.elements)
@@ -5826,7 +6641,8 @@ mysql_prepare_alter_table(THD *thd, TABLE *table,
KEY *tab_key_info= table->key_info;
for (uint j=0; j < table->s->keys; j++, tab_key_info++)
{
- if (tab_key_info->user_defined_key_parts != tab_key_info->ext_key_parts)
+ if (tab_key_info->user_defined_key_parts !=
+ tab_key_info->ext_key_parts)
(void) delete_statistics_for_index(thd, table, tab_key_info,
TRUE);
}
@@ -5973,9 +6789,20 @@ mysql_prepare_alter_table(THD *thd, TABLE *table,
if (alter_info->drop_list.elements)
{
- my_error(ER_CANT_DROP_FIELD_OR_KEY, MYF(0),
- alter_info->drop_list.head()->name);
- goto err;
+ Alter_drop *drop;
+ drop_it.rewind();
+ while ((drop=drop_it++)) {
+ switch (drop->type) {
+ case Alter_drop::KEY:
+ case Alter_drop::COLUMN:
+ my_error(ER_CANT_DROP_FIELD_OR_KEY, MYF(0),
+ alter_info->drop_list.head()->name);
+ goto err;
+ case Alter_drop::FOREIGN_KEY:
+ // Leave the DROP FOREIGN KEY names in the alter_info->drop_list.
+ break;
+ }
+ }
}
if (alter_info->alter_list.elements)
{
@@ -6015,90 +6842,492 @@ err:
}
-/*
- Alter table
+/**
+ Get Create_field object for newly created table by its name
+ in the old version of table.
- SYNOPSIS
- mysql_alter_table()
- thd Thread handle
- new_db If there is a RENAME clause
- new_name If there is a RENAME clause
- create_info Information from the parsing phase about new
- table properties.
- table_list The table to change.
- alter_info Lists of fields, keys to be changed, added
- or dropped.
- order_num How many ORDER BY fields has been specified.
- order List of fields to ORDER BY.
- ignore Whether we have ALTER IGNORE TABLE
- require_online Give an error if we can't do operation online
+ @param alter_info Alter_info describing newly created table.
+ @param old_name Name of field in old table.
- DESCRIPTION
- This is a veery long function and is everything but the kitchen sink :)
- It is used to alter a table and not only by ALTER TABLE but also
- CREATE|DROP INDEX are mapped on this function.
-
- When the ALTER TABLE statement just does a RENAME or ENABLE|DISABLE KEYS,
- or both, then this function short cuts its operation by renaming
- the table and/or enabling/disabling the keys. In this case, the FRM is
- not changed, directly by mysql_alter_table. However, if there is a
- RENAME + change of a field, or an index, the short cut is not used.
- See how `create_list` is used to generate the new FRM regarding the
- structure of the fields. The same is done for the indices of the table.
-
- Important is the fact, that this function tries to do as little work as
- possible, by finding out whether a intermediate table is needed to copy
- data into and when finishing the altering to use it as the original table.
- For this reason the function mysql_compare_tables() is called, which decides
- based on all kind of data how similar are the new and the original
- tables.
+ @returns Pointer to Create_field object, NULL - if field is
+ not present in new version of table.
+*/
- RETURN VALUES
- FALSE OK
- TRUE Error
+static Create_field *get_field_by_old_name(Alter_info *alter_info,
+ const char *old_name)
+{
+ List_iterator_fast<Create_field> new_field_it(alter_info->create_list);
+ Create_field *new_field;
+
+ while ((new_field= new_field_it++))
+ {
+ if (new_field->field &&
+ (my_strcasecmp(system_charset_info,
+ new_field->field->field_name,
+ old_name) == 0))
+ break;
+ }
+ return new_field;
+}
+
+
+/** Type of change to foreign key column, */
+
+enum fk_column_change_type
+{
+ FK_COLUMN_NO_CHANGE, FK_COLUMN_DATA_CHANGE,
+ FK_COLUMN_RENAMED, FK_COLUMN_DROPPED
+};
+
+/**
+ Check that ALTER TABLE's changes on columns of a foreign key are allowed.
+
+ @param[in] thd Thread context.
+ @param[in] alter_info Alter_info describing changes to be done
+ by ALTER TABLE.
+ @param[in] fk_columns List of columns of the foreign key to check.
+ @param[out] bad_column_name Name of field on which ALTER TABLE tries to
+ do prohibited operation.
+
+ @note This function takes into account value of @@foreign_key_checks
+ setting.
+
+ @retval FK_COLUMN_NO_CHANGE No significant changes are to be done on
+ foreign key columns.
+ @retval FK_COLUMN_DATA_CHANGE ALTER TABLE might result in value
+ change in foreign key column (and
+ foreign_key_checks is on).
+ @retval FK_COLUMN_RENAMED Foreign key column is renamed.
+ @retval FK_COLUMN_DROPPED Foreign key column is dropped.
+*/
+
+static enum fk_column_change_type
+fk_check_column_changes(THD *thd, Alter_info *alter_info,
+ List<LEX_STRING> &fk_columns,
+ const char **bad_column_name)
+{
+ List_iterator_fast<LEX_STRING> column_it(fk_columns);
+ LEX_STRING *column;
+
+ *bad_column_name= NULL;
+
+ while ((column= column_it++))
+ {
+ Create_field *new_field= get_field_by_old_name(alter_info, column->str);
+
+ if (new_field)
+ {
+ Field *old_field= new_field->field;
+
+ if (my_strcasecmp(system_charset_info, old_field->field_name,
+ new_field->field_name))
+ {
+ /*
+ Copy algorithm doesn't support proper renaming of columns in
+ the foreign key yet. At the moment we lack API which will tell
+ SE that foreign keys should be updated to use new name of column
+ like it happens in case of in-place algorithm.
+ */
+ *bad_column_name= column->str;
+ return FK_COLUMN_RENAMED;
+ }
+
+ if ((old_field->is_equal(new_field) == IS_EQUAL_NO) ||
+ ((new_field->flags & NOT_NULL_FLAG) &&
+ !(old_field->flags & NOT_NULL_FLAG)))
+ {
+ if (!(thd->variables.option_bits & OPTION_NO_FOREIGN_KEY_CHECKS))
+ {
+ /*
+ Column in a FK has changed significantly. Unless
+ foreign_key_checks are off we prohibit this since this
+ means values in this column might be changed by ALTER
+ and thus referential integrity might be broken,
+ */
+ *bad_column_name= column->str;
+ return FK_COLUMN_DATA_CHANGE;
+ }
+ }
+ }
+ else
+ {
+ /*
+ Column in FK was dropped. Most likely this will break
+ integrity constraints of InnoDB data-dictionary (and thus
+ InnoDB will emit an error), so we prohibit this right away
+ even if foreign_key_checks are off.
+ This also includes a rare case when another field replaces
+ field being dropped since it is easy to break referential
+ integrity in this case.
+ */
+ *bad_column_name= column->str;
+ return FK_COLUMN_DROPPED;
+ }
+ }
+
+ return FK_COLUMN_NO_CHANGE;
+}
+
+
+/**
+ Check if ALTER TABLE we are about to execute using COPY algorithm
+ is not supported as it might break referential integrity.
+
+ @note If foreign_key_checks is disabled (=0), we allow to break
+ referential integrity. But we still disallow some operations
+ like dropping or renaming columns in foreign key since they
+ are likely to break consistency of InnoDB data-dictionary
+ and thus will end-up in error anyway.
+
+ @param[in] thd Thread context.
+ @param[in] table Table to be altered.
+ @param[in] alter_info Lists of fields, keys to be changed, added
+ or dropped.
+ @param[out] alter_ctx ALTER TABLE runtime context.
+ Alter_table_ctx::fk_error_if_delete flag
+ is set if deletion during alter can break
+ foreign key integrity.
+
+ @retval false Success.
+ @retval true Error, ALTER - tries to do change which is not compatible
+ with foreign key definitions on the table.
+*/
+
+static bool fk_prepare_copy_alter_table(THD *thd, TABLE *table,
+ Alter_info *alter_info,
+ Alter_table_ctx *alter_ctx)
+{
+ List <FOREIGN_KEY_INFO> fk_parent_key_list;
+ List <FOREIGN_KEY_INFO> fk_child_key_list;
+ FOREIGN_KEY_INFO *f_key;
+
+ DBUG_ENTER("fk_prepare_copy_alter_table");
+
+ table->file->get_parent_foreign_key_list(thd, &fk_parent_key_list);
+
+ /* OOM when building list. */
+ if (thd->is_error())
+ DBUG_RETURN(true);
+
+ /*
+ Remove from the list all foreign keys in which table participates as
+ parent which are to be dropped by this ALTER TABLE. This is possible
+ when a foreign key has the same table as child and parent.
+ */
+ List_iterator<FOREIGN_KEY_INFO> fk_parent_key_it(fk_parent_key_list);
+
+ while ((f_key= fk_parent_key_it++))
+ {
+ Alter_drop *drop;
+ List_iterator_fast<Alter_drop> drop_it(alter_info->drop_list);
+
+ while ((drop= drop_it++))
+ {
+ /*
+ InnoDB treats foreign key names in case-insensitive fashion.
+ So we do it here too. For database and table name type of
+ comparison used depends on lower-case-table-names setting.
+ For l_c_t_n = 0 we use case-sensitive comparison, for
+ l_c_t_n > 0 modes case-insensitive comparison is used.
+ */
+ if ((drop->type == Alter_drop::FOREIGN_KEY) &&
+ (my_strcasecmp(system_charset_info, f_key->foreign_id->str,
+ drop->name) == 0) &&
+ (my_strcasecmp(table_alias_charset, f_key->foreign_db->str,
+ table->s->db.str) == 0) &&
+ (my_strcasecmp(table_alias_charset, f_key->foreign_table->str,
+ table->s->table_name.str) == 0))
+ fk_parent_key_it.remove();
+ }
+ }
+
+ /*
+ If there are FKs in which this table is parent which were not
+ dropped we need to prevent ALTER deleting rows from the table,
+ as it might break referential integrity. OTOH it is OK to do
+ so if foreign_key_checks are disabled.
+ */
+ if (!fk_parent_key_list.is_empty() &&
+ !(thd->variables.option_bits & OPTION_NO_FOREIGN_KEY_CHECKS))
+ alter_ctx->set_fk_error_if_delete_row(fk_parent_key_list.head());
+
+ fk_parent_key_it.rewind();
+ while ((f_key= fk_parent_key_it++))
+ {
+ enum fk_column_change_type changes;
+ const char *bad_column_name;
+
+ changes= fk_check_column_changes(thd, alter_info,
+ f_key->referenced_fields,
+ &bad_column_name);
+
+ switch(changes)
+ {
+ case FK_COLUMN_NO_CHANGE:
+ /* No significant changes. We can proceed with ALTER! */
+ break;
+ case FK_COLUMN_DATA_CHANGE:
+ {
+ char buff[NAME_LEN*2+2];
+ strxnmov(buff, sizeof(buff)-1, f_key->foreign_db->str, ".",
+ f_key->foreign_table->str, NullS);
+ my_error(ER_FK_COLUMN_CANNOT_CHANGE_CHILD, MYF(0), bad_column_name,
+ f_key->foreign_id->str, buff);
+ DBUG_RETURN(true);
+ }
+ case FK_COLUMN_RENAMED:
+ my_error(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON, MYF(0),
+ "ALGORITHM=COPY",
+ ER(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_RENAME),
+ "ALGORITHM=INPLACE");
+ DBUG_RETURN(true);
+ case FK_COLUMN_DROPPED:
+ {
+ char buff[NAME_LEN*2+2];
+ strxnmov(buff, sizeof(buff)-1, f_key->foreign_db->str, ".",
+ f_key->foreign_table->str, NullS);
+ my_error(ER_FK_COLUMN_CANNOT_DROP_CHILD, MYF(0), bad_column_name,
+ f_key->foreign_id->str, buff);
+ DBUG_RETURN(true);
+ }
+ default:
+ DBUG_ASSERT(0);
+ }
+ }
+
+ table->file->get_foreign_key_list(thd, &fk_child_key_list);
+
+ /* OOM when building list. */
+ if (thd->is_error())
+ DBUG_RETURN(true);
+
+ /*
+ Remove from the list all foreign keys which are to be dropped
+ by this ALTER TABLE.
+ */
+ List_iterator<FOREIGN_KEY_INFO> fk_key_it(fk_child_key_list);
+
+ while ((f_key= fk_key_it++))
+ {
+ Alter_drop *drop;
+ List_iterator_fast<Alter_drop> drop_it(alter_info->drop_list);
+
+ while ((drop= drop_it++))
+ {
+ /* Names of foreign keys in InnoDB are case-insensitive. */
+ if ((drop->type == Alter_drop::FOREIGN_KEY) &&
+ (my_strcasecmp(system_charset_info, f_key->foreign_id->str,
+ drop->name) == 0))
+ fk_key_it.remove();
+ }
+ }
+
+ fk_key_it.rewind();
+ while ((f_key= fk_key_it++))
+ {
+ enum fk_column_change_type changes;
+ const char *bad_column_name;
+
+ changes= fk_check_column_changes(thd, alter_info,
+ f_key->foreign_fields,
+ &bad_column_name);
+
+ switch(changes)
+ {
+ case FK_COLUMN_NO_CHANGE:
+ /* No significant changes. We can proceed with ALTER! */
+ break;
+ case FK_COLUMN_DATA_CHANGE:
+ my_error(ER_FK_COLUMN_CANNOT_CHANGE, MYF(0), bad_column_name,
+ f_key->foreign_id->str);
+ DBUG_RETURN(true);
+ case FK_COLUMN_RENAMED:
+ my_error(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON, MYF(0),
+ "ALGORITHM=COPY",
+ ER(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_RENAME),
+ "ALGORITHM=INPLACE");
+ DBUG_RETURN(true);
+ case FK_COLUMN_DROPPED:
+ my_error(ER_FK_COLUMN_CANNOT_DROP, MYF(0), bad_column_name,
+ f_key->foreign_id->str);
+ DBUG_RETURN(true);
+ default:
+ DBUG_ASSERT(0);
+ }
+ }
+
+ DBUG_RETURN(false);
+}
+
+
+/**
+ Rename table and/or turn indexes on/off without touching .FRM
+
+ @param thd Thread handler
+ @param table_list TABLE_LIST for the table to change
+ @param keys_onoff ENABLE or DISABLE KEYS?
+ @param alter_ctx ALTER TABLE runtime context.
+
+ @return Operation status
+ @retval false Success
+ @retval true Failure
+*/
+
+static bool
+simple_rename_or_index_change(THD *thd, TABLE_LIST *table_list,
+ Alter_info::enum_enable_or_disable keys_onoff,
+ Alter_table_ctx *alter_ctx)
+{
+ TABLE *table= table_list->table;
+ MDL_ticket *mdl_ticket= table->mdl_ticket;
+ int error= 0;
+ DBUG_ENTER("simple_rename_or_index_change");
+
+ if (keys_onoff != Alter_info::LEAVE_AS_IS)
+ {
+ if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
+ DBUG_RETURN(true);
+
+ // It's now safe to take the table level lock.
+ if (lock_tables(thd, table_list, alter_ctx->tables_opened, 0))
+ DBUG_RETURN(true);
+
+ if (keys_onoff == Alter_info::ENABLE)
+ {
+ DEBUG_SYNC(thd,"alter_table_enable_indexes");
+ DBUG_EXECUTE_IF("sleep_alter_enable_indexes", my_sleep(6000000););
+ error= table->file->ha_enable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE);
+ }
+ else if (keys_onoff == Alter_info::DISABLE)
+ error=table->file->ha_disable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE);
+
+ if (error == HA_ERR_WRONG_COMMAND)
+ {
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA),
+ table->file->table_type(),
+ table->s->db.str, table->s->table_name.str);
+ error= 0;
+ }
+ else if (error > 0)
+ {
+ table->file->print_error(error, MYF(0));
+ error= -1;
+ }
+ }
+
+ if (!error && alter_ctx->is_table_renamed())
+ {
+ THD_STAGE_INFO(thd, stage_rename);
+ handlerton *old_db_type= table->s->db_type();
+ /*
+ Then do a 'simple' rename of the table. First we need to close all
+ instances of 'source' table.
+ Note that if wait_while_table_is_used() returns error here (i.e. if
+ this thread was killed) then it must be that previous step of
+ simple rename did nothing and therefore we can safely return
+ without additional clean-up.
+ */
+ if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
+ DBUG_RETURN(true);
+ close_all_tables_for_name(thd, table->s, HA_EXTRA_PREPARE_FOR_RENAME, NULL);
+
+ LEX_STRING old_db_name= { alter_ctx->db, strlen(alter_ctx->db) };
+ LEX_STRING old_table_name=
+ { alter_ctx->table_name, strlen(alter_ctx->table_name) };
+ LEX_STRING new_db_name= { alter_ctx->new_db, strlen(alter_ctx->new_db) };
+ LEX_STRING new_table_name=
+ { alter_ctx->new_alias, strlen(alter_ctx->new_alias) };
+ (void) rename_table_in_stat_tables(thd, &old_db_name, &old_table_name,
+ &new_db_name, &new_table_name);
+
+ if (mysql_rename_table(old_db_type, alter_ctx->db, alter_ctx->table_name,
+ alter_ctx->new_db, alter_ctx->new_alias, 0))
+ error= -1;
+ else if (Table_triggers_list::change_table_name(thd,
+ alter_ctx->db,
+ alter_ctx->alias,
+ alter_ctx->table_name,
+ alter_ctx->new_db,
+ alter_ctx->new_alias))
+ {
+ (void) mysql_rename_table(old_db_type,
+ alter_ctx->new_db, alter_ctx->new_alias,
+ alter_ctx->db, alter_ctx->table_name, 0);
+ error= -1;
+ }
+ }
+
+ if (!error)
+ {
+ error= write_bin_log(thd, TRUE, thd->query(), thd->query_length());
+ if (!error)
+ my_ok(thd);
+ }
+ table_list->table= NULL; // For query cache
+ query_cache_invalidate3(thd, table_list, 0);
+
+ if ((thd->locked_tables_mode == LTM_LOCK_TABLES ||
+ thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES))
+ {
+ /*
+ Under LOCK TABLES we should adjust meta-data locks before finishing
+ statement. Otherwise we can rely on them being released
+ along with the implicit commit.
+ */
+ if (alter_ctx->is_table_renamed())
+ thd->mdl_context.release_all_locks_for_name(mdl_ticket);
+ else
+ mdl_ticket->downgrade_lock(MDL_SHARED_NO_READ_WRITE);
+ }
+ DBUG_RETURN(error != 0);
+}
+
+
+/**
+ Alter table
+
+ @param thd Thread handle
+ @param new_db If there is a RENAME clause
+ @param new_name If there is a RENAME clause
+ @param create_info Information from the parsing phase about new
+ table properties.
+ @param table_list The table to change.
+ @param alter_info Lists of fields, keys to be changed, added
+ or dropped.
+ @param order_num How many ORDER BY fields has been specified.
+ @param order List of fields to ORDER BY.
+ @param ignore Whether we have ALTER IGNORE TABLE
+
+ @retval true Error
+ @retval false Success
+
+ This is a veery long function and is everything but the kitchen sink :)
+ It is used to alter a table and not only by ALTER TABLE but also
+ CREATE|DROP INDEX are mapped on this function.
+
+ When the ALTER TABLE statement just does a RENAME or ENABLE|DISABLE KEYS,
+ or both, then this function short cuts its operation by renaming
+ the table and/or enabling/disabling the keys. In this case, the FRM is
+ not changed, directly by mysql_alter_table. However, if there is a
+ RENAME + change of a field, or an index, the short cut is not used.
+ See how `create_list` is used to generate the new FRM regarding the
+ structure of the fields. The same is done for the indices of the table.
+
+ Altering a table can be done in two ways. The table can be modified
+ directly using an in-place algorithm, or the changes can be done using
+ an intermediate temporary table (copy). In-place is the preferred
+ algorithm as it avoids copying table data. The storage engine
+ selects which algorithm to use in check_if_supported_inplace_alter()
+ based on information about the table changes from fill_alter_inplace_info().
*/
bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
HA_CREATE_INFO *create_info,
TABLE_LIST *table_list,
Alter_info *alter_info,
- uint order_num, ORDER *order, bool ignore,
- bool require_online)
+ uint order_num, ORDER *order, bool ignore)
{
- TABLE *table, *new_table= 0;
- MDL_ticket *mdl_ticket;
- MDL_request target_mdl_request;
- int error= 0;
- char tmp_name[80],old_name[32],new_name_buff[FN_REFLEN + 1];
- char old_name_buff[FN_REFLEN + 1];
- char new_alias_buff[FN_REFLEN], *table_name, *db, *new_alias, *alias;
- char index_file[FN_REFLEN], data_file[FN_REFLEN];
- char path[FN_REFLEN + 1];
- char reg_path[FN_REFLEN+1];
- ha_rows copied,deleted;
- handlerton *old_db_type, *new_db_type, *save_old_db_type;
- enum_alter_table_change_level need_copy_table= ALTER_TABLE_METADATA_ONLY;
-#ifdef WITH_PARTITION_STORAGE_ENGINE
- TABLE *table_for_fast_alter_partition= NULL;
- bool partition_changed= FALSE;
-#endif
- bool need_lock_for_indexes __attribute__((unused)) = TRUE;
- KEY *key_info_buffer;
- uint index_drop_count= 0;
- uint *index_drop_buffer= NULL;
- uint index_add_count= 0;
- handler_add_index *add= NULL;
- bool pending_inplace_add_index= false;
- uint *index_add_buffer= NULL;
- uint candidate_key_count= 0;
- bool no_pk;
- ulong explicit_used_fields= 0;
- enum ha_extra_function extra_func= thd->locked_tables_mode
- ? HA_EXTRA_NOT_USED
- : HA_EXTRA_FORCE_REOPEN;
- LEX_STRING old_db_name= { table_list->db, table_list->db_length };
- LEX_STRING old_table_name= { table_list->table_name,
- table_list->table_name_length };
DBUG_ENTER("mysql_alter_table");
/*
@@ -6107,63 +7336,39 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
it is the case.
TODO: this design is obsolete and will be removed.
*/
- if (table_list && table_list->db && table_list->table_name)
- {
- int table_kind= 0;
+ int table_kind= check_if_log_table(table_list->db_length, table_list->db,
+ table_list->table_name_length,
+ table_list->table_name, false);
- table_kind= check_if_log_table(table_list->db_length, table_list->db,
- table_list->table_name_length,
- table_list->table_name, 0);
-
- if (table_kind)
+ if (table_kind)
+ {
+ /* Disable alter of enabled log tables */
+ if (logger.is_log_table_enabled(table_kind))
{
- /* Disable alter of enabled log tables */
- if (logger.is_log_table_enabled(table_kind))
- {
- my_error(ER_BAD_LOG_STATEMENT, MYF(0), "ALTER");
- DBUG_RETURN(TRUE);
- }
+ my_error(ER_BAD_LOG_STATEMENT, MYF(0), "ALTER");
+ DBUG_RETURN(true);
+ }
- /* Disable alter of log tables to unsupported engine */
- if ((create_info->used_fields & HA_CREATE_USED_ENGINE) &&
- (!create_info->db_type || /* unknown engine */
- !(create_info->db_type->flags & HTON_SUPPORT_LOG_TABLES)))
- {
- my_error(ER_UNSUPORTED_LOG_ENGINE, MYF(0));
- DBUG_RETURN(TRUE);
- }
+ /* Disable alter of log tables to unsupported engine */
+ if ((create_info->used_fields & HA_CREATE_USED_ENGINE) &&
+ (!create_info->db_type || /* unknown engine */
+ !(create_info->db_type->flags & HTON_SUPPORT_LOG_TABLES)))
+ {
+ my_error(ER_UNSUPORTED_LOG_ENGINE, MYF(0),
+ hton_name(create_info->db_type)->str);
+ DBUG_RETURN(true);
+ }
#ifdef WITH_PARTITION_STORAGE_ENGINE
- if (alter_info->flags & ALTER_PARTITION)
- {
- my_error(ER_WRONG_USAGE, MYF(0), "PARTITION", "log table");
- DBUG_RETURN(TRUE);
- }
-#endif
+ if (alter_info->flags & Alter_info::ALTER_PARTITION)
+ {
+ my_error(ER_WRONG_USAGE, MYF(0), "PARTITION", "log table");
+ DBUG_RETURN(true);
}
+#endif
}
- /*
- Assign variables table_name, new_name, db, new_db, path, reg_path
- to simplify further comparisions: we want to see if it's a RENAME
- later just by comparing the pointers, avoiding the need for strcmp.
- */
THD_STAGE_INFO(thd, stage_init);
- table_name=table_list->table_name;
- alias= (lower_case_table_names == 2) ? table_list->alias : table_name;
- db=table_list->db;
- if (!new_db || !my_strcasecmp(table_alias_charset, new_db, db))
- new_db= db;
- build_table_filename(reg_path, sizeof(reg_path) - 1, db, table_name, reg_ext, 0);
- build_table_filename(path, sizeof(path) - 1, db, table_name, "", 0);
-
- mysql_ha_rm_tables(thd, table_list);
-
- /* DISCARD/IMPORT TABLESPACE is always alone in an ALTER TABLE */
- if (alter_info->tablespace_op != NO_TABLESPACE_OP)
- /* Conditionally writes to binlog. */
- DBUG_RETURN(mysql_discard_or_import_tablespace(thd,table_list,
- alter_info->tablespace_op));
/*
Code below can handle only base tables so ensure that we won't open a view.
@@ -6172,20 +7377,21 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
*/
table_list->required_type= FRMTYPE_TABLE;
- Alter_table_prelocking_strategy alter_prelocking_strategy(alter_info);
+ Alter_table_prelocking_strategy alter_prelocking_strategy;
DEBUG_SYNC(thd, "alter_table_before_open_tables");
- error= open_and_lock_tables(thd, table_list, FALSE, 0,
- &alter_prelocking_strategy);
+ uint tables_opened;
+ bool error= open_tables(thd, &table_list, &tables_opened, 0,
+ &alter_prelocking_strategy);
+
+ DEBUG_SYNC(thd, "alter_opened_table");
if (error)
- {
- DBUG_RETURN(TRUE);
- }
+ DBUG_RETURN(true);
- table= table_list->table;
+ TABLE *table= table_list->table;
table->use_all_columns();
- mdl_ticket= table->mdl_ticket;
+ MDL_ticket *mdl_ticket= table->mdl_ticket;
/*
Prohibit changing of the UNION list of a non-temporary MERGE table
@@ -6199,102 +7405,73 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
(table->s->tmp_table == NO_TMP_TABLE))
{
my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0));
- DBUG_RETURN(TRUE);
+ DBUG_RETURN(true);
}
+ Alter_table_ctx alter_ctx(thd, table_list, tables_opened, new_db, new_name);
+
+ MDL_request target_mdl_request;
+
/* Check that we are not trying to rename to an existing table */
- if (new_name)
+ if (alter_ctx.is_table_renamed())
{
- DBUG_PRINT("info", ("new_db.new_name: '%s'.'%s'", new_db, new_name));
- strmov(new_name_buff,new_name);
- strmov(new_alias= new_alias_buff, new_name);
- if (lower_case_table_names)
+ if (table->s->tmp_table != NO_TMP_TABLE)
{
- if (lower_case_table_names != 2)
+ if (find_temporary_table(thd, alter_ctx.new_db, alter_ctx.new_name))
{
- my_casedn_str(files_charset_info, new_name_buff);
- new_alias= new_name; // Create lower case table name
+ my_error(ER_TABLE_EXISTS_ERROR, MYF(0), alter_ctx.new_alias);
+ DBUG_RETURN(true);
}
- my_casedn_str(files_charset_info, new_name);
}
- if (new_db == db &&
- !my_strcasecmp(table_alias_charset, new_name_buff, table_name))
+ else
{
+ MDL_request_list mdl_requests;
+ MDL_request target_db_mdl_request;
+
+ target_mdl_request.init(MDL_key::TABLE,
+ alter_ctx.new_db, alter_ctx.new_name,
+ MDL_EXCLUSIVE, MDL_TRANSACTION);
+ mdl_requests.push_front(&target_mdl_request);
+
/*
- Source and destination table names are equal: make later check
- easier.
+ If we are moving the table to a different database, we also
+ need IX lock on the database name so that the target database
+ is protected by MDL while the table is moved.
*/
- new_alias= new_name= table_name;
- }
- else
- {
- if (table->s->tmp_table != NO_TMP_TABLE)
+ if (alter_ctx.is_database_changed())
{
- if (find_temporary_table(thd,new_db,new_name_buff))
- {
- my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_name_buff);
- DBUG_RETURN(TRUE);
- }
+ target_db_mdl_request.init(MDL_key::SCHEMA, alter_ctx.new_db, "",
+ MDL_INTENTION_EXCLUSIVE,
+ MDL_TRANSACTION);
+ mdl_requests.push_front(&target_db_mdl_request);
}
- else
- {
- MDL_request_list mdl_requests;
- MDL_request target_db_mdl_request;
-
- target_mdl_request.init(MDL_key::TABLE, new_db, new_name,
- MDL_EXCLUSIVE, MDL_TRANSACTION);
- mdl_requests.push_front(&target_mdl_request);
-
- /*
- If we are moving the table to a different database, we also
- need IX lock on the database name so that the target database
- is protected by MDL while the table is moved.
- */
- if (new_db != db)
- {
- target_db_mdl_request.init(MDL_key::SCHEMA, new_db, "",
- MDL_INTENTION_EXCLUSIVE,
- MDL_TRANSACTION);
- mdl_requests.push_front(&target_db_mdl_request);
- }
- /*
- Global intention exclusive lock must have been already acquired when
- table to be altered was open, so there is no need to do it here.
- */
- DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::GLOBAL,
- "", "",
- MDL_INTENTION_EXCLUSIVE));
+ /*
+ Global intention exclusive lock must have been already acquired when
+ table to be altered was open, so there is no need to do it here.
+ */
+ DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::GLOBAL,
+ "", "",
+ MDL_INTENTION_EXCLUSIVE));
- if (thd->mdl_context.acquire_locks(&mdl_requests,
- thd->variables.lock_wait_timeout))
- DBUG_RETURN(TRUE);
+ if (thd->mdl_context.acquire_locks(&mdl_requests,
+ thd->variables.lock_wait_timeout))
+ DBUG_RETURN(true);
- DEBUG_SYNC(thd, "locked_table_name");
- /*
- Table maybe does not exist, but we got an exclusive lock
- on the name, now we can safely try to find out for sure.
- */
- build_table_filename(new_name_buff, sizeof(new_name_buff) - 1,
- new_db, new_name_buff, reg_ext, 0);
- build_table_filename(old_name_buff, sizeof(old_name_buff) - 1,
- db, table_name, reg_ext, 0);
- if (check_table_file_presence(old_name_buff, new_name_buff, new_db,
- new_name, new_alias, TRUE))
- {
- /* Table will be closed in do_command() */
- goto err;
- }
+ DEBUG_SYNC(thd, "locked_table_name");
+ /*
+ Table maybe does not exist, but we got an exclusive lock
+ on the name, now we can safely try to find out for sure.
+ */
+ if (!access(alter_ctx.get_new_filename(), F_OK))
+ {
+ /* Table will be closed in do_command() */
+ my_error(ER_TABLE_EXISTS_ERROR, MYF(0), alter_ctx.new_alias);
+ DBUG_RETURN(true);
}
}
}
- else
- {
- new_alias= (lower_case_table_names == 2) ? alias : table_name;
- new_name= table_name;
- }
- old_db_type= table->s->db_type();
if (!create_info->db_type)
{
#ifdef WITH_PARTITION_STORAGE_ENGINE
@@ -6312,451 +7489,191 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
}
else
#endif
- create_info->db_type= old_db_type;
+ create_info->db_type= table->s->db_type();
}
- if (check_engine(thd, new_db, new_name, create_info))
- goto err;
- new_db_type= create_info->db_type;
+ if (check_engine(thd, alter_ctx.new_db, alter_ctx.new_name, create_info))
+ DBUG_RETURN(true);
- if ((new_db_type != old_db_type ||
- alter_info->flags & ALTER_PARTITION) &&
+ if ((create_info->db_type != table->s->db_type() ||
+ alter_info->flags & Alter_info::ALTER_PARTITION) &&
!table->file->can_switch_engines())
{
my_error(ER_ROW_IS_REFERENCED, MYF(0));
- goto err;
+ DBUG_RETURN(true);
}
/*
- If this is an ALTER TABLE and no explicit row type specified reuse
- the table's row type.
- Note: this is the same as if the row type was specified explicitly and
- we must thus set HA_CREATE_USED_ROW_FORMAT!
+ If this is an ALTER TABLE and no explicit row type specified reuse
+ the table's row type.
+ Note : this is the same as if the row type was specified explicitly.
*/
if (create_info->row_type == ROW_TYPE_NOT_USED)
{
/* ALTER TABLE without explicit row type */
create_info->row_type= table->s->row_type;
- /*
- We have to mark the row type as used, as otherwise the engine may
- change the row format in update_create_info().
- */
- create_info->used_fields|= HA_CREATE_USED_ROW_FORMAT;
- explicit_used_fields|= HA_CREATE_USED_ROW_FORMAT;
+ }
+ else
+ {
+ /* ALTER TABLE with specific row type */
+ create_info->used_fields |= HA_CREATE_USED_ROW_FORMAT;
}
DBUG_PRINT("info", ("old type: %s new type: %s",
- ha_resolve_storage_engine_name(old_db_type),
- ha_resolve_storage_engine_name(new_db_type)));
- if (ha_check_storage_engine_flag(old_db_type, HTON_ALTER_NOT_SUPPORTED) ||
- ha_check_storage_engine_flag(new_db_type, HTON_ALTER_NOT_SUPPORTED))
+ ha_resolve_storage_engine_name(table->s->db_type()),
+ ha_resolve_storage_engine_name(create_info->db_type)));
+ if (ha_check_storage_engine_flag(table->s->db_type(), HTON_ALTER_NOT_SUPPORTED))
{
DBUG_PRINT("info", ("doesn't support alter"));
- my_error(ER_ILLEGAL_HA, MYF(0), table_name);
- goto err;
+ my_error(ER_ILLEGAL_HA, MYF(0), hton_name(table->s->db_type())->str,
+ alter_ctx.db, alter_ctx.table_name);
+ DBUG_RETURN(true);
}
-
+
+ if (ha_check_storage_engine_flag(create_info->db_type,
+ HTON_ALTER_NOT_SUPPORTED))
+ {
+ DBUG_PRINT("info", ("doesn't support alter"));
+ my_error(ER_ILLEGAL_HA, MYF(0), hton_name(create_info->db_type)->str,
+ alter_ctx.new_db, alter_ctx.new_name);
+ DBUG_RETURN(true);
+ }
+
THD_STAGE_INFO(thd, stage_setup);
- if (!(alter_info->flags & ~(ALTER_RENAME | ALTER_KEYS_ONOFF)) &&
+ if (!(alter_info->flags & ~(Alter_info::ALTER_RENAME |
+ Alter_info::ALTER_KEYS_ONOFF)) &&
+ alter_info->requested_algorithm !=
+ Alter_info::ALTER_TABLE_ALGORITHM_COPY &&
!table->s->tmp_table) // no need to touch frm
{
- switch (alter_info->keys_onoff) {
- case LEAVE_AS_IS:
- break;
- case ENABLE:
- if (wait_while_table_is_used(thd, table, extra_func))
- goto err;
- DEBUG_SYNC(thd,"alter_table_enable_indexes");
- error= table->file->ha_enable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE);
- break;
- case DISABLE:
- if (wait_while_table_is_used(thd, table, extra_func))
- goto err;
- error=table->file->ha_disable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE);
- break;
- default:
- DBUG_ASSERT(FALSE);
- error= 0;
- break;
- }
- if (error == HA_ERR_WRONG_COMMAND)
- {
- error= 0;
- push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
- ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA),
- table->alias.c_ptr());
- }
-
- if (!error && (new_name != table_name || new_db != db))
+ // This requires X-lock, no other lock levels supported.
+ if (alter_info->requested_lock != Alter_info::ALTER_TABLE_LOCK_DEFAULT &&
+ alter_info->requested_lock != Alter_info::ALTER_TABLE_LOCK_EXCLUSIVE)
{
- THD_STAGE_INFO(thd, stage_rename);
- /*
- Then do a 'simple' rename of the table. First we need to close all
- instances of 'source' table.
- Note that if wait_while_table_is_used() returns error here (i.e. if
- this thread was killed) then it must be that previous step of
- simple rename did nothing and therefore we can safely return
- without additional clean-up.
- */
- if (wait_while_table_is_used(thd, table, extra_func))
- goto err;
- close_all_tables_for_name(thd, table->s, HA_EXTRA_PREPARE_FOR_RENAME);
- /*
- Then, we want check once again that target table does not exist.
- Actually the order of these two steps does not matter since
- earlier we took exclusive metadata lock on the target table, so
- we do them in this particular order only to be consistent with 5.0,
- in which we don't take this lock and where this order really matters.
- TODO: Investigate if we need this access() check at all.
- */
- if (!access(new_name_buff,F_OK))
- {
- my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_name);
- error= -1;
- }
- else
- {
- *fn_ext(new_name)=0;
-
- LEX_STRING new_db_name= { new_db, strlen(new_db) };
- LEX_STRING new_table_name= { new_alias, strlen(new_alias) };
- (void) rename_table_in_stat_tables(thd, &old_db_name, &old_table_name,
- &new_db_name, &new_table_name);
-
- if (mysql_rename_table(old_db_type,db,table_name,new_db,new_alias, 0))
- error= -1;
- else if (Table_triggers_list::change_table_name(thd, db,
- alias, table_name,
- new_db, new_alias))
- {
- (void) mysql_rename_table(old_db_type, new_db, new_alias, db,
- table_name, 0);
- error= -1;
- }
- }
- }
-
- if (error == HA_ERR_WRONG_COMMAND)
- {
- error= 0;
- push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
- ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA),
- table->alias.c_ptr());
- }
-
- if (!error)
- {
- error= write_bin_log(thd, TRUE, thd->query(), thd->query_length());
- if (!error)
- my_ok(thd);
- }
- else if (error > 0)
- {
- table->file->print_error(error, MYF(0));
- error= -1;
- }
- table_list->table= NULL; // For query cache
- query_cache_invalidate3(thd, table_list, 0);
-
- if ((thd->locked_tables_mode == LTM_LOCK_TABLES ||
- thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES))
- {
- /*
- Under LOCK TABLES we should adjust meta-data locks before finishing
- statement. Otherwise we can rely on them being released
- along with the implicit commit.
- */
- if (new_name != table_name || new_db != db)
- thd->mdl_context.release_all_locks_for_name(mdl_ticket);
- else
- mdl_ticket->downgrade_exclusive_lock(MDL_SHARED_NO_READ_WRITE);
+ my_error(ER_ALTER_OPERATION_NOT_SUPPORTED, MYF(0),
+ "LOCK=NONE/SHARED", "LOCK=EXCLUSIVE");
+ DBUG_RETURN(true);
}
- DBUG_RETURN(error);
+ DBUG_RETURN(simple_rename_or_index_change(thd, table_list,
+ alter_info->keys_onoff,
+ &alter_ctx));
}
/* We have to do full alter table. */
#ifdef WITH_PARTITION_STORAGE_ENGINE
- if (prep_alter_part_table(thd, table, alter_info, create_info, old_db_type,
- &partition_changed,
- db, table_name, path,
- &table_for_fast_alter_partition))
- goto err;
+ bool partition_changed= false;
+ bool fast_alter_partition= false;
+ {
+ if (prep_alter_part_table(thd, table, alter_info, create_info,
+ &alter_ctx, &partition_changed,
+ &fast_alter_partition))
+ {
+ DBUG_RETURN(true);
+ }
+ }
#endif
- /*
- If the old table had partitions and we are doing ALTER TABLE ...
- engine= <new_engine>, the new table must preserve the original
- partitioning. That means that the new engine is still the
- partitioning engine, not the engine specified in the parser.
- This is discovered in prep_alter_part_table, which in such case
- updates create_info->db_type.
- Now we need to update the stack copy of create_info->db_type,
- as otherwise we won't be able to correctly move the files of the
- temporary table to the result table files.
- */
- new_db_type= create_info->db_type;
-
- if (is_index_maintenance_unique (table, alter_info))
- need_copy_table= ALTER_TABLE_DATA_CHANGED;
- if (mysql_prepare_alter_table(thd, table, create_info, alter_info))
- goto err;
-
- /* Remove markers set for update_create_info */
- create_info->used_fields&= ~explicit_used_fields;
-
- if (need_copy_table == ALTER_TABLE_METADATA_ONLY)
- need_copy_table= alter_info->change_level;
+ if (mysql_prepare_alter_table(thd, table, create_info, alter_info,
+ &alter_ctx))
+ {
+ DBUG_RETURN(true);
+ }
- set_table_default_charset(thd, create_info, db);
+ set_table_default_charset(thd, create_info, alter_ctx.db);
promote_first_timestamp_column(&alter_info->create_list);
- if (thd->variables.old_alter_table
- || (table->s->db_type() != create_info->db_type)
#ifdef WITH_PARTITION_STORAGE_ENGINE
- || partition_changed
-#endif
- )
- need_copy_table= ALTER_TABLE_DATA_CHANGED;
- else
+ if (fast_alter_partition)
{
- enum_alter_table_change_level need_copy_table_res;
- /* Check how much the tables differ. */
- if (mysql_compare_tables(table, alter_info,
- create_info, order_num,
- &need_copy_table_res,
- &key_info_buffer,
- &index_drop_buffer, &index_drop_count,
- &index_add_buffer, &index_add_count,
- &candidate_key_count))
- goto err;
-
- DBUG_EXECUTE_IF("alter_table_only_metadata_change", {
- if (need_copy_table_res != ALTER_TABLE_METADATA_ONLY)
- goto err; });
- DBUG_EXECUTE_IF("alter_table_only_index_change", {
- if (need_copy_table_res != ALTER_TABLE_INDEX_CHANGED)
- goto err; });
-
- if (need_copy_table == ALTER_TABLE_METADATA_ONLY)
- need_copy_table= need_copy_table_res;
- }
-
- /*
- If there are index changes only, try to do them in-place. "Index
- changes only" means also that the handler for the table does not
- change. The table is open and locked. The handler can be accessed.
- */
- if (need_copy_table == ALTER_TABLE_INDEX_CHANGED)
- {
- int pk_changed= 0;
- ulong alter_flags= 0;
- ulong needed_inplace_with_read_flags= 0;
- ulong needed_inplace_flags= 0;
- KEY *key;
- uint *idx_p;
- uint *idx_end_p;
-
- alter_flags= table->file->alter_table_flags(alter_info->flags);
- DBUG_PRINT("info", ("alter_flags: %lu", alter_flags));
- /* Check dropped indexes. */
- for (idx_p= index_drop_buffer, idx_end_p= idx_p + index_drop_count;
- idx_p < idx_end_p;
- idx_p++)
- {
- key= table->key_info + *idx_p;
- DBUG_PRINT("info", ("index dropped: '%s'", key->name));
- if (key->flags & HA_NOSAME)
- {
- /*
- Unique key. Check for "PRIMARY".
- or if dropping last unique key
- */
- if ((uint) (key - table->key_info) == table->s->primary_key)
- {
- DBUG_PRINT("info", ("Dropping primary key"));
- /* Primary key. */
- needed_inplace_with_read_flags|= HA_INPLACE_DROP_PK_INDEX_NO_WRITE;
- needed_inplace_flags|= HA_INPLACE_DROP_PK_INDEX_NO_READ_WRITE;
- pk_changed++;
- candidate_key_count--;
- }
- else
- {
- KEY_PART_INFO *part_end= key->key_part + key->user_defined_key_parts;
- bool is_candidate_key= true;
-
- /* Non-primary unique key. */
- needed_inplace_with_read_flags|=
- HA_INPLACE_DROP_UNIQUE_INDEX_NO_WRITE;
- needed_inplace_flags|= HA_INPLACE_DROP_UNIQUE_INDEX_NO_READ_WRITE;
-
- /*
- Check if all fields in key are declared
- NOT NULL and adjust candidate_key_count
- */
- for (KEY_PART_INFO *key_part= key->key_part;
- key_part < part_end;
- key_part++)
- is_candidate_key=
- (is_candidate_key &&
- (! table->field[key_part->fieldnr-1]->maybe_null()));
- if (is_candidate_key)
- candidate_key_count--;
- }
- }
- else
- {
- /* Non-unique key. */
- needed_inplace_with_read_flags|= HA_INPLACE_DROP_INDEX_NO_WRITE;
- needed_inplace_flags|= HA_INPLACE_DROP_INDEX_NO_READ_WRITE;
- }
- }
- no_pk= ((table->s->primary_key == MAX_KEY) ||
- (needed_inplace_with_read_flags &
- HA_INPLACE_DROP_PK_INDEX_NO_WRITE));
- /* Check added indexes. */
- for (idx_p= index_add_buffer, idx_end_p= idx_p + index_add_count;
- idx_p < idx_end_p;
- idx_p++)
- {
- key= key_info_buffer + *idx_p;
- DBUG_PRINT("info", ("index added: '%s'", key->name));
- if (key->flags & HA_NOSAME)
- {
- /* Unique key */
-
- KEY_PART_INFO *part_end= key->key_part + key->user_defined_key_parts;
- bool is_candidate_key= true;
-
- /*
- Check if all fields in key are declared
- NOT NULL
- */
- for (KEY_PART_INFO *key_part= key->key_part;
- key_part < part_end;
- key_part++)
- is_candidate_key=
- (is_candidate_key &&
- (! table->field[key_part->fieldnr]->maybe_null()));
-
- /*
- Check for "PRIMARY"
- or if adding first unique key
- defined on non-nullable fields
- */
-
- if ((!my_strcasecmp(system_charset_info,
- key->name, primary_key_name)) ||
- (no_pk && candidate_key_count == 0 && is_candidate_key))
- {
- DBUG_PRINT("info", ("Adding primary key"));
- /* Primary key. */
- needed_inplace_with_read_flags|= HA_INPLACE_ADD_PK_INDEX_NO_WRITE;
- needed_inplace_flags|= HA_INPLACE_ADD_PK_INDEX_NO_READ_WRITE;
- pk_changed++;
- no_pk= false;
- }
- else
- {
- /* Non-primary unique key. */
- needed_inplace_with_read_flags|= HA_INPLACE_ADD_UNIQUE_INDEX_NO_WRITE;
- needed_inplace_flags|= HA_INPLACE_ADD_UNIQUE_INDEX_NO_READ_WRITE;
- if (ignore)
- {
- /*
- If ignore is used, we have to remove all duplicate rows,
- which require a full table copy.
- */
- need_copy_table= ALTER_TABLE_DATA_CHANGED;
- pk_changed= 2; // Don't change need_copy_table
- break;
- }
- }
- }
- else
- {
- /* Non-unique key. */
- needed_inplace_with_read_flags|= HA_INPLACE_ADD_INDEX_NO_WRITE;
- needed_inplace_flags|= HA_INPLACE_ADD_INDEX_NO_READ_WRITE;
- }
+ /*
+ ALGORITHM and LOCK clauses are generally not allowed by the
+ parser for operations related to partitioning.
+ The exceptions are ALTER_PARTITION and ALTER_REMOVE_PARTITIONING.
+ For consistency, we report ER_ALTER_OPERATION_NOT_SUPPORTED here.
+ */
+ if (alter_info->requested_lock !=
+ Alter_info::ALTER_TABLE_LOCK_DEFAULT)
+ {
+ my_error(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON, MYF(0),
+ "LOCK=NONE/SHARED/EXCLUSIVE",
+ ER(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_PARTITION),
+ "LOCK=DEFAULT");
+ DBUG_RETURN(true);
}
-
- if ((candidate_key_count > 0) &&
- (needed_inplace_with_read_flags & HA_INPLACE_DROP_PK_INDEX_NO_WRITE))
+ else if (alter_info->requested_algorithm !=
+ Alter_info::ALTER_TABLE_ALGORITHM_DEFAULT)
{
- /*
- Dropped primary key when there is some other unique
- not null key that should be converted to primary key
- */
- needed_inplace_with_read_flags|= HA_INPLACE_ADD_PK_INDEX_NO_WRITE;
- needed_inplace_flags|= HA_INPLACE_ADD_PK_INDEX_NO_READ_WRITE;
- pk_changed= 2;
+ my_error(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON, MYF(0),
+ "ALGORITHM=COPY/INPLACE",
+ ER(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_PARTITION),
+ "ALGORITHM=DEFAULT");
+ DBUG_RETURN(true);
}
- DBUG_PRINT("info",
- ("needed_inplace_with_read_flags: 0x%lx, needed_inplace_flags: 0x%lx",
- needed_inplace_with_read_flags, needed_inplace_flags));
/*
- In-place add/drop index is possible only if
- the primary key is not added and dropped in the same statement.
- Otherwise we have to recreate the table.
- need_copy_table is no-zero at this place.
-
- Also, in-place is not possible if we add a primary key
- and drop another key in the same statement. If the drop fails,
- we will not be able to revert adding of primary key.
+ Upgrade from MDL_SHARED_UPGRADABLE to MDL_SHARED_NO_WRITE.
+ Afterwards it's safe to take the table level lock.
*/
- if ( pk_changed < 2 )
+ if (thd->mdl_context.upgrade_shared_lock(mdl_ticket, MDL_SHARED_NO_WRITE,
+ thd->variables.lock_wait_timeout)
+ || lock_tables(thd, table_list, alter_ctx.tables_opened, 0))
{
- if ((needed_inplace_with_read_flags & HA_INPLACE_ADD_PK_INDEX_NO_WRITE) &&
- index_drop_count > 0)
- {
- /*
- Do copy, not in-place ALTER.
- Avoid setting ALTER_TABLE_METADATA_ONLY.
- */
- }
- else if ((alter_flags & needed_inplace_with_read_flags) ==
- needed_inplace_with_read_flags)
- {
- /* All required in-place flags to allow concurrent reads are present. */
- need_copy_table= ALTER_TABLE_METADATA_ONLY;
- need_lock_for_indexes= FALSE;
- }
- else if ((alter_flags & needed_inplace_flags) == needed_inplace_flags)
- {
- /* All required in-place flags are present. */
- need_copy_table= ALTER_TABLE_METADATA_ONLY;
- }
+ DBUG_RETURN(true);
}
- DBUG_PRINT("info", ("need_copy_table: %u need_lock: %d",
- need_copy_table, need_lock_for_indexes));
+
+ // In-place execution of ALTER TABLE for partitioning.
+ DBUG_RETURN(fast_alter_partition_table(thd, table, alter_info,
+ create_info, table_list,
+ alter_ctx.db,
+ alter_ctx.table_name));
}
+#endif
/*
- better have a negative test here, instead of positive, like
- alter_info->flags & ALTER_ADD_COLUMN|ALTER_ADD_INDEX|...
- so that ALTER TABLE won't break when somebody will add new flag
+ Use copy algorithm if:
+ - old_alter_table system variable is set without in-place requested using
+ the ALGORITHM clause.
+ - Or if in-place is impossible for given operation.
+ - Changes to partitioning which were not handled by fast_alter_part_table()
+ needs to be handled using table copying algorithm unless the engine
+ supports auto-partitioning as such engines can do some changes
+ using in-place API.
*/
- if (need_copy_table == ALTER_TABLE_METADATA_ONLY)
- create_info->frm_only= 1;
-
+ if ((thd->variables.old_alter_table &&
+ alter_info->requested_algorithm !=
+ Alter_info::ALTER_TABLE_ALGORITHM_INPLACE)
+ || is_inplace_alter_impossible(table, create_info, alter_info)
#ifdef WITH_PARTITION_STORAGE_ENGINE
- if (table_for_fast_alter_partition)
+ || (partition_changed &&
+ !(table->s->db_type()->partition_flags() & HA_USE_AUTO_PARTITION))
+#endif
+ )
{
- DBUG_RETURN(fast_alter_partition_table(thd, table, alter_info,
- create_info, table_list,
- db, table_name,
- table_for_fast_alter_partition));
+ if (alter_info->requested_algorithm ==
+ Alter_info::ALTER_TABLE_ALGORITHM_INPLACE)
+ {
+ my_error(ER_ALTER_OPERATION_NOT_SUPPORTED, MYF(0),
+ "ALGORITHM=INPLACE", "ALGORITHM=COPY");
+ DBUG_RETURN(true);
+ }
+ alter_info->requested_algorithm= Alter_info::ALTER_TABLE_ALGORITHM_COPY;
}
-#endif
- my_snprintf(tmp_name, sizeof(tmp_name), "%s-%lx_%lx", tmp_file_prefix,
- current_pid, thd->thread_id);
- /* Safety fix for innodb */
- if (lower_case_table_names)
- my_casedn_str(files_charset_info, tmp_name);
+ /*
+ If the old table had partitions and we are doing ALTER TABLE ...
+ engine= <new_engine>, the new table must preserve the original
+ partitioning. This means that the new engine is still the
+ partitioning engine, not the engine specified in the parser.
+ This is discovered in prep_alter_part_table, which in such case
+ updates create_info->db_type.
+ It's therefore important that the assignment below is done
+ after prep_alter_part_table.
+ */
+ handlerton *new_db_type= create_info->db_type;
+ handlerton *old_db_type= table->s->db_type();
+ TABLE *new_table= NULL;
+ ha_rows copied=0,deleted=0;
/*
Handling of symlinked tables:
@@ -6782,287 +7699,337 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
Copy data.
Remove old table and symlinks.
*/
- if (!strcmp(db, new_db)) // Ignore symlink if db changed
+ char index_file[FN_REFLEN], data_file[FN_REFLEN];
+
+ if (!alter_ctx.is_database_changed())
{
if (create_info->index_file_name)
{
/* Fix index_file_name to have 'tmp_name' as basename */
- strmov(index_file, tmp_name);
+ strmov(index_file, alter_ctx.tmp_name);
create_info->index_file_name=fn_same(index_file,
- create_info->index_file_name,
- 1);
+ create_info->index_file_name,
+ 1);
}
if (create_info->data_file_name)
{
/* Fix data_file_name to have 'tmp_name' as basename */
- strmov(data_file, tmp_name);
+ strmov(data_file, alter_ctx.tmp_name);
create_info->data_file_name=fn_same(data_file,
- create_info->data_file_name,
- 1);
+ create_info->data_file_name,
+ 1);
}
}
else
+ {
+ /* Ignore symlink if db is changed. */
create_info->data_file_name=create_info->index_file_name=0;
+ }
DEBUG_SYNC(thd, "alter_table_before_create_table_no_lock");
DBUG_EXECUTE_IF("sleep_before_create_table_no_lock",
my_sleep(100000););
+ /* We can abort alter table for any table type */
+ thd->abort_on_warning= !ignore && thd->is_strict_mode();
+
/*
- Create a table with a temporary name.
- With create_info->frm_only == 1 this creates a .frm file only and
- we keep the original row format.
+ Create .FRM for new version of table with a temporary name.
We don't log the statement, it will be logged later.
+
+ Keep information about keys in newly created table as it
+ will be used later to construct Alter_inplace_info object
+ and by fill_alter_inplace_info() call.
*/
- if (need_copy_table == ALTER_TABLE_METADATA_ONLY)
- {
- DBUG_ASSERT(create_info->frm_only);
- /* Ensure we keep the original table format */
- create_info->table_options= ((create_info->table_options &
- ~HA_OPTION_PACK_RECORD) |
- (table->s->db_create_options &
- HA_OPTION_PACK_RECORD));
- }
+ KEY *key_info;
+ uint key_count;
+ /*
+ Remember if the new definition has new VARCHAR column;
+ create_info->varchar will be reset in create_table_impl()/
+ mysql_prepare_create_table().
+ */
+ bool varchar= create_info->varchar;
+
tmp_disable_binlog(thd);
- error= mysql_create_table_no_lock(thd, new_db, tmp_name,
- create_info,
- alter_info,
- 1, 0, NULL);
+ error= create_table_impl(thd, alter_ctx.new_db, alter_ctx.tmp_name,
+ alter_ctx.get_tmp_path(),
+ create_info, alter_info,
+ true, true, NULL,
+ &key_info, &key_count, FALSE);
reenable_binlog(thd);
+ thd->abort_on_warning= false;
if (error)
- goto err;
+ DBUG_RETURN(true);
+
+ /* Remember that we have not created table in storage engine yet. */
+ bool no_ha_table= true;
- /* Open the table if we need to copy the data. */
- DBUG_PRINT("info", ("need_copy_table: %u", need_copy_table));
- if (need_copy_table != ALTER_TABLE_METADATA_ONLY)
+ if (alter_info->requested_algorithm != Alter_info::ALTER_TABLE_ALGORITHM_COPY)
{
- if (table->s->tmp_table)
+ Alter_inplace_info ha_alter_info(create_info, alter_info,
+ key_info, key_count,
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ thd->work_part_info,
+#else
+ NULL,
+#endif
+ ignore);
+ TABLE *altered_table= NULL;
+ bool use_inplace= true;
+
+ /* Fill the Alter_inplace_info structure. */
+ if (fill_alter_inplace_info(thd, table, varchar, &ha_alter_info))
+ goto err_new_table_cleanup;
+
+ // We assume that the table is non-temporary.
+ DBUG_ASSERT(!table->s->tmp_table);
+
+ if (!(altered_table= open_table_uncached(thd, alter_ctx.get_tmp_path(),
+ alter_ctx.new_db,
+ alter_ctx.tmp_name,
+ true, false)))
+ goto err_new_table_cleanup;
+
+ /* Set markers for fields in TABLE object for altered table. */
+ update_altered_table(ha_alter_info, altered_table);
+
+ /*
+ Mark all columns in 'altered_table' as used to allow usage
+ of its record[0] buffer and Field objects during in-place
+ ALTER TABLE.
+ */
+ altered_table->column_bitmaps_set_no_signal(&altered_table->s->all_set,
+ &altered_table->s->all_set);
+
+ if (ha_alter_info.handler_flags == 0)
{
- Open_table_context ot_ctx(thd, (MYSQL_OPEN_IGNORE_FLUSH |
- MYSQL_LOCK_IGNORE_TIMEOUT));
- TABLE_LIST tbl;
- bzero((void*) &tbl, sizeof(tbl));
- tbl.db= new_db;
- tbl.table_name= tbl.alias= tmp_name;
- /* Table is in thd->temporary_tables */
- (void) open_table(thd, &tbl, thd->mem_root, &ot_ctx);
- new_table= tbl.table;
+ /*
+ No-op ALTER, no need to call handler API functions.
+
+ If this code path is entered for an ALTER statement that
+ should not be a real no-op, new handler flags should be added
+ and fill_alter_inplace_info() adjusted.
+
+ Note that we can end up here if an ALTER statement has clauses
+ that cancel each other out (e.g. ADD/DROP identically index).
+
+ Also note that we ignore the LOCK clause here.
+ */
+ close_temporary_table(thd, altered_table, true, false);
+ goto end_inplace;
+ }
+
+ // Ask storage engine whether to use copy or in-place
+ enum_alter_inplace_result inplace_supported=
+ table->file->check_if_supported_inplace_alter(altered_table,
+ &ha_alter_info);
+
+ switch (inplace_supported) {
+ case HA_ALTER_INPLACE_EXCLUSIVE_LOCK:
+ // If SHARED lock and no particular algorithm was requested, use COPY.
+ if (alter_info->requested_lock ==
+ Alter_info::ALTER_TABLE_LOCK_SHARED &&
+ alter_info->requested_algorithm ==
+ Alter_info::ALTER_TABLE_ALGORITHM_DEFAULT)
+ {
+ use_inplace= false;
+ }
+ // Otherwise, if weaker lock was requested, report errror.
+ else if (alter_info->requested_lock ==
+ Alter_info::ALTER_TABLE_LOCK_NONE ||
+ alter_info->requested_lock ==
+ Alter_info::ALTER_TABLE_LOCK_SHARED)
+ {
+ ha_alter_info.report_unsupported_error("LOCK=NONE/SHARED",
+ "LOCK=EXCLUSIVE");
+ close_temporary_table(thd, altered_table, true, false);
+ goto err_new_table_cleanup;
+ }
+ break;
+ case HA_ALTER_INPLACE_SHARED_LOCK_AFTER_PREPARE:
+ case HA_ALTER_INPLACE_SHARED_LOCK:
+ // If weaker lock was requested, report errror.
+ if (alter_info->requested_lock ==
+ Alter_info::ALTER_TABLE_LOCK_NONE)
+ {
+ ha_alter_info.report_unsupported_error("LOCK=NONE", "LOCK=SHARED");
+ close_temporary_table(thd, altered_table, true, false);
+ goto err_new_table_cleanup;
+ }
+ break;
+ case HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE:
+ case HA_ALTER_INPLACE_NO_LOCK:
+ break;
+ case HA_ALTER_INPLACE_NOT_SUPPORTED:
+ // If INPLACE was requested, report error.
+ if (alter_info->requested_algorithm ==
+ Alter_info::ALTER_TABLE_ALGORITHM_INPLACE)
+ {
+ ha_alter_info.report_unsupported_error("ALGORITHM=INPLACE",
+ "ALGORITHM=COPY");
+ close_temporary_table(thd, altered_table, true, false);
+ goto err_new_table_cleanup;
+ }
+ // COPY with LOCK=NONE is not supported, no point in trying.
+ if (alter_info->requested_lock ==
+ Alter_info::ALTER_TABLE_LOCK_NONE)
+ {
+ ha_alter_info.report_unsupported_error("LOCK=NONE", "LOCK=SHARED");
+ close_temporary_table(thd, altered_table, true, false);
+ goto err_new_table_cleanup;
+ }
+ // Otherwise use COPY
+ use_inplace= false;
+ break;
+ case HA_ALTER_ERROR:
+ default:
+ close_temporary_table(thd, altered_table, true, false);
+ goto err_new_table_cleanup;
+ }
+
+ if (use_inplace)
+ {
+ if (mysql_inplace_alter_table(thd, table_list, table,
+ altered_table,
+ &ha_alter_info,
+ inplace_supported, &target_mdl_request,
+ &alter_ctx))
+ {
+ DBUG_RETURN(true);
+ }
+
+ goto end_inplace;
}
else
{
- char path[FN_REFLEN + 1];
- /* table is a normal table: Create temporary table in same directory */
- build_table_filename(path, sizeof(path) - 1, new_db, tmp_name, "",
- FN_IS_TMP);
- /* Open our intermediate table. */
- new_table= open_table_uncached(thd, path, new_db, tmp_name, TRUE);
+ close_temporary_table(thd, altered_table, true, false);
+ }
+ }
+
+ /* ALTER TABLE using copy algorithm. */
+
+ /* Check if ALTER TABLE is compatible with foreign key definitions. */
+ if (fk_prepare_copy_alter_table(thd, table, alter_info, &alter_ctx))
+ goto err_new_table_cleanup;
+
+ if (!table->s->tmp_table)
+ {
+ // COPY algorithm doesn't work with concurrent writes.
+ if (alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_NONE)
+ {
+ my_error(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON, MYF(0),
+ "LOCK=NONE",
+ ER(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_COPY),
+ "LOCK=SHARED");
+ goto err_new_table_cleanup;
}
- if (!new_table)
+
+ // If EXCLUSIVE lock is requested, upgrade already.
+ if (alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_EXCLUSIVE &&
+ wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
goto err_new_table_cleanup;
+
/*
- Note: In case of MERGE table, we do not attach children. We do not
- copy data for MERGE tables. Only the children have data.
+ Otherwise upgrade to SHARED_NO_WRITE.
+ Note that under LOCK TABLES, we will already have SHARED_NO_READ_WRITE.
*/
+ if (alter_info->requested_lock != Alter_info::ALTER_TABLE_LOCK_EXCLUSIVE &&
+ thd->mdl_context.upgrade_shared_lock(mdl_ticket, MDL_SHARED_NO_WRITE,
+ thd->variables.lock_wait_timeout))
+ goto err_new_table_cleanup;
+
+ DEBUG_SYNC(thd, "alter_table_copy_after_lock_upgrade");
}
- /* Check if we can do the ALTER TABLE as online */
- if (require_online)
+ // It's now safe to take the table level lock.
+ if (lock_tables(thd, table_list, alter_ctx.tables_opened, 0))
+ goto err_new_table_cleanup;
+
{
- if (index_add_count || index_drop_count ||
- (new_table &&
- !(new_table->file->ha_table_flags() & HA_NO_COPY_ON_ALTER)))
- {
- my_error(ER_CANT_DO_ONLINE, MYF(0), "ALTER");
+ if (ha_create_table(thd, alter_ctx.get_tmp_path(),
+ alter_ctx.new_db, alter_ctx.tmp_name,
+ create_info, false))
goto err_new_table_cleanup;
+
+ /* Mark that we have created table in storage engine. */
+ no_ha_table= false;
+
+ if (create_info->options & HA_LEX_CREATE_TMP_TABLE)
+ {
+ if (!open_table_uncached(thd, alter_ctx.get_tmp_path(),
+ alter_ctx.new_db, alter_ctx.tmp_name,
+ true, true))
+ goto err_new_table_cleanup;
}
}
+
+ /* Open the table since we need to copy the data. */
+ if (table->s->tmp_table != NO_TMP_TABLE)
+ {
+ TABLE_LIST tbl;
+ tbl.init_one_table(alter_ctx.new_db, strlen(alter_ctx.new_db),
+ alter_ctx.tmp_name, strlen(alter_ctx.tmp_name),
+ alter_ctx.tmp_name, TL_READ_NO_INSERT);
+ /* Table is in thd->temporary_tables */
+ (void) open_temporary_table(thd, &tbl);
+ new_table= tbl.table;
+ }
+ else
+ {
+ /* table is a normal table: Create temporary table in same directory */
+ /* Open our intermediate table. */
+ new_table= open_table_uncached(thd, alter_ctx.get_tmp_path(),
+ alter_ctx.new_db, alter_ctx.tmp_name,
+ true, true);
+ }
+ if (!new_table)
+ goto err_new_table_cleanup;
+ /*
+ Note: In case of MERGE table, we do not attach children. We do not
+ copy data for MERGE tables. Only the children have data.
+ */
+
/* Copy the data if necessary. */
thd->count_cuted_fields= CHECK_FIELD_WARN; // calc cuted fields
thd->cuted_fields=0L;
- copied=deleted=0;
/*
We do not copy data for MERGE tables. Only the children have data.
MERGE tables have HA_NO_COPY_ON_ALTER set.
*/
- if (new_table && !(new_table->file->ha_table_flags() & HA_NO_COPY_ON_ALTER))
+ if (!(new_table->file->ha_table_flags() & HA_NO_COPY_ON_ALTER))
{
new_table->next_number_field=new_table->found_next_number_field;
+ THD_STAGE_INFO(thd, stage_copy_to_tmp_table);
DBUG_EXECUTE_IF("abort_copy_table", {
my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0));
goto err_new_table_cleanup;
});
- error= copy_data_between_tables(thd, table, new_table,
- alter_info->create_list, ignore,
- order_num, order, &copied, &deleted,
- alter_info->keys_onoff,
- alter_info->error_if_not_empty);
+ if (copy_data_between_tables(thd, table, new_table,
+ alter_info->create_list, ignore,
+ order_num, order, &copied, &deleted,
+ alter_info->keys_onoff,
+ &alter_ctx))
+ goto err_new_table_cleanup;
}
else
{
- /*
- Ensure that we will upgrade the metadata lock if
- handler::enable/disable_indexes() will be called.
- */
- if (alter_info->keys_onoff != LEAVE_AS_IS ||
- table->file->indexes_are_disabled())
- need_lock_for_indexes= true;
- if (!table->s->tmp_table && need_lock_for_indexes &&
- wait_while_table_is_used(thd, table, extra_func))
+ /* Should be MERGE only */
+ DBUG_ASSERT(new_table->file->ht->db_type == DB_TYPE_MRG_MYISAM);
+ if (!table->s->tmp_table &&
+ wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
goto err_new_table_cleanup;
THD_STAGE_INFO(thd, stage_manage_keys);
DEBUG_SYNC(thd, "alter_table_manage_keys");
alter_table_manage_keys(table, table->file->indexes_are_disabled(),
alter_info->keys_onoff);
- error= trans_commit_stmt(thd);
- if (trans_commit_implicit(thd))
- error= 1;
- }
- thd->count_cuted_fields= CHECK_FIELD_IGNORE;
-
- if (error)
- goto err_new_table_cleanup;
-
- /* If we did not need to copy, we might still need to add/drop indexes. */
- if (! new_table)
- {
- uint *key_numbers;
- uint *keyno_p;
- KEY *key_info;
- KEY *key;
- uint *idx_p;
- uint *idx_end_p;
- KEY_PART_INFO *key_part;
- KEY_PART_INFO *part_end;
- DBUG_PRINT("info", ("No new_table, checking add/drop index"));
-
- table->file->ha_prepare_for_alter();
- if (index_add_count)
- {
- /* The add_index() method takes an array of KEY structs. */
- key_info= (KEY*) thd->alloc(sizeof(KEY) * index_add_count);
- key= key_info;
- for (idx_p= index_add_buffer, idx_end_p= idx_p + index_add_count;
- idx_p < idx_end_p;
- idx_p++, key++)
- {
- /* Copy the KEY struct. */
- *key= key_info_buffer[*idx_p];
- /* Fix the key parts. */
- part_end= key->key_part + key->user_defined_key_parts;
- for (key_part= key->key_part; key_part < part_end; key_part++)
- key_part->field= table->field[key_part->fieldnr];
- }
- /* Add the indexes. */
- if ((error= table->file->add_index(table, key_info, index_add_count,
- &add)))
- {
- /* Only report error if handler has not already reported an error */
- if (!thd->is_error())
- {
- /*
- Exchange the key_info for the error message. If we exchange
- key number by key name in the message later, we need correct info.
- */
- KEY *save_key_info= table->key_info;
- table->key_info= key_info;
- table->file->print_error(error, MYF(0));
- table->key_info= save_key_info;
- }
- goto err_new_table_cleanup;
- }
- pending_inplace_add_index= true;
- }
- /*end of if (index_add_count)*/
-
- if (index_drop_count)
- {
- /* Currently we must finalize add index if we also drop indexes */
- if (pending_inplace_add_index)
- {
- /* Committing index changes needs exclusive metadata lock. */
- DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE,
- table_list->db,
- table_list->table_name,
- MDL_EXCLUSIVE));
- if ((error= table->file->final_add_index(add, true)))
- {
- table->file->print_error(error, MYF(0));
- goto err_new_table_cleanup;
- }
- pending_inplace_add_index= false;
- }
- /* The prepare_drop_index() method takes an array of key numbers. */
- key_numbers= (uint*) thd->alloc(sizeof(uint) * index_drop_count);
- keyno_p= key_numbers;
- /* Get the number of each key. */
- for (idx_p= index_drop_buffer, idx_end_p= idx_p + index_drop_count;
- idx_p < idx_end_p;
- idx_p++, keyno_p++)
- *keyno_p= *idx_p;
- /*
- Tell the handler to prepare for drop indexes.
- This re-numbers the indexes to get rid of gaps.
- */
- error= table->file->prepare_drop_index(table, key_numbers,
- index_drop_count);
- if (!error)
- {
- /* Tell the handler to finally drop the indexes. */
- error= table->file->final_drop_index(table);
- }
-
- if (error)
- {
- table->file->print_error(error, MYF(0));
- if (index_add_count) // Drop any new indexes added.
- {
- /*
- Temporarily set table-key_info to include information about the
- indexes added above that we now need to drop.
- */
- KEY *save_key_info= table->key_info;
- table->key_info= key_info_buffer;
- if ((error= table->file->prepare_drop_index(table, index_add_buffer,
- index_add_count)))
- table->file->print_error(error, MYF(0));
- else if ((error= table->file->final_drop_index(table)))
- table->file->print_error(error, MYF(0));
- table->key_info= save_key_info;
- }
-
- /*
- Mark this TABLE instance as stale to avoid
- out-of-sync index information.
- */
- table->m_needs_reopen= true;
- goto err_new_table_cleanup;
- }
- }
- /*end of if (index_drop_count)*/
-
- /*
- The final .frm file is already created as a temporary file
- and will be renamed to the original table name later.
- */
-
- /* Need to commit before a table is unlocked (NDB requirement). */
- DBUG_PRINT("info", ("Committing before unlocking table"));
if (trans_commit_stmt(thd) || trans_commit_implicit(thd))
goto err_new_table_cleanup;
}
- /*end of if (! new_table) for add/drop index*/
-
- DBUG_ASSERT(error == 0);
+ thd->count_cuted_fields= CHECK_FIELD_IGNORE;
if (table->s->tmp_table != NO_TMP_TABLE)
{
- /*
- In-place operations are not supported for temporary tables, so
- we don't have to call final_add_index() in this case. The assert
- verifies that in-place add index has not been done.
- */
- DBUG_ASSERT(!pending_inplace_add_index);
/* Close lock if this is a transactional table */
if (thd->lock)
{
@@ -7070,7 +8037,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
thd->locked_tables_mode != LTM_PRELOCKED_UNDER_LOCK_TABLES)
{
mysql_unlock_tables(thd, thd->lock);
- thd->lock=0;
+ thd->lock= NULL;
}
else
{
@@ -7082,14 +8049,15 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
}
}
/* Remove link to old table and rename the new one */
- close_temporary_table(thd, table, 1, 1);
+ close_temporary_table(thd, table, true, true);
/* Should pass the 'new_name' as we store table name in the cache */
- if (rename_temporary_table(thd, new_table, new_db, new_name))
+ if (rename_temporary_table(thd, new_table,
+ alter_ctx.new_db, alter_ctx.new_name))
goto err_new_table_cleanup;
/* We don't replicate alter table statement on temporary tables */
if (!thd->is_current_stmt_binlog_format_row() &&
- write_bin_log(thd, TRUE, thd->query(), thd->query_length()))
- DBUG_RETURN(TRUE);
+ write_bin_log(thd, true, thd->query(), thd->query_length()))
+ DBUG_RETURN(true);
goto end_temporary;
}
@@ -7098,11 +8066,9 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
not delete it! Even altough MERGE tables do not have their children
attached here it is safe to call close_temporary_table().
*/
- if (new_table)
- {
- close_temporary_table(thd, new_table, 1, 0);
- new_table= 0;
- }
+ close_temporary_table(thd, new_table, true, false);
+ new_table= NULL;
+
DEBUG_SYNC(thd, "alter_table_before_rename_result_table");
/*
@@ -7123,177 +8089,80 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
*/
THD_STAGE_INFO(thd, stage_rename_result_table);
- my_snprintf(old_name, sizeof(old_name), "%s2-%lx-%lx", tmp_file_prefix,
- current_pid, thd->thread_id);
- if (lower_case_table_names)
- my_casedn_str(files_charset_info, old_name);
if (wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_RENAME))
- {
- if (pending_inplace_add_index)
- {
- pending_inplace_add_index= false;
- table->file->final_add_index(add, false);
- }
- // Mark this TABLE instance as stale to avoid out-of-sync index information.
- table->m_needs_reopen= true;
goto err_new_table_cleanup;
- }
- if (pending_inplace_add_index)
- {
- pending_inplace_add_index= false;
- DBUG_EXECUTE_IF("alter_table_rollback_new_index", {
- table->file->final_add_index(add, false);
- my_error(ER_UNKNOWN_ERROR, MYF(0));
- goto err_new_table_cleanup;
- });
- if ((error= table->file->final_add_index(add, true)))
- {
- table->file->print_error(error, MYF(0));
- goto err_new_table_cleanup;
- }
- }
close_all_tables_for_name(thd, table->s,
- new_name != table_name || new_db != db ?
- HA_EXTRA_PREPARE_FOR_RENAME :
- HA_EXTRA_NOT_USED);
-
- error=0;
- table_list->table= table= 0; /* Safety */
- save_old_db_type= old_db_type;
+ alter_ctx.is_table_renamed() ?
+ HA_EXTRA_PREPARE_FOR_RENAME:
+ HA_EXTRA_NOT_USED,
+ NULL);
+ table_list->table= table= NULL; /* Safety */
/*
- This leads to the storage engine (SE) not being notified for renames in
- mysql_rename_table(), because we just juggle with the FRM and nothing
- more. If we have an intermediate table, then we notify the SE that
- it should become the actual table. Later, we will recycle the old table.
- However, in case of ALTER TABLE RENAME there might be no intermediate
- table. This is when the old and new tables are compatible, according to
- mysql_compare_table(). Then, we need one additional call to
- mysql_rename_table() with flag NO_FRM_RENAME, which does nothing else but
- actual rename in the SE and the FRM is not touched. Note that, if the
- table is renamed and the SE is also changed, then an intermediate table
- is created and the additional call will not take place.
+ Rename the old table to temporary name to have a backup in case
+ anything goes wrong while renaming the new table.
*/
-
- if (new_name != table_name || new_db != db)
+ char backup_name[32];
+ my_snprintf(backup_name, sizeof(backup_name), "%s2-%lx-%lx", tmp_file_prefix,
+ current_pid, thd->thread_id);
+ if (lower_case_table_names)
+ my_casedn_str(files_charset_info, backup_name);
+ if (mysql_rename_table(old_db_type, alter_ctx.db, alter_ctx.table_name,
+ alter_ctx.db, backup_name, FN_TO_IS_TMP))
{
- LEX_STRING new_db_name= { new_db, strlen(new_db) };
- LEX_STRING new_table_name= { new_name, strlen(new_name) };
- (void) rename_table_in_stat_tables(thd, &old_db_name, &old_table_name,
- &new_db_name, &new_table_name);
+ // Rename to temporary name failed, delete the new table, abort ALTER.
+ (void) quick_rm_table(thd, new_db_type, alter_ctx.new_db,
+ alter_ctx.tmp_name, FN_IS_TMP);
+ goto err_with_mdl;
}
- if (need_copy_table == ALTER_TABLE_METADATA_ONLY)
- {
- DBUG_ASSERT(new_db_type == old_db_type);
- /* This type cannot happen in regular ALTER. */
- new_db_type= old_db_type= NULL;
- }
- if (mysql_rename_table(old_db_type, db, table_name, db, old_name,
- FN_TO_IS_TMP))
- {
- error=1;
- (void) quick_rm_table(new_db_type, new_db, tmp_name, FN_IS_TMP);
- }
- else if (mysql_rename_table(new_db_type, new_db, tmp_name, new_db,
- new_alias, FN_FROM_IS_TMP))
- {
- /* Try to get everything back. */
- error= 1;
- (void) quick_rm_table(new_db_type, new_db, tmp_name, FN_IS_TMP);
- (void) mysql_rename_table(old_db_type, db, old_name, db, alias,
- FN_FROM_IS_TMP);
- }
- else if (new_name != table_name || new_db != db)
+ // Rename the new table to the correct name.
+ if (mysql_rename_table(new_db_type, alter_ctx.new_db, alter_ctx.tmp_name,
+ alter_ctx.new_db, alter_ctx.new_alias,
+ FN_FROM_IS_TMP))
{
- if (need_copy_table == ALTER_TABLE_METADATA_ONLY &&
- mysql_rename_table(save_old_db_type, db, table_name, new_db,
- new_alias, NO_FRM_RENAME))
- {
- /* Try to get everything back. */
- error= 1;
- (void) quick_rm_table(new_db_type, new_db, new_alias, 0);
- (void) mysql_rename_table(old_db_type, db, old_name, db, alias,
- FN_FROM_IS_TMP);
- }
- else if (Table_triggers_list::change_table_name(thd, db, alias,
- table_name, new_db,
- new_alias))
- {
- /* Try to get everything back. */
- error= 1;
- (void) quick_rm_table(new_db_type, new_db, new_alias, 0);
- (void) mysql_rename_table(old_db_type, db, old_name, db,
- alias, FN_FROM_IS_TMP);
- /*
- If we were performing "fast"/in-place ALTER TABLE we also need
- to restore old name of table in storage engine as a separate
- step, as the above rename affects .FRM only.
- */
- if (need_copy_table == ALTER_TABLE_METADATA_ONLY)
- {
- (void) mysql_rename_table(save_old_db_type, new_db, new_alias,
- db, table_name, NO_FRM_RENAME);
- }
- }
+ // Rename failed, delete the temporary table.
+ (void) quick_rm_table(thd, new_db_type, alter_ctx.new_db,
+ alter_ctx.tmp_name, FN_IS_TMP);
+ // Restore the backup of the original table to the old name.
+ (void) mysql_rename_table(old_db_type, alter_ctx.db, backup_name,
+ alter_ctx.db, alter_ctx.alias, FN_FROM_IS_TMP);
+ goto err_with_mdl;
}
- if (! error)
- (void) quick_rm_table(old_db_type, db, old_name, FN_IS_TMP);
-
- if (error)
- {
- /* This shouldn't happen. But let us play it safe. */
+ // Check if we renamed the table and if so update trigger files.
+ if (alter_ctx.is_table_renamed() &&
+ Table_triggers_list::change_table_name(thd,
+ alter_ctx.db,
+ alter_ctx.alias,
+ alter_ctx.table_name,
+ alter_ctx.new_db,
+ alter_ctx.new_alias))
+ {
+ // Rename succeeded, delete the new table.
+ (void) quick_rm_table(thd, new_db_type,
+ alter_ctx.new_db, alter_ctx.new_alias, 0);
+ // Restore the backup of the original table to the old name.
+ (void) mysql_rename_table(old_db_type, alter_ctx.db, backup_name,
+ alter_ctx.db, alter_ctx.alias, FN_FROM_IS_TMP);
goto err_with_mdl;
}
- if (need_copy_table == ALTER_TABLE_METADATA_ONLY)
+ // ALTER TABLE succeeded, delete the backup of the old table.
+ if (quick_rm_table(thd, old_db_type, alter_ctx.db, backup_name, FN_IS_TMP))
{
/*
- Now we have to inform handler that new .FRM file is in place.
- To do this we need to obtain a handler object for it.
- NO need to tamper with MERGE tables. The real open is done later.
+ The fact that deletion of the backup failed is not critical
+ error, but still worth reporting as it might indicate serious
+ problem with server.
*/
- Open_table_context ot_ctx(thd, MYSQL_OPEN_REOPEN);
- TABLE_LIST temp_table_list;
- TABLE_LIST *t_table_list;
- if (new_name != table_name || new_db != db)
- {
- temp_table_list.init_one_table(new_db, strlen(new_db),
- new_name, strlen(new_name),
- new_name, TL_READ_NO_INSERT);
- temp_table_list.mdl_request.ticket= target_mdl_request.ticket;
- t_table_list= &temp_table_list;
- }
- else
- {
- /*
- Under LOCK TABLES, we have a different mdl_lock_ticket
- points to a different instance than the one set initially
- to request the lock.
- */
- table_list->mdl_request.ticket= mdl_ticket;
- t_table_list= table_list;
- }
- if (open_table(thd, t_table_list, thd->mem_root, &ot_ctx))
- {
- goto err_with_mdl;
- }
-
- /* Tell the handler that a new frm file is in place. */
- error= t_table_list->table->file->ha_create_handler_files(path, NULL,
- CHF_INDEX_FLAG,
- create_info);
+ goto err_with_mdl;
+ }
- DBUG_ASSERT(thd->open_tables == t_table_list->table);
- close_thread_table(thd, &thd->open_tables);
- t_table_list->table= NULL;
+end_inplace:
- if (error)
- goto err_with_mdl;
- }
if (thd->locked_tables_list.reopen_tables(thd))
goto err_with_mdl;
@@ -7304,61 +8173,75 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
ha_binlog_log_query(thd, create_info->db_type, LOGCOM_ALTER_TABLE,
thd->query(), thd->query_length(),
- db, table_name);
+ alter_ctx.db, alter_ctx.table_name);
DBUG_ASSERT(!(mysql_bin_log.is_open() &&
thd->is_current_stmt_binlog_format_row() &&
(create_info->options & HA_LEX_CREATE_TMP_TABLE)));
- if (write_bin_log(thd, TRUE, thd->query(), thd->query_length()))
- DBUG_RETURN(TRUE);
+ if (write_bin_log(thd, true, thd->query(), thd->query_length()))
+ DBUG_RETURN(true);
- table_list->table=0; // For query cache
- query_cache_invalidate3(thd, table_list, 0);
+ if (ha_check_storage_engine_flag(old_db_type, HTON_FLUSH_AFTER_RENAME))
+ {
+ /*
+ For the alter table to be properly flushed to the logs, we
+ have to open the new table. If not, we get a problem on server
+ shutdown. But we do not need to attach MERGE children.
+ */
+ TABLE *t_table;
+ t_table= open_table_uncached(thd, alter_ctx.get_new_path(),
+ alter_ctx.new_db, alter_ctx.new_name,
+ false, true);
+ if (t_table)
+ intern_close_table(t_table);
+ else
+ sql_print_warning("Could not open table %s.%s after rename\n",
+ alter_ctx.new_db, alter_ctx.table_name);
+ ha_flush_logs(old_db_type);
+ }
+ table_list->table= NULL; // For query cache
+ query_cache_invalidate3(thd, table_list, false);
if (thd->locked_tables_mode == LTM_LOCK_TABLES ||
thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES)
{
- if ((new_name != table_name || new_db != db))
+ if (alter_ctx.is_table_renamed())
thd->mdl_context.release_all_locks_for_name(mdl_ticket);
else
- mdl_ticket->downgrade_exclusive_lock(MDL_SHARED_NO_READ_WRITE);
+ mdl_ticket->downgrade_lock(MDL_SHARED_NO_READ_WRITE);
}
end_temporary:
- my_snprintf(tmp_name, sizeof(tmp_name), ER(ER_INSERT_INFO),
+ my_snprintf(alter_ctx.tmp_name, sizeof(alter_ctx.tmp_name),
+ ER(ER_INSERT_INFO),
(ulong) (copied + deleted), (ulong) deleted,
- (ulong) thd->warning_info->statement_warn_count());
- my_ok(thd, copied + deleted, 0L, tmp_name);
- DBUG_RETURN(FALSE);
+ (ulong) thd->get_stmt_da()->current_statement_warn_count());
+ my_ok(thd, copied + deleted, 0L, alter_ctx.tmp_name);
+ DBUG_RETURN(false);
err_new_table_cleanup:
if (new_table)
{
/* close_temporary_table() frees the new_table pointer. */
- close_temporary_table(thd, new_table, 1, 1);
+ close_temporary_table(thd, new_table, true, true);
}
else
- (void) quick_rm_table(new_db_type, new_db, tmp_name,
- create_info->frm_only ? FN_IS_TMP | FRM_ONLY : FN_IS_TMP);
+ (void) quick_rm_table(thd, new_db_type,
+ alter_ctx.new_db, alter_ctx.tmp_name,
+ (FN_IS_TMP | (no_ha_table ? NO_HA_TABLE : 0)));
-err:
-#ifdef WITH_PARTITION_STORAGE_ENGINE
- /* If prep_alter_part_table created an intermediate table, destroy it. */
- if (table_for_fast_alter_partition)
- close_temporary(table_for_fast_alter_partition, 1, 0);
-#endif /* WITH_PARTITION_STORAGE_ENGINE */
/*
No default value was provided for a DATE/DATETIME field, the
current sql_mode doesn't allow the '0000-00-00' value and
the table to be altered isn't empty.
Report error here.
*/
- if (alter_info->error_if_not_empty &&
- thd->warning_info->current_row_for_warning())
+ if (alter_ctx.error_if_not_empty &&
+ thd->get_stmt_da()->current_row_for_warning())
{
const char *f_val= 0;
enum enum_mysql_timestamp_type t_type= MYSQL_TIMESTAMP_DATE;
- switch (alter_info->datetime_field->sql_type)
+ switch (alter_ctx.datetime_field->sql_type)
{
case MYSQL_TYPE_DATE:
case MYSQL_TYPE_NEWDATE:
@@ -7374,14 +8257,14 @@ err:
DBUG_ASSERT(0);
}
bool save_abort_on_warning= thd->abort_on_warning;
- thd->abort_on_warning= TRUE;
- make_truncated_value_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
+ thd->abort_on_warning= true;
+ make_truncated_value_warning(thd, Sql_condition::WARN_LEVEL_WARN,
f_val, strlength(f_val), t_type,
- alter_info->datetime_field->field_name);
+ alter_ctx.datetime_field->field_name);
thd->abort_on_warning= save_abort_on_warning;
}
- DBUG_RETURN(TRUE);
+ DBUG_RETURN(true);
err_with_mdl:
/*
@@ -7392,13 +8275,10 @@ err_with_mdl:
*/
thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0);
thd->mdl_context.release_all_locks_for_name(mdl_ticket);
- DBUG_RETURN(TRUE);
+ DBUG_RETURN(true);
}
-/* Copy all rows from one table to another */
-
-
/**
Prepare the transaction for the alter table's copy phase.
@@ -7447,16 +8327,14 @@ bool mysql_trans_commit_alter_copy_data(THD *thd)
static int
-copy_data_between_tables(THD *thd, TABLE *from,TABLE *to,
- List<Create_field> &create,
- bool ignore,
+copy_data_between_tables(THD *thd, TABLE *from, TABLE *to,
+ List<Create_field> &create, bool ignore,
uint order_num, ORDER *order,
- ha_rows *copied,
- ha_rows *deleted,
- enum enum_enable_or_disable keys_onoff,
- bool error_if_not_empty)
+ ha_rows *copied, ha_rows *deleted,
+ Alter_info::enum_enable_or_disable keys_onoff,
+ Alter_table_ctx *alter_ctx)
{
- int error= 1, errpos= 0;
+ int error= 1;
Copy_field *copy= NULL, *copy_end;
ha_rows found_count= 0, delete_count= 0;
uint length= 0;
@@ -7470,8 +8348,6 @@ copy_data_between_tables(THD *thd, TABLE *from,TABLE *to,
bool auto_increment_field_copied= 0;
ulonglong save_sql_mode= thd->variables.sql_mode;
ulonglong prev_insert_id, time_to_report_progress;
- List_iterator<Create_field> it(create);
- Create_field *def;
Field **dfield_ptr= to->default_field;
DBUG_ENTER("copy_data_between_tables");
@@ -7479,15 +8355,13 @@ copy_data_between_tables(THD *thd, TABLE *from,TABLE *to,
thd_progress_init(thd, 2 + test(order));
if (mysql_trans_prepare_alter_copy_data(thd))
- goto err;
- errpos=1;
+ DBUG_RETURN(-1);
if (!(copy= new Copy_field[to->s->fields]))
- goto err; /* purecov: inspected */
+ DBUG_RETURN(-1); /* purecov: inspected */
if (to->file->ha_external_lock(thd, F_WRLCK))
- goto err;
- errpos= 2;
+ DBUG_RETURN(-1);
/* We need external lock before we can disable/enable keys */
alter_table_manage_keys(to, from->file->indexes_are_disabled(), keys_onoff);
@@ -7498,8 +8372,9 @@ copy_data_between_tables(THD *thd, TABLE *from,TABLE *to,
from->file->info(HA_STATUS_VARIABLE);
to->file->ha_start_bulk_insert(from->file->stats.records,
ignore ? 0 : HA_CREATE_UNIQUE_INDEX_BY_SORT);
- errpos= 3;
+ List_iterator<Create_field> it(create);
+ Create_field *def;
copy_end=copy;
to->s->default_fields= 0;
for (Field **ptr=to->field ; *ptr ; ptr++)
@@ -7548,7 +8423,7 @@ copy_data_between_tables(THD *thd, TABLE *from,TABLE *to,
my_snprintf(warn_buff, sizeof(warn_buff),
"ORDER BY ignored as there is a user-defined clustered index"
" in the table '%-.192s'", from->s->table_name.str);
- push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR,
+ push_warning(thd, Sql_condition::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR,
warn_buff);
}
else
@@ -7581,11 +8456,13 @@ copy_data_between_tables(THD *thd, TABLE *from,TABLE *to,
to->use_all_columns();
to->mark_virtual_columns_for_write(TRUE);
if (init_read_record(&info, thd, from, (SQL_SELECT *) 0, 1, 1, FALSE))
+ {
+ error= 1;
goto err;
- errpos= 4;
- if (ignore)
+ }
+ if (ignore && !alter_ctx->fk_error_if_delete_row)
to->file->extra(HA_EXTRA_IGNORE_DUP_KEY);
- thd->warning_info->reset_current_row_for_warning();
+ thd->get_stmt_da()->reset_current_row_for_warning();
restore_record(to, s->default_values); // Create empty record
thd->progress.max_counter= from->file->records();
@@ -7609,7 +8486,7 @@ copy_data_between_tables(THD *thd, TABLE *from,TABLE *to,
}
/* Return error if source table isn't empty. */
- if (error_if_not_empty)
+ if (alter_ctx->error_if_not_empty)
{
error= 1;
break;
@@ -7643,39 +8520,62 @@ copy_data_between_tables(THD *thd, TABLE *from,TABLE *to,
to->auto_increment_field_not_null= FALSE;
if (error)
{
- if (!ignore ||
- to->file->is_fatal_error(error, HA_CHECK_DUP))
+ if (to->file->is_fatal_error(error, HA_CHECK_DUP))
{
- if (!to->file->is_fatal_error(error, HA_CHECK_DUP))
- {
- uint key_nr= to->file->get_dup_key(error);
- if ((int) key_nr >= 0)
- {
- const char *err_msg= ER(ER_DUP_ENTRY_WITH_KEY_NAME);
- if (key_nr == 0 &&
- (to->key_info[0].key_part[0].field->flags &
- AUTO_INCREMENT_FLAG))
- err_msg= ER(ER_DUP_ENTRY_AUTOINCREMENT_CASE);
- to->file->print_keydup_error(table, &table->key_info[key_nr],
- err_msg, MYF(0));
- break;
- }
- }
-
- to->file->print_error(error,MYF(0));
+ /* Not a duplicate key error. */
+ to->file->print_error(error, MYF(0));
break;
}
- to->file->restore_auto_increment(prev_insert_id);
- delete_count++;
+ else
+ {
+ /* Duplicate key error. */
+ if (alter_ctx->fk_error_if_delete_row)
+ {
+ /*
+ We are trying to omit a row from the table which serves as parent
+ in a foreign key. This might have broken referential integrity so
+ emit an error. Note that we can't ignore this error even if we are
+ executing ALTER IGNORE TABLE. IGNORE allows to skip rows, but
+ doesn't allow to break unique or foreign key constraints,
+ */
+ my_error(ER_FK_CANNOT_DELETE_PARENT, MYF(0),
+ alter_ctx->fk_error_id,
+ alter_ctx->fk_error_table);
+ break;
+ }
+
+ if (ignore)
+ {
+ /* This ALTER IGNORE TABLE. Simply skip row and continue. */
+ to->file->restore_auto_increment(prev_insert_id);
+ delete_count++;
+ }
+ else
+ {
+ /* Ordinary ALTER TABLE. Report duplicate key error. */
+ uint key_nr= to->file->get_dup_key(error);
+ if ((int) key_nr >= 0)
+ {
+ const char *err_msg= ER(ER_DUP_ENTRY_WITH_KEY_NAME);
+ if (key_nr == 0 &&
+ (to->key_info[0].key_part[0].field->flags &
+ AUTO_INCREMENT_FLAG))
+ err_msg= ER(ER_DUP_ENTRY_AUTOINCREMENT_CASE);
+ print_keydup_error(to, key_nr == MAX_KEY ? NULL :
+ &to->key_info[key_nr],
+ err_msg, MYF(0));
+ }
+ else
+ to->file->print_error(error, MYF(0));
+ break;
+ }
+ }
}
else
found_count++;
- thd->warning_info->inc_current_row_for_warning();
+ thd->get_stmt_da()->inc_current_row_for_warning();
}
-
-err:
- if (errpos >= 4)
- end_read_record(&info);
+ end_read_record(&info);
free_io_cache(from);
delete [] copy;
@@ -7687,22 +8587,23 @@ err:
/* We are going to drop the temporary table */
to->file->extra(HA_EXTRA_PREPARE_FOR_DROP);
}
- if (errpos >= 3 && to->file->ha_end_bulk_insert() && error <= 0)
+ if (to->file->ha_end_bulk_insert() && error <= 0)
{
to->file->print_error(my_errno,MYF(0));
error= 1;
}
to->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY);
- if (errpos >= 1 && mysql_trans_commit_alter_copy_data(thd))
+ if (mysql_trans_commit_alter_copy_data(thd))
error= 1;
+ err:
thd->variables.sql_mode= save_sql_mode;
thd->abort_on_warning= 0;
*copied= found_count;
*deleted=delete_count;
to->file->ha_release_auto_increment();
- if (errpos >= 2 && to->file->ha_external_lock(thd,F_UNLCK))
+ if (to->file->ha_external_lock(thd,F_UNLCK))
error=1;
if (error < 0 && to->file->extra(HA_EXTRA_PREPARE_FOR_RENAME))
error= 1;
@@ -7729,13 +8630,6 @@ bool mysql_recreate_table(THD *thd, TABLE_LIST *table_list)
DBUG_ENTER("mysql_recreate_table");
DBUG_ASSERT(!table_list->next_global);
- /*
- table_list->table has been closed and freed. Do not reference
- uninitialized data. open_tables() could fail.
- */
- table_list->table= NULL;
- /* Same applies to MDL ticket. */
- table_list->mdl_request.ticket= NULL;
/* Set lock type which is appropriate for ALTER TABLE. */
table_list->lock_type= TL_READ_NO_INSERT;
/* Same applies to MDL request. */
@@ -7745,10 +8639,11 @@ bool mysql_recreate_table(THD *thd, TABLE_LIST *table_list)
create_info.row_type=ROW_TYPE_NOT_USED;
create_info.default_table_charset=default_charset_info;
/* Force alter table to recreate table */
- alter_info.flags= (ALTER_CHANGE_COLUMN | ALTER_RECREATE);
+ alter_info.flags= (Alter_info::ALTER_CHANGE_COLUMN |
+ Alter_info::ALTER_RECREATE);
DBUG_RETURN(mysql_alter_table(thd, NullS, NullS, &create_info,
table_list, &alter_info, 0,
- (ORDER *) 0, 0, 0));
+ (ORDER *) 0, 0));
}
@@ -7763,23 +8658,48 @@ bool mysql_checksum_table(THD *thd, TABLE_LIST *tables,
field_list.push_back(item = new Item_empty_string("Table", NAME_LEN*2));
item->maybe_null= 1;
- field_list.push_back(item= new Item_int("Checksum", (longlong) 1,
+ field_list.push_back(item= new Item_int("Checksum",
+ (longlong) 1,
MY_INT64_NUM_DECIMAL_DIGITS));
item->maybe_null= 1;
if (protocol->send_result_set_metadata(&field_list,
Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
DBUG_RETURN(TRUE);
+ /*
+ Close all temporary tables which were pre-open to simplify
+ privilege checking. Clear all references to closed tables.
+ */
+ close_thread_tables(thd);
+ for (table= tables; table; table= table->next_local)
+ table->table= NULL;
+
/* Open one table after the other to keep lock time as short as possible. */
for (table= tables; table; table= table->next_local)
{
char table_name[SAFE_NAME_LEN*2+2];
TABLE *t;
+ TABLE_LIST *save_next_global;
strxmov(table_name, table->db ,".", table->table_name, NullS);
- t= table->table= open_n_lock_single_table(thd, table, TL_READ, 0);
- thd->clear_error(); // these errors shouldn't get client
+ /* Remember old 'next' pointer and break the list. */
+ save_next_global= table->next_global;
+ table->next_global= NULL;
+ table->lock_type= TL_READ;
+ /* Allow to open real tables only. */
+ table->required_type= FRMTYPE_TABLE;
+
+ if (open_temporary_tables(thd, table) ||
+ open_and_lock_tables(thd, table, FALSE, 0))
+ {
+ t= NULL;
+ thd->clear_error(); // these errors shouldn't get client
+ }
+ else
+ t= table->table;
+
+ table->next_global= save_next_global;
protocol->prepare_for_resend();
protocol->store(table_name, system_charset_info);
@@ -7926,7 +8846,7 @@ static bool check_engine(THD *thd, const char *db_name,
if (req_engine && req_engine != *new_engine)
{
- push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
ER_WARN_USING_OTHER_HANDLER,
ER(ER_WARN_USING_OTHER_HANDLER),
ha_resolve_storage_engine_name(*new_engine),