Commit 8e553c45 authored by Alexander Barkov's avatar Alexander Barkov

MDEV-8785 Wrong results for EXPLAIN EXTENDED...WHERE NULLIF(latin1_col,...

MDEV-8785 Wrong results for EXPLAIN EXTENDED...WHERE NULLIF(latin1_col, _utf8'a' COLLATE utf8_bin) IS NOT NULL
parent 4278d6d4
...@@ -1090,3 +1090,23 @@ EXPLAIN ...@@ -1090,3 +1090,23 @@ EXPLAIN
} }
} }
DROP TABLE t1; DROP TABLE t1;
#
# MDEV-8785 Wrong results for EXPLAIN EXTENDED...WHERE NULLIF(latin1_col, _utf8'a' COLLATE utf8_bin) IS NOT NULL
#
CREATE TABLE t1 (a VARCHAR(10) CHARACTER SET latin1);
INSERT INTO t1 VALUES ('a'),('A');
EXPLAIN FORMAT=JSON SELECT * FROM t1 WHERE NULLIF(a,_utf8'a' COLLATE utf8_bin);
EXPLAIN
{
"query_block": {
"select_id": 1,
"table": {
"table_name": "t1",
"access_type": "ALL",
"rows": 2,
"filtered": 100,
"attached_condition": "(case when convert(t1.a using utf8) = <cache>((_utf8'a' collate utf8_bin)) then NULL else t1.a end)"
}
}
}
DROP TABLE t1;
...@@ -1390,5 +1390,24 @@ Warning 1292 Incorrect datetime value: '1' ...@@ -1390,5 +1390,24 @@ Warning 1292 Incorrect datetime value: '1'
Warning 1292 Incorrect datetime value: '1' Warning 1292 Incorrect datetime value: '1'
DROP TABLE t1; DROP TABLE t1;
# #
# MDEV-8785 Wrong results for EXPLAIN EXTENDED...WHERE NULLIF(latin1_col, _utf8'a' COLLATE utf8_bin) IS NOT NULL
#
CREATE TABLE t1 (a VARCHAR(10) CHARACTER SET latin1);
INSERT INTO t1 VALUES ('a'),('A');
SELECT a, NULLIF(a,_utf8'a' COLLATE utf8_bin) IS NULL FROM t1;
a NULLIF(a,_utf8'a' COLLATE utf8_bin) IS NULL
a 1
A 0
SELECT CHARSET(NULLIF(a,_utf8'a' COLLATE utf8_bin)) FROM t1;
CHARSET(NULLIF(a,_utf8'a' COLLATE utf8_bin))
latin1
latin1
EXPLAIN EXTENDED SELECT NULLIF(a,_utf8'a' COLLATE utf8_bin) IS NULL AS expr FROM t1;
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
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;
#
# End of 10.1 tests # End of 10.1 tests
# #
...@@ -286,3 +286,11 @@ CREATE TABLE t1 (a VARCHAR(10) CHARACTER SET latin1); ...@@ -286,3 +286,11 @@ CREATE TABLE t1 (a VARCHAR(10) CHARACTER SET latin1);
INSERT INTO t1 VALUES ('a'),('b'); INSERT INTO t1 VALUES ('a'),('b');
EXPLAIN FORMAT=JSON SELECT * FROM t1 WHERE a=_latin1 0xDF; EXPLAIN FORMAT=JSON SELECT * FROM t1 WHERE a=_latin1 0xDF;
DROP TABLE t1; DROP TABLE t1;
--echo #
--echo # MDEV-8785 Wrong results for EXPLAIN EXTENDED...WHERE NULLIF(latin1_col, _utf8'a' COLLATE utf8_bin) IS NOT NULL
--echo #
CREATE TABLE t1 (a VARCHAR(10) CHARACTER SET latin1);
INSERT INTO t1 VALUES ('a'),('A');
EXPLAIN FORMAT=JSON SELECT * FROM t1 WHERE NULLIF(a,_utf8'a' COLLATE utf8_bin);
DROP TABLE t1;
...@@ -868,6 +868,16 @@ CREATE TABLE t1 AS SELECT ...@@ -868,6 +868,16 @@ CREATE TABLE t1 AS SELECT
END AS b; END AS b;
DROP TABLE t1; DROP TABLE t1;
--echo #
--echo # MDEV-8785 Wrong results for EXPLAIN EXTENDED...WHERE NULLIF(latin1_col, _utf8'a' COLLATE utf8_bin) IS NOT NULL
--echo #
CREATE TABLE t1 (a VARCHAR(10) CHARACTER SET latin1);
INSERT INTO t1 VALUES ('a'),('A');
SELECT a, NULLIF(a,_utf8'a' COLLATE utf8_bin) IS NULL FROM t1;
SELECT CHARSET(NULLIF(a,_utf8'a' COLLATE utf8_bin)) FROM t1;
EXPLAIN EXTENDED SELECT NULLIF(a,_utf8'a' COLLATE utf8_bin) IS NULL AS expr FROM t1;
DROP TABLE t1;
--echo # --echo #
--echo # End of 10.1 tests --echo # End of 10.1 tests
......
...@@ -2677,6 +2677,70 @@ Item_func_nullif::fix_length_and_dec() ...@@ -2677,6 +2677,70 @@ Item_func_nullif::fix_length_and_dec()
} }
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
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
to different items.
*/
if (!(query_type & QT_ITEM_FUNC_NULLIF_TO_CASE) || args[0] == m_args0_copy)
{
/*
If no QT_ITEM_FUNC_NULLIF_TO_CASE is requested,
that means we want the original NULLIF() representation,
e.g. when we are in:
SHOW CREATE {VIEW|FUNCTION|PROCEDURE}
The original representation is possible only if
args[0] and m_args0_copy 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
(e.g. equal field propagation done in optimize_cond()) already and
NULLIF() potentially has two different representations of "a":
- one "a" for comparison
- another "a" for the returned value!
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);
str->append(func_name());
str->append('(');
m_args0_copy->print(str, query_type);
str->append(',');
args[1]->print(str, query_type);
str->append(')');
}
else
{
/*
args[0] and m_args0_copy 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,
do it in the CASE style.
*/
str->append(STRING_WITH_LEN("(case when "));
args[0]->print(str, 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);
str->append(STRING_WITH_LEN(" end)"));
}
}
/** /**
@note @note
Note that we have to evaluate the first argument twice as the compare Note that we have to evaluate the first argument twice as the compare
......
...@@ -928,12 +928,7 @@ class Item_func_nullif :public Item_func_hybrid_field_type ...@@ -928,12 +928,7 @@ class Item_func_nullif :public Item_func_hybrid_field_type
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 m_args0_copy->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);
virtual inline void print(String *str, enum_query_type query_type)
{
Item_func::print(str, 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)
......
...@@ -654,9 +654,18 @@ enum enum_query_type ...@@ -654,9 +654,18 @@ enum enum_query_type
/// If Item_subselect should print as just "(subquery#1)" /// If Item_subselect should print as just "(subquery#1)"
/// rather than display the subquery body /// rather than display the subquery body
QT_ITEM_SUBSELECT_ID_ONLY= (1 << 5), QT_ITEM_SUBSELECT_ID_ONLY= (1 << 5),
/// If NULLIF(a,b) should print itself as
/// CASE WHEN a_for_comparison=b THEN NULL ELSE a_for_return_value END
/// when "a" was replaced to two different items
/// (e.g. by equal fields propagation in optimize_cond()).
/// The default behaviour is to print as NULLIF(a_for_return, b)
/// which should be Ok for SHOW CREATE {VIEW|PROCEDURE|FUNCTION}
/// as they are not affected by WHERE optimization.
QT_ITEM_FUNC_NULLIF_TO_CASE= (1 <<6),
/// This value means focus on readability, not on ability to parse back, etc. /// This value means focus on readability, not on ability to parse back, etc.
QT_EXPLAIN= QT_TO_SYSTEM_CHARSET | QT_EXPLAIN= QT_TO_SYSTEM_CHARSET |
QT_ITEM_FUNC_NULLIF_TO_CASE |
QT_ITEM_IDENT_SKIP_CURRENT_DATABASE | QT_ITEM_IDENT_SKIP_CURRENT_DATABASE |
QT_ITEM_CACHE_WRAPPER_SKIP_DETAILS | QT_ITEM_CACHE_WRAPPER_SKIP_DETAILS |
QT_ITEM_SUBSELECT_ID_ONLY, QT_ITEM_SUBSELECT_ID_ONLY,
...@@ -665,7 +674,7 @@ enum enum_query_type ...@@ -665,7 +674,7 @@ enum enum_query_type
/// Be more detailed than QT_EXPLAIN. /// Be more detailed than QT_EXPLAIN.
/// Perhaps we should eventually include QT_ITEM_IDENT_SKIP_CURRENT_DATABASE /// Perhaps we should eventually include QT_ITEM_IDENT_SKIP_CURRENT_DATABASE
/// here, as it would give better readable results /// here, as it would give better readable results
QT_EXPLAIN_EXTENDED= QT_TO_SYSTEM_CHARSET QT_EXPLAIN_EXTENDED= QT_TO_SYSTEM_CHARSET | QT_ITEM_FUNC_NULLIF_TO_CASE
}; };
......
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