Skip to content

Commit 3648af6

Browse files
author
Benny Wang
committed
Fixed bug#21390605: VALGRIND ERROR ON DELETE FROM TABLE CONTAINING AN INDEXED
VIRTUAL COLUMN Take one test case for an example. CREATE TABLE t1 ( a INTEGER, b INTEGER GENERATED ALWAYS AS (a) VIRTUAL, c INTEGER GENERATED ALWAYS AS (b) VIRTUAL, INDEX idx (c)); When calling the callback function my_eval_gcolumn_expr_helper() to evaluate the value of column 'c', the value of virtual generated column 'b' is needed. But the caller only asks for 'c'. So the callback function won't calculate column 'b' before evaluating column 'c'. This results in invalid read of 'b' during calculate 'c'. The solution is to calculate all the base virtual generated columns before evaluating the required column. Instead of adding yet another walk() to gather needed columns, we do the walk() a single time (when opening the table) and build a bitmap (new member Generated_column::base_columns_map). This bitmap replaces the base_columns list. It is different as it lists virtual base columns too (base_columns only listed *stored* base columns). So the InnoDB code, which wants to see the stored base columns only, has been adapted to filter out the bitmap; if it only needs a count of columns, it uses the new member Generated_column::num_non_virtual_base_cols.
1 parent a8f75d8 commit 3648af6

File tree

7 files changed

+106
-50
lines changed

7 files changed

+106
-50
lines changed

mysql-test/suite/gcol/inc/gcol_column_def_options.inc

+12
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,18 @@ INSERT INTO t1(col1,col2,col3,col4,col9,extra)
307307
VALUES(0,6,3,4,REPEAT(4,1000),0);
308308
ALTER TABLE t1 DROP COLUMN col1;
309309
DROP TABLE t1;
310+
311+
--echo # Bug#21390605:VALGRIND ERROR ON DELETE FROM TABLE CONTAINING
312+
--echo # AN INDEXED VIRTUAL COLUMN
313+
CREATE TABLE t1 (
314+
a INTEGER,
315+
b INTEGER GENERATED ALWAYS AS (a) VIRTUAL,
316+
c INTEGER GENERATED ALWAYS AS (b) VIRTUAL,
317+
INDEX idx (b,c)
318+
);
319+
INSERT INTO t1 (a) VALUES (42);
320+
DELETE FROM t1 WHERE c = 42;
321+
DROP TABLE t1;
310322
}
311323

312324
--echo # Bug#20757211: GENERATED COLUMNS: ALTER TABLE CRASHES

mysql-test/suite/gcol/r/gcol_column_def_options_innodb.result

+11
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,17 @@ INSERT INTO t1(col1,col2,col3,col4,col9,extra)
406406
VALUES(0,6,3,4,REPEAT(4,1000),0);
407407
ALTER TABLE t1 DROP COLUMN col1;
408408
DROP TABLE t1;
409+
# Bug#21390605:VALGRIND ERROR ON DELETE FROM TABLE CONTAINING
410+
# AN INDEXED VIRTUAL COLUMN
411+
CREATE TABLE t1 (
412+
a INTEGER,
413+
b INTEGER GENERATED ALWAYS AS (a) VIRTUAL,
414+
c INTEGER GENERATED ALWAYS AS (b) VIRTUAL,
415+
INDEX idx (b,c)
416+
);
417+
INSERT INTO t1 (a) VALUES (42);
418+
DELETE FROM t1 WHERE c = 42;
419+
DROP TABLE t1;
409420
# Bug#20757211: GENERATED COLUMNS: ALTER TABLE CRASHES
410421
# IN FIND_FIELD_IN_TABLE
411422
#

sql/field.h

+13-7
Original file line numberDiff line numberDiff line change
@@ -482,15 +482,13 @@ class Generated_column: public Sql_alloc
482482
LEX_STRING expr_str;
483483
/* It's used to free the items created in parsing generated expression */
484484
Item *item_free_list;
485-
/*
486-
A list of all stored (non-generated and stored generated) columns which a
487-
generated column depends on. It is used by storage engines.
488-
*/
489-
List<Field> base_columns;
485+
/// Bitmap records base columns which a generated column depends on.
486+
MY_BITMAP base_columns_map;
490487

