From f4019f5b3544a18f3ddf32df2c5214c3f8dabdce Mon Sep 17 00:00:00 2001 From: Alexander Barkov Date: Sat, 20 Apr 2019 00:11:50 +0400 Subject: [PATCH] Backporting from 10.4 to 10.3: MDEV-17325 NULL-ability problems with LEAST() in combination with NO_ZERO_DATE and NO_ZERO_IN_DATE This also fixes: MDEV-17299 Assertion `maybe_null' failed in make_sortkey Note, during merge of the 10.1 version of MDEV-17299, please use the 10.3 version of the code (i.e. null merge the 10.1 version). --- mysql-test/main/func_hybrid_type.result | 114 ++++++++++++++++++++++++ mysql-test/main/func_hybrid_type.test | 64 ++++++++++++- mysql-test/main/type_date.result | 25 ++++++ mysql-test/main/type_date.test | 14 +++ sql/sql_type.cc | 91 +++++++++++++++++++ sql/sql_type.h | 2 + 6 files changed, 309 insertions(+), 1 deletion(-) diff --git a/mysql-test/main/func_hybrid_type.result b/mysql-test/main/func_hybrid_type.result index 04c428e745d..109f7a35038 100644 --- a/mysql-test/main/func_hybrid_type.result +++ b/mysql-test/main/func_hybrid_type.result @@ -3771,5 +3771,119 @@ t2 CREATE TABLE `t2` ( DROP TABLE t1, t2; SET sql_mode=DEFAULT; # +# MDEV-17325 NULL-ability problems with LEAST() in combination with NO_ZERO_DATE and NO_ZERO_IN_DATE +# +SET sql_mode='NO_ZERO_DATE,NO_ZERO_IN_DATE'; +SELECT +LEAST('0000-00-00',DATE'2001-01-01') AS s1, +LEAST('0001-00-01',DATE'2001-01-01') AS s2, +LEAST('0000-00-00',TIMESTAMP'2001-01-01 00:00:00') AS s3, +LEAST('0001-00-01',TIMESTAMP'2001-01-01 00:00:00') AS s4, +LEAST(0,DATE'2001-01-01') AS i1, +LEAST(20010001,DATE'2001-01-01') AS i2, +LEAST(0,TIMESTAMP'2001-01-01 00:00:00') AS i3, +LEAST(20010001,TIMESTAMP'2001-01-01 00:00:00') AS i4; +Catalog Database Table Table_alias Column Column_alias Type Length Max length Is_null Flags Decimals Charsetnr +def s1 10 10 0 Y 128 0 63 +def s2 10 10 0 Y 128 0 63 +def s3 12 26 0 Y 128 6 63 +def s4 12 26 0 Y 128 6 63 +def i1 10 10 0 Y 128 0 63 +def i2 10 10 0 Y 128 0 63 +def i3 12 19 0 Y 128 0 63 +def i4 12 19 0 Y 128 0 63 +s1 s2 s3 s4 i1 i2 i3 i4 +NULL NULL NULL NULL NULL NULL NULL NULL +Warnings: +Warning 1292 Incorrect datetime value: '0000-00-00' +Warning 1292 Incorrect datetime value: '0001-00-01' +Warning 1292 Incorrect datetime value: '0000-00-00 00:00:00' +Warning 1292 Incorrect datetime value: '0001-00-01 00:00:00' +Warning 1292 Incorrect datetime value: '0000-00-00' +Warning 1292 Incorrect datetime value: '2001-00-01' +Warning 1292 Incorrect datetime value: '0000-00-00 00:00:00' +Warning 1292 Incorrect datetime value: '2001-00-01 00:00:00' +SET sql_mode='NO_ZERO_DATE,NO_ZERO_IN_DATE'; +CREATE TABLE t1 AS SELECT +LEAST('0000-00-00',DATE'2001-01-01') AS s1, +LEAST('0001-00-01',DATE'2001-01-01') AS s2, +LEAST('0000-00-00',TIMESTAMP'2001-01-01 00:00:00') AS s3, +LEAST('0001-00-01',TIMESTAMP'2001-01-01 00:00:00') AS s4, +LEAST(0,DATE'2001-01-01') AS i1, +LEAST(20010001,DATE'2001-01-01') AS i2, +LEAST(0,TIMESTAMP'2001-01-01 00:00:00') AS i3, +LEAST(20010001,TIMESTAMP'2001-01-01 00:00:00') AS i4; +Warnings: +Warning 1292 Incorrect datetime value: '0000-00-00' +Warning 1292 Incorrect datetime value: '0001-00-01' +Warning 1292 Incorrect datetime value: '0000-00-00 00:00:00' +Warning 1292 Incorrect datetime value: '0001-00-01 00:00:00' +Warning 1292 Incorrect datetime value: '0000-00-00' +Warning 1292 Incorrect datetime value: '2001-00-01' +Warning 1292 Incorrect datetime value: '0000-00-00 00:00:00' +Warning 1292 Incorrect datetime value: '2001-00-01 00:00:00' +SELECT * FROM t1; +s1 s2 s3 s4 i1 i2 i3 i4 +NULL NULL NULL NULL NULL NULL NULL NULL +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `s1` date DEFAULT NULL, + `s2` date DEFAULT NULL, + `s3` datetime(6) DEFAULT NULL, + `s4` datetime(6) DEFAULT NULL, + `i1` date DEFAULT NULL, + `i2` date DEFAULT NULL, + `i3` datetime DEFAULT NULL, + `i4` datetime DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +DROP TABLE t1; +SET timestamp=UNIX_TIMESTAMP('2001-01-01 10:20:30'); +CREATE TABLE t1 AS SELECT LEAST(CURRENT_DATE,CURRENT_TIME) AS c1; +SELECT * FROM t1; +c1 +2001-01-01 00:00:00 +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `c1` datetime NOT NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +DROP TABLE t1; +SET old_mode=ZERO_DATE_TIME_CAST; +CREATE TABLE t1 AS SELECT LEAST(CURRENT_DATE,CURRENT_TIME) AS c1; +Warnings: +Warning 1292 Incorrect datetime value: '0000-00-00 10:20:30' +SELECT * FROM t1; +c1 +NULL +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `c1` datetime DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +DROP TABLE t1; +SET old_mode=DEFAULT; +SET timestamp=DEFAULT; +SET sql_mode=DEFAULT; +SET sql_mode=''; +SELECT LEAST(999,TIME'10:20:30') AS c1; +c1 +NULL +Warnings: +Warning 1292 Incorrect datetime value: '999' +CREATE TABLE t1 AS SELECT LEAST(999,TIME'10:20:30') AS c1; +Warnings: +Warning 1292 Incorrect datetime value: '999' +SELECT * FROM t1; +c1 +NULL +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `c1` time DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +DROP TABLE t1; +SET sql_mode=DEFAULT; +# # End of 10.3 tests # diff --git a/mysql-test/main/func_hybrid_type.test b/mysql-test/main/func_hybrid_type.test index f754d660140..223ae4b2166 100644 --- a/mysql-test/main/func_hybrid_type.test +++ b/mysql-test/main/func_hybrid_type.test @@ -628,6 +628,68 @@ SET sql_mode=DEFAULT; --echo # ---echo # End of 10.3 tests +--echo # MDEV-17325 NULL-ability problems with LEAST() in combination with NO_ZERO_DATE and NO_ZERO_IN_DATE --echo # +SET sql_mode='NO_ZERO_DATE,NO_ZERO_IN_DATE'; + +--disable_ps_protocol +--enable_metadata +SELECT + LEAST('0000-00-00',DATE'2001-01-01') AS s1, + LEAST('0001-00-01',DATE'2001-01-01') AS s2, + LEAST('0000-00-00',TIMESTAMP'2001-01-01 00:00:00') AS s3, + LEAST('0001-00-01',TIMESTAMP'2001-01-01 00:00:00') AS s4, + LEAST(0,DATE'2001-01-01') AS i1, + LEAST(20010001,DATE'2001-01-01') AS i2, + LEAST(0,TIMESTAMP'2001-01-01 00:00:00') AS i3, + LEAST(20010001,TIMESTAMP'2001-01-01 00:00:00') AS i4; +--disable_metadata +--enable_ps_protocol + +SET sql_mode='NO_ZERO_DATE,NO_ZERO_IN_DATE'; +CREATE TABLE t1 AS SELECT + LEAST('0000-00-00',DATE'2001-01-01') AS s1, + LEAST('0001-00-01',DATE'2001-01-01') AS s2, + LEAST('0000-00-00',TIMESTAMP'2001-01-01 00:00:00') AS s3, + LEAST('0001-00-01',TIMESTAMP'2001-01-01 00:00:00') AS s4, + LEAST(0,DATE'2001-01-01') AS i1, + LEAST(20010001,DATE'2001-01-01') AS i2, + LEAST(0,TIMESTAMP'2001-01-01 00:00:00') AS i3, + LEAST(20010001,TIMESTAMP'2001-01-01 00:00:00') AS i4; +SELECT * FROM t1; +SHOW CREATE TABLE t1; +DROP TABLE t1; + +SET timestamp=UNIX_TIMESTAMP('2001-01-01 10:20:30'); + +# A TIME always converts to a non-NULL DATETIME with the new CAST style +# Expect a NOT NULL column +CREATE TABLE t1 AS SELECT LEAST(CURRENT_DATE,CURRENT_TIME) AS c1; +SELECT * FROM t1; +SHOW CREATE TABLE t1; +DROP TABLE t1; + +# A TIME can convert to a NULL DATETIME with old CAST style +# Expect a NULL-able column +SET old_mode=ZERO_DATE_TIME_CAST; +CREATE TABLE t1 AS SELECT LEAST(CURRENT_DATE,CURRENT_TIME) AS c1; +SELECT * FROM t1; +SHOW CREATE TABLE t1; +DROP TABLE t1; +SET old_mode=DEFAULT; +SET timestamp=DEFAULT; + +SET sql_mode=DEFAULT; + +SET sql_mode=''; +SELECT LEAST(999,TIME'10:20:30') AS c1; +CREATE TABLE t1 AS SELECT LEAST(999,TIME'10:20:30') AS c1; +SELECT * FROM t1; +SHOW CREATE TABLE t1; +DROP TABLE t1; +SET sql_mode=DEFAULT; + +--echo # +--echo # End of 10.3 tests +--echo # diff --git a/mysql-test/main/type_date.result b/mysql-test/main/type_date.result index e87cd836317..4b5a0ad63a0 100644 --- a/mysql-test/main/type_date.result +++ b/mysql-test/main/type_date.result @@ -863,6 +863,31 @@ d COUNT(*) NULL 2 DROP TABLE t1; # +# MDEV-17299 Assertion `maybe_null' failed in make_sortkey +# +CREATE TABLE t1 (pk int NOT NULL, d1 date, d2 date NOT NULL); +INSERT INTO t1 values (1,'2018-06-22','2018-06-22'),(2,'2018-07-11','2018-07-11'); +CREATE VIEW v1 AS SELECT * FROM t1; +SELECT group_concat(d1/(CASE 'b' WHEN 'j' THEN 'c' END)) +FROM v1 GROUP BY greatest(pk, 0, d2); +group_concat(d1/(CASE 'b' WHEN 'j' THEN 'c' END)) +NULL +Warnings: +Warning 1292 Incorrect datetime value: '1' for column `test`.`t1`.`pk` at row 1 +Warning 1292 Incorrect datetime value: '2' for column `test`.`t1`.`pk` at row 1 +Warning 1292 Incorrect datetime value: '1' for column `test`.`t1`.`pk` at row 1 +Warning 1292 Incorrect datetime value: '1' for column `test`.`t1`.`pk` at row 1 +Warning 1292 Incorrect datetime value: '2' for column `test`.`t1`.`pk` at row 2 +CREATE TABLE t2 AS SELECT greatest(pk, 0, d2) AS c1 FROM t1 LIMIT 0; +SHOW CREATE TABLE t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `c1` date DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +DROP TABLE t2; +DROP VIEW v1; +DROP TABLE t1; +# # End of 10.1 tests # # diff --git a/mysql-test/main/type_date.test b/mysql-test/main/type_date.test index 8d29a54a26c..befee57183d 100644 --- a/mysql-test/main/type_date.test +++ b/mysql-test/main/type_date.test @@ -591,6 +591,20 @@ INSERT INTO t1 VALUES ('1985-05-13'),('1989-12-24'); SELECT d, COUNT(*) FROM t1 GROUP BY d WITH ROLLUP HAVING CASE d WHEN '2017-05-25' THEN 0 ELSE 1 END; DROP TABLE t1; +--echo # +--echo # MDEV-17299 Assertion `maybe_null' failed in make_sortkey +--echo # + +CREATE TABLE t1 (pk int NOT NULL, d1 date, d2 date NOT NULL); +INSERT INTO t1 values (1,'2018-06-22','2018-06-22'),(2,'2018-07-11','2018-07-11'); +CREATE VIEW v1 AS SELECT * FROM t1; +SELECT group_concat(d1/(CASE 'b' WHEN 'j' THEN 'c' END)) + FROM v1 GROUP BY greatest(pk, 0, d2); +CREATE TABLE t2 AS SELECT greatest(pk, 0, d2) AS c1 FROM t1 LIMIT 0; +SHOW CREATE TABLE t2; +DROP TABLE t2; +DROP VIEW v1; +DROP TABLE t1; --echo # --echo # End of 10.1 tests diff --git a/sql/sql_type.cc b/sql/sql_type.cc index 22eebaf6a38..c2c853efa23 100644 --- a/sql/sql_type.cc +++ b/sql/sql_type.cc @@ -2933,6 +2933,97 @@ bool Type_handler:: } +bool Type_handler_temporal_result:: + Item_func_min_max_fix_attributes(THD *thd, Item_func_min_max *func, + Item **items, uint nitems) const +{ + bool rc= Type_handler::Item_func_min_max_fix_attributes(thd, func, + items, nitems); + if (rc || func->maybe_null) + return rc; + /* + LEAST/GREATES(non-temporal, temporal) can return NULL. + CAST functions Item_{time|datetime|date}_typecast always set maybe_full + to true. Here we try to detect nullability more thoroughly. + Perhaps CAST functions should also reuse this idea eventually. + */ + const Type_handler *hf= func->type_handler(); + for (uint i= 0; i < nitems; i++) + { + /* + If items[i] does not need conversion to the current temporal data + type, then we trust items[i]->maybe_null, which was already ORred + to func->maybe_null in the argument loop in fix_fields(). + If items[i] requires conversion to the current temporal data type, + then conversion can fail and return NULL even for NOT NULL items. + */ + const Type_handler *ha= items[i]->type_handler(); + if (hf == ha) + continue; // No conversion. + if (ha->cmp_type() != TIME_RESULT) + { + func->maybe_null= true; // Conversion from non-temporal is not safe + break; + } + timestamp_type tf= hf->mysql_timestamp_type(); + timestamp_type ta= ha->mysql_timestamp_type(); + if (tf == ta || + (tf == MYSQL_TIMESTAMP_DATETIME && ta == MYSQL_TIMESTAMP_DATE)) + { + /* + If handlers have the same mysql_timestamp_type(), + then conversion is NULL safe. Conversion from DATE to DATETIME + is also safe. This branch includes data type pairs: + Function return type Argument type Comment + -------------------- ------------- ------------- + TIMESTAMP TIMESTAMP no conversion + TIMESTAMP DATETIME not possible + TIMESTAMP DATE not possible + DATETIME DATETIME no conversion + DATETIME TIMESTAMP safe conversion + DATETIME DATE safe conversion + DATE DATE no conversion + TIME TIME no conversion + + Note, a function cannot return TIMESTAMP if it has non-TIMESTAMP + arguments (it would return DATETIME in such case). + */ + DBUG_ASSERT(hf->field_type() != MYSQL_TYPE_TIMESTAMP || tf == ta); + continue; + } + /* + Here we have the following data type pairs that did not match + the condition above: + + Function return type Argument type Comment + -------------------- ------------- ------- + TIMESTAMP TIME Not possible + DATETIME TIME depends on OLD_MODE_ZERO_DATE_TIME_CAST + DATE TIMESTAMP Not possible + DATE DATETIME Not possible + DATE TIME Not possible + TIME TIMESTAMP Not possible + TIME DATETIME Not possible + TIME DATE Not possible + + Most pairs are not possible, because the function data type + would be DATETIME (according to LEAST/GREATEST aggregation rules). + Conversion to DATETIME from TIME is not safe when + OLD_MODE_ZERO_DATE_TIME_CAST is set: + - negative TIME values cannot be converted to not-NULL DATETIME values + - TIME values can produce DATETIME values that do not pass + NO_ZERO_DATE and NO_ZERO_IN_DATE tests. + */ + DBUG_ASSERT(hf->field_type() == MYSQL_TYPE_DATETIME); + if (!(thd->variables.old_behavior & OLD_MODE_ZERO_DATE_TIME_CAST)) + continue; + func->maybe_null= true; + break; + } + return rc; +} + + bool Type_handler_real_result:: Item_func_min_max_fix_attributes(THD *thd, Item_func_min_max *func, Item **items, uint nitems) const diff --git a/sql/sql_type.h b/sql/sql_type.h index 1ddcef2da61..21e0eb56338 100644 --- a/sql/sql_type.h +++ b/sql/sql_type.h @@ -2138,6 +2138,8 @@ public: Item *source_expr, Item *source_const) const; bool subquery_type_allows_materialization(const Item *inner, const Item *outer) const; + bool Item_func_min_max_fix_attributes(THD *thd, Item_func_min_max *func, + Item **items, uint nitems) const; bool Item_sum_hybrid_fix_length_and_dec(Item_sum_hybrid *func) const; bool Item_sum_sum_fix_length_and_dec(Item_sum_sum *) const; bool Item_sum_avg_fix_length_and_dec(Item_sum_avg *) const;