mirror of
				https://github.com/MariaDB/server.git
				synced 2025-10-31 02:46:29 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			955 lines
		
	
	
	
		
			27 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			955 lines
		
	
	
	
		
			27 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*****************************************************************************
 | |
| 
 | |
| Copyright (c) 1997, 2017, Oracle and/or its affiliates. All Rights Reserved.
 | |
| Copyright (c) 2017, 2022, 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/row0vers.cc
 | |
| Row versions
 | |
| 
 | |
| Created 2/6/1997 Heikki Tuuri
 | |
| *******************************************************/
 | |
| 
 | |
| #include "row0vers.h"
 | |
| #include "dict0dict.h"
 | |
| #include "dict0boot.h"
 | |
| #include "btr0btr.h"
 | |
| #include "mach0data.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 "rem0cmp.h"
 | |
| #include "lock0lock.h"
 | |
| #include "row0mysql.h"
 | |
| 
 | |
| /** Check whether all non-virtual index fields are equal.
 | |
| @param[in]	index	the secondary index
 | |
| @param[in]	a	first index entry to compare
 | |
| @param[in]	b	second index entry to compare
 | |
| @return	whether all non-virtual fields are equal */
 | |
| static
 | |
| bool
 | |
| row_vers_non_virtual_fields_equal(
 | |
| 	const dict_index_t*	index,
 | |
| 	const dfield_t*		a,
 | |
| 	const dfield_t*		b)
 | |
