diff --git a/mysql-test/r/gis.result b/mysql-test/r/gis.result
index fdc0e1e..1a4a4bb 100644
--- a/mysql-test/r/gis.result
+++ b/mysql-test/r/gis.result
@@ -2792,19 +2792,18 @@ t2	CREATE TABLE `t2` (
 CREATE TABLE t2 AS SELECT a FROM t1 UNION SELECT b FROM t1
 ERROR: 
 Illegal parameter data types set and geometry for operation 'UNION'
-# This does not preserve geometry type (MDEV-12560)
 CREATE TABLE t1 AS SELECT COALESCE(NULL, Point(1,1));
 SHOW CREATE TABLE t1;
 Table	Create Table
 t1	CREATE TABLE `t1` (
-  `COALESCE(NULL, Point(1,1))` geometry DEFAULT NULL
+  `COALESCE(NULL, Point(1,1))` point DEFAULT NULL
 ) ENGINE=MyISAM DEFAULT CHARSET=latin1
 DROP TABLE t1;
 CREATE TABLE t1 AS SELECT NULL UNION SELECT Point(1,1);
 SHOW CREATE TABLE t1;
 Table	Create Table
 t1	CREATE TABLE `t1` (
-  `NULL` geometry DEFAULT NULL
+  `NULL` point DEFAULT NULL
 ) ENGINE=MyISAM DEFAULT CHARSET=latin1
 DROP TABLE t1;
 DROP PROCEDURE p1;
@@ -3889,12 +3888,11 @@ Table	Create Table
 t2	CREATE TABLE `t2` (
   `LEAST(a,b)` longblob DEFAULT NULL
 ) ENGINE=MyISAM DEFAULT CHARSET=latin1
-# This does not preserve geometry type (MDEV-9405)
 CREATE TABLE t1 AS SELECT LEAST(NULL, Point(1,1));
 SHOW CREATE TABLE t1;
 Table	Create Table
 t1	CREATE TABLE `t1` (
-  `LEAST(NULL, Point(1,1))` geometry DEFAULT NULL
+  `LEAST(NULL, Point(1,1))` point DEFAULT NULL
 ) ENGINE=MyISAM DEFAULT CHARSET=latin1
 DROP TABLE t1;
 DROP PROCEDURE p1;
@@ -4122,5 +4120,185 @@ ERROR HY000: Illegal parameter data types geometry and varchar for operation 'st
 SELECT STR_TO_DATE('2001-01-01', POINT(1,1));
 ERROR HY000: Illegal parameter data types varchar and geometry for operation 'str_to_date'
 #
+# MDEV-12665 Hybrid functions do not preserve geometry type
+#
+CREATE TABLE t1 AS SELECT
+Point(0,0) AS p0,
+COALESCE(Point(0,0)) AS p1,
+CASE WHEN 0 THEN Point(0,0) ELSE Point(1,1) END AS p2;
+SHOW CREATE TABLE t1;
+Table	Create Table
+t1	CREATE TABLE `t1` (
+  `p0` point DEFAULT NULL,
+  `p1` point DEFAULT NULL,
+  `p2` point DEFAULT NULL
+) ENGINE=MyISAM DEFAULT CHARSET=latin1
+DROP TABLE t1;
+CREATE TABLE t1 AS SELECT LEAST(Point(0,0),Point(0,0)) AS p1;
+SHOW CREATE TABLE t1;
+Table	Create Table
+t1	CREATE TABLE `t1` (
+  `p1` point DEFAULT NULL
+) ENGINE=MyISAM DEFAULT CHARSET=latin1
+DROP TABLE t1;
+CREATE TABLE t1 (
+c_geometry GEOMETRY,
+c_point POINT,
+c_linestring LINESTRING,
+c_polygon POLYGON,
+c_multipoint  MULTIPOINT,
+c_multilinestring MULTILINESTRING,
+c_multipolygon MULTIPOLYGON,
+c_geometrycollection GEOMETRYCOLLECTION
+);
+CREATE TABLE t2 AS SELECT
+COALESCE(NULL, c_geometry),
+COALESCE(NULL, c_point),
+COALESCE(NULL, c_linestring),
+COALESCE(NULL, c_polygon),
+COALESCE(NULL, c_multipoint),
+COALESCE(NULL, c_multilinestring),
+COALESCE(NULL, c_multipolygon),
+COALESCE(NULL, c_geometrycollection)
+FROM t1;
+SHOW CREATE TABLE t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `COALESCE(NULL, c_geometry)` geometry DEFAULT NULL,
+  `COALESCE(NULL, c_point)` point DEFAULT NULL,
+  `COALESCE(NULL, c_linestring)` linestring DEFAULT NULL,
+  `COALESCE(NULL, c_polygon)` polygon DEFAULT NULL,
+  `COALESCE(NULL, c_multipoint)` multipoint DEFAULT NULL,
+  `COALESCE(NULL, c_multilinestring)` multilinestring DEFAULT NULL,
+  `COALESCE(NULL, c_multipolygon)` multipolygon DEFAULT NULL,
+  `COALESCE(NULL, c_geometrycollection)` geometrycollection DEFAULT NULL
+) ENGINE=MyISAM DEFAULT CHARSET=latin1
+DROP TABLE t2;
+CREATE TABLE t2 AS SELECT
+COALESCE(c_geometry, NULL),
+COALESCE(c_point, NULL),
+COALESCE(c_linestring, NULL),
+COALESCE(c_polygon, NULL),
+COALESCE(c_multipoint, NULL),
+COALESCE(c_multilinestring, NULL),
+COALESCE(c_multipolygon, NULL),
+COALESCE(c_geometrycollection, NULL)
+FROM t1;
+SHOW CREATE TABLE t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `COALESCE(c_geometry, NULL)` geometry DEFAULT NULL,
+  `COALESCE(c_point, NULL)` point DEFAULT NULL,
+  `COALESCE(c_linestring, NULL)` linestring DEFAULT NULL,
+  `COALESCE(c_polygon, NULL)` polygon DEFAULT NULL,
+  `COALESCE(c_multipoint, NULL)` multipoint DEFAULT NULL,
+  `COALESCE(c_multilinestring, NULL)` multilinestring DEFAULT NULL,
+  `COALESCE(c_multipolygon, NULL)` multipolygon DEFAULT NULL,
+  `COALESCE(c_geometrycollection, NULL)` geometrycollection DEFAULT NULL
+) ENGINE=MyISAM DEFAULT CHARSET=latin1
+DROP TABLE t2;
+CREATE TABLE t2 AS SELECT
+COALESCE(c_geometry, c_geometry),
+COALESCE(c_point, c_point),
+COALESCE(c_linestring, c_linestring),
+COALESCE(c_polygon, c_polygon),
+COALESCE(c_multipoint, c_multipoint),
+COALESCE(c_multilinestring, c_multilinestring),
+COALESCE(c_multipolygon, c_multipolygon),
+COALESCE(c_geometrycollection, c_geometrycollection)
+FROM t1;
+SHOW CREATE TABLE t2;
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `COALESCE(c_geometry, c_geometry)` geometry DEFAULT NULL,
+  `COALESCE(c_point, c_point)` point DEFAULT NULL,
+  `COALESCE(c_linestring, c_linestring)` linestring DEFAULT NULL,
+  `COALESCE(c_polygon, c_polygon)` polygon DEFAULT NULL,
+  `COALESCE(c_multipoint, c_multipoint)` multipoint DEFAULT NULL,
+  `COALESCE(c_multilinestring, c_multilinestring)` multilinestring DEFAULT NULL,
+  `COALESCE(c_multipolygon, c_multipolygon)` multipolygon DEFAULT NULL,
+  `COALESCE(c_geometrycollection, c_geometrycollection)` geometrycollection DEFAULT NULL
+) ENGINE=MyISAM DEFAULT CHARSET=latin1
+DROP TABLE t2;
+DROP TABLE t1;
+#
+# MDEV-12560 Wrong data type for SELECT NULL UNION SELECT Point(1,1)
+#
+CREATE TABLE t1 AS SELECT NULL AS c1 UNION SELECT POINT(1,1);
+SHOW CREATE TABLE t1;
+Table	Create Table
+t1	CREATE TABLE `t1` (
+  `c1` point DEFAULT NULL
+) ENGINE=MyISAM DEFAULT CHARSET=latin1
+DROP TABLE t1;
+CREATE PROCEDURE p1(name TEXT)
+BEGIN
+EXECUTE IMMEDIATE CONCAT('CREATE TABLE t1 (a ', name, ')');
+CREATE TABLE t2 AS
+SELECT a AS a1, a    AS a2, NULL AS a3 FROM t1 UNION
+SELECT a AS a1, NULL AS a2, a    AS a3 FROM t1;
+SHOW CREATE TABLE t2;
+DROP TABLE t2;
+DROP TABLE t1;
+END;
+$$
+CALL p1('geometry');
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `a1` geometry DEFAULT NULL,
+  `a2` geometry DEFAULT NULL,
+  `a3` geometry DEFAULT NULL
+) ENGINE=MyISAM DEFAULT CHARSET=latin1
+CALL p1('point');
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `a1` point DEFAULT NULL,
+  `a2` point DEFAULT NULL,
+  `a3` point DEFAULT NULL
+) ENGINE=MyISAM DEFAULT CHARSET=latin1
+CALL p1('linestring');
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `a1` linestring DEFAULT NULL,
+  `a2` linestring DEFAULT NULL,
+  `a3` linestring DEFAULT NULL
+) ENGINE=MyISAM DEFAULT CHARSET=latin1
+CALL p1('polygon');
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `a1` polygon DEFAULT NULL,
+  `a2` polygon DEFAULT NULL,
+  `a3` polygon DEFAULT NULL
+) ENGINE=MyISAM DEFAULT CHARSET=latin1
+CALL p1('multipoint');
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `a1` multipoint DEFAULT NULL,
+  `a2` multipoint DEFAULT NULL,
+  `a3` multipoint DEFAULT NULL
+) ENGINE=MyISAM DEFAULT CHARSET=latin1
+CALL p1('multilinestring');
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `a1` multilinestring DEFAULT NULL,
+  `a2` multilinestring DEFAULT NULL,
+  `a3` multilinestring DEFAULT NULL
+) ENGINE=MyISAM DEFAULT CHARSET=latin1
+CALL p1('multipolygon');
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `a1` multipolygon DEFAULT NULL,
+  `a2` multipolygon DEFAULT NULL,
+  `a3` multipolygon DEFAULT NULL
+) ENGINE=MyISAM DEFAULT CHARSET=latin1
+CALL p1('geometrycollection');
+Table	Create Table
+t2	CREATE TABLE `t2` (
+  `a1` geometrycollection DEFAULT NULL,
+  `a2` geometrycollection DEFAULT NULL,
+  `a3` geometrycollection DEFAULT NULL
+) ENGINE=MyISAM DEFAULT CHARSET=latin1
+DROP PROCEDURE p1;
+#
 # End of 10.3 tests
 #
