diff options
Diffstat (limited to 'sql/sql_table.cc')
-rw-r--r-- | sql/sql_table.cc | 5445 |
1 files changed, 4279 insertions, 1166 deletions
diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 08ea093f2c8..29d43155778 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -1,4 +1,4 @@ -/* Copyright (C) 2000-2004 MySQL AB +/* Copyright 2000-2008 MySQL AB, 2008 Sun Microsystems, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,62 +16,1457 @@ /* 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); static char *make_unique_key_name(const char *field_name,KEY *start,KEY *end); static int copy_data_between_tables(TABLE *from,TABLE *to, - List<create_field> &create, bool ignore, + List<Create_field> &create, bool ignore, uint order_num, ORDER *order, ha_rows *copied,ha_rows *deleted, enum enum_enable_or_disable keys_onoff, bool error_if_not_empty); -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); -static void set_tmp_file_path(char *buf, size_t bufsize, THD *thd); +static bool prepare_blob_field(THD *thd, Create_field *sql_field); +static bool check_engine(THD *, const char *, HA_CREATE_INFO *); +static int +mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, + Alter_info *alter_info, + bool tmp_table, + uint *db_options, + handler *file, KEY **key_info_buffer, + uint *key_count, int select_field_count); +static bool +mysql_prepare_alter_table(THD *thd, TABLE *table, + HA_CREATE_INFO *create_info, + Alter_info *alter_info); + +#ifndef DBUG_OFF + +/* Wait until we get a 'mysql_kill' signal */ + +static void wait_for_kill_signal(THD *thd) +{ + while (thd->killed == 0) + sleep(1); + // Reset signal and continue as if nothing happend + thd->killed= THD::NOT_KILLED; +} +#endif /* - 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; + size_t 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); +} + + +/** + Check if given string begins with "#mysql50#" prefix, cut it if so. + + @param from string to check and cut + @param to[out] buffer for result string + @param to_length its size + + @retval + 0 no prefix found + @retval + non-0 result string length +*/ + +uint check_n_cut_mysql50_prefix(const char *from, char *to, uint to_length) +{ + if (from[0] == '#' && + !strncmp(from, MYSQL50_TABLE_NAME_PREFIX, + MYSQL50_TABLE_NAME_PREFIX_LENGTH)) + return (uint) (strmake(to, from + MYSQL50_TABLE_NAME_PREFIX_LENGTH, + to_length - 1) - to); + return 0; +} + + +/* + 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 ((length= check_n_cut_mysql50_prefix(from, to, to_length))) + DBUG_RETURN(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. + This may be the same as table_name. + 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) +{ + char dbbuff[FN_REFLEN]; + char tbbuff[FN_REFLEN]; + DBUG_ENTER("build_table_filename"); + DBUG_PRINT("enter", ("db: '%s' table_name: '%s' ext: '%s' flags: %x", + db, table_name, ext, flags)); + + 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))); + + char *end = buff + bufflen; + /* Don't add FN_ROOTDIR if mysql_data_home already includes it */ + char *pos = strnmov(buff, mysql_data_home, bufflen); + size_t rootdir_len= strlen(FN_ROOTDIR); + if (pos - rootdir_len >= buff && + memcmp(pos - rootdir_len, FN_ROOTDIR, rootdir_len) != 0) + pos= strnmov(pos, FN_ROOTDIR, end - pos); + pos= strxnmov(pos, end - pos, dbbuff, FN_ROOTDIR, NullS); +#ifdef USE_SYMDIR + unpack_dirname(buff, buff); + pos= strend(buff); +#endif + pos= strxnmov(pos, end - pos, tbbuff, ext, NullS); + + DBUG_PRINT("exit", ("buff: '%s'", buff)); + DBUG_RETURN(pos - buff); +} + + +/* + 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) +{ + DBUG_ENTER("build_tmptable_filename"); + + char *p= strnmov(buff, mysql_tmpdir, bufflen); + my_snprintf(p, bufflen - (p - buff), "/%s%lx_%lx_%x%s", + tmp_file_prefix, current_pid, + thd->thread_id, thd->tmp_table++, reg_ext); + + if (lower_case_table_names) + { + /* Convert all except tmpdir to lower case */ + my_casedn_str(files_charset_info, p); + } + + size_t length= unpack_filename(buff, buff); + DBUG_PRINT("exit", ("buff: '%s'", buff)); + DBUG_RETURN(length); +} + +/* +-------------------------------------------------------------------------- + + 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 +-------------------------------------------------------------------------- +*/ + + +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 do_release; + bool recovery_phase; + st_global_ddl_log() : inited(false), do_release(false) {} +}; + +st_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; + uchar *file_entry_buf= (uchar*)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, 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, (uchar*)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], + (ulong) const_var); + const_var= IO_SIZE; + int4store(&global_ddl_log.file_entry_buf[DDL_LOG_IO_SIZE_POS], + (ulong) 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(0))) >= 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)); + global_ddl_log.do_release= true; + 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() +{ + 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]; +#ifdef WITH_PARTITION_STORAGE_ENGINE + char *par_ext= (char*)".par"; +#endif + 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 + { + plugin_ref plugin= ha_resolve_by_name(thd, &handler_name); + if (!plugin) + { + my_error(ER_ILLEGAL_HA, MYF(0), ddl_log_entry->handler_name); + goto error; + } + hton= plugin_data(plugin, handlerton*); + file= get_new_handler((TABLE_SHARE*)0, &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->ha_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->ha_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); +} -uint build_table_path(char *buff, size_t bufflen, const char *db, - const char *table, const char *ext) + +/* + 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 + --------------------------------------------------- +*/ + +/* + 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) +{ + 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) { - strxnmov(buff, (uint) (bufflen - 1), mysql_data_home, "/", db, "/", table, - ext, NullS); - return unpack_filename(buff,buff); + 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"); + + if (!global_ddl_log.do_release) + DBUG_VOID_RETURN; + + pthread_mutex_lock(&LOCK_gdl); + while (used_list) + { + DDL_LOG_MEMORY_ENTRY *tmp= used_list->next_log_entry; + my_free(used_list, MYF(0)); + used_list= tmp; + } + while (free_list) + { + DDL_LOG_MEMORY_ENTRY *tmp= free_list->next_log_entry; + my_free(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)); + global_ddl_log.do_release= false; + DBUG_VOID_RETURN; +} + + +/* +--------------------------------------------------------------------------- + + END MODULE DDL log + -------------------- + +--------------------------------------------------------------------------- +*/ + + +/** + @brief construct a temporary shadow file name. + + @details Make a shadow file name used by ALTER TABLE to construct the + modified table (with keeping the original). The modified table is then + moved back as original table. The name must start with the temp file + prefix so it gets filtered out by table files listing routines. + + @param[out] buff buffer to receive the constructed name + @param bufflen size of buff + @param lpt alter table data structure + + @retval path length +*/ + +uint build_table_shadow_filename(char *buff, size_t bufflen, + ALTER_PARTITION_PARAM_TYPE *lpt) +{ + char tmp_name[FN_REFLEN]; + my_snprintf (tmp_name, sizeof (tmp_name), "%s-%s", tmp_file_prefix, + lpt->table_name); + return build_table_filename(buff, bufflen, lpt->db, tmp_name, "", FN_IS_TMP); +} + + +/* + 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_INSTALL_SHADOW If set we should install the new frm + WFRM_KEEP_SHARE If set we know that the share is to be + retained and thus we should ensure share + object is correct, if not set we don't + set the new partition syntax string since + we know the share object is destroyed. + 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]; +#ifdef WITH_PARTITION_STORAGE_ENGINE + char *part_syntax_buf; + uint syntax_len; +#endif + DBUG_ENTER("mysql_write_frm"); + + /* + Build shadow frm file name + */ + build_table_shadow_filename(shadow_path, sizeof(shadow_path), lpt); + strxmov(shadow_frm_name, shadow_path, reg_ext, NullS); + if (flags & WFRM_WRITE_SHADOW) + { + if (mysql_prepare_create_table(lpt->thd, lpt->create_info, + lpt->alter_info, + /*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; + 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->alter_info->create_list, lpt->key_count, + lpt->key_info_buffer, lpt->table->file)) || + lpt->table->file->ha_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. + */ + uchar *data; + size_t length; + if (readfrm(shadow_path, &data, &length) || + packfrm(data, length, &lpt->pack_frm_data, &lpt->pack_frm_len)) + { + my_free(data, MYF(MY_ALLOW_ZERO_PTR)); + my_free(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->ha_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->ha_create_handler_files(path, shadow_path, + CHF_RENAME_FLAG, NULL)) +#else + my_rename(shadow_frm_name, frm_name, MYF(MY_WME))) +#endif + { + error= 1; + goto err; + } +#ifdef WITH_PARTITION_STORAGE_ENGINE + if (part_info && (flags & WFRM_KEEP_SHARE)) + { + TABLE_SHARE *share= lpt->table->s; + char *tmp_part_syntax_str; + if (!(part_syntax_buf= generate_partition_syntax(part_info, + &syntax_len, + TRUE, TRUE))) + { + error= 1; + goto err; + } + if (share->partition_info_buffer_size < syntax_len + 1) + { + share->partition_info_buffer_size= syntax_len+1; + if (!(tmp_part_syntax_str= (char*) strmake_root(&share->mem_root, + part_syntax_buf, + syntax_len))) + { + error= 1; + goto err; + } + share->partition_info= tmp_part_syntax_str; + } + else + memcpy((char*) share->partition_info, part_syntax_buf, syntax_len + 1); + share->partition_info_len= part_info->part_info_len= syntax_len; + part_info->part_info_string= part_syntax_buf; + } +#endif + +err: + 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, THD::NOT_KILLED); + } +} + /* delete (drop) tables. @@ -120,71 +1515,17 @@ bool mysql_rm_table(THD *thd,TABLE_LIST *tables, my_bool if_exists, LOCK_open during wait_if_global_read_lock(), other threads could not close their tables. This would make a pretty deadlock. */ - thd->mysys_var->current_mutex= &LOCK_open; - thd->mysys_var->current_cond= &COND_refresh; - VOID(pthread_mutex_lock(&LOCK_open)); - error= mysql_rm_table_part2(thd, tables, if_exists, drop_temporary, 0, 0); - pthread_mutex_unlock(&LOCK_open); - - pthread_mutex_lock(&thd->mysys_var->mutex); - thd->mysys_var->current_mutex= 0; - thd->mysys_var->current_cond= 0; - pthread_mutex_unlock(&thd->mysys_var->mutex); - if (need_start_waiters) start_waiting_global_read_lock(thd); if (error) DBUG_RETURN(TRUE); - send_ok(thd); + my_ok(thd); DBUG_RETURN(FALSE); } - -/* - delete (drop) tables. - - SYNOPSIS - mysql_rm_table_part2_with_lock() - thd Thread handle - tables List of tables to delete - if_exists If 1, don't give error if one table doesn't exists - dont_log_query Don't write query to log files. This will also not - generate warnings if the handler files doesn't exists - - NOTES - Works like documented in mysql_rm_table(), but don't check - global_read_lock and don't send_ok packet to server. - - RETURN - 0 ok - 1 error -*/ - -int mysql_rm_table_part2_with_lock(THD *thd, - TABLE_LIST *tables, bool if_exists, - bool drop_temporary, bool dont_log_query) -{ - int error; - thd->mysys_var->current_mutex= &LOCK_open; - thd->mysys_var->current_cond= &COND_refresh; - VOID(pthread_mutex_lock(&LOCK_open)); - - error= mysql_rm_table_part2(thd, tables, if_exists, drop_temporary, 1, - dont_log_query); - - pthread_mutex_unlock(&LOCK_open); - - pthread_mutex_lock(&thd->mysys_var->mutex); - thd->mysys_var->current_mutex= 0; - thd->mysys_var->current_cond= 0; - pthread_mutex_unlock(&thd->mysys_var->mutex); - return error; -} - - /* Execute the drop of a normal or temporary table @@ -220,16 +1561,60 @@ 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 error= 0; + 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); - if (!drop_temporary && lock_table_names(thd, tables)) + 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 "); + } + + mysql_ha_rm_tables(thd, tables, FALSE); + + pthread_mutex_lock(&LOCK_open); + + /* + 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->table_category == TABLE_CATEGORY_PERFORMANCE) && + 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"); + pthread_mutex_unlock(&LOCK_open); + DBUG_RETURN(1); + } + } + + if (!drop_temporary && lock_table_names_exclusively(thd, tables)) + { + pthread_mutex_unlock(&LOCK_open); DBUG_RETURN(1); + } /* Don't give warnings for not found errors, as we already generate notes */ thd->no_warnings_for_error= 1; @@ -237,37 +1622,85 @@ 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; + + DBUG_PRINT("table", ("table_l: '%s'.'%s' table: 0x%lx s: 0x%lx", + table->db, table->table_name, (long) table->table, + table->table ? (long) table->table->s : (long) -1)); + + error= drop_temporary_table(thd, table); + + switch (error) { + case 0: + // removed temporary table + tmp_table_deleted= 1; + continue; + case -1: + DBUG_ASSERT(thd->in_sub_stmt); + error= 1; + goto err_with_placeholders; + default: + // temporary table not found + error= 0; + } - mysql_ha_flush(thd, table, MYSQL_HA_CLOSE_FINAL, TRUE); - if (!close_temporary_table(thd, db, table->table_name)) + /* + 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) { - tmp_table_deleted=1; - continue; // removed temporary table + 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) { - error=-1; + error= -1; goto err_with_placeholders; } 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, + table->internal_tmp_table ? + FN_IS_TMP : 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) @@ -280,14 +1713,21 @@ 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; + thd->clear_error(); + } if (error == HA_ERR_ROW_IS_REFERENCED) { /* the table is referenced by a foreign key constraint */ @@ -313,8 +1753,15 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, wrong_tables.append(','); wrong_tables.append(String(table->table_name,system_charset_info)); } + DBUG_PRINT("table", ("table: 0x%lx s: 0x%lx", (long) table->table, + table->table ? (long) table->table->s : (long) -1)); } - thd->tmp_table_used= tmp_table_deleted; + /* + It's safe to unlock LOCK_open: we have an exclusive lock + on the table name. + */ + pthread_mutex_unlock(&LOCK_open); + thd->thread_specific_used|= tmp_table_deleted; error= 0; if (wrong_tables.length()) { @@ -329,34 +1776,89 @@ 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, THD::NOT_KILLED); - 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. + */ } } - + pthread_mutex_lock(&LOCK_open); err_with_placeholders: - if (!drop_temporary) - unlock_table_names(thd, tables, (TABLE_LIST*) 0); + unlock_table_names(thd, tables, (TABLE_LIST*) 0); + pthread_mutex_unlock(&LOCK_open); thd->no_warnings_for_error= 0; DBUG_RETURN(error); } -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 + if (!(flags & FRM_ONLY)) + error|= ha_delete_table(current_thd, base, path, db, table_name, 0); + DBUG_RETURN(error); } /* @@ -489,7 +1991,7 @@ void calculate_interval_lengths(CHARSET_INFO *cs, TYPELIB *interval, for (pos= interval->type_names, len= interval->type_lengths; *pos ; pos++, len++) { - uint length= cs->cset->numchars(cs, *pos, *pos + *len); + size_t length= cs->cset->numchars(cs, *pos, *pos + *len); *tot_length+= length; set_if_bigger(*max_length, (uint32)length); } @@ -507,7 +2009,7 @@ void calculate_interval_lengths(CHARSET_INFO *cs, TYPELIB *interval, table_flags table flags DESCRIPTION - This function prepares a create_field instance. + This function prepares a Create_field instance. Fields such as pack_flag are valid after this call. RETURN VALUES @@ -515,25 +2017,25 @@ void calculate_interval_lengths(CHARSET_INFO *cs, TYPELIB *interval, 1 Error */ -int prepare_create_field(create_field *sql_field, +int prepare_create_field(Create_field *sql_field, uint *blob_columns, int *timestamps, int *timestamps_with_niladic, - uint table_flags) + longlong table_flags) { unsigned int dup_val_count; DBUG_ENTER("prepare_field"); /* - This code came from mysql_prepare_table. + This code came from mysql_prepare_create_table. Indent preserved to make patching easier */ 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); @@ -543,7 +2045,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)) { @@ -583,12 +2085,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) @@ -599,7 +2101,7 @@ int prepare_create_field(create_field *sql_field, sql_field->charset, &dup_val_count)) DBUG_RETURN(1); 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) @@ -616,19 +2118,20 @@ int prepare_create_field(create_field *sql_field, DBUG_RETURN(1); } 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(). + We have sql_field->pack_flag already set here, see + mysql_prepare_create_table(). */ break; - case FIELD_TYPE_NEWDECIMAL: + case MYSQL_TYPE_NEWDECIMAL: sql_field->pack_flag=(FIELDFLAG_NUMBER | (sql_field->flags & UNSIGNED_FLAG ? 0 : FIELDFLAG_DECIMAL) | @@ -636,7 +2139,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) { @@ -674,10 +2177,16 @@ int prepare_create_field(create_field *sql_field, Preparation for table creation SYNOPSIS - mysql_prepare_table() - thd Thread object - create_info Create information (like MAX_ROWS) - alter_info List of columns and indexes to create + mysql_prepare_create_table() + thd Thread object. + create_info Create information (like MAX_ROWS). + alter_info List of columns and indexes 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. @@ -686,19 +2195,20 @@ int prepare_create_field(create_field *sql_field, sets create_info->varchar if the table has a varchar RETURN VALUES - 0 ok - -1 error + FALSE OK + TRUE error */ -static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, - Alter_info *alter_info, - bool tmp_table, - uint *db_options, - handler *file, KEY **key_info_buffer, - uint *key_count, int select_field_count) +static int +mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, + Alter_info *alter_info, + bool tmp_table, + uint *db_options, + handler *file, KEY **key_info_buffer, + uint *key_count, int select_field_count) { const char *key_name; - create_field *sql_field,*dup_field; + Create_field *sql_field,*dup_field; uint field,null_fields,blob_columns,max_key_length; ulong record_offset= 0; KEY *key_info; @@ -706,10 +2216,10 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, int timestamps= 0, timestamps_with_niladic= 0; int field_no,dup_no; int select_field_pos,auto_increment=0; - List_iterator<create_field> it(alter_info->create_list); - List_iterator<create_field> it2(alter_info->create_list); + List_iterator<Create_field> it(alter_info->create_list); + List_iterator<Create_field> it2(alter_info->create_list); uint total_uneven_bit_length= 0; - DBUG_ENTER("mysql_prepare_table"); + DBUG_ENTER("mysql_prepare_create_table"); select_field_pos= alter_info->create_list.elements - select_field_count; null_fields=blob_columns=0; @@ -746,7 +2256,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, strmake(strmake(tmp, save_cs->csname, sizeof(tmp)-4), STRING_WITH_LEN("_bin")); my_error(ER_UNKNOWN_COLLATION, MYF(0), tmp); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } /* @@ -755,36 +2265,33 @@ 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)) - { - Query_arena backup_arena; - bool need_to_change_arena= !thd->stmt_arena->is_conventional(); - if (need_to_change_arena) - { - /* Asser that we don't do that at every PS execute */ - DBUG_ASSERT(thd->stmt_arena->is_first_stmt_execute() || - thd->stmt_arena->is_first_sp_execute()); - thd->set_n_backup_active_arena(thd->stmt_arena, &backup_arena); - } - + (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)) + { + /* + Starting from 5.1 we work here with a copy of Create_field + created by the caller, not with the instance that was + originally created during parsing. It's OK to create + a temporary item and initialize with it a member of the + copy -- this item will be thrown away along with the copy + at the end of execution, and thus not introduce a dangling + pointer in the parsed tree of a prepared statement or a + stored procedure statement. + */ sql_field->def= sql_field->def->safe_charset_converter(save_cs); - if (need_to_change_arena) - thd->restore_active_arena(thd->stmt_arena, &backup_arena); - if (sql_field->def == NULL) { /* Could not convert */ my_error(ER_INVALID_DEFAULT, MYF(0), sql_field->field_name); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } } - 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,12 +2305,11 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, if (!interval) { /* - Create the typelib in prepared statement memory if we're - executing one. + Create the typelib in runtime memory - we will free the + occupied memory at the same time when we free this + sql_field -- at the end of execution. */ - MEM_ROOT *stmt_root= thd->stmt_arena->mem_root; - - interval= sql_field->interval= typelib(stmt_root, + interval= sql_field->interval= typelib(thd->mem_root, sql_field->interval_list); List_iterator<String> int_it(sql_field->interval_list); String conv, *tmp; @@ -814,13 +2320,13 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, DBUG_ASSERT(comma_length > 0); for (uint i= 0; (tmp= int_it++); i++) { - uint lengthsp; + size_t lengthsp; if (String::needs_conversion(tmp->length(), tmp->charset(), cs, &dummy)) { uint cnv_errs; conv.copy(tmp->ptr(), tmp->length(), tmp->charset(), cs, &cnv_errs); - interval->type_names[i]= strmake_root(stmt_root, conv.ptr(), + interval->type_names[i]= strmake_root(thd->mem_root, conv.ptr(), conv.length()); interval->type_lengths[i]= conv.length(); } @@ -830,21 +2336,21 @@ 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], comma_buf, comma_length, NULL, 0)) { my_error(ER_ILLEGAL_VALUE_FOR_TYPE, MYF(0), "set", tmp->ptr()); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } } } 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) @@ -858,7 +2364,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, if ((sql_field->flags & NOT_NULL_FLAG) != 0) { my_error(ER_INVALID_DEFAULT, MYF(0), sql_field->field_name); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } /* else, NULL is an allowed value */ @@ -874,16 +2380,16 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, if (not_found) { my_error(ER_INVALID_DEFAULT, MYF(0), sql_field->field_name); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } } 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); @@ -892,7 +2398,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, if ((sql_field->flags & NOT_NULL_FLAG) != 0) { my_error(ER_INVALID_DEFAULT, MYF(0), sql_field->field_name); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } /* else, the defaults yield the correct length for NULLs. */ @@ -903,7 +2409,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, if (find_type2(interval, def->ptr(), def->length(), cs) == 0) /* not found */ { my_error(ER_INVALID_DEFAULT, MYF(0), sql_field->field_name); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } } } @@ -913,10 +2419,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; @@ -924,7 +2430,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, sql_field->create_length_to_internal_length(); if (prepare_blob_field(thd, sql_field)) - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); if (!(sql_field->flags & NOT_NULL_FLAG)) null_fields++; @@ -932,7 +2438,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, if (check_column_name(sql_field->field_name)) { my_error(ER_WRONG_COLUMN_NAME, MYF(0), sql_field->field_name); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } /* Check if we have used the same field name before */ @@ -949,7 +2455,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, if (field_no < select_field_pos || dup_no >= select_field_pos) { my_error(ER_DUP_FIELDNAME, MYF(0), sql_field->field_name); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } else { @@ -999,10 +2505,10 @@ 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())) - DBUG_RETURN(-1); + file->ha_table_flags())) + DBUG_RETURN(TRUE); if (sql_field->sql_type == MYSQL_TYPE_VARCHAR) - create_info->varchar= 1; + create_info->varchar= TRUE; sql_field->offset= record_offset; if (MTYP_TYPENR(sql_field->unireg_check) == Field::NEXT_NUMBER) auto_increment++; @@ -1012,26 +2518,26 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, { my_message(ER_TOO_MUCH_AUTO_TIMESTAMP_COLS, ER(ER_TOO_MUCH_AUTO_TIMESTAMP_COLS), MYF(0)); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } if (auto_increment > 1) { my_message(ER_WRONG_AUTO_KEY, ER(ER_WRONG_AUTO_KEY), MYF(0)); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } 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); + DBUG_RETURN(TRUE); } - 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)); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } /* Create keys */ @@ -1050,17 +2556,20 @@ 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)); + LEX_STRING key_name_str; if (key->type == Key::FOREIGN_KEY) { fk_key_count++; - foreign_key *fk_key= (foreign_key*) key; + Foreign_key *fk_key= (Foreign_key*) key; if (fk_key->ref_columns.elements && fk_key->ref_columns.elements != fk_key->columns.elements) { my_error(ER_WRONG_FK_DEF, MYF(0), (fk_key->name ? fk_key->name : "foreign key without name"), ER(ER_KEY_REF_DO_NOT_MATCH_TABLE_REF)); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } continue; } @@ -1069,12 +2578,15 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, if (key->columns.elements > tmp) { my_error(ER_TOO_MANY_KEY_PARTS,MYF(0),tmp); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } - if (key->name && strlen(key->name) > NAME_LEN) + key_name_str.str= (char*) key->name; + key_name_str.length= key->name ? strlen(key->name) : 0; + if (check_string_char_length(&key_name_str, "", NAME_CHAR_LEN, + system_charset_info, 1)) { my_error(ER_TOO_LONG_IDENT, MYF(0), key->name); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } key_iterator2.rewind (); if (key->type != Key::FOREIGN_KEY) @@ -1110,31 +2622,31 @@ 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); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } } tmp=file->max_keys(); if (*key_count > tmp) { my_error(ER_TOO_MANY_KEYS,MYF(0),tmp); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } - (*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 + DBUG_RETURN(TRUE); // Out of memory key_iterator.rewind(); key_number=0; for (; (key=key_iterator++) ; key_number++) { uint key_length=0; - key_part_spec *column; + Key_part_spec *column; if (key->name == ignore_key) { @@ -1146,12 +2658,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 @@ -1160,7 +2676,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, #else my_error(ER_FEATURE_DISABLED, MYF(0), sym_group_geom.name, sym_group_geom.needed_define); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); #endif case Key::FOREIGN_KEY: key_number--; // Skip this key @@ -1175,15 +2691,15 @@ 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)); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } } /* @@ -1197,16 +2713,16 @@ 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)); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } if (key_info->key_parts != 1) { my_error(ER_WRONG_ARGUMENTS, MYF(0), "SPATIAL INDEX"); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } } else if (key_info->algorithm == HA_KEY_ALG_RTREE) @@ -1215,24 +2731,36 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, if ((key_info->key_parts & 1) == 1) { my_error(ER_WRONG_ARGUMENTS, MYF(0), "RTREE INDEX"); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } /* TODO: To be deleted */ my_error(ER_NOT_SUPPORTED_YET, MYF(0), "RTREE INDEX"); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); #else my_error(ER_FEATURE_DISABLED, MYF(0), sym_group_rtree.name, sym_group_rtree.needed_define); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); #endif } - List_iterator<key_part_spec> cols(key->columns), cols2(key->columns); + /* 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++) { uint length; - key_part_spec *dup_column; + Key_part_spec *dup_column; it.rewind(); field=0; @@ -1244,7 +2772,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, if (!sql_field) { my_error(ER_KEY_COLUMN_DOES_NOT_EXITS, MYF(0), column->field_name); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } while ((dup_column= cols2++) != column) { @@ -1254,7 +2782,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, my_printf_error(ER_DUP_FIELDNAME, ER(ER_DUP_FIELDNAME),MYF(0), column->field_name); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } } cols2.rewind(); @@ -1284,13 +2812,19 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, { column->length*= sql_field->charset->mbmaxlen; + if (key->type == Key::SPATIAL && column->length) + { + my_error(ER_WRONG_SUB_KEY, MYF(0)); + DBUG_RETURN(TRUE); + } + 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); + DBUG_RETURN(TRUE); } if (f_is_geom(sql_field->pack_flag) && sql_field->geom_type == Field::GEOM_POINT) @@ -1298,7 +2832,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, if (!column->length) { my_error(ER_BLOB_KEY_WITHOUT_LENGTH, MYF(0), column->field_name); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } } #ifdef HAVE_SPATIAL @@ -1324,22 +2858,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(TRUE); + } + if (key->type == Key::SPATIAL) + { + my_message(ER_SPATIAL_CANT_HAVE_NULL, + ER(ER_SPATIAL_CANT_HAVE_NULL), MYF(0)); + DBUG_RETURN(TRUE); + } + } } 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 } } @@ -1371,7 +2907,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, else { my_error(ER_TOO_LONG_KEY,MYF(0),length); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } } } @@ -1379,20 +2915,20 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, (column->length > length || !Field::type_can_have_key_part (sql_field->sql_type) || ((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); + DBUG_RETURN(TRUE); } - 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) { my_error(ER_WRONG_KEY_COLUMN, MYF(0), column->field_name); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } if (length > file->max_key_part_length() && key->type != Key::FULLTEXT) { @@ -1411,7 +2947,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, else { my_error(ER_TOO_LONG_KEY,MYF(0),length); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } } key_part_info->length=(uint16) length; @@ -1444,7 +2980,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, { my_message(ER_MULTIPLE_PRI_KEY, ER(ER_MULTIPLE_PRI_KEY), MYF(0)); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } key_name=primary_key_name; primary_key=1; @@ -1455,7 +2991,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, if (check_if_keyname_exists(key_name, *key_info_buffer, key_info)) { my_error(ER_DUP_KEYNAME, MYF(0), key_name); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } key_info->name=(char*) key_name; } @@ -1463,7 +2999,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, if (!key_info->name || check_column_name(key_info->name)) { my_error(ER_WRONG_NAME_FOR_INDEX, MYF(0), key_info->name); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } if (!(key_info->flags & HA_NULL_PART_KEY)) unique_key=1; @@ -1471,27 +3007,90 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, if (key_length > max_key_length && key->type != Key::FULLTEXT) { my_error(ER_TOO_LONG_KEY,MYF(0),max_key_length); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } 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); + DBUG_RETURN(TRUE); } if (auto_increment > 0) { my_message(ER_WRONG_AUTO_KEY, ER(ER_WRONG_AUTO_KEY), MYF(0)); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } /* Sort keys in optimized order */ - my_qsort((gptr) *key_info_buffer, *key_count, sizeof(KEY), - (qsort_cmp) sort_keys); + my_qsort((uchar*) *key_info_buffer, *key_count, sizeof(KEY), + (qsort_cmp) sort_keys); create_info->null_bits= null_fields; - DBUG_RETURN(0); + /* Check fields. */ + it.rewind(); + while ((sql_field=it++)) + { + Field::utype type= (Field::utype) MTYP_TYPENR(sql_field->unireg_check); + + if (thd->variables.sql_mode & MODE_NO_ZERO_DATE && + !sql_field->def && + sql_field->sql_type == MYSQL_TYPE_TIMESTAMP && + (sql_field->flags & NOT_NULL_FLAG) && + (type == Field::NONE || type == Field::TIMESTAMP_UN_FIELD)) + { + /* + An error should be reported if: + - NO_ZERO_DATE SQL mode is active; + - there is no explicit DEFAULT clause (default column value); + - this is a TIMESTAMP column; + - the column is not NULL; + - this is not the DEFAULT CURRENT_TIMESTAMP column. + + In other words, an error should be reported if + - NO_ZERO_DATE SQL mode is active; + - the column definition is equivalent to + 'column_name TIMESTAMP DEFAULT 0'. + */ + + my_error(ER_INVALID_DEFAULT, MYF(0), sql_field->field_name); + DBUG_RETURN(TRUE); + } + } + + DBUG_RETURN(FALSE); +} + + +/* + 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; + } } @@ -1508,7 +3107,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, In this case the error is given */ -static bool prepare_blob_field(THD *thd, create_field *sql_field) +static bool prepare_blob_field(THD *thd, Create_field *sql_field) { DBUG_ENTER("prepare_blob_field"); @@ -1525,7 +3124,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", @@ -1533,7 +3132,7 @@ static bool prepare_blob_field(THD *thd, create_field *sql_field) push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, ER_AUTO_CONVERT, warn_buff); } - + if ((sql_field->flags & BLOB_FLAG) && sql_field->length) { if (sql_field->sql_type == FIELD_TYPE_BLOB || @@ -1551,8 +3150,9 @@ static bool prepare_blob_field(THD *thd, create_field *sql_field) /* - Preparation of create_field for SP function return values. - Based on code used in the inner loop of mysql_prepare_table() above + Preparation of Create_field for SP function return values. + Based on code used in the inner loop of mysql_prepare_create_table() + above. SYNOPSIS sp_prepare_create_field() @@ -1564,13 +3164,13 @@ static bool prepare_blob_field(THD *thd, create_field *sql_field) */ -void sp_prepare_create_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, @@ -1578,7 +3178,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, @@ -1588,7 +3188,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; @@ -1604,46 +3204,53 @@ void sp_prepare_create_field(THD *thd, create_field *sql_field) Create a table SYNOPSIS - mysql_create_table() - thd Thread object - db Database - table_name Table name - create_info [in/out] Create information (like MAX_ROWS) - alter_info [in/out] List of columns and indexes to create - internal_tmp_table Set to 1 if this is an internal temporary table - (From ALTER TABLE) + mysql_create_table_no_lock() + thd Thread object + db Database + table_name Table name + create_info Create information (like MAX_ROWS) + fields List of fields to create + keys List of keys to create + internal_tmp_table Set to 1 if this is an internal temporary table + (From ALTER TABLE) + select_field_count DESCRIPTION If one creates a temporary table, this is automatically opened + Note that this function assumes that caller already have taken + name-lock on table being created or used some other way to ensure + that concurrent operations won't intervene. mysql_create_table() + is a wrapper that can be used for this. + no_log is needed for the case of CREATE ... SELECT, as the logging will be done later in sql_insert.cc select_field_count is also used for CREATE ... SELECT, and must be zero for standard create of table. - Note that structures passed as 'create_info' and 'alter_info' parameters - may be modified by this function. It is responsibility of the caller to - make a copy of create_info in order to provide correct execution in - prepared statements/stored routines. - RETURN VALUES FALSE OK TRUE error */ -bool mysql_create_table(THD *thd,const char *db, const char *table_name, - HA_CREATE_INFO *create_info, - Alter_info *alter_info, - bool internal_tmp_table, - uint select_field_count) +bool mysql_create_table_no_lock(THD *thd, + const char *db, const char *table_name, + HA_CREATE_INFO *create_info, + Alter_info *alter_info, + bool internal_tmp_table, + uint select_field_count) { char path[FN_REFLEN]; + uint path_length; const char *alias; uint db_options, key_count; KEY *key_info_buffer; handler *file; bool error= TRUE; - DBUG_ENTER("mysql_create_table"); + DBUG_ENTER("mysql_create_table_no_lock"); + DBUG_PRINT("enter", ("db: '%s' table: '%s' tmp: %d", + db, table_name, internal_tmp_table)); + /* Check for duplicate fields and check type of table to create */ if (!alter_info->create_list.elements) @@ -1652,77 +3259,202 @@ 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(alter_info->key_list); + 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_FOREIGN_KEY_ON_PARTITIONED, 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 = %s create_info->db_type = %s", + ha_resolve_storage_engine_name(part_info->default_engine_type), + ha_resolve_storage_engine_name(create_info->db_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: %s", + ha_resolve_storage_engine_name(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 - if (mysql_prepare_table(thd, create_info, alter_info, internal_tmp_table, - &db_options, file, - &key_info_buffer, &key_count, - select_field_count)) - DBUG_RETURN(TRUE); + set_table_default_charset(thd, create_info, (char*) db); + + if (mysql_prepare_create_table(thd, create_info, alter_info, + internal_tmp_table, + &db_options, file, + &key_info_buffer, &key_count, + select_field_count)) + goto err; /* Check if table exists */ if (create_info->options & HA_LEX_CREATE_TMP_TABLE) { - set_tmp_file_path(path, sizeof(path), thd); + path_length= build_tmptable_filename(thd, path, sizeof(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); + 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) { @@ -1730,29 +3462,35 @@ 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; } - DBUG_PRINT("info",("1")); 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)) { - /* - Inspecting table cache for placeholders created by concurrent - CREATE TABLE ... SELECT statements to avoid interfering with them - is 5.0-only solution. Starting from 5.1 we solve this problem by - obtaining name-lock on the table to be created first. - */ - if (table_cache_has_open_placeholder(thd, db, table_name) || - !access(path, F_OK)) + if (!access(path,F_OK)) { if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS) goto warn; - DBUG_PRINT("info",("2")); 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; } } @@ -1782,48 +3520,88 @@ 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; break; default: DBUG_PRINT("info", ("error: %u from storage engine", retcode)); my_error(retcode, MYF(0),table_name); - goto end; + goto unlock_and_end; } } - thd->proc_info="creating table"; + thd_proc_info(thd, "creating table"); create_info->table_existed= 0; // Mark that table is created - if (thd->variables.sql_mode & MODE_NO_DIR_IN_CREATE) +#ifdef HAVE_READLINK + if (test_if_data_home_dir(create_info->data_file_name)) + { + my_error(ER_WRONG_ARGUMENTS, MYF(0), "DATA DIRECTORY"); + goto unlock_and_end; + } + if (test_if_data_home_dir(create_info->index_file_name)) + { + my_error(ER_WRONG_ARGUMENTS, MYF(0), "INDEX DIRECTORY"); + goto unlock_and_end; + } + +#ifdef WITH_PARTITION_STORAGE_ENGINE + if (check_partition_dirs(thd->lex->part_info)) + { + goto unlock_and_end; + } +#endif /* WITH_PARTITION_STORAGE_ENGINE */ + + if (!my_use_symdir || (thd->variables.sql_mode & MODE_NO_DIR_IN_CREATE)) +#endif /* HAVE_READLINK */ + { + if (create_info->data_file_name) + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + WARN_OPTION_IGNORED, ER(WARN_OPTION_IGNORED), + "DATA DIRECTORY"); + if (create_info->index_file_name) + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + WARN_OPTION_IGNORED, ER(WARN_OPTION_IGNORED), + "INDEX DIRECTORY"); create_info->data_file_name= create_info->index_file_name= 0; + } create_info->table_options=db_options; + path[path_length - reg_ext_length]= '\0'; // Remove .frm extension if (rea_create_table(thd, path, db, table_name, create_info, alter_info->create_list, - key_count, key_info_buffer)) - goto end; + 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; + thd->thread_specific_used= TRUE; } - if (!internal_tmp_table && mysql_bin_log.is_open()) - { - thd->clear_error(); - Query_log_event qinfo(thd, thd->query, thd->query_length, - FALSE, FALSE, THD::NOT_KILLED); - 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)); - thd->proc_info="After create"; + +err: + thd_proc_info(thd, "After create"); + delete file; DBUG_RETURN(error); warn: @@ -1832,9 +3610,87 @@ 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 and name-locking aware wrapper for mysql_create_table_no_lock(), +*/ + +bool mysql_create_table(THD *thd, const char *db, const char *table_name, + HA_CREATE_INFO *create_info, + Alter_info *alter_info, + bool internal_tmp_table, + uint select_field_count) +{ + TABLE *name_lock= 0; + 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,(uchar*) 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); + + if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE)) + { + if (lock_table_name_if_not_cached(thd, db, table_name, &name_lock)) + { + result= TRUE; + goto unlock; + } + if (!name_lock) + { + if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS) + { + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + ER_TABLE_EXISTS_ERROR, ER(ER_TABLE_EXISTS_ERROR), + table_name); + create_info->table_existed= 1; + result= FALSE; + } + else + { + my_error(ER_TABLE_EXISTS_ERROR,MYF(0),table_name); + result= TRUE; + } + goto unlock; + } + } + + result= mysql_create_table_no_lock(thd, db, table_name, create_info, + alter_info, + internal_tmp_table, + select_field_count); + +unlock: + if (name_lock) + { + pthread_mutex_lock(&LOCK_open); + unlink_open_table(thd, name_lock, FALSE); + pthread_mutex_unlock(&LOCK_open); + } + 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 **/ @@ -1878,24 +3734,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)); + + file= (base == NULL ? 0 : + get_new_handler((TABLE_SHARE*) 0, thd->mem_root, base)); - build_table_path(from, sizeof(from), old_db, old_name, ""); - build_table_path(to, sizeof(to), new_db, new_name, ""); + 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 @@ -1903,27 +3785,29 @@ 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 (!file || !(error=file->ha_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 */ if (file) - file->rename_table(to_base, from_base); + file->ha_rename_table(to_base, from_base); } } delete file; @@ -1942,8 +3826,9 @@ mysql_rename_table(enum db_type base, wait_while_table_is_used() thd Thread handler table Table to remove from cache - function HA_EXTRA_PREPARE_FOR_DELETE if table is to be deleted - HA_EXTRA_FORCE_REOPEN if table is not be used + function HA_EXTRA_PREPARE_FOR_DROP if table is to be deleted + HA_EXTRA_FORCE_REOPEN if table is not be used + HA_EXTRA_PREPARE_FOR_RENAME if table is to be renamed NOTES When returning, the table will be unusable for other threads until the table is closed. @@ -1953,20 +3838,24 @@ mysql_rename_table(enum db_type base, Win32 clients must also have a WRITE LOCK on the table ! */ -static void wait_while_table_is_used(THD *thd,TABLE *table, - enum ha_extra_function function) +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"); + 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)); + safe_mutex_assert_owner(&LOCK_open); 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; } @@ -1991,7 +3880,7 @@ void close_cached_table(THD *thd, TABLE *table) { DBUG_ENTER("close_cached_table"); - wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_DELETE); + wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN); /* Close lock if this is not got with LOCK TABLES */ if (thd->lock) { @@ -1999,7 +3888,7 @@ void close_cached_table(THD *thd, TABLE *table) thd->lock=0; // Start locked threads } /* Close all copies of 'table'. This also frees all LOCK TABLES lock */ - thd->open_tables=unlink_open_table(thd,thd->open_tables,table); + unlink_open_table(thd, table, TRUE); /* When lock on LOCK_open is freed other threads can continue */ broadcast_refresh(); @@ -2037,23 +3926,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); @@ -2083,16 +3971,22 @@ static int prepare_for_restore(THD* thd, TABLE_LIST* table, DBUG_RETURN(send_check_errmsg(thd, table, "restore", "Failed to open partially restored table")); } + /* A MERGE table must not come here. */ + DBUG_ASSERT(!table->table || !table->table->child_l); pthread_mutex_unlock(&LOCK_open); DBUG_RETURN(0); } -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)) @@ -2100,12 +3994,39 @@ 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); + } + + /* A MERGE table must not come here. */ + DBUG_ASSERT(!table->child_l); + + /* + 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; } /* @@ -2118,14 +4039,10 @@ 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; - if (table->s->frm_version != FRM_VER_TRUE_VARCHAR) { error= send_check_errmsg(thd, table_list, "repair", - "Failed reparing incompatible .FRM file"); + "Failed repairing incompatible .frm file"); goto end; } @@ -2135,10 +4052,12 @@ static int prepare_for_repair(THD* thd, TABLE_LIST *table_list, extentions array. First element of engine file name extentions array is meta/index file extention. Second element - data file extention. */ + 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 @@ -2202,7 +4121,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); } @@ -2236,7 +4159,9 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, int result_code; DBUG_ENTER("mysql_admin_table"); - field_list.push_back(item = new Item_empty_string("Table", NAME_LEN*2)); + if (end_active_trans(thd)) + DBUG_RETURN(1); + field_list.push_back(item = new Item_empty_string("Table", NAME_CHAR_LEN*2)); item->maybe_null = 1; field_list.push_back(item = new Item_empty_string("Op", 10)); item->maybe_null = 1; @@ -2248,17 +4173,19 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) DBUG_RETURN(TRUE); - mysql_ha_flush(thd, tables, MYSQL_HA_CLOSE_FINAL, FALSE); + mysql_ha_rm_tables(thd, tables, FALSE); + for (table= tables; table; table= table->next_local) { char table_name[NAME_LEN*2+2]; char* db = table->db; bool fatal_error=0; + DBUG_PRINT("admin", ("table: '%s'.'%s'", table->db, table->table_name)); + DBUG_PRINT("admin", ("extra_open_options: %u", extra_open_options)); strxmov(table_name, db, ".", table->table_name, NullS); thd->open_options|= extra_open_options; table->lock_type= lock_type; - /* open only one table from local list of command */ { TABLE_LIST *save_next_global, *save_next_local; @@ -2266,7 +4193,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, table->next_global= 0; save_next_local= table->next_local; table->next_local= 0; - select->table_list.first= (byte*)table; + select->table_list.first= (uchar*)table; /* Time zone tables and SP tables can be add to lex->query_tables list, so it have to be prepared. @@ -2279,21 +4206,74 @@ 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; + open_and_lock_tables(thd, table); thd->no_warnings_for_error= 0; table->next_global= save_next_global; table->next_local= save_next_local; thd->open_options&= ~extra_open_options; +#ifdef WITH_PARTITION_STORAGE_ENGINE + if (table->table) + { + /* + Set up which partitions that should be processed + if ALTER TABLE t ANALYZE/CHECK/OPTIMIZE/REPAIR PARTITION .. + */ + Alter_info *alter_info= &lex->alter_info; + + if (alter_info->flags & ALTER_ADMIN_PARTITION) + { + if (!table->table->part_info) + { + my_error(ER_PARTITION_MGMT_ON_NONPARTITIONED, MYF(0)); + DBUG_RETURN(TRUE); + } + uint no_parts_found; + uint no_parts_opt= alter_info->partition_names.elements; + no_parts_found= set_part_state(alter_info, table->table->part_info, + PART_CHANGED); + if (no_parts_found != no_parts_opt && + (!(alter_info->flags & ALTER_ALL_PARTITION))) + { + char buff[FN_REFLEN + MYSQL_ERRMSG_SIZE]; + size_t length; + DBUG_PRINT("admin", ("sending non existent partition error")); + protocol->prepare_for_resend(); + protocol->store(table_name, system_charset_info); + protocol->store(operator_name, system_charset_info); + protocol->store(STRING_WITH_LEN("error"), system_charset_info); + length= my_snprintf(buff, sizeof(buff), + ER(ER_DROP_PARTITION_NON_EXISTENT), + table_name); + protocol->store(buff, length, system_charset_info); + if(protocol->write()) + goto err; + my_eof(thd); + goto err; + } + } + } +#endif } + DBUG_PRINT("admin", ("table: 0x%lx", (long) table->table)); + if (prepare_func) { + DBUG_PRINT("admin", ("calling prepare_func")); switch ((*prepare_func)(thd, table, check_opt)) { case 1: // error, message written to net + ha_autocommit_or_rollback(thd, 1); + end_trans(thd, ROLLBACK); close_thread_tables(thd); + DBUG_PRINT("admin", ("simple error, admin next table")); continue; case -1: // error, message could be written to net + /* purecov: begin inspected */ + DBUG_PRINT("admin", ("severe error, stop")); goto err; + /* purecov: end */ default: // should be 0 otherwise + DBUG_PRINT("admin", ("prepare_func succeeded")); ; } } @@ -2308,6 +4288,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, */ if (!table->table) { + DBUG_PRINT("admin", ("open table failed")); if (!thd->warn_list.elements) push_warning(thd, MYSQL_ERROR::WARN_LEVEL_ERROR, ER_CHECK_NO_SUCH_TABLE, ER(ER_CHECK_NO_SUCH_TABLE)); @@ -2316,7 +4297,9 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, view_checksum(thd, table) == HA_ADMIN_WRONG_CHECKSUM) push_warning(thd, MYSQL_ERROR::WARN_LEVEL_ERROR, ER_VIEW_CHECKSUM, ER(ER_VIEW_CHECKSUM)); - if (thd->net.last_errno == ER_NO_SUCH_TABLE) + if (thd->main_da.is_error() && + (thd->main_da.sql_errno() == ER_NO_SUCH_TABLE || + thd->main_da.sql_errno() == ER_FILE_NOT_FOUND)) /* A missing table is just issued as a failed command */ result_code= HA_ADMIN_FAILED; else @@ -2327,6 +4310,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, if (table->view) { + DBUG_PRINT("admin", ("calling view_operator_func")); result_code= (*view_operator_func)(thd, table); goto send_result; } @@ -2339,8 +4323,10 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, if ((table->table->db_stat & HA_READ_ONLY) && open_for_modify) { + /* purecov: begin inspected */ char buff[FN_REFLEN + MYSQL_ERRMSG_SIZE]; - uint length; + size_t length; + DBUG_PRINT("admin", ("sending error message")); protocol->prepare_for_resend(); protocol->store(table_name, system_charset_info); protocol->store(operator_name, system_charset_info); @@ -2348,25 +4334,32 @@ 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); + end_trans(thd, COMMIT); close_thread_tables(thd); + lex->reset_query_tables_list(FALSE); table->table=0; // For query cache if (protocol->write()) goto err; + thd->main_da.reset_diagnostics_area(); continue; + /* purecov: end */ } /* Close all instances of the table to allow repair to rename files */ if (lock_type == TL_WRITE && table->table->s->version) { + DBUG_PRINT("admin", ("removing table from cache")); 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); + DBUG_EXECUTE_IF("wait_in_mysql_admin_table", wait_for_kill_signal(thd);); if (thd->killed) goto err; /* Flush entries in the query cache involving this table. */ @@ -2376,6 +4369,8 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, if (table->table->s->crashed && operator_func == &handler::ha_check) { + /* purecov: begin inspected */ + DBUG_PRINT("admin", ("sending crashed warning")); protocol->prepare_for_resend(); protocol->store(table_name, system_charset_info); protocol->store(operator_name, system_charset_info); @@ -2384,27 +4379,37 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, system_charset_info); if (protocol->write()) goto err; + /* purecov: end */ } - if (operator_func == &handler::ha_repair) + if (operator_func == &handler::ha_repair && + !(check_opt->sql_flags & TT_USEFRM)) { if ((table->table->file->check_old_types() == HA_ADMIN_NEEDS_ALTER) || (table->table->file->ha_check_for_upgrade(check_opt) == HA_ADMIN_NEEDS_ALTER)) { - my_bool save_no_send_ok= thd->net.no_send_ok; + DBUG_PRINT("admin", ("recreating table")); + ha_autocommit_or_rollback(thd, 1); close_thread_tables(thd); tmp_disable_binlog(thd); // binlogging is done by caller if wanted - thd->net.no_send_ok= TRUE; result_code= mysql_recreate_table(thd, table); - thd->net.no_send_ok= save_no_send_ok; reenable_binlog(thd); + /* + mysql_recreate_table() can push OK or ERROR. + Clear 'OK' status. If there is an error, keep it: + we will store the error message in a result set row + and then clear. + */ + if (thd->main_da.is_ok()) + thd->main_da.reset_diagnostics_area(); goto send_result; } - } + DBUG_PRINT("admin", ("calling operator_func '%s'", operator_name)); result_code = (table->table->file->*operator_func)(thd, check_opt); + DBUG_PRINT("admin", ("operator_func returned: %d", result_code)); send_result: @@ -2418,8 +4423,9 @@ send_result: protocol->prepare_for_resend(); protocol->store(table_name, system_charset_info); protocol->store((char*) operator_name, system_charset_info); - protocol->store(warning_level_names[err->level], - warning_level_length[err->level], system_charset_info); + protocol->store(warning_level_names[err->level].str, + warning_level_names[err->level].length, + system_charset_info); protocol->store(err->msg, system_charset_info); if (protocol->write()) goto err; @@ -2436,8 +4442,8 @@ send_result_message: switch (result_code) { case HA_ADMIN_NOT_IMPLEMENTED: { - char buf[ERRMSGSIZE+20]; - uint length=my_snprintf(buf, ERRMSGSIZE, + char buf[MYSQL_ERRMSG_SIZE]; + size_t length=my_snprintf(buf, sizeof(buf), ER(ER_CHECK_NOT_IMPLEMENTED), operator_name); protocol->store(STRING_WITH_LEN("note"), system_charset_info); protocol->store(buf, length, system_charset_info); @@ -2446,8 +4452,8 @@ send_result_message: case HA_ADMIN_NOT_BASE_TABLE: { - char buf[ERRMSGSIZE+20]; - uint length= my_snprintf(buf, ERRMSGSIZE, + char buf[MYSQL_ERRMSG_SIZE]; + size_t length= my_snprintf(buf, sizeof(buf), ER(ER_BAD_TABLE_ERROR), table_name); protocol->store(STRING_WITH_LEN("note"), system_charset_info); protocol->store(buf, length, system_charset_info); @@ -2492,33 +4498,53 @@ send_result_message: case HA_ADMIN_TRY_ALTER: { - my_bool save_no_send_ok= thd->net.no_send_ok; /* This is currently used only by InnoDB. ha_innobase::optimize() answers "try with alter", so here we close the table, do an ALTER TABLE, reopen the table and do ha_innobase::analyze() on it. + We have to end the row, so analyze could return more rows. */ + protocol->store(STRING_WITH_LEN("note"), system_charset_info); + protocol->store(STRING_WITH_LEN( + "Table does not support optimize, doing recreate + analyze instead"), + system_charset_info); + if (protocol->write()) + goto err; + ha_autocommit_or_rollback(thd, 0); close_thread_tables(thd); + DBUG_PRINT("info", ("HA_ADMIN_TRY_ALTER, trying analyze...")); TABLE_LIST *save_next_local= table->next_local, *save_next_global= table->next_global; table->next_local= table->next_global= 0; tmp_disable_binlog(thd); // binlogging is done by caller if wanted - thd->net.no_send_ok= TRUE; result_code= mysql_recreate_table(thd, table); - thd->net.no_send_ok= save_no_send_ok; reenable_binlog(thd); + /* + mysql_recreate_table() can push OK or ERROR. + Clear 'OK' status. If there is an error, keep it: + we will store the error message in a result set row + and then clear. + */ + if (thd->main_da.is_ok()) + thd->main_da.reset_diagnostics_area(); + ha_autocommit_or_rollback(thd, 0); close_thread_tables(thd); if (!result_code) // recreation went ok { - if ((table->table= open_ltable(thd, table, lock_type)) && - ((result_code= table->table->file->analyze(thd, check_opt)) > 0)) + if ((table->table= open_ltable(thd, table, lock_type, 0)) && + ((result_code= table->table->file->ha_analyze(thd, check_opt)) > 0)) result_code= 0; // analyze went ok } + /* Start a new row for the final status row */ + protocol->prepare_for_resend(); + protocol->store(table_name, system_charset_info); + protocol->store(operator_name, system_charset_info); if (result_code) // either mysql_recreate_table or analyze failed { - const char *err_msg; - if ((err_msg= thd->net.last_error)) + DBUG_ASSERT(thd->is_error()); + if (thd->is_error()) { + const char *err_msg= thd->main_da.message(); if (!thd->vio_ok()) { sql_print_error(err_msg); @@ -2528,12 +4554,14 @@ send_result_message: /* Hijack the row already in-progress. */ protocol->store(STRING_WITH_LEN("error"), system_charset_info); protocol->store(err_msg, system_charset_info); - (void)protocol->write(); + if (protocol->write()) + goto err; /* Start off another row for HA_ADMIN_FAILED */ protocol->prepare_for_resend(); protocol->store(table_name, system_charset_info); protocol->store(operator_name, system_charset_info); } + thd->clear_error(); } } result_code= result_code ? HA_ADMIN_FAILED : HA_ADMIN_OK; @@ -2544,7 +4572,7 @@ send_result_message: case HA_ADMIN_WRONG_CHECKSUM: { protocol->store(STRING_WITH_LEN("note"), system_charset_info); - protocol->store(ER(ER_VIEW_CHECKSUM), (uint) strlen(ER(ER_VIEW_CHECKSUM)), + protocol->store(ER(ER_VIEW_CHECKSUM), strlen(ER(ER_VIEW_CHECKSUM)), system_charset_info); break; } @@ -2552,11 +4580,12 @@ send_result_message: case HA_ADMIN_NEEDS_UPGRADE: case HA_ADMIN_NEEDS_ALTER: { - char buf[ERRMSGSIZE]; - uint length; + char buf[MYSQL_ERRMSG_SIZE]; + size_t length; protocol->store(STRING_WITH_LEN("error"), system_charset_info); - length=my_snprintf(buf, ERRMSGSIZE, ER(ER_TABLE_NEEDS_UPGRADE), table->table_name); + length=my_snprintf(buf, sizeof(buf), ER(ER_TABLE_NEEDS_UPGRADE), + table->table_name); protocol->store(buf, length, system_charset_info); fatal_error=1; break; @@ -2564,8 +4593,8 @@ send_result_message: default: // Probably HA_ADMIN_INTERNAL_ERROR { - char buf[ERRMSGSIZE+20]; - uint length=my_snprintf(buf, ERRMSGSIZE, + char buf[MYSQL_ERRMSG_SIZE]; + size_t length=my_snprintf(buf, sizeof(buf), "Unknown - internal error %d during operation", result_code); protocol->store(STRING_WITH_LEN("error"), system_charset_info); @@ -2585,24 +4614,28 @@ send_result_message: 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); + end_trans(thd, COMMIT); close_thread_tables(thd); - lex->reset_query_tables_list(FALSE); table->table=0; // For query cache if (protocol->write()) goto err; } - send_eof(thd); + my_eof(thd); DBUG_RETURN(FALSE); - err: + +err: + ha_autocommit_or_rollback(thd, 1); + end_trans(thd, ROLLBACK); close_thread_tables(thd); // Shouldn't be needed if (table) table->table=0; @@ -2613,19 +4646,23 @@ send_result_message: bool mysql_backup_table(THD* thd, TABLE_LIST* table_list) { DBUG_ENTER("mysql_backup_table"); + WARN_DEPRECATED(thd, "6.0", "BACKUP TABLE", + "MySQL Administrator (mysqldump, mysql)"); DBUG_RETURN(mysql_admin_table(thd, table_list, 0, "backup", TL_READ, 0, 0, 0, 0, - &handler::backup, 0)); + &handler::ha_backup, 0)); } bool mysql_restore_table(THD* thd, TABLE_LIST* table_list) { DBUG_ENTER("mysql_restore_table"); + WARN_DEPRECATED(thd, "6.0", "RESTORE TABLE", + "MySQL Administrator (mysqldump, mysql)"); DBUG_RETURN(mysql_admin_table(thd, table_list, 0, "restore", TL_WRITE, 1, 1, 0, &prepare_for_restore, - &handler::restore, 0)); + &handler::ha_restore, 0)); } @@ -2646,7 +4683,7 @@ bool mysql_optimize_table(THD* thd, TABLE_LIST* tables, HA_CHECK_OPT* check_opt) DBUG_ENTER("mysql_optimize_table"); DBUG_RETURN(mysql_admin_table(thd, tables, check_opt, "optimize", TL_WRITE, 1,0,0,0, - &handler::optimize, 0)); + &handler::ha_optimize, 0)); } @@ -2741,12 +4778,66 @@ int reassign_keycache_tables(THD *thd, KEY_CACHE *src_cache, bool mysql_preload_keys(THD* thd, TABLE_LIST* tables) { DBUG_ENTER("mysql_preload_keys"); + /* + We cannot allow concurrent inserts. The storage engine reads + directly from the index file, bypassing the cache. It could read + outdated information if parallel inserts into cache blocks happen. + */ DBUG_RETURN(mysql_admin_table(thd, tables, 0, - "preload_keys", TL_READ, 0, 0, 0, 0, + "preload_keys", TL_READ_NO_INSERT, 0, 0, 0, 0, &handler::preload_keys, 0)); } + +/** + @brief Create frm file based on I_S table + + @param[in] thd thread handler + @param[in] schema_table I_S table + @param[in] dst_path path where frm should be created + @param[in] create_info Create info + + @return Operation status + @retval 0 success + @retval 1 error +*/ + + +bool mysql_create_like_schema_frm(THD* thd, TABLE_LIST* schema_table, + char *dst_path, HA_CREATE_INFO *create_info) +{ + HA_CREATE_INFO local_create_info; + Alter_info alter_info; + bool tmp_table= (create_info->options & HA_LEX_CREATE_TMP_TABLE); + uint keys= schema_table->table->s->keys; + uint db_options= 0; + DBUG_ENTER("mysql_create_like_schema_frm"); + + bzero((char*) &local_create_info, sizeof(local_create_info)); + local_create_info.db_type= schema_table->table->s->db_type(); + local_create_info.row_type= schema_table->table->s->row_type; + local_create_info.default_table_charset=default_charset_info; + alter_info.flags= (ALTER_CHANGE_COLUMN | ALTER_RECREATE); + schema_table->table->use_all_columns(); + if (mysql_prepare_alter_table(thd, schema_table->table, + &local_create_info, &alter_info)) + DBUG_RETURN(1); + if (mysql_prepare_create_table(thd, &local_create_info, &alter_info, + tmp_table, &db_options, + schema_table->table->file, + &schema_table->table->s->key_info, &keys, 0)) + DBUG_RETURN(1); + local_create_info.max_rows= 0; + if (mysql_create_frm(thd, dst_path, NullS, NullS, + &local_create_info, alter_info.create_list, + keys, schema_table->table->s->key_info, + schema_table->table->file)) + DBUG_RETURN(1); + DBUG_RETURN(0); +} + + /* Create a table identical to the specified table @@ -2756,104 +4847,78 @@ bool mysql_preload_keys(THD* thd, TABLE_LIST* tables) table Table list element for target table src_table Table list element for source table create_info Create info - table_ident Src table_ident RETURN VALUES FALSE OK TRUE error */ -bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST *src_table, +bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, HA_CREATE_INFO *create_info) { - TABLE **tmp_table; + TABLE *name_lock= 0; char src_path[FN_REFLEN], dst_path[FN_REFLEN]; + uint dst_path_length; char *db= table->db; char *table_name= table->table_name; int err; bool res= TRUE; - db_type not_used; + uint not_used; +#ifdef WITH_PARTITION_STORAGE_ENGINE + char tmp_path[FN_REFLEN]; +#endif + char ts_name[FN_LEN]; DBUG_ENTER("mysql_create_like_table"); + /* - By taking name-lock on the source table and holding LOCK_open mutex we - ensure that no concurrent DDL operation will mess with this table. Note - that holding only name-lock is not enough for this, because it won't block - other DDL statements that only take name-locks on the table and don't - open it (simple name-locks are not exclusive between each other). - - Unfortunately, simply opening this table is not enough for our purproses, - since in 5.0 ALTER TABLE may change .FRM files on disk even if there are - connections that still have old version of table open. This 'optimization' - was removed in 5.1 so there we open the source table instead of taking - name-lock on it. - - We also have to acquire LOCK_open to make copying of .frm file, call to - ha_create_table() and binlogging atomic against concurrent DML and DDL - operations on the target table. + By opening source table we guarantee that it exists and no concurrent + DDL operation will mess with it. Later we also take an exclusive + name-lock on target table name, which makes copying of .frm file, + call to ha_create_table() and binlogging atomic against concurrent DML + and DDL operations on target table. Thus by holding both these "locks" + we ensure that our statement is properly isolated from all concurrent + operations which matter. */ - if (lock_and_wait_for_table_name(thd, src_table)) - DBUG_RETURN(res); - - pthread_mutex_lock(&LOCK_open); - - if ((tmp_table= find_temporary_table(thd, src_table->db, - src_table->table_name))) - strxmov(src_path, (*tmp_table)->s->path, reg_ext, NullS); - else - { - char *tablename_pos= strxmov(src_path, mysql_data_home, "/", NullS); - strxmov(tablename_pos, src_table->db, "/", src_table->table_name, - reg_ext, NullS); - if (lower_case_table_names) - my_casedn_str(files_charset_info, tablename_pos); - /* Resolve symlinks (for windows) */ - fn_format(src_path, src_path, "", "", MYF(MY_UNPACK_FILENAME)); - if (access(src_path, F_OK)) - { - my_error(ER_BAD_TABLE_ERROR, MYF(0), src_table->table_name); - goto err; - } - } + if (open_tables(thd, &src_table, ¬_used, 0)) + DBUG_RETURN(TRUE); - /* - create like should be not allowed for Views, Triggers, ... + /* + For bug#25875, Newly created table through CREATE TABLE .. LIKE + has no ndb_dd attributes; + Add something to get possible tablespace info from src table, + it can get valid tablespace name only for disk-base ndb table */ - if (mysql_frm_type(thd, src_path, ¬_used) != FRMTYPE_TABLE) + if ((src_table->table->file->get_tablespace_name(thd, ts_name, FN_LEN))) { - my_error(ER_WRONG_OBJECT, MYF(0), src_table->db, src_table->table_name, - "BASE TABLE"); - goto err; + create_info->tablespace= ts_name; + create_info->storage_media= HA_SM_DISK; } + strxmov(src_path, src_table->table->s->path.str, reg_ext, NullS); + DBUG_EXECUTE_IF("sleep_create_like_before_check_if_exists", my_sleep(6000000);); /* - Validate the destination table - - skip the destination table name checking as this is already - validated. + Check that destination tables does not exist. Note that its name + was already checked when it was added to the table list. */ if (create_info->options & HA_LEX_CREATE_TMP_TABLE) { if (find_temporary_table(thd, db, table_name)) goto table_exists; - set_tmp_file_path(dst_path, sizeof(dst_path), thd); + dst_path_length= build_tmptable_filename(thd, dst_path, sizeof(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)); - - /* - Note that starting from 5.1 we obtain name-lock on target - table instead of inspecting table cache for presence - of open placeholders (see comment in mysql_create_table()). - */ - if (table_cache_has_open_placeholder(thd, db, table_name) || - !access(dst_path, F_OK)) + if (lock_table_name_if_not_cached(thd, db, table_name, &name_lock)) + goto err; + if (!name_lock) + goto table_exists; + 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; } @@ -2861,27 +4926,62 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST *src_table, /* Create a new table by copying from source table + + Altough exclusive name-lock on target table protects us from concurrent + DML and DDL operations on it we still want to wrap .FRM creation and call + to ha_create_table() in critical section protected by LOCK_open in order + to provide minimal atomicity against operations which disregard name-locks, + like I_S implementation, for example. This is a temporary and should not + be copied. Instead we should fix our code to always honor name-locks. + + Also some engines (e.g. NDB cluster) require that LOCK_open should be held + during the call to ha_create_table(). See bug #28614 for more info. */ - if (my_copy(src_path, dst_path, MYF(MY_DONT_OVERWRITE_FILE))) + VOID(pthread_mutex_lock(&LOCK_open)); + if (src_table->schema_table) + { + if (mysql_create_like_schema_frm(thd, src_table, dst_path, create_info)) + { + VOID(pthread_mutex_unlock(&LOCK_open)); + goto err; + } + } + else if (my_copy(src_path, dst_path, MYF(MY_DONT_OVERWRITE_FILE))) { if (my_errno == ENOENT) my_error(ER_BAD_DB_ERROR,MYF(0),db); else my_error(ER_CANT_CREATE_FILE,MYF(0),dst_path,my_errno); + VOID(pthread_mutex_unlock(&LOCK_open)); goto err; } - DBUG_EXECUTE_IF("sleep_create_like_before_ha_create", my_sleep(6000000);); - /* As mysql_truncate don't work on a new table at this stage of creation, instead create the table directly (for both normal and temporary tables). */ - *fn_ext(dst_path)= 0; +#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 + + DBUG_EXECUTE_IF("sleep_create_like_before_ha_create", my_sleep(6000000);); + + dst_path[dst_path_length - reg_ext_length]= '\0'; // Remove .frm if (thd->variables.keep_files_on_create) create_info->options|= HA_CREATE_KEEP_FILES; - err= ha_create_table(dst_path, create_info, 1); + err= ha_create_table(thd, dst_path, db, table_name, create_info, 1); + VOID(pthread_mutex_unlock(&LOCK_open)); if (create_info->options & HA_LEX_CREATE_TMP_TABLE) { @@ -2891,24 +4991,77 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST *src_table, dst_path); /* purecov: inspected */ goto err; /* purecov: inspected */ } + thd->thread_specific_used= TRUE; } 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 */ } DBUG_EXECUTE_IF("sleep_create_like_before_binlogging", my_sleep(6000000);); - // 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, THD::NOT_KILLED); - 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 + ==== ========= ========= ============================== + */ + if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE)) + { + if (src_table->table->s->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 + + /* + Here we open the destination table, on which we already have + name-lock. This is needed for store_create_info() to work. + The table will be closed by unlink_open_table() at the end + of this function. + */ + table->table= name_lock; + VOID(pthread_mutex_lock(&LOCK_open)); + if (reopen_name_locked_table(thd, table, FALSE)) + { + VOID(pthread_mutex_unlock(&LOCK_open)); + goto err; + } + VOID(pthread_mutex_unlock(&LOCK_open)); + + IF_DBUG(int result=) + store_create_info(thd, table, &query, + create_info, FALSE /* show_database */); + + 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; @@ -2926,34 +5079,30 @@ table_exists: my_error(ER_TABLE_EXISTS_ERROR, MYF(0), table_name); err: - unlock_table_name(thd, src_table); - pthread_mutex_unlock(&LOCK_open); + if (name_lock) + { + pthread_mutex_lock(&LOCK_open); + unlink_open_table(thd, name_lock, FALSE); + 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, "analyze", lock_type, 1, 0, 0, 0, - &handler::analyze, 0)); + &handler::ha_analyze, 0)); } 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, @@ -2979,7 +5128,7 @@ mysql_discard_or_import_tablespace(THD *thd, ALTER TABLE */ - thd->proc_info="discard_or_import_tablespace"; + thd_proc_info(thd, "discard_or_import_tablespace"); discard= test(tablespace_op == DISCARD_TABLESPACE); @@ -2988,15 +5137,15 @@ mysql_discard_or_import_tablespace(THD *thd, not complain when we lock the table */ thd->tablespace_op= TRUE; - if (!(table=open_ltable(thd,table_list,TL_WRITE))) + if (!(table=open_ltable(thd, table_list, TL_WRITE, 0))) { thd->tablespace_op=FALSE; DBUG_RETURN(-1); } - error=table->file->discard_or_import_tablespace(discard); + error= table->file->ha_discard_or_import_tablespace(discard); - thd->proc_info="end"; + thd_proc_info(thd, "end"); if (error) goto err; @@ -3008,24 +5157,20 @@ mysql_discard_or_import_tablespace(THD *thd, query_cache_invalidate3(thd, table_list, 0); /* The ALTER TABLE is always in its own transaction */ - error = ha_commit_stmt(thd); - if (ha_commit(thd)) + error = ha_autocommit_or_rollback(thd, 0); + if (end_active_trans(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, THD::NOT_KILLED); - mysql_bin_log.write(&qinfo); - } + write_bin_log(thd, FALSE, thd->query, thd->query_length); + err: - close_thread_tables(thd); + ha_autocommit_or_rollback(thd, error); thd->tablespace_op=FALSE; if (error == 0) { - send_ok(thd); + my_ok(thd); DBUG_RETURN(0); } @@ -3036,354 +5181,460 @@ err: /* - Manages enabling/disabling of indexes for ALTER TABLE - SYNOPSIS - alter_table_manage_keys() - table Target table - indexes_were_disabled Whether the indexes of the from table - were disabled - keys_onoff ENABLE | DISABLE | LEAVE_AS_IS + compare_tables() + table The original table. + alter_info Alter options, fields and keys for the new + table. + create_info Create options for the new table. + order_num Number of order list elements. + need_copy_table OUT Result of the comparison. Undefined if error. + Otherwise is one of: + ALTER_TABLE_METADATA_ONLY No copy needed + ALTER_TABLE_DATA_CHANGED Data changes, + copy needed + ALTER_TABLE_INDEX_CHANGED Index changes, + copy might be needed + key_info_buffer OUT An array of KEY structs for new indexes + index_drop_buffer OUT An array of offsets into table->key_info. + index_drop_count OUT The number of elements in the array. + index_add_buffer OUT An array of offsets into key_info_buffer. + index_add_count OUT The number of elements in the array. + candidate_key_count OUT The number of candidate keys in original table. + + DESCRIPTION + 'table' (first argument) contains information of the original + table, which includes all corresponding parts that the new + table has in arguments create_list, key_list and create_info. + + 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 - FALSE OK - TRUE Error + TRUE error + FALSE success */ static -bool alter_table_manage_keys(TABLE *table, int indexes_were_disabled, - enum enum_enable_or_disable keys_onoff) +bool +compare_tables(TABLE *table, + Alter_info *alter_info, + HA_CREATE_INFO *create_info, + uint order_num, + enum_alter_table_change_level *need_copy_table, + KEY **key_info_buffer, + uint **index_drop_buffer, uint *index_drop_count, + uint **index_add_buffer, uint *index_add_count, + uint *candidate_key_count) { - int error= 0; - DBUG_ENTER("alter_table_manage_keys"); - DBUG_PRINT("enter", ("table=%p were_disabled=%d on_off=%d", - table, indexes_were_disabled, keys_onoff)); - - switch (keys_onoff) { - case ENABLE: - error= table->file->enable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE); - break; - case LEAVE_AS_IS: - if (!indexes_were_disabled) - break; - /* fall-through: disabled indexes */ - case DISABLE: - error= table->file->disable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE); - } - - if (error == HA_ERR_WRONG_COMMAND) - { - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA), table->s->table_name); - error= 0; - } else if (error) - table->file->print_error(error, MYF(0)); + Field **f_ptr, *field; + uint changes= 0, tmp; + uint key_count; + List_iterator_fast<Create_field> new_field_it, tmp_new_field_it; + Create_field *new_field, *tmp_new_field; + KEY_PART_INFO *key_part; + KEY_PART_INFO *end; + THD *thd= table->in_use; + /* + Remember if the new definition has new VARCHAR column; + create_info->varchar will be reset in mysql_prepare_create_table. + */ + bool varchar= create_info->varchar; + bool not_nullable= true; + DBUG_ENTER("compare_tables"); - DBUG_RETURN(error); -} + /* + Create a copy of alter_info. + To compare the new and old table definitions, we need to "prepare" + the new definition - transform it from parser output to a format + that describes the final table layout (all column defaults are + initialized, duplicate columns are removed). This is done by + mysql_prepare_create_table. Unfortunately, + mysql_prepare_create_table performs its transformations + "in-place", that is, modifies the argument. Since we would + like to keep compare_tables() idempotent (not altering any + of the arguments) we create a copy of alter_info here and + pass it to mysql_prepare_create_table, then use the result + to evaluate possibility of fast ALTER TABLE, and then + destroy the copy. + */ + Alter_info tmp_alter_info(*alter_info, thd->mem_root); + uint db_options= 0; /* not used */ + /* Create the prepared information. */ + if (mysql_prepare_create_table(thd, create_info, + &tmp_alter_info, + (table->s->tmp_table != NO_TMP_TABLE), + &db_options, + table->file, key_info_buffer, + &key_count, 0)) + DBUG_RETURN(1); + /* Allocate result buffers. */ + if (! (*index_drop_buffer= + (uint*) thd->alloc(sizeof(uint) * table->s->keys)) || + ! (*index_add_buffer= + (uint*) thd->alloc(sizeof(uint) * tmp_alter_info.key_list.elements))) + DBUG_RETURN(1); + + /* + Some very basic checks. If number of fields changes, or the + handler, we need to run full ALTER TABLE. In the future + new fields can be added and old dropped without copy, but + not yet. + Test also that engine was not given during ALTER TABLE, or + we are force to run regular alter table (copy). + E.g. ALTER TABLE tbl_name ENGINE=MyISAM. -/* - Alter table + 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. - NOTE - The structures passed as 'create_info' and 'alter_info' parameters may - be modified by this function. It is responsibility of the caller to make - a copy of create_info in order to provide correct execution in prepared - statements/stored routines. -*/ + 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 != alter_info->create_list.elements || + table->s->db_type() != create_info->db_type || + table->s->tmp_table || + create_info->used_fields & HA_CREATE_USED_ENGINE || + create_info->used_fields & HA_CREATE_USED_CHARSET || + create_info->used_fields & HA_CREATE_USED_DEFAULT_CHARSET || + create_info->used_fields & HA_CREATE_USED_ROW_FORMAT || + create_info->used_fields & HA_CREATE_USED_PACK_KEYS || + create_info->used_fields & HA_CREATE_USED_MAX_ROWS || + (alter_info->flags & (ALTER_RECREATE | ALTER_FOREIGN_KEY)) || + order_num || + !table->s->mysql_version || + (table->s->frm_version < FRM_VER_TRUE_VARCHAR && varchar)) + { + *need_copy_table= ALTER_TABLE_DATA_CHANGED; + DBUG_RETURN(0); + } -bool mysql_alter_table(THD *thd,char *new_db, char *new_name, - HA_CREATE_INFO *create_info, - TABLE_LIST *table_list, - Alter_info *alter_info, - uint order_num, ORDER *order, bool ignore) -{ - TABLE *table,*new_table=0; - int error= 0; - 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]; - 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; - frm_type_enum frm_type; /* - Throw an error if the table to be altered isn't empty. - Used in DATE/DATETIME fields default value checking. + Use transformed info to evaluate possibility of fast ALTER TABLE + but use the preserved field to persist modifications. */ - bool error_if_not_empty= FALSE; + new_field_it.init(alter_info->create_list); + tmp_new_field_it.init(tmp_alter_info.create_list); + /* - A field used for error reporting in DATE/DATETIME fields default - value checking. + Go through fields and check if the original ones are compatible + with new table. */ - create_field *new_datetime_field= 0; - DBUG_ENTER("mysql_alter_table"); + for (f_ptr= table->field, new_field= new_field_it++, + tmp_new_field= tmp_new_field_it++; + (field= *f_ptr); + f_ptr++, new_field= new_field_it++, + tmp_new_field= tmp_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; - thd->proc_info="init"; - table_name=table_list->table_name; - alias= (lower_case_table_names == 2) ? table_list->alias : table_name; + /* Check that NULL behavior is same for old and new fields */ + if ((tmp_new_field->flags & NOT_NULL_FLAG) != + (uint) (field->flags & NOT_NULL_FLAG)) + { + *need_copy_table= ALTER_TABLE_DATA_CHANGED; + DBUG_RETURN(0); + } - db=table_list->db; - if (!new_db || !my_strcasecmp(table_alias_charset, new_db, db)) - new_db= db; - used_fields=create_info->used_fields; - - mysql_ha_flush(thd, table_list, MYSQL_HA_CLOSE_FINAL, FALSE); + /* Don't pack rows in old tables if the user has requested this. */ + if (create_info->row_type == ROW_TYPE_DYNAMIC || + (tmp_new_field->flags & BLOB_FLAG) || + tmp_new_field->sql_type == MYSQL_TYPE_VARCHAR && + create_info->row_type != ROW_TYPE_FIXED) + create_info->table_options|= HA_OPTION_PACK_RECORD; - /* DISCARD/IMPORT TABLESPACE is always alone in an ALTER TABLE */ - if (alter_info->tablespace_op != NO_TABLESPACE_OP) - /* Conditionally writes to binlog. */ - DBUG_RETURN(mysql_discard_or_import_tablespace(thd,table_list, - alter_info->tablespace_op)); - sprintf(new_name_buff,"%s/%s/%s%s",mysql_data_home, db, table_name, reg_ext); - unpack_filename(new_name_buff, new_name_buff); - frm_type= mysql_frm_type(thd, new_name_buff, &table_type); - /* Rename a view */ - if (frm_type == FRMTYPE_VIEW && !(alter_info->flags & ~ALTER_RENAME)) + /* Check if field was renamed */ + field->flags&= ~FIELD_IS_RENAMED; + if (my_strcasecmp(system_charset_info, + field->field_name, + tmp_new_field->field_name)) + field->flags|= FIELD_IS_RENAMED; + + /* Evaluate changes bitmap and send to check_if_incompatible_data() */ + if (!(tmp= field->is_equal(tmp_new_field))) + { + *need_copy_table= ALTER_TABLE_DATA_CHANGED; + DBUG_RETURN(0); + } + // 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; + *candidate_key_count= 0; + for (table_key= table->key_info; table_key < table_key_end; table_key++) { - /* - Avoid problems with a rename on a table that we have locked or - if the user is trying to to do this in a transcation context + KEY_PART_INFO *table_part; + KEY_PART_INFO *table_part_end= table_key->key_part + table_key->key_parts; + KEY_PART_INFO *new_part; + + /* + Check if key is a candidate key, i.e. a unique index with no index + fields nullable, then key is either already primary key or could + be promoted to primary key if the original primary key is dropped. + Count all candidate keys. */ - - if (thd->locked_tables || thd->active_transaction()) + not_nullable= true; + for (table_part= table_key->key_part; + table_part < table_part_end; + table_part++) { - my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, - ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); - DBUG_RETURN(1); + not_nullable= not_nullable && (! table_part->field->maybe_null()); } + if ((table_key->flags & HA_NOSAME) && not_nullable) + (*candidate_key_count)++; - if (wait_if_global_read_lock(thd,0,1)) - DBUG_RETURN(1); - VOID(pthread_mutex_lock(&LOCK_open)); - if (lock_table_names(thd, table_list)) + /* Search a new key with the same name. */ + for (new_key= *key_info_buffer; new_key < new_key_end; new_key++) { - error= 1; - goto view_err; + if (! strcmp(table_key->name, new_key->name)) + break; } - - if (!do_rename(thd, table_list, new_db, new_name, new_name, 1)) + if (new_key >= new_key_end) { - if (mysql_bin_log.is_open()) - { - thd->clear_error(); - Query_log_event qinfo(thd, thd->query, thd->query_length, - 0, FALSE, THD::NOT_KILLED); - mysql_bin_log.write(&qinfo); - } - send_ok(thd); + /* 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; } - unlock_table_names(thd, table_list, (TABLE_LIST*) 0); + /* 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; -view_err: - pthread_mutex_unlock(&LOCK_open); - start_waiting_global_read_lock(thd); - DBUG_RETURN(error); + /* + 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)); } - if (!(table=open_ltable(thd,table_list,TL_WRITE_ALLOW_READ))) - DBUG_RETURN(TRUE); + /*end of for (; table_key < table_key_end;) */ - /* Check that we are not trying to rename to an existing table */ - if (new_name) + /* + 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++) { - strmov(new_name_buff,new_name); - strmov(new_alias= new_alias_buff, new_name); - if (lower_case_table_names) + /* Search an old key with the same name. */ + for (table_key= table->key_info; table_key < table_key_end; table_key++) { - if (lower_case_table_names != 2) - { - my_casedn_str(files_charset_info, new_name_buff); - new_alias= new_name; // Create lower case table name - } - my_casedn_str(files_charset_info, new_name); - } - if (new_db == db && - !my_strcasecmp(table_alias_charset, new_name_buff, table_name)) - { - /* - Source and destination table names are equal: make later check - easier. - */ - new_alias= new_name= table_name; + if (! strcmp(table_key->name, new_key->name)) + break; } - else + if (table_key >= table_key_end) { - if (table->s->tmp_table) + /* 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++) { - if (find_temporary_table(thd,new_db,new_name_buff)) - { - my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_name_buff); - DBUG_RETURN(TRUE); - } - } - else - { - char dir_buff[FN_REFLEN]; - bool exists; - strxnmov(dir_buff, FN_REFLEN, mysql_real_data_home, new_db, NullS); - VOID(pthread_mutex_lock(&LOCK_open)); - exists= (table_cache_has_open_placeholder(thd, new_db, new_name) || - !access(fn_format(new_name_buff, new_name_buff, dir_buff, - reg_ext, 0), F_OK)); - VOID(pthread_mutex_unlock(&LOCK_open)); - if (exists) - { - /* Table will be closed in do_command() */ - my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_alias); - DBUG_RETURN(TRUE); - } + // 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)); } } - else + + /* Check if changes are compatible with current handler without a copy */ + if (table->file->check_if_incompatible_data(create_info, changes)) { - new_alias= (lower_case_table_names == 2) ? alias : table_name; - new_name= table_name; + *need_copy_table= ALTER_TABLE_DATA_CHANGED; + DBUG_RETURN(0); } - 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)) - 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)); - 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)) + if (*index_drop_count || *index_add_count) { - DBUG_PRINT("info", ("doesn't support alter")); - my_error(ER_ILLEGAL_HA, MYF(0), table_name); - DBUG_RETURN(TRUE); + *need_copy_table= ALTER_TABLE_INDEX_CHANGED; + DBUG_RETURN(0); } - - thd->proc_info="setup"; - if (!(alter_info->flags & ~(ALTER_RENAME | ALTER_KEYS_ONOFF)) && - !table->s->tmp_table) // no need to touch frm - { - switch (alter_info->keys_onoff) { - case LEAVE_AS_IS: - break; - case ENABLE: - /* - wait_while_table_is_used() ensures that table being altered is - opened only by this thread and that TABLE::TABLE_SHARE::version - of TABLE object corresponding to this table is 0. - The latter guarantees that no DML statement will open this table - until ALTER TABLE finishes (i.e. until close_thread_tables()) - while the fact that the table is still open gives us protection - from concurrent DDL statements. - */ - VOID(pthread_mutex_lock(&LOCK_open)); - wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN); - VOID(pthread_mutex_unlock(&LOCK_open)); - error= table->file->enable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE); - /* COND_refresh will be signaled in close_thread_tables() */ - break; - case DISABLE: - VOID(pthread_mutex_lock(&LOCK_open)); - wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN); - VOID(pthread_mutex_unlock(&LOCK_open)); - error=table->file->disable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE); - /* COND_refresh will be signaled in close_thread_tables() */ - break; - } - if (error == HA_ERR_WRONG_COMMAND) - { - error= 0; - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA), - table->alias); - } - VOID(pthread_mutex_lock(&LOCK_open)); - /* - Unlike to the above case close_cached_table() below will remove ALL - instances of TABLE from table cache (it will also remove table lock - held by this thread). So to make actual table renaming and writing - to binlog atomic we have to put them into the same critical section - protected by LOCK_open mutex. This also removes gap for races between - access() and mysql_rename_table() calls. - */ + *need_copy_table= ALTER_TABLE_METADATA_ONLY; // Tables are compatible + DBUG_RETURN(0); +} - if (!error && (new_name != table_name || new_db != db)) - { - thd->proc_info="rename"; - /* - Then do a 'simple' rename of the table. First we need to close all - instances of 'source' table. - */ - close_cached_table(thd, table); - /* - Then, we want check once again that target table does not exist. - Note that we can't fully rely on results of previous check since - no lock was taken on target table during it. We also can't do this - before calling close_cached_table() as the latter temporarily - releases LOCK_open mutex. - Also note that starting from 5.1 we use approach with obtaining - of name-lock on target table. - */ - if (table_cache_has_open_placeholder(thd, new_db, new_name) || - !access(new_name_buff,F_OK)) - { - my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_name); - error= -1; - } - else - { - *fn_ext(new_name)=0; - if (mysql_rename_table(old_db_type,db,table_name,new_db,new_alias)) - 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)); - error= -1; - } - } - } - if (error == HA_ERR_WRONG_COMMAND) - { - error= 0; - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA), - table->alias); - } +/* + Manages enabling/disabling of indexes for ALTER TABLE - if (!error) - { - if (mysql_bin_log.is_open()) - { - thd->clear_error(); - Query_log_event qinfo(thd, thd->query, thd->query_length, - FALSE, FALSE, THD::NOT_KILLED); - mysql_bin_log.write(&qinfo); - } - send_ok(thd); - } - else if (error > 0) - { - table->file->print_error(error, MYF(0)); - error= -1; - } - VOID(pthread_mutex_unlock(&LOCK_open)); - table_list->table= NULL; // For query cache - query_cache_invalidate3(thd, table_list, 0); - DBUG_RETURN(error); + SYNOPSIS + alter_table_manage_keys() + table Target table + indexes_were_disabled Whether the indexes of the from table + were disabled + keys_onoff ENABLE | DISABLE | LEAVE_AS_IS + + RETURN VALUES + FALSE OK + TRUE Error +*/ + +static +bool alter_table_manage_keys(TABLE *table, int indexes_were_disabled, + enum enum_enable_or_disable keys_onoff) +{ + int error= 0; + DBUG_ENTER("alter_table_manage_keys"); + DBUG_PRINT("enter", ("table=%p were_disabled=%d on_off=%d", + table, indexes_were_disabled, keys_onoff)); + + switch (keys_onoff) { + case ENABLE: + error= table->file->ha_enable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE); + break; + case LEAVE_AS_IS: + if (!indexes_were_disabled) + break; + /* fall-through: disabled indexes */ + case DISABLE: + error= table->file->ha_disable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE); } - /* Full alter table */ + if (error == HA_ERR_WRONG_COMMAND) + { + push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA), + table->s->table_name.str); + error= 0; + } else if (error) + table->file->print_error(error, MYF(0)); + + DBUG_RETURN(error); +} + +/** + Prepare column and key definitions for CREATE TABLE in ALTER TABLE. + + This function transforms parse output of ALTER TABLE - lists of + columns and keys to add, drop or modify into, essentially, + CREATE TABLE definition - a list of columns and keys of the new + table. While doing so, it also performs some (bug not all) + semantic checks. + + This function is invoked when we know that we're going to + perform ALTER TABLE via a temporary table -- i.e. fast ALTER TABLE + is not possible, perhaps because the ALTER statement contains + instructions that require change in table data, not only in + table definition or indexes. + + @param[in,out] thd thread handle. Used as a memory pool + and source of environment information. + @param[in] table the source table, open and locked + Used as an interface to the storage engine + to acquire additional information about + the original table. + @param[in,out] create_info A blob with CREATE/ALTER TABLE + parameters + @param[in,out] alter_info Another blob with ALTER/CREATE parameters. + Originally create_info was used only in + CREATE TABLE and alter_info only in ALTER TABLE. + But since ALTER might end-up doing CREATE, + this distinction is gone and we just carry + around two structures. + + @return + Fills various create_info members based on information retrieved + from the storage engine. + Sets create_info->varchar if the table has a VARCHAR column. + Prepares alter_info->create_list and alter_info->key_list with + columns and keys of the new table. + @retval TRUE error, out of memory or a semantical error in ALTER + TABLE instructions + @retval FALSE success +*/ + +static bool +mysql_prepare_alter_table(THD *thd, TABLE *table, + HA_CREATE_INFO *create_info, + Alter_info *alter_info) +{ + /* New column definitions are added here */ + List<Create_field> new_create_list; + /* New key definitions are added here */ + List<Key> new_key_list; + List_iterator<Alter_drop> drop_it(alter_info->drop_list); + List_iterator<Create_field> def_it(alter_info->create_list); + List_iterator<Alter_column> alter_it(alter_info->alter_list); + List_iterator<Key> key_it(alter_info->key_list); + List_iterator<Create_field> find_it(new_create_list); + List_iterator<Create_field> field_it(new_create_list); + List<Key_part_spec> key_parts; + uint db_create_options= (table->s->db_create_options + & ~(HA_OPTION_PACK_RECORD)); + uint used_fields= create_info->used_fields; + KEY *key_info=table->key_info; + bool rc= TRUE; + + DBUG_ENTER("mysql_prepare_alter_table"); + + create_info->varchar= FALSE; /* Let new create options override the old ones */ if (!(used_fields & HA_CREATE_USED_MIN_ROWS)) create_info->min_rows= table->s->min_rows; @@ -3397,25 +5648,35 @@ view_err: { /* Table has an autoincrement, copy value to new table */ table->file->info(HA_STATUS_AUTO); - create_info->auto_increment_value= table->file->auto_increment_value; + create_info->auto_increment_value= table->file->stats.auto_increment_value; } + if (!(used_fields & HA_CREATE_USED_KEY_BLOCK_SIZE)) + create_info->key_block_size= table->s->key_block_size; + if (!(used_fields & HA_CREATE_USED_TRANSACTIONAL)) + create_info->transactional= table->s->transactional; + if (!create_info->tablespace && create_info->storage_media != HA_SM_MEMORY) + { + char *tablespace= static_cast<char *>(thd->alloc(FN_LEN)); + /* + Regular alter table of disk stored table (no tablespace/storage change) + Copy tablespace name + */ + if (tablespace && + (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(alter_info->create_list); - List_iterator<Alter_column> alter_it(alter_info->alter_list); - Alter_info new_info; // Add new columns and indexes here - create_field *def; + Create_field *def; /* First collect all fields from table which isn't in drop_list */ - Field **f_ptr,*field; for (f_ptr=table->field ; (field= *f_ptr) ; f_ptr++) { if (field->type() == MYSQL_TYPE_STRING) - varchar= TRUE; + create_info->varchar= TRUE; /* Check if field should be dropped */ Alter_drop *drop; drop_it.rewind(); @@ -3452,13 +5713,18 @@ view_err: def->field=field; if (!def->after) { - new_info.create_list.push_back(def); + new_create_list.push_back(def); def_it.remove(); } } else - { // Use old field value - new_info.create_list.push_back(def= new create_field(field, field)); + { + /* + This field was not dropped and not changed, add it to the list + for the new table. + */ + def= new Create_field(field, field); + new_create_list.push_back(def); alter_it.rewind(); // Change default if ALTER Alter_column *alter; while ((alter=alter_it++)) @@ -3468,10 +5734,10 @@ 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); + goto err; } if ((def->def=alter->def)) // Use new default def->flags&= ~NO_DEFAULT_VALUE_FLAG; @@ -3482,13 +5748,12 @@ view_err: } } def_it.rewind(); - List_iterator<create_field> find_it(new_info.create_list); while ((def=def_it++)) // Add new columns { if (def->change && ! def->field) { - my_error(ER_BAD_FIELD_ERROR, MYF(0), def->change, table_name); - DBUG_RETURN(TRUE); + my_error(ER_BAD_FIELD_ERROR, MYF(0), def->change, table->s->table_name.str); + goto err; } /* Check that the DATE/DATETIME not null field we are going to add is @@ -3499,20 +5764,21 @@ view_err: */ if ((def->sql_type == MYSQL_TYPE_DATE || def->sql_type == MYSQL_TYPE_NEWDATE || - def->sql_type == MYSQL_TYPE_DATETIME) && !new_datetime_field && + def->sql_type == MYSQL_TYPE_DATETIME) && + !alter_info->datetime_field && !(~def->flags & (NO_DEFAULT_VALUE_FLAG | NOT_NULL_FLAG)) && thd->variables.sql_mode & MODE_NO_ZERO_DATE) { - new_datetime_field= def; - error_if_not_empty= TRUE; + alter_info->datetime_field= def; + alter_info->error_if_not_empty= TRUE; } if (!def->after) - new_info.create_list.push_back(def); + new_create_list.push_back(def); else if (def->after == first_keyword) - new_info.create_list.push_front(def); + new_create_list.push_front(def); else { - create_field *find; + Create_field *find; find_it.rewind(); while ((find=find_it++)) // Add new columns { @@ -3521,23 +5787,24 @@ view_err: } if (!find) { - my_error(ER_BAD_FIELD_ERROR, MYF(0), def->after, table_name); - DBUG_RETURN(TRUE); + my_error(ER_BAD_FIELD_ERROR, MYF(0), def->after, table->s->table_name.str); + goto err; } find_it.after(def); // Put element after this + alter_info->change_level= ALTER_TABLE_DATA_CHANGED; } } if (alter_info->alter_list.elements) { my_error(ER_BAD_FIELD_ERROR, MYF(0), - alter_info->alter_list.head()->name, table_name); - DBUG_RETURN(TRUE); + alter_info->alter_list.head()->name, table->s->table_name.str); + goto err; } - if (!new_info.create_list.elements) + if (!new_create_list.elements) { my_message(ER_CANT_REMOVE_ALL_FIELDS, ER(ER_CANT_REMOVE_ALL_FIELDS), MYF(0)); - DBUG_RETURN(TRUE); + goto err; } /* @@ -3545,11 +5812,6 @@ view_err: for which some fields exists. */ - List_iterator<Key> key_it(alter_info->key_list); - List_iterator<create_field> field_it(new_info.create_list); - List<key_part_spec> key_parts; - - KEY *key_info=table->key_info; for (uint i=0 ; i < table->s->keys ; i++,key_info++) { char *key_name= key_info->name; @@ -3574,7 +5836,7 @@ view_err: if (!key_part->field) continue; // Wrong field (from UNIREG) const char *key_part_name=key_part->field->field_name; - create_field *cfield; + Create_field *cfield; field_it.rewind(); while ((cfield=field_it++)) { @@ -3607,6 +5869,8 @@ view_err: */ if (!Field::type_can_have_key_part(cfield->field->type()) || !Field::type_can_have_key_part(cfield->sql_type) || + /* spatial keys can't have sub-key length */ + (key_info->flags & HA_SPATIAL) || (cfield->field->field_length == key_part_length && !f_is_blob(key_part->key_type)) || (cfield->length && (cfield->length < key_part_length / @@ -3614,13 +5878,21 @@ view_err: key_part_length= 0; // Use whole field } key_part_length /= key_part->field->charset()->mbmaxlen; - key_parts.push_back(new key_part_spec(cfield->field_name, + key_parts.push_back(new Key_part_spec(cfield->field_name, key_part_length)); } if (key_parts.elements) { + KEY_CREATE_INFO key_create_info; Key *key; enum Key::Keytype key_type; + 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= *plugin_name(key_info->parser); if (key_info->flags & HA_SPATIAL) key_type= Key::SPATIAL; @@ -3637,10 +5909,10 @@ view_err: key_type= Key::MULTIPLE; key= new Key(key_type, key_name, - key_info->algorithm, + &key_create_info, test(key_info->flags & HA_GENERATED_KEY), key_parts); - new_info.key_list.push_back(key); + new_key_list.push_back(key); } } { @@ -3648,12 +5920,12 @@ view_err: while ((key=key_it++)) // Add new keys { if (key->type != Key::FOREIGN_KEY) - new_info.key_list.push_back(key); + new_key_list.push_back(key); if (key->name && !my_strcasecmp(system_charset_info,key->name,primary_key_name)) { my_error(ER_WRONG_NAME_FOR_INDEX, MYF(0), key->name); - DBUG_RETURN(TRUE); + goto err; } } } @@ -3671,17 +5943,6 @@ view_err: goto err; } - db_create_options= table->s->db_create_options & ~(HA_OPTION_PACK_RECORD); - my_snprintf(tmp_name, sizeof(tmp_name), "%s-%lx_%lx", tmp_file_prefix, - current_pid, thd->thread_id); - /* Safety fix for innodb */ - if (lower_case_table_names) - my_casedn_str(files_charset_info, tmp_name); - if (new_db_type != old_db_type && !table->file->can_switch_engines()) { - my_error(ER_ROW_IS_REFERENCED, MYF(0)); - goto err; - } - create_info->db_type=new_db_type; if (!create_info->comment.str) { create_info->comment.str= table->s->comment.str; @@ -3705,32 +5966,703 @@ view_err: if (table->s->tmp_table) create_info->options|=HA_LEX_CREATE_TMP_TABLE; + rc= FALSE; + alter_info->create_list.swap(new_create_list); + alter_info->key_list.swap(new_key_list); +err: + DBUG_RETURN(rc); +} + + +/* + 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 + create_info Information from the parsing phase about new + table properties. + table_list The table to change. + alter_info Lists of fields, keys to be changed, added + or dropped. + order_num How many ORDER BY fields has been specified. + order List of fields to ORDER BY. + ignore Whether we have ALTER IGNORE TABLE + + DESCRIPTION + This is a veery long function and is everything but the kitchen sink :) + It is used to alter a table and not only by ALTER TABLE but also + CREATE|DROP INDEX are mapped on this function. + + When the ALTER TABLE statement just does a RENAME or ENABLE|DISABLE KEYS, + or both, then this function short cuts its operation by renaming + the table and/or enabling/disabling the keys. In this case, the FRM is + not changed, directly by mysql_alter_table. However, if there is a + RENAME + change of a field, or an index, the short cut is not used. + See how `create_list` is used to generate the new FRM regarding the + structure of the fields. The same is done for the indices of the table. + + Important is the fact, that this function tries to do as little work as + possible, by finding out whether a intermediate table is needed to copy + data into and when finishing the altering to use it as the original table. + For this reason the function 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, + TABLE_LIST *table_list, + Alter_info *alter_info, + uint order_num, ORDER *order, bool ignore) +{ + TABLE *table, *new_table= 0, *name_lock= 0; + int error= 0; + 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 path[FN_REFLEN]; + char reg_path[FN_REFLEN+1]; + ha_rows copied,deleted; + handlerton *old_db_type, *new_db_type, *save_old_db_type; + legacy_db_type table_type; + frm_type_enum frm_type; + enum_alter_table_change_level need_copy_table= ALTER_TABLE_METADATA_ONLY; +#ifdef WITH_PARTITION_STORAGE_ENGINE + uint fast_alter_partition= 0; + bool partition_changed= FALSE; +#endif + bool need_lock_for_indexes= TRUE; + KEY *key_info_buffer; + uint index_drop_count; + uint *index_drop_buffer; + uint index_add_count; + uint *index_add_buffer; + uint candidate_key_count; + bool committed= 0; + bool no_pk; + 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); + LINT_INIT(candidate_key_count); + + /* + Check if we attempt to alter mysql.slow_log or + mysql.general_log table and return an error if + it is the case. + TODO: this design is obsolete and will be removed. + */ + if (table_list && table_list->db && table_list->table_name) + { + int table_kind= 0; + + table_kind= check_if_log_table(table_list->db_length, table_list->db, + table_list->table_name_length, + table_list->table_name, 0); + + if (table_kind) + { + /* Disable alter of enabled log tables */ + if (logger.is_log_table_enabled(table_kind)) + { + my_error(ER_BAD_LOG_STATEMENT, MYF(0), "ALTER"); + DBUG_RETURN(TRUE); + } + + /* Disable alter of log tables to unsupported engine */ + if ((create_info->used_fields & HA_CREATE_USED_ENGINE) && + (!create_info->db_type || /* unknown engine */ + !(create_info->db_type->flags & HTON_SUPPORT_LOG_TABLES))) + { + my_error(ER_UNSUPORTED_LOG_ENGINE, MYF(0)); + DBUG_RETURN(TRUE); + } + +#ifdef WITH_PARTITION_STORAGE_ENGINE + if (alter_info->flags & ALTER_PARTITION) + { + my_error(ER_WRONG_USAGE, MYF(0), "PARTITION", "log table"); + DBUG_RETURN(TRUE); + } +#endif + } + } + + /* + Assign variables table_name, new_name, db, new_db, path, reg_path + to simplify further comparisions: we want to see if it's a RENAME + later just by comparing the pointers, avoiding the need for strcmp. + */ + thd_proc_info(thd, "init"); + table_name=table_list->table_name; + alias= (lower_case_table_names == 2) ? table_list->alias : table_name; + db=table_list->db; + if (!new_db || !my_strcasecmp(table_alias_charset, new_db, db)) + new_db= db; + build_table_filename(reg_path, sizeof(reg_path), db, table_name, reg_ext, 0); + build_table_filename(path, sizeof(path), db, table_name, "", 0); + + mysql_ha_rm_tables(thd, table_list, FALSE); + + /* DISCARD/IMPORT TABLESPACE is always alone in an ALTER TABLE */ + if (alter_info->tablespace_op != NO_TABLESPACE_OP) + /* Conditionally writes to binlog. */ + DBUG_RETURN(mysql_discard_or_import_tablespace(thd,table_list, + alter_info->tablespace_op)); + 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 this is just a rename of a view, short cut to the + following scenario: 1) lock LOCK_open 2) do a RENAME + 2) unlock LOCK_open. + This is a copy-paste added to make sure + ALTER (sic:) TABLE .. RENAME works for views. ALTER VIEW is handled + as an independent branch in mysql_execute_command. The need + for a copy-paste arose because the main code flow of ALTER TABLE + ... RENAME tries to use open_ltable, which does not work for views + (open_ltable was never modified to merge table lists of child tables + into the main table list, like open_tables does). + This code is wrong and will be removed, please do not copy. + */ + frm_type= mysql_frm_type(thd, new_name_buff, &table_type); + /* Rename a view */ + /* Sic: there is a race here */ + if (frm_type == FRMTYPE_VIEW && !(alter_info->flags & ~ALTER_RENAME)) + { + /* + Avoid problems with a rename on a table that we have locked or + if the user is trying to to do this in a transcation context + */ + + if (thd->locked_tables || thd->active_transaction()) + { + my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, + ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); + DBUG_RETURN(TRUE); + } + + if (wait_if_global_read_lock(thd,0,1)) + DBUG_RETURN(TRUE); + VOID(pthread_mutex_lock(&LOCK_open)); + if (lock_table_names(thd, table_list)) + { + error= 1; + goto view_err; + } + + if (!do_rename(thd, table_list, new_db, new_name, new_name, 1)) + { + if (mysql_bin_log.is_open()) + { + thd->clear_error(); + Query_log_event qinfo(thd, thd->query, thd->query_length, + 0, FALSE, THD::NOT_KILLED); + mysql_bin_log.write(&qinfo); + } + my_ok(thd); + } + + unlock_table_names(thd, table_list, (TABLE_LIST*) 0); + +view_err: + pthread_mutex_unlock(&LOCK_open); + start_waiting_global_read_lock(thd); + DBUG_RETURN(error); + } + + if (!(table= open_n_lock_single_table(thd, table_list, TL_WRITE_ALLOW_READ))) + DBUG_RETURN(TRUE); + table->use_all_columns(); + + /* + Prohibit changing of the UNION list of a non-temporary MERGE table + under LOCK tables. It would be quite difficult to reuse a shrinked + set of tables from the old table or to open a new TABLE object for + an extended list and verify that they belong to locked tables. + */ + if (thd->locked_tables && + (create_info->used_fields & HA_CREATE_USED_UNION) && + (table->s->tmp_table == NO_TMP_TABLE)) + { + my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0)); + DBUG_RETURN(TRUE); + } + + /* 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) + { + if (lower_case_table_names != 2) + { + my_casedn_str(files_charset_info, new_name_buff); + new_alias= new_name; // Create lower case table name + } + my_casedn_str(files_charset_info, new_name); + } + if (new_db == db && + !my_strcasecmp(table_alias_charset, new_name_buff, table_name)) + { + /* + Source and destination table names are equal: make later check + easier. + */ + new_alias= new_name= table_name; + } + else + { + if (table->s->tmp_table != NO_TMP_TABLE) + { + if (find_temporary_table(thd,new_db,new_name_buff)) + { + my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_name_buff); + DBUG_RETURN(TRUE); + } + } + else + { + if (lock_table_name_if_not_cached(thd, new_db, new_name, &name_lock)) + DBUG_RETURN(TRUE); + if (!name_lock) + { + my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_alias); + DBUG_RETURN(TRUE); + } + + 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); + goto err; + } + } + } + } + else + { + new_alias= (lower_case_table_names == 2) ? alias : table_name; + new_name= table_name; + } + + old_db_type= table->s->db_type(); + if (!create_info->db_type) + { +#ifdef WITH_PARTITION_STORAGE_ENGINE + 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; + } + + if (check_engine(thd, new_name, create_info)) + goto err; + new_db_type= create_info->db_type; + + if ((new_db_type != old_db_type || + alter_info->flags & ALTER_PARTITION) && + !table->file->can_switch_engines()) + { + my_error(ER_ROW_IS_REFERENCED, MYF(0)); + goto err; + } + + if (create_info->row_type == ROW_TYPE_NOT_USED) + create_info->row_type= table->s->row_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)) + { + DBUG_PRINT("info", ("doesn't support alter")); + my_error(ER_ILLEGAL_HA, MYF(0), table_name); + goto err; + } + + thd_proc_info(thd, "setup"); + if (!(alter_info->flags & ~(ALTER_RENAME | ALTER_KEYS_ONOFF)) && + !table->s->tmp_table) // no need to touch frm + { + switch (alter_info->keys_onoff) { + case LEAVE_AS_IS: + break; + case ENABLE: + /* + wait_while_table_is_used() ensures that table being altered is + opened only by this thread and that TABLE::TABLE_SHARE::version + of TABLE object corresponding to this table is 0. + The latter guarantees that no DML statement will open this table + until ALTER TABLE finishes (i.e. until close_thread_tables()) + while the fact that the table is still open gives us protection + from concurrent DDL statements. + */ + VOID(pthread_mutex_lock(&LOCK_open)); + wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN); + VOID(pthread_mutex_unlock(&LOCK_open)); + DBUG_EXECUTE_IF("sleep_alter_enable_indexes", my_sleep(6000000);); + error= table->file->ha_enable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE); + /* COND_refresh will be signaled in close_thread_tables() */ + break; + case DISABLE: + VOID(pthread_mutex_lock(&LOCK_open)); + wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN); + VOID(pthread_mutex_unlock(&LOCK_open)); + error=table->file->ha_disable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE); + /* COND_refresh will be signaled in close_thread_tables() */ + break; + default: + DBUG_ASSERT(FALSE); + error= 0; + break; + } + if (error == HA_ERR_WRONG_COMMAND) + { + error= 0; + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA), + table->alias); + } + + VOID(pthread_mutex_lock(&LOCK_open)); + /* + Unlike to the above case close_cached_table() below will remove ALL + instances of TABLE from table cache (it will also remove table lock + held by this thread). So to make actual table renaming and writing + to binlog atomic we have to put them into the same critical section + protected by LOCK_open mutex. This also removes gap for races between + access() and mysql_rename_table() calls. + */ + + if (!error && (new_name != table_name || new_db != db)) + { + thd_proc_info(thd, "rename"); + /* + Then do a 'simple' rename of the table. First we need to close all + instances of 'source' table. + */ + close_cached_table(thd, table); + /* + Then, we want check once again that target table does not exist. + Actually the order of these two steps does not matter since + earlier we took name-lock on the target table, so we do them + in this particular order only to be consistent with 5.0, in which + we don't take this name-lock and where this order really matters. + TODO: Investigate if we need this access() check at all. + */ + if (!access(new_name_buff,F_OK)) + { + my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_name); + error= -1; + } + else + { + *fn_ext(new_name)=0; + 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, 0)); + error= -1; + } + } + } + + if (error == HA_ERR_WRONG_COMMAND) + { + error= 0; + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA), + table->alias); + } + + if (!error) + { + write_bin_log(thd, TRUE, thd->query, thd->query_length); + my_ok(thd); + } + else if (error > 0) + { + table->file->print_error(error, MYF(0)); + error= -1; + } + if (name_lock) + unlink_open_table(thd, name_lock, FALSE); + VOID(pthread_mutex_unlock(&LOCK_open)); + table_list->table= NULL; // For query cache + query_cache_invalidate3(thd, table_list, 0); + DBUG_RETURN(error); + } + + /* We have to do full alter table. */ + +#ifdef WITH_PARTITION_STORAGE_ENGINE + if (prep_alter_part_table(thd, table, alter_info, create_info, old_db_type, + &partition_changed, &fast_alter_partition)) + goto err; +#endif + /* + If the old table had partitions and we are doing ALTER TABLE ... + engine= <new_engine>, the new table must preserve the original + partitioning. That means that the new engine is still the + partitioning engine, not the engine specified in the parser. + This is discovered in prep_alter_part_table, which in such case + updates create_info->db_type. + Now we need to update the stack copy of create_info->db_type, + as otherwise we won't be able to correctly move the files of the + temporary table to the result table files. + */ + new_db_type= create_info->db_type; + + if (mysql_prepare_alter_table(thd, table, create_info, alter_info)) + goto err; + + need_copy_table= alter_info->change_level; + + set_table_default_charset(thd, create_info, db); + + if (thd->variables.old_alter_table + || (table->s->db_type() != create_info->db_type) +#ifdef WITH_PARTITION_STORAGE_ENGINE + || partition_changed +#endif + ) + need_copy_table= ALTER_TABLE_DATA_CHANGED; + else + { + enum_alter_table_change_level need_copy_table_res; + /* Check how much the tables differ. */ + if (compare_tables(table, alter_info, + create_info, order_num, + &need_copy_table_res, + &key_info_buffer, + &index_drop_buffer, &index_drop_count, + &index_add_buffer, &index_add_count, + &candidate_key_count)) + goto err; + + if (need_copy_table == ALTER_TABLE_METADATA_ONLY) + need_copy_table= need_copy_table_res; + } + + /* + If there are index changes only, try to do them 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; + + alter_flags= table->file->alter_table_flags(alter_info->flags); + DBUG_PRINT("info", ("alter_flags: %lu", alter_flags)); + /* Check dropped indexes. */ + for (idx_p= index_drop_buffer, idx_end_p= idx_p + index_drop_count; + idx_p < idx_end_p; + idx_p++) + { + key= table->key_info + *idx_p; + DBUG_PRINT("info", ("index dropped: '%s'", key->name)); + if (key->flags & HA_NOSAME) + { + /* + Unique key. Check for "PRIMARY". + or if dropping last unique key + */ + if ((uint) (key - table->key_info) == table->s->primary_key) + { + DBUG_PRINT("info", ("Dropping primary key")); + /* Primary key. */ + needed_online_flags|= HA_ONLINE_DROP_PK_INDEX; + needed_fast_flags|= HA_ONLINE_DROP_PK_INDEX_NO_WRITES; + pk_changed++; + candidate_key_count--; + } + else + { + KEY_PART_INFO *part_end= key->key_part + key->key_parts; + bool is_candidate_key= true; + + /* Non-primary unique key. */ + needed_online_flags|= HA_ONLINE_DROP_UNIQUE_INDEX; + needed_fast_flags|= HA_ONLINE_DROP_UNIQUE_INDEX_NO_WRITES; + + /* + Check if all fields in key are declared + NOT NULL and adjust candidate_key_count + */ + for (KEY_PART_INFO *key_part= key->key_part; + key_part < part_end; + key_part++) + is_candidate_key= + (is_candidate_key && + (! table->field[key_part->fieldnr-1]->maybe_null())); + if (is_candidate_key) + candidate_key_count--; + } + } + else + { + /* Non-unique key. */ + needed_online_flags|= HA_ONLINE_DROP_INDEX; + needed_fast_flags|= HA_ONLINE_DROP_INDEX_NO_WRITES; + } + } + no_pk= ((table->s->primary_key == MAX_KEY) || + (needed_online_flags & HA_ONLINE_DROP_PK_INDEX)); + /* Check added indexes. */ + for (idx_p= index_add_buffer, idx_end_p= idx_p + index_add_count; + idx_p < idx_end_p; + idx_p++) + { + key= key_info_buffer + *idx_p; + DBUG_PRINT("info", ("index added: '%s'", key->name)); + if (key->flags & HA_NOSAME) + { + /* Unique key */ + + KEY_PART_INFO *part_end= key->key_part + key->key_parts; + bool is_candidate_key= true; + + /* + Check if all fields in key are declared + NOT NULL + */ + for (KEY_PART_INFO *key_part= key->key_part; + key_part < part_end; + key_part++) + is_candidate_key= + (is_candidate_key && + (! table->field[key_part->fieldnr]->maybe_null())); + + /* + Check for "PRIMARY" + or if adding first unique key + defined on non-nullable fields + */ + + if ((!my_strcasecmp(system_charset_info, + key->name, primary_key_name)) || + (no_pk && candidate_key_count == 0 && is_candidate_key)) + { + DBUG_PRINT("info", ("Adding primary key")); + /* Primary key. */ + needed_online_flags|= HA_ONLINE_ADD_PK_INDEX; + needed_fast_flags|= HA_ONLINE_ADD_PK_INDEX_NO_WRITES; + pk_changed++; + no_pk= false; + } + 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; + } + } + + if ((candidate_key_count > 0) && + (needed_online_flags & HA_ONLINE_DROP_PK_INDEX)) + { + /* + Dropped primary key when there is some other unique + not null key that should be converted to primary key + */ + needed_online_flags|= HA_ONLINE_ADD_PK_INDEX; + needed_fast_flags|= HA_ONLINE_ADD_PK_INDEX_NO_WRITES; + pk_changed= 2; + } + + DBUG_PRINT("info", ("needed_online_flags: 0x%lx, needed_fast_flags: 0x%lx", + needed_online_flags, needed_fast_flags)); + /* + 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= ALTER_TABLE_METADATA_ONLY; + need_lock_for_indexes= FALSE; + } + else if ((alter_flags & needed_fast_flags) == needed_fast_flags) + { + /* All required fast flags are present. */ + need_copy_table= ALTER_TABLE_METADATA_ONLY; + } + } + 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 + */ + if (need_copy_table == ALTER_TABLE_METADATA_ONLY) + create_info->frm_only= 1; - 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. +#ifdef WITH_PARTITION_STORAGE_ENGINE + if (fast_alter_partition) + { + DBUG_ASSERT(!name_lock); + DBUG_RETURN(fast_alter_partition_table(thd, table, alter_info, + create_info, table_list, + db, table_name, + fast_alter_partition)); + } +#endif - 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; + my_snprintf(tmp_name, sizeof(tmp_name), "%s-%lx_%lx", tmp_file_prefix, + current_pid, thd->thread_id); + /* Safety fix for innodb */ + if (lower_case_table_names) + my_casedn_str(files_charset_info, tmp_name); /* Handling of symlinked tables: @@ -3747,8 +6679,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: @@ -3756,7 +6688,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) @@ -3779,16 +6710,23 @@ 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, &new_info, 1, 0); - reenable_binlog(thd); - if (error) - DBUG_RETURN(error); - } - if (need_copy_table) + /* + 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_no_lock(thd, new_db, tmp_name, + create_info, + alter_info, + 1, 0); + reenable_binlog(thd); + if (error) + goto err; + + /* Open the table if we need to copy the data. */ + DBUG_PRINT("info", ("need_copy_table: %u", need_copy_table)); + if (need_copy_table != ALTER_TABLE_METADATA_ONLY) { if (table->s->tmp_table) { @@ -3796,66 +6734,155 @@ 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; + /* + Note: In case of MERGE table, we do not attach children. We do not + copy data for MERGE tables. Only the children have data. + */ } - /* 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) + /* + We do not copy data for MERGE tables. Only the children have data. + MERGE tables have HA_NO_COPY_ON_ALTER set. + */ + if (new_table && !(new_table->file->ha_table_flags() & HA_NO_COPY_ON_ALTER)) { + /* 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, new_info.create_list, - ignore, order_num, order, - &copied, &deleted, alter_info->keys_onoff, - error_if_not_empty); + thd_proc_info(thd, "copy to tmp table"); + error= copy_data_between_tables(table, new_table, + alter_info->create_list, ignore, + order_num, order, &copied, &deleted, + alter_info->keys_onoff, + alter_info->error_if_not_empty); } - else if (!new_table) + else { VOID(pthread_mutex_lock(&LOCK_open)); wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN); VOID(pthread_mutex_unlock(&LOCK_open)); + thd_proc_info(thd, "manage keys"); alter_table_manage_keys(table, table->file->indexes_are_disabled(), alter_info->keys_onoff); - error= ha_commit_stmt(thd); - if (ha_commit(thd)) + error= ha_autocommit_or_rollback(thd, 0); + if (end_active_trans(thd)) error= 1; } - - 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->ha_prepare_for_alter(); + if (index_add_count) + { + /* The add_index() method takes an array of KEY structs. */ + key_info= (KEY*) thd->alloc(sizeof(KEY) * index_add_count); + key= key_info; + for (idx_p= index_add_buffer, idx_end_p= idx_p + index_add_count; + idx_p < idx_end_p; + idx_p++, key++) + { + /* Copy the KEY struct. */ + *key= key_info_buffer[*idx_p]; + /* Fix the key parts. */ + part_end= key->key_part + key->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) { + /* 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; + } + + /* 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_autocommit_or_rollback(thd, 0) || end_active_trans(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) { @@ -3863,196 +6890,235 @@ 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, THD::NOT_KILLED); - 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 */ - my_free((gptr) new_table,MYF(0)); + /* + Close the intermediate table that will be the new table. + Note that MERGE tables do not have their children attached here. + */ + intern_close_table(new_table); + my_free(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 - from the cache, free all locks, close the old table and remove it. + Data is copied. Now we: + 1) Wait until all other threads close old version of table. + 2) Close instances of table open by this thread and replace them + with exclusive name-locks. + 3) Rename the old table to a temp name, rename the new one to the + old name. + 4) If we are under LOCK TABLES and don't do ALTER TABLE ... RENAME + we reopen new version of table. + 5) Write statement to the binary log. + 6) If we are under LOCK TABLES and do ALTER TABLE ... RENAME we + remove name-locks from list of open tables and table cache. + 7) If we are not not under LOCK TABLES we rely on close_thread_tables() + call to remove name-locks from table cache and list of open table. */ - thd->proc_info="rename result table"; + thd_proc_info(thd, "rename result table"); my_snprintf(old_name, sizeof(old_name), "%s2-%lx-%lx", tmp_file_prefix, current_pid, thd->thread_id); if (lower_case_table_names) my_casedn_str(files_charset_info, old_name); -#if (!defined( __WIN__) && !defined( __EMX__) && !defined( OS2)) - 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_cached_table(thd, table); - table=0; // Marker that table is closed - no_table_reopen= TRUE; - } -#if (!defined( __WIN__) && !defined( __EMX__) && !defined( OS2)) - else - table->file->extra(HA_EXTRA_FORCE_REOPEN); // Don't use this file anymore -#endif + wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_RENAME); + close_data_files_and_morph_locks(thd, db, table_name); - if (new_name != table_name || new_db != db) + 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 == ALTER_TABLE_METADATA_ONLY) { - /* - Check that there is no table with target name. See the - comment describing code for 'simple' ALTER TABLE ... RENAME. - */ - if (table_cache_has_open_placeholder(thd, new_db, new_name) || - !access(new_name_buff,F_OK)) - { - 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(pthread_mutex_unlock(&LOCK_open)); - goto err; - } + DBUG_ASSERT(new_db_type == old_db_type); + /* This type cannot happen in regular ALTER. */ + new_db_type= old_db_type= NULL; } - - error=0; - 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)) + 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) || + else if (mysql_rename_table(new_db_type, new_db, tmp_name, new_db, + new_alias, FN_FROM_IS_TMP) || (new_name != table_name || new_db != db) && // we also do rename + (need_copy_table != ALTER_TABLE_METADATA_ONLY || + 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) { - /* - This shouldn't happen. We solve this the safe way by - closing the locked table. - */ - if (table) - close_cached_table(thd,table); - 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 - */ - if (table) - close_cached_table(thd,table); - VOID(quick_rm_table(old_db_type,db,old_name)); + /* This shouldn't happen. But let us play it safe. */ + goto err_with_placeholders; } - else + + if (need_copy_table == ALTER_TABLE_METADATA_ONLY) { /* - Using LOCK TABLES without rename. - This code is never executed on WIN32! - Remove old renamed table, reopen table and get new locks + Now we have to inform handler that new .FRM file is in place. + To do this we need to obtain a handler object for it. + NO need to tamper with MERGE tables. The real open is done later. */ - if (table) - { - VOID(table->file->extra(HA_EXTRA_FORCE_REOPEN)); // Use new file - /* 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); - } - VOID(quick_rm_table(old_db_type,db,old_name)); - if (close_data_tables(thd,db,table_name) || - reopen_tables(thd,1,0)) - { // This shouldn't happen - if (table) - close_cached_table(thd,table); // Remove lock for table - VOID(pthread_mutex_unlock(&LOCK_open)); - goto err; + TABLE *t_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); + table_list->table= name_lock; + if (reopen_name_locked_table(thd, table_list, FALSE)) + goto err_with_placeholders; + t_table= table_list->table; + } + else + { + if (reopen_table(table)) + goto err_with_placeholders; + t_table= table; + } + /* Tell the handler that a new frm file is in place. */ + if (t_table->file->ha_create_handler_files(path, NULL, CHF_INDEX_FLAG, + create_info)) + goto err_with_placeholders; + if (thd->locked_tables && new_name == table_name && new_db == db) + { + /* + We are going to reopen table down on the road, so we have to restore + state of the TABLE object which we used for obtaining of handler + object to make it suitable for reopening. + */ + DBUG_ASSERT(t_table == table); + table->open_placeholder= 1; + close_handle_and_leave_table_as_lock(table); } } - thd->proc_info="end"; - if (mysql_bin_log.is_open()) + + VOID(quick_rm_table(old_db_type, db, old_name, FN_IS_TMP)); + + if (thd->locked_tables && new_name == table_name && new_db == db) { - thd->clear_error(); - Query_log_event qinfo(thd, thd->query, thd->query_length, - FALSE, FALSE, THD::NOT_KILLED); - mysql_bin_log.write(&qinfo); + thd->in_lock_tables= 1; + error= reopen_tables(thd, 1, 1); + thd->in_lock_tables= 0; + if (error) + goto err_with_placeholders; } - broadcast_refresh(); VOID(pthread_mutex_unlock(&LOCK_open)); -#ifdef HAVE_BERKELEY_DB - if (old_db_type == DB_TYPE_BERKELEY_DB) + + thd_proc_info(thd, "end"); + + DBUG_EXECUTE_IF("sleep_alter_before_main_binlog", my_sleep(6000000);); + + 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 have to open the new table. If not, we get a problem on server - shutdown. + shutdown. But we do not need to attach MERGE children. */ char path[FN_REFLEN]; - build_table_path(path, sizeof(path), new_db, table_name, ""); - table=open_temporary_table(thd, path, new_db, tmp_name,0); - if (table) + TABLE *t_table; + build_table_filename(path, sizeof(path), new_db, table_name, "", 0); + t_table= open_temporary_table(thd, path, new_db, tmp_name, 0); + if (t_table) { - intern_close_table(table); - my_free((char*) table, MYF(0)); + intern_close_table(t_table); + my_free(t_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); + if (thd->locked_tables && (new_name != table_name || new_db != db)) + { + /* + If are we under LOCK TABLES and did ALTER TABLE with RENAME we need + to remove placeholders for the old table and for the target table + from the list of open tables and table cache. If we are not under + LOCK TABLES we can rely on close_thread_tables() doing this job. + */ + pthread_mutex_lock(&LOCK_open); + unlink_open_table(thd, table, FALSE); + unlink_open_table(thd, name_lock, FALSE); + pthread_mutex_unlock(&LOCK_open); + } + end_temporary: my_snprintf(tmp_name, sizeof(tmp_name), ER(ER_INSERT_INFO), (ulong) (copied + deleted), (ulong) deleted, (ulong) thd->cuted_fields); - send_ok(thd, copied + deleted, 0L, tmp_name); + my_ok(thd, copied + deleted, 0L, tmp_name); 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, + create_info->frm_only + ? FN_IS_TMP | FRM_ONLY + : FN_IS_TMP)); + err: /* No default value was provided for a DATE/DATETIME field, the @@ -4060,11 +7126,11 @@ err: the table to be altered isn't empty. Report error here. */ - if (error_if_not_empty && thd->row_count) + if (alter_info->error_if_not_empty && thd->row_count) { const char *f_val= 0; enum enum_mysql_timestamp_type t_type= MYSQL_TIMESTAMP_DATE; - switch (new_datetime_field->sql_type) + switch (alter_info->datetime_field->sql_type) { case MYSQL_TYPE_DATE: case MYSQL_TYPE_NEWDATE: @@ -4083,16 +7149,34 @@ err: thd->abort_on_warning= TRUE; make_truncated_value_warning(thd, MYSQL_ERROR::WARN_LEVEL_ERROR, f_val, strlength(f_val), t_type, - new_datetime_field->field_name); + alter_info->datetime_field->field_name); thd->abort_on_warning= save_abort_on_warning; } + if (name_lock) + { + pthread_mutex_lock(&LOCK_open); + unlink_open_table(thd, name_lock, FALSE); + pthread_mutex_unlock(&LOCK_open); + } DBUG_RETURN(TRUE); -} +err_with_placeholders: + /* + An error happened while we were holding exclusive name-lock on table + being altered. To be safe under LOCK TABLES we should remove placeholders + from list of open tables list and table cache. + */ + unlink_open_table(thd, table, FALSE); + if (name_lock) + unlink_open_table(thd, name_lock, FALSE); + VOID(pthread_mutex_unlock(&LOCK_open)); + DBUG_RETURN(TRUE); +} +/* mysql_alter_table */ static int copy_data_between_tables(TABLE *from,TABLE *to, - List<create_field> &create, + List<Create_field> &create, bool ignore, uint order_num, ORDER *order, ha_rows *copied, @@ -4113,6 +7197,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"); /* @@ -4140,12 +7225,12 @@ 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; - List_iterator<create_field> it(create); - create_field *def; + List_iterator<Create_field> it(create); + Create_field *def; copy_end=copy; for (Field **ptr=to->field ; *ptr ; ptr++) { @@ -4173,31 +7258,39 @@ copy_data_between_tables(TABLE *from,TABLE *to, if (order) { - from->sort.io_cache=(IO_CACHE*) my_malloc(sizeof(IO_CACHE), - 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; - error=1; + if (to->s->primary_key != MAX_KEY && to->file->primary_key_is_clustered()) + { + char warn_buff[MYSQL_ERRMSG_SIZE]; + my_snprintf(warn_buff, sizeof(warn_buff), + "ORDER BY ignored as there is a user-defined clustered index" + " in the table '%-.192s'", from->s->table_name.str); + push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR, + warn_buff); + } + else + { + from->sort.io_cache=(IO_CACHE*) my_malloc(sizeof(IO_CACHE), + MYF(MY_FAE | MY_ZEROFILL)); + bzero((char *) &tables, sizeof(tables)); + tables.table= from; + 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) || - setup_order(thd, thd->lex->select_lex.ref_pointer_array, - &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, - &examined_rows)) == - HA_POS_ERROR) - goto err; + if (thd->lex->select_lex.setup_ref_array(thd, order_num) || + setup_order(thd, thd->lex->select_lex.ref_pointer_array, + &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, + 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, FALSE); if (ignore) to->file->extra(HA_EXTRA_IGNORE_DUP_KEY); @@ -4230,18 +7323,33 @@ copy_data_between_tables(TABLE *from,TABLE *to, { copy_ptr->do_copy(copy_ptr); } - error=to->file->write_row((byte*) to->record[0]); + prev_insert_id= to->file->next_insert_id; + error=to->file->ha_write_row(to->record[0]); to->auto_increment_field_not_null= FALSE; if (error) { 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_WITH_KEY_NAME); + if (key_nr == 0 && + (to->key_info[0].key_part[0].field->flags & + AUTO_INCREMENT_FLAG)) + err_msg= ER(ER_DUP_ENTRY_AUTOINCREMENT_CASE); + to->file->print_keydup_error(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 @@ -4251,7 +7359,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; @@ -4268,9 +7376,9 @@ copy_data_between_tables(TABLE *from,TABLE *to, Ensure that the new table is saved properly to disk so that we can do a rename */ - if (ha_commit_stmt(thd)) + if (ha_autocommit_or_rollback(thd, 0)) error=1; - if (ha_commit(thd)) + if (end_active_trans(thd)) error=1; err: @@ -4279,6 +7387,7 @@ copy_data_between_tables(TABLE *from,TABLE *to, free_io_cache(from); *copied= found_count; *deleted=delete_count; + to->file->ha_release_auto_increment(); if (to->file->ha_external_lock(thd,F_UNLCK)) error=1; DBUG_RETURN(error > 0 ? -1 : 0); @@ -4302,20 +7411,26 @@ bool mysql_recreate_table(THD *thd, TABLE_LIST *table_list) Alter_info alter_info; DBUG_ENTER("mysql_recreate_table"); + DBUG_ASSERT(!table_list->next_global); + /* + table_list->table has been closed and freed. Do not reference + uninitialized data. open_tables() could fail. + */ + table_list->table= NULL; bzero((char*) &create_info, sizeof(create_info)); - create_info.db_type=DB_TYPE_DEFAULT; create_info.row_type=ROW_TYPE_NOT_USED; create_info.default_table_charset=default_charset_info; /* Force alter table to recreate table */ - alter_info.flags= ALTER_CHANGE_COLUMN; + alter_info.flags= (ALTER_CHANGE_COLUMN | ALTER_RECREATE); DBUG_RETURN(mysql_alter_table(thd, NullS, NullS, &create_info, - table_list, &alter_info, - 0, (ORDER *) 0, 0)); + table_list, &alter_info, 0, + (ORDER *) 0, 0)); } -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; @@ -4332,6 +7447,7 @@ bool mysql_checksum_table(THD *thd, TABLE_LIST *tables, HA_CHECK_OPT *check_opt) Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) DBUG_RETURN(TRUE); + /* Open one table after the other to keep lock time as short as possible. */ for (table= tables; table; table= table->next_local) { char table_name[NAME_LEN*2+2]; @@ -4339,7 +7455,7 @@ bool mysql_checksum_table(THD *thd, TABLE_LIST *tables, HA_CHECK_OPT *check_opt) strxmov(table_name, table->db ,".", table->table_name, NullS); - t= table->table= open_ltable(thd, table, TL_READ); + t= table->table= open_n_lock_single_table(thd, table, TL_READ); thd->clear_error(); // these errors shouldn't get client protocol->prepare_for_resend(); @@ -4353,10 +7469,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 @@ -4365,10 +7481,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(); @@ -4407,15 +7520,15 @@ 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; f->val_str(&tmp); - row_crc= my_checksum(row_crc, (byte*) tmp.ptr(), tmp.length()); + row_crc= my_checksum(row_crc, (uchar*) tmp.ptr(), tmp.length()); } else - row_crc= my_checksum(row_crc, (byte*) f->ptr, + row_crc= my_checksum(row_crc, f->ptr, f->pack_length()); } @@ -4433,7 +7546,7 @@ bool mysql_checksum_table(THD *thd, TABLE_LIST *tables, HA_CHECK_OPT *check_opt) goto err; } - send_eof(thd); + my_eof(thd); DBUG_RETURN(FALSE); err: @@ -4444,35 +7557,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, + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, 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); } - return FALSE; -} - -static void set_tmp_file_path(char *buf, size_t bufsize, THD *thd) -{ - char *p= strnmov(buf, mysql_tmpdir, (uint) bufsize); - my_snprintf(p, bufsize - (p - buf), "%s%lx_%lx_%x%s", - tmp_file_prefix, current_pid, - thd->thread_id, thd->tmp_table++, reg_ext); - if (lower_case_table_names) + if (create_info->options & HA_LEX_CREATE_TMP_TABLE && + ha_check_storage_engine_flag(*new_engine, HTON_TEMPORARY_NOT_SUPPORTED)) { - /* Convert all except tmpdir to lower case */ - my_casedn_str(files_charset_info, p); + if (create_info->used_fields & HA_CREATE_USED_ENGINE) + { + my_error(ER_ILLEGAL_HA_CREATE_OPTION, MYF(0), + ha_resolve_storage_engine_name(*new_engine), "TEMPORARY"); + *new_engine= 0; + return TRUE; + } + *new_engine= myisam_hton; } + return FALSE; } |