diff --git a/mysql-test/r/type_enum.result b/mysql-test/r/type_enum.result index da85ffe6495..15f16e4d02d 100644 --- a/mysql-test/r/type_enum.result +++ b/mysql-test/r/type_enum.result @@ -1693,3 +1693,35 @@ oe ue ss DROP TABLE t1; +CREATE TABLE t1 ( +a ENUM('ä','ö','ü') character set utf8 default 'ü' +); +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` enum('ä','ö','ü') character set utf8 default 'ü' +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +insert into t1 values ('ä'), ('ö'), ('ü'); +select a from t1 order by a; +a +ä +ö +ü +drop table t1; +set names utf8; +CREATE TABLE t1 ( +a ENUM('ä','ö','ü') character set latin1 default 'ü' +); +insert into t1 values ('ä'),('ö'),('ü'); +set names latin1; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` enum('ä','ö','ü') default 'ü' +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +select a from t1 order by a; +a +ä +ö +ü +drop table t1; diff --git a/mysql-test/t/type_enum.test b/mysql-test/t/type_enum.test index dc2e4d0f469..2c4bbdf8355 100644 --- a/mysql-test/t/type_enum.test +++ b/mysql-test/t/type_enum.test @@ -72,3 +72,31 @@ CREATE TABLE t1 (c enum('ae','oe','ue','ss') collate latin1_german2_ci); INSERT INTO t1 VALUES ('ä'),('ö'),('ü'),('ß'); SELECT * FROM t1; DROP TABLE t1; + +# +# Bug #6379: ENUM values are incorrectly converted +# +# Check latin1 -> utf8 conversion +# +CREATE TABLE t1 ( + a ENUM('ä','ö','ü') character set utf8 default 'ü' +); +show create table t1; +insert into t1 values ('ä'), ('ö'), ('ü'); +select a from t1 order by a; +drop table t1; + +# +# Now check utf8 -> latin1 conversion +# This test emulates loading a script generated with mysqldump +# +set names utf8; +CREATE TABLE t1 ( + a ENUM('ä','ö','ü') character set latin1 default 'ü' +); +insert into t1 values ('ä'),('ö'),('ü'); +# Now check what has been loaded +set names latin1; +show create table t1; +select a from t1 order by a; +drop table t1; diff --git a/sql/field.h b/sql/field.h index 8887da1dc0f..bb999222381 100644 --- a/sql/field.h +++ b/sql/field.h @@ -1198,6 +1198,7 @@ public: uint decimals,flags,pack_length; Field::utype unireg_check; TYPELIB *interval; // Which interval to use + List interval_list; CHARSET_INFO *charset; Field::geometry_type geom_type; Field *field; // For alter table diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 3f55a88b262..3a19a903e00 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -688,7 +688,8 @@ bool add_field_to_list(THD *thd, char *field_name, enum enum_field_types type, uint type_modifier, Item *default_value, Item *on_update_value, LEX_STRING *comment, - char *change, TYPELIB *interval,CHARSET_INFO *cs, + char *change, List *interval_list, + CHARSET_INFO *cs, uint uint_geom_type); void store_position_for_column(const char *name); bool add_to_list(THD *thd, SQL_LIST &list,Item *group,bool asc=0); diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 90c020b3e93..68a5ac29254 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -592,7 +592,6 @@ typedef struct st_lex List var_list; List param_list; SQL_LIST proc_list, auxilliary_table_list, save_list; - TYPELIB *interval; create_field *last_field; char *savepoint_name; // Transaction savepoint id udf_func udf; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index e066e447345..990e52d05ce 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -4115,31 +4115,6 @@ bool mysql_test_parse_for_slave(THD *thd, char *inBuf, uint length) #endif -/* - Calculate interval lengths. - Strip trailing spaces from all strings. - After this function call: - - ENUM uses max_length - - SET uses tot_length. -*/ -void calculate_interval_lengths(THD *thd, TYPELIB *interval, - uint32 *max_length, uint32 *tot_length) -{ - const char **pos; - uint *len; - CHARSET_INFO *cs= thd->variables.character_set_client; - *max_length= *tot_length= 0; - for (pos= interval->type_names, len= interval->type_lengths; - *pos ; pos++, len++) - { - *len= (uint) strip_sp((char*) *pos); - uint length= cs->cset->numchars(cs, *pos, *pos + *len); - *tot_length+= length; - set_if_bigger(*max_length, (uint32)length); - } -} - - /***************************************************************************** ** Store field definition for create ** Return 0 if ok @@ -4150,7 +4125,8 @@ bool add_field_to_list(THD *thd, char *field_name, enum_field_types type, uint type_modifier, Item *default_value, Item *on_update_value, LEX_STRING *comment, - char *change, TYPELIB *interval, CHARSET_INFO *cs, + char *change, + List *interval_list, CHARSET_INFO *cs, uint uint_geom_type) { register create_field *new_field; @@ -4445,62 +4421,39 @@ bool add_field_to_list(THD *thd, char *field_name, enum_field_types type, break; case FIELD_TYPE_SET: { - if (interval->count > sizeof(longlong)*8) + if (interval_list->elements > sizeof(longlong)*8) { - net_printf(thd,ER_TOO_BIG_SET,field_name); /* purecov: inspected */ - DBUG_RETURN(1); /* purecov: inspected */ + net_printf(thd,ER_TOO_BIG_SET,field_name); /* purecov: inspected */ + DBUG_RETURN(1); /* purecov: inspected */ } - new_field->pack_length=(interval->count+7)/8; + new_field->pack_length= (interval_list->elements + 7) / 8; if (new_field->pack_length > 4) - new_field->pack_length=8; - new_field->interval=interval; - uint32 dummy_max_length; - calculate_interval_lengths(thd, interval, - &dummy_max_length, &new_field->length); - new_field->length+= (interval->count - 1); - set_if_smaller(new_field->length,MAX_FIELD_WIDTH-1); - if (default_value) - { - char *not_used; - uint not_used2; - bool not_used3; + new_field->pack_length=8; - thd->cuted_fields=0; - String str,*res; - res=default_value->val_str(&str); - (void) find_set(interval, res->ptr(), res->length(), - &my_charset_bin, - ¬_used, ¬_used2, ¬_used3); - if (thd->cuted_fields) - { - net_printf(thd,ER_INVALID_DEFAULT,field_name); - DBUG_RETURN(1); - } - } + List_iterator it(*interval_list); + String *tmp; + while ((tmp= it++)) + new_field->interval_list.push_back(tmp); + /* + Set fake length to 1 to pass the below conditions. + Real length will be set in mysql_prepare_table() + when we know the character set of the column + */ + new_field->length= 1; } break; case FIELD_TYPE_ENUM: { - new_field->interval=interval; - new_field->pack_length=interval->count < 256 ? 1 : 2; // Should be safe + // Should be safe + new_field->pack_length= interval_list->elements < 256 ? 1 : 2; - uint32 dummy_tot_length; - calculate_interval_lengths(thd, interval, - &new_field->length, &dummy_tot_length); - set_if_smaller(new_field->length,MAX_FIELD_WIDTH-1); - if (default_value) - { - String str,*res; - res=default_value->val_str(&str); - res->strip_sp(); - if (!find_type(interval, res->ptr(), res->length(), 0)) - { - net_printf(thd,ER_INVALID_DEFAULT,field_name); - DBUG_RETURN(1); - } - } - break; + List_iterator it(*interval_list); + String *tmp; + while ((tmp= it++)) + new_field->interval_list.push_back(tmp); + new_field->length= 1; // See comment for FIELD_TYPE_SET above. } + break; } if ((new_field->length > MAX_FIELD_CHARLENGTH && type != FIELD_TYPE_SET && diff --git a/sql/sql_table.cc b/sql/sql_table.cc index eedd9388877..a304e821ecd 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -392,6 +392,41 @@ void check_duplicates_in_interval(const char *set_or_name, } } + +/* + Check TYPELIB (set or enum) max and total lengths + + SYNOPSIS + calculate_interval_lengths() + cs charset+collation pair of the interval + typelib list of values for the column + max_length length of the longest item + tot_length sum of the item lengths + + DESCRIPTION + After this function call: + - ENUM uses max_length + - SET uses tot_length. + + RETURN VALUES + void +*/ +void calculate_interval_lengths(CHARSET_INFO *cs, TYPELIB *interval, + uint32 *max_length, uint32 *tot_length) +{ + const char **pos; + uint *len; + *max_length= *tot_length= 0; + for (pos= interval->type_names, len= interval->type_lengths; + *pos ; pos++, len++) + { + uint length= cs->cset->numchars(cs, *pos, *pos + *len); + *tot_length+= length; + set_if_bigger(*max_length, (uint32)length); + } +} + + /* Preparation for table creation @@ -455,6 +490,91 @@ int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, DBUG_RETURN(-1); } + if ((sql_field->sql_type == FIELD_TYPE_SET || + sql_field->sql_type == FIELD_TYPE_ENUM) && !sql_field->interval) + { + uint32 dummy; + CHARSET_INFO *cs= sql_field->charset; + TYPELIB *interval; + + /* + Create typelib from interval_list, and if necessary + convert strings from client character set to the + column character set. + */ + + interval= sql_field->interval= typelib(sql_field->interval_list); + List_iterator it(sql_field->interval_list); + String conv, *tmp; + for (uint i= 0; (tmp= it++); i++) + { + if (String::needs_conversion(tmp->length(), tmp->charset(), cs, &dummy)) + { + uint cnv_errs; + conv.copy(tmp->ptr(), tmp->length(), tmp->charset(), cs, &cnv_errs); + char *buf= (char*) sql_alloc(conv.length()+1); + memcpy(buf, conv.ptr(), conv.length()); + buf[conv.length()]= '\0'; + interval->type_names[i]= buf; + interval->type_lengths[i]= conv.length(); + } + + // Strip trailing spaces. + uint lengthsp= cs->cset->lengthsp(cs, interval->type_names[i], + interval->type_lengths[i]); + interval->type_lengths[i]= lengthsp; + ((uchar *)interval->type_names[i])[lengthsp]= '\0'; + } + sql_field->interval_list.empty(); // Don't need interval_list anymore + + + /* + Convert the default value from client character + set into the column character set if necessary. + */ + if (sql_field->def) + { + sql_field->def= + sql_field->def->safe_charset_converter(cs); + } + + if (sql_field->sql_type == FIELD_TYPE_SET) + { + if (sql_field->def) + { + char *not_used; + uint not_used2; + bool not_found= 0; + String str, *def= sql_field->def->val_str(&str); + def->length(cs->cset->lengthsp(cs, def->ptr(), def->length())); + (void) find_set(interval, def->ptr(), def->length(), + cs, ¬_used, ¬_used2, ¬_found); + if (not_found) + { + my_error(ER_INVALID_DEFAULT, MYF(0), sql_field->field_name); + DBUG_RETURN(-1); + } + } + calculate_interval_lengths(cs, interval, &dummy, &sql_field->length); + sql_field->length+= (interval->count - 1); + } + else /* FIELD_TYPE_ENUM */ + { + if (sql_field->def) + { + String str, *def= sql_field->def->val_str(&str); + def->length(cs->cset->lengthsp(cs, def->ptr(), def->length())); + if (!find_type2(interval, def->ptr(), def->length(), cs)) + { + my_error(ER_INVALID_DEFAULT, MYF(0), sql_field->field_name); + DBUG_RETURN(-1); + } + } + calculate_interval_lengths(cs, interval, &sql_field->length, &dummy); + } + set_if_smaller(sql_field->length, MAX_FIELD_WIDTH-1); + } + sql_field->create_length_to_internal_length(); /* Don't pack keys in old tables if the user has requested this */ diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 8cbfaf3f99b..49e567ab54b 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1361,7 +1361,7 @@ field_spec: field_ident { LEX *lex=Lex; - lex->length=lex->dec=0; lex->type=0; lex->interval=0; + lex->length=lex->dec=0; lex->type=0; lex->default_value= lex->on_update_value= 0; lex->comment=0; lex->charset=NULL; @@ -1374,7 +1374,7 @@ field_spec: lex->length,lex->dec,lex->type, lex->default_value, lex->on_update_value, lex->comment, - lex->change,lex->interval,lex->charset, + lex->change,&lex->interval_list,lex->charset, lex->uint_geom_type)) YYABORT; }; @@ -1471,17 +1471,9 @@ type: | FIXED_SYM float_options field_options { $$=FIELD_TYPE_DECIMAL;} | ENUM {Lex->interval_list.empty();} '(' string_list ')' opt_binary - { - LEX *lex=Lex; - lex->interval=typelib(lex->interval_list); - $$=FIELD_TYPE_ENUM; - } + { $$=FIELD_TYPE_ENUM; } | SET { Lex->interval_list.empty();} '(' string_list ')' opt_binary - { - LEX *lex=Lex; - lex->interval=typelib(lex->interval_list); - $$=FIELD_TYPE_SET; - } + { $$=FIELD_TYPE_SET; } | LONG_SYM opt_binary { $$=FIELD_TYPE_MEDIUM_BLOB; } | SERIAL_SYM { @@ -1925,7 +1917,7 @@ alter_list_item: | MODIFY_SYM opt_column field_ident { LEX *lex=Lex; - lex->length=lex->dec=0; lex->type=0; lex->interval=0; + lex->length=lex->dec=0; lex->type=0; lex->default_value= lex->on_update_value= 0; lex->comment=0; lex->charset= NULL; @@ -1940,7 +1932,7 @@ alter_list_item: lex->length,lex->dec,lex->type, lex->default_value, lex->on_update_value, lex->comment, - $3.str, lex->interval, lex->charset, + $3.str, &lex->interval_list, lex->charset, lex->uint_geom_type)) YYABORT; }