Commit 61e41ac2 authored by aelkin@mysql.com's avatar aelkin@mysql.com

BUG#17263: incorrect DROP query in temporary tables replication

the fix from 5.0 with necessary modifications applied.
parent 3363effa
...@@ -1167,16 +1167,26 @@ bool close_thread_table(THD *thd, TABLE **table_ptr) ...@@ -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 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) void close_temporary_tables(THD *thd)
{ {
TABLE *table,*next; TABLE *next,
char *query, *end; *prev_table /* prev link is not maintained in TABLE's double-linked list */,
uint query_buf_size; *table;
bool found_user_tables = 0; char *query= (gptr) 0, *end;
uint query_buf_size, max_names_len;
bool found_user_tables;
if (!thd->temporary_tables) if (!thd->temporary_tables)
return; return;
...@@ -1184,51 +1194,124 @@ void close_temporary_tables(THD *thd) ...@@ -1184,51 +1194,124 @@ void close_temporary_tables(THD *thd)
LINT_INIT(end); LINT_INIT(end);
query_buf_size= 50; // Enough for DROP ... TABLE IF EXISTS 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 table not created directly by the user is moved to the tail.
enough; indeed it is enough, because table->table_cache_key.length is 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. 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 // Better add "if exists", in case a RESET MASTER has been done
end=strmov(query, "DROP /*!40005 TEMPORARY */ TABLE IF EXISTS "); end= strmov(query, "DROP /*!40005 TEMPORARY */ TABLE IF EXISTS ");
for (table=thd->temporary_tables ; table ; table=next) /* 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 char *end_cur;
if (table->s->table_name.str[0] != '#') /* Set pseudo_thread_id to be that of the processed table */
found_user_tables = 1; thd->variables.pseudo_thread_id= tmpkeyval(thd, table);
end= strxmov(end, "`",table->s->db.str, "`.`", /* 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); table->s->table_name.str, "`,", NullS);
} next= table->next;
next=table->next;
close_temporary(table, 1, 1); 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(); 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 Imagine the thread had created a temp table, then was doing a SELECT,
the SELECT was killed. Then it's not clever to mark the statement above and the SELECT was killed. Then it's not clever to mark the statement
as "killed", because it's not really a statement updating data, and there above as "killed", because it's not really a statement updating data,
are 99.99% chances it will succeed on slave. and there are 99.99% chances it will succeed on slave. If a real update
If a real update (one updating a persistent table) was killed on the (one updating a persistent table) was killed on the master, then this
master, then this real update will be logged with error_code=killed, real update will be logged with error_code=killed, rightfully causing
rightfully causing the slave to stop. the slave to stop.
*/ */
qinfo.error_code= 0; qinfo.error_code= 0;
mysql_bin_log.write(&qinfo); mysql_bin_log.write(&qinfo);
} }
else
{
next= table->next;
close_temporary(table, 1, 1);
}
}
thd->temporary_tables=0; thd->temporary_tables=0;
} }
......
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