Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 165 additions & 0 deletions mysql-test/suite/heap/blob_update_key_rollback.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
#
# Index consistency after a failed UPDATE on a HEAP table with BLOB
#
# heap_update() moves changed key entries to the new key values
# before writing the new blob chains. When a blob chain write then
# fails with "table is full", the key entries must be moved back to
# the old values, otherwise the index refers to records whose
# columns no longer match the indexed values.
#
SET @save_max_heap= @@max_heap_table_size;
SET max_heap_table_size = 16384;
#
# Hash index: single-row UPDATE fails writing the new blob chain
#
CREATE TABLE t1 (
id INT,
k VARCHAR(20),
b BLOB,
KEY k1 (k)
) ENGINE=HEAP;
INSERT INTO t1 SELECT seq, CONCAT('key', seq), REPEAT('a', 300)
FROM seq_1_to_10;
UPDATE t1 SET k= 'moved', b= REPEAT('z', 60000) WHERE id = 1;
ERROR HY000: The table 't1' is full
CHECK TABLE t1;
Table Op Msg_type Msg_text
test.t1 check status OK
# The row must still be found through the index under the old value
SELECT id, k, LENGTH(b) FROM t1 FORCE INDEX(k1) WHERE k = 'key1';
id k LENGTH(b)
1 key1 300
SELECT COUNT(*) FROM t1 FORCE INDEX(k1) WHERE k = 'moved';
COUNT(*)
0
DROP TABLE t1;
#
# BTREE index: same failure, rb-tree entries must be restored
#
CREATE TABLE t1 (
id INT,
k VARCHAR(20),
b BLOB,
KEY k1 (k) USING BTREE
) ENGINE=HEAP;
INSERT INTO t1 SELECT seq, CONCAT('key', seq), REPEAT('a', 300)
FROM seq_1_to_10;
UPDATE t1 SET k= 'moved', b= REPEAT('z', 60000) WHERE id = 1;
ERROR HY000: The table 't1' is full
CHECK TABLE t1;
Table Op Msg_type Msg_text
test.t1 check status OK
SELECT id, k, LENGTH(b) FROM t1 FORCE INDEX(k1) WHERE k = 'key1';
id k LENGTH(b)
1 key1 300
SELECT COUNT(*) FROM t1 FORCE INDEX(k1) WHERE k = 'moved';
COUNT(*)
0
DROP TABLE t1;
#
# Two indexed columns changed by the same failing UPDATE:
# entries in both indexes must be rolled back
#
CREATE TABLE t1 (
id INT,
k VARCHAR(20),
m VARCHAR(20),
b BLOB,
KEY k1 (k),
KEY k2 (m) USING BTREE
) ENGINE=HEAP;
INSERT INTO t1 SELECT seq, CONCAT('key', seq), CONCAT('mem', seq),
REPEAT('a', 300)
FROM seq_1_to_10;
UPDATE t1 SET k= 'moved', m= 'gone', b= REPEAT('z', 60000) WHERE id = 2;
ERROR HY000: The table 't1' is full
CHECK TABLE t1;
Table Op Msg_type Msg_text
test.t1 check status OK
SELECT id, k, m FROM t1 FORCE INDEX(k1) WHERE k = 'key2';
id k m
2 key2 mem2
SELECT id, k, m FROM t1 FORCE INDEX(k2) WHERE m = 'mem2';
id k m
2 key2 mem2
SELECT COUNT(*) FROM t1 FORCE INDEX(k1) WHERE k = 'moved';
COUNT(*)
0
SELECT COUNT(*) FROM t1 FORCE INDEX(k2) WHERE m = 'gone';
COUNT(*)
0
DROP TABLE t1;
#
# A second index on a column the UPDATE does NOT touch must be left
# alone by the rollback: only the changed key is moved back, the
# unchanged key's entries stay put and keep finding the row.
#
CREATE TABLE t1 (
id INT,
k VARCHAR(20),
fixed VARCHAR(20),
b BLOB,
KEY k1 (k),
KEY k2 (fixed)
) ENGINE=HEAP;
INSERT INTO t1 SELECT seq, CONCAT('key', seq), CONCAT('fix', seq),
REPEAT('a', 300)
FROM seq_1_to_10;
# Only k and b change; 'fixed' is untouched
UPDATE t1 SET k= 'moved', b= REPEAT('z', 60000) WHERE id = 3;
ERROR HY000: The table 't1' is full
CHECK TABLE t1;
Table Op Msg_type Msg_text
test.t1 check status OK
# Changed key rolled back to old value
SELECT id, k, fixed FROM t1 FORCE INDEX(k1) WHERE k = 'key3';
id k fixed
3 key3 fix3
SELECT COUNT(*) FROM t1 FORCE INDEX(k1) WHERE k = 'moved';
COUNT(*)
0
# Untouched key still resolves the row through its own index
SELECT id, k, fixed FROM t1 FORCE INDEX(k2) WHERE fixed = 'fix3';
id k fixed
3 key3 fix3
DROP TABLE t1;
#
# Multi-row UPDATE that fails part-way: rows updated before the
# failure keep their new key values, the failing row keeps its old
# ones, and every row must be reachable through the index by its
# current key value
#
CREATE TABLE t1 (
id INT,
k VARCHAR(20),
b BLOB,
KEY k1 (k)
) ENGINE=HEAP;
INSERT INTO t1 SELECT seq, CONCAT('key', seq), REPEAT('a', 300)
FROM seq_1_to_15;
# Each row grows its blob; the table fills up after some rows
UPDATE t1 SET k= CONCAT('new', id), b= REPEAT('z', 1500);
ERROR HY000: The table 't1' is full
CHECK TABLE t1;
Table Op Msg_type Msg_text
test.t1 check status OK
# Every row must be found via the index under its current key value
SELECT t.id FROM t1 t
WHERE NOT EXISTS (SELECT 1 FROM t1 i FORCE INDEX(k1)
WHERE i.k = t.k AND i.id = t.id)
ORDER BY t.id;
id
# Old and new key values must partition the table, with the
# failure hitting neither the first row nor after the last one.
# The exact fill point depends on platform record layout, so only
# the invariants are checked.
SELECT COUNT(*) AS total,
COUNT(IF(k LIKE 'new%', 1, NULL)) > 0 AS some_updated,
COUNT(IF(k LIKE 'key%', 1, NULL)) > 0 AS some_old,
COUNT(IF(k LIKE 'new%', 1, NULL)) +
COUNT(IF(k LIKE 'key%', 1, NULL)) = COUNT(*) AS partitioned
FROM t1;
total some_updated some_old partitioned
15 1 1 1
DROP TABLE t1;
SET max_heap_table_size = @save_max_heap;
159 changes: 159 additions & 0 deletions mysql-test/suite/heap/blob_update_key_rollback.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
--source include/have_sequence.inc

