1 /* Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved.
2
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License, version 2.0,
5 as published by the Free Software Foundation.
6
7 This program is also distributed with certain software (including
8 but not limited to OpenSSL) that is licensed under separate terms,
9 as designated in a particular file or component or in included license
10 documentation. The authors of MySQL hereby grant you an additional
11 permission to link the program and your derivative works with the
12 separately licensed software that they have included with MySQL.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License, version 2.0, for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
22
23 #include "sql_parse.h" // check_access
24 #include "sql_table.h" // mysql_alter_table,
25 // mysql_exchange_partition
26 #include "sql_base.h" // open_temporary_tables
27 #include "sql_alter.h"
28
29 bool has_external_data_or_index_dir(partition_info &pi);
30
Alter_info(const Alter_info & rhs,MEM_ROOT * mem_root)31 Alter_info::Alter_info(const Alter_info &rhs, MEM_ROOT *mem_root)
32 :drop_list(rhs.drop_list, mem_root),
33 alter_list(rhs.alter_list, mem_root),
34 key_list(rhs.key_list, mem_root),
35 create_list(rhs.create_list, mem_root),
36 flags(rhs.flags),
37 keys_onoff(rhs.keys_onoff),
38 partition_names(rhs.partition_names, mem_root),
39 num_parts(rhs.num_parts),
40 requested_algorithm(rhs.requested_algorithm),
41 requested_lock(rhs.requested_lock)
42 {
43 /*
44 Make deep copies of used objects.
45 This is not a fully deep copy - clone() implementations
46 of Alter_drop, Alter_column, Key, foreign_key, Key_part_spec
47 do not copy string constants. At the same length the only
48 reason we make a copy currently is that ALTER/CREATE TABLE
49 code changes input Alter_info definitions, but string
50 constants never change.
51 */
52 list_copy_and_replace_each_value(drop_list, mem_root);
53 list_copy_and_replace_each_value(alter_list, mem_root);
54 list_copy_and_replace_each_value(key_list, mem_root);
55 list_copy_and_replace_each_value(create_list, mem_root);
56 /* partition_names are not deeply copied currently */
57 }
58
59
set_requested_algorithm(const LEX_STRING * str)60 bool Alter_info::set_requested_algorithm(const LEX_STRING *str)
61 {
62 // To avoid adding new keywords to the grammar, we match strings here.
63 if (!my_strcasecmp(system_charset_info, str->str, "INPLACE"))
64 requested_algorithm= ALTER_TABLE_ALGORITHM_INPLACE;
65 else if (!my_strcasecmp(system_charset_info, str->str, "COPY"))
66 requested_algorithm= ALTER_TABLE_ALGORITHM_COPY;
67 else if (!my_strcasecmp(system_charset_info, str->str, "DEFAULT"))
68 requested_algorithm= ALTER_TABLE_ALGORITHM_DEFAULT;
69 else
70 return true;
71 return false;
72 }
73
74
set_requested_lock(const LEX_STRING * str)75 bool Alter_info::set_requested_lock(const LEX_STRING *str)
76 {
77 // To avoid adding new keywords to the grammar, we match strings here.
78 if (!my_strcasecmp(system_charset_info, str->str, "NONE"))
79 requested_lock= ALTER_TABLE_LOCK_NONE;
80 else if (!my_strcasecmp(system_charset_info, str->str, "SHARED"))
81 requested_lock= ALTER_TABLE_LOCK_SHARED;
82 else if (!my_strcasecmp(system_charset_info, str->str, "EXCLUSIVE"))
83 requested_lock= ALTER_TABLE_LOCK_EXCLUSIVE;
84 else if (!my_strcasecmp(system_charset_info, str->str, "DEFAULT"))
85 requested_lock= ALTER_TABLE_LOCK_DEFAULT;
86 else
87 return true;
88 return false;
89 }
90
91
Alter_table_ctx()92 Alter_table_ctx::Alter_table_ctx()
93 : datetime_field(NULL), error_if_not_empty(false),
94 tables_opened(0),
95 db(NULL), table_name(NULL), alias(NULL),
96 new_db(NULL), new_name(NULL), new_alias(NULL),
97 fk_error_if_delete_row(false), fk_error_id(NULL),
98 fk_error_table(NULL)
99 #ifndef DBUG_OFF
100 , tmp_table(false)
101 #endif
102 {
103 }
104
105
Alter_table_ctx(THD * thd,TABLE_LIST * table_list,uint tables_opened_arg,char * new_db_arg,char * new_name_arg)106 Alter_table_ctx::Alter_table_ctx(THD *thd, TABLE_LIST *table_list,
107 uint tables_opened_arg,
108 char *new_db_arg, char *new_name_arg)
109 : datetime_field(NULL), error_if_not_empty(false),
110 tables_opened(tables_opened_arg),
111 new_db(new_db_arg), new_name(new_name_arg),
112 fk_error_if_delete_row(false), fk_error_id(NULL),
113 fk_error_table(NULL)
114 #ifndef DBUG_OFF
115 , tmp_table(false)
116 #endif
117 {
118 /*
119 Assign members db, table_name, new_db and new_name
120 to simplify further comparisions: we want to see if it's a RENAME
121 later just by comparing the pointers, avoiding the need for strcmp.
122 */
123 db= table_list->db;
124 table_name= table_list->table_name;
125 alias= (lower_case_table_names == 2) ? table_list->alias : table_name;
126
127 if (!new_db || !my_strcasecmp(table_alias_charset, new_db, db))
128 new_db= db;
129
130 if (new_name)
131 {
132 DBUG_PRINT("info", ("new_db.new_name: '%s'.'%s'", new_db, new_name));
133
134 if (lower_case_table_names == 1) // Convert new_name/new_alias to lower case
135 {
136 my_casedn_str(files_charset_info, new_name);
137 new_alias= new_name;
138 }
139 else if (lower_case_table_names == 2) // Convert new_name to lower case
140 {
141 strmov(new_alias= new_alias_buff, new_name);
142 my_casedn_str(files_charset_info, new_name);
143 }
144 else
145 new_alias= new_name; // LCTN=0 => case sensitive + case preserving
146
147 if (!is_database_changed() &&
148 !my_strcasecmp(table_alias_charset, new_name, table_name))
149 {
150 /*
151 Source and destination table names are equal:
152 make is_table_renamed() more efficient.
153 */
154 new_alias= table_name;
155 new_name= table_name;
156 }
157 }
158 else
159 {
160 new_alias= alias;
161 new_name= table_name;
162 }
163
164 my_snprintf(tmp_name, sizeof(tmp_name), "%s-%lx_%lx", tmp_file_prefix,
165 current_pid, thd->thread_id);
166 /* Safety fix for InnoDB */
167 if (lower_case_table_names)
168 my_casedn_str(files_charset_info, tmp_name);
169
170 if (table_list->table->s->tmp_table == NO_TMP_TABLE)
171 {
172 build_table_filename(path, sizeof(path) - 1, db, table_name, "", 0);
173
174 build_table_filename(new_path, sizeof(new_path) - 1, new_db, new_name, "", 0);
175
176 build_table_filename(new_filename, sizeof(new_filename) - 1,
177 new_db, new_name, reg_ext, 0);
178
179 build_table_filename(tmp_path, sizeof(tmp_path) - 1, new_db, tmp_name, "",
180 FN_IS_TMP);
181 }
182 else
183 {
184 /*
185 We are not filling path, new_path and new_filename members if
186 we are altering temporary table as these members are not used in
187 this case. This fact is enforced with assert.
188 */
189 build_tmptable_filename(thd, tmp_path, sizeof(tmp_path));
190 #ifndef DBUG_OFF
191 tmp_table= true;
192 #endif
193 }
194 }
195
196
execute(THD * thd)197 bool Sql_cmd_alter_table::execute(THD *thd)
198 {
199 LEX *lex= thd->lex;
200 /* first SELECT_LEX (have special meaning for many of non-SELECTcommands) */
201 SELECT_LEX *select_lex= &lex->select_lex;
202 /* first table of first SELECT_LEX */
203 TABLE_LIST *first_table= (TABLE_LIST*) select_lex->table_list.first;
204 /*
205 Code in mysql_alter_table() may modify its HA_CREATE_INFO argument,
206 so we have to use a copy of this structure to make execution
207 prepared statement- safe. A shallow copy is enough as no memory
208 referenced from this structure will be modified.
209 @todo move these into constructor...
210 */
211 HA_CREATE_INFO create_info(lex->create_info);
212 Alter_info alter_info(lex->alter_info, thd->mem_root);
213 ulong priv=0;
214 ulong priv_needed= ALTER_ACL;
215 bool result;
216
217 DBUG_ENTER("Sql_cmd_alter_table::execute");
218
219 if (thd->is_fatal_error) /* out of memory creating a copy of alter_info */
220 DBUG_RETURN(TRUE);
221
222 #ifdef WITH_PARTITION_STORAGE_ENGINE
223 {
224 partition_info *part_info= thd->lex->part_info;
225 if (part_info != NULL && has_external_data_or_index_dir(*part_info) &&
226 check_access(thd, FILE_ACL, any_db, NULL, NULL, FALSE, FALSE))
227
228 DBUG_RETURN(TRUE);
229 }
230 #endif
231 /*
232 We also require DROP priv for ALTER TABLE ... DROP PARTITION, as well
233 as for RENAME TO, as being done by SQLCOM_RENAME_TABLE
234 */
235 if (alter_info.flags & (Alter_info::ALTER_DROP_PARTITION |
236 Alter_info::ALTER_RENAME))
237 priv_needed|= DROP_ACL;
238
239 /* Must be set in the parser */
240 DBUG_ASSERT(select_lex->db);
241 DBUG_ASSERT(!(alter_info.flags & Alter_info::ALTER_EXCHANGE_PARTITION));
242 DBUG_ASSERT(!(alter_info.flags & Alter_info::ALTER_ADMIN_PARTITION));
243 if (check_access(thd, priv_needed, first_table->db,
244 &first_table->grant.privilege,
245 &first_table->grant.m_internal,
246 0, 0) ||
247 check_access(thd, INSERT_ACL | CREATE_ACL, select_lex->db,
248 &priv,
249 NULL, /* Don't use first_tab->grant with sel_lex->db */
250 0, 0))
251 DBUG_RETURN(TRUE); /* purecov: inspected */
252
253 /* If it is a merge table, check privileges for merge children. */
254 if (create_info.merge_list.first)
255 {
256 /*
257 The user must have (SELECT_ACL | UPDATE_ACL | DELETE_ACL) on the
258 underlying base tables, even if there are temporary tables with the same
259 names.
260
261 From user's point of view, it might look as if the user must have these
262 privileges on temporary tables to create a merge table over them. This is
263 one of two cases when a set of privileges is required for operations on
264 temporary tables (see also CREATE TABLE).
265
266 The reason for this behavior stems from the following facts:
267
268 - For merge tables, the underlying table privileges are checked only
269 at CREATE TABLE / ALTER TABLE time.
270
271 In other words, once a merge table is created, the privileges of
272 the underlying tables can be revoked, but the user will still have
273 access to the merge table (provided that the user has privileges on
274 the merge table itself).
275
276 - Temporary tables shadow base tables.
277
278 I.e. there might be temporary and base tables with the same name, and
279 the temporary table takes the precedence in all operations.
280
281 - For temporary MERGE tables we do not track if their child tables are
282 base or temporary. As result we can't guarantee that privilege check
283 which was done in presence of temporary child will stay relevant later
284 as this temporary table might be removed.
285
286 If SELECT_ACL | UPDATE_ACL | DELETE_ACL privileges were not checked for
287 the underlying *base* tables, it would create a security breach as in
288 Bug#12771903.
289 */
290
291 if (check_table_access(thd, SELECT_ACL | UPDATE_ACL | DELETE_ACL,
292 create_info.merge_list.first, FALSE, UINT_MAX, FALSE))
293 DBUG_RETURN(TRUE);
294 }
295
296 if (check_grant(thd, priv_needed, first_table, FALSE, UINT_MAX, FALSE))
297 DBUG_RETURN(TRUE); /* purecov: inspected */
298
299 if (lex->name.str && !test_all_bits(priv, INSERT_ACL | CREATE_ACL))
300 {
301 // Rename of table
302 TABLE_LIST tmp_table;
303 memset(&tmp_table, 0, sizeof(tmp_table));
304 tmp_table.table_name= lex->name.str;
305 tmp_table.db= select_lex->db;
306 tmp_table.grant.privilege= priv;
307 if (check_grant(thd, INSERT_ACL | CREATE_ACL, &tmp_table, FALSE,
308 UINT_MAX, FALSE))
309 DBUG_RETURN(TRUE); /* purecov: inspected */
310 }
311
312 /* Don't yet allow changing of symlinks with ALTER TABLE */
313 if (create_info.data_file_name)
314 push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
315 WARN_OPTION_IGNORED, ER(WARN_OPTION_IGNORED),
316 "DATA DIRECTORY");
317 if (create_info.index_file_name)
318 push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
319 WARN_OPTION_IGNORED, ER(WARN_OPTION_IGNORED),
320 "INDEX DIRECTORY");
321 create_info.data_file_name= create_info.index_file_name= NULL;
322
323 thd->enable_slow_log= opt_log_slow_admin_statements;
324
325 result= mysql_alter_table(thd, select_lex->db, lex->name.str,
326 &create_info,
327 first_table,
328 &alter_info,
329 select_lex->order_list.elements,
330 select_lex->order_list.first,
331 lex->ignore);
332
333 DBUG_RETURN(result);
334 }
335
336
execute(THD * thd)337 bool Sql_cmd_discard_import_tablespace::execute(THD *thd)
338 {
339 /* first SELECT_LEX (have special meaning for many of non-SELECTcommands) */
340 SELECT_LEX *select_lex= &thd->lex->select_lex;
341 /* first table of first SELECT_LEX */
342 TABLE_LIST *table_list= (TABLE_LIST*) select_lex->table_list.first;
343
344 if (check_access(thd, ALTER_ACL, table_list->db,
345 &table_list->grant.privilege,
346 &table_list->grant.m_internal,
347 0, 0))
348 return true;
349
350 if (check_grant(thd, ALTER_ACL, table_list, false, UINT_MAX, false))
351 return true;
352
353 thd->enable_slow_log= opt_log_slow_admin_statements;
354
355 /*
356 Check if we attempt to alter mysql.slow_log or
357 mysql.general_log table and return an error if
358 it is the case.
359 TODO: this design is obsolete and will be removed.
360 */
361 int table_kind= check_if_log_table(table_list->db_length, table_list->db,
362 table_list->table_name_length,
363 table_list->table_name, false);
364
365 if (table_kind)
366 {
367 /* Disable alter of enabled log tables */
368 if (logger.is_log_table_enabled(table_kind))
369 {
370 my_error(ER_BAD_LOG_STATEMENT, MYF(0), "ALTER");
371 return true;
372 }
373 }
374
375 /*
376 Add current database to the list of accessed databases
377 for this statement. Needed for MTS.
378 */
379 thd->add_to_binlog_accessed_dbs(table_list->db);
380
381 return
382 mysql_discard_or_import_tablespace(thd, table_list,
383 m_tablespace_op == DISCARD_TABLESPACE);
384 }
385