1 | /* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. |
2 | Copyright (c) 2016, MariaDB Corporation |
3 | |
4 | This program is free software; you can redistribute it and/or modify |
5 | it under the terms of the GNU General Public License as published by |
6 | the Free Software Foundation; version 2 of the License. |
7 | |
8 | This program is distributed in the hope that it will be useful, |
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | GNU General Public License for more details. |
12 | |
13 | You should have received a copy of the GNU General Public License |
14 | along with this program; if not, write to the Free Software |
15 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ |
16 | |
17 | #include "mariadb.h" |
18 | #include "sql_parse.h" // check_access |
19 | #include "sql_table.h" // mysql_alter_table, |
20 | // mysql_exchange_partition |
21 | #include "sql_alter.h" |
22 | #include "wsrep_mysqld.h" |
23 | |
24 | Alter_info::Alter_info(const Alter_info &rhs, MEM_ROOT *mem_root) |
25 | :drop_list(rhs.drop_list, mem_root), |
26 | alter_list(rhs.alter_list, mem_root), |
27 | key_list(rhs.key_list, mem_root), |
28 | create_list(rhs.create_list, mem_root), |
29 | check_constraint_list(rhs.check_constraint_list, mem_root), |
30 | flags(rhs.flags), partition_flags(rhs.partition_flags), |
31 | keys_onoff(rhs.keys_onoff), |
32 | partition_names(rhs.partition_names, mem_root), |
33 | num_parts(rhs.num_parts), |
34 | requested_algorithm(rhs.requested_algorithm), |
35 | requested_lock(rhs.requested_lock) |
36 | { |
37 | /* |
38 | Make deep copies of used objects. |
39 | This is not a fully deep copy - clone() implementations |
40 | of Alter_drop, Alter_column, Key, foreign_key, Key_part_spec |
41 | do not copy string constants. At the same length the only |
42 | reason we make a copy currently is that ALTER/CREATE TABLE |
43 | code changes input Alter_info definitions, but string |
44 | constants never change. |
45 | */ |
46 | list_copy_and_replace_each_value(drop_list, mem_root); |
47 | list_copy_and_replace_each_value(alter_list, mem_root); |
48 | list_copy_and_replace_each_value(key_list, mem_root); |
49 | list_copy_and_replace_each_value(create_list, mem_root); |
50 | /* partition_names are not deeply copied currently */ |
51 | } |
52 | |
53 | |
54 | bool Alter_info::set_requested_algorithm(const LEX_CSTRING *str) |
55 | { |
56 | // To avoid adding new keywords to the grammar, we match strings here. |
57 | if (lex_string_eq(str, STRING_WITH_LEN("INPLACE" ))) |
58 | requested_algorithm= ALTER_TABLE_ALGORITHM_INPLACE; |
59 | else if (lex_string_eq(str, STRING_WITH_LEN("COPY" ))) |
60 | requested_algorithm= ALTER_TABLE_ALGORITHM_COPY; |
61 | else if (lex_string_eq(str, STRING_WITH_LEN("DEFAULT" ))) |
62 | requested_algorithm= ALTER_TABLE_ALGORITHM_DEFAULT; |
63 | else if (lex_string_eq(str, STRING_WITH_LEN("NOCOPY" ))) |
64 | requested_algorithm= ALTER_TABLE_ALGORITHM_NOCOPY; |
65 | else if (lex_string_eq(str, STRING_WITH_LEN("INSTANT" ))) |
66 | requested_algorithm= ALTER_TABLE_ALGORITHM_INSTANT; |
67 | else |
68 | return true; |
69 | return false; |
70 | } |
71 | |
72 | |
73 | bool Alter_info::set_requested_lock(const LEX_CSTRING *str) |
74 | { |
75 | // To avoid adding new keywords to the grammar, we match strings here. |
76 | if (lex_string_eq(str, STRING_WITH_LEN("NONE" ))) |
77 | requested_lock= ALTER_TABLE_LOCK_NONE; |
78 | else if (lex_string_eq(str, STRING_WITH_LEN("SHARED" ))) |
79 | requested_lock= ALTER_TABLE_LOCK_SHARED; |
80 | else if (lex_string_eq(str, STRING_WITH_LEN("EXCLUSIVE" ))) |
81 | requested_lock= ALTER_TABLE_LOCK_EXCLUSIVE; |
82 | else if (lex_string_eq(str, STRING_WITH_LEN("DEFAULT" ))) |
83 | requested_lock= ALTER_TABLE_LOCK_DEFAULT; |
84 | else |
85 | return true; |
86 | return false; |
87 | } |
88 | |
89 | const char* Alter_info::algorithm() const |
90 | { |
91 | switch (requested_algorithm) { |
92 | case ALTER_TABLE_ALGORITHM_INPLACE: |
93 | return "ALGORITHM=INPLACE" ; |
94 | case ALTER_TABLE_ALGORITHM_COPY: |
95 | return "ALGORITHM=COPY" ; |
96 | case ALTER_TABLE_ALGORITHM_DEFAULT: |
97 | return "ALGORITHM=DEFAULT" ; |
98 | case ALTER_TABLE_ALGORITHM_NOCOPY: |
99 | return "ALGORITHM=NOCOPY" ; |
100 | case ALTER_TABLE_ALGORITHM_INSTANT: |
101 | return "ALGORITHM=INSTANT" ; |
102 | } |
103 | |
104 | return NULL; /* purecov: begin deadcode */ |
105 | } |
106 | |
107 | const char* Alter_info::lock() const |
108 | { |
109 | switch (requested_lock) { |
110 | case ALTER_TABLE_LOCK_SHARED: |
111 | return "LOCK=SHARED" ; |
112 | case ALTER_TABLE_LOCK_NONE: |
113 | return "LOCK=NONE" ; |
114 | case ALTER_TABLE_LOCK_DEFAULT: |
115 | return "LOCK=DEFAULT" ; |
116 | case ALTER_TABLE_LOCK_EXCLUSIVE: |
117 | return "LOCK=EXCLUSIVE" ; |
118 | } |
119 | return NULL; /* purecov: begin deadcode */ |
120 | } |
121 | |
122 | |
123 | bool Alter_info::supports_algorithm(THD *thd, enum_alter_inplace_result result, |
124 | const Alter_inplace_info *ha_alter_info) |
125 | { |
126 | if (requested_algorithm == Alter_info::ALTER_TABLE_ALGORITHM_DEFAULT) |
127 | requested_algorithm = (Alter_info::enum_alter_table_algorithm) thd->variables.alter_algorithm; |
128 | |
129 | switch (result) { |
130 | case HA_ALTER_INPLACE_EXCLUSIVE_LOCK: |
131 | case HA_ALTER_INPLACE_SHARED_LOCK: |
132 | case HA_ALTER_INPLACE_NO_LOCK: |
133 | case HA_ALTER_INPLACE_INSTANT: |
134 | return false; |
135 | case HA_ALTER_INPLACE_COPY_NO_LOCK: |
136 | case HA_ALTER_INPLACE_COPY_LOCK: |
137 | if (requested_algorithm >= Alter_info::ALTER_TABLE_ALGORITHM_NOCOPY) |
138 | { |
139 | ha_alter_info->report_unsupported_error(algorithm(), |
140 | "ALGORITHM=INPLACE" ); |
141 | return true; |
142 | } |
143 | return false; |
144 | case HA_ALTER_INPLACE_NOCOPY_NO_LOCK: |
145 | case HA_ALTER_INPLACE_NOCOPY_LOCK: |
146 | if (requested_algorithm == Alter_info::ALTER_TABLE_ALGORITHM_INSTANT) |
147 | { |
148 | ha_alter_info->report_unsupported_error("ALGORITHM=INSTANT" , |
149 | "ALGORITHM=NOCOPY" ); |
150 | return true; |
151 | } |
152 | return false; |
153 | case HA_ALTER_INPLACE_NOT_SUPPORTED: |
154 | if (requested_algorithm >= Alter_info::ALTER_TABLE_ALGORITHM_INPLACE) |
155 | { |
156 | ha_alter_info->report_unsupported_error(algorithm(), |
157 | "ALGORITHM=COPY" ); |
158 | return true; |
159 | } |
160 | return false; |
161 | case HA_ALTER_ERROR: |
162 | return true; |
163 | } |
164 | /* purecov: begin deadcode */ |
165 | DBUG_ASSERT(0); |
166 | return false; |
167 | } |
168 | |
169 | |
170 | bool Alter_info::supports_lock(THD *thd, enum_alter_inplace_result result, |
171 | const Alter_inplace_info *ha_alter_info) |
172 | { |
173 | switch (result) { |
174 | case HA_ALTER_INPLACE_EXCLUSIVE_LOCK: |
175 | // If SHARED lock and no particular algorithm was requested, use COPY. |
176 | if (requested_lock == Alter_info::ALTER_TABLE_LOCK_SHARED && |
177 | requested_algorithm == Alter_info::ALTER_TABLE_ALGORITHM_DEFAULT && |
178 | thd->variables.alter_algorithm == |
179 | Alter_info::ALTER_TABLE_ALGORITHM_DEFAULT) |
180 | return false; |
181 | |
182 | if (requested_lock == Alter_info::ALTER_TABLE_LOCK_SHARED || |
183 | requested_lock == Alter_info::ALTER_TABLE_LOCK_NONE) |
184 | { |
185 | ha_alter_info->report_unsupported_error(lock(), "LOCK=EXCLUSIVE" ); |
186 | return true; |
187 | } |
188 | return false; |
189 | case HA_ALTER_INPLACE_NO_LOCK: |
190 | case HA_ALTER_INPLACE_INSTANT: |
191 | case HA_ALTER_INPLACE_COPY_NO_LOCK: |
192 | case HA_ALTER_INPLACE_NOCOPY_NO_LOCK: |
193 | return false; |
194 | case HA_ALTER_INPLACE_COPY_LOCK: |
195 | case HA_ALTER_INPLACE_NOCOPY_LOCK: |
196 | case HA_ALTER_INPLACE_NOT_SUPPORTED: |
197 | case HA_ALTER_INPLACE_SHARED_LOCK: |
198 | if (requested_lock == Alter_info::ALTER_TABLE_LOCK_NONE) |
199 | { |
200 | ha_alter_info->report_unsupported_error("LOCK=NONE" , "LOCK=SHARED" ); |
201 | return true; |
202 | } |
203 | return false; |
204 | case HA_ALTER_ERROR: |
205 | return true; |
206 | } |
207 | /* purecov: begin deadcode */ |
208 | DBUG_ASSERT(0); |
209 | return false; |
210 | } |
211 | |
212 | Alter_table_ctx::Alter_table_ctx() |
213 | : datetime_field(NULL), error_if_not_empty(false), |
214 | tables_opened(0), |
215 | db(null_clex_str), table_name(null_clex_str), alias(null_clex_str), |
216 | new_db(null_clex_str), new_name(null_clex_str), new_alias(null_clex_str), |
217 | fk_error_if_delete_row(false), fk_error_id(NULL), |
218 | fk_error_table(NULL) |
219 | #ifdef DBUG_ASSERT_EXISTS |
220 | , tmp_table(false) |
221 | #endif |
222 | { |
223 | } |
224 | |
225 | /* |
226 | TODO: new_name_arg changes if lower case table names. |
227 | Should be copied or converted before call |
228 | */ |
229 | |
230 | Alter_table_ctx::Alter_table_ctx(THD *thd, TABLE_LIST *table_list, |
231 | uint tables_opened_arg, |
232 | const LEX_CSTRING *new_db_arg, |
233 | const LEX_CSTRING *new_name_arg) |
234 | : datetime_field(NULL), error_if_not_empty(false), |
235 | tables_opened(tables_opened_arg), |
236 | new_db(*new_db_arg), new_name(*new_name_arg), |
237 | fk_error_if_delete_row(false), fk_error_id(NULL), |
238 | fk_error_table(NULL) |
239 | #ifdef DBUG_ASSERT_EXISTS |
240 | , tmp_table(false) |
241 | #endif |
242 | { |
243 | /* |
244 | Assign members db, table_name, new_db and new_name |
245 | to simplify further comparisions: we want to see if it's a RENAME |
246 | later just by comparing the pointers, avoiding the need for strcmp. |
247 | */ |
248 | db= table_list->db; |
249 | table_name= table_list->table_name; |
250 | alias= (lower_case_table_names == 2) ? table_list->alias : table_name; |
251 | |
252 | if (!new_db.str || !my_strcasecmp(table_alias_charset, new_db.str, db.str)) |
253 | new_db= db; |
254 | |
255 | if (new_name.str) |
256 | { |
257 | DBUG_PRINT("info" , ("new_db.new_name: '%s'.'%s'" , new_db.str, new_name.str)); |
258 | |
259 | if (lower_case_table_names == 1) // Convert new_name/new_alias to lower |
260 | { |
261 | new_name.length= my_casedn_str(files_charset_info, (char*) new_name.str); |
262 | new_alias= new_name; |
263 | } |
264 | else if (lower_case_table_names == 2) // Convert new_name to lower case |
265 | { |
266 | new_alias.str= new_alias_buff; |
267 | new_alias.length= new_name.length; |
268 | strmov(new_alias_buff, new_name.str); |
269 | new_name.length= my_casedn_str(files_charset_info, (char*) new_name.str); |
270 | |
271 | } |
272 | else |
273 | new_alias= new_name; // LCTN=0 => case sensitive + case preserving |
274 | |
275 | if (!is_database_changed() && |
276 | !my_strcasecmp(table_alias_charset, new_name.str, table_name.str)) |
277 | { |
278 | /* |
279 | Source and destination table names are equal: |
280 | make is_table_renamed() more efficient. |
281 | */ |
282 | new_alias= table_name; |
283 | new_name= table_name; |
284 | } |
285 | } |
286 | else |
287 | { |
288 | new_alias= alias; |
289 | new_name= table_name; |
290 | } |
291 | |
292 | tmp_name.str= tmp_name_buff; |
293 | tmp_name.length= my_snprintf(tmp_name_buff, sizeof(tmp_name_buff), "%s-%lx_%llx" , |
294 | tmp_file_prefix, current_pid, thd->thread_id); |
295 | /* Safety fix for InnoDB */ |
296 | if (lower_case_table_names) |
297 | tmp_name.length= my_casedn_str(files_charset_info, tmp_name_buff); |
298 | |
299 | if (table_list->table->s->tmp_table == NO_TMP_TABLE) |
300 | { |
301 | build_table_filename(path, sizeof(path) - 1, db.str, table_name.str, "" , 0); |
302 | |
303 | build_table_filename(new_path, sizeof(new_path) - 1, new_db.str, new_name.str, "" , 0); |
304 | |
305 | build_table_filename(new_filename, sizeof(new_filename) - 1, |
306 | new_db.str, new_name.str, reg_ext, 0); |
307 | |
308 | build_table_filename(tmp_path, sizeof(tmp_path) - 1, new_db.str, tmp_name.str, "" , |
309 | FN_IS_TMP); |
310 | } |
311 | else |
312 | { |
313 | /* |
314 | We are not filling path, new_path and new_filename members if |
315 | we are altering temporary table as these members are not used in |
316 | this case. This fact is enforced with assert. |
317 | */ |
318 | build_tmptable_filename(thd, tmp_path, sizeof(tmp_path)); |
319 | #ifdef DBUG_ASSERT_EXISTS |
320 | tmp_table= true; |
321 | #endif |
322 | } |
323 | } |
324 | |
325 | |
326 | bool Sql_cmd_alter_table::execute(THD *thd) |
327 | { |
328 | LEX *lex= thd->lex; |
329 | /* first SELECT_LEX (have special meaning for many of non-SELECTcommands) */ |
330 | SELECT_LEX *select_lex= &lex->select_lex; |
331 | /* first table of first SELECT_LEX */ |
332 | TABLE_LIST *first_table= (TABLE_LIST*) select_lex->table_list.first; |
333 | /* |
334 | Code in mysql_alter_table() may modify its HA_CREATE_INFO argument, |
335 | so we have to use a copy of this structure to make execution |
336 | prepared statement- safe. A shallow copy is enough as no memory |
337 | referenced from this structure will be modified. |
338 | @todo move these into constructor... |
339 | */ |
340 | HA_CREATE_INFO create_info(lex->create_info); |
341 | Alter_info alter_info(lex->alter_info, thd->mem_root); |
342 | ulong priv=0; |
343 | ulong priv_needed= ALTER_ACL; |
344 | bool result; |
345 | |
346 | DBUG_ENTER("Sql_cmd_alter_table::execute" ); |
347 | |
348 | if (unlikely(thd->is_fatal_error)) |
349 | { |
350 | /* out of memory creating a copy of alter_info */ |
351 | DBUG_RETURN(TRUE); |
352 | } |
353 | /* |
354 | We also require DROP priv for ALTER TABLE ... DROP PARTITION, as well |
355 | as for RENAME TO, as being done by SQLCOM_RENAME_TABLE |
356 | */ |
357 | if ((alter_info.partition_flags & ALTER_PARTITION_DROP) || |
358 | (alter_info.flags & ALTER_RENAME)) |
359 | priv_needed|= DROP_ACL; |
360 | |
361 | /* Must be set in the parser */ |
362 | DBUG_ASSERT(select_lex->db.str); |
363 | DBUG_ASSERT(!(alter_info.partition_flags & ALTER_PARTITION_EXCHANGE)); |
364 | DBUG_ASSERT(!(alter_info.partition_flags & ALTER_PARTITION_ADMIN)); |
365 | if (check_access(thd, priv_needed, first_table->db.str, |
366 | &first_table->grant.privilege, |
367 | &first_table->grant.m_internal, |
368 | 0, 0) || |
369 | check_access(thd, INSERT_ACL | CREATE_ACL, select_lex->db.str, |
370 | &priv, |
371 | NULL, /* Don't use first_tab->grant with sel_lex->db */ |
372 | 0, 0)) |
373 | DBUG_RETURN(TRUE); /* purecov: inspected */ |
374 | |
375 | /* If it is a merge table, check privileges for merge children. */ |
376 | if (create_info.merge_list.first) |
377 | { |
378 | /* |
379 | The user must have (SELECT_ACL | UPDATE_ACL | DELETE_ACL) on the |
380 | underlying base tables, even if there are temporary tables with the same |
381 | names. |
382 | |
383 | From user's point of view, it might look as if the user must have these |
384 | privileges on temporary tables to create a merge table over them. This is |
385 | one of two cases when a set of privileges is required for operations on |
386 | temporary tables (see also CREATE TABLE). |
387 | |
388 | The reason for this behavior stems from the following facts: |
389 | |
390 | - For merge tables, the underlying table privileges are checked only |
391 | at CREATE TABLE / ALTER TABLE time. |
392 | |
393 | In other words, once a merge table is created, the privileges of |
394 | the underlying tables can be revoked, but the user will still have |
395 | access to the merge table (provided that the user has privileges on |
396 | the merge table itself). |
397 | |
398 | - Temporary tables shadow base tables. |
399 | |
400 | I.e. there might be temporary and base tables with the same name, and |
401 | the temporary table takes the precedence in all operations. |
402 | |
403 | - For temporary MERGE tables we do not track if their child tables are |
404 | base or temporary. As result we can't guarantee that privilege check |
405 | which was done in presence of temporary child will stay relevant later |
406 | as this temporary table might be removed. |
407 | |
408 | If SELECT_ACL | UPDATE_ACL | DELETE_ACL privileges were not checked for |
409 | the underlying *base* tables, it would create a security breach as in |
410 | Bug#12771903. |
411 | */ |
412 | |
413 | if (check_table_access(thd, SELECT_ACL | UPDATE_ACL | DELETE_ACL, |
414 | create_info.merge_list.first, FALSE, UINT_MAX, FALSE)) |
415 | DBUG_RETURN(TRUE); |
416 | } |
417 | |
418 | if (check_grant(thd, priv_needed, first_table, FALSE, UINT_MAX, FALSE)) |
419 | DBUG_RETURN(TRUE); /* purecov: inspected */ |
420 | |
421 | if (lex->name.str && !test_all_bits(priv, INSERT_ACL | CREATE_ACL)) |
422 | { |
423 | // Rename of table |
424 | TABLE_LIST tmp_table; |
425 | memset(&tmp_table, 0, sizeof(tmp_table)); |
426 | tmp_table.table_name= lex->name; |
427 | tmp_table.db= select_lex->db; |
428 | tmp_table.grant.privilege= priv; |
429 | if (check_grant(thd, INSERT_ACL | CREATE_ACL, &tmp_table, FALSE, |
430 | UINT_MAX, FALSE)) |
431 | DBUG_RETURN(TRUE); /* purecov: inspected */ |
432 | } |
433 | |
434 | /* Don't yet allow changing of symlinks with ALTER TABLE */ |
435 | if (create_info.data_file_name) |
436 | push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, |
437 | WARN_OPTION_IGNORED, ER_THD(thd, WARN_OPTION_IGNORED), |
438 | "DATA DIRECTORY" ); |
439 | if (create_info.index_file_name) |
440 | push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, |
441 | WARN_OPTION_IGNORED, ER_THD(thd, WARN_OPTION_IGNORED), |
442 | "INDEX DIRECTORY" ); |
443 | create_info.data_file_name= create_info.index_file_name= NULL; |
444 | |
445 | thd->prepare_logs_for_admin_command(); |
446 | |
447 | #ifdef WITH_WSREP |
448 | if ((!thd->is_current_stmt_binlog_format_row() || |
449 | !thd->find_temporary_table(first_table))) |
450 | { |
451 | WSREP_TO_ISOLATION_BEGIN(((lex->name.str) ? select_lex->db.str : NULL), |
452 | ((lex->name.str) ? lex->name.str : NULL), |
453 | first_table); |
454 | } |
455 | #endif /* WITH_WSREP */ |
456 | |
457 | result= mysql_alter_table(thd, &select_lex->db, &lex->name, |
458 | &create_info, |
459 | first_table, |
460 | &alter_info, |
461 | select_lex->order_list.elements, |
462 | select_lex->order_list.first, |
463 | lex->ignore); |
464 | |
465 | DBUG_RETURN(result); |
466 | |
467 | #ifdef WITH_WSREP |
468 | error: |
469 | WSREP_WARN("ALTER TABLE isolation failure" ); |
470 | DBUG_RETURN(TRUE); |
471 | #endif /* WITH_WSREP */ |
472 | } |
473 | |
474 | bool Sql_cmd_discard_import_tablespace::execute(THD *thd) |
475 | { |
476 | /* first SELECT_LEX (have special meaning for many of non-SELECTcommands) */ |
477 | SELECT_LEX *select_lex= &thd->lex->select_lex; |
478 | /* first table of first SELECT_LEX */ |
479 | TABLE_LIST *table_list= (TABLE_LIST*) select_lex->table_list.first; |
480 | |
481 | if (check_access(thd, ALTER_ACL, table_list->db.str, |
482 | &table_list->grant.privilege, |
483 | &table_list->grant.m_internal, |
484 | 0, 0)) |
485 | return true; |
486 | |
487 | if (check_grant(thd, ALTER_ACL, table_list, false, UINT_MAX, false)) |
488 | return true; |
489 | |
490 | thd->prepare_logs_for_admin_command(); |
491 | |
492 | /* |
493 | Check if we attempt to alter mysql.slow_log or |
494 | mysql.general_log table and return an error if |
495 | it is the case. |
496 | TODO: this design is obsolete and will be removed. |
497 | */ |
498 | if (check_if_log_table(table_list, TRUE, "ALTER" )) |
499 | return true; |
500 | |
501 | return |
502 | mysql_discard_or_import_tablespace(thd, table_list, |
503 | m_tablespace_op == DISCARD_TABLESPACE); |
504 | } |
505 | |