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 | |
34 | static TABLE_LIST *rename_tables(THD *thd, TABLE_LIST *table_list, |
35 | bool skip_error); |
36 | static 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 | |
40 | static 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 | |
47 | bool 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 | |
186 | err: |
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 | */ |
201 | static 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 | |
216 | static bool |
217 | do_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 | |
258 | static bool |
259 | do_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 | |
366 | static TABLE_LIST * |
367 | rename_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 | |