commit 269368e696aa9b03be743e7fbc0a5838ba54f49a
Author: Alexander Barkov <bar@mariadb.org>
Date:   Thu Jan 26 13:01:52 2017 +0400

    MDEV-11913 Split sp_get_item_value() into methods in Type_handler
    
    This patch also fixes:
    MDEV-11815 SP variables of temporal data types do not replicate correctly

diff --git a/mysql-test/suite/binlog/r/binlog_stm_sp.result b/mysql-test/suite/binlog/r/binlog_stm_sp.result
new file mode 100644
index 0000000..6c47051
--- /dev/null
+++ b/mysql-test/suite/binlog/r/binlog_stm_sp.result
@@ -0,0 +1,86 @@
+#
+# MDEV-11815 SP variables of temporal data types do not replicate correctly
+#
+CREATE TABLE t1(a INT);
+CREATE PROCEDURE p1()
+BEGIN
+DECLARE i   INT          DEFAULT 123;
+DECLARE b8  BIT(8)       DEFAULT 0x61;
+DECLARE t0  TIME         DEFAULT '01:01:01';
+DECLARE t6  TIME(6)      DEFAULT '01:01:01.123456';
+DECLARE d   DATE         DEFAULT '2001-01-01';
+DECLARE dt0 DATETIME     DEFAULT '2001-01-01 01:01:01';
+DECLARE dt6 DATETIME(6)  DEFAULT '2001-01-01 01:01:01.123456';
+DECLARE ts0 TIMESTAMP    DEFAULT '2001-01-01 01:01:01';
+DECLARE ts6 TIMESTAMP(6) DEFAULT '2001-01-01 01:01:01.123456';
+INSERT INTO t1 VALUES (i=0x61);
+INSERT INTO t1 VALUES (b8=0x61);
+INSERT INTO t1 VALUES (t0=10101);
+INSERT INTO t1 VALUES (t6=10101);
+INSERT INTO t1 VALUES (d=20010101);
+INSERT INTO t1 VALUES (dt0=20010101010101);
+INSERT INTO t1 VALUES (dt6=20010101010101);
+INSERT INTO t1 VALUES (ts0=20010101010101);
+INSERT INTO t1 VALUES (ts6=20010101010101);
+END;
+$$
+CALL p1;
+DROP TABLE t1;
+DROP PROCEDURE p1;
+include/show_binlog_events.inc
+Log_name	Pos	Event_type	Server_id	End_log_pos	Info
+master-bin.000001	#	Gtid	#	#	GTID #-#-#
+master-bin.000001	#	Query	#	#	use `test`; CREATE TABLE t1(a INT)
+master-bin.000001	#	Gtid	#	#	GTID #-#-#
+master-bin.000001	#	Query	#	#	use `test`; CREATE DEFINER=`root`@`localhost` PROCEDURE `p1`()
+BEGIN
+DECLARE i   INT          DEFAULT 123;
+DECLARE b8  BIT(8)       DEFAULT 0x61;
+DECLARE t0  TIME         DEFAULT '01:01:01';
+DECLARE t6  TIME(6)      DEFAULT '01:01:01.123456';
+DECLARE d   DATE         DEFAULT '2001-01-01';
+DECLARE dt0 DATETIME     DEFAULT '2001-01-01 01:01:01';
+DECLARE dt6 DATETIME(6)  DEFAULT '2001-01-01 01:01:01.123456';
+DECLARE ts0 TIMESTAMP    DEFAULT '2001-01-01 01:01:01';
+DECLARE ts6 TIMESTAMP(6) DEFAULT '2001-01-01 01:01:01.123456';
+INSERT INTO t1 VALUES (i=0x61);
+INSERT INTO t1 VALUES (b8=0x61);
+INSERT INTO t1 VALUES (t0=10101);
+INSERT INTO t1 VALUES (t6=10101);
+INSERT INTO t1 VALUES (d=20010101);
+INSERT INTO t1 VALUES (dt0=20010101010101);
+INSERT INTO t1 VALUES (dt6=20010101010101);
+INSERT INTO t1 VALUES (ts0=20010101010101);
+INSERT INTO t1 VALUES (ts6=20010101010101);
+END
+master-bin.000001	#	Gtid	#	#	BEGIN GTID #-#-#
+master-bin.000001	#	Query	#	#	use `test`; INSERT INTO t1 VALUES ( NAME_CONST('i',123)=0x61)
+master-bin.000001	#	Query	#	#	COMMIT
+master-bin.000001	#	Gtid	#	#	BEGIN GTID #-#-#
+master-bin.000001	#	Query	#	#	use `test`; INSERT INTO t1 VALUES ( NAME_CONST('b8',_binary'a' COLLATE 'binary')=0x61)
+master-bin.000001	#	Query	#	#	COMMIT
+master-bin.000001	#	Gtid	#	#	BEGIN GTID #-#-#
+master-bin.000001	#	Query	#	#	use `test`; INSERT INTO t1 VALUES ( NAME_CONST('t0',TIME'01:01:01')=10101)
+master-bin.000001	#	Query	#	#	COMMIT
+master-bin.000001	#	Gtid	#	#	BEGIN GTID #-#-#
+master-bin.000001	#	Query	#	#	use `test`; INSERT INTO t1 VALUES ( NAME_CONST('t6',TIME'01:01:01.123456')=10101)
+master-bin.000001	#	Query	#	#	COMMIT
+master-bin.000001	#	Gtid	#	#	BEGIN GTID #-#-#
+master-bin.000001	#	Query	#	#	use `test`; INSERT INTO t1 VALUES ( NAME_CONST('d',DATE'2001-01-01')=20010101)
+master-bin.000001	#	Query	#	#	COMMIT
+master-bin.000001	#	Gtid	#	#	BEGIN GTID #-#-#
+master-bin.000001	#	Query	#	#	use `test`; INSERT INTO t1 VALUES ( NAME_CONST('dt0',TIMESTAMP'2001-01-01 01:01:01')=20010101010101)
+master-bin.000001	#	Query	#	#	COMMIT
+master-bin.000001	#	Gtid	#	#	BEGIN GTID #-#-#
+master-bin.000001	#	Query	#	#	use `test`; INSERT INTO t1 VALUES ( NAME_CONST('dt6',TIMESTAMP'2001-01-01 01:01:01.123456')=20010101010101)
+master-bin.000001	#	Query	#	#	COMMIT
+master-bin.000001	#	Gtid	#	#	BEGIN GTID #-#-#
+master-bin.000001	#	Query	#	#	use `test`; INSERT INTO t1 VALUES ( NAME_CONST('ts0',TIMESTAMP'2001-01-01 01:01:01')=20010101010101)
+master-bin.000001	#	Query	#	#	COMMIT
+master-bin.000001	#	Gtid	#	#	BEGIN GTID #-#-#
+master-bin.000001	#	Query	#	#	use `test`; INSERT INTO t1 VALUES ( NAME_CONST('ts6',TIMESTAMP'2001-01-01 01:01:01.123456')=20010101010101)
+master-bin.000001	#	Query	#	#	COMMIT
+master-bin.000001	#	Gtid	#	#	GTID #-#-#
+master-bin.000001	#	Query	#	#	use `test`; DROP TABLE `t1` /* generated by server */
+master-bin.000001	#	Gtid	#	#	GTID #-#-#
+master-bin.000001	#	Query	#	#	use `test`; DROP PROCEDURE p1
diff --git a/mysql-test/suite/binlog/t/binlog_stm_sp.test b/mysql-test/suite/binlog/t/binlog_stm_sp.test
new file mode 100644
index 0000000..095b4c7
--- /dev/null
+++ b/mysql-test/suite/binlog/t/binlog_stm_sp.test
@@ -0,0 +1,41 @@
+--source include/have_binlog_format_statement.inc
+
+--disable_query_log
+reset master; # get rid of previous tests binlog
+--enable_query_log
+
+--echo #
+--echo # MDEV-11815 SP variables of temporal data types do not replicate correctly
+--echo #
+
+CREATE TABLE t1(a INT);
+DELIMITER $$;
+CREATE PROCEDURE p1()
+BEGIN
+  DECLARE i   INT          DEFAULT 123;
+  DECLARE b8  BIT(8)       DEFAULT 0x61;
+  DECLARE t0  TIME         DEFAULT '01:01:01';
+  DECLARE t6  TIME(6)      DEFAULT '01:01:01.123456';
+  DECLARE d   DATE         DEFAULT '2001-01-01';
+  DECLARE dt0 DATETIME     DEFAULT '2001-01-01 01:01:01';
+  DECLARE dt6 DATETIME(6)  DEFAULT '2001-01-01 01:01:01.123456';
+  DECLARE ts0 TIMESTAMP    DEFAULT '2001-01-01 01:01:01';
+  DECLARE ts6 TIMESTAMP(6) DEFAULT '2001-01-01 01:01:01.123456';
+  INSERT INTO t1 VALUES (i=0x61);
+  INSERT INTO t1 VALUES (b8=0x61);
+  INSERT INTO t1 VALUES (t0=10101);
+  INSERT INTO t1 VALUES (t6=10101);
+  INSERT INTO t1 VALUES (d=20010101);
+  INSERT INTO t1 VALUES (dt0=20010101010101);
+  INSERT INTO t1 VALUES (dt6=20010101010101);
+  INSERT INTO t1 VALUES (ts0=20010101010101);
+  INSERT INTO t1 VALUES (ts6=20010101010101);
+END;
+$$
+DELIMITER ;$$
+CALL p1;
+DROP TABLE t1;
+DROP PROCEDURE p1;
+
+--let $binlog_file = LAST
+source include/show_binlog_events.inc;
diff --git a/mysql-test/suite/rpl/r/rpl_stm_sp.result b/mysql-test/suite/rpl/r/rpl_stm_sp.result
new file mode 100644
index 0000000..4e2d4c8
--- /dev/null
+++ b/mysql-test/suite/rpl/r/rpl_stm_sp.result
@@ -0,0 +1,26 @@
+include/master-slave.inc
+[connection master]
+#
+# MDEV-11815 SP variables of temporal data types do not replicate correctly
+#
+connection master;
+CREATE TABLE t1(a INT);
+CREATE PROCEDURE p1()
+BEGIN
+DECLARE a TIME DEFAULT '01:01:01';
+INSERT INTO t1 VALUES (a=10101);
+END;
+$$
+CALL p1;
+SELECT * FROM t1;
+a
+1
+connection slave;
+SELECT * FROM t1;
+a
+1
+connection master;
+DROP TABLE t1;
+DROP PROCEDURE p1;
+connection slave;
+include/rpl_end.inc
diff --git a/mysql-test/suite/rpl/t/rpl_stm_sp.test b/mysql-test/suite/rpl/t/rpl_stm_sp.test
new file mode 100644
index 0000000..b99906b
--- /dev/null
+++ b/mysql-test/suite/rpl/t/rpl_stm_sp.test
@@ -0,0 +1,30 @@
+--source include/have_binlog_format_statement.inc
+--source include/master-slave.inc
+
+--echo #
+--echo # MDEV-11815 SP variables of temporal data types do not replicate correctly
+--echo #
+
+connection master;
+CREATE TABLE t1(a INT);
+DELIMITER $$;
+CREATE PROCEDURE p1()
+BEGIN
+  DECLARE a TIME DEFAULT '01:01:01';
+  INSERT INTO t1 VALUES (a=10101);
+END;
+$$
+DELIMITER ;$$
+CALL p1;
+SELECT * FROM t1;
+
+sync_slave_with_master;
+SELECT * FROM t1;
+
+connection master;
+DROP TABLE t1;
+DROP PROCEDURE p1;
+sync_slave_with_master;
+
+
+--source include/rpl_end.inc
diff --git a/sql/sp_head.cc b/sql/sp_head.cc
index 441de33..f55320f 100644
--- a/sql/sp_head.cc
+++ b/sql/sp_head.cc
@@ -92,65 +92,6 @@ sp_map_item_type(enum enum_field_types type)
 }
 
 