491488
Generated_column()
492-
: expr_item(0), item_free_list(0), base_columns(),
493-
field_type(MYSQL_TYPE_LONG), stored_in_db(false)
489+
: expr_item(0), item_free_list(0),
490+
field_type(MYSQL_TYPE_LONG),
491+
stored_in_db(false), num_non_virtual_base_cols(0)
494492
{
495493
expr_str.str= NULL;
496494
expr_str.length= 0;
@@ -515,7 +513,13 @@ class Generated_column: public Sql_alloc
515513
stored_in_db= stored;
516514
}
517515
bool register_base_columns(TABLE *table);
516+
/**
517+
Get the number of non virtual base columns that this generated
518+
column needs.
518519
520+
@return number of non virtual base columns
521+
*/
522+
uint non_virtual_base_columns() const { return num_non_virtual_base_cols; }
519523
private:
520524
/*
521525
The following data is only updated by the parser and read
@@ -524,6 +528,8 @@ class Generated_column: public Sql_alloc
524528
enum_field_types field_type; /* Real field type*/
525529
bool stored_in_db; /* Indication that the field is
526530
phisically stored in the database*/
531+
/// How many non-virtual base columns in base_columns_map
532+
uint num_non_virtual_base_cols;
527533
};
528534

529535
class Proto_field

sql/handler.cc

+26-2
Original file line numberDiff line numberDiff line change
@@ -7911,15 +7911,39 @@ static bool my_eval_gcolumn_expr_helper(THD *thd, TABLE *table,
79117911
blob_len_ptr_array);
79127912

