Commit 36bf417b authored by evgen@sunlight.local's avatar evgen@sunlight.local
Browse files

Bug#27216: functions with parameters of different date types may return wrong

type of the result.

There are several functions that accept parameters of different types.
The result field type of such functions was determined based on
the aggregated result type of its arguments. As the DATE and the DATETIME
types are represented by the STRING type, the result field type
of the affected functions was always STRING for DATE/DATETIME arguments.
The affected functions are COALESCE, IF, IFNULL, CASE, LEAST/GREATEST, CASE.

Now the affected functions aggregate the field types of their arguments rather
than their result types and return the result of aggregation as their result
field type.
The cached_field_type member variable is added to the number of classes to
hold the aggregated result field type.
The str_to_date() function's result field type now defaults to the
MYSQL_TYPE_DATETIME.
The agg_field_type() function is added. It aggregates field types with help
of the Field::field_type_merge() function.
The create_table_from_items() function now uses the 
item->tmp_table_field_from_field_type() function to get the proper field
when the item is a function with a STRING result type.
parent 56c927e6
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -4355,6 +4355,7 @@ static my_bool setup_one_fetch_function(MYSQL_BIND *param, MYSQL_FIELD *field)
  case MYSQL_TYPE_STRING:
  case MYSQL_TYPE_DECIMAL:
  case MYSQL_TYPE_NEWDECIMAL:
  case MYSQL_TYPE_NEWDATE:
    DBUG_ASSERT(param->buffer_length != 0);
    param->fetch_result= fetch_result_str;
    break;
@@ -4427,6 +4428,7 @@ static my_bool setup_one_fetch_function(MYSQL_BIND *param, MYSQL_FIELD *field)
  case MYSQL_TYPE_VAR_STRING:
  case MYSQL_TYPE_STRING:
  case MYSQL_TYPE_BIT:
  case MYSQL_TYPE_NEWDATE:
    param->skip_result= skip_result_string;
    break;
  default:
+1 −1
Original line number Diff line number Diff line
@@ -481,7 +481,7 @@ str_to_date(a,b)
create table t2 select str_to_date(a,b) from t1;
describe t2;
Field	Type	Null	Key	Default	Extra
str_to_date(a,b)	binary(29)	YES		NULL	
str_to_date(a,b)	datetime	YES		NULL	
select str_to_date("2003-01-02 10:11:12.0012", "%Y-%m-%d %H:%i:%S.%f") as f1,
str_to_date("2003-01-02 10:11:12.0012", "%Y-%m-%d %H:%i:%S") as f2,
str_to_date("2003-01-02", "%Y-%m-%d") as f3,
+61 −0
Original line number Diff line number Diff line
@@ -427,3 +427,64 @@ f1
Warnings:
Warning	1292	Incorrect datetime value: '2007010100000' for column 'f1' at row 1
drop table t1;
#
# Bug#27216: functions with parameters of different date types may
#            return wrong type of the result.
#
create table t1 (f1 date, f2 datetime, f3 varchar(20));
create table t2 as select coalesce(f1,f1) as f4 from t1;
desc t2;
Field	Type	Null	Key	Default	Extra
f4	date	YES		NULL	
create table t3 as select coalesce(f1,f2) as f4 from t1;
desc t3;
Field	Type	Null	Key	Default	Extra
f4	datetime	YES		NULL	
create table t4 as select coalesce(f2,f2) as f4 from t1;
desc t4;
Field	Type	Null	Key	Default	Extra
f4	datetime	YES		NULL	
create table t5 as select coalesce(f1,f3) as f4 from t1;
desc t5;
Field	Type	Null	Key	Default	Extra
f4	varbinary(20)	YES		NULL	
create table t6 as select coalesce(f2,f3) as f4 from t1;
desc t6;
Field	Type	Null	Key	Default	Extra
f4	varbinary(20)	YES		NULL	
create table t7 as select coalesce(makedate(1997,1),f2) as f4 from t1;
desc t7;
Field	Type	Null	Key	Default	Extra
f4	datetime	YES		NULL	
create table t8 as select coalesce(cast('01-01-01' as datetime),f2) as f4
from t1;
desc t8;
Field	Type	Null	Key	Default	Extra
f4	datetime	YES		NULL	
create table t9 as select case when 1 then cast('01-01-01' as date)
when 0 then cast('01-01-01' as date) end as f4 from t1;
desc t9;
Field	Type	Null	Key	Default	Extra
f4	date	YES		NULL	
create table t10 as select case when 1 then cast('01-01-01' as datetime)
when 0 then cast('01-01-01' as datetime) end as f4 from t1;
desc t10;
Field	Type	Null	Key	Default	Extra
f4	datetime	YES		NULL	
create table t11 as select if(1, cast('01-01-01' as datetime),
cast('01-01-01' as date)) as f4 from t1;
desc t11;
Field	Type	Null	Key	Default	Extra
f4	datetime	YES		NULL	
create table t12 as select least(cast('01-01-01' as datetime),
cast('01-01-01' as date)) as f4 from t1;
desc t12;
Field	Type	Null	Key	Default	Extra
f4	datetime	YES		NULL	
create table t13 as select ifnull(cast('01-01-01' as datetime),
cast('01-01-01' as date)) as f4 from t1;
desc t13;
Field	Type	Null	Key	Default	Extra
f4	datetime	YES		NULL	
drop tables t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13;
###################################################################
+38 −0
Original line number Diff line number Diff line
@@ -282,3 +282,41 @@ select * from t1 where f1 between 20020101 and 20070101000000;
select * from t1 where f1 between 2002010 and 20070101000000;
select * from t1 where f1 between 20020101 and 2007010100000;
drop table t1;

