From fe79ac5b0e48395e55a4fb78196198b8542cc3d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Tue, 9 Jan 2018 13:45:39 +0200 Subject: [PATCH] MDEV-14837 Duplicate primary keys are allowed after ADD COLUMN / UPDATE This bug affected tables where the PRIMARY KEY contains variable-length columns, and ROW_FORMAT is COMPACT or DYNAMIC. rec_init_offsets_comp_ordinary(): Do not short-cut the parsing of the record header for records that contain explicit values for instantly added columns. rec_copy_prefix_to_buf(): Copy more header for records that contain explicit values for instantly added columns. --- .../suite/innodb/r/instant_alter.result | 32 +++++++++++++++- mysql-test/suite/innodb/t/instant_alter.test | 10 +++++ storage/innobase/rem/rem0rec.cc | 38 ++++++++----------- 3 files changed, 56 insertions(+), 24 deletions(-) diff --git a/mysql-test/suite/innodb/r/instant_alter.result b/mysql-test/suite/innodb/r/instant_alter.result index 581e88b1a2e..1580ba29717 100644 --- a/mysql-test/suite/innodb/r/instant_alter.result +++ b/mysql-test/suite/innodb/r/instant_alter.result @@ -430,6 +430,16 @@ clust_index_size connection default; InnoDB 0 transactions not purged DROP TABLE t1,t2,t3,t4,big; +CREATE TABLE t1 (a VARCHAR(1) PRIMARY KEY) ENGINE=InnoDB ROW_FORMAT=REDUNDANT; +INSERT INTO t1 SET a='a'; +ALTER TABLE t1 ADD COLUMN b INT NOT NULL DEFAULT 0; +UPDATE t1 SET b = 1; +INSERT INTO t1 SET a='a'; +ERROR 23000: Duplicate entry 'a' for key 'PRIMARY' +SELECT * FROM t1; +a b +a 1 +DROP TABLE t1; CREATE TABLE t1 (id INT PRIMARY KEY, c2 INT UNIQUE, c3 POINT NOT NULL DEFAULT ST_GeomFromText('POINT(3 4)'), @@ -806,6 +816,16 @@ clust_index_size connection default; InnoDB 0 transactions not purged DROP TABLE t1,t2,t3,t4,big; +CREATE TABLE t1 (a VARCHAR(1) PRIMARY KEY) ENGINE=InnoDB ROW_FORMAT=COMPACT; +INSERT INTO t1 SET a='a'; +ALTER TABLE t1 ADD COLUMN b INT NOT NULL DEFAULT 0; +UPDATE t1 SET b = 1; +INSERT INTO t1 SET a='a'; +ERROR 23000: Duplicate entry 'a' for key 'PRIMARY' +SELECT * FROM t1; +a b +a 1 +DROP TABLE t1; CREATE TABLE t1 (id INT PRIMARY KEY, c2 INT UNIQUE, c3 POINT NOT NULL DEFAULT ST_GeomFromText('POINT(3 4)'), @@ -1182,10 +1202,20 @@ clust_index_size connection default; InnoDB 0 transactions not purged DROP TABLE t1,t2,t3,t4,big; +CREATE TABLE t1 (a VARCHAR(1) PRIMARY KEY) ENGINE=InnoDB ROW_FORMAT=DYNAMIC; +INSERT INTO t1 SET a='a'; +ALTER TABLE t1 ADD COLUMN b INT NOT NULL DEFAULT 0; +UPDATE t1 SET b = 1; +INSERT INTO t1 SET a='a'; +ERROR 23000: Duplicate entry 'a' for key 'PRIMARY' +SELECT * FROM t1; +a b +a 1 +DROP TABLE t1; disconnect analyze; SELECT variable_value-@old_instant instants FROM information_schema.global_status WHERE variable_name = 'innodb_instant_alter_column'; instants -33 +36 SET GLOBAL innodb_purge_rseg_truncate_frequency= @saved_frequency; diff --git a/mysql-test/suite/innodb/t/instant_alter.test b/mysql-test/suite/innodb/t/instant_alter.test index 8f2261cadf6..d95f412fb37 100644 --- a/mysql-test/suite/innodb/t/instant_alter.test +++ b/mysql-test/suite/innodb/t/instant_alter.test @@ -301,6 +301,16 @@ connection default; --source include/wait_all_purged.inc DROP TABLE t1,t2,t3,t4,big; +# MDEV-14837 Duplicate primary keys are allowed after ADD COLUMN / UPDATE +eval CREATE TABLE t1 (a VARCHAR(1) PRIMARY KEY) $engine; +INSERT INTO t1 SET a='a'; +ALTER TABLE t1 ADD COLUMN b INT NOT NULL DEFAULT 0; +UPDATE t1 SET b = 1; +--error ER_DUP_ENTRY +INSERT INTO t1 SET a='a'; +SELECT * FROM t1; +DROP TABLE t1; + dec $format; } disconnect analyze; diff --git a/storage/innobase/rem/rem0rec.cc b/storage/innobase/rem/rem0rec.cc index 8fb24855e97..644232b72ff 100644 --- a/storage/innobase/rem/rem0rec.cc +++ b/storage/innobase/rem/rem0rec.cc @@ -344,9 +344,6 @@ ordinary: /* We would have !index->is_instant() when rolling back an instant ADD COLUMN operation. */ nulls -= REC_N_NEW_EXTRA_BYTES; - if (rec_offs_n_fields(offsets) <= n_fields) { - goto ordinary; - } /* fall through */ case REC_LEAF_TEMP_COLUMNS_ADDED: ut_ad(index->is_instant()); @@ -1851,6 +1848,7 @@ rec_copy_prefix_to_buf( ulint null_mask; bool is_rtr_node_ptr = false; + ut_ad(n_fields <= index->n_fields); ut_ad(index->n_core_null_bytes <= UT_BITS_IN_BYTES(index->n_nullable)); UNIV_PREFETCH_RW(*buf); @@ -1863,21 +1861,11 @@ rec_copy_prefix_to_buf( } switch (rec_get_status(rec)) { - case REC_STATUS_COLUMNS_ADDED: - /* We would have !index->is_instant() when rolling back - an instant ADD COLUMN operation. */ - ut_ad(index->is_instant() || page_rec_is_default_row(rec)); - if (n_fields >= index->n_core_fields) { - ut_ad(index->is_instant()); - ut_ad(n_fields <= index->n_fields); - nulls = &rec[-REC_N_NEW_EXTRA_BYTES]; - const ulint n_rec = n_fields + 1 - + rec_get_n_add_field(nulls); - const uint n_nullable = index->get_n_nullable(n_rec); - lens = --nulls - UT_BITS_IN_BYTES(n_nullable); - break; - } - /* fall through */ + case REC_STATUS_INFIMUM: + case REC_STATUS_SUPREMUM: + /* infimum or supremum record: no sense to copy anything */ + ut_error; + return(NULL); case REC_STATUS_ORDINARY: ut_ad(n_fields <= index->n_core_fields); nulls = rec - (REC_N_NEW_EXTRA_BYTES + 1); @@ -1897,11 +1885,15 @@ rec_copy_prefix_to_buf( nulls = rec - (REC_N_NEW_EXTRA_BYTES + 1); lens = nulls - index->n_core_null_bytes; break; - case REC_STATUS_INFIMUM: - case REC_STATUS_SUPREMUM: - /* infimum or supremum record: no sense to copy anything */ - ut_error; - return(NULL); + case REC_STATUS_COLUMNS_ADDED: + /* We would have !index->is_instant() when rolling back + an instant ADD COLUMN operation. */ + ut_ad(index->is_instant() || page_rec_is_default_row(rec)); + nulls = &rec[-REC_N_NEW_EXTRA_BYTES]; + const ulint n_rec = index->n_core_fields + 1 + + rec_get_n_add_field(nulls); + const uint n_nullable = index->get_n_nullable(n_rec); + lens = --nulls - UT_BITS_IN_BYTES(n_nullable); } UNIV_PREFETCH_R(lens);