summaryrefslogtreecommitdiff
path: root/storage/innobase/row/row0vers.cc
diff options
context:
space:
mode:
Diffstat (limited to 'storage/innobase/row/row0vers.cc')
-rw-r--r--storage/innobase/row/row0vers.cc764
1 files changed, 764 insertions, 0 deletions
diff --git a/storage/innobase/row/row0vers.cc b/storage/innobase/row/row0vers.cc
new file mode 100644
index 00000000000..0aad8675ff8
--- /dev/null
+++ b/storage/innobase/row/row0vers.cc
@@ -0,0 +1,764 @@
+/*****************************************************************************
+
+Copyright (c) 1997, 2011, Oracle and/or its affiliates. All Rights Reserved.
+
+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, Suite 500, Boston, MA 02110-1335 USA
+
+*****************************************************************************/
+
+/**************************************************//**
+@file row/row0vers.cc
+Row versions
+
+Created 2/6/1997 Heikki Tuuri
+*******************************************************/
+
+#include "row0vers.h"
+
+#ifdef UNIV_NONINL
+#include "row0vers.ic"
+#endif
+
+#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 "read0read.h"
+#include "lock0lock.h"
+
+/*****************************************************************//**
+Finds out if an active transaction has inserted or modified a secondary
+index record.
+@return 0 if committed, else the active transaction id;
+NOTE that this function can return false positives but never false
+negatives. The caller must confirm all positive results by calling
+trx_is_active() while holding lock_sys->mutex. */
+UNIV_INLINE
+trx_id_t
+row_vers_impl_x_locked_low(
+/*=======================*/
+ const rec_t* clust_rec, /*!< in: clustered index record */
+ dict_index_t* clust_index, /*!< in: the clustered index */
+ const rec_t* rec, /*!< in: secondary index record */
+ dict_index_t* index, /*!< in: the secondary index */
+ const ulint* offsets, /*!< in: rec_get_offsets(rec, index) */
+ mtr_t* mtr) /*!< in/out: mini-transaction */
+{
+ trx_id_t trx_id;
+ ibool corrupt;
+ ulint comp;
+ ulint rec_del;
+ const rec_t* version;
+ rec_t* prev_version = NULL;
+ ulint* clust_offsets;
+ mem_heap_t* heap;
+
+ ut_ad(rec_offs_validate(rec, index, offsets));
+
+ heap = mem_heap_create(1024);
+
+ clust_offsets = rec_get_offsets(
+ clust_rec, clust_index, NULL, ULINT_UNDEFINED, &heap);
+
+ trx_id = row_get_rec_trx_id(clust_rec, clust_index, clust_offsets);
+ corrupt = FALSE;
+
+ if (!trx_rw_is_active(trx_id, &corrupt)) {
+ /* The transaction that modified or inserted clust_rec is no
+ longer active, or it is corrupt: no implicit lock on rec */
+ if (corrupt) {
+ lock_report_trx_id_insanity(
+ trx_id, clust_rec, clust_index, clust_offsets,
+ trx_sys_get_max_trx_id());
+ }
+ mem_heap_free(heap);
+ return(0);
+ }
+
+ comp = page_rec_is_comp(rec);
+ ut_ad(index->table == clust_index->table);
+ ut_ad(!!comp == dict_table_is_comp(index->table));
+ ut_ad(!comp == !page_rec_is_comp(clust_rec));
+
+ rec_del = rec_get_deleted_flag(rec, comp);
+
+ /* 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 (version = clust_rec;; version = prev_version) {
+ ulint err;
+ row_ext_t* ext;
+ const dtuple_t* row;
+ dtuple_t* entry;
+ ulint vers_del;
+ trx_id_t prev_trx_id;
+ mem_heap_t* old_heap = heap;
+
+ /* 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);
+
+ err = trx_undo_prev_version_build(
+ clust_rec, mtr, version, clust_index, clust_offsets,
+ heap, &prev_version);
+
+ /* Free version and clust_offsets. */
+
+ mem_heap_free(old_heap);
+
+ if (prev_version == NULL) {
+
+ /* clust_rec must be a fresh insert, because
+ no previous version was found or the transaction
+ has committed. The caller has to recheck as the
+ synopsis of this function states, whether trx_id
+ is active or not. */
+
+ ut_a(err == DB_SUCCESS || err == DB_MISSING_HISTORY);
+
+ break;
+ }
+
+ clust_offsets = rec_get_offsets(
+ prev_version, clust_index, NULL, 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, &ext, heap);
+
+ entry = row_build_index_entry(row, ext, index, heap);
+
+ /* entry may be NULL if a record was inserted in place
+ of a deleted record, and the BLOB pointers of the new
+ record were not initialized yet. But in that case,
+ prev_version should be NULL. */
+
+ ut_a(entry != NULL);
+
+ /* 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. */
+
+ ut_ad(err == DB_SUCCESS);
+
+ /* We check if entry and rec are identified in the alphabetical
+ ordering */
+
+ if (!trx_rw_is_active(trx_id, &corrupt)) {
+ /* Transaction no longer active: no implicit
+ x-lock. This situation should only be possible
+ because we are not holding lock_sys->mutex. */
+ ut_ad(!lock_mutex_own());
+ if (corrupt) {
+ lock_report_trx_id_insanity(
+ trx_id,
+ prev_version, clust_index,
+ clust_offsets,
+ trx_sys_get_max_trx_id());
+ }
+ trx_id = 0;
+ break;
+ } else if (0 == cmp_dtuple_rec(entry, rec, 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 (0 != cmp_dtuple_rec(entry, rec, 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;
+ }
+
+ if (trx_id != prev_trx_id) {
+ /* prev_version was the first version modified by
+ the trx_id transaction: no implicit x-lock */
+
+ trx_id = 0;
+ break;
+ }
+ }
+
+ mem_heap_free(heap);
+ return(trx_id);
+}
+
+/*****************************************************************//**
+Finds out if an active transaction has inserted or modified a secondary
+index record.
+@return 0 if committed, else the active transaction id;
+NOTE that this function can return false positives but never false
+negatives. The caller must confirm all positive results by calling
+trx_is_active() while holding lock_sys->mutex. */
+UNIV_INTERN
+trx_id_t
+row_vers_impl_x_locked(
+/*===================*/
+ const rec_t* rec, /*!< in: record in a secondary index */
+ dict_index_t* index, /*!< in: the secondary index */
+ const ulint* offsets)/*!< in: rec_get_offsets(rec, index) */
+{
+ dict_index_t* clust_index;
+ const rec_t* clust_rec;
+ trx_id_t trx_id;
+ mtr_t mtr;
+
+ ut_ad(!lock_mutex_own());
+ ut_ad(!mutex_own(&trx_sys->mutex));
+
+ 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 (UNIV_UNLIKELY(!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_id = 0;
+ } else {
+ trx_id = row_vers_impl_x_locked_low(
+ clust_rec, clust_index, rec, index, offsets, &mtr);
+ }
+
+ mtr_commit(&mtr);
+
+ return(trx_id);
+}
+
+/*****************************************************************//**
+Finds out if we must preserve a delete marked earlier version of a clustered
+index record, because it is >= the purge view.
+@return TRUE if earlier version should be preserved */
+UNIV_INTERN
+ibool
+row_vers_must_preserve_del_marked(
+/*==============================*/
+ trx_id_t trx_id, /*!< in: transaction id in the version */
+ mtr_t* mtr) /*!< in: mtr holding the latch on the
+ clustered index record; it will also
+ hold the latch on purge_view */
+{
+#ifdef UNIV_SYNC_DEBUG
+ ut_ad(!rw_lock_own(&(purge_sys->latch), RW_LOCK_SHARED));
+#endif /* UNIV_SYNC_DEBUG */
+
+ mtr_s_lock(&(purge_sys->latch), mtr);
+
+ return(!read_view_sees_trx_id(purge_sys->view, trx_id));
+}
+
+/*****************************************************************//**
+Finds out if a version of the record, where the version >= the current
+purge 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 and ientry are identified in
+the alphabetical ordering; exactly in this case we return TRUE.
+@return TRUE if earlier version should have */
+UNIV_INTERN
+ibool
+row_vers_old_has_index_entry(
+/*=========================*/
+ ibool also_curr,/*!< in: TRUE if also rec is included in the
+ versions to search; otherwise only versions
+ prior to it are searched */
+ const rec_t* rec, /*!< in: record in the clustered index; the
+ caller must have a latch on the page */
+ mtr_t* mtr, /*!< in: mtr holding the latch on rec; it will
+ also hold the latch on purge_view */
+ dict_index_t* index, /*!< in: the secondary index */
+ const dtuple_t* ientry) /*!< in: the secondary index entry */
+{
+ const rec_t* version;
+ rec_t* prev_version;
+ dict_index_t* clust_index;
+ ulint* clust_offsets;
+ mem_heap_t* heap;
+ mem_heap_t* heap2;
+ const dtuple_t* row;
+ const dtuple_t* entry;
+ ulint err;
+ ulint comp;
+
+ ut_ad(mtr_memo_contains_page(mtr, rec, MTR_MEMO_PAGE_X_FIX)
+ || mtr_memo_contains_page(mtr, rec, MTR_MEMO_PAGE_S_FIX));
+#ifdef UNIV_SYNC_DEBUG
+ ut_ad(!rw_lock_own(&(purge_sys->latch), RW_LOCK_SHARED));
+#endif /* UNIV_SYNC_DEBUG */
+
+ clust_index = dict_table_get_first_index(index->table);
+
+ comp = page_rec_is_comp(rec);
+ ut_ad(!dict_table_is_comp(index->table) == !comp);
+ heap = mem_heap_create(1024);
+ clust_offsets = rec_get_offsets(rec, clust_index, NULL,
+ ULINT_UNDEFINED, &heap);
+
+ if (also_curr && !rec_get_deleted_flag(rec, comp)) {
+ 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, &ext, heap);
+ 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_cmp(ientry, entry)) {
+
+ mem_heap_free(heap);
+
+ return(TRUE);
+ }
+ }
+
+ version = rec;
+
+ for (;;) {
+ heap2 = heap;
+ heap = mem_heap_create(1024);
+ err = trx_undo_prev_version_build(rec, mtr, version,
+ clust_index, clust_offsets,
+ heap, &prev_version);
+ mem_heap_free(heap2); /* free version and clust_offsets */
+
+ if (err != DB_SUCCESS || !prev_version) {
+ /* Versions end here */
+
+ mem_heap_free(heap);
+
+ return(FALSE);
+ }
+
+ clust_offsets = rec_get_offsets(prev_version, clust_index,
+ NULL, ULINT_UNDEFINED, &heap);
+
+ if (!rec_get_deleted_flag(prev_version, comp)) {
+ 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, &ext, heap);
+ 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_cmp(ientry, entry)) {
+
+ mem_heap_free(heap);
+
+ return(TRUE);
+ }
+ }
+
+ version = prev_version;
+ }
+}
+
+/*****************************************************************//**
+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 DB_SUCCESS or DB_MISSING_HISTORY */
+UNIV_INTERN
+ulint
+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 */
+ ulint** offsets,/*!< in/out: offsets returned by
+ rec_get_offsets(rec, index) */
+ read_view_t* 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
+ record does not exist in the view, that is,
+ it was freshly inserted afterwards */
+{
+ const rec_t* version;
+ rec_t* prev_version;
+ trx_id_t trx_id;
+ mem_heap_t* heap = NULL;
+ byte* buf;
+ ulint err;
+
+ ut_ad(dict_index_is_clust(index));
+ ut_ad(mtr_memo_contains_page(mtr, rec, MTR_MEMO_PAGE_X_FIX)
+ || mtr_memo_contains_page(mtr, rec, MTR_MEMO_PAGE_S_FIX));
+#ifdef UNIV_SYNC_DEBUG
+ ut_ad(!rw_lock_own(&(purge_sys->latch), RW_LOCK_SHARED));
+#endif /* UNIV_SYNC_DEBUG */
+
+ ut_ad(rec_offs_validate(rec, index, *offsets));
+
+ trx_id = row_get_rec_trx_id(rec, index, *offsets);
+
+ ut_ad(!read_view_sees_trx_id(view, trx_id));
+
+ version = rec;
+
+ for (;;) {
+ mem_heap_t* heap2 = heap;
+ trx_undo_rec_t* undo_rec;
+ roll_ptr_t roll_ptr;
+ undo_no_t undo_no;
+ heap = mem_heap_create(1024);
+
+ /* If we have high-granularity consistent read view and
+ creating transaction of the view is the same as trx_id in
+ the record we see this record only in the case when
+ undo_no of the record is < undo_no in the view. */
+
+ if (view->type == VIEW_HIGH_GRANULARITY
+ && view->creator_trx_id == trx_id) {
+
+ roll_ptr = row_get_rec_roll_ptr(version, index,
+ *offsets);
+ undo_rec = trx_undo_get_undo_rec_low(roll_ptr, heap);
+ undo_no = trx_undo_rec_get_undo_no(undo_rec);
+ mem_heap_empty(heap);
+
+ if (view->undo_no > undo_no) {
+ /* The view already sees this version: we can
+ copy it to in_heap and return */
+
+#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 */
+
+ 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,
+ *offsets);
+ err = DB_SUCCESS;
+
+ break;
+ }
+ }
+
+ err = trx_undo_prev_version_build(rec, mtr, version, index,
+ *offsets, heap,
+ &prev_version);
+ if (heap2) {
+ mem_heap_free(heap2); /* free version */
+ }
+
+ if (err != DB_SUCCESS) {
+ break;
+ }
+
+ if (prev_version == NULL) {
+ /* It was a freshly inserted version */
+ *old_vers = NULL;
+ err = DB_SUCCESS;
+
+ break;
+ }
+
+ *offsets = rec_get_offsets(prev_version, index, *offsets,
+ 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 (read_view_sees_trx_id(view, 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, *offsets);
+ err = DB_SUCCESS;
+
+ break;
+ }
+
+ version = prev_version;
+ }/* for (;;) */
+
+ mem_heap_free(heap);
+
+ return(err);
+}
+
+/*****************************************************************//**
+Constructs the last committed version of a clustered index record,
+which should be seen by a semi-consistent read.
+@return DB_SUCCESS or DB_MISSING_HISTORY */
+UNIV_INTERN
+ulint
+row_vers_build_for_semi_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 */
+ ulint** 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 */
+{
+ const rec_t* version;
+ mem_heap_t* heap = NULL;
+ byte* buf;
+ ulint err;
+ trx_id_t rec_trx_id = 0;
+
+ ut_ad(dict_index_is_clust(index));
+ ut_ad(mtr_memo_contains_page(mtr, rec, MTR_MEMO_PAGE_X_FIX)
+ || mtr_memo_contains_page(mtr, rec, MTR_MEMO_PAGE_S_FIX));
+#ifdef UNIV_SYNC_DEBUG
+ ut_ad(!rw_lock_own(&(purge_sys->latch), RW_LOCK_SHARED));
+#endif /* UNIV_SYNC_DEBUG */
+
+ ut_ad(rec_offs_validate(rec, index, *offsets));
+
+ version = rec;
+
+ for (;;) {
+ const trx_t* version_trx;
+ 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;
+ }
+
+ mutex_enter(&trx_sys->mutex);
+ version_trx = trx_get_rw_trx_by_id(version_trx_id);
+ /* Because version_trx is a read-write transaction,
+ its state cannot change from or to NOT_STARTED while
+ we are holding the trx_sys->mutex. It may change from
+ ACTIVE to PREPARED or COMMITTED. */
+ if (version_trx
+ && trx_state_eq(version_trx,
+ TRX_STATE_COMMITTED_IN_MEMORY)) {
+ version_trx = NULL;
+ }
+ mutex_exit(&trx_sys->mutex);
+
+ if (!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;
+ err = DB_SUCCESS;
+ 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,
+ 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, *offsets);
+ err = DB_SUCCESS;
+
+ break;
+ }
+
+ heap2 = heap;
+ heap = mem_heap_create(1024);
+
+ err = trx_undo_prev_version_build(rec, mtr, version, index,
+ *offsets, heap,
+ &prev_version);
+ if (heap2) {
+ mem_heap_free(heap2); /* free version */
+ }
+
+ if (UNIV_UNLIKELY(err != DB_SUCCESS)) {
+ break;
+ }
+
+ if (prev_version == NULL) {
+ /* It was a freshly inserted version */
+ *old_vers = NULL;
+ err = DB_SUCCESS;
+
+ break;
+ }
+
+ version = prev_version;
+ *offsets = rec_get_offsets(version, index, *offsets,
+ 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);
+ }
+
+ return(err);
+}