Commit 54b00f54 authored by unknown's avatar unknown
Browse files

A fix and test case for Bug#5985 ""prepare stmt from "select rand(?)"

crashes server." The fix makes Item_func_rand prepared-statements
aware plus it fixes the case when RAND is used in prepared
statements and replication is on (as well as several similar issues).
Until now we did not reset THD before every execution of a prepared
statement, so if some execution had set thd->time_zone_used
or thd->rand_used they would not be reset until next mysql_parse.
Some of post-review fixes done.


mysql-test/r/ps.result:
  A test case for Bug#5985: test results fixed.
mysql-test/t/ps.test:
  A test case for Bug#5985 "prepare stmt from "select rand(?)" crashes
  server."
sql/item_func.cc:
  Actual fix for Bug#5985: Item_func_rand rewritten to be 
  prepared statements aware.
sql/item_func.h:
  Actual fix for Bug#5985: Item_func_rand rewritten to be 
  prepared statements aware.
sql/mysql_priv.h:
  We need a separate call to reset THD state before every execute of
  a prepared statement. Otherwise things like THD->user_var_events
  are never cleaned up and bloat binary log (as the list of events
  grows from execution to execution).
sql/sql_class.cc:
  Statement::end_statement -> THD::end_statement()
  (a leftover from some design change which is not to pushed now, but the
  leftover is to be pushed).
sql/sql_class.h:
  Statement::end_statement -> THD::end_statement()
  (a leftover from some design change which is not to pushed now, but the
  leftover is to be pushed).
sql/sql_lex.cc:
  Move the part responsible for initializing LEX from mysql_init_query
  to lex_start.
sql/sql_lex.h:
  All lex-related initialization is now in lex_start.
  Move thd->select_number to lex->select_number to be able to use it
  easily in lex_start.
sql/sql_parse.cc:
  Split mysql_init_query into two functions: mysql_reset_thd_for_next_query,
  which is used in PS and conventional execution, and lex_start, used only
  when we want to parse something.
  Fix init_connect to use initialized THD.
sql/sql_prepare.cc:
  Deploy mysql_reset_thd_for_next_query to reset THD state before
  execution of a prepared statement.
  Normally this should have been added to just one place, but
  we have to reset thd before assigning placeholders from variables,
  thus we can't do that in execute_stmt (yuck).
parent d007600b
Loading
Loading
Loading
Loading
+39 −0
Original line number Diff line number Diff line
@@ -336,3 +336,42 @@ id select_type table type possible_keys key key_len ref rows Extra
-	-	-	-	-	-	-	-	NULL	Impossible WHERE
drop table t1;
deallocate prepare stmt;
create table t1 (a int);
insert into t1 (a) values (1), (2), (3), (4);
set @precision=10000000000;
select rand(), 
cast(rand(10)*@precision as unsigned integer),
cast(rand(a)*@precision as unsigned integer) from t1;
rand()	cast(rand(10)*@precision as unsigned integer)	cast(rand(a)*@precision as unsigned integer)
-	6570515219	-
-	1282061302	-
-	6698761160	-
-	9647622201	-
prepare stmt from
"select rand(), 
        cast(rand(10)*@precision as unsigned integer),
        cast(rand(a)*@precision as unsigned integer),
        cast(rand(?)*@precision as unsigned integer) from t1";
set @var=1;
execute stmt using @var;
rand()	cast(rand(10)*@precision as unsigned integer)	cast(rand(a)*@precision as unsigned integer)	cast(rand(?)*@precision as unsigned integer)
-	6570515219	-	4054035371
-	1282061302	-	8716141803
-	6698761160	-	1418603212
-	9647622201	-	944590960
set @var=2;
execute stmt using @var;
rand()	cast(rand(10)*@precision as unsigned integer)	cast(rand(a)*@precision as unsigned integer)	cast(rand(?)*@precision as unsigned integer)
-	6570515219	1559528654	6555866465
-	1282061302	6238114970	1223466192
-	6698761160	6511989195	6449731873
-	9647622201	3845601374	8578261098
set @var=3;
execute stmt using @var;
rand()	cast(rand(10)*@precision as unsigned integer)	cast(rand(a)*@precision as unsigned integer)	cast(rand(?)*@precision as unsigned integer)
-	6570515219	1559528654	9057697559
-	1282061302	6238114970	3730790581
-	6698761160	6511989195	1480860534
-	9647622201	3845601374	6211931236
drop table t1;
deallocate prepare stmt;
+27 −1
Original line number Diff line number Diff line
@@ -363,4 +363,30 @@ execute stmt using @v;
drop table t1;
deallocate prepare stmt;

