Skip to content

Commit 102a85f

Browse files
committed
MDEV-8663: IF Statement returns multiple values erroneously (or Assertion `!null_value' failed in Item::send(Protocol*, String*))
Postreview addons by Bar Fix: keeping contract: NULL value mean NULL pointer in val_str and val_deciman.
1 parent fa51f70 commit 102a85f

File tree

5 files changed

+82
-31
lines changed

5 files changed

+82
-31
lines changed

mysql-test/r/func_if.result

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,3 +234,20 @@ SELECT if(1, NULL, (SELECT min('hello')));
234234
if(1, NULL, (SELECT min('hello')))
235235
NULL
236236
End of 5.2 tests
237+
#
238+
# MDEV-8663: IF Statement returns multiple values erroneously
239+
# (or Assertion `!null_value' failed in Item::send(Protocol*, String*)
240+
#
241+
CREATE TABLE `t1` (
242+
`datas` VARCHAR(25) NOT NULL
243+
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
244+
Warnings:
245+
Warning 1286 Unknown storage engine 'InnoDB'
246+
Warning 1266 Using storage engine MyISAM for table 't1'
247+
INSERT INTO `t1` VALUES ('1,2'), ('2,3'), ('3,4');
248+
SELECT IF(FIND_IN_SET('1', `datas`), 1.5, IF(FIND_IN_SET('2', `datas`), 2, NULL)) AS `First`, '1' AS `Second`, '2' AS `Third` FROM `t1`;
249+
First Second Third
250+
1.5 1 2
251+
2.0 1 2
252+
NULL 1 2
253+
drop table t1;

mysql-test/t/func_if.test

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,20 @@ SELECT if(1, NULL, (SELECT min('hello')));
206206

207207
--echo End of 5.2 tests
208208

209+
--echo #
210+
--echo # MDEV-8663: IF Statement returns multiple values erroneously
211+
--echo # (or Assertion `!null_value' failed in Item::send(Protocol*, String*)
212+
--echo #
213+
CREATE TABLE `t1` (
214+
`datas` VARCHAR(25) NOT NULL
215+
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
216+
217+
INSERT INTO `t1` VALUES ('1,2'), ('2,3'), ('3,4');
218+
219+
SELECT IF(FIND_IN_SET('1', `datas`), 1.5, IF(FIND_IN_SET('2', `datas`), 2, NULL)) AS `First`, '1' AS `Second`, '2' AS `Third` FROM `t1`;
220+
221+
drop table t1;
222+
209223
--disable_query_log
210224
# Restore timezone to default
211225
set time_zone= @@global.time_zone;

sql/item_cmpfunc.cc

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2692,7 +2692,8 @@ Item_func_if::str_op(String *str)
26922692
String *res=arg->val_str(str);
26932693
if (res)
26942694
res->set_charset(collation.collation);
2695-
null_value=arg->null_value;
2695+
if (null_value=arg->null_value)
2696+
res= NULL;
26962697
return res;
26972698
}
26982699

@@ -2703,7 +2704,8 @@ Item_func_if::decimal_op(my_decimal *decimal_value)
27032704
DBUG_ASSERT(fixed == 1);
27042705
Item *arg= args[0]->val_bool() ? args[1] : args[2];
27052706
my_decimal *value= arg->val_decimal(decimal_value);
2706-
null_value= arg->null_value;
2707+
if (null_value= arg->null_value)
2708+
value= NULL;
27072709
return value;
27082710
}
27092711

sql/item_func.cc

Lines changed: 24 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -887,7 +887,7 @@ String *Item_func_hybrid_result_type::val_str(String *str)
887887
case DECIMAL_RESULT:
888888
{
889889
my_decimal decimal_value, *val;
890-
if (!(val= decimal_op(&decimal_value)))
890+
if (!(val= decimal_op_with_null_check(&decimal_value)))
891891
return 0; // null is set
892892
my_decimal_round(E_DEC_FATAL_ERROR, val, decimals, FALSE, val);
893893
str->set_charset(collation.collation);
@@ -914,24 +914,22 @@ String *Item_func_hybrid_result_type::val_str(String *str)
914914
if (is_temporal_type(field_type()))
915915
{
916916
MYSQL_TIME ltime;
917-
if (date_op(&ltime,
918-
field_type() == MYSQL_TYPE_TIME ? TIME_TIME_ONLY : 0) ||
919-
str->alloc(MAX_DATE_STRING_REP_LENGTH))
920-
{
921-
null_value= 1;
917+
if (date_op_with_null_check(&ltime) ||
918+
(null_value= str->alloc(MAX_DATE_STRING_REP_LENGTH)))
922919
return (String *) 0;
923-
}
924920
ltime.time_type= mysql_type_to_time_type(field_type());
925921
str->length(my_TIME_to_str(&ltime, const_cast<char*>(str->ptr()), decimals));
926922
str->set_charset(&my_charset_bin);
923+
DBUG_ASSERT(!null_value);
927924
return str;
928925
}
929-
return str_op(&str_value);
926+
return str_op_with_null_check(&str_value);
930927
case TIME_RESULT:
931928
case ROW_RESULT:
932929
case IMPOSSIBLE_RESULT:
933930
DBUG_ASSERT(0);
934931
}
932+
DBUG_ASSERT(!null_value || (str == NULL));
935933
return str;
936934
}
937935

