commit 0b59aa1a8567f4660715a24e5aa33a5016894ec8
Author: Alexander Barkov <bar@mariadb.org>
Date:   Mon Apr 17 15:09:19 2017 +0400

    MDEV-12514 Split Item_temporal_func::fix_length_and_dec() + MDEV-12515
    
    This patch implements MDEV-12514 according to the task descriptions.
    It automatically fixes:
    MDEV-12515 Wrong value when storing DATE_ADD() and ADDTIME() to a numeric field
    
    Additionally:
    
    a. Moves Item_func::set_attributes_temporal() to
       Type_str_attributes::fix_attributes_temporal(),
      which is a more proper place and name for it.
    
    b. Continues replacing calls for:
         set_handler_by_field_type(MYSQL_TYPE_XXX)
       to corresponding:
         set_handler(&type_handler_xxx)
       which is faster.
       Note, we should eventually get rid of almost all set_handler_by_field_type().
    
    c. Makes type_handler_string, type_handler_time2, type_handler_newdate,
       type_handler_datetime2 public.
       (all built-in handlers will become public eventually)

diff --git a/mysql-test/r/func_time.result b/mysql-test/r/func_time.result
index 54da823..a05c6a7 100644
--- a/mysql-test/r/func_time.result
+++ b/mysql-test/r/func_time.result
@@ -3210,3 +3210,48 @@ DROP TABLE t1,t2;
 #
 # End of 10.1 tests
 #
+#
+# Start of 10.3 tests
+#
+#
+#  MDEV-12515 Wrong value when storing DATE_ADD() and ADDTIME() to a numeric field
+#
+SET sql_mode='';
+CREATE TABLE t1 AS SELECT
+DATE_ADD('2001-01-01',INTERVAL 1 DAY) AS c1,
+ADDTIME('10:20:30',1) AS c2;
+SHOW CREATE TABLE t1;
+Table	Create Table
+t1	CREATE TABLE `t1` (
+  `c1` varchar(19) DEFAULT NULL,
+  `c2` varchar(26) DEFAULT NULL
+) ENGINE=MyISAM DEFAULT CHARSET=latin1
+SELECT * FROM t1;
+c1	c2
+2001-01-02	10:20:31
+DROP TABLE t1;
+CREATE TABLE t2 (c INT);
+INSERT INTO t2 SELECT DATE_ADD('2001-01-01',INTERVAL 1 DAY);
+Warnings:
+Warning	1265	Data truncated for column 'c' at row 1
+INSERT INTO t2 VALUES ('2001-01-02');
+Warnings:
+Warning	1265	Data truncated for column 'c' at row 1
+SELECT * FROM t2;
+c
+2001
+2001
+DROP TABLE t2;
+CREATE TABLE t2 (a INT);
+INSERT INTO t2 VALUES (ADDTIME('10:20:30',1));
+Warnings:
+Warning	1265	Data truncated for column 'a' at row 1
+INSERT INTO t2 VALUES ('10:20:31');
+Warnings:
+Warning	1265	Data truncated for column 'a' at row 1
+SELECT * FROM t2;
+a
+10
+10
+DROP TABLE t2;
+SET sql_mode=DEFAULT;
diff --git a/mysql-test/r/gis.result b/mysql-test/r/gis.result
index e713233..6a7b7e7 100644
--- a/mysql-test/r/gis.result
+++ b/mysql-test/r/gis.result
@@ -3902,5 +3902,36 @@ SELECT 1 MOD COALESCE(a) FROM t1;
 ERROR HY000: Illegal parameter data types bigint and geometry for operation '%'
 DROP TABLE t1;
 #
