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