1/*****************************************************************************
2
3Copyright (c) 2013, 2017, Oracle and/or its affiliates. All Rights Reserved.
4Copyright (c) 2017, 2018, MariaDB Corporation.
5
6This program is free software; you can redistribute it and/or modify it under
7the terms of the GNU General Public License as published by the Free Software
8Foundation; version 2 of the License.
9
10This program is distributed in the hope that it will be useful, but WITHOUT
11ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
13
14You should have received a copy of the GNU General Public License along with
15this program; if not, write to the Free Software Foundation, Inc.,
1651 Franklin Street, Suite 500, Boston, MA 02110-1335 USA
17
18*****************************************************************************/
19
20/**************************************************//**
21@file row/row0trunc.cc
22TRUNCATE implementation
23
24Created 2013-04-12 Sunny Bains
25*******************************************************/
26
27#include "row0trunc.h"
28#include "btr0sea.h"
29#include "pars0pars.h"
30#include "dict0crea.h"
31#include "dict0stats.h"
32#include "dict0stats_bg.h"
33#include "lock0lock.h"
34#include "fts0fts.h"
35#include "fsp0sysspace.h"
36#include "ibuf0ibuf.h"
37#include "os0file.h"
38#include "que0que.h"
39#include "trx0undo.h"
40
41/* FIXME: For temporary tables, use a simple approach of btr_free()
42and btr_create() of each index tree. */
43
44/* FIXME: For persistent tables, remove this code in MDEV-11655
45and use a combination of the transactional DDL log to make atomic the
46low-level operations ha_innobase::delete_table(), ha_innobase::create(). */
47
48bool truncate_t::s_fix_up_active = false;
49truncate_t::tables_t truncate_t::s_tables;
50truncate_t::truncated_tables_t truncate_t::s_truncated_tables;
51
52/**
53Iterator over the the raw records in an index, doesn't support MVCC. */
54class IndexIterator {
55
56public:
57 /**
58 Iterate over an indexes records
59 @param index index to iterate over */
60 explicit IndexIterator(dict_index_t* index)
61 :
62 m_index(index)
63 {
64 /* Do nothing */
65 }
66
67 /**
68 Search for key. Position the cursor on a record GE key.
69 @return DB_SUCCESS or error code. */
70 dberr_t search(dtuple_t& key, bool noredo)
71 {
72 mtr_start(&m_mtr);
73
74 if (noredo) {
75 mtr_set_log_mode(&m_mtr, MTR_LOG_NO_REDO);
76 }
77
78 btr_pcur_open_on_user_rec(
79 m_index,
80 &key,
81 PAGE_CUR_GE,
82 BTR_MODIFY_LEAF,
83 &m_pcur, &m_mtr);
84
85 return(DB_SUCCESS);
86 }
87
88 /**
89 Iterate over all the records
90 @return DB_SUCCESS or error code */
91 template <typename Callback>
92 dberr_t for_each(Callback& callback)
93 {
94 dberr_t err = DB_SUCCESS;
95
96 for (;;) {
97
98 if (!btr_pcur_is_on_user_rec(&m_pcur)
99 || !callback.match(&m_pcur)) {
100
101 /* The end of of the index has been reached. */
102 err = DB_END_OF_INDEX;
103 break;
104 }
105
106 rec_t* rec = btr_pcur_get_rec(&m_pcur);
107
108 if (!rec_get_deleted_flag(rec, FALSE)) {
109
110 err = callback(&m_mtr, &m_pcur);
111
112 if (err != DB_SUCCESS) {
113 break;
114 }
115 }
116
117 btr_pcur_move_to_next_user_rec(&m_pcur, &m_mtr);
118 }
119
120 btr_pcur_close(&m_pcur);
121 mtr_commit(&m_mtr);
122
123 return(err == DB_END_OF_INDEX ? DB_SUCCESS : err);
124 }
125
126private:
127 // Disable copying
128 IndexIterator(const IndexIterator&);
129 IndexIterator& operator=(const IndexIterator&);
130
131private:
132 mtr_t m_mtr;
133 btr_pcur_t m_pcur;
134 dict_index_t* m_index;
135};
136
137/** SysIndex table iterator, iterate over records for a table. */
138class SysIndexIterator {
139
140public:
141 /**
142 Iterate over all the records that match the table id.
143 @return DB_SUCCESS or error code */
144 template <typename Callback>
145 dberr_t for_each(Callback& callback) const
146 {
147 dict_index_t* sys_index;
148 byte buf[DTUPLE_EST_ALLOC(1)];
149 dtuple_t* tuple =
150 dtuple_create_from_mem(buf, sizeof(buf), 1, 0);
151 dfield_t* dfield = dtuple_get_nth_field(tuple, 0);
152
153 dfield_set_data(
154 dfield,
155 callback.table_id(),
156 sizeof(*callback.table_id()));
157
158 sys_index = dict_table_get_first_index(dict_sys->sys_indexes);
159
160 dict_index_copy_types(tuple, sys_index, 1);
161
162 IndexIterator iterator(sys_index);
163
164 /* Search on the table id and position the cursor
165 on GE table_id. */
166 iterator.search(*tuple, callback.get_logging_status());
167
168 return(iterator.for_each(callback));
169 }
170};
171
172/** Generic callback abstract class. */
173class Callback
174{
175
176public:
177 /**
178 Constructor
179 @param table_id id of the table being operated.
180 @param noredo if true turn off logging. */
181 Callback(table_id_t table_id, bool noredo)
182 :
183 m_id(),
184 m_noredo(noredo)
185 {
186 /* Convert to storage byte order. */
187 mach_write_to_8(&m_id, table_id);
188 }
189
190 /**
191 Destructor */
192 virtual ~Callback()
193 {
194 /* Do nothing */
195 }
196
197 /**
198 @param pcur persistent cursor used for iteration
199 @return true if the table id column matches. */
200 bool match(btr_pcur_t* pcur) const
201 {
202 ulint len;
203 const byte* field;
204 rec_t* rec = btr_pcur_get_rec(pcur);
205
206 field = rec_get_nth_field_old(
207 rec, DICT_FLD__SYS_INDEXES__TABLE_ID, &len);
208
209 ut_ad(len == 8);
210
211 return(memcmp(&m_id, field, len) == 0);
212 }
213
214 /**
215 @return pointer to table id storage format buffer */
216 const table_id_t* table_id() const
217 {
218 return(&m_id);
219 }
220
221 /**
222 @return return if logging needs to be turned off. */
223 bool get_logging_status() const
224 {
225 return(m_noredo);
226 }
227
228protected:
229 // Disably copying
230 Callback(const Callback&);
231 Callback& operator=(const Callback&);
232
233protected:
234 /** Table id in storage format */
235 table_id_t m_id;
236
237 /** Turn off logging. */
238 const bool m_noredo;
239};
240
241/**
242Creates a TRUNCATE log record with space id, table name, data directory path,
243tablespace flags, table format, index ids, index types, number of index fields
244and index field information of the table. */
245class TruncateLogger : public Callback {
246
247public:
248 /**
249 Constructor
250
251 @param table Table to truncate
252 @param flags tablespace falgs */
253 TruncateLogger(
254 dict_table_t* table,
255 ulint flags,
256 table_id_t new_table_id)
257 :
258 Callback(table->id, false),
259 m_table(table),
260 m_flags(flags),
261 m_truncate(table->id, new_table_id, table->data_dir_path),
262 m_log_file_name()
263 {
264 /* Do nothing */
265 }
266
267 /**
268 Initialize Truncate Logger by constructing Truncate Log File Name.
269
270 @return DB_SUCCESS or error code. */
271 dberr_t init()
272 {
273 /* Construct log file name. */
274 ulint log_file_name_buf_sz =
275 strlen(srv_log_group_home_dir) + 22 + 22 + 1 /* NUL */
276 + strlen(TruncateLogger::s_log_prefix)
277 + strlen(TruncateLogger::s_log_ext);
278
279 m_log_file_name = UT_NEW_ARRAY_NOKEY(char, log_file_name_buf_sz);
280 if (m_log_file_name == NULL) {
281 return(DB_OUT_OF_MEMORY);
282 }
283 memset(m_log_file_name, 0, log_file_name_buf_sz);
284
285 strcpy(m_log_file_name, srv_log_group_home_dir);
286 ulint log_file_name_len = strlen(m_log_file_name);
287 if (m_log_file_name[log_file_name_len - 1]
288 != OS_PATH_SEPARATOR) {
289
290 m_log_file_name[log_file_name_len]
291 = OS_PATH_SEPARATOR;
292 log_file_name_len = strlen(m_log_file_name);
293 }
294
295 snprintf(m_log_file_name + log_file_name_len,
296 log_file_name_buf_sz - log_file_name_len,
297 "%s" ULINTPF "_" IB_ID_FMT "_%s",
298 TruncateLogger::s_log_prefix,
299 m_table->space_id, m_table->id,
300 TruncateLogger::s_log_ext);
301
302 return(DB_SUCCESS);
303 }
304
305 /**
306 Destructor */
307 ~TruncateLogger()
308 {
309 if (m_log_file_name != NULL) {
310 bool exist;
311 os_file_delete_if_exists(
312 innodb_log_file_key, m_log_file_name, &exist);
313 UT_DELETE_ARRAY(m_log_file_name);
314 m_log_file_name = NULL;
315 }
316 }
317
318 /**
319 @param mtr mini-transaction covering the read
320 @param pcur persistent cursor used for reading
321 @return DB_SUCCESS or error code */
322 dberr_t operator()(mtr_t* mtr, btr_pcur_t* pcur);
323
324 /** Called after iteratoring over the records.
325 @return true if invariant satisfied. */
326 bool debug() const
327 {
328 /* We must find all the index entries on disk. */
329 return(UT_LIST_GET_LEN(m_table->indexes)
330 == m_truncate.indexes());
331 }
332
333 /**
334 Write the TRUNCATE log
335 @return DB_SUCCESS or error code */
336 dberr_t log() const
337 {
338 dberr_t err = DB_SUCCESS;
339
340 if (m_log_file_name == 0) {
341 return(DB_ERROR);
342 }
343
344 bool ret;
345 os_file_t handle = os_file_create(
346 innodb_log_file_key, m_log_file_name,
347 OS_FILE_CREATE, OS_FILE_NORMAL,
348 OS_LOG_FILE, srv_read_only_mode, &ret);
349 if (!ret) {
350 return(DB_IO_ERROR);
351 }
352
353
354 ulint sz = srv_page_size;
355 void* buf = ut_zalloc_nokey(sz + srv_page_size);
356 if (buf == 0) {
357 os_file_close(handle);
358 return(DB_OUT_OF_MEMORY);
359 }
360
361 /* Align the memory for file i/o if we might have O_DIRECT set*/
362 byte* log_buf = static_cast<byte*>(
363 ut_align(buf, srv_page_size));
364
365 lsn_t lsn = log_get_lsn();
366
367 /* Generally loop should exit in single go but
368 just for those 1% of rare cases we need to assume
369 corner case. */
370 do {
371 /* First 4 bytes are reserved for magic number
372 which is currently 0. */
373 err = m_truncate.write(
374 log_buf + 4, log_buf + sz - 4,
375 m_table->space_id, m_table->name.m_name,
376 m_flags, m_table->flags, lsn);
377
378 DBUG_EXECUTE_IF("ib_err_trunc_oom_logging",
379 err = DB_FAIL;);
380
381 if (err != DB_SUCCESS) {
382 ut_ad(err == DB_FAIL);
383 ut_free(buf);
384 sz *= 2;
385 buf = ut_zalloc_nokey(sz + srv_page_size);
386 DBUG_EXECUTE_IF("ib_err_trunc_oom_logging",
387 ut_free(buf);
388 buf = 0;);
389 if (buf == 0) {
390 os_file_close(handle);
391 return(DB_OUT_OF_MEMORY);
392 }
393 log_buf = static_cast<byte*>(
394 ut_align(buf, srv_page_size));
395 }
396
397 } while (err != DB_SUCCESS);
398
399 dberr_t io_err;
400
401 IORequest request(IORequest::WRITE);
402
403 io_err = os_file_write(
404 request, m_log_file_name, handle, log_buf, 0, sz);
405
406 if (io_err != DB_SUCCESS) {
407
408 ib::error()
409 << "IO: Failed to write the file size to '"
410 << m_log_file_name << "'";
411
412 /* Preserve the original error code */
413 if (err == DB_SUCCESS) {
414 err = io_err;
415 }
416 }
417
418 os_file_flush(handle);
419 os_file_close(handle);
420
421 ut_free(buf);
422
423 /* Why we need MLOG_TRUNCATE when we have truncate_log for
424 recovery?
425 - truncate log can protect us if crash happens while truncate
426 is active. Once truncate is done truncate log is removed.
427 - If crash happens post truncate and system is yet to
428 checkpoint, on recovery we would see REDO records from action
429 before truncate (unless we explicitly checkpoint before
430 returning from truncate API. Costly alternative so rejected).
431 - These REDO records may reference a page that doesn't exist
432 post truncate so we need a mechanism to skip all such REDO
433 records. MLOG_TRUNCATE records space_id and lsn that exactly
434 serve the purpose.
435 - If checkpoint happens post truncate and crash happens post
436 this point then neither MLOG_TRUNCATE nor REDO record
437 from action before truncate are accessible. */
438 if (!is_system_tablespace(m_table->space_id)) {
439 mtr_t mtr;
440 byte* log_ptr;
441
442 mtr_start(&mtr);
443
444 log_ptr = mlog_open(&mtr, 11 + 8);
445 log_ptr = mlog_write_initial_log_record_low(
446 MLOG_TRUNCATE, m_table->space_id, 0,
447 log_ptr, &mtr);
448
449 mach_write_to_8(log_ptr, lsn);
450 log_ptr += 8;
451
452 mlog_close(&mtr, log_ptr);
453 mtr_commit(&mtr);
454 }
455
456 return(err);
457 }
458
459 /**
460 Indicate completion of truncate log by writing magic-number.
461 File will be removed from the system but to protect against
462 unlink (File-System) anomalies we ensure we write magic-number. */
463 void done()
464 {
465 if (m_log_file_name == 0) {
466 return;
467 }
468
469 bool ret;
470 os_file_t handle = os_file_create_simple_no_error_handling(
471 innodb_log_file_key, m_log_file_name,
472 OS_FILE_OPEN, OS_FILE_READ_WRITE,
473 srv_read_only_mode, &ret);
474 DBUG_EXECUTE_IF("ib_err_trunc_writing_magic_number",
475 os_file_close(handle);
476 ret = false;);
477 if (!ret) {
478 ib::error() << "Failed to open truncate log file "
479 << m_log_file_name << "."
480 " If server crashes before truncate log is"
481 " removed make sure it is manually removed"
482 " before restarting server";
483 os_file_delete(innodb_log_file_key, m_log_file_name);
484 return;
485 }
486
487 byte buffer[sizeof(TruncateLogger::s_magic)];
488 mach_write_to_4(buffer, TruncateLogger::s_magic);
489
490 dberr_t err;
491
492 IORequest request(IORequest::WRITE);
493
494 err = os_file_write(
495 request,
496 m_log_file_name, handle, buffer, 0, sizeof(buffer));
497
498 if (err != DB_SUCCESS) {
499
500 ib::error()
501 << "IO: Failed to write the magic number to '"
502 << m_log_file_name << "'";
503 }
504
505 DBUG_EXECUTE_IF("ib_trunc_crash_after_updating_magic_no",
506 DBUG_SUICIDE(););
507 os_file_flush(handle);
508 os_file_close(handle);
509 DBUG_EXECUTE_IF("ib_trunc_crash_after_logging_complete",
510 log_buffer_flush_to_disk();
511 os_thread_sleep(1000000);
512 DBUG_SUICIDE(););
513 os_file_delete(innodb_log_file_key, m_log_file_name);
514 }
515
516private:
517 // Disably copying
518 TruncateLogger(const TruncateLogger&);
519 TruncateLogger& operator=(const TruncateLogger&);
520
521private:
522 /** Lookup the index using the index id.
523 @return index instance if found else NULL */
524 const dict_index_t* find(index_id_t id) const
525 {
526 for (const dict_index_t* index = UT_LIST_GET_FIRST(
527 m_table->indexes);
528 index != NULL;
529 index = UT_LIST_GET_NEXT(indexes, index)) {
530
531 if (index->id == id) {
532 return(index);
533 }
534 }
535
536 return(NULL);
537 }
538
539private:
540 /** Table to be truncated */
541 dict_table_t* m_table;
542
543 /** Tablespace flags */
544 ulint m_flags;
545
546 /** Collect table to truncate information */
547 truncate_t m_truncate;
548
549 /** Truncate log file name. */
550 char* m_log_file_name;
551
552
553public:
554 /** Magic Number to indicate truncate action is complete. */
555 const static ib_uint32_t s_magic;
556
557 /** Truncate Log file Prefix. */
558 const static char* s_log_prefix;
559
560 /** Truncate Log file Extension. */
561 const static char* s_log_ext;
562};
563
564const ib_uint32_t TruncateLogger::s_magic = 32743712;
565const char* TruncateLogger::s_log_prefix = "ib_";
566const char* TruncateLogger::s_log_ext = "trunc.log";
567
568/**
569Scan to find out truncate log file from the given directory path.
570
571@param dir_path look for log directory in following path.
572@param log_files cache to hold truncate log file name found.
573@return DB_SUCCESS or error code. */
574dberr_t
575TruncateLogParser::scan(
576 const char* dir_path,
577 trunc_log_files_t& log_files)
578{
579 os_file_dir_t dir;
580 os_file_stat_t fileinfo;
581 dberr_t err = DB_SUCCESS;
582 ulint ext_len = strlen(TruncateLogger::s_log_ext);
583 ulint prefix_len = strlen(TruncateLogger::s_log_prefix);
584 ulint dir_len = strlen(dir_path);
585
586 /* Scan and look out for the truncate log files. */
587 dir = os_file_opendir(dir_path, true);
588 if (dir == NULL) {
589 return(DB_IO_ERROR);
590 }
591
592 while (fil_file_readdir_next_file(
593 &err, dir_path, dir, &fileinfo) == 0) {
594
595 ulint nm_len = strlen(fileinfo.name);
596
597 if (fileinfo.type == OS_FILE_TYPE_FILE
598 && nm_len > ext_len + prefix_len
599 && (0 == strncmp(fileinfo.name + nm_len - ext_len,
600 TruncateLogger::s_log_ext, ext_len))
601 && (0 == strncmp(fileinfo.name,
602 TruncateLogger::s_log_prefix,
603 prefix_len))) {
604
605 if (fileinfo.size == 0) {
606 /* Truncate log not written. Remove the file. */
607 os_file_delete(
608 innodb_log_file_key, fileinfo.name);
609 continue;
610 }
611
612 /* Construct file name by appending directory path */
613 ulint sz = dir_len + 22 + 22 + 1 + ext_len + prefix_len;
614 char* log_file_name = UT_NEW_ARRAY_NOKEY(char, sz);
615 if (log_file_name == NULL) {
616 err = DB_OUT_OF_MEMORY;
617 break;
618 }
619 memset(log_file_name, 0, sz);
620
621 strncpy(log_file_name, dir_path, dir_len);
622 ulint log_file_name_len = strlen(log_file_name);
623 if (log_file_name[log_file_name_len - 1]
624 != OS_PATH_SEPARATOR) {
625
626 log_file_name[log_file_name_len]
627 = OS_PATH_SEPARATOR;
628 log_file_name_len = strlen(log_file_name);
629 }
630 strcat(log_file_name, fileinfo.name);
631 log_files.push_back(log_file_name);
632 }
633 }
634
635 os_file_closedir(dir);
636
637 return(err);
638}
639
640/**
641Parse the log file and populate table to truncate information.
642(Add this table to truncate information to central vector that is then
643 used by truncate fix-up routine to fix-up truncate action of the table.)
644
645@param log_file_name log file to parse
646@return DB_SUCCESS or error code. */
647dberr_t
648TruncateLogParser::parse(
649 const char* log_file_name)
650{
651 dberr_t err = DB_SUCCESS;
652 truncate_t* truncate = NULL;
653
654 /* Open the file and read magic-number to findout if truncate action
655 was completed. */
656 bool ret;
657 os_file_t handle = os_file_create_simple(
658 innodb_log_file_key, log_file_name,
659 OS_FILE_OPEN, OS_FILE_READ_ONLY, srv_read_only_mode, &ret);
660 if (!ret) {
661 ib::error() << "Error opening truncate log file: "
662 << log_file_name;
663 return(DB_IO_ERROR);
664 }
665
666 ulint sz = srv_page_size;
667 void* buf = ut_zalloc_nokey(sz + srv_page_size);
668 if (buf == 0) {
669 os_file_close(handle);
670 return(DB_OUT_OF_MEMORY);
671 }
672
673 IORequest request(IORequest::READ);
674
675 /* Align the memory for file i/o if we might have O_DIRECT set*/
676 byte* log_buf = static_cast<byte*>(ut_align(buf, srv_page_size));
677
678 do {
679 err = os_file_read(request, handle, log_buf, 0, sz);
680
681 if (err != DB_SUCCESS) {
682 os_file_close(handle);
683 break;
684 }
685
686 ulint magic_n = mach_read_from_4(log_buf);
687 if (magic_n == TruncateLogger::s_magic) {
688
689 /* Truncate action completed. Avoid parsing the file. */
690 os_file_close(handle);
691
692 os_file_delete(innodb_log_file_key, log_file_name);
693 break;
694 }
695
696 if (truncate == NULL) {
697 truncate = UT_NEW_NOKEY(truncate_t(log_file_name));
698 if (truncate == NULL) {
699 os_file_close(handle);
700 err = DB_OUT_OF_MEMORY;
701 break;
702 }
703 }
704
705 err = truncate->parse(log_buf + 4, log_buf + sz - 4);
706
707 if (err != DB_SUCCESS) {
708
709 ut_ad(err == DB_FAIL);
710
711 ut_free(buf);
712 buf = 0;
713
714 sz *= 2;
715
716 buf = ut_zalloc_nokey(sz + srv_page_size);
717
718 if (buf == 0) {
719 os_file_close(handle);
720 err = DB_OUT_OF_MEMORY;
721 UT_DELETE(truncate);
722 truncate = NULL;
723 break;
724 }
725
726 log_buf = static_cast<byte*>(
727 ut_align(buf, srv_page_size));
728 }
729 } while (err != DB_SUCCESS);
730
731 ut_free(buf);
732
733 if (err == DB_SUCCESS && truncate != NULL) {
734 truncate_t::add(truncate);
735 os_file_close(handle);
736 }
737
738 return(err);
739}
740
741/**
742Scan and Parse truncate log files.
743
744@param dir_path look for log directory in following path
745@return DB_SUCCESS or error code. */
746dberr_t
747TruncateLogParser::scan_and_parse(
748 const char* dir_path)
749{
750 dberr_t err;
751 trunc_log_files_t log_files;
752
753 /* Scan and trace all the truncate log files. */
754 err = TruncateLogParser::scan(dir_path, log_files);
755
756 /* Parse truncate lof files if scan was successful. */
757 if (err == DB_SUCCESS) {
758
759 for (ulint i = 0;
760 i < log_files.size() && err == DB_SUCCESS;
761 i++) {
762 err = TruncateLogParser::parse(log_files[i]);
763 }
764 }
765
766 trunc_log_files_t::const_iterator end = log_files.end();
767 for (trunc_log_files_t::const_iterator it = log_files.begin();
768 it != end;
769 ++it) {
770 if (*it != NULL) {
771 UT_DELETE_ARRAY(*it);
772 }
773 }
774 log_files.clear();
775
776 return(err);
777}
778
779/** Callback to drop indexes during TRUNCATE */
780class DropIndex : public Callback {
781
782public:
783 /**
784 Constructor
785
786 @param[in,out] table Table to truncate
787 @param[in] noredo whether to disable redo logging */
788 DropIndex(dict_table_t* table, bool noredo)
789 :
790 Callback(table->id, noredo),
791 m_table(table)
792 {
793 /* No op */
794 }
795
796 /**
797 @param mtr mini-transaction covering the read
798 @param pcur persistent cursor used for reading
799 @return DB_SUCCESS or error code */
800 dberr_t operator()(mtr_t* mtr, btr_pcur_t* pcur) const;
801
802private:
803 /** Table to be truncated */
804 dict_table_t* m_table;
805};
806
807/** Callback to create the indexes during TRUNCATE */
808class CreateIndex : public Callback {
809
810public:
811 /**
812 Constructor
813
814 @param[in,out] table Table to truncate
815 @param[in] noredo whether to disable redo logging */
816 CreateIndex(dict_table_t* table, bool noredo)
817 :
818 Callback(table->id, noredo),
819 m_table(table)
820 {
821 /* No op */
822 }
823
824 /**
825 Create the new index and update the root page number in the
826 SysIndex table.
827
828 @param mtr mini-transaction covering the read
829 @param pcur persistent cursor used for reading
830 @return DB_SUCCESS or error code */
831 dberr_t operator()(mtr_t* mtr, btr_pcur_t* pcur) const;
832
833private:
834 // Disably copying
835 CreateIndex(const CreateIndex&);
836 CreateIndex& operator=(const CreateIndex&);
837
838private:
839 /** Table to be truncated */
840 dict_table_t* m_table;
841};
842
843/** Check for presence of table-id in SYS_XXXX tables. */
844class TableLocator : public Callback {
845
846public:
847 /**
848 Constructor
849 @param table_id table_id to look for */
850 explicit TableLocator(table_id_t table_id)
851 :
852 Callback(table_id, false),
853 m_table_found()
854 {
855 /* No op */
856 }
857
858 /**
859 @return true if table is found */
860 bool is_table_found() const
861 {
862 return(m_table_found);
863 }
864
865 /**
866 Look for table-id in SYS_XXXX tables without loading the table.
867
868 @param pcur persistent cursor used for reading
869 @return DB_SUCCESS */
870 dberr_t operator()(mtr_t*, btr_pcur_t*)
871 {
872 m_table_found = true;
873 return(DB_SUCCESS);
874 }
875
876private:
877 /** Set to true if table is present */
878 bool m_table_found;
879};
880
881/**
882@param pcur persistent cursor used for reading
883@return DB_SUCCESS or error code */
884dberr_t
885TruncateLogger::operator()(mtr_t*, btr_pcur_t* pcur)
886{
887 ulint len;
888 const byte* field;
889 rec_t* rec = btr_pcur_get_rec(pcur);
890 truncate_t::index_t index;
891
892 field = rec_get_nth_field_old(
893 rec, DICT_FLD__SYS_INDEXES__TYPE, &len);
894 ut_ad(len == 4);
895 index.m_type = mach_read_from_4(field);
896
897 field = rec_get_nth_field_old(rec, DICT_FLD__SYS_INDEXES__ID, &len);
898 ut_ad(len == 8);
899 index.m_id = mach_read_from_8(field);
900
901 field = rec_get_nth_field_old(
902 rec, DICT_FLD__SYS_INDEXES__PAGE_NO, &len);
903 ut_ad(len == 4);
904 index.m_root_page_no = mach_read_from_4(field);
905
906 /* For compressed tables we need to store extra meta-data
907 required during btr_create(). */
908 if (FSP_FLAGS_GET_ZIP_SSIZE(m_flags)) {
909
910 const dict_index_t* dict_index = find(index.m_id);
911
912 if (dict_index != NULL) {
913
914 dberr_t err = index.set(dict_index);
915
916 if (err != DB_SUCCESS) {
917 m_truncate.clear();
918 return(err);
919 }
920
921 } else {
922 ib::warn() << "Index id " << index.m_id
923 << " not found";
924 }
925 }
926
927 m_truncate.add(index);
928
929 return(DB_SUCCESS);
930}
931
932/**
933Drop an index in the table.
934
935@param mtr mini-transaction covering the read
936@param pcur persistent cursor used for reading
937@return DB_SUCCESS or error code */
938dberr_t
939DropIndex::operator()(mtr_t* mtr, btr_pcur_t* pcur) const
940{
941 rec_t* rec = btr_pcur_get_rec(pcur);
942
943 bool freed = dict_drop_index_tree(rec, pcur, mtr);
944
945#ifdef UNIV_DEBUG
946 {
947 ulint len;
948 const byte* field;
949 ulint index_type;
950
951 field = rec_get_nth_field_old(
952 btr_pcur_get_rec(pcur), DICT_FLD__SYS_INDEXES__TYPE,
953 &len);
954 ut_ad(len == 4);
955
956 index_type = mach_read_from_4(field);
957
958 if (index_type & DICT_CLUSTERED) {
959 /* Clustered index */
960 DBUG_EXECUTE_IF("ib_trunc_crash_on_drop_of_clust_index",
961 log_buffer_flush_to_disk();
962 os_thread_sleep(2000000);
963 DBUG_SUICIDE(););
964 } else if (index_type & DICT_UNIQUE) {
965 /* Unique index */
966 DBUG_EXECUTE_IF("ib_trunc_crash_on_drop_of_uniq_index",
967 log_buffer_flush_to_disk();
968 os_thread_sleep(2000000);
969 DBUG_SUICIDE(););
970 } else if (index_type == 0) {
971 /* Secondary index */
972 DBUG_EXECUTE_IF("ib_trunc_crash_on_drop_of_sec_index",
973 log_buffer_flush_to_disk();
974 os_thread_sleep(2000000);
975 DBUG_SUICIDE(););
976 }
977 }
978#endif /* UNIV_DEBUG */
979
980 DBUG_EXECUTE_IF("ib_err_trunc_drop_index", return DB_ERROR;);
981
982 if (freed) {
983
984 /* We will need to commit and restart the
985 mini-transaction in order to avoid deadlocks.
986 The dict_drop_index_tree() call has freed
987 a page in this mini-transaction, and the rest
988 of this loop could latch another index page.*/
989 const mtr_log_t log_mode = mtr->get_log_mode();
990 mtr_commit(mtr);
991
992 mtr_start(mtr);
993 mtr->set_log_mode(log_mode);
994
995 btr_pcur_restore_position(BTR_MODIFY_LEAF, pcur, mtr);
996 } else {
997 if (!m_table->space) {
998 return DB_ERROR;
999 }
1000 }
1001
1002 return(DB_SUCCESS);
1003}
1004
1005/**
1006Create the new index and update the root page number in the
1007SysIndex table.
1008
1009@param mtr mini-transaction covering the read
1010@param pcur persistent cursor used for reading
1011@return DB_SUCCESS or error code */
1012dberr_t
1013CreateIndex::operator()(mtr_t* mtr, btr_pcur_t* pcur) const
1014{
1015 ulint root_page_no;
1016
1017 root_page_no = dict_recreate_index_tree(m_table, pcur, mtr);
1018
1019#ifdef UNIV_DEBUG
1020 {
1021 ulint len;
1022 const byte* field;
1023 ulint index_type;
1024
1025 field = rec_get_nth_field_old(
1026 btr_pcur_get_rec(pcur), DICT_FLD__SYS_INDEXES__TYPE,
1027 &len);
1028 ut_ad(len == 4);
1029
1030 index_type = mach_read_from_4(field);
1031
1032 if (index_type & DICT_CLUSTERED) {
1033 /* Clustered index */
1034 DBUG_EXECUTE_IF(
1035 "ib_trunc_crash_on_create_of_clust_index",
1036 log_buffer_flush_to_disk();
1037 os_thread_sleep(2000000);
1038 DBUG_SUICIDE(););
1039 } else if (index_type & DICT_UNIQUE) {
1040 /* Unique index */
1041 DBUG_EXECUTE_IF(
1042 "ib_trunc_crash_on_create_of_uniq_index",
1043 log_buffer_flush_to_disk();
1044 os_thread_sleep(2000000);
1045 DBUG_SUICIDE(););
1046 } else if (index_type == 0) {
1047 /* Secondary index */
1048 DBUG_EXECUTE_IF(
1049 "ib_trunc_crash_on_create_of_sec_index",
1050 log_buffer_flush_to_disk();
1051 os_thread_sleep(2000000);
1052 DBUG_SUICIDE(););
1053 }
1054 }
1055#endif /* UNIV_DEBUG */
1056
1057 DBUG_EXECUTE_IF("ib_err_trunc_create_index", return DB_ERROR;);
1058
1059 if (root_page_no != FIL_NULL) {
1060
1061 rec_t* rec = btr_pcur_get_rec(pcur);
1062
1063 page_rec_write_field(
1064 rec, DICT_FLD__SYS_INDEXES__PAGE_NO,
1065 root_page_no, mtr);
1066
1067 /* We will need to commit and restart the
1068 mini-transaction in order to avoid deadlocks.
1069 The dict_create_index_tree() call has allocated
1070 a page in this mini-transaction, and the rest of
1071 this loop could latch another index page. */
1072 mtr_commit(mtr);
1073
1074 mtr_start(mtr);
1075
1076 btr_pcur_restore_position(BTR_MODIFY_LEAF, pcur, mtr);
1077
1078 } else {
1079 if (!m_table->space) {
1080 return(DB_ERROR);
1081 }
1082 }
1083
1084 return(DB_SUCCESS);
1085}
1086
1087/**
1088Rollback the transaction and release the index locks.
1089Drop indexes if table is corrupted so that drop/create
1090sequence works as expected.
1091
1092@param table table to truncate
1093@param trx transaction covering the TRUNCATE
1094@param new_id new table id that was suppose to get assigned
1095 to the table if truncate executed successfully.
1096@param has_internal_doc_id indicate existence of fts index
1097@param no_redo if true, turn-off redo logging
1098@param corrupted table corrupted status
1099@param unlock_index if true then unlock indexes before action */
1100static
1101void
1102row_truncate_rollback(
1103 dict_table_t* table,
1104 trx_t* trx,
1105 table_id_t new_id,
1106 bool has_internal_doc_id,
1107 bool no_redo,
1108 bool corrupted,
1109 bool unlock_index)
1110{
1111 ut_ad(!table->is_temporary());
1112 if (unlock_index) {
1113 dict_table_x_unlock_indexes(table);
1114 }
1115
1116 trx->error_state = DB_SUCCESS;
1117
1118 trx_rollback_to_savepoint(trx, NULL);
1119
1120 trx->error_state = DB_SUCCESS;
1121
1122 if (corrupted) {
1123
1124 /* Cleanup action to ensure we don't left over stale entries
1125 if we are marking table as corrupted. This will ensure
1126 it can be recovered using drop/create sequence. */
1127 dict_table_x_lock_indexes(table);
1128
1129 DropIndex dropIndex(table, no_redo);
1130
1131 SysIndexIterator().for_each(dropIndex);
1132
1133 dict_table_x_unlock_indexes(table);
1134
1135 for (dict_index_t* index = UT_LIST_GET_FIRST(table->indexes);
1136 index != NULL;
1137 index = UT_LIST_GET_NEXT(indexes, index)) {
1138
1139 dict_set_corrupted(index, trx, "TRUNCATE TABLE");
1140 }
1141
1142 if (has_internal_doc_id) {
1143
1144 ut_ad(!trx_is_started(trx));
1145
1146 table_id_t id = table->id;
1147
1148 table->id = new_id;
1149
1150 fts_drop_tables(trx, table);
1151
1152 table->id = id;
1153
1154 ut_ad(trx_is_started(trx));
1155
1156 trx_commit_for_mysql(trx);
1157 }
1158 }
1159
1160 table->corrupted = corrupted;
1161}
1162
1163/**
1164Finish the TRUNCATE operations for both commit and rollback.
1165
1166@param table table being truncated
1167@param trx transaction covering the truncate
1168@param fsp_flags tablespace flags
1169@param logger table to truncate information logger
1170@param err status of truncate operation
1171
1172@return DB_SUCCESS or error code */
1173static MY_ATTRIBUTE((warn_unused_result))
1174dberr_t
1175row_truncate_complete(
1176 dict_table_t* table,
1177 trx_t* trx,
1178 ulint fsp_flags,
1179 TruncateLogger* &logger,
1180 dberr_t err)
1181{
1182 bool is_file_per_table = dict_table_is_file_per_table(table);
1183
1184 row_mysql_unlock_data_dictionary(trx);
1185
1186 DEBUG_SYNC_C("ib_trunc_table_trunc_completing");
1187
1188 if (!table->is_temporary()) {
1189
1190 DBUG_EXECUTE_IF("ib_trunc_crash_before_log_removal",
1191 log_buffer_flush_to_disk();
1192 os_thread_sleep(500000);
1193 DBUG_SUICIDE(););
1194
1195 /* Note: We don't log-checkpoint instead we have written
1196 a special REDO log record MLOG_TRUNCATE that is used to
1197 avoid applying REDO records before truncate for crash
1198 that happens post successful truncate completion. */
1199
1200 if (logger != NULL) {
1201 logger->done();
1202 UT_DELETE(logger);
1203 logger = NULL;
1204 }
1205 }
1206
1207 /* If non-temp file-per-table tablespace... */
1208 if (is_file_per_table
1209 && !table->is_temporary()
1210 && fsp_flags != ULINT_UNDEFINED) {
1211
1212 /* This function will reset back the stop_new_ops
1213 and is_being_truncated so that fil-ops can re-start. */
1214 dberr_t err2 = truncate_t::truncate(
1215 table->space_id,
1216 table->data_dir_path,
1217 table->name.m_name, fsp_flags, false);
1218
1219 if (err2 != DB_SUCCESS) {
1220 return(err2);
1221 }
1222 }
1223
1224 if (err == DB_SUCCESS) {
1225 dict_stats_update(table, DICT_STATS_EMPTY_TABLE);
1226 }
1227
1228 trx->op_info = "";
1229
1230 /* For temporary tables or if there was an error, we need to reset
1231 the dict operation flags. */
1232 trx->ddl = false;
1233 trx->dict_operation = TRX_DICT_OP_NONE;
1234
1235 ut_ad(!trx_is_started(trx));
1236
1237 srv_wake_master_thread();
1238
1239 DBUG_EXECUTE_IF("ib_trunc_crash_after_truncate_done",
1240 DBUG_SUICIDE(););
1241
1242 return(err);
1243}
1244
1245/**
1246Handle FTS truncate issues.
1247@param table table being truncated
1248@param new_id new id for the table
1249@param trx transaction covering the truncate
1250@return DB_SUCCESS or error code. */
1251static MY_ATTRIBUTE((warn_unused_result))
1252dberr_t
1253row_truncate_fts(
1254 dict_table_t* table,
1255 table_id_t new_id,
1256 trx_t* trx)
1257{
1258 dict_table_t fts_table;
1259
1260 fts_table.id = new_id;
1261 fts_table.name = table->name;
1262 fts_table.flags2 = table->flags2;
1263 fts_table.flags = table->flags;
1264 fts_table.space = table->space;
1265
1266 /* table->data_dir_path is used for FTS AUX table
1267 creation. */
1268 if (DICT_TF_HAS_DATA_DIR(table->flags)
1269 && table->data_dir_path == NULL) {
1270 dict_get_and_save_data_dir_path(table, true);
1271 ut_ad(table->data_dir_path != NULL);
1272 }
1273
1274 fts_table.data_dir_path = table->data_dir_path;
1275
1276 dberr_t err = fts_create_common_tables(trx, &fts_table, true);
1277
1278 for (ulint i = 0;
1279 i < ib_vector_size(table->fts->indexes) && err == DB_SUCCESS;
1280 i++) {
1281
1282 dict_index_t* fts_index;
1283
1284 fts_index = static_cast<dict_index_t*>(
1285 ib_vector_getp(table->fts->indexes, i));
1286
1287 err = fts_create_index_tables(trx, fts_index, new_id);
1288 }
1289
1290 DBUG_EXECUTE_IF("ib_err_trunc_during_fts_trunc",
1291 err = DB_ERROR;);
1292
1293 if (err != DB_SUCCESS) {
1294
1295 trx->error_state = DB_SUCCESS;
1296 trx_rollback_to_savepoint(trx, NULL);
1297 trx->error_state = DB_SUCCESS;
1298
1299 ib::error() << "Unable to truncate FTS index for table "
1300 << table->name;
1301 } else {
1302
1303 ut_ad(trx_is_started(trx));
1304 }
1305
1306 return(err);
1307}
1308
1309/**
1310Update system table to reflect new table id.
1311@param old_table_id old table id
1312@param new_table_id new table id
1313@param reserve_dict_mutex if TRUE, acquire/release
1314 dict_sys->mutex around call to pars_sql.
1315@param trx transaction
1316@return error code or DB_SUCCESS */
1317static MY_ATTRIBUTE((warn_unused_result))
1318dberr_t
1319row_truncate_update_table_id(
1320 table_id_t old_table_id,
1321 table_id_t new_table_id,
1322 ibool reserve_dict_mutex,
1323 trx_t* trx)
1324{
1325 pars_info_t* info = NULL;
1326 dberr_t err = DB_SUCCESS;
1327
1328 /* Scan the SYS_XXXX table and update to reflect new table-id. */
1329 info = pars_info_create();
1330 pars_info_add_ull_literal(info, "old_id", old_table_id);
1331 pars_info_add_ull_literal(info, "new_id", new_table_id);
1332
1333 err = que_eval_sql(
1334 info,
1335 "PROCEDURE RENUMBER_TABLE_ID_PROC () IS\n"
1336 "BEGIN\n"
1337 "UPDATE SYS_TABLES"
1338 " SET ID = :new_id\n"
1339 " WHERE ID = :old_id;\n"
1340 "UPDATE SYS_COLUMNS SET TABLE_ID = :new_id\n"
1341 " WHERE TABLE_ID = :old_id;\n"
1342 "UPDATE SYS_INDEXES"
1343 " SET TABLE_ID = :new_id\n"
1344 " WHERE TABLE_ID = :old_id;\n"
1345 "UPDATE SYS_VIRTUAL"
1346 " SET TABLE_ID = :new_id\n"
1347 " WHERE TABLE_ID = :old_id;\n"
1348 "END;\n", reserve_dict_mutex, trx);
1349
1350 return(err);
1351}
1352
1353/**
1354Get the table id to truncate.
1355@param truncate_t old/new table id of table to truncate
1356@return table_id_t table_id to use in SYS_XXXX table update. */
1357static MY_ATTRIBUTE((warn_unused_result))
1358table_id_t
1359row_truncate_get_trunc_table_id(
1360 const truncate_t& truncate)
1361{
1362 TableLocator tableLocator(truncate.old_table_id());
1363
1364 SysIndexIterator().for_each(tableLocator);
1365
1366 return(tableLocator.is_table_found() ?
1367 truncate.old_table_id(): truncate.new_table_id());
1368}
1369
1370/**
1371Update system table to reflect new table id and root page number.
1372@param truncate_t old/new table id of table to truncate
1373 and updated root_page_no of indexes.
1374@param new_table_id new table id
1375@param reserve_dict_mutex if TRUE, acquire/release
1376 dict_sys->mutex around call to pars_sql.
1377@param mark_index_corrupted if true, then mark index corrupted.
1378@return error code or DB_SUCCESS */
1379static MY_ATTRIBUTE((warn_unused_result))
1380dberr_t
1381row_truncate_update_sys_tables_during_fix_up(
1382 const truncate_t& truncate,
1383 table_id_t new_table_id,
1384 ibool reserve_dict_mutex,
1385 bool mark_index_corrupted)
1386{
1387 trx_t* trx = trx_create();
1388
1389 trx_set_dict_operation(trx, TRX_DICT_OP_TABLE);
1390
1391 table_id_t table_id = row_truncate_get_trunc_table_id(truncate);
1392
1393 /* Step-1: Update the root-page-no */
1394
1395 dberr_t err;
1396
1397 err = truncate.update_root_page_no(
1398 trx, table_id, reserve_dict_mutex, mark_index_corrupted);
1399
1400 if (err != DB_SUCCESS) {
1401 return(err);
1402 }
1403
1404 /* Step-2: Update table-id. */
1405
1406 err = row_truncate_update_table_id(
1407 table_id, new_table_id, reserve_dict_mutex, trx);
1408
1409 if (err == DB_SUCCESS) {
1410 dict_mutex_enter_for_mysql();
1411
1412 /* Remove the table with old table_id from cache. */
1413 dict_table_t* old_table = dict_table_open_on_id(
1414 table_id, true, DICT_TABLE_OP_NORMAL);
1415
1416 if (old_table != NULL) {
1417 dict_table_close(old_table, true, false);
1418 dict_table_remove_from_cache(old_table);
1419 }
1420
1421 /* Open table with new table_id and set table as
1422 corrupted if it has FTS index. */
1423
1424 dict_table_t* table = dict_table_open_on_id(
1425 new_table_id, true, DICT_TABLE_OP_NORMAL);
1426 ut_ad(table->id == new_table_id);
1427
1428 bool has_internal_doc_id =
1429 dict_table_has_fts_index(table)
1430 || DICT_TF2_FLAG_IS_SET(
1431 table, DICT_TF2_FTS_HAS_DOC_ID);
1432
1433 if (has_internal_doc_id) {
1434 trx->dict_operation_lock_mode = RW_X_LATCH;
1435 fts_check_corrupt(table, trx);
1436 trx->dict_operation_lock_mode = 0;
1437 }
1438
1439 dict_table_close(table, true, false);
1440 dict_mutex_exit_for_mysql();
1441 }
1442
1443 trx_commit_for_mysql(trx);
1444 trx_free(trx);
1445
1446 return(err);
1447}
1448
1449/**
1450Truncate also results in assignment of new table id, update the system
1451SYSTEM TABLES with the new id.
1452@param table, table being truncated
1453@param new_id, new table id
1454@param has_internal_doc_id, has doc col (fts)
1455@param no_redo if true, turn-off redo logging
1456@param trx transaction handle
1457@return error code or DB_SUCCESS */
1458static MY_ATTRIBUTE((warn_unused_result))
1459dberr_t
1460row_truncate_update_system_tables(
1461 dict_table_t* table,
1462 table_id_t new_id,
1463 bool has_internal_doc_id,
1464 bool no_redo,
1465 trx_t* trx)
1466{
1467 dberr_t err = DB_SUCCESS;
1468
1469 ut_a(!table->is_temporary());
1470
1471 err = row_truncate_update_table_id(table->id, new_id, FALSE, trx);
1472
1473 DBUG_EXECUTE_IF("ib_err_trunc_during_sys_table_update",
1474 err = DB_ERROR;);
1475
1476 if (err != DB_SUCCESS) {
1477
1478 row_truncate_rollback(
1479 table, trx, new_id, has_internal_doc_id,
1480 no_redo, true, false);
1481
1482 ib::error() << "Unable to assign a new identifier to table "
1483 << table->name << " after truncating it. Marked the"
1484 " table as corrupted. In-memory representation is now"
1485 " different from the on-disk representation.";
1486 err = DB_ERROR;
1487 } else {
1488 /* Drop the old FTS index */
1489 if (has_internal_doc_id) {
1490
1491 ut_ad(trx_is_started(trx));
1492
1493 fts_drop_tables(trx, table);
1494
1495 DBUG_EXECUTE_IF("ib_truncate_crash_while_fts_cleanup",
1496 DBUG_SUICIDE(););
1497
1498 ut_ad(trx_is_started(trx));
1499 }
1500
1501 DBUG_EXECUTE_IF("ib_trunc_crash_after_fts_drop",
1502 log_buffer_flush_to_disk();
1503 os_thread_sleep(2000000);
1504 DBUG_SUICIDE(););
1505
1506 dict_table_change_id_in_cache(table, new_id);
1507
1508 /* Reset the Doc ID in cache to 0 */
1509 if (has_internal_doc_id && table->fts->cache != NULL) {
1510 table->fts->fts_status |= TABLE_DICT_LOCKED;
1511 fts_update_next_doc_id(trx, table, NULL, 0);
1512 fts_cache_clear(table->fts->cache);
1513 fts_cache_init(table->fts->cache);
1514 table->fts->fts_status &= uint(~TABLE_DICT_LOCKED);
1515 }
1516 }
1517
1518 return(err);
1519}
1520
1521/**
1522Do foreign key checks before starting TRUNCATE.
1523@param table table being truncated
1524@param trx transaction covering the truncate
1525@return DB_SUCCESS or error code */
1526static MY_ATTRIBUTE((warn_unused_result))
1527dberr_t
1528row_truncate_foreign_key_checks(
1529 const dict_table_t* table,
1530 const trx_t* trx)
1531{
1532 /* Check if the table is referenced by foreign key constraints from
1533 some other table (not the table itself) */
1534
1535 dict_foreign_set::iterator it
1536 = std::find_if(table->referenced_set.begin(),
1537 table->referenced_set.end(),
1538 dict_foreign_different_tables());
1539
1540 if (!srv_read_only_mode
1541 && it != table->referenced_set.end()
1542 && trx->check_foreigns) {
1543
1544 dict_foreign_t* foreign = *it;
1545
1546 FILE* ef = dict_foreign_err_file;
1547
1548 /* We only allow truncating a referenced table if
1549 FOREIGN_KEY_CHECKS is set to 0 */
1550
1551 mutex_enter(&dict_foreign_err_mutex);
1552
1553 rewind(ef);
1554
1555 ut_print_timestamp(ef);
1556
1557 fputs(" Cannot truncate table ", ef);
1558 ut_print_name(ef, trx, table->name.m_name);
1559 fputs(" by DROP+CREATE\n"
1560 "InnoDB: because it is referenced by ", ef);
1561 ut_print_name(ef, trx, foreign->foreign_table_name);
1562 putc('\n', ef);
1563
1564 mutex_exit(&dict_foreign_err_mutex);
1565
1566 return(DB_ERROR);
1567 }
1568
1569 ut_ad(!table->n_foreign_key_checks_running);
1570
1571 return(DB_SUCCESS);
1572}
1573
1574/**
1575Do some sanity checks before starting the actual TRUNCATE.
1576@param table table being truncated
1577@return DB_SUCCESS or error code */
1578static MY_ATTRIBUTE((warn_unused_result))
1579dberr_t
1580row_truncate_sanity_checks(
1581 const dict_table_t* table)
1582{
1583 if (!table->space) {
1584
1585 return(DB_TABLESPACE_DELETED);
1586
1587 } else if (!table->is_readable()) {
1588 if (!table->space) {
1589 return(DB_TABLESPACE_NOT_FOUND);
1590
1591 } else {
1592 return(DB_DECRYPTION_FAILED);
1593 }
1594 } else if (dict_table_is_corrupted(table)) {
1595
1596 return(DB_TABLE_CORRUPT);
1597 }
1598
1599 return(DB_SUCCESS);
1600}
1601
1602/** Reinitialize the original tablespace header with the same space id
1603for single tablespace
1604@param[in] table table belongs to tablespace
1605@param[in] size size in blocks
1606@param[in] trx Transaction covering truncate */
1607static void
1608fil_reinit_space_header_for_table(
1609 dict_table_t* table,
1610 ulint size,
1611 trx_t* trx)
1612{
1613 fil_space_t* space = table->space;
1614 ut_a(!is_system_tablespace(space->id));
1615 ut_ad(space->id == table->space_id);
1616
1617 /* Invalidate in the buffer pool all pages belonging
1618 to the tablespace. The buffer pool scan may take long
1619 time to complete, therefore we release dict_sys->mutex
1620 and the dict operation lock during the scan and aquire
1621 it again after the buffer pool scan.*/
1622
1623 /* Release the lock on the indexes too. So that
1624 they won't violate the latch ordering. */
1625 dict_table_x_unlock_indexes(table);
1626 row_mysql_unlock_data_dictionary(trx);
1627
1628 /* Lock the search latch in shared mode to prevent user
1629 from disabling AHI during the scan */
1630 btr_search_s_lock_all();
1631 DEBUG_SYNC_C("buffer_pool_scan");
1632 buf_LRU_flush_or_remove_pages(space->id, NULL);
1633 btr_search_s_unlock_all();
1634
1635 row_mysql_lock_data_dictionary(trx);
1636
1637 dict_table_x_lock_indexes(table);
1638
1639 /* Remove all insert buffer entries for the tablespace */
1640 ibuf_delete_for_discarded_space(space->id);
1641
1642 mtr_t mtr;
1643
1644 mtr.start();
1645 mtr.set_named_space(space);
1646 mtr_x_lock(&space->latch, &mtr);
1647
1648 ut_ad(UT_LIST_GET_LEN(space->chain) == 1);
1649 space->size = UT_LIST_GET_FIRST(space->chain)->size = size;
1650 fsp_header_init(space, size, &mtr);
1651
1652 mtr.commit();
1653}
1654
1655/**
1656Truncates a table for MySQL.
1657@param table table being truncated
1658@param trx transaction covering the truncate
1659@return error code or DB_SUCCESS */
1660dberr_t
1661row_truncate_table_for_mysql(
1662 dict_table_t* table,
1663 trx_t* trx)
1664{
1665 bool is_file_per_table = dict_table_is_file_per_table(table);
1666 dberr_t err;
1667 TruncateLogger* logger = NULL;
1668 ut_d(const fil_space_t* old_space = table->space);
1669
1670 /* Understanding the truncate flow.
1671
1672 Step-1: Perform intiial sanity check to ensure table can be truncated.
1673 This would include check for tablespace discard status, ibd file
1674 missing, etc ....
1675
1676 Step-3: Validate ownership of needed locks (Exclusive lock).
1677 Ownership will also ensure there is no active SQL queries, INSERT,
1678 SELECT, .....
1679
1680 Step-4: Stop all the background process associated with table.
1681
1682 Step-5: There are few foreign key related constraint under which
1683 we can't truncate table (due to referential integrity unless it is
1684 turned off). Ensure this condition is satisfied.
1685
1686 Step-6: Truncate operation can be rolled back in case of error
1687 till some point. Associate rollback segment to record undo log.
1688
1689 Step-7: Generate new table-id.
1690 Why we need new table-id ?
1691 Purge and rollback case: we assign a new table id for the table.
1692 Since purge and rollback look for the table based on the table id,
1693 they see the table as 'dropped' and discard their operations.
1694
1695 Step-8: Log information about tablespace which includes
1696 table and index information. If there is a crash in the next step
1697 then during recovery we will attempt to fixup the operation.
1698
1699 Step-9: Drop all indexes (this include freeing of the pages
1700 associated with them).
1701
1702 Step-10: Re-create new indexes.
1703
1704 Step-11: Update new table-id to in-memory cache (dictionary),
1705 on-disk (INNODB_SYS_TABLES). INNODB_SYS_INDEXES also needs to
1706 be updated to reflect updated root-page-no of new index created
1707 and updated table-id.
1708
1709 Step-12: Cleanup Stage. Reset auto-inc value to 1.
1710 Release all the locks.
1711 Commit the transaction. Update trx operation state.
1712
1713 Notes:
1714 - On error, log checkpoint is done followed writing of magic number to
1715 truncate log file. If servers crashes after truncate, fix-up action
1716 will not be applied.
1717
1718 - log checkpoint is done before starting truncate table to ensure
1719 that previous REDO log entries are not applied if current truncate
1720 crashes. Consider following use-case:
1721 - create table .... insert/load table .... truncate table (crash)
1722 - on restart table is restored .... truncate table (crash)
1723 - on restart (assuming default log checkpoint is not done) will have
1724 2 REDO log entries for same table. (Note 2 REDO log entries
1725 for different table is not an issue).
1726 For system-tablespace we can't truncate the tablespace so we need
1727 to initiate a local cleanup that involves dropping of indexes and
1728 re-creating them. If we apply stale entry we might end-up issuing
1729 drop on wrong indexes.
1730
1731 - Insert buffer: TRUNCATE TABLE is analogous to DROP TABLE,
1732 so we do not have to remove insert buffer records, as the
1733 insert buffer works at a low level. If a freed page is later
1734 reallocated, the allocator will remove the ibuf entries for
1735 it. When we prepare to truncate *.ibd files, we remove all entries
1736 for the table in the insert buffer tree. This is not strictly
1737 necessary, but we can free up some space in the system tablespace.
1738
1739 - Linear readahead and random readahead: we use the same
1740 method as in 3) to discard ongoing operations. (This is only
1741 relevant for TRUNCATE TABLE by TRUNCATE TABLESPACE.)
1742 Ensure that the table will be dropped by trx_rollback_active() in
1743 case of a crash.
1744 */
1745
1746 /*-----------------------------------------------------------------*/
1747 /* Step-1: Perform intiial sanity check to ensure table can be
1748 truncated. This would include check for tablespace discard status,
1749 ibd file missing, etc .... */
1750 err = row_truncate_sanity_checks(table);
1751 if (err != DB_SUCCESS) {
1752 return(err);
1753
1754 }
1755
1756 if (!table->is_temporary()) {
1757 trx_set_dict_operation(trx, TRX_DICT_OP_TABLE);
1758 }
1759
1760 /* Step-3: Validate ownership of needed locks (Exclusive lock).
1761 Ownership will also ensure there is no active SQL queries, INSERT,
1762 SELECT, .....*/
1763 trx->op_info = "truncating table";
1764 ut_a(trx->dict_operation_lock_mode == 0);
1765 row_mysql_lock_data_dictionary(trx);
1766 ut_ad(mutex_own(&dict_sys->mutex));
1767 ut_ad(rw_lock_own(dict_operation_lock, RW_LOCK_X));
1768
1769 /* Step-4: Stop all the background process associated with table. */
1770 dict_stats_wait_bg_to_stop_using_table(table, trx);
1771
1772 /* Step-5: There are few foreign key related constraint under which
1773 we can't truncate table (due to referential integrity unless it is
1774 turned off). Ensure this condition is satisfied. */
1775 ulint fsp_flags = ULINT_UNDEFINED;
1776 err = row_truncate_foreign_key_checks(table, trx);
1777 if (err != DB_SUCCESS) {
1778 trx_rollback_to_savepoint(trx, NULL);
1779 return(row_truncate_complete(
1780 table, trx, fsp_flags, logger, err));
1781 }
1782
1783 trx->table_id = table->id;
1784 trx_set_dict_operation(trx, TRX_DICT_OP_TABLE);
1785
1786 /* Step-6: Truncate operation can be rolled back in case of error
1787 till some point. Associate rollback segment to record undo log. */
1788 if (!table->is_temporary()) {
1789 mtr_t mtr;
1790 mtr.start();
1791 trx_undo_assign(trx, &err, &mtr);
1792 mtr.commit();
1793
1794 DBUG_EXECUTE_IF("ib_err_trunc_assigning_undo_log",
1795 err = DB_ERROR;);
1796 if (err != DB_SUCCESS) {
1797 trx_rollback_to_savepoint(trx, NULL);
1798 return(row_truncate_complete(
1799 table, trx, fsp_flags, logger, err));
1800 }
1801 }
1802
1803 /* Step-7: Generate new table-id.
1804 Why we need new table-id ?
1805 Purge and rollback: we assign a new table id for the
1806 table. Since purge and rollback look for the table based on
1807 the table id, they see the table as 'dropped' and discard
1808 their operations. */
1809 table_id_t new_id;
1810 dict_hdr_get_new_id(&new_id, NULL, NULL, table, false);
1811
1812 /* Check if table involves FTS index. */
1813 bool has_internal_doc_id =
1814 dict_table_has_fts_index(table)
1815 || DICT_TF2_FLAG_IS_SET(table, DICT_TF2_FTS_HAS_DOC_ID);
1816
1817 bool no_redo = is_file_per_table && !has_internal_doc_id;
1818
1819 /* Step-8: Log information about tablespace which includes
1820 table and index information. If there is a crash in the next step
1821 then during recovery we will attempt to fixup the operation. */
1822
1823 /* Lock all index trees for this table, as we will truncate
1824 the table/index and possibly change their metadata. All
1825 DML/DDL are blocked by table level X lock, with a few exceptions
1826 such as queries into information schema about the table,
1827 MySQL could try to access index stats for this kind of query,
1828 we need to use index locks to sync up */
1829 dict_table_x_lock_indexes(table);
1830
1831 if (!table->is_temporary()) {
1832 fsp_flags = table->space
1833 ? table->space->flags
1834 : ULINT_UNDEFINED;
1835
1836 if (is_file_per_table) {
1837 ut_ad(!table->is_temporary());
1838 ut_ad(dict_table_is_file_per_table(table));
1839
1840 dict_get_and_save_data_dir_path(table, true);
1841 err = table->space
1842 ? fil_prepare_for_truncate(table->space_id)
1843 : DB_TABLESPACE_NOT_FOUND;
1844
1845 DBUG_EXECUTE_IF("ib_err_trunc_preparing_for_truncate",
1846 err = DB_ERROR;);
1847
1848 if (err != DB_SUCCESS) {
1849 row_truncate_rollback(
1850 table, trx, new_id,
1851 has_internal_doc_id,
1852 no_redo, false, true);
1853 return(row_truncate_complete(
1854 table, trx, fsp_flags, logger, err));
1855 }
1856 } else {
1857 DBUG_EXECUTE_IF("ib_err_trunc_preparing_for_truncate",
1858 fsp_flags = ULINT_UNDEFINED;);
1859
1860 if (fsp_flags == ULINT_UNDEFINED) {
1861 row_truncate_rollback(
1862 table, trx, new_id,
1863 has_internal_doc_id,
1864 no_redo, false, true);
1865 return(row_truncate_complete(
1866 table, trx, fsp_flags,
1867 logger, DB_ERROR));
1868 }
1869 }
1870
1871 logger = UT_NEW_NOKEY(TruncateLogger(
1872 table, fsp_flags, new_id));
1873
1874 err = logger->init();
1875 if (err != DB_SUCCESS) {
1876 row_truncate_rollback(
1877 table, trx, new_id, has_internal_doc_id,
1878 no_redo, false, true);
1879 return(row_truncate_complete(
1880 table, trx, fsp_flags, logger, DB_ERROR));
1881
1882 }
1883
1884 err = SysIndexIterator().for_each(*logger);
1885 if (err != DB_SUCCESS) {
1886 row_truncate_rollback(
1887 table, trx, new_id, has_internal_doc_id,
1888 no_redo, false, true);
1889 return(row_truncate_complete(
1890 table, trx, fsp_flags, logger, DB_ERROR));
1891
1892 }
1893
1894 ut_ad(logger->debug());
1895
1896 err = logger->log();
1897
1898 if (err != DB_SUCCESS) {
1899 row_truncate_rollback(
1900 table, trx, new_id, has_internal_doc_id,
1901 no_redo, false, true);
1902 return(row_truncate_complete(
1903 table, trx, fsp_flags, logger, DB_ERROR));
1904 }
1905
1906 DBUG_EXECUTE_IF("ib_trunc_crash_after_redo_log_write_complete",
1907 log_buffer_flush_to_disk();
1908 os_thread_sleep(3000000);
1909 DBUG_SUICIDE(););
1910
1911 DropIndex dropIndex(table, no_redo);
1912
1913 err = SysIndexIterator().for_each(dropIndex);
1914
1915 if (err != DB_SUCCESS) {
1916
1917 row_truncate_rollback(
1918 table, trx, new_id, has_internal_doc_id,
1919 no_redo, true, true);
1920
1921 return(row_truncate_complete(
1922 table, trx, fsp_flags, logger, err));
1923 }
1924
1925 dict_table_get_first_index(table)->remove_instant();
1926 } else {
1927 ut_ad(!table->is_instant());
1928 ut_ad(table->space == fil_system.temp_space);
1929 bool fail = false;
1930 for (dict_index_t* index = UT_LIST_GET_FIRST(table->indexes);
1931 index != NULL;
1932 index = UT_LIST_GET_NEXT(indexes, index)) {
1933 if (index->page != FIL_NULL) {
1934 btr_free(page_id_t(SRV_TMP_SPACE_ID,
1935 index->page),
1936 univ_page_size);
1937 }
1938
1939 mtr_t mtr;
1940 mtr.start();
1941 mtr.set_log_mode(MTR_LOG_NO_REDO);
1942 index->page = btr_create(
1943 index->type, table->space, index->id, index,
1944 NULL, &mtr);
1945 DBUG_EXECUTE_IF("ib_err_trunc_temp_recreate_index",
1946 index->page = FIL_NULL;);
1947 mtr.commit();
1948 if (index->page == FIL_NULL) {
1949 fail = true;
1950 break;
1951 }
1952 }
1953 if (fail) {
1954 for (dict_index_t* index = UT_LIST_GET_FIRST(
1955 table->indexes);
1956 index != NULL;
1957 index = UT_LIST_GET_NEXT(indexes, index)) {
1958 if (index->page != FIL_NULL) {
1959 btr_free(page_id_t(SRV_TMP_SPACE_ID,
1960 index->page),
1961 univ_page_size);
1962 index->page = FIL_NULL;
1963 }
1964 }
1965 }
1966
1967 table->corrupted = fail;
1968 if (fail) {
1969 return row_truncate_complete(
1970 table, trx, fsp_flags, logger, DB_ERROR);
1971 }
1972
1973 DBUG_EXECUTE_IF(
1974 "ib_trunc_crash_during_drop_index_temp_table",
1975 log_buffer_flush_to_disk();
1976 DBUG_SUICIDE(););
1977 }
1978
1979 if (is_file_per_table && fsp_flags != ULINT_UNDEFINED) {
1980 /* A single-table tablespace has initially
1981 FIL_IBD_FILE_INITIAL_SIZE number of pages allocated and an
1982 extra page is allocated for each of the indexes present. But in
1983 the case of clust index 2 pages are allocated and as one is
1984 covered in the calculation as part of table->indexes.count we
1985 take care of the other page by adding 1. */
1986 ulint space_size = table->indexes.count +
1987 FIL_IBD_FILE_INITIAL_SIZE + 1;
1988
1989 if (has_internal_doc_id) {
1990 /* Since aux tables are created for fts indexes and
1991 they use seperate tablespaces. */
1992 space_size -= ib_vector_size(table->fts->indexes);
1993 }
1994
1995 fil_reinit_space_header_for_table(table, space_size, trx);
1996 }
1997
1998 DBUG_EXECUTE_IF("ib_trunc_crash_with_intermediate_log_checkpoint",
1999 log_buffer_flush_to_disk();
2000 os_thread_sleep(2000000);
2001 log_checkpoint(TRUE, TRUE);
2002 os_thread_sleep(1000000);
2003 DBUG_SUICIDE(););
2004
2005 DBUG_EXECUTE_IF("ib_trunc_crash_drop_reinit_done_create_to_start",
2006 log_buffer_flush_to_disk();
2007 os_thread_sleep(2000000);
2008 DBUG_SUICIDE(););
2009
2010 /* Step-10: Re-create new indexes. */
2011 if (!table->is_temporary()) {
2012
2013 CreateIndex createIndex(table, no_redo);
2014
2015 err = SysIndexIterator().for_each(createIndex);
2016
2017 if (err != DB_SUCCESS) {
2018
2019 row_truncate_rollback(
2020 table, trx, new_id, has_internal_doc_id,
2021 no_redo, true, true);
2022
2023 return(row_truncate_complete(
2024 table, trx, fsp_flags, logger, err));
2025 }
2026 }
2027
2028 /* Done with index truncation, release index tree locks,
2029 subsequent work relates to table level metadata change */
2030 dict_table_x_unlock_indexes(table);
2031
2032 if (has_internal_doc_id) {
2033
2034 err = row_truncate_fts(table, new_id, trx);
2035
2036 if (err != DB_SUCCESS) {
2037
2038 row_truncate_rollback(
2039 table, trx, new_id, has_internal_doc_id,
2040 no_redo, true, false);
2041
2042 return(row_truncate_complete(
2043 table, trx, fsp_flags, logger, err));
2044 }
2045 }
2046
2047 /* Step-11: Update new table-id to in-memory cache (dictionary),
2048 on-disk (INNODB_SYS_TABLES). INNODB_SYS_INDEXES also needs to
2049 be updated to reflect updated root-page-no of new index created
2050 and updated table-id. */
2051 if (table->is_temporary()) {
2052
2053 dict_table_change_id_in_cache(table, new_id);
2054 err = DB_SUCCESS;
2055
2056 } else {
2057
2058 /* If this fails then we are in an inconsistent state and
2059 the results are undefined. */
2060 ut_ad(old_space == table->space);
2061
2062 err = row_truncate_update_system_tables(
2063 table, new_id, has_internal_doc_id, no_redo, trx);
2064
2065 if (err != DB_SUCCESS) {
2066 return(row_truncate_complete(
2067 table, trx, fsp_flags, logger, err));
2068 }
2069 }
2070
2071 DBUG_EXECUTE_IF("ib_trunc_crash_on_updating_dict_sys_info",
2072 log_buffer_flush_to_disk();
2073 os_thread_sleep(2000000);
2074 DBUG_SUICIDE(););
2075
2076 /* Step-12: Cleanup Stage. Reset auto-inc value to 1.
2077 Release all the locks.
2078 Commit the transaction. Update trx operation state. */
2079 dict_table_autoinc_lock(table);
2080 dict_table_autoinc_initialize(table, 1);
2081 dict_table_autoinc_unlock(table);
2082
2083 if (trx_is_started(trx)) {
2084
2085 trx_commit_for_mysql(trx);
2086 }
2087
2088 ut_ad(!table->is_instant());
2089
2090 return(row_truncate_complete(table, trx, fsp_flags, logger, err));
2091}
2092
2093/********************************************************//**
2094Recreates table indexes by applying
2095TRUNCATE log record during recovery.
2096@return DB_SUCCESS or error code */
2097static
2098dberr_t
2099fil_recreate_table(
2100/*===============*/
2101 ulint format_flags, /*!< in: page format */
2102 const char* name, /*!< in: table name */
2103 truncate_t& truncate) /*!< in: The information of
2104 TRUNCATE log record */
2105{
2106 ut_ad(!truncate_t::s_fix_up_active);
2107 truncate_t::s_fix_up_active = true;
2108
2109 /* Step-1: Scan for active indexes from REDO logs and drop
2110 all the indexes using low level function that take root_page_no
2111 and space-id. */
2112 truncate.drop_indexes(fil_system.sys_space);
2113
2114 /* Step-2: Scan for active indexes and re-create them. */
2115 dberr_t err = truncate.create_indexes(
2116 name, fil_system.sys_space, format_flags);
2117 if (err != DB_SUCCESS) {
2118 ib::info() << "Recovery failed for TRUNCATE TABLE '"
2119 << name << "' within the system tablespace";
2120 }
2121
2122 truncate_t::s_fix_up_active = false;
2123
2124 return(err);
2125}
2126
2127/********************************************************//**
2128Recreates the tablespace and table indexes by applying
2129TRUNCATE log record during recovery.
2130@return DB_SUCCESS or error code */
2131static
2132dberr_t
2133fil_recreate_tablespace(
2134/*====================*/
2135 ulint space_id, /*!< in: space id */
2136 ulint format_flags, /*!< in: page format */
2137 ulint flags, /*!< in: tablespace flags */
2138 const char* name, /*!< in: table name */
2139 truncate_t& truncate, /*!< in: The information of
2140 TRUNCATE log record */
2141 lsn_t recv_lsn) /*!< in: the end LSN of
2142 the log record */
2143{
2144 dberr_t err = DB_SUCCESS;
2145 mtr_t mtr;
2146
2147 ut_ad(!truncate_t::s_fix_up_active);
2148 truncate_t::s_fix_up_active = true;
2149
2150 /* Step-1: Invalidate buffer pool pages belonging to the tablespace
2151 to re-create. */
2152 buf_LRU_flush_or_remove_pages(space_id, NULL);
2153
2154 /* Remove all insert buffer entries for the tablespace */
2155 ibuf_delete_for_discarded_space(space_id);
2156
2157 /* Step-2: truncate tablespace (reset the size back to original or
2158 default size) of tablespace. */
2159 err = truncate.truncate(
2160 space_id, truncate.get_dir_path(), name, flags, true);
2161
2162 if (err != DB_SUCCESS) {
2163
2164 ib::info() << "Cannot access .ibd file for table '"
2165 << name << "' with tablespace " << space_id
2166 << " while truncating";
2167 return(DB_ERROR);
2168 }
2169
2170 fil_space_t* space = fil_space_acquire(space_id);
2171 if (!space) {
2172 ib::info() << "Missing .ibd file for table '" << name
2173 << "' with tablespace " << space_id;
2174 return(DB_ERROR);
2175 }
2176
2177 const page_size_t page_size(space->flags);
2178
2179 /* Step-3: Initialize Header. */
2180 if (page_size.is_compressed()) {
2181 byte* buf;
2182 page_t* page;
2183
2184 buf = static_cast<byte*>(
2185 ut_zalloc_nokey(3U << srv_page_size_shift));
2186
2187 /* Align the memory for file i/o */
2188 page = static_cast<byte*>(ut_align(buf, srv_page_size));
2189
2190 flags |= FSP_FLAGS_PAGE_SSIZE();
2191
2192 fsp_header_init_fields(page, space_id, flags);
2193
2194 mach_write_to_4(
2195 page + FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID, space_id);
2196
2197 page_zip_des_t page_zip;
2198 page_zip_set_size(&page_zip, page_size.physical());
2199 page_zip.data = page + srv_page_size;
2200
2201#ifdef UNIV_DEBUG
2202 page_zip.m_start =
2203#endif /* UNIV_DEBUG */
2204 page_zip.m_end = page_zip.m_nonempty = page_zip.n_blobs = 0;
2205 buf_flush_init_for_writing(NULL, page, &page_zip, 0);
2206
2207 err = fil_io(IORequestWrite, true, page_id_t(space_id, 0),
2208 page_size, 0, page_size.physical(), page_zip.data,
2209 NULL);
2210
2211 ut_free(buf);
2212
2213 if (err != DB_SUCCESS) {
2214 ib::info() << "Failed to clean header of the"
2215 " table '" << name << "' with tablespace "
2216 << space_id;
2217 goto func_exit;
2218 }
2219 }
2220
2221 mtr_start(&mtr);
2222 /* Don't log the operation while fixing up table truncate operation
2223 as crash at this level can still be sustained with recovery restarting
2224 from last checkpoint. */
2225 mtr_set_log_mode(&mtr, MTR_LOG_NO_REDO);
2226
2227 /* Initialize the first extent descriptor page and
2228 the second bitmap page for the new tablespace. */
2229 fsp_header_init(space, FIL_IBD_FILE_INITIAL_SIZE, &mtr);
2230 mtr_commit(&mtr);
2231
2232 /* Step-4: Re-Create Indexes to newly re-created tablespace.
2233 This operation will restore tablespace back to what it was
2234 when it was created during CREATE TABLE. */
2235 err = truncate.create_indexes(name, space, format_flags);
2236 if (err != DB_SUCCESS) {
2237 goto func_exit;
2238 }
2239
2240 /* Step-5: Write new created pages into ibd file handle and
2241 flush it to disk for the tablespace, in case i/o-handler thread
2242 deletes the bitmap page from buffer. */
2243 mtr_start(&mtr);
2244
2245 mtr_set_log_mode(&mtr, MTR_LOG_NO_REDO);
2246
2247 for (ulint page_no = 0;
2248 page_no < UT_LIST_GET_FIRST(space->chain)->size; ++page_no) {
2249
2250 const page_id_t cur_page_id(space_id, page_no);
2251
2252 buf_block_t* block = buf_page_get(cur_page_id, page_size,
2253 RW_X_LATCH, &mtr);
2254
2255 byte* page = buf_block_get_frame(block);
2256
2257 if (!FSP_FLAGS_GET_ZIP_SSIZE(flags)) {
2258 ut_ad(!page_size.is_compressed());
2259
2260 buf_flush_init_for_writing(
2261 block, page, NULL, recv_lsn);
2262
2263 err = fil_io(IORequestWrite, true, cur_page_id,
2264 page_size, 0, srv_page_size, page, NULL);
2265 } else {
2266 ut_ad(page_size.is_compressed());
2267
2268 /* We don't want to rewrite empty pages. */
2269
2270 if (fil_page_get_type(page) != 0) {
2271 page_zip_des_t* page_zip =
2272 buf_block_get_page_zip(block);
2273
2274 buf_flush_init_for_writing(
2275 block, page, page_zip, recv_lsn);
2276
2277 err = fil_io(IORequestWrite, true,
2278 cur_page_id,
2279 page_size, 0,
2280 page_size.physical(),
2281 page_zip->data, NULL);
2282 } else {
2283#ifdef UNIV_DEBUG
2284 const byte* data = block->page.zip.data;
2285
2286 /* Make sure that the page is really empty */
2287 for (ulint i = 0;
2288 i < page_size.physical();
2289 ++i) {
2290
2291 ut_a(data[i] == 0);
2292 }
2293#endif /* UNIV_DEBUG */
2294 }
2295 }
2296
2297 if (err != DB_SUCCESS) {
2298 ib::info() << "Cannot write page " << page_no
2299 << " into a .ibd file for table '"
2300 << name << "' with tablespace " << space_id;
2301 }
2302 }
2303
2304 mtr_commit(&mtr);
2305
2306 truncate_t::s_fix_up_active = false;
2307func_exit:
2308 space->release();
2309 return(err);
2310}
2311
2312/**
2313Fix the table truncate by applying information parsed from TRUNCATE log.
2314Fix-up includes re-creating table (drop and re-create indexes)
2315@return error code or DB_SUCCESS */
2316dberr_t
2317truncate_t::fixup_tables_in_system_tablespace()
2318{
2319 dberr_t err = DB_SUCCESS;
2320
2321 /* Using the info cached during REDO log scan phase fix the
2322 table truncate. */
2323
2324 for (tables_t::iterator it = s_tables.begin();
2325 it != s_tables.end();) {
2326
2327 if ((*it)->m_space_id == TRX_SYS_SPACE) {
2328 /* Step-1: Drop and re-create indexes. */
2329 ib::info() << "Completing truncate for table with "
2330 "id (" << (*it)->m_old_table_id << ") "
2331 "residing in the system tablespace.";
2332
2333 err = fil_recreate_table(
2334 (*it)->m_format_flags,
2335 (*it)->m_tablename,
2336 **it);
2337
2338 /* Step-2: Update the SYS_XXXX tables to reflect
2339 this new table_id and root_page_no. */
2340 table_id_t new_id;
2341
2342 dict_hdr_get_new_id(&new_id, NULL, NULL, NULL, true);
2343
2344 err = row_truncate_update_sys_tables_during_fix_up(
2345 **it, new_id, TRUE,
2346 (err == DB_SUCCESS) ? false : true);
2347
2348 if (err != DB_SUCCESS) {
2349 break;
2350 }
2351
2352 os_file_delete(
2353 innodb_log_file_key, (*it)->m_log_file_name);
2354 UT_DELETE(*it);
2355 it = s_tables.erase(it);
2356 } else {
2357 ++it;
2358 }
2359 }
2360
2361 /* Also clear the map used to track tablespace truncated. */
2362 s_truncated_tables.clear();
2363
2364 return(err);
2365}
2366
2367/**
2368Fix the table truncate by applying information parsed from TRUNCATE log.
2369Fix-up includes re-creating tablespace.
2370@return error code or DB_SUCCESS */
2371dberr_t
2372truncate_t::fixup_tables_in_non_system_tablespace()
2373{
2374 dberr_t err = DB_SUCCESS;
2375
2376 /* Using the info cached during REDO log scan phase fix the
2377 table truncate. */
2378 tables_t::iterator end = s_tables.end();
2379
2380 for (tables_t::iterator it = s_tables.begin(); it != end; ++it) {
2381
2382 /* All tables in the system tablespace have already been
2383 done and erased from this list. */
2384 ut_a((*it)->m_space_id != TRX_SYS_SPACE);
2385
2386 /* Drop tablespace, drop indexes and re-create indexes. */
2387
2388 ib::info() << "Completing truncate for table with "
2389 "id (" << (*it)->m_old_table_id << ") "
2390 "residing in file-per-table tablespace with "
2391 "id (" << (*it)->m_space_id << ")";
2392
2393 fil_space_t* space = fil_space_get((*it)->m_space_id);
2394
2395 if (!space) {
2396 /* Create the database directory for name,
2397 if it does not exist yet */
2398 fil_create_directory_for_tablename(
2399 (*it)->m_tablename);
2400
2401 space = fil_ibd_create((*it)->m_space_id,
2402 (*it)->m_tablename,
2403 (*it)->m_dir_path,
2404 (*it)->m_tablespace_flags,
2405 FIL_IBD_FILE_INITIAL_SIZE,
2406 (*it)->m_encryption,
2407 (*it)->m_key_id, &err);
2408 if (!space) {
2409 /* If checkpoint is not yet done
2410 and table is dropped and then we might
2411 still have REDO entries for this table
2412 which are INVALID. Ignore them. */
2413 ib::warn() << "Failed to create"
2414 " tablespace for "
2415 << (*it)->m_space_id
2416 << " space-id";
2417 err = DB_ERROR;
2418 break;
2419 }
2420 }
2421
2422 err = fil_recreate_tablespace(
2423 (*it)->m_space_id,
2424 (*it)->m_format_flags,
2425 (*it)->m_tablespace_flags,
2426 (*it)->m_tablename,
2427 **it, log_get_lsn());
2428
2429 /* Step-2: Update the SYS_XXXX tables to reflect new
2430 table-id and root_page_no. */
2431 table_id_t new_id;
2432
2433 dict_hdr_get_new_id(&new_id, NULL, NULL, NULL, true);
2434
2435 err = row_truncate_update_sys_tables_during_fix_up(
2436 **it, new_id, TRUE, (err == DB_SUCCESS) ? false : true);
2437
2438 if (err != DB_SUCCESS) {
2439 break;
2440 }
2441 }
2442
2443 if (err == DB_SUCCESS && s_tables.size() > 0) {
2444
2445 log_make_checkpoint_at(LSN_MAX, TRUE);
2446 }
2447
2448 for (ulint i = 0; i < s_tables.size(); ++i) {
2449 os_file_delete(
2450 innodb_log_file_key, s_tables[i]->m_log_file_name);
2451 UT_DELETE(s_tables[i]);
2452 }
2453
2454 s_tables.clear();
2455
2456 return(err);
2457}
2458
2459/**
2460Constructor
2461
2462@param old_table_id old table id assigned to table before truncate
2463@param new_table_id new table id that will be assigned to table
2464 after truncate
2465@param dir_path directory path */
2466
2467truncate_t::truncate_t(
2468 table_id_t old_table_id,
2469 table_id_t new_table_id,
2470 const char* dir_path)
2471 :
2472 m_space_id(),
2473 m_old_table_id(old_table_id),
2474 m_new_table_id(new_table_id),
2475 m_dir_path(),
2476 m_tablename(),
2477 m_tablespace_flags(),
2478 m_format_flags(),
2479 m_indexes(),
2480 m_log_lsn(),
2481 m_log_file_name(),
2482 /* JAN: TODO: Encryption */
2483 m_encryption(FIL_ENCRYPTION_DEFAULT),
2484 m_key_id(FIL_DEFAULT_ENCRYPTION_KEY)
2485{
2486 if (dir_path != NULL) {
2487 m_dir_path = mem_strdup(dir_path);
2488 }
2489}
2490
2491/**
2492Consturctor
2493
2494@param log_file_name parse the log file during recovery to populate
2495 information related to table to truncate */
2496truncate_t::truncate_t(
2497 const char* log_file_name)
2498 :
2499 m_space_id(),
2500 m_old_table_id(),
2501 m_new_table_id(),
2502 m_dir_path(),
2503 m_tablename(),
2504 m_tablespace_flags(),
2505 m_format_flags(),
2506 m_indexes(),
2507 m_log_lsn(),
2508 m_log_file_name(),
2509 /* JAN: TODO: Encryption */
2510 m_encryption(FIL_ENCRYPTION_DEFAULT),
2511 m_key_id(FIL_DEFAULT_ENCRYPTION_KEY)
2512
2513{
2514 m_log_file_name = mem_strdup(log_file_name);
2515 if (m_log_file_name == NULL) {
2516 ib::fatal() << "Failed creating truncate_t; out of memory";
2517 }
2518}
2519
2520/** Constructor */
2521
2522truncate_t::index_t::index_t()
2523 :
2524 m_id(),
2525 m_type(),
2526 m_root_page_no(FIL_NULL),
2527 m_new_root_page_no(FIL_NULL),
2528 m_n_fields(),
2529 m_trx_id_pos(ULINT_UNDEFINED),
2530 m_fields()
2531{
2532 /* Do nothing */
2533}
2534
2535/** Destructor */
2536
2537truncate_t::~truncate_t()
2538{
2539 if (m_dir_path != NULL) {
2540 ut_free(m_dir_path);
2541 m_dir_path = NULL;
2542 }
2543
2544 if (m_tablename != NULL) {
2545 ut_free(m_tablename);
2546 m_tablename = NULL;
2547 }
2548
2549 if (m_log_file_name != NULL) {
2550 ut_free(m_log_file_name);
2551 m_log_file_name = NULL;
2552 }
2553
2554 m_indexes.clear();
2555}
2556
2557/**
2558@return number of indexes parsed from the log record */
2559
2560size_t
2561truncate_t::indexes() const
2562{
2563 return(m_indexes.size());
2564}
2565
2566/**
2567Update root page number in SYS_XXXX tables.
2568
2569@param trx transaction object
2570@param table_id table id for which information needs to
2571 be updated.
2572@param reserve_dict_mutex if TRUE, acquire/release
2573 dict_sys->mutex around call to pars_sql.
2574@param mark_index_corrupted if true, then mark index corrupted.
2575@return DB_SUCCESS or error code */
2576
2577dberr_t
2578truncate_t::update_root_page_no(
2579 trx_t* trx,
2580 table_id_t table_id,
2581 ibool reserve_dict_mutex,
2582 bool mark_index_corrupted) const
2583{
2584 indexes_t::const_iterator end = m_indexes.end();
2585
2586 dberr_t err = DB_SUCCESS;
2587
2588 for (indexes_t::const_iterator it = m_indexes.begin();
2589 it != end;
2590 ++it) {
2591
2592 pars_info_t* info = pars_info_create();
2593
2594 pars_info_add_int4_literal(
2595 info, "page_no", it->m_new_root_page_no);
2596
2597 pars_info_add_ull_literal(info, "table_id", table_id);
2598
2599 pars_info_add_ull_literal(
2600 info, "index_id",
2601 (mark_index_corrupted ? IB_ID_MAX : it->m_id));
2602
2603 err = que_eval_sql(
2604 info,
2605 "PROCEDURE RENUMBER_IDX_PAGE_NO_PROC () IS\n"
2606 "BEGIN\n"
2607 "UPDATE SYS_INDEXES"
2608 " SET PAGE_NO = :page_no\n"
2609 " WHERE TABLE_ID = :table_id"
2610 " AND ID = :index_id;\n"
2611 "END;\n", reserve_dict_mutex, trx);
2612
2613 if (err != DB_SUCCESS) {
2614 break;
2615 }
2616 }
2617
2618 return(err);
2619}
2620
2621/**
2622Check whether a tablespace was truncated during recovery
2623@param space_id tablespace id to check
2624@return true if the tablespace was truncated */
2625
2626bool
2627truncate_t::is_tablespace_truncated(ulint space_id)
2628{
2629 tables_t::iterator end = s_tables.end();
2630
2631 for (tables_t::iterator it = s_tables.begin(); it != end; ++it) {
2632
2633 if ((*it)->m_space_id == space_id) {
2634
2635 return(true);
2636 }
2637 }
2638
2639 return(false);
2640}
2641
2642/** Was tablespace truncated (on crash before checkpoint).
2643If the MLOG_TRUNCATE redo-record is still available then tablespace
2644was truncated and checkpoint is yet to happen.
2645@param[in] space_id tablespace id to check.
2646@return true if tablespace is was truncated. */
2647bool
2648truncate_t::was_tablespace_truncated(ulint space_id)
2649{
2650 return(s_truncated_tables.find(space_id) != s_truncated_tables.end());
2651}
2652
2653/** Get the lsn associated with space.
2654@param[in] space_id tablespace id to check.
2655@return associated lsn. */
2656lsn_t
2657truncate_t::get_truncated_tablespace_init_lsn(ulint space_id)
2658{
2659 ut_ad(was_tablespace_truncated(space_id));
2660
2661 return(s_truncated_tables.find(space_id)->second);
2662}
2663
2664/**
2665Parses log record during recovery
2666@param start_ptr buffer containing log body to parse
2667@param end_ptr buffer end
2668
2669@return DB_SUCCESS or error code */
2670
2671dberr_t
2672truncate_t::parse(
2673 byte* start_ptr,
2674 const byte* end_ptr)
2675{
2676 /* Parse lsn, space-id, format-flags and tablespace-flags. */
2677 if (end_ptr < start_ptr + (8 + 4 + 4 + 4)) {
2678 return(DB_FAIL);
2679 }
2680
2681 m_log_lsn = mach_read_from_8(start_ptr);
2682 start_ptr += 8;
2683
2684 m_space_id = mach_read_from_4(start_ptr);
2685 start_ptr += 4;
2686
2687 m_format_flags = mach_read_from_4(start_ptr);
2688 start_ptr += 4;
2689
2690 m_tablespace_flags = mach_read_from_4(start_ptr);
2691 start_ptr += 4;
2692
2693 /* Parse table-name. */
2694 if (end_ptr < start_ptr + (2)) {
2695 return(DB_FAIL);
2696 }
2697
2698 ulint n_tablename_len = mach_read_from_2(start_ptr);
2699 start_ptr += 2;
2700
2701 if (n_tablename_len > 0) {
2702 if (end_ptr < start_ptr + n_tablename_len) {
2703 return(DB_FAIL);
2704 }
2705 m_tablename = mem_strdup(reinterpret_cast<char*>(start_ptr));
2706 ut_ad(m_tablename[n_tablename_len - 1] == 0);
2707 start_ptr += n_tablename_len;
2708 }
2709
2710
2711 /* Parse and read old/new table-id, number of indexes */
2712 if (end_ptr < start_ptr + (8 + 8 + 2 + 2)) {
2713 return(DB_FAIL);
2714 }
2715
2716 ut_ad(m_indexes.empty());
2717
2718 m_old_table_id = mach_read_from_8(start_ptr);
2719 start_ptr += 8;
2720
2721 m_new_table_id = mach_read_from_8(start_ptr);
2722 start_ptr += 8;
2723
2724 ulint n_indexes = mach_read_from_2(start_ptr);
2725 start_ptr += 2;
2726
2727 /* Parse the remote directory from TRUNCATE log record */
2728 {
2729 ulint n_tabledirpath_len = mach_read_from_2(start_ptr);
2730 start_ptr += 2;
2731
2732 if (end_ptr < start_ptr + n_tabledirpath_len) {
2733 return(DB_FAIL);
2734 }
2735
2736 if (n_tabledirpath_len > 0) {
2737
2738 m_dir_path = mem_strdup(reinterpret_cast<char*>(start_ptr));
2739 ut_ad(m_dir_path[n_tabledirpath_len - 1] == 0);
2740 start_ptr += n_tabledirpath_len;
2741 }
2742 }
2743
2744 /* Parse index ids and types from TRUNCATE log record */
2745 for (ulint i = 0; i < n_indexes; ++i) {
2746 index_t index;
2747
2748 if (end_ptr < start_ptr + (8 + 4 + 4 + 4)) {
2749 return(DB_FAIL);
2750 }
2751
2752 index.m_id = mach_read_from_8(start_ptr);
2753 start_ptr += 8;
2754
2755 index.m_type = mach_read_from_4(start_ptr);
2756 start_ptr += 4;
2757
2758 index.m_root_page_no = mach_read_from_4(start_ptr);
2759 start_ptr += 4;
2760
2761 index.m_trx_id_pos = mach_read_from_4(start_ptr);
2762 start_ptr += 4;
2763
2764 if (!(index.m_type & DICT_FTS)) {
2765 m_indexes.push_back(index);
2766 }
2767 }
2768
2769 ut_ad(!m_indexes.empty());
2770
2771 if (FSP_FLAGS_GET_ZIP_SSIZE(m_tablespace_flags)) {
2772
2773 /* Parse the number of index fields from TRUNCATE log record */
2774 for (ulint i = 0; i < m_indexes.size(); ++i) {
2775
2776 if (end_ptr < start_ptr + (2 + 2)) {
2777 return(DB_FAIL);
2778 }
2779
2780 m_indexes[i].m_n_fields = mach_read_from_2(start_ptr);
2781 start_ptr += 2;
2782
2783 ulint len = mach_read_from_2(start_ptr);
2784 start_ptr += 2;
2785
2786 if (end_ptr < start_ptr + len) {
2787 return(DB_FAIL);
2788 }
2789
2790 index_t& index = m_indexes[i];
2791
2792 /* Should be NUL terminated. */
2793 ut_ad((start_ptr)[len - 1] == 0);
2794
2795 index_t::fields_t::iterator end;
2796
2797 end = index.m_fields.end();
2798
2799 index.m_fields.insert(
2800 end, start_ptr, &(start_ptr)[len]);
2801
2802 start_ptr += len;
2803 }
2804 }
2805
2806 return(DB_SUCCESS);
2807}
2808
2809/** Parse log record from REDO log file during recovery.
2810@param[in,out] start_ptr buffer containing log body to parse
2811@param[in] end_ptr buffer end
2812@param[in] space_id tablespace identifier
2813@return parsed upto or NULL. */
2814byte*
2815truncate_t::parse_redo_entry(
2816 byte* start_ptr,
2817 const byte* end_ptr,
2818 ulint space_id)
2819{
2820 lsn_t lsn;
2821
2822 /* Parse space-id, lsn */
2823 if (end_ptr < (start_ptr + 8)) {
2824 return(NULL);
2825 }
2826
2827 lsn = mach_read_from_8(start_ptr);
2828 start_ptr += 8;
2829
2830 /* Tablespace can't exist in both state.
2831 (scheduled-for-truncate, was-truncated). */
2832 if (!is_tablespace_truncated(space_id)) {
2833
2834 truncated_tables_t::iterator it =
2835 s_truncated_tables.find(space_id);
2836
2837 if (it == s_truncated_tables.end()) {
2838 s_truncated_tables.insert(
2839 std::pair<ulint, lsn_t>(space_id, lsn));
2840 } else {
2841 it->second = lsn;
2842 }
2843 }
2844
2845 return(start_ptr);
2846}
2847
2848/**
2849Set the truncate log values for a compressed table.
2850@param index index from which recreate infoormation needs to be extracted
2851@return DB_SUCCESS or error code */
2852
2853dberr_t
2854truncate_t::index_t::set(
2855 const dict_index_t* index)
2856{
2857 /* Get trx-id column position (set only for clustered index) */
2858 if (dict_index_is_clust(index)) {
2859 m_trx_id_pos = dict_index_get_sys_col_pos(index, DATA_TRX_ID);
2860 ut_ad(m_trx_id_pos > 0);
2861 ut_ad(m_trx_id_pos != ULINT_UNDEFINED);
2862 } else {
2863 m_trx_id_pos = 0;
2864 }
2865
2866 /* Original logic set this field differently if page is not leaf.
2867 For truncate case this being first page to get created it is
2868 always a leaf page and so we don't need that condition here. */
2869 m_n_fields = dict_index_get_n_fields(index);
2870
2871 /* See requirements of page_zip_fields_encode for size. */
2872 ulint encoded_buf_size = (m_n_fields + 1) * 2;
2873 byte* encoded_buf = UT_NEW_ARRAY_NOKEY(byte, encoded_buf_size);
2874
2875 if (encoded_buf == NULL) {
2876 return(DB_OUT_OF_MEMORY);
2877 }
2878
2879 ulint len = page_zip_fields_encode(
2880 m_n_fields, index, m_trx_id_pos, encoded_buf);
2881 ut_a(len <= encoded_buf_size);
2882
2883 /* Append the encoded fields data. */
2884 m_fields.insert(m_fields.end(), &encoded_buf[0], &encoded_buf[len]);
2885
2886 /* NUL terminate the encoded data */
2887 m_fields.push_back(0);
2888
2889 UT_DELETE_ARRAY(encoded_buf);
2890
2891 return(DB_SUCCESS);
2892}
2893
2894/** Create an index for a table.
2895@param[in] table_name table name, for which to create
2896the index
2897@param[in] space tablespace
2898@param[in] page_size page size of the .ibd file
2899@param[in] index_type type of index to truncate
2900@param[in] index_id id of index to truncate
2901@param[in] btr_redo_create_info control info for ::btr_create()
2902@param[in,out] mtr mini-transaction covering the
2903create index
2904@return root page no or FIL_NULL on failure */
2905inline ulint
2906truncate_t::create_index(
2907 const char* table_name,
2908 fil_space_t* space,
2909 ulint index_type,
2910 index_id_t index_id,
2911 const btr_create_t& btr_redo_create_info,
2912 mtr_t* mtr) const
2913{
2914 ulint root_page_no = btr_create(
2915 index_type, space, index_id,
2916 NULL, &btr_redo_create_info, mtr);
2917
2918 if (root_page_no == FIL_NULL) {
2919
2920 ib::info() << "innodb_force_recovery was set to "
2921 << srv_force_recovery << ". Continuing crash recovery"
2922 " even though we failed to create index " << index_id
2923 << " for compressed table '" << table_name << "' with"
2924 " file " << space->chain.start->name;
2925 }
2926
2927 return(root_page_no);
2928}
2929
2930/** Check if index has been modified since TRUNCATE log snapshot
2931was recorded.
2932@param[in] space tablespace
2933@param[in] root_page_no index root page number
2934@return true if modified else false */
2935inline
2936bool
2937truncate_t::is_index_modified_since_logged(
2938 const fil_space_t* space,
2939 ulint root_page_no) const
2940{
2941 dberr_t err;
2942 mtr_t mtr;
2943
2944 mtr_start(&mtr);
2945
2946 /* Root page could be in free state if truncate crashed after drop_index
2947 and page was not allocated for any other object. */
2948 buf_block_t* block= buf_page_get_gen(
2949 page_id_t(space->id, root_page_no), page_size_t(space->flags),
2950 RW_X_LATCH, NULL,
2951 BUF_GET_POSSIBLY_FREED, __FILE__, __LINE__, &mtr, &err);
2952 if (!block) return true;
2953
2954 page_t* root = buf_block_get_frame(block);
2955
2956#ifdef UNIV_DEBUG
2957 /* If the root page has been freed as part of truncate drop_index action
2958 and not yet allocated for any object still the pagelsn > snapshot lsn */
2959 if (block->page.file_page_was_freed) {
2960 ut_ad(mach_read_from_8(root + FIL_PAGE_LSN) > m_log_lsn);
2961 }
2962#endif /* UNIV_DEBUG */
2963
2964 lsn_t page_lsn = mach_read_from_8(root + FIL_PAGE_LSN);
2965
2966 mtr_commit(&mtr);
2967
2968 if (page_lsn > m_log_lsn) {
2969 return(true);
2970 }
2971
2972 return(false);
2973}
2974
2975/** Drop indexes for a table.
2976@param[in,out] space tablespace */
2977void truncate_t::drop_indexes(fil_space_t* space) const
2978{
2979 mtr_t mtr;
2980
2981 indexes_t::const_iterator end = m_indexes.end();
2982 const page_size_t page_size(space->flags);
2983
2984 for (indexes_t::const_iterator it = m_indexes.begin();
2985 it != end;
2986 ++it) {
2987
2988 ulint root_page_no = it->m_root_page_no;
2989
2990 if (is_index_modified_since_logged(space, root_page_no)) {
2991 /* Page has been modified since TRUNCATE log snapshot
2992 was recorded so not safe to drop the index. */
2993 continue;
2994 }
2995
2996 mtr_start(&mtr);
2997
2998 if (space->id != TRX_SYS_SPACE) {
2999 /* Do not log changes for single-table
3000 tablespaces, we are in recovery mode. */
3001 mtr_set_log_mode(&mtr, MTR_LOG_NO_REDO);
3002 }
3003
3004 if (root_page_no != FIL_NULL) {
3005 const page_id_t root_page_id(space->id, root_page_no);
3006
3007 btr_free_if_exists(
3008 root_page_id, page_size, it->m_id, &mtr);
3009 }
3010
3011 /* If tree is already freed then we might return immediately
3012 in which case we need to release the lock we have acquired
3013 on root_page. */
3014 mtr_commit(&mtr);
3015 }
3016}
3017
3018
3019/** Create the indexes for a table
3020@param[in] table_name table name, for which to create the indexes
3021@param[in,out] space tablespace
3022@param[in] format_flags page format flags
3023@return DB_SUCCESS or error code. */
3024inline dberr_t
3025truncate_t::create_indexes(
3026 const char* table_name,
3027 fil_space_t* space,
3028 ulint format_flags)
3029{
3030 mtr_t mtr;
3031
3032 mtr_start(&mtr);
3033
3034 if (space->id != TRX_SYS_SPACE) {
3035 /* Do not log changes for single-table tablespaces, we
3036 are in recovery mode. */
3037 mtr_set_log_mode(&mtr, MTR_LOG_NO_REDO);
3038 }
3039
3040 /* Create all new index trees with table format, index ids, index
3041 types, number of index fields and index field information taken
3042 out from the TRUNCATE log record. */
3043
3044 ulint root_page_no = FIL_NULL;
3045 indexes_t::iterator end = m_indexes.end();
3046 for (indexes_t::iterator it = m_indexes.begin();
3047 it != end;
3048 ++it) {
3049
3050 btr_create_t btr_redo_create_info(
3051 FSP_FLAGS_GET_ZIP_SSIZE(space->flags)
3052 ? &it->m_fields[0] : NULL);
3053
3054 btr_redo_create_info.format_flags = format_flags;
3055
3056 if (FSP_FLAGS_GET_ZIP_SSIZE(space->flags)) {
3057
3058 btr_redo_create_info.n_fields = it->m_n_fields;
3059 /* Skip the NUL appended field */
3060 btr_redo_create_info.field_len =
3061 it->m_fields.size() - 1;
3062 btr_redo_create_info.trx_id_pos = it->m_trx_id_pos;
3063 }
3064
3065 root_page_no = create_index(
3066 table_name, space, it->m_type, it->m_id,
3067 btr_redo_create_info, &mtr);
3068
3069 if (root_page_no == FIL_NULL) {
3070 break;
3071 }
3072
3073 it->m_new_root_page_no = root_page_no;
3074 }
3075
3076 mtr_commit(&mtr);
3077
3078 return(root_page_no == FIL_NULL ? DB_ERROR : DB_SUCCESS);
3079}
3080
3081/**
3082Write a TRUNCATE log record for fixing up table if truncate crashes.
3083@param start_ptr buffer to write log record
3084@param end_ptr buffer end
3085@param space_id space id
3086@param tablename the table name in the usual databasename/tablename
3087 format of InnoDB
3088@param flags tablespace flags
3089@param format_flags page format
3090@param lsn lsn while logging
3091@return DB_SUCCESS or error code */
3092
3093dberr_t
3094truncate_t::write(
3095 byte* start_ptr,
3096 byte* end_ptr,
3097 ulint space_id,
3098 const char* tablename,
3099 ulint flags,
3100 ulint format_flags,
3101 lsn_t lsn) const
3102{
3103 if (end_ptr < start_ptr) {
3104 return(DB_FAIL);
3105 }
3106
3107 /* LSN, Type, Space-ID, format-flag (also know as log_flag.
3108 Stored in page_no field), tablespace flags */
3109 if (end_ptr < (start_ptr + (8 + 4 + 4 + 4))) {
3110 return(DB_FAIL);
3111 }
3112
3113 mach_write_to_8(start_ptr, lsn);
3114 start_ptr += 8;
3115
3116 mach_write_to_4(start_ptr, space_id);
3117 start_ptr += 4;
3118
3119 mach_write_to_4(start_ptr, format_flags);
3120 start_ptr += 4;
3121
3122 mach_write_to_4(start_ptr, flags);
3123 start_ptr += 4;
3124
3125 /* Name of the table. */
3126 /* Include the NUL in the log record. */
3127 ulint len = strlen(tablename) + 1;
3128 if (end_ptr < (start_ptr + (len + 2))) {
3129 return(DB_FAIL);
3130 }
3131
3132 mach_write_to_2(start_ptr, len);
3133 start_ptr += 2;
3134
3135 memcpy(start_ptr, tablename, len - 1);
3136 start_ptr += len;
3137
3138 DBUG_EXECUTE_IF("ib_trunc_crash_while_writing_redo_log",
3139 DBUG_SUICIDE(););
3140
3141 /* Old/New Table-ID, Number of Indexes and Tablespace dir-path-name. */
3142 /* Write the remote directory of the table into mtr log */
3143 len = m_dir_path != NULL ? strlen(m_dir_path) + 1 : 0;
3144 if (end_ptr < (start_ptr + (len + 8 + 8 + 2 + 2))) {
3145 return(DB_FAIL);
3146 }
3147
3148 /* Write out old-table-id. */
3149 mach_write_to_8(start_ptr, m_old_table_id);
3150 start_ptr += 8;
3151
3152 /* Write out new-table-id. */
3153 mach_write_to_8(start_ptr, m_new_table_id);
3154 start_ptr += 8;
3155
3156 /* Write out the number of indexes. */
3157 mach_write_to_2(start_ptr, m_indexes.size());
3158 start_ptr += 2;
3159
3160 /* Write the length (NUL included) of the .ibd path. */
3161 mach_write_to_2(start_ptr, len);
3162 start_ptr += 2;
3163
3164 if (m_dir_path != NULL) {
3165 memcpy(start_ptr, m_dir_path, len - 1);
3166 start_ptr += len;
3167 }
3168
3169 /* Indexes information (id, type) */
3170 /* Write index ids, type, root-page-no into mtr log */
3171 for (ulint i = 0; i < m_indexes.size(); ++i) {
3172
3173 if (end_ptr < (start_ptr + (8 + 4 + 4 + 4))) {
3174 return(DB_FAIL);
3175 }
3176
3177 mach_write_to_8(start_ptr, m_indexes[i].m_id);
3178 start_ptr += 8;
3179
3180 mach_write_to_4(start_ptr, m_indexes[i].m_type);
3181 start_ptr += 4;
3182
3183 mach_write_to_4(start_ptr, m_indexes[i].m_root_page_no);
3184 start_ptr += 4;
3185
3186 mach_write_to_4(start_ptr, m_indexes[i].m_trx_id_pos);
3187 start_ptr += 4;
3188 }
3189
3190 /* If tablespace compressed then field info of each index. */
3191 if (FSP_FLAGS_GET_ZIP_SSIZE(flags)) {
3192
3193 for (ulint i = 0; i < m_indexes.size(); ++i) {
3194
3195 ulint len = m_indexes[i].m_fields.size();
3196 if (end_ptr < (start_ptr + (len + 2 + 2))) {
3197 return(DB_FAIL);
3198 }
3199
3200 mach_write_to_2(
3201 start_ptr, m_indexes[i].m_n_fields);
3202 start_ptr += 2;
3203
3204 mach_write_to_2(start_ptr, len);
3205 start_ptr += 2;
3206
3207 const byte* ptr = &m_indexes[i].m_fields[0];
3208 memcpy(start_ptr, ptr, len - 1);
3209 start_ptr += len;
3210 }
3211 }
3212
3213 return(DB_SUCCESS);
3214}
3215
3216