mirror of
				https://github.com/MariaDB/server.git
				synced 2025-11-03 20:36:16 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			1696 lines
		
	
	
	
		
			49 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1696 lines
		
	
	
	
		
			49 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/*****************************************************************************
 | 
						|
 | 
						|
Copyright (c) 1997, 2017, Oracle and/or its affiliates. All Rights Reserved.
 | 
						|
Copyright (c) 2017, 2023, MariaDB Corporation.
 | 
						|
 | 
						|
This program is free software; you can redistribute it and/or modify it under
 | 
						|
the terms of the GNU General Public License as published by the Free Software
 | 
						|
Foundation; version 2 of the License.
 | 
						|
 | 
						|
This program is distributed in the hope that it will be useful, but WITHOUT
 | 
						|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 | 
						|
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 | 
						|
 | 
						|
You should have received a copy of the GNU General Public License along with
 | 
						|
this program; if not, write to the Free Software Foundation, Inc.,
 | 
						|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA
 | 
						|
 | 
						|
*****************************************************************************/
 | 
						|
 | 
						|
/**************************************************//**
 | 
						|
@file row/row0purge.cc
 | 
						|
Purge obsolete records
 | 
						|
 | 
						|
Created 3/14/1997 Heikki Tuuri
 | 
						|
*******************************************************/
 | 
						|
 | 
						|
#include "row0purge.h"
 | 
						|
#include "btr0cur.h"
 | 
						|
#include "fsp0fsp.h"
 | 
						|
#include "mach0data.h"
 | 
						|
#include "dict0crea.h"
 | 
						|
#include "dict0stats.h"
 | 
						|
#include "trx0rseg.h"
 | 
						|
#include "trx0trx.h"
 | 
						|
#include "trx0roll.h"
 | 
						|
#include "trx0undo.h"
 | 
						|
#include "trx0purge.h"
 | 
						|
#include "trx0rec.h"
 | 
						|
#include "que0que.h"
 | 
						|
#include "row0row.h"
 | 
						|
#include "row0upd.h"
 | 
						|
#include "row0vers.h"
 | 
						|
#include "row0mysql.h"
 | 
						|
#include "log0log.h"
 | 
						|
#include "srv0mon.h"
 | 
						|
#include "srv0start.h"
 | 
						|
#include "handler.h"
 | 
						|
#include "ha_innodb.h"
 | 
						|
#include "fil0fil.h"
 | 
						|
#include "debug_sync.h"
 | 
						|
#include <mysql/service_thd_mdl.h>
 | 
						|
 | 
						|
/*************************************************************************
 | 
						|
IMPORTANT NOTE: Any operation that generates redo MUST check that there
 | 
						|
is enough space in the redo log before for that operation. This is
 | 
						|
done by calling log_free_check(). The reason for checking the
 | 
						|
availability of the redo log space before the start of the operation is
 | 
						|
that we MUST not hold any synchonization objects when performing the
 | 
						|
check.
 | 
						|
If you make a change in this module make sure that no codepath is
 | 
						|
introduced where a call to log_free_check() is bypassed. */
 | 
						|
 | 
						|
/***********************************************************//**
 | 
						|
Repositions the pcur in the purge node on the clustered index record,
 | 
						|
if found. If the record is not found, close pcur.
 | 
						|
@return TRUE if the record was found */
 | 
						|
static
 | 
						|
ibool
 | 
						|
