MDEV-23337 Rounding functions create a wrong data type for integer input

1. Fixing ROUND(x) and TRUNCATE(x,0) with TINYINT, SMALLINT, MEDIUMINT, BIGINT
   input to preserve the exact data type of the argument when it's possible.

2. Fixing FLOOR(x) and CEILING(x) with TINYINT, SMALLINT, MEDIUMINT, BIGINT
  to preserve the exact data type of the argument.

3. Adding dedicated Type_handler_year::Item_func_round_fix_length_and_dec()
  to easier handle ROUND(x) and TRUNCATE(x,y) for the YEAR(2) and YEAR(4)
  input. They still return INT(2) UNSIGNED and INT(4) UNSIGNED correspondingly,
  as before.
This commit is contained in:
Alexander Barkov 2020-07-30 14:30:21 +04:00
parent c3958ae407
commit a874b6c445
9 changed files with 381 additions and 30 deletions

View File

@ -715,16 +715,16 @@ CEILING(11111111),
CEILING(111111111),
CEILING(1111111111) LIMIT 0;
Catalog Database Table Table_alias Column Column_alias Type Length Max length Is_null Flags Decimals Charsetnr
def CEILING(1) 3 3 0 N 32897 0 63
def CEILING(11) 3 4 0 N 32897 0 63
def CEILING(111) 3 5 0 N 32897 0 63
def CEILING(1111) 3 6 0 N 32897 0 63
def CEILING(11111) 3 7 0 N 32897 0 63
def CEILING(111111) 3 8 0 N 32897 0 63
def CEILING(1111111) 3 9 0 N 32897 0 63
def CEILING(11111111) 8 10 0 N 32897 0 63
def CEILING(111111111) 8 11 0 N 32897 0 63
def CEILING(1111111111) 8 12 0 N 32897 0 63
def CEILING(1) 3 1 0 N 32897 0 63
def CEILING(11) 3 2 0 N 32897 0 63
def CEILING(111) 3 3 0 N 32897 0 63
def CEILING(1111) 3 4 0 N 32897 0 63
def CEILING(11111) 3 5 0 N 32897 0 63
def CEILING(111111) 3 6 0 N 32897 0 63
def CEILING(1111111) 3 7 0 N 32897 0 63
def CEILING(11111111) 3 8 0 N 32897 0 63
def CEILING(111111111) 3 9 0 N 32897 0 63
def CEILING(1111111111) 8 10 0 N 32897 0 63
CEILING(1) CEILING(11) CEILING(111) CEILING(1111) CEILING(11111) CEILING(111111) CEILING(1111111) CEILING(11111111) CEILING(111111111) CEILING(1111111111)
SELECT
FLOOR(1),
@ -738,16 +738,16 @@ FLOOR(11111111),
FLOOR(111111111),
FLOOR(1111111111) LIMIT 0;
Catalog Database Table Table_alias Column Column_alias Type Length Max length Is_null Flags Decimals Charsetnr
def FLOOR(1) 3 3 0 N 32897 0 63
def FLOOR(11) 3 4 0 N 32897 0 63
def FLOOR(111) 3 5 0 N 32897 0 63
def FLOOR(1111) 3 6 0 N 32897 0 63
def FLOOR(11111) 3 7 0 N 32897 0 63
def FLOOR(111111) 3 8 0 N 32897 0 63
def FLOOR(1111111) 3 9 0 N 32897 0 63
def FLOOR(11111111) 8 10 0 N 32897 0 63
def FLOOR(111111111) 8 11 0 N 32897 0 63
def FLOOR(1111111111) 8 12 0 N 32897 0 63
def FLOOR(1) 3 1 0 N 32897 0 63
def FLOOR(11) 3 2 0 N 32897 0 63
def FLOOR(111) 3 3 0 N 32897 0 63
def FLOOR(1111) 3 4 0 N 32897 0 63
def FLOOR(11111) 3 5 0 N 32897 0 63
def FLOOR(111111) 3 6 0 N 32897 0 63
def FLOOR(1111111) 3 7 0 N 32897 0 63
def FLOOR(11111111) 3 8 0 N 32897 0 63
def FLOOR(111111111) 3 9 0 N 32897 0 63
def FLOOR(1111111111) 8 10 0 N 32897 0 63
FLOOR(1) FLOOR(11) FLOOR(111) FLOOR(1111) FLOOR(11111) FLOOR(111111) FLOOR(1111111) FLOOR(11111111) FLOOR(111111111) FLOOR(1111111111)
SELECT
ROUND(1),