+# MDEV-12514 Split Item_temporal_func::fix_length_and_dec()
+#
+SELECT DATE_ADD(POINT(1,1), INTERVAL 10 DAY);
+ERROR HY000: Illegal parameter data types geometry and interval for operation 'date_add_interval'
+SELECT DATE_SUB(POINT(1,1), INTERVAL 10 DAY);
+ERROR HY000: Illegal parameter data types geometry and interval for operation 'date_add_interval'
+SELECT POINT(1,1) + INTERVAL 10 DAY;
+ERROR HY000: Illegal parameter data types geometry and interval for operation 'date_add_interval'
+SELECT POINT(1,1) - INTERVAL 10 DAY;
+ERROR HY000: Illegal parameter data types geometry and interval for operation 'date_add_interval'
+SELECT INTERVAL 10 DAY + POINT(1,1);
+ERROR HY000: Illegal parameter data types geometry and interval for operation 'date_add_interval'
+SELECT INTERVAL 10 DAY + POINT(1,1);
+ERROR HY000: Illegal parameter data types geometry and interval for operation 'date_add_interval'
+SELECT ADDTIME(POINT(1,1), '10:10:10');
+ERROR HY000: Illegal parameter data types geometry and varchar for operation 'add_time'
+SELECT ADDTIME('10:10:10', POINT(1,1));
+ERROR HY000: Illegal parameter data types varchar and geometry for operation 'add_time'
+SELECT ADDTIME(POINT(1,1), TIME'10:10:10');
+ERROR HY000: Illegal parameter data types geometry and time for operation 'add_time'
+SELECT ADDTIME(TIME'10:10:10', POINT(1,1));
+ERROR HY000: Illegal parameter data types time and geometry for operation 'add_time'
+SELECT ADDTIME(POINT(1,1), TIMESTAMP'2001-01-01 10:10:10');
+ERROR HY000: Illegal parameter data types geometry and datetime for operation 'add_time'
+SELECT ADDTIME(TIMESTAMP'2001-01-01 10:10:10', POINT(1,1));
+ERROR HY000: Illegal parameter data types datetime and geometry for operation 'add_time'
+SELECT STR_TO_DATE(POINT(1,1),'%M %d,%Y');
+ERROR HY000: Illegal parameter data types geometry and varchar for operation 'str_to_date'
+SELECT STR_TO_DATE('2001-01-01', POINT(1,1));
+ERROR HY000: Illegal parameter data types varchar and geometry for operation 'str_to_date'
+#
 # End of 10.3 tests
 #
diff --git a/mysql-test/t/func_time.test b/mysql-test/t/func_time.test
index 6e0169d..e8d1d4e 100644
--- a/mysql-test/t/func_time.test
+++ b/mysql-test/t/func_time.test
@@ -1817,3 +1817,35 @@ DROP TABLE t1,t2;
 --echo #
 --echo # End of 10.1 tests
 --echo #
+
+
+--echo #
+--echo # Start of 10.3 tests
+--echo #
+
+--echo #
+--echo #  MDEV-12515 Wrong value when storing DATE_ADD() and ADDTIME() to a numeric field
+--echo #
+
+SET sql_mode='';
+
+CREATE TABLE t1 AS SELECT
+  DATE_ADD('2001-01-01',INTERVAL 1 DAY) AS c1,
+  ADDTIME('10:20:30',1) AS c2;
+SHOW CREATE TABLE t1;
+SELECT * FROM t1;
+DROP TABLE t1;
+
+CREATE TABLE t2 (c INT);
+INSERT INTO t2 SELECT DATE_ADD('2001-01-01',INTERVAL 1 DAY);
+INSERT INTO t2 VALUES ('2001-01-02');
+SELECT * FROM t2;
+DROP TABLE t2;
+
+CREATE TABLE t2 (a INT);
+INSERT INTO t2 VALUES (ADDTIME('10:20:30',1));
+INSERT INTO t2 VALUES ('10:20:31');
+SELECT * FROM t2;
+DROP TABLE t2;
+
+SET sql_mode=DEFAULT;
diff --git a/mysql-test/t/gis.test b/mysql-test/t/gis.test
index a19de83..1b355b7 100644
--- a/mysql-test/t/gis.test
+++ b/mysql-test/t/gis.test
@@ -2090,6 +2090,40 @@ SELECT 1 MOD COALESCE(a) FROM t1;
 
 DROP TABLE t1;
 
+--echo #
+--echo # MDEV-12514 Split Item_temporal_func::fix_length_and_dec()
+--echo #
+
+--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION
+SELECT DATE_ADD(POINT(1,1), INTERVAL 10 DAY);
+--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION
+SELECT DATE_SUB(POINT(1,1), INTERVAL 10 DAY);
+--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION
+SELECT POINT(1,1) + INTERVAL 10 DAY;
+--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION
+SELECT POINT(1,1) - INTERVAL 10 DAY;
+--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION
+SELECT INTERVAL 10 DAY + POINT(1,1);
+--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION
+SELECT INTERVAL 10 DAY + POINT(1,1);
+
+--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION
+SELECT ADDTIME(POINT(1,1), '10:10:10');
+--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION
+SELECT ADDTIME('10:10:10', POINT(1,1));
+--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION
+SELECT ADDTIME(POINT(1,1), TIME'10:10:10');
+--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION
+SELECT ADDTIME(TIME'10:10:10', POINT(1,1));
+--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION
+SELECT ADDTIME(POINT(1,1), TIMESTAMP'2001-01-01 10:10:10');
+--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION
+SELECT ADDTIME(TIMESTAMP'2001-01-01 10:10:10', POINT(1,1));
+
+--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION
+SELECT STR_TO_DATE(POINT(1,1),'%M %d,%Y');
+--error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION
+SELECT STR_TO_DATE('2001-01-01', POINT(1,1));
 
 --echo #
 --echo # End of 10.3 tests