--echo #
--echo # Index consistency after a failed UPDATE on a HEAP table with BLOB
--echo #
--echo # heap_update() moves changed key entries to the new key values
--echo # before writing the new blob chains. When a blob chain write then
--echo # fails with "table is full", the key entries must be moved back to
--echo # the old values, otherwise the index refers to records whose
--echo # columns no longer match the indexed values.
--echo #

SET @save_max_heap= @@max_heap_table_size;
SET max_heap_table_size = 16384;

--echo #
--echo # Hash index: single-row UPDATE fails writing the new blob chain
--echo #

CREATE TABLE t1 (
id INT,
k VARCHAR(20),
b BLOB,
KEY k1 (k)
) ENGINE=HEAP;

INSERT INTO t1 SELECT seq, CONCAT('key', seq), REPEAT('a', 300)
FROM seq_1_to_10;

--error ER_RECORD_FILE_FULL
UPDATE t1 SET k= 'moved', b= REPEAT('z', 60000) WHERE id = 1;

CHECK TABLE t1;
--echo # The row must still be found through the index under the old value
SELECT id, k, LENGTH(b) FROM t1 FORCE INDEX(k1) WHERE k = 'key1';
SELECT COUNT(*) FROM t1 FORCE INDEX(k1) WHERE k = 'moved';
DROP TABLE t1;

