Commit a874b6c4 authored by Alexander Barkov's avatar Alexander Barkov

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.
parent c3958ae4
......@@ -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),
......
......@@ -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
#
......@@ -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 #
......@@ -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
#
......@@ -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 #
......@@ -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));
......
......@@ -1774,7 +1774,7 @@ class Item_func_round :public Item_func_hybrid_field_type
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();
......
......@@ -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;
}
......
......@@ -5124,6 +5124,8 @@ class Type_handler_year: public Type_handler_int_result
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 *,
......
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