Navigation Menu

Skip to content

Commit

Permalink
Fixed bug#36676: multiupdate using LEFT JOIN updates only
Browse files Browse the repository at this point in the history
                 first row or fails with an error:
  ERROR 1022 (23000): Can't write; duplicate key in table ''

The server uses intermediate temporary table to store updated
row data.  The first column of this table contains rowid.
Current server implementation doesn't reset NULL flag of that
column even if the server fills a column with rowid.
To keep each rowid unique, there is an unique index.
An insertion into an unique index takes into account NULL
flag of key value and ignores real data if NULL flag is set.
So, insertion of actually different rowids may lead to two
kind of problems.  Visible effect of each of these problems
depends on an initial engine type of temporary table:

1. If multiupdate initially creates temporary table as
a MyISAM table (a table contains blob columns, and the
create_tmp_table function assumes, that this table is
large), it inserts only one single row and updates
only rows with one corresponding rowid. Other rows are
silently ignored. 

2. If multiupdate initially creates MEMORY temporary
table, fills it with data and reaches size limit for
MEMORY tables (max_heap_table_size), multiupdate
converts MEMORY table into MyISAM table and fails
with an error:
  ERROR 1022 (23000): Can't write; duplicate key in table ''


Multiupdate has been fixed to update the NULL flag of
temporary table rowid columns.
  • Loading branch information
Unknown committed May 18, 2008
1 parent 38ed2a4 commit 1b52d48
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 0 deletions.
45 changes: 45 additions & 0 deletions mysql-test/r/multi_update_tiny_hash.result
@@ -0,0 +1,45 @@
drop table if exists t1, t2;
#
# Bug #36676: multiupdate using LEFT JOIN updates only
# first row or fails with an error:
# ERROR 1022 (23000): Can't write; duplicate key in table ''
#

#
# Multiupdate creates MyISAM temporary table without MEMORY table
#
CREATE TABLE t1 (ID INT);
CREATE TABLE t2 (ID INT,
s1 TEXT, s2 TEXT, s3 VARCHAR(10), s4 TEXT, s5 VARCHAR(10));
INSERT INTO t1 VALUES (1),(2);
INSERT INTO t2 VALUES (1,'test', 'test', 'test', 'test', 'test'),
(2,'test', 'test', 'test', 'test', 'test');
SELECT * FROM t1 LEFT JOIN t2 USING(ID);
ID s1 s2 s3 s4 s5
1 test test test test test
2 test test test test test
UPDATE t1 LEFT JOIN t2 USING(ID) SET s1 = 'changed';
UPDATE t1 JOIN t2 USING(ID) SET s2 = 'changed';
UPDATE t1 LEFT JOIN t2 USING(ID) SET s3 = 'changed';
UPDATE t1 LEFT JOIN t2 USING(ID) SET s4 = 'changed', s5 = 'changed';
SELECT * FROM t1 LEFT JOIN t2 USING(ID);
ID s1 s2 s3 s4 s5
1 changed changed changed changed changed
2 changed changed changed changed changed
DROP TABLE t1, t2;
#
# Multiupdate creates temporary MyISAM table from MEMORY table
#
CREATE TABLE t1 (id INT NOT NULL AUTO_INCREMENT PRIMARY KEY);
CREATE TABLE t2 (id INT, s1 CHAR(255));
INSERT INTO t1 VALUES (0), (0), (0), (0), (0), (0), (0), (0);
INSERT INTO t1 (SELECT 0 FROM t1);
INSERT INTO t1 (SELECT 0 FROM t1);
INSERT INTO t1 (SELECT 0 FROM t1);
INSERT INTO t2 (SELECT ID, 'a' FROM t1);
UPDATE t1 LEFT JOIN t2 USING(id) SET s1 = 'b';
SELECT DISTINCT s1 FROM t1 LEFT JOIN t2 USING(id);
s1
b
DROP TABLE t1, t2;
# End of 5.0 tests
1 change: 1 addition & 0 deletions mysql-test/t/multi_update_tiny_hash-master.opt
@@ -0,0 +1 @@
--set-variable=max_heap_table_size=16384
61 changes: 61 additions & 0 deletions mysql-test/t/multi_update_tiny_hash.test
@@ -0,0 +1,61 @@
#
# Test of update statement that uses many tables,
# --max_heap_table_size=1
#

--disable_warnings
drop table if exists t1, t2;
--enable_warnings

--echo #
--echo # Bug #36676: multiupdate using LEFT JOIN updates only
--echo # first row or fails with an error:
--echo # ERROR 1022 (23000): Can't write; duplicate key in table ''
--echo #

--echo

--echo #
--echo # Multiupdate creates MyISAM temporary table without MEMORY table
--echo #

CREATE TABLE t1 (ID INT);
CREATE TABLE t2 (ID INT,
s1 TEXT, s2 TEXT, s3 VARCHAR(10), s4 TEXT, s5 VARCHAR(10));

INSERT INTO t1 VALUES (1),(2);
INSERT INTO t2 VALUES (1,'test', 'test', 'test', 'test', 'test'),
(2,'test', 'test', 'test', 'test', 'test');

SELECT * FROM t1 LEFT JOIN t2 USING(ID);
UPDATE t1 LEFT JOIN t2 USING(ID) SET s1 = 'changed';
UPDATE t1 JOIN t2 USING(ID) SET s2 = 'changed';
UPDATE t1 LEFT JOIN t2 USING(ID) SET s3 = 'changed';
UPDATE t1 LEFT JOIN t2 USING(ID) SET s4 = 'changed', s5 = 'changed';
SELECT * FROM t1 LEFT JOIN t2 USING(ID);

DROP TABLE t1, t2;

--echo #
--echo # Multiupdate creates temporary MyISAM table from MEMORY table
--echo #

CREATE TABLE t1 (id INT NOT NULL AUTO_INCREMENT PRIMARY KEY);
CREATE TABLE t2 (id INT, s1 CHAR(255));

# insert [1..64] into table `t1`
INSERT INTO t1 VALUES (0), (0), (0), (0), (0), (0), (0), (0);
INSERT INTO t1 (SELECT 0 FROM t1);
INSERT INTO t1 (SELECT 0 FROM t1);
INSERT INTO t1 (SELECT 0 FROM t1);

INSERT INTO t2 (SELECT ID, 'a' FROM t1);

UPDATE t1 LEFT JOIN t2 USING(id) SET s1 = 'b';

SELECT DISTINCT s1 FROM t1 LEFT JOIN t2 USING(id);

DROP TABLE t1, t2;


--echo # End of 5.0 tests
6 changes: 6 additions & 0 deletions sql/sql_update.cc
Expand Up @@ -1440,6 +1440,12 @@ bool multi_update::send_data(List<Item> &not_used_values)
tbl->file->position(tbl->record[0]);
memcpy((char*) tmp_table->field[field_num]->ptr,
(char*) tbl->file->ref, tbl->file->ref_length);
/*
For outer joins a rowid field may have no NOT_NULL_FLAG,
so we have to reset NULL bit for this field.
(set_notnull() resets NULL bit only if available).
*/
tmp_table->field[field_num]->set_notnull();
field_num++;
} while ((tbl= tbl_it++));

Expand Down

0 comments on commit 1b52d48

Please sign in to comment.