Commit 08efa4e0 authored by igor@olga.mysql.com's avatar igor@olga.mysql.com

Fixed bug #26560.

The flag alias_name_used was not set on for the outer references
in subqueries. It resulted in replacement of any outer reference
resolved against an alias for a full field name when the frm 
representation of a view with a subquery was generated. 
If the subquery and the outer query referenced the same table in
their from lists this replacement effectively changed the meaning
of the view and led to wrong results for selects from this view. 

Modified several functions to ensure setting the right value of
the alias_name_used flag for outer references resolved against
aliases.
 
parent 6274ee84
......@@ -3135,4 +3135,41 @@ code COUNT(DISTINCT country)
100 2
DROP VIEW v1;
DROP TABLE t1;
CREATE TABLE t1 (
lid int NOT NULL PRIMARY KEY,
name char(10) NOT NULL
);
INSERT INTO t1 (lid, name) VALUES
(1, 'YES'), (2, 'NO');
CREATE TABLE t2 (
id int NOT NULL PRIMARY KEY,
gid int NOT NULL,
lid int NOT NULL,
dt date
);
INSERT INTO t2 (id, gid, lid, dt) VALUES
(1, 1, 1, '2007-01-01'),(2, 1, 2, '2007-01-02'),
(3, 2, 2, '2007-02-01'),(4, 2, 1, '2007-02-02');
SELECT DISTINCT t2.gid AS lgid,
(SELECT t1.name FROM t1, t2
WHERE t1.lid = t2.lid AND t2.gid = lgid
ORDER BY t2.dt DESC LIMIT 1
) as clid
FROM t2;
lgid clid
1 NO
2 YES
CREATE VIEW v1 AS
SELECT DISTINCT t2.gid AS lgid,
(SELECT t1.name FROM t1, t2
WHERE t1.lid = t2.lid AND t2.gid = lgid
ORDER BY t2.dt DESC LIMIT 1
) as clid
FROM t2;
SELECT * FROM v1;
lgid clid
1 NO
2 YES
DROP VIEW v1;
DROP table t1,t2;
End of 5.0 tests.
......@@ -3058,4 +3058,44 @@ SELECT code, COUNT(DISTINCT country) FROM v1 GROUP BY code ORDER BY MAX(id);
DROP VIEW v1;
DROP TABLE t1;
#
# Bug#26560: view using subquery with a reference to an outer alias
#
CREATE TABLE t1 (
lid int NOT NULL PRIMARY KEY,
name char(10) NOT NULL
);
INSERT INTO t1 (lid, name) VALUES
(1, 'YES'), (2, 'NO');
CREATE TABLE t2 (
id int NOT NULL PRIMARY KEY,
gid int NOT NULL,
lid int NOT NULL,
dt date
);
INSERT INTO t2 (id, gid, lid, dt) VALUES
(1, 1, 1, '2007-01-01'),(2, 1, 2, '2007-01-02'),
(3, 2, 2, '2007-02-01'),(4, 2, 1, '2007-02-02');
SELECT DISTINCT t2.gid AS lgid,
(SELECT t1.name FROM t1, t2
WHERE t1.lid = t2.lid AND t2.gid = lgid
ORDER BY t2.dt DESC LIMIT 1
) as clid
FROM t2;
CREATE VIEW v1 AS
SELECT DISTINCT t2.gid AS lgid,
(SELECT t1.name FROM t1, t2
WHERE t1.lid = t2.lid AND t2.gid = lgid
ORDER BY t2.dt DESC LIMIT 1
) as clid
FROM t2;
SELECT * FROM v1;
DROP VIEW v1;
DROP table t1,t2;
--echo End of 5.0 tests.
......@@ -3320,7 +3320,7 @@ 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;
enum_resolution_type resolution;
/*
Search for a column or derived column named as 'ref' in the SELECT
......@@ -3328,8 +3328,10 @@ resolve_ref_in_select_and_group(THD *thd, Item_ident *ref, SELECT_LEX *select)
*/
if (!(select_ref= find_item_in_list(ref, *(select->get_item_list()),
&counter, REPORT_EXCEPT_NOT_FOUND,
&not_used)))
&resolution)))
return NULL; /* Some error occurred. */
if (resolution == RESOLVED_AGAINST_ALIAS)
ref->alias_name_used= TRUE;
/* If this is a non-aggregated field inside HAVING, search in GROUP BY. */
if (select->having_fix_field && !ref->with_sum_func && group_list)
......@@ -3630,9 +3632,9 @@ Item_field::fix_outer_field(THD *thd, Field **from_field, Item **reference)
*ref= NULL; // Don't call set_properties()
rf= (place == IN_HAVING ?
new Item_ref(context, ref, (char*) table_name,
(char*) field_name) :
(char*) field_name, alias_name_used) :
new Item_direct_ref(context, ref, (char*) table_name,
(char*) field_name));
(char*) field_name, alias_name_used));
*ref= save;
if (!rf)
return -1;
......@@ -3750,12 +3752,14 @@ bool Item_field::fix_fields(THD *thd, Item **reference)
if (thd->lex->current_select->is_item_list_lookup)
{
uint counter;
bool not_used;
enum_resolution_type resolution;
Item** res= find_item_in_list(this, thd->lex->current_select->item_list,
&counter, REPORT_EXCEPT_NOT_FOUND,
&not_used);
&resolution);
if (!res)
return 1;
if (resolution == RESOLVED_AGAINST_ALIAS)
alias_name_used= TRUE;
if (res != (Item **)not_found_item)
{
if ((*res)->type() == Item::FIELD_ITEM)
......@@ -4898,10 +4902,12 @@ Item *Item_field::update_value_transformer(byte *select_arg)
Item_ref::Item_ref(Name_resolution_context *context_arg,
Item **item, const char *table_name_arg,
const char *field_name_arg)
const char *field_name_arg,
bool alias_name_used_arg)
:Item_ident(context_arg, NullS, table_name_arg, field_name_arg),
result_field(0), ref(item)
{
alias_name_used= alias_name_used_arg;
/*
This constructor used to create some internals references over fixed items
*/
......@@ -5184,11 +5190,13 @@ void Item_ref::set_properties()
*/
with_sum_func= (*ref)->with_sum_func;
unsigned_flag= (*ref)->unsigned_flag;
fixed= 1;
if (alias_name_used)
return;
if ((*ref)->type() == FIELD_ITEM)
alias_name_used= ((Item_ident *) (*ref))->alias_name_used;
else
alias_name_used= TRUE; // it is not field, so it is was resolved by alias
fixed= 1;
}
......@@ -5206,7 +5214,7 @@ void Item_ref::print(String *str)
if (ref)
{
if ((*ref)->type() != Item::CACHE_ITEM && ref_type() != VIEW_REF &&
ref_type() != OUTER_REF && name && alias_name_used)
!table_name && name && alias_name_used)
{
THD *thd= current_thd;
append_identifier(thd, str, name, (uint) strlen(name));
......
......@@ -1851,7 +1851,8 @@ public:
with Bar, and if we have a more broader set of problems like this.
*/
Item_ref(Name_resolution_context *context_arg, Item **item,
const char *table_name_arg, const char *field_name_arg);
const char *table_name_arg, const char *field_name_arg,
bool alias_name_used_arg= FALSE);
/* Constructor need to process subselect with temporary tables (see Item) */
Item_ref(THD *thd, Item_ref *item)
......@@ -1926,8 +1927,11 @@ class Item_direct_ref :public Item_ref
public:
Item_direct_ref(Name_resolution_context *context_arg, Item **item,
const char *table_name_arg,
const char *field_name_arg)
:Item_ref(context_arg, item, table_name_arg, field_name_arg) {}
const char *field_name_arg,
bool alias_name_used_arg= FALSE)
:Item_ref(context_arg, item, table_name_arg,
field_name_arg, alias_name_used_arg)
{}
/* Constructor need to process subselect with temporary tables (see Item) */
Item_direct_ref(THD *thd, Item_direct_ref *item) : Item_ref(thd, item) {}
......
......@@ -1008,9 +1008,29 @@ SQL_SELECT *make_select(TABLE *head, table_map const_tables,
table_map read_tables, COND *conds,
bool allow_null_cond, int *error);
extern Item **not_found_item;
/*
This enumeration type is used only by the function find_item_in_list
to return the info on how an item has been resolved against a list
of possibly aliased items.
The item can be resolved:
- against an alias name of the list's element (RESOLVED_AGAINST_ALIAS)
- against non-aliased field name of the list (RESOLVED_WITH_NO_ALIAS)
- against an aliased field name of the list (RESOLVED_BEHIND_ALIAS)
- ignoring the alias name in cases when SQL requires to ignore aliases
(e.g. when the resolved field reference contains a table name or
when the resolved item is an expression) (RESOLVED_IGNORING_ALIAS)
*/
enum enum_resolution_type {
NOT_RESOLVED=0,
RESOLVED_IGNORING_ALIAS,
RESOLVED_BEHIND_ALIAS,
RESOLVED_WITH_NO_ALIAS,
RESOLVED_AGAINST_ALIAS
};
Item ** find_item_in_list(Item *item, List<Item> &items, uint *counter,
find_item_error_report_type report_error,
bool *unaliased);
enum_resolution_type *resolution);
bool get_key_map_from_key_list(key_map *map, TABLE *table,
List<String> *index_list);
bool insert_fields(THD *thd, Name_resolution_context *context,
......
......@@ -3468,10 +3468,13 @@ find_field_in_tables(THD *thd, Item_ident *item,
return not_found_item, report other errors,
return 0
IGNORE_ERRORS Do not report errors, return 0 if error
unaliased Set to true if item is field which was found
by original field name and not by its alias
in item list. Set to false otherwise.
resolution Set to the resolution type if the item is found
(it says whether the item is resolved
against an alias name,
or as a field name without alias,
or as a field hidden by alias,
or ignoring alias)
RETURN VALUES
0 Item is not found or item is not unique,
error message is reported
......@@ -3487,7 +3490,8 @@ Item **not_found_item= (Item**) 0x1;
Item **
find_item_in_list(Item *find, List<Item> &items, uint *counter,
find_item_error_report_type report_error, bool *unaliased)
find_item_error_report_type report_error,
enum_resolution_type *resolution)
{
List_iterator<Item> li(items);
Item **found=0, **found_unaliased= 0, *item;
......@@ -3501,10 +3505,9 @@ find_item_in_list(Item *find, List<Item> &items, uint *counter,
*/
bool is_ref_by_name= 0;
uint unaliased_counter;
LINT_INIT(unaliased_counter); // Dependent on found_unaliased
*unaliased= FALSE;
*resolution= NOT_RESOLVED;
is_ref_by_name= (find->type() == Item::FIELD_ITEM ||
find->type() == Item::REF_ITEM);
......@@ -3571,63 +3574,77 @@ find_item_in_list(Item *find, List<Item> &items, uint *counter,
}
found_unaliased= li.ref();
unaliased_counter= i;
*resolution= RESOLVED_IGNORING_ALIAS;
if (db_name)
break; // Perfect match
}
}
else if (!my_strcasecmp(system_charset_info, item_field->name,
field_name))
{
/*
If table name was not given we should scan through aliases
(or non-aliased fields) first. We are also checking unaliased
name of the field in then next else-if, to be able to find
instantly field (hidden by alias) if no suitable alias (or
non-aliased field) was found.
*/
if (found)
{
if ((*found)->eq(item, 0))
continue; // Same field twice
if (report_error != IGNORE_ERRORS)
my_error(ER_NON_UNIQ_ERROR, MYF(0),
find->full_name(), current_thd->where);
return (Item**) 0;
}
found= li.ref();
*counter= i;
}
else if (!my_strcasecmp(system_charset_info, item_field->field_name,
field_name))
else
{
/*
We will use un-aliased field or react on such ambiguities only if
we won't be able to find aliased field.
Again if we have ambiguity with field outside of select list
we should prefer fields from select list.
*/
if (found_unaliased)
int fname_cmp= my_strcasecmp(system_charset_info,
item_field->field_name,
field_name);
if (!my_strcasecmp(system_charset_info,
item_field->name,field_name))
{
if ((*found_unaliased)->eq(item, 0))
continue; // Same field twice
found_unaliased_non_uniq= 1;
/*
If table name was not given we should scan through aliases
and non-aliased fields first. We are also checking unaliased
name of the field in then next else-if, to be able to find
instantly field (hidden by alias) if no suitable alias or
non-aliased field was found.
*/
if (found)
{
if ((*found)->eq(item, 0))
continue; // Same field twice
if (report_error != IGNORE_ERRORS)
my_error(ER_NON_UNIQ_ERROR, MYF(0),
find->full_name(), current_thd->where);
return (Item**) 0;
}
found= li.ref();
*counter= i;
*resolution= fname_cmp ? RESOLVED_AGAINST_ALIAS:
RESOLVED_WITH_NO_ALIAS;
}
else
else if (!fname_cmp)
{
/*
We will use non-aliased field or react on such ambiguities only if
we won't be able to find aliased field.
Again if we have ambiguity with field outside of select list
we should prefer fields from select list.
*/
if (found_unaliased)
{
if ((*found_unaliased)->eq(item, 0))
continue; // Same field twice
found_unaliased_non_uniq= 1;
}
found_unaliased= li.ref();
unaliased_counter= i;
}
}
}
else if (!table_name && (find->eq(item,0) ||
is_ref_by_name && find->name && item->name &&
!my_strcasecmp(system_charset_info,
item->name,find->name)))
{
found= li.ref();
*counter= i;
break;
}
else if (!table_name)
{
if (is_ref_by_name && find->name && item->name &&
!my_strcasecmp(system_charset_info,item->name,find->name))
{
found= li.ref();
*counter= i;
*resolution= RESOLVED_AGAINST_ALIAS;
break;
}
else if (find->eq(item,0))
{
found= li.ref();
*counter= i;
*resolution= RESOLVED_IGNORING_ALIAS;
break;
}
}
}
if (!found)
{
......@@ -3642,7 +3659,7 @@ find_item_in_list(Item *find, List<Item> &items, uint *counter,
{
found= found_unaliased;
*counter= unaliased_counter;
*unaliased= TRUE;
*resolution= RESOLVED_BEHIND_ALIAS;
}
}
if (found)
......
......@@ -13158,7 +13158,7 @@ find_order_in_list(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables,
Item **select_item; /* The corresponding item from the SELECT clause. */
Field *from_field; /* The corresponding field from the FROM clause. */
uint counter;
bool unaliased;
enum_resolution_type resolution;
/*
Local SP variables may be int but are expressions, not positions.
......@@ -13181,7 +13181,7 @@ find_order_in_list(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables,
}
/* Lookup the current GROUP/ORDER field in the SELECT clause. */
select_item= find_item_in_list(order_item, fields, &counter,
REPORT_EXCEPT_NOT_FOUND, &unaliased);
REPORT_EXCEPT_NOT_FOUND, &resolution);
if (!select_item)
return TRUE; /* The item is not unique, or some other error occured. */
......@@ -13195,7 +13195,7 @@ find_order_in_list(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables,
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 &&
if (resolution == RESOLVED_BEHIND_ALIAS && !order_item->fixed &&
order_item->fix_fields(thd, order->item))
return TRUE;
......@@ -13429,7 +13429,7 @@ setup_new_fields(THD *thd, List<Item> &fields,
thd->set_query_id=1; // Not really needed, but...
uint counter;
bool not_used;
enum_resolution_type not_used;
for (; new_field ; new_field= new_field->next)
{
if ((item= find_item_in_list(*new_field->item, fields, &counter,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment