Commit 969f4918 authored by Alexander Barkov's avatar Alexander Barkov

MDEV-7005 NULLIF does not work as documented

MDEV-7146 NULLIF returns unexpected result with a YEAR field
parent 9f4abde6
...@@ -2900,7 +2900,7 @@ SHOW CREATE TABLE t2; ...@@ -2900,7 +2900,7 @@ SHOW CREATE TABLE t2;
Table Create Table Table Create Table
t2 CREATE TABLE `t2` ( t2 CREATE TABLE `t2` (
`d` date DEFAULT NULL, `d` date DEFAULT NULL,
`bad` varbinary(10) DEFAULT NULL `bad` date DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1 ) ENGINE=MyISAM DEFAULT CHARSET=latin1
DROP TABLE t1, t2; DROP TABLE t1, t2;
SET NAMES latin1; SET NAMES latin1;
......
...@@ -3292,7 +3292,7 @@ SHOW CREATE TABLE t2; ...@@ -3292,7 +3292,7 @@ SHOW CREATE TABLE t2;
Table Create Table Table Create Table
t2 CREATE TABLE `t2` ( t2 CREATE TABLE `t2` (
`d` date DEFAULT NULL, `d` date DEFAULT NULL,
`bad` varchar(10) CHARACTER SET cp1251 DEFAULT NULL `bad` date DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1 ) ENGINE=MyISAM DEFAULT CHARSET=latin1
DROP TABLE t1, t2; DROP TABLE t1, t2;
SET NAMES latin1; SET NAMES latin1;
......
...@@ -3576,7 +3576,7 @@ SHOW CREATE TABLE t2; ...@@ -3576,7 +3576,7 @@ SHOW CREATE TABLE t2;
Table Create Table Table Create Table
t2 CREATE TABLE `t2` ( t2 CREATE TABLE `t2` (
`d` date DEFAULT NULL, `d` date DEFAULT NULL,
`bad` varchar(10) DEFAULT NULL `bad` date DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1 ) ENGINE=MyISAM DEFAULT CHARSET=latin1
DROP TABLE t1, t2; DROP TABLE t1, t2;
SET NAMES latin1; SET NAMES latin1;
......
...@@ -4483,7 +4483,7 @@ SHOW CREATE TABLE t2; ...@@ -4483,7 +4483,7 @@ SHOW CREATE TABLE t2;
Table Create Table Table Create Table
t2 CREATE TABLE `t2` ( t2 CREATE TABLE `t2` (
`d` date DEFAULT NULL, `d` date DEFAULT NULL,
`bad` varchar(10) CHARACTER SET ucs2 DEFAULT NULL `bad` date DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1 ) ENGINE=MyISAM DEFAULT CHARSET=latin1
DROP TABLE t1, t2; DROP TABLE t1, t2;
SET NAMES latin1; SET NAMES latin1;
......
...@@ -5351,7 +5351,7 @@ SHOW CREATE TABLE t2; ...@@ -5351,7 +5351,7 @@ SHOW CREATE TABLE t2;
Table Create Table Table Create Table
t2 CREATE TABLE `t2` ( t2 CREATE TABLE `t2` (
`d` date DEFAULT NULL, `d` date DEFAULT NULL,
`bad` varchar(10) CHARACTER SET utf8 DEFAULT NULL `bad` date DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1 ) ENGINE=MyISAM DEFAULT CHARSET=latin1
DROP TABLE t1, t2; DROP TABLE t1, t2;
SET NAMES latin1; SET NAMES latin1;
......
This diff is collapsed.
This diff is collapsed.
...@@ -525,6 +525,32 @@ static bool convert_const_to_int(THD *thd, Item_field *field_item, ...@@ -525,6 +525,32 @@ static bool convert_const_to_int(THD *thd, Item_field *field_item,
} }
/*
Make a special case of compare with fields to get nicer comparisons
of bigint numbers with constant string.
This directly contradicts the manual (number and a string should
be compared as doubles), but seems to provide more
"intuitive" behavior in some cases (but less intuitive in others).
*/
void Item_func::convert_const_compared_to_int_field(THD *thd)
{
DBUG_ASSERT(arg_count == 2);
if (!thd->lex->is_ps_or_view_context_analysis())
{
int field;
if (args[field= 0]->real_item()->type() == FIELD_ITEM ||
args[field= 1]->real_item()->type() == FIELD_ITEM)
{
Item_field *field_item= (Item_field*) (args[field]->real_item());
if ((field_item->field_type() == MYSQL_TYPE_LONGLONG ||
field_item->field_type() == MYSQL_TYPE_YEAR) &&
convert_const_to_int(thd, field_item, &args[!field]))
args[0]->cmp_context= args[1]->cmp_context= INT_RESULT;
}
}
}
void Item_bool_func2::fix_length_and_dec() void Item_bool_func2::fix_length_and_dec()
{ {
max_length= 1; // Function returns 0 or 1 max_length= 1; // Function returns 0 or 1
...@@ -557,29 +583,9 @@ void Item_bool_func2::fix_length_and_dec() ...@@ -557,29 +583,9 @@ void Item_bool_func2::fix_length_and_dec()
args[0]->cmp_context= args[1]->cmp_context= args[0]->cmp_context= args[1]->cmp_context=
item_cmp_type(args[0]->result_type(), args[1]->result_type()); item_cmp_type(args[0]->result_type(), args[1]->result_type());
/* // Convert constants when compared to int/year field, unless this is LIKE
Make a special case of compare with fields to get nicer comparisons if (functype() != LIKE_FUNC)
of bigint numbers with constant string. convert_const_compared_to_int_field(current_thd);
This directly contradicts the manual (number and a string should
be compared as doubles), but seems to provide more
"intuitive" behavior in some cases (but less intuitive in others).
But disable conversion in case of LIKE function.
*/
THD *thd= current_thd;
if (functype() != LIKE_FUNC && !thd->lex->is_ps_or_view_context_analysis())
{
int field;
if (args[field= 0]->real_item()->type() == FIELD_ITEM ||
args[field= 1]->real_item()->type() == FIELD_ITEM)
{
Item_field *field_item= (Item_field*) (args[field]->real_item());
if ((field_item->field_type() == MYSQL_TYPE_LONGLONG ||
field_item->field_type() == MYSQL_TYPE_YEAR) &&
convert_const_to_int(thd, field_item, &args[!field]))
args[0]->cmp_context= args[1]->cmp_context= INT_RESULT;
}
}
set_cmp_func(); set_cmp_func();
} }
...@@ -2723,18 +2729,21 @@ bool Item_func_if::date_op(MYSQL_TIME *ltime, uint fuzzydate) ...@@ -2723,18 +2729,21 @@ bool Item_func_if::date_op(MYSQL_TIME *ltime, uint fuzzydate)
void void
Item_func_nullif::fix_length_and_dec() Item_func_nullif::fix_length_and_dec()
{ {
Item_bool_func2::fix_length_and_dec(); if (!args[0]) // Only false if EOM
return;
cached_result_type= args[0]->result_type();
cached_field_type= args[0]->field_type();
collation.set(args[0]->collation);
decimals= args[0]->decimals;
unsigned_flag= args[0]->unsigned_flag;
fix_char_length(args[0]->max_char_length());
convert_const_compared_to_int_field(current_thd);
args[0]->cmp_context= args[1]->cmp_context=
item_cmp_type(args[0]->result_type(), args[1]->result_type());
cmp.set_cmp_func(this, tmp_arg, tmp_arg + 1, args[0]->cmp_context);
maybe_null=1; maybe_null=1;
if (args[0]) // Only false if EOM
{
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;
fix_char_length(args[0]->max_char_length());
}
} }
...@@ -2749,7 +2758,7 @@ Item_func_nullif::fix_length_and_dec() ...@@ -2749,7 +2758,7 @@ Item_func_nullif::fix_length_and_dec()
*/ */
double double
Item_func_nullif::val_real() Item_func_nullif::real_op()
{ {
DBUG_ASSERT(fixed == 1); DBUG_ASSERT(fixed == 1);
double value; double value;
...@@ -2758,13 +2767,13 @@ Item_func_nullif::val_real() ...@@ -2758,13 +2767,13 @@ Item_func_nullif::val_real()
null_value=1; null_value=1;
return 0.0; return 0.0;
} }
value= args[0]->val_real(); value= m_args0_copy->val_real();
null_value=args[0]->null_value; null_value=m_args0_copy->null_value;
return value; return value;
} }
longlong longlong
Item_func_nullif::val_int() Item_func_nullif::int_op()
{ {
DBUG_ASSERT(fixed == 1); DBUG_ASSERT(fixed == 1);
longlong value; longlong value;
...@@ -2773,13 +2782,13 @@ Item_func_nullif::val_int() ...@@ -2773,13 +2782,13 @@ Item_func_nullif::val_int()
null_value=1; null_value=1;
return 0; return 0;
} }
value=args[0]->val_int(); value=m_args0_copy->val_int();
null_value=args[0]->null_value; null_value=m_args0_copy->null_value;
return value; return value;
} }
String * String *
Item_func_nullif::val_str(String *str) Item_func_nullif::str_op(String *str)
{ {
DBUG_ASSERT(fixed == 1); DBUG_ASSERT(fixed == 1);
String *res; String *res;
...@@ -2788,14 +2797,14 @@ Item_func_nullif::val_str(String *str) ...@@ -2788,14 +2797,14 @@ Item_func_nullif::val_str(String *str)
null_value=1; null_value=1;
return 0; return 0;
} }
res=args[0]->val_str(str); res=m_args0_copy->val_str(str);
null_value=args[0]->null_value; null_value=m_args0_copy->null_value;
return res; return res;
} }
my_decimal * my_decimal *
Item_func_nullif::val_decimal(my_decimal * decimal_value) Item_func_nullif::decimal_op(my_decimal * decimal_value)
{ {
DBUG_ASSERT(fixed == 1); DBUG_ASSERT(fixed == 1);
my_decimal *res; my_decimal *res;
...@@ -2804,16 +2813,26 @@ Item_func_nullif::val_decimal(my_decimal * decimal_value) ...@@ -2804,16 +2813,26 @@ Item_func_nullif::val_decimal(my_decimal * decimal_value)
null_value=1; null_value=1;
return 0; return 0;
} }
res= args[0]->val_decimal(decimal_value); res= m_args0_copy->val_decimal(decimal_value);
null_value= args[0]->null_value; null_value= m_args0_copy->null_value;
return res; return res;
} }
bool
Item_func_nullif::date_op(MYSQL_TIME *ltime, uint fuzzydate)
{
DBUG_ASSERT(fixed == 1);
if (!cmp.compare())
return (null_value= true);
return (null_value= m_args0_copy->get_date(ltime, fuzzydate));
}
bool bool
Item_func_nullif::is_null() Item_func_nullif::is_null()
{ {
return (null_value= (!cmp.compare() ? 1 : args[0]->null_value)); return (null_value= (!cmp.compare() ? 1 : m_args0_copy->null_value));
} }
......
...@@ -822,20 +822,30 @@ class Item_func_if :public Item_func_case_abbreviation2 ...@@ -822,20 +822,30 @@ class Item_func_if :public Item_func_case_abbreviation2
}; };
class Item_func_nullif :public Item_bool_func2 class Item_func_nullif :public Item_func_hybrid_field_type
{ {
enum Item_result cached_result_type; Arg_comparator cmp;
/*
Remember the first argument in case it will be substituted by either of:
- convert_const_compared_to_int_field()
- agg_item_set_converter() in set_cmp_func()
- cache_converted_constant() in set_cmp_func()
The original item will be stored in m_arg0_copy, to return result.
The substituted item will be stored in args[0], for comparison purposes.
*/
Item *m_args0_copy;
public: public:
Item_func_nullif(Item *a,Item *b) Item_func_nullif(Item *a,Item *b)
:Item_bool_func2(a,b), cached_result_type(INT_RESULT) :Item_func_hybrid_field_type(a, b),
m_args0_copy(a)
{} {}
double val_real(); bool date_op(MYSQL_TIME *ltime, uint fuzzydate);
longlong val_int(); double real_op();
String *val_str(String *str); longlong int_op();
my_decimal *val_decimal(my_decimal *); String *str_op(String *str);
enum Item_result result_type () const { return cached_result_type; } my_decimal *decimal_op(my_decimal *);
void fix_length_and_dec(); void fix_length_and_dec();
uint decimal_precision() const { return args[0]->decimal_precision(); } uint decimal_precision() const { return m_args0_copy->decimal_precision(); }
const char *func_name() const { return "nullif"; } const char *func_name() const { return "nullif"; }
virtual inline void print(String *str, enum_query_type query_type) virtual inline void print(String *str, enum_query_type query_type)
......
...@@ -396,6 +396,7 @@ class Item_func :public Item_result_field ...@@ -396,6 +396,7 @@ class Item_func :public Item_result_field
info.bool_function= &Item::restore_to_before_no_rows_in_result; info.bool_function= &Item::restore_to_before_no_rows_in_result;
walk(&Item::call_bool_func_processor, FALSE, (uchar*) &info); walk(&Item::call_bool_func_processor, FALSE, (uchar*) &info);
} }
void convert_const_compared_to_int_field(THD *thd);
}; };
......
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