Skip to content

Commit 05a4ca4

Browse files
author
Guilhem Bichot
committed
Bug#22133710 GCOLS: READ UNCOMMITTED: ASSERT !TABLE || (!TABLE->WRITE_SET || BITMAP_IS_SET(TA
Problem: in READ UNCOMMITTED isolation, a SELECT reading an indexed generated column could trigger an assertion failure in debug binaries. my_eval_gcolumn_expr*() is called by the storage engine, which may request to evaluate more generated columns than read_set/write_set says. For example, InnoDB's row_sel_sec_rec_is_for_clust_rec() reads the full record from the clustered index and asks us to compute generated columns that match key fields in the used secondary index. So we trust that the engine has filled all base columns necessary to requested computations, and we ignore read_set/write_set. The in_purge branch becomes unneeded. I noticed that the my_rec argument of innobase_get_computed_value() is always NULL, so in agreement with Jimmy I remove it.
1 parent 3035d3f commit 05a4ca4

File tree

9 files changed

+59
-34
lines changed

9 files changed

+59
-34
lines changed

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

+17
Original file line numberDiff line numberDiff line change
@@ -609,3 +609,20 @@ SELECT @c2 - @c1;
609609
@c2 - @c1
610610
0
611611
DROP TABLE t;
612+
#
613+
# Bug#22133710 GCOLS: READ UNCOMMITTED: ASSERT !TABLE || (!TABLE->WRITE_SET || BITMAP_IS_SET(TA
614+
#
615+
CREATE TABLE t (
616+
a INT,
617+
b INT GENERATED ALWAYS AS (1) VIRTUAL,
618+
c INT GENERATED ALWAYS AS (1) VIRTUAL,
619+
d INT GENERATED ALWAYS AS (1) VIRTUAL,
620+
KEY (b,d)
621+
) ENGINE=INNODB;
622+
INSERT INTO t VALUES();
623+
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED ;
624+
SELECT 1 FROM t WHERE c GROUP BY b;
625+
1
626+
1
627+
COMMIT;
628+
DROP TABLE t;

mysql-test/suite/gcol/t/gcol_bugfixes.test

+17
Original file line numberDiff line numberDiff line change
@@ -581,3 +581,20 @@ DO (SELECT (@c2:= c) - a FROM t);
581581
SELECT @c2 - @c1;
582582

583583
DROP TABLE t;
584+
585+
--echo #
586+
--echo # Bug#22133710 GCOLS: READ UNCOMMITTED: ASSERT !TABLE || (!TABLE->WRITE_SET || BITMAP_IS_SET(TA
587+
--echo #
588+
589+
CREATE TABLE t (
590+
a INT,
591+
b INT GENERATED ALWAYS AS (1) VIRTUAL,
592+
c INT GENERATED ALWAYS AS (1) VIRTUAL,
593+
d INT GENERATED ALWAYS AS (1) VIRTUAL,
594+
KEY (b,d)
595+
) ENGINE=INNODB;
596+
INSERT INTO t VALUES();
597+
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED ;
598+
SELECT 1 FROM t WHERE c GROUP BY b;
599+
COMMIT;
600+
DROP TABLE t;

sql/handler.cc