diff --git a/sql/item_func.h b/sql/item_func.h
index 7f9321f..a367a0d 100644
--- a/sql/item_func.h
+++ b/sql/item_func.h
@@ -75,18 +75,10 @@ class Item_func :public Item_func_or_sum
   {
     return count_string_length(item, nitems);
   }
-  void set_attributes_temporal(uint int_part_length, uint dec)
-  {
-    collation.set_numeric();
-    unsigned_flag= 0;
-    decimals= MY_MIN(dec, TIME_SECOND_PART_DIGITS);
-    uint length= decimals + int_part_length + (dec ? 1 : 0);
-    fix_char_length(length);
-  }
   void aggregate_attributes_temporal(uint int_part_length,
                                      Item **item, uint nitems)
   {
-    set_attributes_temporal(int_part_length, count_max_decimals(item, nitems));
+    fix_attributes_temporal(int_part_length, count_max_decimals(item, nitems));
   }
 
   table_map not_null_tables_cache;
diff --git a/sql/item_timefunc.cc b/sql/item_timefunc.cc
index 174f8ff..a4a1f90 100644
--- a/sql/item_timefunc.cc
+++ b/sql/item_timefunc.cc
@@ -1458,34 +1458,6 @@ bool get_interval_value(Item *args,interval_type int_type, INTERVAL *interval)
 }
 
 
-void Item_temporal_func::fix_length_and_dec()
-{ 
-  uint char_length= mysql_temporal_int_part_length(field_type());
-  /*
-    We set maybe_null to 1 as default as any bad argument with date or
-    time can get us to return NULL.
-  */ 
-  maybe_null= (arg_count > 0);
-  if (decimals)
-  {
-    if (decimals == NOT_FIXED_DEC)
-      char_length+= TIME_SECOND_PART_DIGITS + 1;
-    else
-    {
-      set_if_smaller(decimals, TIME_SECOND_PART_DIGITS);
-      char_length+= decimals + 1;
-    }
-  }
-  sql_mode= current_thd->variables.sql_mode &
-                 (MODE_NO_ZERO_IN_DATE | MODE_NO_ZERO_DATE);
-  collation.set(field_type() == MYSQL_TYPE_STRING ?
-                default_charset() : &my_charset_numeric,
-                field_type() == MYSQL_TYPE_STRING ?
-                DERIVATION_COERCIBLE : DERIVATION_NUMERIC,
-                MY_REPERTOIRE_ASCII);
-  fix_char_length(char_length);
-}
-
 String *Item_temporal_func::val_str(String *str)
 {
   DBUG_ASSERT(fixed == 1);
@@ -2009,8 +1981,8 @@ void Item_func_from_unixtime::fix_length_and_dec()
   THD *thd= current_thd;
   thd->time_zone_used= 1;
   tz= thd->variables.time_zone;
-  decimals= args[0]->decimals;
-  Item_temporal_func::fix_length_and_dec();
+  fix_attributes_datetime_not_fixed_dec(args[0]->decimals);
+  maybe_null= true;
 }
 
 