79137913
bool res= false;
7914+
MY_BITMAP fields_to_evaluate;
7915+
my_bitmap_map bitbuf[bitmap_buffer_size(MAX_FIELDS) / sizeof(my_bitmap_map)];
7916+
bitmap_init(&fields_to_evaluate, bitbuf, table->s->fields, 0);
7917+
bitmap_set_all(&fields_to_evaluate);
7918+
bitmap_intersect(&fields_to_evaluate, fields);
7919+
/*
7920+
In addition to evaluating the value for the columns requested by
7921+
the caller we also need to evaluate any virtual columns that these
7922+
depend on.
7923+
This loop goes through the columns that should be evaluated and
7924+
adds all the base columns. If the base column is virtual, it has
7925+
to be evaluated.
7926+
*/
79147927
for (Field **vfield_ptr= table->vfield; *vfield_ptr; vfield_ptr++)
79157928
{
79167929
Field *field= *vfield_ptr;
7917-
79187930
// Validate that the field number is less than the bit map size
79197931
DBUG_ASSERT(field->field_index < fields->n_bits);
79207932

7921-
// Check if we should evaluate this field
79227933
if (bitmap_is_set(fields, field->field_index))
7934+
bitmap_union(&fields_to_evaluate, &field->gcol_info->base_columns_map);
7935+
}
7936+
7937+
/*
7938+
Evaluate all requested columns and all base columns these depends
7939+
on that are virtual.
7940+
*/
7941+
for (Field **vfield_ptr= table->vfield; *vfield_ptr; vfield_ptr++)
7942+
{
7943+
Field *field= *vfield_ptr;
7944+
7945+
// Check if we should evaluate this field
7946+
if (bitmap_is_set(&fields_to_evaluate, field->field_index))
79237947
{
79247948
DBUG_ASSERT(field->gcol_info && field->gcol_info->expr_item->fixed);
79257949
if (in_purge)

sql/table.cc

+25-24
Original file line numberDiff line numberDiff line change
@@ -2721,7 +2721,8 @@ bool fix_fields_gcol_func(THD *thd, Field *field)
27212721
}
27222722

27232723
/**
2724-
Build the base_columns member of this generated column
2724+
Calculate the base_columns_map and num_non_virtual_base_cols members of
2725+
this generated column
27252726
27262727
@param table Table with the checked field
27272728
@@ -2731,8 +2732,10 @@ bool fix_fields_gcol_func(THD *thd, Field *field)
27312732
bool Generated_column::register_base_columns(TABLE *table)
27322733
{
27332734
DBUG_ENTER("register_base_columns");
2734-
my_bitmap_map bitbuf[bitmap_buffer_size(MAX_FIELDS) / sizeof(my_bitmap_map)];
2735-
MY_BITMAP base_columns_map;
2735+
my_bitmap_map *bitbuf=
2736+
static_cast<my_bitmap_map *>(alloc_root(&table->mem_root,
2737+
bitmap_buffer_size(table->s->fields)));
2738+
DBUG_ASSERT(num_non_virtual_base_cols == 0);
27362739
bitmap_init(&base_columns_map, bitbuf, table->s->fields, 0);
27372740

27382741
MY_BITMAP *save_old_read_set= table->read_set;
@@ -2741,14 +2744,14 @@ bool Generated_column::register_base_columns(TABLE *table)
27412744
expr_item->walk(&Item::mark_field_in_map,
27422745
Item::WALK_PREFIX, (uchar *) &mark_fld);
27432746
table->read_set= save_old_read_set;
2747+
2748+
/* Calculate the number of non-virtual base columns */
27442749
for (uint i= 0; i < table->s->fields; i++)
27452750
{
27462751
Field *field= table->field[i];
2747-
/* Virtual generated columns are not needed */
2748-
if (field->stored_in_db &&
2749-
bitmap_is_set(&base_columns_map, field->field_index) &&
2750-
base_columns.push_back(field))
2751-
DBUG_RETURN(true); // OOM
2752+
if (bitmap_is_set(&base_columns_map, field->field_index) &&
2753+
field->stored_in_db)
2754+
num_non_virtual_base_cols++;
27522755
}
27532756
DBUG_RETURN(false);
27542757
}
@@ -6613,12 +6616,6 @@ void TABLE::mark_generated_columns(bool is_update)
66136616
{
66146617
tmp_vfield= *vfield_ptr;
66156618
DBUG_ASSERT(tmp_vfield->gcol_info && tmp_vfield->gcol_info->expr_item);
6616-
Mark_field mark_fld(MARK_COLUMNS_TEMP);
6617-
MY_BITMAP *save_old_read_set= read_set;
6618-
bitmap_clear_all(&dependent_fields);
6619-
read_set= &dependent_fields;
6620-
tmp_vfield->gcol_info->expr_item->walk(&Item::mark_field_in_map,
6621-
Item::WALK_PREFIX, (uchar *) &mark_fld);
66226619

66236620
/*
66246621
We need to evaluate the GC if:
@@ -6632,9 +6629,9 @@ void TABLE::mark_generated_columns(bool is_update)
66326629
be located first, for that the GC's value is needed.
66336630
*/
66346631
if ((!tmp_vfield->stored_in_db && tmp_vfield->m_indexed) ||
6635-
bitmap_is_overlapping(read_set, write_set))
6632+
bitmap_is_overlapping(write_set,
6633+
&tmp_vfield->gcol_info->base_columns_map))
66366634
{
6637-
read_set= save_old_read_set;
66386635
// The GC needs to be updated
66396636
tmp_vfield->table->mark_column_used(in_use, tmp_vfield,
66406637
MARK_COLUMNS_WRITE);
@@ -6643,8 +6640,6 @@ void TABLE::mark_generated_columns(bool is_update)
66436640
MARK_COLUMNS_READ);
66446641
bitmap_updated= TRUE;
66456642
}
6646-
else
6647-
read_set= save_old_read_set;
66486643
}
66496644
}
66506645
else // Insert needs to evaluate all generated columns
@@ -7570,9 +7565,9 @@ void TABLE::mark_gcol_in_maps(Field *field)
75707565
{
75717566
bitmap_set_bit(write_set, field->field_index);
75727567
/*
7573-
Using MARK_COLUMNS_TEMP has the effect that underlying base columns
7574-
are added to read_set but not added to requirements for an index to be
7575-
covering (covering_keys is not touched). So, if we have:
7568+
Note that underlying base columns are here added to read_set but not added
7569+
to requirements for an index to be covering (covering_keys is not touched).
7570+
So, if we have:
75767571
SELECT gcol FROM t :
75777572
- an index covering gcol only (not including base columns), can still be
75787573
chosen by the optimizer; note that InnoDB's build_template_needs_field()
@@ -7582,7 +7577,13 @@ void TABLE::mark_gcol_in_maps(Field *field)
75827577
they are in read_set.
75837578
- Note how this relies on InnoDB's behaviour.
75847579
*/
7585-
Mark_field mf(this, MARK_COLUMNS_TEMP);
7586-
field->gcol_info->expr_item->walk(&Item::mark_field_in_map,Item::WALK_PREFIX,
7587-
reinterpret_cast<uchar *>(&mf));
7580+
for (uint i= 0; i < s->fields; i++)
7581+
{
7582+
if (bitmap_is_set(&field->gcol_info->base_columns_map, i))
7583+
{
7584+
bitmap_set_bit(read_set, i);
7585+
if (this->field[i]->is_virtual_gcol())
7586+
bitmap_set_bit(write_set, i);
7587+
}
7588+
}
75887589
}