View File

@ -412,5 +412,221 @@ COUNT(*)
0
DROP TABLE t1;
#
# MDEV-23337 Rounding functions create a wrong data type for numeric input
#
CREATE PROCEDURE p1(t VARCHAR(64))
BEGIN
SELECT t AS ``;
EXECUTE IMMEDIATE REPLACE('CREATE TABLE t1 (a TYPE)', 'TYPE', t);
INSERT IGNORE INTO t1 VALUES (-100000000000000000000000000000);
INSERT IGNORE INTO t1 VALUES (100000000000000000000000000000);
CREATE TABLE t2 AS SELECT a, ROUND(a), TRUNCATE(a,0), FLOOR(a), CEILING(a) FROM t1;
SHOW CREATE TABLE t2;
SELECT * FROM t2;
DROP TABLE t1, t2;
END;
$$
CALL p1('tinyint');
tinyint
Table t2
Create Table CREATE TABLE `t2` (
`a` tinyint(4) DEFAULT NULL,
`ROUND(a)` tinyint(4) DEFAULT NULL,
`TRUNCATE(a,0)` tinyint(4) DEFAULT NULL,
`FLOOR(a)` tinyint(4) DEFAULT NULL,
`CEILING(a)` tinyint(4) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1
a -128
ROUND(a) -128
TRUNCATE(a,0) -128
FLOOR(a) -128
CEILING(a) -128
a 127
ROUND(a) 127
TRUNCATE(a,0) 127
FLOOR(a) 127
CEILING(a) 127
CALL p1('smallint');
smallint
Table t2
Create Table CREATE TABLE `t2` (
`a` smallint(6) DEFAULT NULL,
`ROUND(a)` smallint(6) DEFAULT NULL,
`TRUNCATE(a,0)` smallint(6) DEFAULT NULL,
`FLOOR(a)` smallint(6) DEFAULT NULL,
`CEILING(a)` smallint(6) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1
a -32768
ROUND(a) -32768
TRUNCATE(a,0) -32768
FLOOR(a) -32768
CEILING(a) -32768
a 32767
ROUND(a) 32767
TRUNCATE(a,0) 32767
FLOOR(a) 32767
CEILING(a) 32767
CALL p1('mediumint');
mediumint
Table t2
Create Table CREATE TABLE `t2` (
`a` mediumint(9) DEFAULT NULL,
`ROUND(a)` mediumint(9) DEFAULT NULL,
`TRUNCATE(a,0)` mediumint(9) DEFAULT NULL,
`FLOOR(a)` mediumint(9) DEFAULT NULL,
`CEILING(a)` mediumint(9) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1
a -8388608
ROUND(a) -8388608
TRUNCATE(a,0) -8388608
FLOOR(a) -8388608
CEILING(a) -8388608
a 8388607
ROUND(a) 8388607
TRUNCATE(a,0) 8388607
FLOOR(a) 8388607
CEILING(a) 8388607
CALL p1('int');
int
Table t2
Create Table CREATE TABLE `t2` (
`a` int(11) DEFAULT NULL,
`ROUND(a)` int(11) DEFAULT NULL,
`TRUNCATE(a,0)` int(11) DEFAULT NULL,
`FLOOR(a)` int(11) DEFAULT NULL,
`CEILING(a)` int(11) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1
a -2147483648
ROUND(a) -2147483648
TRUNCATE(a,0) -2147483648
FLOOR(a) -2147483648
CEILING(a) -2147483648
a 2147483647
ROUND(a) 2147483647
TRUNCATE(a,0) 2147483647
FLOOR(a) 2147483647
CEILING(a) 2147483647
CALL p1('bigint');
bigint
Table t2
Create Table CREATE TABLE `t2` (
`a` bigint(20) DEFAULT NULL,
`ROUND(a)` bigint(20) DEFAULT NULL,
`TRUNCATE(a,0)` bigint(20) DEFAULT NULL,
`FLOOR(a)` bigint(20) DEFAULT NULL,
`CEILING(a)` bigint(20) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1
a -9223372036854775808
ROUND(a) -9223372036854775808
TRUNCATE(a,0) -9223372036854775808
FLOOR(a) -9223372036854775808
CEILING(a) -9223372036854775808
a 9223372036854775807
ROUND(a) 9223372036854775807
TRUNCATE(a,0) 9223372036854775807
FLOOR(a) 9223372036854775807
CEILING(a) 9223372036854775807
CALL p1('tinyint unsigned');
tinyint unsigned
Table t2
Create Table CREATE TABLE `t2` (
`a` tinyint(3) unsigned DEFAULT NULL,
`ROUND(a)` tinyint(3) unsigned DEFAULT NULL,
`TRUNCATE(a,0)` tinyint(3) unsigned DEFAULT NULL,
`FLOOR(a)` tinyint(3) unsigned DEFAULT NULL,
`CEILING(a)` tinyint(3) unsigned DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1
a 0
ROUND(a) 0
TRUNCATE(a,0) 0
FLOOR(a) 0
CEILING(a) 0
a 255
ROUND(a) 255
TRUNCATE(a,0) 255
FLOOR(a) 255
CEILING(a) 255
CALL p1('smallint unsigned');
smallint unsigned
Table t2
Create Table CREATE TABLE `t2` (
`a` smallint(5) unsigned DEFAULT NULL,
`ROUND(a)` smallint(5) unsigned DEFAULT NULL,
`TRUNCATE(a,0)` smallint(5) unsigned DEFAULT NULL,
`FLOOR(a)` smallint(5) unsigned DEFAULT NULL,
`CEILING(a)` smallint(5) unsigned DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1
a 0
ROUND(a) 0
TRUNCATE(a,0) 0
FLOOR(a) 0
CEILING(a) 0
a 65535
ROUND(a) 65535
TRUNCATE(a,0) 65535
FLOOR(a) 65535
CEILING(a) 65535
CALL p1('mediumint unsigned');
mediumint unsigned
Table t2
Create Table CREATE TABLE `t2` (
`a` mediumint(8) unsigned DEFAULT NULL,
`ROUND(a)` mediumint(8) unsigned DEFAULT NULL,
`TRUNCATE(a,0)` mediumint(8) unsigned DEFAULT NULL,
`FLOOR(a)` mediumint(8) unsigned DEFAULT NULL,
`CEILING(a)` mediumint(8) unsigned DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1
a 0
ROUND(a) 0
TRUNCATE(a,0) 0
FLOOR(a) 0
CEILING(a) 0
a 16777215
ROUND(a) 16777215
TRUNCATE(a,0) 16777215
FLOOR(a) 16777215
CEILING(a) 16777215
CALL p1('int unsigned');
int unsigned
Table t2
Create Table CREATE TABLE `t2` (
`a` int(10) unsigned DEFAULT NULL,
`ROUND(a)` int(10) unsigned DEFAULT NULL,
`TRUNCATE(a,0)` int(10) unsigned DEFAULT NULL,
`FLOOR(a)` int(10) unsigned DEFAULT NULL,
`CEILING(a)` int(10) unsigned DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1
a 0
ROUND(a) 0
TRUNCATE(a,0) 0
FLOOR(a) 0
CEILING(a) 0
a 4294967295
ROUND(a) 4294967295
TRUNCATE(a,0) 4294967295
FLOOR(a) 4294967295
CEILING(a) 4294967295
CALL p1('bigint unsigned');
bigint unsigned
Table t2
Create Table CREATE TABLE `t2` (
`a` bigint(20) unsigned DEFAULT NULL,
`ROUND(a)` bigint(20) unsigned DEFAULT NULL,
`TRUNCATE(a,0)` bigint(20) unsigned DEFAULT NULL,
`FLOOR(a)` bigint(20) unsigned DEFAULT NULL,
`CEILING(a)` bigint(20) unsigned DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1
a 0
ROUND(a) 0
TRUNCATE(a,0) 0
FLOOR(a) 0
CEILING(a) 0
a 18446744073709551615
ROUND(a) 18446744073709551615
TRUNCATE(a,0) 18446744073709551615
FLOOR(a) 18446744073709551615
CEILING(a) 18446744073709551615
DROP PROCEDURE p1;
#
# End of 10.4 tests
#

View File

@ -293,6 +293,41 @@ SELECT COUNT(*) FROM t1 WHERE a XOR a;
DROP TABLE t1;
--echo #
--echo # MDEV-23337 Rounding functions create a wrong data type for numeric input
--echo #
DELIMITER $$;
CREATE PROCEDURE p1(t VARCHAR(64))
BEGIN
SELECT t AS ``;
EXECUTE IMMEDIATE REPLACE('CREATE TABLE t1 (a TYPE)', 'TYPE', t);
INSERT IGNORE INTO t1 VALUES (-100000000000000000000000000000);
INSERT IGNORE INTO t1 VALUES (100000000000000000000000000000);
CREATE TABLE t2 AS SELECT a, ROUND(a), TRUNCATE(a,0), FLOOR(a), CEILING(a) FROM t1;
SHOW CREATE TABLE t2;
SELECT * FROM t2;
DROP TABLE t1, t2;
END;
$$
DELIMITER ;$$
--vertical_results
CALL p1('tinyint');
CALL p1('smallint');
CALL p1('mediumint');
CALL p1('int');
CALL p1('bigint');
CALL p1('tinyint unsigned');
CALL p1('smallint unsigned');
CALL p1('mediumint unsigned');
CALL p1('int unsigned');
CALL p1('bigint unsigned');
--horizontal_results
DROP PROCEDURE p1;
--echo #
--echo # End of 10.4 tests
--echo #

View File

@ -585,5 +585,32 @@ COALESCE(a) DATE(COALESCE(a))
NULL NULL
DROP TABLE t1;
#
# MDEV-23337 Rounding functions create a wrong data type for numeric input
#
CREATE TABLE t1 (a YEAR(2));
Warnings:
Note 1287 'YEAR(2)' is deprecated and will be removed in a future release. Please use YEAR(4) instead
CREATE TABLE t2 AS SELECT a, ROUND(a), TRUNCATE(a,0), FLOOR(a), CEILING(a) FROM t1;
Warnings:
Note 1287 'YEAR(2)' is deprecated and will be removed in a future release. Please use YEAR(4) instead
DESC t2;
Field Type Null Key Default Extra
a year(2) YES NULL
ROUND(a) int(2) unsigned YES NULL
TRUNCATE(a,0) int(2) unsigned YES NULL
FLOOR(a) int(2) unsigned YES NULL
CEILING(a) int(2) unsigned YES NULL
DROP TABLE t2,t1;
CREATE TABLE t1 (a YEAR(4));
CREATE TABLE t2 AS SELECT a, ROUND(a), TRUNCATE(a,0), FLOOR(a), CEILING(a) FROM t1;
DESC t2;
Field Type Null Key Default Extra
a year(4) YES NULL
ROUND(a) int(4) unsigned YES NULL
TRUNCATE(a,0) int(4) unsigned YES NULL
FLOOR(a) int(4) unsigned YES NULL
CEILING(a) int(4) unsigned YES NULL
DROP TABLE t2,t1;
#
# End of 10.4 tests
#

View File

@ -326,6 +326,21 @@ INSERT INTO t1 VALUES (NULL);
SELECT COALESCE(a), DATE(COALESCE(a)) FROM t1;
DROP TABLE t1;
--echo #
--echo # MDEV-23337 Rounding functions create a wrong data type for numeric input
--echo #
CREATE TABLE t1 (a YEAR(2));
CREATE TABLE t2 AS SELECT a, ROUND(a), TRUNCATE(a,0), FLOOR(a), CEILING(a) FROM t1;
DESC t2;
DROP TABLE t2,t1;
CREATE TABLE t1 (a YEAR(4));
CREATE TABLE t2 AS SELECT a, ROUND(a), TRUNCATE(a,0), FLOOR(a), CEILING(a) FROM t1;
DESC t2;
DROP TABLE t2,t1;
--echo #
--echo # End of 10.4 tests
--echo #

View File

@ -2446,8 +2446,24 @@ void Item_func_round::fix_arg_datetime()
}
void Item_func_round::fix_arg_int()
/**
Calculate data type and attributes for INT-alike input.
@param [IN] preferred - The preferred data type handler for simple cases
such as ROUND(x) and TRUNCATE(x,0), when the input
is short enough to fit into an integer type
(without extending to DECIMAL).
- If `preferred` is not NULL, then the code tries
to preserve the given data type handler and
data type attributes of the argument.
- If `preferred` is NULL, then the code fully
calculates attributes using
args[0]->decimal_precision() and chooses between
INT and BIGINT, depending on attributes.
*/
void Item_func_round::fix_arg_int(const Type_handler *preferred)
{
DBUG_ASSERT(args[0]->decimals == 0);
if (args[1]->const_item())
{
Longlong_hybrid val1= args[1]->to_longlong_hybrid();
@ -2456,13 +2472,35 @@ void Item_func_round::fix_arg_int()
else if ((!val1.to_uint(DECIMAL_MAX_SCALE) && truncate) ||
args[0]->decimal_precision() < DECIMAL_LONGLONG_DIGITS)
{
// Here we can keep INT_RESULT
// Length can increase in some cases: ROUND(9,-1) -> 10
int length_can_increase= MY_TEST(!truncate && val1.neg());
max_length= args[0]->decimal_precision() + length_can_increase;
// Here we can keep INT_RESULT
unsigned_flag= args[0]->unsigned_flag;
decimals= 0;
set_handler(type_handler_long_or_longlong());
if (preferred)
{
Type_std_attributes::set(args[0]);
if (!length_can_increase)
{
// Preserve the exact data type and attributes
set_handler(preferred);
}
else
{
max_length++;
set_handler(type_handler_long_or_longlong());
}
}
else
{
/*
This branch is currently used for hex hybrid only.
It's known to be unsigned. So sign length is 0.
*/
DBUG_ASSERT(args[0]->unsigned_flag); // no needs to add sign length
max_length= args[0]->decimal_precision() + length_can_increase;
unsigned_flag= true;
decimals= 0;
set_handler(type_handler_long_or_longlong());
}
}
else
fix_length_and_dec_decimal(val1.to_uint(DECIMAL_MAX_SCALE));

View File

@ -1774,7 +1774,7 @@ public:
return NULL;
}
void fix_arg_decimal();
void fix_arg_int();
void fix_arg_int(const Type_handler *preferred);
void fix_arg_double();
void fix_arg_time();
void fix_arg_datetime();

View File

@ -5659,7 +5659,15 @@ bool Type_handler_row::
bool Type_handler_int_result::
Item_func_round_fix_length_and_dec(Item_func_round *item) const
{
item->fix_arg_int();
item->fix_arg_int(this);
return false;
}
bool Type_handler_year::
Item_func_round_fix_length_and_dec(Item_func_round *item) const
{
item->fix_arg_int(&type_handler_long); // 10.5 merge: fix to type_handler_ulong
return false;
}
@ -5667,7 +5675,7 @@ bool Type_handler_int_result::
bool Type_handler_hex_hybrid::
Item_func_round_fix_length_and_dec(Item_func_round *item) const
{
item->fix_arg_int();
item->fix_arg_int(NULL);
return false;
}
@ -5766,7 +5774,17 @@ bool Type_handler_row::
bool Type_handler_int_result::
Item_func_int_val_fix_length_and_dec(Item_func_int_val *item) const
{
item->fix_length_and_dec_int_or_decimal();
item->Type_std_attributes::set(item->arguments()[0]);
item->set_handler(this);
return false;
}
bool Type_handler_year::
Item_func_int_val_fix_length_and_dec(Item_func_int_val *item) const
{
item->Type_std_attributes::set(item->arguments()[0]);
item->set_handler(&type_handler_long);
return false;
}

View File

@ -5124,6 +5124,8 @@ public:
const Column_definition_attributes *attr,
uint32 flags) const;
Item_cache *Item_get_cache(THD *thd, const Item *item) const;
bool Item_func_round_fix_length_and_dec(Item_func_round *) const;
bool Item_func_int_val_fix_length_and_dec(Item_func_int_val *) const;
void Item_get_date(THD *thd, Item *item, Temporal::Warn *warn,
MYSQL_TIME *ltime, date_mode_t fuzzydate) const;
void Item_func_hybrid_field_type_get_date(THD *,