Commit 73a10cbc authored by Marko Mäkelä's avatar Marko Mäkelä

MDEV-16065 Assertion failed in btr_pcur_restore_position_func on UPDATE

btr_pcur_store_position(): Assert that the 'default row' record never
is the only record in a page. (If that would happen, an empty
root page would be re-created in the non-instant format, not containing
the special record.) When the cursor is positioned on the page infimum,
never use the 'default row' as the BTR_PCUR_BEFORE reference.
(This is additional cleanup, not fixing the bug.)

rec_copy_prefix_to_buf(): When converting a record prefix to
the non-instant-add format, copy the original number of null flags.
Rename the variable instant_len to instant_omit, and introduce a
few more variables to make the code easiser to read.

Note: In purge, rec_copy_prefix_to_buf() is also used for storing the
persistent cursor position on a 'default row' record. The stored record
reference will be garbage, but row_search_on_row_ref() will do special
handling to reposition the cursor on the 'default row', based on
ref->info_bits.

innodb.dml_purge: Also cover the 'default row'.
parent dcc5de66
...@@ -11,6 +11,7 @@ connect prevent_purge,localhost,root; ...@@ -11,6 +11,7 @@ connect prevent_purge,localhost,root;
START TRANSACTION WITH CONSISTENT SNAPSHOT; START TRANSACTION WITH CONSISTENT SNAPSHOT;
connection default; connection default;
INSERT INTO t1 VALUES(1,2),(3,4); INSERT INTO t1 VALUES(1,2),(3,4);
ALTER TABLE t1 ADD COLUMN c INT;
UPDATE t1 SET b=-3 WHERE a=3; UPDATE t1 SET b=-3 WHERE a=3;
connect con1,localhost,root; connect con1,localhost,root;
BEGIN; BEGIN;
...@@ -21,8 +22,13 @@ InnoDB 0 transactions not purged ...@@ -21,8 +22,13 @@ InnoDB 0 transactions not purged
disconnect con1; disconnect con1;
FLUSH TABLE t1 FOR EXPORT; FLUSH TABLE t1 FOR EXPORT;
Clustered index root page contents: Clustered index root page contents:
N_RECS=2; LEVEL=0 N_RECS=3; LEVEL=0
header=0x010000030087 (a=0x696e66696d756d00) header=0x0100000300c6 (a=0x696e66696d756d00)
header=0x1000200b0087 (a=0x80000000,
DB_TRX_ID=0x000000000000,
DB_ROLL_PTR=0x80000000000000,
b=0x80000000,
c=NULL(4 bytes))
header=0x0000100900a6 (a=0x80000001, header=0x0000100900a6 (a=0x80000001,
DB_TRX_ID=0x000000000000, DB_TRX_ID=0x000000000000,
DB_ROLL_PTR=0x80000000000000, DB_ROLL_PTR=0x80000000000000,
...@@ -31,11 +37,11 @@ header=0x000018090074 (a=0x80000003, ...@@ -31,11 +37,11 @@ header=0x000018090074 (a=0x80000003,
DB_TRX_ID=0x000000000000, DB_TRX_ID=0x000000000000,
DB_ROLL_PTR=0x80000000000000, DB_ROLL_PTR=0x80000000000000,
b=0x7ffffffd) b=0x7ffffffd)
header=0x030008030000 (a=0x73757072656d756d00) header=0x040008030000 (a=0x73757072656d756d00)
UNLOCK TABLES; UNLOCK TABLES;
SELECT * FROM t1; SELECT * FROM t1;
a b a b c
1 2 1 2 NULL
3 -3 3 -3 NULL
DROP TABLE t1; DROP TABLE t1;
SET GLOBAL innodb_purge_rseg_truncate_frequency = @saved_frequency; SET GLOBAL innodb_purge_rseg_truncate_frequency = @saved_frequency;
...@@ -440,6 +440,12 @@ SELECT * FROM t1; ...@@ -440,6 +440,12 @@ SELECT * FROM t1;
a b a b
a 1 a 1
DROP TABLE t1; DROP TABLE t1;
CREATE TABLE t1 (a INT, b VARCHAR(8), PRIMARY KEY(b,a)) ENGINE=InnoDB ROW_FORMAT=REDUNDANT;
INSERT INTO t1 VALUES (1,'foo');
ALTER TABLE t1 ADD COLUMN c INT;
UPDATE t1 SET c = 1;
UPDATE t1 SET c = 2;
DROP TABLE t1;
CREATE TABLE t1 CREATE TABLE t1
(id INT PRIMARY KEY, c2 INT UNIQUE, (id INT PRIMARY KEY, c2 INT UNIQUE,
c3 POINT NOT NULL DEFAULT ST_GeomFromText('POINT(3 4)'), c3 POINT NOT NULL DEFAULT ST_GeomFromText('POINT(3 4)'),
...@@ -826,6 +832,12 @@ SELECT * FROM t1; ...@@ -826,6 +832,12 @@ SELECT * FROM t1;
a b a b
a 1 a 1
DROP TABLE t1; DROP TABLE t1;
CREATE TABLE t1 (a INT, b VARCHAR(8), PRIMARY KEY(b,a)) ENGINE=InnoDB ROW_FORMAT=COMPACT;
INSERT INTO t1 VALUES (1,'foo');
ALTER TABLE t1 ADD COLUMN c INT;
UPDATE t1 SET c = 1;
UPDATE t1 SET c = 2;
DROP TABLE t1;
CREATE TABLE t1 CREATE TABLE t1
(id INT PRIMARY KEY, c2 INT UNIQUE, (id INT PRIMARY KEY, c2 INT UNIQUE,
c3 POINT NOT NULL DEFAULT ST_GeomFromText('POINT(3 4)'), c3 POINT NOT NULL DEFAULT ST_GeomFromText('POINT(3 4)'),
...@@ -1212,10 +1224,16 @@ SELECT * FROM t1; ...@@ -1212,10 +1224,16 @@ SELECT * FROM t1;
a b a b
a 1 a 1
DROP TABLE t1; DROP TABLE t1;
CREATE TABLE t1 (a INT, b VARCHAR(8), PRIMARY KEY(b,a)) ENGINE=InnoDB ROW_FORMAT=DYNAMIC;
INSERT INTO t1 VALUES (1,'foo');
ALTER TABLE t1 ADD COLUMN c INT;
UPDATE t1 SET c = 1;
UPDATE t1 SET c = 2;
DROP TABLE t1;
disconnect analyze; disconnect analyze;
SELECT variable_value-@old_instant instants SELECT variable_value-@old_instant instants
FROM information_schema.global_status FROM information_schema.global_status
WHERE variable_name = 'innodb_instant_alter_column'; WHERE variable_name = 'innodb_instant_alter_column';
instants instants
36 39
SET GLOBAL innodb_purge_rseg_truncate_frequency= @saved_frequency; SET GLOBAL innodb_purge_rseg_truncate_frequency= @saved_frequency;
...@@ -35,7 +35,7 @@ ALTER TABLE t4 ADD COLUMN b INT; ...@@ -35,7 +35,7 @@ ALTER TABLE t4 ADD COLUMN b INT;
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
LEFT JOIN t4 ON (NUMERIC_SCALE = pk); LEFT JOIN t4 ON (NUMERIC_SCALE = pk);
COUNT(*) COUNT(*)
1748 953
SET DEBUG_SYNC='innodb_inplace_alter_table_enter SIGNAL enter WAIT_FOR delete'; SET DEBUG_SYNC='innodb_inplace_alter_table_enter SIGNAL enter WAIT_FOR delete';
ALTER TABLE t4 ADD COLUMN c INT; ALTER TABLE t4 ADD COLUMN c INT;
connect dml,localhost,root,,; connect dml,localhost,root,,;
......
...@@ -20,6 +20,7 @@ START TRANSACTION WITH CONSISTENT SNAPSHOT; ...@@ -20,6 +20,7 @@ START TRANSACTION WITH CONSISTENT SNAPSHOT;
--connection default --connection default
INSERT INTO t1 VALUES(1,2),(3,4); INSERT INTO t1 VALUES(1,2),(3,4);
ALTER TABLE t1 ADD COLUMN c INT;
UPDATE t1 SET b=-3 WHERE a=3; UPDATE t1 SET b=-3 WHERE a=3;
--connect (con1,localhost,root) --connect (con1,localhost,root)
...@@ -47,7 +48,7 @@ sysseek(FILE, 3*$ps, 0) || die "Unable to seek $file"; ...@@ -47,7 +48,7 @@ sysseek(FILE, 3*$ps, 0) || die "Unable to seek $file";
die "Unable to read $file" unless sysread(FILE, $page, $ps) == $ps; die "Unable to read $file" unless sysread(FILE, $page, $ps) == $ps;
print "N_RECS=", unpack("n", substr($page,38+16,2)); print "N_RECS=", unpack("n", substr($page,38+16,2));
print "; LEVEL=", unpack("n", substr($page,38+26,2)), "\n"; print "; LEVEL=", unpack("n", substr($page,38+26,2)), "\n";
my @fields=("a","DB_TRX_ID","DB_ROLL_PTR", "b"); my @fields=qw(a DB_TRX_ID DB_ROLL_PTR b c);
for (my $offset= 0x65; $offset; for (my $offset= 0x65; $offset;
$offset= unpack("n", substr($page,$offset-2,2))) $offset= unpack("n", substr($page,$offset-2,2)))
{ {
......
...@@ -311,6 +311,14 @@ INSERT INTO t1 SET a='a'; ...@@ -311,6 +311,14 @@ INSERT INTO t1 SET a='a';
SELECT * FROM t1; SELECT * FROM t1;
DROP TABLE t1; DROP TABLE t1;
# MDEV-16065 Assertion failed in btr_pcur_restore_position_func on UPDATE
eval CREATE TABLE t1 (a INT, b VARCHAR(8), PRIMARY KEY(b,a)) $engine;
INSERT INTO t1 VALUES (1,'foo');
ALTER TABLE t1 ADD COLUMN c INT;
UPDATE t1 SET c = 1;
UPDATE t1 SET c = 2;
DROP TABLE t1;
dec $format; dec $format;
} }
disconnect analyze; disconnect analyze;
......
...@@ -127,6 +127,8 @@ btr_pcur_store_position( ...@@ -127,6 +127,8 @@ btr_pcur_store_position(
mtr, dict_index_get_lock(index), mtr, dict_index_get_lock(index),
MTR_MEMO_X_LOCK | MTR_MEMO_SX_LOCK))); MTR_MEMO_X_LOCK | MTR_MEMO_SX_LOCK)));
cursor->old_stored = true;
if (page_is_empty(page)) { if (page_is_empty(page)) {
/* It must be an empty index tree; NOTE that in this case /* It must be an empty index tree; NOTE that in this case
we do not store the modify_clock, but always do a search we do not store the modify_clock, but always do a search
...@@ -136,10 +138,7 @@ btr_pcur_store_position( ...@@ -136,10 +138,7 @@ btr_pcur_store_position(
ut_ad(page_is_leaf(page)); ut_ad(page_is_leaf(page));
ut_ad(page_get_page_no(page) == index->page); ut_ad(page_get_page_no(page) == index->page);
cursor->old_stored = true;
if (page_rec_is_supremum_low(offs)) { if (page_rec_is_supremum_low(offs)) {
cursor->rel_pos = BTR_PCUR_AFTER_LAST_IN_TREE; cursor->rel_pos = BTR_PCUR_AFTER_LAST_IN_TREE;
} else { } else {
cursor->rel_pos = BTR_PCUR_BEFORE_FIRST_IN_TREE; cursor->rel_pos = BTR_PCUR_BEFORE_FIRST_IN_TREE;
...@@ -149,21 +148,25 @@ btr_pcur_store_position( ...@@ -149,21 +148,25 @@ btr_pcur_store_position(
} }
if (page_rec_is_supremum_low(offs)) { if (page_rec_is_supremum_low(offs)) {
rec = page_rec_get_prev(rec); rec = page_rec_get_prev(rec);
cursor->rel_pos = BTR_PCUR_AFTER; ut_ad(!page_rec_is_infimum(rec));
ut_ad(!rec_is_default_row(rec, index));
cursor->rel_pos = BTR_PCUR_AFTER;
} else if (page_rec_is_infimum_low(offs)) { } else if (page_rec_is_infimum_low(offs)) {
rec = page_rec_get_next(rec);
if (rec_is_default_row(rec, index)) {
rec = page_rec_get_next(rec); rec = page_rec_get_next(rec);
ut_ad(!page_rec_is_supremum(rec));
}
cursor->rel_pos = BTR_PCUR_BEFORE; cursor->rel_pos = BTR_PCUR_BEFORE;
} else { } else {
cursor->rel_pos = BTR_PCUR_ON; cursor->rel_pos = BTR_PCUR_ON;
} }
cursor->old_stored = true;
cursor->old_rec = dict_index_copy_rec_order_prefix( cursor->old_rec = dict_index_copy_rec_order_prefix(
index, rec, &cursor->old_n_fields, index, rec, &cursor->old_n_fields,
&cursor->old_rec_buf, &cursor->buf_size); &cursor->old_rec_buf, &cursor->buf_size);
......
...@@ -1835,11 +1835,6 @@ rec_copy_prefix_to_buf( ...@@ -1835,11 +1835,6 @@ rec_copy_prefix_to_buf(
or NULL */ or NULL */
ulint* buf_size) /*!< in/out: buffer size */ ulint* buf_size) /*!< in/out: buffer size */
{ {
const byte* nulls;
const byte* lens;
ulint prefix_len = 0;
ulint instant_len = 0;
ut_ad(n_fields <= index->n_fields || dict_index_is_ibuf(index)); ut_ad(n_fields <= index->n_fields || dict_index_is_ibuf(index));
ut_ad(index->n_core_null_bytes <= UT_BITS_IN_BYTES(index->n_nullable)); ut_ad(index->n_core_null_bytes <= UT_BITS_IN_BYTES(index->n_nullable));
UNIV_PREFETCH_RW(*buf); UNIV_PREFETCH_RW(*buf);
...@@ -1852,6 +1847,12 @@ rec_copy_prefix_to_buf( ...@@ -1852,6 +1847,12 @@ rec_copy_prefix_to_buf(
buf, buf_size)); buf, buf_size));
} }
ulint prefix_len = 0;
ulint instant_omit = 0;
const byte* nulls = rec - (REC_N_NEW_EXTRA_BYTES + 1);
const byte* nullf = nulls;
const byte* lens = nulls - index->n_core_null_bytes;
switch (rec_get_status(rec)) { switch (rec_get_status(rec)) {
default: default:
/* infimum or supremum record: no sense to copy anything */ /* infimum or supremum record: no sense to copy anything */
...@@ -1859,12 +1860,8 @@ rec_copy_prefix_to_buf( ...@@ -1859,12 +1860,8 @@ rec_copy_prefix_to_buf(
return(NULL); return(NULL);
case REC_STATUS_ORDINARY: case REC_STATUS_ORDINARY:
ut_ad(n_fields <= index->n_core_fields); ut_ad(n_fields <= index->n_core_fields);
nulls = rec - (REC_N_NEW_EXTRA_BYTES + 1);
lens = nulls - index->n_core_null_bytes;
break; break;
case REC_STATUS_NODE_PTR: case REC_STATUS_NODE_PTR:
nulls = rec - (REC_N_NEW_EXTRA_BYTES + 1);
lens = nulls - index->n_core_null_bytes;
/* For R-tree, we need to copy the child page number field. */ /* For R-tree, we need to copy the child page number field. */
compile_time_assert(DICT_INDEX_SPATIAL_NODEPTR_SIZE == 1); compile_time_assert(DICT_INDEX_SPATIAL_NODEPTR_SIZE == 1);
if (dict_index_is_spatial(index)) { if (dict_index_is_spatial(index)) {
...@@ -1890,15 +1887,18 @@ rec_copy_prefix_to_buf( ...@@ -1890,15 +1887,18 @@ rec_copy_prefix_to_buf(
/* We would have !index->is_instant() when rolling back /* We would have !index->is_instant() when rolling back
an instant ADD COLUMN operation. */ an instant ADD COLUMN operation. */
ut_ad(index->is_instant() || page_rec_is_default_row(rec)); ut_ad(index->is_instant() || page_rec_is_default_row(rec));
nulls = &rec[-REC_N_NEW_EXTRA_BYTES]; nulls++;
const ulint n_rec = ulint(index->n_core_fields) + 1 const ulint n_rec = ulint(index->n_core_fields) + 1
+ rec_get_n_add_field(nulls); + rec_get_n_add_field(nulls);
instant_len = ulint(&rec[-REC_N_NEW_EXTRA_BYTES] - nulls); instant_omit = ulint(&rec[-REC_N_NEW_EXTRA_BYTES] - nulls);
ut_ad(instant_len == 1 || instant_len == 2); ut_ad(instant_omit == 1 || instant_omit == 2);
const uint n_nullable = index->get_n_nullable(n_rec); nullf = nulls;
lens = --nulls - UT_BITS_IN_BYTES(n_nullable); const uint nb = UT_BITS_IN_BYTES(index->get_n_nullable(n_rec));
instant_omit += nb - index->n_core_null_bytes;
lens = --nulls - nb;
} }
const byte* const lenf = lens;
UNIV_PREFETCH_R(lens); UNIV_PREFETCH_R(lens);
/* read the lengths of fields 0..n */ /* read the lengths of fields 0..n */
...@@ -1950,27 +1950,39 @@ rec_copy_prefix_to_buf( ...@@ -1950,27 +1950,39 @@ rec_copy_prefix_to_buf(
UNIV_PREFETCH_R(rec + prefix_len); UNIV_PREFETCH_R(rec + prefix_len);
prefix_len += ulint(rec - (lens + 1)) - instant_len; ulint size = prefix_len + ulint(rec - (lens + 1)) - instant_omit;
if ((*buf == NULL) || (*buf_size < prefix_len)) { if (*buf == NULL || *buf_size < size) {
ut_free(*buf); ut_free(*buf);
*buf_size = prefix_len; *buf_size = size;
*buf = static_cast<byte*>(ut_malloc_nokey(prefix_len)); *buf = static_cast<byte*>(ut_malloc_nokey(size));
} }
if (instant_len) { if (instant_omit) {
ulint hdr = ulint(&rec[-REC_N_NEW_EXTRA_BYTES] - (lens + 1)) /* Copy and convert the record header to a format where
- instant_len; instant ADD COLUMN has not been used:
memcpy(*buf, lens + 1, hdr); + lengths of variable-length fields in the prefix
memcpy(*buf + hdr, &rec[-REC_N_NEW_EXTRA_BYTES], - omit any null flag bytes for any instantly added columns
prefix_len - hdr); + index->n_core_null_bytes of null flags
ut_ad(rec_get_status(*buf + hdr + REC_N_NEW_EXTRA_BYTES) - omit the n_add_fields header (1 or 2 bytes)
== REC_STATUS_COLUMNS_ADDED); + REC_N_NEW_EXTRA_BYTES of fixed header */
rec_set_status(*buf + hdr + REC_N_NEW_EXTRA_BYTES, byte* b = *buf;
REC_STATUS_ORDINARY); /* copy the lengths of the variable-length fields */
return *buf + hdr + REC_N_NEW_EXTRA_BYTES; memcpy(b, lens + 1, ulint(lenf - lens));
b += ulint(lenf - lens);
/* copy the null flags */
memcpy(b, nullf - index->n_core_null_bytes,
index->n_core_null_bytes);
b += index->n_core_null_bytes + REC_N_NEW_EXTRA_BYTES;
ut_ad(ulint(b - *buf) + prefix_len == size);
/* copy the fixed-size header and the record prefix */
memcpy(b - REC_N_NEW_EXTRA_BYTES, rec - REC_N_NEW_EXTRA_BYTES,
prefix_len + REC_N_NEW_EXTRA_BYTES);
ut_ad(rec_get_status(b) == REC_STATUS_COLUMNS_ADDED);
rec_set_status(b, REC_STATUS_ORDINARY);
return b;
} else { } else {
memcpy(*buf, lens + 1, prefix_len); memcpy(*buf, lens + 1, size);
return *buf + (rec - (lens + 1)); return *buf + (rec - (lens + 1));
} }
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment