Commit e63311c2 authored by Alexander Barkov's avatar Alexander Barkov

MDEV-33496 Out of range error in AVG(YEAR(datetime)) due to a wrong data type

Functions extracting non-negative datetime components:

- YEAR(dt),        EXTRACT(YEAR FROM dt)
- QUARTER(td),     EXTRACT(QUARTER FROM dt)
- MONTH(dt),       EXTRACT(MONTH FROM dt)
- WEEK(dt),        EXTRACT(WEEK FROM dt)
- HOUR(dt),
- MINUTE(dt),
- SECOND(dt),
- MICROSECOND(dt),
- DAYOFYEAR(dt)
- EXTRACT(YEAR_MONTH FROM dt)

did not set their max_length properly, so in the DECIMAL
context they created a too small DECIMAL column, which
led to the 'Out of range value' error.

The problem is that most of these functions historically
returned the signed INT data type.

There were two simple ways to fix these functions:
1. Add +1 to max_length.
   But this would also change their size in the string context
   and create too long VARCHAR columns, with +1 excessive size.

2. Preserve max_length, but change the data type from INT to INT UNSIGNED.
   But this would break backward compatibility.
   Also, using UNSIGNED is generally not desirable,
   it's better to stay with signed when possible.

This fix implements another solution, which it makes all these functions
work well in all contexts: int, decimal, string.

Fix details:

- Adding a new special class Type_handler_long_ge0 - the data type
  handler for expressions which:
  * should look like normal signed INT
  * but which known not to return negative values
  Expressions handled by Type_handler_long_ge0 store in Item::max_length
  only the number of digits, without adding +1 for the sign.

- Fixing Item_extract to use Type_handler_long_ge0
  for non-negative datetime components:
   YEAR, YEAR_MONTH, QUARTER, MONTH, WEEK

- Adding a new abstract class Item_long_ge0_func, for functions
  returning non-negative datetime components.
  Item_long_ge0_func uses Type_handler_long_ge0 as the type handler.
  The class hierarchy now looks as follows:

Item_long_ge0_func
  Item_long_func_date_field
    Item_func_to_days
    Item_func_dayofmonth
    Item_func_dayofyear
    Item_func_quarter
    Item_func_year
  Item_long_func_time_field
    Item_func_hour
    Item_func_minute
    Item_func_second
    Item_func_microsecond

- Cleanup: EXTRACT(QUARTER FROM dt) created an excessive VARCHAR column
  in string context. Changing its length from 2 to 1.
parent d57c44f6
This diff is collapsed.
......@@ -263,3 +263,254 @@ SELECT
FROM t1;
DROP TABLE t1;
--enable_view_protocol
--echo #
--echo # Start of 10.5 tests
--echo #
--echo #
--echo # MDEV-33496 Out of range error in AVG(YEAR(datetime)) due to a wrong data type
--echo #
let select01=SELECT ?, CAST(? AS UNSIGNED), CAST(? AS SIGNED), ABS(?), ROUND(?), -?, ROUND(?,-1), ?+0, ?+0.0, CONCAT(?), LEAST(?,?), COALESCE(?), COALESCE(?,CAST(1 AS SIGNED)), COALESCE(?,CAST(1 AS UNSIGNED)), @a:=?;
let pcount01=16;
let select02=SELECT AVG(?), MIN(?), MAX(?), GROUP_CONCAT(?);
let pcount02=4;
let ts=TIMESTAMP'2001-12-13 10:20:30.999999';
eval CREATE FUNCTION select01() RETURNS TEXT RETURN '$select01';
eval CREATE FUNCTION select02() RETURNS TEXT RETURN '$select02';
CREATE TABLE t1 (a DATETIME(6));
INSERT INTO t1 VALUES ('2001-12-31 10:20:30.999999');
DELIMITER $$;
CREATE FUNCTION params(expr TEXT, count INT) RETURNS TEXT
BEGIN
RETURN CONCAT(expr, REPEAT(CONCAT(', ', expr), count-1));
END;
$$
CREATE PROCEDURE show_drop()
BEGIN
SELECT TABLE_NAME, COLUMN_TYPE, COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA='test'
AND TABLE_NAME IN ('t1e_nm','t2e_nm','t1f_nm','t2f_nm',
't1e_ps','t1f_ps','t2e_ps','t2f_ps')
ORDER BY LEFT(TABLE_NAME, 2), ORDINAL_POSITION, TABLE_NAME;
FOR rec IN (SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA='test'
AND TABLE_NAME IN ('t1e_nm','t2e_nm','t1f_nm','t2f_nm',
't1e_ps','t1f_ps','t2e_ps','t2f_ps'))
DO
EXECUTE IMMEDIATE CONCAT('DROP TABLE ', rec.TABLE_NAME);
END FOR;
END;
$$
CREATE PROCEDURE p1(unit VARCHAR(32))
BEGIN
DECLARE do_extract BOOL DEFAULT unit NOT IN('DAYOFYEAR');
DECLARE query01 TEXT DEFAULT
CONCAT('CREATE TABLE t2 AS ', select01(), ' FROM t1');
DECLARE query02 TEXT DEFAULT
CONCAT('CREATE TABLE t2 AS ', select02(), ' FROM t1');
IF (do_extract)
THEN
EXECUTE IMMEDIATE REPLACE(REPLACE(query01,'t2','t1e_nm'),'?', CONCAT('EXTRACT(',unit,' FROM a)'));
EXECUTE IMMEDIATE REPLACE(REPLACE(query02,'t2','t2e_nm'),'?', CONCAT('EXTRACT(',unit,' FROM a)'));
END IF;
EXECUTE IMMEDIATE REPLACE(REPLACE(query01,'t2','t1f_nm'),'?', CONCAT(unit,'(a)'));
EXECUTE IMMEDIATE REPLACE(REPLACE(query02,'t2','t2f_nm'),'?', CONCAT(unit,'(a)'));
END;
$$
DELIMITER ;$$
--echo
--echo
--echo # EXTRACT(YEAR FROM expr) and YEAR(expr) are equivalent
CALL p1('YEAR');
let extr=EXTRACT(YEAR FROM $ts);
let func=YEAR($ts);
let extr01=`SELECT params("$extr", $pcount01) AS p`;
let func01=`SELECT params("$func", $pcount01) AS p`;
let extr02=`SELECT params("$extr", $pcount02) AS p`;
let func02=`SELECT params("$func", $pcount02) AS p`;
eval EXECUTE IMMEDIATE 'CREATE TABLE t1e_ps AS $select01' USING $extr01;
eval EXECUTE IMMEDIATE 'CREATE TABLE t1f_ps AS $select01' USING $func01;
eval EXECUTE IMMEDIATE 'CREATE TABLE t2e_ps AS $select02' USING $extr02;
eval EXECUTE IMMEDIATE 'CREATE TABLE t2f_ps AS $select02' USING $func02;
CALL show_drop;
--echo
--echo
--echo # EXTRACT(QUARTER FROM expr) and QUARTER(expr) are equavalent
CALL p1('QUARTER');
let extr=EXTRACT(QUARTER FROM $ts);
let func=QUARTER($ts);
let extr01=`SELECT params("$extr", $pcount01) AS p`;
let func01=`SELECT params("$func", $pcount01) AS p`;
let extr02=`SELECT params("$extr", $pcount02) AS p`;
let func02=`SELECT params("$func", $pcount02) AS p`;
eval EXECUTE IMMEDIATE 'CREATE TABLE t1e_ps AS $select01' USING $extr01;
eval EXECUTE IMMEDIATE 'CREATE TABLE t1f_ps AS $select01' USING $func01;
eval EXECUTE IMMEDIATE 'CREATE TABLE t2e_ps AS $select02' USING $extr02;
eval EXECUTE IMMEDIATE 'CREATE TABLE t2f_ps AS $select02' USING $func02;
CALL show_drop;
--echo
--echo
--echo # EXTRACT(MONTH FROM expr) and MONTH(expr) are equavalent
CALL p1('MONTH');
let extr=EXTRACT(MONTH FROM $ts);
let func=MONTH($ts);
let extr01=`SELECT params("$extr", $pcount01) AS p`;
let func01=`SELECT params("$func", $pcount01) AS p`;
let extr02=`SELECT params("$extr", $pcount02) AS p`;
let func02=`SELECT params("$func", $pcount02) AS p`;
eval EXECUTE IMMEDIATE 'CREATE TABLE t1e_ps AS $select01' USING $extr01;
eval EXECUTE IMMEDIATE 'CREATE TABLE t1f_ps AS $select01' USING $func01;
eval EXECUTE IMMEDIATE 'CREATE TABLE t2e_ps AS $select02' USING $extr02;
eval EXECUTE IMMEDIATE 'CREATE TABLE t2f_ps AS $select02' USING $func02;
CALL show_drop;
--echo
--echo
--echo # EXTRACT(WEEK FROM expr) and WEEK(expr) are equavalent
CALL p1('WEEK');
let extr=EXTRACT(WEEK FROM $ts);
let func=WEEK($ts);
let extr01=`SELECT params("$extr", $pcount01) AS p`;
let func01=`SELECT params("$func", $pcount01) AS p`;
let extr02=`SELECT params("$extr", $pcount02) AS p`;
let func02=`SELECT params("$func", $pcount02) AS p`;
eval EXECUTE IMMEDIATE 'CREATE TABLE t1e_ps AS $select01' USING $extr01;
eval EXECUTE IMMEDIATE 'CREATE TABLE t1f_ps AS $select01' USING $func01;
eval EXECUTE IMMEDIATE 'CREATE TABLE t2e_ps AS $select02' USING $extr02;
eval EXECUTE IMMEDIATE 'CREATE TABLE t2f_ps AS $select02' USING $func02;
CALL show_drop;
--echo
--echo
--echo # EXTRACT(DAY FROM expr) returns hours/24 and includes the sign for TIME
--echo # DAY(expr) returns the DD part of CAST(expr AS DATETIME)
CALL p1('DAY');
let extr=EXTRACT(DAY FROM $ts);
let func=DAY($ts);
let extr01=`SELECT params("$extr", $pcount01) AS p`;
let func01=`SELECT params("$func", $pcount01) AS p`;
let extr02=`SELECT params("$extr", $pcount02) AS p`;
let func02=`SELECT params("$func", $pcount02) AS p`;
eval EXECUTE IMMEDIATE 'CREATE TABLE t1e_ps AS $select01' USING $extr01;
eval EXECUTE IMMEDIATE 'CREATE TABLE t1f_ps AS $select01' USING $func01;
eval EXECUTE IMMEDIATE 'CREATE TABLE t2e_ps AS $select02' USING $extr02;
eval EXECUTE IMMEDIATE 'CREATE TABLE t2f_ps AS $select02' USING $func02;
CALL show_drop;
--echo
--echo
--echo # EXTRACT(HOUR FROM expr) returns hours%24 and includes the sign for TIME
--echo # HOUR(expr) returns the hh part of CAST(expr AS DATETIME)
CALL p1('HOUR');
let extr=EXTRACT(HOUR FROM $ts);
let func=HOUR($ts);
let extr01=`SELECT params("$extr", $pcount01) AS p`;
let func01=`SELECT params("$func", $pcount01) AS p`;
let extr02=`SELECT params("$extr", $pcount02) AS p`;
let func02=`SELECT params("$func", $pcount02) AS p`;
eval EXECUTE IMMEDIATE 'CREATE TABLE t1e_ps AS $select01' USING $extr01;
eval EXECUTE IMMEDIATE 'CREATE TABLE t1f_ps AS $select01' USING $func01;
eval EXECUTE IMMEDIATE 'CREATE TABLE t2e_ps AS $select02' USING $extr02;
eval EXECUTE IMMEDIATE 'CREATE TABLE t2f_ps AS $select02' USING $func02;
CALL show_drop;
--echo
--echo
--echo # EXTRACT(MINUTE FROM expr) includes the sign for TIME
--echo # MINUTE(expr) returns the absolute value
CALL p1('MINUTE');
let extr=EXTRACT(MINUTE FROM $ts);
let func=MINUTE($ts);
let extr01=`SELECT params("$extr", $pcount01) AS p`;
let func01=`SELECT params("$func", $pcount01) AS p`;
let extr02=`SELECT params("$extr", $pcount02) AS p`;
let func02=`SELECT params("$func", $pcount02) AS p`;
eval EXECUTE IMMEDIATE 'CREATE TABLE t1e_ps AS $select01' USING $extr01;
eval EXECUTE IMMEDIATE 'CREATE TABLE t1f_ps AS $select01' USING $func01;
eval EXECUTE IMMEDIATE 'CREATE TABLE t2e_ps AS $select02' USING $extr02;
eval EXECUTE IMMEDIATE 'CREATE TABLE t2f_ps AS $select02' USING $func02;
CALL show_drop;
--echo
--echo
--echo # EXTRACT(SECONDS FROM expr) includes the sign for TIME
--echo # SECONDS(expr) returns the absolute value
CALL p1('SECOND');
let extr=EXTRACT(SECOND FROM $ts);
let func=SECOND($ts);
let extr01=`SELECT params("$extr", $pcount01) AS p`;
let func01=`SELECT params("$func", $pcount01) AS p`;
let extr02=`SELECT params("$extr", $pcount02) AS p`;
let func02=`SELECT params("$func", $pcount02) AS p`;
eval EXECUTE IMMEDIATE 'CREATE TABLE t1e_ps AS $select01' USING $extr01;
eval EXECUTE IMMEDIATE 'CREATE TABLE t1f_ps AS $select01' USING $func01;
eval EXECUTE IMMEDIATE 'CREATE TABLE t2e_ps AS $select02' USING $extr02;
eval EXECUTE IMMEDIATE 'CREATE TABLE t2f_ps AS $select02' USING $func02;
CALL show_drop;
--echo
--echo
--echo # EXTRACT(MICROSECONDS FROM expr) includes the sign for TIME
--echo # MICROSECONDS(expr) returns the absolute value
CALL p1('MICROSECOND');
let extr=EXTRACT(MICROSECOND FROM $ts);
let func=MICROSECOND($ts);
let extr01=`SELECT params("$extr", $pcount01) AS p`;
let func01=`SELECT params("$func", $pcount01) AS p`;
let extr02=`SELECT params("$extr", $pcount02) AS p`;
let func02=`SELECT params("$func", $pcount02) AS p`;
eval EXECUTE IMMEDIATE 'CREATE TABLE t1e_ps AS $select01' USING $extr01;
eval EXECUTE IMMEDIATE 'CREATE TABLE t1f_ps AS $select01' USING $func01;
eval EXECUTE IMMEDIATE 'CREATE TABLE t2e_ps AS $select02' USING $extr02;
eval EXECUTE IMMEDIATE 'CREATE TABLE t2f_ps AS $select02' USING $func02;
CALL show_drop;
--echo
--echo
--echo # DAYOFYEAR
CALL p1('DAYOFYEAR');
let func=DAYOFYEAR($ts);
let func01=`SELECT params("$func", $pcount01) AS p`;
let func02=`SELECT params("$func", $pcount02) AS p`;
eval EXECUTE IMMEDIATE 'CREATE TABLE t1f_ps AS $select01' USING $func01;
eval EXECUTE IMMEDIATE 'CREATE TABLE t2f_ps AS $select02' USING $func02;
CALL show_drop;
DROP TABLE t1;
DROP PROCEDURE p1;
DROP PROCEDURE show_drop;
DROP FUNCTION params;
DROP FUNCTION select01;
DROP FUNCTION select02;
--echo #
--echo # End of 10.5 tests
--echo #
......@@ -3142,7 +3142,7 @@ Catalog Database Table Table_alias Column Column_alias Type Length Max length Is
def test t1 t1 a a 12 26 26 Y 128 6 63
def EXTRACT(YEAR FROM a) 3 4 4 Y 32896 0 63
def EXTRACT(YEAR_MONTH FROM a) 3 6 6 Y 32896 0 63
def EXTRACT(QUARTER FROM a) 3 2 1 Y 32896 0 63
def EXTRACT(QUARTER FROM a) 3 1 1 Y 32896 0 63
def EXTRACT(MONTH FROM a) 3 2 2 Y 32896 0 63
def EXTRACT(WEEK FROM a) 3 2 2 Y 32896 0 63
def EXTRACT(DAY FROM a) 3 3 2 Y 32896 0 63
......@@ -3230,11 +3230,11 @@ SHOW CREATE TABLE t2;
Table Create Table
t2 CREATE TABLE `t2` (
`a` datetime(6) DEFAULT NULL,
`EXTRACT(YEAR FROM a)` int(4) DEFAULT NULL,
`EXTRACT(YEAR_MONTH FROM a)` int(6) DEFAULT NULL,
`EXTRACT(YEAR FROM a)` int(5) DEFAULT NULL,
`EXTRACT(YEAR_MONTH FROM a)` int(7) DEFAULT NULL,
`EXTRACT(QUARTER FROM a)` int(2) DEFAULT NULL,
`EXTRACT(MONTH FROM a)` int(2) DEFAULT NULL,
`EXTRACT(WEEK FROM a)` int(2) DEFAULT NULL,
`EXTRACT(MONTH FROM a)` int(3) DEFAULT NULL,
`EXTRACT(WEEK FROM a)` int(3) DEFAULT NULL,
`EXTRACT(DAY FROM a)` int(3) DEFAULT NULL,
`EXTRACT(DAY_HOUR FROM a)` int(5) DEFAULT NULL,
`EXTRACT(DAY_MINUTE FROM a)` int(7) DEFAULT NULL,
......@@ -3281,7 +3281,7 @@ Catalog Database Table Table_alias Column Column_alias Type Length Max length Is
def test t1 t1 a a 11 17 17 Y 128 6 63
def EXTRACT(YEAR FROM a) 3 4 1 Y 32896 0 63
def EXTRACT(YEAR_MONTH FROM a) 3 6 1 Y 32896 0 63
def EXTRACT(QUARTER FROM a) 3 2 1 Y 32896 0 63
def EXTRACT(QUARTER FROM a) 3 1 1 Y 32896 0 63
def EXTRACT(MONTH FROM a) 3 2 1 Y 32896 0 63
def EXTRACT(WEEK FROM a) 3 2 9 Y 32896 0 63
def EXTRACT(DAY FROM a) 3 3 3 Y 32896 0 63
......@@ -3411,11 +3411,11 @@ SHOW CREATE TABLE t2;
Table Create Table
t2 CREATE TABLE `t2` (
`a` time(6) DEFAULT NULL,
`EXTRACT(YEAR FROM a)` int(4) DEFAULT NULL,
`EXTRACT(YEAR_MONTH FROM a)` int(6) DEFAULT NULL,
`EXTRACT(YEAR FROM a)` int(5) DEFAULT NULL,
`EXTRACT(YEAR_MONTH FROM a)` int(7) DEFAULT NULL,
`EXTRACT(QUARTER FROM a)` int(2) DEFAULT NULL,
`EXTRACT(MONTH FROM a)` int(2) DEFAULT NULL,
`EXTRACT(WEEK FROM a)` int(2) DEFAULT NULL,
`EXTRACT(MONTH FROM a)` int(3) DEFAULT NULL,
`EXTRACT(WEEK FROM a)` int(3) DEFAULT NULL,
`EXTRACT(DAY FROM a)` int(3) DEFAULT NULL,
`EXTRACT(DAY_HOUR FROM a)` int(5) DEFAULT NULL,
`EXTRACT(DAY_MINUTE FROM a)` int(7) DEFAULT NULL,
......
......@@ -1798,7 +1798,7 @@ t5 CREATE TABLE `t5` (
`param09` longtext DEFAULT NULL,
`const10` bigint(17) DEFAULT NULL,
`param10` bigint(20) DEFAULT NULL,
`const11` int(4) DEFAULT NULL,
`const11` int(5) DEFAULT NULL,
`param11` bigint(20) DEFAULT NULL,
`const12` binary(0) DEFAULT NULL,
`param12` bigint(20) DEFAULT NULL,
......@@ -1828,7 +1828,7 @@ def test t5 t5 const09 const09 12 19 19 Y 128 0 63
def test t5 t5 param09 param09 252 4294967295 19 Y 16 0 8
def test t5 t5 const10 const10 8 17 9 Y 32768 0 63
def test t5 t5 param10 param10 8 20 9 Y 32768 0 63
def test t5 t5 const11 const11 3 4 4 Y 32768 0 63
def test t5 t5 const11 const11 3 5 4 Y 32768 0 63
def test t5 t5 param11 param11 8 20 4 Y 32768 0 63
def test t5 t5 const12 const12 254 0 0 Y 128 0 63
def test t5 t5 param12 param12 8 20 0 Y 32768 0 63
......
......@@ -1781,7 +1781,7 @@ t5 CREATE TABLE `t5` (
`param09` longtext DEFAULT NULL,
`const10` bigint(17) DEFAULT NULL,
`param10` bigint(20) DEFAULT NULL,
`const11` int(4) DEFAULT NULL,
`const11` int(5) DEFAULT NULL,
`param11` bigint(20) DEFAULT NULL,
`const12` binary(0) DEFAULT NULL,
`param12` bigint(20) DEFAULT NULL,
......@@ -1811,7 +1811,7 @@ def test t5 t5 const09 const09 12 19 19 Y 128 0 63
def test t5 t5 param09 param09 252 4294967295 19 Y 16 0 8
def test t5 t5 const10 const10 8 17 9 Y 32768 0 63
def test t5 t5 param10 param10 8 20 9 Y 32768 0 63
def test t5 t5 const11 const11 3 4 4 Y 32768 0 63
def test t5 t5 const11 const11 3 5 4 Y 32768 0 63
def test t5 t5 param11 param11 8 20 4 Y 32768 0 63
def test t5 t5 const12 const12 254 0 0 Y 128 0 63
def test t5 t5 param12 param12 8 20 0 Y 32768 0 63
......
......@@ -1782,7 +1782,7 @@ t5 CREATE TABLE `t5` (
`param09` longtext DEFAULT NULL,
`const10` bigint(17) DEFAULT NULL,
`param10` bigint(20) DEFAULT NULL,
`const11` int(4) DEFAULT NULL,
`const11` int(5) DEFAULT NULL,
`param11` bigint(20) DEFAULT NULL,
`const12` binary(0) DEFAULT NULL,
`param12` bigint(20) DEFAULT NULL,
......@@ -1812,7 +1812,7 @@ def test t5 t5 const09 const09 12 19 19 Y 128 0 63
def test t5 t5 param09 param09 252 4294967295 19 Y 16 0 8
def test t5 t5 const10 const10 8 17 9 Y 32768 0 63
def test t5 t5 param10 param10 8 20 9 Y 32768 0 63
def test t5 t5 const11 const11 3 4 4 Y 32768 0 63
def test t5 t5 const11 const11 3 5 4 Y 32768 0 63
def test t5 t5 param11 param11 8 20 4 Y 32768 0 63
def test t5 t5 const12 const12 254 0 0 Y 128 0 63
def test t5 t5 param12 param12 8 20 0 Y 32768 0 63
......
......@@ -1719,7 +1719,7 @@ t5 CREATE TABLE `t5` (
`param09` longtext DEFAULT NULL,
`const10` bigint(17) DEFAULT NULL,
`param10` bigint(20) DEFAULT NULL,
`const11` int(4) DEFAULT NULL,
`const11` int(5) DEFAULT NULL,
`param11` bigint(20) DEFAULT NULL,
`const12` binary(0) DEFAULT NULL,
`param12` bigint(20) DEFAULT NULL,
......@@ -1749,7 +1749,7 @@ def test t5 t5 const09 const09 12 19 19 Y 128 0 63
def test t5 t5 param09 param09 252 4294967295 19 Y 16 0 8
def test t5 t5 const10 const10 8 17 9 Y 32768 0 63
def test t5 t5 param10 param10 8 20 9 Y 32768 0 63
def test t5 t5 const11 const11 3 4 4 Y 32768 0 63
def test t5 t5 const11 const11 3 5 4 Y 32768 0 63
def test t5 t5 param11 param11 8 20 4 Y 32768 0 63
def test t5 t5 const12 const12 254 0 0 Y 128 0 63
def test t5 t5 param12 param12 8 20 0 Y 32768 0 63
......@@ -5087,7 +5087,7 @@ t5 CREATE TABLE `t5` (
`param09` longtext DEFAULT NULL,
`const10` bigint(17) DEFAULT NULL,
`param10` bigint(20) DEFAULT NULL,
`const11` int(4) DEFAULT NULL,
`const11` int(5) DEFAULT NULL,
`param11` bigint(20) DEFAULT NULL,
`const12` binary(0) DEFAULT NULL,
`param12` bigint(20) DEFAULT NULL,
......@@ -5117,7 +5117,7 @@ def test t5 t5 const09 const09 12 19 19 Y 128 0 63
def test t5 t5 param09 param09 252 4294967295 19 Y 16 0 8
def test t5 t5 const10 const10 8 17 9 Y 32768 0 63
def test t5 t5 param10 param10 8 20 9 Y 32768 0 63
def test t5 t5 const11 const11 3 4 4 Y 32768 0 63
def test t5 t5 const11 const11 3 5 4 Y 32768 0 63
def test t5 t5 param11 param11 8 20 4 Y 32768 0 63
def test t5 t5 const12 const12 254 0 0 Y 128 0 63
def test t5 t5 param12 param12 8 20 0 Y 32768 0 63
......
......@@ -1798,7 +1798,7 @@ t5 CREATE TABLE `t5` (
`param09` longtext DEFAULT NULL,
`const10` bigint(17) DEFAULT NULL,
`param10` bigint(20) DEFAULT NULL,
`const11` int(4) DEFAULT NULL,
`const11` int(5) DEFAULT NULL,
`param11` bigint(20) DEFAULT NULL,
`const12` binary(0) DEFAULT NULL,
`param12` bigint(20) DEFAULT NULL,
......@@ -1828,7 +1828,7 @@ def test t5 t5 const09 const09 12 19 19 Y 128 0 63
def test t5 t5 param09 param09 252 4294967295 19 Y 16 0 8
def test t5 t5 const10 const10 8 17 9 Y 32768 0 63
def test t5 t5 param10 param10 8 20 9 Y 32768 0 63
def test t5 t5 const11 const11 3 4 4 Y 32768 0 63
def test t5 t5 const11 const11 3 5 4 Y 32768 0 63
def test t5 t5 param11 param11 8 20 4 Y 32768 0 63
def test t5 t5 const12 const12 254 0 0 Y 128 0 63
def test t5 t5 param12 param12 8 20 0 Y 32768 0 63
......
......@@ -1918,6 +1918,18 @@ void Item_func_abs::fix_length_and_dec_int()
set_handler(type_handler_long_or_longlong());
}
void Item_func_abs::fix_length_and_dec_sint_ge0()
{
/*
We're converting slong_ge0 to slong/slonglong.
Add one character for the sign into max_length.
*/
max_length= args[0]->decimal_precision() + 1/*sign*/;
DBUG_ASSERT(!args[0]->unsigned_flag);
unsigned_flag= false;
set_handler(type_handler_long_or_longlong());
}
void Item_func_abs::fix_length_and_dec_double()
{
......@@ -2594,6 +2606,22 @@ void Item_func_round::fix_arg_int(const Type_handler *preferred,
}
void Item_func_round::fix_arg_slong_ge0()
{
DBUG_ASSERT(!args[0]->unsigned_flag);
DBUG_ASSERT(args[0]->decimals == 0);
Type_std_attributes::set(args[0]);
/*
We're converting the data type from slong_ge0 to slong/slonglong.
Add one character for the sign,
to change max_length notation from "max_length digits" to
"max_length-1 digits and the sign".
*/
max_length+= 1/*sign*/ + test_if_length_can_increase();
set_handler(type_handler_long_or_longlong());
}
void Item_func_round::fix_arg_hex_hybrid()
{
DBUG_ASSERT(args[0]->decimals == 0);
......@@ -4801,7 +4829,9 @@ Item_func_set_user_var::fix_length_and_dec()
if (args[0]->collation.derivation == DERIVATION_NUMERIC)
{
collation.set(DERIVATION_NUMERIC);
fix_length_and_charset(args[0]->max_char_length(), &my_charset_numeric);
uint sign_length= args[0]->type_handler() == &type_handler_slong_ge0 ? 1: 0;
fix_length_and_charset(args[0]->max_char_length() + sign_length,
&my_charset_numeric);
}
else
{
......
......@@ -1239,6 +1239,24 @@ class Item_long_func: public Item_int_func
};
class Item_long_ge0_func: public Item_int_func
{
public:
Item_long_ge0_func(THD *thd): Item_int_func(thd) { }
Item_long_ge0_func(THD *thd, Item *a): Item_int_func(thd, a) {}
Item_long_ge0_func(THD *thd, Item *a, Item *b): Item_int_func(thd, a, b) {}
Item_long_ge0_func(THD *thd, Item *a, Item *b, Item *c): Item_int_func(thd, a, b, c) {}
Item_long_ge0_func(THD *thd, List<Item> &list): Item_int_func(thd, list) { }
Item_long_ge0_func(THD *thd, Item_long_ge0_func *item) :Item_int_func(thd, item) {}
const Type_handler *type_handler() const
{
DBUG_ASSERT(!unsigned_flag);
return &type_handler_slong_ge0;
}
bool fix_length_and_dec() { max_length= 10; return FALSE; }
};
class Item_func_hash: public Item_int_func
{
public:
......@@ -1362,6 +1380,13 @@ class Item_func_signed :public Item_int_func
{
fix_char_length(MAX_BIGINT_WIDTH);
}
void fix_length_and_dec_sint_ge0()
{
uint32 digits= args[0]->decimal_precision();
DBUG_ASSERT(digits > 0);
DBUG_ASSERT(digits <= MY_INT64_NUM_DECIMAL_DIGITS);
fix_char_length(digits + (unsigned_flag ? 0 : 1/*sign*/));
}
void fix_length_and_dec_generic()
{
uint32 char_length= MY_MIN(args[0]->max_char_length(),
......@@ -1723,6 +1748,7 @@ class Item_func_abs :public Item_func_num1
my_decimal *decimal_op(my_decimal *);
const char *func_name() const { return "abs"; }
void fix_length_and_dec_int();
void fix_length_and_dec_sint_ge0();
void fix_length_and_dec_double();
void fix_length_and_dec_decimal();
bool fix_length_and_dec();
......@@ -1981,6 +2007,7 @@ class Item_func_round :public Item_func_hybrid_field_type
void fix_arg_int(const Type_handler *preferred,
const Type_std_attributes *preferred_attributes,
bool use_decimal_on_length_increase);
void fix_arg_slong_ge0();
void fix_arg_hex_hybrid();
void fix_arg_double();
void fix_arg_time();
......
......@@ -1205,6 +1205,21 @@ bool Item_sum_hybrid::fix_length_and_dec_numeric(const Type_handler *handler)
}
bool Item_sum_hybrid::fix_length_and_dec_sint_ge0()
{
// We don't have Item_field's of "ge0" type handlers.
DBUG_ASSERT(args[0]->real_item()->type() != FIELD_ITEM);
Type_std_attributes::set(args[0]);
/*
We're converting from e.g. slong_ge0 to slonglong
and need to add one extra character for the sign.
*/
max_length++;
set_handler(&type_handler_slonglong);
return false;
}
/**
MAX(str_field) converts ENUM/SET to CHAR, and preserve all other types
for Fields.
......
......@@ -1115,6 +1115,7 @@ class Item_sum_hybrid : public Item_sum,
{ return Type_handler_hybrid_field_type::type_handler(); }
bool fix_length_and_dec_generic();
bool fix_length_and_dec_numeric(const Type_handler *h);
bool fix_length_and_dec_sint_ge0();
bool fix_length_and_dec_string();
};
......
......@@ -2142,7 +2142,7 @@ bool Item_extract::fix_length_and_dec()
switch (int_type) {
case INTERVAL_YEAR: set_date_length(4); break; // YYYY
case INTERVAL_YEAR_MONTH: set_date_length(6); break; // YYYYMM
case INTERVAL_QUARTER: set_date_length(2); break; // 1..4
case INTERVAL_QUARTER: set_date_length(1); break; // 1..4
case INTERVAL_MONTH: set_date_length(2); break; // MM
case INTERVAL_WEEK: set_date_length(2); break; // 0..52
case INTERVAL_DAY: set_day_length(daylen); break; // DD
......
......@@ -30,23 +30,23 @@ bool get_interval_value(THD *thd, Item *args,
interval_type int_type, INTERVAL *interval);
class Item_long_func_date_field: public Item_long_func
class Item_long_func_date_field: public Item_long_ge0_func
{
bool check_arguments() const
{ return args[0]->check_type_can_return_date(func_name()); }
public:
Item_long_func_date_field(THD *thd, Item *a)
:Item_long_func(thd, a) { }
:Item_long_ge0_func(thd, a) { }
};
class Item_long_func_time_field: public Item_long_func
class Item_long_func_time_field: public Item_long_ge0_func
{
bool check_arguments() const
{ return args[0]->check_type_can_return_time(func_name()); }
public:
Item_long_func_time_field(THD *thd, Item *a)
:Item_long_func(thd, a) { }
:Item_long_ge0_func(thd, a) { }
};
......@@ -166,10 +166,10 @@ class Item_func_dayofmonth :public Item_long_func_date_field
};
class Item_func_month :public Item_long_func
class Item_func_month :public Item_long_ge0_func
{
public:
Item_func_month(THD *thd, Item *a): Item_long_func(thd, a)
Item_func_month(THD *thd, Item *a): Item_long_ge0_func(thd, a)
{ }
longlong val_int();
const char *func_name() const { return "month"; }
......@@ -333,7 +333,7 @@ class Item_func_second :public Item_long_func_time_field
};
class Item_func_week :public Item_long_func
class Item_func_week :public Item_long_ge0_func
{
bool check_arguments() const
{
......@@ -341,8 +341,8 @@ class Item_func_week :public Item_long_func
(arg_count > 1 && args[1]->check_type_can_return_int(func_name()));
}
public:
Item_func_week(THD *thd, Item *a): Item_long_func(thd, a) {}
Item_func_week(THD *thd, Item *a, Item *b): Item_long_func(thd, a, b) {}
Item_func_week(THD *thd, Item *a): Item_long_ge0_func(thd, a) {}
Item_func_week(THD *thd, Item *a, Item *b): Item_long_ge0_func(thd, a, b) {}
longlong val_int();
const char *func_name() const { return "week"; }
bool fix_length_and_dec()
......@@ -969,12 +969,17 @@ class Item_extract :public Item_int_func,
void set_date_length(uint32 length)
{
/*
Although DATE components (e.g. YEAR, YEAR_MONTH, QUARTER, MONTH, WEEK)
cannot have a sign, we should probably still add +1,
because all around the code we assume that max_length is sign inclusive.
Another options is to set unsigned_flag to "true".
DATE components (e.g. YEAR, YEAR_MONTH, QUARTER, MONTH, WEEK)
return non-negative values but historically EXTRACT for date
components always returned the signed int data type.
So do equivalent functions YEAR(), QUARTER(), MONTH(), WEEK().
Let's set the data type to "signed int, but not negative",
so "this" produces better data types in VARCHAR and DECIMAL context
by using the fact that all of the max_length characters are spent
for digits (non of them are spent for the sign).
*/
set_handler(handler_by_length(max_length= length, 10)); // QQ: see above
set_handler(&type_handler_slong_ge0);
fix_char_length(length);
m_date_mode= date_mode_t(0);
}
void set_day_length(uint32 length)
......
......@@ -41,6 +41,7 @@ Named_type_handler<Type_handler_bool> type_handler_bool("boolean");
Named_type_handler<Type_handler_tiny> type_handler_stiny("tinyint");
Named_type_handler<Type_handler_short> type_handler_sshort("smallint");
Named_type_handler<Type_handler_long> type_handler_slong("int");
Named_type_handler<Type_handler_long_ge0> type_handler_slong_ge0("int");
Named_type_handler<Type_handler_int24> type_handler_sint24("mediumint");
Named_type_handler<Type_handler_longlong> type_handler_slonglong("bigint");
Named_type_handler<Type_handler_utiny> type_handler_utiny("tiny unsigned");
......@@ -4629,6 +4630,10 @@ bool Type_handler_general_purpose_int::
bool unsigned_flag= items[0]->unsigned_flag;
for (uint i= 1; i < nitems; i++)
{
/*
TODO: avoid creating DECIMAL for a mix of ulong and slong_ge0.
It's too late for 10.5. Let's do it in a higher version.
*/
if (unsigned_flag != items[i]->unsigned_flag)
{
// Convert a mixture of signed and unsigned int to decimal
......@@ -4638,6 +4643,21 @@ bool Type_handler_general_purpose_int::
}
}
func->aggregate_attributes_int(items, nitems);
for (uint i= 0; i < nitems; i++)
{
if (items[i]->type_handler() == &type_handler_slong_ge0)
{
/*
A slong_ge0 argument found.
We need to add an extra character for the sign.
TODO: rewrite aggregate_attributes_int() to find
the maximum decimal_precision() instead of the maximum max_length.
This change is too late for 10.5, so let's do it in a higher version.
*/
uint digits_and_sign= items[i]->decimal_precision() + 1;
set_if_bigger(func->max_length, digits_and_sign);
}
}
handler->set_handler(func->unsigned_flag ?
handler->type_handler()->type_handler_unsigned() :
handler->type_handler()->type_handler_signed());
......@@ -4931,6 +4951,13 @@ bool Type_handler_real_result::
/*************************************************************************/
bool Type_handler_long_ge0::
Item_sum_hybrid_fix_length_and_dec(Item_sum_hybrid *func) const
{
return func->fix_length_and_dec_sint_ge0();
}
bool Type_handler_int_result::
Item_sum_hybrid_fix_length_and_dec(Item_sum_hybrid *func) const
{
......@@ -6373,6 +6400,14 @@ bool Type_handler_int_result::
}
bool Type_handler_long_ge0::
Item_func_round_fix_length_and_dec(Item_func_round *item) const
{
item->fix_arg_slong_ge0();
return false;
}
bool Type_handler_year::
Item_func_round_fix_length_and_dec(Item_func_round *item) const
{
......@@ -6594,6 +6629,14 @@ bool Type_handler_int_result::
}
bool Type_handler_long_ge0::
Item_func_abs_fix_length_and_dec(Item_func_abs *item) const
{
item->fix_length_and_dec_sint_ge0();
return false;
}
bool Type_handler_real_result::
Item_func_abs_fix_length_and_dec(Item_func_abs *item) const
{
......@@ -6704,6 +6747,22 @@ bool Type_handler::
}
bool Type_handler_long_ge0::
Item_func_signed_fix_length_and_dec(Item_func_signed *item) const
{
item->fix_length_and_dec_sint_ge0();
return false;
}
bool Type_handler_long_ge0::
Item_func_unsigned_fix_length_and_dec(Item_func_unsigned *item) const
{
item->fix_length_and_dec_sint_ge0();
return false;
}
bool Type_handler_string_result::
Item_func_signed_fix_length_and_dec(Item_func_signed *item) const
{
......@@ -7189,6 +7248,18 @@ uint Type_handler_int_result::Item_decimal_precision(const Item *item) const
return MY_MIN(prec, DECIMAL_MAX_PRECISION);
}
uint Type_handler_long_ge0::Item_decimal_precision(const Item *item) const
{
DBUG_ASSERT(item->max_length);
DBUG_ASSERT(!item->decimals);
/*
Unlinke in Type_handler_long, Type_handler_long_ge does
not reserve one character for the sign. All max_length
characters are digits.
*/
return MY_MIN(item->max_length, DECIMAL_MAX_PRECISION);
}
uint Type_handler_time_common::Item_decimal_precision(const Item *item) const
{
return 7 + MY_MIN(item->decimals, TIME_SECOND_PART_DIGITS);
......@@ -8190,6 +8261,26 @@ Field *Type_handler_long::
}
Field *Type_handler_long_ge0::
make_table_field_from_def(TABLE_SHARE *share, MEM_ROOT *mem_root,
const LEX_CSTRING *name,
const Record_addr &rec, const Bit_addr &bit,
const Column_definition_attributes *attr,
uint32 flags) const
{
/*
We're converting signed long_ge0 to signed long.
So add one character for the sign.
*/
return new (mem_root)
Field_long(rec.ptr(), (uint32) attr->length + 1/*sign*/,
rec.null_ptr(), rec.null_bit(),
attr->unireg_check, name,
f_is_zerofill(attr->pack_flag) != 0,
f_is_dec(attr->pack_flag) == 0);
}
Field *Type_handler_longlong::
make_table_field_from_def(TABLE_SHARE *share, MEM_ROOT *mem_root,
const LEX_CSTRING *name,
......
......@@ -5758,6 +5758,38 @@ class Type_handler_long: public Type_handler_general_purpose_int
};
/*
The expression of this type reports itself as signed,
however it's known not to return negative values.
Items of this data type count only digits in Item::max_length,
without adding +1 for the sign. This allows expressions
of this type convert nicely to VARCHAR and DECIMAL.
For example, YEAR(now()) is:
- VARCHAR(4) in a string context
- DECIMAL(4,0) in a decimal context
- but INT(5) in an integer context
*/
class Type_handler_long_ge0: public Type_handler_long
{
public:
uint Item_decimal_precision(const Item *item) const override;
bool Item_func_signed_fix_length_and_dec(Item_func_signed *item)
const override;
bool Item_func_unsigned_fix_length_and_dec(Item_func_unsigned *item)
const override;
bool Item_func_abs_fix_length_and_dec(Item_func_abs *) const override;
bool Item_func_round_fix_length_and_dec(Item_func_round *) const override;
bool Item_sum_hybrid_fix_length_and_dec(Item_sum_hybrid *func) const override;
Field *make_table_field_from_def(TABLE_SHARE *share,
MEM_ROOT *mem_root,
const LEX_CSTRING *name,
const Record_addr &addr,
const Bit_addr &bit,
const Column_definition_attributes *attr,
uint32 flags) const override;
};
class Type_handler_ulong: public Type_handler_long
{
public:
......@@ -7598,6 +7630,7 @@ extern MYSQL_PLUGIN_IMPORT Named_type_handler<Type_handler_tiny> type_han
extern MYSQL_PLUGIN_IMPORT Named_type_handler<Type_handler_short> type_handler_sshort;
extern MYSQL_PLUGIN_IMPORT Named_type_handler<Type_handler_int24> type_handler_sint24;
extern MYSQL_PLUGIN_IMPORT Named_type_handler<Type_handler_long> type_handler_slong;
extern MYSQL_PLUGIN_IMPORT Named_type_handler<Type_handler_long_ge0> type_handler_slong_ge0;
extern MYSQL_PLUGIN_IMPORT Named_type_handler<Type_handler_longlong> type_handler_slonglong;
extern Named_type_handler<Type_handler_utiny> type_handler_utiny;
......
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