diff --git a/mysql-test/t/gis.test b/mysql-test/t/gis.test
index 9b3dc69..3479733 100644
--- a/mysql-test/t/gis.test
+++ b/mysql-test/t/gis.test
@@ -1789,7 +1789,6 @@ CALL p1('CREATE TABLE t1 (a ENUM(0x61), b Point)');
 CALL p1('CREATE TABLE t1 (a SET(0x61), b Point)');
 --enable_query_log
 
---echo # This does not preserve geometry type (MDEV-12560)
 CREATE TABLE t1 AS SELECT COALESCE(NULL, Point(1,1));
 SHOW CREATE TABLE t1;
 DROP TABLE t1;
@@ -1932,7 +1931,6 @@ CALL p1('CREATE TABLE t1 (a ENUM(0x61), b Point)');
 CALL p1('CREATE TABLE t1 (a SET(0x61), b Point)');
 --enable_query_log
 
---echo # This does not preserve geometry type (MDEV-9405)
 CREATE TABLE t1 AS SELECT LEAST(NULL, Point(1,1));
 SHOW CREATE TABLE t1;
 DROP TABLE t1;
@@ -2197,5 +2195,104 @@ SELECT STR_TO_DATE(POINT(1,1),'%M %d,%Y');
 SELECT STR_TO_DATE('2001-01-01', POINT(1,1));
 
 --echo #
