Skip to content

Commit fc4b73f

Browse files
author
Andrzej Jarzabek
committed
Bug#37478594 Virtual column value may be incorrectly set to NULL on cascade update
Problem: When an ON DELETE SET NULL cascade updates a row in a child table, setting to NULL all the columns on which a virtual column is based, one of 2 things may happen: 1. the virtual field is also set to NULL in the update, instead of being re-calculated. 2. The value of the virtual field is not re-calculated at all. This impacts indexes based on the virtual field, leading to index corruption. Root causes: For both ON DELETE SET NULL and ON UPDATE SET NULL actions the virtual field calculation attempts to use parent table update vector to detrmine which base fields should be treated as NULL for the purpose of the calculation. However, in the ON DELETE SET NULL scenario the parent row is deleted and consequently there is no meaningful update vector for it. Additionally, the scenario where the virtual column is based on all the columns that comprise the FK is treated as special, somewhat hiding the problem - the virtual field is "calculated" to NULL without actually calculating the underlying expression. If the expression is such that its value is NULL if all column operands are NULL, this happens to be correct - but obviously this not true for all expressions. Fix: When virtual fields are calculated as part of a FOREIGN KEY action, all the updates for the non-virtual columns of the child table are already known at that point. Instead of looking up updates to the parent table, these updates may be used directly by the calculation. This may be applied uniformly to all FK update actions: ON DELETE SET NULL, ON UPDATE CASCADE and ON UPDATE SET NULL. Change-Id: I6ee5c0502dc397276edac09513d0e2f27387aeaf
1 parent 5951f03 commit fc4b73f

25 files changed

+375
-382
lines changed

storage/innobase/btr/btr0sea.cc

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -417,8 +417,7 @@ static void btr_search_info_update_hash(btr_cur_t *cursor) {
417417
return;
418418
}
419419

