Skip to content

Commit

Permalink
bugfix: UPDATE and virtual BLOBs
Browse files Browse the repository at this point in the history
When updating a table with virtual BLOB columns, the following might happen:
- an old record is read from the table, it has no virtual blob values
- update_virtual_fields() is run, vcol blob gets its value into the
  record. But only a pointer to the value is in the table->record[0],
  the value is in Field_blob::value String (but it doesn't have to be!
  it can be in the record, if the column is just a copy of another
  columns: ... b VARCHAR, c BLOB AS (b) ...)
- store_record(table,record[1]), old record now is in record[1]
- fill_record() prepares new values in record[0], vcol blob is updated,
  new value replaces the old one in the Field_blob::value
- now both record[1] and record[0] have a pointer that points to the
  *new* vcol blob value. Or record[1] has a pointer to nowhere if
  Field_blob::value had to realloc.

To resolve this we unlink vcol blobs from the pointer to the
data (in the record[1]). Because the value is not *always* in
the Field_blob::value String, we need to remember what blobs
were unlinked. The orphan memory must be freed manually.

To complicate the matter, ha_update_row() is also used in
multi-update, in REPLACE, in INSERT ... ON DUPLICATE KEY UPDATE,
also on REPLACE ... SELECT, REPLACE DELAYED, and LOAD DATA REPLACE, etc
  • Loading branch information
vuvova committed Dec 12, 2016
1 parent aebb103 commit f73bdb6
Show file tree
Hide file tree
Showing 8 changed files with 301 additions and 7 deletions.
100 changes: 100 additions & 0 deletions mysql-test/suite/vcol/r/update.result
Expand Up @@ -54,3 +54,103 @@ select * from t;
a b c
9 10 10
drop table t, t2;
create table t1 (a int, b int, c int, d int, e int);
insert t1 values (1,2,3,4,5), (1,2,3,4,5);
create table t (a int primary key,
b int, c blob as (b), index (c(57)),
d blob, e blob as (d), index (e(57)))
replace select * from t1;
Warnings:
Warning 1906 The value specified for computed column 'c' in table 't' ignored
Warning 1906 The value specified for computed column 'e' in table 't' ignored
Warning 1906 The value specified for computed column 'c' in table 't' ignored
Warning 1906 The value specified for computed column 'e' in table 't' ignored
check table t;
Table Op Msg_type Msg_text
test.t check status OK
select * from t;
a b c d e
1 2 2 4 4
update t set a=10, b=1, d=1;
check table t;
Table Op Msg_type Msg_text
test.t check status OK
select * from t;
a b c d e
10 1 1 1 1
replace t (a,b,d) values (10,2,2);
check table t;
Table Op Msg_type Msg_text
test.t check status OK
select * from t;
a b c d e
10 2 2 2 2
insert t(a,b,d) values (10) on duplicate key update b=3;
ERROR 21S01: Column count doesn't match value count at row 1
insert t(a,b,d) values (10,2,2) on duplicate key update b=3, d=3;
check table t;
Table Op Msg_type Msg_text
test.t check status OK
select * from t;
a b c d e
10 3 3 3 3
replace t (a,b,d) select 10,4,4;
check table t;
Table Op Msg_type Msg_text
test.t check status OK
select * from t;
a b c d e
10 4 4 4 4
insert t(a,b,d) select 10,4,4 on duplicate key update b=5, d=5;
check table t;
Table Op Msg_type Msg_text
test.t check status OK
select * from t;
a b c d e
10 5 5 5 5
replace delayed t (a,b,d) values (10,6,6);
flush tables;
check table t;
Table Op Msg_type Msg_text
test.t check status OK
select * from t;
a b c d e
10 6 6 6 6
insert delayed t(a,b,d) values (10,6,6) on duplicate key update b=7, d=7;
flush tables;
check table t;
Table Op Msg_type Msg_text
test.t check status OK
select * from t;
a b c d e
10 7 7 7 7
load data infile 'MYSQLTEST_VARDIR/tmp/vblobs.txt' replace into table t;
check table t;
Table Op Msg_type Msg_text
test.t check status OK
select * from t;
a b c d e
10 8 8 8 8
update t set a=11, b=9, d=9 where a>5;
check table t;
Table Op Msg_type Msg_text
test.t check status OK
select * from t;
a b c d e
11 9 9 9 9
create table t2 select * from t;
update t, t2 set t.b=10, t.d=10 where t.a=t2.a;
check table t;
Table Op Msg_type Msg_text
test.t check status OK
select * from t;
a b c d e
11 10 10 10 10
update t, t tt set t.b=11, tt.d=11 where t.a=tt.a;
check table t;
Table Op Msg_type Msg_text
test.t check status OK
select * from t;
a b c d e
11 11 11 11 11
drop table t, t1, t2;
45 changes: 45 additions & 0 deletions mysql-test/suite/vcol/t/update.test
Expand Up @@ -65,3 +65,48 @@ create table t2 select * from t;
update t, t2 set t.b=10 where t.a=t2.a;
check table t; select * from t;
drop table t, t2;

