Skip to content

Commit f97c71a

Browse files
author
Andrzej Jarzabek
committed
Bug#37602657 virtual index corruption
Problem: In some scenarios fields in secondary indexes based on virtual columns become corrupted with random or invalid values. Cause: When an inplace update happens to a secondary index keyed on an virtual column, the format of the update vector is confused - the field_no member contains the actual field number, but some code wants it to be the virtual column number. The alternative meaning of field_no makes sense for clustered index, which does not have fields for virtual columns. When for various reasons we need the update vector to contain information about updates to virtual column values for the row, we create "virtual field updates" which do not actually update any fields. Fix: "Virtual field updates" with alternative semantics for field_no should only be used for clustered index update vector. For updates to materialized virtual column values in secondary indexes, we treat them as regular field updates. Change-Id: I6574d5df9ad0f6c527a8e020cb1eb44a87f88f2f
1 parent 9c4b5d6 commit f97c71a

File tree

14 files changed

+153
-119
lines changed

14 files changed

+153
-119
lines changed

storage/innobase/btr/btr0cur.cc

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3137,6 +3137,7 @@ void btr_cur_update_in_place_log(ulint flags, const rec_t *rec,
31373137
ut_d(const page_t *page = page_align(rec));
31383138
ut_ad(flags < 256);
31393139
ut_ad(page_is_comp(page) == dict_table_is_comp(index->table));
3140+
ut_d(update->validate_for_index(index));
31403141

31413142
const bool opened = mlog_open_and_write_index(
31423143
mtr, rec, index, MLOG_REC_UPDATE_IN_PLACE,
@@ -3220,7 +3221,7 @@ byte *btr_cur_parse_update_in_place(
32203221

32213222
heap = mem_heap_create(256, UT_LOCATION_HERE);
32223223

3223-
ptr = row_upd_index_parse(ptr, end_ptr, heap, &update);
3224+
ptr = row_upd_index_parse(ptr, end_ptr, heap, &update, index);
32243225

32253226
if (!ptr || !page) {
32263227
goto func_exit;
@@ -3527,6 +3528,8 @@ dberr_t btr_cur_optimistic_update(ulint flags, btr_cur_t *cursor,
35273528
thr_get_trx(thr)->id == trx_id);
35283529
ut_ad(fil_page_index_page_check(page));
35293530
ut_ad(btr_page_get_index_id(page) == index->id);
3531+
ut_ad(update);
3532+
ut_d(update->validate_for_index(index));
35303533

35313534
DBUG_EXECUTE_IF("DB_ZIP_OVERFLOW_on_btr_cur_optimistic_update",
35323535
return (DB_ZIP_OVERFLOW););
@@ -3724,7 +3727,8 @@ dberr_t btr_cur_optimistic_update(ulint flags, btr_cur_t *cursor,
37243727
btr_cur_prefetch_siblings(block);
37253728
}
37263729

3727-
return (err);
3730+
ut_d(update->validate_for_index(index));
3731+
return err;
37283732
}
37293733

37303734
/** If, in a split, a new supremum record was created as the predecessor of the
@@ -3816,6 +3820,7 @@ dberr_t btr_cur_pessimistic_update(ulint flags, btr_cur_t *cursor,
38163820
(BTR_NO_UNDO_LOG_FLAG | BTR_NO_LOCKING_FLAG | BTR_CREATE_FLAG |
38173821
BTR_KEEP_SYS_FLAG) ||
38183822
thr_get_trx(thr)->id == trx_id);
3823+
ut_d(update->validate_for_index(index));
38193824

38203825
err = optim_err = btr_cur_optimistic_update(
38213826
flags | BTR_KEEP_IBUF_BITMAP, cursor, offsets, offsets_heap, update,
@@ -4161,6 +4166,7 @@ dberr_t btr_cur_pessimistic_update(ulint flags, btr_cur_t *cursor,
41614166

41624167
*big_rec = big_rec_vec;
41634168

4169+
ut_d(update->validate_for_index(index));
41644170
return err;
41654171
}
41664172

storage/innobase/data/data0data.cc

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,32 @@ std::ostream &big_rec_t::print(std::ostream &out) const {
779779
out << "]";
780780
return (out);
781781
}
782+
783+
void dtuple_t::validate() const {
784+
for (uint16_t i = 0; i < n_fields; i++) {
785+
const dfield_t &field = fields[i];
786+
/* All fields in the fields vector represent actual fields in the
787+
record, so their DATA_VIRTUAL flag is cleared regardless of
788+
whether they are defined for virtual columns. */
789+
ut_a((field.type.prtype & DATA_VIRTUAL) == 0U);
790+
}
791+
for (uint16_t i = 0; i < n_v_fields; i++) {
792+
const dfield_t &field = v_fields[i];
793+
/* All fields in the v_fields vector represent fields not present
794+
in the record, and as such have their DATA_VIRTUAL flag set. */
795+
ut_a((field.type.prtype & DATA_VIRTUAL) == DATA_VIRTUAL);
796+
}
797+
}
798+
799+
void dtuple_t::validate_for_index(const dict_index_t *index) const {
800+
validate();
801+
if (!index->is_clustered()) {
802+
/* Only tuples for clustered index may have virtual fields.
803+
Secondary index fields based on virtual columns are not
804+
considered virtual. */
805+
ut_a(n_v_fields == 0);
806+
}
807+
}
782808
#endif /* UNIV_DEBUG */
783809

784810
trx_id_t dtuple_t::get_trx_id() const {

storage/innobase/dict/dict0dict.cc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2893,7 +2893,10 @@ void dict_index_copy_types(dtuple_t *tuple, const dict_index_t *index,
28932893

28942894
ifield = index->get_field(i);
28952895
dfield_type = dfield_get_type(dtuple_get_nth_field(tuple, i));
2896+
ut_ad(!ifield->col->is_virtual() || !index->is_clustered());
28962897
ifield->col->copy_type(dfield_type);
2898+
/* Field for materialized virtual column is not itself virtual. */
2899+
dfield_type->prtype &= ~DATA_VIRTUAL;
28972900
if (dict_index_is_spatial(index) &&
28982901
DATA_GEOMETRY_MTYPE(dfield_type->mtype)) {
28992902
dfield_type->prtype |= DATA_GIS_MBR;
@@ -5296,7 +5299,7 @@ upd_t *DDTableBuffer::update_set_metadata(const dtuple_t *entry,
52965299
dfield_copy(&upd_field->new_val, metadata_dfield);
52975300
upd_field_set_field_no(upd_field, METADATA_FIELD_NO, m_index);
52985301

5299-
ut_ad(update->validate());
5302+
ut_d(update->validate());
53005303

53015304
return (update);
53025305
}

storage/innobase/handler/ha_innodb.cc

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9315,10 +9315,11 @@ static void innobase_get_multi_value_and_diff(
93159315
}
93169316

93179317
/** Checks which fields have changed in a row and stores information
9318-
of them to an update vector.
9318+
of them to an update vector for the table's clustered index.
93199319
@return DB_SUCCESS or error code */
93209320
static dberr_t calc_row_difference(
9321-
upd_t *uvect, /*!< in/out: update vector */
9321+
upd_t *uvect, /*!< in/out: update vector for the
9322+
clustered index */
93229323
const uchar *old_row, /*!< in: old row in MySQL format */
93239324
uchar *new_row, /*!< in: new row in MySQL format */
93249325
TABLE *table, /*!< in: table in MySQL data
@@ -9701,7 +9702,7 @@ static dberr_t calc_row_difference(
97019702
if (changes_fts_column && !changes_fts_doc_col) {
97029703
ib::warn(ER_IB_MSG_559) << "A new Doc ID must be supplied"
97039704
" while updating FTS indexed columns.";
9704-
return (DB_FTS_INVALID_DOCID);
9705+
return DB_FTS_INVALID_DOCID;
97059706
}
97069707

97079708
/* Doc ID must monotonically increase */
@@ -9711,7 +9712,7 @@ static dberr_t calc_row_difference(
97119712
<< innodb_table->fts->cache->next_doc_id - 1
97129713
<< " for table " << innodb_table->name;
97139714

9714-
return (DB_FTS_INVALID_DOCID);
9715+
return DB_FTS_INVALID_DOCID;
97159716
} else if ((doc_id - prebuilt->table->fts->cache->next_doc_id) >=
97169717
FTS_DOC_ID_MAX_STEP) {
97179718
ib::warn(ER_IB_MSG_561)
@@ -9750,8 +9751,8 @@ static dberr_t calc_row_difference(
97509751

97519752
ut_a(buf <= (byte *)original_upd_buff + buff_len);
97529753

9753-
ut_ad(uvect->validate());
9754-
return (DB_SUCCESS);
9754+
ut_d(uvect->validate_for_index(clust_index));
9755+
return DB_SUCCESS;
97559756
}
97569757

97579758
/**

storage/innobase/ibuf/ibuf0ibuf.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3505,6 +3505,7 @@ static void ibuf_insert_to_index_page(
35053505
ut_ad(!dict_index_is_online_ddl(index)); // this is an ibuf_dummy index
35063506
ut_ad(ibuf_inside(mtr));
35073507
ut_ad(dtuple_check_typed(entry));
3508+
ut_d(entry->validate_for_index(index));
35083509
/* A change buffer merge must occur before users are granted
35093510
any access to the page. No adaptive hash index entries may
35103511
point to a freshly read page. */

storage/innobase/include/data0data.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -793,6 +793,11 @@ struct dtuple_t {
793793
}
794794
return false;
795795
}
796+
797+
#ifdef UNIV_DEBUG
798+
void validate() const;
799+
void validate_for_index(const dict_index_t *index) const;
800+
#endif /* UNIV_DEBUG */
796801
};
797802

798803
/** A slot for a field in a big rec vector */

storage/innobase/include/dict0mem.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -776,7 +776,7 @@ struct dict_col_t {
776776
ut_ad(type);
777777

778778
ut_ad(mtype == type->mtype);
779-
ut_ad(prtype == type->prtype);
779+
ut_ad((prtype | DATA_VIRTUAL) == (type->prtype | DATA_VIRTUAL));
780780
// ut_ad(col->len == type->len);
781781
#ifndef UNIV_HOTBACKUP
782782
ut_ad(mbminmaxlen == type->mbminmaxlen);

storage/innobase/include/row0upd.h

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -298,11 +298,11 @@ static inline bool row_upd_changes_ord_field_binary(
298298
}
299299

300300
/** Checks if an FTS indexed column is affected by an UPDATE.
301+
@param[in] table table
302+
@param[in] upd_field clustered index field update
301303
@return offset within fts_t::indexes if FTS indexed column updated else
302304
ULINT_UNDEFINED */
303-
ulint row_upd_changes_fts_column(
304-
dict_table_t *table, /*!< in: table */
305-
upd_field_t *upd_field); /*!< in: field to check */
305+
ulint row_upd_changes_fts_column(dict_table_t *table, upd_field_t *upd_field);
306306
/** Checks if an FTS Doc ID column is affected by an UPDATE.
307307
@return whether Doc ID column is affected */
308308
[[nodiscard]] bool row_upd_changes_doc_id(
@@ -350,12 +350,15 @@ void row_upd_rec_sys_fields_in_recovery(rec_t *rec, page_zip_des_t *page_zip,
350350
trx_id_t trx_id, roll_ptr_t roll_ptr);
351351

352352
/** Parses the log data written by row_upd_index_write_log.
353+
@param[in] ptr buffer
354+
@param[in] end_ptr buffer end
355+
@param[in] heap memory heap where update vector is built
356+
@param[out] update_out update vector
357+
@param[in] index the index corresponding to the update
353358
@return log data end or NULL */
354-
byte *row_upd_index_parse(const byte *ptr, /*!< in: buffer */
355-
const byte *end_ptr, /*!< in: buffer end */
356-
mem_heap_t *heap, /*!< in: memory heap where update
357-
vector is built */
358-
upd_t **update_out); /*!< out: update vector */
359+
byte *row_upd_index_parse(const byte *ptr, const byte *end_ptr,
360+
mem_heap_t *heap, upd_t **update_out,
361+
dict_index_t *index);
359362

360363
/** Get the new autoinc counter from the update vector when there is
361364
an autoinc field defined in this table.
@@ -554,11 +557,6 @@ inline std::ostream &operator<<(std::ostream &out, const upd_field_t &obj) {
554557
return (obj.print(out));
555558
}
556559

557-
/* check whether an update field is on virtual column */
558-
inline bool upd_fld_is_virtual_col(const upd_field_t *upd_fld) {
559-
return upd_fld->is_virtual();
560-
}
561-
562560
/* check whether an update field is on multi-value virtual column */
563561
inline bool upd_fld_is_multi_value_col(const upd_field_t *upd_fld) {
564562
return dfield_is_multi_value(&upd_fld->new_val);
@@ -626,14 +624,26 @@ struct upd_t {
626624
}
627625

628626
#ifdef UNIV_DEBUG
629-
bool validate() const {
627+
void validate() const {
630628
for (ulint i = 0; i < n_fields; ++i) {
631629
dfield_t *field = &fields[i].new_val;
632630
if (dfield_is_ext(field)) {
633631
ut_ad(dfield_get_len(field) >= BTR_EXTERN_FIELD_REF_SIZE);
634632
}
635633
}
636-
return (true);
634+
}
635+
636+
void validate_for_index(const dict_index_t *index) const {
637+
validate();
638+
for (ulint i = 0; i < n_fields; ++i) {
639+
const upd_field_t &field = fields[i];
640+
ut_a(index->is_clustered() || !field.is_virtual());
641+
if (!field.is_virtual()) {
642+
ut_a(field.field_no < dict_index_get_n_fields(index));
643+
} else {
644+
ut_a(field.field_no < index->table->n_v_cols);
645+
}
646+
}
637647
}
638648
#endif // UNIV_DEBUG
639649

storage/innobase/include/row0upd.ic

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,6 @@ inline void upd_field_set_field_no(upd_field_t *upd_field, ulint field_no,
9393
<< index->n_fields << " fields";
9494
ut_d(ut_error);
9595
}
96-
97-
index->get_col(field_no)->copy_type(dfield_get_type(&upd_field->new_val));
9896
}
9997

10098
inline void upd_field_set_v_field_no(upd_field_t *upd_field, ulint field_no,
@@ -121,7 +119,7 @@ inline const upd_field_t *upd_get_field_by_field_no(const upd_t *update,
121119
const upd_field_t *uf = upd_get_nth_field(update, i);
122120

123121
/* matches only if the field matches that of is_virtual */
124-
if ((!is_virtual) != (!upd_fld_is_virtual_col(uf))) {
122+
if ((!is_virtual) != (!uf->is_virtual())) {
125123
continue;
126124
}
127125

storage/innobase/pars/pars0pars.cc

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,12 +1047,13 @@ static void pars_process_assign_list(upd_node_t *node) /*!< in: update node */
10471047

10481048
col_sym = assign_node->col;
10491049

1050-
upd_field_set_field_no(upd_field, clust_index->get_col_pos(col_sym->col_no),
1050+
const dict_col_t *const col = node->table->get_col(col_sym->col_no);
1051+
upd_field_set_field_no(upd_field, dict_col_get_clust_pos(col, clust_index),
10511052
clust_index);
10521053
upd_field->exp = assign_node->val;
1054+
col->copy_type(dfield_get_type(&upd_field->new_val));
10531055

1054-
if (!clust_index->get_col(upd_field->field_no)
1055-
->get_fixed_size(dict_table_is_comp(node->table))) {
1056+
if (!col->get_fixed_size(dict_table_is_comp(node->table))) {
10561057
changes_field_size = 0;
10571058
}
10581059

0 commit comments

Comments
 (0)