Commit 9f1a3d10 authored by unknown's avatar unknown

Fix bug #15706 find_field_in_tables() returns field from outer select

If item->cached_table is set, find_field_in_tables() returns found field
even if it doesn't belong to current select. Because Item_field::fix_fields
doesn't expect such behaviour, reported bug occurs.

Item_field::fix_fields() was modifed to detect when find_field_in_tables() 
can return field from outer select and process such fields accordingly.
In order to ease this code which was searching and processing outed fields was
moved into separate function called Item_field::fix_outer_field().


sql/item_subselect.h:
  Fixed bug #15706: find_field_in_tables() returns field from outer select
  Item_field::fix_outer_field() was marked as friend to Item_subselect class.
sql/item.h:
  Fixed bug #15706: find_field_in_tables() returns field from outer select
  fix_outer_field() function is added to the Item_field class.
sql/item.cc:
  Fixed bug #15706: find_field_in_tables() returns field from outer select
  
  Item_ref::fix_fields() and Item_field::fix_fields() were modifed to detect when
  find_field_in_tables() can return field from outer select and process such
  fields accordingly.
  In order to ease this, code Item_field::fix_fields() which was searching and
  processing outer fields was moved into separate function called
  Item_field::fix_outer_field().
  To the Item_field::fix_field() added a loop for finding context for found field.
mysql-test/t/disabled.def:
  Fixed bug #15706: find_field_in_tables() returns field from outer select
  Enable subselect test
