Loading mysql-test/r/type_date.result +17 −0 Original line number Diff line number Diff line Loading @@ -79,3 +79,20 @@ SELECT DATE_FORMAT(f1, "%l.%i %p") , DATE_FORMAT(f2, "%l.%i %p") FROM t1; DATE_FORMAT(f1, "%l.%i %p") DATE_FORMAT(f2, "%l.%i %p") 9.00 AM 12.00 PM DROP TABLE t1; CREATE TABLE t1 (f1 DATE); CREATE TABLE t2 (f2 VARCHAR(8)); CREATE TABLE t3 (f2 CHAR(8)); INSERT INTO t1 VALUES ('1978-11-26'); INSERT INTO t2 SELECT f1+0 FROM t1; INSERT INTO t2 SELECT f1+0 FROM t1 UNION SELECT f1+0 FROM t1; INSERT INTO t3 SELECT f1+0 FROM t1; INSERT INTO t3 SELECT f1+0 FROM t1 UNION SELECT f1+0 FROM t1; SELECT * FROM t2; f2 19781126 19781126 SELECT * FROM t3; f2 19781126 19781126 DROP TABLE t1, t2, t3; mysql-test/t/type_date.test +19 −0 Original line number Diff line number Diff line Loading @@ -84,3 +84,22 @@ CREATE TABLE t1 (f1 time default NULL, f2 time default NULL) TYPE=MyISAM; INSERT INTO t1 (f1, f2) VALUES ('09:00', '12:00'); SELECT DATE_FORMAT(f1, "%l.%i %p") , DATE_FORMAT(f2, "%l.%i %p") FROM t1; DROP TABLE t1; # # Bug 4937: different date -> string conversion when using SELECT ... UNION # and INSERT ... SELECT ... UNION # CREATE TABLE t1 (f1 DATE); CREATE TABLE t2 (f2 VARCHAR(8)); CREATE TABLE t3 (f2 CHAR(8)); INSERT INTO t1 VALUES ('1978-11-26'); INSERT INTO t2 SELECT f1+0 FROM t1; INSERT INTO t2 SELECT f1+0 FROM t1 UNION SELECT f1+0 FROM t1; INSERT INTO t3 SELECT f1+0 FROM t1; INSERT INTO t3 SELECT f1+0 FROM t1 UNION SELECT f1+0 FROM t1; SELECT * FROM t2; SELECT * FROM t3; DROP TABLE t1, t2, t3; sql/field.cc +50 −40 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ #include "sql_select.h" #include <m_ctype.h> #include <errno.h> #include <assert.h> #ifdef HAVE_FCONVERT #include <floatingpoint.h> #endif Loading @@ -58,6 +59,8 @@ template class List_iterator<create_field>; uchar Field_null::null[1]={1}; const char field_separator=','; #define DOUBLE_TO_STRING_CONVERSION_BUFFER_SIZE 320 /***************************************************************************** Static help functions *****************************************************************************/ Loading Loading @@ -739,7 +742,7 @@ void Field_decimal::store(double nr) reg4 uint i,length; char fyllchar,*to; char buff[320]; char buff[DOUBLE_TO_STRING_CONVERSION_BUFFER_SIZE]; fyllchar = zerofill ? (char) '0' : (char) ' '; #ifdef HAVE_SNPRINTF Loading Loading @@ -2326,46 +2329,20 @@ String *Field_double::val_str(String *val_buffer, #endif doubleget(nr,ptr); uint to_length=max(field_length,320); uint to_length=max(field_length, DOUBLE_TO_STRING_CONVERSION_BUFFER_SIZE); val_buffer->alloc(to_length); char *to=(char*) val_buffer->ptr(); if (dec >= NOT_FIXED_DEC) { /* Let's try to pretty print a floating point number. Here we use '%-*.*g' conversion string: '-' stands for left-padding with spaces, if such padding will take place '*' is a placeholder for the first argument, field_length, and signifies minimal width of result string. If result is less than field length it will be space-padded. Note, however, that we'll not pass spaces to Field_string::store(const char *, ...), due to strcend in the next line. '.*' is a placeholder for DBL_DIG and defines maximum number of significant digits in the result string. DBL_DIG is a hardware specific C define for maximum number of decimal digits of a floating point number, such that rounding to hardware floating point representation and back to decimal will not lead to loss of precision. I.e if DBL_DIG is 15, number 123456789111315 can be represented as double without precision loss. As one can judge from this description, chosing DBL_DIG here is questionable, especially because it introduces a system dependency. 'g' means that conversion will use [-]ddd.ddd (conventional) style, and fall back to [-]d.ddde[+|i]ddd (scientific) style if there is no enough space for all digits. Maximum length of result string (not counting spaces) is (I guess) DBL_DIG + 8, where 8 is 1 for sign, 1 for decimal point, 1 for exponent sign, 1 for exponent, and 4 for exponent value. XXX: why do we use space-padding and trim spaces in the next line? */ sprintf(to,"%-*.*g",(int) field_length,DBL_DIG,nr); to=strcend(to,' '); } else { #ifdef HAVE_FCONVERT char buff[320],*pos=buff; char buff[DOUBLE_TO_STRING_CONVERSION_BUFFER_SIZE], char *pos= buff; int decpt,sign,tmp_dec=dec; VOID(fconvert(nr,tmp_dec,&decpt,&sign,buff)); Loading Loading @@ -3721,13 +3698,50 @@ void Field_string::store(const char *from,uint length) } /* Store double value in Field_string or Field_varstring. SYNOPSIS store_double_in_string_field() field field to store value in field_length number of characters in the field nr number DESCRIPTION Pretty prints double number into field_length characters buffer. */ static void store_double_in_string_field(Field_str *field, uint32 field_length, double nr) { bool use_scientific_notation=TRUE; char buff[DOUBLE_TO_STRING_CONVERSION_BUFFER_SIZE]; int length; if (field_length < 32 && nr > 1) { if (field->ceiling == 0) { static double e[]= {1e1, 1e2, 1e4, 1e8, 1e16 }; double p= 1; for (int i= sizeof(e)/sizeof(e[0]), j= 1<<i ; j; i--, j>>= 1 ) { if (field_length & j) p*= e[i]; } field->ceiling= p-1; } use_scientific_notation= (field->ceiling < nr); } length= sprintf(buff, "%-.*g", use_scientific_notation ? max(0,field_length-5) : field_length, nr); DBUG_ASSERT(length <= field_length); field->store(buff, (uint) length); } void Field_string::store(double nr) { char buff[MAX_FIELD_WIDTH],*end; int width=min(field_length,DBL_DIG+5); sprintf(buff,"%-*.*g",width,max(width-5,0),nr); end=strcend(buff,' '); Field_string::store(buff,(uint) (end - buff)); store_double_in_string_field(this, field_length, nr); } Loading Loading @@ -3927,11 +3941,7 @@ void Field_varstring::store(const char *from,uint length) void Field_varstring::store(double nr) { char buff[MAX_FIELD_WIDTH],*end; int width=min(field_length,DBL_DIG+5); sprintf(buff,"%-*.*g",width,max(width-5,0),nr); end=strcend(buff,' '); Field_varstring::store(buff,(uint) (end - buff)); store_double_in_string_field(this, field_length, nr); } Loading sql/field.h +2 −1 Original line number Diff line number Diff line Loading @@ -255,12 +255,13 @@ class Field_num :public Field { class Field_str :public Field { public: double ceiling; // for ::store(double nr) Field_str(char *ptr_arg,uint32 len_arg, uchar *null_ptr_arg, uchar null_bit_arg, utype unireg_check_arg, const char *field_name_arg, struct st_table *table_arg) :Field(ptr_arg, len_arg, null_ptr_arg, null_bit_arg, unireg_check_arg, field_name_arg, table_arg) unireg_check_arg, field_name_arg, table_arg), ceiling(0.0) {} Item_result result_type () const { return STRING_RESULT; } uint decimals() const { return NOT_FIXED_DEC; } Loading Loading
mysql-test/r/type_date.result +17 −0 Original line number Diff line number Diff line Loading @@ -79,3 +79,20 @@ SELECT DATE_FORMAT(f1, "%l.%i %p") , DATE_FORMAT(f2, "%l.%i %p") FROM t1; DATE_FORMAT(f1, "%l.%i %p") DATE_FORMAT(f2, "%l.%i %p") 9.00 AM 12.00 PM DROP TABLE t1; CREATE TABLE t1 (f1 DATE); CREATE TABLE t2 (f2 VARCHAR(8)); CREATE TABLE t3 (f2 CHAR(8)); INSERT INTO t1 VALUES ('1978-11-26'); INSERT INTO t2 SELECT f1+0 FROM t1; INSERT INTO t2 SELECT f1+0 FROM t1 UNION SELECT f1+0 FROM t1; INSERT INTO t3 SELECT f1+0 FROM t1; INSERT INTO t3 SELECT f1+0 FROM t1 UNION SELECT f1+0 FROM t1; SELECT * FROM t2; f2 19781126 19781126 SELECT * FROM t3; f2 19781126 19781126 DROP TABLE t1, t2, t3;
mysql-test/t/type_date.test +19 −0 Original line number Diff line number Diff line Loading @@ -84,3 +84,22 @@ CREATE TABLE t1 (f1 time default NULL, f2 time default NULL) TYPE=MyISAM; INSERT INTO t1 (f1, f2) VALUES ('09:00', '12:00'); SELECT DATE_FORMAT(f1, "%l.%i %p") , DATE_FORMAT(f2, "%l.%i %p") FROM t1; DROP TABLE t1; # # Bug 4937: different date -> string conversion when using SELECT ... UNION # and INSERT ... SELECT ... UNION # CREATE TABLE t1 (f1 DATE); CREATE TABLE t2 (f2 VARCHAR(8)); CREATE TABLE t3 (f2 CHAR(8)); INSERT INTO t1 VALUES ('1978-11-26'); INSERT INTO t2 SELECT f1+0 FROM t1; INSERT INTO t2 SELECT f1+0 FROM t1 UNION SELECT f1+0 FROM t1; INSERT INTO t3 SELECT f1+0 FROM t1; INSERT INTO t3 SELECT f1+0 FROM t1 UNION SELECT f1+0 FROM t1; SELECT * FROM t2; SELECT * FROM t3; DROP TABLE t1, t2, t3;
sql/field.cc +50 −40 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ #include "sql_select.h" #include <m_ctype.h> #include <errno.h> #include <assert.h> #ifdef HAVE_FCONVERT #include <floatingpoint.h> #endif Loading @@ -58,6 +59,8 @@ template class List_iterator<create_field>; uchar Field_null::null[1]={1}; const char field_separator=','; #define DOUBLE_TO_STRING_CONVERSION_BUFFER_SIZE 320 /***************************************************************************** Static help functions *****************************************************************************/ Loading Loading @@ -739,7 +742,7 @@ void Field_decimal::store(double nr) reg4 uint i,length; char fyllchar,*to; char buff[320]; char buff[DOUBLE_TO_STRING_CONVERSION_BUFFER_SIZE]; fyllchar = zerofill ? (char) '0' : (char) ' '; #ifdef HAVE_SNPRINTF Loading Loading @@ -2326,46 +2329,20 @@ String *Field_double::val_str(String *val_buffer, #endif doubleget(nr,ptr); uint to_length=max(field_length,320); uint to_length=max(field_length, DOUBLE_TO_STRING_CONVERSION_BUFFER_SIZE); val_buffer->alloc(to_length); char *to=(char*) val_buffer->ptr(); if (dec >= NOT_FIXED_DEC) { /* Let's try to pretty print a floating point number. Here we use '%-*.*g' conversion string: '-' stands for left-padding with spaces, if such padding will take place '*' is a placeholder for the first argument, field_length, and signifies minimal width of result string. If result is less than field length it will be space-padded. Note, however, that we'll not pass spaces to Field_string::store(const char *, ...), due to strcend in the next line. '.*' is a placeholder for DBL_DIG and defines maximum number of significant digits in the result string. DBL_DIG is a hardware specific C define for maximum number of decimal digits of a floating point number, such that rounding to hardware floating point representation and back to decimal will not lead to loss of precision. I.e if DBL_DIG is 15, number 123456789111315 can be represented as double without precision loss. As one can judge from this description, chosing DBL_DIG here is questionable, especially because it introduces a system dependency. 'g' means that conversion will use [-]ddd.ddd (conventional) style, and fall back to [-]d.ddde[+|i]ddd (scientific) style if there is no enough space for all digits. Maximum length of result string (not counting spaces) is (I guess) DBL_DIG + 8, where 8 is 1 for sign, 1 for decimal point, 1 for exponent sign, 1 for exponent, and 4 for exponent value. XXX: why do we use space-padding and trim spaces in the next line? */ sprintf(to,"%-*.*g",(int) field_length,DBL_DIG,nr); to=strcend(to,' '); } else { #ifdef HAVE_FCONVERT char buff[320],*pos=buff; char buff[DOUBLE_TO_STRING_CONVERSION_BUFFER_SIZE], char *pos= buff; int decpt,sign,tmp_dec=dec; VOID(fconvert(nr,tmp_dec,&decpt,&sign,buff)); Loading Loading @@ -3721,13 +3698,50 @@ void Field_string::store(const char *from,uint length) } /* Store double value in Field_string or Field_varstring. SYNOPSIS store_double_in_string_field() field field to store value in field_length number of characters in the field nr number DESCRIPTION Pretty prints double number into field_length characters buffer. */ static void store_double_in_string_field(Field_str *field, uint32 field_length, double nr) { bool use_scientific_notation=TRUE; char buff[DOUBLE_TO_STRING_CONVERSION_BUFFER_SIZE]; int length; if (field_length < 32 && nr > 1) { if (field->ceiling == 0) { static double e[]= {1e1, 1e2, 1e4, 1e8, 1e16 }; double p= 1; for (int i= sizeof(e)/sizeof(e[0]), j= 1<<i ; j; i--, j>>= 1 ) { if (field_length & j) p*= e[i]; } field->ceiling= p-1; } use_scientific_notation= (field->ceiling < nr); } length= sprintf(buff, "%-.*g", use_scientific_notation ? max(0,field_length-5) : field_length, nr); DBUG_ASSERT(length <= field_length); field->store(buff, (uint) length); } void Field_string::store(double nr) { char buff[MAX_FIELD_WIDTH],*end; int width=min(field_length,DBL_DIG+5); sprintf(buff,"%-*.*g",width,max(width-5,0),nr); end=strcend(buff,' '); Field_string::store(buff,(uint) (end - buff)); store_double_in_string_field(this, field_length, nr); } Loading Loading @@ -3927,11 +3941,7 @@ void Field_varstring::store(const char *from,uint length) void Field_varstring::store(double nr) { char buff[MAX_FIELD_WIDTH],*end; int width=min(field_length,DBL_DIG+5); sprintf(buff,"%-*.*g",width,max(width-5,0),nr); end=strcend(buff,' '); Field_varstring::store(buff,(uint) (end - buff)); store_double_in_string_field(this, field_length, nr); } Loading
sql/field.h +2 −1 Original line number Diff line number Diff line Loading @@ -255,12 +255,13 @@ class Field_num :public Field { class Field_str :public Field { public: double ceiling; // for ::store(double nr) Field_str(char *ptr_arg,uint32 len_arg, uchar *null_ptr_arg, uchar null_bit_arg, utype unireg_check_arg, const char *field_name_arg, struct st_table *table_arg) :Field(ptr_arg, len_arg, null_ptr_arg, null_bit_arg, unireg_check_arg, field_name_arg, table_arg) unireg_check_arg, field_name_arg, table_arg), ceiling(0.0) {} Item_result result_type () const { return STRING_RESULT; } uint decimals() const { return NOT_FIXED_DEC; } Loading