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