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