Commit 6040b0ab authored by konstantin@mysql.com's avatar konstantin@mysql.com

A fix and a test case for Bug#9520 "SELECT DISTINCT crashes server

with cursor". The patch refactors do_select/sub_select
functions, which implement the nested loop algorithm, and reuses them to
fetch rows for cursors as well.
Pushing with view.test failing (--ps-protocol).
parent f35c1a18
......@@ -2223,7 +2223,7 @@ void mysql_stmt_fetch(THD *thd, char *packet, uint packet_length)
my_pthread_setprio(pthread_self(), QUERY_PRIOR);
thd->protocol= &thd->protocol_prep; // Switch to binary protocol
(void) stmt->cursor->fetch(num_rows);
stmt->cursor->fetch(num_rows);
thd->protocol= &thd->protocol_simple; // Use normal protocol
if (!(specialflag & SPECIAL_NO_PRIOR))
......
......@@ -114,17 +114,31 @@ static bool create_myisam_tmp_table(TABLE *table,TMP_TABLE_PARAM *param,
static Next_select_func setup_end_select_func(JOIN *join);
static int do_select(JOIN *join,List<Item> *fields,TABLE *tmp_table,
Procedure *proc);
static int sub_select_cache(JOIN *join,JOIN_TAB *join_tab,bool end_of_records);
static int sub_select(JOIN *join,JOIN_TAB *join_tab,bool end_of_records);
static int flush_cached_records(JOIN *join,JOIN_TAB *join_tab,bool skip_last);
static int end_send(JOIN *join, JOIN_TAB *join_tab, bool end_of_records);
static int end_send_group(JOIN *join, JOIN_TAB *join_tab,bool end_of_records);
static int end_write(JOIN *join, JOIN_TAB *join_tab, bool end_of_records);
static int end_update(JOIN *join, JOIN_TAB *join_tab, bool end_of_records);
static int end_unique_update(JOIN *join,JOIN_TAB *join_tab,
bool end_of_records);
static int end_write_group(JOIN *join, JOIN_TAB *join_tab,
bool end_of_records);
static enum_nested_loop_state
sub_select_cache(JOIN *join, JOIN_TAB *join_tab, bool end_of_records);
static enum_nested_loop_state
evaluate_join_record(JOIN *join, JOIN_TAB *join_tab,
int error, my_bool *report_error);
static enum_nested_loop_state
evaluate_null_complemented_join_record(JOIN *join, JOIN_TAB *join_tab);
static enum_nested_loop_state
sub_select(JOIN *join,JOIN_TAB *join_tab, bool end_of_records);
static enum_nested_loop_state
flush_cached_records(JOIN *join, JOIN_TAB *join_tab, bool skip_last);
static enum_nested_loop_state
end_send(JOIN *join, JOIN_TAB *join_tab, bool end_of_records);
static enum_nested_loop_state
end_send_group(JOIN *join, JOIN_TAB *join_tab, bool end_of_records);
static enum_nested_loop_state
end_write(JOIN *join, JOIN_TAB *join_tab, bool end_of_records);
static enum_nested_loop_state
end_update(JOIN *join, JOIN_TAB *join_tab, bool end_of_records);
static enum_nested_loop_state
end_unique_update(JOIN *join, JOIN_TAB *join_tab, bool end_of_records);
static enum_nested_loop_state
end_write_group(JOIN *join, JOIN_TAB *join_tab, bool end_of_records);
static int test_if_group_changed(List<Item_buff> &list);
static int join_read_const_table(JOIN_TAB *tab, POSITION *pos);
static int join_read_system(JOIN_TAB *tab);
......@@ -1800,13 +1814,7 @@ Cursor::open(JOIN *join_arg)
happen for the first table in join_tab list
*/
DBUG_ASSERT(join_tab->table->null_row == 0);
/*
There is always at least one record in the table, as otherwise we
wouldn't have opened the cursor. Therefore a failure is the only
reason read_first_record can return not 0.
*/
DBUG_RETURN(join_tab->read_first_record(join_tab));
DBUG_RETURN(0);
}
......@@ -1816,97 +1824,36 @@ Cursor::open(JOIN *join_arg)
PRECONDITION:
Cursor is open
RETURN VALUES:
-4 there are more rows, send_eof sent to the client
0 no more rows, send_eof was sent to the client, cursor is closed
other fatal fetch error, cursor is closed (error is not reported)
none, this function will send error or OK to network if necessary.
*/
int
void
Cursor::fetch(ulong num_rows)
{
THD *thd= join->thd;
JOIN_TAB *join_tab= join->join_tab + join->const_tables;
COND *on_expr= *join_tab->on_expr_ref;
COND *select_cond= join_tab->select_cond;
READ_RECORD *info= &join_tab->read_record;
int error= 0;
enum_nested_loop_state error= NESTED_LOOP_OK;
/* save references to memory, allocated during fetch */
thd->set_n_backup_item_arena(this, &thd->stmt_backup);
join->fetch_limit+= num_rows;
/*
Run while there are new rows in the first table;
For each row, satisfying ON and WHERE clauses (those parts of them which
can be evaluated early), call next_select.
*/
do
{
int no_more_rows;
join->examined_rows++;
if (thd->killed) /* Aborted by user */
{
my_message(ER_SERVER_SHUTDOWN, ER(ER_SERVER_SHUTDOWN), MYF(0));
return -1;
}
if (on_expr == 0 || on_expr->val_int())
{
if (select_cond == 0 || select_cond->val_int())
{
/*
TODO: call table->unlock_row() to unlock row failed selection,
when this feature will be used.
*/
error= join_tab->next_select(join, join_tab + 1, 0);
DBUG_ASSERT(error <= 0);
if (error)
{
/* real error or LIMIT/FETCH LIMIT worked */
if (error == -4)
{
/*
FETCH LIMIT, read ahead one row, and close cursor
if there is no more rows XXX: to be fixed to support
non-equi-joins!
*/
if ((no_more_rows= info->read_record(info)))
error= no_more_rows > 0 ? -1: 0;
}
break;
}
}
}
/* read next row; break loop if there was an error */
if ((no_more_rows= info->read_record(info)))
{
if (no_more_rows > 0)
error= -1;
else
{
enum { END_OF_RECORDS= 1 };
error= join_tab->next_select(join, join_tab+1, (int) END_OF_RECORDS);
}
break;
}
}
while (thd->net.report_error == 0);
if (thd->net.report_error)
error= -1;
if (error == -3) /* LIMIT clause worked */
error= 0;
error= sub_select(join, join_tab, 0);
if (error == NESTED_LOOP_OK || error == NESTED_LOOP_NO_MORE_ROWS)
error= sub_select(join,join_tab,1);
if (error == NESTED_LOOP_QUERY_LIMIT)
error= NESTED_LOOP_OK; /* select_limit used */
if (error == NESTED_LOOP_CURSOR_LIMIT)
join->resume_nested_loop= TRUE;
#ifdef USING_TRANSACTIONS
ha_release_temporary_latches(thd);
#endif
thd->restore_backup_item_arena(this, &thd->stmt_backup);
if (error == -4)
if (error == NESTED_LOOP_CURSOR_LIMIT)
{
/* Fetch limit worked, possibly more rows are there */
thd->server_status|= SERVER_STATUS_CURSOR_EXISTS;
......@@ -1916,20 +1863,19 @@ Cursor::fetch(ulong num_rows)
else
{
close();
if (error == 0)
if (error == NESTED_LOOP_OK)
{
thd->server_status|= SERVER_STATUS_LAST_ROW_SENT;
::send_eof(thd);
thd->server_status&= ~SERVER_STATUS_LAST_ROW_SENT;
}
else
else if (error != NESTED_LOOP_KILLED)
my_message(ER_OUT_OF_RESOURCES, ER(ER_OUT_OF_RESOURCES), MYF(0));
/* free cursor memory */
free_items(free_list);
free_list= 0;
free_root(&main_mem_root, MYF(0));
}
return error;
}
......@@ -8948,7 +8894,8 @@ static Next_select_func setup_end_select_func(JOIN *join)
static int
do_select(JOIN *join,List<Item> *fields,TABLE *table,Procedure *procedure)
{
int error= 0;
int rc= 0;
enum_nested_loop_state error= NESTED_LOOP_OK;
JOIN_TAB *join_tab;
DBUG_ENTER("do_select");
......@@ -8978,24 +8925,30 @@ do_select(JOIN *join,List<Item> *fields,TABLE *table,Procedure *procedure)
if (!join->conds || join->conds->val_int())
{
Next_select_func end_select= join->join_tab[join->tables-1].next_select;
if (!(error=(*end_select)(join,join_tab,0)) || error == -3)
error=(*end_select)(join,join_tab,1);
error= (*end_select)(join,join_tab,0);
if (error == NESTED_LOOP_OK || error == NESTED_LOOP_QUERY_LIMIT)
error= (*end_select)(join,join_tab,1);
}
else if (join->send_row_on_empty_set())
error= join->result->send_data(*join->fields);
rc= join->result->send_data(*join->fields);
}
else
{
error= sub_select(join,join_tab,0);
if (error >= 0)
if (error == NESTED_LOOP_OK || error == NESTED_LOOP_NO_MORE_ROWS)
error= sub_select(join,join_tab,1);
if (error == -3)
error= 0; /* select_limit used */
if (error == NESTED_LOOP_QUERY_LIMIT)
error= NESTED_LOOP_OK; /* select_limit used */
}
if (error == NESTED_LOOP_NO_MORE_ROWS)
error= NESTED_LOOP_OK;
if (error >= 0)
if (error == NESTED_LOOP_OK)
{
error=0;
/*
Sic: this branch works even if rc != 0, e.g. when
send_data above returns an error.
*/
if (!table) // If sending data to client
{
/*
......@@ -9004,10 +8957,12 @@ do_select(JOIN *join,List<Item> *fields,TABLE *table,Procedure *procedure)
*/
join->join_free(0); // Unlock all cursors
if (join->result->send_eof())
error= 1; // Don't send error
rc= 1; // Don't send error
}
DBUG_PRINT("info",("%ld records output",join->send_records));
}
else
rc= -1;
if (table)
{
int tmp, new_errno= 0;
......@@ -9025,40 +8980,42 @@ do_select(JOIN *join,List<Item> *fields,TABLE *table,Procedure *procedure)
table->file->print_error(new_errno,MYF(0));
}
#ifndef DBUG_OFF
if (error)
if (rc)
{
DBUG_PRINT("error",("Error: do_select() failed"));
}
#endif
DBUG_RETURN(join->thd->net.report_error ? -1 : error);
DBUG_RETURN(join->thd->net.report_error ? -1 : rc);
}
static int
static enum_nested_loop_state
sub_select_cache(JOIN *join,JOIN_TAB *join_tab,bool end_of_records)
{
int error;
enum_nested_loop_state rc;
if (end_of_records)
{
if ((error=flush_cached_records(join,join_tab,FALSE)) < 0)
return error; /* purecov: inspected */
return sub_select(join,join_tab,end_of_records);
rc= flush_cached_records(join,join_tab,FALSE);
if (rc == NESTED_LOOP_OK || rc == NESTED_LOOP_NO_MORE_ROWS)
rc= sub_select(join,join_tab,end_of_records);
return rc;
}
if (join->thd->killed) // If aborted by user
{
join->thd->send_kill_message();
return -2; /* purecov: inspected */
return NESTED_LOOP_KILLED; /* purecov: inspected */
}
if (join_tab->use_quick != 2 || test_if_quick_select(join_tab) <= 0)
{
if (!store_record_in_cache(&join_tab->cache))
return 0; // There is more room in cache
return NESTED_LOOP_OK; // There is more room in cache
return flush_cached_records(join,join_tab,FALSE);
}
if ((error=flush_cached_records(join,join_tab,TRUE)) < 0)
return error; /* purecov: inspected */
return sub_select(join,join_tab,end_of_records); /* Use ordinary select */
rc= flush_cached_records(join, join_tab, TRUE);
if (rc == NESTED_LOOP_OK || rc == NESTED_LOOP_NO_MORE_ROWS)
rc= sub_select(join, join_tab, end_of_records);
return rc;
}
/*
......@@ -9170,11 +9127,10 @@ sub_select_cache(JOIN *join,JOIN_TAB *join_tab,bool end_of_records)
table of the embedding nested join, if any.
RETURN
0, if success
# of the error, otherwise
return one of enum_nested_loop_state, except NESTED_LOOP_NO_MORE_ROWS.
*/
static int
static enum_nested_loop_state
sub_select(JOIN *join,JOIN_TAB *join_tab,bool end_of_records)
{
join_tab->table->null_row=0;
......@@ -9182,206 +9138,258 @@ sub_select(JOIN *join,JOIN_TAB *join_tab,bool end_of_records)
return (*join_tab->next_select)(join,join_tab+1,end_of_records);
int error;
JOIN_TAB *first_unmatched;
JOIN_TAB *tab;
/* Cache variables for faster loop */
COND *select_cond= join_tab->select_cond;
enum_nested_loop_state rc;
my_bool *report_error= &(join->thd->net.report_error);
READ_RECORD *info= &join_tab->read_record;
join->return_tab= join_tab;
if (join_tab->last_inner)
if (join->resume_nested_loop)
{
/* If not the last table, plunge down the nested loop */
if (join_tab < join->join_tab + join->tables - 1)
rc= (*join_tab->next_select)(join, join_tab + 1, 0);
else
{
join->resume_nested_loop= FALSE;
rc= NESTED_LOOP_OK;
}
}
else
{
/* join_tab is the first inner table for an outer join operation. */
join->return_tab= join_tab;
/* Set initial state of guard variables for this table.*/
join_tab->found=0;
join_tab->not_null_compl= 1;
if (join_tab->last_inner)
{
/* join_tab is the first inner table for an outer join operation. */
/* Set first_unmatched for the last inner table of this group */
join_tab->last_inner->first_unmatched= join_tab;
/* Set initial state of guard variables for this table.*/
join_tab->found=0;
join_tab->not_null_compl= 1;
/* Set first_unmatched for the last inner table of this group */
join_tab->last_inner->first_unmatched= join_tab;
}
join->thd->row_count= 0;
error= (*join_tab->read_first_record)(join_tab);
rc= evaluate_join_record(join, join_tab, error, report_error);
}
if (!(error=(*join_tab->read_first_record)(join_tab)))
while (rc == NESTED_LOOP_OK)
{
bool not_exists_optimize= join_tab->table->reginfo.not_exists_optimize;
bool not_used_in_distinct=join_tab->not_used_in_distinct;
ha_rows found_records=join->found_records;
READ_RECORD *info= &join_tab->read_record;
error= info->read_record(info);
rc= evaluate_join_record(join, join_tab, error, report_error);
}
join->thd->row_count= 0;
do
if (rc == NESTED_LOOP_NO_MORE_ROWS &&
join_tab->last_inner && !join_tab->found)
rc= evaluate_null_complemented_join_record(join, join_tab);
if (rc == NESTED_LOOP_NO_MORE_ROWS)
rc= NESTED_LOOP_OK;
return rc;
}
/*
Process one record of the nested loop join.
DESCRIPTION
This function will evaluate parts of WHERE/ON clauses that are
applicable to the partial record on hand and in case of success
submit this record to the next level of the nested loop.
*/
static enum_nested_loop_state
evaluate_join_record(JOIN *join, JOIN_TAB *join_tab,
int error, my_bool *report_error)
{
bool not_exists_optimize= join_tab->table->reginfo.not_exists_optimize;
bool not_used_in_distinct=join_tab->not_used_in_distinct;
ha_rows found_records=join->found_records;
COND *select_cond= join_tab->select_cond;
if (error > 0 || (*report_error)) // Fatal error
return NESTED_LOOP_ERROR;
if (error < 0)
return NESTED_LOOP_NO_MORE_ROWS;
if (join->thd->killed) // Aborted by user
{
join->thd->send_kill_message();
return NESTED_LOOP_KILLED; /* purecov: inspected */
}
DBUG_PRINT("info", ("select cond 0x%lx", (ulong)select_cond));
if (!select_cond || select_cond->val_int())
{
/*
There is no select condition or the attached pushed down
condition is true => a match is found.
*/
bool found= 1;
while (join_tab->first_unmatched && found)
{
if (join->thd->killed) // Aborted by user
{
join->thd->send_kill_message();
return -2; /* purecov: inspected */
}
DBUG_PRINT("info", ("select cond 0x%lx", (ulong)select_cond));
if (!select_cond || select_cond->val_int())
/*
The while condition is always false if join_tab is not
the last inner join table of an outer join operation.
*/
JOIN_TAB *first_unmatched= join_tab->first_unmatched;
/*
Mark that a match for current outer table is found.
This activates push down conditional predicates attached
to the all inner tables of the outer join.
*/
first_unmatched->found= 1;
for (JOIN_TAB *tab= first_unmatched; tab <= join_tab; tab++)
{
/*
There is no select condition or the attached pushed down
condition is true => a match is found.
*/
bool found= 1;
while (join_tab->first_unmatched && found)
/* Check all predicates that has just been activated. */
/*
Actually all predicates non-guarded by first_unmatched->found
will be re-evaluated again. It could be fixed, but, probably,
it's not worth doing now.
*/
if (tab->select_cond && !tab->select_cond->val_int())
{
/*
The while condition is always false if join_tab is not
the last inner join table of an outer join operation.
*/
first_unmatched= join_tab->first_unmatched;
/*
Mark that a match for current outer table is found.
This activates push down conditional predicates attached
to the all inner tables of the outer join.
*/
first_unmatched->found= 1;
for (tab= first_unmatched; tab <= join_tab; tab++)
{
/* Check all predicates that has just been activated. */
/* The condition attached to table tab is false */
if (tab == join_tab)
found= 0;
else
{
/*
Actually all predicates non-guarded by first_unmatched->found
will be re-evaluated again. It could be fixed, but, probably,
it's not worth doing now.
*/
if (tab->select_cond && !tab->select_cond->val_int())
{
/* The condition attached to table tab is false */
if (tab == join_tab)
found= 0;
else
{
/*
Set a return point if rejected predicate is attached
not to the last table of the current nest level.
*/
join->return_tab= tab;
return 0;
}
}
Set a return point if rejected predicate is attached
not to the last table of the current nest level.
*/
join->return_tab= tab;
return NESTED_LOOP_OK;
}
/*
Check whether join_tab is not the last inner table
for another embedding outer join.
*/
if ((first_unmatched= first_unmatched->first_upper) &&
first_unmatched->last_inner != join_tab)
first_unmatched= 0;
join_tab->first_unmatched= first_unmatched;
}
/*
It was not just a return to lower loop level when one
of the newly activated predicates is evaluated as false
(See above join->return_tab= tab).
*/
join->examined_rows++;
join->thd->row_count++;
if (found)
{
if (not_exists_optimize)
break;
/* A match from join_tab is found for the current partial join. */
if ((error=(*join_tab->next_select)(join, join_tab+1, 0)) < 0)
return error;
if (join->return_tab < join_tab)
return 0;
/*
Test if this was a SELECT DISTINCT query on a table that
was not in the field list; In this case we can abort if
we found a row, as no new rows can be added to the result.
*/
if (not_used_in_distinct && found_records != join->found_records)
return 0;
}
else
info->file->unlock_row();
}
else
{
/*
The condition pushed down to the table join_tab rejects all rows
with the beginning coinciding with the current partial join.
*/
join->examined_rows++;
join->thd->row_count++;
}
} while (!(error=info->read_record(info)) && !(*report_error));
}
if (error > 0 || (*report_error)) // Fatal error
return -1;
if (join_tab->last_inner && !join_tab->found)
{
/*
The table join_tab is the first inner table of a outer join operation
and no matches has been found for the current outer row.
*/
JOIN_TAB *last_inner_tab= join_tab->last_inner;
for ( ; join_tab <= last_inner_tab ; join_tab++)
{
/* Change the the values of guard predicate variables. */
join_tab->found= 1;
join_tab->not_null_compl= 0;
/* The outer row is complemented by nulls for each inner tables */
restore_record(join_tab->table,s->default_values); // Make empty record
mark_as_null_row(join_tab->table); // For group by without error
select_cond= join_tab->select_cond;
/* Check all attached conditions for inner table rows. */
if (select_cond && !select_cond->val_int())
return 0;
}
join_tab--;
/*
The row complemented by nulls might be the first row
of embedding outer joins.
If so, perform the same actions as in the code
for the first regular outer join row above.
*/
for ( ; ; )
{
first_unmatched= join_tab->first_unmatched;
/*
Check whether join_tab is not the last inner table
for another embedding outer join.
*/
if ((first_unmatched= first_unmatched->first_upper) &&
first_unmatched->last_inner != join_tab)
first_unmatched= 0;
join_tab->first_unmatched= first_unmatched;
if (!first_unmatched)
break;
first_unmatched->found= 1;
for (JOIN_TAB *tab= first_unmatched; tab <= join_tab; tab++)
{
if (tab->select_cond && !tab->select_cond->val_int())
{
join->return_tab= tab;
return 0;
}
}
}
/*
The row complemented by nulls satisfies all conditions
attached to inner tables.
Send the row complemented by nulls to be joined with the
remaining tables.
*/
if ((error=(*join_tab->next_select)(join, join_tab+1 ,0)) < 0)
return error;
It was not just a return to lower loop level when one
of the newly activated predicates is evaluated as false
(See above join->return_tab= tab).
*/
join->examined_rows++;
join->thd->row_count++;
if (found)
{
enum enum_nested_loop_state rc;
if (not_exists_optimize)
return NESTED_LOOP_NO_MORE_ROWS;
/* A match from join_tab is found for the current partial join. */
rc= (*join_tab->next_select)(join, join_tab+1, 0);
if (rc != NESTED_LOOP_OK && rc != NESTED_LOOP_NO_MORE_ROWS)
return rc;
if (join->return_tab < join_tab)
return NESTED_LOOP_OK;
/*
Test if this was a SELECT DISTINCT query on a table that
was not in the field list; In this case we can abort if
we found a row, as no new rows can be added to the result.
*/
if (not_used_in_distinct && found_records != join->found_records)
return NESTED_LOOP_OK;
}
else
join_tab->read_record.file->unlock_row();
}
return 0;
else
{
/*
The condition pushed down to the table join_tab rejects all rows
with the beginning coinciding with the current partial join.
*/
join->examined_rows++;
join->thd->row_count++;
}
return NESTED_LOOP_OK;
}
static int
/*
DESCRIPTION
Construct a NULL complimented partial join record and feed it to the next
level of the nested loop. This function is used in case we have
an OUTER join and no matching record was found.
*/
static enum_nested_loop_state
evaluate_null_complemented_join_record(JOIN *join, JOIN_TAB *join_tab)
{
/*
The table join_tab is the first inner table of a outer join operation
and no matches has been found for the current outer row.
*/
JOIN_TAB *last_inner_tab= join_tab->last_inner;
/* Cache variables for faster loop */
COND *select_cond;
for ( ; join_tab <= last_inner_tab ; join_tab++)
{
/* Change the the values of guard predicate variables. */
join_tab->found= 1;
join_tab->not_null_compl= 0;
/* The outer row is complemented by nulls for each inner tables */
restore_record(join_tab->table,s->default_values); // Make empty record
mark_as_null_row(join_tab->table); // For group by without error
select_cond= join_tab->select_cond;
/* Check all attached conditions for inner table rows. */
if (select_cond && !select_cond->val_int())
return NESTED_LOOP_OK;
}
join_tab--;
/*
The row complemented by nulls might be the first row
of embedding outer joins.
If so, perform the same actions as in the code
for the first regular outer join row above.
*/
for ( ; ; )
{
JOIN_TAB *first_unmatched= join_tab->first_unmatched;
if ((first_unmatched= first_unmatched->first_upper) &&
first_unmatched->last_inner != join_tab)
first_unmatched= 0;
join_tab->first_unmatched= first_unmatched;
if (!first_unmatched)
break;
first_unmatched->found= 1;
for (JOIN_TAB *tab= first_unmatched; tab <= join_tab; tab++)
{
if (tab->select_cond && !tab->select_cond->val_int())
{
join->return_tab= tab;
return NESTED_LOOP_OK;
}
}
}
/*
The row complemented by nulls satisfies all conditions
attached to inner tables.
Send the row complemented by nulls to be joined with the
remaining tables.
*/
return (*join_tab->next_select)(join, join_tab+1, 0);
}
static enum_nested_loop_state
flush_cached_records(JOIN *join,JOIN_TAB *join_tab,bool skip_last)
{
enum_nested_loop_state rc= NESTED_LOOP_OK;
int error;
READ_RECORD *info;
if (!join_tab->cache.records)
return 0; /* Nothing to do */
return NESTED_LOOP_OK; /* Nothing to do */
if (skip_last)
(void) store_record_in_cache(&join_tab->cache); // Must save this for later
if (join_tab->use_quick == 2)
......@@ -9396,7 +9404,7 @@ flush_cached_records(JOIN *join,JOIN_TAB *join_tab,bool skip_last)
if ((error=join_init_read_record(join_tab)))
{
reset_cache_write(&join_tab->cache);
return -error; /* No records or error */
return error < 0 ? NESTED_LOOP_NO_MORE_ROWS: NESTED_LOOP_ERROR;
}
for (JOIN_TAB *tmp=join->join_tab; tmp != join_tab ; tmp++)
......@@ -9411,11 +9419,11 @@ flush_cached_records(JOIN *join,JOIN_TAB *join_tab,bool skip_last)
if (join->thd->killed)
{
join->thd->send_kill_message();
return -2; // Aborted by user /* purecov: inspected */
return NESTED_LOOP_KILLED; // Aborted by user /* purecov: inspected */
}
SQL_SELECT *select=join_tab->select;
if (!error && (!join_tab->cache.select ||
!join_tab->cache.select->skip_record()))
if (rc == NESTED_LOOP_OK &&
(!join_tab->cache.select || !join_tab->cache.select->skip_record()))
{
uint i;
reset_cache_read(&join_tab->cache);
......@@ -9423,11 +9431,14 @@ flush_cached_records(JOIN *join,JOIN_TAB *join_tab,bool skip_last)
{
read_cached_record(join_tab);
if (!select || !select->skip_record())
if ((error=(join_tab->next_select)(join,join_tab+1,0)) < 0)
{
rc= (join_tab->next_select)(join,join_tab+1,0);
if (rc != NESTED_LOOP_OK && rc != NESTED_LOOP_NO_MORE_ROWS)
{
reset_cache_write(&join_tab->cache);
return error; /* purecov: inspected */
return rc;
}
}
}
}
} while (!(error=info->read_record(info)));
......@@ -9436,10 +9447,10 @@ flush_cached_records(JOIN *join,JOIN_TAB *join_tab,bool skip_last)
read_cached_record(join_tab); // Restore current record
reset_cache_write(&join_tab->cache);
if (error > 0) // Fatal error
return -1; /* purecov: inspected */
return NESTED_LOOP_ERROR; /* purecov: inspected */
for (JOIN_TAB *tmp2=join->join_tab; tmp2 != join_tab ; tmp2++)
tmp2->table->status=tmp2->status;
return 0;
return NESTED_LOOP_OK;
}
......@@ -9905,13 +9916,32 @@ join_read_next_same_or_null(READ_RECORD *info)
/*****************************************************************************
The different end of select functions
These functions returns < 0 when end is reached, 0 on ok and > 0 if a
fatal error (like table corruption) was detected
DESCRIPTION
Functions that end one nested loop iteration. Different functions
are used to support GROUP BY clause and to redirect records
to a table (e.g. in case of SELECT into a temporary table) or to the
network client.
RETURN VALUES
NESTED_LOOP_OK - the record has been successfully handled
NESTED_LOOP_ERROR - a fatal error (like table corruption)
was detected
NESTED_LOOP_KILLED - thread shutdown was requested while processing
the record
NESTED_LOOP_QUERY_LIMIT - the record has been successfully handled;
additionally, the nested loop produced the
number of rows specified in the LIMIT clause
for the query
NESTED_LOOP_CURSOR_LIMIT - the record has been successfully handled;
additionally, there is a cursor and the nested
loop algorithm produced the number of rows
that is specified for current cursor fetch
operation.
All return values except NESTED_LOOP_OK abort the nested loop.
*****************************************************************************/
/* ARGSUSED */
static int
static enum_nested_loop_state
end_send(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
bool end_of_records)
{
......@@ -9920,14 +9950,14 @@ end_send(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
{
int error;
if (join->having && join->having->val_int() == 0)
DBUG_RETURN(0); // Didn't match having
DBUG_RETURN(NESTED_LOOP_OK); // Didn't match having
error=0;
if (join->procedure)
error=join->procedure->send_row(*join->fields);
else if (join->do_send_rows)
error=join->result->send_data(*join->fields);
if (error)
DBUG_RETURN(-1); /* purecov: inspected */
DBUG_RETURN(NESTED_LOOP_ERROR); /* purecov: inspected */
if (++join->send_records >= join->unit->select_limit_cnt &&
join->do_send_rows)
{
......@@ -9961,10 +9991,10 @@ end_send(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
join->do_send_rows= 0;
if (join->unit->fake_select_lex)
join->unit->fake_select_lex->select_limit= HA_POS_ERROR;
DBUG_RETURN(0);
DBUG_RETURN(NESTED_LOOP_OK);
}
}
DBUG_RETURN(-3); // Abort nicely
DBUG_RETURN(NESTED_LOOP_QUERY_LIMIT); // Abort nicely
}
else if (join->send_records >= join->fetch_limit)
{
......@@ -9972,20 +10002,20 @@ end_send(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
There is a server side cursor and all rows for
this fetch request are sent.
*/
DBUG_RETURN(-4);
DBUG_RETURN(NESTED_LOOP_CURSOR_LIMIT);
}
}
else
{
if (join->procedure && join->procedure->end_of_records())
DBUG_RETURN(-1);
DBUG_RETURN(NESTED_LOOP_ERROR);
}
DBUG_RETURN(0);
DBUG_RETURN(NESTED_LOOP_OK);
}
/* ARGSUSED */
static int
static enum_nested_loop_state
end_send_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
bool end_of_records)
{
......@@ -10037,14 +10067,14 @@ end_send_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
}
}
if (error > 0)
DBUG_RETURN(-1); /* purecov: inspected */
DBUG_RETURN(NESTED_LOOP_ERROR); /* purecov: inspected */
if (end_of_records)
DBUG_RETURN(0);
DBUG_RETURN(NESTED_LOOP_OK);
if (join->send_records >= join->unit->select_limit_cnt &&
join->do_send_rows)
{
if (!(join->select_options & OPTION_FOUND_ROWS))
DBUG_RETURN(-3); // Abort nicely
DBUG_RETURN(NESTED_LOOP_QUERY_LIMIT); // Abort nicely
join->do_send_rows=0;
join->unit->select_limit_cnt = HA_POS_ERROR;
}
......@@ -10054,14 +10084,14 @@ end_send_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
There is a server side cursor and all rows
for this fetch request are sent.
*/
DBUG_RETURN(-4);
DBUG_RETURN(NESTED_LOOP_CURSOR_LIMIT);
}
}
}
else
{
if (end_of_records)
DBUG_RETURN(0);
DBUG_RETURN(NESTED_LOOP_OK);
join->first_record=1;
VOID(test_if_group_changed(join->group_fields));
}
......@@ -10069,33 +10099,32 @@ end_send_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
{
copy_fields(&join->tmp_table_param);
if (init_sum_functions(join->sum_funcs, join->sum_funcs_end[idx+1]))
DBUG_RETURN(-1);
DBUG_RETURN(NESTED_LOOP_ERROR);
if (join->procedure)
join->procedure->add();
DBUG_RETURN(0);
DBUG_RETURN(NESTED_LOOP_OK);
}
}
if (update_sum_func(join->sum_funcs))
DBUG_RETURN(-1);
DBUG_RETURN(NESTED_LOOP_ERROR);
if (join->procedure)
join->procedure->add();
DBUG_RETURN(0);
DBUG_RETURN(NESTED_LOOP_OK);
}
/* ARGSUSED */
static int
static enum_nested_loop_state
end_write(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
bool end_of_records)
{
TABLE *table=join->tmp_table;
int error;
DBUG_ENTER("end_write");
if (join->thd->killed) // Aborted by user
{
join->thd->send_kill_message();
DBUG_RETURN(-2); /* purecov: inspected */
DBUG_RETURN(NESTED_LOOP_KILLED); /* purecov: inspected */
}
if (!end_of_records)
{
......@@ -10120,6 +10149,7 @@ end_write(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
#endif
if (!join->having || join->having->val_int())
{
int error;
join->found_records++;
if ((error=table->file->write_row(table->record[0])))
{
......@@ -10128,28 +10158,28 @@ end_write(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
goto end;
if (create_myisam_from_heap(join->thd, table, &join->tmp_table_param,
error,1))
DBUG_RETURN(-1); // Not a table_is_full error
DBUG_RETURN(NESTED_LOOP_ERROR); // Not a table_is_full error
table->s->uniques=0; // To ensure rows are the same
}
if (++join->send_records >= join->tmp_table_param.end_write_records &&
join->do_send_rows)
{
if (!(join->select_options & OPTION_FOUND_ROWS))
DBUG_RETURN(-3);
DBUG_RETURN(NESTED_LOOP_QUERY_LIMIT);
join->do_send_rows=0;
join->unit->select_limit_cnt = HA_POS_ERROR;
DBUG_RETURN(0);
DBUG_RETURN(NESTED_LOOP_OK);
}
}
}
end:
DBUG_RETURN(0);
DBUG_RETURN(NESTED_LOOP_OK);
}
/* Group by searching after group record and updating it if possible */
/* ARGSUSED */
static int
static enum_nested_loop_state
end_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
bool end_of_records)
{
......@@ -10159,11 +10189,11 @@ end_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
DBUG_ENTER("end_update");
if (end_of_records)
DBUG_RETURN(0);
DBUG_RETURN(NESTED_LOOP_OK);
if (join->thd->killed) // Aborted by user
{
join->thd->send_kill_message();
DBUG_RETURN(-2); /* purecov: inspected */
DBUG_RETURN(NESTED_LOOP_KILLED); /* purecov: inspected */
}
join->found_records++;
......@@ -10187,9 +10217,9 @@ end_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
table->record[0])))
{
table->file->print_error(error,MYF(0)); /* purecov: inspected */
DBUG_RETURN(-1); /* purecov: inspected */
DBUG_RETURN(NESTED_LOOP_ERROR); /* purecov: inspected */
}
DBUG_RETURN(0);
DBUG_RETURN(NESTED_LOOP_OK);
}
/*
......@@ -10211,19 +10241,19 @@ end_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
{
if (create_myisam_from_heap(join->thd, table, &join->tmp_table_param,
error, 0))
DBUG_RETURN(-1); // Not a table_is_full error
DBUG_RETURN(NESTED_LOOP_ERROR); // Not a table_is_full error
/* Change method to update rows */
table->file->ha_index_init(0);
join->join_tab[join->tables-1].next_select=end_unique_update;
}
join->send_records++;
DBUG_RETURN(0);
DBUG_RETURN(NESTED_LOOP_OK);
}
/* Like end_update, but this is done with unique constraints instead of keys */
static int
static enum_nested_loop_state
end_unique_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
bool end_of_records)
{
......@@ -10232,11 +10262,11 @@ end_unique_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
DBUG_ENTER("end_unique_update");
if (end_of_records)
DBUG_RETURN(0);
DBUG_RETURN(NESTED_LOOP_OK);
if (join->thd->killed) // Aborted by user
{
join->thd->send_kill_message();
DBUG_RETURN(-2); /* purecov: inspected */
DBUG_RETURN(NESTED_LOOP_KILLED); /* purecov: inspected */
}
init_tmptable_sum_functions(join->sum_funcs);
......@@ -10250,12 +10280,12 @@ end_unique_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
if ((int) table->file->get_dup_key(error) < 0)
{
table->file->print_error(error,MYF(0)); /* purecov: inspected */
DBUG_RETURN(-1); /* purecov: inspected */
DBUG_RETURN(NESTED_LOOP_ERROR); /* purecov: inspected */
}
if (table->file->rnd_pos(table->record[1],table->file->dupp_ref))
{
table->file->print_error(error,MYF(0)); /* purecov: inspected */
DBUG_RETURN(-1); /* purecov: inspected */
DBUG_RETURN(NESTED_LOOP_ERROR); /* purecov: inspected */
}
restore_record(table,record[1]);
update_tmptable_sum_func(join->sum_funcs,table);
......@@ -10263,27 +10293,26 @@ end_unique_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
table->record[0])))
{
table->file->print_error(error,MYF(0)); /* purecov: inspected */
DBUG_RETURN(-1); /* purecov: inspected */
DBUG_RETURN(NESTED_LOOP_ERROR); /* purecov: inspected */
}
}
DBUG_RETURN(0);
DBUG_RETURN(NESTED_LOOP_OK);
}
/* ARGSUSED */
static int
static enum_nested_loop_state
end_write_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
bool end_of_records)
{
TABLE *table=join->tmp_table;
int error;
int idx= -1;
DBUG_ENTER("end_write_group");
if (join->thd->killed)
{ // Aborted by user
join->thd->send_kill_message();
DBUG_RETURN(-2); /* purecov: inspected */
DBUG_RETURN(NESTED_LOOP_KILLED); /* purecov: inspected */
}
if (!join->first_record || end_of_records ||
(idx=test_if_group_changed(join->group_fields)) >= 0)
......@@ -10302,28 +10331,27 @@ end_write_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
}
copy_sum_funcs(join->sum_funcs,
join->sum_funcs_end[send_group_parts]);
if (join->having && join->having->val_int() == 0)
error= -1;
else if ((error= table->file->write_row(table->record[0])))
if (!join->having || join->having->val_int())
{
if (create_myisam_from_heap(join->thd, table,
&join->tmp_table_param,
error, 0))
DBUG_RETURN(-1);
int error= table->file->write_row(table->record[0]);
if (error && create_myisam_from_heap(join->thd, table,
&join->tmp_table_param,
error, 0))
DBUG_RETURN(NESTED_LOOP_ERROR);
}
if (join->rollup.state != ROLLUP::STATE_NONE)
{
if (join->rollup_write_data((uint) (idx+1), table))
DBUG_RETURN(-1);
DBUG_RETURN(NESTED_LOOP_ERROR);
}
if (end_of_records)
DBUG_RETURN(0);
DBUG_RETURN(NESTED_LOOP_OK);
}
}
else
{
if (end_of_records)
DBUG_RETURN(0);
DBUG_RETURN(NESTED_LOOP_OK);
join->first_record=1;
VOID(test_if_group_changed(join->group_fields));
}
......@@ -10332,17 +10360,17 @@ end_write_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
copy_fields(&join->tmp_table_param);
copy_funcs(join->tmp_table_param.items_to_copy);
if (init_sum_functions(join->sum_funcs, join->sum_funcs_end[idx+1]))
DBUG_RETURN(-1);
DBUG_RETURN(NESTED_LOOP_ERROR);
if (join->procedure)
join->procedure->add();
DBUG_RETURN(0);
DBUG_RETURN(NESTED_LOOP_OK);
}
}
if (update_sum_func(join->sum_funcs))
DBUG_RETURN(-1);
DBUG_RETURN(NESTED_LOOP_ERROR);
if (join->procedure)
join->procedure->add();
DBUG_RETURN(0);
DBUG_RETURN(NESTED_LOOP_OK);
}
......
......@@ -91,7 +91,15 @@ enum join_type { JT_UNKNOWN,JT_SYSTEM,JT_CONST,JT_EQ_REF,JT_REF,JT_MAYBE_REF,
class JOIN;
typedef int (*Next_select_func)(JOIN *,struct st_join_table *,bool);
enum enum_nested_loop_state
{
NESTED_LOOP_KILLED= -2, NESTED_LOOP_ERROR= -1,
NESTED_LOOP_OK= 0, NESTED_LOOP_NO_MORE_ROWS= 1,
NESTED_LOOP_QUERY_LIMIT= 3, NESTED_LOOP_CURSOR_LIMIT= 4
};
typedef enum_nested_loop_state
(*Next_select_func)(JOIN *, struct st_join_table *, bool);
typedef int (*Read_record_func)(struct st_join_table *tab);
......@@ -162,6 +170,11 @@ class JOIN :public Sql_alloc
uint send_group_parts;
bool sort_and_group,first_record,full_join,group, no_field_update;
bool do_send_rows;
/*
TRUE when we want to resume nested loop iterations when
fetching data from a cursor
*/
bool resume_nested_loop;
table_map const_table_map,found_const_table_map,outer_join;
ha_rows send_records,found_records,examined_rows,row_limit, select_limit;
/*
......@@ -263,6 +276,7 @@ class JOIN :public Sql_alloc
sort_and_group= 0;
first_record= 0;
do_send_rows= 1;
resume_nested_loop= FALSE;
send_records= 0;
found_records= 0;
fetch_limit= HA_POS_ERROR;
......@@ -374,7 +388,7 @@ class Cursor: public Sql_alloc, public Item_arena
void reset_thd(THD *thd);
int open(JOIN *join);
int fetch(ulong num_rows);
void fetch(ulong num_rows);
void reset() { join= 0; }
bool is_open() const { return join != 0; }
void close();
......
......@@ -12854,6 +12854,59 @@ static void test_bug9159()
myquery(rc);
}
/* Crash when opening a cursor to a query with DISTICNT and no key */
static void test_bug9520()
{
MYSQL_STMT *stmt;
MYSQL_BIND bind[1];
char a[6];
ulong a_len;
int rc, row_count= 0;
myheader("test_bug9520");
mysql_query(mysql, "drop table if exists t1");
mysql_query(mysql, "create table t1 (a char(5), b char(5), c char(5),"
" primary key (a, b, c))");
rc= mysql_query(mysql, "insert into t1 values ('x', 'y', 'z'), "
" ('a', 'b', 'c'), ('k', 'l', 'm')");
myquery(rc);
stmt= open_cursor("select distinct b from t1");
/*
Not crashes with:
stmt= open_cursor("select distinct a from t1");
*/
rc= mysql_stmt_execute(stmt);
check_execute(stmt, rc);
bzero(bind, sizeof(bind));
bind[0].buffer_type= MYSQL_TYPE_STRING;
bind[0].buffer= (char*) a;
bind[0].buffer_length= sizeof(a);
bind[0].length= &a_len;
mysql_stmt_bind_result(stmt, bind);
while (!(rc= mysql_stmt_fetch(stmt)))
row_count++;
DIE_UNLESS(rc == MYSQL_NO_DATA);
printf("Fetched %d rows\n", row_count);
DBUG_ASSERT(row_count == 3);
mysql_stmt_close(stmt);
rc= mysql_query(mysql, "drop table t1");
myquery(rc);
}
/*
Read and parse arguments and MySQL options from my.cnf
*/
......@@ -13079,6 +13132,7 @@ static struct my_tests_st my_tests[]= {
{ "test_bug8722", test_bug8722 },
{ "test_bug8880", test_bug8880 },
{ "test_bug9159", test_bug9159 },
{ "test_bug9520", test_bug9520 },
{ 0, 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