row_purge_reposition_pcur(
 | 
						|
/*======================*/
 | 
						|
	btr_latch_mode	mode,	/*!< in: latching mode */
 | 
						|
	purge_node_t*	node,	/*!< in: row purge node */
 | 
						|
	mtr_t*		mtr)	/*!< in: mtr */
 | 
						|
{
 | 
						|
	if (node->found_clust) {
 | 
						|
		ut_ad(node->validate_pcur());
 | 
						|
 | 
						|
		node->found_clust =
 | 
						|
		  node->pcur.restore_position(mode, mtr) ==
 | 
						|
		    btr_pcur_t::SAME_ALL;
 | 
						|
 | 
						|
	} else {
 | 
						|
		node->found_clust = row_search_on_row_ref(
 | 
						|
			&node->pcur, mode, node->table, node->ref, mtr);
 | 
						|
 | 
						|
		if (node->found_clust) {
 | 
						|
			btr_pcur_store_position(&node->pcur, mtr);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/* Close the current cursor if we fail to position it correctly. */
 | 
						|
	if (!node->found_clust) {
 | 
						|
		btr_pcur_close(&node->pcur);
 | 
						|
	}
 | 
						|
 | 
						|
	return(node->found_clust);
 | 
						|
}
 | 
						|
 | 
						|
/***********************************************************//**
 | 
						|
Removes a delete marked clustered index record if possible.
 | 
						|
@retval true if the row was not found, or it was successfully removed
 | 
						|
@retval false if the row was modified after the delete marking */
 | 
						|
static MY_ATTRIBUTE((nonnull, warn_unused_result))
 | 
						|
bool
 | 
						|
row_purge_remove_clust_if_poss_low(
 | 
						|
/*===============================*/
 | 
						|
	purge_node_t*	node,	/*!< in/out: row purge node */
 | 
						|
	btr_latch_mode	mode)	/*!< in: BTR_MODIFY_LEAF or BTR_PURGE_TREE */
 | 
						|
{
 | 
						|
	dict_index_t* index = dict_table_get_first_index(node->table);
 | 
						|
	table_id_t table_id = 0;
 | 
						|
	index_id_t index_id = 0;
 | 
						|
	dict_table_t *table = nullptr;
 | 
						|
	pfs_os_file_t f = OS_FILE_CLOSED;
 | 
						|
 | 
						|
	if (table_id) {
 | 
						|
retry:
 | 
						|
		dict_sys.lock(SRW_LOCK_CALL);
 | 
						|
		table = dict_sys.find_table(table_id);
 | 
						|
		if (!table) {
 | 
						|
			dict_sys.unlock();
 | 
						|
		} else if (table->n_rec_locks) {
 | 
						|
			for (dict_index_t* ind = UT_LIST_GET_FIRST(
 | 
						|
				     table->indexes); ind;
 | 
						|
			     ind = UT_LIST_GET_NEXT(indexes, ind)) {
 | 
						|
				if (ind->id == index_id) {
 | 
						|
					lock_discard_for_index(*ind);
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	mtr_t mtr;
 | 
						|
	mtr.start();
 | 
						|
	index->set_modified(mtr);
 | 
						|
	log_free_check();
 | 
						|
	bool success = true;
 | 
						|
 | 
						|
	if (!row_purge_reposition_pcur(mode, node, &mtr)) {
 | 
						|
		/* The record was already removed. */
 | 
						|
removed:
 | 
						|
		mtr.commit();
 | 
						|
close_and_exit:
 | 
						|
		if (table) {
 | 
						|
			dict_sys.unlock();
 | 
						|
		}
 | 
						|
		return success;
 | 
						|
	}
 | 
						|
 | 
						|
	if (node->table->id == DICT_INDEXES_ID) {
 | 
						|
		/* If this is a record of the SYS_INDEXES table, then
 | 
						|
		we have to free the file segments of the index tree
 | 
						|
		associated with the index */
 | 
						|
		if (!table_id) {
 | 
						|
			const rec_t* rec = btr_pcur_get_rec(&node->pcur);
 | 
						|
 | 
						|
			table_id = mach_read_from_8(rec);
 | 
						|
			index_id = mach_read_from_8(rec + 8);
 | 
						|
			if (table_id) {
 | 
						|
				mtr.commit();
 | 
						|
				goto retry;
 | 
						|
			}
 | 
						|
			ut_ad("corrupted SYS_INDEXES record" == 0);
 | 
						|
		}
 | 
						|
 | 
						|
		const uint32_t space_id = dict_drop_index_tree(
 | 
						|
			&node->pcur, nullptr, &mtr);
 | 
						|
		if (space_id) {
 | 
						|
			if (table) {
 | 
						|
				if (table->get_ref_count() == 0) {
 | 
						|
					dict_sys.remove(table);
 | 
						|
				} else if (table->space_id == space_id) {
 | 
						|
					table->space = nullptr;
 | 
						|
					table->file_unreadable = true;
 | 
						|
				}
 | 
						|
				dict_sys.unlock();
 | 
						|
				table = nullptr;
 | 
						|
			}
 | 
						|
			f = fil_delete_tablespace(space_id);
 | 
						|
		}
 | 
						|
 | 
						|
		mtr.commit();
 | 
						|
 | 
						|
		if (table) {
 | 
						|
			dict_sys.unlock();
 | 
						|
			table = nullptr;
 | 
						|
		}
 | 
						|
 | 
						|
		mtr.start();
 | 
						|
		index->set_modified(mtr);
 | 
						|
 | 
						|
		if (!row_purge_reposition_pcur(mode, node, &mtr)) {
 | 
						|
			goto removed;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	rec_t* rec = btr_pcur_get_rec(&node->pcur);
 | 
						|
	rec_offs offsets_[REC_OFFS_NORMAL_SIZE];
 | 
						|
	rec_offs_init(offsets_);
 | 
						|
	mem_heap_t* heap = NULL;
 | 
						|
	rec_offs* offsets = rec_get_offsets(rec, index, offsets_,
 | 
						|
					    index->n_core_fields,
 | 
						|
					    ULINT_UNDEFINED, &heap);
 | 
						|
 | 
						|
	if (node->roll_ptr != row_get_rec_roll_ptr(rec, index, offsets)) {
 | 
						|
		/* Someone else has modified the record later: do not remove */
 | 
						|
		goto func_exit;
 | 
						|
	}
 | 
						|
 | 
						|
	ut_ad(rec_get_deleted_flag(rec, rec_offs_comp(offsets)));
 | 
						|
	/* In delete-marked records, DB_TRX_ID must
 | 
						|
	always refer to an existing undo log record. */
 | 
						|
	ut_ad(row_get_rec_trx_id(rec, index, offsets));
 | 
						|
 | 
						|
#ifdef ENABLED_DEBUG_SYNC
 | 
						|
  DBUG_EXECUTE_IF("enable_row_purge_remove_clust_if_poss_low_sync_point",
 | 
						|
                  debug_sync_set_action
 | 
						|
                  (current_thd,
 | 
						|
                   STRING_WITH_LEN(
 | 
						|
                     "now SIGNAL "
 | 
						|
                       "row_purge_remove_clust_if_poss_low_before_delete "
 | 
						|
                     "WAIT_FOR "
 | 
						|
                       "row_purge_remove_clust_if_poss_low_cont"));
 | 
						|
                  );
 | 
						|
#endif
 | 
						|
	if (mode == BTR_MODIFY_LEAF) {
 | 
						|
		success = DB_FAIL != btr_cur_optimistic_delete(
 | 
						|
			btr_pcur_get_btr_cur(&node->pcur), 0, &mtr);
 | 
						|
	} else {
 | 
						|
		dberr_t	err;
 | 
						|
		ut_ad(mode == BTR_PURGE_TREE);
 | 
						|
		btr_cur_pessimistic_delete(
 | 
						|
			&err, FALSE, btr_pcur_get_btr_cur(&node->pcur), 0,
 | 
						|
			false, &mtr);
 | 
						|
		success = err == DB_SUCCESS;
 | 
						|
	}
 | 
						|
 | 
						|
func_exit:
 | 
						|
	if (heap) {
 | 
						|
		mem_heap_free(heap);
 | 
						|
	}
 | 
						|
 | 
						|
	/* Persistent cursor is closed if reposition fails. */
 | 
						|
	if (node->found_clust) {
 | 
						|
		btr_pcur_commit_specify_mtr(&node->pcur, &mtr);
 | 
						|
	} else {
 | 
						|
		mtr_commit(&mtr);
 | 
						|
	}
 | 
						|
 | 
						|
	goto close_and_exit;
 | 
						|
}
 | 
						|
 | 
						|
/***********************************************************//**
 | 
						|
Removes a clustered index record if it has not been modified after the delete
 | 
						|
marking.
 | 
						|
@retval true if the row was not found, or it was successfully removed
 | 
						|
@retval false the purge needs to be suspended because of running out
 | 
						|
of file space. */
 | 
						|
static MY_ATTRIBUTE((nonnull, warn_unused_result))
 | 
						|
bool
 | 
						|
row_purge_remove_clust_if_poss(
 | 
						|
/*===========================*/
 | 
						|
	purge_node_t*	node)	/*!< in/out: row purge node */
 | 
						|
{
 | 
						|
	if (row_purge_remove_clust_if_poss_low(node, BTR_MODIFY_LEAF)) {
 | 
						|
		return(true);
 | 
						|
	}
 | 
						|
 | 
						|
	for (ulint n_tries = 0;
 | 
						|
	     n_tries < BTR_CUR_RETRY_DELETE_N_TIMES;
 | 
						|
	     n_tries++) {
 | 
						|
		if (row_purge_remove_clust_if_poss_low(node, BTR_PURGE_TREE)) {
 | 
						|
			return(true);
 | 
						|
		}
 | 
						|
 | 
						|
		std::this_thread::sleep_for(BTR_CUR_RETRY_SLEEP_TIME);
 | 
						|
	}
 | 
						|
 | 
						|
	return(false);
 | 
						|
}
 | 
						|
 | 
						|
/** Check a virtual column value index secondary virtual index matches
 | 
						|
that of current cluster index record, which is recreated from information
 | 
						|
stored in undo log
 | 
						|
@param[in]	rec		record in the clustered index
 | 
						|
@param[in]	icentry		the index entry built from a cluster row
 | 
						|
@param[in]	clust_index	cluster index
 | 
						|
@param[in]	clust_offsets	offsets on the cluster record
 | 
						|
@param[in]	index		the secondary index
 | 
						|
@param[in]	ientry		the secondary index entry
 | 
						|
@param[in]	roll_ptr	the rollback pointer for the purging record
 | 
						|
@param[in]	trx_id		trx id for the purging record
 | 
						|
@param[in,out]	mtr		mini-transaction
 | 
						|
@param[in,out]	v_row		dtuple holding the virtual rows (if needed)
 | 
						|
@return true if matches, false otherwise */
 | 
						|
static
 | 
						|
bool
 | 
						|
row_purge_vc_matches_cluster(
 | 
						|
	const rec_t*	rec,
 | 
						|
	const dtuple_t* icentry,
 | 
						|
	dict_index_t*	clust_index,
 | 
						|
	rec_offs*	clust_offsets,
 | 
						|
	dict_index_t*	index,
 | 
						|
	const dtuple_t* ientry,
 | 
						|
	roll_ptr_t	roll_ptr,
 | 
						|
	trx_id_t	trx_id,
 | 
						|
	mtr_t*		mtr,
 | 
						|
	dtuple_t**	vrow)
 | 
						|
{
 | 
						|
	const rec_t*	version;
 | 
						|
	rec_t*          prev_version;
 | 
						|
	mem_heap_t*	heap2;
 | 
						|
	mem_heap_t*	heap = NULL;
 | 
						|
	mem_heap_t*	tuple_heap;
 | 
						|
	ulint		num_v = dict_table_get_n_v_cols(index->table);
 | 
						|
	bool		compare[REC_MAX_N_FIELDS];
 | 
						|
	ulint		n_fields = dtuple_get_n_fields(ientry);
 | 
						|
	ulint		n_non_v_col = 0;
 | 
						|
	ulint		n_cmp_v_col = 0;
 | 
						|
	const dfield_t* field1;
 | 
						|
	dfield_t*	field2;
 | 
						|
	ulint		i;
 | 
						|
 | 
						|
	/* First compare non-virtual columns (primary keys) */
 | 
						|
	ut_ad(index->n_fields == n_fields);
 | 
						|
	ut_ad(n_fields == dtuple_get_n_fields(icentry));
 | 
						|
	ut_ad(mtr->memo_contains_page_flagged(rec,
 | 
						|
					      MTR_MEMO_PAGE_S_FIX
 | 
						|
					      | MTR_MEMO_PAGE_X_FIX));
 | 
						|
 | 
						|
	{
 | 
						|
		const dfield_t* a = ientry->fields;
 | 
						|
		const dfield_t* b = icentry->fields;
 | 
						|
 | 
						|
		for (const dict_field_t *ifield = index->fields,
 | 
						|
			     *const end = &index->fields[index->n_fields];
 | 
						|
		     ifield != end; ifield++, a++, b++) {
 | 
						|
			if (!ifield->col->is_virtual()) {
 | 
						|
				if (cmp_dfield_dfield(a, b)) {
 | 
						|
					return false;
 | 
						|
				}
 | 
						|
				n_non_v_col++;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	tuple_heap = mem_heap_create(1024);
 | 
						|
 | 
						|
	ut_ad(n_fields > n_non_v_col);
 | 
						|
 | 
						|
	*vrow = dtuple_create_with_vcol(tuple_heap, 0, num_v);
 | 
						|
	dtuple_init_v_fld(*vrow);
 | 
						|
 | 
						|
	for (i = 0; i < num_v; i++) {
 | 
						|
		dfield_get_type(dtuple_get_nth_v_field(*vrow, i))->mtype
 | 
						|
			 = DATA_MISSING;
 | 
						|
		compare[i] = false;
 | 
						|
	}
 | 
						|
 | 
						|
	version = rec;
 | 
						|
 | 
						|
	while (n_cmp_v_col < n_fields - n_non_v_col) {
 | 
						|
		heap2 = heap;
 | 
						|
		heap = mem_heap_create(1024);
 | 
						|
		roll_ptr_t	cur_roll_ptr = row_get_rec_roll_ptr(
 | 
						|
			version, clust_index, clust_offsets);
 | 
						|
 | 
						|
		ut_ad(cur_roll_ptr != 0);
 | 
						|
		ut_ad(roll_ptr != 0);
 | 
						|
 | 
						|
		trx_undo_prev_version_build(
 | 
						|
			version, clust_index, clust_offsets,
 | 
						|
			heap, &prev_version, mtr,
 | 
						|
			TRX_UNDO_PREV_IN_PURGE | TRX_UNDO_GET_OLD_V_VALUE,
 | 
						|
			nullptr, vrow);
 | 
						|
 | 
						|
		if (heap2) {
 | 
						|
			mem_heap_free(heap2);
 | 
						|
		}
 | 
						|
 | 
						|
		if (!prev_version) {
 | 
						|
			/* Versions end here */
 | 
						|
			goto func_exit;
 | 
						|
		}
 | 
						|
 | 
						|
		clust_offsets = rec_get_offsets(prev_version, clust_index,
 | 
						|
						NULL,
 | 
						|
						clust_index->n_core_fields,
 | 
						|
						ULINT_UNDEFINED, &heap);
 | 
						|
 | 
						|
		ulint	entry_len = dict_index_get_n_fields(index);
 | 
						|
 | 
						|
		for (i = 0; i < entry_len; i++) {
 | 
						|
			const dict_field_t*	ind_field
 | 
						|
				 = dict_index_get_nth_field(index, i);
 | 
						|
			const dict_col_t*	col = ind_field->col;
 | 
						|
			field1 = dtuple_get_nth_field(ientry, i);
 | 
						|
 | 
						|
			if (!col->is_virtual()) {
 | 
						|
				continue;
 | 
						|
			}
 | 
						|
 | 
						|
			const dict_v_col_t*     v_col
 | 
						|
                                = reinterpret_cast<const dict_v_col_t*>(col);
 | 
						|
			field2
 | 
						|
				= dtuple_get_nth_v_field(*vrow, v_col->v_pos);
 | 
						|
 | 
						|
			if ((dfield_get_type(field2)->mtype != DATA_MISSING)
 | 
						|
			    && (!compare[v_col->v_pos])) {
 | 
						|
 | 
						|
				if (ind_field->prefix_len != 0
 | 
						|
				    && !dfield_is_null(field2)) {
 | 
						|
					field2->len = unsigned(
 | 
						|
						dtype_get_at_most_n_mbchars(
 | 
						|
							field2->type.prtype,
 | 
						|
							field2->type.mbminlen,
 | 
						|
							field2->type.mbmaxlen,
 | 
						|
							ind_field->prefix_len,
 | 
						|
							field2->len,
 | 
						|
							static_cast<char*>
 | 
						|
							(field2->data)));
 | 
						|
				}
 | 
						|
 | 
						|
				/* The index field mismatch */
 | 
						|
				if (cmp_dfield_dfield(field2, field1)) {
 | 
						|
					mem_heap_free(tuple_heap);
 | 
						|
					mem_heap_free(heap);
 | 
						|
					return(false);
 | 
						|
				}
 | 
						|
 | 
						|
				compare[v_col->v_pos] = true;
 | 
						|
				n_cmp_v_col++;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		trx_id_t	rec_trx_id = row_get_rec_trx_id(
 | 
						|
			prev_version, clust_index, clust_offsets);
 | 
						|
 | 
						|
		if (rec_trx_id < trx_id || roll_ptr == cur_roll_ptr) {
 | 
						|
			break;
 | 
						|
		}
 | 
						|
 | 
						|
		version = prev_version;
 | 
						|
	}
 | 
						|
 | 
						|
func_exit:
 | 
						|
	if (n_cmp_v_col == 0) {
 | 
						|
		*vrow = NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	mem_heap_free(tuple_heap);
 | 
						|
	mem_heap_free(heap);
 | 
						|
 | 
						|
	/* FIXME: In the case of n_cmp_v_col is not the same as
 | 
						|
	n_fields - n_non_v_col, callback is needed to compare the rest
 | 
						|
	columns. At the timebeing, we will need to return true */
 | 
						|
	return (true);
 | 
						|
}
 | 
						|
 | 
						|
/** @return whether two data tuples are equal */
 | 
						|
bool dtuple_coll_eq(const dtuple_t &tuple1, const dtuple_t &tuple2)
 | 
						|
{
 | 
						|
  ut_ad(tuple1.magic_n == DATA_TUPLE_MAGIC_N);
 | 
						|
  ut_ad(tuple2.magic_n == DATA_TUPLE_MAGIC_N);
 | 
						|
  ut_ad(dtuple_check_typed(&tuple1));
 | 
						|
  ut_ad(dtuple_check_typed(&tuple2));
 | 
						|
  ut_ad(tuple1.n_fields == tuple2.n_fields);
 | 
						|
 | 
						|
  for (ulint i= 0; i < tuple1.n_fields; i++)
 | 
						|
    if (cmp_dfield_dfield(&tuple1.fields[i], &tuple2.fields[i]))
 | 
						|
      return false;
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
/** Finds out if a version of the record, where the version >= the current
 | 
						|
purge_sys.view, should have ientry as its secondary index entry. We check
 | 
						|
if there is any not delete marked version of the record where the trx
 | 
						|
id >= purge view, and the secondary index entry == ientry; exactly in
 | 
						|
this case we return TRUE.
 | 
						|
@param node    purge node
 | 
						|
@param index   secondary index
 | 
						|
@param ientry  secondary index entry
 | 
						|
@param mtr     mini-transaction
 | 
						|
@return whether ientry cannot be purged */
 | 
						|
static bool row_purge_is_unsafe(const purge_node_t &node,
 | 
						|
                                dict_index_t *index,
 | 
						|
                                const dtuple_t *ientry, mtr_t *mtr)
 | 
						|
{
 | 
						|
	const rec_t*	rec = btr_pcur_get_rec(&node.pcur);
 | 
						|
	roll_ptr_t	roll_ptr = node.roll_ptr;
 | 
						|
	trx_id_t	trx_id = node.trx_id;
 | 
						|
	const rec_t*	version;
 | 
						|
	rec_t*		prev_version;
 | 
						|
	dict_index_t*	clust_index = node.pcur.index();
 | 
						|
	rec_offs*	clust_offsets;
 | 
						|
	mem_heap_t*	heap;
 | 
						|
	dtuple_t*	row;
 | 
						|
	const dtuple_t*	entry;
 | 
						|
	dtuple_t*	vrow = NULL;
 | 
						|
	mem_heap_t*	v_heap = NULL;
 | 
						|
	dtuple_t*	cur_vrow = NULL;
 | 
						|
 | 
						|
	ut_ad(index->table == clust_index->table);
 | 
						|
	heap = mem_heap_create(1024);
 | 
						|
	clust_offsets = rec_get_offsets(rec, clust_index, NULL,
 | 
						|
					clust_index->n_core_fields,
 | 
						|
					ULINT_UNDEFINED, &heap);
 | 
						|
 | 
						|
	if (dict_index_has_virtual(index)) {
 | 
						|
		v_heap = mem_heap_create(100);
 | 
						|
	}
 | 
						|
 | 
						|
	if (!rec_get_deleted_flag(rec, rec_offs_comp(clust_offsets))) {
 | 
						|
		row_ext_t*	ext;
 | 
						|
 | 
						|
		/* The top of the stack of versions is locked by the
 | 
						|
		mtr holding a latch on the page containing the
 | 
						|
		clustered index record. The bottom of the stack is
 | 
						|
		locked by the fact that the purge_sys.view must
 | 
						|
		'overtake' any read view of an active transaction.
 | 
						|
		Thus, it is safe to fetch the prefixes for
 | 
						|
		externally stored columns. */
 | 
						|
		row = row_build(ROW_COPY_POINTERS, clust_index,
 | 
						|
				rec, clust_offsets,
 | 
						|
				NULL, NULL, NULL, &ext, heap);
 | 
						|
 | 
						|
		if (dict_index_has_virtual(index)) {
 | 
						|
 | 
						|
 | 
						|
#ifdef DBUG_OFF
 | 
						|
# define dbug_v_purge false
 | 
						|
#else /* DBUG_OFF */
 | 
						|
                        bool    dbug_v_purge = false;
 | 
						|
#endif /* DBUG_OFF */
 | 
						|
 | 
						|
			DBUG_EXECUTE_IF(
 | 
						|
				"ib_purge_virtual_index_callback",
 | 
						|
				dbug_v_purge = true;);
 | 
						|
 | 
						|
			roll_ptr_t t_roll_ptr = row_get_rec_roll_ptr(
 | 
						|
				rec, clust_index, clust_offsets);
 | 
						|
 | 
						|
			/* if the row is newly inserted, then the virtual
 | 
						|
			columns need to be computed */
 | 
						|
			if (trx_undo_roll_ptr_is_insert(t_roll_ptr)
 | 
						|
			    || dbug_v_purge) {
 | 
						|
 | 
						|
				if (!row_vers_build_clust_v_col(
 | 
						|
					    row, clust_index, index, heap)) {
 | 
						|
					goto unsafe_to_purge;
 | 
						|
				}
 | 
						|
 | 
						|
				entry = row_build_index_entry(
 | 
						|
					row, ext, index, heap);
 | 
						|
				if (entry && dtuple_coll_eq(*ientry, *entry)) {
 | 
						|
					goto unsafe_to_purge;
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				/* Build index entry out of row */
 | 
						|
				entry = row_build_index_entry(row, ext, index, heap);
 | 
						|
				/* entry could only be NULL if
 | 
						|
				the clustered index record is an uncommitted
 | 
						|
				inserted record whose BLOBs have not been
 | 
						|
				written yet. The secondary index record
 | 
						|
				can be safely removed, because it cannot
 | 
						|
				possibly refer to this incomplete
 | 
						|
				clustered index record. (Insert would
 | 
						|
				always first be completed for the
 | 
						|
				clustered index record, then proceed to
 | 
						|
				secondary indexes.) */
 | 
						|
 | 
						|
				if (entry && row_purge_vc_matches_cluster(
 | 
						|
					    rec, entry,
 | 
						|
					    clust_index, clust_offsets,
 | 
						|
					    index, ientry, roll_ptr,
 | 
						|
					    trx_id, mtr, &vrow)) {
 | 
						|
					goto unsafe_to_purge;
 | 
						|
				}
 | 
						|
			}
 | 
						|
			clust_offsets = rec_get_offsets(rec, clust_index, NULL,
 | 
						|
							clust_index
 | 
						|
							->n_core_fields,
 | 
						|
							ULINT_UNDEFINED, &heap);
 | 
						|
		} else {
 | 
						|
 | 
						|
			entry = row_build_index_entry(
 | 
						|
				row, ext, index, heap);
 | 
						|
 | 
						|
			/* If entry == NULL, the record contains unset BLOB
 | 
						|
			pointers.  This must be a freshly inserted record.  If
 | 
						|
			this is called from
 | 
						|
			row_purge_remove_sec_if_poss_low(), the thread will
 | 
						|
			hold latches on the clustered index and the secondary
 | 
						|
			index.  Because the insert works in three steps:
 | 
						|
 | 
						|
				(1) insert the record to clustered index
 | 
						|
				(2) store the BLOBs and update BLOB pointers
 | 
						|
				(3) insert records to secondary indexes
 | 
						|
 | 
						|
			the purge thread can safely ignore freshly inserted
 | 
						|
			records and delete the secondary index record.  The
 | 
						|
			thread that inserted the new record will be inserting
 | 
						|
			the secondary index records. */
 | 
						|
 | 
						|
			/* NOTE that we cannot do the comparison as binary
 | 
						|
			fields because the row is maybe being modified so that
 | 
						|
			the clustered index record has already been updated to
 | 
						|
			a different binary value in a char field, but the
 | 
						|
			collation identifies the old and new value anyway! */
 | 
						|
			if (entry && dtuple_coll_eq(*ientry, *entry)) {
 | 
						|
unsafe_to_purge:
 | 
						|
				mem_heap_free(heap);
 | 
						|
 | 
						|
				if (v_heap) {
 | 
						|
					mem_heap_free(v_heap);
 | 
						|
				}
 | 
						|
				return true;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	} else if (dict_index_has_virtual(index)) {
 | 
						|
		/* The current cluster index record could be
 | 
						|
		deleted, but the previous version of it might not. We will
 | 
						|
		need to get the virtual column data from undo record
 | 
						|
		associated with current cluster index */
 | 
						|
 | 
						|
		cur_vrow = row_vers_build_cur_vrow(
 | 
						|
			rec, clust_index, &clust_offsets,
 | 
						|
			index, trx_id, roll_ptr, heap, v_heap, mtr);
 | 
						|
	}
 | 
						|
 | 
						|
	version = rec;
 | 
						|
 | 
						|
	for (;;) {
 | 
						|
		mem_heap_t* heap2 = heap;
 | 
						|
		heap = mem_heap_create(1024);
 | 
						|
		vrow = NULL;
 | 
						|
 | 
						|
		trx_undo_prev_version_build(version,
 | 
						|
					    clust_index, clust_offsets,
 | 
						|
					    heap, &prev_version, mtr,
 | 
						|
					    TRX_UNDO_CHECK_PURGE_PAGES,
 | 
						|
					    nullptr,
 | 
						|
					    dict_index_has_virtual(index)
 | 
						|
					    ? &vrow : nullptr);
 | 
						|
		mem_heap_free(heap2); /* free version and clust_offsets */
 | 
						|
 | 
						|
		if (!prev_version) {
 | 
						|
			/* Versions end here */
 | 
						|
			mem_heap_free(heap);
 | 
						|
 | 
						|
			if (v_heap) {
 | 
						|
				mem_heap_free(v_heap);
 | 
						|
			}
 | 
						|
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
 | 
						|
		clust_offsets = rec_get_offsets(prev_version, clust_index,
 | 
						|
						NULL,
 | 
						|
						clust_index->n_core_fields,
 | 
						|
						ULINT_UNDEFINED, &heap);
 | 
						|
 | 
						|
		if (dict_index_has_virtual(index)) {
 | 
						|
			if (vrow) {
 | 
						|
				if (dtuple_vcol_data_missing(*vrow, *index)) {
 | 
						|
					goto nochange_index;
 | 
						|
				}
 | 
						|
				/* Keep the virtual row info for the next
 | 
						|
				version, unless it is changed */
 | 
						|
				mem_heap_empty(v_heap);
 | 
						|
				cur_vrow = dtuple_copy(vrow, v_heap);
 | 
						|
				dtuple_dup_v_fld(cur_vrow, v_heap);
 | 
						|
			}
 | 
						|
 | 
						|
			if (!cur_vrow) {
 | 
						|
				/* Nothing for this index has changed,
 | 
						|
				continue */
 | 
						|
nochange_index:
 | 
						|
				version = prev_version;
 | 
						|
				continue;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if (!rec_get_deleted_flag(prev_version,
 | 
						|
					  rec_offs_comp(clust_offsets))) {
 | 
						|
			row_ext_t*	ext;
 | 
						|
 | 
						|
			/* The stack of versions is locked by mtr.
 | 
						|
			Thus, it is safe to fetch the prefixes for
 | 
						|
			externally stored columns. */
 | 
						|
			row = row_build(ROW_COPY_POINTERS, clust_index,
 | 
						|
					prev_version, clust_offsets,
 | 
						|
					NULL, NULL, NULL, &ext, heap);
 | 
						|
 | 
						|
			if (dict_index_has_virtual(index)) {
 | 
						|
				ut_ad(cur_vrow);
 | 
						|
				ut_ad(row->n_v_fields == cur_vrow->n_v_fields);
 | 
						|
				dtuple_copy_v_fields(row, cur_vrow);
 | 
						|
			}
 | 
						|
 | 
						|
			entry = row_build_index_entry(row, ext, index, heap);
 | 
						|
 | 
						|
			/* If entry == NULL, the record contains unset
 | 
						|
			BLOB pointers.  This must be a freshly
 | 
						|
			inserted record that we can safely ignore.
 | 
						|
			For the justification, see the comments after
 | 
						|
			the previous row_build_index_entry() call. */
 | 
						|
 | 
						|
			/* NOTE that we cannot do the comparison as binary
 | 
						|
			fields because maybe the secondary index record has
 | 
						|
			already been updated to a different binary value in
 | 
						|
			a char field, but the collation identifies the old
 | 
						|
			and new value anyway! */
 | 
						|
 | 
						|
			if (entry && dtuple_coll_eq(*ientry, *entry)) {
 | 
						|
				goto unsafe_to_purge;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		version = prev_version;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/** Determines if it is possible to remove a secondary index entry.
 | 
						|
Removal is possible if the secondary index entry does not refer to any
 | 
						|
not delete marked version of a clustered index record where DB_TRX_ID
 | 
						|
is newer than the purge view.
 | 
						|
 | 
						|
NOTE: This function should only be called by the purge thread, only
 | 
						|
while holding a latch on the leaf page of the secondary index entry.
 | 
						|
It is possible that this function first returns true and then false,
 | 
						|
if a user transaction inserts a record that the secondary index entry
 | 
						|
would refer to.
 | 
						|
However, in that case, the user transaction would also re-insert the
 | 
						|
secondary index entry after purge has removed it and released the leaf
 | 
						|
page latch.
 | 
						|
@param node   row purge node
 | 
						|
@param index  secondary index
 | 
						|
@param entry  secondary index entry
 | 
						|
@param mtr    mini-transaction for looking up clustered index
 | 
						|
@return whether the secondary index record can be purged */
 | 
						|
static bool row_purge_poss_sec(purge_node_t *node, dict_index_t *index,
 | 
						|
			       const dtuple_t *entry, mtr_t *mtr)
 | 
						|
{
 | 
						|
  ut_ad(!index->is_clust());
 | 
						|
  const auto savepoint= mtr->get_savepoint();
 | 
						|
  bool can_delete= !row_purge_reposition_pcur(BTR_SEARCH_LEAF, node, mtr);
 | 
						|
 | 
						|
  if (!can_delete)
 | 
						|
  {
 | 
						|
    ut_ad(node->pcur.pos_state == BTR_PCUR_IS_POSITIONED);
 | 
						|
    can_delete= !row_purge_is_unsafe(*node, index, entry, mtr);
 | 
						|
    node->pcur.pos_state = BTR_PCUR_WAS_POSITIONED;
 | 
						|
    node->pcur.latch_mode= BTR_NO_LATCHES;
 | 
						|
  }
 | 
						|
 | 
						|
  mtr->rollback_to_savepoint(savepoint);
 | 
						|
  return can_delete;
 | 
						|
}
 | 
						|
 | 
						|
/** Report an error about not delete-marked secondary index record
 | 
						|
that was about to be purged.
 | 
						|
@param cur   cursor on the secondary index record
 | 
						|
@param entry search key */
 | 
						|
ATTRIBUTE_COLD ATTRIBUTE_NOINLINE
 | 
						|
static void row_purge_del_mark_error(const btr_cur_t &cursor,
 | 
						|
                                     const dtuple_t &entry)
 | 
						|
{
 | 
						|
  const dict_index_t *index= cursor.index();
 | 
						|
  ib::error() << "tried to purge non-delete-marked record in index "
 | 
						|
              << index->name << " of table " << index->table->name
 | 
						|
              << ": tuple: " << entry
 | 
						|
              << ", record: " << rec_index_print(cursor.page_cur.rec, index);
 | 
						|
  ut_ad(0);
 | 
						|
}
 | 
						|
 | 
						|
__attribute__((nonnull, warn_unused_result))
 | 
						|
/** Remove a secondary index entry if possible, by modifying the index tree.
 | 
						|
@param node             purge node
 | 
						|
@param index            secondary index
 | 
						|
@param entry            index entry
 | 
						|
@param page_max_trx_id  the PAGE_MAX_TRX_ID
 | 
						|
                        when row_purge_remove_sec_if_poss_leaf() was invoked
 | 
						|
@return whether the operation succeeded */
 | 
						|
static bool row_purge_remove_sec_if_poss_tree(purge_node_t *node,
 | 
						|
					      dict_index_t *index,
 | 
						|
					      const dtuple_t *entry,
 | 
						|
					      trx_id_t page_max_trx_id)
 | 
						|
{
 | 
						|
	btr_pcur_t		pcur;
 | 
						|
	bool			success	= true;
 | 
						|
	dberr_t			err;
 | 
						|
	mtr_t			mtr;
 | 
						|
 | 
						|
	log_free_check();
 | 
						|
#ifdef ENABLED_DEBUG_SYNC
 | 
						|
	DBUG_EXECUTE_IF("enable_row_purge_sec_tree_sync",
 | 
						|
		debug_sync_set_action(current_thd, STRING_WITH_LEN(
 | 
						|
			"now SIGNAL "
 | 
						|
			"purge_sec_tree_begin"));
 | 
						|
		debug_sync_set_action(current_thd, STRING_WITH_LEN(
 | 
						|
			"now WAIT_FOR "
 | 
						|
			"purge_sec_tree_execute"));
 | 
						|
	);
 | 
						|
#endif
 | 
						|
	mtr.start();
 | 
						|
	index->set_modified(mtr);
 | 
						|
	pcur.btr_cur.page_cur.index = index;
 | 
						|
 | 
						|
	if (index->is_spatial()) {
 | 
						|
		if (rtr_search(entry, BTR_PURGE_TREE, &pcur, nullptr, &mtr)) {
 | 
						|
			goto func_exit;
 | 
						|
		}
 | 
						|
	} else if (!row_search_index_entry(entry, BTR_PURGE_TREE,
 | 
						|
					   &pcur, &mtr)) {
 | 
						|
		/* Not found.  This is a legitimate condition.  In a
 | 
						|
		rollback, InnoDB will remove secondary recs that would
 | 
						|
		be purged anyway.  Then the actual purge will not find
 | 
						|
		the secondary index record.  Also, the purge itself is
 | 
						|
		eager: if it comes to consider a secondary index
 | 
						|
		record, and notices it does not need to exist in the
 | 
						|
		index, it will remove it.  Then if/when the purge
 | 
						|
		comes to consider the secondary index record a second
 | 
						|
		time, it will not exist any more in the index. */
 | 
						|
		goto func_exit;
 | 
						|
	}
 | 
						|
 | 
						|
	/* We should remove the index record if no later version of the row,
 | 
						|
	which cannot be purged yet, requires its existence. If some requires,
 | 
						|
	we should do nothing. */
 | 
						|
 | 
						|
	if (page_max_trx_id
 | 
						|
	    == page_get_max_trx_id(btr_cur_get_page(&pcur.btr_cur))
 | 
						|
	    || row_purge_poss_sec(node, index, entry, &mtr)) {
 | 
						|
 | 
						|
		/* Remove the index record, which should have been
 | 
						|
		marked for deletion. */
 | 
						|
		if (!rec_get_deleted_flag(btr_pcur_get_rec(&pcur),
 | 
						|
					  index->table->not_redundant())) {
 | 
						|
			row_purge_del_mark_error(pcur.btr_cur, *entry);
 | 
						|
			goto func_exit;
 | 
						|
		}
 | 
						|
 | 
						|
		btr_cur_pessimistic_delete(&err, FALSE, &pcur.btr_cur,
 | 
						|
					   0, false, &mtr);
 | 
						|
		switch (UNIV_EXPECT(err, DB_SUCCESS)) {
 | 
						|
		case DB_SUCCESS:
 | 
						|
			break;
 | 
						|
		case DB_OUT_OF_FILE_SPACE:
 | 
						|
			success = FALSE;
 | 
						|
			break;
 | 
						|
		default:
 | 
						|
			ut_error;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
func_exit:
 | 
						|
	btr_pcur_close(&pcur); // FIXME: need this?
 | 
						|
	mtr.commit();
 | 
						|
	return success;
 | 
						|
}
 | 
						|
 | 
						|
/** Compute a nonzero return value of row_purge_remove_sec_if_poss_leaf().
 | 
						|
@param page  latched secondary index page
 | 
						|
@return PAGE_MAX_TRX_ID for row_purge_remove_sec_if_poss_tree()
 | 
						|
@retval 1 if a further row_purge_poss_sec() check is necessary */
 | 
						|
ATTRIBUTE_NOINLINE ATTRIBUTE_COLD
 | 
						|
static trx_id_t row_purge_check(const page_t *page) noexcept
 | 
						|
{
 | 
						|
  trx_id_t id= page_get_max_trx_id(page);
 | 
						|
  ut_ad(id);
 | 
						|
  if (trx_sys.find_same_or_older_in_purge(purge_sys.query->trx, id))
 | 
						|
    /* Because an active transaction may modify the secondary index
 | 
						|
    but not PAGE_MAX_TRX_ID, row_purge_poss_sec() must be invoked
 | 
						|
    again after re-latching the page. Let us return a bogus ID. Yes,
 | 
						|
    an actual transaction with ID 1 would create the InnoDB dictionary
 | 
						|
    tables in dict_sys_t::create_or_check_sys_tables(), but it would
 | 
						|
    exclusively write TRX_UNDO_INSERT_REC records. Purging those
 | 
						|
    records never involves row_purge_remove_sec_if_poss_tree(). */
 | 
						|
    id= 1;
 | 
						|
  return id;
 | 
						|
}
 | 
						|
 | 
						|
__attribute__((nonnull, warn_unused_result))
 | 
						|
/** Remove a secondary index entry if possible, without modifying the tree.
 | 
						|
@param node             purge node
 | 
						|
@param index            secondary index
 | 
						|
@param entry            index entry
 | 
						|
@return PAGE_MAX_TRX_ID for row_purge_remove_sec_if_poss_tree()
 | 
						|
@retval 1 if a further row_purge_poss_sec() check is necessary
 | 
						|
@retval 0 if success or if not found */
 | 
						|
static trx_id_t row_purge_remove_sec_if_poss_leaf(purge_node_t *node,
 | 
						|
                                                  dict_index_t *index,
 | 
						|
                                                  const dtuple_t *entry)
 | 
						|
{
 | 
						|
	mtr_t			mtr;
 | 
						|
	btr_pcur_t		pcur;
 | 
						|
	trx_id_t		page_max_trx_id = 0;
 | 
						|
 | 
						|
	log_free_check();
 | 
						|
	ut_ad(index->table == node->table);
 | 
						|
	ut_ad(!index->table->is_temporary());
 | 
						|
	mtr.start();
 | 
						|
	index->set_modified(mtr);
 | 
						|
 | 
						|
	pcur.btr_cur.page_cur.index = index;
 | 
						|
 | 
						|
	if (index->is_spatial()) {
 | 
						|
		if (!rtr_search(entry, BTR_MODIFY_LEAF, &pcur, nullptr,
 | 
						|
				&mtr)) {
 | 
						|
			goto found;
 | 
						|
		}
 | 
						|
	} else if (btr_pcur_open(entry, PAGE_CUR_LE, BTR_MODIFY_LEAF, &pcur,
 | 
						|
				 &mtr)
 | 
						|
		   == DB_SUCCESS
 | 
						|
		   && !btr_pcur_is_before_first_on_page(&pcur)
 | 
						|
		   && btr_pcur_get_low_match(&pcur)
 | 
						|
		   == dtuple_get_n_fields(entry)) {
 | 
						|
found:
 | 
						|
		/* Before attempting to purge a record, check
 | 
						|
		if it is safe to do so. */
 | 
						|
		if (row_purge_poss_sec(node, index, entry, &mtr)) {
 | 
						|
			/* Only delete-marked records should be purged. */
 | 
						|
			if (!rec_get_deleted_flag(btr_pcur_get_rec(&pcur),
 | 
						|
						  index->table
 | 
						|
						  ->not_redundant())) {
 | 
						|
				row_purge_del_mark_error(pcur.btr_cur, *entry);
 | 
						|
				mtr.commit();
 | 
						|
				dict_set_corrupted(index, "purge");
 | 
						|
				goto cleanup;
 | 
						|
			}
 | 
						|
 | 
						|
			if (index->is_spatial()) {
 | 
						|
				const buf_block_t* block = btr_pcur_get_block(
 | 
						|
					&pcur);
 | 
						|
                                const page_id_t id{block->page.id()};
 | 
						|
 | 
						|
				if (id.page_no() != index->page
 | 
						|
				    && page_get_n_recs(block->page.frame) < 2
 | 
						|
				    && !lock_test_prdt_page_lock(nullptr, id)){
 | 
						|
					/* this is the last record on page,
 | 
						|
					and it has a "page" lock on it,
 | 
						|
					which mean search is still depending
 | 
						|
					on it, so do not delete */
 | 
						|
					DBUG_LOG("purge",
 | 
						|
						 "skip purging last"
 | 
						|
						 " record on page " << id);
 | 
						|
					goto func_exit;
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			if (btr_cur_optimistic_delete(&pcur.btr_cur, 0, &mtr)
 | 
						|
			    == DB_FAIL) {
 | 
						|
				page_max_trx_id = row_purge_check(
 | 
						|
					btr_pcur_get_page(&pcur));
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
func_exit:
 | 
						|
	mtr.commit();
 | 
						|
cleanup:
 | 
						|
	btr_pcur_close(&pcur);
 | 
						|
	return page_max_trx_id;
 | 
						|
}
 | 
						|
 | 
						|
/***********************************************************//**
 | 
						|
Removes a secondary index entry if possible. */
 | 
						|
UNIV_INLINE MY_ATTRIBUTE((nonnull(1,2)))
 | 
						|
void
 | 
						|
row_purge_remove_sec_if_poss(
 | 
						|
/*=========================*/
 | 
						|
	purge_node_t*	node,	/*!< in: row purge node */
 | 
						|
	dict_index_t*	index,	/*!< in: index */
 | 
						|
	const dtuple_t*	entry)	/*!< in: index entry */
 | 
						|
{
 | 
						|
  if (UNIV_UNLIKELY(!entry))
 | 
						|
    /* The node->row must have lacked some fields of this index. This
 | 
						|
    is possible when the undo log record was written before this index
 | 
						|
    was created. */
 | 
						|
    return;
 | 
						|
 | 
						|
  if (trx_id_t page_max_trx_id=
 | 
						|
      row_purge_remove_sec_if_poss_leaf(node, index, entry))
 | 
						|
    for (auto n_tries= BTR_CUR_RETRY_DELETE_N_TIMES;
 | 
						|
         !row_purge_remove_sec_if_poss_tree(node, index, entry,
 | 
						|
                                            page_max_trx_id);
 | 
						|
         std::this_thread::sleep_for(BTR_CUR_RETRY_SLEEP_TIME))
 | 
						|
      /* The delete operation may fail if we have little
 | 
						|
      file space left (if innodb_file_per_table=0?) */
 | 
						|
      ut_a(--n_tries);
 | 
						|
}
 | 
						|
 | 
						|
/***********************************************************//**
 | 
						|
Purges a delete marking of a record.
 | 
						|
@retval true if the row was not found, or it was successfully removed
 | 
						|
@retval false the purge needs to be suspended because of
 | 
						|
running out of file space */
 | 
						|
static MY_ATTRIBUTE((nonnull, warn_unused_result))
 | 
						|
bool row_purge_del_mark(purge_node_t *node)
 | 
						|
{
 | 
						|
  if (node->index)
 | 
						|
  {
 | 
						|
    mem_heap_t *heap= mem_heap_create(1024);
 | 
						|
 | 
						|
    do
 | 
						|
    {
 | 
						|
      if (node->index->type & (DICT_FTS | DICT_CORRUPT))
 | 
						|
        continue;
 | 
						|
      if (!node->index->is_committed())
 | 
						|
        continue;
 | 
						|
      dtuple_t* entry= row_build_index_entry_low(node->row, nullptr,
 | 
						|
                                                 node->index, heap,
 | 
						|
                                                 ROW_BUILD_FOR_PURGE);
 | 
						|
      row_purge_remove_sec_if_poss(node, node->index, entry);
 | 
						|
      mem_heap_empty(heap);
 | 
						|
    }
 | 
						|
    while ((node->index= dict_table_get_next_index(node->index)));
 | 
						|
 | 
						|
    mem_heap_free(heap);
 | 
						|
  }
 | 
						|
 | 
						|
  bool result= row_purge_remove_clust_if_poss(node);
 | 
						|
 | 
						|
#ifdef ENABLED_DEBUG_SYNC
 | 
						|
  DBUG_EXECUTE_IF("enable_row_purge_del_mark_exit_sync_point",
 | 
						|
                  debug_sync_set_action
 | 
						|
                  (current_thd,
 | 
						|
                   STRING_WITH_LEN("now SIGNAL row_purge_del_mark_finished"));
 | 
						|
                  );
 | 
						|
#endif
 | 
						|
 | 
						|
  return result;
 | 
						|
}
 | 
						|
 | 
						|
/** Reset DB_TRX_ID, DB_ROLL_PTR of a clustered index record
 | 
						|
whose old history can no longer be observed.
 | 
						|
@param[in,out]	node	purge node
 | 
						|
@param[in,out]	mtr	mini-transaction (will be started and committed) */
 | 
						|
static void row_purge_reset_trx_id(purge_node_t* node, mtr_t* mtr)
 | 
						|
{
 | 
						|
	/* Reset DB_TRX_ID, DB_ROLL_PTR for old records. */
 | 
						|
	mtr->start();
 | 
						|
 | 
						|
	if (row_purge_reposition_pcur(BTR_MODIFY_LEAF, node, mtr)) {
 | 
						|
		dict_index_t*	index = dict_table_get_first_index(
 | 
						|
			node->table);
 | 
						|
		ulint	trx_id_pos = index->n_uniq ? index->n_uniq : 1;
 | 
						|
		rec_t*	rec = btr_pcur_get_rec(&node->pcur);
 | 
						|
		mem_heap_t*	heap = NULL;
 | 
						|
		/* Reserve enough offsets for the PRIMARY KEY and 2 columns
 | 
						|
		so that we can access DB_TRX_ID, DB_ROLL_PTR. */
 | 
						|
		rec_offs offsets_[REC_OFFS_HEADER_SIZE + MAX_REF_PARTS + 3];
 | 
						|
		rec_offs_init(offsets_);
 | 
						|
		rec_offs*	offsets = rec_get_offsets(
 | 
						|
			rec, index, offsets_, index->n_core_fields,
 | 
						|
			trx_id_pos + 2, &heap);
 | 
						|
		ut_ad(heap == NULL);
 | 
						|
 | 
						|
		ut_ad(dict_index_get_nth_field(index, trx_id_pos)
 | 
						|
		      ->col->mtype == DATA_SYS);
 | 
						|
		ut_ad(dict_index_get_nth_field(index, trx_id_pos)
 | 
						|
		      ->col->prtype == (DATA_TRX_ID | DATA_NOT_NULL));
 | 
						|
		ut_ad(dict_index_get_nth_field(index, trx_id_pos + 1)
 | 
						|
		      ->col->mtype == DATA_SYS);
 | 
						|
		ut_ad(dict_index_get_nth_field(index, trx_id_pos + 1)
 | 
						|
		      ->col->prtype == (DATA_ROLL_PTR | DATA_NOT_NULL));
 | 
						|
 | 
						|
		/* Only update the record if DB_ROLL_PTR matches (the
 | 
						|
		record has not been modified after this transaction
 | 
						|
		became purgeable) */
 | 
						|
		if (node->roll_ptr
 | 
						|
		    == row_get_rec_roll_ptr(rec, index, offsets)) {
 | 
						|
			ut_ad(!rec_get_deleted_flag(
 | 
						|
					rec, rec_offs_comp(offsets))
 | 
						|
			      || rec_is_alter_metadata(rec, *index));
 | 
						|
			DBUG_LOG("purge", "reset DB_TRX_ID="
 | 
						|
				 << ib::hex(row_get_rec_trx_id(
 | 
						|
						    rec, index, offsets)));
 | 
						|
 | 
						|
			index->set_modified(*mtr);
 | 
						|
			buf_block_t* block = btr_pcur_get_block(&node->pcur);
 | 
						|
			if (UNIV_LIKELY_NULL(block->page.zip.data)) {
 | 
						|
				page_zip_write_trx_id_and_roll_ptr(
 | 
						|
					block, rec, offsets, trx_id_pos,
 | 
						|
					0, 1ULL << ROLL_PTR_INSERT_FLAG_POS,
 | 
						|
					mtr);
 | 
						|
			} else {
 | 
						|
				ulint	len;
 | 
						|
				byte*	ptr = rec_get_nth_field(
 | 
						|
					rec, offsets, trx_id_pos, &len);
 | 
						|
				ut_ad(len == DATA_TRX_ID_LEN);
 | 
						|
				size_t offs = ptr - block->page.frame;
 | 
						|
				mtr->memset(block, offs, DATA_TRX_ID_LEN, 0);
 | 
						|
				offs += DATA_TRX_ID_LEN;
 | 
						|
				mtr->write<1,mtr_t::MAYBE_NOP>(
 | 
						|
					*block, block->page.frame + offs,
 | 
						|
					0x80U);
 | 
						|
				mtr->memset(block, offs + 1,
 | 
						|
					    DATA_ROLL_PTR_LEN - 1, 0);
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	mtr->commit();
 | 
						|
}
 | 
						|
 | 
						|
/***********************************************************//**
 | 
						|
Purges an update of an existing record. Also purges an update of a delete
 | 
						|
marked record if that record contained an externally stored field. */
 | 
						|
static
 | 
						|
void
 | 
						|
row_purge_upd_exist_or_extern_func(
 | 
						|
/*===============================*/
 | 
						|
#ifdef UNIV_DEBUG
 | 
						|
	const que_thr_t*thr,		/*!< in: query thread */
 | 
						|
#endif /* UNIV_DEBUG */
 | 
						|
	purge_node_t*	node,		/*!< in: row purge node */
 | 
						|
	const trx_undo_rec_t*	undo_rec)	/*!< in: record to purge */
 | 
						|
{
 | 
						|
	mem_heap_t*	heap;
 | 
						|
 | 
						|
	ut_ad(!node->table->skip_alter_undo);
 | 
						|
 | 
						|
	if (node->rec_type == TRX_UNDO_UPD_DEL_REC
 | 
						|
	    || (node->cmpl_info & UPD_NODE_NO_ORD_CHANGE)
 | 
						|
	    || !node->index) {
 | 
						|
 | 
						|
		goto skip_secondaries;
 | 
						|
	}
 | 
						|
 | 
						|
	heap = mem_heap_create(1024);
 | 
						|
 | 
						|
	do {
 | 
						|
		if (node->index->type & (DICT_FTS | DICT_CORRUPT)) {
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		if (!node->index->is_committed()) {
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		if (row_upd_changes_ord_field_binary(node->index, node->update,
 | 
						|
						     thr, NULL, NULL)) {
 | 
						|
			/* Build the older version of the index entry */
 | 
						|
			dtuple_t*	entry = row_build_index_entry_low(
 | 
						|
				node->row, NULL, node->index,
 | 
						|
				heap, ROW_BUILD_FOR_PURGE);
 | 
						|
			row_purge_remove_sec_if_poss(node, node->index, entry);
 | 
						|
 | 
						|
			ut_ad(node->table);
 | 
						|
 | 
						|
			mem_heap_empty(heap);
 | 
						|
		}
 | 
						|
	} while ((node->index = dict_table_get_next_index(node->index)));
 | 
						|
 | 
						|
	mem_heap_free(heap);
 | 
						|
 | 
						|
skip_secondaries:
 | 
						|
	mtr_t		mtr;
 | 
						|
	dict_index_t*	index = dict_table_get_first_index(node->table);
 | 
						|
	/* Free possible externally stored fields */
 | 
						|
	for (ulint i = 0; i < upd_get_n_fields(node->update); i++) {
 | 
						|
 | 
						|
		const upd_field_t*	ufield
 | 
						|
			= upd_get_nth_field(node->update, i);
 | 
						|
 | 
						|
		if (dfield_is_ext(&ufield->new_val)) {
 | 
						|
			bool		is_insert;
 | 
						|
			ulint		rseg_id;
 | 
						|
			uint32_t	page_no;
 | 
						|
			uint16_t	offset;
 | 
						|
 | 
						|
			/* We use the fact that new_val points to
 | 
						|
			undo_rec and get thus the offset of
 | 
						|
			dfield data inside the undo record. Then we
 | 
						|
			can calculate from node->roll_ptr the file
 | 
						|
			address of the new_val data */
 | 
						|
 | 
						|
			const uint16_t internal_offset = uint16_t(
 | 
						|
				static_cast<const byte*>
 | 
						|
				(dfield_get_data(&ufield->new_val))
 | 
						|
				- undo_rec);
 | 
						|
 | 
						|
			ut_a(internal_offset < srv_page_size);
 | 
						|
 | 
						|
			trx_undo_decode_roll_ptr(node->roll_ptr,
 | 
						|
						 &is_insert, &rseg_id,
 | 
						|
						 &page_no, &offset);
 | 
						|
 | 
						|
			const trx_rseg_t &rseg = trx_sys.rseg_array[rseg_id];
 | 
						|
			ut_ad(rseg.is_persistent());
 | 
						|
 | 
						|
			mtr.start();
 | 
						|
 | 
						|
			/* We have to acquire an SX-latch to the clustered
 | 
						|
			index tree (exclude other tree changes) */
 | 
						|
 | 
						|
			mtr_sx_lock_index(index, &mtr);
 | 
						|
 | 
						|
			index->set_modified(mtr);
 | 
						|
 | 
						|
			/* NOTE: we must also acquire a U latch to the
 | 
						|
			root page of the tree. We will need it when we
 | 
						|
			free pages from the tree. If the tree is of height 1,
 | 
						|
			the tree X-latch does NOT protect the root page,
 | 
						|
			because it is also a leaf page. Since we will have a
 | 
						|
			latch on an undo log page, we would break the
 | 
						|
			latching order if we would only later latch the
 | 
						|
			root page of such a tree! */
 | 
						|
 | 
						|
			dberr_t err;
 | 
						|
			if (!btr_root_block_get(index, RW_SX_LATCH, &mtr,
 | 
						|
						&err)) {
 | 
						|
			} else if (buf_block_t* block =
 | 
						|
				   buf_page_get(page_id_t(rseg.space->id,
 | 
						|
							  page_no),
 | 
						|
						0, RW_X_LATCH, &mtr)) {
 | 
						|
				buf_page_make_young_if_needed(&block->page);
 | 
						|
 | 
						|
				byte* data_field = block->page.frame
 | 
						|
					+ offset + internal_offset;
 | 
						|
 | 
						|
				ut_a(dfield_get_len(&ufield->new_val)
 | 
						|
				     >= BTR_EXTERN_FIELD_REF_SIZE);
 | 
						|
				btr_free_externally_stored_field(
 | 
						|
					index,
 | 
						|
					data_field
 | 
						|
					+ dfield_get_len(&ufield->new_val)
 | 
						|
					- BTR_EXTERN_FIELD_REF_SIZE,
 | 
						|
					NULL, NULL, block, 0, false, &mtr);
 | 
						|
			}
 | 
						|
 | 
						|
			mtr.commit();
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	row_purge_reset_trx_id(node, &mtr);
 | 
						|
}
 | 
						|
 | 
						|
#ifdef UNIV_DEBUG
 | 
						|
# define row_purge_upd_exist_or_extern(thr,node,undo_rec)	\
 | 
						|
	row_purge_upd_exist_or_extern_func(thr,node,undo_rec)
 | 
						|
#else /* UNIV_DEBUG */
 | 
						|
# define row_purge_upd_exist_or_extern(thr,node,undo_rec)	\
 | 
						|
	row_purge_upd_exist_or_extern_func(node,undo_rec)
 | 
						|
#endif /* UNIV_DEBUG */
 | 
						|
 | 
						|
/** Build a partial row from an update undo log record for purge.
 | 
						|
Any columns which occur as ordering in any index of the table are present.
 | 
						|
Any missing columns are indicated by col->mtype == DATA_MISSING.
 | 
						|
 | 
						|
@param ptr    remaining part of the undo log record
 | 
						|
@param index  clustered index
 | 
						|
@param node   purge node
 | 
						|
@return pointer to remaining part of undo record */
 | 
						|
static byte *row_purge_get_partial(const byte *ptr, const dict_index_t &index,
 | 
						|
                                   purge_node_t *node)
 | 
						|
{
 | 
						|
  bool first_v_col= true;
 | 
						|
  bool is_undo_log= true;
 | 
						|
 | 
						|
  ut_ad(index.is_primary());
 | 
						|
  ut_ad(index.n_uniq == node->ref->n_fields);
 | 
						|
 | 
						|
  node->row= dtuple_create_with_vcol(node->heap, index.table->n_cols,
 | 
						|
                                     index.table->n_v_cols);
 | 
						|
 | 
						|
  /* Mark all columns in the row uninitialized, so that
 | 
						|
  we can distinguish missing fields from fields that are SQL NULL. */
 | 
						|
  for (ulint i= 0; i < index.table->n_cols; i++)
 | 
						|
    node->row->fields[i].type.mtype= DATA_MISSING;
 | 
						|
 | 
						|
  dtuple_init_v_fld(node->row);
 | 
						|
 | 
						|
  for (const upd_field_t *uf= node->update->fields, *const ue=
 | 
						|
         node->update->fields + node->update->n_fields; uf != ue; uf++)
 | 
						|
  {
 | 
						|
    if (!uf->old_v_val)
 | 
						|
    {
 | 
						|
      const dict_col_t &c= *dict_index_get_nth_col(&index, uf->field_no);
 | 
						|
      if (!c.is_dropped())
 | 
						|
        node->row->fields[c.ind]= uf->new_val;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  const byte *end_ptr= ptr + mach_read_from_2(ptr);
 | 
						|
  ptr+= 2;
 | 
						|
 | 
						|
  while (ptr != end_ptr)
 | 
						|
  {
 | 
						|
    dfield_t *dfield;
 | 
						|
    const byte *field;
 | 
						|
    const dict_col_t *col;
 | 
						|
    uint32_t len, orig_len, field_no= mach_read_next_compressed(&ptr);
 | 
						|
 | 
						|
    if (field_no >= REC_MAX_N_FIELDS)
 | 
						|
    {
 | 
						|
      ptr= trx_undo_read_v_idx(index.table, ptr, first_v_col, &is_undo_log,
 | 
						|
                               &field_no);
 | 
						|
      first_v_col= false;
 | 
						|
 | 
						|
      ptr= trx_undo_rec_get_col_val(ptr, &field, &len, &orig_len);
 | 
						|
 | 
						|
      if (field_no == FIL_NULL)
 | 
						|
        continue; /* there no longer is an index on the virtual column */
 | 
						|
 | 
						|
      dict_v_col_t *vcol= dict_table_get_nth_v_col(index.table, field_no);
 | 
						|
      col =&vcol->m_col;
 | 
						|
      dfield= dtuple_get_nth_v_field(node->row, vcol->v_pos);
 | 
						|
      dict_col_copy_type(&vcol->m_col, &dfield->type);
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
      ptr= trx_undo_rec_get_col_val(ptr, &field, &len, &orig_len);
 | 
						|
      col= dict_index_get_nth_col(&index, field_no);
 | 
						|
      if (col->is_dropped())
 | 
						|
        continue;
 | 
						|
      dfield= dtuple_get_nth_field(node->row, col->ind);
 | 
						|
      ut_ad(dfield->type.mtype == DATA_MISSING ||
 | 
						|
            dict_col_type_assert_equal(col, &dfield->type));
 | 
						|
      ut_ad(dfield->type.mtype == DATA_MISSING ||
 | 
						|
            dfield->len == len ||
 | 
						|
            (len != UNIV_SQL_NULL && len >= UNIV_EXTERN_STORAGE_FIELD));
 | 
						|
      dict_col_copy_type(dict_table_get_nth_col(index.table, col->ind),
 | 
						|
                         &dfield->type);
 | 
						|
    }
 | 
						|
 | 
						|
    dfield_set_data(dfield, field, len);
 | 
						|
 | 
						|
    if (len == UNIV_SQL_NULL || len < UNIV_EXTERN_STORAGE_FIELD)
 | 
						|
      continue;
 | 
						|
 | 
						|
    spatial_status_t spatial_status= static_cast<spatial_status_t>
 | 
						|
      ((len & SPATIAL_STATUS_MASK) >> SPATIAL_STATUS_SHIFT);
 | 
						|
    len&= ~SPATIAL_STATUS_MASK;
 | 
						|
 | 
						|
    /* Keep compatible with 5.7.9 format. */
 | 
						|
    if (spatial_status == SPATIAL_UNKNOWN)
 | 
						|
      spatial_status= dict_col_get_spatial_status(col);
 | 
						|
 | 
						|
    switch (UNIV_EXPECT(spatial_status, SPATIAL_NONE)) {
 | 
						|
    case SPATIAL_ONLY:
 | 
						|
      ut_ad(len - UNIV_EXTERN_STORAGE_FIELD == DATA_MBR_LEN);
 | 
						|
      dfield_set_len(dfield, len - UNIV_EXTERN_STORAGE_FIELD);
 | 
						|
      break;
 | 
						|
 | 
						|
    case SPATIAL_MIXED:
 | 
						|
      dfield_set_len(dfield, len - UNIV_EXTERN_STORAGE_FIELD - DATA_MBR_LEN);
 | 
						|
      break;
 | 
						|
 | 
						|
    default:
 | 
						|
      dfield_set_len(dfield, len - UNIV_EXTERN_STORAGE_FIELD);
 | 
						|
      break;
 | 
						|
    }
 | 
						|
 | 
						|
    dfield_set_ext(dfield);
 | 
						|
    dfield_set_spatial_status(dfield, spatial_status);
 | 
						|
 | 
						|
    if (!col->ord_part || spatial_status == SPATIAL_ONLY ||
 | 
						|
        node->rec_type == TRX_UNDO_UPD_DEL_REC)
 | 
						|
      continue;
 | 
						|
    /* If the prefix of this BLOB column is indexed, ensure that enough
 | 
						|
    prefix is stored in the undo log record. */
 | 
						|
    ut_a(dfield_get_len(dfield) >= BTR_EXTERN_FIELD_REF_SIZE);
 | 
						|
    ut_a(dict_table_has_atomic_blobs(index.table) ||
 | 
						|
         dfield_get_len(dfield) >=
 | 
						|
         REC_ANTELOPE_MAX_INDEX_COL_LEN + BTR_EXTERN_FIELD_REF_SIZE);
 | 
						|
  }
 | 
						|
 | 
						|
  for (ulint i= 0; i < index.n_uniq; i++)
 | 
						|
  {
 | 
						|
    dfield_t &field= node->row->fields[index.fields[i].col->ind];
 | 
						|
    if (field.type.mtype == DATA_MISSING)
 | 
						|
      field= node->ref->fields[i];
 | 
						|
  }
 | 
						|
 | 
						|
  return const_cast<byte*>(ptr);
 | 
						|
}
 | 
						|
 | 
						|
MY_ATTRIBUTE((nonnull,warn_unused_result))
 | 
						|
/** Parses the row reference and other info in a modify undo log record.
 | 
						|
@param[in]	node		row undo node
 | 
						|
@param[in]	undo_rec	record to purge
 | 
						|
@param[in]	thr		query thread
 | 
						|
@param[out]	updated_extern	true if an externally stored field was
 | 
						|
				updated
 | 
						|
@return true if purge operation required */
 | 
						|
static
 | 
						|
bool
 | 
						|
row_purge_parse_undo_rec(
 | 
						|
	purge_node_t*		node,
 | 
						|
	const trx_undo_rec_t*	undo_rec,
 | 
						|
	que_thr_t*		thr,
 | 
						|
	bool*			updated_extern)
 | 
						|
{
 | 
						|
	dict_index_t*	clust_index;
 | 
						|
	undo_no_t	undo_no;
 | 
						|
	table_id_t	table_id;
 | 
						|
	roll_ptr_t	roll_ptr;
 | 
						|
	byte		info_bits;
 | 
						|
	byte		type;
 | 
						|
 | 
						|
	const byte* ptr = trx_undo_rec_get_pars(
 | 
						|
		undo_rec, &type, &node->cmpl_info,
 | 
						|
		updated_extern, &undo_no, &table_id);
 | 
						|
 | 
						|
	node->rec_type = type;
 | 
						|
 | 
						|
	switch (type) {
 | 
						|
	case TRX_UNDO_RENAME_TABLE:
 | 
						|
		return false;
 | 
						|
	case TRX_UNDO_EMPTY:
 | 
						|
	case TRX_UNDO_INSERT_METADATA:
 | 
						|
	case TRX_UNDO_INSERT_REC:
 | 
						|
		/* These records do not store any transaction identifier. */
 | 
						|
		node->trx_id = TRX_ID_MAX;
 | 
						|
		break;
 | 
						|
	default:
 | 
						|
#ifdef UNIV_DEBUG
 | 
						|
		ut_ad("unknown undo log record type" == 0);
 | 
						|
		return false;
 | 
						|
	case TRX_UNDO_UPD_DEL_REC:
 | 
						|
	case TRX_UNDO_UPD_EXIST_REC:
 | 
						|
	case TRX_UNDO_DEL_MARK_REC:
 | 
						|
#endif /* UNIV_DEBUG */
 | 
						|
		ptr = trx_undo_update_rec_get_sys_cols(ptr, &node->trx_id,
 | 
						|
						       &roll_ptr, &info_bits);
 | 
						|
		break;
 | 
						|
	}
 | 
						|
 | 
						|
	auto &tables_entry= node->tables[table_id];
 | 
						|
	node->table = tables_entry.first;
 | 
						|
	if (!node->table) {
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
#ifndef DBUG_OFF
 | 
						|
	if (MDL_ticket* mdl = tables_entry.second) {
 | 
						|
		static_cast<MDL_context*>(thd_mdl_context(current_thd))
 | 
						|
			->lock_warrant = mdl->get_ctx();
 | 
						|
	}
 | 
						|
#endif
 | 
						|
	ut_ad(!node->table->is_temporary());
 | 
						|
 | 
						|
	clust_index = dict_table_get_first_index(node->table);
 | 
						|
 | 
						|
	if (clust_index->is_corrupted()) {
 | 
						|
		/* The table was corrupt in the data dictionary.
 | 
						|
		dict_set_corrupted() works on an index, and
 | 
						|
		we do not have an index to call it with. */
 | 
						|
		DBUG_ASSERT(table_id == node->table->id);
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	switch (type) {
 | 
						|
	case TRX_UNDO_INSERT_METADATA:
 | 
						|
		node->ref = &trx_undo_metadata;
 | 
						|
		return true;
 | 
						|
	case TRX_UNDO_EMPTY:
 | 
						|
		node->ref = nullptr;
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	ptr = trx_undo_rec_get_row_ref(ptr, clust_index, &(node->ref),
 | 
						|
				       node->heap);
 | 
						|
 | 
						|
	if (type == TRX_UNDO_INSERT_REC) {
 | 
						|
		return(true);
 | 
						|
	}
 | 
						|
 | 
						|
	ptr = trx_undo_update_rec_get_update(ptr, clust_index, type,
 | 
						|
					     node->trx_id,
 | 
						|
					     roll_ptr, info_bits,
 | 
						|
					     node->heap, &(node->update));
 | 
						|
 | 
						|
	/* Read to the partial row the fields that occur in indexes */
 | 
						|
 | 
						|
	if (!(node->cmpl_info & UPD_NODE_NO_ORD_CHANGE)) {
 | 
						|
		ut_ad(!(node->update->info_bits & REC_INFO_MIN_REC_FLAG));
 | 
						|
		ptr = row_purge_get_partial(ptr, *clust_index, node);
 | 
						|
	} else if (node->update->info_bits & REC_INFO_MIN_REC_FLAG) {
 | 
						|
		node->ref = &trx_undo_metadata;
 | 
						|
	}
 | 
						|
 | 
						|
	return(true);
 | 
						|
}
 | 
						|
 | 
						|
/** Purges the parsed record.
 | 
						|
@param[in]	node		row purge node
 | 
						|
@param[in]	undo_rec	record to purge
 | 
						|
@param[in]	thr		query thread
 | 
						|
@param[in]	updated_extern	whether external columns were updated
 | 
						|
@return true if purged, false if skipped */
 | 
						|
static MY_ATTRIBUTE((nonnull, warn_unused_result))
 | 
						|
bool
 | 
						|
row_purge_record_func(
 | 
						|
	purge_node_t*	node,
 | 
						|
	const trx_undo_rec_t*	undo_rec,
 | 
						|
#if defined UNIV_DEBUG || defined WITH_WSREP
 | 
						|
	const que_thr_t*thr,
 | 
						|
#endif /* UNIV_DEBUG || WITH_WSREP */
 | 
						|
	bool		updated_extern)
 | 
						|
{
 | 
						|
	ut_ad(!node->found_clust);
 | 
						|
	ut_ad(!node->table->skip_alter_undo);
 | 
						|
	ut_ad(!trx_undo_roll_ptr_is_insert(node->roll_ptr));
 | 
						|
 | 
						|
	node->index = dict_table_get_next_index(
 | 
						|
		dict_table_get_first_index(node->table));
 | 
						|
 | 
						|
	bool purged = true;
 | 
						|
 | 
						|
	switch (node->rec_type) {
 | 
						|
	case TRX_UNDO_EMPTY:
 | 
						|
		break;
 | 
						|
	case TRX_UNDO_DEL_MARK_REC:
 | 
						|
		purged = row_purge_del_mark(node);
 | 
						|
		if (purged) {
 | 
						|
			if (node->table->stat_initialized()
 | 
						|
			    && srv_stats_include_delete_marked) {
 | 
						|
				dict_stats_update_if_needed(
 | 
						|
					node->table, *thr->graph->trx);
 | 
						|
			}
 | 
						|
			MONITOR_INC(MONITOR_N_DEL_ROW_PURGE);
 | 
						|
		}
 | 
						|
		break;
 | 
						|
	case TRX_UNDO_INSERT_METADATA:
 | 
						|
	case TRX_UNDO_INSERT_REC:
 | 
						|
		node->roll_ptr |= 1ULL << ROLL_PTR_INSERT_FLAG_POS;
 | 
						|
		/* fall through */
 | 
						|
	default:
 | 
						|
		if (!updated_extern) {
 | 
						|
			mtr_t		mtr;
 | 
						|
			row_purge_reset_trx_id(node, &mtr);
 | 
						|
			break;
 | 
						|
		}
 | 
						|
		/* fall through */
 | 
						|
	case TRX_UNDO_UPD_EXIST_REC:
 | 
						|
		row_purge_upd_exist_or_extern(thr, node, undo_rec);
 | 
						|
		MONITOR_INC(MONITOR_N_UPD_EXIST_EXTERN);
 | 
						|
		break;
 | 
						|
	}
 | 
						|
 | 
						|
	if (node->found_clust) {
 | 
						|
		node->found_clust = false;
 | 
						|
		btr_pcur_close(&node->pcur);
 | 
						|
	}
 | 
						|
 | 
						|
	return(purged);
 | 
						|
}
 | 
						|
 | 
						|
#if defined UNIV_DEBUG || defined WITH_WSREP
 | 
						|
# define row_purge_record(node,undo_rec,thr,updated_extern)	\
 | 
						|
	row_purge_record_func(node,undo_rec,thr,updated_extern)
 | 
						|
#else /* UNIV_DEBUG || WITH_WSREP */
 | 
						|
# define row_purge_record(node,undo_rec,thr,updated_extern)	\
 | 
						|
	row_purge_record_func(node,undo_rec,updated_extern)
 | 
						|
#endif /* UNIV_DEBUG || WITH_WSREP */
 | 
						|
 | 
						|
/***********************************************************//**
 | 
						|
Fetches an undo log record and does the purge for the recorded operation.
 | 
						|
If none left, or the current purge completed, returns the control to the
 | 
						|
parent node, which is always a query thread node. */
 | 
						|
static MY_ATTRIBUTE((nonnull))
 | 
						|
void
 | 
						|
row_purge(
 | 
						|
/*======*/
 | 
						|
	purge_node_t*	node,		/*!< in: row purge node */
 | 
						|
	const trx_undo_rec_t*	undo_rec,	/*!< in: record to purge */
 | 
						|
	que_thr_t*	thr)		/*!< in: query thread */
 | 
						|
{
 | 
						|
	if (undo_rec != reinterpret_cast<trx_undo_rec_t*>(-1)) {
 | 
						|
		bool	updated_extern;
 | 
						|
 | 
						|
		while (row_purge_parse_undo_rec(
 | 
						|
			       node, undo_rec, thr, &updated_extern)) {
 | 
						|
 | 
						|
			bool purged = row_purge_record(
 | 
						|
				node, undo_rec, thr, updated_extern);
 | 
						|
 | 
						|
			if (purged
 | 
						|
			    || srv_shutdown_state > SRV_SHUTDOWN_INITIATED) {
 | 
						|
				return;
 | 
						|
			}
 | 
						|
 | 
						|
			/* Retry the purge in a second. */
 | 
						|
			std::this_thread::sleep_for(std::chrono::seconds(1));
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
inline void purge_node_t::start()
 | 
						|
{
 | 
						|
  ut_ad(in_progress);
 | 
						|
  DBUG_ASSERT(common.type == QUE_NODE_PURGE);
 | 
						|
 | 
						|
  row= nullptr;
 | 
						|
  ref= nullptr;
 | 
						|
  index= nullptr;
 | 
						|
  update= nullptr;
 | 
						|
  found_clust= false;
 | 
						|
  rec_type= 0;
 | 
						|
  cmpl_info= 0;
 | 
						|
}
 | 
						|
 | 
						|
/** Reset the state at end
 | 
						|
@return the query graph parent */
 | 
						|
inline que_node_t *purge_node_t::end(THD *thd)
 | 
						|
{
 | 
						|
  DBUG_ASSERT(common.type == QUE_NODE_PURGE);
 | 
						|
  ut_ad(undo_recs.empty());
 | 
						|
  ut_d(in_progress= false);
 | 
						|
  innobase_reset_background_thd(thd);
 | 
						|
#ifndef DBUG_OFF
 | 
						|
  static_cast<MDL_context*>(thd_mdl_context(thd))->lock_warrant= nullptr;
 | 
						|
#endif
 | 
						|
  mem_heap_empty(heap);
 | 
						|
  return common.parent;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/***********************************************************//**
 | 
						|
Does the purge operation.
 | 
						|
@return query thread to run next */
 | 
						|
que_thr_t*
 | 
						|
row_purge_step(
 | 
						|
/*===========*/
 | 
						|
	que_thr_t*	thr)	/*!< in: query thread */
 | 
						|
{
 | 
						|
	purge_node_t*	node;
 | 
						|
 | 
						|
	node = static_cast<purge_node_t*>(thr->run_node);
 | 
						|
 | 
						|
	node->start();
 | 
						|
 | 
						|
	while (!node->undo_recs.empty()) {
 | 
						|
		trx_purge_rec_t purge_rec = node->undo_recs.front();
 | 
						|
		node->undo_recs.pop();
 | 
						|
		node->roll_ptr = purge_rec.roll_ptr;
 | 
						|
 | 
						|
		row_purge(node, purge_rec.undo_rec, thr);
 | 
						|
	}
 | 
						|
 | 
						|
	thr->run_node = node->end(current_thd);
 | 
						|
	return(thr);
 | 
						|
}
 | 
						|
 | 
						|
#ifdef UNIV_DEBUG
 | 
						|
/***********************************************************//**
 | 
						|
Validate the persistent cursor. The purge node has two references
 | 
						|
to the clustered index record - one via the ref member, and the
 | 
						|
other via the persistent cursor.  These two references must match
 | 
						|
each other if the found_clust flag is set.
 | 
						|
@return true if the stored copy of persistent cursor is consistent
 | 
						|
with the ref member.*/
 | 
						|
bool
 | 
						|
purge_node_t::validate_pcur()
 | 
						|
{
 | 
						|
	if (!found_clust) {
 | 
						|
		return(true);
 | 
						|
	}
 | 
						|
 | 
						|
	if (index == NULL) {
 | 
						|
		return(true);
 | 
						|
	}
 | 
						|
 | 
						|
	if (index->type == DICT_FTS) {
 | 
						|
		return(true);
 | 
						|
	}
 | 
						|
 | 
						|
	if (!pcur.old_rec) {
 | 
						|
		return(true);
 | 
						|
	}
 | 
						|
 | 
						|
	dict_index_t* clust_index = pcur.index();
 | 
						|
 | 
						|
	rec_offs* offsets = rec_get_offsets(
 | 
						|
		pcur.old_rec, clust_index, NULL, pcur.old_n_core_fields,
 | 
						|
		pcur.old_n_fields, &heap);
 | 
						|
 | 
						|
	/* Here we are comparing the purge ref record and the stored initial
 | 
						|
	part in persistent cursor. Both cases we store n_uniq fields of the
 | 
						|
	cluster index and so it is fine to do the comparison. We note this
 | 
						|
	dependency here as pcur and ref belong to different modules. */
 | 
						|
	int st = cmp_dtuple_rec(ref, pcur.old_rec, clust_index, offsets);
 | 
						|
 | 
						|
	if (st != 0) {
 | 
						|
		ib::error() << "Purge node pcur validation failed";
 | 
						|
		ib::error() << rec_printer(ref).str();
 | 
						|
		ib::error() << rec_printer(pcur.old_rec, offsets).str();
 | 
						|
		return(false);
 | 
						|
	}
 | 
						|
 | 
						|
	return(true);
 | 
						|
}
 | 
						|
#endif /* UNIV_DEBUG */
 |