MySQL 5.6.14 Source Code Document
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
sql_truncate.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 "debug_sync.h" // DEBUG_SYNC
17 #include "table.h" // TABLE, FOREIGN_KEY_INFO
18 #include "sql_class.h" // THD
19 #include "sql_base.h" // open_and_lock_tables
20 #include "sql_table.h" // write_bin_log
21 #include "datadict.h" // dd_recreate_table()
22 #include "lock.h" // MYSQL_OPEN_* flags
23 #include "sql_acl.h" // DROP_ACL
24 #include "sql_parse.h" // check_one_table_access()
25 #include "sql_truncate.h"
26 #include "sql_show.h" //append_identifier()
27 
28 
38 static bool fk_info_append_fields(String *str, List<LEX_STRING> *fields)
39 {
40  bool res= FALSE;
41  LEX_STRING *field;
43 
44  while ((field= it++))
45  {
46  append_identifier(NULL, str, field->str, field->length);
47  res|= str->append(", ");
48  }
49 
50  str->chop();
51  str->chop();
52 
53  return res;
54 }
55 
56 
66 static const char *fk_info_str(THD *thd, FOREIGN_KEY_INFO *fk_info)
67 {
68  bool res= FALSE;
69  char buffer[STRING_BUFFER_USUAL_SIZE*2];
70  String str(buffer, sizeof(buffer), system_charset_info);
71 
72  str.length(0);
73 
74  /*
75  `db`.`tbl`, CONSTRAINT `id` FOREIGN KEY (`fk`) REFERENCES `db`.`tbl` (`fk`)
76  */
77 
78  append_identifier(NULL, &str, fk_info->foreign_db->str,
79  fk_info->foreign_db->length);
80  res|= str.append(".");
81  append_identifier(NULL, &str, fk_info->foreign_table->str,
82  fk_info->foreign_table->length);
83  res|= str.append(", CONSTRAINT ");
84  append_identifier(NULL, &str, fk_info->foreign_id->str,
85  fk_info->foreign_id->length);
86  res|= str.append(" FOREIGN KEY (");
87  res|= fk_info_append_fields(&str, &fk_info->foreign_fields);
88  res|= str.append(") REFERENCES ");
89  append_identifier(NULL, &str, fk_info->referenced_db->str,
90  fk_info->referenced_db->length);
91  res|= str.append(".");
92  append_identifier(NULL, &str, fk_info->referenced_table->str,
93  fk_info->referenced_table->length);
94  res|= str.append(" (");
95  res|= fk_info_append_fields(&str, &fk_info->referenced_fields);
96  res|= str.append(')');
97 
98  return res ? NULL : thd->strmake(str.ptr(), str.length());
99 }
100 
101 
119 static bool
120 fk_truncate_illegal_if_parent(THD *thd, TABLE *table)
121 {
122  FOREIGN_KEY_INFO *fk_info;
123  List<FOREIGN_KEY_INFO> fk_list;
125 
126  /*
127  Bail out early if the table is not referenced by a foreign key.
128  In this case, the table could only be, if at all, a child table.
129  */
130  if (! table->file->referenced_by_foreign_key())
131  return FALSE;
132 
133  /*
134  This table _is_ referenced by a foreign key. At this point, only
135  self-referencing keys are acceptable. For this reason, get the list
136  of foreign keys referencing this table in order to check the name
137  of the child (dependent) tables.
138  */
139  table->file->get_parent_foreign_key_list(thd, &fk_list);
140 
141  /* Out of memory when building list. */
142  if (thd->is_error())
143  return TRUE;
144 
145  it.init(fk_list);
146 
147  /* Loop over the set of foreign keys for which this table is a parent. */
148  while ((fk_info= it++))
149  {
150  DBUG_ASSERT(!my_strcasecmp(system_charset_info,
151  fk_info->referenced_db->str,
152  table->s->db.str));
153 
154  DBUG_ASSERT(!my_strcasecmp(system_charset_info,
155  fk_info->referenced_table->str,
156  table->s->table_name.str));
157 
158  if (my_strcasecmp(system_charset_info, fk_info->foreign_db->str,
159  table->s->db.str) ||
160  my_strcasecmp(system_charset_info, fk_info->foreign_table->str,
161  table->s->table_name.str))
162  break;
163  }
164 
165  /* Table is parent in a non-self-referencing foreign key. */
166  if (fk_info)
167  {
168  my_error(ER_TRUNCATE_ILLEGAL_FK, MYF(0), fk_info_str(thd, fk_info));
169  return TRUE;
170  }
171 
172  return FALSE;
173 }
174 
175 
176 /*
177  Open and truncate a locked table.
178 
179  @param thd Thread context.
180  @param table_ref Table list element for the table to be truncated.
181  @param is_tmp_table True if element refers to a temp table.
182 
183  @retval 0 Success.
184  @retval > 0 Error code.
185 */
186 
188  bool is_tmp_table)
189 {
190  int error= 0;
191  uint flags= 0;
192  DBUG_ENTER("Sql_cmd_truncate_table::handler_truncate");
193 
194  /*
195  Can't recreate, the engine must mechanically delete all rows
196  in the table. Use open_and_lock_tables() to open a write cursor.
197  */
198 
199  /* If it is a temporary table, no need to take locks. */
200  if (!is_tmp_table)
201  {
202  /* We don't need to load triggers. */
203  DBUG_ASSERT(table_ref->trg_event_map == 0);
204  /*
205  Our metadata lock guarantees that no transaction is reading
206  or writing into the table. Yet, to open a write cursor we need
207  a thr_lock lock. Allow to open base tables only.
208  */
209  table_ref->required_type= FRMTYPE_TABLE;
210  /*
211  Ignore pending FLUSH TABLES since we don't want to release
212  the MDL lock taken above and otherwise there is no way to
213  wait for FLUSH TABLES in deadlock-free fashion.
214  */
215  flags= MYSQL_OPEN_IGNORE_FLUSH;
216  /*
217  Even though we have an MDL lock on the table here, we don't
218  pass MYSQL_OPEN_HAS_MDL_LOCK to open_and_lock_tables
219  since to truncate a MERGE table, we must open and lock
220  merge children, and on those we don't have an MDL lock.
221  Thus clear the ticket to satisfy MDL asserts.
222  */
223  table_ref->mdl_request.ticket= NULL;
224  }
225 
226  /* Open the table as it will handle some required preparations. */
227  if (open_and_lock_tables(thd, table_ref, FALSE, flags))
228  DBUG_RETURN(1);
229 
230  /* Whether to truncate regardless of foreign keys. */
231  if (! (thd->variables.option_bits & OPTION_NO_FOREIGN_KEY_CHECKS))
232  error= fk_truncate_illegal_if_parent(thd, table_ref->table);
233 
234  if (!error && (error= table_ref->table->file->ha_truncate()))
235  table_ref->table->file->print_error(error, MYF(0));
236 
237  DBUG_RETURN(error);
238 }
239 
240 
241 /*
242  Close and recreate a temporary table. In case of success,
243  write truncate statement into the binary log if in statement
244  mode.
245 
246  @param thd Thread context.
247  @param table The temporary table.
248 
249  @retval FALSE Success.
250  @retval TRUE Error.
251 */
252 
253 static bool recreate_temporary_table(THD *thd, TABLE *table)
254 {
255  bool error= TRUE;
256  TABLE_SHARE *share= table->s;
257  HA_CREATE_INFO create_info;
258  handlerton *table_type= table->s->db_type();
259  DBUG_ENTER("recreate_temporary_table");
260 
261  memset(&create_info, 0, sizeof(create_info));
262 
263  table->file->info(HA_STATUS_AUTO | HA_STATUS_NO_LOCK);
264 
265  /* Don't free share. */
266  close_temporary_table(thd, table, FALSE, FALSE);
267 
268  /*
269  We must use share->normalized_path.str since for temporary tables it
270  differs from what dd_recreate_table() would generate based
271  on table and schema names.
272  */
273  ha_create_table(thd, share->normalized_path.str, share->db.str,
274  share->table_name.str, &create_info, true, true);
275 
276  if (open_table_uncached(thd, share->path.str, share->db.str,
277  share->table_name.str, true, true))
278  {
279  error= FALSE;
280  thd->thread_specific_used= TRUE;
281  }
282  else
283  rm_temporary_table(table_type, share->path.str);
284 
285  free_table_share(share);
286  my_free(table);
287 
288  DBUG_RETURN(error);
289 }
290 
291 
292 /*
293  Handle locking a base table for truncate.
294 
295  @param[in] thd Thread context.
296  @param[in] table_ref Table list element for the table to
297  be truncated.
298  @param[out] hton_can_recreate Set to TRUE if table can be dropped
299  and recreated.
300 
301  @retval FALSE Success.
302  @retval TRUE Error.
303 */
304 
306  bool *hton_can_recreate)
307 {
308  TABLE *table= NULL;
309  DBUG_ENTER("Sql_cmd_truncate_table::lock_table");
310 
311  /* Lock types are set in the parser. */
312  DBUG_ASSERT(table_ref->lock_type == TL_WRITE);
313  /* The handler truncate protocol dictates a exclusive lock. */
314  DBUG_ASSERT(table_ref->mdl_request.type == MDL_EXCLUSIVE);
315 
316  /*
317  Before doing anything else, acquire a metadata lock on the table,
318  or ensure we have one. We don't use open_and_lock_tables()
319  right away because we want to be able to truncate (and recreate)
320  corrupted tables, those that we can't fully open.
321 
322  MySQL manual documents that TRUNCATE can be used to repair a
323  damaged table, i.e. a table that can not be fully "opened".
324  In particular MySQL manual says: As long as the table format
325  file tbl_name.frm is valid, the table can be re-created as
326  an empty table with TRUNCATE TABLE, even if the data or index
327  files have become corrupted.
328  */
329  if (thd->locked_tables_mode)
330  {
331  if (!(table= find_table_for_mdl_upgrade(thd, table_ref->db,
332  table_ref->table_name, FALSE)))
333  DBUG_RETURN(TRUE);
334 
335  *hton_can_recreate= ha_check_storage_engine_flag(table->s->db_type(),
336  HTON_CAN_RECREATE);
337  table_ref->mdl_request.ticket= table->mdl_ticket;
338  }
339  else
340  {
341  /* Acquire an exclusive lock. */
342  DBUG_ASSERT(table_ref->next_global == NULL);
343  if (lock_table_names(thd, table_ref, NULL,
344  thd->variables.lock_wait_timeout, 0))
345  DBUG_RETURN(TRUE);
346 
347  if (dd_check_storage_engine_flag(thd, table_ref->db, table_ref->table_name,
348  HTON_CAN_RECREATE, hton_can_recreate))
349  DBUG_RETURN(TRUE);
350  }
351 
352  /*
353  A storage engine can recreate or truncate the table only if there
354  are no references to it from anywhere, i.e. no cached TABLE in the
355  table cache.
356  */
357  if (thd->locked_tables_mode)
358  {
359  DEBUG_SYNC(thd, "upgrade_lock_for_truncate");
360  /* To remove the table from the cache we need an exclusive lock. */
361  if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
362  DBUG_RETURN(TRUE);
363  m_ticket_downgrade= table->mdl_ticket;
364  /* Close if table is going to be recreated. */
365  if (*hton_can_recreate)
366  close_all_tables_for_name(thd, table->s, false, NULL);
367  }
368  else
369  {
370  /* Table is already locked exclusively. Remove cached instances. */
371  tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table_ref->db,
372  table_ref->table_name, FALSE);
373  }
374 
375  DBUG_RETURN(FALSE);
376 }
377 
378 
379 /*
380  Optimized delete of all rows by doing a full generate of the table.
381 
382  @remark Will work even if the .MYI and .MYD files are destroyed.
383  In other words, it works as long as the .FRM is intact and
384  the engine supports re-create.
385 
386  @param thd Thread context.
387  @param table_ref Table list element for the table to be truncated.
388 
389  @retval FALSE Success.
390  @retval TRUE Error.
391 */
392 
394 {
395  int error;
396  bool binlog_stmt;
397  DBUG_ENTER("Sql_cmd_truncate_table::truncate_table");
398 
399  DBUG_ASSERT((!table_ref->table) ||
400  (table_ref->table && table_ref->table->s));
401 
402  /* Initialize, or reinitialize in case of reexecution (SP). */
403  m_ticket_downgrade= NULL;
404 
405  /* If it is a temporary table, no need to take locks. */
406  if (is_temporary_table(table_ref))
407  {
408  TABLE *tmp_table= table_ref->table;
409 
410  /* In RBR, the statement is not binlogged if the table is temporary. */
411  binlog_stmt= !thd->is_current_stmt_binlog_format_row();
412 
413  /* Note that a temporary table cannot be partitioned. */
414  if (ha_check_storage_engine_flag(tmp_table->s->db_type(), HTON_CAN_RECREATE))
415  {
416  if ((error= recreate_temporary_table(thd, tmp_table)))
417  binlog_stmt= FALSE; /* No need to binlog failed truncate-by-recreate. */
418 
419  DBUG_ASSERT(! thd->transaction.stmt.cannot_safely_rollback());
420  }
421  else
422  {
423  /*
424  The engine does not support truncate-by-recreate. Open the
425  table and invoke the handler truncate. In such a manner this
426  can in fact open several tables if it's a temporary MyISAMMRG
427  table.
428  */
429  error= handler_truncate(thd, table_ref, TRUE);
430  }
431 
432  /*
433  No need to invalidate the query cache, queries with temporary
434  tables are not in the cache. No need to write to the binary
435  log a failed row-by-row delete even if under RBR as the table
436  might not exist on the slave.
437  */
438  }
439  else /* It's not a temporary table. */
440  {
441  bool hton_can_recreate;
442 
443  if (lock_table(thd, table_ref, &hton_can_recreate))
444  DBUG_RETURN(TRUE);
445 
446  if (hton_can_recreate)
447  {
448  /*
449  The storage engine can truncate the table by creating an
450  empty table with the same structure.
451  */
452  error= dd_recreate_table(thd, table_ref->db, table_ref->table_name);
453 
454  if (thd->locked_tables_mode && thd->locked_tables_list.reopen_tables(thd))
455  thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0);
456 
457  /* No need to binlog a failed truncate-by-recreate. */
458  binlog_stmt= !error;
459  }
460  else
461  {
462  /*
463  The engine does not support truncate-by-recreate.
464  Attempt to use the handler truncate method.
465  */
466  error= handler_truncate(thd, table_ref, FALSE);
467 
468  /*
469  All effects of a TRUNCATE TABLE operation are committed even if
470  truncation fails. Thus, the query must be written to the binary
471  log. The only exception is a unimplemented truncate method.
472  */
473  binlog_stmt= !error || error != HA_ERR_WRONG_COMMAND;
474  }
475 
476  /*
477  If we tried to open a MERGE table and failed due to problems with the
478  children tables, the table will have been closed and table_ref->table
479  will be invalid. Reset the pointer here in any case as
480  query_cache_invalidate does not need a valid TABLE object.
481  */
482  table_ref->table= NULL;
483  query_cache_invalidate3(thd, table_ref, FALSE);
484  }
485 
486  /* DDL is logged in statement format, regardless of binlog format. */
487  if (binlog_stmt)
488  error|= write_bin_log(thd, !error, thd->query(), thd->query_length());
489 
490  /*
491  A locked table ticket was upgraded to a exclusive lock. After the
492  the query has been written to the binary log, downgrade the lock
493  to a shared one.
494  */
495  if (m_ticket_downgrade)
496  m_ticket_downgrade->downgrade_lock(MDL_SHARED_NO_READ_WRITE);
497 
498  DBUG_RETURN(error);
499 }
500 
501 
510 {
511  bool res= TRUE;
512  TABLE_LIST *first_table= thd->lex->select_lex.table_list.first;
513  DBUG_ENTER("Sql_cmd_truncate_table::execute");
514 
515  if (check_one_table_access(thd, DROP_ACL, first_table))
516  DBUG_RETURN(res);
517 
518  if (! (res= truncate_table(thd, first_table)))
519  my_ok(thd);
520 
521  DBUG_RETURN(res);
522 }
523