From 1a95ed1df2bdd26444a973e07ffd84c045deb734 Mon Sep 17 00:00:00 2001 From: "tnurnberg@mysql.com/white.intern.koehntopp.de" <> Date: Mon, 26 Nov 2007 08:20:40 +0100 Subject: [PATCH 01/25] Bug#31752: check strmake() bounds strmake() calls are easy to get wrong. Add checks in extra debug mode to identify possible exploits. Remove some dead code. Remove some off-by-one errors identified with new checks. --- sql/log.cc | 2 +- sql/repl_failsafe.cc | 2 +- sql/set_var.cc | 2 +- sql/sql_show.cc | 4 ++-- sql/unireg.cc | 3 +++ strings/strmake.c | 32 +++++++++++++++++--------------- 6 files changed, 25 insertions(+), 20 deletions(-) diff --git a/sql/log.cc b/sql/log.cc index b91ec2b3dee..5a4f02a827b 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -966,7 +966,7 @@ void MYSQL_LOG::make_log_name(char* buf, const char* log_ident) if (dir_len > FN_REFLEN) dir_len=FN_REFLEN-1; strnmov(buf, log_file_name, dir_len); - strmake(buf+dir_len, log_ident, FN_REFLEN - dir_len); + strmake(buf+dir_len, log_ident, FN_REFLEN - dir_len -1); } diff --git a/sql/repl_failsafe.cc b/sql/repl_failsafe.cc index 4c8703226a6..4ea90346638 100644 --- a/sql/repl_failsafe.cc +++ b/sql/repl_failsafe.cc @@ -926,7 +926,7 @@ int load_master_data(THD* thd) 0, (SLAVE_IO | SLAVE_SQL))) send_error(thd, ER_MASTER_INFO); strmake(active_mi->master_log_name, row[0], - sizeof(active_mi->master_log_name)); + sizeof(active_mi->master_log_name) -1); active_mi->master_log_pos= my_strtoll10(row[1], (char**) 0, &error); /* at least in recent versions, the condition below should be false */ if (active_mi->master_log_pos < BIN_LOG_HEADER_SIZE) diff --git a/sql/set_var.cc b/sql/set_var.cc index 520ee5c9f70..1d18eba30a8 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -1573,7 +1573,7 @@ bool sys_var::check_set(THD *thd, set_var *var, TYPELIB *enum_names) ¬_used)); if (error_len) { - strmake(buff, error, min(sizeof(buff), error_len)); + strmake(buff, error, min(sizeof(buff) - 1, error_len)); goto err; } } diff --git a/sql/sql_show.cc b/sql/sql_show.cc index bf0e254d3e4..be658c8fe5d 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -136,7 +136,7 @@ int mysqld_show_tables(THD *thd,const char *db,const char *wild) { Item_string *field=new Item_string("",0,thd->charset()); List field_list; - char path[FN_LEN],*end; + char path[FN_REFLEN],*end; List files; char *file_name; Protocol *protocol= thd->protocol; @@ -457,7 +457,7 @@ int mysqld_extend_show_tables(THD *thd,const char *db,const char *wild) Item *item; List files; List field_list; - char path[FN_LEN]; + char path[FN_REFLEN]; char *file_name; TABLE *table; Protocol *protocol= thd->protocol; diff --git a/sql/unireg.cc b/sql/unireg.cc index e5ee0222f20..795198fc55f 100644 --- a/sql/unireg.cc +++ b/sql/unireg.cc @@ -140,6 +140,9 @@ bool mysql_create_frm(THD *thd, my_string file_name, strmake((char*) forminfo+47,create_info->comment ? create_info->comment : "", 60); forminfo[46]=(uchar) strlen((char*)forminfo+47); // Length of comment +#ifdef EXTRA_DEBUG + memset((char*) forminfo+47 + forminfo[46], 0, 61 - forminfo[46]); +#endif if (my_pwrite(file,(byte*) fileinfo,64,0L,MYF_RW) || my_pwrite(file,(byte*) keybuff,key_info_length, diff --git a/strings/strmake.c b/strings/strmake.c index d2252f648f6..47d8a04e361 100644 --- a/strings/strmake.c +++ b/strings/strmake.c @@ -28,23 +28,25 @@ #include #include "m_string.h" -#ifdef BAD_STRING_COMPILER - -char *strmake(char *dst,const char *src,uint length) -{ - reg1 char *res; - - if ((res=memccpy(dst,src,0,length))) - return res-1; - dst[length]=0; - return dst+length; -} - -#define strmake strmake_overlapp /* Use orginal for overlapping str */ -#endif - char *strmake(register char *dst, register const char *src, uint length) { +#ifdef EXTRA_DEBUG + /* + 'length' is the maximum length of the string; the buffer needs + to be one character larger to accomodate the terminating '\0'. + This is easy to get wrong, so we make sure we write to the + entire length of the buffer to identify incorrect buffer-sizes. + We only initialise the "unused" part of the buffer here, a) for + efficiency, and b) because dst==src is allowed, so initialising + the entire buffer would overwrite the source-string. Also, we + write a character rather than '\0' as this makes spotting these + problems in the results easier. + */ + uint n= strlen(src) + 1; + if (n <= length) + memset(dst + n, (int) 'Z', length - n + 1); +#endif + while (length--) if (! (*dst++ = *src++)) return dst-1; From 883338a9498d6a02cdb6de102abad58f9f49575a Mon Sep 17 00:00:00 2001 From: "tnurnberg@mysql.com/white.intern.koehntopp.de" <> Date: Mon, 26 Nov 2007 09:13:23 +0100 Subject: [PATCH 02/25] Bug#31752: check strmake() bounds strmake() called with wrong parameters: 5.0-specific fixes. --- client/mysql.cc | 5 ++++- sql/sp.cc | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/client/mysql.cc b/client/mysql.cc index 8e1b6c2a9b4..ff2c1d228cd 100644 --- a/client/mysql.cc +++ b/client/mysql.cc @@ -2987,7 +2987,10 @@ com_connect(String *buffer, char *line) Two null bytes are needed in the end of buff to allow get_arg to find end of string the second time it's called. */ - strmake(buff, line, sizeof(buff)-2); + tmp= strmake(buff, line, sizeof(buff)-2); +#ifdef EXTRA_DEBUG + tmp[1]= 0; +#endif tmp= get_arg(buff, 0); if (tmp && *tmp) { diff --git a/sql/sp.cc b/sql/sp.cc index 75d6fa4618f..bae5933aec1 100644 --- a/sql/sp.cc +++ b/sql/sp.cc @@ -1902,7 +1902,7 @@ sp_use_new_db(THD *thd, LEX_STRING new_db, LEX_STRING *old_db, if (thd->db) { - old_db->length= (strmake(old_db->str, thd->db, old_db->length) - + old_db->length= (strmake(old_db->str, thd->db, old_db->length - 1) - old_db->str); } else From 6bb485b0801e99e4871e9a921dcb7fcb33d662fd Mon Sep 17 00:00:00 2001 From: "tnurnberg@mysql.com/white.intern.koehntopp.de" <> Date: Mon, 26 Nov 2007 09:38:42 +0100 Subject: [PATCH 03/25] Bug#31752: check strmake() bounds strmake() called with wrong parameters: 5.1-specific fixes. --- sql/sql_db.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/sql_db.cc b/sql/sql_db.cc index abbf2131957..5fd0f873364 100644 --- a/sql/sql_db.cc +++ b/sql/sql_db.cc @@ -1387,7 +1387,7 @@ static void backup_current_db_name(THD *thd, } else { - strmake(saved_db_name->str, thd->db, saved_db_name->length); + strmake(saved_db_name->str, thd->db, saved_db_name->length - 1); saved_db_name->length= thd->db_length; } } From dddced964b7d3662f6bec97dc8a21f598a4b3ec7 Mon Sep 17 00:00:00 2001 From: "tnurnberg@mysql.com/white.intern.koehntopp.de" <> Date: Thu, 6 Dec 2007 11:48:27 +0100 Subject: [PATCH 04/25] Bug#31752: check strmake() bounds post-fixes: prevent semi-related overflow, additional comments --- mysys/mf_pack.c | 4 ++-- sql/log.cc | 2 +- sql/sql_show.cc | 5 +++-- sql/unireg.cc | 5 +++++ 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/mysys/mf_pack.c b/mysys/mf_pack.c index 049aa59a578..485b2c5ec3d 100644 --- a/mysys/mf_pack.c +++ b/mysys/mf_pack.c @@ -272,7 +272,7 @@ void symdirget(char *dir) SYNOPSIS unpack_dirname() - to Store result here. May be = from + to result-buffer, FN_REFLEN characters. may be == from from 'Packed' directory name (may contain ~) IMPLEMENTATION @@ -398,7 +398,7 @@ uint unpack_filename(my_string to, const char *from) /* Convert filename (unix standard) to system standard */ /* Used before system command's like open(), create() .. */ - /* Returns length of to */ + /* Returns used length of to; total length should be FN_REFLEN */ uint system_filename(my_string to, const char *from) { diff --git a/sql/log.cc b/sql/log.cc index 5a4f02a827b..4e0964fe9d6 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -963,7 +963,7 @@ err: void MYSQL_LOG::make_log_name(char* buf, const char* log_ident) { uint dir_len = dirname_length(log_file_name); - if (dir_len > FN_REFLEN) + if (dir_len >= FN_REFLEN) dir_len=FN_REFLEN-1; strnmov(buf, log_file_name, dir_len); strmake(buf+dir_len, log_ident, FN_REFLEN - dir_len -1); diff --git a/sql/sql_show.cc b/sql/sql_show.cc index be658c8fe5d..741d30e6a99 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -136,7 +136,8 @@ int mysqld_show_tables(THD *thd,const char *db,const char *wild) { Item_string *field=new Item_string("",0,thd->charset()); List field_list; - char path[FN_REFLEN],*end; + char path[FN_REFLEN],*end; // for unpack_dirname() + List files; char *file_name; Protocol *protocol= thd->protocol; @@ -457,7 +458,7 @@ int mysqld_extend_show_tables(THD *thd,const char *db,const char *wild) Item *item; List files; List field_list; - char path[FN_REFLEN]; + char path[FN_REFLEN]; // for unpack_dirname() char *file_name; TABLE *table; Protocol *protocol= thd->protocol; diff --git a/sql/unireg.cc b/sql/unireg.cc index 795198fc55f..dcb49bc1766 100644 --- a/sql/unireg.cc +++ b/sql/unireg.cc @@ -141,6 +141,11 @@ bool mysql_create_frm(THD *thd, my_string file_name, 60); forminfo[46]=(uchar) strlen((char*)forminfo+47); // Length of comment #ifdef EXTRA_DEBUG + /* + EXTRA_DEBUG causes strmake() to initialize its buffer behind the + payload with a magic value to detect wrong buffer-sizes. We + explicitly zero that segment again. + */ memset((char*) forminfo+47 + forminfo[46], 0, 61 - forminfo[46]); #endif From 8651aeb2c49415f458f8f8bb41d9b09ac838929c Mon Sep 17 00:00:00 2001 From: "tnurnberg@mysql.com/white.intern.koehntopp.de" <> Date: Mon, 10 Dec 2007 09:17:18 +0100 Subject: [PATCH 05/25] Bug#32770: LAST_DAY() returns a DATE, but somehow internally keeps track of the TIME. LAST_DAY() says it returns a DATE, not a DATETIME, but didn't zero the time fields. Adapted from a patch kindly supplied by Claudio Cherubino. --- mysql-test/r/func_time.result | 3 +++ mysql-test/t/func_time.test | 7 +++++++ sql/item_timefunc.cc | 2 ++ 3 files changed, 12 insertions(+) diff --git a/mysql-test/r/func_time.result b/mysql-test/r/func_time.result index 71234385c0d..f25f9ed9e0a 100644 --- a/mysql-test/r/func_time.result +++ b/mysql-test/r/func_time.result @@ -1282,4 +1282,7 @@ DATE_ADD('20071108', INTERVAL 1 DAY) select DATE_ADD(20071108, INTERVAL 1 DAY); DATE_ADD(20071108, INTERVAL 1 DAY) 2007-11-09 +select LAST_DAY('2007-12-06 08:59:19.05') - INTERVAL 1 SECOND; +LAST_DAY('2007-12-06 08:59:19.05') - INTERVAL 1 SECOND +2007-12-30 23:59:59 End of 5.0 tests diff --git a/mysql-test/t/func_time.test b/mysql-test/t/func_time.test index f8249b7cf7c..b0f47e0ad56 100644 --- a/mysql-test/t/func_time.test +++ b/mysql-test/t/func_time.test @@ -797,4 +797,11 @@ select DATE_ADD(20071108181000, INTERVAL 1 DAY); select DATE_ADD('20071108', INTERVAL 1 DAY); select DATE_ADD(20071108, INTERVAL 1 DAY); +# +# Bug#32770: LAST_DAY() returns a DATE, but somehow internally keeps +# track of the TIME. +# + +select LAST_DAY('2007-12-06 08:59:19.05') - INTERVAL 1 SECOND; + --echo End of 5.0 tests diff --git a/sql/item_timefunc.cc b/sql/item_timefunc.cc index 7ed5e375f5b..82159658883 100644 --- a/sql/item_timefunc.cc +++ b/sql/item_timefunc.cc @@ -3411,6 +3411,8 @@ bool Item_func_last_day::get_date(MYSQL_TIME *ltime, uint fuzzy_date) ltime->day= days_in_month[month_idx]; if ( month_idx == 1 && calc_days_in_year(ltime->year) == 366) ltime->day= 29; + ltime->hour= ltime->minute= ltime->second= 0; + ltime->second_part= 0; ltime->time_type= MYSQL_TIMESTAMP_DATE; return 0; } From 3f0f275030246c333b38c48c8b8be204e95c4f38 Mon Sep 17 00:00:00 2001 From: "tnurnberg@mysql.com/white.intern.koehntopp.de" <> Date: Tue, 11 Dec 2007 10:12:05 +0100 Subject: [PATCH 06/25] Bug#31990: MINUTE() and SECOND() return bogus results when used on a DATE HOUR(), MINUTE(), ... returned spurious results when used on a DATE-cast. This happened because DATE-cast object did not overload get_time() method in superclass Item. The default method was inappropriate here and misinterpreted the data. Patch adds missing method; get_time() on DATE-casts now returns SQL-NULL on NULL input, 0 otherwise. This coincides with the way DATE-columns behave. Also fixes similar bug in Date-Field now. --- mysql-test/r/cast.result | 24 ++++++++++++++++++++++++ mysql-test/r/type_date.result | 5 +++++ mysql-test/t/cast.test | 22 ++++++++++++++++++++++ mysql-test/t/type_date.test | 8 ++++++++ sql/field.cc | 7 +++++++ sql/field.h | 1 + sql/item_timefunc.cc | 7 +++++++ sql/item_timefunc.h | 1 + 8 files changed, 75 insertions(+) diff --git a/mysql-test/r/cast.result b/mysql-test/r/cast.result index 524ff48d69e..88601eceb0a 100644 --- a/mysql-test/r/cast.result +++ b/mysql-test/r/cast.result @@ -414,4 +414,28 @@ NULL NULL 20070719 drop table t1; +CREATE TABLE t1 (f1 DATE); +INSERT INTO t1 VALUES ('2007-07-19'), (NULL); +SELECT HOUR(f1), +MINUTE(f1), +SECOND(f1) FROM t1; +HOUR(f1) MINUTE(f1) SECOND(f1) +0 0 0 +NULL NULL NULL +SELECT HOUR(CAST('2007-07-19' AS DATE)), +MINUTE(CAST('2007-07-19' AS DATE)), +SECOND(CAST('2007-07-19' AS DATE)); +HOUR(CAST('2007-07-19' AS DATE)) MINUTE(CAST('2007-07-19' AS DATE)) SECOND(CAST('2007-07-19' AS DATE)) +0 0 0 +SELECT HOUR(CAST(NULL AS DATE)), +MINUTE(CAST(NULL AS DATE)), +SECOND(CAST(NULL AS DATE)); +HOUR(CAST(NULL AS DATE)) MINUTE(CAST(NULL AS DATE)) SECOND(CAST(NULL AS DATE)) +NULL NULL NULL +SELECT HOUR(NULL), +MINUTE(NULL), +SECOND(NULL); +HOUR(NULL) MINUTE(NULL) SECOND(NULL) +NULL NULL NULL +DROP TABLE t1; End of 5.0 tests diff --git a/mysql-test/r/type_date.result b/mysql-test/r/type_date.result index bd2a43569dd..f16b873f37b 100644 --- a/mysql-test/r/type_date.result +++ b/mysql-test/r/type_date.result @@ -214,4 +214,9 @@ INSERT INTO t1 VALUES ('0000-00-00'); ERROR 22007: Incorrect date value: '0000-00-00' for column 'a' at row 1 SET SQL_MODE=DEFAULT; DROP TABLE t1,t2; +CREATE TABLE t1 SELECT curdate() AS f1; +SELECT hour(f1), minute(f1), second(f1) FROM t1; +hour(f1) minute(f1) second(f1) +0 0 0 +DROP TABLE t1; End of 5.0 tests diff --git a/mysql-test/t/cast.test b/mysql-test/t/cast.test index 316b79efe4d..df475b49746 100644 --- a/mysql-test/t/cast.test +++ b/mysql-test/t/cast.test @@ -246,4 +246,26 @@ INSERT INTO t1(d1) VALUES ('2007-07-19 08:30:00'), (NULL), SELECT cast(date(d1) as signed) FROM t1; drop table t1; +# +# Bug #31990: MINUTE() and SECOND() return bogus results when used on a DATE +# + +# Show that HH:MM:SS of a DATE are 0, and that it's the same for columns +# and typecasts (NULL in, NULL out). +CREATE TABLE t1 (f1 DATE); +INSERT INTO t1 VALUES ('2007-07-19'), (NULL); +SELECT HOUR(f1), + MINUTE(f1), + SECOND(f1) FROM t1; +SELECT HOUR(CAST('2007-07-19' AS DATE)), + MINUTE(CAST('2007-07-19' AS DATE)), + SECOND(CAST('2007-07-19' AS DATE)); +SELECT HOUR(CAST(NULL AS DATE)), + MINUTE(CAST(NULL AS DATE)), + SECOND(CAST(NULL AS DATE)); +SELECT HOUR(NULL), + MINUTE(NULL), + SECOND(NULL); +DROP TABLE t1; + --echo End of 5.0 tests diff --git a/mysql-test/t/type_date.test b/mysql-test/t/type_date.test index 507537457d3..3f2ee4234bd 100644 --- a/mysql-test/t/type_date.test +++ b/mysql-test/t/type_date.test @@ -190,4 +190,12 @@ INSERT INTO t1 VALUES ('0000-00-00'); SET SQL_MODE=DEFAULT; DROP TABLE t1,t2; +# +# Bug #31990: MINUTE() and SECOND() return bogus results when used on a DATE +# + +CREATE TABLE t1 SELECT curdate() AS f1; +SELECT hour(f1), minute(f1), second(f1) FROM t1; +DROP TABLE t1; + --echo End of 5.0 tests diff --git a/sql/field.cc b/sql/field.cc index 86853389c64..db438c7c4d0 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -5194,6 +5194,13 @@ String *Field_date::val_str(String *val_buffer, } +bool Field_date::get_time(MYSQL_TIME *ltime) +{ + bzero((char *)ltime, sizeof(MYSQL_TIME)); + return 0; +} + + int Field_date::cmp(const char *a_ptr, const char *b_ptr) { int32 a,b; diff --git a/sql/field.h b/sql/field.h index 8c01931fa21..8ae39f78558 100644 --- a/sql/field.h +++ b/sql/field.h @@ -933,6 +933,7 @@ public: double val_real(void); longlong val_int(void); String *val_str(String*,String *); + bool get_time(MYSQL_TIME *ltime); bool send_binary(Protocol *protocol); int cmp(const char *,const char*); void sort_string(char *buff,uint length); diff --git a/sql/item_timefunc.cc b/sql/item_timefunc.cc index c1fa9dce038..7ed5e375f5b 100644 --- a/sql/item_timefunc.cc +++ b/sql/item_timefunc.cc @@ -2645,6 +2645,13 @@ bool Item_date_typecast::get_date(MYSQL_TIME *ltime, uint fuzzy_date) } +bool Item_date_typecast::get_time(MYSQL_TIME *ltime) +{ + bzero((char *)ltime, sizeof(MYSQL_TIME)); + return args[0]->null_value; +} + + String *Item_date_typecast::val_str(String *str) { DBUG_ASSERT(fixed == 1); diff --git a/sql/item_timefunc.h b/sql/item_timefunc.h index a5ecbc57e8d..b647e93b700 100644 --- a/sql/item_timefunc.h +++ b/sql/item_timefunc.h @@ -779,6 +779,7 @@ public: const char *func_name() const { return "cast_as_date"; } String *val_str(String *str); bool get_date(MYSQL_TIME *ltime, uint fuzzy_date); + bool get_time(MYSQL_TIME *ltime); const char *cast_type() const { return "date"; } enum_field_types field_type() const { return MYSQL_TYPE_DATE; } Field *tmp_table_field(TABLE *t_arg) From 867a78654946c7c7ebed430b2b8437e837a0455d Mon Sep 17 00:00:00 2001 From: "mhansson/martin@linux-st28.site" <> Date: Tue, 11 Dec 2007 20:15:03 +0100 Subject: [PATCH 07/25] Bug#32848: Data type conversion bug in union subselects in MySQL 5.0.38 There were two problems when inferring the correct field types resulting from UNION queries. - If the type is NULL for all corresponding fields in the UNION, the resulting type would be NULL, while the type is BINARY(0) if there is just a single SELECT NULL. - If one SELECT in the UNION uses a subselect, a temporary table is created to represent the subselect, and the result type defaults to a STRING type, hiding the fact that the type was unknown(just a NULL value). Fixed by remembering whenever a field was created from a NULL value and pass type NULL to the type coercion if that is the case, and creating a string field as result of UNION only if the type would otherwise be NULL. --- mysql-test/r/union.result | 24 ++++++++++++++++++++++++ mysql-test/t/union.test | 24 ++++++++++++++++++++++++ sql/field.cc | 3 ++- sql/field.h | 10 ++++++++++ sql/item.cc | 4 ++++ sql/sql_select.cc | 2 ++ 6 files changed, 66 insertions(+), 1 deletion(-) diff --git a/mysql-test/r/union.result b/mysql-test/r/union.result index efdd8195fb5..0b77459e02f 100644 --- a/mysql-test/r/union.result +++ b/mysql-test/r/union.result @@ -1389,4 +1389,28 @@ select @var; 1 (select 2) union (select 1 into @var); ERROR 42000: Result consisted of more than one row +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1), (2), (3); +CREATE TABLE t2 SELECT * FROM (SELECT NULL) a UNION SELECT a FROM t1; +DESC t2; +Field Type Null Key Default Extra +NULL int(11) YES NULL +CREATE TABLE t3 SELECT a FROM t1 UNION SELECT * FROM (SELECT NULL) a; +DESC t3; +Field Type Null Key Default Extra +a int(11) YES NULL +CREATE TABLE t4 SELECT NULL; +DESC t4; +Field Type Null Key Default Extra +NULL binary(0) YES NULL +CREATE TABLE t5 SELECT NULL UNION SELECT NULL; +DESC t5; +Field Type Null Key Default Extra +NULL binary(0) YES NULL +CREATE TABLE t6 +SELECT * FROM (SELECT * FROM (SELECT NULL)a) b UNION SELECT a FROM t1; +DESC t6; +Field Type Null Key Default Extra +NULL int(11) YES NULL +DROP TABLE t1, t2, t3, t4, t5, t6; End of 5.0 tests diff --git a/mysql-test/t/union.test b/mysql-test/t/union.test index 22f09466b1c..84616aa281d 100644 --- a/mysql-test/t/union.test +++ b/mysql-test/t/union.test @@ -877,4 +877,28 @@ DROP TABLE t1; select @var; --error 1172 (select 2) union (select 1 into @var); + +# +# Bug#32848: Data type conversion bug in union subselects in MySQL 5.0.38 +# +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1), (2), (3); + +CREATE TABLE t2 SELECT * FROM (SELECT NULL) a UNION SELECT a FROM t1; +DESC t2; + +CREATE TABLE t3 SELECT a FROM t1 UNION SELECT * FROM (SELECT NULL) a; +DESC t3; + +CREATE TABLE t4 SELECT NULL; +DESC t4; + +CREATE TABLE t5 SELECT NULL UNION SELECT NULL; +DESC t5; + +CREATE TABLE t6 +SELECT * FROM (SELECT * FROM (SELECT NULL)a) b UNION SELECT a FROM t1; +DESC t6; + +DROP TABLE t1, t2, t3, t4, t5, t6; --echo End of 5.0 tests diff --git a/sql/field.cc b/sql/field.cc index e6e4195ba1e..1a0e93c82ac 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -1304,7 +1304,8 @@ Field::Field(char *ptr_arg,uint32 length_arg,uchar *null_ptr_arg, field_name(field_name_arg), query_id(0), key_start(0), part_of_key(0), part_of_sortkey(0), unireg_check(unireg_check_arg), - field_length(length_arg), null_bit(null_bit_arg) + field_length(length_arg), null_bit(null_bit_arg), + is_created_from_null_item(FALSE) { flags=null_ptr ? 0: NOT_NULL_FLAG; comment.str= (char*) ""; diff --git a/sql/field.h b/sql/field.h index 4fcdb50f8c7..d9161d36dac 100644 --- a/sql/field.h +++ b/sql/field.h @@ -88,6 +88,16 @@ public: uint field_index; // field number in fields array uint16 flags; uchar null_bit; // Bit used to test null bit + /** + If true, this field was created in create_tmp_field_from_item from a NULL + value. This means that the type of the field is just a guess, and the type + may be freely coerced to another type. + + @see create_tmp_field_from_item + @see Item_type_holder::get_real_type + + */ + bool is_created_from_null_item; Field(char *ptr_arg,uint32 length_arg,uchar *null_ptr_arg,uchar null_bit_arg, utype unireg_check_arg, const char *field_name_arg, diff --git a/sql/item.cc b/sql/item.cc index 3177c0fb1e8..5df0f441619 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -6569,6 +6569,8 @@ enum_field_types Item_type_holder::get_real_type(Item *item) */ Field *field= ((Item_field *) item)->field; enum_field_types type= field->real_type(); + if (field->is_created_from_null_item) + return MYSQL_TYPE_NULL; /* work around about varchar type field detection */ if (type == MYSQL_TYPE_STRING && field->type() == MYSQL_TYPE_VAR_STRING) return MYSQL_TYPE_VAR_STRING; @@ -6820,6 +6822,8 @@ Field *Item_type_holder::make_field_by_type(TABLE *table) Field::NONE, name, table, get_set_pack_length(enum_set_typelib->count), enum_set_typelib, collation.collation); + case MYSQL_TYPE_NULL: + return make_string_field(table); default: break; } diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 3529de1c28a..74db7770c6f 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -8974,6 +8974,8 @@ static Field *create_tmp_field_from_item(THD *thd, Item *item, TABLE *table, *((*copy_func)++) = item; // Save for copy_funcs if (modify_item) item->set_result_field(new_field); + if (item->type() == MYSQL_TYPE_NULL) + new_field->is_created_from_null_item= TRUE; return new_field; } From 33c4431d39e19fce24c5d3df43fc474439bff115 Mon Sep 17 00:00:00 2001 From: "mhansson/martin@linux-st28.site" <> Date: Thu, 13 Dec 2007 11:19:05 +0100 Subject: [PATCH 08/25] Bug #32858: Erro: "Incorrect usage of UNION and INTO" does not take subselects into account It is forbidden to use the SELECT INTO construction inside UNION statements unless on the last SELECT of the union. The parser records whether it has seen INTO or not when parsing a UNION statement. But if the INTO was legally used in an outer query, an error is thrown if UNION is seen in a subquery. Fixed in 5.0 by remembering the nesting level of INTO tokens and mitigate the error unless it collides with the UNION. --- mysql-test/r/union.result | 42 ++++++++++++++++++++++++++++ mysql-test/t/union.test | 59 +++++++++++++++++++++++++++++++++++++++ sql/sql_class.cc | 1 + sql/sql_class.h | 37 ++++++++++++++++++++++-- sql/sql_yacc.yy | 25 +++++++++++------ 5 files changed, 152 insertions(+), 12 deletions(-) diff --git a/mysql-test/r/union.result b/mysql-test/r/union.result index efdd8195fb5..dadc6bb3558 100644 --- a/mysql-test/r/union.result +++ b/mysql-test/r/union.result @@ -1389,4 +1389,46 @@ select @var; 1 (select 2) union (select 1 into @var); ERROR 42000: Result consisted of more than one row +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1); +SELECT a INTO @v FROM ( +SELECT a FROM t1 +UNION +SELECT a FROM t1 +) alias; +SELECT a INTO OUTFILE 'union.out.file' FROM ( +SELECT a FROM t1 +UNION +SELECT a FROM t1 WHERE 0 +) alias; +SELECT a INTO DUMPFILE 'union.out.file2' FROM ( +SELECT a FROM t1 +UNION +SELECT a FROM t1 WHERE 0 +) alias; +SELECT a FROM ( +SELECT a FROM t1 +UNION +SELECT a INTO @v FROM t1 +) alias; +SELECT a FROM ( +SELECT a FROM t1 +UNION +SELECT a INTO OUTFILE 'union.out.file3' FROM t1 +) alias; +SELECT a FROM ( +SELECT a FROM t1 +UNION +SELECT a INTO DUMPFILE 'union.out.file4' FROM t1 +) alias; +SELECT a FROM t1 UNION SELECT a INTO @v FROM t1; +SELECT a FROM t1 UNION SELECT a INTO OUTFILE 'union.out.file5' FROM t1; +SELECT a FROM t1 UNION SELECT a INTO OUTFILE 'union.out.file6' FROM t1; +SELECT a INTO @v FROM t1 UNION SELECT a FROM t1; +ERROR HY000: Incorrect usage of UNION and INTO +SELECT a INTO OUTFILE 'union.out.file7' FROM t1 UNION SELECT a FROM t1; +ERROR HY000: Incorrect usage of UNION and INTO +SELECT a INTO DUMPFILE 'union.out.file8' FROM t1 UNION SELECT a FROM t1; +ERROR HY000: Incorrect usage of UNION and INTO +DROP TABLE t1; End of 5.0 tests diff --git a/mysql-test/t/union.test b/mysql-test/t/union.test index 22f09466b1c..54e00455d9a 100644 --- a/mysql-test/t/union.test +++ b/mysql-test/t/union.test @@ -877,4 +877,63 @@ DROP TABLE t1; select @var; --error 1172 (select 2) union (select 1 into @var); + +# +# Bug#32858: Erro: "Incorrect usage of UNION and INTO" does not take subselects +# into account +# +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1); + +SELECT a INTO @v FROM ( + SELECT a FROM t1 + UNION + SELECT a FROM t1 +) alias; + +SELECT a INTO OUTFILE 'union.out.file' FROM ( + SELECT a FROM t1 + UNION + SELECT a FROM t1 WHERE 0 +) alias; + +SELECT a INTO DUMPFILE 'union.out.file2' FROM ( + SELECT a FROM t1 + UNION + SELECT a FROM t1 WHERE 0 +) alias; + +# +# INTO will not be allowed in subqueries in version 5.1 and above. +# +SELECT a FROM ( + SELECT a FROM t1 + UNION + SELECT a INTO @v FROM t1 +) alias; + +SELECT a FROM ( + SELECT a FROM t1 + UNION + SELECT a INTO OUTFILE 'union.out.file3' FROM t1 +) alias; + +SELECT a FROM ( + SELECT a FROM t1 + UNION + SELECT a INTO DUMPFILE 'union.out.file4' FROM t1 +) alias; + +SELECT a FROM t1 UNION SELECT a INTO @v FROM t1; +SELECT a FROM t1 UNION SELECT a INTO OUTFILE 'union.out.file5' FROM t1; +SELECT a FROM t1 UNION SELECT a INTO OUTFILE 'union.out.file6' FROM t1; +--error ER_WRONG_USAGE +SELECT a INTO @v FROM t1 UNION SELECT a FROM t1; +--error ER_WRONG_USAGE +SELECT a INTO OUTFILE 'union.out.file7' FROM t1 UNION SELECT a FROM t1; +--error ER_WRONG_USAGE +SELECT a INTO DUMPFILE 'union.out.file8' FROM t1 UNION SELECT a FROM t1; + +DROP TABLE t1; + --echo End of 5.0 tests diff --git a/sql/sql_class.cc b/sql/sql_class.cc index b67f63778dc..87553837641 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -931,6 +931,7 @@ void THD::rollback_item_tree_changes() select_result::select_result() { thd=current_thd; + nest_level= -1; } void select_result::send_error(uint errcode,const char *err) diff --git a/sql/sql_class.h b/sql/sql_class.h index e6d65f3133a..80665f8b4dc 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1891,6 +1891,7 @@ class select_result :public Sql_alloc { protected: THD *thd; SELECT_LEX_UNIT *unit; + uint nest_level; public: select_result(); virtual ~select_result() {}; @@ -1927,6 +1928,12 @@ public: */ virtual void cleanup(); void set_thd(THD *thd_arg) { thd= thd_arg; } + /** + The nest level, if supported. + @return + -1 if nest level is undefined, otherwise a positive integer. + */ + int get_nest_level() { return nest_level; } #ifdef EMBEDDED_LIBRARY virtual void begin_dataset() {} #else @@ -2006,7 +2013,14 @@ class select_export :public select_to_file { bool is_unsafe_field_sep; bool fixed_row_size; public: - select_export(sql_exchange *ex) :select_to_file(ex) {} + /** + Creates a select_export to represent INTO OUTFILE with a + defined level of subquery nesting. + */ + select_export(sql_exchange *ex, uint nest_level_arg) :select_to_file(ex) + { + nest_level= nest_level_arg; + } ~select_export(); int prepare(List &list, SELECT_LEX_UNIT *u); bool send_data(List &items); @@ -2015,7 +2029,15 @@ public: class select_dump :public select_to_file { public: - select_dump(sql_exchange *ex) :select_to_file(ex) {} + /** + Creates a select_export to represent INTO DUMPFILE with a + defined level of subquery nesting. + */ + select_dump(sql_exchange *ex, uint nest_level_arg) : + select_to_file(ex) + { + nest_level= nest_level_arg; + } int prepare(List &list, SELECT_LEX_UNIT *u); bool send_data(List &items); }; @@ -2422,7 +2444,16 @@ class select_dumpvar :public select_result_interceptor { ha_rows row_count; public: List var_list; - select_dumpvar() { var_list.empty(); row_count= 0;} + /** + Creates a select_dumpvar to represent INTO with a defined + level of subquery nesting. + */ + select_dumpvar(uint nest_level_arg) + { + var_list.empty(); + row_count= 0; + nest_level= nest_level_arg; + } ~select_dumpvar() {} int prepare(List &list, SELECT_LEX_UNIT *u); bool send_data(List &items); diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 82100b3d3dd..0c2c8c2bc8e 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -6356,7 +6356,8 @@ procedure_item: select_var_list_init: { LEX *lex=Lex; - if (!lex->describe && (!(lex->result= new select_dumpvar()))) + if (!lex->describe && + (!(lex->result= new select_dumpvar(lex->nest_level)))) MYSQL_YYABORT; } select_var_list @@ -6430,7 +6431,7 @@ into_destination: LEX *lex= Lex; lex->uncacheable(UNCACHEABLE_SIDEEFFECT); if (!(lex->exchange= new sql_exchange($2.str, 0)) || - !(lex->result= new select_export(lex->exchange))) + !(lex->result= new select_export(lex->exchange, lex->nest_level))) MYSQL_YYABORT; } opt_field_term opt_line_term @@ -6442,7 +6443,7 @@ into_destination: lex->uncacheable(UNCACHEABLE_SIDEEFFECT); if (!(lex->exchange= new sql_exchange($2.str,1))) MYSQL_YYABORT; - if (!(lex->result= new select_dump(lex->exchange))) + if (!(lex->result= new select_dump(lex->exchange, lex->nest_level))) MYSQL_YYABORT; } } @@ -9421,12 +9422,18 @@ union_list: UNION_SYM union_option { LEX *lex=Lex; - if (lex->result) - { - /* Only the last SELECT can have INTO...... */ - my_error(ER_WRONG_USAGE, MYF(0), "UNION", "INTO"); - MYSQL_YYABORT; - } + if (lex->result && + (lex->result->get_nest_level() == -1 || + lex->result->get_nest_level() == lex->nest_level)) + { + /* + Only the last SELECT can have INTO unless the INTO and UNION + are at different nest levels. In version 5.1 and above, INTO + will onle be allowed at top level. + */ + my_error(ER_WRONG_USAGE, MYF(0), "UNION", "INTO"); + MYSQL_YYABORT; + } if (lex->current_select->linkage == GLOBAL_OPTIONS_TYPE) { my_parse_error(ER(ER_SYNTAX_ERROR)); From cdeecc515470e93a613caae769b94c7892fd4b04 Mon Sep 17 00:00:00 2001 From: "mhansson/martin@linux-st28.site" <> Date: Fri, 14 Dec 2007 12:24:20 +0100 Subject: [PATCH 09/25] Bug#32798: DISTINCT in GROUP_CONCAT clause fails when ordering by a column with null values For queries containing GROUP_CONCAT(DISTINCT fields ORDER BY fields), there was a limitation that the DISTINCT fields had to be the same as ORDER BY fields, owing to the fact that one single sorted tree was used for keeping track of tuples, ordering and uniqueness. Fixed by introducing a second structure to handle uniqueness so that the original structure has only to order the result. --- mysql-test/r/func_gconcat.result | 63 ++++++++++++- mysql-test/t/func_gconcat.test | 43 +++++++++ sql/item_sum.cc | 152 ++++++++++++++++--------------- sql/item_sum.h | 28 ++++-- 4 files changed, 203 insertions(+), 83 deletions(-) diff --git a/mysql-test/r/func_gconcat.result b/mysql-test/r/func_gconcat.result index 35487c25ae3..26592e4f898 100644 --- a/mysql-test/r/func_gconcat.result +++ b/mysql-test/r/func_gconcat.result @@ -271,7 +271,7 @@ group_concat(distinct s1 order by s2) c,b,a select group_concat(distinct s1 order by s2) from t1; group_concat(distinct s1 order by s2) -c,b,a,c +c,b,a drop table t1; create table t1 (a int, c int); insert into t1 values (1, 2), (2, 3), (2, 4), (3, 5); @@ -819,4 +819,65 @@ id group_concat(b.name) 1 óra,óra 2 óra,óra drop table t1; +CREATE TABLE t1 (a INT, b INT); +INSERT INTO t1 VALUES (1, 1), (2, 2), (2, 3); +SELECT GROUP_CONCAT(DISTINCT a ORDER BY b) FROM t1; +GROUP_CONCAT(DISTINCT a ORDER BY b) +1,2 +SELECT GROUP_CONCAT(DISTINCT a ORDER BY b DESC) FROM t1; +GROUP_CONCAT(DISTINCT a ORDER BY b DESC) +2,1 +SELECT GROUP_CONCAT(DISTINCT a) FROM t1; +GROUP_CONCAT(DISTINCT a) +1,2 +SELECT GROUP_CONCAT(DISTINCT a + 1 ORDER BY 3 - b) FROM t1; +GROUP_CONCAT(DISTINCT a + 1 ORDER BY 3 - b) +3,2 +SELECT GROUP_CONCAT(DISTINCT a + 1 ORDER BY b) FROM t1; +GROUP_CONCAT(DISTINCT a + 1 ORDER BY b) +2,3 +SELECT GROUP_CONCAT(a ORDER BY 3 - b) FROM t1; +GROUP_CONCAT(a ORDER BY 3 - b) +2,2,1 +CREATE TABLE t2 (a INT, b INT, c INT, d INT); +INSERT INTO t2 VALUES (1,1, 1,1), (1,1, 2,2), (1,2, 2,1), (2,1, 1,2); +SELECT GROUP_CONCAT(DISTINCT a, b ORDER BY c, d) FROM t2; +GROUP_CONCAT(DISTINCT a, b ORDER BY c, d) +11,21,12 +SELECT GROUP_CONCAT(DISTINCT a, b ORDER BY d, c) FROM t2; +GROUP_CONCAT(DISTINCT a, b ORDER BY d, c) +11,12,21 +CREATE TABLE t3 (a INT, b INT, c INT); +INSERT INTO t3 VALUES (1, 1, 1), (2, 1, 2), (3, 2, 1); +SELECT GROUP_CONCAT(DISTINCT a, b ORDER BY b, c) FROM t3; +GROUP_CONCAT(DISTINCT a, b ORDER BY b, c) +11,21,32 +SELECT GROUP_CONCAT(DISTINCT a, b ORDER BY c, b) FROM t3; +GROUP_CONCAT(DISTINCT a, b ORDER BY c, b) +11,32,21 +SELECT GROUP_CONCAT(DISTINCT a, b ORDER BY a, b) FROM t1; +GROUP_CONCAT(DISTINCT a, b ORDER BY a, b) +11,22,23 +SELECT GROUP_CONCAT(DISTINCT b, a ORDER BY a, b) FROM t1; +GROUP_CONCAT(DISTINCT b, a ORDER BY a, b) +11,22,32 +SELECT GROUP_CONCAT(DISTINCT a, b ORDER BY b, a) FROM t1; +GROUP_CONCAT(DISTINCT a, b ORDER BY b, a) +11,22,23 +SELECT GROUP_CONCAT(DISTINCT b, a ORDER BY a, b) FROM t1; +GROUP_CONCAT(DISTINCT b, a ORDER BY a, b) +11,22,32 +SELECT GROUP_CONCAT(DISTINCT a ORDER BY a, b) FROM t1; +GROUP_CONCAT(DISTINCT a ORDER BY a, b) +1,2 +SELECT GROUP_CONCAT(DISTINCT b ORDER BY b, a) FROM t1; +GROUP_CONCAT(DISTINCT b ORDER BY b, a) +1,2,3 +SELECT GROUP_CONCAT(DISTINCT a, b ORDER BY a) FROM t1; +GROUP_CONCAT(DISTINCT a, b ORDER BY a) +11,23,22 +SELECT GROUP_CONCAT(DISTINCT b, a ORDER BY b) FROM t1; +GROUP_CONCAT(DISTINCT b, a ORDER BY b) +11,22,32 +DROP TABLE t1, t2, t3; End of 5.0 tests diff --git a/mysql-test/t/func_gconcat.test b/mysql-test/t/func_gconcat.test index ff3ba951870..85f81520863 100644 --- a/mysql-test/t/func_gconcat.test +++ b/mysql-test/t/func_gconcat.test @@ -562,4 +562,47 @@ insert into t1 (id, name) values (2, " select b.id, group_concat(b.name) from t1 a, t1 b group by b.id; drop table t1; +# +# Bug#32798: DISTINCT in GROUP_CONCAT clause fails when ordering by a column +# with null values +#' +CREATE TABLE t1 (a INT, b INT); + +INSERT INTO t1 VALUES (1, 1), (2, 2), (2, 3); + +SELECT GROUP_CONCAT(DISTINCT a ORDER BY b) FROM t1; +SELECT GROUP_CONCAT(DISTINCT a ORDER BY b DESC) FROM t1; +SELECT GROUP_CONCAT(DISTINCT a) FROM t1; + +SELECT GROUP_CONCAT(DISTINCT a + 1 ORDER BY 3 - b) FROM t1; +SELECT GROUP_CONCAT(DISTINCT a + 1 ORDER BY b) FROM t1; +SELECT GROUP_CONCAT(a ORDER BY 3 - b) FROM t1; + +CREATE TABLE t2 (a INT, b INT, c INT, d INT); + +# There is one duplicate in the expression list: 1,10 +# There is one duplicate in ORDER BY list, but that shouldnt matter: 1,10 +INSERT INTO t2 VALUES (1,1, 1,1), (1,1, 2,2), (1,2, 2,1), (2,1, 1,2); + +SELECT GROUP_CONCAT(DISTINCT a, b ORDER BY c, d) FROM t2; +SELECT GROUP_CONCAT(DISTINCT a, b ORDER BY d, c) FROM t2; + +CREATE TABLE t3 (a INT, b INT, c INT); + +INSERT INTO t3 VALUES (1, 1, 1), (2, 1, 2), (3, 2, 1); + +SELECT GROUP_CONCAT(DISTINCT a, b ORDER BY b, c) FROM t3; +SELECT GROUP_CONCAT(DISTINCT a, b ORDER BY c, b) FROM t3; + +SELECT GROUP_CONCAT(DISTINCT a, b ORDER BY a, b) FROM t1; +SELECT GROUP_CONCAT(DISTINCT b, a ORDER BY a, b) FROM t1; +SELECT GROUP_CONCAT(DISTINCT a, b ORDER BY b, a) FROM t1; +SELECT GROUP_CONCAT(DISTINCT b, a ORDER BY a, b) FROM t1; +SELECT GROUP_CONCAT(DISTINCT a ORDER BY a, b) FROM t1; +SELECT GROUP_CONCAT(DISTINCT b ORDER BY b, a) FROM t1; +SELECT GROUP_CONCAT(DISTINCT a, b ORDER BY a) FROM t1; +SELECT GROUP_CONCAT(DISTINCT b, a ORDER BY b) FROM t1; + +DROP TABLE t1, t2, t3; + --echo End of 5.0 tests diff --git a/sql/item_sum.cc b/sql/item_sum.cc index 92b6b57af08..74b8115e354 100644 --- a/sql/item_sum.cc +++ b/sql/item_sum.cc @@ -2831,44 +2831,51 @@ String *Item_sum_udf_str::val_str(String *str) concat of values from "group by" operation BUGS - DISTINCT and ORDER BY only works if ORDER BY uses all fields and only fields - in expression list Blobs doesn't work with DISTINCT or ORDER BY *****************************************************************************/ -/* - function of sort for syntax: - GROUP_CONCAT(DISTINCT expr,...) + + +/** + Compares the values for fields in expr list of GROUP_CONCAT. + @note + + GROUP_CONCAT([DISTINCT] expr [,expr ...] + [ORDER BY {unsigned_integer | col_name | expr} + [ASC | DESC] [,col_name ...]] + [SEPARATOR str_val]) + + @return + @retval -1 : key1 < key2 + @retval 0 : key1 = key2 + @retval 1 : key1 > key2 */ -int group_concat_key_cmp_with_distinct(void* arg, byte* key1, - byte* key2) +int group_concat_key_cmp_with_distinct(void* arg, const void* key1, + const void* key2) { - Item_func_group_concat* grp_item= (Item_func_group_concat*)arg; - TABLE *table= grp_item->table; - Item **field_item, **end; + Item_func_group_concat *item_func= (Item_func_group_concat*)arg; + TABLE *table= item_func->table; - for (field_item= grp_item->args, end= field_item + grp_item->arg_count_field; - field_item < end; - field_item++) + for (uint i= 0; i < item_func->arg_count_field; i++) { + Item *item= item_func->args[i]; + /* + If field_item is a const item then either get_tp_table_field returns 0 + or it is an item over a const table. + */ + if (item->const_item()) + continue; /* We have to use get_tmp_table_field() instead of real_item()->get_tmp_table_field() because we want the field in the temporary table, not the original field */ - Field *field= (*field_item)->get_tmp_table_field(); - /* - If field_item is a const item then either get_tp_table_field returns 0 - or it is an item over a const table. - */ - if (field && !(*field_item)->const_item()) - { - int res; - uint offset= field->offset() - table->s->null_bytes; - if ((res= field->cmp((char *) key1 + offset, (char *) key2 + offset))) - return res; - } + Field *field= item->get_tmp_table_field(); + int res; + uint offset= field->offset()-table->s->null_bytes; + if((res= field->cmp((char*)key1 + offset, (char*)key2 + offset))) + return res; } return 0; } @@ -2879,7 +2886,8 @@ int group_concat_key_cmp_with_distinct(void* arg, byte* key1, GROUP_CONCAT(expr,... ORDER BY col,... ) */ -int group_concat_key_cmp_with_order(void* arg, byte* key1, byte* key2) +int group_concat_key_cmp_with_order(void* arg, const void* key1, + const void* key2) { Item_func_group_concat* grp_item= (Item_func_group_concat*) arg; ORDER **order_item, **end; @@ -2917,25 +2925,6 @@ int group_concat_key_cmp_with_order(void* arg, byte* key1, byte* key2) } -/* - function of sort for syntax: - GROUP_CONCAT(DISTINCT expr,... ORDER BY col,... ) - - BUG: - This doesn't work in the case when the order by contains data that - is not part of the field list because tree-insert will not notice - the duplicated values when inserting things sorted by ORDER BY -*/ - -int group_concat_key_cmp_with_distinct_and_order(void* arg,byte* key1, - byte* key2) -{ - if (!group_concat_key_cmp_with_distinct(arg,key1,key2)) - return 0; - return(group_concat_key_cmp_with_order(arg,key1,key2)); -} - - /* Append data from current leaf to item->result */ @@ -3020,7 +3009,7 @@ Item_func_group_concat(Name_resolution_context *context_arg, bool distinct_arg, List *select_list, SQL_LIST *order_list, String *separator_arg) :tmp_table_param(0), warning(0), - separator(separator_arg), tree(0), table(0), + separator(separator_arg), tree(0), unique_filter(NULL), table(0), order(0), context(context_arg), arg_count_order(order_list ? order_list->elements : 0), arg_count_field(select_list->elements), @@ -3075,6 +3064,7 @@ Item_func_group_concat::Item_func_group_concat(THD *thd, warning(item->warning), separator(item->separator), tree(item->tree), + unique_filter(item->unique_filter), table(item->table), order(item->order), context(item->context), @@ -3125,6 +3115,11 @@ void Item_func_group_concat::cleanup() delete_tree(tree); tree= 0; } + if (unique_filter) + { + delete unique_filter; + unique_filter= NULL; + } if (warning) { char warn_buff[MYSQL_ERRMSG_SIZE]; @@ -3154,6 +3149,8 @@ void Item_func_group_concat::clear() no_appended= TRUE; if (tree) reset_tree(tree); + if (distinct) + unique_filter->reset(); /* No need to reset the table as we never call write_row */ } @@ -3177,9 +3174,19 @@ bool Item_func_group_concat::add() } null_value= FALSE; + bool row_eligible= TRUE; + + if (distinct) + { + /* Filter out duplicate rows. */ + uint count= unique_filter->elements_in_tree(); + unique_filter->unique_add(table->record[0] + table->s->null_bytes); + if (count == unique_filter->elements_in_tree()) + row_eligible= FALSE; + } TREE_ELEMENT *el= 0; // Only for safety - if (tree) + if (row_eligible && tree) el= tree_insert(tree, table->record[0] + table->s->null_bytes, 0, tree->custom_arg); /* @@ -3187,7 +3194,7 @@ bool Item_func_group_concat::add() we can dump the row here in case of GROUP_CONCAT(DISTINCT...) instead of doing tree traverse later. */ - if (!warning_for_row && + if (row_eligible && !warning_for_row && (!tree || (el->count == 1 && distinct && !arg_count_order))) dump_leaf_key(table->record[0] + table->s->null_bytes, 1, this); @@ -3263,7 +3270,6 @@ bool Item_func_group_concat::setup(THD *thd) { List list; SELECT_LEX *select_lex= thd->lex->current_select; - qsort_cmp2 compare_key; DBUG_ENTER("Item_func_group_concat::setup"); /* @@ -3334,38 +3340,33 @@ bool Item_func_group_concat::setup(THD *thd) table->file->extra(HA_EXTRA_NO_ROWS); table->no_rows= 1; + /* + Need sorting or uniqueness: init tree and choose a function to sort. + Don't reserve space for NULLs: if any of gconcat arguments is NULL, + the row is not added to the result. + */ + uint tree_key_length= table->s->reclength - table->s->null_bytes; - if (distinct || arg_count_order) + if (arg_count_order) { - /* - Need sorting: init tree and choose a function to sort. - Don't reserve space for NULLs: if any of gconcat arguments is NULL, - the row is not added to the result. - */ - uint tree_key_length= table->s->reclength - table->s->null_bytes; - tree= &tree_base; - if (arg_count_order) - { - if (distinct) - compare_key= (qsort_cmp2) group_concat_key_cmp_with_distinct_and_order; - else - compare_key= (qsort_cmp2) group_concat_key_cmp_with_order; - } - else - { - compare_key= (qsort_cmp2) group_concat_key_cmp_with_distinct; - } /* - Create a tree for sorting. The tree is used to sort and to remove - duplicate values (according to the syntax of this function). If there - is no DISTINCT or ORDER BY clauses, we don't create this tree. + Create a tree for sorting. The tree is used to sort (according to the + syntax of this function). If there is no ORDER BY clause, we don't + create this tree. */ init_tree(tree, (uint) min(thd->variables.max_heap_table_size, thd->variables.sortbuff_size/16), 0, - tree_key_length, compare_key, 0, NULL, (void*) this); + tree_key_length, + group_concat_key_cmp_with_order , 0, NULL, (void*) this); } + if (distinct) + unique_filter= new Unique(group_concat_key_cmp_with_distinct, + (void*)this, + tree_key_length, + thd->variables.max_heap_table_size); + DBUG_RETURN(FALSE); } @@ -3435,3 +3436,10 @@ void Item_func_group_concat::print(String *str) str->append(*separator); str->append(STRING_WITH_LEN("\')")); } + + +Item_func_group_concat::~Item_func_group_concat() +{ + if (unique_filter) + delete unique_filter; +} diff --git a/sql/item_sum.h b/sql/item_sum.h index d18454cc3b8..bf0abe53eea 100644 --- a/sql/item_sum.h +++ b/sql/item_sum.h @@ -1173,11 +1173,22 @@ class Item_func_group_concat : public Item_sum String *separator; TREE tree_base; TREE *tree; + + /** + If DISTINCT is used with this GROUP_CONCAT, this member is used to filter + out duplicates. + @see Item_func_group_concat::setup + @see Item_func_group_concat::add + @see Item_func_group_concat::clear + */ + Unique *unique_filter; TABLE *table; ORDER **order; Name_resolution_context *context; - uint arg_count_order; // total count of ORDER BY items - uint arg_count_field; // count of arguments + /** The number of ORDER BY items. */ + uint arg_count_order; + /** The number of selected items, aka the expr list. */ + uint arg_count_field; uint count_cut_values; bool distinct; bool warning_for_row; @@ -1190,13 +1201,10 @@ class Item_func_group_concat : public Item_sum */ Item_func_group_concat *original; - friend int group_concat_key_cmp_with_distinct(void* arg, byte* key1, - byte* key2); - friend int group_concat_key_cmp_with_order(void* arg, byte* key1, - byte* key2); - friend int group_concat_key_cmp_with_distinct_and_order(void* arg, - byte* key1, - byte* key2); + friend int group_concat_key_cmp_with_distinct(void* arg, const void* key1, + const void* key2); + friend int group_concat_key_cmp_with_order(void* arg, const void* key1, + const void* key2); friend int dump_leaf_key(byte* key, element_count count __attribute__((unused)), Item_func_group_concat *group_concat_item); @@ -1207,7 +1215,7 @@ public: SQL_LIST *is_order, String *is_separator); Item_func_group_concat(THD *thd, Item_func_group_concat *item); - ~Item_func_group_concat() {} + ~Item_func_group_concat(); void cleanup(); enum Sumfunctype sum_func () const {return GROUP_CONCAT_FUNC;} From f282ce6fb6a66ea04af2c9b07061b2c93c3d3aec Mon Sep 17 00:00:00 2001 From: "mhansson/martin@linux-st28.site" <> Date: Fri, 14 Dec 2007 13:35:52 +0100 Subject: [PATCH 10/25] Bug#32798: DISTINCT in GROUP_CONCAT clause fails when ordering by a column with null values Post-merge fix --- sql/item_sum.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/item_sum.cc b/sql/item_sum.cc index cbd86a760be..6da51cedb8b 100644 --- a/sql/item_sum.cc +++ b/sql/item_sum.cc @@ -2931,7 +2931,7 @@ int group_concat_key_cmp_with_order(void* arg, const void* key1, int res; uint offset= (field->offset(field->table->record[0]) - table->s->null_bytes); - if ((res= field->cmp(key1 + offset, key2 + offset))) + if ((res= field->cmp((uchar*)key1 + offset, (uchar*)key2 + offset))) return (*order_item)->asc ? res : -res; } } From f25e30ebed447b3749ecea0efbb0c80a2e71b34f Mon Sep 17 00:00:00 2001 From: "igor@olga.mysql.com" <> Date: Fri, 14 Dec 2007 13:42:46 -0800 Subject: [PATCH 11/25] Fixed bug #27848. In a union without braces, the order by at the end is applied to the overall union. It therefore should not interfere with the individual select parts of the union. Fixed by changing our parser rules appropriately. --- mysql-test/r/union.result | 51 +++++++++++++++++++++++++++++++++++++++ mysql-test/t/union.test | 44 +++++++++++++++++++++++++++++++++ sql/sql_yacc.yy | 11 ++++++++- 3 files changed, 105 insertions(+), 1 deletion(-) diff --git a/mysql-test/r/union.result b/mysql-test/r/union.result index efdd8195fb5..c7a6035aab2 100644 --- a/mysql-test/r/union.result +++ b/mysql-test/r/union.result @@ -1389,4 +1389,55 @@ select @var; 1 (select 2) union (select 1 into @var); ERROR 42000: Result consisted of more than one row +CREATE TABLE t1 (a int); +INSERT INTO t1 VALUES (10), (20); +CREATE TABLE t2 (b int); +INSERT INTO t2 VALUES (10), (50), (50); +SELECT a,1 FROM t1 +UNION +SELECT b, COUNT(*) FROM t2 GROUP BY b WITH ROLLUP +ORDER BY a; +a 1 +NULL 3 +10 1 +20 1 +50 2 +SELECT a,1 FROM t1 +UNION +SELECT b, COUNT(*) FROM t2 GROUP BY b WITH ROLLUP +ORDER BY a DESC; +a 1 +50 2 +20 1 +10 1 +NULL 3 +SELECT a,1 FROM t1 +UNION +SELECT b, COUNT(*) FROM t2 GROUP BY b WITH ROLLUP +ORDER BY a ASC LIMIT 3; +a 1 +NULL 3 +10 1 +20 1 +SELECT a,1 FROM t1 +UNION ALL +SELECT b, COUNT(*) FROM t2 GROUP BY b WITH ROLLUP +ORDER BY a DESC; +a 1 +50 2 +20 1 +10 1 +10 1 +NULL 3 +SELECT a,1 FROM t1 +UNION +(SELECT b, COUNT(*) FROM t2 GROUP BY b WITH ROLLUP ORDER BY a); +ERROR HY000: Incorrect usage of CUBE/ROLLUP and ORDER BY +SELECT a,1 FROM t1 +UNION ALL +SELECT b, COUNT(*) FROM t2 GROUP BY b WITH ROLLUP ORDER BY a +UNION +SELECT 1,1; +ERROR HY000: Incorrect usage of UNION and ORDER BY +DROP TABLE t1,t2; End of 5.0 tests diff --git a/mysql-test/t/union.test b/mysql-test/t/union.test index 22f09466b1c..0277b01e21a 100644 --- a/mysql-test/t/union.test +++ b/mysql-test/t/union.test @@ -877,4 +877,48 @@ DROP TABLE t1; select @var; --error 1172 (select 2) union (select 1 into @var); + +# +# Bug#27848: order-by of union clashes with rollup of select part +# + +CREATE TABLE t1 (a int); +INSERT INTO t1 VALUES (10), (20); +CREATE TABLE t2 (b int); +INSERT INTO t2 VALUES (10), (50), (50); + +SELECT a,1 FROM t1 +UNION +SELECT b, COUNT(*) FROM t2 GROUP BY b WITH ROLLUP +ORDER BY a; + +SELECT a,1 FROM t1 +UNION +SELECT b, COUNT(*) FROM t2 GROUP BY b WITH ROLLUP +ORDER BY a DESC; + +SELECT a,1 FROM t1 +UNION +SELECT b, COUNT(*) FROM t2 GROUP BY b WITH ROLLUP +ORDER BY a ASC LIMIT 3; + +SELECT a,1 FROM t1 +UNION ALL +SELECT b, COUNT(*) FROM t2 GROUP BY b WITH ROLLUP +ORDER BY a DESC; + +--error ER_WRONG_USAGE +SELECT a,1 FROM t1 +UNION +(SELECT b, COUNT(*) FROM t2 GROUP BY b WITH ROLLUP ORDER BY a); + +--error ER_WRONG_USAGE +SELECT a,1 FROM t1 +UNION ALL +SELECT b, COUNT(*) FROM t2 GROUP BY b WITH ROLLUP ORDER BY a +UNION +SELECT 1,1; + +DROP TABLE t1,t2; + --echo End of 5.0 tests diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index bd85ce55edb..170ae1b889b 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -4211,6 +4211,14 @@ select_paren: my_parse_error(ER(ER_SYNTAX_ERROR)); MYSQL_YYABORT; } + if (sel->linkage == UNION_TYPE && + sel->olap != UNSPECIFIED_OLAP_TYPE && + sel->master_unit()->fake_select_lex) + { + my_error(ER_WRONG_USAGE, MYF(0), + "CUBE/ROLLUP", "ORDER BY"); + MYSQL_YYABORT; + } /* select in braces, can't contain global parameters */ if (sel->master_unit()->fake_select_lex) sel->master_unit()->global_parameters= @@ -6165,7 +6173,8 @@ order_clause: SELECT_LEX *sel= lex->current_select; SELECT_LEX_UNIT *unit= sel-> master_unit(); if (sel->linkage != GLOBAL_OPTIONS_TYPE && - sel->olap != UNSPECIFIED_OLAP_TYPE) + sel->olap != UNSPECIFIED_OLAP_TYPE && + (sel->linkage != UNION_TYPE || sel->braces)) { my_error(ER_WRONG_USAGE, MYF(0), "CUBE/ROLLUP", "ORDER BY"); From b52ac74d54763aba52642606fc4d013a78d6759c Mon Sep 17 00:00:00 2001 From: "tnurnberg@mysql.com/white.intern.koehntopp.de" <> Date: Mon, 17 Dec 2007 06:13:26 +0100 Subject: [PATCH 12/25] Bug#32350: mysqldump should show more version information in output show version of mysqldump, release of server et al., and version of current server in mysqldumps. --- client/mysqldump.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/mysqldump.c b/client/mysqldump.c index 3bf9fff1b86..b24da4b0847 100644 --- a/client/mysqldump.c +++ b/client/mysqldump.c @@ -477,7 +477,9 @@ static void write_header(FILE *sql_file, char *db_name) { if (opt_comments) { - fprintf(sql_file, "-- MySQL dump %s\n--\n", DUMP_VERSION); + fprintf(sql_file, + "-- MySQL dump %s Distrib %s, for %s (%s)\n--\n", + DUMP_VERSION, MYSQL_SERVER_VERSION, SYSTEM_TYPE, MACHINE_TYPE); fprintf(sql_file, "-- Host: %s Database: %s\n", current_host ? current_host : "localhost", db_name ? db_name : ""); From 491b664fadeccf9243a491f7e8416f15e9171023 Mon Sep 17 00:00:00 2001 From: "tnurnberg@mysql.com/white.intern.koehntopp.de" <> Date: Mon, 17 Dec 2007 10:43:19 +0100 Subject: [PATCH 13/25] Bug#31752: check strmake() bounds build fixes --- strings/strmake.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/strings/strmake.c b/strings/strmake.c index 05b5878d99c..df8d78e8476 100644 --- a/strings/strmake.c +++ b/strings/strmake.c @@ -27,7 +27,7 @@ #include #include "m_string.h" -char *strmake(register char *dst, register const char *src, uint length) +char *strmake(register char *dst, register const char *src, size_t length) { #ifdef EXTRA_DEBUG /* From 93894a444e5a523d6d499725305c3b389b20294b Mon Sep 17 00:00:00 2001 From: "tnurnberg@mysql.com/white.intern.koehntopp.de" <> Date: Mon, 17 Dec 2007 12:09:21 +0100 Subject: [PATCH 14/25] Bug#31752: check strmake() bounds fix test program --- client/mysqltest.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/mysqltest.c b/client/mysqltest.c index 04dfce28ab4..d5a03961364 100644 --- a/client/mysqltest.c +++ b/client/mysqltest.c @@ -3067,7 +3067,7 @@ void do_get_file_name(struct st_command *command, if (*p) *p++= 0; command->last_argument= p; - strmake(dest, name, dest_max_len); + strmake(dest, name, dest_max_len - 1); } @@ -6375,7 +6375,7 @@ int main(int argc, char **argv) if (save_file[0]) { - strmake(command->require_file, save_file, sizeof(save_file)); + strmake(command->require_file, save_file, sizeof(save_file) - 1); save_file[0]= 0; } run_query(cur_con, command, flags); From 695342d6768d603fa4ed066b629ce9aae6d92034 Mon Sep 17 00:00:00 2001 From: "gkodinov/kgeorge@magare.gmz" <> Date: Mon, 17 Dec 2007 18:56:39 +0200 Subject: [PATCH 15/25] Bug #19390: Test 'rpl_trigger' fails, might be random The checks in the test for bug #12480 were too wide and made the test to depend on the procedures and triggers present in the server. Corrected the test to check only for the procedure and trigger it creates. --- mysql-test/r/rpl_trigger.result | 12 ++++++++---- mysql-test/t/rpl_trigger.test | 12 ++++++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/mysql-test/r/rpl_trigger.result b/mysql-test/r/rpl_trigger.result index f8573eec75f..51e7c15bc12 100644 --- a/mysql-test/r/rpl_trigger.result +++ b/mysql-test/r/rpl_trigger.result @@ -88,21 +88,25 @@ select a=b && a=c from t1; a=b && a=c 1 SELECT routine_name, definer -FROM information_schema.routines; +FROM information_schema.routines +WHERE routine_name = 'bug12480'; routine_name definer bug12480 root@localhost SELECT trigger_name, definer -FROM information_schema.triggers; +FROM information_schema.triggers +WHERE trigger_name = 't1_first'; trigger_name definer t1_first root@localhost --- On slave -- SELECT routine_name, definer -FROM information_schema.routines; +FROM information_schema.routines +WHERE routine_name = 'bug12480'; routine_name definer bug12480 root@localhost SELECT trigger_name, definer -FROM information_schema.triggers; +FROM information_schema.triggers +WHERE trigger_name = 't1_first'; trigger_name definer t1_first root@localhost select a=b && a=c from t1; diff --git a/mysql-test/t/rpl_trigger.test b/mysql-test/t/rpl_trigger.test index faba89e7a73..da6cea10698 100644 --- a/mysql-test/t/rpl_trigger.test +++ b/mysql-test/t/rpl_trigger.test @@ -93,10 +93,12 @@ let $time=`select a from t1`; # - dump definers on the slave; SELECT routine_name, definer -FROM information_schema.routines; +FROM information_schema.routines +WHERE routine_name = 'bug12480'; SELECT trigger_name, definer -FROM information_schema.triggers; +FROM information_schema.triggers +WHERE trigger_name = 't1_first'; save_master_pos; connection slave; @@ -111,10 +113,12 @@ select "--- On slave --" as ""; # item. SELECT routine_name, definer -FROM information_schema.routines; +FROM information_schema.routines +WHERE routine_name = 'bug12480'; SELECT trigger_name, definer -FROM information_schema.triggers; +FROM information_schema.triggers +WHERE trigger_name = 't1_first'; select a=b && a=c from t1; --disable_query_log From 5a9225f4c2fe886c7fb411091deba31c0a5042d9 Mon Sep 17 00:00:00 2001 From: "tnurnberg@mysql.com/white.intern.koehntopp.de" <> Date: Thu, 20 Dec 2007 08:15:34 +0100 Subject: [PATCH 16/25] Bug#31990: MINUTE() and SECOND() return bogus results when used on a DATE post-merge fixes --- mysql-test/r/type_date.result | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mysql-test/r/type_date.result b/mysql-test/r/type_date.result index 7d5dabf1929..17ccf63c349 100644 --- a/mysql-test/r/type_date.result +++ b/mysql-test/r/type_date.result @@ -260,4 +260,9 @@ INSERT INTO t1 VALUES ('1000-00-00'); ERROR 22007: Incorrect date value: '1000-00-00' for column 'a' at row 1 SET SQL_MODE=DEFAULT; DROP TABLE t1,t2; +CREATE TABLE t1 SELECT curdate() AS f1; +SELECT hour(f1), minute(f1), second(f1) FROM t1; +hour(f1) minute(f1) second(f1) +0 0 0 +DROP TABLE t1; End of 5.0 tests From 6f6e18809a43f0b76490ef04d0e730b6c7cb260a Mon Sep 17 00:00:00 2001 From: "mhansson/martin@linux-st28.site" <> Date: Thu, 20 Dec 2007 10:58:21 +0100 Subject: [PATCH 17/25] Bug#32848: Data type conversion bug in union subselects in MySQL 5.0.38 Warnings elimination --- sql/sql_select.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 74db7770c6f..ed4b10e6221 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -8974,7 +8974,7 @@ static Field *create_tmp_field_from_item(THD *thd, Item *item, TABLE *table, *((*copy_func)++) = item; // Save for copy_funcs if (modify_item) item->set_result_field(new_field); - if (item->type() == MYSQL_TYPE_NULL) + if (item->type() == Item::NULL_ITEM) new_field->is_created_from_null_item= TRUE; return new_field; } From e4efb4a521b98355346b164431ef19108894f396 Mon Sep 17 00:00:00 2001 From: "gkodinov/kgeorge@macbook.gmz" <> Date: Fri, 21 Dec 2007 12:44:24 +0200 Subject: [PATCH 18/25] Bug #33256: CREATE ... SELECT creates obsolete table w/ Field_date instead of Field_newdate Field_date was still used in temp table creation. Fixed by using Field_newdate consistently throughout the server except when reading tables defined with older MySQL version. No test suite is possible because both Field_date and Field_newdate return the same values in all the metadata calls. --- mysql-test/r/type_decimal.result | 4 ---- sql/field.h | 4 ++++ sql/item.cc | 2 +- sql/item_sum.cc | 2 +- sql/item_timefunc.cc | 2 +- sql/item_timefunc.h | 6 +++--- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/mysql-test/r/type_decimal.result b/mysql-test/r/type_decimal.result index a438755ce6b..a2ccf042165 100644 --- a/mysql-test/r/type_decimal.result +++ b/mysql-test/r/type_decimal.result @@ -786,10 +786,6 @@ from (select 1 as s,'t' as t union select null, null ) as sub1; select group_concat(t) from t1 group by week(date)/10; group_concat(t) t -Warnings: -Warning 1292 Truncated incorrect datetime value: '0000-00-00' -Warning 1292 Truncated incorrect datetime value: '0000-00-00' -Warning 1292 Truncated incorrect datetime value: '0000-00-00' drop table t1; CREATE TABLE t1 ( qty decimal(16,6) default NULL, diff --git a/sql/field.h b/sql/field.h index e5c473ed556..476ca797fa0 100644 --- a/sql/field.h +++ b/sql/field.h @@ -961,6 +961,10 @@ public: :Field_str(ptr_arg, 10, null_ptr_arg, null_bit_arg, unireg_check_arg, field_name_arg, table_arg, cs) {} + Field_newdate(bool maybe_null_arg, const char *field_name_arg, + struct st_table *table_arg, CHARSET_INFO *cs) + :Field_str((char*) 0,10, maybe_null_arg ? (uchar*) "": 0,0, + NONE, field_name_arg, table_arg, cs) {} enum_field_types type() const { return FIELD_TYPE_DATE;} enum_field_types real_type() const { return FIELD_TYPE_NEWDATE; } enum ha_base_keytype key_type() const { return HA_KEYTYPE_UINT24; } diff --git a/sql/item.cc b/sql/item.cc index 2d49c7b6d60..cafa40ecbb0 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -4384,7 +4384,7 @@ Field *Item::tmp_table_field_from_field_type(TABLE *table) name, table, 0, unsigned_flag); case MYSQL_TYPE_NEWDATE: case MYSQL_TYPE_DATE: - return new Field_date(maybe_null, name, table, &my_charset_bin); + return new Field_newdate(maybe_null, name, table, &my_charset_bin); case MYSQL_TYPE_TIME: return new Field_time(maybe_null, name, table, &my_charset_bin); case MYSQL_TYPE_TIMESTAMP: diff --git a/sql/item_sum.cc b/sql/item_sum.cc index 7d00de493aa..3d261dc2c36 100644 --- a/sql/item_sum.cc +++ b/sql/item_sum.cc @@ -628,7 +628,7 @@ Field *Item_sum_hybrid::create_tmp_field(bool group, TABLE *table, */ switch (args[0]->field_type()) { case MYSQL_TYPE_DATE: - return new Field_date(maybe_null, name, table, collation.collation); + return new Field_newdate(maybe_null, name, table, collation.collation); case MYSQL_TYPE_TIME: return new Field_time(maybe_null, name, table, collation.collation); case MYSQL_TYPE_TIMESTAMP: diff --git a/sql/item_timefunc.cc b/sql/item_timefunc.cc index 04060d190a0..0cb3c963dad 100644 --- a/sql/item_timefunc.cc +++ b/sql/item_timefunc.cc @@ -3303,7 +3303,7 @@ Field *Item_func_str_to_date::tmp_table_field(TABLE *t_arg) if (cached_field_type == MYSQL_TYPE_TIME) return (new Field_time(maybe_null, name, t_arg, &my_charset_bin)); if (cached_field_type == MYSQL_TYPE_DATE) - return (new Field_date(maybe_null, name, t_arg, &my_charset_bin)); + return (new Field_newdate(maybe_null, name, t_arg, &my_charset_bin)); if (cached_field_type == MYSQL_TYPE_DATETIME) return (new Field_datetime(maybe_null, name, t_arg, &my_charset_bin)); return (new Field_string(max_length, maybe_null, name, t_arg, &my_charset_bin)); diff --git a/sql/item_timefunc.h b/sql/item_timefunc.h index b647e93b700..7960c03d2e5 100644 --- a/sql/item_timefunc.h +++ b/sql/item_timefunc.h @@ -340,7 +340,7 @@ public: } Field *tmp_table_field(TABLE *t_arg) { - return (new Field_date(maybe_null, name, t_arg, &my_charset_bin)); + return (new Field_newdate(maybe_null, name, t_arg, &my_charset_bin)); } bool result_as_longlong() { return TRUE; } my_decimal *val_decimal(my_decimal *decimal_value) @@ -784,7 +784,7 @@ public: enum_field_types field_type() const { return MYSQL_TYPE_DATE; } Field *tmp_table_field(TABLE *t_arg) { - return (new Field_date(maybe_null, name, t_arg, &my_charset_bin)); + return (new Field_newdate(maybe_null, name, t_arg, &my_charset_bin)); } void fix_length_and_dec() { @@ -884,7 +884,7 @@ public: } Field *tmp_table_field(TABLE *t_arg) { - return (new Field_date(maybe_null, name, t_arg, &my_charset_bin)); + return (new Field_newdate(maybe_null, name, t_arg, &my_charset_bin)); } longlong val_int(); my_decimal *val_decimal(my_decimal *decimal_value) From 578b3632cce5b97d63d1c7d9bb14e9ce631af8d3 Mon Sep 17 00:00:00 2001 From: "kaa@polly.(none)" <> Date: Mon, 24 Dec 2007 17:42:13 +0300 Subject: [PATCH 19/25] Fix for bug #33305: Test case in 'skip_grants' file need dynamic loading to be compiled in The problem was that on a statically built server an attempt to create a UDF resulted in a different, but reasonable error ("Can't open shared library" instead of "UDFs are unavailable with the --skip-grant-tables option"), which caused a failure for the test case for bug #32020. Fixed by moving the test case for bug #32020 from skip_grants.test to a separate test to ensure that it is only run when the server is built with support for dynamically loaded libraries. --- mysql-test/r/skip_grants.result | 4 ---- mysql-test/r/udf_skip_grants.result | 5 +++++ mysql-test/t/skip_grants.test | 10 --------- mysql-test/t/udf_skip_grants-master.opt | 1 + mysql-test/t/udf_skip_grants.test | 28 +++++++++++++++++++++++++ 5 files changed, 34 insertions(+), 14 deletions(-) create mode 100644 mysql-test/r/udf_skip_grants.result create mode 100644 mysql-test/t/udf_skip_grants-master.opt create mode 100644 mysql-test/t/udf_skip_grants.test diff --git a/mysql-test/r/skip_grants.result b/mysql-test/r/skip_grants.result index 1ef35b051d6..cba46b13f19 100644 --- a/mysql-test/r/skip_grants.result +++ b/mysql-test/r/skip_grants.result @@ -70,8 +70,4 @@ count(*) select count(*) from information_schema.USER_PRIVILEGES; count(*) 0 -CREATE FUNCTION a RETURNS STRING SONAME ''; -ERROR HY000: Can't initialize function 'a'; UDFs are unavailable with the --skip-grant-tables option -DROP FUNCTION a; -ERROR 42000: FUNCTION test.a does not exist End of 5.0 tests diff --git a/mysql-test/r/udf_skip_grants.result b/mysql-test/r/udf_skip_grants.result new file mode 100644 index 00000000000..8d7081ebf6f --- /dev/null +++ b/mysql-test/r/udf_skip_grants.result @@ -0,0 +1,5 @@ +CREATE FUNCTION a RETURNS STRING SONAME ''; +ERROR HY000: Can't initialize function 'a'; UDFs are unavailable with the --skip-grant-tables option +DROP FUNCTION a; +ERROR 42000: FUNCTION test.a does not exist +End of 5.0 tests diff --git a/mysql-test/t/skip_grants.test b/mysql-test/t/skip_grants.test index 02a381063ee..1021d629540 100644 --- a/mysql-test/t/skip_grants.test +++ b/mysql-test/t/skip_grants.test @@ -117,14 +117,4 @@ select count(*) from information_schema.SCHEMA_PRIVILEGES; select count(*) from information_schema.TABLE_PRIVILEGES; select count(*) from information_schema.USER_PRIVILEGES; -# -# Bug #32020: loading udfs while --skip-grant-tables is enabled causes out of -# memory errors -# - ---error ER_CANT_INITIALIZE_UDF -CREATE FUNCTION a RETURNS STRING SONAME ''; ---error ER_SP_DOES_NOT_EXIST -DROP FUNCTION a; - --echo End of 5.0 tests diff --git a/mysql-test/t/udf_skip_grants-master.opt b/mysql-test/t/udf_skip_grants-master.opt new file mode 100644 index 00000000000..5699a3387b8 --- /dev/null +++ b/mysql-test/t/udf_skip_grants-master.opt @@ -0,0 +1 @@ +--skip-grant-tables diff --git a/mysql-test/t/udf_skip_grants.test b/mysql-test/t/udf_skip_grants.test new file mode 100644 index 00000000000..bd9402e0d8a --- /dev/null +++ b/mysql-test/t/udf_skip_grants.test @@ -0,0 +1,28 @@ +####################### udf_skip_grants.test ########################### +# # +# Test for bug #32020 "loading udfs while --skip-grant-tables is # +# enabled causes out of memory errors" # +# # +# Creation: # +# 2007-12-24 akopytov Moved the test case for bug #32020 from # +# skip_grants.test to a separate test to ensure # +# that it is only run when the server is built # +# with support for dynamically loaded libraries # +# (see bug #33305). # +# # +######################################################################## + +-- source include/not_embedded.inc +-- source include/have_udf.inc + +# +# Bug #32020: loading udfs while --skip-grant-tables is enabled causes out of +# memory errors +# + +--error ER_CANT_INITIALIZE_UDF +CREATE FUNCTION a RETURNS STRING SONAME ''; +--error ER_SP_DOES_NOT_EXIST +DROP FUNCTION a; + +--echo End of 5.0 tests From 5b9e61b282a624945dd2631f58d1949b6645dc51 Mon Sep 17 00:00:00 2001 From: "gkodinov/kgeorge@macbook.gmz" <> Date: Tue, 8 Jan 2008 12:27:15 +0200 Subject: [PATCH 20/25] merge of bug 33256 5.0-opt -> 5.1-opt --- sql/field.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sql/field.h b/sql/field.h index 365072a4eec..a6a1d8bfabc 100644 --- a/sql/field.h +++ b/sql/field.h @@ -1270,9 +1270,9 @@ public: unireg_check_arg, field_name_arg, cs) {} Field_newdate(bool maybe_null_arg, const char *field_name_arg, - struct st_table *table_arg, CHARSET_INFO *cs) - :Field_str((char*) 0,10, maybe_null_arg ? (uchar*) "": 0,0, - NONE, field_name_arg, table_arg, cs) {} + CHARSET_INFO *cs) + :Field_str((uchar*) 0,10, maybe_null_arg ? (uchar*) "": 0,0, + NONE, field_name_arg, cs) {} enum_field_types type() const { return MYSQL_TYPE_DATE;} enum_field_types real_type() const { return MYSQL_TYPE_NEWDATE; } enum ha_base_keytype key_type() const { return HA_KEYTYPE_UINT24; } From ce111a0d734951a363e2f44670e0e40100d7f757 Mon Sep 17 00:00:00 2001 From: "evgen@moonbone.local" <> Date: Wed, 9 Jan 2008 00:40:41 +0300 Subject: [PATCH 21/25] Bug#33675: Usage of an uninitialized memory by filesort in a subquery caused server crash. The filesort implementation has an optimization for subquery execution which consists of reusing previously allocated buffers. In particular the call to the read_buffpek_from_file function might be skipped when a big enough buffer for buffer descriptors (buffpeks) is already allocated. Beside allocating memory for buffpeks this function fills allocated buffer with data read from disk. Skipping it might led to using an arbitrary memory as fields' data and finally to a crash. Now the read_buffpek_from_file function is always called. It allocates new buffer only when necessary, but always fill it with correct data. --- mysql-test/r/subselect.result | 9 +++++++++ mysql-test/t/subselect.test | 22 ++++++++++++++++++++++ sql/filesort.cc | 22 +++++++++++++--------- 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/mysql-test/r/subselect.result b/mysql-test/r/subselect.result index f6487ae3ddf..75df77b0790 100644 --- a/mysql-test/r/subselect.result +++ b/mysql-test/r/subselect.result @@ -4383,4 +4383,13 @@ SELECT 2 FROM t1 WHERE EXISTS ((SELECT 1 FROM t2 WHERE t1.a=t2.a) UNION ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'UNION (SELECT 1 FROM t2 WHERE t1.a = t2.a))' at line 2 DROP TABLE t1,t2; +create table t1(f11 int, f12 int); +create table t2(f21 int unsigned not null, f22 int, f23 varchar(10)); +insert into t1 values(1,1),(2,2), (3, 3); +set session sort_buffer_size= 33*1024; +select count(*) from t1 where f12 = +(select f22 from t2 where f22 = f12 order by f21 desc, f22, f23 limit 1); +count(*) +3 +drop table t1,t2; End of 5.0 tests. diff --git a/mysql-test/t/subselect.test b/mysql-test/t/subselect.test index ea080964058..88e4f683e9e 100644 --- a/mysql-test/t/subselect.test +++ b/mysql-test/t/subselect.test @@ -3230,4 +3230,26 @@ SELECT 2 FROM t1 WHERE EXISTS ((SELECT 1 FROM t2 WHERE t1.a=t2.a) UNION DROP TABLE t1,t2; +# +# Bug#33675: Usage of an uninitialized memory by filesort in a subquery +# caused server crash. +# +create table t1(f11 int, f12 int); +create table t2(f21 int unsigned not null, f22 int, f23 varchar(10)); +insert into t1 values(1,1),(2,2), (3, 3); +let $i=10000; +--disable_query_log +--disable_warnings +while ($i) +{ + eval insert into t2 values (-1 , $i/5000 + 1, '$i'); + dec $i; +} +--enable_warnings +--enable_query_log +set session sort_buffer_size= 33*1024; +select count(*) from t1 where f12 = +(select f22 from t2 where f22 = f12 order by f21 desc, f22, f23 limit 1); + +drop table t1,t2; --echo End of 5.0 tests. diff --git a/sql/filesort.cc b/sql/filesort.cc index 08ffa2211fa..5a8e3627758 100644 --- a/sql/filesort.cc +++ b/sql/filesort.cc @@ -37,7 +37,8 @@ if (my_b_write((file),(byte*) (from),param->ref_length)) \ static char **make_char_array(char **old_pos, register uint fields, uint length, myf my_flag); -static BUFFPEK *read_buffpek_from_file(IO_CACHE *buffer_file, uint count); +static byte *read_buffpek_from_file(IO_CACHE *buffer_file, uint count, + byte *buf); static ha_rows find_all_keys(SORTPARAM *param,SQL_SELECT *select, uchar * *sort_keys, IO_CACHE *buffer_file, IO_CACHE *tempfile,IO_CACHE *indexfile); @@ -238,9 +239,10 @@ ha_rows filesort(THD *thd, TABLE *table, SORT_FIELD *sortorder, uint s_length, } else { - if (!table_sort.buffpek && table_sort.buffpek_len < maxbuffer && - !(table_sort.buffpek= - (byte *) read_buffpek_from_file(&buffpek_pointers, maxbuffer))) + if (!(table_sort.buffpek= + read_buffpek_from_file(&buffpek_pointers, maxbuffer, + (table_sort.buffpek_len < maxbuffer ? + NULL : table_sort.buffpek)))) goto err; buffpek= (BUFFPEK *) table_sort.buffpek; table_sort.buffpek_len= maxbuffer; @@ -368,18 +370,20 @@ static char **make_char_array(char **old_pos, register uint fields, /* Read 'count' number of buffer pointers into memory */ -static BUFFPEK *read_buffpek_from_file(IO_CACHE *buffpek_pointers, uint count) +static byte *read_buffpek_from_file(IO_CACHE *buffpek_pointers, uint count, + byte *buf) { - ulong length; - BUFFPEK *tmp; + ulong length= sizeof(BUFFPEK)*count; + byte *tmp= buf; DBUG_ENTER("read_buffpek_from_file"); if (count > UINT_MAX/sizeof(BUFFPEK)) return 0; /* sizeof(BUFFPEK)*count will overflow */ - tmp=(BUFFPEK*) my_malloc(length=sizeof(BUFFPEK)*count, MYF(MY_WME)); + if (!tmp) + tmp= (byte *)my_malloc(length, MYF(MY_WME)); if (tmp) { if (reinit_io_cache(buffpek_pointers,READ_CACHE,0L,0,0) || - my_b_read(buffpek_pointers, (byte*) tmp, length)) + my_b_read(buffpek_pointers, tmp, length)) { my_free((char*) tmp, MYF(0)); tmp=0; From 2fc45f017d511ea2386a8c0417eadeab849f3de6 Mon Sep 17 00:00:00 2001 From: "gkodinov/kgeorge@macbook.gmz" <> Date: Wed, 9 Jan 2008 16:49:13 +0200 Subject: [PATCH 22/25] Bug #33133: Views are not transparent When resolving references we need to take into consideration the view "fields" and allow qualified access to them. Fixed by extending the reference resolution to process view fields correctly. --- mysql-test/r/func_group.result | 12 ++++++++++++ mysql-test/t/func_group.test | 15 +++++++++++++++ sql/sql_base.cc | 31 ++++++++++++++++++++++++++++++- 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/mysql-test/r/func_group.result b/mysql-test/r/func_group.result index 1e130877088..4785ca9919d 100644 --- a/mysql-test/r/func_group.result +++ b/mysql-test/r/func_group.result @@ -1407,4 +1407,16 @@ SELECT COUNT(*), a FROM t1; COUNT(*) a 4 1 DROP TABLE t1; +set SQL_MODE=ONLY_FULL_GROUP_BY; +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1),(2),(3),(4); +CREATE VIEW v1 AS SELECT a,(a + 1) AS y FROM t1; +EXPLAIN EXTENDED SELECT y FROM v1 GROUP BY v1.y; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 4 Using temporary; Using filesort +Warnings: +Note 1003 select (`test`.`t1`.`a` + 1) AS `y` from `test`.`t1` group by (`test`.`t1`.`a` + 1) +DROP VIEW v1; +DROP TABLE t1; +SET SQL_MODE=DEFAULT; End of 5.0 tests diff --git a/mysql-test/t/func_group.test b/mysql-test/t/func_group.test index 25cb13a2f75..75a380c733f 100644 --- a/mysql-test/t/func_group.test +++ b/mysql-test/t/func_group.test @@ -901,5 +901,20 @@ SELECT COUNT(*), a FROM t1; DROP TABLE t1; +# +# Bug #33133: Views are not transparent +# + +set SQL_MODE=ONLY_FULL_GROUP_BY; + +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1),(2),(3),(4); +CREATE VIEW v1 AS SELECT a,(a + 1) AS y FROM t1; +EXPLAIN EXTENDED SELECT y FROM v1 GROUP BY v1.y; + +DROP VIEW v1; +DROP TABLE t1; +SET SQL_MODE=DEFAULT; + ### --echo End of 5.0 tests diff --git a/sql/sql_base.cc b/sql/sql_base.cc index fd921be1ecf..f2dd58bc8e5 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -4255,7 +4255,36 @@ find_item_in_list(Item *find, List &items, uint *counter, *resolution= RESOLVED_IGNORING_ALIAS; break; } - } + } + else if (table_name && item->type() == Item::REF_ITEM && + ((Item_ref *)item)->ref_type() == Item_ref::VIEW_REF) + { + /* + TODO:Here we process prefixed view references only. What we should + really do is process all types of Item_refs. But this will currently + lead to a clash with the way references to outer SELECTs (from the + HAVING clause) are handled in e.g. : + SELECT 1 FROM t1 AS t1_o GROUP BY a + HAVING (SELECT t1_o.a FROM t1 AS t1_i GROUP BY t1_i.a LIMIT 1). + Processing all Item_refs here will cause t1_o.a to resolve to itself. + We still need to process the special case of Item_direct_view_ref + because in the context of views they have the same meaning as + Item_field for tables. + */ + Item_ident *item_ref= (Item_ident *) item; + if (item_ref->name && item_ref->table_name && + !my_strcasecmp(system_charset_info, item_ref->name, field_name) && + !my_strcasecmp(table_alias_charset, item_ref->table_name, + table_name) && + (!db_name || (item_ref->db_name && + !strcmp (item_ref->db_name, db_name)))) + { + found= li.ref(); + *counter= i; + *resolution= RESOLVED_IGNORING_ALIAS; + break; + } + } } if (!found) { From 76c0aefcc22da152f9ffd3942f6ef6084e706b78 Mon Sep 17 00:00:00 2001 From: "gkodinov/kgeorge@macbook.gmz" <> Date: Wed, 9 Jan 2008 18:00:12 +0200 Subject: [PATCH 23/25] merge of the fix for bug 33133 to 5.1-opt --- mysql-test/r/func_group.result | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mysql-test/r/func_group.result b/mysql-test/r/func_group.result index f3819418edc..4c1abb160c6 100644 --- a/mysql-test/r/func_group.result +++ b/mysql-test/r/func_group.result @@ -1403,8 +1403,8 @@ CREATE TABLE t1 (a INT); INSERT INTO t1 VALUES (1),(2),(3),(4); CREATE VIEW v1 AS SELECT a,(a + 1) AS y FROM t1; EXPLAIN EXTENDED SELECT y FROM v1 GROUP BY v1.y; -id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t1 ALL NULL NULL NULL NULL 4 Using temporary; Using filesort +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 4 100.00 Using temporary; Using filesort Warnings: Note 1003 select (`test`.`t1`.`a` + 1) AS `y` from `test`.`t1` group by (`test`.`t1`.`a` + 1) DROP VIEW v1; From e11a62776d974237e578def09bf829d508278267 Mon Sep 17 00:00:00 2001 From: "kaa@kaamos.(none)" <> Date: Thu, 10 Jan 2008 15:07:55 +0300 Subject: [PATCH 24/25] Post-merge fix after merging 5.0 -> 5.1. Replaced 'byte' with 'uchar'. --- sql/filesort.cc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sql/filesort.cc b/sql/filesort.cc index b9de65bb46b..23bc5c04648 100644 --- a/sql/filesort.cc +++ b/sql/filesort.cc @@ -37,8 +37,8 @@ if (my_b_write((file),(uchar*) (from),param->ref_length)) \ static char **make_char_array(char **old_pos, register uint fields, uint length, myf my_flag); -static byte *read_buffpek_from_file(IO_CACHE *buffer_file, uint count, - byte *buf); +static uchar *read_buffpek_from_file(IO_CACHE *buffer_file, uint count, + uchar *buf); static ha_rows find_all_keys(SORTPARAM *param,SQL_SELECT *select, uchar * *sort_keys, IO_CACHE *buffer_file, IO_CACHE *tempfile,IO_CACHE *indexfile); @@ -376,16 +376,16 @@ static char **make_char_array(char **old_pos, register uint fields, /* Read 'count' number of buffer pointers into memory */ -static byte *read_buffpek_from_file(IO_CACHE *buffpek_pointers, uint count, - byte *buf) +static uchar *read_buffpek_from_file(IO_CACHE *buffpek_pointers, uint count, + uchar *buf) { ulong length= sizeof(BUFFPEK)*count; - byte *tmp= buf; + uchar *tmp= buf; DBUG_ENTER("read_buffpek_from_file"); if (count > UINT_MAX/sizeof(BUFFPEK)) return 0; /* sizeof(BUFFPEK)*count will overflow */ if (!tmp) - tmp= (byte *)my_malloc(length, MYF(MY_WME)); + tmp= (uchar *)my_malloc(length, MYF(MY_WME)); if (tmp) { if (reinit_io_cache(buffpek_pointers,READ_CACHE,0L,0,0) || From 830b8f0e3017755415ed9331d6a44cd7c44779c2 Mon Sep 17 00:00:00 2001 From: "evgen@moonbone.local" <> Date: Thu, 10 Jan 2008 18:54:34 +0300 Subject: [PATCH 25/25] filesort.cc: Bug#33675: Usage of an uninitialized memory by filesort in a subquery caused server crash. Free smaller buffer before allocating bigger one. --- sql/filesort.cc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sql/filesort.cc b/sql/filesort.cc index 5a8e3627758..43b079e83d5 100644 --- a/sql/filesort.cc +++ b/sql/filesort.cc @@ -239,10 +239,14 @@ ha_rows filesort(THD *thd, TABLE *table, SORT_FIELD *sortorder, uint s_length, } else { + if (table_sort.buffpek && table_sort.buffpek_len < maxbuffer) + { + x_free(table_sort.buffpek); + table_sort.buffpek= 0; + } if (!(table_sort.buffpek= read_buffpek_from_file(&buffpek_pointers, maxbuffer, - (table_sort.buffpek_len < maxbuffer ? - NULL : table_sort.buffpek)))) + table_sort.buffpek))) goto err; buffpek= (BUFFPEK *) table_sort.buffpek; table_sort.buffpek_len= maxbuffer;