1 /*****************************************************************************
2
3 Copyright (c) 1997, 2015, Oracle and/or its affiliates. All Rights Reserved.
4
5 This program is free software; you can redistribute it and/or modify it under
6 the terms of the GNU General Public License as published by the Free Software
7 Foundation; version 2 of the License.
8
9 This program is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License along with
14 this program; if not, write to the Free Software Foundation, Inc.,
15 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA
16
17 *****************************************************************************/
18
19 /**************************************************//**
20 @file row/row0purge.c
21 Purge obsolete records
22
23 Created 3/14/1997 Heikki Tuuri
24 *******************************************************/
25
26 #include "row0purge.h"
27
28 #ifdef UNIV_NONINL
29 #include "row0purge.ic"
30 #endif
31
32 #include "fsp0fsp.h"
33 #include "mach0data.h"
34 #include "trx0rseg.h"
35 #include "trx0trx.h"
36 #include "trx0roll.h"
37 #include "trx0undo.h"
38 #include "trx0purge.h"
39 #include "trx0rec.h"
40 #include "que0que.h"
41 #include "row0row.h"
42 #include "row0upd.h"
43 #include "row0vers.h"
44 #include "row0mysql.h"
45 #include "log0log.h"
46 #include "rem0cmp.h"
47
48 /*************************************************************************
49 IMPORTANT NOTE: Any operation that generates redo MUST check that there
50 is enough space in the redo log before for that operation. This is
51 done by calling log_free_check(). The reason for checking the
52 availability of the redo log space before the start of the operation is
53 that we MUST not hold any synchonization objects when performing the
54 check.
55 If you make a change in this module make sure that no codepath is
56 introduced where a call to log_free_check() is bypassed. */
57
58 /********************************************************************//**
59 Creates a purge node to a query graph.
60 @return own: purge node */
61 UNIV_INTERN
62 purge_node_t*
row_purge_node_create(que_thr_t * parent,mem_heap_t * heap)63 row_purge_node_create(
64 /*==================*/
65 que_thr_t* parent, /*!< in: parent node, i.e., a thr node */
66 mem_heap_t* heap) /*!< in: memory heap where created */
67 {
68 purge_node_t* node;
69
70 ut_ad(parent && heap);
71
72 node = mem_heap_alloc(heap, sizeof(purge_node_t));
73
74 node->common.type = QUE_NODE_PURGE;
75 node->common.parent = parent;
76
77 node->heap = mem_heap_create(256);
78
79 return(node);
80 }
81
82 /***********************************************************//**
83 Repositions the pcur in the purge node on the clustered index record,
84 if found. If the record is not found, close pcur.
85 @return TRUE if the record was found */
86 static
87 ibool
row_purge_reposition_pcur(ulint mode,purge_node_t * node,mtr_t * mtr)88 row_purge_reposition_pcur(
89 /*======================*/
90 ulint mode, /*!< in: latching mode */
91 purge_node_t* node, /*!< in: row purge node */
92 mtr_t* mtr) /*!< in: mtr */
93 {
94 if (node->found_clust) {
95 ut_ad(row_purge_validate_pcur(node));
96
97 node->found_clust = btr_pcur_restore_position(
98 mode, &(node->pcur), mtr);
99
100 } else {
101
102 node->found_clust = row_search_on_row_ref(
103 &(node->pcur), mode, node->table, node->ref, mtr);
104
105 if (node->found_clust) {
106 btr_pcur_store_position(&(node->pcur), mtr);
107 }
108 }
109
110 /* Close the current cursor if we fail to position it correctly. */
111 if (!node->found_clust) {
112 btr_pcur_close(&node->pcur);
113 }
114
115 return(node->found_clust);
116 }
117
118 /***********************************************************//**
119 Removes a delete marked clustered index record if possible.
120 @return TRUE if success, or if not found, or if modified after the
121 delete marking */
122 static
123 ibool
row_purge_remove_clust_if_poss_low(purge_node_t * node,ulint mode)124 row_purge_remove_clust_if_poss_low(
125 /*===============================*/
126 purge_node_t* node, /*!< in: row purge node */
127 ulint mode) /*!< in: BTR_MODIFY_LEAF or BTR_MODIFY_TREE */
128 {
129 dict_index_t* index;
130 btr_pcur_t* pcur;
131 btr_cur_t* btr_cur;
132 ibool success;
133 ulint err;
134 mtr_t mtr;
135 rec_t* rec;
136 mem_heap_t* heap = NULL;
137 ulint offsets_[REC_OFFS_NORMAL_SIZE];
138 rec_offs_init(offsets_);
139
140 index = dict_table_get_first_index(node->table);
141
142 pcur = &(node->pcur);
143 btr_cur = btr_pcur_get_btr_cur(pcur);
144
145 log_free_check();
146 mtr_start(&mtr);
147
148 success = row_purge_reposition_pcur(mode, node, &mtr);
149
150 if (!success) {
151 /* The record is already removed */
152 /* Persistent cursor is closed if reposition fails. */
153 mtr_commit(&mtr);
154
155 return(TRUE);
156 }
157
158 rec = btr_pcur_get_rec(pcur);
159
160 if (node->roll_ptr != row_get_rec_roll_ptr(
161 rec, index, rec_get_offsets(rec, index, offsets_,
162 ULINT_UNDEFINED, &heap))) {
163 if (UNIV_LIKELY_NULL(heap)) {
164 mem_heap_free(heap);
165 }
166 /* Someone else has modified the record later: do not remove */
167 btr_pcur_commit_specify_mtr(pcur, &mtr);
168
169 return(TRUE);
170 }
171
172 if (UNIV_LIKELY_NULL(heap)) {
173 mem_heap_free(heap);
174 }
175
176 if (mode == BTR_MODIFY_LEAF) {
177 success = btr_cur_optimistic_delete(btr_cur, &mtr);
178 } else {
179 ut_ad(mode == BTR_MODIFY_TREE);
180 btr_cur_pessimistic_delete(&err, FALSE, btr_cur,
181 RB_NONE, &mtr);
182
183 if (err == DB_SUCCESS) {
184 success = TRUE;
185 } else if (err == DB_OUT_OF_FILE_SPACE) {
186 success = FALSE;
187 } else {
188 ut_error;
189 }
190 }
191
192 btr_pcur_commit_specify_mtr(pcur, &mtr);
193
194 return(success);
195 }
196
197 /***********************************************************//**
198 Removes a clustered index record if it has not been modified after the delete
199 marking. */
200 static
201 void
row_purge_remove_clust_if_poss(purge_node_t * node)202 row_purge_remove_clust_if_poss(
203 /*===========================*/
204 purge_node_t* node) /*!< in: row purge node */
205 {
206 ibool success;
207 ulint n_tries = 0;
208
209 /* fputs("Purge: Removing clustered record\n", stderr); */
210
211 success = row_purge_remove_clust_if_poss_low(node, BTR_MODIFY_LEAF);
212 if (success) {
213
214 return;
215 }
216 retry:
217 success = row_purge_remove_clust_if_poss_low(node, BTR_MODIFY_TREE);
218 /* The delete operation may fail if we have little
219 file space left: TODO: easiest to crash the database
220 and restart with more file space */
221
222 if (!success && n_tries < BTR_CUR_RETRY_DELETE_N_TIMES) {
223 n_tries++;
224
225 os_thread_sleep(BTR_CUR_RETRY_SLEEP_TIME);
226
227 goto retry;
228 }
229
230 ut_a(success);
231 }
232
233 /***********************************************************//**
234 Determines if it is possible to remove a secondary index entry.
235 Removal is possible if the secondary index entry does not refer to any
236 not delete marked version of a clustered index record where DB_TRX_ID
237 is newer than the purge view.
238
239 NOTE: This function should only be called by the purge thread, only
240 while holding a latch on the leaf page of the secondary index entry
241 (or keeping the buffer pool watch on the page). It is possible that
242 this function first returns TRUE and then FALSE, if a user transaction
243 inserts a record that the secondary index entry would refer to.
244 However, in that case, the user transaction would also re-insert the
245 secondary index entry after purge has removed it and released the leaf
246 page latch.
247 @return TRUE if the secondary index record can be purged */
248 UNIV_INTERN
249 ibool
row_purge_poss_sec(purge_node_t * node,dict_index_t * index,const dtuple_t * entry)250 row_purge_poss_sec(
251 /*===============*/
252 purge_node_t* node, /*!< in/out: row purge node */
253 dict_index_t* index, /*!< in: secondary index */
254 const dtuple_t* entry) /*!< in: secondary index entry */
255 {
256 ibool can_delete;
257 mtr_t mtr;
258
259 ut_ad(!dict_index_is_clust(index));
260 mtr_start(&mtr);
261
262 can_delete = !row_purge_reposition_pcur(BTR_SEARCH_LEAF, node, &mtr)
263 || !row_vers_old_has_index_entry(TRUE,
264 btr_pcur_get_rec(&node->pcur),
265 &mtr, index, entry);
266
267 /* Persistent cursor is closed if reposition fails. */
268 if (node->found_clust) {
269 btr_pcur_commit_specify_mtr(&node->pcur, &mtr);
270 } else {
271 mtr_commit(&mtr);
272 }
273
274 return(can_delete);
275 }
276
277 /***************************************************************
278 Removes a secondary index entry if possible, by modifying the
279 index tree. Does not try to buffer the delete.
280 @return TRUE if success or if not found */
281 static
282 ibool
row_purge_remove_sec_if_poss_tree(purge_node_t * node,dict_index_t * index,const dtuple_t * entry)283 row_purge_remove_sec_if_poss_tree(
284 /*==============================*/
285 purge_node_t* node, /*!< in: row purge node */
286 dict_index_t* index, /*!< in: index */
287 const dtuple_t* entry) /*!< in: index entry */
288 {
289 btr_pcur_t pcur;
290 btr_cur_t* btr_cur;
291 ibool success = TRUE;
292 ulint err;
293 mtr_t mtr;
294 enum row_search_result search_result;
295
296 log_free_check();
297 mtr_start(&mtr);
298
299 search_result = row_search_index_entry(index, entry, BTR_MODIFY_TREE,
300 &pcur, &mtr);
301
302 switch (search_result) {
303 case ROW_NOT_FOUND:
304 /* Not found. This is a legitimate condition. In a
305 rollback, InnoDB will remove secondary recs that would
306 be purged anyway. Then the actual purge will not find
307 the secondary index record. Also, the purge itself is
308 eager: if it comes to consider a secondary index
309 record, and notices it does not need to exist in the
310 index, it will remove it. Then if/when the purge
311 comes to consider the secondary index record a second
312 time, it will not exist any more in the index. */
313
314 /* fputs("PURGE:........sec entry not found\n", stderr); */
315 /* dtuple_print(stderr, entry); */
316 goto func_exit;
317 case ROW_FOUND:
318 break;
319 case ROW_BUFFERED:
320 case ROW_NOT_DELETED_REF:
321 /* These are invalid outcomes, because the mode passed
322 to row_search_index_entry() did not include any of the
323 flags BTR_INSERT, BTR_DELETE, or BTR_DELETE_MARK. */
324 ut_error;
325 }
326
327 btr_cur = btr_pcur_get_btr_cur(&pcur);
328
329 /* We should remove the index record if no later version of the row,
330 which cannot be purged yet, requires its existence. If some requires,
331 we should do nothing. */
332
333 if (row_purge_poss_sec(node, index, entry)) {
334 /* Remove the index record, which should have been
335 marked for deletion. */
336 ut_ad(REC_INFO_DELETED_FLAG
337 & rec_get_info_bits(btr_cur_get_rec(btr_cur),
338 dict_table_is_comp(index->table)));
339
340 btr_cur_pessimistic_delete(&err, FALSE, btr_cur,
341 RB_NONE, &mtr);
342 switch (UNIV_EXPECT(err, DB_SUCCESS)) {
343 case DB_SUCCESS:
344 break;
345 case DB_OUT_OF_FILE_SPACE:
346 success = FALSE;
347 break;
348 default:
349 ut_error;
350 }
351 }
352
353 func_exit:
354 btr_pcur_close(&pcur);
355 mtr_commit(&mtr);
356
357 return(success);
358 }
359
360 /***************************************************************
361 Removes a secondary index entry without modifying the index tree,
362 if possible.
363 @return TRUE if success or if not found */
364 static
365 ibool
row_purge_remove_sec_if_poss_leaf(purge_node_t * node,dict_index_t * index,const dtuple_t * entry)366 row_purge_remove_sec_if_poss_leaf(
367 /*==============================*/
368 purge_node_t* node, /*!< in: row purge node */
369 dict_index_t* index, /*!< in: index */
370 const dtuple_t* entry) /*!< in: index entry */
371 {
372 mtr_t mtr;
373 btr_pcur_t pcur;
374 enum row_search_result search_result;
375
376 log_free_check();
377
378 mtr_start(&mtr);
379
380 /* Set the purge node for the call to row_purge_poss_sec(). */
381 pcur.btr_cur.purge_node = node;
382 /* Set the query thread, so that ibuf_insert_low() will be
383 able to invoke thd_get_trx(). */
384 pcur.btr_cur.thr = que_node_get_parent(node);
385
386 search_result = row_search_index_entry(
387 index, entry, BTR_MODIFY_LEAF | BTR_DELETE, &pcur, &mtr);
388
389 switch (search_result) {
390 ibool success;
391 case ROW_FOUND:
392 /* Before attempting to purge a record, check
393 if it is safe to do so. */
394 if (row_purge_poss_sec(node, index, entry)) {
395 btr_cur_t* btr_cur = btr_pcur_get_btr_cur(&pcur);
396
397 /* Only delete-marked records should be purged. */
398 ut_ad(REC_INFO_DELETED_FLAG
399 & rec_get_info_bits(
400 btr_cur_get_rec(btr_cur),
401 dict_table_is_comp(index->table)));
402
403 if (!btr_cur_optimistic_delete(btr_cur, &mtr)) {
404
405 /* The index entry could not be deleted. */
406 success = FALSE;
407 goto func_exit;
408 }
409 }
410 /* fall through (the index entry is still needed,
411 or the deletion succeeded) */
412 case ROW_NOT_DELETED_REF:
413 /* The index entry is still needed. */
414 case ROW_BUFFERED:
415 /* The deletion was buffered. */
416 case ROW_NOT_FOUND:
417 /* The index entry does not exist, nothing to do. */
418 success = TRUE;
419 func_exit:
420 btr_pcur_close(&pcur);
421 mtr_commit(&mtr);
422 return(success);
423 }
424
425 ut_error;
426 return(FALSE);
427 }
428
429 /***********************************************************//**
430 Removes a secondary index entry if possible. */
431 UNIV_INLINE
432 void
row_purge_remove_sec_if_poss(purge_node_t * node,dict_index_t * index,dtuple_t * entry)433 row_purge_remove_sec_if_poss(
434 /*=========================*/
435 purge_node_t* node, /*!< in: row purge node */
436 dict_index_t* index, /*!< in: index */
437 dtuple_t* entry) /*!< in: index entry */
438 {
439 ibool success;
440 ulint n_tries = 0;
441
442 /* fputs("Purge: Removing secondary record\n", stderr); */
443
444 if (row_purge_remove_sec_if_poss_leaf(node, index, entry)) {
445
446 return;
447 }
448 retry:
449 success = row_purge_remove_sec_if_poss_tree(node, index, entry);
450 /* The delete operation may fail if we have little
451 file space left: TODO: easiest to crash the database
452 and restart with more file space */
453
454 if (!success && n_tries < BTR_CUR_RETRY_DELETE_N_TIMES) {
455
456 n_tries++;
457
458 os_thread_sleep(BTR_CUR_RETRY_SLEEP_TIME);
459
460 goto retry;
461 }
462
463 ut_a(success);
464 }
465
466 /***********************************************************//**
467 Purges a delete marking of a record. */
468 static
469 void
row_purge_del_mark(purge_node_t * node)470 row_purge_del_mark(
471 /*===============*/
472 purge_node_t* node) /*!< in: row purge node */
473 {
474 mem_heap_t* heap;
475 dtuple_t* entry;
476 dict_index_t* index;
477
478 ut_ad(node);
479
480 heap = mem_heap_create(1024);
481
482 while (node->index != NULL) {
483 /* skip corrupted secondary index */
484 dict_table_skip_corrupt_index(node->index);
485
486 if (!node->index) {
487 break;
488 }
489
490 index = node->index;
491
492 /* Build the index entry */
493 entry = row_build_index_entry(node->row, NULL, index, heap);
494 ut_a(entry);
495 row_purge_remove_sec_if_poss(node, index, entry);
496
497 node->index = dict_table_get_next_index(node->index);
498 }
499
500 mem_heap_free(heap);
501
502 row_purge_remove_clust_if_poss(node);
503 }
504
505 /***********************************************************//**
506 Purges an update of an existing record. Also purges an update of a delete
507 marked record if that record contained an externally stored field. */
508 static
509 void
row_purge_upd_exist_or_extern_func(const que_thr_t * thr,purge_node_t * node)510 row_purge_upd_exist_or_extern_func(
511 /*===============================*/
512 #ifdef UNIV_DEBUG
513 const que_thr_t*thr, /*!< in: query thread */
514 #endif /* UNIV_DEBUG */
515 purge_node_t* node) /*!< in: row purge node */
516 {
517 mem_heap_t* heap;
518 dtuple_t* entry;
519 dict_index_t* index;
520 ibool is_insert;
521 ulint rseg_id;
522 ulint page_no;
523 ulint offset;
524 ulint i;
525 mtr_t mtr;
526
527 ut_ad(node);
528
529 if (node->rec_type == TRX_UNDO_UPD_DEL_REC
530 || (node->cmpl_info & UPD_NODE_NO_ORD_CHANGE)) {
531
532 goto skip_secondaries;
533 }
534
535 heap = mem_heap_create(1024);
536
537 while (node->index != NULL) {
538 dict_table_skip_corrupt_index(node->index);
539
540 if (!node->index) {
541 break;
542 }
543
544 index = node->index;
545
546 if (row_upd_changes_ord_field_binary(node->index, node->update,
547 thr, NULL, NULL)) {
548 /* Build the older version of the index entry */
549 entry = row_build_index_entry(node->row, NULL,
550 index, heap);
551 ut_a(entry);
552 row_purge_remove_sec_if_poss(node, index, entry);
553 }
554
555 node->index = dict_table_get_next_index(node->index);
556 }
557
558 mem_heap_free(heap);
559
560 skip_secondaries:
561 /* Free possible externally stored fields */
562 for (i = 0; i < upd_get_n_fields(node->update); i++) {
563
564 const upd_field_t* ufield
565 = upd_get_nth_field(node->update, i);
566
567 if (dfield_is_ext(&ufield->new_val)) {
568 buf_block_t* block;
569 ulint internal_offset;
570 byte* data_field;
571
572 /* We use the fact that new_val points to
573 node->undo_rec and get thus the offset of
574 dfield data inside the undo record. Then we
575 can calculate from node->roll_ptr the file
576 address of the new_val data */
577
578 internal_offset
579 = ((const byte*)
580 dfield_get_data(&ufield->new_val))
581 - node->undo_rec;
582
583 ut_a(internal_offset < UNIV_PAGE_SIZE);
584
585 trx_undo_decode_roll_ptr(node->roll_ptr,
586 &is_insert, &rseg_id,
587 &page_no, &offset);
588 mtr_start(&mtr);
589
590 /* We have to acquire an X-latch to the clustered
591 index tree */
592
593 index = dict_table_get_first_index(node->table);
594
595 mtr_x_lock(dict_index_get_lock(index), &mtr);
596
597 /* NOTE: we must also acquire an X-latch to the
598 root page of the tree. We will need it when we
599 free pages from the tree. If the tree is of height 1,
600 the tree X-latch does NOT protect the root page,
601 because it is also a leaf page. Since we will have a
602 latch on an undo log page, we would break the
603 latching order if we would only later latch the
604 root page of such a tree! */
605
606 btr_root_get(index, &mtr);
607
608 /* We assume in purge of externally stored fields
609 that the space id of the undo log record is 0! */
610
611 block = buf_page_get(0, 0, page_no, RW_X_LATCH, &mtr);
612 buf_block_dbg_add_level(block, SYNC_TRX_UNDO_PAGE);
613
614 data_field = buf_block_get_frame(block)
615 + offset + internal_offset;
616
617 ut_a(dfield_get_len(&ufield->new_val)
618 >= BTR_EXTERN_FIELD_REF_SIZE);
619 btr_free_externally_stored_field(
620 index,
621 data_field + dfield_get_len(&ufield->new_val)
622 - BTR_EXTERN_FIELD_REF_SIZE,
623 NULL, NULL, NULL, 0, RB_NONE, &mtr);
624 mtr_commit(&mtr);
625 }
626 }
627 }
628
629 #ifdef UNIV_DEBUG
630 # define row_purge_upd_exist_or_extern(thr,node) \
631 row_purge_upd_exist_or_extern_func(thr,node)
632 #else /* UNIV_DEBUG */
633 # define row_purge_upd_exist_or_extern(thr,node) \
634 row_purge_upd_exist_or_extern_func(node)
635 #endif /* UNIV_DEBUG */
636
637 /***********************************************************//**
638 Parses the row reference and other info in a modify undo log record.
639 @return TRUE if purge operation required: NOTE that then the CALLER
640 must unfreeze data dictionary! */
641 static
642 ibool
row_purge_parse_undo_rec(purge_node_t * node,ibool * updated_extern,que_thr_t * thr)643 row_purge_parse_undo_rec(
644 /*=====================*/
645 purge_node_t* node, /*!< in: row undo node */
646 ibool* updated_extern,
647 /*!< out: TRUE if an externally stored field
648 was updated */
649 que_thr_t* thr) /*!< in: query thread */
650 {
651 dict_index_t* clust_index;
652 byte* ptr;
653 trx_t* trx;
654 undo_no_t undo_no;
655 table_id_t table_id;
656 trx_id_t trx_id;
657 roll_ptr_t roll_ptr;
658 ulint info_bits;
659 ulint type;
660
661 ut_ad(node && thr);
662
663 trx = thr_get_trx(thr);
664
665 ptr = trx_undo_rec_get_pars(
666 node->undo_rec, &type, &node->cmpl_info,
667 updated_extern, &undo_no, &table_id);
668 node->rec_type = type;
669
670 if (type == TRX_UNDO_UPD_DEL_REC && !(*updated_extern)) {
671
672 return(FALSE);
673 }
674
675 ptr = trx_undo_update_rec_get_sys_cols(ptr, &trx_id, &roll_ptr,
676 &info_bits);
677 node->table = NULL;
678
679 if (type == TRX_UNDO_UPD_EXIST_REC
680 && node->cmpl_info & UPD_NODE_NO_ORD_CHANGE
681 && !(*updated_extern)) {
682
683 /* Purge requires no changes to indexes: we may return */
684
685 return(FALSE);
686 }
687
688 /* Prevent DROP TABLE etc. from running when we are doing the purge
689 for this row */
690
691 row_mysql_freeze_data_dictionary(trx);
692
693 mutex_enter(&(dict_sys->mutex));
694
695 node->table = dict_table_get_on_id_low(table_id);
696
697 mutex_exit(&(dict_sys->mutex));
698
699 if (node->table == NULL) {
700 /* The table has been dropped: no need to do purge */
701 err_exit:
702 row_mysql_unfreeze_data_dictionary(trx);
703 return(FALSE);
704 }
705
706 if (node->table->ibd_file_missing) {
707 /* We skip purge of missing .ibd files */
708
709 node->table = NULL;
710
711 goto err_exit;
712 }
713
714 clust_index = dict_table_get_first_index(node->table);
715
716 if (clust_index == NULL) {
717 /* The table was corrupt in the data dictionary */
718
719 goto err_exit;
720 }
721
722 ptr = trx_undo_rec_get_row_ref(ptr, clust_index, &(node->ref),
723 node->heap);
724
725 ptr = trx_undo_update_rec_get_update(ptr, clust_index, type, trx_id,
726 roll_ptr, info_bits, trx,
727 node->heap, &(node->update));
728
729 /* Read to the partial row the fields that occur in indexes */
730
731 if (!(node->cmpl_info & UPD_NODE_NO_ORD_CHANGE)) {
732 ptr = trx_undo_rec_get_partial_row(
733 ptr, clust_index, &node->row,
734 type == TRX_UNDO_UPD_DEL_REC,
735 node->heap);
736 }
737
738 return(TRUE);
739 }
740
741 /***********************************************************//**
742 Fetches an undo log record and does the purge for the recorded operation.
743 If none left, or the current purge completed, returns the control to the
744 parent node, which is always a query thread node. */
745 static __attribute__((nonnull))
746 void
row_purge(purge_node_t * node,que_thr_t * thr)747 row_purge(
748 /*======*/
749 purge_node_t* node, /*!< in: row purge node */
750 que_thr_t* thr) /*!< in: query thread */
751 {
752 ibool updated_extern;
753
754 ut_ad(node);
755 ut_ad(thr);
756
757 node->undo_rec = trx_purge_fetch_next_rec(&node->roll_ptr,
758 &node->reservation,
759 node->heap);
760 if (!node->undo_rec) {
761 /* Purge completed for this query thread */
762
763 thr->run_node = que_node_get_parent(node);
764
765 return;
766 }
767
768 if (node->undo_rec != &trx_purge_dummy_rec
769 && row_purge_parse_undo_rec(node, &updated_extern, thr)) {
770 node->found_clust = FALSE;
771
772 node->index = dict_table_get_next_index(
773 dict_table_get_first_index(node->table));
774
775 if (node->rec_type == TRX_UNDO_DEL_MARK_REC) {
776 row_purge_del_mark(node);
777
778 } else if (updated_extern
779 || node->rec_type == TRX_UNDO_UPD_EXIST_REC) {
780
781 row_purge_upd_exist_or_extern(thr, node);
782 }
783
784 if (node->found_clust) {
785 btr_pcur_close(&(node->pcur));
786 }
787
788 row_mysql_unfreeze_data_dictionary(thr_get_trx(thr));
789 }
790
791 /* Do some cleanup */
792 trx_purge_rec_release(node->reservation);
793 mem_heap_empty(node->heap);
794
795 thr->run_node = node;
796 }
797
798 /***********************************************************//**
799 Does the purge operation for a single undo log record. This is a high-level
800 function used in an SQL execution graph.
801 @return query thread to run next or NULL */
802 UNIV_INTERN
803 que_thr_t*
row_purge_step(que_thr_t * thr)804 row_purge_step(
805 /*===========*/
806 que_thr_t* thr) /*!< in: query thread */
807 {
808 purge_node_t* node;
809
810 ut_ad(thr);
811
812 node = thr->run_node;
813
814 ut_ad(que_node_get_type(node) == QUE_NODE_PURGE);
815
816 row_purge(node, thr);
817
818 return(thr);
819 }
820
821 #ifdef UNIV_DEBUG
822 /***********************************************************//**
823 Validate the persisent cursor in the purge node. The purge node has two
824 references to the clustered index record - one via the ref member, and the
825 other via the persistent cursor. These two references must match each
826 other if the found_clust flag is set.
827 @return true if the stored copy of persistent cursor is consistent
828 with the ref member.*/
829 ibool
row_purge_validate_pcur(purge_node_t * node)830 row_purge_validate_pcur(
831 purge_node_t* node)
832 {
833 dict_index_t* clust_index;
834 ulint* offsets;
835 int st;
836
837 if (!node->found_clust) {
838 return(TRUE);
839 }
840
841 if (node->index == NULL) {
842 return(TRUE);
843 }
844
845 if (node->pcur.old_stored != BTR_PCUR_OLD_STORED) {
846 return(TRUE);
847 }
848
849 clust_index = node->pcur.btr_cur.index;
850
851 offsets = rec_get_offsets(node->pcur.old_rec, clust_index, NULL,
852 node->pcur.old_n_fields, &node->heap);
853
854 /* Here we are comparing the purge ref record and the stored initial
855 part in persistent cursor. Both cases we store n_uniq fields of the
856 cluster index and so it is fine to do the comparison. We note this
857 dependency here as pcur and ref belong to different modules. */
858 st = cmp_dtuple_rec(node->ref, node->pcur.old_rec, offsets);
859
860 if (st != 0) {
861 fprintf(stderr, "Purge node pcur validation failed\n");
862 dtuple_print(stderr, node->ref);
863 rec_print(stderr, node->pcur.old_rec, clust_index);
864 return(FALSE);
865 }
866
867 return(TRUE);
868 }
869 #endif /* UNIV_DEBUG */
870