Commit 94768542 authored by Alexander Barkov's avatar Alexander Barkov

MDEV-9369 IN operator with ( num, NULL ) gives inconsistent result

Based on this commit into MySQL-5.7:
> commit 8e51b845aafc8b4cdebd763c8aebda262ac2d4cd
> Author: Guilhem Bichot <guilhem.bichot@oracle.com>
> Date:   Mon Nov 4 15:44:55 2013 +0100
>
>    Bug#13944462 'NULL IN (XX)' RETURNS WRONG RESULTS
parent 7cb16dc2
...@@ -50,6 +50,12 @@ id select_type table type possible_keys key key_len ref rows filtered Extra ...@@ -50,6 +50,12 @@ id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL No tables used 1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL No tables used
Warnings: Warnings:
Note 1003 select ((1,2,(3,4)) in ((3,2,(3,4)),(1,2,(3,NULL)))) AS `row(1,2,row(3,4)) IN (row(3,2,row(3,4)), row(1,2,row(3,NULL)))` Note 1003 select ((1,2,(3,4)) in ((3,2,(3,4)),(1,2,(3,NULL)))) AS `row(1,2,row(3,4)) IN (row(3,2,row(3,4)), row(1,2,row(3,NULL)))`
select row(1,2,row(3,null)) IN (row(3,2,row(3,4)), row(1,2,row(4,5)));
row(1,2,row(3,null)) IN (row(3,2,row(3,4)), row(1,2,row(4,5)))
0
select row(1,2,row(3,null)) IN (row(3,2,row(3,4)), row(1,2,row(3,5)));
row(1,2,row(3,null)) IN (row(3,2,row(3,4)), row(1,2,row(3,5)))
NULL
SELECT (1,2,3)=(0,NULL,3); SELECT (1,2,3)=(0,NULL,3);
(1,2,3)=(0,NULL,3) (1,2,3)=(0,NULL,3)
0 0
...@@ -130,7 +136,7 @@ ROW(c,2,3) IN(row(1,b,a), row(2,3,1)) ...@@ -130,7 +136,7 @@ ROW(c,2,3) IN(row(1,b,a), row(2,3,1))
0 0
0 0
1 1
NULL 0
select ROW(a,b,c) IN(row(1,2,3), row(3,2,1)) from t1; select ROW(a,b,c) IN(row(1,2,3), row(3,2,1)) from t1;
ROW(a,b,c) IN(row(1,2,3), row(3,2,1)) ROW(a,b,c) IN(row(1,2,3), row(3,2,1))
1 1
...@@ -509,5 +515,11 @@ Warnings: ...@@ -509,5 +515,11 @@ Warnings:
Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` where ((`test`.`t1`.`a` = 10) and (`test`.`t1`.`b` = 10)) Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` where ((`test`.`t1`.`a` = 10) and (`test`.`t1`.`b` = 10))
DROP TABLE t1; DROP TABLE t1;
# #
# MDEV-9369 IN operator with ( num, NULL ) gives inconsistent result
#
SELECT (1,null) NOT IN ((2,2),(3,3)), (1,null) NOT IN ((2,2)), (1,null) NOT IN ((3,3));
(1,null) NOT IN ((2,2),(3,3)) (1,null) NOT IN ((2,2)) (1,null) NOT IN ((3,3))
1 1 1
#
# End of 10.1 tests # End of 10.1 tests
# #
...@@ -19,10 +19,12 @@ select (1,2,(3,4)) IN ((3,2,(3,4)), (1,2,(3,4))); ...@@ -19,10 +19,12 @@ select (1,2,(3,4)) IN ((3,2,(3,4)), (1,2,(3,4)));
select row(1,2,row(3,4)) IN (row(3,2,row(3,4)), row(1,2,4)); select row(1,2,row(3,4)) IN (row(3,2,row(3,4)), row(1,2,4));
select row(1,2,row(3,4)) IN (row(3,2,row(3,4)), row(1,2,row(3,NULL))); select row(1,2,row(3,4)) IN (row(3,2,row(3,4)), row(1,2,row(3,NULL)));
explain extended select row(1,2,row(3,4)) IN (row(3,2,row(3,4)), row(1,2,row(3,NULL))); explain extended select row(1,2,row(3,4)) IN (row(3,2,row(3,4)), row(1,2,row(3,NULL)));
select row(1,2,row(3,null)) IN (row(3,2,row(3,4)), row(1,2,row(4,5)));
select row(1,2,row(3,null)) IN (row(3,2,row(3,4)), row(1,2,row(3,5)));
SELECT (1,2,3)=(0,NULL,3); SELECT (1,2,3)=(0,NULL,3);
SELECT (1,2,3)=(1,NULL,3); SELECT (1,2,3)=(1,NULL,3);
# here's something for Sanja to fix :)
SELECT (1,2,3)=(1,NULL,0); SELECT (1,2,3)=(1,NULL,0);
SELECT ROW(1,2,3)=ROW(1,2,3); SELECT ROW(1,2,3)=ROW(1,2,3);
...@@ -300,6 +302,13 @@ EXPLAIN EXTENDED SELECT * FROM t1 WHERE a=10 AND b=10 AND a>=10; ...@@ -300,6 +302,13 @@ EXPLAIN EXTENDED SELECT * FROM t1 WHERE a=10 AND b=10 AND a>=10;
EXPLAIN EXTENDED SELECT * FROM t1 WHERE (a,b)=(10,10) AND a>=10; EXPLAIN EXTENDED SELECT * FROM t1 WHERE (a,b)=(10,10) AND a>=10;
DROP TABLE t1; DROP TABLE t1;
--echo #
--echo # MDEV-9369 IN operator with ( num, NULL ) gives inconsistent result
--echo #
SELECT (1,null) NOT IN ((2,2),(3,3)), (1,null) NOT IN ((2,2)), (1,null) NOT IN ((3,3));
--echo # --echo #
--echo # End of 10.1 tests --echo # End of 10.1 tests
--echo # --echo #
...@@ -2914,7 +2914,7 @@ Item *Item_func_case::find_item(String *str) ...@@ -2914,7 +2914,7 @@ Item *Item_func_case::find_item(String *str)
return else_expr_num != -1 ? args[else_expr_num] : 0; return else_expr_num != -1 ? args[else_expr_num] : 0;
value_added_map|= 1U << (uint)cmp_type; value_added_map|= 1U << (uint)cmp_type;
} }
if (!cmp_items[(uint)cmp_type]->cmp(args[i]) && !args[i]->null_value) if (cmp_items[(uint)cmp_type]->cmp(args[i]) == FALSE)
return args[i + 1]; return args[i + 1];
} }
} }
...@@ -3545,11 +3545,11 @@ static int cmp_decimal(void *cmp_arg, my_decimal *a, my_decimal *b) ...@@ -3545,11 +3545,11 @@ static int cmp_decimal(void *cmp_arg, my_decimal *a, my_decimal *b)
} }
int in_vector::find(Item *item) bool in_vector::find(Item *item)
{ {
uchar *result=get_value(item); uchar *result=get_value(item);
if (!result || !used_count) if (!result || !used_count)
return 0; // Null value return false; // Null value
uint start,end; uint start,end;
start=0; end=used_count-1; start=0; end=used_count-1;
...@@ -3558,13 +3558,13 @@ int in_vector::find(Item *item) ...@@ -3558,13 +3558,13 @@ int in_vector::find(Item *item)
uint mid=(start+end+1)/2; uint mid=(start+end+1)/2;
int res; int res;
if ((res=(*compare)(collation, base+mid*size, result)) == 0) if ((res=(*compare)(collation, base+mid*size, result)) == 0)
return 1; return true;
if (res < 0) if (res < 0)
start=mid; start=mid;
else else
end=mid-1; end=mid-1;
} }
return (int) ((*compare)(collation, base+start*size, result) == 0); return ((*compare)(collation, base+start*size, result) == 0);
} }
in_string::in_string(uint elements,qsort2_cmp cmp_func, CHARSET_INFO *cs) in_string::in_string(uint elements,qsort2_cmp cmp_func, CHARSET_INFO *cs)
...@@ -3894,14 +3894,20 @@ int cmp_item_row::cmp(Item *arg) ...@@ -3894,14 +3894,20 @@ int cmp_item_row::cmp(Item *arg)
arg->bring_value(); arg->bring_value();
for (uint i=0; i < n; i++) for (uint i=0; i < n; i++)
{ {
if (comparators[i]->cmp(arg->element_index(i))) const int rc= comparators[i]->cmp(arg->element_index(i));
switch (rc)
{ {
if (!arg->element_index(i)->null_value) case UNKNOWN:
return 1; was_null= true;
was_null= 1; break;
case TRUE:
return TRUE;
case FALSE:
break; // elements #i are equal
} }
arg->null_value|= arg->element_index(i)->null_value;
} }
return (arg->null_value= was_null); return was_null ? UNKNOWN : FALSE;
} }
...@@ -3924,15 +3930,15 @@ void cmp_item_decimal::store_value(Item *item) ...@@ -3924,15 +3930,15 @@ void cmp_item_decimal::store_value(Item *item)
/* val may be zero if item is nnull */ /* val may be zero if item is nnull */
if (val && val != &value) if (val && val != &value)
my_decimal2decimal(val, &value); my_decimal2decimal(val, &value);
m_null_value= item->null_value;
} }
int cmp_item_decimal::cmp(Item *arg) int cmp_item_decimal::cmp(Item *arg)
{ {
my_decimal tmp_buf, *tmp= arg->val_decimal(&tmp_buf); my_decimal tmp_buf, *tmp= arg->val_decimal(&tmp_buf);
if (arg->null_value) return (m_null_value || arg->null_value) ?
return 1; UNKNOWN : (my_decimal_cmp(&value, tmp) != 0);
return my_decimal_cmp(&value, tmp);
} }
...@@ -3956,12 +3962,14 @@ void cmp_item_datetime::store_value(Item *item) ...@@ -3956,12 +3962,14 @@ void cmp_item_datetime::store_value(Item *item)
enum_field_types f_type= enum_field_types f_type=
tmp_item[0]->field_type_for_temporal_comparison(warn_item); tmp_item[0]->field_type_for_temporal_comparison(warn_item);
value= get_datetime_value(thd, &tmp_item, &lval_cache, f_type, &is_null); value= get_datetime_value(thd, &tmp_item, &lval_cache, f_type, &is_null);
m_null_value= item->null_value;
} }
int cmp_item_datetime::cmp(Item *arg) int cmp_item_datetime::cmp(Item *arg)
{ {
return value != arg->val_temporal_packed(warn_item); const bool rc= value != arg->val_temporal_packed(warn_item);
return (m_null_value || arg->null_value) ? UNKNOWN : rc;
} }
...@@ -3985,10 +3993,10 @@ bool Item_func_in::count_sargable_conds(uchar *arg) ...@@ -3985,10 +3993,10 @@ bool Item_func_in::count_sargable_conds(uchar *arg)
} }
bool Item_func_in::nulls_in_row() bool Item_func_in::list_contains_null()
{ {
Item **arg,**arg_end; Item **arg,**arg_end;
for (arg= args+1, arg_end= args+arg_count; arg != arg_end ; arg++) for (arg= args + 1, arg_end= args+arg_count; arg != arg_end ; arg++)
{ {
if ((*arg)->null_inside()) if ((*arg)->null_inside())
return 1; return 1;
...@@ -4103,6 +4111,32 @@ void Item_func_in::fix_length_and_dec() ...@@ -4103,6 +4111,32 @@ void Item_func_in::fix_length_and_dec()
} }
} }
/*
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 (m_compare_type == ROW_RESULT &&
((!is_top_level_item() || negated) && // 3
(list_contains_null() || args[0]->maybe_null))) // 4
bisection_possible= false;
}
if (type_cnt == 1) if (type_cnt == 1)
{ {
if (m_compare_type == STRING_RESULT && if (m_compare_type == STRING_RESULT &&
...@@ -4115,7 +4149,7 @@ void Item_func_in::fix_length_and_dec() ...@@ -4115,7 +4149,7 @@ void Item_func_in::fix_length_and_dec()
uint cols= args[0]->cols(); uint cols= args[0]->cols();
cmp_item_row *cmp= 0; cmp_item_row *cmp= 0;
if (const_itm && !nulls_in_row()) if (bisection_possible)
{ {
array= new (thd->mem_root) in_row(thd, arg_count-1, 0); array= new (thd->mem_root) in_row(thd, arg_count-1, 0);
cmp= &((in_row*)array)->tmp; cmp= &((in_row*)array)->tmp;
...@@ -4144,11 +4178,8 @@ void Item_func_in::fix_length_and_dec() ...@@ -4144,11 +4178,8 @@ void Item_func_in::fix_length_and_dec()
} }
} }
} }
/*
Row item with NULLs inside can return NULL or FALSE => if (bisection_possible)
they can't be processed as static
*/
if (type_cnt == 1 && const_itm && !nulls_in_row())
{ {
/* /*
IN must compare INT columns and constants as int values (the same IN must compare INT columns and constants as int values (the same
...@@ -4203,20 +4234,25 @@ void Item_func_in::fix_length_and_dec() ...@@ -4203,20 +4234,25 @@ void Item_func_in::fix_length_and_dec()
array= new (thd->mem_root) in_datetime(date_arg, arg_count - 1); array= new (thd->mem_root) in_datetime(date_arg, arg_count - 1);
break; break;
} }
if (array && !(thd->is_fatal_error)) // If not EOM if (!array || thd->is_fatal_error) // OOM
return;
uint j=0;
for (uint i=1 ; i < arg_count ; i++)
{ {
uint j=0; array->set(j,args[i]);
for (uint i=1 ; i < arg_count ; i++) if (!args[i]->null_value)
j++; // include this cell in the array.
else
{ {
array->set(j,args[i]); /*
if (!args[i]->null_value) // Skip NULL values We don't put NULL values in array, to avoid erronous matches in
j++; bisection.
else */
have_null= 1; have_null= 1;
} }
if ((array->used_count= j))
array->sort();
} }
if ((array->used_count= j))
array->sort();
} }
else else
{ {
...@@ -4284,7 +4320,14 @@ longlong Item_func_in::val_int() ...@@ -4284,7 +4320,14 @@ longlong Item_func_in::val_int()
uint value_added_map= 0; uint value_added_map= 0;
if (array) if (array)
{ {
int tmp=array->find(args[0]); bool tmp=array->find(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); null_value=args[0]->null_value || (!tmp && have_null);
return (longlong) (!null_value && tmp != negated); return (longlong) (!null_value && tmp != negated);
} }
...@@ -4306,13 +4349,12 @@ longlong Item_func_in::val_int() ...@@ -4306,13 +4349,12 @@ longlong Item_func_in::val_int()
if (!(value_added_map & (1U << (uint)cmp_type))) if (!(value_added_map & (1U << (uint)cmp_type)))
{ {
in_item->store_value(args[0]); in_item->store_value(args[0]);
if ((null_value= args[0]->null_value))
return 0;
value_added_map|= 1U << (uint)cmp_type; value_added_map|= 1U << (uint)cmp_type;
} }
if (!in_item->cmp(args[i]) && !args[i]->null_value) const int rc= in_item->cmp(args[i]);
if (rc == FALSE)
return (longlong) (!negated); return (longlong) (!negated);
have_null|= args[i]->null_value; have_null|= (rc == UNKNOWN);
} }
null_value= have_null; null_value= have_null;
...@@ -6434,7 +6476,8 @@ longlong Item_equal::val_int() ...@@ -6434,7 +6476,8 @@ longlong Item_equal::val_int()
/* Skip fields of tables that has not been read yet */ /* Skip fields of tables that has not been read yet */
if (!field->table->status || (field->table->status & STATUS_NULL_ROW)) if (!field->table->status || (field->table->status & STATUS_NULL_ROW))
{ {
if (eval_item->cmp(item) || (null_value= item->null_value)) const int rc= eval_item->cmp(item);
if ((rc == TRUE) || (null_value= (rc == UNKNOWN)))
return 0; return 0;
} }
} }
......
...@@ -796,6 +796,7 @@ public: ...@@ -796,6 +796,7 @@ public:
public: public:
inline void negate() { negated= !negated; } inline void negate() { negated= !negated; }
inline void top_level_item() { pred_level= 1; } inline void top_level_item() { pred_level= 1; }
bool is_top_level_item() const { return pred_level; }
Item *neg_transformer(THD *thd) Item *neg_transformer(THD *thd)
{ {
negated= !negated; negated= !negated;
...@@ -1075,7 +1076,7 @@ public: ...@@ -1075,7 +1076,7 @@ public:
{ {
my_qsort2(base,used_count,size,compare,(void*)collation); my_qsort2(base,used_count,size,compare,(void*)collation);
} }
int find(Item *item); bool find(Item *item);
/* /*
Create an instance of Item_{type} (e.g. Item_decimal) constant object Create an instance of Item_{type} (e.g. Item_decimal) constant object
...@@ -1243,6 +1244,10 @@ public: ...@@ -1243,6 +1244,10 @@ public:
cmp_item() { cmp_charset= &my_charset_bin; } cmp_item() { cmp_charset= &my_charset_bin; }
virtual ~cmp_item() {} virtual ~cmp_item() {}
virtual void store_value(Item *item)= 0; virtual void store_value(Item *item)= 0;
/**
@returns result (TRUE, FALSE or UNKNOWN) of
"stored argument's value <> item's value"
*/
virtual int cmp(Item *item)= 0; virtual int cmp(Item *item)= 0;
// for optimized IN with row // for optimized IN with row
virtual int compare(cmp_item *item)= 0; virtual int compare(cmp_item *item)= 0;
...@@ -1255,7 +1260,14 @@ public: ...@@ -1255,7 +1260,14 @@ public:
} }
}; };
class cmp_item_string :public cmp_item /// cmp_item which stores a scalar (i.e. non-ROW).
class cmp_item_scalar : public cmp_item
{
protected:
bool m_null_value; ///< If stored value is NULL
};
class cmp_item_string : public cmp_item_scalar
{ {
protected: protected:
String *value_res; String *value_res;
...@@ -1281,14 +1293,20 @@ public: ...@@ -1281,14 +1293,20 @@ public:
void store_value(Item *item) void store_value(Item *item)
{ {
value_res= item->val_str(&value); value_res= item->val_str(&value);
m_null_value= item->null_value;
} }
int cmp(Item *arg) int cmp(Item *arg)
{ {
char buff[STRING_BUFFER_USUAL_SIZE]; char buff[STRING_BUFFER_USUAL_SIZE];
String tmp(buff, sizeof(buff), cmp_charset), *res; String tmp(buff, sizeof(buff), cmp_charset), *res= arg->val_str(&tmp);
res= arg->val_str(&tmp); if (m_null_value || arg->null_value)
return (value_res ? (res ? sortcmp(value_res, res, cmp_charset) : 1) : return UNKNOWN;
(res ? -1 : 0)); if (value_res && res)
return sortcmp(value_res, res, cmp_charset) != 0;
else if (!value_res && !res)
return FALSE;
else
return TRUE;
} }
int compare(cmp_item *ci) int compare(cmp_item *ci)
{ {
...@@ -1303,7 +1321,7 @@ public: ...@@ -1303,7 +1321,7 @@ public:
} }
}; };
class cmp_item_int :public cmp_item class cmp_item_int : public cmp_item_scalar
{ {
longlong value; longlong value;
public: public:
...@@ -1311,10 +1329,12 @@ public: ...@@ -1311,10 +1329,12 @@ public:
void store_value(Item *item) void store_value(Item *item)
{ {
value= item->val_int(); value= item->val_int();
m_null_value= item->null_value;
} }
int cmp(Item *arg) int cmp(Item *arg)
{ {
return value != arg->val_int(); const bool rc= value != arg->val_int();
return (m_null_value || arg->null_value) ? UNKNOWN : rc;
} }
int compare(cmp_item *ci) int compare(cmp_item *ci)
{ {
...@@ -1330,7 +1350,7 @@ public: ...@@ -1330,7 +1350,7 @@ public:
If the left item is a constant one then its value is cached in the If the left item is a constant one then its value is cached in the
lval_cache variable. lval_cache variable.
*/ */
class cmp_item_datetime :public cmp_item class cmp_item_datetime : public cmp_item_scalar
{ {
longlong value; longlong value;
public: public:
...@@ -1348,7 +1368,7 @@ public: ...@@ -1348,7 +1368,7 @@ public:
cmp_item *make_same(); cmp_item *make_same();
}; };
class cmp_item_real :public cmp_item class cmp_item_real : public cmp_item_scalar
{ {
double value; double value;
public: public:
...@@ -1356,10 +1376,12 @@ public: ...@@ -1356,10 +1376,12 @@ public:
void store_value(Item *item) void store_value(Item *item)
{ {
value= item->val_real(); value= item->val_real();
m_null_value= item->null_value;
} }
int cmp(Item *arg) int cmp(Item *arg)
{ {
return value != arg->val_real(); const bool rc= value != arg->val_real();
return (m_null_value || arg->null_value) ? UNKNOWN : rc;
} }
int compare(cmp_item *ci) int compare(cmp_item *ci)
{ {
...@@ -1370,7 +1392,7 @@ public: ...@@ -1370,7 +1392,7 @@ public:
}; };
class cmp_item_decimal :public cmp_item class cmp_item_decimal : public cmp_item_scalar
{ {
my_decimal value; my_decimal value;
public: public:
...@@ -1397,12 +1419,13 @@ public: ...@@ -1397,12 +1419,13 @@ public:
void store_value(Item *item) void store_value(Item *item)
{ {
value_res= item->val_str(&value); value_res= item->val_str(&value);
m_null_value= item->null_value;
} }
int cmp(Item *item) int cmp(Item *item)
{ {
// Should never be called // Should never be called
DBUG_ASSERT(0); DBUG_ASSERT(false);
return 1; return TRUE;
} }
int compare(cmp_item *ci) int compare(cmp_item *ci)
{ {
...@@ -1467,33 +1490,43 @@ public: ...@@ -1467,33 +1490,43 @@ public:
}; };
/* /*
The Item_func_in class implements the in_expr IN(values_list) function. The Item_func_in class implements
in_expr IN (<in value list>)
and
in_expr NOT IN (<in value list>)
The current implementation distinguishes 2 cases: The current implementation distinguishes 2 cases:
1) all items in the value_list are constants and have the same 1) all items in <in value list> are constants and have the same
result type. This case is handled by in_vector class. result type. This case is handled by in_vector class.
2) items in the value_list have different result types or there is some 2) otherwise Item_func_in employs several cmp_item objects to perform
non-constant items. comparisons of in_expr and an item from <in value list>. One cmp_item
In this case Item_func_in employs several cmp_item objects to performs
comparisons of in_expr and an item from the values_list. One cmp_item
object for each result type. Different result types are collected in the object for each result type. Different result types are collected in the
fix_length_and_dec() member function by means of collect_cmp_types() fix_length_and_dec() member function by means of collect_cmp_types()
function. function.
*/ */
class Item_func_in :public Item_func_opt_neg class Item_func_in :public Item_func_opt_neg
{ {
/**
Usable if <in value list> is made only of constants. Returns true if one
of these constants contains a NULL. Example:
IN ( (-5, (12,NULL)), ... ).
*/
bool list_contains_null();
protected: protected:
SEL_TREE *get_func_mm_tree(RANGE_OPT_PARAM *param, SEL_TREE *get_func_mm_tree(RANGE_OPT_PARAM *param,
Field *field, Item *value); Field *field, Item *value);
public: public:
/* /// An array of values, created when the bisection lookup method is used
an array of values when the right hand arguments of IN
are all SQL constant and there are no nulls
*/
in_vector *array; in_vector *array;
/**
If there is some NULL among <in value list>, during a val_int() call; for
example
IN ( (1,(3,'col')), ... ), where 'col' is a column which evaluates to
NULL.
*/
bool have_null; bool have_null;
/* /**
true when all arguments of the IN clause are of compatible types true when all arguments of the IN list are of compatible types
and can be used safely as comparisons for key conditions and can be used safely as comparisons for key conditions
*/ */
bool arg_types_compatible; bool arg_types_compatible;
...@@ -1547,7 +1580,6 @@ public: ...@@ -1547,7 +1580,6 @@ public:
virtual void print(String *str, enum_query_type query_type); virtual void print(String *str, enum_query_type query_type);
enum Functype functype() const { return IN_FUNC; } enum Functype functype() const { return IN_FUNC; }
const char *func_name() const { return " IN "; } const char *func_name() const { return " IN "; }
bool nulls_in_row();
bool eval_not_null_tables(uchar *opt_arg); bool eval_not_null_tables(uchar *opt_arg);
void fix_after_pullout(st_select_lex *new_parent, Item **ref); void fix_after_pullout(st_select_lex *new_parent, Item **ref);
bool count_sargable_conds(uchar *arg); bool count_sargable_conds(uchar *arg);
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
#define ITEM_ROW_INCLUDED #define ITEM_ROW_INCLUDED
/* /*
Copyright (c) 2002, 2010, Oracle and/or its affiliates. Copyright (c) 2002, 2013, Oracle and/or its affiliates.
This program is free software; you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
...@@ -28,11 +28,20 @@ ...@@ -28,11 +28,20 @@
@endverbatim @endverbatim
*/ */
/**
Item which stores (x,y,...) and ROW(x,y,...).
Note that this can be recursive: ((x,y),(z,t)) is a ROW of ROWs.
*/
class Item_row: public Item, class Item_row: public Item,
private Item_args, private Item_args,
private Used_tables_and_const_cache private Used_tables_and_const_cache
{ {
table_map not_null_tables_cache; table_map not_null_tables_cache;
/**
If elements are made only of constants, of which one or more are
NULL. For example, this item is (1,2,NULL), or ( (1,NULL), (2,3) ).
*/
bool with_null; bool with_null;
public: public:
Item_row(THD *thd, List<Item> &list): Item_row(THD *thd, List<Item> &list):
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment