1/*
2 Copyright (c) 2000, 2013, Oracle and/or its affiliates.
3 Copyright (c) 2011, 2013, Monty Program Ab.
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; version 2 of the License.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
17
18/*
19 Atomic rename of table; RENAME TABLE t1 to t2, tmp to t1 [,...]
20*/
21
22#include "mariadb.h"
23#include "sql_priv.h"
24#include "unireg.h"
25#include "sql_rename.h"
26#include "sql_cache.h" // query_cache_*
27#include "sql_table.h" // write_bin_log
28#include "sql_view.h" // mysql_frm_type, mysql_rename_view
29#include "sql_trigger.h"
30#include "sql_base.h" // tdc_remove_table, lock_table_names,
31#include "sql_handler.h" // mysql_ha_rm_tables
32#include "sql_statistics.h"
33
34static TABLE_LIST *rename_tables(THD *thd, TABLE_LIST *table_list,
35 bool skip_error);
36static bool do_rename(THD *thd, TABLE_LIST *ren_table, const LEX_CSTRING *new_db,
37 const LEX_CSTRING *new_table_name, const LEX_CSTRING *new_table_alias,
38 bool skip_error);
39
40static TABLE_LIST *reverse_table_list(TABLE_LIST *table_list);
41
42/*
43 Every two entries in the table_list form a pair of original name and
44 the new name.
45*/
46
47bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent)
48{
49 bool error= 1;
50 bool binlog_error= 0;
51 TABLE_LIST *ren_table= 0;
52 int to_table;
53 const char *rename_log_table[2]= {NULL, NULL};
54 DBUG_ENTER("mysql_rename_tables");
55
56 /*
57 Avoid problems with a rename on a table that we have locked or
58 if the user is trying to to do this in a transcation context
59 */
60
61 if (thd->locked_tables_mode || thd->in_active_multi_stmt_transaction())
62 {
63 my_message(ER_LOCK_OR_ACTIVE_TRANSACTION,
64 ER_THD(thd, ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0));
65 DBUG_RETURN(1);
66 }
67
68 mysql_ha_rm_tables(thd, table_list);
69
70 if (logger.is_log_table_enabled(QUERY_LOG_GENERAL) ||
71 logger.is_log_table_enabled(QUERY_LOG_SLOW))
72 {
73
74 /*
75 Rules for rename of a log table:
76
77 IF 1. Log tables are enabled
78 AND 2. Rename operates on the log table and nothing is being
79 renamed to the log table.
80 DO 3. Throw an error message.
81 ELSE 4. Perform rename.
82 */
83
84 for (to_table= 0, ren_table= table_list; ren_table;
85 to_table= 1 - to_table, ren_table= ren_table->next_local)
86 {
87 int log_table_rename;
88 if ((log_table_rename= check_if_log_table(ren_table, TRUE, NullS)))
89 {
90 /*
91 as we use log_table_rename as an array index, we need it to start
92 with 0, while QUERY_LOG_SLOW == 1 and QUERY_LOG_GENERAL == 2.
93 So, we shift the value to start with 0;
94 */
95 log_table_rename--;
96 if (rename_log_table[log_table_rename])
97 {
98 if (to_table)
99 rename_log_table[log_table_rename]= NULL;
100 else
101 {
102 /*
103 Two renames of "log_table TO" w/o rename "TO log_table" in
104 between.
105 */
106 my_error(ER_CANT_RENAME_LOG_TABLE, MYF(0),
107 ren_table->table_name.str,
108 ren_table->table_name.str);
109 goto err;
110 }
111 }
112 else
113 {
114 if (to_table)
115 {
116 /*
117 Attempt to rename a table TO log_table w/o renaming
118 log_table TO some table.
119 */
120 my_error(ER_CANT_RENAME_LOG_TABLE, MYF(0),
121 ren_table->table_name.str,
122 ren_table->table_name.str);
123 goto err;
124 }
125 else
126 {
127 /* save the name of the log table to report an error */
128 rename_log_table[log_table_rename]= ren_table->table_name.str;
129 }
130 }
131 }
132 }
133 if (rename_log_table[0] || rename_log_table[1])
134 {
135 if (rename_log_table[0])
136 my_error(ER_CANT_RENAME_LOG_TABLE, MYF(0), rename_log_table[0],
137 rename_log_table[0]);
138 else
139 my_error(ER_CANT_RENAME_LOG_TABLE, MYF(0), rename_log_table[1],
140 rename_log_table[1]);
141 goto err;
142 }
143 }
144
145 if (lock_table_names(thd, table_list, 0, thd->variables.lock_wait_timeout,
146 0))
147 goto err;
148
149 error=0;
150 /*
151 An exclusive lock on table names is satisfactory to ensure
152 no other thread accesses this table.
153 */
154 if ((ren_table=rename_tables(thd,table_list,0)))
155 {
156 /* Rename didn't succeed; rename back the tables in reverse order */
157 TABLE_LIST *table;
158
159 /* Reverse the table list */
160 table_list= reverse_table_list(table_list);
161
162 /* Find the last renamed table */
163 for (table= table_list;
164 table->next_local != ren_table ;
165 table= table->next_local->next_local) ;
166 table= table->next_local->next_local; // Skip error table
167 /* Revert to old names */
168 rename_tables(thd, table, 1);
169
170 /* Revert the table list (for prepared statements) */
171 table_list= reverse_table_list(table_list);
172
173 error= 1;
174 }
175
176 if (likely(!silent && !error))
177 {
178 binlog_error= write_bin_log(thd, TRUE, thd->query(), thd->query_length());
179 if (likely(!binlog_error))
180 my_ok(thd);
181 }
182
183 if (likely(!error))
184 query_cache_invalidate3(thd, table_list, 0);
185
186err:
187 DBUG_RETURN(error || binlog_error);
188}
189
190
191/*
192 reverse table list
193
194 SYNOPSIS
195 reverse_table_list()
196 table_list pointer to table _list
197
198 RETURN
199 pointer to new (reversed) list
200*/
201static TABLE_LIST *reverse_table_list(TABLE_LIST *table_list)
202{
203 TABLE_LIST *prev= 0;
204
205 while (table_list)
206 {
207 TABLE_LIST *next= table_list->next_local;
208 table_list->next_local= prev;
209 prev= table_list;
210 table_list= next;
211 }
212 return (prev);
213}
214
215
216static bool
217do_rename_temporary(THD *thd, TABLE_LIST *ren_table, TABLE_LIST *new_table,
218 bool skip_error)
219{
220 LEX_CSTRING *new_alias;
221 DBUG_ENTER("do_rename_temporary");
222
223 new_alias= (lower_case_table_names == 2) ? &new_table->alias :
224 &new_table->table_name;
225
226 if (is_temporary_table(new_table))
227 {
228 my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_alias->str);
229 DBUG_RETURN(1); // This can't be skipped
230 }
231
232
233 DBUG_RETURN(thd->rename_temporary_table(ren_table->table,
234 &new_table->db, new_alias));
235}
236
237
238/*
239 Rename a single table or a view
240
241 SYNPOSIS
242 do_rename()
243 thd Thread handle
244 ren_table A table/view to be renamed
245 new_db The database to which the table to be moved to
246 new_table_name The new table/view name
247 new_table_alias The new table/view alias
248 skip_error Whether to skip error
249
250 DESCRIPTION
251 Rename a single table or a view.
252
253 RETURN
254 false Ok
255 true rename failed
256*/
257
258static bool
259do_rename(THD *thd, TABLE_LIST *ren_table, const LEX_CSTRING *new_db,
260 const LEX_CSTRING *new_table_name, const LEX_CSTRING *new_table_alias,
261 bool skip_error)
262{
263 int rc= 1;
264 handlerton *hton;
265 LEX_CSTRING old_alias, new_alias;
266 DBUG_ENTER("do_rename");
267
268 if (lower_case_table_names == 2)
269 {
270 old_alias= ren_table->alias;
271 new_alias= *new_table_alias;
272 }
273 else
274 {
275 old_alias= ren_table->table_name;
276 new_alias= *new_table_name;
277 }
278 DBUG_ASSERT(new_alias.str);
279
280 if (ha_table_exists(thd, new_db, &new_alias))
281 {
282 my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_alias.str);
283 DBUG_RETURN(1); // This can't be skipped
284 }
285
286 if (ha_table_exists(thd, &ren_table->db, &old_alias, &hton) && hton)
287 {
288 DBUG_ASSERT(!thd->locked_tables_mode);
289 tdc_remove_table(thd, TDC_RT_REMOVE_ALL,
290 ren_table->db.str, ren_table->table_name.str, false);
291
292 if (hton != view_pseudo_hton)
293 {
294 if (!(rc= mysql_rename_table(hton, &ren_table->db, &old_alias,
295 new_db, &new_alias, 0)))
296 {
297 (void) rename_table_in_stat_tables(thd, &ren_table->db,
298 &ren_table->table_name,
299 new_db, &new_alias);
300 if ((rc= Table_triggers_list::change_table_name(thd, &ren_table->db,
301 &old_alias,
302 &ren_table->table_name,
303 new_db,
304 &new_alias)))
305 {
306 /*
307 We've succeeded in renaming table's .frm and in updating
308 corresponding handler data, but have failed to update table's
309 triggers appropriately. So let us revert operations on .frm
310 and handler's data and report about failure to rename table.
311 */
312 (void) mysql_rename_table(hton, new_db, &new_alias,
313 &ren_table->db, &old_alias, NO_FK_CHECKS);
314 }
315 }
316 }
317 else
318 {
319 /*
320 change of schema is not allowed
321 except of ALTER ...UPGRADE DATA DIRECTORY NAME command
322 because a view has valid internal db&table names in this case.
323 */
324 if (thd->lex->sql_command != SQLCOM_ALTER_DB_UPGRADE &&
325 cmp(&ren_table->db, new_db))
326 my_error(ER_FORBID_SCHEMA_CHANGE, MYF(0), ren_table->db.str, new_db->str);
327 else
328 rc= mysql_rename_view(thd, new_db, &new_alias, ren_table);
329 }
330 }
331 else
332 {
333 my_error(ER_NO_SUCH_TABLE, MYF(0), ren_table->db.str, old_alias.str);
334 }
335 if (unlikely(rc && !skip_error))
336 DBUG_RETURN(1);
337
338 DBUG_RETURN(0);
339}
340
341
342/*
343 Rename all tables in list; Return pointer to wrong entry if something goes
344 wrong. Note that the table_list may be empty!
345*/
346
347/*
348 Rename tables/views in the list
349
350 SYNPOSIS
351 rename_tables()
352 thd Thread handle
353 table_list List of tables to rename
354 skip_error Whether to skip errors
355
356 DESCRIPTION
357 Take a table/view name from and odd list element and rename it to a
358 the name taken from list element+1. Note that the table_list may be
359 empty.
360
361 RETURN
362 false Ok
363 true rename failed
364*/
365
366static TABLE_LIST *
367rename_tables(THD *thd, TABLE_LIST *table_list, bool skip_error)
368{
369 TABLE_LIST *ren_table, *new_table;
370
371 DBUG_ENTER("rename_tables");
372
373 for (ren_table= table_list; ren_table; ren_table= new_table->next_local)
374 {
375 new_table= ren_table->next_local;
376
377 if (is_temporary_table(ren_table) ?
378 do_rename_temporary(thd, ren_table, new_table, skip_error) :
379 do_rename(thd, ren_table, &new_table->db, &new_table->table_name,
380 &new_table->alias, skip_error))
381 DBUG_RETURN(ren_table);
382 }
383 DBUG_RETURN(0);
384}
385