-/**
-  Return a string representation of the Item value.
-
-  @param thd     thread handle
-  @param str     string buffer for representation of the value
-
-  @note
-    If the item has a string result type, the string is escaped
-    according to its character set.
-
-  @retval
-    NULL      on error
-  @retval
-    non-NULL  a pointer to valid a valid string on success
-*/
-
-static String *
-sp_get_item_value(THD *thd, Item *item, String *str)
-{
-  switch (item->result_type()) {
-  case REAL_RESULT:
-  case INT_RESULT:
-  case DECIMAL_RESULT:
-    if (item->field_type() != MYSQL_TYPE_BIT)
-      return item->val_str(str);
-    else {/* Bit type is handled as binary string */}
-  case STRING_RESULT:
-    {
-      String *result= item->val_str(str);
-
-      if (!result)
-        return NULL;
-
-      {
-        StringBuffer<STRING_BUFFER_USUAL_SIZE> buf(result->charset());
-        CHARSET_INFO *cs= thd->variables.character_set_client;
-
-        buf.append('_');
-        buf.append(result->charset()->csname);
-        if (cs->escape_with_backslash_is_dangerous)
-          buf.append(' ');
-        append_query_string(cs, &buf, result->ptr(), result->length(),
-                           thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES);
-        buf.append(" COLLATE '");
-        buf.append(item->collation.collation->name);
-        buf.append('\'');
-        str->copy(buf);
-
-        return str;
-      }
-    }
-
-  case ROW_RESULT:
-  default:
-    return NULL;
-  }
-}
-
-
 bool Item_splocal::append_for_log(THD *thd, String *str)
 {
   if (fix_fields(thd, NULL))
@@ -165,7 +106,9 @@ bool Item_splocal::append_for_log(THD *thd, String *str)
     return true;
 
   StringBuffer<STRING_BUFFER_USUAL_SIZE> str_value_holder(&my_charset_latin1);
-  String *str_value= sp_get_item_value(thd, this_item(), &str_value_holder);
+  Item *item= this_item();
+  String *str_value= item->type_handler()->sp_get_item_value(thd, item,
+                                                             &str_value_holder);
   if (str_value)
     return str->append(*str_value) || str->append(')');
   else
@@ -1758,9 +1701,9 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount,
       if (arg_no)
         binlog_buf.append(',');
 
-      str_value= sp_get_item_value(thd, nctx->get_item(arg_no),
-                                   &str_value_holder);
-
+      Item *item= nctx->get_item(arg_no);
+      str_value= item->type_handler()->sp_get_item_value(thd, item,
+                                                         &str_value_holder);
       if (str_value)
         binlog_buf.append(*str_value);
       else
diff --git a/sql/sql_type.cc b/sql/sql_type.cc
index d43dd2c..0826595 100644
--- a/sql/sql_type.cc
+++ b/sql/sql_type.cc
@@ -2013,3 +2013,99 @@ bool Type_handler_temporal_result::
   return func->get_date_native(ltime, fuzzydate);
 }
 
+/***************************************************************************/
+
+String *Type_handler_row::
+          sp_get_item_value(THD *thd, Item *item, String *str) const
+{
+  DBUG_ASSERT(0);
+  return NULL;
+}
+
+
+String *Type_handler::
+          sp_get_item_value_csstr(THD *thd, Item *item, String *str) const
+{
+  String *result= item->val_str(str);
+
+  if (!result)
+    return NULL;
+
+  StringBuffer<STRING_BUFFER_USUAL_SIZE> buf(result->charset());
+  CHARSET_INFO *cs= thd->variables.character_set_client;
+
+  buf.append('_');
+  buf.append(result->charset()->csname);
+  if (cs->escape_with_backslash_is_dangerous)
+    buf.append(' ');
+  append_query_string(cs, &buf, result->ptr(), result->length(),
+                     thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES);
+  buf.append(" COLLATE '");
+  buf.append(item->collation.collation->name);
+  buf.append('\'');
+  str->copy(buf);
+
+  return str;
+}
+
+
+String *Type_handler_numeric::
+          sp_get_item_value(THD *thd, Item *item, String *str) const
+{
+  return item->val_str(str);
+}
+
+
+String *Type_handler::
+          sp_get_item_value_temporal(THD *thd, Item *item, String *str,
+                                     const Name &type_name, String *buf) const
+{
+  String *result= item->val_str(buf);
+  return !result ||
+         str->realloc(type_name.length() + result->length() + 2) ||
+         str->copy(type_name.ptr(), type_name.length(), &my_charset_latin1) ||
+         str->append('\'') ||
+         str->append(result->ptr(), result->length()) ||
+         str->append('\'') ?
+         NULL :
+         str;
+}
+
+
+String *Type_handler_time_common::
+          sp_get_item_value(THD *thd, Item *item, String *str) const
+{
+  StringBuffer<MAX_TIME_FULL_WIDTH+1> buf;
+  return sp_get_item_value_temporal(thd, item, str,
+                                    Name(C_STRING_WITH_LEN("TIME")), &buf);
+}
+
+
+String *Type_handler_date_common::
+          sp_get_item_value(THD *thd, Item *item, String *str) const
+{
+  StringBuffer<MAX_DATE_WIDTH+1> buf;
+  return sp_get_item_value_temporal(thd, item, str,
+                                    Name(C_STRING_WITH_LEN("DATE")), &buf);
+}
+
+
+String *Type_handler_datetime_common::
+          sp_get_item_value(THD *thd, Item *item, String *str) const
+{
+  StringBuffer<MAX_DATETIME_FULL_WIDTH+1> buf;
+  return sp_get_item_value_temporal(thd, item, str,
+                                    Name(C_STRING_WITH_LEN("TIMESTAMP")), &buf);
+}
+
+
+String *Type_handler_timestamp_common::
+          sp_get_item_value(THD *thd, Item *item, String *str) const
+{
+  StringBuffer<MAX_DATETIME_FULL_WIDTH+1> buf;
+  return sp_get_item_value_temporal(thd, item, str,
+                                    Name(C_STRING_WITH_LEN("TIMESTAMP")), &buf);
+}
+
+
+/***************************************************************************/
diff --git a/sql/sql_type.h b/sql/sql_type.h
index 29e1a29..4a99e65 100644
--- a/sql/sql_type.h
+++ b/sql/sql_type.h
@@ -259,6 +259,9 @@ class Name: private LEX_CSTRING
 class Type_handler
 {
 protected:
+  String *sp_get_item_value_csstr(THD *thd, Item *item, String *str) const;
+  String *sp_get_item_value_temporal(THD *thd, Item *item, String *str,
+                                     const Name &type_name, String *buf) const;
   void make_sort_key_longlong(uchar *to,
                               bool maybe_null, bool null_value,
                               bool unsigned_flag,
@@ -347,6 +350,24 @@ class Type_handler
   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;
+
+  /**
+    Return a string representation of the Item value.
+
+    @param thd     thread handle
+    @param str     string buffer for representation of the value
+
+    @note
+      If the item has a string result type, the string is escaped
+      according to its character set.
+
+    @retval
+      NULL      on error
+    @retval
+      non-NULL  a pointer to valid a valid string on success
+  */
+  virtual String *sp_get_item_value(THD *thd, Item *item, String *str) const= 0;
+
   /**
     Check if
       WHERE expr=value AND expr=const
@@ -481,6 +502,7 @@ class Type_handler_row: public Type_handler
     DBUG_ASSERT(0);
     return 1;
   }
+  String *sp_get_item_value(THD *thd, Item *item, String *str) const;
   bool can_change_cond_ref_to_const(Item_bool_func2 *target,
                                    Item *target_expr, Item *target_value,
                                    Item_bool_func2 *source,
@@ -585,6 +607,7 @@ class Type_handler_numeric: public Type_handler
                                                   const Type_handler *handler)
                                                   const;
 public:
+  String *sp_get_item_value(THD *thd, Item *item, String *str) const;
   double Item_func_min_max_val_real(Item_func_min_max *) const;
   longlong Item_func_min_max_val_int(Item_func_min_max *) const;
   my_decimal *Item_func_min_max_val_decimal(Item_func_min_max *,
@@ -788,6 +811,10 @@ class Type_handler_string_result: public Type_handler
                   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;
+  String *sp_get_item_value(THD *thd, Item *item, String *str) const
+  {
+    return sp_get_item_value_csstr(thd, item, str);
+  }
   bool can_change_cond_ref_to_const(Item_bool_func2 *target,
                                    Item *target_expr, Item *target_value,
                                    Item_bool_func2 *source,
@@ -934,6 +961,10 @@ class Type_handler_bit: public Type_handler_int_result
   const Name name() const { return m_name_bit; }
   enum_field_types field_type() const { return MYSQL_TYPE_BIT; }
   uint32 max_display_length(const Item *item) const;
+  String *sp_get_item_value(THD *thd, Item *item, String *str) const
+  {
+    return sp_get_item_value_csstr(thd, item, str);
+  }
   Field *make_conversion_table_field(TABLE *, uint metadata,
                                      const Field *target) const;
 };
@@ -975,6 +1006,7 @@ class Type_handler_time_common: public Type_handler_temporal_result
   enum_field_types field_type() const { return MYSQL_TYPE_TIME; }
   const Type_handler *type_handler_for_comparison() const;
   int Item_save_in_field(Item *item, Field *field, bool no_conversions) const;
+  String *sp_get_item_value(THD *thd, Item *item, String *str) const;
   bool Item_hybrid_func_fix_attributes(THD *thd, Item_hybrid_func *func,
                                        Item **items, uint nitems) const;
   cmp_item *make_cmp_item(THD *thd, CHARSET_INFO *cs) const;
@@ -1019,6 +1051,7 @@ class Type_handler_date_common: public Type_handler_temporal_with_date
   virtual ~Type_handler_date_common() {}
   const Name name() const { return m_name_date; }
   enum_field_types field_type() const { return MYSQL_TYPE_DATE; }
+  String *sp_get_item_value(THD *thd, Item *item, String *str) const;
   bool Item_hybrid_func_fix_attributes(THD *thd, Item_hybrid_func *func,
                                        Item **items, uint nitems) const;
 };
@@ -1048,6 +1081,7 @@ class Type_handler_datetime_common: public Type_handler_temporal_with_date
   virtual ~Type_handler_datetime_common() {}
   const Name name() const { return m_name_datetime; }
   enum_field_types field_type() const { return MYSQL_TYPE_DATETIME; }
+  String *sp_get_item_value(THD *thd, Item *item, String *str) const;
   bool Item_hybrid_func_fix_attributes(THD *thd, Item_hybrid_func *func,
                                        Item **items, uint nitems) const;
 };
@@ -1079,6 +1113,7 @@ class Type_handler_timestamp_common: public Type_handler_temporal_with_date
   virtual ~Type_handler_timestamp_common() {}
   const Name name() const { return m_name_timestamp; }
   enum_field_types field_type() const { return MYSQL_TYPE_TIMESTAMP; }
+  String *sp_get_item_value(THD *thd, Item *item, String *str) const;
   bool Item_hybrid_func_fix_attributes(THD *thd, Item_hybrid_func *func,
                                        Item **items, uint nitems) const;
 };
