diff options
Diffstat (limited to 'sql/sql_rename.cc')
-rw-r--r-- | sql/sql_rename.cc | 429 |
1 files changed, 429 insertions, 0 deletions
diff --git a/sql/sql_rename.cc b/sql/sql_rename.cc new file mode 100644 index 00000000..77a1e46a --- /dev/null +++ b/sql/sql_rename.cc @@ -0,0 +1,429 @@ +/* + Copyright (c) 2000, 2013, Oracle and/or its affiliates. + Copyright (c) 2011, 2013, Monty Program Ab. + + 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 */ + +/* + Atomic rename of table; RENAME TABLE t1 to t2, tmp to t1 [,...] +*/ + +#include "mariadb.h" +#include "sql_priv.h" +#include "unireg.h" +#include "sql_rename.h" +#include "sql_cache.h" // query_cache_* +#include "sql_table.h" // write_bin_log +#include "sql_view.h" // mysql_frm_type, mysql_rename_view +#include "sql_trigger.h" +#include "sql_base.h" // tdc_remove_table, lock_table_names, +#include "sql_handler.h" // mysql_ha_rm_tables +#include "sql_statistics.h" + +static TABLE_LIST *rename_tables(THD *thd, TABLE_LIST *table_list, + bool skip_error, bool if_exits, + bool *force_if_exists); +static bool do_rename(THD *thd, TABLE_LIST *ren_table, + const LEX_CSTRING *new_db, + const LEX_CSTRING *new_table_name, + const LEX_CSTRING *new_table_alias, + bool skip_error, bool if_exists, bool *force_if_exists); + +static TABLE_LIST *reverse_table_list(TABLE_LIST *table_list); + +/* + Every two entries in the table_list form a pair of original name and + the new name. +*/ + +bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent, + bool if_exists) +{ + bool error= 1; + bool binlog_error= 0, force_if_exists; + TABLE_LIST *ren_table= 0; + int to_table; + const char *rename_log_table[2]= {NULL, NULL}; + DBUG_ENTER("mysql_rename_tables"); + + /* + Avoid problems with a rename on a table that we have locked or + if the user is trying to to do this in a transcation context + */ + + if (thd->locked_tables_mode || thd->in_active_multi_stmt_transaction()) + { + my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, + ER_THD(thd, ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); + DBUG_RETURN(1); + } + + mysql_ha_rm_tables(thd, table_list); + + if (logger.is_log_table_enabled(QUERY_LOG_GENERAL) || + logger.is_log_table_enabled(QUERY_LOG_SLOW)) + { + + /* + Rules for rename of a log table: + + IF 1. Log tables are enabled + AND 2. Rename operates on the log table and nothing is being + renamed to the log table. + DO 3. Throw an error message. + ELSE 4. Perform rename. + */ + + for (to_table= 0, ren_table= table_list; ren_table; + to_table= 1 - to_table, ren_table= ren_table->next_local) + { + int log_table_rename; + if ((log_table_rename= check_if_log_table(ren_table, TRUE, NullS))) + { + /* + as we use log_table_rename as an array index, we need it to start + with 0, while QUERY_LOG_SLOW == 1 and QUERY_LOG_GENERAL == 2. + So, we shift the value to start with 0; + */ + log_table_rename--; + if (rename_log_table[log_table_rename]) + { + if (to_table) + rename_log_table[log_table_rename]= NULL; + else + { + /* + Two renames of "log_table TO" w/o rename "TO log_table" in + between. + */ + my_error(ER_CANT_RENAME_LOG_TABLE, MYF(0), + ren_table->table_name.str, + ren_table->table_name.str); + goto err; + } + } + else + { + if (to_table) + { + /* + Attempt to rename a table TO log_table w/o renaming + log_table TO some table. + */ + my_error(ER_CANT_RENAME_LOG_TABLE, MYF(0), + ren_table->table_name.str, + ren_table->table_name.str); + goto err; + } + else + { + /* save the name of the log table to report an error */ + rename_log_table[log_table_rename]= ren_table->table_name.str; + } + } + } + } + if (rename_log_table[0] || rename_log_table[1]) + { + if (rename_log_table[0]) + my_error(ER_CANT_RENAME_LOG_TABLE, MYF(0), rename_log_table[0], + rename_log_table[0]); + else + my_error(ER_CANT_RENAME_LOG_TABLE, MYF(0), rename_log_table[1], + rename_log_table[1]); + goto err; + } + } + + if (lock_table_names(thd, table_list, 0, thd->variables.lock_wait_timeout, + 0)) + goto err; + + error=0; + /* + An exclusive lock on table names is satisfactory to ensure + no other thread accesses this table. + */ + if ((ren_table= rename_tables(thd, table_list, 0, if_exists, + &force_if_exists))) + { + /* Rename didn't succeed; rename back the tables in reverse order */ + TABLE_LIST *table; + + /* Reverse the table list */ + table_list= reverse_table_list(table_list); + + /* Find the last renamed table */ + for (table= table_list; + table->next_local != ren_table ; + table= table->next_local->next_local) ; + table= table->next_local->next_local; // Skip error table + /* Revert to old names */ + rename_tables(thd, table, 1, if_exists, &force_if_exists); + + /* Revert the table list (for prepared statements) */ + table_list= reverse_table_list(table_list); + + error= 1; + } + + if (likely(!silent && !error)) + { + ulonglong save_option_bits= thd->variables.option_bits; + if (force_if_exists && ! if_exists) + { + /* Add IF EXISTS to binary log */ + thd->variables.option_bits|= OPTION_IF_EXISTS; + } + binlog_error= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + thd->variables.option_bits= save_option_bits; + + if (likely(!binlog_error)) + my_ok(thd); + } + + if (likely(!error)) + query_cache_invalidate3(thd, table_list, 0); + +err: + DBUG_RETURN(error || binlog_error); +} + + +/* + reverse table list + + SYNOPSIS + reverse_table_list() + table_list pointer to table _list + + RETURN + pointer to new (reversed) list +*/ +static TABLE_LIST *reverse_table_list(TABLE_LIST *table_list) +{ + TABLE_LIST *prev= 0; + + while (table_list) + { + TABLE_LIST *next= table_list->next_local; + table_list->next_local= prev; + prev= table_list; + table_list= next; + } + return (prev); +} + + +static bool +do_rename_temporary(THD *thd, TABLE_LIST *ren_table, TABLE_LIST *new_table, + bool skip_error) +{ + LEX_CSTRING *new_alias; + DBUG_ENTER("do_rename_temporary"); + + new_alias= (lower_case_table_names == 2) ? &new_table->alias : + &new_table->table_name; + + if (thd->find_temporary_table(new_table, THD::TMP_TABLE_ANY)) + { + my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_alias->str); + DBUG_RETURN(1); // This can't be skipped + } + + + DBUG_RETURN(thd->rename_temporary_table(ren_table->table, + &new_table->db, new_alias)); +} + + +/* + Rename a single table or a view + + SYNPOSIS + do_rename() + thd Thread handle + ren_table A table/view to be renamed + new_db The database to which the table to be moved to + new_table_name The new table/view name + new_table_alias The new table/view alias + skip_error Whether to skip error + if_exists Skip error, but only if the table didn't exists + force_if_exists Set to 1 if we have to log the query with 'IF EXISTS' + Otherwise don't touch the value + + DESCRIPTION + Rename a single table or a view. + + RETURN + false Ok + true rename failed +*/ + +static bool +do_rename(THD *thd, TABLE_LIST *ren_table, const LEX_CSTRING *new_db, + const LEX_CSTRING *new_table_name, + const LEX_CSTRING *new_table_alias, + bool skip_error, bool if_exists, bool *force_if_exists) +{ + int rc= 1; + handlerton *hton, *new_hton; + LEX_CSTRING old_alias, new_alias; + DBUG_ENTER("do_rename"); + DBUG_PRINT("enter", ("skip_error: %d if_exists: %d", (int) skip_error, + (int) if_exists)); + + if (lower_case_table_names == 2) + { + old_alias= ren_table->alias; + new_alias= *new_table_alias; + } + else + { + old_alias= ren_table->table_name; + new_alias= *new_table_name; + } + DBUG_ASSERT(new_alias.str); + + if (!ha_table_exists(thd, &ren_table->db, &old_alias, &hton) || !hton) + { + my_error(ER_NO_SUCH_TABLE, MYF((skip_error | if_exists) ? ME_NOTE : 0), + ren_table->db.str, old_alias.str); + DBUG_RETURN(skip_error || if_exists ? 0 : 1); + } + + if (hton != view_pseudo_hton && + ha_check_if_updates_are_ignored(thd, hton, "RENAME")) + { + /* + Shared table. Just drop the old .frm as it's not correct anymore + Discovery will find the old table when it's accessed + */ + tdc_remove_table(thd, ren_table->db.str, ren_table->table_name.str); + quick_rm_table(thd, 0, &ren_table->db, &old_alias, FRM_ONLY, 0); + DBUG_RETURN(0); + } + + if (ha_table_exists(thd, new_db, &new_alias, &new_hton)) + { + my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_alias.str); + DBUG_RETURN(1); // This can't be skipped + } + + DBUG_ASSERT(!thd->locked_tables_mode); + +#ifdef WITH_WSREP + if (WSREP(thd) && hton && hton != view_pseudo_hton && + !wsrep_should_replicate_ddl(thd, hton->db_type)) + DBUG_RETURN(1); +#endif + + tdc_remove_table(thd, ren_table->db.str, ren_table->table_name.str); + + if (hton != view_pseudo_hton) + { + if (hton->flags & HTON_TABLE_MAY_NOT_EXIST_ON_SLAVE) + *force_if_exists= 1; + + thd->replication_flags= 0; + if (!(rc= mysql_rename_table(hton, &ren_table->db, &old_alias, + new_db, &new_alias, 0))) + { + (void) rename_table_in_stat_tables(thd, &ren_table->db, + &ren_table->table_name, + new_db, &new_alias); + if ((rc= Table_triggers_list::change_table_name(thd, &ren_table->db, + &old_alias, + &ren_table->table_name, + new_db, + &new_alias))) + { + /* + We've succeeded in renaming table's .frm and in updating + corresponding handler data, but have failed to update table's + triggers appropriately. So let us revert operations on .frm + and handler's data and report about failure to rename table. + */ + (void) mysql_rename_table(hton, new_db, &new_alias, + &ren_table->db, &old_alias, NO_FK_CHECKS); + } + } + if (thd->replication_flags & OPTION_IF_EXISTS) + *force_if_exists= 1; + } + else + { + /* + Change of schema is not allowed + except of ALTER ...UPGRADE DATA DIRECTORY NAME command + because a view has valid internal db&table names in this case. + */ + if (thd->lex->sql_command != SQLCOM_ALTER_DB_UPGRADE && + cmp(&ren_table->db, new_db)) + my_error(ER_FORBID_SCHEMA_CHANGE, MYF(0), ren_table->db.str, new_db->str); + else + rc= mysql_rename_view(thd, new_db, &new_alias, ren_table); + } + DBUG_RETURN(rc && !skip_error ? 1 : 0); +} + + +/* + Rename all tables in list; Return pointer to wrong entry if something goes + wrong. Note that the table_list may be empty! +*/ + +/* + Rename tables/views in the list + + SYNPOSIS + rename_tables() + thd Thread handle + table_list List of tables to rename + skip_error Whether to skip errors + if_exists Don't give an error if table doesn't exists + force_if_exists Set to 1 if we have to log the query with 'IF EXISTS' + Otherwise set it to 0 + + DESCRIPTION + Take a table/view name from and odd list element and rename it to a + the name taken from list element+1. Note that the table_list may be + empty. + + RETURN + 0 Ok + table pointer to the table list element which rename failed +*/ + +static TABLE_LIST * +rename_tables(THD *thd, TABLE_LIST *table_list, bool skip_error, + bool if_exists, bool *force_if_exists) +{ + TABLE_LIST *ren_table, *new_table; + DBUG_ENTER("rename_tables"); + + *force_if_exists= 0; + + for (ren_table= table_list; ren_table; ren_table= new_table->next_local) + { + new_table= ren_table->next_local; + + if (is_temporary_table(ren_table) ? + do_rename_temporary(thd, ren_table, new_table, skip_error) : + do_rename(thd, ren_table, &new_table->db, &new_table->table_name, + &new_table->alias, skip_error, if_exists, force_if_exists)) + DBUG_RETURN(ren_table); + } + DBUG_RETURN(0); +} |