#
# A test case for Bug#5985 prepare stmt from "select rand(?)" crashes
# server. Check that Item_func_rand is prepared-statements friendly.
#
create table t1 (a int);
insert into t1 (a) values (1), (2), (3), (4);
set @precision=10000000000;
--replace_column 1 - 3 -
select rand(), 
       cast(rand(10)*@precision as unsigned integer),
       cast(rand(a)*@precision as unsigned integer) from t1;
prepare stmt from
"select rand(), 
        cast(rand(10)*@precision as unsigned integer),
        cast(rand(a)*@precision as unsigned integer),
        cast(rand(?)*@precision as unsigned integer) from t1";
set @var=1;
--replace_column 1 - 3 -
execute stmt using @var;
set @var=2;
--replace_column 1 -
execute stmt using @var;
set @var=3;
--replace_column 1 -
execute stmt using @var;
drop table t1;
deallocate prepare stmt;
+26 −8
Original line number Diff line number Diff line
@@ -1010,21 +1010,38 @@ double Item_func_round::val()
}


void Item_func_rand::fix_length_and_dec()
bool Item_func_rand::fix_fields(THD *thd, struct st_table_list *tables,
                                Item **ref)
{
  decimals=NOT_FIXED_DEC; 
  max_length=float_length(decimals);
  Item_real_func::fix_fields(thd, tables, ref);
  used_tables_cache|= RAND_TABLE_BIT;
  if (arg_count)
  {					// Only use argument once in query
    uint32 tmp= (uint32) (args[0]->val_int());
    if ((rand= (struct rand_struct*) sql_alloc(sizeof(*rand))))
    /*
      Allocate rand structure once: we must use thd->current_arena
      to create rand in proper mem_root if it's a prepared statement or
      stored procedure.
    */
    if (!rand && !(rand= (struct rand_struct*)
                   thd->current_arena->alloc(sizeof(*rand))))
      return TRUE;
    /*
      PARAM_ITEM is returned if we're in statement prepare and consequently
      no placeholder value is set yet.
    */
    if (args[0]->type() != PARAM_ITEM)
    {
      /*
        TODO: do not do reinit 'rand' for every execute of PS/SP if
        args[0] is a constant.
      */
      uint32 tmp= (uint32) args[0]->val_int();
      randominit(rand, (uint32) (tmp*0x10001L+55555555L),
                 (uint32) (tmp*0x10000001L));
    }
  }
  else
  {
    THD *thd= current_thd;
    /*
      No need to send a Rand log event if seed was given eg: RAND(seed),
      as it will be replicated in the query as such.
@@ -1038,6 +1055,7 @@ void Item_func_rand::fix_length_and_dec()
    thd->rand_saved_seed2=thd->rand.seed2;
    rand= &thd->rand;
  }
  return FALSE;
}

void Item_func_rand::update_used_tables()
+3 −3
Original line number Diff line number Diff line
@@ -512,13 +512,13 @@ class Item_func_rand :public Item_real_func
{
  struct rand_struct *rand;
public:
  Item_func_rand(Item *a) :Item_real_func(a) {}
  Item_func_rand(Item *a) :Item_real_func(a), rand(0) {}
  Item_func_rand()	  :Item_real_func() {}
  double val();
  const char *func_name() const { return "rand"; }
  bool const_item() const { return 0; }
  void update_used_tables();
  void fix_length_and_dec();
  bool fix_fields(THD *thd, struct st_table_list *tables, Item **ref);
};


+1 −0
Original line number Diff line number Diff line
@@ -447,6 +447,7 @@ bool is_update_query(enum enum_sql_command command);
bool alloc_query(THD *thd, char *packet, ulong packet_length);
void mysql_init_select(LEX *lex);
void mysql_init_query(THD *thd, uchar *buf, uint length);
void mysql_reset_thd_for_next_command(THD *thd);
bool mysql_new_select(LEX *lex, bool move_down);
void create_select_for_variable(const char *var_name);
void mysql_init_multi_delete(LEX *lex);
Loading