mariadb/sql/item_row.cc
Igor Babaev eb057dce20 MDEV-15035 Wrong results when calling a stored procedure
multiple times with different arguments.

If the ON expression of an outer join is an OR formula with one
of the disjunct being a constant formula then the expression
cannot be null-rejected if the constant formula is true. Otherwise
it can be null-rejected and if so the outer join can be converted
into inner join. This optimization was added in the patch for
mdev-4817. Yet the code had a defect: if the query was used in
a stored procedure with parameters and the constant item contained
some of them then the value of this constant item depended on the
values of the parameters. With some parameters it may be true,
for others not. The validity of conversion to inner join is checked
only once and it happens only for the first call of procedure.
So if the  parameters in the first call allowed the conversion it
was done and next calls used the transformed query though there
could be calls whose parameters made the conversion invalid.

Fixed by cheking whether the constant disjunct in the ON expression
originally contained an SP parameter. If so the expression is not
considered as null-rejected. For this check a new item's attribute
was intruduced: Item::with_param. It is calculated for each item
by fix fields() functions.
Also moved the call of optimize_constant_subqueries() in
JOIN::optimize after the call of simplify_joins(). The reason
for this is that after the optimization introduced by the patch
for mdev-4817 simplify_joins() can use the results of execution
of non-expensive constant subqueries and this is not valid.
2018-04-25 09:22:06 -07:00

262 lines
6.1 KiB
C++

/*
Copyright (c) 2002, 2011, Oracle and/or its affiliates.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
#include "sql_priv.h"
/*
It is necessary to include set_var.h instead of item.h because there
are dependencies on include order for set_var.h and item.h. This
will be resolved later.
*/
#include "sql_class.h" // THD, set_var.h: THD
#include "set_var.h"
/**
Row items used for comparing rows and IN operations on rows:
@verbatim
(a, b, c) > (10, 10, 30)
(a, b, c) = (select c, d, e, from t1 where x=12)
(a, b, c) IN ((1,2,2), (3,4,5), (6,7,8)
(a, b, c) IN (select c, d, e, from t1)
@endverbatim
@todo
think placing 2-3 component items in item (as it done for function
*/
Item_row::Item_row(List<Item> &arg):
Item(), used_tables_cache(0), not_null_tables_cache(0),
const_item_cache(1), with_null(0)
{
//TODO: think placing 2-3 component items in item (as it done for function)
if ((arg_count= arg.elements))
{
items= (Item**) sql_alloc(sizeof(Item*)*arg_count);
if (!items)
{
arg_count= 0;
return;
}
}
else
items= 0;
List_iterator<Item> li(arg);
uint i= 0;
Item *item;
while ((item= li++))
{
items[i]= item;
i++;
}
}
Item_row::Item_row(Item *item):
Item(),
used_tables_cache(0),
not_null_tables_cache(0),
arg_count(item->cols()),
const_item_cache(1),
with_null(0)
{
items= (Item**) sql_alloc(sizeof(Item*) * arg_count);
if (!items)
{
arg_count= 0;
return;
}
for (uint i= 0; i < arg_count; i++)
{
items[i]= item->element_index(i);
}
}
void Item_row::illegal_method_call(const char *method)
{
DBUG_ENTER("Item_row::illegal_method_call");
DBUG_PRINT("error", ("!!! %s method was called for row item", method));
DBUG_ASSERT(0);
my_error(ER_OPERAND_COLUMNS, MYF(0), 1);
DBUG_VOID_RETURN;
}
bool Item_row::fix_fields(THD *thd, Item **ref)
{
DBUG_ASSERT(fixed == 0);
null_value= 0;
maybe_null= 0;
Item **arg, **arg_end;
for (arg= items, arg_end= items+arg_count; arg != arg_end ; arg++)
{
if (!(*arg)->fixed &&
(*arg)->fix_fields(thd, arg))
return TRUE;
// we can't assign 'item' before, because fix_fields() can change arg
Item *item= *arg;
used_tables_cache |= item->used_tables();
const_item_cache&= item->const_item() && !with_null;
not_null_tables_cache|= item->not_null_tables();
if (const_item_cache)
{
if (item->cols() > 1)
with_null|= item->null_inside();
else
{
if (item->is_null())
with_null|= 1;
}
}
maybe_null|= item->maybe_null;
with_sum_func= with_sum_func || item->with_sum_func;
with_field= with_field || item->with_field;
with_subselect|= item->with_subselect;
with_param|= item->with_param;
}
fixed= 1;
return FALSE;
}
bool
Item_row::eval_not_null_tables(uchar *opt_arg)
{
Item **arg,**arg_end;
not_null_tables_cache= 0;
if (arg_count)
{
for (arg= items, arg_end= items+arg_count; arg != arg_end ; arg++)
{
not_null_tables_cache|= (*arg)->not_null_tables();
}
}
return FALSE;
}
void Item_row::cleanup()
{
DBUG_ENTER("Item_row::cleanup");
Item::cleanup();
/* Reset to the original values */
used_tables_cache= 0;
const_item_cache= 1;
with_null= 0;
DBUG_VOID_RETURN;
}
void Item_row::split_sum_func(THD *thd, Item **ref_pointer_array,
List<Item> &fields)
{
Item **arg, **arg_end;
for (arg= items, arg_end= items+arg_count; arg != arg_end ; arg++)
(*arg)->split_sum_func2(thd, ref_pointer_array, fields, arg, TRUE);
}
void Item_row::update_used_tables()
{
used_tables_cache= 0;
const_item_cache= 1;
for (uint i= 0; i < arg_count; i++)
{
items[i]->update_used_tables();
used_tables_cache|= items[i]->used_tables();
const_item_cache&= items[i]->const_item();
}
}
void Item_row::fix_after_pullout(st_select_lex *new_parent, Item **ref)
{
used_tables_cache= 0;
const_item_cache= 1;
not_null_tables_cache= 0;
for (uint i= 0; i < arg_count; i++)
{
items[i]->fix_after_pullout(new_parent, &items[i]);
used_tables_cache|= items[i]->used_tables();
const_item_cache&= items[i]->const_item();
not_null_tables_cache|= items[i]->not_null_tables();
}
}
bool Item_row::check_cols(uint c)
{
if (c != arg_count)
{
my_error(ER_OPERAND_COLUMNS, MYF(0), c);
return 1;
}
return 0;
}
void Item_row::print(String *str, enum_query_type query_type)
{
str->append('(');
for (uint i= 0; i < arg_count; i++)
{
if (i)
str->append(',');
items[i]->print(str, query_type);
}
str->append(')');
}
bool Item_row::walk(Item_processor processor, bool walk_subquery, uchar *arg)
{
for (uint i= 0; i < arg_count; i++)
{
if (items[i]->walk(processor, walk_subquery, arg))
return 1;
}
return (this->*processor)(arg);
}
Item *Item_row::transform(Item_transformer transformer, uchar *arg)
{
DBUG_ASSERT(!current_thd->stmt_arena->is_stmt_prepare());
for (uint i= 0; i < arg_count; i++)
{
Item *new_item= items[i]->transform(transformer, arg);
if (!new_item)
return 0;
/*
THD::change_item_tree() should be called only if the tree was
really transformed, i.e. when a new item has been created.
Otherwise we'll be allocating a lot of unnecessary memory for
change records at each execution.
*/
if (items[i] != new_item)
current_thd->change_item_tree(&items[i], new_item);
}
return (this->*transformer)(arg);
}
void Item_row::bring_value()
{
for (uint i= 0; i < arg_count; i++)
items[i]->bring_value();
}