diff options
Diffstat (limited to 'sql/sql_union.cc')
-rw-r--r-- | sql/sql_union.cc | 638 |
1 files changed, 394 insertions, 244 deletions
diff --git a/sql/sql_union.cc b/sql/sql_union.cc index 34acd79f18b..75fd9be88bd 100644 --- a/sql/sql_union.cc +++ b/sql/sql_union.cc @@ -1,4 +1,4 @@ -/* Copyright (C) 2000 MySQL AB +/* Copyright (C) 2000-2003 MySQL AB This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,303 +24,453 @@ #include "mysql_priv.h" #include "sql_select.h" - -int mysql_union(THD *thd, LEX *lex,select_result *result) +int mysql_union(THD *thd, LEX *lex, select_result *result, + SELECT_LEX_UNIT *unit) { - SELECT_LEX *sl, *last_sl, *lex_sl; - ORDER *order; - List<Item> item_list; - TABLE *table; - int res; - ulonglong add_rows= 0; - ulong found_rows_for_union= lex->select_lex.options & OPTION_FOUND_ROWS; - ulong describe= lex->select_lex.options & SELECT_DESCRIBE; - TABLE_LIST result_table_list; - TABLE_LIST *first_table=(TABLE_LIST *)lex->select_lex.table_list.first; - TMP_TABLE_PARAM tmp_table_param; - select_union *union_result; DBUG_ENTER("mysql_union"); + int res= 0; + if (!(res= unit->prepare(thd, result, SELECT_NO_UNLOCK))) + res= unit->exec(); + res|= unit->cleanup(); + DBUG_RETURN(res); +} + + +/*************************************************************************** +** store records in temporary table for UNION +***************************************************************************/ + +select_union::select_union(TABLE *table_par) + :table(table_par), not_describe(0) +{ + bzero((char*) &info,sizeof(info)); + /* + We can always use DUP_IGNORE because the temporary table will only + contain a unique key if we are using not using UNION ALL + */ + info.handle_duplicates= DUP_IGNORE; +} + +select_union::~select_union() +{ +} - /* Fix tables 'to-be-unioned-from' list to point at opened tables */ - last_sl= &lex->select_lex; - for (sl= last_sl; - sl && sl->linkage != NOT_A_SELECT; - last_sl=sl, sl=sl->next) + +int select_union::prepare(List<Item> &list, SELECT_LEX_UNIT *u) +{ + unit= u; + return 0; +} + + +bool select_union::send_data(List<Item> &values) +{ + if (unit->offset_limit_cnt) + { // using limit offset,count + unit->offset_limit_cnt--; + return 0; + } + fill_record(table->field, values, 1); + if (thd->net.report_error || write_record(table,&info)) { - for (TABLE_LIST *cursor= (TABLE_LIST *)sl->table_list.first; - cursor; - cursor=cursor->next) + if (thd->net.last_errno == ER_RECORD_FILE_FULL) { - cursor->table= (my_reinterpret_cast(TABLE_LIST*) (cursor->table))->table; + thd->clear_error(); // do not report user about table overflow + if (create_myisam_from_heap(thd, table, &tmp_table_param, + info.last_errno, 1)) + return 1; } + else + return 1; } + return 0; +} - /* last_sel now points at the last select where the ORDER BY is stored */ - if (sl) - { - /* - The found SL is an extra SELECT_LEX argument that contains - the ORDER BY and LIMIT parameter for the whole UNION - */ - lex_sl= sl; - order= (ORDER *) lex_sl->order_list.first; - // This is done to eliminate unnecessary slowing down of the first query - if (!order || !describe) - last_sl->next=0; // Remove this extra element - } - else if (!last_sl->braces) - { - lex_sl= last_sl; // ORDER BY is here - order= (ORDER *) lex_sl->order_list.first; - } - else + +bool select_union::send_eof() +{ + return 0; +} + + +bool select_union::flush() +{ + int error; + if ((error=table->file->extra(HA_EXTRA_NO_CACHE))) { - lex_sl=0; - order=0; + table->file->print_error(error,MYF(0)); + ::send_error(thd); + return 1; } + return 0; +} + + +int st_select_lex_unit::prepare(THD *thd_arg, select_result *sel_result, + ulong additional_options) +{ + SELECT_LEX *lex_select_save= thd_arg->lex->current_select; + SELECT_LEX *sl, *first_select; + select_result *tmp_result; + DBUG_ENTER("st_select_lex_unit::prepare"); + + /* + result object should be reassigned even if preparing already done for + max/min subquery (ALL/ANY optimization) + */ + result= sel_result; + + if (prepared) + DBUG_RETURN(0); + prepared= 1; + res= 0; - if (describe) + thd_arg->lex->current_select= sl= first_select= first_select_in_union(); + found_rows_for_union= first_select->options & OPTION_FOUND_ROWS; + + /* Global option */ + + if (first_select->next_select()) { - Item *item; - item_list.push_back(new Item_empty_string("table",NAME_LEN)); - item_list.push_back(new Item_empty_string("type",10)); - item_list.push_back(item=new Item_empty_string("possible_keys", - NAME_LEN*MAX_KEY)); - item->maybe_null=1; - item_list.push_back(item=new Item_empty_string("key",NAME_LEN)); - item->maybe_null=1; - item_list.push_back(item=new Item_int("key_len",0,3)); - item->maybe_null=1; - item_list.push_back(item=new Item_empty_string("ref", - NAME_LEN*MAX_REF_PARTS)); - item->maybe_null=1; - item_list.push_back(new Item_real("rows",0.0,0,10)); - item_list.push_back(new Item_empty_string("Extra",255)); + if (!(tmp_result= union_result= new select_union(0))) + goto err; + union_result->not_describe= 1; + union_result->tmp_table_param.init(); } else { - Item *item; - List_iterator<Item> it(lex->select_lex.item_list); - TABLE_LIST *first_table= (TABLE_LIST*) lex->select_lex.table_list.first; - - /* Create a list of items that will be in the result set */ - while ((item= it++)) - if (item_list.push_back(item)) - DBUG_RETURN(-1); - if (setup_tables(first_table) || - setup_fields(thd,first_table,item_list,0,0,1)) - DBUG_RETURN(-1); + tmp_result= sel_result; + // single select should be processed like select in p[arantses + first_select->braces= 1; } - bzero((char*) &tmp_table_param,sizeof(tmp_table_param)); - tmp_table_param.field_count=item_list.elements; - if (!(table=create_tmp_table(thd, &tmp_table_param, item_list, - (ORDER*) 0, !describe & !lex->union_option, - 1, 0, - (lex->select_lex.options | thd->options | - TMP_TABLE_ALL_COLUMNS)))) - DBUG_RETURN(-1); - table->file->extra(HA_EXTRA_WRITE_CACHE); - table->file->extra(HA_EXTRA_IGNORE_DUP_KEY); - bzero((char*) &result_table_list,sizeof(result_table_list)); - result_table_list.db= (char*) ""; - result_table_list.real_name=result_table_list.alias= (char*) "union"; - result_table_list.table=table; - - if (!(union_result=new select_union(table))) + for (;sl; sl= sl->next_select()) { - res= -1; - goto exit; - } - union_result->not_describe= !describe; - union_result->tmp_table_param=&tmp_table_param; - for (sl= &lex->select_lex; sl; sl=sl->next) - { - ha_rows records_at_start; - lex->select=sl; - /* Don't use offset for the last union if there is no braces */ - if (sl != lex_sl) + sl->options|= SELECT_NO_UNLOCK; + JOIN *join= new JOIN(thd_arg, sl->item_list, + sl->options | thd_arg->options | additional_options, + tmp_result); + thd_arg->lex->current_select= sl; + offset_limit_cnt= sl->offset_limit; + select_limit_cnt= sl->select_limit+sl->offset_limit; + if (select_limit_cnt < sl->select_limit) + select_limit_cnt= HA_POS_ERROR; // no limit + if (select_limit_cnt == HA_POS_ERROR || sl->braces) + sl->options&= ~OPTION_FOUND_ROWS; + + res= join->prepare(&sl->ref_pointer_array, + (TABLE_LIST*) sl->table_list.first, sl->with_wild, + sl->where, + ((sl->braces) ? sl->order_list.elements : 0) + + sl->group_list.elements, + (sl->braces) ? + (ORDER *)sl->order_list.first : (ORDER *) 0, + (ORDER*) sl->group_list.first, + sl->having, + (ORDER*) NULL, + sl, this); + if (res || thd_arg->is_fatal_error) + goto err; + if (sl == first_select) { - thd->offset_limit= sl->offset_limit; - thd->select_limit=sl->select_limit+sl->offset_limit; + types.empty(); + List_iterator_fast<Item> it(sl->item_list); + Item *item_tmp; + while ((item_tmp= it++)) + { + types.push_back(new Item_type_holder(thd_arg, item_tmp)); + } + + if (thd_arg->is_fatal_error) + goto err; // out of memory } else { - thd->offset_limit= 0; - /* - We can't use LIMIT at this stage if we are using ORDER BY for the - whole query - */ - thd->select_limit= HA_POS_ERROR; - if (! sl->order_list.first) - thd->select_limit= sl->select_limit+sl->offset_limit; - } - if (thd->select_limit < sl->select_limit) - thd->select_limit= HA_POS_ERROR; // no limit - - /* - When using braces, SQL_CALC_FOUND_ROWS affects the whole query. - We don't calculate found_rows() per union part - */ - if (thd->select_limit == HA_POS_ERROR || sl->braces) - sl->options&= ~OPTION_FOUND_ROWS; - else - { - /* - We are doing an union without braces. In this case - SQL_CALC_FOUND_ROWS should be done on all sub parts - */ - sl->options|= found_rows_for_union; + if (types.elements != sl->item_list.elements) + { + my_message(ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT, + ER(ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT),MYF(0)); + goto err; + } + List_iterator_fast<Item> it(sl->item_list); + List_iterator_fast<Item> tp(types); + Item *type, *item_tmp; + while ((type= tp++, item_tmp= it++)) + { + if (((Item_type_holder*)type)->join_types(thd_arg, item_tmp)) + DBUG_RETURN(-1); + } } + } - records_at_start= table->file->records; - res=mysql_select(thd, (describe && sl->linkage==NOT_A_SELECT) ? - first_table : (TABLE_LIST*) sl->table_list.first, - sl->item_list, - sl->where, - (sl->braces) ? (ORDER *)sl->order_list.first : - (ORDER *) 0, - (ORDER*) sl->group_list.first, - sl->having, - (ORDER*) NULL, - sl->options | thd->options | SELECT_NO_UNLOCK | - describe, - union_result); - if (res) - goto exit; - /* Needed for the following test and for records_at_start in next loop */ - table->file->info(HA_STATUS_VARIABLE); - if (found_rows_for_union & sl->options) + if (first_select->next_select()) + { + union_result->tmp_table_param.field_count= types.elements; + if (!(table= create_tmp_table(thd_arg, + &union_result->tmp_table_param, types, + (ORDER*) 0, !union_option, 1, + (first_select_in_union()->options | + thd_arg->options | + TMP_TABLE_ALL_COLUMNS), + HA_POS_ERROR, (char*) ""))) + goto err; + table->file->extra(HA_EXTRA_WRITE_CACHE); + table->file->extra(HA_EXTRA_IGNORE_DUP_KEY); + bzero((char*) &result_table_list, sizeof(result_table_list)); + result_table_list.db= (char*) ""; + result_table_list.real_name= result_table_list.alias= (char*) "union"; + result_table_list.table= table; + union_result->set_table(table); + + item_list.empty(); + thd_arg->lex->current_select= lex_select_save; { - /* - This is a union without braces. Remember the number of rows that could - also have been part of the result set. - We get this from the difference of between total number of possible - rows and actual rows added to the temporary table. - */ - add_rows+= (ulonglong) (thd->limit_found_rows - (table->file->records - - records_at_start)); + Field **field; + for (field= table->field; *field; field++) + { + if (item_list.push_back(new Item_field(*field))) + DBUG_RETURN(-1); + } } } - if (union_result->flush()) + else + first_select->braces= 0; // remove our changes + + thd_arg->lex->current_select= lex_select_save; + + DBUG_RETURN(res || thd_arg->is_fatal_error ? 1 : 0); + +err: + thd_arg->lex->current_select= lex_select_save; + DBUG_RETURN(-1); +} + + +int st_select_lex_unit::exec() +{ + SELECT_LEX *lex_select_save= thd->lex->current_select; + SELECT_LEX *select_cursor=first_select_in_union(); + ulonglong add_rows=0; + DBUG_ENTER("st_select_lex_unit::exec"); + + if (executed && !uncacheable) + DBUG_RETURN(0); + executed= 1; + + if (uncacheable || !item || !item->assigned()) { - res= 1; // Error is already sent - goto exit; + if (optimized && item && item->assigned()) + { + item->assigned(0); // We will reinit & rexecute unit + item->reset(); + table->file->delete_all_rows(); + } + for (SELECT_LEX *sl= select_cursor; sl; sl= sl->next_select()) + { + ha_rows records_at_start= 0; + thd->lex->current_select= sl; + + if (optimized) + res= sl->join->reinit(); + else + { + if (sl != global_parameters) + { + offset_limit_cnt= sl->offset_limit; + select_limit_cnt= sl->select_limit+sl->offset_limit; + } + else + { + offset_limit_cnt= 0; + /* + We can't use LIMIT at this stage if we are using ORDER BY for the + whole query + */ + if (sl->order_list.first) + select_limit_cnt= HA_POS_ERROR; + else + select_limit_cnt= sl->select_limit+sl->offset_limit; + } + if (select_limit_cnt < sl->select_limit) + select_limit_cnt= HA_POS_ERROR; // no limit + + /* + When using braces, SQL_CALC_FOUND_ROWS affects the whole query. + We don't calculate found_rows() per union part + */ + if (select_limit_cnt == HA_POS_ERROR || sl->braces) + sl->options&= ~OPTION_FOUND_ROWS; + else + { + /* + We are doing an union without braces. In this case + SQL_CALC_FOUND_ROWS should be done on all sub parts + */ + sl->options|= found_rows_for_union; + } + sl->join->select_options=sl->options; + /* + As far as union share table space we should reassign table map, + which can be spoiled by 'prepare' of JOIN of other UNION parts + if it use same tables + */ + uint tablenr=0; + for (TABLE_LIST *table_list= (TABLE_LIST*) sl->table_list.first; + table_list; + table_list= table_list->next, tablenr++) + { + if (table_list->shared) + { + /* + review notes: Check it carefully. I still can't understand + why I should not touch table->used_keys. For my point of + view we should do here same procedura as it was done by + setup_table + */ + setup_table_map(table_list->table, table_list, tablenr); + } + } + res= sl->join->optimize(); + } + if (!res) + { + records_at_start= table->file->records; + sl->join->exec(); + res= sl->join->error; + offset_limit_cnt= sl->offset_limit; + if (!res && union_result->flush()) + { + thd->lex->current_select= lex_select_save; + DBUG_RETURN(1); + } + } + if (res) + { + thd->lex->current_select= lex_select_save; + DBUG_RETURN(res); + } + /* Needed for the following test and for records_at_start in next loop */ + table->file->info(HA_STATUS_VARIABLE); + if (found_rows_for_union & sl->options) + { + /* + This is a union without braces. Remember the number of rows that + could also have been part of the result set. + We get this from the difference of between total number of possible + rows and actual rows added to the temporary table. + */ + add_rows+= (ulonglong) (thd->limit_found_rows - (ulonglong) + ((table->file->records - records_at_start))); + } + } } - delete union_result; + optimized= 1; /* Send result to 'result' */ - lex->select = &lex->select_lex; - res =-1; + + + res= -1; { - /* Create a list of fields in the temporary table */ - List_iterator<Item> it(item_list); - Field **field; - thd->lex.select_lex.ftfunc_list.empty(); + List<Item_func_match> empty_list; + empty_list.empty(); - for (field=table->field ; *field ; field++) - { - (void) it++; - (void) it.replace(new Item_field(*field)); - } - if (!thd->fatal_error) // Check if EOM + if (!thd->is_fatal_error) // Check if EOM { - if (lex_sl) + ulong options_tmp= thd->options; + thd->lex->current_select= fake_select_lex; + offset_limit_cnt= global_parameters->offset_limit; + select_limit_cnt= global_parameters->select_limit + + global_parameters->offset_limit; + + if (select_limit_cnt < global_parameters->select_limit) + select_limit_cnt= HA_POS_ERROR; // no limit + if (select_limit_cnt == HA_POS_ERROR) + options_tmp&= ~OPTION_FOUND_ROWS; + else if (found_rows_for_union && !thd->lex->describe) + options_tmp|= OPTION_FOUND_ROWS; + fake_select_lex->ftfunc_list= &empty_list; + fake_select_lex->table_list.link_in_list((byte *)&result_table_list, + (byte **) + &result_table_list.next); + JOIN *join= fake_select_lex->join; + if (!join) { - thd->offset_limit=lex_sl->offset_limit; - thd->select_limit=lex_sl->select_limit+lex_sl->offset_limit; - if (thd->select_limit < lex_sl->select_limit) - thd->select_limit= HA_POS_ERROR; // no limit - if (thd->select_limit == HA_POS_ERROR) - thd->options&= ~OPTION_FOUND_ROWS; + /* + allocate JOIN for fake select only once (privent + mysql_select automatic allocation) + */ + fake_select_lex->join= new JOIN(thd, item_list, thd->options, result); + /* + Fake st_select_lex should have item list for correctref_array + allocation. + */ + fake_select_lex->item_list= item_list; } - else + else { - thd->offset_limit= 0; - thd->select_limit= thd->variables.select_limit; - if (found_rows_for_union && !describe) - thd->options|= OPTION_FOUND_ROWS; + JOIN_TAB *tab,*end; + for (tab=join->join_tab,end=tab+join->tables ; tab != end ; tab++) + { + delete tab->select; + delete tab->quick; + } + join->init(thd, item_list, thd->options, result); } - if (describe) - thd->select_limit= HA_POS_ERROR; // no limit - - res=mysql_select(thd,&result_table_list, - item_list, NULL, (describe) ? 0 : order, - (ORDER*) NULL, NULL, (ORDER*) NULL, - thd->options, result); + res= mysql_select(thd, &fake_select_lex->ref_pointer_array, + &result_table_list, + 0, item_list, NULL, + global_parameters->order_list.elements, + (ORDER*)global_parameters->order_list.first, + (ORDER*) NULL, NULL, (ORDER*) NULL, + options_tmp | SELECT_NO_UNLOCK, + result, this, fake_select_lex); if (!res) thd->limit_found_rows = (ulonglong)table->file->records + add_rows; + /* + Mark for slow query log if any of the union parts didn't use + indexes efficiently + */ } } - -exit: - free_tmp_table(thd,table); + thd->lex->current_select= lex_select_save; DBUG_RETURN(res); } -/*************************************************************************** -** store records in temporary table for UNION -***************************************************************************/ - -select_union::select_union(TABLE *table_par) - :table(table_par), not_describe(0) -{ - bzero((char*) &info,sizeof(info)); - /* - We can always use DUP_IGNORE because the temporary table will only - contain a unique key if we are using not using UNION ALL - */ - info.handle_duplicates=DUP_IGNORE; -} - -select_union::~select_union() +int st_select_lex_unit::cleanup() { -} - + int error= 0; + DBUG_ENTER("st_select_lex_unit::cleanup"); -int select_union::prepare(List<Item> &list) -{ - if (not_describe && list.elements != table->fields) + if (cleaned) { - my_message(ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT, - ER(ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT),MYF(0)); - return -1; + DBUG_RETURN(0); } - return 0; -} + cleaned= 0; -bool select_union::send_data(List<Item> &values) -{ - if (thd->offset_limit) - { // using limit offset,count - thd->offset_limit--; - return 0; + if (union_result) + { + delete union_result; + union_result=0; // Safety + if (table) + free_tmp_table(thd, table); + table= 0; // Safety } - - fill_record(table->field, values, 1); - if ((write_record(table,&info))) + JOIN *join; + for (SELECT_LEX *sl= first_select_in_union(); sl; sl= sl->next_select()) { - if (create_myisam_from_heap(thd, table, tmp_table_param, info.last_errno, - 1)) - return 1; + if ((join= sl->join)) + { + error|= sl->join->cleanup(); + delete join; + } } - return 0; -} - -bool select_union::send_eof() -{ - return 0; -} - -bool select_union::flush() -{ - int error; - if ((error=table->file->extra(HA_EXTRA_NO_CACHE))) + if (fake_select_lex && (join= fake_select_lex->join)) { - table->file->print_error(error,MYF(0)); - ::send_error(&thd->net); - return 1; + join->tables_list= 0; + join->tables= 0; + error|= join->cleanup(); + delete join; } - return 0; + DBUG_RETURN(error); } |