#
# blobs
# This tests BLOB_VALUE_ORPHANAGE
#
create table t1 (a int, b int, c int, d int, e int);
insert t1 values (1,2,3,4,5), (1,2,3,4,5);
create table t (a int primary key,
b int, c blob as (b), index (c(57)),
d blob, e blob as (d), index (e(57)))
replace select * from t1;
check table t; select * from t;
update t set a=10, b=1, d=1;
check table t; select * from t;
replace t (a,b,d) values (10,2,2);
check table t; select * from t;
--error ER_WRONG_VALUE_COUNT_ON_ROW
insert t(a,b,d) values (10) on duplicate key update b=3;
insert t(a,b,d) values (10,2,2) on duplicate key update b=3, d=3;
check table t; select * from t;
replace t (a,b,d) select 10,4,4;
check table t; select * from t;
insert t(a,b,d) select 10,4,4 on duplicate key update b=5, d=5;
check table t; select * from t;
replace delayed t (a,b,d) values (10,6,6);
flush tables;
check table t; select * from t;
insert delayed t(a,b,d) values (10,6,6) on duplicate key update b=7, d=7;
flush tables;
check table t; select * from t;
--write_file $MYSQLTEST_VARDIR/tmp/vblobs.txt
10 8 foo 8 foo
EOF
--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
--eval load data infile '$MYSQLTEST_VARDIR/tmp/vblobs.txt' replace into table t
--remove_file $MYSQLTEST_VARDIR/tmp/vblobs.txt
check table t; select * from t;
update t set a=11, b=9, d=9 where a>5;
check table t; select * from t;
create table t2 select * from t;
update t, t2 set t.b=10, t.d=10 where t.a=t2.a;
check table t; select * from t;
update t, t tt set t.b=11, tt.d=11 where t.a=tt.a;
check table t; select * from t;
drop table t, t1, t2;
3 changes: 3 additions & 0 deletions sql/field.h
Expand Up @@ -3324,6 +3324,9 @@ class Field_blob :public Field_longstr {
uint max_packed_col_length(uint max_length);
void free() { value.free(); }
inline void clear_temporary() { bzero((uchar*) &value, sizeof(value)); }
inline bool owns_ptr(uchar* p) const { return p == (uchar*)value.ptr(); }
inline void own_value_ptr()
{ value.reset((char*)get_ptr(), get_length(), get_length(), value.charset()); }
uint size_of() const { return sizeof(*this); }
bool has_charset(void) const
{ return charset() == &my_charset_bin ? FALSE : TRUE; }
Expand Down
99 changes: 97 additions & 2 deletions sql/sql_class.h
Expand Up @@ -201,6 +201,99 @@ typedef struct st_user_var_events
bool unsigned_flag;
} BINLOG_USER_VAR_EVENT;


