Skip to content

Commit 8e553c4

Browse files
author
Alexander Barkov
committed
MDEV-8785 Wrong results for EXPLAIN EXTENDED...WHERE NULLIF(latin1_col, _utf8'a' COLLATE utf8_bin) IS NOT NULL
1 parent 4278d6d commit 8e553c4

File tree

7 files changed

+132
-7
lines changed

7 files changed

+132
-7
lines changed

mysql-test/r/explain_json.result

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1090,3 +1090,23 @@ EXPLAIN
10901090
}
10911091
}
10921092
DROP TABLE t1;
1093+
#
1094+
# MDEV-8785 Wrong results for EXPLAIN EXTENDED...WHERE NULLIF(latin1_col, _utf8'a' COLLATE utf8_bin) IS NOT NULL
1095+
#
1096+
CREATE TABLE t1 (a VARCHAR(10) CHARACTER SET latin1);
1097+
INSERT INTO t1 VALUES ('a'),('A');
1098+
EXPLAIN FORMAT=JSON SELECT * FROM t1 WHERE NULLIF(a,_utf8'a' COLLATE utf8_bin);
1099+
EXPLAIN
1100+
{
1101+
"query_block": {
1102+
"select_id": 1,
1103+
"table": {
1104+
"table_name": "t1",
1105+
"access_type": "ALL",
1106+
"rows": 2,
1107+
"filtered": 100,
1108+
"attached_condition": "(case when convert(t1.a using utf8) = <cache>((_utf8'a' collate utf8_bin)) then NULL else t1.a end)"
1109+
}
1110+
}
1111+
}
1112+
DROP TABLE t1;

