summaryrefslogtreecommitdiff
path: root/storage/innobase/row/row0umod.cc
diff options
context:
space:
mode:
Diffstat (limited to 'storage/innobase/row/row0umod.cc')
-rw-r--r--storage/innobase/row/row0umod.cc911
1 files changed, 911 insertions, 0 deletions
diff --git a/storage/innobase/row/row0umod.cc b/storage/innobase/row/row0umod.cc
new file mode 100644
index 00000000000..c9b42265553
--- /dev/null
+++ b/storage/innobase/row/row0umod.cc
@@ -0,0 +1,911 @@
+/*****************************************************************************
+
+Copyright (c) 1997, 2012, 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/row0umod.cc
+Undo modify of a row
+
+Created 2/27/1997 Heikki Tuuri
+*******************************************************/
+
+#include "row0umod.h"
+
+#ifdef UNIV_NONINL
+#include "row0umod.ic"
+#endif
+
+#include "dict0dict.h"
+#include "dict0boot.h"
+#include "trx0undo.h"
+#include "trx0roll.h"
+#include "btr0btr.h"
+#include "mach0data.h"
+#include "row0undo.h"
+#include "row0vers.h"
+#include "trx0trx.h"
+#include "trx0rec.h"
+#include "row0row.h"
+#include "row0upd.h"
+#include "que0que.h"
+#include "log0log.h"
+
+/* Considerations on undoing a modify operation.
+(1) Undoing a delete marking: all index records should be found. Some of
+them may have delete mark already FALSE, if the delete mark operation was
+stopped underway, or if the undo operation ended prematurely because of a
+system crash.
+(2) Undoing an update of a delete unmarked record: the newer version of
+an updated secondary index entry should be removed if no prior version
+of the clustered index record requires its existence. Otherwise, it should
+be delete marked.
+(3) Undoing an update of a delete marked record. In this kind of update a
+delete marked clustered index record was delete unmarked and possibly also
+some of its fields were changed. Now, it is possible that the delete marked
+version has become obsolete at the time the undo is started. */
+
+/*************************************************************************
+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. */
+
+/***********************************************************//**
+Checks if also the previous version of the clustered index record was
+modified or inserted by the same transaction, and its undo number is such
+that it should be undone in the same rollback.
+@return TRUE if also previous modify or insert of this row should be undone */
+static
+ibool
+row_undo_mod_undo_also_prev_vers(
+/*=============================*/
+ undo_node_t* node, /*!< in: row undo node */
+ undo_no_t* undo_no)/*!< out: the undo number */
+{
+ trx_undo_rec_t* undo_rec;
+ trx_t* trx;
+
+ trx = node->trx;
+
+ if (node->new_trx_id != trx->id) {
+
+ *undo_no = 0;
+ return(FALSE);
+ }
+
+ undo_rec = trx_undo_get_undo_rec_low(node->new_roll_ptr, node->heap);
+
+ *undo_no = trx_undo_rec_get_undo_no(undo_rec);
+
+ return(trx->roll_limit <= *undo_no);
+}
+
+/***********************************************************//**
+Undoes a modify in a clustered index record.
+@return DB_SUCCESS, DB_FAIL, or error code: we may run out of file space */
+static
+ulint
+row_undo_mod_clust_low(
+/*===================*/
+ undo_node_t* node, /*!< in: row undo node */
+ que_thr_t* thr, /*!< in: query thread */
+ mtr_t* mtr, /*!< in: mtr; must be committed before
+ latching any further pages */
+ ulint mode) /*!< in: BTR_MODIFY_LEAF or BTR_MODIFY_TREE */
+{
+ btr_pcur_t* pcur;
+ btr_cur_t* btr_cur;
+ ulint err;
+#ifdef UNIV_DEBUG
+ ibool success;
+#endif /* UNIV_DEBUG */
+
+ pcur = &(node->pcur);
+ btr_cur = btr_pcur_get_btr_cur(pcur);
+
+#ifdef UNIV_DEBUG
+ success =
+#endif /* UNIV_DEBUG */
+ btr_pcur_restore_position(mode, pcur, mtr);
+
+ ut_ad(success);
+
+ if (mode == BTR_MODIFY_LEAF) {
+
+ err = btr_cur_optimistic_update(BTR_NO_LOCKING_FLAG
+ | BTR_NO_UNDO_LOG_FLAG
+ | BTR_KEEP_SYS_FLAG,
+ btr_cur, node->update,
+ node->cmpl_info, thr, mtr);
+ } else {
+ mem_heap_t* heap = NULL;
+ big_rec_t* dummy_big_rec;
+
+ ut_ad(mode == BTR_MODIFY_TREE);
+
+ err = btr_cur_pessimistic_update(
+ BTR_NO_LOCKING_FLAG
+ | BTR_NO_UNDO_LOG_FLAG
+ | BTR_KEEP_SYS_FLAG,
+ btr_cur, &heap, &dummy_big_rec, node->update,
+ node->cmpl_info, thr, mtr);
+
+ ut_a(!dummy_big_rec);
+ if (UNIV_LIKELY_NULL(heap)) {
+ mem_heap_free(heap);
+ }
+ }
+
+ return(err);
+}
+
+/***********************************************************//**
+Removes a clustered index record after undo if possible.
+This is attempted when the record was inserted by updating a
+delete-marked record and there no longer exist transactions
+that would see the delete-marked record. In other words, we
+roll back the insert by purging the record.
+@return DB_SUCCESS, DB_FAIL, or error code: we may run out of file space */
+static
+ulint
+row_undo_mod_remove_clust_low(
+/*==========================*/
+ undo_node_t* node, /*!< in: row undo node */
+ que_thr_t* thr, /*!< in: query thread */
+ mtr_t* mtr, /*!< in: mtr */
+ ulint mode) /*!< in: BTR_MODIFY_LEAF or BTR_MODIFY_TREE */
+{
+ btr_cur_t* btr_cur;
+ ulint err;
+
+ ut_ad(node->rec_type == TRX_UNDO_UPD_DEL_REC);
+
+ /* Find out if the record has been purged already
+ or if we can remove it. */
+
+ if (!btr_pcur_restore_position(mode, &node->pcur, mtr)
+ || row_vers_must_preserve_del_marked(node->new_trx_id, mtr)) {
+
+ return(DB_SUCCESS);
+ }
+
+ btr_cur = btr_pcur_get_btr_cur(&node->pcur);
+
+ if (mode == BTR_MODIFY_LEAF) {
+ err = btr_cur_optimistic_delete(btr_cur, mtr)
+ ? DB_SUCCESS
+ : DB_FAIL;
+ } else {
+ ut_ad(mode == BTR_MODIFY_TREE);
+
+ /* This operation is analogous to purge, we can free also
+ inherited externally stored fields */
+
+ btr_cur_pessimistic_delete(&err, FALSE, btr_cur,
+ thr_is_recv(thr)
+ ? RB_RECOVERY_PURGE_REC
+ : RB_NONE, mtr);
+
+ /* The delete operation may fail if we have little
+ file space left: TODO: easiest to crash the database
+ and restart with more file space */
+ }
+
+ return(err);
+}
+
+/***********************************************************//**
+Undoes a modify in a clustered index record. Sets also the node state for the
+next round of undo.
+@return DB_SUCCESS or error code: we may run out of file space */
+static
+ulint
+row_undo_mod_clust(
+/*===============*/
+ undo_node_t* node, /*!< in: row undo node */
+ que_thr_t* thr) /*!< in: query thread */
+{
+ btr_pcur_t* pcur;
+ mtr_t mtr;
+ ulint err;
+ ibool success;
+ ibool more_vers;
+ undo_no_t new_undo_no;
+
+ ut_ad(node && thr);
+
+ log_free_check();
+
+ /* Check if also the previous version of the clustered index record
+ should be undone in this same rollback operation */
+
+ more_vers = row_undo_mod_undo_also_prev_vers(node, &new_undo_no);
+
+ pcur = &(node->pcur);
+
+ mtr_start(&mtr);
+
+ /* Try optimistic processing of the record, keeping changes within
+ the index page */
+
+ err = row_undo_mod_clust_low(node, thr, &mtr, BTR_MODIFY_LEAF);
+
+ if (err != DB_SUCCESS) {
+ btr_pcur_commit_specify_mtr(pcur, &mtr);
+
+ /* We may have to modify tree structure: do a pessimistic
+ descent down the index tree */
+
+ mtr_start(&mtr);
+
+ err = row_undo_mod_clust_low(node, thr, &mtr, BTR_MODIFY_TREE);
+ }
+
+ btr_pcur_commit_specify_mtr(pcur, &mtr);
+
+ if (err == DB_SUCCESS && node->rec_type == TRX_UNDO_UPD_DEL_REC) {
+
+ mtr_start(&mtr);
+
+ err = row_undo_mod_remove_clust_low(node, thr, &mtr,
+ BTR_MODIFY_LEAF);
+ if (err != DB_SUCCESS) {
+ btr_pcur_commit_specify_mtr(pcur, &mtr);
+
+ /* We may have to modify tree structure: do a
+ pessimistic descent down the index tree */
+
+ mtr_start(&mtr);
+
+ err = row_undo_mod_remove_clust_low(node, thr, &mtr,
+ BTR_MODIFY_TREE);
+ }
+
+ btr_pcur_commit_specify_mtr(pcur, &mtr);
+ }
+
+ node->state = UNDO_NODE_FETCH_NEXT;
+
+ trx_undo_rec_release(node->trx, node->undo_no);
+
+ if (more_vers && err == DB_SUCCESS) {
+
+ /* Reserve the undo log record to the prior version after
+ committing &mtr: this is necessary to comply with the latching
+ order, as &mtr may contain the fsp latch which is lower in
+ the latch hierarchy than trx->undo_mutex. */
+
+ success = trx_undo_rec_reserve(node->trx, new_undo_no);
+
+ if (success) {
+ node->state = UNDO_NODE_PREV_VERS;
+ }
+ }
+
+ return(err);
+}
+
+/***********************************************************//**
+Delete marks or removes a secondary index entry if found.
+@return DB_SUCCESS, DB_FAIL, or DB_OUT_OF_FILE_SPACE */
+static
+ulint
+row_undo_mod_del_mark_or_remove_sec_low(
+/*====================================*/
+ undo_node_t* node, /*!< in: row undo node */
+ que_thr_t* thr, /*!< in: query thread */
+ dict_index_t* index, /*!< in: index */
+ dtuple_t* entry, /*!< in: index entry */
+ ulint mode) /*!< in: latch mode BTR_MODIFY_LEAF or
+ BTR_MODIFY_TREE */
+{
+ btr_pcur_t pcur;
+ btr_cur_t* btr_cur;
+ ibool success;
+ ibool old_has;
+ ulint err;
+ mtr_t mtr;
+ mtr_t mtr_vers;
+ enum row_search_result search_result;
+
+ log_free_check();
+ mtr_start(&mtr);
+
+ btr_cur = btr_pcur_get_btr_cur(&pcur);
+
+ ut_ad(mode == BTR_MODIFY_TREE || mode == BTR_MODIFY_LEAF);
+
+ search_result = row_search_index_entry(index, entry, mode,
+ &pcur, &mtr);
+
+ switch (UNIV_EXPECT(search_result, ROW_FOUND)) {
+ case ROW_NOT_FOUND:
+ /* In crash recovery, the secondary index record may
+ be missing if the UPDATE did not have time to insert
+ the secondary index records before the crash. When we
+ are undoing that UPDATE in crash recovery, the record
+ may be missing.
+
+ In normal processing, if an update ends in a deadlock
+ before it has inserted all updated secondary index
+ records, then the undo will not find those records. */
+
+ err = DB_SUCCESS;
+ goto func_exit;
+ case ROW_FOUND:
+ break;
+ case ROW_BUFFERED:
+ case ROW_NOT_DELETED_REF:
+ /* These are invalid outcomes, because the mode passed
+ to row_search_index_entry() did not include any of the
+ flags BTR_INSERT, BTR_DELETE, or BTR_DELETE_MARK. */
+ ut_error;
+ }
+
+ /* We should remove the index record if no prior version of the row,
+ which cannot be purged yet, requires its existence. If some requires,
+ we should delete mark the record. */
+
+ mtr_start(&mtr_vers);
+
+ success = btr_pcur_restore_position(BTR_SEARCH_LEAF, &(node->pcur),
+ &mtr_vers);
+ ut_a(success);
+
+ old_has = row_vers_old_has_index_entry(FALSE,
+ btr_pcur_get_rec(&(node->pcur)),
+ &mtr_vers, index, entry);
+ if (old_has) {
+ err = btr_cur_del_mark_set_sec_rec(BTR_NO_LOCKING_FLAG,
+ btr_cur, TRUE, thr, &mtr);
+ ut_ad(err == DB_SUCCESS);
+ } else {
+ /* Remove the index record */
+
+ if (mode == BTR_MODIFY_LEAF) {
+ success = btr_cur_optimistic_delete(btr_cur, &mtr);
+ if (success) {
+ err = DB_SUCCESS;
+ } else {
+ err = DB_FAIL;
+ }
+ } else {
+ ut_ad(mode == BTR_MODIFY_TREE);
+
+ /* No need to distinguish RB_RECOVERY_PURGE here,
+ because we are deleting a secondary index record:
+ the distinction between RB_NORMAL and
+ RB_RECOVERY_PURGE only matters when deleting a
+ record that contains externally stored
+ columns. */
+ ut_ad(!dict_index_is_clust(index));
+ btr_cur_pessimistic_delete(&err, FALSE, btr_cur,
+ RB_NORMAL, &mtr);
+
+ /* The delete operation may fail if we have little
+ file space left: TODO: easiest to crash the database
+ and restart with more file space */
+ }
+ }
+
+ btr_pcur_commit_specify_mtr(&(node->pcur), &mtr_vers);
+
+func_exit:
+ btr_pcur_close(&pcur);
+ mtr_commit(&mtr);
+
+ return(err);
+}
+
+/***********************************************************//**
+Delete marks or removes a secondary index entry if found.
+NOTE that if we updated the fields of a delete-marked secondary index record
+so that alphabetically they stayed the same, e.g., 'abc' -> 'aBc', we cannot
+return to the original values because we do not know them. But this should
+not cause problems because in row0sel.cc, in queries we always retrieve the
+clustered index record or an earlier version of it, if the secondary index
+record through which we do the search is delete-marked.
+@return DB_SUCCESS or DB_OUT_OF_FILE_SPACE */
+static
+ulint
+row_undo_mod_del_mark_or_remove_sec(
+/*================================*/
+ undo_node_t* node, /*!< in: row undo node */
+ que_thr_t* thr, /*!< in: query thread */
+ dict_index_t* index, /*!< in: index */
+ dtuple_t* entry) /*!< in: index entry */
+{
+ ulint err;
+
+ err = row_undo_mod_del_mark_or_remove_sec_low(node, thr, index,
+ entry, BTR_MODIFY_LEAF);
+ if (err == DB_SUCCESS) {
+
+ return(err);
+ }
+
+ err = row_undo_mod_del_mark_or_remove_sec_low(node, thr, index,
+ entry, BTR_MODIFY_TREE);
+ return(err);
+}
+
+/***********************************************************//**
+Delete unmarks a secondary index entry which must be found. It might not be
+delete-marked at the moment, but it does not harm to unmark it anyway. We also
+need to update the fields of the secondary index record if we updated its
+fields but alphabetically they stayed the same, e.g., 'abc' -> 'aBc'.
+@return DB_FAIL or DB_SUCCESS or DB_OUT_OF_FILE_SPACE */
+static
+ulint
+row_undo_mod_del_unmark_sec_and_undo_update(
+/*========================================*/
+ ulint mode, /*!< in: search mode: BTR_MODIFY_LEAF or
+ BTR_MODIFY_TREE */
+ que_thr_t* thr, /*!< in: query thread */
+ dict_index_t* index, /*!< in: index */
+ const dtuple_t* entry) /*!< in: index entry */
+{
+ mem_heap_t* heap;
+ btr_pcur_t pcur;
+ btr_cur_t* btr_cur;
+ upd_t* update;
+ ulint err = DB_SUCCESS;
+ big_rec_t* dummy_big_rec;
+ mtr_t mtr;
+ trx_t* trx = thr_get_trx(thr);
+ enum row_search_result search_result;
+
+ /* Ignore indexes that are being created. */
+ if (UNIV_UNLIKELY(*index->name == TEMP_INDEX_PREFIX)) {
+
+ return(DB_SUCCESS);
+ }
+
+ log_free_check();
+ mtr_start(&mtr);
+
+ ut_ad(mode == BTR_MODIFY_TREE || mode == BTR_MODIFY_LEAF);
+
+ search_result = row_search_index_entry(index, entry, mode,
+ &pcur, &mtr);
+
+ switch (search_result) {
+ case ROW_BUFFERED:
+ case ROW_NOT_DELETED_REF:
+ /* These are invalid outcomes, because the mode passed
+ to row_search_index_entry() did not include any of the
+ flags BTR_INSERT, BTR_DELETE, or BTR_DELETE_MARK. */
+ ut_error;
+ case ROW_NOT_FOUND:
+ fputs("InnoDB: error in sec index entry del undo in\n"
+ "InnoDB: ", stderr);
+ dict_index_name_print(stderr, trx, index);
+ fputs("\n"
+ "InnoDB: tuple ", stderr);
+ dtuple_print(stderr, entry);
+ fputs("\n"
+ "InnoDB: record ", stderr);
+ rec_print(stderr, btr_pcur_get_rec(&pcur), index);
+ putc('\n', stderr);
+ trx_print(stderr, trx, 0);
+ fputs("\n"
+ "InnoDB: Submit a detailed bug report"
+ " to http://bugs.mysql.com\n", stderr);
+ ut_ad(0);
+ break;
+ case ROW_FOUND:
+ btr_cur = btr_pcur_get_btr_cur(&pcur);
+ err = btr_cur_del_mark_set_sec_rec(BTR_NO_LOCKING_FLAG,
+ btr_cur, FALSE, thr, &mtr);
+ ut_a(err == DB_SUCCESS);
+ heap = mem_heap_create(100);
+
+ update = row_upd_build_sec_rec_difference_binary(
+ index, entry, btr_cur_get_rec(btr_cur), trx, heap);
+ if (upd_get_n_fields(update) == 0) {
+
+ /* Do nothing */
+
+ } else if (mode == BTR_MODIFY_LEAF) {
+ /* Try an optimistic updating of the record, keeping
+ changes within the page */
+
+ err = btr_cur_optimistic_update(
+ BTR_KEEP_SYS_FLAG | BTR_NO_LOCKING_FLAG,
+ btr_cur, update, 0, thr, &mtr);
+ switch (err) {
+ case DB_OVERFLOW:
+ case DB_UNDERFLOW:
+ case DB_ZIP_OVERFLOW:
+ err = DB_FAIL;
+ }
+ } else {
+ ut_a(mode == BTR_MODIFY_TREE);
+ err = btr_cur_pessimistic_update(
+ BTR_KEEP_SYS_FLAG | BTR_NO_LOCKING_FLAG,
+ btr_cur, &heap, &dummy_big_rec,
+ update, 0, thr, &mtr);
+ ut_a(!dummy_big_rec);
+ }
+
+ mem_heap_free(heap);
+ }
+
+ btr_pcur_close(&pcur);
+ mtr_commit(&mtr);
+
+ return(err);
+}
+
+/***********************************************************//**
+Undoes a modify in secondary indexes when undo record type is UPD_DEL.
+@return DB_SUCCESS or DB_OUT_OF_FILE_SPACE */
+static
+ulint
+row_undo_mod_upd_del_sec(
+/*=====================*/
+ undo_node_t* node, /*!< in: row undo node */
+ que_thr_t* thr) /*!< in: query thread */
+{
+ mem_heap_t* heap;
+ ulint err = DB_SUCCESS;
+
+ ut_ad(node->rec_type == TRX_UNDO_UPD_DEL_REC);
+
+ heap = mem_heap_create(1024);
+
+ while (node->index != NULL) {
+ dict_index_t* index = node->index;
+ dtuple_t* entry;
+
+ if (index->type & DICT_FTS) {
+ dict_table_next_uncorrupted_index(node->index);
+ continue;
+ }
+
+ entry = row_build_index_entry(
+ node->row, node->ext, index, heap);
+
+ if (UNIV_UNLIKELY(!entry)) {
+ /* The database must have crashed after
+ inserting a clustered index record but before
+ writing all the externally stored columns of
+ that record. Because secondary index entries
+ are inserted after the clustered index record,
+ we may assume that the secondary index record
+ does not exist. However, this situation may
+ only occur during the rollback of incomplete
+ transactions. */
+ ut_a(thr_is_recv(thr));
+ } else {
+ err = row_undo_mod_del_mark_or_remove_sec(
+ node, thr, index, entry);
+
+ if (UNIV_UNLIKELY(err != DB_SUCCESS)) {
+
+ break;
+ }
+ }
+
+ mem_heap_empty(heap);
+ dict_table_next_uncorrupted_index(node->index);
+ }
+
+ mem_heap_free(heap);
+
+ return(err);
+}
+
+/***********************************************************//**
+Undoes a modify in secondary indexes when undo record type is DEL_MARK.
+@return DB_SUCCESS or DB_OUT_OF_FILE_SPACE */
+static
+ulint
+row_undo_mod_del_mark_sec(
+/*======================*/
+ undo_node_t* node, /*!< in: row undo node */
+ que_thr_t* thr) /*!< in: query thread */
+{
+ mem_heap_t* heap;
+ ulint err = DB_SUCCESS;
+
+ heap = mem_heap_create(1024);
+
+ while (node->index != NULL) {
+ dict_index_t* index = node->index;
+ dtuple_t* entry;
+
+ if (index->type == DICT_FTS) {
+ dict_table_next_uncorrupted_index(node->index);
+ continue;
+ }
+
+ entry = row_build_index_entry(
+ node->row, node->ext, index, heap);
+
+ ut_a(entry);
+
+ err = row_undo_mod_del_unmark_sec_and_undo_update(
+ BTR_MODIFY_LEAF, thr, index, entry);
+ if (err == DB_FAIL) {
+ err = row_undo_mod_del_unmark_sec_and_undo_update(
+ BTR_MODIFY_TREE, thr, index, entry);
+ }
+
+ if (UNIV_UNLIKELY(err != DB_SUCCESS)) {
+
+ break;
+ }
+
+ mem_heap_empty(heap);
+ dict_table_next_uncorrupted_index(node->index);
+ }
+
+ mem_heap_free(heap);
+
+ return(err);
+}
+
+/***********************************************************//**
+Undoes a modify in secondary indexes when undo record type is UPD_EXIST.
+@return DB_SUCCESS or DB_OUT_OF_FILE_SPACE */
+static
+ulint
+row_undo_mod_upd_exist_sec(
+/*=======================*/
+ undo_node_t* node, /*!< in: row undo node */
+ que_thr_t* thr) /*!< in: query thread */
+{
+ mem_heap_t* heap;
+ ulint err = DB_SUCCESS;
+
+ if (node->index == NULL
+ || (node->cmpl_info & UPD_NODE_NO_ORD_CHANGE)) {
+ /* No change in secondary indexes */
+
+ return(err);
+ }
+
+ heap = mem_heap_create(1024);
+
+ while (node->index != NULL) {
+ dict_index_t* index = node->index;
+ dtuple_t* entry;
+
+ if (index->type == DICT_FTS
+ || !row_upd_changes_ord_field_binary(
+ index, node->update, thr, node->row, node->ext)) {
+ dict_table_next_uncorrupted_index(node->index);
+ continue;
+ }
+
+ /* Build the newest version of the index entry */
+ entry = row_build_index_entry(node->row, node->ext,
+ index, heap);
+ if (UNIV_UNLIKELY(!entry)) {
+ /* The server must have crashed in
+ row_upd_clust_rec_by_insert() before
+ the updated externally stored columns (BLOBs)
+ of the new clustered index entry were written. */
+
+ /* The table must be in DYNAMIC or COMPRESSED
+ format. REDUNDANT and COMPACT formats
+ store a local 768-byte prefix of each
+ externally stored column. */
+ ut_a(dict_table_get_format(index->table)
+ >= UNIV_FORMAT_B);
+
+ /* This is only legitimate when
+ rolling back an incomplete transaction
+ after crash recovery. */
+ ut_a(thr_get_trx(thr)->is_recovered);
+
+ /* The server must have crashed before
+ completing the insert of the new
+ clustered index entry and before
+ inserting to the secondary indexes.
+ Because node->row was not yet written
+ to this index, we can ignore it. But
+ we must restore node->undo_row. */
+ } else {
+ /* NOTE that if we updated the fields of a
+ delete-marked secondary index record so that
+ alphabetically they stayed the same, e.g.,
+ 'abc' -> 'aBc', we cannot return to the
+ original values because we do not know them.
+ But this should not cause problems because
+ in row0sel.cc, in queries we always retrieve
+ the clustered index record or an earlier
+ version of it, if the secondary index record
+ through which we do the search is
+ delete-marked. */
+
+ err = row_undo_mod_del_mark_or_remove_sec(
+ node, thr, index, entry);
+ if (err != DB_SUCCESS) {
+ break;
+ }
+ }
+
+ mem_heap_empty(heap);
+ /* We may have to update the delete mark in the
+ secondary index record of the previous version of
+ the row. We also need to update the fields of
+ the secondary index record if we updated its fields
+ but alphabetically they stayed the same, e.g.,
+ 'abc' -> 'aBc'. */
+ entry = row_build_index_entry(node->undo_row,
+ node->undo_ext,
+ index, heap);
+ ut_a(entry);
+
+ err = row_undo_mod_del_unmark_sec_and_undo_update(
+ BTR_MODIFY_LEAF, thr, index, entry);
+ if (err == DB_FAIL) {
+ err = row_undo_mod_del_unmark_sec_and_undo_update(
+ BTR_MODIFY_TREE, thr, index, entry);
+ }
+
+ if (UNIV_UNLIKELY(err != DB_SUCCESS)) {
+ break;
+ }
+
+ mem_heap_empty(heap);
+ dict_table_next_uncorrupted_index(node->index);
+ }
+
+ mem_heap_free(heap);
+
+ return(err);
+}
+
+/***********************************************************//**
+Parses the row reference and other info in a modify undo log record. */
+static
+void
+row_undo_mod_parse_undo_rec(
+/*========================*/
+ undo_node_t* node, /*!< in: row undo node */
+ que_thr_t* thr, /*!< in: query thread */
+ ibool dict_locked) /*!< in: TRUE if own dict_sys->mutex */
+{
+ dict_index_t* clust_index;
+ byte* ptr;
+ undo_no_t undo_no;
+ table_id_t table_id;
+ trx_id_t trx_id;
+ roll_ptr_t roll_ptr;
+ ulint info_bits;
+ ulint type;
+ ulint cmpl_info;
+ ibool dummy_extern;
+ trx_t* trx;
+
+ ut_ad(node && thr);
+ trx = thr_get_trx(thr);
+ ptr = trx_undo_rec_get_pars(node->undo_rec, &type, &cmpl_info,
+ &dummy_extern, &undo_no, &table_id);
+ node->rec_type = type;
+
+ node->table = dict_table_open_on_id(table_id, dict_locked);
+
+ /* TODO: other fixes associated with DROP TABLE + rollback in the
+ same table by another user */
+
+ if (node->table == NULL) {
+ /* Table was dropped */
+ return;
+ }
+
+ if (node->table->ibd_file_missing) {
+ dict_table_close(node->table, dict_locked);
+
+ /* We skip undo operations to missing .ibd files */
+ node->table = NULL;
+
+ return;
+ }
+
+ clust_index = dict_table_get_first_index(node->table);
+
+ ptr = trx_undo_update_rec_get_sys_cols(ptr, &trx_id, &roll_ptr,
+ &info_bits);
+
+ ptr = trx_undo_rec_get_row_ref(ptr, clust_index, &(node->ref),
+ node->heap);
+
+ trx_undo_update_rec_get_update(ptr, clust_index, type, trx_id,
+ roll_ptr, info_bits, trx,
+ node->heap, &(node->update));
+ node->new_roll_ptr = roll_ptr;
+ node->new_trx_id = trx_id;
+ node->cmpl_info = cmpl_info;
+
+ if (!row_undo_search_clust_to_pcur(node)) {
+
+ dict_table_close(node->table, dict_locked);
+
+ node->table = NULL;
+ }
+}
+
+/***********************************************************//**
+Undoes a modify operation on a row of a table.
+@return DB_SUCCESS or error code */
+UNIV_INTERN
+ulint
+row_undo_mod(
+/*=========*/
+ undo_node_t* node, /*!< in: row undo node */
+ que_thr_t* thr) /*!< in: query thread */
+{
+ ulint err;
+ ibool dict_locked;
+
+ ut_ad(node && thr);
+ ut_ad(node->state == UNDO_NODE_MODIFY);
+
+ dict_locked = thr_get_trx(thr)->dict_operation_lock_mode == RW_X_LATCH;
+
+ row_undo_mod_parse_undo_rec(node, thr, dict_locked);
+
+ if (node->table == NULL) {
+ /* It is already undone, or will be undone by another query
+ thread, or table was dropped */
+
+ trx_undo_rec_release(node->trx, node->undo_no);
+ node->state = UNDO_NODE_FETCH_NEXT;
+
+ return(DB_SUCCESS);
+ }
+
+ node->index = dict_table_get_next_index(
+ dict_table_get_first_index(node->table));
+
+ /* Skip all corrupted secondary index */
+ dict_table_skip_corrupt_index(node->index);
+
+ switch (node->rec_type) {
+ case TRX_UNDO_UPD_EXIST_REC:
+ err = row_undo_mod_upd_exist_sec(node, thr);
+ break;
+ case TRX_UNDO_DEL_MARK_REC:
+ err = row_undo_mod_del_mark_sec(node, thr);
+ break;
+ case TRX_UNDO_UPD_DEL_REC:
+ err = row_undo_mod_upd_del_sec(node, thr);
+ break;
+ default:
+ ut_error;
+ err = DB_ERROR;
+ }
+
+ if (err == DB_SUCCESS) {
+
+ err = row_undo_mod_clust(node, thr);
+ }
+
+ dict_table_close(node->table, dict_locked);
+
+ node->table = NULL;
+
+ return(err);
+}