| {
 | |
| 	const dict_field_t* end = &index->fields[index->n_fields];
 | |
| 
 | |
| 	for (const dict_field_t* ifield = index->fields; ifield != end;
 | |
| 	     ifield++) {
 | |
| 		if (!ifield->col->is_virtual()
 | |
| 		    && cmp_dfield_dfield(a++, b++)) {
 | |
| 			return false;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| /** Determine if an active transaction has inserted or modified a secondary
 | |
| index record.
 | |
| @param[in,out]	caller_trx	trx of current thread
 | |
| @param[in]	clust_rec	clustered index record
 | |
| @param[in]	clust_index	clustered index
 | |
| @param[in]	rec		secondary index record
 | |
| @param[in]	index		secondary index
 | |
| @param[in]	offsets		rec_get_offsets(rec, index)
 | |
| @param[in,out]	mtr		mini-transaction
 | |
| @return	the active transaction; state must be rechecked after
 | |
| acquiring trx->mutex, and trx->release_reference() must be invoked
 | |
| @retval	NULL if the record was committed */
 | |
| UNIV_INLINE
 | |
| trx_t*
 | |
| row_vers_impl_x_locked_low(
 | |
| 	trx_t*		caller_trx,
 | |
| 	const rec_t*	clust_rec,
 | |
| 	dict_index_t*	clust_index,
 | |
| 	const rec_t*	rec,
 | |
| 	dict_index_t*	index,
 | |
| 	const rec_offs*	offsets,
 | |
| 	mtr_t*		mtr)
 | |
| {
 | |
| 	trx_id_t	trx_id;
 | |
| 	rec_t*		prev_version = NULL;
 | |
| 	rec_offs	clust_offsets_[REC_OFFS_NORMAL_SIZE];
 | |
| 	rec_offs*	clust_offsets;
 | |
| 	mem_heap_t*	heap;
 | |
| 	dtuple_t*	ientry = NULL;
 | |
| 	mem_heap_t*	v_heap = NULL;
 | |
| 	dtuple_t*	cur_vrow = NULL;
 | |
| 
 | |
| 	rec_offs_init(clust_offsets_);
 | |
| 
 | |
| 	DBUG_ENTER("row_vers_impl_x_locked_low");
 | |
| 
 | |
| 	ut_ad(rec_offs_validate(rec, index, offsets));
 | |
| 	ut_ad(mtr->memo_contains_page_flagged(clust_rec,
 | |
| 					      MTR_MEMO_PAGE_S_FIX
 | |
| 					      | MTR_MEMO_PAGE_X_FIX));
 | |
| 
 | |
| 	if (ulint trx_id_offset = clust_index->trx_id_offset) {
 | |
| 		trx_id = mach_read_from_6(clust_rec + trx_id_offset);
 | |
| 		if (trx_id == 0) {
 | |
| 			/* The transaction history was already purged. */
 | |
| 			DBUG_RETURN(0);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	heap = mem_heap_create(1024);
 | |
| 
 | |
| 	clust_offsets = rec_get_offsets(clust_rec, clust_index, clust_offsets_,
 | |
| 					clust_index->n_core_fields,
 | |
| 					ULINT_UNDEFINED, &heap);
 | |
| 
 | |
| 	trx_id = row_get_rec_trx_id(clust_rec, clust_index, clust_offsets);
 | |
| 	if (trx_id == 0) {
 | |
| 		/* The transaction history was already purged. */
 | |
| 		mem_heap_free(heap);
 | |
| 		DBUG_RETURN(0);
 | |
| 	}
 | |
| 
 | |
| 	ut_ad(!clust_index->table->is_temporary());
 | |
| 
 | |
| 	trx_t*	trx;
 | |
| 
 | |
| 	if (trx_id == caller_trx->id) {
 | |
| 		trx = caller_trx;
 | |
| 		trx->reference();
 | |
| 	} else {
 | |
| 		trx = trx_sys.find(caller_trx, trx_id);
 | |
| 		if (trx == 0) {
 | |
| 			/* The transaction that modified or inserted
 | |
| 			clust_rec is no longer active, or it is
 | |
| 			corrupt: no implicit lock on rec */
 | |
| 			lock_check_trx_id_sanity(trx_id, clust_rec,
 | |
| 						 clust_index, clust_offsets);
 | |
| 			mem_heap_free(heap);
 | |
| 			DBUG_RETURN(0);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	const bool comp = index->table->not_redundant();
 | |
|         ut_ad(!!page_rec_is_comp(rec) == comp);
 | |
| 	ut_ad(index->table == clust_index->table);
 | |
| 	ut_ad(!comp == !page_rec_is_comp(clust_rec));
 | |
| 
 | |
| 	const ulint rec_del = rec_get_deleted_flag(rec, comp);
 | |
| 
 | |
| 	if (dict_index_has_virtual(index)) {
 | |
| 		ulint	est_size = DTUPLE_EST_ALLOC(index->n_fields);
 | |
| 
 | |
| 		/* Allocate the dtuple for virtual columns extracted from undo
 | |
| 		log with its own heap, so to avoid it being freed as we
 | |
| 		iterating in the version loop below. */
 | |
| 		v_heap = mem_heap_create(est_size);
 | |
| 		ientry = row_rec_to_index_entry(rec, index, offsets, v_heap);
 | |
| 	}
 | |
| 
 | |
| 	/* We look up if some earlier version, which was modified by
 | |
| 	the trx_id transaction, of the clustered index record would
 | |
| 	require rec to be in a different state (delete marked or
 | |
| 	unmarked, or have different field values, or not existing). If
 | |
| 	there is such a version, then rec was modified by the trx_id
 | |
| 	transaction, and it has an implicit x-lock on rec. Note that
 | |
| 	if clust_rec itself would require rec to be in a different
 | |
| 	state, then the trx_id transaction has not yet had time to
 | |
| 	modify rec, and does not necessarily have an implicit x-lock
 | |
| 	on rec. */
 | |
| 
 | |
| 	for (const rec_t* version = clust_rec;; version = prev_version) {
 | |
| 		row_ext_t*	ext;
 | |
| 		dtuple_t*	row;
 | |
| 		dtuple_t*	entry;
 | |
| 		ulint		vers_del;
 | |
| 		trx_id_t	prev_trx_id;
 | |
| 		mem_heap_t*	old_heap = heap;
 | |
| 		dtuple_t*	vrow = NULL;
 | |
| 
 | |
| 		/* We keep the semaphore in mtr on the clust_rec page, so
 | |
| 		that no other transaction can update it and get an
 | |
| 		implicit x-lock on rec until mtr_commit(mtr). */
 | |
| 
 | |
| 		heap = mem_heap_create(1024);
 | |
| 
 | |
| 		trx_undo_prev_version_build(
 | |
| 			version, clust_index, clust_offsets,
 | |
| 			heap, &prev_version, mtr, 0, NULL,
 | |
| 			dict_index_has_virtual(index) ? &vrow : NULL);
 | |
| 		ut_d(bool owns_trx_mutex = trx->mutex_is_owner());
 | |
| 		ut_d(if (!owns_trx_mutex)
 | |
| 		    trx->mutex_lock();)
 | |
| 		const bool committed = trx_state_eq(
 | |
| 			trx, TRX_STATE_COMMITTED_IN_MEMORY);
 | |
| 		ut_d(if (!owns_trx_mutex)
 | |
| 		  trx->mutex_unlock();)
 | |
| 
 | |
| 		/* The oldest visible clustered index version must not be
 | |
| 		delete-marked, because we never start a transaction by
 | |
| 		inserting a delete-marked record. */
 | |
| 		ut_ad(committed || prev_version
 | |
| 		      || !rec_get_deleted_flag(version, comp));
 | |
| 
 | |
| 		/* Free version and clust_offsets. */
 | |
| 		mem_heap_free(old_heap);
 | |
| 
 | |
| 		if (committed) {
 | |
| 			goto not_locked;
 | |
| 		}
 | |
| 
 | |
| 		if (prev_version == NULL) {
 | |
| 
 | |
| 			/* We reached the oldest visible version without
 | |
| 			finding an older version of clust_rec that would
 | |
| 			match the secondary index record.  If the secondary
 | |
| 			index record is not delete marked, then clust_rec
 | |
| 			is considered the correct match of the secondary
 | |
| 			index record and hence holds the implicit lock. */
 | |
| 
 | |
| 			if (rec_del) {
 | |
| 				/* The secondary index record is del marked.
 | |
| 				So, the implicit lock holder of clust_rec
 | |
| 				did not modify the secondary index record yet,
 | |
| 				and is not holding an implicit lock on it.
 | |
| 
 | |
| 				This assumes that whenever a row is inserted
 | |
| 				or updated, the leaf page record always is
 | |
| 				created with a clear delete-mark flag.
 | |
| 				(We never insert a delete-marked record.) */
 | |
| not_locked:
 | |
| 				trx->release_reference();
 | |
| 				trx = 0;
 | |
| 			}
 | |
| 
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 		clust_offsets = rec_get_offsets(
 | |
| 			prev_version, clust_index, clust_offsets_,
 | |
| 			clust_index->n_core_fields,
 | |
| 			ULINT_UNDEFINED, &heap);
 | |
| 
 | |
| 		vers_del = rec_get_deleted_flag(prev_version, comp);
 | |
| 
 | |
| 		prev_trx_id = row_get_rec_trx_id(prev_version, clust_index,
 | |
| 						 clust_offsets);
 | |
| 
 | |
| 		/* 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)) {
 | |
| 			if (vrow) {
 | |
| 				/* Keep the virtual row info for the next
 | |
| 				version */
 | |
| 				cur_vrow = dtuple_copy(vrow, v_heap);
 | |
| 				dtuple_dup_v_fld(cur_vrow, v_heap);
 | |
| 			}
 | |
| 
 | |
| 			if (!cur_vrow) {
 | |
| 				/* Build index entry out of row */
 | |
| 				entry = row_build_index_entry(row, ext, index,
 | |
| 							      heap);
 | |
| 
 | |
| 				/* entry could only be NULL (the
 | |
| 				clustered index record could contain
 | |
| 				BLOB pointers that are NULL) if we
 | |
| 				were accessing a freshly inserted
 | |
| 				record before it was fully inserted.
 | |
| 				prev_version cannot possibly be such
 | |
| 				an incomplete record, because its
 | |
| 				transaction would have to be committed
 | |
| 				in order for later versions of the
 | |
| 				record to be able to exist. */
 | |
| 				ut_ad(entry);
 | |
| 
 | |
| 				/* If the indexed virtual columns has changed,
 | |
| 				there must be log record to generate vrow.
 | |
| 				Otherwise, it is not changed, so no need
 | |
| 				to compare */
 | |
| 				if (!row_vers_non_virtual_fields_equal(
 | |
| 					    index,
 | |
| 					    ientry->fields, entry->fields)) {
 | |
| 					if (rec_del != vers_del) {
 | |
| 						break;
 | |
| 					}
 | |
| 				} else if (!rec_del) {
 | |
| 					break;
 | |
| 				}
 | |
| 
 | |
| 				goto result_check;
 | |
| 			} else {
 | |
| 				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);
 | |
| 
 | |
| 		/* entry could only be NULL (the clustered index
 | |
| 		record could contain BLOB pointers that are NULL) if
 | |
| 		we were accessing a freshly inserted record before it
 | |
| 		was fully inserted.  prev_version cannot possibly be
 | |
| 		such an incomplete record, because its transaction
 | |
| 		would have to be committed in order for later versions
 | |
| 		of the record to be able to exist. */
 | |
| 		ut_ad(entry);
 | |
| 
 | |
| 		/* If we get here, we know that the trx_id transaction
 | |
| 		modified prev_version. Let us check if prev_version
 | |
| 		would require rec to be in a different state. */
 | |
| 
 | |
| 		/* The previous version of clust_rec must be
 | |
| 		accessible, because clust_rec was not a fresh insert.
 | |
| 		There is no guarantee that the transaction is still
 | |
| 		active. */
 | |
| 
 | |
| 		/* We check if entry and rec are identified in the alphabetical
 | |
| 		ordering */
 | |
| 		if (0 == cmp_dtuple_rec(entry, rec, index, offsets)) {
 | |
| 			/* The delete marks of rec and prev_version should be
 | |
| 			equal for rec to be in the state required by
 | |
| 			prev_version */
 | |
| 
 | |
| 			if (rec_del != vers_del) {
 | |
| 
 | |
| 				break;
 | |
| 			}
 | |
| 
 | |
| 			/* It is possible that the row was updated so that the
 | |
| 			secondary index record remained the same in
 | |
| 			alphabetical ordering, but the field values changed
 | |
| 			still. For example, 'abc' -> 'ABC'. Check also that. */
 | |
| 
 | |
| 			dtuple_set_types_binary(
 | |
| 				entry, dtuple_get_n_fields(entry));
 | |
| 
 | |
| 			if (cmp_dtuple_rec(entry, rec, index, offsets)) {
 | |
| 
 | |
| 				break;
 | |
| 			}
 | |
| 
 | |
| 		} else if (!rec_del) {
 | |
| 			/* The delete mark should be set in rec for it to be
 | |
| 			in the state required by prev_version */
 | |
| 
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| result_check:
 | |
| 		if (trx->id != prev_trx_id) {
 | |
| 			/* prev_version was the first version modified by
 | |
| 			the trx_id transaction: no implicit x-lock */
 | |
| 			goto not_locked;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (trx) {
 | |
| 		DBUG_PRINT("info", ("Implicit lock is held by trx:" TRX_ID_FMT,
 | |
| 				    trx_id));
 | |
| 	}
 | |
| 
 | |
| 	if (v_heap != NULL) {
 | |
| 		mem_heap_free(v_heap);
 | |
| 	}
 | |
| 
 | |
| 	mem_heap_free(heap);
 | |
| 	DBUG_RETURN(trx);
 | |
| }
 | |
| 
 | |
| /** Determine if an active transaction has inserted or modified a secondary
 | |
| index record.
 | |
| @param[in,out]	caller_trx	trx of current thread
 | |
| @param[in]	rec	secondary index record
 | |
| @param[in]	index	secondary index
 | |
| @param[in]	offsets	rec_get_offsets(rec, index)
 | |
| @return	the active transaction; state must be rechecked after
 | |
| acquiring trx->mutex, and trx->release_reference() must be invoked
 | |
| @retval	NULL if the record was committed */
 | |
| trx_t*
 | |
| row_vers_impl_x_locked(
 | |
| 	trx_t*		caller_trx,
 | |
| 	const rec_t*	rec,
 | |
| 	dict_index_t*	index,
 | |
| 	const rec_offs*	offsets)
 | |
| {
 | |
| 	mtr_t		mtr;
 | |
| 	trx_t*		trx;
 | |
| 	const rec_t*	clust_rec;
 | |
| 	dict_index_t*	clust_index;
 | |
| 
 | |
| 	/* The function must not be invoked under lock_sys latch to prevert
 | |
| 	latching orded violation, i.e. page latch must be acquired before
 | |
| 	lock_sys latch */
 | |
| 	lock_sys.assert_unlocked();
 | |
| 	/* The current function can be called from lock_rec_unlock_unmodified()
 | |
| 	under lock_sys.wr_lock() */
 | |
| 
 | |
| 	mtr_start(&mtr);
 | |
| 
 | |
| 	/* Search for the clustered index record. The latch on the
 | |
| 	page of clust_rec locks the top of the stack of versions. The
 | |
| 	bottom of the version stack is not locked; oldest versions may
 | |
| 	disappear by the fact that transactions may be committed and
 | |
| 	collected by the purge. This is not a problem, because we are
 | |
| 	only interested in active transactions. */
 | |
| 
 | |
| 	clust_rec = row_get_clust_rec(
 | |
| 		BTR_SEARCH_LEAF, rec, index, &clust_index, &mtr);
 | |
| 
 | |
| 	if (!clust_rec) {
 | |
| 		/* In a rare case it is possible that no clust rec is found
 | |
| 		for a secondary index record: if in row0umod.cc
 | |
| 		row_undo_mod_remove_clust_low() we have already removed the
 | |
| 		clust rec, while purge is still cleaning and removing
 | |
| 		secondary index records associated with earlier versions of
 | |
| 		the clustered index record. In that case there cannot be
 | |
| 		any implicit lock on the secondary index record, because
 | |
| 		an active transaction which has modified the secondary index
 | |
| 		record has also modified the clustered index record. And in
 | |
| 		a rollback we always undo the modifications to secondary index
 | |
| 		records before the clustered index record. */
 | |
| 
 | |
| 		trx = 0;
 | |
| 	} else {
 | |
| 		trx = row_vers_impl_x_locked_low(
 | |
| 				caller_trx, clust_rec, clust_index, rec, index,
 | |
| 				offsets, &mtr);
 | |
| 
 | |
| 		ut_ad(trx == 0 || trx->is_referenced());
 | |
| 	}
 | |
| 
 | |
| 	mtr_commit(&mtr);
 | |
| 
 | |
| 	return(trx);
 | |
| }
 | |
| 
 | |
| /** build virtual column value from current cluster index record data
 | |
| @param[in,out]	row		the cluster index row in dtuple form
 | |
| @param[in]	clust_index	clustered index
 | |
| @param[in]	index		the secondary index
 | |
| @param[in]	heap		heap used to build virtual dtuple. */
 | |
| bool
 | |
| row_vers_build_clust_v_col(
 | |
| 	dtuple_t*		row,
 | |
| 	dict_index_t*		clust_index,
 | |
| 	dict_index_t*		index,
 | |
| 	mem_heap_t*		heap)
 | |
| {
 | |
| 	THD*		thd= current_thd;
 | |
| 	TABLE*		maria_table= 0;
 | |
| 
 | |
| 	ut_ad(dict_index_has_virtual(index));
 | |
| 	ut_ad(index->table == clust_index->table);
 | |
| 
 | |
| 	DEBUG_SYNC(current_thd, "ib_clust_v_col_before_row_allocated");
 | |
| 
 | |
| 	ib_vcol_row vc(nullptr);
 | |
| 	byte *record = vc.record(thd, index, &maria_table);
 | |
| 
 | |
| 	ut_ad(maria_table);
 | |
| 
 | |
| 	for (ulint i = 0; i < dict_index_get_n_fields(index); i++) {
 | |
| 		const dict_col_t* c = dict_index_get_nth_col(index, i);
 | |
| 
 | |
| 		if (c->is_virtual()) {
 | |
| 			const dict_v_col_t* col
 | |
| 				= reinterpret_cast<const dict_v_col_t*>(c);
 | |
| 
 | |
| 			dfield_t *vfield = innobase_get_computed_value(
 | |
| 				row, col, clust_index, &vc.heap,
 | |
| 				heap, NULL, thd, maria_table, record, NULL,
 | |
| 				NULL);
 | |
| 			if (!vfield) {
 | |
| 				innobase_report_computed_value_failed(row);
 | |
| 				ut_ad(0);
 | |
| 				return false;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| /** Build latest virtual column data from undo log
 | |
| @param[in]	rec		clustered index record
 | |
| @param[in]	clust_index	clustered index
 | |
| @param[in,out]	clust_offsets	offsets on the clustered index record
 | |
| @param[in]	index		the secondary index
 | |
| @param[in]	trx_id		transaction ID on the purging record,
 | |
| 				or 0 if called outside purge
 | |
| @param[in]	roll_ptr	the rollback pointer for the purging record
 | |
| @param[in,out]	v_heap		heap used to build vrow
 | |
| @param[out]	v_row		dtuple holding the virtual rows
 | |
| @param[in,out]	mtr		mtr holding the latch on rec */
 | |
| static
 | |
| void
 | |
| row_vers_build_cur_vrow_low(
 | |
| 	const rec_t*		rec,
 | |
| 	dict_index_t*		clust_index,
 | |
| 	rec_offs*		clust_offsets,
 | |
| 	dict_index_t*		index,
 | |
| 	trx_id_t		trx_id,
 | |
| 	roll_ptr_t		roll_ptr,
 | |
| 	mem_heap_t*		v_heap,
 | |
| 	dtuple_t**		vrow,
 | |
| 	mtr_t*			mtr)
 | |
| {
 | |
| 	const rec_t*	version;
 | |
| 	rec_t*		prev_version;
 | |
| 	mem_heap_t*	heap = NULL;
 | |
| 	ulint		num_v = dict_table_get_n_v_cols(index->table);
 | |
| 	const dfield_t* field;
 | |
| 	ulint		i;
 | |
| 	bool		all_filled = false;
 | |
| 
 | |
| 	*vrow = dtuple_create_with_vcol(v_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;
 | |
| 	}
 | |
| 
 | |
| 	ut_ad(mtr->memo_contains_page_flagged(rec,
 | |
| 					      MTR_MEMO_PAGE_S_FIX
 | |
| 					      | MTR_MEMO_PAGE_X_FIX));
 | |
| 
 | |
| 	version = rec;
 | |
| 
 | |
| 	/* If this is called by purge thread, set TRX_UNDO_PREV_IN_PURGE
 | |
| 	bit to search the undo log until we hit the current undo log with
 | |
| 	roll_ptr */
 | |
| 	const ulint	status = trx_id
 | |
| 		? TRX_UNDO_PREV_IN_PURGE | TRX_UNDO_GET_OLD_V_VALUE
 | |
| 		: TRX_UNDO_GET_OLD_V_VALUE;
 | |
| 
 | |
| 	while (!all_filled) {
 | |
| 		mem_heap_t*	heap2 = heap;
 | |
| 		heap = mem_heap_create(1024);
 | |
| 		roll_ptr_t	cur_roll_ptr = row_get_rec_roll_ptr(
 | |
| 			version, clust_index, clust_offsets);
 | |
| 
 | |
| 		trx_undo_prev_version_build(
 | |
| 			version, clust_index, clust_offsets,
 | |
| 			heap, &prev_version, mtr, status, nullptr, vrow);
 | |
| 
 | |
| 		if (heap2) {
 | |
| 			mem_heap_free(heap2);
 | |
| 		}
 | |
| 
 | |
| 		if (!prev_version) {
 | |
| 			/* Versions end here */
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 		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);
 | |
| 
 | |
| 		all_filled = true;
 | |
| 
 | |
| 		for (i = 0; i < entry_len; i++) {
 | |
| 			const dict_col_t* col
 | |
| 				= dict_index_get_nth_col(index, i);
 | |
| 
 | |
| 			if (!col->is_virtual()) {
 | |
| 				continue;
 | |
| 			}
 | |
| 
 | |
| 			const dict_v_col_t*	v_col
 | |
| 				= reinterpret_cast<const dict_v_col_t*>(col);
 | |
| 			field = dtuple_get_nth_v_field(*vrow, v_col->v_pos);
 | |
| 
 | |
| 			if (dfield_get_type(field)->mtype == DATA_MISSING) {
 | |
| 				all_filled = false;
 | |
| 				break;
 | |
| 			}
 | |
| 
 | |
| 		}
 | |
| 
 | |
| 		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;
 | |
| 	}
 | |
| 
 | |
| 	mem_heap_free(heap);
 | |
| }
 | |
| 
 | |
| /** Build a dtuple contains virtual column data for current cluster index
 | |
| @param[in]	in_purge	called by purge thread
 | |
| @param[in]	rec		cluster index rec
 | |
| @param[in]	clust_index	cluster index
 | |
| @param[in]	clust_offsets	cluster rec offset
 | |
| @param[in]	index		secondary index
 | |
| @param[in]	trx_id		transaction ID on the purging record,
 | |
| 				or 0 if called outside purge
 | |
| @param[in]	roll_ptr	roll_ptr for the purge record
 | |
| @param[in,out]	heap		heap memory
 | |
| @param[in,out]	v_heap		heap memory to keep virtual column tuple
 | |
| @param[in,out]	mtr		mini-transaction
 | |
| @return dtuple contains virtual column data */
 | |
| dtuple_t*
 | |
| row_vers_build_cur_vrow(
 | |
| 	const rec_t*		rec,
 | |
| 	dict_index_t*		clust_index,
 | |
| 	rec_offs**		clust_offsets,
 | |
| 	dict_index_t*		index,
 | |
| 	trx_id_t		trx_id,
 | |
| 	roll_ptr_t		roll_ptr,
 | |
| 	mem_heap_t*		heap,
 | |
| 	mem_heap_t*		v_heap,
 | |
| 	mtr_t*			mtr)
 | |
| {
 | |
| 	dtuple_t* cur_vrow = NULL;
 | |
| 
 | |
| 	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)) {
 | |
| 
 | |
| 		ut_ad(!rec_get_deleted_flag(rec, page_rec_is_comp(rec)));
 | |
| 
 | |
| 		/* This is a newly inserted record and cannot
 | |
| 		be deleted, So the externally stored field
 | |
| 		cannot be freed yet. */
 | |
| 		dtuple_t* row = row_build(ROW_COPY_POINTERS, clust_index,
 | |
| 					  rec, *clust_offsets,
 | |
| 					  NULL, NULL, NULL, NULL, heap);
 | |
| 
 | |
| 		if (!row_vers_build_clust_v_col(row, clust_index, index,
 | |
| 						heap)) {
 | |
| 			return nullptr;
 | |
| 		}
 | |
| 
 | |
| 		cur_vrow = dtuple_copy(row, v_heap);
 | |
| 		dtuple_dup_v_fld(cur_vrow, v_heap);
 | |
| 	} else {
 | |
| 		/* Try to fetch virtual column data from undo log */
 | |
| 		row_vers_build_cur_vrow_low(
 | |
| 			rec, clust_index, *clust_offsets,
 | |
| 			index, trx_id, roll_ptr, v_heap, &cur_vrow, mtr);
 | |
| 	}
 | |
| 
 | |
| 	*clust_offsets = rec_get_offsets(rec, clust_index, NULL,
 | |
| 					 clust_index->n_core_fields,
 | |
| 					 ULINT_UNDEFINED, &heap);
 | |
| 	return(cur_vrow);
 | |
| }
 | |
| 
 | |
| /** Find out whether data tuple has missing data type
 | |
| for indexed virtual column.
 | |
| @param tuple   data tuple
 | |
| @param index   virtual index
 | |
| @return true if tuple has missing column type */
 | |
| bool dtuple_vcol_data_missing(const dtuple_t &tuple,
 | |
|                               const dict_index_t &index)
 | |
| {
 | |
|   for (ulint i= 0; i < index.n_uniq; i++)
 | |
|   {
 | |
|     dict_col_t *col= index.fields[i].col;
 | |
|     if (!col->is_virtual())
 | |
|       continue;
 | |
|     dict_v_col_t *vcol= reinterpret_cast<dict_v_col_t*>(col);
 | |
|     for (ulint j= 0; j < index.table->n_v_cols; j++)
 | |
|       if (vcol == &index.table->v_cols[j] &&
 | |
|           tuple.v_fields[j].type.mtype == DATA_MISSING)
 | |
|         return true;
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| /*****************************************************************//**
 | |
| Constructs the version of a clustered index record which a consistent
 | |
| read should see. We assume that the trx id stored in rec is such that
 | |
| the consistent read should not see rec in its present version.
 | |
| @return error code
 | |
| @retval DB_SUCCESS if a previous version was fetched
 | |
| @retval DB_MISSING_HISTORY if the history is missing (a sign of corruption) */
 | |
| dberr_t
 | |
| row_vers_build_for_consistent_read(
 | |
| /*===============================*/
 | |
| 	const rec_t*	rec,	/*!< in: record in a clustered index; the
 | |
| 				caller must have a latch on the page; this
 | |
| 				latch locks the top of the stack of versions
 | |
| 				of this records */
 | |
| 	mtr_t*		mtr,	/*!< in: mtr holding the latch on rec */
 | |
| 	dict_index_t*	index,	/*!< in: the clustered index */
 | |
| 	rec_offs**	offsets,/*!< in/out: offsets returned by
 | |
| 				rec_get_offsets(rec, index) */
 | |
| 	ReadView*	view,	/*!< in: the consistent read view */
 | |
| 	mem_heap_t**	offset_heap,/*!< in/out: memory heap from which
 | |
| 				the offsets are allocated */
 | |
| 	mem_heap_t*	in_heap,/*!< in: memory heap from which the memory for
 | |
| 				*old_vers is allocated; memory for possible
 | |
| 				intermediate versions is allocated and freed
 | |
| 				locally within the function */
 | |
| 	rec_t**		old_vers,/*!< out, own: old version, or NULL
 | |
| 				if the history is missing or the record
 | |
| 				does not exist in the view, that is,
 | |
| 				it was freshly inserted afterwards */
 | |
| 	dtuple_t**	vrow)	/*!< out: virtual row */
 | |
| {
 | |
| 	const rec_t*	version;
 | |
| 	rec_t*		prev_version;
 | |
| 	trx_id_t	trx_id;
 | |
| 	mem_heap_t*	heap		= NULL;
 | |
| 	byte*		buf;
 | |
| 	dberr_t		err;
 | |
| 
 | |
| 	ut_ad(index->is_primary());
 | |
| 	ut_ad(mtr->memo_contains_page_flagged(rec, MTR_MEMO_PAGE_X_FIX
 | |
| 					      | MTR_MEMO_PAGE_S_FIX));
 | |
| 
 | |
| 	ut_ad(rec_offs_validate(rec, index, *offsets));
 | |
| 
 | |
| 	trx_id = row_get_rec_trx_id(rec, index, *offsets);
 | |
| 
 | |
| 	ut_ad(!view->changes_visible(trx_id));
 | |
| 
 | |
| 	ut_ad(!vrow || !(*vrow));
 | |
| 
 | |
| 	version = rec;
 | |
| 
 | |
| 	for (;;) {
 | |
| 		mem_heap_t*	prev_heap = heap;
 | |
| 
 | |
| 		heap = mem_heap_create(1024);
 | |
| 
 | |
| 		if (vrow) {
 | |
| 			*vrow = NULL;
 | |
| 		}
 | |
| 
 | |
| 		/* If purge can't see the record then we can't rely on
 | |
| 		the UNDO log record. */
 | |
| 
 | |
| 		err = trx_undo_prev_version_build(
 | |
| 			version, index, *offsets, heap,
 | |
| 			&prev_version, mtr, 0, NULL, vrow);
 | |
| 
 | |
| 		if (prev_heap != NULL) {
 | |
| 			mem_heap_free(prev_heap);
 | |
| 		}
 | |
| 
 | |
| 		if (prev_version == NULL) {
 | |
| 			/* It was a freshly inserted version */
 | |
| 			*old_vers = NULL;
 | |
| 			ut_ad(!vrow || !(*vrow));
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 		*offsets = rec_get_offsets(
 | |
| 			prev_version, index, *offsets,
 | |
| 			index->n_core_fields, ULINT_UNDEFINED, offset_heap);
 | |
| 
 | |
| #if defined UNIV_DEBUG || defined UNIV_BLOB_LIGHT_DEBUG
 | |
| 		ut_a(!rec_offs_any_null_extern(prev_version, *offsets));
 | |
| #endif /* UNIV_DEBUG || UNIV_BLOB_LIGHT_DEBUG */
 | |
| 
 | |
| 		trx_id = row_get_rec_trx_id(prev_version, index, *offsets);
 | |
| 
 | |
| 		if (view->changes_visible(trx_id)) {
 | |
| 
 | |
| 			/* The view already sees this version: we can copy
 | |
| 			it to in_heap and return */
 | |
| 
 | |
| 			buf = static_cast<byte*>(
 | |
| 				mem_heap_alloc(
 | |
| 					in_heap, rec_offs_size(*offsets)));
 | |
| 
 | |
| 			*old_vers = rec_copy(buf, prev_version, *offsets);
 | |
| 			rec_offs_make_valid(*old_vers, index, true, *offsets);
 | |
| 
 | |
| 			if (vrow && *vrow) {
 | |
| 				*vrow = dtuple_copy(*vrow, in_heap);
 | |
| 				dtuple_dup_v_fld(*vrow, in_heap);
 | |
| 			}
 | |
| 			break;
 | |
| 		} else if (trx_id >= view->low_limit_id()
 | |
| 			   && trx_id >= trx_sys.get_max_trx_id()) {
 | |
| 			err = DB_CORRUPTION;
 | |
| 			break;
 | |
| 		}
 | |
| 		version = prev_version;
 | |
| 	}
 | |
| 
 | |
| 	mem_heap_free(heap);
 | |
| 
 | |
| 	return(err);
 | |
| }
 | |
| 
 | |
| #if defined __aarch64__&&defined __GNUC__&&__GNUC__==4&&!defined __clang__
 | |
| /* Avoid GCC 4.8.5 internal compiler error "could not split insn". */
 | |
| # pragma GCC optimize ("O0")
 | |
| #endif
 | |
| /*****************************************************************//**
 | |
| Constructs the last committed version of a clustered index record,
 | |
| which should be seen by a semi-consistent read. */
 | |
| void
 | |
| row_vers_build_for_semi_consistent_read(
 | |
| /*====================================*/
 | |
| 	trx_t*		caller_trx,/*!<in/out: trx of current thread */
 | |
| 	const rec_t*	rec,	/*!< in: record in a clustered index; the
 | |
| 				caller must have a latch on the page; this
 | |
| 				latch locks the top of the stack of versions
 | |
| 				of this records */
 | |
| 	mtr_t*		mtr,	/*!< in: mtr holding the latch on rec */
 | |
| 	dict_index_t*	index,	/*!< in: the clustered index */
 | |
| 	rec_offs**	offsets,/*!< in/out: offsets returned by
 | |
| 				rec_get_offsets(rec, index) */
 | |
| 	mem_heap_t**	offset_heap,/*!< in/out: memory heap from which
 | |
| 				the offsets are allocated */
 | |
| 	mem_heap_t*	in_heap,/*!< in: memory heap from which the memory for
 | |
| 				*old_vers is allocated; memory for possible
 | |
| 				intermediate versions is allocated and freed
 | |
| 				locally within the function */
 | |
| 	const rec_t**	old_vers,/*!< out: rec, old version, or NULL if the
 | |
| 				record does not exist in the view, that is,
 | |
| 				it was freshly inserted afterwards */
 | |
| 	dtuple_t**	vrow)	/*!< out: virtual row, old version, or NULL
 | |
| 				if it is not updated in the view */
 | |
| {
 | |
| 	const rec_t*	version;
 | |
| 	mem_heap_t*	heap		= NULL;
 | |
| 	byte*		buf;
 | |
| 	trx_id_t	rec_trx_id	= 0;
 | |
| 
 | |
| 	ut_ad(index->is_primary());
 | |
| 	ut_ad(mtr->memo_contains_page_flagged(rec, MTR_MEMO_PAGE_X_FIX
 | |
| 					      | MTR_MEMO_PAGE_S_FIX));
 | |
| 
 | |
| 	ut_ad(rec_offs_validate(rec, index, *offsets));
 | |
| 
 | |
| 	version = rec;
 | |
| 	ut_ad(!vrow || !(*vrow));
 | |
| 
 | |
| 	for (;;) {
 | |
| 		mem_heap_t*	heap2;
 | |
| 		rec_t*		prev_version;
 | |
| 		trx_id_t	version_trx_id;
 | |
| 
 | |
| 		version_trx_id = row_get_rec_trx_id(version, index, *offsets);
 | |
| 		if (rec == version) {
 | |
| 			rec_trx_id = version_trx_id;
 | |
| 		}
 | |
| 
 | |
| 		if (!trx_sys.is_registered(caller_trx, version_trx_id)) {
 | |
| committed_version_trx:
 | |
| 			/* We found a version that belongs to a
 | |
| 			committed transaction: return it. */
 | |
| 
 | |
| #if defined UNIV_DEBUG || defined UNIV_BLOB_LIGHT_DEBUG
 | |
| 			ut_a(!rec_offs_any_null_extern(version, *offsets));
 | |
| #endif /* UNIV_DEBUG || UNIV_BLOB_LIGHT_DEBUG */
 | |
| 
 | |
| 			if (rec == version) {
 | |
| 				*old_vers = rec;
 | |
| 				if (vrow) {
 | |
| 					*vrow = NULL;
 | |
| 				}
 | |
| 				break;
 | |
| 			}
 | |
| 
 | |
| 			/* We assume that a rolled-back transaction stays in
 | |
| 			TRX_STATE_ACTIVE state until all the changes have been
 | |
| 			rolled back and the transaction is removed from
 | |
| 			the global list of transactions. */
 | |
| 
 | |
| 			if (rec_trx_id == version_trx_id) {
 | |
| 				/* The transaction was committed while
 | |
| 				we searched for earlier versions.
 | |
| 				Return the current version as a
 | |
| 				semi-consistent read. */
 | |
| 
 | |
| 				version = rec;
 | |
| 				*offsets = rec_get_offsets(
 | |
| 					version, index, *offsets,
 | |
| 					index->n_core_fields, ULINT_UNDEFINED,
 | |
| 					offset_heap);
 | |
| 			}
 | |
| 
 | |
| 			buf = static_cast<byte*>(
 | |
| 				mem_heap_alloc(
 | |
| 					in_heap, rec_offs_size(*offsets)));
 | |
| 
 | |
| 			*old_vers = rec_copy(buf, version, *offsets);
 | |
| 			rec_offs_make_valid(*old_vers, index, true, *offsets);
 | |
| 			if (vrow && *vrow) {
 | |
| 				*vrow = dtuple_copy(*vrow, in_heap);
 | |
| 				dtuple_dup_v_fld(*vrow, in_heap);
 | |
| 			}
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 		DEBUG_SYNC_C("after_row_vers_check_trx_active");
 | |
| 
 | |
| 		heap2 = heap;
 | |
| 		heap = mem_heap_create(1024);
 | |
| 
 | |
| 		if (trx_undo_prev_version_build(version, index, *offsets, heap,
 | |
| 						&prev_version, mtr, 0,
 | |
| 						in_heap, vrow) != DB_SUCCESS) {
 | |
| 			mem_heap_free(heap);
 | |
| 			heap = heap2;
 | |
| 			heap2 = NULL;
 | |
| 			goto committed_version_trx;
 | |
| 		}
 | |
| 
 | |
| 		if (heap2) {
 | |
| 			mem_heap_free(heap2); /* free version */
 | |
| 		}
 | |
| 
 | |
| 		if (prev_version == NULL) {
 | |
| 			/* It was a freshly inserted version */
 | |
| 			*old_vers = NULL;
 | |
| 			ut_ad(!vrow || !(*vrow));
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 		version = prev_version;
 | |
| 		*offsets = rec_get_offsets(version, index, *offsets,
 | |
| 					   index->n_core_fields,
 | |
| 					   ULINT_UNDEFINED, offset_heap);
 | |
| #if defined UNIV_DEBUG || defined UNIV_BLOB_LIGHT_DEBUG
 | |
| 		ut_a(!rec_offs_any_null_extern(version, *offsets));
 | |
| #endif /* UNIV_DEBUG || UNIV_BLOB_LIGHT_DEBUG */
 | |
| 	}/* for (;;) */
 | |
| 
 | |
| 	if (heap) {
 | |
| 		mem_heap_free(heap);
 | |
| 	}
 | |
| }
 | 