+--echo # MDEV-12665 Hybrid functions do not preserve geometry type
+--echo #
+
+CREATE TABLE t1 AS SELECT
+  Point(0,0) AS p0,
+  COALESCE(Point(0,0)) AS p1,
+  CASE WHEN 0 THEN Point(0,0) ELSE Point(1,1) END AS p2;
+SHOW CREATE TABLE t1;
+DROP TABLE t1;
+
+CREATE TABLE t1 AS SELECT LEAST(Point(0,0),Point(0,0)) AS p1;
+SHOW CREATE TABLE t1;
+DROP TABLE t1;
+
+CREATE TABLE t1 (
+  c_geometry GEOMETRY,
+  c_point POINT,
+  c_linestring LINESTRING,
+  c_polygon POLYGON,
+  c_multipoint  MULTIPOINT,
+  c_multilinestring MULTILINESTRING,
+  c_multipolygon MULTIPOLYGON,
+  c_geometrycollection GEOMETRYCOLLECTION
+);
+
+CREATE TABLE t2 AS SELECT
+  COALESCE(NULL, c_geometry),
+  COALESCE(NULL, c_point),
+  COALESCE(NULL, c_linestring),
+  COALESCE(NULL, c_polygon),
+  COALESCE(NULL, c_multipoint),
+  COALESCE(NULL, c_multilinestring),
+  COALESCE(NULL, c_multipolygon),
+  COALESCE(NULL, c_geometrycollection)
+FROM t1;
+SHOW CREATE TABLE t2;
+DROP TABLE t2;
+
+CREATE TABLE t2 AS SELECT
+  COALESCE(c_geometry, NULL),
+  COALESCE(c_point, NULL),
+  COALESCE(c_linestring, NULL),
+  COALESCE(c_polygon, NULL),
+  COALESCE(c_multipoint, NULL),
+  COALESCE(c_multilinestring, NULL),
+  COALESCE(c_multipolygon, NULL),
+  COALESCE(c_geometrycollection, NULL)
+FROM t1;
+SHOW CREATE TABLE t2;
+DROP TABLE t2;
+
+CREATE TABLE t2 AS SELECT
+  COALESCE(c_geometry, c_geometry),
+  COALESCE(c_point, c_point),
+  COALESCE(c_linestring, c_linestring),
+  COALESCE(c_polygon, c_polygon),
+  COALESCE(c_multipoint, c_multipoint),
+  COALESCE(c_multilinestring, c_multilinestring),
+  COALESCE(c_multipolygon, c_multipolygon),
+  COALESCE(c_geometrycollection, c_geometrycollection)
+FROM t1;
+SHOW CREATE TABLE t2;
+DROP TABLE t2;
+
+DROP TABLE t1;
+
+
+--echo #
+--echo # MDEV-12560 Wrong data type for SELECT NULL UNION SELECT Point(1,1)
+--echo #
+
+CREATE TABLE t1 AS SELECT NULL AS c1 UNION SELECT POINT(1,1);
+SHOW CREATE TABLE t1;
+DROP TABLE t1;
+
+DELIMITER $$;
+CREATE PROCEDURE p1(name TEXT)
+BEGIN
+  EXECUTE IMMEDIATE CONCAT('CREATE TABLE t1 (a ', name, ')');
+  CREATE TABLE t2 AS
+    SELECT a AS a1, a    AS a2, NULL AS a3 FROM t1 UNION
+    SELECT a AS a1, NULL AS a2, a    AS a3 FROM t1;
+  SHOW CREATE TABLE t2;
+  DROP TABLE t2;
+  DROP TABLE t1;
+END;
+$$
+DELIMITER ;$$
+CALL p1('geometry');
+CALL p1('point');
+CALL p1('linestring');
+CALL p1('polygon');
+CALL p1('multipoint');
+CALL p1('multilinestring');
+CALL p1('multipolygon');
+CALL p1('geometrycollection');
+DROP PROCEDURE p1;
+
+--echo #
 --echo # End of 10.3 tests
 --echo #