--echo #
--echo # BTREE index: same failure, rb-tree entries must be restored
--echo #

CREATE TABLE t1 (
id INT,
k VARCHAR(20),
b BLOB,
KEY k1 (k) USING BTREE
) ENGINE=HEAP;

INSERT INTO t1 SELECT seq, CONCAT('key', seq), REPEAT('a', 300)
FROM seq_1_to_10;

--error ER_RECORD_FILE_FULL
UPDATE t1 SET k= 'moved', b= REPEAT('z', 60000) WHERE id = 1;

CHECK TABLE t1;
SELECT id, k, LENGTH(b) FROM t1 FORCE INDEX(k1) WHERE k = 'key1';
SELECT COUNT(*) FROM t1 FORCE INDEX(k1) WHERE k = 'moved';
DROP TABLE t1;

--echo #
--echo # Two indexed columns changed by the same failing UPDATE:
--echo # entries in both indexes must be rolled back
--echo #

CREATE TABLE t1 (
id INT,
k VARCHAR(20),
m VARCHAR(20),
b BLOB,
KEY k1 (k),
KEY k2 (m) USING BTREE
) ENGINE=HEAP;

INSERT INTO t1 SELECT seq, CONCAT('key', seq), CONCAT('mem', seq),
REPEAT('a', 300)
FROM seq_1_to_10;

--error ER_RECORD_FILE_FULL
UPDATE t1 SET k= 'moved', m= 'gone', b= REPEAT('z', 60000) WHERE id = 2;

CHECK TABLE t1;
SELECT id, k, m FROM t1 FORCE INDEX(k1) WHERE k = 'key2';
SELECT id, k, m FROM t1 FORCE INDEX(k2) WHERE m = 'mem2';
SELECT COUNT(*) FROM t1 FORCE INDEX(k1) WHERE k = 'moved';
SELECT COUNT(*) FROM t1 FORCE INDEX(k2) WHERE m = 'gone';
DROP TABLE t1;

--echo #
--echo # A second index on a column the UPDATE does NOT touch must be left
--echo # alone by the rollback: only the changed key is moved back, the
--echo # unchanged key's entries stay put and keep finding the row.
--echo #

CREATE TABLE t1 (
id INT,
k VARCHAR(20),
fixed VARCHAR(20),
b BLOB,
KEY k1 (k),
KEY k2 (fixed)
) ENGINE=HEAP;

INSERT INTO t1 SELECT seq, CONCAT('key', seq), CONCAT('fix', seq),
REPEAT('a', 300)
FROM seq_1_to_10;

--echo # Only k and b change; 'fixed' is untouched
--error ER_RECORD_FILE_FULL
UPDATE t1 SET k= 'moved', b= REPEAT('z', 60000) WHERE id = 3;

CHECK TABLE t1;
--echo # Changed key rolled back to old value
SELECT id, k, fixed FROM t1 FORCE INDEX(k1) WHERE k = 'key3';
SELECT COUNT(*) FROM t1 FORCE INDEX(k1) WHERE k = 'moved';
--echo # Untouched key still resolves the row through its own index
SELECT id, k, fixed FROM t1 FORCE INDEX(k2) WHERE fixed = 'fix3';
DROP TABLE t1;

--echo #
--echo # Multi-row UPDATE that fails part-way: rows updated before the
--echo # failure keep their new key values, the failing row keeps its old
--echo # ones, and every row must be reachable through the index by its
--echo # current key value
--echo #

