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
24Alter_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
54bool 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
73bool 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
89const 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
107const 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
123bool 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
170bool 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
212Alter_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
230Alter_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
326bool 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
468error:
469 WSREP_WARN("ALTER TABLE isolation failure");
470 DBUG_RETURN(TRUE);
471#endif /* WITH_WSREP */
472}
473
474bool 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