parent a6698800
......@@ -12,4 +12,3 @@
sp-goto : GOTO is currently is disabled - will be fixed in the future
kill : Unstable test case, bug#9712
subselect : Bug#15706
......@@ -3210,6 +3210,252 @@ resolve_ref_in_select_and_group(THD *thd, Item_ident *ref, SELECT_LEX *select)
}
/*
Resolve the name of an outer select column reference.
SYNOPSIS
Item_field::fix_outer_field()
thd [in] current thread
from_field [in/out] found field reference or (Field*)not_found_field
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 outer selects that contain current select.
NOTES
This is the inner loop of Item_field::fix_fields:
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 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.
}
IMPLEMENTATION
In prepared statements, because of cache, find_field_in_tables()
can resolve fields even if they don't belong to current context.
In this case this method only finds appropriate context and marks
current select as dependent. The found reference of field should be
provided in 'from_field'.
RETURN
1 - column succefully resolved and fix_fields() should continue.
0 - column fully fixed and fix_fields() should return FALSE
-1 - error occured
*/
int
Item_field::fix_outer_field(THD *thd, Field **from_field, Item **reference)
{
enum_parsing_place place= NO_MATTER;
bool field_found= (*from_field != not_found_field);
bool upward_lookup= FALSE;
/*
If there are outer contexts (outer selects, but current select is
not derived table or view) try to resolve this reference in the
outer contexts.
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.
*/
Name_resolution_context *last_checked_context= context;
Item **ref= (Item **) not_found_item;
Name_resolution_context *outer_context= context->outer_context;
for (;
outer_context;
outer_context= outer_context->outer_context)
{
SELECT_LEX *select= outer_context->select_lex;
Item_subselect *prev_subselect_item=
last_checked_context->select_lex->master_unit()->item;
last_checked_context= outer_context;
upward_lookup= TRUE;
place= prev_subselect_item->parsing_place;
/*
If outer_field is set, field was already found by first call
to find_field_in_tables(). Only need to find appropriate context.
*/
if (field_found && outer_context->select_lex !=
cached_table->select_lex)
continue;
/*
In case of a view, find_field_in_tables() writes the pointer to
the found view field into '*reference', in other words, it
substitutes this Item_field with the found expression.
*/
if (field_found || (*from_field= find_field_in_tables(thd, this,
outer_context->
first_name_resolution_table,
outer_context->
last_name_resolution_table,
reference,
IGNORE_EXCEPT_NON_UNIQUE,
TRUE, TRUE)) !=
not_found_field)
{
if (*from_field)
{
if (*from_field != view_ref_found)
{
prev_subselect_item->used_tables_cache|= (*from_field)->table->map;
prev_subselect_item->const_item_cache= 0;
if (thd->lex->in_sum_func &&
thd->lex->in_sum_func->nest_level ==
thd->lex->current_select->nest_level)
{
Item::Type type= (*reference)->type();
set_if_bigger(thd->lex->in_sum_func->max_arg_level,
select->nest_level);
set_field(*from_field);
fixed= 1;
mark_as_dependent(thd, last_checked_context->select_lex,
context->select_lex, this,
((type == REF_ITEM || type == FIELD_ITEM) ?
(Item_ident*) (*reference) : 0));
return 0;
}
}
else
{
Item::Type type= (*reference)->type();
prev_subselect_item->used_tables_cache|=
(*reference)->used_tables();
prev_subselect_item->const_item_cache&=
(*reference)->const_item();
mark_as_dependent(thd, last_checked_context->select_lex,
context->select_lex, this,
((type == REF_ITEM || type == FIELD_ITEM) ?
(Item_ident*) (*reference) :
0));
/*
A reference to a view field had been found and we
substituted it instead of this Item (find_field_in_tables
does it by assigning the new value to *reference), so now
we can return from this function.
*/
return 0;
}
}
break;
}
/* Search in SELECT and GROUP lists of the outer select. */
if (outer_context->resolve_in_select_list)
{
if (!(ref= resolve_ref_in_select_and_group(thd, this, select)))
return -1; /* Some error occurred (e.g. ambiguous names). */
if (ref != not_found_item)
{
DBUG_ASSERT(*ref && (*ref)->fixed);
prev_subselect_item->used_tables_cache|= (*ref)->used_tables();
prev_subselect_item->const_item_cache&= (*ref)->const_item();
break;
}
}
/*
Reference is not found in this select => this subquery depend on
outer select (or we just trying to find wrong identifier, in this
case it does not matter which used tables bits we set)
*/
prev_subselect_item->used_tables_cache|= OUTER_REF_TABLE_BIT;
prev_subselect_item->const_item_cache= 0;
}
DBUG_ASSERT(ref != 0);
if (!*from_field)
return -1;
if (ref == not_found_item && *from_field == not_found_field)
{
if (upward_lookup)
{
// We can't say exactly what absent table or field
my_error(ER_BAD_FIELD_ERROR, MYF(0), full_name(), thd->where);
}
else
{
/* Call find_field_in_tables only to report the error */
find_field_in_tables(thd, this,
context->first_name_resolution_table,
context->last_name_resolution_table,
reference, REPORT_ALL_ERRORS,
!any_privileges &&
TRUE, TRUE);
}
return -1;
}
else if (ref != not_found_item)
{
Item *save;
Item_ref *rf;
/* Should have been checked in resolve_ref_in_select_and_group(). */
DBUG_ASSERT(*ref && (*ref)->fixed);
/*
Here, a subset of actions performed by Item_ref::set_properties
is not enough. So we pass ptr to NULL into Item_[direct]_ref
constructor, so no initialization is performed, and call
fix_fields() below.
*/
save= *ref;
*ref= NULL; // Don't call set_properties()
rf= (place == IN_HAVING ?
new Item_ref(context, ref, (char*) table_name,
(char*) field_name) :
new Item_direct_ref(context, ref, (char*) table_name,
(char*) field_name));
*ref= save;
if (!rf)
return -1;
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()
*/
DBUG_ASSERT(!rf->fixed); // Assured by Item_ref()
if (rf->fix_fields(thd, reference) || rf->check_cols(1))
return -1;
mark_as_dependent(thd, last_checked_context->select_lex,
context->select_lex, this,
rf);
return 0;
}
else
{
mark_as_dependent(thd, last_checked_context->select_lex,
context->select_lex,
this, this);
if (last_checked_context->select_lex->having_fix_field)
{
Item_ref *rf;
rf= new Item_ref(context,
(cached_table->db[0] ? cached_table->db : 0),
(char*) cached_table->alias, (char*) field_name);
if (!rf)
return -1;
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()
*/
DBUG_ASSERT(!rf->fixed); // Assured by Item_ref()
if (rf->fix_fields(thd, reference) || rf->check_cols(1))
return -1;
return 0;
}
}
return 1;
}
/*
Resolve the name of a column reference.
......@@ -3257,12 +3503,11 @@ resolve_ref_in_select_and_group(THD *thd, Item_ident *ref, SELECT_LEX *select)
bool Item_field::fix_fields(THD *thd, Item **reference)
{
enum_parsing_place place= NO_MATTER;
DBUG_ASSERT(fixed == 0);
if (!field) // If field is not checked
{
bool upward_lookup= FALSE;
Field *from_field= (Field *)not_found_field;
bool outer_fixed= false;
/*
In case of view, find_field_in_tables() write pointer to view field
expression to 'reference', i.e. it substitute that expression instead
......@@ -3277,7 +3522,7 @@ bool Item_field::fix_fields(THD *thd, Item **reference)
TRUE)) ==
not_found_field)
{
int ret;
/* Look up in current select's item_list to find aliased fields */
if (thd->lex->current_select->is_item_list_lookup)
{
......@@ -3292,197 +3537,11 @@ bool Item_field::fix_fields(THD *thd, Item **reference)
return 0;
}
}
/*
If there are outer contexts (outer selects, but current select is
not derived table or view) try to resolve this reference in the
outer contexts.
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.
*/
Name_resolution_context *last_checked_context= context;
Item **ref= (Item **) not_found_item;
Name_resolution_context *outer_context= context->outer_context;
for (;
outer_context;
outer_context= outer_context->outer_context)
{
SELECT_LEX *select= outer_context->select_lex;
Item_subselect *prev_subselect_item=
last_checked_context->select_lex->master_unit()->item;
last_checked_context= outer_context;
upward_lookup= TRUE;
place= prev_subselect_item->parsing_place;
/*
In case of a view, find_field_in_tables() writes the pointer to
the found view field into '*reference', in other words, it
substitutes this Item_field with the found expression.
*/
if ((from_field= find_field_in_tables(thd, this,
outer_context->
first_name_resolution_table,
outer_context->
last_name_resolution_table,
reference,
IGNORE_EXCEPT_NON_UNIQUE,
TRUE, TRUE)) !=
not_found_field)
{
if (from_field)
{
if (from_field != view_ref_found)
{
prev_subselect_item->used_tables_cache|= from_field->table->map;
prev_subselect_item->const_item_cache= 0;
if (thd->lex->in_sum_func &&
thd->lex->in_sum_func->nest_level ==
thd->lex->current_select->nest_level)
{
Item::Type type= (*reference)->type();
set_if_bigger(thd->lex->in_sum_func->max_arg_level,
select->nest_level);
set_field(from_field);
fixed= 1;
mark_as_dependent(thd, last_checked_context->select_lex,
context->select_lex, this,
((type == REF_ITEM || type == FIELD_ITEM) ?
(Item_ident*) (*reference) : 0));
return FALSE;
}
}
else
{
Item::Type type= (*reference)->type();
prev_subselect_item->used_tables_cache|=
(*reference)->used_tables();
prev_subselect_item->const_item_cache&=
(*reference)->const_item();
mark_as_dependent(thd, last_checked_context->select_lex,
context->select_lex, this,
((type == REF_ITEM || type == FIELD_ITEM) ?
(Item_ident*) (*reference) :
0));
/*
A reference to a view field had been found and we
substituted it instead of this Item (find_field_in_tables
does it by assigning the new value to *reference), so now
we can return from this function.
*/
return FALSE;
}
}
break;
}
/* Search in SELECT and GROUP lists of the outer select. */
if (outer_context->resolve_in_select_list)
{
if (!(ref= resolve_ref_in_select_and_group(thd, this, select)))
goto error; /* Some error occurred (e.g. ambiguous names). */
if (ref != not_found_item)
{
DBUG_ASSERT(*ref && (*ref)->fixed);
prev_subselect_item->used_tables_cache|= (*ref)->used_tables();
prev_subselect_item->const_item_cache&= (*ref)->const_item();
break;
}
}
/*
Reference is not found in this select => this subquery depend on
outer select (or we just trying to find wrong identifier, in this
case it does not matter which used tables bits we set)
*/
prev_subselect_item->used_tables_cache|= OUTER_REF_TABLE_BIT;
prev_subselect_item->const_item_cache= 0;
}
DBUG_ASSERT(ref != 0);
if (!from_field)
goto error;
if (ref == not_found_item && from_field == not_found_field)
{
if (upward_lookup)
{
// We can't say exactly what absent table or field
my_error(ER_BAD_FIELD_ERROR, MYF(0), full_name(), thd->where);
}
else
{
/* Call find_field_in_tables only to report the error */
find_field_in_tables(thd, this,
context->first_name_resolution_table,
context->last_name_resolution_table,
reference, REPORT_ALL_ERRORS,
!any_privileges &&
TRUE, TRUE);
}
goto error;
}
else if (ref != not_found_item)
{
Item *save;
Item_ref *rf;
/* Should have been checked in resolve_ref_in_select_and_group(). */
DBUG_ASSERT(*ref && (*ref)->fixed);
/*
Here, a subset of actions performed by Item_ref::set_properties
is not enough. So we pass ptr to NULL into Item_[direct]_ref
constructor, so no initialization is performed, and call
fix_fields() below.
*/
save= *ref;
*ref= NULL; // Don't call set_properties()
rf= (place == IN_HAVING ?
new Item_ref(context, ref, (char*) table_name,
(char*) field_name) :
new Item_direct_ref(context, ref, (char*) table_name,
(char*) field_name));
*ref= save;
if (!rf)
goto error;
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()
*/
DBUG_ASSERT(!rf->fixed); // Assured by Item_ref()
if (rf->fix_fields(thd, reference) || rf->check_cols(1))
goto error;
mark_as_dependent(thd, last_checked_context->select_lex,
context->select_lex, this,
rf);
return FALSE;
}
else
{
mark_as_dependent(thd, last_checked_context->select_lex,
context->select_lex,
this, this);
if (last_checked_context->select_lex->having_fix_field)
{
Item_ref *rf;
rf= new Item_ref(context,
(cached_table->db[0] ? cached_table->db : 0),
(char*) cached_table->alias, (char*) field_name);
if (!rf)
goto error;
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()
*/
DBUG_ASSERT(!rf->fixed); // Assured by Item_ref()
if (rf->fix_fields(thd, reference) || rf->check_cols(1))
goto error;
return FALSE;
}
}
if ((ret= fix_outer_field(thd, &from_field, reference)) < 0)
goto error;
else if (!ret)
return FALSE;
outer_fixed= TRUE;
}
else if (!from_field)
goto error;
......@@ -3502,6 +3561,17 @@ bool Item_field::fix_fields(THD *thd, Item **reference)
if (from_field == view_ref_found)
return FALSE;
if (!outer_fixed && cached_table && cached_table->select_lex &&
context->select_lex &&
cached_table->select_lex != context->select_lex)
{
int ret;
if ((ret= fix_outer_field(thd, &from_field, reference)) < 0)
goto error;
else if (!ret)
return FALSE;
}
set_field(from_field);
if (thd->lex->in_sum_func &&
thd->lex->in_sum_func->nest_level ==
......@@ -4620,6 +4690,25 @@ bool Item_ref::fix_fields(THD *thd, Item **reference)
}
if (from_field != not_found_field)
{
if (cached_table && cached_table->select_lex &&
outer_context->select_lex &&
cached_table->select_lex != outer_context->select_lex)
{
/*
Due to cache, find_field_in_tables() can return field which
doesn't belong to provided outer_context. In this case we have
to find proper field context in order to fix field correcly.
*/
do
{
outer_context= outer_context->outer_context;
select= outer_context->select_lex;
prev_subselect_item=
last_checked_context->select_lex->master_unit()->item;
last_checked_context= outer_context;
} while (outer_context && outer_context->select_lex &&
cached_table->select_lex != outer_context->select_lex);
}
prev_subselect_item->used_tables_cache|= from_field->table->map;
prev_subselect_item->const_item_cache= 0;
break;
......
......@@ -1161,6 +1161,7 @@ class Item_field :public Item_ident
inline uint32 max_disp_length() { return field->max_length(); }
Item_field *filed_for_view_update() { return this; }
Item *safe_charset_converter(CHARSET_INFO *tocs);
int fix_outer_field(THD *thd, Field **field, Item **reference);
friend class Item_default_value;
friend class Item_insert_value;
friend class st_select_lex_unit;
......
......@@ -125,6 +125,7 @@ class Item_subselect :public Item_result_field
friend class select_subselect;
friend class Item_in_optimizer;
friend bool Item_field::fix_fields(THD *, Item **);
friend int Item_field::fix_outer_field(THD *, Field **, Item **);
friend bool Item_ref::fix_fields(THD *, Item **);
friend void mark_select_range_as_dependent(THD*,
st_select_lex*, st_select_lex*,
......
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