diff --git a/mysql-test/suite/innodb/r/foreign_key.result b/mysql-test/suite/innodb/r/foreign_key.result index f87aeb56b77b4..769af9cc22089 100644 --- a/mysql-test/suite/innodb/r/foreign_key.result +++ b/mysql-test/suite/innodb/r/foreign_key.result @@ -727,7 +727,9 @@ pk a b 13 0 1 14 0 1 15 1 0 -disconnect con1; +connection con1; +COMMIT; +connection default; InnoDB 0 transactions not purged CHECK TABLE t1; Table Op Msg_type Msg_text @@ -906,5 +908,26 @@ CONSTRAINT FK_t1_id FOREIGN KEY (t1_id) REFERENCES t1 (id) ALTER TABLE t1 MODIFY id INT unsigned AUTO_INCREMENT; DROP TABLE t1,t2; # -# End of 10.4 tests +# MDEV-31441 BLOB corruption on UPDATE of PRIMARY KEY with FOREIGN KEY # +CREATE TABLE t1 (pk INT PRIMARY KEY, t TEXT) ENGINE=InnoDB; +CREATE TABLE t2 (pk INT PRIMARY KEY, FOREIGN KEY (pk) REFERENCES t1(pk)) +ENGINE=InnoDB; +SET @blob = REPEAT('A', @@innodb_page_size / 2); +INSERT INTO t1 SET pk=1, t=@blob; +INSERT INTO t2 SET pk=1; +connection con1; +BEGIN; +DELETE FROM t2; +connection default; +UPDATE t1 SET pk=12; +connection con1; +COMMIT; +disconnect con1; +connection default; +UPDATE t1 SET pk=1; +SELECT pk,t=@blob FROM t1; +pk t=@blob +1 1 +DROP TABLE t2, t1; +# End of 10.4 tests diff --git a/mysql-test/suite/innodb/t/foreign_key.test b/mysql-test/suite/innodb/t/foreign_key.test index 032bab2408d53..9de72eb4681d2 100644 --- a/mysql-test/suite/innodb/t/foreign_key.test +++ b/mysql-test/suite/innodb/t/foreign_key.test @@ -732,7 +732,9 @@ SELECT a FROM t1 FORCE INDEX(a); # the "goto rollback_to_savept" in row_mysql_handle_errors() is reverted. SELECT * FROM t1; # Allow purge to continue by closing the read view. -disconnect con1; +connection con1; +COMMIT; +connection default; # Wait for purge. With the fix reverted, the server would crash here. --source include/wait_all_purged.inc @@ -954,7 +956,35 @@ ALTER TABLE t1 MODIFY id INT unsigned AUTO_INCREMENT; DROP TABLE t1,t2; --echo # ---echo # End of 10.4 tests +--echo # MDEV-31441 BLOB corruption on UPDATE of PRIMARY KEY with FOREIGN KEY --echo # +CREATE TABLE t1 (pk INT PRIMARY KEY, t TEXT) ENGINE=InnoDB; +CREATE TABLE t2 (pk INT PRIMARY KEY, FOREIGN KEY (pk) REFERENCES t1(pk)) +ENGINE=InnoDB; + +SET @blob = REPEAT('A', @@innodb_page_size / 2); +INSERT INTO t1 SET pk=1, t=@blob; +INSERT INTO t2 SET pk=1; +--connection con1 +BEGIN; +DELETE FROM t2; +--connection default +# The following will be blocked by a FOREIGN KEY check on pk=1 in t2. +--send +UPDATE t1 SET pk=12; +--connection con1 +let $wait_condition= +SELECT count(*) > 0 FROM INFORMATION_SCHEMA.PROCESSLIST WHERE state='Updating'; +--source include/wait_condition.inc +COMMIT; +--disconnect con1 +--connection default +--reap +UPDATE t1 SET pk=1; +SELECT pk,t=@blob FROM t1; +DROP TABLE t2, t1; + +--echo # End of 10.4 tests + --source include/wait_until_count_sessions.inc diff --git a/storage/innobase/row/row0upd.cc b/storage/innobase/row/row0upd.cc index 0700792e44148..58c93e4db0fa7 100644 --- a/storage/innobase/row/row0upd.cc +++ b/storage/innobase/row/row0upd.cc @@ -2698,6 +2698,25 @@ row_upd_clust_rec_by_insert_inherit_func( return(inherit); } +/** Mark 'disowned' BLOBs as 'owned' and 'inherited' again, +after resuming from a lock wait. +@param entry clustered index entry */ +static ATTRIBUTE_COLD void row_upd_reown_inherited_fields(dtuple_t *entry) +{ + for (ulint i= 0; i < entry->n_fields; i++) + { + const dfield_t *dfield= dtuple_get_nth_field(entry, i); + if (dfield_is_ext(dfield)) + { + byte *blob_len= static_cast(dfield->data) + + dfield->len - (BTR_EXTERN_FIELD_REF_SIZE - BTR_EXTERN_LEN); + ut_ad(*blob_len & BTR_EXTERN_OWNER_FLAG); + *blob_len= byte(*blob_len & ~BTR_EXTERN_OWNER_FLAG) | + BTR_EXTERN_INHERITED_FLAG; + } + } +} + /***********************************************************//** Marks the clustered index record deleted and inserts the updated version of the record to the index. This function should be used when the ordering @@ -2776,12 +2795,16 @@ row_upd_clust_rec_by_insert( /* If the clustered index record is already delete marked, then we are here after a DB_LOCK_WAIT. Skip delete marking clustered index and disowning - its blobs. */ + its blobs. Mark the BLOBs in the index entry + (which we copied from the already "disowned" rec) + as "owned", like it was on the previous call of + row_upd_clust_rec_by_insert(). */ ut_ad(row_get_rec_trx_id(rec, index, offsets) == trx->id); ut_ad(!trx_undo_roll_ptr_is_insert( row_get_rec_roll_ptr(rec, index, offsets))); + row_upd_reown_inherited_fields(entry); goto check_fk; }