Skip to content

Commit 9b325e1

Browse files
MDEV-39179: Incorrect NULL handling in UPDATE ... RETURNING result
Analysis: OLD_VALUE() swapped only field->ptr, leaving null_ptr pointing to the current record. This caused incorrect NULL results. Fix: Store null_ptr_old for record[1] and swap it together with ptr to preserve correct NULL semantics.
1 parent 85e4f5c commit 9b325e1

9 files changed

Lines changed: 57 additions & 10 deletions

File tree

mysql-test/main/update.result

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1137,4 +1137,15 @@ UPDATE t3 SET a = 10 RETURNING OLD_VALUE(a) AS old_value;
11371137
old_value
11381138
1
11391139
DROP TABLE t3;
1140+
#
1141+
# MDEV-39179: Incorrect NULL handling in UPDATE ... RETURNING result
1142+
#
1143+
CREATE OR REPLACE TABLE t1(a VARCHAR(10));
1144+
INSERT INTO t1 VALUES('') RETURNING *;
1145+
a
1146+
1147+
UPDATE t1 SET a=NULL RETURNING OLD_VALUE(a) AS old, a AS new;
1148+
old new
1149+
NULL
1150+
DROP TABLE t1;
11401151
# End of 13.0 test

mysql-test/main/update.test

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -995,4 +995,14 @@ UPDATE t3 SET a = 10 RETURNING OLD_VALUE(a) AS old_value;
995995

996996
DROP TABLE t3;
997997

998+
--echo #
999+
--echo # MDEV-39179: Incorrect NULL handling in UPDATE ... RETURNING result
1000+
--echo #
1001+
1002+
CREATE OR REPLACE TABLE t1(a VARCHAR(10));
1003+
1004+
INSERT INTO t1 VALUES('') RETURNING *;
1005+
UPDATE t1 SET a=NULL RETURNING OLD_VALUE(a) AS old, a AS new;
1006+
DROP TABLE t1;
1007+
9981008
--echo # End of 13.0 test

sql/field.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1934,8 +1934,8 @@ String *Field::val_int_as_str(String *val_buffer, bool unsigned_val)
19341934
Field::Field(uchar *ptr_arg,uint32 length_arg,uchar *null_ptr_arg,
19351935
uchar null_bit_arg,
19361936
utype unireg_check_arg, const LEX_CSTRING *field_name_arg)
1937-
:ptr(ptr_arg),
1938-
null_ptr(null_ptr_arg), table(0), orig_table(0),
1937+
:ptr(ptr_arg), ptr_old(nullptr),
1938+
null_ptr(null_ptr_arg), null_ptr_old(nullptr), table(0), orig_table(0),
19391939
table_name(0), field_name(*field_name_arg), option_list(0),
19401940
option_struct(0), key_start(0), part_of_key(0),
19411941
part_of_key_not_clustered(0), vcol_direct_part_of_key(0), part_of_sortkey(0),

