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