diff options
Diffstat (limited to 'sql/sql_table.cc')
-rw-r--r-- | sql/sql_table.cc | 3745 |
1 files changed, 3285 insertions, 460 deletions
diff --git a/sql/sql_table.cc b/sql/sql_table.cc index b3bd3182a59..7057c783701 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -16,19 +16,19 @@ /* drop and alter of tables */ #include "mysql_priv.h" -#ifdef HAVE_BERKELEY_DB -#include "ha_berkeley.h" -#endif #include <hash.h> #include <myisam.h> #include <my_dir.h> #include "sp_head.h" #include "sql_trigger.h" +#include "sql_show.h" #ifdef __WIN__ #include <io.h> #endif +int creating_table= 0; // How many mysql_create_table are running + const char *primary_key_name="PRIMARY"; static bool check_if_keyname_exists(const char *name,KEY *start, KEY *end); @@ -41,35 +41,1421 @@ static int copy_data_between_tables(TABLE *from,TABLE *to, static bool prepare_blob_field(THD *thd, create_field *sql_field); static bool check_engine(THD *thd, const char *table_name, - enum db_type *new_engine); + HA_CREATE_INFO *create_info); +static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, + List<create_field> *fields, + List<Key> *keys, bool tmp_table, + uint *db_options, + handler *file, KEY **key_info_buffer, + uint *key_count, int select_field_count); + +#define MYSQL50_TABLE_NAME_PREFIX "#mysql50#" +#define MYSQL50_TABLE_NAME_PREFIX_LENGTH 9 /* - Build the path to a file for a table (or the base path that can - then have various extensions stuck on to it). + Translate a file name to a table name (WL #1324). SYNOPSIS - build_table_path() - buff Buffer to build the path into - bufflen sizeof(buff) - db Name of database - table Name of table - ext Filename extension + filename_to_tablename() + from The file name in my_charset_filename. + to OUT The table name in system_charset_info. + to_length The size of the table name buffer. RETURN - 0 Error - # Size of path - */ + Table name length. +*/ + +uint filename_to_tablename(const char *from, char *to, uint to_length) +{ + uint errors; + uint res; + DBUG_ENTER("filename_to_tablename"); + DBUG_PRINT("enter", ("from '%s'", from)); + + if (!memcmp(from, tmp_file_prefix, tmp_file_prefix_length)) + { + /* Temporary table name. */ + res= (strnmov(to, from, to_length) - to); + } + else + { + res= strconvert(&my_charset_filename, from, + system_charset_info, to, to_length, &errors); + if (errors) // Old 5.0 name + { + res= (strxnmov(to, to_length, MYSQL50_TABLE_NAME_PREFIX, from, NullS) - + to); + sql_print_error("Invalid (old?) table or database name '%s'", from); + /* + TODO: add a stored procedure for fix table and database names, + and mention its name in error log. + */ + } + } + + DBUG_PRINT("exit", ("to '%s'", to)); + DBUG_RETURN(res); +} + + +/* + Translate a table name to a file name (WL #1324). + + SYNOPSIS + tablename_to_filename() + from The table name in system_charset_info. + to OUT The file name in my_charset_filename. + to_length The size of the file name buffer. + + RETURN + File name length. +*/ + +uint tablename_to_filename(const char *from, char *to, uint to_length) +{ + uint errors, length; + DBUG_ENTER("tablename_to_filename"); + DBUG_PRINT("enter", ("from '%s'", from)); + + if (from[0] == '#' && !strncmp(from, MYSQL50_TABLE_NAME_PREFIX, + MYSQL50_TABLE_NAME_PREFIX_LENGTH)) + DBUG_RETURN((uint) (strmake(to, from+MYSQL50_TABLE_NAME_PREFIX_LENGTH, + to_length-1) - + (from + MYSQL50_TABLE_NAME_PREFIX_LENGTH))); + length= strconvert(system_charset_info, from, + &my_charset_filename, to, to_length, &errors); + if (check_if_legal_tablename(to) && + length + 4 < to_length) + { + memcpy(to + length, "@@@", 4); + length+= 3; + } + DBUG_PRINT("exit", ("to '%s'", to)); + DBUG_RETURN(length); +} + + +/* + Creates path to a file: mysql_data_dir/db/table.ext + + SYNOPSIS + build_table_filename() + buff Where to write result in my_charset_filename. + bufflen buff size + db Database name in system_charset_info. + table_name Table name in system_charset_info. + ext File extension. + flags FN_FROM_IS_TMP or FN_TO_IS_TMP or FN_IS_TMP + table_name is temporary, do not change. + + NOTES + + Uses database and table name, and extension to create + a file name in mysql_data_dir. Database and table + names are converted from system_charset_info into "fscs". + Unless flags indicate a temporary table name. + 'db' is always converted. + 'ext' is not converted. + + The conversion suppression is required for ALTER TABLE. This + statement creates intermediate tables. These are regular + (non-temporary) tables with a temporary name. Their path names must + be derivable from the table name. So we cannot use + build_tmptable_filename() for them. + + RETURN + path length +*/ + +uint build_table_filename(char *buff, size_t bufflen, const char *db, + const char *table_name, const char *ext, uint flags) +{ + uint length; + char dbbuff[FN_REFLEN]; + char tbbuff[FN_REFLEN]; + DBUG_ENTER("build_table_filename"); + + if (flags & FN_IS_TMP) // FN_FROM_IS_TMP | FN_TO_IS_TMP + strnmov(tbbuff, table_name, sizeof(tbbuff)); + else + VOID(tablename_to_filename(table_name, tbbuff, sizeof(tbbuff))); + + VOID(tablename_to_filename(db, dbbuff, sizeof(dbbuff))); + length= strxnmov(buff, bufflen, mysql_data_home, "/", dbbuff, + "/", tbbuff, ext, NullS) - buff; + DBUG_PRINT("exit", ("buff: '%s'", buff)); + DBUG_RETURN(length); +} + + +/* + Creates path to a file: mysql_tmpdir/#sql1234_12_1.ext + + SYNOPSIS + build_tmptable_filename() + thd The thread handle. + buff Where to write result in my_charset_filename. + bufflen buff size + + NOTES + + Uses current_pid, thread_id, and tmp_table counter to create + a file name in mysql_tmpdir. + + RETURN + path length +*/ + +uint build_tmptable_filename(THD* thd, char *buff, size_t bufflen) +{ + uint length; + char tmp_table_name[tmp_file_prefix_length+22+22+22+3]; + DBUG_ENTER("build_tmptable_filename"); + + my_snprintf(tmp_table_name, sizeof(tmp_table_name), + "%s%lx_%lx_%x", + tmp_file_prefix, current_pid, + thd->thread_id, thd->tmp_table++); + + strxnmov(buff, bufflen, mysql_tmpdir, "/", tmp_table_name, reg_ext, NullS); + length= unpack_filename(buff, buff); + DBUG_PRINT("exit", ("buff: '%s'", buff)); + DBUG_RETURN(length); +} + +/* + Return values for compare_tables(). + If you make compare_tables() non-static, move them to a header file. +*/ +#define ALTER_TABLE_DATA_CHANGED 1 +#define ALTER_TABLE_INDEX_CHANGED 2 + + +/* + SYNOPSIS + mysql_copy_create_list() + orig_create_list Original list of created fields + inout::new_create_list Copy of original list + + RETURN VALUES + FALSE Success + TRUE Memory allocation error + + DESCRIPTION + mysql_prepare_table destroys the create_list and in some cases we need + this lists for more purposes. Thus we copy it specifically for use + by mysql_prepare_table +*/ + +static int mysql_copy_create_list(List<create_field> *orig_create_list, + List<create_field> *new_create_list) +{ + List_iterator<create_field> prep_field_it(*orig_create_list); + create_field *prep_field; + DBUG_ENTER("mysql_copy_create_list"); + + while ((prep_field= prep_field_it++)) + { + create_field *field= new create_field(*prep_field); + if (!field || new_create_list->push_back(field)) + { + mem_alloc_error(2); + DBUG_RETURN(TRUE); + } + } + DBUG_RETURN(FALSE); +} + + +/* + SYNOPSIS + mysql_copy_key_list() + orig_key Original list of keys + inout::new_key Copy of original list + + RETURN VALUES + FALSE Success + TRUE Memory allocation error + + DESCRIPTION + mysql_prepare_table destroys the key list and in some cases we need + this lists for more purposes. Thus we copy it specifically for use + by mysql_prepare_table +*/ + +static int mysql_copy_key_list(List<Key> *orig_key, + List<Key> *new_key) +{ + List_iterator<Key> prep_key_it(*orig_key); + Key *prep_key; + DBUG_ENTER("mysql_copy_key_list"); + + while ((prep_key= prep_key_it++)) + { + List<key_part_spec> prep_columns; + List_iterator<key_part_spec> prep_col_it(prep_key->columns); + key_part_spec *prep_col; + Key *temp_key; + + while ((prep_col= prep_col_it++)) + { + key_part_spec *prep_key_part; + + if (!(prep_key_part= new key_part_spec(*prep_col))) + { + mem_alloc_error(sizeof(key_part_spec)); + DBUG_RETURN(TRUE); + } + if (prep_columns.push_back(prep_key_part)) + { + mem_alloc_error(2); + DBUG_RETURN(TRUE); + } + } + if (!(temp_key= new Key(prep_key->type, prep_key->name, + &prep_key->key_create_info, + prep_key->generated, + prep_columns))) + { + mem_alloc_error(sizeof(Key)); + DBUG_RETURN(TRUE); + } + if (new_key->push_back(temp_key)) + { + mem_alloc_error(2); + DBUG_RETURN(TRUE); + } + } + DBUG_RETURN(FALSE); +} + +/* +-------------------------------------------------------------------------- + + MODULE: DDL log + ----------------- + + This module is used to ensure that we can recover from crashes that occur + in the middle of a meta-data operation in MySQL. E.g. DROP TABLE t1, t2; + We need to ensure that both t1 and t2 are dropped and not only t1 and + also that each table drop is entirely done and not "half-baked". + + To support this we create log entries for each meta-data statement in the + ddl log while we are executing. These entries are dropped when the + operation is completed. + + At recovery those entries that were not completed will be executed. + + There is only one ddl log in the system and it is protected by a mutex + and there is a global struct that contains information about its current + state. + + History: + First version written in 2006 by Mikael Ronstrom +-------------------------------------------------------------------------- +*/ + + +typedef struct st_global_ddl_log +{ + /* + We need to adjust buffer size to be able to handle downgrades/upgrades + where IO_SIZE has changed. We'll set the buffer size such that we can + handle that the buffer size was upto 4 times bigger in the version + that wrote the DDL log. + */ + char file_entry_buf[4*IO_SIZE]; + char file_name_str[FN_REFLEN]; + char *file_name; + DDL_LOG_MEMORY_ENTRY *first_free; + DDL_LOG_MEMORY_ENTRY *first_used; + uint num_entries; + File file_id; + uint name_len; + uint io_size; + bool inited; + bool recovery_phase; +} GLOBAL_DDL_LOG; + +GLOBAL_DDL_LOG global_ddl_log; + +pthread_mutex_t LOCK_gdl; + +#define DDL_LOG_ENTRY_TYPE_POS 0 +#define DDL_LOG_ACTION_TYPE_POS 1 +#define DDL_LOG_PHASE_POS 2 +#define DDL_LOG_NEXT_ENTRY_POS 4 +#define DDL_LOG_NAME_POS 8 + +#define DDL_LOG_NUM_ENTRY_POS 0 +#define DDL_LOG_NAME_LEN_POS 4 +#define DDL_LOG_IO_SIZE_POS 8 + +/* + Read one entry from ddl log file + SYNOPSIS + read_ddl_log_file_entry() + entry_no Entry number to read + RETURN VALUES + TRUE Error + FALSE Success +*/ + +static bool read_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; + uint io_size= global_ddl_log.io_size; + DBUG_ENTER("read_ddl_log_file_entry"); + + if (my_pread(file_id, (byte*)file_entry_buf, io_size, io_size * entry_no, + MYF(MY_WME)) != io_size) + error= TRUE; + DBUG_RETURN(error); +} + + +/* + Write one entry from ddl log file + SYNOPSIS + write_ddl_log_file_entry() + entry_no Entry number to read + RETURN VALUES + TRUE Error + FALSE Success +*/ + +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; + DBUG_ENTER("write_ddl_log_file_entry"); + + if (my_pwrite(file_id, (byte*)file_entry_buf, + IO_SIZE, IO_SIZE * entry_no, MYF(MY_WME)) != IO_SIZE) + error= TRUE; + DBUG_RETURN(error); +} + + +/* + Write ddl log header + SYNOPSIS + write_ddl_log_header() + RETURN VALUES + TRUE Error + 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], + global_ddl_log.num_entries); + const_var= FN_LEN; + int4store(&global_ddl_log.file_entry_buf[DDL_LOG_NAME_LEN_POS], + const_var); + const_var= IO_SIZE; + int4store(&global_ddl_log.file_entry_buf[DDL_LOG_IO_SIZE_POS], + const_var); + if (write_ddl_log_file_entry(0UL)) + { + sql_print_error("Error writing ddl log header"); + DBUG_RETURN(TRUE); + } + VOID(sync_ddl_log()); + DBUG_RETURN(error); +} + + +/* + Create ddl log file name + SYNOPSIS + create_ddl_log_file_name() + file_name Filename setup + RETURN VALUES + NONE +*/ + +static inline void create_ddl_log_file_name(char *file_name) +{ + strxmov(file_name, mysql_data_home, "/", "ddl_log.log", NullS); +} + + +/* + Read header of ddl log file + SYNOPSIS + read_ddl_log_header() + RETURN VALUES + > 0 Last entry in ddl log + 0 No entries in ddl log + DESCRIPTION + When we read the ddl log header we get information about maximum sizes + of names in the ddl log and we also get information about the number + of entries in the ddl log. +*/ + +static uint read_ddl_log_header() +{ + char *file_entry_buf= (char*)global_ddl_log.file_entry_buf; + char file_name[FN_REFLEN]; + uint entry_no; + bool successful_open= FALSE; + DBUG_ENTER("read_ddl_log_header"); + + create_ddl_log_file_name(file_name); + if ((global_ddl_log.file_id= my_open(file_name, + O_RDWR | O_BINARY, MYF(MY_WME))) >= 0) + { + if (read_ddl_log_file_entry(0UL)) + { + /* Write message into error log */ + sql_print_error("Failed to read ddl log file in recovery"); + } + else + successful_open= TRUE; + } + entry_no= uint4korr(&file_entry_buf[DDL_LOG_NUM_ENTRY_POS]); + global_ddl_log.name_len= uint4korr(&file_entry_buf[DDL_LOG_NAME_LEN_POS]); + if (successful_open) + { + global_ddl_log.io_size= uint4korr(&file_entry_buf[DDL_LOG_IO_SIZE_POS]); + DBUG_ASSERT(global_ddl_log.io_size <= + sizeof(global_ddl_log.file_entry_buf)); + } + else + { + entry_no= 0; + } + global_ddl_log.first_free= NULL; + global_ddl_log.first_used= NULL; + global_ddl_log.num_entries= 0; + VOID(pthread_mutex_init(&LOCK_gdl, MY_MUTEX_INIT_FAST)); + DBUG_RETURN(entry_no); +} + + +/* + Read a ddl log entry + SYNOPSIS + read_ddl_log_entry() + read_entry Number of entry to read + out:entry_info Information from entry + RETURN VALUES + TRUE Error + FALSE Success + DESCRIPTION + Read a specified entry in the ddl log +*/ + +bool read_ddl_log_entry(uint read_entry, DDL_LOG_ENTRY *ddl_log_entry) +{ + char *file_entry_buf= (char*)&global_ddl_log.file_entry_buf; + uint inx; + uchar single_char; + DBUG_ENTER("read_ddl_log_entry"); + + if (read_ddl_log_file_entry(read_entry)) + { + DBUG_RETURN(TRUE); + } + ddl_log_entry->entry_pos= read_entry; + single_char= file_entry_buf[DDL_LOG_ENTRY_TYPE_POS]; + ddl_log_entry->entry_type= (enum ddl_log_entry_code)single_char; + single_char= file_entry_buf[DDL_LOG_ACTION_TYPE_POS]; + ddl_log_entry->action_type= (enum ddl_log_action_code)single_char; + ddl_log_entry->phase= file_entry_buf[DDL_LOG_PHASE_POS]; + ddl_log_entry->next_entry= uint4korr(&file_entry_buf[DDL_LOG_NEXT_ENTRY_POS]); + ddl_log_entry->name= &file_entry_buf[DDL_LOG_NAME_POS]; + inx= DDL_LOG_NAME_POS + global_ddl_log.name_len; + ddl_log_entry->from_name= &file_entry_buf[inx]; + inx+= global_ddl_log.name_len; + ddl_log_entry->handler_name= &file_entry_buf[inx]; + DBUG_RETURN(FALSE); +} + + +/* + Initialise ddl log + SYNOPSIS + init_ddl_log() + + DESCRIPTION + Write the header of the ddl log file and length of names. Also set + number of entries to zero. + + RETURN VALUES + TRUE Error + FALSE Success +*/ + +static bool init_ddl_log() +{ + bool error= FALSE; + char file_name[FN_REFLEN]; + DBUG_ENTER("init_ddl_log"); + + if (global_ddl_log.inited) + goto end; + + global_ddl_log.io_size= IO_SIZE; + create_ddl_log_file_name(file_name); + if ((global_ddl_log.file_id= my_create(file_name, + CREATE_MODE, + O_RDWR | O_TRUNC | O_BINARY, + MYF(MY_WME))) < 0) + { + /* Couldn't create ddl log file, this is serious error */ + sql_print_error("Failed to open ddl log file"); + DBUG_RETURN(TRUE); + } + global_ddl_log.inited= TRUE; + if (write_ddl_log_header()) + { + VOID(my_close(global_ddl_log.file_id, MYF(MY_WME))); + global_ddl_log.inited= FALSE; + DBUG_RETURN(TRUE); + } + +end: + DBUG_RETURN(FALSE); +} + + +/* + Execute one action in a ddl log entry + SYNOPSIS + execute_ddl_log_action() + ddl_log_entry Information in action entry to execute + RETURN VALUES + TRUE Error + FALSE Success +*/ + +static int execute_ddl_log_action(THD *thd, DDL_LOG_ENTRY *ddl_log_entry) +{ + bool frm_action= FALSE; + LEX_STRING handler_name; + handler *file= NULL; + MEM_ROOT mem_root; + int error= TRUE; + char to_path[FN_REFLEN]; + char from_path[FN_REFLEN]; + char *par_ext= (char*)".par"; + handlerton *hton; + DBUG_ENTER("execute_ddl_log_action"); + + if (ddl_log_entry->entry_type == DDL_IGNORE_LOG_ENTRY_CODE) + { + DBUG_RETURN(FALSE); + } + 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); + if (!strcmp(ddl_log_entry->handler_name, reg_ext)) + frm_action= TRUE; + else + { + TABLE_SHARE dummy; + + hton= ha_resolve_by_name(thd, &handler_name); + if (!hton) + { + my_error(ER_ILLEGAL_HA, MYF(0), ddl_log_entry->handler_name); + goto error; + } + bzero(&dummy, sizeof(TABLE_SHARE)); + file= get_new_handler(&dummy, &mem_root, hton); + if (!file) + { + mem_alloc_error(sizeof(handler)); + goto error; + } + } + switch (ddl_log_entry->action_type) + { + case DDL_LOG_REPLACE_ACTION: + case DDL_LOG_DELETE_ACTION: + { + if (ddl_log_entry->phase == 0) + { + if (frm_action) + { + strxmov(to_path, ddl_log_entry->name, reg_ext, NullS); + if ((error= my_delete(to_path, MYF(MY_WME)))) + { + if (my_errno != ENOENT) + break; + } +#ifdef WITH_PARTITION_STORAGE_ENGINE + strxmov(to_path, ddl_log_entry->name, par_ext, NullS); + VOID(my_delete(to_path, MYF(MY_WME))); +#endif + } + else + { + if ((error= file->delete_table(ddl_log_entry->name))) + { + if (error != ENOENT && error != HA_ERR_NO_SUCH_TABLE) + break; + } + } + if ((deactivate_ddl_log_entry(ddl_log_entry->entry_pos))) + break; + VOID(sync_ddl_log()); + error= FALSE; + if (ddl_log_entry->action_type == DDL_LOG_DELETE_ACTION) + break; + } + DBUG_ASSERT(ddl_log_entry->action_type == DDL_LOG_REPLACE_ACTION); + /* + Fall through and perform the rename action of the replace + action. We have already indicated the success of the delete + action in the log entry by stepping up the phase. + */ + } + case DDL_LOG_RENAME_ACTION: + { + error= TRUE; + if (frm_action) + { + strxmov(to_path, ddl_log_entry->name, reg_ext, NullS); + strxmov(from_path, ddl_log_entry->from_name, reg_ext, NullS); + if (my_rename(from_path, to_path, MYF(MY_WME))) + break; +#ifdef WITH_PARTITION_STORAGE_ENGINE + strxmov(to_path, ddl_log_entry->name, par_ext, NullS); + strxmov(from_path, ddl_log_entry->from_name, par_ext, NullS); + VOID(my_rename(from_path, to_path, MYF(MY_WME))); +#endif + } + else + { + if (file->rename_table(ddl_log_entry->from_name, + ddl_log_entry->name)) + break; + } + if ((deactivate_ddl_log_entry(ddl_log_entry->entry_pos))) + break; + VOID(sync_ddl_log()); + error= FALSE; + break; + } + default: + DBUG_ASSERT(0); + break; + } + delete file; +error: + free_root(&mem_root, MYF(0)); + DBUG_RETURN(error); +} + + +/* + Get a free entry in the ddl log + SYNOPSIS + get_free_ddl_log_entry() + out:active_entry A ddl log memory entry returned + RETURN VALUES + TRUE Error + FALSE Success +*/ + +static bool get_free_ddl_log_entry(DDL_LOG_MEMORY_ENTRY **active_entry, + bool *write_header) +{ + DDL_LOG_MEMORY_ENTRY *used_entry; + DDL_LOG_MEMORY_ENTRY *first_used= global_ddl_log.first_used; + DBUG_ENTER("get_free_ddl_log_entry"); + + if (global_ddl_log.first_free == NULL) + { + if (!(used_entry= (DDL_LOG_MEMORY_ENTRY*)my_malloc( + sizeof(DDL_LOG_MEMORY_ENTRY), MYF(MY_WME)))) + { + sql_print_error("Failed to allocate memory for ddl log free list"); + DBUG_RETURN(TRUE); + } + global_ddl_log.num_entries++; + used_entry->entry_pos= global_ddl_log.num_entries; + *write_header= TRUE; + } + else + { + used_entry= global_ddl_log.first_free; + global_ddl_log.first_free= used_entry->next_log_entry; + *write_header= FALSE; + } + /* + Move from free list to used list + */ + used_entry->next_log_entry= first_used; + used_entry->prev_log_entry= NULL; + global_ddl_log.first_used= used_entry; + if (first_used) + first_used->prev_log_entry= used_entry; + + *active_entry= used_entry; + DBUG_RETURN(FALSE); +} + + +/* + External interface methods for the DDL log Module + --------------------------------------------------- +*/ -static uint build_table_path(char *buff, size_t bufflen, const char *db, - const char *table, const char *ext) +/* + SYNOPSIS + write_ddl_log_entry() + ddl_log_entry Information about log entry + out:entry_written Entry information written into + + RETURN VALUES + TRUE Error + FALSE Success + + DESCRIPTION + A careful write of the ddl log is performed to ensure that we can + handle crashes occurring during CREATE and ALTER TABLE processing. +*/ + +bool write_ddl_log_entry(DDL_LOG_ENTRY *ddl_log_entry, + DDL_LOG_MEMORY_ENTRY **active_entry) { - strxnmov(buff, bufflen-1, mysql_data_home, "/", db, "/", table, ext, - NullS); - return unpack_filename(buff,buff); + bool error, write_header; + DBUG_ENTER("write_ddl_log_entry"); + + if (init_ddl_log()) + { + DBUG_RETURN(TRUE); + } + global_ddl_log.file_entry_buf[DDL_LOG_ENTRY_TYPE_POS]= + (char)DDL_LOG_ENTRY_CODE; + global_ddl_log.file_entry_buf[DDL_LOG_ACTION_TYPE_POS]= + (char)ddl_log_entry->action_type; + global_ddl_log.file_entry_buf[DDL_LOG_PHASE_POS]= 0; + int4store(&global_ddl_log.file_entry_buf[DDL_LOG_NEXT_ENTRY_POS], + ddl_log_entry->next_entry); + DBUG_ASSERT(strlen(ddl_log_entry->name) < FN_LEN); + strmake(&global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS], + ddl_log_entry->name, FN_LEN - 1); + if (ddl_log_entry->action_type == DDL_LOG_RENAME_ACTION || + ddl_log_entry->action_type == DDL_LOG_REPLACE_ACTION) + { + DBUG_ASSERT(strlen(ddl_log_entry->from_name) < FN_LEN); + strmake(&global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS + FN_LEN], + ddl_log_entry->from_name, FN_LEN - 1); + } + else + global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS + FN_LEN]= 0; + DBUG_ASSERT(strlen(ddl_log_entry->handler_name) < FN_LEN); + strmake(&global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS + (2*FN_LEN)], + ddl_log_entry->handler_name, FN_LEN - 1); + if (get_free_ddl_log_entry(active_entry, &write_header)) + { + DBUG_RETURN(TRUE); + } + error= FALSE; + if (write_ddl_log_file_entry((*active_entry)->entry_pos)) + { + error= TRUE; + sql_print_error("Failed to write entry_no = %u", + (*active_entry)->entry_pos); + } + if (write_header && !error) + { + VOID(sync_ddl_log()); + if (write_ddl_log_header()) + error= TRUE; + } + if (error) + release_ddl_log_memory_entry(*active_entry); + DBUG_RETURN(error); } +/* + Write final entry in the ddl log + SYNOPSIS + write_execute_ddl_log_entry() + first_entry First entry in linked list of entries + to execute, if 0 = NULL it means that + the entry is removed and the entries + are put into the free list. + complete Flag indicating we are simply writing + info about that entry has been completed + in:out:active_entry Entry to execute, 0 = NULL if the entry + is written first time and needs to be + returned. In this case the entry written + is returned in this parameter + RETURN VALUES + TRUE Error + FALSE Success + + DESCRIPTION + This is the last write in the ddl log. The previous log entries have + already been written but not yet synched to disk. + We write a couple of log entries that describes action to perform. + This entries are set-up in a linked list, however only when a first + execute entry is put as the first entry these will be executed. + This routine writes this first +*/ + +bool write_execute_ddl_log_entry(uint first_entry, + bool complete, + DDL_LOG_MEMORY_ENTRY **active_entry) +{ + bool write_header= FALSE; + char *file_entry_buf= (char*)global_ddl_log.file_entry_buf; + DBUG_ENTER("write_execute_ddl_log_entry"); + + if (init_ddl_log()) + { + DBUG_RETURN(TRUE); + } + if (!complete) + { + /* + We haven't synched the log entries yet, we synch them now before + writing the execute entry. If complete is true we haven't written + any log entries before, we are only here to write the execute + entry to indicate it is done. + */ + VOID(sync_ddl_log()); + file_entry_buf[DDL_LOG_ENTRY_TYPE_POS]= (char)DDL_LOG_EXECUTE_CODE; + } + else + file_entry_buf[DDL_LOG_ENTRY_TYPE_POS]= (char)DDL_IGNORE_LOG_ENTRY_CODE; + file_entry_buf[DDL_LOG_ACTION_TYPE_POS]= 0; /* Ignored for execute entries */ + file_entry_buf[DDL_LOG_PHASE_POS]= 0; + int4store(&file_entry_buf[DDL_LOG_NEXT_ENTRY_POS], first_entry); + file_entry_buf[DDL_LOG_NAME_POS]= 0; + file_entry_buf[DDL_LOG_NAME_POS + FN_LEN]= 0; + file_entry_buf[DDL_LOG_NAME_POS + 2*FN_LEN]= 0; + if (!(*active_entry)) + { + if (get_free_ddl_log_entry(active_entry, &write_header)) + { + DBUG_RETURN(TRUE); + } + } + if (write_ddl_log_file_entry((*active_entry)->entry_pos)) + { + sql_print_error("Error writing execute entry in ddl log"); + release_ddl_log_memory_entry(*active_entry); + DBUG_RETURN(TRUE); + } + VOID(sync_ddl_log()); + if (write_header) + { + if (write_ddl_log_header()) + { + release_ddl_log_memory_entry(*active_entry); + DBUG_RETURN(TRUE); + } + } + DBUG_RETURN(FALSE); +} + + +/* + For complex rename operations we need to deactivate individual entries. + SYNOPSIS + deactivate_ddl_log_entry() + entry_no Entry position of record to change + RETURN VALUES + TRUE Error + FALSE Success + DESCRIPTION + 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. +*/ + +bool deactivate_ddl_log_entry(uint entry_no) +{ + char *file_entry_buf= (char*)global_ddl_log.file_entry_buf; + DBUG_ENTER("deactivate_ddl_log_entry"); + + if (!read_ddl_log_file_entry(entry_no)) + { + if (file_entry_buf[DDL_LOG_ENTRY_TYPE_POS] == DDL_LOG_ENTRY_CODE) + { + 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_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 + { + 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); +} + + +/* + Sync ddl log file + SYNOPSIS + sync_ddl_log() + RETURN VALUES + TRUE Error + FALSE Success +*/ + +bool sync_ddl_log() +{ + bool error= FALSE; + DBUG_ENTER("sync_ddl_log"); + + if ((!global_ddl_log.recovery_phase) && + init_ddl_log()) + { + DBUG_RETURN(TRUE); + } + if (my_sync(global_ddl_log.file_id, MYF(0))) + { + /* Write to error log */ + sql_print_error("Failed to sync ddl log"); + error= TRUE; + } + DBUG_RETURN(error); +} + + +/* + Release a log memory entry + SYNOPSIS + release_ddl_log_memory_entry() + log_memory_entry Log memory entry to release + RETURN VALUES + NONE +*/ + +void release_ddl_log_memory_entry(DDL_LOG_MEMORY_ENTRY *log_entry) +{ + DDL_LOG_MEMORY_ENTRY *first_free= global_ddl_log.first_free; + DDL_LOG_MEMORY_ENTRY *next_log_entry= log_entry->next_log_entry; + DDL_LOG_MEMORY_ENTRY *prev_log_entry= log_entry->prev_log_entry; + DBUG_ENTER("release_ddl_log_memory_entry"); + + global_ddl_log.first_free= log_entry; + log_entry->next_log_entry= first_free; + + if (prev_log_entry) + prev_log_entry->next_log_entry= next_log_entry; + else + global_ddl_log.first_used= next_log_entry; + if (next_log_entry) + next_log_entry->prev_log_entry= prev_log_entry; + DBUG_VOID_RETURN; +} + + +/* + Execute one entry in the ddl log. Executing an entry means executing + a linked list of actions. + SYNOPSIS + execute_ddl_log_entry() + first_entry Reference to first action in entry + RETURN VALUES + TRUE Error + FALSE Success +*/ + +bool execute_ddl_log_entry(THD *thd, uint first_entry) +{ + DDL_LOG_ENTRY ddl_log_entry; + uint read_entry= first_entry; + DBUG_ENTER("execute_ddl_log_entry"); + + pthread_mutex_lock(&LOCK_gdl); + do + { + if (read_ddl_log_entry(read_entry, &ddl_log_entry)) + { + /* Write to error log and continue with next log entry */ + sql_print_error("Failed to read entry = %u from ddl log", + read_entry); + break; + } + DBUG_ASSERT(ddl_log_entry.entry_type == DDL_LOG_ENTRY_CODE || + ddl_log_entry.entry_type == DDL_IGNORE_LOG_ENTRY_CODE); + + if (execute_ddl_log_action(thd, &ddl_log_entry)) + { + /* Write to error log and continue with next log entry */ + sql_print_error("Failed to execute action for entry = %u from ddl log", + read_entry); + break; + } + read_entry= ddl_log_entry.next_entry; + } while (read_entry); + pthread_mutex_unlock(&LOCK_gdl); + DBUG_RETURN(FALSE); +} + + +/* + Close the ddl log + SYNOPSIS + close_ddl_log() + RETURN VALUES + NONE +*/ + +static void close_ddl_log() +{ + DBUG_ENTER("close_ddl_log"); + if (global_ddl_log.file_id >= 0) + { + VOID(my_close(global_ddl_log.file_id, MYF(MY_WME))); + global_ddl_log.file_id= (File) -1; + } + DBUG_VOID_RETURN; +} + + +/* + Execute the ddl log at recovery of MySQL Server + SYNOPSIS + execute_ddl_log_recovery() + RETURN VALUES + NONE +*/ + +void execute_ddl_log_recovery() +{ + uint num_entries, i; + THD *thd; + DDL_LOG_ENTRY ddl_log_entry; + char file_name[FN_REFLEN]; + DBUG_ENTER("execute_ddl_log_recovery"); + + /* + Initialise global_ddl_log struct + */ + bzero(global_ddl_log.file_entry_buf, sizeof(global_ddl_log.file_entry_buf)); + global_ddl_log.inited= FALSE; + global_ddl_log.recovery_phase= TRUE; + global_ddl_log.io_size= IO_SIZE; + global_ddl_log.file_id= (File) -1; + + /* + To be able to run this from boot, we allocate a temporary THD + */ + if (!(thd=new THD)) + DBUG_VOID_RETURN; + thd->thread_stack= (char*) &thd; + thd->store_globals(); + + num_entries= read_ddl_log_header(); + for (i= 1; i < num_entries + 1; i++) + { + if (read_ddl_log_entry(i, &ddl_log_entry)) + { + sql_print_error("Failed to read entry no = %u from ddl log", + i); + continue; + } + if (ddl_log_entry.entry_type == DDL_LOG_EXECUTE_CODE) + { + if (execute_ddl_log_entry(thd, ddl_log_entry.next_entry)) + { + /* Real unpleasant scenario but we continue anyways. */ + continue; + } + } + } + close_ddl_log(); + create_ddl_log_file_name(file_name); + VOID(my_delete(file_name, MYF(0))); + global_ddl_log.recovery_phase= FALSE; + delete thd; + /* Remember that we don't have a THD */ + my_pthread_setspecific_ptr(THR_THD, 0); + DBUG_VOID_RETURN; +} + + +/* + Release all memory allocated to the ddl log + SYNOPSIS + release_ddl_log() + RETURN VALUES + NONE +*/ + +void release_ddl_log() +{ + DDL_LOG_MEMORY_ENTRY *free_list= global_ddl_log.first_free; + DDL_LOG_MEMORY_ENTRY *used_list= global_ddl_log.first_used; + DBUG_ENTER("release_ddl_log"); + + pthread_mutex_lock(&LOCK_gdl); + while (used_list) + { + DDL_LOG_MEMORY_ENTRY *tmp= used_list->next_log_entry; + my_free((char*)used_list, MYF(0)); + used_list= tmp; + } + while (free_list) + { + DDL_LOG_MEMORY_ENTRY *tmp= free_list->next_log_entry; + my_free((char*)free_list, MYF(0)); + free_list= tmp; + } + close_ddl_log(); + global_ddl_log.inited= 0; + pthread_mutex_unlock(&LOCK_gdl); + VOID(pthread_mutex_destroy(&LOCK_gdl)); + DBUG_VOID_RETURN; +} + + +/* +--------------------------------------------------------------------------- + + END MODULE DDL log + -------------------- + +--------------------------------------------------------------------------- +*/ + + +/* + SYNOPSIS + mysql_write_frm() + lpt Struct carrying many parameters needed for this + method + flags Flags as defined below + WFRM_INITIAL_WRITE If set we need to prepare table before + creating the frm file + WFRM_CREATE_HANDLER_FILES If set we need to create the handler file as + part of the creation of the frm file + WFRM_PACK_FRM If set we should pack the frm file and delete + the frm file + + RETURN VALUES + TRUE Error + FALSE Success + + DESCRIPTION + A support method that creates a new frm file and in this process it + regenerates the partition data. It works fine also for non-partitioned + tables since it only handles partitioned data if it exists. +*/ + +bool mysql_write_frm(ALTER_PARTITION_PARAM_TYPE *lpt, uint flags) +{ + /* + Prepare table to prepare for writing a new frm file where the + partitions in add/drop state have temporarily changed their state + We set tmp_table to avoid get errors on naming of primary key index. + */ + int error= 0; + char path[FN_REFLEN+1]; + char shadow_path[FN_REFLEN+1]; + char shadow_frm_name[FN_REFLEN+1]; + char frm_name[FN_REFLEN+1]; + DBUG_ENTER("mysql_write_frm"); + + /* + Build shadow frm file name + */ + build_table_filename(shadow_path, sizeof(shadow_path), lpt->db, + lpt->table_name, "#", 0); + strxmov(shadow_frm_name, shadow_path, reg_ext, NullS); + if (flags & WFRM_WRITE_SHADOW) + { + if (mysql_copy_create_list(lpt->create_list, + &lpt->new_create_list) || + mysql_copy_key_list(lpt->key_list, + &lpt->new_key_list) || + mysql_prepare_table(lpt->thd, lpt->create_info, + &lpt->new_create_list, + &lpt->new_key_list, + /*tmp_table*/ 1, + &lpt->db_options, + lpt->table->file, + &lpt->key_info_buffer, + &lpt->key_count, + /*select_field_count*/ 0)) + { + DBUG_RETURN(TRUE); + } +#ifdef WITH_PARTITION_STORAGE_ENGINE + { + partition_info *part_info= lpt->table->part_info; + char *part_syntax_buf; + uint syntax_len; + + if (part_info) + { + if (!(part_syntax_buf= generate_partition_syntax(part_info, + &syntax_len, + TRUE, TRUE))) + { + DBUG_RETURN(TRUE); + } + part_info->part_info_string= part_syntax_buf; + part_info->part_info_len= syntax_len; + } + } +#endif + /* Write shadow frm file */ + lpt->create_info->table_options= lpt->db_options; + if ((mysql_create_frm(lpt->thd, shadow_frm_name, lpt->db, + lpt->table_name, lpt->create_info, + lpt->new_create_list, lpt->key_count, + lpt->key_info_buffer, lpt->table->file)) || + lpt->table->file->create_handler_files(shadow_path, NULL, + CHF_CREATE_FLAG, + lpt->create_info)) + { + my_delete(shadow_frm_name, MYF(0)); + error= 1; + goto end; + } + } + if (flags & WFRM_PACK_FRM) + { + /* + We need to pack the frm file and after packing it we delete the + frm file to ensure it doesn't get used. This is only used for + handlers that have the main version of the frm file stored in the + handler. + */ + const void *data= 0; + uint length= 0; + if (readfrm(shadow_path, &data, &length) || + packfrm(data, length, &lpt->pack_frm_data, &lpt->pack_frm_len)) + { + my_free((char*)data, MYF(MY_ALLOW_ZERO_PTR)); + my_free((char*)lpt->pack_frm_data, MYF(MY_ALLOW_ZERO_PTR)); + mem_alloc_error(length); + error= 1; + goto end; + } + error= my_delete(shadow_frm_name, MYF(MY_WME)); + } + if (flags & WFRM_INSTALL_SHADOW) + { +#ifdef WITH_PARTITION_STORAGE_ENGINE + partition_info *part_info= lpt->part_info; +#endif + /* + Build frm file name + */ + build_table_filename(path, sizeof(path), lpt->db, + lpt->table_name, "", 0); + strxmov(frm_name, path, reg_ext, NullS); + /* + When we are changing to use new frm file we need to ensure that we + don't collide with another thread in process to open the frm file. + We start by deleting the .frm file and possible .par file. Then we + write to the DDL log that we have completed the delete phase by + increasing the phase of the log entry. Next step is to rename the + new .frm file and the new .par file to the real name. After + completing this we write a new phase to the log entry that will + deactivate it. + */ + VOID(pthread_mutex_lock(&LOCK_open)); + if (my_delete(frm_name, MYF(MY_WME)) || +#ifdef WITH_PARTITION_STORAGE_ENGINE + lpt->table->file->create_handler_files(path, shadow_path, + CHF_DELETE_FLAG, NULL) || + deactivate_ddl_log_entry(part_info->frm_log_entry->entry_pos) || + (sync_ddl_log(), FALSE) || +#endif +#ifdef WITH_PARTITION_STORAGE_ENGINE + my_rename(shadow_frm_name, frm_name, MYF(MY_WME)) || + lpt->table->file->create_handler_files(path, shadow_path, + CHF_RENAME_FLAG, NULL)) +#else + my_rename(shadow_frm_name, frm_name, MYF(MY_WME))) +#endif + { + error= 1; + } + VOID(pthread_mutex_unlock(&LOCK_open)); +#ifdef WITH_PARTITION_STORAGE_ENGINE + deactivate_ddl_log_entry(part_info->frm_log_entry->entry_pos); + part_info->frm_log_entry= NULL; + VOID(sync_ddl_log()); +#endif + } + +end: + DBUG_RETURN(error); +} + + +/* + SYNOPSIS + write_bin_log() + thd Thread object + clear_error is clear_error to be called + query Query to log + query_length Length of query + + RETURN VALUES + NONE + + DESCRIPTION + Write the binlog if open, routine used in multiple places in this + file +*/ + +void write_bin_log(THD *thd, bool clear_error, + char const *query, ulong query_length) +{ + if (mysql_bin_log.is_open()) + { + if (clear_error) + thd->clear_error(); + thd->binlog_query(THD::STMT_QUERY_TYPE, + query, query_length, FALSE, FALSE); + } +} + /* delete (drop) tables. @@ -218,13 +1604,50 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, bool dont_log_query) { TABLE_LIST *table; - char path[FN_REFLEN], *alias; + char path[FN_REFLEN], *alias; + uint path_length; String wrong_tables; int error; + int non_temp_tables_count= 0; bool some_tables_deleted=0, tmp_table_deleted=0, foreign_key_error=0; - + String built_query; DBUG_ENTER("mysql_rm_table_part2"); + LINT_INIT(alias); + LINT_INIT(path_length); + safe_mutex_assert_owner(&LOCK_open); + + if (thd->current_stmt_binlog_row_based && !dont_log_query) + { + built_query.set_charset(system_charset_info); + if (if_exists) + built_query.append("DROP TABLE IF EXISTS "); + else + built_query.append("DROP TABLE "); + } + /* + If we have the table in the definition cache, we don't have to check the + .frm file to find if the table is a normal table (not view) and what + engine to use. + */ + + for (table= tables; table; table= table->next_local) + { + TABLE_SHARE *share; + table->db_type= NULL; + if ((share= get_cached_table_share(table->db, table->table_name))) + table->db_type= share->db_type; + + /* Disable drop of enabled log tables */ + if (share && share->log_table && + check_if_log_table(table->db_length, table->db, + table->table_name_length, table->table_name, 1)) + { + my_error(ER_BAD_LOG_STATEMENT, MYF(0), "DROP"); + DBUG_RETURN(1); + } + } + if (!drop_temporary && lock_table_names(thd, tables)) DBUG_RETURN(1); @@ -234,37 +1657,72 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, for (table= tables; table; table= table->next_local) { char *db=table->db; - db_type table_type= DB_TYPE_UNKNOWN; + handlerton *table_type; + enum legacy_db_type frm_db_type; mysql_ha_flush(thd, table, MYSQL_HA_CLOSE_FINAL, TRUE); - if (!close_temporary_table(thd, db, table->table_name)) + if (!close_temporary_table(thd, table)) { tmp_table_deleted=1; continue; // removed temporary table } + /* + If row-based replication is used and the table is not a + temporary table, we add the table name to the drop statement + being built. The string always end in a comma and the comma + will be chopped off before being written to the binary log. + */ + if (thd->current_stmt_binlog_row_based && !dont_log_query) + { + non_temp_tables_count++; + /* + Don't write the database name if it is the current one (or if + thd->db is NULL). + */ + built_query.append("`"); + if (thd->db == NULL || strcmp(db,thd->db) != 0) + { + built_query.append(db); + built_query.append("`.`"); + } + + built_query.append(table->table_name); + built_query.append("`,"); + } + error=0; + table_type= table->db_type; if (!drop_temporary) { + TABLE *locked_table; abort_locked_tables(thd, db, table->table_name); remove_table_from_cache(thd, db, table->table_name, RTFC_WAIT_OTHER_THREAD_FLAG | RTFC_CHECK_KILLED_FLAG); - drop_locked_tables(thd, db, table->table_name); + /* + If the table was used in lock tables, remember it so that + unlock_table_names can free it + */ + if ((locked_table= drop_locked_tables(thd, db, table->table_name))) + table->table= locked_table; + if (thd->killed) { thd->no_warnings_for_error= 0; DBUG_RETURN(-1); } alias= (lower_case_table_names == 2) ? table->alias : table->table_name; - /* remove form file and isam files */ - build_table_path(path, sizeof(path), db, alias, reg_ext); + /* remove .frm file and engine files */ + path_length= build_table_filename(path, sizeof(path), + db, alias, reg_ext, 0); } if (drop_temporary || - (access(path,F_OK) && - ha_create_table_from_engine(thd,db,alias)) || - (!drop_view && - mysql_frm_type(thd, path, &table_type) != FRMTYPE_TABLE)) + (table_type == NULL && + (access(path, F_OK) && + ha_create_table_from_engine(thd, db, alias)) || + (!drop_view && + mysql_frm_type(thd, path, &frm_db_type) != FRMTYPE_TABLE))) { // Table was not found on disk and table can't be created from engine if (if_exists) @@ -277,13 +1735,17 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, else { char *end; - if (table_type == DB_TYPE_UNKNOWN) - mysql_frm_type(thd, path, &table_type); - *(end=fn_ext(path))=0; // Remove extension for delete - error= ha_delete_table(thd, table_type, path, table->table_name, + if (table_type == NULL) + { + mysql_frm_type(thd, path, &frm_db_type); + table_type= ha_resolve_by_legacy_type(thd, frm_db_type); + } + // Remove extension for delete + *(end= path + path_length - reg_ext_length)= '\0'; + error= ha_delete_table(thd, table_type, path, db, table->table_name, !dont_log_query); if ((error == ENOENT || error == HA_ERR_NO_SUCH_TABLE) && - (if_exists || table_type == DB_TYPE_UNKNOWN)) + (if_exists || table_type == NULL)) error= 0; if (error == HA_ERR_ROW_IS_REFERENCED) { @@ -326,12 +1788,48 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, if (some_tables_deleted || tmp_table_deleted || !error) { query_cache_invalidate3(thd, tables, 0); - if (!dont_log_query && mysql_bin_log.is_open()) + if (!dont_log_query) { - if (!error) - thd->clear_error(); - Query_log_event qinfo(thd, thd->query, thd->query_length, FALSE, FALSE); - mysql_bin_log.write(&qinfo); + if (!thd->current_stmt_binlog_row_based || + non_temp_tables_count > 0 && !tmp_table_deleted) + { + /* + In this case, we are either using statement-based + replication or using row-based replication but have only + deleted one or more non-temporary tables (and no temporary + tables). In this case, we can write the original query into + the binary log. + */ + write_bin_log(thd, !error, thd->query, thd->query_length); + } + else if (thd->current_stmt_binlog_row_based && + non_temp_tables_count > 0 && + tmp_table_deleted) + { + /* + In this case we have deleted both temporary and + non-temporary tables, so: + - since we have deleted a non-temporary table we have to + binlog the statement, but + - since we have deleted a temporary table we cannot binlog + the statement (since the table has not been created on the + slave, this might cause the slave to stop). + + Instead, we write a built statement, only containing the + non-temporary tables, to the binary log + */ + built_query.chop(); // Chop of the last comma + built_query.append(" /* generated by server */"); + write_bin_log(thd, !error, built_query.ptr(), built_query.length()); + } + /* + The remaining cases are: + - no tables where deleted and + - only temporary tables where deleted and row-based + replication is used. + In both these cases, nothing should be written to the binary + log. + */ } } @@ -342,16 +1840,35 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, } -int quick_rm_table(enum db_type base,const char *db, - const char *table_name) +/* + 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(). + + RETURN + 0 OK + != 0 Error +*/ + +bool quick_rm_table(handlerton *base,const char *db, + const char *table_name, uint flags) { char path[FN_REFLEN]; - int error=0; - build_table_path(path, sizeof(path), db, table_name, reg_ext); + bool error= 0; + DBUG_ENTER("quick_rm_table"); + + uint path_length= build_table_filename(path, sizeof(path), + db, table_name, reg_ext, flags); if (my_delete(path,MYF(0))) - error=1; /* purecov: inspected */ - *fn_ext(path)= 0; // Remove reg_ext - return ha_delete_table(current_thd, base, path, table_name, 0) || error; + error= 1; /* purecov: inspected */ + path[path_length - reg_ext_length]= '\0'; // Remove reg_ext + DBUG_RETURN(ha_delete_table(current_thd, base, path, db, table_name, 0) || + error); } /* @@ -495,7 +2012,7 @@ void calculate_interval_lengths(CHARSET_INFO *cs, TYPELIB *interval, int prepare_create_field(create_field *sql_field, uint *blob_columns, int *timestamps, int *timestamps_with_niladic, - uint table_flags) + longlong table_flags) { DBUG_ENTER("prepare_field"); @@ -506,10 +2023,10 @@ int prepare_create_field(create_field *sql_field, DBUG_ASSERT(sql_field->charset); switch (sql_field->sql_type) { - case FIELD_TYPE_BLOB: - case FIELD_TYPE_MEDIUM_BLOB: - case FIELD_TYPE_TINY_BLOB: - case FIELD_TYPE_LONG_BLOB: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_LONG_BLOB: sql_field->pack_flag=FIELDFLAG_BLOB | pack_length_to_packflag(sql_field->pack_length - portable_sizeof_char_ptr); @@ -519,7 +2036,7 @@ int prepare_create_field(create_field *sql_field, sql_field->unireg_check=Field::BLOB_FIELD; (*blob_columns)++; break; - case FIELD_TYPE_GEOMETRY: + case MYSQL_TYPE_GEOMETRY: #ifdef HAVE_SPATIAL if (!(table_flags & HA_CAN_GEOMETRY)) { @@ -559,12 +2076,12 @@ int prepare_create_field(create_field *sql_field, } #endif /* fall through */ - case FIELD_TYPE_STRING: + case MYSQL_TYPE_STRING: sql_field->pack_flag=0; if (sql_field->charset->state & MY_CS_BINSORT) sql_field->pack_flag|=FIELDFLAG_BINARY; break; - case FIELD_TYPE_ENUM: + case MYSQL_TYPE_ENUM: sql_field->pack_flag=pack_length_to_packflag(sql_field->pack_length) | FIELDFLAG_INTERVAL; if (sql_field->charset->state & MY_CS_BINSORT) @@ -574,7 +2091,7 @@ int prepare_create_field(create_field *sql_field, sql_field->interval, sql_field->charset); break; - case FIELD_TYPE_SET: + case MYSQL_TYPE_SET: sql_field->pack_flag=pack_length_to_packflag(sql_field->pack_length) | FIELDFLAG_BITFIELD; if (sql_field->charset->state & MY_CS_BINSORT) @@ -584,19 +2101,19 @@ int prepare_create_field(create_field *sql_field, sql_field->interval, sql_field->charset); break; - case FIELD_TYPE_DATE: // Rest of string types - case FIELD_TYPE_NEWDATE: - case FIELD_TYPE_TIME: - case FIELD_TYPE_DATETIME: - case FIELD_TYPE_NULL: + case MYSQL_TYPE_DATE: // Rest of string types + case MYSQL_TYPE_NEWDATE: + case MYSQL_TYPE_TIME: + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_NULL: sql_field->pack_flag=f_settype((uint) sql_field->sql_type); break; - case FIELD_TYPE_BIT: + case MYSQL_TYPE_BIT: /* We have sql_field->pack_flag already set here, see mysql_prepare_table(). */ break; - case FIELD_TYPE_NEWDECIMAL: + case MYSQL_TYPE_NEWDECIMAL: sql_field->pack_flag=(FIELDFLAG_NUMBER | (sql_field->flags & UNSIGNED_FLAG ? 0 : FIELDFLAG_DECIMAL) | @@ -604,7 +2121,7 @@ int prepare_create_field(create_field *sql_field, FIELDFLAG_ZEROFILL : 0) | (sql_field->decimals << FIELDFLAG_DEC_SHIFT)); break; - case FIELD_TYPE_TIMESTAMP: + case MYSQL_TYPE_TIMESTAMP: /* We should replace old TIMESTAMP fields with their newer analogs */ if (sql_field->unireg_check == Field::TIMESTAMP_OLD_FIELD) { @@ -643,10 +2160,16 @@ int prepare_create_field(create_field *sql_field, SYNOPSIS mysql_prepare_table() - thd Thread object - create_info Create information (like MAX_ROWS) - fields List of fields to create - keys List of keys to create + thd Thread object. + create_info Create information (like MAX_ROWS). + fields List of fields to create. + keys List of keys to create. + tmp_table If a temporary table is to be created. + db_options INOUT Table options (like HA_OPTION_PACK_RECORD). + file The handler for the new table. + key_info_buffer OUT An array of KEY structs for the indexes. + key_count OUT The number of elements in the array. + select_field_count The number of fields coming from a select table. DESCRIPTION Prepares the table and key structures for table creation. @@ -723,10 +2246,10 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, */ if (sql_field->def && save_cs != sql_field->def->collation.collation && - (sql_field->sql_type == FIELD_TYPE_VAR_STRING || - sql_field->sql_type == FIELD_TYPE_STRING || - sql_field->sql_type == FIELD_TYPE_SET || - sql_field->sql_type == FIELD_TYPE_ENUM)) + (sql_field->sql_type == MYSQL_TYPE_VAR_STRING || + sql_field->sql_type == MYSQL_TYPE_STRING || + sql_field->sql_type == MYSQL_TYPE_SET || + sql_field->sql_type == MYSQL_TYPE_ENUM)) { Query_arena backup_arena; bool need_to_change_arena= !thd->stmt_arena->is_conventional(); @@ -751,8 +2274,8 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, } } - if (sql_field->sql_type == FIELD_TYPE_SET || - sql_field->sql_type == FIELD_TYPE_ENUM) + if (sql_field->sql_type == MYSQL_TYPE_SET || + sql_field->sql_type == MYSQL_TYPE_ENUM) { uint32 dummy; CHARSET_INFO *cs= sql_field->charset; @@ -798,7 +2321,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, interval->type_lengths[i]); interval->type_lengths[i]= lengthsp; ((uchar *)interval->type_names[i])[lengthsp]= '\0'; - if (sql_field->sql_type == FIELD_TYPE_SET) + if (sql_field->sql_type == MYSQL_TYPE_SET) { if (cs->coll->instr(cs, interval->type_names[i], interval->type_lengths[i], @@ -812,7 +2335,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, sql_field->interval_list.empty(); // Don't need interval_list anymore } - if (sql_field->sql_type == FIELD_TYPE_SET) + if (sql_field->sql_type == MYSQL_TYPE_SET) { uint32 field_length; if (sql_field->def != NULL) @@ -848,10 +2371,10 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, calculate_interval_lengths(cs, interval, &dummy, &field_length); sql_field->length= field_length + (interval->count - 1); } - else /* FIELD_TYPE_ENUM */ + else /* MYSQL_TYPE_ENUM */ { uint32 field_length; - DBUG_ASSERT(sql_field->sql_type == FIELD_TYPE_ENUM); + DBUG_ASSERT(sql_field->sql_type == MYSQL_TYPE_ENUM); if (sql_field->def != NULL) { String str, *def= sql_field->def->val_str(&str); @@ -881,10 +2404,10 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, set_if_smaller(sql_field->length, MAX_FIELD_WIDTH-1); } - if (sql_field->sql_type == FIELD_TYPE_BIT) + if (sql_field->sql_type == MYSQL_TYPE_BIT) { sql_field->pack_flag= FIELDFLAG_NUMBER; - if (file->table_flags() & HA_CAN_BIT_FIELD) + if (file->ha_table_flags() & HA_CAN_BIT_FIELD) total_uneven_bit_length+= sql_field->length & 7; else sql_field->pack_flag|= FIELDFLAG_TREAT_BIT_AS_CHAR; @@ -967,7 +2490,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, if (prepare_create_field(sql_field, &blob_columns, ×tamps, ×tamps_with_niladic, - file->table_flags())) + file->ha_table_flags())) DBUG_RETURN(-1); if (sql_field->sql_type == MYSQL_TYPE_VARCHAR) create_info->varchar= 1; @@ -988,14 +2511,14 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, DBUG_RETURN(-1); } if (auto_increment && - (file->table_flags() & HA_NO_AUTO_INCREMENT)) + (file->ha_table_flags() & HA_NO_AUTO_INCREMENT)) { my_message(ER_TABLE_CANT_HANDLE_AUTO_INCREMENT, ER(ER_TABLE_CANT_HANDLE_AUTO_INCREMENT), MYF(0)); DBUG_RETURN(-1); } - if (blob_columns && (file->table_flags() & HA_NO_BLOBS)) + if (blob_columns && (file->ha_table_flags() & HA_NO_BLOBS)) { my_message(ER_TABLE_CANT_HANDLE_BLOB, ER(ER_TABLE_CANT_HANDLE_BLOB), MYF(0)); @@ -1017,6 +2540,8 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, while ((key=key_iterator++)) { + DBUG_PRINT("info", ("key name: '%s' type: %d", key->name ? key->name : + "(none)" , key->type)); if (key->type == Key::FOREIGN_KEY) { fk_key_count++; @@ -1077,7 +2602,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, key_parts+=key->columns.elements; else (*key_count)--; - if (key->name && !tmp_table && + if (key->name && !tmp_table && (key->type != Key::PRIMARY) && !my_strcasecmp(system_charset_info,key->name,primary_key_name)) { my_error(ER_WRONG_NAME_FOR_INDEX, MYF(0), key->name); @@ -1091,7 +2616,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, DBUG_RETURN(-1); } - (*key_info_buffer) = key_info= (KEY*) sql_calloc(sizeof(KEY)* *key_count); + (*key_info_buffer)= key_info= (KEY*) sql_calloc(sizeof(KEY) * (*key_count)); key_part_info=(KEY_PART_INFO*) sql_calloc(sizeof(KEY_PART_INFO)*key_parts); if (!*key_info_buffer || ! key_part_info) DBUG_RETURN(-1); // Out of memory @@ -1113,12 +2638,16 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, break; } - switch(key->type){ + switch (key->type) { case Key::MULTIPLE: key_info->flags= 0; break; case Key::FULLTEXT: key_info->flags= HA_FULLTEXT; + if ((key_info->parser_name= &key->key_create_info.parser_name)->str) + key_info->flags|= HA_USES_PARSER; + else + key_info->parser_name= 0; break; case Key::SPATIAL: #ifdef HAVE_SPATIAL @@ -1142,11 +2671,11 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, key_info->key_parts=(uint8) key->columns.elements; key_info->key_part=key_part_info; key_info->usable_key_parts= key_number; - key_info->algorithm=key->algorithm; + key_info->algorithm= key->key_create_info.algorithm; if (key->type == Key::FULLTEXT) { - if (!(file->table_flags() & HA_CAN_FULLTEXT)) + if (!(file->ha_table_flags() & HA_CAN_FULLTEXT)) { my_message(ER_TABLE_CANT_HANDLE_FT, ER(ER_TABLE_CANT_HANDLE_FT), MYF(0)); @@ -1164,7 +2693,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, /* TODO: Add proper checks if handler supports key_type and algorithm */ if (key_info->flags & HA_SPATIAL) { - if (!(file->table_flags() & HA_CAN_RTREEKEYS)) + if (!(file->ha_table_flags() & HA_CAN_RTREEKEYS)) { my_message(ER_TABLE_CANT_HANDLE_SPKEYS, ER(ER_TABLE_CANT_HANDLE_SPKEYS), MYF(0)); @@ -1194,6 +2723,18 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, #endif } + /* Take block size from key part or table part */ + /* + TODO: Add warning if block size changes. We can't do it here, as + this may depend on the size of the key + */ + key_info->block_size= (key->key_create_info.block_size ? + key->key_create_info.block_size : + create_info->key_block_size); + + if (key_info->block_size) + key_info->flags|= HA_USES_BLOCK_SIZE; + List_iterator<key_part_spec> cols(key->columns), cols2(key->columns); CHARSET_INFO *ft_key_charset=0; // for FULLTEXT for (uint column_nr=0 ; (column=cols++) ; column_nr++) @@ -1254,7 +2795,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, if (f_is_blob(sql_field->pack_flag) || (f_is_geom(sql_field->pack_flag) && key->type != Key::SPATIAL)) { - if (!(file->table_flags() & HA_CAN_INDEX_BLOBS)) + if (!(file->ha_table_flags() & HA_CAN_INDEX_BLOBS)) { my_error(ER_BLOB_USED_AS_KEY, MYF(0), column->field_name); DBUG_RETURN(-1); @@ -1291,22 +2832,24 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, null_fields--; } else - key_info->flags|= HA_NULL_PART_KEY; - if (!(file->table_flags() & HA_NULL_IN_KEY)) - { - my_error(ER_NULL_COLUMN_IN_INDEX, MYF(0), column->field_name); - DBUG_RETURN(-1); - } - if (key->type == Key::SPATIAL) - { - my_message(ER_SPATIAL_CANT_HAVE_NULL, - ER(ER_SPATIAL_CANT_HAVE_NULL), MYF(0)); - DBUG_RETURN(-1); - } + { + key_info->flags|= HA_NULL_PART_KEY; + if (!(file->ha_table_flags() & HA_NULL_IN_KEY)) + { + my_error(ER_NULL_COLUMN_IN_INDEX, MYF(0), column->field_name); + DBUG_RETURN(-1); + } + if (key->type == Key::SPATIAL) + { + my_message(ER_SPATIAL_CANT_HAVE_NULL, + ER(ER_SPATIAL_CANT_HAVE_NULL), MYF(0)); + DBUG_RETURN(-1); + } + } } if (MTYP_TYPENR(sql_field->unireg_check) == Field::NEXT_NUMBER) { - if (column_nr == 0 || (file->table_flags() & HA_AUTO_PART_KEY)) + if (column_nr == 0 || (file->ha_table_flags() & HA_AUTO_PART_KEY)) auto_increment--; // Field is used } } @@ -1343,14 +2886,14 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, else if (!f_is_geom(sql_field->pack_flag) && (column->length > length || ((f_is_packed(sql_field->pack_flag) || - ((file->table_flags() & HA_NO_PREFIX_CHAR_KEYS) && + ((file->ha_table_flags() & HA_NO_PREFIX_CHAR_KEYS) && (key_info->flags & HA_NOSAME))) && column->length != length))) { my_message(ER_WRONG_SUB_KEY, ER(ER_WRONG_SUB_KEY), MYF(0)); DBUG_RETURN(-1); } - else if (!(file->table_flags() & HA_NO_PREFIX_CHAR_KEYS)) + else if (!(file->ha_table_flags() & HA_NO_PREFIX_CHAR_KEYS)) length=column->length; } else if (length == 0) @@ -1436,7 +2979,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, key_info++; } if (!unique_key && !primary_key && - (file->table_flags() & HA_REQUIRE_PRIMARY_KEY)) + (file->ha_table_flags() & HA_REQUIRE_PRIMARY_KEY)) { my_message(ER_REQUIRES_PRIMARY_KEY, ER(ER_REQUIRES_PRIMARY_KEY), MYF(0)); DBUG_RETURN(-1); @@ -1456,6 +2999,38 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, /* + Set table default charset, if not set + + SYNOPSIS + set_table_default_charset() + create_info Table create information + + DESCRIPTION + If the table character set was not given explicitely, + let's fetch the database default character set and + apply it to the table. +*/ + +static void set_table_default_charset(THD *thd, + HA_CREATE_INFO *create_info, char *db) +{ + /* + If the table character set was not given explicitly, + let's fetch the database default character set and + apply it to the table. + */ + if (!create_info->default_table_charset) + { + HA_CREATE_INFO db_info; + + load_db_opt_by_name(thd, db, &db_info); + + create_info->default_table_charset= db_info.default_table_charset; + } +} + + +/* Extend long VARCHAR fields to blob & prepare field if it's a blob SYNOPSIS @@ -1485,7 +3060,7 @@ static bool prepare_blob_field(THD *thd, create_field *sql_field) MAX_FIELD_VARCHARLENGTH / sql_field->charset->mbmaxlen); DBUG_RETURN(1); } - sql_field->sql_type= FIELD_TYPE_BLOB; + sql_field->sql_type= MYSQL_TYPE_BLOB; sql_field->flags|= BLOB_FLAG; sprintf(warn_buff, ER(ER_AUTO_CONVERT), sql_field->field_name, (sql_field->charset == &my_charset_bin) ? "VARBINARY" : "VARCHAR", @@ -1496,7 +3071,7 @@ static bool prepare_blob_field(THD *thd, create_field *sql_field) if ((sql_field->flags & BLOB_FLAG) && sql_field->length) { - if (sql_field->sql_type == FIELD_TYPE_BLOB) + if (sql_field->sql_type == MYSQL_TYPE_BLOB) { /* The user has given a length to the blob column */ sql_field->sql_type= get_blob_type_from_length(sql_field->length); @@ -1524,11 +3099,11 @@ static bool prepare_blob_field(THD *thd, create_field *sql_field) void sp_prepare_create_field(THD *thd, create_field *sql_field) { - if (sql_field->sql_type == FIELD_TYPE_SET || - sql_field->sql_type == FIELD_TYPE_ENUM) + if (sql_field->sql_type == MYSQL_TYPE_SET || + sql_field->sql_type == MYSQL_TYPE_ENUM) { uint32 field_length, dummy; - if (sql_field->sql_type == FIELD_TYPE_SET) + if (sql_field->sql_type == MYSQL_TYPE_SET) { calculate_interval_lengths(sql_field->charset, sql_field->interval, &dummy, @@ -1536,7 +3111,7 @@ void sp_prepare_create_field(THD *thd, create_field *sql_field) sql_field->length= field_length + (sql_field->interval->count - 1); } - else /* FIELD_TYPE_ENUM */ + else /* MYSQL_TYPE_ENUM */ { calculate_interval_lengths(sql_field->charset, sql_field->interval, @@ -1546,7 +3121,7 @@ void sp_prepare_create_field(THD *thd, create_field *sql_field) set_if_smaller(sql_field->length, MAX_FIELD_WIDTH-1); } - if (sql_field->sql_type == FIELD_TYPE_BIT) + if (sql_field->sql_type == MYSQL_TYPE_BIT) { sql_field->pack_flag= FIELDFLAG_NUMBER | FIELDFLAG_TREAT_BIT_AS_CHAR; @@ -1559,18 +3134,47 @@ void sp_prepare_create_field(THD *thd, create_field *sql_field) /* + Copy HA_CREATE_INFO struct + SYNOPSIS + copy_create_info() + lex_create_info The create_info struct setup by parser + RETURN VALUES + > 0 A pointer to a copy of the lex_create_info + 0 Memory allocation error + DESCRIPTION + Allocate memory for copy of HA_CREATE_INFO structure from parser + to ensure we can reuse the parser struct in stored procedures + and prepared statements. +*/ + +static HA_CREATE_INFO *copy_create_info(HA_CREATE_INFO *lex_create_info) +{ + HA_CREATE_INFO *create_info; + if (!(create_info= (HA_CREATE_INFO*)sql_alloc(sizeof(HA_CREATE_INFO)))) + mem_alloc_error(sizeof(HA_CREATE_INFO)); + else + memcpy((void*)create_info, (void*)lex_create_info, sizeof(HA_CREATE_INFO)); + return create_info; +} + + +/* Create a table SYNOPSIS - mysql_create_table() + mysql_create_table_internal() thd Thread object db Database table_name Table name - create_info Create information (like MAX_ROWS) + lex_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 + use_copy_create_info Should we make a copy of create info (we do this + when this is called from sql_parse.cc where we + want to ensure lex object isn't manipulated. DESCRIPTION If one creates a temporary table, this is automatically opened @@ -1585,20 +3189,36 @@ void sp_prepare_create_field(THD *thd, create_field *sql_field) TRUE error */ -bool mysql_create_table(THD *thd,const char *db, const char *table_name, - HA_CREATE_INFO *create_info, - List<create_field> &fields, - List<Key> &keys,bool internal_tmp_table, - uint select_field_count) +bool mysql_create_table_internal(THD *thd, + const char *db, const char *table_name, + HA_CREATE_INFO *lex_create_info, + List<create_field> &fields, + List<Key> &keys,bool internal_tmp_table, + uint select_field_count, + bool use_copy_create_info) { char path[FN_REFLEN]; + uint path_length; const char *alias; uint db_options, key_count; KEY *key_info_buffer; + HA_CREATE_INFO *create_info; handler *file; bool error= TRUE; - DBUG_ENTER("mysql_create_table"); + DBUG_ENTER("mysql_create_table_internal"); + DBUG_PRINT("enter", ("db: '%s' table: '%s' tmp: %d", + db, table_name, internal_tmp_table)); + if (use_copy_create_info) + { + if (!(create_info= copy_create_info(lex_create_info))) + { + DBUG_RETURN(TRUE); + } + } + else + create_info= lex_create_info; + /* Check for duplicate fields and check type of table to create */ if (!fields.elements) { @@ -1606,81 +3226,215 @@ bool mysql_create_table(THD *thd,const char *db, const char *table_name, MYF(0)); DBUG_RETURN(TRUE); } - if (check_engine(thd, table_name, &create_info->db_type)) + if (check_engine(thd, table_name, create_info)) DBUG_RETURN(TRUE); db_options= create_info->table_options; if (create_info->row_type == ROW_TYPE_DYNAMIC) db_options|=HA_OPTION_PACK_RECORD; alias= table_case_name(create_info, table_name); - file= get_new_handler((TABLE*) 0, thd->mem_root, create_info->db_type); - -#ifdef NOT_USED - /* - if there is a technical reason for a handler not to have support - for temp. tables this code can be re-enabled. - Otherwise, if a handler author has a wish to prohibit usage of - temporary tables for his handler he should implement a check in - ::create() method - */ - if ((create_info->options & HA_LEX_CREATE_TMP_TABLE) && - (file->table_flags() & HA_NO_TEMP_TABLES)) + if (!(file= get_new_handler((TABLE_SHARE*) 0, thd->mem_root, + create_info->db_type))) { - my_error(ER_ILLEGAL_HA, MYF(0), table_name); + mem_alloc_error(sizeof(handler)); DBUG_RETURN(TRUE); } -#endif +#ifdef WITH_PARTITION_STORAGE_ENGINE + partition_info *part_info= thd->work_part_info; - /* - If the table character set was not given explicitely, - let's fetch the database default character set and - apply it to the table. - */ - if (!create_info->default_table_charset) + if (!part_info && create_info->db_type->partition_flags && + (create_info->db_type->partition_flags() & HA_USE_AUTO_PARTITION)) { - HA_CREATE_INFO db_info; - - load_db_opt_by_name(thd, db, &db_info); + /* + Table is not defined as a partitioned table but the engine handles + all tables as partitioned. The handler will set up the partition info + object with the default settings. + */ + thd->work_part_info= part_info= new partition_info(); + if (!part_info) + { + mem_alloc_error(sizeof(partition_info)); + DBUG_RETURN(TRUE); + } + file->set_auto_partitions(part_info); + part_info->default_engine_type= create_info->db_type; + part_info->is_auto_partitioned= TRUE; + } + if (part_info) + { + /* + The table has been specified as a partitioned table. + If this is part of an ALTER TABLE the handler will be the partition + handler but we need to specify the default handler to use for + 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(keys); + Key *key; + handlerton *part_engine_type= create_info->db_type; + char *part_syntax_buf; + uint syntax_len; + handlerton *engine_type; + if (create_info->options & HA_LEX_CREATE_TMP_TABLE) + { + 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_CANNOT_ADD_FOREIGN, MYF(0)); + goto err; + } + } + if ((part_engine_type == partition_hton) && + part_info->default_engine_type) + { + /* + This only happens at ALTER TABLE. + default_engine_type was assigned from the engine set in the ALTER + TABLE command. + */ + ; + } + else + { + if (create_info->used_fields & HA_CREATE_USED_ENGINE) + { + part_info->default_engine_type= create_info->db_type; + } + else + { + if (part_info->default_engine_type == NULL) + { + part_info->default_engine_type= ha_checktype(thd, + DB_TYPE_DEFAULT, 0, 0); + } + } + } + DBUG_PRINT("info", ("db_type = %d", + ha_legacy_type(part_info->default_engine_type))); + if (part_info->check_partition_info(thd, &engine_type, file, + create_info, TRUE)) + goto err; + part_info->default_engine_type= engine_type; - create_info->default_table_charset= db_info.default_table_charset; + /* + We reverse the partitioning parser and generate a standard format + for syntax stored in frm file. + */ + if (!(part_syntax_buf= generate_partition_syntax(part_info, + &syntax_len, + TRUE, TRUE))) + goto err; + part_info->part_info_string= part_syntax_buf; + part_info->part_info_len= syntax_len; + if ((!(engine_type->partition_flags && + engine_type->partition_flags() & HA_CAN_PARTITION)) || + create_info->db_type == partition_hton) + { + /* + The handler assigned to the table cannot handle partitioning. + Assign the partition handler as the handler of the table. + */ + DBUG_PRINT("info", ("db_type: %d", + ha_legacy_type(create_info->db_type))); + delete file; + create_info->db_type= partition_hton; + if (!(file= get_ha_partition(part_info))) + { + DBUG_RETURN(TRUE); + } + /* + If we have default number of partitions or subpartitions we + might require to set-up the part_info object such that it + creates a proper .par file. The current part_info object is + only used to create the frm-file and .par-file. + */ + if (part_info->use_default_no_partitions && + part_info->no_parts && + (int)part_info->no_parts != + file->get_default_no_partitions(create_info)) + { + uint i; + List_iterator<partition_element> part_it(part_info->partitions); + part_it++; + DBUG_ASSERT(thd->lex->sql_command != SQLCOM_CREATE_TABLE); + for (i= 1; i < part_info->partitions.elements; i++) + (part_it++)->part_state= PART_TO_BE_DROPPED; + } + else if (part_info->is_sub_partitioned() && + part_info->use_default_no_subpartitions && + part_info->no_subparts && + (int)part_info->no_subparts != + file->get_default_no_partitions(create_info)) + { + DBUG_ASSERT(thd->lex->sql_command != SQLCOM_CREATE_TABLE); + part_info->no_subparts= file->get_default_no_partitions(create_info); + } + } + else if (create_info->db_type != engine_type) + { + /* + We come here when we don't use a partitioned handler. + Since we use a partitioned table it must be "native partitioned". + We have switched engine from defaults, most likely only specified + engines in partition clauses. + */ + delete file; + if (!(file= get_new_handler((TABLE_SHARE*) 0, thd->mem_root, + engine_type))) + { + mem_alloc_error(sizeof(handler)); + DBUG_RETURN(TRUE); + } + } } +#endif + + set_table_default_charset(thd, create_info, (char*) db); if (mysql_prepare_table(thd, create_info, &fields, &keys, internal_tmp_table, &db_options, file, &key_info_buffer, &key_count, select_field_count)) - DBUG_RETURN(TRUE); + goto err; /* Check if table exists */ if (create_info->options & HA_LEX_CREATE_TMP_TABLE) { - my_snprintf(path, sizeof(path), "%s%s%lx_%lx_%x%s", - mysql_tmpdir, tmp_file_prefix, current_pid, thd->thread_id, - thd->tmp_table++, reg_ext); + path_length= build_tmptable_filename(thd, path, sizeof(path)); if (lower_case_table_names) my_casedn_str(files_charset_info, path); create_info->table_options|=HA_CREATE_DELAY_KEY_WRITE; } else { - #ifdef FN_DEVCHAR - /* check if the table name contains FN_DEVCHAR when defined */ - const char *start= alias; - while (*start != '\0') - { - if (*start == FN_DEVCHAR) - { - my_error(ER_WRONG_TABLE_NAME, MYF(0), alias); - DBUG_RETURN(TRUE); - } - start++; - } - #endif - build_table_path(path, sizeof(path), db, alias, reg_ext); + #ifdef FN_DEVCHAR + /* check if the table name contains FN_DEVCHAR when defined */ + const char *start= alias; + while (*start != '\0') + { + if (*start == FN_DEVCHAR) + { + my_error(ER_WRONG_TABLE_NAME, MYF(0), alias); + DBUG_RETURN(TRUE); + } + start++; + } +#endif + path_length= build_table_filename(path, sizeof(path), 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_TMP_TABLE) && + find_temporary_table(thd, db, table_name)) { if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS) { @@ -1688,11 +3442,13 @@ bool mysql_create_table(THD *thd,const char *db, const char *table_name, push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, ER_TABLE_EXISTS_ERROR, ER(ER_TABLE_EXISTS_ERROR), alias); - DBUG_RETURN(FALSE); + error= 0; + goto err; } my_error(ER_TABLE_EXISTS_ERROR, MYF(0), alias); - DBUG_RETURN(TRUE); + goto err; } + VOID(pthread_mutex_lock(&LOCK_open)); if (!internal_tmp_table && !(create_info->options & HA_LEX_CREATE_TMP_TABLE)) { @@ -1701,7 +3457,20 @@ bool mysql_create_table(THD *thd,const char *db, const char *table_name, if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS) goto warn; my_error(ER_TABLE_EXISTS_ERROR,MYF(0),table_name); - goto end; + goto unlock_and_end; + } + /* + We don't assert here, but check the result, because the table could be + in the table definition cache and in the same time the .frm could be + missing from the disk, in case of manual intervention which deletes + the .frm file. The user has to use FLUSH TABLES; to clear the cache. + Then she could create the table. This case is pretty obscure and + therefore we don't introduce a new error message only for it. + */ + if (get_cached_table_share(db, alias)) + { + my_error(ER_TABLE_EXISTS_ERROR, MYF(0), table_name); + goto unlock_and_end; } } @@ -1725,7 +3494,7 @@ bool mysql_create_table(THD *thd,const char *db, const char *table_name, if (create_if_not_exists) goto warn; my_error(ER_TABLE_EXISTS_ERROR,MYF(0),table_name); - goto end; + goto unlock_and_end; } } @@ -1736,31 +3505,41 @@ bool mysql_create_table(THD *thd,const char *db, const char *table_name, create_info->data_file_name= create_info->index_file_name= 0; create_info->table_options=db_options; - if (rea_create_table(thd, path, db, table_name, - create_info, fields, key_count, - key_info_buffer)) - goto end; + path[path_length - reg_ext_length]= '\0'; // Remove .frm extension + if (rea_create_table(thd, path, db, table_name, create_info, fields, + key_count, key_info_buffer, file)) + goto unlock_and_end; + if (create_info->options & HA_LEX_CREATE_TMP_TABLE) { /* Open table and put in temporary table list */ if (!(open_temporary_table(thd, path, db, table_name, 1))) { (void) rm_temporary_table(create_info->db_type, path); - goto end; + goto unlock_and_end; } thd->tmp_table_used= 1; } - if (!internal_tmp_table && mysql_bin_log.is_open()) - { - thd->clear_error(); - Query_log_event qinfo(thd, thd->query, thd->query_length, FALSE, FALSE); - mysql_bin_log.write(&qinfo); - } - error= FALSE; -end: + /* + Don't write statement if: + - It is an internal temporary table, + - Row-based logging is used and it we are creating a temporary table, or + - The binary log is not open. + Otherwise, the statement shall be binlogged. + */ + if (!internal_tmp_table && + (!thd->current_stmt_binlog_row_based || + (thd->current_stmt_binlog_row_based && + !(create_info->options & HA_LEX_CREATE_TMP_TABLE)))) + write_bin_log(thd, TRUE, thd->query, thd->query_length); + error= FALSE; +unlock_and_end: VOID(pthread_mutex_unlock(&LOCK_open)); + +err: thd->proc_info="After create"; + delete file; DBUG_RETURN(error); warn: @@ -1769,9 +3548,54 @@ warn: ER_TABLE_EXISTS_ERROR, ER(ER_TABLE_EXISTS_ERROR), alias); create_info->table_existed= 1; // Mark that table existed - goto end; + goto unlock_and_end; +} + + +/* + Database locking aware wrapper for mysql_create_table_internal(), +*/ + +bool mysql_create_table(THD *thd, const char *db, const char *table_name, + HA_CREATE_INFO *create_info, + List<create_field> &fields, + List<Key> &keys,bool internal_tmp_table, + uint select_field_count, + bool use_copy_create_info) +{ + bool result; + DBUG_ENTER("mysql_create_table"); + + /* Wait for any database locks */ + pthread_mutex_lock(&LOCK_lock_db); + while (!thd->killed && + hash_search(&lock_db_cache,(byte*) db, strlen(db))) + { + wait_for_condition(thd, &LOCK_lock_db, &COND_refresh); + pthread_mutex_lock(&LOCK_lock_db); + } + + if (thd->killed) + { + pthread_mutex_unlock(&LOCK_lock_db); + DBUG_RETURN(TRUE); + } + creating_table++; + pthread_mutex_unlock(&LOCK_lock_db); + + result= mysql_create_table_internal(thd, db, table_name, create_info, + fields, keys, internal_tmp_table, + select_field_count, + use_copy_create_info); + + pthread_mutex_lock(&LOCK_lock_db); + if (!--creating_table && creating_database) + pthread_cond_signal(&COND_refresh); + pthread_mutex_unlock(&LOCK_lock_db); + DBUG_RETURN(result); } + /* ** Give the key name after the first field with an optional '_#' after **/ @@ -1815,24 +3639,50 @@ make_unique_key_name(const char *field_name,KEY *start,KEY *end) ** Alter a table definition ****************************************************************************/ + +/* + 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 +*/ + bool -mysql_rename_table(enum db_type base, - const char *old_db, - const char *old_name, - const char *new_db, - const char *new_name) +mysql_rename_table(handlerton *base, const char *old_db, + const char *old_name, const char *new_db, + const char *new_name, uint flags) { THD *thd= current_thd; char from[FN_REFLEN], to[FN_REFLEN], lc_from[FN_REFLEN], lc_to[FN_REFLEN]; char *from_base= from, *to_base= to; char tmp_name[NAME_LEN+1]; - handler *file= (base == DB_TYPE_UNKNOWN ? 0 : - get_new_handler((TABLE*) 0, thd->mem_root, base)); + handler *file; int error=0; DBUG_ENTER("mysql_rename_table"); + DBUG_PRINT("enter", ("old: '%s'.'%s' new: '%s'.'%s'", + old_db, old_name, new_db, new_name)); - build_table_path(from, sizeof(from), old_db, old_name, ""); - build_table_path(to, sizeof(to), new_db, new_name, ""); + file= (base == NULL ? 0 : + get_new_handler((TABLE_SHARE*) 0, thd->mem_root, base)); + + build_table_filename(from, sizeof(from), old_db, old_name, "", + flags & FN_FROM_IS_TMP); + build_table_filename(to, sizeof(to), new_db, new_name, "", + flags & FN_TO_IS_TMP); /* If lower_case_table_names == 2 (case-preserving but case-insensitive @@ -1840,22 +3690,24 @@ mysql_rename_table(enum db_type base, a lowercase file name, but we leave the .frm in mixed case. */ if (lower_case_table_names == 2 && file && - !(file->table_flags() & HA_FILE_BASED)) + !(file->ha_table_flags() & HA_FILE_BASED)) { strmov(tmp_name, old_name); my_casedn_str(files_charset_info, tmp_name); - build_table_path(lc_from, sizeof(lc_from), old_db, tmp_name, ""); + build_table_filename(lc_from, sizeof(lc_from), old_db, tmp_name, "", + flags & FN_FROM_IS_TMP); from_base= lc_from; strmov(tmp_name, new_name); my_casedn_str(files_charset_info, tmp_name); - build_table_path(lc_to, sizeof(lc_to), new_db, tmp_name, ""); + build_table_filename(lc_to, sizeof(lc_to), new_db, tmp_name, "", + flags & FN_TO_IS_TMP); to_base= lc_to; } if (!file || !(error=file->rename_table(from_base, to_base))) { - if (rename_file_ext(from,to,reg_ext)) + if (!(flags & NO_FRM_RENAME) && rename_file_ext(from,to,reg_ext)) { error=my_errno; /* Restore old file name */ @@ -1893,17 +3745,19 @@ mysql_rename_table(enum db_type base, static void wait_while_table_is_used(THD *thd,TABLE *table, enum ha_extra_function function) { - DBUG_PRINT("enter",("table: %s", table->s->table_name)); DBUG_ENTER("wait_while_table_is_used"); - safe_mutex_assert_owner(&LOCK_open); + DBUG_PRINT("enter", ("table: '%s' share: 0x%lx db_stat: %u version: %lu", + table->s->table_name.str, (ulong) table->s, + table->db_stat, table->s->version)); VOID(table->file->extra(function)); /* Mark all tables that are in use as 'old' */ - mysql_lock_abort(thd, table); // end threads waiting on lock + mysql_lock_abort(thd, table, TRUE); /* end threads waiting on lock */ /* Wait until all there are no other threads that has this table open */ - remove_table_from_cache(thd, table->s->db, - table->s->table_name, RTFC_WAIT_OTHER_THREAD_FLAG); + remove_table_from_cache(thd, table->s->db.str, + table->s->table_name.str, + RTFC_WAIT_OTHER_THREAD_FLAG); DBUG_VOID_RETURN; } @@ -1974,23 +3828,22 @@ static int prepare_for_restore(THD* thd, TABLE_LIST* table, else { char* backup_dir= thd->lex->backup_dir; - char src_path[FN_REFLEN], dst_path[FN_REFLEN]; + char src_path[FN_REFLEN], dst_path[FN_REFLEN], uname[FN_REFLEN]; char* table_name= table->table_name; char* db= table->db; - if (fn_format_relative_to_data_home(src_path, table_name, backup_dir, - reg_ext)) + VOID(tablename_to_filename(table->table_name, uname, sizeof(uname))); + + if (fn_format_relative_to_data_home(src_path, uname, backup_dir, reg_ext)) DBUG_RETURN(-1); // protect buffer overflow - my_snprintf(dst_path, sizeof(dst_path), "%s%s/%s", - mysql_real_data_home, db, table_name); + build_table_filename(dst_path, sizeof(dst_path), + db, table_name, reg_ext, 0); if (lock_and_wait_for_table_name(thd,table)) DBUG_RETURN(-1); - if (my_copy(src_path, - fn_format(dst_path, dst_path,"", reg_ext, 4), - MYF(MY_WME))) + if (my_copy(src_path, dst_path, MYF(MY_WME))) { pthread_mutex_lock(&LOCK_open); unlock_table_name(thd, table); @@ -2025,11 +3878,15 @@ static int prepare_for_restore(THD* thd, TABLE_LIST* table, } -static int prepare_for_repair(THD* thd, TABLE_LIST *table_list, +static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, HA_CHECK_OPT *check_opt) { int error= 0; TABLE tmp_table, *table; + TABLE_SHARE *share; + char from[FN_REFLEN],tmp[FN_REFLEN+32]; + const char **ext; + MY_STAT stat_info; DBUG_ENTER("prepare_for_repair"); if (!(check_opt->sql_flags & TT_USEFRM)) @@ -2037,12 +3894,35 @@ static int prepare_for_repair(THD* thd, TABLE_LIST *table_list, if (!(table= table_list->table)) /* if open_ltable failed */ { - char name[FN_REFLEN]; - build_table_path(name, sizeof(name), table_list->db, - table_list->table_name, ""); - if (openfrm(thd, name, "", 0, 0, 0, &tmp_table)) + char key[MAX_DBKEY_LENGTH]; + uint key_length; + + key_length= create_table_def_key(thd, key, table_list, 0); + pthread_mutex_lock(&LOCK_open); + if (!(share= (get_table_share(thd, table_list, key, key_length, 0, + &error)))) + { + pthread_mutex_unlock(&LOCK_open); DBUG_RETURN(0); // Can't open frm file + } + + if (open_table_from_share(thd, share, "", 0, 0, 0, &tmp_table, FALSE)) + { + release_table_share(share, RELEASE_NORMAL); + pthread_mutex_unlock(&LOCK_open); + DBUG_RETURN(0); // Out of memory + } table= &tmp_table; + pthread_mutex_unlock(&LOCK_open); + } + /* + REPAIR TABLE ... USE_FRM for temporary tables makes little sense. + */ + if (table->s->tmp_table) + { + error= send_check_errmsg(thd, table_list, "repair", + "Cannot repair temporary table from .frm file"); + goto end; } /* @@ -2055,18 +3935,16 @@ static int prepare_for_repair(THD* thd, TABLE_LIST *table_list, - Run a normal repair using the new index file and the old data file */ - char from[FN_REFLEN],tmp[FN_REFLEN+32]; - const char **ext= table->file->bas_ext(); - MY_STAT stat_info; - /* Check if this is a table type that stores index and data separately, like ISAM or MyISAM */ + ext= table->file->bas_ext(); if (!ext[0] || !ext[1]) goto end; // No data file - strxmov(from, table->s->path, ext[1], NullS); // Name of data file + // Name of data file + strxmov(from, table->s->normalized_path.str, ext[1], NullS); if (!my_stat(from, &stat_info, MYF(0))) goto end; // Can't use USE_FRM flag @@ -2130,7 +4008,11 @@ static int prepare_for_repair(THD* thd, TABLE_LIST *table_list, end: if (table == &tmp_table) - closefrm(table); // Free allocated memory + { + pthread_mutex_lock(&LOCK_open); + closefrm(table, 1); // Free allocated memory + pthread_mutex_unlock(&LOCK_open); + } DBUG_RETURN(error); } @@ -2161,9 +4043,11 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, Item *item; Protocol *protocol= thd->protocol; LEX *lex= thd->lex; - int result_code; + int result_code, disable_logs= 0; DBUG_ENTER("mysql_admin_table"); + if (end_active_trans(thd)) + DBUG_RETURN(1); field_list.push_back(item = new Item_empty_string("Table", NAME_LEN*2)); item->maybe_null = 1; field_list.push_back(item = new Item_empty_string("Op", 10)); @@ -2204,6 +4088,23 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, thd->no_warnings_for_error= no_warnings_for_error; if (view_operator_func == NULL) table->required_type=FRMTYPE_TABLE; + + /* + If we want to perform an admin operation on the log table + (E.g. rename) and lock_type >= TL_READ_NO_INSERT disable + log tables + */ + + if (check_if_log_table(table->db_length, table->db, + table->table_name_length, + table->table_name, 1) && + lock_type >= TL_READ_NO_INSERT) + { + disable_logs= 1; + logger.lock(); + logger.tmp_close_log_tables(thd); + } + open_and_lock_tables(thd, table); thd->no_warnings_for_error= 0; table->next_global= save_next_global; @@ -2214,6 +4115,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, { switch ((*prepare_func)(thd, table, check_opt)) { case 1: // error, message written to net + ha_autocommit_or_rollback(thd, 1); close_thread_tables(thd); continue; case -1: // error, message could be written to net @@ -2255,6 +4157,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, View opening can be interrupted in the middle of process so some tables can be left opening */ + ha_autocommit_or_rollback(thd, 1); close_thread_tables(thd); lex->reset_query_tables_list(FALSE); if (protocol->write()) @@ -2279,7 +4182,9 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, length= my_snprintf(buff, sizeof(buff), ER(ER_OPEN_AS_READONLY), table_name); protocol->store(buff, length, system_charset_info); + ha_autocommit_or_rollback(thd, 0); close_thread_tables(thd); + lex->reset_query_tables_list(FALSE); table->table=0; // For query cache if (protocol->write()) goto err; @@ -2287,14 +4192,15 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, } /* Close all instances of the table to allow repair to rename files */ - if (lock_type == TL_WRITE && table->table->s->version) + if (lock_type == TL_WRITE && table->table->s->version && + !table->table->s->log_table) { pthread_mutex_lock(&LOCK_open); const char *old_message=thd->enter_cond(&COND_refresh, &LOCK_open, "Waiting to get writelock"); - mysql_lock_abort(thd,table->table); - remove_table_from_cache(thd, table->table->s->db, - table->table->s->table_name, + mysql_lock_abort(thd,table->table, TRUE); + remove_table_from_cache(thd, table->table->s->db.str, + table->table->s->table_name.str, RTFC_WAIT_OTHER_THREAD_FLAG | RTFC_CHECK_KILLED_FLAG); thd->exit_cond(old_message); @@ -2323,6 +4229,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, (table->table->file->ha_check_for_upgrade(check_opt) == HA_ADMIN_NEEDS_ALTER)) { + ha_autocommit_or_rollback(thd, 1); close_thread_tables(thd); tmp_disable_binlog(thd); // binlogging is done by caller if wanted result_code= mysql_recreate_table(thd, table, 0); @@ -2409,6 +4316,7 @@ send_result_message: "try with alter", so here we close the table, do an ALTER TABLE, reopen the table and do ha_innobase::analyze() on it. */ + ha_autocommit_or_rollback(thd, 0); close_thread_tables(thd); TABLE_LIST *save_next_local= table->next_local, *save_next_global= table->next_global; @@ -2416,6 +4324,7 @@ send_result_message: tmp_disable_binlog(thd); // binlogging is done by caller if wanted result_code= mysql_recreate_table(thd, table, 0); reenable_binlog(thd); + ha_autocommit_or_rollback(thd, 0); close_thread_tables(thd); if (!result_code) // recreation went ok { @@ -2485,34 +4394,50 @@ send_result_message: } if (table->table) { + /* in the below check we do not refresh the log tables */ if (fatal_error) table->table->s->version=0; // Force close of table - else if (open_for_modify) + else if (open_for_modify && !table->table->s->log_table) { if (table->table->s->tmp_table) table->table->file->info(HA_STATUS_CONST); else { pthread_mutex_lock(&LOCK_open); - remove_table_from_cache(thd, table->table->s->db, - table->table->s->table_name, RTFC_NO_FLAG); + remove_table_from_cache(thd, table->table->s->db.str, + table->table->s->table_name.str, RTFC_NO_FLAG); pthread_mutex_unlock(&LOCK_open); } /* May be something modified consequently we have to invalidate cache */ query_cache_invalidate3(thd, table->table, 0); } } + ha_autocommit_or_rollback(thd, 0); close_thread_tables(thd); - lex->reset_query_tables_list(FALSE); table->table=0; // For query cache if (protocol->write()) goto err; } send_eof(thd); + if (disable_logs) + { + if (logger.reopen_log_tables()) + my_error(ER_CANT_ACTIVATE_LOG, MYF(0)); + logger.unlock(); + } DBUG_RETURN(FALSE); + err: + ha_autocommit_or_rollback(thd, 1); close_thread_tables(thd); // Shouldn't be needed + /* enable logging back if needed */ + if (disable_logs) + { + if (logger.reopen_log_tables()) + my_error(ER_CANT_ACTIVATE_LOG, MYF(0)); + logger.unlock(); + } if (table) table->table=0; DBUG_RETURN(TRUE); @@ -2672,21 +4597,29 @@ bool mysql_preload_keys(THD* thd, TABLE_LIST* tables) */ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, - HA_CREATE_INFO *create_info, + HA_CREATE_INFO *lex_create_info, Table_ident *table_ident) { - TABLE **tmp_table; - char src_path[FN_REFLEN], dst_path[FN_REFLEN]; + TABLE *tmp_table; + char src_path[FN_REFLEN], dst_path[FN_REFLEN], tmp_path[FN_REFLEN]; + char src_table_name_buff[FN_REFLEN], src_db_name_buff[FN_REFLEN]; + uint dst_path_length; char *db= table->db; char *table_name= table->table_name; char *src_db; char *src_table= table_ident->table.str; int err; - bool res= TRUE; - db_type not_used; + bool res= TRUE, unlock_dst_table= FALSE; + enum legacy_db_type not_used; + HA_CREATE_INFO *create_info; - TABLE_LIST src_tables_list; + TABLE_LIST src_tables_list, dst_tables_list; DBUG_ENTER("mysql_create_like_table"); + + if (!(create_info= copy_create_info(lex_create_info))) + { + DBUG_RETURN(TRUE); + } DBUG_ASSERT(table_ident->db.str); /* Must be set in the parser */ src_db= table_ident->db.str; @@ -2700,27 +4633,20 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, my_error(ER_WRONG_TABLE_NAME, MYF(0), src_table); DBUG_RETURN(TRUE); } - if (!src_db || check_db_name(src_db)) + if (!src_db || check_db_name(&table_ident->db)) { my_error(ER_WRONG_DB_NAME, MYF(0), src_db ? src_db : "NULL"); DBUG_RETURN(-1); } - bzero((gptr)&src_tables_list, sizeof(src_tables_list)); - src_tables_list.db= src_db; - src_tables_list.table_name= src_table; - - if (lock_and_wait_for_table_name(thd, &src_tables_list)) - goto err; - if ((tmp_table= find_temporary_table(thd, src_db, src_table))) - strxmov(src_path, (*tmp_table)->s->path, reg_ext, NullS); + strxmov(src_path, tmp_table->s->path.str, reg_ext, NullS); else { - strxmov(src_path, mysql_data_home, "/", src_db, "/", src_table, - reg_ext, NullS); + build_table_filename(src_path, sizeof(src_path), + src_db, src_table, reg_ext, 0); /* Resolve symlinks (for windows) */ - fn_format(src_path, src_path, "", "", MYF(MY_UNPACK_FILENAME)); + unpack_filename(src_path, src_path); if (lower_case_table_names) my_casedn_str(files_charset_info, src_path); if (access(src_path, F_OK)) @@ -2739,6 +4665,34 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, goto err; } + if (lower_case_table_names) + { + if (src_db) + { + strmake(src_db_name_buff, src_db, + min(sizeof(src_db_name_buff) - 1, table_ident->db.length)); + my_casedn_str(files_charset_info, src_db_name_buff); + src_db= src_db_name_buff; + } + if (src_table) + { + strmake(src_table_name_buff, src_table, + min(sizeof(src_table_name_buff) - 1, table_ident->table.length)); + my_casedn_str(files_charset_info, src_table_name_buff); + src_table= src_table_name_buff; + } + } + + bzero((gptr)&src_tables_list, sizeof(src_tables_list)); + src_tables_list.db= src_db; + src_tables_list.db_length= table_ident->db.length; + src_tables_list.lock_type= TL_READ; + src_tables_list.table_name= src_table; + src_tables_list.alias= src_table; + + if (simple_open_n_lock_tables(thd, &src_tables_list)) + DBUG_RETURN(TRUE); + /* Validate the destination table @@ -2749,18 +4703,15 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, { if (find_temporary_table(thd, db, table_name)) goto table_exists; - my_snprintf(dst_path, sizeof(dst_path), "%s%s%lx_%lx_%x%s", - mysql_tmpdir, tmp_file_prefix, current_pid, - thd->thread_id, thd->tmp_table++, reg_ext); + dst_path_length= build_tmptable_filename(thd, dst_path, sizeof(dst_path)); if (lower_case_table_names) my_casedn_str(files_charset_info, dst_path); create_info->table_options|= HA_CREATE_DELAY_KEY_WRITE; } else { - strxmov(dst_path, mysql_data_home, "/", db, "/", table_name, - reg_ext, NullS); - fn_format(dst_path, dst_path, "", "", MYF(MY_UNPACK_FILENAME)); + dst_path_length= build_table_filename(dst_path, sizeof(dst_path), + db, table_name, reg_ext, 0); if (!access(dst_path, F_OK)) goto table_exists; } @@ -2782,9 +4733,23 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, creation, instead create the table directly (for both normal and temporary tables). */ - *fn_ext(dst_path)= 0; - err= ha_create_table(dst_path, create_info, 1); - +#ifdef WITH_PARTITION_STORAGE_ENGINE + /* + For partitioned tables we need to copy the .par file as well since + it is used in open_table_def to even be able to create a new handler. + There is no way to find out here if the original table is a + partitioned table so we copy the file and ignore any errors. + */ + fn_format(tmp_path, dst_path, reg_ext, ".par", MYF(MY_REPLACE_EXT)); + strmov(dst_path, tmp_path); + fn_format(tmp_path, src_path, reg_ext, ".par", MYF(MY_REPLACE_EXT)); + strmov(src_path, tmp_path); + my_copy(src_path, dst_path, MYF(MY_DONT_OVERWRITE_FILE)); +#endif + dst_path[dst_path_length - reg_ext_length]= '\0'; // Remove .frm + pthread_mutex_lock(&LOCK_open); + err= ha_create_table(thd, dst_path, db, table_name, create_info, 1); + pthread_mutex_unlock(&LOCK_open); if (create_info->options & HA_LEX_CREATE_TMP_TABLE) { if (err || !open_temporary_table(thd, dst_path, db, table_name, 1)) @@ -2797,17 +4762,79 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, else if (err) { (void) quick_rm_table(create_info->db_type, db, - table_name); /* purecov: inspected */ + table_name, 0); /* purecov: inspected */ goto err; /* purecov: inspected */ } - // Must be written before unlock - if (mysql_bin_log.is_open()) + /* + We have to write the query before we unlock the tables. + */ + if (thd->current_stmt_binlog_row_based) { - thd->clear_error(); - Query_log_event qinfo(thd, thd->query, thd->query_length, FALSE, FALSE); - mysql_bin_log.write(&qinfo); + /* + Since temporary tables are not replicated under row-based + replication, CREATE TABLE ... LIKE ... needs special + treatement. We have four cases to consider, according to the + following decision table: + + ==== ========= ========= ============================== + Case Target Source Write to binary log + ==== ========= ========= ============================== + 1 normal normal Original statement + 2 normal temporary Generated statement + 3 temporary normal Nothing + 4 temporary temporary Nothing + ==== ========= ========= ============================== + + The variable 'tmp_table' below is used to see if the source + table is a temporary table: if it is set, then the source table + was a temporary table and we can take apropriate actions. + */ + if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE)) + { + if (tmp_table) // Case 2 + { + char buf[2048]; + String query(buf, sizeof(buf), system_charset_info); + query.length(0); // Have to zero it since constructor doesn't + uint counter; + + /* + Here we open the destination table. This is needed for + store_create_info() to work. The table will be closed + by close_thread_tables() at the end of the statement. + */ + if (open_tables(thd, &table, &counter, 0)) + goto err; + + bzero((gptr)&dst_tables_list, sizeof(dst_tables_list)); + dst_tables_list.db= table->db; + dst_tables_list.table_name= table->table_name; + + /* + lock destination table name, to make sure that nobody + can drop/alter the table while we execute store_create_info() + */ + if (lock_and_wait_for_table_name(thd, &dst_tables_list)) + goto err; + else + unlock_dst_table= TRUE; + + int result= store_create_info(thd, table, &query, create_info); + + DBUG_ASSERT(result == 0); // store_create_info() always return 0 + write_bin_log(thd, TRUE, query.ptr(), query.length()); + } + else // Case 1 + write_bin_log(thd, TRUE, thd->query, thd->query_length); + } + /* + Case 3 and 4 does nothing under RBR + */ } + else + write_bin_log(thd, TRUE, thd->query, thd->query_length); + res= FALSE; goto err; @@ -2825,20 +4852,19 @@ table_exists: my_error(ER_TABLE_EXISTS_ERROR, MYF(0), table_name); err: - pthread_mutex_lock(&LOCK_open); - unlock_table_name(thd, &src_tables_list); - pthread_mutex_unlock(&LOCK_open); + if (unlock_dst_table) + { + pthread_mutex_lock(&LOCK_open); + unlock_table_name(thd, &dst_tables_list); + pthread_mutex_unlock(&LOCK_open); + } DBUG_RETURN(res); } bool mysql_analyze_table(THD* thd, TABLE_LIST* tables, HA_CHECK_OPT* check_opt) { -#ifdef OS2 - thr_lock_type lock_type = TL_WRITE; -#else thr_lock_type lock_type = TL_READ_NO_INSERT; -#endif DBUG_ENTER("mysql_analyze_table"); DBUG_RETURN(mysql_admin_table(thd, tables, check_opt, @@ -2849,11 +4875,7 @@ bool mysql_analyze_table(THD* thd, TABLE_LIST* tables, HA_CHECK_OPT* check_opt) bool mysql_check_table(THD* thd, TABLE_LIST* tables,HA_CHECK_OPT* check_opt) { -#ifdef OS2 - thr_lock_type lock_type = TL_WRITE; -#else thr_lock_type lock_type = TL_READ_NO_INSERT; -#endif DBUG_ENTER("mysql_check_table"); DBUG_RETURN(mysql_admin_table(thd, tables, check_opt, @@ -2913,12 +4935,10 @@ mysql_discard_or_import_tablespace(THD *thd, error=1; if (error) goto err; - if (mysql_bin_log.is_open()) - { - Query_log_event qinfo(thd, thd->query, thd->query_length, FALSE, FALSE); - mysql_bin_log.write(&qinfo); - } + write_bin_log(thd, FALSE, thd->query, thd->query_length); + err: + ha_autocommit_or_rollback(thd, error); close_thread_tables(thd); thd->tablespace_op=FALSE; @@ -2935,6 +4955,250 @@ err: /* + SYNOPSIS + compare_tables() + table The original table. + create_list The fields for the new table. + key_info_buffer An array of KEY structs for the new indexes. + key_count The number of elements in the array. + create_info Create options for the new table. + alter_info Alter options. + order_num Number of order list elements. + 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. + + 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. + + 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. + + RETURN VALUES + 0 No copy needed + ALTER_TABLE_DATA_CHANGED Data changes, copy needed + ALTER_TABLE_INDEX_CHANGED Index changes, copy might be needed +*/ + +static uint compare_tables(TABLE *table, List<create_field> *create_list, + KEY *key_info_buffer, uint key_count, + HA_CREATE_INFO *create_info, + ALTER_INFO *alter_info, uint order_num, + uint *index_drop_buffer, uint *index_drop_count, + uint *index_add_buffer, uint *index_add_count, + bool varchar) +{ + Field **f_ptr, *field; + uint changes= 0, tmp; + List_iterator_fast<create_field> new_field_it(*create_list); + create_field *new_field; + KEY_PART_INFO *key_part; + KEY_PART_INFO *end; + DBUG_ENTER("compare_tables"); + + /* + 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. + + 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. + + 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->fields != 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 || + (alter_info->flags & (ALTER_RECREATE | ALTER_FOREIGN_KEY)) || + order_num || + !table->s->mysql_version || + (table->s->frm_version < FRM_VER_TRUE_VARCHAR && varchar)) + DBUG_RETURN(ALTER_TABLE_DATA_CHANGED); + + /* + Go through fields and check if the original ones are compatible + with new table. + */ + for (f_ptr= table->field, new_field= new_field_it++; + (field= *f_ptr); f_ptr++, new_field= new_field_it++) + { + /* 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 ((new_field->flags & NOT_NULL_FLAG) != + (uint) (field->flags & NOT_NULL_FLAG)) + DBUG_RETURN(ALTER_TABLE_DATA_CHANGED); + + /* Don't pack rows in old tables if the user has requested this. */ + if (create_info->row_type == ROW_TYPE_DYNAMIC || + (new_field->flags & BLOB_FLAG) || + 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 */ + field->flags&= ~FIELD_IS_RENAMED; + if (my_strcasecmp(system_charset_info, + field->field_name, + new_field->field_name)) + field->flags|= FIELD_IS_RENAMED; + + /* Evaluate changes bitmap and send to check_if_incompatible_data() */ + if (!(tmp= field->is_equal(new_field))) + DBUG_RETURN(ALTER_TABLE_DATA_CHANGED); + // Clear indexed marker + field->flags&= ~FIELD_IN_ADD_INDEX; + changes|= tmp; + } + + /* + Go through keys and check if the original ones are compatible + with new 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; + + DBUG_PRINT("info", ("index count old: %d new: %d", + table->s->keys, key_count)); + /* + Step through all keys of the old table and search matching new keys. + */ + *index_drop_count= 0; + *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->key_parts; + KEY_PART_INFO *new_part; + + /* Search a new 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) + { + /* Key not found. Add the offset of the key to the drop buffer. */ + index_drop_buffer[(*index_drop_count)++]= table_key - table->key_info; + 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) != + (new_key->flags & HA_KEYFLAG_MASK)) || + (table_key->key_parts != new_key->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++) + { + /* + 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. + */ + if ((table_part->length != new_part->length) || + (table_part->fieldnr - 1 != new_part->fieldnr)) + 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->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; + } + DBUG_PRINT("info", ("index changed: '%s'", table_key->name)); + } + /*end of for (; table_key < table_key_end;) */ + + /* + 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++) + { + /* Search an old 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) + { + /* 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->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; + } + DBUG_PRINT("info", ("index added: '%s'", new_key->name)); + } + } + + /* Check if changes are compatible with current handler without a copy */ + if (table->file->check_if_incompatible_data(create_info, changes)) + DBUG_RETURN(ALTER_TABLE_DATA_CHANGED); + + if (*index_drop_count || *index_add_count) + DBUG_RETURN(ALTER_TABLE_INDEX_CHANGED); + + DBUG_RETURN(0); // Tables are compatible +} + + +/* Manages enabling/disabling of indexes for ALTER TABLE SYNOPSIS @@ -2984,10 +5248,55 @@ bool alter_table_manage_keys(TABLE *table, int indexes_were_disabled, /* Alter table + + SYNOPSIS + mysql_alter_table() + thd Thread handle + new_db If there is a RENAME clause + new_name If there is a RENAME clause + lex_create_info Information from the parsing phase. Since some + clauses are common to CREATE and ALTER TABLE, the + data is stored in lex->create_info. The non-common + is stored in lex->alter_info. + table_list The table to change. + fields lex->create_list - List of fields to be changed, + added or dropped. + keys lex->key_list - List of 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 + alter_info Information from the parsing phase specific to ALTER + TABLE and not shared with CREATE TABLE. + do_send_ok Whether to call send_ok() on success. + + 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 `fields` 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 compare_tables() is called, which decides + based on all kind of data how similar are the new and the original + tables. + + RETURN VALUES + FALSE OK + TRUE Error */ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, - HA_CREATE_INFO *create_info, + HA_CREATE_INFO *lex_create_info, TABLE_LIST *table_list, List<create_field> &fields, List<Key> &keys, uint order_num, ORDER *order, bool ignore, @@ -2997,25 +5306,80 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, int error; char tmp_name[80],old_name[32],new_name_buff[FN_REFLEN]; char new_alias_buff[FN_REFLEN], *table_name, *db, *new_alias, *alias; - char index_file[FN_REFLEN], data_file[FN_REFLEN]; + char index_file[FN_REFLEN], data_file[FN_REFLEN], tablespace[FN_LEN]; + char path[FN_REFLEN]; + char reg_path[FN_REFLEN+1]; ha_rows copied,deleted; - ulonglong next_insert_id; uint db_create_options, used_fields; - enum db_type old_db_type, new_db_type, table_type; - bool need_copy_table; - bool no_table_reopen= FALSE, varchar= FALSE; + handlerton *old_db_type, *new_db_type, *save_old_db_type; + legacy_db_type table_type; + HA_CREATE_INFO *create_info; frm_type_enum frm_type; + uint need_copy_table= 0; + bool no_table_reopen= FALSE, varchar= FALSE; +#ifdef WITH_PARTITION_STORAGE_ENGINE + uint fast_alter_partition= 0; + bool partition_changed= FALSE; +#endif + List<create_field> prepared_create_list; + List<Key> prepared_key_list; + bool need_lock_for_indexes= TRUE; + uint db_options= 0; + uint key_count; + KEY *key_info_buffer; + uint index_drop_count; + uint *index_drop_buffer; + uint index_add_count; + uint *index_add_buffer; + bool committed= 0; DBUG_ENTER("mysql_alter_table"); + LINT_INIT(index_add_count); + LINT_INIT(index_drop_count); + LINT_INIT(index_add_buffer); + LINT_INIT(index_drop_buffer); + + if (table_list && table_list->db && table_list->table_name) + { + int table_kind= 0; + + table_kind= check_if_log_table(table_list->db_length, table_list->db, + table_list->table_name_length, + table_list->table_name, 0); + + /* Disable alter of enabled log tables */ + if (table_kind && logger.is_log_table_enabled(table_kind)) + { + my_error(ER_BAD_LOG_STATEMENT, MYF(0), "ALTER"); + DBUG_RETURN(TRUE); + } + + /* Disable alter of log tables to unsupported engine */ + if (table_kind && + (lex_create_info->used_fields & HA_CREATE_USED_ENGINE) && + (!lex_create_info->db_type || /* unknown engine */ + !(lex_create_info->db_type->flags & HTON_SUPPORT_LOG_TABLES))) + { + my_error(ER_UNSUPORTED_LOG_ENGINE, MYF(0)); + DBUG_RETURN(TRUE); + } + } + thd->proc_info="init"; + if (!(create_info= copy_create_info(lex_create_info))) + { + DBUG_RETURN(TRUE); + } 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), db, table_name, reg_ext, 0); + build_table_filename(path, sizeof(path), db, table_name, "", 0); + used_fields=create_info->used_fields; - + mysql_ha_flush(thd, table_list, MYSQL_HA_CLOSE_FINAL, FALSE); /* DISCARD/IMPORT TABLESPACE is always alone in an ALTER TABLE */ @@ -3023,8 +5387,9 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, /* Conditionally writes to binlog. */ DBUG_RETURN(mysql_discard_or_import_tablespace(thd,table_list, alter_info->tablespace_op)); - sprintf(new_name_buff,"%s/%s/%s%s",mysql_data_home, db, table_name, reg_ext); - unpack_filename(new_name_buff, new_name_buff); + strxnmov(new_name_buff, sizeof (new_name_buff) - 1, mysql_data_home, "/", db, + "/", table_name, reg_ext, NullS); + (void) unpack_filename(new_name_buff, new_name_buff); if (lower_case_table_names != 2) my_casedn_str(files_charset_info, new_name_buff); frm_type= mysql_frm_type(thd, new_name_buff, &table_type); @@ -3070,10 +5435,12 @@ view_err: } if (!(table=open_ltable(thd,table_list,TL_WRITE_ALLOW_READ))) DBUG_RETURN(TRUE); + table->use_all_columns(); /* Check that we are not trying to rename to an existing table */ if (new_name) { + 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) @@ -3096,7 +5463,7 @@ view_err: } else { - if (table->s->tmp_table) + if (table->s->tmp_table != NO_TMP_TABLE) { if (find_temporary_table(thd,new_db,new_name_buff)) { @@ -3106,10 +5473,9 @@ view_err: } else { - char dir_buff[FN_REFLEN]; - strxnmov(dir_buff, FN_REFLEN, mysql_real_data_home, new_db, NullS); - if (!access(fn_format(new_name_buff,new_name_buff,dir_buff,reg_ext,0), - F_OK)) + build_table_filename(new_name_buff, sizeof(new_name_buff), + new_db, new_name_buff, reg_ext, 0); + if (!access(new_name_buff, F_OK)) { /* Table will be closed in do_command() */ my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_alias); @@ -3125,15 +5491,42 @@ view_err: } old_db_type= table->s->db_type; - if (create_info->db_type == DB_TYPE_DEFAULT) - create_info->db_type= old_db_type; - if (check_engine(thd, new_name, &create_info->db_type)) + if (!create_info->db_type) + { +#ifdef WITH_PARTITION_STORAGE_ENGINE + if (table->part_info && + create_info->used_fields & HA_CREATE_USED_ENGINE) + { + /* + This case happens when the user specified + ENGINE = x where x is a non-existing storage engine + We set create_info->db_type to default_engine_type + to ensure we don't change underlying engine type + due to a erroneously given engine name. + */ + create_info->db_type= table->part_info->default_engine_type; + } + else +#endif + create_info->db_type= old_db_type; + } + +#ifdef WITH_PARTITION_STORAGE_ENGINE + if (prep_alter_part_table(thd, table, alter_info, create_info, old_db_type, + &partition_changed, &fast_alter_partition)) + { + DBUG_RETURN(TRUE); + } +#endif + if (check_engine(thd, new_name, create_info)) DBUG_RETURN(TRUE); new_db_type= create_info->db_type; if (create_info->row_type == ROW_TYPE_NOT_USED) create_info->row_type= table->s->row_type; - DBUG_PRINT("info", ("old type: %d new type: %d", old_db_type, new_db_type)); + 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)) { @@ -3183,14 +5576,15 @@ view_err: else { *fn_ext(new_name)=0; + table->s->version= 0; // Force removal of table def close_cached_table(thd, table); - if (mysql_rename_table(old_db_type,db,table_name,new_db,new_alias)) + 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, table_name, new_db, new_alias)) { VOID(mysql_rename_table(old_db_type, new_db, new_alias, db, - table_name)); + table_name, 0)); error= -1; } } @@ -3206,12 +5600,7 @@ view_err: if (!error) { - if (mysql_bin_log.is_open()) - { - thd->clear_error(); - Query_log_event qinfo(thd, thd->query, thd->query_length, FALSE, FALSE); - mysql_bin_log.write(&qinfo); - } + write_bin_log(thd, TRUE, thd->query, thd->query_length); if (do_send_ok) send_ok(thd); } @@ -3226,7 +5615,7 @@ view_err: DBUG_RETURN(error); } - /* Full alter table */ + /* We have to do full alter table */ /* Let new create options override the old ones */ if (!(used_fields & HA_CREATE_USED_MIN_ROWS)) @@ -3237,7 +5626,18 @@ view_err: create_info->avg_row_length= table->s->avg_row_length; if (!(used_fields & HA_CREATE_USED_DEFAULT_CHARSET)) create_info->default_table_charset= table->s->table_charset; + if (!(used_fields & HA_CREATE_USED_KEY_BLOCK_SIZE)) + create_info->key_block_size= table->s->key_block_size; + if (!create_info->tablespace && create_info->storage_media != HA_SM_MEMORY) + { + /* + Regular alter table of disk stored table (no tablespace/storage change) + Copy tablespace name + */ + if ((table->file->get_tablespace_name(thd, tablespace, FN_LEN))) + create_info->tablespace= tablespace; + } restore_record(table, s->default_values); // Empty record for DEFAULT List_iterator<Alter_drop> drop_it(alter_info->drop_list); List_iterator<create_field> def_it(fields); @@ -3296,7 +5696,11 @@ view_err: } } else - { // Use old field value + { + /* + This field was not dropped and not changed, add it to the list + for the new table. + */ create_list.push_back(def=new create_field(field,field)); alter_it.rewind(); // Change default if ALTER Alter_column *alter; @@ -3307,7 +5711,7 @@ view_err: } if (alter) { - if (def->sql_type == FIELD_TYPE_BLOB) + if (def->sql_type == MYSQL_TYPE_BLOB) { my_error(ER_BLOB_CANT_HAVE_DEFAULT, MYF(0), def->change); DBUG_RETURN(TRUE); @@ -3441,6 +5845,16 @@ view_err: key_part_length)); } if (key_parts.elements) + { + KEY_CREATE_INFO key_create_info; + bzero((char*) &key_create_info, sizeof(key_create_info)); + + key_create_info.algorithm= key_info->algorithm; + if (key_info->flags & HA_USES_BLOCK_SIZE) + key_create_info.block_size= key_info->block_size; + if (key_info->flags & HA_USES_PARSER) + key_create_info.parser_name= *key_info->parser_name; + key_list.push_back(new Key(key_info->flags & HA_SPATIAL ? Key::SPATIAL : (key_info->flags & HA_NOSAME ? (!my_strcasecmp(system_charset_info, @@ -3449,9 +5863,10 @@ view_err: (key_info->flags & HA_FULLTEXT ? Key::FULLTEXT : Key::MULTIPLE)), key_name, - key_info->algorithm, + &key_create_info, test(key_info->flags & HA_GENERATED_KEY), key_parts)); + } } { Key *key; @@ -3515,32 +5930,203 @@ view_err: if (table->s->tmp_table) create_info->options|=HA_LEX_CREATE_TMP_TABLE; + set_table_default_charset(thd, create_info, db); + + { + /* + For some purposes we need prepared table structures and translated + key descriptions with proper default key name assignment. + + Unfortunately, mysql_prepare_table() modifies the field and key + lists. mysql_create_table() needs the unmodified lists. Hence, we + need to copy the lists and all their elements. The lists contain + pointers to the elements only. + + We cannot copy conditionally because the partition code always + needs prepared lists and compare_tables() needs them and is almost + always called. + */ + + /* Copy fields. */ + List_iterator<create_field> prep_field_it(create_list); + create_field *prep_field; + while ((prep_field= prep_field_it++)) + prepared_create_list.push_back(new create_field(*prep_field)); + + /* Copy keys and key parts. */ + List_iterator<Key> prep_key_it(key_list); + Key *prep_key; + while ((prep_key= prep_key_it++)) + { + List<key_part_spec> prep_columns; + List_iterator<key_part_spec> prep_col_it(prep_key->columns); + key_part_spec *prep_col; + + while ((prep_col= prep_col_it++)) + prep_columns.push_back(new key_part_spec(*prep_col)); + prepared_key_list.push_back(new Key(prep_key->type, prep_key->name, + &prep_key->key_create_info, + prep_key->generated, prep_columns)); + } + + /* Create the prepared information. */ + if (mysql_prepare_table(thd, create_info, &prepared_create_list, + &prepared_key_list, + (table->s->tmp_table != NO_TMP_TABLE), &db_options, + table->file, &key_info_buffer, &key_count, 0)) + goto err; + } + + 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= 1; + else + { + /* Try to optimize ALTER TABLE. Allocate result buffers. */ + if (! (index_drop_buffer= + (uint*) thd->alloc(sizeof(uint) * table->s->keys)) || + ! (index_add_buffer= + (uint*) thd->alloc(sizeof(uint) * prepared_key_list.elements))) + goto err; + /* Check how much the tables differ. */ + need_copy_table= compare_tables(table, &prepared_create_list, + key_info_buffer, key_count, + create_info, alter_info, order_num, + index_drop_buffer, &index_drop_count, + index_add_buffer, &index_add_count, + varchar); + } + + /* + If there are index changes only, try to do them online. "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_online_flags= 0; + ulong needed_fast_flags= 0; + KEY *key; + uint *idx_p; + uint *idx_end_p; + + if (table->s->db_type->alter_table_flags) + alter_flags= table->s->db_type->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". */ + if (! my_strcasecmp(system_charset_info, + key->name, primary_key_name)) + { + /* Primary key. */ + needed_online_flags|= HA_ONLINE_DROP_PK_INDEX; + needed_fast_flags|= HA_ONLINE_DROP_PK_INDEX_NO_WRITES; + pk_changed++; + } + else + { + /* Non-primary unique key. */ + needed_online_flags|= HA_ONLINE_DROP_UNIQUE_INDEX; + needed_fast_flags|= HA_ONLINE_DROP_UNIQUE_INDEX_NO_WRITES; + } + } + else + { + /* Non-unique key. */ + needed_online_flags|= HA_ONLINE_DROP_INDEX; + needed_fast_flags|= HA_ONLINE_DROP_INDEX_NO_WRITES; + } + } + + /* 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. Check for "PRIMARY". */ + if (! my_strcasecmp(system_charset_info, + key->name, primary_key_name)) + { + /* Primary key. */ + needed_online_flags|= HA_ONLINE_ADD_PK_INDEX; + needed_fast_flags|= HA_ONLINE_ADD_PK_INDEX_NO_WRITES; + pk_changed++; + } + else + { + /* Non-primary unique key. */ + needed_online_flags|= HA_ONLINE_ADD_UNIQUE_INDEX; + needed_fast_flags|= HA_ONLINE_ADD_UNIQUE_INDEX_NO_WRITES; + } + } + else + { + /* Non-unique key. */ + needed_online_flags|= HA_ONLINE_ADD_INDEX; + needed_fast_flags|= HA_ONLINE_ADD_INDEX_NO_WRITES; + } + } + + /* + Online or fast 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. + */ + if ( pk_changed < 2 ) + { + if ((alter_flags & needed_online_flags) == needed_online_flags) + { + /* All required online flags are present. */ + need_copy_table= 0; + need_lock_for_indexes= FALSE; + } + else if ((alter_flags & needed_fast_flags) == needed_fast_flags) + { + /* All required fast flags are present. */ + need_copy_table= 0; + } + } + DBUG_PRINT("info", ("need_copy_table: %u need_lock: %d", + need_copy_table, need_lock_for_indexes)); + } + /* 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 - - MySQL uses frm version to determine the type of the data fields and - their layout. See Field_string::type() for details. - Thus, if the table is too old we may have to rebuild the data to - update the layout. - - 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. */ - need_copy_table= (alter_info->flags & - ~(ALTER_CHANGE_COLUMN_DEFAULT|ALTER_OPTIONS) || - (create_info->used_fields & - ~(HA_CREATE_USED_COMMENT|HA_CREATE_USED_PASSWORD)) || - table->s->tmp_table || - !table->s->mysql_version || - (table->s->frm_version < FRM_VER_TRUE_VARCHAR && varchar)); - create_info->frm_only= !need_copy_table; + if (!need_copy_table) + create_info->frm_only= 1; + +#ifdef WITH_PARTITION_STORAGE_ENGINE + if (fast_alter_partition) + { + DBUG_RETURN(fast_alter_partition_table(thd, table, alter_info, + create_info, table_list, + &create_list, &key_list, + db, table_name, + fast_alter_partition)); + } +#endif /* Handling of symlinked tables: @@ -3557,8 +6143,8 @@ view_err: old data and index files. Create also symlinks to point at the new tables. Copy data. - At end, rename temporary tables and symlinks to temporary table - to final table name. + At end, rename intermediate tables, and symlinks to intermediate + table, to final table name. Remove old table and old symlinks If rename is made to another database: @@ -3566,7 +6152,6 @@ view_err: Copy data. Remove old table and symlinks. */ - if (!strcmp(db, new_db)) // Ignore symlink if db changed { if (create_info->index_file_name) @@ -3589,15 +6174,19 @@ view_err: else create_info->data_file_name=create_info->index_file_name=0; - /* We don't log the statement, it will be logged later. */ - { - tmp_disable_binlog(thd); - error= mysql_create_table(thd, new_db, tmp_name, - create_info,create_list,key_list,1,0); - reenable_binlog(thd); - if (error) - DBUG_RETURN(error); - } + /* + Create a table with a temporary name. + With create_info->frm_only == 1 this creates a .frm file only. + We don't log the statement, it will be logged later. + */ + tmp_disable_binlog(thd); + error= mysql_create_table(thd, new_db, tmp_name, + create_info,create_list,key_list,1,0,0); + reenable_binlog(thd); + if (error) + DBUG_RETURN(error); + + /* Open the table if we need to copy the data. */ if (need_copy_table) { if (table->s->tmp_table) @@ -3606,64 +6195,214 @@ view_err: bzero((void*) &tbl, sizeof(tbl)); tbl.db= new_db; tbl.table_name= tbl.alias= tmp_name; + /* Table is in thd->temporary_tables */ new_table= open_table(thd, &tbl, thd->mem_root, (bool*) 0, MYSQL_LOCK_IGNORE_FLUSH); } else { char path[FN_REFLEN]; - my_snprintf(path, sizeof(path), "%s/%s/%s", mysql_data_home, - new_db, tmp_name); - fn_format(path,path,"","",4); + /* table is a normal table: Create temporary table in same directory */ + build_table_filename(path, sizeof(path), new_db, tmp_name, "", + FN_IS_TMP); + /* Open our intermediate table */ new_table=open_temporary_table(thd, path, new_db, tmp_name,0); } if (!new_table) - { - VOID(quick_rm_table(new_db_type,new_db,tmp_name)); - goto err; - } + goto err1; } - /* We don't want update TIMESTAMP fields during ALTER TABLE. */ + /* Copy the data if necessary. */ thd->count_cuted_fields= CHECK_FIELD_WARN; // calc cuted fields thd->cuted_fields=0L; thd->proc_info="copy to tmp table"; - next_insert_id=thd->next_insert_id; // Remember for logging copied=deleted=0; - if (new_table && !new_table->s->is_view) + if (new_table && !(new_table->file->ha_table_flags() & HA_NO_COPY_ON_ALTER)) { + /* We don't want update TIMESTAMP fields during ALTER TABLE. */ new_table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; new_table->next_number_field=new_table->found_next_number_field; error=copy_data_between_tables(table, new_table, create_list, ignore, - order_num, order, &copied, &deleted, + order_num, order, &copied, &deleted, alter_info->keys_onoff); } - else if (!new_table) + else { VOID(pthread_mutex_lock(&LOCK_open)); wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN); - table->file->external_lock(thd, F_WRLCK); + table->file->ha_external_lock(thd, F_WRLCK); alter_table_manage_keys(table, table->file->indexes_are_disabled(), alter_info->keys_onoff); - table->file->external_lock(thd, F_UNLCK); + table->file->ha_external_lock(thd, F_UNLCK); VOID(pthread_mutex_unlock(&LOCK_open)); } - - thd->last_insert_id=next_insert_id; // Needed for correct log thd->count_cuted_fields= CHECK_FIELD_IGNORE; - if (table->s->tmp_table) + /* If we did not need to copy, we might still need to add/drop indexes. */ + if (! new_table) { - /* We changed a temporary table */ - if (error) + 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->prepare_for_alter(); + if (index_add_count) + { +#ifdef XXX_TO_BE_DONE_LATER_BY_WL3020_AND_WL1892 + if (! need_lock_for_indexes) + { + /* Downgrade the write lock. */ + mysql_lock_downgrade_write(thd, table, TL_WRITE_ALLOW_WRITE); + } + + /* Create a new .frm file for crash recovery. */ + /* TODO: Must set INDEX_TO_BE_ADDED flags in the frm file. */ + VOID(pthread_mutex_lock(&LOCK_open)); + error= (mysql_create_frm(thd, reg_path, db, table_name, + create_info, prepared_create_list, key_count, + key_info_buffer, table->file) || + table->file->create_handler_files(reg_path, NULL, CHF_INDEX_FLAG, + create_info)); + VOID(pthread_mutex_unlock(&LOCK_open)); + if (error) + goto err1; +#endif + + /* 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->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))) + { + /* + 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 err1; + } + } + /*end of if (index_add_count)*/ + + if (index_drop_count) { +#ifdef XXX_TO_BE_DONE_LATER_BY_WL3020_AND_WL1892 + /* Create a new .frm file for crash recovery. */ + /* TODO: Must set INDEX_IS_ADDED in the frm file. */ + /* TODO: Must set INDEX_TO_BE_DROPPED in the frm file. */ + VOID(pthread_mutex_lock(&LOCK_open)); + error= (mysql_create_frm(thd, reg_path, db, table_name, + create_info, prepared_create_list, key_count, + key_info_buffer, table->file) || + table->file->create_handler_files(reg_path, NULL, CHF_INDEX_FLAG, + create_info)); + VOID(pthread_mutex_unlock(&LOCK_open)); + if (error) + goto err1; + + if (! need_lock_for_indexes) + { + LOCK_PARAM_TYPE lpt; + + lpt.thd= thd; + lpt.table= table; + lpt.db= db; + lpt.table_name= table_name; + lpt.create_info= create_info; + lpt.create_list= &create_list; + lpt.key_count= key_count; + lpt.key_info_buffer= key_info_buffer; + abort_and_upgrade_lock(lpt); + } +#endif + + /* 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; /* - The following function call will free the new_table pointer, - in close_temporary_table(), so we can safely directly jump to err + Tell the handler to prepare for drop indexes. + This re-numbers the indexes to get rid of gaps. */ - close_temporary_table(thd,new_db,tmp_name); - goto err; + if ((error= table->file->prepare_drop_index(table, key_numbers, + index_drop_count))) + { + table->file->print_error(error, MYF(0)); + goto err1; + } + +#ifdef XXX_TO_BE_DONE_LATER_BY_WL3020 + if (! need_lock_for_indexes) + { + /* Downgrade the lock again. */ + if (table->reginfo.lock_type == TL_WRITE_ALLOW_READ) + { + LOCK_PARAM_TYPE lpt; + + lpt.thd= thd; + lpt.table= table; + lpt.db= db; + lpt.table_name= table_name; + lpt.create_info= create_info; + lpt.create_list= &create_list; + lpt.key_count= key_count; + lpt.key_info_buffer= key_info_buffer; + close_open_tables_and_downgrade(lpt); + } + } +#endif + + /* Tell the handler to finally drop the indexes. */ + if ((error= table->file->final_drop_index(table))) + { + table->file->print_error(error, MYF(0)); + goto err1; + } } + /*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 (ha_commit_stmt(thd) || ha_commit(thd)) + goto err1; + committed= 1; + } + /*end of if (! new_table) for add/drop index*/ + + if (table->s->tmp_table != NO_TMP_TABLE) + { + /* We changed a temporary table */ + if (error) + goto err1; /* Close lock if this is a transactional table */ if (thd->lock) { @@ -3671,43 +6410,33 @@ view_err: thd->lock=0; } /* Remove link to old table and rename the new one */ - close_temporary_table(thd, table->s->db, table_name); + close_temporary_table(thd, table, 1, 1); /* Should pass the 'new_name' as we store table name in the cache */ if (rename_temporary_table(thd, new_table, new_db, new_name)) - { // Fatal error - close_temporary_table(thd,new_db,tmp_name); - my_free((gptr) new_table,MYF(0)); - goto err; - } - /* - Writing to the binlog does not need to be synchronized for temporary tables, - which are thread-specific. - */ - if (mysql_bin_log.is_open()) - { - thd->clear_error(); - Query_log_event qinfo(thd, thd->query, thd->query_length, FALSE, FALSE); - mysql_bin_log.write(&qinfo); - } + goto err1; + /* We don't replicate alter table statement on temporary tables */ + if (!thd->current_stmt_binlog_row_based) + write_bin_log(thd, TRUE, thd->query, thd->query_length); goto end_temporary; } if (new_table) { - intern_close_table(new_table); /* close temporary table */ + /* Close the intermediate table that will be the new table */ + intern_close_table(new_table); my_free((gptr) new_table,MYF(0)); } VOID(pthread_mutex_lock(&LOCK_open)); if (error) { - VOID(quick_rm_table(new_db_type,new_db,tmp_name)); + VOID(quick_rm_table(new_db_type, new_db, tmp_name, FN_IS_TMP)); VOID(pthread_mutex_unlock(&LOCK_open)); goto err; } /* Data is copied. Now we rename the old table to a temp name, - rename the new one to the old name, remove all entries from the old table + rename the new one to the old name, remove all entries about the old table from the cache, free all locks, close the old table and remove it. */ @@ -3722,50 +6451,72 @@ view_err: { error=1; my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_name_buff); - VOID(quick_rm_table(new_db_type,new_db,tmp_name)); + VOID(quick_rm_table(new_db_type, new_db, tmp_name, FN_IS_TMP)); VOID(pthread_mutex_unlock(&LOCK_open)); goto err; } } -#if (!defined( __WIN__) && !defined( __EMX__) && !defined( OS2)) +#if !defined( __WIN__) if (table->file->has_transactions()) #endif { /* Win32 and InnoDB can't drop a table that is in use, so we must - close the original table at before doing the rename + close the original table before doing the rename */ + table->s->version= 0; // Force removal of table def close_cached_table(thd, table); table=0; // Marker that table is closed no_table_reopen= TRUE; } -#if (!defined( __WIN__) && !defined( __EMX__) && !defined( OS2)) +#if !defined( __WIN__) else table->file->extra(HA_EXTRA_FORCE_REOPEN); // Don't use this file anymore #endif error=0; + save_old_db_type= old_db_type; + + /* + 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 + 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. + */ if (!need_copy_table) - new_db_type=old_db_type=DB_TYPE_UNKNOWN; // this type cannot happen in regular ALTER - if (mysql_rename_table(old_db_type,db,table_name,db,old_name)) + new_db_type=old_db_type= NULL; // this type cannot happen in regular ALTER + 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)); + 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) || + new_alias, FN_FROM_IS_TMP) || (new_name != table_name || new_db != db) && // we also do rename + (need_copy_table || + mysql_rename_table(save_old_db_type, db, table_name, new_db, + new_alias, NO_FRM_RENAME)) && Table_triggers_list::change_table_name(thd, db, table_name, new_db, new_alias)) - - { // Try to get everything back + { + /* Try to get everything back. */ error=1; - VOID(quick_rm_table(new_db_type,new_db,new_alias)); - VOID(quick_rm_table(new_db_type,new_db,tmp_name)); - VOID(mysql_rename_table(old_db_type,db,old_name,db,alias)); + VOID(quick_rm_table(new_db_type,new_db,new_alias, 0)); + 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)); } + if (error) { /* @@ -3773,19 +6524,52 @@ view_err: closing the locked table. */ if (table) + { + table->s->version= 0; // Force removal of table def close_cached_table(thd,table); + } VOID(pthread_mutex_unlock(&LOCK_open)); goto err; } + if (! need_copy_table) + { + if (! table) + { + if (new_name != table_name || new_db != db) + { + table_list->alias= new_name; + table_list->table_name= new_name; + table_list->table_name_length= strlen(new_name); + table_list->db= new_db; + table_list->db_length= strlen(new_db); + } + + VOID(pthread_mutex_unlock(&LOCK_open)); + if (! (table= open_ltable(thd, table_list, TL_WRITE_ALLOW_READ))) + goto err; + VOID(pthread_mutex_lock(&LOCK_open)); + } + /* Tell the handler that a new frm file is in place. */ + if (table->file->create_handler_files(path, NULL, CHF_INDEX_FLAG, + create_info)) + { + VOID(pthread_mutex_unlock(&LOCK_open)); + goto err; + } + } + if (thd->lock || new_name != table_name || no_table_reopen) // True if WIN32 { /* - Not table locking or alter table with rename - free locks and remove old table + Not table locking or alter table with rename. + Free locks and remove old table */ if (table) + { + table->s->version= 0; // Force removal of table def close_cached_table(thd,table); - VOID(quick_rm_table(old_db_type,db,old_name)); + } + VOID(quick_rm_table(old_db_type, db, old_name, FN_IS_TMP)); } else { @@ -3800,39 +6584,48 @@ view_err: /* Mark in-use copies old */ remove_table_from_cache(thd,db,table_name,RTFC_NO_FLAG); /* end threads waiting on lock */ - mysql_lock_abort(thd,table); + mysql_lock_abort(thd,table, TRUE); } - VOID(quick_rm_table(old_db_type,db,old_name)); + VOID(quick_rm_table(old_db_type, db, old_name, FN_IS_TMP)); if (close_data_tables(thd,db,table_name) || reopen_tables(thd,1,0)) { // This shouldn't happen if (table) + { + table->s->version= 0; // Force removal of table def close_cached_table(thd,table); // Remove lock for table + } VOID(pthread_mutex_unlock(&LOCK_open)); goto err; } } - /* The ALTER TABLE is always in its own transaction */ - error = ha_commit_stmt(thd); - if (ha_commit(thd)) - error=1; - if (error) + VOID(pthread_mutex_unlock(&LOCK_open)); + broadcast_refresh(); + /* + The ALTER TABLE is always in its own transaction. + Commit must not be called while LOCK_open is locked. It could call + wait_if_global_read_lock(), which could create a deadlock if called + with LOCK_open. + */ + if (!committed) { - VOID(pthread_mutex_unlock(&LOCK_open)); - broadcast_refresh(); - goto err; + error = ha_commit_stmt(thd); + if (ha_commit(thd)) + error=1; + if (error) + goto err; } thd->proc_info="end"; - if (mysql_bin_log.is_open()) - { - thd->clear_error(); - Query_log_event qinfo(thd, thd->query, thd->query_length, FALSE, FALSE); - mysql_bin_log.write(&qinfo); - } - broadcast_refresh(); - VOID(pthread_mutex_unlock(&LOCK_open)); -#ifdef HAVE_BERKELEY_DB - if (old_db_type == DB_TYPE_BERKELEY_DB) + + ha_binlog_log_query(thd, create_info->db_type, LOGCOM_ALTER_TABLE, + thd->query, thd->query_length, + db, table_name); + + DBUG_ASSERT(!(mysql_bin_log.is_open() && thd->current_stmt_binlog_row_based && + (create_info->options & HA_LEX_CREATE_TMP_TABLE))); + write_bin_log(thd, TRUE, thd->query, thd->query_length); + + 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 @@ -3840,7 +6633,7 @@ view_err: shutdown. */ char path[FN_REFLEN]; - build_table_path(path, sizeof(path), new_db, table_name, ""); + build_table_filename(path, sizeof(path), new_db, table_name, "", 0); table=open_temporary_table(thd, path, new_db, tmp_name,0); if (table) { @@ -3848,11 +6641,10 @@ view_err: my_free((char*) table, MYF(0)); } else - sql_print_warning("Could not open BDB table %s.%s after rename\n", + sql_print_warning("Could not open table %s.%s after rename\n", new_db,table_name); - (void) berkeley_flush_logs(); + ha_flush_logs(old_db_type); } -#endif table_list->table=0; // For query cache query_cache_invalidate3(thd, table_list, 0); @@ -3865,10 +6657,19 @@ end_temporary: thd->some_tables_deleted=0; DBUG_RETURN(FALSE); +err1: + if (new_table) + { + /* close_temporary_table() frees the new_table pointer. */ + close_temporary_table(thd, new_table, 1, 1); + } + else + VOID(quick_rm_table(new_db_type, new_db, tmp_name, FN_IS_TMP)); + err: DBUG_RETURN(TRUE); } - +/* mysql_alter_table */ static int copy_data_between_tables(TABLE *from,TABLE *to, @@ -3892,6 +6693,7 @@ copy_data_between_tables(TABLE *from,TABLE *to, ha_rows examined_rows; bool auto_increment_field_copied= 0; ulong save_sql_mode; + ulonglong prev_insert_id; DBUG_ENTER("copy_data_between_tables"); /* @@ -3907,7 +6709,7 @@ copy_data_between_tables(TABLE *from,TABLE *to, if (!(copy= new Copy_field[to->s->fields])) DBUG_RETURN(-1); /* purecov: inspected */ - if (to->file->external_lock(thd, F_WRLCK)) + if (to->file->ha_external_lock(thd, F_WRLCK)) DBUG_RETURN(-1); /* We need external lock before we can disable/enable keys */ @@ -3920,7 +6722,7 @@ copy_data_between_tables(TABLE *from,TABLE *to, MODE_STRICT_ALL_TABLES)); from->file->info(HA_STATUS_VARIABLE); - to->file->start_bulk_insert(from->file->records); + to->file->ha_start_bulk_insert(from->file->stats.records); save_sql_mode= thd->variables.sql_mode; @@ -3957,8 +6759,8 @@ copy_data_between_tables(TABLE *from,TABLE *to, MYF(MY_FAE | MY_ZEROFILL)); bzero((char*) &tables,sizeof(tables)); tables.table= from; - tables.alias= tables.table_name= (char*) from->s->table_name; - tables.db= (char*) from->s->db; + tables.alias= tables.table_name= from->s->table_name.str; + tables.db= from->s->db.str; error=1; if (thd->lex->select_lex.setup_ref_array(thd, order_num) || @@ -3966,18 +6768,14 @@ copy_data_between_tables(TABLE *from,TABLE *to, &tables, fields, all_fields, order) || !(sortorder=make_unireg_sortorder(order, &length, NULL)) || (from->sort.found_records = filesort(thd, from, sortorder, length, - (SQL_SELECT *) 0, HA_POS_ERROR, + (SQL_SELECT *) 0, HA_POS_ERROR, 1, &examined_rows)) == HA_POS_ERROR) goto err; }; - /* - Handler must be told explicitly to retrieve all columns, because - this function does not set field->query_id in the columns to the - current query id - */ - from->file->extra(HA_EXTRA_RETRIEVE_ALL_COLS); + /* Tell handler that we have values for all columns in the to table */ + to->use_all_columns(); init_read_record(&info, thd, from, (SQL_SELECT *) 0, 1,1); if (ignore) to->file->extra(HA_EXTRA_IGNORE_DUP_KEY); @@ -4004,16 +6802,31 @@ copy_data_between_tables(TABLE *from,TABLE *to, { copy_ptr->do_copy(copy_ptr); } - if ((error=to->file->write_row((byte*) to->record[0]))) + prev_insert_id= to->file->next_insert_id; + if ((error=to->file->ha_write_row((byte*) to->record[0]))) { if (!ignore || - (error != HA_ERR_FOUND_DUPP_KEY && - error != HA_ERR_FOUND_DUPP_UNIQUE)) + 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); + 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(key_nr, err_msg); + break; + } + } + to->file->print_error(error,MYF(0)); break; } - to->file->restore_auto_increment(); + to->file->restore_auto_increment(prev_insert_id); delete_count++; } else @@ -4023,7 +6836,7 @@ copy_data_between_tables(TABLE *from,TABLE *to, free_io_cache(from); delete [] copy; // This is never 0 - if (to->file->end_bulk_insert() && error <= 0) + if (to->file->ha_end_bulk_insert() && error <= 0) { to->file->print_error(my_errno,MYF(0)); error=1; @@ -4047,7 +6860,8 @@ copy_data_between_tables(TABLE *from,TABLE *to, free_io_cache(from); *copied= found_count; *deleted=delete_count; - if (to->file->external_lock(thd,F_UNLCK)) + to->file->ha_release_auto_increment(); + if (to->file->ha_external_lock(thd,F_UNLCK)) error=1; DBUG_RETURN(error > 0 ? -1 : 0); } @@ -4076,11 +6890,11 @@ bool mysql_recreate_table(THD *thd, TABLE_LIST *table_list, lex->col_list.empty(); lex->alter_info.reset(); bzero((char*) &create_info,sizeof(create_info)); - create_info.db_type=DB_TYPE_DEFAULT; + create_info.db_type= 0; create_info.row_type=ROW_TYPE_NOT_USED; create_info.default_table_charset=default_charset_info; /* Force alter table to recreate table */ - lex->alter_info.flags= ALTER_CHANGE_COLUMN; + lex->alter_info.flags= (ALTER_CHANGE_COLUMN | ALTER_RECREATE); DBUG_RETURN(mysql_alter_table(thd, NullS, NullS, &create_info, table_list, lex->create_list, lex->key_list, 0, (ORDER *) 0, @@ -4088,7 +6902,8 @@ bool mysql_recreate_table(THD *thd, TABLE_LIST *table_list, } -bool mysql_checksum_table(THD *thd, TABLE_LIST *tables, HA_CHECK_OPT *check_opt) +bool mysql_checksum_table(THD *thd, TABLE_LIST *tables, + HA_CHECK_OPT *check_opt) { TABLE_LIST *table; List<Item> field_list; @@ -4125,10 +6940,10 @@ bool mysql_checksum_table(THD *thd, TABLE_LIST *tables, HA_CHECK_OPT *check_opt) } else { - if (t->file->table_flags() & HA_HAS_CHECKSUM && + if (t->file->ha_table_flags() & HA_HAS_CHECKSUM && !(check_opt->flags & T_EXTEND)) protocol->store((ulonglong)t->file->checksum()); - else if (!(t->file->table_flags() & HA_HAS_CHECKSUM) && + else if (!(t->file->ha_table_flags() & HA_HAS_CHECKSUM) && (check_opt->flags & T_QUICK)) protocol->store_null(); else @@ -4137,10 +6952,7 @@ bool mysql_checksum_table(THD *thd, TABLE_LIST *tables, HA_CHECK_OPT *check_opt) ha_checksum crc= 0; uchar null_mask=256 - (1 << t->s->last_null_bit_pos); - /* InnoDB must be told explicitly to retrieve all columns, because - this function does not set field->query_id in the columns to the - current query id */ - t->file->extra(HA_EXTRA_RETRIEVE_ALL_COLS); + t->use_all_columns(); if (t->file->ha_rnd_init(1)) protocol->store_null(); @@ -4169,7 +6981,7 @@ bool mysql_checksum_table(THD *thd, TABLE_LIST *tables, HA_CHECK_OPT *check_opt) for (uint i= 0; i < t->s->fields; i++ ) { Field *f= t->field[i]; - if ((f->type() == FIELD_TYPE_BLOB) || + if ((f->type() == MYSQL_TYPE_BLOB) || (f->type() == MYSQL_TYPE_VARCHAR)) { String tmp; @@ -4206,22 +7018,35 @@ bool mysql_checksum_table(THD *thd, TABLE_LIST *tables, HA_CHECK_OPT *check_opt) } static bool check_engine(THD *thd, const char *table_name, - enum db_type *new_engine) + HA_CREATE_INFO *create_info) { - enum db_type req_engine= *new_engine; + handlerton **new_engine= &create_info->db_type; + handlerton *req_engine= *new_engine; bool no_substitution= test(thd->variables.sql_mode & MODE_NO_ENGINE_SUBSTITUTION); - if ((*new_engine= - ha_checktype(thd, req_engine, no_substitution, 1)) == DB_TYPE_UNKNOWN) + if (!(*new_engine= ha_checktype(thd, ha_legacy_type(req_engine), + no_substitution, 1))) return TRUE; - if (req_engine != *new_engine) + if (req_engine && req_engine != *new_engine) { push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_USING_OTHER_HANDLER, ER(ER_WARN_USING_OTHER_HANDLER), - ha_get_storage_engine(*new_engine), + ha_resolve_storage_engine_name(*new_engine), table_name); } + if (create_info->options & HA_LEX_CREATE_TMP_TABLE && + ha_check_storage_engine_flag(*new_engine, HTON_TEMPORARY_NOT_SUPPORTED)) + { + if (create_info->used_fields & HA_CREATE_USED_ENGINE) + { + my_error(ER_ILLEGAL_HA_CREATE_OPTION, MYF(0), + hton2plugin[(*new_engine)->slot]->name.str, "TEMPORARY"); + *new_engine= 0; + return TRUE; + } + *new_engine= myisam_hton; + } return FALSE; } |