diff --git a/sql/item.cc b/sql/item.cc
index c145e69..3bb232f 100644
--- a/sql/item.cc
+++ b/sql/item.cc
@@ -10029,18 +10029,14 @@ void Item_cache_row::set_null()
 Item_type_holder::Item_type_holder(THD *thd, Item *item)
   :Item(thd, item),
    Type_handler_hybrid_field_type(item->real_type_handler()),
-   enum_set_typelib(0),
-   geometry_type(Field::GEOM_GEOMETRY)
+   Type_geometry_attributes(item),
+   enum_set_typelib(0)
 {
   DBUG_ASSERT(item->fixed);
   maybe_null= item->maybe_null;
   get_full_info(item);
   DBUG_ASSERT(!decimals || Item_type_holder::result_type() != INT_RESULT);
   prev_decimal_int_part= item->decimal_int_part();
-#ifdef HAVE_SPATIAL
-  if (item->field_type() == MYSQL_TYPE_GEOMETRY)
-    geometry_type= item->get_geometry_type();
-#endif /* HAVE_SPATIAL */
 }
 
 
@@ -10093,9 +10089,7 @@ bool Item_type_holder::join_types(THD *thd, Item *item)
   else
     decimals= MY_MAX(decimals, item->decimals);
 
-  if (Item_type_holder::field_type() == FIELD_TYPE_GEOMETRY)
-    geometry_type=
-      Field_geom::geometry_type_merge(geometry_type, item->get_geometry_type());
+  Type_geometry_attributes::join(item);
 
   if (Item_type_holder::result_type() == DECIMAL_RESULT)
   {
diff --git a/sql/item.h b/sql/item.h
index 0e00457..cf61e63 100644
--- a/sql/item.h
+++ b/sql/item.h
@@ -1805,6 +1805,55 @@ inline Item* get_item_copy (THD *thd, MEM_ROOT *mem_root, T* item)
 }	
 
 
