Commit faad7355 authored by unknown's avatar unknown

fix for bug #19690: ORDER BY eliminates rows from the result

Depending on the queries we use different data processing methods
and can lose some data in case of double (and decimal in 4.1) fields.

The fix consists of two parts:
1. double comparison changed, now double a is equal to double b 
if (a-b) is less than 5*0.1^(1 + max(a->decimals, b->decimals)). 
For example, if a->decimals==1, b->decimals==2, a==b if (a-b)<0.005
2. if we use a temporary table, store double values there as is 
to avoid any data conversion (rounding).


mysql-test/r/type_float.result:
  fix for bug #19690: ORDER BY eliminates rows from the result
    - test result
mysql-test/t/type_float.test:
  fix for bug #19690: ORDER BY eliminates rows from the result
    - test case
sql/field.cc:
  fix for bug #19690: ORDER BY eliminates rows from the result
    - use not_fixed flag instead of dec to check bounds.
sql/field.h:
  fix for bug #19690: ORDER BY eliminates rows from the result
    - Field_Double::not_fixed flag introduced, which is set if dec == NOT_FIXED_DEC
      and is used in the ::store() to check bounds. 
    - new constructor introduced (with not_fixed_arg parameter).
sql/init.cc:
  fix for bug #19690: ORDER BY eliminates rows from the result
    - fill log_01[] array with 0.1 powers.
sql/item_cmpfunc.cc:
  fix for bug #19690: ORDER BY eliminates rows from the result
    - compare_real_fixed() and compare_e_real_fixed() introduced,
      they consider double a == double b if a-b is less than 'precision',
      'precision' is set to 5*0.1^(1 + max(a->decimals, b->decimals)), 
      for example, if a->decimals==1, b->decimals==2, 'precision' is 0.005
    - use the above functions if both arguments are fixed.
sql/item_cmpfunc.h:
  fix for bug #19690: ORDER BY eliminates rows from the result
    - Arg_comparator::presision introduced.
    - Arg_comparator::compare_real_fixed(), Arg_comparator::compare_e_real_fixed() introduced.
sql/mysql_priv.h:
  fix for bug #19690: ORDER BY eliminates rows from the result
    - log_01 array of 0.1 powers added.
sql/mysqld.cc:
  fix for bug #19690: ORDER BY eliminates rows from the result
    - log_01 array of 0.1 powers added.
sql/sql_select.cc:
  fix for bug #19690: ORDER BY eliminates rows from the result
    - if we create double field in a temporary table, set not_fixed flag
      (use proper constructor) to avoid data conversion 
      in the Field_double::store(). Otherwise we can lose some data.