+16-11
Original file line numberDiff line numberDiff line change
@@ -8087,7 +8087,7 @@ static bool my_eval_gcolumn_expr_helper(THD *thd, TABLE *table,
80878087
repoint_field_to_record(table, old_buf, record);
80888088

80898089
blob_len_ptr blob_len_ptr_array[MAX_FIELDS];
8090-
8090+
80918091
/*
80928092
If it's purge thread, we need get the space allocated by storage engine
80938093
for blob.
@@ -8123,7 +8123,20 @@ static bool my_eval_gcolumn_expr_helper(THD *thd, TABLE *table,
81238123
/*
81248124
Evaluate all requested columns and all base columns these depends
81258125
on that are virtual.
8126+
8127+
This function is called by the storage engine, which may request to
8128+
evaluate more generated columns than read_set/write_set says.
8129+
For example, InnoDB's row_sel_sec_rec_is_for_clust_rec() reads the full
8130+
record from the clustered index and asks us to compute generated columns
8131+
that match key fields in the used secondary index. So we trust that the
8132+
engine has filled all base columns necessary to requested computations,
8133+
and we ignore read_set/write_set.
81268134
*/
8135+
8136+
my_bitmap_map *old_maps[2];
8137+
dbug_tmp_use_all_columns(table, old_maps,
8138+
table->read_set, table->write_set);
8139+
81278140
for (Field **vfield_ptr= table->vfield; *vfield_ptr; vfield_ptr++)
81288141
{
81298142
Field *field= *vfield_ptr;
@@ -8133,16 +8146,6 @@ static bool my_eval_gcolumn_expr_helper(THD *thd, TABLE *table,
81338146
field->is_virtual_gcol())
81348147
{
81358148
DBUG_ASSERT(field->gcol_info && field->gcol_info->expr_item->fixed);
8136-
if (in_purge)
8137-
{
8138-
/*
8139-
Adding to read_set/write_set is normally done up-front by high-level
8140-
layers before calling any handler's read/delete/etc functions.
8141-
But, for the specific case of the purge thread, it has no high-level
8142-
layer to manage read_set/write_set.
8143-
*/
8144-
table->mark_column_used(thd, field, MARK_COLUMNS_WRITE);
8145-
}
81468149

81478150
const type_conversion_status save_in_field_status=
81488151
field->gcol_info->expr_item->save_in_field(field, 0);
@@ -8163,6 +8166,8 @@ static bool my_eval_gcolumn_expr_helper(THD *thd, TABLE *table,
81638166
}
81648167
}
81658168

8169+
dbug_tmp_restore_column_maps(table->read_set, table->write_set, old_maps);
8170+
81668171
/*
81678172
If it's a purge thread, we need copy the blob data into specified place
81688173
allocated by storage engine so that the blob data still can be accessed

storage/innobase/handler/ha_innodb.cc

+4-16
Original file line numberDiff line numberDiff line change
@@ -19953,7 +19953,6 @@ innobase_init_vc_templ(
1995319953
@param[in,out] row the data row
1995419954
@param[in] col virtual column
1995519955
@param[in] index index
19956-
@param[in,out] my_rec mysql record to store the data
1995719956
@param[in,out] local_heap heap memory for processing large data etc.
1995819957
@param[in,out] heap memory heap that copies the actual index row
1995919958
@param[in] ifield index field
@@ -19965,7 +19964,6 @@ innobase_get_computed_value(
1996519964
const dtuple_t* row,
1996619965
const dict_v_col_t* col,
1996719966
const dict_index_t* index,
19968-
byte* my_rec,
1996919967
mem_heap_t** local_heap,
1997019968
mem_heap_t* heap,
1997119969
const dict_field_t* ifield,
@@ -19992,22 +19990,12 @@ innobase_get_computed_value(
1999219990
*local_heap = mem_heap_create(UNIV_PAGE_SIZE);
1999319991
}
1999419992

19995-
if (!my_rec) {
19996-
mysql_rec = static_cast<byte*>(mem_heap_alloc(
19997-
*local_heap, index->table->vc_templ->rec_len));
19998-
} else {
19999-
mysql_rec = my_rec;
20000-
}
20001-
19993+
mysql_rec = static_cast<byte*>(mem_heap_alloc(
19994+
*local_heap, index->table->vc_templ->rec_len));
2000219995
buf = static_cast<byte*>(mem_heap_alloc(
2000319996
*local_heap, index->table->vc_templ->rec_len));
2000419997
} else {
20005-
if (!my_rec) {
20006-
mysql_rec = rec_buf1;
20007-
} else {
20008-
mysql_rec = my_rec;
20009-
}
20010-
19998+
mysql_rec = rec_buf1;
2001119999
buf = rec_buf2;
2001220000
}
2001320001

@@ -20112,7 +20100,7 @@ innobase_get_computed_value(
2011220100
}
2011320101

2011420102
/* we just want to store the data in passed in MySQL record */
20115-
if (my_rec || ret != 0) {
20103+
if (ret != 0) {
2011620104
return(NULL);
2011720105
}
2011820106

storage/innobase/include/row0mysql.h

-2
Original file line numberDiff line numberDiff line change
@@ -914,7 +914,6 @@ struct SysIndexCallback {
914914
@param[in,out] row the data row
915915
@param[in] col virtual column
916916
@param[in] index index on the virtual column
917-
@param[in,out] my_rec MySQL record to store the rows
918917
@param[in,out] local_heap heap memory for processing large data etc.
919918
@param[in,out] heap memory heap that copies the actual index row
920919
@param[in] ifield index field
@@ -925,7 +924,6 @@ innobase_get_computed_value(
925924
const dtuple_t* row,
926925
const dict_v_col_t* col,
927926
const dict_index_t* index,
928-
byte* my_rec,
929927
mem_heap_t** local_heap,
930928
mem_heap_t* heap,
931929
const dict_field_t* ifield,

storage/innobase/row/row0merge.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -581,7 +581,7 @@ row_merge_buf_add(
581581
= dict_table_get_first_index(new_table);
582582

583583
row_field = innobase_get_computed_value(
584-
row, v_col, clust_index, NULL,
584+
row, v_col, clust_index,
585585
v_heap, NULL, ifield, false);
586586

587587
if (row_field == NULL) {

storage/innobase/row/row0sel.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ row_sel_sec_rec_is_for_clust_rec(
231231

232232
vfield = innobase_get_computed_value(
233233
row, v_col, clust_index,
234-
NULL, &heap, NULL, NULL, false);
234+
&heap, NULL, NULL, false);
235235

236236
clust_len = vfield->len;
237237
clust_field = static_cast<byte*>(vfield->data);

storage/innobase/row/row0upd.cc

+2-2
Original file line numberDiff line numberDiff line change
@@ -928,7 +928,7 @@ row_upd_build_difference_binary(
928928
dfield = dtuple_get_nth_v_field(entry, i);
929929

930930
dfield_t* vfield = innobase_get_computed_value(
931-
update->old_vrow, col, index, NULL,
931+
update->old_vrow, col, index,
932932
&v_heap, heap, NULL, false);
933933

934934
if (!dfield_data_is_binary_equal(
@@ -1929,7 +1929,7 @@ row_upd_store_v_row(
19291929
/* Need to compute, this happens when
19301930
deleting row */
19311931
innobase_get_computed_value(
1932-
node->row, col, index, NULL,
1932+
node->row, col, index,
19331933
&heap, node->heap, NULL,
19341934
false);
19351935
}

storage/innobase/row/row0vers.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,7 @@ row_vers_build_clust_v_col(
492492
ind_field->col);
493493

494494
innobase_get_computed_value(
495-
row, col, clust_index, NULL, &local_heap,
495+
row, col, clust_index, &local_heap,
496496
heap, NULL, true);
497497
}
498498
}

0 commit comments

Comments
 (0)