CREATE TABLE t1 (
id INT,
k VARCHAR(20),
b BLOB,
KEY k1 (k)
) ENGINE=HEAP;

INSERT INTO t1 SELECT seq, CONCAT('key', seq), REPEAT('a', 300)
FROM seq_1_to_15;

--echo # Each row grows its blob; the table fills up after some rows
--error ER_RECORD_FILE_FULL
UPDATE t1 SET k= CONCAT('new', id), b= REPEAT('z', 1500);

CHECK TABLE t1;
--echo # Every row must be found via the index under its current key value
SELECT t.id FROM t1 t
WHERE NOT EXISTS (SELECT 1 FROM t1 i FORCE INDEX(k1)
WHERE i.k = t.k AND i.id = t.id)
ORDER BY t.id;
--echo # Old and new key values must partition the table, with the
--echo # failure hitting neither the first row nor after the last one.
--echo # The exact fill point depends on platform record layout, so only
--echo # the invariants are checked.
SELECT COUNT(*) AS total,
COUNT(IF(k LIKE 'new%', 1, NULL)) > 0 AS some_updated,
COUNT(IF(k LIKE 'key%', 1, NULL)) > 0 AS some_old,
COUNT(IF(k LIKE 'new%', 1, NULL)) +
COUNT(IF(k LIKE 'key%', 1, NULL)) = COUNT(*) AS partitioned
FROM t1;
DROP TABLE t1;

SET max_heap_table_size = @save_max_heap;
65 changes: 65 additions & 0 deletions mysql-test/suite/heap/update_key_rollback.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#
# Index consistency after a failed UPDATE on a HEAP table
#
# heap_update() moves the changed key entries to their new key values
# before it updates the record. A failure raised after that has to
# move them back, whatever the failure was: the recovery used to run
# only for an enumerated set of error codes, and anything outside it
# left the index describing a record image that is not in the table.
#
#
# k1 is moved to the new value, then the delete of the old k2 entry
# fails with "table is marked as crashed"
#
CREATE TABLE t1 (
id INT,
a INT,
b INT,
KEY k1 (a) USING BTREE,
KEY k2 (b)
) ENGINE=HEAP;
INSERT INTO t1 VALUES (1, 10, 100), (2, 20, 200);
CALL mtr.add_suppression("Index for table .t1. is corrupt");
SET SESSION debug_dbug="+d,hp_delete_key_not_found";
UPDATE t1 SET a= 11, b= 101 WHERE id = 1;
ERROR HY000: Index for table 't1' is corrupt; try to repair it
SET SESSION debug_dbug="";
CHECK TABLE t1;
Table Op Msg_type Msg_text
test.t1 check status OK
# Both indexes must still find the row under its unchanged values
SELECT id, a, b FROM t1 FORCE INDEX(k1) WHERE a = 10;
id a b
1 10 100
SELECT id, a, b FROM t1 FORCE INDEX(k2) WHERE b = 100;
id a b
1 10 100
SELECT COUNT(*) FROM t1 FORCE INDEX(k1) WHERE a = 11;
COUNT(*)
0
SELECT COUNT(*) FROM t1 FORCE INDEX(k2) WHERE b = 101;
COUNT(*)
0
SELECT id, a, b FROM t1 ORDER BY id;
id a b
1 10 100
2 20 200
#
# A duplicate key is still reported as a duplicate
#
CREATE TABLE t2 (
id INT,
a INT,
UNIQUE KEY k1 (a) USING BTREE
) ENGINE=HEAP;
INSERT INTO t2 VALUES (1, 10), (2, 20);
UPDATE t2 SET a= 20 WHERE id = 1;
ERROR 23000: Duplicate entry '20' for key 'k1'
CHECK TABLE t2;
Table Op Msg_type Msg_text
test.t2 check status OK
SELECT id, a FROM t2 ORDER BY id;
id a
1 10
2 20
DROP TABLE t1, t2;
Loading
Loading