Skip to content

Commit 4aebba3

Browse files
author
Alexander Barkov
committed
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.
1 parent 8e553c4 commit 4aebba3

File tree

7 files changed

+161
-49
lines changed

7 files changed

+161
-49
lines changed

mysql-test/r/null.result

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1409,5 +1409,61 @@ Warnings:
14091409
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`
14101410
DROP TABLE t1;
14111411
#
1412+
# MDEV-8740 Wrong result for SELECT..WHERE year_field=10 AND NULLIF(year_field,2011.1)='2011'
1413+
#
1414+
CREATE TABLE t1 (a YEAR);
1415+
INSERT INTO t1 VALUES (2010),(2011);
1416+
SELECT a=10 AND NULLIF(a,2011.1)='2011' AS cond FROM t1;
1417+
cond
1418+
0
1419+
0
1420+
SELECT * FROM t1 WHERE a=10;
1421+
a
1422+
2010
1423+
SELECT * FROM t1 WHERE NULLIF(a,2011.1)='2011';
1424+
a
1425+
SELECT * FROM t1 WHERE a=10 AND NULLIF(a,2011.1)='2011';
1426+
a
1427+
EXPLAIN EXTENDED
1428+
SELECT * FROM t1 WHERE a=10 AND NULLIF(a,2011.1)='2011';
1429+
id select_type table type possible_keys key key_len ref rows filtered Extra
1430+
1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL Impossible WHERE
1431+
Warnings:
1432+
Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where 0
1433+
EXPLAIN EXTENDED
1434+
SELECT * FROM t1 WHERE a=10 AND NULLIF(a,2011.1)=CONCAT('2011',RAND());
1435+
id select_type table type possible_keys key key_len ref rows filtered Extra
1436+
1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 Using where
1437+
Warnings:
1438+
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())))
1439+
DROP TABLE t1;
1440+
#
1441+
# MDEV-8754 Wrong result for SELECT..WHERE year_field=2020 AND NULLIF(year_field,2010)='2020'
1442+
#
1443+
CREATE TABLE t1 (a YEAR);
1444+
INSERT INTO t1 VALUES (2010),(2020);
1445+
SELECT * FROM t1 WHERE a=2020;
1446+
a
1447+
2020
1448+
SELECT * FROM t1 WHERE NULLIF(a,2010)='2020';
1449+
a
1450+
2020
1451+
SELECT * FROM t1 WHERE a=2020 AND NULLIF(a,2010)='2020';
1452+
a
1453+
2020
1454+
EXPLAIN EXTENDED
1455+
SELECT * FROM t1 WHERE a=2020 AND NULLIF(a,2010)='2020';
1456+
id select_type table type possible_keys key key_len ref rows filtered Extra
1457+
1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 Using where
1458+
Warnings:
1459+
Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where (`test`.`t1`.`a` = 2020)
1460+
EXPLAIN EXTENDED
1461+
SELECT * FROM t1 WHERE a=2020 AND NULLIF(a,2010)=CONCAT('2020',RAND());
1462+
id select_type table type possible_keys key key_len ref rows filtered Extra
1463+
1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 Using where
1464+
Warnings:
1465+
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())))
1466+
DROP TABLE t1;
1467+
#
14121468
# End of 10.1 tests
14131469
#

mysql-test/t/null.test

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -879,6 +879,37 @@ EXPLAIN EXTENDED SELECT NULLIF(a,_utf8'a' COLLATE utf8_bin) IS NULL AS expr FROM
879879
DROP TABLE t1;
880880

881881

882+
--echo #
883+
--echo # MDEV-8740 Wrong result for SELECT..WHERE year_field=10 AND NULLIF(year_field,2011.1)='2011'
884+
--echo #
885+
CREATE TABLE t1 (a YEAR);
886+
INSERT INTO t1 VALUES (2010),(2011);
887+
SELECT a=10 AND NULLIF(a,2011.1)='2011' AS cond FROM t1;
888+
SELECT * FROM t1 WHERE a=10;
889+
SELECT * FROM t1 WHERE NULLIF(a,2011.1)='2011';
890+
SELECT * FROM t1 WHERE a=10 AND NULLIF(a,2011.1)='2011';
891+
EXPLAIN EXTENDED
892+
SELECT * FROM t1 WHERE a=10 AND NULLIF(a,2011.1)='2011';
893+
EXPLAIN EXTENDED
894+
SELECT * FROM t1 WHERE a=10 AND NULLIF(a,2011.1)=CONCAT('2011',RAND());
895+
DROP TABLE t1;
896+
897+
898+
--echo #
899+
--echo # MDEV-8754 Wrong result for SELECT..WHERE year_field=2020 AND NULLIF(year_field,2010)='2020'
900+
--echo #
901+
CREATE TABLE t1 (a YEAR);
902+
INSERT INTO t1 VALUES (2010),(2020);
903+
SELECT * FROM t1 WHERE a=2020;
904+
SELECT * FROM t1 WHERE NULLIF(a,2010)='2020';
905+
SELECT * FROM t1 WHERE a=2020 AND NULLIF(a,2010)='2020';
906+
EXPLAIN EXTENDED
907+
SELECT * FROM t1 WHERE a=2020 AND NULLIF(a,2010)='2020';
908+
EXPLAIN EXTENDED
909+
SELECT * FROM t1 WHERE a=2020 AND NULLIF(a,2010)=CONCAT('2020',RAND());
910+
DROP TABLE t1;
911+
912+
882913
--echo #
883914
--echo # End of 10.1 tests
884915
--echo #

sql/item.cc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6683,6 +6683,18 @@ bool Item_field::send(Protocol *protocol, String *buffer)
66836683
}
66846684

66856685

6686+
Item* Item::propagate_equal_fields_and_change_item_tree(THD *thd,
6687+
const Context &ctx,
6688+
COND_EQUAL *cond,
6689+
Item **place)
6690+
{
6691+
Item *item= propagate_equal_fields(thd, ctx, cond);
6692+
if (item && item != this)
6693+
thd->change_item_tree(place, item);
6694+
return item;
6695+
}
6696+
6697+
66866698
void Item_field::update_null_value()
66876699
{
66886700
/*

sql/item.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1455,6 +1455,11 @@ class Item: public Value_source, public Type_std_attributes
14551455
return this;
14561456
};
14571457

1458+
Item* propagate_equal_fields_and_change_item_tree(THD *thd,
1459+
const Context &ctx,
1460+
COND_EQUAL *cond,
1461+
Item **place);
1462+
14581463
/*
14591464
@brief
14601465
Processor used to check acceptability of an item in the defining

sql/item_cmpfunc.cc

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,7 @@ static bool convert_const_to_int(THD *thd, Item_field *field_item,
473473
*/
474474
void Item_func::convert_const_compared_to_int_field(THD *thd)
475475
{
476-
DBUG_ASSERT(arg_count == 2);
476+
DBUG_ASSERT(arg_count >= 2); // Item_func_nullif has arg_count == 3
477477
if (!thd->lex->is_ps_or_view_context_analysis())
478478
{
479479
int field;
@@ -491,7 +491,7 @@ void Item_func::convert_const_compared_to_int_field(THD *thd)
491491

492492
bool Item_func::setup_args_and_comparator(THD *thd, Arg_comparator *cmp)
493493
{
494-
DBUG_ASSERT(arg_count == 2);
494+
DBUG_ASSERT(arg_count >= 2); // Item_func_nullif has arg_count == 3
495495

496496
if (args[0]->cmp_type() == STRING_RESULT &&
497497
args[1]->cmp_type() == STRING_RESULT &&
@@ -502,7 +502,7 @@ bool Item_func::setup_args_and_comparator(THD *thd, Arg_comparator *cmp)
502502
DBUG_ASSERT(functype() != LIKE_FUNC);
503503
convert_const_compared_to_int_field(thd);
504504

505-
return cmp->set_cmp_func(this, tmp_arg, tmp_arg + 1, true);
505+
return cmp->set_cmp_func(this, &args[0], &args[1], true);
506506
}
507507

508508

@@ -2663,15 +2663,15 @@ bool Item_func_if::date_op(MYSQL_TIME *ltime, uint fuzzydate)
26632663
void
26642664
Item_func_nullif::fix_length_and_dec()
26652665
{
2666-
if (!m_args0_copy)// Only false if EOM
2666+
if (!args[2])// Only false if EOM
26672667
return;
26682668

2669-
cached_result_type= m_args0_copy->result_type();
2670-
cached_field_type= m_args0_copy->field_type();
2671-
collation.set(m_args0_copy->collation);
2672-
decimals= m_args0_copy->decimals;
2673-
unsigned_flag= m_args0_copy->unsigned_flag;
2674-
fix_char_length(m_args0_copy->max_char_length());
2669+
cached_result_type= args[2]->result_type();
2670+
cached_field_type= args[2]->field_type();
2671+
collation.set(args[2]->collation);
2672+
decimals= args[2]->decimals;
2673+
unsigned_flag= args[2]->unsigned_flag;
2674+
fix_char_length(args[2]->max_char_length());
26752675
maybe_null=1;
26762676
setup_args_and_comparator(current_thd, &cmp);
26772677
}
@@ -2683,16 +2683,16 @@ void Item_func_nullif::print(String *str, enum_query_type query_type)
26832683
NULLIF(a,b) is implemented according to the SQL standard as a short for
26842684
CASE WHEN a=b THEN NULL ELSE a END
26852685
2686-
The constructor of Item_func_nullif sets args[0] and m_args0_copy to the
2686+
The constructor of Item_func_nullif sets args[0] and args[2] to the
26872687
same item "a", and sets args[1] to "b".
26882688
26892689
If "this" is a part of a WHERE or ON condition, then:
26902690
- the left "a" is a subject to equal field propagation with ANY_SUBST.
26912691
- 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
2692+
Therefore, after equal field propagation args[0] and args[2] can point
26932693
to different items.
26942694
*/
2695-
if (!(query_type & QT_ITEM_FUNC_NULLIF_TO_CASE) || args[0] == m_args0_copy)
2695+
if (!(query_type & QT_ITEM_FUNC_NULLIF_TO_CASE) || args[0] == args[2])
26962696
{
26972697
/*
26982698
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)
27012701
SHOW CREATE {VIEW|FUNCTION|PROCEDURE}
27022702
27032703
The original representation is possible only if
2704-
args[0] and m_args0_copy still point to the same Item.
2704+
args[0] and args[2] still point to the same Item.
27052705
27062706
The caller must pass call print() with QT_ITEM_FUNC_NULLIF_TO_CASE
27072707
if an expression has undergone some optimization
@@ -2713,18 +2713,18 @@ void Item_func_nullif::print(String *str, enum_query_type query_type)
27132713
Note, the EXPLAIN EXTENDED and EXPLAIN FORMAT=JSON routines
27142714
do pass QT_ITEM_FUNC_NULLIF_TO_CASE to print().
27152715
*/
2716-
DBUG_ASSERT(args[0] == m_args0_copy);
2716+
DBUG_ASSERT(args[0] == args[2]);
27172717
str->append(func_name());
27182718
str->append('(');
2719-
m_args0_copy->print(str, query_type);
2719+
args[2]->print(str, query_type);
27202720
str->append(',');
27212721
args[1]->print(str, query_type);
27222722
str->append(')');
27232723
}
27242724
else
27252725
{
27262726
/*
2727-
args[0] and m_args0_copy are different items.
2727+
args[0] and args[2] are different items.
27282728
This is possible after WHERE optimization (equal fields propagation etc),
27292729
e.g. in EXPLAIN EXTENDED or EXPLAIN FORMAT=JSON.
27302730
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)
27352735
str->append(STRING_WITH_LEN(" = "));
27362736
args[1]->print(str, query_type);
27372737
str->append(STRING_WITH_LEN(" then NULL else "));
2738-
m_args0_copy->print(str, query_type);
2738+
args[2]->print(str, query_type);
27392739
str->append(STRING_WITH_LEN(" end)"));
27402740
}
27412741
}
@@ -2761,8 +2761,8 @@ Item_func_nullif::real_op()
27612761
null_value=1;
27622762
return 0.0;
27632763
}
2764-
value= m_args0_copy->val_real();
2765-
null_value=m_args0_copy->null_value;
2764+
value= args[2]->val_real();
2765+
null_value= args[2]->null_value;
27662766
return value;
27672767
}
27682768

@@ -2776,8 +2776,8 @@ Item_func_nullif::int_op()
27762776
null_value=1;
27772777
return 0;
27782778
}
2779-
value=m_args0_copy->val_int();
2780-
null_value=m_args0_copy->null_value;
2779+
value= args[2]->val_int();
2780+
null_value= args[2]->null_value;
27812781
return value;
27822782
}
27832783

@@ -2791,8 +2791,8 @@ Item_func_nullif::str_op(String *str)
27912791
null_value=1;
27922792
return 0;
27932793
}
2794-
res=m_args0_copy->val_str(str);
2795-
null_value=m_args0_copy->null_value;
2794+
res= args[2]->val_str(str);
2795+
null_value= args[2]->null_value;
27962796
return res;
27972797
}
27982798

@@ -2807,8 +2807,8 @@ Item_func_nullif::decimal_op(my_decimal * decimal_value)
28072807
null_value=1;
28082808
return 0;
28092809
}
2810-
res= m_args0_copy->val_decimal(decimal_value);
2811-
null_value= m_args0_copy->null_value;
2810+
res= args[2]->val_decimal(decimal_value);
2811+
null_value= args[2]->null_value;
28122812
return res;
28132813
}
28142814

@@ -2819,14 +2819,14 @@ Item_func_nullif::date_op(MYSQL_TIME *ltime, uint fuzzydate)
28192819
DBUG_ASSERT(fixed == 1);
28202820
if (!cmp.compare())
28212821
return (null_value= true);
2822-
return (null_value= m_args0_copy->get_date(ltime, fuzzydate));
2822+
return (null_value= args[2]->get_date(ltime, fuzzydate));
28232823
}
28242824

28252825

28262826
bool
28272827
Item_func_nullif::is_null()
28282828
{
2829-
return (null_value= (!cmp.compare() ? 1 : m_args0_copy->null_value));
2829+
return (null_value= (!cmp.compare() ? 1 : args[2]->null_value));
28302830
}
28312831

28322832

sql/item_cmpfunc.h

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -907,37 +907,48 @@ class Item_func_nullif :public Item_func_hybrid_field_type
907907
{
908908
Arg_comparator cmp;
909909
/*
910-
Remember the first argument in case it will be substituted by either of:
911-
- convert_const_compared_to_int_field()
910+
NULLIF(a,b) is a short for:
911+
CASE WHEN a=b THEN NULL ELSE a END
912+
913+
The left "a" is for comparison purposes.
914+
The right "a" is for return value purposes.
915+
These are two different "a" and they can be replaced to different items.
916+
917+
The left "a" is in a comparison and can be replaced by:
918+
- Item_func::convert_const_compared_to_int_field()
912919
- agg_item_set_converter() in set_cmp_func()
913-
- cache_converted_constant() in set_cmp_func()
914-
The original item will be stored in m_arg0_copy, to return result.
915-
The substituted item will be stored in args[0], for comparison purposes.
920+
- Arg_comparator::cache_converted_constant() in set_cmp_func()
921+
922+
Both "a"s are subject to equal fields propagation and can be replaced by:
923+
- Item_field::propagate_equal_fields(ANY_SUBST) for the left "a"
924+
- Item_field::propagate_equal_fields(IDENTITY_SUBST) for the right "a"
916925
*/
917-
Item *m_args0_copy;
918926
public:
927+
// Put "a" to args[0] for comparison and to args[2] for the returned value.
919928
Item_func_nullif(THD *thd, Item *a, Item *b):
920-
Item_func_hybrid_field_type(thd, a, b),
921-
m_args0_copy(a)
929+
Item_func_hybrid_field_type(thd, a, b, a)
922930
{}
923931
bool date_op(MYSQL_TIME *ltime, uint fuzzydate);
924932
double real_op();
925933
longlong int_op();
926934
String *str_op(String *str);
927935
my_decimal *decimal_op(my_decimal *);
928936
void fix_length_and_dec();
929-
uint decimal_precision() const { return m_args0_copy->decimal_precision(); }
937+
uint decimal_precision() const { return args[2]->decimal_precision(); }
930938
const char *func_name() const { return "nullif"; }
931939
void print(String *str, enum_query_type query_type);
932940
table_map not_null_tables() const { return 0; }
933941
bool is_null();
934942
Item* propagate_equal_fields(THD *thd, const Context &ctx, COND_EQUAL *cond)
935943
{
936-
Item_args::propagate_equal_fields(thd,
937-
Context(ANY_SUBST,
938-
cmp.compare_type(),
939-
cmp.cmp_collation.collation),
940-
cond);
944+
Context cmpctx(ANY_SUBST, cmp.compare_type(), cmp.cmp_collation.collation);
945+
args[0]->propagate_equal_fields_and_change_item_tree(thd, cmpctx,
946+
cond, &args[0]);
947+
args[1]->propagate_equal_fields_and_change_item_tree(thd, cmpctx,
948+
cond, &args[1]);
949+
args[2]->propagate_equal_fields_and_change_item_tree(thd,
950+
Context_identity(),
951+
cond, &args[2]);
941952
return this;
942953
}
943954
};

sql/item_func.cc

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -420,13 +420,10 @@ void Item_args::propagate_equal_fields(THD *thd,
420420
const Item::Context &ctx,
421421
COND_EQUAL *cond)
422422
{
423-
Item **arg,**arg_end;
424-
for (arg= args, arg_end= args + arg_count; arg != arg_end; arg++)
425-
{
426-
Item *new_item= (*arg)->propagate_equal_fields(thd, ctx, cond);
427-
if (new_item && *arg != new_item)
428-
thd->change_item_tree(arg, new_item);
429-
}
423+
uint i;
424+
for (i= 0; i < arg_count; i++)
425+
args[i]->propagate_equal_fields_and_change_item_tree(thd, ctx, cond,
426+
&args[i]);
430427
}
431428

432429

0 commit comments

Comments
 (0)