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) ...@@ -2223,7 +2223,7 @@ void mysql_stmt_fetch(THD *thd, char *packet, uint packet_length)
my_pthread_setprio(pthread_self(), QUERY_PRIOR); my_pthread_setprio(pthread_self(), QUERY_PRIOR);
thd->protocol= &thd->protocol_prep; // Switch to binary protocol 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 thd->protocol= &thd->protocol_simple; // Use normal protocol
if (!(specialflag & SPECIAL_NO_PRIOR)) if (!(specialflag & SPECIAL_NO_PRIOR))
......
...@@ -114,17 +114,31 @@ static bool create_myisam_tmp_table(TABLE *table,TMP_TABLE_PARAM *param, ...@@ -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 Next_select_func setup_end_select_func(JOIN *join);
static int do_select(JOIN *join,List<Item> *fields,TABLE *tmp_table, static int do_select(JOIN *join,List<Item> *fields,TABLE *tmp_table,
Procedure *proc); 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 enum_nested_loop_state
static int flush_cached_records(JOIN *join,JOIN_TAB *join_tab,bool skip_last); sub_select_cache(JOIN *join, JOIN_TAB *join_tab, bool end_of_records);
static int end_send(JOIN *join, JOIN_TAB *join_tab, bool end_of_records); static enum_nested_loop_state
static int end_send_group(JOIN *join, JOIN_TAB *join_tab,bool end_of_records); evaluate_join_record(JOIN *join, JOIN_TAB *join_tab,
static int end_write(JOIN *join, JOIN_TAB *join_tab, bool end_of_records); int error, my_bool *report_error);
static int end_update(JOIN *join, JOIN_TAB *join_tab, bool end_of_records); static enum_nested_loop_state
static int end_unique_update(JOIN *join,JOIN_TAB *join_tab, evaluate_null_complemented_join_record(JOIN *join, JOIN_TAB *join_tab);
bool end_of_records); static enum_nested_loop_state
static int end_write_group(JOIN *join, JOIN_TAB *join_tab, sub_select(JOIN *join,JOIN_TAB *join_tab, bool end_of_records);
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 test_if_group_changed(List<Item_buff> &list);
static int join_read_const_table(JOIN_TAB *tab, POSITION *pos); static int join_read_const_table(JOIN_TAB *tab, POSITION *pos);
static int join_read_system(JOIN_TAB *tab); static int join_read_system(JOIN_TAB *tab);
...@@ -1800,13 +1814,7 @@ Cursor::open(JOIN *join_arg) ...@@ -1800,13 +1814,7 @@ Cursor::open(JOIN *join_arg)
happen for the first table in join_tab list happen for the first table in join_tab list
*/ */
DBUG_ASSERT(join_tab->table->null_row == 0); DBUG_ASSERT(join_tab->table->null_row == 0);
DBUG_RETURN(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));
} }
...@@ -1816,97 +1824,36 @@ Cursor::open(JOIN *join_arg) ...@@ -1816,97 +1824,36 @@ Cursor::open(JOIN *join_arg)
PRECONDITION: PRECONDITION:
Cursor is open Cursor is open
RETURN VALUES: RETURN VALUES:
-4 there are more rows, send_eof sent to the client none, this function will send error or OK to network if necessary.
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)
*/ */
int void
Cursor::fetch(ulong num_rows) Cursor::fetch(ulong num_rows)
{ {
THD *thd= join->thd; THD *thd= join->thd;
JOIN_TAB *join_tab= join->join_tab + join->const_tables; JOIN_TAB *join_tab= join->join_tab + join->const_tables;
COND *on_expr= *join_tab->on_expr_ref; enum_nested_loop_state error= NESTED_LOOP_OK;
COND *select_cond= join_tab->select_cond;
READ_RECORD *info= &join_tab->read_record;
int error= 0;
/* save references to memory, allocated during fetch */ /* save references to memory, allocated during fetch */
thd->set_n_backup_item_arena(this, &thd->stmt_backup); thd->set_n_backup_item_arena(this, &thd->stmt_backup);
join->fetch_limit+= num_rows; 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= sub_select(join, join_tab, 0);
error= 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 #ifdef USING_TRANSACTIONS
ha_release_temporary_latches(thd); ha_release_temporary_latches(thd);
#endif #endif
thd->restore_backup_item_arena(this, &thd->stmt_backup); 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 */ /* Fetch limit worked, possibly more rows are there */
thd->server_status|= SERVER_STATUS_CURSOR_EXISTS; thd->server_status|= SERVER_STATUS_CURSOR_EXISTS;
...@@ -1916,20 +1863,19 @@ Cursor::fetch(ulong num_rows) ...@@ -1916,20 +1863,19 @@ Cursor::fetch(ulong num_rows)
else else
{ {
close(); close();
if (error == 0) if (error == NESTED_LOOP_OK)
{ {
thd->server_status|= SERVER_STATUS_LAST_ROW_SENT; thd->server_status|= SERVER_STATUS_LAST_ROW_SENT;
::send_eof(thd); ::send_eof(thd);
thd->server_status&= ~SERVER_STATUS_LAST_ROW_SENT; 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)); my_message(ER_OUT_OF_RESOURCES, ER(ER_OUT_OF_RESOURCES), MYF(0));
/* free cursor memory */ /* free cursor memory */
free_items(free_list); free_items(free_list);
free_list= 0; free_list= 0;
free_root(&main_mem_root, MYF(0)); free_root(&main_mem_root, MYF(0));
} }
return error;
} }
...@@ -8948,7 +8894,8 @@ static Next_select_func setup_end_select_func(JOIN *join) ...@@ -8948,7 +8894,8 @@ static Next_select_func setup_end_select_func(JOIN *join)
static int static int
do_select(JOIN *join,List<Item> *fields,TABLE *table,Procedure *procedure) 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; JOIN_TAB *join_tab;
DBUG_ENTER("do_select"); DBUG_ENTER("do_select");
...@@ -8978,24 +8925,30 @@ do_select(JOIN *join,List<Item> *fields,TABLE *table,Procedure *procedure) ...@@ -8978,24 +8925,30 @@ do_select(JOIN *join,List<Item> *fields,TABLE *table,Procedure *procedure)
if (!join->conds || join->conds->val_int()) if (!join->conds || join->conds->val_int())
{ {
Next_select_func end_select= join->join_tab[join->tables-1].next_select; 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,0);
error=(*end_select)(join,join_tab,1); 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()) else if (join->send_row_on_empty_set())
error= join->result->send_data(*join->fields); rc= join->result->send_data(*join->fields);
} }
else else
{ {
error= sub_select(join,join_tab,0); 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); error= sub_select(join,join_tab,1);
if (error == -3) if (error == NESTED_LOOP_QUERY_LIMIT)
error= 0; /* select_limit used */ 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 if (!table) // If sending data to client
{ {
/* /*
...@@ -9004,10 +8957,12 @@ do_select(JOIN *join,List<Item> *fields,TABLE *table,Procedure *procedure) ...@@ -9004,10 +8957,12 @@ do_select(JOIN *join,List<Item> *fields,TABLE *table,Procedure *procedure)
*/ */
join->join_free(0); // Unlock all cursors join->join_free(0); // Unlock all cursors
if (join->result->send_eof()) 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)); DBUG_PRINT("info",("%ld records output",join->send_records));
} }
else
rc= -1;
if (table) if (table)
{ {
int tmp, new_errno= 0; int tmp, new_errno= 0;
...@@ -9025,40 +8980,42 @@ do_select(JOIN *join,List<Item> *fields,TABLE *table,Procedure *procedure) ...@@ -9025,40 +8980,42 @@ do_select(JOIN *join,List<Item> *fields,TABLE *table,Procedure *procedure)
table->file->print_error(new_errno,MYF(0)); table->file->print_error(new_errno,MYF(0));
} }
#ifndef DBUG_OFF #ifndef DBUG_OFF
if (error) if (rc)
{ {
DBUG_PRINT("error",("Error: do_select() failed")); DBUG_PRINT("error",("Error: do_select() failed"));
} }
#endif #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) 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 (end_of_records)
{ {
if ((error=flush_cached_records(join,join_tab,FALSE)) < 0) rc= flush_cached_records(join,join_tab,FALSE);
return error; /* purecov: inspected */ if (rc == NESTED_LOOP_OK || rc == NESTED_LOOP_NO_MORE_ROWS)
return sub_select(join,join_tab,end_of_records); rc= sub_select(join,join_tab,end_of_records);
return rc;
} }
if (join->thd->killed) // If aborted by user if (join->thd->killed) // If aborted by user
{ {
join->thd->send_kill_message(); 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 (join_tab->use_quick != 2 || test_if_quick_select(join_tab) <= 0)
{ {
if (!store_record_in_cache(&join_tab->cache)) 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); return flush_cached_records(join,join_tab,FALSE);
} }
if ((error=flush_cached_records(join,join_tab,TRUE)) < 0) rc= flush_cached_records(join, join_tab, TRUE);
return error; /* purecov: inspected */ if (rc == NESTED_LOOP_OK || rc == NESTED_LOOP_NO_MORE_ROWS)
return sub_select(join,join_tab,end_of_records); /* Use ordinary select */ 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) ...@@ -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. table of the embedding nested join, if any.
RETURN RETURN
0, if success return one of enum_nested_loop_state, except NESTED_LOOP_NO_MORE_ROWS.
# of the error, otherwise
*/ */
static int static enum_nested_loop_state
sub_select(JOIN *join,JOIN_TAB *join_tab,bool end_of_records) sub_select(JOIN *join,JOIN_TAB *join_tab,bool end_of_records)
{ {
join_tab->table->null_row=0; join_tab->table->null_row=0;
...@@ -9182,206 +9138,258 @@ sub_select(JOIN *join,JOIN_TAB *join_tab,bool end_of_records) ...@@ -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); return (*join_tab->next_select)(join,join_tab+1,end_of_records);
int error; int error;
JOIN_TAB *first_unmatched; enum_nested_loop_state rc;
JOIN_TAB *tab;
/* Cache variables for faster loop */
COND *select_cond= join_tab->select_cond;
my_bool *report_error= &(join->thd->net.report_error); my_bool *report_error= &(join->thd->net.report_error);
READ_RECORD *info= &join_tab->read_record;
join->return_tab= join_tab; if (join->resume_nested_loop)
{
if (join_tab->last_inner) /* 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.*/ if (join_tab->last_inner)
join_tab->found=0; {
join_tab->not_null_compl= 1; /* join_tab is the first inner table for an outer join operation. */
/* Set first_unmatched for the last inner table of this group */ /* Set initial state of guard variables for this table.*/
join_tab->last_inner->first_unmatched= join_tab; 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; error= info->read_record(info);
bool not_used_in_distinct=join_tab->not_used_in_distinct; rc= evaluate_join_record(join, join_tab, error, report_error);
ha_rows found_records=join->found_records; }
READ_RECORD *info= &join_tab->read_record;
join->thd->row_count= 0; if (rc == NESTED_LOOP_NO_MORE_ROWS &&
do 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 /*
{ The while condition is always false if join_tab is not
join->thd->send_kill_message(); the last inner join table of an outer join operation.
return -2; /* purecov: inspected */ */
} JOIN_TAB *first_unmatched= join_tab->first_unmatched;
DBUG_PRINT("info", ("select cond 0x%lx", (ulong)select_cond)); /*
if (!select_cond || select_cond->val_int()) 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++)
{ {
/* /* Check all predicates that has just been activated. */
There is no select condition or the attached pushed down /*
condition is true => a match is found. Actually all predicates non-guarded by first_unmatched->found
*/ will be re-evaluated again. It could be fixed, but, probably,
bool found= 1; it's not worth doing now.
while (join_tab->first_unmatched && found) */
if (tab->select_cond && !tab->select_cond->val_int())
{ {
/* /* The condition attached to table tab is false */
The while condition is always false if join_tab is not if (tab == join_tab)
the last inner join table of an outer join operation. found= 0;
*/ else
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. */
/* /*
Actually all predicates non-guarded by first_unmatched->found Set a return point if rejected predicate is attached
will be re-evaluated again. It could be fixed, but, probably, not to the last table of the current nest level.
it's not worth doing now. */
*/ join->return_tab= tab;
if (tab->select_cond && !tab->select_cond->val_int()) return NESTED_LOOP_OK;
{
/* 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;
}
}
} }
/*
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 /*
{ Check whether join_tab is not the last inner table
/* for another embedding outer join.
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;
if ((first_unmatched= first_unmatched->first_upper) && if ((first_unmatched= first_unmatched->first_upper) &&
first_unmatched->last_inner != join_tab) first_unmatched->last_inner != join_tab)
first_unmatched= 0; first_unmatched= 0;
join_tab->first_unmatched= first_unmatched; 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 It was not just a return to lower loop level when one
attached to inner tables. of the newly activated predicates is evaluated as false
Send the row complemented by nulls to be joined with the (See above join->return_tab= tab).
remaining tables. */
*/ join->examined_rows++;
if ((error=(*join_tab->next_select)(join, join_tab+1 ,0)) < 0) join->thd->row_count++;
return error;
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) flush_cached_records(JOIN *join,JOIN_TAB *join_tab,bool skip_last)
{ {
enum_nested_loop_state rc= NESTED_LOOP_OK;
int error; int error;
READ_RECORD *info; READ_RECORD *info;
if (!join_tab->cache.records) if (!join_tab->cache.records)
return 0; /* Nothing to do */ return NESTED_LOOP_OK; /* Nothing to do */
if (skip_last) if (skip_last)
(void) store_record_in_cache(&join_tab->cache); // Must save this for later (void) store_record_in_cache(&join_tab->cache); // Must save this for later
if (join_tab->use_quick == 2) if (join_tab->use_quick == 2)
...@@ -9396,7 +9404,7 @@ flush_cached_records(JOIN *join,JOIN_TAB *join_tab,bool skip_last) ...@@ -9396,7 +9404,7 @@ flush_cached_records(JOIN *join,JOIN_TAB *join_tab,bool skip_last)
if ((error=join_init_read_record(join_tab))) if ((error=join_init_read_record(join_tab)))
{ {
reset_cache_write(&join_tab->cache); 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++) 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) ...@@ -9411,11 +9419,11 @@ flush_cached_records(JOIN *join,JOIN_TAB *join_tab,bool skip_last)
if (join->thd->killed) if (join->thd->killed)
{ {
join->thd->send_kill_message(); 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; SQL_SELECT *select=join_tab->select;
if (!error && (!join_tab->cache.select || if (rc == NESTED_LOOP_OK &&
!join_tab->cache.select->skip_record())) (!join_tab->cache.select || !join_tab->cache.select->skip_record()))
{ {
uint i; uint i;
reset_cache_read(&join_tab->cache); reset_cache_read(&join_tab->cache);
...@@ -9423,11 +9431,14 @@ flush_cached_records(JOIN *join,JOIN_TAB *join_tab,bool skip_last) ...@@ -9423,11 +9431,14 @@ flush_cached_records(JOIN *join,JOIN_TAB *join_tab,bool skip_last)
{ {
read_cached_record(join_tab); read_cached_record(join_tab);
if (!select || !select->skip_record()) 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); reset_cache_write(&join_tab->cache);
return error; /* purecov: inspected */ return rc;
} }
}
} }
} }
} while (!(error=info->read_record(info))); } while (!(error=info->read_record(info)));
...@@ -9436,10 +9447,10 @@ flush_cached_records(JOIN *join,JOIN_TAB *join_tab,bool skip_last) ...@@ -9436,10 +9447,10 @@ flush_cached_records(JOIN *join,JOIN_TAB *join_tab,bool skip_last)
read_cached_record(join_tab); // Restore current record read_cached_record(join_tab); // Restore current record
reset_cache_write(&join_tab->cache); reset_cache_write(&join_tab->cache);
if (error > 0) // Fatal error 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++) for (JOIN_TAB *tmp2=join->join_tab; tmp2 != join_tab ; tmp2++)
tmp2->table->status=tmp2->status; tmp2->table->status=tmp2->status;
return 0; return NESTED_LOOP_OK;
} }
...@@ -9905,13 +9916,32 @@ join_read_next_same_or_null(READ_RECORD *info) ...@@ -9905,13 +9916,32 @@ join_read_next_same_or_null(READ_RECORD *info)
/***************************************************************************** /*****************************************************************************
The different end of select functions DESCRIPTION
These functions returns < 0 when end is reached, 0 on ok and > 0 if a Functions that end one nested loop iteration. Different functions
fatal error (like table corruption) was detected 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 */ /* ARGSUSED */
static int static enum_nested_loop_state
end_send(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), end_send(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
bool end_of_records) bool end_of_records)
{ {
...@@ -9920,14 +9950,14 @@ end_send(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), ...@@ -9920,14 +9950,14 @@ end_send(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
{ {
int error; int error;
if (join->having && join->having->val_int() == 0) 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; error=0;
if (join->procedure) if (join->procedure)
error=join->procedure->send_row(*join->fields); error=join->procedure->send_row(*join->fields);
else if (join->do_send_rows) else if (join->do_send_rows)
error=join->result->send_data(*join->fields); error=join->result->send_data(*join->fields);
if (error) if (error)
DBUG_RETURN(-1); /* purecov: inspected */ DBUG_RETURN(NESTED_LOOP_ERROR); /* purecov: inspected */
if (++join->send_records >= join->unit->select_limit_cnt && if (++join->send_records >= join->unit->select_limit_cnt &&
join->do_send_rows) join->do_send_rows)
{ {
...@@ -9961,10 +9991,10 @@ end_send(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), ...@@ -9961,10 +9991,10 @@ end_send(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
join->do_send_rows= 0; join->do_send_rows= 0;
if (join->unit->fake_select_lex) if (join->unit->fake_select_lex)
join->unit->fake_select_lex->select_limit= HA_POS_ERROR; 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) else if (join->send_records >= join->fetch_limit)
{ {
...@@ -9972,20 +10002,20 @@ end_send(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), ...@@ -9972,20 +10002,20 @@ end_send(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
There is a server side cursor and all rows for There is a server side cursor and all rows for
this fetch request are sent. this fetch request are sent.
*/ */
DBUG_RETURN(-4); DBUG_RETURN(NESTED_LOOP_CURSOR_LIMIT);
} }
} }
else else
{ {
if (join->procedure && join->procedure->end_of_records()) 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 */ /* ARGSUSED */
static int static enum_nested_loop_state
end_send_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), end_send_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
bool end_of_records) bool end_of_records)
{ {
...@@ -10037,14 +10067,14 @@ end_send_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), ...@@ -10037,14 +10067,14 @@ end_send_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
} }
} }
if (error > 0) if (error > 0)
DBUG_RETURN(-1); /* purecov: inspected */ DBUG_RETURN(NESTED_LOOP_ERROR); /* purecov: inspected */
if (end_of_records) if (end_of_records)
DBUG_RETURN(0); DBUG_RETURN(NESTED_LOOP_OK);
if (join->send_records >= join->unit->select_limit_cnt && if (join->send_records >= join->unit->select_limit_cnt &&
join->do_send_rows) join->do_send_rows)
{ {
if (!(join->select_options & OPTION_FOUND_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->do_send_rows=0;
join->unit->select_limit_cnt = HA_POS_ERROR; join->unit->select_limit_cnt = HA_POS_ERROR;
} }
...@@ -10054,14 +10084,14 @@ end_send_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), ...@@ -10054,14 +10084,14 @@ end_send_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
There is a server side cursor and all rows There is a server side cursor and all rows
for this fetch request are sent. for this fetch request are sent.
*/ */
DBUG_RETURN(-4); DBUG_RETURN(NESTED_LOOP_CURSOR_LIMIT);
} }
} }
} }
else else
{ {
if (end_of_records) if (end_of_records)
DBUG_RETURN(0); DBUG_RETURN(NESTED_LOOP_OK);
join->first_record=1; join->first_record=1;
VOID(test_if_group_changed(join->group_fields)); VOID(test_if_group_changed(join->group_fields));
} }
...@@ -10069,33 +10099,32 @@ end_send_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), ...@@ -10069,33 +10099,32 @@ end_send_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
{ {
copy_fields(&join->tmp_table_param); copy_fields(&join->tmp_table_param);
if (init_sum_functions(join->sum_funcs, join->sum_funcs_end[idx+1])) if (init_sum_functions(join->sum_funcs, join->sum_funcs_end[idx+1]))
DBUG_RETURN(-1); DBUG_RETURN(NESTED_LOOP_ERROR);
if (join->procedure) if (join->procedure)
join->procedure->add(); join->procedure->add();
DBUG_RETURN(0); DBUG_RETURN(NESTED_LOOP_OK);
} }
} }
if (update_sum_func(join->sum_funcs)) if (update_sum_func(join->sum_funcs))
DBUG_RETURN(-1); DBUG_RETURN(NESTED_LOOP_ERROR);
if (join->procedure) if (join->procedure)
join->procedure->add(); join->procedure->add();
DBUG_RETURN(0); DBUG_RETURN(NESTED_LOOP_OK);
} }
/* ARGSUSED */ /* ARGSUSED */
static int static enum_nested_loop_state
end_write(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), end_write(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
bool end_of_records) bool end_of_records)
{ {
TABLE *table=join->tmp_table; TABLE *table=join->tmp_table;
int error;
DBUG_ENTER("end_write"); DBUG_ENTER("end_write");
if (join->thd->killed) // Aborted by user if (join->thd->killed) // Aborted by user
{ {
join->thd->send_kill_message(); join->thd->send_kill_message();
DBUG_RETURN(-2); /* purecov: inspected */ DBUG_RETURN(NESTED_LOOP_KILLED); /* purecov: inspected */
} }
if (!end_of_records) if (!end_of_records)
{ {
...@@ -10120,6 +10149,7 @@ end_write(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), ...@@ -10120,6 +10149,7 @@ end_write(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
#endif #endif
if (!join->having || join->having->val_int()) if (!join->having || join->having->val_int())
{ {
int error;
join->found_records++; join->found_records++;
if ((error=table->file->write_row(table->record[0]))) if ((error=table->file->write_row(table->record[0])))
{ {
...@@ -10128,28 +10158,28 @@ end_write(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), ...@@ -10128,28 +10158,28 @@ end_write(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
goto end; goto end;
if (create_myisam_from_heap(join->thd, table, &join->tmp_table_param, if (create_myisam_from_heap(join->thd, table, &join->tmp_table_param,
error,1)) 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 table->s->uniques=0; // To ensure rows are the same
} }
if (++join->send_records >= join->tmp_table_param.end_write_records && if (++join->send_records >= join->tmp_table_param.end_write_records &&
join->do_send_rows) join->do_send_rows)
{ {
if (!(join->select_options & OPTION_FOUND_ROWS)) if (!(join->select_options & OPTION_FOUND_ROWS))
DBUG_RETURN(-3); DBUG_RETURN(NESTED_LOOP_QUERY_LIMIT);
join->do_send_rows=0; join->do_send_rows=0;
join->unit->select_limit_cnt = HA_POS_ERROR; join->unit->select_limit_cnt = HA_POS_ERROR;
DBUG_RETURN(0); DBUG_RETURN(NESTED_LOOP_OK);
} }
} }
} }
end: end:
DBUG_RETURN(0); DBUG_RETURN(NESTED_LOOP_OK);
} }
/* Group by searching after group record and updating it if possible */ /* Group by searching after group record and updating it if possible */
/* ARGSUSED */ /* ARGSUSED */
static int static enum_nested_loop_state
end_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), end_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
bool end_of_records) bool end_of_records)
{ {
...@@ -10159,11 +10189,11 @@ end_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), ...@@ -10159,11 +10189,11 @@ end_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
DBUG_ENTER("end_update"); DBUG_ENTER("end_update");
if (end_of_records) if (end_of_records)
DBUG_RETURN(0); DBUG_RETURN(NESTED_LOOP_OK);
if (join->thd->killed) // Aborted by user if (join->thd->killed) // Aborted by user
{ {
join->thd->send_kill_message(); join->thd->send_kill_message();
DBUG_RETURN(-2); /* purecov: inspected */ DBUG_RETURN(NESTED_LOOP_KILLED); /* purecov: inspected */
} }
join->found_records++; join->found_records++;
...@@ -10187,9 +10217,9 @@ end_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), ...@@ -10187,9 +10217,9 @@ end_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
table->record[0]))) table->record[0])))
{ {
table->file->print_error(error,MYF(0)); /* purecov: inspected */ 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)), ...@@ -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, if (create_myisam_from_heap(join->thd, table, &join->tmp_table_param,
error, 0)) 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 */ /* Change method to update rows */
table->file->ha_index_init(0); table->file->ha_index_init(0);
join->join_tab[join->tables-1].next_select=end_unique_update; join->join_tab[join->tables-1].next_select=end_unique_update;
} }
join->send_records++; join->send_records++;
DBUG_RETURN(0); DBUG_RETURN(NESTED_LOOP_OK);
} }
/* Like end_update, but this is done with unique constraints instead of keys */ /* 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)), end_unique_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
bool end_of_records) bool end_of_records)
{ {
...@@ -10232,11 +10262,11 @@ end_unique_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), ...@@ -10232,11 +10262,11 @@ end_unique_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
DBUG_ENTER("end_unique_update"); DBUG_ENTER("end_unique_update");
if (end_of_records) if (end_of_records)
DBUG_RETURN(0); DBUG_RETURN(NESTED_LOOP_OK);
if (join->thd->killed) // Aborted by user if (join->thd->killed) // Aborted by user
{ {
join->thd->send_kill_message(); join->thd->send_kill_message();
DBUG_RETURN(-2); /* purecov: inspected */ DBUG_RETURN(NESTED_LOOP_KILLED); /* purecov: inspected */
} }
init_tmptable_sum_functions(join->sum_funcs); init_tmptable_sum_functions(join->sum_funcs);
...@@ -10250,12 +10280,12 @@ end_unique_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), ...@@ -10250,12 +10280,12 @@ end_unique_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
if ((int) table->file->get_dup_key(error) < 0) if ((int) table->file->get_dup_key(error) < 0)
{ {
table->file->print_error(error,MYF(0)); /* purecov: inspected */ 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)) if (table->file->rnd_pos(table->record[1],table->file->dupp_ref))
{ {
table->file->print_error(error,MYF(0)); /* purecov: inspected */ 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]); restore_record(table,record[1]);
update_tmptable_sum_func(join->sum_funcs,table); update_tmptable_sum_func(join->sum_funcs,table);
...@@ -10263,27 +10293,26 @@ end_unique_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), ...@@ -10263,27 +10293,26 @@ end_unique_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
table->record[0]))) table->record[0])))
{ {
table->file->print_error(error,MYF(0)); /* purecov: inspected */ 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 */ /* ARGSUSED */
static int static enum_nested_loop_state
end_write_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), end_write_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
bool end_of_records) bool end_of_records)
{ {
TABLE *table=join->tmp_table; TABLE *table=join->tmp_table;
int error;
int idx= -1; int idx= -1;
DBUG_ENTER("end_write_group"); DBUG_ENTER("end_write_group");
if (join->thd->killed) if (join->thd->killed)
{ // Aborted by user { // Aborted by user
join->thd->send_kill_message(); join->thd->send_kill_message();
DBUG_RETURN(-2); /* purecov: inspected */ DBUG_RETURN(NESTED_LOOP_KILLED); /* purecov: inspected */
} }
if (!join->first_record || end_of_records || if (!join->first_record || end_of_records ||
(idx=test_if_group_changed(join->group_fields)) >= 0) (idx=test_if_group_changed(join->group_fields)) >= 0)
...@@ -10302,28 +10331,27 @@ end_write_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), ...@@ -10302,28 +10331,27 @@ end_write_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
} }
copy_sum_funcs(join->sum_funcs, copy_sum_funcs(join->sum_funcs,
join->sum_funcs_end[send_group_parts]); join->sum_funcs_end[send_group_parts]);
if (join->having && join->having->val_int() == 0) if (!join->having || join->having->val_int())
error= -1;
else if ((error= table->file->write_row(table->record[0])))
{ {
if (create_myisam_from_heap(join->thd, table, int error= table->file->write_row(table->record[0]);
&join->tmp_table_param, if (error && create_myisam_from_heap(join->thd, table,
error, 0)) &join->tmp_table_param,
DBUG_RETURN(-1); error, 0))
DBUG_RETURN(NESTED_LOOP_ERROR);
} }
if (join->rollup.state != ROLLUP::STATE_NONE) if (join->rollup.state != ROLLUP::STATE_NONE)
{ {
if (join->rollup_write_data((uint) (idx+1), table)) if (join->rollup_write_data((uint) (idx+1), table))
DBUG_RETURN(-1); DBUG_RETURN(NESTED_LOOP_ERROR);
} }
if (end_of_records) if (end_of_records)
DBUG_RETURN(0); DBUG_RETURN(NESTED_LOOP_OK);
} }
} }
else else
{ {
if (end_of_records) if (end_of_records)
DBUG_RETURN(0); DBUG_RETURN(NESTED_LOOP_OK);
join->first_record=1; join->first_record=1;
VOID(test_if_group_changed(join->group_fields)); VOID(test_if_group_changed(join->group_fields));
} }
...@@ -10332,17 +10360,17 @@ end_write_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), ...@@ -10332,17 +10360,17 @@ end_write_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
copy_fields(&join->tmp_table_param); copy_fields(&join->tmp_table_param);
copy_funcs(join->tmp_table_param.items_to_copy); copy_funcs(join->tmp_table_param.items_to_copy);
if (init_sum_functions(join->sum_funcs, join->sum_funcs_end[idx+1])) if (init_sum_functions(join->sum_funcs, join->sum_funcs_end[idx+1]))
DBUG_RETURN(-1); DBUG_RETURN(NESTED_LOOP_ERROR);
if (join->procedure) if (join->procedure)
join->procedure->add(); join->procedure->add();
DBUG_RETURN(0); DBUG_RETURN(NESTED_LOOP_OK);
} }
} }
if (update_sum_func(join->sum_funcs)) if (update_sum_func(join->sum_funcs))
DBUG_RETURN(-1); DBUG_RETURN(NESTED_LOOP_ERROR);
if (join->procedure) if (join->procedure)
join->procedure->add(); 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, ...@@ -91,7 +91,15 @@ enum join_type { JT_UNKNOWN,JT_SYSTEM,JT_CONST,JT_EQ_REF,JT_REF,JT_MAYBE_REF,
class JOIN; 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); typedef int (*Read_record_func)(struct st_join_table *tab);
...@@ -162,6 +170,11 @@ class JOIN :public Sql_alloc ...@@ -162,6 +170,11 @@ class JOIN :public Sql_alloc
uint send_group_parts; uint send_group_parts;
bool sort_and_group,first_record,full_join,group, no_field_update; bool sort_and_group,first_record,full_join,group, no_field_update;
bool do_send_rows; 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; table_map const_table_map,found_const_table_map,outer_join;
ha_rows send_records,found_records,examined_rows,row_limit, select_limit; ha_rows send_records,found_records,examined_rows,row_limit, select_limit;
/* /*
...@@ -263,6 +276,7 @@ class JOIN :public Sql_alloc ...@@ -263,6 +276,7 @@ class JOIN :public Sql_alloc
sort_and_group= 0; sort_and_group= 0;
first_record= 0; first_record= 0;
do_send_rows= 1; do_send_rows= 1;
resume_nested_loop= FALSE;
send_records= 0; send_records= 0;
found_records= 0; found_records= 0;
fetch_limit= HA_POS_ERROR; fetch_limit= HA_POS_ERROR;
...@@ -374,7 +388,7 @@ class Cursor: public Sql_alloc, public Item_arena ...@@ -374,7 +388,7 @@ class Cursor: public Sql_alloc, public Item_arena
void reset_thd(THD *thd); void reset_thd(THD *thd);
int open(JOIN *join); int open(JOIN *join);
int fetch(ulong num_rows); void fetch(ulong num_rows);
void reset() { join= 0; } void reset() { join= 0; }
bool is_open() const { return join != 0; } bool is_open() const { return join != 0; }
void close(); void close();
......
...@@ -12854,6 +12854,59 @@ static void test_bug9159() ...@@ -12854,6 +12854,59 @@ static void test_bug9159()
myquery(rc); 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 Read and parse arguments and MySQL options from my.cnf
*/ */
...@@ -13079,6 +13132,7 @@ static struct my_tests_st my_tests[]= { ...@@ -13079,6 +13132,7 @@ static struct my_tests_st my_tests[]= {
{ "test_bug8722", test_bug8722 }, { "test_bug8722", test_bug8722 },
{ "test_bug8880", test_bug8880 }, { "test_bug8880", test_bug8880 },
{ "test_bug9159", test_bug9159 }, { "test_bug9159", test_bug9159 },
{ "test_bug9520", test_bug9520 },
{ 0, 0 } { 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