/*
When updating a table with virtual BLOB columns, the following might happen:
- an old record is read from the table, it has no vcol blob.
- update_virtual_fields() is run, vcol blob gets its value into the
record. But only a pointer to the value is in the table->record[0],
the value is in Field_blob::value String (or, it can be elsewhere!)
- store_record(table,record[1]), old record now is in record[1]
- fill_record() prepares new values in record[0], vcol blob is updated,
new value replaces the old one in the Field_blob::value
- now both record[1] and record[0] have a pointer that points to the
*new* vcol blob value. Or record[1] has a pointer to nowhere if
Field_blob::value had to realloc.
To resolve this we unlink vcol blobs from the pointer to the
data (in the record[1]). The orphan memory must be freed manually
(but, again, only if it was owned by Field_blob::value String).
With REPLACE and INSERT ... ON DUP KEY UPATE it's even more complex.
There is no store_record(table,record[1]), instead the row is read
directly into record[1].
*/
struct BLOB_VALUE_ORPHANAGE {
MY_BITMAP map;
TABLE *table;
BLOB_VALUE_ORPHANAGE() { map.bitmap= NULL; }
~BLOB_VALUE_ORPHANAGE() { free(); }
bool init(TABLE *table_arg)
{
table= table_arg;
if (table->s->virtual_fields && table->s->blob_fields)
return bitmap_init(&map, NULL, table->s->virtual_fields, FALSE);
map.bitmap= NULL;
return 0;
}
void free() { bitmap_free(&map); }

/** Remove blob's ownership from blob value memory
@note the memory becomes orphaned, it needs to be freed using
free_orphans() or re-attached back to blobs using adopt_orphans()
*/
void make_orphans()
{
DBUG_ASSERT(!table || !table->s->virtual_fields || !table->s->blob_fields || map.bitmap);
if (!map.bitmap)
return;
for (Field **ptr=table->vfield; *ptr; ptr++)
{
Field_blob *vb= (Field_blob*)(*ptr);
if (!(vb->flags & BLOB_FLAG) || !vb->owns_ptr(vb->get_ptr()))
continue;
bitmap_set_bit(&map, ptr - table->vfield);
vb->clear_temporary();
}
}

/** Frees orphaned blob values
@note It is assumed that value pointers are in table->record[1], while
Field_blob::ptr's point to table->record[0] as usual
*/
void free_orphans()
{
DBUG_ASSERT(!table || !table->s->virtual_fields || !table->s->blob_fields || map.bitmap);
if (!map.bitmap)
return;
for (Field **ptr=table->vfield; *ptr; ptr++)
{
Field_blob *vb= (Field_blob*)(*ptr);
if (vb->flags & BLOB_FLAG && bitmap_fast_test_and_clear(&map, ptr - table->vfield))
my_free(vb->get_ptr(table->s->rec_buff_length));
}
DBUG_ASSERT(bitmap_is_clear_all(&map));
}

/** Restores blob's ownership over previously orphaned values */
void adopt_orphans()
{
DBUG_ASSERT(!table || !table->s->virtual_fields || !table->s->blob_fields || map.bitmap);
if (!map.bitmap)
return;
for (Field **ptr=table->vfield; *ptr; ptr++)
{
Field_blob *vb= (Field_blob*)(*ptr);
if (vb->flags & BLOB_FLAG && bitmap_fast_test_and_clear(&map, ptr - table->vfield))
vb->own_value_ptr();
}
DBUG_ASSERT(bitmap_is_clear_all(&map));
}
};


/*
The COPY_INFO structure is used by INSERT/REPLACE code.
The schema of the row counting by the INSERT/INSERT ... ON DUPLICATE KEY
Expand All @@ -213,7 +306,7 @@ typedef struct st_user_var_events
of the INSERT ... ON DUPLICATE KEY UPDATE no matter whether the row
was actually changed or not.
*/
typedef struct st_copy_info {
struct COPY_INFO {
ha_rows records; /**< Number of processed records */
ha_rows deleted; /**< Number of deleted records */
ha_rows updated; /**< Number of updated records */
Expand All @@ -229,7 +322,8 @@ typedef struct st_copy_info {
/* for VIEW ... WITH CHECK OPTION */
TABLE_LIST *view;
TABLE_LIST *table_list; /* Normal table */
} COPY_INFO;
BLOB_VALUE_ORPHANAGE vblobs0, vblobs1; // vcol blobs of record[0] and record[1]
};


class Key_part_spec :public Sql_alloc {
Expand Down Expand Up @@ -5317,6 +5411,7 @@ class multi_update :public select_result_interceptor
TABLE_LIST *update_tables, *table_being_updated;
TABLE **tmp_tables, *main_table, *table_to_update;
TMP_TABLE_PARAM *tmp_table_param;
BLOB_VALUE_ORPHANAGE *vblobs;
ha_rows updated, found;
List <Item> *fields, *values;
List <Item> **fields_for_table, **values_for_table;
Expand Down

0 comments on commit f73bdb6

Please sign in to comment.