@@ -2039,8 +2011,8 @@ bool Item_func_from_unixtime::get_date(MYSQL_TIME *ltime,
 
 void Item_func_convert_tz::fix_length_and_dec()
 {
-  decimals= args[0]->temporal_precision(MYSQL_TYPE_DATETIME);
-  Item_temporal_func::fix_length_and_dec();
+  fix_attributes_datetime(args[0]->temporal_precision(MYSQL_TYPE_DATETIME));
+  maybe_null= true;
 }
 
 
@@ -2093,6 +2065,13 @@ void Item_date_add_interval::fix_length_and_dec()
 {
   enum_field_types arg0_field_type;
 
+  if (!args[0]->type_handler()->is_traditional_type())
+  {
+    my_error(ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION, MYF(0),
+             args[0]->type_handler()->name().ptr(),
+             "interval", func_name());
+    return;
+  }
   /*
     The field type for the result of an Item_datefunc is defined as
     follows:
@@ -2108,7 +2087,6 @@ void Item_date_add_interval::fix_length_and_dec()
       (This is because you can't know if the string contains a DATE,
       MYSQL_TIME or DATETIME argument)
   */
-  set_handler_by_field_type(MYSQL_TYPE_STRING);
   arg0_field_type= args[0]->field_type();
   uint interval_dec= 0;
   if (int_type == INTERVAL_MICROSECOND ||
@@ -2121,30 +2099,47 @@ void Item_date_add_interval::fix_length_and_dec()
   if (arg0_field_type == MYSQL_TYPE_DATETIME ||
       arg0_field_type == MYSQL_TYPE_TIMESTAMP)
   {
-    decimals= MY_MAX(args[0]->temporal_precision(MYSQL_TYPE_DATETIME), interval_dec);
-    set_handler_by_field_type(MYSQL_TYPE_DATETIME);
+    uint dec= MY_MAX(args[0]->temporal_precision(MYSQL_TYPE_DATETIME),
+                     interval_dec);
+    set_handler(&type_handler_datetime);
+    fix_attributes_datetime(dec);
   }
   else if (arg0_field_type == MYSQL_TYPE_DATE)
   {
     if (int_type <= INTERVAL_DAY || int_type == INTERVAL_YEAR_MONTH)
-      set_handler_by_field_type(arg0_field_type);
+    {
+      set_handler(&type_handler_newdate);
+      fix_attributes_date();
+    }
     else
     {
-      decimals= interval_dec;
-      set_handler_by_field_type(MYSQL_TYPE_DATETIME);
+      set_handler(&type_handler_datetime2);
+      fix_attributes_datetime(interval_dec);
     }
   }
   else if (arg0_field_type == MYSQL_TYPE_TIME)
   {
-    decimals= MY_MAX(args[0]->temporal_precision(MYSQL_TYPE_TIME), interval_dec);
+    uint dec= MY_MAX(args[0]->temporal_precision(MYSQL_TYPE_TIME), interval_dec);
     if (int_type >= INTERVAL_DAY && int_type != INTERVAL_YEAR_MONTH)
-      set_handler_by_field_type(arg0_field_type);
+    {
+      set_handler(&type_handler_time2);
+      fix_attributes_time(dec);
+    }
     else
-      set_handler_by_field_type(MYSQL_TYPE_DATETIME);
+    {
+      set_handler(&type_handler_datetime2);
+      fix_attributes_datetime(dec);
+    }
   }
   else
-    decimals= MY_MAX(args[0]->temporal_precision(MYSQL_TYPE_DATETIME), interval_dec);
-  Item_temporal_func::fix_length_and_dec();
+  {
+    uint dec= MY_MAX(args[0]->temporal_precision(MYSQL_TYPE_DATETIME),
+                     interval_dec);
+    set_handler(&type_handler_string);
+    collation.set(default_charset(), DERIVATION_COERCIBLE, MY_REPERTOIRE_ASCII);
+    fix_char_length_temporal_not_fixed_dec(MAX_DATETIME_WIDTH, dec);
+  }
+  maybe_null= true;
 }
 
 
@@ -2649,8 +2644,15 @@ bool Item_func_makedate::get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date)
 void Item_func_add_time::fix_length_and_dec()
 {
   enum_field_types arg0_field_type;
-  decimals= MY_MAX(args[0]->decimals, args[1]->decimals);
 
+  if (!args[0]->type_handler()->is_traditional_type() ||
+      !args[1]->type_handler()->is_traditional_type())
+  {
+    my_error(ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION, MYF(0),
+             args[0]->type_handler()->name().ptr(),
+             args[1]->type_handler()->name().ptr(), func_name());
+    return;
+  }
   /*
     The field type for the result of an Item_func_add_time function is defined
     as follows:
@@ -2661,24 +2663,32 @@ void Item_func_add_time::fix_length_and_dec()
     - Otherwise the result is MYSQL_TYPE_STRING
   */
 
-  set_handler_by_field_type(MYSQL_TYPE_STRING);
   arg0_field_type= args[0]->field_type();
   if (arg0_field_type == MYSQL_TYPE_DATE ||
       arg0_field_type == MYSQL_TYPE_DATETIME ||
       arg0_field_type == MYSQL_TYPE_TIMESTAMP ||
       is_date)
   {
-    set_handler_by_field_type(MYSQL_TYPE_DATETIME);
-    decimals= MY_MAX(args[0]->temporal_precision(MYSQL_TYPE_DATETIME),
+    uint dec= MY_MAX(args[0]->temporal_precision(MYSQL_TYPE_DATETIME),
                      args[1]->temporal_precision(MYSQL_TYPE_TIME));
+    set_handler(&type_handler_datetime2);
+    fix_attributes_datetime(dec);
   }
   else if (arg0_field_type == MYSQL_TYPE_TIME)
   {
-    set_handler_by_field_type(MYSQL_TYPE_TIME);
-    decimals= MY_MAX(args[0]->temporal_precision(MYSQL_TYPE_TIME),
+    uint dec= MY_MAX(args[0]->temporal_precision(MYSQL_TYPE_TIME),
                      args[1]->temporal_precision(MYSQL_TYPE_TIME));
+    set_handler(&type_handler_time2);
+    fix_attributes_time(dec);
+  }
+  else
+  {
+    uint dec= MY_MAX(args[0]->decimals, args[1]->decimals);
+    set_handler(&type_handler_string);
+    collation.set(default_charset(), DERIVATION_COERCIBLE, MY_REPERTOIRE_ASCII);
+    fix_char_length_temporal_not_fixed_dec(MAX_DATETIME_WIDTH, dec);
   }
-  Item_temporal_func::fix_length_and_dec();
+  maybe_null= true;
 }
 
 /**
@@ -3169,6 +3179,14 @@ get_date_time_result_type(const char *format, uint length)
 
 void Item_func_str_to_date::fix_length_and_dec()
 {
+  if (!args[0]->type_handler()->is_traditional_type() ||
+      !args[1]->type_handler()->is_traditional_type())
+  {
+    my_error(ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION, MYF(0),
+             args[0]->type_handler()->name().ptr(),
+             args[1]->type_handler()->name().ptr(), func_name());
+    return;
+  }
   if (agg_arg_charsets(collation, args, 2, MY_COLL_ALLOW_CONV, 1))
     return;
   if (collation.collation->mbminlen > 1)
@@ -3180,8 +3198,10 @@ void Item_func_str_to_date::fix_length_and_dec()
 #endif
   }
 
-  set_handler_by_field_type(MYSQL_TYPE_DATETIME);
-  decimals= TIME_SECOND_PART_DIGITS;
+  maybe_null= true;
+  set_handler(&type_handler_datetime2);
+  fix_attributes_datetime(TIME_SECOND_PART_DIGITS);
+
   if ((const_item= args[1]->const_item()))
   {
     char format_buff[64];
@@ -3195,25 +3215,29 @@ void Item_func_str_to_date::fix_length_and_dec()
         get_date_time_result_type(format->ptr(), format->length());
       switch (cached_format_type) {
       case DATE_ONLY:
-        set_handler_by_field_type(MYSQL_TYPE_DATE);
+        set_handler(&type_handler_newdate);
+        fix_attributes_date();
         break;
       case TIME_MICROSECOND:
-        decimals= 6;
-        /* fall through */
+        set_handler(&type_handler_time2);
+        fix_attributes_time(TIME_SECOND_PART_DIGITS);
+        break;
       case TIME_ONLY:
-        set_handler_by_field_type(MYSQL_TYPE_TIME);
+        set_handler(&type_handler_time2);
+        fix_attributes_time(0);
         break;
       case DATE_TIME_MICROSECOND:
-        decimals= 6;
-        /* fall through */
+        set_handler(&type_handler_datetime2);
+        fix_attributes_datetime(TIME_SECOND_PART_DIGITS);
+        break;
       case DATE_TIME:
-        set_handler_by_field_type(MYSQL_TYPE_DATETIME);
+        set_handler(&type_handler_datetime2);
+        fix_attributes_datetime(0);
         break;
       }
     }
   }
   cached_timestamp_type= mysql_type_to_time_type(field_type());
