diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 18:00:34 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 18:00:34 +0000 |
commit | 3f619478f796eddbba6e39502fe941b285dd97b1 (patch) | |
tree | e2c7b5777f728320e5b5542b6213fd3591ba51e2 /sql/sql_rename.cc | |
parent | Initial commit. (diff) | |
download | mariadb-3f619478f796eddbba6e39502fe941b285dd97b1.tar.xz mariadb-3f619478f796eddbba6e39502fe941b285dd97b1.zip |
Adding upstream version 1:10.11.6.upstream/1%10.11.6upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sql/sql_rename.cc')
-rw-r--r-- | sql/sql_rename.cc | 549 |
1 files changed, 549 insertions, 0 deletions
diff --git a/sql/sql_rename.cc b/sql/sql_rename.cc new file mode 100644 index 00000000..1137e0e1 --- /dev/null +++ b/sql/sql_rename.cc @@ -0,0 +1,549 @@ +/* + Copyright (c) 2000, 2013, Oracle and/or its affiliates. + Copyright (c) 2011, 2021, 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" +#include "ddl_log.h" +#include "wsrep_mysqld.h" +#include "debug.h" + +/* used to hold table entries for as part of list of renamed temporary tables */ +struct TABLE_PAIR +{ + TABLE_LIST *from, *to; +}; + + +static bool rename_tables(THD *thd, TABLE_LIST *table_list, + DDL_LOG_STATE *ddl_log_state, + bool skip_error, bool if_exits, + bool *force_if_exists); + +/* + 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}; + DDL_LOG_STATE ddl_log_state; + 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; + bzero(&ddl_log_state, sizeof(ddl_log_state)); + + /* + An exclusive lock on table names is satisfactory to ensure + no other thread accesses this table. + */ + error= rename_tables(thd, table_list, &ddl_log_state, + 0, if_exists, &force_if_exists); + + 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; + } + + debug_crash_here("ddl_log_rename_before_binlog"); + /* + Store xid in ddl log and binary log so that we can check on ddl recovery + if the item is in the binary log (and thus the operation was complete + */ + thd->binlog_xid= thd->query_id; + ddl_log_update_xid(&ddl_log_state, thd->binlog_xid); + binlog_error= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + if (binlog_error) + error= 1; + thd->binlog_xid= 0; + thd->variables.option_bits= save_option_bits; + debug_crash_here("ddl_log_rename_after_binlog"); + + if (likely(!binlog_error)) + my_ok(thd); + } + + if (likely(!error)) + { + query_cache_invalidate3(thd, table_list, 0); + ddl_log_complete(&ddl_log_state); + } + else + { + /* Revert the renames of normal tables with the help of the ddl log */ + ddl_log_revert(thd, &ddl_log_state); + } + +err: + DBUG_RETURN(error || binlog_error); +} + + +static bool +do_rename_temporary(THD *thd, TABLE_LIST *ren_table, TABLE_LIST *new_table) +{ + 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)); +} + + +/** + Parameters for do_rename +*/ + +struct rename_param +{ + LEX_CSTRING old_alias, new_alias; + LEX_CUSTRING old_version; + handlerton *from_table_hton; +}; + + +/** + check_rename() + + Check pre-conditions for rename + - From table should exists + - To table should not exists. + + SYNOPSIS + @param new_table_name The new table/view name + @param new_table_alias The new table/view alias + @param if_exists If not set, give an error if the table does not + exists. If set, just give a warning in this case. + @return + @retval 0 ok + @retval >0 Error (from table doesn't exists or to table exists) + @retval <0 Can't do rename, but no error +*/ + +static int +check_rename(THD *thd, rename_param *param, + TABLE_LIST *ren_table, + const LEX_CSTRING *new_db, + const LEX_CSTRING *new_table_name, + const LEX_CSTRING *new_table_alias, + bool if_exists) +{ + DBUG_ENTER("check_rename"); + DBUG_PRINT("enter", ("if_exists: %d", (int) if_exists)); + + + if (lower_case_table_names == 2) + { + param->old_alias= ren_table->alias; + param->new_alias= *new_table_alias; + } + else + { + param->old_alias= ren_table->table_name; + param->new_alias= *new_table_name; + } + DBUG_ASSERT(param->new_alias.str); + + if (!ha_table_exists(thd, &ren_table->db, ¶m->old_alias, + ¶m->old_version, NULL, + ¶m->from_table_hton) || + !param->from_table_hton) + { + my_error(ER_NO_SUCH_TABLE, MYF(if_exists ? ME_NOTE : 0), + ren_table->db.str, param->old_alias.str); + DBUG_RETURN(if_exists ? -1 : 1); + } + + if (param->from_table_hton != view_pseudo_hton && + ha_check_if_updates_are_ignored(thd, param->from_table_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, ¶m->old_alias, FRM_ONLY, 0); + DBUG_RETURN(-1); + } + + if (ha_table_exists(thd, new_db, ¶m->new_alias, NULL, NULL, 0)) + { + my_error(ER_TABLE_EXISTS_ERROR, MYF(0), param->new_alias.str); + DBUG_RETURN(1); // This can't be skipped + } + DBUG_RETURN(0); +} + + +/* + 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 + skip_error 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. + In case of failure, all changes will be reverted + + RETURN + false Ok + true rename failed +*/ + +static bool +do_rename(THD *thd, rename_param *param, DDL_LOG_STATE *ddl_log_state, + TABLE_LIST *ren_table, const LEX_CSTRING *new_db, + bool skip_error, bool *force_if_exists) +{ + int rc= 1; + handlerton *hton; + LEX_CSTRING *old_alias, *new_alias; + TRIGGER_RENAME_PARAM rename_param; + DBUG_ENTER("do_rename"); + DBUG_PRINT("enter", ("skip_error: %d", (int) skip_error)); + + old_alias= ¶m->old_alias; + new_alias= ¶m->new_alias; + hton= param->from_table_hton; + + DBUG_ASSERT(!thd->locked_tables_mode); + +#ifdef WITH_WSREP + if (WSREP(thd) && hton && hton != view_pseudo_hton && + !wsrep_should_replicate_ddl(thd, hton)) + 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; + + /* Check if we can rename triggers */ + if (Table_triggers_list::prepare_for_rename(thd, &rename_param, + &ren_table->db, + old_alias, + &ren_table->table_name, + new_db, + new_alias)) + DBUG_RETURN(!skip_error); + + thd->replication_flags= 0; + + if (ddl_log_rename_table(ddl_log_state, hton, + &ren_table->db, old_alias, new_db, new_alias)) + DBUG_RETURN(1); + + debug_crash_here("ddl_log_rename_before_rename_table"); + if (!(rc= mysql_rename_table(hton, &ren_table->db, old_alias, + new_db, new_alias, ¶m->old_version, 0))) + { + /* Table rename succeded. + It's safe to start recovery at rename trigger phase + */ + debug_crash_here("ddl_log_rename_before_phase_trigger"); + ddl_log_update_phase(ddl_log_state, DDL_RENAME_PHASE_TRIGGER); + + debug_crash_here("ddl_log_rename_before_rename_trigger"); + + if (!(rc= Table_triggers_list::change_table_name(thd, + &rename_param, + &ren_table->db, + old_alias, + &ren_table->table_name, + new_db, + new_alias))) + { + debug_crash_here("ddl_log_rename_before_stat_tables"); + (void) rename_table_in_stat_tables(thd, &ren_table->db, + &ren_table->table_name, + new_db, new_alias); + debug_crash_here("ddl_log_rename_after_stat_tables"); + } + else + { + /* + 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. + */ + debug_crash_here("ddl_log_rename_after_failed_rename_trigger"); + (void) mysql_rename_table(hton, new_db, new_alias, + &ren_table->db, old_alias, ¶m->old_version, + NO_FK_CHECKS); + debug_crash_here("ddl_log_rename_after_revert_rename_table"); + ddl_log_disable_entry(ddl_log_state); + debug_crash_here("ddl_log_rename_after_disable_entry"); + } + } + 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); + DBUG_RETURN(1); + } + + ddl_log_rename_view(ddl_log_state, &ren_table->db, + &ren_table->table_name, new_db, new_alias); + debug_crash_here("ddl_log_rename_before_rename_view"); + rc= mysql_rename_view(thd, new_db, new_alias, &ren_table->db, + &ren_table->table_name); + debug_crash_here("ddl_log_rename_after_rename_view"); + if (rc) + { + /* + On error mysql_rename_view() will leave things as such. + */ + ddl_log_disable_entry(ddl_log_state); + debug_crash_here("ddl_log_rename_after_disable_entry"); + } + } + 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 + ddl_log_state ddl logging + 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 + 1 error + All tables are reverted to their original names +*/ + +static bool +rename_tables(THD *thd, TABLE_LIST *table_list, DDL_LOG_STATE *ddl_log_state, + bool skip_error, bool if_exists, bool *force_if_exists) +{ + TABLE_LIST *ren_table, *new_table; + List<TABLE_PAIR> tmp_tables; + 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)) + { + /* + Store renamed temporary tables into a list. + We don't store these in the ddl log to avoid writes and syncs + when only using temporary tables. We don't need the log as + all temporary tables will disappear anyway in a crash. + */ + TABLE_PAIR *pair= (TABLE_PAIR*) thd->alloc(sizeof(*pair)); + if (! pair || tmp_tables.push_front(pair, thd->mem_root)) + goto revert_rename; + pair->from= ren_table; + pair->to= new_table; + + if (do_rename_temporary(thd, ren_table, new_table)) + goto revert_rename; + } + else + { + int error; + rename_param param; + error= check_rename(thd, ¶m, ren_table, &new_table->db, + &new_table->table_name, + &new_table->alias, (skip_error || if_exists)); + if (error < 0) + continue; // Ignore rename (if exists) + if (error > 0) + goto revert_rename; + + if (do_rename(thd, ¶m, ddl_log_state, + ren_table, &new_table->db, + skip_error, force_if_exists)) + goto revert_rename; + } + } + DBUG_RETURN(0); + +revert_rename: + /* Revert temporary tables. Normal tables are reverted in the caller */ + List_iterator_fast<TABLE_PAIR> it(tmp_tables); + while (TABLE_PAIR *pair= it++) + do_rename_temporary(thd, pair->to, pair->from); + + DBUG_RETURN(1); +} |