+class Type_geometry_attributes
+{
+  uint m_geometry_type;
+  static const uint m_geometry_type_unknown= Field::GEOM_GEOMETRYCOLLECTION + 1;
+  void copy(const Item *item)
+  {
+    // Ignore implicit NULLs
+    m_geometry_type= item->type_handler() == &type_handler_geometry ?
+                     item->uint_geometry_type() :
+                     m_geometry_type_unknown;
+  }
+public:
+  Type_geometry_attributes()
+   :m_geometry_type(m_geometry_type_unknown)
+  { }
+  Type_geometry_attributes(const Item *item)
+   :m_geometry_type(m_geometry_type_unknown)
+  {
+    copy(item);
+  }
+  void join(const Item *item)
+  {
+    // Ignore implicit NULLs
+    if (m_geometry_type == m_geometry_type_unknown)
+      copy(item);
+    else if (item->type_handler() == &type_handler_geometry)
+    {
+      m_geometry_type=
+        Field_geom::geometry_type_merge((Field_geom::geometry_type)
+                                         m_geometry_type,
+                                        (Field_geom::geometry_type)
+                                         item->uint_geometry_type());
+    }
+  }
+  Field::geometry_type get_geometry_type() const
+  {
+    return m_geometry_type == m_geometry_type_unknown ?
+           Field::GEOM_GEOMETRY :
+           (Field::geometry_type) m_geometry_type;
+  }
+  void set_geometry_type(uint type)
+  {
+    DBUG_ASSERT(type <= m_geometry_type_unknown);
+    m_geometry_type= type;
+  }
+};
+
+
+
 /**
   Compare two Items for List<Item>::add_unique()
 */
