diff --git a/mysql-test/main/func_hybrid_type.result b/mysql-test/main/func_hybrid_type.result index ebf7191a3f7..2c1d03859f7 100644 --- a/mysql-test/main/func_hybrid_type.result +++ b/mysql-test/main/func_hybrid_type.result @@ -3755,3 +3755,123 @@ DROP TABLE t1; # # End of 10.3 tests # +# +# Start of 10.4 tests +# +# +# 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 time value: '999' +CREATE TABLE t1 AS SELECT LEAST(999,TIME'10:20:30') AS c1; +Warnings: +Warning 1292 Incorrect time 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.4 tests +# diff --git a/mysql-test/main/func_hybrid_type.test b/mysql-test/main/func_hybrid_type.test index 954c7de53fa..a85ffcecf92 100644 --- a/mysql-test/main/func_hybrid_type.test +++ b/mysql-test/main/func_hybrid_type.test @@ -615,3 +615,74 @@ DROP TABLE t1; --echo # End of 10.3 tests --echo # +--echo # +--echo # Start of 10.4 tests +--echo # + +--echo # +--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.4 tests +--echo # diff --git a/sql/sql_type.cc b/sql/sql_type.cc index 8a6022ae5de..04882f557e9 100644 --- a/sql/sql_type.cc +++ b/sql/sql_type.cc @@ -3480,6 +3480,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 06cfb330097..0e8a433ac74 100644 --- a/sql/sql_type.h +++ b/sql/sql_type.h @@ -3283,6 +3283,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;