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.cc4835
1 files changed, 3758 insertions, 1077 deletions
diff --git a/sql/sql_table.cc b/sql/sql_table.cc
index 6cbe98fe862..cd39623f2b4 100644
--- a/sql/sql_table.cc
+++ b/sql/sql_table.cc
@@ -16,62 +16,1396 @@
/* drop and alter of tables */
#include "mysql_priv.h"
-#ifdef HAVE_BERKELEY_DB
-#include "ha_berkeley.h"
-#endif
#include <hash.h>
#include <myisam.h>
#include <my_dir.h>
#include "sp_head.h"
#include "sql_trigger.h"
+#include "sql_show.h"
#ifdef __WIN__
#include <io.h>
#endif
+int creating_table= 0; // How many mysql_create_table are running
+
const char *primary_key_name="PRIMARY";
static bool check_if_keyname_exists(const char *name,KEY *start, KEY *end);
static char *make_unique_key_name(const char *field_name,KEY *start,KEY *end);
static int copy_data_between_tables(TABLE *from,TABLE *to,
- List<create_field> &create, bool ignore,
+ List<Create_field> &create, bool ignore,
uint order_num, ORDER *order,
ha_rows *copied,ha_rows *deleted,
enum enum_enable_or_disable keys_onoff,
bool error_if_not_empty);
-static bool prepare_blob_field(THD *thd, create_field *sql_field);
-static bool check_engine(THD *thd, const char *table_name,
- enum db_type *new_engine);
-static void set_tmp_file_path(char *buf, size_t bufsize, THD *thd);
+static bool prepare_blob_field(THD *thd, Create_field *sql_field);
+static bool check_engine(THD *, const char *, HA_CREATE_INFO *);
+static bool
+mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info,
+ Alter_info *alter_info,
+ bool tmp_table,
+ uint *db_options,
+ handler *file, KEY **key_info_buffer,
+ uint *key_count, int select_field_count);
+static bool
+mysql_prepare_alter_table(THD *thd, TABLE *table,
+ HA_CREATE_INFO *create_info,
+ Alter_info *alter_info);
+
+#ifndef DBUG_OFF
+
+/* Wait until we get a 'mysql_kill' signal */
+
+static void wait_for_kill_signal(THD *thd)
+{
+ while (thd->killed == 0)
+ sleep(1);
+ // Reset signal and continue as if nothing happend
+ thd->killed= THD::NOT_KILLED;
+}
+#endif
/*
- Build the path to a file for a table (or the base path that can
- then have various extensions stuck on to it).
+ Translate a file name to a table name (WL #1324).
SYNOPSIS
- build_table_path()
- buff Buffer to build the path into
- bufflen sizeof(buff)
- db Name of database
- table Name of table
- ext Filename extension
+ filename_to_tablename()
+ from The file name in my_charset_filename.
+ to OUT The table name in system_charset_info.
+ to_length The size of the table name buffer.
RETURN
- 0 Error
- # Size of path
- */
+ Table name length.
+*/
+
+uint filename_to_tablename(const char *from, char *to, uint to_length)
+{
+ uint errors;
+ 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.
+ This may be the same as table_name.
+ bufflen buff size
+ db Database name in system_charset_info.
+ table_name Table name in system_charset_info.
+ ext File extension.
+ flags FN_FROM_IS_TMP or FN_TO_IS_TMP or FN_IS_TMP
+ table_name is temporary, do not change.
+
+ NOTES
+
+ Uses database and table name, and extension to create
+ a file name in mysql_data_dir. Database and table
+ names are converted from system_charset_info into "fscs".
+ Unless flags indicate a temporary table name.
+ 'db' is always converted.
+ 'ext' is not converted.
+
+ The conversion suppression is required for ALTER TABLE. This
+ statement creates intermediate tables. These are regular
+ (non-temporary) tables with a temporary name. Their path names must
+ be derivable from the table name. So we cannot use
+ build_tmptable_filename() for them.
+
+ RETURN
+ path length
+*/
+
+uint build_table_filename(char *buff, size_t bufflen, const char *db,
+ const char *table_name, const char *ext, uint flags)
+{
+ char dbbuff[FN_REFLEN];
+ char tbbuff[FN_REFLEN];
+ DBUG_ENTER("build_table_filename");
+ DBUG_PRINT("enter", ("db: '%s' table_name: '%s' ext: '%s' flags: %x",
+ db, table_name, ext, flags));
+
+ if (flags & FN_IS_TMP) // FN_FROM_IS_TMP | FN_TO_IS_TMP
+ strnmov(tbbuff, table_name, sizeof(tbbuff));
+ else
+ VOID(tablename_to_filename(table_name, tbbuff, sizeof(tbbuff)));
+
+ VOID(tablename_to_filename(db, dbbuff, sizeof(dbbuff)));
+
+ char *end = buff + bufflen;
+ /* Don't add FN_ROOTDIR if mysql_data_home already includes it */
+ char *pos = strnmov(buff, mysql_data_home, bufflen);
+ int rootdir_len= strlen(FN_ROOTDIR);
+ if (pos - rootdir_len >= buff &&
+ memcmp(pos - rootdir_len, FN_ROOTDIR, rootdir_len) != 0)
+ pos= strnmov(pos, FN_ROOTDIR, end - pos);
+ pos= strxnmov(pos, end - pos, dbbuff, FN_ROOTDIR, tbbuff, ext, NullS);
+
+ DBUG_PRINT("exit", ("buff: '%s'", buff));
+ DBUG_RETURN(pos - buff);
+}
+
+
+/*
+ Creates path to a file: mysql_tmpdir/#sql1234_12_1.ext
+
+ SYNOPSIS
+ build_tmptable_filename()
+ thd The thread handle.
+ buff Where to write result in my_charset_filename.
+ bufflen buff size
+
+ NOTES
+
+ Uses current_pid, thread_id, and tmp_table counter to create
+ a file name in mysql_tmpdir.
+
+ RETURN
+ path length
+*/
+
+uint build_tmptable_filename(THD* thd, char *buff, size_t bufflen)
+{
+ DBUG_ENTER("build_tmptable_filename");
+
+ char *p= strnmov(buff, mysql_tmpdir, bufflen);
+ my_snprintf(p, bufflen - (p - buff), "/%s%lx_%lx_%x%s",
+ tmp_file_prefix, current_pid,
+ thd->thread_id, thd->tmp_table++, reg_ext);
+
+ if (lower_case_table_names)
+ {
+ /* Convert all except tmpdir to lower case */
+ my_casedn_str(files_charset_info, p);
+ }
+
+ uint length= unpack_filename(buff, buff);
+ DBUG_PRINT("exit", ("buff: '%s'", buff));
+ DBUG_RETURN(length);
+}
+
+/*
+--------------------------------------------------------------------------
+
+ MODULE: DDL log
+ -----------------
+
+ This module is used to ensure that we can recover from crashes that occur
+ in the middle of a meta-data operation in MySQL. E.g. DROP TABLE t1, t2;
+ We need to ensure that both t1 and t2 are dropped and not only t1 and
+ also that each table drop is entirely done and not "half-baked".
+
+ To support this we create log entries for each meta-data statement in the
+ ddl log while we are executing. These entries are dropped when the
+ operation is completed.
+
+ At recovery those entries that were not completed will be executed.
+
+ There is only one ddl log in the system and it is protected by a mutex
+ and there is a global struct that contains information about its current
+ state.
+
+ History:
+ First version written in 2006 by Mikael Ronstrom
+--------------------------------------------------------------------------
+*/
+
+
+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;
+ uchar *file_entry_buf= (uchar*)global_ddl_log.file_entry_buf;
+ uint io_size= global_ddl_log.io_size;
+ DBUG_ENTER("read_ddl_log_file_entry");
+
+ if (my_pread(file_id, file_entry_buf, io_size, io_size * entry_no,
+ MYF(MY_WME)) != io_size)
+ error= TRUE;
+ DBUG_RETURN(error);
+}
+
+
+/*
+ Write one entry from ddl log file
+ SYNOPSIS
+ write_ddl_log_file_entry()
+ entry_no Entry number to read
+ RETURN VALUES
+ TRUE Error
+ FALSE Success
+*/
+
+static bool write_ddl_log_file_entry(uint entry_no)
+{
+ bool error= FALSE;
+ File file_id= global_ddl_log.file_id;
+ char *file_entry_buf= (char*)global_ddl_log.file_entry_buf;
+ DBUG_ENTER("write_ddl_log_file_entry");
+
+ if (my_pwrite(file_id, (uchar*)file_entry_buf,
+ IO_SIZE, IO_SIZE * entry_no, MYF(MY_WME)) != IO_SIZE)
+ error= TRUE;
+ DBUG_RETURN(error);
+}
+
+
+/*
+ Write ddl log header
+ SYNOPSIS
+ write_ddl_log_header()
+ RETURN VALUES
+ TRUE Error
+ FALSE Success
+*/
+
+static bool write_ddl_log_header()
+{
+ uint16 const_var;
+ bool error= FALSE;
+ DBUG_ENTER("write_ddl_log_header");
+
+ int4store(&global_ddl_log.file_entry_buf[DDL_LOG_NUM_ENTRY_POS],
+ global_ddl_log.num_entries);
+ const_var= FN_LEN;
+ int4store(&global_ddl_log.file_entry_buf[DDL_LOG_NAME_LEN_POS],
+ (ulong) const_var);
+ const_var= IO_SIZE;
+ int4store(&global_ddl_log.file_entry_buf[DDL_LOG_IO_SIZE_POS],
+ (ulong) const_var);
+ if (write_ddl_log_file_entry(0UL))
+ {
+ sql_print_error("Error writing ddl log header");
+ DBUG_RETURN(TRUE);
+ }
+ VOID(sync_ddl_log());
+ DBUG_RETURN(error);
+}
+
+
+/*
+ Create ddl log file name
+ SYNOPSIS
+ create_ddl_log_file_name()
+ file_name Filename setup
+ RETURN VALUES
+ NONE
+*/
+
+static inline void create_ddl_log_file_name(char *file_name)
+{
+ strxmov(file_name, mysql_data_home, "/", "ddl_log.log", NullS);
+}
+
+
+/*
+ Read header of ddl log file
+ SYNOPSIS
+ read_ddl_log_header()
+ RETURN VALUES
+ > 0 Last entry in ddl log
+ 0 No entries in ddl log
+ DESCRIPTION
+ When we read the ddl log header we get information about maximum sizes
+ of names in the ddl log and we also get information about the number
+ of entries in the ddl log.
+*/
+
+static uint read_ddl_log_header()
+{
+ char *file_entry_buf= (char*)global_ddl_log.file_entry_buf;
+ char file_name[FN_REFLEN];
+ uint entry_no;
+ bool successful_open= FALSE;
+ DBUG_ENTER("read_ddl_log_header");
+
+ create_ddl_log_file_name(file_name);
+ if ((global_ddl_log.file_id= my_open(file_name,
+ O_RDWR | O_BINARY, MYF(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()
+{
+ char file_name[FN_REFLEN];
+ DBUG_ENTER("init_ddl_log");
+
+ if (global_ddl_log.inited)
+ goto end;
+
+ global_ddl_log.io_size= IO_SIZE;
+ create_ddl_log_file_name(file_name);
+ if ((global_ddl_log.file_id= my_create(file_name,
+ CREATE_MODE,
+ O_RDWR | O_TRUNC | O_BINARY,
+ MYF(MY_WME))) < 0)
+ {
+ /* Couldn't create ddl log file, this is serious error */
+ sql_print_error("Failed to open ddl log file");
+ DBUG_RETURN(TRUE);
+ }
+ global_ddl_log.inited= TRUE;
+ if (write_ddl_log_header())
+ {
+ VOID(my_close(global_ddl_log.file_id, MYF(MY_WME)));
+ global_ddl_log.inited= FALSE;
+ DBUG_RETURN(TRUE);
+ }
+
+end:
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Execute one action in a ddl log entry
+ SYNOPSIS
+ execute_ddl_log_action()
+ ddl_log_entry Information in action entry to execute
+ RETURN VALUES
+ TRUE Error
+ FALSE Success
+*/
+
+static int execute_ddl_log_action(THD *thd, DDL_LOG_ENTRY *ddl_log_entry)
+{
+ bool frm_action= FALSE;
+ LEX_STRING handler_name;
+ handler *file= NULL;
+ MEM_ROOT mem_root;
+ int error= TRUE;
+ char to_path[FN_REFLEN];
+ char from_path[FN_REFLEN];
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ char *par_ext= (char*)".par";
+#endif
+ handlerton *hton;
+ DBUG_ENTER("execute_ddl_log_action");
+
+ if (ddl_log_entry->entry_type == DDL_IGNORE_LOG_ENTRY_CODE)
+ {
+ DBUG_RETURN(FALSE);
+ }
+ handler_name.str= (char*)ddl_log_entry->handler_name;
+ handler_name.length= strlen(ddl_log_entry->handler_name);
+ init_sql_alloc(&mem_root, TABLE_ALLOC_BLOCK_SIZE, 0);
+ if (!strcmp(ddl_log_entry->handler_name, reg_ext))
+ frm_action= TRUE;
+ else
+ {
+ plugin_ref plugin= ha_resolve_by_name(thd, &handler_name);
+ if (!plugin)
+ {
+ my_error(ER_ILLEGAL_HA, MYF(0), ddl_log_entry->handler_name);
+ goto error;
+ }
+ hton= plugin_data(plugin, handlerton*);
+ file= get_new_handler((TABLE_SHARE*)0, &mem_root, hton);
+ if (!file)
+ {
+ mem_alloc_error(sizeof(handler));
+ goto error;
+ }
+ }
+ switch (ddl_log_entry->action_type)
+ {
+ case DDL_LOG_REPLACE_ACTION:
+ case DDL_LOG_DELETE_ACTION:
+ {
+ if (ddl_log_entry->phase == 0)
+ {
+ if (frm_action)
+ {
+ strxmov(to_path, ddl_log_entry->name, reg_ext, NullS);
+ if ((error= my_delete(to_path, MYF(MY_WME))))
+ {
+ if (my_errno != ENOENT)
+ break;
+ }
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ strxmov(to_path, ddl_log_entry->name, par_ext, NullS);
+ VOID(my_delete(to_path, MYF(MY_WME)));
+#endif
+ }
+ else
+ {
+ if ((error= file->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
+ ---------------------------------------------------
+*/
+
+/*
+ SYNOPSIS
+ write_ddl_log_entry()
+ ddl_log_entry Information about log entry
+ out:entry_written Entry information written into
+
+ RETURN VALUES
+ TRUE Error
+ FALSE Success
+
+ DESCRIPTION
+ A careful write of the ddl log is performed to ensure that we can
+ handle crashes occurring during CREATE and ALTER TABLE processing.
+*/
+
+bool write_ddl_log_entry(DDL_LOG_ENTRY *ddl_log_entry,
+ DDL_LOG_MEMORY_ENTRY **active_entry)
+{
+ bool error, write_header;
+ DBUG_ENTER("write_ddl_log_entry");
+
+ if (init_ddl_log())
+ {
+ DBUG_RETURN(TRUE);
+ }
+ global_ddl_log.file_entry_buf[DDL_LOG_ENTRY_TYPE_POS]=
+ (char)DDL_LOG_ENTRY_CODE;
+ global_ddl_log.file_entry_buf[DDL_LOG_ACTION_TYPE_POS]=
+ (char)ddl_log_entry->action_type;
+ global_ddl_log.file_entry_buf[DDL_LOG_PHASE_POS]= 0;
+ int4store(&global_ddl_log.file_entry_buf[DDL_LOG_NEXT_ENTRY_POS],
+ ddl_log_entry->next_entry);
+ DBUG_ASSERT(strlen(ddl_log_entry->name) < FN_LEN);
+ strmake(&global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS],
+ ddl_log_entry->name, FN_LEN - 1);
+ if (ddl_log_entry->action_type == DDL_LOG_RENAME_ACTION ||
+ ddl_log_entry->action_type == DDL_LOG_REPLACE_ACTION)
+ {
+ DBUG_ASSERT(strlen(ddl_log_entry->from_name) < FN_LEN);
+ strmake(&global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS + FN_LEN],
+ ddl_log_entry->from_name, FN_LEN - 1);
+ }
+ else
+ global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS + FN_LEN]= 0;
+ DBUG_ASSERT(strlen(ddl_log_entry->handler_name) < FN_LEN);
+ strmake(&global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS + (2*FN_LEN)],
+ ddl_log_entry->handler_name, FN_LEN - 1);
+ if (get_free_ddl_log_entry(active_entry, &write_header))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ error= FALSE;
+ if (write_ddl_log_file_entry((*active_entry)->entry_pos))
+ {
+ error= TRUE;
+ sql_print_error("Failed to write entry_no = %u",
+ (*active_entry)->entry_pos);
+ }
+ if (write_header && !error)
+ {
+ VOID(sync_ddl_log());
+ if (write_ddl_log_header())
+ error= TRUE;
+ }
+ if (error)
+ release_ddl_log_memory_entry(*active_entry);
+ DBUG_RETURN(error);
+}
+
+
+/*
+ Write final entry in the ddl log
+ SYNOPSIS
+ write_execute_ddl_log_entry()
+ first_entry First entry in linked list of entries
+ to execute, if 0 = NULL it means that
+ the entry is removed and the entries
+ are put into the free list.
+ complete Flag indicating we are simply writing
+ info about that entry has been completed
+ in:out:active_entry Entry to execute, 0 = NULL if the entry
+ is written first time and needs to be
+ returned. In this case the entry written
+ is returned in this parameter
+ RETURN VALUES
+ TRUE Error
+ FALSE Success
+
+ DESCRIPTION
+ This is the last write in the ddl log. The previous log entries have
+ already been written but not yet synched to disk.
+ We write a couple of log entries that describes action to perform.
+ This entries are set-up in a linked list, however only when a first
+ execute entry is put as the first entry these will be executed.
+ This routine writes this first
+*/
+
+bool write_execute_ddl_log_entry(uint first_entry,
+ bool complete,
+ DDL_LOG_MEMORY_ENTRY **active_entry)
+{
+ 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(used_list, MYF(0));
+ used_list= tmp;
+ }
+ while (free_list)
+ {
+ DDL_LOG_MEMORY_ENTRY *tmp= free_list->next_log_entry;
+ my_free(free_list, MYF(0));
+ free_list= tmp;
+ }
+ close_ddl_log();
+ global_ddl_log.inited= 0;
+ pthread_mutex_unlock(&LOCK_gdl);
+ VOID(pthread_mutex_destroy(&LOCK_gdl));
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+---------------------------------------------------------------------------
+
+ END MODULE DDL log
+ --------------------
+
+---------------------------------------------------------------------------
+*/
+
+
+/**
+ @brief construct a temporary shadow file name.
+
+ @details Make a shadow file name used by ALTER TABLE to construct the
+ modified table (with keeping the original). The modified table is then
+ moved back as original table. The name must start with the temp file
+ prefix so it gets filtered out by table files listing routines.
+
+ @param[out] buff buffer to receive the constructed name
+ @param bufflen size of buff
+ @param lpt alter table data structure
+
+ @retval path length
+*/
-uint build_table_path(char *buff, size_t bufflen, const char *db,
- const char *table, const char *ext)
+uint build_table_shadow_filename(char *buff, size_t bufflen,
+ ALTER_PARTITION_PARAM_TYPE *lpt)
{
- strxnmov(buff, bufflen-1, mysql_data_home, "/", db, "/", table, ext,
- NullS);
- return unpack_filename(buff,buff);
+ char tmp_name[FN_REFLEN];
+ my_snprintf (tmp_name, sizeof (tmp_name), "%s-%s", tmp_file_prefix,
+ lpt->table_name);
+ return build_table_filename(buff, bufflen, lpt->db, tmp_name, "", FN_IS_TMP);
}
+/*
+ SYNOPSIS
+ mysql_write_frm()
+ lpt Struct carrying many parameters needed for this
+ method
+ flags Flags as defined below
+ WFRM_INITIAL_WRITE If set we need to prepare table before
+ creating the frm file
+ WFRM_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_shadow_filename(shadow_path, sizeof(shadow_path), lpt);
+ strxmov(shadow_frm_name, shadow_path, reg_ext, NullS);
+ if (flags & WFRM_WRITE_SHADOW)
+ {
+ if (mysql_prepare_create_table(lpt->thd, lpt->create_info,
+ lpt->alter_info,
+ /*tmp_table*/ 1,
+ &lpt->db_options,
+ lpt->table->file,
+ &lpt->key_info_buffer,
+ &lpt->key_count,
+ /*select_field_count*/ 0))
+ {
+ DBUG_RETURN(TRUE);
+ }
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ {
+ partition_info *part_info= lpt->table->part_info;
+ char *part_syntax_buf;
+ uint syntax_len;
+
+ if (part_info)
+ {
+ TABLE_SHARE *share= lpt->table->s;
+ if (!(part_syntax_buf= generate_partition_syntax(part_info,
+ &syntax_len,
+ TRUE, TRUE)))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ part_info->part_info_string= part_syntax_buf;
+ share->partition_info_len= part_info->part_info_len= syntax_len;
+ if (share->partition_info_buffer_size < syntax_len + 1)
+ {
+ share->partition_info_buffer_size= syntax_len+1;
+ if (!(share->partition_info=
+ (char*) alloc_root(&share->mem_root, syntax_len+1)))
+ DBUG_RETURN(TRUE);
+
+ }
+ memcpy((char*) share->partition_info, part_syntax_buf, syntax_len + 1);
+ }
+ }
+#endif
+ /* Write shadow frm file */
+ lpt->create_info->table_options= lpt->db_options;
+ if ((mysql_create_frm(lpt->thd, shadow_frm_name, lpt->db,
+ lpt->table_name, lpt->create_info,
+ lpt->alter_info->create_list, lpt->key_count,
+ lpt->key_info_buffer, lpt->table->file)) ||
+ lpt->table->file->create_handler_files(shadow_path, NULL,
+ CHF_CREATE_FLAG,
+ lpt->create_info))
+ {
+ my_delete(shadow_frm_name, MYF(0));
+ error= 1;
+ goto end;
+ }
+ }
+ if (flags & WFRM_PACK_FRM)
+ {
+ /*
+ We need to pack the frm file and after packing it we delete the
+ frm file to ensure it doesn't get used. This is only used for
+ handlers that have the main version of the frm file stored in the
+ handler.
+ */
+ uchar *data;
+ size_t length;
+ if (readfrm(shadow_path, &data, &length) ||
+ packfrm(data, length, &lpt->pack_frm_data, &lpt->pack_frm_len))
+ {
+ my_free(data, MYF(MY_ALLOW_ZERO_PTR));
+ my_free(lpt->pack_frm_data, MYF(MY_ALLOW_ZERO_PTR));
+ mem_alloc_error(length);
+ error= 1;
+ goto end;
+ }
+ error= my_delete(shadow_frm_name, MYF(MY_WME));
+ }
+ if (flags & WFRM_INSTALL_SHADOW)
+ {
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ partition_info *part_info= lpt->part_info;
+#endif
+ /*
+ Build frm file name
+ */
+ build_table_filename(path, sizeof(path), lpt->db,
+ lpt->table_name, "", 0);
+ strxmov(frm_name, path, reg_ext, NullS);
+ /*
+ When we are changing to use new frm file we need to ensure that we
+ don't collide with another thread in process to open the frm file.
+ We start by deleting the .frm file and possible .par file. Then we
+ write to the DDL log that we have completed the delete phase by
+ increasing the phase of the log entry. Next step is to rename the
+ new .frm file and the new .par file to the real name. After
+ completing this we write a new phase to the log entry that will
+ deactivate it.
+ */
+ VOID(pthread_mutex_lock(&LOCK_open));
+ if (my_delete(frm_name, MYF(MY_WME)) ||
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ lpt->table->file->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.
@@ -120,19 +1454,8 @@ bool mysql_rm_table(THD *thd,TABLE_LIST *tables, my_bool if_exists,
LOCK_open during wait_if_global_read_lock(), other threads could not
close their tables. This would make a pretty deadlock.
*/
- thd->mysys_var->current_mutex= &LOCK_open;
- thd->mysys_var->current_cond= &COND_refresh;
- VOID(pthread_mutex_lock(&LOCK_open));
-
error= mysql_rm_table_part2(thd, tables, if_exists, drop_temporary, 0, 0);
- pthread_mutex_unlock(&LOCK_open);
-
- pthread_mutex_lock(&thd->mysys_var->mutex);
- thd->mysys_var->current_mutex= 0;
- thd->mysys_var->current_cond= 0;
- pthread_mutex_unlock(&thd->mysys_var->mutex);
-
if (need_start_waiters)
start_waiting_global_read_lock(thd);
@@ -142,49 +1465,6 @@ bool mysql_rm_table(THD *thd,TABLE_LIST *tables, my_bool if_exists,
DBUG_RETURN(FALSE);
}
-
-/*
- delete (drop) tables.
-
- SYNOPSIS
- mysql_rm_table_part2_with_lock()
- thd Thread handle
- tables List of tables to delete
- if_exists If 1, don't give error if one table doesn't exists
- dont_log_query Don't write query to log files. This will also not
- generate warnings if the handler files doesn't exists
-
- NOTES
- Works like documented in mysql_rm_table(), but don't check
- global_read_lock and don't send_ok packet to server.
-
- RETURN
- 0 ok
- 1 error
-*/
-
-int mysql_rm_table_part2_with_lock(THD *thd,
- TABLE_LIST *tables, bool if_exists,
- bool drop_temporary, bool dont_log_query)
-{
- int error;
- thd->mysys_var->current_mutex= &LOCK_open;
- thd->mysys_var->current_cond= &COND_refresh;
- VOID(pthread_mutex_lock(&LOCK_open));
-
- error= mysql_rm_table_part2(thd, tables, if_exists, drop_temporary, 1,
- dont_log_query);
-
- pthread_mutex_unlock(&LOCK_open);
-
- pthread_mutex_lock(&thd->mysys_var->mutex);
- thd->mysys_var->current_mutex= 0;
- thd->mysys_var->current_cond= 0;
- pthread_mutex_unlock(&thd->mysys_var->mutex);
- return error;
-}
-
-
/*
Execute the drop of a normal or temporary table
@@ -220,16 +1500,58 @@ 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);
+
+ 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 ");
+ }
+
+ pthread_mutex_lock(&LOCK_open);
+
+ /*
+ If we have the table in the definition cache, we don't have to check the
+ .frm file to find if the table is a normal table (not view) and what
+ engine to use.
+ */
+
+ for (table= tables; table; table= table->next_local)
+ {
+ TABLE_SHARE *share;
+ table->db_type= NULL;
+ if ((share= get_cached_table_share(table->db, table->table_name)))
+ table->db_type= share->db_type();
+
+ /* Disable drop of enabled log tables */
+ if (share && (share->table_category == TABLE_CATEGORY_PERFORMANCE) &&
+ check_if_log_table(table->db_length, table->db,
+ table->table_name_length, table->table_name, 1))
+ {
+ my_error(ER_BAD_LOG_STATEMENT, MYF(0), "DROP");
+ pthread_mutex_unlock(&LOCK_open);
+ DBUG_RETURN(1);
+ }
+ }
- if (!drop_temporary && lock_table_names(thd, tables))
+ if (!drop_temporary && lock_table_names_exclusively(thd, tables))
+ {
+ pthread_mutex_unlock(&LOCK_open);
DBUG_RETURN(1);
+ }
/* Don't give warnings for not found errors, as we already generate notes */
thd->no_warnings_for_error= 1;
@@ -237,37 +1559,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))
+ mysql_ha_flush(thd, table, MYSQL_HA_CLOSE_FINAL, 1);
+ 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)
{
- error=-1;
+ error= -1;
goto err_with_placeholders;
}
alias= (lower_case_table_names == 2) ? table->alias : table->table_name;
- /* remove form file and isam files */
- build_table_path(path, sizeof(path), db, alias, reg_ext);
+ /* remove .frm file and engine files */
+ path_length= build_table_filename(path, sizeof(path),
+ db, alias, reg_ext, 0);
}
if (drop_temporary ||
- (access(path,F_OK) &&
- ha_create_table_from_engine(thd,db,alias)) ||
- (!drop_view &&
- mysql_frm_type(thd, path, &table_type) != FRMTYPE_TABLE))
+ (table_type == NULL &&
+ (access(path, F_OK) &&
+ ha_create_table_from_engine(thd, db, alias)) ||
+ (!drop_view &&
+ mysql_frm_type(thd, path, &frm_db_type) != FRMTYPE_TABLE)))
{
// Table was not found on disk and table can't be created from engine
if (if_exists)
@@ -280,13 +1637,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)
{
@@ -314,7 +1675,12 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists,
wrong_tables.append(String(table->table_name,system_charset_info));
}
}
- thd->tmp_table_used= tmp_table_deleted;
+ /*
+ It's safe to unlock LOCK_open: we have an exclusive lock
+ on the table name.
+ */
+ pthread_mutex_unlock(&LOCK_open);
+ thd->thread_specific_used|= tmp_table_deleted;
error= 0;
if (wrong_tables.length())
{
@@ -329,33 +1695,88 @@ 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.
+ */
}
}
-
+ pthread_mutex_lock(&LOCK_open);
err_with_placeholders:
- if (!drop_temporary)
- unlock_table_names(thd, tables, (TABLE_LIST*) 0);
+ unlock_table_names(thd, tables, (TABLE_LIST*) 0);
+ pthread_mutex_unlock(&LOCK_open);
thd->no_warnings_for_error= 0;
DBUG_RETURN(error);
}
-int quick_rm_table(enum db_type base,const char *db,
- const char *table_name)
+/*
+ Quickly remove a table.
+
+ SYNOPSIS
+ quick_rm_table()
+ base The handlerton handle.
+ db The database name.
+ table_name The table name.
+ flags flags for build_table_filename().
+
+ RETURN
+ 0 OK
+ != 0 Error
+*/
+
+bool quick_rm_table(handlerton *base,const char *db,
+ const char *table_name, uint flags)
{
char path[FN_REFLEN];
- int error=0;
- build_table_path(path, sizeof(path), db, table_name, reg_ext);
+ bool error= 0;
+ DBUG_ENTER("quick_rm_table");
+
+ uint path_length= build_table_filename(path, sizeof(path),
+ db, table_name, reg_ext, flags);
if (my_delete(path,MYF(0)))
- error=1; /* purecov: inspected */
- *fn_ext(path)= 0; // Remove reg_ext
- return ha_delete_table(current_thd, base, path, table_name, 0) || error;
+ error= 1; /* purecov: inspected */
+ path[path_length - reg_ext_length]= '\0'; // Remove reg_ext
+ DBUG_RETURN(ha_delete_table(current_thd, base, path, db, table_name, 0) ||
+ error);
}
/*
@@ -500,7 +1921,7 @@ void calculate_interval_lengths(CHARSET_INFO *cs, TYPELIB *interval,
table_flags table flags
DESCRIPTION
- This function prepares a create_field instance.
+ This function prepares a Create_field instance.
Fields such as pack_flag are valid after this call.
RETURN VALUES
@@ -508,25 +1929,25 @@ void calculate_interval_lengths(CHARSET_INFO *cs, TYPELIB *interval,
1 Error
*/
-int prepare_create_field(create_field *sql_field,
+int prepare_create_field(Create_field *sql_field,
uint *blob_columns,
int *timestamps, int *timestamps_with_niladic,
- uint table_flags)
+ longlong table_flags)
{
unsigned int dup_val_count;
DBUG_ENTER("prepare_field");
/*
- This code came from mysql_prepare_table.
+ This code came from mysql_prepare_create_table.
Indent preserved to make patching easier
*/
DBUG_ASSERT(sql_field->charset);
switch (sql_field->sql_type) {
- case FIELD_TYPE_BLOB:
- case FIELD_TYPE_MEDIUM_BLOB:
- case FIELD_TYPE_TINY_BLOB:
- case FIELD_TYPE_LONG_BLOB:
+ case MYSQL_TYPE_BLOB:
+ case MYSQL_TYPE_MEDIUM_BLOB:
+ case MYSQL_TYPE_TINY_BLOB:
+ case MYSQL_TYPE_LONG_BLOB:
sql_field->pack_flag=FIELDFLAG_BLOB |
pack_length_to_packflag(sql_field->pack_length -
portable_sizeof_char_ptr);
@@ -536,7 +1957,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))
{
@@ -576,12 +1997,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)
@@ -592,7 +2013,7 @@ int prepare_create_field(create_field *sql_field,
sql_field->charset, &dup_val_count))
DBUG_RETURN(1);
break;
- case FIELD_TYPE_SET:
+ case MYSQL_TYPE_SET:
sql_field->pack_flag=pack_length_to_packflag(sql_field->pack_length) |
FIELDFLAG_BITFIELD;
if (sql_field->charset->state & MY_CS_BINSORT)
@@ -609,19 +2030,20 @@ int prepare_create_field(create_field *sql_field,
DBUG_RETURN(1);
}
break;
- case FIELD_TYPE_DATE: // Rest of string types
- case FIELD_TYPE_NEWDATE:
- case FIELD_TYPE_TIME:
- case FIELD_TYPE_DATETIME:
- case FIELD_TYPE_NULL:
+ case MYSQL_TYPE_DATE: // Rest of string types
+ case MYSQL_TYPE_NEWDATE:
+ case MYSQL_TYPE_TIME:
+ case MYSQL_TYPE_DATETIME:
+ case MYSQL_TYPE_NULL:
sql_field->pack_flag=f_settype((uint) sql_field->sql_type);
break;
- case FIELD_TYPE_BIT:
+ case MYSQL_TYPE_BIT:
/*
- We have sql_field->pack_flag already set here, see mysql_prepare_table().
+ We have sql_field->pack_flag already set here, see
+ mysql_prepare_create_table().
*/
break;
- case FIELD_TYPE_NEWDECIMAL:
+ case MYSQL_TYPE_NEWDECIMAL:
sql_field->pack_flag=(FIELDFLAG_NUMBER |
(sql_field->flags & UNSIGNED_FLAG ? 0 :
FIELDFLAG_DECIMAL) |
@@ -629,7 +2051,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)
{
@@ -667,10 +2089,16 @@ int prepare_create_field(create_field *sql_field,
Preparation for table creation
SYNOPSIS
- mysql_prepare_table()
- thd Thread object
- create_info Create information (like MAX_ROWS)
- alter_info List of columns and indexes to create
+ mysql_prepare_create_table()
+ thd Thread object.
+ create_info Create information (like MAX_ROWS).
+ alter_info List of columns and indexes to create
+ tmp_table If a temporary table is to be created.
+ db_options INOUT Table options (like HA_OPTION_PACK_RECORD).
+ file The handler for the new table.
+ key_info_buffer OUT An array of KEY structs for the indexes.
+ key_count OUT The number of elements in the array.
+ select_field_count The number of fields coming from a select table.
DESCRIPTION
Prepares the table and key structures for table creation.
@@ -679,19 +2107,20 @@ int prepare_create_field(create_field *sql_field,
sets create_info->varchar if the table has a varchar
RETURN VALUES
- 0 ok
- -1 error
+ FALSE OK
+ TRUE error
*/
-static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info,
- Alter_info *alter_info,
- bool tmp_table,
- uint *db_options,
- handler *file, KEY **key_info_buffer,
- uint *key_count, int select_field_count)
+static bool
+mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info,
+ Alter_info *alter_info,
+ bool tmp_table,
+ uint *db_options,
+ handler *file, KEY **key_info_buffer,
+ uint *key_count, int select_field_count)
{
const char *key_name;
- create_field *sql_field,*dup_field;
+ Create_field *sql_field,*dup_field;
uint field,null_fields,blob_columns,max_key_length;
ulong record_offset= 0;
KEY *key_info;
@@ -699,10 +2128,10 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info,
int timestamps= 0, timestamps_with_niladic= 0;
int field_no,dup_no;
int select_field_pos,auto_increment=0;
- List_iterator<create_field> it(alter_info->create_list);
- List_iterator<create_field> it2(alter_info->create_list);
+ List_iterator<Create_field> it(alter_info->create_list);
+ List_iterator<Create_field> it2(alter_info->create_list);
uint total_uneven_bit_length= 0;
- DBUG_ENTER("mysql_prepare_table");
+ DBUG_ENTER("mysql_prepare_create_table");
select_field_pos= alter_info->create_list.elements - select_field_count;
null_fields=blob_columns=0;
@@ -739,7 +2168,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info,
strmake(strmake(tmp, save_cs->csname, sizeof(tmp)-4),
STRING_WITH_LEN("_bin"));
my_error(ER_UNKNOWN_COLLATION, MYF(0), tmp);
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
}
/*
@@ -748,36 +2177,33 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info,
*/
if (sql_field->def &&
save_cs != sql_field->def->collation.collation &&
- (sql_field->sql_type == FIELD_TYPE_VAR_STRING ||
- sql_field->sql_type == FIELD_TYPE_STRING ||
- sql_field->sql_type == FIELD_TYPE_SET ||
- sql_field->sql_type == FIELD_TYPE_ENUM))
- {
- Query_arena backup_arena;
- bool need_to_change_arena= !thd->stmt_arena->is_conventional();
- if (need_to_change_arena)
- {
- /* Asser that we don't do that at every PS execute */
- DBUG_ASSERT(thd->stmt_arena->is_first_stmt_execute() ||
- thd->stmt_arena->is_first_sp_execute());
- thd->set_n_backup_active_arena(thd->stmt_arena, &backup_arena);
- }
-
+ (sql_field->sql_type == MYSQL_TYPE_VAR_STRING ||
+ sql_field->sql_type == MYSQL_TYPE_STRING ||
+ sql_field->sql_type == MYSQL_TYPE_SET ||
+ sql_field->sql_type == MYSQL_TYPE_ENUM))
+ {
+ /*
+ Starting from 5.1 we work here with a copy of Create_field
+ created by the caller, not with the instance that was
+ originally created during parsing. It's OK to create
+ a temporary item and initialize with it a member of the
+ copy -- this item will be thrown away along with the copy
+ at the end of execution, and thus not introduce a dangling
+ pointer in the parsed tree of a prepared statement or a
+ stored procedure statement.
+ */
sql_field->def= sql_field->def->safe_charset_converter(save_cs);
- if (need_to_change_arena)
- thd->restore_active_arena(thd->stmt_arena, &backup_arena);
-
if (sql_field->def == NULL)
{
/* Could not convert */
my_error(ER_INVALID_DEFAULT, MYF(0), sql_field->field_name);
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
}
}
- if (sql_field->sql_type == FIELD_TYPE_SET ||
- sql_field->sql_type == FIELD_TYPE_ENUM)
+ if (sql_field->sql_type == MYSQL_TYPE_SET ||
+ sql_field->sql_type == MYSQL_TYPE_ENUM)
{
uint32 dummy;
CHARSET_INFO *cs= sql_field->charset;
@@ -791,12 +2217,11 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info,
if (!interval)
{
/*
- Create the typelib in prepared statement memory if we're
- executing one.
+ Create the typelib in runtime memory - we will free the
+ occupied memory at the same time when we free this
+ sql_field -- at the end of execution.
*/
- MEM_ROOT *stmt_root= thd->stmt_arena->mem_root;
-
- interval= sql_field->interval= typelib(stmt_root,
+ interval= sql_field->interval= typelib(thd->mem_root,
sql_field->interval_list);
List_iterator<String> int_it(sql_field->interval_list);
String conv, *tmp;
@@ -813,7 +2238,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info,
{
uint cnv_errs;
conv.copy(tmp->ptr(), tmp->length(), tmp->charset(), cs, &cnv_errs);
- interval->type_names[i]= strmake_root(stmt_root, conv.ptr(),
+ interval->type_names[i]= strmake_root(thd->mem_root, conv.ptr(),
conv.length());
interval->type_lengths[i]= conv.length();
}
@@ -823,21 +2248,21 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info,
interval->type_lengths[i]);
interval->type_lengths[i]= lengthsp;
((uchar *)interval->type_names[i])[lengthsp]= '\0';
- if (sql_field->sql_type == FIELD_TYPE_SET)
+ if (sql_field->sql_type == MYSQL_TYPE_SET)
{
if (cs->coll->instr(cs, interval->type_names[i],
interval->type_lengths[i],
comma_buf, comma_length, NULL, 0))
{
my_error(ER_ILLEGAL_VALUE_FOR_TYPE, MYF(0), "set", tmp->ptr());
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
}
}
}
sql_field->interval_list.empty(); // Don't need interval_list anymore
}
- if (sql_field->sql_type == FIELD_TYPE_SET)
+ if (sql_field->sql_type == MYSQL_TYPE_SET)
{
uint32 field_length;
if (sql_field->def != NULL)
@@ -851,7 +2276,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info,
if ((sql_field->flags & NOT_NULL_FLAG) != 0)
{
my_error(ER_INVALID_DEFAULT, MYF(0), sql_field->field_name);
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
}
/* else, NULL is an allowed value */
@@ -867,16 +2292,16 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info,
if (not_found)
{
my_error(ER_INVALID_DEFAULT, MYF(0), sql_field->field_name);
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
}
}
calculate_interval_lengths(cs, interval, &dummy, &field_length);
sql_field->length= field_length + (interval->count - 1);
}
- else /* FIELD_TYPE_ENUM */
+ else /* MYSQL_TYPE_ENUM */
{
uint32 field_length;
- DBUG_ASSERT(sql_field->sql_type == FIELD_TYPE_ENUM);
+ DBUG_ASSERT(sql_field->sql_type == MYSQL_TYPE_ENUM);
if (sql_field->def != NULL)
{
String str, *def= sql_field->def->val_str(&str);
@@ -885,7 +2310,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info,
if ((sql_field->flags & NOT_NULL_FLAG) != 0)
{
my_error(ER_INVALID_DEFAULT, MYF(0), sql_field->field_name);
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
}
/* else, the defaults yield the correct length for NULLs. */
@@ -896,7 +2321,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info,
if (find_type2(interval, def->ptr(), def->length(), cs) == 0) /* not found */
{
my_error(ER_INVALID_DEFAULT, MYF(0), sql_field->field_name);
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
}
}
}
@@ -906,10 +2331,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;
@@ -917,7 +2342,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info,
sql_field->create_length_to_internal_length();
if (prepare_blob_field(thd, sql_field))
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
if (!(sql_field->flags & NOT_NULL_FLAG))
null_fields++;
@@ -925,7 +2350,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info,
if (check_column_name(sql_field->field_name))
{
my_error(ER_WRONG_COLUMN_NAME, MYF(0), sql_field->field_name);
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
}
/* Check if we have used the same field name before */
@@ -942,7 +2367,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info,
if (field_no < select_field_pos || dup_no >= select_field_pos)
{
my_error(ER_DUP_FIELDNAME, MYF(0), sql_field->field_name);
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
}
else
{
@@ -992,10 +2417,10 @@ 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()))
- DBUG_RETURN(-1);
+ file->ha_table_flags()))
+ DBUG_RETURN(TRUE);
if (sql_field->sql_type == MYSQL_TYPE_VARCHAR)
- create_info->varchar= 1;
+ create_info->varchar= TRUE;
sql_field->offset= record_offset;
if (MTYP_TYPENR(sql_field->unireg_check) == Field::NEXT_NUMBER)
auto_increment++;
@@ -1005,26 +2430,26 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info,
{
my_message(ER_TOO_MUCH_AUTO_TIMESTAMP_COLS,
ER(ER_TOO_MUCH_AUTO_TIMESTAMP_COLS), MYF(0));
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
}
if (auto_increment > 1)
{
my_message(ER_WRONG_AUTO_KEY, ER(ER_WRONG_AUTO_KEY), MYF(0));
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
}
if (auto_increment &&
- (file->table_flags() & HA_NO_AUTO_INCREMENT))
+ (file->ha_table_flags() & HA_NO_AUTO_INCREMENT))
{
my_message(ER_TABLE_CANT_HANDLE_AUTO_INCREMENT,
ER(ER_TABLE_CANT_HANDLE_AUTO_INCREMENT), MYF(0));
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
}
- if (blob_columns && (file->table_flags() & HA_NO_BLOBS))
+ if (blob_columns && (file->ha_table_flags() & HA_NO_BLOBS))
{
my_message(ER_TABLE_CANT_HANDLE_BLOB, ER(ER_TABLE_CANT_HANDLE_BLOB),
MYF(0));
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
}
/* Create keys */
@@ -1043,17 +2468,20 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info,
while ((key=key_iterator++))
{
+ DBUG_PRINT("info", ("key name: '%s' type: %d", key->name ? key->name :
+ "(none)" , key->type));
+ LEX_STRING key_name_str;
if (key->type == Key::FOREIGN_KEY)
{
fk_key_count++;
- foreign_key *fk_key= (foreign_key*) key;
+ Foreign_key *fk_key= (Foreign_key*) key;
if (fk_key->ref_columns.elements &&
fk_key->ref_columns.elements != fk_key->columns.elements)
{
my_error(ER_WRONG_FK_DEF, MYF(0),
(fk_key->name ? fk_key->name : "foreign key without name"),
ER(ER_KEY_REF_DO_NOT_MATCH_TABLE_REF));
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
}
continue;
}
@@ -1062,12 +2490,15 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info,
if (key->columns.elements > tmp)
{
my_error(ER_TOO_MANY_KEY_PARTS,MYF(0),tmp);
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
}
- if (key->name && strlen(key->name) > NAME_LEN)
+ key_name_str.str= (char*) key->name;
+ key_name_str.length= key->name ? strlen(key->name) : 0;
+ if (check_string_char_length(&key_name_str, "", NAME_CHAR_LEN,
+ system_charset_info, 1))
{
my_error(ER_TOO_LONG_IDENT, MYF(0), key->name);
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
}
key_iterator2.rewind ();
if (key->type != Key::FOREIGN_KEY)
@@ -1103,31 +2534,31 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info,
key_parts+=key->columns.elements;
else
(*key_count)--;
- if (key->name && !tmp_table &&
+ if (key->name && !tmp_table && (key->type != Key::PRIMARY) &&
!my_strcasecmp(system_charset_info,key->name,primary_key_name))
{
my_error(ER_WRONG_NAME_FOR_INDEX, MYF(0), key->name);
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
}
}
tmp=file->max_keys();
if (*key_count > tmp)
{
my_error(ER_TOO_MANY_KEYS,MYF(0),tmp);
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
}
- (*key_info_buffer) = key_info= (KEY*) sql_calloc(sizeof(KEY)* *key_count);
+ (*key_info_buffer)= key_info= (KEY*) sql_calloc(sizeof(KEY) * (*key_count));
key_part_info=(KEY_PART_INFO*) sql_calloc(sizeof(KEY_PART_INFO)*key_parts);
if (!*key_info_buffer || ! key_part_info)
- DBUG_RETURN(-1); // Out of memory
+ DBUG_RETURN(TRUE); // Out of memory
key_iterator.rewind();
key_number=0;
for (; (key=key_iterator++) ; key_number++)
{
uint key_length=0;
- key_part_spec *column;
+ Key_part_spec *column;
if (key->name == ignore_key)
{
@@ -1139,12 +2570,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
@@ -1153,7 +2588,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info,
#else
my_error(ER_FEATURE_DISABLED, MYF(0),
sym_group_geom.name, sym_group_geom.needed_define);
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
#endif
case Key::FOREIGN_KEY:
key_number--; // Skip this key
@@ -1168,15 +2603,15 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info,
key_info->key_parts=(uint8) key->columns.elements;
key_info->key_part=key_part_info;
key_info->usable_key_parts= key_number;
- key_info->algorithm=key->algorithm;
+ key_info->algorithm= key->key_create_info.algorithm;
if (key->type == Key::FULLTEXT)
{
- if (!(file->table_flags() & HA_CAN_FULLTEXT))
+ if (!(file->ha_table_flags() & HA_CAN_FULLTEXT))
{
my_message(ER_TABLE_CANT_HANDLE_FT, ER(ER_TABLE_CANT_HANDLE_FT),
MYF(0));
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
}
}
/*
@@ -1190,16 +2625,16 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info,
/* TODO: Add proper checks if handler supports key_type and algorithm */
if (key_info->flags & HA_SPATIAL)
{
- if (!(file->table_flags() & HA_CAN_RTREEKEYS))
+ if (!(file->ha_table_flags() & HA_CAN_RTREEKEYS))
{
my_message(ER_TABLE_CANT_HANDLE_SPKEYS, ER(ER_TABLE_CANT_HANDLE_SPKEYS),
MYF(0));
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
}
if (key_info->key_parts != 1)
{
my_error(ER_WRONG_ARGUMENTS, MYF(0), "SPATIAL INDEX");
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
}
}
else if (key_info->algorithm == HA_KEY_ALG_RTREE)
@@ -1208,24 +2643,36 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info,
if ((key_info->key_parts & 1) == 1)
{
my_error(ER_WRONG_ARGUMENTS, MYF(0), "RTREE INDEX");
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
}
/* TODO: To be deleted */
my_error(ER_NOT_SUPPORTED_YET, MYF(0), "RTREE INDEX");
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
#else
my_error(ER_FEATURE_DISABLED, MYF(0),
sym_group_rtree.name, sym_group_rtree.needed_define);
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
#endif
}
- List_iterator<key_part_spec> cols(key->columns), cols2(key->columns);
+ /* Take block size from key part or table part */
+ /*
+ TODO: Add warning if block size changes. We can't do it here, as
+ this may depend on the size of the key
+ */
+ key_info->block_size= (key->key_create_info.block_size ?
+ key->key_create_info.block_size :
+ create_info->key_block_size);
+
+ if (key_info->block_size)
+ key_info->flags|= HA_USES_BLOCK_SIZE;
+
+ List_iterator<Key_part_spec> cols(key->columns), cols2(key->columns);
CHARSET_INFO *ft_key_charset=0; // for FULLTEXT
for (uint column_nr=0 ; (column=cols++) ; column_nr++)
{
uint length;
- key_part_spec *dup_column;
+ Key_part_spec *dup_column;
it.rewind();
field=0;
@@ -1237,7 +2684,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info,
if (!sql_field)
{
my_error(ER_KEY_COLUMN_DOES_NOT_EXITS, MYF(0), column->field_name);
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
}
while ((dup_column= cols2++) != column)
{
@@ -1247,7 +2694,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info,
my_printf_error(ER_DUP_FIELDNAME,
ER(ER_DUP_FIELDNAME),MYF(0),
column->field_name);
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
}
}
cols2.rewind();
@@ -1277,13 +2724,19 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info,
{
column->length*= sql_field->charset->mbmaxlen;
+ if (key->type == Key::SPATIAL && column->length)
+ {
+ my_error(ER_WRONG_SUB_KEY, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+
if (f_is_blob(sql_field->pack_flag) ||
(f_is_geom(sql_field->pack_flag) && key->type != Key::SPATIAL))
{
- if (!(file->table_flags() & HA_CAN_INDEX_BLOBS))
+ if (!(file->ha_table_flags() & HA_CAN_INDEX_BLOBS))
{
my_error(ER_BLOB_USED_AS_KEY, MYF(0), column->field_name);
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
}
if (f_is_geom(sql_field->pack_flag) && sql_field->geom_type ==
Field::GEOM_POINT)
@@ -1291,7 +2744,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info,
if (!column->length)
{
my_error(ER_BLOB_KEY_WITHOUT_LENGTH, MYF(0), column->field_name);
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
}
}
#ifdef HAVE_SPATIAL
@@ -1317,22 +2770,24 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info,
null_fields--;
}
else
- key_info->flags|= HA_NULL_PART_KEY;
- if (!(file->table_flags() & HA_NULL_IN_KEY))
- {
- my_error(ER_NULL_COLUMN_IN_INDEX, MYF(0), column->field_name);
- DBUG_RETURN(-1);
- }
- if (key->type == Key::SPATIAL)
- {
- my_message(ER_SPATIAL_CANT_HAVE_NULL,
- ER(ER_SPATIAL_CANT_HAVE_NULL), MYF(0));
- DBUG_RETURN(-1);
- }
+ {
+ key_info->flags|= HA_NULL_PART_KEY;
+ if (!(file->ha_table_flags() & HA_NULL_IN_KEY))
+ {
+ my_error(ER_NULL_COLUMN_IN_INDEX, MYF(0), column->field_name);
+ DBUG_RETURN(TRUE);
+ }
+ if (key->type == Key::SPATIAL)
+ {
+ my_message(ER_SPATIAL_CANT_HAVE_NULL,
+ ER(ER_SPATIAL_CANT_HAVE_NULL), MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+ }
}
if (MTYP_TYPENR(sql_field->unireg_check) == Field::NEXT_NUMBER)
{
- if (column_nr == 0 || (file->table_flags() & HA_AUTO_PART_KEY))
+ if (column_nr == 0 || (file->ha_table_flags() & HA_AUTO_PART_KEY))
auto_increment--; // Field is used
}
}
@@ -1364,7 +2819,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info,
else
{
my_error(ER_TOO_LONG_KEY,MYF(0),length);
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
}
}
}
@@ -1372,20 +2827,20 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info,
(column->length > length ||
!Field::type_can_have_key_part (sql_field->sql_type) ||
((f_is_packed(sql_field->pack_flag) ||
- ((file->table_flags() & HA_NO_PREFIX_CHAR_KEYS) &&
+ ((file->ha_table_flags() & HA_NO_PREFIX_CHAR_KEYS) &&
(key_info->flags & HA_NOSAME))) &&
column->length != length)))
{
my_message(ER_WRONG_SUB_KEY, ER(ER_WRONG_SUB_KEY), MYF(0));
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
}
- else if (!(file->table_flags() & HA_NO_PREFIX_CHAR_KEYS))
+ else if (!(file->ha_table_flags() & HA_NO_PREFIX_CHAR_KEYS))
length=column->length;
}
else if (length == 0)
{
my_error(ER_WRONG_KEY_COLUMN, MYF(0), column->field_name);
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
}
if (length > file->max_key_part_length() && key->type != Key::FULLTEXT)
{
@@ -1404,7 +2859,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info,
else
{
my_error(ER_TOO_LONG_KEY,MYF(0),length);
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
}
}
key_part_info->length=(uint16) length;
@@ -1433,7 +2888,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info,
{
my_message(ER_MULTIPLE_PRI_KEY, ER(ER_MULTIPLE_PRI_KEY),
MYF(0));
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
}
key_name=primary_key_name;
primary_key=1;
@@ -1444,7 +2899,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info,
if (check_if_keyname_exists(key_name, *key_info_buffer, key_info))
{
my_error(ER_DUP_KEYNAME, MYF(0), key_name);
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
}
key_info->name=(char*) key_name;
}
@@ -1452,7 +2907,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info,
if (!key_info->name || check_column_name(key_info->name))
{
my_error(ER_WRONG_NAME_FOR_INDEX, MYF(0), key_info->name);
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
}
if (!(key_info->flags & HA_NULL_PART_KEY))
unique_key=1;
@@ -1460,27 +2915,59 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info,
if (key_length > max_key_length && key->type != Key::FULLTEXT)
{
my_error(ER_TOO_LONG_KEY,MYF(0),max_key_length);
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
}
key_info++;
}
if (!unique_key && !primary_key &&
- (file->table_flags() & HA_REQUIRE_PRIMARY_KEY))
+ (file->ha_table_flags() & HA_REQUIRE_PRIMARY_KEY))
{
my_message(ER_REQUIRES_PRIMARY_KEY, ER(ER_REQUIRES_PRIMARY_KEY), MYF(0));
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
}
if (auto_increment > 0)
{
my_message(ER_WRONG_AUTO_KEY, ER(ER_WRONG_AUTO_KEY), MYF(0));
- DBUG_RETURN(-1);
+ DBUG_RETURN(TRUE);
}
/* Sort keys in optimized order */
- qsort((gptr) *key_info_buffer, *key_count, sizeof(KEY),
+ qsort((uchar*) *key_info_buffer, *key_count, sizeof(KEY),
(qsort_cmp) sort_keys);
create_info->null_bits= null_fields;
- DBUG_RETURN(0);
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Set table default charset, if not set
+
+ SYNOPSIS
+ set_table_default_charset()
+ create_info Table create information
+
+ DESCRIPTION
+ If the table character set was not given explicitely,
+ let's fetch the database default character set and
+ apply it to the table.
+*/
+
+static void set_table_default_charset(THD *thd,
+ HA_CREATE_INFO *create_info, char *db)
+{
+ /*
+ If the table character set was not given explicitly,
+ let's fetch the database default character set and
+ apply it to the table.
+ */
+ if (!create_info->default_table_charset)
+ {
+ HA_CREATE_INFO db_info;
+
+ load_db_opt_by_name(thd, db, &db_info);
+
+ create_info->default_table_charset= db_info.default_table_charset;
+ }
}
@@ -1497,7 +2984,7 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info,
In this case the error is given
*/
-static bool prepare_blob_field(THD *thd, create_field *sql_field)
+static bool prepare_blob_field(THD *thd, Create_field *sql_field)
{
DBUG_ENTER("prepare_blob_field");
@@ -1514,7 +3001,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",
@@ -1525,7 +3012,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);
@@ -1538,8 +3025,9 @@ static bool prepare_blob_field(THD *thd, create_field *sql_field)
/*
- Preparation of create_field for SP function return values.
- Based on code used in the inner loop of mysql_prepare_table() above
+ Preparation of Create_field for SP function return values.
+ Based on code used in the inner loop of mysql_prepare_create_table()
+ above.
SYNOPSIS
sp_prepare_create_field()
@@ -1551,13 +3039,13 @@ static bool prepare_blob_field(THD *thd, create_field *sql_field)
*/
-void sp_prepare_create_field(THD *thd, create_field *sql_field)
+void sp_prepare_create_field(THD *thd, Create_field *sql_field)
{
- if (sql_field->sql_type == FIELD_TYPE_SET ||
- sql_field->sql_type == FIELD_TYPE_ENUM)
+ if (sql_field->sql_type == MYSQL_TYPE_SET ||
+ sql_field->sql_type == MYSQL_TYPE_ENUM)
{
uint32 field_length, dummy;
- if (sql_field->sql_type == FIELD_TYPE_SET)
+ if (sql_field->sql_type == MYSQL_TYPE_SET)
{
calculate_interval_lengths(sql_field->charset,
sql_field->interval, &dummy,
@@ -1565,7 +3053,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,
@@ -1575,7 +3063,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;
@@ -1591,46 +3079,53 @@ void sp_prepare_create_field(THD *thd, create_field *sql_field)
Create a table
SYNOPSIS
- mysql_create_table()
- thd Thread object
- db Database
- table_name Table name
- create_info [in/out] Create information (like MAX_ROWS)
- alter_info [in/out] List of columns and indexes to create
- internal_tmp_table Set to 1 if this is an internal temporary table
- (From ALTER TABLE)
+ mysql_create_table_no_lock()
+ thd Thread object
+ db Database
+ table_name Table name
+ create_info Create information (like MAX_ROWS)
+ fields List of fields to create
+ keys List of keys to create
+ internal_tmp_table Set to 1 if this is an internal temporary table
+ (From ALTER TABLE)
+ select_field_count
DESCRIPTION
If one creates a temporary table, this is automatically opened
+ Note that this function assumes that caller already have taken
+ name-lock on table being created or used some other way to ensure
+ that concurrent operations won't intervene. mysql_create_table()
+ is a wrapper that can be used for this.
+
no_log is needed for the case of CREATE ... SELECT,
as the logging will be done later in sql_insert.cc
select_field_count is also used for CREATE ... SELECT,
and must be zero for standard create of table.
- Note that structures passed as 'create_info' and 'alter_info' parameters
- may be modified by this function. It is responsibility of the caller to
- make a copy of create_info in order to provide correct execution in
- prepared statements/stored routines.
-
RETURN VALUES
FALSE OK
TRUE error
*/
-bool mysql_create_table(THD *thd,const char *db, const char *table_name,
- HA_CREATE_INFO *create_info,
- Alter_info *alter_info,
- bool internal_tmp_table,
- uint select_field_count)
+bool mysql_create_table_no_lock(THD *thd,
+ const char *db, const char *table_name,
+ HA_CREATE_INFO *create_info,
+ Alter_info *alter_info,
+ bool internal_tmp_table,
+ uint select_field_count)
{
char path[FN_REFLEN];
+ uint path_length;
const char *alias;
uint db_options, key_count;
KEY *key_info_buffer;
handler *file;
bool error= TRUE;
- DBUG_ENTER("mysql_create_table");
+ DBUG_ENTER("mysql_create_table_no_lock");
+ DBUG_PRINT("enter", ("db: '%s' table: '%s' tmp: %d",
+ db, table_name, internal_tmp_table));
+
/* Check for duplicate fields and check type of table to create */
if (!alter_info->create_list.elements)
@@ -1639,77 +3134,209 @@ bool mysql_create_table(THD *thd,const char *db, const char *table_name,
MYF(0));
DBUG_RETURN(TRUE);
}
- if (check_engine(thd, table_name, &create_info->db_type))
+ if (check_engine(thd, table_name, create_info))
DBUG_RETURN(TRUE);
db_options= create_info->table_options;
if (create_info->row_type == ROW_TYPE_DYNAMIC)
db_options|=HA_OPTION_PACK_RECORD;
alias= table_case_name(create_info, table_name);
- file= get_new_handler((TABLE*) 0, thd->mem_root, create_info->db_type);
-
-#ifdef NOT_USED
- /*
- if there is a technical reason for a handler not to have support
- for temp. tables this code can be re-enabled.
- Otherwise, if a handler author has a wish to prohibit usage of
- temporary tables for his handler he should implement a check in
- ::create() method
- */
- if ((create_info->options & HA_LEX_CREATE_TMP_TABLE) &&
- (file->table_flags() & HA_NO_TEMP_TABLES))
+ if (!(file= get_new_handler((TABLE_SHARE*) 0, thd->mem_root,
+ create_info->db_type)))
{
- my_error(ER_ILLEGAL_HA, MYF(0), table_name);
+ mem_alloc_error(sizeof(handler));
DBUG_RETURN(TRUE);
}
-#endif
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ partition_info *part_info= thd->work_part_info;
- /*
- If the table character set was not given explicitely,
- let's fetch the database default character set and
- apply it to the table.
- */
- if (!create_info->default_table_charset)
+ if (!part_info && create_info->db_type->partition_flags &&
+ (create_info->db_type->partition_flags() & HA_USE_AUTO_PARTITION))
{
- HA_CREATE_INFO db_info;
-
- load_db_opt_by_name(thd, db, &db_info);
+ /*
+ Table is not defined as a partitioned table but the engine handles
+ all tables as partitioned. The handler will set up the partition info
+ object with the default settings.
+ */
+ thd->work_part_info= part_info= new partition_info();
+ if (!part_info)
+ {
+ mem_alloc_error(sizeof(partition_info));
+ DBUG_RETURN(TRUE);
+ }
+ file->set_auto_partitions(part_info);
+ part_info->default_engine_type= create_info->db_type;
+ part_info->is_auto_partitioned= TRUE;
+ }
+ if (part_info)
+ {
+ /*
+ The table has been specified as a partitioned table.
+ If this is part of an ALTER TABLE the handler will be the partition
+ handler but we need to specify the default handler to use for
+ partitions also in the call to check_partition_info. We transport
+ this information in the default_db_type variable, it is either
+ DB_TYPE_DEFAULT or the engine set in the ALTER TABLE command.
+
+ Check that we don't use foreign keys in the table since it won't
+ work even with InnoDB beneath it.
+ */
+ List_iterator<Key> key_iterator(alter_info->key_list);
+ Key *key;
+ handlerton *part_engine_type= create_info->db_type;
+ char *part_syntax_buf;
+ uint syntax_len;
+ handlerton *engine_type;
+ if (create_info->options & HA_LEX_CREATE_TMP_TABLE)
+ {
+ my_error(ER_PARTITION_NO_TEMPORARY, MYF(0));
+ goto err;
+ }
+ while ((key= key_iterator++))
+ {
+ if (key->type == Key::FOREIGN_KEY &&
+ !part_info->is_auto_partitioned)
+ {
+ my_error(ER_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
- if (mysql_prepare_table(thd, create_info, alter_info, internal_tmp_table,
- &db_options, file,
- &key_info_buffer, &key_count,
- select_field_count))
- DBUG_RETURN(TRUE);
+ set_table_default_charset(thd, create_info, (char*) db);
+
+ if (mysql_prepare_create_table(thd, create_info, alter_info,
+ internal_tmp_table,
+ &db_options, file,
+ &key_info_buffer, &key_count,
+ select_field_count))
+ goto err;
/* Check if table exists */
if (create_info->options & HA_LEX_CREATE_TMP_TABLE)
{
- set_tmp_file_path(path, sizeof(path), thd);
+ path_length= build_tmptable_filename(thd, path, sizeof(path));
create_info->table_options|=HA_CREATE_DELAY_KEY_WRITE;
}
else
{
- #ifdef FN_DEVCHAR
- /* check if the table name contains FN_DEVCHAR when defined */
- const char *start= alias;
- while (*start != '\0')
- {
- if (*start == FN_DEVCHAR)
- {
- my_error(ER_WRONG_TABLE_NAME, MYF(0), alias);
- DBUG_RETURN(TRUE);
- }
- start++;
- }
- #endif
- build_table_path(path, sizeof(path), db, alias, reg_ext);
+ #ifdef FN_DEVCHAR
+ /* check if the table name contains FN_DEVCHAR when defined */
+ if (strchr(alias, FN_DEVCHAR))
+ {
+ my_error(ER_WRONG_TABLE_NAME, MYF(0), alias);
+ DBUG_RETURN(TRUE);
+ }
+#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)
{
@@ -1717,29 +3344,35 @@ bool mysql_create_table(THD *thd,const char *db, const char *table_name,
push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
ER_TABLE_EXISTS_ERROR, ER(ER_TABLE_EXISTS_ERROR),
alias);
- DBUG_RETURN(FALSE);
+ error= 0;
+ goto err;
}
- DBUG_PRINT("info",("1"));
my_error(ER_TABLE_EXISTS_ERROR, MYF(0), alias);
- DBUG_RETURN(TRUE);
+ goto err;
}
+
VOID(pthread_mutex_lock(&LOCK_open));
if (!internal_tmp_table && !(create_info->options & HA_LEX_CREATE_TMP_TABLE))
{
- /*
- Inspecting table cache for placeholders created by concurrent
- CREATE TABLE ... SELECT statements to avoid interfering with them
- is 5.0-only solution. Starting from 5.1 we solve this problem by
- obtaining name-lock on the table to be created first.
- */
- if (table_cache_has_open_placeholder(thd, db, table_name) ||
- !access(path, F_OK))
+ if (!access(path,F_OK))
{
if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS)
goto warn;
- DBUG_PRINT("info",("2"));
my_error(ER_TABLE_EXISTS_ERROR,MYF(0),table_name);
- goto end;
+ goto unlock_and_end;
+ }
+ /*
+ We don't assert here, but check the result, because the table could be
+ in the table definition cache and in the same time the .frm could be
+ missing from the disk, in case of manual intervention which deletes
+ the .frm file. The user has to use FLUSH TABLES; to clear the cache.
+ Then she could create the table. This case is pretty obscure and
+ therefore we don't introduce a new error message only for it.
+ */
+ if (get_cached_table_share(db, alias))
+ {
+ my_error(ER_TABLE_EXISTS_ERROR, MYF(0), table_name);
+ goto unlock_and_end;
}
}
@@ -1769,12 +3402,12 @@ bool mysql_create_table(THD *thd,const char *db, const char *table_name,
if (create_if_not_exists)
goto warn;
my_error(ER_TABLE_EXISTS_ERROR,MYF(0),table_name);
- goto end;
+ goto unlock_and_end;
break;
default:
DBUG_PRINT("info", ("error: %u from storage engine", retcode));
my_error(retcode, MYF(0),table_name);
- goto end;
+ goto unlock_and_end;
}
}
@@ -1785,31 +3418,42 @@ 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;
+ path[path_length - reg_ext_length]= '\0'; // Remove .frm extension
if (rea_create_table(thd, path, db, table_name,
create_info, alter_info->create_list,
- key_count, key_info_buffer))
- goto end;
+ key_count, key_info_buffer, file))
+ goto unlock_and_end;
+
if (create_info->options & HA_LEX_CREATE_TMP_TABLE)
{
/* Open table and put in temporary table list */
if (!(open_temporary_table(thd, path, db, table_name, 1)))
{
(void) rm_temporary_table(create_info->db_type, path);
- goto end;
+ goto unlock_and_end;
}
- thd->tmp_table_used= 1;
+ thd->thread_specific_used= TRUE;
}
- if (!internal_tmp_table && mysql_bin_log.is_open())
- {
- thd->clear_error();
- Query_log_event qinfo(thd, thd->query, thd->query_length, FALSE, FALSE);
- 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:
@@ -1818,9 +3462,87 @@ warn:
ER_TABLE_EXISTS_ERROR, ER(ER_TABLE_EXISTS_ERROR),
alias);
create_info->table_existed= 1; // Mark that table existed
- goto end;
+ goto unlock_and_end;
}
+
+/*
+ Database and name-locking aware wrapper for mysql_create_table_no_lock(),
+*/
+
+bool mysql_create_table(THD *thd, const char *db, const char *table_name,
+ HA_CREATE_INFO *create_info,
+ Alter_info *alter_info,
+ bool internal_tmp_table,
+ uint select_field_count)
+{
+ TABLE *name_lock= 0;
+ bool result;
+ DBUG_ENTER("mysql_create_table");
+
+ /* Wait for any database locks */
+ pthread_mutex_lock(&LOCK_lock_db);
+ while (!thd->killed &&
+ hash_search(&lock_db_cache,(uchar*) db, strlen(db)))
+ {
+ wait_for_condition(thd, &LOCK_lock_db, &COND_refresh);
+ pthread_mutex_lock(&LOCK_lock_db);
+ }
+
+ if (thd->killed)
+ {
+ pthread_mutex_unlock(&LOCK_lock_db);
+ DBUG_RETURN(TRUE);
+ }
+ creating_table++;
+ pthread_mutex_unlock(&LOCK_lock_db);
+
+ if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE))
+ {
+ if (lock_table_name_if_not_cached(thd, db, table_name, &name_lock))
+ {
+ result= TRUE;
+ goto unlock;
+ }
+ if (!name_lock)
+ {
+ if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS)
+ {
+ push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
+ ER_TABLE_EXISTS_ERROR, ER(ER_TABLE_EXISTS_ERROR),
+ table_name);
+ create_info->table_existed= 1;
+ result= FALSE;
+ }
+ else
+ {
+ my_error(ER_TABLE_EXISTS_ERROR,MYF(0),table_name);
+ result= TRUE;
+ }
+ goto unlock;
+ }
+ }
+
+ result= mysql_create_table_no_lock(thd, db, table_name, create_info,
+ alter_info,
+ internal_tmp_table,
+ select_field_count);
+
+unlock:
+ if (name_lock)
+ {
+ pthread_mutex_lock(&LOCK_open);
+ unlink_open_table(thd, name_lock, FALSE);
+ pthread_mutex_unlock(&LOCK_open);
+ }
+ pthread_mutex_lock(&LOCK_lock_db);
+ if (!--creating_table && creating_database)
+ pthread_cond_signal(&COND_refresh);
+ pthread_mutex_unlock(&LOCK_lock_db);
+ DBUG_RETURN(result);
+}
+
+
/*
** Give the key name after the first field with an optional '_#' after
**/
@@ -1864,24 +3586,50 @@ make_unique_key_name(const char *field_name,KEY *start,KEY *end)
** Alter a table definition
****************************************************************************/
+
+/*
+ Rename a table.
+
+ SYNOPSIS
+ mysql_rename_table()
+ base The handlerton handle.
+ old_db The old database name.
+ old_name The old table name.
+ new_db The new database name.
+ new_name The new table name.
+ flags flags for build_table_filename().
+ FN_FROM_IS_TMP old_name is temporary.
+ FN_TO_IS_TMP new_name is temporary.
+ NO_FRM_RENAME Don't rename the FRM file
+ but only the table in the storage engine.
+
+ RETURN
+ FALSE OK
+ TRUE Error
+*/
+
bool
-mysql_rename_table(enum db_type base,
- const char *old_db,
- const char *old_name,
- const char *new_db,
- const char *new_name)
+mysql_rename_table(handlerton *base, const char *old_db,
+ const char *old_name, const char *new_db,
+ const char *new_name, uint flags)
{
THD *thd= current_thd;
char from[FN_REFLEN], to[FN_REFLEN], lc_from[FN_REFLEN], lc_to[FN_REFLEN];
char *from_base= from, *to_base= to;
char tmp_name[NAME_LEN+1];
- handler *file= (base == DB_TYPE_UNKNOWN ? 0 :
- get_new_handler((TABLE*) 0, thd->mem_root, base));
+ handler *file;
int error=0;
DBUG_ENTER("mysql_rename_table");
+ DBUG_PRINT("enter", ("old: '%s'.'%s' new: '%s'.'%s'",
+ old_db, old_name, new_db, new_name));
+
+ file= (base == NULL ? 0 :
+ get_new_handler((TABLE_SHARE*) 0, thd->mem_root, base));
- build_table_path(from, sizeof(from), old_db, old_name, "");
- build_table_path(to, sizeof(to), new_db, new_name, "");
+ build_table_filename(from, sizeof(from), old_db, old_name, "",
+ flags & FN_FROM_IS_TMP);
+ build_table_filename(to, sizeof(to), new_db, new_name, "",
+ flags & FN_TO_IS_TMP);
/*
If lower_case_table_names == 2 (case-preserving but case-insensitive
@@ -1889,22 +3637,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 */
@@ -1942,17 +3692,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;
}
@@ -1985,7 +3737,7 @@ void close_cached_table(THD *thd, TABLE *table)
thd->lock=0; // Start locked threads
}
/* Close all copies of 'table'. This also frees all LOCK TABLES lock */
- thd->open_tables=unlink_open_table(thd,thd->open_tables,table);
+ unlink_open_table(thd, table, TRUE);
/* When lock on LOCK_open is freed other threads can continue */
broadcast_refresh();
@@ -2023,23 +3775,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);
@@ -2074,11 +3825,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))
@@ -2086,12 +3841,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;
}
/*
@@ -2104,20 +3882,18 @@ 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. We assume fixed order of engine file name
extentions array. First element of engine file name extentions array
is meta/index file extention. Second element - data file extention.
*/
+ ext= table->file->bas_ext();
if (!ext[0] || !ext[1])
goto end; // No data file
- strxmov(from, table->s->path, ext[1], NullS); // Name of data file
+ // Name of data file
+ strxmov(from, table->s->normalized_path.str, ext[1], NullS);
if (!my_stat(from, &stat_info, MYF(0)))
goto end; // Can't use USE_FRM flag
@@ -2181,7 +3957,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);
}
@@ -2215,7 +3995,9 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
int result_code;
DBUG_ENTER("mysql_admin_table");
- field_list.push_back(item = new Item_empty_string("Table", NAME_LEN*2));
+ if (end_active_trans(thd))
+ DBUG_RETURN(1);
+ field_list.push_back(item = new Item_empty_string("Table", NAME_CHAR_LEN*2));
item->maybe_null = 1;
field_list.push_back(item = new Item_empty_string("Op", 10));
item->maybe_null = 1;
@@ -2237,7 +4019,6 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
strxmov(table_name, db, ".", table->table_name, NullS);
thd->open_options|= extra_open_options;
table->lock_type= lock_type;
-
/* open only one table from local list of command */
{
TABLE_LIST *save_next_global, *save_next_local;
@@ -2245,7 +4026,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
table->next_global= 0;
save_next_local= table->next_local;
table->next_local= 0;
- select->table_list.first= (byte*)table;
+ select->table_list.first= (uchar*)table;
/*
Time zone tables and SP tables can be add to lex->query_tables list,
so it have to be prepared.
@@ -2258,6 +4039,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
thd->no_warnings_for_error= no_warnings_for_error;
if (view_operator_func == NULL)
table->required_type=FRMTYPE_TABLE;
+
open_and_lock_tables(thd, table);
thd->no_warnings_for_error= 0;
table->next_global= save_next_global;
@@ -2268,6 +4050,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
@@ -2316,7 +4099,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;
@@ -2329,12 +4114,13 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
pthread_mutex_lock(&LOCK_open);
const char *old_message=thd->enter_cond(&COND_refresh, &LOCK_open,
"Waiting to get writelock");
- mysql_lock_abort(thd,table->table);
- remove_table_from_cache(thd, table->table->s->db,
- table->table->s->table_name,
+ mysql_lock_abort(thd,table->table, TRUE);
+ remove_table_from_cache(thd, table->table->s->db.str,
+ table->table->s->table_name.str,
RTFC_WAIT_OTHER_THREAD_FLAG |
RTFC_CHECK_KILLED_FLAG);
thd->exit_cond(old_message);
+ DBUG_EXECUTE_IF("wait_in_mysql_admin_table", wait_for_kill_signal(thd););
if (thd->killed)
goto err;
/* Flush entries in the query cache involving this table. */
@@ -2354,13 +4140,15 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
goto err;
}
- if (operator_func == &handler::ha_repair)
+ if (operator_func == &handler::ha_repair &&
+ !(check_opt->sql_flags & TT_USEFRM))
{
if ((table->table->file->check_old_types() == HA_ADMIN_NEEDS_ALTER) ||
(table->table->file->ha_check_for_upgrade(check_opt) ==
HA_ADMIN_NEEDS_ALTER))
{
my_bool save_no_send_ok= thd->net.no_send_ok;
+ ha_autocommit_or_rollback(thd, 1);
close_thread_tables(thd);
tmp_disable_binlog(thd); // binlogging is done by caller if wanted
thd->net.no_send_ok= TRUE;
@@ -2386,8 +4174,9 @@ send_result:
protocol->prepare_for_resend();
protocol->store(table_name, system_charset_info);
protocol->store((char*) operator_name, system_charset_info);
- protocol->store(warning_level_names[err->level],
- warning_level_length[err->level], system_charset_info);
+ protocol->store(warning_level_names[err->level].str,
+ warning_level_names[err->level].length,
+ system_charset_info);
protocol->store(err->msg, system_charset_info);
if (protocol->write())
goto err;
@@ -2466,6 +4255,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;
@@ -2475,10 +4265,11 @@ send_result_message:
result_code= mysql_recreate_table(thd, table);
thd->net.no_send_ok= save_no_send_ok;
reenable_binlog(thd);
+ ha_autocommit_or_rollback(thd, 0);
close_thread_tables(thd);
if (!result_code) // recreation went ok
{
- if ((table->table= open_ltable(thd, table, lock_type)) &&
+ if ((table->table= open_ltable(thd, table, lock_type, 0)) &&
((result_code= table->table->file->analyze(thd, check_opt)) > 0))
result_code= 0; // analyze went ok
}
@@ -2553,16 +4344,16 @@ send_result_message:
else
{
pthread_mutex_lock(&LOCK_open);
- remove_table_from_cache(thd, table->table->s->db,
- table->table->s->table_name, RTFC_NO_FLAG);
+ remove_table_from_cache(thd, table->table->s->db.str,
+ table->table->s->table_name.str, RTFC_NO_FLAG);
pthread_mutex_unlock(&LOCK_open);
}
/* May be something modified consequently we have to invalidate cache */
query_cache_invalidate3(thd, table->table, 0);
}
}
+ ha_autocommit_or_rollback(thd, 0);
close_thread_tables(thd);
- lex->reset_query_tables_list(FALSE);
table->table=0; // For query cache
if (protocol->write())
goto err;
@@ -2570,7 +4361,9 @@ send_result_message:
send_eof(thd);
DBUG_RETURN(FALSE);
+
err:
+ ha_autocommit_or_rollback(thd, 1);
close_thread_tables(thd); // Shouldn't be needed
if (table)
table->table=0;
@@ -2581,6 +4374,8 @@ send_result_message:
bool mysql_backup_table(THD* thd, TABLE_LIST* table_list)
{
DBUG_ENTER("mysql_backup_table");
+ WARN_DEPRECATED(thd, "5.2", "BACKUP TABLE",
+ "MySQL Administrator (mysqldump, mysql)");
DBUG_RETURN(mysql_admin_table(thd, table_list, 0,
"backup", TL_READ, 0, 0, 0, 0,
&handler::backup, 0));
@@ -2590,6 +4385,8 @@ bool mysql_backup_table(THD* thd, TABLE_LIST* table_list)
bool mysql_restore_table(THD* thd, TABLE_LIST* table_list)
{
DBUG_ENTER("mysql_restore_table");
+ WARN_DEPRECATED(thd, "5.2", "RESTORE TABLE",
+ "MySQL Administrator (mysqldump, mysql)");
DBUG_RETURN(mysql_admin_table(thd, table_list, 0,
"restore", TL_WRITE, 1, 1, 0,
&prepare_for_restore,
@@ -2709,8 +4506,13 @@ int reassign_keycache_tables(THD *thd, KEY_CACHE *src_cache,
bool mysql_preload_keys(THD* thd, TABLE_LIST* tables)
{
DBUG_ENTER("mysql_preload_keys");
+ /*
+ We cannot allow concurrent inserts. The storage engine reads
+ directly from the index file, bypassing the cache. It could read
+ outdated information if parallel inserts into cache blocks happen.
+ */
DBUG_RETURN(mysql_admin_table(thd, tables, 0,
- "preload_keys", TL_READ, 0, 0, 0, 0,
+ "preload_keys", TL_READ_NO_INSERT, 0, 0, 0, 0,
&handler::preload_keys, 0));
}
@@ -2724,101 +4526,81 @@ bool mysql_preload_keys(THD* thd, TABLE_LIST* tables)
table Table list element for target table
src_table Table list element for source table
create_info Create info
- table_ident Src table_ident
RETURN VALUES
FALSE OK
TRUE error
*/
-bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST *src_table,
+bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table,
HA_CREATE_INFO *create_info)
{
- TABLE **tmp_table;
+ TABLE *name_lock= 0;
char src_path[FN_REFLEN], dst_path[FN_REFLEN];
+ uint dst_path_length;
char *db= table->db;
char *table_name= table->table_name;
int err;
bool res= TRUE;
- db_type not_used;
+ uint not_used;
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ char tmp_path[FN_REFLEN];
+#endif
+ char ts_name[FN_LEN];
DBUG_ENTER("mysql_create_like_table");
- /*
- By taking name-lock on the source table and holding LOCK_open mutex we
- ensure that no concurrent DDL operation will mess with this table. Note
- that holding only name-lock is not enough for this, because it won't block
- other DDL statements that only take name-locks on the table and don't
- open it (simple name-locks are not exclusive between each other).
-
- Unfortunately, simply opening this table is not enough for our purproses,
- since in 5.0 ALTER TABLE may change .FRM files on disk even if there are
- connections that still have old version of table open. This 'optimization'
- was removed in 5.1 so there we open the source table instead of taking
- name-lock on it.
-
- We also have to acquire LOCK_open to make copying of .frm file, call to
- ha_create_table() and binlogging atomic against concurrent DML and DDL
- operations on the target table.
- */
- if (lock_and_wait_for_table_name(thd, src_table))
- goto err;
- pthread_mutex_lock(&LOCK_open);
+ /* CREATE TABLE ... LIKE is not allowed for views. */
+ src_table->required_type= FRMTYPE_TABLE;
- if ((tmp_table= find_temporary_table(thd, src_table->db,
- src_table->table_name)))
- strxmov(src_path, (*tmp_table)->s->path, reg_ext, NullS);
- else
- {
- strxmov(src_path, mysql_data_home, "/", src_table->db, "/",
- src_table->table_name, reg_ext, NullS);
- /* Resolve symlinks (for windows) */
- fn_format(src_path, src_path, "", "", MYF(MY_UNPACK_FILENAME));
- if (access(src_path, F_OK))
- {
- my_error(ER_BAD_TABLE_ERROR, MYF(0), src_table->table_name);
- goto err;
- }
- }
+ /*
+ By opening source table we guarantee that it exists and no concurrent
+ DDL operation will mess with it. Later we also take an exclusive
+ name-lock on target table name, which makes copying of .frm file,
+ call to ha_create_table() and binlogging atomic against concurrent DML
+ and DDL operations on target table. Thus by holding both these "locks"
+ we ensure that our statement is properly isolated from all concurrent
+ operations which matter.
+ */
+ if (open_tables(thd, &src_table, &not_used, 0))
+ DBUG_RETURN(TRUE);
- /*
- create like should be not allowed for Views, Triggers, ...
+ /*
+ For bug#25875, Newly created table through CREATE TABLE .. LIKE
+ has no ndb_dd attributes;
+ Add something to get possible tablespace info from src table,
+ it can get valid tablespace name only for disk-base ndb table
*/
- if (mysql_frm_type(thd, src_path, &not_used) != FRMTYPE_TABLE)
+ if ((src_table->table->file->get_tablespace_name(thd, ts_name, FN_LEN)))
{
- my_error(ER_WRONG_OBJECT, MYF(0), src_table->db, src_table->table_name,
- "BASE TABLE");
- goto err;
+ create_info->tablespace= ts_name;
+ create_info->storage_media= HA_SM_DISK;
}
+ strxmov(src_path, src_table->table->s->path.str, reg_ext, NullS);
+
DBUG_EXECUTE_IF("sleep_create_like_before_check_if_exists", my_sleep(6000000););
/*
- Validate the destination table
-
- skip the destination table name checking as this is already
- validated.
+ Check that destination tables does not exist. Note that its name
+ was already checked when it was added to the table list.
*/
if (create_info->options & HA_LEX_CREATE_TMP_TABLE)
{
if (find_temporary_table(thd, db, table_name))
goto table_exists;
- set_tmp_file_path(dst_path, sizeof(dst_path), thd);
+ dst_path_length= build_tmptable_filename(thd, dst_path, sizeof(dst_path));
create_info->table_options|= HA_CREATE_DELAY_KEY_WRITE;
}
else
{
- strxmov(dst_path, mysql_data_home, "/", db, "/", table_name,
- reg_ext, NullS);
- fn_format(dst_path, dst_path, "", "", MYF(MY_UNPACK_FILENAME));
-
- /*
- Note that starting from 5.1 we obtain name-lock on target
- table instead of inspecting table cache for presence
- of open placeholders (see comment in mysql_create_table()).
- */
- if (table_cache_has_open_placeholder(thd, db, table_name) ||
- !access(dst_path, F_OK))
+ if (lock_table_name_if_not_cached(thd, db, table_name, &name_lock))
+ goto err;
+ if (!name_lock)
+ goto table_exists;
+ dst_path_length= build_table_filename(dst_path, sizeof(dst_path),
+ db, table_name, reg_ext, 0);
+ if (!access(dst_path, F_OK))
goto table_exists;
}
@@ -2826,27 +4608,54 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST *src_table,
/*
Create a new table by copying from source table
+
+ Altough exclusive name-lock on target table protects us from concurrent
+ DML and DDL operations on it we still want to wrap .FRM creation and call
+ to ha_create_table() in critical section protected by LOCK_open in order
+ to provide minimal atomicity against operations which disregard name-locks,
+ like I_S implementation, for example. This is a temporary and should not
+ be copied. Instead we should fix our code to always honor name-locks.
+
+ Also some engines (e.g. NDB cluster) require that LOCK_open should be held
+ during the call to ha_create_table(). See bug #28614 for more info.
*/
+ VOID(pthread_mutex_lock(&LOCK_open));
if (my_copy(src_path, dst_path, MYF(MY_DONT_OVERWRITE_FILE)))
{
if (my_errno == ENOENT)
my_error(ER_BAD_DB_ERROR,MYF(0),db);
else
my_error(ER_CANT_CREATE_FILE,MYF(0),dst_path,my_errno);
+ VOID(pthread_mutex_unlock(&LOCK_open));
goto err;
}
- DBUG_EXECUTE_IF("sleep_create_like_before_ha_create", my_sleep(6000000););
-
/*
As mysql_truncate don't work on a new table at this stage of
creation, instead create the table directly (for both normal
and temporary tables).
*/
- *fn_ext(dst_path)= 0;
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ /*
+ For partitioned tables we need to copy the .par file as well since
+ it is used in open_table_def to even be able to create a new handler.
+ There is no way to find out here if the original table is a
+ partitioned table so we copy the file and ignore any errors.
+ */
+ fn_format(tmp_path, dst_path, reg_ext, ".par", MYF(MY_REPLACE_EXT));
+ strmov(dst_path, tmp_path);
+ fn_format(tmp_path, src_path, reg_ext, ".par", MYF(MY_REPLACE_EXT));
+ strmov(src_path, tmp_path);
+ my_copy(src_path, dst_path, MYF(MY_DONT_OVERWRITE_FILE));
+#endif
+
+ DBUG_EXECUTE_IF("sleep_create_like_before_ha_create", my_sleep(6000000););
+
+ dst_path[dst_path_length - reg_ext_length]= '\0'; // Remove .frm
if (thd->variables.keep_files_on_create)
create_info->options|= HA_CREATE_KEEP_FILES;
- err= ha_create_table(dst_path, create_info, 1);
+ err= ha_create_table(thd, dst_path, db, table_name, create_info, 1);
+ VOID(pthread_mutex_unlock(&LOCK_open));
if (create_info->options & HA_LEX_CREATE_TMP_TABLE)
{
@@ -2860,19 +4669,71 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST *src_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 */
}
DBUG_EXECUTE_IF("sleep_create_like_before_binlogging", my_sleep(6000000););
- // Must be written before unlock
- if (mysql_bin_log.is_open())
+ /*
+ We have to write the query before we unlock the tables.
+ */
+ if (thd->current_stmt_binlog_row_based)
{
- thd->clear_error();
- Query_log_event qinfo(thd, thd->query, thd->query_length, FALSE, FALSE);
- mysql_bin_log.write(&qinfo);
+ /*
+ Since temporary tables are not replicated under row-based
+ replication, CREATE TABLE ... LIKE ... needs special
+ treatement. We have four cases to consider, according to the
+ following decision table:
+
+ ==== ========= ========= ==============================
+ Case Target Source Write to binary log
+ ==== ========= ========= ==============================
+ 1 normal normal Original statement
+ 2 normal temporary Generated statement
+ 3 temporary normal Nothing
+ 4 temporary temporary Nothing
+ ==== ========= ========= ==============================
+ */
+ if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE))
+ {
+ if (src_table->table->s->tmp_table) // Case 2
+ {
+ char buf[2048];
+ String query(buf, sizeof(buf), system_charset_info);
+ query.length(0); // Have to zero it since constructor doesn't
+
+ /*
+ Here we open the destination table, on which we already have
+ name-lock. This is needed for store_create_info() to work.
+ The table will be closed by unlink_open_table() at the end
+ of this function.
+ */
+ table->table= name_lock;
+ VOID(pthread_mutex_lock(&LOCK_open));
+ if (reopen_name_locked_table(thd, table, FALSE))
+ {
+ VOID(pthread_mutex_unlock(&LOCK_open));
+ goto err;
+ }
+ VOID(pthread_mutex_unlock(&LOCK_open));
+
+ IF_DBUG(int result=) store_create_info(thd, table, &query,
+ create_info);
+
+ 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;
@@ -2890,19 +4751,19 @@ table_exists:
my_error(ER_TABLE_EXISTS_ERROR, MYF(0), table_name);
err:
- unlock_table_name(thd, src_table);
- pthread_mutex_unlock(&LOCK_open);
+ if (name_lock)
+ {
+ pthread_mutex_lock(&LOCK_open);
+ unlink_open_table(thd, name_lock, FALSE);
+ pthread_mutex_unlock(&LOCK_open);
+ }
DBUG_RETURN(res);
}
bool mysql_analyze_table(THD* thd, TABLE_LIST* tables, HA_CHECK_OPT* check_opt)
{
-#ifdef OS2
- thr_lock_type lock_type = TL_WRITE;
-#else
thr_lock_type lock_type = TL_READ_NO_INSERT;
-#endif
DBUG_ENTER("mysql_analyze_table");
DBUG_RETURN(mysql_admin_table(thd, tables, check_opt,
@@ -2913,11 +4774,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,
@@ -2952,7 +4809,7 @@ mysql_discard_or_import_tablespace(THD *thd,
not complain when we lock the table
*/
thd->tablespace_op= TRUE;
- if (!(table=open_ltable(thd,table_list,TL_WRITE)))
+ if (!(table=open_ltable(thd, table_list, TL_WRITE, 0)))
{
thd->tablespace_op=FALSE;
DBUG_RETURN(-1);
@@ -2977,12 +4834,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;
@@ -2999,352 +4854,427 @@ err:
/*
- Manages enabling/disabling of indexes for ALTER TABLE
-
SYNOPSIS
- alter_table_manage_keys()
- table Target table
- indexes_were_disabled Whether the indexes of the from table
- were disabled
- keys_onoff ENABLE | DISABLE | LEAVE_AS_IS
+ compare_tables()
+ table The original table.
+ alter_info Alter options, fields and keys for the new
+ table.
+ create_info Create options for the new table.
+ order_num Number of order list elements.
+ need_copy_table OUT Result of the comparison. Undefined if error.
+ Otherwise is one of:
+ ALTER_TABLE_METADATA_ONLY No copy needed
+ ALTER_TABLE_DATA_CHANGED Data changes,
+ copy needed
+ ALTER_TABLE_INDEX_CHANGED Index changes,
+ copy might be needed
+ key_info_buffer OUT An array of KEY structs for new indexes
+ index_drop_buffer OUT An array of offsets into table->key_info.
+ index_drop_count OUT The number of elements in the array.
+ index_add_buffer OUT An array of offsets into key_info_buffer.
+ index_add_count OUT The number of elements in the array.
+
+ DESCRIPTION
+ 'table' (first argument) contains information of the original
+ table, which includes all corresponding parts that the new
+ table has in arguments create_list, key_list and create_info.
+
+ By comparing the changes between the original and new table
+ we can determine how much it has changed after ALTER TABLE
+ and whether we need to make a copy of the table, or just change
+ the .frm file.
+
+ If there are no data changes, but index changes, 'index_drop_buffer'
+ and/or 'index_add_buffer' are populated with offsets into
+ table->key_info or key_info_buffer respectively for the indexes
+ that need to be dropped and/or (re-)created.
RETURN VALUES
- FALSE OK
- TRUE Error
+ TRUE error
+ FALSE success
*/
static
-bool alter_table_manage_keys(TABLE *table, int indexes_were_disabled,
- enum enum_enable_or_disable keys_onoff)
+bool
+compare_tables(TABLE *table,
+ Alter_info *alter_info,
+ HA_CREATE_INFO *create_info,
+ uint order_num,
+ enum_alter_table_change_level *need_copy_table,
+ KEY **key_info_buffer,
+ uint **index_drop_buffer, uint *index_drop_count,
+ uint **index_add_buffer, uint *index_add_count)
{
- int error= 0;
- DBUG_ENTER("alter_table_manage_keys");
- DBUG_PRINT("enter", ("table=%p were_disabled=%d on_off=%d",
- table, indexes_were_disabled, keys_onoff));
+ Field **f_ptr, *field;
+ uint changes= 0, tmp;
+ uint key_count;
+ List_iterator_fast<Create_field> new_field_it(alter_info->create_list);
+ Create_field *new_field;
+ KEY_PART_INFO *key_part;
+ KEY_PART_INFO *end;
+ /*
+ Remember if the new definition has new VARCHAR column;
+ create_info->varchar will be reset in mysql_prepare_create_table.
+ */
+ bool varchar= create_info->varchar;
+ DBUG_ENTER("compare_tables");
- switch (keys_onoff) {
- case ENABLE:
- error= table->file->enable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE);
- break;
- case LEAVE_AS_IS:
- if (!indexes_were_disabled)
- break;
- /* fall-through: disabled indexes */
- case DISABLE:
- error= table->file->disable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE);
+ {
+ THD *thd= table->in_use;
+ /*
+ Create a copy of alter_info.
+ To compare the new and old table definitions, we need to "prepare"
+ the new definition - transform it from parser output to a format
+ that describes the final table layout (all column defaults are
+ initialized, duplicate columns are removed). This is done by
+ mysql_prepare_create_table. Unfortunately,
+ mysql_prepare_create_table performs its transformations
+ "in-place", that is, modifies the argument. Since we would
+ like to keep compare_tables() idempotent (not altering any
+ of the arguments) we create a copy of alter_info here and
+ pass it to mysql_prepare_create_table, then use the result
+ to evaluate possibility of fast ALTER TABLE, and then
+ destroy the copy.
+ */
+ Alter_info tmp_alter_info(*alter_info, thd->mem_root);
+ uint db_options= 0; /* not used */
+ /* Create the prepared information. */
+ if (mysql_prepare_create_table(thd, create_info,
+ &tmp_alter_info,
+ (table->s->tmp_table != NO_TMP_TABLE),
+ &db_options,
+ table->file, key_info_buffer,
+ &key_count, 0))
+ DBUG_RETURN(1);
+ /* Allocate result buffers. */
+ if (! (*index_drop_buffer=
+ (uint*) thd->alloc(sizeof(uint) * table->s->keys)) ||
+ ! (*index_add_buffer=
+ (uint*) thd->alloc(sizeof(uint) * tmp_alter_info.key_list.elements)))
+ DBUG_RETURN(1);
}
+ /*
+ Some very basic checks. If number of fields changes, or the
+ handler, we need to run full ALTER TABLE. In the future
+ new fields can be added and old dropped without copy, but
+ not yet.
- if (error == HA_ERR_WRONG_COMMAND)
+ 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 != alter_info->create_list.elements ||
+ table->s->db_type() != create_info->db_type ||
+ table->s->tmp_table ||
+ create_info->used_fields & HA_CREATE_USED_ENGINE ||
+ create_info->used_fields & HA_CREATE_USED_CHARSET ||
+ create_info->used_fields & HA_CREATE_USED_DEFAULT_CHARSET ||
+ (alter_info->flags & (ALTER_RECREATE | ALTER_FOREIGN_KEY)) ||
+ order_num ||
+ !table->s->mysql_version ||
+ (table->s->frm_version < FRM_VER_TRUE_VARCHAR && varchar))
{
- push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
- ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA), table->s->table_name);
- error= 0;
- } else if (error)
- table->file->print_error(error, MYF(0));
+ *need_copy_table= ALTER_TABLE_DATA_CHANGED;
+ DBUG_RETURN(0);
+ }
- DBUG_RETURN(error);
-}
+ /*
+ 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))
+ {
+ *need_copy_table= ALTER_TABLE_DATA_CHANGED;
+ DBUG_RETURN(0);
+ }
-/*
- Alter table
+ /* 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;
- NOTE
- The structures passed as 'create_info' and 'alter_info' parameters may
- be modified by this function. It is responsibility of the caller to make
- a copy of create_info in order to provide correct execution in prepared
- statements/stored routines.
-*/
+ /* Evaluate changes bitmap and send to check_if_incompatible_data() */
+ if (!(tmp= field->is_equal(new_field)))
+ {
+ *need_copy_table= ALTER_TABLE_DATA_CHANGED;
+ DBUG_RETURN(0);
+ }
+ // Clear indexed marker
+ field->flags&= ~FIELD_IN_ADD_INDEX;
+ changes|= tmp;
+ }
-bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
- HA_CREATE_INFO *create_info,
- TABLE_LIST *table_list,
- Alter_info *alter_info,
- uint order_num, ORDER *order, bool ignore)
-{
- TABLE *table,*new_table=0;
- int error= 0;
- char tmp_name[80],old_name[32],new_name_buff[FN_REFLEN];
- char new_alias_buff[FN_REFLEN], *table_name, *db, *new_alias, *alias;
- char index_file[FN_REFLEN], data_file[FN_REFLEN];
- ha_rows copied,deleted;
- ulonglong next_insert_id;
- uint db_create_options, used_fields;
- enum db_type old_db_type, new_db_type, table_type;
- bool need_copy_table;
- bool no_table_reopen= FALSE, varchar= FALSE;
- frm_type_enum frm_type;
/*
- Throw an error if the table to be altered isn't empty.
- Used in DATE/DATETIME fields default value checking.
+ Go through keys and check if the original ones are compatible
+ with new table.
*/
- bool error_if_not_empty= FALSE;
+ 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));
/*
- A field used for error reporting in DATE/DATETIME fields default
- value checking.
+ Step through all keys of the old table and search matching new keys.
*/
- create_field *new_datetime_field= 0;
- DBUG_ENTER("mysql_alter_table");
-
- thd->proc_info="init";
- table_name=table_list->table_name;
- alias= (lower_case_table_names == 2) ? table_list->alias : table_name;
-
- db=table_list->db;
- if (!new_db || !my_strcasecmp(table_alias_charset, new_db, db))
- new_db= db;
- 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 */
- if (alter_info->tablespace_op != NO_TABLESPACE_OP)
- /* Conditionally writes to binlog. */
- DBUG_RETURN(mysql_discard_or_import_tablespace(thd,table_list,
- alter_info->tablespace_op));
- sprintf(new_name_buff,"%s/%s/%s%s",mysql_data_home, db, table_name, reg_ext);
- unpack_filename(new_name_buff, new_name_buff);
- frm_type= mysql_frm_type(thd, new_name_buff, &table_type);
- /* Rename a view */
- if (frm_type == FRMTYPE_VIEW && !(alter_info->flags & ~ALTER_RENAME))
+ *index_drop_count= 0;
+ *index_add_count= 0;
+ for (table_key= table->key_info; table_key < table_key_end; table_key++)
{
- /*
- Avoid problems with a rename on a table that we have locked or
- if the user is trying to to do this in a transcation context
- */
+ KEY_PART_INFO *table_part;
+ KEY_PART_INFO *table_part_end= table_key->key_part + table_key->key_parts;
+ KEY_PART_INFO *new_part;
- if (thd->locked_tables || thd->active_transaction())
+ /* Search a new key with the same name. */
+ for (new_key= *key_info_buffer; new_key < new_key_end; new_key++)
{
- my_message(ER_LOCK_OR_ACTIVE_TRANSACTION,
- ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0));
- DBUG_RETURN(1);
+ 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;
}
- if (wait_if_global_read_lock(thd,0,1))
- DBUG_RETURN(1);
- VOID(pthread_mutex_lock(&LOCK_open));
- if (lock_table_names(thd, table_list))
+ /* 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++)
{
- error= 1;
- goto view_err;
+ /*
+ 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;
}
-
- if (!do_rename(thd, table_list, new_db, new_name, new_name, 1))
+ 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++)
{
- if (mysql_bin_log.is_open())
- {
- thd->clear_error();
- Query_log_event qinfo(thd, thd->query, thd->query_length, 0, FALSE);
- mysql_bin_log.write(&qinfo);
- }
- send_ok(thd);
+ // Mark field to be part of new key
+ field= table->field[key_part->fieldnr];
+ field->flags|= FIELD_IN_ADD_INDEX;
}
-
- unlock_table_names(thd, table_list, (TABLE_LIST*) 0);
-
-view_err:
- pthread_mutex_unlock(&LOCK_open);
- start_waiting_global_read_lock(thd);
- DBUG_RETURN(error);
+ DBUG_PRINT("info", ("index changed: '%s'", table_key->name));
}
- if (!(table=open_ltable(thd,table_list,TL_WRITE_ALLOW_READ)))
- DBUG_RETURN(TRUE);
+ /*end of for (; table_key < table_key_end;) */
- /* Check that we are not trying to rename to an existing table */
- if (new_name)
+ /*
+ Step through all keys of the new table and find matching old keys.
+ */
+ for (new_key= *key_info_buffer; new_key < new_key_end; new_key++)
{
- strmov(new_name_buff,new_name);
- strmov(new_alias= new_alias_buff, new_name);
- if (lower_case_table_names)
+ /* Search an old key with the same name. */
+ for (table_key= table->key_info; table_key < table_key_end; table_key++)
{
- if (lower_case_table_names != 2)
- {
- my_casedn_str(files_charset_info, new_name_buff);
- new_alias= new_name; // Create lower case table name
- }
- my_casedn_str(files_charset_info, new_name);
- }
- if (new_db == db &&
- !my_strcasecmp(table_alias_charset, new_name_buff, table_name))
- {
- /*
- Source and destination table names are equal: make later check
- easier.
- */
- new_alias= new_name= table_name;
+ if (! strcmp(table_key->name, new_key->name))
+ break;
}
- else
+ if (table_key >= table_key_end)
{
- if (table->s->tmp_table)
+ /* Key not found. Add the offset of the key to the add buffer. */
+ (*index_add_buffer)[(*index_add_count)++]= new_key - *key_info_buffer;
+ key_part= new_key->key_part;
+ end= key_part + new_key->key_parts;
+ for(; key_part != end; key_part++)
{
- if (find_temporary_table(thd,new_db,new_name_buff))
- {
- my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_name_buff);
- DBUG_RETURN(TRUE);
- }
- }
- else
- {
- char dir_buff[FN_REFLEN];
- bool exists;
- strxnmov(dir_buff, FN_REFLEN, mysql_real_data_home, new_db, NullS);
- VOID(pthread_mutex_lock(&LOCK_open));
- exists= (table_cache_has_open_placeholder(thd, new_db, new_name) ||
- !access(fn_format(new_name_buff, new_name_buff, dir_buff,
- reg_ext, 0), F_OK));
- VOID(pthread_mutex_unlock(&LOCK_open));
- if (exists)
- {
- /* Table will be closed in do_command() */
- my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_alias);
- DBUG_RETURN(TRUE);
- }
+ // Mark field to be part of new key
+ field= table->field[key_part->fieldnr];
+ field->flags|= FIELD_IN_ADD_INDEX;
}
+ DBUG_PRINT("info", ("index added: '%s'", new_key->name));
}
}
- else
+
+ /* Check if changes are compatible with current handler without a copy */
+ if (table->file->check_if_incompatible_data(create_info, changes))
{
- new_alias= (lower_case_table_names == 2) ? alias : table_name;
- new_name= table_name;
+ *need_copy_table= ALTER_TABLE_DATA_CHANGED;
+ DBUG_RETURN(0);
}
- old_db_type= table->s->db_type;
- if (create_info->db_type == DB_TYPE_DEFAULT)
- create_info->db_type= old_db_type;
- if (check_engine(thd, new_name, &create_info->db_type))
- DBUG_RETURN(TRUE);
- new_db_type= create_info->db_type;
- if (create_info->row_type == ROW_TYPE_NOT_USED)
- create_info->row_type= table->s->row_type;
-
- DBUG_PRINT("info", ("old type: %d new type: %d", old_db_type, new_db_type));
- if (ha_check_storage_engine_flag(old_db_type, HTON_ALTER_NOT_SUPPORTED) ||
- ha_check_storage_engine_flag(new_db_type, HTON_ALTER_NOT_SUPPORTED))
+ if (*index_drop_count || *index_add_count)
{
- DBUG_PRINT("info", ("doesn't support alter"));
- my_error(ER_ILLEGAL_HA, MYF(0), table_name);
- DBUG_RETURN(TRUE);
+ *need_copy_table= ALTER_TABLE_INDEX_CHANGED;
+ DBUG_RETURN(0);
}
-
- thd->proc_info="setup";
- if (!(alter_info->flags & ~(ALTER_RENAME | ALTER_KEYS_ONOFF)) &&
- !table->s->tmp_table) // no need to touch frm
- {
- switch (alter_info->keys_onoff) {
- case LEAVE_AS_IS:
- break;
- case ENABLE:
- /*
- wait_while_table_is_used() ensures that table being altered is
- opened only by this thread and that TABLE::TABLE_SHARE::version
- of TABLE object corresponding to this table is 0.
- The latter guarantees that no DML statement will open this table
- until ALTER TABLE finishes (i.e. until close_thread_tables())
- while the fact that the table is still open gives us protection
- from concurrent DDL statements.
- */
- VOID(pthread_mutex_lock(&LOCK_open));
- wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN);
- VOID(pthread_mutex_unlock(&LOCK_open));
- error= table->file->enable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE);
- /* COND_refresh will be signaled in close_thread_tables() */
- break;
- case DISABLE:
- VOID(pthread_mutex_lock(&LOCK_open));
- wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN);
- VOID(pthread_mutex_unlock(&LOCK_open));
- error=table->file->disable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE);
- /* COND_refresh will be signaled in close_thread_tables() */
- break;
- }
- if (error == HA_ERR_WRONG_COMMAND)
- {
- error= 0;
- push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
- ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA),
- table->alias);
- }
- VOID(pthread_mutex_lock(&LOCK_open));
- /*
- Unlike to the above case close_cached_table() below will remove ALL
- instances of TABLE from table cache (it will also remove table lock
- held by this thread). So to make actual table renaming and writing
- to binlog atomic we have to put them into the same critical section
- protected by LOCK_open mutex. This also removes gap for races between
- access() and mysql_rename_table() calls.
- */
+ *need_copy_table= ALTER_TABLE_METADATA_ONLY; // Tables are compatible
+ DBUG_RETURN(0);
+}
- if (!error && (new_name != table_name || new_db != db))
- {
- thd->proc_info="rename";
- /*
- Then do a 'simple' rename of the table. First we need to close all
- instances of 'source' table.
- */
- close_cached_table(thd, table);
- /*
- Then, we want check once again that target table does not exist.
- Note that we can't fully rely on results of previous check since
- no lock was taken on target table during it. We also can't do this
- before calling close_cached_table() as the latter temporarily
- releases LOCK_open mutex.
- Also note that starting from 5.1 we use approach with obtaining
- of name-lock on target table.
- */
- if (table_cache_has_open_placeholder(thd, new_db, new_name) ||
- !access(new_name_buff,F_OK))
- {
- my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_name);
- error= -1;
- }
- else
- {
- *fn_ext(new_name)=0;
- if (mysql_rename_table(old_db_type,db,table_name,new_db,new_alias))
- error= -1;
- else if (Table_triggers_list::change_table_name(thd, db, table_name,
- new_db, new_alias))
- {
- VOID(mysql_rename_table(old_db_type, new_db, new_alias, db,
- table_name));
- error= -1;
- }
- }
- }
- if (error == HA_ERR_WRONG_COMMAND)
- {
- error= 0;
- push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
- ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA),
- table->alias);
- }
+/*
+ Manages enabling/disabling of indexes for ALTER TABLE
- if (!error)
- {
- if (mysql_bin_log.is_open())
- {
- thd->clear_error();
- Query_log_event qinfo(thd, thd->query, thd->query_length, FALSE, FALSE);
- mysql_bin_log.write(&qinfo);
- }
- send_ok(thd);
- }
- else if (error > 0)
- {
- table->file->print_error(error, MYF(0));
- error= -1;
- }
- VOID(pthread_mutex_unlock(&LOCK_open));
- table_list->table= NULL; // For query cache
- query_cache_invalidate3(thd, table_list, 0);
- DBUG_RETURN(error);
+ SYNOPSIS
+ alter_table_manage_keys()
+ table Target table
+ indexes_were_disabled Whether the indexes of the from table
+ were disabled
+ keys_onoff ENABLE | DISABLE | LEAVE_AS_IS
+
+ RETURN VALUES
+ FALSE OK
+ TRUE Error
+*/
+
+static
+bool alter_table_manage_keys(TABLE *table, int indexes_were_disabled,
+ enum enum_enable_or_disable keys_onoff)
+{
+ int error= 0;
+ DBUG_ENTER("alter_table_manage_keys");
+ DBUG_PRINT("enter", ("table=%p were_disabled=%d on_off=%d",
+ table, indexes_were_disabled, keys_onoff));
+
+ switch (keys_onoff) {
+ case ENABLE:
+ error= table->file->enable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE);
+ break;
+ case LEAVE_AS_IS:
+ if (!indexes_were_disabled)
+ break;
+ /* fall-through: disabled indexes */
+ case DISABLE:
+ error= table->file->disable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE);
}
- /* Full alter table */
+ if (error == HA_ERR_WRONG_COMMAND)
+ {
+ push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
+ ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA), table->s->table_name);
+ error= 0;
+ } else if (error)
+ table->file->print_error(error, MYF(0));
+
+ DBUG_RETURN(error);
+}
+
+
+/**
+ Prepare column and key definitions for CREATE TABLE in ALTER TABLE.
+
+ This function transforms parse output of ALTER TABLE - lists of
+ columns and keys to add, drop or modify into, essentially,
+ CREATE TABLE definition - a list of columns and keys of the new
+ table. While doing so, it also performs some (bug not all)
+ semantic checks.
+
+ This function is invoked when we know that we're going to
+ perform ALTER TABLE via a temporary table -- i.e. fast ALTER TABLE
+ is not possible, perhaps because the ALTER statement contains
+ instructions that require change in table data, not only in
+ table definition or indexes.
+
+ @param[in,out] thd thread handle. Used as a memory pool
+ and source of environment information.
+ @param[in] table the source table, open and locked
+ Used as an interface to the storage engine
+ to acquire additional information about
+ the original table.
+ @param[in,out] create_info A blob with CREATE/ALTER TABLE
+ parameters
+ @param[in,out] alter_info Another blob with ALTER/CREATE parameters.
+ Originally create_info was used only in
+ CREATE TABLE and alter_info only in ALTER TABLE.
+ But since ALTER might end-up doing CREATE,
+ this distinction is gone and we just carry
+ around two structures.
+
+ @return
+ Fills various create_info members based on information retrieved
+ from the storage engine.
+ Sets create_info->varchar if the table has a VARCHAR column.
+ Prepares alter_info->create_list and alter_info->key_list with
+ columns and keys of the new table.
+ @retval TRUE error, out of memory or a semantical error in ALTER
+ TABLE instructions
+ @retval FALSE success
+*/
+
+static bool
+mysql_prepare_alter_table(THD *thd, TABLE *table,
+ HA_CREATE_INFO *create_info,
+ Alter_info *alter_info)
+{
+ /* New column definitions are added here */
+ List<Create_field> new_create_list;
+ /* New key definitions are added here */
+ List<Key> new_key_list;
+ List_iterator<Alter_drop> drop_it(alter_info->drop_list);
+ List_iterator<Create_field> def_it(alter_info->create_list);
+ List_iterator<Alter_column> alter_it(alter_info->alter_list);
+ List_iterator<Key> key_it(alter_info->key_list);
+ List_iterator<Create_field> find_it(new_create_list);
+ List_iterator<Create_field> field_it(new_create_list);
+ List<Key_part_spec> key_parts;
+ uint db_create_options= (table->s->db_create_options
+ & ~(HA_OPTION_PACK_RECORD));
+ uint used_fields= create_info->used_fields;
+ KEY *key_info=table->key_info;
+ bool rc= TRUE;
+ DBUG_ENTER("mysql_prepare_alter_table");
+
+ create_info->varchar= FALSE;
/* Let new create options override the old ones */
if (!(used_fields & HA_CREATE_USED_MIN_ROWS))
create_info->min_rows= table->s->min_rows;
@@ -3358,25 +5288,35 @@ view_err:
{
/* Table has an autoincrement, copy value to new table */
table->file->info(HA_STATUS_AUTO);
- create_info->auto_increment_value= table->file->auto_increment_value;
+ create_info->auto_increment_value= table->file->stats.auto_increment_value;
}
+ if (!(used_fields & HA_CREATE_USED_KEY_BLOCK_SIZE))
+ create_info->key_block_size= table->s->key_block_size;
+ if (!(used_fields & HA_CREATE_USED_TRANSACTIONAL))
+ create_info->transactional= table->s->transactional;
+ if (!create_info->tablespace && create_info->storage_media != HA_SM_MEMORY)
+ {
+ char *tablespace= static_cast<char *>(thd->alloc(FN_LEN));
+ /*
+ Regular alter table of disk stored table (no tablespace/storage change)
+ Copy tablespace name
+ */
+ if (tablespace &&
+ (table->file->get_tablespace_name(thd, tablespace, FN_LEN)))
+ create_info->tablespace= tablespace;
+ }
restore_record(table, s->default_values); // Empty record for DEFAULT
- List_iterator<Alter_drop> drop_it(alter_info->drop_list);
- List_iterator<create_field> def_it(alter_info->create_list);
- List_iterator<Alter_column> alter_it(alter_info->alter_list);
- Alter_info new_info; // Add new columns and indexes here
- create_field *def;
+ Create_field *def;
/*
First collect all fields from table which isn't in drop_list
*/
-
Field **f_ptr,*field;
for (f_ptr=table->field ; (field= *f_ptr) ; f_ptr++)
{
if (field->type() == MYSQL_TYPE_STRING)
- varchar= TRUE;
+ create_info->varchar= TRUE;
/* Check if field should be dropped */
Alter_drop *drop;
drop_it.rewind();
@@ -3413,13 +5353,18 @@ view_err:
def->field=field;
if (!def->after)
{
- new_info.create_list.push_back(def);
+ new_create_list.push_back(def);
def_it.remove();
}
}
else
- { // Use old field value
- new_info.create_list.push_back(def= new create_field(field, field));
+ {
+ /*
+ This field was not dropped and not changed, add it to the list
+ for the new table.
+ */
+ def= new Create_field(field, field);
+ new_create_list.push_back(def);
alter_it.rewind(); // Change default if ALTER
Alter_column *alter;
while ((alter=alter_it++))
@@ -3429,10 +5374,10 @@ view_err:
}
if (alter)
{
- if (def->sql_type == FIELD_TYPE_BLOB)
+ if (def->sql_type == MYSQL_TYPE_BLOB)
{
my_error(ER_BLOB_CANT_HAVE_DEFAULT, MYF(0), def->change);
- DBUG_RETURN(TRUE);
+ goto err;
}
if ((def->def=alter->def)) // Use new default
def->flags&= ~NO_DEFAULT_VALUE_FLAG;
@@ -3443,13 +5388,12 @@ view_err:
}
}
def_it.rewind();
- List_iterator<create_field> find_it(new_info.create_list);
while ((def=def_it++)) // Add new columns
{
if (def->change && ! def->field)
{
- my_error(ER_BAD_FIELD_ERROR, MYF(0), def->change, table_name);
- DBUG_RETURN(TRUE);
+ my_error(ER_BAD_FIELD_ERROR, MYF(0), def->change, table->s->table_name);
+ goto err;
}
/*
Check that the DATE/DATETIME not null field we are going to add is
@@ -3460,20 +5404,21 @@ view_err:
*/
if ((def->sql_type == MYSQL_TYPE_DATE ||
def->sql_type == MYSQL_TYPE_NEWDATE ||
- def->sql_type == MYSQL_TYPE_DATETIME) && !new_datetime_field &&
+ def->sql_type == MYSQL_TYPE_DATETIME) &&
+ !alter_info->datetime_field &&
!(~def->flags & (NO_DEFAULT_VALUE_FLAG | NOT_NULL_FLAG)) &&
thd->variables.sql_mode & MODE_NO_ZERO_DATE)
{
- new_datetime_field= def;
- error_if_not_empty= TRUE;
+ alter_info->datetime_field= def;
+ alter_info->error_if_not_empty= TRUE;
}
if (!def->after)
- new_info.create_list.push_back(def);
+ new_create_list.push_back(def);
else if (def->after == first_keyword)
- new_info.create_list.push_front(def);
+ new_create_list.push_front(def);
else
{
- create_field *find;
+ Create_field *find;
find_it.rewind();
while ((find=find_it++)) // Add new columns
{
@@ -3482,23 +5427,24 @@ view_err:
}
if (!find)
{
- my_error(ER_BAD_FIELD_ERROR, MYF(0), def->after, table_name);
- DBUG_RETURN(TRUE);
+ my_error(ER_BAD_FIELD_ERROR, MYF(0), def->after, table->s->table_name);
+ goto err;
}
find_it.after(def); // Put element after this
+ alter_info->change_level= ALTER_TABLE_DATA_CHANGED;
}
}
if (alter_info->alter_list.elements)
{
my_error(ER_BAD_FIELD_ERROR, MYF(0),
- alter_info->alter_list.head()->name, table_name);
- DBUG_RETURN(TRUE);
+ alter_info->alter_list.head()->name, table->s->table_name);
+ goto err;
}
- if (!new_info.create_list.elements)
+ if (!new_create_list.elements)
{
my_message(ER_CANT_REMOVE_ALL_FIELDS, ER(ER_CANT_REMOVE_ALL_FIELDS),
MYF(0));
- DBUG_RETURN(TRUE);
+ goto err;
}
/*
@@ -3506,11 +5452,6 @@ view_err:
for which some fields exists.
*/
- List_iterator<Key> key_it(alter_info->key_list);
- List_iterator<create_field> field_it(new_info.create_list);
- List<key_part_spec> key_parts;
-
- KEY *key_info=table->key_info;
for (uint i=0 ; i < table->s->keys ; i++,key_info++)
{
char *key_name= key_info->name;
@@ -3535,7 +5476,7 @@ view_err:
if (!key_part->field)
continue; // Wrong field (from UNIREG)
const char *key_part_name=key_part->field->field_name;
- create_field *cfield;
+ Create_field *cfield;
field_it.rewind();
while ((cfield=field_it++))
{
@@ -3568,6 +5509,8 @@ view_err:
*/
if (!Field::type_can_have_key_part(cfield->field->type()) ||
!Field::type_can_have_key_part(cfield->sql_type) ||
+ /* spatial keys can't have sub-key length */
+ (key_info->flags & HA_SPATIAL) ||
(cfield->field->field_length == key_part_length &&
!f_is_blob(key_part->key_type)) ||
(cfield->length && (cfield->length < key_part_length /
@@ -3575,13 +5518,21 @@ view_err:
key_part_length= 0; // Use whole field
}
key_part_length /= key_part->field->charset()->mbmaxlen;
- key_parts.push_back(new key_part_spec(cfield->field_name,
+ key_parts.push_back(new Key_part_spec(cfield->field_name,
key_part_length));
}
if (key_parts.elements)
{
+ KEY_CREATE_INFO key_create_info;
Key *key;
enum Key::Keytype key_type;
+ bzero((char*) &key_create_info, sizeof(key_create_info));
+
+ key_create_info.algorithm= key_info->algorithm;
+ if (key_info->flags & HA_USES_BLOCK_SIZE)
+ key_create_info.block_size= key_info->block_size;
+ if (key_info->flags & HA_USES_PARSER)
+ key_create_info.parser_name= *key_info->parser_name;
if (key_info->flags & HA_SPATIAL)
key_type= Key::SPATIAL;
@@ -3598,10 +5549,10 @@ view_err:
key_type= Key::MULTIPLE;
key= new Key(key_type, key_name,
- key_info->algorithm,
+ &key_create_info,
test(key_info->flags & HA_GENERATED_KEY),
key_parts);
- new_info.key_list.push_back(key);
+ new_key_list.push_back(key);
}
}
{
@@ -3609,12 +5560,12 @@ view_err:
while ((key=key_it++)) // Add new keys
{
if (key->type != Key::FOREIGN_KEY)
- new_info.key_list.push_back(key);
+ new_key_list.push_back(key);
if (key->name &&
!my_strcasecmp(system_charset_info,key->name,primary_key_name))
{
my_error(ER_WRONG_NAME_FOR_INDEX, MYF(0), key->name);
- DBUG_RETURN(TRUE);
+ goto err;
}
}
}
@@ -3632,17 +5583,6 @@ view_err:
goto err;
}
- db_create_options= table->s->db_create_options & ~(HA_OPTION_PACK_RECORD);
- my_snprintf(tmp_name, sizeof(tmp_name), "%s-%lx_%lx", tmp_file_prefix,
- current_pid, thd->thread_id);
- /* Safety fix for innodb */
- if (lower_case_table_names)
- my_casedn_str(files_charset_info, tmp_name);
- if (new_db_type != old_db_type && !table->file->can_switch_engines()) {
- my_error(ER_ROW_IS_REFERENCED, MYF(0));
- goto err;
- }
- create_info->db_type=new_db_type;
if (!create_info->comment.str)
{
create_info->comment.str= table->s->comment.str;
@@ -3666,32 +5606,625 @@ view_err:
if (table->s->tmp_table)
create_info->options|=HA_LEX_CREATE_TMP_TABLE;
+ rc= FALSE;
+ alter_info->create_list.swap(new_create_list);
+ alter_info->key_list.swap(new_key_list);
+err:
+ DBUG_RETURN(rc);
+}
+
+
+/*
+ Alter table
+
+ SYNOPSIS
+ mysql_alter_table()
+ thd Thread handle
+ new_db If there is a RENAME clause
+ new_name If there is a RENAME clause
+ create_info Information from the parsing phase about new
+ table properties.
+ table_list The table to change.
+ alter_info Lists of fields, keys to be changed, added
+ or dropped.
+ order_num How many ORDER BY fields has been specified.
+ order List of fields to ORDER BY.
+ ignore Whether we have ALTER IGNORE TABLE
+
+ DESCRIPTION
+ This is a veery long function and is everything but the kitchen sink :)
+ It is used to alter a table and not only by ALTER TABLE but also
+ CREATE|DROP INDEX are mapped on this function.
+
+ When the ALTER TABLE statement just does a RENAME or ENABLE|DISABLE KEYS,
+ or both, then this function short cuts its operation by renaming
+ the table and/or enabling/disabling the keys. In this case, the FRM is
+ not changed, directly by mysql_alter_table. However, if there is a
+ RENAME + change of a field, or an index, the short cut is not used.
+ See how `create_list` is used to generate the new FRM regarding the
+ structure of the fields. The same is done for the indices of the table.
+
+ Important is the fact, that this function tries to do as little work as
+ possible, by finding out whether a intermediate table is needed to copy
+ data into and when finishing the altering to use it as the original table.
+ For this reason the function compare_tables() is called, which decides
+ based on all kind of data how similar are the new and the original
+ tables.
+
+ RETURN VALUES
+ FALSE OK
+ TRUE Error
+*/
+
+bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
+ HA_CREATE_INFO *create_info,
+ TABLE_LIST *table_list,
+ Alter_info *alter_info,
+ uint order_num, ORDER *order, bool ignore)
+{
+ TABLE *table, *new_table= 0, *name_lock= 0;
+ int error= 0;
+ char tmp_name[80],old_name[32],new_name_buff[FN_REFLEN];
+ char new_alias_buff[FN_REFLEN], *table_name, *db, *new_alias, *alias;
+ char index_file[FN_REFLEN], data_file[FN_REFLEN];
+ char path[FN_REFLEN];
+ char reg_path[FN_REFLEN+1];
+ ha_rows copied,deleted;
+ handlerton *old_db_type, *new_db_type, *save_old_db_type;
+ legacy_db_type table_type;
+ frm_type_enum frm_type;
+ enum_alter_table_change_level need_copy_table= ALTER_TABLE_METADATA_ONLY;
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ uint fast_alter_partition= 0;
+ bool partition_changed= FALSE;
+#endif
+ bool need_lock_for_indexes= TRUE;
+ KEY *key_info_buffer;
+ uint index_drop_count;
+ uint *index_drop_buffer;
+ uint index_add_count;
+ uint *index_add_buffer;
+ 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);
+
+ /*
+ Check if we attempt to alter mysql.slow_log or
+ mysql.general_log table and return an error if
+ it is the case.
+ TODO: this design is obsolete and will be removed.
+ */
+ if (table_list && table_list->db && table_list->table_name)
+ {
+ int table_kind= 0;
+
+ table_kind= check_if_log_table(table_list->db_length, table_list->db,
+ table_list->table_name_length,
+ table_list->table_name, 0);
+
+ if (table_kind)
+ {
+ /* Disable alter of enabled log tables */
+ if (logger.is_log_table_enabled(table_kind))
+ {
+ my_error(ER_BAD_LOG_STATEMENT, MYF(0), "ALTER");
+ DBUG_RETURN(TRUE);
+ }
+
+ /* Disable alter of log tables to unsupported engine */
+ if ((create_info->used_fields & HA_CREATE_USED_ENGINE) &&
+ (!create_info->db_type || /* unknown engine */
+ !(create_info->db_type->flags & HTON_SUPPORT_LOG_TABLES)))
+ {
+ my_error(ER_UNSUPORTED_LOG_ENGINE, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ if (alter_info->flags & ALTER_PARTITION)
+ {
+ my_error(ER_WRONG_USAGE, MYF(0), "PARTITION", "log table");
+ DBUG_RETURN(TRUE);
+ }
+#endif
+ }
+ }
+
+ /*
+ Assign variables table_name, new_name, db, new_db, path, reg_path
+ to simplify further comparisions: we want to see if it's a RENAME
+ later just by comparing the pointers, avoiding the need for strcmp.
+ */
+ thd->proc_info="init";
+ table_name=table_list->table_name;
+ alias= (lower_case_table_names == 2) ? table_list->alias : table_name;
+ db=table_list->db;
+ if (!new_db || !my_strcasecmp(table_alias_charset, new_db, db))
+ new_db= db;
+ build_table_filename(reg_path, sizeof(reg_path), db, table_name, reg_ext, 0);
+ build_table_filename(path, sizeof(path), db, table_name, "", 0);
+
+
+ mysql_ha_flush(thd, table_list, MYSQL_HA_CLOSE_FINAL, FALSE);
+
+ /* DISCARD/IMPORT TABLESPACE is always alone in an ALTER TABLE */
+ if (alter_info->tablespace_op != NO_TABLESPACE_OP)
+ /* Conditionally writes to binlog. */
+ DBUG_RETURN(mysql_discard_or_import_tablespace(thd,table_list,
+ alter_info->tablespace_op));
+ strxnmov(new_name_buff, sizeof (new_name_buff) - 1, mysql_data_home, "/", db,
+ "/", table_name, reg_ext, NullS);
+ (void) unpack_filename(new_name_buff, new_name_buff);
+ /*
+ If this is just a rename of a view, short cut to the
+ following scenario: 1) lock LOCK_open 2) do a RENAME
+ 2) unlock LOCK_open.
+ This is a copy-paste added to make sure
+ ALTER (sic:) TABLE .. RENAME works for views. ALTER VIEW is handled
+ as an independent branch in mysql_execute_command. The need
+ for a copy-paste arose because the main code flow of ALTER TABLE
+ ... RENAME tries to use open_ltable, which does not work for views
+ (open_ltable was never modified to merge table lists of child tables
+ into the main table list, like open_tables does).
+ This code is wrong and will be removed, please do not copy.
+ */
+ frm_type= mysql_frm_type(thd, new_name_buff, &table_type);
+ /* Rename a view */
+ /* Sic: there is a race here */
+ if (frm_type == FRMTYPE_VIEW && !(alter_info->flags & ~ALTER_RENAME))
+ {
+ /*
+ Avoid problems with a rename on a table that we have locked or
+ if the user is trying to to do this in a transcation context
+ */
+
+ if (thd->locked_tables || thd->active_transaction())
+ {
+ my_message(ER_LOCK_OR_ACTIVE_TRANSACTION,
+ ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+
+ if (wait_if_global_read_lock(thd,0,1))
+ DBUG_RETURN(TRUE);
+ VOID(pthread_mutex_lock(&LOCK_open));
+ if (lock_table_names(thd, table_list))
+ {
+ error= 1;
+ goto view_err;
+ }
+
+ if (!do_rename(thd, table_list, new_db, new_name, new_name, 1))
+ {
+ if (mysql_bin_log.is_open())
+ {
+ thd->clear_error();
+ Query_log_event qinfo(thd, thd->query, thd->query_length, 0, FALSE);
+ mysql_bin_log.write(&qinfo);
+ }
+ send_ok(thd);
+ }
+
+ unlock_table_names(thd, table_list, (TABLE_LIST*) 0);
+
+view_err:
+ pthread_mutex_unlock(&LOCK_open);
+ start_waiting_global_read_lock(thd);
+ DBUG_RETURN(error);
+ }
+ if (!(table=open_ltable(thd, table_list, TL_WRITE_ALLOW_READ, 0)))
+ 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)
+ {
+ if (lower_case_table_names != 2)
+ {
+ my_casedn_str(files_charset_info, new_name_buff);
+ new_alias= new_name; // Create lower case table name
+ }
+ my_casedn_str(files_charset_info, new_name);
+ }
+ if (new_db == db &&
+ !my_strcasecmp(table_alias_charset, new_name_buff, table_name))
+ {
+ /*
+ Source and destination table names are equal: make later check
+ easier.
+ */
+ new_alias= new_name= table_name;
+ }
+ else
+ {
+ if (table->s->tmp_table != NO_TMP_TABLE)
+ {
+ if (find_temporary_table(thd,new_db,new_name_buff))
+ {
+ my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_name_buff);
+ DBUG_RETURN(TRUE);
+ }
+ }
+ else
+ {
+ if (lock_table_name_if_not_cached(thd, new_db, new_name, &name_lock))
+ DBUG_RETURN(TRUE);
+ if (!name_lock)
+ {
+ my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_alias);
+ DBUG_RETURN(TRUE);
+ }
+
+ build_table_filename(new_name_buff, sizeof(new_name_buff),
+ new_db, new_name_buff, reg_ext, 0);
+ if (!access(new_name_buff, F_OK))
+ {
+ /* Table will be closed in do_command() */
+ my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_alias);
+ goto err;
+ }
+ }
+ }
+ }
+ else
+ {
+ new_alias= (lower_case_table_names == 2) ? alias : table_name;
+ new_name= table_name;
+ }
+
+ old_db_type= table->s->db_type();
+ if (!create_info->db_type)
+ {
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ if (table->part_info &&
+ create_info->used_fields & HA_CREATE_USED_ENGINE)
+ {
+ /*
+ This case happens when the user specified
+ ENGINE = x where x is a non-existing storage engine
+ We set create_info->db_type to default_engine_type
+ to ensure we don't change underlying engine type
+ due to a erroneously given engine name.
+ */
+ create_info->db_type= table->part_info->default_engine_type;
+ }
+ else
+#endif
+ create_info->db_type= old_db_type;
+ }
+
+ if (check_engine(thd, new_name, create_info))
+ goto err;
+ new_db_type= create_info->db_type;
+
+ if (new_db_type != old_db_type &&
+ !table->file->can_switch_engines())
+ {
+ my_error(ER_ROW_IS_REFERENCED, MYF(0));
+ goto err;
+ }
+
+ if (create_info->row_type == ROW_TYPE_NOT_USED)
+ create_info->row_type= table->s->row_type;
+
+ DBUG_PRINT("info", ("old type: %s new type: %s",
+ ha_resolve_storage_engine_name(old_db_type),
+ ha_resolve_storage_engine_name(new_db_type)));
+ if (ha_check_storage_engine_flag(old_db_type, HTON_ALTER_NOT_SUPPORTED) ||
+ ha_check_storage_engine_flag(new_db_type, HTON_ALTER_NOT_SUPPORTED))
+ {
+ DBUG_PRINT("info", ("doesn't support alter"));
+ my_error(ER_ILLEGAL_HA, MYF(0), table_name);
+ goto err;
+ }
+
+ thd->proc_info="setup";
+ if (!(alter_info->flags & ~(ALTER_RENAME | ALTER_KEYS_ONOFF)) &&
+ !table->s->tmp_table) // no need to touch frm
+ {
+ switch (alter_info->keys_onoff) {
+ case LEAVE_AS_IS:
+ break;
+ case ENABLE:
+ /*
+ wait_while_table_is_used() ensures that table being altered is
+ opened only by this thread and that TABLE::TABLE_SHARE::version
+ of TABLE object corresponding to this table is 0.
+ The latter guarantees that no DML statement will open this table
+ until ALTER TABLE finishes (i.e. until close_thread_tables())
+ while the fact that the table is still open gives us protection
+ from concurrent DDL statements.
+ */
+ VOID(pthread_mutex_lock(&LOCK_open));
+ wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN);
+ VOID(pthread_mutex_unlock(&LOCK_open));
+ DBUG_EXECUTE_IF("sleep_alter_enable_indexes", my_sleep(6000000););
+ error= table->file->enable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE);
+ /* COND_refresh will be signaled in close_thread_tables() */
+ break;
+ case DISABLE:
+ VOID(pthread_mutex_lock(&LOCK_open));
+ wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN);
+ VOID(pthread_mutex_unlock(&LOCK_open));
+ error=table->file->disable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE);
+ /* COND_refresh will be signaled in close_thread_tables() */
+ break;
+ default:
+ DBUG_ASSERT(FALSE);
+ error= 0;
+ break;
+ }
+ if (error == HA_ERR_WRONG_COMMAND)
+ {
+ error= 0;
+ push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
+ ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA),
+ table->alias);
+ }
+
+ VOID(pthread_mutex_lock(&LOCK_open));
+ /*
+ Unlike to the above case close_cached_table() below will remove ALL
+ instances of TABLE from table cache (it will also remove table lock
+ held by this thread). So to make actual table renaming and writing
+ to binlog atomic we have to put them into the same critical section
+ protected by LOCK_open mutex. This also removes gap for races between
+ access() and mysql_rename_table() calls.
+ */
+
+ if (!error && (new_name != table_name || new_db != db))
+ {
+ thd->proc_info="rename";
+ /*
+ Then do a 'simple' rename of the table. First we need to close all
+ instances of 'source' table.
+ */
+ close_cached_table(thd, table);
+ /*
+ Then, we want check once again that target table does not exist.
+ Actually the order of these two steps does not matter since
+ earlier we took name-lock on the target table, so we do them
+ in this particular order only to be consistent with 5.0, in which
+ we don't take this name-lock and where this order really matters.
+ TODO: Investigate if we need this access() check at all.
+ */
+ if (!access(new_name_buff,F_OK))
+ {
+ my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_name);
+ error= -1;
+ }
+ else
+ {
+ *fn_ext(new_name)=0;
+ if (mysql_rename_table(old_db_type,db,table_name,new_db,new_alias, 0))
+ error= -1;
+ else if (Table_triggers_list::change_table_name(thd, db, table_name,
+ new_db, new_alias))
+ {
+ VOID(mysql_rename_table(old_db_type, new_db, new_alias, db,
+ table_name, 0));
+ error= -1;
+ }
+ }
+ }
+
+ if (error == HA_ERR_WRONG_COMMAND)
+ {
+ error= 0;
+ push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
+ ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA),
+ table->alias);
+ }
+
+ if (!error)
+ {
+ write_bin_log(thd, TRUE, thd->query, thd->query_length);
+ send_ok(thd);
+ }
+ else if (error > 0)
+ {
+ table->file->print_error(error, MYF(0));
+ error= -1;
+ }
+ if (name_lock)
+ unlink_open_table(thd, name_lock, FALSE);
+ VOID(pthread_mutex_unlock(&LOCK_open));
+ table_list->table= NULL; // For query cache
+ query_cache_invalidate3(thd, table_list, 0);
+ DBUG_RETURN(error);
+ }
+
+ /* We have to do full alter table. */
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ if (prep_alter_part_table(thd, table, alter_info, create_info, old_db_type,
+ &partition_changed, &fast_alter_partition))
+ goto err;
+#endif
+ /*
+ If the old table had partitions and we are doing ALTER TABLE ...
+ engine= <new_engine>, the new table must preserve the original
+ partitioning. That means that the new engine is still the
+ partitioning engine, not the engine specified in the parser.
+ This is discovered in prep_alter_part_table, which in such case
+ updates create_info->db_type.
+ Now we need to update the stack copy of create_info->db_type,
+ as otherwise we won't be able to correctly move the files of the
+ temporary table to the result table files.
+ */
+ new_db_type= create_info->db_type;
+
+ if (mysql_prepare_alter_table(thd, table, create_info, alter_info))
+ goto err;
+
+ need_copy_table= alter_info->change_level;
+
+ set_table_default_charset(thd, create_info, db);
+
+ if (thd->variables.old_alter_table
+ || (table->s->db_type() != create_info->db_type)
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ || partition_changed
+#endif
+ )
+ need_copy_table= ALTER_TABLE_DATA_CHANGED;
+ else
+ {
+ enum_alter_table_change_level need_copy_table_res;
+ /* Check how much the tables differ. */
+ if (compare_tables(table, alter_info,
+ create_info, order_num,
+ &need_copy_table_res,
+ &key_info_buffer,
+ &index_drop_buffer, &index_drop_count,
+ &index_add_buffer, &index_add_count))
+ goto err;
+
+ if (need_copy_table == ALTER_TABLE_METADATA_ONLY)
+ need_copy_table= need_copy_table_res;
+ }
+
+ /*
+ If there are index changes only, try to do them online. "Index
+ changes only" means also that the handler for the table does not
+ change. The table is open and locked. The handler can be accessed.
+ */
+ if (need_copy_table == ALTER_TABLE_INDEX_CHANGED)
+ {
+ int pk_changed= 0;
+ ulong alter_flags= 0;
+ ulong needed_online_flags= 0;
+ ulong needed_fast_flags= 0;
+ KEY *key;
+ uint *idx_p;
+ uint *idx_end_p;
+
+ 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= ALTER_TABLE_METADATA_ONLY;
+ need_lock_for_indexes= FALSE;
+ }
+ else if ((alter_flags & needed_fast_flags) == needed_fast_flags)
+ {
+ /* All required fast flags are present. */
+ need_copy_table= ALTER_TABLE_METADATA_ONLY;
+ }
+ }
+ DBUG_PRINT("info", ("need_copy_table: %u need_lock: %d",
+ need_copy_table, need_lock_for_indexes));
+ }
+
/*
better have a negative test here, instead of positive, like
alter_info->flags & ALTER_ADD_COLUMN|ALTER_ADD_INDEX|...
so that ALTER TABLE won't break when somebody will add new flag
+ */
+ if (need_copy_table == ALTER_TABLE_METADATA_ONLY)
+ create_info->frm_only= 1;
- MySQL uses frm version to determine the type of the data fields and
- their layout. See Field_string::type() for details.
- Thus, if the table is too old we may have to rebuild the data to
- update the layout.
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ if (fast_alter_partition)
+ {
+ DBUG_ASSERT(!name_lock);
+ DBUG_RETURN(fast_alter_partition_table(thd, table, alter_info,
+ create_info, table_list,
+ db, table_name,
+ fast_alter_partition));
+ }
+#endif
- There was a bug prior to mysql-4.0.25. Number of null fields was
- calculated incorrectly. As a result frm and data files gets out of
- sync after fast alter table. There is no way to determine by which
- mysql version (in 4.0 and 4.1 branches) table was created, thus we
- disable fast alter table for all tables created by mysql versions
- prior to 5.0 branch.
- See BUG#6236.
- */
- need_copy_table= (alter_info->flags &
- ~(ALTER_CHANGE_COLUMN_DEFAULT|ALTER_OPTIONS) ||
- (create_info->used_fields &
- ~(HA_CREATE_USED_COMMENT|HA_CREATE_USED_PASSWORD)) ||
- table->s->tmp_table ||
- !table->s->mysql_version ||
- (table->s->frm_version < FRM_VER_TRUE_VARCHAR && varchar));
- create_info->frm_only= !need_copy_table;
+ my_snprintf(tmp_name, sizeof(tmp_name), "%s-%lx_%lx", tmp_file_prefix,
+ current_pid, thd->thread_id);
+ /* Safety fix for innodb */
+ if (lower_case_table_names)
+ my_casedn_str(files_charset_info, tmp_name);
/*
Handling of symlinked tables:
@@ -3708,8 +6241,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:
@@ -3717,7 +6250,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)
@@ -3740,16 +6272,22 @@ view_err:
else
create_info->data_file_name=create_info->index_file_name=0;
- /* We don't log the statement, it will be logged later. */
- {
- tmp_disable_binlog(thd);
- error= mysql_create_table(thd, new_db, tmp_name,
- create_info, &new_info, 1, 0);
- reenable_binlog(thd);
- if (error)
- DBUG_RETURN(error);
- }
- if (need_copy_table)
+ /*
+ Create a table with a temporary name.
+ With create_info->frm_only == 1 this creates a .frm file only.
+ We don't log the statement, it will be logged later.
+ */
+ tmp_disable_binlog(thd);
+ error= mysql_create_table_no_lock(thd, new_db, tmp_name,
+ create_info,
+ alter_info,
+ 1, 0);
+ reenable_binlog(thd);
+ if (error)
+ goto err;
+
+ /* Open the table if we need to copy the data. */
+ if (need_copy_table != ALTER_TABLE_METADATA_ONLY)
{
if (table->s->tmp_table)
{
@@ -3757,40 +6295,40 @@ 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, new_info.create_list,
- ignore, order_num, order,
- &copied, &deleted, alter_info->keys_onoff,
- error_if_not_empty);
+ error= copy_data_between_tables(table, new_table,
+ alter_info->create_list, ignore,
+ order_num, order, &copied, &deleted,
+ alter_info->keys_onoff,
+ alter_info->error_if_not_empty);
}
- else if (!new_table)
+ else
{
VOID(pthread_mutex_lock(&LOCK_open));
wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN);
@@ -3801,22 +6339,102 @@ view_err:
if (ha_commit(thd))
error= 1;
}
-
- thd->last_insert_id=next_insert_id; // Needed for correct log
thd->count_cuted_fields= CHECK_FIELD_IGNORE;
- if (table->s->tmp_table)
+ /* If we did not need to copy, we might still need to add/drop indexes. */
+ if (! new_table)
{
- /* We changed a temporary table */
- if (error)
+ uint *key_numbers;
+ uint *keyno_p;
+ KEY *key_info;
+ KEY *key;
+ uint *idx_p;
+ uint *idx_end_p;
+ KEY_PART_INFO *key_part;
+ KEY_PART_INFO *part_end;
+ DBUG_PRINT("info", ("No new_table, checking add/drop index"));
+
+ table->file->prepare_for_alter();
+ if (index_add_count)
+ {
+ /* The add_index() method takes an array of KEY structs. */
+ key_info= (KEY*) thd->alloc(sizeof(KEY) * index_add_count);
+ key= key_info;
+ for (idx_p= index_add_buffer, idx_end_p= idx_p + index_add_count;
+ idx_p < idx_end_p;
+ idx_p++, key++)
+ {
+ /* Copy the KEY struct. */
+ *key= key_info_buffer[*idx_p];
+ /* Fix the key parts. */
+ part_end= key->key_part + key->key_parts;
+ for (key_part= key->key_part; key_part < part_end; key_part++)
+ key_part->field= table->field[key_part->fieldnr];
+ }
+ /* Add the indexes. */
+ if ((error= table->file->add_index(table, key_info, index_add_count)))
+ {
+ /*
+ Exchange the key_info for the error message. If we exchange
+ key number by key name in the message later, we need correct info.
+ */
+ KEY *save_key_info= table->key_info;
+ table->key_info= key_info;
+ table->file->print_error(error, MYF(0));
+ table->key_info= save_key_info;
+ goto err1;
+ }
+ }
+ /*end of if (index_add_count)*/
+
+ if (index_drop_count)
{
+ /* The prepare_drop_index() method takes an array of key numbers. */
+ key_numbers= (uint*) thd->alloc(sizeof(uint) * index_drop_count);
+ keyno_p= key_numbers;
+ /* Get the number of each key. */
+ for (idx_p= index_drop_buffer, idx_end_p= idx_p + index_drop_count;
+ idx_p < idx_end_p;
+ idx_p++, keyno_p++)
+ *keyno_p= *idx_p;
/*
- The following function call will free the new_table pointer,
- in close_temporary_table(), so we can safely directly jump to err
+ Tell the handler to prepare for drop indexes.
+ This re-numbers the indexes to get rid of gaps.
*/
- close_temporary_table(thd, new_db, tmp_name);
- goto err;
+ if ((error= table->file->prepare_drop_index(table, key_numbers,
+ index_drop_count)))
+ {
+ table->file->print_error(error, MYF(0));
+ goto err1;
+ }
+
+ /* Tell the handler to finally drop the indexes. */
+ if ((error= table->file->final_drop_index(table)))
+ {
+ table->file->print_error(error, MYF(0));
+ goto err1;
+ }
}
+ /*end of if (index_drop_count)*/
+
+ /*
+ The final .frm file is already created as a temporary file
+ and will be renamed to the original table name later.
+ */
+
+ /* Need to commit before a table is unlocked (NDB requirement). */
+ DBUG_PRINT("info", ("Committing before unlocking table"));
+ if (ha_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)
{
@@ -3824,44 +6442,44 @@ 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 */
- my_free((gptr) new_table,MYF(0));
+ /* Close the intermediate table that will be the new table */
+ intern_close_table(new_table);
+ my_free(new_table,MYF(0));
}
VOID(pthread_mutex_lock(&LOCK_open));
if (error)
{
- VOID(quick_rm_table(new_db_type,new_db,tmp_name));
+ VOID(quick_rm_table(new_db_type, new_db, tmp_name, FN_IS_TMP));
VOID(pthread_mutex_unlock(&LOCK_open));
goto err;
}
/*
- Data is copied. Now we rename the old table to a temp name,
- rename the new one to the old name, remove all entries from the old table
- from the cache, free all locks, close the old table and remove it.
+ Data is copied. Now we:
+ 1) Wait until all other threads close old version of table.
+ 2) Close instances of table open by this thread and replace them
+ with exclusive name-locks.
+ 3) Rename the old table to a temp name, rename the new one to the
+ old name.
+ 4) If we are under LOCK TABLES and don't do ALTER TABLE ... RENAME
+ we reopen new version of table.
+ 5) Write statement to the binary log.
+ 6) If we are under LOCK TABLES and do ALTER TABLE ... RENAME we
+ remove name-locks from list of open tables and table cache.
+ 7) If we are not not under LOCK TABLES we rely on close_thread_tables()
+ call to remove name-locks from table cache and list of open table.
*/
thd->proc_info="rename result table";
@@ -3870,117 +6488,128 @@ view_err:
if (lower_case_table_names)
my_casedn_str(files_charset_info, old_name);
-#if (!defined( __WIN__) && !defined( __EMX__) && !defined( OS2))
- if (table->file->has_transactions())
-#endif
- {
- /*
- Win32 and InnoDB can't drop a table that is in use, so we must
- close the original table at before doing the rename
- */
- close_cached_table(thd, table);
- table=0; // Marker that table is closed
- no_table_reopen= TRUE;
- }
-#if (!defined( __WIN__) && !defined( __EMX__) && !defined( OS2))
- else
- table->file->extra(HA_EXTRA_FORCE_REOPEN); // Don't use this file anymore
-#endif
+ wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_DELETE);
+ close_data_files_and_morph_locks(thd, db, table_name);
+
+ error=0;
+ save_old_db_type= old_db_type;
- if (new_name != table_name || new_db != db)
+ /*
+ This leads to the storage engine (SE) not being notified for renames in
+ mysql_rename_table(), because we just juggle with the FRM and nothing
+ more. If we have an intermediate table, then we notify the SE that
+ it should become the actual table. Later, we will recycle the old table.
+ However, in case of ALTER TABLE RENAME there might be no intermediate
+ table. This is when the old and new tables are compatible, according to
+ compare_table(). Then, we need one additional call to
+ mysql_rename_table() with flag NO_FRM_RENAME, which does nothing else but
+ actual rename in the SE and the FRM is not touched. Note that, if the
+ table is renamed and the SE is also changed, then an intermediate table
+ is created and the additional call will not take place.
+ */
+ if (need_copy_table == ALTER_TABLE_METADATA_ONLY)
{
- /*
- Check that there is no table with target name. See the
- comment describing code for 'simple' ALTER TABLE ... RENAME.
- */
- if (table_cache_has_open_placeholder(thd, new_db, new_name) ||
- !access(new_name_buff,F_OK))
- {
- error=1;
- my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_name_buff);
- VOID(quick_rm_table(new_db_type,new_db,tmp_name));
- VOID(pthread_mutex_unlock(&LOCK_open));
- goto err;
- }
+ DBUG_ASSERT(new_db_type == old_db_type);
+ /* This type cannot happen in regular ALTER. */
+ new_db_type= old_db_type= NULL;
}
-
- error=0;
- if (!need_copy_table)
- new_db_type=old_db_type=DB_TYPE_UNKNOWN; // this type cannot happen in regular ALTER
- if (mysql_rename_table(old_db_type,db,table_name,db,old_name))
+ if (mysql_rename_table(old_db_type, db, table_name, db, old_name,
+ FN_TO_IS_TMP))
{
error=1;
- VOID(quick_rm_table(new_db_type,new_db,tmp_name));
+ VOID(quick_rm_table(new_db_type, new_db, tmp_name, FN_IS_TMP));
}
- else if (mysql_rename_table(new_db_type,new_db,tmp_name,new_db,
- new_alias) ||
+ else if (mysql_rename_table(new_db_type, new_db, tmp_name, new_db,
+ new_alias, FN_FROM_IS_TMP) ||
(new_name != table_name || new_db != db) && // we also do rename
+ (need_copy_table != ALTER_TABLE_METADATA_ONLY ||
+ mysql_rename_table(save_old_db_type, db, table_name, new_db,
+ new_alias, NO_FRM_RENAME)) &&
Table_triggers_list::change_table_name(thd, db, table_name,
new_db, new_alias))
-
- { // Try to get everything back
+ {
+ /* Try to get everything back. */
error=1;
- VOID(quick_rm_table(new_db_type,new_db,new_alias));
- VOID(quick_rm_table(new_db_type,new_db,tmp_name));
- VOID(mysql_rename_table(old_db_type,db,old_name,db,alias));
+ VOID(quick_rm_table(new_db_type,new_db,new_alias, 0));
+ VOID(quick_rm_table(new_db_type, new_db, tmp_name, FN_IS_TMP));
+ VOID(mysql_rename_table(old_db_type, db, old_name, db, alias,
+ FN_FROM_IS_TMP));
}
+
if (error)
{
- /*
- This shouldn't happen. We solve this the safe way by
- closing the locked table.
- */
- if (table)
- close_cached_table(thd,table);
- VOID(pthread_mutex_unlock(&LOCK_open));
- goto err;
+ /* This shouldn't happen. But let us play it safe. */
+ goto err_with_placeholders;
}
- if (thd->lock || new_name != table_name || no_table_reopen) // True if WIN32
- {
- /*
- Not table locking or alter table with rename
- free locks and remove old table
- */
- if (table)
- close_cached_table(thd,table);
- VOID(quick_rm_table(old_db_type,db,old_name));
- }
- else
+
+ if (need_copy_table == ALTER_TABLE_METADATA_ONLY)
{
/*
- Using LOCK TABLES without rename.
- This code is never executed on WIN32!
- Remove old renamed table, reopen table and get new locks
+ Now we have to inform handler that new .FRM file is in place.
+ To do this we need to obtain a handler object for it.
*/
- if (table)
- {
- VOID(table->file->extra(HA_EXTRA_FORCE_REOPEN)); // Use new file
- /* Mark in-use copies old */
- remove_table_from_cache(thd,db,table_name,RTFC_NO_FLAG);
- /* end threads waiting on lock */
- mysql_lock_abort(thd,table);
- }
- VOID(quick_rm_table(old_db_type,db,old_name));
- if (close_data_tables(thd,db,table_name) ||
- reopen_tables(thd,1,0))
- { // This shouldn't happen
- if (table)
- close_cached_table(thd,table); // Remove lock for table
- VOID(pthread_mutex_unlock(&LOCK_open));
- goto err;
+ TABLE *t_table;
+ if (new_name != table_name || new_db != db)
+ {
+ table_list->alias= new_name;
+ table_list->table_name= new_name;
+ table_list->table_name_length= strlen(new_name);
+ table_list->db= new_db;
+ table_list->db_length= strlen(new_db);
+ table_list->table= name_lock;
+ if (reopen_name_locked_table(thd, table_list, FALSE))
+ goto err_with_placeholders;
+ t_table= table_list->table;
+ }
+ else
+ {
+ if (reopen_table(table))
+ goto err_with_placeholders;
+ t_table= table;
+ }
+ /* Tell the handler that a new frm file is in place. */
+ if (t_table->file->create_handler_files(path, NULL, CHF_INDEX_FLAG,
+ create_info))
+ goto err_with_placeholders;
+ if (thd->locked_tables && new_name == table_name && new_db == db)
+ {
+ /*
+ We are going to reopen table down on the road, so we have to restore
+ state of the TABLE object which we used for obtaining of handler
+ object to make it suitable for reopening.
+ */
+ DBUG_ASSERT(t_table == table);
+ table->open_placeholder= 1;
+ close_handle_and_leave_table_as_lock(table);
}
}
- thd->proc_info="end";
- if (mysql_bin_log.is_open())
+
+ VOID(quick_rm_table(old_db_type, db, old_name, FN_IS_TMP));
+
+ if (thd->locked_tables && new_name == table_name && new_db == db)
{
- thd->clear_error();
- Query_log_event qinfo(thd, thd->query, thd->query_length, FALSE, FALSE);
- mysql_bin_log.write(&qinfo);
+ thd->in_lock_tables= 1;
+ error= reopen_tables(thd, 1, 0);
+ thd->in_lock_tables= 0;
+ if (error)
+ goto err_with_placeholders;
}
- broadcast_refresh();
VOID(pthread_mutex_unlock(&LOCK_open));
-#ifdef HAVE_BERKELEY_DB
- if (old_db_type == DB_TYPE_BERKELEY_DB)
+
+ thd->proc_info="end";
+
+ DBUG_EXECUTE_IF("sleep_alter_before_main_binlog", my_sleep(6000000););
+
+ ha_binlog_log_query(thd, create_info->db_type, LOGCOM_ALTER_TABLE,
+ thd->query, thd->query_length,
+ db, table_name);
+
+ DBUG_ASSERT(!(mysql_bin_log.is_open() &&
+ thd->current_stmt_binlog_row_based &&
+ (create_info->options & HA_LEX_CREATE_TMP_TABLE)));
+ write_bin_log(thd, TRUE, thd->query, thd->query_length);
+
+ if (ha_check_storage_engine_flag(old_db_type, HTON_FLUSH_AFTER_RENAME))
{
/*
For the alter table to be properly flushed to the logs, we
@@ -3988,22 +6617,36 @@ view_err:
shutdown.
*/
char path[FN_REFLEN];
- build_table_path(path, sizeof(path), new_db, table_name, "");
- table=open_temporary_table(thd, path, new_db, tmp_name,0);
- if (table)
+ TABLE *t_table;
+ build_table_filename(path, sizeof(path), new_db, table_name, "", 0);
+ t_table= open_temporary_table(thd, path, new_db, tmp_name, 0);
+ if (t_table)
{
- intern_close_table(table);
- my_free((char*) table, MYF(0));
+ intern_close_table(t_table);
+ my_free(t_table, MYF(0));
}
else
- sql_print_warning("Could not open BDB table %s.%s after rename\n",
+ sql_print_warning("Could not open table %s.%s after rename\n",
new_db,table_name);
- (void) berkeley_flush_logs();
+ ha_flush_logs(old_db_type);
}
-#endif
table_list->table=0; // For query cache
query_cache_invalidate3(thd, table_list, 0);
+ if (thd->locked_tables && (new_name != table_name || new_db != db))
+ {
+ /*
+ If are we under LOCK TABLES and did ALTER TABLE with RENAME we need
+ to remove placeholders for the old table and for the target table
+ from the list of open tables and table cache. If we are not under
+ LOCK TABLES we can rely on close_thread_tables() doing this job.
+ */
+ pthread_mutex_lock(&LOCK_open);
+ unlink_open_table(thd, table, FALSE);
+ unlink_open_table(thd, name_lock, FALSE);
+ pthread_mutex_unlock(&LOCK_open);
+ }
+
end_temporary:
my_snprintf(tmp_name, sizeof(tmp_name), ER(ER_INSERT_INFO),
(ulong) (copied + deleted), (ulong) deleted,
@@ -4012,6 +6655,15 @@ 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:
/*
No default value was provided for a DATE/DATETIME field, the
@@ -4019,11 +6671,11 @@ err:
the table to be altered isn't empty.
Report error here.
*/
- if (error_if_not_empty && thd->row_count)
+ if (alter_info->error_if_not_empty && thd->row_count)
{
const char *f_val= 0;
enum enum_mysql_timestamp_type t_type= MYSQL_TIMESTAMP_DATE;
- switch (new_datetime_field->sql_type)
+ switch (alter_info->datetime_field->sql_type)
{
case MYSQL_TYPE_DATE:
case MYSQL_TYPE_NEWDATE:
@@ -4042,16 +6694,34 @@ err:
thd->abort_on_warning= TRUE;
make_truncated_value_warning(thd, MYSQL_ERROR::WARN_LEVEL_ERROR,
f_val, strlength(f_val), t_type,
- new_datetime_field->field_name);
+ alter_info->datetime_field->field_name);
thd->abort_on_warning= save_abort_on_warning;
}
+ if (name_lock)
+ {
+ pthread_mutex_lock(&LOCK_open);
+ unlink_open_table(thd, name_lock, FALSE);
+ pthread_mutex_unlock(&LOCK_open);
+ }
DBUG_RETURN(TRUE);
-}
+err_with_placeholders:
+ /*
+ An error happened while we were holding exclusive name-lock on table
+ being altered. To be safe under LOCK TABLES we should remove placeholders
+ from list of open tables list and table cache.
+ */
+ unlink_open_table(thd, table, FALSE);
+ if (name_lock)
+ unlink_open_table(thd, name_lock, FALSE);
+ VOID(pthread_mutex_unlock(&LOCK_open));
+ DBUG_RETURN(TRUE);
+}
+/* mysql_alter_table */
static int
copy_data_between_tables(TABLE *from,TABLE *to,
- List<create_field> &create,
+ List<Create_field> &create,
bool ignore,
uint order_num, ORDER *order,
ha_rows *copied,
@@ -4072,6 +6742,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");
/*
@@ -4099,12 +6770,12 @@ copy_data_between_tables(TABLE *from,TABLE *to,
MODE_STRICT_ALL_TABLES));
from->file->info(HA_STATUS_VARIABLE);
- to->file->start_bulk_insert(from->file->records);
+ to->file->ha_start_bulk_insert(from->file->stats.records);
save_sql_mode= thd->variables.sql_mode;
- List_iterator<create_field> it(create);
- create_field *def;
+ List_iterator<Create_field> it(create);
+ Create_field *def;
copy_end=copy;
for (Field **ptr=to->field ; *ptr ; ptr++)
{
@@ -4136,8 +6807,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) ||
@@ -4145,18 +6816,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);
@@ -4189,18 +6856,33 @@ copy_data_between_tables(TABLE *from,TABLE *to,
{
copy_ptr->do_copy(copy_ptr);
}
- error=to->file->write_row((byte*) to->record[0]);
+ prev_insert_id= to->file->next_insert_id;
+ error=to->file->write_row(to->record[0]);
to->auto_increment_field_not_null= FALSE;
if (error)
{
if (!ignore ||
- (error != HA_ERR_FOUND_DUPP_KEY &&
- error != HA_ERR_FOUND_DUPP_UNIQUE))
+ to->file->is_fatal_error(error, HA_CHECK_DUP))
{
+ if (!to->file->is_fatal_error(error, HA_CHECK_DUP))
+ {
+ uint key_nr= to->file->get_dup_key(error);
+ if ((int) key_nr >= 0)
+ {
+ const char *err_msg= ER(ER_DUP_ENTRY_WITH_KEY_NAME);
+ if (key_nr == 0 &&
+ (to->key_info[0].key_part[0].field->flags &
+ AUTO_INCREMENT_FLAG))
+ err_msg= ER(ER_DUP_ENTRY_AUTOINCREMENT_CASE);
+ to->file->print_keydup_error(key_nr, err_msg);
+ break;
+ }
+ }
+
to->file->print_error(error,MYF(0));
break;
}
- to->file->restore_auto_increment();
+ to->file->restore_auto_increment(prev_insert_id);
delete_count++;
}
else
@@ -4210,7 +6892,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;
@@ -4238,6 +6920,7 @@ copy_data_between_tables(TABLE *from,TABLE *to,
free_io_cache(from);
*copied= found_count;
*deleted=delete_count;
+ to->file->ha_release_auto_increment();
if (to->file->ha_external_lock(thd,F_UNLCK))
error=1;
DBUG_RETURN(error > 0 ? -1 : 0);
@@ -4263,18 +6946,19 @@ bool mysql_recreate_table(THD *thd, TABLE_LIST *table_list)
DBUG_ENTER("mysql_recreate_table");
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 */
- alter_info.flags= ALTER_CHANGE_COLUMN;
+ alter_info.flags= (ALTER_CHANGE_COLUMN | ALTER_RECREATE);
DBUG_RETURN(mysql_alter_table(thd, NullS, NullS, &create_info,
- table_list, &alter_info,
- 0, (ORDER *) 0, 0));
+ table_list, &alter_info, 0,
+ (ORDER *) 0, 0));
}
-bool mysql_checksum_table(THD *thd, TABLE_LIST *tables, HA_CHECK_OPT *check_opt)
+bool mysql_checksum_table(THD *thd, TABLE_LIST *tables,
+ HA_CHECK_OPT *check_opt)
{
TABLE_LIST *table;
List<Item> field_list;
@@ -4298,7 +6982,7 @@ bool mysql_checksum_table(THD *thd, TABLE_LIST *tables, HA_CHECK_OPT *check_opt)
strxmov(table_name, table->db ,".", table->table_name, NullS);
- t= table->table= open_ltable(thd, table, TL_READ);
+ t= table->table= open_ltable(thd, table, TL_READ, 0);
thd->clear_error(); // these errors shouldn't get client
protocol->prepare_for_resend();
@@ -4312,10 +6996,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
@@ -4324,10 +7008,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();
@@ -4356,15 +7037,15 @@ bool mysql_checksum_table(THD *thd, TABLE_LIST *tables, HA_CHECK_OPT *check_opt)
for (uint i= 0; i < t->s->fields; i++ )
{
Field *f= t->field[i];
- if ((f->type() == FIELD_TYPE_BLOB) ||
+ if ((f->type() == MYSQL_TYPE_BLOB) ||
(f->type() == MYSQL_TYPE_VARCHAR))
{
String tmp;
f->val_str(&tmp);
- row_crc= my_checksum(row_crc, (byte*) tmp.ptr(), tmp.length());
+ row_crc= my_checksum(row_crc, (uchar*) tmp.ptr(), tmp.length());
}
else
- row_crc= my_checksum(row_crc, (byte*) f->ptr,
+ row_crc= my_checksum(row_crc, f->ptr,
f->pack_length());
}
@@ -4393,35 +7074,35 @@ bool mysql_checksum_table(THD *thd, TABLE_LIST *tables, HA_CHECK_OPT *check_opt)
}
static bool check_engine(THD *thd, const char *table_name,
- enum db_type *new_engine)
+ HA_CREATE_INFO *create_info)
{
- enum db_type req_engine= *new_engine;
+ handlerton **new_engine= &create_info->db_type;
+ handlerton *req_engine= *new_engine;
bool no_substitution=
test(thd->variables.sql_mode & MODE_NO_ENGINE_SUBSTITUTION);
- if ((*new_engine=
- ha_checktype(thd, req_engine, no_substitution, 1)) == DB_TYPE_UNKNOWN)
+ if (!(*new_engine= ha_checktype(thd, ha_legacy_type(req_engine),
+ no_substitution, 1)))
return TRUE;
- if (req_engine != *new_engine)
+ if (req_engine && req_engine != *new_engine)
{
- push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
+ push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
ER_WARN_USING_OTHER_HANDLER,
ER(ER_WARN_USING_OTHER_HANDLER),
- ha_get_storage_engine(*new_engine),
+ ha_resolve_storage_engine_name(*new_engine),
table_name);
}
- return FALSE;
-}
-
-static void set_tmp_file_path(char *buf, size_t bufsize, THD *thd)
-{
- char *p= strnmov(buf, mysql_tmpdir, bufsize);
- my_snprintf(p, bufsize - (p - buf), "%s%lx_%lx_%x%s",
- tmp_file_prefix, current_pid,
- thd->thread_id, thd->tmp_table++, reg_ext);
- if (lower_case_table_names)
+ if (create_info->options & HA_LEX_CREATE_TMP_TABLE &&
+ ha_check_storage_engine_flag(*new_engine, HTON_TEMPORARY_NOT_SUPPORTED))
{
- /* Convert all except tmpdir to lower case */
- my_casedn_str(files_charset_info, p);
+ if (create_info->used_fields & HA_CREATE_USED_ENGINE)
+ {
+ my_error(ER_ILLEGAL_HA_CREATE_OPTION, MYF(0),
+ ha_resolve_storage_engine_name(*new_engine), "TEMPORARY");
+ *new_engine= 0;
+ return TRUE;
+ }
+ *new_engine= myisam_hton;
}
+ return FALSE;
}