summaryrefslogtreecommitdiff
path: root/sql/item_func.cc
diff options
context:
space:
mode:
authorAlexey Kopytov <Alexey.Kopytov@Sun.com>2009-07-03 11:41:19 +0400
committerAlexey Kopytov <Alexey.Kopytov@Sun.com>2009-07-03 11:41:19 +0400
commit096c12b2c42b83d879314276fe6471edf8c61fd0 (patch)
treeeb8d560e9cb8f8cec2e70a926da8fae128f04e81 /sql/item_func.cc
parent96c069142da6876014ff1515f59f1e2aba712273 (diff)
downloadmariadb-git-096c12b2c42b83d879314276fe6471edf8c61fd0.tar.gz
Bug #45262: Bad effects with CREATE TABLE and DECIMAL
Using DECIMAL constants with more than 65 digits in CREATE TABLE ... SELECT led to bogus errors in release builds or assertion failures in debug builds. The problem was in inconsistency in how DECIMAL constants and fields are handled internally. We allow arbitrarily long DECIMAL constants, whereas DECIMAL(M,D) columns are limited to M<=65 and D<=30. my_decimal_precision_to_length() was used in both Item and Field code and truncated precision to DECIMAL_MAX_PRECISION when calculating value length without adjusting precision and decimals. As a result, a DECIMAL constant with more than 65 digits ended up having length less than precision or decimals which led to assertion failures. Fixed by modifying my_decimal_precision_to_length() so that precision is truncated to DECIMAL_MAX_PRECISION only for Field object which is indicated by the new 'truncate' parameter. Another inconsistency fixed by this patch is how DECIMAL constants and expressions are handled for CREATE ... SELECT. create_tmp_field_from_item() (which is used for constants) was changed as a part of the bugfix for bug #24907 to handle long DECIMAL constants gracefully. Item_func::tmp_table_field() (which is used for expressions) on the other hand was still using a simplistic approach when creating a Field_new_decimal from a DECIMAL expression. mysql-test/r/type_newdecimal.result: Added a test case for bug #45262. mysql-test/t/type_newdecimal.test: Added a test case for bug #45262. sql/item.cc: Use the new 'truncate' parameter in my_decimal_precision_to_length(). sql/item_cmpfunc.cc: Use the new 'truncate' parameter in my_decimal_precision_to_length(). sql/item_func.cc: 1. Use the new 'truncate' parameter in my_decimal_precision_to_length(). 2. Do not truncate decimal precision to DECIMAL_MAX_PRECISION for additive expressions involving long DECIMAL constants. 3. Fixed an incosistency in how DECIMAL constants and expressions are handled for CREATE ... SELECT. sql/item_func.h: Use the new 'truncate' parameter in my_decimal_precision_to_length(). sql/item_sum.cc: Use the new 'truncate' parameter in my_decimal_precision_to_length(). sql/my_decimal.h: Do not truncate precision to DECIMAL_MAX_PRECISION when calculating length in my_decimal_precision_to_length() if 'truncate' parameter is FALSE. sql/sql_select.cc: 1. Use the new 'truncate' parameter in my_decimal_precision_to_length(). 2. Use a more correct logic when adjusting value's length.
Diffstat (limited to 'sql/item_func.cc')
-rw-r--r--sql/item_func.cc71
1 files changed, 54 insertions, 17 deletions
diff --git a/sql/item_func.cc b/sql/item_func.cc
index 6193ab285f2..7f7f79480ca 100644
--- a/sql/item_func.cc
+++ b/sql/item_func.cc
@@ -447,11 +447,46 @@ Field *Item_func::tmp_table_field(TABLE *t_arg)
res= make_string_field(t_arg);
break;
case DECIMAL_RESULT:
- res= new Field_new_decimal(my_decimal_precision_to_length(decimal_precision(),
- decimals,
- unsigned_flag),
- maybe_null, name, t_arg, decimals, unsigned_flag);
+ {
+ uint8 dec= decimals;
+ uint8 intg= decimal_precision() - dec;
+ uint32 len= max_length;
+
+ /*
+ Trying to put too many digits overall in a DECIMAL(prec,dec)
+ will always throw a warning. We must limit dec to
+ DECIMAL_MAX_SCALE however to prevent an assert() later.
+ */
+
+ if (dec > 0)
+ {
+ int overflow;
+
+ dec= min(dec, DECIMAL_MAX_SCALE);
+
+ /*
+ If the value still overflows the field with the corrected dec,
+ we'll throw out decimals rather than integers. This is still
+ bad and of course throws a truncation warning.
+ */
+
+ const int required_length=
+ my_decimal_precision_to_length(intg + dec, dec,
+ unsigned_flag);
+
+ overflow= required_length - len;
+
+ if (overflow > 0)
+ dec= max(0, dec - overflow); // too long, discard fract
+ else
+ /* Corrected value fits. */
+ len= required_length;
+ }
+
+ res= new Field_new_decimal(len, maybe_null, name,
+ t_arg, dec, unsigned_flag);
break;
+ }
case ROW_RESULT:
default:
// This case should never be chosen
@@ -540,8 +575,8 @@ void Item_func::count_decimal_length()
set_if_smaller(unsigned_flag, args[i]->unsigned_flag);
}
int precision= min(max_int_part + decimals, DECIMAL_MAX_PRECISION);
- max_length= my_decimal_precision_to_length(precision, decimals,
- unsigned_flag);
+ max_length= my_decimal_precision_to_length_no_truncation(precision, decimals,
+ unsigned_flag);
}
@@ -1158,16 +1193,15 @@ void Item_func_additive_op::result_precision()
decimals= max(args[0]->decimals, args[1]->decimals);
int arg1_int= args[0]->decimal_precision() - args[0]->decimals;
int arg2_int= args[1]->decimal_precision() - args[1]->decimals;
- int est_prec= max(arg1_int, arg2_int) + 1 + decimals;
- int precision= min(est_prec, DECIMAL_MAX_PRECISION);
+ int precision= max(arg1_int, arg2_int) + 1 + decimals;
/* Integer operations keep unsigned_flag if one of arguments is unsigned */
if (result_type() == INT_RESULT)
unsigned_flag= args[0]->unsigned_flag | args[1]->unsigned_flag;
else
unsigned_flag= args[0]->unsigned_flag & args[1]->unsigned_flag;
- max_length= my_decimal_precision_to_length(precision, decimals,
- unsigned_flag);
+ max_length= my_decimal_precision_to_length_no_truncation(precision, decimals,
+ unsigned_flag);
}
@@ -1270,7 +1304,8 @@ void Item_func_mul::result_precision()
decimals= min(args[0]->decimals + args[1]->decimals, DECIMAL_MAX_SCALE);
uint est_prec = args[0]->decimal_precision() + args[1]->decimal_precision();
uint precision= min(est_prec, DECIMAL_MAX_PRECISION);
- max_length= my_decimal_precision_to_length(precision, decimals,unsigned_flag);
+ max_length= my_decimal_precision_to_length_no_truncation(precision, decimals,
+ unsigned_flag);
}
@@ -1326,8 +1361,8 @@ void Item_func_div::result_precision()
else
unsigned_flag= args[0]->unsigned_flag & args[1]->unsigned_flag;
decimals= min(args[0]->decimals + prec_increment, DECIMAL_MAX_SCALE);
- max_length= my_decimal_precision_to_length(precision, decimals,
- unsigned_flag);
+ max_length= my_decimal_precision_to_length_no_truncation(precision, decimals,
+ unsigned_flag);
}
@@ -2013,8 +2048,9 @@ void Item_func_round::fix_length_and_dec()
precision-= decimals_delta - length_increase;
decimals= min(decimals_to_set, DECIMAL_MAX_SCALE);
- max_length= my_decimal_precision_to_length(precision, decimals,
- unsigned_flag);
+ max_length= my_decimal_precision_to_length_no_truncation(precision,
+ decimals,
+ unsigned_flag);
break;
}
default:
@@ -2249,8 +2285,9 @@ void Item_func_min_max::fix_length_and_dec()
}
}
else if ((cmp_type == DECIMAL_RESULT) || (cmp_type == INT_RESULT))
- max_length= my_decimal_precision_to_length(max_int_part+decimals, decimals,
- unsigned_flag);
+ max_length= my_decimal_precision_to_length_no_truncation(max_int_part +
+ decimals, decimals,
+ unsigned_flag);
cached_field_type= agg_field_type(args, arg_count);
}