diff options
Diffstat (limited to 'sql/ddl_log.cc')
-rw-r--r-- | sql/ddl_log.cc | 3587 |
1 files changed, 3587 insertions, 0 deletions
diff --git a/sql/ddl_log.cc b/sql/ddl_log.cc new file mode 100644 index 00000000..ffd2c24f --- /dev/null +++ b/sql/ddl_log.cc @@ -0,0 +1,3587 @@ +/* + Copyright (c) 2000, 2019, Oracle and/or its affiliates. + Copyright (c) 2010, 2021, MariaDB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA +*/ + +#include "mariadb.h" +#include "mysqld.h" +#include "sql_class.h" // init_sql_alloc() +#include "log.h" // sql_print_error() +#include "ddl_log.h" +#include "ha_partition.h" // PAR_EXT +#include "sql_table.h" // build_table_filename +#include "sql_statistics.h" // rename_table_in_stats_tables +#include "sql_view.h" // mysql_rename_view() +#include "strfunc.h" // strconvert +#include "sql_show.h" // append_identifier() +#include "sql_db.h" // drop_database_objects() +#include <mysys_err.h> // EE_LINK + + +/*-------------------------------------------------------------------------- + + 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. + + DDL recovery after a crash works the following way: + + - ddl_log_initialize() initializes the global global_ddl_log variable + and opens the binary log if it exists. If it doesn't exists a new one + is created. + - ddl_log_close_binlogged_events() loops over all log events and checks if + their xid (stored in the EXECUTE_CODE event) is in the binary log. If xid + exists in the binary log the entry is marked as finished in the ddl log. + - After a new binary log is created and is open for new entries, + ddl_log_execute_recovery() is executed on remaining open events: + - Loop over all events + - For each entry with DDL_LOG_ENTRY_CODE execute the remaining phases + in ddl_log_execute_entry_no_lock() + + The ddl_log.log file is created at startup and deleted when server goes down. + After the final recovery phase is done, the file is truncated. + + History: + First version written in 2006 by Mikael Ronstrom + Second version in 2020 by Monty +--------------------------------------------------------------------------*/ + +#define DDL_LOG_MAGIC_LENGTH 4 +/* How many times to try to execute a ddl log entry that causes crashes */ +#define DDL_LOG_MAX_RETRY 3 +#define DDL_LOG_RETRY_MASK 0xFF +#define DDL_LOG_RETRY_BITS 8 + +uchar ddl_log_file_magic[]= +{ (uchar) 254, (uchar) 254, (uchar) 11, (uchar) 2 }; + +/* Action names for ddl_log_action_code */ + +const char *ddl_log_action_name[DDL_LOG_LAST_ACTION]= +{ + "Unknown", "partitioning delete", "partitioning rename", + "partitioning replace", "partitioning exchange", + "rename table", "rename view", + "initialize drop table", "drop table", + "drop view", "drop trigger", "drop db", "create table", "create view", + "delete tmp file", "create trigger", "alter table", "store query" +}; + +/* Number of phases per entry */ +const uchar ddl_log_entry_phases[DDL_LOG_LAST_ACTION]= +{ + 0, 1, 1, 2, + (uchar) EXCH_PHASE_END, (uchar) DDL_RENAME_PHASE_END, 1, 1, + (uchar) DDL_DROP_PHASE_END, 1, 1, + (uchar) DDL_DROP_DB_PHASE_END, (uchar) DDL_CREATE_TABLE_PHASE_END, + (uchar) DDL_CREATE_VIEW_PHASE_END, 0, (uchar) DDL_CREATE_TRIGGER_PHASE_END, + DDL_ALTER_TABLE_PHASE_END, 1 +}; + + +struct st_global_ddl_log +{ + uchar *file_entry_buf; + DDL_LOG_MEMORY_ENTRY *first_free; + DDL_LOG_MEMORY_ENTRY *first_used; + File file_id; + uint num_entries; + uint name_pos; + uint io_size; + bool initialized; + bool open, backup_done, created; +}; + +/* + The following structure is only used during startup recovery + for writing queries to the binary log. + */ + +class st_ddl_recovery { +public: + String drop_table; + String drop_view; + String query; + String db; + size_t drop_table_init_length, drop_view_init_length; + char current_db[NAME_LEN]; + uint execute_entry_pos; + ulonglong xid; +}; + +static st_global_ddl_log global_ddl_log; +static st_ddl_recovery recovery_state; + +mysql_mutex_t LOCK_gdl; + +/* Positions to different data in a ddl log block */ +static constexpr unsigned DDL_LOG_ENTRY_TYPE_POS= 0; +/* + Note that ACTION_TYPE and PHASE_POS must be after each other. + See update_phase() +*/ +static constexpr unsigned DDL_LOG_ACTION_TYPE_POS= 1; +static constexpr unsigned DDL_LOG_PHASE_POS= 2; +static constexpr unsigned DDL_LOG_NEXT_ENTRY_POS= 4; +/* Flags to remember something unique about the query, like if .frm was used */ +static constexpr unsigned DDL_LOG_FLAG_POS= 8; +/* Used to store XID entry that was written to binary log */ +static constexpr unsigned DDL_LOG_XID_POS= 10; +/* Used to store unique uuid from the .frm file */ +static constexpr unsigned DDL_LOG_UUID_POS= 18; +/* ID_POS can be used to store something unique, like file size (4 bytes) */ +static constexpr unsigned DDL_LOG_ID_POS= DDL_LOG_UUID_POS + MY_UUID_SIZE; +static constexpr unsigned DDL_LOG_END_POS= DDL_LOG_ID_POS + 8; + +/* + Position to where names are stored in the ddl log blocks. The current + value is stored in the header and can thus be changed if we need more + space for constants in the header than what is between DDL_LOG_ID_POS and + DDL_LOG_TMP_NAME_POS. +*/ +static constexpr unsigned DDL_LOG_TMP_NAME_POS= 56; + +/* Definitions for the ddl log header, the first block in the file */ +/* IO_SIZE is stored in the header and can thus be changed */ +static constexpr unsigned DDL_LOG_IO_SIZE= IO_SIZE; + +/* Header is stored in positions 0-3 */ +static constexpr unsigned DDL_LOG_IO_SIZE_POS= 4; +static constexpr unsigned DDL_LOG_NAME_OFFSET_POS= 6; +/* Marks if we have done a backup of the ddl log */ +static constexpr unsigned DDL_LOG_BACKUP_OFFSET_POS= 8; +/* Sum of the above variables */ +static constexpr unsigned DDL_LOG_HEADER_SIZE= 4+2+2+1; + +static void ddl_log_free_lists(); + +/** + Sync the ddl log file. + + @return Operation status + @retval FALSE Success + @retval TRUE Error +*/ + +static bool ddl_log_sync_file() +{ + DBUG_ENTER("ddl_log_sync_file"); + DBUG_RETURN(mysql_file_sync(global_ddl_log.file_id, MYF(MY_WME))); +} + +/* Same as above, but ensure we have the LOCK_gdl locked */ + +static bool ddl_log_sync_no_lock() +{ + DBUG_ENTER("ddl_log_sync_no_lock"); + + mysql_mutex_assert_owner(&LOCK_gdl); + DBUG_RETURN(ddl_log_sync_file()); +} + + +/** + Create ddl log file name. + @param file_name Filename setup +*/ + +static inline void create_ddl_log_file_name(char *file_name, bool backup) +{ + fn_format(file_name, opt_ddl_recovery_file, mysql_data_home, + backup ? "-backup.log" : ".log", MYF(MY_REPLACE_EXT)); +} + + +/** + Write ddl log header. + + @return Operation status + @retval TRUE Error + @retval FALSE Success +*/ + +static bool write_ddl_log_header() +{ + uchar header[DDL_LOG_HEADER_SIZE]; + DBUG_ENTER("write_ddl_log_header"); + + memcpy(&header, ddl_log_file_magic, DDL_LOG_MAGIC_LENGTH); + int2store(&header[DDL_LOG_IO_SIZE_POS], global_ddl_log.io_size); + int2store(&header[DDL_LOG_NAME_OFFSET_POS], global_ddl_log.name_pos); + header[DDL_LOG_BACKUP_OFFSET_POS]= 0; + + if (mysql_file_pwrite(global_ddl_log.file_id, + header, sizeof(header), 0, + MYF(MY_WME | MY_NABP))) + DBUG_RETURN(TRUE); + DBUG_RETURN(ddl_log_sync_file()); +} + + +/* + Mark in the ddl log file that we have made a backup of it +*/ + +static void mark_ddl_log_header_backup_done() +{ + uchar marker[1]; + marker[0]= 1; + (void) mysql_file_pwrite(global_ddl_log.file_id, + marker, sizeof(marker), DDL_LOG_BACKUP_OFFSET_POS, + MYF(MY_WME | MY_NABP)); +} + + +void ddl_log_create_backup_file() +{ + char org_file_name[FN_REFLEN]; + char backup_file_name[FN_REFLEN]; + + create_ddl_log_file_name(org_file_name, 0); + create_ddl_log_file_name(backup_file_name, 1); + + my_copy(org_file_name, backup_file_name, MYF(MY_WME)); + mark_ddl_log_header_backup_done(); +} + + +/** + Read one entry from ddl log file. + + @param entry_pos Entry number to read + + @return Operation status + @retval true Error + @retval false Success +*/ + +static bool read_ddl_log_file_entry(uint entry_pos) +{ + uchar *file_entry_buf= global_ddl_log.file_entry_buf; + size_t io_size= global_ddl_log.io_size; + DBUG_ENTER("read_ddl_log_file_entry"); + + mysql_mutex_assert_owner(&LOCK_gdl); + DBUG_RETURN (mysql_file_pread(global_ddl_log.file_id, + file_entry_buf, io_size, + io_size * entry_pos, + MYF(MY_WME | MY_NABP))); +} + + +/** + Write one entry to ddl log file. + + @param entry_pos Entry number to write + + @return + @retval true Error + @retval false Success +*/ + +static bool write_ddl_log_file_entry(uint entry_pos) +{ + bool error= FALSE; + File file_id= global_ddl_log.file_id; + uchar *file_entry_buf= global_ddl_log.file_entry_buf; + DBUG_ENTER("write_ddl_log_file_entry"); + + mysql_mutex_assert_owner(&LOCK_gdl); // To be removed + DBUG_RETURN(mysql_file_pwrite(file_id, file_entry_buf, + global_ddl_log.io_size, + global_ddl_log.io_size * entry_pos, + MYF(MY_WME | MY_NABP))); + DBUG_RETURN(error); +} + + +/** + Update phase of ddl log entry + + @param entry_pos ddl_log entry to update + @param phase New phase + + @return + @retval 0 ok + @retval 1 Write error. Error given + + This is done without locks as it's guaranteed to be atomic +*/ + +static bool update_phase(uint entry_pos, uchar phase) +{ + DBUG_ENTER("update_phase"); + DBUG_PRINT("ddl_log", ("pos: %u phase: %u", entry_pos, (uint) phase)); + + DBUG_RETURN(mysql_file_pwrite(global_ddl_log.file_id, &phase, 1, + global_ddl_log.io_size * entry_pos + + DDL_LOG_PHASE_POS, + MYF(MY_WME | MY_NABP)) || + ddl_log_sync_file()); +} + + +/* + Update flags in ddl log entry + + This is not synced as it usually followed by a phase change, which will sync. +*/ + +static bool update_flags(uint entry_pos, uint16 flags) +{ + uchar buff[2]; + DBUG_ENTER("update_flags"); + + int2store(buff, flags); + DBUG_RETURN(mysql_file_pwrite(global_ddl_log.file_id, buff, sizeof(buff), + global_ddl_log.io_size * entry_pos + + DDL_LOG_FLAG_POS, + MYF(MY_WME | MY_NABP))); +} + + +static bool update_next_entry_pos(uint entry_pos, uint next_entry) +{ + uchar buff[4]; + DBUG_ENTER("update_next_entry_pos"); + + DBUG_PRINT("ddl_log", ("pos: %u->%u", entry_pos, next_entry)); + + int4store(buff, next_entry); + DBUG_RETURN(mysql_file_pwrite(global_ddl_log.file_id, buff, sizeof(buff), + global_ddl_log.io_size * entry_pos + + DDL_LOG_NEXT_ENTRY_POS, + MYF(MY_WME | MY_NABP))); +} + + +static bool update_xid(uint entry_pos, ulonglong xid) +{ + uchar buff[8]; + DBUG_ENTER("update_xid"); + + int8store(buff, xid); + DBUG_RETURN(mysql_file_pwrite(global_ddl_log.file_id, buff, sizeof(buff), + global_ddl_log.io_size * entry_pos + + DDL_LOG_XID_POS, + MYF(MY_WME | MY_NABP)) || + ddl_log_sync_file()); +} + + +static bool update_unique_id(uint entry_pos, ulonglong id) +{ + uchar buff[8]; + DBUG_ENTER("update_unique_xid"); + + int8store(buff, id); + DBUG_RETURN(mysql_file_pwrite(global_ddl_log.file_id, buff, sizeof(buff), + global_ddl_log.io_size * entry_pos + + DDL_LOG_ID_POS, + MYF(MY_WME | MY_NABP)) || + ddl_log_sync_file()); +} + + +/* + Disable an execute entry + + @param entry_pos ddl_log entry to update + + Notes: + We don't need sync here as this is mainly done during + recover phase to mark already done entries. We instead sync all entries + at the same time. +*/ + +static bool disable_execute_entry(uint entry_pos) +{ + uchar buff[1]; + DBUG_ENTER("disable_execute_entry"); + DBUG_PRINT("ddl_log", ("pos: {%u}", entry_pos)); + + buff[0]= DDL_LOG_IGNORE_ENTRY_CODE; + DBUG_RETURN(mysql_file_pwrite(global_ddl_log.file_id, buff, sizeof(buff), + global_ddl_log.io_size * entry_pos + + DDL_LOG_ENTRY_TYPE_POS, + MYF(MY_WME | MY_NABP))); +} + +/* + Disable an execute entry +*/ + +bool ddl_log_disable_execute_entry(DDL_LOG_MEMORY_ENTRY **active_entry) +{ + bool res= disable_execute_entry((*active_entry)->entry_pos); + ddl_log_sync_no_lock(); + return res; +} + + +/* + Check if an executive entry is active + + @return 0 Entry is active + @return 1 Entry is not active +*/ + +static bool is_execute_entry_active(uint entry_pos) +{ + uchar buff[1]; + DBUG_ENTER("disable_execute_entry"); + + if (mysql_file_pread(global_ddl_log.file_id, buff, sizeof(buff), + global_ddl_log.io_size * entry_pos + + DDL_LOG_ENTRY_TYPE_POS, + MYF(MY_WME | MY_NABP))) + DBUG_RETURN(1); + DBUG_RETURN(buff[0] == (uchar) DDL_LOG_EXECUTE_CODE); +} + + +/** + Read header of ddl log file. + + 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. + + This is read only once at server startup, so no mutex is needed. + + @return Last entry in ddl log (0 if no entries). + @return -1 if log could not be opened or could not be read +*/ + +static int read_ddl_log_header(const char *file_name) +{ + uchar header[DDL_LOG_HEADER_SIZE]; + int max_entry; + int file_id; + uint io_size; + DBUG_ENTER("read_ddl_log_header"); + + if ((file_id= mysql_file_open(key_file_global_ddl_log, + file_name, + O_RDWR | O_BINARY, MYF(0))) < 0) + DBUG_RETURN(-1); + + if (mysql_file_read(file_id, + header, sizeof(header), MYF(MY_WME | MY_NABP))) + { + /* Write message into error log */ + sql_print_error("DDL_LOG: Failed to read ddl log file '%s' during " + "recovery", file_name); + goto err; + } + + if (memcmp(header, ddl_log_file_magic, 4)) + { + /* Probably upgrade from MySQL 10.5 or earlier */ + sql_print_warning("DDL_LOG: Wrong header in %s. Assuming it is an old " + "recovery file from MariaDB 10.5 or earlier. " + "Skipping DDL recovery", file_name); + goto err; + } + + io_size= uint2korr(&header[DDL_LOG_IO_SIZE_POS]); + global_ddl_log.name_pos= uint2korr(&header[DDL_LOG_NAME_OFFSET_POS]); + global_ddl_log.backup_done= header[DDL_LOG_BACKUP_OFFSET_POS]; + + max_entry= (uint) (mysql_file_seek(file_id, 0L, MY_SEEK_END, MYF(0)) / + io_size); + if (max_entry) + max_entry--; // Don't count first block + + if (!(global_ddl_log.file_entry_buf= (uchar*) + my_malloc(key_memory_DDL_LOG_MEMORY_ENTRY, io_size, + MYF(MY_WME | MY_ZEROFILL)))) + goto err; + + global_ddl_log.open= TRUE; + global_ddl_log.created= 0; + global_ddl_log.file_id= file_id; + global_ddl_log.num_entries= max_entry; + global_ddl_log.io_size= io_size; + DBUG_RETURN(max_entry); + +err: + if (file_id >= 0) + my_close(file_id, MYF(0)); + /* We return -1 to force the ddl log to be re-created */ + DBUG_RETURN(-1); +} + + +/* + Store and read strings in ddl log buffers + + Format is: + 2 byte: length (not counting end \0) + X byte: string value of length 'length' + 1 byte: \0 +*/ + +static uchar *store_string(uchar *pos, uchar *end, const LEX_CSTRING *str) +{ + uint32 length= (uint32) str->length; + if (unlikely(pos + 2 + length + 1 > end)) + { + DBUG_ASSERT(0); + return end; // Overflow + } + + int2store(pos, length); + if (likely(length)) + memcpy(pos+2, str->str, length); + pos[2+length]= 0; // Store end \0 + return pos + 2 + length +1; +} + + +static LEX_CSTRING get_string(uchar **pos, const uchar *end) +{ + LEX_CSTRING tmp; + uint32 length; + if (likely(*pos + 3 <= end)) + { + length= uint2korr(*pos); + if (likely(*pos + 2 + length + 1 <= end)) + { + char *str= (char*) *pos+2; + *pos= *pos + 2 + length + 1; + tmp.str= str; + tmp.length= length; + return tmp; + } + } + /* + Overflow on read, should never happen + Set *pos to end to ensure any future calls also returns empty string + */ + DBUG_ASSERT(0); + *pos= (uchar*) end; + tmp.str= ""; + tmp.length= 0; + return tmp; +} + + +/** + Convert from ddl_log_entry struct to file_entry_buf binary blob. + + @param ddl_log_entry filled in ddl_log_entry struct. +*/ + +static void set_global_from_ddl_log_entry(const DDL_LOG_ENTRY *ddl_log_entry) +{ + uchar *file_entry_buf= global_ddl_log.file_entry_buf, *pos, *end; + + mysql_mutex_assert_owner(&LOCK_gdl); + + file_entry_buf[DDL_LOG_ENTRY_TYPE_POS]= (uchar) ddl_log_entry->entry_type; + file_entry_buf[DDL_LOG_ACTION_TYPE_POS]= (uchar) ddl_log_entry->action_type; + file_entry_buf[DDL_LOG_PHASE_POS]= (uchar) ddl_log_entry->phase; + int4store(file_entry_buf+DDL_LOG_NEXT_ENTRY_POS, ddl_log_entry->next_entry); + int2store(file_entry_buf+DDL_LOG_FLAG_POS, ddl_log_entry->flags); + int8store(file_entry_buf+DDL_LOG_XID_POS, ddl_log_entry->xid); + memcpy(file_entry_buf+DDL_LOG_UUID_POS, ddl_log_entry->uuid, MY_UUID_SIZE); + int8store(file_entry_buf+DDL_LOG_ID_POS, ddl_log_entry->unique_id); + bzero(file_entry_buf+DDL_LOG_END_POS, + global_ddl_log.name_pos - DDL_LOG_END_POS); + + pos= file_entry_buf + global_ddl_log.name_pos; + end= file_entry_buf + global_ddl_log.io_size; + + pos= store_string(pos, end, &ddl_log_entry->handler_name); + pos= store_string(pos, end, &ddl_log_entry->db); + pos= store_string(pos, end, &ddl_log_entry->name); + pos= store_string(pos, end, &ddl_log_entry->from_handler_name); + pos= store_string(pos, end, &ddl_log_entry->from_db); + pos= store_string(pos, end, &ddl_log_entry->from_name); + pos= store_string(pos, end, &ddl_log_entry->tmp_name); + pos= store_string(pos, end, &ddl_log_entry->extra_name); + bzero(pos, global_ddl_log.io_size - (pos - file_entry_buf)); +} + + +/* + Calculate how much space we have left in the log entry for one string + + This can be used to check if we have space to store the query string + in the block. +*/ + +static size_t ddl_log_free_space_in_entry(const DDL_LOG_ENTRY *ddl_log_entry) +{ + size_t length= global_ddl_log.name_pos + 3*7; // 3 byte per string below + length+= ddl_log_entry->handler_name.length; + length+= ddl_log_entry->db.length; + length+= ddl_log_entry->name.length; + length+= ddl_log_entry->from_handler_name.length; + length+= ddl_log_entry->from_db.length; + length+= ddl_log_entry->from_name.length; + length+= ddl_log_entry->tmp_name.length; + length+= ddl_log_entry->extra_name.length; + return global_ddl_log.io_size - length - 3; // 3 is for storing next string +} + + +/** + Convert from file_entry_buf binary blob to ddl_log_entry struct. + + @param[out] ddl_log_entry struct to fill in. + + @note Strings (names) are pointing to the global_ddl_log structure, + so LOCK_gdl needs to be hold until they are read or copied. +*/ + +static void set_ddl_log_entry_from_global(DDL_LOG_ENTRY *ddl_log_entry, + const uint read_entry) +{ + uchar *file_entry_buf= global_ddl_log.file_entry_buf, *pos; + const uchar *end= file_entry_buf + global_ddl_log.io_size; + uchar single_char; + + mysql_mutex_assert_owner(&LOCK_gdl); + 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->flags= uint2korr(file_entry_buf + DDL_LOG_FLAG_POS); + ddl_log_entry->xid= uint8korr(file_entry_buf + DDL_LOG_XID_POS); + ddl_log_entry->unique_id= uint8korr(file_entry_buf + DDL_LOG_ID_POS); + memcpy(ddl_log_entry->uuid, file_entry_buf+ DDL_LOG_UUID_POS, MY_UUID_SIZE); + + pos= file_entry_buf + global_ddl_log.name_pos; + ddl_log_entry->handler_name= get_string(&pos, end); + ddl_log_entry->db= get_string(&pos, end); + ddl_log_entry->name= get_string(&pos, end); + ddl_log_entry->from_handler_name= get_string(&pos, end); + ddl_log_entry->from_db= get_string(&pos, end); + ddl_log_entry->from_name= get_string(&pos, end); + ddl_log_entry->tmp_name= get_string(&pos, end); + ddl_log_entry->extra_name= get_string(&pos, end); +} + + +/** + Read a ddl log entry. + + Read a specified entry in the ddl log. + + @param read_entry Number of entry to read + @param[out] entry_info Information from entry + + @return Operation status + @retval TRUE Error + @retval FALSE Success +*/ + +static bool read_ddl_log_entry(uint read_entry, DDL_LOG_ENTRY *ddl_log_entry) +{ + DBUG_ENTER("read_ddl_log_entry"); + + if (read_ddl_log_file_entry(read_entry)) + { + sql_print_error("DDL_LOG: Failed to read entry %u", read_entry); + DBUG_RETURN(TRUE); + } + set_ddl_log_entry_from_global(ddl_log_entry, read_entry); + DBUG_RETURN(FALSE); +} + + +/** + Create the ddl log file + + @return Operation status + @retval TRUE Error + @retval FALSE Success +*/ + +static bool create_ddl_log() +{ + char file_name[FN_REFLEN]; + DBUG_ENTER("create_ddl_log"); + + ddl_log_free_lists(); + global_ddl_log.open= 0; + global_ddl_log.created= 1; + global_ddl_log.num_entries= 0; + global_ddl_log.name_pos= DDL_LOG_TMP_NAME_POS; + global_ddl_log.num_entries= 0; + global_ddl_log.backup_done= 0; + + /* + Fix file_entry_buf if the old log had a different io_size or if open of old + log didn't succeed. + */ + if (global_ddl_log.io_size != DDL_LOG_IO_SIZE) + { + uchar *ptr= (uchar*) + my_realloc(key_memory_DDL_LOG_MEMORY_ENTRY, + global_ddl_log.file_entry_buf, DDL_LOG_IO_SIZE, + MYF(MY_WME | MY_ALLOW_ZERO_PTR)); + if (ptr) // Resize succeded */ + { + global_ddl_log.file_entry_buf= ptr; + global_ddl_log.io_size= DDL_LOG_IO_SIZE; + } + if (!global_ddl_log.file_entry_buf) + DBUG_RETURN(TRUE); + } + DBUG_ASSERT(global_ddl_log.file_entry_buf); + bzero(global_ddl_log.file_entry_buf, global_ddl_log.io_size); + create_ddl_log_file_name(file_name, 0); + if ((global_ddl_log.file_id= + mysql_file_create(key_file_global_ddl_log, + file_name, CREATE_MODE, + O_RDWR | O_TRUNC | O_BINARY, + MYF(MY_WME | ME_ERROR_LOG))) < 0) + { + /* Couldn't create ddl log file, this is serious error */ + sql_print_error("DDL_LOG: Failed to create ddl log file: %s", file_name); + my_free(global_ddl_log.file_entry_buf); + global_ddl_log.file_entry_buf= 0; + DBUG_RETURN(TRUE); + } + if (write_ddl_log_header()) + { + (void) mysql_file_close(global_ddl_log.file_id, MYF(MY_WME)); + my_free(global_ddl_log.file_entry_buf); + global_ddl_log.file_entry_buf= 0; + DBUG_RETURN(TRUE); + } + global_ddl_log.open= TRUE; + DBUG_RETURN(FALSE); +} + + +/** + Open ddl log and initialise ddl log variables + Create a backuip of of +*/ + +bool ddl_log_initialize() +{ + char file_name[FN_REFLEN]; + DBUG_ENTER("ddl_log_initialize"); + + bzero(&global_ddl_log, sizeof(global_ddl_log)); + global_ddl_log.file_id= (File) -1; + global_ddl_log.initialized= 1; + + mysql_mutex_init(key_LOCK_gdl, &LOCK_gdl, MY_MUTEX_INIT_SLOW); + + create_ddl_log_file_name(file_name, 0); + if (unlikely(read_ddl_log_header(file_name) < 0)) + { + /* Fatal error, log not opened. Recreate it */ + if (create_ddl_log()) + DBUG_RETURN(1); + } + DBUG_RETURN(0); +} + + +/** + @brief Deactivate an individual entry. + + @details For complex rename operations we need to deactivate individual + entries. + + During replace operations where we start with an existing table called + t1 and a replacement table called t1#temp or something else and where + we want to delete t1 and rename t1#temp to t1 this is not possible to + do in a safe manner unless the ddl log is informed of the phases in + the change. + + Delete actions are 1-phase actions that can be ignored immediately after + being executed. + Rename actions from x to y is also a 1-phase action since there is no + interaction with any other handlers named x and y. + Replace action where drop y and x -> y happens needs to be a two-phase + action. Thus the first phase will drop y and the second phase will + rename x -> y. + + @param entry_pos Entry position of record to change + + @return Operation status + @retval TRUE Error + @retval FALSE Success +*/ + +static bool ddl_log_increment_phase_no_lock(uint entry_pos) +{ + uchar *file_entry_buf= global_ddl_log.file_entry_buf; + DBUG_ENTER("ddl_log_increment_phase_no_lock"); + + mysql_mutex_assert_owner(&LOCK_gdl); + if (!read_ddl_log_file_entry(entry_pos)) + { + ddl_log_entry_code code= ((ddl_log_entry_code) + file_entry_buf[DDL_LOG_ENTRY_TYPE_POS]); + ddl_log_action_code action= ((ddl_log_action_code) + file_entry_buf[DDL_LOG_ACTION_TYPE_POS]); + + if (code == DDL_LOG_ENTRY_CODE && action < (uint) DDL_LOG_LAST_ACTION) + { + /* + Log entry: + Increase the phase by one. If complete mark it done (IGNORE). + */ + char phase= file_entry_buf[DDL_LOG_PHASE_POS]+ 1; + if (ddl_log_entry_phases[action] <= phase) + { + DBUG_ASSERT(phase == ddl_log_entry_phases[action]); + /* Same effect as setting DDL_LOG_IGNORE_ENTRY_CODE */ + phase= DDL_LOG_FINAL_PHASE; + } + file_entry_buf[DDL_LOG_PHASE_POS]= phase; + if (update_phase(entry_pos, phase)) + DBUG_RETURN(TRUE); + } + else + { + /* + Trying to deativate an execute entry or already deactive entry. + This should not happen + */ + DBUG_ASSERT(0); + } + } + else + { + sql_print_error("DDL_LOG: Failed in reading entry before updating it"); + DBUG_RETURN(TRUE); + } + DBUG_RETURN(FALSE); +} + + +/* + Increment phase and sync ddl log. This expects LOCK_gdl to be locked +*/ + +static bool increment_phase(uint entry_pos) +{ + if (ddl_log_increment_phase_no_lock(entry_pos)) + return 1; + ddl_log_sync_no_lock(); + return 0; +} + + +/* + Ignore errors from the file system about: + - Non existing tables or file (from drop table or delete file) + - Error about tables files that already exists. + - Error from delete table (from Drop_table_error_handler) + - Wrong trigger definer (from Drop_table_error_handler) +*/ + +class ddl_log_error_handler : public Internal_error_handler +{ +public: + int handled_errors; + int unhandled_errors; + int first_error; + bool only_ignore_non_existing_errors; + + ddl_log_error_handler() : handled_errors(0), unhandled_errors(0), + first_error(0), only_ignore_non_existing_errors(0) + {} + + bool handle_condition(THD *thd, + uint sql_errno, + const char* sqlstate, + Sql_condition::enum_warning_level *level, + const char* msg, + Sql_condition ** cond_hdl) + { + *cond_hdl= NULL; + if (non_existing_table_error(sql_errno) || + (!only_ignore_non_existing_errors && + (sql_errno == EE_LINK || + sql_errno == EE_DELETE || sql_errno == ER_TRG_NO_DEFINER))) + { + handled_errors++; + return TRUE; + } + if (!first_error) + first_error= sql_errno; + + if (*level == Sql_condition::WARN_LEVEL_ERROR) + unhandled_errors++; + return FALSE; + } + bool safely_trapped_errors() + { + return (handled_errors > 0 && unhandled_errors == 0); + } +}; + + +/* + Build a filename for a table, trigger file or .frm + Delete also any temporary file suffixed with ~ + + @return 0 Temporary file deleted + @return 1 No temporary file found +*/ + +static bool build_filename_and_delete_tmp_file(char *path, size_t path_length, + const LEX_CSTRING *db, + const LEX_CSTRING *name, + const char *ext, + PSI_file_key psi_key) +{ + bool deleted; + uint length= build_table_filename(path, path_length-1, + db->str, name->str, ext, 0); + path[length]= '~'; + path[length+1]= 0; + deleted= mysql_file_delete(psi_key, path, MYF(0)) != 0; + path[length]= 0; + return deleted; +} + + +static LEX_CSTRING end_comment= +{ STRING_WITH_LEN(" /* generated by ddl recovery */")}; + + +/** + Log DROP query to binary log with comment + + This function is only run during recovery +*/ + +static void ddl_log_to_binary_log(THD *thd, String *query) +{ + LEX_CSTRING thd_db= thd->db; + + lex_string_set(&thd->db, recovery_state.current_db); + query->length(query->length()-1); // Removed end ',' + query->append(&end_comment); + mysql_mutex_unlock(&LOCK_gdl); + (void) thd->binlog_query(THD::STMT_QUERY_TYPE, + query->ptr(), query->length(), + TRUE, FALSE, FALSE, 0); + mysql_mutex_lock(&LOCK_gdl); + thd->db= thd_db; +} + + +/** + Log DROP TABLE/VIEW to binary log when needed + + @result 0 Nothing was done + @result 1 Query was logged to binary log & query was reset + + Logging happens in the following cases + - This is the last DROP entry + - The query could be longer than max_packet_length if we would add another + table name to the query + + When we log, we always log all found tables and views at the same time. This + is done to simply the exceute code as otherwise we would have to keep + information of what was logged. +*/ + +static bool ddl_log_drop_to_binary_log(THD *thd, DDL_LOG_ENTRY *ddl_log_entry, + String *query) +{ + DBUG_ENTER("ddl_log_drop_to_binary_log"); + if (mysql_bin_log.is_open()) + { + if (!ddl_log_entry->next_entry || + query->length() + end_comment.length + NAME_LEN + 100 > + thd->variables.max_allowed_packet) + { + if (recovery_state.drop_table.length() > + recovery_state.drop_table_init_length) + { + ddl_log_to_binary_log(thd, &recovery_state.drop_table); + recovery_state.drop_table.length(recovery_state.drop_table_init_length); + } + if (recovery_state.drop_view.length() > + recovery_state.drop_view_init_length) + { + ddl_log_to_binary_log(thd, &recovery_state.drop_view); + recovery_state.drop_view.length(recovery_state.drop_view_init_length); + } + DBUG_RETURN(1); + } + } + DBUG_RETURN(0); +} + +/* + Create a new handler based on handlerton name +*/ + +static handler *create_handler(THD *thd, MEM_ROOT *mem_root, + LEX_CSTRING *name) +{ + handlerton *hton; + handler *file; + plugin_ref plugin= my_plugin_lock_by_name(thd, name, + MYSQL_STORAGE_ENGINE_PLUGIN); + if (!plugin) + { + my_error(ER_UNKNOWN_STORAGE_ENGINE, MYF(ME_ERROR_LOG), name->str); + return 0; + } + hton= plugin_hton(plugin); + if (!ha_storage_engine_is_enabled(hton)) + { + my_error(ER_STORAGE_ENGINE_DISABLED, MYF(ME_ERROR_LOG), name->str); + return 0; + } + if ((file= hton->create(hton, (TABLE_SHARE*) 0, mem_root))) + file->init(); + return file; +} + + +/* + Rename a table and its .frm file for a ddl_log_entry + + We first rename the table and then the .frm file as some engines, + like connect, needs the .frm file to exists to be able to do an rename. +*/ + +static void execute_rename_table(DDL_LOG_ENTRY *ddl_log_entry, handler *file, + const LEX_CSTRING *from_db, + const LEX_CSTRING *from_table, + const LEX_CSTRING *to_db, + const LEX_CSTRING *to_table, + uint flags, + char *from_path, char *to_path) +{ + uint to_length=0, fr_length=0; + DBUG_ENTER("execute_rename_table"); + + if (file->needs_lower_case_filenames()) + { + build_lower_case_table_filename(from_path, FN_REFLEN, + from_db, from_table, + flags & FN_FROM_IS_TMP); + build_lower_case_table_filename(to_path, FN_REFLEN, + to_db, to_table, flags & FN_TO_IS_TMP); + } + else + { + fr_length= build_table_filename(from_path, FN_REFLEN, + from_db->str, from_table->str, "", + flags & FN_FROM_IS_TMP); + to_length= build_table_filename(to_path, FN_REFLEN, + to_db->str, to_table->str, "", + flags & FN_TO_IS_TMP); + } + file->ha_rename_table(from_path, to_path); + if (file->needs_lower_case_filenames()) + { + /* + We have to rebuild the file names as the .frm file should be used + without lower case conversion + */ + fr_length= build_table_filename(from_path, FN_REFLEN, + from_db->str, from_table->str, reg_ext, + flags & FN_FROM_IS_TMP); + to_length= build_table_filename(to_path, FN_REFLEN, + to_db->str, to_table->str, reg_ext, + flags & FN_TO_IS_TMP); + } + else + { + strmov(from_path+fr_length, reg_ext); + strmov(to_path+to_length, reg_ext); + } + if (!access(from_path, F_OK)) + (void) mysql_file_rename(key_file_frm, from_path, to_path, MYF(MY_WME)); + DBUG_VOID_RETURN; +} + + +/* + Update triggers + + If swap_tables == 0 (Restoring the original in case of failed rename) + Convert triggers for db.name -> from_db.from_name + else (Doing the rename in case of ALTER TABLE ... RENAME) + Convert triggers for from_db.from_name -> db.extra_name +*/ + +static void rename_triggers(THD *thd, DDL_LOG_ENTRY *ddl_log_entry, + bool swap_tables) +{ + LEX_CSTRING to_table, from_table, to_db, from_db, from_converted_name; + char to_path[FN_REFLEN+1], from_path[FN_REFLEN+1], conv_path[FN_REFLEN+1]; + + if (!swap_tables) + { + from_db= ddl_log_entry->db; + from_table= ddl_log_entry->name; + to_db= ddl_log_entry->from_db; + to_table= ddl_log_entry->from_name; + } + else + { + from_db= ddl_log_entry->from_db; + from_table= ddl_log_entry->from_name; + to_db= ddl_log_entry->db; + to_table= ddl_log_entry->extra_name; + } + + build_filename_and_delete_tmp_file(from_path, sizeof(from_path), + &from_db, &from_table, + TRG_EXT, key_file_trg); + build_filename_and_delete_tmp_file(to_path, sizeof(to_path), + &to_db, &to_table, + TRG_EXT, key_file_trg); + if (lower_case_table_names) + { + uint errors; + from_converted_name.str= conv_path; + from_converted_name.length= + strconvert(system_charset_info, from_table.str, from_table.length, + files_charset_info, conv_path, FN_REFLEN, &errors); + } + else + from_converted_name= from_table; + + if (!access(to_path, F_OK)) + { + /* + The original file was never renamed or we crashed in recovery + just after renaming back the file. + In this case the current file is correct and we can remove any + left over copied files + */ + (void) mysql_file_delete(key_file_trg, from_path, MYF(0)); + } + else if (!access(from_path, F_OK)) + { + /* .TRG file was renamed. Rename it back */ + /* + We have to create a MDL lock as change_table_names() checks that we + have a mdl locks for the table + */ + MDL_request mdl_request; + TRIGGER_RENAME_PARAM trigger_param; + int error __attribute__((unused)); + MDL_REQUEST_INIT(&mdl_request, MDL_key::TABLE, + from_db.str, + from_converted_name.str, + MDL_EXCLUSIVE, MDL_EXPLICIT); + error= thd->mdl_context.acquire_lock(&mdl_request, 1); + /* acquire_locks() should never fail during recovery */ + DBUG_ASSERT(error == 0); + + (void) Table_triggers_list::prepare_for_rename(thd, + &trigger_param, + &from_db, + &from_table, + &from_converted_name, + &to_db, + &to_table); + (void) Table_triggers_list::change_table_name(thd, + &trigger_param, + &from_db, + &from_table, + &from_converted_name, + &to_db, + &to_table); + thd->mdl_context.release_lock(mdl_request.ticket); + } +} + + +/* + Update stat tables + + If swap_tables == 0 + Convert stats for from_db.from_table -> db.name + else + Convert stats for db.name -> from_db.from_table +*/ + +static void rename_in_stat_tables(THD *thd, DDL_LOG_ENTRY *ddl_log_entry, + bool swap_tables) +{ + LEX_CSTRING from_table, to_table, from_db, to_db, from_converted_name; + char conv_path[FN_REFLEN+1]; + + if (!swap_tables) + { + from_db= ddl_log_entry->db; + from_table= ddl_log_entry->name; + to_db= ddl_log_entry->from_db; + to_table= ddl_log_entry->from_name; + } + else + { + from_db= ddl_log_entry->from_db; + from_table= ddl_log_entry->from_name; + to_db= ddl_log_entry->db; + to_table= ddl_log_entry->extra_name; + } + if (lower_case_table_names) + { + uint errors; + from_converted_name.str= conv_path; + from_converted_name.length= + strconvert(system_charset_info, from_table.str, from_table.length, + files_charset_info, conv_path, FN_REFLEN, &errors); + } + else + from_converted_name= from_table; + + (void) rename_table_in_stat_tables(thd, + &from_db, + &from_converted_name, + &to_db, + &to_table); +} + + +/** + Execute one action in a ddl log entry + + @param ddl_log_entry Information in action entry to execute + + @return Operation status + @retval TRUE Error + @retval FALSE Success +*/ + +static int ddl_log_execute_action(THD *thd, MEM_ROOT *mem_root, + DDL_LOG_ENTRY *ddl_log_entry) +{ + LEX_CSTRING handler_name; + handler *file= NULL; + char to_path[FN_REFLEN+1], from_path[FN_REFLEN+1]; + handlerton *hton= 0; + ddl_log_error_handler no_such_table_handler; + uint entry_pos= ddl_log_entry->entry_pos; + int error; + bool frm_action= FALSE; + DBUG_ENTER("ddl_log_execute_action"); + + mysql_mutex_assert_owner(&LOCK_gdl); + DBUG_PRINT("ddl_log", + ("pos: %u=>%u->%u type: %u action: %u (%s) phase: %u " + "handler: '%s' name: '%s' from_name: '%s' tmp_name: '%s'", + recovery_state.execute_entry_pos, + ddl_log_entry->entry_pos, + ddl_log_entry->next_entry, + (uint) ddl_log_entry->entry_type, + (uint) ddl_log_entry->action_type, + ddl_log_action_name[ddl_log_entry->action_type], + (uint) ddl_log_entry->phase, + ddl_log_entry->handler_name.str, + ddl_log_entry->name.str, + ddl_log_entry->from_name.str, + ddl_log_entry->tmp_name.str)); + + if (ddl_log_entry->entry_type == DDL_LOG_IGNORE_ENTRY_CODE || + ddl_log_entry->phase == DDL_LOG_FINAL_PHASE) + DBUG_RETURN(FALSE); + + handler_name= ddl_log_entry->handler_name; + thd->push_internal_handler(&no_such_table_handler); + + if (!strcmp(ddl_log_entry->handler_name.str, reg_ext)) + frm_action= TRUE; + else if (ddl_log_entry->handler_name.length) + { + if (!(file= create_handler(thd, mem_root, &handler_name))) + goto end; + hton= file->ht; + } + + 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.str, reg_ext, NullS); + if (unlikely((error= mysql_file_delete(key_file_frm, to_path, + MYF(MY_WME | + MY_IGNORE_ENOENT))))) + break; +#ifdef WITH_PARTITION_STORAGE_ENGINE + strxmov(to_path, ddl_log_entry->name.str, PAR_EXT, NullS); + (void) mysql_file_delete(key_file_partition_ddl_log, to_path, + MYF(0)); +#endif + } + else + { + if (unlikely((error= hton->drop_table(hton, ddl_log_entry->name.str)))) + { + if (!non_existing_table_error(error)) + break; + } + } + if (increment_phase(entry_pos)) + break; + error= 0; + 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. + */ + /* fall through */ + case DDL_LOG_RENAME_ACTION: + { + error= TRUE; + if (frm_action) + { + strxmov(to_path, ddl_log_entry->name.str, reg_ext, NullS); + strxmov(from_path, ddl_log_entry->from_name.str, reg_ext, NullS); + (void) mysql_file_rename(key_file_frm, from_path, to_path, MYF(MY_WME)); +#ifdef WITH_PARTITION_STORAGE_ENGINE + strxmov(to_path, ddl_log_entry->name.str, PAR_EXT, NullS); + strxmov(from_path, ddl_log_entry->from_name.str, PAR_EXT, NullS); + (void) mysql_file_rename(key_file_partition_ddl_log, from_path, to_path, + MYF(MY_WME)); +#endif + } + else + (void) file->ha_rename_table(ddl_log_entry->from_name.str, + ddl_log_entry->name.str); + if (increment_phase(entry_pos)) + break; + break; + } + case DDL_LOG_EXCHANGE_ACTION: + { + /* We hold LOCK_gdl, so we can alter global_ddl_log.file_entry_buf */ + uchar *file_entry_buf= global_ddl_log.file_entry_buf; + /* not yet implemented for frm */ + DBUG_ASSERT(!frm_action); + /* + Using a case-switch here to revert all currently done phases, + since it will fall through until the first phase is undone. + */ + switch (ddl_log_entry->phase) { + case EXCH_PHASE_TEMP_TO_FROM: + /* tmp_name -> from_name possibly done */ + (void) file->ha_rename_table(ddl_log_entry->from_name.str, + ddl_log_entry->tmp_name.str); + /* decrease the phase and sync */ + file_entry_buf[DDL_LOG_PHASE_POS]--; + if (write_ddl_log_file_entry(entry_pos)) + break; + (void) ddl_log_sync_no_lock(); + /* fall through */ + case EXCH_PHASE_FROM_TO_NAME: + /* from_name -> name possibly done */ + (void) file->ha_rename_table(ddl_log_entry->name.str, + ddl_log_entry->from_name.str); + /* decrease the phase and sync */ + file_entry_buf[DDL_LOG_PHASE_POS]--; + if (write_ddl_log_file_entry(entry_pos)) + break; + (void) ddl_log_sync_no_lock(); + /* fall through */ + case EXCH_PHASE_NAME_TO_TEMP: + /* name -> tmp_name possibly done */ + (void) file->ha_rename_table(ddl_log_entry->tmp_name.str, + ddl_log_entry->name.str); + /* disable the entry and sync */ + file_entry_buf[DDL_LOG_ENTRY_TYPE_POS]= DDL_LOG_IGNORE_ENTRY_CODE; + (void) write_ddl_log_file_entry(entry_pos); + (void) ddl_log_sync_no_lock(); + break; + } + break; + } + case DDL_LOG_RENAME_TABLE_ACTION: + { + /* + We should restore things by renaming from + 'entry->name' to 'entry->from_name' + */ + switch (ddl_log_entry->phase) { + case DDL_RENAME_PHASE_TRIGGER: + rename_triggers(thd, ddl_log_entry, 0); + if (increment_phase(entry_pos)) + break; + /* fall through */ + case DDL_RENAME_PHASE_STAT: + /* + Stat tables must be updated last so that we can handle a rename of + a stat table. For now we just rememeber that we have to update it + */ + update_flags(ddl_log_entry->entry_pos, DDL_LOG_FLAG_UPDATE_STAT); + ddl_log_entry->flags|= DDL_LOG_FLAG_UPDATE_STAT; + /* fall through */ + case DDL_RENAME_PHASE_TABLE: + /* Restore frm and table to original names */ + execute_rename_table(ddl_log_entry, file, + &ddl_log_entry->db, &ddl_log_entry->name, + &ddl_log_entry->from_db, &ddl_log_entry->from_name, + 0, + from_path, to_path); + + if (ddl_log_entry->flags & DDL_LOG_FLAG_UPDATE_STAT) + { + /* Update stat tables last */ + rename_in_stat_tables(thd, ddl_log_entry, 0); + } + + /* disable the entry and sync */ + (void) update_phase(entry_pos, DDL_LOG_FINAL_PHASE); + break; + default: + DBUG_ASSERT(0); + break; + } + break; + } + case DDL_LOG_RENAME_VIEW_ACTION: + { + LEX_CSTRING from_table, to_table; + from_table= ddl_log_entry->from_name; + to_table= ddl_log_entry->name; + + /* Delete any left over .frm~ files */ + build_filename_and_delete_tmp_file(to_path, sizeof(to_path) - 1, + &ddl_log_entry->db, + &ddl_log_entry->name, + reg_ext, + key_file_fileparser); + build_filename_and_delete_tmp_file(from_path, sizeof(from_path) - 1, + &ddl_log_entry->from_db, + &ddl_log_entry->from_name, + reg_ext, key_file_fileparser); + + /* Rename view back if the original rename did succeed */ + if (!access(to_path, F_OK)) + (void) mysql_rename_view(thd, + &ddl_log_entry->from_db, &from_table, + &ddl_log_entry->db, &to_table); + (void) update_phase(entry_pos, DDL_LOG_FINAL_PHASE); + } + break; + /* + Initialize variables for DROP TABLE and DROP VIEW + In normal cases a query only contains one action. However in case of + DROP DATABASE we may get a mix of both and we have to keep these + separate. + */ + case DDL_LOG_DROP_INIT_ACTION: + { + LEX_CSTRING *comment= &ddl_log_entry->tmp_name; + recovery_state.drop_table.length(0); + recovery_state.drop_table.set_charset(system_charset_info); + recovery_state.drop_table.append(STRING_WITH_LEN("DROP TABLE IF EXISTS ")); + if (comment->length) + { + recovery_state.drop_table.append(comment); + recovery_state.drop_table.append(' '); + } + recovery_state.drop_table_init_length= recovery_state.drop_table.length(); + + recovery_state.drop_view.length(0); + recovery_state.drop_view.set_charset(system_charset_info); + recovery_state.drop_view.append(STRING_WITH_LEN("DROP VIEW IF EXISTS ")); + recovery_state.drop_view_init_length= recovery_state.drop_view.length(); + + strmake(recovery_state.current_db, + ddl_log_entry->from_db.str, sizeof(recovery_state.current_db)-1); + /* We don't increment phase as we want to retry this in case of crash */ + break; + } + case DDL_LOG_DROP_TABLE_ACTION: + { + LEX_CSTRING db, table, path; + db= ddl_log_entry->db; + table= ddl_log_entry->name; + /* Note that path is without .frm extension */ + path= ddl_log_entry->tmp_name; + + switch (ddl_log_entry->phase) { + case DDL_DROP_PHASE_TABLE: + if (hton) + { + no_such_table_handler.only_ignore_non_existing_errors= 1; + error= hton->drop_table(hton, path.str); + no_such_table_handler.only_ignore_non_existing_errors= 0; + if (error) + { + if (!non_existing_table_error(error)) + break; + error= -1; + } + } + else + error= ha_delete_table_force(thd, path.str, &db, &table); + if (error <= 0) + { + /* Not found or already deleted. Delete .frm if it exists */ + strxnmov(to_path, sizeof(to_path)-1, path.str, reg_ext, NullS); + mysql_file_delete(key_file_frm, to_path, MYF(MY_WME|MY_IGNORE_ENOENT)); + error= 0; + } + if (increment_phase(entry_pos)) + break; + /* Fall through */ + case DDL_DROP_PHASE_TRIGGER: + Table_triggers_list::drop_all_triggers(thd, &db, &table, + MYF(MY_WME | MY_IGNORE_ENOENT)); + if (increment_phase(entry_pos)) + break; + /* Fall through */ + case DDL_DROP_PHASE_BINLOG: + if (strcmp(recovery_state.current_db, db.str)) + { + append_identifier(thd, &recovery_state.drop_table, &db); + recovery_state.drop_table.append('.'); + } + append_identifier(thd, &recovery_state.drop_table, &table); + recovery_state.drop_table.append(','); + /* We don't increment phase as we want to retry this in case of crash */ + + if (ddl_log_drop_to_binary_log(thd, ddl_log_entry, + &recovery_state.drop_table)) + { + if (increment_phase(entry_pos)) + break; + } + break; + case DDL_DROP_PHASE_RESET: + /* We have already logged all previous drop's. Clear the query */ + recovery_state.drop_table.length(recovery_state.drop_table_init_length); + recovery_state.drop_view.length(recovery_state.drop_view_init_length); + break; + } + break; + } + case DDL_LOG_DROP_VIEW_ACTION: + { + LEX_CSTRING db, table, path; + db= ddl_log_entry->db; + table= ddl_log_entry->name; + /* Note that for views path is WITH .frm extension */ + path= ddl_log_entry->tmp_name; + + if (ddl_log_entry->phase == 0) + { + mysql_file_delete(key_file_frm, path.str, MYF(MY_WME|MY_IGNORE_ENOENT)); + if (strcmp(recovery_state.current_db, db.str)) + { + append_identifier(thd, &recovery_state.drop_view, &db); + recovery_state.drop_view.append('.'); + } + append_identifier(thd, &recovery_state.drop_view, &table); + recovery_state.drop_view.append(','); + + if (ddl_log_drop_to_binary_log(thd, ddl_log_entry, + &recovery_state.drop_view)) + { + if (increment_phase(entry_pos)) + break; + } + } + else + { + /* We have already logged all previous drop's. Clear the query */ + recovery_state.drop_table.length(recovery_state.drop_table_init_length); + recovery_state.drop_view.length(recovery_state.drop_table_init_length); + } + break; + } + case DDL_LOG_DROP_TRIGGER_ACTION: + { + MY_STAT stat_info; + off_t frm_length= 1; // Impossible length + LEX_CSTRING thd_db= thd->db; + + /* Delete trigger temporary file if it still exists */ + if (!build_filename_and_delete_tmp_file(to_path, sizeof(to_path) - 1, + &ddl_log_entry->db, + &ddl_log_entry->name, + TRG_EXT, + key_file_fileparser)) + { + /* Temporary file existed and was deleted, nothing left to do */ + (void) update_phase(entry_pos, DDL_LOG_FINAL_PHASE); + break; + } + /* + We can use length of TRG file as an indication if trigger was removed. + If there is no file, then it means that this was the last trigger + and the file was removed. + */ + if (my_stat(to_path, &stat_info, MYF(0))) + frm_length= (off_t) stat_info.st_size; + if (frm_length != (off_t) ddl_log_entry->unique_id && + mysql_bin_log.is_open()) + { + /* + File size changed and it was not binlogged (as this entry was + executed) + */ + (void) rm_trigname_file(to_path, &ddl_log_entry->db, + &ddl_log_entry->from_name, + MYF(0)); + + recovery_state.drop_table.length(0); + recovery_state.drop_table.set_charset(system_charset_info); + if (ddl_log_entry->tmp_name.length) + { + /* We can use the original query */ + recovery_state.drop_table.append(&ddl_log_entry->tmp_name); + } + else + { + /* Generate new query */ + recovery_state.drop_table.append(STRING_WITH_LEN("DROP TRIGGER IF " + "EXISTS ")); + append_identifier(thd, &recovery_state.drop_table, + &ddl_log_entry->from_name); + recovery_state.drop_table.append(&end_comment); + } + if (mysql_bin_log.is_open()) + { + mysql_mutex_unlock(&LOCK_gdl); + thd->db= ddl_log_entry->db; + (void) thd->binlog_query(THD::STMT_QUERY_TYPE, + recovery_state.drop_table.ptr(), + recovery_state.drop_table.length(), TRUE, FALSE, + FALSE, 0); + thd->db= thd_db; + mysql_mutex_lock(&LOCK_gdl); + } + } + (void) update_phase(entry_pos, DDL_LOG_FINAL_PHASE); + break; + } + case DDL_LOG_DROP_DB_ACTION: + { + LEX_CSTRING db, path; + db= ddl_log_entry->db; + path= ddl_log_entry->tmp_name; + + switch (ddl_log_entry->phase) { + case DDL_DROP_DB_PHASE_INIT: + drop_database_objects(thd, &path, &db, + !my_strcasecmp(system_charset_info, + MYSQL_SCHEMA_NAME.str, db.str)); + + strxnmov(to_path, sizeof(to_path)-1, path.str, MY_DB_OPT_FILE, NullS); + mysql_file_delete_with_symlink(key_file_misc, to_path, "", MYF(0)); + + (void) rm_dir_w_symlink(path.str, 0); + if (increment_phase(entry_pos)) + break; + /* fall through */ + case DDL_DROP_DB_PHASE_LOG: + { + String *query= &recovery_state.drop_table; + + query->length(0); + query->append(STRING_WITH_LEN("DROP DATABASE IF EXISTS ")); + append_identifier(thd, query, &db); + query->append(&end_comment); + + if (mysql_bin_log.is_open()) + { + mysql_mutex_unlock(&LOCK_gdl); + (void) thd->binlog_query(THD::STMT_QUERY_TYPE, + query->ptr(), query->length(), + TRUE, FALSE, FALSE, 0); + mysql_mutex_lock(&LOCK_gdl); + } + (void) update_phase(entry_pos, DDL_LOG_FINAL_PHASE); + break; + } + } + break; + } + case DDL_LOG_CREATE_TABLE_ACTION: + { + LEX_CSTRING db, table, path; + db= ddl_log_entry->db; + table= ddl_log_entry->name; + path= ddl_log_entry->tmp_name; + + /* Don't delete the table if we didn't create it */ + if (ddl_log_entry->flags == 0) + { + if (hton) + { + if ((error= hton->drop_table(hton, path.str))) + { + if (!non_existing_table_error(error)) + break; + error= -1; + } + } + else + error= ha_delete_table_force(thd, path.str, &db, &table); + } + strxnmov(to_path, sizeof(to_path)-1, path.str, reg_ext, NullS); + mysql_file_delete(key_file_frm, to_path, MYF(MY_WME|MY_IGNORE_ENOENT)); + if (ddl_log_entry->phase == DDL_CREATE_TABLE_PHASE_LOG) + { + /* + The server logged CREATE TABLE ... SELECT into binary log + before crashing. As the commit failed and we have delete the + table above, we have now to log the DROP of the created table. + */ + + String *query= &recovery_state.drop_table; + query->length(0); + query->append(STRING_WITH_LEN("DROP TABLE IF EXISTS ")); + append_identifier(thd, query, &db); + query->append('.'); + append_identifier(thd, query, &table); + query->append(&end_comment); + + if (mysql_bin_log.is_open()) + { + mysql_mutex_unlock(&LOCK_gdl); + (void) thd->binlog_query(THD::STMT_QUERY_TYPE, + query->ptr(), query->length(), + TRUE, FALSE, FALSE, 0); + mysql_mutex_lock(&LOCK_gdl); + } + } + (void) update_phase(entry_pos, DDL_LOG_FINAL_PHASE); + error= 0; + break; + } + case DDL_LOG_CREATE_VIEW_ACTION: + { + char *path= to_path; + size_t path_length= ddl_log_entry->tmp_name.length; + memcpy(path, ddl_log_entry->tmp_name.str, path_length+1); + path[path_length+1]= 0; // Prepare for extending + + /* Remove temporary parser file */ + path[path_length]='~'; + mysql_file_delete(key_file_fileparser, path, + MYF(MY_WME|MY_IGNORE_ENOENT)); + path[path_length]= 0; + + switch (ddl_log_entry->phase) { + case DDL_CREATE_VIEW_PHASE_NO_OLD_VIEW: + { + /* + No old view exists, so we can just delete the .frm and temporary files + */ + path[path_length]='-'; + mysql_file_delete(key_file_fileparser, path, + MYF(MY_WME|MY_IGNORE_ENOENT)); + path[path_length]= 0; + mysql_file_delete(key_file_frm, path, MYF(MY_WME|MY_IGNORE_ENOENT)); + break; + } + case DDL_CREATE_VIEW_PHASE_DELETE_VIEW_COPY: + { + /* + Old view existed. We crashed before we had done a copy and change + state to DDL_CREATE_VIEW_PHASE_OLD_VIEW_COPIED + */ + path[path_length]='-'; + mysql_file_delete(key_file_fileparser, path, + MYF(MY_WME|MY_IGNORE_ENOENT)); + path[path_length]= 0; + break; + } + case DDL_CREATE_VIEW_PHASE_OLD_VIEW_COPIED: + { + /* + Old view existed copied to '-' file. Restore it + */ + memcpy(from_path, path, path_length+2); + from_path[path_length]='-'; + if (!access(from_path, F_OK)) + mysql_file_rename(key_file_fileparser, from_path, path, MYF(MY_WME)); + break; + } + } + (void) update_phase(entry_pos, DDL_LOG_FINAL_PHASE); + break; + } + case DDL_LOG_DELETE_TMP_FILE_ACTION: + { + LEX_CSTRING path= ddl_log_entry->tmp_name; + DBUG_ASSERT(ddl_log_entry->unique_id <= UINT_MAX32); + if (!ddl_log_entry->unique_id || + !is_execute_entry_active((uint) ddl_log_entry->unique_id)) + mysql_file_delete(key_file_fileparser, path.str, + MYF(MY_WME|MY_IGNORE_ENOENT)); + (void) update_phase(entry_pos, DDL_LOG_FINAL_PHASE); + break; + } + case DDL_LOG_CREATE_TRIGGER_ACTION: + { + LEX_CSTRING db, table, trigger; + db= ddl_log_entry->db; + table= ddl_log_entry->name; + trigger= ddl_log_entry->tmp_name; + + /* Delete backup .TRG (trigger file) if it exists */ + (void) build_filename_and_delete_tmp_file(to_path, sizeof(to_path) - 1, + &db, &table, + TRG_EXT, + key_file_fileparser); + (void) build_filename_and_delete_tmp_file(to_path, sizeof(to_path) - 1, + &db, &trigger, + TRN_EXT, + key_file_fileparser); + switch (ddl_log_entry->phase) { + case DDL_CREATE_TRIGGER_PHASE_DELETE_COPY: + { + size_t length; + /* Delete copy of .TRN and .TRG files */ + length= build_table_filename(to_path, sizeof(to_path) - 1, + db.str, table.str, TRG_EXT, 0); + to_path[length]= '-'; + to_path[length+1]= 0; + mysql_file_delete(key_file_fileparser, to_path, + MYF(MY_WME|MY_IGNORE_ENOENT)); + + length= build_table_filename(to_path, sizeof(to_path) - 1, + db.str, trigger.str, TRN_EXT, 0); + to_path[length]= '-'; + to_path[length+1]= 0; + mysql_file_delete(key_file_fileparser, to_path, + MYF(MY_WME|MY_IGNORE_ENOENT)); + } + /* Nothing else to do */ + (void) update_phase(entry_pos, DDL_LOG_FINAL_PHASE); + break; + case DDL_CREATE_TRIGGER_PHASE_OLD_COPIED: + { + LEX_CSTRING path= {to_path, 0}; + size_t length; + /* Restore old version if the .TRN and .TRG files */ + length= build_table_filename(to_path, sizeof(to_path) - 1, + db.str, table.str, TRG_EXT, 0); + to_path[length]='-'; + to_path[length+1]= 0; + path.length= length+1; + /* an old TRN file only exist in the case if REPLACE was used */ + if (!access(to_path, F_OK)) + sql_restore_definition_file(&path); + + length= build_table_filename(to_path, sizeof(to_path) - 1, + db.str, trigger.str, TRN_EXT, 0); + to_path[length]='-'; + to_path[length+1]= 0; + path.length= length+1; + if (!access(to_path, F_OK)) + sql_restore_definition_file(&path); + else + { + /* + There was originally no .TRN for this trigger. + Delete the newly created one. + */ + to_path[length]= 0; + mysql_file_delete(key_file_fileparser, to_path, + MYF(MY_WME|MY_IGNORE_ENOENT)); + } + (void) update_phase(entry_pos, DDL_LOG_FINAL_PHASE); + break; + } + case DDL_CREATE_TRIGGER_PHASE_NO_OLD_TRIGGER: + { + /* No old trigger existed. We can just delete the .TRN and .TRG files */ + build_table_filename(to_path, sizeof(to_path) - 1, + db.str, table.str, TRG_EXT, 0); + mysql_file_delete(key_file_fileparser, to_path, + MYF(MY_WME|MY_IGNORE_ENOENT)); + build_table_filename(to_path, sizeof(to_path) - 1, + db.str, trigger.str, TRN_EXT, 0); + mysql_file_delete(key_file_fileparser, to_path, + MYF(MY_WME|MY_IGNORE_ENOENT)); + (void) update_phase(entry_pos, DDL_LOG_FINAL_PHASE); + break; + } + } + break; + } + case DDL_LOG_ALTER_TABLE_ACTION: + { + handlerton *org_hton, *partition_hton; + handler *org_file; + bool is_renamed= ddl_log_entry->flags & DDL_LOG_FLAG_ALTER_RENAME; + bool new_version_ready= 0, new_version_unusable= 0; + LEX_CSTRING db, table; + db= ddl_log_entry->db; + table= ddl_log_entry->name; + + if (!(org_file= create_handler(thd, mem_root, + &ddl_log_entry->from_handler_name))) + goto end; + /* Handlerton of the final table and any temporary tables */ + org_hton= org_file->ht; + /* + partition_hton is the hton for the new file, or + in case of ALTER of a partitioned table, the underlying + table + */ + partition_hton= hton; + + if (ddl_log_entry->flags & DDL_LOG_FLAG_ALTER_PARTITION) + { + /* + The from and to tables where both using the partition engine. + */ + hton= org_hton; + } + switch (ddl_log_entry->phase) { + case DDL_ALTER_TABLE_PHASE_RENAME_FAILED: + /* + We come here when the final rename of temporary table (#sql-alter) to + the original name failed. Now we have to delete the temporary table + and restore the backup. + */ + quick_rm_table(thd, hton, &db, &table, FN_IS_TMP); + if (!is_renamed) + { + execute_rename_table(ddl_log_entry, file, + &ddl_log_entry->from_db, + &ddl_log_entry->extra_name, // #sql-backup + &ddl_log_entry->from_db, + &ddl_log_entry->from_name, + FN_FROM_IS_TMP, + from_path, to_path); + } + (void) update_phase(entry_pos, DDL_LOG_FINAL_PHASE); + break; + case DDL_ALTER_TABLE_PHASE_PREPARE_INPLACE: + /* We crashed before ddl_log_update_unique_id() was called */ + new_version_unusable= 1; + /* fall through */ + case DDL_ALTER_TABLE_PHASE_INPLACE_COPIED: + /* The inplace alter table is committed and ready to be used */ + if (!new_version_unusable) + new_version_ready= 1; + /* fall through */ + case DDL_ALTER_TABLE_PHASE_INPLACE: + { + int fr_length, to_length; + /* + Inplace alter table was used. + On disk there are now a table with the original name, the + original .frm file and potentially a #sql-alter...frm file + with the new definition. + */ + fr_length= build_table_filename(from_path, sizeof(from_path) - 1, + ddl_log_entry->db.str, + ddl_log_entry->name.str, + reg_ext, 0); + to_length= build_table_filename(to_path, sizeof(to_path) - 1, + ddl_log_entry->from_db.str, + ddl_log_entry->from_name.str, + reg_ext, 0); + if (!access(from_path, F_OK)) // Does #sql-alter.. exists? + { + LEX_CUSTRING version= {ddl_log_entry->uuid, MY_UUID_SIZE}; + /* + Temporary .frm file exists. This means that that the table in + the storage engine can be of either old or new version. + If old version, delete the new .frm table and keep the old one. + If new version, replace the old .frm with the new one. + */ + to_path[to_length - reg_ext_length]= 0; // Remove .frm + if (!new_version_unusable && + ( !partition_hton->check_version || new_version_ready || + !partition_hton->check_version(partition_hton, + to_path, &version, + ddl_log_entry->unique_id))) + { + /* Table is up to date */ + + /* + Update state so that if we crash and retry the ddl log entry, + we know that we can use the new table even if .frm is renamed. + */ + if (ddl_log_entry->phase != DDL_ALTER_TABLE_PHASE_INPLACE_COPIED) + (void) update_phase(entry_pos, + DDL_ALTER_TABLE_PHASE_INPLACE_COPIED); + /* Replace old .frm file with new one */ + to_path[to_length - reg_ext_length]= FN_EXTCHAR; + (void) mysql_file_rename(key_file_frm, from_path, to_path, + MYF(MY_WME)); + new_version_ready= 1; + } + else + { + DBUG_ASSERT(!new_version_ready); + /* + Use original version of the .frm file. + Remove temporary #sql-alter.frm file and the #sql-alter table. + We have also to remove the temporary table as some storage engines, + like InnoDB, may use it as an internal temporary table + during inplace alter table. + */ + from_path[fr_length - reg_ext_length]= 0; + error= org_hton->drop_table(org_hton, from_path); + if (non_existing_table_error(error)) + error= 0; + from_path[fr_length - reg_ext_length]= FN_EXTCHAR; + mysql_file_delete(key_file_frm, from_path, + MYF(MY_WME|MY_IGNORE_ENOENT)); + (void) update_phase(entry_pos, DDL_LOG_FINAL_PHASE); + break; + } + } + if (is_renamed && new_version_ready) + { + /* After the renames above, the original table is now in from_name */ + ddl_log_entry->name= ddl_log_entry->from_name; + /* Rename db.name -> db.extra_name */ + execute_rename_table(ddl_log_entry, file, + &ddl_log_entry->db, &ddl_log_entry->name, + &ddl_log_entry->db, &ddl_log_entry->extra_name, + 0, + from_path, to_path); + } + (void) update_phase(entry_pos, DDL_ALTER_TABLE_PHASE_UPDATE_TRIGGERS); + goto update_triggers; + } + case DDL_ALTER_TABLE_PHASE_COPIED: + { + char *from_end; + /* + New table is created and we have the query for the binary log. + We should remove the original table and in the next stage replace + it with the new one. + */ + build_table_filename(from_path, sizeof(from_path) - 1, + ddl_log_entry->from_db.str, + ddl_log_entry->from_name.str, + "", 0); + build_table_filename(to_path, sizeof(to_path) - 1, + ddl_log_entry->db.str, + ddl_log_entry->name.str, + "", 0); + from_end= strend(from_path); + if (likely(org_hton)) + { + error= org_hton->drop_table(org_hton, from_path); + if (non_existing_table_error(error)) + error= 0; + } + strmov(from_end, reg_ext); + mysql_file_delete(key_file_frm, from_path, + MYF(MY_WME|MY_IGNORE_ENOENT)); + *from_end= 0; // Remove extension + + (void) update_phase(entry_pos, DDL_ALTER_TABLE_PHASE_OLD_RENAMED); + } + /* fall through */ + case DDL_ALTER_TABLE_PHASE_OLD_RENAMED: + { + /* + The new table (from_path) is up to date. + Original table is either renamed as backup table (normal case), + only frm is renamed (in case of engine change) or deleted above. + */ + if (!is_renamed) + { + uint length; + /* Rename new "temporary" table to the original wanted name */ + execute_rename_table(ddl_log_entry, file, + &ddl_log_entry->db, + &ddl_log_entry->name, + &ddl_log_entry->from_db, + &ddl_log_entry->from_name, + FN_FROM_IS_TMP, + from_path, to_path); + + /* + Remove backup (only happens if alter table used without rename). + Backup name is always in lower case, so there is no need for + converting table names. + */ + length= build_table_filename(from_path, sizeof(from_path) - 1, + ddl_log_entry->from_db.str, + ddl_log_entry->extra_name.str, + "", FN_IS_TMP); + if (likely(org_hton)) + { + if (ddl_log_entry->flags & DDL_LOG_FLAG_ALTER_ENGINE_CHANGED) + { + /* Only frm is renamed, storage engine files have original name */ + build_table_filename(to_path, sizeof(from_path) - 1, + ddl_log_entry->from_db.str, + ddl_log_entry->from_name.str, + "", 0); + error= org_hton->drop_table(org_hton, to_path); + } + else + error= org_hton->drop_table(org_hton, from_path); + if (non_existing_table_error(error)) + error= 0; + } + strmov(from_path + length, reg_ext); + mysql_file_delete(key_file_frm, from_path, + MYF(MY_WME|MY_IGNORE_ENOENT)); + } + else + execute_rename_table(ddl_log_entry, file, + &ddl_log_entry->db, &ddl_log_entry->name, + &ddl_log_entry->db, &ddl_log_entry->extra_name, + FN_FROM_IS_TMP, + from_path, to_path); + (void) update_phase(entry_pos, DDL_ALTER_TABLE_PHASE_UPDATE_TRIGGERS); + } + /* fall through */ + case DDL_ALTER_TABLE_PHASE_UPDATE_TRIGGERS: + update_triggers: + { + if (is_renamed) + { + // rename_triggers will rename from: from_db.from_name -> db.extra_name + rename_triggers(thd, ddl_log_entry, 1); + (void) update_phase(entry_pos, DDL_ALTER_TABLE_PHASE_UPDATE_STATS); + } + } + /* fall through */ + case DDL_ALTER_TABLE_PHASE_UPDATE_STATS: + if (is_renamed) + { + ddl_log_entry->name= ddl_log_entry->from_name; + ddl_log_entry->from_name= ddl_log_entry->extra_name; + rename_in_stat_tables(thd, ddl_log_entry, 1); + (void) update_phase(entry_pos, DDL_ALTER_TABLE_PHASE_UPDATE_STATS); + } + /* fall through */ + case DDL_ALTER_TABLE_PHASE_UPDATE_BINARY_LOG: + { + /* Write ALTER TABLE query to binary log */ + if (recovery_state.query.length() && mysql_bin_log.is_open()) + { + LEX_CSTRING save_db; + /* Reuse old xid value if possible */ + if (!recovery_state.xid) + recovery_state.xid= server_uuid_value(); + thd->binlog_xid= recovery_state.xid; + update_xid(recovery_state.execute_entry_pos, thd->binlog_xid); + + mysql_mutex_unlock(&LOCK_gdl); + save_db= thd->db; + lex_string_set3(&thd->db, recovery_state.db.ptr(), + recovery_state.db.length()); + (void) thd->binlog_query(THD::STMT_QUERY_TYPE, + recovery_state.query.ptr(), + recovery_state.query.length(), + TRUE, FALSE, FALSE, 0); + thd->binlog_xid= 0; + thd->db= save_db; + mysql_mutex_lock(&LOCK_gdl); + } + recovery_state.query.length(0); + (void) update_phase(entry_pos, DDL_LOG_FINAL_PHASE); + break; + } + /* + The following cases are when alter table failed and we have to roll + back + */ + case DDL_ALTER_TABLE_PHASE_CREATED: + { + /* + Temporary table should have been created. Delete it. + */ + if (likely(hton)) + { + error= hton->drop_table(hton, ddl_log_entry->tmp_name.str); + if (non_existing_table_error(error)) + error= 0; + } + (void) update_phase(entry_pos, DDL_ALTER_TABLE_PHASE_INIT); + } + /* fall through */ + case DDL_ALTER_TABLE_PHASE_INIT: + { + /* + A temporary .frm and possible a .par files should have been created + */ + strxmov(to_path, ddl_log_entry->tmp_name.str, reg_ext, NullS); + mysql_file_delete(key_file_frm, to_path, MYF(MY_WME|MY_IGNORE_ENOENT)); + strxmov(to_path, ddl_log_entry->tmp_name.str, PAR_EXT, NullS); + mysql_file_delete(key_file_partition_ddl_log, to_path, + MYF(MY_WME|MY_IGNORE_ENOENT)); + (void) update_phase(entry_pos, DDL_LOG_FINAL_PHASE); + break; + } + } + delete org_file; + break; + } + case DDL_LOG_STORE_QUERY_ACTION: + { + /* + Read query for next ddl command + */ + if (ddl_log_entry->flags) + { + /* + First QUERY event. Allocate query string. + Query length is stored in unique_id + */ + if (recovery_state.query.alloc((size_t) (ddl_log_entry->unique_id+1))) + goto end; + recovery_state.query.length(0); + recovery_state.db.copy(ddl_log_entry->db.str, ddl_log_entry->db.length, + system_charset_info); + } + if (unlikely(recovery_state.query.length() + + ddl_log_entry->extra_name.length > + recovery_state.query.alloced_length())) + { + /* Impossible length. Ignore query */ + recovery_state.query.length(0); + error= 1; + my_error(ER_INTERNAL_ERROR, MYF(0), + "DDL log: QUERY event has impossible length"); + break; + } + recovery_state.query.qs_append(&ddl_log_entry->extra_name); + break; + } + default: + DBUG_ASSERT(0); + break; + } + +end: + delete file; + /* We are only interested in errors that where not ignored */ + if ((error= (no_such_table_handler.unhandled_errors > 0))) + my_errno= no_such_table_handler.first_error; + thd->pop_internal_handler(); + DBUG_RETURN(error); +} + + +/** + Get a free entry in the ddl log + + @param[out] active_entry A ddl log memory entry returned + @param[out] write_header Set to 1 if ddl log was enlarged + + @return Operation status + @retval TRUE Error + @retval FALSE Success +*/ + +static bool ddl_log_get_free_entry(DDL_LOG_MEMORY_ENTRY **active_entry) +{ + DDL_LOG_MEMORY_ENTRY *used_entry; + DDL_LOG_MEMORY_ENTRY *first_used= global_ddl_log.first_used; + DBUG_ENTER("ddl_log_get_free_entry"); + + if (global_ddl_log.first_free == NULL) + { + if (!(used_entry= ((DDL_LOG_MEMORY_ENTRY*) + my_malloc(key_memory_DDL_LOG_MEMORY_ENTRY, + sizeof(DDL_LOG_MEMORY_ENTRY), MYF(MY_WME))))) + { + sql_print_error("DDL_LOG: Failed to allocate memory for ddl log free " + "list"); + *active_entry= 0; + DBUG_RETURN(TRUE); + } + global_ddl_log.num_entries++; + used_entry->entry_pos= global_ddl_log.num_entries; + } + else + { + used_entry= global_ddl_log.first_free; + global_ddl_log.first_free= used_entry->next_log_entry; + } + /* + Move from free list to used list + */ + used_entry->next_log_entry= first_used; + used_entry->prev_log_entry= NULL; + used_entry->next_active_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); +} + + +/** + Release a log memory entry. + @param log_memory_entry Log memory entry to release +*/ + +void ddl_log_release_memory_entry(DDL_LOG_MEMORY_ENTRY *log_entry) +{ + 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("ddl_log_release_memory_entry"); + + mysql_mutex_assert_owner(&LOCK_gdl); + log_entry->next_log_entry= global_ddl_log.first_free; + global_ddl_log.first_free= log_entry; + + 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; + // Ensure we get a crash if we try to access this link again. + log_entry->next_active_log_entry= (DDL_LOG_MEMORY_ENTRY*) 0x1; + DBUG_VOID_RETURN; +} + + +/** + Execute one entry in the ddl log. + + Executing an entry means executing a linked list of actions. + + @param first_entry Reference to first action in entry + + @return Operation status + @retval TRUE Error + @retval FALSE Success +*/ + +static bool ddl_log_execute_entry_no_lock(THD *thd, uint first_entry) +{ + DDL_LOG_ENTRY ddl_log_entry; + uint read_entry= first_entry; + MEM_ROOT mem_root; + DBUG_ENTER("ddl_log_execute_entry_no_lock"); + + mysql_mutex_assert_owner(&LOCK_gdl); + init_sql_alloc(key_memory_gdl, &mem_root, TABLE_ALLOC_BLOCK_SIZE, 0, + MYF(MY_THREAD_SPECIFIC)); + do + { + if (read_ddl_log_entry(read_entry, &ddl_log_entry)) + { + /* Error logged to error log. Continue with next log entry */ + break; + } + DBUG_ASSERT(ddl_log_entry.entry_type == DDL_LOG_ENTRY_CODE || + ddl_log_entry.entry_type == DDL_LOG_IGNORE_ENTRY_CODE); + + if (ddl_log_execute_action(thd, &mem_root, &ddl_log_entry)) + { + uint action_type= ddl_log_entry.action_type; + if (action_type >= DDL_LOG_LAST_ACTION) + action_type= 0; + + /* Write to error log and continue with next log entry */ + sql_print_error("DDL_LOG: Got error %d when trying to execute action " + "for entry %u of type '%s'", + (int) my_errno, read_entry, + ddl_log_action_name[action_type]); + break; + } + read_entry= ddl_log_entry.next_entry; + } while (read_entry); + + free_root(&mem_root, MYF(0)); + DBUG_RETURN(FALSE); +} + + +/* + External interface methods for the DDL log Module + --------------------------------------------------- +*/ + +/** + Write a ddl log entry. + + A careful write of the ddl log is performed to ensure that we can + handle crashes occurring during CREATE and ALTER TABLE processing. + + @param ddl_log_entry Information about log entry + @param[out] entry_written Entry information written into + + @return Operation status + @retval TRUE Error + @retval FALSE Success +*/ + +bool ddl_log_write_entry(DDL_LOG_ENTRY *ddl_log_entry, + DDL_LOG_MEMORY_ENTRY **active_entry) +{ + bool error; + DBUG_ENTER("ddl_log_write_entry"); + + *active_entry= 0; + mysql_mutex_assert_owner(&LOCK_gdl); + DBUG_ASSERT(global_ddl_log.open); + if (unlikely(!global_ddl_log.open)) + { + my_error(ER_INTERNAL_ERROR, MYF(0), "ddl log not initialized"); + DBUG_RETURN(TRUE); + } + + ddl_log_entry->entry_type= DDL_LOG_ENTRY_CODE; + set_global_from_ddl_log_entry(ddl_log_entry); + if (ddl_log_get_free_entry(active_entry)) + DBUG_RETURN(TRUE); + + error= FALSE; + DBUG_PRINT("ddl_log", + ("pos: %u->%u action: %u (%s) phase: %u " + "handler: '%s' name: '%s' from_name: '%s' tmp_name: '%s'", + (*active_entry)->entry_pos, + (uint) ddl_log_entry->next_entry, + (uint) ddl_log_entry->action_type, + ddl_log_action_name[ddl_log_entry->action_type], + (uint) ddl_log_entry->phase, + ddl_log_entry->handler_name.str, + ddl_log_entry->name.str, + ddl_log_entry->from_name.str, + ddl_log_entry->tmp_name.str)); + + if (unlikely(write_ddl_log_file_entry((*active_entry)->entry_pos))) + { + sql_print_error("DDL_LOG: Failed to write entry %u", + (*active_entry)->entry_pos); + ddl_log_release_memory_entry(*active_entry); + *active_entry= 0; + error= TRUE; + } + DBUG_RETURN(error); +} + + +/** + @brief Write or update execute entry in the ddl log. + + @details An execute entry points to the first entry that should + be excuted during recovery. In some cases it's only written once, + in other cases it's updated for each log entry to point to the new + header for the list. + + When called, 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 an execute entry is put as the first entry these will be + executed during recovery. + + @param first_entry First entry in linked list of entries + to execute. + @param cond_entry Check and don't execute if cond_entry is active + @param[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 Operation status + @retval TRUE Error + @retval FALSE Success +*/ + +bool ddl_log_write_execute_entry(uint first_entry, + uint cond_entry, + DDL_LOG_MEMORY_ENTRY **active_entry) +{ + uchar *file_entry_buf= global_ddl_log.file_entry_buf; + bool got_free_entry= 0; + DBUG_ENTER("ddl_log_write_execute_entry"); + + mysql_mutex_assert_owner(&LOCK_gdl); + /* + We haven't synched the log entries yet, we sync them now before + writing the execute entry. + */ + (void) ddl_log_sync_no_lock(); + bzero(file_entry_buf, global_ddl_log.io_size); + + file_entry_buf[DDL_LOG_ENTRY_TYPE_POS]= (uchar)DDL_LOG_EXECUTE_CODE; + int4store(file_entry_buf + DDL_LOG_NEXT_ENTRY_POS, first_entry); + int8store(file_entry_buf + DDL_LOG_ID_POS, ((ulonglong)cond_entry << DDL_LOG_RETRY_BITS)); + + if (!(*active_entry)) + { + if (ddl_log_get_free_entry(active_entry)) + DBUG_RETURN(TRUE); + got_free_entry= TRUE; + } + DBUG_PRINT("ddl_log", + ("pos: %u=>%u", + (*active_entry)->entry_pos, first_entry)); + if (write_ddl_log_file_entry((*active_entry)->entry_pos)) + { + sql_print_error("DDL_LOG: Error writing execute entry %u", + (*active_entry)->entry_pos); + if (got_free_entry) + { + ddl_log_release_memory_entry(*active_entry); + *active_entry= 0; + } + DBUG_RETURN(TRUE); + } + (void) ddl_log_sync_no_lock(); + DBUG_RETURN(FALSE); +} + + +/** + Increment phase for entry. Will deactivate entry after all phases are done + + @details see ddl_log_increment_phase_no_lock. + + @param entry_pos Entry position of record to change + + @return Operation status + @retval TRUE Error + @retval FALSE Success +*/ + +bool ddl_log_increment_phase(uint entry_pos) +{ + bool error; + DBUG_ENTER("ddl_log_increment_phase"); + DBUG_PRINT("ddl_log", ("pos: %u", entry_pos)); + + mysql_mutex_lock(&LOCK_gdl); + error= ddl_log_increment_phase_no_lock(entry_pos); + mysql_mutex_unlock(&LOCK_gdl); + DBUG_RETURN(error); +} + + +/** + Sync ddl log file. + + @return Operation status + @retval TRUE Error + @retval FALSE Success +*/ + +bool ddl_log_sync() +{ + bool error; + DBUG_ENTER("ddl_log_sync"); + + mysql_mutex_lock(&LOCK_gdl); + error= ddl_log_sync_no_lock(); + mysql_mutex_unlock(&LOCK_gdl); + + DBUG_RETURN(error); +} + + +/** + Execute one entry in the ddl log. + + Executing an entry means executing a linked list of actions. + + This function is called for recovering partitioning in case of error. + + @param first_entry Reference to first action in entry + + @return Operation status + @retval TRUE Error + @retval FALSE Success +*/ + +bool ddl_log_execute_entry(THD *thd, uint first_entry) +{ + bool error; + DBUG_ENTER("ddl_log_execute_entry"); + + mysql_mutex_lock(&LOCK_gdl); + error= ddl_log_execute_entry_no_lock(thd, first_entry); + mysql_mutex_unlock(&LOCK_gdl); + DBUG_RETURN(error); +} + + +/** + Close the ddl log. +*/ + +static void close_ddl_log() +{ + DBUG_ENTER("close_ddl_log"); + if (global_ddl_log.file_id >= 0) + { + (void) mysql_file_close(global_ddl_log.file_id, MYF(MY_WME)); + global_ddl_log.file_id= (File) -1; + } + global_ddl_log.open= 0; + DBUG_VOID_RETURN; +} + + +/** + Loop over ddl log excute entries and mark those that are already stored + in the binary log as completed + + @return + @retval 0 ok + @return 1 fail (write error) + +*/ + +bool ddl_log_close_binlogged_events(HASH *xids) +{ + uint i; + DDL_LOG_ENTRY ddl_log_entry; + DBUG_ENTER("ddl_log_close_binlogged_events"); + + if (global_ddl_log.num_entries == 0 || xids->records == 0) + DBUG_RETURN(0); + + mysql_mutex_lock(&LOCK_gdl); + for (i= 1; i <= global_ddl_log.num_entries; i++) + { + if (read_ddl_log_entry(i, &ddl_log_entry)) + break; // Read error. Stop reading + DBUG_PRINT("xid",("xid: %llu", ddl_log_entry.xid)); + if (ddl_log_entry.entry_type == DDL_LOG_EXECUTE_CODE && + ddl_log_entry.xid != 0 && + my_hash_search(xids, (uchar*) &ddl_log_entry.xid, + sizeof(ddl_log_entry.xid))) + { + if (disable_execute_entry(i)) + { + mysql_mutex_unlock(&LOCK_gdl); + DBUG_RETURN(1); // Write error. Fatal! + } + } + } + (void) ddl_log_sync_no_lock(); + mysql_mutex_unlock(&LOCK_gdl); + DBUG_RETURN(0); +} + + +/** + Execute the ddl log at recovery of MySQL Server. + + @return + @retval 0 Ok. + @retval > 0 Fatal error. We have to abort (can't create ddl log) + @return < -1 Recovery failed, but new log exists and is usable + +*/ + +int ddl_log_execute_recovery() +{ + uint i, count= 0; + int error= 0; + THD *thd, *original_thd; + DDL_LOG_ENTRY ddl_log_entry; + static char recover_query_string[]= "INTERNAL DDL LOG RECOVER IN PROGRESS"; + DBUG_ENTER("ddl_log_execute_recovery"); + + if (!global_ddl_log.backup_done && !global_ddl_log.created) + ddl_log_create_backup_file(); + + if (global_ddl_log.num_entries == 0) + DBUG_RETURN(0); + + /* + To be able to run this from boot, we allocate a temporary THD + */ + if (!(thd=new THD(0))) + { + DBUG_ASSERT(0); // Fatal error + DBUG_RETURN(1); + } + original_thd= current_thd; // Probably NULL + thd->thread_stack= (char*) &thd; + thd->store_globals(); + thd->init(); // Needed for error messages + + thd->log_all_errors= (global_system_variables.log_warnings >= 3); + recovery_state.drop_table.free(); + recovery_state.drop_view.free(); + recovery_state.query.free(); + recovery_state.db.free(); + + thd->set_query(recover_query_string, strlen(recover_query_string)); + + mysql_mutex_lock(&LOCK_gdl); + for (i= 1; i <= global_ddl_log.num_entries; i++) + { + if (read_ddl_log_entry(i, &ddl_log_entry)) + { + error= -1; + continue; + } + if (ddl_log_entry.entry_type == DDL_LOG_EXECUTE_CODE) + { + /* + Remeber information about executive ddl log entry, + used for binary logging during recovery + */ + recovery_state.execute_entry_pos= i; + recovery_state.xid= ddl_log_entry.xid; + + /* purecov: begin tested */ + if ((ddl_log_entry.unique_id & DDL_LOG_RETRY_MASK) > DDL_LOG_MAX_RETRY) + { + error= -1; + continue; + } + update_unique_id(i, ++ddl_log_entry.unique_id); + if ((ddl_log_entry.unique_id & DDL_LOG_RETRY_MASK) > DDL_LOG_MAX_RETRY) + { + sql_print_error("DDL_LOG: Aborting executing entry %u after %llu " + "retries", i, ddl_log_entry.unique_id); + error= -1; + continue; + } + /* purecov: end tested */ + + uint cond_entry= (uint)(ddl_log_entry.unique_id >> DDL_LOG_RETRY_BITS); + + if (cond_entry && is_execute_entry_active(cond_entry)) + { + if (disable_execute_entry(i)) + error= -1; + continue; + } + + if (ddl_log_execute_entry_no_lock(thd, ddl_log_entry.next_entry)) + { + /* Real unpleasant scenario but we have to continue anyway */ + error= -1; + continue; + } + count++; + } + } + recovery_state.drop_table.free(); + recovery_state.drop_view.free(); + recovery_state.query.free(); + recovery_state.db.free(); + close_ddl_log(); + mysql_mutex_unlock(&LOCK_gdl); + thd->reset_query(); + delete thd; + set_current_thd(original_thd); + + /* + Create a new ddl_log to get rid of old stuff and ensure that header matches + the current source version + */ + if (create_ddl_log()) + error= 1; + if (count > 0) + sql_print_information("DDL_LOG: Crash recovery executed %u entries", + count); + + set_current_thd(original_thd); + DBUG_RETURN(error); +} + + +static void ddl_log_free_lists() +{ + DDL_LOG_MEMORY_ENTRY *free_list= global_ddl_log.first_free; + DDL_LOG_MEMORY_ENTRY *used_list= global_ddl_log.first_used; + + while (used_list) + { + DDL_LOG_MEMORY_ENTRY *tmp= used_list->next_log_entry; + my_free(used_list); + used_list= tmp; + } + while (free_list) + { + DDL_LOG_MEMORY_ENTRY *tmp= free_list->next_log_entry; + my_free(free_list); + free_list= tmp; + } + global_ddl_log.first_free= global_ddl_log.first_used= 0; +} + + +/** + Release all memory allocated to the ddl log and delete the ddl log +*/ + +void ddl_log_release() +{ + char file_name[FN_REFLEN]; + DBUG_ENTER("ddl_log_release"); + + if (!global_ddl_log.initialized) + DBUG_VOID_RETURN; + + global_ddl_log.initialized= 0; + ddl_log_free_lists(); + + my_free(global_ddl_log.file_entry_buf); + global_ddl_log.file_entry_buf= 0; + close_ddl_log(); + + create_ddl_log_file_name(file_name, 0); + (void) mysql_file_delete(key_file_global_ddl_log, file_name, MYF(0)); + mysql_mutex_destroy(&LOCK_gdl); + DBUG_VOID_RETURN; +} + + +/** + Methods for DDL_LOG_STATE +*/ + +void ddl_log_add_entry(DDL_LOG_STATE *state, DDL_LOG_MEMORY_ENTRY *log_entry) +{ + log_entry->next_active_log_entry= state->list; + state->main_entry= state->list= log_entry; +} + + +void ddl_log_release_entries(DDL_LOG_STATE *ddl_log_state) +{ + DDL_LOG_MEMORY_ENTRY *next; + for (DDL_LOG_MEMORY_ENTRY *log_entry= ddl_log_state->list; + log_entry; + log_entry= next) + { + next= log_entry->next_active_log_entry; + ddl_log_release_memory_entry(log_entry); + } + ddl_log_state->list= 0; + + if (ddl_log_state->execute_entry) + { + ddl_log_release_memory_entry(ddl_log_state->execute_entry); + ddl_log_state->execute_entry= 0; // Not needed but future safe + } +} + + +/**************************************************************************** + Implementations of common ddl entries +*****************************************************************************/ + +/** + Complete ddl logging. This is done when all statements has completed + successfully and we can disable the execute log entry. +*/ + +void ddl_log_complete(DDL_LOG_STATE *state) +{ + DBUG_ENTER("ddl_log_complete"); + + if (unlikely(!state->list)) + DBUG_VOID_RETURN; // ddl log not used + + mysql_mutex_lock(&LOCK_gdl); + if (likely(state->execute_entry)) + ddl_log_disable_execute_entry(&state->execute_entry); + ddl_log_release_entries(state); + mysql_mutex_unlock(&LOCK_gdl); + state->list= 0; + DBUG_VOID_RETURN; +}; + + +/** + Revert (execute) all entries in the ddl log + + This is called for failed rename table, create trigger or drop trigger. +*/ + +bool ddl_log_revert(THD *thd, DDL_LOG_STATE *state) +{ + bool res= 0; + DBUG_ENTER("ddl_log_revert"); + + if (unlikely(!state->list)) + DBUG_RETURN(0); // ddl log not used + + mysql_mutex_lock(&LOCK_gdl); + if (likely(state->execute_entry)) + { + res= ddl_log_execute_entry_no_lock(thd, state->list->entry_pos); + ddl_log_disable_execute_entry(&state->execute_entry); + } + ddl_log_release_entries(state); + mysql_mutex_unlock(&LOCK_gdl); + state->list= 0; + DBUG_RETURN(res); +} + + +/* + Update phase of main ddl log entry (usually the last one created, + except in case of query events, the one before the query event). +*/ + +bool ddl_log_update_phase(DDL_LOG_STATE *state, uchar phase) +{ + DBUG_ENTER("ddl_log_update_phase"); + if (likely(state->list)) + DBUG_RETURN(update_phase(state->main_entry->entry_pos, phase)); + DBUG_RETURN(0); +} + + +/* + Update flag bits in main ddl log entry (usually last created, except in case + of query events, the one before the query event. +*/ + +bool ddl_log_add_flag(DDL_LOG_STATE *state, uint16 flags) +{ + DBUG_ENTER("ddl_log_update_phase"); + if (likely(state->list)) + { + state->flags|= flags; + DBUG_RETURN(update_flags(state->main_entry->entry_pos, state->flags)); + } + DBUG_RETURN(0); +} + + +/** + Update unique_id (used for inplace alter table) +*/ + +bool ddl_log_update_unique_id(DDL_LOG_STATE *state, ulonglong id) +{ + DBUG_ENTER("ddl_log_update_unique_id"); + DBUG_PRINT("enter", ("id: %llu", id)); + /* The following may not be true in case of temporary tables */ + if (likely(state->list)) + DBUG_RETURN(update_unique_id(state->main_entry->entry_pos, id)); + DBUG_RETURN(0); +} + + +/** + Disable last ddl entry +*/ + +bool ddl_log_disable_entry(DDL_LOG_STATE *state) +{ + DBUG_ENTER("ddl_log_disable_entry"); + /* The following may not be true in case of temporary tables */ + if (likely(state->list)) + DBUG_RETURN(update_phase(state->list->entry_pos, DDL_LOG_FINAL_PHASE)); + DBUG_RETURN(0); +} + + +/** + Update XID for execute event +*/ + +bool ddl_log_update_xid(DDL_LOG_STATE *state, ulonglong xid) +{ + DBUG_ENTER("ddl_log_update_xid"); + DBUG_PRINT("enter", ("xid: %llu", xid)); + /* The following may not be true in case of temporary tables */ + if (likely(state->execute_entry)) + DBUG_RETURN(update_xid(state->execute_entry->entry_pos, xid)); + DBUG_RETURN(0); +} + + +/* + Write ddl_log_entry and write or update ddl_execute_entry + + Will update DDL_LOG_STATE->flags +*/ + +static bool ddl_log_write(DDL_LOG_STATE *ddl_state, + DDL_LOG_ENTRY *ddl_log_entry) +{ + int error; + DDL_LOG_MEMORY_ENTRY *log_entry; + DBUG_ENTER("ddl_log_write"); + + mysql_mutex_lock(&LOCK_gdl); + error= ((ddl_log_write_entry(ddl_log_entry, &log_entry)) || + ddl_log_write_execute_entry(log_entry->entry_pos, 0, + &ddl_state->execute_entry)); + mysql_mutex_unlock(&LOCK_gdl); + if (error) + { + if (log_entry) + ddl_log_release_memory_entry(log_entry); + DBUG_RETURN(1); + } + ddl_log_add_entry(ddl_state, log_entry); + ddl_state->flags|= ddl_log_entry->flags; // Update cache + DBUG_RETURN(0); +} + + +/** + Logging of rename table +*/ + +bool ddl_log_rename_table(DDL_LOG_STATE *ddl_state, + handlerton *hton, + const LEX_CSTRING *org_db, + const LEX_CSTRING *org_alias, + const LEX_CSTRING *new_db, + const LEX_CSTRING *new_alias) +{ + DDL_LOG_ENTRY ddl_log_entry; + DBUG_ENTER("ddl_log_rename_file"); + + bzero(&ddl_log_entry, sizeof(ddl_log_entry)); + + ddl_log_entry.action_type= DDL_LOG_RENAME_TABLE_ACTION; + ddl_log_entry.next_entry= ddl_state->list ? ddl_state->list->entry_pos : 0; + lex_string_set(&ddl_log_entry.handler_name, + ha_resolve_storage_engine_name(hton)); + ddl_log_entry.db= *const_cast<LEX_CSTRING*>(new_db); + ddl_log_entry.name= *const_cast<LEX_CSTRING*>(new_alias); + ddl_log_entry.from_db= *const_cast<LEX_CSTRING*>(org_db); + ddl_log_entry.from_name= *const_cast<LEX_CSTRING*>(org_alias); + ddl_log_entry.phase= DDL_RENAME_PHASE_TABLE; + + DBUG_RETURN(ddl_log_write(ddl_state, &ddl_log_entry)); +} + +/* + Logging of rename view +*/ + +bool ddl_log_rename_view(DDL_LOG_STATE *ddl_state, + const LEX_CSTRING *org_db, + const LEX_CSTRING *org_alias, + const LEX_CSTRING *new_db, + const LEX_CSTRING *new_alias) +{ + DDL_LOG_ENTRY ddl_log_entry; + DBUG_ENTER("ddl_log_rename_file"); + + bzero(&ddl_log_entry, sizeof(ddl_log_entry)); + + ddl_log_entry.action_type= DDL_LOG_RENAME_VIEW_ACTION; + ddl_log_entry.next_entry= ddl_state->list ? ddl_state->list->entry_pos : 0; + ddl_log_entry.db= *const_cast<LEX_CSTRING*>(new_db); + ddl_log_entry.name= *const_cast<LEX_CSTRING*>(new_alias); + ddl_log_entry.from_db= *const_cast<LEX_CSTRING*>(org_db); + ddl_log_entry.from_name= *const_cast<LEX_CSTRING*>(org_alias); + + DBUG_RETURN(ddl_log_write(ddl_state, &ddl_log_entry)); +} + + +/** + Logging of DROP TABLE and DROP VIEW + + Note that in contrast to rename, which are re-done in reverse order, + deletes are stored in a linked list according to delete order. This + is to ensure that the tables, for the query generated for binlog, + is in original delete order. +*/ + +static bool ddl_log_drop_init(DDL_LOG_STATE *ddl_state, + ddl_log_action_code action_code, + const LEX_CSTRING *db, + const LEX_CSTRING *comment) +{ + DDL_LOG_ENTRY ddl_log_entry; + DBUG_ENTER("ddl_log_drop_file"); + + bzero(&ddl_log_entry, sizeof(ddl_log_entry)); + + ddl_log_entry.action_type= action_code; + ddl_log_entry.from_db= *const_cast<LEX_CSTRING*>(db); + ddl_log_entry.tmp_name= *const_cast<LEX_CSTRING*>(comment); + + DBUG_RETURN(ddl_log_write(ddl_state, &ddl_log_entry)); +} + + +bool ddl_log_drop_table_init(DDL_LOG_STATE *ddl_state, + const LEX_CSTRING *db, + const LEX_CSTRING *comment) +{ + return ddl_log_drop_init(ddl_state, DDL_LOG_DROP_INIT_ACTION, + db, comment); +} + +bool ddl_log_drop_view_init(DDL_LOG_STATE *ddl_state, + const LEX_CSTRING *db) +{ + return ddl_log_drop_init(ddl_state, DDL_LOG_DROP_INIT_ACTION, + db, &empty_clex_str); +} + + +/** + Log DROP TABLE to the ddl log. + + This code does not call ddl_log_write() as we want the events to + be stored in call order instead of reverse order, which is the normal + case for all other events. + See also comment before ddl_log_drop_init(). +*/ + +static bool ddl_log_drop(DDL_LOG_STATE *ddl_state, + ddl_log_action_code action_code, + uint phase, + handlerton *hton, + const LEX_CSTRING *path, + const LEX_CSTRING *db, + const LEX_CSTRING *table) +{ + DDL_LOG_ENTRY ddl_log_entry; + DDL_LOG_MEMORY_ENTRY *log_entry; + DBUG_ENTER("ddl_log_drop"); + + DBUG_ASSERT(ddl_state->list); + bzero(&ddl_log_entry, sizeof(ddl_log_entry)); + + ddl_log_entry.action_type= action_code; + if (hton) + lex_string_set(&ddl_log_entry.handler_name, + ha_resolve_storage_engine_name(hton)); + ddl_log_entry.db= *const_cast<LEX_CSTRING*>(db); + ddl_log_entry.name= *const_cast<LEX_CSTRING*>(table); + ddl_log_entry.tmp_name= *const_cast<LEX_CSTRING*>(path); + ddl_log_entry.phase= (uchar) phase; + + mysql_mutex_lock(&LOCK_gdl); + if (ddl_log_write_entry(&ddl_log_entry, &log_entry)) + goto error; + + (void) ddl_log_sync_no_lock(); + if (update_next_entry_pos(ddl_state->list->entry_pos, + log_entry->entry_pos)) + { + ddl_log_release_memory_entry(log_entry); + goto error; + } + + mysql_mutex_unlock(&LOCK_gdl); + ddl_log_add_entry(ddl_state, log_entry); + DBUG_RETURN(0); + +error: + mysql_mutex_unlock(&LOCK_gdl); + DBUG_RETURN(1); +} + + +bool ddl_log_drop_table(DDL_LOG_STATE *ddl_state, + handlerton *hton, + const LEX_CSTRING *path, + const LEX_CSTRING *db, + const LEX_CSTRING *table) +{ + DBUG_ENTER("ddl_log_drop_table"); + DBUG_RETURN(ddl_log_drop(ddl_state, + DDL_LOG_DROP_TABLE_ACTION, DDL_DROP_PHASE_TABLE, + hton, path, db, table)); +} + + +bool ddl_log_drop_view(DDL_LOG_STATE *ddl_state, + const LEX_CSTRING *path, + const LEX_CSTRING *db, + const LEX_CSTRING *table) +{ + DBUG_ENTER("ddl_log_drop_view"); + DBUG_RETURN(ddl_log_drop(ddl_state, + DDL_LOG_DROP_VIEW_ACTION, 0, + (handlerton*) 0, path, db, table)); +} + + +bool ddl_log_drop_trigger(DDL_LOG_STATE *ddl_state, + const LEX_CSTRING *db, + const LEX_CSTRING *table, + const LEX_CSTRING *trigger_name, + const LEX_CSTRING *query) +{ + DDL_LOG_ENTRY ddl_log_entry; + MY_STAT stat_info; + char path[FN_REFLEN+1]; + off_t frm_length= 0; + size_t max_query_length; + DBUG_ENTER("ddl_log_drop_trigger"); + + build_table_filename(path, sizeof(path)-1, db->str, table->str, TRG_EXT, 0); + + /* We can use length of frm file as an indication if trigger was removed */ + if (my_stat(path, &stat_info, MYF(MY_WME | ME_WARNING))) + frm_length= (off_t) stat_info.st_size; + + bzero(&ddl_log_entry, sizeof(ddl_log_entry)); + + ddl_log_entry.action_type= DDL_LOG_DROP_TRIGGER_ACTION; + ddl_log_entry.unique_id= (ulonglong) frm_length; + ddl_log_entry.db= *const_cast<LEX_CSTRING*>(db); + ddl_log_entry.name= *const_cast<LEX_CSTRING*>(table); + ddl_log_entry.from_name= *const_cast<LEX_CSTRING*>(trigger_name); + + /* + If we can store query as is, we store it. Otherwise it will be + re-generated on recovery + */ + + max_query_length= ddl_log_free_space_in_entry(&ddl_log_entry); + if (max_query_length >= query->length) + ddl_log_entry.tmp_name= *const_cast<LEX_CSTRING*>(query); + + DBUG_RETURN(ddl_log_write(ddl_state, &ddl_log_entry)); +} + + +/** + Log DROP DATABASE + + This is logged after all DROP TABLE's for the database. + As now know we are going to log DROP DATABASE to the binary log, we want + to ignore want to ignore all preceding DROP TABLE entries. We do that by + linking this entry directly after the execute entry and forgetting the + link to the previous entries (not setting ddl_log_entry.next_entry) +*/ + +bool ddl_log_drop_db(DDL_LOG_STATE *ddl_state, + const LEX_CSTRING *db, const LEX_CSTRING *path) +{ + DDL_LOG_ENTRY ddl_log_entry; + DBUG_ENTER("ddl_log_drop_db"); + + bzero(&ddl_log_entry, sizeof(ddl_log_entry)); + ddl_log_entry.action_type= DDL_LOG_DROP_DB_ACTION; + ddl_log_entry.db= *const_cast<LEX_CSTRING*>(db); + ddl_log_entry.tmp_name= *const_cast<LEX_CSTRING*>(path); + DBUG_RETURN(ddl_log_write(ddl_state, &ddl_log_entry)); +} + + +/** + Log CREATE TABLE + + @param only_frm On recovery, only drop the .frm. This is needed for + example when deleting a table that was discovered. +*/ + +bool ddl_log_create_table(DDL_LOG_STATE *ddl_state, + handlerton *hton, + const LEX_CSTRING *path, + const LEX_CSTRING *db, + const LEX_CSTRING *table, + bool only_frm) +{ + DDL_LOG_ENTRY ddl_log_entry; + DBUG_ENTER("ddl_log_create_table"); + + bzero(&ddl_log_entry, sizeof(ddl_log_entry)); + ddl_log_entry.action_type= DDL_LOG_CREATE_TABLE_ACTION; + if (hton) + lex_string_set(&ddl_log_entry.handler_name, + ha_resolve_storage_engine_name(hton)); + ddl_log_entry.db= *const_cast<LEX_CSTRING*>(db); + ddl_log_entry.name= *const_cast<LEX_CSTRING*>(table); + ddl_log_entry.tmp_name= *const_cast<LEX_CSTRING*>(path); + ddl_log_entry.flags= only_frm ? DDL_LOG_FLAG_ONLY_FRM : 0; + + DBUG_RETURN(ddl_log_write(ddl_state, &ddl_log_entry)); +} + + +/** + Log CREATE VIEW +*/ + +bool ddl_log_create_view(DDL_LOG_STATE *ddl_state, + const LEX_CSTRING *path, + enum_ddl_log_create_view_phase phase) +{ + DDL_LOG_ENTRY ddl_log_entry; + DBUG_ENTER("ddl_log_create_view"); + + bzero(&ddl_log_entry, sizeof(ddl_log_entry)); + ddl_log_entry.action_type= DDL_LOG_CREATE_VIEW_ACTION; + ddl_log_entry.tmp_name= *const_cast<LEX_CSTRING*>(path); + ddl_log_entry.phase= (uchar) phase; + DBUG_RETURN(ddl_log_write(ddl_state, &ddl_log_entry)); +} + + +/** + Log creation of temporary file that should be deleted during recovery + + @param ddl_log_state ddl_state + @param path Path to file to be deleted + @param depending_state If not NULL, then do not delete the temp file if this + entry exists and is active. +*/ + +bool ddl_log_delete_tmp_file(DDL_LOG_STATE *ddl_state, + const LEX_CSTRING *path, + DDL_LOG_STATE *depending_state) +{ + DDL_LOG_ENTRY ddl_log_entry; + DBUG_ENTER("ddl_log_delete_tmp_file"); + + bzero(&ddl_log_entry, sizeof(ddl_log_entry)); + ddl_log_entry.action_type= DDL_LOG_DELETE_TMP_FILE_ACTION; + ddl_log_entry.next_entry= ddl_state->list ? ddl_state->list->entry_pos : 0; + ddl_log_entry.tmp_name= *const_cast<LEX_CSTRING*>(path); + if (depending_state) + ddl_log_entry.unique_id= depending_state->execute_entry->entry_pos; + DBUG_RETURN(ddl_log_write(ddl_state, &ddl_log_entry)); +} + + +/** + Log CREATE TRIGGER +*/ + +bool ddl_log_create_trigger(DDL_LOG_STATE *ddl_state, + const LEX_CSTRING *db, const LEX_CSTRING *table, + const LEX_CSTRING *trigger_name, + enum_ddl_log_create_trigger_phase phase) +{ + DDL_LOG_ENTRY ddl_log_entry; + DBUG_ENTER("ddl_log_create_view"); + + bzero(&ddl_log_entry, sizeof(ddl_log_entry)); + ddl_log_entry.action_type= DDL_LOG_CREATE_TRIGGER_ACTION; + ddl_log_entry.db= *const_cast<LEX_CSTRING*>(db); + ddl_log_entry.name= *const_cast<LEX_CSTRING*>(table); + ddl_log_entry.tmp_name= *const_cast<LEX_CSTRING*>(trigger_name); + ddl_log_entry.phase= (uchar) phase; + DBUG_RETURN(ddl_log_write(ddl_state, &ddl_log_entry)); +} + + +/** + Log ALTER TABLE + + $param backup_name Name of backup table. In case of ALTER TABLE rename + this is the final table name +*/ + +bool ddl_log_alter_table(DDL_LOG_STATE *ddl_state, + handlerton *org_hton, + const LEX_CSTRING *db, const LEX_CSTRING *table, + handlerton *new_hton, + handlerton *partition_underlying_hton, + const LEX_CSTRING *new_db, + const LEX_CSTRING *new_table, + const LEX_CSTRING *frm_path, + const LEX_CSTRING *backup_name, + const LEX_CUSTRING *version, + ulonglong table_version, + bool is_renamed) +{ + DDL_LOG_ENTRY ddl_log_entry; + DBUG_ENTER("ddl_log_alter_table"); + DBUG_ASSERT(new_hton); + DBUG_ASSERT(org_hton); + + bzero(&ddl_log_entry, sizeof(ddl_log_entry)); + ddl_log_entry.action_type= DDL_LOG_ALTER_TABLE_ACTION; + if (new_hton) + lex_string_set(&ddl_log_entry.handler_name, + ha_resolve_storage_engine_name(new_hton)); + /* Store temporary table name */ + ddl_log_entry.db= *const_cast<LEX_CSTRING*>(new_db); + ddl_log_entry.name= *const_cast<LEX_CSTRING*>(new_table); + if (org_hton) + lex_string_set(&ddl_log_entry.from_handler_name, + ha_resolve_storage_engine_name(org_hton)); + ddl_log_entry.from_db= *const_cast<LEX_CSTRING*>(db); + ddl_log_entry.from_name= *const_cast<LEX_CSTRING*>(table); + ddl_log_entry.tmp_name= *const_cast<LEX_CSTRING*>(frm_path); + ddl_log_entry.extra_name= *const_cast<LEX_CSTRING*>(backup_name); + ddl_log_entry.flags= is_renamed ? DDL_LOG_FLAG_ALTER_RENAME : 0; + ddl_log_entry.unique_id= table_version; + + /* + If we are doing an inplace of a partition engine, we need to log the + underlaying engine. We store this is in ddl_log_entry.handler_name + */ + if (new_hton == org_hton && partition_underlying_hton != new_hton) + { + lex_string_set(&ddl_log_entry.handler_name, + ha_resolve_storage_engine_name(partition_underlying_hton)); + ddl_log_entry.flags|= DDL_LOG_FLAG_ALTER_PARTITION; + } + DBUG_ASSERT(version->length == MY_UUID_SIZE); + memcpy(ddl_log_entry.uuid, version->str, version->length); + DBUG_RETURN(ddl_log_write(ddl_state, &ddl_log_entry)); +} + + +/* + Store query that later should be logged to binary log + + The links of the query log event is + + execute_log_event -> first log_query_event [-> log_query_event...] -> + action_log_event (probably a LOG_ALTER_TABLE_ACTION event) + + This ensures that when we execute the log_query_event it can collect + the full query from the log_query_events and then execute the + action_log_event with the original query stored in 'recovery_state.query'. + + The query is stored in ddl_log_entry.extra_name as this is the last string + stored in the log block (makes it easier to check and debug). +*/ + +bool ddl_log_store_query(THD *thd, DDL_LOG_STATE *ddl_state, + const char *query, size_t length) +{ + DDL_LOG_ENTRY ddl_log_entry; + DDL_LOG_MEMORY_ENTRY *first_entry, *next_entry= 0; + DDL_LOG_MEMORY_ENTRY *original_entry= ddl_state->list; + size_t max_query_length; + uint entry_pos, next_entry_pos= 0, parent_entry_pos; + DBUG_ENTER("ddl_log_store_query"); + DBUG_ASSERT(length <= UINT_MAX32); + DBUG_ASSERT(length > 0); + DBUG_ASSERT(ddl_state->list); + + bzero(&ddl_log_entry, sizeof(ddl_log_entry)); + ddl_log_entry.action_type= DDL_LOG_STORE_QUERY_ACTION; + ddl_log_entry.unique_id= length; + ddl_log_entry.flags= 1; // First entry + ddl_log_entry.db= thd->db; // Current database + + max_query_length= ddl_log_free_space_in_entry(&ddl_log_entry); + + mysql_mutex_lock(&LOCK_gdl); + ddl_log_entry.entry_type= DDL_LOG_ENTRY_CODE; + + if (ddl_log_get_free_entry(&first_entry)) + goto err; + parent_entry_pos= ddl_state->list->entry_pos; + entry_pos= first_entry->entry_pos; + ddl_log_add_entry(ddl_state, first_entry); + + while (length) + { + size_t write_length= MY_MIN(length, max_query_length); + ddl_log_entry.extra_name.str= query; + ddl_log_entry.extra_name.length= write_length; + + query+= write_length; + length-= write_length; + + if (length > 0) + { + if (ddl_log_get_free_entry(&next_entry)) + goto err; + ddl_log_entry.next_entry= next_entry_pos= next_entry->entry_pos; + ddl_log_add_entry(ddl_state, next_entry); + } + else + { + /* point next link of last query_action event to the original action */ + ddl_log_entry.next_entry= parent_entry_pos; + } + set_global_from_ddl_log_entry(&ddl_log_entry); + if (unlikely(write_ddl_log_file_entry(entry_pos))) + goto err; + entry_pos= next_entry_pos; + ddl_log_entry.flags= 0; // Only first entry has this set + ddl_log_entry.db.length= 0; // Don't need DB anymore + ddl_log_entry.extra_name.length= 0; + max_query_length= ddl_log_free_space_in_entry(&ddl_log_entry); + } + if (ddl_log_write_execute_entry(first_entry->entry_pos, + &ddl_state->execute_entry)) + goto err; + + /* Set the original entry to be used for future PHASE updates */ + ddl_state->main_entry= original_entry; + mysql_mutex_unlock(&LOCK_gdl); + DBUG_RETURN(0); +err: + /* + Allocated ddl_log entries will be released by the + ddl_log_release_entries() call in dl_log_complete() + */ + mysql_mutex_unlock(&LOCK_gdl); + DBUG_RETURN(1); +} + + +/* + Log an delete frm file +*/ + +/* + TODO: Partitioning atomic DDL refactoring: this should be replaced with + ddl_log_create_table(). +*/ +bool ddl_log_delete_frm(DDL_LOG_STATE *ddl_state, const char *to_path) +{ + DDL_LOG_ENTRY ddl_log_entry; + DDL_LOG_MEMORY_ENTRY *log_entry; + DBUG_ENTER("ddl_log_delete_frm"); + bzero(&ddl_log_entry, sizeof(ddl_log_entry)); + ddl_log_entry.action_type= DDL_LOG_DELETE_ACTION; + ddl_log_entry.next_entry= ddl_state->list ? ddl_state->list->entry_pos : 0; + + lex_string_set(&ddl_log_entry.handler_name, reg_ext); + lex_string_set(&ddl_log_entry.name, to_path); + + mysql_mutex_assert_owner(&LOCK_gdl); + if (ddl_log_write_entry(&ddl_log_entry, &log_entry)) + DBUG_RETURN(1); + + ddl_log_add_entry(ddl_state, log_entry); + DBUG_RETURN(0); +} |