@@ -944,7 +942,7 @@ double Item_func_hybrid_result_type::val_real()
944942
{
945943
my_decimal decimal_value, *val;
946944
double result;
947-
if (!(val= decimal_op(&decimal_value)))
945+
if (!(val= decimal_op_with_null_check(&decimal_value)))
948946
return 0.0; // null is set
949947
my_decimal2double(E_DEC_FATAL_ERROR, val, &result);
950948
return result;
@@ -961,18 +959,14 @@ double Item_func_hybrid_result_type::val_real()
961959
if (is_temporal_type(field_type()))
962960
{
963961
MYSQL_TIME ltime;
964-
if (date_op(&ltime,
965-
field_type() == MYSQL_TYPE_TIME ? TIME_TIME_ONLY : 0 ))
966-
{
967-
null_value= 1;
962+
if (date_op_with_null_check(&ltime))
968963
return 0;
969-
}
970964
ltime.time_type= mysql_type_to_time_type(field_type());
971965
return TIME_to_double(&ltime);
972966
}
973967
char *end_not_used;
974968
int err_not_used;
975-
String *res= str_op(&str_value);
969+
String *res= str_op_with_null_check(&str_value);
976970
return (res ? my_strntod(res->charset(), (char*) res->ptr(), res->length(),
977971
&end_not_used, &err_not_used) : 0.0);
978972
}
@@ -992,7 +986,7 @@ longlong Item_func_hybrid_result_type::val_int()
992986
case DECIMAL_RESULT:
993987
{
994988
my_decimal decimal_value, *val;
995-
if (!(val= decimal_op(&decimal_value)))
989+
if (!(val= decimal_op_with_null_check(&decimal_value)))
996990
return 0; // null is set
997991
longlong result;
998992
my_decimal2int(E_DEC_FATAL_ERROR, val, unsigned_flag, &result);
@@ -1007,18 +1001,14 @@ longlong Item_func_hybrid_result_type::val_int()
10071001
if (is_temporal_type(field_type()))
10081002
{
10091003
MYSQL_TIME ltime;
1010-
if (date_op(&ltime,
1011-
field_type() == MYSQL_TYPE_TIME ? TIME_TIME_ONLY : 0))
1012-
{
1013-
null_value= 1;
1004+
if (date_op_with_null_check(&ltime))
10141005
return 0;
1015-
}
10161006
ltime.time_type= mysql_type_to_time_type(field_type());
10171007
return TIME_to_ulonglong(&ltime);
10181008
}
10191009
int err_not_used;
10201010
String *res;
1021-
if (!(res= str_op(&str_value)))
1011+
if (!(res= str_op_with_null_check(&str_value)))
10221012
return 0;
10231013

10241014
char *end= (char*) res->ptr() + res->length();
@@ -1040,17 +1030,21 @@ my_decimal *Item_func_hybrid_result_type::val_decimal(my_decimal *decimal_value)
10401030
DBUG_ASSERT(fixed == 1);
10411031
switch (cached_result_type) {
10421032
case DECIMAL_RESULT:
1043-
val= decimal_op(decimal_value);
1033+
val= decimal_op_with_null_check(decimal_value);
10441034
break;
10451035
case INT_RESULT:
10461036
{
10471037
longlong result= int_op();
1038+
if (null_value)
1039+
return NULL;
10481040
int2my_decimal(E_DEC_FATAL_ERROR, result, unsigned_flag, decimal_value);
10491041
break;
10501042
}
10511043
case REAL_RESULT:
10521044
{
10531045
double result= (double)real_op();
1046+
if (null_value)
1047+
return NULL;
10541048
double2my_decimal(E_DEC_FATAL_ERROR, result, decimal_value);
10551049
break;
10561050
}
@@ -1059,19 +1053,20 @@ my_decimal *Item_func_hybrid_result_type::val_decimal(my_decimal *decimal_value)
10591053
if (is_temporal_type(field_type()))
10601054
{
10611055
MYSQL_TIME ltime;
1062-
if (date_op(&ltime,
1063-
field_type() == MYSQL_TYPE_TIME ? TIME_TIME_ONLY : 0))
1056+
if (date_op_with_null_check(&ltime))
10641057
{
10651058
my_decimal_set_zero(decimal_value);
1066-
null_value= 1;
10671059
return 0;
10681060
}
10691061
ltime.time_type= mysql_type_to_time_type(field_type());
10701062
return date2my_decimal(&ltime, decimal_value);
10711063
}
10721064
String *res;
1073-
if (!(res= str_op(&str_value)))
1065+
if (!(res= str_op_with_null_check(&str_value)))
1066+
{
1067+
null_value= 1;
10741068
return NULL;
1069+
}
10751070

10761071
str2my_decimal(E_DEC_FATAL_ERROR, (char*) res->ptr(),
10771072
res->length(), res->charset(), decimal_value);
@@ -1094,7 +1089,7 @@ bool Item_func_hybrid_result_type::get_date(MYSQL_TIME *ltime,
10941089
case DECIMAL_RESULT:
10951090
{
10961091
my_decimal value, *res;
1097-
if (!(res= decimal_op(&value)) ||
1092+
if (!(res= decimal_op_with_null_check(&value)) ||
10981093
decimal_to_datetime_with_warn(res, ltime, fuzzydate,
10991094
field_name_or_null()))
11001095
goto err;
@@ -1124,7 +1119,7 @@ bool Item_func_hybrid_result_type::get_date(MYSQL_TIME *ltime,
11241119
return date_op(ltime, fuzzydate);
11251120
char buff[40];
11261121
String tmp(buff,sizeof(buff), &my_charset_bin),*res;
1127-
if (!(res= str_op(&tmp)) ||
1122+
if (!(res= str_op_with_null_check(&tmp)) ||
11281123
str_to_datetime_with_warn(res->charset(), res->ptr(), res->length(),
11291124
ltime, fuzzydate) <= MYSQL_TIMESTAMP_ERROR)
11301125
goto err;

sql/item_func.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,29 @@ class Item_real_func :public Item_func
411411

412412
class Item_func_hybrid_result_type: public Item_func
413413
{
414+
/*
415+
Helper methods to make sure that the result of
416+
decimal_op(), str_op() and date_op() is properly synched with null_value.
417+
*/
418+
bool date_op_with_null_check(MYSQL_TIME *ltime)
419+
{
420+
bool rc= date_op(ltime,
421+
field_type() == MYSQL_TYPE_TIME ? TIME_TIME_ONLY : 0);
422+
DBUG_ASSERT(!rc ^ null_value);
423+
return rc;
424+
}
425+
String *str_op_with_null_check(String *str)
426+
{
427+
String *res= str_op(str);
428+
DBUG_ASSERT((res != NULL) ^ null_value);
429+
return res;
430+
}
431+
my_decimal *decimal_op_with_null_check(my_decimal *decimal_buffer)
432+
{
433+
my_decimal *res= decimal_op(decimal_buffer);
434+
DBUG_ASSERT((res != NULL) ^ null_value);
435+
return res;
436+
}
414437
protected:
415438
Item_result cached_result_type;
416439

0 commit comments

Comments
 (0)