420-
const uint16_t n_unique =
421-
static_cast<uint16_t>(dict_index_get_n_unique_in_tree(index));
420+
const uint16_t n_unique = dict_index_get_n_unique_in_tree(index);
422421
const auto info = index->search_info;
423422
if (info->n_hash_potential != 0) {
424423
const auto prefix_info = info->prefix_info.load();

storage/innobase/data/data0data.cc

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,7 @@ big_rec_t *dtuple_convert_big_rec(dict_index_t *index, upd_t *upd,
474474
byte *data;
475475
ulint longest = 0;
476476
ulint longest_i = ULINT_MAX;
477-
upd_field_t *uf = nullptr;
477+
const upd_field_t *uf = nullptr;
478478

479479
for (ulint i = dict_index_get_n_unique_in_tree(index);
480480
i < dtuple_get_n_fields(entry); i++) {
@@ -606,7 +606,8 @@ big_rec_t *dtuple_convert_big_rec(dict_index_t *index, upd_t *upd,
606606
if (upd == nullptr) {
607607
big_rec.ext_in_old = false;
608608
} else {
609-
upd_field_t *uf = upd->get_field_by_field_no(longest_i, index);
609+
const upd_field_t *const uf =
610+
upd->get_field_by_field_no(longest_i, index);
610611
ut_ad(uf != nullptr);
611612
big_rec.ext_in_old = uf->ext_in_old;
612613
}
@@ -657,7 +658,7 @@ big_rec_t *big_rec_t::alloc(mem_heap_t *heap, ulint n_fld) {
657658
mem_heap_alloc(heap, n_fld * sizeof(big_rec_field_t)));
658659

659660
rec->n_fields = 0;
660-
return (rec);
661+
return rec;
661662
}
662663

663664
dfield_t *dfield_t::clone(mem_heap_t *heap) {
@@ -677,13 +678,13 @@ dfield_t *dfield_t::clone(mem_heap_t *heap) {
677678
obj->data = nullptr;
678679
}
679680

680-
return (obj);
681+
return obj;
681682
}
682683

683684
byte *dfield_t::blobref() const {
684685
ut_ad(ext);
685686

686-
return (static_cast<byte *>(data) + len - BTR_EXTERN_FIELD_REF_SIZE);
687+
return static_cast<byte *>(data) + len - BTR_EXTERN_FIELD_REF_SIZE;
687688
}
688689

689690
uint32_t dfield_t::lob_version() const {

storage/innobase/ddl/ddl0builder.cc

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -793,7 +793,6 @@ dberr_t Builder::get_virtual_column(Copy_ctx &ctx, const dict_field_t *ifield,
793793
size_t &mv_rows_added) noexcept {
794794
const auto n_added = mv_rows_added;
795795
auto v_col = reinterpret_cast<const dict_v_col_t *>(col);
796-
const auto clust_index = m_ctx.m_new_table->first_index();
797796
auto key_buffer = m_thread_ctxs[ctx.m_thread_id]->m_key_buffer;
798797

799798
if (col->is_multi_value()) {
@@ -807,8 +806,8 @@ dberr_t Builder::get_virtual_column(Copy_ctx &ctx, const dict_field_t *ifield,
807806
auto p = m_v_heap.get();
808807

809808
src_field = innobase_get_computed_value(
810-
ctx.m_row.m_ptr, v_col, clust_index, &p, key_buffer->heap(), ifield,
811-
m_ctx.thd(), ctx.m_my_table, m_ctx.m_old_table, nullptr, nullptr);
809+
ctx.m_row.m_ptr, v_col, m_ctx.m_new_table, &p, key_buffer->heap(),
810+
m_ctx.thd(), ctx.m_my_table, ifield, m_ctx.m_old_table);
812811

813812
m_v_heap.reset(p);
814813

@@ -838,8 +837,8 @@ dberr_t Builder::get_virtual_column(Copy_ctx &ctx, const dict_field_t *ifield,
838837
auto p = m_v_heap.get();
839838

840839
src_field = innobase_get_computed_value(
841-
ctx.m_row.m_ptr, v_col, clust_index, &p, nullptr, ifield, m_ctx.thd(),
842-
ctx.m_my_table, m_ctx.m_old_table, nullptr, nullptr);
840+
ctx.m_row.m_ptr, v_col, m_ctx.m_new_table, &p, nullptr, m_ctx.thd(),
841+
ctx.m_my_table, ifield, m_ctx.m_old_table);
843842

844843
m_v_heap.reset(p);
845844

storage/innobase/dict/dict0dict.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2406,9 +2406,9 @@ static void dict_index_try_cache_rec_offsets(dict_index_t *index) {
24062406
This is not an assert crucial for correctness. It's just to show that there's
24072407
no obvious regression w.r.t intrinsic tables. */
24082408
if (index->table->is_intrinsic() && index->n_uniq != n_unique_in_tree) {
2409-
ut_a(index->n_uniq == n_unique_in_tree - 1);
2409+
ut_a(index->n_uniq == n_unique_in_tree - 1U);
24102410
ut_a(!index->is_clustered());
2411-
ut_a(index->get_field(n_unique_in_tree - 1)->fixed_len);
2411+
ut_a(index->get_field(n_unique_in_tree - 1U)->fixed_len);
24122412
}
24132413
#endif
24142414
for (size_t i = 0; i < n_unique_in_tree; i++) {

storage/innobase/handler/ha_innodb.cc

Lines changed: 40 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -23551,53 +23551,32 @@ void innobase_rename_vc_templ(dict_table_t *table) {
2355123551
table->vc_templ->tb_name.assign(table_name);
2355223552
}
2355323553

23554-
dfield_t *innobase_get_field_from_update_vector(dict_foreign_t *foreign,
23555-
upd_t *update,
23556-
uint32_t col_no) {
23557-
dict_table_t *parent_table = foreign->referenced_table;
23558-
dict_index_t *parent_index = foreign->referenced_index;
23559-
uint32_t parent_field_no;
23560-
uint32_t parent_col_no;
23561-
uint32_t child_col_no;
23562-
23563-
for (uint32_t i = 0; i < foreign->n_fields; i++) {
23564-
child_col_no = foreign->foreign_index->get_col_no(i);
23565-
if (child_col_no != col_no) {
23566-
continue;
23567-
}
23568-
parent_col_no = parent_index->get_col_no(i);
23569-
parent_field_no = dict_table_get_nth_col_pos(parent_table, parent_col_no);
23570-
for (uint32_t j = 0; j < update->n_fields; j++) {
23571-
upd_field_t *parent_ufield = &update->fields[j];
23572-
if (parent_ufield->field_no == parent_field_no) {
23573-
return (&parent_ufield->new_val);
23574-
}
23554+
/** Get the updated field value from an update vector for the given column
23555+
number.
23556+
@param[in] update the update vector for the table's clustered index
23557+
@param[in] col_no the number of the non-virtual column in the table
23558+
@return the field's new value if it's updated, otherwise nullptr */
23559+
static const dfield_t *innobase_get_field_from_update_vector(
23560+
const upd_t *update, uint32_t col_no) {
23561+
const dict_table_t *const table = update->table;
23562+
const dict_index_t *const clustered_index = table->first_index();
23563+
const auto index_field_pos = clustered_index->get_col_pos(col_no);
23564+
23565+
for (uint32_t j = 0; j < update->n_fields; j++) {
23566+
const upd_field_t *const ufield = &update->fields[j];
23567+
if (ufield->field_no == index_field_pos) {
23568+
return &ufield->new_val;
2357523569
}
2357623570
}
2357723571

2357823572
return (nullptr);
2357923573
}
2358023574

23581-
/** Get the computed value by supplying the base column values.
23582-
@param[in,out] row the data row
23583-
@param[in] col virtual column
23584-
@param[in] index index on the virtual column
23585-
@param[in,out] local_heap heap memory for processing large data etc.
23586-
@param[in,out] heap memory heap that copies the actual index row
23587-
@param[in] ifield index field
23588-
@param[in] thd MySQL thread handle
23589-
@param[in,out] mysql_table mysql table object
23590-
@param[in] old_table during ALTER TABLE, this is the old table
23591-
or NULL.
23592-
@param[in] parent_update update vector for the parent row
23593-
@param[in] foreign foreign key information
23594-
@return the field filled with computed value, or NULL if just want
23595-
to store the value in passed in "my_rec" */
2359623575
dfield_t *innobase_get_computed_value(
23597-
const dtuple_t *row, const dict_v_col_t *col, const dict_index_t *index,
23598-
mem_heap_t **local_heap, mem_heap_t *heap, const dict_field_t *ifield,
23599-
THD *thd, TABLE *mysql_table, const dict_table_t *old_table,
23600-
upd_t *parent_update, dict_foreign_t *foreign) {
23576+
const dtuple_t *row, const dict_v_col_t *col, const dict_table_t *table,
23577+
mem_heap_t **local_heap, mem_heap_t *heap, THD *thd, TABLE *mysql_table,
23578+
const dict_field_t *ifield, const dict_table_t *old_table,
23579+
upd_t *row_update) {
2360123580
byte rec_buf1[REC_VERSION_56_MAX_INDEX_COL_LEN];
2360223581
byte rec_buf2[REC_VERSION_56_MAX_INDEX_COL_LEN];
2360323582
byte *mysql_rec;
@@ -23607,36 +23586,27 @@ dfield_t *innobase_get_computed_value(
2360723586
ulong mv_length = 0;
2360823587
const char *mv_data_ptr = nullptr;
2360923588

23610-
const page_size_t page_size = (old_table == nullptr)
23611-
? dict_table_page_size(index->table)
23612-
: dict_table_page_size(old_table);
23613-
23614-
const dict_index_t *clust_index = nullptr;
23615-
if (old_table == nullptr) {
23616-
clust_index = index->table->first_index();
23617-
} else {
23618-
clust_index = old_table->first_index();
23619-
}
23589+
/* table definition to use for externally stored fields */
23590+
const dict_table_t *const ext_src_table = (old_table ? old_table : table);
23591+
const page_size_t page_size = dict_table_page_size(ext_src_table);
2362023592

2362123593
ulint ret = 0;
2362223594

23623-
ut_ad(index->table->vc_templ);
23595+
ut_ad(table->vc_templ);
2362423596
ut_ad(thd != nullptr);
2362523597

2362623598
const mysql_row_templ_t *vctempl =
23627-
index->table->vc_templ
23628-
->vtempl[index->table->vc_templ->n_col + col->v_pos];
23599+
table->vc_templ->vtempl[table->vc_templ->n_col + col->v_pos];
2362923600

23630-
if (!heap ||
23631-
index->table->vc_templ->rec_len >= REC_VERSION_56_MAX_INDEX_COL_LEN) {
23601+
if (!heap || table->vc_templ->rec_len >= REC_VERSION_56_MAX_INDEX_COL_LEN) {
2363223602
if (*local_heap == nullptr) {
2363323603
*local_heap = mem_heap_create(UNIV_PAGE_SIZE, UT_LOCATION_HERE);
2363423604
}
2363523605

2363623606
mysql_rec = static_cast<byte *>(
23637-
mem_heap_alloc(*local_heap, index->table->vc_templ->rec_len));
23607+
mem_heap_alloc(*local_heap, table->vc_templ->rec_len));
2363823608
buf = static_cast<byte *>(
23639-
mem_heap_alloc(*local_heap, index->table->vc_templ->rec_len));
23609+
mem_heap_alloc(*local_heap, table->vc_templ->rec_len));
2364023610
} else {
2364123611
mysql_rec = rec_buf1;
2364223612
buf = rec_buf2;
@@ -23646,12 +23616,11 @@ dfield_t *innobase_get_computed_value(
2364623616
dict_col_t *base_col = col->base_col[i];
2364723617
const dfield_t *row_field = nullptr;
2364823618
uint32_t col_no = base_col->ind;
23649-
const mysql_row_templ_t *templ = index->table->vc_templ->vtempl[col_no];
23619+
const mysql_row_templ_t *const templ = table->vc_templ->vtempl[col_no];
2365023620
const byte *data;
2365123621

23652-
if (parent_update != nullptr) {
23653-
row_field =
23654-
innobase_get_field_from_update_vector(foreign, parent_update, col_no);
23622+
if (row_update != nullptr) {
23623+
row_field = innobase_get_field_from_update_vector(row_update, col_no);
2365523624
}
2365623625

2365723626
if (row_field == nullptr) {
@@ -23667,20 +23636,20 @@ dfield_t *innobase_get_computed_value(
2366723636
}
2366823637

2366923638
data = lob::btr_copy_externally_stored_field(
23670-
thd_to_trx(thd), clust_index, &len, nullptr, data, page_size,
23671-
dfield_get_len(row_field), false, *local_heap);
23639+
thd_to_trx(thd), ext_src_table->first_index(), &len, nullptr, data,
23640+
page_size, dfield_get_len(row_field), false, *local_heap);
2367223641
}
2367323642

2367423643
if (len == UNIV_SQL_NULL) {
2367523644
mysql_rec[templ->mysql_null_byte_offset] |=
2367623645
(byte)templ->mysql_null_bit_mask;
2367723646
memcpy(mysql_rec + templ->mysql_col_offset,
23678-
static_cast<const byte *>(index->table->vc_templ->default_rec +
23647+
static_cast<const byte *>(table->vc_templ->default_rec +
2367923648
templ->mysql_col_offset),
2368023649
templ->mysql_col_len);
2368123650
} else {
2368223651
row_sel_field_store_in_mysql_format(
23683-
mysql_rec + templ->mysql_col_offset, templ, index,
23652+
mysql_rec + templ->mysql_col_offset, templ, table->first_index(),
2368423653
templ->clust_rec_field_no, (const byte *)data, len, ULINT_UNDEFINED);
2368523654

2368623655
if (templ->mysql_null_bit_mask) {
@@ -23715,7 +23684,7 @@ dfield_t *innobase_get_computed_value(
2371523684
only 1 byte, other BLOBs won't be affected */
2371623685
max_len = 255;
2371723686
} else {
23718-
max_len = DICT_MAX_FIELD_LEN_BY_FORMAT(index->table) + 1;
23687+
max_len = DICT_MAX_FIELD_LEN_BY_FORMAT(table) + 1;
2371923688
}
2372023689

2372123690
byte *blob_mem = static_cast<byte *>(mem_heap_alloc(heap, max_len));
@@ -23725,8 +23694,8 @@ dfield_t *innobase_get_computed_value(
2372523694
}
2372623695

2372723696
/* open a temporary table handle */
23728-
mysql_table = tblhdl.open(thd, index->table->vc_templ->db_name.c_str(),
23729-
index->table->vc_templ->tb_name.c_str());
23697+
mysql_table = tblhdl.open(thd, table->vc_templ->db_name.c_str(),
23698+
table->vc_templ->tb_name.c_str());
2373023699
}
2373123700
if (mysql_table) {
2373223701
ret = handler::my_eval_gcolumn_expr(
@@ -23763,8 +23732,8 @@ dfield_t *innobase_get_computed_value(
2376323732
json_binary::Value v(json_binary::parse_binary(mv_data_ptr, mv_length));
2376423733
multi_value_data *value = nullptr;
2376523734

23766-
bool succ = innobase_store_multi_value(
23767-
v, value, fld, field, dict_table_is_comp(index->table), heap);
23735+
bool succ = innobase_store_multi_value(v, value, fld, field,
23736+
dict_table_is_comp(table), heap);
2376823737
if (!succ) {
2376923738
ut_error;
2377023739
}
@@ -23773,7 +23742,7 @@ dfield_t *innobase_get_computed_value(
2377323742
} else {
2377423743
row_mysql_store_col_in_innobase_format(
2377523744
field, buf, true, mysql_rec + vctempl->mysql_col_offset,
23776-
vctempl->mysql_col_len, dict_table_is_comp(index->table));
23745+
vctempl->mysql_col_len, dict_table_is_comp(table));
2377723746
}
2377823747
field->type.prtype |= DATA_VIRTUAL;
2377923748

storage/innobase/include/data0data.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ If len>0, tests the first len bytes of the content for equality.
157157
@return true if both fields are NULL or if they are equal */
158158
[[nodiscard]] inline bool dfield_datas_are_binary_equal(const dfield_t *field1,
159159
const dfield_t *field2,
160-
ulint len);
160+
ulint len = 0);
161161
/** Tests if dfield data length and content is equal to the given.
162162
@param[in] field Field
163163
@param[in] len Data length or UNIV_SQL_NULL

storage/innobase/include/dict0dict.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -693,7 +693,7 @@ added column.
693693
/** Gets the number of fields in the internal representation of an index,
694694
including fields added by the dictionary system.
695695
@return number of fields */
696-
[[nodiscard]] static inline ulint dict_index_get_n_fields(
696+
[[nodiscard]] inline uint16_t dict_index_get_n_fields(
697697
const dict_index_t *index); /*!< in: an internal
698698
representation of index (in
699699
the dictionary cache) */
@@ -702,14 +702,14 @@ added column.
702702
we do not take multiversioning into account: in the B-tree use the value
703703
returned by dict_index_get_n_unique_in_tree.
704704
@return number of fields */
705-
[[nodiscard]] static inline ulint dict_index_get_n_unique(
705+
[[nodiscard]] inline uint16_t dict_index_get_n_unique(
706706
const dict_index_t *index); /*!< in: an internal representation
707707
of index (in the dictionary cache) */
708708
/** Gets the number of fields in the internal representation of an index
709709
which uniquely determine the position of an index entry in the index, if
710710
we also take multiversioning into account.
711711
@return number of fields */
712-
[[nodiscard]] static inline ulint dict_index_get_n_unique_in_tree(
712+
[[nodiscard]] inline uint16_t dict_index_get_n_unique_in_tree(
713713
const dict_index_t *index); /*!< in: an internal representation
714714
of index (in the dictionary cache) */
715715

@@ -723,14 +723,14 @@ index, if we also take multiversioning into account. Note, it doesn't
723723
include page no field.
724724
@param[in] index index
725725
@return number of fields */
726-
[[nodiscard]] static inline uint16_t dict_index_get_n_unique_in_tree_nonleaf(
726+
[[nodiscard]] inline uint16_t dict_index_get_n_unique_in_tree_nonleaf(
727727
const dict_index_t *index);
728728
/** Gets the number of user-defined ordering fields in the index. In the
729729
internal representation we add the row id to the ordering fields to make all
730730
indexes unique, but this function returns the number of fields the user defined
731731
in the index as ordering fields.
732732
@return number of fields */
733-
[[nodiscard]] static inline ulint dict_index_get_n_ordering_defined_by_user(
733+
[[nodiscard]] inline ulint dict_index_get_n_ordering_defined_by_user(
734734
const dict_index_t *index); /*!< in: an internal representation
735735
of index (in the dictionary cache) */
736736
/** Returns true if the index contains a column or a prefix of that column.

0 commit comments

Comments
 (0)