sql/field.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -809,8 +809,10 @@ class Field: public Value_source
809809
/**
810810
Byte where the @c NULL bit is stored inside a record. If this Field is a
811811
@c NOT @c NULL field, this member is @c NULL.
812+
null_ptr_old points to old field.
812813
*/
813814
uchar *null_ptr;
815+
uchar *null_ptr_old;
814816
/*
815817
Note that you can use table->in_use as replacement for current_thd member
816818
only inside of val_*() and store() members (e.g. you can't use it in cons)

sql/item.cc

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7966,13 +7966,19 @@ bool Item_old_field::send(Protocol *protocol, st_value *buffer)
79667966
{
79677967
bool result;
79687968

7969-
std::swap(field->ptr_old, field->ptr);
7969+
change_field_ptr();
79707970
result= Item_field::send(protocol, buffer);
7971-
std::swap(field->ptr_old, field->ptr);
7971+
change_field_ptr();
79727972

79737973
return result;
79747974
}
79757975

7976+
void Item_old_field::change_field_ptr()
7977+
{
7978+
std::swap(field->ptr_old, field->ptr);
7979+
std::swap(field->null_ptr, field->null_ptr_old);
7980+
}
7981+
79767982

79777983
bool Item_old_field::fix_fields(THD *thd, Item **reference)
79787984
{
@@ -7985,6 +7991,16 @@ bool Item_old_field::fix_fields(THD *thd, Item **reference)
79857991
{
79867992
field->ptr_old= field->table->record[1] +
79877993
field->offset(field->table->record[0]);
7994+
if (field->null_ptr)
7995+
{
7996+
field->null_ptr_old =
7997+
field->table->record[1] +
7998+
(field->null_ptr - field->table->record[0]);
7999+
}
8000+
else
8001+
{
8002+
field->null_ptr_old = nullptr;
8003+
}
79888004
}
79898005
else
79908006
{

sql/item.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4114,6 +4114,7 @@ class Item_old_field: public Item_field
41144114
bool send(Protocol *protocol, st_value *buffer) override;
41154115
Type type() const override { return FIELD_OLD_ITEM; }
41164116
bool fix_fields(THD *, Item **) override;
4117+
void change_field_ptr();
41174118

41184119
private:
41194120
uchar *saved_row_ref;

sql/sql_update.cc

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,7 @@ bool Sql_cmd_update::update_single_table(THD *thd)
391391
Explain_update *explain;
392392
query_plan.index= MAX_KEY;
393393
query_plan.using_filesort= FALSE;
394+
bool record_was_same, need_update, trg_skip_row;
394395

395396
// For System Versioning (may need to insert new fields to a table).
396397
ha_rows rows_inserted= 0;
@@ -977,7 +978,7 @@ bool Sql_cmd_update::update_single_table(THD *thd)
977978
if (unlikely(returning_result->send_result_set_metadata(
978979
thd->lex->returning()->returning_list,
979980
Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)))
980-
error= 1;
981+
goto error;
981982
}
982983

983984
while (!(error=info.read_record()) && !thd->killed)
@@ -996,7 +997,7 @@ bool Sql_cmd_update::update_single_table(THD *thd)
996997
cut_fields_for_portion_of_time(thd, table,
997998
table_list->period_conditions);
998999

999-
bool trg_skip_row= false;
1000+
trg_skip_row= false;
10001001
if (fill_record_n_invoke_before_triggers(thd, table, *fields, *values, 0,
10011002
TRG_EVENT_UPDATE,
10021003
&trg_skip_row))
@@ -1011,8 +1012,8 @@ bool Sql_cmd_update::update_single_table(THD *thd)
10111012

10121013
found++;
10131014

1014-
bool record_was_same= false;
1015-
bool need_update= !can_compare_record || compare_record(table);
1015+
record_was_same= false;
1016+
need_update= !can_compare_record || compare_record(table);
10161017

10171018
if (need_update)
10181019
{
@@ -3262,7 +3263,10 @@ bool Sql_cmd_update::execute_inner(THD *thd)
32623263
/* This is UPDATE ... RETURNING. It will return output to the client */
32633264
if (thd->lex->analyze_stmt)
32643265
{
3265-
returning_result= new (thd->mem_root) select_send_analyze(thd);
3266+
if (!(returning_result= new (thd->mem_root) select_send_analyze(thd)))
3267+
{
3268+
return true;
3269+
}
32663270
save_protocol= thd->protocol;
32673271
thd->protocol= new Protocol_discard(thd);
32683272
}
@@ -3294,6 +3298,7 @@ bool Sql_cmd_update::execute_inner(THD *thd)
32943298
{
32953299
delete thd->protocol;
32963300
thd->protocol= save_protocol;
3301+
save_protocol= nullptr;
32973302
}
32983303
if (unlikely(res))
32993304
{

sql/sql_update.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ class Sql_cmd_update final : public Sql_cmd_dml
4848
ha_rows found{0}, updated{0};
4949
Sql_cmd_update(bool multitable_arg)
5050
: orig_multitable(multitable_arg), multitable(multitable_arg),
51-
save_protocol(nullptr)
51+
returning_result(nullptr), save_protocol(nullptr)
5252
{}
5353

5454
enum_sql_command sql_command_code() const override

sql/sql_yacc.yy

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9935,6 +9935,7 @@ expr:
99359935
lex->sql_command == SQLCOM_UPDATE_MULTI))
99369936
{
99379937
my_error(ER_WRONG_USAGE, MYF(0), "OLD_VALUE", "non-UPDATE");
9938+
MYSQL_YYABORT;
99389939
}
99399940
$$= new (thd->mem_root)
99409941
Item_old_field(thd,
@@ -14583,6 +14584,7 @@ opt_returning:
1458314584
{
1458414585
my_error(ER_NOT_SUPPORTED_YET, MYF(0),
1458514586
"RETURNING for multi-table UPDATE");
14587+
MYSQL_YYABORT;
1458614588
}
1458714589

1458814590
/*

0 commit comments

Comments
 (0)