1 /* Copyright (c) 2010, 2015, Oracle and/or its affiliates.
2    Copyright (c) 2012, 2018, MariaDB
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-1335  USA */
16 
17 #include "mariadb.h"
18 #include "debug_sync.h"  // DEBUG_SYNC
19 #include "table.h"       // TABLE, FOREIGN_KEY_INFO
20 #include "sql_class.h"   // THD
21 #include "sql_base.h"    // open_and_lock_tables
22 #include "sql_table.h"   // write_bin_log
23 #include "datadict.h"    // dd_recreate_table()
24 #include "lock.h"        // MYSQL_OPEN_* flags
25 #include "sql_acl.h"     // DROP_ACL
26 #include "sql_parse.h"   // check_one_table_access()
27 #include "sql_truncate.h"
28 #include "wsrep_mysqld.h"
29 #include "sql_show.h"    //append_identifier()
30 #include "sql_select.h"
31 #include "sql_delete.h"
32 
33 /**
34   Append a list of field names to a string.
35 
36   @param  str     The string.
37   @param  fields  The list of field names.
38 
39   @return TRUE on failure, FALSE otherwise.
40 */
41 
fk_info_append_fields(THD * thd,String * str,List<LEX_CSTRING> * fields)42 static bool fk_info_append_fields(THD *thd, String *str,
43                                   List<LEX_CSTRING> *fields)
44 {
45   bool res= FALSE;
46   LEX_CSTRING *field;
47   List_iterator_fast<LEX_CSTRING> it(*fields);
48 
49   while ((field= it++))
50   {
51     res|= append_identifier(thd, str, field);
52     res|= str->append(", ");
53   }
54 
55   str->chop();
56   str->chop();
57 
58   return res;
59 }
60 
61 
62 /**
63   Generate a foreign key description suitable for a error message.
64 
65   @param thd          Thread context.
66   @param fk_info   The foreign key information.
67 
68   @return A human-readable string describing the foreign key.
69 */
70 
fk_info_str(THD * thd,FOREIGN_KEY_INFO * fk_info)71 static const char *fk_info_str(THD *thd, FOREIGN_KEY_INFO *fk_info)
72 {
73   bool res= FALSE;
74   char buffer[STRING_BUFFER_USUAL_SIZE*2];
75   String str(buffer, sizeof(buffer), system_charset_info);
76 
77   str.length(0);
78 
79   /*
80     `db`.`tbl`, CONSTRAINT `id` FOREIGN KEY (`fk`) REFERENCES `db`.`tbl` (`fk`)
81   */
82 
83   res|= append_identifier(thd, &str, fk_info->foreign_db);
84   res|= str.append(".");
85   res|= append_identifier(thd, &str, fk_info->foreign_table);
86   res|= str.append(", CONSTRAINT ");
87   res|= append_identifier(thd, &str, fk_info->foreign_id);
88   res|= str.append(" FOREIGN KEY (");
89   res|= fk_info_append_fields(thd, &str, &fk_info->foreign_fields);
90   res|= str.append(") REFERENCES ");
91   res|= append_identifier(thd, &str, fk_info->referenced_db);
92   res|= str.append(".");
93   res|= append_identifier(thd, &str, fk_info->referenced_table);
94   res|= str.append(" (");
95   res|= fk_info_append_fields(thd, &str, &fk_info->referenced_fields);
96   res|= str.append(')');
97 
98   return res ? NULL : thd->strmake(str.ptr(), str.length());
99 }
100 
101 
102 /**
103   Check and emit a fatal error if the table which is going to be
104   affected by TRUNCATE TABLE is a parent table in some non-self-
105   referencing foreign key.
106 
107   @remark The intention is to allow truncate only for tables that
108           are not dependent on other tables.
109 
110   @param  thd    Thread context.
111   @param  table  Table handle.
112 
113   @retval FALSE  This table is not parent in a non-self-referencing foreign
114                  key. Statement can proceed.
115   @retval TRUE   This table is parent in a non-self-referencing foreign key,
116                  error was emitted.
117 */
118 
119 static bool
fk_truncate_illegal_if_parent(THD * thd,TABLE * table)120 fk_truncate_illegal_if_parent(THD *thd, TABLE *table)
121 {
122   FOREIGN_KEY_INFO *fk_info;
123   List<FOREIGN_KEY_INFO> fk_list;
124   List_iterator_fast<FOREIGN_KEY_INFO> it;
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 (unlikely(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     if (lex_string_cmp(system_charset_info, fk_info->referenced_db,
151                        &table->s->db) ||
152         lex_string_cmp(system_charset_info, fk_info->referenced_table,
153                        &table->s->table_name) ||
154         lex_string_cmp(system_charset_info, fk_info->foreign_db,
155                        &table->s->db) ||
156         lex_string_cmp(system_charset_info, fk_info->foreign_table,
157                        &table->s->table_name))
158       break;
159   }
160 
161   /* Table is parent in a non-self-referencing foreign key. */
162   if (fk_info)
163   {
164     my_error(ER_TRUNCATE_ILLEGAL_FK, MYF(0), fk_info_str(thd, fk_info));
165     return TRUE;
166   }
167 
168   return FALSE;
169 }
170 
171 
172 /*
173   Open and truncate a locked table.
174 
175   @param  thd           Thread context.
176   @param  table_ref     Table list element for the table to be truncated.
177   @param  is_tmp_table  True if element refers to a temp table.
178 
179   @retval TRUNCATE_OK   Truncate was successful and statement can be safely
180                         binlogged.
181   @retval TRUNCATE_FAILED_BUT_BINLOG Truncate failed but still go ahead with
182                         binlogging as in case of non transactional tables
183                         partial truncation is possible.
184 
185   @retval TRUNCATE_FAILED_SKIP_BINLOG Truncate was not successful hence donot
186                         binlong the statement.
187 */
188 
189 enum Sql_cmd_truncate_table::truncate_result
handler_truncate(THD * thd,TABLE_LIST * table_ref,bool is_tmp_table)190 Sql_cmd_truncate_table::handler_truncate(THD *thd, TABLE_LIST *table_ref,
191                                          bool is_tmp_table)
192 {
193   int error= 0;
194   uint flags= 0;
195   DBUG_ENTER("Sql_cmd_truncate_table::handler_truncate");
196 
197   /*
198     Can't recreate, the engine must mechanically delete all rows
199     in the table. Use open_and_lock_tables() to open a write cursor.
200   */
201 
202   /* If it is a temporary table, no need to take locks. */
203   if (!is_tmp_table)
204   {
205     /* We don't need to load triggers. */
206     DBUG_ASSERT(table_ref->trg_event_map == 0);
207     /*
208       Our metadata lock guarantees that no transaction is reading
209       or writing into the table. Yet, to open a write cursor we need
210       a thr_lock lock. Allow to open base tables only.
211     */
212     table_ref->required_type= TABLE_TYPE_NORMAL;
213     /*
214       Ignore pending FLUSH TABLES since we don't want to release
215       the MDL lock taken above and otherwise there is no way to
216       wait for FLUSH TABLES in deadlock-free fashion.
217     */
218     flags= MYSQL_OPEN_IGNORE_FLUSH;
219     /*
220       Even though we have an MDL lock on the table here, we don't
221       pass MYSQL_OPEN_HAS_MDL_LOCK to open_and_lock_tables
222       since to truncate a MERGE table, we must open and lock
223       merge children, and on those we don't have an MDL lock.
224       Thus clear the ticket to satisfy MDL asserts.
225     */
226     table_ref->mdl_request.ticket= NULL;
227   }
228 
229   /* Open the table as it will handle some required preparations. */
230   if (open_and_lock_tables(thd, table_ref, FALSE, flags))
231     DBUG_RETURN(TRUNCATE_FAILED_SKIP_BINLOG);
232 
233   /* Whether to truncate regardless of foreign keys. */
234   if (! (thd->variables.option_bits & OPTION_NO_FOREIGN_KEY_CHECKS))
235     if (fk_truncate_illegal_if_parent(thd, table_ref->table))
236       DBUG_RETURN(TRUNCATE_FAILED_SKIP_BINLOG);
237 
238   error= table_ref->table->file->ha_truncate();
239   if (unlikely(error))
240   {
241     table_ref->table->file->print_error(error, MYF(0));
242     /*
243       If truncate method is not implemented then we don't binlog the
244       statement. If truncation has failed in a transactional engine then also
245       we don't binlog the statment. Only in non transactional engine we binlog
246       inspite of errors.
247      */
248     if (error == HA_ERR_WRONG_COMMAND ||
249         table_ref->table->file->has_transactions())
250       DBUG_RETURN(TRUNCATE_FAILED_SKIP_BINLOG);
251     else
252       DBUG_RETURN(TRUNCATE_FAILED_BUT_BINLOG);
253   }
254   DBUG_RETURN(TRUNCATE_OK);
255 }
256 
257 
258 /*
259   Handle locking a base table for truncate.
260 
261   @param[in]  thd               Thread context.
262   @param[in]  table_ref         Table list element for the table to
263                                 be truncated.
264   @param[out] hton_can_recreate Set to TRUE if table can be dropped
265                                 and recreated.
266 
267   @retval  FALSE  Success.
268   @retval  TRUE   Error.
269 */
270 
lock_table(THD * thd,TABLE_LIST * table_ref,bool * hton_can_recreate)271 bool Sql_cmd_truncate_table::lock_table(THD *thd, TABLE_LIST *table_ref,
272                                         bool *hton_can_recreate)
273 {
274   TABLE *table= NULL;
275   DBUG_ENTER("Sql_cmd_truncate_table::lock_table");
276 
277   /* Lock types are set in the parser. */
278   DBUG_ASSERT(table_ref->lock_type == TL_WRITE);
279   /* The handler truncate protocol dictates a exclusive lock. */
280   DBUG_ASSERT(table_ref->mdl_request.type == MDL_EXCLUSIVE);
281 
282   /*
283     Before doing anything else, acquire a metadata lock on the table,
284     or ensure we have one.  We don't use open_and_lock_tables()
285     right away because we want to be able to truncate (and recreate)
286     corrupted tables, those that we can't fully open.
287 
288     MySQL manual documents that TRUNCATE can be used to repair a
289     damaged table, i.e. a table that can not be fully "opened".
290     In particular MySQL manual says: As long as the table format
291     file tbl_name.frm is valid, the table can be re-created as
292     an empty table with TRUNCATE TABLE, even if the data or index
293     files have become corrupted.
294   */
295   if (thd->locked_tables_mode)
296   {
297     if (!(table= find_table_for_mdl_upgrade(thd, table_ref->db.str,
298                                             table_ref->table_name.str, NULL)))
299       DBUG_RETURN(TRUE);
300 
301     *hton_can_recreate= ha_check_storage_engine_flag(table->file->ht,
302                                                      HTON_CAN_RECREATE);
303     table_ref->mdl_request.ticket= table->mdl_ticket;
304   }
305   else
306   {
307     handlerton *hton;
308     bool is_sequence;
309 
310     /* Acquire an exclusive lock. */
311     DBUG_ASSERT(table_ref->next_global == NULL);
312     if (lock_table_names(thd, table_ref, NULL,
313                          thd->variables.lock_wait_timeout, 0))
314       DBUG_RETURN(TRUE);
315 
316     if (!ha_table_exists(thd, &table_ref->db, &table_ref->table_name,
317                          &hton, &is_sequence) ||
318         hton == view_pseudo_hton)
319     {
320       my_error(ER_NO_SUCH_TABLE, MYF(0), table_ref->db.str,
321                table_ref->table_name.str);
322       DBUG_RETURN(TRUE);
323     }
324 
325     if (!hton)
326     {
327       /*
328         The table exists, but its storage engine is unknown, perhaps not
329         loaded at the moment. We need to open and parse the frm to know the
330         storage engine in question, so let's proceed with the truncation and
331         try to open the table. This will produce the correct error message
332         about unknown engine.
333       */
334       *hton_can_recreate= false;
335     }
336     else
337       *hton_can_recreate= !is_sequence && hton->flags & HTON_CAN_RECREATE;
338   }
339 
340   /*
341     A storage engine can recreate or truncate the table only if there
342     are no references to it from anywhere, i.e. no cached TABLE in the
343     table cache.
344   */
345   if (thd->locked_tables_mode)
346   {
347     DEBUG_SYNC(thd, "upgrade_lock_for_truncate");
348     /* To remove the table from the cache we need an exclusive lock. */
349     if (wait_while_table_is_used(thd, table,
350           *hton_can_recreate ? HA_EXTRA_PREPARE_FOR_DROP : HA_EXTRA_NOT_USED))
351       DBUG_RETURN(TRUE);
352     m_ticket_downgrade= table->mdl_ticket;
353     /* Close if table is going to be recreated. */
354     if (*hton_can_recreate)
355       close_all_tables_for_name(thd, table->s, HA_EXTRA_NOT_USED, NULL);
356   }
357   else
358   {
359     /* Table is already locked exclusively. Remove cached instances. */
360     tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table_ref->db.str,
361                      table_ref->table_name.str, FALSE);
362   }
363 
364   DBUG_RETURN(FALSE);
365 }
366 
367 
368 /*
369   Optimized delete of all rows by doing a full generate of the table.
370 
371   @remark Will work even if the .MYI and .MYD files are destroyed.
372           In other words, it works as long as the .FRM is intact and
373           the engine supports re-create.
374 
375   @param  thd         Thread context.
376   @param  table_ref   Table list element for the table to be truncated.
377 
378   @retval  FALSE  Success.
379   @retval  TRUE   Error.
380 */
381 
truncate_table(THD * thd,TABLE_LIST * table_ref)382 bool Sql_cmd_truncate_table::truncate_table(THD *thd, TABLE_LIST *table_ref)
383 {
384   int error;
385   bool binlog_stmt;
386   DBUG_ENTER("Sql_cmd_truncate_table::truncate_table");
387 
388   DBUG_ASSERT((!table_ref->table) ||
389               (table_ref->table && table_ref->table->s));
390 
391   /* Initialize, or reinitialize in case of reexecution (SP). */
392   m_ticket_downgrade= NULL;
393 
394   /* If it is a temporary table, no need to take locks. */
395   if (is_temporary_table(table_ref))
396   {
397     /* In RBR, the statement is not binlogged if the table is temporary. */
398     binlog_stmt= !thd->is_current_stmt_binlog_format_row();
399 
400     thd->close_unused_temporary_table_instances(table_ref);
401 
402     error= handler_truncate(thd, table_ref, TRUE);
403 
404     /*
405       No need to invalidate the query cache, queries with temporary
406       tables are not in the cache. No need to write to the binary
407       log a failed row-by-row delete even if under RBR as the table
408       might not exist on the slave.
409     */
410   }
411   else /* It's not a temporary table. */
412   {
413     bool hton_can_recreate;
414 
415     if (WSREP(thd) &&
416         wsrep_to_isolation_begin(thd, table_ref->db.str, table_ref->table_name.str, 0))
417         DBUG_RETURN(TRUE);
418     if (lock_table(thd, table_ref, &hton_can_recreate))
419       DBUG_RETURN(TRUE);
420 
421     if (hton_can_recreate)
422     {
423      /*
424         The storage engine can truncate the table by creating an
425         empty table with the same structure.
426       */
427       error= dd_recreate_table(thd, table_ref->db.str, table_ref->table_name.str);
428 
429       if (thd->locked_tables_mode && thd->locked_tables_list.reopen_tables(thd, false))
430       {
431           thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0);
432           error= 1;
433       }
434       /* No need to binlog a failed truncate-by-recreate. */
435       binlog_stmt= !error;
436     }
437     else
438     {
439       /*
440         The engine does not support truncate-by-recreate.
441         Attempt to use the handler truncate method.
442       */
443       error= handler_truncate(thd, table_ref, FALSE);
444 
445       if (error == TRUNCATE_OK && thd->locked_tables_mode &&
446           (table_ref->table->file->ht->flags &
447            HTON_REQUIRES_CLOSE_AFTER_TRUNCATE))
448       {
449         thd->locked_tables_list.mark_table_for_reopen(thd, table_ref->table);
450         if (unlikely(thd->locked_tables_list.reopen_tables(thd, true)))
451           thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0);
452       }
453 
454       /*
455         All effects of a TRUNCATE TABLE operation are committed even if
456         truncation fails in the case of non transactional tables. Thus, the
457         query must be written to the binary log. The only exception is a
458         unimplemented truncate method.
459       */
460       if (unlikely(error == TRUNCATE_OK || error == TRUNCATE_FAILED_BUT_BINLOG))
461         binlog_stmt= true;
462       else
463         binlog_stmt= false;
464     }
465 
466     /*
467       If we tried to open a MERGE table and failed due to problems with the
468       children tables, the table will have been closed and table_ref->table
469       will be invalid. Reset the pointer here in any case as
470       query_cache_invalidate does not need a valid TABLE object.
471     */
472     table_ref->table= NULL;
473     query_cache_invalidate3(thd, table_ref, FALSE);
474   }
475 
476   /* DDL is logged in statement format, regardless of binlog format. */
477   if (binlog_stmt)
478     error|= write_bin_log(thd, !error, thd->query(), thd->query_length());
479 
480   /*
481     A locked table ticket was upgraded to a exclusive lock. After the
482     the query has been written to the binary log, downgrade the lock
483     to a shared one.
484   */
485   if (m_ticket_downgrade)
486     m_ticket_downgrade->downgrade_lock(MDL_SHARED_NO_READ_WRITE);
487 
488   DBUG_RETURN(error);
489 }
490 
491 /**
492   Execute a TRUNCATE statement at runtime.
493 
494   @param  thd   The current thread.
495 
496   @return FALSE on success.
497 */
498 
execute(THD * thd)499 bool Sql_cmd_truncate_table::execute(THD *thd)
500 {
501   bool res= TRUE;
502   TABLE_LIST *table= thd->lex->select_lex.table_list.first;
503   DBUG_ENTER("Sql_cmd_truncate_table::execute");
504 
505   if (check_one_table_access(thd, DROP_ACL, table))
506     DBUG_RETURN(res);
507 
508   if (! (res= truncate_table(thd, table)))
509     my_ok(thd);
510 
511   DBUG_RETURN(res);
512 }
513