maria-developers team mailing list archive
-
maria-developers team
-
Mailing list archive
-
Message #10241
Re: Please review MDEV-11672 mysql_list_field() returns wrong default values for VIEW
-
To:
Oleksandr Byelkin <sanja@xxxxxxxxxxx>, maria-developers <maria-developers@xxxxxxxxxxxxxxxxxxx>
-
From:
Alexander Barkov <bar@xxxxxxxxxxx>
-
Date:
Wed, 28 Dec 2016 10:26:48 +0400
-
In-reply-to:
<CADYFmDQE9wD0Bn4MGhhF+_93K2dngGP49NdDjMnhQdGuiOyEcg@mail.gmail.com>
-
User-agent:
Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Thunderbird/45.4.0
Sanja,
On 12/27/2016 09:39 PM, Oleksandr Byelkin wrote:
> Hi, Alexander!
>
> It is OK to push after fixing in the comment of the patch
> mysql_list_fields with mysqld_list_fields (IMHO it is server function
> do not set type etc.)
Thanks for review!
I fixed the comment.
But then I realized that it's a good idea to add tests for other
field types (e.g. DECIMAL, DATETIME, ENUM), and found that my
fix was slightly incomplete:
Item_ident_for_show::Item_ident_for_show() should copy not
only collation from the field, but the entire Type_std_attributes
(i.e. unsigned_flag, decimals and max_length).
So I also fixed this, with some additional minor changes
to avoid code duplication.
This is an updated version. Please have a look.
Thanks!
>
> On Tue, Dec 27, 2016 at 4:46 PM, Alexander Barkov <bar@xxxxxxxxxxx
> <mailto:bar@xxxxxxxxxxx>> wrote:
>
> Hello Sanja,
>
> Can you please review a patch for MDEV-11672?
>
> Thanks!
>
>
commit 3331dc53361bbdc83562732e2eaf6215564296f4
Author: Alexander Barkov <bar@xxxxxxxxxxx>
Date: Wed Dec 28 10:21:19 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:
a. Removes Send_field::charsetnr, as it's been unused since
introduction of Item::charset_for_protocol() in MySQL-5.5.
b. Adds the "const" qualifier to Field::char_length().
This is needed for (5.a), see below.
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, or val_int() for INT/BIGINT.
This required additional changes:
a. in DBUG_ASSERT in Protocol::store(const char *,size_t,CHARSET_INFO),
This method is now used by mysqld_list_fields(), which
(unlike normal SELECT queries) does not set
field_types/field_pos/field_count.
b. Item_ident_for_show::Item_ident_for_show() now set standard attributes
(collation, decimals, max_length) according to the referenced field,
to make charset_for_protocol() return the correct value.
5. In order to share the code between Item_field::set_field() and
Item_ident_for_show::Item_ident_for_show():
a. Introduces a new method Type_std_attributes::set(const Field*)
b. To make (a) possible, moves Item::fix_char_length() from Item
to Type_std_attributes, also moves char_to_byte_length_safe()
from item.h to sql_type.h
c. Additionally, moves Item::fix_length_and_charset() and
Item::max_char_length() from Item to Type_std_attributes.
This is not directly needed for the fix and is done just for symmetry
with fix_char_length(), as these three methods are directly related
to each other.
diff --git a/sql/field.cc b/sql/field.cc
index 480fcfb..48ae682 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;
@@ -10747,7 +10746,7 @@ Column_definition::Column_definition(THD *thd, Field *old_field,
length
*/
-uint32 Field_blob::char_length()
+uint32 Field_blob::char_length() const
{
switch (packlength)
{
diff --git a/sql/field.h b/sql/field.h
index 7410288..c37c858 100644
--- a/sql/field.h
+++ b/sql/field.h
@@ -1353,7 +1353,7 @@ class Field: public Value_source
longlong convert_decimal2longlong(const my_decimal *val, bool unsigned_flag,
int *err);
/* The max. number of characters */
- virtual uint32 char_length()
+ virtual uint32 char_length() const
{
return field_length / charset()->mbmaxlen;
}
@@ -3344,7 +3344,7 @@ class Field_blob :public Field_longstr {
bool has_charset(void) const
{ return charset() == &my_charset_bin ? FALSE : TRUE; }
uint32 max_display_length();
- uint32 char_length();
+ uint32 char_length() const;
uint is_equal(Create_field *new_field);
private:
int do_save_field_metadata(uchar *first_byte);
@@ -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..c37613e 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 ?
@@ -2559,15 +2558,11 @@ void Item_field::set_field(Field *field_par)
{
field=result_field=field_par; // for easy coding with fields
maybe_null=field->maybe_null();
- decimals= field->decimals();
+ Type_std_attributes::set(field_par);
table_name= *field_par->table_name;
field_name= field_par->field_name;
db_name= field_par->table->s->db.str;
alias_name_used= field_par->table->alias_name_used;
- unsigned_flag= MY_TEST(field_par->flags & UNSIGNED_FLAG);
- collation.set(field_par->charset(), field_par->derivation(),
- field_par->repertoire());
- fix_char_length(field_par->char_length());
max_length= adjust_max_effective_column_length(field_par, max_length);
@@ -4259,7 +4254,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 +5786,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..8499bef 100644
--- a/sql/item.h
+++ b/sql/item.h
@@ -96,13 +96,6 @@ enum precedence {
typedef Bounds_checked_array<Item*> Ref_ptr_array;
-static inline uint32
-char_to_byte_length_safe(uint32 char_length_arg, uint32 mbmaxlen_arg)
-{
- ulonglong tmp= ((ulonglong) char_length_arg) * mbmaxlen_arg;
- return (tmp > UINT_MAX32) ? (uint32) UINT_MAX32 : (uint32) tmp;
-}
-
bool mark_unsupported_function(const char *where, void *store, uint result);
/* convenience helper for mark_unsupported_function() above */
@@ -1362,14 +1355,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)
@@ -1687,20 +1675,8 @@ class Item: public Value_source,
{ return Field::GEOM_GEOMETRY; };
String *check_well_formed_result(String *str, bool send_error= 0);
bool eq_by_collation(Item *item, bool binary_cmp, CHARSET_INFO *cs);
- uint32 max_char_length() const
- { return max_length / collation.collation->mbmaxlen; }
bool too_big_for_varchar() const
{ return max_char_length() > CONVERT_IF_BIGGER_TO_BLOB; }
- void fix_length_and_charset(uint32 max_char_length_arg, CHARSET_INFO *cs)
- {
- max_length= char_to_byte_length_safe(max_char_length_arg, cs->mbmaxlen);
- collation.collation= cs;
- }
- void fix_char_length(uint32 max_char_length_arg)
- {
- max_length= char_to_byte_length_safe(max_char_length_arg,
- collation.collation->mbmaxlen);
- }
/*
Return TRUE if the item points to a column of an outer-joined table.
*/
@@ -2319,7 +2295,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)
- {}
+ {
+ Type_std_attributes::set(par_field);
+ }
enum Type type() const { return FIELD_ITEM; }
double val_real() { return field->val_real(); }
@@ -2327,9 +2305,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 +2483,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..4dc8031 100644
--- a/sql/sql_type.cc
+++ b/sql/sql_type.cc
@@ -56,6 +56,15 @@ Type_handler_newdecimal type_handler_newdecimal;
Type_handler_datetime type_handler_datetime;
+void Type_std_attributes::set(const Field *field)
+{
+ decimals= field->decimals();
+ unsigned_flag= MY_TEST(field->flags & UNSIGNED_FLAG);
+ collation.set(field->charset(), field->derivation(), field->repertoire());
+ fix_char_length(field->char_length());
+}
+
+
/**
This method is used by:
- Item_user_var_as_out_param::field_type()
@@ -101,6 +110,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..12f846e 100644
--- a/sql/sql_type.h
+++ b/sql/sql_type.h
@@ -176,6 +176,13 @@ class DTCollation {
};
+static inline uint32
+char_to_byte_length_safe(uint32 char_length_arg, uint32 mbmaxlen_arg)
+{
+ ulonglong tmp= ((ulonglong) char_length_arg) * mbmaxlen_arg;
+ return (tmp > UINT_MAX32) ? (uint32) UINT_MAX32 : (uint32) tmp;
+}
+
/**
A class to store type attributes for the standard data types.
Does not include attributes for the extended data types
@@ -217,6 +224,19 @@ class Type_std_attributes
{
*this= other;
}
+ void set(const Field *field);
+ uint32 max_char_length() const
+ { return max_length / collation.collation->mbmaxlen; }
+ void fix_length_and_charset(uint32 max_char_length_arg, CHARSET_INFO *cs)
+ {
+ max_length= char_to_byte_length_safe(max_char_length_arg, cs->mbmaxlen);
+ collation.collation= cs;
+ }
+ void fix_char_length(uint32 max_char_length_arg)
+ {
+ max_length= char_to_byte_length_safe(max_char_length_arg,
+ collation.collation->mbmaxlen);
+ }
};
@@ -246,6 +266,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 +649,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..a1c28f0 100644
--- a/tests/mysql_client_test.c
+++ b/tests/mysql_client_test.c
@@ -8398,6 +8398,76 @@ 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,"
+ " i3 BIGINT UNSIGNED NOT NULL DEFAULT 0xFFFFFFFFFFFFFFFF,"
+ " s1 VARCHAR(10) CHARACTER SET latin1 NOT NULL DEFAULT 's1def',"
+ " d1 DECIMAL(31,1) NOT NULL DEFAULT 111111111122222222223333333333.9,"
+ " t1 DATETIME(6) NOT NULL DEFAULT '2001-01-01 10:20:30.123456',"
+ " e1 ENUM('a','b') NOT NULL DEFAULT 'a'"
+ ")");
+ 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, "i3", "i3", MYSQL_TYPE_LONGLONG,
+ table_name, table_name, current_db,
+ 20, "18446744073709551615");
+
+ verify_prepare_field(result, 2, "s1", "s1", MYSQL_TYPE_VAR_STRING,
+ table_name, table_name, current_db,
+ 10, "s1def");
+
+ verify_prepare_field(result, 3, "d1", "d1", MYSQL_TYPE_NEWDECIMAL,
+ table_name, table_name, current_db,
+ 33, "111111111122222222223333333333.9");
+
+ verify_prepare_field(result, 4, "t1", "t1", MYSQL_TYPE_DATETIME,
+ table_name, table_name, current_db,
+ 26, "2001-01-01 10:20:30.123456");
+
+ verify_prepare_field(result, 5, "e1", "e1", MYSQL_TYPE_STRING,
+ table_name, table_name, current_db,
+ 1, "a");
+
+ 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 +19628,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 },
References