diff options
-rw-r--r-- | BitKeeper/etc/logging_ok | 2 | ||||
-rw-r--r-- | Makefile.am | 5 | ||||
-rw-r--r-- | client/mysqladmin.cc | 2 | ||||
-rw-r--r-- | config/ac-macros/misc.m4 | 4 | ||||
-rw-r--r-- | configure.in | 3 | ||||
-rw-r--r-- | include/config-win.h | 17 | ||||
-rw-r--r-- | include/my_global.h | 2 | ||||
-rw-r--r-- | mysql-test/r/bigint.result | 17 | ||||
-rw-r--r-- | mysql-test/r/cast.result | 73 | ||||
-rw-r--r-- | mysql-test/r/innodb-replace.result | 1 | ||||
-rw-r--r-- | mysql-test/r/user_var.result | 41 | ||||
-rw-r--r-- | mysql-test/t/bigint.test | 10 | ||||
-rw-r--r-- | mysql-test/t/cast.test | 26 | ||||
-rw-r--r-- | mysql-test/t/innodb-replace.test | 4 | ||||
-rw-r--r-- | mysql-test/t/user_var.test | 26 | ||||
-rw-r--r-- | mysys/default.c | 135 | ||||
-rw-r--r-- | mysys/mf_keycache.c | 11 | ||||
-rw-r--r-- | sql/field.h | 2 | ||||
-rw-r--r-- | sql/handler.cc | 7 | ||||
-rw-r--r-- | sql/hostname.cc | 9 | ||||
-rw-r--r-- | sql/item.h | 7 | ||||
-rw-r--r-- | sql/item_func.cc | 88 | ||||
-rw-r--r-- | sql/item_func.h | 10 | ||||
-rw-r--r-- | sql/protocol.cc | 2 | ||||
-rw-r--r-- | sql/sql_insert.cc | 4 | ||||
-rw-r--r-- | sql/sql_prepare.cc | 2 | ||||
-rw-r--r-- | sql/sql_select.cc | 746 | ||||
-rw-r--r-- | sql/sql_select.h | 18 | ||||
-rw-r--r-- | sql/unireg.cc | 7 | ||||
-rwxr-xr-x | tests/index_corrupt.pl | 212 | ||||
-rw-r--r-- | tests/mysql_client_test.c | 61 |
31 files changed, 1100 insertions, 454 deletions
diff --git a/BitKeeper/etc/logging_ok b/BitKeeper/etc/logging_ok index e706a3358d5..346dcae3cfd 100644 --- a/BitKeeper/etc/logging_ok +++ b/BitKeeper/etc/logging_ok @@ -50,6 +50,8 @@ dlenev@build.mysql.com dlenev@jabberwock.localdomain dlenev@mysql.com ejonore@mc03.ndb.mysql.com +evgen@moonbone.(none) +evgen@moonbone.local gbichot@production.mysql.com gbichot@quadita2.mysql.com gbichot@quadxeon.mysql.com diff --git a/Makefile.am b/Makefile.am index 29a52ff9bfc..eec27484710 100644 --- a/Makefile.am +++ b/Makefile.am @@ -102,5 +102,10 @@ tags: test: cd mysql-test; ./mysql-test-run && ./mysql-test-run --ps-protocol +test-force: + cd mysql-test; \ + mysql-test-run --force ;\ + mysql-test-run --ps-protocol --force + # Don't update the files from bitkeeper %::SCCS/s.% diff --git a/client/mysqladmin.cc b/client/mysqladmin.cc index 1eec9bc2555..32e0dfdb837 100644 --- a/client/mysqladmin.cc +++ b/client/mysqladmin.cc @@ -727,7 +727,7 @@ static int execute_commands(MYSQL *mysql,int argc, char **argv) void (*func) (MYSQL_RES*, MYSQL_ROW, uint); new_line = 1; - if (mysql_query(mysql, "show status") || + if (mysql_query(mysql, "show /*!50002 GLOBAL */ status") || !(res = mysql_store_result(mysql))) { my_printf_error(0, "unable to show status; error: '%s'", MYF(ME_BELL), diff --git a/config/ac-macros/misc.m4 b/config/ac-macros/misc.m4 index 201702e5379..d75dedafa2a 100644 --- a/config/ac-macros/misc.m4 +++ b/config/ac-macros/misc.m4 @@ -646,8 +646,8 @@ m4_define([_AC_PROG_CXX_EXIT_DECLARATION], 'void exit (int);' \ '#include <stdlib.h>' do - _AC_COMPILE_IFELSE([AC_LANG_PROGRAM([@%:@include <stdlib.h> -$ac_declaration], + _AC_COMPILE_IFELSE([AC_LANG_PROGRAM([$ac_declaration +@%:@include <stdlib.h>], [exit (42);])], [], [continue]) diff --git a/configure.in b/configure.in index 57f99c29410..8b0a0ddeb56 100644 --- a/configure.in +++ b/configure.in @@ -1805,6 +1805,9 @@ If you are using gcc 2.8.# you should upgrade to egcs 1.0.3 or newer and try again]); fi fi +AC_CHECK_TYPES([sigset_t, off_t], [], [], [#include <sys/types.h>]) +AC_CHECK_TYPES([size_t], [], [], [#include <stdio.h>]) + MYSQL_PTHREAD_YIELD ###################################################################### diff --git a/include/config-win.h b/include/config-win.h index bc392ce73d8..a4f81a0ec6a 100644 --- a/include/config-win.h +++ b/include/config-win.h @@ -106,20 +106,33 @@ functions */ /* Type information */ +#if defined(__EMX__) || !defined(HAVE_UINT) +#undef HAVE_UINT +#define HAVE_UINT typedef unsigned short ushort; typedef unsigned int uint; +#endif /* defined(__EMX__) || !defined(HAVE_UINT) */ + typedef unsigned __int64 ulonglong; /* Microsofts 64 bit types */ typedef __int64 longlong; +#ifndef HAVE_SIGSET_T typedef int sigset_t; +#endif #define longlong_defined -/* off_t should not be __int64 because of conflicts in header files; - Use my_off_t or os_off_t instead */ +/* + off_t should not be __int64 because of conflicts in header files; + Use my_off_t or os_off_t instead +*/ +#ifndef HAVE_OFF_T typedef long off_t; +#endif typedef __int64 os_off_t; #ifdef _WIN64 typedef UINT_PTR rf_SetTimer; #else +#ifndef HAVE_SIZE_T typedef unsigned int size_t; +#endif typedef uint rf_SetTimer; #endif diff --git a/include/my_global.h b/include/my_global.h index 0353dc91fcf..55ec427e3e1 100644 --- a/include/my_global.h +++ b/include/my_global.h @@ -414,6 +414,8 @@ int __void__; #endif #if defined(__EMX__) || !defined(HAVE_UINT) +#undef HAVE_UINT +#define HAVE_UINT typedef unsigned int uint; typedef unsigned short ushort; #endif diff --git a/mysql-test/r/bigint.result b/mysql-test/r/bigint.result index e372c307ba7..a0c8f317db2 100644 --- a/mysql-test/r/bigint.result +++ b/mysql-test/r/bigint.result @@ -128,3 +128,20 @@ t2.value64=t1.value64; value64 value32 value64 value32 9223372036854775807 2 9223372036854775807 4 drop table t1, t2; +create table t1 select 1 as 'a'; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` bigint(1) NOT NULL default '0' +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +drop table t1; +create table t1 select 9223372036854775809 as 'a'; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` bigint(19) unsigned NOT NULL default '0' +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +select * from t1; +a +9223372036854775809 +drop table t1; diff --git a/mysql-test/r/cast.result b/mysql-test/r/cast.result index feefd47c611..6dc608a9289 100644 --- a/mysql-test/r/cast.result +++ b/mysql-test/r/cast.result @@ -4,12 +4,11 @@ CAST(1-2 AS UNSIGNED) select CAST(CAST(1-2 AS UNSIGNED) AS SIGNED INTEGER); CAST(CAST(1-2 AS UNSIGNED) AS SIGNED INTEGER) -1 -select CONVERT('-1',UNSIGNED); -CONVERT('-1',UNSIGNED) -18446744073709551615 select CAST('10 ' as unsigned integer); CAST('10 ' as unsigned integer) 10 +Warnings: +Warning 1292 Truncated incorrect INTEGER value: '10 ' select cast(-5 as unsigned) | 1, cast(-5 as unsigned) & -1; cast(-5 as unsigned) | 1 cast(-5 as unsigned) & -1 18446744073709551611 18446744073709551611 @@ -100,6 +99,41 @@ select 10E+0+'a'; 10 Warnings: Warning 1292 Truncated incorrect DOUBLE value: 'a' +select cast('18446744073709551616' as unsigned); +cast('18446744073709551616' as unsigned) +18446744073709551615 +Warnings: +Warning 1292 Truncated incorrect INTEGER value: '18446744073709551616' +select cast('18446744073709551616' as signed); +cast('18446744073709551616' as signed) +-1 +Warnings: +Warning 1292 Truncated incorrect INTEGER value: '18446744073709551616' +select cast('9223372036854775809' as signed); +cast('9223372036854775809' as signed) +-9223372036854775807 +Warnings: +Warning 1105 Cast to signed converted positive out-of-range integer to it's negative complement +select cast('-1' as unsigned); +cast('-1' as unsigned) +18446744073709551615 +Warnings: +Warning 1105 Cast to unsigned converted negative integer to it's positive complement +select cast('abc' as signed); +cast('abc' as signed) +0 +Warnings: +Warning 1292 Truncated incorrect INTEGER value: 'abc' +select cast('1a' as signed); +cast('1a' as signed) +1 +Warnings: +Warning 1292 Truncated incorrect INTEGER value: '1a' +select cast('' as signed); +cast('' as signed) +0 +Warnings: +Warning 1292 Truncated incorrect INTEGER value: '' set names binary; select cast(_latin1'test' as char character set latin2); cast(_latin1'test' as char character set latin2) @@ -255,6 +289,39 @@ timediff(cast('2004-12-30 12:00:00' as time), '12:00:00') select timediff(cast('1 12:00:00' as time), '12:00:00'); timediff(cast('1 12:00:00' as time), '12:00:00') 24:00:00 +select cast(18446744073709551615 as unsigned); +cast(18446744073709551615 as unsigned) +18446744073709551615 +select cast(18446744073709551615 as signed); +cast(18446744073709551615 as signed) +-1 +select cast('18446744073709551615' as unsigned); +cast('18446744073709551615' as unsigned) +18446744073709551615 +select cast('18446744073709551615' as signed); +cast('18446744073709551615' as signed) +-1 +Warnings: +Warning 1105 Cast to signed converted positive out-of-range integer to it's negative complement +select cast('9223372036854775807' as signed); +cast('9223372036854775807' as signed) +9223372036854775807 +select cast(concat('184467440','73709551615') as unsigned); +cast(concat('184467440','73709551615') as unsigned) +18446744073709551615 +select cast(concat('184467440','73709551615') as signed); +cast(concat('184467440','73709551615') as signed) +-1 +Warnings: +Warning 1105 Cast to signed converted positive out-of-range integer to it's negative complement +select cast(repeat('1',20) as unsigned); +cast(repeat('1',20) as unsigned) +11111111111111111111 +select cast(repeat('1',20) as signed); +cast(repeat('1',20) as signed) +-7335632962598440505 +Warnings: +Warning 1105 Cast to signed converted positive out-of-range integer to it's negative complement select cast('1.2' as decimal(3,2)); cast('1.2' as decimal(3,2)) 1.20 diff --git a/mysql-test/r/innodb-replace.result b/mysql-test/r/innodb-replace.result index a27806640ad..b7edcc49e56 100644 --- a/mysql-test/r/innodb-replace.result +++ b/mysql-test/r/innodb-replace.result @@ -1,3 +1,4 @@ +drop table if exists t1; create table t1 (c1 char(5) unique not null, c2 int, stamp timestamp) engine=innodb; select * from t1; c1 c2 stamp diff --git a/mysql-test/r/user_var.result b/mysql-test/r/user_var.result index 68eae111111..d9a647ce2c3 100644 --- a/mysql-test/r/user_var.result +++ b/mysql-test/r/user_var.result @@ -182,3 +182,44 @@ coercibility(@v1) coercibility(@v2) coercibility(@v3) coercibility(@v4) set session @honk=99; set one_shot @honk=99; ERROR HY000: The 'SET ONE_SHOT' syntax is reserved for purposes internal to the MySQL server +set @first_var= NULL; +create table t1 select @first_var; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `@first_var` longblob +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +drop table t1; +set @first_var= cast(NULL as signed integer); +create table t1 select @first_var; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `@first_var` bigint(20) default NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +drop table t1; +set @first_var= NULL; +create table t1 select @first_var; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `@first_var` bigint(20) default NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +drop table t1; +set @first_var= concat(NULL); +create table t1 select @first_var; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `@first_var` longblob +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +drop table t1; +set @first_var=1; +set @first_var= cast(NULL as CHAR); +create table t1 select @first_var; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `@first_var` longtext +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +drop table t1; diff --git a/mysql-test/t/bigint.test b/mysql-test/t/bigint.test index a26b78254e7..99c8a13d0b5 100644 --- a/mysql-test/t/bigint.test +++ b/mysql-test/t/bigint.test @@ -104,3 +104,13 @@ t2.value64=t1.value64; drop table t1, t2; +# +# Test of CREATE ... SELECT and unsigned integers +# +create table t1 select 1 as 'a'; +show create table t1; +drop table t1; +create table t1 select 9223372036854775809 as 'a'; +show create table t1; +select * from t1; +drop table t1; diff --git a/mysql-test/t/cast.test b/mysql-test/t/cast.test index e7dd49394ee..cafecd6000d 100644 --- a/mysql-test/t/cast.test +++ b/mysql-test/t/cast.test @@ -4,7 +4,6 @@ select CAST(1-2 AS UNSIGNED); select CAST(CAST(1-2 AS UNSIGNED) AS SIGNED INTEGER); -select CONVERT('-1',UNSIGNED); select CAST('10 ' as unsigned integer); select cast(-5 as unsigned) | 1, cast(-5 as unsigned) & -1; select cast(-5 as unsigned) -1, cast(-5 as unsigned) + 1; @@ -34,6 +33,15 @@ select 10+'a'; select 10.0+cast('a' as decimal); select 10E+0+'a'; +# out-of-range cases +select cast('18446744073709551616' as unsigned); +select cast('18446744073709551616' as signed); +select cast('9223372036854775809' as signed); +select cast('-1' as unsigned); +select cast('abc' as signed); +select cast('1a' as signed); +select cast('' as signed); + # # Character set convertion # @@ -132,6 +140,22 @@ select timediff(cast('2004-12-30 12:00:00' as time), '12:00:00'); # Still we should not throw away "days" part of time value select timediff(cast('1 12:00:00' as time), '12:00:00'); +# +# Bug #7036: Casting from string to unsigned would cap value of result at +# maximum signed value instead of maximum unsigned value +# +select cast(18446744073709551615 as unsigned); +select cast(18446744073709551615 as signed); +select cast('18446744073709551615' as unsigned); +select cast('18446744073709551615' as signed); +select cast('9223372036854775807' as signed); + +select cast(concat('184467440','73709551615') as unsigned); +select cast(concat('184467440','73709551615') as signed); + +select cast(repeat('1',20) as unsigned); +select cast(repeat('1',20) as signed); + #decimal-related additions select cast('1.2' as decimal(3,2)); select 1e18 * cast('1.2' as decimal(3,2)); diff --git a/mysql-test/t/innodb-replace.test b/mysql-test/t/innodb-replace.test index e7e96da1443..516f058a68e 100644 --- a/mysql-test/t/innodb-replace.test +++ b/mysql-test/t/innodb-replace.test @@ -2,6 +2,10 @@ # embedded server ignores 'delayed', so skip this -- source include/not_embedded.inc +--disable_warnings +drop table if exists t1; +--enable_warnings + # # Bug #1078 # diff --git a/mysql-test/t/user_var.test b/mysql-test/t/user_var.test index ef360f2231d..b9d06558f34 100644 --- a/mysql-test/t/user_var.test +++ b/mysql-test/t/user_var.test @@ -119,3 +119,29 @@ select coercibility(@v1),coercibility(@v2),coercibility(@v3),coercibility(@v4); set session @honk=99; --error 1382 set one_shot @honk=99; + +# +# Bug #6598: problem with cast(NULL as signed integer); +# + +set @first_var= NULL; +create table t1 select @first_var; +show create table t1; +drop table t1; +set @first_var= cast(NULL as signed integer); +create table t1 select @first_var; +show create table t1; +drop table t1; +set @first_var= NULL; +create table t1 select @first_var; +show create table t1; +drop table t1; +set @first_var= concat(NULL); +create table t1 select @first_var; +show create table t1; +drop table t1; +set @first_var=1; +set @first_var= cast(NULL as CHAR); +create table t1 select @first_var; +show create table t1; +drop table t1; diff --git a/mysys/default.c b/mysys/default.c index 0f33c94d17e..aa2293ac0af 100644 --- a/mysys/default.c +++ b/mysys/default.c @@ -410,6 +410,56 @@ static int search_default_file(Process_option_func opt_handler, /* + Skip over keyword and get argument after keyword + + SYNOPSIS + get_argument() + keyword Include directive keyword + kwlen Length of keyword + ptr Pointer to the keword in the line under process + line line number + + RETURN + 0 error + # Returns pointer to the argument after the keyword. +*/ + +static char *get_argument(const char *keyword, uint kwlen, + char *ptr, char *name, uint line) +{ + char *end; + + /* Skip over "include / includedir keyword" and following whitespace */ + + for (ptr+= kwlen - 1; + my_isspace(&my_charset_latin1, ptr[0]); + ptr++) + {} + + /* + Trim trailing whitespace from directory name + The -1 below is for the newline added by fgets() + Note that my_isspace() is true for \r and \n + */ + for (end= ptr + strlen(ptr) - 1; + my_isspace(&my_charset_latin1, *(end - 1)); + end--) + {} + end[0]= 0; /* Cut off end space */ + + /* Print error msg if there is nothing after !include* directive */ + if (end <= ptr) + { + fprintf(stderr, + "error: Wrong '!%s' directive in config file: %s at line %d\n", + keyword, name, line); + return 0; + } + return ptr; +} + + +/* Open a configuration file (if exists) and read given options from it SYNOPSIS @@ -497,40 +547,34 @@ static int search_default_file_with_ext(Process_option_func opt_handler, continue; /* Configuration File Directives */ - if ((*ptr == '!') && (recursion_level < max_recursion_level)) + if ((*ptr == '!')) { + if (recursion_level >= max_recursion_level) + { + for (end= ptr + strlen(ptr) - 1; + my_isspace(&my_charset_latin1, *(end - 1)); + end--) + {} + end[0]= 0; + fprintf(stderr, + "Warning: skipping '%s' directive as maximum include" + "recursion level was reached in file %s at line %d\n", + ptr, name, line); + continue; + } + /* skip over `!' and following whitespace */ for (++ptr; my_isspace(&my_charset_latin1, ptr[0]); ptr++) {} - if ((!strncmp(ptr, includedir_keyword, sizeof(includedir_keyword) - 1)) - && my_isspace(&my_charset_latin1, ptr[sizeof(includedir_keyword) - 1])) + if ((!strncmp(ptr, includedir_keyword, + sizeof(includedir_keyword) - 1)) && + my_isspace(&my_charset_latin1, ptr[sizeof(includedir_keyword) - 1])) { - /* skip over "includedir" and following whitespace */ - for (ptr+= sizeof(includedir_keyword) - 1; - my_isspace(&my_charset_latin1, ptr[0]); ptr++) - {} - - /* trim trailing whitespace from directory name */ - end= ptr + strlen(ptr) - 1; - /* fgets() stores the newline character in the buffer */ - if ((end[0] == '\n') || (end[0] == '\r') || - my_isspace(&my_charset_latin1, end[0])) - { - for (; my_isspace(&my_charset_latin1, *(end - 1)); end--) - {} - end[0]= 0; - } - - /* print error msg if there is nothing after !includedir directive */ - if (end == ptr) - { - fprintf(stderr, - "error: Wrong !includedir directive in config " - "file: %s at line %d\n", - name,line); - goto err; - } + if (!(ptr= get_argument(includedir_keyword, + sizeof(includedir_keyword), + ptr, name, line))) + goto err; if (!(search_dir= my_dir(ptr, MYF(MY_WME)))) goto err; @@ -559,28 +603,13 @@ static int search_default_file_with_ext(Process_option_func opt_handler, my_dirend(search_dir); } - else if ((!strncmp(ptr, include_keyword, sizeof(include_keyword) - 1)) - && my_isspace(&my_charset_latin1, ptr[sizeof(include_keyword) - 1])) + else if ((!strncmp(ptr, include_keyword, sizeof(include_keyword) - 1)) && + my_isspace(&my_charset_latin1, ptr[sizeof(include_keyword)-1])) { - /* skip over `include' and following whitespace */ - for (ptr+= sizeof(include_keyword) - 1; - my_isspace(&my_charset_latin1, ptr[0]); ptr++) - {} - - /* trim trailing whitespace from filename */ - end= ptr + strlen(ptr) - 1; - for (; my_isspace(&my_charset_latin1, *(end - 1)) ; end--) - {} - end[0]= 0; - - if (end == ptr) - { - fprintf(stderr, - "error: Wrong !include directive in config " - "file: %s at line %d\n", - name,line); - goto err; - } + if (!(ptr= get_argument(include_keyword, + sizeof(include_keyword), ptr, + name, line))) + goto err; search_default_file_with_ext(opt_handler, handler_ctx, "", "", ptr, recursion_level + 1); @@ -588,14 +617,6 @@ static int search_default_file_with_ext(Process_option_func opt_handler, continue; } - else - if (recursion_level >= max_recursion_level) - { - fprintf(stderr, - "warning: skipping !include directive as maximum include" - "recursion level was reached in file %s at line %d\n", - name, line); - } if (*ptr == '[') /* Group name */ { diff --git a/mysys/mf_keycache.c b/mysys/mf_keycache.c index 4967b60cd68..2308536cd37 100644 --- a/mysys/mf_keycache.c +++ b/mysys/mf_keycache.c @@ -1025,8 +1025,8 @@ static void reg_requests(KEY_CACHE *keycache, BLOCK_LINK *block, int count) for a too long time (this time is determined by parameter age_threshold). */ -static inline void unreg_request(KEY_CACHE *keycache, - BLOCK_LINK *block, int at_end) +static void unreg_request(KEY_CACHE *keycache, + BLOCK_LINK *block, int at_end) { if (! --block->requests) { @@ -1045,10 +1045,13 @@ static inline void unreg_request(KEY_CACHE *keycache, } link_block(keycache, block, hot, (my_bool)at_end); block->last_hit_time= keycache->keycache_time; - if (++keycache->keycache_time - keycache->used_ins->last_hit_time > + keycache->keycache_time++; + + block= keycache->used_ins; + /* Check if we should link a hot block to the warm block */ + if (block && keycache->keycache_time - block->last_hit_time > keycache->age_threshold) { - block= keycache->used_ins; unlink_block(keycache, block); link_block(keycache, block, 0, 0); if (block->temperature != BLOCK_WARM) diff --git a/sql/field.h b/sql/field.h index 0f1cc0b95aa..ac9c2f351b3 100644 --- a/sql/field.h +++ b/sql/field.h @@ -118,6 +118,7 @@ public: String *val_int_as_str(String *val_buffer, my_bool unsigned_flag); virtual Item_result result_type () const=0; virtual Item_result cmp_type () const { return result_type(); } + virtual Item_result cast_to_int_type () const { return result_type(); } static enum_field_types field_type_merge(enum_field_types, enum_field_types); static Item_result result_merge_type(enum_field_types); bool eq(Field *field) @@ -1216,6 +1217,7 @@ public: } enum_field_types type() const { return FIELD_TYPE_STRING; } enum Item_result cmp_type () const { return INT_RESULT; } + enum Item_result cast_to_int_type () const { return INT_RESULT; } enum ha_base_keytype key_type() const; int store(const char *to,uint length,CHARSET_INFO *charset); int store(double nr); diff --git a/sql/handler.cc b/sql/handler.cc index d2501afe97a..7318de1c503 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -185,9 +185,10 @@ enum db_type ha_checktype(enum db_type database_type) thd= current_thd; return ((enum db_type) thd->variables.table_type != DB_TYPE_UNKNOWN ? (enum db_type) thd->variables.table_type : - (enum db_type) global_system_variables.table_type != - DB_TYPE_UNKNOWN ? - (enum db_type) global_system_variables.table_type : DB_TYPE_MYISAM); + ((enum db_type) global_system_variables.table_type != + DB_TYPE_UNKNOWN ? + (enum db_type) global_system_variables.table_type : DB_TYPE_MYISAM) + ); } /* ha_checktype */ diff --git a/sql/hostname.cc b/sql/hostname.cc index c74d230bbcb..fe2fad6f3b2 100644 --- a/sql/hostname.cc +++ b/sql/hostname.cc @@ -177,7 +177,14 @@ my_string ip_to_hostname(struct in_addr *in, uint *errors) &tmp_errno))) { DBUG_PRINT("error",("gethostbyname_r returned %d",tmp_errno)); - add_wrong_ip(in); + /* + Don't cache responses when the DSN server is down, as otherwise + transient DNS failure may leave any number of clients (those + that attempted to connect during the outage) unable to connect + indefinitely. + */ + if (tmp_errno == HOST_NOT_FOUND || tmp_error == NO_DATA) + add_wrong_ip(in); my_gethostbyname_r_free(); DBUG_RETURN(0); } diff --git a/sql/item.h b/sql/item.h index e86c66ca6f3..850a2d6636d 100644 --- a/sql/item.h +++ b/sql/item.h @@ -302,7 +302,8 @@ public: { return save_in_field(field, 1); } virtual bool send(Protocol *protocol, String *str); virtual bool eq(const Item *, bool binary_cmp) const; - virtual Item_result result_type () const { return REAL_RESULT; } + virtual Item_result result_type() const { return REAL_RESULT; } + virtual Item_result cast_to_int_type() const { return result_type(); } virtual enum_field_types field_type() const; virtual enum Type type() const =0; /* valXXX methods must return NULL or 0 or 0.0 if null_value is set. */ @@ -738,6 +739,10 @@ public: { return field->result_type(); } + Item_result cast_to_int_type() const + { + return field->cast_to_int_type(); + } enum_field_types field_type() const { return field->type(); diff --git a/sql/item_func.cc b/sql/item_func.cc index 9825de66477..4e3011ac4bb 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -904,6 +904,58 @@ void Item_func_signed::print(String *str) } +longlong Item_func_signed::val_int_from_str(int *error) +{ + char buff[MAX_FIELD_WIDTH], *end; + String tmp(buff,sizeof(buff), &my_charset_bin), *res; + longlong value; + + /* + For a string result, we must first get the string and then convert it + to a longlong + */ + + if (!(res= args[0]->val_str(&tmp))) + { + null_value= 1; + *error= 0; + return 0; + } + null_value= 0; + end= (char*) res->ptr()+ res->length(); + value= my_strtoll10(res->ptr(), &end, error); + if (*error > 0 || end != res->ptr()+ res->length()) + push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, + ER_TRUNCATED_WRONG_VALUE, + ER(ER_TRUNCATED_WRONG_VALUE), "INTEGER", + res->c_ptr()); + return value; +} + + +longlong Item_func_signed::val_int() +{ + longlong value; + int error; + + if (args[0]->cast_to_int_type() != STRING_RESULT) + { + value= args[0]->val_int(); + null_value= args[0]->null_value; + return value; + } + + value= val_int_from_str(&error); + if (value < 0 && error == 0) + { + push_warning(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR, + "Cast to signed converted positive out-of-range integer to " + "it's negative complement"); + } + return value; +} + + void Item_func_unsigned::print(String *str) { str->append("cast(", 5); @@ -913,6 +965,27 @@ void Item_func_unsigned::print(String *str) } +longlong Item_func_unsigned::val_int() +{ + longlong value; + int error; + + if (args[0]->cast_to_int_type() != STRING_RESULT) + { + value= args[0]->val_int(); + null_value= args[0]->null_value; + return value; + } + + value= val_int_from_str(&error); + if (error < 0) + push_warning(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR, + "Cast to unsigned converted negative integer to it's " + "positive complement"); + return value; +} + + String *Item_decimal_typecast::val_str(String *str) { my_decimal tmp_buf, *tmp= val_decimal(&tmp_buf); @@ -3271,7 +3344,8 @@ bool Item_func_set_user_var::fix_fields(THD *thd, TABLE_LIST *tables, from the argument if the argument is NULL and the variable has previously been initialized. */ - if (!entry->collation.collation || !args[0]->null_value) + null_item= (args[0]->type() == NULL_ITEM); + if (!entry->collation.collation || !null_item) entry->collation.set(args[0]->collation.collation, DERIVATION_IMPLICIT); collation.set(entry->collation.collation, DERIVATION_IMPLICIT); cached_result_type= args[0]->result_type(); @@ -3315,8 +3389,8 @@ update_hash(user_var_entry *entry, bool set_null, void *ptr, uint length, char *pos= (char*) entry+ ALIGN_SIZE(sizeof(user_var_entry)); if (entry->value && entry->value != pos) my_free(entry->value,MYF(0)); - entry->value=0; - entry->length=0; + entry->value= 0; + entry->length= 0; } else { @@ -3355,9 +3429,9 @@ update_hash(user_var_entry *entry, bool set_null, void *ptr, uint length, if (type == DECIMAL_RESULT) ((my_decimal*)entry->value)->fix_buffer_pointer(); entry->length= length; - entry->type=type; entry->collation.set(cs, dv); } + entry->type=type; return 0; } @@ -3366,6 +3440,12 @@ bool Item_func_set_user_var::update_hash(void *ptr, uint length, Item_result type, CHARSET_INFO *cs, Derivation dv) { + /* + If we set a variable explicitely to NULL then keep the old + result type of the variable + */ + if ((null_value= args[0]->null_value) && null_item) + type= entry->type; // Don't change type of item if (::update_hash(entry, (null_value= args[0]->null_value), ptr, length, type, cs, dv)) { diff --git a/sql/item_func.h b/sql/item_func.h index cb0b7dc02a4..76d1151f3bf 100644 --- a/sql/item_func.h +++ b/sql/item_func.h @@ -262,12 +262,8 @@ public: null_value= args[0]->null_value; return tmp; } - longlong val_int() - { - longlong tmp= args[0]->val_int(); - null_value= args[0]->null_value; - return tmp; - } + longlong val_int(); + longlong val_int_from_str(int *error); void fix_length_and_dec() { max_length=args[0]->max_length; unsigned_flag=0; } void print(String *str); @@ -281,6 +277,7 @@ public: const char *func_name() const { return "cast_as_unsigned"; } void fix_length_and_dec() { max_length=args[0]->max_length; unsigned_flag=1; } + longlong val_int(); void print(String *str); }; @@ -1071,6 +1068,7 @@ class Item_func_set_user_var :public Item_func char buffer[MAX_FIELD_WIDTH]; String value; my_decimal decimal_buff; + bool null_item; union { longlong vint; diff --git a/sql/protocol.cc b/sql/protocol.cc index 2ea435d84ef..9a96da004c5 100644 --- a/sql/protocol.cc +++ b/sql/protocol.cc @@ -820,7 +820,7 @@ bool Protocol_simple::store_long(longlong from) #endif char buff[20]; return net_store_data((char*) buff, - (uint) (int10_to_str((int)from,buff, (from <0)?-10:10)-buff)); + (uint) (int10_to_str((long int)from,buff, (from <0)?-10:10)-buff)); } diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index ac8f5a06745..729f9751bba 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -89,7 +89,7 @@ static int check_insert_fields(THD *thd, TABLE_LIST *table_list, } if (values.elements != table->s->fields) { - my_error(ER_WRONG_VALUE_COUNT_ON_ROW, MYF(0), 1); + my_error(ER_WRONG_VALUE_COUNT_ON_ROW, MYF(0), 1L); return -1; } #ifndef NO_EMBEDDED_ACCESS_CHECKS @@ -112,7 +112,7 @@ static int check_insert_fields(THD *thd, TABLE_LIST *table_list, int res; if (fields.elements != values.elements) { - my_error(ER_WRONG_VALUE_COUNT_ON_ROW, MYF(0), 1); + my_error(ER_WRONG_VALUE_COUNT_ON_ROW, MYF(0), 1L); return -1; } diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 56d09d7c563..17c5f51f1e1 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -2223,7 +2223,7 @@ void mysql_stmt_fetch(THD *thd, char *packet, uint packet_length) my_pthread_setprio(pthread_self(), QUERY_PRIOR); thd->protocol= &thd->protocol_prep; // Switch to binary protocol - (void) stmt->cursor->fetch(num_rows); + stmt->cursor->fetch(num_rows); thd->protocol= &thd->protocol_simple; // Use normal protocol if (!(specialflag & SPECIAL_NO_PRIOR)) diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 81f062c06ec..18172eb1f03 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -114,17 +114,31 @@ static bool create_myisam_tmp_table(TABLE *table,TMP_TABLE_PARAM *param, static Next_select_func setup_end_select_func(JOIN *join); static int do_select(JOIN *join,List<Item> *fields,TABLE *tmp_table, Procedure *proc); -static int sub_select_cache(JOIN *join,JOIN_TAB *join_tab,bool end_of_records); -static int sub_select(JOIN *join,JOIN_TAB *join_tab,bool end_of_records); -static int flush_cached_records(JOIN *join,JOIN_TAB *join_tab,bool skip_last); -static int end_send(JOIN *join, JOIN_TAB *join_tab, bool end_of_records); -static int end_send_group(JOIN *join, JOIN_TAB *join_tab,bool end_of_records); -static int end_write(JOIN *join, JOIN_TAB *join_tab, bool end_of_records); -static int end_update(JOIN *join, JOIN_TAB *join_tab, bool end_of_records); -static int end_unique_update(JOIN *join,JOIN_TAB *join_tab, - bool end_of_records); -static int end_write_group(JOIN *join, JOIN_TAB *join_tab, - bool end_of_records); + +static enum_nested_loop_state +sub_select_cache(JOIN *join, JOIN_TAB *join_tab, bool end_of_records); +static enum_nested_loop_state +evaluate_join_record(JOIN *join, JOIN_TAB *join_tab, + int error, my_bool *report_error); +static enum_nested_loop_state +evaluate_null_complemented_join_record(JOIN *join, JOIN_TAB *join_tab); +static enum_nested_loop_state +sub_select(JOIN *join,JOIN_TAB *join_tab, bool end_of_records); +static enum_nested_loop_state +flush_cached_records(JOIN *join, JOIN_TAB *join_tab, bool skip_last); +static enum_nested_loop_state +end_send(JOIN *join, JOIN_TAB *join_tab, bool end_of_records); +static enum_nested_loop_state +end_send_group(JOIN *join, JOIN_TAB *join_tab, bool end_of_records); +static enum_nested_loop_state +end_write(JOIN *join, JOIN_TAB *join_tab, bool end_of_records); +static enum_nested_loop_state +end_update(JOIN *join, JOIN_TAB *join_tab, bool end_of_records); +static enum_nested_loop_state +end_unique_update(JOIN *join, JOIN_TAB *join_tab, bool end_of_records); +static enum_nested_loop_state +end_write_group(JOIN *join, JOIN_TAB *join_tab, bool end_of_records); + static int test_if_group_changed(List<Item_buff> &list); static int join_read_const_table(JOIN_TAB *tab, POSITION *pos); static int join_read_system(JOIN_TAB *tab); @@ -1800,13 +1814,7 @@ Cursor::open(JOIN *join_arg) happen for the first table in join_tab list */ DBUG_ASSERT(join_tab->table->null_row == 0); - - /* - There is always at least one record in the table, as otherwise we - wouldn't have opened the cursor. Therefore a failure is the only - reason read_first_record can return not 0. - */ - DBUG_RETURN(join_tab->read_first_record(join_tab)); + DBUG_RETURN(0); } @@ -1816,97 +1824,36 @@ Cursor::open(JOIN *join_arg) PRECONDITION: Cursor is open RETURN VALUES: - -4 there are more rows, send_eof sent to the client - 0 no more rows, send_eof was sent to the client, cursor is closed - other fatal fetch error, cursor is closed (error is not reported) + none, this function will send error or OK to network if necessary. */ -int +void Cursor::fetch(ulong num_rows) { THD *thd= join->thd; JOIN_TAB *join_tab= join->join_tab + join->const_tables; - COND *on_expr= *join_tab->on_expr_ref; - COND *select_cond= join_tab->select_cond; - READ_RECORD *info= &join_tab->read_record; - int error= 0; + enum_nested_loop_state error= NESTED_LOOP_OK; /* save references to memory, allocated during fetch */ thd->set_n_backup_item_arena(this, &thd->stmt_backup); join->fetch_limit+= num_rows; - /* - Run while there are new rows in the first table; - For each row, satisfying ON and WHERE clauses (those parts of them which - can be evaluated early), call next_select. - */ - do - { - int no_more_rows; - - join->examined_rows++; - - if (thd->killed) /* Aborted by user */ - { - my_message(ER_SERVER_SHUTDOWN, ER(ER_SERVER_SHUTDOWN), MYF(0)); - return -1; - } - - if (on_expr == 0 || on_expr->val_int()) - { - if (select_cond == 0 || select_cond->val_int()) - { - /* - TODO: call table->unlock_row() to unlock row failed selection, - when this feature will be used. - */ - error= join_tab->next_select(join, join_tab + 1, 0); - DBUG_ASSERT(error <= 0); - if (error) - { - /* real error or LIMIT/FETCH LIMIT worked */ - if (error == -4) - { - /* - FETCH LIMIT, read ahead one row, and close cursor - if there is no more rows XXX: to be fixed to support - non-equi-joins! - */ - if ((no_more_rows= info->read_record(info))) - error= no_more_rows > 0 ? -1: 0; - } - break; - } - } - } - /* read next row; break loop if there was an error */ - if ((no_more_rows= info->read_record(info))) - { - if (no_more_rows > 0) - error= -1; - else - { - enum { END_OF_RECORDS= 1 }; - error= join_tab->next_select(join, join_tab+1, (int) END_OF_RECORDS); - } - break; - } - } - while (thd->net.report_error == 0); - - if (thd->net.report_error) - error= -1; - if (error == -3) /* LIMIT clause worked */ - error= 0; + error= sub_select(join, join_tab, 0); + if (error == NESTED_LOOP_OK || error == NESTED_LOOP_NO_MORE_ROWS) + error= sub_select(join,join_tab,1); + if (error == NESTED_LOOP_QUERY_LIMIT) + error= NESTED_LOOP_OK; /* select_limit used */ + if (error == NESTED_LOOP_CURSOR_LIMIT) + join->resume_nested_loop= TRUE; #ifdef USING_TRANSACTIONS ha_release_temporary_latches(thd); #endif thd->restore_backup_item_arena(this, &thd->stmt_backup); - if (error == -4) + if (error == NESTED_LOOP_CURSOR_LIMIT) { /* Fetch limit worked, possibly more rows are there */ thd->server_status|= SERVER_STATUS_CURSOR_EXISTS; @@ -1916,20 +1863,19 @@ Cursor::fetch(ulong num_rows) else { close(); - if (error == 0) + if (error == NESTED_LOOP_OK) { thd->server_status|= SERVER_STATUS_LAST_ROW_SENT; ::send_eof(thd); thd->server_status&= ~SERVER_STATUS_LAST_ROW_SENT; } - else + else if (error != NESTED_LOOP_KILLED) my_message(ER_OUT_OF_RESOURCES, ER(ER_OUT_OF_RESOURCES), MYF(0)); /* free cursor memory */ free_items(free_list); free_list= 0; free_root(&main_mem_root, MYF(0)); } - return error; } @@ -2546,8 +2492,8 @@ merge_key_fields(KEY_FIELD *start,KEY_FIELD *new_fields,KEY_FIELD *end, KEY_OPTIMIZE_EXISTS) | ((old->optimize | new_fields->optimize) & KEY_OPTIMIZE_REF_OR_NULL)); - old->null_rejecting= old->null_rejecting && - new_fields->null_rejecting; + old->null_rejecting= (old->null_rejecting && + new_fields->null_rejecting); } } else if (old->eq_func && new_fields->eq_func && @@ -2559,8 +2505,8 @@ merge_key_fields(KEY_FIELD *start,KEY_FIELD *new_fields,KEY_FIELD *end, KEY_OPTIMIZE_EXISTS) | ((old->optimize | new_fields->optimize) & KEY_OPTIMIZE_REF_OR_NULL)); - old->null_rejecting= old->null_rejecting && - new_fields->null_rejecting; + old->null_rejecting= (old->null_rejecting && + new_fields->null_rejecting); } else if (old->eq_func && new_fields->eq_func && (old->val->is_null() || new_fields->val->is_null())) @@ -2572,7 +2518,7 @@ merge_key_fields(KEY_FIELD *start,KEY_FIELD *new_fields,KEY_FIELD *end, if (old->val->is_null()) old->val= new_fields->val; /* The referred expression can be NULL: */ - old->null_rejecting= false; + old->null_rejecting= 0; } else { @@ -2734,6 +2680,8 @@ add_key_field(KEY_FIELD **key_fields,uint and_level, Item_func *cond, If the condition has form "tbl.keypart = othertbl.field" and othertbl.field can be NULL, there will be no matches if othertbl.field has NULL value. + We use null_rejecting in add_not_null_conds() to add + 'othertbl.field IS NOT NULL' to tab->select_cond. */ (*key_fields)->null_rejecting= (cond->functype() == Item_func::EQ_FUNC) && ((*value)->type() == Item::FIELD_ITEM) && @@ -8949,7 +8897,8 @@ static Next_select_func setup_end_select_func(JOIN *join) static int do_select(JOIN *join,List<Item> *fields,TABLE *table,Procedure *procedure) { - int error= 0; + int rc= 0; + enum_nested_loop_state error= NESTED_LOOP_OK; JOIN_TAB *join_tab; DBUG_ENTER("do_select"); @@ -8979,24 +8928,30 @@ do_select(JOIN *join,List<Item> *fields,TABLE *table,Procedure *procedure) if (!join->conds || join->conds->val_int()) { Next_select_func end_select= join->join_tab[join->tables-1].next_select; - if (!(error=(*end_select)(join,join_tab,0)) || error == -3) - error=(*end_select)(join,join_tab,1); + error= (*end_select)(join,join_tab,0); + if (error == NESTED_LOOP_OK || error == NESTED_LOOP_QUERY_LIMIT) + error= (*end_select)(join,join_tab,1); } else if (join->send_row_on_empty_set()) - error= join->result->send_data(*join->fields); + rc= join->result->send_data(*join->fields); } else { error= sub_select(join,join_tab,0); - if (error >= 0) + if (error == NESTED_LOOP_OK || error == NESTED_LOOP_NO_MORE_ROWS) error= sub_select(join,join_tab,1); - if (error == -3) - error= 0; /* select_limit used */ + if (error == NESTED_LOOP_QUERY_LIMIT) + error= NESTED_LOOP_OK; /* select_limit used */ } + if (error == NESTED_LOOP_NO_MORE_ROWS) + error= NESTED_LOOP_OK; - if (error >= 0) + if (error == NESTED_LOOP_OK) { - error=0; + /* + Sic: this branch works even if rc != 0, e.g. when + send_data above returns an error. + */ if (!table) // If sending data to client { /* @@ -9005,10 +8960,12 @@ do_select(JOIN *join,List<Item> *fields,TABLE *table,Procedure *procedure) */ join->join_free(0); // Unlock all cursors if (join->result->send_eof()) - error= 1; // Don't send error + rc= 1; // Don't send error } DBUG_PRINT("info",("%ld records output",join->send_records)); } + else + rc= -1; if (table) { int tmp, new_errno= 0; @@ -9026,40 +8983,42 @@ do_select(JOIN *join,List<Item> *fields,TABLE *table,Procedure *procedure) table->file->print_error(new_errno,MYF(0)); } #ifndef DBUG_OFF - if (error) + if (rc) { DBUG_PRINT("error",("Error: do_select() failed")); } #endif - DBUG_RETURN(join->thd->net.report_error ? -1 : error); + DBUG_RETURN(join->thd->net.report_error ? -1 : rc); } -static int +static enum_nested_loop_state sub_select_cache(JOIN *join,JOIN_TAB *join_tab,bool end_of_records) { - int error; + enum_nested_loop_state rc; if (end_of_records) { - if ((error=flush_cached_records(join,join_tab,FALSE)) < 0) - return error; /* purecov: inspected */ - return sub_select(join,join_tab,end_of_records); + rc= flush_cached_records(join,join_tab,FALSE); + if (rc == NESTED_LOOP_OK || rc == NESTED_LOOP_NO_MORE_ROWS) + rc= sub_select(join,join_tab,end_of_records); + return rc; } if (join->thd->killed) // If aborted by user { join->thd->send_kill_message(); - return -2; /* purecov: inspected */ + return NESTED_LOOP_KILLED; /* purecov: inspected */ } if (join_tab->use_quick != 2 || test_if_quick_select(join_tab) <= 0) { if (!store_record_in_cache(&join_tab->cache)) - return 0; // There is more room in cache + return NESTED_LOOP_OK; // There is more room in cache return flush_cached_records(join,join_tab,FALSE); } - if ((error=flush_cached_records(join,join_tab,TRUE)) < 0) - return error; /* purecov: inspected */ - return sub_select(join,join_tab,end_of_records); /* Use ordinary select */ + rc= flush_cached_records(join, join_tab, TRUE); + if (rc == NESTED_LOOP_OK || rc == NESTED_LOOP_NO_MORE_ROWS) + rc= sub_select(join, join_tab, end_of_records); + return rc; } /* @@ -9171,11 +9130,10 @@ sub_select_cache(JOIN *join,JOIN_TAB *join_tab,bool end_of_records) table of the embedding nested join, if any. RETURN - 0, if success - # of the error, otherwise + return one of enum_nested_loop_state, except NESTED_LOOP_NO_MORE_ROWS. */ -static int +static enum_nested_loop_state sub_select(JOIN *join,JOIN_TAB *join_tab,bool end_of_records) { join_tab->table->null_row=0; @@ -9183,206 +9141,258 @@ sub_select(JOIN *join,JOIN_TAB *join_tab,bool end_of_records) return (*join_tab->next_select)(join,join_tab+1,end_of_records); int error; - JOIN_TAB *first_unmatched; - JOIN_TAB *tab; - /* Cache variables for faster loop */ - COND *select_cond= join_tab->select_cond; + enum_nested_loop_state rc; my_bool *report_error= &(join->thd->net.report_error); + READ_RECORD *info= &join_tab->read_record; - join->return_tab= join_tab; - - if (join_tab->last_inner) + if (join->resume_nested_loop) + { + /* If not the last table, plunge down the nested loop */ + if (join_tab < join->join_tab + join->tables - 1) + rc= (*join_tab->next_select)(join, join_tab + 1, 0); + else + { + join->resume_nested_loop= FALSE; + rc= NESTED_LOOP_OK; + } + } + else { - /* join_tab is the first inner table for an outer join operation. */ + join->return_tab= join_tab; - /* Set initial state of guard variables for this table.*/ - join_tab->found=0; - join_tab->not_null_compl= 1; + if (join_tab->last_inner) + { + /* join_tab is the first inner table for an outer join operation. */ - /* Set first_unmatched for the last inner table of this group */ - join_tab->last_inner->first_unmatched= join_tab; + /* Set initial state of guard variables for this table.*/ + join_tab->found=0; + join_tab->not_null_compl= 1; + + /* Set first_unmatched for the last inner table of this group */ + join_tab->last_inner->first_unmatched= join_tab; + } + join->thd->row_count= 0; + + error= (*join_tab->read_first_record)(join_tab); + rc= evaluate_join_record(join, join_tab, error, report_error); } - if (!(error=(*join_tab->read_first_record)(join_tab))) + while (rc == NESTED_LOOP_OK) { - bool not_exists_optimize= join_tab->table->reginfo.not_exists_optimize; - bool not_used_in_distinct=join_tab->not_used_in_distinct; - ha_rows found_records=join->found_records; - READ_RECORD *info= &join_tab->read_record; + error= info->read_record(info); + rc= evaluate_join_record(join, join_tab, error, report_error); + } - join->thd->row_count= 0; - do + if (rc == NESTED_LOOP_NO_MORE_ROWS && + join_tab->last_inner && !join_tab->found) + rc= evaluate_null_complemented_join_record(join, join_tab); + + if (rc == NESTED_LOOP_NO_MORE_ROWS) + rc= NESTED_LOOP_OK; + return rc; +} + + +/* + Process one record of the nested loop join. + + DESCRIPTION + This function will evaluate parts of WHERE/ON clauses that are + applicable to the partial record on hand and in case of success + submit this record to the next level of the nested loop. +*/ + +static enum_nested_loop_state +evaluate_join_record(JOIN *join, JOIN_TAB *join_tab, + int error, my_bool *report_error) +{ + bool not_exists_optimize= join_tab->table->reginfo.not_exists_optimize; + bool not_used_in_distinct=join_tab->not_used_in_distinct; + ha_rows found_records=join->found_records; + COND *select_cond= join_tab->select_cond; + + if (error > 0 || (*report_error)) // Fatal error + return NESTED_LOOP_ERROR; + if (error < 0) + return NESTED_LOOP_NO_MORE_ROWS; + if (join->thd->killed) // Aborted by user + { + join->thd->send_kill_message(); + return NESTED_LOOP_KILLED; /* purecov: inspected */ + } + DBUG_PRINT("info", ("select cond 0x%lx", (ulong)select_cond)); + if (!select_cond || select_cond->val_int()) + { + /* + There is no select condition or the attached pushed down + condition is true => a match is found. + */ + bool found= 1; + while (join_tab->first_unmatched && found) { - if (join->thd->killed) // Aborted by user - { - join->thd->send_kill_message(); - return -2; /* purecov: inspected */ - } - DBUG_PRINT("info", ("select cond 0x%lx", (ulong)select_cond)); - if (!select_cond || select_cond->val_int()) + /* + The while condition is always false if join_tab is not + the last inner join table of an outer join operation. + */ + JOIN_TAB *first_unmatched= join_tab->first_unmatched; + /* + Mark that a match for current outer table is found. + This activates push down conditional predicates attached + to the all inner tables of the outer join. + */ + first_unmatched->found= 1; + for (JOIN_TAB *tab= first_unmatched; tab <= join_tab; tab++) { - /* - There is no select condition or the attached pushed down - condition is true => a match is found. - */ - bool found= 1; - while (join_tab->first_unmatched && found) + /* Check all predicates that has just been activated. */ + /* + Actually all predicates non-guarded by first_unmatched->found + will be re-evaluated again. It could be fixed, but, probably, + it's not worth doing now. + */ + if (tab->select_cond && !tab->select_cond->val_int()) { - /* - The while condition is always false if join_tab is not - the last inner join table of an outer join operation. - */ - first_unmatched= join_tab->first_unmatched; - /* - Mark that a match for current outer table is found. - This activates push down conditional predicates attached - to the all inner tables of the outer join. - */ - first_unmatched->found= 1; - for (tab= first_unmatched; tab <= join_tab; tab++) - { - /* Check all predicates that has just been activated. */ + /* The condition attached to table tab is false */ + if (tab == join_tab) + found= 0; + else + { /* - Actually all predicates non-guarded by first_unmatched->found - will be re-evaluated again. It could be fixed, but, probably, - it's not worth doing now. - */ - if (tab->select_cond && !tab->select_cond->val_int()) - { - /* The condition attached to table tab is false */ - if (tab == join_tab) - found= 0; - else - { - /* - Set a return point if rejected predicate is attached - not to the last table of the current nest level. - */ - join->return_tab= tab; - return 0; - } - } + Set a return point if rejected predicate is attached + not to the last table of the current nest level. + */ + join->return_tab= tab; + return NESTED_LOOP_OK; } - /* - Check whether join_tab is not the last inner table - for another embedding outer join. - */ - if ((first_unmatched= first_unmatched->first_upper) && - first_unmatched->last_inner != join_tab) - first_unmatched= 0; - join_tab->first_unmatched= first_unmatched; } - - /* - It was not just a return to lower loop level when one - of the newly activated predicates is evaluated as false - (See above join->return_tab= tab). - */ - join->examined_rows++; - join->thd->row_count++; - - if (found) - { - if (not_exists_optimize) - break; - /* A match from join_tab is found for the current partial join. */ - if ((error=(*join_tab->next_select)(join, join_tab+1, 0)) < 0) - return error; - if (join->return_tab < join_tab) - return 0; - /* - Test if this was a SELECT DISTINCT query on a table that - was not in the field list; In this case we can abort if - we found a row, as no new rows can be added to the result. - */ - if (not_used_in_distinct && found_records != join->found_records) - return 0; - } - else - info->file->unlock_row(); } - else - { - /* - The condition pushed down to the table join_tab rejects all rows - with the beginning coinciding with the current partial join. - */ - join->examined_rows++; - join->thd->row_count++; - } - - } while (!(error=info->read_record(info)) && !(*report_error)); - } - if (error > 0 || (*report_error)) // Fatal error - return -1; - - if (join_tab->last_inner && !join_tab->found) - { - /* - The table join_tab is the first inner table of a outer join operation - and no matches has been found for the current outer row. - */ - JOIN_TAB *last_inner_tab= join_tab->last_inner; - for ( ; join_tab <= last_inner_tab ; join_tab++) - { - /* Change the the values of guard predicate variables. */ - join_tab->found= 1; - join_tab->not_null_compl= 0; - /* The outer row is complemented by nulls for each inner tables */ - restore_record(join_tab->table,s->default_values); // Make empty record - mark_as_null_row(join_tab->table); // For group by without error - select_cond= join_tab->select_cond; - /* Check all attached conditions for inner table rows. */ - if (select_cond && !select_cond->val_int()) - return 0; - } - join_tab--; - /* - The row complemented by nulls might be the first row - of embedding outer joins. - If so, perform the same actions as in the code - for the first regular outer join row above. - */ - for ( ; ; ) - { - first_unmatched= join_tab->first_unmatched; + /* + Check whether join_tab is not the last inner table + for another embedding outer join. + */ if ((first_unmatched= first_unmatched->first_upper) && first_unmatched->last_inner != join_tab) first_unmatched= 0; join_tab->first_unmatched= first_unmatched; - if (!first_unmatched) - break; - first_unmatched->found= 1; - for (JOIN_TAB *tab= first_unmatched; tab <= join_tab; tab++) - { - if (tab->select_cond && !tab->select_cond->val_int()) - { - join->return_tab= tab; - return 0; - } - } } + /* - The row complemented by nulls satisfies all conditions - attached to inner tables. - Send the row complemented by nulls to be joined with the - remaining tables. - */ - if ((error=(*join_tab->next_select)(join, join_tab+1 ,0)) < 0) - return error; + It was not just a return to lower loop level when one + of the newly activated predicates is evaluated as false + (See above join->return_tab= tab). + */ + join->examined_rows++; + join->thd->row_count++; + + if (found) + { + enum enum_nested_loop_state rc; + if (not_exists_optimize) + return NESTED_LOOP_NO_MORE_ROWS; + /* A match from join_tab is found for the current partial join. */ + rc= (*join_tab->next_select)(join, join_tab+1, 0); + if (rc != NESTED_LOOP_OK && rc != NESTED_LOOP_NO_MORE_ROWS) + return rc; + if (join->return_tab < join_tab) + return NESTED_LOOP_OK; + /* + Test if this was a SELECT DISTINCT query on a table that + was not in the field list; In this case we can abort if + we found a row, as no new rows can be added to the result. + */ + if (not_used_in_distinct && found_records != join->found_records) + return NESTED_LOOP_OK; + } + else + join_tab->read_record.file->unlock_row(); } - return 0; + else + { + /* + The condition pushed down to the table join_tab rejects all rows + with the beginning coinciding with the current partial join. + */ + join->examined_rows++; + join->thd->row_count++; + } + return NESTED_LOOP_OK; } -static int +/* + DESCRIPTION + Construct a NULL complimented partial join record and feed it to the next + level of the nested loop. This function is used in case we have + an OUTER join and no matching record was found. +*/ + +static enum_nested_loop_state +evaluate_null_complemented_join_record(JOIN *join, JOIN_TAB *join_tab) +{ + /* + The table join_tab is the first inner table of a outer join operation + and no matches has been found for the current outer row. + */ + JOIN_TAB *last_inner_tab= join_tab->last_inner; + /* Cache variables for faster loop */ + COND *select_cond; + for ( ; join_tab <= last_inner_tab ; join_tab++) + { + /* Change the the values of guard predicate variables. */ + join_tab->found= 1; + join_tab->not_null_compl= 0; + /* The outer row is complemented by nulls for each inner tables */ + restore_record(join_tab->table,s->default_values); // Make empty record + mark_as_null_row(join_tab->table); // For group by without error + select_cond= join_tab->select_cond; + /* Check all attached conditions for inner table rows. */ + if (select_cond && !select_cond->val_int()) + return NESTED_LOOP_OK; + } + join_tab--; + /* + The row complemented by nulls might be the first row + of embedding outer joins. + If so, perform the same actions as in the code + for the first regular outer join row above. + */ + for ( ; ; ) + { + JOIN_TAB *first_unmatched= join_tab->first_unmatched; + if ((first_unmatched= first_unmatched->first_upper) && + first_unmatched->last_inner != join_tab) + first_unmatched= 0; + join_tab->first_unmatched= first_unmatched; + if (!first_unmatched) + break; + first_unmatched->found= 1; + for (JOIN_TAB *tab= first_unmatched; tab <= join_tab; tab++) + { + if (tab->select_cond && !tab->select_cond->val_int()) + { + join->return_tab= tab; + return NESTED_LOOP_OK; + } + } + } + /* + The row complemented by nulls satisfies all conditions + attached to inner tables. + Send the row complemented by nulls to be joined with the + remaining tables. + */ + return (*join_tab->next_select)(join, join_tab+1, 0); +} + + +static enum_nested_loop_state flush_cached_records(JOIN *join,JOIN_TAB *join_tab,bool skip_last) { + enum_nested_loop_state rc= NESTED_LOOP_OK; int error; READ_RECORD *info; if (!join_tab->cache.records) - return 0; /* Nothing to do */ + return NESTED_LOOP_OK; /* Nothing to do */ if (skip_last) (void) store_record_in_cache(&join_tab->cache); // Must save this for later if (join_tab->use_quick == 2) @@ -9397,7 +9407,7 @@ flush_cached_records(JOIN *join,JOIN_TAB *join_tab,bool skip_last) if ((error=join_init_read_record(join_tab))) { reset_cache_write(&join_tab->cache); - return -error; /* No records or error */ + return error < 0 ? NESTED_LOOP_NO_MORE_ROWS: NESTED_LOOP_ERROR; } for (JOIN_TAB *tmp=join->join_tab; tmp != join_tab ; tmp++) @@ -9412,11 +9422,11 @@ flush_cached_records(JOIN *join,JOIN_TAB *join_tab,bool skip_last) if (join->thd->killed) { join->thd->send_kill_message(); - return -2; // Aborted by user /* purecov: inspected */ + return NESTED_LOOP_KILLED; // Aborted by user /* purecov: inspected */ } SQL_SELECT *select=join_tab->select; - if (!error && (!join_tab->cache.select || - !join_tab->cache.select->skip_record())) + if (rc == NESTED_LOOP_OK && + (!join_tab->cache.select || !join_tab->cache.select->skip_record())) { uint i; reset_cache_read(&join_tab->cache); @@ -9424,11 +9434,14 @@ flush_cached_records(JOIN *join,JOIN_TAB *join_tab,bool skip_last) { read_cached_record(join_tab); if (!select || !select->skip_record()) - if ((error=(join_tab->next_select)(join,join_tab+1,0)) < 0) + { + rc= (join_tab->next_select)(join,join_tab+1,0); + if (rc != NESTED_LOOP_OK && rc != NESTED_LOOP_NO_MORE_ROWS) { reset_cache_write(&join_tab->cache); - return error; /* purecov: inspected */ + return rc; } + } } } } while (!(error=info->read_record(info))); @@ -9437,10 +9450,10 @@ flush_cached_records(JOIN *join,JOIN_TAB *join_tab,bool skip_last) read_cached_record(join_tab); // Restore current record reset_cache_write(&join_tab->cache); if (error > 0) // Fatal error - return -1; /* purecov: inspected */ + return NESTED_LOOP_ERROR; /* purecov: inspected */ for (JOIN_TAB *tmp2=join->join_tab; tmp2 != join_tab ; tmp2++) tmp2->table->status=tmp2->status; - return 0; + return NESTED_LOOP_OK; } @@ -9906,13 +9919,32 @@ join_read_next_same_or_null(READ_RECORD *info) /***************************************************************************** - The different end of select functions - These functions returns < 0 when end is reached, 0 on ok and > 0 if a - fatal error (like table corruption) was detected + DESCRIPTION + Functions that end one nested loop iteration. Different functions + are used to support GROUP BY clause and to redirect records + to a table (e.g. in case of SELECT into a temporary table) or to the + network client. + + RETURN VALUES + NESTED_LOOP_OK - the record has been successfully handled + NESTED_LOOP_ERROR - a fatal error (like table corruption) + was detected + NESTED_LOOP_KILLED - thread shutdown was requested while processing + the record + NESTED_LOOP_QUERY_LIMIT - the record has been successfully handled; + additionally, the nested loop produced the + number of rows specified in the LIMIT clause + for the query + NESTED_LOOP_CURSOR_LIMIT - the record has been successfully handled; + additionally, there is a cursor and the nested + loop algorithm produced the number of rows + that is specified for current cursor fetch + operation. + All return values except NESTED_LOOP_OK abort the nested loop. *****************************************************************************/ /* ARGSUSED */ -static int +static enum_nested_loop_state end_send(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), bool end_of_records) { @@ -9921,14 +9953,14 @@ end_send(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), { int error; if (join->having && join->having->val_int() == 0) - DBUG_RETURN(0); // Didn't match having + DBUG_RETURN(NESTED_LOOP_OK); // Didn't match having error=0; if (join->procedure) error=join->procedure->send_row(*join->fields); else if (join->do_send_rows) error=join->result->send_data(*join->fields); if (error) - DBUG_RETURN(-1); /* purecov: inspected */ + DBUG_RETURN(NESTED_LOOP_ERROR); /* purecov: inspected */ if (++join->send_records >= join->unit->select_limit_cnt && join->do_send_rows) { @@ -9962,10 +9994,10 @@ end_send(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), join->do_send_rows= 0; if (join->unit->fake_select_lex) join->unit->fake_select_lex->select_limit= HA_POS_ERROR; - DBUG_RETURN(0); + DBUG_RETURN(NESTED_LOOP_OK); } } - DBUG_RETURN(-3); // Abort nicely + DBUG_RETURN(NESTED_LOOP_QUERY_LIMIT); // Abort nicely } else if (join->send_records >= join->fetch_limit) { @@ -9973,20 +10005,20 @@ end_send(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), There is a server side cursor and all rows for this fetch request are sent. */ - DBUG_RETURN(-4); + DBUG_RETURN(NESTED_LOOP_CURSOR_LIMIT); } } else { if (join->procedure && join->procedure->end_of_records()) - DBUG_RETURN(-1); + DBUG_RETURN(NESTED_LOOP_ERROR); } - DBUG_RETURN(0); + DBUG_RETURN(NESTED_LOOP_OK); } /* ARGSUSED */ -static int +static enum_nested_loop_state end_send_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), bool end_of_records) { @@ -10038,14 +10070,14 @@ end_send_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), } } if (error > 0) - DBUG_RETURN(-1); /* purecov: inspected */ + DBUG_RETURN(NESTED_LOOP_ERROR); /* purecov: inspected */ if (end_of_records) - DBUG_RETURN(0); + DBUG_RETURN(NESTED_LOOP_OK); if (join->send_records >= join->unit->select_limit_cnt && join->do_send_rows) { if (!(join->select_options & OPTION_FOUND_ROWS)) - DBUG_RETURN(-3); // Abort nicely + DBUG_RETURN(NESTED_LOOP_QUERY_LIMIT); // Abort nicely join->do_send_rows=0; join->unit->select_limit_cnt = HA_POS_ERROR; } @@ -10055,14 +10087,14 @@ end_send_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), There is a server side cursor and all rows for this fetch request are sent. */ - DBUG_RETURN(-4); + DBUG_RETURN(NESTED_LOOP_CURSOR_LIMIT); } } } else { if (end_of_records) - DBUG_RETURN(0); + DBUG_RETURN(NESTED_LOOP_OK); join->first_record=1; VOID(test_if_group_changed(join->group_fields)); } @@ -10070,33 +10102,32 @@ end_send_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), { copy_fields(&join->tmp_table_param); if (init_sum_functions(join->sum_funcs, join->sum_funcs_end[idx+1])) - DBUG_RETURN(-1); + DBUG_RETURN(NESTED_LOOP_ERROR); if (join->procedure) join->procedure->add(); - DBUG_RETURN(0); + DBUG_RETURN(NESTED_LOOP_OK); } } if (update_sum_func(join->sum_funcs)) - DBUG_RETURN(-1); + DBUG_RETURN(NESTED_LOOP_ERROR); if (join->procedure) join->procedure->add(); - DBUG_RETURN(0); + DBUG_RETURN(NESTED_LOOP_OK); } /* ARGSUSED */ -static int +static enum_nested_loop_state end_write(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), bool end_of_records) { TABLE *table=join->tmp_table; - int error; DBUG_ENTER("end_write"); if (join->thd->killed) // Aborted by user { join->thd->send_kill_message(); - DBUG_RETURN(-2); /* purecov: inspected */ + DBUG_RETURN(NESTED_LOOP_KILLED); /* purecov: inspected */ } if (!end_of_records) { @@ -10121,6 +10152,7 @@ end_write(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), #endif if (!join->having || join->having->val_int()) { + int error; join->found_records++; if ((error=table->file->write_row(table->record[0]))) { @@ -10129,28 +10161,28 @@ end_write(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), goto end; if (create_myisam_from_heap(join->thd, table, &join->tmp_table_param, error,1)) - DBUG_RETURN(-1); // Not a table_is_full error + DBUG_RETURN(NESTED_LOOP_ERROR); // Not a table_is_full error table->s->uniques=0; // To ensure rows are the same } if (++join->send_records >= join->tmp_table_param.end_write_records && join->do_send_rows) { if (!(join->select_options & OPTION_FOUND_ROWS)) - DBUG_RETURN(-3); + DBUG_RETURN(NESTED_LOOP_QUERY_LIMIT); join->do_send_rows=0; join->unit->select_limit_cnt = HA_POS_ERROR; - DBUG_RETURN(0); + DBUG_RETURN(NESTED_LOOP_OK); } } } end: - DBUG_RETURN(0); + DBUG_RETURN(NESTED_LOOP_OK); } /* Group by searching after group record and updating it if possible */ /* ARGSUSED */ -static int +static enum_nested_loop_state end_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), bool end_of_records) { @@ -10160,11 +10192,11 @@ end_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), DBUG_ENTER("end_update"); if (end_of_records) - DBUG_RETURN(0); + DBUG_RETURN(NESTED_LOOP_OK); if (join->thd->killed) // Aborted by user { join->thd->send_kill_message(); - DBUG_RETURN(-2); /* purecov: inspected */ + DBUG_RETURN(NESTED_LOOP_KILLED); /* purecov: inspected */ } join->found_records++; @@ -10188,9 +10220,9 @@ end_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), table->record[0]))) { table->file->print_error(error,MYF(0)); /* purecov: inspected */ - DBUG_RETURN(-1); /* purecov: inspected */ + DBUG_RETURN(NESTED_LOOP_ERROR); /* purecov: inspected */ } - DBUG_RETURN(0); + DBUG_RETURN(NESTED_LOOP_OK); } /* @@ -10212,19 +10244,19 @@ end_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), { if (create_myisam_from_heap(join->thd, table, &join->tmp_table_param, error, 0)) - DBUG_RETURN(-1); // Not a table_is_full error + DBUG_RETURN(NESTED_LOOP_ERROR); // Not a table_is_full error /* Change method to update rows */ table->file->ha_index_init(0); join->join_tab[join->tables-1].next_select=end_unique_update; } join->send_records++; - DBUG_RETURN(0); + DBUG_RETURN(NESTED_LOOP_OK); } /* Like end_update, but this is done with unique constraints instead of keys */ -static int +static enum_nested_loop_state end_unique_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), bool end_of_records) { @@ -10233,11 +10265,11 @@ end_unique_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), DBUG_ENTER("end_unique_update"); if (end_of_records) - DBUG_RETURN(0); + DBUG_RETURN(NESTED_LOOP_OK); if (join->thd->killed) // Aborted by user { join->thd->send_kill_message(); - DBUG_RETURN(-2); /* purecov: inspected */ + DBUG_RETURN(NESTED_LOOP_KILLED); /* purecov: inspected */ } init_tmptable_sum_functions(join->sum_funcs); @@ -10251,12 +10283,12 @@ end_unique_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), if ((int) table->file->get_dup_key(error) < 0) { table->file->print_error(error,MYF(0)); /* purecov: inspected */ - DBUG_RETURN(-1); /* purecov: inspected */ + DBUG_RETURN(NESTED_LOOP_ERROR); /* purecov: inspected */ } if (table->file->rnd_pos(table->record[1],table->file->dupp_ref)) { table->file->print_error(error,MYF(0)); /* purecov: inspected */ - DBUG_RETURN(-1); /* purecov: inspected */ + DBUG_RETURN(NESTED_LOOP_ERROR); /* purecov: inspected */ } restore_record(table,record[1]); update_tmptable_sum_func(join->sum_funcs,table); @@ -10264,27 +10296,26 @@ end_unique_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), table->record[0]))) { table->file->print_error(error,MYF(0)); /* purecov: inspected */ - DBUG_RETURN(-1); /* purecov: inspected */ + DBUG_RETURN(NESTED_LOOP_ERROR); /* purecov: inspected */ } } - DBUG_RETURN(0); + DBUG_RETURN(NESTED_LOOP_OK); } /* ARGSUSED */ -static int +static enum_nested_loop_state end_write_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), bool end_of_records) { TABLE *table=join->tmp_table; - int error; int idx= -1; DBUG_ENTER("end_write_group"); if (join->thd->killed) { // Aborted by user join->thd->send_kill_message(); - DBUG_RETURN(-2); /* purecov: inspected */ + DBUG_RETURN(NESTED_LOOP_KILLED); /* purecov: inspected */ } if (!join->first_record || end_of_records || (idx=test_if_group_changed(join->group_fields)) >= 0) @@ -10303,28 +10334,27 @@ end_write_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), } copy_sum_funcs(join->sum_funcs, join->sum_funcs_end[send_group_parts]); - if (join->having && join->having->val_int() == 0) - error= -1; - else if ((error= table->file->write_row(table->record[0]))) + if (!join->having || join->having->val_int()) { - if (create_myisam_from_heap(join->thd, table, - &join->tmp_table_param, - error, 0)) - DBUG_RETURN(-1); + int error= table->file->write_row(table->record[0]); + if (error && create_myisam_from_heap(join->thd, table, + &join->tmp_table_param, + error, 0)) + DBUG_RETURN(NESTED_LOOP_ERROR); } if (join->rollup.state != ROLLUP::STATE_NONE) { if (join->rollup_write_data((uint) (idx+1), table)) - DBUG_RETURN(-1); + DBUG_RETURN(NESTED_LOOP_ERROR); } if (end_of_records) - DBUG_RETURN(0); + DBUG_RETURN(NESTED_LOOP_OK); } } else { if (end_of_records) - DBUG_RETURN(0); + DBUG_RETURN(NESTED_LOOP_OK); join->first_record=1; VOID(test_if_group_changed(join->group_fields)); } @@ -10333,17 +10363,17 @@ end_write_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), copy_fields(&join->tmp_table_param); copy_funcs(join->tmp_table_param.items_to_copy); if (init_sum_functions(join->sum_funcs, join->sum_funcs_end[idx+1])) - DBUG_RETURN(-1); + DBUG_RETURN(NESTED_LOOP_ERROR); if (join->procedure) join->procedure->add(); - DBUG_RETURN(0); + DBUG_RETURN(NESTED_LOOP_OK); } } if (update_sum_func(join->sum_funcs)) - DBUG_RETURN(-1); + DBUG_RETURN(NESTED_LOOP_ERROR); if (join->procedure) join->procedure->add(); - DBUG_RETURN(0); + DBUG_RETURN(NESTED_LOOP_OK); } diff --git a/sql/sql_select.h b/sql/sql_select.h index c9a9b26d1b4..82efb62f7a3 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -91,7 +91,15 @@ enum join_type { JT_UNKNOWN,JT_SYSTEM,JT_CONST,JT_EQ_REF,JT_REF,JT_MAYBE_REF, class JOIN; -typedef int (*Next_select_func)(JOIN *,struct st_join_table *,bool); +enum enum_nested_loop_state +{ + NESTED_LOOP_KILLED= -2, NESTED_LOOP_ERROR= -1, + NESTED_LOOP_OK= 0, NESTED_LOOP_NO_MORE_ROWS= 1, + NESTED_LOOP_QUERY_LIMIT= 3, NESTED_LOOP_CURSOR_LIMIT= 4 +}; + +typedef enum_nested_loop_state +(*Next_select_func)(JOIN *, struct st_join_table *, bool); typedef int (*Read_record_func)(struct st_join_table *tab); @@ -162,6 +170,11 @@ class JOIN :public Sql_alloc uint send_group_parts; bool sort_and_group,first_record,full_join,group, no_field_update; bool do_send_rows; + /* + TRUE when we want to resume nested loop iterations when + fetching data from a cursor + */ + bool resume_nested_loop; table_map const_table_map,found_const_table_map,outer_join; ha_rows send_records,found_records,examined_rows,row_limit, select_limit; /* @@ -263,6 +276,7 @@ class JOIN :public Sql_alloc sort_and_group= 0; first_record= 0; do_send_rows= 1; + resume_nested_loop= FALSE; send_records= 0; found_records= 0; fetch_limit= HA_POS_ERROR; @@ -374,7 +388,7 @@ public: void reset_thd(THD *thd); int open(JOIN *join); - int fetch(ulong num_rows); + void fetch(ulong num_rows); void reset() { join= 0; } bool is_open() const { return join != 0; } void close(); diff --git a/sql/unireg.cc b/sql/unireg.cc index 929ca5c672e..95a383e0f01 100644 --- a/sql/unireg.cc +++ b/sql/unireg.cc @@ -685,6 +685,9 @@ static bool make_empty_rec(THD *thd, File file,enum db_type table_type, thd->count_cuted_fields= CHECK_FIELD_WARN; // To find wrong default values while ((field=it++)) { + /* + regfield don't have to be deleted as it's allocated with sql_alloc() + */ Field *regfield=make_field((char*) buff+field->offset,field->length, null_pos, null_count & 7, @@ -696,7 +699,8 @@ static bool make_empty_rec(THD *thd, File file,enum db_type table_type, field->interval, field->field_name, &table); - DBUG_ASSERT(regfield); + if (!regfield) + goto err; // End of memory if (!(field->flags & NOT_NULL_FLAG)) null_count++; @@ -730,7 +734,6 @@ static bool make_empty_rec(THD *thd, File file,enum db_type table_type, regfield->store(ER(ER_NO), (uint) strlen(ER(ER_NO)),system_charset_info); else regfield->reset(); - delete regfield; } /* Fill not used startpos */ diff --git a/tests/index_corrupt.pl b/tests/index_corrupt.pl new file mode 100755 index 00000000000..19bf54f5d11 --- /dev/null +++ b/tests/index_corrupt.pl @@ -0,0 +1,212 @@ +#!/usr/bin/perl -w +# +# This is a test for a key cache bug (bug #10167) +# To expose the bug mysqld should be started with --key-buffer-size=64K +# + +$opt_loop_count=100000; # Change this to make test harder/easier + +##################### Standard benchmark inits ############################## + +use DBI; +use Getopt::Long; +use Benchmark; + +package main; + +$opt_skip_create=$opt_skip_in=$opt_verbose=$opt_fast_insert= + $opt_lock_tables=$opt_debug=$opt_skip_delete=$opt_fast=$opt_force=0; +$opt_host=$opt_user=$opt_password=""; $opt_db="test"; + +GetOptions("host=s","db=s","loop-count=i","skip-create","skip-in", + "skip-delete","verbose","fast-insert","lock-tables","debug","fast", + "force","user=s","password=s") || die "Aborted"; +$opt_verbose=$opt_debug=$opt_lock_tables=$opt_fast_insert=$opt_fast=$opt_skip_in=$opt_force=undef; # Ignore warnings from these + +$firsttable = "bench_f1"; +$secondtable = "bench_f2"; +$kill_file= "/tmp/mysqltest_index_corrupt.$$"; + +#### +#### Start timeing and start test +#### + +$start_time=new Benchmark; +if (!$opt_skip_create) +{ + $dbh = DBI->connect("DBI:mysql:$opt_db:$opt_host", + $opt_user, $opt_password, + { PrintError => 0}) || die $DBI::errstr; + $dbh->do("drop table if exists $firsttable, $secondtable"); + + print "Creating tables in $opt_db\n"; + $dbh->do("create table $firsttable ( +c_pollid INTEGER NOT NULL, +c_time BIGINT NOT NULL, +c_data DOUBLE NOT NULL, +c_error INTEGER NOT NULL, +c_warning INTEGER NOT NULL, +c_okay INTEGER NOT NULL, +c_unknown INTEGER NOT NULL, +c_rolled_up BIT NOT NULL, +INDEX t_mgmt_hist_r_i1 (c_pollid), +INDEX t_mgmt_hist_r_i2 (c_time), +INDEX t_mgmt_hist_r_i3 (c_rolled_up))") or die $DBI::errstr; + + $dbh->do("create table $secondtable ( +c_pollid INTEGER NOT NULL, +c_min_time BIGINT NOT NULL, +c_max_time BIGINT NOT NULL, +c_min_data DOUBLE NOT NULL, +c_max_data DOUBLE NOT NULL, +c_avg_data DOUBLE NOT NULL, +c_error INTEGER NOT NULL, +c_warning INTEGER NOT NULL, +c_okay INTEGER NOT NULL, +c_unknown INTEGER NOT NULL, +c_rolled_up BIT NOT NULL, +INDEX t_mgmt_hist_d_i1 (c_pollid), +INDEX t_mgmt_hist_d_i2 (c_min_time), +INDEX t_mgmt_hist_d_i3 (c_max_time), +INDEX t_mgmt_hist_d_i4 (c_rolled_up))") or die $DBI::errstr; + + + $dbh->disconnect; $dbh=0; # Close handler +} +$|= 1; # Autoflush + +#### +#### Start the tests +#### + +print "Running tests\n"; +insert_in_bench() if (($pid=fork()) == 0); $work{$pid}="insert"; +select_from_bench() if (($pid=fork()) == 0); $work{$pid}="insert-select; +delete_from_bench() if (($pid=fork()) == 0); $work{$pid}="delete"; + +$errors=0; +while (($pid=wait()) != -1) +{ + $ret=$?/256; + print "thread '" . $work{$pid} . "' finished with exit code $ret\n"; + $errors++ if ($ret != 0); +} + +if (!$opt_skip_delete && !$errors) +{ + $dbh = DBI->connect("DBI:mysql:$opt_db:$opt_host", + $opt_user, $opt_password, + { PrintError => 0}) || die $DBI::errstr; + $dbh->do("drop table $firsttable, $secondtable"); +} +print ($errors ? "Test failed\n" :"Test ok\n"); + +$end_time=new Benchmark; +print "Total time: " . + timestr(timediff($end_time, $start_time),"noc") . "\n"; + +unlink $kill_file; + +exit(0); + +# +# Insert records in the two tables +# + +sub insert_in_bench +{ + my ($dbh,$rows,$found,$i); + + $dbh = DBI->connect("DBI:mysql:$opt_db:$opt_host", + $opt_user, $opt_password, + { PrintError => 0}) || die $DBI::errstr; + for ($rows= 1; $rows <= $opt_loop_count ; $rows++) + { + $c_pollid = sprintf("%d",rand 1000); + $c_time = sprintf("%d",rand 100000); + $c_data = rand 1000000; + $test = rand 1; + $c_error=0; + $c_warning=0; + $c_okay=0; + $c_unknown=0; + if ($test < .8) { + $c_okay=1; + } elsif ($test <.9) { + $c_error=1; + } elsif ($test <.95) { + $c_warning=1; + } else { + $c_unknown=1; + } + $statement = "INSERT INTO $firsttable (c_pollid, c_time, c_data, c_error +, c_warning, c_okay, c_unknown, c_rolled_up) ". + "VALUES ($c_pollid,$c_time,$c_data,$c_error,$c_warning,$c_okay,$c_unknown,0)"; + $cursor = $dbh->prepare($statement); + $cursor->execute(); + $cursor->finish(); + } + + $dbh->disconnect; $dbh=0; + print "insert_in_bench: Inserted $rows rows\n"; + + # Kill other threads + open(KILLFILE, "> $kill_file"); + close(KILLFILE); + + exit(0); +} + + +sub select_from_bench +{ + my ($dbh,$rows,$cursor); + + $dbh = DBI->connect("DBI:mysql:$opt_db:$opt_host", + $opt_user, $opt_password, + { PrintError => 0}) || die $DBI::errstr; + for ($rows= 1; $rows < $opt_loop_count ; $rows++) + { + $t_value = rand 100000; + $t_value2 = $t_value+10000; + $statement = "INSERT INTO $secondtable (c_pollid, c_min_time, c_max_time +, c_min_data, c_max_data, c_avg_data, c_error, c_warning, c_okay, c_unknown, c_rolled_up) SELECT c_pollid, MIN(c_time), MAX(c_time), MIN(c_data), MAX(c_data), AVG(c_data), SUM(c_error), SUM(c_warning), SUM(c_okay), SUM(c_unknown), 0 FROM $firsttable WHERE (c_time>=$t_value) AND (c_time<$t_value2) AND (c_rolled_up=0) GROUP BY c_pollid"; + $cursor = $dbh->prepare($statement); + $cursor->execute(); + $cursor->finish(); + sleep 1; + if (-e $kill_file) + { + last; + } + } + print "select_from_bench: insert-select executed $rows times\n"; + exit(0); +} + + +sub delete_from_bench +{ + my ($dbh,$row, $t_value, $t2_value, $statement, $cursor); + + $dbh = DBI->connect("DBI:mysql:$opt_db:$opt_host", + $opt_user, $opt_password, + { PrintError => 0}) || die $DBI::errstr; + + for ($rows= 1; $rows < $opt_loop_count ; $rows++) + { + $t_value = rand 50000; + $t2_value = $t_value + 50001; + $statement = "DELETE FROM $firsttable WHERE (c_time>$t_value) AND (c_time<$t2_value)"; + $cursor = $dbh->prepare($statement); + $cursor->execute(); + $cursor->finish(); + sleep 10; + if (-e $kill_file) + { + last; + } + } + print "delete: delete executed $rows times\n"; + exit(0); +} diff --git a/tests/mysql_client_test.c b/tests/mysql_client_test.c index 9b7c8281043..d484c9af7ee 100644 --- a/tests/mysql_client_test.c +++ b/tests/mysql_client_test.c @@ -7046,6 +7046,7 @@ static void test_set_option() bug #89 (reported by mark@mysql.com) */ +#ifndef EMBEDDED_LIBRARY static void test_prepare_grant() { int rc; @@ -7138,7 +7139,7 @@ static void test_prepare_grant() } } - +#endif /* Test a crash when invalid/corrupted .frm is used in the @@ -12597,7 +12598,7 @@ static void test_bug8330() const char *stmt_text; MYSQL_STMT *stmt[2]; int i, rc; - char *query= "select a,b from t1 where a=?"; + const char *query= "select a,b from t1 where a=?"; MYSQL_BIND bind[2]; long lval[2]; @@ -12788,7 +12789,7 @@ static void test_bug8722() } -MYSQL_STMT *open_cursor(char *query) +MYSQL_STMT *open_cursor(const char *query) { int rc; const ulong type= (ulong)CURSOR_TYPE_READ_ONLY; @@ -12854,6 +12855,59 @@ static void test_bug9159() myquery(rc); } + +/* Crash when opening a cursor to a query with DISTICNT and no key */ + +static void test_bug9520() +{ + MYSQL_STMT *stmt; + MYSQL_BIND bind[1]; + char a[6]; + ulong a_len; + int rc, row_count= 0; + + myheader("test_bug9520"); + + mysql_query(mysql, "drop table if exists t1"); + mysql_query(mysql, "create table t1 (a char(5), b char(5), c char(5)," + " primary key (a, b, c))"); + rc= mysql_query(mysql, "insert into t1 values ('x', 'y', 'z'), " + " ('a', 'b', 'c'), ('k', 'l', 'm')"); + myquery(rc); + + stmt= open_cursor("select distinct b from t1"); + + /* + Not crashes with: + stmt= open_cursor("select distinct a from t1"); + */ + + rc= mysql_stmt_execute(stmt); + check_execute(stmt, rc); + + bzero(bind, sizeof(bind)); + bind[0].buffer_type= MYSQL_TYPE_STRING; + bind[0].buffer= (char*) a; + bind[0].buffer_length= sizeof(a); + bind[0].length= &a_len; + + mysql_stmt_bind_result(stmt, bind); + + while (!(rc= mysql_stmt_fetch(stmt))) + row_count++; + + DIE_UNLESS(rc == MYSQL_NO_DATA); + + printf("Fetched %d rows\n", row_count); + DBUG_ASSERT(row_count == 3); + + mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "drop table t1"); + myquery(rc); +} + + /* Read and parse arguments and MySQL options from my.cnf */ @@ -13079,6 +13133,7 @@ static struct my_tests_st my_tests[]= { { "test_bug8722", test_bug8722 }, { "test_bug8880", test_bug8880 }, { "test_bug9159", test_bug9159 }, + { "test_bug9520", test_bug9520 }, { 0, 0 } }; |