-  Item_temporal_func::fix_length_and_dec();
 }
 
 
diff --git a/sql/item_timefunc.h b/sql/item_timefunc.h
index f66c57e..40b8c16 100644
--- a/sql/item_timefunc.h
+++ b/sql/item_timefunc.h
@@ -31,16 +31,6 @@ enum date_time_format_types
 };
 
 
-static inline uint
-mysql_temporal_int_part_length(enum enum_field_types mysql_type)
-{
-  static uint max_time_type_width[5]=
-  { MAX_DATETIME_WIDTH, MAX_DATETIME_WIDTH, MAX_DATE_WIDTH,
-    MAX_DATETIME_WIDTH, MIN_TIME_WIDTH };
-  return max_time_type_width[mysql_type_to_time_type(mysql_type)+2];
-}
-
-
 bool get_interval_value(Item *args,interval_type int_type, INTERVAL *interval);
 
 class Item_func_period_add :public Item_int_func
@@ -531,7 +521,6 @@ class Item_func_time_to_sec :public Item_func_seconds_hybrid
 
 class Item_temporal_func: public Item_func
 {
-  sql_mode_t sql_mode;
 public:
   Item_temporal_func(THD *thd): Item_func(thd) {}
   Item_temporal_func(THD *thd, Item *a): Item_func(thd, a) {}
@@ -549,7 +538,6 @@ class Item_temporal_func: public Item_func
   { return tmp_table_field_from_field_type(table, false, false); }
   int save_in_field(Field *field, bool no_conversions)
   { return save_date_in_field(field, no_conversions); }
-  void fix_length_and_dec();
 };
 
 