--echo #
--echo # Bug#27216: functions with parameters of different date types may
--echo #            return wrong type of the result.
--echo #
create table t1 (f1 date, f2 datetime, f3 varchar(20));
create table t2 as select coalesce(f1,f1) as f4 from t1;
desc t2;
create table t3 as select coalesce(f1,f2) as f4 from t1;
desc t3;
create table t4 as select coalesce(f2,f2) as f4 from t1;
desc t4;
create table t5 as select coalesce(f1,f3) as f4 from t1;
desc t5;
create table t6 as select coalesce(f2,f3) as f4 from t1;
desc t6;
create table t7 as select coalesce(makedate(1997,1),f2) as f4 from t1;
desc t7;
create table t8 as select coalesce(cast('01-01-01' as datetime),f2) as f4
  from t1;
desc t8;
create table t9 as select case when 1 then cast('01-01-01' as date)
  when 0 then cast('01-01-01' as date) end as f4 from t1;
desc t9;
create table t10 as select case when 1 then cast('01-01-01' as datetime)
  when 0 then cast('01-01-01' as datetime) end as f4 from t1;
desc t10;
create table t11 as select if(1, cast('01-01-01' as datetime),
  cast('01-01-01' as date)) as f4 from t1;
desc t11;
create table t12 as select least(cast('01-01-01' as datetime),
  cast('01-01-01' as date)) as f4 from t1;
desc t12;
create table t13 as select ifnull(cast('01-01-01' as datetime),
  cast('01-01-01' as date)) as f4 from t1;
desc t13;
drop tables t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13;
--echo ###################################################################
+36 −4
Original line number Diff line number Diff line
@@ -147,6 +147,36 @@ static int agg_cmp_type(THD *thd, Item_result *type, Item **items, uint nitems)
}


/**
  @brief Aggregates field types from the array of items.

  @param[in] items  array of items to aggregate the type from
  @paran[in] nitems number of items in the array

  @details This function aggregates field types from the array of items.
    Found type is supposed to be used later as the result field type
    of a multi-argument function.
    Aggregation itself is performed by the Field::field_type_merge()
    function.

  @note The term "aggregation" is used here in the sense of inferring the
    result type of a function from its argument types.

  @return aggregated field type.
*/

enum_field_types agg_field_type(Item **items, uint nitems)
{
  uint i;
  if (!nitems || items[0]->result_type() == ROW_RESULT )
    return (enum_field_types)-1;
  enum_field_types res= items[0]->field_type();
  for (i= 1 ; i < nitems ; i++)
    res= Field::field_type_merge(res, items[i]->field_type());
  return res;
}


static void my_coll_agg_error(DTCollation &c1, DTCollation &c2,
                              const char *fname)
{
@@ -2009,9 +2039,7 @@ Item_func_ifnull::fix_length_and_dec()
  default:
    DBUG_ASSERT(0);
  }
  cached_field_type= args[0]->field_type();
  if (cached_field_type != args[1]->field_type())
    cached_field_type= Item_func::field_type();
  cached_field_type= agg_field_type(args, 2);
}


@@ -2159,11 +2187,13 @@ Item_func_if::fix_length_and_dec()
  {
    cached_result_type= arg2_type;
    collation.set(args[2]->collation.collation);
    cached_field_type= args[2]->field_type();
  }
  else if (null2)
  {
    cached_result_type= arg1_type;
    collation.set(args[1]->collation.collation);
    cached_field_type= args[1]->field_type();
  }
  else
  {
@@ -2177,6 +2207,7 @@ Item_func_if::fix_length_and_dec()
    {
      collation.set(&my_charset_bin);	// Number
    }
    cached_field_type= agg_field_type(args + 1, 2);
  }

  if ((cached_result_type == DECIMAL_RESULT )
@@ -2556,7 +2587,7 @@ void Item_func_case::fix_length_and_dec()
      agg_arg_charsets(collation, agg, nagg, MY_COLL_ALLOW_CONV, 1))
    return;
  
  
  cached_field_type= agg_field_type(agg, nagg);
  /*
    Aggregate first expression and all THEN expression types
    and collations when string comparison
@@ -2695,6 +2726,7 @@ my_decimal *Item_func_coalesce::decimal_op(my_decimal *decimal_value)

void Item_func_coalesce::fix_length_and_dec()
{
  cached_field_type= agg_field_type(args, arg_count);
  agg_result_type(&hybrid_type, args, arg_count);
  switch (hybrid_type) {
  case STRING_RESULT:
Loading