diff --git a/mysql-test/r/func_hybrid_type.result b/mysql-test/r/func_hybrid_type.result index 5eefcbb36ea..72e26c50274 100644 --- a/mysql-test/r/func_hybrid_type.result +++ b/mysql-test/r/func_hybrid_type.result @@ -3059,5 +3059,177 @@ DROP TABLE t2; DROP TABLE t1; SET timestamp=DEFAULT; # +# MDEV-8910 Wrong metadata or field type for MAX(COALESCE(string_field)) +# +CREATE TABLE t1 (c1 TINYBLOB, c2 MEDIUMBLOB, c3 BLOB, c4 LONGBLOB); +CREATE TABLE t2 AS +SELECT +MAX(COALESCE(c1)) AS c1, +MAX(COALESCE(c2)) AS c2, +MAX(COALESCE(c3)) AS c3, +MAX(COALESCE(c4)) AS c4 +FROM t1; +SHOW CREATE TABLE t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `c1` varbinary(255) DEFAULT NULL, + `c2` mediumblob, + `c3` blob, + `c4` longblob +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +SELECT +MAX(COALESCE(c1)) AS c1, +MAX(COALESCE(c2)) AS c2, +MAX(COALESCE(c3)) AS c3, +MAX(COALESCE(c4)) AS c4 +FROM t1; +Catalog Database Table Table_alias Column Column_alias Type Length Max length Is_null Flags Decimals Charsetnr +def c1 253 255 0 Y 128 31 63 +def c2 250 16777215 0 Y 128 31 63 +def c3 252 65535 0 Y 128 31 63 +def c4 251 4294967295 0 Y 128 31 63 +c1 c2 c3 c4 +NULL NULL NULL NULL +DROP TABLE t2; +DROP TABLE t1; +CREATE TABLE t1 (c1 CHAR(1), c2 CHAR(255)) CHARACTER SET latin1; +CREATE TABLE t2 AS +SELECT +MAX(COALESCE(c1)) AS c1, +MAX(COALESCE(c2)) AS c2 +FROM t1; +SHOW CREATE TABLE t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `c1` varchar(1) DEFAULT NULL, + `c2` varchar(255) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +SELECT +MAX(COALESCE(c1)) AS c1, +MAX(COALESCE(c2)) AS c2 +FROM t1; +Catalog Database Table Table_alias Column Column_alias Type Length Max length Is_null Flags Decimals Charsetnr +def c1 253 1 0 Y 0 31 8 +def c2 253 255 0 Y 0 31 8 +c1 c2 +NULL NULL +DROP TABLE t2; +DROP TABLE t1; +CREATE TABLE t1 (c1 CHAR(1), c2 CHAR(255)) CHARACTER SET utf8; +CREATE TABLE t2 AS +SELECT +MAX(COALESCE(c1)) AS c1, +MAX(COALESCE(c2)) AS c2 +FROM t1; +SHOW CREATE TABLE t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `c1` varchar(1) CHARACTER SET utf8 DEFAULT NULL, + `c2` varchar(255) CHARACTER SET utf8 DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +SELECT +MAX(COALESCE(c1)) AS c1, +MAX(COALESCE(c2)) AS c2 +FROM t1; +Catalog Database Table Table_alias Column Column_alias Type Length Max length Is_null Flags Decimals Charsetnr +def c1 253 1 0 Y 0 31 8 +def c2 253 255 0 Y 0 31 8 +c1 c2 +NULL NULL +DROP TABLE t2; +DROP TABLE t1; +CREATE TABLE t1 (c1 VARCHAR(1), c2 VARCHAR(255), c3 VARCHAR(20000)) CHARACTER SET latin1; +CREATE TABLE t2 AS +SELECT +MAX(COALESCE(c1)) AS c1, +MAX(COALESCE(c2)) AS c2, +MAX(COALESCE(c3)) AS c3 +FROM t1; +SHOW CREATE TABLE t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `c1` varchar(1) DEFAULT NULL, + `c2` varchar(255) DEFAULT NULL, + `c3` text +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +SELECT +MAX(COALESCE(c1)) AS c1, +MAX(COALESCE(c2)) AS c2, +MAX(COALESCE(c3)) AS c3 +FROM t1; +Catalog Database Table Table_alias Column Column_alias Type Length Max length Is_null Flags Decimals Charsetnr +def c1 253 1 0 Y 0 31 8 +def c2 253 255 0 Y 0 31 8 +def c3 252 20000 0 Y 0 31 8 +c1 c2 c3 +NULL NULL NULL +DROP TABLE t2; +DROP TABLE t1; +CREATE TABLE t1 (c1 VARCHAR(1), c2 VARCHAR(255), c3 VARCHAR(20000)) CHARACTER SET utf8; +CREATE TABLE t2 AS +SELECT +MAX(COALESCE(c1)) AS c1, +MAX(COALESCE(c2)) AS c2, +MAX(COALESCE(c3)) AS c3 +FROM t1; +SHOW CREATE TABLE t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `c1` varchar(1) CHARACTER SET utf8 DEFAULT NULL, + `c2` varchar(255) CHARACTER SET utf8 DEFAULT NULL, + `c3` text CHARACTER SET utf8 +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +SELECT +MAX(COALESCE(c1)) AS c1, +MAX(COALESCE(c2)) AS c2, +MAX(COALESCE(c3)) AS c3 +FROM t1; +Catalog Database Table Table_alias Column Column_alias Type Length Max length Is_null Flags Decimals Charsetnr +def c1 253 1 0 Y 0 31 8 +def c2 253 255 0 Y 0 31 8 +def c3 252 60000 0 Y 0 31 8 +c1 c2 c3 +NULL NULL NULL +DROP TABLE t2; +DROP TABLE t1; +CREATE TABLE t1 (c1 ENUM('a')) CHARACTER SET latin1; +CREATE TABLE t2 AS +SELECT +MAX(COALESCE(c1)) AS c1 +FROM t1; +SHOW CREATE TABLE t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `c1` varchar(1) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +SELECT +MAX(COALESCE(c1)) AS c1 +FROM t1; +Catalog Database Table Table_alias Column Column_alias Type Length Max length Is_null Flags Decimals Charsetnr +def c1 253 1 0 Y 0 31 8 +c1 +NULL +DROP TABLE t2; +DROP TABLE t1; +CREATE TABLE t1 (c1 ENUM('a')) CHARACTER SET utf8; +CREATE TABLE t2 AS +SELECT +MAX(COALESCE(c1)) AS c1 +FROM t1; +SHOW CREATE TABLE t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `c1` varchar(1) CHARACTER SET utf8 DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +SELECT +MAX(COALESCE(c1)) AS c1 +FROM t1; +Catalog Database Table Table_alias Column Column_alias Type Length Max length Is_null Flags Decimals Charsetnr +def c1 253 1 0 Y 0 31 8 +c1 +NULL +DROP TABLE t2; +DROP TABLE t1; +# # End of 10.1 tests # diff --git a/mysql-test/t/func_hybrid_type.test b/mysql-test/t/func_hybrid_type.test index 911ca7591a1..b4f995d7f76 100644 --- a/mysql-test/t/func_hybrid_type.test +++ b/mysql-test/t/func_hybrid_type.test @@ -165,6 +165,140 @@ DROP TABLE t2; DROP TABLE t1; SET timestamp=DEFAULT; +--echo # +--echo # MDEV-8910 Wrong metadata or field type for MAX(COALESCE(string_field)) +--echo # +CREATE TABLE t1 (c1 TINYBLOB, c2 MEDIUMBLOB, c3 BLOB, c4 LONGBLOB); +CREATE TABLE t2 AS +SELECT + MAX(COALESCE(c1)) AS c1, + MAX(COALESCE(c2)) AS c2, + MAX(COALESCE(c3)) AS c3, + MAX(COALESCE(c4)) AS c4 +FROM t1; +SHOW CREATE TABLE t2; +--disable_ps_protocol +--enable_metadata +SELECT + MAX(COALESCE(c1)) AS c1, + MAX(COALESCE(c2)) AS c2, + MAX(COALESCE(c3)) AS c3, + MAX(COALESCE(c4)) AS c4 +FROM t1; +--disable_metadata +--enable_ps_protocol +DROP TABLE t2; +DROP TABLE t1; + +CREATE TABLE t1 (c1 CHAR(1), c2 CHAR(255)) CHARACTER SET latin1; +CREATE TABLE t2 AS +SELECT + MAX(COALESCE(c1)) AS c1, + MAX(COALESCE(c2)) AS c2 +FROM t1; +SHOW CREATE TABLE t2; +--disable_ps_protocol +--enable_metadata +SELECT + MAX(COALESCE(c1)) AS c1, + MAX(COALESCE(c2)) AS c2 +FROM t1; +--disable_metadata +--enable_ps_protocol +DROP TABLE t2; +DROP TABLE t1; + +CREATE TABLE t1 (c1 CHAR(1), c2 CHAR(255)) CHARACTER SET utf8; +CREATE TABLE t2 AS +SELECT + MAX(COALESCE(c1)) AS c1, + MAX(COALESCE(c2)) AS c2 +FROM t1; +SHOW CREATE TABLE t2; +--disable_ps_protocol +--enable_metadata +SELECT + MAX(COALESCE(c1)) AS c1, + MAX(COALESCE(c2)) AS c2 +FROM t1; +--disable_metadata +--enable_ps_protocol +DROP TABLE t2; +DROP TABLE t1; + +CREATE TABLE t1 (c1 VARCHAR(1), c2 VARCHAR(255), c3 VARCHAR(20000)) CHARACTER SET latin1; +CREATE TABLE t2 AS +SELECT + MAX(COALESCE(c1)) AS c1, + MAX(COALESCE(c2)) AS c2, + MAX(COALESCE(c3)) AS c3 +FROM t1; +SHOW CREATE TABLE t2; +--disable_ps_protocol +--enable_metadata +SELECT + MAX(COALESCE(c1)) AS c1, + MAX(COALESCE(c2)) AS c2, + MAX(COALESCE(c3)) AS c3 +FROM t1; +--disable_metadata +--enable_ps_protocol +DROP TABLE t2; +DROP TABLE t1; + +CREATE TABLE t1 (c1 VARCHAR(1), c2 VARCHAR(255), c3 VARCHAR(20000)) CHARACTER SET utf8; +CREATE TABLE t2 AS +SELECT + MAX(COALESCE(c1)) AS c1, + MAX(COALESCE(c2)) AS c2, + MAX(COALESCE(c3)) AS c3 +FROM t1; +SHOW CREATE TABLE t2; +--disable_ps_protocol +--enable_metadata +SELECT + MAX(COALESCE(c1)) AS c1, + MAX(COALESCE(c2)) AS c2, + MAX(COALESCE(c3)) AS c3 +FROM t1; +--disable_metadata +--enable_ps_protocol +DROP TABLE t2; +DROP TABLE t1; + +CREATE TABLE t1 (c1 ENUM('a')) CHARACTER SET latin1; +CREATE TABLE t2 AS +SELECT + MAX(COALESCE(c1)) AS c1 +FROM t1; +SHOW CREATE TABLE t2; +--disable_ps_protocol +--enable_metadata +SELECT + MAX(COALESCE(c1)) AS c1 +FROM t1; +--disable_metadata +--enable_ps_protocol +DROP TABLE t2; +DROP TABLE t1; + +CREATE TABLE t1 (c1 ENUM('a')) CHARACTER SET utf8; +CREATE TABLE t2 AS +SELECT + MAX(COALESCE(c1)) AS c1 +FROM t1; +SHOW CREATE TABLE t2; +--disable_ps_protocol +--enable_metadata +SELECT + MAX(COALESCE(c1)) AS c1 +FROM t1; +--disable_metadata +--enable_ps_protocol +DROP TABLE t2; +DROP TABLE t1; + + --echo # --echo # End of 10.1 tests --echo # diff --git a/sql/item.cc b/sql/item.cc index 80ed2bf8109..1e809d56340 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -5362,17 +5362,6 @@ void Item::make_field(Send_field *tmp_field) } -enum_field_types Item::string_field_type() const -{ - enum_field_types f_type= MYSQL_TYPE_VAR_STRING; - if (max_length >= 16777216) - f_type= MYSQL_TYPE_LONG_BLOB; - else if (max_length >= 65536) - f_type= MYSQL_TYPE_MEDIUM_BLOB; - return f_type; -} - - void Item_empty_string::make_field(Send_field *tmp_field) { init_make_field(tmp_field, string_field_type()); diff --git a/sql/item.h b/sql/item.h index 5b07885eb14..b8cb0ce5fd6 100644 --- a/sql/item.h +++ b/sql/item.h @@ -761,7 +761,10 @@ public: /* ... while cmp_type() specifies how it should be compared */ Item_result cmp_type() const; virtual Item_result cast_to_int_type() const { return cmp_type(); } - virtual enum_field_types string_field_type() const; + enum_field_types string_field_type() const + { + return Type_handler::string_type_handler(max_length)->field_type(); + } enum_field_types field_type() const; virtual enum Type type() const =0; /* diff --git a/sql/item_sum.cc b/sql/item_sum.cc index 58be20e0322..0ffe5dcbbba 100644 --- a/sql/item_sum.cc +++ b/sql/item_sum.cc @@ -1187,7 +1187,7 @@ Item_sum_hybrid::fix_fields(THD *thd, Item **ref) if ((!item->fixed && item->fix_fields(thd, args)) || (item= args[0])->check_cols(1)) return TRUE; - decimals=item->decimals; + Type_std_attributes::set(args[0]); with_subselect= args[0]->with_subselect; Item *item2= item->real_item(); @@ -1196,13 +1196,13 @@ Item_sum_hybrid::fix_fields(THD *thd, Item **ref) else if (item->cmp_type() == TIME_RESULT) set_handler_by_field_type(item2->field_type()); else - set_handler_by_result_type(item2->result_type()); + set_handler_by_result_type(item2->result_type(), + max_length, collation.collation); switch (Item_sum_hybrid::result_type()) { case INT_RESULT: case DECIMAL_RESULT: case STRING_RESULT: - max_length= item->max_length; break; case REAL_RESULT: max_length= float_length(decimals); @@ -1214,7 +1214,6 @@ Item_sum_hybrid::fix_fields(THD *thd, Item **ref) setup_hybrid(thd, args[0], NULL); /* MIN/MAX can return NULL for empty set indepedent of the used column */ maybe_null= 1; - unsigned_flag=item->unsigned_flag; result_field=0; null_value=1; fix_length_and_dec(); diff --git a/sql/sql_type.cc b/sql/sql_type.cc index ce0d9852119..7eea689567c 100644 --- a/sql/sql_type.cc +++ b/sql/sql_type.cc @@ -15,6 +15,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "sql_type.h" +#include "sql_const.h" static Type_handler_tiny type_handler_tiny; static Type_handler_short type_handler_short; @@ -41,9 +42,47 @@ static Type_handler_blob type_handler_blob; static Type_handler_geometry type_handler_geometry; -Type_handler_hybrid_field_type::Type_handler_hybrid_field_type() - :m_type_handler(&type_handler_double) +/** + This method is used by: + - Item_func_set_user_var + - Item_func_get_user_var + - Item_user_var_as_out_param + - Item_func_udf_str + + TODO: type_handler_adjusted_to_max_octet_length() and string_type_handler() + provide very similar functionality, to properly choose between + VARCHAR/VARBINARY vs TEXT/BLOB variations taking into accoung maximum + possible octet length. + + We should probably get rid of either of them and use the same method + all around the code. +*/ +const Type_handler * +Type_handler::string_type_handler(uint max_octet_length) const { + if (max_octet_length >= 16777216) + return &type_handler_long_blob; + else if (max_octet_length >= 65536) + return &type_handler_medium_blob; + return &type_handler_varchar; +} + + +/** + This method is used by Item_sum_hybrid, e.g. MAX(item), MIN(item). +*/ +const Type_handler * +Type_handler_string_result::type_handler_adjusted_to_max_octet_length( + uint max_octet_length, + CHARSET_INFO *cs) const +{ + if (max_octet_length / cs->mbmaxlen <= CONVERT_IF_BIGGER_TO_BLOB) + return &type_handler_varchar; // See also Item::too_big_for_varchar() + if (max_octet_length >= 16777216) + return &type_handler_long_blob; + else if (max_octet_length >= 65536) + return &type_handler_medium_blob; + return &type_handler_blob; } @@ -64,6 +103,12 @@ Type_handler_hybrid_field_type::get_handler_by_result_type(Item_result type) } +Type_handler_hybrid_field_type::Type_handler_hybrid_field_type() + :m_type_handler(&type_handler_double) +{ +} + + const Type_handler * Type_handler_hybrid_field_type::get_handler_by_field_type(enum_field_types type) const diff --git a/sql/sql_type.h b/sql/sql_type.h index 5ad259db88b..0953804770f 100644 --- a/sql/sql_type.h +++ b/sql/sql_type.h @@ -25,12 +25,19 @@ class Type_handler { +protected: + const Type_handler *string_type_handler(uint max_octet_length) const; public: virtual enum_field_types field_type() const= 0; virtual Item_result result_type() const= 0; virtual Item_result cmp_type() const= 0; + virtual const Type_handler* + type_handler_adjusted_to_max_octet_length(uint max_octet_length, + CHARSET_INFO *cs) const + { return this; } }; + /*** Abstract classes for every XXX_RESULT */ class Type_handler_real_result: public Type_handler @@ -70,6 +77,9 @@ 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; } + const Type_handler * + type_handler_adjusted_to_max_octet_length(uint max_octet_length, + CHARSET_INFO *cs) const; }; @@ -283,10 +293,27 @@ public: { return (m_type_handler= get_handler_by_result_type(type)); } + const Type_handler *set_handler_by_result_type(Item_result type, + uint max_octet_length, + CHARSET_INFO *cs) + { + m_type_handler= get_handler_by_result_type(type); + return m_type_handler= + m_type_handler->type_handler_adjusted_to_max_octet_length(max_octet_length, + cs); + } const Type_handler *set_handler_by_field_type(enum_field_types type) { return (m_type_handler= get_handler_by_field_type(type)); } + const Type_handler * + type_handler_adjusted_to_max_octet_length(uint max_octet_length, + CHARSET_INFO *cs) const + { + return + m_type_handler->type_handler_adjusted_to_max_octet_length(max_octet_length, + cs); + } }; #endif /* SQL_TYPE_H_INCLUDED */