Commit 22709897 authored by Nikita Malyavin's avatar Nikita Malyavin

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 12614af1. 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.
parent 0e8981ef
...@@ -300,3 +300,17 @@ Table Op Msg_type Msg_text ...@@ -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 note Table does not support optimize, doing recreate + analyze instead
test.t1 optimize status OK test.t1 optimize status OK
DROP TABLE t1; 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), ...@@ -314,3 +314,23 @@ CREATE TABLE t1 (id INT PRIMARY KEY, a CHAR(3),
INSERT INTO t1 (id,a) VALUES (1,'foo'); INSERT INTO t1 (id,a) VALUES (1,'foo');
OPTIMIZE TABLE t1; OPTIMIZE TABLE t1;
DROP 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( ...@@ -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 /** Allows InnoDB to update internal structures with concurrent
writes blocked (provided that check_if_supported_inplace_alter() writes blocked (provided that check_if_supported_inplace_alter()
...@@ -5951,9 +5955,9 @@ ha_innobase::prepare_inplace_alter_table( ...@@ -5951,9 +5955,9 @@ ha_innobase::prepare_inplace_alter_table(
== Alter_inplace_info::CHANGE_CREATE_OPTION == Alter_inplace_info::CHANGE_CREATE_OPTION
&& !innobase_need_rebuild(ha_alter_info, table))) { && !innobase_need_rebuild(ha_alter_info, table))) {
ha_innobase_inplace_ctx *ctx = NULL;
if (heap) { if (heap) {
ha_alter_info->handler_ctx ctx = new ha_innobase_inplace_ctx(
= new ha_innobase_inplace_ctx(
m_prebuilt, m_prebuilt,
drop_index, n_drop_index, drop_index, n_drop_index,
rename_index, n_rename_index, rename_index, n_rename_index,
...@@ -5962,6 +5966,7 @@ ha_innobase::prepare_inplace_alter_table( ...@@ -5962,6 +5966,7 @@ ha_innobase::prepare_inplace_alter_table(
ha_alter_info->online, ha_alter_info->online,
heap, indexed_table, heap, indexed_table,
col_names, ULINT_UNDEFINED, 0, 0, 0); col_names, ULINT_UNDEFINED, 0, 0, 0);
ha_alter_info->handler_ctx = ctx;
} }
DBUG_ASSERT(m_prebuilt->trx->dict_operation_lock_mode == 0); DBUG_ASSERT(m_prebuilt->trx->dict_operation_lock_mode == 0);
...@@ -5981,6 +5986,24 @@ ha_innobase::prepare_inplace_alter_table( ...@@ -5981,6 +5986,24 @@ ha_innobase::prepare_inplace_alter_table(
DBUG_RETURN(true); 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); DBUG_RETURN(false);
} }
...@@ -6098,35 +6121,6 @@ ha_innobase::prepare_inplace_alter_table( ...@@ -6098,35 +6121,6 @@ ha_innobase::prepare_inplace_alter_table(
add_fts_doc_id_idx)); 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 /* Check whether a columnn length change alter operation requires
to rebuild the template. to rebuild the template.
@param[in] altered_table TABLE object for new version of table. @param[in] altered_table TABLE object for new version of table.
...@@ -6138,9 +6132,9 @@ to rebuild the template. ...@@ -6138,9 +6132,9 @@ to rebuild the template.
static static
bool bool
alter_templ_needs_rebuild( alter_templ_needs_rebuild(
TABLE* altered_table, const TABLE* altered_table,
Alter_inplace_info* ha_alter_info, const Alter_inplace_info* ha_alter_info,
dict_table_t* table) const dict_table_t* table)
{ {
ulint i = 0; ulint i = 0;
List_iterator_fast<Create_field> cf_it( List_iterator_fast<Create_field> cf_it(
...@@ -6152,8 +6146,7 @@ alter_templ_needs_rebuild( ...@@ -6152,8 +6146,7 @@ alter_templ_needs_rebuild(
for (ulint j=0; j < table->n_cols; j++) { for (ulint j=0; j < table->n_cols; j++) {
dict_col_t* cols dict_col_t* cols
= dict_table_get_nth_col(table, j); = dict_table_get_nth_col(table, j);
if (cf->length > cols->len if (cf->length > cols->len) {
&& dict_col_in_v_indexes(table, cols)) {
return(true); return(true);
} }
} }
......
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