Commit 5b06d371 authored by Marko Mäkelä's avatar Marko Mäkelä

Test and fix instant ALTER TABLE...ADD COLUMN crash recovery

trx_t::in_rollback: Make the field available in non-debug builds.

row_undo_ins_remove_clust_rec(): On the rollback of an insert into
SYS_COLUMNS, revert instant ADD COLUMN in the cache by removing the
last column from the table and the clustered index.

page_set_instant(): Remove a too strict assertion. A rollback would
not reset the root page from FIL_PAGE_TYPE_INSTANT to FIL_PAGE_INDEX,
so the PAGE_INSTANT field could already be set.

rec_init_offsets_comp_ordinary(), rec_copy_prefix_to_buf(),
row_undo_search_clust_to_pcur(): Adjust too strict assertions.

row_search_on_row_ref(), row_undo_mod_parse_undo_rec(), row_undo_mod(),
trx_undo_update_rec_get_update(): Handle the 'default row'
as a special case.

btr_cur_trim(): In an update, trim the tuple as needed. For the
'default row', handle rollback specially. For user records, omit
fields that match the 'default row'.

btr_cur_optimistic_delete_func(), btr_cur_pessimistic_delete():
Skip locking and adaptive hash index for the 'default row'.
parent fa388693
#
# MDEV-11369: Instant ADD COLUMN for InnoDB
#
CREATE TABLE t1(id INT PRIMARY KEY, c2 INT UNIQUE) ENGINE=InnoDB;
CREATE TABLE t2 LIKE t1;
INSERT INTO t1 VALUES(1,2);
BEGIN;
INSERT INTO t2 VALUES(2,1);
ALTER TABLE t2 ADD COLUMN (c3 TEXT NOT NULL DEFAULT 'De finibus bonorum');
connect ddl, localhost, root;
SET DEBUG_SYNC='innodb_alter_inplace_before_commit SIGNAL ddl WAIT_FOR ever';
ALTER TABLE t1 ADD COLUMN (c3 TEXT NOT NULL DEFAULT ' et malorum');
connection default;
SET DEBUG_SYNC='now WAIT_FOR ddl';
SET GLOBAL innodb_flush_log_at_trx_commit=1;
COMMIT;
# Kill the server
disconnect ddl;
SET GLOBAL innodb_purge_rseg_truncate_frequency=1;
SELECT * FROM t1;
id c2
1 2
SELECT * FROM t2;
id c2 c3
2 1 De finibus bonorum
BEGIN;
DELETE FROM t1;
ROLLBACK;
InnoDB 0 transactions not purged
connect ddl, localhost, root;
SET DEBUG_SYNC='innodb_alter_inplace_before_commit SIGNAL ddl WAIT_FOR ever';
ALTER TABLE t2 ADD COLUMN (c4 TEXT NOT NULL DEFAULT ' et malorum');
connection default;
SET DEBUG_SYNC='now WAIT_FOR ddl';
SET GLOBAL innodb_flush_log_at_trx_commit=1;
DELETE FROM t1;
# Kill the server
disconnect ddl;
SET @saved_frequency= @@GLOBAL.innodb_purge_rseg_truncate_frequency;
SET GLOBAL innodb_purge_rseg_truncate_frequency=1;
SELECT * FROM t1;
id c2
SELECT * FROM t2;
id c2 c3
2 1 De finibus bonorum
BEGIN;
INSERT INTO t1 SET id=1;
DELETE FROM t2;
ROLLBACK;
InnoDB 0 transactions not purged
DELETE FROM t2;
InnoDB 0 transactions not purged
SHOW CREATE TABLE t1;
Table Create Table
t1 CREATE TABLE `t1` (
`id` int(11) NOT NULL,
`c2` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `c2` (`c2`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
SHOW CREATE TABLE t2;
Table Create Table
t2 CREATE TABLE `t2` (
`id` int(11) NOT NULL,
`c2` int(11) DEFAULT NULL,
`c3` text NOT NULL DEFAULT 'De finibus bonorum',
PRIMARY KEY (`id`),
UNIQUE KEY `c2` (`c2`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
DROP TABLE t1,t2;
SET GLOBAL innodb_purge_rseg_truncate_frequency=@saved_frequency;
CREATE TABLE t1(a INT PRIMARY KEY, b INT, KEY(b)) ENGINE=InnoDB
PARTITION BY KEY() PARTITIONS 3;
INSERT INTO t1 (a) VALUES (1),(2),(3),(4),(5);
SET @saved_dbug= @@SESSION.debug_dbug;
SET DEBUG_DBUG='+d,ib_commit_inplace_fail_2';
ALTER TABLE t1 ADD COLUMN c CHAR(3) DEFAULT 'lie';
ERROR HY000: Internal error: Injected error!
SET DEBUG_DBUG= @saved_dbug;
BEGIN;
UPDATE t1 SET b=a+1;
INSERT INTO t1 VALUES (0,1);
ROLLBACK;
SELECT * FROM t1;
a b
1 NULL
2 NULL
3 NULL
4 NULL
5 NULL
ALTER TABLE t1 ADD COLUMN c CHAR(3) DEFAULT 'lie';
SET DEBUG_DBUG='+d,ib_commit_inplace_fail_1';
SET DEBUG_DBUG= @saved_dbug;
BEGIN;
DELETE FROM t1;
INSERT INTO t1 VALUES (1,2,'foo');
ROLLBACK;
SHOW CREATE TABLE t1;
Table Create Table
t1 CREATE TABLE `t1` (
`a` int(11) NOT NULL,
`b` int(11) DEFAULT NULL,
`c` char(3) DEFAULT 'lie',
PRIMARY KEY (`a`),
KEY `b` (`b`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
PARTITION BY KEY ()
PARTITIONS 3
DROP TABLE t1;
--source include/have_innodb.inc
# The embedded server tests do not support restarting.
--source include/not_embedded.inc
--source include/have_debug.inc
--source include/have_debug_sync.inc
--echo #
--echo # MDEV-11369: Instant ADD COLUMN for InnoDB
--echo #
CREATE TABLE t1(id INT PRIMARY KEY, c2 INT UNIQUE) ENGINE=InnoDB;
CREATE TABLE t2 LIKE t1;
INSERT INTO t1 VALUES(1,2);
BEGIN;
INSERT INTO t2 VALUES(2,1);
ALTER TABLE t2 ADD COLUMN (c3 TEXT NOT NULL DEFAULT 'De finibus bonorum');
connect ddl, localhost, root;
SET DEBUG_SYNC='innodb_alter_inplace_before_commit SIGNAL ddl WAIT_FOR ever';
--send
ALTER TABLE t1 ADD COLUMN (c3 TEXT NOT NULL DEFAULT ' et malorum');
connection default;
SET DEBUG_SYNC='now WAIT_FOR ddl';
SET GLOBAL innodb_flush_log_at_trx_commit=1;
COMMIT;
--source include/kill_mysqld.inc
disconnect ddl;
--source include/start_mysqld.inc
SET GLOBAL innodb_purge_rseg_truncate_frequency=1;
SELECT * FROM t1;
SELECT * FROM t2;
BEGIN;
DELETE FROM t1;
ROLLBACK;
--source include/wait_all_purged.inc
connect ddl, localhost, root;
SET DEBUG_SYNC='innodb_alter_inplace_before_commit SIGNAL ddl WAIT_FOR ever';
--send
ALTER TABLE t2 ADD COLUMN (c4 TEXT NOT NULL DEFAULT ' et malorum');
connection default;
SET DEBUG_SYNC='now WAIT_FOR ddl';
SET GLOBAL innodb_flush_log_at_trx_commit=1;
DELETE FROM t1;
--source include/kill_mysqld.inc
disconnect ddl;
--source include/start_mysqld.inc
SET @saved_frequency= @@GLOBAL.innodb_purge_rseg_truncate_frequency;
SET GLOBAL innodb_purge_rseg_truncate_frequency=1;
SELECT * FROM t1;
SELECT * FROM t2;
BEGIN;
INSERT INTO t1 SET id=1;
DELETE FROM t2;
ROLLBACK;
--source include/wait_all_purged.inc
DELETE FROM t2;
--source include/wait_all_purged.inc
SHOW CREATE TABLE t1;
SHOW CREATE TABLE t2;
DROP TABLE t1,t2;
SET GLOBAL innodb_purge_rseg_truncate_frequency=@saved_frequency;
--source include/have_innodb.inc
--source include/have_debug.inc
--source include/have_partition.inc
CREATE TABLE t1(a INT PRIMARY KEY, b INT, KEY(b)) ENGINE=InnoDB
PARTITION BY KEY() PARTITIONS 3;
INSERT INTO t1 (a) VALUES (1),(2),(3),(4),(5);
SET @saved_dbug= @@SESSION.debug_dbug;
SET DEBUG_DBUG='+d,ib_commit_inplace_fail_2';
--error ER_INTERNAL_ERROR
ALTER TABLE t1 ADD COLUMN c CHAR(3) DEFAULT 'lie';
SET DEBUG_DBUG= @saved_dbug;
BEGIN;
UPDATE t1 SET b=a+1;
INSERT INTO t1 VALUES (0,1);
ROLLBACK;
SELECT * FROM t1;
ALTER TABLE t1 ADD COLUMN c CHAR(3) DEFAULT 'lie';
SET DEBUG_DBUG='+d,ib_commit_inplace_fail_1';
# FIXME: This crashes in rollback!
#--error ER_INTERNAL_ERROR
#ALTER TABLE t1 ADD COLUMN d INT NOT NULL DEFAULT -42;
SET DEBUG_DBUG= @saved_dbug;
BEGIN;
DELETE FROM t1;
INSERT INTO t1 VALUES (1,2,'foo');
ROLLBACK;
SHOW CREATE TABLE t1;
DROP TABLE t1;
......@@ -3968,6 +3968,60 @@ btr_cur_update_in_place(
return(err);
}
/** Trim an update tuple due to instant ADD COLUMN, if needed.
For normal records, the trailing instantly added fields that match
the 'default row' are omitted.
For the special 'default row' record on a table on which instant
ADD COLUMN has already been executed, both ADD COLUMN and the
rollback of ADD COLUMN need to be handled specially.
@param[in,out] entry index entry
@param[in] index index
@param[in] update update vector
@param[in] thr execution thread */
static inline
void
btr_cur_trim(
dtuple_t* entry,
const dict_index_t* index,
const upd_t* update,
const que_thr_t* thr)
{
if (!index->is_instant()) {
} else if (UNIV_UNLIKELY(update->info_bits == REC_INFO_DEFAULT_ROW)) {
/* We are either updating a 'default row'
(instantly adding columns to a table where instant ADD was
already executed) or rolling back such an operation. */
ut_ad(!upd_get_nth_field(update, 0)->orig_len);
ut_ad(upd_get_nth_field(update, 0)->field_no
> index->n_core_fields);
if (thr->graph->trx->in_rollback) {
/* This rollback can occur either as part of
ha_innobase::commit_inplace_alter_table() rolling
back after a failed innobase_add_instant_try(),
or as part of crash recovery. The table would
be in the data dictionary cache only if we roll
back as part of crash recovery. */
ut_ad(index->table->cached
== trx_is_recv(thr->graph->trx));
/* The DB_TRX_ID,DB_ROLL_PTR are always last,
and there should be some change to roll back.
The first field in the update vector is the
first instantly added column logged by
innobase_add_instant_try(). */
ut_ad(update->n_fields > 2);
ulint n_fields = upd_get_nth_field(update, 0)
->field_no;
ut_ad(n_fields + 1 >= entry->n_fields);
entry->n_fields = n_fields;
}
} else {
entry->trim(*index);
}
}
/*************************************************************//**
Tries to update a record on a page in an index tree. It is assumed that mtr
holds an x-latch on the page. The operation does not succeed if there is too
......@@ -4099,10 +4153,7 @@ btr_cur_optimistic_update(
Thus the following call is safe. */
row_upd_index_replace_new_col_vals_index_pos(new_entry, index, update,
*heap);
if (index->is_instant()
&& UNIV_LIKELY(update->info_bits != REC_INFO_DEFAULT_ROW)) {
new_entry->trim(*index);
}
btr_cur_trim(new_entry, index, update, thr);
old_rec_size = rec_offs_size(*offsets);
new_rec_size = rec_get_converted_size(index, new_entry, 0);
......@@ -4432,10 +4483,7 @@ btr_cur_pessimistic_update(
itself. Thus the following call is safe. */
row_upd_index_replace_new_col_vals_index_pos(new_entry, index, update,
entry_heap);
if (index->is_instant()
&& UNIV_LIKELY(update->info_bits != REC_INFO_DEFAULT_ROW)) {
new_entry->trim(*index);
}
btr_cur_trim(new_entry, index, update, thr);
/* We have to set appropriate extern storage bits in the new
record to be inserted: we have to remember which fields were such */
......@@ -5238,8 +5286,6 @@ btr_cur_optimistic_delete_func(
}
rec = btr_cur_get_rec(cursor);
ut_ad(!(rec_get_info_bits(rec, page_rec_is_comp(rec))
& REC_INFO_MIN_REC_FLAG));
offsets = rec_get_offsets(rec, cursor->index, offsets, true,
ULINT_UNDEFINED, &heap);
......@@ -5252,9 +5298,17 @@ btr_cur_optimistic_delete_func(
page_t* page = buf_block_get_frame(block);
page_zip_des_t* page_zip= buf_block_get_page_zip(block);
lock_update_delete(block, rec);
if (UNIV_UNLIKELY(rec_get_info_bits(rec, page_rec_is_comp(rec))
& REC_INFO_MIN_REC_FLAG)) {
/* This should be rolling back instant ADD COLUMN. */
ut_ad(cursor->index->table->supports_instant());
ut_ad(!cursor->index->is_instant());
ut_ad(cursor->index->is_clust());
} else {
lock_update_delete(block, rec);
btr_search_update_hash_on_delete(cursor);
btr_search_update_hash_on_delete(cursor);
}
if (page_zip) {
#ifdef UNIV_ZIP_DEBUG
......@@ -5382,9 +5436,6 @@ btr_cur_pessimistic_delete(
#ifdef UNIV_ZIP_DEBUG
ut_a(!page_zip || page_zip_validate(page_zip, page, index));
#endif /* UNIV_ZIP_DEBUG */
ut_ad(!page_is_leaf(page)
|| !(rec_get_info_bits(rec, page_rec_is_comp(rec))
& REC_INFO_MIN_REC_FLAG));
offsets = rec_get_offsets(rec, index, NULL, page_is_leaf(page),
ULINT_UNDEFINED, &heap);
......@@ -5398,35 +5449,39 @@ btr_cur_pessimistic_delete(
#endif /* UNIV_ZIP_DEBUG */
}
if (flags == 0) {
lock_update_delete(block, rec);
}
if (UNIV_UNLIKELY(page_get_n_recs(page) < 2)
&& UNIV_UNLIKELY(dict_index_get_page(index)
!= block->page.id.page_no())) {
/* If there is only one record, drop the whole page in
btr_discard_page, if this is not the root page */
if (page_is_leaf(page)) {
const bool is_default_row = rec_get_info_bits(
rec, page_rec_is_comp(rec)) & REC_INFO_MIN_REC_FLAG;
if (UNIV_UNLIKELY(is_default_row)) {
/* This should be rolling back instant ADD COLUMN. */
ut_ad(rollback);
ut_ad(index->table->supports_instant());
ut_ad(index->is_instant());
ut_ad(index->is_clust());
} else if (flags == 0) {
lock_update_delete(block, rec);
}
btr_discard_page(cursor, mtr);
if (!page_is_root(page)) {
if (page_get_n_recs(page) < 2) {
ut_ad(page_get_n_recs(page) == 1);
/* If there is only one record, drop
the whole page. */
ret = TRUE;
btr_discard_page(cursor, mtr);
goto return_after_reservations;
}
if (page_is_leaf(page)) {
if (UNIV_UNLIKELY(page_is_root(page)
&& page_get_n_recs(page)
== 1 + index->is_instant())) {
/* The whole index (and table) becomes logically empty.
Empty the whole page, including any 'default row'. */
ret = TRUE;
goto return_after_reservations;
}
} else if (page_get_n_recs(page) == 1 + index->is_instant()) {
/* The whole index (and table) becomes
logically empty. Empty the whole page,
including any 'default row'. */
ut_ad(!index->is_instant()
|| rec_is_default_row(
page_rec_get_next_const(
page_get_infimum_rec(page)),
index));
index));
btr_page_empty(block, page_zip, index, 0, mtr);
page_cur_set_after_last(block,
btr_cur_get_page_cur(cursor));
......@@ -5440,7 +5495,9 @@ btr_cur_pessimistic_delete(
goto return_after_reservations;
}
btr_search_update_hash_on_delete(cursor);
if (UNIV_LIKELY(!is_default_row)) {
btr_search_update_hash_on_delete(cursor);
}
} else if (UNIV_UNLIKELY(page_rec_is_first(rec, page))) {
rec_t* next_rec = page_rec_get_next(rec);
......
......@@ -9286,6 +9286,7 @@ ha_innobase::commit_inplace_alter_table(
}
/* Commit or roll back the changes to the data dictionary. */
DEBUG_SYNC(m_user_thd, "innodb_alter_inplace_before_commit");
if (fail) {
trx_rollback_for_mysql(trx);
......
......@@ -1138,7 +1138,6 @@ page_set_instant(page_t* page, unsigned n, mtr_t* mtr)
ut_ad(n > 0);
ut_ad(n < 1U << 10);
uint16_t i = page_header_get_field(page, PAGE_INSTANT);
ut_ad(i <= PAGE_NO_DIRECTION);
i |= n << 6;
mlog_write_ulint(PAGE_HEADER + PAGE_INSTANT + page, i,
MLOG_2BYTES, mtr);
......
......@@ -1177,10 +1177,8 @@ struct trx_t {
trx_rsegs_t rsegs; /* rollback segments for undo logging */
undo_no_t roll_limit; /*!< least undo number to undo during
a partial rollback; 0 otherwise */
#ifdef UNIV_DEBUG
bool in_rollback; /*!< true when the transaction is
executing a partial or full rollback */
#endif /* UNIV_DEBUG */
ulint pages_undone; /*!< number of undo log pages undone
since the last undo log truncation */
/*------------------------------*/
......
......@@ -341,7 +341,8 @@ rec_init_offsets_comp_ordinary(
index->n_nullable));
break;
case REC_LEAF_COLUMNS_ADDED:
ut_ad(index->is_instant());
/* We would have !index->is_instant() when rolling back
an instant ADD COLUMN operation. */
nulls -= REC_N_NEW_EXTRA_BYTES;
if (rec_offs_n_fields(offsets) <= n_fields) {
goto ordinary;
......@@ -1963,8 +1964,11 @@ rec_copy_prefix_to_buf(
switch (rec_get_status(rec)) {
case REC_STATUS_COLUMNS_ADDED:
ut_ad(index->is_instant());
/* We would have !index->is_instant() when rolling back
an instant ADD COLUMN operation. */
ut_ad(index->is_instant() || page_rec_is_default_row(rec));
if (n_fields >= index->n_core_fields) {
ut_ad(index->is_instant());
ut_ad(n_fields <= index->n_fields);
nulls = &rec[-REC_N_NEW_EXTRA_BYTES];
const ulint n_rec = n_fields + 1
......
......@@ -1011,9 +1011,24 @@ row_search_on_row_ref(
index = dict_table_get_first_index(table);
ut_a(dtuple_get_n_fields(ref) == dict_index_get_n_unique(index));
btr_pcur_open(index, ref, PAGE_CUR_LE, mode, pcur, mtr);
if (UNIV_UNLIKELY(ref->info_bits)) {
ut_ad(ref->info_bits == REC_INFO_DEFAULT_ROW);
ut_ad(ref->n_fields <= index->n_uniq);
btr_pcur_open_at_index_side(true, index, mode, pcur, true, 0,
mtr);
btr_pcur_move_to_next_user_rec(pcur, mtr);
/* We do not necessarily have index->is_instant() here,
because we could be executing a rollback of an
instant ADD COLUMN operation. The function
rec_is_default_row() asserts index->is_instant();
we do not want to call it here. */
return rec_get_info_bits(btr_pcur_get_rec(pcur),
dict_table_is_comp(index->table))
& REC_INFO_MIN_REC_FLAG;
} else {
ut_a(ref->n_fields == index->n_uniq);
btr_pcur_open(index, ref, PAGE_CUR_LE, mode, pcur, mtr);
}
low_match = btr_pcur_get_low_match(pcur);
......
......@@ -117,7 +117,8 @@ row_undo_ins_remove_clust_rec(
mem_heap_free(heap);
}
if (node->table->id == DICT_INDEXES_ID) {
switch (node->table->id) {
case DICT_INDEXES_ID:
ut_ad(!online);
ut_ad(node->trx->dict_operation_lock_mode == RW_X_LATCH);
......@@ -132,6 +133,82 @@ row_undo_ins_remove_clust_rec(
success = btr_pcur_restore_position(
BTR_MODIFY_LEAF, &node->pcur, &mtr);
ut_a(success);
break;
case DICT_COLUMNS_ID:
/* This is rolling back an INSERT into SYS_COLUMNS.
If it was part of an instant ADD COLUMN operation, we
must modify the table definition. At this point, any
corresponding operation to the 'default row' will have
been rolled back. */
ut_ad(!online);
ut_ad(node->trx->dict_operation_lock_mode == RW_X_LATCH);
const rec_t* rec = btr_pcur_get_rec(&node->pcur);
if (rec_get_n_fields_old(rec)
!= DICT_NUM_FIELDS__SYS_COLUMNS) {
break;
}
ulint len;
const byte* data = rec_get_nth_field_old(
rec, DICT_FLD__SYS_COLUMNS__TABLE_ID, &len);
if (len != 8) {
break;
}
const table_id_t table_id = mach_read_from_8(data);
data = rec_get_nth_field_old(rec, DICT_FLD__SYS_COLUMNS__POS,
&len);
if (len != 4) {
break;
}
const unsigned pos = mach_read_from_4(data);
if (pos == 0 || pos >= (1U << 16)) {
break;
}
dict_table_t* table = dict_table_open_on_id(
table_id, true, DICT_TABLE_OP_OPEN_ONLY_IF_CACHED);
if (!table) {
break;
}
dict_index_t* index = dict_table_get_first_index(table);
if (index && index->is_instant()
&& DATA_N_SYS_COLS + 1 + pos == table->n_cols) {
/* This is the rollback of an instant ADD COLUMN.
Remove the column from the dictionary cache,
but keep the system columns. */
char* names = const_cast<char*>(
dict_table_get_col_name(table, pos));
const char* sys = names + strlen(names) + 1;
static const char system[]
= "DB_ROW_ID\0DB_TRX_ID\0DB_ROLL_PTR";
ut_ad(!memcmp(sys, system, sizeof system));
index->n_nullable -= index->fields[--index->n_fields]
.col->is_nullable();
memmove(names, sys, sizeof system);
table->n_cols--;
memmove(table->cols + pos, table->cols + pos + 1,
DATA_N_SYS_COLS * sizeof *table->cols);
for (uint i = 3; i--; ) {
table->cols[table->n_cols - i].ind--;
}
if (dict_index_is_auto_gen_clust(index)) {
ut_ad(index->n_uniq == 1);
dict_field_t* field = index->fields;
field->name = sys;
field->col = dict_table_get_sys_col(
table, DATA_ROW_ID);
}
dict_field_t* field = &index->fields[index->n_uniq];
field->name = sys + sizeof "DB_ROW_ID";
field->col = dict_table_get_sys_col(table,
DATA_TRX_ID);
field++;
field->name = sys + sizeof "DB_ROW_ID\0DB_TRX_ID";
field->col = dict_table_get_sys_col(table,
DATA_ROLL_PTR);
}
dict_table_close(table, true, false);
}
if (btr_cur_optimistic_delete(btr_cur, 0, &mtr)) {
......@@ -370,7 +447,7 @@ row_undo_ins_parse_undo_rec(
} else {
ut_ad(type == TRX_UNDO_INSERT_DEFAULT);
static const dtuple_t min_rec = {
REC_INFO_MIN_REC_FLAG, 0, 0,
REC_INFO_DEFAULT_ROW, 0, 0,
NULL, 0, NULL,
UT_LIST_NODE_T(dtuple_t)()
#ifdef UNIV_DEBUG
......
......@@ -1163,6 +1163,21 @@ row_undo_mod_parse_undo_rec(
node->heap, &(node->update));
node->new_trx_id = trx_id;
node->cmpl_info = cmpl_info;
ut_ad(!node->ref->info_bits);
if (node->update->info_bits & REC_INFO_MIN_REC_FLAG) {
/* This must be an undo log record for a subsequent
instant ADD COLUMN on a table, extending the
'default value' record. */
ut_ad(clust_index->is_instant());
if (node->update->info_bits != REC_INFO_MIN_REC_FLAG) {
ut_ad(!"wrong info_bits in undo log record");
goto close_table;
}
node->update->info_bits = REC_INFO_DEFAULT_ROW;
const_cast<dtuple_t*>(node->ref)->info_bits
= REC_INFO_DEFAULT_ROW;
}
if (!row_undo_search_clust_to_pcur(node)) {
/* As long as this rolling-back transaction exists,
......@@ -1224,6 +1239,12 @@ row_undo_mod(
node->index = dict_table_get_first_index(node->table);
ut_ad(dict_index_is_clust(node->index));
if (node->ref->info_bits) {
ut_ad(node->ref->info_bits == REC_INFO_DEFAULT_ROW);
goto rollback_clust;
}
/* Skip the clustered index (the first index) */
node->index = dict_table_get_next_index(node->index);
......@@ -1246,6 +1267,7 @@ row_undo_mod(
}
if (err == DB_SUCCESS) {
rollback_clust:
err = row_undo_mod_clust(node, thr);
bool update_statistics
......
......@@ -233,7 +233,8 @@ row_undo_search_clust_to_pcur(
row_upd_replace(node->undo_row, &node->undo_ext,
clust_index, node->update, node->heap);
} else {
ut_ad(!(node->row->info_bits & REC_INFO_MIN_REC_FLAG));
ut_ad((node->row->info_bits == REC_INFO_MIN_REC_FLAG)
== (node->rec_type == TRX_UNDO_INSERT_DEFAULT));
node->undo_row = NULL;
node->undo_ext = NULL;
}
......
......@@ -1515,6 +1515,7 @@ trx_undo_update_rec_get_update(
ulint orig_len;
bool is_virtual;
upd_field = upd_get_nth_field(update, i);
field_no = mach_read_next_compressed(&ptr);
is_virtual = (field_no >= REC_MAX_N_FIELDS);
......@@ -1526,27 +1527,6 @@ trx_undo_update_rec_get_update(
index->table, ptr, first_v_col, &is_undo_log,
&field_no);
first_v_col = false;
} else if (field_no >= dict_index_get_n_fields(index)) {
ib::error() << "Trying to access update undo rec"
" field " << field_no
<< " in index " << index->name
<< " of table " << index->table->name
<< " but index has only "
<< dict_index_get_n_fields(index)
<< " fields " << BUG_REPORT_MSG
<< ". Run also CHECK TABLE "
<< index->table->name << "."
" n_fields = " << n_fields << ", i = " << i
<< ", ptr " << ptr;
ut_ad(0);
*upd = NULL;
return(NULL);
}
upd_field = upd_get_nth_field(update, i);
if (is_virtual) {
/* This column could be dropped or no longer indexed */
if (field_no == ULINT_UNDEFINED) {
/* Mark this is no longer needed */
......@@ -1560,10 +1540,31 @@ trx_undo_update_rec_get_update(
continue;
}
upd_field_set_v_field_no(
upd_field, field_no, index);
upd_field_set_v_field_no(upd_field, field_no, index);
} else if (field_no < index->n_fields) {
upd_field_set_field_no(upd_field, field_no, index,trx);
} else if (update->info_bits == REC_INFO_MIN_REC_FLAG
&& index->is_instant() && trx->in_rollback) {
/* This must be a rollback of a subsequent
instant ADD COLUMN operation. This will be
detected and handled by btr_cur_trim(). */
upd_field->field_no = field_no;
upd_field->orig_len = 0;
} else {
upd_field_set_field_no(upd_field, field_no, index, trx);
ib::error() << "Trying to access update undo rec"
" field " << field_no
<< " in index " << index->name
<< " of table " << index->table->name
<< " but index has only "
<< dict_index_get_n_fields(index)
<< " fields " << BUG_REPORT_MSG
<< ". Run also CHECK TABLE "
<< index->table->name << "."
" n_fields = " << n_fields << ", i = " << i;
ut_ad(0);
*upd = NULL;
return(NULL);
}
ptr = trx_undo_rec_get_col_val(ptr, &field, &len, &orig_len);
......
......@@ -1012,7 +1012,7 @@ trx_roll_pop_top_rec_of_trx(trx_t* trx, roll_ptr_t* roll_ptr, mem_heap_t* heap)
if the transaction object is committed and reused
later, we will default to a full ROLLBACK. */
trx->roll_limit = 0;
ut_d(trx->in_rollback = false);
trx->in_rollback = false;
mutex_exit(&trx->undo_mutex);
return(NULL);
}
......@@ -1129,7 +1129,7 @@ trx_rollback_start(
ut_ad(!trx->in_rollback);
trx->roll_limit = roll_limit;
ut_d(trx->in_rollback = true);
trx->in_rollback = true;
ut_a(trx->roll_limit <= trx->undo_no);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment