Skip to content

Commit 27130e2

Browse files
Bug #33053297 VIRTUAL INDEX CORRUPTED DURING CASCADE UPDATE ON CHILD
TABLE Problem: Child table has FOREIGN KEY (FK) constraint with referential action ON DELETE SET NULL (or ON UPDATE SET NULL). And child table also has an index over (FK column, virtual column). When a row is deleted from parent table, FK constraint check on child table, set both [FK column, virtual column] value in the index to NULL. Fix: Instead of setting virtual column value to NULL, derives it from the base columns. when FK column is set to NULL in the child table, three cases need to be taken care while setting virtual column value in the index: 1. If all base columns are in FK then set virtual column value to NULL. 2. If no base columns is in FK then set virtual column value to OLD value. 3. If one or more base columns are in FK then set those base column value to NULL and compute virtual column value again. RB: 26751 Reviewed by : Rahul Agarkar <rahul.agarkar@oracle.com>
1 parent f8ed177 commit 27130e2

File tree

7 files changed

+360
-9
lines changed

7 files changed

+360
-9
lines changed

mysql-test/suite/innodb/r/foreign_key.result

+147
Original file line numberDiff line numberDiff line change
@@ -150,3 +150,150 @@ unique_constraint_name
150150
PRIMARY
151151
DROP TABLE t2;
152152
DROP TABLE t1;
153+
#
154+
# Bug#33053297 VIRTUAL INDEX CORRUPTED DURING CASCADE UPDATE ON CHILD TABLE
155+
#
156+
#Test-Case 1
157+
CREATE TABLE `emails` (
158+
`id` int unsigned NOT NULL AUTO_INCREMENT,
159+
PRIMARY KEY (`id`)
160+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
161+
ROW_FORMAT=DYNAMIC;
162+
CREATE TABLE `email_stats` (
163+
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
164+
`email_id` int unsigned DEFAULT NULL,
165+
`date_sent` datetime NOT NULL,
166+
`generated_sent_date` date GENERATED ALWAYS AS
167+
(concat(year(`date_sent`),'-',lpad(month(`date_sent`),2,'0'),
168+
'-',lpad(dayofmonth(`date_sent`),2,'0'))) VIRTUAL,
169+
PRIMARY KEY (`id`),
170+
KEY `IDX_ES1` (`email_id`),
171+
KEY `mautic_generated_sent_date_email_id`
172+
(`generated_sent_date`,`email_id`),
173+
CONSTRAINT `FK_EA1` FOREIGN KEY (`email_id`) REFERENCES
174+
`emails` (`id`) ON DELETE SET NULL
175+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
176+
ROW_FORMAT=DYNAMIC;
177+
INSERT INTO `emails` VALUES (1);
178+
INSERT INTO `email_stats` (`id`, `email_id`, `date_sent`) VALUES
179+
(1,1,'2020-10-22 13:32:41');
180+
SELECT * FROM `email_stats`;
181+
id email_id date_sent generated_sent_date
182+
1 1 2020-10-22 13:32:41 2020-10-22
183+
DELETE FROM `emails`;
184+
DELETE FROM `email_stats`;
185+
DROP TABLE `email_stats`;
186+
DROP TABLE `emails`;
187+
# Test-Case 2
188+
CREATE TABLE `emails` (
189+
`id` int unsigned NOT NULL AUTO_INCREMENT,
190+
PRIMARY KEY (`id`)
191+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
192+
ROW_FORMAT=DYNAMIC;
193+
CREATE TABLE `email_stats` (
194+
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
195+
`email_id` int unsigned DEFAULT NULL,
196+
`date_sent` datetime NOT NULL,
197+
`generated_sent_date` date GENERATED ALWAYS AS
198+
(concat(year(`date_sent`),'-',lpad(month(`date_sent`),2,'0'),
199+
'-',lpad(dayofmonth(`date_sent`),2,'0'))) VIRTUAL,
200+
PRIMARY KEY (`id`),
201+
KEY `IDX_ES1` (`email_id`),
202+
KEY `mautic_generated_sent_date_email_id`
203+
(`generated_sent_date`,`email_id`),
204+
CONSTRAINT `FK_EA1` FOREIGN KEY (`email_id`) REFERENCES
205+
`emails` (`id`) ON DELETE SET NULL ON UPDATE SET NULL
206+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
207+
ROW_FORMAT=DYNAMIC;
208+
INSERT INTO `emails` VALUES (1);
209+
INSERT INTO `email_stats` (`id`, `email_id`, `date_sent`) VALUES
210+
(1,1,'2020-10-22 13:32:41');
211+
UPDATE `emails` SET `id` = 2 where `id` = 1;
212+
SELECT id FROM `email_stats` WHERE `generated_sent_date` IS NULL;
213+
id
214+
SELECT * FROM `email_stats`;
215+
id email_id date_sent generated_sent_date
216+
1 NULL 2020-10-22 13:32:41 2020-10-22
217+
UPDATE `email_stats` SET `email_id`=2
218+
WHERE DATE(`generated_sent_date`) = '2020-10-22';
219+
SELECT * FROM `email_stats`;
220+
id email_id date_sent generated_sent_date
221+
1 2 2020-10-22 13:32:41 2020-10-22
222+
DROP TABLE `email_stats`;
223+
DROP TABLE `emails`;
224+
# test-case 3
225+
CREATE TABLE `emails` (
226+
`id` int unsigned NOT NULL AUTO_INCREMENT,
227+
PRIMARY KEY (`id`)
228+
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
229+
ROW_FORMAT=DYNAMIC;
230+
CREATE TABLE `email_stats` (
231+
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
232+
`email_id` int unsigned DEFAULT NULL,
233+
`date_sent` datetime NOT NULL,
234+
`generated_sent_email` varchar(20) GENERATED ALWAYS AS
235+
(CONCAT(YEAR(`date_sent`),'-', COALESCE(`email_id`, ' '))) VIRTUAL,
236+
PRIMARY KEY (`id`),
237+
KEY `idx_es1` (`email_id`),
238+
KEY `mautic_generated_sent_date_email`
239+
(`generated_sent_email`,`email_id`),
240+
CONSTRAINT `fk_ea1` FOREIGN KEY (`email_id`) REFERENCES
241+
`emails` (`id`) ON DELETE SET NULL
242+
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
243+
ROW_FORMAT=DYNAMIC;
244+
INSERT INTO `emails` VALUES (1);
245+
INSERT INTO `email_stats` (`id`, `email_id`, `date_sent`) VALUES
246+
(1,1,'2020-10-22 13:32:41');
247+
SELECT * FROM `email_stats`;
248+
id email_id date_sent generated_sent_email
249+
1 1 2020-10-22 13:32:41 2020-1
250+
SELECT `date_sent` FROM `email_stats` WHERE `generated_sent_email` = '2020-1';
251+
date_sent
252+
2020-10-22 13:32:41
253+
DELETE FROM `emails`;
254+
SELECT * FROM `email_stats`;
255+
id email_id date_sent generated_sent_email
256+
1 NULL 2020-10-22 13:32:41 2020-
257+
SELECT `date_sent` FROM `email_stats` WHERE `generated_sent_email` = '2020-';
258+
date_sent
259+
2020-10-22 13:32:41
260+
DROP TABLE `email_stats`;
261+
DROP TABLE `emails`;
262+
# test-case 4
263+
CREATE TABLE `emails` (
264+
`id` int unsigned NOT NULL AUTO_INCREMENT,
265+
PRIMARY KEY (`id`)
266+
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
267+
ROW_FORMAT=DYNAMIC;
268+
CREATE TABLE `email_stats` (
269+
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
270+
`email_id` int unsigned DEFAULT NULL,
271+
`date_sent` datetime NOT NULL,
272+
`generated_sent_email` varchar(20) GENERATED ALWAYS AS
273+
(CONCAT(YEAR(`date_sent`),'-', COALESCE(`email_id`, ' '))) VIRTUAL,
274+
PRIMARY KEY (`id`),
275+
KEY `idx_es1` (`email_id`),
276+
KEY `mautic_generated_sent_date_email`
277+
(`generated_sent_email`,`email_id`),
278+
CONSTRAINT `fk_ea1` FOREIGN KEY (`email_id`) REFERENCES
279+
`emails` (`id`) ON UPDATE SET NULL
280+
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
281+
ROW_FORMAT=DYNAMIC;
282+
INSERT INTO `emails` VALUES (1);
283+
INSERT INTO `email_stats` (`id`, `email_id`, `date_sent`) VALUES
284+
(1,1,'2020-10-22 13:32:41');
285+
SELECT * FROM `email_stats`;
286+
id email_id date_sent generated_sent_email
287+
1 1 2020-10-22 13:32:41 2020-1
288+
SELECT `date_sent` FROM `email_stats` WHERE `generated_sent_email` = '2020-1';
289+
date_sent
290+
2020-10-22 13:32:41
291+
UPDATE `emails` SET `id` = 2 WHERE `id` = 1;
292+
SELECT * FROM `email_stats`;
293+
id email_id date_sent generated_sent_email
294+
1 NULL 2020-10-22 13:32:41 2020-
295+
SELECT `date_sent` FROM `email_stats` WHERE `generated_sent_email` = '2020-';
296+
date_sent
297+
2020-10-22 13:32:41
298+
DROP TABLE `email_stats`;
299+
DROP TABLE `emails`;

mysql-test/suite/innodb/t/foreign_key.test

+140
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,143 @@ WHERE table_name = 't2';
113113

114114
DROP TABLE t2;
115115
DROP TABLE t1;
116+
117+
--echo #
118+
--echo # Bug#33053297 VIRTUAL INDEX CORRUPTED DURING CASCADE UPDATE ON CHILD TABLE
119+
--echo #
120+
121+
--echo #Test-Case 1
122+
CREATE TABLE `emails` (
123+
`id` int unsigned NOT NULL AUTO_INCREMENT,
124+
PRIMARY KEY (`id`)
125+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
126+
ROW_FORMAT=DYNAMIC;
127+
128+
CREATE TABLE `email_stats` (
129+
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
130+
`email_id` int unsigned DEFAULT NULL,
131+
`date_sent` datetime NOT NULL,
132+
`generated_sent_date` date GENERATED ALWAYS AS
133+
(concat(year(`date_sent`),'-',lpad(month(`date_sent`),2,'0'),
134+
'-',lpad(dayofmonth(`date_sent`),2,'0'))) VIRTUAL,
135+
PRIMARY KEY (`id`),
136+
KEY `IDX_ES1` (`email_id`),
137+
KEY `mautic_generated_sent_date_email_id`
138+
(`generated_sent_date`,`email_id`),
139+
CONSTRAINT `FK_EA1` FOREIGN KEY (`email_id`) REFERENCES
140+
`emails` (`id`) ON DELETE SET NULL
141+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
142+
ROW_FORMAT=DYNAMIC;
143+
144+
INSERT INTO `emails` VALUES (1);
145+
INSERT INTO `email_stats` (`id`, `email_id`, `date_sent`) VALUES
146+
(1,1,'2020-10-22 13:32:41');
147+
SELECT * FROM `email_stats`;
148+
DELETE FROM `emails`;
149+
DELETE FROM `email_stats`;
150+
151+
#clean up.
152+
DROP TABLE `email_stats`;
153+
DROP TABLE `emails`;
154+
155+
--echo # Test-Case 2
156+
CREATE TABLE `emails` (
157+
`id` int unsigned NOT NULL AUTO_INCREMENT,
158+
PRIMARY KEY (`id`)
159+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
160+
ROW_FORMAT=DYNAMIC;
161+
162+
CREATE TABLE `email_stats` (
163+
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
164+
`email_id` int unsigned DEFAULT NULL,
165+
`date_sent` datetime NOT NULL,
166+
`generated_sent_date` date GENERATED ALWAYS AS
167+
(concat(year(`date_sent`),'-',lpad(month(`date_sent`),2,'0'),
168+
'-',lpad(dayofmonth(`date_sent`),2,'0'))) VIRTUAL,
169+
PRIMARY KEY (`id`),
170+
KEY `IDX_ES1` (`email_id`),
171+
KEY `mautic_generated_sent_date_email_id`
172+
(`generated_sent_date`,`email_id`),
173+
CONSTRAINT `FK_EA1` FOREIGN KEY (`email_id`) REFERENCES
174+
`emails` (`id`) ON DELETE SET NULL ON UPDATE SET NULL
175+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
176+
ROW_FORMAT=DYNAMIC;
177+
INSERT INTO `emails` VALUES (1);
178+
INSERT INTO `email_stats` (`id`, `email_id`, `date_sent`) VALUES
179+
(1,1,'2020-10-22 13:32:41');
180+
UPDATE `emails` SET `id` = 2 where `id` = 1;
181+
SELECT id FROM `email_stats` WHERE `generated_sent_date` IS NULL;
182+
SELECT * FROM `email_stats`;
183+
UPDATE `email_stats` SET `email_id`=2
184+
WHERE DATE(`generated_sent_date`) = '2020-10-22';
185+
SELECT * FROM `email_stats`;
186+
187+
#clean up.
188+
DROP TABLE `email_stats`;
189+
DROP TABLE `emails`;
190+
191+
--echo # test-case 3
192+
CREATE TABLE `emails` (
193+
`id` int unsigned NOT NULL AUTO_INCREMENT,
194+
PRIMARY KEY (`id`)
195+
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
196+
ROW_FORMAT=DYNAMIC;
197+
CREATE TABLE `email_stats` (
198+
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
199+
`email_id` int unsigned DEFAULT NULL,
200+
`date_sent` datetime NOT NULL,
201+
`generated_sent_email` varchar(20) GENERATED ALWAYS AS
202+
(CONCAT(YEAR(`date_sent`),'-', COALESCE(`email_id`, ' '))) VIRTUAL,
203+
PRIMARY KEY (`id`),
204+
KEY `idx_es1` (`email_id`),
205+
KEY `mautic_generated_sent_date_email`
206+
(`generated_sent_email`,`email_id`),
207+
CONSTRAINT `fk_ea1` FOREIGN KEY (`email_id`) REFERENCES
208+
`emails` (`id`) ON DELETE SET NULL
209+
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
210+
ROW_FORMAT=DYNAMIC;
211+
INSERT INTO `emails` VALUES (1);
212+
INSERT INTO `email_stats` (`id`, `email_id`, `date_sent`) VALUES
213+
(1,1,'2020-10-22 13:32:41');
214+
SELECT * FROM `email_stats`;
215+
SELECT `date_sent` FROM `email_stats` WHERE `generated_sent_email` = '2020-1';
216+
DELETE FROM `emails`;
217+
SELECT * FROM `email_stats`;
218+
SELECT `date_sent` FROM `email_stats` WHERE `generated_sent_email` = '2020-';
219+
220+
#clean up.
221+
DROP TABLE `email_stats`;
222+
DROP TABLE `emails`;
223+
224+
--echo # test-case 4
225+
CREATE TABLE `emails` (
226+
`id` int unsigned NOT NULL AUTO_INCREMENT,
227+
PRIMARY KEY (`id`)
228+
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
229+
ROW_FORMAT=DYNAMIC;
230+
CREATE TABLE `email_stats` (
231+
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
232+
`email_id` int unsigned DEFAULT NULL,
233+
`date_sent` datetime NOT NULL,
234+
`generated_sent_email` varchar(20) GENERATED ALWAYS AS
235+
(CONCAT(YEAR(`date_sent`),'-', COALESCE(`email_id`, ' '))) VIRTUAL,
236+
PRIMARY KEY (`id`),
237+
KEY `idx_es1` (`email_id`),
238+
KEY `mautic_generated_sent_date_email`
239+
(`generated_sent_email`,`email_id`),
240+
CONSTRAINT `fk_ea1` FOREIGN KEY (`email_id`) REFERENCES
241+
`emails` (`id`) ON UPDATE SET NULL
242+
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
243+
ROW_FORMAT=DYNAMIC;
244+
INSERT INTO `emails` VALUES (1);
245+
INSERT INTO `email_stats` (`id`, `email_id`, `date_sent`) VALUES
246+
(1,1,'2020-10-22 13:32:41');
247+
SELECT * FROM `email_stats`;
248+
SELECT `date_sent` FROM `email_stats` WHERE `generated_sent_email` = '2020-1';
249+
UPDATE `emails` SET `id` = 2 WHERE `id` = 1;
250+
SELECT * FROM `email_stats`;
251+
SELECT `date_sent` FROM `email_stats` WHERE `generated_sent_email` = '2020-';
252+
253+
#clean up.
254+
DROP TABLE `email_stats`;
255+
DROP TABLE `emails`;

storage/innobase/dict/dict0dict.cc

+22
Original file line numberDiff line numberDiff line change
@@ -7166,3 +7166,25 @@ dict_table_extent_size(
71667166

71677167
return(pages_in_extent);
71687168
}
7169+
7170+
/** @return number of base columns of virtual column in foreign key column
7171+
@param[in] vcol in-memory virtual column
7172+
@param[in] foreign in-memory Foreign key constraint */
7173+
uint32_t dict_vcol_base_is_foreign_key(dict_v_col_t *vcol,
7174+
dict_foreign_t *foreign) {
7175+
7176+
const dict_table_t *table = foreign->foreign_table;
7177+
uint32_t foreign_col_count = 0;
7178+
7179+
for (uint32_t i = 0; i < foreign->n_fields; i++) {
7180+
const char *foreign_col_name = foreign->foreign_col_names[i];
7181+
for (uint32_t j = 0; j < vcol->num_base; j++) {
7182+
if (innobase_strcasecmp(foreign_col_name,
7183+
dict_table_get_col_name(table,
7184+
vcol->base_col[j]->ind)) == 0) {
7185+
foreign_col_count++;
7186+
}
7187+
}
7188+
}
7189+
return foreign_col_count;
7190+
}

storage/innobase/handler/ha_innodb.cc

+7-8
Original file line numberDiff line numberDiff line change
@@ -20788,20 +20788,19 @@ given col_no.
2078820788
@param[in] update updated parent vector.
2078920789
@param[in] col_no base column position of the child table to check
2079020790
@return updated field from the parent update vector, else NULL */
20791-
static
2079220791
dfield_t*
2079320792
innobase_get_field_from_update_vector(
2079420793
dict_foreign_t* foreign,
2079520794
upd_t* update,
20796-
ulint col_no)
20795+
uint32_t col_no)
2079720796
{
2079820797
dict_table_t* parent_table = foreign->referenced_table;
2079920798
dict_index_t* parent_index = foreign->referenced_index;
20800-
ulint parent_field_no;
20801-
ulint parent_col_no;
20802-
ulint child_col_no;
20799+
uint32_t parent_field_no;
20800+
uint32_t parent_col_no;
20801+
uint32_t child_col_no;
2080320802

20804-
for (ulint i = 0; i < foreign->n_fields; i++) {
20803+
for (uint32_t i = 0; i < foreign->n_fields; i++) {
2080520804
child_col_no = dict_index_get_nth_col_no(
2080620805
foreign->foreign_index, i);
2080720806
if (child_col_no != col_no) {
@@ -20811,7 +20810,7 @@ innobase_get_field_from_update_vector(
2081120810
parent_field_no = dict_table_get_nth_col_pos(
2081220811
parent_table, parent_col_no);
2081320812

20814-
for (ulint j = 0; j < update->n_fields; j++) {
20813+
for (uint32_t j = 0; j < update->n_fields; j++) {
2081520814
upd_field_t* parent_ufield
2081620815
= &update->fields[j];
2081720816

@@ -20891,7 +20890,7 @@ innobase_get_computed_value(
2089120890
for (ulint i = 0; i < col->num_base; i++) {
2089220891
dict_col_t* base_col = col->base_col[i];
2089320892
const dfield_t* row_field = NULL;
20894-
ulint col_no = base_col->ind;
20893+
uint32_t col_no = base_col->ind;
2089520894
const mysql_row_templ_t* templ
2089620895
= index->table->vc_templ->vtempl[col_no];
2089720896
const byte* data;

storage/innobase/include/dict0dict.h

+4
Original file line numberDiff line numberDiff line change
@@ -2149,6 +2149,10 @@ bool
21492149
dict_table_is_partition(
21502150
const dict_table_t* table);
21512151

2152+
/** @return true if all base column of virtual column is foreign key column
2153+
@param[in] vcol in-memory virtul column
2154+
@param[in] foreign in-memory Foreign key constraint */
2155+
uint32_t dict_vcol_base_is_foreign_key(dict_v_col_t *vcol, dict_foreign_t *foreign);
21522156

21532157
#endif /* !UNIV_HOTBACKUP */
21542158

storage/innobase/include/row0mysql.h

+12
Original file line numberDiff line numberDiff line change
@@ -951,6 +951,18 @@ struct SysIndexCallback {
951951
virtual void operator()(mtr_t* mtr, btr_pcur_t* pcur) throw() = 0;
952952
};
953953

954+
/** Get the updated parent field value from the update vector for the
955+
given col_no.
956+
@param[in] foreign foreign key information
957+
@param[in] update updated parent vector.
958+
@param[in] col_no base column position of the child table to check
959+
@return updated field from the parent update vector, else NULL */
960+
dfield_t*
961+
innobase_get_field_from_update_vector(
962+
dict_foreign_t* foreign,
963+
upd_t* update,
964+
uint32_t col_no);
965+
954966
/** Get the computed value by supplying the base column values.
955967
@param[in,out] row the data row
956968
@param[in] col virtual column

0 commit comments

Comments
 (0)