mirror of
https://github.com/MariaDB/server.git
synced 2026-10-23 18:49:29 +03:00
MDEV-29510 ASAN errors upon updating a BLOB column twice in one UPDATE
UPDATE t SET a='x', b=a, a='foobarqux' makes b alias a's value buffer (field_conv_memcpy() copies the blob pointer while copy_blobs is off). Storing a again frees that buffer, and b is left dangling. The same happens in INSERT ... ON DUPLICATE KEY UPDATE. fill_record(update=true) now stores a blob by value, unless the source is a plain same-type blob column still in engine memory: nothing frees that, so aliasing stays safe and cheap. Field_blob::store_field() copies such blobs verbatim, skipping the charset check. INSERT ... ON DUPLICATE KEY UPDATE keeps the inserted row's blob pointers in insert_values, pointing into Field_blob::value. Keep those buffers aside while the UPDATE clause runs, so that storing into a column does not overwrite what VALUES() reads later.
This commit is contained in:
@@ -472,4 +472,157 @@ insert into t1 set j = (value (j)) on duplicate key update i = default;
|
||||
insert into t1 set j = (value (j)) on duplicate key update i = default;
|
||||
drop table t1;
|
||||
drop sequence s;
|
||||
#
|
||||
# MDEV-29510: ASAN errors upon updating a BLOB column twice in one UPDATE
|
||||
#
|
||||
CREATE TABLE t (pk INT PRIMARY KEY, a BLOB, b BLOB) ENGINE=Aria;
|
||||
INSERT INTO t VALUES (1,'foobarqux','x'),(2,'baz','qux');
|
||||
INSERT INTO t VALUES (1,'z','z'),(2,'z','z')
|
||||
ON DUPLICATE KEY UPDATE a = 'x', b = a, a = 'foobarqux';
|
||||
SELECT * FROM t ORDER BY pk;
|
||||
pk a b
|
||||
1 foobarqux x
|
||||
2 foobarqux x
|
||||
INSERT INTO t VALUES (1,'v1','v2') ON DUPLICATE KEY UPDATE b = a, a = VALUES(a);
|
||||
SELECT * FROM t ORDER BY pk;
|
||||
pk a b
|
||||
1 v1 foobarqux
|
||||
2 foobarqux x
|
||||
INSERT INTO t SELECT 2,'z','z' ON DUPLICATE KEY UPDATE a = 'x', b = a, a = 'y';
|
||||
SELECT * FROM t ORDER BY pk;
|
||||
pk a b
|
||||
1 v1 foobarqux
|
||||
2 y x
|
||||
INSERT INTO t VALUES (1,'v1','v2')
|
||||
ON DUPLICATE KEY UPDATE b = VALUES(a), a = VALUES(b);
|
||||
SELECT * FROM t WHERE pk=1;
|
||||
pk a b
|
||||
1 v2 v1
|
||||
INSERT INTO t VALUES (1,'v1','v2')
|
||||
ON DUPLICATE KEY UPDATE a = 'xxxxxxxxxxxx', b = VALUES(a);
|
||||
SELECT * FROM t WHERE pk=1;
|
||||
pk a b
|
||||
1 xxxxxxxxxxxx v1
|
||||
INSERT INTO t VALUES (1,'v1','v2')
|
||||
ON DUPLICATE KEY UPDATE a = 'x', b = a, a = VALUES(b);
|
||||
SELECT * FROM t WHERE pk=1;
|
||||
pk a b
|
||||
1 v2 x
|
||||
DROP TABLE t;
|
||||
# NOT NULL blobs and a trigger: the row is stored through other fields
|
||||
CREATE TABLE t (pk INT PRIMARY KEY, a BLOB NOT NULL, b BLOB NOT NULL)
|
||||
ENGINE=Aria;
|
||||
CREATE TRIGGER tr BEFORE INSERT ON t FOR EACH ROW SET @x=1;
|
||||
INSERT INTO t VALUES (1,'v1','v2');
|
||||
INSERT INTO t VALUES (1,'v1','v2')
|
||||
ON DUPLICATE KEY UPDATE b = VALUES(a), a = VALUES(b);
|
||||
SELECT * FROM t;
|
||||
pk a b
|
||||
1 v2 v1
|
||||
INSERT INTO t VALUES (1,'v1','v2')
|
||||
ON DUPLICATE KEY UPDATE a = 'xxxxxxxxxxxx', b = VALUES(a);
|
||||
SELECT * FROM t;
|
||||
pk a b
|
||||
1 xxxxxxxxxxxx v1
|
||||
DROP TABLE t;
|
||||
CREATE TABLE t (pk INT PRIMARY KEY, a BLOB, b BLOB) ENGINE=MyISAM;
|
||||
INSERT INTO t VALUES (1,'foobarqux','x'),(2,'baz','qux');
|
||||
INSERT INTO t VALUES (1,'z','z'),(2,'z','z')
|
||||
ON DUPLICATE KEY UPDATE a = 'x', b = a, a = 'foobarqux';
|
||||
SELECT * FROM t ORDER BY pk;
|
||||
pk a b
|
||||
1 foobarqux x
|
||||
2 foobarqux x
|
||||
INSERT INTO t VALUES (1,'v1','v2') ON DUPLICATE KEY UPDATE b = a, a = VALUES(a);
|
||||
SELECT * FROM t ORDER BY pk;
|
||||
pk a b
|
||||
1 v1 foobarqux
|
||||
2 foobarqux x
|
||||
INSERT INTO t SELECT 2,'z','z' ON DUPLICATE KEY UPDATE a = 'x', b = a, a = 'y';
|
||||
SELECT * FROM t ORDER BY pk;
|
||||
pk a b
|
||||
1 v1 foobarqux
|
||||
2 y x
|
||||
INSERT INTO t VALUES (1,'v1','v2')
|
||||
ON DUPLICATE KEY UPDATE b = VALUES(a), a = VALUES(b);
|
||||
SELECT * FROM t WHERE pk=1;
|
||||
pk a b
|
||||
1 v2 v1
|
||||
INSERT INTO t VALUES (1,'v1','v2')
|
||||
ON DUPLICATE KEY UPDATE a = 'xxxxxxxxxxxx', b = VALUES(a);
|
||||
SELECT * FROM t WHERE pk=1;
|
||||
pk a b
|
||||
1 xxxxxxxxxxxx v1
|
||||
INSERT INTO t VALUES (1,'v1','v2')
|
||||
ON DUPLICATE KEY UPDATE a = 'x', b = a, a = VALUES(b);
|
||||
SELECT * FROM t WHERE pk=1;
|
||||
pk a b
|
||||
1 v2 x
|
||||
DROP TABLE t;
|
||||
# NOT NULL blobs and a trigger: the row is stored through other fields
|
||||
CREATE TABLE t (pk INT PRIMARY KEY, a BLOB NOT NULL, b BLOB NOT NULL)
|
||||
ENGINE=MyISAM;
|
||||
CREATE TRIGGER tr BEFORE INSERT ON t FOR EACH ROW SET @x=1;
|
||||
INSERT INTO t VALUES (1,'v1','v2');
|
||||
INSERT INTO t VALUES (1,'v1','v2')
|
||||
ON DUPLICATE KEY UPDATE b = VALUES(a), a = VALUES(b);
|
||||
SELECT * FROM t;
|
||||
pk a b
|
||||
1 v2 v1
|
||||
INSERT INTO t VALUES (1,'v1','v2')
|
||||
ON DUPLICATE KEY UPDATE a = 'xxxxxxxxxxxx', b = VALUES(a);
|
||||
SELECT * FROM t;
|
||||
pk a b
|
||||
1 xxxxxxxxxxxx v1
|
||||
DROP TABLE t;
|
||||
CREATE TABLE t (pk INT PRIMARY KEY, a BLOB, b BLOB) ENGINE=InnoDB;
|
||||
INSERT INTO t VALUES (1,'foobarqux','x'),(2,'baz','qux');
|
||||
INSERT INTO t VALUES (1,'z','z'),(2,'z','z')
|
||||
ON DUPLICATE KEY UPDATE a = 'x', b = a, a = 'foobarqux';
|
||||
SELECT * FROM t ORDER BY pk;
|
||||
pk a b
|
||||
1 foobarqux x
|
||||
2 foobarqux x
|
||||
INSERT INTO t VALUES (1,'v1','v2') ON DUPLICATE KEY UPDATE b = a, a = VALUES(a);
|
||||
SELECT * FROM t ORDER BY pk;
|
||||
pk a b
|
||||
1 v1 foobarqux
|
||||
2 foobarqux x
|
||||
INSERT INTO t SELECT 2,'z','z' ON DUPLICATE KEY UPDATE a = 'x', b = a, a = 'y';
|
||||
SELECT * FROM t ORDER BY pk;
|
||||
pk a b
|
||||
1 v1 foobarqux
|
||||
2 y x
|
||||
INSERT INTO t VALUES (1,'v1','v2')
|
||||
ON DUPLICATE KEY UPDATE b = VALUES(a), a = VALUES(b);
|
||||
SELECT * FROM t WHERE pk=1;
|
||||
pk a b
|
||||
1 v2 v1
|
||||
INSERT INTO t VALUES (1,'v1','v2')
|
||||
ON DUPLICATE KEY UPDATE a = 'xxxxxxxxxxxx', b = VALUES(a);
|
||||
SELECT * FROM t WHERE pk=1;
|
||||
pk a b
|
||||
1 xxxxxxxxxxxx v1
|
||||
INSERT INTO t VALUES (1,'v1','v2')
|
||||
ON DUPLICATE KEY UPDATE a = 'x', b = a, a = VALUES(b);
|
||||
SELECT * FROM t WHERE pk=1;
|
||||
pk a b
|
||||
1 v2 x
|
||||
DROP TABLE t;
|
||||
# NOT NULL blobs and a trigger: the row is stored through other fields
|
||||
CREATE TABLE t (pk INT PRIMARY KEY, a BLOB NOT NULL, b BLOB NOT NULL)
|
||||
ENGINE=InnoDB;
|
||||
CREATE TRIGGER tr BEFORE INSERT ON t FOR EACH ROW SET @x=1;
|
||||
INSERT INTO t VALUES (1,'v1','v2');
|
||||
INSERT INTO t VALUES (1,'v1','v2')
|
||||
ON DUPLICATE KEY UPDATE b = VALUES(a), a = VALUES(b);
|
||||
SELECT * FROM t;
|
||||
pk a b
|
||||
1 v2 v1
|
||||
INSERT INTO t VALUES (1,'v1','v2')
|
||||
ON DUPLICATE KEY UPDATE a = 'xxxxxxxxxxxx', b = VALUES(a);
|
||||
SELECT * FROM t;
|
||||
pk a b
|
||||
1 xxxxxxxxxxxx v1
|
||||
DROP TABLE t;
|
||||
# End of 10.11 tests
|
||||
|
||||
@@ -360,4 +360,45 @@ insert into t1 set j = (value (j)) on duplicate key update i = default;
|
||||
drop table t1;
|
||||
drop sequence s;
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-29510: ASAN errors upon updating a BLOB column twice in one UPDATE
|
||||
--echo #
|
||||
let $engine= Aria;
|
||||
while ($engine)
|
||||
{
|
||||
eval CREATE TABLE t (pk INT PRIMARY KEY, a BLOB, b BLOB) ENGINE=$engine;
|
||||
INSERT INTO t VALUES (1,'foobarqux','x'),(2,'baz','qux');
|
||||
INSERT INTO t VALUES (1,'z','z'),(2,'z','z')
|
||||
ON DUPLICATE KEY UPDATE a = 'x', b = a, a = 'foobarqux';
|
||||
SELECT * FROM t ORDER BY pk;
|
||||
INSERT INTO t VALUES (1,'v1','v2') ON DUPLICATE KEY UPDATE b = a, a = VALUES(a);
|
||||
SELECT * FROM t ORDER BY pk;
|
||||
INSERT INTO t SELECT 2,'z','z' ON DUPLICATE KEY UPDATE a = 'x', b = a, a = 'y';
|
||||
SELECT * FROM t ORDER BY pk;
|
||||
INSERT INTO t VALUES (1,'v1','v2')
|
||||
ON DUPLICATE KEY UPDATE b = VALUES(a), a = VALUES(b);
|
||||
SELECT * FROM t WHERE pk=1;
|
||||
INSERT INTO t VALUES (1,'v1','v2')
|
||||
ON DUPLICATE KEY UPDATE a = 'xxxxxxxxxxxx', b = VALUES(a);
|
||||
SELECT * FROM t WHERE pk=1;
|
||||
INSERT INTO t VALUES (1,'v1','v2')
|
||||
ON DUPLICATE KEY UPDATE a = 'x', b = a, a = VALUES(b);
|
||||
SELECT * FROM t WHERE pk=1;
|
||||
DROP TABLE t;
|
||||
--echo # NOT NULL blobs and a trigger: the row is stored through other fields
|
||||
eval CREATE TABLE t (pk INT PRIMARY KEY, a BLOB NOT NULL, b BLOB NOT NULL)
|
||||
ENGINE=$engine;
|
||||
CREATE TRIGGER tr BEFORE INSERT ON t FOR EACH ROW SET @x=1;
|
||||
INSERT INTO t VALUES (1,'v1','v2');
|
||||
INSERT INTO t VALUES (1,'v1','v2')
|
||||
ON DUPLICATE KEY UPDATE b = VALUES(a), a = VALUES(b);
|
||||
SELECT * FROM t;
|
||||
INSERT INTO t VALUES (1,'v1','v2')
|
||||
ON DUPLICATE KEY UPDATE a = 'xxxxxxxxxxxx', b = VALUES(a);
|
||||
SELECT * FROM t;
|
||||
DROP TABLE t;
|
||||
let $engine= `SELECT CASE '$engine' WHEN 'Aria' THEN 'MyISAM'
|
||||
WHEN 'MyISAM' THEN 'InnoDB' ELSE '' END`;
|
||||
}
|
||||
|
||||
--echo # End of 10.11 tests
|
||||
|
||||
@@ -844,4 +844,220 @@ id v
|
||||
44 444
|
||||
55 555
|
||||
drop table t1, t2, t3;
|
||||
#
|
||||
# MDEV-29510: ASAN errors upon updating a BLOB column twice in one UPDATE
|
||||
#
|
||||
CREATE TABLE t (pk INT PRIMARY KEY, a BLOB, b BLOB) ENGINE=Aria;
|
||||
INSERT INTO t VALUES (1,'foo','bar'),(2,'baz','qux');
|
||||
UPDATE t SET a = 'x', b = a, a = 'foobarqux';
|
||||
SELECT pk, a, b FROM t ORDER BY pk;
|
||||
pk a b
|
||||
1 foobarqux x
|
||||
2 foobarqux x
|
||||
DROP TABLE t;
|
||||
CREATE TABLE t (pk INT PRIMARY KEY, a BLOB, b BLOB, c BLOB, g TEXT,
|
||||
h TEXT, vb BLOB AS (CONCAT(a,'!')) VIRTUAL,
|
||||
tb TINYBLOB) ENGINE=Aria;
|
||||
INSERT INTO t (pk,a,b,c,g,h) VALUES (1,'foo','bar','baz','tg','th');
|
||||
# sources in the engine row buffer
|
||||
UPDATE t SET b=a, a=b;
|
||||
UPDATE t SET h=g, g=h;
|
||||
SELECT pk, a, b, g, h FROM t;
|
||||
pk a b g h
|
||||
1 foo foo tg tg
|
||||
# sources in a field buffer
|
||||
UPDATE t SET a=CONCAT(a,'x'), b=a, a='y';
|
||||
SELECT pk, a, b FROM t;
|
||||
pk a b
|
||||
1 y foox
|
||||
UPDATE t SET b=a, c=b, a=REPEAT('z',300);
|
||||
SELECT pk, LEFT(a,3), b, c FROM t;
|
||||
pk LEFT(a,3) b c
|
||||
1 zzz y y
|
||||
UPDATE t SET a='m', b=a, c=b, b='n', a='o';
|
||||
SELECT pk, a, b, c FROM t;
|
||||
pk a b c
|
||||
1 o n m
|
||||
# virtual column source
|
||||
UPDATE t SET b=vb, a='longer-than-before';
|
||||
SELECT pk, a, b, vb FROM t;
|
||||
pk a b vb
|
||||
1 longer-than-before o! longer-than-before!
|
||||
# target shorter than the source
|
||||
UPDATE t SET a=REPEAT('a',1000);
|
||||
UPDATE t SET tb=a, a='y';
|
||||
ERROR 22001: Data too long for column 'tb' at row 1
|
||||
SELECT pk, LENGTH(a), tb FROM t;
|
||||
pk LENGTH(a) tb
|
||||
1 1000 NULL
|
||||
DROP TABLE t;
|
||||
# NOT NULL blobs and a trigger: the row is stored through other fields
|
||||
CREATE TABLE t (pk INT PRIMARY KEY, a BLOB NOT NULL, b BLOB NOT NULL)
|
||||
ENGINE=Aria;
|
||||
CREATE TRIGGER tr BEFORE UPDATE ON t FOR EACH ROW SET @x=1;
|
||||
INSERT INTO t VALUES (1,'foo','bar'),(2,'baz','qux');
|
||||
UPDATE t SET a='x', b=a, a=REPEAT('q',300);
|
||||
SELECT pk, LEFT(a,3), b FROM t;
|
||||
pk LEFT(a,3) b
|
||||
1 qqq x
|
||||
2 qqq x
|
||||
UPDATE t SET b=a, a=b;
|
||||
SELECT pk, LEFT(a,3), LEFT(b,3) FROM t;
|
||||
pk LEFT(a,3) LEFT(b,3)
|
||||
1 qqq qqq
|
||||
2 qqq qqq
|
||||
DROP TABLE t;
|
||||
# SIMULTANEOUS_ASSIGNMENT
|
||||
CREATE TABLE t (pk INT PRIMARY KEY, a BLOB, b BLOB) ENGINE=Aria;
|
||||
INSERT INTO t VALUES (1,'foo','bar');
|
||||
SET @save_sql_mode= @@sql_mode;
|
||||
SET sql_mode= CONCAT(@@sql_mode, ',SIMULTANEOUS_ASSIGNMENT');
|
||||
UPDATE t SET b=a, a=b;
|
||||
SELECT * FROM t;
|
||||
pk a b
|
||||
1 bar foo
|
||||
SET sql_mode= @save_sql_mode;
|
||||
DROP TABLE t;
|
||||
CREATE TABLE t (pk INT PRIMARY KEY, a BLOB, b BLOB) ENGINE=MyISAM;
|
||||
INSERT INTO t VALUES (1,'foo','bar'),(2,'baz','qux');
|
||||
UPDATE t SET a = 'x', b = a, a = 'foobarqux';
|
||||
SELECT pk, a, b FROM t ORDER BY pk;
|
||||
pk a b
|
||||
1 foobarqux x
|
||||
2 foobarqux x
|
||||
DROP TABLE t;
|
||||
CREATE TABLE t (pk INT PRIMARY KEY, a BLOB, b BLOB, c BLOB, g TEXT,
|
||||
h TEXT, vb BLOB AS (CONCAT(a,'!')) VIRTUAL,
|
||||
tb TINYBLOB) ENGINE=MyISAM;
|
||||
INSERT INTO t (pk,a,b,c,g,h) VALUES (1,'foo','bar','baz','tg','th');
|
||||
# sources in the engine row buffer
|
||||
UPDATE t SET b=a, a=b;
|
||||
UPDATE t SET h=g, g=h;
|
||||
SELECT pk, a, b, g, h FROM t;
|
||||
pk a b g h
|
||||
1 foo foo tg tg
|
||||
# sources in a field buffer
|
||||
UPDATE t SET a=CONCAT(a,'x'), b=a, a='y';
|
||||
SELECT pk, a, b FROM t;
|
||||
pk a b
|
||||
1 y foox
|
||||
UPDATE t SET b=a, c=b, a=REPEAT('z',300);
|
||||
SELECT pk, LEFT(a,3), b, c FROM t;
|
||||
pk LEFT(a,3) b c
|
||||
1 zzz y y
|
||||
UPDATE t SET a='m', b=a, c=b, b='n', a='o';
|
||||
SELECT pk, a, b, c FROM t;
|
||||
pk a b c
|
||||
1 o n m
|
||||
# virtual column source
|
||||
UPDATE t SET b=vb, a='longer-than-before';
|
||||
SELECT pk, a, b, vb FROM t;
|
||||
pk a b vb
|
||||
1 longer-than-before o! longer-than-before!
|
||||
# target shorter than the source
|
||||
UPDATE t SET a=REPEAT('a',1000);
|
||||
UPDATE t SET tb=a, a='y';
|
||||
ERROR 22001: Data too long for column 'tb' at row 1
|
||||
SELECT pk, LENGTH(a), tb FROM t;
|
||||
pk LENGTH(a) tb
|
||||
1 1000 NULL
|
||||
DROP TABLE t;
|
||||
# NOT NULL blobs and a trigger: the row is stored through other fields
|
||||
CREATE TABLE t (pk INT PRIMARY KEY, a BLOB NOT NULL, b BLOB NOT NULL)
|
||||
ENGINE=MyISAM;
|
||||
CREATE TRIGGER tr BEFORE UPDATE ON t FOR EACH ROW SET @x=1;
|
||||
INSERT INTO t VALUES (1,'foo','bar'),(2,'baz','qux');
|
||||
UPDATE t SET a='x', b=a, a=REPEAT('q',300);
|
||||
SELECT pk, LEFT(a,3), b FROM t;
|
||||
pk LEFT(a,3) b
|
||||
1 qqq x
|
||||
2 qqq x
|
||||
UPDATE t SET b=a, a=b;
|
||||
SELECT pk, LEFT(a,3), LEFT(b,3) FROM t;
|
||||
pk LEFT(a,3) LEFT(b,3)
|
||||
1 qqq qqq
|
||||
2 qqq qqq
|
||||
DROP TABLE t;
|
||||
# SIMULTANEOUS_ASSIGNMENT
|
||||
CREATE TABLE t (pk INT PRIMARY KEY, a BLOB, b BLOB) ENGINE=MyISAM;
|
||||
INSERT INTO t VALUES (1,'foo','bar');
|
||||
SET @save_sql_mode= @@sql_mode;
|
||||
SET sql_mode= CONCAT(@@sql_mode, ',SIMULTANEOUS_ASSIGNMENT');
|
||||
UPDATE t SET b=a, a=b;
|
||||
SELECT * FROM t;
|
||||
pk a b
|
||||
1 bar foo
|
||||
SET sql_mode= @save_sql_mode;
|
||||
DROP TABLE t;
|
||||
CREATE TABLE t (pk INT PRIMARY KEY, a BLOB, b BLOB) ENGINE=InnoDB;
|
||||
INSERT INTO t VALUES (1,'foo','bar'),(2,'baz','qux');
|
||||
UPDATE t SET a = 'x', b = a, a = 'foobarqux';
|
||||
SELECT pk, a, b FROM t ORDER BY pk;
|
||||
pk a b
|
||||
1 foobarqux x
|
||||
2 foobarqux x
|
||||
DROP TABLE t;
|
||||
CREATE TABLE t (pk INT PRIMARY KEY, a BLOB, b BLOB, c BLOB, g TEXT,
|
||||
h TEXT, vb BLOB AS (CONCAT(a,'!')) VIRTUAL,
|
||||
tb TINYBLOB) ENGINE=InnoDB;
|
||||
INSERT INTO t (pk,a,b,c,g,h) VALUES (1,'foo','bar','baz','tg','th');
|
||||
# sources in the engine row buffer
|
||||
UPDATE t SET b=a, a=b;
|
||||
UPDATE t SET h=g, g=h;
|
||||
SELECT pk, a, b, g, h FROM t;
|
||||
pk a b g h
|
||||
1 foo foo tg tg
|
||||
# sources in a field buffer
|
||||
UPDATE t SET a=CONCAT(a,'x'), b=a, a='y';
|
||||
SELECT pk, a, b FROM t;
|
||||
pk a b
|
||||
1 y foox
|
||||
UPDATE t SET b=a, c=b, a=REPEAT('z',300);
|
||||
SELECT pk, LEFT(a,3), b, c FROM t;
|
||||
pk LEFT(a,3) b c
|
||||
1 zzz y y
|
||||
UPDATE t SET a='m', b=a, c=b, b='n', a='o';
|
||||
SELECT pk, a, b, c FROM t;
|
||||
pk a b c
|
||||
1 o n m
|
||||
# virtual column source
|
||||
UPDATE t SET b=vb, a='longer-than-before';
|
||||
SELECT pk, a, b, vb FROM t;
|
||||
pk a b vb
|
||||
1 longer-than-before o! longer-than-before!
|
||||
# target shorter than the source
|
||||
UPDATE t SET a=REPEAT('a',1000);
|
||||
UPDATE t SET tb=a, a='y';
|
||||
ERROR 22001: Data too long for column 'tb' at row 1
|
||||
SELECT pk, LENGTH(a), tb FROM t;
|
||||
pk LENGTH(a) tb
|
||||
1 1000 NULL
|
||||
DROP TABLE t;
|
||||
# NOT NULL blobs and a trigger: the row is stored through other fields
|
||||
CREATE TABLE t (pk INT PRIMARY KEY, a BLOB NOT NULL, b BLOB NOT NULL)
|
||||
ENGINE=InnoDB;
|
||||
CREATE TRIGGER tr BEFORE UPDATE ON t FOR EACH ROW SET @x=1;
|
||||
INSERT INTO t VALUES (1,'foo','bar'),(2,'baz','qux');
|
||||
UPDATE t SET a='x', b=a, a=REPEAT('q',300);
|
||||
SELECT pk, LEFT(a,3), b FROM t;
|
||||
pk LEFT(a,3) b
|
||||
1 qqq x
|
||||
2 qqq x
|
||||
UPDATE t SET b=a, a=b;
|
||||
SELECT pk, LEFT(a,3), LEFT(b,3) FROM t;
|
||||
pk LEFT(a,3) LEFT(b,3)
|
||||
1 qqq qqq
|
||||
2 qqq qqq
|
||||
DROP TABLE t;
|
||||
# SIMULTANEOUS_ASSIGNMENT
|
||||
CREATE TABLE t (pk INT PRIMARY KEY, a BLOB, b BLOB) ENGINE=InnoDB;
|
||||
INSERT INTO t VALUES (1,'foo','bar');
|
||||
SET @save_sql_mode= @@sql_mode;
|
||||
SET sql_mode= CONCAT(@@sql_mode, ',SIMULTANEOUS_ASSIGNMENT');
|
||||
UPDATE t SET b=a, a=b;
|
||||
SELECT * FROM t;
|
||||
pk a b
|
||||
1 bar foo
|
||||
SET sql_mode= @save_sql_mode;
|
||||
DROP TABLE t;
|
||||
# End of MariaDB 10.11 tests
|
||||
|
||||
@@ -746,4 +746,65 @@ select * from t3;
|
||||
|
||||
drop table t1, t2, t3;
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-29510: ASAN errors upon updating a BLOB column twice in one UPDATE
|
||||
--echo #
|
||||
let $engine= Aria;
|
||||
while ($engine)
|
||||
{
|
||||
eval CREATE TABLE t (pk INT PRIMARY KEY, a BLOB, b BLOB) ENGINE=$engine;
|
||||
INSERT INTO t VALUES (1,'foo','bar'),(2,'baz','qux');
|
||||
UPDATE t SET a = 'x', b = a, a = 'foobarqux';
|
||||
SELECT pk, a, b FROM t ORDER BY pk;
|
||||
DROP TABLE t;
|
||||
|
||||
eval CREATE TABLE t (pk INT PRIMARY KEY, a BLOB, b BLOB, c BLOB, g TEXT,
|
||||
h TEXT, vb BLOB AS (CONCAT(a,'!')) VIRTUAL,
|
||||
tb TINYBLOB) ENGINE=$engine;
|
||||
INSERT INTO t (pk,a,b,c,g,h) VALUES (1,'foo','bar','baz','tg','th');
|
||||
--echo # sources in the engine row buffer
|
||||
UPDATE t SET b=a, a=b;
|
||||
UPDATE t SET h=g, g=h;
|
||||
SELECT pk, a, b, g, h FROM t;
|
||||
--echo # sources in a field buffer
|
||||
UPDATE t SET a=CONCAT(a,'x'), b=a, a='y';
|
||||
SELECT pk, a, b FROM t;
|
||||
UPDATE t SET b=a, c=b, a=REPEAT('z',300);
|
||||
SELECT pk, LEFT(a,3), b, c FROM t;
|
||||
UPDATE t SET a='m', b=a, c=b, b='n', a='o';
|
||||
SELECT pk, a, b, c FROM t;
|
||||
--echo # virtual column source
|
||||
UPDATE t SET b=vb, a='longer-than-before';
|
||||
SELECT pk, a, b, vb FROM t;
|
||||
--echo # target shorter than the source
|
||||
UPDATE t SET a=REPEAT('a',1000);
|
||||
--error ER_DATA_TOO_LONG
|
||||
UPDATE t SET tb=a, a='y';
|
||||
SELECT pk, LENGTH(a), tb FROM t;
|
||||
DROP TABLE t;
|
||||
|
||||
--echo # NOT NULL blobs and a trigger: the row is stored through other fields
|
||||
eval CREATE TABLE t (pk INT PRIMARY KEY, a BLOB NOT NULL, b BLOB NOT NULL)
|
||||
ENGINE=$engine;
|
||||
CREATE TRIGGER tr BEFORE UPDATE ON t FOR EACH ROW SET @x=1;
|
||||
INSERT INTO t VALUES (1,'foo','bar'),(2,'baz','qux');
|
||||
UPDATE t SET a='x', b=a, a=REPEAT('q',300);
|
||||
SELECT pk, LEFT(a,3), b FROM t;
|
||||
UPDATE t SET b=a, a=b;
|
||||
SELECT pk, LEFT(a,3), LEFT(b,3) FROM t;
|
||||
DROP TABLE t;
|
||||
|
||||
--echo # SIMULTANEOUS_ASSIGNMENT
|
||||
eval CREATE TABLE t (pk INT PRIMARY KEY, a BLOB, b BLOB) ENGINE=$engine;
|
||||
INSERT INTO t VALUES (1,'foo','bar');
|
||||
SET @save_sql_mode= @@sql_mode;
|
||||
SET sql_mode= CONCAT(@@sql_mode, ',SIMULTANEOUS_ASSIGNMENT');
|
||||
UPDATE t SET b=a, a=b;
|
||||
SELECT * FROM t;
|
||||
SET sql_mode= @save_sql_mode;
|
||||
DROP TABLE t;
|
||||
let $engine= `SELECT CASE '$engine' WHEN 'Aria' THEN 'MyISAM'
|
||||
WHEN 'MyISAM' THEN 'InnoDB' ELSE '' END`;
|
||||
}
|
||||
|
||||
--echo # End of MariaDB 10.11 tests
|
||||
|
||||
10
sql/field.cc
10
sql/field.cc
@@ -8807,6 +8807,16 @@ int Field_blob::copy_value(Field_blob *from)
|
||||
}
|
||||
|
||||
|
||||
bool Field_blob::blob_copy_is_verbatim(const Field *from) const
|
||||
{
|
||||
return type() == MYSQL_TYPE_BLOB && from->type() == MYSQL_TYPE_BLOB &&
|
||||
from->pack_length() == Field_blob::pack_length() &&
|
||||
from->charset() == field_charset() &&
|
||||
!compression_method() && !from->compression_method() &&
|
||||
!f_is_hex_escape(flags) && !table->blob_storage;
|
||||
}
|
||||
|
||||
|
||||
int Field_blob::store(const char *from,size_t length,CHARSET_INFO *cs)
|
||||
{
|
||||
DBUG_ASSERT(marked_for_write_or_computed());
|
||||
|
||||
32
sql/field.h
32
sql/field.h
@@ -4517,6 +4517,15 @@ public:
|
||||
return do_copy_blob;
|
||||
return get_identical_copy_func();
|
||||
}
|
||||
/* Is 'data' in the buffer of 'value' (overwritten or freed by a store) */
|
||||
bool in_value(const char *data) const
|
||||
{
|
||||
return value.is_alloced() && data >= value.ptr() &&
|
||||
data < value.ptr() + value.alloced_length();
|
||||
}
|
||||
bool data_in_value() const { return in_value((const char*) get_ptr()); }
|
||||
/* Is 'from' a blob that is stored as is, without conversion or truncation */
|
||||
bool blob_copy_is_verbatim(const Field *from) const;
|
||||
int store_field(Field *from) override
|
||||
{ // Be sure the value is stored
|
||||
if (field_charset() == &my_charset_bin &&
|
||||
@@ -4527,9 +4536,28 @@ public:
|
||||
value.copy(tmp.ptr(), tmp.length(), &my_charset_bin);
|
||||
return store(value.ptr(), value.length(), &my_charset_bin);
|
||||
}
|
||||
if (table->copy_blobs)
|
||||
{
|
||||
/* 'value' is the copy target: read the source into a local String */
|
||||
String tmp;
|
||||
from->val_str(&tmp);
|
||||
if (tmp.length() && blob_copy_is_verbatim(from))
|
||||
{
|
||||
/* The data is already well-formed and fits: skip the charset check */
|
||||
DBUG_ASSERT(marked_for_write_or_computed());
|
||||
DBUG_ASSERT(!in_value(tmp.ptr()));
|
||||
if (value.copy(tmp.ptr(), tmp.length(), &my_charset_bin))
|
||||
{
|
||||
bzero(ptr, Field_blob::pack_length());
|
||||
return -1;
|
||||
}
|
||||
set_ptr(value.length(), (uchar*) value.ptr());
|
||||
return 0;
|
||||
}
|
||||
return store(tmp.ptr(), tmp.length(), from->charset());
|
||||
}
|
||||
from->val_str(&value);
|
||||
if (table->copy_blobs ||
|
||||
(!value.is_alloced() && from->is_varchar_and_in_write_set()))
|
||||
if (!value.is_alloced() && from->is_varchar_and_in_write_set())
|
||||
value.copy();
|
||||
return store(value.ptr(), value.length(), from->charset());
|
||||
}
|
||||
|
||||
@@ -9115,6 +9115,46 @@ static void unwind_stored_field_offsets(const List<Item> &fields, Item_field *en
|
||||
******************************************************************************/
|
||||
|
||||
|
||||
/*
|
||||
Blob assignments in UPDATE alias the source buffer. That breaks for
|
||||
SET a=.., b=a, a=.. : b points into a's buffer, which storing a again
|
||||
overwrites or frees.
|
||||
The guard makes the assignments of the SET list that need it store by value.
|
||||
*/
|
||||
class Copy_blobs_guard
|
||||
{
|
||||
TABLE *table;
|
||||
bool saved, active;
|
||||
public:
|
||||
Copy_blobs_guard(TABLE *table_arg, bool active_arg)
|
||||
: table(table_arg), saved(table_arg->copy_blobs), active(active_arg) {}
|
||||
void set(bool copy) { if (active) table->copy_blobs= saved || copy; }
|
||||
void restore() { table->copy_blobs= saved; }
|
||||
~Copy_blobs_guard() { restore(); }
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
A blob taken as is from a column of a row read from the engine points into
|
||||
engine memory, which stays valid until the row is written. Anything else
|
||||
may point into a buffer owned by a field, or needs conversion, and must be
|
||||
copied.
|
||||
*/
|
||||
static bool blob_source_needs_copy(Field *to, Item *value)
|
||||
{
|
||||
if (!(to->flags & BLOB_FLAG))
|
||||
return false;
|
||||
Item *src= value->real_item();
|
||||
if (src->type() != Item::FIELD_ITEM)
|
||||
return true;
|
||||
Field *from= static_cast<Item_field*>(src)->result_field;
|
||||
Field_blob *to_blob= static_cast<Field_blob*>(to);
|
||||
/* blob_copy_is_verbatim() makes 'from' a Field_blob for the cast below */
|
||||
return !to_blob->blob_copy_is_verbatim(from) || from->vcol_info ||
|
||||
static_cast<Field_blob*>(from)->data_in_value();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Fill the fields of a table with the values of an Item list
|
||||
|
||||
@@ -9150,6 +9190,8 @@ fill_record(THD *thd, TABLE *table_arg, List<Item> &fields, List<Item> &values,
|
||||
bool save_abort_on_warning= thd->abort_on_warning;
|
||||
bool save_no_errors= thd->no_errors;
|
||||
DBUG_ENTER("fill_record");
|
||||
Copy_blobs_guard copy_blobs_guard(table_arg,
|
||||
update && table_arg->s->blob_fields);
|
||||
|
||||
thd->no_errors= ignore_errors;
|
||||
/*
|
||||
@@ -9190,6 +9232,7 @@ fill_record(THD *thd, TABLE *table_arg, List<Item> &fields, List<Item> &values,
|
||||
{
|
||||
if (!skip_sys_field)
|
||||
{
|
||||
copy_blobs_guard.set(blob_source_needs_copy(rfield, value));
|
||||
if (value->save_in_field(rfield, 0) < 0 && !ignore_errors)
|
||||
{
|
||||
my_message(ER_UNKNOWN_ERROR, ER_THD(thd, ER_UNKNOWN_ERROR), MYF(0));
|
||||
@@ -9208,6 +9251,9 @@ fill_record(THD *thd, TABLE *table_arg, List<Item> &fields, List<Item> &values,
|
||||
}
|
||||
}
|
||||
|
||||
/* Virtual and default fields are filled after the SET list: no copies. */
|
||||
copy_blobs_guard.restore();
|
||||
|
||||
if (update && thd->variables.sql_mode & MODE_SIMULTANEOUS_ASSIGNMENT)
|
||||
unwind_stored_field_offsets(fields, NULL);
|
||||
|
||||
|
||||
@@ -2169,6 +2169,54 @@ int Write_record::replace_row(ha_rows *inserted, ha_rows *deleted)
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
insert_values keeps the blob pointers of the row being inserted. They point
|
||||
into the buffers of Field_blob::value, which storing into the fields in the
|
||||
UPDATE clause would overwrite or free. Keep the buffers aside while the
|
||||
update runs. With BEFORE triggers, NOT NULL columns are stored through the
|
||||
fields of TABLE::field_to_fill(), so their buffers are kept too.
|
||||
*/
|
||||
class Insert_values_blobs
|
||||
{
|
||||
TABLE *table;
|
||||
String *held;
|
||||
bool oom;
|
||||
public:
|
||||
Insert_values_blobs(TABLE *table_arg)
|
||||
: table(table_arg), held(NULL), oom(false)
|
||||
{
|
||||
uint n= table->s->blob_fields;
|
||||
if (!n)
|
||||
return;
|
||||
if (!(held= (String*) my_malloc(PSI_INSTRUMENT_ME, 2 * n * sizeof(String),
|
||||
MYF(MY_WME))))
|
||||
{
|
||||
oom= true;
|
||||
return;
|
||||
}
|
||||
Field **fill= table->field_to_fill();
|
||||
for (uint i= 0; i < 2 * n; i++)
|
||||
::new (&held[i]) String;
|
||||
for (uint i= 0; i < n; i++)
|
||||
{
|
||||
uint fn= table->s->blob_field[i];
|
||||
static_cast<Field_blob*>(table->field[fn])->swap(held[i], false);
|
||||
if (fill[fn] != table->field[fn])
|
||||
static_cast<Field_blob*>(fill[fn])->swap(held[n + i], false);
|
||||
}
|
||||
}
|
||||
~Insert_values_blobs()
|
||||
{
|
||||
for (uint i= 0; held && i < 2 * table->s->blob_fields; i++)
|
||||
held[i].~String();
|
||||
my_free(held);
|
||||
}
|
||||
Insert_values_blobs(const Insert_values_blobs&)= delete;
|
||||
Insert_values_blobs &operator=(const Insert_values_blobs&)= delete;
|
||||
bool failed() const { return oom; }
|
||||
};
|
||||
|
||||
|
||||
int Write_record::insert_on_duplicate_update(ha_rows *inserted,
|
||||
ha_rows *updated)
|
||||
{
|
||||
@@ -2188,6 +2236,9 @@ int Write_record::insert_on_duplicate_update(ha_rows *inserted,
|
||||
*/
|
||||
DBUG_ASSERT(table->insert_values != NULL);
|
||||
store_record(table,insert_values);
|
||||
Insert_values_blobs insert_values_blobs(table);
|
||||
if (unlikely(insert_values_blobs.failed()))
|
||||
return restore_on_error();
|
||||
restore_record(table,record[1]);
|
||||
table->reset_default_fields();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user