Commit b6f42918 authored by unknown's avatar unknown
Browse files

BUG#17263: incorrect DROP query in temporary tables replication

the fix from 5.0 with necessary modifications applied.


sql/sql_base.cc:
  Manual merge with 5.0.
parent 3b6263b8
Loading
Loading
Loading
Loading
+122 −39
Original line number Diff line number Diff line
@@ -1167,16 +1167,26 @@ bool close_thread_table(THD *thd, TABLE **table_ptr)
}


/* close_temporary_tables' internal, 4 is due to uint4korr definition */
static inline uint  tmpkeyval(THD *thd, TABLE *table)
{
  return uint4korr(table->s->table_cache_key.str + table->s->table_cache_key.length - 4);
}


/*
  Close all temporary tables created by 'CREATE TEMPORARY TABLE' for thread
  creates one DROP TEMPORARY TABLE binlog event for each pseudo-thread 
*/

void close_temporary_tables(THD *thd)
{
  TABLE *table,*next;
  char *query, *end;
  uint query_buf_size; 
  bool found_user_tables = 0;
  TABLE *next,
    *prev_table /* prev link is not maintained in TABLE's double-linked list */,
    *table;
  char *query= (gptr) 0, *end;
  uint query_buf_size, max_names_len; 
  bool found_user_tables;

  if (!thd->temporary_tables)
    return;
@@ -1184,51 +1194,124 @@ void close_temporary_tables(THD *thd)
  LINT_INIT(end);
  query_buf_size= 50;   // Enough for DROP ... TABLE IF EXISTS

  for (table=thd->temporary_tables ; table ; table=table->next)
  /* 
     insertion sort of temp tables by pseudo_thread_id to build ordered list 
     of sublists of equal pseudo_thread_id
  */
  for (prev_table= thd->temporary_tables, 
         table= prev_table->next,
         found_user_tables= (prev_table->s->table_name.str[0] != '#'); 
       table;
       prev_table= table, table= table->next)
  {
    TABLE *prev_sorted /* same as for prev_table */,
      *sorted;
    /*
      We are going to add 4 ` around the db/table names, so 1 does not look
      enough; indeed it is enough, because table->table_cache_key.length is
      table not created directly by the user is moved to the tail. 
      Fixme/todo: nothing (I checked the manual) prevents user to create temp
      with `#' 
    */
    if (table->s->table_name.str[0] == '#')
      continue;
    else 
    {
      found_user_tables = 1;
    }
    for (prev_sorted= NULL, sorted= thd->temporary_tables; sorted != table; 
         prev_sorted= sorted, sorted= sorted->next)
    {
      if (sorted->s->table_name.str[0] == '#' || tmpkeyval(thd, sorted) > tmpkeyval(thd, table))
      {
        /* move into the sorted part of the list from the unsorted */
        prev_table->next= table->next;
        table->next= sorted;
        if (prev_sorted) 
        {
          prev_sorted->next= table;
        }
        else
        {
          thd->temporary_tables= table;
        }
        table= prev_table;
        break;
      }
    }
  }  
  /* 
     calc query_buf_size as max per sublists, one sublist per pseudo thread id.
     Also stop at first occurence of `#'-named table that starts 
     all implicitly created temp tables
  */
  for (max_names_len= 0, table=thd->temporary_tables; 
       table && table->s->table_name.str[0] != '#';
       table=table->next)
  {
    uint tmp_names_len;
    for (tmp_names_len= table->s->table_cache_key.length + 1;
         table->next && table->s->table_name.str[0] != '#' &&
           tmpkeyval(thd, table) == tmpkeyval(thd, table->next);
         table=table->next)
    {
      /*
        We are going to add 4 ` around the db/table names, so 1 might not look
        enough; indeed it is enough, because table->s->table_cache_key.length is
        greater (by 8, because of server_id and thread_id) than db||table.
      */
    query_buf_size+= table->s->table_cache_key.length+1;
      tmp_names_len += table->next->s->table_cache_key.length + 1;
    }
    if (tmp_names_len > max_names_len) max_names_len= tmp_names_len;
  }
  
  if ((query = alloc_root(thd->mem_root, query_buf_size)))
  /* allocate */
  if (found_user_tables && mysql_bin_log.is_open() &&
      !thd->current_stmt_binlog_row_based &&
      (query = alloc_root(thd->mem_root, query_buf_size+= max_names_len)))
    // Better add "if exists", in case a RESET MASTER has been done
    end= strmov(query, "DROP /*!40005 TEMPORARY */ TABLE IF EXISTS ");

  /* scan sorted tmps to generate sequence of DROP */
  for (table=thd->temporary_tables; table; table= next)
  {
    if (query) // we might be out of memory, but this is not fatal
    if (query // we might be out of memory, but this is not fatal 
        && table->s->table_name.str[0] != '#') 
    {
      // skip temporary tables not created directly by the user
      if (table->s->table_name.str[0] != '#')
	found_user_tables = 1;
      end= strxmov(end, "`",table->s->db.str, "`.`",
      char *end_cur;
      /* Set pseudo_thread_id to be that of the processed table */
      thd->variables.pseudo_thread_id= tmpkeyval(thd, table);
      /* Loop forward through all tables within the sublist of
         common pseudo_thread_id to create single DROP query */
      for (end_cur= end;
           table && table->s->table_name.str[0] != '#' &&
             tmpkeyval(thd, table) == thd->variables.pseudo_thread_id;
           table= next)
      {
        end_cur= strxmov(end_cur, "`", table->s->db.str, "`.`",
                      table->s->table_name.str, "`,", NullS);
    }
        next= table->next;
        close_temporary(table, 1, 1);
      }
  if (query && found_user_tables && mysql_bin_log.is_open() &&
      !thd->current_stmt_binlog_row_based) // CREATE TEMP TABLE not binlogged if row-based
  {
    /* The -1 is to remove last ',' */
      thd->clear_error();
    Query_log_event qinfo(thd, query, (ulong)(end-query)-1, 0, FALSE);
      /* The -1 is to remove last ',' */
      Query_log_event qinfo(thd, query, (ulong)(end_cur - query) - 1, 0, FALSE);
      /*
      Imagine the thread had created a temp table, then was doing a SELECT, and
      the SELECT was killed. Then it's not clever to mark the statement above
      as "killed", because it's not really a statement updating data, and there
      are 99.99% chances it will succeed on slave.
      If a real update (one updating a persistent table) was killed on the
      master, then this real update will be logged with error_code=killed,
      rightfully causing the slave to stop.
        Imagine the thread had created a temp table, then was doing a SELECT,
        and the SELECT was killed. Then it's not clever to mark the statement
        above as "killed", because it's not really a statement updating data,
        and there are 99.99% chances it will succeed on slave.  If a real update
        (one updating a persistent table) was killed on the master, then this
        real update will be logged with error_code=killed, rightfully causing
        the slave to stop.
      */
      qinfo.error_code= 0;
      mysql_bin_log.write(&qinfo);
    }
    else 
    {
      next= table->next;
      close_temporary(table, 1, 1);
    }
  }
  thd->temporary_tables=0;
}