mysql-test/r/null.result

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1390,5 +1390,24 @@ Warning 1292 Incorrect datetime value: '1'
13901390
Warning 1292 Incorrect datetime value: '1'
13911391
DROP TABLE t1;
13921392
#
1393+
# MDEV-8785 Wrong results for EXPLAIN EXTENDED...WHERE NULLIF(latin1_col, _utf8'a' COLLATE utf8_bin) IS NOT NULL
1394+
#
1395+
CREATE TABLE t1 (a VARCHAR(10) CHARACTER SET latin1);
1396+
INSERT INTO t1 VALUES ('a'),('A');
1397+
SELECT a, NULLIF(a,_utf8'a' COLLATE utf8_bin) IS NULL FROM t1;
1398+
a NULLIF(a,_utf8'a' COLLATE utf8_bin) IS NULL
1399+
a 1
1400+
A 0
1401+
SELECT CHARSET(NULLIF(a,_utf8'a' COLLATE utf8_bin)) FROM t1;
1402+
CHARSET(NULLIF(a,_utf8'a' COLLATE utf8_bin))
1403+
latin1
1404+
latin1
1405+
EXPLAIN EXTENDED SELECT NULLIF(a,_utf8'a' COLLATE utf8_bin) IS NULL AS expr FROM t1;
1406+
id select_type table type possible_keys key key_len ref rows filtered Extra
1407+
1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00
1408+
Warnings:
1409+
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`
1410+
DROP TABLE t1;
1411+
#
13931412
# End of 10.1 tests
13941413
#

mysql-test/t/explain_json.test

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,3 +286,11 @@ CREATE TABLE t1 (a VARCHAR(10) CHARACTER SET latin1);
286286
INSERT INTO t1 VALUES ('a'),('b');
287287
EXPLAIN FORMAT=JSON SELECT * FROM t1 WHERE a=_latin1 0xDF;
288288
DROP TABLE t1;
289+
290+
--echo #
291+
--echo # MDEV-8785 Wrong results for EXPLAIN EXTENDED...WHERE NULLIF(latin1_col, _utf8'a' COLLATE utf8_bin) IS NOT NULL
292+
--echo #
293+
CREATE TABLE t1 (a VARCHAR(10) CHARACTER SET latin1);
294+
INSERT INTO t1 VALUES ('a'),('A');
295+
EXPLAIN FORMAT=JSON SELECT * FROM t1 WHERE NULLIF(a,_utf8'a' COLLATE utf8_bin);
296+
DROP TABLE t1;

mysql-test/t/null.test

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -868,6 +868,16 @@ CREATE TABLE t1 AS SELECT
868868
END AS b;
869869
DROP TABLE t1;
870870

871+
--echo #
872+
--echo # MDEV-8785 Wrong results for EXPLAIN EXTENDED...WHERE NULLIF(latin1_col, _utf8'a' COLLATE utf8_bin) IS NOT NULL
873+
--echo #
874+
CREATE TABLE t1 (a VARCHAR(10) CHARACTER SET latin1);
875+
INSERT INTO t1 VALUES ('a'),('A');
876+
SELECT a, NULLIF(a,_utf8'a' COLLATE utf8_bin) IS NULL FROM t1;
877+
SELECT CHARSET(NULLIF(a,_utf8'a' COLLATE utf8_bin)) FROM t1;
878+
EXPLAIN EXTENDED SELECT NULLIF(a,_utf8'a' COLLATE utf8_bin) IS NULL AS expr FROM t1;
879+
DROP TABLE t1;
880+
871881

872882
--echo #
873883
--echo # End of 10.1 tests

sql/item_cmpfunc.cc

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2677,6 +2677,70 @@ Item_func_nullif::fix_length_and_dec()
26772677
}
26782678

26792679

2680+
void Item_func_nullif::print(String *str, enum_query_type query_type)
2681+
{
2682+
/*
2683+
NULLIF(a,b) is implemented according to the SQL standard as a short for
2684+
CASE WHEN a=b THEN NULL ELSE a END
2685+
2686+
The constructor of Item_func_nullif sets args[0] and m_args0_copy to the
2687+
same item "a", and sets args[1] to "b".
2688+
2689+
If "this" is a part of a WHERE or ON condition, then:
2690+
- the left "a" is a subject to equal field propagation with ANY_SUBST.
2691+
- the right "a" is a subject to equal field propagation with IDENTITY_SUBST.
2692+
Therefore, after equal field propagation args[0] and m_args0_copy can point
2693+
to different items.
2694+
*/
2695+
if (!(query_type & QT_ITEM_FUNC_NULLIF_TO_CASE) || args[0] == m_args0_copy)
2696+
{
2697+
/*
2698+
If no QT_ITEM_FUNC_NULLIF_TO_CASE is requested,
2699+
that means we want the original NULLIF() representation,
2700+
e.g. when we are in:
2701+
SHOW CREATE {VIEW|FUNCTION|PROCEDURE}
2702+
2703+
The original representation is possible only if
2704+
args[0] and m_args0_copy still point to the same Item.
2705+
2706+
The caller must pass call print() with QT_ITEM_FUNC_NULLIF_TO_CASE
2707+
if an expression has undergone some optimization
2708+
(e.g. equal field propagation done in optimize_cond()) already and
2709+
NULLIF() potentially has two different representations of "a":
2710+
- one "a" for comparison
2711+
- another "a" for the returned value!
2712+
2713+
Note, the EXPLAIN EXTENDED and EXPLAIN FORMAT=JSON routines
2714+
do pass QT_ITEM_FUNC_NULLIF_TO_CASE to print().
2715+
*/
2716+
DBUG_ASSERT(args[0] == m_args0_copy);
2717+
str->append(func_name());
2718+
str->append('(');
2719+
m_args0_copy->print(str, query_type);
2720+
str->append(',');
2721+
args[1]->print(str, query_type);
2722+
str->append(')');
2723+
}
2724+
else
2725+
{
2726+
/*
2727+
args[0] and m_args0_copy are different items.
2728+
This is possible after WHERE optimization (equal fields propagation etc),
2729+
e.g. in EXPLAIN EXTENDED or EXPLAIN FORMAT=JSON.
2730+
As it's not possible to print as a function with 2 arguments any more,
2731+
do it in the CASE style.
2732+
*/
2733+
str->append(STRING_WITH_LEN("(case when "));
2734+
args[0]->print(str, query_type);
2735+
str->append(STRING_WITH_LEN(" = "));
2736+
args[1]->print(str, query_type);
2737+
str->append(STRING_WITH_LEN(" then NULL else "));
2738+
m_args0_copy->print(str, query_type);
2739+
str->append(STRING_WITH_LEN(" end)"));
2740+
}
2741+
}
2742+
2743+
26802744
/**
26812745
@note
26822746
Note that we have to evaluate the first argument twice as the compare

sql/item_cmpfunc.h

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -928,12 +928,7 @@ class Item_func_nullif :public Item_func_hybrid_field_type
928928
void fix_length_and_dec();
929929
uint decimal_precision() const { return m_args0_copy->decimal_precision(); }
930930
const char *func_name() const { return "nullif"; }
931-
932-
virtual inline void print(String *str, enum_query_type query_type)
933-
{
934-
Item_func::print(str, query_type);
935-
}
936-
931+
void print(String *str, enum_query_type query_type);
937932
table_map not_null_tables() const { return 0; }
938933
bool is_null();
939934
Item* propagate_equal_fields(THD *thd, const Context &ctx, COND_EQUAL *cond)

sql/mysqld.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -654,9 +654,18 @@ enum enum_query_type
654654
/// If Item_subselect should print as just "(subquery#1)"
655655
/// rather than display the subquery body
656656
QT_ITEM_SUBSELECT_ID_ONLY= (1 << 5),
657+
/// If NULLIF(a,b) should print itself as
658+
/// CASE WHEN a_for_comparison=b THEN NULL ELSE a_for_return_value END
659+
/// when "a" was replaced to two different items
660+
/// (e.g. by equal fields propagation in optimize_cond()).
661+
/// The default behaviour is to print as NULLIF(a_for_return, b)
662+
/// which should be Ok for SHOW CREATE {VIEW|PROCEDURE|FUNCTION}
663+
/// as they are not affected by WHERE optimization.
664+
QT_ITEM_FUNC_NULLIF_TO_CASE= (1 <<6),
657665

658666
/// This value means focus on readability, not on ability to parse back, etc.
659667
QT_EXPLAIN= QT_TO_SYSTEM_CHARSET |
668+
QT_ITEM_FUNC_NULLIF_TO_CASE |
660669
QT_ITEM_IDENT_SKIP_CURRENT_DATABASE |
661670
QT_ITEM_CACHE_WRAPPER_SKIP_DETAILS |
662671
QT_ITEM_SUBSELECT_ID_ONLY,
@@ -665,7 +674,7 @@ enum enum_query_type
665674
/// Be more detailed than QT_EXPLAIN.
666675
/// Perhaps we should eventually include QT_ITEM_IDENT_SKIP_CURRENT_DATABASE
667676
/// here, as it would give better readable results
668-
QT_EXPLAIN_EXTENDED= QT_TO_SYSTEM_CHARSET
677+
QT_EXPLAIN_EXTENDED= QT_TO_SYSTEM_CHARSET | QT_ITEM_FUNC_NULLIF_TO_CASE
669678
};
670679

671680

0 commit comments

Comments
 (0)