Commit 4aebba3a authored by Alexander Barkov's avatar Alexander Barkov

MDEV-8740 Wrong result for SELECT..WHERE year_field=10 AND NULLIF(year_field,2011.1)='2011'

MDEV-8754 Wrong result for SELECT..WHERE year_field=2020 AND NULLIF(year_field,2010)='2020'
Problems:
1. Item_func_nullif stored a copy of args[0] in a private member m_args0_copy,
   which was invisible for the inherited Item_func menthods, like
   update_used_tables(). As a result, after equal field propagation
   things like Item_func_nullif::const_item() could return wrong result
   and a non-constant NULLIF() was erroneously treated as a constant
   at optimize_cond() time.
   Solution: removing m_args0_copy and storing the return value item
   in args[2] instead.
2. Equal field propagation did not work well for Item_fun_nullif.
   Solution: using ANY_SUBST for args[0] and args[1], as they are in
   comparison, and IDENTITY_SUBST for args[2], as it's not in comparison.
parent 8e553c45
...@@ -1409,5 +1409,61 @@ Warnings: ...@@ -1409,5 +1409,61 @@ Warnings:
Note 1003 select isnull((case when convert(`test`.`t1`.`a` using utf8) = (_utf8'a' collate utf8_bin) then NULL else `test`.`t1`.`a` end)) AS `expr` from `test`.`t1` Note 1003 select isnull((case when convert(`test`.`t1`.`a` using utf8) = (_utf8'a' collate utf8_bin) then NULL else `test`.`t1`.`a` end)) AS `expr` from `test`.`t1`
DROP TABLE t1; DROP TABLE t1;
# #
# MDEV-8740 Wrong result for SELECT..WHERE year_field=10 AND NULLIF(year_field,2011.1)='2011'
#
CREATE TABLE t1 (a YEAR);
INSERT INTO t1 VALUES (2010),(2011);
SELECT a=10 AND NULLIF(a,2011.1)='2011' AS cond FROM t1;
cond
0
0
SELECT * FROM t1 WHERE a=10;
a
2010
SELECT * FROM t1 WHERE NULLIF(a,2011.1)='2011';
a
SELECT * FROM t1 WHERE a=10 AND NULLIF(a,2011.1)='2011';
a
EXPLAIN EXTENDED
SELECT * FROM t1 WHERE a=10 AND NULLIF(a,2011.1)='2011';
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL Impossible WHERE
Warnings:
Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where 0
EXPLAIN EXTENDED
SELECT * FROM t1 WHERE a=10 AND NULLIF(a,2011.1)=CONCAT('2011',RAND());
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 Using where
Warnings:
Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where ((`test`.`t1`.`a` = 2010) and (<cache>((case when 2010 = 2011 then NULL else '2010' end)) = concat('2011',rand())))
DROP TABLE t1;
#
# MDEV-8754 Wrong result for SELECT..WHERE year_field=2020 AND NULLIF(year_field,2010)='2020'
#
CREATE TABLE t1 (a YEAR);
INSERT INTO t1 VALUES (2010),(2020);
SELECT * FROM t1 WHERE a=2020;
a
2020
SELECT * FROM t1 WHERE NULLIF(a,2010)='2020';
a
2020
SELECT * FROM t1 WHERE a=2020 AND NULLIF(a,2010)='2020';
a
2020
EXPLAIN EXTENDED
SELECT * FROM t1 WHERE a=2020 AND NULLIF(a,2010)='2020';
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 Using where
Warnings:
Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where (`test`.`t1`.`a` = 2020)
EXPLAIN EXTENDED
SELECT * FROM t1 WHERE a=2020 AND NULLIF(a,2010)=CONCAT('2020',RAND());
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 Using where
Warnings:
Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where ((`test`.`t1`.`a` = 2020) and (<cache>((case when 2020 = 2010 then NULL else '2020' end)) = concat('2020',rand())))
DROP TABLE t1;
#
# End of 10.1 tests # End of 10.1 tests
# #
...@@ -879,6 +879,37 @@ EXPLAIN EXTENDED SELECT NULLIF(a,_utf8'a' COLLATE utf8_bin) IS NULL AS expr FROM ...@@ -879,6 +879,37 @@ EXPLAIN EXTENDED SELECT NULLIF(a,_utf8'a' COLLATE utf8_bin) IS NULL AS expr FROM
DROP TABLE t1; DROP TABLE t1;
--echo #
--echo # MDEV-8740 Wrong result for SELECT..WHERE year_field=10 AND NULLIF(year_field,2011.1)='2011'
--echo #
CREATE TABLE t1 (a YEAR);
INSERT INTO t1 VALUES (2010),(2011);
SELECT a=10 AND NULLIF(a,2011.1)='2011' AS cond FROM t1;
SELECT * FROM t1 WHERE a=10;
SELECT * FROM t1 WHERE NULLIF(a,2011.1)='2011';
SELECT * FROM t1 WHERE a=10 AND NULLIF(a,2011.1)='2011';
EXPLAIN EXTENDED
SELECT * FROM t1 WHERE a=10 AND NULLIF(a,2011.1)='2011';
EXPLAIN EXTENDED
SELECT * FROM t1 WHERE a=10 AND NULLIF(a,2011.1)=CONCAT('2011',RAND());
DROP TABLE t1;
--echo #
--echo # MDEV-8754 Wrong result for SELECT..WHERE year_field=2020 AND NULLIF(year_field,2010)='2020'
--echo #
CREATE TABLE t1 (a YEAR);
INSERT INTO t1 VALUES (2010),(2020);
SELECT * FROM t1 WHERE a=2020;
SELECT * FROM t1 WHERE NULLIF(a,2010)='2020';
SELECT * FROM t1 WHERE a=2020 AND NULLIF(a,2010)='2020';
EXPLAIN EXTENDED
SELECT * FROM t1 WHERE a=2020 AND NULLIF(a,2010)='2020';
EXPLAIN EXTENDED
SELECT * FROM t1 WHERE a=2020 AND NULLIF(a,2010)=CONCAT('2020',RAND());
DROP TABLE t1;
--echo # --echo #
--echo # End of 10.1 tests --echo # End of 10.1 tests
--echo # --echo #
...@@ -6683,6 +6683,18 @@ bool Item_field::send(Protocol *protocol, String *buffer) ...@@ -6683,6 +6683,18 @@ bool Item_field::send(Protocol *protocol, String *buffer)
} }
Item* Item::propagate_equal_fields_and_change_item_tree(THD *thd,
const Context &ctx,
COND_EQUAL *cond,
Item **place)
{
Item *item= propagate_equal_fields(thd, ctx, cond);
if (item && item != this)
thd->change_item_tree(place, item);
return item;
}
void Item_field::update_null_value() void Item_field::update_null_value()
{ {
/* /*
......
...@@ -1455,6 +1455,11 @@ public: ...@@ -1455,6 +1455,11 @@ public:
return this; return this;
}; };
Item* propagate_equal_fields_and_change_item_tree(THD *thd,
const Context &ctx,
COND_EQUAL *cond,
Item **place);
/* /*
@brief @brief
Processor used to check acceptability of an item in the defining Processor used to check acceptability of an item in the defining
......
...@@ -473,7 +473,7 @@ static bool convert_const_to_int(THD *thd, Item_field *field_item, ...@@ -473,7 +473,7 @@ static bool convert_const_to_int(THD *thd, Item_field *field_item,
*/ */
void Item_func::convert_const_compared_to_int_field(THD *thd) void Item_func::convert_const_compared_to_int_field(THD *thd)
{ {
DBUG_ASSERT(arg_count == 2); DBUG_ASSERT(arg_count >= 2); // Item_func_nullif has arg_count == 3
if (!thd->lex->is_ps_or_view_context_analysis()) if (!thd->lex->is_ps_or_view_context_analysis())
{ {
int field; int field;
...@@ -491,7 +491,7 @@ void Item_func::convert_const_compared_to_int_field(THD *thd) ...@@ -491,7 +491,7 @@ void Item_func::convert_const_compared_to_int_field(THD *thd)
bool Item_func::setup_args_and_comparator(THD *thd, Arg_comparator *cmp) bool Item_func::setup_args_and_comparator(THD *thd, Arg_comparator *cmp)
{ {
DBUG_ASSERT(arg_count == 2); DBUG_ASSERT(arg_count >= 2); // Item_func_nullif has arg_count == 3
if (args[0]->cmp_type() == STRING_RESULT && if (args[0]->cmp_type() == STRING_RESULT &&
args[1]->cmp_type() == STRING_RESULT && args[1]->cmp_type() == STRING_RESULT &&
...@@ -502,7 +502,7 @@ bool Item_func::setup_args_and_comparator(THD *thd, Arg_comparator *cmp) ...@@ -502,7 +502,7 @@ bool Item_func::setup_args_and_comparator(THD *thd, Arg_comparator *cmp)
DBUG_ASSERT(functype() != LIKE_FUNC); DBUG_ASSERT(functype() != LIKE_FUNC);
convert_const_compared_to_int_field(thd); convert_const_compared_to_int_field(thd);
return cmp->set_cmp_func(this, tmp_arg, tmp_arg + 1, true); return cmp->set_cmp_func(this, &args[0], &args[1], true);
} }
...@@ -2663,15 +2663,15 @@ bool Item_func_if::date_op(MYSQL_TIME *ltime, uint fuzzydate) ...@@ -2663,15 +2663,15 @@ 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()
{ {
if (!m_args0_copy) // Only false if EOM if (!args[2]) // Only false if EOM
return; return;
cached_result_type= m_args0_copy->result_type(); cached_result_type= args[2]->result_type();
cached_field_type= m_args0_copy->field_type(); cached_field_type= args[2]->field_type();
collation.set(m_args0_copy->collation); collation.set(args[2]->collation);
decimals= m_args0_copy->decimals; decimals= args[2]->decimals;
unsigned_flag= m_args0_copy->unsigned_flag; unsigned_flag= args[2]->unsigned_flag;
fix_char_length(m_args0_copy->max_char_length()); fix_char_length(args[2]->max_char_length());
maybe_null=1; maybe_null=1;
setup_args_and_comparator(current_thd, &cmp); setup_args_and_comparator(current_thd, &cmp);
} }
...@@ -2683,16 +2683,16 @@ void Item_func_nullif::print(String *str, enum_query_type query_type) ...@@ -2683,16 +2683,16 @@ void Item_func_nullif::print(String *str, enum_query_type query_type)
NULLIF(a,b) is implemented according to the SQL standard as a short for NULLIF(a,b) is implemented according to the SQL standard as a short for
CASE WHEN a=b THEN NULL ELSE a END CASE WHEN a=b THEN NULL ELSE a END
The constructor of Item_func_nullif sets args[0] and m_args0_copy to the The constructor of Item_func_nullif sets args[0] and args[2] to the
same item "a", and sets args[1] to "b". same item "a", and sets args[1] to "b".
If "this" is a part of a WHERE or ON condition, then: If "this" is a part of a WHERE or ON condition, then:
- the left "a" is a subject to equal field propagation with ANY_SUBST. - the left "a" is a subject to equal field propagation with ANY_SUBST.
- the right "a" is a subject to equal field propagation with IDENTITY_SUBST. - the right "a" is a subject to equal field propagation with IDENTITY_SUBST.
Therefore, after equal field propagation args[0] and m_args0_copy can point Therefore, after equal field propagation args[0] and args[2] can point
to different items. to different items.
*/ */
if (!(query_type & QT_ITEM_FUNC_NULLIF_TO_CASE) || args[0] == m_args0_copy) if (!(query_type & QT_ITEM_FUNC_NULLIF_TO_CASE) || args[0] == args[2])
{ {
/* /*
If no QT_ITEM_FUNC_NULLIF_TO_CASE is requested, If no QT_ITEM_FUNC_NULLIF_TO_CASE is requested,
...@@ -2701,7 +2701,7 @@ void Item_func_nullif::print(String *str, enum_query_type query_type) ...@@ -2701,7 +2701,7 @@ void Item_func_nullif::print(String *str, enum_query_type query_type)
SHOW CREATE {VIEW|FUNCTION|PROCEDURE} SHOW CREATE {VIEW|FUNCTION|PROCEDURE}
The original representation is possible only if The original representation is possible only if
args[0] and m_args0_copy still point to the same Item. args[0] and args[2] still point to the same Item.
The caller must pass call print() with QT_ITEM_FUNC_NULLIF_TO_CASE The caller must pass call print() with QT_ITEM_FUNC_NULLIF_TO_CASE
if an expression has undergone some optimization if an expression has undergone some optimization
...@@ -2713,10 +2713,10 @@ void Item_func_nullif::print(String *str, enum_query_type query_type) ...@@ -2713,10 +2713,10 @@ void Item_func_nullif::print(String *str, enum_query_type query_type)
Note, the EXPLAIN EXTENDED and EXPLAIN FORMAT=JSON routines Note, the EXPLAIN EXTENDED and EXPLAIN FORMAT=JSON routines
do pass QT_ITEM_FUNC_NULLIF_TO_CASE to print(). do pass QT_ITEM_FUNC_NULLIF_TO_CASE to print().
*/ */
DBUG_ASSERT(args[0] == m_args0_copy); DBUG_ASSERT(args[0] == args[2]);
str->append(func_name()); str->append(func_name());
str->append('('); str->append('(');
m_args0_copy->print(str, query_type); args[2]->print(str, query_type);
str->append(','); str->append(',');
args[1]->print(str, query_type); args[1]->print(str, query_type);
str->append(')'); str->append(')');
...@@ -2724,7 +2724,7 @@ void Item_func_nullif::print(String *str, enum_query_type query_type) ...@@ -2724,7 +2724,7 @@ void Item_func_nullif::print(String *str, enum_query_type query_type)
else else
{ {
/* /*
args[0] and m_args0_copy are different items. args[0] and args[2] are different items.
This is possible after WHERE optimization (equal fields propagation etc), This is possible after WHERE optimization (equal fields propagation etc),
e.g. in EXPLAIN EXTENDED or EXPLAIN FORMAT=JSON. e.g. in EXPLAIN EXTENDED or EXPLAIN FORMAT=JSON.
As it's not possible to print as a function with 2 arguments any more, As it's not possible to print as a function with 2 arguments any more,
...@@ -2735,7 +2735,7 @@ void Item_func_nullif::print(String *str, enum_query_type query_type) ...@@ -2735,7 +2735,7 @@ void Item_func_nullif::print(String *str, enum_query_type query_type)
str->append(STRING_WITH_LEN(" = ")); str->append(STRING_WITH_LEN(" = "));
args[1]->print(str, query_type); args[1]->print(str, query_type);
str->append(STRING_WITH_LEN(" then NULL else ")); str->append(STRING_WITH_LEN(" then NULL else "));
m_args0_copy->print(str, query_type); args[2]->print(str, query_type);
str->append(STRING_WITH_LEN(" end)")); str->append(STRING_WITH_LEN(" end)"));
} }
} }
...@@ -2761,8 +2761,8 @@ Item_func_nullif::real_op() ...@@ -2761,8 +2761,8 @@ Item_func_nullif::real_op()
null_value=1; null_value=1;
return 0.0; return 0.0;
} }
value= m_args0_copy->val_real(); value= args[2]->val_real();
null_value=m_args0_copy->null_value; null_value= args[2]->null_value;
return value; return value;
} }
...@@ -2776,8 +2776,8 @@ Item_func_nullif::int_op() ...@@ -2776,8 +2776,8 @@ Item_func_nullif::int_op()
null_value=1; null_value=1;
return 0; return 0;
} }
value=m_args0_copy->val_int(); value= args[2]->val_int();
null_value=m_args0_copy->null_value; null_value= args[2]->null_value;
return value; return value;
} }
...@@ -2791,8 +2791,8 @@ Item_func_nullif::str_op(String *str) ...@@ -2791,8 +2791,8 @@ Item_func_nullif::str_op(String *str)
null_value=1; null_value=1;
return 0; return 0;
} }
res=m_args0_copy->val_str(str); res= args[2]->val_str(str);
null_value=m_args0_copy->null_value; null_value= args[2]->null_value;
return res; return res;
} }
...@@ -2807,8 +2807,8 @@ Item_func_nullif::decimal_op(my_decimal * decimal_value) ...@@ -2807,8 +2807,8 @@ Item_func_nullif::decimal_op(my_decimal * decimal_value)
null_value=1; null_value=1;
return 0; return 0;
} }
res= m_args0_copy->val_decimal(decimal_value); res= args[2]->val_decimal(decimal_value);
null_value= m_args0_copy->null_value; null_value= args[2]->null_value;
return res; return res;
} }
...@@ -2819,14 +2819,14 @@ Item_func_nullif::date_op(MYSQL_TIME *ltime, uint fuzzydate) ...@@ -2819,14 +2819,14 @@ Item_func_nullif::date_op(MYSQL_TIME *ltime, uint fuzzydate)
DBUG_ASSERT(fixed == 1); DBUG_ASSERT(fixed == 1);
if (!cmp.compare()) if (!cmp.compare())
return (null_value= true); return (null_value= true);
return (null_value= m_args0_copy->get_date(ltime, fuzzydate)); return (null_value= args[2]->get_date(ltime, fuzzydate));
} }
bool bool
Item_func_nullif::is_null() Item_func_nullif::is_null()
{ {
return (null_value= (!cmp.compare() ? 1 : m_args0_copy->null_value)); return (null_value= (!cmp.compare() ? 1 : args[2]->null_value));
} }
......
...@@ -907,18 +907,26 @@ class Item_func_nullif :public Item_func_hybrid_field_type ...@@ -907,18 +907,26 @@ class Item_func_nullif :public Item_func_hybrid_field_type
{ {
Arg_comparator cmp; Arg_comparator cmp;
/* /*
Remember the first argument in case it will be substituted by either of: NULLIF(a,b) is a short for:
- convert_const_compared_to_int_field() CASE WHEN a=b THEN NULL ELSE a END
The left "a" is for comparison purposes.
The right "a" is for return value purposes.
These are two different "a" and they can be replaced to different items.
The left "a" is in a comparison and can be replaced by:
- Item_func::convert_const_compared_to_int_field()
- agg_item_set_converter() in set_cmp_func() - agg_item_set_converter() in set_cmp_func()
- cache_converted_constant() in set_cmp_func() - Arg_comparator::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. Both "a"s are subject to equal fields propagation and can be replaced by:
- Item_field::propagate_equal_fields(ANY_SUBST) for the left "a"
- Item_field::propagate_equal_fields(IDENTITY_SUBST) for the right "a"
*/ */
Item *m_args0_copy;
public: public:
// Put "a" to args[0] for comparison and to args[2] for the returned value.
Item_func_nullif(THD *thd, Item *a, Item *b): Item_func_nullif(THD *thd, Item *a, Item *b):
Item_func_hybrid_field_type(thd, a, b), Item_func_hybrid_field_type(thd, a, b, a)
m_args0_copy(a)
{} {}
bool date_op(MYSQL_TIME *ltime, uint fuzzydate); bool date_op(MYSQL_TIME *ltime, uint fuzzydate);
double real_op(); double real_op();
...@@ -926,18 +934,21 @@ public: ...@@ -926,18 +934,21 @@ public:
String *str_op(String *str); String *str_op(String *str);
my_decimal *decimal_op(my_decimal *); my_decimal *decimal_op(my_decimal *);
void fix_length_and_dec(); void fix_length_and_dec();
uint decimal_precision() const { return m_args0_copy->decimal_precision(); } uint decimal_precision() const { return args[2]->decimal_precision(); }
const char *func_name() const { return "nullif"; } const char *func_name() const { return "nullif"; }
void print(String *str, enum_query_type query_type); void print(String *str, enum_query_type query_type);
table_map not_null_tables() const { return 0; } table_map not_null_tables() const { return 0; }
bool is_null(); bool is_null();
Item* propagate_equal_fields(THD *thd, const Context &ctx, COND_EQUAL *cond) Item* propagate_equal_fields(THD *thd, const Context &ctx, COND_EQUAL *cond)
{ {
Item_args::propagate_equal_fields(thd, Context cmpctx(ANY_SUBST, cmp.compare_type(), cmp.cmp_collation.collation);
Context(ANY_SUBST, args[0]->propagate_equal_fields_and_change_item_tree(thd, cmpctx,
cmp.compare_type(), cond, &args[0]);
cmp.cmp_collation.collation), args[1]->propagate_equal_fields_and_change_item_tree(thd, cmpctx,
cond); cond, &args[1]);
args[2]->propagate_equal_fields_and_change_item_tree(thd,
Context_identity(),
cond, &args[2]);
return this; return this;
} }
}; };
......
...@@ -420,13 +420,10 @@ void Item_args::propagate_equal_fields(THD *thd, ...@@ -420,13 +420,10 @@ void Item_args::propagate_equal_fields(THD *thd,
const Item::Context &ctx, const Item::Context &ctx,
COND_EQUAL *cond) COND_EQUAL *cond)
{ {
Item **arg,**arg_end; uint i;
for (arg= args, arg_end= args + arg_count; arg != arg_end; arg++) for (i= 0; i < arg_count; i++)
{ args[i]->propagate_equal_fields_and_change_item_tree(thd, ctx, cond,
Item *new_item= (*arg)->propagate_equal_fields(thd, ctx, cond); &args[i]);
if (new_item && *arg != new_item)
thd->change_item_tree(arg, new_item);
}
} }
......
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