parent e167d79e
...@@ -278,4 +278,75 @@ select 1e-308, 1.00000001e-300, 100000000e-300; ...@@ -278,4 +278,75 @@ select 1e-308, 1.00000001e-300, 100000000e-300;
select 10e307; select 10e307;
10e307 10e307
1e+308 1e+308
create table t1(a int, b double(8, 2));
insert into t1 values
(1, 28.50), (1, 121.85), (1, 157.23), (1, 1351.00), (1, -1965.35), (1, 81.75),
(1, 217.08), (1, 7.94), (4, 96.07), (4, 6404.65), (4, -6500.72), (2, 100.00),
(5, 5.00), (5, -2104.80), (5, 2033.80), (5, 0.07), (5, 65.93),
(3, -4986.24), (3, 5.00), (3, 4857.34), (3, 123.74), (3, 0.16),
(6, -1695.31), (6, 1003.77), (6, 499.72), (6, 191.82);
explain select sum(b) s from t1 group by a;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 ALL NULL NULL NULL NULL 26 Using temporary; Using filesort
select sum(b) s from t1 group by a;
s
0.00
100.00
0.00
-0.00
-0.00
0.00
select sum(b) s from t1 group by a having s <> 0;
s
100.00
select sum(b) s from t1 group by a having s <> 0 order by s;
s
100.00
select sum(b) s from t1 group by a having s <=> 0;
s
0.00
0.00
-0.00
-0.00
0.00
select sum(b) s from t1 group by a having s <=> 0 order by s;
s
-0.00
-0.00
0.00
0.00
0.00
alter table t1 add key (a, b);
explain select sum(b) s from t1 group by a;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 index NULL a 14 NULL 26 Using index
select sum(b) s from t1 group by a;
s
0.00
100.00
0.00
-0.00
0.00
0.00
select sum(b) s from t1 group by a having s <> 0;
s
100.00
select sum(b) s from t1 group by a having s <> 0 order by s;
s
100.00
select sum(b) s from t1 group by a having s <=> 0;
s
0.00
0.00
-0.00
0.00
0.00
select sum(b) s from t1 group by a having s <=> 0 order by s;
s
-0.00
0.00
0.00
0.00
0.00
drop table t1;
End of 4.1 tests End of 4.1 tests
...@@ -188,4 +188,29 @@ select 1e-308, 1.00000001e-300, 100000000e-300; ...@@ -188,4 +188,29 @@ select 1e-308, 1.00000001e-300, 100000000e-300;
# check if overflows are detected correctly # check if overflows are detected correctly
select 10e307; select 10e307;
#
# Bug #19690: ORDER BY eliminates rows from the result
#
create table t1(a int, b double(8, 2));
insert into t1 values
(1, 28.50), (1, 121.85), (1, 157.23), (1, 1351.00), (1, -1965.35), (1, 81.75),
(1, 217.08), (1, 7.94), (4, 96.07), (4, 6404.65), (4, -6500.72), (2, 100.00),
(5, 5.00), (5, -2104.80), (5, 2033.80), (5, 0.07), (5, 65.93),
(3, -4986.24), (3, 5.00), (3, 4857.34), (3, 123.74), (3, 0.16),
(6, -1695.31), (6, 1003.77), (6, 499.72), (6, 191.82);
explain select sum(b) s from t1 group by a;
select sum(b) s from t1 group by a;
select sum(b) s from t1 group by a having s <> 0;
select sum(b) s from t1 group by a having s <> 0 order by s;
select sum(b) s from t1 group by a having s <=> 0;
select sum(b) s from t1 group by a having s <=> 0 order by s;
alter table t1 add key (a, b);
explain select sum(b) s from t1 group by a;
select sum(b) s from t1 group by a;
select sum(b) s from t1 group by a having s <> 0;
select sum(b) s from t1 group by a having s <> 0 order by s;
select sum(b) s from t1 group by a having s <=> 0;
select sum(b) s from t1 group by a having s <=> 0 order by s;
drop table t1;
--echo End of 4.1 tests --echo End of 4.1 tests
...@@ -3318,7 +3318,7 @@ int Field_double::store(double nr) ...@@ -3318,7 +3318,7 @@ int Field_double::store(double nr)
else else
{ {
double max_value; double max_value;
if (dec >= NOT_FIXED_DEC) if (not_fixed)
{ {
max_value= DBL_MAX; max_value= DBL_MAX;
} }
......
...@@ -616,6 +616,7 @@ class Field_float :public Field_num { ...@@ -616,6 +616,7 @@ class Field_float :public Field_num {
class Field_double :public Field_num { class Field_double :public Field_num {
public: public:
my_bool not_fixed;
Field_double(char *ptr_arg, uint32 len_arg, uchar *null_ptr_arg, Field_double(char *ptr_arg, uint32 len_arg, uchar *null_ptr_arg,
uchar null_bit_arg, uchar null_bit_arg,
enum utype unireg_check_arg, const char *field_name_arg, enum utype unireg_check_arg, const char *field_name_arg,
...@@ -623,12 +624,20 @@ class Field_double :public Field_num { ...@@ -623,12 +624,20 @@ class Field_double :public Field_num {
uint8 dec_arg,bool zero_arg,bool unsigned_arg) uint8 dec_arg,bool zero_arg,bool unsigned_arg)
:Field_num(ptr_arg, len_arg, null_ptr_arg, null_bit_arg, :Field_num(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,
dec_arg, zero_arg,unsigned_arg) dec_arg, zero_arg, unsigned_arg),
not_fixed(dec_arg >= NOT_FIXED_DEC)
{} {}
Field_double(uint32 len_arg, bool maybe_null_arg, const char *field_name_arg, Field_double(uint32 len_arg, bool maybe_null_arg, const char *field_name_arg,
struct st_table *table_arg, uint8 dec_arg) struct st_table *table_arg, uint8 dec_arg)
:Field_num((char*) 0, len_arg, maybe_null_arg ? (uchar*) "": 0, (uint) 0, :Field_num((char *) 0, len_arg, maybe_null_arg ? (uchar *) "" : 0, (uint) 0,
NONE, field_name_arg, table_arg,dec_arg,0,0) NONE, field_name_arg, table_arg,dec_arg, 0, 0),
not_fixed(dec_arg >= NOT_FIXED_DEC)
{}
Field_double(uint32 len_arg, bool maybe_null_arg, const char *field_name_arg,
struct st_table *table_arg, uint8 dec_arg, my_bool not_fixed_srg)
:Field_num((char *) 0, len_arg, maybe_null_arg ? (uchar *) "" : 0, (uint) 0,
NONE, field_name_arg, table_arg, dec_arg, 0, 0),
not_fixed(not_fixed_srg)
{} {}
enum_field_types type() const { return FIELD_TYPE_DOUBLE;} enum_field_types type() const { return FIELD_TYPE_DOUBLE;}
enum ha_base_keytype key_type() const { return HA_KEYTYPE_DOUBLE; } enum ha_base_keytype key_type() const { return HA_KEYTYPE_DOUBLE; }
......
...@@ -45,6 +45,12 @@ void unireg_init(ulong options) ...@@ -45,6 +45,12 @@ void unireg_init(ulong options)
{ /* It's used by filesort... */ { /* It's used by filesort... */
log_10[i]= nr ; nr*= 10.0; log_10[i]= nr ; nr*= 10.0;
} }
/* Make a tab of powers of 0.1 */
for (i= 0, nr= 0.1; i < array_elements(log_01); i++)
{
log_01[i]= nr;
nr*= 0.1;
}
specialflag|=options; /* Set options from argv */ specialflag|=options; /* Set options from argv */
DBUG_VOID_RETURN; DBUG_VOID_RETURN;
} }
...@@ -352,6 +352,17 @@ int Arg_comparator::set_compare_func(Item_bool_func2 *item, Item_result type) ...@@ -352,6 +352,17 @@ int Arg_comparator::set_compare_func(Item_bool_func2 *item, Item_result type)
func= &Arg_comparator::compare_e_int_diff_signedness; func= &Arg_comparator::compare_e_int_diff_signedness;
} }
} }
else if (type == REAL_RESULT)
{
if ((*a)->decimals < NOT_FIXED_DEC && (*b)->decimals < NOT_FIXED_DEC)
{
precision= 5 * log_01[max((*a)->decimals, (*b)->decimals)];
if (func == &Arg_comparator::compare_real)
func= &Arg_comparator::compare_real_fixed;
else if (func == &Arg_comparator::compare_e_real)
func= &Arg_comparator::compare_e_real_fixed;
}
}
return 0; return 0;
} }
...@@ -459,6 +470,44 @@ int Arg_comparator::compare_e_real() ...@@ -459,6 +470,44 @@ int Arg_comparator::compare_e_real()
return test(val1 == val2); return test(val1 == val2);
} }
int Arg_comparator::compare_real_fixed()
{
/*
Fix yet another manifestation of Bug#2338. 'Volatile' will instruct
gcc to flush double values out of 80-bit Intel FPU registers before
performing the comparison.
*/
volatile double val1, val2;
val1= (*a)->val();
if (!(*a)->null_value)
{
val2= (*b)->val();
if (!(*b)->null_value)
{
owner->null_value= 0;
if (val1 == val2 || fabs(val1 - val2) < precision)
return 0;
if (val1 < val2)
return -1;
return 1;
}
}
owner->null_value= 1;
return -1;
}
int Arg_comparator::compare_e_real_fixed()
{
double val1= (*a)->val();
double val2= (*b)->val();
if ((*a)->null_value || (*b)->null_value)
return test((*a)->null_value && (*b)->null_value);
return test(val1 == val2 || fabs(val1 - val2) < precision);
}
int Arg_comparator::compare_int_signed() int Arg_comparator::compare_int_signed()
{ {
longlong val1= (*a)->val_int(); longlong val1= (*a)->val_int();
......
...@@ -33,6 +33,7 @@ class Arg_comparator: public Sql_alloc ...@@ -33,6 +33,7 @@ class Arg_comparator: public Sql_alloc
arg_cmp_func func; arg_cmp_func func;
Item_bool_func2 *owner; Item_bool_func2 *owner;
Arg_comparator *comparators; // used only for compare_row() Arg_comparator *comparators; // used only for compare_row()
double precision;
public: public:
DTCollation cmp_collation; DTCollation cmp_collation;
...@@ -77,6 +78,8 @@ class Arg_comparator: public Sql_alloc ...@@ -77,6 +78,8 @@ class Arg_comparator: public Sql_alloc
int compare_e_int(); // compare args[0] & args[1] int compare_e_int(); // compare args[0] & args[1]
int compare_e_int_diff_signedness(); int compare_e_int_diff_signedness();
int compare_e_row(); // compare args[0] & args[1] int compare_e_row(); // compare args[0] & args[1]
int compare_real_fixed();
int compare_e_real_fixed();
static arg_cmp_func comparator_matrix [4][2]; static arg_cmp_func comparator_matrix [4][2];
......
...@@ -907,6 +907,7 @@ extern char glob_hostname[FN_REFLEN], mysql_home[FN_REFLEN]; ...@@ -907,6 +907,7 @@ extern char glob_hostname[FN_REFLEN], mysql_home[FN_REFLEN];
extern char pidfile_name[FN_REFLEN], system_time_zone[30], *opt_init_file; extern char pidfile_name[FN_REFLEN], system_time_zone[30], *opt_init_file;
extern char log_error_file[FN_REFLEN]; extern char log_error_file[FN_REFLEN];
extern double log_10[32]; extern double log_10[32];
extern double log_01[32];
extern ulonglong log_10_int[20]; extern ulonglong log_10_int[20];
extern ulonglong keybuff_size; extern ulonglong keybuff_size;
extern ulong refresh_version,flush_version, thread_id,query_id,opened_tables; extern ulong refresh_version,flush_version, thread_id,query_id,opened_tables;
......
...@@ -361,6 +361,7 @@ ulong my_bind_addr; /* the address we bind to */ ...@@ -361,6 +361,7 @@ ulong my_bind_addr; /* the address we bind to */
volatile ulong cached_thread_count= 0; volatile ulong cached_thread_count= 0;
double log_10[32]; /* 10 potences */ double log_10[32]; /* 10 potences */
double log_01[32];
time_t start_time; time_t start_time;
char mysql_home[FN_REFLEN], pidfile_name[FN_REFLEN], system_time_zone[30]; char mysql_home[FN_REFLEN], pidfile_name[FN_REFLEN], system_time_zone[30];
......
...@@ -5006,6 +5006,8 @@ static Field* create_tmp_field_from_field(THD *thd, Field* org_field, ...@@ -5006,6 +5006,8 @@ static Field* create_tmp_field_from_field(THD *thd, Field* org_field,
new_field->flags&= ~NOT_NULL_FLAG; // Because of outer join new_field->flags&= ~NOT_NULL_FLAG; // Because of outer join
if (org_field->type() == FIELD_TYPE_VAR_STRING) if (org_field->type() == FIELD_TYPE_VAR_STRING)
table->db_create_options|= HA_OPTION_PACK_RECORD; table->db_create_options|= HA_OPTION_PACK_RECORD;
else if (org_field->type() == FIELD_TYPE_DOUBLE)
((Field_double *) new_field)->not_fixed= TRUE;
} }
return new_field; return new_field;
} }
...@@ -5045,7 +5047,7 @@ static Field* create_tmp_field_from_item(THD *thd, Item *item, TABLE *table, ...@@ -5045,7 +5047,7 @@ static Field* create_tmp_field_from_item(THD *thd, Item *item, TABLE *table,
switch (item->result_type()) { switch (item->result_type()) {
case REAL_RESULT: case REAL_RESULT:
new_field=new Field_double(item->max_length, maybe_null, new_field=new Field_double(item->max_length, maybe_null,
item->name, table, item->decimals); item->name, table, item->decimals, TRUE);
break; break;
case INT_RESULT: case INT_RESULT:
new_field=new Field_longlong(item->max_length, maybe_null, new_field=new Field_longlong(item->max_length, maybe_null,
...@@ -5136,8 +5138,8 @@ Field *create_tmp_field(THD *thd, TABLE *table,Item *item, Item::Type type, ...@@ -5136,8 +5138,8 @@ Field *create_tmp_field(THD *thd, TABLE *table,Item *item, Item::Type type,
return new Field_string(sizeof(double)+sizeof(longlong), return new Field_string(sizeof(double)+sizeof(longlong),
0, item->name,table,&my_charset_bin); 0, item->name,table,&my_charset_bin);
else else
return new Field_double(item_sum->max_length,maybe_null, return new Field_double(item_sum->max_length, maybe_null,
item->name, table, item_sum->decimals); item->name, table, item_sum->decimals, TRUE);
case Item_sum::VARIANCE_FUNC: /* Place for sum & count */ case Item_sum::VARIANCE_FUNC: /* Place for sum & count */
case Item_sum::STD_FUNC: case Item_sum::STD_FUNC:
if (group) if (group)
...@@ -5145,7 +5147,7 @@ Field *create_tmp_field(THD *thd, TABLE *table,Item *item, Item::Type type, ...@@ -5145,7 +5147,7 @@ Field *create_tmp_field(THD *thd, TABLE *table,Item *item, Item::Type type,
0, item->name,table,&my_charset_bin); 0, item->name,table,&my_charset_bin);
else else
return new Field_double(item_sum->max_length, maybe_null, return new Field_double(item_sum->max_length, maybe_null,
item->name,table,item_sum->decimals); item->name, table, item_sum->decimals, TRUE);
case Item_sum::UNIQUE_USERS_FUNC: case Item_sum::UNIQUE_USERS_FUNC:
return new Field_long(9,maybe_null,item->name,table,1); return new Field_long(9,maybe_null,item->name,table,1);
case Item_sum::MIN_FUNC: case Item_sum::MIN_FUNC:
...@@ -5160,8 +5162,8 @@ Field *create_tmp_field(THD *thd, TABLE *table,Item *item, Item::Type type, ...@@ -5160,8 +5162,8 @@ Field *create_tmp_field(THD *thd, TABLE *table,Item *item, Item::Type type,
default: default:
switch (item_sum->result_type()) { switch (item_sum->result_type()) {
case REAL_RESULT: case REAL_RESULT:
return new Field_double(item_sum->max_length,maybe_null, return new Field_double(item_sum->max_length, maybe_null,
item->name,table,item_sum->decimals); item->name, table, item_sum->decimals, TRUE);
case INT_RESULT: case INT_RESULT:
return new Field_longlong(item_sum->max_length,maybe_null, return new Field_longlong(item_sum->max_length,maybe_null,
item->name,table,item->unsigned_flag); item->name,table,item->unsigned_flag);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment