Commit e9cb71fc authored by Gleb Shchepa's avatar Gleb Shchepa

Bug#26020: User-Defined Variables are not consistent with

           columns data types

The "SELECT @lastId, @lastId := Id FROM t" query returns
different result sets depending on the type of the Id column
(INT or BIGINT).

Note: this fix doesn't cover the case when a select query
references an user variable and stored function that
updates a value of that variable, in this case a result
is indeterminate.


The server uses incorrect assumption about a constantness of
an user variable value as a select list item: 

The server caches a last query number where that variable
was changed and compares this number with a current query
number. If these numbers are different, the server guesses,
that the variable is not updating in the current query, so
a respective select list item is a constant. However, in some
common cases the server updates cached query number too late.


The server has been modified to memorize user variable
assignments during the parse phase to take them into account
on the next (query preparation) phase independently of the
order of user variable references/assignments in a select
item list.


mysql-test/r/user_var.result:
  Added test case for bug #26020.
mysql-test/t/user_var.test:
  Added test case for bug #26020.
sql/item_func.cc:
  An update of entry and update_query_id variables has been
  moved from Item_func_set_user_var::fix_fields() to a separate
  method, Item_func_set_user_var::set_entry().
sql/item_func.h:
  1. The Item_func_set_user_var::set_entry() method has been
  added to update Item_func_set_user_var::entry.
  
  2. The Item_func_set_user_var::entry_thd field has beend
  added to update Item_func_set_user_var::entry only when
  needed.
sql/sql_base.cc:
  Fix: setup_fiedls() calls Item_func_set_user_var::set_entry()
  for all items from the thd->lex->set_var_list before the first
  call of ::fix_fields().
sql/sql_lex.cc:
  The lex_start function has been modified to reset
  the st_lex::set_var_list list.
sql/sql_lex.h:
  New st_lex::set_var_list field has been added to
  memorize all user variable assignments in the current
  select query.
sql/sql_yacc.yy:
  The variable_aux rule has been modified to memorize
  in-query user variable assignments in the
  st_lex::set_var_list list.
parent 4efa8d5a
...@@ -121,8 +121,8 @@ select @a:=0; ...@@ -121,8 +121,8 @@ select @a:=0;
select @a+0, @a:=@a+0+count(*), count(*), @a+0 from t1 group by i; select @a+0, @a:=@a+0+count(*), count(*), @a+0 from t1 group by i;
@a+0 @a:=@a+0+count(*) count(*) @a+0 @a+0 @a:=@a+0+count(*) count(*) @a+0
0 1 1 0 0 1 1 0
1 3 2 0 0 2 2 0
3 6 3 0 0 3 3 0
set @a=0; set @a=0;
select @a,@a:="hello",@a,@a:=3,@a,@a:="hello again" from t1 group by i; select @a,@a:="hello",@a,@a:=3,@a,@a:="hello again" from t1 group by i;
@a @a:="hello" @a @a:=3 @a @a:="hello again" @a @a:="hello" @a @a:=3 @a @a:="hello again"
...@@ -370,4 +370,33 @@ select @rownum := @rownum + 1 as row, ...@@ -370,4 +370,33 @@ select @rownum := @rownum + 1 as row,
@prev_score := a as score @prev_score := a as score
from t1 order by score desc; from t1 order by score desc;
drop table t1; drop table t1;
create table t1(b bigint);
insert into t1 (b) values (10), (30), (10);
set @var := 0;
select if(b=@var, 999, b) , @var := b from t1 order by b;
if(b=@var, 999, b) @var := b
10 10
999 10
30 30
drop table t1;
create temporary table t1 (id int);
insert into t1 values (2), (3), (3), (4);
set @lastid=-1;
select @lastid != id, @lastid, @lastid := id from t1;
@lastid != id @lastid @lastid := id
1 -1 2
1 2 3
0 3 3
1 3 4
drop table t1;
create temporary table t1 (id bigint);
insert into t1 values (2), (3), (3), (4);
set @lastid=-1;
select @lastid != id, @lastid, @lastid := id from t1;
@lastid != id @lastid @lastid := id
1 -1 2
1 2 3
0 3 3
1 3 4
drop table t1;
End of 5.1 tests End of 5.1 tests
...@@ -263,4 +263,26 @@ from t1 order by score desc; ...@@ -263,4 +263,26 @@ from t1 order by score desc;
--enable_result_log --enable_result_log
drop table t1; drop table t1;
#
# Bug#26020: User-Defined Variables are not consistent with columns data types
#
create table t1(b bigint);
insert into t1 (b) values (10), (30), (10);
set @var := 0;
select if(b=@var, 999, b) , @var := b from t1 order by b;
drop table t1;
create temporary table t1 (id int);
insert into t1 values (2), (3), (3), (4);
set @lastid=-1;
select @lastid != id, @lastid, @lastid := id from t1;
drop table t1;
create temporary table t1 (id bigint);
insert into t1 values (2), (3), (3), (4);
set @lastid=-1;
select @lastid != id, @lastid, @lastid := id from t1;
drop table t1;
--echo End of 5.1 tests --echo End of 5.1 tests
...@@ -3805,6 +3805,24 @@ static user_var_entry *get_variable(HASH *hash, LEX_STRING &name, ...@@ -3805,6 +3805,24 @@ static user_var_entry *get_variable(HASH *hash, LEX_STRING &name,
return entry; return entry;
} }
bool Item_func_set_user_var::set_entry(THD *thd, bool create_if_not_exists)
{
if (thd == entry_thd && entry)
return FALSE;
entry_thd= thd;
if (!(entry= get_variable(&thd->user_vars, name, create_if_not_exists)))
return TRUE;
/*
Remember the last query which updated it, this way a query can later know
if this variable is a constant item in the query (it is if update_query_id
is different from query_id).
*/
entry->update_query_id= thd->query_id;
return FALSE;
}
/* /*
When a user variable is updated (in a SET command or a query like When a user variable is updated (in a SET command or a query like
SELECT @a:= ). SELECT @a:= ).
...@@ -3814,15 +3832,8 @@ bool Item_func_set_user_var::fix_fields(THD *thd, Item **ref) ...@@ -3814,15 +3832,8 @@ bool Item_func_set_user_var::fix_fields(THD *thd, Item **ref)
{ {
DBUG_ASSERT(fixed == 0); DBUG_ASSERT(fixed == 0);
/* fix_fields will call Item_func_set_user_var::fix_length_and_dec */ /* fix_fields will call Item_func_set_user_var::fix_length_and_dec */
if (Item_func::fix_fields(thd, ref) || if (Item_func::fix_fields(thd, ref) || set_entry(thd, TRUE))
!(entry= get_variable(&thd->user_vars, name, 1)))
return TRUE; return TRUE;
/*
Remember the last query which updated it, this way a query can later know
if this variable is a constant item in the query (it is if update_query_id
is different from query_id).
*/
entry->update_query_id= thd->query_id;
/* /*
As it is wrong and confusing to associate any As it is wrong and confusing to associate any
character set with NULL, @a should be latin2 character set with NULL, @a should be latin2
......
...@@ -1294,6 +1294,17 @@ class Item_func_set_user_var :public Item_func ...@@ -1294,6 +1294,17 @@ class Item_func_set_user_var :public Item_func
{ {
enum Item_result cached_result_type; enum Item_result cached_result_type;
user_var_entry *entry; user_var_entry *entry;
/*
The entry_thd variable is used:
1) to skip unnecessary updates of the entry field (see above);
2) to reset the entry field that was initialized in the other thread
(for example, an item tree of a trigger that updates user variables
may be shared between several connections, and the entry_thd field
prevents updates of one connection user variables from a concurrent
connection calling the same trigger that initially updated some
user variable it the first connection context).
*/
THD *entry_thd;
char buffer[MAX_FIELD_WIDTH]; char buffer[MAX_FIELD_WIDTH];
String value; String value;
my_decimal decimal_buff; my_decimal decimal_buff;
...@@ -1309,7 +1320,8 @@ class Item_func_set_user_var :public Item_func ...@@ -1309,7 +1320,8 @@ class Item_func_set_user_var :public Item_func
public: public:
LEX_STRING name; // keep it public LEX_STRING name; // keep it public
Item_func_set_user_var(LEX_STRING a,Item *b) Item_func_set_user_var(LEX_STRING a,Item *b)
:Item_func(b), cached_result_type(INT_RESULT), name(a) :Item_func(b), cached_result_type(INT_RESULT),
entry(NULL), entry_thd(NULL), name(a)
{} {}
enum Functype functype() const { return SUSERVAR_FUNC; } enum Functype functype() const { return SUSERVAR_FUNC; }
double val_real(); double val_real();
...@@ -1340,6 +1352,7 @@ public: ...@@ -1340,6 +1352,7 @@ public:
} }
void save_org_in_field(Field *field) { (void)save_in_field(field, 1, 0); } void save_org_in_field(Field *field) { (void)save_in_field(field, 1, 0); }
bool register_field_in_read_map(uchar *arg); bool register_field_in_read_map(uchar *arg);
bool set_entry(THD *thd, bool create_if_not_exists);
}; };
......
...@@ -7296,6 +7296,22 @@ bool setup_fields(THD *thd, Item **ref_pointer_array, ...@@ -7296,6 +7296,22 @@ bool setup_fields(THD *thd, Item **ref_pointer_array,
if (ref_pointer_array) if (ref_pointer_array)
bzero(ref_pointer_array, sizeof(Item *) * fields.elements); bzero(ref_pointer_array, sizeof(Item *) * fields.elements);
/*
We call set_entry() there (before fix_fields() of the whole list of field
items) because:
1) the list of field items has same order as in the query, and the
Item_func_get_user_var item may go before the Item_func_set_user_var:
SELECT @a, @a := 10 FROM t;
2) The entry->update_query_id value controls constantness of
Item_func_get_user_var items, so in presence of Item_func_set_user_var
items we have to refresh their entries before fixing of
Item_func_get_user_var items.
*/
List_iterator<Item_func_set_user_var> li(thd->lex->set_var_list);
Item_func_set_user_var *var;
while ((var= li++))
var->set_entry(thd, FALSE);
Item **ref= ref_pointer_array; Item **ref= ref_pointer_array;
thd->lex->current_select->cur_pos_in_select_list= 0; thd->lex->current_select->cur_pos_in_select_list= 0;
while ((item= it++)) while ((item= it++))
......
...@@ -293,6 +293,7 @@ void lex_start(THD *thd) ...@@ -293,6 +293,7 @@ void lex_start(THD *thd)
lex->select_lex.init_query(); lex->select_lex.init_query();
lex->value_list.empty(); lex->value_list.empty();
lex->update_list.empty(); lex->update_list.empty();
lex->set_var_list.empty();
lex->param_list.empty(); lex->param_list.empty();
lex->view_list.empty(); lex->view_list.empty();
lex->prepared_stmt_params.empty(); lex->prepared_stmt_params.empty();
......
...@@ -1549,6 +1549,7 @@ typedef struct st_lex : public Query_tables_list ...@@ -1549,6 +1549,7 @@ typedef struct st_lex : public Query_tables_list
List<Item> *insert_list,field_list,value_list,update_list; List<Item> *insert_list,field_list,value_list,update_list;
List<List_item> many_values; List<List_item> many_values;
List<set_var_base> var_list; List<set_var_base> var_list;
List<Item_func_set_user_var> set_var_list; // in-query assignment list
List<Item_param> param_list; List<Item_param> param_list;
List<LEX_STRING> view_list; // view list (list of field names in view) List<LEX_STRING> view_list; // view list (list of field names in view)
/* /*
......
...@@ -8064,11 +8064,13 @@ variable: ...@@ -8064,11 +8064,13 @@ variable:
variable_aux: variable_aux:
ident_or_text SET_VAR expr ident_or_text SET_VAR expr
{ {
$$= new (YYTHD->mem_root) Item_func_set_user_var($1, $3); Item_func_set_user_var *item;
$$= item= new (YYTHD->mem_root) Item_func_set_user_var($1, $3);
if ($$ == NULL) if ($$ == NULL)
MYSQL_YYABORT; MYSQL_YYABORT;
LEX *lex= Lex; LEX *lex= Lex;
lex->uncacheable(UNCACHEABLE_RAND); lex->uncacheable(UNCACHEABLE_RAND);
lex->set_var_list.push_back(item);
} }
| ident_or_text | ident_or_text
{ {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment