diff --git a/sql/item.cc b/sql/item.cc
index a1c3f13..831e9b4 100644
--- a/sql/item.cc
+++ b/sql/item.cc
@@ -9958,12 +9958,12 @@ bool Item_type_holder::join_types(THD *thd, Item *item)
     if (collation.collation != &my_charset_bin)
     {
       max_length= MY_MAX(old_max_chars * collation.collation->mbmaxlen,
-                      display_length(item) /
+                      item->max_display_length() /
                       item->collation.collation->mbmaxlen *
                       collation.collation->mbmaxlen);
     }
     else
-      set_if_bigger(max_length, display_length(item));
+      set_if_bigger(max_length, item->max_display_length());
     break;
   }
   case REAL_RESULT:
@@ -10000,7 +10000,7 @@ bool Item_type_holder::join_types(THD *thd, Item *item)
     break;
   }
   default:
-    max_length= MY_MAX(max_length, display_length(item));
+    max_length= MY_MAX(max_length, item->max_display_length());
   };
   maybe_null|= item->maybe_null;
   get_full_info(item);
@@ -10012,64 +10012,6 @@ bool Item_type_holder::join_types(THD *thd, Item *item)
   DBUG_RETURN(FALSE);
 }
 
-/**
-  Calculate lenth for merging result for given Item type.
-
-  @param item  Item for length detection
-
-  @return
-    length
-*/
-
-uint32 Item_type_holder::display_length(Item *item)
-{
-  if (item->type() == Item::FIELD_ITEM)
-    return ((Item_field *)item)->max_disp_length();
-
-  switch (item->field_type())
-  {
-  case MYSQL_TYPE_DECIMAL:
-  case MYSQL_TYPE_TIMESTAMP:
-  case MYSQL_TYPE_DATE:
-  case MYSQL_TYPE_TIME:
-  case MYSQL_TYPE_DATETIME:
-  case MYSQL_TYPE_YEAR:
-  case MYSQL_TYPE_NEWDATE:
-  case MYSQL_TYPE_VARCHAR:
-  case MYSQL_TYPE_BIT:
-  case MYSQL_TYPE_NEWDECIMAL:
-  case MYSQL_TYPE_ENUM:
-  case MYSQL_TYPE_SET:
-  case MYSQL_TYPE_TINY_BLOB:
-  case MYSQL_TYPE_MEDIUM_BLOB:
-  case MYSQL_TYPE_LONG_BLOB:
-  case MYSQL_TYPE_BLOB:
-  case MYSQL_TYPE_VAR_STRING:
-  case MYSQL_TYPE_STRING:
-  case MYSQL_TYPE_GEOMETRY:
-    return item->max_length;
-  case MYSQL_TYPE_TINY:
-    return 4;
-  case MYSQL_TYPE_SHORT:
-    return 6;
-  case MYSQL_TYPE_LONG:
-    return MY_INT32_NUM_DECIMAL_DIGITS;
-  case MYSQL_TYPE_FLOAT:
-    return 25;
-  case MYSQL_TYPE_DOUBLE:
-    return 53;
-  case MYSQL_TYPE_NULL:
-    return 0;
-  case MYSQL_TYPE_LONGLONG:
-    return 20;
-  case MYSQL_TYPE_INT24:
-    return 8;
-  default:
-    DBUG_ASSERT(0); // we should never go there
-    return 0;
-  }
-}
-
 
 /**
   Make temporary table field according collected information about type
diff --git a/sql/item.h b/sql/item.h
index 6651fee..724356d 100644
--- a/sql/item.h
+++ b/sql/item.h
@@ -697,6 +697,34 @@ class Item: public Value_source,
   {
     return Type_handler::string_type_handler(max_length)->field_type();
   }
+  /*
+    Calculate the maximum length of an expression.
+    This method is used in data type aggregation for UNION, e.g.:
+      SELECT 'b' UNION SELECT COALESCE(double_10_3_field) FROM t1;
+
+    The result is usually equal to max_length, except for some numeric types.
+    In case of the INT, FLOAT, DOUBLE data types Item::max_length and
+    Item::decimals are ignored, so the returned value depends only on the
+    data type itself. E.g. for an expression of the DOUBLE(10,3) data type,
+    the result is always 53 (length 10 and precision 3 do not matter).
+
+    max_length is ignored for these numeric data types because the length limit
+    means only "expected maximum length", it is not a hard limit, so it does
+    not impose any data truncation. E.g. a column of the type INT(4) can
+    normally store big values up to 2147483647 without truncation. When we're
+    aggregating such column for UNION it's important to create a long enough
+    result column, not to lose any data.
+
+    For detailed behaviour of various data types see implementations of
+    the corresponding Type_handler_xxx::max_display_length().
+
+    Note, Item_field::max_display_length() overrides this to get
+    max_display_length() from the underlying field.
+  */
+  virtual uint32 max_display_length() const
+  {
+    return type_handler()->max_display_length(this);
+  }
   Item_cache* get_cache(THD *thd) const
   {
     return type_handler()->Item_get_cache(thd, this);
@@ -2498,7 +2526,7 @@ class Item_field :public Item_ident
   Item_equal *find_item_equal(COND_EQUAL *cond_equal);
   Item* propagate_equal_fields(THD *, const Context &, COND_EQUAL *);
   Item *replace_equal_field(THD *thd, uchar *arg);
-  inline uint32 max_disp_length() { return field->max_display_length(); }
+  uint32 max_display_length() const { return field->max_display_length(); }
   Item_field *field_for_view_update() { return this; }
   int fix_outer_field(THD *thd, Field **field, Item **reference);
   virtual Item *update_value_transformer(THD *thd, uchar *select_arg);
@@ -5575,7 +5603,6 @@ class Item_type_holder: public Item,
   String *val_str(String*);
   bool join_types(THD *thd, Item *);
   Field *make_field_by_type(TABLE *table);
-  static uint32 display_length(Item *item);
   static enum_field_types get_real_type(Item *);
   Field::geometry_type get_geometry_type() const { return geometry_type; };
   Item* get_copy(THD *thd, MEM_ROOT *mem_root) { return 0; }
diff --git a/sql/sql_type.cc b/sql/sql_type.cc
index fc34c7c..cc87c8e 100644
--- a/sql/sql_type.cc
+++ b/sql/sql_type.cc
@@ -695,6 +695,37 @@ Field *Type_handler_set::make_conversion_table_field(TABLE *table,
 
 /*************************************************************************/
 
+uint32 Type_handler_decimal_result::max_display_length(const Item *item) const
+{
+  return item->max_length;
+}
+
+
+uint32 Type_handler_temporal_result::max_display_length(const Item *item) const
+{
+  return item->max_length;
+}
+
+
+uint32 Type_handler_string_result::max_display_length(const Item *item) const
+{
+  return item->max_length;
+}
+
+
+uint32 Type_handler_year::max_display_length(const Item *item) const
+{
+  return item->max_length;
+}
+
+
+uint32 Type_handler_bit::max_display_length(const Item *item) const
+{
+  return item->max_length;
+}
+
+/*************************************************************************/
+
 int Type_handler_time_common::Item_save_in_field(Item *item, Field *field,
                                                  bool no_conversions) const
 {
diff --git a/sql/sql_type.h b/sql/sql_type.h
index 6a694df..d9bc0eb 100644
--- a/sql/sql_type.h
+++ b/sql/sql_type.h
@@ -291,6 +291,7 @@ class Type_handler
                           const Type_std_attributes *item,
                           SORT_FIELD_ATTR *attr) const= 0;
 
+  virtual uint32 max_display_length(const Item *item) const= 0;
   virtual int Item_save_in_field(Item *item, Field *field,
                                  bool no_conversions) const= 0;
   virtual Item_cache *Item_get_cache(THD *thd, const Item *item) const= 0;
@@ -372,6 +373,11 @@ class Type_handler_row: public Type_handler
   {
     DBUG_ASSERT(0);
   }
+  uint32 max_display_length(const Item *item) const
+  {
+    DBUG_ASSERT(0);
+    return 0;
+  }
   int Item_save_in_field(Item *item, Field *field, bool no_conversions) const
   {
     DBUG_ASSERT(0);
@@ -493,6 +499,7 @@ class Type_handler_decimal_result: public Type_handler_numeric
   void sortlength(THD *thd,
                   const Type_std_attributes *item,
                   SORT_FIELD_ATTR *attr) const;
+  uint32 max_display_length(const Item *item) const;
   int Item_save_in_field(Item *item, Field *field, bool no_conversions) const;
   Item_cache *Item_get_cache(THD *thd, const Item *item) const;
   bool set_comparator_func(Arg_comparator *cmp) const;
@@ -564,6 +571,7 @@ class Type_handler_temporal_result: public Type_handler
   void sortlength(THD *thd,
                   const Type_std_attributes *item,
                   SORT_FIELD_ATTR *attr) const;
+  uint32 max_display_length(const Item *item) const;
   Item_cache *Item_get_cache(THD *thd, const Item *item) const;
   bool set_comparator_func(Arg_comparator *cmp) const;
   bool Item_sum_hybrid_fix_length_and_dec(Item_sum_hybrid *func) const;
@@ -600,6 +608,7 @@ class Type_handler_string_result: public Type_handler
   void sortlength(THD *thd,
                   const Type_std_attributes *item,
                   SORT_FIELD_ATTR *attr) const;
+  uint32 max_display_length(const Item *item) const;
   int Item_save_in_field(Item *item, Field *field, bool no_conversions) const;
   Item_cache *Item_get_cache(THD *thd, const Item *item) const;
   bool set_comparator_func(Arg_comparator *cmp) const;
@@ -649,6 +658,7 @@ class Type_handler_tiny: public Type_handler_int_result
 public:
   virtual ~Type_handler_tiny() {}
   enum_field_types field_type() const { return MYSQL_TYPE_TINY; }
+  uint32 max_display_length(const Item *item) const { return 4; }
   Field *make_conversion_table_field(TABLE *TABLE, uint metadata,
                                      const Field *target) const;
 };
@@ -659,6 +669,7 @@ class Type_handler_short: public Type_handler_int_result
 public:
   virtual ~Type_handler_short() {}
   enum_field_types field_type() const { return MYSQL_TYPE_SHORT; }
+  uint32 max_display_length(const Item *item) const { return 6; }
   Field *make_conversion_table_field(TABLE *TABLE, uint metadata,
                                      const Field *target) const;
 };
@@ -669,6 +680,10 @@ class Type_handler_long: public Type_handler_int_result
 public:
   virtual ~Type_handler_long() {}
   enum_field_types field_type() const { return MYSQL_TYPE_LONG; }
+  uint32 max_display_length(const Item *item) const
+  {
+    return MY_INT32_NUM_DECIMAL_DIGITS;
+  }
   Field *make_conversion_table_field(TABLE *TABLE, uint metadata,
                                      const Field *target) const;
 };
@@ -679,6 +694,7 @@ class Type_handler_longlong: public Type_handler_int_result
 public:
   virtual ~Type_handler_longlong() {}
   enum_field_types field_type() const { return MYSQL_TYPE_LONGLONG; }
+  uint32 max_display_length(const Item *item) const { return 20; }
   Field *make_conversion_table_field(TABLE *TABLE, uint metadata,
                                      const Field *target) const;
 };
@@ -689,6 +705,7 @@ class Type_handler_int24: public Type_handler_int_result
 public:
   virtual ~Type_handler_int24() {}
   enum_field_types field_type() const { return MYSQL_TYPE_INT24; }
+  uint32 max_display_length(const Item *item) const { return 8; }
   Field *make_conversion_table_field(TABLE *, uint metadata,
                                      const Field *target) const;
 };
@@ -699,6 +716,7 @@ class Type_handler_year: public Type_handler_int_result
 public:
   virtual ~Type_handler_year() {}
   enum_field_types field_type() const { return MYSQL_TYPE_YEAR; }
+  uint32 max_display_length(const Item *item) const;
   Field *make_conversion_table_field(TABLE *, uint metadata,
                                      const Field *target) const;
 };
@@ -709,6 +727,7 @@ class Type_handler_bit: public Type_handler_int_result
 public:
   virtual ~Type_handler_bit() {}
   enum_field_types field_type() const { return MYSQL_TYPE_BIT; }
+  uint32 max_display_length(const Item *item) const;
   Field *make_conversion_table_field(TABLE *, uint metadata,
                                      const Field *target) const;
 };
@@ -719,6 +738,7 @@ class Type_handler_float: public Type_handler_real_result
 public:
   virtual ~Type_handler_float() {}
   enum_field_types field_type() const { return MYSQL_TYPE_FLOAT; }
+  uint32 max_display_length(const Item *item) const { return 25; }
   Field *make_num_distinct_aggregator_field(MEM_ROOT *, const Item *) const;
   Field *make_conversion_table_field(TABLE *, uint metadata,
                                      const Field *target) const;
@@ -730,6 +750,7 @@ class Type_handler_double: public Type_handler_real_result
 public:
   virtual ~Type_handler_double() {}
   enum_field_types field_type() const { return MYSQL_TYPE_DOUBLE; }
+  uint32 max_display_length(const Item *item) const { return 53; }
   Field *make_conversion_table_field(TABLE *, uint metadata,
                                      const Field *target) const;
 };
@@ -860,6 +881,7 @@ class Type_handler_null: public Type_handler_string_result
 public:
   virtual ~Type_handler_null() {}
   enum_field_types field_type() const { return MYSQL_TYPE_NULL; }
+  uint32 max_display_length(const Item *item) const { return 0; }
   Field *make_conversion_table_field(TABLE *, uint metadata,
                                      const Field *target) const;
 };
