Commit d301b4a0 authored by unknown's avatar unknown
Browse files

Bug#22646 LC_TIME_NAMES: Assignment to non-UTF8 target fails

Problem: After introducing of LC_TIME_NAMES variable,  the 
function date_format() can return international non-ascii
characters in month and weekday names. Thus, it cannot return
a binary string anymore, because inserting a result of date_format()
into a column with non-utf8 character set produces garbage.
Fix: date_format() now returns a character string, using
"collation_connection" to detect character set and collation
for the returned value. This allows to insert
results of date_format() properly into columns with
various character sets.


mysql-test/r/ctype_utf8.result:
  Adding test case.
  Fixing old result.
mysql-test/t/ctype_utf8.test:
  Adding test case.
sql/item_timefunc.cc:
  DATE_FORMAT() now returns a character string
  instead of binary string:
  - make_date_time() now converts localte data from UTF8 to 
  the character set of "str" argument, instead of copying as is.
  - fix_dec_and_length() now uses "collation_connection"
  instead of "binary" for the result, it also now
  multiplies to mbmaxlen when calculating max_length
parent b227a3d3
Loading
Loading
Loading
Loading
+23 −1
Original line number Diff line number Diff line
@@ -124,12 +124,34 @@ create table t1 select date_format("2004-01-19 10:10:10", "%Y-%m-%d");
show create table t1;
Table	Create Table
t1	CREATE TABLE `t1` (
  `date_format("2004-01-19 10:10:10", "%Y-%m-%d")` binary(10) default NULL
  `date_format("2004-01-19 10:10:10", "%Y-%m-%d")` char(10) character set utf8 default NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1
select * from t1;
date_format("2004-01-19 10:10:10", "%Y-%m-%d")
2004-01-19
drop table t1;
set names utf8;
set LC_TIME_NAMES='fr_FR';
create table t1 (s1 char(20) character set latin1);
insert into t1 values (date_format('2004-02-02','%M'));
select hex(s1) from t1;
hex(s1)
66E97672696572
drop table t1;
create table t1 (s1 char(20) character set koi8r);
set LC_TIME_NAMES='ru_RU';
insert into t1 values (date_format('2004-02-02','%M'));
insert into t1 values (date_format('2004-02-02','%b'));
insert into t1 values (date_format('2004-02-02','%W'));
insert into t1 values (date_format('2004-02-02','%a'));
select hex(s1), s1 from t1;
hex(s1)	s1
E6C5D7D2C1CCD1	Февраля
E6C5D7	Фев
F0CFCEC5C4C5CCD8CEC9CB	Понедельник
F0CEC4	Пнд
drop table t1;
set LC_TIME_NAMES='en_US';
set names koi8r;
create table t1 (s1 char(1) character set utf8);
insert into t1 values (_koi8r'');
+20 −0
Original line number Diff line number Diff line
@@ -93,6 +93,26 @@ show create table t1;
select * from t1;
drop table t1;

#
# Bug#22646 LC_TIME_NAMES: Assignment to non-UTF8 target fails
#
set names utf8;
set LC_TIME_NAMES='fr_FR';
create table t1 (s1 char(20) character set latin1);
insert into t1 values (date_format('2004-02-02','%M'));
select hex(s1) from t1;
drop table t1;
create table t1 (s1 char(20) character set koi8r);
set LC_TIME_NAMES='ru_RU';
insert into t1 values (date_format('2004-02-02','%M'));
insert into t1 values (date_format('2004-02-02','%b'));
insert into t1 values (date_format('2004-02-02','%W'));
insert into t1 values (date_format('2004-02-02','%a'));
select hex(s1), s1 from t1;
drop table t1;
set LC_TIME_NAMES='en_US';


#
# Bug #2366  	Wrong utf8 behaviour when data is truncated
#
+36 −47
Original line number Diff line number Diff line
@@ -595,16 +595,10 @@ bool make_date_time(DATE_TIME_FORMAT *format, TIME *l_time,
  uint weekday;
  ulong length;
  const char *ptr, *end;
  MY_LOCALE *locale;
  THD *thd= current_thd;
  char buf[128];
  String tmp(buf, sizeof(buf), thd->variables.character_set_results);
  uint errors= 0;
  MY_LOCALE *locale= thd->variables.lc_time_names;

  tmp.length(0);
  str->length(0);
  str->set_charset(&my_charset_bin);
  locale = thd->variables.lc_time_names;

  if (l_time->neg)
    str->append("-", 1);
@@ -620,38 +614,34 @@ bool make_date_time(DATE_TIME_FORMAT *format, TIME *l_time,
      case 'M':
        if (!l_time->month)
          return 1;
	tmp.copy(locale->month_names->type_names[l_time->month-1],
        str->append(locale->month_names->type_names[l_time->month-1],
                    strlen(locale->month_names->type_names[l_time->month-1]),
		   system_charset_info, tmp.charset(), &errors);
	str->append(tmp.ptr(), tmp.length());
                    system_charset_info);
        break;
      case 'b':
        if (!l_time->month)
          return 1;
	tmp.copy(locale->ab_month_names->type_names[l_time->month-1],
        str->append(locale->ab_month_names->type_names[l_time->month-1],
                    strlen(locale->ab_month_names->type_names[l_time->month-1]),
		 system_charset_info, tmp.charset(), &errors);
	str->append(tmp.ptr(), tmp.length());
                    system_charset_info);
        break;
      case 'W':
        if (type == MYSQL_TIMESTAMP_TIME)
          return 1;
        weekday= calc_weekday(calc_daynr(l_time->year,l_time->month,
                              l_time->day),0);
	tmp.copy(locale->day_names->type_names[weekday],
        str->append(locale->day_names->type_names[weekday],
                    strlen(locale->day_names->type_names[weekday]),
		 system_charset_info, tmp.charset(), &errors);
	str->append(tmp.ptr(), tmp.length());
                    system_charset_info);
        break;
      case 'a':
        if (type == MYSQL_TIMESTAMP_TIME)
          return 1;
        weekday=calc_weekday(calc_daynr(l_time->year,l_time->month,
                             l_time->day),0);
	tmp.copy(locale->ab_day_names->type_names[weekday],
        str->append(locale->ab_day_names->type_names[weekday],
                    strlen(locale->ab_day_names->type_names[weekday]),
		 system_charset_info, tmp.charset(), &errors);
	str->append(tmp.ptr(), tmp.length());
                    system_charset_info);
        break;
      case 'D':
	if (type == MYSQL_TIMESTAMP_TIME)
@@ -1638,8 +1628,9 @@ longlong Item_func_sec_to_time::val_int()

void Item_func_date_format::fix_length_and_dec()
{
  THD* thd= current_thd;
  decimals=0;
  collation.set(&my_charset_bin);
  collation.set(thd->variables.collation_connection);
  if (args[1]->type() == STRING_ITEM)
  {						// Optimize the normal case
    fixed_length=1;
@@ -1653,17 +1644,14 @@ void Item_func_date_format::fix_length_and_dec()
    args[1]->collation.set(
        get_charset_by_csname(args[1]->collation.collation->csname,
                              MY_CS_BINSORT,MYF(0)), DERIVATION_COERCIBLE);
    /*
      The result is a binary string (no reason to use collation->mbmaxlen
      This is becasue make_date_time() only returns binary strings
    */
    max_length= format_length(((Item_string*) args[1])->const_string());
    max_length= format_length(((Item_string*) args[1])->const_string()) *
                collation.collation->mbmaxlen;
  }
  else
  {
    fixed_length=0;
    /* The result is a binary string (no reason to use collation->mbmaxlen */
    max_length=min(args[1]->max_length,MAX_BLOB_WIDTH) * 10;
    max_length= min(args[1]->max_length,MAX_BLOB_WIDTH) * 10 * 
                collation.collation->mbmaxlen;
    set_if_smaller(max_length,MAX_BLOB_WIDTH);
  }
  maybe_null=1;					// If wrong date
@@ -1783,6 +1771,7 @@ String *Item_func_date_format::val_str(String *str)
  date_time_format.format.length= format->length(); 

  /* Create the result string */
  str->set_charset(collation.collation);
  if (!make_date_time(&date_time_format, &l_time,
                      is_time_format ? MYSQL_TIMESTAMP_TIME :
                                       MYSQL_TIMESTAMP_DATE,