Commit fe79ac5b authored by Marko Mäkelä's avatar Marko Mäkelä

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.
parent 5a1283a4
...@@ -430,6 +430,16 @@ clust_index_size ...@@ -430,6 +430,16 @@ clust_index_size
connection default; connection default;
InnoDB 0 transactions not purged InnoDB 0 transactions not purged
DROP TABLE t1,t2,t3,t4,big; 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 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)'),
...@@ -806,6 +816,16 @@ clust_index_size ...@@ -806,6 +816,16 @@ clust_index_size
connection default; connection default;
InnoDB 0 transactions not purged InnoDB 0 transactions not purged
DROP TABLE t1,t2,t3,t4,big; 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 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)'),
...@@ -1182,10 +1202,20 @@ clust_index_size ...@@ -1182,10 +1202,20 @@ clust_index_size
connection default; connection default;
InnoDB 0 transactions not purged InnoDB 0 transactions not purged
DROP TABLE t1,t2,t3,t4,big; 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; 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
33 36
SET GLOBAL innodb_purge_rseg_truncate_frequency= @saved_frequency; SET GLOBAL innodb_purge_rseg_truncate_frequency= @saved_frequency;
...@@ -301,6 +301,16 @@ connection default; ...@@ -301,6 +301,16 @@ connection default;
--source include/wait_all_purged.inc --source include/wait_all_purged.inc
DROP TABLE t1,t2,t3,t4,big; 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; dec $format;
} }
disconnect analyze; disconnect analyze;
......
...@@ -344,9 +344,6 @@ rec_init_offsets_comp_ordinary( ...@@ -344,9 +344,6 @@ rec_init_offsets_comp_ordinary(
/* 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. */
nulls -= REC_N_NEW_EXTRA_BYTES; nulls -= REC_N_NEW_EXTRA_BYTES;
if (rec_offs_n_fields(offsets) <= n_fields) {
goto ordinary;
}
/* fall through */ /* fall through */
case REC_LEAF_TEMP_COLUMNS_ADDED: case REC_LEAF_TEMP_COLUMNS_ADDED:
ut_ad(index->is_instant()); ut_ad(index->is_instant());
...@@ -1851,6 +1848,7 @@ rec_copy_prefix_to_buf( ...@@ -1851,6 +1848,7 @@ rec_copy_prefix_to_buf(
ulint null_mask; ulint null_mask;
bool is_rtr_node_ptr = false; 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)); ut_ad(index->n_core_null_bytes <= UT_BITS_IN_BYTES(index->n_nullable));
UNIV_PREFETCH_RW(*buf); UNIV_PREFETCH_RW(*buf);
...@@ -1863,21 +1861,11 @@ rec_copy_prefix_to_buf( ...@@ -1863,21 +1861,11 @@ rec_copy_prefix_to_buf(
} }
switch (rec_get_status(rec)) { switch (rec_get_status(rec)) {
case REC_STATUS_COLUMNS_ADDED: case REC_STATUS_INFIMUM:
/* We would have !index->is_instant() when rolling back case REC_STATUS_SUPREMUM:
an instant ADD COLUMN operation. */ /* infimum or supremum record: no sense to copy anything */
ut_ad(index->is_instant() || page_rec_is_default_row(rec)); ut_error;
if (n_fields >= index->n_core_fields) { return(NULL);
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_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); nulls = rec - (REC_N_NEW_EXTRA_BYTES + 1);
...@@ -1897,11 +1885,15 @@ rec_copy_prefix_to_buf( ...@@ -1897,11 +1885,15 @@ rec_copy_prefix_to_buf(
nulls = rec - (REC_N_NEW_EXTRA_BYTES + 1); nulls = rec - (REC_N_NEW_EXTRA_BYTES + 1);
lens = nulls - index->n_core_null_bytes; lens = nulls - index->n_core_null_bytes;
break; break;
case REC_STATUS_INFIMUM: case REC_STATUS_COLUMNS_ADDED:
case REC_STATUS_SUPREMUM: /* We would have !index->is_instant() when rolling back
/* infimum or supremum record: no sense to copy anything */ an instant ADD COLUMN operation. */
ut_error; ut_ad(index->is_instant() || page_rec_is_default_row(rec));
return(NULL); 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); UNIV_PREFETCH_R(lens);
......
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