@@ -557,22 +545,20 @@ class Item_temporal_func: public Item_func
   Abstract class for functions returning TIME, DATE, DATETIME or string values,
   whose data type depends on parameters and is set at fix_fields time.
 */
-class Item_temporal_hybrid_func: public Item_temporal_func,
-                                 public Type_handler_hybrid_field_type
+class Item_temporal_hybrid_func: public Item_hybrid_func
 {
 protected:
   String ascii_buf; // Conversion buffer
 public:
   Item_temporal_hybrid_func(THD *thd, Item *a, Item *b):
-    Item_temporal_func(thd, a, b) {}
-  const Type_handler *type_handler() const
-  { return Type_handler_hybrid_field_type::type_handler(); }
-  enum_field_types field_type() const
-  { return Type_handler_hybrid_field_type::field_type(); }
-  enum Item_result result_type () const
-  { return Type_handler_hybrid_field_type::result_type(); }
-  enum Item_result cmp_type () const
-  { return Type_handler_hybrid_field_type::cmp_type(); }
+    Item_hybrid_func(thd, a, b) {}
+
+  longlong val_int() { return val_int_from_date(); }
+  double val_real() { return val_real_from_date(); }
+  bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date)= 0;
+  my_decimal *val_decimal(my_decimal *decimal_value)
+  { return  val_decimal_from_date(decimal_value); }
+
   /**
     Fix the returned timestamp to match field_type(),
     which is important for val_str().
@@ -599,6 +585,11 @@ class Item_datefunc :public Item_temporal_func
   Item_datefunc(THD *thd, Item *a): Item_temporal_func(thd, a) { }
   Item_datefunc(THD *thd, Item *a, Item *b): Item_temporal_func(thd, a, b) { }
   enum_field_types field_type() const { return MYSQL_TYPE_DATE; }
+  void fix_length_and_dec()
+  {
+    fix_attributes_date();
+    maybe_null= (arg_count > 0);
+  }
 };
 
 
@@ -635,6 +626,7 @@ class Item_func_curtime :public Item_timefunc
   Item_func_curtime(THD *thd, uint dec): Item_timefunc(thd), last_query_id(0)
   { decimals= dec; }
   bool fix_fields(THD *, Item **);
+  void fix_length_and_dec() { fix_attributes_time(decimals); }
   bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date);
   /* 
     Abstract method that defines which time zone is used for conversion.
@@ -722,6 +714,7 @@ class Item_func_now :public Item_datetimefunc
   Item_func_now(THD *thd, uint dec): Item_datetimefunc(thd), last_query_id(0)
   { decimals= dec; }
   bool fix_fields(THD *, Item **);
+  void fix_length_and_dec() { fix_attributes_datetime(decimals); }
   bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date);
   virtual void store_now_in_TIME(THD *thd, MYSQL_TIME *now_time)=0;
   bool check_vcol_func_processor(void *arg)
@@ -886,8 +879,8 @@ class Item_func_sec_to_time :public Item_timefunc
   bool get_date(MYSQL_TIME *res, ulonglong fuzzy_date);
   void fix_length_and_dec()
   {
-    decimals= MY_MIN(args[0]->decimals, TIME_SECOND_PART_DIGITS);
-    Item_timefunc::fix_length_and_dec();
+    fix_attributes_time(args[0]->decimals);
+    maybe_null= true;
   }
   const char *func_name() const { return "sec_to_time"; }
   Item *get_copy(THD *thd, MEM_ROOT *mem_root)
@@ -1066,11 +1059,12 @@ class Item_temporal_typecast: public Item_temporal_func
   Item_temporal_typecast(THD *thd, Item *a): Item_temporal_func(thd, a) {}
   virtual const char *cast_type() const = 0;
   void print(String *str, enum_query_type query_type);
-  void fix_length_and_dec_generic()
+  void fix_length_and_dec_generic(uint int_part_len)
   {
     if (decimals == NOT_FIXED_DEC)
       decimals= args[0]->temporal_precision(field_type());
-    Item_temporal_func::fix_length_and_dec();
+    fix_attributes_temporal(int_part_len, decimals);
+    maybe_null= true;
   }
 };
 
@@ -1163,9 +1157,10 @@ class Item_func_timediff :public Item_timefunc
   const char *func_name() const { return "timediff"; }
   void fix_length_and_dec()
   {
-    decimals= MY_MAX(args[0]->temporal_precision(MYSQL_TYPE_TIME),
+    uint dec= MY_MAX(args[0]->temporal_precision(MYSQL_TYPE_TIME),
                      args[1]->temporal_precision(MYSQL_TYPE_TIME));
-    Item_timefunc::fix_length_and_dec();
+    fix_attributes_time(dec);
+    maybe_null= true;
   }
   bool get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date);
   Item *get_copy(THD *thd, MEM_ROOT *mem_root)
@@ -1180,8 +1175,8 @@ class Item_func_maketime :public Item_timefunc
   {}
   void fix_length_and_dec()
   {
-    decimals= MY_MIN(args[2]->decimals, TIME_SECOND_PART_DIGITS);
-    Item_timefunc::fix_length_and_dec();
+    fix_attributes_time(args[2]->decimals);
+    maybe_null= true;
   }
   const char *func_name() const { return "maketime"; }
   bool get_date(MYSQL_TIME *ltime, ulonglong fuzzy_date);
diff --git a/sql/sql_type.cc b/sql/sql_type.cc
index 337111f..cf5e4e4 100644
--- a/sql/sql_type.cc
+++ b/sql/sql_type.cc
@@ -26,14 +26,10 @@ static Type_handler_long        type_handler_long;
 static Type_handler_int24       type_handler_int24;
 static Type_handler_year        type_handler_year;
 static Type_handler_time        type_handler_time;
-static Type_handler_time2       type_handler_time2;
 static Type_handler_date        type_handler_date;
-static Type_handler_newdate     type_handler_newdate;
-static Type_handler_datetime2   type_handler_datetime2;
 static Type_handler_timestamp   type_handler_timestamp;
 static Type_handler_timestamp2  type_handler_timestamp2;
 static Type_handler_olddecimal  type_handler_olddecimal;
-static Type_handler_string      type_handler_string;
 static Type_handler_tiny_blob   type_handler_tiny_blob;
 static Type_handler_medium_blob type_handler_medium_blob;
 static Type_handler_long_blob   type_handler_long_blob;
@@ -42,6 +38,7 @@ static Type_handler_blob        type_handler_blob;
 
 Type_handler_null        type_handler_null;
 Type_handler_row         type_handler_row;
+Type_handler_string      type_handler_string;
 Type_handler_varchar     type_handler_varchar;
 Type_handler_longlong    type_handler_longlong;
 Type_handler_float       type_handler_float;
@@ -52,6 +49,10 @@ Type_handler_bit         type_handler_bit;
 Type_handler_enum        type_handler_enum;
 Type_handler_set         type_handler_set;
 
+Type_handler_time2       type_handler_time2;
+Type_handler_newdate     type_handler_newdate;
+Type_handler_datetime2   type_handler_datetime2;
+
 #ifdef HAVE_SPATIAL
 Type_handler_geometry    type_handler_geometry;
 #endif
@@ -1346,7 +1347,7 @@ bool Type_handler_date_common::
        Item_hybrid_func_fix_attributes(THD *thd, Item_hybrid_func *func,
                                        Item **items, uint nitems) const
 {
-  func->set_attributes_temporal(MAX_DATE_WIDTH, 0);
+  func->fix_attributes_date();
   return false;
 }
 
@@ -2688,7 +2689,7 @@ bool Type_handler_numeric::
 bool Type_handler::
        Item_time_typecast_fix_length_and_dec(Item_time_typecast *item) const
 {
-  item->fix_length_and_dec_generic();
+  item->fix_length_and_dec_generic(MIN_TIME_WIDTH);
   return false;
 }
 
@@ -2696,7 +2697,7 @@ bool Type_handler::
 bool Type_handler::
        Item_date_typecast_fix_length_and_dec(Item_date_typecast *item) const
 {
-  item->fix_length_and_dec_generic();
+  item->fix_length_and_dec_generic(MAX_DATE_WIDTH);
   return false;
 }
 
@@ -2705,7 +2706,7 @@ bool Type_handler::
        Item_datetime_typecast_fix_length_and_dec(Item_datetime_typecast *item)
                                                  const
 {
-  item->fix_length_and_dec_generic();
+  item->fix_length_and_dec_generic(MAX_DATETIME_WIDTH);
   return false;
 
 }
diff --git a/sql/sql_type.h b/sql/sql_type.h
index 891c785..bd5290a 100644
--- a/sql/sql_type.h
+++ b/sql/sql_type.h
@@ -23,6 +23,8 @@
 
 #include "mysqld.h"
 #include "sql_array.h"
+#include "sql_const.h"
+#include "my_time.h"
 
 class Field;
 class Item;
@@ -261,6 +263,54 @@ class Type_std_attributes
     max_length= char_to_byte_length_safe(max_char_length_arg,
                                          collation.collation->mbmaxlen);
   }
+  void fix_char_length_temporal_not_fixed_dec(uint int_part_length, uint dec)
+  {
+    uint char_length= int_part_length;
+    if ((decimals= dec))
+    {
+      if (decimals == NOT_FIXED_DEC)
+        char_length+= TIME_SECOND_PART_DIGITS + 1;
+      else
+      {
+        set_if_smaller(decimals, TIME_SECOND_PART_DIGITS);
+        char_length+= decimals + 1;
+      }
+    }
+    fix_char_length(char_length);
+  }
+  void fix_attributes_temporal_not_fixed_dec(uint int_part_length, uint dec)
+  {
+    collation.set_numeric();
+    unsigned_flag= 0;
+    fix_char_length_temporal_not_fixed_dec(int_part_length, dec);
+  }
+  void fix_attributes_time_not_fixed_dec(uint dec)
+  {
+    fix_attributes_temporal_not_fixed_dec(MIN_TIME_WIDTH, dec);
+  }
+  void fix_attributes_datetime_not_fixed_dec(uint dec)
+  {
+    fix_attributes_temporal_not_fixed_dec(MAX_DATETIME_WIDTH, dec);
+  }
+  void fix_attributes_temporal(uint int_part_length, uint dec)
+  {
+    collation.set_numeric();
+    unsigned_flag= 0;
+    decimals= MY_MIN(dec, TIME_SECOND_PART_DIGITS);
+    max_length= decimals + int_part_length + (dec ? 1 : 0);
+  }
+  void fix_attributes_date()
+  {
+    fix_attributes_temporal(MAX_DATE_WIDTH, 0);
+  }
+  void fix_attributes_time(uint dec)
+  {
+    fix_attributes_temporal(MIN_TIME_WIDTH, dec);
+  }
+  void fix_attributes_datetime(uint dec)
+  {
+    fix_attributes_temporal(MAX_DATETIME_WIDTH, dec);
+  }
 };
 
 
@@ -1585,6 +1635,7 @@ class Type_handler_hybrid_field_type
 
 extern Type_handler_row   type_handler_row;
 extern Type_handler_null  type_handler_null;
+extern Type_handler_string type_handler_string;
 extern Type_handler_varchar type_handler_varchar;
 extern Type_handler_longlong type_handler_longlong;
 extern Type_handler_float type_handler_float;
@@ -1596,6 +1647,10 @@ extern Type_handler_bit type_handler_bit;
 extern Type_handler_enum type_handler_enum;
 extern Type_handler_set type_handler_set;
 
+extern Type_handler_time2       type_handler_time2;
+extern Type_handler_newdate     type_handler_newdate;
+extern Type_handler_datetime2   type_handler_datetime2;
+
 
 class Type_aggregator
 {
