7978 lines
217 KiB
C++
7978 lines
217 KiB
C++
/* Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
|
|
|
|
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 */
|
|
|
|
|
|
/**
|
|
@file
|
|
|
|
@brief
|
|
This file defines all compare functions
|
|
*/
|
|
|
|
#include <m_ctype.h>
|
|
#include "sql_select.h"
|
|
#include "sql_optimizer.h" // JOIN_TAB
|
|
#include "sql_parse.h" // check_stack_overrun
|
|
#include "sql_time.h" // make_truncated_value_warning
|
|
#include "opt_trace.h"
|
|
#include "parse_tree_helpers.h"
|
|
#include "template_utils.h"
|
|
#include "item_json_func.h" // json_value, get_json_atom_wrapper
|
|
|
|
#include <algorithm>
|
|
using std::min;
|
|
using std::max;
|
|
#include "aggregate_check.h"
|
|
|
|
static bool convert_constant_item(THD *, Item_field *, Item **);
|
|
static longlong
|
|
get_year_value(THD *thd, Item ***item_arg, Item **cache_arg,
|
|
const Item *warn_item, bool *is_null);
|
|
|
|
static Item_result item_store_type(Item_result a, Item *item,
|
|
my_bool unsigned_flag)
|
|
{
|
|
Item_result b= item->result_type();
|
|
|
|
if (a == STRING_RESULT || b == STRING_RESULT)
|
|
return STRING_RESULT;
|
|
else if (a == REAL_RESULT || b == REAL_RESULT)
|
|
return REAL_RESULT;
|
|
else if (a == DECIMAL_RESULT || b == DECIMAL_RESULT ||
|
|
unsigned_flag != item->unsigned_flag)
|
|
return DECIMAL_RESULT;
|
|
else
|
|
return INT_RESULT;
|
|
}
|
|
|
|
static void agg_result_type(Item_result *type, my_bool *unsigned_flag,
|
|
Item **items, uint nitems)
|
|
{
|
|
Item **item, **item_end;
|
|
|
|
*type= STRING_RESULT;
|
|
*unsigned_flag= FALSE;
|
|
/* Skip beginning NULL items */
|
|
for (item= items, item_end= item + nitems; item < item_end; item++)
|
|
{
|
|
if ((*item)->type() != Item::NULL_ITEM)
|
|
{
|
|
*type= (*item)->result_type();
|
|
*unsigned_flag= (*item)->unsigned_flag;
|
|
item++;
|
|
break;
|
|
}
|
|
}
|
|
/* Combine result types. Note: NULL items don't affect the result */
|
|
for (; item < item_end; item++)
|
|
{
|
|
if ((*item)->type() != Item::NULL_ITEM)
|
|
{
|
|
*type= item_store_type(*type, *item, *unsigned_flag);
|
|
*unsigned_flag= *unsigned_flag && (*item)->unsigned_flag;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
Compare row signature of two expressions
|
|
|
|
SYNOPSIS:
|
|
cmp_row_type()
|
|
item1 the first expression
|
|
item2 the second expression
|
|
|
|
DESCRIPTION
|
|
The function checks that two expressions have compatible row signatures
|
|
i.e. that the number of columns they return are the same and that if they
|
|
are both row expressions then each component from the first expression has
|
|
a row signature compatible with the signature of the corresponding component
|
|
of the second expression.
|
|
|
|
RETURN VALUES
|
|
1 type incompatibility has been detected
|
|
0 otherwise
|
|
*/
|
|
|
|
static int cmp_row_type(Item* item1, Item* item2)
|
|
{
|
|
uint n= item1->cols();
|
|
if (item2->check_cols(n))
|
|
return 1;
|
|
for (uint i=0; i<n; i++)
|
|
{
|
|
if (item2->element_index(i)->check_cols(item1->element_index(i)->cols()) ||
|
|
(item1->element_index(i)->result_type() == ROW_RESULT &&
|
|
cmp_row_type(item1->element_index(i), item2->element_index(i))))
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
Aggregates result types from the array of items.
|
|
|
|
SYNOPSIS:
|
|
agg_cmp_type()
|
|
type [out] the aggregated type
|
|
items array of items to aggregate the type from
|
|
nitems number of items in the array
|
|
|
|
DESCRIPTION
|
|
This function aggregates result types from the array of items. Found type
|
|
supposed to be used later for comparison of values of these items.
|
|
Aggregation itself is performed by the item_cmp_type() function.
|
|
@param[out] type the aggregated type
|
|
@param items array of items to aggregate the type from
|
|
@param nitems number of items in the array
|
|
|
|
@retval
|
|
1 type incompatibility has been detected
|
|
@retval
|
|
0 otherwise
|
|
*/
|
|
|
|
static int agg_cmp_type(Item_result *type, Item **items, uint nitems)
|
|
{
|
|
uint i;
|
|
type[0]= items[0]->result_type();
|
|
for (i= 1 ; i < nitems ; i++)
|
|
{
|
|
type[0]= item_cmp_type(type[0], items[i]->result_type());
|
|
/*
|
|
When aggregating types of two row expressions we have to check
|
|
that they have the same cardinality and that each component
|
|
of the first row expression has a compatible row signature with
|
|
the signature of the corresponding component of the second row
|
|
expression.
|
|
*/
|
|
if (type[0] == ROW_RESULT && cmp_row_type(items[0], items[i]))
|
|
return 1; // error found: invalid usage of rows
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
@brief Aggregates field types from the array of items.
|
|
|
|
@param[in] items array of items to aggregate the type from
|
|
@paran[in] nitems number of items in the array
|
|
|
|
@details This function aggregates field types from the array of items.
|
|
Found type is supposed to be used later as the result field type
|
|
of a multi-argument function.
|
|
Aggregation itself is performed by the Field::field_type_merge()
|
|
function.
|
|
|
|
@note The term "aggregation" is used here in the sense of inferring the
|
|
result type of a function from its argument types.
|
|
|
|
@return aggregated field type.
|
|
*/
|
|
|
|
enum_field_types agg_field_type(Item **items, uint nitems)
|
|
{
|
|
uint i;
|
|
if (!nitems || items[0]->result_type() == ROW_RESULT )
|
|
return (enum_field_types)-1;
|
|
enum_field_types res= items[0]->field_type();
|
|
for (i= 1 ; i < nitems ; i++)
|
|
res= Field::field_type_merge(res, items[i]->field_type());
|
|
return real_type_to_type(res);
|
|
}
|
|
|
|
/*
|
|
Collects different types for comparison of first item with each other items
|
|
|
|
SYNOPSIS
|
|
collect_cmp_types()
|
|
items Array of items to collect types from
|
|
nitems Number of items in the array
|
|
skip_nulls Don't collect types of NULL items if TRUE
|
|
|
|
DESCRIPTION
|
|
This function collects different result types for comparison of the first
|
|
item in the list with each of the remaining items in the 'items' array.
|
|
|
|
RETURN
|
|
0 - if row type incompatibility has been detected (see cmp_row_type)
|
|
Bitmap of collected types - otherwise
|
|
*/
|
|
|
|
static uint collect_cmp_types(Item **items, uint nitems, bool skip_nulls= FALSE)
|
|
{
|
|
uint i;
|
|
uint found_types;
|
|
Item_result left_result= items[0]->result_type();
|
|
DBUG_ASSERT(nitems > 1);
|
|
found_types= 0;
|
|
for (i= 1; i < nitems ; i++)
|
|
{
|
|
if (skip_nulls && items[i]->type() == Item::NULL_ITEM)
|
|
continue; // Skip NULL constant items
|
|
if ((left_result == ROW_RESULT ||
|
|
items[i]->result_type() == ROW_RESULT) &&
|
|
cmp_row_type(items[0], items[i]))
|
|
return 0;
|
|
found_types|= 1U << (uint)item_cmp_type(left_result,
|
|
items[i]->result_type());
|
|
}
|
|
/*
|
|
Even if all right-hand items are NULLs and we are skipping them all, we need
|
|
at least one type bit in the found_type bitmask.
|
|
*/
|
|
if (skip_nulls && !found_types)
|
|
found_types= 1U << (uint)left_result;
|
|
return found_types;
|
|
}
|
|
|
|
static void my_coll_agg_error(DTCollation &c1, DTCollation &c2,
|
|
const char *fname)
|
|
{
|
|
my_error(ER_CANT_AGGREGATE_2COLLATIONS, MYF(0),
|
|
c1.collation->name,c1.derivation_name(),
|
|
c2.collation->name,c2.derivation_name(),
|
|
fname);
|
|
}
|
|
|
|
|
|
/**
|
|
This implementation of the factory method also implements flattening of
|
|
row constructors. Examples of flattening are:
|
|
|
|
- ROW(a, b) op ROW(x, y) => a op x P b op y.
|
|
- ROW(a, ROW(b, c) op ROW(x, ROW(y, z))) => a op x P b op y P c op z.
|
|
|
|
P is either AND or OR, depending on the comparison operation, and this
|
|
detail is left for combine().
|
|
|
|
The actual operator @i op is created by the concrete subclass in
|
|
create_scalar_predicate().
|
|
*/
|
|
Item_bool_func *Linear_comp_creator::create(Item *a, Item *b) const
|
|
{
|
|
/*
|
|
Test if the arguments are row constructors and thus can be flattened into
|
|
a list of ANDs or ORs.
|
|
*/
|
|
if (a->type() == Item::ROW_ITEM && b->type() == Item::ROW_ITEM)
|
|
{
|
|
if (a->cols() != b->cols())
|
|
{
|
|
my_error(ER_OPERAND_COLUMNS, MYF(0), a->cols());
|
|
return NULL;
|
|
}
|
|
DBUG_ASSERT(a->cols() > 1);
|
|
List<Item> list;
|
|
for (uint i= 0; i < a->cols(); ++i)
|
|
list.push_back(create(a->element_index(i), b->element_index(i)));
|
|
return combine(list);
|
|
}
|
|
return create_scalar_predicate(a, b);
|
|
}
|
|
|
|
|
|
Item_bool_func *Eq_creator::create_scalar_predicate(Item *a, Item *b) const
|
|
{
|
|
DBUG_ASSERT(a->type() != Item::ROW_ITEM || b->type() != Item::ROW_ITEM);
|
|
return new Item_func_eq(a, b);
|
|
}
|
|
|
|
|
|
Item_bool_func *Eq_creator::combine(List<Item> list) const
|
|
{
|
|
return new Item_cond_and(list);
|
|
}
|
|
|
|
|
|
Item_bool_func *Equal_creator::create_scalar_predicate(Item *a, Item *b)
|
|
const
|
|
{
|
|
DBUG_ASSERT(a->type() != Item::ROW_ITEM || b->type() != Item::ROW_ITEM);
|
|
return new Item_func_equal(a, b);
|
|
}
|
|
|
|
|
|
Item_bool_func *Equal_creator::combine(List<Item> list) const
|
|
{
|
|
return new Item_cond_and(list);
|
|
}
|
|
|
|
|
|
Item_bool_func* Ne_creator::create_scalar_predicate(Item *a, Item *b) const
|
|
{
|
|
DBUG_ASSERT(a->type() != Item::ROW_ITEM || b->type() != Item::ROW_ITEM);
|
|
return new Item_func_ne(a, b);
|
|
}
|
|
|
|
|
|
Item_bool_func *Ne_creator::combine(List<Item> list) const
|
|
{
|
|
return new Item_cond_or(list);
|
|
}
|
|
|
|
|
|
Item_bool_func* Gt_creator::create(Item *a, Item *b) const
|
|
{
|
|
return new Item_func_gt(a, b);
|
|
}
|
|
|
|
|
|
Item_bool_func* Lt_creator::create(Item *a, Item *b) const
|
|
{
|
|
return new Item_func_lt(a, b);
|
|
}
|
|
|
|
|
|
Item_bool_func* Ge_creator::create(Item *a, Item *b) const
|
|
{
|
|
return new Item_func_ge(a, b);
|
|
}
|
|
|
|
|
|
Item_bool_func* Le_creator::create(Item *a, Item *b) const
|
|
{
|
|
return new Item_func_le(a, b);
|
|
}
|
|
|
|
float Item_func_not::get_filtering_effect(table_map filter_for_table,
|
|
table_map read_tables,
|
|
const MY_BITMAP *fields_to_ignore,
|
|
double rows_in_table)
|
|
{
|
|
const float filter= args[0]->get_filtering_effect(filter_for_table,
|
|
read_tables,
|
|
fields_to_ignore,
|
|
rows_in_table);
|
|
|
|
/*
|
|
If the predicate that will be negated has COND_FILTER_ALLPASS
|
|
filtering it means that some dependent tables have not been
|
|
read, that the predicate is of a type that filtering effect is
|
|
not calculated for or something similar. In any case, the
|
|
filtering effect of the inverted predicate should also be
|
|
COND_FILTER_ALLPASS.
|
|
*/
|
|
if (filter == COND_FILTER_ALLPASS)
|
|
return COND_FILTER_ALLPASS;
|
|
|
|
return 1.0f - filter;
|
|
}
|
|
|
|
/*
|
|
Test functions
|
|
Most of these returns 0LL if false and 1LL if true and
|
|
NULL if some arg is NULL.
|
|
*/
|
|
|
|
longlong Item_func_not::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
bool value= args[0]->val_bool();
|
|
null_value=args[0]->null_value;
|
|
return ((!null_value && value == 0) ? 1 : 0);
|
|
}
|
|
|
|
/*
|
|
We put any NOT expression into parenthesis to avoid
|
|
possible problems with internal view representations where
|
|
any '!' is converted to NOT. It may cause a problem if
|
|
'!' is used in an expression together with other operators
|
|
whose precedence is lower than the precedence of '!' yet
|
|
higher than the precedence of NOT.
|
|
*/
|
|
|
|
void Item_func_not::print(String *str, enum_query_type query_type)
|
|
{
|
|
str->append('(');
|
|
Item_func::print(str, query_type);
|
|
str->append(')');
|
|
}
|
|
|
|
/**
|
|
special NOT for ALL subquery.
|
|
*/
|
|
|
|
|
|
longlong Item_func_not_all::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
bool value= args[0]->val_bool();
|
|
|
|
/*
|
|
return TRUE if there was records in underlying select in max/min
|
|
optimization (ALL subquery)
|
|
*/
|
|
if (empty_underlying_subquery())
|
|
return 1;
|
|
|
|
null_value= args[0]->null_value;
|
|
return ((!null_value && value == 0) ? 1 : 0);
|
|
}
|
|
|
|
|
|
bool Item_func_not_all::empty_underlying_subquery()
|
|
{
|
|
DBUG_ASSERT(subselect || !(test_sum_item || test_sub_item));
|
|
/*
|
|
When outer argument is NULL the subquery has not yet been evaluated, we
|
|
need to evaluate it to get to know whether it returns any rows to return
|
|
the correct result. 'ANY' subqueries are an exception because the
|
|
result would be false or null which for a top level item always mean false.
|
|
The subselect->unit->item->... chain should be used instead of
|
|
subselect->... to workaround subquery transformation which could make
|
|
subselect->engine unusable.
|
|
*/
|
|
if (subselect &&
|
|
subselect->substype() != Item_subselect::ANY_SUBS &&
|
|
!subselect->unit->item->is_evaluated())
|
|
subselect->unit->item->exec();
|
|
return ((test_sum_item && !test_sum_item->any_value()) ||
|
|
(test_sub_item && !test_sub_item->any_value()));
|
|
}
|
|
|
|
void Item_func_not_all::print(String *str, enum_query_type query_type)
|
|
{
|
|
if (show)
|
|
Item_func::print(str, query_type);
|
|
else
|
|
args[0]->print(str, query_type);
|
|
}
|
|
|
|
|
|
/**
|
|
Special NOP (No OPeration) for ALL subquery. It is like
|
|
Item_func_not_all.
|
|
|
|
@return
|
|
(return TRUE if underlying subquery do not return rows) but if subquery
|
|
returns some rows it return same value as argument (TRUE/FALSE).
|
|
*/
|
|
|
|
longlong Item_func_nop_all::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
longlong value= args[0]->val_int();
|
|
|
|
/*
|
|
return FALSE if there was records in underlying select in max/min
|
|
optimization (SAME/ANY subquery)
|
|
*/
|
|
if (empty_underlying_subquery())
|
|
return 0;
|
|
|
|
null_value= args[0]->null_value;
|
|
return (null_value || value == 0) ? 0 : 1;
|
|
}
|
|
|
|
|
|
/**
|
|
Convert a constant item to an int and replace the original item.
|
|
|
|
The function converts a constant expression or string to an integer.
|
|
On successful conversion the original item is substituted for the
|
|
result of the item evaluation.
|
|
This is done when comparing DATE/TIME of different formats and
|
|
also when comparing bigint to strings (in which case strings
|
|
are converted to bigints).
|
|
|
|
@param thd thread handle
|
|
@param field item will be converted using the type of this field
|
|
@param[in,out] item reference to the item to convert
|
|
|
|
@note
|
|
This function is called only at prepare stage.
|
|
As all derived tables are filled only after all derived tables
|
|
are prepared we do not evaluate items with subselects here because
|
|
they can contain derived tables and thus we may attempt to use a
|
|
table that has not been populated yet.
|
|
|
|
@retval
|
|
0 Can't convert item
|
|
@retval
|
|
1 Item was replaced with an integer version of the item
|
|
*/
|
|
|
|
static bool convert_constant_item(THD *thd, Item_field *field_item,
|
|
Item **item)
|
|
{
|
|
Field *field= field_item->field;
|
|
int result= 0;
|
|
|
|
if ((*item)->const_item() &&
|
|
/*
|
|
In case of GC it's possible that this func will be called on an
|
|
already converted constant. Don't convert it again.
|
|
*/
|
|
!((*item)->field_type() == field_item->field_type() &&
|
|
(*item)->basic_const_item()))
|
|
{
|
|
TABLE *table= field->table;
|
|
sql_mode_t orig_sql_mode= thd->variables.sql_mode;
|
|
enum_check_fields orig_count_cuted_fields= thd->count_cuted_fields;
|
|
my_bitmap_map *old_maps[2];
|
|
ulonglong orig_field_val= 0; /* original field value if valid */
|
|
|
|
old_maps[0]= NULL;
|
|
old_maps[1]= NULL;
|
|
|
|
if (table)
|
|
dbug_tmp_use_all_columns(table, old_maps,
|
|
table->read_set, table->write_set);
|
|
/* For comparison purposes allow invalid dates like 2000-01-32 */
|
|
thd->variables.sql_mode= (orig_sql_mode & ~MODE_NO_ZERO_DATE) |
|
|
MODE_INVALID_DATES;
|
|
thd->count_cuted_fields= CHECK_FIELD_IGNORE;
|
|
|
|
/*
|
|
Store the value of the field/constant if it references an outer field
|
|
because the call to save_in_field below overrides that value.
|
|
Don't save field value if no data has been read yet.
|
|
Outer constant values are always saved.
|
|
*/
|
|
bool save_field_value= (field_item->depended_from &&
|
|
(field_item->const_item() ||
|
|
!(field->table->status &
|
|
(STATUS_GARBAGE | STATUS_NOT_FOUND))));
|
|
if (save_field_value)
|
|
orig_field_val= field->val_int();
|
|
int rc;
|
|
if (!(*item)->is_null() &&
|
|
(((rc= (*item)->save_in_field(field, true)) == TYPE_OK) ||
|
|
rc == TYPE_NOTE_TIME_TRUNCATED)) // TS-TODO
|
|
{
|
|
int field_cmp= 0;
|
|
/*
|
|
If item is a decimal value, we must reject it if it was truncated.
|
|
TODO: consider doing the same for MYSQL_TYPE_YEAR,.
|
|
However: we have tests which assume that things '1999' and
|
|
'1991-01-01 01:01:01' can be converted to year.
|
|
Testing for MYSQL_TYPE_YEAR here, would treat such literals
|
|
as 'incorrect DOUBLE value'.
|
|
See Bug#13580652 YEAR COLUMN CAN BE EQUAL TO 1999.1
|
|
*/
|
|
if (field->type() == MYSQL_TYPE_LONGLONG)
|
|
{
|
|
field_cmp= stored_field_cmp_to_item(thd, field, *item);
|
|
DBUG_PRINT("info", ("convert_constant_item %d", field_cmp));
|
|
}
|
|
|
|
if (0 == field_cmp)
|
|
{
|
|
Item *tmp= field->type() == MYSQL_TYPE_TIME ?
|
|
#define OLD_CMP
|
|
#ifdef OLD_CMP
|
|
new Item_time_with_ref(field->decimals(),
|
|
field->val_time_temporal(), *item) :
|
|
#else
|
|
new Item_time_with_ref(max((*item)->time_precision(),
|
|
field->decimals()),
|
|
(*item)->val_time_temporal(),
|
|
*item) :
|
|
#endif
|
|
field->is_temporal_with_date() ?
|
|
#ifdef OLD_CMP
|
|
new Item_datetime_with_ref(field->type(),
|
|
field->decimals(),
|
|
field->val_date_temporal(),
|
|
*item) :
|
|
#else
|
|
new Item_datetime_with_ref(field->type(),
|
|
max((*item)->datetime_precision(),
|
|
field->decimals()),
|
|
(*item)->val_date_temporal(),
|
|
*item) :
|
|
#endif
|
|
new Item_int_with_ref(field->type(), field->val_int(), *item,
|
|
MY_TEST(field->flags & UNSIGNED_FLAG));
|
|
if (tmp)
|
|
thd->change_item_tree(item, tmp);
|
|
result= 1; // Item was replaced
|
|
}
|
|
}
|
|
/* Restore the original field value. */
|
|
if (save_field_value)
|
|
{
|
|
result= field->store(orig_field_val, TRUE);
|
|
/* orig_field_val must be a valid value that can be restored back. */
|
|
DBUG_ASSERT(!result);
|
|
}
|
|
thd->variables.sql_mode= orig_sql_mode;
|
|
thd->count_cuted_fields= orig_count_cuted_fields;
|
|
if (table)
|
|
dbug_tmp_restore_column_maps(table->read_set, table->write_set, old_maps);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
bool Item_bool_func2::convert_constant_arg(THD *thd, Item *field, Item **item)
|
|
{
|
|
if (field->real_item()->type() != FIELD_ITEM)
|
|
return false;
|
|
|
|
Item_field *field_item= (Item_field*) (field->real_item());
|
|
if (field_item->field->can_be_compared_as_longlong() &&
|
|
!(field_item->is_temporal_with_date() &&
|
|
(*item)->result_type() == STRING_RESULT))
|
|
{
|
|
if (convert_constant_item(thd, field_item, item))
|
|
{
|
|
cmp.set_cmp_func(this, tmp_arg, tmp_arg + 1, INT_RESULT);
|
|
field->cmp_context= (*item)->cmp_context= INT_RESULT;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
void Item_bool_func2::fix_length_and_dec()
|
|
{
|
|
max_length= 1; // Function returns 0 or 1
|
|
THD *thd;
|
|
|
|
/*
|
|
As some compare functions are generated after sql_yacc,
|
|
we have to check for out of memory conditions here
|
|
*/
|
|
if (!args[0] || !args[1])
|
|
return;
|
|
|
|
DBUG_ENTER("Item_bool_func2::fix_length_and_dec");
|
|
|
|
/*
|
|
See agg_item_charsets() in item.cc for comments
|
|
on character set and collation aggregation.
|
|
*/
|
|
if (args[0]->result_type() == STRING_RESULT &&
|
|
args[1]->result_type() == STRING_RESULT &&
|
|
agg_arg_charsets_for_comparison(cmp.cmp_collation, args, 2))
|
|
DBUG_VOID_RETURN;
|
|
|
|
args[0]->cmp_context= args[1]->cmp_context=
|
|
item_cmp_type(args[0]->result_type(), args[1]->result_type());
|
|
// Make a special case of compare with fields to get nicer DATE comparisons
|
|
|
|
if (functype() == LIKE_FUNC) // Disable conversion in case of LIKE function.
|
|
{
|
|
set_cmp_func();
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
/*
|
|
Geometry item cannot participate in an arithmetic or string comparison or
|
|
a full text search, except in equal/not equal comparison.
|
|
We allow geometry arguments in equal/not equal, since such
|
|
comparisons are used now and are meaningful, although it simply compares
|
|
the GEOMETRY byte string rather than doing a geometric equality comparison.
|
|
*/
|
|
const Functype func_type= functype();
|
|
if (func_type == LT_FUNC || func_type == LE_FUNC || func_type == GE_FUNC ||
|
|
func_type == GT_FUNC || func_type == FT_FUNC)
|
|
reject_geometry_args(arg_count, args, this);
|
|
|
|
thd= current_thd;
|
|
if (!thd->lex->is_ps_or_view_context_analysis())
|
|
{
|
|
if (convert_constant_arg(thd, args[0], &args[1]) ||
|
|
convert_constant_arg(thd, args[1], &args[0]))
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
set_cmp_func();
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
void Arg_comparator::cleanup()
|
|
{
|
|
if (comparators != NULL)
|
|
{
|
|
/*
|
|
We cannot rely on (*a)->cols(), since *a may be deallocated
|
|
at this point, so use comparator_count to loop.
|
|
*/
|
|
for (size_t i= 0; i < comparator_count; i++)
|
|
{
|
|
comparators[i].cleanup();
|
|
}
|
|
}
|
|
delete[] comparators;
|
|
comparators= 0;
|
|
delete_json_scalar_holder(json_scalar);
|
|
json_scalar= 0;
|
|
}
|
|
|
|
|
|
int Arg_comparator::set_compare_func(Item_result_field *item, Item_result type)
|
|
{
|
|
owner= item;
|
|
func= comparator_matrix[type]
|
|
[is_owner_equal_func()];
|
|
|
|
switch (type) {
|
|
case ROW_RESULT:
|
|
{
|
|
uint n= (*a)->cols();
|
|
if (n != (*b)->cols())
|
|
{
|
|
my_error(ER_OPERAND_COLUMNS, MYF(0), n);
|
|
comparators= 0;
|
|
return 1;
|
|
}
|
|
if (!(comparators= new Arg_comparator[n]))
|
|
return 1;
|
|
comparator_count= n;
|
|
|
|
for (uint i=0; i < n; i++)
|
|
{
|
|
if ((*a)->element_index(i)->cols() != (*b)->element_index(i)->cols())
|
|
{
|
|
my_error(ER_OPERAND_COLUMNS, MYF(0), (*a)->element_index(i)->cols());
|
|
return 1;
|
|
}
|
|
if (comparators[i].set_cmp_func(owner, (*a)->addr(i), (*b)->addr(i),
|
|
set_null))
|
|
return 1;
|
|
}
|
|
break;
|
|
}
|
|
case STRING_RESULT:
|
|
{
|
|
/*
|
|
We must set cmp_charset here as we may be called from for an automatic
|
|
generated item, like in natural join
|
|
*/
|
|
if (cmp_collation.set((*a)->collation, (*b)->collation, MY_COLL_CMP_CONV) ||
|
|
cmp_collation.derivation == DERIVATION_NONE)
|
|
{
|
|
my_coll_agg_error((*a)->collation, (*b)->collation,
|
|
owner->func_name());
|
|
return 1;
|
|
}
|
|
if (cmp_collation.collation == &my_charset_bin)
|
|
{
|
|
/*
|
|
We are using BLOB/BINARY/VARBINARY, change to compare byte by byte,
|
|
without removing end space
|
|
*/
|
|
if (func == &Arg_comparator::compare_string)
|
|
func= &Arg_comparator::compare_binary_string;
|
|
else if (func == &Arg_comparator::compare_e_string)
|
|
func= &Arg_comparator::compare_e_binary_string;
|
|
|
|
/*
|
|
As this is binary compassion, mark all fields that they can't be
|
|
transformed. Otherwise we would get into trouble with comparisons
|
|
like:
|
|
WHERE col= 'j' AND col LIKE BINARY 'j'
|
|
which would be transformed to:
|
|
WHERE col= 'j'
|
|
*/
|
|
(*a)->walk(&Item::set_no_const_sub, Item::WALK_POSTFIX, NULL);
|
|
(*b)->walk(&Item::set_no_const_sub, Item::WALK_POSTFIX, NULL);
|
|
}
|
|
break;
|
|
}
|
|
case INT_RESULT:
|
|
{
|
|
if ((*a)->is_temporal() && (*b)->is_temporal())
|
|
{
|
|
func= is_owner_equal_func() ?
|
|
&Arg_comparator::compare_e_time_packed :
|
|
&Arg_comparator::compare_time_packed;
|
|
}
|
|
else if (func == &Arg_comparator::compare_int_signed)
|
|
{
|
|
if ((*a)->unsigned_flag)
|
|
func= (((*b)->unsigned_flag)?
|
|
&Arg_comparator::compare_int_unsigned :
|
|
&Arg_comparator::compare_int_unsigned_signed);
|
|
else if ((*b)->unsigned_flag)
|
|
func= &Arg_comparator::compare_int_signed_unsigned;
|
|
}
|
|
else if (func== &Arg_comparator::compare_e_int)
|
|
{
|
|
if ((*a)->unsigned_flag ^ (*b)->unsigned_flag)
|
|
func= &Arg_comparator::compare_e_int_diff_signedness;
|
|
}
|
|
break;
|
|
}
|
|
case DECIMAL_RESULT:
|
|
break;
|
|
case REAL_RESULT:
|
|
{
|
|
if ((*a)->decimals < NOT_FIXED_DEC && (*b)->decimals < NOT_FIXED_DEC)
|
|
{
|
|
precision= 5 / log_10[max((*a)->decimals, (*b)->decimals) + 1];
|
|
if (func == &Arg_comparator::compare_real)
|
|
func= &Arg_comparator::compare_real_fixed;
|
|
else if (func == &Arg_comparator::compare_e_real)
|
|
func= &Arg_comparator::compare_e_real_fixed;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
DBUG_ASSERT(0);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
Parse date provided in a string to a MYSQL_TIME.
|
|
|
|
@param[in] thd Thread handle
|
|
@param[in] str A string to convert
|
|
@param[in] warn_type Type of the timestamp for issuing the warning
|
|
@param[in] warn_name Field name for issuing the warning
|
|
@param[out] l_time The MYSQL_TIME objects is initialized.
|
|
|
|
Parses a date provided in the string str into a MYSQL_TIME object. If the
|
|
string contains an incorrect date or doesn't correspond to a date at all
|
|
then a warning is issued. The warn_type and the warn_name arguments are used
|
|
as the name and the type of the field when issuing the warning. If any input
|
|
was discarded (trailing or non-timestamp-y characters), return value will be
|
|
TRUE.
|
|
|
|
@return Status flag
|
|
@retval FALSE Success.
|
|
@retval True Indicates failure.
|
|
*/
|
|
|
|
bool get_mysql_time_from_str(THD *thd, String *str, timestamp_type warn_type,
|
|
const char *warn_name, MYSQL_TIME *l_time)
|
|
{
|
|
bool value;
|
|
MYSQL_TIME_STATUS status;
|
|
my_time_flags_t flags= TIME_FUZZY_DATE | TIME_INVALID_DATES;
|
|
|
|
if (thd->variables.sql_mode & MODE_NO_ZERO_IN_DATE)
|
|
flags|= TIME_NO_ZERO_IN_DATE;
|
|
if (thd->variables.sql_mode & MODE_NO_ZERO_DATE)
|
|
flags|= TIME_NO_ZERO_DATE;
|
|
|
|
if (!str_to_datetime(str, l_time, flags, &status) &&
|
|
(l_time->time_type == MYSQL_TIMESTAMP_DATETIME ||
|
|
l_time->time_type == MYSQL_TIMESTAMP_DATE))
|
|
/*
|
|
Do not return yet, we may still want to throw a "trailing garbage"
|
|
warning.
|
|
*/
|
|
value= FALSE;
|
|
else
|
|
{
|
|
value= TRUE;
|
|
status.warnings= MYSQL_TIME_WARN_TRUNCATED; /* force warning */
|
|
}
|
|
|
|
if (status.warnings > 0)
|
|
make_truncated_value_warning(thd, Sql_condition::SL_WARNING,
|
|
ErrConvString(str), warn_type, warn_name);
|
|
|
|
return value;
|
|
}
|
|
|
|
|
|
/**
|
|
@brief Convert date provided in a string
|
|
to its packed temporal int representation.
|
|
|
|
@param[in] thd thread handle
|
|
@param[in] str a string to convert
|
|
@param[in] warn_type type of the timestamp for issuing the warning
|
|
@param[in] warn_name field name for issuing the warning
|
|
@param[out] error_arg could not extract a DATE or DATETIME
|
|
|
|
@details Convert date provided in the string str to the int
|
|
representation. If the string contains wrong date or doesn't
|
|
contain it at all then a warning is issued. The warn_type and
|
|
the warn_name arguments are used as the name and the type of the
|
|
field when issuing the warning.
|
|
|
|
@return
|
|
converted value. 0 on error and on zero-dates -- check 'failure'
|
|
*/
|
|
static ulonglong get_date_from_str(THD *thd, String *str,
|
|
timestamp_type warn_type,
|
|
const char *warn_name, bool *error_arg)
|
|
{
|
|
MYSQL_TIME l_time;
|
|
*error_arg= get_mysql_time_from_str(thd, str, warn_type, warn_name, &l_time);
|
|
|
|
if (*error_arg)
|
|
return 0;
|
|
return TIME_to_longlong_datetime_packed(&l_time);
|
|
}
|
|
|
|
|
|
/**
|
|
Check if str_arg is a constant and convert it to datetime packed value.
|
|
Note, const_value may stay untouched, so the caller is responsible to
|
|
initialize it.
|
|
|
|
@param date_arg date argument, it's name is used for error reporting.
|
|
@param str_arg string argument to get datetime value from.
|
|
@param[out] const_value the converted value is stored here, if not NULL.
|
|
|
|
@return true on error, false on success, false if str_arg is not a const.
|
|
*/
|
|
bool Arg_comparator::get_date_from_const(Item *date_arg,
|
|
Item *str_arg,
|
|
ulonglong *const_value)
|
|
{
|
|
THD *thd= current_thd;
|
|
/*
|
|
Do not cache GET_USER_VAR() function as its const_item() may return TRUE
|
|
for the current thread but it still may change during the execution.
|
|
Don't use cache while in the context analysis mode only (i.e. for
|
|
EXPLAIN/CREATE VIEW and similar queries). Cache is useless in such
|
|
cases and can cause problems. For example evaluating subqueries can
|
|
confuse storage engines since in context analysis mode tables
|
|
aren't locked.
|
|
*/
|
|
if (!thd->lex->is_ps_or_view_context_analysis() &&
|
|
str_arg->const_item() &&
|
|
(str_arg->type() != Item::FUNC_ITEM ||
|
|
((Item_func*) str_arg)->functype() != Item_func::GUSERVAR_FUNC))
|
|
{
|
|
ulonglong value;
|
|
if (str_arg->field_type() == MYSQL_TYPE_TIME)
|
|
{
|
|
// Convert from TIME to DATETIME
|
|
value= str_arg->val_date_temporal();
|
|
if (str_arg->null_value)
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// Convert from string to DATETIME
|
|
DBUG_ASSERT(str_arg->result_type() == STRING_RESULT);
|
|
bool error;
|
|
String tmp, *str_val= 0;
|
|
timestamp_type t_type= (date_arg->field_type() == MYSQL_TYPE_DATE ?
|
|
MYSQL_TIMESTAMP_DATE : MYSQL_TIMESTAMP_DATETIME);
|
|
str_val= str_arg->val_str(&tmp);
|
|
if (str_arg->null_value)
|
|
return true;
|
|
value= get_date_from_str(thd, str_val, t_type,
|
|
date_arg->item_name.ptr(), &error);
|
|
if (error)
|
|
return true;
|
|
}
|
|
if (const_value)
|
|
*const_value= value;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
Check whether compare_datetime() can be used to compare items.
|
|
|
|
SYNOPSIS
|
|
Arg_comparator::can_compare_as_dates()
|
|
a, b [in] items to be compared
|
|
const_value [out] converted value of the string constant, if any
|
|
|
|
DESCRIPTION
|
|
Check several cases when the DATE/DATETIME comparator should be used.
|
|
The following cases are checked:
|
|
1. Both a and b is a DATE/DATETIME field/function returning string or
|
|
int result.
|
|
2. Only a or b is a DATE/DATETIME field/function returning string or
|
|
int result and the other item (b or a) is an item with string result.
|
|
If the second item is a constant one then it's checked to be
|
|
convertible to the DATE/DATETIME type. If the constant can't be
|
|
converted to a DATE/DATETIME then the compare_datetime() comparator
|
|
isn't used and the warning about wrong DATE/DATETIME value is issued.
|
|
In all other cases (date-[int|real|decimal]/[int|real|decimal]-date)
|
|
the comparison is handled by other comparators.
|
|
If the datetime comparator can be used and one the operands of the
|
|
comparison is a string constant that was successfully converted to a
|
|
DATE/DATETIME type then the result of the conversion is returned in the
|
|
const_value if it is provided. If there is no constant or
|
|
compare_datetime() isn't applicable then the *const_value remains
|
|
unchanged.
|
|
|
|
@return true if can compare as dates, false otherwise.
|
|
*/
|
|
|
|
bool
|
|
Arg_comparator::can_compare_as_dates(Item *a, Item *b, ulonglong *const_value)
|
|
{
|
|
if (a->type() == Item::ROW_ITEM || b->type() == Item::ROW_ITEM)
|
|
return false;
|
|
|
|
if (a->is_temporal_with_date())
|
|
{
|
|
if (b->is_temporal_with_date()) // date[time] + date
|
|
{
|
|
return true;
|
|
}
|
|
else if (b->result_type() == STRING_RESULT) // date[time] + string
|
|
{
|
|
return !get_date_from_const(a, b, const_value);
|
|
}
|
|
else
|
|
return false; // date[time] + number
|
|
}
|
|
else if (b->is_temporal_with_date() &&
|
|
a->result_type() == STRING_RESULT) // string + date[time]
|
|
{
|
|
return !get_date_from_const(b, a, const_value);
|
|
}
|
|
else
|
|
return false; // No date[time] items found
|
|
}
|
|
|
|
|
|
/*
|
|
Retrieves correct TIME value from the given item.
|
|
|
|
SYNOPSIS
|
|
get_time_value()
|
|
thd thread handle
|
|
item_arg [in/out] item to retrieve TIME value from
|
|
cache_arg [in/out] pointer to place to store the cache item to
|
|
warn_item [in] unused
|
|
is_null [out] TRUE <=> the item_arg is null
|
|
|
|
DESCRIPTION
|
|
Retrieves the correct TIME value from given item for comparison by the
|
|
compare_datetime() function.
|
|
If item's result can be compared as longlong then its int value is used
|
|
and a value returned by get_time function is used otherwise.
|
|
If an item is a constant one then its value is cached and it isn't
|
|
get parsed again. An Item_cache_int object is used for for cached values.
|
|
It seamlessly substitutes the original item. The cache item is marked as
|
|
non-constant to prevent re-caching it again.
|
|
|
|
RETURN
|
|
obtained value
|
|
*/
|
|
|
|
longlong
|
|
get_time_value(THD *thd, Item ***item_arg, Item **cache_arg,
|
|
const Item *warn_item, bool *is_null)
|
|
{
|
|
longlong value;
|
|
Item *item= **item_arg;
|
|
|
|
/*
|
|
Note, it's wrong to assume that we always get
|
|
a TIME expression or NULL here:
|
|
|
|
DBUG_ASSERT(item->field_type() == MYSQL_TYPE_TIME ||
|
|
item->field_type() == MYSQL_TYPE_NULL);
|
|
|
|
because when this condition is optimized:
|
|
|
|
WHERE time_column=DATE(NULL) AND time_column=TIME(NULL);
|
|
|
|
rhe first AND part is eliminated and DATE(NULL) is substituted
|
|
to the second AND part like this:
|
|
|
|
WHERE DATE(NULL) = TIME(NULL) // as TIME
|
|
|
|
whose Arg_comparator has already get_time_value set for both arguments.
|
|
Therefore, get_time_value is executed for DATE(NULL).
|
|
This condition is further evaluated as impossible condition.
|
|
|
|
TS-TODO: perhaps such cases should be evaluated without
|
|
calling get_time_value at all.
|
|
|
|
See a similar comment in Arg_comparator::compare_time_packed.
|
|
*/
|
|
value= item->val_time_temporal();
|
|
*is_null= item->null_value;
|
|
|
|
/*
|
|
Do not cache GET_USER_VAR() function as its const_item() may return TRUE
|
|
for the current thread but it still may change during the execution.
|
|
*/
|
|
if (item->const_item() && cache_arg &&
|
|
item->type() != Item::CACHE_ITEM &&
|
|
(item->type() != Item::FUNC_ITEM ||
|
|
((Item_func*)item)->functype() != Item_func::GUSERVAR_FUNC))
|
|
{
|
|
Item_cache_datetime *cache= new Item_cache_datetime(item->field_type());
|
|
/* Mark the cache as non-const to prevent re-caching. */
|
|
cache->set_used_tables(1);
|
|
cache->store(item, value);
|
|
*cache_arg= cache;
|
|
*item_arg= cache_arg;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
|
|
/**
|
|
Sets compare functions for various datatypes.
|
|
|
|
NOTE
|
|
The result type of a comparison is chosen by item_cmp_type().
|
|
Here we override the chosen result type for certain expression
|
|
containing date or time or decimal expressions.
|
|
*/
|
|
int Arg_comparator::set_cmp_func(Item_result_field *owner_arg,
|
|
Item **a1, Item **a2,
|
|
Item_result type)
|
|
{
|
|
ulonglong const_value= (ulonglong)-1;
|
|
owner= owner_arg;
|
|
set_null= set_null && owner_arg;
|
|
a= a1;
|
|
b= a2;
|
|
|
|
if (type != ROW_RESULT &&
|
|
(((*a)->result_type() == STRING_RESULT &&
|
|
(*a)->field_type() == MYSQL_TYPE_JSON) ||
|
|
((*b)->result_type() == STRING_RESULT &&
|
|
(*b)->field_type() == MYSQL_TYPE_JSON)))
|
|
{
|
|
// Use the JSON comparator if at least one of the arguments is JSON.
|
|
is_nulls_eq= is_owner_equal_func();
|
|
func= &Arg_comparator::compare_json;
|
|
return 0;
|
|
}
|
|
|
|
if (can_compare_as_dates(*a, *b, &const_value))
|
|
{
|
|
a_cache= 0;
|
|
b_cache= 0;
|
|
|
|
if (const_value != (ulonglong)-1)
|
|
{
|
|
/*
|
|
cache_converted_constant can't be used here because it can't
|
|
correctly convert a DATETIME value from string to int representation.
|
|
*/
|
|
Item_cache_datetime *cache= new Item_cache_datetime(MYSQL_TYPE_DATETIME);
|
|
/* Mark the cache as non-const to prevent re-caching. */
|
|
cache->set_used_tables(1);
|
|
if (!(*a)->is_temporal_with_date())
|
|
{
|
|
cache->store((*a), const_value);
|
|
a_cache= cache;
|
|
a= &a_cache;
|
|
}
|
|
else
|
|
{
|
|
cache->store((*b), const_value);
|
|
b_cache= cache;
|
|
b= &b_cache;
|
|
}
|
|
}
|
|
is_nulls_eq= is_owner_equal_func();
|
|
func= &Arg_comparator::compare_datetime;
|
|
get_value_a_func= &get_datetime_value;
|
|
get_value_b_func= &get_datetime_value;
|
|
cmp_collation.set(&my_charset_numeric);
|
|
set_cmp_context_for_datetime();
|
|
return 0;
|
|
}
|
|
else if ((type == STRING_RESULT ||
|
|
// When comparing time field and cached/converted time constant
|
|
type == REAL_RESULT) &&
|
|
(*a)->field_type() == MYSQL_TYPE_TIME &&
|
|
(*b)->field_type() == MYSQL_TYPE_TIME)
|
|
{
|
|
/* Compare TIME values as integers. */
|
|
a_cache= 0;
|
|
b_cache= 0;
|
|
is_nulls_eq= is_owner_equal_func();
|
|
func= &Arg_comparator::compare_datetime;
|
|
get_value_a_func= &get_time_value;
|
|
get_value_b_func= &get_time_value;
|
|
set_cmp_context_for_datetime();
|
|
return 0;
|
|
}
|
|
else if (type == STRING_RESULT &&
|
|
(*a)->result_type() == STRING_RESULT &&
|
|
(*b)->result_type() == STRING_RESULT)
|
|
{
|
|
DTCollation coll;
|
|
coll.set((*a)->collation, (*b)->collation, MY_COLL_CMP_CONV);
|
|
if (agg_item_set_converter(coll, owner->func_name(), a, 1,
|
|
MY_COLL_CMP_CONV, 1) ||
|
|
agg_item_set_converter(coll, owner->func_name(), b, 1,
|
|
MY_COLL_CMP_CONV, 1))
|
|
return true;
|
|
}
|
|
else if (try_year_cmp_func(type))
|
|
{
|
|
return 0;
|
|
}
|
|
else if (type == REAL_RESULT &&
|
|
(((*a)->result_type() == DECIMAL_RESULT && !(*a)->const_item() &&
|
|
(*b)->result_type() == STRING_RESULT && (*b)->const_item()) ||
|
|
((*b)->result_type() == DECIMAL_RESULT && !(*b)->const_item() &&
|
|
(*a)->result_type() == STRING_RESULT && (*a)->const_item())))
|
|
{
|
|
/*
|
|
<non-const decimal expression> <cmp> <const string expression>
|
|
or
|
|
<const string expression> <cmp> <non-const decimal expression>
|
|
|
|
Do comparison as decimal rather than float, in order not to lose precision.
|
|
*/
|
|
type= DECIMAL_RESULT;
|
|
}
|
|
|
|
THD *thd= current_thd;
|
|
a= cache_converted_constant(thd, a, &a_cache, type);
|
|
b= cache_converted_constant(thd, b, &b_cache, type);
|
|
return set_compare_func(owner_arg, type);
|
|
}
|
|
|
|
|
|
int Arg_comparator::set_cmp_func(Item_result_field *owner_arg,
|
|
Item **a1, Item **a2, bool set_null_arg)
|
|
{
|
|
set_null= set_null_arg;
|
|
const Item_result item_result=
|
|
item_cmp_type((*a1)->result_type(),
|
|
(*a2)->result_type());
|
|
return set_cmp_func(owner_arg, a1, a2, item_result);
|
|
}
|
|
|
|
/*
|
|
Helper function to call from Arg_comparator::set_cmp_func()
|
|
*/
|
|
|
|
bool Arg_comparator::try_year_cmp_func(Item_result type)
|
|
{
|
|
if (type == ROW_RESULT)
|
|
return FALSE;
|
|
|
|
bool a_is_year= (*a)->field_type() == MYSQL_TYPE_YEAR;
|
|
bool b_is_year= (*b)->field_type() == MYSQL_TYPE_YEAR;
|
|
|
|
if (!a_is_year && !b_is_year)
|
|
return FALSE;
|
|
|
|
if (a_is_year && b_is_year)
|
|
{
|
|
get_value_a_func= &get_year_value;
|
|
get_value_b_func= &get_year_value;
|
|
}
|
|
else if (a_is_year && (*b)->is_temporal_with_date())
|
|
{
|
|
get_value_a_func= &get_year_value;
|
|
get_value_b_func= &get_datetime_value;
|
|
}
|
|
else if (b_is_year && (*a)->is_temporal_with_date())
|
|
{
|
|
get_value_b_func= &get_year_value;
|
|
get_value_a_func= &get_datetime_value;
|
|
}
|
|
else
|
|
return FALSE;
|
|
|
|
is_nulls_eq= is_owner_equal_func();
|
|
func= &Arg_comparator::compare_datetime;
|
|
set_cmp_context_for_datetime();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
Convert and cache a constant.
|
|
|
|
@param value [in] An item to cache
|
|
@param cache_item [out] Placeholder for the cache item
|
|
@param type [in] Comparison type
|
|
|
|
@details
|
|
When given item is a constant and its type differs from comparison type
|
|
then cache its value to avoid type conversion of this constant on each
|
|
evaluation. In this case the value is cached and the reference to the cache
|
|
is returned.
|
|
Original value is returned otherwise.
|
|
|
|
@return cache item or original value.
|
|
*/
|
|
|
|
Item** Arg_comparator::cache_converted_constant(THD *thd_arg, Item **value,
|
|
Item **cache_item,
|
|
Item_result type)
|
|
{
|
|
/* Don't need cache if doing context analysis only. */
|
|
if (!thd_arg->lex->is_ps_or_view_context_analysis() &&
|
|
(*value)->const_item() && type != (*value)->result_type())
|
|
{
|
|
Item_cache *cache= Item_cache::get_cache(*value, type);
|
|
cache->setup(*value);
|
|
*cache_item= cache;
|
|
return cache_item;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
|
|
void Arg_comparator::set_datetime_cmp_func(Item_result_field *owner_arg,
|
|
Item **a1, Item **b1)
|
|
{
|
|
owner= owner_arg;
|
|
a= a1;
|
|
b= b1;
|
|
a_cache= 0;
|
|
b_cache= 0;
|
|
is_nulls_eq= FALSE;
|
|
func= &Arg_comparator::compare_datetime;
|
|
get_value_a_func= &get_datetime_value;
|
|
get_value_b_func= &get_datetime_value;
|
|
set_cmp_context_for_datetime();
|
|
}
|
|
|
|
|
|
/*
|
|
Retrieves correct DATETIME value from given item.
|
|
|
|
SYNOPSIS
|
|
get_datetime_value()
|
|
thd thread handle
|
|
item_arg [in/out] item to retrieve DATETIME value from
|
|
cache_arg [in/out] pointer to place to store the caching item to
|
|
warn_item [in] item for issuing the conversion warning
|
|
is_null [out] TRUE <=> the item_arg is null
|
|
|
|
DESCRIPTION
|
|
Retrieves the correct DATETIME value from given item for comparison by the
|
|
compare_datetime() function.
|
|
If item's result can be compared as longlong then its int value is used
|
|
and its string value is used otherwise. Strings are always parsed and
|
|
converted to int values by the get_date_from_str() function.
|
|
This allows us to compare correctly string dates with missed insignificant
|
|
zeros. If an item is a constant one then its value is cached and it isn't
|
|
get parsed again. An Item_cache_int object is used for caching values. It
|
|
seamlessly substitutes the original item. The cache item is marked as
|
|
non-constant to prevent re-caching it again. In order to compare
|
|
correctly DATE and DATETIME items the result of the former are treated as
|
|
a DATETIME with zero time (00:00:00).
|
|
|
|
RETURN
|
|
obtained value
|
|
*/
|
|
|
|
|
|
longlong
|
|
get_datetime_value(THD *thd, Item ***item_arg, Item **cache_arg,
|
|
const Item *warn_item, bool *is_null)
|
|
{
|
|
longlong value= 0;
|
|
String buf, *str= 0;
|
|
Item *item= **item_arg;
|
|
|
|
if (item->is_temporal())
|
|
{
|
|
value= item->val_date_temporal();
|
|
*is_null= item->null_value;
|
|
}
|
|
else
|
|
{
|
|
str= item->val_str(&buf);
|
|
*is_null= item->null_value;
|
|
}
|
|
if (*is_null)
|
|
return ~(ulonglong) 0;
|
|
/*
|
|
Convert strings to the integer DATE/DATETIME representation.
|
|
Even if both dates provided in strings we can't compare them directly as
|
|
strings as there is no warranty that they are correct and do not miss
|
|
some insignificant zeros.
|
|
*/
|
|
if (str)
|
|
{
|
|
bool error;
|
|
enum_field_types f_type= warn_item->field_type();
|
|
timestamp_type t_type= f_type ==
|
|
MYSQL_TYPE_DATE ? MYSQL_TIMESTAMP_DATE : MYSQL_TIMESTAMP_DATETIME;
|
|
value= (longlong) get_date_from_str(thd, str, t_type, warn_item->item_name.ptr(), &error);
|
|
/*
|
|
If str did not contain a valid date according to the current
|
|
SQL_MODE, get_date_from_str() has already thrown a warning,
|
|
and we don't want to throw NULL on invalid date (see 5.2.6
|
|
"SQL modes" in the manual), so we're done here.
|
|
*/
|
|
}
|
|
/*
|
|
Do not cache GET_USER_VAR() function as its const_item() may return TRUE
|
|
for the current thread but it still may change during the execution.
|
|
*/
|
|
if (item->const_item() && cache_arg &&
|
|
item->type() != Item::CACHE_ITEM &&
|
|
(item->type() != Item::FUNC_ITEM ||
|
|
((Item_func*)item)->functype() != Item_func::GUSERVAR_FUNC))
|
|
{
|
|
Item_cache_datetime *cache= new Item_cache_datetime(MYSQL_TYPE_DATETIME);
|
|
/* Mark the cache as non-const to prevent re-caching. */
|
|
cache->set_used_tables(1);
|
|
cache->store(item, value);
|
|
*cache_arg= cache;
|
|
*item_arg= cache_arg;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
|
|
/*
|
|
Retrieves YEAR value of 19XX-00-00 00:00:00 form from given item.
|
|
|
|
SYNOPSIS
|
|
get_year_value()
|
|
thd thread handle
|
|
item_arg [in/out] item to retrieve YEAR value from
|
|
cache_arg [in/out] pointer to place to store the caching item to
|
|
warn_item [in] item for issuing the conversion warning
|
|
is_null [out] TRUE <=> the item_arg is null
|
|
|
|
DESCRIPTION
|
|
Retrieves the YEAR value of 19XX form from given item for comparison by the
|
|
compare_datetime() function.
|
|
Converts year to DATETIME of form YYYY-00-00 00:00:00 for the compatibility
|
|
with the get_datetime_value function result.
|
|
|
|
RETURN
|
|
obtained value
|
|
*/
|
|
|
|
static longlong
|
|
get_year_value(THD *thd, Item ***item_arg, Item **cache_arg,
|
|
const Item *warn_item, bool *is_null)
|
|
{
|
|
longlong value= 0;
|
|
Item *item= **item_arg;
|
|
|
|
value= item->val_int();
|
|
*is_null= item->null_value;
|
|
if (*is_null)
|
|
return ~(ulonglong) 0;
|
|
|
|
/* Convert year to DATETIME packed format */
|
|
return year_to_longlong_datetime_packed(static_cast<long>(value));
|
|
}
|
|
|
|
|
|
/*
|
|
Compare items values as dates.
|
|
|
|
SYNOPSIS
|
|
Arg_comparator::compare_datetime()
|
|
|
|
DESCRIPTION
|
|
Compare items values as DATE/DATETIME for both EQUAL_FUNC and from other
|
|
comparison functions. The correct DATETIME values are obtained
|
|
with help of the get_datetime_value() function.
|
|
|
|
RETURN
|
|
If is_nulls_eq is TRUE:
|
|
1 if items are equal or both are null
|
|
0 otherwise
|
|
If is_nulls_eq is FALSE:
|
|
-1 a < b or at least one item is null
|
|
0 a == b
|
|
1 a > b
|
|
See the table:
|
|
is_nulls_eq | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
|
|
a_is_null | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 |
|
|
b_is_null | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 0 |
|
|
result | 1 | 0 | 0 |0/1|-1 |-1 |-1 |-1/0/1|
|
|
*/
|
|
|
|
int Arg_comparator::compare_datetime()
|
|
{
|
|
bool a_is_null, b_is_null;
|
|
longlong a_value, b_value;
|
|
THD *thd= current_thd;
|
|
|
|
/* Get DATE/DATETIME/TIME value of the 'a' item. */
|
|
a_value= (*get_value_a_func)(thd, &a, &a_cache, *b, &a_is_null);
|
|
if (!is_nulls_eq && a_is_null)
|
|
{
|
|
if (set_null)
|
|
owner->null_value= 1;
|
|
return -1;
|
|
}
|
|
|
|
/* Get DATE/DATETIME/TIME value of the 'b' item. */
|
|
b_value= (*get_value_b_func)(thd, &b, &b_cache, *a, &b_is_null);
|
|
if (a_is_null || b_is_null)
|
|
{
|
|
if (set_null)
|
|
owner->null_value= is_nulls_eq ? 0 : 1;
|
|
return is_nulls_eq ? (a_is_null == b_is_null) : -1;
|
|
}
|
|
|
|
/* Here we have two not-NULL values. */
|
|
if (set_null)
|
|
owner->null_value= 0;
|
|
|
|
/* Compare values. */
|
|
if (is_nulls_eq)
|
|
return (a_value == b_value);
|
|
return a_value < b_value ? -1 : (a_value > b_value ? 1 : 0);
|
|
}
|
|
|
|
|
|
/**
|
|
Get one of the arguments to the comparator as a JSON value.
|
|
|
|
@param[in] arg pointer to the argument
|
|
@param[in,out] value buffer used for reading the JSON value
|
|
@param[in,out] tmp buffer used for converting string values to the
|
|
correct charset, if necessary
|
|
@param[out] result where to store the result
|
|
@param[in,out] scalar pointer to a location with pre-allocated memory
|
|
used for JSON scalars that are converted from
|
|
SQL scalars
|
|
|
|
@retval false on success
|
|
@retval true on failure
|
|
*/
|
|
static bool get_json_arg(Item* arg, String *value, String *tmp,
|
|
Json_wrapper *result, Json_scalar_holder **scalar)
|
|
{
|
|
Json_scalar_holder *holder= NULL;
|
|
|
|
/*
|
|
If the argument is a non-JSON type, it gets converted to a JSON
|
|
scalar. Use the pre-allocated memory passed in via the "scalar"
|
|
argument. Note, however, that geometry types are not converted
|
|
to scalars. They are converted to JSON objects by get_json_atom_wrapper().
|
|
*/
|
|
if ((arg->field_type() != MYSQL_TYPE_JSON) &&
|
|
(arg->field_type() != MYSQL_TYPE_GEOMETRY))
|
|
{
|
|
/*
|
|
If it's a constant item, and we've already read it, just return
|
|
the value that's cached in the pre-allocated memory.
|
|
*/
|
|
if (*scalar && arg->const_item())
|
|
{
|
|
Json_wrapper tmp(get_json_scalar_from_holder(*scalar));
|
|
tmp.set_alias();
|
|
result->steal(&tmp);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
Allocate memory to hold the scalar, if we haven't already done
|
|
so. Otherwise, we reuse the previously allocated memory.
|
|
*/
|
|
if (!*scalar)
|
|
*scalar= create_json_scalar_holder();
|
|
|
|
holder= *scalar;
|
|
}
|
|
|
|
return get_json_atom_wrapper(&arg, 0, "<=", value, tmp, result, holder, true);
|
|
}
|
|
|
|
|
|
/**
|
|
Compare two Item objects as JSON.
|
|
|
|
If one of the arguments is NULL, and the owner is not EQUAL_FUNC,
|
|
the null_value flag of the owner will be set to true.
|
|
|
|
@return
|
|
|
|
If is_nulls_eq is true, return 1 if both items are not NULL and
|
|
they are equal, or if both items are NULL; otherwise, return 0.
|
|
|
|
If is_nulls_eq is false, return -1 if at least one of the items is
|
|
NULL or if the first item is less than the second item, return 0
|
|
if the two items are equal, return 1 if the first item is greater
|
|
than the second item.
|
|
*/
|
|
int Arg_comparator::compare_json()
|
|
{
|
|
char buf[STRING_BUFFER_USUAL_SIZE];
|
|
String tmp(buf, sizeof(buf), &my_charset_bin);
|
|
|
|
// Get the JSON value in the a Item.
|
|
Json_wrapper aw;
|
|
if (get_json_arg(*a, &value1, &tmp, &aw, &json_scalar))
|
|
return 1;
|
|
|
|
bool a_is_null= (*a)->null_value;
|
|
if (a_is_null)
|
|
{
|
|
if (!is_nulls_eq)
|
|
{
|
|
if (set_null)
|
|
owner->null_value= true;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// Get the JSON value in the b Item.
|
|
Json_wrapper bw;
|
|
if (get_json_arg(*b, &value1, &tmp, &bw, &json_scalar))
|
|
return 1;
|
|
|
|
bool b_is_null= (*b)->null_value;
|
|
if (b_is_null)
|
|
{
|
|
if (!is_nulls_eq)
|
|
{
|
|
if (set_null)
|
|
owner->null_value= true;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (set_null)
|
|
owner->null_value= false;
|
|
|
|
/*
|
|
If we were called by the <=> operator, we should return 0/1
|
|
instead of -1/0/1. 0 means not equal, 1 means equal. The <=>
|
|
operator considers two NULLs equal.
|
|
*/
|
|
if (is_nulls_eq)
|
|
{
|
|
if (a_is_null || b_is_null)
|
|
return a_is_null == b_is_null;
|
|
else
|
|
return aw.compare(bw) == 0;
|
|
}
|
|
|
|
// Otherwise, return -1/0/1.
|
|
return aw.compare(bw);
|
|
}
|
|
|
|
|
|
int Arg_comparator::compare_string()
|
|
{
|
|
String *res1,*res2;
|
|
if ((res1= (*a)->val_str(&value1)))
|
|
{
|
|
if ((res2= (*b)->val_str(&value2)))
|
|
{
|
|
if (set_null)
|
|
owner->null_value= 0;
|
|
return sortcmp(res1,res2,cmp_collation.collation);
|
|
}
|
|
}
|
|
if (set_null)
|
|
owner->null_value= 1;
|
|
return -1;
|
|
}
|
|
|
|
|
|
/**
|
|
Compare strings byte by byte. End spaces are also compared.
|
|
|
|
@retval
|
|
<0 *a < *b
|
|
@retval
|
|
0 *b == *b
|
|
@retval
|
|
>0 *a > *b
|
|
*/
|
|
|
|
int Arg_comparator::compare_binary_string()
|
|
{
|
|
String *res1,*res2;
|
|
if ((res1= (*a)->val_str(&value1)))
|
|
{
|
|
if ((res2= (*b)->val_str(&value2)))
|
|
{
|
|
if (set_null)
|
|
owner->null_value= 0;
|
|
size_t res1_length= res1->length();
|
|
size_t res2_length= res2->length();
|
|
int cmp= memcmp(res1->ptr(), res2->ptr(), min(res1_length,res2_length));
|
|
return cmp ? cmp : (int) (res1_length - res2_length);
|
|
}
|
|
}
|
|
if (set_null)
|
|
owner->null_value= 1;
|
|
return -1;
|
|
}
|
|
|
|
|
|
/**
|
|
Compare strings, but take into account that NULL == NULL.
|
|
*/
|
|
|
|
|
|
int Arg_comparator::compare_e_string()
|
|
{
|
|
String *res1,*res2;
|
|
res1= (*a)->val_str(&value1);
|
|
res2= (*b)->val_str(&value2);
|
|
if (!res1 || !res2)
|
|
return MY_TEST(res1 == res2);
|
|
return MY_TEST(sortcmp(res1, res2, cmp_collation.collation) == 0);
|
|
}
|
|
|
|
|
|
int Arg_comparator::compare_e_binary_string()
|
|
{
|
|
String *res1,*res2;
|
|
res1= (*a)->val_str(&value1);
|
|
res2= (*b)->val_str(&value2);
|
|
if (!res1 || !res2)
|
|
return MY_TEST(res1 == res2);
|
|
return MY_TEST(stringcmp(res1, res2) == 0);
|
|
}
|
|
|
|
|
|
int Arg_comparator::compare_real()
|
|
{
|
|
/*
|
|
Fix yet another manifestation of Bug#2338. 'Volatile' will instruct
|
|
gcc to flush double values out of 80-bit Intel FPU registers before
|
|
performing the comparison.
|
|
*/
|
|
volatile double val1, val2;
|
|
val1= (*a)->val_real();
|
|
if (!(*a)->null_value)
|
|
{
|
|
val2= (*b)->val_real();
|
|
if (!(*b)->null_value)
|
|
{
|
|
if (set_null)
|
|
owner->null_value= 0;
|
|
if (val1 < val2) return -1;
|
|
if (val1 == val2) return 0;
|
|
return 1;
|
|
}
|
|
}
|
|
if (set_null)
|
|
owner->null_value= 1;
|
|
return -1;
|
|
}
|
|
|
|
int Arg_comparator::compare_decimal()
|
|
{
|
|
my_decimal decimal1;
|
|
my_decimal *val1= (*a)->val_decimal(&decimal1);
|
|
if (!(*a)->null_value)
|
|
{
|
|
my_decimal decimal2;
|
|
my_decimal *val2= (*b)->val_decimal(&decimal2);
|
|
if (!(*b)->null_value)
|
|
{
|
|
if (set_null)
|
|
owner->null_value= 0;
|
|
return my_decimal_cmp(val1, val2);
|
|
}
|
|
}
|
|
if (set_null)
|
|
owner->null_value= 1;
|
|
return -1;
|
|
}
|
|
|
|
int Arg_comparator::compare_e_real()
|
|
{
|
|
double val1= (*a)->val_real();
|
|
double val2= (*b)->val_real();
|
|
if ((*a)->null_value || (*b)->null_value)
|
|
return MY_TEST((*a)->null_value && (*b)->null_value);
|
|
return MY_TEST(val1 == val2);
|
|
}
|
|
|
|
int Arg_comparator::compare_e_decimal()
|
|
{
|
|
my_decimal decimal1, decimal2;
|
|
my_decimal *val1= (*a)->val_decimal(&decimal1);
|
|
my_decimal *val2= (*b)->val_decimal(&decimal2);
|
|
if ((*a)->null_value || (*b)->null_value)
|
|
return MY_TEST((*a)->null_value && (*b)->null_value);
|
|
return MY_TEST(my_decimal_cmp(val1, val2) == 0);
|
|
}
|
|
|
|
|
|
int Arg_comparator::compare_real_fixed()
|
|
{
|
|
/*
|
|
Fix yet another manifestation of Bug#2338. 'Volatile' will instruct
|
|
gcc to flush double values out of 80-bit Intel FPU registers before
|
|
performing the comparison.
|
|
*/
|
|
volatile double val1, val2;
|
|
val1= (*a)->val_real();
|
|
if (!(*a)->null_value)
|
|
{
|
|
val2= (*b)->val_real();
|
|
if (!(*b)->null_value)
|
|
{
|
|
if (set_null)
|
|
owner->null_value= 0;
|
|
if (val1 == val2 || fabs(val1 - val2) < precision)
|
|
return 0;
|
|
if (val1 < val2)
|
|
return -1;
|
|
return 1;
|
|
}
|
|
}
|
|
if (set_null)
|
|
owner->null_value= 1;
|
|
return -1;
|
|
}
|
|
|
|
|
|
int Arg_comparator::compare_e_real_fixed()
|
|
{
|
|
double val1= (*a)->val_real();
|
|
double val2= (*b)->val_real();
|
|
if ((*a)->null_value || (*b)->null_value)
|
|
return MY_TEST((*a)->null_value && (*b)->null_value);
|
|
return MY_TEST(val1 == val2 || fabs(val1 - val2) < precision);
|
|
}
|
|
|
|
|
|
int Arg_comparator::compare_int_signed()
|
|
{
|
|
longlong val1= (*a)->val_int();
|
|
if (!(*a)->null_value)
|
|
{
|
|
longlong val2= (*b)->val_int();
|
|
if (!(*b)->null_value)
|
|
{
|
|
if (set_null)
|
|
owner->null_value= 0;
|
|
if (val1 < val2) return -1;
|
|
if (val1 == val2) return 0;
|
|
return 1;
|
|
}
|
|
}
|
|
if (set_null)
|
|
owner->null_value= 1;
|
|
return -1;
|
|
}
|
|
|
|
|
|
/**
|
|
Compare arguments using numeric packed temporal representation.
|
|
*/
|
|
int Arg_comparator::compare_time_packed()
|
|
{
|
|
/*
|
|
Note, we cannot do this:
|
|
DBUG_ASSERT((*a)->field_type() == MYSQL_TYPE_TIME);
|
|
DBUG_ASSERT((*b)->field_type() == MYSQL_TYPE_TIME);
|
|
|
|
SELECT col_time_key FROM t1
|
|
WHERE
|
|
col_time_key != UTC_DATE()
|
|
AND
|
|
col_time_key = MAKEDATE(43, -2852);
|
|
|
|
is rewritten to:
|
|
|
|
SELECT col_time_key FROM t1
|
|
WHERE
|
|
MAKEDATE(43, -2852) != UTC_DATE()
|
|
AND
|
|
col_time_key = MAKEDATE(43, -2852);
|
|
*/
|
|
longlong val1= (*a)->val_time_temporal();
|
|
if (!(*a)->null_value)
|
|
{
|
|
longlong val2= (*b)->val_time_temporal();
|
|
if (!(*b)->null_value)
|
|
{
|
|
if (set_null)
|
|
owner->null_value= 0;
|
|
return val1 < val2 ? -1 : val1 > val2 ? 1 : 0;
|
|
}
|
|
}
|
|
if (set_null)
|
|
owner->null_value= 1;
|
|
return -1;
|
|
}
|
|
|
|
|
|
/**
|
|
Compare arguments using numeric packed representation for '<=>'.
|
|
*/
|
|
int Arg_comparator::compare_e_time_packed()
|
|
{
|
|
longlong val1= (*a)->val_time_temporal();
|
|
longlong val2= (*b)->val_time_temporal();
|
|
if ((*a)->null_value || (*b)->null_value)
|
|
return MY_TEST((*a)->null_value && (*b)->null_value);
|
|
return MY_TEST(val1 == val2);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
Compare values as BIGINT UNSIGNED.
|
|
*/
|
|
|
|
int Arg_comparator::compare_int_unsigned()
|
|
{
|
|
ulonglong val1= (*a)->val_int();
|
|
if (!(*a)->null_value)
|
|
{
|
|
ulonglong val2= (*b)->val_int();
|
|
if (!(*b)->null_value)
|
|
{
|
|
if (set_null)
|
|
owner->null_value= 0;
|
|
if (val1 < val2) return -1;
|
|
if (val1 == val2) return 0;
|
|
return 1;
|
|
}
|
|
}
|
|
if (set_null)
|
|
owner->null_value= 1;
|
|
return -1;
|
|
}
|
|
|
|
|
|
/**
|
|
Compare signed (*a) with unsigned (*B)
|
|
*/
|
|
|
|
int Arg_comparator::compare_int_signed_unsigned()
|
|
{
|
|
longlong sval1= (*a)->val_int();
|
|
if (!(*a)->null_value)
|
|
{
|
|
ulonglong uval2= (ulonglong)(*b)->val_int();
|
|
if (!(*b)->null_value)
|
|
{
|
|
if (set_null)
|
|
owner->null_value= 0;
|
|
if (sval1 < 0 || (ulonglong)sval1 < uval2)
|
|
return -1;
|
|
if ((ulonglong)sval1 == uval2)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
}
|
|
if (set_null)
|
|
owner->null_value= 1;
|
|
return -1;
|
|
}
|
|
|
|
|
|
/**
|
|
Compare unsigned (*a) with signed (*B)
|
|
*/
|
|
|
|
int Arg_comparator::compare_int_unsigned_signed()
|
|
{
|
|
ulonglong uval1= (ulonglong)(*a)->val_int();
|
|
if (!(*a)->null_value)
|
|
{
|
|
longlong sval2= (*b)->val_int();
|
|
if (!(*b)->null_value)
|
|
{
|
|
if (set_null)
|
|
owner->null_value= 0;
|
|
if (sval2 < 0)
|
|
return 1;
|
|
if (uval1 < (ulonglong)sval2)
|
|
return -1;
|
|
if (uval1 == (ulonglong)sval2)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
}
|
|
if (set_null)
|
|
owner->null_value= 1;
|
|
return -1;
|
|
}
|
|
|
|
|
|
int Arg_comparator::compare_e_int()
|
|
{
|
|
longlong val1= (*a)->val_int();
|
|
longlong val2= (*b)->val_int();
|
|
if ((*a)->null_value || (*b)->null_value)
|
|
return MY_TEST((*a)->null_value && (*b)->null_value);
|
|
return MY_TEST(val1 == val2);
|
|
}
|
|
|
|
/**
|
|
Compare unsigned *a with signed *b or signed *a with unsigned *b.
|
|
*/
|
|
int Arg_comparator::compare_e_int_diff_signedness()
|
|
{
|
|
longlong val1= (*a)->val_int();
|
|
longlong val2= (*b)->val_int();
|
|
if ((*a)->null_value || (*b)->null_value)
|
|
return MY_TEST((*a)->null_value && (*b)->null_value);
|
|
return (val1 >= 0) && MY_TEST(val1 == val2);
|
|
}
|
|
|
|
int Arg_comparator::compare_row()
|
|
{
|
|
int res= 0;
|
|
bool was_null= 0;
|
|
(*a)->bring_value();
|
|
(*b)->bring_value();
|
|
|
|
if ((*a)->null_value || (*b)->null_value)
|
|
{
|
|
owner->null_value= 1;
|
|
return -1;
|
|
}
|
|
|
|
uint n= (*a)->cols();
|
|
for (uint i= 0; i<n; i++)
|
|
{
|
|
res= comparators[i].compare();
|
|
/* Aggregate functions don't need special null handling. */
|
|
if (owner->null_value && owner->type() == Item::FUNC_ITEM)
|
|
{
|
|
// NULL was compared
|
|
switch (((Item_func*)owner)->functype()) {
|
|
case Item_func::NE_FUNC:
|
|
break; // NE never aborts on NULL even if abort_on_null is set
|
|
case Item_func::LT_FUNC:
|
|
case Item_func::LE_FUNC:
|
|
case Item_func::GT_FUNC:
|
|
case Item_func::GE_FUNC:
|
|
return -1; // <, <=, > and >= always fail on NULL
|
|
default: // EQ_FUNC
|
|
if (((Item_bool_func2*)owner)->abort_on_null)
|
|
return -1; // We do not need correct NULL returning
|
|
}
|
|
was_null= 1;
|
|
owner->null_value= 0;
|
|
res= 0; // continue comparison (maybe we will meet explicit difference)
|
|
}
|
|
else if (res)
|
|
return res;
|
|
}
|
|
if (was_null)
|
|
{
|
|
/*
|
|
There was NULL(s) in comparison in some parts, but there was no
|
|
explicit difference in other parts, so we have to return NULL.
|
|
*/
|
|
owner->null_value= 1;
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
int Arg_comparator::compare_e_row()
|
|
{
|
|
(*a)->bring_value();
|
|
(*b)->bring_value();
|
|
uint n= (*a)->cols();
|
|
for (uint i= 0; i<n; i++)
|
|
{
|
|
if (!comparators[i].compare())
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
|
|
void Item_func_truth::fix_length_and_dec()
|
|
{
|
|
maybe_null= 0;
|
|
null_value= 0;
|
|
decimals= 0;
|
|
max_length= 1;
|
|
}
|
|
|
|
|
|
void Item_func_truth::print(String *str, enum_query_type query_type)
|
|
{
|
|
str->append('(');
|
|
args[0]->print(str, query_type);
|
|
str->append(STRING_WITH_LEN(" is "));
|
|
if (! affirmative)
|
|
str->append(STRING_WITH_LEN("not "));
|
|
if (value)
|
|
str->append(STRING_WITH_LEN("true"));
|
|
else
|
|
str->append(STRING_WITH_LEN("false"));
|
|
str->append(')');
|
|
}
|
|
|
|
|
|
bool Item_func_truth::val_bool()
|
|
{
|
|
bool val= args[0]->val_bool();
|
|
if (args[0]->null_value)
|
|
{
|
|
/*
|
|
NULL val IS {TRUE, FALSE} --> FALSE
|
|
NULL val IS NOT {TRUE, FALSE} --> TRUE
|
|
*/
|
|
return (! affirmative);
|
|
}
|
|
|
|
if (affirmative)
|
|
{
|
|
/* {TRUE, FALSE} val IS {TRUE, FALSE} value */
|
|
return (val == value);
|
|
}
|
|
|
|
/* {TRUE, FALSE} val IS NOT {TRUE, FALSE} value */
|
|
return (val != value);
|
|
}
|
|
|
|
|
|
longlong Item_func_truth::val_int()
|
|
{
|
|
return (val_bool() ? 1 : 0);
|
|
}
|
|
|
|
|
|
bool Item_in_optimizer::fix_left(THD *thd, Item **ref)
|
|
{
|
|
/*
|
|
Refresh this pointer as left_expr may have been substituted
|
|
during resolving.
|
|
*/
|
|
args[0]= ((Item_in_subselect *)args[1])->left_expr;
|
|
|
|
if ((!args[0]->fixed && args[0]->fix_fields(thd, args)) ||
|
|
(!cache && !(cache= Item_cache::get_cache(args[0]))))
|
|
return 1;
|
|
|
|
cache->setup(args[0]);
|
|
used_tables_cache= args[0]->used_tables();
|
|
if (cache->cols() == 1)
|
|
{
|
|
cache->set_used_tables(used_tables_cache);
|
|
}
|
|
else
|
|
{
|
|
uint n= cache->cols();
|
|
for (uint i= 0; i < n; i++)
|
|
{
|
|
((Item_cache *)cache->element_index(i))->
|
|
set_used_tables(args[0]->element_index(i)->used_tables());
|
|
}
|
|
}
|
|
not_null_tables_cache= args[0]->not_null_tables();
|
|
with_sum_func= args[0]->with_sum_func;
|
|
if ((const_item_cache= args[0]->const_item()))
|
|
cache->store(args[0]);
|
|
return 0;
|
|
}
|
|
|
|
|
|
bool Item_in_optimizer::fix_fields(THD *thd, Item **ref)
|
|
{
|
|
DBUG_ASSERT(fixed == 0);
|
|
if (fix_left(thd, ref))
|
|
return TRUE;
|
|
if (args[0]->maybe_null)
|
|
maybe_null=1;
|
|
|
|
if (!args[1]->fixed && args[1]->fix_fields(thd, args+1))
|
|
return TRUE;
|
|
Item_in_subselect * sub= (Item_in_subselect *)args[1];
|
|
if (args[0]->cols() != sub->engine->cols())
|
|
{
|
|
my_error(ER_OPERAND_COLUMNS, MYF(0), args[0]->cols());
|
|
return TRUE;
|
|
}
|
|
if (args[1]->maybe_null)
|
|
maybe_null=1;
|
|
with_sum_func= with_sum_func || args[1]->with_sum_func;
|
|
used_tables_cache|= args[1]->used_tables();
|
|
not_null_tables_cache|= args[1]->not_null_tables();
|
|
|
|
if (!sub->is_top_level_item())
|
|
{
|
|
/*
|
|
This is a NOT IN subquery predicate (or equivalent). Null values passed
|
|
from outer tables and used in the left-hand expression of the predicate
|
|
must be considered in the evaluation, hence filter out these tables
|
|
from the set of null-rejecting tables.
|
|
*/
|
|
not_null_tables_cache&= ~args[0]->not_null_tables();
|
|
}
|
|
const_item_cache&= args[1]->const_item();
|
|
fixed= 1;
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
void Item_in_optimizer::fix_after_pullout(st_select_lex *parent_select,
|
|
st_select_lex *removed_select)
|
|
{
|
|
used_tables_cache= get_initial_pseudo_tables();
|
|
not_null_tables_cache= 0;
|
|
const_item_cache= 1;
|
|
|
|
/*
|
|
No need to call fix_after_pullout() on args[0] (ie left expression),
|
|
as Item_in_subselect::fix_after_pullout() will do this.
|
|
So, just forward the call to the Item_in_subselect object.
|
|
*/
|
|
|
|
args[1]->fix_after_pullout(parent_select, removed_select);
|
|
|
|
used_tables_cache|= args[1]->used_tables();
|
|
not_null_tables_cache|= args[1]->not_null_tables();
|
|
const_item_cache&= args[1]->const_item();
|
|
}
|
|
|
|
|
|
/**
|
|
The implementation of optimized \<outer expression\> [NOT] IN \<subquery\>
|
|
predicates. It applies to predicates which have gone through the IN->EXISTS
|
|
transformation in in_to_exists_transformer functions; not to subquery
|
|
materialization (which has no triggered conditions).
|
|
|
|
The implementation works as follows.
|
|
For the current value of the outer expression
|
|
|
|
- If it contains only NULL values, the original (before rewrite by the
|
|
Item_in_subselect rewrite methods) inner subquery is non-correlated and
|
|
was previously executed, there is no need to re-execute it, and the
|
|
previous return value is returned.
|
|
|
|
- If it contains NULL values, check if there is a partial match for the
|
|
inner query block by evaluating it. For clarity we repeat here the
|
|
transformation previously performed on the sub-query. The expression
|
|
|
|
<tt>
|
|
( oc_1, ..., oc_n )
|
|
\<in predicate\>
|
|
( SELECT ic_1, ..., ic_n
|
|
FROM \<table\>
|
|
WHERE \<inner where\>
|
|
)
|
|
</tt>
|
|
|
|
was transformed into
|
|
|
|
<tt>
|
|
( oc_1, ..., oc_n )
|
|
\<in predicate\>
|
|
( SELECT ic_1, ..., ic_n
|
|
FROM \<table\>
|
|
WHERE \<inner where\> AND ... ( ic_k = oc_k OR ic_k IS NULL )
|
|
HAVING ... NOT ic_k IS NULL
|
|
)
|
|
</tt>
|
|
|
|
The evaluation will now proceed according to special rules set up
|
|
elsewhere. These rules include:
|
|
|
|
- The HAVING NOT \<inner column\> IS NULL conditions added by the
|
|
aforementioned rewrite methods will detect whether they evaluated (and
|
|
rejected) a NULL value and if so, will cause the subquery to evaluate
|
|
to NULL.
|
|
|
|
- The added WHERE and HAVING conditions are present only for those inner
|
|
columns that correspond to outer column that are not NULL at the moment.
|
|
|
|
- If there is an eligible index for executing the subquery, the special
|
|
access method "Full scan on NULL key" is employed which ensures that
|
|
the inner query will detect if there are NULL values resulting from the
|
|
inner query. This access method will quietly resort to table scan if it
|
|
needs to find NULL values as well.
|
|
|
|
- Under these conditions, the sub-query need only be evaluated in order to
|
|
find out whether it produced any rows.
|
|
|
|
- If it did, we know that there was a partial match since there are
|
|
NULL values in the outer row expression.
|
|
|
|
- If it did not, the result is FALSE or UNKNOWN. If at least one of the
|
|
HAVING sub-predicates rejected a NULL value corresponding to an outer
|
|
non-NULL, and hence the inner query block returns UNKNOWN upon
|
|
evaluation, there was a partial match and the result is UNKNOWN.
|
|
|
|
- If it contains no NULL values, the call is forwarded to the inner query
|
|
block.
|
|
|
|
@see Item_in_subselect::val_bool()
|
|
@see Item_is_not_null_test::val_int()
|
|
*/
|
|
|
|
longlong Item_in_optimizer::val_int()
|
|
{
|
|
bool tmp;
|
|
DBUG_ASSERT(fixed == 1);
|
|
cache->store(args[0]);
|
|
cache->cache_value();
|
|
|
|
if (cache->null_value)
|
|
{
|
|
Item_in_subselect * const item_subs=
|
|
static_cast<Item_in_subselect *>(args[1]);
|
|
/*
|
|
We're evaluating
|
|
"<outer_value_list> [NOT] IN (SELECT <inner_value_list>...)"
|
|
where one or more of the outer values is NULL.
|
|
*/
|
|
if (item_subs->is_top_level_item())
|
|
{
|
|
/*
|
|
We're evaluating a top level item, e.g.
|
|
"<outer_value_list> IN (SELECT <inner_value_list>...)",
|
|
and in this case a NULL value in the outer_value_list means
|
|
that the result shall be NULL/FALSE (makes no difference for
|
|
top level items). The cached value is NULL, so just return
|
|
NULL.
|
|
*/
|
|
null_value= 1;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
We're evaluating an item where a NULL value in either the
|
|
outer or inner value list does not automatically mean that we
|
|
can return NULL/FALSE. An example of such a query is
|
|
"<outer_value_list> NOT IN (SELECT <inner_value_list>...)"
|
|
The result when there is at least one NULL value is: NULL if the
|
|
SELECT evaluated over the non-NULL values produces at least
|
|
one row, FALSE otherwise
|
|
*/
|
|
bool all_left_cols_null= true;
|
|
const uint ncols= cache->cols();
|
|
|
|
/*
|
|
Turn off the predicates that are based on column compares for
|
|
which the left part is currently NULL
|
|
*/
|
|
for (uint i= 0; i < ncols; i++)
|
|
{
|
|
if (cache->element_index(i)->null_value)
|
|
item_subs->set_cond_guard_var(i, FALSE);
|
|
else
|
|
all_left_cols_null= false;
|
|
}
|
|
|
|
if (all_left_cols_null && result_for_null_param != UNKNOWN &&
|
|
!item_subs->dependent_before_in2exists())
|
|
{
|
|
/*
|
|
This subquery was originally not correlated. The IN->EXISTS
|
|
transformation may have made it correlated, but only to the left
|
|
expression. All values in the left expression are NULL, and we have
|
|
already evaluated the subquery for all NULL values: return the same
|
|
result we did last time without evaluating the subquery.
|
|
*/
|
|
null_value= result_for_null_param;
|
|
}
|
|
else
|
|
{
|
|
/* The subquery has to be evaluated */
|
|
(void) item_subs->val_bool_result();
|
|
if (!item_subs->value)
|
|
null_value= item_subs->null_value;
|
|
else
|
|
null_value= TRUE;
|
|
if (all_left_cols_null)
|
|
result_for_null_param= null_value;
|
|
}
|
|
|
|
/* Turn all predicates back on */
|
|
for (uint i= 0; i < ncols; i++)
|
|
item_subs->set_cond_guard_var(i, TRUE);
|
|
}
|
|
return 0;
|
|
}
|
|
tmp= args[1]->val_bool_result();
|
|
null_value= args[1]->null_value;
|
|
return tmp;
|
|
}
|
|
|
|
|
|
void Item_in_optimizer::keep_top_level_cache()
|
|
{
|
|
cache->keep_array();
|
|
save_cache= 1;
|
|
}
|
|
|
|
|
|
void Item_in_optimizer::cleanup()
|
|
{
|
|
DBUG_ENTER("Item_in_optimizer::cleanup");
|
|
Item_bool_func::cleanup();
|
|
if (!save_cache)
|
|
cache= 0;
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
bool Item_in_optimizer::is_null()
|
|
{
|
|
val_int();
|
|
return null_value;
|
|
}
|
|
|
|
|
|
/**
|
|
Transform an Item_in_optimizer and its arguments with a callback function.
|
|
|
|
@param transformer the transformer callback function to be applied to the
|
|
nodes of the tree of the object
|
|
@param parameter to be passed to the transformer
|
|
|
|
@detail
|
|
Recursively transform the left and the right operand of this Item. The
|
|
Right operand is an Item_in_subselect or its subclass. To avoid the
|
|
creation of new Items, we use the fact the the left operand of the
|
|
Item_in_subselect is the same as the one of 'this', so instead of
|
|
transforming its operand, we just assign the left operand of the
|
|
Item_in_subselect to be equal to the left operand of 'this'.
|
|
The transformation is not applied further to the subquery operand
|
|
if the IN predicate.
|
|
|
|
@returns
|
|
@retval pointer to the transformed item
|
|
@retval NULL if an error occurred
|
|
*/
|
|
|
|
Item *Item_in_optimizer::transform(Item_transformer transformer, uchar *argument)
|
|
{
|
|
Item *new_item;
|
|
|
|
DBUG_ASSERT(!current_thd->stmt_arena->is_stmt_prepare());
|
|
DBUG_ASSERT(arg_count == 2);
|
|
|
|
/* Transform the left IN operand. */
|
|
new_item= args[0]->transform(transformer, argument);
|
|
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 (args[0] != new_item)
|
|
current_thd->change_item_tree(args, new_item);
|
|
|
|
/*
|
|
Transform the right IN operand which should be an Item_in_subselect or a
|
|
subclass of it. The left operand of the IN must be the same as the left
|
|
operand of this Item_in_optimizer, so in this case there is no further
|
|
transformation, we only make both operands the same.
|
|
TODO: is it the way it should be?
|
|
*/
|
|
DBUG_ASSERT((args[1])->type() == Item::SUBSELECT_ITEM &&
|
|
(((Item_subselect*)(args[1]))->substype() ==
|
|
Item_subselect::IN_SUBS ||
|
|
((Item_subselect*)(args[1]))->substype() ==
|
|
Item_subselect::ALL_SUBS ||
|
|
((Item_subselect*)(args[1]))->substype() ==
|
|
Item_subselect::ANY_SUBS));
|
|
|
|
Item_in_subselect *in_arg= (Item_in_subselect*)args[1];
|
|
|
|
if (in_arg->left_expr != args[0])
|
|
current_thd->change_item_tree(&in_arg->left_expr, args[0]);
|
|
|
|
return (this->*transformer)(argument);
|
|
}
|
|
|
|
|
|
void Item_in_optimizer::replace_argument(THD *thd, Item **oldpp, Item *newp)
|
|
{
|
|
// Maintain the invariant described in this class's comment
|
|
Item_in_subselect *ss= down_cast<Item_in_subselect *>(args[1]);
|
|
thd->change_item_tree(&ss->left_expr, newp);
|
|
/*
|
|
fix_left() does cache setup. This setup() does (mainly)
|
|
cache->example=arg[0]; we could wonder why change_item_tree isn't used
|
|
instead of this simple assignment. The reason is that cache->setup() is
|
|
called at every fix_fields(), so every execution, so it's not important if
|
|
the previous execution left a non-rolled-back now-pointing-to-garbage
|
|
cache->example - it will be overwritten.
|
|
*/
|
|
fix_left(thd, NULL);
|
|
}
|
|
|
|
longlong Item_func_eq::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
int value= cmp.compare();
|
|
return value == 0 ? 1 : 0;
|
|
}
|
|
|
|
|
|
/** Same as Item_func_eq, but NULL = NULL. */
|
|
|
|
void Item_func_equal::fix_length_and_dec()
|
|
{
|
|
Item_bool_func2::fix_length_and_dec();
|
|
maybe_null=null_value=0;
|
|
}
|
|
|
|
longlong Item_func_equal::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
return cmp.compare();
|
|
}
|
|
|
|
float Item_func_ne::get_filtering_effect(table_map filter_for_table,
|
|
table_map read_tables,
|
|
const MY_BITMAP *fields_to_ignore,
|
|
double rows_in_table)
|
|
{
|
|
const Item_field* fld=
|
|
contributes_to_filter(read_tables, filter_for_table, fields_to_ignore);
|
|
if (!fld)
|
|
return COND_FILTER_ALLPASS;
|
|
|
|
return 1.0f - fld->get_cond_filter_default_probability(rows_in_table,
|
|
COND_FILTER_EQUALITY);
|
|
}
|
|
|
|
longlong Item_func_ne::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
int value= cmp.compare();
|
|
return value != 0 && !null_value ? 1 : 0;
|
|
}
|
|
|
|
float Item_func_equal::get_filtering_effect(table_map filter_for_table,
|
|
table_map read_tables,
|
|
const MY_BITMAP *fields_to_ignore,
|
|
double rows_in_table)
|
|
{
|
|
const Item_field* fld=
|
|
contributes_to_filter(read_tables, filter_for_table, fields_to_ignore);
|
|
if (!fld)
|
|
return COND_FILTER_ALLPASS;
|
|
|
|
return fld->get_cond_filter_default_probability(rows_in_table,
|
|
COND_FILTER_EQUALITY);
|
|
}
|
|
|
|
float Item_func_ge::get_filtering_effect(table_map filter_for_table,
|
|
table_map read_tables,
|
|
const MY_BITMAP *fields_to_ignore,
|
|
double rows_in_table)
|
|
{
|
|
const Item_field* fld=
|
|
contributes_to_filter(read_tables, filter_for_table, fields_to_ignore);
|
|
if (!fld)
|
|
return COND_FILTER_ALLPASS;
|
|
|
|
return fld->get_cond_filter_default_probability(rows_in_table,
|
|
COND_FILTER_INEQUALITY);
|
|
}
|
|
|
|
float Item_func_lt::get_filtering_effect(table_map filter_for_table,
|
|
table_map read_tables,
|
|
const MY_BITMAP *fields_to_ignore,
|
|
double rows_in_table)
|
|
{
|
|
const Item_field* fld=
|
|
contributes_to_filter(read_tables, filter_for_table, fields_to_ignore);
|
|
if (!fld)
|
|
return COND_FILTER_ALLPASS;
|
|
|
|
return fld->get_cond_filter_default_probability(rows_in_table,
|
|
COND_FILTER_INEQUALITY);
|
|
}
|
|
|
|
float Item_func_le::get_filtering_effect(table_map filter_for_table,
|
|
table_map read_tables,
|
|
const MY_BITMAP *fields_to_ignore,
|
|
double rows_in_table)
|
|
{
|
|
const Item_field* fld=
|
|
contributes_to_filter(read_tables, filter_for_table, fields_to_ignore);
|
|
if (!fld)
|
|
return COND_FILTER_ALLPASS;
|
|
|
|
return fld->get_cond_filter_default_probability(rows_in_table,
|
|
COND_FILTER_INEQUALITY);
|
|
}
|
|
|
|
float Item_func_gt::get_filtering_effect(table_map filter_for_table,
|
|
table_map read_tables,
|
|
const MY_BITMAP *fields_to_ignore,
|
|
double rows_in_table)
|
|
{
|
|
const Item_field* fld=
|
|
contributes_to_filter(read_tables, filter_for_table, fields_to_ignore);
|
|
if (!fld)
|
|
return COND_FILTER_ALLPASS;
|
|
|
|
return fld->get_cond_filter_default_probability(rows_in_table,
|
|
COND_FILTER_INEQUALITY);
|
|
}
|
|
|
|
longlong Item_func_ge::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
int value= cmp.compare();
|
|
return value >= 0 ? 1 : 0;
|
|
}
|
|
|
|
longlong Item_func_gt::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
int value= cmp.compare();
|
|
return value > 0 ? 1 : 0;
|
|
}
|
|
|
|
longlong Item_func_le::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
int value= cmp.compare();
|
|
return value <= 0 && !null_value ? 1 : 0;
|
|
}
|
|
|
|
|
|
longlong Item_func_lt::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
int value= cmp.compare();
|
|
return value < 0 && !null_value ? 1 : 0;
|
|
}
|
|
|
|
|
|
longlong Item_func_strcmp::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
String *a=args[0]->val_str(&cmp.value1);
|
|
String *b=args[1]->val_str(&cmp.value2);
|
|
if (!a || !b)
|
|
{
|
|
null_value=1;
|
|
return 0;
|
|
}
|
|
int value= sortcmp(a,b,cmp.cmp_collation.collation);
|
|
null_value=0;
|
|
return !value ? 0 : (value < 0 ? (longlong) -1 : (longlong) 1);
|
|
}
|
|
|
|
|
|
bool Item_func_opt_neg::eq(const Item *item, bool binary_cmp) const
|
|
{
|
|
/* Assume we don't have rtti */
|
|
if (this == item)
|
|
return 1;
|
|
if (item->type() != FUNC_ITEM)
|
|
return 0;
|
|
Item_func *item_func=(Item_func*) item;
|
|
if (arg_count != item_func->arg_count ||
|
|
functype() != item_func->functype())
|
|
return 0;
|
|
if (negated != ((Item_func_opt_neg *) item_func)->negated)
|
|
return 0;
|
|
for (uint i=0; i < arg_count ; i++)
|
|
if (!args[i]->eq(item_func->arguments()[i], binary_cmp))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
|
|
bool Item_func_interval::itemize(Parse_context *pc, Item **res)
|
|
{
|
|
if (skip_itemize(res))
|
|
return false;
|
|
if (row == NULL || // OOM in constructor
|
|
super::itemize(pc, res))
|
|
return true;
|
|
DBUG_ASSERT(row == args[0]); // row->itemize() is not needed
|
|
return false;
|
|
}
|
|
|
|
|
|
Item_row *Item_func_interval::alloc_row(const POS &pos, MEM_ROOT *mem_root,
|
|
Item *expr1, Item *expr2,
|
|
PT_item_list *opt_expr_list)
|
|
{
|
|
List<Item> *list= opt_expr_list ? &opt_expr_list->value
|
|
: new (mem_root) List<Item>;
|
|
if (list == NULL)
|
|
return NULL;
|
|
list->push_front(expr2);
|
|
row= new (mem_root) Item_row(pos, expr1, *list);
|
|
return row;
|
|
}
|
|
|
|
|
|
void Item_func_interval::fix_length_and_dec()
|
|
{
|
|
uint rows= row->cols();
|
|
|
|
//The number of columns in one argument is limited to one
|
|
for (uint i= 0; i < rows; i++)
|
|
{
|
|
if (row->element_index(i)->check_cols(1))
|
|
return;
|
|
}
|
|
|
|
use_decimal_comparison= ((row->element_index(0)->result_type() ==
|
|
DECIMAL_RESULT) ||
|
|
(row->element_index(0)->result_type() ==
|
|
INT_RESULT));
|
|
if (rows > 8)
|
|
{
|
|
bool not_null_consts= TRUE;
|
|
|
|
for (uint i= 1; not_null_consts && i < rows; i++)
|
|
{
|
|
Item *el= row->element_index(i);
|
|
not_null_consts= el->const_item() && !el->is_null();
|
|
}
|
|
|
|
if (not_null_consts &&
|
|
(intervals=
|
|
(interval_range*) sql_alloc(sizeof(interval_range) * (rows - 1))))
|
|
{
|
|
if (use_decimal_comparison)
|
|
{
|
|
for (uint i= 1; i < rows; i++)
|
|
{
|
|
Item *el= row->element_index(i);
|
|
interval_range *range= intervals + (i-1);
|
|
if ((el->result_type() == DECIMAL_RESULT) ||
|
|
(el->result_type() == INT_RESULT))
|
|
{
|
|
range->type= DECIMAL_RESULT;
|
|
range->dec.init();
|
|
my_decimal *dec= el->val_decimal(&range->dec);
|
|
if (dec != &range->dec)
|
|
{
|
|
range->dec= *dec;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
range->type= REAL_RESULT;
|
|
range->dbl= el->val_real();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (uint i= 1; i < rows; i++)
|
|
{
|
|
intervals[i-1].dbl= row->element_index(i)->val_real();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
maybe_null= 0;
|
|
max_length= 2;
|
|
used_tables_cache|= row->used_tables();
|
|
not_null_tables_cache= row->not_null_tables();
|
|
with_sum_func= with_sum_func || row->with_sum_func;
|
|
const_item_cache&= row->const_item();
|
|
}
|
|
|
|
|
|
/**
|
|
Appends function name and arguments list to the String str.
|
|
|
|
@note
|
|
Arguments of INTERVAL function are stored in "Item_row" object. Function
|
|
print_args calls print function of "Item_row" class. Item_row::print
|
|
function append "(", "argument_list" and ")" to String str.
|
|
|
|
@param str [in/out] String to which the func_name and argument list
|
|
should be appeneded.
|
|
@param query_type [in] Query type
|
|
*/
|
|
|
|
void Item_func_interval::print(String *str, enum_query_type query_type)
|
|
{
|
|
str->append(func_name());
|
|
print_args(str, 0, query_type);
|
|
}
|
|
|
|
|
|
/**
|
|
Execute Item_func_interval().
|
|
|
|
@note
|
|
If we are doing a decimal comparison, we are evaluating the first
|
|
item twice.
|
|
|
|
@return
|
|
- -1 if null value,
|
|
- 0 if lower than lowest
|
|
- 1 - arg_count-1 if between args[n] and args[n+1]
|
|
- arg_count if higher than biggest argument
|
|
*/
|
|
|
|
longlong Item_func_interval::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
double value;
|
|
my_decimal dec_buf, *dec= NULL;
|
|
uint i;
|
|
|
|
if (use_decimal_comparison)
|
|
{
|
|
dec= row->element_index(0)->val_decimal(&dec_buf);
|
|
if (row->element_index(0)->null_value)
|
|
return -1;
|
|
my_decimal2double(E_DEC_FATAL_ERROR, dec, &value);
|
|
}
|
|
else
|
|
{
|
|
value= row->element_index(0)->val_real();
|
|
if (row->element_index(0)->null_value)
|
|
return -1;
|
|
}
|
|
|
|
if (intervals)
|
|
{ // Use binary search to find interval
|
|
uint start,end;
|
|
start= 0;
|
|
end= row->cols()-2;
|
|
while (start != end)
|
|
{
|
|
uint mid= (start + end + 1) / 2;
|
|
interval_range *range= intervals + mid;
|
|
my_bool cmp_result;
|
|
/*
|
|
The values in the range intervall may have different types,
|
|
Only do a decimal comparision of the first argument is a decimal
|
|
and we are comparing against a decimal
|
|
*/
|
|
if (dec && range->type == DECIMAL_RESULT)
|
|
cmp_result= my_decimal_cmp(&range->dec, dec) <= 0;
|
|
else
|
|
cmp_result= (range->dbl <= value);
|
|
if (cmp_result)
|
|
start= mid;
|
|
else
|
|
end= mid - 1;
|
|
}
|
|
interval_range *range= intervals+start;
|
|
return ((dec && range->type == DECIMAL_RESULT) ?
|
|
my_decimal_cmp(dec, &range->dec) < 0 :
|
|
value < range->dbl) ? 0 : start + 1;
|
|
}
|
|
|
|
for (i=1 ; i < row->cols() ; i++)
|
|
{
|
|
Item *el= row->element_index(i);
|
|
if (use_decimal_comparison &&
|
|
((el->result_type() == DECIMAL_RESULT) ||
|
|
(el->result_type() == INT_RESULT)))
|
|
{
|
|
my_decimal e_dec_buf, *e_dec= el->val_decimal(&e_dec_buf);
|
|
/* Skip NULL ranges. */
|
|
if (el->null_value)
|
|
continue;
|
|
if (my_decimal_cmp(e_dec, dec) > 0)
|
|
return i - 1;
|
|
}
|
|
else
|
|
{
|
|
double val= el->val_real();
|
|
/* Skip NULL ranges. */
|
|
if (el->null_value)
|
|
continue;
|
|
if (val > value)
|
|
return i - 1;
|
|
}
|
|
}
|
|
return i-1;
|
|
}
|
|
|
|
|
|
/**
|
|
Perform context analysis of a BETWEEN item tree.
|
|
|
|
This function performs context analysis (name resolution) and calculates
|
|
various attributes of the item tree with Item_func_between as its root.
|
|
The function saves in ref the pointer to the item or to a newly created
|
|
item that is considered as a replacement for the original one.
|
|
|
|
@param thd reference to the global context of the query thread
|
|
@param ref pointer to Item* variable where pointer to resulting "fixed"
|
|
item is to be assigned
|
|
|
|
@note
|
|
Let T0(e)/T1(e) be the value of not_null_tables(e) when e is used on
|
|
a predicate/function level. Then it's easy to show that:
|
|
@verbatim
|
|
T0(e BETWEEN e1 AND e2) = union(T1(e),T1(e1),T1(e2))
|
|
T1(e BETWEEN e1 AND e2) = union(T1(e),intersection(T1(e1),T1(e2)))
|
|
T0(e NOT BETWEEN e1 AND e2) = union(T1(e),intersection(T1(e1),T1(e2)))
|
|
T1(e NOT BETWEEN e1 AND e2) = union(T1(e),intersection(T1(e1),T1(e2)))
|
|
@endverbatim
|
|
|
|
@retval
|
|
0 ok
|
|
@retval
|
|
1 got error
|
|
*/
|
|
|
|
bool Item_func_between::fix_fields(THD *thd, Item **ref)
|
|
{
|
|
if (Item_func_opt_neg::fix_fields(thd, ref))
|
|
return 1;
|
|
|
|
thd->lex->current_select()->between_count++;
|
|
|
|
// not_null_tables_cache == union(T1(e),T1(e1),T1(e2))
|
|
if (pred_level && !negated)
|
|
return 0;
|
|
|
|
// not_null_tables_cache == union(T1(e), intersection(T1(e1),T1(e2)))
|
|
not_null_tables_cache= (args[0]->not_null_tables() |
|
|
(args[1]->not_null_tables() &
|
|
args[2]->not_null_tables()));
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void Item_func_between::fix_after_pullout(st_select_lex *parent_select,
|
|
st_select_lex *removed_select)
|
|
{
|
|
Item_func_opt_neg::fix_after_pullout(parent_select, removed_select);
|
|
|
|
// not_null_tables_cache == union(T1(e),T1(e1),T1(e2))
|
|
if (pred_level && !negated)
|
|
return;
|
|
|
|
// not_null_tables_cache == union(T1(e), intersection(T1(e1),T1(e2)))
|
|
not_null_tables_cache= args[0]->not_null_tables() |
|
|
(args[1]->not_null_tables() &
|
|
args[2]->not_null_tables());
|
|
}
|
|
|
|
|
|
void Item_func_between::fix_length_and_dec()
|
|
{
|
|
max_length= 1;
|
|
int i;
|
|
int datetime_items_found= 0;
|
|
int time_items_found= 0;
|
|
compare_as_dates_with_strings= false;
|
|
compare_as_temporal_times= compare_as_temporal_dates= false;
|
|
THD *thd= current_thd;
|
|
|
|
/*
|
|
As some compare functions are generated after sql_yacc,
|
|
we have to check for out of memory conditions here
|
|
*/
|
|
if (!args[0] || !args[1] || !args[2])
|
|
return;
|
|
if ( agg_cmp_type(&cmp_type, args, 3))
|
|
return;
|
|
if (cmp_type == STRING_RESULT &&
|
|
agg_arg_charsets_for_comparison(cmp_collation, args, 3))
|
|
return;
|
|
|
|
/*
|
|
See comments for the code block doing similar checks in
|
|
Item_bool_func2::fix_length_and_dec().
|
|
*/
|
|
reject_geometry_args(arg_count, args, this);
|
|
|
|
/*
|
|
JSON values will be compared as strings, and not with the JSON
|
|
comparator as one might expect. Raise a warning if one of the
|
|
arguments is JSON.
|
|
*/
|
|
unsupported_json_comparison(arg_count, args,
|
|
"comparison of JSON in the BETWEEN operator");
|
|
|
|
/*
|
|
Detect the comparison of DATE/DATETIME items.
|
|
At least one of items should be a DATE/DATETIME item and other items
|
|
should return the STRING result.
|
|
*/
|
|
if (cmp_type == STRING_RESULT)
|
|
{
|
|
for (i= 0; i < 3; i++)
|
|
{
|
|
if (args[i]->is_temporal_with_date())
|
|
datetime_items_found++;
|
|
else
|
|
if (args[i]->field_type() == MYSQL_TYPE_TIME)
|
|
time_items_found++;
|
|
}
|
|
}
|
|
|
|
if (datetime_items_found + time_items_found == 3)
|
|
{
|
|
if (time_items_found == 3)
|
|
{
|
|
// All items are TIME
|
|
cmp_type= INT_RESULT;
|
|
compare_as_temporal_times= true;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
There is at least one DATE or DATETIME item,
|
|
all other items are DATE, DATETIME or TIME.
|
|
*/
|
|
cmp_type= INT_RESULT;
|
|
compare_as_temporal_dates= true;
|
|
}
|
|
}
|
|
else if (datetime_items_found > 0)
|
|
{
|
|
/*
|
|
There is at least one DATE or DATETIME item.
|
|
All other items are DATE, DATETIME or strings.
|
|
*/
|
|
compare_as_dates_with_strings= true;
|
|
ge_cmp.set_datetime_cmp_func(this, args, args + 1);
|
|
le_cmp.set_datetime_cmp_func(this, args, args + 2);
|
|
}
|
|
else if (args[0]->real_item()->type() == FIELD_ITEM &&
|
|
thd->lex->sql_command != SQLCOM_CREATE_VIEW &&
|
|
thd->lex->sql_command != SQLCOM_SHOW_CREATE)
|
|
{
|
|
Item_field *field_item= (Item_field*) (args[0]->real_item());
|
|
if (field_item->field->can_be_compared_as_longlong())
|
|
{
|
|
/*
|
|
The following can't be recoded with || as convert_constant_item
|
|
changes the argument
|
|
*/
|
|
const bool cvt_arg1= convert_constant_item(thd, field_item, &args[1]);
|
|
const bool cvt_arg2= convert_constant_item(thd, field_item, &args[2]);
|
|
if (args[0]->is_temporal())
|
|
{ // special handling of date/time etc.
|
|
if (cvt_arg1 || cvt_arg2)
|
|
cmp_type=INT_RESULT;
|
|
}
|
|
else
|
|
{
|
|
if (cvt_arg1 && cvt_arg2)
|
|
cmp_type=INT_RESULT;
|
|
}
|
|
|
|
if (args[0]->is_temporal() &&
|
|
args[1]->is_temporal() &&
|
|
args[2]->is_temporal())
|
|
{
|
|
/*
|
|
An expression:
|
|
time_or_datetime_field
|
|
BETWEEN const_number_or_time_or_datetime_expr1
|
|
AND const_number_or_time_or_datetime_expr2
|
|
was rewritten to:
|
|
time_field
|
|
BETWEEN Item_time_with_ref1
|
|
AND Item_time_with_ref2
|
|
or
|
|
datetime_field
|
|
BETWEEN Item_datetime_with_ref1
|
|
AND Item_datetime_with_ref2
|
|
*/
|
|
if (field_item->field_type() == MYSQL_TYPE_TIME)
|
|
compare_as_temporal_times= true;
|
|
else if (field_item->is_temporal_with_date())
|
|
compare_as_temporal_dates= true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
float
|
|
Item_func_between::get_filtering_effect(table_map filter_for_table,
|
|
table_map read_tables,
|
|
const MY_BITMAP *fields_to_ignore,
|
|
double rows_in_table)
|
|
{
|
|
const Item_field* fld=
|
|
contributes_to_filter(read_tables, filter_for_table, fields_to_ignore);
|
|
if (!fld)
|
|
return COND_FILTER_ALLPASS;
|
|
|
|
const float filter=
|
|
fld->get_cond_filter_default_probability(rows_in_table,
|
|
COND_FILTER_BETWEEN);
|
|
|
|
return negated ? 1.0f - filter : filter;
|
|
}
|
|
|
|
/**
|
|
A helper function for Item_func_between::val_int() to avoid
|
|
over/underflow when comparing large values.
|
|
|
|
@tparam LLorULL ulonglong or longlong
|
|
|
|
@param compare_as_temporal_dates copy of Item_func_between member variable
|
|
@param compare_as_temporal_times copy of Item_func_between member variable
|
|
@param negated copy of Item_func_between member variable
|
|
@param args copy of Item_func_between member variable
|
|
@param null_value [out] set to true if result is not true/false
|
|
|
|
@retval true if: args[1] <= args[0] <= args[2]
|
|
*/
|
|
template<typename LLorULL>
|
|
longlong compare_between_int_result(bool compare_as_temporal_dates,
|
|
bool compare_as_temporal_times,
|
|
bool negated,
|
|
Item **args,
|
|
my_bool *null_value)
|
|
{
|
|
{
|
|
LLorULL a, b, value;
|
|
value= compare_as_temporal_times ? args[0]->val_time_temporal() :
|
|
compare_as_temporal_dates ? args[0]->val_date_temporal() :
|
|
args[0]->val_int();
|
|
if ((*null_value= args[0]->null_value))
|
|
return 0; /* purecov: inspected */
|
|
if (compare_as_temporal_times)
|
|
{
|
|
a= args[1]->val_time_temporal();
|
|
b= args[2]->val_time_temporal();
|
|
}
|
|
else if (compare_as_temporal_dates)
|
|
{
|
|
a= args[1]->val_date_temporal();
|
|
b= args[2]->val_date_temporal();
|
|
}
|
|
else
|
|
{
|
|
a= args[1]->val_int();
|
|
b= args[2]->val_int();
|
|
}
|
|
|
|
if (args[0]->unsigned_flag)
|
|
{
|
|
/*
|
|
Comparing as unsigned.
|
|
value BETWEEN <some negative number> AND <some number>
|
|
rewritten to
|
|
value BETWEEN 0 AND <some number>
|
|
*/
|
|
if (!args[1]->unsigned_flag && static_cast<longlong>(a) < 0)
|
|
a = 0;
|
|
/*
|
|
Comparing as unsigned.
|
|
value BETWEEN <some number> AND <some negative number>
|
|
rewritten to
|
|
1 BETWEEN <some number> AND 0
|
|
*/
|
|
if (!args[2]->unsigned_flag && static_cast<longlong>(b) < 0)
|
|
{
|
|
b= 0;
|
|
value= 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Comparing as signed, but b is unsigned, and really large
|
|
if (args[2]->unsigned_flag && (longlong) b < 0)
|
|
b= LLONG_MAX;
|
|
}
|
|
|
|
if (!args[1]->null_value && !args[2]->null_value)
|
|
return (longlong) ((value >= a && value <= b) != negated);
|
|
if (args[1]->null_value && args[2]->null_value)
|
|
*null_value= 1;
|
|
else if (args[1]->null_value)
|
|
{
|
|
*null_value= value <= b; // not null if false range.
|
|
}
|
|
else
|
|
{
|
|
*null_value= value >= a;
|
|
}
|
|
return value;
|
|
}
|
|
}
|
|
|
|
|
|
longlong Item_func_between::val_int()
|
|
{ // ANSI BETWEEN
|
|
DBUG_ASSERT(fixed == 1);
|
|
if (compare_as_dates_with_strings)
|
|
{
|
|
int ge_res, le_res;
|
|
|
|
ge_res= ge_cmp.compare();
|
|
if ((null_value= args[0]->null_value))
|
|
return 0;
|
|
le_res= le_cmp.compare();
|
|
|
|
if (!args[1]->null_value && !args[2]->null_value)
|
|
return (longlong) ((ge_res >= 0 && le_res <=0) != negated);
|
|
else if (args[1]->null_value)
|
|
{
|
|
null_value= le_res > 0; // not null if false range.
|
|
}
|
|
else
|
|
{
|
|
null_value= ge_res < 0;
|
|
}
|
|
}
|
|
else if (cmp_type == STRING_RESULT)
|
|
{
|
|
String *value,*a,*b;
|
|
value=args[0]->val_str(&value0);
|
|
if ((null_value=args[0]->null_value))
|
|
return 0;
|
|
a=args[1]->val_str(&value1);
|
|
b=args[2]->val_str(&value2);
|
|
if (!args[1]->null_value && !args[2]->null_value)
|
|
return (longlong) ((sortcmp(value,a,cmp_collation.collation) >= 0 &&
|
|
sortcmp(value,b,cmp_collation.collation) <= 0) !=
|
|
negated);
|
|
if (args[1]->null_value && args[2]->null_value)
|
|
null_value=1;
|
|
else if (args[1]->null_value)
|
|
{
|
|
// Set to not null if false range.
|
|
null_value= sortcmp(value,b,cmp_collation.collation) <= 0;
|
|
}
|
|
else
|
|
{
|
|
// Set to not null if false range.
|
|
null_value= sortcmp(value,a,cmp_collation.collation) >= 0;
|
|
}
|
|
}
|
|
else if (cmp_type == INT_RESULT)
|
|
{
|
|
longlong value;
|
|
if (args[0]->unsigned_flag)
|
|
value= compare_between_int_result<ulonglong>(compare_as_temporal_dates,
|
|
compare_as_temporal_times,
|
|
negated,
|
|
args,
|
|
&null_value);
|
|
else
|
|
value= compare_between_int_result<longlong>(compare_as_temporal_dates,
|
|
compare_as_temporal_times,
|
|
negated,
|
|
args,
|
|
&null_value);
|
|
if (args[0]->null_value)
|
|
return 0; /* purecov: inspected */
|
|
if (!args[1]->null_value && !args[2]->null_value)
|
|
return value;
|
|
}
|
|
else if (cmp_type == DECIMAL_RESULT)
|
|
{
|
|
my_decimal dec_buf, *dec= args[0]->val_decimal(&dec_buf),
|
|
a_buf, *a_dec, b_buf, *b_dec;
|
|
if ((null_value=args[0]->null_value))
|
|
return 0; /* purecov: inspected */
|
|
a_dec= args[1]->val_decimal(&a_buf);
|
|
b_dec= args[2]->val_decimal(&b_buf);
|
|
if (!args[1]->null_value && !args[2]->null_value)
|
|
return (longlong) ((my_decimal_cmp(dec, a_dec) >= 0 &&
|
|
my_decimal_cmp(dec, b_dec) <= 0) != negated);
|
|
if (args[1]->null_value && args[2]->null_value)
|
|
null_value=1;
|
|
else if (args[1]->null_value)
|
|
null_value= (my_decimal_cmp(dec, b_dec) <= 0);
|
|
else
|
|
null_value= (my_decimal_cmp(dec, a_dec) >= 0);
|
|
}
|
|
else
|
|
{
|
|
double value= args[0]->val_real(),a,b;
|
|
if ((null_value=args[0]->null_value))
|
|
return 0; /* purecov: inspected */
|
|
a= args[1]->val_real();
|
|
b= args[2]->val_real();
|
|
if (!args[1]->null_value && !args[2]->null_value)
|
|
return (longlong) ((value >= a && value <= b) != negated);
|
|
if (args[1]->null_value && args[2]->null_value)
|
|
null_value=1;
|
|
else if (args[1]->null_value)
|
|
{
|
|
null_value= value <= b; // not null if false range.
|
|
}
|
|
else
|
|
{
|
|
null_value= value >= a;
|
|
}
|
|
}
|
|
return (longlong) (!null_value && negated);
|
|
}
|
|
|
|
|
|
void Item_func_between::print(String *str, enum_query_type query_type)
|
|
{
|
|
str->append('(');
|
|
args[0]->print(str, query_type);
|
|
if (negated)
|
|
str->append(STRING_WITH_LEN(" not"));
|
|
str->append(STRING_WITH_LEN(" between "));
|
|
args[1]->print(str, query_type);
|
|
str->append(STRING_WITH_LEN(" and "));
|
|
args[2]->print(str, query_type);
|
|
str->append(')');
|
|
}
|
|
|
|
void
|
|
Item_func_ifnull::fix_length_and_dec()
|
|
{
|
|
uint32 char_length;
|
|
agg_result_type(&hybrid_type, &unsigned_flag, args, 2);
|
|
cached_field_type= agg_field_type(args, 2);
|
|
maybe_null=args[1]->maybe_null;
|
|
decimals= max(args[0]->decimals, args[1]->decimals);
|
|
|
|
if (hybrid_type == DECIMAL_RESULT || hybrid_type == INT_RESULT)
|
|
{
|
|
int len0= args[0]->max_char_length() - args[0]->decimals
|
|
- (args[0]->unsigned_flag ? 0 : 1);
|
|
|
|
int len1= args[1]->max_char_length() - args[1]->decimals
|
|
- (args[1]->unsigned_flag ? 0 : 1);
|
|
|
|
char_length= max(len0, len1) + decimals + (unsigned_flag ? 0 : 1);
|
|
}
|
|
else
|
|
char_length= max(args[0]->max_char_length(), args[1]->max_char_length());
|
|
|
|
switch (hybrid_type) {
|
|
case STRING_RESULT:
|
|
if (count_string_result_length(cached_field_type, args, arg_count))
|
|
return;
|
|
break;
|
|
case DECIMAL_RESULT:
|
|
case REAL_RESULT:
|
|
break;
|
|
case INT_RESULT:
|
|
decimals= 0;
|
|
break;
|
|
case ROW_RESULT:
|
|
default:
|
|
DBUG_ASSERT(0);
|
|
}
|
|
fix_char_length(char_length);
|
|
}
|
|
|
|
|
|
uint Item_func_ifnull::decimal_precision() const
|
|
{
|
|
int arg0_int_part= args[0]->decimal_int_part();
|
|
int arg1_int_part= args[1]->decimal_int_part();
|
|
int max_int_part= max(arg0_int_part, arg1_int_part);
|
|
int precision= max_int_part + decimals;
|
|
return min<uint>(precision, DECIMAL_MAX_PRECISION);
|
|
}
|
|
|
|
|
|
Field *Item_func_ifnull::tmp_table_field(TABLE *table)
|
|
{
|
|
return tmp_table_field_from_field_type(table, 0);
|
|
}
|
|
|
|
double
|
|
Item_func_ifnull::real_op()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
double value= args[0]->val_real();
|
|
if (!args[0]->null_value)
|
|
{
|
|
null_value=0;
|
|
return value;
|
|
}
|
|
value= args[1]->val_real();
|
|
if ((null_value=args[1]->null_value))
|
|
return 0.0;
|
|
return value;
|
|
}
|
|
|
|
longlong
|
|
Item_func_ifnull::int_op()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
longlong value=args[0]->val_int();
|
|
if (!args[0]->null_value)
|
|
{
|
|
null_value=0;
|
|
return value;
|
|
}
|
|
value=args[1]->val_int();
|
|
if ((null_value=args[1]->null_value))
|
|
return 0;
|
|
return value;
|
|
}
|
|
|
|
|
|
my_decimal *Item_func_ifnull::decimal_op(my_decimal *decimal_value)
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
my_decimal *value= args[0]->val_decimal(decimal_value);
|
|
if (!args[0]->null_value)
|
|
{
|
|
null_value= 0;
|
|
return value;
|
|
}
|
|
value= args[1]->val_decimal(decimal_value);
|
|
if ((null_value= args[1]->null_value))
|
|
return 0;
|
|
return value;
|
|
}
|
|
|
|
|
|
bool Item_func_ifnull::val_json(Json_wrapper *result)
|
|
{
|
|
null_value= 0;
|
|
if (json_value(args, 0, result))
|
|
return error_json();
|
|
|
|
if (!args[0]->null_value)
|
|
return false;
|
|
|
|
if (json_value(args, 1, result))
|
|
return error_json();
|
|
|
|
null_value= args[1]->null_value;
|
|
return false;
|
|
}
|
|
|
|
|
|
bool Item_func_ifnull::date_op(MYSQL_TIME *ltime, my_time_flags_t fuzzydate)
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
if (!args[0]->get_date(ltime, fuzzydate))
|
|
return (null_value= false);
|
|
return (null_value= args[1]->get_date(ltime, fuzzydate));
|
|
}
|
|
|
|
|
|
bool Item_func_ifnull::time_op(MYSQL_TIME *ltime)
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
if (!args[0]->get_time(ltime))
|
|
return (null_value= false);
|
|
return (null_value= args[1]->get_time(ltime));
|
|
}
|
|
|
|
|
|
String *
|
|
Item_func_ifnull::str_op(String *str)
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
String *res =args[0]->val_str(str);
|
|
if (!args[0]->null_value)
|
|
{
|
|
null_value=0;
|
|
res->set_charset(collation.collation);
|
|
return res;
|
|
}
|
|
res=args[1]->val_str(str);
|
|
if ((null_value=args[1]->null_value))
|
|
return 0;
|
|
res->set_charset(collation.collation);
|
|
return res;
|
|
}
|
|
|
|
|
|
/**
|
|
Perform context analysis of an IF item tree.
|
|
|
|
This function performs context analysis (name resolution) and calculates
|
|
various attributes of the item tree with Item_func_if as its root.
|
|
The function saves in ref the pointer to the item or to a newly created
|
|
item that is considered as a replacement for the original one.
|
|
|
|
@param thd reference to the global context of the query thread
|
|
@param ref pointer to Item* variable where pointer to resulting "fixed"
|
|
item is to be assigned
|
|
|
|
@note
|
|
Let T0(e)/T1(e) be the value of not_null_tables(e) when e is used on
|
|
a predicate/function level. Then it's easy to show that:
|
|
@verbatim
|
|
T0(IF(e,e1,e2) = T1(IF(e,e1,e2))
|
|
T1(IF(e,e1,e2)) = intersection(T1(e1),T1(e2))
|
|
@endverbatim
|
|
|
|
@retval
|
|
0 ok
|
|
@retval
|
|
1 got error
|
|
*/
|
|
|
|
bool
|
|
Item_func_if::fix_fields(THD *thd, Item **ref)
|
|
{
|
|
DBUG_ASSERT(fixed == 0);
|
|
args[0]->top_level_item();
|
|
|
|
if (Item_func::fix_fields(thd, ref))
|
|
return 1;
|
|
|
|
not_null_tables_cache= (args[1]->not_null_tables() &
|
|
args[2]->not_null_tables());
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void Item_func_if::fix_after_pullout(st_select_lex *parent_select,
|
|
st_select_lex *removed_select)
|
|
{
|
|
Item_func::fix_after_pullout(parent_select, removed_select);
|
|
|
|
not_null_tables_cache= (args[1]->not_null_tables() &
|
|
args[2]->not_null_tables());
|
|
}
|
|
|
|
|
|
void Item_func_if::cache_type_info(Item *source)
|
|
{
|
|
collation.set(source->collation);
|
|
cached_field_type= source->field_type();
|
|
cached_result_type= source->result_type();
|
|
decimals= source->decimals;
|
|
max_length= source->max_length;
|
|
maybe_null= source->maybe_null;
|
|
unsigned_flag= source->unsigned_flag;
|
|
}
|
|
|
|
|
|
void
|
|
Item_func_if::fix_length_and_dec()
|
|
{
|
|
// Let IF(cond, expr, NULL) and IF(cond, NULL, expr) inherit type from expr.
|
|
if (args[1]->type() == NULL_ITEM)
|
|
{
|
|
cache_type_info(args[2]);
|
|
maybe_null= true;
|
|
// If both arguments are NULL, make resulting type BINARY(0).
|
|
if (args[2]->type() == NULL_ITEM)
|
|
cached_field_type= MYSQL_TYPE_STRING;
|
|
return;
|
|
}
|
|
if (args[2]->type() == NULL_ITEM)
|
|
{
|
|
cache_type_info(args[1]);
|
|
maybe_null= true;
|
|
return;
|
|
}
|
|
|
|
agg_result_type(&cached_result_type, &unsigned_flag, args + 1, 2);
|
|
cached_field_type= agg_field_type(args + 1, 2);
|
|
maybe_null= args[1]->maybe_null || args[2]->maybe_null;
|
|
decimals= max(args[1]->decimals, args[2]->decimals);
|
|
|
|
if (cached_result_type == STRING_RESULT)
|
|
{
|
|
if (count_string_result_length(cached_field_type, args + 1, 2))
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
collation.set_numeric(); // Number
|
|
}
|
|
|
|
uint32 char_length;
|
|
if ((cached_result_type == DECIMAL_RESULT )
|
|
|| (cached_result_type == INT_RESULT))
|
|
{
|
|
int len1= args[1]->max_length - args[1]->decimals
|
|
- (args[1]->unsigned_flag ? 0 : 1);
|
|
|
|
int len2= args[2]->max_length - args[2]->decimals
|
|
- (args[2]->unsigned_flag ? 0 : 1);
|
|
|
|
char_length= max(len1, len2) + decimals + (unsigned_flag ? 0 : 1);
|
|
}
|
|
else
|
|
char_length= max(args[1]->max_char_length(), args[2]->max_char_length());
|
|
fix_char_length(char_length);
|
|
}
|
|
|
|
|
|
uint Item_func_if::decimal_precision() const
|
|
{
|
|
int arg1_prec= args[1]->decimal_int_part();
|
|
int arg2_prec= args[2]->decimal_int_part();
|
|
int precision=max(arg1_prec,arg2_prec) + decimals;
|
|
return min<uint>(precision, DECIMAL_MAX_PRECISION);
|
|
}
|
|
|
|
|
|
double
|
|
Item_func_if::val_real()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
Item *arg= args[0]->val_bool() ? args[1] : args[2];
|
|
double value= arg->val_real();
|
|
null_value=arg->null_value;
|
|
return value;
|
|
}
|
|
|
|
longlong
|
|
Item_func_if::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
Item *arg= args[0]->val_bool() ? args[1] : args[2];
|
|
longlong value=arg->val_int();
|
|
null_value=arg->null_value;
|
|
return value;
|
|
}
|
|
|
|
String *
|
|
Item_func_if::val_str(String *str)
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
|
|
switch (field_type())
|
|
{
|
|
case MYSQL_TYPE_DATETIME:
|
|
case MYSQL_TYPE_TIMESTAMP:
|
|
return val_string_from_datetime(str);
|
|
case MYSQL_TYPE_DATE:
|
|
return val_string_from_date(str);
|
|
case MYSQL_TYPE_TIME:
|
|
return val_string_from_time(str);
|
|
default:
|
|
{
|
|
Item *item= args[0]->val_bool() ? args[1] : args[2];
|
|
String *res;
|
|
if ((res= item->val_str(str)))
|
|
{
|
|
res->set_charset(collation.collation);
|
|
null_value= 0;
|
|
return res;
|
|
}
|
|
}
|
|
}
|
|
null_value= true;
|
|
return (String *) 0;
|
|
}
|
|
|
|
|
|
my_decimal *
|
|
Item_func_if::val_decimal(my_decimal *decimal_value)
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
Item *arg= args[0]->val_bool() ? args[1] : args[2];
|
|
my_decimal *value= arg->val_decimal(decimal_value);
|
|
null_value= arg->null_value;
|
|
return value;
|
|
}
|
|
|
|
|
|
bool Item_func_if::val_json(Json_wrapper *wr)
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
Item *arg= args[0]->val_bool() ? args[1] : args[2];
|
|
Item *args[]= {arg};
|
|
bool ok= json_value(args, 0, wr);
|
|
null_value= arg->null_value;
|
|
return ok;
|
|
}
|
|
|
|
|
|
bool Item_func_if::get_date(MYSQL_TIME *ltime, my_time_flags_t fuzzydate)
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
Item *arg= args[0]->val_bool() ? args[1] : args[2];
|
|
return (null_value= arg->get_date(ltime, fuzzydate));
|
|
}
|
|
|
|
|
|
bool Item_func_if::get_time(MYSQL_TIME *ltime)
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
Item *arg= args[0]->val_bool() ? args[1] : args[2];
|
|
return (null_value= arg->get_time(ltime));
|
|
}
|
|
|
|
|
|
void
|
|
Item_func_nullif::fix_length_and_dec()
|
|
{
|
|
Item_bool_func2::fix_length_and_dec();
|
|
maybe_null=1;
|
|
if (args[0]) // Only false if EOM
|
|
{
|
|
max_length=args[0]->max_length;
|
|
decimals=args[0]->decimals;
|
|
unsigned_flag= args[0]->unsigned_flag;
|
|
cached_result_type= args[0]->result_type();
|
|
if (cached_result_type == STRING_RESULT &&
|
|
agg_arg_charsets_for_comparison(collation, args, arg_count))
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
@note
|
|
Note that we have to evaluate the first argument twice as the compare
|
|
may have been done with a different type than return value
|
|
@return
|
|
NULL if arguments are equal
|
|
@return
|
|
the first argument if not equal
|
|
*/
|
|
|
|
double
|
|
Item_func_nullif::val_real()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
double value;
|
|
if (!cmp.compare())
|
|
{
|
|
null_value=1;
|
|
return 0.0;
|
|
}
|
|
value= args[0]->val_real();
|
|
null_value=args[0]->null_value;
|
|
return value;
|
|
}
|
|
|
|
longlong
|
|
Item_func_nullif::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
longlong value;
|
|
if (!cmp.compare())
|
|
{
|
|
null_value=1;
|
|
return 0;
|
|
}
|
|
value=args[0]->val_int();
|
|
null_value=args[0]->null_value;
|
|
return value;
|
|
}
|
|
|
|
String *
|
|
Item_func_nullif::val_str(String *str)
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
String *res;
|
|
if (!cmp.compare())
|
|
{
|
|
null_value=1;
|
|
return 0;
|
|
}
|
|
res=args[0]->val_str(str);
|
|
null_value=args[0]->null_value;
|
|
return res;
|
|
}
|
|
|
|
|
|
my_decimal *
|
|
Item_func_nullif::val_decimal(my_decimal * decimal_value)
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
my_decimal *res;
|
|
if (!cmp.compare())
|
|
{
|
|
null_value=1;
|
|
return 0;
|
|
}
|
|
res= args[0]->val_decimal(decimal_value);
|
|
null_value= args[0]->null_value;
|
|
return res;
|
|
}
|
|
|
|
|
|
bool
|
|
Item_func_nullif::is_null()
|
|
{
|
|
return (null_value= (!cmp.compare() ? 1 : args[0]->null_value));
|
|
}
|
|
|
|
|
|
/**
|
|
Find and return matching items for CASE or ELSE item if all compares
|
|
are failed or NULL if ELSE item isn't defined.
|
|
|
|
IMPLEMENTATION
|
|
In order to do correct comparisons of the CASE expression (the expression
|
|
between CASE and the first WHEN) with each WHEN expression several
|
|
comparators are used. One for each result type. CASE expression can be
|
|
evaluated up to # of different result types are used. To check whether
|
|
the CASE expression already was evaluated for a particular result type
|
|
a bit mapped variable value_added_map is used. Result types are mapped
|
|
to it according to their int values i.e. STRING_RESULT is mapped to bit
|
|
0, REAL_RESULT to bit 1, so on.
|
|
|
|
@retval
|
|
NULL Nothing found and there is no ELSE expression defined
|
|
@retval
|
|
item Found item or ELSE item if defined and all comparisons are
|
|
failed
|
|
*/
|
|
|
|
Item *Item_func_case::find_item(String *str)
|
|
{
|
|
uint value_added_map= 0;
|
|
|
|
if (first_expr_num == -1)
|
|
{
|
|
for (uint i=0 ; i < ncases ; i+=2)
|
|
{
|
|
// No expression between CASE and the first WHEN
|
|
if (args[i]->val_bool())
|
|
return args[i+1];
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Compare every WHEN argument with it and return the first match */
|
|
for (uint i=0 ; i < ncases ; i+=2)
|
|
{
|
|
if (args[i]->real_item()->type() == NULL_ITEM)
|
|
continue;
|
|
cmp_type= item_cmp_type(left_result_type, args[i]->result_type());
|
|
DBUG_ASSERT(cmp_type != ROW_RESULT);
|
|
DBUG_ASSERT(cmp_items[(uint)cmp_type]);
|
|
if (!(value_added_map & (1U << (uint)cmp_type)))
|
|
{
|
|
cmp_items[(uint)cmp_type]->store_value(args[first_expr_num]);
|
|
if ((null_value=args[first_expr_num]->null_value))
|
|
return else_expr_num != -1 ? args[else_expr_num] : 0;
|
|
value_added_map|= 1U << (uint)cmp_type;
|
|
}
|
|
if (cmp_items[(uint)cmp_type]->cmp(args[i]) == FALSE)
|
|
return args[i + 1];
|
|
}
|
|
}
|
|
// No, WHEN clauses all missed, return ELSE expression
|
|
return else_expr_num != -1 ? args[else_expr_num] : 0;
|
|
}
|
|
|
|
|
|
String *Item_func_case::val_str(String *str)
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
switch (field_type()) {
|
|
case MYSQL_TYPE_DATETIME:
|
|
case MYSQL_TYPE_TIMESTAMP:
|
|
return val_string_from_datetime(str);
|
|
case MYSQL_TYPE_DATE:
|
|
return val_string_from_date(str);
|
|
case MYSQL_TYPE_TIME:
|
|
return val_string_from_time(str);
|
|
default:
|
|
{
|
|
Item *item= find_item(str);
|
|
if (item)
|
|
{
|
|
String *res;
|
|
if ((res= item->val_str(str)))
|
|
{
|
|
res->set_charset(collation.collation);
|
|
null_value= 0;
|
|
return res;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
null_value= true;
|
|
return (String *) 0;
|
|
}
|
|
|
|
|
|
longlong Item_func_case::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
char buff[MAX_FIELD_WIDTH];
|
|
String dummy_str(buff,sizeof(buff),default_charset());
|
|
Item *item=find_item(&dummy_str);
|
|
longlong res;
|
|
|
|
if (!item)
|
|
{
|
|
null_value=1;
|
|
return 0;
|
|
}
|
|
res=item->val_int();
|
|
null_value=item->null_value;
|
|
return res;
|
|
}
|
|
|
|
double Item_func_case::val_real()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
char buff[MAX_FIELD_WIDTH];
|
|
String dummy_str(buff,sizeof(buff),default_charset());
|
|
Item *item=find_item(&dummy_str);
|
|
double res;
|
|
|
|
if (!item)
|
|
{
|
|
null_value=1;
|
|
return 0;
|
|
}
|
|
res= item->val_real();
|
|
null_value=item->null_value;
|
|
return res;
|
|
}
|
|
|
|
|
|
my_decimal *Item_func_case::val_decimal(my_decimal *decimal_value)
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
char buff[MAX_FIELD_WIDTH];
|
|
String dummy_str(buff, sizeof(buff), default_charset());
|
|
Item *item= find_item(&dummy_str);
|
|
my_decimal *res;
|
|
|
|
if (!item)
|
|
{
|
|
null_value=1;
|
|
return 0;
|
|
}
|
|
|
|
res= item->val_decimal(decimal_value);
|
|
null_value= item->null_value;
|
|
return res;
|
|
}
|
|
|
|
|
|
bool Item_func_case::val_json(Json_wrapper *wr)
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
char buff[MAX_FIELD_WIDTH];
|
|
String dummy_str(buff, sizeof(buff), default_charset());
|
|
Item *item= find_item(&dummy_str);
|
|
|
|
if (!item)
|
|
{
|
|
null_value= true;
|
|
return false;
|
|
}
|
|
|
|
Item *args[]= {item};
|
|
return json_value(args, 0, wr);
|
|
}
|
|
|
|
|
|
bool Item_func_case::get_date(MYSQL_TIME *ltime, my_time_flags_t fuzzydate)
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
char buff[MAX_FIELD_WIDTH];
|
|
String dummy_str(buff, sizeof(buff), default_charset());
|
|
Item *item= find_item(&dummy_str);
|
|
if (!item)
|
|
return (null_value= true);
|
|
return (null_value= item->get_date(ltime, fuzzydate));
|
|
}
|
|
|
|
|
|
bool Item_func_case::get_time(MYSQL_TIME *ltime)
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
char buff[MAX_FIELD_WIDTH];
|
|
String dummy_str(buff, sizeof(buff), default_charset());
|
|
Item *item= find_item(&dummy_str);
|
|
if (!item)
|
|
return (null_value= true);
|
|
return (null_value= item->get_time(ltime));
|
|
}
|
|
|
|
|
|
bool Item_func_case::fix_fields(THD *thd, Item **ref)
|
|
{
|
|
/*
|
|
buff should match stack usage from
|
|
Item_func_case::val_int() -> Item_func_case::find_item()
|
|
*/
|
|
uchar buff[MAX_FIELD_WIDTH*2+sizeof(String)*2+sizeof(String*)*2+sizeof(double)*2+sizeof(longlong)*2];
|
|
bool res= Item_func::fix_fields(thd, ref);
|
|
/*
|
|
Call check_stack_overrun after fix_fields to be sure that stack variable
|
|
is not optimized away
|
|
*/
|
|
if (check_stack_overrun(thd, STACK_MIN_SIZE, buff))
|
|
return TRUE; // Fatal error flag is set!
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
Check if (*place) and new_value points to different Items and call
|
|
THD::change_item_tree() if needed.
|
|
|
|
This function is a workaround for implementation deficiency in
|
|
Item_func_case. The problem there is that the 'args' attribute contains
|
|
Items from different expressions.
|
|
|
|
The function must not be used elsewhere and will be remove eventually.
|
|
*/
|
|
|
|
static void change_item_tree_if_needed(THD *thd,
|
|
Item **place,
|
|
Item *new_value)
|
|
{
|
|
if (*place == new_value)
|
|
return;
|
|
|
|
thd->change_item_tree(place, new_value);
|
|
}
|
|
|
|
/**
|
|
This function is a shared part to fix_length_and_dec() for numeric result
|
|
type of CASE and COALESCE.
|
|
COALESCE is a CASE abbreviation according to the standard.
|
|
*/
|
|
static void fix_num_length_and_dec_shared_for_case(Item_func *item_func,
|
|
Item_result result_type,
|
|
Item **item,
|
|
uint nitems)
|
|
{
|
|
switch (result_type)
|
|
{
|
|
case DECIMAL_RESULT:
|
|
item_func->count_decimal_length(item, nitems);
|
|
break;
|
|
case REAL_RESULT:
|
|
item_func->count_real_length(item, nitems);
|
|
break;
|
|
case INT_RESULT:
|
|
item_func->count_only_length(item, nitems);
|
|
item_func->decimals= 0;
|
|
break;
|
|
case ROW_RESULT:
|
|
default:
|
|
DBUG_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
void Item_func_case::fix_length_and_dec()
|
|
{
|
|
Item **agg;
|
|
uint nagg;
|
|
uint found_types= 0;
|
|
THD *thd= current_thd;
|
|
|
|
if (!(agg= (Item**) sql_alloc(sizeof(Item*)*(ncases+1))))
|
|
return;
|
|
|
|
// Determine nullability based on THEN and ELSE expressions:
|
|
|
|
maybe_null= else_expr_num == -1 || args[else_expr_num]->maybe_null;
|
|
|
|
for (Item **arg= args + 1; arg < args + arg_count; arg+= 2)
|
|
maybe_null|= (*arg)->maybe_null;
|
|
|
|
/*
|
|
Aggregate all THEN and ELSE expression types
|
|
and collations when string result
|
|
*/
|
|
|
|
for (nagg= 0; nagg < ncases / 2; nagg++)
|
|
agg[nagg]= args[nagg * 2 + 1];
|
|
|
|
if (else_expr_num != -1)
|
|
agg[nagg++]= args[else_expr_num];
|
|
|
|
cached_field_type= agg_field_type(agg, nagg);
|
|
agg_result_type(&cached_result_type, &unsigned_flag, agg, nagg);
|
|
if (cached_result_type == STRING_RESULT)
|
|
{
|
|
/* Note: String result type is the same for CASE and COALESCE. */
|
|
if (count_string_result_length(cached_field_type, agg, nagg))
|
|
return;
|
|
/*
|
|
Copy all THEN and ELSE items back to args[] array.
|
|
Some of the items might have been changed to Item_func_conv_charset.
|
|
*/
|
|
for (nagg= 0 ; nagg < ncases / 2 ; nagg++)
|
|
change_item_tree_if_needed(thd, &args[nagg * 2 + 1], agg[nagg]);
|
|
|
|
if (else_expr_num != -1)
|
|
change_item_tree_if_needed(thd, &args[else_expr_num], agg[nagg++]);
|
|
}
|
|
else
|
|
{
|
|
collation.set_numeric();
|
|
fix_num_length_and_dec_shared_for_case(this, cached_result_type, agg,
|
|
nagg);
|
|
}
|
|
|
|
|
|
/*
|
|
Aggregate first expression and all WHEN expression types
|
|
and collations when string comparison
|
|
*/
|
|
if (first_expr_num != -1)
|
|
{
|
|
uint i;
|
|
agg[0]= args[first_expr_num];
|
|
left_result_type= agg[0]->result_type();
|
|
|
|
/*
|
|
As the first expression and WHEN expressions
|
|
are intermixed in args[] array THEN and ELSE items,
|
|
extract the first expression and all WHEN expressions into
|
|
a temporary array, to process them easier.
|
|
*/
|
|
for (nagg= 0; nagg < ncases/2 ; nagg++)
|
|
agg[nagg+1]= args[nagg*2];
|
|
nagg++;
|
|
if (!(found_types= collect_cmp_types(agg, nagg)))
|
|
return;
|
|
if (found_types & (1U << STRING_RESULT))
|
|
{
|
|
/*
|
|
If we'll do string comparison, we also need to aggregate
|
|
character set and collation for first/WHEN items and
|
|
install converters for some of them to cmp_collation when necessary.
|
|
This is done because cmp_item compatators cannot compare
|
|
strings in two different character sets.
|
|
Some examples when we install converters:
|
|
|
|
1. Converter installed for the first expression:
|
|
|
|
CASE latin1_item WHEN utf16_item THEN ... END
|
|
|
|
is replaced to:
|
|
|
|
CASE CONVERT(latin1_item USING utf16) WHEN utf16_item THEN ... END
|
|
|
|
2. Converter installed for the left WHEN item:
|
|
|
|
CASE utf16_item WHEN latin1_item THEN ... END
|
|
|
|
is replaced to:
|
|
|
|
CASE utf16_item WHEN CONVERT(latin1_item USING utf16) THEN ... END
|
|
*/
|
|
if (agg_arg_charsets_for_comparison(cmp_collation, agg, nagg))
|
|
return;
|
|
/*
|
|
Now copy first expression and all WHEN expressions back to args[]
|
|
arrray, because some of the items might have been changed to converters
|
|
(e.g. Item_func_conv_charset, or Item_string for constants).
|
|
*/
|
|
change_item_tree_if_needed(thd, &args[first_expr_num], agg[0]);
|
|
|
|
for (nagg= 0; nagg < ncases / 2; nagg++)
|
|
change_item_tree_if_needed(thd, &args[nagg * 2], agg[nagg + 1]);
|
|
}
|
|
for (i= 0; i <= (uint)DECIMAL_RESULT; i++)
|
|
{
|
|
if (found_types & (1U << i) && !cmp_items[i])
|
|
{
|
|
DBUG_ASSERT((Item_result)i != ROW_RESULT);
|
|
if (!(cmp_items[i]=
|
|
cmp_item::get_comparator((Item_result)i, args[first_expr_num],
|
|
cmp_collation.collation)))
|
|
return;
|
|
}
|
|
}
|
|
/*
|
|
Set cmp_context of all WHEN arguments. This prevents
|
|
Item_field::equal_fields_propagator() from transforming a
|
|
zerofill argument into a string constant. Such a change would
|
|
require rebuilding cmp_items.
|
|
*/
|
|
for (i= 0; i < ncases; i+= 2)
|
|
args[i]->cmp_context= item_cmp_type(left_result_type,
|
|
args[i]->result_type());
|
|
}
|
|
|
|
}
|
|
|
|
|
|
uint Item_func_case::decimal_precision() const
|
|
{
|
|
int max_int_part=0;
|
|
for (uint i=0 ; i < ncases ; i+=2)
|
|
set_if_bigger(max_int_part, args[i+1]->decimal_int_part());
|
|
|
|
if (else_expr_num != -1)
|
|
set_if_bigger(max_int_part, args[else_expr_num]->decimal_int_part());
|
|
return min<uint>(max_int_part + decimals, DECIMAL_MAX_PRECISION);
|
|
}
|
|
|
|
|
|
/**
|
|
@todo
|
|
Fix this so that it prints the whole CASE expression
|
|
*/
|
|
|
|
void Item_func_case::print(String *str, enum_query_type query_type)
|
|
{
|
|
str->append(STRING_WITH_LEN("(case "));
|
|
if (first_expr_num != -1)
|
|
{
|
|
args[first_expr_num]->print(str, query_type);
|
|
str->append(' ');
|
|
}
|
|
for (uint i=0 ; i < ncases ; i+=2)
|
|
{
|
|
str->append(STRING_WITH_LEN("when "));
|
|
args[i]->print(str, query_type);
|
|
str->append(STRING_WITH_LEN(" then "));
|
|
args[i+1]->print(str, query_type);
|
|
str->append(' ');
|
|
}
|
|
if (else_expr_num != -1)
|
|
{
|
|
str->append(STRING_WITH_LEN("else "));
|
|
args[else_expr_num]->print(str, query_type);
|
|
str->append(' ');
|
|
}
|
|
str->append(STRING_WITH_LEN("end)"));
|
|
}
|
|
|
|
|
|
void Item_func_case::cleanup()
|
|
{
|
|
uint i;
|
|
DBUG_ENTER("Item_func_case::cleanup");
|
|
Item_func::cleanup();
|
|
for (i= 0; i <= (uint)DECIMAL_RESULT; i++)
|
|
{
|
|
delete cmp_items[i];
|
|
cmp_items[i]= 0;
|
|
}
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
/**
|
|
Coalesce - return first not NULL argument.
|
|
*/
|
|
|
|
Item_func_coalesce::Item_func_coalesce(const POS &pos, PT_item_list *list)
|
|
: Item_func_numhybrid(pos, list)
|
|
{}
|
|
|
|
String *Item_func_coalesce::str_op(String *str)
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
null_value=0;
|
|
for (uint i=0 ; i < arg_count ; i++)
|
|
{
|
|
String *res;
|
|
if ((res=args[i]->val_str(str)))
|
|
return res;
|
|
}
|
|
null_value=1;
|
|
return 0;
|
|
}
|
|
|
|
|
|
bool Item_func_coalesce::val_json(Json_wrapper *wr)
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
null_value= false;
|
|
for (uint i= 0; i < arg_count; i++)
|
|
{
|
|
if (json_value(args, i, wr))
|
|
return error_json();
|
|
|
|
if (!args[i]->null_value)
|
|
return false;
|
|
}
|
|
|
|
null_value= true;
|
|
return false;
|
|
}
|
|
|
|
|
|
longlong Item_func_coalesce::int_op()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
null_value=0;
|
|
for (uint i=0 ; i < arg_count ; i++)
|
|
{
|
|
longlong res=args[i]->val_int();
|
|
if (!args[i]->null_value)
|
|
return res;
|
|
}
|
|
null_value=1;
|
|
return 0;
|
|
}
|
|
|
|
double Item_func_coalesce::real_op()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
null_value=0;
|
|
for (uint i=0 ; i < arg_count ; i++)
|
|
{
|
|
double res= args[i]->val_real();
|
|
if (!args[i]->null_value)
|
|
return res;
|
|
}
|
|
null_value=1;
|
|
return 0;
|
|
}
|
|
|
|
|
|
my_decimal *Item_func_coalesce::decimal_op(my_decimal *decimal_value)
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
null_value= 0;
|
|
for (uint i= 0; i < arg_count; i++)
|
|
{
|
|
my_decimal *res= args[i]->val_decimal(decimal_value);
|
|
if (!args[i]->null_value)
|
|
return res;
|
|
}
|
|
null_value=1;
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
bool Item_func_coalesce::date_op(MYSQL_TIME *ltime, my_time_flags_t fuzzydate)
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
for (uint i= 0; i < arg_count; i++)
|
|
{
|
|
if (!args[i]->get_date(ltime, fuzzydate))
|
|
return (null_value= false);
|
|
}
|
|
return (null_value= true);
|
|
}
|
|
|
|
|
|
bool Item_func_coalesce::time_op(MYSQL_TIME *ltime)
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
for (uint i= 0; i < arg_count; i++)
|
|
{
|
|
if (!args[i]->get_time(ltime))
|
|
return (null_value= false);
|
|
}
|
|
return (null_value= true);
|
|
}
|
|
|
|
|
|
void Item_func_coalesce::fix_length_and_dec()
|
|
{
|
|
cached_field_type= agg_field_type(args, arg_count);
|
|
agg_result_type(&hybrid_type, &unsigned_flag, args, arg_count);
|
|
if (hybrid_type == STRING_RESULT)
|
|
count_string_result_length(cached_field_type, args, arg_count);
|
|
else
|
|
fix_num_length_and_dec_shared_for_case(this, hybrid_type, args,
|
|
arg_count);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Classes and function for the IN operator
|
|
****************************************************************************/
|
|
|
|
/*
|
|
Determine which of the signed longlong arguments is bigger
|
|
|
|
SYNOPSIS
|
|
cmp_longs()
|
|
a_val left argument
|
|
b_val right argument
|
|
|
|
DESCRIPTION
|
|
This function will compare two signed longlong arguments
|
|
and will return -1, 0, or 1 if left argument is smaller than,
|
|
equal to or greater than the right argument.
|
|
|
|
RETURN VALUE
|
|
-1 left argument is smaller than the right argument.
|
|
0 left argument is equal to the right argument.
|
|
1 left argument is greater than the right argument.
|
|
*/
|
|
static inline int cmp_longs (longlong a_val, longlong b_val)
|
|
{
|
|
return a_val < b_val ? -1 : a_val == b_val ? 0 : 1;
|
|
}
|
|
|
|
|
|
/*
|
|
Determine which of the unsigned longlong arguments is bigger
|
|
|
|
SYNOPSIS
|
|
cmp_ulongs()
|
|
a_val left argument
|
|
b_val right argument
|
|
|
|
DESCRIPTION
|
|
This function will compare two unsigned longlong arguments
|
|
and will return -1, 0, or 1 if left argument is smaller than,
|
|
equal to or greater than the right argument.
|
|
|
|
RETURN VALUE
|
|
-1 left argument is smaller than the right argument.
|
|
0 left argument is equal to the right argument.
|
|
1 left argument is greater than the right argument.
|
|
*/
|
|
static inline int cmp_ulongs (ulonglong a_val, ulonglong b_val)
|
|
{
|
|
return a_val < b_val ? -1 : a_val == b_val ? 0 : 1;
|
|
}
|
|
|
|
|
|
/*
|
|
Compare two integers in IN value list format (packed_longlong)
|
|
|
|
SYNOPSIS
|
|
cmp_longlong()
|
|
a left argument
|
|
b right argument
|
|
|
|
DESCRIPTION
|
|
This function will compare two integer arguments in the IN value list
|
|
format and will return -1, 0, or 1 if left argument is smaller than,
|
|
equal to or greater than the right argument.
|
|
It's used in sorting the IN values list and finding an element in it.
|
|
Depending on the signedness of the arguments cmp_longlong() will
|
|
compare them as either signed (using cmp_longs()) or unsigned (using
|
|
cmp_ulongs()).
|
|
|
|
RETURN VALUE
|
|
-1 left argument is smaller than the right argument.
|
|
0 left argument is equal to the right argument.
|
|
1 left argument is greater than the right argument.
|
|
*/
|
|
int cmp_longlong(const in_longlong::packed_longlong *a,
|
|
const in_longlong::packed_longlong *b)
|
|
{
|
|
if (a->unsigned_flag != b->unsigned_flag)
|
|
{
|
|
/*
|
|
One of the args is unsigned and is too big to fit into the
|
|
positive signed range. Report no match.
|
|
*/
|
|
if ((a->unsigned_flag && ((ulonglong) a->val) > (ulonglong) LLONG_MAX) ||
|
|
(b->unsigned_flag && ((ulonglong) b->val) > (ulonglong) LLONG_MAX))
|
|
return a->unsigned_flag ? 1 : -1;
|
|
/*
|
|
Although the signedness differs both args can fit into the signed
|
|
positive range. Make them signed and compare as usual.
|
|
*/
|
|
return cmp_longs (a->val, b->val);
|
|
}
|
|
if (a->unsigned_flag)
|
|
return cmp_ulongs ((ulonglong) a->val, (ulonglong) b->val);
|
|
else
|
|
return cmp_longs (a->val, b->val);
|
|
}
|
|
|
|
|
|
class Cmp_longlong :
|
|
public std::binary_function<const in_longlong::packed_longlong &,
|
|
const in_longlong::packed_longlong &, bool>
|
|
{
|
|
public:
|
|
bool operator()(const in_longlong::packed_longlong &a,
|
|
const in_longlong::packed_longlong &b)
|
|
{
|
|
return cmp_longlong(&a, &b) < 0;
|
|
}
|
|
};
|
|
|
|
|
|
void in_longlong::sort()
|
|
{
|
|
std::sort(base.begin(), base.end(), Cmp_longlong());
|
|
}
|
|
|
|
|
|
bool in_longlong::find_value(const void *value) const
|
|
{
|
|
const in_longlong::packed_longlong *val=
|
|
static_cast<const in_longlong::packed_longlong*>(value);
|
|
|
|
return std::binary_search(base.begin(), base.end(), *val, Cmp_longlong());
|
|
}
|
|
|
|
|
|
bool in_longlong::compare_elems(uint pos1, uint pos2) const
|
|
{
|
|
return cmp_longlong(&base[pos1], &base[pos2]) != 0;
|
|
}
|
|
|
|
|
|
class Cmp_row :
|
|
public std::binary_function<const cmp_item_row *, const cmp_item_row *, bool>
|
|
{
|
|
public:
|
|
bool operator()(const cmp_item_row *a, const cmp_item_row *b)
|
|
{
|
|
return a->compare(b) < 0;
|
|
}
|
|
};
|
|
|
|
|
|
void in_row::sort()
|
|
{
|
|
std::sort(base_pointers.begin(), base_pointers.end(), Cmp_row());
|
|
}
|
|
|
|
|
|
bool in_row::find_value(const void *value) const
|
|
{
|
|
const cmp_item_row *row= static_cast<const cmp_item_row*>(value);
|
|
return std::binary_search(base_pointers.begin(), base_pointers.end(),
|
|
row, Cmp_row());
|
|
}
|
|
|
|
|
|
bool in_row::compare_elems(uint pos1, uint pos2) const
|
|
{
|
|
return base_pointers[pos1]->compare(base_pointers[pos2]) != 0;
|
|
}
|
|
|
|
|
|
bool in_vector::find_item(Item *item)
|
|
{
|
|
uchar *result=get_value(item);
|
|
if (!result || !used_count)
|
|
return false; // Null value
|
|
return find_value(result);
|
|
}
|
|
|
|
|
|
in_string::in_string(THD *thd, uint elements, qsort2_cmp cmp_func,
|
|
const CHARSET_INFO *cs)
|
|
: in_vector(elements),
|
|
tmp(buff, sizeof(buff), &my_charset_bin),
|
|
base_objects(thd->mem_root, elements),
|
|
base_pointers(thd->mem_root, elements),
|
|
compare(cmp_func),
|
|
collation(cs)
|
|
{
|
|
for (uint ix= 0; ix < elements; ++ix)
|
|
{
|
|
base_pointers[ix]= &base_objects[ix];
|
|
}
|
|
}
|
|
|
|
in_string::~in_string()
|
|
{
|
|
for (uint i= 0; i < base_objects.size(); i++)
|
|
{
|
|
base_objects[i].mem_free();
|
|
}
|
|
}
|
|
|
|
void in_string::set(uint pos, Item *item)
|
|
{
|
|
String *str= base_pointers[pos];
|
|
String *res= item->val_str(str);
|
|
if (res && res != str)
|
|
{
|
|
if (res->uses_buffer_owned_by(str))
|
|
res->copy();
|
|
if (item->type() == Item::FUNC_ITEM)
|
|
str->copy(*res);
|
|
else
|
|
*str= *res;
|
|
}
|
|
if (!str->charset())
|
|
{
|
|
const CHARSET_INFO *cs;
|
|
if (!(cs= item->collation.collation))
|
|
cs= &my_charset_bin; // Should never happen for STR items
|
|
str->set_charset(cs);
|
|
}
|
|
}
|
|
|
|
|
|
uchar *in_string::get_value(Item *item)
|
|
{
|
|
return (uchar*) item->val_str(&tmp);
|
|
}
|
|
|
|
|
|
class Cmp_string :
|
|
public std::binary_function<const String *, const String *, bool>
|
|
{
|
|
public:
|
|
Cmp_string(qsort2_cmp cmp_func, const CHARSET_INFO *cs)
|
|
: compare(cmp_func), collation(cs)
|
|
{}
|
|
bool operator()(const String *a, const String *b)
|
|
{
|
|
return compare(collation, a, b) < 0;
|
|
}
|
|
private:
|
|
qsort2_cmp compare;
|
|
const CHARSET_INFO *collation;
|
|
};
|
|
|
|
|
|
// Our String objects have strange copy semantics, sort pointers instead.
|
|
void in_string::sort()
|
|
{
|
|
std::sort(base_pointers.begin(), base_pointers.end(),
|
|
Cmp_string(compare, collation));
|
|
}
|
|
|
|
|
|
bool in_string::find_value(const void *value) const
|
|
{
|
|
const String *str= static_cast<const String*>(value);
|
|
return std::binary_search(base_pointers.begin(), base_pointers.end(),
|
|
str, Cmp_string(compare, collation));
|
|
}
|
|
|
|
|
|
bool in_string::compare_elems(uint pos1, uint pos2) const
|
|
{
|
|
return compare(collation, base_pointers[pos1], base_pointers[pos2]) != 0;
|
|
}
|
|
|
|
|
|
in_row::in_row(THD *thd, uint elements, Item * item)
|
|
: in_vector(elements),
|
|
base_objects(thd->mem_root, elements),
|
|
base_pointers(thd->mem_root, elements)
|
|
{
|
|
for (uint ix= 0; ix < elements; ++ix)
|
|
{
|
|
base_pointers[ix]= &base_objects[ix];
|
|
}
|
|
}
|
|
|
|
in_row::~in_row()
|
|
{
|
|
delete_container_pointers(base_pointers);
|
|
}
|
|
|
|
uchar *in_row::get_value(Item *item)
|
|
{
|
|
tmp.store_value(item);
|
|
if (item->is_null())
|
|
return 0;
|
|
return (uchar *)&tmp;
|
|
}
|
|
|
|
void in_row::set(uint pos, Item *item)
|
|
{
|
|
DBUG_ENTER("in_row::set");
|
|
DBUG_PRINT("enter", ("pos: %u item: 0x%lx", pos, (ulong) item));
|
|
base_pointers[pos]->store_value_by_template(&tmp, item);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
in_longlong::in_longlong(THD *thd, uint elements)
|
|
: in_vector(elements),
|
|
base(thd->mem_root, elements)
|
|
{
|
|
}
|
|
|
|
void in_longlong::set(uint pos,Item *item)
|
|
{
|
|
struct packed_longlong *buff= &base[pos];
|
|
|
|
buff->val= item->val_int();
|
|
buff->unsigned_flag= item->unsigned_flag;
|
|
}
|
|
|
|
uchar *in_longlong::get_value(Item *item)
|
|
{
|
|
tmp.val= item->val_int();
|
|
if (item->null_value)
|
|
return 0;
|
|
tmp.unsigned_flag= item->unsigned_flag;
|
|
return (uchar*) &tmp;
|
|
}
|
|
|
|
|
|
void in_time_as_longlong::set(uint pos,Item *item)
|
|
{
|
|
struct packed_longlong *buff= &base[pos];
|
|
buff->val= item->val_time_temporal();
|
|
buff->unsigned_flag= item->unsigned_flag;
|
|
}
|
|
|
|
|
|
uchar *in_time_as_longlong::get_value(Item *item)
|
|
{
|
|
tmp.val= item->val_time_temporal();
|
|
if (item->null_value)
|
|
return 0;
|
|
tmp.unsigned_flag= item->unsigned_flag;
|
|
return (uchar*) &tmp;
|
|
}
|
|
|
|
|
|
void in_datetime_as_longlong::set(uint pos,Item *item)
|
|
{
|
|
struct packed_longlong *buff= &base[pos];
|
|
buff->val= item->val_date_temporal();
|
|
buff->unsigned_flag= item->unsigned_flag;
|
|
}
|
|
|
|
|
|
uchar *in_datetime_as_longlong::get_value(Item *item)
|
|
{
|
|
tmp.val= item->val_date_temporal();
|
|
if (item->null_value)
|
|
return 0;
|
|
tmp.unsigned_flag= item->unsigned_flag;
|
|
return (uchar*) &tmp;
|
|
}
|
|
|
|
|
|
void in_datetime::set(uint pos,Item *item)
|
|
{
|
|
Item **tmp_item= &item;
|
|
bool is_null;
|
|
struct packed_longlong *buff= &base[pos];
|
|
|
|
buff->val= get_datetime_value(current_thd, &tmp_item, 0, warn_item, &is_null);
|
|
buff->unsigned_flag= 1L;
|
|
}
|
|
|
|
|
|
uchar *in_datetime::get_value(Item *item)
|
|
{
|
|
bool is_null;
|
|
Item **tmp_item= lval_cache ? &lval_cache : &item;
|
|
tmp.val= get_datetime_value(current_thd, &tmp_item, &lval_cache, warn_item, &is_null);
|
|
if (item->null_value)
|
|
return 0;
|
|
tmp.unsigned_flag= 1L;
|
|
return (uchar*) &tmp;
|
|
}
|
|
|
|
|
|
in_double::in_double(THD *thd, uint elements)
|
|
: in_vector(elements),
|
|
base(thd->mem_root, elements)
|
|
{
|
|
}
|
|
|
|
void in_double::set(uint pos,Item *item)
|
|
{
|
|
base[pos]= item->val_real();
|
|
}
|
|
|
|
uchar *in_double::get_value(Item *item)
|
|
{
|
|
tmp= item->val_real();
|
|
if (item->null_value)
|
|
return 0; /* purecov: inspected */
|
|
return (uchar*) &tmp;
|
|
}
|
|
|
|
|
|
void in_double::sort()
|
|
{
|
|
std::sort(base.begin(), base.end());
|
|
}
|
|
|
|
|
|
bool in_double::find_value(const void *value) const
|
|
{
|
|
const double *dbl= static_cast<const double*>(value);
|
|
return std::binary_search(base.begin(), base.end(), *dbl);
|
|
}
|
|
|
|
|
|
bool in_double::compare_elems(uint pos1, uint pos2) const
|
|
{
|
|
return base[pos1] != base[pos2];
|
|
}
|
|
|
|
|
|
in_decimal::in_decimal(THD *thd, uint elements)
|
|
: in_vector(elements),
|
|
base(thd->mem_root, elements)
|
|
{
|
|
}
|
|
|
|
|
|
void in_decimal::set(uint pos, Item *item)
|
|
{
|
|
/* as far as 'item' is constant, we can store reference on my_decimal */
|
|
my_decimal *dec= &base[pos];
|
|
my_decimal *res= item->val_decimal(dec);
|
|
/* if item->val_decimal() is evaluated to NULL then res == 0 */
|
|
if (!item->null_value && res != dec)
|
|
my_decimal2decimal(res, dec);
|
|
}
|
|
|
|
|
|
uchar *in_decimal::get_value(Item *item)
|
|
{
|
|
my_decimal *result= item->val_decimal(&val);
|
|
if (item->null_value)
|
|
return 0;
|
|
return (uchar *)result;
|
|
}
|
|
|
|
|
|
void in_decimal::sort()
|
|
{
|
|
std::sort(base.begin(), base.end());
|
|
}
|
|
|
|
|
|
bool in_decimal::find_value(const void *value) const
|
|
{
|
|
const my_decimal *dec= static_cast<const my_decimal*>(value);
|
|
return std::binary_search(base.begin(), base.end(), *dec);
|
|
}
|
|
|
|
|
|
bool in_decimal::compare_elems(uint pos1, uint pos2) const
|
|
{
|
|
return base[pos1] != base[pos2];
|
|
}
|
|
|
|
|
|
cmp_item* cmp_item::get_comparator(Item_result result_type, const Item *item,
|
|
const CHARSET_INFO *cs)
|
|
{
|
|
switch (result_type) {
|
|
case STRING_RESULT:
|
|
/*
|
|
Temporal types shouldn't be compared as strings. Since date/time formats
|
|
may be different, e.g. '20000102' == '2000-01-02'."
|
|
*/
|
|
if (item->is_temporal())
|
|
return new cmp_item_datetime(item);
|
|
else
|
|
return new cmp_item_string(cs);
|
|
case INT_RESULT:
|
|
return new cmp_item_int;
|
|
case REAL_RESULT:
|
|
return new cmp_item_real;
|
|
case ROW_RESULT:
|
|
return new cmp_item_row;
|
|
case DECIMAL_RESULT:
|
|
return new cmp_item_decimal;
|
|
default:
|
|
DBUG_ASSERT(0);
|
|
break;
|
|
}
|
|
return 0; // to satisfy compiler :)
|
|
}
|
|
|
|
|
|
cmp_item* cmp_item_string::make_same()
|
|
{
|
|
return new cmp_item_string(cmp_charset);
|
|
}
|
|
|
|
cmp_item* cmp_item_int::make_same()
|
|
{
|
|
return new cmp_item_int();
|
|
}
|
|
|
|
cmp_item* cmp_item_real::make_same()
|
|
{
|
|
return new cmp_item_real();
|
|
}
|
|
|
|
cmp_item* cmp_item_row::make_same()
|
|
{
|
|
return new cmp_item_row();
|
|
}
|
|
|
|
|
|
cmp_item_row::~cmp_item_row()
|
|
{
|
|
DBUG_ENTER("~cmp_item_row");
|
|
DBUG_PRINT("enter",("this: 0x%lx", (long) this));
|
|
if (comparators)
|
|
{
|
|
for (uint i= 0; i < n; i++)
|
|
{
|
|
if (comparators[i])
|
|
delete comparators[i];
|
|
}
|
|
}
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
void cmp_item_row::alloc_comparators(Item *item)
|
|
{
|
|
n= item->cols();
|
|
DBUG_ASSERT(comparators == NULL);
|
|
if (!comparators)
|
|
comparators= (cmp_item **) current_thd->mem_calloc(sizeof(cmp_item *)*n);
|
|
if (comparators)
|
|
{
|
|
for (uint i= 0; i < n; i++)
|
|
{
|
|
DBUG_ASSERT(comparators[i] == NULL);
|
|
Item *item_i= item->element_index(i);
|
|
if (!(comparators[i]=
|
|
cmp_item::get_comparator(item_i->result_type(), item_i,
|
|
item_i->collation.collation)))
|
|
break; // new failed
|
|
if (item_i->result_type() == ROW_RESULT)
|
|
static_cast<cmp_item_row*>(comparators[i])->alloc_comparators(item_i);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void cmp_item_row::store_value(Item *item)
|
|
{
|
|
DBUG_ENTER("cmp_item_row::store_value");
|
|
DBUG_ASSERT(comparators);
|
|
if (comparators)
|
|
{
|
|
item->bring_value();
|
|
item->null_value= 0;
|
|
for (uint i= 0; i < n; i++)
|
|
{
|
|
comparators[i]->store_value(item->element_index(i));
|
|
item->null_value|= item->element_index(i)->null_value;
|
|
}
|
|
}
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
void cmp_item_row::store_value_by_template(cmp_item *t, Item *item)
|
|
{
|
|
cmp_item_row *tmpl= (cmp_item_row*) t;
|
|
if (tmpl->n != item->cols())
|
|
{
|
|
my_error(ER_OPERAND_COLUMNS, MYF(0), tmpl->n);
|
|
return;
|
|
}
|
|
n= tmpl->n;
|
|
if ((comparators= (cmp_item **) sql_alloc(sizeof(cmp_item *)*n)))
|
|
{
|
|
item->bring_value();
|
|
item->null_value= 0;
|
|
for (uint i=0; i < n; i++)
|
|
{
|
|
if (!(comparators[i]= tmpl->comparators[i]->make_same()))
|
|
break; // new failed
|
|
comparators[i]->store_value_by_template(tmpl->comparators[i],
|
|
item->element_index(i));
|
|
item->null_value|= item->element_index(i)->null_value;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int cmp_item_row::cmp(Item *arg)
|
|
{
|
|
arg->null_value= 0;
|
|
if (arg->cols() != n)
|
|
{
|
|
my_error(ER_OPERAND_COLUMNS, MYF(0), n);
|
|
return 1;
|
|
}
|
|
bool was_null= 0;
|
|
arg->bring_value();
|
|
for (uint i=0; i < n; i++)
|
|
{
|
|
const int rc= comparators[i]->cmp(arg->element_index(i));
|
|
switch (rc)
|
|
{
|
|
case UNKNOWN:
|
|
was_null= true;
|
|
break;
|
|
case TRUE:
|
|
return TRUE;
|
|
case FALSE:
|
|
break; // elements #i are equal
|
|
}
|
|
arg->null_value|= arg->element_index(i)->null_value;
|
|
}
|
|
return was_null ? UNKNOWN : FALSE;
|
|
}
|
|
|
|
|
|
int cmp_item_row::compare(const cmp_item *c) const
|
|
{
|
|
const cmp_item_row *l_cmp= down_cast<const cmp_item_row*>(c);
|
|
for (uint i=0; i < n; i++)
|
|
{
|
|
int res;
|
|
if ((res= comparators[i]->compare(l_cmp->comparators[i])))
|
|
return res;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
void cmp_item_decimal::store_value(Item *item)
|
|
{
|
|
my_decimal *val= item->val_decimal(&value);
|
|
/* val may be zero if item is nnull */
|
|
if (val && val != &value)
|
|
my_decimal2decimal(val, &value);
|
|
set_null_value(item->null_value);
|
|
}
|
|
|
|
|
|
int cmp_item_decimal::cmp(Item *arg)
|
|
{
|
|
my_decimal tmp_buf, *tmp= arg->val_decimal(&tmp_buf);
|
|
return (m_null_value || arg->null_value) ?
|
|
UNKNOWN : (my_decimal_cmp(&value, tmp) != 0);
|
|
}
|
|
|
|
|
|
int cmp_item_decimal::compare(const cmp_item *arg) const
|
|
{
|
|
const cmp_item_decimal *l_cmp= down_cast<const cmp_item_decimal*>(arg);
|
|
return my_decimal_cmp(&value, &l_cmp->value);
|
|
}
|
|
|
|
|
|
cmp_item* cmp_item_decimal::make_same()
|
|
{
|
|
return new cmp_item_decimal();
|
|
}
|
|
|
|
|
|
cmp_item_datetime::cmp_item_datetime(const Item *warn_item_arg)
|
|
:warn_item(warn_item_arg), lval_cache(0),
|
|
has_date(warn_item_arg->is_temporal_with_date())
|
|
{}
|
|
|
|
void cmp_item_datetime::store_value(Item *item)
|
|
{
|
|
bool is_null;
|
|
Item **tmp_item= lval_cache ? &lval_cache : &item;
|
|
if (has_date)
|
|
value= get_datetime_value(current_thd, &tmp_item, &lval_cache, warn_item,
|
|
&is_null);
|
|
else
|
|
value= get_time_value(current_thd, &tmp_item, &lval_cache, warn_item,
|
|
&is_null);
|
|
set_null_value(item->null_value);
|
|
}
|
|
|
|
|
|
int cmp_item_datetime::cmp(Item *arg)
|
|
{
|
|
bool is_null;
|
|
Item **tmp_item= &arg;
|
|
longlong value2= 0;
|
|
if (has_date)
|
|
value2= get_datetime_value(current_thd, &tmp_item, 0, warn_item, &is_null);
|
|
else
|
|
value2= get_time_value(current_thd, &tmp_item, 0, warn_item, &is_null);
|
|
|
|
const bool rc= (value != value2);
|
|
return (m_null_value || arg->null_value) ? UNKNOWN : rc;
|
|
}
|
|
|
|
|
|
int cmp_item_datetime::compare(const cmp_item *ci) const
|
|
{
|
|
const cmp_item_datetime *l_cmp= down_cast<const cmp_item_datetime*>(ci);
|
|
return (value < l_cmp->value) ? -1 : ((value == l_cmp->value) ? 0 : 1);
|
|
}
|
|
|
|
|
|
cmp_item *cmp_item_datetime::make_same()
|
|
{
|
|
return new cmp_item_datetime(warn_item);
|
|
}
|
|
|
|
|
|
float
|
|
Item_func_in::get_single_col_filtering_effect(Item_ident *fieldref,
|
|
table_map filter_for_table,
|
|
const MY_BITMAP *fields_to_ignore,
|
|
double rows_in_table)
|
|
{
|
|
/*
|
|
Does not contribute to filtering effect if
|
|
1) This field belongs to another table.
|
|
2) Filter effect for this field has already been taken into
|
|
account. 'fieldref' may be a field or a reference to a field
|
|
(through a view, to an outer table etc)
|
|
*/
|
|
if ((fieldref->used_tables() != filter_for_table) || // 1)
|
|
bitmap_is_set(fields_to_ignore,
|
|
static_cast<Item_field*>
|
|
(fieldref->real_item())->field->field_index)) // 2)
|
|
return COND_FILTER_ALLPASS;
|
|
|
|
const Item_field *fld= (Item_field*)fieldref->real_item();
|
|
return fld->get_cond_filter_default_probability(rows_in_table,
|
|
COND_FILTER_EQUALITY);
|
|
|
|
}
|
|
|
|
float Item_func_in::get_filtering_effect(table_map filter_for_table,
|
|
table_map read_tables,
|
|
const MY_BITMAP *fields_to_ignore,
|
|
double rows_in_table)
|
|
{
|
|
|
|
DBUG_ASSERT((read_tables & filter_for_table) == 0);
|
|
/*
|
|
To contribute to filtering effect, the condition must refer to
|
|
exactly one unread table: the table filtering is currently
|
|
calculated for.
|
|
|
|
Dependent subqueries are not considered available values and no
|
|
filtering should be calculated for this item if the IN list
|
|
contains one. dep_subq_in_list is 'true' if the IN list contains a
|
|
dependent subquery.
|
|
*/
|
|
if ((used_tables() & ~read_tables) != filter_for_table ||
|
|
dep_subq_in_list)
|
|
return COND_FILTER_ALLPASS;
|
|
|
|
/*
|
|
No matter how many row values are input the filtering effect
|
|
shall not be higher than in_max_filter (currently 0.5).
|
|
*/
|
|
const float in_max_filter= 0.5f;
|
|
|
|
float filter= COND_FILTER_ALLPASS;
|
|
if (args[0]->type() == Item::ROW_ITEM)
|
|
{
|
|
/*
|
|
This is a row value IN predicate:
|
|
"WHERE (col1, col2, ...) IN ((1,2,..), ...)"
|
|
which can be rewritten to:
|
|
"WHERE (col1=1 AND col2=2...) OR (col1=.. AND col2=...) OR ..."
|
|
|
|
The filtering effect is:
|
|
filter= #row_values * filter(<single_row_value>)
|
|
|
|
where filter(<single_row_value>) = filter(col1) * filter(col2) * ...
|
|
|
|
In other words, we ignore the fact that there could be identical
|
|
row values since writing "WHERE (a,b) IN ((1,1), (1,1), ...)" is
|
|
not expected input from a user.
|
|
*/
|
|
Item_row* lhs_row= static_cast<Item_row*>(args[0]);
|
|
// For all items in the left row
|
|
float single_rowval_filter= COND_FILTER_ALLPASS;
|
|
for (uint i= 0; i < lhs_row->cols(); i++)
|
|
{
|
|
/*
|
|
May contribute to condition filtering only if
|
|
lhs_row->element_index(i) is a field or a reference to a field
|
|
(through a view, to an outer table etc)
|
|
*/
|
|
if (lhs_row->element_index(i)->real_item()->type() == Item::FIELD_ITEM)
|
|
{
|
|
Item_ident *fieldref=
|
|
static_cast<Item_ident*>(lhs_row->element_index(i));
|
|
|
|
const float tmp_filt= get_single_col_filtering_effect(fieldref,
|
|
filter_for_table,
|
|
fields_to_ignore,
|
|
rows_in_table);
|
|
single_rowval_filter*= tmp_filt;
|
|
}
|
|
}
|
|
|
|
/*
|
|
If single_rowval_filter == COND_FILTER_ALLPASS, the filtering
|
|
effect of this field should be ignored. If not, selectivity
|
|
should not be higher than 'in_max_filter' even if there are a
|
|
lot of values on the right hand side
|
|
|
|
arg_count includes the left hand side item
|
|
*/
|
|
if (single_rowval_filter != COND_FILTER_ALLPASS)
|
|
filter= min((arg_count - 1) * single_rowval_filter, in_max_filter);
|
|
}
|
|
else if (args[0]->real_item()->type() == Item::FIELD_ITEM)
|
|
{
|
|
/*
|
|
This is a single-column IN predicate:
|
|
"WHERE col IN (1, 2, ...)"
|
|
which can be rewritten to:
|
|
"WHERE col=1 OR col1=2 OR ..."
|
|
|
|
The filtering effect is: #values_right_hand_side * selectivity(=)
|
|
|
|
As for row values, it is assumed that no values on the right
|
|
hand side are identical.
|
|
*/
|
|
DBUG_ASSERT(args[0]->type() == FIELD_ITEM || args[0]->type() == REF_ITEM);
|
|
Item_ident *fieldref= static_cast<Item_ident*>(args[0]);
|
|
|
|
const float tmp_filt= get_single_col_filtering_effect(fieldref,
|
|
filter_for_table,
|
|
fields_to_ignore,
|
|
rows_in_table);
|
|
/*
|
|
If tmp_filt == COND_FILTER_ALLPASS, the filtering effect of this
|
|
field should be ignored. If not, selectivity should not be
|
|
higher than 'in_max_filter' even if there are a lot of values on
|
|
the right hand side
|
|
|
|
arg_count includes the left hand side item
|
|
*/
|
|
if (tmp_filt != COND_FILTER_ALLPASS)
|
|
filter= min((arg_count - 1) * tmp_filt, in_max_filter);
|
|
}
|
|
|
|
if (negated && filter != COND_FILTER_ALLPASS)
|
|
filter= 1.0f - filter;
|
|
|
|
DBUG_ASSERT(filter >= 0.0f && filter <= 1.0f);
|
|
return filter;
|
|
}
|
|
|
|
bool Item_func_in::list_contains_null()
|
|
{
|
|
Item **arg,**arg_end;
|
|
for (arg= args + 1, arg_end= args+arg_count; arg != arg_end ; arg++)
|
|
{
|
|
if ((*arg)->null_inside())
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
Perform context analysis of an IN item tree.
|
|
|
|
This function performs context analysis (name resolution) and calculates
|
|
various attributes of the item tree with Item_func_in as its root.
|
|
The function saves in ref the pointer to the item or to a newly created
|
|
item that is considered as a replacement for the original one.
|
|
|
|
@param thd reference to the global context of the query thread
|
|
@param ref pointer to Item* variable where pointer to resulting "fixed"
|
|
item is to be assigned
|
|
|
|
@note
|
|
Let T0(e)/T1(e) be the value of not_null_tables(e) when e is used on
|
|
a predicate/function level. Then it's easy to show that:
|
|
@verbatim
|
|
T0(e IN(e1,...,en)) = union(T1(e),intersection(T1(ei)))
|
|
T1(e IN(e1,...,en)) = union(T1(e),intersection(T1(ei)))
|
|
T0(e NOT IN(e1,...,en)) = union(T1(e),union(T1(ei)))
|
|
T1(e NOT IN(e1,...,en)) = union(T1(e),intersection(T1(ei)))
|
|
@endverbatim
|
|
|
|
@retval
|
|
0 ok
|
|
@retval
|
|
1 got error
|
|
*/
|
|
|
|
bool Item_func_in::fix_fields(THD *thd, Item **ref)
|
|
{
|
|
if (Item_func_opt_neg::fix_fields(thd, ref))
|
|
return true;
|
|
|
|
// not_null_tables_cache == union(T1(e),union(T1(ei)))
|
|
if (pred_level && negated)
|
|
return false;
|
|
|
|
// not_null_tables_cache = union(T1(e),intersection(T1(ei)))
|
|
not_null_tables_cache= ~(table_map) 0;
|
|
Item **arg_end= args + arg_count;
|
|
for (Item **arg= args + 1; arg != arg_end; arg++)
|
|
not_null_tables_cache&= (*arg)->not_null_tables();
|
|
not_null_tables_cache|= (*args)->not_null_tables();
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void Item_func_in::fix_after_pullout(st_select_lex *parent_select,
|
|
st_select_lex *removed_select)
|
|
{
|
|
Item_func_opt_neg::fix_after_pullout(parent_select, removed_select);
|
|
|
|
// not_null_tables_cache == union(T1(e),union(T1(ei)))
|
|
if (pred_level && negated)
|
|
return;
|
|
|
|
// not_null_tables_cache = union(T1(e),intersection(T1(ei)))
|
|
not_null_tables_cache= ~(table_map) 0;
|
|
Item **arg_end= args + arg_count;
|
|
for (Item **arg= args + 1; arg != arg_end; arg++)
|
|
not_null_tables_cache&= (*arg)->not_null_tables();
|
|
not_null_tables_cache|= (*args)->not_null_tables();
|
|
}
|
|
|
|
|
|
static int srtcmp_in(CHARSET_INFO *cs, const String *x,const String *y)
|
|
{
|
|
return cs->coll->strnncollsp(cs,
|
|
(uchar *) x->ptr(),x->length(),
|
|
(uchar *) y->ptr(),y->length(), 0);
|
|
}
|
|
|
|
|
|
void Item_func_in::fix_length_and_dec()
|
|
{
|
|
Item **arg, **arg_end;
|
|
bool const_itm= 1;
|
|
THD *thd= current_thd;
|
|
bool datetime_found= FALSE;
|
|
/* TRUE <=> arguments values will be compared as DATETIMEs. */
|
|
bool compare_as_datetime= FALSE;
|
|
Item *date_arg= 0;
|
|
uint found_types= 0;
|
|
uint type_cnt= 0, i;
|
|
Item_result cmp_type= STRING_RESULT;
|
|
left_result_type= args[0]->result_type();
|
|
if (!(found_types= collect_cmp_types(args, arg_count, true)))
|
|
return;
|
|
|
|
for (arg= args + 1, arg_end= args + arg_count; arg != arg_end ; arg++)
|
|
{
|
|
if (!arg[0]->const_item())
|
|
{
|
|
const_itm= 0;
|
|
if (arg[0]->real_item()->type() == Item::SUBSELECT_ITEM)
|
|
dep_subq_in_list= true;
|
|
break;
|
|
}
|
|
}
|
|
for (i= 0; i <= (uint)DECIMAL_RESULT; i++)
|
|
{
|
|
if (found_types & (1U << i))
|
|
{
|
|
(type_cnt)++;
|
|
cmp_type= (Item_result) i;
|
|
}
|
|
}
|
|
|
|
/*
|
|
First conditions for bisection to be possible:
|
|
1. All types are similar, and
|
|
2. All expressions in <in value list> are const
|
|
*/
|
|
bool bisection_possible=
|
|
type_cnt == 1 && // 1
|
|
const_itm; // 2
|
|
if (bisection_possible)
|
|
{
|
|
/*
|
|
In the presence of NULLs, the correct result of evaluating this item
|
|
must be UNKNOWN or FALSE. To achieve that:
|
|
- If type is scalar, we can use bisection and the "have_null" boolean.
|
|
- If type is ROW, we will need to scan all of <in value list> when
|
|
searching, so bisection is impossible. Unless:
|
|
3. UNKNOWN and FALSE are equivalent results
|
|
4. Neither left expression nor <in value list> contain any NULL value
|
|
*/
|
|
|
|
if (cmp_type == ROW_RESULT &&
|
|
!((is_top_level_item() && !negated) || // 3
|
|
(!list_contains_null() && !args[0]->maybe_null))) // 4
|
|
bisection_possible= false;
|
|
}
|
|
|
|
/*
|
|
JSON values will be compared as strings, and not with the JSON
|
|
comparator as one might expect. Raise a warning if one of the
|
|
arguments is JSON. (The degenerate case x IN (y) may get rewritten
|
|
to x = y, though, and then the JSON comparator will be used if one
|
|
of the arguments is JSON.)
|
|
*/
|
|
unsupported_json_comparison(arg_count, args,
|
|
"comparison of JSON in the IN operator");
|
|
|
|
if (type_cnt == 1)
|
|
{
|
|
if (cmp_type == STRING_RESULT &&
|
|
agg_arg_charsets_for_comparison(cmp_collation, args, arg_count))
|
|
return;
|
|
/*
|
|
When comparing rows create the row comparator object beforehand to ease
|
|
the DATETIME comparison detection procedure.
|
|
*/
|
|
if (cmp_type == ROW_RESULT)
|
|
{
|
|
cmp_item_row *cmp= 0;
|
|
if (bisection_possible)
|
|
{
|
|
array= new in_row(thd, arg_count-1, 0);
|
|
cmp= &((in_row*)array)->tmp;
|
|
}
|
|
else
|
|
{
|
|
if (!(cmp= new cmp_item_row))
|
|
return;
|
|
cmp_items[ROW_RESULT]= cmp;
|
|
}
|
|
cmp->n= args[0]->cols();
|
|
cmp->alloc_comparators(args[0]);
|
|
}
|
|
/* All DATE/DATETIME fields/functions has the STRING result type. */
|
|
if (cmp_type == STRING_RESULT || cmp_type == ROW_RESULT)
|
|
{
|
|
uint col, cols= args[0]->cols();
|
|
|
|
for (col= 0; col < cols; col++)
|
|
{
|
|
bool skip_column= FALSE;
|
|
/*
|
|
Check that all items to be compared has the STRING result type and at
|
|
least one of them is a DATE/DATETIME item.
|
|
*/
|
|
for (arg= args, arg_end= args + arg_count; arg != arg_end ; arg++)
|
|
{
|
|
Item *itm= ((cmp_type == STRING_RESULT) ? arg[0] :
|
|
arg[0]->element_index(col));
|
|
if (itm->result_type() != STRING_RESULT)
|
|
{
|
|
skip_column= TRUE;
|
|
break;
|
|
}
|
|
else if (itm->is_temporal_with_date())
|
|
{
|
|
datetime_found= TRUE;
|
|
/*
|
|
Internally all DATE/DATETIME values are converted to the DATETIME
|
|
type. So try to find a DATETIME item to issue correct warnings.
|
|
*/
|
|
if (!date_arg)
|
|
date_arg= itm;
|
|
else if (itm->field_type() == MYSQL_TYPE_DATETIME)
|
|
{
|
|
date_arg= itm;
|
|
/* All arguments are already checked to have the STRING result. */
|
|
if (cmp_type == STRING_RESULT)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (skip_column)
|
|
continue;
|
|
if (datetime_found)
|
|
{
|
|
if (cmp_type == ROW_RESULT)
|
|
{
|
|
cmp_item **cmp= 0;
|
|
if (array)
|
|
cmp= ((in_row*)array)->tmp.comparators + col;
|
|
else
|
|
cmp= ((cmp_item_row*)cmp_items[ROW_RESULT])->comparators + col;
|
|
*cmp= new cmp_item_datetime(date_arg);
|
|
/* Reset variables for the next column. */
|
|
date_arg= 0;
|
|
datetime_found= FALSE;
|
|
}
|
|
else
|
|
compare_as_datetime= TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bisection_possible)
|
|
{
|
|
if (compare_as_datetime)
|
|
array= new in_datetime(thd, date_arg, arg_count - 1);
|
|
else
|
|
{
|
|
/*
|
|
IN must compare INT columns and constants as int values (the same
|
|
way as equality does).
|
|
So we must check here if the column on the left and all the constant
|
|
values on the right can be compared as integers and adjust the
|
|
comparison type accordingly.
|
|
*/
|
|
bool datetime_as_longlong= false;
|
|
if (args[0]->real_item()->type() == FIELD_ITEM &&
|
|
thd->lex->sql_command != SQLCOM_CREATE_VIEW &&
|
|
thd->lex->sql_command != SQLCOM_SHOW_CREATE &&
|
|
cmp_type != INT_RESULT)
|
|
{
|
|
Item_field *field_item= (Item_field*) (args[0]->real_item());
|
|
if (field_item->field->can_be_compared_as_longlong())
|
|
{
|
|
bool all_converted= true;
|
|
for (arg=args + 1, arg_end= args + arg_count; arg != arg_end ; arg++)
|
|
{
|
|
if (!convert_constant_item (thd, field_item, &arg[0]))
|
|
all_converted= false;
|
|
}
|
|
if (all_converted)
|
|
{
|
|
cmp_type= INT_RESULT;
|
|
datetime_as_longlong= field_item->is_temporal();
|
|
}
|
|
}
|
|
}
|
|
switch (cmp_type) {
|
|
case STRING_RESULT:
|
|
array=new in_string(thd, arg_count-1, (qsort2_cmp) srtcmp_in,
|
|
cmp_collation.collation);
|
|
break;
|
|
case INT_RESULT:
|
|
array= datetime_as_longlong ?
|
|
args[0]->field_type() == MYSQL_TYPE_TIME ?
|
|
(in_vector*) new in_time_as_longlong(thd, arg_count - 1) :
|
|
(in_vector*) new in_datetime_as_longlong(thd, arg_count - 1) :
|
|
(in_vector*) new in_longlong(thd, arg_count - 1);
|
|
break;
|
|
case REAL_RESULT:
|
|
array= new in_double(thd, arg_count-1);
|
|
break;
|
|
case ROW_RESULT:
|
|
/*
|
|
The row comparator was created at the beginning.
|
|
*/
|
|
break;
|
|
case DECIMAL_RESULT:
|
|
array= new in_decimal(thd, arg_count - 1);
|
|
break;
|
|
default:
|
|
DBUG_ASSERT(0);
|
|
return;
|
|
}
|
|
}
|
|
if (!array || thd->is_fatal_error) // OOM
|
|
return;
|
|
uint j=0;
|
|
for (uint i=1 ; i < arg_count ; i++)
|
|
{
|
|
array->set(j,args[i]);
|
|
if (!args[i]->null_value)
|
|
j++; // include this cell in the array.
|
|
else
|
|
{
|
|
/*
|
|
We don't put NULL values in array, to avoid erronous matches in
|
|
bisection.
|
|
*/
|
|
have_null= 1;
|
|
}
|
|
}
|
|
array->used_count= j;
|
|
DBUG_ASSERT(array->used_count <= array->count);
|
|
if (array->used_count < array->count)
|
|
array->shrink_array(j);
|
|
|
|
if (array->used_count)
|
|
array->sort();
|
|
}
|
|
else
|
|
{
|
|
if (compare_as_datetime)
|
|
cmp_items[STRING_RESULT]= new cmp_item_datetime(date_arg);
|
|
else
|
|
{
|
|
for (i= 0; i <= (uint) DECIMAL_RESULT; i++)
|
|
{
|
|
if (found_types & (1U << i) && !cmp_items[i])
|
|
{
|
|
if ((Item_result)i == STRING_RESULT &&
|
|
agg_arg_charsets_for_comparison(cmp_collation, args, arg_count))
|
|
return;
|
|
if (!cmp_items[i] && !(cmp_items[i]=
|
|
cmp_item::get_comparator((Item_result)i, args[0],
|
|
cmp_collation.collation)))
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Opt_trace_object(&thd->opt_trace).add("IN_uses_bisection",
|
|
bisection_possible);
|
|
/*
|
|
Set cmp_context of all arguments. This prevents
|
|
Item_field::equal_fields_propagator() from transforming a zerofill integer
|
|
argument into a string constant. Such a change would require rebuilding
|
|
cmp_itmes.
|
|
*/
|
|
for (arg= args + 1, arg_end= args + arg_count; arg != arg_end ; arg++)
|
|
{
|
|
arg[0]->cmp_context= item_cmp_type(left_result_type, arg[0]->result_type());
|
|
}
|
|
max_length= 1;
|
|
}
|
|
|
|
|
|
void Item_func_in::print(String *str, enum_query_type query_type)
|
|
{
|
|
str->append('(');
|
|
args[0]->print(str, query_type);
|
|
if (negated)
|
|
str->append(STRING_WITH_LEN(" not"));
|
|
str->append(STRING_WITH_LEN(" in ("));
|
|
print_args(str, 1, query_type);
|
|
str->append(STRING_WITH_LEN("))"));
|
|
}
|
|
|
|
|
|
/*
|
|
Evaluate the function and return its value.
|
|
|
|
SYNOPSIS
|
|
val_int()
|
|
|
|
DESCRIPTION
|
|
Evaluate the function and return its value.
|
|
|
|
IMPLEMENTATION
|
|
If the array object is defined then the value of the function is
|
|
calculated by means of this array.
|
|
Otherwise several cmp_item objects are used in order to do correct
|
|
comparison of left expression and an expression from the values list.
|
|
One cmp_item object correspond to one used comparison type. Left
|
|
expression can be evaluated up to number of different used comparison
|
|
types. A bit mapped variable value_added_map is used to check whether
|
|
the left expression already was evaluated for a particular result type.
|
|
Result types are mapped to it according to their integer values i.e.
|
|
STRING_RESULT is mapped to bit 0, REAL_RESULT to bit 1, so on.
|
|
|
|
RETURN
|
|
Value of the function
|
|
*/
|
|
|
|
longlong Item_func_in::val_int()
|
|
{
|
|
cmp_item *in_item;
|
|
DBUG_ASSERT(fixed == 1);
|
|
uint value_added_map= 0;
|
|
if (array)
|
|
{
|
|
bool tmp=array->find_item(args[0]);
|
|
/*
|
|
NULL on left -> UNKNOWN.
|
|
Found no match, and NULL on right -> UNKNOWN.
|
|
NULL on right can never give a match, as it is not stored in
|
|
array.
|
|
See also the 'bisection_possible' variable in fix_length_and_dec().
|
|
*/
|
|
null_value=args[0]->null_value || (!tmp && have_null);
|
|
return (longlong) (!null_value && tmp != negated);
|
|
}
|
|
|
|
if ((null_value= args[0]->real_item()->type() == NULL_ITEM))
|
|
return 0;
|
|
|
|
have_null= 0;
|
|
for (uint i= 1 ; i < arg_count ; i++)
|
|
{
|
|
if (args[i]->real_item()->type() == NULL_ITEM)
|
|
{
|
|
have_null= TRUE;
|
|
continue;
|
|
}
|
|
Item_result cmp_type= item_cmp_type(left_result_type, args[i]->result_type());
|
|
in_item= cmp_items[(uint)cmp_type];
|
|
DBUG_ASSERT(in_item);
|
|
if (!(value_added_map & (1U << (uint)cmp_type)))
|
|
{
|
|
in_item->store_value(args[0]);
|
|
value_added_map|= 1U << (uint)cmp_type;
|
|
}
|
|
const int rc= in_item->cmp(args[i]);
|
|
if (rc == FALSE)
|
|
return (longlong) (!negated);
|
|
have_null|= (rc == UNKNOWN);
|
|
}
|
|
|
|
null_value= have_null;
|
|
return (longlong) (!null_value && negated);
|
|
}
|
|
|
|
|
|
void Item::check_deprecated_bin_op(const Item *a, const Item *b)
|
|
{
|
|
/*
|
|
We want to warn about cases which will likely change behaviour in future
|
|
versions. The conditions to emit a warning are:
|
|
|
|
1. If there's only one argument, the item should be a [VAR]BINARY
|
|
argument (1) and it should be different from the hex/bit/NULL literal (2).
|
|
|
|
2. If there are two arguments, both should be [VAR]BINARY (3) and at least
|
|
one of them should be different from the hex/bit/NULL literal (4)
|
|
*/
|
|
if (a->result_type() == STRING_RESULT &&
|
|
a->collation.collation == &my_charset_bin && // (1), (3)
|
|
(!b ||
|
|
(b->result_type() == STRING_RESULT &&
|
|
b->collation.collation == &my_charset_bin)) && // (3)
|
|
((a->type() != Item::VARBIN_ITEM &&
|
|
a->type() != Item::NULL_ITEM) || // (2), (4)
|
|
(b && b->type() != Item::VARBIN_ITEM &&
|
|
b->type() != Item::NULL_ITEM))) // (4)
|
|
{
|
|
push_warning_printf(current_thd, Sql_condition::SL_WARNING,
|
|
ER_WARN_DEPRECATED_SYNTAX,
|
|
"Bitwise operations on BINARY will change behavior"
|
|
" in a future version, check the 'Bit functions'"
|
|
" section in the manual.");
|
|
}
|
|
}
|
|
|
|
longlong Item_func_bit_or::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
ulonglong arg1= (ulonglong) args[0]->val_int();
|
|
if (args[0]->null_value)
|
|
{
|
|
null_value=1; /* purecov: inspected */
|
|
return 0; /* purecov: inspected */
|
|
}
|
|
ulonglong arg2= (ulonglong) args[1]->val_int();
|
|
if (args[1]->null_value)
|
|
{
|
|
null_value=1;
|
|
return 0;
|
|
}
|
|
null_value=0;
|
|
return (longlong) (arg1 | arg2);
|
|
}
|
|
|
|
|
|
longlong Item_func_bit_and::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
ulonglong arg1= (ulonglong) args[0]->val_int();
|
|
if (args[0]->null_value)
|
|
{
|
|
null_value=1; /* purecov: inspected */
|
|
return 0; /* purecov: inspected */
|
|
}
|
|
ulonglong arg2= (ulonglong) args[1]->val_int();
|
|
if (args[1]->null_value)
|
|
{
|
|
null_value=1; /* purecov: inspected */
|
|
return 0; /* purecov: inspected */
|
|
}
|
|
null_value=0;
|
|
return (longlong) (arg1 & arg2);
|
|
}
|
|
|
|
Item_cond::Item_cond(THD *thd, Item_cond *item)
|
|
:Item_bool_func(thd, item),
|
|
abort_on_null(item->abort_on_null)
|
|
{
|
|
/*
|
|
item->list will be copied by copy_andor_arguments() call
|
|
*/
|
|
}
|
|
|
|
/**
|
|
Contextualization for Item_cond functional items
|
|
|
|
Item_cond successors use Item_cond::list instead of Item_func::args
|
|
and Item_func::arg_count, so we can't itemize parse-time Item_cond
|
|
objects by forwarding a contextualization process to the parent Item_func
|
|
class: we need to overload this function to run a contextualization
|
|
the Item_cond::list items.
|
|
*/
|
|
bool Item_cond::itemize(Parse_context *pc, Item **res)
|
|
{
|
|
if (skip_itemize(res))
|
|
return false;
|
|
if (super::itemize(pc, res))
|
|
return true;
|
|
|
|
List_iterator<Item> li(list);
|
|
Item *item;
|
|
while ((item= li++))
|
|
{
|
|
if (item->itemize(pc, &item))
|
|
return true;
|
|
li.replace(item);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
void Item_cond::copy_andor_arguments(THD *thd, Item_cond *item)
|
|
{
|
|
List_iterator_fast<Item> li(item->list);
|
|
while (Item *it= li++)
|
|
{
|
|
DBUG_ASSERT(it->real_item()); // Sanity check (no dangling 'ref')
|
|
list.push_back(it->copy_andor_structure(thd));
|
|
}
|
|
}
|
|
|
|
|
|
bool
|
|
Item_cond::fix_fields(THD *thd, Item **ref)
|
|
{
|
|
DBUG_ASSERT(fixed == 0);
|
|
List_iterator<Item> li(list);
|
|
Item *item;
|
|
|
|
/*
|
|
Semi-join flattening should only be performed for predicates on
|
|
the AND-top-level. Disable it if this condition is not an AND.
|
|
*/
|
|
Disable_semijoin_flattening DSF(thd->lex->current_select(),
|
|
functype() != COND_AND_FUNC);
|
|
|
|
uchar buff[sizeof(char*)]; // Max local vars in function
|
|
used_tables_cache= 0;
|
|
const_item_cache= true;
|
|
|
|
if (functype() == COND_AND_FUNC && abort_on_null)
|
|
not_null_tables_cache= 0;
|
|
else
|
|
not_null_tables_cache= ~(table_map) 0;
|
|
|
|
if (check_stack_overrun(thd, STACK_MIN_SIZE, buff))
|
|
return TRUE; // Fatal error flag is set!
|
|
/*
|
|
The following optimization reduces the depth of an AND-OR tree.
|
|
E.g. a WHERE clause like
|
|
F1 AND (F2 AND (F2 AND F4))
|
|
is parsed into a tree with the same nested structure as defined
|
|
by braces. This optimization will transform such tree into
|
|
AND (F1, F2, F3, F4).
|
|
Trees of OR items are flattened as well:
|
|
((F1 OR F2) OR (F3 OR F4)) => OR (F1, F2, F3, F4)
|
|
Items for removed AND/OR levels will dangle until the death of the
|
|
entire statement.
|
|
The optimization is currently prepared statements and stored procedures
|
|
friendly as it doesn't allocate any memory and its effects are durable
|
|
(i.e. do not depend on PS/SP arguments).
|
|
*/
|
|
while ((item=li++))
|
|
{
|
|
while (item->type() == Item::COND_ITEM &&
|
|
((Item_cond*) item)->functype() == functype() &&
|
|
!((Item_cond*) item)->list.is_empty())
|
|
{ // Identical function
|
|
li.replace(((Item_cond*) item)->list);
|
|
((Item_cond*) item)->list.empty();
|
|
item= *li.ref(); // new current item
|
|
}
|
|
if (abort_on_null)
|
|
item->top_level_item();
|
|
|
|
// item can be substituted in fix_fields
|
|
if ((!item->fixed &&
|
|
item->fix_fields(thd, li.ref())) ||
|
|
(item= *li.ref())->check_cols(1))
|
|
return TRUE; /* purecov: inspected */
|
|
used_tables_cache|= item->used_tables();
|
|
const_item_cache&= item->const_item();
|
|
|
|
if (functype() == COND_AND_FUNC && abort_on_null)
|
|
not_null_tables_cache|= item->not_null_tables();
|
|
else
|
|
not_null_tables_cache&= item->not_null_tables();
|
|
with_sum_func|= item->with_sum_func;
|
|
with_subselect|= item->has_subquery();
|
|
with_stored_program|= item->has_stored_program();
|
|
maybe_null|= item->maybe_null;
|
|
}
|
|
thd->lex->current_select()->cond_count+= list.elements;
|
|
fix_length_and_dec();
|
|
fixed= true;
|
|
return false;
|
|
}
|
|
|
|
|
|
void Item_cond::fix_after_pullout(st_select_lex *parent_select,
|
|
st_select_lex *removed_select)
|
|
{
|
|
List_iterator<Item> li(list);
|
|
Item *item;
|
|
|
|
used_tables_cache= get_initial_pseudo_tables();
|
|
const_item_cache= true;
|
|
|
|
if (functype() == COND_AND_FUNC && abort_on_null)
|
|
not_null_tables_cache= 0;
|
|
else
|
|
not_null_tables_cache= ~(table_map) 0;
|
|
|
|
while ((item=li++))
|
|
{
|
|
item->fix_after_pullout(parent_select, removed_select);
|
|
used_tables_cache|= item->used_tables();
|
|
const_item_cache&= item->const_item();
|
|
if (functype() == COND_AND_FUNC && abort_on_null)
|
|
not_null_tables_cache|= item->not_null_tables();
|
|
else
|
|
not_null_tables_cache&= item->not_null_tables();
|
|
}
|
|
}
|
|
|
|
|
|
bool Item_cond::eq(const Item *item, bool binary_cmp) const
|
|
{
|
|
if (this == item)
|
|
return true;
|
|
if (item->type() != COND_ITEM)
|
|
return false;
|
|
const Item_cond *item_cond= down_cast<const Item_cond *>(item);
|
|
if (functype() != item_cond->functype() ||
|
|
list.elements != item_cond->list.elements ||
|
|
func_name() != item_cond->func_name())
|
|
return false;
|
|
// Item_cond never uses "args". Inspect "list" instead.
|
|
DBUG_ASSERT(arg_count == 0 && item_cond->arg_count == 0);
|
|
List_iterator_fast<Item> it1(const_cast<Item_cond *>(this)->list);
|
|
List_iterator_fast<Item> it2(const_cast<Item_cond *>(item_cond)->list);
|
|
Item *i;
|
|
while ((i= it1++))
|
|
if (!i->eq(it2++, binary_cmp))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
|
|
bool Item_cond::walk(Item_processor processor, enum_walk walk, uchar *arg)
|
|
{
|
|
if ((walk & WALK_PREFIX) && (this->*processor)(arg))
|
|
return true;
|
|
|
|
List_iterator_fast<Item> li(list);
|
|
Item *item;
|
|
while ((item= li++))
|
|
{
|
|
if (item->walk(processor, walk, arg))
|
|
return true;
|
|
}
|
|
return (walk & WALK_POSTFIX) && (this->*processor)(arg);
|
|
}
|
|
|
|
|
|
/**
|
|
Transform an Item_cond object with a transformer callback function.
|
|
|
|
The function recursively applies the transform method to each
|
|
member item of the condition list.
|
|
If the call of the method for a member item returns a new item
|
|
the old item is substituted for a new one.
|
|
After this the transformer is applied to the root node
|
|
of the Item_cond object.
|
|
|
|
@param transformer the transformer callback function to be applied to
|
|
the nodes of the tree of the object
|
|
@param arg parameter to be passed to the transformer
|
|
|
|
@return
|
|
Item returned as the result of transformation of the root node
|
|
*/
|
|
|
|
Item *Item_cond::transform(Item_transformer transformer, uchar *arg)
|
|
{
|
|
DBUG_ASSERT(!current_thd->stmt_arena->is_stmt_prepare());
|
|
|
|
List_iterator<Item> li(list);
|
|
Item *item;
|
|
while ((item= li++))
|
|
{
|
|
Item *new_item= item->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 (new_item != item)
|
|
current_thd->change_item_tree(li.ref(), new_item);
|
|
}
|
|
return Item_func::transform(transformer, arg);
|
|
}
|
|
|
|
|
|
/**
|
|
Compile Item_cond object with a processor and a transformer
|
|
callback functions.
|
|
|
|
First the function applies the analyzer to the root node of
|
|
the Item_func object. Then if the analyzer succeeeds (returns TRUE)
|
|
the function recursively applies the compile method to member
|
|
item of the condition list.
|
|
If the call of the method for a member item returns a new item
|
|
the old item is substituted for a new one.
|
|
After this the transformer is applied to the root node
|
|
of the Item_cond object.
|
|
|
|
@param analyzer the analyzer callback function to be applied to the
|
|
nodes of the tree of the object
|
|
@param[in,out] arg_p parameter to be passed to the analyzer
|
|
@param transformer the transformer callback function to be applied to the
|
|
nodes of the tree of the object
|
|
@param arg_t parameter to be passed to the transformer
|
|
|
|
@return Item returned as result of transformation of the node,
|
|
the same item if no transformation applied, or NULL if
|
|
transformation caused an error.
|
|
*/
|
|
|
|
Item *Item_cond::compile(Item_analyzer analyzer, uchar **arg_p,
|
|
Item_transformer transformer, uchar *arg_t)
|
|
{
|
|
if (!(this->*analyzer)(arg_p))
|
|
return this;
|
|
|
|
List_iterator<Item> li(list);
|
|
Item *item;
|
|
while ((item= li++))
|
|
{
|
|
/*
|
|
The same parameter value of arg_p must be passed
|
|
to analyze any argument of the condition formula.
|
|
*/
|
|
uchar *arg_v= *arg_p;
|
|
Item *new_item= item->compile(analyzer, &arg_v, transformer, arg_t);
|
|
if (new_item == NULL)
|
|
return NULL;
|
|
if (new_item != item)
|
|
current_thd->change_item_tree(li.ref(), new_item);
|
|
}
|
|
// strange to call transform(): each argument will thus have the transformer
|
|
// called twice on it (in compile() above and Item_func::transform below)??
|
|
return Item_func::transform(transformer, arg_t);
|
|
}
|
|
|
|
void Item_cond::traverse_cond(Cond_traverser traverser,
|
|
void *arg, traverse_order order)
|
|
{
|
|
List_iterator<Item> li(list);
|
|
Item *item;
|
|
|
|
switch(order) {
|
|
case(PREFIX):
|
|
(*traverser)(this, arg);
|
|
while ((item= li++))
|
|
{
|
|
item->traverse_cond(traverser, arg, order);
|
|
}
|
|
(*traverser)(NULL, arg);
|
|
break;
|
|
case(POSTFIX):
|
|
while ((item= li++))
|
|
{
|
|
item->traverse_cond(traverser, arg, order);
|
|
}
|
|
(*traverser)(this, arg);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Move SUM items out from item tree and replace with reference.
|
|
|
|
The split is done to get an unique item for each SUM function
|
|
so that we can easily find and calculate them.
|
|
(Calculation done by update_sum_func() and copy_sum_funcs() in
|
|
sql_select.cc)
|
|
|
|
@param thd Thread handler
|
|
@param ref_pointer_array Pointer to array of reference fields
|
|
@param fields All fields in select
|
|
|
|
@note
|
|
This function is run on all expression (SELECT list, WHERE, HAVING etc)
|
|
that have or refer (HAVING) to a SUM expression.
|
|
*/
|
|
|
|
void Item_cond::split_sum_func(THD *thd, Ref_ptr_array ref_pointer_array,
|
|
List<Item> &fields)
|
|
{
|
|
List_iterator<Item> li(list);
|
|
Item *item;
|
|
while ((item= li++))
|
|
item->split_sum_func2(thd, ref_pointer_array, fields, li.ref(), TRUE);
|
|
}
|
|
|
|
|
|
void Item_cond::update_used_tables()
|
|
{
|
|
List_iterator_fast<Item> li(list);
|
|
Item *item;
|
|
|
|
used_tables_cache=0;
|
|
const_item_cache=1;
|
|
with_subselect= false;
|
|
with_stored_program= false;
|
|
while ((item=li++))
|
|
{
|
|
item->update_used_tables();
|
|
used_tables_cache|= item->used_tables();
|
|
const_item_cache&= item->const_item();
|
|
with_subselect|= item->has_subquery();
|
|
with_stored_program|= item->has_stored_program();
|
|
}
|
|
}
|
|
|
|
|
|
void Item_cond::print(String *str, enum_query_type query_type)
|
|
{
|
|
str->append('(');
|
|
List_iterator_fast<Item> li(list);
|
|
Item *item;
|
|
if ((item=li++))
|
|
item->print(str, query_type);
|
|
while ((item=li++))
|
|
{
|
|
str->append(' ');
|
|
str->append(func_name());
|
|
str->append(' ');
|
|
item->print(str, query_type);
|
|
}
|
|
str->append(')');
|
|
}
|
|
|
|
|
|
void Item_cond::neg_arguments(THD *thd)
|
|
{
|
|
List_iterator<Item> li(list);
|
|
Item *item;
|
|
while ((item= li++)) /* Apply not transformation to the arguments */
|
|
{
|
|
Item *new_item= item->neg_transformer(thd);
|
|
if (!new_item)
|
|
{
|
|
if (!(new_item= new Item_func_not(item)))
|
|
return; // Fatal OEM error
|
|
}
|
|
(void) li.replace(new_item);
|
|
}
|
|
}
|
|
|
|
|
|
float Item_cond_and::get_filtering_effect(table_map filter_for_table,
|
|
table_map read_tables,
|
|
const MY_BITMAP *fields_to_ignore,
|
|
double rows_in_table)
|
|
{
|
|
if (!(used_tables() & filter_for_table))
|
|
return COND_FILTER_ALLPASS; // No conditions below this apply to the table
|
|
|
|
float filter= COND_FILTER_ALLPASS;
|
|
List_iterator<Item> it(list);
|
|
Item *item;
|
|
|
|
/*
|
|
Calculated as "Conjunction of independent events":
|
|
P(A and B ...) = P(A) * P(B) * ...
|
|
*/
|
|
while ((item= it++))
|
|
filter*= item->get_filtering_effect(filter_for_table,
|
|
read_tables,
|
|
fields_to_ignore,
|
|
rows_in_table);
|
|
return filter;
|
|
}
|
|
|
|
/**
|
|
Evaluation of AND(expr, expr, expr ...).
|
|
|
|
@note
|
|
abort_if_null is set for AND expressions for which we don't care if the
|
|
result is NULL or 0. This is set for:
|
|
- WHERE clause
|
|
- HAVING clause
|
|
- IF(expression)
|
|
|
|
@retval
|
|
1 If all expressions are true
|
|
@retval
|
|
0 If all expressions are false or if we find a NULL expression and
|
|
'abort_on_null' is set.
|
|
@retval
|
|
NULL if all expression are either 1 or NULL
|
|
*/
|
|
|
|
|
|
longlong Item_cond_and::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
List_iterator_fast<Item> li(list);
|
|
Item *item;
|
|
null_value= 0;
|
|
while ((item=li++))
|
|
{
|
|
if (!item->val_bool())
|
|
{
|
|
if (abort_on_null || !(null_value= item->null_value))
|
|
return 0; // return FALSE
|
|
}
|
|
}
|
|
return null_value ? 0 : 1;
|
|
}
|
|
|
|
|
|
float Item_cond_or::get_filtering_effect(table_map filter_for_table,
|
|
table_map read_tables,
|
|
const MY_BITMAP *fields_to_ignore,
|
|
double rows_in_table)
|
|
{
|
|
if (!(used_tables() & filter_for_table))
|
|
return COND_FILTER_ALLPASS; // No conditions below this apply to the table
|
|
|
|
float filter= 0.0f;
|
|
List_iterator<Item> it(list);
|
|
Item *item;
|
|
while ((item= it++))
|
|
{
|
|
const float cur_filter=
|
|
item->get_filtering_effect(filter_for_table, read_tables,
|
|
fields_to_ignore, rows_in_table);
|
|
/*
|
|
Calculated as "Disjunction of independent events":
|
|
P(A or B) = P(A) + P(B) - P(A) * P(B)
|
|
|
|
If any of the ORed predicates has a filtering effect of
|
|
COND_FILTER_ALLPASS, the end result is COND_FILTER_ALLPASS. This is as
|
|
expected since COND_FILTER_ALLPASS means that a) the predicate has
|
|
no filtering effect at all, or b) the predicate's filtering
|
|
effect is unknown. In both cases, the only meaningful result is
|
|
for OR to produce COND_FILTER_ALLPASS.
|
|
*/
|
|
filter= filter + cur_filter - (filter * cur_filter);
|
|
}
|
|
return filter;
|
|
}
|
|
|
|
longlong Item_cond_or::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
List_iterator_fast<Item> li(list);
|
|
Item *item;
|
|
null_value=0;
|
|
while ((item=li++))
|
|
{
|
|
if (item->val_bool())
|
|
{
|
|
null_value=0;
|
|
return 1;
|
|
}
|
|
if (item->null_value)
|
|
null_value=1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
Create an AND expression from two expressions.
|
|
|
|
@param a expression or NULL
|
|
@param b expression.
|
|
@param org_item Don't modify a if a == *org_item.
|
|
If a == NULL, org_item is set to point at b,
|
|
to ensure that future calls will not modify b.
|
|
|
|
@note
|
|
This will not modify item pointed to by org_item or b
|
|
The idea is that one can call this in a loop and create and
|
|
'and' over all items without modifying any of the original items.
|
|
|
|
@retval
|
|
NULL Error
|
|
@retval
|
|
Item
|
|
*/
|
|
|
|
Item *and_expressions(Item *a, Item *b, Item **org_item)
|
|
{
|
|
if (!a)
|
|
return (*org_item= b);
|
|
if (a == *org_item)
|
|
{
|
|
Item_cond *res;
|
|
if ((res= new Item_cond_and(a, b)))
|
|
{
|
|
res->set_used_tables(a->used_tables() | b->used_tables());
|
|
res->set_not_null_tables(a->not_null_tables() | b->not_null_tables());
|
|
}
|
|
return res;
|
|
}
|
|
if (((Item_cond_and*) a)->add(b))
|
|
return 0;
|
|
((Item_cond_and*) a)->set_used_tables(a->used_tables() | b->used_tables());
|
|
((Item_cond_and*) a)->set_not_null_tables(a->not_null_tables() |
|
|
b->not_null_tables());
|
|
return a;
|
|
}
|
|
|
|
float Item_func_isnull::get_filtering_effect(table_map filter_for_table,
|
|
table_map read_tables,
|
|
const MY_BITMAP *fields_to_ignore,
|
|
double rows_in_table)
|
|
{
|
|
const Item_field* fld=
|
|
contributes_to_filter(read_tables, filter_for_table, fields_to_ignore);
|
|
if (!fld)
|
|
return COND_FILTER_ALLPASS;
|
|
|
|
return fld->get_cond_filter_default_probability(rows_in_table,
|
|
COND_FILTER_EQUALITY);
|
|
}
|
|
|
|
longlong Item_func_isnull::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
/*
|
|
Handle optimization if the argument can't be null
|
|
This has to be here because of the test in update_used_tables().
|
|
*/
|
|
if (const_item_cache)
|
|
return cached_value;
|
|
return args[0]->is_null() ? 1: 0;
|
|
}
|
|
|
|
longlong Item_is_not_null_test::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
DBUG_ENTER("Item_is_not_null_test::val_int");
|
|
if (!used_tables_cache && !with_subselect && !with_stored_program)
|
|
{
|
|
/*
|
|
TODO: Currently this branch never executes, since used_tables_cache
|
|
is never equal to 0 -- it always contains RAND_TABLE_BIT,
|
|
see get_initial_pseudo_tables().
|
|
*/
|
|
owner->was_null|= (!cached_value);
|
|
DBUG_PRINT("info", ("cached: %ld", (long) cached_value));
|
|
DBUG_RETURN(cached_value);
|
|
}
|
|
if (args[0]->is_null())
|
|
{
|
|
DBUG_PRINT("info", ("null"));
|
|
owner->was_null|= 1;
|
|
DBUG_RETURN(0);
|
|
}
|
|
else
|
|
DBUG_RETURN(1);
|
|
}
|
|
|
|
/**
|
|
Optimize case of not_null_column IS NULL.
|
|
*/
|
|
void Item_is_not_null_test::update_used_tables()
|
|
{
|
|
const table_map initial_pseudo_tables= get_initial_pseudo_tables();
|
|
used_tables_cache= initial_pseudo_tables;
|
|
if (!args[0]->maybe_null)
|
|
{
|
|
cached_value= 1;
|
|
return;
|
|
}
|
|
args[0]->update_used_tables();
|
|
with_subselect= args[0]->has_subquery();
|
|
with_stored_program= args[0]->has_stored_program();
|
|
used_tables_cache|= args[0]->used_tables();
|
|
if (used_tables_cache == initial_pseudo_tables && !with_subselect &&
|
|
!with_stored_program)
|
|
/* Remember if the value is always NULL or never NULL */
|
|
cached_value= !args[0]->is_null();
|
|
}
|
|
|
|
float
|
|
Item_func_isnotnull::get_filtering_effect(table_map filter_for_table,
|
|
table_map read_tables,
|
|
const MY_BITMAP *fields_to_ignore,
|
|
double rows_in_table)
|
|
{
|
|
const Item_field* fld=
|
|
contributes_to_filter(read_tables, filter_for_table, fields_to_ignore);
|
|
if (!fld)
|
|
return COND_FILTER_ALLPASS;
|
|
|
|
return 1.0f - fld->get_cond_filter_default_probability(rows_in_table,
|
|
COND_FILTER_EQUALITY);
|
|
}
|
|
|
|
longlong Item_func_isnotnull::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
return args[0]->is_null() ? 0 : 1;
|
|
}
|
|
|
|
|
|
void Item_func_isnotnull::print(String *str, enum_query_type query_type)
|
|
{
|
|
str->append('(');
|
|
args[0]->print(str, query_type);
|
|
str->append(STRING_WITH_LEN(" is not null)"));
|
|
}
|
|
|
|
|
|
float Item_func_like::get_filtering_effect(table_map filter_for_table,
|
|
table_map read_tables,
|
|
const MY_BITMAP *fields_to_ignore,
|
|
double rows_in_table)
|
|
{
|
|
const Item_field* fld=
|
|
contributes_to_filter(read_tables, filter_for_table, fields_to_ignore);
|
|
if (!fld)
|
|
return COND_FILTER_ALLPASS;
|
|
|
|
/*
|
|
Filtering effect is similar to that of BETWEEN because
|
|
|
|
* "col like abc%" is similar to
|
|
"col between abc and abd"
|
|
The same applies for 'abc_'
|
|
* "col like %abc" can be seen as
|
|
"reverse(col) like cba%"" (see above)
|
|
* "col like "abc%def%..." is also similar
|
|
|
|
Now we're left with "col like <string_no_wildcards>" which should
|
|
have filtering effect like equality, but it would be costly to
|
|
look through the whole string searching for wildcards and since
|
|
LIKE is mostly used for wildcards this isn't checked.
|
|
*/
|
|
return fld->get_cond_filter_default_probability(rows_in_table,
|
|
COND_FILTER_BETWEEN);
|
|
}
|
|
|
|
|
|
bool Item_func_like::itemize(Parse_context *pc, Item **res)
|
|
{
|
|
if (skip_itemize(res))
|
|
return false;
|
|
if (super::itemize(pc, res) ||
|
|
(escape_item != NULL && escape_item->itemize(pc, &escape_item)))
|
|
return true;
|
|
|
|
if (escape_item == NULL)
|
|
{
|
|
THD *thd= pc->thd;
|
|
escape_item=
|
|
((thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES) ?
|
|
new (pc->mem_root) Item_string("", 0, &my_charset_latin1) :
|
|
new (pc->mem_root) Item_string("\\", 1, &my_charset_latin1));
|
|
}
|
|
return escape_item == NULL;
|
|
}
|
|
|
|
|
|
longlong Item_func_like::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
|
|
if (!escape_evaluated && eval_escape_clause(current_thd))
|
|
return error_int();
|
|
|
|
String* res = args[0]->val_str(&cmp.value1);
|
|
if (args[0]->null_value)
|
|
{
|
|
null_value=1;
|
|
return 0;
|
|
}
|
|
String* res2 = args[1]->val_str(&cmp.value2);
|
|
if (args[1]->null_value)
|
|
{
|
|
null_value=1;
|
|
return 0;
|
|
}
|
|
null_value=0;
|
|
if (can_do_bm)
|
|
return bm_matches(res->ptr(), res->length()) ? 1 : 0;
|
|
return my_wildcmp(cmp.cmp_collation.collation,
|
|
res->ptr(),res->ptr()+res->length(),
|
|
res2->ptr(),res2->ptr()+res2->length(),
|
|
escape,wild_one,wild_many) ? 0 : 1;
|
|
}
|
|
|
|
|
|
/**
|
|
We can optimize a where if first character isn't a wildcard
|
|
*/
|
|
|
|
Item_func::optimize_type Item_func_like::select_optimize() const
|
|
{
|
|
if (!args[1]->const_item())
|
|
return OPTIMIZE_NONE;
|
|
|
|
String* res2= args[1]->val_str((String *)&cmp.value2);
|
|
if (!res2)
|
|
return OPTIMIZE_NONE;
|
|
|
|
if (!res2->length()) // Can optimize empty wildcard: column LIKE ''
|
|
return OPTIMIZE_OP;
|
|
|
|
DBUG_ASSERT(res2->ptr());
|
|
char first= res2->ptr()[0];
|
|
return (first == wild_many || first == wild_one) ?
|
|
OPTIMIZE_NONE : OPTIMIZE_OP;
|
|
}
|
|
|
|
|
|
bool Item_func_like::fix_fields(THD *thd, Item **ref)
|
|
{
|
|
DBUG_ASSERT(fixed == 0);
|
|
|
|
Disable_semijoin_flattening DSF(thd->lex->current_select(), true);
|
|
|
|
if (Item_bool_func2::fix_fields(thd, ref) ||
|
|
escape_item->fix_fields(thd, &escape_item) ||
|
|
escape_item->check_cols(1))
|
|
return true;
|
|
|
|
if (!escape_item->const_during_execution())
|
|
{
|
|
my_error(ER_WRONG_ARGUMENTS,MYF(0),"ESCAPE");
|
|
return true;
|
|
}
|
|
|
|
if (escape_item->const_item())
|
|
{
|
|
/*
|
|
We need to know the escape character in order to apply Boyer-Moore. Since
|
|
it is const, it is safe to evaluate it now at the resolution stage.
|
|
*/
|
|
if (eval_escape_clause(thd))
|
|
return true;
|
|
|
|
/*
|
|
We could also do boyer-more for non-const items, but as we would have to
|
|
recompute the tables for each row it's not worth it.
|
|
*/
|
|
if (args[1]->const_item() && !use_strnxfrm(collation.collation) &&
|
|
!(specialflag & SPECIAL_NO_NEW_FUNC))
|
|
{
|
|
String* res2 = args[1]->val_str(&cmp.value2);
|
|
if (thd->is_error())
|
|
return true;
|
|
if (!res2)
|
|
return false; // Null argument
|
|
|
|
const size_t len = res2->length();
|
|
const char* first = res2->ptr();
|
|
const char* last = first + len - 1;
|
|
|
|
/*
|
|
Minimum length pattern before Boyer-Moore is used
|
|
for SELECT "text" LIKE "%pattern%" including the two
|
|
wildcards in class Item_func_like.
|
|
*/
|
|
|
|
const size_t min_bm_pattern_len= 5;
|
|
|
|
if (len > min_bm_pattern_len &&
|
|
*first == wild_many &&
|
|
*last == wild_many)
|
|
{
|
|
const char* tmp = first + 1;
|
|
for (; *tmp != wild_many && *tmp != wild_one && *tmp != escape; tmp++) ;
|
|
can_do_bm= (tmp == last) && !use_mb(args[0]->collation.collation);
|
|
}
|
|
if (can_do_bm)
|
|
{
|
|
pattern_len = (int) len - 2;
|
|
pattern = thd->strmake(first + 1, pattern_len);
|
|
DBUG_PRINT("info", ("Initializing pattern: '%s'", first));
|
|
int *suff = (int*) thd->alloc((int) (sizeof(int)*
|
|
((pattern_len + 1)*2+
|
|
alphabet_size)));
|
|
bmGs = suff + pattern_len + 1;
|
|
bmBc = bmGs + pattern_len + 1;
|
|
bm_compute_good_suffix_shifts(suff);
|
|
bm_compute_bad_character_shifts();
|
|
DBUG_PRINT("info",("done"));
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Item_func_like::cleanup()
|
|
{
|
|
can_do_bm= false;
|
|
escape_evaluated= false;
|
|
Item_bool_func2::cleanup();
|
|
}
|
|
|
|
/**
|
|
Evaluate the expression in the escape clause.
|
|
|
|
@param thd thread handler
|
|
@return false on success, true on failure
|
|
*/
|
|
bool Item_func_like::eval_escape_clause(THD *thd)
|
|
{
|
|
DBUG_ASSERT(!escape_evaluated);
|
|
|
|
String buf;
|
|
String *escape_str= escape_item->val_str(&buf);
|
|
if (escape_str)
|
|
{
|
|
const char *escape_str_ptr= escape_str->ptr();
|
|
if (escape_used_in_parsing && (
|
|
(((thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES) &&
|
|
escape_str->numchars() != 1) ||
|
|
escape_str->numchars() > 1)))
|
|
{
|
|
my_error(ER_WRONG_ARGUMENTS,MYF(0),"ESCAPE");
|
|
return true;
|
|
}
|
|
|
|
if (use_mb(cmp.cmp_collation.collation))
|
|
{
|
|
const CHARSET_INFO *cs= escape_str->charset();
|
|
my_wc_t wc;
|
|
int rc= cs->cset->mb_wc(cs, &wc,
|
|
(const uchar*) escape_str_ptr,
|
|
(const uchar*) escape_str_ptr +
|
|
escape_str->length());
|
|
escape= (int) (rc > 0 ? wc : '\\');
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
In the case of 8bit character set, we pass native
|
|
code instead of Unicode code as "escape" argument.
|
|
Convert to "cs" if charset of escape differs.
|
|
*/
|
|
const CHARSET_INFO *cs= cmp.cmp_collation.collation;
|
|
size_t unused;
|
|
if (escape_str->needs_conversion(escape_str->length(),
|
|
escape_str->charset(), cs, &unused))
|
|
{
|
|
char ch;
|
|
uint errors;
|
|
size_t cnvlen= copy_and_convert(&ch, 1, cs, escape_str_ptr,
|
|
escape_str->length(),
|
|
escape_str->charset(), &errors);
|
|
escape= cnvlen ? ch : '\\';
|
|
}
|
|
else
|
|
escape= escape_str_ptr ? *escape_str_ptr : '\\';
|
|
}
|
|
}
|
|
else
|
|
escape= '\\';
|
|
|
|
escape_evaluated= true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
@brief Compile regular expression.
|
|
|
|
@param[in] send_error send error message if any.
|
|
|
|
@details Make necessary character set conversion then
|
|
compile regular expression passed in the args[1].
|
|
|
|
@retval 0 success.
|
|
@retval 1 error occurred.
|
|
@retval -1 given null regular expression.
|
|
*/
|
|
|
|
int Item_func_regex::regcomp(bool send_error)
|
|
{
|
|
char buff[MAX_FIELD_WIDTH];
|
|
String tmp(buff,sizeof(buff),&my_charset_bin);
|
|
String *res= args[1]->val_str(&tmp);
|
|
int error;
|
|
|
|
if (args[1]->null_value)
|
|
return -1;
|
|
|
|
if (regex_compiled)
|
|
{
|
|
if (!stringcmp(res, &prev_regexp))
|
|
return 0;
|
|
prev_regexp.copy(*res);
|
|
my_regfree(&preg);
|
|
regex_compiled= 0;
|
|
}
|
|
|
|
if (cmp_collation.collation != regex_lib_charset)
|
|
{
|
|
/* Convert UCS2 strings to UTF8 */
|
|
uint dummy_errors;
|
|
if (conv.copy(res->ptr(), res->length(), res->charset(),
|
|
regex_lib_charset, &dummy_errors))
|
|
return 1;
|
|
res= &conv;
|
|
}
|
|
|
|
if ((error= my_regcomp(&preg, res->c_ptr_safe(),
|
|
regex_lib_flags, regex_lib_charset)))
|
|
{
|
|
if (send_error)
|
|
{
|
|
(void) my_regerror(error, &preg, buff, sizeof(buff));
|
|
my_error(ER_REGEXP_ERROR, MYF(0), buff);
|
|
}
|
|
return 1;
|
|
}
|
|
regex_compiled= 1;
|
|
return 0;
|
|
}
|
|
|
|
|
|
bool
|
|
Item_func_regex::fix_fields(THD *thd, Item **ref)
|
|
{
|
|
DBUG_ASSERT(fixed == 0);
|
|
|
|
Disable_semijoin_flattening DSF(thd->lex->current_select(), true);
|
|
|
|
if ((!args[0]->fixed &&
|
|
args[0]->fix_fields(thd, args)) || args[0]->check_cols(1) ||
|
|
(!args[1]->fixed &&
|
|
args[1]->fix_fields(thd, args + 1)) || args[1]->check_cols(1))
|
|
return TRUE; /* purecov: inspected */
|
|
with_sum_func=args[0]->with_sum_func || args[1]->with_sum_func;
|
|
with_subselect= args[0]->has_subquery() || args[1]->has_subquery();
|
|
with_stored_program= args[0]->has_stored_program() ||
|
|
args[1]->has_stored_program();
|
|
max_length= 1;
|
|
decimals= 0;
|
|
|
|
if (agg_arg_charsets_for_comparison(cmp_collation, args, 2))
|
|
return TRUE;
|
|
|
|
regex_lib_flags= (cmp_collation.collation->state &
|
|
(MY_CS_BINSORT | MY_CS_CSSORT)) ?
|
|
MY_REG_EXTENDED | MY_REG_NOSUB :
|
|
MY_REG_EXTENDED | MY_REG_NOSUB | MY_REG_ICASE;
|
|
/*
|
|
If the case of UCS2 and other non-ASCII character sets,
|
|
we will convert patterns and strings to UTF8.
|
|
*/
|
|
regex_lib_charset= (cmp_collation.collation->mbminlen > 1) ?
|
|
&my_charset_utf8_general_ci :
|
|
cmp_collation.collation;
|
|
|
|
used_tables_cache=args[0]->used_tables() | args[1]->used_tables();
|
|
not_null_tables_cache= (args[0]->not_null_tables() |
|
|
args[1]->not_null_tables());
|
|
const_item_cache=args[0]->const_item() && args[1]->const_item();
|
|
if (!regex_compiled && args[1]->const_item())
|
|
{
|
|
int comp_res= regcomp(TRUE);
|
|
if (comp_res == -1)
|
|
{ // Will always return NULL
|
|
maybe_null=1;
|
|
fixed= 1;
|
|
return FALSE;
|
|
}
|
|
else if (comp_res)
|
|
return TRUE;
|
|
regex_is_const= 1;
|
|
maybe_null= args[0]->maybe_null;
|
|
}
|
|
else
|
|
maybe_null=1;
|
|
fixed= 1;
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
longlong Item_func_regex::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
char buff[MAX_FIELD_WIDTH];
|
|
String tmp(buff,sizeof(buff),&my_charset_bin);
|
|
String *res= args[0]->val_str(&tmp);
|
|
|
|
if ((null_value= (args[0]->null_value ||
|
|
(!regex_is_const && regcomp(FALSE)))))
|
|
return 0;
|
|
|
|
if (cmp_collation.collation != regex_lib_charset)
|
|
{
|
|
/* Convert UCS2 strings to UTF8 */
|
|
uint dummy_errors;
|
|
if (conv.copy(res->ptr(), res->length(), res->charset(),
|
|
regex_lib_charset, &dummy_errors))
|
|
{
|
|
null_value= 1;
|
|
return 0;
|
|
}
|
|
res= &conv;
|
|
}
|
|
return my_regexec(&preg,res->c_ptr_safe(),0,(my_regmatch_t*) 0,0) ? 0 : 1;
|
|
}
|
|
|
|
|
|
void Item_func_regex::cleanup()
|
|
{
|
|
DBUG_ENTER("Item_func_regex::cleanup");
|
|
Item_bool_func::cleanup();
|
|
if (regex_compiled)
|
|
{
|
|
my_regfree(&preg);
|
|
regex_compiled=0;
|
|
prev_regexp.length(0);
|
|
}
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
#define likeconv(cs,A) (uchar) (cs)->sort_order[(uchar) (A)]
|
|
|
|
|
|
/**
|
|
Precomputation dependent only on pattern_len.
|
|
*/
|
|
|
|
void Item_func_like::bm_compute_suffixes(int *suff)
|
|
{
|
|
const int plm1 = pattern_len - 1;
|
|
int f = 0;
|
|
int g = plm1;
|
|
int *const splm1 = suff + plm1;
|
|
const CHARSET_INFO *cs= cmp.cmp_collation.collation;
|
|
|
|
*splm1 = pattern_len;
|
|
|
|
if (!cs->sort_order)
|
|
{
|
|
int i;
|
|
for (i = pattern_len - 2; i >= 0; i--)
|
|
{
|
|
int tmp = *(splm1 + i - f);
|
|
if (g < i && tmp < i - g)
|
|
suff[i] = tmp;
|
|
else
|
|
{
|
|
if (i < g)
|
|
g = i; // g = min(i, g)
|
|
f = i;
|
|
while (g >= 0 && pattern[g] == pattern[g + plm1 - f])
|
|
g--;
|
|
suff[i] = f - g;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int i;
|
|
for (i = pattern_len - 2; 0 <= i; --i)
|
|
{
|
|
int tmp = *(splm1 + i - f);
|
|
if (g < i && tmp < i - g)
|
|
suff[i] = tmp;
|
|
else
|
|
{
|
|
if (i < g)
|
|
g = i; // g = min(i, g)
|
|
f = i;
|
|
while (g >= 0 &&
|
|
likeconv(cs, pattern[g]) == likeconv(cs, pattern[g + plm1 - f]))
|
|
g--;
|
|
suff[i] = f - g;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
Precomputation dependent only on pattern_len.
|
|
*/
|
|
|
|
void Item_func_like::bm_compute_good_suffix_shifts(int *suff)
|
|
{
|
|
bm_compute_suffixes(suff);
|
|
|
|
int *end = bmGs + pattern_len;
|
|
int *k;
|
|
for (k = bmGs; k < end; k++)
|
|
*k = pattern_len;
|
|
|
|
int tmp;
|
|
int i;
|
|
int j = 0;
|
|
const int plm1 = pattern_len - 1;
|
|
for (i = plm1; i > -1; i--)
|
|
{
|
|
if (suff[i] == i + 1)
|
|
{
|
|
for (tmp = plm1 - i; j < tmp; j++)
|
|
{
|
|
int *tmp2 = bmGs + j;
|
|
if (*tmp2 == pattern_len)
|
|
*tmp2 = tmp;
|
|
}
|
|
}
|
|
}
|
|
|
|
int *tmp2;
|
|
for (tmp = plm1 - i; j < tmp; j++)
|
|
{
|
|
tmp2 = bmGs + j;
|
|
if (*tmp2 == pattern_len)
|
|
*tmp2 = tmp;
|
|
}
|
|
|
|
tmp2 = bmGs + plm1;
|
|
for (i = 0; i <= pattern_len - 2; i++)
|
|
*(tmp2 - suff[i]) = plm1 - i;
|
|
}
|
|
|
|
|
|
/**
|
|
Precomputation dependent on pattern_len.
|
|
*/
|
|
|
|
void Item_func_like::bm_compute_bad_character_shifts()
|
|
{
|
|
int *i;
|
|
int *end = bmBc + alphabet_size;
|
|
int j;
|
|
const int plm1 = pattern_len - 1;
|
|
const CHARSET_INFO *cs= cmp.cmp_collation.collation;
|
|
|
|
for (i = bmBc; i < end; i++)
|
|
*i = pattern_len;
|
|
|
|
if (!cs->sort_order)
|
|
{
|
|
for (j = 0; j < plm1; j++)
|
|
bmBc[(uchar) pattern[j]] = plm1 - j;
|
|
}
|
|
else
|
|
{
|
|
for (j = 0; j < plm1; j++)
|
|
bmBc[likeconv(cs,pattern[j])] = plm1 - j;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
Search for pattern in text.
|
|
|
|
@return
|
|
returns true/false for match/no match
|
|
*/
|
|
|
|
bool Item_func_like::bm_matches(const char* text, size_t text_len) const
|
|
{
|
|
int bcShift;
|
|
int shift = pattern_len;
|
|
int j = 0;
|
|
const CHARSET_INFO *cs= cmp.cmp_collation.collation;
|
|
|
|
const int plm1= pattern_len - 1;
|
|
const int tlmpl= text_len - pattern_len;
|
|
|
|
/* Searching */
|
|
if (!cs->sort_order)
|
|
{
|
|
while (j <= tlmpl)
|
|
{
|
|
int i;
|
|
|
|
for (i= plm1; (i >= 0) && (pattern[i] == text[i + j]) ;--i) {}
|
|
|
|
if (i < 0)
|
|
return true;
|
|
else
|
|
{
|
|
bcShift= bmBc[(uchar) text[i + j]] - plm1 + i;
|
|
shift= max(bcShift, bmGs[i]);
|
|
}
|
|
j+= shift;
|
|
}
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
while (j <= tlmpl)
|
|
{
|
|
int i;
|
|
|
|
for (i= plm1;
|
|
(i >= 0) && likeconv(cs,pattern[i]) == likeconv(cs,text[i + j]);
|
|
--i) {}
|
|
|
|
if (i < 0)
|
|
return true;
|
|
else
|
|
{
|
|
bcShift= bmBc[likeconv(cs, text[i + j])] - plm1 + i;
|
|
shift= max(bcShift, bmGs[i]);
|
|
}
|
|
j+= shift;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
float Item_func_xor::get_filtering_effect(table_map filter_for_table,
|
|
table_map read_tables,
|
|
const MY_BITMAP *fields_to_ignore,
|
|
double rows_in_table)
|
|
{
|
|
DBUG_ASSERT(arg_count == 2);
|
|
|
|
const float filter0=
|
|
args[0]->get_filtering_effect(filter_for_table, read_tables,
|
|
fields_to_ignore, rows_in_table);
|
|
if (filter0 == COND_FILTER_ALLPASS)
|
|
return COND_FILTER_ALLPASS;
|
|
|
|
const float filter1=
|
|
args[1]->get_filtering_effect(filter_for_table, read_tables,
|
|
fields_to_ignore, rows_in_table);
|
|
|
|
if (filter1 == COND_FILTER_ALLPASS)
|
|
return COND_FILTER_ALLPASS;
|
|
|
|
/*
|
|
Calculated as "exactly one of independent events":
|
|
P(A and not B) + P(B and not A) = P(A) + P(B) - 2 * P(A) * P(B)
|
|
*/
|
|
return filter0 + filter1 - (2 * filter0 * filter1);
|
|
}
|
|
|
|
/**
|
|
Make a logical XOR of the arguments.
|
|
|
|
If either operator is NULL, return NULL.
|
|
|
|
@todo
|
|
(low priority) Change this to be optimized as: @n
|
|
A XOR B -> (A) == 1 AND (B) <> 1) OR (A <> 1 AND (B) == 1) @n
|
|
To be able to do this, we would however first have to extend the MySQL
|
|
range optimizer to handle OR better.
|
|
|
|
@note
|
|
As we don't do any index optimization on XOR this is not going to be
|
|
very fast to use.
|
|
*/
|
|
|
|
longlong Item_func_xor::val_int()
|
|
{
|
|
DBUG_ASSERT(fixed == 1);
|
|
int result= 0;
|
|
null_value= false;
|
|
for (uint i= 0; i < arg_count; i++)
|
|
{
|
|
result^= (args[i]->val_int() != 0);
|
|
if (args[i]->null_value)
|
|
{
|
|
null_value= true;
|
|
return 0;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
Apply NOT transformation to the item and return a new one.
|
|
|
|
|
|
Transform the item using next rules:
|
|
@verbatim
|
|
a AND b AND ... -> NOT(a) OR NOT(b) OR ...
|
|
a OR b OR ... -> NOT(a) AND NOT(b) AND ...
|
|
NOT(a) -> a
|
|
a = b -> a != b
|
|
a != b -> a = b
|
|
a < b -> a >= b
|
|
a >= b -> a < b
|
|
a > b -> a <= b
|
|
a <= b -> a > b
|
|
IS NULL(a) -> IS NOT NULL(a)
|
|
IS NOT NULL(a) -> IS NULL(a)
|
|
@endverbatim
|
|
|
|
@param thd thread handler
|
|
|
|
@return
|
|
New item or
|
|
NULL if we cannot apply NOT transformation (see Item::neg_transformer()).
|
|
*/
|
|
|
|
Item *Item_func_not::neg_transformer(THD *thd) /* NOT(x) -> x */
|
|
{
|
|
return args[0];
|
|
}
|
|
|
|
|
|
Item *Item_bool_rowready_func2::neg_transformer(THD *thd)
|
|
{
|
|
Item *item= negated_item();
|
|
return item;
|
|
}
|
|
|
|
/**
|
|
XOR can be negated by negating one of the operands:
|
|
|
|
NOT (a XOR b) => (NOT a) XOR b
|
|
=> a XOR (NOT b)
|
|
|
|
@param thd Thread handle
|
|
@return New negated item
|
|
*/
|
|
Item *Item_func_xor::neg_transformer(THD *thd)
|
|
{
|
|
Item *neg_operand;
|
|
Item_func_xor *new_item;
|
|
if ((neg_operand= args[0]->neg_transformer(thd)))
|
|
// args[0] has neg_tranformer
|
|
new_item= new(thd->mem_root) Item_func_xor(neg_operand, args[1]);
|
|
else if ((neg_operand= args[1]->neg_transformer(thd)))
|
|
// args[1] has neg_tranformer
|
|
new_item= new(thd->mem_root) Item_func_xor(args[0], neg_operand);
|
|
else
|
|
{
|
|
neg_operand= new(thd->mem_root) Item_func_not(args[0]);
|
|
new_item= new(thd->mem_root) Item_func_xor(neg_operand, args[1]);
|
|
}
|
|
return new_item;
|
|
}
|
|
|
|
|
|
/**
|
|
a IS NULL -> a IS NOT NULL.
|
|
*/
|
|
Item *Item_func_isnull::neg_transformer(THD *thd)
|
|
{
|
|
Item *item= new Item_func_isnotnull(args[0]);
|
|
return item;
|
|
}
|
|
|
|
|
|
/**
|
|
a IS NOT NULL -> a IS NULL.
|
|
*/
|
|
Item *Item_func_isnotnull::neg_transformer(THD *thd)
|
|
{
|
|
Item *item= new Item_func_isnull(args[0]);
|
|
return item;
|
|
}
|
|
|
|
|
|
Item *Item_cond_and::neg_transformer(THD *thd) /* NOT(a AND b AND ...) -> */
|
|
/* NOT a OR NOT b OR ... */
|
|
{
|
|
neg_arguments(thd);
|
|
Item *item= new Item_cond_or(list);
|
|
return item;
|
|
}
|
|
|
|
|
|
Item *Item_cond_or::neg_transformer(THD *thd) /* NOT(a OR b OR ...) -> */
|
|
/* NOT a AND NOT b AND ... */
|
|
{
|
|
neg_arguments(thd);
|
|
Item *item= new Item_cond_and(list);
|
|
return item;
|
|
}
|
|
|
|
|
|
Item *Item_func_nop_all::neg_transformer(THD *thd)
|
|
{
|
|
/* "NOT (e $cmp$ ANY (SELECT ...)) -> e $rev_cmp$" ALL (SELECT ...) */
|
|
Item_func_not_all *new_item= new Item_func_not_all(args[0]);
|
|
Item_allany_subselect *allany= (Item_allany_subselect*)args[0];
|
|
allany->func= allany->func_creator(FALSE);
|
|
allany->all= !allany->all;
|
|
allany->upper_item= new_item;
|
|
return new_item;
|
|
}
|
|
|
|
Item *Item_func_not_all::neg_transformer(THD *thd)
|
|
{
|
|
/* "NOT (e $cmp$ ALL (SELECT ...)) -> e $rev_cmp$" ANY (SELECT ...) */
|
|
Item_func_nop_all *new_item= new Item_func_nop_all(args[0]);
|
|
Item_allany_subselect *allany= (Item_allany_subselect*)args[0];
|
|
allany->all= !allany->all;
|
|
allany->func= allany->func_creator(TRUE);
|
|
allany->upper_item= new_item;
|
|
return new_item;
|
|
}
|
|
|
|
Item *Item_func_eq::negated_item() /* a = b -> a != b */
|
|
{
|
|
return new Item_func_ne(args[0], args[1]);
|
|
}
|
|
|
|
|
|
Item *Item_func_ne::negated_item() /* a != b -> a = b */
|
|
{
|
|
return new Item_func_eq(args[0], args[1]);
|
|
}
|
|
|
|
|
|
Item *Item_func_lt::negated_item() /* a < b -> a >= b */
|
|
{
|
|
return new Item_func_ge(args[0], args[1]);
|
|
}
|
|
|
|
|
|
Item *Item_func_ge::negated_item() /* a >= b -> a < b */
|
|
{
|
|
return new Item_func_lt(args[0], args[1]);
|
|
}
|
|
|
|
|
|
Item *Item_func_gt::negated_item() /* a > b -> a <= b */
|
|
{
|
|
return new Item_func_le(args[0], args[1]);
|
|
}
|
|
|
|
|
|
Item *Item_func_le::negated_item() /* a <= b -> a > b */
|
|
{
|
|
return new Item_func_gt(args[0], args[1]);
|
|
}
|
|
|
|
/**
|
|
just fake method, should never be called.
|
|
*/
|
|
Item *Item_bool_rowready_func2::negated_item()
|
|
{
|
|
DBUG_ASSERT(0);
|
|
return 0;
|
|
}
|
|
|
|
Item_equal::Item_equal(Item_field *f1, Item_field *f2)
|
|
: Item_bool_func(), const_item(0), eval_item(0), cond_false(0),
|
|
compare_as_dates(FALSE)
|
|
{
|
|
const_item_cache= 0;
|
|
fields.push_back(f1);
|
|
fields.push_back(f2);
|
|
}
|
|
|
|
Item_equal::Item_equal(Item *c, Item_field *f)
|
|
: Item_bool_func(), eval_item(0), cond_false(0)
|
|
{
|
|
const_item_cache= 0;
|
|
fields.push_back(f);
|
|
const_item= c;
|
|
compare_as_dates= f->is_temporal_with_date();
|
|
}
|
|
|
|
|
|
Item_equal::Item_equal(Item_equal *item_equal)
|
|
: Item_bool_func(), eval_item(0), cond_false(0)
|
|
{
|
|
const_item_cache= 0;
|
|
List_iterator_fast<Item_field> li(item_equal->fields);
|
|
Item_field *item;
|
|
while ((item= li++))
|
|
{
|
|
fields.push_back(item);
|
|
}
|
|
const_item= item_equal->const_item;
|
|
compare_as_dates= item_equal->compare_as_dates;
|
|
cond_false= item_equal->cond_false;
|
|
}
|
|
|
|
|
|
bool Item_equal::compare_const(THD *thd, Item *c)
|
|
{
|
|
if (compare_as_dates)
|
|
{
|
|
cmp.set_datetime_cmp_func(this, &c, &const_item);
|
|
cond_false= cmp.compare();
|
|
}
|
|
else
|
|
{
|
|
Item_func_eq *func= new Item_func_eq(c, const_item);
|
|
if (func == NULL)
|
|
return true;
|
|
if (func->set_cmp_func())
|
|
return true;
|
|
func->quick_fix_field();
|
|
cond_false= !func->val_int();
|
|
}
|
|
if (thd->is_error())
|
|
return true;
|
|
if (cond_false)
|
|
const_item_cache= 1;
|
|
return false;
|
|
}
|
|
|
|
|
|
bool Item_equal::add(THD *thd, Item *c, Item_field *f)
|
|
{
|
|
if (cond_false)
|
|
return false;
|
|
if (!const_item)
|
|
{
|
|
DBUG_ASSERT(f);
|
|
const_item= c;
|
|
compare_as_dates= f->is_temporal_with_date();
|
|
return false;
|
|
}
|
|
return compare_const(thd, c);
|
|
}
|
|
|
|
|
|
bool Item_equal::add(THD *thd, Item *c)
|
|
{
|
|
if (cond_false)
|
|
return false;
|
|
if (!const_item)
|
|
{
|
|
const_item= c;
|
|
return false;
|
|
}
|
|
return compare_const(thd, c);
|
|
}
|
|
|
|
void Item_equal::add(Item_field *f)
|
|
{
|
|
fields.push_back(f);
|
|
}
|
|
|
|
uint Item_equal::members()
|
|
{
|
|
return fields.elements;
|
|
}
|
|
|
|
|
|
/**
|
|
Check whether a field is referred in the multiple equality.
|
|
|
|
The function checks whether field is occurred in the Item_equal object .
|
|
|
|
@param field field whose occurrence is to be checked
|
|
|
|
@retval
|
|
1 if nultiple equality contains a reference to field
|
|
@retval
|
|
0 otherwise
|
|
*/
|
|
|
|
bool Item_equal::contains(Field *field)
|
|
{
|
|
List_iterator_fast<Item_field> it(fields);
|
|
Item_field *item;
|
|
while ((item= it++))
|
|
{
|
|
if (field->eq(item->field))
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
Join members of another Item_equal object.
|
|
|
|
The function actually merges two multiple equalities.
|
|
After this operation the Item_equal object additionally contains
|
|
the field items of another item of the type Item_equal.
|
|
If the optional constant items are not equal the cond_false flag is
|
|
set to 1.
|
|
|
|
@param thd thread handler
|
|
@param item multiple equality whose members are to be joined
|
|
|
|
@returns false if success, true if error
|
|
*/
|
|
|
|
bool Item_equal::merge(THD *thd, Item_equal *item)
|
|
{
|
|
fields.concat(&item->fields);
|
|
Item *c= item->const_item;
|
|
if (c)
|
|
{
|
|
/*
|
|
The flag cond_false will be set to 1 after this, if
|
|
the multiple equality already contains a constant and its
|
|
value is not equal to the value of c.
|
|
*/
|
|
if (add(thd, c))
|
|
return true;
|
|
}
|
|
cond_false|= item->cond_false;
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
Order field items in multiple equality according to a sorting criteria.
|
|
|
|
The function perform ordering of the field items in the Item_equal
|
|
object according to the criteria determined by the cmp callback parameter.
|
|
If cmp(item_field1,item_field2,arg)<0 than item_field1 must be
|
|
placed after item_fiel2.
|
|
|
|
The function sorts field items by the exchange sort algorithm.
|
|
The list of field items is looked through and whenever two neighboring
|
|
members follow in a wrong order they are swapped. This is performed
|
|
again and again until we get all members in a right order.
|
|
|
|
@param compare function to compare field item
|
|
@param arg context extra parameter for the cmp function
|
|
*/
|
|
|
|
void Item_equal::sort(Item_field_cmpfunc compare, void *arg)
|
|
{
|
|
fields.sort((Node_cmp_func)compare, arg);
|
|
}
|
|
|
|
|
|
/**
|
|
Check appearance of new constant items in the multiple equality object.
|
|
|
|
The function checks appearance of new constant items among
|
|
the members of multiple equalities. Each new constant item is
|
|
compared with the designated constant item if there is any in the
|
|
multiple equality. If there is none the first new constant item
|
|
becomes designated.
|
|
|
|
@param thd thread handler
|
|
|
|
@returns false if success, true if error
|
|
*/
|
|
|
|
bool Item_equal::update_const(THD *thd)
|
|
{
|
|
List_iterator<Item_field> it(fields);
|
|
Item *item;
|
|
while ((item= it++))
|
|
{
|
|
if (item->const_item() &&
|
|
/*
|
|
Don't propagate constant status of outer-joined column.
|
|
Such a constant status here is a result of:
|
|
a) empty outer-joined table: in this case such a column has a
|
|
value of NULL; but at the same time other arguments of
|
|
Item_equal don't have to be NULLs and the value of the whole
|
|
multiple equivalence expression doesn't have to be NULL or FALSE
|
|
because of the outer join nature;
|
|
or
|
|
b) outer-joined table contains only 1 row: the result of
|
|
this column is equal to a row field value *or* NULL.
|
|
Both values are inacceptable as Item_equal constants.
|
|
*/
|
|
!item->is_outer_field())
|
|
{
|
|
it.remove();
|
|
if (add(thd, item))
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Item_equal::fix_fields(THD *thd, Item **ref)
|
|
{
|
|
List_iterator_fast<Item_field> li(fields);
|
|
Item *item;
|
|
not_null_tables_cache= used_tables_cache= 0;
|
|
const_item_cache= 0;
|
|
while ((item= li++))
|
|
{
|
|
used_tables_cache|= item->used_tables();
|
|
not_null_tables_cache|= item->not_null_tables();
|
|
maybe_null|= item->maybe_null;
|
|
}
|
|
fix_length_and_dec();
|
|
fixed= 1;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
Get filtering effect for multiple equalities, i.e.
|
|
"tx.col = value_1 = ... = value_n" where value_i may be a
|
|
constant, a column etc.
|
|
|
|
The multiple equality only contributes to the filtering effect for
|
|
'filter_for_table' if
|
|
a) A column in 'filter_for_table' is referred to
|
|
b) at least one value_i is a constant or a column in a table
|
|
already read
|
|
|
|
If this multiple equality refers to more than one column in
|
|
'filter_for_table', the predicates on all these fields will
|
|
contribute to the filtering effect.
|
|
*/
|
|
float Item_equal::get_filtering_effect(table_map filter_for_table,
|
|
table_map read_tables,
|
|
const MY_BITMAP *fields_to_ignore,
|
|
double rows_in_table)
|
|
{
|
|
// This predicate does not refer to a column in 'filter_for_table'
|
|
if (!(used_tables() & filter_for_table))
|
|
return COND_FILTER_ALLPASS;
|
|
|
|
float filter= COND_FILTER_ALLPASS;
|
|
/*
|
|
Keep track of whether or not a usable value that is either a
|
|
constant or a column in an already read table has been found.
|
|
*/
|
|
bool found_comparable= false;
|
|
|
|
// Is there a constant that this multiple equality is equal to?
|
|
if (const_item)
|
|
found_comparable= true;
|
|
|
|
List_iterator<Item_field> it(fields);
|
|
|
|
Item_field *cur_field;
|
|
/*
|
|
Calculate filtering effect for all applicable fields. If this
|
|
item has multiple fields from 'filter_for_table', each of these
|
|
fields will contribute to the filtering effect.
|
|
*/
|
|
while ((cur_field= it++))
|
|
{
|
|
if (cur_field->used_tables() & read_tables)
|
|
{
|
|
// cur_field is a field in a table earlier in the join sequence.
|
|
found_comparable= true;
|
|
}
|
|
else if (cur_field->used_tables() == filter_for_table)
|
|
{
|
|
if (bitmap_is_set(fields_to_ignore, cur_field->field->field_index))
|
|
{
|
|
/*
|
|
cur_field is a field in 'filter_for_table', but it is a
|
|
field which already contributes to the filtering effect.
|
|
Its value can still be used as a constant if another column
|
|
in the same table is referred to in this multiple equality.
|
|
*/
|
|
found_comparable= true;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
cur_field is a field in 'filter_for_table', and it's not one
|
|
of the fields that must be ignored
|
|
*/
|
|
float cur_filter=
|
|
cur_field->get_cond_filter_default_probability(rows_in_table,
|
|
COND_FILTER_EQUALITY);
|
|
|
|
// Use index statistics if available for this field
|
|
if (!cur_field->field->key_start.is_clear_all())
|
|
{
|
|
// cur_field is indexed - there may be statistics for it.
|
|
const TABLE *tab= cur_field->field->table;
|
|
|
|
for (uint j= 0; j < tab->s->keys; j++)
|
|
{
|
|
if (cur_field->field->key_start.is_set(j) &&
|
|
tab->key_info[j].has_records_per_key(0))
|
|
{
|
|
cur_filter= static_cast<float>(tab->key_info[j].records_per_key(0) / rows_in_table);
|
|
break;
|
|
}
|
|
}
|
|
/*
|
|
Since rec_per_key and rows_per_table are calculated at
|
|
different times, their values may not be in synch and thus
|
|
it is possible that cur_filter is greater than 1.0 if
|
|
rec_per_key is outdated. Force the filter to 1.0 in such
|
|
cases.
|
|
*/
|
|
if (cur_filter >= 1.0)
|
|
cur_filter= 1.0f;
|
|
}
|
|
|
|
filter*= cur_filter;
|
|
}
|
|
}
|
|
}
|
|
return found_comparable ? filter : COND_FILTER_ALLPASS;
|
|
}
|
|
|
|
void Item_equal::update_used_tables()
|
|
{
|
|
List_iterator_fast<Item_field> li(fields);
|
|
Item *item;
|
|
not_null_tables_cache= used_tables_cache= 0;
|
|
if ((const_item_cache= cond_false))
|
|
return;
|
|
with_subselect= false;
|
|
with_stored_program= false;
|
|
while ((item=li++))
|
|
{
|
|
item->update_used_tables();
|
|
used_tables_cache|= item->used_tables();
|
|
not_null_tables_cache|= item->not_null_tables();
|
|
/* see commentary at Item_equal::update_const() */
|
|
const_item_cache&= item->const_item() && !item->is_outer_field();
|
|
with_subselect|= item->has_subquery();
|
|
with_stored_program|= item->has_stored_program();
|
|
}
|
|
}
|
|
|
|
longlong Item_equal::val_int()
|
|
{
|
|
Item_field *item_field;
|
|
if (cond_false)
|
|
return 0;
|
|
List_iterator_fast<Item_field> it(fields);
|
|
Item *item= const_item ? const_item : it++;
|
|
eval_item->store_value(item);
|
|
if ((null_value= item->null_value))
|
|
return 0;
|
|
while ((item_field= it++))
|
|
{
|
|
/* Skip fields of non-const tables. They haven't been read yet */
|
|
if (item_field->field->table->const_table)
|
|
{
|
|
const int rc= eval_item->cmp(item_field);
|
|
if ((rc == TRUE) || (null_value= (rc == UNKNOWN)))
|
|
return 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
void Item_equal::fix_length_and_dec()
|
|
{
|
|
Item *item= get_first();
|
|
eval_item= cmp_item::get_comparator(item->result_type(), item,
|
|
item->collation.collation);
|
|
}
|
|
|
|
bool Item_equal::walk(Item_processor processor, enum_walk walk, uchar *arg)
|
|
{
|
|
if ((walk & WALK_PREFIX) && (this->*processor)(arg))
|
|
return true;
|
|
|
|
List_iterator_fast<Item_field> it(fields);
|
|
Item *item;
|
|
while ((item= it++))
|
|
{
|
|
if (item->walk(processor, walk, arg))
|
|
return true;
|
|
}
|
|
|
|
return (walk & WALK_POSTFIX) && (this->*processor)(arg);
|
|
}
|
|
|
|
Item *Item_equal::transform(Item_transformer transformer, uchar *arg)
|
|
{
|
|
DBUG_ASSERT(!current_thd->stmt_arena->is_stmt_prepare());
|
|
|
|
List_iterator<Item_field> it(fields);
|
|
Item *item;
|
|
while ((item= it++))
|
|
{
|
|
Item *new_item= item->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 (new_item != item)
|
|
current_thd->change_item_tree((Item **) it.ref(), new_item);
|
|
}
|
|
return Item_func::transform(transformer, arg);
|
|
}
|
|
|
|
void Item_equal::print(String *str, enum_query_type query_type)
|
|
{
|
|
str->append(func_name());
|
|
str->append('(');
|
|
List_iterator_fast<Item_field> it(fields);
|
|
Item *item;
|
|
if (const_item)
|
|
const_item->print(str, query_type);
|
|
else
|
|
{
|
|
item= it++;
|
|
item->print(str, query_type);
|
|
}
|
|
while ((item= it++))
|
|
{
|
|
str->append(',');
|
|
str->append(' ');
|
|
item->print(str, query_type);
|
|
}
|
|
str->append(')');
|
|
}
|
|
|
|
|
|
longlong Item_func_trig_cond::val_int()
|
|
{
|
|
if (trig_var == NULL)
|
|
{
|
|
DBUG_ASSERT(m_join != NULL && m_idx >= 0);
|
|
switch(trig_type)
|
|
{
|
|
case IS_NOT_NULL_COMPL:
|
|
trig_var= &m_join->qep_tab[m_idx].not_null_compl;
|
|
break;
|
|
case FOUND_MATCH:
|
|
trig_var= &m_join->qep_tab[m_idx].found;
|
|
break;
|
|
default:
|
|
DBUG_ASSERT(false); /* purecov: inspected */
|
|
return 0;
|
|
}
|
|
}
|
|
return *trig_var ? args[0]->val_int() : 1;
|
|
}
|
|
|
|
void Item_func_trig_cond::print(String *str, enum_query_type query_type)
|
|
{
|
|
/*
|
|
Print:
|
|
<if>(<property><(optional list of source tables)>, condition, TRUE)
|
|
which means: if a certain property (<property>) is true, then return
|
|
the value of <condition>, else return TRUE. If source tables are
|
|
present, they are the owner of the property.
|
|
*/
|
|
str->append(func_name());
|
|
str->append("(");
|
|
switch(trig_type)
|
|
{
|
|
case IS_NOT_NULL_COMPL:
|
|
str->append("is_not_null_compl");
|
|
break;
|
|
case FOUND_MATCH:
|
|
str->append("found_match");
|
|
break;
|
|
case OUTER_FIELD_IS_NOT_NULL:
|
|
str->append("outer_field_is_not_null");
|
|
break;
|
|
default:
|
|
DBUG_ASSERT(0);
|
|
}
|
|
if (m_join != NULL)
|
|
{
|
|
/*
|
|
Item printing is done at various stages of optimization, so there can
|
|
be a JOIN_TAB or a QEP_TAB.
|
|
*/
|
|
TABLE *table, *last_inner_table;
|
|
plan_idx last_inner;
|
|
if (m_join->qep_tab)
|
|
{
|
|
QEP_TAB *qep_tab= &m_join->qep_tab[m_idx];
|
|
table= qep_tab->table();
|
|
last_inner= qep_tab->last_inner();
|
|
last_inner_table= m_join->qep_tab[last_inner].table();
|
|
}
|
|
else
|
|
{
|
|
JOIN_TAB *join_tab= m_join->best_ref[m_idx];
|
|
table= join_tab->table();
|
|
last_inner= join_tab->last_inner();
|
|
last_inner_table= m_join->best_ref[last_inner]->table();
|
|
}
|
|
str->append("(");
|
|
str->append(table->alias);
|
|
if (last_inner != m_idx)
|
|
{
|
|
/* case of t1 LEFT JOIN (t2,t3,...): print range of inner tables */
|
|
str->append("..");
|
|
str->append(last_inner_table->alias);
|
|
}
|
|
str->append(")");
|
|
}
|
|
str->append(", ");
|
|
args[0]->print(str, query_type);
|
|
str->append(", true)");
|
|
}
|
|
|
|
|
|
/**
|
|
Get item that can be substituted for the supplied item.
|
|
|
|
@param field field item to get substitution field for, which must be
|
|
present within the multiple equality itself.
|
|
|
|
@retval Found substitution item in the multiple equality.
|
|
|
|
@details Get the first item of multiple equality that can be substituted
|
|
for the given field item. In order to make semijoin materialization strategy
|
|
work correctly we can't propagate equal fields between a materialized
|
|
semijoin and the outer query (or any other semijoin) unconditionally.
|
|
Thus the field is returned according to the following rules:
|
|
|
|
1) If the given field belongs to a materialized semijoin then the
|
|
first field in the multiple equality which belongs to the same semijoin
|
|
is returned.
|
|
2) If the given field doesn't belong to a materialized semijoin then
|
|
the first field in the multiple equality is returned.
|
|
*/
|
|
|
|
Item_field* Item_equal::get_subst_item(const Item_field *field)
|
|
{
|
|
DBUG_ASSERT(field != NULL);
|
|
|
|
const JOIN_TAB *field_tab= field->field->table->reginfo.join_tab;
|
|
|
|
/*
|
|
field_tab is NULL if this function was not called from
|
|
JOIN::optimize() but from e.g. mysql_delete() or mysql_update().
|
|
In these cases there is only one table and no semijoin
|
|
*/
|
|
if (field_tab &&
|
|
sj_is_materialize_strategy(field_tab->get_sj_strategy()))
|
|
{
|
|
/*
|
|
It's a field from a materialized semijoin. We can substitute it only
|
|
with a field from the same semijoin.
|
|
|
|
Example: suppose we have a join_tab order:
|
|
|
|
ot1 ot2 <subquery> ot3 SJM(it1 it2 it3)
|
|
|
|
<subquery> is the temporary table that is materialized from the join
|
|
of it1, it2 and it3.
|
|
|
|
and equality ot2.col = <subquery>.col = it1.col = it2.col
|
|
|
|
If we're looking for best substitute for 'it2.col', we must pick it1.col
|
|
and not ot2.col. it2.col is evaluated while performing materialization,
|
|
when the outer tables are not available in the execution.
|
|
|
|
Note that subquery materialization does not have the same problem:
|
|
even though IN->EXISTS has injected equalities involving outer query's
|
|
expressions, it has wrapped those expressions in variants of Item_ref,
|
|
never Item_field, so they can be part of an Item_equal only if they are
|
|
constant (in which case there is no problem with choosing them below);
|
|
@see check_simple_equality().
|
|
*/
|
|
List_iterator<Item_field> it(fields);
|
|
Item_field *item;
|
|
plan_idx first= field_tab->first_sj_inner(), last= field_tab->last_sj_inner();
|
|
|
|
while ((item= it++))
|
|
{
|
|
plan_idx idx= item->field->table->reginfo.join_tab->idx();
|
|
if (idx >= first && idx <= last)
|
|
return item;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
The field is not in a materialized semijoin nest. We can return
|
|
the first field in the multiple equality.
|
|
|
|
Example: suppose we have a join_tab order with MaterializeLookup:
|
|
|
|
ot1 ot2 <subquery> SJM(it1 it2)
|
|
|
|
Here we should always pick the first field in the multiple equality,
|
|
as this will be present before all other dependent fields.
|
|
|
|
Example: suppose we have a join_tab order with MaterializeScan:
|
|
|
|
<subquery> ot1 ot2 SJM(it1 it2)
|
|
|
|
and equality <subquery>.col = ot2.col = ot1.col = it2.col.
|
|
|
|
When looking for best substitute for ot2.col, we should pick
|
|
<subquery>.col, because column values from the inner materialized tables
|
|
are copied to the temporary table <subquery>, and when we run the scan,
|
|
field values are read into this table's field buffers.
|
|
*/
|
|
return fields.head();
|
|
}
|
|
DBUG_ASSERT(FALSE); // Should never get here.
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
Transform an Item_equal object after having added a table that
|
|
represents a materialized semi-join.
|
|
|
|
@details
|
|
If the multiple equality represented by the Item_equal object contains
|
|
a field from the subquery that was used to create the materialized table,
|
|
add the corresponding key field from the materialized table to the
|
|
multiple equality.
|
|
@see JOIN::update_equalities_for_sjm() for the reason.
|
|
*/
|
|
|
|
Item* Item_equal::equality_substitution_transformer(uchar *arg)
|
|
{
|
|
TABLE_LIST *sj_nest= reinterpret_cast<TABLE_LIST *>(arg);
|
|
List_iterator<Item_field> it(fields);
|
|
List<Item_field> added_fields;
|
|
Item_field *item;
|
|
// Iterate over the fields in the multiple equality
|
|
while ((item= it++))
|
|
{
|
|
// Skip fields that do not come from materialized subqueries
|
|
const JOIN_TAB *tab= item->field->table->reginfo.join_tab;
|
|
if (!tab || !sj_is_materialize_strategy(tab->get_sj_strategy()))
|
|
continue;
|
|
|
|
// Iterate over the fields selected from the subquery
|
|
List_iterator<Item> mit(sj_nest->nested_join->sj_inner_exprs);
|
|
Item *existing;
|
|
uint fieldno= 0;
|
|
while ((existing= mit++))
|
|
{
|
|
if (existing->real_item()->eq(item, false))
|
|
added_fields.push_back(sj_nest->nested_join->sjm.mat_fields[fieldno]);
|
|
fieldno++;
|
|
}
|
|
}
|
|
fields.concat(&added_fields);
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
Replace arg of Item_func_eq object after having added a table that
|
|
represents a materialized semi-join.
|
|
|
|
@details
|
|
The right argument of an injected semi-join equality (which comes from
|
|
the select list of the subquery) is replaced with the corresponding
|
|
column from the materialized temporary table, if the left and right
|
|
arguments are not from the same semi-join nest.
|
|
@see JOIN::update_equalities_for_sjm() for why this is needed.
|
|
*/
|
|
Item* Item_func_eq::equality_substitution_transformer(uchar *arg)
|
|
{
|
|
TABLE_LIST *sj_nest= reinterpret_cast<TABLE_LIST *>(arg);
|
|
|
|
// Iterate over the fields selected from the subquery
|
|
List_iterator<Item> mit(sj_nest->nested_join->sj_inner_exprs);
|
|
Item *existing;
|
|
uint fieldno= 0;
|
|
while ((existing= mit++))
|
|
{
|
|
if (existing->real_item()->eq(args[1], false) &&
|
|
(args[0]->used_tables() & ~sj_nest->sj_inner_tables))
|
|
current_thd->change_item_tree(args+1,
|
|
sj_nest->nested_join->sjm.mat_fields[fieldno]);
|
|
fieldno++;
|
|
}
|
|
return this;
|
|
}
|
|
|
|
float Item_func_eq::get_filtering_effect(table_map filter_for_table,
|
|
table_map read_tables,
|
|
const MY_BITMAP *fields_to_ignore,
|
|
double rows_in_table)
|
|
{
|
|
const Item_field* fld=
|
|
contributes_to_filter(read_tables, filter_for_table, fields_to_ignore);
|
|
if (!fld)
|
|
return COND_FILTER_ALLPASS;
|
|
|
|
return fld->get_cond_filter_default_probability(rows_in_table,
|
|
COND_FILTER_EQUALITY);
|
|
}
|
|
|
|
|
|
bool Item_func_any_value::aggregate_check_group(uchar *arg)
|
|
{
|
|
Group_check *gc= reinterpret_cast<Group_check *>(arg);
|
|
if (gc->is_stopped(this))
|
|
return false;
|
|
gc->stop_at(this);
|
|
return false;
|
|
}
|
|
|
|
|
|
bool Item_func_any_value::aggregate_check_distinct(uchar *arg)
|
|
{
|
|
Distinct_check *dc= reinterpret_cast<Distinct_check *>(arg);
|
|
if (dc->is_stopped(this))
|
|
return false;
|
|
dc->stop_at(this);
|
|
return false;
|
|
}
|