1
0
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:
forkfun
2026-07-14 13:48:18 +02:00
parent 75fe7a5118
commit f05761934b
8 changed files with 608 additions and 2 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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());

View File

@@ -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());
}

View File

@@ -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);

View File

@@ -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();