From 9161669492563cc99d2bae22c17fa0ca7874352f Mon Sep 17 00:00:00 2001 From: "timour@mysql.com" <> Date: Thu, 28 Oct 2004 17:31:26 +0300 Subject: [PATCH 1/8] Implementation of WL#1972 "Evaluate HAVING before SELECT select-list" --- mysql-test/r/having.result | 116 +++++++++++++++ mysql-test/t/having.test | 117 +++++++++++++++ sql/item.cc | 296 ++++++++++++++++++++++++++++--------- 3 files changed, 462 insertions(+), 67 deletions(-) diff --git a/mysql-test/r/having.result b/mysql-test/r/having.result index 2e94974e953..f312cc6659f 100644 --- a/mysql-test/r/having.result +++ b/mysql-test/r/having.result @@ -128,3 +128,119 @@ id description c 1 test 0 2 test2 0 drop table t1,t2,t3; +drop table if exists wl1972; +create table wl1972 ( +col1 int, +col2 varchar(5) character set latin1 collate latin1_bin) +engine=innodb; +insert into wl1972 values(10,'hello'); +insert into wl1972 values(20,'hello'); +insert into wl1972 values(30,'hello'); +insert into wl1972 values(10,'bye'); +insert into wl1972 values(10,'sam'); +insert into wl1972 values(10,'bob'); +select count(*) from wl1972 group by col1 having col1 = 10; +count(*) +4 +select count(*) as count_col1 from wl1972 group by col1 having col1 = 10; +count_col1 +4 +select count(*) as count_col1 from wl1972 as t1 group by col1 having col1 = 10; +count_col1 +4 +select count(*) from wl1972 group by col2 having col2 = 'hello'; +count(*) +3 +select count(*) from wl1972 group by col2 having col1 = 10; +ERROR 42S22: Unknown column 'col1' in 'having clause' +select col1 as count_col1 from wl1972 as t1 group by col1 having col1 = 10; +count_col1 +10 +select col1 as count_col1 from wl1972 as t1 group by col1 having count_col1 = 10; +count_col1 +10 +select col1 as count_col1 from wl1972 as t1 group by count_col1 having col1 = 10; +count_col1 +10 +select col1 as count_col1 from wl1972 as t1 group by count_col1 having count_col1 = 10; +count_col1 +10 +select col1 as count_col1,col2 from wl1972 as t1 group by col1,col2 having col1 = 10; +count_col1 col2 +10 bob +10 bye +10 hello +10 sam +select col1 as count_col1,col2 from wl1972 as t1 group by col1,col2 having count_col1 = 10; +count_col1 col2 +10 bob +10 bye +10 hello +10 sam +select col1 as count_col1,col2 from wl1972 as t1 group by col1,col2 having col2 = 'hello'; +count_col1 col2 +10 hello +20 hello +30 hello +select col1 as count_col1,col2 as group_col2 from wl1972 as t1 +group by col1,col2 having group_col2 = 'hello'; +count_col1 group_col2 +10 hello +20 hello +30 hello +select sum(col1) as co12 from wl1972 group by col2 having col2 10; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '10' at line 1 +select sum(col1) as co2, count(col2) as cc from wl1972 group by col1 having col1 =10; +co2 cc +40 4 +select t1.col1 from wl1972 as t1 where t1.col2 in +(select t2.col2 from wl1972 as t2 +group by t2.col1,t2.col2 having t2.col1<=10); +col1 +10 +20 +30 +10 +10 +10 +select t1.col1 from wl1972 as t1 where t1.col2 in +(select t2.col2 from wl1972 as t2 +group by t2.col1,t2.col2 having t2.col1<= +(select min(t3.col1) from wl1972 as t3)); +col1 +10 +20 +30 +10 +10 +10 +drop table wl1972; +create table wl1972 (s1 int); +insert into wl1972 values (1),(2),(3); +select count(*) from wl1972 group by s1 having s1 is null; +count(*) +select s1*0 as s1 from wl1972 group by s1 having s1 <> 0; +s1 +select s1*0 from wl1972 group by s1 having s1 = 0; +s1*0 +select s1 from wl1972 group by 1 having 1 = 0; +s1 +select count(s1) from wl1972 group by s1 having count(1+1)=2; +count(s1) +select count(s1) from wl1972 group by s1 having s1*0=0; +count(s1) +1 +1 +1 +select * from wl1972 a, wl1972 b group by a.s1 having s1 is null; +ERROR 23000: Column 's1' in having clause is ambiguous +drop table wl1972; +create table wl1972 (s1 char character set latin1 collate latin1_german1_ci); +insert into wl1972 values ('ü'),('y'); +Warnings: +Warning 1265 Data truncated for column 's1' at row 1 +select s1,count(s1) from wl1972 +group by s1 collate latin1_swedish_ci having s1 = 'y'; +s1 count(s1) +y 1 +drop table wl1972; diff --git a/mysql-test/t/having.test b/mysql-test/t/having.test index 12a44fd75dc..8d21f070956 100644 --- a/mysql-test/t/having.test +++ b/mysql-test/t/having.test @@ -122,3 +122,120 @@ from t1 a left join t3 b on a.id=b.order_id group by a.id, a.description having (a.description is not null) and (c=0); drop table t1,t2,t3; + + +# +# Tests for WL#1972 CORRECT EVALUATION OF COLUMN REFERENCES IN THE HAVING CLAUSE +# Per the SAP VERI tests and WL#1972, MySQL must ensure that HAVING can +# correctly evaluate column references from the GROUP BY clause, even if the +# same references are not also found in the select list. +# + +# set global sql_mode='ansi'; +# set session sql_mode='ansi'; + +--disable_warnings +drop table if exists wl1972; +--enable_warnings + +create table wl1972 ( + col1 int, + col2 varchar(5) character set latin1 collate latin1_bin) +engine=innodb; + +insert into wl1972 values(10,'hello'); +insert into wl1972 values(20,'hello'); +insert into wl1972 values(30,'hello'); +insert into wl1972 values(10,'bye'); +insert into wl1972 values(10,'sam'); +insert into wl1972 values(10,'bob'); + +select count(*) from wl1972 group by col1 having col1 = 10; + +select count(*) as count_col1 from wl1972 group by col1 having col1 = 10; + +select count(*) as count_col1 from wl1972 as t1 group by col1 having col1 = 10; + +select count(*) from wl1972 group by col2 having col2 = 'hello'; + +--error 1054 +select count(*) from wl1972 group by col2 having col1 = 10; + +select col1 as count_col1 from wl1972 as t1 group by col1 having col1 = 10; + +select col1 as count_col1 from wl1972 as t1 group by col1 having count_col1 = 10; + +select col1 as count_col1 from wl1972 as t1 group by count_col1 having col1 = 10; +# ANSI: should return SQLSTATE 42000 Syntax error or access violation +# MySQL: returns 10 - because of GROUP BY name resolution + +select col1 as count_col1 from wl1972 as t1 group by count_col1 having count_col1 = 10; +# ANSI: should return SQLSTATE 42000 Syntax error or access violation +# MySQL: returns 10 - because of GROUP BY name resolution + +select col1 as count_col1,col2 from wl1972 as t1 group by col1,col2 having col1 = 10; + +select col1 as count_col1,col2 from wl1972 as t1 group by col1,col2 having count_col1 = 10; + +select col1 as count_col1,col2 from wl1972 as t1 group by col1,col2 having col2 = 'hello'; + +select col1 as count_col1,col2 as group_col2 from wl1972 as t1 +group by col1,col2 having group_col2 = 'hello'; + +--error 1064 +select sum(col1) as co12 from wl1972 group by col2 having col2 10; + +select sum(col1) as co2, count(col2) as cc from wl1972 group by col1 having col1 =10; + +select t1.col1 from wl1972 as t1 where t1.col2 in + (select t2.col2 from wl1972 as t2 + group by t2.col1,t2.col2 having t2.col1<=10); + +select t1.col1 from wl1972 as t1 where t1.col2 in + (select t2.col2 from wl1972 as t2 + group by t2.col1,t2.col2 having t2.col1<= + (select min(t3.col1) from wl1972 as t3)); + +drop table wl1972; + +# More queries to test ANSI compatibility +create table wl1972 (s1 int); +insert into wl1972 values (1),(2),(3); + +select count(*) from wl1972 group by s1 having s1 is null; + +select s1*0 as s1 from wl1972 group by s1 having s1 <> 0; +# ANSI requires: 3 rows +# MySQL returns: 0 rows - because of GROUP BY name resolution + +select s1*0 from wl1972 group by s1 having s1 = 0; + +select s1 from wl1972 group by 1 having 1 = 0; + +select count(s1) from wl1972 group by s1 having count(1+1)=2; +# ANSI requires: 3 rows +# MySQL returns: 0 rows - because of GROUP BY name resolution + +select count(s1) from wl1972 group by s1 having s1*0=0; + +-- error 1052 +select * from wl1972 a, wl1972 b group by a.s1 having s1 is null; +# ANSI requires: 0 rows +# MySQL returns: +# "ERROR 1052 (23000): Column 's1' in having clause is ambiguous" +# I think the column is ambiguous in ANSI too. +# It is the same as: +# select a.s1, b.s1 from wl1972 a, wl1972 b group by a.s1 having s1 is null; +# currently we first check SELECT, thus s1 is ambiguous. + +drop table wl1972; + +create table wl1972 (s1 char character set latin1 collate latin1_german1_ci); +insert into wl1972 values ('ü'),('y'); + +select s1,count(s1) from wl1972 +group by s1 collate latin1_swedish_ci having s1 = 'y'; +# ANSI requires: 1 row, with count(s1) = 2 +# MySQL returns: 1 row, with count(s1) = 1 + +drop table wl1972; diff --git a/sql/item.cc b/sql/item.cc index 134b04d5540..b500d84ec4a 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -26,6 +26,7 @@ #include "sql_acl.h" #include "sp_head.h" #include "sql_trigger.h" +#include "sql_select.h" static void mark_as_dependent(THD *thd, SELECT_LEX *last, SELECT_LEX *current, @@ -2261,55 +2262,207 @@ bool Item_field::send(Protocol *protocol, String *buffer) return protocol->store(result_field); } + /* - This is used for HAVING clause - Find field in select list having the same name - */ + Search a GROUP BY clause for a field with a certain name. + + SYNOPSIS + find_field_in_group_list() + find_item the item being searched for + group_list GROUP BY clause + + DESCRIPTION + Search the GROUP BY list for a column named as find_item. When searching + preference is given to columns that are qualified with the same table (and + database) name as the one being searched for. + + RETURN + - the found item on success + - NULL if find_item is not in group_list +*/ + +static Item** find_field_in_group_list(Item *find_item, ORDER *group_list) +{ + const char *db_name; + const char *table_name; + const char *field_name; + ORDER *found_group= NULL; + int found_match_degree= 0; + Item_field *cur_field; + int cur_match_degree= 0; + + if (find_item->type() == Item::FIELD_ITEM || + find_item->type() == Item::REF_ITEM) + { + db_name= ((Item_ident*) find_item)->db_name; + table_name= ((Item_ident*) find_item)->table_name; + field_name= ((Item_ident*) find_item)->field_name; + } + else + return NULL; + + DBUG_ASSERT(field_name); + + for (ORDER *cur_group= group_list ; cur_group ; cur_group= cur_group->next) + { + if ((*(cur_group->item))->type() == Item::FIELD_ITEM) + { + cur_field= (Item_field*) *cur_group->item; + cur_match_degree= 0; + + DBUG_ASSERT(cur_field->field_name); + + if (!my_strcasecmp(system_charset_info, + cur_field->field_name, field_name)) + ++cur_match_degree; + else + continue; + + if (cur_field->table_name && table_name && + !strcmp(cur_field->table_name, table_name)) + { /* If field_name is qualified by a table name. */ + ++cur_match_degree; + if (cur_field->db_name && db_name && + !strcmp(cur_field->db_name, db_name)) + /* If field_name is also qualified by a database name. */ + ++cur_match_degree; + } + + if (cur_match_degree > found_match_degree) + { + found_match_degree= cur_match_degree; + found_group= cur_group; + } + } + } + if (found_group) + return found_group->item; + else + return NULL; +} + + +/* + Resolve the name of a column reference. + + SYNOPSIS + Item_ref::fix_fields() + thd [in] Current thread + tables [in] The tables in the FROM clause + reference [in/out] View column if this item was resolved to a view column + + DESCRIPTION + The method resolves the column reference represented by this as an Item + present in either of: GROUP BY clause, SELECT clause, outer queries. It is + used for columns in the HAVING clause which are not under aggregate + functions. + + NOTES + The name resolution algorithm used is: + resolve_extended([T_j].col_ref_i) + { + Search for a column named col_ref_i [in table T_j] + in the GROUP BY clause of Q. + + Search for a column or derived column named col_ref_i [in table T_j] + in the SELECT list of Q. + + if found different columns with the same name in GROUP BY and SELECT + issue an error. + + // Lookup in outer queries. + if such a column is NOT found AND there are outer queries + { + for each outer query Q_k beginning from the inner-most one + { + search for a column or derived column named col_ref_i + [in table T_j] in the SELECT list of Q_k; + if such a column is not found + { + search for a column or derived column named col_ref_i + [in table T_j] in the FROM clause of Q_k; + } + } + } + } + This procedure treats GROUP BY and SELECT as one namespace for column + references in HAVING. + + RETURN + TRUE if error + FALSE on success +*/ bool Item_ref::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) { DBUG_ASSERT(fixed == 0); uint counter; + SELECT_LEX *current_sel= thd->lex->current_select; + List *search_namespace= current_sel->get_item_list(); + bool is_having_field= current_sel->having_fix_field; + Item **group_by_ref= NULL; + if (!ref) { TABLE_LIST *table_list; bool upward_lookup= 0; - SELECT_LEX_UNIT *prev_unit= thd->lex->current_select->master_unit(); - SELECT_LEX *sl= prev_unit->outer_select(); + SELECT_LEX_UNIT *prev_unit= current_sel->master_unit(); + SELECT_LEX *outer_sel= prev_unit->outer_select(); + /* - Finding only in current select will be performed for selects that have - not outer one and for derived tables (which not support using outer - fields for now) + Search for a column or derived column named as 'this' in the SELECT + clause of current_select. */ - if ((ref= find_item_in_list(this, - *(thd->lex->current_select->get_item_list()), - &counter, - ((sl && - thd->lex->current_select->master_unit()-> - first_select()->linkage != - DERIVED_TABLE_TYPE) ? - REPORT_EXCEPT_NOT_FOUND : - REPORT_ALL_ERRORS))) == - (Item **)not_found_item) + if (!(ref= find_item_in_list(this, *search_namespace, &counter, + REPORT_EXCEPT_NOT_FOUND))) + return TRUE; /* Some error occurred. */ + + /* If this is a non-aggregated field inside HAVING, search in GROUP BY. */ + if (is_having_field && !this->with_sum_func) { + group_by_ref= find_field_in_group_list(this, (ORDER*) + current_sel->group_list.first); + /* Check if the fields found in SELECT and GROUP BY are the same field. */ + if (group_by_ref && ref != (Item **) not_found_item && + !((*group_by_ref)->eq(*ref, 0))) + { + my_printf_error(ER_NON_UNIQ_ERROR, ER(ER_NON_UNIQ_ERROR), + MYF(0), this->full_name(), current_thd->where); + return TRUE; + } + } + + + /* + If we didn't find such a column in the current query, and if there is an + outer select, and this is not a derived table (which do not support the + use of outer fields for now), search the outer select(s) for a column + named as 'this'. + */ + if (!group_by_ref && (ref == (Item **) not_found_item) && outer_sel && + (current_sel->master_unit()->first_select()->linkage != + DERIVED_TABLE_TYPE)) + { + /* + We can't join the columns of the outer & current selects, because of + scope of view rules. For example if both tables (outer & current) have + field 'field' it is not a mistake to refer to this field without + qualifying it with a table name, but if we join tables in one list it + will cause error ER_NON_UNIQ_ERROR in find_item_in_list. + */ upward_lookup= 1; Field *tmp= (Field*) not_found_field; - /* - We can't find table field in table list of current select, - consequently we have to find it in outer subselect(s). - We can't join lists of outer & current select, because of scope - of view rules. For example if both tables (outer & current) have - field 'field' it is not mistake to refer to this field without - mention of table name, but if we join tables in one list it will - cause error ER_NON_UNIQ_ERROR in find_item_in_list. - */ SELECT_LEX *last=0; - for ( ; sl ; sl= (prev_unit= sl->master_unit())->outer_select()) + + for ( ; outer_sel ; + outer_sel= (prev_unit= outer_sel->master_unit())->outer_select()) { - last= sl; + last= outer_sel; Item_subselect *prev_subselect_item= prev_unit->item; - if (sl->resolve_mode == SELECT_LEX::SELECT_MODE && - (ref= find_item_in_list(this, sl->item_list, + + /* Search in the SELECT list of the current outer sub-select. */ + if (outer_sel->resolve_mode == SELECT_LEX::SELECT_MODE && + (ref= find_item_in_list(this, outer_sel->item_list, &counter, REPORT_EXCEPT_NOT_FOUND)) != (Item **)not_found_item) @@ -2321,21 +2474,24 @@ bool Item_ref::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) } break; } - table_list= sl->get_table_list(); - if (sl->resolve_mode == SELECT_LEX::INSERT_MODE && table_list) + + /* Search in the tables in the FROM clause of the outer select. */ + table_list= outer_sel->get_table_list(); + if (outer_sel->resolve_mode == SELECT_LEX::INSERT_MODE && table_list) { - // it is primary INSERT st_select_lex => skip first table resolving + /* It is primary INSERT st_select_lex => skip the first table. */ table_list= table_list->next_local; } enum_parsing_place place= prev_subselect_item->parsing_place; /* - Check table fields only if subquery used somewhere out of HAVING - or SELECT list or outer SELECT do not use groupping (i.e. tables - are accessable) + Check table fields only if the subquery is used somewhere out of + HAVING or SELECT list, or outer SELECT does not use grouping + (i.e. tables are accessible) */ if (((place != IN_HAVING && place != SELECT_LIST) || - (sl->with_sum_func == 0 && sl->group_list.elements == 0)) && + (outer_sel->with_sum_func == 0 && + outer_sel->group_list.elements == 0)) && (tmp= find_field_in_tables(thd, this, table_list, reference, 0, 1)) != not_found_field) @@ -2362,53 +2518,50 @@ bool Item_ref::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) prev_subselect_item->used_tables_cache|= OUTER_REF_TABLE_BIT; prev_subselect_item->const_item_cache= 0; - if (sl->master_unit()->first_select()->linkage == + if (outer_sel->master_unit()->first_select()->linkage == DERIVED_TABLE_TYPE) - break; // do not look over derived table + break; /* Do not consider derived tables. */ } if (!ref) - return 1; + return TRUE; else if (!tmp) - return -1; + return TRUE; else if (ref == (Item **)not_found_item && tmp == not_found_field) { if (upward_lookup) { - // We can't say exactly what absend (table or field) + /* We can't say exactly what was absent (a table or a field). */ my_printf_error(ER_BAD_FIELD_ERROR, ER(ER_BAD_FIELD_ERROR), MYF(0), full_name(), thd->where); } else { // Call to report error - find_item_in_list(this, - *(thd->lex->current_select->get_item_list()), - &counter, + find_item_in_list(this, *search_namespace, &counter, REPORT_ALL_ERRORS); } ref= 0; - return 1; + return TRUE; } else if (tmp != not_found_field) { - ref= 0; // To prevent "delete *ref;" on ~Item_erf() of this item + ref= 0; // To prevent "delete *ref;" on ~Item_ref() of this item if (tmp != view_ref_found) { Item_field* fld; if (!((*reference)= fld= new Item_field(tmp))) - return 1; - mark_as_dependent(thd, last, thd->lex->current_select, fld); + return TRUE; + mark_as_dependent(thd, last, current_sel, fld); register_item_tree_changing(reference); - return 0; + return FALSE; } /* We can leave expression substituted from view for next PS/SP - rexecution (i.e. do not register this substitution for reverting - on cleupup() (register_item_tree_changing())), because this - subtree will be fix_field'ed during - setup_tables()->setup_ancestor() (i.e. before all other - expressions of query, and references on tables which do not + re-execution (i.e. do not register this substitution for reverting on + cleanup() (register_item_tree_changing())), because this subtree will + be fix_field'ed during setup_tables()->setup_ancestor() (i.e. before + all other expressions of query, and references on tables which do not present in query will not make problems. Also we suppose that view can't be changed during PS/SP life. @@ -2420,24 +2573,34 @@ bool Item_ref::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) { my_error(ER_ILLEGAL_REFERENCE, MYF(0), name, "forward reference in item list"); - return -1; + return TRUE; } - mark_as_dependent(thd, last, thd->lex->current_select, + mark_as_dependent(thd, last, current_sel, this); ref= last->ref_pointer_array + counter; } } - else if (!ref) - return 1; + else if (!group_by_ref && ref == (Item **) not_found_item) + { + my_printf_error(ER_BAD_FIELD_ERROR, ER(ER_BAD_FIELD_ERROR), MYF(0), + this->full_name(), current_thd->where); + return TRUE; + } else { + if (ref != (Item **) not_found_item) + ref= current_sel->ref_pointer_array + counter; + else if (group_by_ref) + ref= group_by_ref; + else + DBUG_ASSERT(FALSE); + if (!(*ref)->fixed) { my_error(ER_ILLEGAL_REFERENCE, MYF(0), name, "forward reference in item list"); - return -1; + return TRUE; } - ref= thd->lex->current_select->ref_pointer_array + counter; } } @@ -2450,15 +2613,14 @@ bool Item_ref::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) */ if (((*ref)->with_sum_func && name && (depended_from || - !(thd->lex->current_select->linkage != GLOBAL_OPTIONS_TYPE && - thd->lex->current_select->having_fix_field))) || + !(current_sel->linkage != GLOBAL_OPTIONS_TYPE && is_having_field))) || !(*ref)->fixed) { my_error(ER_ILLEGAL_REFERENCE, MYF(0), name, ((*ref)->with_sum_func? "reference on group function": "forward reference in item list")); - return 1; + return TRUE; } max_length= (*ref)->max_length; maybe_null= (*ref)->maybe_null; @@ -2468,8 +2630,8 @@ bool Item_ref::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) fixed= 1; if (ref && (*ref)->check_cols(1)) - return 1; - return 0; + return TRUE; + return FALSE; } From 09f1c7096c354843ad75db94c0a7116c0a967986 Mon Sep 17 00:00:00 2001 From: "timour@mysql.com" <> Date: Tue, 2 Nov 2004 18:23:15 +0200 Subject: [PATCH 2/8] WL#1972 "Evaluate HAVING before SELECT select-list" - Changed name resolution for GROUP BY so that derived columns do not shadow table columns from the FROM clause. As a result GROUP BY now is handled as a true ANSI extentsion. - Issue a warning when HAVING is resolved into ambiguous columns, and prefer the columns from the GROUP BY clause over SELECT columns. --- mysql-test/r/having.result | 3 ++ sql/item.cc | 69 ++++++++++++++++----------- sql/mysql_priv.h | 9 ++-- sql/sp.cc | 2 +- sql/sql_base.cc | 64 +++++++++++++++---------- sql/sql_help.cc | 2 +- sql/sql_select.cc | 97 ++++++++++++++++++++++++++++++-------- 7 files changed, 171 insertions(+), 75 deletions(-) diff --git a/mysql-test/r/having.result b/mysql-test/r/having.result index f312cc6659f..04f73792dd6 100644 --- a/mysql-test/r/having.result +++ b/mysql-test/r/having.result @@ -221,6 +221,9 @@ select count(*) from wl1972 group by s1 having s1 is null; count(*) select s1*0 as s1 from wl1972 group by s1 having s1 <> 0; s1 +0 +0 +0 select s1*0 from wl1972 group by s1 having s1 = 0; s1*0 select s1 from wl1972 group by 1 having 1 = 0; diff --git a/sql/item.cc b/sql/item.cc index b500d84ec4a..738814bc353 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -1397,7 +1397,8 @@ bool Item_field::fix_fields(THD *thd, TABLE_LIST *tables, Item **ref) { bool upward_lookup= 0; Field *tmp= (Field *)not_found_field; - if ((tmp= find_field_in_tables(thd, this, tables, ref, 0, + if ((tmp= find_field_in_tables(thd, this, tables, ref, + IGNORE_EXCEPT_NON_UNIQUE, !any_privileges)) == not_found_field) { @@ -1449,7 +1450,8 @@ bool Item_field::fix_fields(THD *thd, TABLE_LIST *tables, Item **ref) (sl->with_sum_func == 0 && sl->group_list.elements == 0)) && (tmp= find_field_in_tables(thd, this, table_list, ref, - 0, 1)) != not_found_field) + IGNORE_EXCEPT_NON_UNIQUE, 1)) != + not_found_field) { if (tmp) { @@ -1505,7 +1507,7 @@ bool Item_field::fix_fields(THD *thd, TABLE_LIST *tables, Item **ref) else { // Call to report error - find_field_in_tables(thd, this, tables, ref, 1, 1); + find_field_in_tables(thd, this, tables, ref, REPORT_ALL_ERRORS, 1); } return -1; } @@ -2347,31 +2349,39 @@ static Item** find_field_in_group_list(Item *find_item, ORDER *group_list) SYNOPSIS Item_ref::fix_fields() - thd [in] Current thread - tables [in] The tables in the FROM clause - reference [in/out] View column if this item was resolved to a view column + thd [in] current thread + tables [in] the tables in the FROM clause + reference [in/out] view column if this item was resolved to a view column DESCRIPTION - The method resolves the column reference represented by this as an Item - present in either of: GROUP BY clause, SELECT clause, outer queries. It is + The method resolves the column reference represented by 'this' as a column + present in one of: GROUP BY clause, SELECT clause, outer queries. It is used for columns in the HAVING clause which are not under aggregate functions. NOTES + The general idea behind the name resolution algorithm is that it searches + both the SELECT and GROUP BY clauses, and in case of a name conflict + prefers GROUP BY column names over SELECT names. We extend ANSI SQL in that + when no GROUP BY column is found, then a HAVING name is resolved as a + possibly derived SELECT column. + The name resolution algorithm used is: + resolve_extended([T_j].col_ref_i) { - Search for a column named col_ref_i [in table T_j] - in the GROUP BY clause of Q. - Search for a column or derived column named col_ref_i [in table T_j] in the SELECT list of Q. - if found different columns with the same name in GROUP BY and SELECT - issue an error. + Search for a column named col_ref_i [in table T_j] + in the GROUP BY clause of Q. - // Lookup in outer queries. - if such a column is NOT found AND there are outer queries + If found different columns with the same name in GROUP BY and SELECT + issue a warning and return the GROUP BY column, + otherwise return the found SELECT column. + + if such a column is NOT found AND // Lookup in outer queries. + there are outer queries { for each outer query Q_k beginning from the inner-most one { @@ -2385,8 +2395,9 @@ static Item** find_field_in_group_list(Item *find_item, ORDER *group_list) } } } - This procedure treats GROUP BY and SELECT as one namespace for column - references in HAVING. + + This procedure treats GROUP BY and SELECT clauses as one namespace for + column references in HAVING. RETURN TRUE if error @@ -2398,9 +2409,10 @@ bool Item_ref::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) DBUG_ASSERT(fixed == 0); uint counter; SELECT_LEX *current_sel= thd->lex->current_select; - List *search_namespace= current_sel->get_item_list(); + List *select_fields= current_sel->get_item_list(); bool is_having_field= current_sel->having_fix_field; Item **group_by_ref= NULL; + bool ambiguous_fields= FALSE; if (!ref) { @@ -2413,7 +2425,7 @@ bool Item_ref::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) Search for a column or derived column named as 'this' in the SELECT clause of current_select. */ - if (!(ref= find_item_in_list(this, *search_namespace, &counter, + if (!(ref= find_item_in_list(this, *select_fields, &counter, REPORT_EXCEPT_NOT_FOUND))) return TRUE; /* Some error occurred. */ @@ -2422,20 +2434,22 @@ bool Item_ref::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) { group_by_ref= find_field_in_group_list(this, (ORDER*) current_sel->group_list.first); + /* Check if the fields found in SELECT and GROUP BY are the same field. */ if (group_by_ref && ref != (Item **) not_found_item && !((*group_by_ref)->eq(*ref, 0))) { - my_printf_error(ER_NON_UNIQ_ERROR, ER(ER_NON_UNIQ_ERROR), - MYF(0), this->full_name(), current_thd->where); - return TRUE; + ambiguous_fields= TRUE; + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + ER_NON_UNIQ_ERROR, ER(ER_NON_UNIQ_ERROR), + this->full_name(), current_thd->where); + } } - /* If we didn't find such a column in the current query, and if there is an - outer select, and this is not a derived table (which do not support the + outer select, and it is not a derived table (which do not support the use of outer fields for now), search the outer select(s) for a column named as 'this'. */ @@ -2494,7 +2508,8 @@ bool Item_ref::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) outer_sel->group_list.elements == 0)) && (tmp= find_field_in_tables(thd, this, table_list, reference, - 0, 1)) != not_found_field) + IGNORE_EXCEPT_NON_UNIQUE, TRUE)) != + not_found_field) { if (tmp) { @@ -2538,7 +2553,7 @@ bool Item_ref::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) else { // Call to report error - find_item_in_list(this, *search_namespace, &counter, + find_item_in_list(this, *select_fields, &counter, REPORT_ALL_ERRORS); } ref= 0; @@ -2588,7 +2603,7 @@ bool Item_ref::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) } else { - if (ref != (Item **) not_found_item) + if (ref != (Item **) not_found_item && !ambiguous_fields) ref= current_sel->ref_pointer_array + counter; else if (group_by_ref) ref= group_by_ref; diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index d8916149b77..8c27e0130a1 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -650,8 +650,13 @@ void execute_init_command(THD *thd, sys_var_str *init_command_var, rw_lock_t *var_mutex); extern const Field *not_found_field; extern const Field *view_ref_found; + +enum find_item_error_report_type {REPORT_ALL_ERRORS, REPORT_EXCEPT_NOT_FOUND, + IGNORE_ERRORS, REPORT_EXCEPT_NON_UNIQUE, + IGNORE_EXCEPT_NON_UNIQUE}; Field *find_field_in_tables(THD *thd, Item_ident *item, TABLE_LIST *tables, - Item **ref, bool report_error, + Item **ref, + find_item_error_report_type report_error, bool check_privileges); Field * find_field_in_table(THD *thd, TABLE_LIST *table_list, @@ -765,8 +770,6 @@ TABLE *unlink_open_table(THD *thd,TABLE *list,TABLE *find); SQL_SELECT *make_select(TABLE *head, table_map const_tables, table_map read_tables, COND *conds, int *error, bool allow_null_cond= false); -enum find_item_error_report_type {REPORT_ALL_ERRORS, REPORT_EXCEPT_NOT_FOUND, - IGNORE_ERRORS}; extern const Item **not_found_item; Item ** find_item_in_list(Item *item, List &items, uint *counter, find_item_error_report_type report_error); diff --git a/sql/sp.cc b/sql/sp.cc index e444a412760..323c1acd525 100644 --- a/sql/sp.cc +++ b/sql/sp.cc @@ -615,7 +615,7 @@ db_show_routine_status(THD *thd, int type, const char *wild) Item_field *field= new Item_field("mysql", "proc", used_field->field_name); if (!(used_field->field= find_field_in_tables(thd, field, &tables, - 0, TRUE, 1))) + 0, REPORT_ALL_ERRORS, 1))) { res= SP_INTERNAL_ERROR; goto err_case1; diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 4f273fbd0c4..a8a44205b64 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -2173,25 +2173,33 @@ Field *find_field_in_real_table(THD *thd, TABLE *table, find_field_in_tables() thd Pointer to current thread structure item Field item that should be found - tables Tables for scanning - ref if view field is found, pointer to view item will - be returned via this parameter - report_error If FALSE then do not report error if item not found - and return not_found_field + tables Tables to be searched for item + ref If 'item' is resolved to a view field, ref is set to + point to the found view field + report_error Degree of error reporting: + - IGNORE_ERRORS then do not report any error + - IGNORE_EXCEPT_NON_UNIQUE report only non-unique + fields, suppress all other errors + - REPORT_EXCEPT_NON_UNIQUE report all other errors + except when non-unique fields were found + - REPORT_ALL_ERRORS check_privileges need to check privileges RETURN VALUES - 0 Field is not found or field is not unique- error - message is reported - not_found_field Function was called with report_error == FALSE and - field was not found. no error message reported. - view_ref_found view field is found, item passed through ref parameter - found field + 0 No field was found, or the found field is not unique, or + there are no sufficient access priviligaes for the + found field, or the field is qualified with non-existing + table. + not_found_field The function was called with report_error == + (IGNORE_ERRORS || IGNORE_EXCEPT_NON_UNIQUE) and a + field was not found. + view_ref_found View field is found, item passed through ref parameter + found field If a item was resolved to some field */ Field * find_field_in_tables(THD *thd, Item_ident *item, TABLE_LIST *tables, - Item **ref, bool report_error, + Item **ref, find_item_error_report_type report_error, bool check_privileges) { Field *found=0; @@ -2268,8 +2276,10 @@ find_field_in_tables(THD *thd, Item_ident *item, TABLE_LIST *tables, return find; if (found) { - my_printf_error(ER_NON_UNIQ_ERROR,ER(ER_NON_UNIQ_ERROR),MYF(0), - item->full_name(),thd->where); + if (report_error == REPORT_ALL_ERRORS || + report_error == IGNORE_EXCEPT_NON_UNIQUE) + my_printf_error(ER_NON_UNIQ_ERROR,ER(ER_NON_UNIQ_ERROR),MYF(0), + item->full_name(),thd->where); return (Field*) 0; } found=find; @@ -2278,7 +2288,8 @@ find_field_in_tables(THD *thd, Item_ident *item, TABLE_LIST *tables, } if (found) return found; - if (!found_table && report_error) + if (!found_table && (report_error == REPORT_ALL_ERRORS || + report_error == REPORT_EXCEPT_NON_UNIQUE)) { char buff[NAME_LEN*2+1]; if (db && db[0]) @@ -2286,28 +2297,30 @@ find_field_in_tables(THD *thd, Item_ident *item, TABLE_LIST *tables, strxnmov(buff,sizeof(buff)-1,db,".",table_name,NullS); table_name=buff; } - if (report_error) - { + if (report_error == REPORT_ALL_ERRORS || + report_error == REPORT_EXCEPT_NON_UNIQUE) my_printf_error(ER_UNKNOWN_TABLE, ER(ER_UNKNOWN_TABLE), MYF(0), table_name, thd->where); - } else return (Field*) not_found_field; } else - if (report_error) + if (report_error == REPORT_ALL_ERRORS || + report_error == REPORT_EXCEPT_NON_UNIQUE) my_printf_error(ER_BAD_FIELD_ERROR,ER(ER_BAD_FIELD_ERROR),MYF(0), item->full_name(),thd->where); else return (Field*) not_found_field; return (Field*) 0; } + bool allow_rowid= tables && !tables->next_local; // Only one table for (; tables ; tables= tables->next_local) { if (!tables->table) { - if (report_error) + if (report_error == REPORT_ALL_ERRORS || + report_error == REPORT_EXCEPT_NON_UNIQUE) my_printf_error(ER_BAD_FIELD_ERROR,ER(ER_BAD_FIELD_ERROR),MYF(0), item->full_name(),thd->where); return (Field*) not_found_field; @@ -2332,8 +2345,10 @@ find_field_in_tables(THD *thd, Item_ident *item, TABLE_LIST *tables, { if (!thd->where) // Returns first found break; - my_printf_error(ER_NON_UNIQ_ERROR,ER(ER_NON_UNIQ_ERROR),MYF(0), - name,thd->where); + if (report_error == REPORT_ALL_ERRORS || + report_error == IGNORE_EXCEPT_NON_UNIQUE) + my_printf_error(ER_NON_UNIQ_ERROR,ER(ER_NON_UNIQ_ERROR),MYF(0), + name,thd->where); return (Field*) 0; } found=field; @@ -2341,7 +2356,8 @@ find_field_in_tables(THD *thd, Item_ident *item, TABLE_LIST *tables, } if (found) return found; - if (report_error) + if (report_error == REPORT_ALL_ERRORS || + report_error == REPORT_EXCEPT_NON_UNIQUE) my_printf_error(ER_BAD_FIELD_ERROR, ER(ER_BAD_FIELD_ERROR), MYF(0), item->full_name(), thd->where); else @@ -2377,7 +2393,7 @@ find_field_in_tables(THD *thd, Item_ident *item, TABLE_LIST *tables, found field */ -// Special Item pointer for find_item_in_list returning +/* Special Item pointer to serve as a return value from find_item_in_list(). */ const Item **not_found_item= (const Item**) 0x1; diff --git a/sql/sql_help.cc b/sql/sql_help.cc index cba74c93a6a..62ee708b695 100644 --- a/sql/sql_help.cc +++ b/sql/sql_help.cc @@ -88,7 +88,7 @@ static bool init_fields(THD *thd, TABLE_LIST *tables, Item_field *field= new Item_field("mysql", find_fields->table_name, find_fields->field_name); if (!(find_fields->field= find_field_in_tables(thd, field, tables, - 0, TRUE, 1))) + 0, REPORT_ALL_ERRORS, 1))) DBUG_RETURN(1); } DBUG_RETURN(0); diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 9ba191a3f3a..99d076bd335 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -11115,24 +11115,53 @@ cp_buffer_from_ref(TABLE_REF *ref) *****************************************************************************/ /* - Find order/group item in requested columns and change the item to point at - it. If item doesn't exists, add it first in the field list - Return 0 if ok. + Resolve an ORDER BY or GROUP BY column reference. + + SYNOPSIS + find_order_in_list() + thd [in] Pointer to current thread structure + ref_pointer_array [in/out] All select, group and order by fields + tables [in] List of tables to search in (usually FROM clause) + order [in] Column reference to be resolved + fields [in] List of fields to search in (usually SELECT list) + all_fields [in/out] All select, group and order by fields + is_group_field [in] True if order is a GROUP field, false if + ORDER by field + + DESCRIPTION + Given a column reference (represented by 'order') from a GROUP BY or ORDER + BY clause, find the actual column it represents. If the column being + resolved is from the GROUP BY clause, the procedure searches the SELECT + list 'fields' and the columns in the FROM list 'tables'. If 'order' is from + the ORDER BY clause, only the SELECT list is being searched. + + If 'order' is resolved to an Item, then order->item is set to the found + Item. If there is no item for the found column (that is, it was resolved + into a table field), order->item is 'fixed' and is added to all_fields and + ref_pointer_array. + + RETURN + 0 if ok + 1 if error occurred */ static int -find_order_in_list(THD *thd, Item **ref_pointer_array, - TABLE_LIST *tables,ORDER *order, List &fields, - List &all_fields) +find_order_in_list(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables, + ORDER *order, List &fields, List &all_fields, + bool is_group_field) { - Item *itemptr=*order->item; - if (itemptr->type() == Item::INT_ITEM) + Item *order_item=*order->item; /* The item from the GROUP/ORDER caluse. */ + Item::Type order_item_type; + Item **select_item; /* The corresponding item from the SELECT clause. */ + Field *from_field; /* The corresponding field from the FROM clause. */ + + if (order_item->type() == Item::INT_ITEM) { /* Order by position */ - uint count= (uint) itemptr->val_int(); + uint count= (uint) order_item->val_int(); if (!count || count > fields.elements) { my_printf_error(ER_BAD_FIELD_ERROR,ER(ER_BAD_FIELD_ERROR), - MYF(0),itemptr->full_name(), + MYF(0),order_item->full_name(), thd->where); return 1; } @@ -11142,17 +11171,47 @@ find_order_in_list(THD *thd, Item **ref_pointer_array, order->counter_used= 1; return 0; } + /* Lookup the current GROUP/ORDER field in the SELECT clause. */ uint counter; - Item **item= find_item_in_list(itemptr, fields, &counter, + select_item= find_item_in_list(order_item, fields, &counter, REPORT_EXCEPT_NOT_FOUND); - if (!item) - return 1; + if (!select_item) + return 1; /* Some error occured. */ - if (item != (Item **)not_found_item) + + /* Check whether the resolved field is not ambiguos. */ + if (select_item != not_found_item) { - order->item= ref_pointer_array + counter; - order->in_field_list=1; - return 0; + /* Lookup the current GROUP field in the FROM clause. */ + order_item_type= order_item->type(); + if (is_group_field && + order_item_type == Item::FIELD_ITEM || order_item_type == Item::REF_ITEM) + { + Item **view_ref= NULL; + from_field= find_field_in_tables(thd, (Item_ident*) order_item, tables, + view_ref, IGNORE_ERRORS, TRUE); + if(!from_field) + from_field= (Field*) not_found_field; + } + else + from_field= (Field*) not_found_field; + + if (from_field == not_found_field || + from_field && from_field != view_ref_found && + (*select_item)->type() == Item::FIELD_ITEM && + ((Item_field*) (*select_item))->field->eq(from_field)) + /* + If there is no such field in the FROM clause, or it is the same field as + the one found in the SELECT clause, then use the Item created for the + SELECT field. As a result if there was a derived field that 'shadowed' + a table field with the same name, the table field will be chosen over + the derived field. + */ + { + order->item= ref_pointer_array + counter; + order->in_field_list=1; + return 0; + } } order->in_field_list=0; @@ -11187,7 +11246,7 @@ int setup_order(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables, for (; order; order=order->next) { if (find_order_in_list(thd, ref_pointer_array, tables, order, fields, - all_fields)) + all_fields, FALSE)) return 1; } return 0; @@ -11239,7 +11298,7 @@ setup_group(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables, for (; order; order=order->next) { if (find_order_in_list(thd, ref_pointer_array, tables, order, fields, - all_fields)) + all_fields, TRUE)) return 1; (*order->item)->marker=1; /* Mark found */ if ((*order->item)->with_sum_func) From 7031a0b02674ac2fe6fa2afd4e0819e7810ec13e Mon Sep 17 00:00:00 2001 From: "timour@mysql.com" <> Date: Tue, 2 Nov 2004 20:11:00 +0200 Subject: [PATCH 3/8] fixed spelling --- sql/sql_base.cc | 11 ++++++----- sql/sql_select.cc | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/sql/sql_base.cc b/sql/sql_base.cc index a8a44205b64..9f9b9b4ed63 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -2187,7 +2187,7 @@ Field *find_field_in_real_table(THD *thd, TABLE *table, RETURN VALUES 0 No field was found, or the found field is not unique, or - there are no sufficient access priviligaes for the + there are no sufficient access priviliges for the found field, or the field is qualified with non-existing table. not_found_field The function was called with report_error == @@ -2217,8 +2217,8 @@ find_field_in_tables(THD *thd, Item_ident *item, TABLE_LIST *tables, TABLE_LIST *tables is not changed during query execution (which is true for all queries except RENAME but luckily RENAME doesn't use fields...) so we can rely on reusing pointer to its member. - With this optimisation we also miss case when addition of one more - field makes some prepared query ambiguous and so erronous, but we + With this optimization we also miss case when addition of one more + field makes some prepared query ambiguous and so erroneous, but we accept this trade off. */ found= find_field_in_real_table(thd, item->cached_table->table, @@ -2239,7 +2239,7 @@ find_field_in_tables(THD *thd, Item_ident *item, TABLE_LIST *tables, if (db && lower_case_table_names) { /* - convert database to lower case for comparision. + convert database to lower case for comparison. We can't do this in Item_field as this would change the 'name' of the item which may be used in the select list */ @@ -2779,7 +2779,8 @@ bool get_key_map_from_key_list(key_map *map, TABLE *table, any_privileges 0 If we should ensure that we have SELECT privileges for all columns 1 If any privilege is ok - allocate_view_names if true view names will be copied to current Item_arena memory (made for SP/PS) + allocate_view_names if true view names will be copied to current Item_arena + memory (made for SP/PS) RETURN 0 ok 'it' is updated to point at last inserted diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 99d076bd335..178d36faa2a 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -11141,7 +11141,7 @@ cp_buffer_from_ref(TABLE_REF *ref) ref_pointer_array. RETURN - 0 if ok + 0 if OK 1 if error occurred */ From 660e1236b570f245a839f337bb2aaf11a3e08f62 Mon Sep 17 00:00:00 2001 From: "timour@mysql.com" <> Date: Fri, 5 Nov 2004 15:48:44 +0200 Subject: [PATCH 4/8] WL#1972 "Evaluate HAVING before SELECT" - more tests, post-review changes, bug-fixes, simplifications, and improved comments --- mysql-test/r/having.result | 174 ++++++++++---- mysql-test/t/having.test | 204 ++++++++++------ sql/item.cc | 468 ++++++++++++++++++++----------------- sql/sql_base.cc | 7 +- 4 files changed, 524 insertions(+), 329 deletions(-) diff --git a/mysql-test/r/having.result b/mysql-test/r/having.result index 04f73792dd6..6f548d20668 100644 --- a/mysql-test/r/having.result +++ b/mysql-test/r/having.result @@ -1,4 +1,4 @@ -drop table if exists t1,t2; +drop table if exists t1,t2,t3; create table t1 (a int); select count(a) as b from t1 where a=0 having b > 0; b @@ -128,74 +128,76 @@ id description c 1 test 0 2 test2 0 drop table t1,t2,t3; -drop table if exists wl1972; -create table wl1972 ( -col1 int, -col2 varchar(5) character set latin1 collate latin1_bin) -engine=innodb; -insert into wl1972 values(10,'hello'); -insert into wl1972 values(20,'hello'); -insert into wl1972 values(30,'hello'); -insert into wl1972 values(10,'bye'); -insert into wl1972 values(10,'sam'); -insert into wl1972 values(10,'bob'); -select count(*) from wl1972 group by col1 having col1 = 10; +create table t1 (col1 int, col2 varchar(5), col_t1 int); +create table t2 (col1 int, col2 varchar(5), col_t2 int); +create table t3 (col1 int, col2 varchar(5), col_t3 int); +insert into t1 values(10,'hello',10); +insert into t1 values(20,'hello',20); +insert into t1 values(30,'hello',30); +insert into t1 values(10,'bye',10); +insert into t1 values(10,'sam',10); +insert into t1 values(10,'bob',10); +insert into t2 select * from t1; +insert into t3 select * from t1; +select count(*) from t1 group by col1 having col1 = 10; count(*) 4 -select count(*) as count_col1 from wl1972 group by col1 having col1 = 10; +select count(*) as count_col1 from t1 group by col1 having col1 = 10; count_col1 4 -select count(*) as count_col1 from wl1972 as t1 group by col1 having col1 = 10; +select count(*) as count_col1 from t1 as tmp1 group by col1 having col1 = 10; count_col1 4 -select count(*) from wl1972 group by col2 having col2 = 'hello'; +select count(*) from t1 group by col2 having col2 = 'hello'; count(*) 3 -select count(*) from wl1972 group by col2 having col1 = 10; +select count(*) from t1 group by col2 having col1 = 10; ERROR 42S22: Unknown column 'col1' in 'having clause' -select col1 as count_col1 from wl1972 as t1 group by col1 having col1 = 10; +select col1 as count_col1 from t1 as tmp1 group by col1 having col1 = 10; count_col1 10 -select col1 as count_col1 from wl1972 as t1 group by col1 having count_col1 = 10; +select col1 as count_col1 from t1 as tmp1 group by col1 having count_col1 = 10; count_col1 10 -select col1 as count_col1 from wl1972 as t1 group by count_col1 having col1 = 10; +select col1 as count_col1 from t1 as tmp1 group by count_col1 having col1 = 10; count_col1 10 -select col1 as count_col1 from wl1972 as t1 group by count_col1 having count_col1 = 10; +select col1 as count_col1 from t1 as tmp1 group by count_col1 having count_col1 = 10; count_col1 10 -select col1 as count_col1,col2 from wl1972 as t1 group by col1,col2 having col1 = 10; +select col1 as count_col1,col2 from t1 as tmp1 group by col1,col2 having col1 = 10; count_col1 col2 10 bob 10 bye 10 hello 10 sam -select col1 as count_col1,col2 from wl1972 as t1 group by col1,col2 having count_col1 = 10; +select col1 as count_col1,col2 from t1 as tmp1 group by col1,col2 having count_col1 = 10; count_col1 col2 10 bob 10 bye 10 hello 10 sam -select col1 as count_col1,col2 from wl1972 as t1 group by col1,col2 having col2 = 'hello'; +select col1 as count_col1,col2 from t1 as tmp1 group by col1,col2 having col2 = 'hello'; count_col1 col2 10 hello 20 hello 30 hello -select col1 as count_col1,col2 as group_col2 from wl1972 as t1 -group by col1,col2 having group_col2 = 'hello'; +select col1 as count_col1,col2 as group_col2 from t1 as tmp1 group by col1,col2 having group_col2 = 'hello'; count_col1 group_col2 10 hello 20 hello 30 hello -select sum(col1) as co12 from wl1972 group by col2 having col2 10; +select sum(col1) as co12 from t1 group by col2 having col2 10; ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '10' at line 1 -select sum(col1) as co2, count(col2) as cc from wl1972 group by col1 having col1 =10; +select sum(col1) as co2, count(col2) as cc from t1 group by col1 having col1 =10; co2 cc 40 4 -select t1.col1 from wl1972 as t1 where t1.col2 in -(select t2.col2 from wl1972 as t2 -group by t2.col1,t2.col2 having t2.col1<=10); +select t2.col2 from t2 group by t2.col1, t2.col2 having t1.col1 <= 10; +ERROR 42S22: Unknown column 't1.col1' in 'having clause' +select t1.col1 from t1 +where t1.col2 in +(select t2.col2 from t2 +group by t2.col1, t2.col2 having t2.col1 <= 10); col1 10 20 @@ -203,10 +205,12 @@ col1 10 10 10 -select t1.col1 from wl1972 as t1 where t1.col2 in -(select t2.col2 from wl1972 as t2 -group by t2.col1,t2.col2 having t2.col1<= -(select min(t3.col1) from wl1972 as t3)); +select t1.col1 from t1 +where t1.col2 in +(select t2.col2 from t2 +group by t2.col1, t2.col2 +having t2.col1 <= +(select min(t3.col1) from t3)); col1 10 20 @@ -214,36 +218,106 @@ col1 10 10 10 -drop table wl1972; -create table wl1972 (s1 int); -insert into wl1972 values (1),(2),(3); -select count(*) from wl1972 group by s1 having s1 is null; +select t1.col1 from t1 +where t1.col2 in +(select t2.col2 from t2 +group by t2.col1, t2.col2 having t1.col1 <= 10); +col1 +10 +10 +10 +10 +select t1.col1 as tmp_col from t1 +where t1.col2 in +(select t2.col2 from t2 +group by t2.col1, t2.col2 having tmp_col <= 10); +tmp_col +10 +10 +10 +10 +select t1.col1 from t1 +where t1.col2 in +(select t2.col2 from t2 +group by t2.col1, t2.col2 having col_t1 <= 10); +col1 +10 +10 +10 +10 +select t1.col1 from t1 +where t1.col2 in +(select t2.col2 from t2 +group by t2.col1, t2.col2 having col_t1 <= 10) +having col_t1 <= 20; +ERROR 42S22: Unknown column 'col_t1' in 'having clause' +select t1.col1 from t1 +where t1.col2 in +(select t2.col2 from t2 +group by t2.col1, t2.col2 having col_t1 <= 10) +group by col_t1 +having col_t1 <= 20; +col1 +10 +select col_t1, sum(col1) from t1 +group by col_t1 +having col_t1 > 10 and +exists (select sum(t2.col1) from t2 +group by t2.col2 having t2.col2 > 'b'); +col_t1 sum(col1) +20 20 +30 30 +select sum(col1) from t1 +group by col_t1 +having col_t1 in (select sum(t2.col1) from t2 +group by t2.col2, t2.col1 having t2.col1 = t1.col1); +ERROR 42S22: Unknown column 't1.col1' in 'having clause' +select sum(col1) from t1 +group by col_t1 +having col_t1 in (select sum(t2.col1) from t2 +group by t2.col2, t2.col1 having t2.col1 = col_t1); +sum(col1) +40 +20 +30 +select t1.col1, t2.col1 from t1, t2 where t1.col1 = t2.col1 +group by t1.col1, t2.col1 having col1 = 2; +ERROR 23000: Column 'col1' in having clause is ambiguous +select t1.col1*10+t2.col1 from t1,t2 where t1.col1=t2.col1 +group by t1.col1, t2.col1 having col1 = 2; +ERROR 23000: Column 'col1' in having clause is ambiguous +drop table t1, t2, t3; +create table t1 (s1 int); +insert into t1 values (1),(2),(3); +select count(*) from t1 group by s1 having s1 is null; count(*) -select s1*0 as s1 from wl1972 group by s1 having s1 <> 0; +select s1*0 as s1 from t1 group by s1 having s1 <> 0; s1 0 0 0 -select s1*0 from wl1972 group by s1 having s1 = 0; +Warnings: +Warning 1052 Column 's1' in having clause is ambiguous +select s1*0 from t1 group by s1 having s1 = 0; s1*0 -select s1 from wl1972 group by 1 having 1 = 0; +select s1 from t1 group by 1 having 1 = 0; s1 -select count(s1) from wl1972 group by s1 having count(1+1)=2; +select count(s1) from t1 group by s1 having count(1+1)=2; count(s1) -select count(s1) from wl1972 group by s1 having s1*0=0; +select count(s1) from t1 group by s1 having s1*0=0; count(s1) 1 1 1 -select * from wl1972 a, wl1972 b group by a.s1 having s1 is null; +select * from t1 a, t1 b group by a.s1 having s1 is null; ERROR 23000: Column 's1' in having clause is ambiguous -drop table wl1972; -create table wl1972 (s1 char character set latin1 collate latin1_german1_ci); -insert into wl1972 values ('ü'),('y'); +drop table t1; +create table t1 (s1 char character set latin1 collate latin1_german1_ci); +insert into t1 values ('ü'),('y'); Warnings: Warning 1265 Data truncated for column 's1' at row 1 -select s1,count(s1) from wl1972 +select s1,count(s1) from t1 group by s1 collate latin1_swedish_ci having s1 = 'y'; s1 count(s1) y 1 -drop table wl1972; +drop table t1; diff --git a/mysql-test/t/having.test b/mysql-test/t/having.test index 8d21f070956..d2a5ca4361e 100644 --- a/mysql-test/t/having.test +++ b/mysql-test/t/having.test @@ -2,7 +2,7 @@ # --disable_warnings -drop table if exists t1,t2; +drop table if exists t1,t2,t3; --enable_warnings create table t1 (a int); @@ -134,108 +134,180 @@ drop table t1,t2,t3; # set global sql_mode='ansi'; # set session sql_mode='ansi'; ---disable_warnings -drop table if exists wl1972; ---enable_warnings +create table t1 (col1 int, col2 varchar(5), col_t1 int); +create table t2 (col1 int, col2 varchar(5), col_t2 int); +create table t3 (col1 int, col2 varchar(5), col_t3 int); -create table wl1972 ( - col1 int, - col2 varchar(5) character set latin1 collate latin1_bin) -engine=innodb; +insert into t1 values(10,'hello',10); +insert into t1 values(20,'hello',20); +insert into t1 values(30,'hello',30); +insert into t1 values(10,'bye',10); +insert into t1 values(10,'sam',10); +insert into t1 values(10,'bob',10); -insert into wl1972 values(10,'hello'); -insert into wl1972 values(20,'hello'); -insert into wl1972 values(30,'hello'); -insert into wl1972 values(10,'bye'); -insert into wl1972 values(10,'sam'); -insert into wl1972 values(10,'bob'); - -select count(*) from wl1972 group by col1 having col1 = 10; - -select count(*) as count_col1 from wl1972 group by col1 having col1 = 10; - -select count(*) as count_col1 from wl1972 as t1 group by col1 having col1 = 10; - -select count(*) from wl1972 group by col2 having col2 = 'hello'; +insert into t2 select * from t1; +insert into t3 select * from t1; +select count(*) from t1 group by col1 having col1 = 10; +select count(*) as count_col1 from t1 group by col1 having col1 = 10; +select count(*) as count_col1 from t1 as tmp1 group by col1 having col1 = 10; +select count(*) from t1 group by col2 having col2 = 'hello'; --error 1054 -select count(*) from wl1972 group by col2 having col1 = 10; - -select col1 as count_col1 from wl1972 as t1 group by col1 having col1 = 10; - -select col1 as count_col1 from wl1972 as t1 group by col1 having count_col1 = 10; - -select col1 as count_col1 from wl1972 as t1 group by count_col1 having col1 = 10; +select count(*) from t1 group by col2 having col1 = 10; +select col1 as count_col1 from t1 as tmp1 group by col1 having col1 = 10; +select col1 as count_col1 from t1 as tmp1 group by col1 having count_col1 = 10; +select col1 as count_col1 from t1 as tmp1 group by count_col1 having col1 = 10; # ANSI: should return SQLSTATE 42000 Syntax error or access violation # MySQL: returns 10 - because of GROUP BY name resolution - -select col1 as count_col1 from wl1972 as t1 group by count_col1 having count_col1 = 10; +select col1 as count_col1 from t1 as tmp1 group by count_col1 having count_col1 = 10; # ANSI: should return SQLSTATE 42000 Syntax error or access violation # MySQL: returns 10 - because of GROUP BY name resolution - -select col1 as count_col1,col2 from wl1972 as t1 group by col1,col2 having col1 = 10; - -select col1 as count_col1,col2 from wl1972 as t1 group by col1,col2 having count_col1 = 10; - -select col1 as count_col1,col2 from wl1972 as t1 group by col1,col2 having col2 = 'hello'; - -select col1 as count_col1,col2 as group_col2 from wl1972 as t1 -group by col1,col2 having group_col2 = 'hello'; - +select col1 as count_col1,col2 from t1 as tmp1 group by col1,col2 having col1 = 10; +select col1 as count_col1,col2 from t1 as tmp1 group by col1,col2 having count_col1 = 10; +select col1 as count_col1,col2 from t1 as tmp1 group by col1,col2 having col2 = 'hello'; +select col1 as count_col1,col2 as group_col2 from t1 as tmp1 group by col1,col2 having group_col2 = 'hello'; --error 1064 -select sum(col1) as co12 from wl1972 group by col2 having col2 10; +select sum(col1) as co12 from t1 group by col2 having col2 10; +select sum(col1) as co2, count(col2) as cc from t1 group by col1 having col1 =10; +--error 1054 +select t2.col2 from t2 group by t2.col1, t2.col2 having t1.col1 <= 10; -select sum(col1) as co2, count(col2) as cc from wl1972 group by col1 having col1 =10; -select t1.col1 from wl1972 as t1 where t1.col2 in - (select t2.col2 from wl1972 as t2 - group by t2.col1,t2.col2 having t2.col1<=10); +# +# queries with nested sub-queries +# -select t1.col1 from wl1972 as t1 where t1.col2 in - (select t2.col2 from wl1972 as t2 - group by t2.col1,t2.col2 having t2.col1<= - (select min(t3.col1) from wl1972 as t3)); +# the having column is resolved in the same query +select t1.col1 from t1 +where t1.col2 in + (select t2.col2 from t2 + group by t2.col1, t2.col2 having t2.col1 <= 10); -drop table wl1972; +select t1.col1 from t1 +where t1.col2 in + (select t2.col2 from t2 + group by t2.col1, t2.col2 + having t2.col1 <= + (select min(t3.col1) from t3)); + +# the having column is resolved in the SELECT clause of the outer query - +# works in ANSI +select t1.col1 from t1 +where t1.col2 in + (select t2.col2 from t2 + group by t2.col1, t2.col2 having t1.col1 <= 10); + +# the having column is resolved in the SELECT clause of the outer query - +# error in ANSI, works with MySQL extension +select t1.col1 as tmp_col from t1 +where t1.col2 in + (select t2.col2 from t2 + group by t2.col1, t2.col2 having tmp_col <= 10); + +# the having column is resolved in the FROM clause of the outer query - +# works in ANSI +select t1.col1 from t1 +where t1.col2 in + (select t2.col2 from t2 + group by t2.col1, t2.col2 having col_t1 <= 10); + +# nested queries with HAVING, inner having column resolved in outer FROM clause +# the outer having column is not referenced in GROUP BY which results in an error +--error 1054 +select t1.col1 from t1 +where t1.col2 in + (select t2.col2 from t2 + group by t2.col1, t2.col2 having col_t1 <= 10) +having col_t1 <= 20; + +# both having columns are resolved in the GROUP clause of the outer query +select t1.col1 from t1 +where t1.col2 in + (select t2.col2 from t2 + group by t2.col1, t2.col2 having col_t1 <= 10) +group by col_t1 +having col_t1 <= 20; + + +# +# nested HAVING clauses +# + +# non-correlated subqueries +select col_t1, sum(col1) from t1 +group by col_t1 +having col_t1 > 10 and + exists (select sum(t2.col1) from t2 + group by t2.col2 having t2.col2 > 'b'); + +# correlated subqueries - inner having column 't1.col2' resolves to +# the outer FROM clause, which cannot be used because the outer query +# is grouped +--error 1054 +select sum(col1) from t1 +group by col_t1 +having col_t1 in (select sum(t2.col1) from t2 + group by t2.col2, t2.col1 having t2.col1 = t1.col1); + +# correlated subqueries - inner having column 'col_t1' resolves to +# the outer GROUP clause +select sum(col1) from t1 +group by col_t1 +having col_t1 in (select sum(t2.col1) from t2 + group by t2.col2, t2.col1 having t2.col1 = col_t1); + + +# +# queries with joins and ambiguous column names +# +--error 1052 +select t1.col1, t2.col1 from t1, t2 where t1.col1 = t2.col1 +group by t1.col1, t2.col1 having col1 = 2; + +--error 1052 +select t1.col1*10+t2.col1 from t1,t2 where t1.col1=t2.col1 +group by t1.col1, t2.col1 having col1 = 2; + +drop table t1, t2, t3; # More queries to test ANSI compatibility -create table wl1972 (s1 int); -insert into wl1972 values (1),(2),(3); +create table t1 (s1 int); +insert into t1 values (1),(2),(3); -select count(*) from wl1972 group by s1 having s1 is null; +select count(*) from t1 group by s1 having s1 is null; -select s1*0 as s1 from wl1972 group by s1 having s1 <> 0; +select s1*0 as s1 from t1 group by s1 having s1 <> 0; # ANSI requires: 3 rows # MySQL returns: 0 rows - because of GROUP BY name resolution -select s1*0 from wl1972 group by s1 having s1 = 0; +select s1*0 from t1 group by s1 having s1 = 0; -select s1 from wl1972 group by 1 having 1 = 0; +select s1 from t1 group by 1 having 1 = 0; -select count(s1) from wl1972 group by s1 having count(1+1)=2; +select count(s1) from t1 group by s1 having count(1+1)=2; # ANSI requires: 3 rows # MySQL returns: 0 rows - because of GROUP BY name resolution -select count(s1) from wl1972 group by s1 having s1*0=0; +select count(s1) from t1 group by s1 having s1*0=0; -- error 1052 -select * from wl1972 a, wl1972 b group by a.s1 having s1 is null; +select * from t1 a, t1 b group by a.s1 having s1 is null; # ANSI requires: 0 rows # MySQL returns: # "ERROR 1052 (23000): Column 's1' in having clause is ambiguous" # I think the column is ambiguous in ANSI too. # It is the same as: -# select a.s1, b.s1 from wl1972 a, wl1972 b group by a.s1 having s1 is null; +# select a.s1, b.s1 from t1 a, t1 b group by a.s1 having s1 is null; # currently we first check SELECT, thus s1 is ambiguous. -drop table wl1972; +drop table t1; -create table wl1972 (s1 char character set latin1 collate latin1_german1_ci); -insert into wl1972 values ('ü'),('y'); +create table t1 (s1 char character set latin1 collate latin1_german1_ci); +insert into t1 values ('ü'),('y'); -select s1,count(s1) from wl1972 +select s1,count(s1) from t1 group by s1 collate latin1_swedish_ci having s1 = 'y'; # ANSI requires: 1 row, with count(s1) = 2 # MySQL returns: 1 row, with count(s1) = 1 -drop table wl1972; +drop table t1; diff --git a/sql/item.cc b/sql/item.cc index 738814bc353..8b611f0ddb5 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -2320,14 +2320,22 @@ static Item** find_field_in_group_list(Item *find_item, ORDER *group_list) else continue; - if (cur_field->table_name && table_name && - !strcmp(cur_field->table_name, table_name)) - { /* If field_name is qualified by a table name. */ + if (cur_field->table_name && table_name) + { + /* If field_name is qualified by a table name. */ + if (strcmp(cur_field->table_name, table_name)) + /* Same field names, different tables. */ + return NULL; + ++cur_match_degree; - if (cur_field->db_name && db_name && - !strcmp(cur_field->db_name, db_name)) + if (cur_field->db_name && db_name) + { /* If field_name is also qualified by a database name. */ + if (strcmp(cur_field->db_name, db_name)) + /* Same field names, different databases. */ + return NULL; ++cur_match_degree; + } } if (cur_match_degree > found_match_degree) @@ -2335,8 +2343,21 @@ static Item** find_field_in_group_list(Item *find_item, ORDER *group_list) found_match_degree= cur_match_degree; found_group= cur_group; } + else if (found_group && (cur_match_degree == found_match_degree) && + ! (*(found_group->item))->eq(cur_field, 0)) + { + /* + If the current resolve candidate matches equally well as the current + best match, they must reference the same column, otherwise the field + is ambiguous. + */ + my_printf_error(ER_NON_UNIQ_ERROR, ER(ER_NON_UNIQ_ERROR), + MYF(0), find_item->full_name(), current_thd->where); + return NULL; + } } } + if (found_group) return found_group->item; else @@ -2344,6 +2365,97 @@ static Item** find_field_in_group_list(Item *find_item, ORDER *group_list) } +/* + Resolve a column reference in a sub-select. + + SYNOPSIS + resolve_ref_in_select_and_group() + thd current thread + ref column reference being resolved + select the sub-select that ref is resolved against + + DESCRIPTION + Resolve a column reference (usually inside a HAVING clause) against the + SELECT and GROUP BY clauses of the query described by 'select'. The name + resolution algorithm searches both the SELECT and GROUP BY clauses, and in + case of a name conflict prefers GROUP BY column names over SELECT names. If + both clauses contain different fields with the same names, a warning is + issued that name of 'ref' is ambiguous. We extend ANSI SQL in that when no + GROUP BY column is found, then a HAVING name is resolved as a possibly + derived SELECT column. + + NOTES + The resolution procedure is: + - Search for a column or derived column named col_ref_i [in table T_j] + in the SELECT clause of Q. + - Search for a column named col_ref_i [in table T_j] + in the GROUP BY clause of Q. + - If found different columns with the same name in GROUP BY and SELECT + - issue a warning and return the GROUP BY column, + - otherwise return the found SELECT column. + + + RETURN + NULL - there was an error, and the error was already reported + not_found_item - the item was not resolved, no error was reported + resolved item - if the item was resolved +*/ +static Item** +resolve_ref_in_select_and_group(THD *thd, Item_ref *ref, SELECT_LEX *select) +{ + Item **group_by_ref= NULL; + Item **select_ref= NULL; + ORDER *group_list= (ORDER*) select->group_list.first; + bool ambiguous_fields= FALSE; + uint counter; + + /* + Search for a column or derived column named as 'ref' in the SELECT + clause of the current select. + */ + if (!(select_ref= find_item_in_list(ref, *(select->get_item_list()), &counter, + REPORT_EXCEPT_NOT_FOUND))) + return NULL; /* Some error occurred. */ + + /* If this is a non-aggregated field inside HAVING, search in GROUP BY. */ + if (select->having_fix_field && !ref->with_sum_func && group_list) + { + group_by_ref= find_field_in_group_list(ref, group_list); + + /* Check if the fields found in SELECT and GROUP BY are the same field. */ + if (group_by_ref && (select_ref != not_found_item) && + !((*group_by_ref)->eq(*select_ref, 0))) + { + ambiguous_fields= TRUE; + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_NON_UNIQ_ERROR, + ER(ER_NON_UNIQ_ERROR), ref->full_name(), + current_thd->where); + + } + } + + if (select_ref != not_found_item || group_by_ref) + { + if (select_ref != not_found_item && !ambiguous_fields) + { + if (*select_ref && !(*select_ref)->fixed) + { + my_error(ER_ILLEGAL_REFERENCE, MYF(0), ref->name, + "forward reference in item list"); + return NULL; + } + return (select->ref_pointer_array + counter); + } + else if (group_by_ref) + return group_by_ref; + else + DBUG_ASSERT(FALSE); + } + else + return (Item**) not_found_item; +} + + /* Resolve the name of a column reference. @@ -2356,38 +2468,30 @@ static Item** find_field_in_group_list(Item *find_item, ORDER *group_list) DESCRIPTION The method resolves the column reference represented by 'this' as a column present in one of: GROUP BY clause, SELECT clause, outer queries. It is - used for columns in the HAVING clause which are not under aggregate - functions. + used typically for columns in the HAVING clause which are not under + aggregate functions. NOTES - The general idea behind the name resolution algorithm is that it searches - both the SELECT and GROUP BY clauses, and in case of a name conflict - prefers GROUP BY column names over SELECT names. We extend ANSI SQL in that - when no GROUP BY column is found, then a HAVING name is resolved as a - possibly derived SELECT column. - The name resolution algorithm used is: resolve_extended([T_j].col_ref_i) { Search for a column or derived column named col_ref_i [in table T_j] - in the SELECT list of Q. - - Search for a column named col_ref_i [in table T_j] - in the GROUP BY clause of Q. - - If found different columns with the same name in GROUP BY and SELECT - issue a warning and return the GROUP BY column, - otherwise return the found SELECT column. + in the SELECT and GROUP clauses of Q. if such a column is NOT found AND // Lookup in outer queries. there are outer queries { for each outer query Q_k beginning from the inner-most one { - search for a column or derived column named col_ref_i - [in table T_j] in the SELECT list of Q_k; - if such a column is not found + Search for a column or derived column named col_ref_i + [in table T_j] in the SELECT and GROUP clauses of Q_k. + + if such a column is not found AND + - Q_k is not a group query AND + - Q_k is not inside an aggregate function + OR + - Q_(k-1) is not in a HAVING or SELECT clause of Q_k { search for a column or derived column named col_ref_i [in table T_j] in the FROM clause of Q_k; @@ -2407,214 +2511,157 @@ static Item** find_field_in_group_list(Item *find_item, ORDER *group_list) bool Item_ref::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) { DBUG_ASSERT(fixed == 0); - uint counter; SELECT_LEX *current_sel= thd->lex->current_select; - List *select_fields= current_sel->get_item_list(); - bool is_having_field= current_sel->having_fix_field; - Item **group_by_ref= NULL; - bool ambiguous_fields= FALSE; if (!ref) { - TABLE_LIST *table_list; - bool upward_lookup= 0; SELECT_LEX_UNIT *prev_unit= current_sel->master_unit(); SELECT_LEX *outer_sel= prev_unit->outer_select(); + ORDER *group_list= (ORDER*) current_sel->group_list.first; + bool ambiguous_fields= FALSE; + Item **group_by_ref= NULL; - /* - Search for a column or derived column named as 'this' in the SELECT - clause of current_select. - */ - if (!(ref= find_item_in_list(this, *select_fields, &counter, - REPORT_EXCEPT_NOT_FOUND))) - return TRUE; /* Some error occurred. */ + if (!(ref= resolve_ref_in_select_and_group(thd, this, current_sel))) + return TRUE; /* Some error occured (e.g. ambigous names). */ - /* If this is a non-aggregated field inside HAVING, search in GROUP BY. */ - if (is_having_field && !this->with_sum_func) - { - group_by_ref= find_field_in_group_list(this, (ORDER*) - current_sel->group_list.first); - - /* Check if the fields found in SELECT and GROUP BY are the same field. */ - if (group_by_ref && ref != (Item **) not_found_item && - !((*group_by_ref)->eq(*ref, 0))) - { - ambiguous_fields= TRUE; - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_NON_UNIQ_ERROR, ER(ER_NON_UNIQ_ERROR), - this->full_name(), current_thd->where); - - } - } - - /* - If we didn't find such a column in the current query, and if there is an - outer select, and it is not a derived table (which do not support the - use of outer fields for now), search the outer select(s) for a column - named as 'this'. - */ - if (!group_by_ref && (ref == (Item **) not_found_item) && outer_sel && - (current_sel->master_unit()->first_select()->linkage != - DERIVED_TABLE_TYPE)) + if (ref == not_found_item) /* This reference was not resolved. */ { /* - We can't join the columns of the outer & current selects, because of - scope of view rules. For example if both tables (outer & current) have - field 'field' it is not a mistake to refer to this field without - qualifying it with a table name, but if we join tables in one list it - will cause error ER_NON_UNIQ_ERROR in find_item_in_list. + If there is an outer select, and it is not a derived table (which do + not support the use of outer fields for now), try to resolve this + reference in the outer select(s). + + We treat each subselect as a separate namespace, so that different + subselects may contain columns with the same names. The subselects are + searched starting from the innermost. */ - upward_lookup= 1; - Field *tmp= (Field*) not_found_field; - SELECT_LEX *last=0; - - for ( ; outer_sel ; - outer_sel= (prev_unit= outer_sel->master_unit())->outer_select()) + if (outer_sel && (current_sel->master_unit()->first_select()->linkage != + DERIVED_TABLE_TYPE)) { - last= outer_sel; - Item_subselect *prev_subselect_item= prev_unit->item; + TABLE_LIST *table_list; + Field *tmp= (Field*) not_found_field; + SELECT_LEX *last= 0; - /* Search in the SELECT list of the current outer sub-select. */ - if (outer_sel->resolve_mode == SELECT_LEX::SELECT_MODE && - (ref= find_item_in_list(this, outer_sel->item_list, - &counter, - REPORT_EXCEPT_NOT_FOUND)) != - (Item **)not_found_item) - { - if (*ref && (*ref)->fixed) // Avoid crash in case of error - { - prev_subselect_item->used_tables_cache|= (*ref)->used_tables(); - prev_subselect_item->const_item_cache&= (*ref)->const_item(); - } - break; - } + for ( ; outer_sel ; + outer_sel= (prev_unit= outer_sel->master_unit())->outer_select()) + { + last= outer_sel; + Item_subselect *prev_subselect_item= prev_unit->item; - /* Search in the tables in the FROM clause of the outer select. */ - table_list= outer_sel->get_table_list(); - if (outer_sel->resolve_mode == SELECT_LEX::INSERT_MODE && table_list) - { - /* It is primary INSERT st_select_lex => skip the first table. */ - table_list= table_list->next_local; - } - enum_parsing_place place= prev_subselect_item->parsing_place; - /* - Check table fields only if the subquery is used somewhere out of - HAVING or SELECT list, or outer SELECT does not use grouping - (i.e. tables are accessible) - */ - if (((place != IN_HAVING && - place != SELECT_LIST) || - (outer_sel->with_sum_func == 0 && - outer_sel->group_list.elements == 0)) && - (tmp= find_field_in_tables(thd, this, - table_list, reference, - IGNORE_EXCEPT_NON_UNIQUE, TRUE)) != - not_found_field) - { - if (tmp) + /* Search in the SELECT and GROUP lists of the outer select. */ + if (outer_sel->resolve_mode == SELECT_LEX::SELECT_MODE) { - if (tmp != view_ref_found) + if (!(ref= resolve_ref_in_select_and_group(thd, this, outer_sel))) + return TRUE; /* Some error occured (e.g. ambigous names). */ + if (ref != not_found_item) { - prev_subselect_item->used_tables_cache|= tmp->table->map; - prev_subselect_item->const_item_cache= 0; - } - else - { - prev_subselect_item->used_tables_cache|= - (*reference)->used_tables(); - prev_subselect_item->const_item_cache&= - (*reference)->const_item(); + DBUG_ASSERT(*ref && (*ref)->fixed); + /* + Avoid crash in case of error. + TODO: what does this comment mean? + */ + prev_subselect_item->used_tables_cache|= (*ref)->used_tables(); + prev_subselect_item->const_item_cache&= (*ref)->const_item(); + break; } } - break; - } - // Reference is not found => depend from outer (or just error) - prev_subselect_item->used_tables_cache|= OUTER_REF_TABLE_BIT; - prev_subselect_item->const_item_cache= 0; + /* Search in the tables of the FROM clause of the outer select. */ + table_list= outer_sel->get_table_list(); + if (outer_sel->resolve_mode == SELECT_LEX::INSERT_MODE && table_list) + { + /* It is primary INSERT st_select_lex => skip the first table. */ + table_list= table_list->next_local; + } + enum_parsing_place place= prev_subselect_item->parsing_place; + /* + Check table fields only if the subquery is used somewhere out of + HAVING or SELECT list, or outer SELECT does not use grouping + (i.e. tables are accessible). + TODO: + Here we could first find the field anyway, and then test this + condition, so that we can give a better error message - + ER_WRONG_FIELD_WITH_GROUP, instead of the less informative + ER_BAD_FIELD_ERROR which we produce now. + */ + if (((place != IN_HAVING && place != SELECT_LIST) || + (!outer_sel->with_sum_func && + outer_sel->group_list.elements == 0))) + { + if ((tmp= find_field_in_tables(thd, this, table_list, reference, + IGNORE_EXCEPT_NON_UNIQUE, TRUE)) != + not_found_field) + { + if (tmp != view_ref_found) + { + prev_subselect_item->used_tables_cache|= tmp->table->map; + prev_subselect_item->const_item_cache= 0; + } + else + { + prev_subselect_item->used_tables_cache|= + (*reference)->used_tables(); + prev_subselect_item->const_item_cache&= + (*reference)->const_item(); + } + break; + } + } - if (outer_sel->master_unit()->first_select()->linkage == - DERIVED_TABLE_TYPE) - break; /* Do not consider derived tables. */ - } + /* Reference is not found => depend on outer (or just error). */ + prev_subselect_item->used_tables_cache|= OUTER_REF_TABLE_BIT; + prev_subselect_item->const_item_cache= 0; - if (!ref) - return TRUE; - else if (!tmp) - return TRUE; - else if (ref == (Item **)not_found_item && tmp == not_found_field) - { - if (upward_lookup) - { - /* We can't say exactly what was absent (a table or a field). */ - my_printf_error(ER_BAD_FIELD_ERROR, ER(ER_BAD_FIELD_ERROR), MYF(0), - full_name(), thd->where); - } - else - { - // Call to report error - find_item_in_list(this, *select_fields, &counter, - REPORT_ALL_ERRORS); - } - ref= 0; - return TRUE; - } - else if (tmp != not_found_field) - { - ref= 0; // To prevent "delete *ref;" on ~Item_ref() of this item - if (tmp != view_ref_found) - { - Item_field* fld; - if (!((*reference)= fld= new Item_field(tmp))) - return TRUE; - mark_as_dependent(thd, last, current_sel, fld); - register_item_tree_changing(reference); - return FALSE; - } - /* - We can leave expression substituted from view for next PS/SP - re-execution (i.e. do not register this substitution for reverting on - cleanup() (register_item_tree_changing())), because this subtree will - be fix_field'ed during setup_tables()->setup_ancestor() (i.e. before - all other expressions of query, and references on tables which do not - present in query will not make problems. + if (outer_sel->master_unit()->first_select()->linkage == + DERIVED_TABLE_TYPE) + break; /* Do not consider derived tables. */ + } - Also we suppose that view can't be changed during PS/SP life. - */ + DBUG_ASSERT(ref); + if (!tmp) + return TRUE; + else if (ref == not_found_item && tmp == not_found_field) + { + my_printf_error(ER_BAD_FIELD_ERROR, ER(ER_BAD_FIELD_ERROR), MYF(0), + this->full_name(), current_thd->where); + ref= 0; + return TRUE; + } + else if (tmp != not_found_field) + { + ref= 0; // To prevent "delete *ref;" on ~Item_ref() of this item + if (tmp != view_ref_found) + { + Item_field* fld= new Item_field(tmp); + if (!((*reference)= fld)) + return TRUE; + mark_as_dependent(thd, last, current_sel, fld); + register_item_tree_changing(reference); + return FALSE; + } + /* + We can leave expression substituted from view for next PS/SP + re-execution (i.e. do not register this substitution for reverting + on cleanup() (register_item_tree_changing())), because this subtree + will be fix_field'ed during setup_tables()->setup_ancestor() + (i.e. before all other expressions of query, and references on + tables which do not present in query will not make problems. + + Also we suppose that view can't be changed during PS/SP life. + */ + } + else + { + DBUG_ASSERT(*ref && (*ref)->fixed); + mark_as_dependent(thd, last, current_sel, this); + } } else { - if (!(*ref)->fixed) - { - my_error(ER_ILLEGAL_REFERENCE, MYF(0), name, - "forward reference in item list"); - return TRUE; - } - mark_as_dependent(thd, last, current_sel, - this); - ref= last->ref_pointer_array + counter; - } - } - else if (!group_by_ref && ref == (Item **) not_found_item) - { - my_printf_error(ER_BAD_FIELD_ERROR, ER(ER_BAD_FIELD_ERROR), MYF(0), - this->full_name(), current_thd->where); - return TRUE; - } - else - { - if (ref != (Item **) not_found_item && !ambiguous_fields) - ref= current_sel->ref_pointer_array + counter; - else if (group_by_ref) - ref= group_by_ref; - else - DBUG_ASSERT(FALSE); - - if (!(*ref)->fixed) - { - my_error(ER_ILLEGAL_REFERENCE, MYF(0), name, - "forward reference in item list"); - return TRUE; + /* The current reference cannot be resolved in this query. */ + my_printf_error(ER_BAD_FIELD_ERROR, ER(ER_BAD_FIELD_ERROR), MYF(0), + this->full_name(), current_thd->where); + return TRUE; } } } @@ -2625,15 +2672,18 @@ bool Item_ref::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) with sub-select's / derived tables, while it prevents this check when Item_ref is created in an expression involving summing function, which is to be placed in the user variable. + + TODO: this comment is impossible to understand. */ if (((*ref)->with_sum_func && name && (depended_from || - !(current_sel->linkage != GLOBAL_OPTIONS_TYPE && is_having_field))) || + !(current_sel->linkage != GLOBAL_OPTIONS_TYPE && + current_sel->having_fix_field))) || !(*ref)->fixed) { my_error(ER_ILLEGAL_REFERENCE, MYF(0), name, ((*ref)->with_sum_func? - "reference on group function": + "reference to group function": "forward reference in item list")); return TRUE; } diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 9f9b9b4ed63..e65cfaccad1 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -2186,10 +2186,9 @@ Field *find_field_in_real_table(THD *thd, TABLE *table, check_privileges need to check privileges RETURN VALUES - 0 No field was found, or the found field is not unique, or - there are no sufficient access priviliges for the - found field, or the field is qualified with non-existing - table. + 0 If error: the found field is not unique, or there are + no sufficient access priviliges for the found field, + or the field is qualified with non-existing table. not_found_field The function was called with report_error == (IGNORE_ERRORS || IGNORE_EXCEPT_NON_UNIQUE) and a field was not found. From 974c29b97b1ce2fe09693d4f711c54ae697fdb5b Mon Sep 17 00:00:00 2001 From: "timour@mysql.com" <> Date: Tue, 9 Nov 2004 17:56:33 +0200 Subject: [PATCH 5/8] WL#1972 "Evaluate HAVING before SELECT select-list" - post-review fix regarding Item_fields - added test for the changed name resolution --- mysql-test/r/having.result | 7 + mysql-test/t/having.test | 6 +- sql/item.cc | 604 +++++++++++++++++++------------------ 3 files changed, 318 insertions(+), 299 deletions(-) diff --git a/mysql-test/r/having.result b/mysql-test/r/having.result index 6f548d20668..86c9adf8cf6 100644 --- a/mysql-test/r/having.result +++ b/mysql-test/r/having.result @@ -245,6 +245,13 @@ col1 10 10 10 +select sum(col1) from t1 +group by col_t1 +having (select col_t1 from t2 where col_t1 = col_t2 order by col_t2 limit 1); +sum(col1) +40 +20 +30 select t1.col1 from t1 where t1.col2 in (select t2.col2 from t2 diff --git a/mysql-test/t/having.test b/mysql-test/t/having.test index d2a5ca4361e..b0fc600030b 100644 --- a/mysql-test/t/having.test +++ b/mysql-test/t/having.test @@ -211,6 +211,11 @@ where t1.col2 in (select t2.col2 from t2 group by t2.col1, t2.col2 having col_t1 <= 10); +# Item_field must be resolved in the same way as Item_ref +select sum(col1) from t1 +group by col_t1 +having (select col_t1 from t2 where col_t1 = col_t2 order by col_t2 limit 1); + # nested queries with HAVING, inner having column resolved in outer FROM clause # the outer having column is not referenced in GROUP BY which results in an error --error 1054 @@ -256,7 +261,6 @@ group by col_t1 having col_t1 in (select sum(t2.col1) from t2 group by t2.col2, t2.col1 having t2.col1 = col_t1); - # # queries with joins and ambiguous column names # diff --git a/sql/item.cc b/sql/item.cc index 8b611f0ddb5..919bce36280 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -1390,113 +1390,312 @@ static void mark_as_dependent(THD *thd, SELECT_LEX *last, SELECT_LEX *current, } -bool Item_field::fix_fields(THD *thd, TABLE_LIST *tables, Item **ref) +/* + Search a GROUP BY clause for a field with a certain name. + + SYNOPSIS + find_field_in_group_list() + find_item the item being searched for + group_list GROUP BY clause + + DESCRIPTION + Search the GROUP BY list for a column named as find_item. When searching + preference is given to columns that are qualified with the same table (and + database) name as the one being searched for. + + RETURN + - the found item on success + - NULL if find_item is not in group_list +*/ + +static Item** find_field_in_group_list(Item *find_item, ORDER *group_list) +{ + const char *db_name; + const char *table_name; + const char *field_name; + ORDER *found_group= NULL; + int found_match_degree= 0; + Item_field *cur_field; + int cur_match_degree= 0; + + if (find_item->type() == Item::FIELD_ITEM || + find_item->type() == Item::REF_ITEM) + { + db_name= ((Item_ident*) find_item)->db_name; + table_name= ((Item_ident*) find_item)->table_name; + field_name= ((Item_ident*) find_item)->field_name; + } + else + return NULL; + + DBUG_ASSERT(field_name); + + for (ORDER *cur_group= group_list ; cur_group ; cur_group= cur_group->next) + { + if ((*(cur_group->item))->type() == Item::FIELD_ITEM) + { + cur_field= (Item_field*) *cur_group->item; + cur_match_degree= 0; + + DBUG_ASSERT(cur_field->field_name); + + if (!my_strcasecmp(system_charset_info, + cur_field->field_name, field_name)) + ++cur_match_degree; + else + continue; + + if (cur_field->table_name && table_name) + { + /* If field_name is qualified by a table name. */ + if (strcmp(cur_field->table_name, table_name)) + /* Same field names, different tables. */ + return NULL; + + ++cur_match_degree; + if (cur_field->db_name && db_name) + { + /* If field_name is also qualified by a database name. */ + if (strcmp(cur_field->db_name, db_name)) + /* Same field names, different databases. */ + return NULL; + ++cur_match_degree; + } + } + + if (cur_match_degree > found_match_degree) + { + found_match_degree= cur_match_degree; + found_group= cur_group; + } + else if (found_group && (cur_match_degree == found_match_degree) && + ! (*(found_group->item))->eq(cur_field, 0)) + { + /* + If the current resolve candidate matches equally well as the current + best match, they must reference the same column, otherwise the field + is ambiguous. + */ + my_printf_error(ER_NON_UNIQ_ERROR, ER(ER_NON_UNIQ_ERROR), + MYF(0), find_item->full_name(), current_thd->where); + return NULL; + } + } + } + + if (found_group) + return found_group->item; + else + return NULL; +} + + +/* + Resolve a column reference in a sub-select. + + SYNOPSIS + resolve_ref_in_select_and_group() + thd current thread + ref column reference being resolved + select the sub-select that ref is resolved against + + DESCRIPTION + Resolve a column reference (usually inside a HAVING clause) against the + SELECT and GROUP BY clauses of the query described by 'select'. The name + resolution algorithm searches both the SELECT and GROUP BY clauses, and in + case of a name conflict prefers GROUP BY column names over SELECT names. If + both clauses contain different fields with the same names, a warning is + issued that name of 'ref' is ambiguous. We extend ANSI SQL in that when no + GROUP BY column is found, then a HAVING name is resolved as a possibly + derived SELECT column. + + NOTES + The resolution procedure is: + - Search for a column or derived column named col_ref_i [in table T_j] + in the SELECT clause of Q. + - Search for a column named col_ref_i [in table T_j] + in the GROUP BY clause of Q. + - If found different columns with the same name in GROUP BY and SELECT + - issue a warning and return the GROUP BY column, + - otherwise return the found SELECT column. + + + RETURN + NULL - there was an error, and the error was already reported + not_found_item - the item was not resolved, no error was reported + resolved item - if the item was resolved +*/ + +static Item** +resolve_ref_in_select_and_group(THD *thd, Item_ident *ref, SELECT_LEX *select) +{ + Item **group_by_ref= NULL; + Item **select_ref= NULL; + ORDER *group_list= (ORDER*) select->group_list.first; + bool ambiguous_fields= FALSE; + uint counter; + + /* + Search for a column or derived column named as 'ref' in the SELECT + clause of the current select. + */ + if (!(select_ref= find_item_in_list(ref, *(select->get_item_list()), &counter, + REPORT_EXCEPT_NOT_FOUND))) + return NULL; /* Some error occurred. */ + + /* If this is a non-aggregated field inside HAVING, search in GROUP BY. */ + if (select->having_fix_field && !ref->with_sum_func && group_list) + { + group_by_ref= find_field_in_group_list(ref, group_list); + + /* Check if the fields found in SELECT and GROUP BY are the same field. */ + if (group_by_ref && (select_ref != not_found_item) && + !((*group_by_ref)->eq(*select_ref, 0))) + { + ambiguous_fields= TRUE; + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_NON_UNIQ_ERROR, + ER(ER_NON_UNIQ_ERROR), ref->full_name(), + current_thd->where); + + } + } + + if (select_ref != not_found_item || group_by_ref) + { + if (select_ref != not_found_item && !ambiguous_fields) + { + if (*select_ref && !(*select_ref)->fixed) + { + my_error(ER_ILLEGAL_REFERENCE, MYF(0), ref->name, + "forward reference in item list"); + return NULL; + } + return (select->ref_pointer_array + counter); + } + else if (group_by_ref) + return group_by_ref; + else + DBUG_ASSERT(FALSE); + } + else + return (Item**) not_found_item; +} + + +bool Item_field::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) { DBUG_ASSERT(fixed == 0); if (!field) // If field is not checked { - bool upward_lookup= 0; - Field *tmp= (Field *)not_found_field; - if ((tmp= find_field_in_tables(thd, this, tables, ref, + bool upward_lookup= FALSE; + Field *from_field= (Field *)not_found_field; + if ((from_field= find_field_in_tables(thd, this, tables, reference, IGNORE_EXCEPT_NON_UNIQUE, !any_privileges)) == not_found_field) { - /* - We can't find table field in table list of current select, - consequently we have to find it in outer subselect(s). - We can't join lists of outer & current select, because of scope - of view rules. For example if both tables (outer & current) have - field 'field' it is not mistake to refer to this field without - mention of table name, but if we join tables in one list it will - cause error ER_NON_UNIQ_ERROR in find_field_in_tables. - */ - SELECT_LEX *last= 0; #ifdef EMBEDDED_LIBRARY thd->net.last_errno= 0; #endif + SELECT_LEX *last= 0; TABLE_LIST *table_list; - Item **refer= (Item **)not_found_item; - uint counter; - // Prevent using outer fields in subselects, that is not supported now - SELECT_LEX *cursel= (SELECT_LEX *) thd->lex->current_select; - if (cursel->master_unit()->first_select()->linkage != DERIVED_TABLE_TYPE) + Item **ref= (Item **) not_found_item; + SELECT_LEX *current_sel= (SELECT_LEX *) thd->lex->current_select; + /* + If there is an outer select, and it is not a derived table (which do + not support the use of outer fields for now), try to resolve this + reference in the outer select(s). + + We treat each subselect as a separate namespace, so that different + subselects may contain columns with the same names. The subselects are + searched starting from the innermost. + */ + if (current_sel->master_unit()->first_select()->linkage != + DERIVED_TABLE_TYPE) { - SELECT_LEX_UNIT *prev_unit= cursel->master_unit(); - for (SELECT_LEX *sl= prev_unit->outer_select(); - sl; - sl= (prev_unit= sl->master_unit())->outer_select()) + SELECT_LEX_UNIT *prev_unit= current_sel->master_unit(); + SELECT_LEX *outer_sel= prev_unit->outer_select(); + for ( ; outer_sel ; + outer_sel= (prev_unit= outer_sel->master_unit())->outer_select()) { - upward_lookup= 1; - table_list= (last= sl)->get_table_list(); - if (sl->resolve_mode == SELECT_LEX::INSERT_MODE && table_list) - { + last= outer_sel; + Item_subselect *prev_subselect_item= prev_unit->item; + upward_lookup= TRUE; + + /* Search in the tables of the FROM clause of the outer select. */ + table_list= outer_sel->get_table_list(); + if (outer_sel->resolve_mode == SELECT_LEX::INSERT_MODE && table_list) /* - it is primary INSERT st_select_lex => skip first table - resolving + It is a primary INSERT st_select_lex => do not resolve against the + first table. */ table_list= table_list->next_local; - } - Item_subselect *prev_subselect_item= prev_unit->item; enum_parsing_place place= prev_subselect_item->parsing_place; /* - check table fields only if subquery used somewhere out of HAVING - or SELECT list or outer SELECT do not use groupping (i.e. tables - are accessable) + Check table fields only if the subquery is used somewhere out of + HAVING or SELECT list, or the outer SELECT does not use grouping + (i.e. tables are accessible). */ - if (((place != IN_HAVING && - place != SELECT_LIST) || - (sl->with_sum_func == 0 && sl->group_list.elements == 0)) && - (tmp= find_field_in_tables(thd, this, - table_list, ref, - IGNORE_EXCEPT_NON_UNIQUE, 1)) != + if (((place != IN_HAVING && place != SELECT_LIST) || + (outer_sel->with_sum_func == 0 && + outer_sel->group_list.elements == 0)) && + (from_field= find_field_in_tables(thd, this, table_list, + reference, + IGNORE_EXCEPT_NON_UNIQUE, + TRUE)) != not_found_field) { - if (tmp) + if (from_field) { - if (tmp != view_ref_found) + if (from_field != view_ref_found) { - prev_subselect_item->used_tables_cache|= tmp->table->map; + prev_subselect_item->used_tables_cache|= from_field->table->map; prev_subselect_item->const_item_cache= 0; } else { prev_subselect_item->used_tables_cache|= - (*ref)->used_tables(); + (*reference)->used_tables(); prev_subselect_item->const_item_cache&= - (*ref)->const_item(); + (*reference)->const_item(); } } break; } - if (sl->resolve_mode == SELECT_LEX::SELECT_MODE && - (refer= find_item_in_list(this, sl->item_list, &counter, - REPORT_EXCEPT_NOT_FOUND)) != - (Item **) not_found_item) + + /* Search in the SELECT and GROUP lists of the outer select. */ + if (outer_sel->resolve_mode == SELECT_LEX::SELECT_MODE) { - if (*refer && (*refer)->fixed) // Avoid crash in case of error - { - prev_subselect_item->used_tables_cache|= (*refer)->used_tables(); - prev_subselect_item->const_item_cache&= (*refer)->const_item(); - } - break; + if (!(ref= resolve_ref_in_select_and_group(thd, this, outer_sel))) + return TRUE; /* Some error occured (e.g. ambigous names). */ + if (ref != not_found_item) + { + DBUG_ASSERT(*ref && (*ref)->fixed); + /* + Avoid crash in case of error. + TODO: what does this comment mean? + */ + prev_subselect_item->used_tables_cache|= (*ref)->used_tables(); + prev_subselect_item->const_item_cache&= (*ref)->const_item(); + break; + } } // Reference is not found => depend from outer (or just error) prev_subselect_item->used_tables_cache|= OUTER_REF_TABLE_BIT; prev_subselect_item->const_item_cache= 0; - if (sl->master_unit()->first_select()->linkage == + if (outer_sel->master_unit()->first_select()->linkage == DERIVED_TABLE_TYPE) break; // do not look over derived table } } - if (!tmp) - return -1; - if (!refer) - return 1; - if (tmp == not_found_field && refer == (Item **)not_found_item) + + DBUG_ASSERT(ref); + if (!from_field) + return TRUE; + if (ref == not_found_item && from_field == not_found_field) { if (upward_lookup) { @@ -1507,59 +1706,54 @@ bool Item_field::fix_fields(THD *thd, TABLE_LIST *tables, Item **ref) else { // Call to report error - find_field_in_tables(thd, this, tables, ref, REPORT_ALL_ERRORS, 1); + find_field_in_tables(thd, this, tables, reference, REPORT_ALL_ERRORS, + TRUE); } - return -1; + return TRUE; } - else if (refer != (Item **)not_found_item) + else if (ref != not_found_item) { - if (!(*refer)->fixed) - { - my_error(ER_ILLEGAL_REFERENCE, MYF(0), name, - "forward reference in item list"); - return -1; - } + /* Should be checked in resolve_ref_in_select_and_group(). */ + DBUG_ASSERT(*ref && (*ref)->fixed); Item_ref *rf; - *ref= rf= new Item_ref(last->ref_pointer_array + counter, - ref, - (char *)table_name, - (char *)field_name); - register_item_tree_changing(ref); + *reference= rf= new Item_ref(ref, reference, (char *) table_name, + (char *) field_name); + register_item_tree_changing(reference); if (!rf) - return 1; + return TRUE; /* rf is Item_ref => never substitute other items (in this case) during fix_fields() => we can use rf after fix_fields() */ - if (rf->fix_fields(thd, tables, ref) || rf->check_cols(1)) - return 1; + if (rf->fix_fields(thd, tables, reference) || rf->check_cols(1)) + return TRUE; - mark_as_dependent(thd, last, cursel, rf); - return 0; + mark_as_dependent(thd, last, current_sel, rf); + return FALSE; } else { - mark_as_dependent(thd, last, cursel, this); + mark_as_dependent(thd, last, current_sel, this); if (last->having_fix_field) { Item_ref *rf; - *ref= rf= new Item_ref(ref, *ref, - (cached_table->db[0]?cached_table->db:0), - (char *)cached_table->alias, - (char *)field_name); + *reference= rf= new Item_ref(reference, *reference, + (cached_table->db[0]?cached_table->db:0), + (char *)cached_table->alias, + (char *)field_name); if (!rf) - return 1; + return TRUE; /* rf is Item_ref => never substitute other items (in this case) during fix_fields() => we can use rf after fix_fields() */ - return rf->fix_fields(thd, tables, ref) || rf->check_cols(1); + return rf->fix_fields(thd, tables, reference) || rf->check_cols(1); } } } - else if (!tmp) - return -1; + else if (!from_field) + return TRUE; /* if it is not expression from merged VIEW we will set this field. @@ -1573,8 +1767,8 @@ bool Item_field::fix_fields(THD *thd, TABLE_LIST *tables, Item **ref) Also we suppose that view can't be changed during PS/SP life. */ - if (tmp != view_ref_found) - set_field(tmp); + if (from_field != view_ref_found) + set_field(from_field); } else if (thd->set_query_id && field->query_id != thd->query_id) { @@ -1610,12 +1804,12 @@ bool Item_field::fix_fields(THD *thd, TABLE_LIST *tables, Item **ref) thd->host_or_ip, field_name, tab); - return 1; + return TRUE; } } #endif fixed= 1; - return 0; + return FALSE; } void Item_field::cleanup() @@ -2265,197 +2459,6 @@ bool Item_field::send(Protocol *protocol, String *buffer) } -/* - Search a GROUP BY clause for a field with a certain name. - - SYNOPSIS - find_field_in_group_list() - find_item the item being searched for - group_list GROUP BY clause - - DESCRIPTION - Search the GROUP BY list for a column named as find_item. When searching - preference is given to columns that are qualified with the same table (and - database) name as the one being searched for. - - RETURN - - the found item on success - - NULL if find_item is not in group_list -*/ - -static Item** find_field_in_group_list(Item *find_item, ORDER *group_list) -{ - const char *db_name; - const char *table_name; - const char *field_name; - ORDER *found_group= NULL; - int found_match_degree= 0; - Item_field *cur_field; - int cur_match_degree= 0; - - if (find_item->type() == Item::FIELD_ITEM || - find_item->type() == Item::REF_ITEM) - { - db_name= ((Item_ident*) find_item)->db_name; - table_name= ((Item_ident*) find_item)->table_name; - field_name= ((Item_ident*) find_item)->field_name; - } - else - return NULL; - - DBUG_ASSERT(field_name); - - for (ORDER *cur_group= group_list ; cur_group ; cur_group= cur_group->next) - { - if ((*(cur_group->item))->type() == Item::FIELD_ITEM) - { - cur_field= (Item_field*) *cur_group->item; - cur_match_degree= 0; - - DBUG_ASSERT(cur_field->field_name); - - if (!my_strcasecmp(system_charset_info, - cur_field->field_name, field_name)) - ++cur_match_degree; - else - continue; - - if (cur_field->table_name && table_name) - { - /* If field_name is qualified by a table name. */ - if (strcmp(cur_field->table_name, table_name)) - /* Same field names, different tables. */ - return NULL; - - ++cur_match_degree; - if (cur_field->db_name && db_name) - { - /* If field_name is also qualified by a database name. */ - if (strcmp(cur_field->db_name, db_name)) - /* Same field names, different databases. */ - return NULL; - ++cur_match_degree; - } - } - - if (cur_match_degree > found_match_degree) - { - found_match_degree= cur_match_degree; - found_group= cur_group; - } - else if (found_group && (cur_match_degree == found_match_degree) && - ! (*(found_group->item))->eq(cur_field, 0)) - { - /* - If the current resolve candidate matches equally well as the current - best match, they must reference the same column, otherwise the field - is ambiguous. - */ - my_printf_error(ER_NON_UNIQ_ERROR, ER(ER_NON_UNIQ_ERROR), - MYF(0), find_item->full_name(), current_thd->where); - return NULL; - } - } - } - - if (found_group) - return found_group->item; - else - return NULL; -} - - -/* - Resolve a column reference in a sub-select. - - SYNOPSIS - resolve_ref_in_select_and_group() - thd current thread - ref column reference being resolved - select the sub-select that ref is resolved against - - DESCRIPTION - Resolve a column reference (usually inside a HAVING clause) against the - SELECT and GROUP BY clauses of the query described by 'select'. The name - resolution algorithm searches both the SELECT and GROUP BY clauses, and in - case of a name conflict prefers GROUP BY column names over SELECT names. If - both clauses contain different fields with the same names, a warning is - issued that name of 'ref' is ambiguous. We extend ANSI SQL in that when no - GROUP BY column is found, then a HAVING name is resolved as a possibly - derived SELECT column. - - NOTES - The resolution procedure is: - - Search for a column or derived column named col_ref_i [in table T_j] - in the SELECT clause of Q. - - Search for a column named col_ref_i [in table T_j] - in the GROUP BY clause of Q. - - If found different columns with the same name in GROUP BY and SELECT - - issue a warning and return the GROUP BY column, - - otherwise return the found SELECT column. - - - RETURN - NULL - there was an error, and the error was already reported - not_found_item - the item was not resolved, no error was reported - resolved item - if the item was resolved -*/ -static Item** -resolve_ref_in_select_and_group(THD *thd, Item_ref *ref, SELECT_LEX *select) -{ - Item **group_by_ref= NULL; - Item **select_ref= NULL; - ORDER *group_list= (ORDER*) select->group_list.first; - bool ambiguous_fields= FALSE; - uint counter; - - /* - Search for a column or derived column named as 'ref' in the SELECT - clause of the current select. - */ - if (!(select_ref= find_item_in_list(ref, *(select->get_item_list()), &counter, - REPORT_EXCEPT_NOT_FOUND))) - return NULL; /* Some error occurred. */ - - /* If this is a non-aggregated field inside HAVING, search in GROUP BY. */ - if (select->having_fix_field && !ref->with_sum_func && group_list) - { - group_by_ref= find_field_in_group_list(ref, group_list); - - /* Check if the fields found in SELECT and GROUP BY are the same field. */ - if (group_by_ref && (select_ref != not_found_item) && - !((*group_by_ref)->eq(*select_ref, 0))) - { - ambiguous_fields= TRUE; - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_NON_UNIQ_ERROR, - ER(ER_NON_UNIQ_ERROR), ref->full_name(), - current_thd->where); - - } - } - - if (select_ref != not_found_item || group_by_ref) - { - if (select_ref != not_found_item && !ambiguous_fields) - { - if (*select_ref && !(*select_ref)->fixed) - { - my_error(ER_ILLEGAL_REFERENCE, MYF(0), ref->name, - "forward reference in item list"); - return NULL; - } - return (select->ref_pointer_array + counter); - } - else if (group_by_ref) - return group_by_ref; - else - DBUG_ASSERT(FALSE); - } - else - return (Item**) not_found_item; -} - - /* Resolve the name of a column reference. @@ -2539,7 +2542,7 @@ bool Item_ref::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) DERIVED_TABLE_TYPE)) { TABLE_LIST *table_list; - Field *tmp= (Field*) not_found_field; + Field *from_field= (Field*) not_found_field; SELECT_LEX *last= 0; for ( ; outer_sel ; @@ -2569,14 +2572,16 @@ bool Item_ref::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) /* Search in the tables of the FROM clause of the outer select. */ table_list= outer_sel->get_table_list(); if (outer_sel->resolve_mode == SELECT_LEX::INSERT_MODE && table_list) - { - /* It is primary INSERT st_select_lex => skip the first table. */ + /* + It is a primary INSERT st_select_lex => do not resolve against the + first table. + */ table_list= table_list->next_local; - } + enum_parsing_place place= prev_subselect_item->parsing_place; /* Check table fields only if the subquery is used somewhere out of - HAVING or SELECT list, or outer SELECT does not use grouping + HAVING or SELECT list, or the outer SELECT does not use grouping (i.e. tables are accessible). TODO: Here we could first find the field anyway, and then test this @@ -2588,13 +2593,15 @@ bool Item_ref::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) (!outer_sel->with_sum_func && outer_sel->group_list.elements == 0))) { - if ((tmp= find_field_in_tables(thd, this, table_list, reference, - IGNORE_EXCEPT_NON_UNIQUE, TRUE)) != + if ((from_field= find_field_in_tables(thd, this, table_list, + reference, + IGNORE_EXCEPT_NON_UNIQUE, + TRUE)) != not_found_field) { - if (tmp != view_ref_found) + if (from_field != view_ref_found) { - prev_subselect_item->used_tables_cache|= tmp->table->map; + prev_subselect_item->used_tables_cache|= from_field->table->map; prev_subselect_item->const_item_cache= 0; } else @@ -2618,21 +2625,21 @@ bool Item_ref::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) } DBUG_ASSERT(ref); - if (!tmp) + if (!from_field) return TRUE; - else if (ref == not_found_item && tmp == not_found_field) + if (ref == not_found_item && from_field == not_found_field) { my_printf_error(ER_BAD_FIELD_ERROR, ER(ER_BAD_FIELD_ERROR), MYF(0), this->full_name(), current_thd->where); ref= 0; return TRUE; } - else if (tmp != not_found_field) + else if (from_field != not_found_field) { ref= 0; // To prevent "delete *ref;" on ~Item_ref() of this item - if (tmp != view_ref_found) + if (from_field != view_ref_found) { - Item_field* fld= new Item_field(tmp); + Item_field* fld= new Item_field(from_field); if (!((*reference)= fld)) return TRUE; mark_as_dependent(thd, last, current_sel, fld); @@ -2652,6 +2659,7 @@ bool Item_ref::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) } else { + /* Should be checked in resolve_ref_in_select_and_group(). */ DBUG_ASSERT(*ref && (*ref)->fixed); mark_as_dependent(thd, last, current_sel, this); } From 866abfddf45b16d3087fad35a4fe527a6323b662 Mon Sep 17 00:00:00 2001 From: "timour@mysql.com" <> Date: Wed, 10 Nov 2004 12:43:08 +0200 Subject: [PATCH 6/8] WL#1972 "Evaluate HAVING before SELECT select-list" - post-review changes --- sql/item.cc | 86 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 65 insertions(+), 21 deletions(-) diff --git a/sql/item.cc b/sql/item.cc index 919bce36280..12b0f7b0796 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -1564,7 +1564,8 @@ resolve_ref_in_select_and_group(THD *thd, Item_ident *ref, SELECT_LEX *select) { if (select_ref != not_found_item && !ambiguous_fields) { - if (*select_ref && !(*select_ref)->fixed) + DBUG_ASSERT(*select_ref); + if (! (*select_ref)->fixed) { my_error(ER_ILLEGAL_REFERENCE, MYF(0), ref->name, "forward reference in item list"); @@ -1582,6 +1583,58 @@ resolve_ref_in_select_and_group(THD *thd, Item_ident *ref, SELECT_LEX *select) } +/* + Resolve the name of a column reference. + + SYNOPSIS + Item_field::fix_fields() + thd [in] current thread + tables [in] the tables in a FROM clause + reference [in/out] view column if this item was resolved to a view column + + DESCRIPTION + The method resolves the column reference represented by 'this' as a column + present in one of: FROM clause, SELECT clause, GROUP BY clause of a query + Q, or in outer queries that contain Q. + + NOTES + The name resolution algorithm used is (where [T_j] is an optional table + name that qualifies the column name): + + resolve_column_reference([T_j].col_ref_i) + { + search for a column or derived column named col_ref_i + [in table T_j] in the FROM clause of Q; + + if such a column is NOT found AND // Lookup in outer queries. + there are outer queries + { + for each outer query Q_k beginning from the inner-most one + { + if - Q_k is not a group query AND + - Q_k is not inside an aggregate function + OR + - Q_(k-1) is not in a HAVING or SELECT clause of Q_k + { + search for a column or derived column named col_ref_i + [in table T_j] in the FROM clause of Q_k; + } + + if such a column is not found + Search for a column or derived column named col_ref_i + [in table T_j] in the SELECT and GROUP clauses of Q_k. + } + } + } + + Notice that compared to Item_ref::fix_fields, here we first search the FROM + clause, and then we search the SELECT and GROUP BY clauses. + + RETURN + TRUE if error + FALSE on success +*/ + bool Item_field::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) { DBUG_ASSERT(fixed == 0); @@ -1672,10 +1725,6 @@ bool Item_field::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) if (ref != not_found_item) { DBUG_ASSERT(*ref && (*ref)->fixed); - /* - Avoid crash in case of error. - TODO: what does this comment mean? - */ prev_subselect_item->used_tables_cache|= (*ref)->used_tables(); prev_subselect_item->const_item_cache&= (*ref)->const_item(); break; @@ -1699,7 +1748,7 @@ bool Item_field::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) { if (upward_lookup) { - // We can't say exactly what absend table or field + // We can't say exactly what absent table or field my_printf_error(ER_BAD_FIELD_ERROR, ER(ER_BAD_FIELD_ERROR), MYF(0), full_name(), thd->where); } @@ -2460,12 +2509,12 @@ bool Item_field::send(Protocol *protocol, String *buffer) /* - Resolve the name of a column reference. + Resolve the name of a reference to a column reference. SYNOPSIS Item_ref::fix_fields() thd [in] current thread - tables [in] the tables in the FROM clause + tables [in] the tables in a FROM clause reference [in/out] view column if this item was resolved to a view column DESCRIPTION @@ -2475,7 +2524,8 @@ bool Item_field::send(Protocol *protocol, String *buffer) aggregate functions. NOTES - The name resolution algorithm used is: + The name resolution algorithm used is (where [T_j] is an optional table + name that qualifies the column name): resolve_extended([T_j].col_ref_i) { @@ -2504,7 +2554,9 @@ bool Item_field::send(Protocol *protocol, String *buffer) } This procedure treats GROUP BY and SELECT clauses as one namespace for - column references in HAVING. + column references in HAVING. Notice that compared to + Item_field::fix_fields, here we first search the SELECT and GROUP BY + clauses, and then we search the FROM clause. RETURN TRUE if error @@ -2559,10 +2611,6 @@ bool Item_ref::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) if (ref != not_found_item) { DBUG_ASSERT(*ref && (*ref)->fixed); - /* - Avoid crash in case of error. - TODO: what does this comment mean? - */ prev_subselect_item->used_tables_cache|= (*ref)->used_tables(); prev_subselect_item->const_item_cache&= (*ref)->const_item(); break; @@ -2675,13 +2723,9 @@ bool Item_ref::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) } /* - The following conditional is changed as to correctly identify - incorrect references in group functions or forward references - with sub-select's / derived tables, while it prevents this - check when Item_ref is created in an expression involving - summing function, which is to be placed in the user variable. - - TODO: this comment is impossible to understand. + Check if this is an incorrect reference in a group function or forward + reference. Do not issue an error if this is an unnamed reference inside an + aggregate function. */ if (((*ref)->with_sum_func && name && (depended_from || From 71c4cc4ae6e8463b46676451f330fcb3a0cd8380 Mon Sep 17 00:00:00 2001 From: "timour@mysql.com" <> Date: Thu, 11 Nov 2004 10:09:50 +0200 Subject: [PATCH 7/8] WL#1972 - manual merge with latest bk source --- sql/sql_select.cc | 474 +++++++++++++++++++++++++++------------------- 1 file changed, 277 insertions(+), 197 deletions(-) diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 178d36faa2a..924b74e2e6e 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -91,7 +91,7 @@ static int return_zero_rows(JOIN *join, select_result *res,TABLE_LIST *tables, uint select_options, const char *info, Item *having, Procedure *proc, SELECT_LEX_UNIT *unit); -static COND *build_equal_items(COND *cond, +static COND *build_equal_items(THD *thd, COND *cond, COND_EQUAL *inherited, List *join_list, COND_EQUAL **cond_equal_ref); @@ -101,6 +101,7 @@ static COND* substitute_for_best_equal_field(COND *cond, static COND *simplify_joins(JOIN *join, List *join_list, COND *conds, bool top); static COND *optimize_cond(JOIN *join, COND *conds, + List *join_list, Item::cond_result *cond_value); static bool resolve_nested_join (TABLE_LIST *table); static COND *remove_eq_conds(THD *thd, COND *cond, @@ -191,6 +192,7 @@ static void init_tmptable_sum_functions(Item_sum **func); static void update_tmptable_sum_func(Item_sum **func,TABLE *tmp_table); static void copy_sum_funcs(Item_sum **func_ptr); static bool add_ref_to_table_cond(THD *thd, JOIN_TAB *join_tab); +static bool setup_sum_funcs(THD *thd, Item_sum **func_ptr); static bool init_sum_functions(Item_sum **func, Item_sum **end); static bool update_sum_func(Item_sum **func); static void select_describe(JOIN *join, bool need_tmp_table,bool need_order, @@ -228,16 +230,11 @@ int handle_select(THD *thd, LEX *lex, select_result *result) select_lex->options | thd->options, result, unit, select_lex); } - - /* Don't set res if it's -1 as we may want this later */ DBUG_PRINT("info",("res: %d report_error: %d", res, thd->net.report_error)); - if (thd->net.report_error) - res= 1; - if (unlikely(res)) + if (thd->net.report_error || res < 0) { - if (res > 0) - result->send_error(0, NullS); + result->send_error(0, NullS); result->abort(); res= 1; // Error sent to client } @@ -333,7 +330,7 @@ JOIN::prepare(Item ***rref_pointer_array, if (having_fix_rc || thd->net.report_error) DBUG_RETURN(-1); /* purecov: inspected */ if (having->with_sum_func) - having->split_sum_func(ref_pointer_array, all_fields); + having->split_sum_func(thd, ref_pointer_array, all_fields); } if (!thd->lex->view_prepare_mode) @@ -558,17 +555,7 @@ JOIN::optimize() thd->restore_backup_item_arena(arena, &backup); } - /* - Build all multiple equality predicates and eliminate equality - predicates that can be inferred from these multiple equalities. - For each reference of a field included into a multiple equality - that occurs in a function set a pointer to the multiple equality - predicate. Substitute a constant instead of this field if the - multiple equality contains a constant. - */ - conds= build_equal_items(conds, NULL, join_list, &cond_equal); - - conds= optimize_cond(this, conds,&cond_value); + conds= optimize_cond(this, conds, join_list, &cond_value); if (thd->net.report_error) { error= 1; @@ -686,6 +673,7 @@ JOIN::optimize() { conds= substitute_for_best_equal_field(conds, cond_equal, map2table); conds->update_used_tables(); + DBUG_EXECUTE("where", print_where(conds, "after substitute_best_equal");); } /* Permorm the the optimization on fields evaluation mentioned above @@ -1003,13 +991,15 @@ JOIN::optimize() if (create_sort_index(thd, this, group_list, HA_POS_ERROR, HA_POS_ERROR) || alloc_group_fields(this, group_list) || - make_sum_func_list(all_fields, fields_list, 1)) + make_sum_func_list(all_fields, fields_list, 1) || + setup_sum_funcs(thd, sum_funcs)) DBUG_RETURN(1); group_list=0; } else { - if (make_sum_func_list(all_fields, fields_list, 0)) + if (make_sum_func_list(all_fields, fields_list, 0) || + setup_sum_funcs(thd, sum_funcs)) DBUG_RETURN(1); if (!group_list && ! exec_tmp_table1->distinct && order && simple_order) { @@ -1385,6 +1375,7 @@ JOIN::exec() } if (curr_join->make_sum_func_list(*curr_all_fields, *curr_fields_list, 1, TRUE) || + setup_sum_funcs(curr_join->thd, curr_join->sum_funcs) || (tmp_error= do_select(curr_join, (List *) 0, curr_tmp_table, 0))) { @@ -1472,7 +1463,9 @@ JOIN::exec() set_items_ref_array(items3); if (curr_join->make_sum_func_list(*curr_all_fields, *curr_fields_list, - 1, TRUE) || thd->is_fatal_error) + 1, TRUE) || + setup_sum_funcs(curr_join->thd, curr_join->sum_funcs) || + thd->is_fatal_error) DBUG_VOID_RETURN; } if (curr_join->group_list || curr_join->order) @@ -1665,9 +1658,13 @@ Cursor::init_from_thd(THD *thd) /* We need to save and reset thd->mem_root, otherwise it'll be freed later in mysql_parse. + + We can't just change the thd->mem_root here as we want to keep the things + that is already allocated in thd->mem_root for Cursor::fetch() */ - mem_root= thd->mem_root; - init_sql_alloc(&thd->mem_root, + main_mem_root= *thd->mem_root; + /* Allocate new memory root for thd */ + init_sql_alloc(thd->mem_root, thd->variables.query_alloc_block_size, thd->variables.query_prealloc_size); @@ -1681,7 +1678,7 @@ Cursor::init_from_thd(THD *thd) open_tables= thd->open_tables; lock= thd->lock; query_id= thd->query_id; - free_list= thd->free_list; + free_list= thd->free_list; reset_thd(thd); /* XXX: thd->locked_tables is not changed. @@ -1696,8 +1693,6 @@ Cursor::init_from_thd(THD *thd) void Cursor::init_thd(THD *thd) { - thd->mem_root= mem_root; - DBUG_ASSERT(thd->derived_tables == 0); thd->derived_tables= derived_tables; @@ -1707,7 +1702,6 @@ Cursor::init_thd(THD *thd) DBUG_ASSERT(thd->lock== 0); thd->lock= lock; thd->query_id= query_id; - thd->free_list= free_list; } @@ -1725,11 +1719,10 @@ int Cursor::open(JOIN *join_arg) { join= join_arg; - THD *thd= join->thd; - /* First non-constant table */ JOIN_TAB *join_tab= join->join_tab + join->const_tables; + DBUG_ENTER("Cursor::open"); /* Send fields description to the client; server_status is sent @@ -1751,7 +1744,9 @@ Cursor::open(JOIN *join_arg) join->fetch_limit= join->unit->offset_limit_cnt; /* Disable JOIN CACHE as it is not working with cursors yet */ - for (JOIN_TAB *tab= join_tab; tab != join->join_tab + join->tables - 1; ++tab) + for (JOIN_TAB *tab= join_tab; + tab != join->join_tab + join->tables - 1; + tab++) { if (tab->next_select == sub_select_cache) tab->next_select= sub_select; @@ -1765,7 +1760,7 @@ Cursor::open(JOIN *join_arg) */ DBUG_ASSERT(join_tab->table->null_row == 0); - return join_tab->read_first_record(join_tab); + DBUG_RETURN(join_tab->read_first_record(join_tab)); } @@ -1784,13 +1779,15 @@ int Cursor::fetch(ulong num_rows) { 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; COND *select_cond= join_tab->select_cond; READ_RECORD *info= &join_tab->read_record; - int error= 0; + /* save references to memory, allocated during fetch */ + thd->set_n_backup_item_arena(this, &thd->stmt_backup); + join->fetch_limit+= num_rows; /* @@ -1855,53 +1852,37 @@ Cursor::fetch(ulong num_rows) if (thd->net.report_error) error= -1; - switch (error) { - /* Fetch limit worked, possibly more rows are there */ - case -4: + if (error == -3) /* LIMIT clause worked */ + error= 0; + +#ifdef USING_TRANSACTIONS if (thd->transaction.all.innobase_tid) ha_release_temporary_latches(thd); +#endif + + thd->restore_backup_item_arena(this, &thd->stmt_backup); + if (error == -4) + { + /* Fetch limit worked, possibly more rows are there */ thd->server_status|= SERVER_STATUS_CURSOR_EXISTS; ::send_eof(thd); thd->server_status&= ~SERVER_STATUS_CURSOR_EXISTS; - /* save references to memory, allocated during fetch */ - mem_root= thd->mem_root; - free_list= thd->free_list; - break; - /* Limit clause worked: this is the same as 'no more rows' */ - case -3: /* LIMIT clause worked */ - error= 0; - /* fallthrough */ - case 0: /* No more rows */ - if (thd->transaction.all.innobase_tid) - ha_release_temporary_latches(thd); + } + else + { close(); - thd->server_status|= SERVER_STATUS_LAST_ROW_SENT; - ::send_eof(thd); - thd->server_status&= ~SERVER_STATUS_LAST_ROW_SENT; - join= 0; - unit= 0; - free_items(thd->free_list); - thd->free_list= free_list= 0; - /* - Must be last, as some memory might be allocated for free purposes, - like in free_tmp_table() (TODO: fix this issue) - */ - mem_root= thd->mem_root; - free_root(&mem_root, MYF(0)); - break; - default: - close(); - join= 0; - unit= 0; - free_items(thd->free_list); - thd->free_list= free_list= 0; - /* - Must be last, as some memory might be allocated for free purposes, - like in free_tmp_table() (TODO: fix this issue) - */ - mem_root= thd->mem_root; - free_root(&mem_root, MYF(0)); - break; + if (error == 0) + { + thd->server_status|= SERVER_STATUS_LAST_ROW_SENT; + ::send_eof(thd); + thd->server_status&= ~SERVER_STATUS_LAST_ROW_SENT; + } + else + send_error(thd, ER_OUT_OF_RESOURCES); + /* free cursor memory */ + free_items(free_list); + free_list= 0; + free_root(&main_mem_root, MYF(0)); } return error; } @@ -1939,6 +1920,8 @@ Cursor::close() thd->derived_tables= tmp_derived_tables; thd->lock= tmp_lock; } + join= 0; + unit= 0; } @@ -1951,7 +1934,7 @@ Cursor::~Cursor() Must be last, as some memory might be allocated for free purposes, like in free_tmp_table() (TODO: fix this issue) */ - free_root(&mem_root, MYF(0)); + free_root(&main_mem_root, MYF(0)); } /*********************************************************************/ @@ -5030,7 +5013,7 @@ add_found_match_trig_cond(JOIN_TAB *tab, COND *cond, JOIN_TAB *root_tab) { tmp= new Item_func_trig_cond(tmp, &tab->found); } - if (!tmp) + if (tmp) tmp->quick_fix_field(); return tmp; } @@ -5304,8 +5287,17 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) /* Join with outer join condition */ COND *orig_cond=sel->cond; sel->cond= and_conds(sel->cond, *tab->on_expr_ref); + + /* + We can't call sel->cond->fix_fields, + as it will break tab->on_expr if it's AND condition + (fix_fields currently removes extra AND/OR levels). + Yet attributes of the just built condition are not needed. + Thus we call sel->cond->quick_fix_field for safety. + */ if (sel->cond && !sel->cond->fixed) - sel->cond->fix_fields(join->thd, 0, &sel->cond); + sel->cond->quick_fix_field(); + if (sel->test_quick_select(join->thd, tab->keys, used_tables & ~ current_map, (join->select_options & @@ -6030,7 +6022,7 @@ template class List_iterator; find_item_equal() cond_equal multiple equalities to search in field field to look for - inherited_fl :out set up to TRUE iff multiple equality is found + inherited_fl :out set up to TRUE if multiple equality is found on upper levels (not on current level of cond_equal) DESCRIPTION @@ -6448,12 +6440,14 @@ static COND *build_equal_items_for_cond(COND *cond, return cond; } + /* Build multiple equalities for a condition and all on expressions that inherit these multiple equalities SYNOPSIS build_equal_items() + thd Thread handler cond condition to build the multiple equalities for inherited path to all inherited multiple equality items join_list list of join tables to which the condition refers to @@ -6505,7 +6499,7 @@ static COND *build_equal_items_for_cond(COND *cond, pointer to the transformed condition containing multiple equalities */ -static COND *build_equal_items(COND *cond, +static COND *build_equal_items(THD *thd, COND *cond, COND_EQUAL *inherited, List *join_list, COND_EQUAL **cond_equal_ref) @@ -6542,12 +6536,13 @@ static COND *build_equal_items(COND *cond, { if (table->on_expr) { + Item *expr; List *join_list= table->nested_join ? &table->nested_join->join_list : NULL; - table->on_expr= build_equal_items(table->on_expr, - inherited, - join_list, - &table->cond_equal); + expr= build_equal_items(thd, table->on_expr, inherited, join_list, + &table->cond_equal); + if (expr != table->on_expr) + thd->change_item_tree(&table->on_expr, expr); } } } @@ -6555,6 +6550,7 @@ static COND *build_equal_items(COND *cond, return cond; } + /* Compare field items by table order in the execution plan @@ -6649,10 +6645,7 @@ static Item *eliminate_item_equal(COND *cond, COND_EQUAL *upper_levels, List eq_list; Item_func_eq *eq_item= 0; if (((Item *) item_equal)->const_item() && !item_equal->val_int()) - { - cond= new Item_int((char*) "FALSE",0,1); - return cond; - } + return new Item_int((longlong) 0,1); Item *item_const= item_equal->get_const(); Item_equal_iterator it(*item_equal); Item *head; @@ -6695,9 +6688,14 @@ static Item *eliminate_item_equal(COND *cond, COND_EQUAL *upper_levels, } if (!cond && !eq_list.head()) + { + if (!eq_item) + return new Item_int((longlong) 1,1); return eq_item; + } - eq_list.push_back(eq_item); + if (eq_item) + eq_list.push_back(eq_item); if (!cond) cond= new Item_cond_and(eq_list); else @@ -6799,14 +6797,16 @@ static COND* substitute_for_best_equal_field(COND *cond, return cond; } + /* change field = field to field = const for each found field = const in the and_level */ static void -change_cond_ref_to_const(I_List *save_list,Item *and_father, - Item *cond, Item *field, Item *value) +change_cond_ref_to_const(THD *thd, I_List *save_list, + Item *and_father, Item *cond, + Item *field, Item *value) { if (cond->type() == Item::COND_ITEM) { @@ -6815,7 +6815,7 @@ change_cond_ref_to_const(I_List *save_list,Item *and_father, List_iterator li(*((Item_cond*) cond)->argument_list()); Item *item; while ((item=li++)) - change_cond_ref_to_const(save_list,and_level ? cond : item, item, + change_cond_ref_to_const(thd, save_list,and_level ? cond : item, item, field, value); return; } @@ -6823,8 +6823,9 @@ change_cond_ref_to_const(I_List *save_list,Item *and_father, return; // Not a boolean function Item_bool_func2 *func= (Item_bool_func2*) cond; - Item *left_item= func->arguments()[0]; - Item *right_item= func->arguments()[1]; + Item **args= func->arguments(); + Item *left_item= args[0]; + Item *right_item= args[1]; Item_func::Functype functype= func->functype(); if (right_item->eq(field,0) && left_item != value && @@ -6835,7 +6836,7 @@ change_cond_ref_to_const(I_List *save_list,Item *and_father, Item *tmp=value->new_item(); if (tmp) { - func->arguments()[1] = tmp; + thd->change_item_tree(args + 1, tmp); func->update_used_tables(); if ((functype == Item_func::EQ_FUNC || functype == Item_func::EQUAL_FUNC) && and_father != cond && !left_item->const_item()) @@ -6856,13 +6857,14 @@ change_cond_ref_to_const(I_List *save_list,Item *and_father, Item *tmp=value->new_item(); if (tmp) { - func->arguments()[0] = value = tmp; + thd->change_item_tree(args, tmp); + value= tmp; func->update_used_tables(); if ((functype == Item_func::EQ_FUNC || functype == Item_func::EQUAL_FUNC) && and_father != cond && !right_item->const_item()) { - func->arguments()[0] = func->arguments()[1]; // For easy check - func->arguments()[1] = value; + args[0]= args[1]; // For easy check + thd->change_item_tree(args + 1, value); cond->marker=1; COND_CMP *tmp2; if ((tmp2=new COND_CMP(and_father,func))) @@ -6908,8 +6910,8 @@ static Item *remove_additional_cond(Item* conds) } static void -propagate_cond_constants(I_List *save_list,COND *and_father, - COND *cond) +propagate_cond_constants(THD *thd, I_List *save_list, + COND *and_father, COND *cond) { if (cond->type() == Item::COND_ITEM) { @@ -6920,18 +6922,19 @@ propagate_cond_constants(I_List *save_list,COND *and_father, I_List save; while ((item=li++)) { - propagate_cond_constants(&save,and_level ? cond : item, item); + propagate_cond_constants(thd, &save,and_level ? cond : item, item); } if (and_level) { // Handle other found items I_List_iterator cond_itr(save); COND_CMP *cond_cmp; while ((cond_cmp=cond_itr++)) - if (!cond_cmp->cmp_func->arguments()[0]->const_item()) - change_cond_ref_to_const(&save,cond_cmp->and_level, - cond_cmp->and_level, - cond_cmp->cmp_func->arguments()[0], - cond_cmp->cmp_func->arguments()[1]); + { + Item **args= cond_cmp->cmp_func->arguments(); + if (!args[0]->const_item()) + change_cond_ref_to_const(thd, &save,cond_cmp->and_level, + cond_cmp->and_level, args[0], args[1]); + } } } else if (and_father != cond && !cond->marker) // In a AND group @@ -6941,29 +6944,25 @@ propagate_cond_constants(I_List *save_list,COND *and_father, ((Item_func*) cond)->functype() == Item_func::EQUAL_FUNC)) { Item_func_eq *func=(Item_func_eq*) cond; - bool left_const= func->arguments()[0]->const_item(); - bool right_const=func->arguments()[1]->const_item(); + Item **args= func->arguments(); + bool left_const= args[0]->const_item(); + bool right_const= args[1]->const_item(); if (!(left_const && right_const) && - (func->arguments()[0]->result_type() == - (func->arguments()[1]->result_type()))) + args[0]->result_type() == args[1]->result_type()) { if (right_const) { - func->arguments()[1]=resolve_const_item(func->arguments()[1], - func->arguments()[0]); + resolve_const_item(thd, &args[1], args[0]); func->update_used_tables(); - change_cond_ref_to_const(save_list,and_father,and_father, - func->arguments()[0], - func->arguments()[1]); + change_cond_ref_to_const(thd, save_list, and_father, and_father, + args[0], args[1]); } else if (left_const) { - func->arguments()[0]=resolve_const_item(func->arguments()[0], - func->arguments()[1]); + resolve_const_item(thd, &args[0], args[1]); func->update_used_tables(); - change_cond_ref_to_const(save_list,and_father,and_father, - func->arguments()[1], - func->arguments()[0]); + change_cond_ref_to_const(thd, save_list, and_father, and_father, + args[1], args[0]); } } } @@ -7091,6 +7090,7 @@ simplify_joins(JOIN *join, List *join_list, COND *conds, bool top) */ if (table->on_expr) { + Item *expr; /* If an on expression E is attached to the table, check all null rejected predicates in this expression. @@ -7099,8 +7099,9 @@ simplify_joins(JOIN *join, List *join_list, COND *conds, bool top) the outer join is converted to an inner join and the corresponding on expression is added to E. */ - table->on_expr= simplify_joins(join, &nested_join->join_list, - table->on_expr, FALSE); + expr= simplify_joins(join, &nested_join->join_list, + table->on_expr, FALSE); + table->on_expr= expr; } nested_join->used_tables= (table_map) 0; nested_join->not_null_tables=(table_map) 0; @@ -7209,8 +7210,10 @@ simplify_joins(JOIN *join, List *join_list, COND *conds, bool top) DBUG_RETURN(conds); } + static COND * -optimize_cond(JOIN *join, COND *conds, Item::cond_result *cond_value) +optimize_cond(JOIN *join, COND *conds, List *join_list, + Item::cond_result *cond_value) { THD *thd= join->thd; SELECT_LEX *select= thd->lex->current_select; @@ -7223,9 +7226,21 @@ optimize_cond(JOIN *join, COND *conds, Item::cond_result *cond_value) } else { + /* + Build all multiple equality predicates and eliminate equality + predicates that can be inferred from these multiple equalities. + For each reference of a field included into a multiple equality + that occurs in a function set a pointer to the multiple equality + predicate. Substitute a constant instead of this field if the + multiple equality contains a constant. + */ DBUG_EXECUTE("where", print_where(conds, "original");); + conds= build_equal_items(join->thd, conds, NULL, join_list, + &join->cond_equal); + DBUG_EXECUTE("where",print_where(conds,"after equal_items");); + /* change field = field to field = const for each found field = const */ - propagate_cond_constants((I_List *) 0,conds,conds); + propagate_cond_constants(thd, (I_List *) 0, conds, conds); /* Remove all instances of item == item Remove all and-levels where CONST item != CONST item @@ -7461,21 +7476,28 @@ const_expression_in_where(COND *cond, Item *comp_item, Item **const_item) the record in the original table. If modify_item is 0 then fill_record() will update the temporary table + convert_blob_length If >0 create a varstring(convert_blob_length) field + instead of blob. RETURN 0 on error new_created field */ -static Field* create_tmp_field_from_field(THD *thd, - Field* org_field, - Item *item, - TABLE *table, - bool modify_item) + +static Field* create_tmp_field_from_field(THD *thd, Field* org_field, + Item *item, TABLE *table, + bool modify_item, + uint convert_blob_length) { Field *new_field; - // The following should always be true - if ((new_field= org_field->new_field(&thd->mem_root,table))) + if (convert_blob_length && org_field->flags & BLOB_FLAG) + new_field= new Field_varstring(convert_blob_length, org_field->maybe_null(), + org_field->field_name, table, + org_field->charset()); + else + new_field= org_field->new_field(thd->mem_root, table); + if (new_field) { if (modify_item) ((Item_field *)item)->result_field= new_field; @@ -7506,16 +7528,16 @@ static Field* create_tmp_field_from_field(THD *thd, the record in the original table. If modify_item is 0 then fill_record() will update the temporary table + convert_blob_length If >0 create a varstring(convert_blob_length) field + instead of blob. RETURN 0 on error new_created field */ -static Field* create_tmp_field_from_item(THD *thd, - Item *item, - TABLE *table, - Item ***copy_func, - bool modify_item) +static Field* create_tmp_field_from_item(THD *thd, Item *item, TABLE *table, + Item ***copy_func, bool modify_item, + uint convert_blob_length) { bool maybe_null=item->maybe_null; Field *new_field; @@ -7532,13 +7554,18 @@ static Field* create_tmp_field_from_item(THD *thd, break; case STRING_RESULT: if (item->max_length > 255) - new_field= new Field_blob(item->max_length, maybe_null, - item->name, table, - item->collation.collation); + { + if (convert_blob_length) + new_field= new Field_varstring(convert_blob_length, maybe_null, + item->name, table, + item->collation.collation); + else + new_field= new Field_blob(item->max_length, maybe_null, item->name, + table, item->collation.collation); + } else - new_field= new Field_string(item->max_length, maybe_null, - item->name, table, - item->collation.collation); + new_field= new Field_string(item->max_length, maybe_null, item->name, + table, item->collation.collation); break; case ROW_RESULT: default: @@ -7575,6 +7602,8 @@ static Field* create_tmp_field_from_item(THD *thd, the record in the original table. If modify_item is 0 then fill_record() will update the temporary table + convert_blob_length If >0 create a varstring(convert_blob_length) field + instead of blob. RETURN 0 on error @@ -7582,8 +7611,8 @@ static Field* create_tmp_field_from_item(THD *thd, */ Field *create_tmp_field(THD *thd, TABLE *table,Item *item, Item::Type type, - Item ***copy_func, Field **from_field, - bool group, bool modify_item) + Item ***copy_func, Field **from_field, + bool group, bool modify_item, uint convert_blob_length) { switch (type) { case Item::SUM_FUNC_ITEM: @@ -7618,8 +7647,15 @@ Field *create_tmp_field(THD *thd, TABLE *table,Item *item, Item::Type type, item->name,table,item->unsigned_flag); case STRING_RESULT: if (item_sum->max_length > 255) - return new Field_blob(item_sum->max_length,maybe_null, - item->name,table,item->collation.collation); + { + if (convert_blob_length) + return new Field_varstring(convert_blob_length, maybe_null, + item->name, table, + item->collation.collation); + else + return new Field_blob(item_sum->max_length, maybe_null, item->name, + table, item->collation.collation); + } return new Field_string(item_sum->max_length,maybe_null, item->name,table,item->collation.collation); case ROW_RESULT: @@ -7636,8 +7672,8 @@ Field *create_tmp_field(THD *thd, TABLE *table,Item *item, Item::Type type, case Item::DEFAULT_VALUE_ITEM: { Item_field *field= (Item_field*) item; - return create_tmp_field_from_field(thd, (*from_field= field->field), - item, table, modify_item); + return create_tmp_field_from_field(thd, (*from_field= field->field), item, + table, modify_item, convert_blob_length); } case Item::FUNC_ITEM: case Item::COND_ITEM: @@ -7652,14 +7688,16 @@ Field *create_tmp_field(THD *thd, TABLE *table,Item *item, Item::Type type, case Item::REF_ITEM: case Item::NULL_ITEM: case Item::VARBIN_ITEM: - return create_tmp_field_from_item(thd, item, table, - copy_func, modify_item); + return create_tmp_field_from_item(thd, item, table, copy_func, modify_item, + convert_blob_length); case Item::TYPE_HOLDER: { Field *example= ((Item_type_holder *)item)->example(); if (example) - return create_tmp_field_from_field(thd, example, item, table, 0); - return create_tmp_field_from_item(thd, item, table, copy_func, 0); + return create_tmp_field_from_field(thd, example, item, table, 0, + convert_blob_length); + return create_tmp_field_from_item(thd, item, table, copy_func, 0, + convert_blob_length); } default: // Dosen't have to be stored return 0; @@ -7818,12 +7856,14 @@ create_tmp_table(THD *thd,TMP_TABLE_PARAM *param,List &fields, ((Item_sum*) item)->result_field=0; for (i=0 ; i < ((Item_sum*) item)->arg_count ; i++) { - Item *arg= ((Item_sum*) item)->args[i]; + Item **argp= ((Item_sum*) item)->args + i; + Item *arg= *argp; if (!arg->const_item()) { Field *new_field= - create_tmp_field(thd, table,arg,arg->type(),©_func, - tmp_from_field, group != 0,not_all_columns); + create_tmp_field(thd, table, arg, arg->type(), ©_func, + tmp_from_field, group != 0,not_all_columns, + param->convert_blob_length); if (!new_field) goto err; // Should be OOM tmp_from_field++; @@ -7834,7 +7874,7 @@ create_tmp_table(THD *thd,TMP_TABLE_PARAM *param,List &fields, *blob_field++= new_field; blob_count++; } - ((Item_sum*) item)->args[i]= new Item_field(new_field); + thd->change_item_tree(argp, new Item_field(new_field)); if (!(new_field->flags & NOT_NULL_FLAG)) { null_count++; @@ -7842,7 +7882,7 @@ create_tmp_table(THD *thd,TMP_TABLE_PARAM *param,List &fields, new_field->maybe_null() is still false, it will be changed below. But we have to setup Item_field correctly */ - ((Item_sum*) item)->args[i]->maybe_null=1; + (*argp)->maybe_null=1; } } } @@ -7859,9 +7899,10 @@ create_tmp_table(THD *thd,TMP_TABLE_PARAM *param,List &fields, We here distinguish between UNION and multi-table-updates by the fact that in the later case group is set to the row pointer. */ - Field *new_field=create_tmp_field(thd, table, item,type, ©_func, - tmp_from_field, group != 0, - not_all_columns || group !=0); + Field *new_field= create_tmp_field(thd, table, item, type, ©_func, + tmp_from_field, group != 0, + not_all_columns || group !=0, + param->convert_blob_length); if (!new_field) { if (thd->is_fatal_error) @@ -8058,7 +8099,7 @@ create_tmp_table(THD *thd,TMP_TABLE_PARAM *param,List &fields, if (!using_unique_constraint) { group->buff=(char*) group_buff; - if (!(group->field=field->new_field(&thd->mem_root,table))) + if (!(group->field=field->new_field(thd->mem_root,table))) goto err; /* purecov: inspected */ if (maybe_null) { @@ -8569,7 +8610,7 @@ do_select(JOIN *join,List *fields,TABLE *table,Procedure *procedure) DBUG_PRINT("error",("Error: do_select() failed")); } #endif - DBUG_RETURN(error || join->thd->net.report_error); + DBUG_RETURN(join->thd->net.report_error ? -1 : error); } @@ -9050,6 +9091,7 @@ join_read_const_table(JOIN_TAB *tab, POSITION *pos) { table->key_read=1; table->file->extra(HA_EXTRA_KEYREAD); + tab->index= tab->ref.key; } if ((error=join_read_const(tab))) { @@ -10037,10 +10079,23 @@ part_of_refkey(TABLE *table,Field *field) /***************************************************************************** Test if one can use the key to resolve ORDER BY - Returns: 1 if key is ok. - 0 if key can't be used - -1 if reverse key can be used - used_key_parts is set to key parts used if length != 0 + + SYNOPSIS + test_if_order_by_key() + order Sort order + table Table to sort + idx Index to check + used_key_parts Return value for used key parts. + + + NOTES + used_key_parts is set to correct key parts used if return value != 0 + (On other cases, used_key_part may be changed) + + RETURN + 1 key is ok. + 0 Key can't be used + -1 Reverse key can be used *****************************************************************************/ static int test_if_order_by_key(ORDER *order, TABLE *table, uint idx, @@ -10069,13 +10124,17 @@ static int test_if_order_by_key(ORDER *order, TABLE *table, uint idx, DBUG_RETURN(0); /* set flag to 1 if we can use read-next on key, else to -1 */ - flag= ((order->asc == !(key_part->key_part_flag & HA_REVERSE_SORT)) ? 1 : -1); + flag= ((order->asc == !(key_part->key_part_flag & HA_REVERSE_SORT)) ? + 1 : -1); if (reverse && flag != reverse) DBUG_RETURN(0); reverse=flag; // Remember if reverse key_part++; } *used_key_parts= (uint) (key_part - table->key_info[idx].key_part); + if (reverse == -1 && !(table->file->index_flags(idx, *used_key_parts-1, 1) & + HA_READ_PREV)) + reverse= 0; // Index can't be used DBUG_RETURN(reverse); } @@ -10288,14 +10347,11 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit, if (!select->quick->reverse_sorted()) { int quick_type= select->quick->get_type(); - /* here used_key_parts >0 */ - if (!(table->file->index_flags(ref_key,used_key_parts-1, 1) - & HA_READ_PREV) || - quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE || + if (quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE || quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT || quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION || quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX) - DBUG_RETURN(0); // Use filesort + DBUG_RETURN(0); // Use filesort /* ORDER BY range_key DESC */ QUICK_SELECT_DESC *tmp=new QUICK_SELECT_DESC((QUICK_RANGE_SELECT*)(select->quick), @@ -10317,9 +10373,6 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit, Use a traversal function that starts by reading the last row with key part (A) and then traverse the index backwards. */ - if (!(table->file->index_flags(ref_key,used_key_parts-1, 1) - & HA_READ_PREV)) - DBUG_RETURN(0); // Use filesort tab->read_first_record= join_read_last_key; tab->read_record.read_record= join_read_prev_same; /* fall through */ @@ -10365,7 +10418,7 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit, if (keys.is_set(nr)) { int flag; - if ((flag=test_if_order_by_key(order, table, nr, ¬_used))) + if ((flag= test_if_order_by_key(order, table, nr, ¬_used))) { if (!no_changes) { @@ -11150,7 +11203,7 @@ find_order_in_list(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables, ORDER *order, List &fields, List &all_fields, bool is_group_field) { - Item *order_item=*order->item; /* The item from the GROUP/ORDER caluse. */ + Item *order_item= *order->item; /* The item from the GROUP/ORDER caluse. */ Item::Type order_item_type; Item **select_item; /* The corresponding item from the SELECT clause. */ Field *from_field; /* The corresponding field from the FROM clause. */ @@ -11161,8 +11214,7 @@ find_order_in_list(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables, if (!count || count > fields.elements) { my_printf_error(ER_BAD_FIELD_ERROR,ER(ER_BAD_FIELD_ERROR), - MYF(0),order_item->full_name(), - thd->where); + MYF(0), order_item->full_name(), thd->where); return 1; } order->item= ref_pointer_array + count - 1; @@ -11173,8 +11225,9 @@ find_order_in_list(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables, } /* Lookup the current GROUP/ORDER field in the SELECT clause. */ uint counter; + bool unaliased; select_item= find_item_in_list(order_item, fields, &counter, - REPORT_EXCEPT_NOT_FOUND); + REPORT_EXCEPT_NOT_FOUND, &unaliased); if (!select_item) return 1; /* Some error occured. */ @@ -11182,6 +11235,14 @@ find_order_in_list(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables, /* Check whether the resolved field is not ambiguos. */ if (select_item != not_found_item) { + /* + If we have found field not by its alias in select list but by its + original field name, we should additionaly check if we have conflict + for this name (in case if we would perform lookup in all tables). + */ + if (unaliased && !order_item->fixed && order_item->fix_fields(thd, tables, order->item)) + return 1; + /* Lookup the current GROUP field in the FROM clause. */ order_item_type= order_item->type(); if (is_group_field && @@ -11215,16 +11276,15 @@ find_order_in_list(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables, } order->in_field_list=0; - Item *it= *order->item; /* - We check it->fixed because Item_func_group_concat can put + We check order_item->fixed because Item_func_group_concat can put arguments for which fix_fields already was called. 'it' reassigned in if condition because fix_field can change it. */ - if (!it->fixed && - (it->fix_fields(thd, tables, order->item) || - (it= *order->item)->check_cols(1) || + if (!order_item->fixed && + (order_item->fix_fields(thd, tables, order->item) || + (order_item= *order->item)->check_cols(1) || thd->is_fatal_error)) return 1; // Wrong field uint el= all_fields.elements; @@ -11234,6 +11294,7 @@ find_order_in_list(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables, return 0; } + /* Change order to point at item in select list. If item isn't a number and doesn't exits in the select list, add it the the field list. @@ -11344,10 +11405,11 @@ setup_new_fields(THD *thd,TABLE_LIST *tables,List &fields, thd->set_query_id=1; // Not really needed, but... uint counter; + bool not_used; for (; new_field ; new_field= new_field->next) { if ((item= find_item_in_list(*new_field->item, fields, &counter, - IGNORE_ERRORS))) + IGNORE_ERRORS, ¬_used))) new_field->item=item; /* Change to shared Item */ else { @@ -11695,7 +11757,7 @@ setup_copy_fields(THD *thd, TMP_TABLE_PARAM *param, saved value */ Field *field= item->field; - item->result_field=field->new_field(&thd->mem_root,field->table); + item->result_field=field->new_field(thd->mem_root,field->table); char *tmp=(char*) sql_alloc(field->pack_length()+1); if (!tmp) goto err; @@ -11705,6 +11767,8 @@ setup_copy_fields(THD *thd, TMP_TABLE_PARAM *param, } } else if ((pos->type() == Item::FUNC_ITEM || + pos->type() == Item::SUBSELECT_ITEM || + pos->type() == Item::CACHE_ITEM || pos->type() == Item::COND_ITEM) && !pos->with_sum_func) { // Save for send fields @@ -11812,9 +11876,6 @@ bool JOIN::alloc_func_list() before_group_by Set to 1 if this is called before GROUP BY handling recompute Set to TRUE if sum_funcs must be recomputed - NOTES - Calls ::setup() for all item_sum objects in field_list - RETURN 0 ok 1 error @@ -11835,12 +11896,7 @@ bool JOIN::make_sum_func_list(List &field_list, List &send_fields, while ((item=it++)) { if (item->type() == Item::SUM_FUNC_ITEM && !item->const_item()) - { *func++= (Item_sum*) item; - /* let COUNT(DISTINCT) create the temporary table */ - if (((Item_sum*) item)->setup(thd)) - DBUG_RETURN(TRUE); - } } if (before_group_by && rollup.state == ROLLUP::STATE_INITED) { @@ -11985,6 +12041,30 @@ change_refs_to_tmp_fields(THD *thd, Item **ref_pointer_array, Code for calculating functions ******************************************************************************/ + +/* + Call ::setup for all sum functions + + SYNOPSIS + setup_sum_funcs() + thd thread handler + func_ptr sum function list + + RETURN + FALSE ok + TRUE error +*/ + +static bool setup_sum_funcs(THD *thd, Item_sum **func_ptr) +{ + Item_sum *func; + while ((func= *(func_ptr++))) + if (func->setup(thd)) + return TRUE; + return FALSE; +} + + static void init_tmptable_sum_functions(Item_sum **func_ptr) { @@ -12140,7 +12220,7 @@ bool JOIN::rollup_init() return 1; rollup.ref_pointer_arrays= (Item***) (rollup.fields + send_group_parts); ref_array= (Item**) (rollup.ref_pointer_arrays+send_group_parts); - rollup.item_null= new (&thd->mem_root) Item_null(); + rollup.item_null= new (thd->mem_root) Item_null(); /* Prepare space for field list for the different levels From 9a4ffb5ad9d7ef4fd4d9bfe161ffabf74ee8c7ac Mon Sep 17 00:00:00 2001 From: "timour@mysql.com" <> Date: Thu, 11 Nov 2004 11:16:51 +0200 Subject: [PATCH 8/8] WL#1972 - manual merge with latest bk source tree --- sql/item.cc | 475 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 346 insertions(+), 129 deletions(-) diff --git a/sql/item.cc b/sql/item.cc index 12b0f7b0796..59126603544 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -46,11 +46,11 @@ void item_init(void) } Item::Item(): - name_length(0), fixed(0) + name_length(0), fixed(0), + collation(default_charset(), DERIVATION_COERCIBLE) { marker= 0; maybe_null=null_value=with_sum_func=unsigned_flag=0; - collation.set(default_charset(), DERIVATION_COERCIBLE); name= 0; decimals= 0; max_length= 0; @@ -114,13 +114,31 @@ void Item::cleanup() DBUG_PRINT("info", ("Item: 0x%lx", this)); DBUG_PRINT("info", ("Type: %d", (int)type())); fixed=0; + marker= 0; DBUG_VOID_RETURN; } + +/* + cleanup() item if it is 'fixed' + + SYNOPSIS + cleanup_processor() + arg - a dummy parameter, is not used here +*/ + +bool Item::cleanup_processor(byte *arg) +{ + if (fixed) + cleanup(); + return FALSE; +} + + Item_ident::Item_ident(const char *db_name_par,const char *table_name_par, const char *field_name_par) :orig_db_name(db_name_par), orig_table_name(table_name_par), - orig_field_name(field_name_par), changed_during_fix_field(0), + orig_field_name(field_name_par), db_name(db_name_par), table_name(table_name_par), field_name(field_name_par), cached_field_index(NO_CACHED_FIELD_INDEX), cached_table(0), depended_from(0) @@ -134,7 +152,6 @@ Item_ident::Item_ident(THD *thd, Item_ident *item) orig_db_name(item->orig_db_name), orig_table_name(item->orig_table_name), orig_field_name(item->orig_field_name), - changed_during_fix_field(0), db_name(item->db_name), table_name(item->table_name), field_name(item->field_name), @@ -151,11 +168,6 @@ void Item_ident::cleanup() table_name, orig_table_name, field_name, orig_field_name)); Item::cleanup(); - if (changed_during_fix_field) - { - *changed_during_fix_field= this; - changed_during_fix_field= 0; - } db_name= orig_db_name; table_name= orig_table_name; field_name= orig_field_name; @@ -267,6 +279,41 @@ bool Item::eq(const Item *item, bool binary_cmp) const } +Item *Item::safe_charset_converter(CHARSET_INFO *tocs) +{ + /* + Don't allow automatic conversion to non-Unicode charsets, + as it potentially loses data. + */ + if (!(tocs->state & MY_CS_UNICODE)) + return NULL; // safe conversion is not possible + return new Item_func_conv_charset(this, tocs); +} + + +Item *Item_string::safe_charset_converter(CHARSET_INFO *tocs) +{ + Item_string *conv; + uint conv_errors; + String tmp, cstr, *ostr= val_str(&tmp); + cstr.copy(ostr->ptr(), ostr->length(), ostr->charset(), tocs, &conv_errors); + if (conv_errors || !(conv= new Item_string(cstr.ptr(), cstr.length(), + cstr.charset(), + collation.derivation))) + { + /* + Safe conversion is not possible (or EOM). + We could not convert a string into the requested character set + without data loss. The target charset does not cover all the + characters from the string. Operation cannot be done correctly. + */ + return NULL; + } + conv->str_value.copy(); + return conv; +} + + bool Item_string::eq(const Item *item, bool binary_cmp) const { if (type() == item->type()) @@ -361,7 +408,43 @@ Item_splocal::type() const } -bool DTCollation::aggregate(DTCollation &dt, bool superset_conversion) + +/* + Aggregate two collations together taking + into account their coercibility (aka derivation): + + 0 == DERIVATION_EXPLICIT - an explicitely written COLLATE clause + 1 == DERIVATION_NONE - a mix of two different collations + 2 == DERIVATION_IMPLICIT - a column + 3 == DERIVATION_COERCIBLE - a string constant + + The most important rules are: + + 1. If collations are the same: + chose this collation, and the strongest derivation. + + 2. If collations are different: + - Character sets may differ, but only if conversion without + data loss is possible. The caller provides flags whether + character set conversion attempts should be done. If no + flags are substituted, then the character sets must be the same. + Currently processed flags are: + MY_COLL_ALLOW_SUPERSET_CONV - allow conversion to a superset + MY_COLL_ALLOW_COERCIBLE_CONV - allow conversion of a coercible value + - two EXPLICIT collations produce an error, e.g. this is wrong: + CONCAT(expr1 collate latin1_swedish_ci, expr2 collate latin1_german_ci) + - the side with smaller derivation value wins, + i.e. a column is stronger than a string constant, + an explicit COLLATE clause is stronger than a column. + - if derivations are the same, we have DERIVATION_NONE, + we'll wait for an explicit COLLATE clause which possibly can + come from another argument later: for example, this is valid, + but we don't know yet when collecting the first two arguments: + CONCAT(latin1_swedish_ci_column, + latin1_german1_ci_column, + expr COLLATE latin1_german2_ci) +*/ +bool DTCollation::aggregate(DTCollation &dt, uint flags) { nagg++; if (!my_charset_same(collation, dt.collation)) @@ -392,28 +475,37 @@ bool DTCollation::aggregate(DTCollation &dt, bool superset_conversion) else ; // Do nothing } - else if (superset_conversion) + else if ((flags & MY_COLL_ALLOW_SUPERSET_CONV) && + derivation < dt.derivation && + collation->state & MY_CS_UNICODE) { - if (derivation < dt.derivation && - collation->state & MY_CS_UNICODE) - ; // Do nothing - else if (dt.derivation < derivation && - dt.collation->state & MY_CS_UNICODE) - { - set(dt); - strong= nagg; - } - else - { - // Cannot convert to superset - set(0, DERIVATION_NONE); - return 1; - } + // Do nothing + } + else if ((flags & MY_COLL_ALLOW_SUPERSET_CONV) && + dt.derivation < derivation && + dt.collation->state & MY_CS_UNICODE) + { + set(dt); + strong= nagg; + } + else if ((flags & MY_COLL_ALLOW_COERCIBLE_CONV) && + derivation < dt.derivation && + dt.derivation == DERIVATION_COERCIBLE) + { + // Do nothing; + } + else if ((flags & MY_COLL_ALLOW_COERCIBLE_CONV) && + dt.derivation < derivation && + derivation == DERIVATION_COERCIBLE) + { + set(dt); + strong= nagg; } else { + // Cannot apply conversion set(0, DERIVATION_NONE); - return 1; + return 1; } } else if (derivation < dt.derivation) @@ -452,19 +544,47 @@ Item_field::Item_field(Field *f) have_privileges(0), any_privileges(0) { set_field(f); - collation.set(DERIVATION_IMPLICIT); - fixed= 1; + /* + field_name and talbe_name should not point to garbage + if this item is to be reused + */ + orig_table_name= orig_field_name= ""; } Item_field::Item_field(THD *thd, Field *f) - :Item_ident(NullS, thd->strdup(f->table_name), - thd->strdup(f->field_name)), + :Item_ident(f->table->table_cache_key, f->table_name, f->field_name), item_equal(0), no_const_subst(0), have_privileges(0), any_privileges(0) { + /* + We always need to provide Item_field with a fully qualified field + name to avoid ambiguity when executing prepared statements like + SELECT * from d1.t1, d2.t1; (assuming d1.t1 and d2.t1 have columns + with same names). + This is because prepared statements never deal with wildcards in + select list ('*') and always fix fields using fully specified path + (i.e. db.table.column). + No check for OOM: if db_name is NULL, we'll just get + "Field not found" error. + We need to copy db_name, table_name and field_name because they must + be allocated in the statement memory, not in table memory (the table + structure can go away and pop up again between subsequent executions + of a prepared statement). + */ + if (thd->current_arena->is_stmt_prepare()) + { + if (db_name) + orig_db_name= thd->strdup(db_name); + orig_table_name= thd->strdup(table_name); + orig_field_name= thd->strdup(field_name); + /* + We don't restore 'name' in cleanup because it's not changed + during execution. Still we need it to point to persistent + memory if this item is to be reused. + */ + name= (char*) orig_field_name; + } set_field(f); - collation.set(DERIVATION_IMPLICIT); - fixed= 1; } // Constructor need to process subselect with temporary tables (see Item) @@ -491,6 +611,21 @@ void Item_field::set_field(Field *field_par) db_name=field_par->table->table_cache_key; unsigned_flag=test(field_par->flags & UNSIGNED_FLAG); collation.set(field_par->charset(), DERIVATION_IMPLICIT); + fixed= 1; +} + + +/* + Reset this item to point to a field from the new temporary table. + This is used when we create a new temporary table for each execution + of prepared statement. +*/ + +void Item_field::reset_field(Field *f) +{ + set_field(f); + /* 'name' is pointing at field->field_name of old field */ + name= (char*) f->field_name; } const char *Item_ident::full_name() const @@ -793,6 +928,12 @@ String *Item_null::val_str(String *str) } +Item *Item_null::safe_charset_converter(CHARSET_INFO *tocs) +{ + collation.set(tocs); + return this; +} + /*********************** Item_param related ******************************/ /* @@ -888,7 +1029,9 @@ bool Item_param::set_str(const char *str, ulong length) Assign string with no conversion: data is converted only after it's been written to the binary log. */ - if (str_value.copy(str, length, &my_charset_bin, &my_charset_bin)) + uint dummy_errors; + if (str_value.copy(str, length, &my_charset_bin, &my_charset_bin, + &dummy_errors)) DBUG_RETURN(TRUE); state= STRING_VALUE; maybe_null= 0; @@ -1043,7 +1186,7 @@ int Item_param::save_in_field(Field *field, bool no_conversions) return field->store(str_value.ptr(), str_value.length(), str_value.charset()); case NULL_VALUE: - return set_field_to_null(field); + return set_field_to_null_with_conversions(field, no_conversions); case NO_VALUE: default: DBUG_ASSERT(0); @@ -1146,9 +1289,10 @@ String *Item_param::val_str(String* str) return str; case TIME_VALUE: { - if (str->reserve(MAX_DATE_REP_LENGTH)) + if (str->reserve(MAX_DATE_STRING_REP_LENGTH)) break; - TIME_to_string(&value.time, str); + str->length((uint) my_TIME_to_str(&value.time, (char*) str->ptr())); + str->set_charset(&my_charset_bin); return str; } case NULL_VALUE: @@ -1178,24 +1322,19 @@ const String *Item_param::query_val_str(String* str) const case TIME_VALUE: { char *buf, *ptr; - String tmp; str->length(0); /* TODO: in case of error we need to notify replication that binary log contains wrong statement */ - if (str->reserve(MAX_DATE_REP_LENGTH+3)) + if (str->reserve(MAX_DATE_STRING_REP_LENGTH+3)) break; /* Create date string inplace */ buf= str->c_ptr_quick(); ptr= buf; *ptr++= '\''; - tmp.set(ptr, MAX_DATE_REP_LENGTH, &my_charset_bin); - tmp.length(0); - TIME_to_string(&value.time, &tmp); - - ptr+= tmp.length(); + ptr+= (uint) my_TIME_to_str(&value.time, ptr); *ptr++= '\''; str->length((uint32) (ptr - buf)); break; @@ -1249,6 +1388,10 @@ bool Item_param::convert_str_value(THD *thd) value.cs_info.character_set_client, value.cs_info.final_character_set_of_str_value); } + else + str_value.set_charset(value.cs_info.final_character_set_of_str_value); + /* Here str_value is guaranteed to be in final_character_set_of_str_value */ + max_length= str_value.length(); decimals= 0; /* @@ -1373,7 +1516,7 @@ bool Item_ref_null_helper::get_date(TIME *ltime, uint fuzzydate) static void mark_as_dependent(THD *thd, SELECT_LEX *last, SELECT_LEX *current, Item_ident *item) { - // store pointer on SELECT_LEX from wich item is dependent + // store pointer on SELECT_LEX from which item is dependent item->depended_from= last; current->mark_as_dependent(last); if (thd->lex->describe & DESCRIBE_EXTENDED) @@ -1390,6 +1533,8 @@ static void mark_as_dependent(THD *thd, SELECT_LEX *last, SELECT_LEX *current, } + + /* Search a GROUP BY clause for a field with a certain name. @@ -1534,13 +1679,14 @@ resolve_ref_in_select_and_group(THD *thd, Item_ident *ref, SELECT_LEX *select) ORDER *group_list= (ORDER*) select->group_list.first; bool ambiguous_fields= FALSE; uint counter; + bool not_used; /* Search for a column or derived column named as 'ref' in the SELECT clause of the current select. */ if (!(select_ref= find_item_in_list(ref, *(select->get_item_list()), &counter, - REPORT_EXCEPT_NOT_FOUND))) + REPORT_EXCEPT_NOT_FOUND, ¬_used))) return NULL; /* Some error occurred. */ /* If this is a non-aggregated field inside HAVING, search in GROUP BY. */ @@ -1687,10 +1833,10 @@ bool Item_field::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) enum_parsing_place place= prev_subselect_item->parsing_place; /* Check table fields only if the subquery is used somewhere out of - HAVING or SELECT list, or the outer SELECT does not use grouping - (i.e. tables are accessible). + HAVING, or the outer SELECT does not use grouping (i.e. tables are + accessible). */ - if (((place != IN_HAVING && place != SELECT_LIST) || + if ((place != IN_HAVING || (outer_sel->with_sum_func == 0 && outer_sel->group_list.elements == 0)) && (from_field= find_field_in_tables(thd, this, table_list, @@ -1762,15 +1908,14 @@ bool Item_field::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) } else if (ref != not_found_item) { - /* Should be checked in resolve_ref_in_select_and_group(). */ + /* Should have been checked in resolve_ref_in_select_and_group(). */ DBUG_ASSERT(*ref && (*ref)->fixed); - Item_ref *rf; - *reference= rf= new Item_ref(ref, reference, (char *) table_name, - (char *) field_name); - register_item_tree_changing(reference); + Item_ref *rf= new Item_ref(last->ref_pointer_array + counter, + (char *)table_name, (char *)field_name); if (!rf) return TRUE; + thd->change_item_tree(reference, rf); /* rf is Item_ref => never substitute other items (in this case) during fix_fields() => we can use rf after fix_fields() @@ -1787,12 +1932,11 @@ bool Item_field::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) if (last->having_fix_field) { Item_ref *rf; - *reference= rf= new Item_ref(reference, *reference, - (cached_table->db[0]?cached_table->db:0), - (char *)cached_table->alias, - (char *)field_name); + rf= new Item_ref((cached_table->db[0] ? cached_table->db : 0), + (char*) cached_table->alias, (char*) field_name); if (!rf) return TRUE; + thd->change_item_tree(reference, rf); /* rf is Item_ref => never substitute other items (in this case) during fix_fields() => we can use rf after fix_fields() @@ -1861,6 +2005,14 @@ bool Item_field::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) return FALSE; } + +Item *Item_field::safe_charset_converter(CHARSET_INFO *tocs) +{ + no_const_subst= 1; + return Item::safe_charset_converter(tocs); +} + + void Item_field::cleanup() { DBUG_ENTER("Item_field::cleanup"); @@ -1871,6 +2023,7 @@ void Item_field::cleanup() I.e. we can drop 'field'. */ field= result_field= 0; + DBUG_VOID_RETURN; } /* @@ -1915,7 +2068,8 @@ Item_equal *Item_field::find_item_equal(COND_EQUAL *cond_equal) /* - Set a pointer to the multiple equality the field reference belongs to (if any) + Set a pointer to the multiple equality the field reference belongs to + (if any) SYNOPSIS equal_fields_propagator() @@ -1954,7 +2108,21 @@ Item *Item_field::equal_fields_propagator(byte *arg) /* - Set a pointer to the multiple equality the field reference belongs to (if any) + Mark the item to not be part of substitution if it's not a binary item + See comments in Arg_comparator::set_compare_func() for details +*/ + +Item *Item_field::set_no_const_sub(byte *arg) +{ + if (field->charset() != &my_charset_bin) + no_const_subst=1; + return this; +} + + +/* + Set a pointer to the multiple equality the field reference belongs to + (if any) SYNOPSIS replace_equal_field_processor() @@ -1990,6 +2158,7 @@ bool Item_field::replace_equal_field_processor(byte *arg) return 0; } + void Item::init_make_field(Send_field *tmp_field, enum enum_field_types field_type) { @@ -2000,7 +2169,9 @@ void Item::init_make_field(Send_field *tmp_field, tmp_field->table_name= empty_name; tmp_field->col_name= name; tmp_field->charsetnr= collation.collation->number; - tmp_field->flags=maybe_null ? 0 : NOT_NULL_FLAG; + tmp_field->flags= (maybe_null ? 0 : NOT_NULL_FLAG) | + (my_binary_compare(collation.collation) ? + BINARY_FLAG : 0); tmp_field->type=field_type; tmp_field->length=max_length; tmp_field->decimals=decimals; @@ -2629,15 +2800,15 @@ bool Item_ref::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) enum_parsing_place place= prev_subselect_item->parsing_place; /* Check table fields only if the subquery is used somewhere out of - HAVING or SELECT list, or the outer SELECT does not use grouping - (i.e. tables are accessible). + HAVING or the outer SELECT does not use grouping (i.e. tables are + accessible). TODO: Here we could first find the field anyway, and then test this condition, so that we can give a better error message - ER_WRONG_FIELD_WITH_GROUP, instead of the less informative ER_BAD_FIELD_ERROR which we produce now. */ - if (((place != IN_HAVING && place != SELECT_LIST) || + if ((place != IN_HAVING || (!outer_sel->with_sum_func && outer_sel->group_list.elements == 0))) { @@ -2679,19 +2850,24 @@ bool Item_ref::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) { my_printf_error(ER_BAD_FIELD_ERROR, ER(ER_BAD_FIELD_ERROR), MYF(0), this->full_name(), current_thd->where); - ref= 0; + ref= 0; // Safety return TRUE; } - else if (from_field != not_found_field) + if (from_field != not_found_field) { - ref= 0; // To prevent "delete *ref;" on ~Item_ref() of this item + /* + Set ref to 0 as we are replacing this item with the found item and + this will ensure we get an error if this item would be used + elsewhere + */ + ref= 0; // Safety if (from_field != view_ref_found) { - Item_field* fld= new Item_field(from_field); - if (!((*reference)= fld)) + Item_field* fld; + if (!(fld= new Item_field(tmp))) return TRUE; - mark_as_dependent(thd, last, current_sel, fld); - register_item_tree_changing(reference); + thd->change_item_tree(reference, fld); + mark_as_dependent(thd, last, thd->lex->current_select, fld); return FALSE; } /* @@ -2757,8 +2933,6 @@ void Item_ref::cleanup() DBUG_ENTER("Item_ref::cleanup"); Item_ident::cleanup(); result_field= 0; - if (hook_ptr) - *hook_ptr= orig_item; DBUG_VOID_RETURN; } @@ -2881,7 +3055,6 @@ bool Item_default_value::fix_fields(THD *thd, def_field->move_field(def_field->table->default_values - def_field->table->record[0]); set_field(def_field); - fixed= 1; return 0; } @@ -2939,7 +3112,6 @@ bool Item_insert_value::fix_fields(THD *thd, set_field(new Field_null(0, 0, Field::NONE, tmp_field->field_name, tmp_field->table, &my_charset_bin)); } - fixed= 1; return 0; } @@ -3058,10 +3230,12 @@ Item_result item_cmp_type(Item_result a,Item_result b) } -Item *resolve_const_item(Item *item,Item *comp_item) +void resolve_const_item(THD *thd, Item **ref, Item *comp_item) { + Item *item= *ref; + Item *new_item; if (item->basic_const_item()) - return item; // Can't be better + return; // Can't be better Item_result res_type=item_cmp_type(comp_item->result_type(), item->result_type()); char *name=item->name; // Alloced by sql_alloc @@ -3072,27 +3246,32 @@ Item *resolve_const_item(Item *item,Item *comp_item) String tmp(buff,sizeof(buff),&my_charset_bin),*result; result=item->val_str(&tmp); if (item->null_value) - return new Item_null(name); - uint length=result->length(); - char *tmp_str=sql_strmake(result->ptr(),length); - return new Item_string(name,tmp_str,length,result->charset()); + new_item= new Item_null(name); + else + { + uint length= result->length(); + char *tmp_str= sql_strmake(result->ptr(), length); + new_item= new Item_string(name, tmp_str, length, result->charset()); + } } - if (res_type == INT_RESULT) + else if (res_type == INT_RESULT) { longlong result=item->val_int(); uint length=item->max_length; bool null_value=item->null_value; - return (null_value ? (Item*) new Item_null(name) : - (Item*) new Item_int(name,result,length)); + new_item= (null_value ? (Item*) new Item_null(name) : + (Item*) new Item_int(name, result, length)); } else { // It must REAL_RESULT double result=item->val(); uint length=item->max_length,decimals=item->decimals; bool null_value=item->null_value; - return (null_value ? (Item*) new Item_null(name) : - (Item*) new Item_real(name,result,decimals,length)); + new_item= (null_value ? (Item*) new Item_null(name) : (Item*) + new Item_real(name, result, decimals, length)); } + if (new_item) + thd->change_item_tree(ref, new_item); } /* @@ -3319,6 +3498,7 @@ Item_type_holder::Item_type_holder(THD *thd, Item *item) else field_example= 0; max_length= real_length(item); + maybe_null= item->maybe_null; collation.set(item->collation); } @@ -3336,62 +3516,90 @@ static Item_result type_convertor[4][4]= {STRING_RESULT, REAL_RESULT, INT_RESULT, ROW_RESULT}, {ROW_RESULT, ROW_RESULT, ROW_RESULT, ROW_RESULT}}; + +/* + Values of 'from' field can be stored in 'to' field. + + SYNOPSIS + is_attr_compatible() + from Item which values should be saved + to Item where values should be saved + + RETURN + 1 can be saved + 0 can not be saved +*/ + +inline bool is_attr_compatible(Item *from, Item *to) +{ + return ((to->max_length >= from->max_length) && + (to->maybe_null || !from->maybe_null) && + (to->result_type() != STRING_RESULT || + from->result_type() != STRING_RESULT || + my_charset_same(from->collation.collation, + to->collation.collation))); +} + + bool Item_type_holder::join_types(THD *thd, Item *item) { uint32 new_length= real_length(item); - bool change_field= 0, skip_store_field= 0; - Item_result new_type= type_convertor[item_type][item->result_type()]; + bool use_new_field= 0, use_expression_type= 0; + Item_result new_result_type= type_convertor[item_type][item->result_type()]; + bool item_is_a_field= item->type() == Item::FIELD_ITEM; - // we have both fields - if (field_example && item->type() == Item::FIELD_ITEM) + /* + Check if both items point to fields: in this case we + can adjust column types of result table in the union smartly. + */ + if (field_example && item_is_a_field) { Field *field= ((Item_field *)item)->field; - if (field_example->field_cast_type() != field->field_cast_type()) + /* Can 'field_example' field store data of the column? */ + if ((use_new_field= + (!field->field_cast_compatible(field_example->field_cast_type()) || + !is_attr_compatible(item, this)))) { - if (!(change_field= - field_example->field_cast_compatible(field->field_cast_type()))) - { - /* - if old field can't store value of 'worse' new field we will make - decision about result field type based only on Item result type - */ - if (!field->field_cast_compatible(field_example->field_cast_type())) - skip_store_field= 1; - } + /* + The old field can't store value of the new field. + Check if the new field can store value of the old one. + */ + use_expression_type|= + (!field_example->field_cast_compatible(field->field_cast_type()) || + !is_attr_compatible(this, item)); } } - - // size/type should be changed - if (change_field || - (new_type != item_type) || - (max_length < new_length) || - ((new_type == INT_RESULT) && - (decimals < item->decimals)) || - (!maybe_null && item->maybe_null) || - (item_type == STRING_RESULT && new_type == STRING_RESULT && - !my_charset_same(collation.collation, item->collation.collation))) + else if (field_example || item_is_a_field) { - // new field has some parameters worse then current - skip_store_field|= (change_field && - (max_length > new_length) || - ((new_type == INT_RESULT) && - (decimals > item->decimals)) || - (maybe_null && !item->maybe_null) || - (item_type == STRING_RESULT && - new_type == STRING_RESULT && - !my_charset_same(collation.collation, - item->collation.collation))); /* - It is safe assign pointer on field, because it will be used just after - all JOIN::prepare calls and before any SELECT execution + Expression types can't be mixed with field types, we have to use + expression types. */ - if (skip_store_field || item->type() != Item::FIELD_ITEM) + use_new_field= 1; // make next if test easier + use_expression_type= 1; + } + + /* Check whether size/type of the result item should be changed */ + if (use_new_field || + (new_result_type != item_type) || (new_length > max_length) || + (!maybe_null && item->maybe_null) || + (item_type == STRING_RESULT && + collation.collation != item->collation.collation)) + { + const char *old_cs,*old_derivation; + if (use_expression_type || !item_is_a_field) field_example= 0; else + { + /* + It is safe to assign a pointer to field here, because it will be used + before any table is closed. + */ field_example= ((Item_field*) item)->field; + } - const char *old_cs= collation.collation->name, - *old_derivation= collation.derivation_name(); + old_cs= collation.collation->name; + old_derivation= collation.derivation_name(); if (item_type == STRING_RESULT && collation.aggregate(item->collation)) { my_error(ER_CANT_AGGREGATE_2COLLATIONS, MYF(0), @@ -3405,18 +3613,18 @@ bool Item_type_holder::join_types(THD *thd, Item *item) max_length= max(max_length, new_length); decimals= max(decimals, item->decimals); maybe_null|= item->maybe_null; - item_type= new_type; + item_type= new_result_type; } DBUG_ASSERT(item_type != ROW_RESULT); return 0; } + uint32 Item_type_holder::real_length(Item *item) { if (item->type() == Item::FIELD_ITEM) - { return ((Item_field *)item)->max_disp_length(); - } + switch (item->result_type()) { case STRING_RESULT: @@ -3452,6 +3660,14 @@ String *Item_type_holder::val_str(String*) return 0; } +void Item_result_field::cleanup() +{ + DBUG_ENTER("Item_result_field::cleanup()"); + Item::cleanup(); + result_field= 0; + DBUG_VOID_RETURN; +} + /***************************************************************************** ** Instantiate templates *****************************************************************************/ @@ -3460,5 +3676,6 @@ String *Item_type_holder::val_str(String*) template class List; template class List_iterator; template class List_iterator_fast; +template class List_iterator_fast; template class List; #endif