storage/innobase/handler/ha_innodb.cc

+17-15
Original file line numberDiff line numberDiff line change
@@ -9259,26 +9259,28 @@ innodb_base_col_setup(
92599259
dict_v_col_t* v_col)
92609260
{
92619261
int n = 0;
9262-
const Field* base_field;
92639262

9264-
List_iterator<Field> iter(field->gcol_info->base_columns);
9263+
for (uint i= 0; i < field->table->s->fields; ++i) {
9264+
const Field* base_field = field->table->field[i];
92659265

9266-
while ((base_field = iter++)) {
9267-
ulint z;
9266+
if (!base_field->is_virtual_gcol()
9267+
&& bitmap_is_set(&field->gcol_info->base_columns_map, i)) {
9268+
ulint z;
92689269

9269-
for (z = 0; z < table->n_cols; z++) {
9270-
const char* name = dict_table_get_col_name(table, z);
9271-
if (!innobase_strcasecmp(name,
9272-
base_field->field_name)) {
9273-
break;
9270+
for (z = 0; z < table->n_cols; z++) {
9271+
const char* name = dict_table_get_col_name(table, z);
9272+
if (!innobase_strcasecmp(name,
9273+
base_field->field_name)) {
9274+
break;
9275+
}
92749276
}
9275-
}
92769277

9277-
ut_ad(z != table->n_cols);
9278+
ut_ad(z != table->n_cols);
92789279

9279-
v_col->base_col[n] = dict_table_get_nth_col(table, z);
9280-
ut_ad(v_col->base_col[n]->ind == z);
9281-
n++;
9280+
v_col->base_col[n] = dict_table_get_nth_col(table, z);
9281+
ut_ad(v_col->base_col[n]->ind == z);
9282+
n++;
9283+
}
92829284
}
92839285
}
92849286

@@ -9525,7 +9527,7 @@ create_table_info_t::create_table_def()
95259527
| is_virtual,
95269528
charset_no),
95279529
col_len, i,
9528-
field->gcol_info->base_columns.elements);
9530+
field->gcol_info->non_virtual_base_columns());
95299531
}
95309532
}
95319533

storage/innobase/handler/handler0alter.cc

+2-2
Original file line numberDiff line numberDiff line change
@@ -3190,7 +3190,7 @@ prepare_inplace_add_virtual(
31903190

31913191
ctx->add_vcol[j].m_col.ind = i - 1;
31923192
ctx->add_vcol[j].num_base =
3193-
field->gcol_info->base_columns.elements;
3193+
field->gcol_info->non_virtual_base_columns();
31943194
ctx->add_vcol_name[j] = field->field_name;
31953195
ctx->add_vcol[j].base_col = static_cast<dict_col_t**>(
31963196
mem_heap_alloc(ctx->heap, ctx->add_vcol[j].num_base
@@ -4086,7 +4086,7 @@ prepare_inplace_alter_table_dict(
40864086
field_type, charset_no)
40874087
| DATA_VIRTUAL,
40884088
col_len, i,
4089-
field->gcol_info->base_columns.elements);
4089+
field->gcol_info->non_virtual_base_columns());
40904090
} else {
40914091
dict_mem_table_add_col(
40924092
ctx->new_table, ctx->heap,

0 commit comments

Comments
 (0)