@@ -5774,12 +5823,11 @@ class Item_cache_row: public Item_cache
   single SP/PS execution.
 */
 class Item_type_holder: public Item,
-                        public Type_handler_hybrid_field_type
+                        public Type_handler_hybrid_field_type,
+                        public Type_geometry_attributes
 {
 protected:
   TYPELIB *enum_set_typelib;
-  Field::geometry_type geometry_type;
-
   void get_full_info(Item *item);
 
   /* It is used to count decimal precision in join_types */
@@ -5828,7 +5876,10 @@ class Item_type_holder: public Item,
            make_and_init_table_field(&name, Record_addr(maybe_null),
                                      *this, table);
   }
-  Field::geometry_type get_geometry_type() const { return geometry_type; };
+  Field::geometry_type get_geometry_type() const
+  {
+    return Type_geometry_attributes::get_geometry_type();
+  }
   Item* get_copy(THD *thd, MEM_ROOT *mem_root) { return 0; }
 };
 
diff --git a/sql/item_func.h b/sql/item_func.h
index 33f671c..4a90551 100644
--- a/sql/item_func.h
+++ b/sql/item_func.h
@@ -381,7 +381,8 @@ class Item_real_func :public Item_func
   Functions whose returned field type is determined at fix_fields() time.
 */
 class Item_hybrid_func: public Item_func,
-                        public Type_handler_hybrid_field_type
+                        public Type_handler_hybrid_field_type,
+                        public Type_geometry_attributes
 {
 protected:
   bool fix_attributes(Item **item, uint nitems);
@@ -402,6 +403,8 @@ class Item_hybrid_func: public Item_func,
   { return Type_handler_hybrid_field_type::result_type(); }
   enum Item_result cmp_type () const
   { return Type_handler_hybrid_field_type::cmp_type(); }
+  Field::geometry_type get_geometry_type() const
+  { return Type_geometry_attributes::get_geometry_type(); };
 };
 
 
diff --git a/sql/sql_type.cc b/sql/sql_type.cc
index 23cfe20..52893f6 100644
--- a/sql/sql_type.cc
+++ b/sql/sql_type.cc
@@ -2115,6 +2115,26 @@ bool Type_handler_timestamp_common::
   return false;
 }
 
+#ifdef HAVE_SPATIAL
+bool Type_handler_geometry::
+       Item_hybrid_func_fix_attributes(THD *thd, Item_hybrid_func *func,
+                                       Item **items, uint nitems) const
+{
+  DBUG_ASSERT(nitems > 0);
+  Type_geometry_attributes gattr(items[0]);
+  for (uint i= 1; i < nitems; i++)
+    gattr.join(items[i]);
+  func->set_geometry_type(gattr.get_geometry_type());
+  func->collation.set(&my_charset_bin);
+  func->unsigned_flag= false;
+  func->decimals= 0;
+  func->max_length= (uint32) UINT_MAX32;
+  func->maybe_null= true;
+  return false;
+}
+#endif
+
+
 /*************************************************************************/
 
 bool Type_handler::
diff --git a/sql/sql_type.h b/sql/sql_type.h
index 67a302e..b28b76d 100644
--- a/sql/sql_type.h
+++ b/sql/sql_type.h
@@ -2032,6 +2032,8 @@ class Type_handler_geometry: public Type_handler_string_result
   bool Item_func_int_val_fix_length_and_dec(Item_func_int_val *) const;
   bool Item_func_abs_fix_length_and_dec(Item_func_abs *) const;
   bool Item_func_neg_fix_length_and_dec(Item_func_neg *) const;
+  bool Item_hybrid_func_fix_attributes(THD *thd, Item_hybrid_func *func,
+                                       Item **items, uint nitems) const;
   bool Item_sum_sum_fix_length_and_dec(Item_sum_sum *) const;
   bool Item_sum_avg_fix_length_and_dec(Item_sum_avg *) const;
   bool Item_sum_variance_fix_length_and_dec(Item_sum_variance *) const;
