1 /*****************************************************************************
2
3 Copyright (c) 1997, 2017, Oracle and/or its affiliates. All Rights Reserved.
4 Copyright (c) 2017, 2021, MariaDB Corporation.
5
6 This program is free software; you can redistribute it and/or modify it under
7 the terms of the GNU General Public License as published by the Free Software
8 Foundation; version 2 of the License.
9
10 This program is distributed in the hope that it will be useful, but WITHOUT
11 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License along with
15 this program; if not, write to the Free Software Foundation, Inc.,
16 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA
17
18 *****************************************************************************/
19
20 /**************************************************//**
21 @file row/row0purge.cc
22 Purge obsolete records
23
24 Created 3/14/1997 Heikki Tuuri
25 *******************************************************/
26
27 #include "row0purge.h"
28 #include "fsp0fsp.h"
29 #include "mach0data.h"
30 #include "dict0stats.h"
31 #include "trx0rseg.h"
32 #include "trx0trx.h"
33 #include "trx0roll.h"
34 #include "trx0undo.h"
35 #include "trx0purge.h"
36 #include "trx0rec.h"
37 #include "que0que.h"
38 #include "row0row.h"
39 #include "row0upd.h"
40 #include "row0vers.h"
41 #include "row0mysql.h"
42 #include "row0log.h"
43 #include "log0log.h"
44 #include "srv0mon.h"
45 #include "srv0start.h"
46 #include "handler.h"
47 #include "ha_innodb.h"
48 #include "fil0fil.h"
49 #include "debug_sync.h"
50
51 /*************************************************************************
52 IMPORTANT NOTE: Any operation that generates redo MUST check that there
53 is enough space in the redo log before for that operation. This is
54 done by calling log_free_check(). The reason for checking the
55 availability of the redo log space before the start of the operation is
56 that we MUST not hold any synchonization objects when performing the
57 check.
58 If you make a change in this module make sure that no codepath is
59 introduced where a call to log_free_check() is bypassed. */
60
61 /***********************************************************//**
62 Repositions the pcur in the purge node on the clustered index record,
63 if found. If the record is not found, close pcur.
64 @return TRUE if the record was found */
65 static
66 ibool
row_purge_reposition_pcur(ulint mode,purge_node_t * node,mtr_t * mtr)67 row_purge_reposition_pcur(
68 /*======================*/
69 ulint mode, /*!< in: latching mode */
70 purge_node_t* node, /*!< in: row purge node */
71 mtr_t* mtr) /*!< in: mtr */
72 {
73 if (node->found_clust) {
74 ut_ad(node->validate_pcur());
75
76 node->found_clust = btr_pcur_restore_position(mode, &node->pcur, mtr);
77
78 } else {
79 node->found_clust = row_search_on_row_ref(
80 &node->pcur, mode, node->table, node->ref, mtr);
81
82 if (node->found_clust) {
83 btr_pcur_store_position(&node->pcur, mtr);
84 }
85 }
86
87 /* Close the current cursor if we fail to position it correctly. */
88 if (!node->found_clust) {
89 btr_pcur_close(&node->pcur);
90 }
91
92 return(node->found_clust);
93 }
94
95 /***********************************************************//**
96 Removes a delete marked clustered index record if possible.
97 @retval true if the row was not found, or it was successfully removed
98 @retval false if the row was modified after the delete marking */
99 static MY_ATTRIBUTE((nonnull, warn_unused_result))
100 bool
row_purge_remove_clust_if_poss_low(purge_node_t * node,ulint mode)101 row_purge_remove_clust_if_poss_low(
102 /*===============================*/
103 purge_node_t* node, /*!< in/out: row purge node */
104 ulint mode) /*!< in: BTR_MODIFY_LEAF or BTR_MODIFY_TREE */
105 {
106 ut_ad(rw_lock_own(&dict_sys.latch, RW_LOCK_S)
107 || node->vcol_info.is_used());
108
109 dict_index_t* index = dict_table_get_first_index(node->table);
110
111 log_free_check();
112
113 mtr_t mtr;
114 mtr.start();
115
116 if (!row_purge_reposition_pcur(mode, node, &mtr)) {
117 /* The record was already removed. */
118 mtr.commit();
119 return true;
120 }
121
122 ut_d(const bool was_instant = !!index->table->instant);
123 index->set_modified(mtr);
124
125 rec_t* rec = btr_pcur_get_rec(&node->pcur);
126 rec_offs offsets_[REC_OFFS_NORMAL_SIZE];
127 rec_offs_init(offsets_);
128 mem_heap_t* heap = NULL;
129 rec_offs* offsets = rec_get_offsets(rec, index, offsets_,
130 index->n_core_fields,
131 ULINT_UNDEFINED, &heap);
132 bool success = true;
133
134 if (node->roll_ptr != row_get_rec_roll_ptr(rec, index, offsets)) {
135 /* Someone else has modified the record later: do not remove */
136 goto func_exit;
137 }
138
139 ut_ad(rec_get_deleted_flag(rec, rec_offs_comp(offsets)));
140 /* In delete-marked records, DB_TRX_ID must
141 always refer to an existing undo log record. */
142 ut_ad(row_get_rec_trx_id(rec, index, offsets));
143
144 if (mode == BTR_MODIFY_LEAF) {
145 success = btr_cur_optimistic_delete(
146 btr_pcur_get_btr_cur(&node->pcur), 0, &mtr);
147 } else {
148 dberr_t err;
149 ut_ad(mode == (BTR_MODIFY_TREE | BTR_LATCH_FOR_DELETE));
150 btr_cur_pessimistic_delete(
151 &err, FALSE, btr_pcur_get_btr_cur(&node->pcur), 0,
152 false, &mtr);
153
154 switch (err) {
155 case DB_SUCCESS:
156 break;
157 case DB_OUT_OF_FILE_SPACE:
158 success = false;
159 break;
160 default:
161 ut_error;
162 }
163 }
164
165 /* Prove that dict_index_t::clear_instant_alter() was
166 not called with index->table->instant != NULL. */
167 ut_ad(!was_instant || index->table->instant);
168
169 func_exit:
170 if (heap) {
171 mem_heap_free(heap);
172 }
173
174 /* Persistent cursor is closed if reposition fails. */
175 if (node->found_clust) {
176 btr_pcur_commit_specify_mtr(&node->pcur, &mtr);
177 } else {
178 mtr_commit(&mtr);
179 }
180
181 return(success);
182 }
183
184 /***********************************************************//**
185 Removes a clustered index record if it has not been modified after the delete
186 marking.
187 @retval true if the row was not found, or it was successfully removed
188 @retval false the purge needs to be suspended because of running out
189 of file space. */
190 static MY_ATTRIBUTE((nonnull, warn_unused_result))
191 bool
row_purge_remove_clust_if_poss(purge_node_t * node)192 row_purge_remove_clust_if_poss(
193 /*===========================*/
194 purge_node_t* node) /*!< in/out: row purge node */
195 {
196 if (row_purge_remove_clust_if_poss_low(node, BTR_MODIFY_LEAF)) {
197 return(true);
198 }
199
200 for (ulint n_tries = 0;
201 n_tries < BTR_CUR_RETRY_DELETE_N_TIMES;
202 n_tries++) {
203 if (row_purge_remove_clust_if_poss_low(
204 node, BTR_MODIFY_TREE | BTR_LATCH_FOR_DELETE)) {
205 return(true);
206 }
207
208 os_thread_sleep(BTR_CUR_RETRY_SLEEP_TIME);
209 }
210
211 return(false);
212 }
213
214 /** Tries to store secondary index cursor before openin mysql table for
215 virtual index condition computation.
216 @param[in,out] node row purge node
217 @param[in] index secondary index
218 @param[in,out] sec_pcur secondary index cursor
219 @param[in,out] sec_mtr mini-transaction which holds
220 secondary index entry */
row_purge_store_vsec_cur(purge_node_t * node,dict_index_t * index,btr_pcur_t * sec_pcur,mtr_t * sec_mtr)221 static void row_purge_store_vsec_cur(
222 purge_node_t* node,
223 dict_index_t* index,
224 btr_pcur_t* sec_pcur,
225 mtr_t* sec_mtr)
226 {
227 row_purge_reposition_pcur(BTR_SEARCH_LEAF, node, sec_mtr);
228
229 if (!node->found_clust) {
230 return;
231 }
232
233 node->vcol_info.set_requested();
234
235 btr_pcur_store_position(sec_pcur, sec_mtr);
236
237 btr_pcurs_commit_specify_mtr(&node->pcur, sec_pcur, sec_mtr);
238 }
239
240 /** Tries to restore secondary index cursor after opening the mysql table
241 @param[in,out] node row purge node
242 @param[in] index secondary index
243 @param[in,out] sec_mtr mini-transaction which holds secondary index entry
244 @param[in] is_tree true=pessimistic purge,
245 false=optimistic (leaf-page only)
246 @return false in case of restore failure. */
row_purge_restore_vsec_cur(purge_node_t * node,dict_index_t * index,btr_pcur_t * sec_pcur,mtr_t * sec_mtr,bool is_tree)247 static bool row_purge_restore_vsec_cur(
248 purge_node_t* node,
249 dict_index_t* index,
250 btr_pcur_t* sec_pcur,
251 mtr_t* sec_mtr,
252 bool is_tree)
253 {
254 sec_mtr->start();
255 index->set_modified(*sec_mtr);
256
257 return btr_pcur_restore_position(
258 is_tree ? BTR_PURGE_TREE : BTR_PURGE_LEAF,
259 sec_pcur, sec_mtr);
260 }
261
262 /** Determines if it is possible to remove a secondary index entry.
263 Removal is possible if the secondary index entry does not refer to any
264 not delete marked version of a clustered index record where DB_TRX_ID
265 is newer than the purge view.
266
267 NOTE: This function should only be called by the purge thread, only
268 while holding a latch on the leaf page of the secondary index entry
269 (or keeping the buffer pool watch on the page). It is possible that
270 this function first returns true and then false, if a user transaction
271 inserts a record that the secondary index entry would refer to.
272 However, in that case, the user transaction would also re-insert the
273 secondary index entry after purge has removed it and released the leaf
274 page latch.
275 @param[in,out] node row purge node
276 @param[in] index secondary index
277 @param[in] entry secondary index entry
278 @param[in,out] sec_pcur secondary index cursor or NULL
279 if it is called for purge buffering
280 operation.
281 @param[in,out] sec_mtr mini-transaction which holds
282 secondary index entry or NULL if it is
283 called for purge buffering operation.
284 @param[in] is_tree true=pessimistic purge,
285 false=optimistic (leaf-page only)
286 @return true if the secondary index record can be purged */
287 bool
row_purge_poss_sec(purge_node_t * node,dict_index_t * index,const dtuple_t * entry,btr_pcur_t * sec_pcur,mtr_t * sec_mtr,bool is_tree)288 row_purge_poss_sec(
289 purge_node_t* node,
290 dict_index_t* index,
291 const dtuple_t* entry,
292 btr_pcur_t* sec_pcur,
293 mtr_t* sec_mtr,
294 bool is_tree)
295 {
296 bool can_delete;
297 mtr_t mtr;
298
299 ut_ad(!dict_index_is_clust(index));
300
301 const bool store_cur = sec_mtr && !node->vcol_info.is_used()
302 && dict_index_has_virtual(index);
303
304 if (store_cur) {
305 row_purge_store_vsec_cur(node, index, sec_pcur, sec_mtr);
306 ut_ad(sec_mtr->has_committed()
307 == node->vcol_info.is_requested());
308
309 /* The PRIMARY KEY value was not found in the clustered
310 index. The secondary index record found. We can purge
311 the secondary index record. */
312 if (!node->vcol_info.is_requested()) {
313 ut_ad(!node->found_clust);
314 return true;
315 }
316 }
317
318 retry_purge_sec:
319 mtr_start(&mtr);
320
321 can_delete = !row_purge_reposition_pcur(BTR_SEARCH_LEAF, node, &mtr)
322 || !row_vers_old_has_index_entry(true,
323 btr_pcur_get_rec(&node->pcur),
324 &mtr, index, entry,
325 node->roll_ptr, node->trx_id,
326 &node->vcol_info);
327
328 if (node->vcol_info.is_first_fetch()) {
329 ut_ad(store_cur);
330
331 const TABLE* t= node->vcol_info.table();
332 DBUG_LOG("purge", "retry " << t
333 << (is_tree ? " tree" : " leaf")
334 << index->name << "," << index->table->name
335 << ": " << rec_printer(entry).str());
336
337 ut_ad(mtr.has_committed());
338
339 if (t) {
340 node->vcol_info.set_used();
341 goto retry_purge_sec;
342 }
343
344 node->table = NULL;
345 sec_pcur = NULL;
346 return false;
347 }
348
349 /* Persistent cursor is closed if reposition fails. */
350 if (node->found_clust) {
351 btr_pcur_commit_specify_mtr(&node->pcur, &mtr);
352 } else {
353 mtr.commit();
354 }
355
356 ut_ad(mtr.has_committed());
357
358 /* If the virtual column info is not used then reset the virtual column
359 info. */
360 if (node->vcol_info.is_requested()
361 && !node->vcol_info.is_used()) {
362 node->vcol_info.reset();
363 }
364
365 if (store_cur && !row_purge_restore_vsec_cur(
366 node, index, sec_pcur, sec_mtr, is_tree)) {
367 return false;
368 }
369
370 return can_delete;
371 }
372
373 /***************************************************************
374 Removes a secondary index entry if possible, by modifying the
375 index tree. Does not try to buffer the delete.
376 @return TRUE if success or if not found */
377 static MY_ATTRIBUTE((nonnull, warn_unused_result))
378 ibool
row_purge_remove_sec_if_poss_tree(purge_node_t * node,dict_index_t * index,const dtuple_t * entry)379 row_purge_remove_sec_if_poss_tree(
380 /*==============================*/
381 purge_node_t* node, /*!< in: row purge node */
382 dict_index_t* index, /*!< in: index */
383 const dtuple_t* entry) /*!< in: index entry */
384 {
385 btr_pcur_t pcur;
386 ibool success = TRUE;
387 dberr_t err;
388 mtr_t mtr;
389 enum row_search_result search_result;
390
391 log_free_check();
392 mtr.start();
393 index->set_modified(mtr);
394
395 if (!index->is_committed()) {
396 /* The index->online_status may change if the index is
397 or was being created online, but not committed yet. It
398 is protected by index->lock. */
399 mtr_sx_lock_index(index, &mtr);
400
401 if (dict_index_is_online_ddl(index)) {
402 /* Online secondary index creation will not
403 copy any delete-marked records. Therefore
404 there is nothing to be purged. We must also
405 skip the purge when a completed index is
406 dropped by rollback_inplace_alter_table(). */
407 goto func_exit_no_pcur;
408 }
409 } else {
410 /* For secondary indexes,
411 index->online_status==ONLINE_INDEX_COMPLETE if
412 index->is_committed(). */
413 ut_ad(!dict_index_is_online_ddl(index));
414 }
415
416 search_result = row_search_index_entry(
417 index, entry,
418 BTR_MODIFY_TREE | BTR_LATCH_FOR_DELETE,
419 &pcur, &mtr);
420
421 switch (search_result) {
422 case ROW_NOT_FOUND:
423 /* Not found. This is a legitimate condition. In a
424 rollback, InnoDB will remove secondary recs that would
425 be purged anyway. Then the actual purge will not find
426 the secondary index record. Also, the purge itself is
427 eager: if it comes to consider a secondary index
428 record, and notices it does not need to exist in the
429 index, it will remove it. Then if/when the purge
430 comes to consider the secondary index record a second
431 time, it will not exist any more in the index. */
432
433 /* fputs("PURGE:........sec entry not found\n", stderr); */
434 /* dtuple_print(stderr, entry); */
435 goto func_exit;
436 case ROW_FOUND:
437 break;
438 case ROW_BUFFERED:
439 case ROW_NOT_DELETED_REF:
440 /* These are invalid outcomes, because the mode passed
441 to row_search_index_entry() did not include any of the
442 flags BTR_INSERT, BTR_DELETE, or BTR_DELETE_MARK. */
443 ut_error;
444 }
445
446 /* We should remove the index record if no later version of the row,
447 which cannot be purged yet, requires its existence. If some requires,
448 we should do nothing. */
449
450 if (row_purge_poss_sec(node, index, entry, &pcur, &mtr, true)) {
451
452 /* Remove the index record, which should have been
453 marked for deletion. */
454 if (!rec_get_deleted_flag(btr_cur_get_rec(
455 btr_pcur_get_btr_cur(&pcur)),
456 dict_table_is_comp(index->table))) {
457 ib::error()
458 << "tried to purge non-delete-marked record"
459 " in index " << index->name
460 << " of table " << index->table->name
461 << ": tuple: " << *entry
462 << ", record: " << rec_index_print(
463 btr_cur_get_rec(
464 btr_pcur_get_btr_cur(&pcur)),
465 index);
466
467 ut_ad(0);
468
469 goto func_exit;
470 }
471
472 btr_cur_pessimistic_delete(&err, FALSE,
473 btr_pcur_get_btr_cur(&pcur),
474 0, false, &mtr);
475 switch (UNIV_EXPECT(err, DB_SUCCESS)) {
476 case DB_SUCCESS:
477 break;
478 case DB_OUT_OF_FILE_SPACE:
479 success = FALSE;
480 break;
481 default:
482 ut_error;
483 }
484 }
485
486 if (node->vcol_op_failed()) {
487 ut_ad(mtr.has_committed());
488 ut_ad(!pcur.old_rec_buf);
489 ut_ad(pcur.pos_state == BTR_PCUR_NOT_POSITIONED);
490 return false;
491 }
492
493 func_exit:
494 btr_pcur_close(&pcur); // FIXME: need this?
495 func_exit_no_pcur:
496 mtr.commit();
497
498 return(success);
499 }
500
501 /***************************************************************
502 Removes a secondary index entry without modifying the index tree,
503 if possible.
504 @retval true if success or if not found
505 @retval false if row_purge_remove_sec_if_poss_tree() should be invoked */
506 static MY_ATTRIBUTE((nonnull, warn_unused_result))
507 bool
row_purge_remove_sec_if_poss_leaf(purge_node_t * node,dict_index_t * index,const dtuple_t * entry)508 row_purge_remove_sec_if_poss_leaf(
509 /*==============================*/
510 purge_node_t* node, /*!< in: row purge node */
511 dict_index_t* index, /*!< in: index */
512 const dtuple_t* entry) /*!< in: index entry */
513 {
514 mtr_t mtr;
515 btr_pcur_t pcur;
516 enum btr_latch_mode mode;
517 enum row_search_result search_result;
518 bool success = true;
519
520 log_free_check();
521 ut_ad(index->table == node->table);
522 ut_ad(!index->table->is_temporary());
523 mtr.start();
524 index->set_modified(mtr);
525
526 if (!index->is_committed()) {
527 /* For uncommitted spatial index, we also skip the purge. */
528 if (dict_index_is_spatial(index)) {
529 goto func_exit_no_pcur;
530 }
531
532 /* The index->online_status may change if the the
533 index is or was being created online, but not
534 committed yet. It is protected by index->lock. */
535 mtr_s_lock_index(index, &mtr);
536
537 if (dict_index_is_online_ddl(index)) {
538 /* Online secondary index creation will not
539 copy any delete-marked records. Therefore
540 there is nothing to be purged. We must also
541 skip the purge when a completed index is
542 dropped by rollback_inplace_alter_table(). */
543 goto func_exit_no_pcur;
544 }
545
546 mode = BTR_PURGE_LEAF_ALREADY_S_LATCHED;
547 } else {
548 /* For secondary indexes,
549 index->online_status==ONLINE_INDEX_COMPLETE if
550 index->is_committed(). */
551 ut_ad(!dict_index_is_online_ddl(index));
552
553 /* Change buffering is disabled for spatial index and
554 virtual index. */
555 mode = (dict_index_is_spatial(index)
556 || dict_index_has_virtual(index))
557 ? BTR_MODIFY_LEAF
558 : BTR_PURGE_LEAF;
559 }
560
561 /* Set the purge node for the call to row_purge_poss_sec(). */
562 pcur.btr_cur.purge_node = node;
563 if (dict_index_is_spatial(index)) {
564 rw_lock_sx_lock(dict_index_get_lock(index));
565 pcur.btr_cur.thr = NULL;
566 } else {
567 /* Set the query thread, so that ibuf_insert_low() will be
568 able to invoke thd_get_trx(). */
569 pcur.btr_cur.thr = static_cast<que_thr_t*>(
570 que_node_get_parent(node));
571 }
572
573 search_result = row_search_index_entry(
574 index, entry, mode, &pcur, &mtr);
575
576 if (dict_index_is_spatial(index)) {
577 rw_lock_sx_unlock(dict_index_get_lock(index));
578 }
579
580 switch (search_result) {
581 case ROW_FOUND:
582 /* Before attempting to purge a record, check
583 if it is safe to do so. */
584 if (row_purge_poss_sec(node, index, entry, &pcur, &mtr, false)) {
585 btr_cur_t* btr_cur = btr_pcur_get_btr_cur(&pcur);
586
587 /* Only delete-marked records should be purged. */
588 if (!rec_get_deleted_flag(
589 btr_cur_get_rec(btr_cur),
590 dict_table_is_comp(index->table))) {
591
592 ib::error()
593 << "tried to purge non-delete-marked"
594 " record" " in index " << index->name
595 << " of table " << index->table->name
596 << ": tuple: " << *entry
597 << ", record: "
598 << rec_index_print(
599 btr_cur_get_rec(btr_cur),
600 index);
601 ut_ad(0);
602
603 btr_pcur_close(&pcur);
604
605 goto func_exit_no_pcur;
606 }
607
608 if (dict_index_is_spatial(index)) {
609 const page_t* page;
610 const trx_t* trx = NULL;
611
612 if (btr_cur->rtr_info != NULL
613 && btr_cur->rtr_info->thr != NULL) {
614 trx = thr_get_trx(
615 btr_cur->rtr_info->thr);
616 }
617
618 page = btr_cur_get_page(btr_cur);
619
620 if (!lock_test_prdt_page_lock(
621 trx,
622 page_get_space_id(page),
623 page_get_page_no(page))
624 && page_get_n_recs(page) < 2
625 && btr_cur_get_block(btr_cur)
626 ->page.id.page_no() !=
627 dict_index_get_page(index)) {
628 /* this is the last record on page,
629 and it has a "page" lock on it,
630 which mean search is still depending
631 on it, so do not delete */
632 DBUG_LOG("purge",
633 "skip purging last"
634 " record on page "
635 << btr_cur_get_block(btr_cur)
636 ->page.id);
637
638 btr_pcur_close(&pcur);
639 mtr.commit();
640 return(success);
641 }
642 }
643
644 if (!btr_cur_optimistic_delete(btr_cur, 0, &mtr)) {
645
646 /* The index entry could not be deleted. */
647 success = false;
648 }
649 }
650
651 if (node->vcol_op_failed()) {
652 btr_pcur_close(&pcur);
653 return false;
654 }
655
656 /* (The index entry is still needed,
657 or the deletion succeeded) */
658 /* fall through */
659 case ROW_NOT_DELETED_REF:
660 /* The index entry is still needed. */
661 case ROW_BUFFERED:
662 /* The deletion was buffered. */
663 case ROW_NOT_FOUND:
664 /* The index entry does not exist, nothing to do. */
665 btr_pcur_close(&pcur); // FIXME: do we need these? when is btr_cur->rtr_info set?
666 func_exit_no_pcur:
667 mtr.commit();
668 return(success);
669 }
670
671 ut_error;
672 return(false);
673 }
674
675 /***********************************************************//**
676 Removes a secondary index entry if possible. */
677 UNIV_INLINE MY_ATTRIBUTE((nonnull(1,2)))
678 void
row_purge_remove_sec_if_poss(purge_node_t * node,dict_index_t * index,const dtuple_t * entry)679 row_purge_remove_sec_if_poss(
680 /*=========================*/
681 purge_node_t* node, /*!< in: row purge node */
682 dict_index_t* index, /*!< in: index */
683 const dtuple_t* entry) /*!< in: index entry */
684 {
685 ibool success;
686 ulint n_tries = 0;
687
688 /* fputs("Purge: Removing secondary record\n", stderr); */
689
690 if (!entry) {
691 /* The node->row must have lacked some fields of this
692 index. This is possible when the undo log record was
693 written before this index was created. */
694 return;
695 }
696
697 if (row_purge_remove_sec_if_poss_leaf(node, index, entry)) {
698
699 return;
700 }
701 retry:
702 if (node->vcol_op_failed()) {
703 return;
704 }
705
706 success = row_purge_remove_sec_if_poss_tree(node, index, entry);
707 /* The delete operation may fail if we have little
708 file space left: TODO: easiest to crash the database
709 and restart with more file space */
710
711 if (!success && n_tries < BTR_CUR_RETRY_DELETE_N_TIMES) {
712
713 n_tries++;
714
715 os_thread_sleep(BTR_CUR_RETRY_SLEEP_TIME);
716
717 goto retry;
718 }
719
720 ut_a(success);
721 }
722
723 /** Skip uncommitted virtual indexes on newly added virtual column.
724 @param[in,out] index dict index object */
725 static
726 inline
727 void
row_purge_skip_uncommitted_virtual_index(dict_index_t * & index)728 row_purge_skip_uncommitted_virtual_index(
729 dict_index_t*& index)
730 {
731 /* We need to skip virtual indexes which is not
732 committed yet. It's safe because these indexes are
733 newly created by alter table, and because we do
734 not support LOCK=NONE when adding an index on newly
735 added virtual column.*/
736 while (index != NULL && dict_index_has_virtual(index)
737 && !index->is_committed() && index->has_new_v_col()) {
738 index = dict_table_get_next_index(index);
739 }
740 }
741
742 /***********************************************************//**
743 Purges a delete marking of a record.
744 @retval true if the row was not found, or it was successfully removed
745 @retval false the purge needs to be suspended because of
746 running out of file space */
747 static MY_ATTRIBUTE((nonnull, warn_unused_result))
748 bool
row_purge_del_mark(purge_node_t * node)749 row_purge_del_mark(
750 /*===============*/
751 purge_node_t* node) /*!< in/out: row purge node */
752 {
753 mem_heap_t* heap;
754
755 heap = mem_heap_create(1024);
756
757 while (node->index != NULL) {
758 /* skip corrupted secondary index */
759 dict_table_skip_corrupt_index(node->index);
760
761 row_purge_skip_uncommitted_virtual_index(node->index);
762
763 if (!node->index) {
764 break;
765 }
766
767 if (node->index->type != DICT_FTS) {
768 dtuple_t* entry = row_build_index_entry_low(
769 node->row, NULL, node->index,
770 heap, ROW_BUILD_FOR_PURGE);
771 row_purge_remove_sec_if_poss(node, node->index, entry);
772
773 if (node->vcol_op_failed()) {
774 mem_heap_free(heap);
775 return false;
776 }
777
778 mem_heap_empty(heap);
779 }
780
781 node->index = dict_table_get_next_index(node->index);
782 }
783
784 mem_heap_free(heap);
785
786 return(row_purge_remove_clust_if_poss(node));
787 }
788
789 /** Reset DB_TRX_ID, DB_ROLL_PTR of a clustered index record
790 whose old history can no longer be observed.
791 @param[in,out] node purge node
792 @param[in,out] mtr mini-transaction (will be started and committed) */
row_purge_reset_trx_id(purge_node_t * node,mtr_t * mtr)793 static void row_purge_reset_trx_id(purge_node_t* node, mtr_t* mtr)
794 {
795 ut_ad(rw_lock_own(&dict_sys.latch, RW_LOCK_S)
796 || node->vcol_info.is_used());
797 /* Reset DB_TRX_ID, DB_ROLL_PTR for old records. */
798 mtr->start();
799
800 if (row_purge_reposition_pcur(BTR_MODIFY_LEAF, node, mtr)) {
801 dict_index_t* index = dict_table_get_first_index(
802 node->table);
803 ulint trx_id_pos = index->n_uniq ? index->n_uniq : 1;
804 rec_t* rec = btr_pcur_get_rec(&node->pcur);
805 mem_heap_t* heap = NULL;
806 /* Reserve enough offsets for the PRIMARY KEY and 2 columns
807 so that we can access DB_TRX_ID, DB_ROLL_PTR. */
808 rec_offs offsets_[REC_OFFS_HEADER_SIZE + MAX_REF_PARTS + 2];
809 rec_offs_init(offsets_);
810 rec_offs* offsets = rec_get_offsets(
811 rec, index, offsets_, index->n_core_fields,
812 trx_id_pos + 2, &heap);
813 ut_ad(heap == NULL);
814
815 ut_ad(dict_index_get_nth_field(index, trx_id_pos)
816 ->col->mtype == DATA_SYS);
817 ut_ad(dict_index_get_nth_field(index, trx_id_pos)
818 ->col->prtype == (DATA_TRX_ID | DATA_NOT_NULL));
819 ut_ad(dict_index_get_nth_field(index, trx_id_pos + 1)
820 ->col->mtype == DATA_SYS);
821 ut_ad(dict_index_get_nth_field(index, trx_id_pos + 1)
822 ->col->prtype == (DATA_ROLL_PTR | DATA_NOT_NULL));
823
824 /* Only update the record if DB_ROLL_PTR matches (the
825 record has not been modified after this transaction
826 became purgeable) */
827 if (node->roll_ptr
828 == row_get_rec_roll_ptr(rec, index, offsets)) {
829 ut_ad(!rec_get_deleted_flag(
830 rec, rec_offs_comp(offsets))
831 || rec_is_alter_metadata(rec, *index));
832 DBUG_LOG("purge", "reset DB_TRX_ID="
833 << ib::hex(row_get_rec_trx_id(
834 rec, index, offsets)));
835
836 index->set_modified(*mtr);
837 if (page_zip_des_t* page_zip
838 = buf_block_get_page_zip(
839 btr_pcur_get_block(&node->pcur))) {
840 page_zip_write_trx_id_and_roll_ptr(
841 page_zip, rec, offsets, trx_id_pos,
842 0, 1ULL << ROLL_PTR_INSERT_FLAG_POS,
843 mtr);
844 } else {
845 ulint len;
846 byte* ptr = rec_get_nth_field(
847 rec, offsets, trx_id_pos, &len);
848 ut_ad(len == DATA_TRX_ID_LEN);
849 mlog_write_string(ptr, reset_trx_id,
850 sizeof reset_trx_id, mtr);
851 }
852 }
853 }
854
855 mtr->commit();
856 }
857
858 /***********************************************************//**
859 Purges an update of an existing record. Also purges an update of a delete
860 marked record if that record contained an externally stored field. */
861 static
862 void
row_purge_upd_exist_or_extern_func(const que_thr_t * thr,purge_node_t * node,trx_undo_rec_t * undo_rec)863 row_purge_upd_exist_or_extern_func(
864 /*===============================*/
865 #ifdef UNIV_DEBUG
866 const que_thr_t*thr, /*!< in: query thread */
867 #endif /* UNIV_DEBUG */
868 purge_node_t* node, /*!< in: row purge node */
869 trx_undo_rec_t* undo_rec) /*!< in: record to purge */
870 {
871 mem_heap_t* heap;
872
873 ut_ad(rw_lock_own(&dict_sys.latch, RW_LOCK_S)
874 || node->vcol_info.is_used());
875 ut_ad(!node->table->skip_alter_undo);
876
877 if (node->rec_type == TRX_UNDO_UPD_DEL_REC
878 || (node->cmpl_info & UPD_NODE_NO_ORD_CHANGE)) {
879
880 goto skip_secondaries;
881 }
882
883 heap = mem_heap_create(1024);
884
885 while (node->index != NULL) {
886 dict_table_skip_corrupt_index(node->index);
887
888 row_purge_skip_uncommitted_virtual_index(node->index);
889
890 if (!node->index) {
891 break;
892 }
893
894 if (row_upd_changes_ord_field_binary(node->index, node->update,
895 thr, NULL, NULL)) {
896 /* Build the older version of the index entry */
897 dtuple_t* entry = row_build_index_entry_low(
898 node->row, NULL, node->index,
899 heap, ROW_BUILD_FOR_PURGE);
900 row_purge_remove_sec_if_poss(node, node->index, entry);
901
902 if (node->vcol_op_failed()) {
903 ut_ad(!node->table);
904 mem_heap_free(heap);
905 return;
906 }
907 ut_ad(node->table);
908
909 mem_heap_empty(heap);
910 }
911
912 node->index = dict_table_get_next_index(node->index);
913 }
914
915 mem_heap_free(heap);
916
917 skip_secondaries:
918 mtr_t mtr;
919 dict_index_t* index = dict_table_get_first_index(node->table);
920 /* Free possible externally stored fields */
921 for (ulint i = 0; i < upd_get_n_fields(node->update); i++) {
922
923 const upd_field_t* ufield
924 = upd_get_nth_field(node->update, i);
925
926 if (dfield_is_ext(&ufield->new_val)) {
927 trx_rseg_t* rseg;
928 buf_block_t* block;
929 ulint internal_offset;
930 byte* data_field;
931 ibool is_insert;
932 ulint rseg_id;
933 ulint page_no;
934 ulint offset;
935
936 /* We use the fact that new_val points to
937 undo_rec and get thus the offset of
938 dfield data inside the undo record. Then we
939 can calculate from node->roll_ptr the file
940 address of the new_val data */
941
942 internal_offset = ulint(
943 static_cast<const byte*>
944 (dfield_get_data(&ufield->new_val))
945 - undo_rec);
946
947 ut_a(internal_offset < srv_page_size);
948
949 trx_undo_decode_roll_ptr(node->roll_ptr,
950 &is_insert, &rseg_id,
951 &page_no, &offset);
952
953 rseg = trx_sys.rseg_array[rseg_id];
954
955 ut_a(rseg != NULL);
956 ut_ad(rseg->id == rseg_id);
957 ut_ad(rseg->is_persistent());
958
959 mtr.start();
960
961 /* We have to acquire an SX-latch to the clustered
962 index tree (exclude other tree changes) */
963
964 mtr_sx_lock_index(index, &mtr);
965
966 index->set_modified(mtr);
967
968 /* NOTE: we must also acquire an X-latch to the
969 root page of the tree. We will need it when we
970 free pages from the tree. If the tree is of height 1,
971 the tree X-latch does NOT protect the root page,
972 because it is also a leaf page. Since we will have a
973 latch on an undo log page, we would break the
974 latching order if we would only later latch the
975 root page of such a tree! */
976
977 btr_root_get(index, &mtr);
978
979 block = buf_page_get(
980 page_id_t(rseg->space->id, page_no),
981 0, RW_X_LATCH, &mtr);
982
983 buf_block_dbg_add_level(block, SYNC_TRX_UNDO_PAGE);
984
985 data_field = buf_block_get_frame(block)
986 + offset + internal_offset;
987
988 ut_a(dfield_get_len(&ufield->new_val)
989 >= BTR_EXTERN_FIELD_REF_SIZE);
990 btr_free_externally_stored_field(
991 index,
992 data_field + dfield_get_len(&ufield->new_val)
993 - BTR_EXTERN_FIELD_REF_SIZE,
994 NULL, NULL, NULL, 0, false, &mtr);
995 mtr.commit();
996 }
997 }
998
999 row_purge_reset_trx_id(node, &mtr);
1000 }
1001
1002 #ifdef UNIV_DEBUG
1003 # define row_purge_upd_exist_or_extern(thr,node,undo_rec) \
1004 row_purge_upd_exist_or_extern_func(thr,node,undo_rec)
1005 #else /* UNIV_DEBUG */
1006 # define row_purge_upd_exist_or_extern(thr,node,undo_rec) \
1007 row_purge_upd_exist_or_extern_func(node,undo_rec)
1008 #endif /* UNIV_DEBUG */
1009
1010 /***********************************************************//**
1011 Parses the row reference and other info in a modify undo log record.
1012 @return true if purge operation required */
1013 static
1014 bool
row_purge_parse_undo_rec(purge_node_t * node,trx_undo_rec_t * undo_rec,bool * updated_extern,que_thr_t * thr)1015 row_purge_parse_undo_rec(
1016 /*=====================*/
1017 purge_node_t* node, /*!< in: row undo node */
1018 trx_undo_rec_t* undo_rec, /*!< in: record to purge */
1019 bool* updated_extern, /*!< out: true if an externally
1020 stored field was updated */
1021 que_thr_t* thr) /*!< in: query thread */
1022 {
1023 dict_index_t* clust_index;
1024 byte* ptr;
1025 undo_no_t undo_no;
1026 table_id_t table_id;
1027 roll_ptr_t roll_ptr;
1028 ulint info_bits;
1029 ulint type;
1030
1031 ut_ad(node != NULL);
1032 ut_ad(thr != NULL);
1033
1034 ptr = trx_undo_rec_get_pars(
1035 undo_rec, &type, &node->cmpl_info,
1036 updated_extern, &undo_no, &table_id);
1037
1038 node->rec_type = type;
1039
1040 switch (type) {
1041 case TRX_UNDO_RENAME_TABLE:
1042 return false;
1043 case TRX_UNDO_INSERT_METADATA:
1044 case TRX_UNDO_INSERT_REC:
1045 /* These records do not store any transaction identifier.
1046
1047 FIXME: Update SYS_TABLES.ID on both DISCARD TABLESPACE
1048 and IMPORT TABLESPACE to get rid of the repeated lookups! */
1049 node->trx_id = TRX_ID_MAX;
1050 break;
1051 default:
1052 #ifdef UNIV_DEBUG
1053 ut_ad(!"unknown undo log record type");
1054 return false;
1055 case TRX_UNDO_UPD_DEL_REC:
1056 case TRX_UNDO_UPD_EXIST_REC:
1057 case TRX_UNDO_DEL_MARK_REC:
1058 #endif /* UNIV_DEBUG */
1059 ptr = trx_undo_update_rec_get_sys_cols(ptr, &node->trx_id,
1060 &roll_ptr, &info_bits);
1061 break;
1062 }
1063
1064 if (node->is_skipped(table_id)) {
1065 return false;
1066 }
1067
1068 /* Prevent DROP TABLE etc. from running when we are doing the purge
1069 for this row */
1070
1071 try_again:
1072 rw_lock_s_lock_inline(&dict_sys.latch, 0, __FILE__, __LINE__);
1073
1074 node->table = dict_table_open_on_id(
1075 table_id, FALSE, DICT_TABLE_OP_NORMAL);
1076
1077 trx_id_t trx_id = TRX_ID_MAX;
1078
1079 if (node->table == NULL) {
1080 /* The table has been dropped: no need to do purge */
1081 goto err_exit;
1082 }
1083
1084 ut_ad(!node->table->is_temporary());
1085
1086 if (!fil_table_accessible(node->table)) {
1087 goto inaccessible;
1088 }
1089
1090 switch (type) {
1091 case TRX_UNDO_INSERT_METADATA:
1092 case TRX_UNDO_INSERT_REC:
1093 break;
1094 default:
1095 if (!node->table->n_v_cols || node->table->vc_templ
1096 || !dict_table_has_indexed_v_cols(node->table)) {
1097 break;
1098 }
1099 /* Need server fully up for virtual column computation */
1100 if (!mysqld_server_started) {
1101
1102 dict_table_close(node->table, FALSE, FALSE);
1103 rw_lock_s_unlock(&dict_sys.latch);
1104 if (srv_shutdown_state > SRV_SHUTDOWN_INITIATED) {
1105 return(false);
1106 }
1107 os_thread_sleep(1000000);
1108 goto try_again;
1109 }
1110
1111 node->vcol_info.set_requested();
1112 node->vcol_info.set_used();
1113 node->vcol_info.set_table(innobase_init_vc_templ(node->table));
1114 node->vcol_info.set_used();
1115 }
1116
1117 clust_index = dict_table_get_first_index(node->table);
1118
1119 if (!clust_index || clust_index->is_corrupted()) {
1120 /* The table was corrupt in the data dictionary.
1121 dict_set_corrupted() works on an index, and
1122 we do not have an index to call it with. */
1123 inaccessible:
1124 DBUG_ASSERT(table_id == node->table->id);
1125 trx_id = node->table->def_trx_id;
1126 if (!trx_id) {
1127 trx_id = TRX_ID_MAX;
1128 }
1129
1130 dict_table_close(node->table, FALSE, FALSE);
1131 node->table = NULL;
1132 err_exit:
1133 rw_lock_s_unlock(&dict_sys.latch);
1134 node->skip(table_id, trx_id);
1135 return(false);
1136 }
1137
1138 if (type == TRX_UNDO_INSERT_METADATA) {
1139 node->ref = &trx_undo_metadata;
1140 return(true);
1141 }
1142
1143 ptr = trx_undo_rec_get_row_ref(ptr, clust_index, &(node->ref),
1144 node->heap);
1145
1146 if (type == TRX_UNDO_INSERT_REC) {
1147 return(true);
1148 }
1149
1150 ptr = trx_undo_update_rec_get_update(ptr, clust_index, type,
1151 node->trx_id,
1152 roll_ptr, info_bits,
1153 node->heap, &(node->update));
1154
1155 /* Read to the partial row the fields that occur in indexes */
1156
1157 if (!(node->cmpl_info & UPD_NODE_NO_ORD_CHANGE)) {
1158 ut_ad(!(node->update->info_bits & REC_INFO_MIN_REC_FLAG));
1159 ptr = trx_undo_rec_get_partial_row(
1160 ptr, clust_index, node->update, &node->row,
1161 type == TRX_UNDO_UPD_DEL_REC,
1162 node->heap);
1163 } else if (node->update->info_bits & REC_INFO_MIN_REC_FLAG) {
1164 node->ref = &trx_undo_metadata;
1165 }
1166
1167 return(true);
1168 }
1169
1170 /***********************************************************//**
1171 Purges the parsed record.
1172 @return true if purged, false if skipped */
1173 static MY_ATTRIBUTE((nonnull, warn_unused_result))
1174 bool
row_purge_record_func(purge_node_t * node,trx_undo_rec_t * undo_rec,const que_thr_t * thr,bool updated_extern)1175 row_purge_record_func(
1176 /*==================*/
1177 purge_node_t* node, /*!< in: row purge node */
1178 trx_undo_rec_t* undo_rec, /*!< in: record to purge */
1179 #if defined UNIV_DEBUG || defined WITH_WSREP
1180 const que_thr_t*thr, /*!< in: query thread */
1181 #endif /* UNIV_DEBUG || WITH_WSREP */
1182 bool updated_extern) /*!< in: whether external columns
1183 were updated */
1184 {
1185 dict_index_t* clust_index;
1186 bool purged = true;
1187
1188 ut_ad(!node->found_clust);
1189 ut_ad(!node->table->skip_alter_undo);
1190
1191 clust_index = dict_table_get_first_index(node->table);
1192
1193 node->index = dict_table_get_next_index(clust_index);
1194 ut_ad(!trx_undo_roll_ptr_is_insert(node->roll_ptr));
1195
1196 switch (node->rec_type) {
1197 case TRX_UNDO_DEL_MARK_REC:
1198 purged = row_purge_del_mark(node);
1199 if (purged) {
1200 if (node->table->stat_initialized
1201 && srv_stats_include_delete_marked) {
1202 dict_stats_update_if_needed(
1203 node->table, *thr->graph->trx);
1204 }
1205 MONITOR_INC(MONITOR_N_DEL_ROW_PURGE);
1206 }
1207 break;
1208 case TRX_UNDO_INSERT_METADATA:
1209 case TRX_UNDO_INSERT_REC:
1210 node->roll_ptr |= 1ULL << ROLL_PTR_INSERT_FLAG_POS;
1211 /* fall through */
1212 default:
1213 if (!updated_extern) {
1214 mtr_t mtr;
1215 row_purge_reset_trx_id(node, &mtr);
1216 break;
1217 }
1218 /* fall through */
1219 case TRX_UNDO_UPD_EXIST_REC:
1220 row_purge_upd_exist_or_extern(thr, node, undo_rec);
1221 MONITOR_INC(MONITOR_N_UPD_EXIST_EXTERN);
1222 break;
1223 }
1224
1225 if (node->found_clust) {
1226 btr_pcur_close(&node->pcur);
1227 node->found_clust = FALSE;
1228 }
1229
1230 if (node->table != NULL) {
1231 dict_table_close(node->table, FALSE, FALSE);
1232 node->table = NULL;
1233 }
1234
1235 return(purged);
1236 }
1237
1238 #if defined UNIV_DEBUG || defined WITH_WSREP
1239 # define row_purge_record(node,undo_rec,thr,updated_extern) \
1240 row_purge_record_func(node,undo_rec,thr,updated_extern)
1241 #else /* UNIV_DEBUG || WITH_WSREP */
1242 # define row_purge_record(node,undo_rec,thr,updated_extern) \
1243 row_purge_record_func(node,undo_rec,updated_extern)
1244 #endif /* UNIV_DEBUG || WITH_WSREP */
1245
1246 /***********************************************************//**
1247 Fetches an undo log record and does the purge for the recorded operation.
1248 If none left, or the current purge completed, returns the control to the
1249 parent node, which is always a query thread node. */
1250 static MY_ATTRIBUTE((nonnull))
1251 void
row_purge(purge_node_t * node,trx_undo_rec_t * undo_rec,que_thr_t * thr)1252 row_purge(
1253 /*======*/
1254 purge_node_t* node, /*!< in: row purge node */
1255 trx_undo_rec_t* undo_rec, /*!< in: record to purge */
1256 que_thr_t* thr) /*!< in: query thread */
1257 {
1258 if (undo_rec != &trx_purge_dummy_rec) {
1259 bool updated_extern;
1260
1261 while (row_purge_parse_undo_rec(
1262 node, undo_rec, &updated_extern, thr)) {
1263
1264 bool purged = row_purge_record(
1265 node, undo_rec, thr, updated_extern);
1266
1267 if (!node->vcol_info.is_used()) {
1268 rw_lock_s_unlock(&dict_sys.latch);
1269 }
1270
1271 ut_ad(!rw_lock_own(&dict_sys.latch, RW_LOCK_S));
1272
1273 if (purged
1274 || srv_shutdown_state > SRV_SHUTDOWN_INITIATED
1275 || node->vcol_op_failed()) {
1276 return;
1277 }
1278
1279 /* Retry the purge in a second. */
1280 os_thread_sleep(1000000);
1281 }
1282 }
1283 }
1284
1285 /***********************************************************//**
1286 Reset the purge query thread. */
1287 UNIV_INLINE
1288 void
row_purge_end(que_thr_t * thr)1289 row_purge_end(
1290 /*==========*/
1291 que_thr_t* thr) /*!< in: query thread */
1292 {
1293 ut_ad(thr);
1294
1295 thr->run_node = static_cast<purge_node_t*>(thr->run_node)->end();
1296
1297 ut_a(thr->run_node != NULL);
1298 }
1299
1300 /***********************************************************//**
1301 Does the purge operation for a single undo log record. This is a high-level
1302 function used in an SQL execution graph.
1303 @return query thread to run next or NULL */
1304 que_thr_t*
row_purge_step(que_thr_t * thr)1305 row_purge_step(
1306 /*===========*/
1307 que_thr_t* thr) /*!< in: query thread */
1308 {
1309 purge_node_t* node;
1310
1311 node = static_cast<purge_node_t*>(thr->run_node);
1312
1313 node->start();
1314
1315 #ifdef UNIV_DEBUG
1316 srv_slot_t *slot = thr->thread_slot;
1317 ut_ad(slot);
1318
1319 rw_lock_x_lock(&slot->debug_sync_lock);
1320 while (UT_LIST_GET_LEN(slot->debug_sync)) {
1321 srv_slot_t::debug_sync_t *sync =
1322 UT_LIST_GET_FIRST(slot->debug_sync);
1323 bool result = debug_sync_set_action(current_thd,
1324 sync->str,
1325 strlen(sync->str));
1326 ut_a(!result);
1327
1328 UT_LIST_REMOVE(slot->debug_sync, sync);
1329 ut_free(sync);
1330 }
1331 rw_lock_x_unlock(&slot->debug_sync_lock);
1332 #endif
1333
1334 if (!(node->undo_recs == NULL || ib_vector_is_empty(node->undo_recs))) {
1335 trx_purge_rec_t*purge_rec;
1336
1337 purge_rec = static_cast<trx_purge_rec_t*>(
1338 ib_vector_pop(node->undo_recs));
1339
1340 node->roll_ptr = purge_rec->roll_ptr;
1341
1342 row_purge(node, purge_rec->undo_rec, thr);
1343
1344 if (ib_vector_is_empty(node->undo_recs)) {
1345 row_purge_end(thr);
1346 } else {
1347 thr->run_node = node;
1348 node->vcol_info.reset();
1349 }
1350 } else {
1351 row_purge_end(thr);
1352 }
1353
1354 innobase_reset_background_thd(thr_get_trx(thr)->mysql_thd);
1355
1356 return(thr);
1357 }
1358
1359 #ifdef UNIV_DEBUG
1360 /***********************************************************//**
1361 Validate the persisent cursor. The purge node has two references
1362 to the clustered index record - one via the ref member, and the
1363 other via the persistent cursor. These two references must match
1364 each other if the found_clust flag is set.
1365 @return true if the stored copy of persistent cursor is consistent
1366 with the ref member.*/
1367 bool
validate_pcur()1368 purge_node_t::validate_pcur()
1369 {
1370 if (!found_clust) {
1371 return(true);
1372 }
1373
1374 if (index == NULL) {
1375 return(true);
1376 }
1377
1378 if (index->type == DICT_FTS) {
1379 return(true);
1380 }
1381
1382 if (!pcur.old_stored) {
1383 return(true);
1384 }
1385
1386 dict_index_t* clust_index = pcur.btr_cur.index;
1387
1388 rec_offs* offsets = rec_get_offsets(
1389 pcur.old_rec, clust_index, NULL, pcur.old_n_core_fields,
1390 pcur.old_n_fields, &heap);
1391
1392 /* Here we are comparing the purge ref record and the stored initial
1393 part in persistent cursor. Both cases we store n_uniq fields of the
1394 cluster index and so it is fine to do the comparison. We note this
1395 dependency here as pcur and ref belong to different modules. */
1396 int st = cmp_dtuple_rec(ref, pcur.old_rec, offsets);
1397
1398 if (st != 0) {
1399 ib::error() << "Purge node pcur validation failed";
1400 ib::error() << rec_printer(ref).str();
1401 ib::error() << rec_printer(pcur.old_rec, offsets).str();
1402 return(false);
1403 }
1404
1405 return(true);
1406 }
1407 #endif /* UNIV_DEBUG */
1408