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