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-1301 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
42static 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
71static 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
119static bool
120fk_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 DBUG_ASSERT(!lex_string_cmp(system_charset_info,
151 fk_info->referenced_db,
152 &table->s->db));
153
154 DBUG_ASSERT(!lex_string_cmp(system_charset_info,
155 fk_info->referenced_table,
156 &table->s->table_name));
157
158 if (lex_string_cmp(system_charset_info, fk_info->foreign_db,
159 &table->s->db) ||
160 lex_string_cmp(system_charset_info, fk_info->foreign_table,
161 &table->s->table_name))
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 TRUNCATE_OK Truncate was successful and statement can be safely
184 binlogged.
185 @retval TRUNCATE_FAILED_BUT_BINLOG Truncate failed but still go ahead with
186 binlogging as in case of non transactional tables
187 partial truncation is possible.
188
189 @retval TRUNCATE_FAILED_SKIP_BINLOG Truncate was not successful hence donot
190 binlong the statement.
191*/
192
193enum Sql_cmd_truncate_table::truncate_result
194Sql_cmd_truncate_table::handler_truncate(THD *thd, TABLE_LIST *table_ref,
195 bool is_tmp_table)
196{
197 int error= 0;
198 uint flags= 0;
199 DBUG_ENTER("Sql_cmd_truncate_table::handler_truncate");
200
201 /*
202 Can't recreate, the engine must mechanically delete all rows
203 in the table. Use open_and_lock_tables() to open a write cursor.
204 */
205
206 /* If it is a temporary table, no need to take locks. */
207 if (!is_tmp_table)
208 {
209 /* We don't need to load triggers. */
210 DBUG_ASSERT(table_ref->trg_event_map == 0);
211 /*
212 Our metadata lock guarantees that no transaction is reading
213 or writing into the table. Yet, to open a write cursor we need
214 a thr_lock lock. Allow to open base tables only.
215 */
216 table_ref->required_type= TABLE_TYPE_NORMAL;
217 /*
218 Ignore pending FLUSH TABLES since we don't want to release
219 the MDL lock taken above and otherwise there is no way to
220 wait for FLUSH TABLES in deadlock-free fashion.
221 */
222 flags= MYSQL_OPEN_IGNORE_FLUSH;
223 /*
224 Even though we have an MDL lock on the table here, we don't
225 pass MYSQL_OPEN_HAS_MDL_LOCK to open_and_lock_tables
226 since to truncate a MERGE table, we must open and lock
227 merge children, and on those we don't have an MDL lock.
228 Thus clear the ticket to satisfy MDL asserts.
229 */
230 table_ref->mdl_request.ticket= NULL;
231 }
232
233 /* Open the table as it will handle some required preparations. */
234 if (open_and_lock_tables(thd, table_ref, FALSE, flags))
235 DBUG_RETURN(TRUNCATE_FAILED_SKIP_BINLOG);
236
237 /* Whether to truncate regardless of foreign keys. */
238 if (! (thd->variables.option_bits & OPTION_NO_FOREIGN_KEY_CHECKS))
239 if (fk_truncate_illegal_if_parent(thd, table_ref->table))
240 DBUG_RETURN(TRUNCATE_FAILED_SKIP_BINLOG);
241
242 error= table_ref->table->file->ha_truncate();
243 if (unlikely(error))
244 {
245 table_ref->table->file->print_error(error, MYF(0));
246 /*
247 If truncate method is not implemented then we don't binlog the
248 statement. If truncation has failed in a transactional engine then also
249 we don't binlog the statment. Only in non transactional engine we binlog
250 inspite of errors.
251 */
252 if (error == HA_ERR_WRONG_COMMAND ||
253 table_ref->table->file->has_transactions())
254 DBUG_RETURN(TRUNCATE_FAILED_SKIP_BINLOG);
255 else
256 DBUG_RETURN(TRUNCATE_FAILED_BUT_BINLOG);
257 }
258 DBUG_RETURN(TRUNCATE_OK);
259}
260
261
262/*
263 Handle locking a base table for truncate.
264
265 @param[in] thd Thread context.
266 @param[in] table_ref Table list element for the table to
267 be truncated.
268 @param[out] hton_can_recreate Set to TRUE if table can be dropped
269 and recreated.
270
271 @retval FALSE Success.
272 @retval TRUE Error.
273*/
274
275bool Sql_cmd_truncate_table::lock_table(THD *thd, TABLE_LIST *table_ref,
276 bool *hton_can_recreate)
277{
278 TABLE *table= NULL;
279 DBUG_ENTER("Sql_cmd_truncate_table::lock_table");
280
281 /* Lock types are set in the parser. */
282 DBUG_ASSERT(table_ref->lock_type == TL_WRITE);
283 /* The handler truncate protocol dictates a exclusive lock. */
284 DBUG_ASSERT(table_ref->mdl_request.type == MDL_EXCLUSIVE);
285
286 /*
287 Before doing anything else, acquire a metadata lock on the table,
288 or ensure we have one. We don't use open_and_lock_tables()
289 right away because we want to be able to truncate (and recreate)
290 corrupted tables, those that we can't fully open.
291
292 MySQL manual documents that TRUNCATE can be used to repair a
293 damaged table, i.e. a table that can not be fully "opened".
294 In particular MySQL manual says: As long as the table format
295 file tbl_name.frm is valid, the table can be re-created as
296 an empty table with TRUNCATE TABLE, even if the data or index
297 files have become corrupted.
298 */
299 if (thd->locked_tables_mode)
300 {
301 if (!(table= find_table_for_mdl_upgrade(thd, table_ref->db.str,
302 table_ref->table_name.str, FALSE)))
303 DBUG_RETURN(TRUE);
304
305 *hton_can_recreate= ha_check_storage_engine_flag(table->file->ht,
306 HTON_CAN_RECREATE);
307 table_ref->mdl_request.ticket= table->mdl_ticket;
308 }
309 else
310 {
311 handlerton *hton;
312 bool is_sequence;
313
314 /* Acquire an exclusive lock. */
315 DBUG_ASSERT(table_ref->next_global == NULL);
316 if (lock_table_names(thd, table_ref, NULL,
317 thd->variables.lock_wait_timeout, 0))
318 DBUG_RETURN(TRUE);
319
320 if (!ha_table_exists(thd, &table_ref->db, &table_ref->table_name,
321 &hton, &is_sequence) ||
322 hton == view_pseudo_hton)
323 {
324 my_error(ER_NO_SUCH_TABLE, MYF(0), table_ref->db.str,
325 table_ref->table_name.str);
326 DBUG_RETURN(TRUE);
327 }
328
329 if (!hton)
330 {
331 /*
332 The table exists, but its storage engine is unknown, perhaps not
333 loaded at the moment. We need to open and parse the frm to know the
334 storage engine in question, so let's proceed with the truncation and
335 try to open the table. This will produce the correct error message
336 about unknown engine.
337 */
338 *hton_can_recreate= false;
339 }
340 else
341 *hton_can_recreate= !is_sequence && hton->flags & HTON_CAN_RECREATE;
342 }
343
344 /*
345 A storage engine can recreate or truncate the table only if there
346 are no references to it from anywhere, i.e. no cached TABLE in the
347 table cache.
348 */
349 if (thd->locked_tables_mode)
350 {
351 DEBUG_SYNC(thd, "upgrade_lock_for_truncate");
352 /* To remove the table from the cache we need an exclusive lock. */
353 if (wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_DROP))
354 DBUG_RETURN(TRUE);
355 m_ticket_downgrade= table->mdl_ticket;
356 /* Close if table is going to be recreated. */
357 if (*hton_can_recreate)
358 close_all_tables_for_name(thd, table->s, HA_EXTRA_NOT_USED, NULL);
359 }
360 else
361 {
362 /* Table is already locked exclusively. Remove cached instances. */
363 tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table_ref->db.str,
364 table_ref->table_name.str, FALSE);
365 }
366
367 DBUG_RETURN(FALSE);
368}
369
370
371/*
372 Optimized delete of all rows by doing a full generate of the table.
373
374 @remark Will work even if the .MYI and .MYD files are destroyed.
375 In other words, it works as long as the .FRM is intact and
376 the engine supports re-create.
377
378 @param thd Thread context.
379 @param table_ref Table list element for the table to be truncated.
380
381 @retval FALSE Success.
382 @retval TRUE Error.
383*/
384
385bool Sql_cmd_truncate_table::truncate_table(THD *thd, TABLE_LIST *table_ref)
386{
387 int error;
388 bool binlog_stmt;
389 DBUG_ENTER("Sql_cmd_truncate_table::truncate_table");
390
391 DBUG_ASSERT((!table_ref->table) ||
392 (table_ref->table && table_ref->table->s));
393
394 /* Initialize, or reinitialize in case of reexecution (SP). */
395 m_ticket_downgrade= NULL;
396
397 /* If it is a temporary table, no need to take locks. */
398 if (is_temporary_table(table_ref))
399 {
400 /* In RBR, the statement is not binlogged if the table is temporary. */
401 binlog_stmt= !thd->is_current_stmt_binlog_format_row();
402
403 error= handler_truncate(thd, table_ref, TRUE);
404
405 /*
406 No need to invalidate the query cache, queries with temporary
407 tables are not in the cache. No need to write to the binary
408 log a failed row-by-row delete even if under RBR as the table
409 might not exist on the slave.
410 */
411 }
412 else /* It's not a temporary table. */
413 {
414 bool hton_can_recreate;
415
416 if (WSREP(thd) &&
417 wsrep_to_isolation_begin(thd, table_ref->db.str, table_ref->table_name.str, 0))
418 DBUG_RETURN(TRUE);
419 if (lock_table(thd, table_ref, &hton_can_recreate))
420 DBUG_RETURN(TRUE);
421
422 if (hton_can_recreate)
423 {
424 /*
425 The storage engine can truncate the table by creating an
426 empty table with the same structure.
427 */
428 error= dd_recreate_table(thd, table_ref->db.str, table_ref->table_name.str);
429
430 if (thd->locked_tables_mode && thd->locked_tables_list.reopen_tables(thd, false))
431 {
432 thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0);
433 error=1;
434 }
435
436 /* No need to binlog a failed truncate-by-recreate. */
437 binlog_stmt= !error;
438 }
439 else
440 {
441 /*
442 The engine does not support truncate-by-recreate.
443 Attempt to use the handler truncate method.
444 */
445 error= handler_truncate(thd, table_ref, FALSE);
446
447 /*
448 All effects of a TRUNCATE TABLE operation are committed even if
449 truncation fails in the case of non transactional tables. Thus, the
450 query must be written to the binary log. The only exception is a
451 unimplemented truncate method.
452 */
453 if (unlikely(error == TRUNCATE_OK || error == TRUNCATE_FAILED_BUT_BINLOG))
454 binlog_stmt= true;
455 else
456 binlog_stmt= false;
457 }
458
459 /*
460 If we tried to open a MERGE table and failed due to problems with the
461 children tables, the table will have been closed and table_ref->table
462 will be invalid. Reset the pointer here in any case as
463 query_cache_invalidate does not need a valid TABLE object.
464 */
465 table_ref->table= NULL;
466 query_cache_invalidate3(thd, table_ref, FALSE);
467 }
468
469 /* DDL is logged in statement format, regardless of binlog format. */
470 if (binlog_stmt)
471 error|= write_bin_log(thd, !error, thd->query(), thd->query_length());
472
473 /*
474 A locked table ticket was upgraded to a exclusive lock. After the
475 the query has been written to the binary log, downgrade the lock
476 to a shared one.
477 */
478 if (m_ticket_downgrade)
479 m_ticket_downgrade->downgrade_lock(MDL_SHARED_NO_READ_WRITE);
480
481 DBUG_RETURN(error);
482}
483
484/**
485 Execute a TRUNCATE statement at runtime.
486
487 @param thd The current thread.
488
489 @return FALSE on success.
490*/
491
492bool Sql_cmd_truncate_table::execute(THD *thd)
493{
494 bool res= TRUE;
495 TABLE_LIST *table= thd->lex->select_lex.table_list.first;
496 DBUG_ENTER("Sql_cmd_truncate_table::execute");
497
498 if (check_one_table_access(thd, DROP_ACL, table))
499 DBUG_RETURN(res);
500
501 if (! (res= truncate_table(thd, table)))
502 my_ok(thd);
503
504 DBUG_RETURN(res);
505}
506