Skip to content
Permalink
Browse files
MDEV-20154 Assertion len <= col->len | ... failed in row_merge_buf_add
len was containing garbage, since vctempl->mysql_col_offset was
containing old value while calling row_mysql_store_col_in_innobase_format
from innobase_get_computed_value().

It was not updated after the first ALTER TABLE call, because it's INPLACE
logic considered there's nothing to update, and exited immediately from
ha_innobase::inplace_alter_table().

However, vcol metadata needs an update, since vcols structure is changed
in mysql record.

The regression was introduced by 12614af. There, refcount==1 condition
was removed, which turned out to be crucial, though racy. The idea was to
update vc_templ after each (sequencing) ALTER TABLE.

We should do the same another way, and there may be a plenty of solutions,
but the simplest one is to add a following condition:
  if vcol structure is changed, drop vc_templ; it will be recreated on next
  ha_innobase::open() call.

in prepare_inplace_alter_table. It is safe, since innodb inplace changes
require at least HA_ALTER_INPLACE_SHARED_LOCK_AFTER_PREPARE, which
guarantee MDL_EXCLUSIVE on this stage.

alter_templ_needs_rebuild() also has to track the columns not indexed, to
keep vc_templ correct.

Note that vc_templ is always kept constructed and available after
ha_innobase::open() call, even on INSERT, though no virtual columns are
evaluated during that statement
inside innodb.

In the test case suplied, it will be recreated on the second ALTER TABLE.
  • Loading branch information
FooBarrior committed Jul 29, 2021
1 parent 0e8981e commit 2270989
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 36 deletions.
@@ -300,3 +300,17 @@ Table Op Msg_type Msg_text
test.t1 optimize note Table does not support optimize, doing recreate + analyze instead
test.t1 optimize status OK
DROP TABLE t1;
#
# MDEV-20154 Assertion `len <= col->len || ((col->mtype) == 5
# || (col->mtype) == 14)' failed in row_merge_buf_add
#
CREATE TABLE t1 (
a VARCHAR(2500),
b VARCHAR(2499) AS (a) VIRTUAL
) ENGINE=InnoDB;
INSERT INTO t1 (a) VALUES ('foo');
ALTER TABLE t1 MODIFY a VARCHAR(2600), ALGORITHM=INPLACE;
ALTER TABLE t1 ADD KEY (b), ALGORITHM=INPLACE;
# Cleanup
DROP TABLE t1;
# End of 10.2 tests
@@ -314,3 +314,23 @@ CREATE TABLE t1 (id INT PRIMARY KEY, a CHAR(3),
INSERT INTO t1 (id,a) VALUES (1,'foo');
OPTIMIZE TABLE t1;
DROP TABLE t1;

--echo #
--echo # MDEV-20154 Assertion `len <= col->len || ((col->mtype) == 5
--echo # || (col->mtype) == 14)' failed in row_merge_buf_add
--echo #

CREATE TABLE t1 (
a VARCHAR(2500),
b VARCHAR(2499) AS (a) VIRTUAL
) ENGINE=InnoDB;
INSERT INTO t1 (a) VALUES ('foo');

ALTER TABLE t1 MODIFY a VARCHAR(2600), ALGORITHM=INPLACE;
ALTER TABLE t1 ADD KEY (b), ALGORITHM=INPLACE;

--echo # Cleanup
DROP TABLE t1;

--echo # End of 10.2 tests

@@ -5355,6 +5355,10 @@ alter_fill_stored_column(
}
}

static bool alter_templ_needs_rebuild(const TABLE* altered_table,
const Alter_inplace_info* ha_alter_info,
const dict_table_t* table);


/** Allows InnoDB to update internal structures with concurrent
writes blocked (provided that check_if_supported_inplace_alter()
@@ -5951,9 +5955,9 @@ ha_innobase::prepare_inplace_alter_table(
== Alter_inplace_info::CHANGE_CREATE_OPTION
&& !innobase_need_rebuild(ha_alter_info, table))) {

ha_innobase_inplace_ctx *ctx = NULL;
if (heap) {
ha_alter_info->handler_ctx
= new ha_innobase_inplace_ctx(
ctx = new ha_innobase_inplace_ctx(
m_prebuilt,
drop_index, n_drop_index,
rename_index, n_rename_index,
@@ -5962,6 +5966,7 @@ ha_innobase::prepare_inplace_alter_table(
ha_alter_info->online,
heap, indexed_table,
col_names, ULINT_UNDEFINED, 0, 0, 0);
ha_alter_info->handler_ctx = ctx;
}

DBUG_ASSERT(m_prebuilt->trx->dict_operation_lock_mode == 0);
@@ -5981,6 +5986,24 @@ ha_innobase::prepare_inplace_alter_table(
DBUG_RETURN(true);
}

if (!(ha_alter_info->handler_flags & INNOBASE_ALTER_DATA)
&& alter_templ_needs_rebuild(altered_table, ha_alter_info,
ctx->new_table)
&& ctx->new_table->n_v_cols > 0) {
/* Changing maria record structure may end up here only
if virtual columns were altered. In this case, however,
vc_templ should be rebuilt. Since we don't actually
change any stored data, we can just dispose vc_templ;
it will be recreated on next ha_innobase::open(). */

DBUG_ASSERT(ctx->new_table == ctx->old_table);

dict_free_vc_templ(ctx->new_table->vc_templ);
UT_DELETE(ctx->new_table->vc_templ);

ctx->new_table->vc_templ = NULL;
}

DBUG_RETURN(false);
}

@@ -6098,35 +6121,6 @@ ha_innobase::prepare_inplace_alter_table(
add_fts_doc_id_idx));
}

/** Check that the column is part of a virtual index(index contains
virtual column) in the table
@param[in] table Table containing column
@param[in] col column to be checked
@return true if this column is indexed with other virtual columns */
static
bool
dict_col_in_v_indexes(
dict_table_t* table,
dict_col_t* col)
{
for (dict_index_t* index = dict_table_get_next_index(
dict_table_get_first_index(table)); index != NULL;
index = dict_table_get_next_index(index)) {
if (!dict_index_has_virtual(index)) {
continue;
}
for (ulint k = 0; k < index->n_fields; k++) {
dict_field_t* field
= dict_index_get_nth_field(index, k);
if (field->col->ind == col->ind) {
return(true);
}
}
}

return(false);
}

/* Check whether a columnn length change alter operation requires
to rebuild the template.
@param[in] altered_table TABLE object for new version of table.
@@ -6138,9 +6132,9 @@ to rebuild the template.
static
bool
alter_templ_needs_rebuild(
TABLE* altered_table,
Alter_inplace_info* ha_alter_info,
dict_table_t* table)
const TABLE* altered_table,
const Alter_inplace_info* ha_alter_info,
const dict_table_t* table)
{
ulint i = 0;
List_iterator_fast<Create_field> cf_it(
@@ -6152,8 +6146,7 @@ alter_templ_needs_rebuild(
for (ulint j=0; j < table->n_cols; j++) {
dict_col_t* cols
= dict_table_get_nth_col(table, j);
if (cf->length > cols->len
&& dict_col_in_v_indexes(table, cols)) {
if (cf->length > cols->len) {
return(true);
}
}

0 comments on commit 2270989

Please sign in to comment.