← Back to team overview

maria-developers team mailing list archive

Please review MDEV-11672 mysql_list_field() returns wrong default values for VIEW

 

Hello Sanja,

Can you please review a patch for MDEV-11672?

Thanks!
commit c41ccee0fcf5d5354faceadcb77e2e508075be74
Author: Alexander Barkov <bar@xxxxxxxxxxx>
Date:   Tue Dec 27 19:44:54 2016 +0400

    MDEV-11672 mysql_list_field() returns wrong default values for VIEW
    
    The problem happened because Item_ident_for_show::field_type() always
    returned MYSQL_TYPE_DOUBLE and ignored the actual data type of the
    referenced Field. As a result, the execution always used
    Item_ident_for_show::val_real() to send the default value of the field,
    so most default values for non-numeric types were displayed as '0'.
    
    This patch:
    1. (Cleanup) Removes Send_field::charsetnr, as it's been unused since
       introduction of Item::charset_for_protocol() in MySQL-5.5.
    
    2. Introduces a new virtual method Type_handler::charset_for_protocol(),
       returning item->collation.collation for string data types, or
       &my_charset_bin for non-string data types.
    
    3. Changes Item::charset_for_protocol() from virtual to non-virtual.
       It now calls type_handler()->charset_for_protocol().
       As a good side effect, duplicate code in Item::charset_for_protocol() and
       Item_temporal_hybrid_func() is now gone.
    
    4. Fixes Item_ident_for_show::field_type() to correctly return
       its data type according to the data type of the referenced field.
       This actually fixes the problem reported in MDEV-11672.
       Now the default value is sent using a correct method, e.g.
       val_str() for VARCHAR/TEXT.
       This required additional changes:
       - in DBUG_ASSERT in Protocol::store(const char *,size_t,CHARSET_INFO),
         This method is now used by mysql_list_fields(), which
         (unlike normal SELECT queries) does not set
         field_types/field_pos/field_count.
       - Item_ident_for_show::Item_ident_for_show() now sets the collation
         according to the referenced field, to make charset_for_protocol()
         return the correct value.

diff --git a/sql/field.cc b/sql/field.cc
index 480fcfb..592445d 100644
--- a/sql/field.cc
+++ b/sql/field.cc
@@ -1944,7 +1944,6 @@ void Field::make_field(Send_field *field)
     field->org_col_name= "";
   }
   field->col_name= field_name;
-  field->charsetnr= charset()->number;
   field->length=field_length;
   field->type=type();
   field->flags=table->maybe_null ? (flags & ~NOT_NULL_FLAG) : flags;
diff --git a/sql/field.h b/sql/field.h
index 7410288..3d7cd77 100644
--- a/sql/field.h
+++ b/sql/field.h
@@ -3939,7 +3939,7 @@ class Send_field :public Sql_alloc {
   const char *table_name,*org_table_name;
   const char *col_name,*org_col_name;
   ulong length;
-  uint charsetnr, flags, decimals;
+  uint flags, decimals;
   enum_field_types type;
   Send_field() {}
 };
diff --git a/sql/item.cc b/sql/item.cc
index 2a10587..3cb0276 100644
--- a/sql/item.cc
+++ b/sql/item.cc
@@ -2397,7 +2397,6 @@ void Item_ident_for_show::make_field(THD *thd, Send_field *tmp_field)
   tmp_field->table_name= tmp_field->org_table_name= table_name;
   tmp_field->db_name= db_name;
   tmp_field->col_name= tmp_field->org_col_name= field->field_name;
-  tmp_field->charsetnr= field->charset()->number;
   tmp_field->length=field->field_length;
   tmp_field->type=field->type();
   tmp_field->flags= field->table->maybe_null ? 
@@ -4259,7 +4258,6 @@ void Item_param::make_field(THD *thd, Send_field *field)
   field->org_col_name= m_out_param_info->org_col_name;
 
   field->length= m_out_param_info->length;
-  field->charsetnr= m_out_param_info->charsetnr;
   field->flags= m_out_param_info->flags;
   field->decimals= m_out_param_info->decimals;
   field->type= m_out_param_info->type;
@@ -5792,7 +5790,6 @@ void Item::init_make_field(Send_field *tmp_field,
   tmp_field->org_col_name=	empty_name;
   tmp_field->table_name=	empty_name;
   tmp_field->col_name=		name;
-  tmp_field->charsetnr=         collation.collation->number;
   tmp_field->flags=             (maybe_null ? 0 : NOT_NULL_FLAG) | 
                                 (my_binary_compare(charset_for_protocol()) ?
                                  BINARY_FLAG : 0);
diff --git a/sql/item.h b/sql/item.h
index ed7d07e..d62ffbc 100644
--- a/sql/item.h
+++ b/sql/item.h
@@ -1362,14 +1362,9 @@ class Item: public Value_source,
 
   static CHARSET_INFO *default_charset();
 
-  /*
-    For backward compatibility, to make numeric
-    data types return "binary" charset in client-side metadata.
-  */
-  virtual CHARSET_INFO *charset_for_protocol(void) const
+  CHARSET_INFO *charset_for_protocol(void) const
   {
-    return cmp_type() == STRING_RESULT ? collation.collation :
-                                         &my_charset_bin;
+    return type_handler()->charset_for_protocol(this);
   };
 
   virtual bool walk(Item_processor processor, bool walk_subquery, void *arg)
@@ -2319,7 +2314,9 @@ class Item_ident_for_show :public Item
   Item_ident_for_show(THD *thd, Field *par_field, const char *db_arg,
                       const char *table_name_arg):
     Item(thd), field(par_field), db_name(db_arg), table_name(table_name_arg)
-  {}
+  {
+    collation.set(par_field->charset());
+  }
 
   enum Type type() const { return FIELD_ITEM; }
   double val_real() { return field->val_real(); }
@@ -2327,9 +2324,7 @@ class Item_ident_for_show :public Item
   String *val_str(String *str) { return field->val_str(str); }
   my_decimal *val_decimal(my_decimal *dec) { return field->val_decimal(dec); }
   void make_field(THD *thd, Send_field *tmp_field);
-  CHARSET_INFO *charset_for_protocol(void) const
-  { return field->charset_for_protocol(); }
-  enum_field_types field_type() const { return MYSQL_TYPE_DOUBLE; }
+  enum_field_types field_type() const { return field->type(); }
   Item* get_copy(THD *thd, MEM_ROOT *mem_root)
   { return get_item_copy<Item_ident_for_show>(thd, mem_root, this); }
 };
@@ -2507,8 +2502,6 @@ class Item_field :public Item_ident
     DBUG_ASSERT(field_type() == MYSQL_TYPE_GEOMETRY);
     return field->get_geometry_type();
   }
-  CHARSET_INFO *charset_for_protocol(void) const
-  { return field->charset_for_protocol(); }
   friend class Item_default_value;
   friend class Item_insert_value;
   friend class st_select_lex_unit;
diff --git a/sql/item_timefunc.h b/sql/item_timefunc.h
index ad92f50..1014326 100644
--- a/sql/item_timefunc.h
+++ b/sql/item_timefunc.h
@@ -573,18 +573,6 @@ class Item_temporal_hybrid_func: public Item_temporal_func,
   { return Type_handler_hybrid_field_type::result_type(); }
   enum Item_result cmp_type () const
   { return Type_handler_hybrid_field_type::cmp_type(); }
-  CHARSET_INFO *charset_for_protocol() const
-  {
-    /*
-      Can return TIME, DATE, DATETIME or VARCHAR depending on arguments.
-      Send using "binary" when TIME, DATE or DATETIME,
-      or using collation.collation when VARCHAR
-      (which is fixed from @@collation_connection in fix_length_and_dec).
-    */
-    DBUG_ASSERT(fixed == 1);
-    return Item_temporal_hybrid_func::field_type() == MYSQL_TYPE_STRING ?
-           collation.collation : &my_charset_bin;
-  }
   /**
     Fix the returned timestamp to match field_type(),
     which is important for val_str().
diff --git a/sql/protocol.cc b/sql/protocol.cc
index f8b68c0..3cc7307 100644
--- a/sql/protocol.cc
+++ b/sql/protocol.cc
@@ -1116,7 +1116,7 @@ bool Protocol_text::store(const char *from, size_t length,
 #ifndef DBUG_OFF
   DBUG_PRINT("info", ("Protocol_text::store field %u (%u): %.*s", field_pos,
                       field_count, (int) length, (length == 0 ? "" : from)));
-  DBUG_ASSERT(field_pos < field_count);
+  DBUG_ASSERT(field_types == 0 || field_pos < field_count);
   DBUG_ASSERT(field_types == 0 ||
 	      field_types[field_pos] == MYSQL_TYPE_DECIMAL ||
               field_types[field_pos] == MYSQL_TYPE_BIT ||
diff --git a/sql/sql_type.cc b/sql/sql_type.cc
index 0f6424e..6ff72c0 100644
--- a/sql/sql_type.cc
+++ b/sql/sql_type.cc
@@ -101,6 +101,23 @@ Type_handler_string_result::type_handler_adjusted_to_max_octet_length(
 }
 
 
+CHARSET_INFO *Type_handler::charset_for_protocol(const Item *item) const
+{
+  /*
+    For backward compatibility, to make numeric
+    data types return "binary" charset in client-side metadata.
+  */
+  return &my_charset_bin;
+}
+
+
+CHARSET_INFO *
+Type_handler_string_result::charset_for_protocol(const Item *item) const
+{
+  return item->collation.collation;
+}
+
+
 const Type_handler *
 Type_handler::get_handler_by_cmp_type(Item_result type)
 {
diff --git a/sql/sql_type.h b/sql/sql_type.h
index e1a5883..b57c070 100644
--- a/sql/sql_type.h
+++ b/sql/sql_type.h
@@ -246,6 +246,7 @@ class Type_handler
   virtual Item_result result_type() const= 0;
   virtual Item_result cmp_type() const= 0;
   virtual const Type_handler *type_handler_for_comparison() const= 0;
+  virtual CHARSET_INFO *charset_for_protocol(const Item *item) const;
   virtual const Type_handler*
   type_handler_adjusted_to_max_octet_length(uint max_octet_length,
                                             CHARSET_INFO *cs) const
@@ -628,6 +629,7 @@ class Type_handler_string_result: public Type_handler
 public:
   Item_result result_type() const { return STRING_RESULT; }
   Item_result cmp_type() const { return STRING_RESULT; }
+  CHARSET_INFO *charset_for_protocol(const Item *item) const;
   virtual ~Type_handler_string_result() {}
   const Type_handler *type_handler_for_comparison() const;
   const Type_handler *
diff --git a/tests/mysql_client_test.c b/tests/mysql_client_test.c
index 58e3dda..9e997c2 100644
--- a/tests/mysql_client_test.c
+++ b/tests/mysql_client_test.c
@@ -8398,6 +8398,53 @@ static void test_list_fields()
 }
 
 
+static void test_list_fields_default()
+{
+  int rc, i;
+  myheader("test_list_fields_default");
+
+  rc= mysql_query(mysql, "DROP TABLE IF EXISTS t1");
+  myquery(rc);
+
+  rc= mysql_query(mysql,
+                  "CREATE TABLE t1 ("
+                  " i1 INT NOT NULL DEFAULT 0,"
+                  " s1 VARCHAR(10) CHARACTER SET latin1 NOT NULL DEFAULT 's1def')");
+  myquery(rc);
+
+  rc= mysql_query(mysql, "DROP VIEW IF EXISTS v1");
+  myquery(rc);
+
+  rc= mysql_query(mysql, "CREATE VIEW v1 AS SELECT * FROM t1");
+  myquery(rc);
+
+  /*
+    Checking that mysql_list_fields() returns the same result
+    for a TABLE and a VIEW on the same table.
+  */
+  for (i= 0; i < 2; i++)
+  {
+    const char *table_name= i == 0 ? "t1" : "v1";
+    MYSQL_RES *result= mysql_list_fields(mysql, table_name, NULL);
+    mytest(result);
+
+    rc= my_process_result_set(result);
+    DIE_UNLESS(rc == 0);
+
+    verify_prepare_field(result, 0, "i1", "i1", MYSQL_TYPE_LONG,
+                         table_name, table_name, current_db, 11, "0");
+
+    verify_prepare_field(result, 1, "s1", "s1", MYSQL_TYPE_VAR_STRING,
+                         table_name, table_name, current_db, 10, "s1def");
+
+    mysql_free_result(result);
+  }
+
+  myquery(mysql_query(mysql, "DROP VIEW v1"));
+  myquery(mysql_query(mysql, "DROP TABLE t1"));
+}
+
+
 static void test_bug19671()
 {
   MYSQL_RES *result;
@@ -19558,6 +19605,7 @@ static struct my_tests_st my_tests[]= {
   { "test_fetch_column", test_fetch_column },
   { "test_mem_overun", test_mem_overun },
   { "test_list_fields", test_list_fields },
+  { "test_list_fields_default", test_list_fields_default },
   { "test_free_result", test_free_result },
   { "test_free_store_result", test_free_store_result },
   { "test_sqlmode", test_sqlmode },