summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander Barkov <bar@mariadb.org>2015-09-10 17:13:35 +0400
committerAlexander Barkov <bar@mariadb.org>2015-09-10 17:13:35 +0400
commit4aebba3aeba2d413268455c3c8c7cbfd04e2f94f (patch)
tree048d73a20b078c43c6f567a5957e5e7e53005160
parent8e553c455c4740a51d2a7d0e23c3c79863b5df22 (diff)
downloadmariadb-git-4aebba3aeba2d413268455c3c8c7cbfd04e2f94f.tar.gz
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.
-rw-r--r--mysql-test/r/null.result56
-rw-r--r--mysql-test/t/null.test31
-rw-r--r--sql/item.cc12
-rw-r--r--sql/item.h5
-rw-r--r--sql/item_cmpfunc.cc56
-rw-r--r--sql/item_cmpfunc.h39
-rw-r--r--sql/item_func.cc11
7 files changed, 161 insertions, 49 deletions
diff --git a/mysql-test/r/null.result b/mysql-test/r/null.result
index e5ce6a10f09..04044602a68 100644
--- a/mysql-test/r/null.result
+++ b/mysql-test/r/null.result
@@ -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`
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
#
diff --git a/mysql-test/t/null.test b/mysql-test/t/null.test
index 695c22f3bbd..5347a961c59 100644
--- a/mysql-test/t/null.test
+++ b/mysql-test/t/null.test
@@ -880,5 +880,36 @@ 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 # End of 10.1 tests
--echo #
diff --git a/sql/item.cc b/sql/item.cc
index 9762ff3d5e7..3182f9d6a47 100644
--- a/sql/item.cc
+++ b/sql/item.cc
@@ -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()
{
/*
diff --git a/sql/item.h b/sql/item.h
index 3dfb7d8ec06..d3dbc543e82 100644
--- a/sql/item.h
+++ b/sql/item.h
@@ -1455,6 +1455,11 @@ public:
return this;
};
+ Item* propagate_equal_fields_and_change_item_tree(THD *thd,
+ const Context &ctx,
+ COND_EQUAL *cond,
+ Item **place);
+
/*
@brief
Processor used to check acceptability of an item in the defining
diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc
index 04afafd2915..7a7fd98dd74 100644
--- a/sql/item_cmpfunc.cc
+++ b/sql/item_cmpfunc.cc
@@ -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)
{
- 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())
{
int field;
@@ -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)
{
- DBUG_ASSERT(arg_count == 2);
+ DBUG_ASSERT(arg_count >= 2); // Item_func_nullif has arg_count == 3
if (args[0]->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)
DBUG_ASSERT(functype() != LIKE_FUNC);
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)
void
Item_func_nullif::fix_length_and_dec()
{
- if (!m_args0_copy) // Only false if EOM
+ if (!args[2]) // Only false if EOM
return;
- cached_result_type= m_args0_copy->result_type();
- cached_field_type= m_args0_copy->field_type();
- collation.set(m_args0_copy->collation);
- decimals= m_args0_copy->decimals;
- unsigned_flag= m_args0_copy->unsigned_flag;
- fix_char_length(m_args0_copy->max_char_length());
+ cached_result_type= args[2]->result_type();
+ cached_field_type= args[2]->field_type();
+ collation.set(args[2]->collation);
+ decimals= args[2]->decimals;
+ unsigned_flag= args[2]->unsigned_flag;
+ fix_char_length(args[2]->max_char_length());
maybe_null=1;
setup_args_and_comparator(current_thd, &cmp);
}
@@ -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
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".
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 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.
*/
- 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,
@@ -2701,7 +2701,7 @@ void Item_func_nullif::print(String *str, enum_query_type query_type)
SHOW CREATE {VIEW|FUNCTION|PROCEDURE}
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
if an expression has undergone some optimization
@@ -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
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('(');
- m_args0_copy->print(str, query_type);
+ args[2]->print(str, query_type);
str->append(',');
args[1]->print(str, query_type);
str->append(')');
@@ -2724,7 +2724,7 @@ void Item_func_nullif::print(String *str, enum_query_type query_type)
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),
e.g. in EXPLAIN EXTENDED or EXPLAIN FORMAT=JSON.
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)
str->append(STRING_WITH_LEN(" = "));
args[1]->print(str, query_type);
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)"));
}
}
@@ -2761,8 +2761,8 @@ Item_func_nullif::real_op()
null_value=1;
return 0.0;
}
- value= m_args0_copy->val_real();
- null_value=m_args0_copy->null_value;
+ value= args[2]->val_real();
+ null_value= args[2]->null_value;
return value;
}
@@ -2776,8 +2776,8 @@ Item_func_nullif::int_op()
null_value=1;
return 0;
}
- value=m_args0_copy->val_int();
- null_value=m_args0_copy->null_value;
+ value= args[2]->val_int();
+ null_value= args[2]->null_value;
return value;
}
@@ -2791,8 +2791,8 @@ Item_func_nullif::str_op(String *str)
null_value=1;
return 0;
}
- res=m_args0_copy->val_str(str);
- null_value=m_args0_copy->null_value;
+ res= args[2]->val_str(str);
+ null_value= args[2]->null_value;
return res;
}
@@ -2807,8 +2807,8 @@ Item_func_nullif::decimal_op(my_decimal * decimal_value)
null_value=1;
return 0;
}
- res= m_args0_copy->val_decimal(decimal_value);
- null_value= m_args0_copy->null_value;
+ res= args[2]->val_decimal(decimal_value);
+ null_value= args[2]->null_value;
return res;
}
@@ -2819,14 +2819,14 @@ 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));
+ return (null_value= args[2]->get_date(ltime, fuzzydate));
}
bool
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));
}
diff --git a/sql/item_cmpfunc.h b/sql/item_cmpfunc.h
index 436bbcd259a..cc94a8c08e6 100644
--- a/sql/item_cmpfunc.h
+++ b/sql/item_cmpfunc.h
@@ -907,18 +907,26 @@ class Item_func_nullif :public Item_func_hybrid_field_type
{
Arg_comparator cmp;
/*
- Remember the first argument in case it will be substituted by either of:
- - convert_const_compared_to_int_field()
+ NULLIF(a,b) is a short for:
+ 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()
- - 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.
+ - Arg_comparator::cache_converted_constant() in set_cmp_func()
+
+ 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:
+ // 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_hybrid_field_type(thd, a, b),
- m_args0_copy(a)
+ Item_func_hybrid_field_type(thd, a, b, a)
{}
bool date_op(MYSQL_TIME *ltime, uint fuzzydate);
double real_op();
@@ -926,18 +934,21 @@ public:
String *str_op(String *str);
my_decimal *decimal_op(my_decimal *);
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"; }
void print(String *str, enum_query_type query_type);
table_map not_null_tables() const { return 0; }
bool is_null();
Item* propagate_equal_fields(THD *thd, const Context &ctx, COND_EQUAL *cond)
{
- Item_args::propagate_equal_fields(thd,
- Context(ANY_SUBST,
- cmp.compare_type(),
- cmp.cmp_collation.collation),
- cond);
+ Context cmpctx(ANY_SUBST, cmp.compare_type(), cmp.cmp_collation.collation);
+ args[0]->propagate_equal_fields_and_change_item_tree(thd, cmpctx,
+ cond, &args[0]);
+ args[1]->propagate_equal_fields_and_change_item_tree(thd, cmpctx,
+ cond, &args[1]);
+ args[2]->propagate_equal_fields_and_change_item_tree(thd,
+ Context_identity(),
+ cond, &args[2]);
return this;
}
};
diff --git a/sql/item_func.cc b/sql/item_func.cc
index 23fb45fb037..bc4a39b577b 100644
--- a/sql/item_func.cc
+++ b/sql/item_func.cc
@@ -420,13 +420,10 @@ void Item_args::propagate_equal_fields(THD *thd,
const Item::Context &ctx,
COND_EQUAL *cond)
{
- Item **arg,**arg_end;
- for (arg= args, arg_end= args + arg_count; arg != arg_end; arg++)
- {
- Item *new_item= (*arg)->propagate_equal_fields(thd, ctx, cond);
- if (new_item && *arg != new_item)
- thd->change_item_tree(arg, new_item);
- }
+ uint i;
+ for (i= 0; i < arg_count; i++)
+ args[i]->propagate_equal_fields_and_change_item_tree(thd, ctx, cond,
+ &args[i]);
}