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