From 1d20b6ffd548d87f20ae999d54fef41f0c696ee0 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 5 Mar 2008 16:27:35 +0200 Subject: [PATCH 01/40] Bug #33544: UDF_INIT member decimals initialized wrong with STRING_RESULT argument There is a "magic" number for precision : NOT_FIXED_DEC. This means that the precision is not a fixed number. But this constant was re-defined in several files and was not available to the UDF developers. Moved the NOT_FIXED_DEC definition to the correct header and removed the redundant definitions. client/sql_string.cc: Bug #33544: moved NOT_FIXED_DEC to the correct header client/sql_string.h: Bug #33544: moved NOT_FIXED_DEC to the correct header include/mysql_com.h: Bug #33544: moved NOT_FIXED_DEC to the correct header libmysql/libmysql.c: Bug #33544: moved NOT_FIXED_DEC to the correct header sql/field.h: Bug #33544: moved NOT_FIXED_DEC to the correct header sql/sql_string.cc: Bug #33544: moved NOT_FIXED_DEC to the correct header sql/sql_string.h: Bug #33544: moved NOT_FIXED_DEC to the correct header storage/ndb/include/kernel/signaldata/DictTabInfo.hpp: Bug #33544: moved NOT_FIXED_DEC to the correct header --- client/sql_string.cc | 1 + client/sql_string.h | 4 ---- include/mysql_com.h | 1 + libmysql/libmysql.c | 3 --- sql/field.h | 1 - sql/sql_string.cc | 1 + sql/sql_string.h | 4 ---- storage/ndb/include/kernel/signaldata/DictTabInfo.hpp | 4 ---- 8 files changed, 3 insertions(+), 16 deletions(-) diff --git a/client/sql_string.cc b/client/sql_string.cc index c41463999aa..9ccccca6f73 100644 --- a/client/sql_string.cc +++ b/client/sql_string.cc @@ -23,6 +23,7 @@ #include #include #include +#include #ifdef HAVE_FCONVERT #include #endif diff --git a/client/sql_string.h b/client/sql_string.h index da19c1ccfe5..f134e59e6b2 100644 --- a/client/sql_string.h +++ b/client/sql_string.h @@ -19,10 +19,6 @@ #pragma interface /* gcc class implementation */ #endif -#ifndef NOT_FIXED_DEC -#define NOT_FIXED_DEC 31 -#endif - class String; int sortcmp(const String *a,const String *b, CHARSET_INFO *cs); String *copy_if_not_alloced(String *a,String *b,uint32 arg_length); diff --git a/include/mysql_com.h b/include/mysql_com.h index 7eefad44716..60f6660c789 100644 --- a/include/mysql_com.h +++ b/include/mysql_com.h @@ -468,4 +468,5 @@ uchar *net_store_length(uchar *pkg, ulonglong length); #define MYSQL_STMT_HEADER 4 #define MYSQL_LONG_DATA_HEADER 6 +#define NOT_FIXED_DEC 31 #endif diff --git a/libmysql/libmysql.c b/libmysql/libmysql.c index a1fb1d8a3c4..7d438612251 100644 --- a/libmysql/libmysql.c +++ b/libmysql/libmysql.c @@ -3794,10 +3794,7 @@ static void fetch_float_with_conversion(MYSQL_BIND *param, MYSQL_FIELD *field, */ char buff[MAX_DOUBLE_STRING_REP_LENGTH]; char *end; - /* TODO: move this to a header shared between client and server. */ -#define NOT_FIXED_DEC 31 if (field->decimals >= NOT_FIXED_DEC) -#undef NOT_FIXED_DEC { /* The 14 below is to ensure that the server and client has the same diff --git a/sql/field.h b/sql/field.h index 9855ec563d5..c3a90dc0754 100644 --- a/sql/field.h +++ b/sql/field.h @@ -23,7 +23,6 @@ #pragma interface /* gcc class implementation */ #endif -#define NOT_FIXED_DEC 31 #define DATETIME_DEC 6 const uint32 max_field_size= (uint32) 4294967295U; diff --git a/sql/sql_string.cc b/sql/sql_string.cc index 7fa3786c382..d2899f496a5 100644 --- a/sql/sql_string.cc +++ b/sql/sql_string.cc @@ -23,6 +23,7 @@ #include #include #include +#include #ifdef HAVE_FCONVERT #include #endif diff --git a/sql/sql_string.h b/sql/sql_string.h index 128ed749b5f..19d63a94f54 100644 --- a/sql/sql_string.h +++ b/sql/sql_string.h @@ -19,10 +19,6 @@ #pragma interface /* gcc class implementation */ #endif -#ifndef NOT_FIXED_DEC -#define NOT_FIXED_DEC 31 -#endif - class String; int sortcmp(const String *a,const String *b, CHARSET_INFO *cs); String *copy_if_not_alloced(String *a,String *b,uint32 arg_length); diff --git a/storage/ndb/include/kernel/signaldata/DictTabInfo.hpp b/storage/ndb/include/kernel/signaldata/DictTabInfo.hpp index 3923d8e6fbf..43de91b7c22 100644 --- a/storage/ndb/include/kernel/signaldata/DictTabInfo.hpp +++ b/storage/ndb/include/kernel/signaldata/DictTabInfo.hpp @@ -30,10 +30,6 @@ #define DECIMAL_MAX_LENGTH ((8 * 9) - 8) -#ifndef NOT_FIXED_DEC -#define NOT_FIXED_DEC 31 -#endif - C_MODE_START extern int decimal_bin_size(int, int); C_MODE_END From a91607769ab8d54cbc7bc99d3b0e8d56ed85ed80 Mon Sep 17 00:00:00 2001 From: "Tatiana A. Nurnberg" Date: Mon, 9 Feb 2009 06:21:48 +0100 Subject: [PATCH 02/40] Bug#42661: sec_to_time() and signedness Bug#42662: maketime() and signedness Item_time_typecast::val_int() dropped sign from MYSQL_TIME gotten using from get_time(). Propagates sign now. mysql-test/r/func_sapdb.result: Show that comparisons with TIME(-...) work now. mysql-test/t/func_sapdb.test: Show that comparisons with TIME(-...) work now. sql/item_timefunc.cc: don't drop sign when converting from MYSQL_TIME --- mysql-test/r/func_sapdb.result | 30 ++++++++++++++++++++++++++++++ mysql-test/t/func_sapdb.test | 20 ++++++++++++++++++++ sql/item_timefunc.cc | 5 +++-- 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/mysql-test/r/func_sapdb.result b/mysql-test/r/func_sapdb.result index 3a8515c8831..516d1df1abc 100644 --- a/mysql-test/r/func_sapdb.result +++ b/mysql-test/r/func_sapdb.result @@ -282,3 +282,33 @@ TIMEDIFF(TIME('17:59:00'),TIME('17:00:00')), TIMEDIFF(TIME('17:00:00'),TIME('17:59:00')); 1Eq 1NEq1 1NEq2 2Eq 2NEq1 2NEq2 3Eq 3NEq1 3NEq2 Time0 Time00 Literal0000 TIMEDIFF(TIME('17:59:00'),TIME('17:00:00')) TIMEDIFF(TIME('17:00:00'),TIME('17:59:00')) 1 0 0 1 0 0 1 0 0 00:00:00 00:00:00 00:00:00 00:59:00 -00:59:00 +SELECT sec_to_time(3020399)=time('838:59:59'); +sec_to_time(3020399)=time('838:59:59') +1 +SELECT sec_to_time(-3020399)=time('-838:59:59'); +sec_to_time(-3020399)=time('-838:59:59') +1 +SELECT sec_to_time(-3020399)='-838:59:59'; +sec_to_time(-3020399)='-838:59:59' +1 +SELECT time(sec_to_time(-3020399))=time('-838:59:59'); +time(sec_to_time(-3020399))=time('-838:59:59') +1 +SELECT time(sec_to_time(-3020399))=time('-838:59:58'); +time(sec_to_time(-3020399))=time('-838:59:58') +0 +SELECT maketime(-1,0,1)='-01:00:01'; +maketime(-1,0,1)='-01:00:01' +1 +SELECT TIME(maketime(-1,0,1))=TIME('-01:00:01'); +TIME(maketime(-1,0,1))=TIME('-01:00:01') +1 +SELECT maketime(-1,0,1)=TIME('-01:00:01'); +maketime(-1,0,1)=TIME('-01:00:01') +1 +SELECT maketime(1,0,1)=TIME('01:00:01'); +maketime(1,0,1)=TIME('01:00:01') +1 +SELECT maketime(1,0,1)=TIME('01:00:02'); +maketime(1,0,1)=TIME('01:00:02') +0 diff --git a/mysql-test/t/func_sapdb.test b/mysql-test/t/func_sapdb.test index 1292c475732..6fed52e1b84 100644 --- a/mysql-test/t/func_sapdb.test +++ b/mysql-test/t/func_sapdb.test @@ -169,4 +169,24 @@ SELECT TIMEDIFF(TIME('17:00:00'),TIME('17:00:00'))=TIME('00:00:00') AS 1Eq, TIMEDIFF(TIME('17:59:00'),TIME('17:00:00')), TIMEDIFF(TIME('17:00:00'),TIME('17:59:00')); +# +# Bug#42661 - sec_to_time() and signedness +# + +SELECT sec_to_time(3020399)=time('838:59:59'); +SELECT sec_to_time(-3020399)=time('-838:59:59'); +SELECT sec_to_time(-3020399)='-838:59:59'; +SELECT time(sec_to_time(-3020399))=time('-838:59:59'); +SELECT time(sec_to_time(-3020399))=time('-838:59:58'); + +# +# Bug#42662 - maketime() and signedness +# + +SELECT maketime(-1,0,1)='-01:00:01'; +SELECT TIME(maketime(-1,0,1))=TIME('-01:00:01'); +SELECT maketime(-1,0,1)=TIME('-01:00:01'); +SELECT maketime(1,0,1)=TIME('01:00:01'); +SELECT maketime(1,0,1)=TIME('01:00:02'); + # End of 5.0 tests diff --git a/sql/item_timefunc.cc b/sql/item_timefunc.cc index 38d9d62bd99..6a48c13f2e1 100644 --- a/sql/item_timefunc.cc +++ b/sql/item_timefunc.cc @@ -1730,7 +1730,7 @@ longlong Item_func_sec_to_time::val_int() sec_to_time(arg_val, args[0]->unsigned_flag, <ime); return (ltime.neg ? -1 : 1) * - ((ltime.hour)*10000 + ltime.minute*100 + ltime.second); + (longlong) ((ltime.hour)*10000 + ltime.minute*100 + ltime.second); } @@ -2648,7 +2648,8 @@ longlong Item_time_typecast::val_int() null_value= 1; return 0; } - return ltime.hour * 10000L + ltime.minute * 100 + ltime.second; + return (ltime.neg ? -1 : 1) * + (longlong) ((ltime.hour)*10000 + ltime.minute*100 + ltime.second); } String *Item_time_typecast::val_str(String *str) From 7e3b4d21c0010bf6bc4fd8cdb8eee3caf8207f29 Mon Sep 17 00:00:00 2001 From: Alexey Kopytov Date: Tue, 13 Oct 2009 12:31:42 +0400 Subject: [PATCH 03/40] Backport of the patch for bug #8457 "Precision math: DIV returns incorrect result with large decimal value" For the DIV operator, neither operands nor result were checked for integer overflows. This patch changes the DIV behavior for non-integer operands as follows: if either of the operands has a non-integer type, convert both operands to the DECIMAL type, then calculate the division using DECIMAL arithmetics. Convert the resulting DECIMAL value into BIGINT [UNSIGNED] if it fits into the corresponding range, or throw an 'out of range' error otherwise. mysql-test/r/func_math.result: Added a test case for bug #8457. Fixed results for a test case depending on the wrong behavior. mysql-test/r/type_varchar.result: Fixed results for a test case depending on the wrong behavior. mysql-test/t/func_math.test: Added a test case for bug #8457. sql/item_func.cc: If either of the operands has a non-integer type, convert both operands to the DECIMAL type, then calculate the division using DECIMAL arithmetics. Convert the resulting DECIMAL value into BIGINT [UNSIGNED] if it fits into the corresponding range, or throw an 'out of range' error otherwise. --- mysql-test/r/func_math.result | 11 +++++++++++ mysql-test/r/type_varchar.result | 5 +++-- mysql-test/t/func_math.test | 11 +++++++++++ sql/item_func.cc | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 2 deletions(-) diff --git a/mysql-test/r/func_math.result b/mysql-test/r/func_math.result index d8b8a14afc6..f469564c78b 100644 --- a/mysql-test/r/func_math.result +++ b/mysql-test/r/func_math.result @@ -382,6 +382,9 @@ y SELECT b DIV 900 y FROM t1 GROUP BY y; y 0 +Warnings: +Warning 1366 Incorrect decimal value: '' for column '' at row -1 +Warning 1366 Incorrect decimal value: '' for column '' at row -1 SELECT c DIV 900 y FROM t1 GROUP BY y; y 0 @@ -482,4 +485,12 @@ RAND(i) 0.155220427694936 DROP TABLE t1; # +select 123456789012345678901234567890.123456789012345678901234567890 div 1 as x; +ERROR 22003: Out of range value for column 'x' at row 1 +select "123456789012345678901234567890.123456789012345678901234567890" div 1 as x; +ERROR 22003: Out of range value for column 'x' at row 1 +SHOW WARNINGS; +Level Code Message +Warning 1292 Truncated incorrect DECIMAL value: '' +Error 1264 Out of range value for column 'x' at row 1 End of 5.1 tests diff --git a/mysql-test/r/type_varchar.result b/mysql-test/r/type_varchar.result index 96042a91bae..94628eb16ee 100644 --- a/mysql-test/r/type_varchar.result +++ b/mysql-test/r/type_varchar.result @@ -475,8 +475,9 @@ a (a DIV 2) 60 30 t 0 Warnings: -Warning 1292 Truncated incorrect INTEGER value: '1a' -Warning 1292 Truncated incorrect INTEGER value: 't ' +Warning 1292 Truncated incorrect DECIMAL value: '1a' +Warning 1366 Incorrect decimal value: '' for column '' at row -1 +Warning 1292 Truncated incorrect DECIMAL value: 't ' SELECT a,CAST(a AS SIGNED) FROM t1 ORDER BY a; a CAST(a AS SIGNED) 10 10 diff --git a/mysql-test/t/func_math.test b/mysql-test/t/func_math.test index 91fdce8addb..b999b1e8c1b 100644 --- a/mysql-test/t/func_math.test +++ b/mysql-test/t/func_math.test @@ -309,4 +309,15 @@ DROP TABLE t1; --echo # +# +# Bug #8457: Precision math: +# DIV returns incorrect result with large decimal value +# Bug #46606:Casting error for large numbers in 5.4 when 'div' is used + +--error ER_WARN_DATA_OUT_OF_RANGE +select 123456789012345678901234567890.123456789012345678901234567890 div 1 as x; +--error ER_WARN_DATA_OUT_OF_RANGE +select "123456789012345678901234567890.123456789012345678901234567890" div 1 as x; +SHOW WARNINGS; + --echo End of 5.1 tests diff --git a/sql/item_func.cc b/sql/item_func.cc index cf499aaf93c..c0a84e3be28 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -1348,6 +1348,38 @@ void Item_func_div::fix_length_and_dec() longlong Item_func_int_div::val_int() { DBUG_ASSERT(fixed == 1); + + /* + Perform division using DECIMAL math if either of the operands has a + non-integer type + */ + if (args[0]->result_type() != INT_RESULT || + args[1]->result_type() != INT_RESULT) + { + my_decimal value0, value1, tmp; + my_decimal *val0, *val1; + longlong res; + int err; + + val0= args[0]->val_decimal(&value0); + val1= args[1]->val_decimal(&value1); + if ((null_value= (args[0]->null_value || args[1]->null_value))) + return 0; + + if ((err= my_decimal_div(E_DEC_FATAL_ERROR & ~E_DEC_DIV_ZERO, &tmp, + val0, val1, 0)) > 3) + { + if (err == E_DEC_DIV_ZERO) + signal_divide_by_null(); + return 0; + } + + if (my_decimal2int(E_DEC_FATAL_ERROR, &tmp, unsigned_flag, &res) & + E_DEC_OVERFLOW) + my_error(ER_WARN_DATA_OUT_OF_RANGE, MYF(0), name, 1); + return res; + } + longlong value=args[0]->val_int(); longlong val2=args[1]->val_int(); if ((null_value= (args[0]->null_value || args[1]->null_value))) From 0dae88ca2af903668c7221f2447b7be42c3a78d9 Mon Sep 17 00:00:00 2001 From: Magne Mahre Date: Tue, 3 Nov 2009 10:22:22 +0100 Subject: [PATCH 04/40] Bug#35224: mysqldump --help is very confusing The presence of "--skip" parameters is obscure, when it should be obvious from the text. Now, for boolean options, when they're default to ON and the --skip is more useful parameter, then tell the user of its existence. Backported from 6.0-codebase, revid 2572.14.1 --- client/mysqlcheck.c | 2 +- client/mysqldump.c | 2 +- mysys/my_getopt.c | 8 ++++++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/client/mysqlcheck.c b/client/mysqlcheck.c index 1affea1d189..5c801eb6e26 100644 --- a/client/mysqlcheck.c +++ b/client/mysqlcheck.c @@ -128,7 +128,7 @@ static struct my_option my_long_options[] = "Faster than extended-check, but only finds 99.99 percent of all errors. Should be good enough for most cases.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, {"write-binlog", OPT_WRITE_BINLOG, - "Log ANALYZE, OPTIMIZE and REPAIR TABLE commands. Enabled by default; use --skip-write-binlog when commands should not be sent to replication slaves.", + "Log ANALYZE, OPTIMIZE and REPAIR TABLE commands. Use --skip-write-binlog when commands should not be sent to replication slaves.", (uchar**) &opt_write_binlog, (uchar**) &opt_write_binlog, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0}, {"optimize", 'o', "Optimize table.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, diff --git a/client/mysqldump.c b/client/mysqldump.c index aa81a0e98bf..1066ad4703a 100644 --- a/client/mysqldump.c +++ b/client/mysqldump.c @@ -402,7 +402,7 @@ static struct my_option my_long_options[] = (uchar**) &opt_routines, (uchar**) &opt_routines, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, {"set-charset", OPT_SET_CHARSET, - "Add 'SET NAMES default_character_set' to the output. Enabled by default; suppress with --skip-set-charset.", + "Add 'SET NAMES default_character_set' to the output.", (uchar**) &opt_set_charset, (uchar**) &opt_set_charset, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0}, {"set-variable", 'O', diff --git a/mysys/my_getopt.c b/mysys/my_getopt.c index 22b1216f99c..533a29c9fd4 100644 --- a/mysys/my_getopt.c +++ b/mysys/my_getopt.c @@ -1220,6 +1220,14 @@ void my_print_help(const struct my_option *options) printf("%s", comment); } putchar('\n'); + if ((optp->var_type & GET_TYPE_MASK) == GET_NO_ARG || + (optp->var_type & GET_TYPE_MASK) == GET_BOOL) + { + if (optp->def_value != 0) + { + printf("%*s(Defaults to on; use --skip-%s to disable.)\n", name_space, "", optp->name); + } + } } } From 5388fbaed027a5fd04adc48dff24084052994295 Mon Sep 17 00:00:00 2001 From: Magne Mahre Date: Tue, 3 Nov 2009 23:29:16 +0100 Subject: [PATCH 05/40] Bug #36466: Adding days to day_microsecond changes interpretation of microseco When less than six places are given for microseconds, we zerofill from the right (leftmost place is always 1/10s). We only did this when all announced date/time fields were given; now we also format fractional seconds when more significant fields are left out. mysql-test/r/func_time.result: show that we treat fractions of seconds correctly (zerofill from right to six places) even if we left out fields on the left mysql-test/t/func_time.test: show that we treat fractions of seconds correctly (zerofill from right to six places) even if we left out fields on the left sql/item_timefunc.cc: format fractions of seconds even if announced more significant fields were left out --- mysql-test/r/func_time.result | 6 ++++++ mysql-test/t/func_time.test | 10 ++++++++++ sql/item_timefunc.cc | 13 +++++++------ 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/mysql-test/r/func_time.result b/mysql-test/r/func_time.result index 5e72b69c41d..59527e0b418 100644 --- a/mysql-test/r/func_time.result +++ b/mysql-test/r/func_time.result @@ -1301,6 +1301,12 @@ SELECT '2008-02-18' + INTERVAL 1 FRAC_SECOND; 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 'FRAC_SECOND' at line 1 SELECT '2008-02-18' - INTERVAL 1 FRAC_SECOND; 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 'FRAC_SECOND' at line 1 +select date_add('1000-01-01 00:00:00', interval '1.03:02:01.05' day_microsecond); +date_add('1000-01-01 00:00:00', interval '1.03:02:01.05' day_microsecond) +1000-01-02 03:02:01.050000 +select date_add('1000-01-01 00:00:00', interval '1.02' day_microsecond); +date_add('1000-01-01 00:00:00', interval '1.02' day_microsecond) +1000-01-01 00:00:01.020000 End of 5.0 tests select date_sub("0050-01-01 00:00:01",INTERVAL 2 SECOND); date_sub("0050-01-01 00:00:01",INTERVAL 2 SECOND) diff --git a/mysql-test/t/func_time.test b/mysql-test/t/func_time.test index b9da946a55f..95b8a8ec38d 100644 --- a/mysql-test/t/func_time.test +++ b/mysql-test/t/func_time.test @@ -819,6 +819,16 @@ SELECT '2008-02-18' + INTERVAL 1 FRAC_SECOND; --error ER_PARSE_ERROR SELECT '2008-02-18' - INTERVAL 1 FRAC_SECOND; +# +# Bug #36466: +# Adding days to day_microsecond changes interpretation of microseconds +# + +# show that we treat fractions of seconds correctly (zerofill from right to +# six places) even if we left out fields on the left. +select date_add('1000-01-01 00:00:00', interval '1.03:02:01.05' day_microsecond); +select date_add('1000-01-01 00:00:00', interval '1.02' day_microsecond); + --echo End of 5.0 tests # diff --git a/sql/item_timefunc.cc b/sql/item_timefunc.cc index eefc8429f5e..b5dddc38d2a 100644 --- a/sql/item_timefunc.cc +++ b/sql/item_timefunc.cc @@ -865,6 +865,8 @@ static bool get_interval_info(const char *str,uint length,CHARSET_INFO *cs, { const char *end=str+length; uint i; + long msec_length= 0; + while (str != end && !my_isdigit(cs,*str)) str++; @@ -874,12 +876,7 @@ static bool get_interval_info(const char *str,uint length,CHARSET_INFO *cs, const char *start= str; for (value=0; str != end && my_isdigit(cs,*str) ; str++) value= value*LL(10) + (longlong) (*str - '0'); - if (transform_msec && i == count - 1) // microseconds always last - { - long msec_length= 6 - (uint) (str - start); - if (msec_length > 0) - value*= (long) log_10_int[msec_length]; - } + msec_length= 6 - (str - start); values[i]= value; while (str != end && !my_isdigit(cs,*str)) str++; @@ -893,6 +890,10 @@ static bool get_interval_info(const char *str,uint length,CHARSET_INFO *cs, break; } } + + if (transform_msec && msec_length > 0) + values[count - 1] *= (long) log_10_int[msec_length]; + return (str != end); } From a5d74eb1b406e20658a1ef9ff5ac8add7b0b1c96 Mon Sep 17 00:00:00 2001 From: Magne Mahre Date: Wed, 4 Nov 2009 11:28:50 +0100 Subject: [PATCH 06/40] Bug#42664: Sign ignored for TIME types when not comparing as longlong Another code-path dropped sign of TIME, presuming all time is positive. Minds sign now. Patch depends on ChangeSet for 42661. mysql-test/r/type_time.result: Show we now no longer ignore sign of TIME-type in this code-path. mysql-test/t/type_time.test: Show we now no longer ignore sign of TIME-type in this code-path. sql/item_cmpfunc.cc: TIME_to_ulonglong() (somewhat obviously) loses sign of its argument, so we put it back in where needed. --- mysql-test/r/type_time.result | 25 +++++++++++++++++++++++++ mysql-test/t/type_time.test | 21 ++++++++++++++++++++- sql/item_cmpfunc.cc | 3 ++- 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/mysql-test/r/type_time.result b/mysql-test/r/type_time.result index e4b90196c2d..a5880f2b452 100644 --- a/mysql-test/r/type_time.result +++ b/mysql-test/r/type_time.result @@ -85,6 +85,7 @@ sec_to_time(time_to_sec(t)) 13:00:00 09:00:00 drop table t1; +End of 4.1 tests select cast('100:55:50' as time) < cast('24:00:00' as time); cast('100:55:50' as time) < cast('24:00:00' as time) 0 @@ -138,3 +139,27 @@ CAST(c AS TIME) 00:00:00 DROP TABLE t1; End of 5.0 tests +CREATE TABLE t1 (f1 TIME); +INSERT INTO t1 VALUES ('24:00:00'); +SELECT '24:00:00' = (SELECT f1 FROM t1); +'24:00:00' = (SELECT f1 FROM t1) +1 +SELECT CAST('24:00:00' AS TIME) = (SELECT f1 FROM t1); +CAST('24:00:00' AS TIME) = (SELECT f1 FROM t1) +1 +SELECT CAST('-24:00:00' AS TIME) = (SELECT f1 FROM t1); +CAST('-24:00:00' AS TIME) = (SELECT f1 FROM t1) +0 +TRUNCATE t1; +INSERT INTO t1 VALUES ('-24:00:00'); +SELECT CAST('24:00:00' AS TIME) = (SELECT f1 FROM t1); +CAST('24:00:00' AS TIME) = (SELECT f1 FROM t1) +0 +SELECT CAST('-24:00:00' AS TIME) = (SELECT f1 FROM t1); +CAST('-24:00:00' AS TIME) = (SELECT f1 FROM t1) +1 +SELECT '-24:00:00' = (SELECT f1 FROM t1); +'-24:00:00' = (SELECT f1 FROM t1) +1 +DROP TABLE t1; +End of 6.0 tests diff --git a/mysql-test/t/type_time.test b/mysql-test/t/type_time.test index 5bb521601e5..3cec27d7782 100644 --- a/mysql-test/t/type_time.test +++ b/mysql-test/t/type_time.test @@ -39,7 +39,7 @@ drop table t1; # SELECT CAST(0.2359591234567e+30 AS TIME); # ########################################################## -# End of 4.1 tests +--echo End of 4.1 tests # # Bug#29555: Comparing time values as strings may lead to a wrong result. @@ -90,3 +90,22 @@ DROP TABLE t1; --echo End of 5.0 tests + + +# +# Bug#42664 - Sign ignored for TIME types when not comparing as longlong +# + +CREATE TABLE t1 (f1 TIME); +INSERT INTO t1 VALUES ('24:00:00'); +SELECT '24:00:00' = (SELECT f1 FROM t1); +SELECT CAST('24:00:00' AS TIME) = (SELECT f1 FROM t1); +SELECT CAST('-24:00:00' AS TIME) = (SELECT f1 FROM t1); +TRUNCATE t1; +INSERT INTO t1 VALUES ('-24:00:00'); +SELECT CAST('24:00:00' AS TIME) = (SELECT f1 FROM t1); +SELECT CAST('-24:00:00' AS TIME) = (SELECT f1 FROM t1); +SELECT '-24:00:00' = (SELECT f1 FROM t1); +DROP TABLE t1; + +--echo End of 6.0 tests diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc index c29031d25b5..07160b8307a 100644 --- a/sql/item_cmpfunc.cc +++ b/sql/item_cmpfunc.cc @@ -829,7 +829,8 @@ get_time_value(THD *thd, Item ***item_arg, Item **cache_arg, else { *is_null= item->get_time(<ime); - value= !*is_null ? (longlong) TIME_to_ulonglong_datetime(<ime) : 0; + value= !*is_null ? (longlong) TIME_to_ulonglong_datetime(<ime) * + (ltime.neg ? -1 : 1) : 0; } /* Do not cache GET_USER_VAR() function as its const_item() may return TRUE From 17a6040fbcaf1610ccd4d69fe3849a6fcb3d71a8 Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Wed, 4 Nov 2009 12:59:46 +0100 Subject: [PATCH 07/40] Bug #43867 ALTER TABLE on a partitioned table causes unnecessary deadlocks Backport of revno: 2617.68.35 The problem was that if one connection is running a multi-statement transaction which involves a single partitioned table, and another connection attempts to alter the table to drop a non-existing partition, (which of course will fail), the first connection still gets ER_LOCK_DEADLOCK and cannot proceed anymore. This bug is no longer reproducable. This has also been tested with the patch for Bug#46654 "False deadlock on concurrent DML/DDL with partitions, inconsistent behavior" which concerned a similar problem but where the ALTER TABLE is semantically correct. Test case added in partition_sync.test. --- mysql-test/r/partition_sync.result | 25 ++++++++++++++++++ mysql-test/t/partition_sync.test | 41 ++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 mysql-test/r/partition_sync.result create mode 100644 mysql-test/t/partition_sync.test diff --git a/mysql-test/r/partition_sync.result b/mysql-test/r/partition_sync.result new file mode 100644 index 00000000000..31cf0569464 --- /dev/null +++ b/mysql-test/r/partition_sync.result @@ -0,0 +1,25 @@ +# +# Bug #43867 ALTER TABLE on a partitioned table +# causes unnecessary deadlocks +# +CREATE TABLE t1 (a int) PARTITION BY RANGE (a) +(PARTITION p0 VALUES LESS THAN (1), +PARTITION p1 VALUES LESS THAN (2)); +INSERT INTO t1 VALUES (0),(1); +# Connection 2 +BEGIN; +SELECT * FROM t1; +a +0 +1 +# Connection 1 +ALTER TABLE t1 DROP PARTITION p3; +ERROR HY000: Error in list of partitions to DROP +# Connection 2 +# This failed with deadlock and should not do so. +SELECT * FROM t1; +a +0 +1 +# Connection 1 +DROP TABLE t1; diff --git a/mysql-test/t/partition_sync.test b/mysql-test/t/partition_sync.test new file mode 100644 index 00000000000..a732b35b8b9 --- /dev/null +++ b/mysql-test/t/partition_sync.test @@ -0,0 +1,41 @@ +--source include/have_partition.inc +# Save the initial number of concurrent sessions. +--source include/count_sessions.inc + +--echo # +--echo # Bug #43867 ALTER TABLE on a partitioned table +--echo # causes unnecessary deadlocks +--echo # + +CREATE TABLE t1 (a int) PARTITION BY RANGE (a) +(PARTITION p0 VALUES LESS THAN (1), + PARTITION p1 VALUES LESS THAN (2)); + +INSERT INTO t1 VALUES (0),(1); + +connect(con1,localhost,root); + +--echo # Connection 2 +connection con1; +BEGIN; +SELECT * FROM t1; + +--echo # Connection 1 +connection default; +--error ER_DROP_PARTITION_NON_EXISTENT +ALTER TABLE t1 DROP PARTITION p3; + +--echo # Connection 2 +connection con1; +--echo # This failed with deadlock and should not do so. +SELECT * FROM t1; + +--echo # Connection 1 +connection default; +disconnect con1; +DROP TABLE t1; + + +# Check that all connections opened by test cases in this file are really +# gone so execution of other tests won't be affected by their presence. +--source include/wait_until_count_sessions.inc From 77b8e5d9b575a0f134fc43694871879cf4c9c27c Mon Sep 17 00:00:00 2001 From: Magne Mahre Date: Wed, 4 Nov 2009 13:20:02 +0100 Subject: [PATCH 08/40] Bug#26780: automatic vertical output for wide results Feature from Eric Bergen, CLA signed 2007-06-27. Adds new mysql client option "--auto-vertical-output", which causes the client to test whether a result table is too wide for the current window (where available) and emit vertical results in that case. Otherwise, it sends normal tabular results. client/client_priv.h: Add another enum value to client options, for automatic vertical output. client/mysql.cc: Add another command-line option, "auto-vertical-output". Add functions to get the terminal width and functions to get the widths of fields. Use them together to emit vertical output when some output table is too wide to fit in the terminal. If the terminal doesn't support reading its width, then assume 80-positions wide. mysql-test/r/mysql.result: Show that various select statements do work as expected. Wide tables become vertical and narrow ones do not. mysql-test/t/mysql.test: Show that various select statements do work as expected. These should be suitable for a wide range of window capabilities and sizes. Under extreme circumstances, the results could be arbitrary. --- client/client_priv.h | 1 + client/mysql.cc | 93 +++++++++++++++++- mysql-test/r/mysql.result | 196 ++++++++++++++++++++++++++++++++++++++ mysql-test/t/mysql.test | 8 ++ 4 files changed, 295 insertions(+), 3 deletions(-) diff --git a/client/client_priv.h b/client/client_priv.h index e99d34ffa97..c24e125e200 100644 --- a/client/client_priv.h +++ b/client/client_priv.h @@ -78,6 +78,7 @@ enum options_client OPT_SLAP_DETACH, OPT_MYSQL_REPLACE_INTO, OPT_BASE64_OUTPUT_MODE, OPT_SERVER_ID, OPT_FIX_TABLE_NAMES, OPT_FIX_DB_NAMES, OPT_SSL_VERIFY_SERVER_CERT, + OPT_AUTO_VERTICAL_OUTPUT, OPT_DEBUG_INFO, OPT_DEBUG_CHECK, OPT_COLUMN_TYPES, OPT_ERROR_LOG_FILE, OPT_WRITE_BINLOG, OPT_DUMP_DATE, OPT_INIT_COMMAND, diff --git a/client/mysql.cc b/client/mysql.cc index 35322d37c2b..39ba7f10371 100644 --- a/client/mysql.cc +++ b/client/mysql.cc @@ -143,6 +143,7 @@ static my_bool ignore_errors=0,wait_flag=0,quick=0, tty_password= 0, opt_nobeep=0, opt_reconnect=1, opt_secure_auth= 0, default_pager_set= 0, opt_sigint_ignore= 0, + auto_vertical_output= 0, show_warnings= 0, executing_query= 0, interrupted_query= 0, ignore_spaces= 0; static my_bool debug_info_flag, debug_check_flag; @@ -185,6 +186,7 @@ static MEM_ROOT hash_mem_root; static uint prompt_counter; static char delimiter[16]= DEFAULT_DELIMITER; static uint delimiter_length= 1; +unsigned short terminal_width= 80; #ifdef HAVE_SMEM static char *shared_memory_base_name=0; @@ -238,6 +240,8 @@ static const char* construct_prompt(); static char *get_arg(char *line, my_bool get_next_arg); static void init_username(); static void add_int_to_prompt(int toadd); +static int get_result_width(MYSQL_RES *res); +static int get_field_disp_length(MYSQL_FIELD * field); /* A structure which contains information on the commands this program can understand. */ @@ -1065,6 +1069,10 @@ static void mysql_end_timer(ulong start_time,char *buff); static void nice_time(double sec,char *buff,bool part_second); extern "C" sig_handler mysql_end(int sig); extern "C" sig_handler handle_sigint(int sig); +#if defined(HAVE_TERMIOS_H) +static sig_handler window_resize(int sig); +#endif + int main(int argc,char *argv[]) { @@ -1144,8 +1152,8 @@ int main(int argc,char *argv[]) if (sql_connect(current_host,current_db,current_user,opt_password, opt_silent)) { - quick=1; // Avoid history - status.exit_status=1; + quick= 1; // Avoid history + status.exit_status= 1; mysql_end(-1); } if (!status.batch) @@ -1157,6 +1165,13 @@ int main(int argc,char *argv[]) signal(SIGINT, handle_sigint); // Catch SIGINT to clean up signal(SIGQUIT, mysql_end); // Catch SIGQUIT to clean up +#if defined(HAVE_TERMIOS_H) + /* Readline will call this if it installs a handler */ + signal(SIGWINCH, window_resize); + /* call the SIGWINCH handler to get the default term width */ + window_resize(0); +#endif + put_info("Welcome to the MySQL monitor. Commands end with ; or \\g.", INFO_INFO); sprintf((char*) glob_buffer.ptr(), @@ -1315,6 +1330,16 @@ err: } +#if defined(HAVE_TERMIOS_H) +sig_handler window_resize(int sig) +{ + struct winsize window_size; + + if (ioctl(fileno(stdin), TIOCGWINSZ, &window_size) == 0) + terminal_width= window_size.ws_col; +} +#endif + static struct my_option my_long_options[] = { {"help", '?', "Display this help and exit.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, @@ -1332,6 +1357,9 @@ static struct my_option my_long_options[] = {"no-auto-rehash", 'A', "No automatic rehashing. One has to use 'rehash' to get table and field completion. This gives a quicker start of mysql and disables rehashing on reconnect. WARNING: options deprecated; use --disable-auto-rehash instead.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"auto-vertical-output", OPT_AUTO_VERTICAL_OUTPUT, + "Automatically switch to vertical output mode if the result is wider than the terminal width.", + (uchar**) &auto_vertical_output, (uchar**) &auto_vertical_output, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, {"batch", 'B', "Don't use history file. Disable interactive behavior. (Enables --silent)", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, {"character-sets-dir", OPT_CHARSETS_DIR, @@ -3038,7 +3066,7 @@ com_go(String *buffer,char *line __attribute__((unused))) print_table_data_html(result); else if (opt_xml) print_table_data_xml(result); - else if (vertical) + else if (vertical || (auto_vertical_output && (terminal_width < get_result_width(result)))) print_table_data_vertically(result); else if (opt_silent && verbose <= 2 && !output_tables) print_tab_data(result); @@ -3369,6 +3397,65 @@ print_table_data(MYSQL_RES *result) my_afree((uchar*) num_flag); } +/** + Return the length of a field after it would be rendered into text. + + This doesn't know or care about multibyte characters. Assume we're + using such a charset. We can't know that all of the upcoming rows + for this column will have bytes that each render into some fraction + of a character. It's at least possible that a row has bytes that + all render into one character each, and so the maximum length is + still the number of bytes. (Assumption 1: This can't be better + because we can never know the number of characters that the DB is + going to send -- only the number of bytes. 2: Chars <= Bytes.) + + @param field Pointer to a field to be inspected + + @returns number of character positions to be used, at most +*/ +static int get_field_disp_length(MYSQL_FIELD *field) +{ + uint length= column_names ? field->name_length : 0; + + if (quick) + length= max(length, field->length); + else + length= max(length, field->max_length); + + if (length < 4 && !IS_NOT_NULL(field->flags)) + length= 4; /* Room for "NULL" */ + + return length; +} + +/** + For a new result, return the max number of characters that any + upcoming row may return. + + @param result Pointer to the result to judge + + @returns The max number of characters in any row of this result +*/ +static int get_result_width(MYSQL_RES *result) +{ + unsigned int len= 0; + MYSQL_FIELD *field; + MYSQL_FIELD_OFFSET offset; + +#ifndef DBUG_OFF + offset= mysql_field_tell(result); + DBUG_ASSERT(offset == 0); +#else + offset= 0; +#endif + + while ((field= mysql_fetch_field(result)) != NULL) + len+= get_field_disp_length(field) + 3; /* plus bar, space, & final space */ + + (void) mysql_field_seek(result, offset); + + return len + 1; /* plus final bar. */ +} static void tee_print_sized_data(const char *data, unsigned int data_length, unsigned int total_bytes_to_send, bool right_justified) diff --git a/mysql-test/r/mysql.result b/mysql-test/r/mysql.result index 70e93c3e01f..ee64f60c759 100644 --- a/mysql-test/r/mysql.result +++ b/mysql-test/r/mysql.result @@ -201,6 +201,202 @@ COUNT (*) ERROR 2005 (HY000) at line 1: Unknown MySQL server host 'invalid_hostname' (errno) End of 5.0 tests WARNING: --server-arg option not supported in this configuration. +*************************** 1. row *************************** +1: 1 +2: 2 +3: 3 +4: 4 +5: 5 +6: 6 +7: 7 +8: 8 +9: 9 +0: 0 +1: 1 +2: 2 +3: 3 +4: 4 +5: 5 +6: 6 +7: 7 +8: 8 +9: 9 +0: 0 +1: 1 +2: 2 +3: 3 +4: 4 +5: 5 +6: 6 +7: 7 +8: 8 +9: 9 +0: 0 +1: 1 +2: 2 +3: 3 +4: 4 +5: 5 +6: 6 +7: 7 +8: 8 +9: 9 +0: 0 +1: 1 +2: 2 +3: 3 +4: 4 +5: 5 +6: 6 +7: 7 +8: 8 +9: 9 +0: 0 +1: 1 +2: 2 +3: 3 +4: 4 +5: 5 +6: 6 +7: 7 +8: 8 +9: 9 +0: 0 +1: 1 +2: 2 +3: 3 +4: 4 +5: 5 +6: 6 +7: 7 +8: 8 +9: 9 +0: 0 +1: 1 +2: 2 +3: 3 +4: 4 +5: 5 +6: 6 +7: 7 +8: 8 +9: 9 +0: 0 +1: 1 +2: 2 +3: 3 +4: 4 +5: 5 +6: 6 +7: 7 +8: 8 +9: 9 +0: 0 +1: 1 +2: 2 +3: 3 +4: 4 +5: 5 +6: 6 +7: 7 +8: 8 +9: 9 +0: 0 +1: 1 +2: 2 +3: 3 +4: 4 +5: 5 +6: 6 +7: 7 +8: 8 +9: 9 +0: 0 +1: 1 +2: 2 +3: 3 +4: 4 +5: 5 +6: 6 +7: 7 +8: 8 +9: 9 +0: 0 +1: 1 +2: 2 +3: 3 +4: 4 +5: 5 +6: 6 +7: 7 +8: 8 +9: 9 +0: 0 +1: 1 +2: 2 +3: 3 +4: 4 +5: 5 +6: 6 +7: 7 +8: 8 +9: 9 +0: 0 +1: 1 +2: 2 +3: 3 +4: 4 +5: 5 +6: 6 +7: 7 +8: 8 +9: 9 +0: 0 +1: 1 +2: 2 +3: 3 +4: 4 +5: 5 +6: 6 +7: 7 +8: 8 +9: 9 +0: 0 +1: 1 +2: 2 +3: 3 +4: 4 +5: 5 +6: 6 +7: 7 +8: 8 +9: 9 +0: 0 +1: 1 +2: 2 +3: 3 +4: 4 +5: 5 +6: 6 +7: 7 +8: 8 +9: 9 +0: 0 +1: 1 +2: 2 +3: 3 +4: 4 +5: 5 +6: 6 +7: 7 +8: 8 +9: 9 +0: 0 ++---+ +| 1 | ++---+ +| 1 | ++---+ Warning (Code 1286): Unknown table engine 'nonexistent' Warning (Code 1266): Using storage engine MyISAM for table 't2' Warning (Code 1286): Unknown table engine 'nonexistent2' diff --git a/mysql-test/t/mysql.test b/mysql-test/t/mysql.test index 6e74e065720..8cc12c6a41f 100644 --- a/mysql-test/t/mysql.test +++ b/mysql-test/t/mysql.test @@ -366,6 +366,14 @@ remove_file $MYSQLTEST_VARDIR/tmp/bug31060.sql; --exec $MYSQL --server-arg=no-defaults test -e "quit" --enable_query_log +# +# Bug#26780: patch to add auto vertical output option to the cli. +# +# Make this wide enough that it will wrap almost everywhere. +--exec $MYSQL test --auto-vertical-output --table -e "SELECT 1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0;" +# Too short to wrap. +--exec $MYSQL test --auto-vertical-output --table -e "SELECT 1;" + # # Bug #25146: Some warnings/errors not shown when using --show-warnings # From a5ab2c30df0570a2601bb27e4c5649109f6c14a2 Mon Sep 17 00:00:00 2001 From: Magne Mahre Date: Wed, 4 Nov 2009 14:31:03 +0100 Subject: [PATCH 09/40] BUG #8368 "mysqldump needs --slave-data option" Added this option, named as "--dump-slave". The purpose of this option is to be able to produce a dump from a slave used for making backups of the master. Originally, dumping from the main master was fine, but as more data accumulated, the dump process would take over 30 minutes, locking up the master database hence website for 30 minutes. A slave dedicated to producing backups was the answer, but I needed a dump that could be used to restore a slave instantly and in order to do that, it has to have three things contained in the dump: 1. "STOP SLAVE;" at the beginning 2. "CHANGE MASTER TO ..." 3. "START SLAVE;" at the end These options in this changeset contain this. --stop-slave adds "STOP SLAVE" to the beginning of the dump and "STOP SLAVE" to the end of the dump. --include-host gives the user the option to have the host explicitely added to the "CHANGE MASTER TO ..." line. --dump-slave adds the "CHANGE MASTER ..." to the dump representing not the slave's master binlog info, but the slave's master's info from "SHOW SLAVE STATUS" client/client_priv.h: Added OPT_SLAVE_DATA to client_priv.h client/mysqldump.c: * Added --dump-slave option (name per Brian) * Added --stop-slave to print "STOP SLAVE;" into the dump * Added --include-host option to include "MASTER_HOST=..." and "MASTER_PORT=..." to the dump since unlike --master-data, the host can't be assumed to be the local host * Added do_start_slave and do_stop_slave to stop the slave sql thread upon start of the dump process, and to start the slave sql upon finish of dump process - to keep the log information frozen during this time. * Added do_show_slave_status for obtaining slave information needed to compose "CHANGE MASTER ..." output to the master of this slave. * Added necessary long options and defines required for new options --- client/client_priv.h | 3 + client/mysqldump.c | 183 ++++++++++++++++++++++++ mysql-test/r/rpl_mysqldump_slave.result | 17 +++ mysql-test/t/rpl_mysqldump_slave.test | 25 ++++ 4 files changed, 228 insertions(+) create mode 100644 mysql-test/r/rpl_mysqldump_slave.result create mode 100644 mysql-test/t/rpl_mysqldump_slave.test diff --git a/client/client_priv.h b/client/client_priv.h index c24e125e200..53027465771 100644 --- a/client/client_priv.h +++ b/client/client_priv.h @@ -62,6 +62,9 @@ enum options_client OPT_MYSQL_NUMBER_OF_QUERY, OPT_IGNORE_TABLE,OPT_INSERT_IGNORE,OPT_SHOW_WARNINGS,OPT_DROP_DATABASE, OPT_TZ_UTC, OPT_AUTO_CLOSE, OPT_CREATE_SLAP_SCHEMA, + OPT_MYSQLDUMP_SLAVE_APPLY, + OPT_MYSQLDUMP_SLAVE_DATA, + OPT_MYSQLDUMP_INCLUDE_MASTER_HOST_PORT, OPT_SLAP_CSV, OPT_SLAP_CREATE_STRING, OPT_SLAP_AUTO_GENERATE_SQL_LOAD_TYPE, OPT_SLAP_AUTO_GENERATE_WRITE_NUM, OPT_SLAP_AUTO_GENERATE_ADD_AUTO, diff --git a/client/mysqldump.c b/client/mysqldump.c index 1066ad4703a..95206802fd7 100644 --- a/client/mysqldump.c +++ b/client/mysqldump.c @@ -98,6 +98,8 @@ static my_bool verbose= 0, opt_no_create_info= 0, opt_no_data= 0, opt_complete_insert= 0, opt_drop_database= 0, opt_replace_into= 0, opt_dump_triggers= 0, opt_routines=0, opt_tz_utc=1, + opt_slave_apply= 0, + opt_include_master_host_port= 0, opt_events= 0, opt_alltspcs=0, opt_notspcs= 0; static my_bool insert_pat_inited= 0, debug_info_flag= 0, debug_check_flag= 0; @@ -118,7 +120,10 @@ static my_bool server_supports_switching_charsets= TRUE; static ulong opt_compatible_mode= 0; #define MYSQL_OPT_MASTER_DATA_EFFECTIVE_SQL 1 #define MYSQL_OPT_MASTER_DATA_COMMENTED_SQL 2 +#define MYSQL_OPT_SLAVE_DATA_EFFECTIVE_SQL 1 +#define MYSQL_OPT_SLAVE_DATA_COMMENTED_SQL 2 static uint opt_mysql_port= 0, opt_master_data; +static uint opt_slave_data; static uint my_end_arg; static char * opt_mysql_unix_port=0; static int first_error=0; @@ -206,6 +211,10 @@ static struct my_option my_long_options[] = {"allow-keywords", OPT_KEYWORDS, "Allow creation of column names that are keywords.", (uchar**) &opt_keywords, (uchar**) &opt_keywords, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"apply-slave-statements", OPT_MYSQLDUMP_SLAVE_APPLY, + "Adds 'STOP SLAVE' prior to 'CHANGE MASTER' and 'START SLAVE' to bottom of dump.", + (uchar**) &opt_slave_apply, (uchar**) &opt_slave_apply, 0, GET_BOOL, NO_ARG, + 0, 0, 0, 0, 0, 0}, #ifdef __NETWARE__ {"autoclose", OPT_AUTO_CLOSE, "Auto close the screen on exit for Netware.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, @@ -264,6 +273,19 @@ static struct my_option my_long_options[] = {"disable-keys", 'K', "'/*!40000 ALTER TABLE tb_name DISABLE KEYS */; and '/*!40000 ALTER TABLE tb_name ENABLE KEYS */; will be put in the output.", (uchar**) &opt_disable_keys, (uchar**) &opt_disable_keys, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0}, + {"dump-slave", OPT_MYSQLDUMP_SLAVE_DATA, + "This causes the binary log position and filename of the master to be " + "appended to the dumped data output. Setting the value to 1, will print" + "it as a CHANGE MASTER command in the dumped data output; if equal" + " to 2, that command will be prefixed with a comment symbol. " + "This option will turn --lock-all-tables on, unless " + "--single-transaction is specified too (in which case a " + "global read lock is only taken a short time at the beginning of the dump " + "- don't forget to read about --single-transaction below). In all cases " + "any action on logs will happen at the exact moment of the dump." + "Option automatically turns --lock-tables off.", + (uchar**) &opt_slave_data, (uchar**) &opt_slave_data, 0, + GET_UINT, OPT_ARG, 0, 0, MYSQL_OPT_SLAVE_DATA_COMMENTED_SQL, 0, 0, 0}, {"events", 'E', "Dump events.", (uchar**) &opt_events, (uchar**) &opt_events, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, @@ -317,6 +339,12 @@ static struct my_option my_long_options[] = "use the directive multiple times, once for each table. Each table must " "be specified with both database and table names, e.g. --ignore-table=database.table", 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"include-master-host-port", OPT_MYSQLDUMP_INCLUDE_MASTER_HOST_PORT, + "Adds 'MASTER_HOST=, MASTER_PORT=' to 'CHANGE MASTER TO..' in dump produced with --dump-slave.", + (uchar**) &opt_include_master_host_port, + (uchar**) &opt_include_master_host_port, + 0, GET_BOOL, NO_ARG, + 0, 0, 0, 0, 0, 0}, {"insert-ignore", OPT_INSERT_IGNORE, "Insert rows with INSERT IGNORE.", (uchar**) &opt_ignore, (uchar**) &opt_ignore, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, @@ -764,6 +792,10 @@ get_one_option(int optid, const struct my_option *opt __attribute__((unused)), if (!argument) /* work like in old versions */ opt_master_data= MYSQL_OPT_MASTER_DATA_EFFECTIVE_SQL; break; + case (int) OPT_MYSQLDUMP_SLAVE_DATA: + if (!argument) /* work like in old versions */ + opt_slave_data= MYSQL_OPT_SLAVE_DATA_EFFECTIVE_SQL; + break; case (int) OPT_OPTIMIZE: extended_insert= opt_drop= opt_lock= quick= create_options= opt_disable_keys= lock_tables= opt_set_charset= 1; @@ -896,6 +928,14 @@ static int get_options(int *argc, char ***argv) return(EX_USAGE); } + /* We don't delete master logs if slave data option */ + if (opt_slave_data) + { + opt_lock_all_tables= !opt_single_transaction; + opt_master_data= 0; + opt_delete_master_logs= 0; + } + /* Ensure consistency of the set of binlog & locking options */ if (opt_delete_master_logs && !opt_master_data) opt_master_data= MYSQL_OPT_MASTER_DATA_COMMENTED_SQL; @@ -906,7 +946,10 @@ static int get_options(int *argc, char ***argv) return(EX_USAGE); } if (opt_master_data) + { opt_lock_all_tables= !opt_single_transaction; + opt_slave_data= 0; + } if (opt_single_transaction || opt_lock_all_tables) lock_tables= 0; if (enclosed && opt_enclosed) @@ -4333,6 +4376,130 @@ static int do_show_master_status(MYSQL *mysql_con) return 0; } +static int do_stop_slave_sql(MYSQL *mysql_con) +{ + MYSQL_RES *slave; + /* We need to check if the slave sql is running in the first place */ + if (mysql_query_with_error_report(mysql_con, &slave, "SHOW SLAVE STATUS")) + return(1); + else + { + MYSQL_ROW row= mysql_fetch_row(slave); + if (row && row[11]) + { + /* if SLAVE SQL is not running, we don't stop it */ + if (!strcmp(row[11],"No")) + { + mysql_free_result(slave); + /* Silently assume that they don't have the slave running */ + return(0); + } + } + } + mysql_free_result(slave); + + /* now, stop slave if running */ + if (mysql_query_with_error_report(mysql_con, 0, "STOP SLAVE SQL_THREAD")) + return(1); + + return(0); +} + +static int add_stop_slave(void) +{ + if (opt_comments) + fprintf(md_result_file, + "\n--\n-- stop slave statement to make a recovery dump)\n--\n\n"); + fprintf(md_result_file, "STOP SLAVE;\n"); + return(0); +} + +static int add_slave_statements(void) +{ + if (opt_comments) + fprintf(md_result_file, + "\n--\n-- start slave statement to make a recovery dump)\n--\n\n"); + fprintf(md_result_file, "START SLAVE;\n"); + return(0); +} + +static int do_show_slave_status(MYSQL *mysql_con) +{ + MYSQL_RES *slave; + const char *comment_prefix= + (opt_slave_data == MYSQL_OPT_SLAVE_DATA_COMMENTED_SQL) ? "-- " : ""; + if (mysql_query_with_error_report(mysql_con, &slave, "SHOW SLAVE STATUS")) + { + if (!ignore_errors) + { + /* SHOW SLAVE STATUS reports nothing and --force is not enabled */ + my_printf_error(0, "Error: Slave not set up", MYF(0)); + } + mysql_free_result(slave); + return 1; + } + else + { + MYSQL_ROW row= mysql_fetch_row(slave); + if (row && row[9] && row[21]) + { + /* SHOW MASTER STATUS reports file and position */ + if (opt_comments) + fprintf(md_result_file, + "\n--\n-- Position to start replication or point-in-time " + "recovery from (the master of this slave)\n--\n\n"); + + fprintf(md_result_file, "%sCHANGE MASTER TO ", comment_prefix); + + if (opt_include_master_host_port) + { + if (row[1]) + fprintf(md_result_file, "MASTER_HOST='%s', ", row[1]); + if (row[3]) + fprintf(md_result_file, "MASTER_PORT='%s', ", row[3]); + } + fprintf(md_result_file, + "MASTER_LOG_FILE='%s', MASTER_LOG_POS=%s;\n", row[9], row[21]); + + check_io(md_result_file); + } + mysql_free_result(slave); + } + return 0; +} + +static int do_start_slave_sql(MYSQL *mysql_con) +{ + MYSQL_RES *slave; + /* We need to check if the slave sql is stopped in the first place */ + if (mysql_query_with_error_report(mysql_con, &slave, "SHOW SLAVE STATUS")) + return(1); + else + { + MYSQL_ROW row= mysql_fetch_row(slave); + if (row && row[11]) + { + /* if SLAVE SQL is not running, we don't start it */ + if (!strcmp(row[11],"Yes")) + { + mysql_free_result(slave); + /* Silently assume that they don't have the slave running */ + return(0); + } + } + } + mysql_free_result(slave); + + /* now, start slave if stopped */ + if (mysql_query_with_error_report(mysql_con, 0, "START SLAVE")) + { + my_printf_error(0, "Error: Unable to start slave", MYF(0)); + return 1; + } + return(0); +} + + static int do_flush_tables_read_lock(MYSQL *mysql_con) { @@ -4995,6 +5162,9 @@ int main(int argc, char **argv) if (!path) write_header(md_result_file, *argv); + if (opt_slave_data && do_stop_slave_sql(mysql)) + goto err; + if ((opt_lock_all_tables || opt_master_data) && do_flush_tables_read_lock(mysql)) goto err; @@ -5013,8 +5183,13 @@ int main(int argc, char **argv) goto err; flush_logs= 0; /* not anymore; that would not be sensible */ } + /* Add 'STOP SLAVE to beginning of dump */ + if (opt_slave_apply && add_stop_slave()) + goto err; if (opt_master_data && do_show_master_status(mysql)) goto err; + if (opt_slave_data && do_show_slave_status(mysql)) + goto err; if (opt_single_transaction && do_unlock_tables(mysql)) /* unlock but no commit! */ goto err; @@ -5042,6 +5217,14 @@ int main(int argc, char **argv) dump_databases(argv); } + /* if --dump-slave , start the slave sql thread */ + if (opt_slave_data && do_start_slave_sql(mysql)) + goto err; + + /* add 'START SLAVE' to end of dump */ + if (opt_slave_apply && add_slave_statements()) + goto err; + /* ensure dumped data flushed */ if (md_result_file && fflush(md_result_file)) { diff --git a/mysql-test/r/rpl_mysqldump_slave.result b/mysql-test/r/rpl_mysqldump_slave.result new file mode 100644 index 00000000000..158a43a658c --- /dev/null +++ b/mysql-test/r/rpl_mysqldump_slave.result @@ -0,0 +1,17 @@ +stop slave; +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; +reset master; +reset slave; +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; +start slave; +# +# New --dump-slave, --apply-slave-statements functionality +# +use test; +CHANGE MASTER TO MASTER_LOG_FILE='master-bin.000001', MASTER_LOG_POS=107; +STOP SLAVE; +CHANGE MASTER TO MASTER_LOG_FILE='master-bin.000001', MASTER_LOG_POS=107; +START SLAVE; +STOP SLAVE; +CHANGE MASTER TO MASTER_HOST='127.0.0.1', MASTER_PORT='MASTER_MYPORT', MASTER_LOG_FILE='master-bin.000001', MASTER_LOG_POS=107; +START SLAVE; diff --git a/mysql-test/t/rpl_mysqldump_slave.test b/mysql-test/t/rpl_mysqldump_slave.test new file mode 100644 index 00000000000..5723f1282aa --- /dev/null +++ b/mysql-test/t/rpl_mysqldump_slave.test @@ -0,0 +1,25 @@ +source include/master-slave.inc; + +--echo # +--echo # New --dump-slave, --apply-slave-statements functionality +--echo # + +# There is a gap between when START SLAVE returns and when MASTER_LOG_FILE and +# MASTER_LOG_POS are set. Ensure that we don't call SHOW SLAVE STATUS during +# that gap. +--sync_slave_with_master + +connection master; +use test; + +connection slave; + +# Execute mysqldump with --dump-slave +--exec $MYSQL_DUMP_SLAVE --compact --dump-slave test + +# Execute mysqldump with --dump-slave and --apply-slave-statements +--exec $MYSQL_DUMP_SLAVE --compact --dump-slave --apply-slave-statements test + +--replace_result $MASTER_MYPORT MASTER_MYPORT +# Execute mysqldump with --dump-slave ,--apply-slave-statements and --include-master-host-port +--exec $MYSQL_DUMP_SLAVE --compact --dump-slave --apply-slave-statements --include-master-host-port test From bda76437ae2bff206e53cbeca352651e361cd045 Mon Sep 17 00:00:00 2001 From: Magne Mahre Date: Thu, 5 Nov 2009 10:31:58 +0100 Subject: [PATCH 10/40] Fix compile error on HP/UX. SIGWINCH is not defined in termios.h on HP/UX. --- client/mysql.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/mysql.cc b/client/mysql.cc index 39ba7f10371..e538ed7df3a 100644 --- a/client/mysql.cc +++ b/client/mysql.cc @@ -1069,7 +1069,7 @@ static void mysql_end_timer(ulong start_time,char *buff); static void nice_time(double sec,char *buff,bool part_second); extern "C" sig_handler mysql_end(int sig); extern "C" sig_handler handle_sigint(int sig); -#if defined(HAVE_TERMIOS_H) +#if defined(HAVE_TERMIOS_H) && defined(GWINSZ_IN_SYS_IOCTL) static sig_handler window_resize(int sig); #endif @@ -1165,7 +1165,7 @@ int main(int argc,char *argv[]) signal(SIGINT, handle_sigint); // Catch SIGINT to clean up signal(SIGQUIT, mysql_end); // Catch SIGQUIT to clean up -#if defined(HAVE_TERMIOS_H) +#if defined(HAVE_TERMIOS_H) && defined(GWINSZ_IN_SYS_IOCTL) /* Readline will call this if it installs a handler */ signal(SIGWINCH, window_resize); /* call the SIGWINCH handler to get the default term width */ @@ -1330,7 +1330,7 @@ err: } -#if defined(HAVE_TERMIOS_H) +#if defined(HAVE_TERMIOS_H) && defined(GWINSZ_IN_SYS_IOCTL) sig_handler window_resize(int sig) { struct winsize window_size; From 63bfe08d560082ac6fc7e21f28e797aabc3718e0 Mon Sep 17 00:00:00 2001 From: Davi Arnaut Date: Tue, 10 Nov 2009 12:21:50 -0200 Subject: [PATCH 11/40] Backport of Bug#41971 to mysql-next-mr ------------------------------------------------------------ revno: 2617.31.21 revision-id: davi.arnaut@sun.com-20090402193933-2zbhg15kd0z3xh8r parent: alik@sun.com-20090402081500-78l1hpkx03twe4bf committer: Davi Arnaut branch nick: 41971-6.0 timestamp: Thu 2009-04-02 16:39:33 -0300 message: Bug#41971: Thread state on embedded server is always "Writing to net" The problem is that the state of a thread on a embedded server is always displayed as "Writing to net", which is wrong as there is no "network" in the embedded server. The solution is only exclude, on a embedded server, the thread state conditions that are related to network operations. Other thread states related to waiting on conditions or other operations are preserved. sql/sql_show.cc: Unroll conditionals into a function. Skip check for network operations on the embedded library. Change use of thread_info:: command to retrieve the command directly from the thread handle -- they have the same value. --- sql/sql_show.cc | 57 ++++++++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 76037792e94..5997ceaa941 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -1697,6 +1697,32 @@ public: template class I_List; #endif +static const char *thread_state_info(THD *tmp) +{ + if (tmp->locked) + return "Locked"; +#ifndef EMBEDDED_LIBRARY + if (tmp->net.reading_or_writing) + { + if (tmp->net.reading_or_writing == 2) + return "Writing to net"; + else if (tmp->command == COM_SLEEP) + return ""; + else + return "Reading from net"; + } + else +#endif + { + if (tmp->proc_info) + return tmp->proc_info; + else if (tmp->mysys_var && tmp->mysys_var->current_cond) + return "Waiting on cond"; + else + return NULL; + } +} + void mysqld_list_processes(THD *thd,const char *user, bool verbose) { Item *field; @@ -1758,20 +1784,7 @@ void mysqld_list_processes(THD *thd,const char *user, bool verbose) if ((mysys_var= tmp->mysys_var)) pthread_mutex_lock(&mysys_var->mutex); thd_info->proc_info= (char*) (tmp->killed == THD::KILL_CONNECTION? "Killed" : 0); -#ifndef EMBEDDED_LIBRARY - thd_info->state_info= (char*) (tmp->locked ? "Locked" : - tmp->net.reading_or_writing ? - (tmp->net.reading_or_writing == 2 ? - "Writing to net" : - thd_info->command == COM_SLEEP ? "" : - "Reading from net") : - tmp->proc_info ? tmp->proc_info : - tmp->mysys_var && - tmp->mysys_var->current_cond ? - "Waiting on cond" : NullS); -#else - thd_info->state_info= (char*)"Writing to net"; -#endif + thd_info->state_info= thread_state_info(tmp); if (mysys_var) pthread_mutex_unlock(&mysys_var->mutex); @@ -1883,21 +1896,7 @@ int fill_schema_processlist(THD* thd, TABLE_LIST* tables, COND* cond) table->field[5]->store((longlong)(tmp->start_time ? now - tmp->start_time : 0), FALSE); /* STATE */ -#ifndef EMBEDDED_LIBRARY - val= (char*) (tmp->locked ? "Locked" : - tmp->net.reading_or_writing ? - (tmp->net.reading_or_writing == 2 ? - "Writing to net" : - tmp->command == COM_SLEEP ? "" : - "Reading from net") : - tmp->proc_info ? tmp->proc_info : - tmp->mysys_var && - tmp->mysys_var->current_cond ? - "Waiting on cond" : NullS); -#else - val= (char *) (tmp->proc_info ? tmp->proc_info : NullS); -#endif - if (val) + if ((val= thread_state_info(tmp))) { table->field[6]->store(val, strlen(val), cs); table->field[6]->set_notnull(); From 33a02e3f9224cda378c97366362d717e5dd3e286 Mon Sep 17 00:00:00 2001 From: Davi Arnaut Date: Tue, 10 Nov 2009 12:23:55 -0200 Subject: [PATCH 12/40] Backport of Bug#47304 to mysql-next-mr ------------------------------------------------------------ revno: 3624 revision-id: jon.hauglid@sun.com-20090928163426-2lg1gofzz44xzzxf parent: alik@sun.com-20090928050057-r0a62x9czr01q7oe committer: Jon Olav Hauglid branch nick: mysql-6.0-codebase-bugfixing-bug47304 timestamp: Mon 2009-09-28 18:34:26 +0200 message: Bug #47304 Test main.mdl_sync fails on embedded server The problem was that SHOW PROCESSLIST was trying to access a thread that was not properly running and therefore had an uninitialized mutex. This patch explicitly resets thd->mysys_var after each embedded server command to prevent the mutex from being accessed in an illegal state. The patch also re-enables lock_multi.test and mdl_sync.test for embedded server as they had been disabled because of this bug. --- libmysqld/lib_sql.cc | 1 + mysql-test/t/lock_multi.test | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/libmysqld/lib_sql.cc b/libmysqld/lib_sql.cc index 1a078d0d0c6..9717c2fcfb6 100644 --- a/libmysqld/lib_sql.cc +++ b/libmysqld/lib_sql.cc @@ -138,6 +138,7 @@ emb_advanced_command(MYSQL *mysql, enum enum_server_command command, result= dispatch_command(command, thd, (char *) arg, arg_length); thd->cur_data= 0; + thd->mysys_var= NULL; if (!skip_check) result= thd->is_error() ? -1 : 0; diff --git a/mysql-test/t/lock_multi.test b/mysql-test/t/lock_multi.test index 47b5aa0292b..dbaa96b0374 100644 --- a/mysql-test/t/lock_multi.test +++ b/mysql-test/t/lock_multi.test @@ -1,5 +1,3 @@ --- source include/not_embedded.inc - # Save the initial number of concurrent sessions --source include/count_sessions.inc From 8063dd8763c2061713999b46d441ad0727568982 Mon Sep 17 00:00:00 2001 From: Kristofer Pettersson Date: Tue, 10 Nov 2009 15:56:05 +0100 Subject: [PATCH 13/40] Bug#27145 EXTRA_ACL troubles Correction of backport patch: * Fixed signature of check_access_table() for embedded build * Fixed typo for last argument in a check_access() call from UINT_MAX to 0. --- sql/mysql_priv.h | 4 ++-- sql/sql_parse.cc | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index d5ecc3dd3dc..5d521fca0f8 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -1064,9 +1064,9 @@ inline bool check_access(THD *thd, ulong access, const char *db, return false; } inline bool check_table_access(THD *thd, ulong requirements,TABLE_LIST *tables, - bool no_errors, bool any_combination_of_privileges_will_do, - uint number) + uint number, + bool no_errors) { return false; } #endif /*NO_EMBEDDED_ACCESS_CHECKS*/ diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index b99ff1c99c5..b5a088af539 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -2268,7 +2268,7 @@ mysql_execute_command(THD *thd) privileges_requested, all_tables, FALSE, UINT_MAX, FALSE); else - res= check_access(thd, privileges_requested, any_db, 0, 0, 0, UINT_MAX); + res= check_access(thd, privileges_requested, any_db, 0, 0, 0, 0); if (res) break; From 9cfd2fce0cd72a2600b60ed182b808210cef88e5 Mon Sep 17 00:00:00 2001 From: Davi Arnaut Date: Tue, 10 Nov 2009 16:11:27 -0200 Subject: [PATCH 14/40] Backport of Bug#36649 to mysql-next-mr ------------------------------------------------------------ revno: 2630.39.3 revision-id: davi.arnaut@sun.com-20081210215359-i876m4zgc2d6rzs3 parent: kostja@sun.com-20081208222938-9es7wl61moli71ht committer: Davi Arnaut branch nick: 36649-6.0 timestamp: Wed 2008-12-10 19:53:59 -0200 message: Bug#36649: Condition area is not properly cleaned up after stored routine invocation The problem is that the diagnostics area of a trigger is not isolated from the area of the statement that caused the trigger invocation. In MySQL terms, it means that warnings generated during the execution of the trigger are not removed from the "warning area" at the end of the execution. Before this fix, the rules for MySQL message list life cycle (see manual entry for SHOW WARNINGS) did not apply to statements inside stored programs: - The manual says that the list of messages is cleared by a statement that uses a table (any table). However, such statement, if run inside a stored program did not clear the message list. - The manual says that the list is cleared by a statement that generates a new error or a warning, but this was not the case with stored program statements either and is changed to be the case as well. In other words, after this fix, a statement has the same effect on the message list regardless of whether it's executed inside a stored program/sub-statement or not. This introduces an incompatible change: - before this fix, a, e.g. statement inside a trigger could never clear the global warning list - after this fix, a trigger that generates a warning or uses a table, clears the global warning list - however, when we leave a trigger or a function, the caller's warning information is restored (see more on this below). This change is not backward compatible as it is intended to make MySQL behavior similar to the SQL standard behavior: A stored function or trigger will get its own "warning area" (or, in standard terminology, diagnostics area). At the beginning of the stored function or trigger, all messages from the caller area will be copied to the area of the trigger. During execution, the message list will be cleared according to the MySQL rules described on the manual (SHOW WARNINGS entry). At the end of the function/trigger, the "warning area" will be destroyed along with all warnings it contains, except that if the last statement of the function/trigger generated messages, these are copied into the "warning area" of the caller. Consequently, statements that use a table or generate a warning *will* clear warnings inside the trigger, but that will have no effect to the warning list of the calling (outer) statement. mysql-test/r/sp.result: Fix test case results. mysql-test/r/trigger.result: Fix test case results. mysql-test/t/sp.test: Add test case for Bug#36649 mysql-test/t/trigger.test: Add test case for Bug#36649 sql/sp_head.cc: Emulate multiple warning areas -- one per stored program instance. sql/sql_parse.cc: Message list reset rules are the same for statements inside or outside compound statements. --- mysql-test/r/sp.result | 95 ++++++++++++++++++++++++++++++ mysql-test/r/trigger.result | 47 +++++++++++++++ mysql-test/t/sp.test | 113 ++++++++++++++++++++++++++++++++++++ mysql-test/t/trigger.test | 64 ++++++++++++++++++++ sql/sp_head.cc | 1 - sql/sql_parse.cc | 1 - 6 files changed, 319 insertions(+), 2 deletions(-) diff --git a/mysql-test/r/sp.result b/mysql-test/r/sp.result index 45dac2f5523..26ba2af8f68 100644 --- a/mysql-test/r/sp.result +++ b/mysql-test/r/sp.result @@ -6941,6 +6941,101 @@ SELECT * FROM t1 WHERE a = f1(); ERROR 42S02: Table 'test.t_non_existing' doesn't exist DROP FUNCTION f1; DROP TABLE t1; +DROP PROCEDURE IF EXISTS p1; +CREATE PROCEDURE p1(a INT, b CHAR) +BEGIN +IF a > 0 THEN +CALL p1(a-1, 'ab'); +ELSE +SELECT 1; +END IF; +END| +SET @save_max_sp_recursion= @@max_sp_recursion_depth; +SET @@max_sp_recursion_depth= 5; +CALL p1(4, 'a'); +1 +1 +Warnings: +Warning 1265 Data truncated for column 'b' at row 1 +Warning 1265 Data truncated for column 'b' at row 1 +Warning 1265 Data truncated for column 'b' at row 1 +Warning 1265 Data truncated for column 'b' at row 1 +SET @@max_sp_recursion_depth= @save_max_sp_recursion; +DROP PROCEDURE p1; +DROP PROCEDURE IF EXISTS p1; +CREATE PROCEDURE p1(a CHAR) +BEGIN +SELECT 1; +SELECT CAST('10 ' as UNSIGNED INTEGER); +SELECT 1; +END| +CALL p1('data truncated parameter'); +1 +1 +CAST('10 ' as UNSIGNED INTEGER) +10 +1 +1 +Warnings: +Warning 1265 Data truncated for column 'a' at row 1 +Warning 1292 Truncated incorrect INTEGER value: '10 ' +DROP PROCEDURE p1; +DROP PROCEDURE IF EXISTS p1; +DROP PROCEDURE IF EXISTS p2; +DROP PROCEDURE IF EXISTS p3; +DROP PROCEDURE IF EXISTS p4; +CREATE PROCEDURE p1() +CALL p2()| +CREATE PROCEDURE p2() +CALL p3()| +CREATE PROCEDURE p3() +CALL p4()| +CREATE PROCEDURE p4() +BEGIN +SELECT 1; +SELECT CAST('10 ' as UNSIGNED INTEGER); +SELECT 2; +END| +CALL p1(); +1 +1 +CAST('10 ' as UNSIGNED INTEGER) +10 +2 +2 +Warnings: +Warning 1292 Truncated incorrect INTEGER value: '10 ' +DROP PROCEDURE p1; +DROP PROCEDURE p2; +DROP PROCEDURE p3; +DROP PROCEDURE p4; +DROP FUNCTION IF EXISTS f1; +DROP FUNCTION IF EXISTS f2; +DROP FUNCTION IF EXISTS f3; +DROP FUNCTION IF EXISTS f4; +DROP TABLE IF EXISTS t1; +CREATE TABLE t1 (a CHAR(2)); +INSERT INTO t1 VALUES ('aa'); +CREATE FUNCTION f1() RETURNS CHAR +RETURN (SELECT f2())| +CREATE FUNCTION f2() RETURNS CHAR +RETURN (SELECT f3())| +CREATE FUNCTION f3() RETURNS CHAR +RETURN (SELECT f4())| +CREATE FUNCTION f4() RETURNS CHAR +BEGIN +RETURN (SELECT a FROM t1); +END| +SELECT f1(); +f1() +a +Warnings: +Warning 1265 Data truncated for column 'f4()' at row 1 +DROP FUNCTION f1; +DROP FUNCTION f2; +DROP FUNCTION f3; +DROP FUNCTION f4; +DROP TABLE t1; # # Bug#34197: CREATE PROCEDURE fails when COMMENT truncated in non # strict SQL mode diff --git a/mysql-test/r/trigger.result b/mysql-test/r/trigger.result index 1a6494e03ad..47b50b233b3 100644 --- a/mysql-test/r/trigger.result +++ b/mysql-test/r/trigger.result @@ -2115,3 +2115,50 @@ s1 DELETE FROM t1; DROP TABLE t1; DROP TEMPORARY TABLE t2; +DROP TRIGGER IF EXISTS trg1; +DROP TABLE IF EXISTS t1; +CREATE TABLE t1 (a INT); +CREATE TRIGGER trg1 BEFORE INSERT ON t1 FOR EACH ROW +BEGIN +DECLARE a CHAR; +SELECT 'ab' INTO a; +SELECT 'ab' INTO a; +SELECT 'a' INTO a; +END| +INSERT INTO t1 VALUES (1); +Warnings: +Warning 1265 Data truncated for column 'a' at row 1 +DROP TRIGGER trg1; +DROP TABLE t1; +DROP TRIGGER IF EXISTS trg1; +DROP TRIGGER IF EXISTS trg2; +DROP TABLE IF EXISTS t1; +CREATE TABLE t1 (a INT); +CREATE TRIGGER trg1 BEFORE INSERT ON t1 FOR EACH ROW +BEGIN +DECLARE trg1 CHAR; +SELECT 'ab' INTO trg1; +END| +CREATE TRIGGER trg2 AFTER INSERT ON t1 FOR EACH ROW +BEGIN +DECLARE trg2 CHAR; +SELECT 'ab' INTO trg2; +END| +INSERT INTO t1 VALUES (0); +Warnings: +Warning 1265 Data truncated for column 'trg1' at row 1 +Warning 1265 Data truncated for column 'trg2' at row 1 +SELECT * FROM t1; +a +0 +SHOW WARNINGS; +Level Code Message +INSERT INTO t1 VALUES (1),(2); +Warnings: +Warning 1265 Data truncated for column 'trg1' at row 1 +Warning 1265 Data truncated for column 'trg2' at row 1 +Warning 1265 Data truncated for column 'trg1' at row 1 +Warning 1265 Data truncated for column 'trg2' at row 1 +DROP TRIGGER trg1; +DROP TRIGGER trg2; +DROP TABLE t1; diff --git a/mysql-test/t/sp.test b/mysql-test/t/sp.test index eef843ded59..a9825d13f66 100644 --- a/mysql-test/t/sp.test +++ b/mysql-test/t/sp.test @@ -8295,6 +8295,119 @@ SELECT * FROM t1 WHERE a = f1(); DROP FUNCTION f1; DROP TABLE t1; +# +# Bug#36649: Condition area is not properly cleaned up after stored routine invocation +# + +--disable_warnings +DROP PROCEDURE IF EXISTS p1; +--enable_warnings + +delimiter |; +CREATE PROCEDURE p1(a INT, b CHAR) +BEGIN + IF a > 0 THEN + CALL p1(a-1, 'ab'); + ELSE + SELECT 1; + END IF; +END| +delimiter ;| + +SET @save_max_sp_recursion= @@max_sp_recursion_depth; +SET @@max_sp_recursion_depth= 5; +CALL p1(4, 'a'); +SET @@max_sp_recursion_depth= @save_max_sp_recursion; + +DROP PROCEDURE p1; + +# +# Ensure that rules for message list clean up are being respected. +# + +--disable_warnings +DROP PROCEDURE IF EXISTS p1; +--enable_warnings + +delimiter |; +CREATE PROCEDURE p1(a CHAR) +BEGIN + SELECT 1; + SELECT CAST('10 ' as UNSIGNED INTEGER); + SELECT 1; +END| +delimiter ;| + +CALL p1('data truncated parameter'); + +DROP PROCEDURE p1; + +# +# Cascading stored procedure/function calls. +# + +--disable_warnings +DROP PROCEDURE IF EXISTS p1; +DROP PROCEDURE IF EXISTS p2; +DROP PROCEDURE IF EXISTS p3; +DROP PROCEDURE IF EXISTS p4; +--enable_warnings + +delimiter |; +CREATE PROCEDURE p1() + CALL p2()| +CREATE PROCEDURE p2() + CALL p3()| +CREATE PROCEDURE p3() + CALL p4()| +CREATE PROCEDURE p4() +BEGIN + SELECT 1; + SELECT CAST('10 ' as UNSIGNED INTEGER); + SELECT 2; +END| +delimiter ;| + +CALL p1(); + +DROP PROCEDURE p1; +DROP PROCEDURE p2; +DROP PROCEDURE p3; +DROP PROCEDURE p4; + +--disable_warnings +DROP FUNCTION IF EXISTS f1; +DROP FUNCTION IF EXISTS f2; +DROP FUNCTION IF EXISTS f3; +DROP FUNCTION IF EXISTS f4; +DROP TABLE IF EXISTS t1; +--enable_warnings + +CREATE TABLE t1 (a CHAR(2)); + +INSERT INTO t1 VALUES ('aa'); + +delimiter |; +CREATE FUNCTION f1() RETURNS CHAR + RETURN (SELECT f2())| +CREATE FUNCTION f2() RETURNS CHAR + RETURN (SELECT f3())| +CREATE FUNCTION f3() RETURNS CHAR + RETURN (SELECT f4())| +CREATE FUNCTION f4() RETURNS CHAR +BEGIN + RETURN (SELECT a FROM t1); +END| +delimiter ;| + +SELECT f1(); + +DROP FUNCTION f1; +DROP FUNCTION f2; +DROP FUNCTION f3; +DROP FUNCTION f4; +DROP TABLE t1; + --echo # --echo # Bug#34197: CREATE PROCEDURE fails when COMMENT truncated in non --echo # strict SQL mode diff --git a/mysql-test/t/trigger.test b/mysql-test/t/trigger.test index 3ab724a835a..f3b9d6bb91e 100644 --- a/mysql-test/t/trigger.test +++ b/mysql-test/t/trigger.test @@ -2425,3 +2425,67 @@ DELETE FROM t1; DROP TABLE t1; DROP TEMPORARY TABLE t2; + +# +# Bug#36649: Condition area is not properly cleaned up after stored routine invocation +# + +--disable_warnings +DROP TRIGGER IF EXISTS trg1; +DROP TABLE IF EXISTS t1; +--enable_warnings + +CREATE TABLE t1 (a INT); + +delimiter |; +CREATE TRIGGER trg1 BEFORE INSERT ON t1 FOR EACH ROW +BEGIN + DECLARE a CHAR; + SELECT 'ab' INTO a; + SELECT 'ab' INTO a; + SELECT 'a' INTO a; +END| +delimiter ;| + +INSERT INTO t1 VALUES (1); + +DROP TRIGGER trg1; +DROP TABLE t1; + +# +# Successive trigger actuations +# + +--disable_warnings +DROP TRIGGER IF EXISTS trg1; +DROP TRIGGER IF EXISTS trg2; +DROP TABLE IF EXISTS t1; +--enable_warnings + +CREATE TABLE t1 (a INT); + +delimiter |; + +CREATE TRIGGER trg1 BEFORE INSERT ON t1 FOR EACH ROW +BEGIN + DECLARE trg1 CHAR; + SELECT 'ab' INTO trg1; +END| + +CREATE TRIGGER trg2 AFTER INSERT ON t1 FOR EACH ROW +BEGIN + DECLARE trg2 CHAR; + SELECT 'ab' INTO trg2; +END| + +delimiter ;| + +INSERT INTO t1 VALUES (0); +SELECT * FROM t1; +SHOW WARNINGS; +INSERT INTO t1 VALUES (1),(2); + +DROP TRIGGER trg1; +DROP TRIGGER trg2; +DROP TABLE t1; + diff --git a/sql/sp_head.cc b/sql/sp_head.cc index eb9ee6eeebf..6e5c55665c8 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -1083,7 +1083,6 @@ sp_head::execute(THD *thd) Item_change_list old_change_list; String old_packet; Reprepare_observer *save_reprepare_observer= thd->m_reprepare_observer; - Object_creation_ctx *saved_creation_ctx; Warning_info *saved_warning_info, warning_info(thd->warning_info->warn_id()); diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index b5a088af539..8c2f70a36a0 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -2065,7 +2065,6 @@ mysql_execute_command(THD *thd) A better approach would be to reset this for any commands that is not a SHOW command or a select that only access local variables, but for now this is probably good enough. - Don't reset warnings when executing a stored routine. */ if ((sql_command_flags[lex->sql_command] & CF_DIAGNOSTIC_STMT) != 0) thd->warning_info->set_read_only(TRUE); From c34c66650deabf97764e8aba55c168e41ab69647 Mon Sep 17 00:00:00 2001 From: Davi Arnaut Date: Tue, 10 Nov 2009 16:14:53 -0200 Subject: [PATCH 15/40] Backport of Bug#32140 to mysql-next-mr ------------------------------------------------------------ revno: 2618 revision-id: sp1r-davi@mysql.com/endora.local-20080418131946-26951 parent: sp1r-davi@mysql.com/endora.local-20080417190810-26185 committer: davi@mysql.com/endora.local timestamp: Fri 2008-04-18 10:19:46 -0300 message: Bug#32140: wrong error code caught when an SF() call is interruped with KILL query The problem is that killing a query which calls a stored function could return a wrong error (table corrupt) instead of the query interrupted error message. The solution is to not set the table corrupt error if the query is killed, the query interrupted error message will be set later when the query is finished. sql/sp.cc: Don't set a error if the thread was killed, the query interrupted error will be set later. --- sql/sp.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sql/sp.cc b/sql/sp.cc index 9d5efe227dc..8beaa96be54 100644 --- a/sql/sp.cc +++ b/sql/sp.cc @@ -1711,6 +1711,9 @@ sp_cache_routines_and_add_tables_aux(THD *thd, LEX *lex, ret= SP_OK; break; default: + /* Query might have been killed, don't set error. */ + if (thd->killed) + break; /* Any error when loading an existing routine is either some problem with the mysql.proc table, or a parse error because the contents From 4297dcaac875b835a1680200b1d2220154aca49d Mon Sep 17 00:00:00 2001 From: Davi Arnaut Date: Tue, 10 Nov 2009 16:26:39 -0200 Subject: [PATCH 16/40] Backport of Bug#36785 to mysql-next-mr ------------------------------------------------------------ revno: 2630.2.13 revision-id: davi@mysql.com-20080612190452-cx6h7rm557bcq7sa parent: davi@mysql.com-20080611124915-csejwrxfdga9upho committer: Davi Arnaut branch nick: 36785-6.0 timestamp: Thu 2008-06-12 16:04:52 -0300 message: Bug#36785: Wrong error message when group_concat() exceeds max length The problem is that when ER_CUT_VALUE_GROUP_CONCAT is elevated to a error, the message does not get updated with the number of cut lines when group_concat() exceeds max length. The solution is to modify the warning message to be more meaningful by giving the number of the line that was cut and to issue the warning for each line that is cut. This approach is inline with how other per-row truncated data warnings are issued avoids violating the warning internal interface. mysql-test/suite/sys_vars/r/group_concat_max_len_func.result: Update result to reflect the new warning message sql/share/errmsg.txt: Update warning message ER_CUT_VALUE_GROUP_CONCAT. --- .../suite/sys_vars/r/group_concat_max_len_func.result | 11 ++++++++--- sql/share/errmsg.txt | 1 + 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/mysql-test/suite/sys_vars/r/group_concat_max_len_func.result b/mysql-test/suite/sys_vars/r/group_concat_max_len_func.result index 52d67b8d274..f04dfee583d 100644 --- a/mysql-test/suite/sys_vars/r/group_concat_max_len_func.result +++ b/mysql-test/suite/sys_vars/r/group_concat_max_len_func.result @@ -41,7 +41,10 @@ id rollno GROUP_CONCAT(name) 4 3 Reco 7 4 Reco Warnings: -Warning 1260 4 line(s) were cut by GROUP_CONCAT() +Warning 1260 Row 1 was cut by GROUP_CONCAT() +Warning 1260 Row 2 was cut by GROUP_CONCAT() +Warning 1260 Row 3 was cut by GROUP_CONCAT() +Warning 1260 Row 4 was cut by GROUP_CONCAT() ## Changing session value of variable and verifying its behavior, ## ## warning should come here ## SET @@session.group_concat_max_len = 10; @@ -52,7 +55,9 @@ id rollno GROUP_CONCAT(name) 4 3 Record_4,R 7 4 Record_7,R Warnings: -Warning 1260 3 line(s) were cut by GROUP_CONCAT() +Warning 1260 Row 2 was cut by GROUP_CONCAT() +Warning 1260 Row 5 was cut by GROUP_CONCAT() +Warning 1260 Row 7 was cut by GROUP_CONCAT() '#--------------------FN_DYNVARS_034_03-------------------------#' ## Connecting with new connection test_con2 ## ## Verifying initial value of variable. It should be 4 ## @@ -71,7 +76,7 @@ id rollno GROUP_CONCAT(name) 4 3 Record_4,Record_6 7 4 Record_7,Record_8 Warnings: -Warning 1260 1 line(s) were cut by GROUP_CONCAT() +Warning 1260 Row 3 was cut by GROUP_CONCAT() '#--------------------FN_DYNVARS_034_04-------------------------#' ## Setting session value of variable to 26. No warning should appear here ## ## because the value after concatination is less than 30 ## diff --git a/sql/share/errmsg.txt b/sql/share/errmsg.txt index 317087854c1..cdda89413f1 100644 --- a/sql/share/errmsg.txt +++ b/sql/share/errmsg.txt @@ -4880,6 +4880,7 @@ ER_ZLIB_Z_DATA_ERROR spa "ZLIB: Dato de entrada fué corrompido para zlib" ER_CUT_VALUE_GROUP_CONCAT eng "Row %u was cut by GROUP_CONCAT()" + por "Linha %u foi cortada por GROUP_CONCAT()" ER_WARN_TOO_FEW_RECORDS 01000 eng "Row %ld doesn't contain data for all columns" ger "Zeile %ld enthält nicht für alle Felder Daten" From c809048c64a5fc8b215dc32b76ebefd5a833707a Mon Sep 17 00:00:00 2001 From: Davi Arnaut Date: Tue, 10 Nov 2009 16:32:23 -0200 Subject: [PATCH 17/40] Backport of Bug#37843 to mysql-next-mr sql/sql_cache.cc: Swap return value description. --- sql/sql_cache.cc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sql/sql_cache.cc b/sql/sql_cache.cc index 6f550f92987..e91da2ddacb 100644 --- a/sql/sql_cache.cc +++ b/sql/sql_cache.cc @@ -1330,12 +1330,12 @@ end: @param thd Pointer to the thread handler @param sql A pointer to the sql statement * @param query_length Length of the statement in characters - + @return status code - @retval 1 Query was not cached. - @retval 0 The query was cached and user was sent the result. - @retval -1 The query was cached but we didn't have rights to use it. - + @retval 0 Query was not cached. + @retval 1 The query was cached and user was sent the result. + @retval -1 The query was cached but we didn't have rights to use it. + In case of -1, no error is sent to the client. *) The buffer must be allocated memory of size: From e26de1ca16d60870184a6a30504c02bce5c4070d Mon Sep 17 00:00:00 2001 From: Davi Arnaut Date: Tue, 10 Nov 2009 16:48:46 -0200 Subject: [PATCH 18/40] Backport of Bug#27525 to mysql-next-mr ------------------------------------------------------------ revno: 2572.2.1 revision-id: sp1r-davi@mysql.com/endora.local-20080227225948-16317 parent: sp1r-anozdrin/alik@quad.-20080226165712-10409 committer: davi@mysql.com/endora.local timestamp: Wed 2008-02-27 19:59:48 -0300 message: Bug#27525 table not found when using multi-table-deletes with aliases over several databas Bug#30234 Unexpected behavior using DELETE with AS and USING The multi-delete statement has a documented limitation that cross-database multiple-table deletes using aliases are not supported because it fails to find the tables by alias if it belongs to a different database. The problem is that when building the list of tables to delete from, if a database name is not specified (maybe an alias) it defaults to the name of the current selected database, making impossible to to properly resolve tables by alias later. Another problem is a inconsistency of the multiple table delete syntax that permits ambiguities in a delete statement (aliases that refer to multiple different tables or vice-versa). The first step for a solution and proper implementation of the cross-databse multiple table delete is to get rid of any ambiguities in a multiple table statement. Currently, the parser is accepting multiple table delete statements that have no obvious meaning, such as: DELETE a1 FROM db1.t1 AS a1, db2.t2 AS a1; DELETE a1 AS a1 FROM db1.t1 AS a1, db2.t2 AS a1; The solution is to resolve the left part of a delete statement using the right part, if the a table on right has an alias, it must be referenced in the left using the given alias. Also, each table on the left side must match unambiguously only one table in the right side. mysql-test/r/delete.result: Add test case result for Bug#27525 and Bug#21148 mysql-test/r/derived.result: Update error. mysql-test/suite/rpl/r/rpl_multi_delete2.result: Update syntax. mysql-test/suite/rpl/t/rpl_multi_delete2.test: Update syntax. mysql-test/t/delete.test: Add test case for Bug#27525 and Bug#21148 mysql-test/t/derived.test: Update statement error, alias is properly resolved now. sql/sql_parse.cc: Implement new algorithm for the resolution of alias in a multiple table delete statement. sql/sql_yacc.yy: Rework multi-delete parser rules to not accept table alias for the table source list. sql/table.h: Add flag to signal that the table has a alias set or that fully qualified table name was given. --- mysql-test/r/delete.result | 143 +++++++++++++++- mysql-test/r/derived.result | 2 +- .../suite/rpl/r/rpl_multi_delete2.result | 2 +- mysql-test/suite/rpl/t/rpl_multi_delete2.test | 2 +- mysql-test/t/delete.test | 155 +++++++++++++++++- mysql-test/t/derived.test | 2 +- sql/sql_parse.cc | 74 +++++++-- sql/sql_yacc.yy | 20 +-- sql/table.h | 6 + 9 files changed, 378 insertions(+), 28 deletions(-) diff --git a/mysql-test/r/delete.result b/mysql-test/r/delete.result index 0124a7da35a..03a3165aad0 100644 --- a/mysql-test/r/delete.result +++ b/mysql-test/r/delete.result @@ -259,8 +259,8 @@ ERROR 42S02: Unknown table 't2' in MULTI DELETE DELETE FROM db1.t1 alias USING db1.t1, db2.t1 alias WHERE db1.t1.a = alias.a; 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 'alias USING db1.t1, db2.t1 alias WHERE db1.t1.a = alias.a' at line 1 DELETE FROM alias USING db1.t1, db2.t1 alias WHERE db1.t1.a = alias.a; -ERROR 42S02: Unknown table 'alias' in MULTI DELETE DELETE FROM db2.alias USING db1.t1, db2.t1 alias WHERE db1.t1.a = alias.a; +ERROR 42S02: Unknown table 'alias' in MULTI DELETE DELETE FROM t1 USING t1 WHERE a = 1; SELECT * FROM t1; a @@ -279,6 +279,147 @@ ERROR 42000: Incorrect number of arguments for FUNCTION test.f1; expected 0, got DROP TABLE t1; DROP FUNCTION f1; End of 5.0 tests +DROP DATABASE IF EXISTS db1; +DROP DATABASE IF EXISTS db2; +DROP DATABASE IF EXISTS db3; +DROP DATABASE IF EXISTS db4; +DROP TABLE IF EXISTS t1, t2; +DROP PROCEDURE IF EXISTS count; +USE test; +CREATE DATABASE db1; +CREATE DATABASE db2; +CREATE TABLE db1.t1 (a INT, b INT); +INSERT INTO db1.t1 VALUES (1,1),(2,2),(3,3); +CREATE TABLE db1.t2 AS SELECT * FROM db1.t1; +CREATE TABLE db2.t1 AS SELECT * FROM db1.t2; +CREATE TABLE db2.t2 AS SELECT * FROM db2.t1; +CREATE TABLE t1 AS SELECT * FROM db2.t2; +CREATE TABLE t2 AS SELECT * FROM t1; +CREATE PROCEDURE count_rows() +BEGIN +SELECT COUNT(*) AS "COUNT(db1.t1)" FROM db1.t1; +SELECT COUNT(*) AS "COUNT(db1.t2)" FROM db1.t2; +SELECT COUNT(*) AS "COUNT(db2.t1)" FROM db2.t1; +SELECT COUNT(*) AS "COUNT(db2.t2)" FROM db2.t2; +SELECT COUNT(*) AS "COUNT(test.t1)" FROM test.t1; +SELECT COUNT(*) AS "COUNT(test.t2)" FROM test.t2; +END| +CREATE DATABASE db3; +USE db3; +DROP DATABASE db3; +SELECT * FROM t1; +ERROR 3D000: No database selected +DELETE a1,a2 FROM db1.t1, db2.t2; +ERROR 3D000: No database selected +DELETE a1,a2 FROM db1.t1, db2.t2; +ERROR 3D000: No database selected +DELETE a1,a2 FROM db1.t1 AS a1, db2.t2; +ERROR 3D000: No database selected +DELETE a1,a2 FROM db1.t1, db2.t2 AS a2; +ERROR 3D000: No database selected +DELETE a1,a2 FROM db3.t1 AS a1, db4.t2 AS a2; +ERROR 3D000: No database selected +DELETE a1,a2 FROM db3.t1 AS a1, db4.t2 AS a2; +ERROR 3D000: No database selected +DELETE FROM a1,a2 USING db1.t1, db2.t2; +ERROR 3D000: No database selected +DELETE FROM a1,a2 USING db1.t1, db2.t2; +ERROR 3D000: No database selected +DELETE FROM a1,a2 USING db1.t1 AS a1, db2.t2; +ERROR 3D000: No database selected +DELETE FROM a1,a2 USING db1.t1, db2.t2 AS a2; +ERROR 3D000: No database selected +DELETE FROM a1,a2 USING db3.t1 AS a1, db4.t2 AS a2; +ERROR 3D000: No database selected +DELETE FROM a1,a2 USING db3.t1 AS a1, db4.t2 AS a2; +ERROR 3D000: No database selected +DELETE a1 FROM db1.t1 AS a1, db2.t2 AS a1; +ERROR 3D000: No database selected +DELETE a1 FROM db1.a1, db2.t2 AS a1; +ERROR 3D000: No database selected +DELETE a1 FROM a1, db1.t1 AS a1; +ERROR 3D000: No database selected +DELETE t1 FROM db1.t1, db2.t1 AS a1; +ERROR 3D000: No database selected +DELETE t1 FROM db1.t1 AS a1, db2.t1 AS a2; +ERROR 3D000: No database selected +DELETE t1 FROM db1.t1, db2.t1; +ERROR 3D000: No database selected +USE test; +DELETE a1,a2 FROM db1.t1, db2.t2; +ERROR 42S02: Unknown table 'a1' in MULTI DELETE +DELETE a1,a2 FROM db1.t1, db2.t2; +ERROR 42S02: Unknown table 'a1' in MULTI DELETE +DELETE a1,a2 FROM db1.t1 AS a1, db2.t2; +ERROR 42S02: Unknown table 'a2' in MULTI DELETE +DELETE a1,a2 FROM db1.t1, db2.t2 AS a2; +ERROR 42S02: Unknown table 'a1' in MULTI DELETE +DELETE a1,a2 FROM db3.t1 AS a1, db4.t2 AS a2; +ERROR 42S02: Table 'db3.t1' doesn't exist +DELETE a1,a2 FROM db3.t1 AS a1, db4.t2 AS a2; +ERROR 42S02: Table 'db3.t1' doesn't exist +DELETE FROM a1,a2 USING db1.t1, db2.t2; +ERROR 42S02: Unknown table 'a1' in MULTI DELETE +DELETE FROM a1,a2 USING db1.t1, db2.t2; +ERROR 42S02: Unknown table 'a1' in MULTI DELETE +DELETE FROM a1,a2 USING db1.t1 AS a1, db2.t2; +ERROR 42S02: Unknown table 'a2' in MULTI DELETE +DELETE FROM a1,a2 USING db1.t1, db2.t2 AS a2; +ERROR 42S02: Unknown table 'a1' in MULTI DELETE +DELETE FROM a1,a2 USING db3.t1 AS a1, db4.t2 AS a2; +ERROR 42S02: Table 'db3.t1' doesn't exist +DELETE FROM a1,a2 USING db3.t1 AS a1, db4.t2 AS a2; +ERROR 42S02: Table 'db3.t1' doesn't exist +DELETE a1 FROM db1.t1 AS a1, db2.t2 AS a1; +ERROR 42000: Not unique table/alias: 'a1' +DELETE a1 FROM db1.a1, db2.t2 AS a1; +ERROR 42S02: Table 'db1.a1' doesn't exist +DELETE a1 FROM a1, db1.t1 AS a1; +ERROR 42000: Not unique table/alias: 'a1' +DELETE t1 FROM db1.t1, db2.t1 AS a1; +ERROR 42S02: Unknown table 't1' in MULTI DELETE +DELETE t1 FROM db1.t1 AS a1, db2.t1 AS a2; +ERROR 42S02: Unknown table 't1' in MULTI DELETE +DELETE t1 FROM db1.t1, db2.t1; +ERROR 42S02: Unknown table 't1' in MULTI DELETE +DELETE t1 FROM db1.t2 AS t1, db2.t2 AS t2 WHERE t2.a = 1 AND t1.a = t2.a; +SELECT ROW_COUNT(); +ROW_COUNT() +1 +CALL count_rows(); +COUNT(db1.t1) +3 +COUNT(db1.t2) +2 +COUNT(db2.t1) +3 +COUNT(db2.t2) +3 +COUNT(test.t1) +3 +COUNT(test.t2) +3 +DELETE a1, a2 FROM db2.t1 AS a1, t2 AS a2 WHERE a1.a = 2 AND a2.a = 2; +SELECT ROW_COUNT(); +ROW_COUNT() +2 +CALL count_rows(); +COUNT(db1.t1) +3 +COUNT(db1.t2) +2 +COUNT(db2.t1) +2 +COUNT(db2.t2) +3 +COUNT(test.t1) +3 +COUNT(test.t2) +2 +DROP DATABASE db1; +DROP DATABASE db2; +DROP PROCEDURE count_rows; +DROP TABLE t1, t2; # # Bug#46958: Assertion in Diagnostics_area::set_ok_status, trigger, # merge table diff --git a/mysql-test/r/derived.result b/mysql-test/r/derived.result index 80f04ffd455..53cd89c13c1 100644 --- a/mysql-test/r/derived.result +++ b/mysql-test/r/derived.result @@ -277,7 +277,7 @@ select * from t1; N M 3 0 delete P1.*,p2.* from `t1` AS P1 INNER JOIN (SELECT N FROM `t1` GROUP BY N HAVING Count(M) > 1) AS p2 ON P1.N = p2.N; -ERROR 42S02: Unknown table 'p2' in MULTI DELETE +ERROR HY000: The target table p2 of the DELETE is not updatable delete P1.* from `t1` AS P1 INNER JOIN (SELECT aaa FROM `t1` GROUP BY N HAVING Count(M) > 1) AS P2 ON P1.N = P2.N; ERROR 42S22: Unknown column 'aaa' in 'field list' drop table t1; diff --git a/mysql-test/suite/rpl/r/rpl_multi_delete2.result b/mysql-test/suite/rpl/r/rpl_multi_delete2.result index 87abe60b268..8e6a6a3d4d0 100644 --- a/mysql-test/suite/rpl/r/rpl_multi_delete2.result +++ b/mysql-test/suite/rpl/r/rpl_multi_delete2.result @@ -16,7 +16,7 @@ DELETE alias FROM a alias WHERE alias.i=1; SELECT * FROM a; i insert into a values(2),(3); -delete a alias FROM a alias where alias.i=2; +delete alias FROM a alias where alias.i=2; select * from a; i 3 diff --git a/mysql-test/suite/rpl/t/rpl_multi_delete2.test b/mysql-test/suite/rpl/t/rpl_multi_delete2.test index 81379d4056b..e91fad1872a 100644 --- a/mysql-test/suite/rpl/t/rpl_multi_delete2.test +++ b/mysql-test/suite/rpl/t/rpl_multi_delete2.test @@ -25,7 +25,7 @@ INSERT INTO a VALUES(1); DELETE alias FROM a alias WHERE alias.i=1; SELECT * FROM a; insert into a values(2),(3); -delete a alias FROM a alias where alias.i=2; +delete alias FROM a alias where alias.i=2; select * from a; save_master_pos; connection slave; diff --git a/mysql-test/t/delete.test b/mysql-test/t/delete.test index d77f5eb128b..81cadb432af 100644 --- a/mysql-test/t/delete.test +++ b/mysql-test/t/delete.test @@ -265,8 +265,8 @@ DELETE FROM t1, alias USING t1, t2 alias WHERE t1.a = alias.a; DELETE FROM t1, t2 USING t1, t2 alias WHERE t1.a = alias.a; --error ER_PARSE_ERROR DELETE FROM db1.t1 alias USING db1.t1, db2.t1 alias WHERE db1.t1.a = alias.a; ---error ER_UNKNOWN_TABLE DELETE FROM alias USING db1.t1, db2.t1 alias WHERE db1.t1.a = alias.a; +--error ER_UNKNOWN_TABLE DELETE FROM db2.alias USING db1.t1, db2.t1 alias WHERE db1.t1.a = alias.a; DELETE FROM t1 USING t1 WHERE a = 1; SELECT * FROM t1; @@ -293,6 +293,159 @@ DROP FUNCTION f1; --echo End of 5.0 tests +# +# Bug#27525: table not found when using multi-table-deletes with aliases over +# several databas +# Bug#21148: MULTI-DELETE fails to resolve a table by alias if it's from a +# different database +# + +--disable_warnings +DROP DATABASE IF EXISTS db1; +DROP DATABASE IF EXISTS db2; +DROP DATABASE IF EXISTS db3; +DROP DATABASE IF EXISTS db4; +DROP TABLE IF EXISTS t1, t2; +DROP PROCEDURE IF EXISTS count; +--enable_warnings +USE test; +CREATE DATABASE db1; +CREATE DATABASE db2; + +CREATE TABLE db1.t1 (a INT, b INT); +INSERT INTO db1.t1 VALUES (1,1),(2,2),(3,3); +CREATE TABLE db1.t2 AS SELECT * FROM db1.t1; +CREATE TABLE db2.t1 AS SELECT * FROM db1.t2; +CREATE TABLE db2.t2 AS SELECT * FROM db2.t1; +CREATE TABLE t1 AS SELECT * FROM db2.t2; +CREATE TABLE t2 AS SELECT * FROM t1; + +delimiter |; +CREATE PROCEDURE count_rows() +BEGIN + SELECT COUNT(*) AS "COUNT(db1.t1)" FROM db1.t1; + SELECT COUNT(*) AS "COUNT(db1.t2)" FROM db1.t2; + SELECT COUNT(*) AS "COUNT(db2.t1)" FROM db2.t1; + SELECT COUNT(*) AS "COUNT(db2.t2)" FROM db2.t2; + SELECT COUNT(*) AS "COUNT(test.t1)" FROM test.t1; + SELECT COUNT(*) AS "COUNT(test.t2)" FROM test.t2; +END| +delimiter ;| + +# +# Testing without a selected database +# + +CREATE DATABASE db3; +USE db3; +DROP DATABASE db3; +--error ER_NO_DB_ERROR +SELECT * FROM t1; + +# Detect missing table references + +--error ER_NO_DB_ERROR +DELETE a1,a2 FROM db1.t1, db2.t2; +--error ER_NO_DB_ERROR +DELETE a1,a2 FROM db1.t1, db2.t2; +--error ER_NO_DB_ERROR +DELETE a1,a2 FROM db1.t1 AS a1, db2.t2; +--error ER_NO_DB_ERROR +DELETE a1,a2 FROM db1.t1, db2.t2 AS a2; +--error ER_NO_DB_ERROR +DELETE a1,a2 FROM db3.t1 AS a1, db4.t2 AS a2; +--error ER_NO_DB_ERROR +DELETE a1,a2 FROM db3.t1 AS a1, db4.t2 AS a2; + +--error ER_NO_DB_ERROR +DELETE FROM a1,a2 USING db1.t1, db2.t2; +--error ER_NO_DB_ERROR +DELETE FROM a1,a2 USING db1.t1, db2.t2; +--error ER_NO_DB_ERROR +DELETE FROM a1,a2 USING db1.t1 AS a1, db2.t2; +--error ER_NO_DB_ERROR +DELETE FROM a1,a2 USING db1.t1, db2.t2 AS a2; +--error ER_NO_DB_ERROR +DELETE FROM a1,a2 USING db3.t1 AS a1, db4.t2 AS a2; +--error ER_NO_DB_ERROR +DELETE FROM a1,a2 USING db3.t1 AS a1, db4.t2 AS a2; + +# Ambiguous table references + +--error ER_NO_DB_ERROR +DELETE a1 FROM db1.t1 AS a1, db2.t2 AS a1; +--error ER_NO_DB_ERROR +DELETE a1 FROM db1.a1, db2.t2 AS a1; +--error ER_NO_DB_ERROR +DELETE a1 FROM a1, db1.t1 AS a1; +--error ER_NO_DB_ERROR +DELETE t1 FROM db1.t1, db2.t1 AS a1; +--error ER_NO_DB_ERROR +DELETE t1 FROM db1.t1 AS a1, db2.t1 AS a2; +--error ER_NO_DB_ERROR +DELETE t1 FROM db1.t1, db2.t1; + +# Test all again, now with a selected database + +USE test; + +# Detect missing table references + +--error ER_UNKNOWN_TABLE +DELETE a1,a2 FROM db1.t1, db2.t2; +--error ER_UNKNOWN_TABLE +DELETE a1,a2 FROM db1.t1, db2.t2; +--error ER_UNKNOWN_TABLE +DELETE a1,a2 FROM db1.t1 AS a1, db2.t2; +--error ER_UNKNOWN_TABLE +DELETE a1,a2 FROM db1.t1, db2.t2 AS a2; +--error ER_NO_SUCH_TABLE +DELETE a1,a2 FROM db3.t1 AS a1, db4.t2 AS a2; +--error ER_NO_SUCH_TABLE +DELETE a1,a2 FROM db3.t1 AS a1, db4.t2 AS a2; + +--error ER_UNKNOWN_TABLE +DELETE FROM a1,a2 USING db1.t1, db2.t2; +--error ER_UNKNOWN_TABLE +DELETE FROM a1,a2 USING db1.t1, db2.t2; +--error ER_UNKNOWN_TABLE +DELETE FROM a1,a2 USING db1.t1 AS a1, db2.t2; +--error ER_UNKNOWN_TABLE +DELETE FROM a1,a2 USING db1.t1, db2.t2 AS a2; +--error ER_NO_SUCH_TABLE +DELETE FROM a1,a2 USING db3.t1 AS a1, db4.t2 AS a2; +--error ER_NO_SUCH_TABLE +DELETE FROM a1,a2 USING db3.t1 AS a1, db4.t2 AS a2; + +# Ambiguous table references + +--error ER_NONUNIQ_TABLE +DELETE a1 FROM db1.t1 AS a1, db2.t2 AS a1; +--error ER_NO_SUCH_TABLE +DELETE a1 FROM db1.a1, db2.t2 AS a1; +--error ER_NONUNIQ_TABLE +DELETE a1 FROM a1, db1.t1 AS a1; +--error ER_UNKNOWN_TABLE +DELETE t1 FROM db1.t1, db2.t1 AS a1; +--error ER_UNKNOWN_TABLE +DELETE t1 FROM db1.t1 AS a1, db2.t1 AS a2; +--error ER_UNKNOWN_TABLE +DELETE t1 FROM db1.t1, db2.t1; + +# Test multiple-table cross database deletes + +DELETE t1 FROM db1.t2 AS t1, db2.t2 AS t2 WHERE t2.a = 1 AND t1.a = t2.a; +SELECT ROW_COUNT(); +CALL count_rows(); +DELETE a1, a2 FROM db2.t1 AS a1, t2 AS a2 WHERE a1.a = 2 AND a2.a = 2; +SELECT ROW_COUNT(); +CALL count_rows(); + +DROP DATABASE db1; +DROP DATABASE db2; +DROP PROCEDURE count_rows; +DROP TABLE t1, t2; + --echo # --echo # Bug#46958: Assertion in Diagnostics_area::set_ok_status, trigger, --echo # merge table diff --git a/mysql-test/t/derived.test b/mysql-test/t/derived.test index d28c19bbd18..27e85ee237b 100644 --- a/mysql-test/t/derived.test +++ b/mysql-test/t/derived.test @@ -158,7 +158,7 @@ UPDATE `t1` AS P1 INNER JOIN (SELECT aaaa FROM `t1` GROUP BY N HAVING Count(M) > delete P1.* from `t1` AS P1 INNER JOIN (SELECT N FROM `t1` GROUP BY N HAVING Count(M) > 1) AS P2 ON P1.N = P2.N; select * from t1; --replace_result P2 p2 ---error ER_UNKNOWN_TABLE +--error ER_NON_UPDATABLE_TABLE delete P1.*,P2.* from `t1` AS P1 INNER JOIN (SELECT N FROM `t1` GROUP BY N HAVING Count(M) > 1) AS P2 ON P1.N = P2.N; -- error 1054 delete P1.* from `t1` AS P1 INNER JOIN (SELECT aaa FROM `t1` GROUP BY N HAVING Count(M) > 1) AS P2 ON P1.N = P2.N; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 8c2f70a36a0..926717ef733 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -6486,13 +6486,17 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, DBUG_RETURN(0); /* purecov: inspected */ if (table->db.str) { + ptr->is_fqtn= TRUE; ptr->db= table->db.str; ptr->db_length= table->db.length; } else if (lex->copy_db_to(&ptr->db, &ptr->db_length)) DBUG_RETURN(0); + else + ptr->is_fqtn= FALSE; ptr->alias= alias_str; + ptr->is_alias= alias ? TRUE : FALSE; if (lower_case_table_names && table->table.length) table->table.length= my_casedn_str(files_charset_info, table->table.str); ptr->table_name=table->table.str; @@ -7539,6 +7543,63 @@ bool multi_delete_precheck(THD *thd, TABLE_LIST *tables) } +/* + Given a table in the source list, find a correspondent table in the + table references list. + + @param lex Pointer to LEX representing multi-delete. + @param src Source table to match. + @param ref Table references list. + + @remark The source table list (tables listed before the FROM clause + or tables listed in the FROM clause before the USING clause) may + contain table names or aliases that must match unambiguously one, + and only one, table in the target table list (table references list, + after FROM/USING clause). + + @return Matching table, NULL otherwise. +*/ + +static TABLE_LIST *multi_delete_table_match(LEX *lex, TABLE_LIST *tbl, + TABLE_LIST *tables) +{ + TABLE_LIST *match= NULL; + DBUG_ENTER("multi_delete_table_match"); + + for (TABLE_LIST *elem= tables; elem; elem= elem->next_local) + { + int cmp; + + if (tbl->is_fqtn && elem->is_alias) + continue; /* no match */ + if (tbl->is_fqtn && elem->is_fqtn) + cmp= my_strcasecmp(table_alias_charset, tbl->table_name, elem->table_name) || + strcmp(tbl->db, elem->db); + else if (elem->is_alias) + cmp= my_strcasecmp(table_alias_charset, tbl->alias, elem->alias); + else + cmp= my_strcasecmp(table_alias_charset, tbl->table_name, elem->table_name) || + strcmp(tbl->db, elem->db); + + if (cmp) + continue; + + if (match) + { + my_error(ER_NONUNIQ_TABLE, MYF(0), elem->alias); + DBUG_RETURN(NULL); + } + + match= elem; + } + + if (!match) + my_error(ER_UNKNOWN_TABLE, MYF(0), tbl->table_name, "MULTI DELETE"); + + DBUG_RETURN(match); +} + + /** Link tables in auxilary table list of multi-delete with corresponding elements in main table list, and set proper locks for them. @@ -7564,20 +7625,9 @@ bool multi_delete_set_locks_and_link_aux_tables(LEX *lex) { lex->table_count++; /* All tables in aux_tables must be found in FROM PART */ - TABLE_LIST *walk; - for (walk= tables; walk; walk= walk->next_local) - { - if (!my_strcasecmp(table_alias_charset, - target_tbl->alias, walk->alias) && - !strcmp(walk->db, target_tbl->db)) - break; - } + TABLE_LIST *walk= multi_delete_table_match(lex, target_tbl, tables); if (!walk) - { - my_error(ER_UNKNOWN_TABLE, MYF(0), - target_tbl->table_name, "MULTI DELETE"); DBUG_RETURN(TRUE); - } if (!walk->derived) { target_tbl->table_name= walk->table_name; diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 54e3a131865..d4b422da30d 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1352,7 +1352,7 @@ END_OF_INPUT %type '-' '+' '*' '/' '%' '(' ')' ',' '!' '{' '}' '&' '|' AND_SYM OR_SYM OR_OR_SYM BETWEEN_SYM CASE_SYM - THEN_SYM WHEN_SYM DIV_SYM MOD_SYM OR2_SYM AND_AND_SYM + THEN_SYM WHEN_SYM DIV_SYM MOD_SYM OR2_SYM AND_AND_SYM DELETE_SYM %% /* @@ -9994,7 +9994,7 @@ delete: lex->ignore= 0; lex->select_lex.init_order(); } - opt_delete_options single_multi {} + opt_delete_options single_multi ; single_multi: @@ -10009,45 +10009,45 @@ single_multi: | table_wild_list { mysql_init_multi_delete(Lex); } FROM join_table_list where_clause - { + { if (multi_delete_set_locks_and_link_aux_tables(Lex)) MYSQL_YYABORT; } | FROM table_alias_ref_list { mysql_init_multi_delete(Lex); } USING join_table_list where_clause - { + { if (multi_delete_set_locks_and_link_aux_tables(Lex)) MYSQL_YYABORT; } ; table_wild_list: - table_wild_one {} - | table_wild_list ',' table_wild_one {} + table_wild_one + | table_wild_list ',' table_wild_one ; table_wild_one: - ident opt_wild opt_table_alias + ident opt_wild { Table_ident *ti= new Table_ident($1); if (ti == NULL) MYSQL_YYABORT; if (!Select->add_table_to_list(YYTHD, ti, - $3, + NULL, TL_OPTION_UPDATING | TL_OPTION_ALIAS, Lex->lock_option)) MYSQL_YYABORT; } - | ident '.' ident opt_wild opt_table_alias + | ident '.' ident opt_wild { Table_ident *ti= new Table_ident(YYTHD, $1, $3, 0); if (ti == NULL) MYSQL_YYABORT; if (!Select->add_table_to_list(YYTHD, ti, - $5, + NULL, TL_OPTION_UPDATING | TL_OPTION_ALIAS, Lex->lock_option)) MYSQL_YYABORT; diff --git a/sql/table.h b/sql/table.h index 49a97958807..76bebd3fdaa 100644 --- a/sql/table.h +++ b/sql/table.h @@ -1332,6 +1332,12 @@ struct TABLE_LIST */ bool create; bool internal_tmp_table; + /** TRUE if an alias for this table was specified in the SQL. */ + bool is_alias; + /** TRUE if the table is referred to in the statement using a fully + qualified name (.). + */ + bool is_fqtn; /* View creation context. */ From 80582b000f5cd5f6dfb24bed45df907b2c96ab02 Mon Sep 17 00:00:00 2001 From: Davi Arnaut Date: Tue, 10 Nov 2009 17:09:27 -0200 Subject: [PATCH 19/40] Backport of Bug#10374 to mysql-next-mr ------------------------------------------------------------ revno: 2597.37.3 revision-id: sp1r-davi@mysql.com/endora.local-20080328123626-16430 parent: sp1r-anozdrin/alik@quad.opbmk-20080327125300-11290 committer: davi@mysql.com/endora.local timestamp: Fri 2008-03-28 09:36:26 -0300 message: Bug#10374 GET_LOCK does not let connection to close on the server side if it's aborted The problem is that the server doesn't detect aborted connections which are waiting on a lock or sleeping (user sleep), wasting system resources for a connection that is already dead. The solution is to peek at the connection every five seconds to verify if the connection is not aborted. A aborted connection is detect by polling the connection socket for available data to be read or end of file and in case of eof, the wait is aborted and the connection killed. include/violite.h: Export vio_peek_read function. mysql-test/r/dirty_close.result: Add test case result for Bug#10374 mysql-test/t/dirty_close.test: Add test case for Bug#10374 sql/item_func.cc: While waiting for a condition to be signaled, check if the connection is not broken every INTERRUPT_INTERVAL seconds. sql/sql_class.cc: Add function which checks if the client connection was aborted. sql/sql_class.h: Add function prototype. vio/viosocket.c: Add poll and peek functions for Windows and Unix. --- include/violite.h | 1 + mysql-test/r/dirty_close.result | 10 ++++++ mysql-test/t/dirty_close.test | 32 ++++++++++++++++++ sql/item_func.cc | 58 +++++++++++++++++++++++++++------ sql/sql_class.cc | 24 ++++++++++++++ sql/sql_class.h | 5 ++- vio/viosocket.c | 49 +++++++++++++++++++++++++--- 7 files changed, 164 insertions(+), 15 deletions(-) diff --git a/include/violite.h b/include/violite.h index f833606233c..0e07e1c69ca 100644 --- a/include/violite.h +++ b/include/violite.h @@ -88,6 +88,7 @@ my_bool vio_peer_addr(Vio* vio, char *buf, uint16 *port); /* Remotes in_addr */ void vio_in_addr(Vio *vio, struct in_addr *in); my_bool vio_poll_read(Vio *vio,uint timeout); +my_bool vio_peek_read(Vio *vio, uint *bytes); #ifdef HAVE_OPENSSL #include diff --git a/mysql-test/r/dirty_close.result b/mysql-test/r/dirty_close.result index b49b72f1b95..f7012ff9c01 100644 --- a/mysql-test/r/dirty_close.result +++ b/mysql-test/r/dirty_close.result @@ -7,3 +7,13 @@ n 2 3 DROP TABLE t1; +SELECT GET_LOCK("dangling", 0); +GET_LOCK("dangling", 0) +1 +SELECT GET_LOCK('dangling', 3600);; +SELECT GET_LOCK('dangling', 3600);; +SELECT RELEASE_LOCK('dangling'); +RELEASE_LOCK('dangling') +1 +GET_LOCK('dangling', 3600) +1 diff --git a/mysql-test/t/dirty_close.test b/mysql-test/t/dirty_close.test index 1bbd53e8c06..17a729000e3 100644 --- a/mysql-test/t/dirty_close.test +++ b/mysql-test/t/dirty_close.test @@ -22,6 +22,38 @@ disconnect con2; # End of 4.1 tests +# +# Bug#10374 GET_LOCK does not let connection to close on the server side if it's aborted +# + +connection default; +SELECT GET_LOCK("dangling", 0); +connect(con1, localhost, root,,); +connection con1; +--send SELECT GET_LOCK('dangling', 3600); +connection default; +let $wait_condition= + SELECT COUNT(*) = 1 FROM INFORMATION_SCHEMA.PROCESSLIST WHERE STATE = "User lock" + AND INFO = "SELECT GET_LOCK('dangling', 3600)"; +--source include/wait_condition.inc +dirty_close con1; +let $wait_condition= + SELECT COUNT(*) = 0 FROM INFORMATION_SCHEMA.PROCESSLIST WHERE STATE = "User lock" + AND INFO = "SELECT GET_LOCK('dangling', 3600)"; +--source include/wait_condition.inc +connect(con1, localhost, root,,); +--send SELECT GET_LOCK('dangling', 3600); +connection default; +let $wait_condition= + SELECT COUNT(*) = 1 FROM INFORMATION_SCHEMA.PROCESSLIST WHERE STATE = "User lock" + AND INFO = "SELECT GET_LOCK('dangling', 3600)"; +--source include/wait_condition.inc +SELECT RELEASE_LOCK('dangling'); +connection con1; +--reap +connection default; +disconnect con1; + # Wait till all disconnects are completed --source include/wait_until_count_sessions.inc diff --git a/sql/item_func.cc b/sql/item_func.cc index f4db3ef03e7..8f2739f8e77 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -3437,6 +3437,48 @@ void debug_sync_point(const char* lock_name, uint lock_timeout) #endif + +/** + Wait for a given condition to be signaled within the specified timeout. + + @param cond the condition variable to wait on + @param lock the associated mutex + @param abstime the amount of time in seconds to wait + + @retval return value from pthread_cond_timedwait +*/ + +#define INTERRUPT_INTERVAL (5 * ULL(1000000000)) + +static int interruptible_wait(THD *thd, pthread_cond_t *cond, + pthread_mutex_t *lock, double time) +{ + int error; + struct timespec abstime; + ulonglong slice, timeout= (ulonglong) (time * 1000000000.0); + + do + { + /* Wait for a fixed interval. */ + if (timeout > INTERRUPT_INTERVAL) + slice= INTERRUPT_INTERVAL; + else + slice= timeout; + + timeout-= slice; + set_timespec_nsec(abstime, slice); + error= pthread_cond_timedwait(cond, lock, &abstime); + if (error == ETIMEDOUT || error == ETIME) + { + /* Return error if timed out or connection is broken. */ + if (!timeout || !thd->vio_is_connected()) + break; + } + } while (error && timeout); + + return error; +} + /** Get a user level lock. If the thread has an old lock this is first released. @@ -3452,8 +3494,7 @@ longlong Item_func_get_lock::val_int() { DBUG_ASSERT(fixed == 1); String *res=args[0]->val_str(&value); - longlong timeout=args[1]->val_int(); - struct timespec abstime; + double timeout= args[1]->val_real(); THD *thd=current_thd; User_level_lock *ull; int error; @@ -3517,12 +3558,11 @@ longlong Item_func_get_lock::val_int() thd->mysys_var->current_mutex= &LOCK_user_locks; thd->mysys_var->current_cond= &ull->cond; - set_timespec(abstime,timeout); error= 0; while (ull->locked && !thd->killed) { DBUG_PRINT("info", ("waiting on lock")); - error= pthread_cond_timedwait(&ull->cond,&LOCK_user_locks,&abstime); + error= interruptible_wait(thd, &ull->cond, &LOCK_user_locks, timeout); if (error == ETIMEDOUT || error == ETIME) { DBUG_PRINT("info", ("lock wait timeout")); @@ -3717,13 +3757,13 @@ void Item_func_benchmark::print(String *str, enum_query_type query_type) longlong Item_func_sleep::val_int() { THD *thd= current_thd; - struct timespec abstime; pthread_cond_t cond; + double timeout; int error; DBUG_ASSERT(fixed == 1); - double time= args[0]->val_real(); + timeout= args[0]->val_real(); /* On 64-bit OSX pthread_cond_timedwait() waits forever if passed abstime time has already been exceeded by @@ -3733,10 +3773,8 @@ longlong Item_func_sleep::val_int() We assume that the lines between this test and the call to pthread_cond_timedwait() will be executed in less than 0.00001 sec. */ - if (time < 0.00001) + if (timeout < 0.00001) return 0; - - set_timespec_nsec(abstime, (ulonglong)(time * ULL(1000000000))); pthread_cond_init(&cond, NULL); pthread_mutex_lock(&LOCK_user_locks); @@ -3748,7 +3786,7 @@ longlong Item_func_sleep::val_int() error= 0; while (!thd->killed) { - error= pthread_cond_timedwait(&cond, &LOCK_user_locks, &abstime); + error= interruptible_wait(thd, &cond, &LOCK_user_locks, timeout); if (error == ETIMEDOUT || error == ETIME) break; error= 0; diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 212d727b7f1..e765d892884 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -1618,6 +1618,30 @@ void THD::rollback_item_tree_changes() } +#ifndef EMBEDDED_LIBRARY + +/** + Check that the endpoint is still available. +*/ + +bool THD::vio_is_connected() +{ + uint bytes= 0; + + /* End of input is signaled by poll if the socket is aborted. */ + if (vio_poll_read(net.vio, 0)) + return TRUE; + + /* Socket is aborted if signaled but no data is available. */ + if (vio_peek_read(net.vio, &bytes)) + return TRUE; + + return bytes ? TRUE : FALSE; +} + +#endif + + /***************************************************************************** ** Functions to provide a interface to select results *****************************************************************************/ diff --git a/sql/sql_class.h b/sql/sql_class.h index 3fe82429492..5117304a26f 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1961,9 +1961,12 @@ public: DBUG_VOID_RETURN; } inline bool vio_ok() const { return net.vio != 0; } + /** Return FALSE if connection to client is broken. */ + bool vio_is_connected(); #else void clear_error(); - inline bool vio_ok() const { return true; } + inline bool vio_ok() const { return TRUE; } + inline bool vio_is_connected() { return TRUE; } #endif /** Mark the current error as fatal. Warning: this does not diff --git a/vio/viosocket.c b/vio/viosocket.c index 2a22f8c7c15..e83559729b9 100644 --- a/vio/viosocket.c +++ b/vio/viosocket.c @@ -360,9 +360,24 @@ void vio_in_addr(Vio *vio, struct in_addr *in) my_bool vio_poll_read(Vio *vio,uint timeout) { -#ifndef HAVE_POLL - return 0; -#else +#ifdef __WIN__ + int res, fd= vio->sd; + fd_set readfds, errorfds; + struct timeval tm; + DBUG_ENTER("vio_poll"); + tm.tv_sec= timeout; + tm.tv_usec= 0; + FD_ZERO(&readfds); + FD_ZERO(&errorfds); + FD_SET(fd, &readfds); + FD_SET(fd, &errorfds); + if ((res= select(fd, &readfds, NULL, &errorfds, &tm) <= 0)) + { + DBUG_RETURN(res < 0 ? 0 : 1); + } + res= FD_ISSET(fd, &readfds) || FD_ISSET(fd, &errorfds); + DBUG_RETURN(!res); +#elif defined(HAVE_POLL) struct pollfd fds; int res; DBUG_ENTER("vio_poll"); @@ -373,11 +388,37 @@ my_bool vio_poll_read(Vio *vio,uint timeout) { DBUG_RETURN(res < 0 ? 0 : 1); /* Don't return 1 on errors */ } - DBUG_RETURN(fds.revents & POLLIN ? 0 : 1); + DBUG_RETURN(fds.revents & (POLLIN | POLLERR | POLLHUP) ? 0 : 1); +#else + return 0; #endif } +my_bool vio_peek_read(Vio *vio, uint *bytes) +{ +#ifdef __WIN__ + int len; + if (ioctlsocket(vio->sd, FIONREAD, &len)) + return TRUE; + *bytes= len; + return FALSE; +#elif FIONREAD + int len; + if (ioctl(vio->sd, FIONREAD, &len) < 0) + return TRUE; + *bytes= len; + return FALSE; +#else + char buf[1024]; + ssize_t res= recv(vio->sd, &buf, sizeof(buf), MSG_PEEK); + if (res < 0) + return TRUE; + *bytes= res; + return FALSE; +#endif +} + void vio_timeout(Vio *vio, uint which, uint timeout) { #if defined(SO_SNDTIMEO) && defined(SO_RCVTIMEO) From 17871ade9a47d1c2e34187c0be9c2cb880d29caa Mon Sep 17 00:00:00 2001 From: Davi Arnaut Date: Tue, 10 Nov 2009 17:36:38 -0200 Subject: [PATCH 20/40] Backport of Bug#41860 to mysql-next-mr ------------------------------------------------------------ revno: 3317 revision-id: davi.arnaut@sun.com-20090522170916-fzc5ca3tjs9roy1t parent: patrick.crews@sun.com-20090522152933-ole8s3suy4zqyvku committer: Davi Arnaut branch nick: 41860-6.0 timestamp: Fri 2009-05-22 14:09:16 -0300 message: Bug#41860: Without Windows named pipe The problem was that the patch for Bug#10374 broke named pipe and shared memory transports on Windows due to a failure to implement a dummy poll method for transports other than BSD sockets. Another problem was that mysqltest lacked support for named pipe and shared memory connections, which lead to misleading test cases that were supposed run common queries over both transports. The solution is to properly implement, at the VIO layer, the poll and is_connected methods. The is_connected method is implemented for every suppported transport and the poll one only where it makes sense. Furthermore, support for named pipe and shared memory connections is added to mysqltest as to enable testing of both transports using the test suite. client/mysqltest.cc: Add support for named pipe and shared memory connections. include/violite.h: Move private functions to vio/vio_priv.h Add poll_read and is_connected methods. mysql-test/t/named_pipe.test: Run tests over a named pipe connection. mysql-test/t/shm.test: Run tests over a shared memory connection. sql/item_func.cc: Rename method. sql/sql_class.cc: Remove higher-level vio_is_connected implementation. sql/sql_class.h: Rename vio_is_connected to not conflict with the vio one. Verify that there is a valid vio. vio/vio.c: Add poll_read and is_connected methods. vio/vio_priv.h: Add private functions. vio/viosocket.c: Implement the is_connected method for the various transports. --- client/mysqltest.cc | 38 +++++++++++ include/violite.h | 17 ++--- mysql-test/t/named_pipe.test | 5 ++ mysql-test/t/shm.test | 10 ++- sql/item_func.cc | 2 +- sql/sql_class.cc | 24 ------- sql/sql_class.h | 7 +- vio/vio.c | 30 +++++++++ vio/vio_priv.h | 14 ++++ vio/viosocket.c | 123 +++++++++++++++++++++++++++++++++-- 10 files changed, 223 insertions(+), 47 deletions(-) diff --git a/client/mysqltest.cc b/client/mysqltest.cc index b62d101e884..93e6e58e221 100644 --- a/client/mysqltest.cc +++ b/client/mysqltest.cc @@ -4859,6 +4859,8 @@ do_handle_error: - options to use for the connection * SSL - use SSL if available * COMPRESS - use compression if available + * SHM - use shared memory if available + * PIPE - use named pipe if available */ @@ -4867,6 +4869,7 @@ void do_connect(struct st_command *command) int con_port= opt_port; char *con_options; my_bool con_ssl= 0, con_compress= 0; + my_bool con_pipe= 0, con_shm= 0; struct st_connection* con_slot; static DYNAMIC_STRING ds_connection_name; @@ -4877,6 +4880,9 @@ void do_connect(struct st_command *command) static DYNAMIC_STRING ds_port; static DYNAMIC_STRING ds_sock; static DYNAMIC_STRING ds_options; +#ifdef HAVE_SMEM + static DYNAMIC_STRING ds_shm; +#endif const struct command_arg connect_args[] = { { "connection name", ARG_STRING, TRUE, &ds_connection_name, "Name of the connection" }, { "host", ARG_STRING, TRUE, &ds_host, "Host to connect to" }, @@ -4904,6 +4910,11 @@ void do_connect(struct st_command *command) die("Illegal argument for port: '%s'", ds_port.str); } +#ifdef HAVE_SMEM + /* Shared memory */ + init_dynamic_string(&ds_shm, ds_sock.str, 0, 0); +#endif + /* Sock */ if (ds_sock.length) { @@ -4942,6 +4953,10 @@ void do_connect(struct st_command *command) con_ssl= 1; else if (!strncmp(con_options, "COMPRESS", 8)) con_compress= 1; + else if (!strncmp(con_options, "PIPE", 4)) + con_pipe= 1; + else if (!strncmp(con_options, "SHM", 3)) + con_shm= 1; else die("Illegal option to connect: %.*s", (int) (end - con_options), con_options); @@ -4994,6 +5009,26 @@ void do_connect(struct st_command *command) } #endif +#ifdef __WIN__ + if (con_pipe) + { + uint protocol= MYSQL_PROTOCOL_PIPE; + mysql_options(&con_slot->mysql, MYSQL_OPT_PROTOCOL, &protocol); + } +#endif + +#ifdef HAVE_SMEM + if (con_shm) + { + uint protocol= MYSQL_PROTOCOL_MEMORY; + if (!ds_shm.length) + die("Missing shared memory base name"); + mysql_options(&con_slot->mysql, MYSQL_OPT_PROTOCOL, &protocol); + mysql_options(&con_slot->mysql, MYSQL_SHARED_MEMORY_BASE_NAME, ds_shm.str); + } +#endif + + /* Use default db name */ if (ds_database.length == 0) dynstr_set(&ds_database, opt_db); @@ -5026,6 +5061,9 @@ void do_connect(struct st_command *command) dynstr_free(&ds_port); dynstr_free(&ds_sock); dynstr_free(&ds_options); +#ifdef HAVE_SMEM + dynstr_free(&ds_shm); +#endif DBUG_VOID_RETURN; } diff --git a/include/violite.h b/include/violite.h index 0e07e1c69ca..395feb4e0df 100644 --- a/include/violite.h +++ b/include/violite.h @@ -51,9 +51,6 @@ Vio* vio_new_win32shared_memory(NET *net,HANDLE handle_file_map, HANDLE event_client_wrote, HANDLE event_client_read, HANDLE event_conn_closed); -size_t vio_read_pipe(Vio *vio, uchar * buf, size_t size); -size_t vio_write_pipe(Vio *vio, const uchar * buf, size_t size); -int vio_close_pipe(Vio * vio); #else #define HANDLE void * #endif /* __WIN__ */ @@ -87,8 +84,8 @@ my_socket vio_fd(Vio*vio); my_bool vio_peer_addr(Vio* vio, char *buf, uint16 *port); /* Remotes in_addr */ void vio_in_addr(Vio *vio, struct in_addr *in); -my_bool vio_poll_read(Vio *vio,uint timeout); -my_bool vio_peek_read(Vio *vio, uint *bytes); +my_bool vio_poll_read(Vio *vio, uint timeout); +my_bool vio_is_connected(Vio *vio); #ifdef HAVE_OPENSSL #include @@ -137,12 +134,6 @@ struct st_VioSSLFd void free_vio_ssl_acceptor_fd(struct st_VioSSLFd *fd); #endif /* HAVE_OPENSSL */ -#ifdef HAVE_SMEM -size_t vio_read_shared_memory(Vio *vio, uchar * buf, size_t size); -size_t vio_write_shared_memory(Vio *vio, const uchar * buf, size_t size); -int vio_close_shared_memory(Vio * vio); -#endif - void vio_end(void); #ifdef __cplusplus @@ -165,6 +156,8 @@ void vio_end(void); #define vio_peer_addr(vio, buf, prt) (vio)->peer_addr(vio, buf, prt) #define vio_in_addr(vio, in) (vio)->in_addr(vio, in) #define vio_timeout(vio, which, seconds) (vio)->timeout(vio, which, seconds) +#define vio_poll_read(vio, timeout) (vio)->poll_read(vio, timeout) +#define vio_is_connected(vio) (vio)->is_connected(vio) #endif /* !defined(DONT_MAP_VIO) */ /* This enumerator is used in parser - should be always visible */ @@ -209,6 +202,8 @@ struct st_vio my_bool (*was_interrupted)(Vio*); int (*vioclose)(Vio*); void (*timeout)(Vio*, unsigned int which, unsigned int timeout); + my_bool (*poll_read)(Vio *vio, uint timeout); + my_bool (*is_connected)(Vio*); #ifdef HAVE_OPENSSL void *ssl_arg; #endif diff --git a/mysql-test/t/named_pipe.test b/mysql-test/t/named_pipe.test index e3dfd24bb52..e88fd8e1ef8 100644 --- a/mysql-test/t/named_pipe.test +++ b/mysql-test/t/named_pipe.test @@ -9,6 +9,11 @@ if (`SELECT '$nmp' != 'ON'`){ skip No named pipe support; } +# Connect using named pipe for testing +connect(pipe_con,localhost,root,,,,,PIPE); + # Source select test case -- source include/common-tests.inc +connection default; +disconnect pipe_con; diff --git a/mysql-test/t/shm.test b/mysql-test/t/shm.test index 88e96ae7b45..567caa4989a 100644 --- a/mysql-test/t/shm.test +++ b/mysql-test/t/shm.test @@ -7,10 +7,17 @@ let $shm= query_get_value("SHOW VARIABLES LIKE 'shared_memory'", Value, 1); if (`SELECT '$shm' != 'ON'`){ skip No shm support; } +let $shm_name= query_get_value("SHOW GLOBAL VARIABLES LIKE 'shared_memory_base_name'", Value, 1); + +# Connect using SHM for testing +connect(shm_con,localhost,root,,,,$shm_name,SHM); # Source select test case -- source include/common-tests.inc +connection default; +disconnect shm_con; + # # Bug #24924: shared-memory-base-name that is too long causes buffer overflow # @@ -20,7 +27,6 @@ if (`SELECT '$shm' != 'ON'`){ # Bug #33899: Deadlock in mysql_real_query with shared memory connections # -let $name= query_get_value("SHOW GLOBAL VARIABLES LIKE 'shared_memory_base_name'", Value, 1); let $stmt= `SELECT REPEAT('a', 2048)`; SET @max_allowed_packet= @@global.max_allowed_packet; @@ -30,7 +36,7 @@ SET GLOBAL max_allowed_packet= 1024; SET GLOBAL net_buffer_length= 1024; --error 1 ---exec echo SELECT '$stmt'| $MYSQL --protocol=memory --shared-memory-base-name=$name 2>&1 +--exec echo SELECT '$stmt'| $MYSQL --protocol=memory --shared-memory-base-name=$shm_name 2>&1 SET GLOBAL max_allowed_packet= @max_allowed_packet; SET GLOBAL net_buffer_length= @net_buffer_length; diff --git a/sql/item_func.cc b/sql/item_func.cc index 8f2739f8e77..c466dcc5cb9 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -3471,7 +3471,7 @@ static int interruptible_wait(THD *thd, pthread_cond_t *cond, if (error == ETIMEDOUT || error == ETIME) { /* Return error if timed out or connection is broken. */ - if (!timeout || !thd->vio_is_connected()) + if (!timeout || !thd->is_connected()) break; } } while (error && timeout); diff --git a/sql/sql_class.cc b/sql/sql_class.cc index e765d892884..212d727b7f1 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -1618,30 +1618,6 @@ void THD::rollback_item_tree_changes() } -#ifndef EMBEDDED_LIBRARY - -/** - Check that the endpoint is still available. -*/ - -bool THD::vio_is_connected() -{ - uint bytes= 0; - - /* End of input is signaled by poll if the socket is aborted. */ - if (vio_poll_read(net.vio, 0)) - return TRUE; - - /* Socket is aborted if signaled but no data is available. */ - if (vio_peek_read(net.vio, &bytes)) - return TRUE; - - return bytes ? TRUE : FALSE; -} - -#endif - - /***************************************************************************** ** Functions to provide a interface to select results *****************************************************************************/ diff --git a/sql/sql_class.h b/sql/sql_class.h index 5117304a26f..87dcf361aa3 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1962,11 +1962,14 @@ public: } inline bool vio_ok() const { return net.vio != 0; } /** Return FALSE if connection to client is broken. */ - bool vio_is_connected(); + bool is_connected() + { + return vio_ok() ? vio_is_connected(net.vio) : FALSE; + } #else void clear_error(); inline bool vio_ok() const { return TRUE; } - inline bool vio_is_connected() { return TRUE; } + inline bool is_connected() { return TRUE; } #endif /** Mark the current error as fatal. Warning: this does not diff --git a/vio/vio.c b/vio/vio.c index e088687098b..5eca4bd1dc6 100644 --- a/vio/vio.c +++ b/vio/vio.c @@ -22,6 +22,28 @@ #include "vio_priv.h" +#if defined(__WIN__) || defined(HAVE_SMEM) + +/** + Stub poll_read method that defaults to indicate that there + is data to read. + + Used for named pipe and shared memory VIO types. + + @param vio Unused. + @param timeout Unused. + + @retval FALSE There is data to read. +*/ + +static my_bool no_poll_read(Vio *vio __attribute__((unused)), + uint timeout __attribute__((unused))) +{ + return FALSE; +} + +#endif + /* * Helper to fill most of the Vio* with defaults. */ @@ -60,6 +82,8 @@ static void vio_init(Vio* vio, enum enum_vio_type type, vio->vioblocking =vio_blocking; vio->is_blocking =vio_is_blocking; vio->timeout =vio_ignore_timeout; + vio->poll_read =no_poll_read; + vio->is_connected =vio_is_connected_pipe; } else /* default is VIO_TYPE_TCPIP */ #endif @@ -80,6 +104,8 @@ static void vio_init(Vio* vio, enum enum_vio_type type, vio->vioblocking =vio_blocking; vio->is_blocking =vio_is_blocking; vio->timeout =vio_ignore_timeout; + vio->poll_read =no_poll_read; + vio->is_connected =vio_is_connected_shared_memory; } else #endif @@ -100,6 +126,8 @@ static void vio_init(Vio* vio, enum enum_vio_type type, vio->vioblocking =vio_ssl_blocking; vio->is_blocking =vio_is_blocking; vio->timeout =vio_timeout; + vio->poll_read =vio_poll_read; + vio->is_connected =vio_is_connected; } else /* default is VIO_TYPE_TCPIP */ #endif /* HAVE_OPENSSL */ @@ -118,6 +146,8 @@ static void vio_init(Vio* vio, enum enum_vio_type type, vio->vioblocking =vio_blocking; vio->is_blocking =vio_is_blocking; vio->timeout =vio_timeout; + vio->poll_read =vio_poll_read; + vio->is_connected =vio_is_connected; } DBUG_VOID_RETURN; } diff --git a/vio/vio_priv.h b/vio/vio_priv.h index 792fad4cc66..c32d973393f 100644 --- a/vio/vio_priv.h +++ b/vio/vio_priv.h @@ -25,6 +25,20 @@ #include #include +#ifdef __WIN__ +size_t vio_read_pipe(Vio *vio, uchar * buf, size_t size); +size_t vio_write_pipe(Vio *vio, const uchar * buf, size_t size); +my_bool vio_is_connected_pipe(Vio *vio); +int vio_close_pipe(Vio * vio); +#endif + +#ifdef HAVE_SMEM +size_t vio_read_shared_memory(Vio *vio, uchar * buf, size_t size); +size_t vio_write_shared_memory(Vio *vio, const uchar * buf, size_t size); +my_bool vio_is_connected_shared_memory(Vio *vio); +int vio_close_shared_memory(Vio * vio); +#endif + void vio_ignore_timeout(Vio *vio, uint which, uint timeout); void vio_timeout(Vio *vio,uint which, uint timeout); diff --git a/vio/viosocket.c b/vio/viosocket.c index e83559729b9..055112f944e 100644 --- a/vio/viosocket.c +++ b/vio/viosocket.c @@ -356,15 +356,26 @@ void vio_in_addr(Vio *vio, struct in_addr *in) } -/* Return 0 if there is data to be read */ +/** + Indicate whether there is data to read on a given socket. -my_bool vio_poll_read(Vio *vio,uint timeout) + @note An exceptional condition event and/or errors are + interpreted as if there is data to read. + + @param sd A connected socket. + @param timeout Maximum time in seconds to poll. + + @retval FALSE There is data to read. + @retval TRUE There is no data to read. +*/ + +static my_bool socket_poll_read(my_socket sd, uint timeout) { #ifdef __WIN__ - int res, fd= vio->sd; + my_socket fd= sd; fd_set readfds, errorfds; struct timeval tm; - DBUG_ENTER("vio_poll"); + DBUG_ENTER("socket_poll_read"); tm.tv_sec= timeout; tm.tv_usec= 0; FD_ZERO(&readfds); @@ -380,8 +391,8 @@ my_bool vio_poll_read(Vio *vio,uint timeout) #elif defined(HAVE_POLL) struct pollfd fds; int res; - DBUG_ENTER("vio_poll"); - fds.fd=vio->sd; + DBUG_ENTER("socket_poll_read"); + fds.fd=sd; fds.events=POLLIN; fds.revents=0; if ((res=poll(&fds,1,(int) timeout*1000)) <= 0) @@ -395,7 +406,17 @@ my_bool vio_poll_read(Vio *vio,uint timeout) } -my_bool vio_peek_read(Vio *vio, uint *bytes) +/** + Retrieve the amount of data that can be read from a socket. + + @param vio A VIO object. + @param bytes[out] The amount of bytes available. + + @retval FALSE Success. + @retval TRUE Failure. +*/ + +static my_bool socket_peek_read(Vio *vio, uint *bytes) { #ifdef __WIN__ int len; @@ -419,6 +440,78 @@ my_bool vio_peek_read(Vio *vio, uint *bytes) #endif } + +/** + Indicate whether there is data to read on a given socket. + + @remark Errors are interpreted as if there is data to read. + + @param sd A connected socket. + @param timeout Maximum time in seconds to wait. + + @retval FALSE There is data (or EOF) to read. Also FALSE if error. + @retval TRUE There is _NO_ data to read or timed out. +*/ + +my_bool vio_poll_read(Vio *vio, uint timeout) +{ + my_socket sd= vio->sd; + DBUG_ENTER("vio_poll_read"); +#ifdef HAVE_OPENSSL + if (vio->type == VIO_TYPE_SSL) + sd= SSL_get_fd((SSL*) vio->ssl_arg); +#endif + DBUG_RETURN(socket_poll_read(sd, timeout)); +} + + +/** + Determine if the endpoint of a connection is still available. + + @remark The socket is assumed to be disconnected if an EOF + condition is encountered. + + @param vio The VIO object. + + @retval TRUE EOF condition not found. + @retval FALSE EOF condition is signaled. +*/ + +my_bool vio_is_connected(Vio *vio) +{ + uint bytes= 0; + DBUG_ENTER("vio_is_connected"); + + /* In the presence of errors the socket is assumed to be connected. */ + + /* + The first step of detecting a EOF condition is veryfing + whether there is data to read. Data in this case would + be the EOF. + */ + if (vio_poll_read(vio, 0)) + DBUG_RETURN(TRUE); + + /* + The second step is read() or recv() from the socket returning + 0 (EOF). Unfortunelly, it's not possible to call read directly + as we could inadvertently read meaningful connection data. + Simulate a read by retrieving the number of bytes available to + read -- 0 meaning EOF. + */ + if (socket_peek_read(vio, &bytes)) + DBUG_RETURN(TRUE); + +#ifdef HAVE_OPENSSL + /* There might be buffered data at the SSL layer. */ + if (!bytes && vio->type == VIO_TYPE_SSL) + bytes= SSL_pending((SSL*) vio->ssl_arg); +#endif + + DBUG_RETURN(bytes ? TRUE : FALSE); +} + + void vio_timeout(Vio *vio, uint which, uint timeout) { #if defined(SO_SNDTIMEO) && defined(SO_RCVTIMEO) @@ -487,6 +580,16 @@ size_t vio_write_pipe(Vio * vio, const uchar* buf, size_t size) DBUG_RETURN((size_t) length); } + +my_bool vio_is_connected_pipe(Vio *vio) +{ + if (PeekNamedPipe(vio->hPipe, NULL, 0, NULL, NULL, NULL)) + return TRUE; + else + return (GetLastError() != ERROR_BROKEN_PIPE); +} + + int vio_close_pipe(Vio * vio) { int r; @@ -625,6 +728,12 @@ size_t vio_write_shared_memory(Vio * vio, const uchar* buf, size_t size) } +my_bool vio_is_connected_shared_memory(Vio *vio) +{ + return (WaitForSingleObject(vio->event_conn_closed, 0) != WAIT_OBJECT_0); +} + + /** Close shared memory and DBUG_PRINT any errors that happen on closing. @return Zero if all closing functions succeed, and nonzero otherwise. From e879919a9f7bb3681bc4a8522c30e335d1fc28ce Mon Sep 17 00:00:00 2001 From: Davi Arnaut Date: Tue, 10 Nov 2009 18:31:28 -0200 Subject: [PATCH 21/40] Backport of Bug#15192 to mysql-next-mr ------------------------------------------------------------ revno: 2597.4.17 revision-id: sp1r-davi@mysql.com/endora.local-20080328174753-24337 parent: sp1r-anozdrin/alik@quad.opbmk-20080328140038-16479 committer: davi@mysql.com/endora.local timestamp: Fri 2008-03-28 14:47:53 -0300 message: Bug#15192 "fatal errors" are caught by handlers in stored procedures The problem is that fatal errors (e.g.: out of memory) were being caught by stored procedure exception handlers which could cause the execution to not be stopped due to a continue handler. The solution is to not call any exception handler if the error is fatal and send the fatal error to the client. mysql-test/r/sp-error.result: Add test case result for Bug#15192 mysql-test/t/sp-error.test: Add test case for Bug#15192 mysys/my_alloc.c: Pass flag to signal fatal error in memory root allocations. sql/event_data_objects.cc: Use init_sql_alloc to initialize memory roots, which uses the sql error handler to push errors. sql/ha_partition.cc: Pass flag to signal fatal error instead of calling fatal_error. sql/item_func.cc: Pass flag to signal fatal error instead of calling fatal_error. sql/item_subselect.cc: Remove redundant fatal error, memory root already pushes error. sql/opt_sum.cc: Pass flag to signal fatal error instead of calling fatal_error. sql/sp_head.cc: Allocator already sets fatal error. sql/sql_class.h: A error must exist for it to be fatal. Pass flag to signal fatal error instead of calling fatal_error. sql/sql_insert.cc: Pass flag to signal fatal error instead of calling fatal_error. sql/sql_list.h: Pass flag to signal fatal error instead of calling fatal_error. sql/sql_parse.cc: Pass flag to signal fatal error instead of calling fatal_error. sql/sql_partition.cc: Pass flag to signal fatal error instead of calling fatal_error. sql/sql_select.cc: Pass flag to signal fatal error instead of calling fatal_error. sql/sql_servers.cc: Use init_sql_alloc to initialize memory roots, which uses the sql error handler to push errors. sql/sql_show.cc: Pass flag to signal fatal error instead of calling fatal_error. sql/sql_trigger.cc: Use init_sql_alloc to initialize memory roots, which uses the sql error handler to push errors. sql/sql_update.cc: Pass flag to signal fatal error instead of calling fatal_error. sql/tztime.cc: Use init_sql_alloc to initialize memory roots, which uses the sql error handler to push errors. --- mysql-test/r/sp-error.result | 11 +++++++++++ mysql-test/t/sp-error.test | 21 +++++++++++++++++++++ mysys/my_alloc.c | 4 ++-- sql/event_data_objects.cc | 2 +- sql/ha_partition.cc | 3 +-- sql/item_func.cc | 23 +++++++++-------------- sql/item_subselect.cc | 7 +------ sql/opt_sum.cc | 6 ++---- sql/sp_head.cc | 3 --- sql/sql_class.h | 6 +++++- sql/sql_insert.cc | 14 +++++--------- sql/sql_list.h | 2 +- sql/sql_parse.cc | 1 - sql/sql_partition.cc | 8 ++------ sql/sql_select.cc | 8 +++----- sql/sql_servers.cc | 4 ++-- sql/sql_show.cc | 3 +-- sql/sql_trigger.cc | 4 ++-- sql/sql_update.cc | 18 ++++++++++-------- sql/tztime.cc | 2 +- 20 files changed, 80 insertions(+), 70 deletions(-) diff --git a/mysql-test/r/sp-error.result b/mysql-test/r/sp-error.result index 17ab2b79043..50b641e8052 100644 --- a/mysql-test/r/sp-error.result +++ b/mysql-test/r/sp-error.result @@ -1659,6 +1659,17 @@ begin declare continue handler for sqlstate '00000' set @x=0; end$$ ERROR 42000: Bad SQLSTATE: '00000' +drop procedure if exists p1; +set @old_recursion_depth = @@max_sp_recursion_depth; +set @@max_sp_recursion_depth = 255; +create procedure p1(a int) +begin +declare continue handler for sqlexception select 'exception'; +call p1(a+1); +end| +call p1(1); +set @@max_sp_recursion_depth = @old_recursion_depth; +drop procedure p1; LOAD DATA INFILE '../../tmp/proc.txt' INTO TABLE mysql.proc; CREATE TABLE t1 (a INT, b INT); INSERT INTO t1 VALUES (1,1), (2,2); diff --git a/mysql-test/t/sp-error.test b/mysql-test/t/sp-error.test index 66b960c938f..574002d3d38 100644 --- a/mysql-test/t/sp-error.test +++ b/mysql-test/t/sp-error.test @@ -2420,6 +2420,27 @@ end$$ delimiter ;$$ +# +# Bug#15192: "fatal errors" are caught by handlers in stored procedures +# + +--disable_warnings +drop procedure if exists p1; +--enable_warnings +set @old_recursion_depth = @@max_sp_recursion_depth; +set @@max_sp_recursion_depth = 255; +delimiter |; +create procedure p1(a int) +begin + declare continue handler for sqlexception select 'exception'; + call p1(a+1); +end| +delimiter ;| +--error 0,ER_STACK_OVERRUN_NEED_MORE +call p1(1); +set @@max_sp_recursion_depth = @old_recursion_depth; +drop procedure p1; + # # BUG#NNNN: New bug synopsis # diff --git a/mysys/my_alloc.c b/mysys/my_alloc.c index 2607ea57d08..9c45cdc2277 100644 --- a/mysys/my_alloc.c +++ b/mysys/my_alloc.c @@ -155,7 +155,7 @@ void *alloc_root(MEM_ROOT *mem_root, size_t length) DBUG_ASSERT(alloc_root_inited(mem_root)); length+=ALIGN_SIZE(sizeof(USED_MEM)); - if (!(next = (USED_MEM*) my_malloc(length,MYF(MY_WME)))) + if (!(next = (USED_MEM*) my_malloc(length,MYF(MY_WME | ME_FATALERROR)))) { if (mem_root->error_handler) (*mem_root->error_handler)(); @@ -198,7 +198,7 @@ void *alloc_root(MEM_ROOT *mem_root, size_t length) get_size= length+ALIGN_SIZE(sizeof(USED_MEM)); get_size= max(get_size, block_size); - if (!(next = (USED_MEM*) my_malloc(get_size,MYF(MY_WME)))) + if (!(next = (USED_MEM*) my_malloc(get_size,MYF(MY_WME | ME_FATALERROR)))) { if (mem_root->error_handler) (*mem_root->error_handler)(); diff --git a/sql/event_data_objects.cc b/sql/event_data_objects.cc index 271ac73bd30..8e7f72854ea 100644 --- a/sql/event_data_objects.cc +++ b/sql/event_data_objects.cc @@ -196,7 +196,7 @@ Event_basic::Event_basic() { DBUG_ENTER("Event_basic::Event_basic"); /* init memory root */ - init_alloc_root(&mem_root, 256, 512); + init_sql_alloc(&mem_root, 256, 512); dbname.str= name.str= NULL; dbname.length= name.length= 0; time_zone= NULL; diff --git a/sql/ha_partition.cc b/sql/ha_partition.cc index a8613e13f1e..bd7cb30d89b 100644 --- a/sql/ha_partition.cc +++ b/sql/ha_partition.cc @@ -1980,8 +1980,7 @@ partition_element *ha_partition::find_partition_element(uint part_id) return part_elem; } DBUG_ASSERT(0); - my_error(ER_OUT_OF_RESOURCES, MYF(0)); - current_thd->fatal_error(); // Abort + my_error(ER_OUT_OF_RESOURCES, MYF(ME_FATALERROR)); return NULL; } diff --git a/sql/item_func.cc b/sql/item_func.cc index c466dcc5cb9..e3ce4af0f4d 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -3818,7 +3818,7 @@ static user_var_entry *get_variable(HASH *hash, LEX_STRING &name, uint size=ALIGN_SIZE(sizeof(user_var_entry))+name.length+1+extra_size; if (!my_hash_inited(hash)) return 0; - if (!(entry = (user_var_entry*) my_malloc(size,MYF(MY_WME)))) + if (!(entry = (user_var_entry*) my_malloc(size,MYF(MY_WME | ME_FATALERROR)))) return 0; entry->name.str=(char*) entry+ ALIGN_SIZE(sizeof(user_var_entry))+ extra_size; @@ -3956,6 +3956,8 @@ bool Item_func_set_user_var::register_field_in_read_map(uchar *arg) @param dv derivation for new value @param unsigned_arg indiates if a value of type INT_RESULT is unsigned + @note Sets error and fatal error if allocation fails. + @retval false success @retval @@ -3999,7 +4001,8 @@ update_hash(user_var_entry *entry, bool set_null, void *ptr, uint length, if (entry->value == pos) entry->value=0; entry->value= (char*) my_realloc(entry->value, length, - MYF(MY_ALLOW_ZERO_PTR | MY_WME)); + MYF(MY_ALLOW_ZERO_PTR | MY_WME | + ME_FATALERROR)); if (!entry->value) return 1; } @@ -4036,7 +4039,6 @@ Item_func_set_user_var::update_hash(void *ptr, uint length, if (::update_hash(entry, (null_value= args[0]->null_value), ptr, length, res_type, cs, dv, unsigned_arg)) { - current_thd->fatal_error(); // Probably end of memory null_value= 1; return 1; } @@ -4769,11 +4771,6 @@ void Item_func_get_user_var::fix_length_and_dec() m_cached_result_type= STRING_RESULT; max_length= MAX_BLOB_WIDTH; } - - if (error) - thd->fatal_error(); - - return; } @@ -4857,18 +4854,16 @@ bool Item_user_var_as_out_param::fix_fields(THD *thd, Item **ref) void Item_user_var_as_out_param::set_null_value(CHARSET_INFO* cs) { - if (::update_hash(entry, TRUE, 0, 0, STRING_RESULT, cs, - DERIVATION_IMPLICIT, 0 /* unsigned_arg */)) - current_thd->fatal_error(); // Probably end of memory + ::update_hash(entry, TRUE, 0, 0, STRING_RESULT, cs, + DERIVATION_IMPLICIT, 0 /* unsigned_arg */); } void Item_user_var_as_out_param::set_value(const char *str, uint length, CHARSET_INFO* cs) { - if (::update_hash(entry, FALSE, (void*)str, length, STRING_RESULT, cs, - DERIVATION_IMPLICIT, 0 /* unsigned_arg */)) - current_thd->fatal_error(); // Probably end of memory + ::update_hash(entry, FALSE, (void*)str, length, STRING_RESULT, cs, + DERIVATION_IMPLICIT, 0 /* unsigned_arg */); } diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index d16710f6660..9c34ae102ee 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -1735,8 +1735,6 @@ subselect_union_engine::subselect_union_engine(st_select_lex_unit *u, :subselect_engine(item_arg, result_arg) { unit= u; - if (!result_arg) //out of memory - current_thd->fatal_error(); unit->item= item_arg; } @@ -1748,10 +1746,7 @@ int subselect_single_select_engine::prepare() join= new JOIN(thd, select_lex->item_list, select_lex->options | SELECT_NO_UNLOCK, result); if (!join || !result) - { - thd->fatal_error(); //out of memory - return 1; - } + return 1; /* Fatal error is set already. */ prepared= 1; SELECT_LEX *save_select= thd->lex->current_select; thd->lex->current_select= select_lex; diff --git a/sql/opt_sum.cc b/sql/opt_sum.cc index 13b10ac2e8f..d85e976e9c8 100644 --- a/sql/opt_sum.cc +++ b/sql/opt_sum.cc @@ -175,8 +175,7 @@ int opt_sum_query(TABLE_LIST *tables, List &all_fields,COND *conds) error= tl->table->file->info(HA_STATUS_VARIABLE | HA_STATUS_NO_LOCK); if(error) { - tl->table->file->print_error(error, MYF(0)); - tl->table->in_use->fatal_error(); + tl->table->file->print_error(error, MYF(ME_FATALERROR)); return error; } count*= tl->table->file->stats.records; @@ -427,8 +426,7 @@ int opt_sum_query(TABLE_LIST *tables, List &all_fields,COND *conds) if (error == HA_ERR_KEY_NOT_FOUND || error == HA_ERR_END_OF_FILE) return HA_ERR_KEY_NOT_FOUND; // No rows matching WHERE /* HA_ERR_LOCK_DEADLOCK or some other error */ - table->file->print_error(error, MYF(0)); - table->in_use->fatal_error(); + table->file->print_error(error, MYF(ME_FATALERROR)); return(error); } removed_tables|= table->map; diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 6e5c55665c8..71cd2936912 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -4012,10 +4012,7 @@ sp_add_to_query_tables(THD *thd, LEX *lex, TABLE_LIST *table; if (!(table= (TABLE_LIST *)thd->calloc(sizeof(TABLE_LIST)))) - { - thd->fatal_error(); return NULL; - } table->db_length= strlen(db); table->db= thd->strmake(db, table->db_length); table->table_name_length= strlen(name); diff --git a/sql/sql_class.h b/sql/sql_class.h index 87dcf361aa3..5a2a7af1ba0 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1978,6 +1978,7 @@ public: */ inline void fatal_error() { + DBUG_ASSERT(main_da.is_error()); is_fatal_error= 1; DBUG_PRINT("error",("Fatal error set")); } @@ -2139,7 +2140,10 @@ public: else { x_free(db); - db= new_db ? my_strndup(new_db, new_db_len, MYF(MY_WME)) : NULL; + if (new_db) + db= my_strndup(new_db, new_db_len, MYF(MY_WME | ME_FATALERROR)); + else + db= NULL; } db_length= db ? new_db_len : 0; return new_db && !db; diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index f2f95dfb553..8087f644e0f 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -1922,20 +1922,17 @@ bool delayed_get_table(THD *thd, TABLE_LIST *table_list) if (! (di= find_handler(thd, table_list))) { if (!(di= new Delayed_insert())) - { - thd->fatal_error(); goto end_create; - } pthread_mutex_lock(&LOCK_thread_count); thread_count++; pthread_mutex_unlock(&LOCK_thread_count); di->thd.set_db(table_list->db, (uint) strlen(table_list->db)); - di->thd.set_query(my_strdup(table_list->table_name, MYF(MY_WME)), 0); + di->thd.set_query(my_strdup(table_list->table_name, + MYF(MY_WME | ME_FATALERROR)), 0); if (di->thd.db == NULL || di->thd.query == NULL) { /* The error is reported */ delete di; - thd->fatal_error(); goto end_create; } di->table_list= *table_list; // Needed to open table @@ -1953,8 +1950,7 @@ bool delayed_get_table(THD *thd, TABLE_LIST *table_list) pthread_mutex_unlock(&di->mutex); di->unlock(); delete di; - my_error(ER_CANT_CREATE_THREAD, MYF(0), error); - thd->fatal_error(); + my_error(ER_CANT_CREATE_THREAD, MYF(ME_FATALERROR), error); goto end_create; } @@ -2331,8 +2327,8 @@ static void handle_delayed_insert_impl(THD *thd, Delayed_insert *di) } if (!(di->table->file->ha_table_flags() & HA_CAN_INSERT_DELAYED)) { - thd->fatal_error(); - my_error(ER_DELAYED_NOT_SUPPORTED, MYF(0), di->table_list.table_name); + my_error(ER_DELAYED_NOT_SUPPORTED, MYF(ME_FATALERROR), + di->table_list.table_name); goto err; } if (di->table->triggers) diff --git a/sql/sql_list.h b/sql/sql_list.h index 74f4cc0ec0d..e1bf05fff23 100644 --- a/sql/sql_list.h +++ b/sql/sql_list.h @@ -458,7 +458,7 @@ struct ilink struct ilink **prev,*next; static void *operator new(size_t size) throw () { - return (void*)my_malloc((uint)size, MYF(MY_WME | MY_FAE)); + return (void*)my_malloc((uint)size, MYF(MY_WME | MY_FAE | ME_FATALERROR)); } static void operator delete(void* ptr_arg, size_t size) { diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 926717ef733..a22daeabf0d 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -5823,7 +5823,6 @@ bool check_stack_overrun(THD *thd, long margin, my_snprintf(ebuff, sizeof(ebuff), ER(ER_STACK_OVERRUN_NEED_MORE), stack_used, my_thread_stack_size, margin); my_message(ER_STACK_OVERRUN_NEED_MORE, ebuff, MYF(ME_FATALERROR)); - thd->fatal_error(); return 1; } #ifndef DBUG_OFF diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc index eb711001ef6..efafc15704b 100644 --- a/sql/sql_partition.cc +++ b/sql/sql_partition.cc @@ -2084,8 +2084,7 @@ char *generate_partition_syntax(partition_info *part_info, default: DBUG_ASSERT(0); /* We really shouldn't get here, no use in continuing from here */ - my_error(ER_OUT_OF_RESOURCES, MYF(0)); - current_thd->fatal_error(); + my_error(ER_OUT_OF_RESOURCES, MYF(ME_FATALERROR)); DBUG_RETURN(NULL); } if (part_info->part_expr) @@ -5150,10 +5149,7 @@ static bool mysql_change_partitions(ALTER_PARTITION_PARAM_TYPE *lpt) &lpt->deleted, lpt->pack_frm_data, lpt->pack_frm_len))) { - if (error != ER_OUTOFMEMORY) - file->print_error(error, MYF(0)); - else - lpt->thd->fatal_error(); + file->print_error(error, MYF(error != ER_OUTOFMEMORY ? 0 : ME_FATALERROR)); DBUG_RETURN(TRUE); } DBUG_RETURN(FALSE); diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 549fbb653d1..50b9a4a2f6c 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -9628,7 +9628,7 @@ Field *create_tmp_field(THD *thd, TABLE *table,Item *item, Item::Type type, Item_sum *item_sum=(Item_sum*) item; result= item_sum->create_tmp_field(group, table, convert_blob_length); if (!result) - thd->fatal_error(); + my_error(ER_OUT_OF_RESOURCES, MYF(ME_FATALERROR)); return result; } case Item::FIELD_ITEM: @@ -10771,8 +10771,7 @@ bool create_myisam_from_heap(THD *thd, TABLE *table, TMP_TABLE_PARAM *param, We don't want this error to be converted to a warning, e.g. in case of INSERT IGNORE ... SELECT. */ - thd->fatal_error(); - table->file->print_error(error,MYF(0)); + table->file->print_error(error, MYF(ME_FATALERROR)); DBUG_RETURN(1); } @@ -14930,8 +14929,7 @@ calc_group_buffer(JOIN *join,ORDER *group) default: /* This case should never be choosen */ DBUG_ASSERT(0); - my_error(ER_OUT_OF_RESOURCES, MYF(0)); - join->thd->fatal_error(); + my_error(ER_OUT_OF_RESOURCES, MYF(ME_FATALERROR)); } } parts++; diff --git a/sql/sql_servers.cc b/sql/sql_servers.cc index e8fa3d984a7..be86d1150b7 100644 --- a/sql/sql_servers.cc +++ b/sql/sql_servers.cc @@ -128,7 +128,7 @@ bool servers_init(bool dont_read_servers_table) } /* Initialize the mem root for data */ - init_alloc_root(&mem, ACL_ALLOC_BLOCK_SIZE, 0); + init_sql_alloc(&mem, ACL_ALLOC_BLOCK_SIZE, 0); if (dont_read_servers_table) goto end; @@ -180,7 +180,7 @@ static bool servers_load(THD *thd, TABLE_LIST *tables) my_hash_reset(&servers_cache); free_root(&mem, MYF(0)); - init_alloc_root(&mem, ACL_ALLOC_BLOCK_SIZE, 0); + init_sql_alloc(&mem, ACL_ALLOC_BLOCK_SIZE, 0); init_read_record(&read_record_info,thd,table=tables[0].table,NULL,1,0, FALSE); diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 5997ceaa941..bb99ae87c32 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -4960,8 +4960,7 @@ static int get_schema_partitions_record(THD *thd, TABLE_LIST *tables, break; default: DBUG_ASSERT(0); - my_error(ER_OUT_OF_RESOURCES, MYF(0)); - current_thd->fatal_error(); + my_error(ER_OUT_OF_RESOURCES, MYF(ME_FATALERROR)); DBUG_RETURN(1); } table->field[7]->set_notnull(); diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index 7b689167783..f8dd6c74c49 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -1660,7 +1660,7 @@ bool Table_triggers_list::drop_all_triggers(THD *thd, char *db, char *name) DBUG_ENTER("drop_all_triggers"); bzero(&table, sizeof(table)); - init_alloc_root(&table.mem_root, 8192, 0); + init_sql_alloc(&table.mem_root, 8192, 0); if (Table_triggers_list::check_n_load(thd, db, name, &table, 1)) { @@ -1871,7 +1871,7 @@ bool Table_triggers_list::change_table_name(THD *thd, const char *db, DBUG_ENTER("change_table_name"); bzero(&table, sizeof(table)); - init_alloc_root(&table.mem_root, 8192, 0); + init_sql_alloc(&table.mem_root, 8192, 0); /* This method interfaces the mysql server code protected by diff --git a/sql/sql_update.cc b/sql/sql_update.cc index cf6bf5d0633..3bfa8e26a16 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -662,11 +662,13 @@ int mysql_update(THD *thd, If (ignore && error is ignorable) we don't have to do anything; otherwise... */ + myf flags= 0; + if (table->file->is_fatal_error(error, HA_CHECK_DUP_KEY)) - thd->fatal_error(); /* Other handler errors are fatal */ + flags|= ME_FATALERROR; /* Other handler errors are fatal */ prepare_record_for_error_message(error, table); - table->file->print_error(error,MYF(0)); + table->file->print_error(error,MYF(flags)); error= 1; break; } @@ -763,9 +765,8 @@ int mysql_update(THD *thd, */ { /* purecov: begin inspected */ - thd->fatal_error(); prepare_record_for_error_message(loc_error, table); - table->file->print_error(loc_error,MYF(0)); + table->file->print_error(loc_error,MYF(ME_FATALERROR)); error= 1; /* purecov: end */ } @@ -1737,11 +1738,13 @@ bool multi_update::send_data(List ¬_used_values) If (ignore && error == is ignorable) we don't have to do anything; otherwise... */ + myf flags= 0; + if (table->file->is_fatal_error(error, HA_CHECK_DUP_KEY)) - thd->fatal_error(); /* Other handler errors are fatal */ + flags|= ME_FATALERROR; /* Other handler errors are fatal */ prepare_record_for_error_message(error, table); - table->file->print_error(error,MYF(0)); + table->file->print_error(error,MYF(flags)); DBUG_RETURN(1); } } @@ -2024,9 +2027,8 @@ int multi_update::do_updates() err: { - thd->fatal_error(); prepare_record_for_error_message(local_error, table); - table->file->print_error(local_error,MYF(0)); + table->file->print_error(local_error,MYF(ME_FATALERROR)); } err2: diff --git a/sql/tztime.cc b/sql/tztime.cc index 650678c721b..93ff614b10b 100644 --- a/sql/tztime.cc +++ b/sql/tztime.cc @@ -1594,7 +1594,7 @@ my_tz_init(THD *org_thd, const char *default_tzname, my_bool bootstrap) my_hash_free(&tz_names); goto end; } - init_alloc_root(&tz_storage, 32 * 1024, 0); + init_sql_alloc(&tz_storage, 32 * 1024, 0); VOID(pthread_mutex_init(&tz_LOCK, MY_MUTEX_INIT_FAST)); tz_inited= 1; From ff847056319f41cd8b6c70833ec3647146d12c72 Mon Sep 17 00:00:00 2001 From: Davi Arnaut Date: Tue, 10 Nov 2009 18:43:43 -0200 Subject: [PATCH 22/40] Backport of Bug#27249 to mysql-next-mr ------------------------------------------------------------ revno: 2476.784.4 revision-id: sp1r-davi@moksha.local-20071008114751-46069 parent: sp1r-davi@moksha.local-20071003002731-48537 committer: davi@moksha.local timestamp: Mon 2007-10-08 08:47:51 -0300 message: Bug#27249 table_wild with alias: select t1.* as something Aliases to table wildcards are silently ignored, but they should not be allowed as it is non-standard and currently useless. There is not point in having a alias to a wildcard of column names. The solution is to rewrite the select_item rule so that aliases for table wildcards are not accepted. Contribution by Martin Friebe mysql-test/r/alias.result: Add test case result for Bug#27249 mysql-test/t/alias.test: Add test case for Bug#27249 sql/sql_yacc.yy: Split up select_item rule so that aliases for table wildcards are not accepted by the parser. --- mysql-test/r/alias.result | 139 ++++++++++++++++++++++++++++++++++++++ mysql-test/t/alias.test | 129 +++++++++++++++++++++++++++++++++++ sql/sql_yacc.yy | 16 +++-- 3 files changed, 277 insertions(+), 7 deletions(-) diff --git a/mysql-test/r/alias.result b/mysql-test/r/alias.result index 6f0315da234..3190e8994e5 100644 --- a/mysql-test/r/alias.result +++ b/mysql-test/r/alias.result @@ -73,3 +73,142 @@ UPDATE t1 SET t1.xstatus_vor = Greatest(t1.xstatus_vor,1) WHERE t1.aufnr = "40004712" AND t1.plnfl = "000001" AND t1.vornr > "0010" ORDER BY t1.vornr ASC LIMIT 1; drop table t1; +drop table if exists t1,t2,t3; +create table t1 (a int, b int, c int); +create table t2 (d int); +create table t3 (a1 int, b1 int, c1 int); +insert into t1 values(1,2,3); +insert into t1 values(11,22,33); +insert into t2 values(99); +select t1.* as 'with_alias' from t1; +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 'as 'with_alias' from t1' at line 1 +select t2.* as 'with_alias' from t2; +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 'as 'with_alias' from t2' at line 1 +select t1.*, t1.* as 'with_alias' from t1; +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 'as 'with_alias' from t1' at line 1 +select t1.* as 'with_alias', t1.* from t1; +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 'as 'with_alias', t1.* from t1' at line 1 +select t1.* as 'with_alias', t1.* as 'alias2' from t1; +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 'as 'with_alias', t1.* as 'alias2' from t1' at line 1 +select t1.* as 'with_alias', a, t1.* as 'alias2' from t1; +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 'as 'with_alias', a, t1.* as 'alias2' from t1' at line 1 +select a, t1.* as 'with_alias' from t1; +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 'as 'with_alias' from t1' at line 1 +select t1.* as 'with_alias', a from t1; +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 'as 'with_alias', a from t1' at line 1 +select a, t1.* as 'with_alias', b from t1; +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 'as 'with_alias', b from t1' at line 1 +select (select d from t2 where d > a), t1.* as 'with_alias' from t1; +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 'as 'with_alias' from t1' at line 1 +select t1.* as 'with_alias', (select a from t2 where d > a) from t1; +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 'as 'with_alias', (select a from t2 where d > a) from t1' at line 1 +select a as 'x', t1.* as 'with_alias' from t1; +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 'as 'with_alias' from t1' at line 1 +select t1.* as 'with_alias', a as 'x' from t1; +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 'as 'with_alias', a as 'x' from t1' at line 1 +select a as 'x', t1.* as 'with_alias', b as 'x' from t1; +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 'as 'with_alias', b as 'x' from t1' at line 1 +select (select d from t2 where d > a) as 'x', t1.* as 'with_alias' from t1; +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 'as 'with_alias' from t1' at line 1 +select t1.* as 'with_alias', (select a from t2 where d > a) as 'x' from t1; +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 'as 'with_alias', (select a from t2 where d > a) as 'x' from t1' at line 1 +select (select t2.* as 'x' from t2) from t1; +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 'as 'x' from t2) from t1' at line 1 +select a, (select t2.* as 'x' from t2) from t1; +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 'as 'x' from t2) from t1' at line 1 +select t1.*, (select t2.* as 'x' from t2) from t1; +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 'as 'x' from t2) from t1' at line 1 +insert into t3 select t1.* as 'with_alias' from t1; +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 'as 'with_alias' from t1' at line 1 +insert into t3 select t2.* as 'with_alias', 1, 2 from t2; +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 'as 'with_alias', 1, 2 from t2' at line 1 +insert into t3 select t2.* as 'with_alias', d as 'x', d as 'z' from t2; +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 'as 'with_alias', d as 'x', d as 'z' from t2' at line 1 +insert into t3 select t2.*, t2.* as 'with_alias', 3 from t2; +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 'as 'with_alias', 3 from t2' at line 1 +create table t3 select t1.* as 'with_alias' from t1; +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 'as 'with_alias' from t1' at line 1 +create table t3 select t2.* as 'with_alias', 1, 2 from t2; +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 'as 'with_alias', 1, 2 from t2' at line 1 +create table t3 select t2.* as 'with_alias', d as 'x', d as 'z' from t2; +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 'as 'with_alias', d as 'x', d as 'z' from t2' at line 1 +create table t3 select t2.*, t2.* as 'with_alias', 3 from t2; +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 'as 'with_alias', 3 from t2' at line 1 +select t1.* from t1; +a b c +1 2 3 +11 22 33 +select t2.* from t2; +d +99 +select t1.*, t1.* from t1; +a b c a b c +1 2 3 1 2 3 +11 22 33 11 22 33 +select t1.*, a, t1.* from t1; +a b c a a b c +1 2 3 1 1 2 3 +11 22 33 11 11 22 33 +select a, t1.* from t1; +a a b c +1 1 2 3 +11 11 22 33 +select t1.*, a from t1; +a b c a +1 2 3 1 +11 22 33 11 +select a, t1.*, b from t1; +a a b c b +1 1 2 3 2 +11 11 22 33 22 +select (select d from t2 where d > a), t1.* from t1; +(select d from t2 where d > a) a b c +99 1 2 3 +99 11 22 33 +select t1.*, (select a from t2 where d > a) from t1; +a b c (select a from t2 where d > a) +1 2 3 1 +11 22 33 11 +select a as 'x', t1.* from t1; +x a b c +1 1 2 3 +11 11 22 33 +select t1.*, a as 'x' from t1; +a b c x +1 2 3 1 +11 22 33 11 +select a as 'x', t1.*, b as 'x' from t1; +x a b c x +1 1 2 3 2 +11 11 22 33 22 +select (select d from t2 where d > a) as 'x', t1.* from t1; +x a b c +99 1 2 3 +99 11 22 33 +select t1.*, (select a from t2 where d > a) as 'x' from t1; +a b c x +1 2 3 1 +11 22 33 11 +select (select t2.* from t2) from t1; +(select t2.* from t2) +99 +99 +select a, (select t2.* from t2) from t1; +a (select t2.* from t2) +1 99 +11 99 +select t1.*, (select t2.* from t2) from t1; +a b c (select t2.* from t2) +1 2 3 99 +11 22 33 99 +insert into t3 select t1.* from t1; +insert into t3 select t2.*, 1, 2 from t2; +insert into t3 select t2.*, d as 'x', d as 'z' from t2; +insert into t3 select t2.*, t2.*, 3 from t2; +create table t4 select t1.* from t1; +drop table t4; +create table t4 select t2.*, 1, 2 from t2; +drop table t4; +create table t4 select t2.*, d as 'x', d as 'z' from t2; +drop table t4; +drop table t1,t2,t3; diff --git a/mysql-test/t/alias.test b/mysql-test/t/alias.test index 6546581eef2..0e2d57598e2 100644 --- a/mysql-test/t/alias.test +++ b/mysql-test/t/alias.test @@ -86,3 +86,132 @@ ASC LIMIT 1; drop table t1; # End of 4.1 tests + +# +# Bug#27249 table_wild with alias: select t1.* as something +# + +--disable_warnings +drop table if exists t1,t2,t3; +--enable_warnings + +create table t1 (a int, b int, c int); +create table t2 (d int); +create table t3 (a1 int, b1 int, c1 int); +insert into t1 values(1,2,3); +insert into t1 values(11,22,33); +insert into t2 values(99); + +# Invalid queries with alias on wild +--error ER_PARSE_ERROR +select t1.* as 'with_alias' from t1; +--error ER_PARSE_ERROR +select t2.* as 'with_alias' from t2; +--error ER_PARSE_ERROR +select t1.*, t1.* as 'with_alias' from t1; +--error ER_PARSE_ERROR +select t1.* as 'with_alias', t1.* from t1; +--error ER_PARSE_ERROR +select t1.* as 'with_alias', t1.* as 'alias2' from t1; +--error ER_PARSE_ERROR +select t1.* as 'with_alias', a, t1.* as 'alias2' from t1; + +# other fields without alias +--error ER_PARSE_ERROR +select a, t1.* as 'with_alias' from t1; +--error ER_PARSE_ERROR +select t1.* as 'with_alias', a from t1; +--error ER_PARSE_ERROR +select a, t1.* as 'with_alias', b from t1; +--error ER_PARSE_ERROR +select (select d from t2 where d > a), t1.* as 'with_alias' from t1; +--error ER_PARSE_ERROR +select t1.* as 'with_alias', (select a from t2 where d > a) from t1; + +# other fields with alias +--error ER_PARSE_ERROR +select a as 'x', t1.* as 'with_alias' from t1; +--error ER_PARSE_ERROR +select t1.* as 'with_alias', a as 'x' from t1; +--error ER_PARSE_ERROR +select a as 'x', t1.* as 'with_alias', b as 'x' from t1; +--error ER_PARSE_ERROR +select (select d from t2 where d > a) as 'x', t1.* as 'with_alias' from t1; +--error ER_PARSE_ERROR +select t1.* as 'with_alias', (select a from t2 where d > a) as 'x' from t1; + +# some more subquery +--error ER_PARSE_ERROR +select (select t2.* as 'x' from t2) from t1; +--error ER_PARSE_ERROR +select a, (select t2.* as 'x' from t2) from t1; +--error ER_PARSE_ERROR +select t1.*, (select t2.* as 'x' from t2) from t1; + +# insert +--error ER_PARSE_ERROR +insert into t3 select t1.* as 'with_alias' from t1; +--error ER_PARSE_ERROR +insert into t3 select t2.* as 'with_alias', 1, 2 from t2; +--error ER_PARSE_ERROR +insert into t3 select t2.* as 'with_alias', d as 'x', d as 'z' from t2; +--error ER_PARSE_ERROR +insert into t3 select t2.*, t2.* as 'with_alias', 3 from t2; + +# create +--error ER_PARSE_ERROR +create table t3 select t1.* as 'with_alias' from t1; +--error ER_PARSE_ERROR +create table t3 select t2.* as 'with_alias', 1, 2 from t2; +--error ER_PARSE_ERROR +create table t3 select t2.* as 'with_alias', d as 'x', d as 'z' from t2; +--error ER_PARSE_ERROR +create table t3 select t2.*, t2.* as 'with_alias', 3 from t2; + +# +# Valid queries without alias on wild +# (proof the above fail due to invalid aliasing) +# + +select t1.* from t1; +select t2.* from t2; +select t1.*, t1.* from t1; +select t1.*, a, t1.* from t1; + +# other fields without alias +select a, t1.* from t1; +select t1.*, a from t1; +select a, t1.*, b from t1; +select (select d from t2 where d > a), t1.* from t1; +select t1.*, (select a from t2 where d > a) from t1; + +# other fields with alias +select a as 'x', t1.* from t1; +select t1.*, a as 'x' from t1; +select a as 'x', t1.*, b as 'x' from t1; +select (select d from t2 where d > a) as 'x', t1.* from t1; +select t1.*, (select a from t2 where d > a) as 'x' from t1; + +# some more subquery +select (select t2.* from t2) from t1; +select a, (select t2.* from t2) from t1; +select t1.*, (select t2.* from t2) from t1; + +# insert +insert into t3 select t1.* from t1; +insert into t3 select t2.*, 1, 2 from t2; +insert into t3 select t2.*, d as 'x', d as 'z' from t2; +insert into t3 select t2.*, t2.*, 3 from t2; + +# create +create table t4 select t1.* from t1; +drop table t4; +create table t4 select t2.*, 1, 2 from t2; +drop table t4; +create table t4 select t2.*, d as 'x', d as 'z' from t2; +drop table t4; + +# end +drop table t1,t2,t3; + +# End of 5.2 tests diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index d4b422da30d..c85a4290a44 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1193,7 +1193,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %type literal text_literal insert_ident order_ident - simple_ident select_item2 expr opt_expr opt_else sum_expr in_sum_expr + simple_ident expr opt_expr opt_else sum_expr in_sum_expr variable variable_aux bool_pri predicate bit_expr table_wild simple_expr udf_expr @@ -6891,7 +6891,14 @@ select_item_list: ; select_item: - remember_name select_item2 remember_end select_alias + remember_name table_wild remember_end + { + THD *thd= YYTHD; + + if (add_item_to_list(thd, $2)) + MYSQL_YYABORT; + } + | remember_name expr remember_end select_alias { THD *thd= YYTHD; DBUG_ASSERT($1 < $3); @@ -6928,11 +6935,6 @@ remember_end: } ; -select_item2: - table_wild { $$=$1; /* table.* */ } - | expr { $$=$1; } - ; - select_alias: /* empty */ { $$=null_lex_str;} | AS ident { $$=$2; } From 891dffa32293a1700bb940f55825488d86915010 Mon Sep 17 00:00:00 2001 From: Davi Arnaut Date: Tue, 10 Nov 2009 18:51:14 -0200 Subject: [PATCH 23/40] Backport of Bug#45767 to mysql-next-mr ------------------------------------------------------------ revno: 3405 revision-id: davi.arnaut@sun.com-20090626124624-m4wolyo5193j4cu7 parent: luis.soares@sun.com-20090626113019-1j4mn1jos480u9f3 committer: Davi Arnaut branch nick: mysql-pe timestamp: Fri 2009-06-26 09:46:24 -0300 message: Bug#45767: deprecate/remove Field::pack_key, Field::unpack_key, Field::pack_cmp Remove unused and dead code. Parts of the patch contributed by Zardosht Kasheff --- sql/field.cc | 350 ---------------------------------------------- sql/field.h | 41 ------ sql/sql_parse.cc | 48 ------- sql/sql_string.cc | 16 --- 4 files changed, 455 deletions(-) diff --git a/sql/field.cc b/sql/field.cc index b1790cb6934..20b4f669e92 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -6916,86 +6916,6 @@ int Field_string::do_save_field_metadata(uchar *metadata_ptr) } -/* - Compare two packed keys - - SYNOPSIS - pack_cmp() - a New key - b Original key - length Key length - insert_or_update 1 if this is an insert or update - - RETURN - < 0 a < b - 0 a = b - > 0 a > b -*/ - -int Field_string::pack_cmp(const uchar *a, const uchar *b, uint length, - my_bool insert_or_update) -{ - uint a_length, b_length; - if (length > 255) - { - a_length= uint2korr(a); - b_length= uint2korr(b); - a+= 2; - b+= 2; - } - else - { - a_length= (uint) *a++; - b_length= (uint) *b++; - } - return field_charset->coll->strnncollsp(field_charset, - a, a_length, - b, b_length, - insert_or_update); -} - - -/** - Compare a packed key against row. - - @param key Original key - @param length Key length. (May be less than field length) - @param insert_or_update 1 if this is an insert or update - - @return - < 0 row < key - @return - 0 row = key - @return - > 0 row > key -*/ - -int Field_string::pack_cmp(const uchar *key, uint length, - my_bool insert_or_update) -{ - uint row_length, local_key_length; - uchar *end; - if (length > 255) - { - local_key_length= uint2korr(key); - key+= 2; - } - else - local_key_length= (uint) *key++; - - /* Only use 'length' of key, not field_length */ - end= ptr + length; - while (end > ptr && end[-1] == ' ') - end--; - row_length= (uint) (end - ptr); - - return field_charset->coll->strnncollsp(field_charset, - ptr, row_length, - key, local_key_length, - insert_or_update); -} - - uint Field_string::packed_col_length(const uchar *data_ptr, uint length) { if (length > 255) @@ -7355,90 +7275,6 @@ uchar *Field_varstring::pack(uchar *to, const uchar *from, } -uchar * -Field_varstring::pack_key(uchar *to, const uchar *key, uint max_length, - bool low_byte_first __attribute__((unused))) -{ - uint length= length_bytes == 1 ? (uint) *key : uint2korr(key); - uint local_char_length= ((field_charset->mbmaxlen > 1) ? - max_length/field_charset->mbmaxlen : max_length); - key+= length_bytes; - if (length > local_char_length) - { - local_char_length= my_charpos(field_charset, key, key+length, - local_char_length); - set_if_smaller(length, local_char_length); - } - *to++= (char) (length & 255); - if (max_length > 255) - *to++= (char) (length >> 8); - if (length) - memcpy(to, key, length); - return to+length; -} - - -/** - Unpack a key into a record buffer. - - A VARCHAR key has a maximum size of 64K-1. - In its packed form, the length field is one or two bytes long, - depending on 'max_length'. - - @param to Pointer into the record buffer. - @param key Pointer to the packed key. - @param max_length Key length limit from key description. - - @return - Pointer to end of 'key' (To the next key part if multi-segment key) -*/ - -const uchar * -Field_varstring::unpack_key(uchar *to, const uchar *key, uint max_length, - bool low_byte_first __attribute__((unused))) -{ - /* get length of the blob key */ - uint32 length= *key++; - if (max_length > 255) - length+= (*key++) << 8; - - /* put the length into the record buffer */ - if (length_bytes == 1) - *ptr= (uchar) length; - else - int2store(ptr, length); - memcpy(ptr + length_bytes, key, length); - return key + length; -} - -/** - Create a packed key that will be used for storage in the index tree. - - @param to Store packed key segment here - @param from Key segment (as given to index_read()) - @param max_length Max length of key - - @return - end of key storage -*/ - -uchar * -Field_varstring::pack_key_from_key_image(uchar *to, const uchar *from, uint max_length, - bool low_byte_first __attribute__((unused))) -{ - /* Key length is always stored as 2 bytes */ - uint length= uint2korr(from); - if (length > max_length) - length= max_length; - *to++= (char) (length & 255); - if (max_length > 255) - *to++= (char) (length >> 8); - if (length) - memcpy(to, from+HA_KEY_BLOB_LENGTH, length); - return to+length; -} - - /** Unpack a varstring field from row data. @@ -7481,59 +7317,6 @@ Field_varstring::unpack(uchar *to, const uchar *from, } -int Field_varstring::pack_cmp(const uchar *a, const uchar *b, - uint key_length_arg, - my_bool insert_or_update) -{ - uint a_length, b_length; - if (key_length_arg > 255) - { - a_length=uint2korr(a); a+= 2; - b_length=uint2korr(b); b+= 2; - } - else - { - a_length= (uint) *a++; - b_length= (uint) *b++; - } - return field_charset->coll->strnncollsp(field_charset, - a, a_length, - b, b_length, - insert_or_update); -} - - -int Field_varstring::pack_cmp(const uchar *b, uint key_length_arg, - my_bool insert_or_update) -{ - uchar *a= ptr+ length_bytes; - uint a_length= length_bytes == 1 ? (uint) *ptr : uint2korr(ptr); - uint b_length; - uint local_char_length= ((field_charset->mbmaxlen > 1) ? - key_length_arg / field_charset->mbmaxlen : - key_length_arg); - - if (key_length_arg > 255) - { - b_length=uint2korr(b); b+= HA_KEY_BLOB_LENGTH; - } - else - b_length= (uint) *b++; - - if (a_length > local_char_length) - { - local_char_length= my_charpos(field_charset, a, a+a_length, - local_char_length); - set_if_smaller(a_length, local_char_length); - } - - return field_charset->coll->strnncollsp(field_charset, - a, a_length, - b, b_length, - insert_or_update); -} - - uint Field_varstring::packed_col_length(const uchar *data_ptr, uint length) { if (length > 255) @@ -8232,139 +8015,6 @@ const uchar *Field_blob::unpack(uchar *to, DBUG_RETURN(from + master_packlength + length); } -/* Keys for blobs are like keys on varchars */ - -int Field_blob::pack_cmp(const uchar *a, const uchar *b, uint key_length_arg, - my_bool insert_or_update) -{ - uint a_length, b_length; - if (key_length_arg > 255) - { - a_length=uint2korr(a); a+=2; - b_length=uint2korr(b); b+=2; - } - else - { - a_length= (uint) *a++; - b_length= (uint) *b++; - } - return field_charset->coll->strnncollsp(field_charset, - a, a_length, - b, b_length, - insert_or_update); -} - - -int Field_blob::pack_cmp(const uchar *b, uint key_length_arg, - my_bool insert_or_update) -{ - uchar *a; - uint a_length, b_length; - memcpy_fixed(&a,ptr+packlength,sizeof(char*)); - if (!a) - return key_length_arg > 0 ? -1 : 0; - - a_length= get_length(ptr); - if (key_length_arg > 255) - { - b_length= uint2korr(b); b+=2; - } - else - b_length= (uint) *b++; - return field_charset->coll->strnncollsp(field_charset, - a, a_length, - b, b_length, - insert_or_update); -} - -/** Create a packed key that will be used for storage from a MySQL row. */ - -uchar * -Field_blob::pack_key(uchar *to, const uchar *from, uint max_length, - bool low_byte_first __attribute__((unused))) -{ - uchar *save= ptr; - ptr= (uchar*) from; - uint32 length=get_length(); // Length of from string - uint local_char_length= ((field_charset->mbmaxlen > 1) ? - max_length/field_charset->mbmaxlen : max_length); - if (length) - get_ptr((uchar**) &from); - if (length > local_char_length) - local_char_length= my_charpos(field_charset, from, from+length, - local_char_length); - set_if_smaller(length, local_char_length); - *to++= (uchar) length; - if (max_length > 255) // 2 byte length - *to++= (uchar) (length >> 8); - memcpy(to, from, length); - ptr=save; // Restore org row pointer - return to+length; -} - - -/** - Unpack a blob key into a record buffer. - - A blob key has a maximum size of 64K-1. - In its packed form, the length field is one or two bytes long, - depending on 'max_length'. - Depending on the maximum length of a blob, its length field is - put into 1 to 4 bytes. This is a property of the blob object, - described by 'packlength'. - Blobs are internally stored apart from the record buffer, which - contains a pointer to the blob buffer. - - - @param to Pointer into the record buffer. - @param from Pointer to the packed key. - @param max_length Key length limit from key description. - - @return - Pointer into 'from' past the last byte copied from packed key. -*/ - -const uchar * -Field_blob::unpack_key(uchar *to, const uchar *from, uint max_length, - bool low_byte_first __attribute__((unused))) -{ - /* get length of the blob key */ - uint32 length= *from++; - if (max_length > 255) - length+= *from++ << 8; - - /* put the length into the record buffer */ - put_length(to, length); - - /* put the address of the blob buffer or NULL */ - if (length) - memcpy_fixed(to + packlength, &from, sizeof(from)); - else - bzero(to + packlength, sizeof(from)); - - /* point to first byte of next field in 'from' */ - return from + length; -} - - -/** Create a packed key that will be used for storage from a MySQL key. */ - -uchar * -Field_blob::pack_key_from_key_image(uchar *to, const uchar *from, uint max_length, - bool low_byte_first __attribute__((unused))) -{ - uint length=uint2korr(from); - if (length > max_length) - length=max_length; - *to++= (char) (length & 255); - if (max_length > 255) - *to++= (char) (length >> 8); - if (length) - memcpy(to, from+HA_KEY_BLOB_LENGTH, length); - return to+length; -} - - uint Field_blob::packed_col_length(const uchar *data_ptr, uint length) { if (length > 255) diff --git a/sql/field.h b/sql/field.h index 9ad00e0dab3..16e1078e178 100644 --- a/sql/field.h +++ b/sql/field.h @@ -410,32 +410,11 @@ public: DBUG_RETURN(result); } - virtual uchar *pack_key(uchar* to, const uchar *from, - uint max_length, bool low_byte_first) - { - return pack(to, from, max_length, low_byte_first); - } - virtual uchar *pack_key_from_key_image(uchar* to, const uchar *from, - uint max_length, bool low_byte_first) - { - return pack(to, from, max_length, low_byte_first); - } - virtual const uchar *unpack_key(uchar* to, const uchar *from, - uint max_length, bool low_byte_first) - { - return unpack(to, from, max_length, low_byte_first); - } virtual uint packed_col_length(const uchar *to, uint length) { return length;} virtual uint max_packed_col_length(uint max_length) { return max_length;} - virtual int pack_cmp(const uchar *a,const uchar *b, uint key_length_arg, - my_bool insert_or_update) - { return cmp(a,b); } - virtual int pack_cmp(const uchar *b, uint key_length_arg, - my_bool insert_or_update) - { return cmp(ptr,b); } uint offset(uchar *record) { return (uint) (ptr - record); @@ -1507,9 +1486,6 @@ public: int compatible_field_size(uint field_metadata, const Relay_log_info *rli); uint row_pack_length() { return (field_length + 1); } - int pack_cmp(const uchar *a,const uchar *b,uint key_length, - my_bool insert_or_update); - int pack_cmp(const uchar *b,uint key_length,my_bool insert_or_update); uint packed_col_length(const uchar *to, uint length); uint max_packed_col_length(uint max_length); uint size_of() const { return sizeof(*this); } @@ -1583,16 +1559,8 @@ public: void sql_type(String &str) const; virtual uchar *pack(uchar *to, const uchar *from, uint max_length, bool low_byte_first); - uchar *pack_key(uchar *to, const uchar *from, uint max_length, bool low_byte_first); - uchar *pack_key_from_key_image(uchar* to, const uchar *from, - uint max_length, bool low_byte_first); virtual const uchar *unpack(uchar* to, const uchar *from, uint param_data, bool low_byte_first); - const uchar *unpack_key(uchar* to, const uchar *from, - uint max_length, bool low_byte_first); - int pack_cmp(const uchar *a, const uchar *b, uint key_length, - my_bool insert_or_update); - int pack_cmp(const uchar *b, uint key_length,my_bool insert_or_update); int cmp_binary(const uchar *a,const uchar *b, uint32 max_length=~0L); int key_cmp(const uchar *,const uchar*); int key_cmp(const uchar *str, uint length); @@ -1768,17 +1736,8 @@ public: } virtual uchar *pack(uchar *to, const uchar *from, uint max_length, bool low_byte_first); - uchar *pack_key(uchar *to, const uchar *from, - uint max_length, bool low_byte_first); - uchar *pack_key_from_key_image(uchar* to, const uchar *from, - uint max_length, bool low_byte_first); virtual const uchar *unpack(uchar *to, const uchar *from, uint param_data, bool low_byte_first); - const uchar *unpack_key(uchar* to, const uchar *from, - uint max_length, bool low_byte_first); - int pack_cmp(const uchar *a, const uchar *b, uint key_length, - my_bool insert_or_update); - int pack_cmp(const uchar *b, uint key_length,my_bool insert_or_update); uint packed_col_length(const uchar *col_ptr, uint length); uint max_packed_col_length(uint max_length); void free() { value.free(); } diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index a22daeabf0d..4aa37d82bf0 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1362,54 +1362,6 @@ bool dispatch_command(enum enum_server_command command, THD *thd, thd->stmt_da->disable_status(); // Don't send anything back error=TRUE; // End server break; - -#ifdef REMOVED - case COM_CREATE_DB: // QQ: To be removed - { - LEX_STRING db, alias; - HA_CREATE_INFO create_info; - - status_var_increment(thd->status_var.com_stat[SQLCOM_CREATE_DB]); - if (thd->make_lex_string(&db, packet, packet_length, FALSE) || - thd->make_lex_string(&alias, db.str, db.length, FALSE) || - check_db_name(&db)) - { - my_error(ER_WRONG_DB_NAME, MYF(0), db.str ? db.str : "NULL"); - break; - } - if (check_access(thd, CREATE_ACL, db.str , 0, 1, 0, - is_schema_db(db.str))) - break; - general_log_print(thd, command, "%.*s", db.length, db.str); - bzero(&create_info, sizeof(create_info)); - mysql_create_db(thd, (lower_case_table_names == 2 ? alias.str : db.str), - &create_info, 0); - break; - } - case COM_DROP_DB: // QQ: To be removed - { - status_var_increment(thd->status_var.com_stat[SQLCOM_DROP_DB]); - LEX_STRING db; - - if (thd->make_lex_string(&db, packet, packet_length, FALSE) || - check_db_name(&db)) - { - my_error(ER_WRONG_DB_NAME, MYF(0), db.str ? db.str : "NULL"); - break; - } - if (check_access(thd, DROP_ACL, db.str, 0, 1, 0, is_schema_db(db.str))) - break; - if (thd->locked_tables || thd->active_transaction()) - { - my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, - ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); - break; - } - general_log_write(thd, command, "%.*s", db.length, db.str); - mysql_rm_db(thd, db.str, 0, 0); - break; - } -#endif #ifndef EMBEDDED_LIBRARY case COM_BINLOG_DUMP: { diff --git a/sql/sql_string.cc b/sql/sql_string.cc index 593450cacd5..491e51e8a92 100644 --- a/sql/sql_string.cc +++ b/sql/sql_string.cc @@ -499,22 +499,6 @@ bool String::append(const char *s,uint32 arg_length, CHARSET_INFO *cs) return FALSE; } - -#ifdef TO_BE_REMOVED -bool String::append(FILE* file, uint32 arg_length, myf my_flags) -{ - if (realloc(str_length+arg_length)) - return TRUE; - if (my_fread(file, (uchar*) Ptr + str_length, arg_length, my_flags)) - { - shrink(str_length); - return TRUE; - } - str_length+=arg_length; - return FALSE; -} -#endif - bool String::append(IO_CACHE* file, uint32 arg_length) { if (realloc(str_length+arg_length)) From 1c3c72ce178f8a0139066d90836e2b9e11324723 Mon Sep 17 00:00:00 2001 From: Davi Arnaut Date: Tue, 10 Nov 2009 22:39:06 -0200 Subject: [PATCH 24/40] The FIONREAD macro could expand to a expression which is invalid to preporcessor conditionals. Instead use the autoconf generated macro to test the presence. --- vio/viosocket.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vio/viosocket.c b/vio/viosocket.c index 055112f944e..aaaa8ceba97 100644 --- a/vio/viosocket.c +++ b/vio/viosocket.c @@ -372,6 +372,7 @@ void vio_in_addr(Vio *vio, struct in_addr *in) static my_bool socket_poll_read(my_socket sd, uint timeout) { #ifdef __WIN__ + int res; my_socket fd= sd; fd_set readfds, errorfds; struct timeval tm; @@ -424,7 +425,7 @@ static my_bool socket_peek_read(Vio *vio, uint *bytes) return TRUE; *bytes= len; return FALSE; -#elif FIONREAD +#elif FIONREAD_IN_SYS_IOCTL int len; if (ioctl(vio->sd, FIONREAD, &len) < 0) return TRUE; From 3c3b11c3b876b36db7243d53390d02e9ffb3fad1 Mon Sep 17 00:00:00 2001 From: Magne Mahre Date: Wed, 11 Nov 2009 17:03:02 +0100 Subject: [PATCH 25/40] Bug #14637: trim trailing spaces processes data only byte wise (From: gkodinov) Use and int * where possible to scan for trailing space in a string instead of always iterating char-by-char. Using the attached benchmark file on a 32 bit Intel Core 2 Duo CPU I've got 43485 ms run with the fix compared to 44373 without it. Backported to 5.6.0 (next-mr-runtime) 6.0-codebase revid: 2476.1362.1 include/m_string.h: scan for space through ints strings/ctype-bin.c: scan for space through ints strings/ctype-latin1.c: scan for space through ints strings/ctype-mb.c: scan for space through ints strings/ctype-simple.c: scan for space through ints --- include/m_string.h | 70 ++++++++++++++++++++++++++++++++++++++++++ strings/ctype-bin.c | 5 +-- strings/ctype-latin1.c | 5 ++- strings/ctype-mb.c | 5 +-- strings/ctype-simple.c | 10 +++--- 5 files changed, 78 insertions(+), 17 deletions(-) diff --git a/include/m_string.h b/include/m_string.h index c24bfd7aa6c..264bf6d545d 100644 --- a/include/m_string.h +++ b/include/m_string.h @@ -266,4 +266,74 @@ typedef struct st_mysql_lex_string LEX_STRING; #define USTRING_WITH_LEN(X) ((uchar*) X), ((size_t) (sizeof(X) - 1)) #define C_STRING_WITH_LEN(X) ((char *) (X)), ((size_t) (sizeof(X) - 1)) +/* SPACE_INT is a word that contains only spaces */ +#if SIZEOF_INT == 4 +#define SPACE_INT 0x20202020 +#elif SIZEOF_INT == 8 +#define SPACE_INT 0x2020202020202020 +#else +#error define the appropriate constant for a word full of spaces +#endif + +/** + Skip trailing space. + + On most systems reading memory in larger chunks (ideally equal to the size of + the chinks that the machine physically reads from memory) causes fewer memory + access loops and hence increased performance. + This is why the 'int' type is used : it's closest to that (according to how + it's defined in C). + So when we determine the amount of whitespace at the end of a string we do + the following : + 1. We divide the string into 3 zones : + a) from the start of the string (__start) to the first multiple + of sizeof(int) (__start_words) + b) from the end of the string (__end) to the last multiple of sizeof(int) + (__end_words) + c) a zone that is aligned to sizeof(int) and can be safely accessed + through an int * + 2. We start comparing backwards from (c) char-by-char. If all we find is + space then we continue + 3. If there are elements in zone (b) we compare them as unsigned ints to a + int mask (SPACE_INT) consisting of all spaces + 4. Finally we compare the remaining part (a) of the string char by char. + This covers for the last non-space unsigned int from 3. (if any) + + This algorithm works well for relatively larger strings, but it will slow + the things down for smaller strings (because of the additional calculations + and checks compared to the naive method). Thus the barrier of length 20 + is added. + + @param ptr pointer to the input string + @param len the length of the string + @return the last non-space character +*/ + +static inline const uchar *skip_trailing_space(const uchar *ptr,size_t len) +{ + const uchar *start= ptr; + const uchar *end= ptr + len; + + if (len > 20) + { + const uchar *end_words= (const uchar *) + (((intptr)end) / SIZEOF_INT * SIZEOF_INT); + const uchar *start_words= (const uchar *) + ((((intptr)start) + SIZEOF_INT - 1) / SIZEOF_INT * SIZEOF_INT); + + DBUG_ASSERT(((intptr)start) >= SIZEOF_INT); + if (end_words > start) + { + while (end > end_words && end[-1] == 0x20) + end--; + if (end[-1] == 0x20 && start_words < end_words) + while (end > start_words && ((unsigned *)end)[-1] == SPACE_INT) + end -= SIZEOF_INT; + } + } + while (end > start && end[-1] == 0x20) + end--; + return (end); +} + #endif diff --git a/strings/ctype-bin.c b/strings/ctype-bin.c index 95d97af2bfb..3b6a977e47c 100644 --- a/strings/ctype-bin.c +++ b/strings/ctype-bin.c @@ -278,14 +278,11 @@ void my_hash_sort_8bit_bin(CHARSET_INFO *cs __attribute__((unused)), { const uchar *pos = key; - key+= len; - /* Remove trailing spaces. We have to do this to be able to compare 'A ' and 'A' as identical */ - while (key > pos && key[-1] == ' ') - key--; + key= skip_trailing_space(key, len); for (; pos < (uchar*) key ; pos++) { diff --git a/strings/ctype-latin1.c b/strings/ctype-latin1.c index e5333c4101b..dbd91c09637 100644 --- a/strings/ctype-latin1.c +++ b/strings/ctype-latin1.c @@ -678,13 +678,12 @@ void my_hash_sort_latin1_de(CHARSET_INFO *cs __attribute__((unused)), const uchar *key, size_t len, ulong *nr1, ulong *nr2) { - const uchar *end= key+len; + const uchar *end; /* Remove end space. We have to do this to be able to compare 'AE' and 'Ä' as identical */ - while (end > key && end[-1] == ' ') - end--; + end= skip_trailing_space(key, len); for (; key < end ; key++) { diff --git a/strings/ctype-mb.c b/strings/ctype-mb.c index 903811e2ab9..b0f7c297260 100644 --- a/strings/ctype-mb.c +++ b/strings/ctype-mb.c @@ -469,14 +469,11 @@ static void my_hash_sort_mb_bin(CHARSET_INFO *cs __attribute__((unused)), { const uchar *pos = key; - key+= len; - /* Remove trailing spaces. We have to do this to be able to compare 'A ' and 'A' as identical */ - while (key > pos && key[-1] == ' ') - key--; + key= skip_trailing_space(key, len); for (; pos < (uchar*) key ; pos++) { diff --git a/strings/ctype-simple.c b/strings/ctype-simple.c index 4f3aaa6f668..6ff8c83dcf7 100644 --- a/strings/ctype-simple.c +++ b/strings/ctype-simple.c @@ -304,14 +304,13 @@ void my_hash_sort_simple(CHARSET_INFO *cs, ulong *nr1, ulong *nr2) { register uchar *sort_order=cs->sort_order; - const uchar *end= key + len; + const uchar *end; /* Remove end space. We have to do this to be able to compare 'A ' and 'A' as identical */ - while (end > key && end[-1] == ' ') - end--; + end= skip_trailing_space(key, len); for (; key < (uchar*) end ; key++) { @@ -1165,9 +1164,8 @@ size_t my_well_formed_len_8bit(CHARSET_INFO *cs __attribute__((unused)), size_t my_lengthsp_8bit(CHARSET_INFO *cs __attribute__((unused)), const char *ptr, size_t length) { - const char *end= ptr+length; - while (end > ptr && end[-1] == ' ') - end--; + const char *end; + end= (const char *) skip_trailing_space((const uchar *)ptr, length); return (size_t) (end-ptr); } From fa08bae760431b2c325bbc6a41cf706b1e76a2b3 Mon Sep 17 00:00:00 2001 From: Magne Mahre Date: Wed, 11 Nov 2009 17:17:58 +0100 Subject: [PATCH 26/40] Bug #5724 'mysqladmin password' should be allowed to prompt for password Add support for being prompted for new passwords by mysqladmin instead of specifying them on the command line. (Bug #5724, patch by Harrison Fisk) --- client/mysqladmin.cc | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/client/mysqladmin.cc b/client/mysqladmin.cc index a3eee14e0d1..00a83c60d25 100644 --- a/client/mysqladmin.cc +++ b/client/mysqladmin.cc @@ -900,23 +900,38 @@ static int execute_commands(MYSQL *mysql,int argc, char **argv) { char buff[128],crypted_pw[64]; time_t start_time; + char *typed_password= NULL, *verified= NULL; /* Do initialization the same way as we do in mysqld */ start_time=time((time_t*) 0); randominit(&rand_st,(ulong) start_time,(ulong) start_time/2); - if (argc < 2) + if (argc < 1) { my_printf_error(0, "Too few arguments to change password", error_flags); return 1; } - if (argv[1][0]) + else if (argc == 1) + { + /* prompt for password */ + typed_password= get_tty_password("New password: "); + verified= get_tty_password("Confirm new password: "); + if (strcmp(typed_password, verified) != 0) + { + my_printf_error(0,"Passwords don't match",MYF(ME_BELL)); + return -1; + } + } + else + typed_password= argv[1]; + + if (typed_password[0]) { - char *pw= argv[1]; bool old= (find_type(argv[0], &command_typelib, 2) == ADMIN_OLD_PASSWORD); #ifdef __WIN__ - uint pw_len= (uint) strlen(pw); - if (pw_len > 1 && pw[0] == '\'' && pw[pw_len-1] == '\'') + uint pw_len= strlen(typed_password); + if (pw_len > 1 && typed_password[0] == '\'' && + typed_password[pw_len-1] == '\'') printf("Warning: single quotes were not trimmed from the password by" " your command\nline client, as you might have expected.\n"); #endif @@ -954,9 +969,9 @@ static int execute_commands(MYSQL *mysql,int argc, char **argv) } } if (old) - make_scrambled_password_323(crypted_pw, pw); + make_scrambled_password_323(crypted_pw, typed_password); else - make_scrambled_password(crypted_pw, pw); + make_scrambled_password(crypted_pw, typed_password); } else crypted_pw[0]=0; /* No password */ @@ -991,6 +1006,12 @@ static int execute_commands(MYSQL *mysql,int argc, char **argv) return -1; } } + /* free up memory from prompted password */ + if (typed_password != argv[1]) + { + my_free(typed_password,MYF(MY_ALLOW_ZERO_PTR)); + my_free(verified,MYF(MY_ALLOW_ZERO_PTR)); + } argc--; argv++; break; } @@ -1082,8 +1103,8 @@ static void usage(void) kill id,id,... Kill mysql threads"); #if MYSQL_VERSION_ID >= 32200 puts("\ - password new-password Change old password to new-password, MySQL 4.1 hashing.\n\ - old-password new-password Change old password to new-password in old format.\n"); + password [new-password] Change old password to new-password in current format\n\ + old-password [new-password] Change old password to new-password in old format"); #endif puts("\ ping Check if mysqld is alive\n\ From 272b9ad5de9f12a3d571fb96b89c42368b633e1c Mon Sep 17 00:00:00 2001 From: Magne Mahre Date: Wed, 11 Nov 2009 17:22:27 +0100 Subject: [PATCH 27/40] Return type fix (strlen returns size_t) --- client/mysqladmin.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/mysqladmin.cc b/client/mysqladmin.cc index 00a83c60d25..500a890a9ab 100644 --- a/client/mysqladmin.cc +++ b/client/mysqladmin.cc @@ -929,7 +929,7 @@ static int execute_commands(MYSQL *mysql,int argc, char **argv) bool old= (find_type(argv[0], &command_typelib, 2) == ADMIN_OLD_PASSWORD); #ifdef __WIN__ - uint pw_len= strlen(typed_password); + size_t pw_len= strlen(typed_password); if (pw_len > 1 && typed_password[0] == '\'' && typed_password[pw_len-1] == '\'') printf("Warning: single quotes were not trimmed from the password by" From cc87cb9fbf1a003c4591f250761bf6c510ae2b3b Mon Sep 17 00:00:00 2001 From: Magne Mahre Date: Wed, 11 Nov 2009 19:54:57 +0100 Subject: [PATCH 28/40] Fix of incorrect casting for large sizes --- include/m_string.h | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/include/m_string.h b/include/m_string.h index 264bf6d545d..3bbb7156bf1 100644 --- a/include/m_string.h +++ b/include/m_string.h @@ -311,18 +311,17 @@ typedef struct st_mysql_lex_string LEX_STRING; static inline const uchar *skip_trailing_space(const uchar *ptr,size_t len) { - const uchar *start= ptr; const uchar *end= ptr + len; if (len > 20) { - const uchar *end_words= (const uchar *) - (((intptr)end) / SIZEOF_INT * SIZEOF_INT); - const uchar *start_words= (const uchar *) - ((((intptr)start) + SIZEOF_INT - 1) / SIZEOF_INT * SIZEOF_INT); + const uchar *end_words= (const uchar *)(intptr) + (((ulonglong)(intptr)end) / SIZEOF_INT * SIZEOF_INT); + const uchar *start_words= (const uchar *)(intptr) + ((((ulonglong)(intptr)ptr) + SIZEOF_INT - 1) / SIZEOF_INT * SIZEOF_INT); - DBUG_ASSERT(((intptr)start) >= SIZEOF_INT); - if (end_words > start) + DBUG_ASSERT(((ulonglong)(intptr)ptr) >= SIZEOF_INT); + if (end_words > ptr) { while (end > end_words && end[-1] == 0x20) end--; @@ -331,7 +330,7 @@ static inline const uchar *skip_trailing_space(const uchar *ptr,size_t len) end -= SIZEOF_INT; } } - while (end > start && end[-1] == 0x20) + while (end > ptr && end[-1] == 0x20) end--; return (end); } From a176b249129878c2f05ae2b5e9edf8322103fc67 Mon Sep 17 00:00:00 2001 From: Magne Mahre Date: Wed, 11 Nov 2009 20:25:32 +0100 Subject: [PATCH 29/40] Bug #37097 mysqltest add result file format version 2 Add result file format v2 Backport to 5.6.0 --- client/mysqltest.cc | 127 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 120 insertions(+), 7 deletions(-) diff --git a/client/mysqltest.cc b/client/mysqltest.cc index 93e6e58e221..00afadae5c3 100644 --- a/client/mysqltest.cc +++ b/client/mysqltest.cc @@ -75,7 +75,8 @@ enum { OPT_SKIP_SAFEMALLOC=OPT_MAX_CLIENT_OPTION, OPT_PS_PROTOCOL, OPT_SP_PROTOCOL, OPT_CURSOR_PROTOCOL, OPT_VIEW_PROTOCOL, - OPT_MAX_CONNECT_RETRIES, OPT_MARK_PROGRESS, OPT_LOG_DIR, OPT_TAIL_LINES + OPT_MAX_CONNECT_RETRIES, OPT_MARK_PROGRESS, OPT_LOG_DIR, OPT_TAIL_LINES, + OPT_RESULT_FORMAT_VERSION }; static int record= 0, opt_sleep= -1; @@ -85,6 +86,7 @@ const char *opt_logdir= ""; const char *opt_include= 0, *opt_charsets_dir; static int opt_port= 0; static int opt_max_connect_retries; +static int opt_result_format_version; static my_bool opt_compress= 0, silent= 0, verbose= 0; static my_bool debug_info_flag= 0, debug_check_flag= 0; static my_bool tty_password= 0; @@ -281,11 +283,13 @@ enum enum_commands { Q_SEND_QUIT, Q_CHANGE_USER, Q_MKDIR, Q_RMDIR, Q_LIST_FILES, Q_LIST_FILES_WRITE_FILE, Q_LIST_FILES_APPEND_FILE, Q_SEND_SHUTDOWN, Q_SHUTDOWN_SERVER, + Q_RESULT_FORMAT_VERSION, Q_MOVE_FILE, Q_UNKNOWN, /* Unknown command. */ Q_COMMENT, /* Comments, ignored. */ - Q_COMMENT_WITH_COMMAND + Q_COMMENT_WITH_COMMAND, + Q_EMPTY_LINE }; @@ -375,6 +379,7 @@ const char *command_names[]= "list_files_append_file", "send_shutdown", "shutdown_server", + "result_format", "move_file", 0 @@ -2190,6 +2195,59 @@ void var_query_set(VAR *var, const char *query, const char** query_end) } +static void +set_result_format_version(ulong new_version) +{ + switch (new_version){ + case 1: + /* The first format */ + break; + case 2: + /* New format that also writes comments and empty lines + from test file to result */ + break; + default: + die("Version format %lu has not yet been implemented", new_version); + break; + } + opt_result_format_version= new_version; +} + + +/* + Set the result format version to use when generating + the .result file +*/ + +static void +do_result_format_version(struct st_command *command) +{ + long version; + static DYNAMIC_STRING ds_version; + const struct command_arg result_format_args[] = { + "version", ARG_STRING, TRUE, &ds_version, "Version to use", + }; + + DBUG_ENTER("do_result_format_version"); + + check_command_args(command, command->first_argument, + result_format_args, + sizeof(result_format_args)/sizeof(struct command_arg), + ','); + + /* Convert version number to int */ + if (!str2int(ds_version.str, 10, (long) 0, (long) INT_MAX, &version)) + die("Invalid version number: '%s'", ds_version.str); + + set_result_format_version(version); + + dynstr_append(&ds_res, "result_format: "); + dynstr_append_mem(&ds_res, ds_version.str, ds_version.length); + dynstr_append(&ds_res, "\n"); + dynstr_free(&ds_version); +} + + /* Set variable from the result of a field in a query @@ -5265,7 +5323,7 @@ my_bool end_of_query(int c) int read_line(char *buf, int size) { - char c, UNINIT_VAR(last_quote); + char c, UNINIT_VAR(last_quote), last_char= 0; char *p= buf, *buf_end= buf + size - 1; int skip_char= 0; enum {R_NORMAL, R_Q, R_SLASH_IN_Q, @@ -5363,14 +5421,24 @@ int read_line(char *buf, int size) } else if (my_isspace(charset_info, c)) { - /* Skip all space at begining of line */ if (c == '\n') { + if (last_char == '\n') + { + /* Two new lines in a row, return empty line */ + DBUG_PRINT("info", ("Found two new lines in a row")); + *p++= c; + *p= 0; + DBUG_RETURN(0); + } + /* Query hasn't started yet */ start_lineno= cur_file->lineno; DBUG_PRINT("info", ("Query hasn't started yet, start_lineno: %d", start_lineno)); } + + /* Skip all space at begining of line */ skip_char= 1; } else if (end_of_query(c)) @@ -5411,6 +5479,8 @@ int read_line(char *buf, int size) } + last_char= c; + if (!skip_char) { /* Could be a multibyte character */ @@ -5620,9 +5690,10 @@ int read_command(struct st_command** command_ptr) DBUG_RETURN(1); } - convert_to_format_v1(read_command_buf); + if (opt_result_format_version == 1) + convert_to_format_v1(read_command_buf); - DBUG_PRINT("info", ("query: %s", read_command_buf)); + DBUG_PRINT("info", ("query: '%s'", read_command_buf)); if (*p == '#') { command->type= Q_COMMENT; @@ -5632,6 +5703,10 @@ int read_command(struct st_command** command_ptr) command->type= Q_COMMENT_WITH_COMMAND; p+= 2; /* Skip past -- */ } + else if (*p == '\n') + { + command->type= Q_EMPTY_LINE; + } /* Skip leading spaces */ while (*p && my_isspace(charset_info, *p)) @@ -5726,6 +5801,11 @@ static struct my_option my_long_options[] = {"result-file", 'R', "Read/Store result from/in this file.", (uchar**) &result_file_name, (uchar**) &result_file_name, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"result-format-version", OPT_RESULT_FORMAT_VERSION, + "Version of the result file format to use", + (uchar**) &opt_result_format_version, + (uchar**) &opt_result_format_version, 0, + GET_INT, REQUIRED_ARG, 1, 1, 2, 0, 0, 0}, {"server-arg", 'A', "Send option value to embedded server as a parameter.", 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"server-file", 'F', "Read embedded server arguments from file.", @@ -5929,6 +6009,9 @@ get_one_option(int optid, const struct my_option *opt __attribute__((unused)), sf_malloc_quick=1; #endif break; + case OPT_RESULT_FORMAT_VERSION: + set_result_format_version(opt_result_format_version); + break; case 'V': print_version(); exit(0); @@ -7799,6 +7882,7 @@ int main(int argc, char **argv) case Q_MOVE_FILE: do_move_file(command); break; case Q_CHMOD_FILE: do_chmod_file(command); break; case Q_PERL: do_perl(command); break; + case Q_RESULT_FORMAT_VERSION: do_result_format_version(command); break; case Q_DELIMITER: do_delimiter(command); break; @@ -7915,9 +7999,38 @@ int main(int argc, char **argv) do_sync_with_master2(command, 0); break; } - case Q_COMMENT: /* Ignore row */ + case Q_COMMENT: + { command->last_argument= command->end; + + /* Don't output comments in v1 */ + if (opt_result_format_version == 1) + break; + + /* Don't output comments if query logging is off */ + if (disable_query_log) + break; + + /* Write comment's with two starting #'s to result file */ + const char* p= command->query; + if (p && *p == '#' && *(p+1) == '#') + { + dynstr_append_mem(&ds_res, command->query, command->query_len); + dynstr_append(&ds_res, "\n"); + } break; + } + case Q_EMPTY_LINE: + /* Don't output newline in v1 */ + if (opt_result_format_version == 1) + break; + + /* Don't output newline if query logging is off */ + if (disable_query_log) + break; + + dynstr_append(&ds_res, "\n"); + break; case Q_PING: handle_command_error(command, mysql_ping(&cur_con->mysql)); break; From 827cbd2bb52c9ad95f3fdecff2a58126ca57cd85 Mon Sep 17 00:00:00 2001 From: Magne Mahre Date: Wed, 11 Nov 2009 21:43:31 +0100 Subject: [PATCH 30/40] Bug#40700: aclocal warnings for missing cache-id's Just change mysql_foo to mysql_cv_foo for one cache-id variable name. There was only one bad variable name, present in 5.0 and 5.1, but not in the -pe branch. Backported to 5.6.0 (mysql-next-mr-runtime) --- config/ac-macros/misc.m4 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/ac-macros/misc.m4 b/config/ac-macros/misc.m4 index 1eec0e9e18c..996ac62e025 100644 --- a/config/ac-macros/misc.m4 +++ b/config/ac-macros/misc.m4 @@ -601,15 +601,15 @@ dnl --------------------------------------------------------------------------- dnl MYSQL_NEEDS_MYSYS_NEW AC_DEFUN([MYSQL_NEEDS_MYSYS_NEW], -[AC_CACHE_CHECK([needs mysys_new helpers], mysql_use_mysys_new, +[AC_CACHE_CHECK([needs mysys_new helpers], mysql_cv_use_mysys_new, [ AC_LANG_PUSH(C++) AC_TRY_LINK([], [ class A { public: int b; }; A *a=new A; a->b=10; delete a; -], mysql_use_mysys_new=no, mysql_use_mysys_new=yes) +], mysql_cv_use_mysys_new=no, mysql_cv_use_mysys_new=yes) AC_LANG_POP(C++) ]) -if test "$mysql_use_mysys_new" = "yes" +if test "$mysql_cv_use_mysys_new" = "yes" then AC_DEFINE([USE_MYSYS_NEW], [1], [Needs to use mysys_new helpers]) fi From 5128b54c38ae183d18a29e00b318c22046445715 Mon Sep 17 00:00:00 2001 From: Davi Arnaut Date: Fri, 13 Nov 2009 10:56:38 -0200 Subject: [PATCH 31/40] Post-merge fixes for backports. mysql-test/r/sp-error.result: Update test case result. mysql-test/t/dirty_close.test: Dirty close does not work under embedded. mysql-test/t/sp-error.test: Use the specific error number so it won't catch other non-fatal errors. --- mysql-test/r/sp-error.result | 3 ++- mysql-test/t/dirty_close.test | 1 + mysql-test/t/sp-error.test | 6 +++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/mysql-test/r/sp-error.result b/mysql-test/r/sp-error.result index 50b641e8052..c435e6a7fd4 100644 --- a/mysql-test/r/sp-error.result +++ b/mysql-test/r/sp-error.result @@ -1664,7 +1664,8 @@ set @old_recursion_depth = @@max_sp_recursion_depth; set @@max_sp_recursion_depth = 255; create procedure p1(a int) begin -declare continue handler for sqlexception select 'exception'; +declare continue handler for 1436 -- ER_STACK_OVERRUN_NEED_MORE +select 'exception'; call p1(a+1); end| call p1(1); diff --git a/mysql-test/t/dirty_close.test b/mysql-test/t/dirty_close.test index 17a729000e3..e49618170ab 100644 --- a/mysql-test/t/dirty_close.test +++ b/mysql-test/t/dirty_close.test @@ -1,3 +1,4 @@ +--source include/not_embedded.inc # Save the initial number of concurrent sessions --source include/count_sessions.inc diff --git a/mysql-test/t/sp-error.test b/mysql-test/t/sp-error.test index 574002d3d38..5dd4ea80ed0 100644 --- a/mysql-test/t/sp-error.test +++ b/mysql-test/t/sp-error.test @@ -2419,7 +2419,6 @@ end$$ delimiter ;$$ - # # Bug#15192: "fatal errors" are caught by handlers in stored procedures # @@ -2432,11 +2431,12 @@ set @@max_sp_recursion_depth = 255; delimiter |; create procedure p1(a int) begin - declare continue handler for sqlexception select 'exception'; + declare continue handler for 1436 -- ER_STACK_OVERRUN_NEED_MORE + select 'exception'; call p1(a+1); end| delimiter ;| ---error 0,ER_STACK_OVERRUN_NEED_MORE +--error 0,ER_STACK_OVERRUN_NEED_MORE,ER_SP_RECURSION_LIMIT call p1(1); set @@max_sp_recursion_depth = @old_recursion_depth; drop procedure p1; From ce1cd22dca059dd15395bded2767267c320e0376 Mon Sep 17 00:00:00 2001 From: Marc Alff Date: Mon, 16 Nov 2009 14:14:54 -0700 Subject: [PATCH 32/40] New tree for merges --- .bzr-mysql/default.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.bzr-mysql/default.conf b/.bzr-mysql/default.conf index e9ae26ca793..1dcfe53534b 100644 --- a/.bzr-mysql/default.conf +++ b/.bzr-mysql/default.conf @@ -1,4 +1,4 @@ [MYSQL] post_commit_to = "commits@lists.mysql.com" post_push_to = "commits@lists.mysql.com" -tree_name = "mysql-5.5-next-mr" +tree_name = "mysql-5.6-mext-mr-marc" From 382ae222902919f37ae749acada48ee05e3704e6 Mon Sep 17 00:00:00 2001 From: Marc Alff Date: Tue, 17 Nov 2009 17:11:32 -0700 Subject: [PATCH 33/40] WL#2595 kernel-independent atomic operations Backport from 6.0.14 to 5.6.0 Original code from Sergei Golubchik --- configure.in | 90 ++++++----- include/Makefile.am | 6 +- include/atomic/gcc_builtins.h | 5 +- include/atomic/generic-msvc.h | 116 ++++++++++++++ include/atomic/nolock.h | 45 +++--- include/atomic/rwlock.h | 70 +++++++-- include/atomic/solaris.h | 8 +- include/atomic/x86-gcc.h | 28 ++-- include/my_atomic.h | 285 +++++++++++++++++++++++++--------- mysys/my_atomic.c | 33 +++- mysys/my_getncpus.c | 34 ++-- unittest/mysys/Makefile.am | 2 + unittest/mysys/my_atomic-t.c | 155 ++++++------------ unittest/mysys/thr_template.c | 92 +++++++++++ 14 files changed, 681 insertions(+), 288 deletions(-) create mode 100644 include/atomic/generic-msvc.h create mode 100644 unittest/mysys/thr_template.c diff --git a/configure.in b/configure.in index 3910422a0f7..dd2c53a2b52 100644 --- a/configure.in +++ b/configure.in @@ -1750,64 +1750,74 @@ then fi AC_ARG_WITH([atomic-ops], - AC_HELP_STRING([--with-atomic-ops=rwlocks|smp|up], - [Implement atomic operations using pthread rwlocks or atomic CPU - instructions for multi-processor (default) or uniprocessor - configuration]), , [with_atomic_ops=smp]) + AS_HELP_STRING([--with-atomic-ops=rwlocks|smp|up], + [Implement atomic operations using pthread rwlocks or atomic CPU + instructions for multi-processor or uniprocessor + configuration. By default gcc built-in sync functions are used, + if available and 'smp' configuration otherwise.])) case "$with_atomic_ops" in "up") AC_DEFINE([MY_ATOMIC_MODE_DUMMY], [1], [Assume single-CPU mode, no concurrency]) ;; "rwlocks") AC_DEFINE([MY_ATOMIC_MODE_RWLOCKS], [1], [Use pthread rwlocks for atomic ops]) ;; "smp") ;; + "") + ;; *) AC_MSG_ERROR(["$with_atomic_ops" is not a valid value for --with-atomic-ops]) ;; esac AC_CACHE_CHECK([whether the compiler provides atomic builtins], - [mysql_cv_gcc_atomic_builtins], [AC_TRY_RUN([ - int main() - { - int foo= -10; int bar= 10; - if (!__sync_fetch_and_add(&foo, bar) || foo) - return -1; - bar= __sync_lock_test_and_set(&foo, bar); - if (bar || foo != 10) - return -1; - bar= __sync_val_compare_and_swap(&bar, foo, 15); - if (bar) - return -1; - return 0; - } -], [mysql_cv_gcc_atomic_builtins=yes], + [mysql_cv_gcc_atomic_builtins], + [AC_RUN_IFELSE( + [AC_LANG_PROGRAM( + [ + ], + [[ + int foo= -10; int bar= 10; + if (!__sync_fetch_and_add(&foo, bar) || foo) + return -1; + bar= __sync_lock_test_and_set(&foo, bar); + if (bar || foo != 10) + return -1; + bar= __sync_val_compare_and_swap(&bar, foo, 15); + if (bar) + return -1; + return 0; + ]] + )], + [mysql_cv_gcc_atomic_builtins=yes], [mysql_cv_gcc_atomic_builtins=no], - [mysql_cv_gcc_atomic_builtins=no])]) - + [mysql_cv_gcc_atomic_builtins=no] +)]) if test "x$mysql_cv_gcc_atomic_builtins" = xyes; then AC_DEFINE(HAVE_GCC_ATOMIC_BUILTINS, 1, [Define to 1 if compiler provides atomic builtins.]) fi AC_CACHE_CHECK([whether the OS provides atomic_* functions like Solaris], - [mysql_cv_solaris_atomic], [AC_TRY_RUN([ -#include -int -main() -{ - int foo = -10; int bar = 10; - if (atomic_add_int_nv((uint_t *)&foo, bar) || foo) - return -1; - bar = atomic_swap_uint((uint_t *)&foo, (uint_t)bar); - if (bar || foo != 10) - return -1; - bar = atomic_cas_uint((uint_t *)&bar, (uint_t)foo, 15); - if (bar) - return -1; - return 0; -} -], [mysql_cv_solaris_atomic=yes], + [mysql_cv_solaris_atomic], + [AC_RUN_IFELSE( + [AC_LANG_PROGRAM( + [ + #include + ] + [[ + int foo = -10; int bar = 10; + if (atomic_add_int_nv((uint_t *)&foo, bar) || foo) + return -1; + bar = atomic_swap_uint((uint_t *)&foo, (uint_t)bar); + if (bar || foo != 10) + return -1; + bar = atomic_cas_uint((uint_t *)&bar, (uint_t)foo, 15); + if (bar) + return -1; + return 0; + ]] + )], + [mysql_cv_solaris_atomic=yes], [mysql_cv_solaris_atomic=no], - [mysql_cv_solaris_atomic=no])]) - + [mysql_cv_solaris_atomic=no] +)]) if test "x$mysql_cv_solaris_atomic" = xyes; then AC_DEFINE(HAVE_SOLARIS_ATOMIC, 1, [Define to 1 if OS provides atomic_* functions like Solaris.]) diff --git a/include/Makefile.am b/include/Makefile.am index 0a7a35bd6fa..e20b08afe35 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -39,9 +39,9 @@ noinst_HEADERS = config-win.h config-netware.h my_bit.h \ thr_lock.h t_ctype.h violite.h my_md5.h base64.h \ my_handler.h my_time.h service_versions.h \ my_vle.h my_user.h my_atomic.h atomic/nolock.h \ - atomic/rwlock.h atomic/x86-gcc.h atomic/x86-msvc.h \ - atomic/solaris.h \ - atomic/gcc_builtins.h my_libwrap.h my_stacktrace.h + atomic/rwlock.h atomic/x86-gcc.h atomic/generic-msvc.h \ + atomic/gcc_builtins.h my_libwrap.h my_stacktrace.h \ + atomic/solaris.h EXTRA_DIST = mysql.h.pp mysql/plugin.h.pp probes_mysql.d.base diff --git a/include/atomic/gcc_builtins.h b/include/atomic/gcc_builtins.h index 01ebc38707e..100ff80cacd 100644 --- a/include/atomic/gcc_builtins.h +++ b/include/atomic/gcc_builtins.h @@ -18,7 +18,7 @@ #define make_atomic_add_body(S) \ v= __sync_fetch_and_add(a, v); -#define make_atomic_swap_body(S) \ +#define make_atomic_fas_body(S) \ v= __sync_lock_test_and_set(a, v); #define make_atomic_cas_body(S) \ int ## S sav; \ @@ -28,7 +28,10 @@ #ifdef MY_ATOMIC_MODE_DUMMY #define make_atomic_load_body(S) ret= *a #define make_atomic_store_body(S) *a= v +#define MY_ATOMIC_MODE "gcc-builtins-up" + #else +#define MY_ATOMIC_MODE "gcc-builtins-smp" #define make_atomic_load_body(S) \ ret= __sync_fetch_and_or(a, 0); #define make_atomic_store_body(S) \ diff --git a/include/atomic/generic-msvc.h b/include/atomic/generic-msvc.h new file mode 100644 index 00000000000..f1e1b0e88c9 --- /dev/null +++ b/include/atomic/generic-msvc.h @@ -0,0 +1,116 @@ +/* Copyright (C) 2006-2008 MySQL AB, 2008-2009 Sun Microsystems, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#ifndef _atomic_h_cleanup_ +#define _atomic_h_cleanup_ "atomic/generic-msvc.h" + +/* + We don't implement anything specific for MY_ATOMIC_MODE_DUMMY, always use + intrinsics. + 8 and 16-bit atomics are not implemented, but it can be done if necessary. +*/ +#undef MY_ATOMIC_HAS_8_16 + +/* + x86 compilers (both VS2003 or VS2005) never use instrinsics, but generate + function calls to kernel32 instead, even in the optimized build. + We force intrinsics as described in MSDN documentation for + _InterlockedCompareExchange. +*/ +#ifdef _M_IX86 + +#if (_MSC_VER >= 1500) +#include +#else +C_MODE_START +/*Visual Studio 2003 and earlier do not have prototypes for atomic intrinsics*/ +LONG _InterlockedExchange (LONG volatile *Target,LONG Value); +LONG _InterlockedCompareExchange (LONG volatile *Target, LONG Value, LONG Comp); +LONG _InterlockedExchangeAdd (LONG volatile *Addend, LONG Value); +C_MODE_END + +#pragma intrinsic(_InterlockedExchangeAdd) +#pragma intrinsic(_InterlockedCompareExchange) +#pragma intrinsic(_InterlockedExchange) +#endif + +#define InterlockedExchange _InterlockedExchange +#define InterlockedExchangeAdd _InterlockedExchangeAdd +#define InterlockedCompareExchange _InterlockedCompareExchange +/* + No need to do something special for InterlockedCompareExchangePointer + as it is a #define to InterlockedCompareExchange. The same applies to + InterlockedExchangePointer. +*/ +#endif /*_M_IX86*/ + +#define MY_ATOMIC_MODE "msvc-intrinsics" +#define IL_EXCHG_ADD32(X,Y) InterlockedExchangeAdd((volatile LONG *)(X),(Y)) +#define IL_COMP_EXCHG32(X,Y,Z) InterlockedCompareExchange((volatile LONG *)(X),(Y),(Z)) +#define IL_COMP_EXCHGptr InterlockedCompareExchangePointer +#define IL_EXCHG32(X,Y) InterlockedExchange((volatile LONG *)(X),(Y)) +#define IL_EXCHGptr InterlockedExchangePointer +#define make_atomic_add_body(S) \ + v= IL_EXCHG_ADD ## S (a, v) +#define make_atomic_cas_body(S) \ + int ## S initial_cmp= *cmp; \ + int ## S initial_a= IL_COMP_EXCHG ## S (a, set, initial_cmp); \ + if (!(ret= (initial_a == initial_cmp))) *cmp= initial_a; +#define make_atomic_swap_body(S) \ + v= IL_EXCHG ## S (a, v) +#define make_atomic_load_body(S) \ + ret= 0; /* avoid compiler warning */ \ + ret= IL_COMP_EXCHG ## S (a, ret, ret); + +/* + my_yield_processor (equivalent of x86 PAUSE instruction) should be used + to improve performance on hyperthreaded CPUs. Intel recommends to use it in + spin loops also on non-HT machines to reduce power consumption (see e.g + http://softwarecommunity.intel.com/articles/eng/2004.htm) + + Running benchmarks for spinlocks implemented with InterlockedCompareExchange + and YieldProcessor shows that much better performance is achieved by calling + YieldProcessor in a loop - that is, yielding longer. On Intel boxes setting + loop count in the range 200-300 brought best results. + */ +#ifndef YIELD_LOOPS +#define YIELD_LOOPS 200 +#endif + +static __inline int my_yield_processor() +{ + int i; + for(i=0; irw) -#define my_atomic_rwlock_init(name) pthread_rwlock_init(& (name)->rw, 0) -#define my_atomic_rwlock_rdlock(name) pthread_rwlock_rdlock(& (name)->rw) -#define my_atomic_rwlock_wrlock(name) pthread_rwlock_wrlock(& (name)->rw) -#define my_atomic_rwlock_rdunlock(name) pthread_rwlock_unlock(& (name)->rw) -#define my_atomic_rwlock_wrunlock(name) pthread_rwlock_unlock(& (name)->rw) -#define MY_ATOMIC_MODE "rwlocks" +#else /* not MY_ATOMIC_MODE_DUMMY */ + +typedef struct {pthread_mutex_t rw;} my_atomic_rwlock_t; + +#ifndef SAFE_MUTEX + +/* + we're using read-write lock macros but map them to mutex locks, and they're + faster. Still, having semantically rich API we can change the + underlying implementation, if necessary. +*/ +#define my_atomic_rwlock_destroy(name) pthread_mutex_destroy(& (name)->rw) +#define my_atomic_rwlock_init(name) pthread_mutex_init(& (name)->rw, 0) +#define my_atomic_rwlock_rdlock(name) pthread_mutex_lock(& (name)->rw) +#define my_atomic_rwlock_wrlock(name) pthread_mutex_lock(& (name)->rw) +#define my_atomic_rwlock_rdunlock(name) pthread_mutex_unlock(& (name)->rw) +#define my_atomic_rwlock_wrunlock(name) pthread_mutex_unlock(& (name)->rw) + +#else /* SAFE_MUTEX */ + +/* + SAFE_MUTEX pollutes the compiling name space with macros + that alter pthread_mutex_t, pthread_mutex_init, etc. + Atomic operations should never use the safe mutex wrappers. + Unfortunately, there is no way to have both: + - safe mutex macros expanding pthread_mutex_lock to safe_mutex_lock + - my_atomic macros expanding to unmodified pthread_mutex_lock + inlined in the same compilation unit. + So, in case of SAFE_MUTEX, a function call is required. + Given that SAFE_MUTEX is a debugging facility, + this extra function call is not a performance concern for + production builds. +*/ +C_MODE_START +extern void plain_pthread_mutex_init(safe_mutex_t *); +extern void plain_pthread_mutex_destroy(safe_mutex_t *); +extern void plain_pthread_mutex_lock(safe_mutex_t *); +extern void plain_pthread_mutex_unlock(safe_mutex_t *); +C_MODE_END + +#define my_atomic_rwlock_destroy(name) plain_pthread_mutex_destroy(&(name)->rw) +#define my_atomic_rwlock_init(name) plain_pthread_mutex_init(&(name)->rw) +#define my_atomic_rwlock_rdlock(name) plain_pthread_mutex_lock(&(name)->rw) +#define my_atomic_rwlock_wrlock(name) plain_pthread_mutex_lock(&(name)->rw) +#define my_atomic_rwlock_rdunlock(name) plain_pthread_mutex_unlock(&(name)->rw) +#define my_atomic_rwlock_wrunlock(name) plain_pthread_mutex_unlock(&(name)->rw) + +#endif /* SAFE_MUTEX */ + +#define MY_ATOMIC_MODE "mutex" +#ifndef MY_ATOMIC_MODE_RWLOCKS +#define MY_ATOMIC_MODE_RWLOCKS 1 +#endif #endif #define make_atomic_add_body(S) int ## S sav; sav= *a; *a+= v; v=sav; -#define make_atomic_swap_body(S) int ## S sav; sav= *a; *a= v; v=sav; +#define make_atomic_fas_body(S) int ## S sav; sav= *a; *a= v; v=sav; #define make_atomic_cas_body(S) if ((ret= (*a == *cmp))) *a= set; else *cmp=*a; #define make_atomic_load_body(S) ret= *a; #define make_atomic_store_body(S) *a= v; diff --git a/include/atomic/solaris.h b/include/atomic/solaris.h index 4c51253d2d5..45efd9faaba 100644 --- a/include/atomic/solaris.h +++ b/include/atomic/solaris.h @@ -186,25 +186,25 @@ my_atomic_storeptr(void * volatile *a, void *v) /* ------------------------------------------------------------------------ */ STATIC_INLINE int8 -my_atomic_swap8(int8 volatile *a, int8 v) +my_atomic_fas8(int8 volatile *a, int8 v) { return ((int8) atomic_swap_8((volatile uint8_t *)a, (uint8_t)v)); } STATIC_INLINE int16 -my_atomic_swap16(int16 volatile *a, int16 v) +my_atomic_fas16(int16 volatile *a, int16 v) { return ((int16) atomic_swap_16((volatile uint16_t *)a, (uint16_t)v)); } STATIC_INLINE int32 -my_atomic_swap32(int32 volatile *a, int32 v) +my_atomic_fas32(int32 volatile *a, int32 v) { return ((int32) atomic_swap_32((volatile uint32_t *)a, (uint32_t)v)); } STATIC_INLINE void * -my_atomic_swapptr(void * volatile *a, void *v) +my_atomic_fasptr(void * volatile *a, void *v) { return (atomic_swap_ptr(a, v)); } diff --git a/include/atomic/x86-gcc.h b/include/atomic/x86-gcc.h index c3029f9c1b4..59090bc26b7 100644 --- a/include/atomic/x86-gcc.h +++ b/include/atomic/x86-gcc.h @@ -22,10 +22,18 @@ architectures support double-word (128-bit) cas. */ -#ifdef MY_ATOMIC_NO_XADD -#define MY_ATOMIC_MODE "gcc-x86" LOCK "-no-xadd" +#ifdef __x86_64__ +# ifdef MY_ATOMIC_NO_XADD +# define MY_ATOMIC_MODE "gcc-amd64" LOCK_prefix "-no-xadd" +# else +# define MY_ATOMIC_MODE "gcc-amd64" LOCK_prefix +# endif #else -#define MY_ATOMIC_MODE "gcc-x86" LOCK +# ifdef MY_ATOMIC_NO_XADD +# define MY_ATOMIC_MODE "gcc-x86" LOCK_prefix "-no-xadd" +# else +# define MY_ATOMIC_MODE "gcc-x86" LOCK_prefix +# endif #endif /* fix -ansi errors while maintaining readability */ @@ -35,12 +43,12 @@ #ifndef MY_ATOMIC_NO_XADD #define make_atomic_add_body(S) \ - asm volatile (LOCK "; xadd %0, %1;" : "+r" (v) , "+m" (*a)) + asm volatile (LOCK_prefix "; xadd %0, %1;" : "+r" (v) , "+m" (*a)) #endif -#define make_atomic_swap_body(S) \ - asm volatile ("; xchg %0, %1;" : "+q" (v) , "+m" (*a)) +#define make_atomic_fas_body(S) \ + asm volatile ("xchg %0, %1;" : "+q" (v) , "+m" (*a)) #define make_atomic_cas_body(S) \ - asm volatile (LOCK "; cmpxchg %3, %0; setz %2;" \ + asm volatile (LOCK_prefix "; cmpxchg %3, %0; setz %2;" \ : "+m" (*a), "+a" (*cmp), "=q" (ret): "r" (set)) #ifdef MY_ATOMIC_MODE_DUMMY @@ -49,14 +57,14 @@ #else /* Actually 32-bit reads/writes are always atomic on x86 - But we add LOCK here anyway to force memory barriers + But we add LOCK_prefix here anyway to force memory barriers */ #define make_atomic_load_body(S) \ ret=0; \ - asm volatile (LOCK "; cmpxchg %2, %0" \ + asm volatile (LOCK_prefix "; cmpxchg %2, %0" \ : "+m" (*a), "+a" (ret): "r" (ret)) #define make_atomic_store_body(S) \ - asm volatile ("; xchg %0, %1;" : "+m" (*a) : "r" (v)) + asm volatile ("; xchg %0, %1;" : "+m" (*a), "+r" (v)) #endif #endif /* ATOMIC_X86_GCC_INCLUDED */ diff --git a/include/my_atomic.h b/include/my_atomic.h index f5da6e6a0d9..85cf87165fb 100644 --- a/include/my_atomic.h +++ b/include/my_atomic.h @@ -16,9 +16,51 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +/* + This header defines five atomic operations: + + my_atomic_add#(&var, what) + add 'what' to *var, and return the old value of *var + + my_atomic_fas#(&var, what) + 'Fetch And Store' + store 'what' in *var, and return the old value of *var + + my_atomic_cas#(&var, &old, new) + 'Compare And Swap' + if *var is equal to *old, then store 'new' in *var, and return TRUE + otherwise store *var in *old, and return FALSE + + my_atomic_load#(&var) + return *var + + my_atomic_store#(&var, what) + store 'what' in *var + + '#' is substituted by a size suffix - 8, 16, 32, or ptr + (e.g. my_atomic_add8, my_atomic_fas32, my_atomic_casptr). + + NOTE This operations are not always atomic, so they always must be + enclosed in my_atomic_rwlock_rdlock(lock)/my_atomic_rwlock_rdunlock(lock) + or my_atomic_rwlock_wrlock(lock)/my_atomic_rwlock_wrunlock(lock). + Hint: if a code block makes intensive use of atomic ops, it make sense + to take/release rwlock once for the whole block, not for every statement. + + On architectures where these operations are really atomic, rwlocks will + be optimized away. + 8- and 16-bit atomics aren't implemented for windows (see generic-msvc.h), + but can be added, if necessary. +*/ + #ifndef my_atomic_rwlock_init #define intptr void * +/** + On most platforms we implement 8-bit, 16-bit, 32-bit and "pointer" + operations. Thus the symbol below is defined by default; platforms + where we leave out 8-bit or 16-bit operations should undefine it. +*/ +#define MY_ATOMIC_HAS_8_16 1 #ifndef MY_ATOMIC_MODE_RWLOCKS /* @@ -27,126 +69,225 @@ #include "atomic/nolock.h" #endif -#ifndef MY_ATOMIC_NOLOCK -/* - * Have to use rw-locks for atomic ops - */ +#ifndef make_atomic_cas_body +/* nolock.h was not able to generate even a CAS function, fall back */ #include "atomic/rwlock.h" -#endif - -#ifndef MY_ATOMICS_MADE - +#else +/* define missing functions by using the already generated ones */ #ifndef make_atomic_add_body -#define make_atomic_add_body(S) \ +#define make_atomic_add_body(S) \ int ## S tmp=*a; \ while (!my_atomic_cas ## S(a, &tmp, tmp+v)); \ v=tmp; #endif +#ifndef make_atomic_fas_body +#define make_atomic_fas_body(S) \ + int ## S tmp=*a; \ + while (!my_atomic_cas ## S(a, &tmp, v)); \ + v=tmp; +#endif +#ifndef make_atomic_load_body +#define make_atomic_load_body(S) \ + ret= 0; /* avoid compiler warning */ \ + (void)(my_atomic_cas ## S(a, &ret, ret)); +#endif +#ifndef make_atomic_store_body +#define make_atomic_store_body(S) \ + (void)(my_atomic_fas ## S (a, v)); +#endif +#endif + +/* + transparent_union doesn't work in g++ + Bug ? + + Darwin's gcc doesn't want to put pointers in a transparent_union + when built with -arch ppc64. Complains: + warning: 'transparent_union' attribute ignored +*/ +#if defined(__GNUC__) && !defined(__cplusplus) && \ + ! (defined(__APPLE__) && defined(_ARCH_PPC64)) +/* + we want to be able to use my_atomic_xxx functions with + both signed and unsigned integers. But gcc will issue a warning + "passing arg N of `my_atomic_XXX' as [un]signed due to prototype" + if the signedness of the argument doesn't match the prototype, or + "pointer targets in passing argument N of my_atomic_XXX differ in signedness" + if int* is used where uint* is expected (or vice versa). + Let's shut these warnings up +*/ +#define make_transparent_unions(S) \ + typedef union { \ + int ## S i; \ + uint ## S u; \ + } U_ ## S __attribute__ ((transparent_union)); \ + typedef union { \ + int ## S volatile *i; \ + uint ## S volatile *u; \ + } Uv_ ## S __attribute__ ((transparent_union)); +#define uintptr intptr +make_transparent_unions(8) +make_transparent_unions(16) +make_transparent_unions(32) +make_transparent_unions(ptr) +#undef uintptr +#undef make_transparent_unions +#define a U_a.i +#define cmp U_cmp.i +#define v U_v.i +#define set U_set.i +#else +#define U_8 int8 +#define U_16 int16 +#define U_32 int32 +#define U_ptr intptr +#define Uv_8 int8 +#define Uv_16 int16 +#define Uv_32 int32 +#define Uv_ptr intptr +#define U_a volatile *a +#define U_cmp *cmp +#define U_v v +#define U_set set +#endif /* __GCC__ transparent_union magic */ #ifdef HAVE_INLINE -#define make_atomic_add(S) \ -STATIC_INLINE int ## S my_atomic_add ## S( \ - int ## S volatile *a, int ## S v) \ -{ \ - make_atomic_add_body(S); \ - return v; \ +#define make_atomic_cas(S) \ +STATIC_INLINE int my_atomic_cas ## S(Uv_ ## S U_a, \ + Uv_ ## S U_cmp, U_ ## S U_set) \ +{ \ + int8 ret; \ + make_atomic_cas_body(S); \ + return ret; \ } -#define make_atomic_swap(S) \ -STATIC_INLINE int ## S my_atomic_swap ## S( \ - int ## S volatile *a, int ## S v) \ -{ \ - make_atomic_swap_body(S); \ - return v; \ +#define make_atomic_add(S) \ +STATIC_INLINE int ## S my_atomic_add ## S( \ + Uv_ ## S U_a, U_ ## S U_v) \ +{ \ + make_atomic_add_body(S); \ + return v; \ } -#define make_atomic_cas(S) \ -STATIC_INLINE int my_atomic_cas ## S(int ## S volatile *a, \ - int ## S *cmp, int ## S set) \ -{ \ - int8 ret; \ - make_atomic_cas_body(S); \ - return ret; \ +#define make_atomic_fas(S) \ +STATIC_INLINE int ## S my_atomic_fas ## S( \ + Uv_ ## S U_a, U_ ## S U_v) \ +{ \ + make_atomic_fas_body(S); \ + return v; \ } -#define make_atomic_load(S) \ -STATIC_INLINE int ## S my_atomic_load ## S(int ## S volatile *a) \ -{ \ - int ## S ret; \ - make_atomic_load_body(S); \ - return ret; \ +#define make_atomic_load(S) \ +STATIC_INLINE int ## S my_atomic_load ## S(Uv_ ## S U_a) \ +{ \ + int ## S ret; \ + make_atomic_load_body(S); \ + return ret; \ } -#define make_atomic_store(S) \ -STATIC_INLINE void my_atomic_store ## S( \ - int ## S volatile *a, int ## S v) \ -{ \ - make_atomic_store_body(S); \ +#define make_atomic_store(S) \ +STATIC_INLINE void my_atomic_store ## S( \ + Uv_ ## S U_a, U_ ## S U_v) \ +{ \ + make_atomic_store_body(S); \ } #else /* no inline functions */ -#define make_atomic_add(S) \ -extern int ## S my_atomic_add ## S(int ## S volatile *a, int ## S v); +#define make_atomic_add(S) \ +extern int ## S my_atomic_add ## S(Uv_ ## S U_a, U_ ## S U_v); -#define make_atomic_swap(S) \ -extern int ## S my_atomic_swap ## S(int ## S volatile *a, int ## S v); +#define make_atomic_fas(S) \ +extern int ## S my_atomic_fas ## S(Uv_ ## S U_a, U_ ## S U_v); -#define make_atomic_cas(S) \ -extern int my_atomic_cas ## S(int ## S volatile *a, int ## S *cmp, int ## S set); +#define make_atomic_cas(S) \ +extern int my_atomic_cas ## S(Uv_ ## S U_a, Uv_ ## S U_cmp, U_ ## S U_set); -#define make_atomic_load(S) \ -extern int ## S my_atomic_load ## S(int ## S volatile *a); +#define make_atomic_load(S) \ +extern int ## S my_atomic_load ## S(Uv_ ## S U_a); -#define make_atomic_store(S) \ -extern void my_atomic_store ## S(int ## S volatile *a, int ## S v); +#define make_atomic_store(S) \ +extern void my_atomic_store ## S(Uv_ ## S U_a, U_ ## S U_v); #endif /* HAVE_INLINE */ -make_atomic_cas( 8) +#ifdef MY_ATOMIC_HAS_8_16 +make_atomic_cas(8) make_atomic_cas(16) +#endif make_atomic_cas(32) make_atomic_cas(ptr) -make_atomic_add( 8) +#ifdef MY_ATOMIC_HAS_8_16 +make_atomic_add(8) make_atomic_add(16) +#endif make_atomic_add(32) -make_atomic_load( 8) +#ifdef MY_ATOMIC_HAS_8_16 +make_atomic_load(8) make_atomic_load(16) +#endif make_atomic_load(32) make_atomic_load(ptr) -make_atomic_store( 8) +#ifdef MY_ATOMIC_HAS_8_16 +make_atomic_fas(8) +make_atomic_fas(16) +#endif +make_atomic_fas(32) +make_atomic_fas(ptr) + +#ifdef MY_ATOMIC_HAS_8_16 +make_atomic_store(8) make_atomic_store(16) +#endif make_atomic_store(32) make_atomic_store(ptr) -make_atomic_swap( 8) -make_atomic_swap(16) -make_atomic_swap(32) -make_atomic_swap(ptr) - -#undef make_atomic_add -#undef make_atomic_cas -#undef make_atomic_load -#undef make_atomic_store -#undef make_atomic_swap -#undef make_atomic_add_body -#undef make_atomic_cas_body -#undef make_atomic_load_body -#undef make_atomic_store_body -#undef make_atomic_swap_body -#undef intptr - -#endif /* MY_ATOMICS_MADE */ - #ifdef _atomic_h_cleanup_ #include _atomic_h_cleanup_ #undef _atomic_h_cleanup_ #endif +#undef U_8 +#undef U_16 +#undef U_32 +#undef U_ptr +#undef Uv_8 +#undef Uv_16 +#undef Uv_32 +#undef Uv_ptr +#undef a +#undef cmp +#undef v +#undef set +#undef U_a +#undef U_cmp +#undef U_v +#undef U_set +#undef make_atomic_add +#undef make_atomic_cas +#undef make_atomic_load +#undef make_atomic_store +#undef make_atomic_fas +#undef make_atomic_add_body +#undef make_atomic_cas_body +#undef make_atomic_load_body +#undef make_atomic_store_body +#undef make_atomic_fas_body +#undef intptr + +/* + the macro below defines (as an expression) the code that + will be run in spin-loops. Intel manuals recummend to have PAUSE there. + It is expected to be defined in include/atomic/ *.h files +*/ +#ifndef LF_BACKOFF +#define LF_BACKOFF (1) +#endif + #define MY_ATOMIC_OK 0 #define MY_ATOMIC_NOT_1CPU 1 extern int my_atomic_initialize(); diff --git a/mysys/my_atomic.c b/mysys/my_atomic.c index aa04d55f624..6bc76f0de3c 100644 --- a/mysys/my_atomic.c +++ b/mysys/my_atomic.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2006 MySQL AB +/* Copyright (C) 2006 MySQL AB, 2008-2009 Sun Microsystems, Inc This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -14,7 +14,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include -#include +#include #ifndef HAVE_INLINE /* the following will cause all inline functions to be instantiated */ @@ -43,3 +43,32 @@ int my_atomic_initialize() #endif } +#ifdef SAFE_MUTEX +#undef pthread_mutex_init +#undef pthread_mutex_destroy +#undef pthread_mutex_lock +#undef pthread_mutex_unlock + +void plain_pthread_mutex_init(safe_mutex_t *m) +{ + pthread_mutex_init(& m->mutex, NULL); +} + +void plain_pthread_mutex_destroy(safe_mutex_t *m) +{ + pthread_mutex_destroy(& m->mutex); +} + +void plain_pthread_mutex_lock(safe_mutex_t *m) +{ + pthread_mutex_lock(& m->mutex); +} + +void plain_pthread_mutex_unlock(safe_mutex_t *m) +{ + pthread_mutex_unlock(& m->mutex); +} + +#endif + + diff --git a/mysys/my_getncpus.c b/mysys/my_getncpus.c index 82e87dee2e4..5be961e3bc9 100644 --- a/mysys/my_getncpus.c +++ b/mysys/my_getncpus.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2006 MySQL AB +/* Copyright (C) 2006 MySQL AB, 2008-2009 Sun Microsystems, Inc This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,24 +16,34 @@ /* get the number of (online) CPUs */ #include "mysys_priv.h" +#ifdef HAVE_UNISTD_H #include +#endif static int ncpus=0; -#ifdef _SC_NPROCESSORS_ONLN int my_getncpus() { if (!ncpus) + { +#ifdef _SC_NPROCESSORS_ONLN ncpus= sysconf(_SC_NPROCESSORS_ONLN); +#elif defined(__WIN__) + SYSTEM_INFO sysinfo; + + /* + * We are not calling GetNativeSystemInfo here because (1) we + * don't believe that they return different values for number + * of processors and (2) if WOW64 limits processors for Win32 + * then we don't want to try to override that. + */ + GetSystemInfo(&sysinfo); + + ncpus= sysinfo.dwNumberOfProcessors; +#else +/* unknown so play safe: assume SMP and forbid uniprocessor build */ + ncpus= 2; +#endif + } return ncpus; } - -#else -/* unknown */ -int my_getncpus() -{ - return 2; -} - -#endif - diff --git a/unittest/mysys/Makefile.am b/unittest/mysys/Makefile.am index 56c65d71396..6e8058c4d9b 100644 --- a/unittest/mysys/Makefile.am +++ b/unittest/mysys/Makefile.am @@ -16,6 +16,8 @@ AM_CPPFLAGS = @ZLIB_INCLUDES@ -I$(top_builddir)/include AM_CPPFLAGS += -I$(top_srcdir)/include -I$(top_srcdir)/unittest/mytap +noinst_HEADERS = thr_template.c + LDADD = $(top_builddir)/unittest/mytap/libmytap.a \ $(top_builddir)/mysys/libmysys.a \ $(top_builddir)/dbug/libdbug.a \ diff --git a/unittest/mysys/my_atomic-t.c b/unittest/mysys/my_atomic-t.c index f2bcd360508..1a558e8cb73 100644 --- a/unittest/mysys/my_atomic-t.c +++ b/unittest/mysys/my_atomic-t.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2006 MySQL AB +/* Copyright (C) 2006-2008 MySQL AB, 2008 Sun Microsystems, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -13,10 +13,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include -#include -#include -#include +#include "thr_template.c" /* at least gcc 3.4.5 and 3.4.6 (but not 3.2.3) on RHEL */ #if __GNUC__ == 3 && __GNUC_MINOR__ == 4 @@ -25,181 +22,125 @@ #define GCC_BUG_WORKAROUND #endif -int32 a32,b32,c32; +volatile uint32 b32; +volatile int32 c32; my_atomic_rwlock_t rwl; -pthread_attr_t thr_attr; -pthread_mutex_t mutex; -pthread_cond_t cond; -int N; - /* add and sub a random number in a loop. Must get 0 at the end */ -pthread_handler_t test_atomic_add_handler(void *arg) +pthread_handler_t test_atomic_add(void *arg) { - int m=*(int *)arg; + int m= (*(int *)arg)/2; GCC_BUG_WORKAROUND int32 x; - for (x=((int)((long)(&m))); m ; m--) + for (x= ((int)(intptr)(&m)); m ; m--) { - x=x*m+0x87654321; + x= (x*m+0x87654321) & INT_MAX32; my_atomic_rwlock_wrlock(&rwl); - my_atomic_add32(&a32, x); + my_atomic_add32(&bad, x); my_atomic_rwlock_wrunlock(&rwl); my_atomic_rwlock_wrlock(&rwl); - my_atomic_add32(&a32, -x); + my_atomic_add32(&bad, -x); my_atomic_rwlock_wrunlock(&rwl); } pthread_mutex_lock(&mutex); - N--; - if (!N) pthread_cond_signal(&cond); + if (!--running_threads) pthread_cond_signal(&cond); pthread_mutex_unlock(&mutex); return 0; } /* 1. generate thread number 0..N-1 from b32 - 2. add it to a32 + 2. add it to bad 3. swap thread numbers in c32 4. (optionally) one more swap to avoid 0 as a result - 5. subtract result from a32 - must get 0 in a32 at the end + 5. subtract result from bad + must get 0 in bad at the end */ -pthread_handler_t test_atomic_swap_handler(void *arg) +pthread_handler_t test_atomic_fas(void *arg) { - int m=*(int *)arg; - int32 x; + int m= *(int *)arg; + int32 x; my_atomic_rwlock_wrlock(&rwl); - x=my_atomic_add32(&b32, 1); + x= my_atomic_add32(&b32, 1); my_atomic_rwlock_wrunlock(&rwl); my_atomic_rwlock_wrlock(&rwl); - my_atomic_add32(&a32, x); + my_atomic_add32(&bad, x); my_atomic_rwlock_wrunlock(&rwl); for (; m ; m--) { my_atomic_rwlock_wrlock(&rwl); - x=my_atomic_swap32(&c32, x); + x= my_atomic_fas32(&c32, x); my_atomic_rwlock_wrunlock(&rwl); } if (!x) { my_atomic_rwlock_wrlock(&rwl); - x=my_atomic_swap32(&c32, x); + x= my_atomic_fas32(&c32, x); my_atomic_rwlock_wrunlock(&rwl); } my_atomic_rwlock_wrlock(&rwl); - my_atomic_add32(&a32, -x); + my_atomic_add32(&bad, -x); my_atomic_rwlock_wrunlock(&rwl); pthread_mutex_lock(&mutex); - N--; - if (!N) pthread_cond_signal(&cond); + if (!--running_threads) pthread_cond_signal(&cond); pthread_mutex_unlock(&mutex); return 0; } /* - same as test_atomic_add_handler, but my_atomic_add32 is emulated with - (slower) my_atomic_cas32 + same as test_atomic_add, but my_atomic_add32 is emulated with + my_atomic_cas32 - notice that the slowdown is proportional to the + number of CPUs */ -pthread_handler_t test_atomic_cas_handler(void *arg) +pthread_handler_t test_atomic_cas(void *arg) { - int m=*(int *)arg, ok; - GCC_BUG_WORKAROUND int32 x,y; - for (x=((int)((long)(&m))); m ; m--) + int m= (*(int *)arg)/2, ok= 0; + GCC_BUG_WORKAROUND int32 x, y; + for (x= ((int)(intptr)(&m)); m ; m--) { my_atomic_rwlock_wrlock(&rwl); - y=my_atomic_load32(&a32); + y= my_atomic_load32(&bad); my_atomic_rwlock_wrunlock(&rwl); - - x=x*m+0x87654321; + x= (x*m+0x87654321) & INT_MAX32; do { my_atomic_rwlock_wrlock(&rwl); - ok=my_atomic_cas32(&a32, &y, y+x); + ok= my_atomic_cas32(&bad, &y, (uint32)y+x); my_atomic_rwlock_wrunlock(&rwl); - } while (!ok); + } while (!ok) ; do { my_atomic_rwlock_wrlock(&rwl); - ok=my_atomic_cas32(&a32, &y, y-x); + ok= my_atomic_cas32(&bad, &y, y-x); my_atomic_rwlock_wrunlock(&rwl); - } while (!ok); + } while (!ok) ; } pthread_mutex_lock(&mutex); - N--; - if (!N) pthread_cond_signal(&cond); + if (!--running_threads) pthread_cond_signal(&cond); pthread_mutex_unlock(&mutex); return 0; } -void test_atomic(const char *test, pthread_handler handler, int n, int m) + +void do_tests() { - pthread_t t; - ulonglong now=my_getsystime(); - - a32= 0; - b32= 0; - c32= 0; - - diag("Testing %s with %d threads, %d iterations... ", test, n, m); - for (N=n ; n ; n--) - { - if (pthread_create(&t, &thr_attr, handler, &m) != 0) - { - diag("Could not create thread"); - a32= 1; - goto err; - } - } - - pthread_mutex_lock(&mutex); - while (N) - pthread_cond_wait(&cond, &mutex); - pthread_mutex_unlock(&mutex); - now=my_getsystime()-now; -err: - ok(a32 == 0, "tested %s in %g secs", test, ((double)now)/1e7); -} - -int main() -{ - int err; - MY_INIT("my_atomic-t.c"); - - diag("N CPUs: %d", my_getncpus()); - err= my_atomic_initialize(); - plan(4); - ok(err == 0, "my_atomic_initialize() returned %d", err); - pthread_attr_init(&thr_attr); - pthread_attr_setdetachstate(&thr_attr,PTHREAD_CREATE_DETACHED); - pthread_mutex_init(&mutex, 0); - pthread_cond_init(&cond, 0); + bad= my_atomic_initialize(); + ok(!bad, "my_atomic_initialize() returned %d", bad); + my_atomic_rwlock_init(&rwl); -#ifdef HPUX11 -#define CYCLES 1000 -#else -#define CYCLES 10000 -#endif -#define THREADS 100 - test_atomic("my_atomic_add32", test_atomic_add_handler, THREADS, CYCLES); - test_atomic("my_atomic_swap32", test_atomic_swap_handler, THREADS, CYCLES); - test_atomic("my_atomic_cas32", test_atomic_cas_handler, THREADS, CYCLES); - /* - workaround until we know why it crashes randomly on some machine - (BUG#22320). - */ - sleep(2); + b32= c32= 0; + test_concurrently("my_atomic_add32", test_atomic_add, THREADS, CYCLES); + b32= c32= 0; + test_concurrently("my_atomic_fas32", test_atomic_fas, THREADS, CYCLES); + b32= c32= 0; + test_concurrently("my_atomic_cas32", test_atomic_cas, THREADS, CYCLES); - pthread_mutex_destroy(&mutex); - pthread_cond_destroy(&cond); - pthread_attr_destroy(&thr_attr); my_atomic_rwlock_destroy(&rwl); - return exit_status(); } - diff --git a/unittest/mysys/thr_template.c b/unittest/mysys/thr_template.c new file mode 100644 index 00000000000..1ac03e474fd --- /dev/null +++ b/unittest/mysys/thr_template.c @@ -0,0 +1,92 @@ +/* Copyright (C) 2006-2008 MySQL AB, 2008 Sun Microsystems, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include +#include +#include +#include + +volatile uint32 bad; +pthread_attr_t thr_attr; +pthread_mutex_t mutex; +pthread_cond_t cond; +uint running_threads; + +void do_tests(); + +void test_concurrently(const char *test, pthread_handler handler, int n, int m) +{ + pthread_t t; + ulonglong now= my_getsystime(); + + bad= 0; + + diag("Testing %s with %d threads, %d iterations... ", test, n, m); + for (running_threads= n ; n ; n--) + { + if (pthread_create(&t, &thr_attr, handler, &m) != 0) + { + diag("Could not create thread"); + abort(); + } + } + pthread_mutex_lock(&mutex); + while (running_threads) + pthread_cond_wait(&cond, &mutex); + pthread_mutex_unlock(&mutex); + + now= my_getsystime()-now; + ok(!bad, "tested %s in %g secs (%d)", test, ((double)now)/1e7, bad); +} + +int main(int argc __attribute__((unused)), char **argv) +{ + MY_INIT("thd_template"); + + if (argv[1] && *argv[1]) + DBUG_SET_INITIAL(argv[1]); + + pthread_mutex_init(&mutex, 0); + pthread_cond_init(&cond, 0); + pthread_attr_init(&thr_attr); + pthread_attr_setdetachstate(&thr_attr,PTHREAD_CREATE_DETACHED); + +#ifdef MY_ATOMIC_MODE_RWLOCKS +#if defined(HPUX11) || defined(__POWERPC__) /* showed to be very slow (scheduler-related) */ +#define CYCLES 300 +#else +#define CYCLES 3000 +#endif +#else +#define CYCLES 3000 +#endif +#define THREADS 30 + + diag("N CPUs: %d, atomic ops: %s", my_getncpus(), MY_ATOMIC_MODE); + + do_tests(); + + /* + workaround until we know why it crashes randomly on some machine + (BUG#22320). + */ + sleep(2); + pthread_mutex_destroy(&mutex); + pthread_cond_destroy(&cond); + pthread_attr_destroy(&thr_attr); + my_end(0); + return exit_status(); +} + From 7b9fd341a00636995dc176f86e11f9e90bc0f5c9 Mon Sep 17 00:00:00 2001 From: Marc Alff Date: Tue, 17 Nov 2009 17:36:39 -0700 Subject: [PATCH 34/40] Misc cleanup --- include/Makefile.am | 2 +- include/atomic/x86-msvc.h | 96 --------------------------------------- 2 files changed, 1 insertion(+), 97 deletions(-) delete mode 100644 include/atomic/x86-msvc.h diff --git a/include/Makefile.am b/include/Makefile.am index e20b08afe35..e712ef18722 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -41,7 +41,7 @@ noinst_HEADERS = config-win.h config-netware.h my_bit.h \ my_vle.h my_user.h my_atomic.h atomic/nolock.h \ atomic/rwlock.h atomic/x86-gcc.h atomic/generic-msvc.h \ atomic/gcc_builtins.h my_libwrap.h my_stacktrace.h \ - atomic/solaris.h + atomic/solaris.h EXTRA_DIST = mysql.h.pp mysql/plugin.h.pp probes_mysql.d.base diff --git a/include/atomic/x86-msvc.h b/include/atomic/x86-msvc.h deleted file mode 100644 index c4885bb8451..00000000000 --- a/include/atomic/x86-msvc.h +++ /dev/null @@ -1,96 +0,0 @@ -/* Copyright (C) 2006 MySQL AB - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; version 2 of the License. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - -/* - XXX 64-bit atomic operations can be implemented using - cmpxchg8b, if necessary -*/ - -// Would it be better to use intrinsics ? -// (InterlockedCompareExchange, InterlockedCompareExchange16 -// InterlockedExchangeAdd, InterlockedExchange) - -#ifndef _atomic_h_cleanup_ -#define _atomic_h_cleanup_ "atomic/x86-msvc.h" - -#define MY_ATOMIC_MODE "msvc-x86" LOCK - -#define make_atomic_add_body(S) \ - _asm { \ - _asm mov reg_ ## S, v \ - _asm LOCK xadd *a, reg_ ## S \ - _asm movzx v, reg_ ## S \ - } -#define make_atomic_cas_body(S) \ - _asm { \ - _asm mov areg_ ## S, *cmp \ - _asm mov reg2_ ## S, set \ - _asm LOCK cmpxchg *a, reg2_ ## S \ - _asm mov *cmp, areg_ ## S \ - _asm setz al \ - _asm movzx ret, al \ - } -#define make_atomic_swap_body(S) \ - _asm { \ - _asm mov reg_ ## S, v \ - _asm xchg *a, reg_ ## S \ - _asm mov v, reg_ ## S \ - } - -#ifdef MY_ATOMIC_MODE_DUMMY -#define make_atomic_load_body(S) ret=*a -#define make_atomic_store_body(S) *a=v -#else -/* - Actually 32-bit reads/writes are always atomic on x86 - But we add LOCK here anyway to force memory barriers -*/ -#define make_atomic_load_body(S) \ - _asm { \ - _asm mov areg_ ## S, 0 \ - _asm mov reg2_ ## S, areg_ ## S \ - _asm LOCK cmpxchg *a, reg2_ ## S \ - _asm mov ret, areg_ ## S \ - } -#define make_atomic_store_body(S) \ - _asm { \ - _asm mov reg_ ## S, v \ - _asm xchg *a, reg_ ## S \ - } -#endif - -#define reg_8 al -#define reg_16 ax -#define reg_32 eax -#define areg_8 al -#define areg_16 ax -#define areg_32 eax -#define reg2_8 bl -#define reg2_16 bx -#define reg2_32 ebx - -#else /* cleanup */ - -#undef reg_8 -#undef reg_16 -#undef reg_32 -#undef areg_8 -#undef areg_16 -#undef areg_32 -#undef reg2_8 -#undef reg2_16 -#undef reg2_32 -#endif - From 244eced1a797f15dd0ebe0a58b6c054c26b4fa28 Mon Sep 17 00:00:00 2001 From: Marc Alff Date: Tue, 17 Nov 2009 19:31:40 -0700 Subject: [PATCH 35/40] WL#3230 concurrent hash Backport from 6.0.14 to 5.6.0 Original code from Sergei Golubchik --- configure.in | 2 +- include/Makefile.am | 2 +- include/lf.h | 268 +++++++++++++++++++ include/my_pthread.h | 13 + mysys/Makefile.am | 3 +- mysys/lf_alloc-pin.c | 534 +++++++++++++++++++++++++++++++++++++ mysys/lf_dynarray.c | 207 ++++++++++++++ mysys/lf_hash.c | 505 +++++++++++++++++++++++++++++++++++ mysys/my_thr_init.c | 3 + unittest/mysys/Makefile.am | 2 +- unittest/mysys/lf-t.c | 178 +++++++++++++ 11 files changed, 1713 insertions(+), 4 deletions(-) create mode 100644 include/lf.h create mode 100644 mysys/lf_alloc-pin.c create mode 100644 mysys/lf_dynarray.c create mode 100644 mysys/lf_hash.c create mode 100644 unittest/mysys/lf-t.c diff --git a/configure.in b/configure.in index dd2c53a2b52..db86eeaa779 100644 --- a/configure.in +++ b/configure.in @@ -2114,7 +2114,7 @@ AC_CHECK_FUNCS(alarm bcmp bfill bmove bsearch bzero \ pthread_setprio_np pthread_setschedparam pthread_sigmask readlink \ realpath rename rint rwlock_init setupterm \ shmget shmat shmdt shmctl sigaction sigemptyset sigaddset \ - sighold sigset sigthreadmask port_create sleep \ + sighold sigset sigthreadmask port_create sleep thr_yield \ snprintf socket stpcpy strcasecmp strerror strsignal strnlen strpbrk strstr \ strtol strtoll strtoul strtoull tell tempnam thr_setconcurrency vidattr \ posix_fallocate backtrace backtrace_symbols backtrace_symbols_fd) diff --git a/include/Makefile.am b/include/Makefile.am index e712ef18722..83a22f1beec 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -30,7 +30,7 @@ pkginclude_HEADERS = $(HEADERS_ABI) my_dbug.h m_string.h my_sys.h \ m_ctype.h my_attribute.h $(HEADERS_GEN_CONFIGURE) \ $(HEADERS_GEN_MAKE) probes_mysql.h probes_mysql_nodtrace.h -noinst_HEADERS = config-win.h config-netware.h my_bit.h \ +noinst_HEADERS = config-win.h config-netware.h lf.h my_bit.h \ heap.h my_bitmap.h my_uctype.h \ myisam.h myisampack.h myisammrg.h ft_global.h\ mysys_err.h my_base.h help_start.h help_end.h \ diff --git a/include/lf.h b/include/lf.h new file mode 100644 index 00000000000..7e8f05f4ada --- /dev/null +++ b/include/lf.h @@ -0,0 +1,268 @@ +/* Copyright (C) 2007-2008 MySQL AB, 2008-2009 Sun Microsystems, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#ifndef _lf_h +#define _lf_h + +#include + +C_MODE_START + +/* + Helpers to define both func() and _func(), where + func() is a _func() protected by my_atomic_rwlock_wrlock() +*/ + +#define lock_wrap(f, t, proto_args, args, lock) \ +t _ ## f proto_args; \ +static inline t f proto_args \ +{ \ + t ret; \ + my_atomic_rwlock_wrlock(lock); \ + ret= _ ## f args; \ + my_atomic_rwlock_wrunlock(lock); \ + return ret; \ +} + +#define lock_wrap_void(f, proto_args, args, lock) \ +void _ ## f proto_args; \ +static inline void f proto_args \ +{ \ + my_atomic_rwlock_wrlock(lock); \ + _ ## f args; \ + my_atomic_rwlock_wrunlock(lock); \ +} + +#define nolock_wrap(f, t, proto_args, args) \ +t _ ## f proto_args; \ +static inline t f proto_args \ +{ \ + return _ ## f args; \ +} + +#define nolock_wrap_void(f, proto_args, args) \ +void _ ## f proto_args; \ +static inline void f proto_args \ +{ \ + _ ## f args; \ +} + +/* + wait-free dynamic array, see lf_dynarray.c + + 4 levels of 256 elements each mean 4311810304 elements in an array - it + should be enough for a while +*/ +#define LF_DYNARRAY_LEVEL_LENGTH 256 +#define LF_DYNARRAY_LEVELS 4 + +typedef struct { + void * volatile level[LF_DYNARRAY_LEVELS]; + uint size_of_element; + my_atomic_rwlock_t lock; +} LF_DYNARRAY; + +typedef int (*lf_dynarray_func)(void *, void *); + +void lf_dynarray_init(LF_DYNARRAY *array, uint element_size); +void lf_dynarray_destroy(LF_DYNARRAY *array); + +nolock_wrap(lf_dynarray_value, void *, + (LF_DYNARRAY *array, uint idx), + (array, idx)) +lock_wrap(lf_dynarray_lvalue, void *, + (LF_DYNARRAY *array, uint idx), + (array, idx), + &array->lock) +nolock_wrap(lf_dynarray_iterate, int, + (LF_DYNARRAY *array, lf_dynarray_func func, void *arg), + (array, func, arg)) + +/* + pin manager for memory allocator, lf_alloc-pin.c +*/ + +#define LF_PINBOX_PINS 4 +#define LF_PURGATORY_SIZE 10 + +typedef void lf_pinbox_free_func(void *, void *, void*); + +typedef struct { + LF_DYNARRAY pinarray; + lf_pinbox_free_func *free_func; + void *free_func_arg; + uint free_ptr_offset; + uint32 volatile pinstack_top_ver; /* this is a versioned pointer */ + uint32 volatile pins_in_array; /* number of elements in array */ +} LF_PINBOX; + +typedef struct { + void * volatile pin[LF_PINBOX_PINS]; + LF_PINBOX *pinbox; + void **stack_ends_here; + void *purgatory; + uint32 purgatory_count; + uint32 volatile link; +/* we want sizeof(LF_PINS) to be 64 to avoid false sharing */ +#if SIZEOF_INT*2+SIZEOF_CHARP*(LF_PINBOX_PINS+3) != 64 + char pad[64-sizeof(uint32)*2-sizeof(void*)*(LF_PINBOX_PINS+3)]; +#endif +} LF_PINS; + +/* + shortcut macros to do an atomic_wrlock on a structure that uses pins + (e.g. lf_hash). +*/ +#define lf_rwlock_by_pins(PINS) \ + my_atomic_rwlock_wrlock(&(PINS)->pinbox->pinarray.lock) +#define lf_rwunlock_by_pins(PINS) \ + my_atomic_rwlock_wrunlock(&(PINS)->pinbox->pinarray.lock) + +/* + compile-time assert, to require "no less than N" pins + it's enough if it'll fail on at least one compiler, so + we'll enable it on GCC only, which supports zero-length arrays. +*/ +#if defined(__GNUC__) && defined(MY_LF_EXTRA_DEBUG) +#define LF_REQUIRE_PINS(N) \ + static const char require_pins[LF_PINBOX_PINS-N] \ + __attribute__ ((unused)); \ + static const int LF_NUM_PINS_IN_THIS_FILE= N; +#define _lf_pin(PINS, PIN, ADDR) \ + ( \ + assert(PIN < LF_NUM_PINS_IN_THIS_FILE), \ + my_atomic_storeptr(&(PINS)->pin[PIN], (ADDR)) \ + ) +#else +#define LF_REQUIRE_PINS(N) +#define _lf_pin(PINS, PIN, ADDR) my_atomic_storeptr(&(PINS)->pin[PIN], (ADDR)) +#endif + +#define _lf_unpin(PINS, PIN) _lf_pin(PINS, PIN, NULL) +#define lf_pin(PINS, PIN, ADDR) \ + do { \ + lf_rwlock_by_pins(PINS); \ + _lf_pin(PINS, PIN, ADDR); \ + lf_rwunlock_by_pins(PINS); \ + } while (0) +#define lf_unpin(PINS, PIN) lf_pin(PINS, PIN, NULL) +#define _lf_assert_pin(PINS, PIN) assert((PINS)->pin[PIN] != 0) +#define _lf_assert_unpin(PINS, PIN) assert((PINS)->pin[PIN] == 0) + +void lf_pinbox_init(LF_PINBOX *pinbox, uint free_ptr_offset, + lf_pinbox_free_func *free_func, void * free_func_arg); +void lf_pinbox_destroy(LF_PINBOX *pinbox); + +lock_wrap(lf_pinbox_get_pins, LF_PINS *, + (LF_PINBOX *pinbox), + (pinbox), + &pinbox->pinarray.lock) +lock_wrap_void(lf_pinbox_put_pins, + (LF_PINS *pins), + (pins), + &pins->pinbox->pinarray.lock) +lock_wrap_void(lf_pinbox_free, + (LF_PINS *pins, void *addr), + (pins, addr), + &pins->pinbox->pinarray.lock) + +/* + memory allocator, lf_alloc-pin.c +*/ + +typedef struct st_lf_allocator { + LF_PINBOX pinbox; + uchar * volatile top; + uint element_size; + uint32 volatile mallocs; + void (*constructor)(uchar *); /* called, when an object is malloc()'ed */ + void (*destructor)(uchar *); /* called, when an object is free()'d */ +} LF_ALLOCATOR; + +void lf_alloc_init(LF_ALLOCATOR *allocator, uint size, uint free_ptr_offset); +void lf_alloc_destroy(LF_ALLOCATOR *allocator); +uint lf_alloc_pool_count(LF_ALLOCATOR *allocator); +/* + shortcut macros to access underlying pinbox functions from an LF_ALLOCATOR + see _lf_pinbox_get_pins() and _lf_pinbox_put_pins() +*/ +#define _lf_alloc_free(PINS, PTR) _lf_pinbox_free((PINS), (PTR)) +#define lf_alloc_free(PINS, PTR) lf_pinbox_free((PINS), (PTR)) +#define _lf_alloc_get_pins(A) _lf_pinbox_get_pins(&(A)->pinbox) +#define lf_alloc_get_pins(A) lf_pinbox_get_pins(&(A)->pinbox) +#define _lf_alloc_put_pins(PINS) _lf_pinbox_put_pins(PINS) +#define lf_alloc_put_pins(PINS) lf_pinbox_put_pins(PINS) +#define lf_alloc_direct_free(ALLOC, ADDR) my_free((uchar*)(ADDR), MYF(0)) + +lock_wrap(lf_alloc_new, void *, + (LF_PINS *pins), + (pins), + &pins->pinbox->pinarray.lock) + +C_MODE_END + +/* + extendible hash, lf_hash.c +*/ +#include + +C_MODE_START + +#define LF_HASH_UNIQUE 1 + +/* lf_hash overhead per element (that is, sizeof(LF_SLIST) */ +extern const int LF_HASH_OVERHEAD; + +typedef struct { + LF_DYNARRAY array; /* hash itself */ + LF_ALLOCATOR alloc; /* allocator for elements */ + my_hash_get_key get_key; /* see HASH */ + CHARSET_INFO *charset; /* see HASH */ + uint key_offset, key_length; /* see HASH */ + uint element_size; /* size of memcpy'ed area on insert */ + uint flags; /* LF_HASH_UNIQUE, etc */ + int32 volatile size; /* size of array */ + int32 volatile count; /* number of elements in the hash */ +} LF_HASH; + +void lf_hash_init(LF_HASH *hash, uint element_size, uint flags, + uint key_offset, uint key_length, my_hash_get_key get_key, + CHARSET_INFO *charset); +void lf_hash_destroy(LF_HASH *hash); +int lf_hash_insert(LF_HASH *hash, LF_PINS *pins, const void *data); +void *lf_hash_search(LF_HASH *hash, LF_PINS *pins, const void *key, uint keylen); +int lf_hash_delete(LF_HASH *hash, LF_PINS *pins, const void *key, uint keylen); +/* + shortcut macros to access underlying pinbox functions from an LF_HASH + see _lf_pinbox_get_pins() and _lf_pinbox_put_pins() +*/ +#define _lf_hash_get_pins(HASH) _lf_alloc_get_pins(&(HASH)->alloc) +#define lf_hash_get_pins(HASH) lf_alloc_get_pins(&(HASH)->alloc) +#define _lf_hash_put_pins(PINS) _lf_pinbox_put_pins(PINS) +#define lf_hash_put_pins(PINS) lf_pinbox_put_pins(PINS) +#define lf_hash_search_unpin(PINS) lf_unpin((PINS), 2) +/* + cleanup +*/ + +#undef lock_wrap_void +#undef lock_wrap +#undef nolock_wrap_void +#undef nolock_wrap + +C_MODE_END + +#endif + diff --git a/include/my_pthread.h b/include/my_pthread.h index b6d9feae067..3e23c0fa410 100644 --- a/include/my_pthread.h +++ b/include/my_pthread.h @@ -152,6 +152,7 @@ int pthread_join(pthread_t thread, void **value_ptr); #define pthread_detach_this_thread() #define pthread_condattr_init(A) #define pthread_condattr_destroy(A) +#define pthread_yield() SwitchToThread() #define my_pthread_getprio(thread_id) pthread_dummy(0) @@ -385,6 +386,17 @@ void my_pthread_attr_getstacksize(pthread_attr_t *attrib, size_t *size); int my_pthread_mutex_trylock(pthread_mutex_t *mutex); #endif +#if !defined(HAVE_PTHREAD_YIELD_ONE_ARG) && !defined(HAVE_PTHREAD_YIELD_ZERO_ARG) +/* no pthread_yield() available */ +#ifdef HAVE_SCHED_YIELD +#define pthread_yield() sched_yield() +#elif defined(HAVE_PTHREAD_YIELD_NP) /* can be Mac OS X */ +#define pthread_yield() pthread_yield_np() +#elif defined(HAVE_THR_YIELD) +#define pthread_yield() thr_yield() +#endif +#endif + /* The defines set_timespec and set_timespec_nsec should be used for calculating an absolute time at which @@ -663,6 +675,7 @@ struct st_my_thread_var my_bool init; struct st_my_thread_var *next,**prev; void *opt_info; + void *stack_ends_here; #ifndef DBUG_OFF void *dbug; char name[THREAD_NAME_SIZE+1]; diff --git a/mysys/Makefile.am b/mysys/Makefile.am index 68fde34ee07..3b53fc1dd59 100644 --- a/mysys/Makefile.am +++ b/mysys/Makefile.am @@ -30,7 +30,8 @@ libmysys_a_SOURCES = my_init.c my_getwd.c mf_getdate.c my_mmap.c \ mf_tempdir.c my_lock.c mf_brkhant.c my_alarm.c \ my_malloc.c my_realloc.c my_once.c mulalloc.c \ my_alloc.c safemalloc.c my_new.cc \ - my_vle.c my_atomic.c \ + my_vle.c my_atomic.c lf_hash.c \ + lf_dynarray.c lf_alloc-pin.c \ my_fopen.c my_fstream.c my_getsystime.c \ my_error.c errors.c my_div.c my_messnc.c \ mf_format.c mf_same.c mf_dirname.c mf_fn_ext.c \ diff --git a/mysys/lf_alloc-pin.c b/mysys/lf_alloc-pin.c new file mode 100644 index 00000000000..fda9b97791d --- /dev/null +++ b/mysys/lf_alloc-pin.c @@ -0,0 +1,534 @@ +/* QQ: TODO multi-pinbox */ +/* Copyright (C) 2006-2008 MySQL AB, 2008-2009 Sun Microsystems, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/* + wait-free concurrent allocator based on pinning addresses + + It works as follows: every thread (strictly speaking - every CPU, but + it's too difficult to do) has a small array of pointers. They're called + "pins". Before using an object its address must be stored in this array + (pinned). When an object is no longer necessary its address must be + removed from this array (unpinned). When a thread wants to free() an + object it scans all pins of all threads to see if somebody has this + object pinned. If yes - the object is not freed (but stored in a + "purgatory"). To reduce the cost of a single free() pins are not scanned + on every free() but only added to (thread-local) purgatory. On every + LF_PURGATORY_SIZE free() purgatory is scanned and all unpinned objects + are freed. + + Pins are used to solve ABA problem. To use pins one must obey + a pinning protocol: + + 1. Let's assume that PTR is a shared pointer to an object. Shared means + that any thread may modify it anytime to point to a different object + and free the old object. Later the freed object may be potentially + allocated by another thread. If we're unlucky that other thread may + set PTR to point to this object again. This is ABA problem. + 2. Create a local pointer LOCAL_PTR. + 3. Pin the PTR in a loop: + do + { + LOCAL_PTR= PTR; + pin(PTR, PIN_NUMBER); + } while (LOCAL_PTR != PTR) + 4. It is guaranteed that after the loop has ended, LOCAL_PTR + points to an object (or NULL, if PTR may be NULL), that + will never be freed. It is not guaranteed though + that LOCAL_PTR == PTR (as PTR can change any time) + 5. When done working with the object, remove the pin: + unpin(PIN_NUMBER) + 6. When copying pins (as in the list traversing loop: + pin(CUR, 1); + while () + { + do // standard + { // pinning + NEXT=CUR->next; // loop + pin(NEXT, 0); // see #3 + } while (NEXT != CUR->next); // above + ... + ... + CUR=NEXT; + pin(CUR, 1); // copy pin[0] to pin[1] + } + which keeps CUR address constantly pinned), note than pins may be + copied only upwards (!!!), that is pin[N] to pin[M], M > N. + 7. Don't keep the object pinned longer than necessary - the number of + pins you have is limited (and small), keeping an object pinned + prevents its reuse and cause unnecessary mallocs. + + Explanations: + + 3. The loop is important. The following can occur: + thread1> LOCAL_PTR= PTR + thread2> free(PTR); PTR=0; + thread1> pin(PTR, PIN_NUMBER); + now thread1 cannot access LOCAL_PTR, even if it's pinned, + because it points to a freed memory. That is, it *must* + verify that it has indeed pinned PTR, the shared pointer. + + 6. When a thread wants to free some LOCAL_PTR, and it scans + all lists of pins to see whether it's pinned, it does it + upwards, from low pin numbers to high. Thus another thread + must copy an address from one pin to another in the same + direction - upwards, otherwise the scanning thread may + miss it. + + Implementation details: + + Pins are given away from a "pinbox". Pinbox is stack-based allocator. + It used dynarray for storing pins, new elements are allocated by dynarray + as necessary, old are pushed in the stack for reuse. ABA is solved by + versioning a pointer - because we use an array, a pointer to pins is 16 bit, + upper 16 bits are used for a version. + + It is assumed that pins belong to a THD and are not transferable + between THD's (LF_PINS::stack_ends_here being a primary reason + for this limitation). +*/ +#include +#include +#include + +#define LF_PINBOX_MAX_PINS 65536 + +static void _lf_pinbox_real_free(LF_PINS *pins); + +/* + Initialize a pinbox. Normally called from lf_alloc_init. + See the latter for details. +*/ +void lf_pinbox_init(LF_PINBOX *pinbox, uint free_ptr_offset, + lf_pinbox_free_func *free_func, void *free_func_arg) +{ + DBUG_ASSERT(free_ptr_offset % sizeof(void *) == 0); + compile_time_assert(sizeof(LF_PINS) == 64); + lf_dynarray_init(&pinbox->pinarray, sizeof(LF_PINS)); + pinbox->pinstack_top_ver= 0; + pinbox->pins_in_array= 0; + pinbox->free_ptr_offset= free_ptr_offset; + pinbox->free_func= free_func; + pinbox->free_func_arg= free_func_arg; +} + +void lf_pinbox_destroy(LF_PINBOX *pinbox) +{ + lf_dynarray_destroy(&pinbox->pinarray); +} + +/* + Get pins from a pinbox. Usually called via lf_alloc_get_pins() or + lf_hash_get_pins(). + + SYNOPSYS + pinbox - + + DESCRIPTION + get a new LF_PINS structure from a stack of unused pins, + or allocate a new one out of dynarray. + + NOTE + It is assumed that pins belong to a thread and are not transferable + between threads. +*/ +LF_PINS *_lf_pinbox_get_pins(LF_PINBOX *pinbox) +{ + uint32 pins, next, top_ver; + LF_PINS *el; + /* + We have an array of max. 64k elements. + The highest index currently allocated is pinbox->pins_in_array. + Freed elements are in a lifo stack, pinstack_top_ver. + pinstack_top_ver is 32 bits; 16 low bits are the index in the + array, to the first element of the list. 16 high bits are a version + (every time the 16 low bits are updated, the 16 high bits are + incremented). Versioniong prevents the ABA problem. + */ + top_ver= pinbox->pinstack_top_ver; + do + { + if (!(pins= top_ver % LF_PINBOX_MAX_PINS)) + { + /* the stack of free elements is empty */ + pins= my_atomic_add32((int32 volatile*) &pinbox->pins_in_array, 1)+1; + if (unlikely(pins >= LF_PINBOX_MAX_PINS)) + return 0; + /* + note that the first allocated element has index 1 (pins==1). + index 0 is reserved to mean "NULL pointer" + */ + el= (LF_PINS *)_lf_dynarray_lvalue(&pinbox->pinarray, pins); + if (unlikely(!el)) + return 0; + break; + } + el= (LF_PINS *)_lf_dynarray_value(&pinbox->pinarray, pins); + next= el->link; + } while (!my_atomic_cas32((int32 volatile*) &pinbox->pinstack_top_ver, + (int32*) &top_ver, + top_ver-pins+next+LF_PINBOX_MAX_PINS)); + /* + set el->link to the index of el in the dynarray (el->link has two usages: + - if element is allocated, it's its own index + - if element is free, it's its next element in the free stack + */ + el->link= pins; + el->purgatory_count= 0; + el->pinbox= pinbox; + el->stack_ends_here= & my_thread_var->stack_ends_here; + return el; +} + +/* + Put pins back to a pinbox. Usually called via lf_alloc_put_pins() or + lf_hash_put_pins(). + + DESCRIPTION + empty the purgatory (XXX deadlock warning below!), + push LF_PINS structure to a stack +*/ +void _lf_pinbox_put_pins(LF_PINS *pins) +{ + LF_PINBOX *pinbox= pins->pinbox; + uint32 top_ver, nr; + nr= pins->link; +#ifdef MY_LF_EXTRA_DEBUG + { + int i; + for (i= 0; i < LF_PINBOX_PINS; i++) + DBUG_ASSERT(pins->pin[i] == 0); + } +#endif + /* + XXX this will deadlock if other threads will wait for + the caller to do something after _lf_pinbox_put_pins(), + and they would have pinned addresses that the caller wants to free. + Thus: only free pins when all work is done and nobody can wait for you!!! + */ + while (pins->purgatory_count) + { + _lf_pinbox_real_free(pins); + if (pins->purgatory_count) + { + my_atomic_rwlock_wrunlock(&pins->pinbox->pinarray.lock); + pthread_yield(); + my_atomic_rwlock_wrlock(&pins->pinbox->pinarray.lock); + } + } + top_ver= pinbox->pinstack_top_ver; + do + { + pins->link= top_ver % LF_PINBOX_MAX_PINS; + } while (!my_atomic_cas32((int32 volatile*) &pinbox->pinstack_top_ver, + (int32*) &top_ver, + top_ver-pins->link+nr+LF_PINBOX_MAX_PINS)); + return; +} + +static int ptr_cmp(void **a, void **b) +{ + return *a < *b ? -1 : *a == *b ? 0 : 1; +} + +#define add_to_purgatory(PINS, ADDR) \ + do \ + { \ + *(void **)((char *)(ADDR)+(PINS)->pinbox->free_ptr_offset)= \ + (PINS)->purgatory; \ + (PINS)->purgatory= (ADDR); \ + (PINS)->purgatory_count++; \ + } while (0) + +/* + Free an object allocated via pinbox allocator + + DESCRIPTION + add an object to purgatory. if necessary, call _lf_pinbox_real_free() + to actually free something. +*/ +void _lf_pinbox_free(LF_PINS *pins, void *addr) +{ + add_to_purgatory(pins, addr); + if (pins->purgatory_count % LF_PURGATORY_SIZE) + _lf_pinbox_real_free(pins); +} + +struct st_harvester { + void **granary; + int npins; +}; + +/* + callback for _lf_dynarray_iterate: + scan all pins of all threads and accumulate all pins +*/ +static int harvest_pins(LF_PINS *el, struct st_harvester *hv) +{ + int i; + LF_PINS *el_end= el+min(hv->npins, LF_DYNARRAY_LEVEL_LENGTH); + for (; el < el_end; el++) + { + for (i= 0; i < LF_PINBOX_PINS; i++) + { + void *p= el->pin[i]; + if (p) + *hv->granary++= p; + } + } + /* + hv->npins may become negative below, but it means that + we're on the last dynarray page and harvest_pins() won't be + called again. We don't bother to make hv->npins() correct + (that is 0) in this case. + */ + hv->npins-= LF_DYNARRAY_LEVEL_LENGTH; + return 0; +} + +/* + callback for _lf_dynarray_iterate: + scan all pins of all threads and see if addr is present there +*/ +static int match_pins(LF_PINS *el, void *addr) +{ + int i; + LF_PINS *el_end= el+LF_DYNARRAY_LEVEL_LENGTH; + for (; el < el_end; el++) + for (i= 0; i < LF_PINBOX_PINS; i++) + if (el->pin[i] == addr) + return 1; + return 0; +} + +#if STACK_DIRECTION < 0 +#define available_stack_size(CUR,END) (long) ((char*)(CUR) - (char*)(END)) +#else +#define available_stack_size(CUR,END) (long) ((char*)(END) - (char*)(CUR)) +#endif + +#define next_node(P, X) (*((uchar * volatile *)(((uchar *)(X)) + (P)->free_ptr_offset))) +#define anext_node(X) next_node(&allocator->pinbox, (X)) + +/* + Scan the purgatory and free everything that can be freed +*/ +static void _lf_pinbox_real_free(LF_PINS *pins) +{ + int npins, alloca_size; + void *list, **addr; + void *first, *last= NULL; + LF_PINBOX *pinbox= pins->pinbox; + + LINT_INIT(first); + npins= pinbox->pins_in_array+1; + +#ifdef HAVE_ALLOCA + alloca_size= sizeof(void *)*LF_PINBOX_PINS*npins; + /* create a sorted list of pinned addresses, to speed up searches */ + if (available_stack_size(&pinbox, *pins->stack_ends_here) > alloca_size) + { + struct st_harvester hv; + addr= (void **) alloca(alloca_size); + hv.granary= addr; + hv.npins= npins; + /* scan the dynarray and accumulate all pinned addresses */ + _lf_dynarray_iterate(&pinbox->pinarray, + (lf_dynarray_func)harvest_pins, &hv); + + npins= hv.granary-addr; + /* and sort them */ + if (npins) + qsort(addr, npins, sizeof(void *), (qsort_cmp)ptr_cmp); + } + else +#endif + addr= 0; + + list= pins->purgatory; + pins->purgatory= 0; + pins->purgatory_count= 0; + while (list) + { + void *cur= list; + list= *(void **)((char *)cur+pinbox->free_ptr_offset); + if (npins) + { + if (addr) /* use binary search */ + { + void **a, **b, **c; + for (a= addr, b= addr+npins-1, c= a+(b-a)/2; (b-a) > 1; c= a+(b-a)/2) + if (cur == *c) + a= b= c; + else if (cur > *c) + a= c; + else + b= c; + if (cur == *a || cur == *b) + goto found; + } + else /* no alloca - no cookie. linear search here */ + { + if (_lf_dynarray_iterate(&pinbox->pinarray, + (lf_dynarray_func)match_pins, cur)) + goto found; + } + } + /* not pinned - freeing */ + if (last) + last= next_node(pinbox, last)= (uchar *)cur; + else + first= last= (uchar *)cur; + continue; +found: + /* pinned - keeping */ + add_to_purgatory(pins, cur); + } + if (last) + pinbox->free_func(first, last, pinbox->free_func_arg); +} + +/* lock-free memory allocator for fixed-size objects */ + +LF_REQUIRE_PINS(1) + +/* + callback for _lf_pinbox_real_free to free a list of unpinned objects - + add it back to the allocator stack + + DESCRIPTION + 'first' and 'last' are the ends of the linked list of nodes: + first->el->el->....->el->last. Use first==last to free only one element. +*/ +static void alloc_free(uchar *first, + uchar volatile *last, + LF_ALLOCATOR *allocator) +{ + /* + we need a union here to access type-punned pointer reliably. + otherwise gcc -fstrict-aliasing will not see 'tmp' changed in the loop + */ + union { uchar * node; void *ptr; } tmp; + tmp.node= allocator->top; + do + { + anext_node(last)= tmp.node; + } while (!my_atomic_casptr((void **)(char *)&allocator->top, + (void **)&tmp.ptr, first) && LF_BACKOFF); +} + +/* + initialize lock-free allocator + + SYNOPSYS + allocator - + size a size of an object to allocate + free_ptr_offset an offset inside the object to a sizeof(void *) + memory that is guaranteed to be unused after + the object is put in the purgatory. Unused by ANY + thread, not only the purgatory owner. + This memory will be used to link waiting-to-be-freed + objects in a purgatory list. +*/ +void lf_alloc_init(LF_ALLOCATOR *allocator, uint size, uint free_ptr_offset) +{ + lf_pinbox_init(&allocator->pinbox, free_ptr_offset, + (lf_pinbox_free_func *)alloc_free, allocator); + allocator->top= 0; + allocator->mallocs= 0; + allocator->element_size= size; + allocator->constructor= 0; + allocator->destructor= 0; + DBUG_ASSERT(size >= sizeof(void*) + free_ptr_offset); +} + +/* + destroy the allocator, free everything that's in it + + NOTE + As every other init/destroy function here and elsewhere it + is not thread safe. No, this function is no different, ensure + that no thread needs the allocator before destroying it. + We are not responsible for any damage that may be caused by + accessing the allocator when it is being or has been destroyed. + Oh yes, and don't put your cat in a microwave. +*/ +void lf_alloc_destroy(LF_ALLOCATOR *allocator) +{ + uchar *node= allocator->top; + while (node) + { + uchar *tmp= anext_node(node); + if (allocator->destructor) + allocator->destructor(node); + my_free((void *)node, MYF(0)); + node= tmp; + } + lf_pinbox_destroy(&allocator->pinbox); + allocator->top= 0; +} + +/* + Allocate and return an new object. + + DESCRIPTION + Pop an unused object from the stack or malloc it is the stack is empty. + pin[0] is used, it's removed on return. +*/ +void *_lf_alloc_new(LF_PINS *pins) +{ + LF_ALLOCATOR *allocator= (LF_ALLOCATOR *)(pins->pinbox->free_func_arg); + uchar *node; + for (;;) + { + do + { + node= allocator->top; + _lf_pin(pins, 0, node); + } while (node != allocator->top && LF_BACKOFF); + if (!node) + { + node= (void *)my_malloc(allocator->element_size, MYF(MY_WME)); + if (allocator->constructor) + allocator->constructor(node); +#ifdef MY_LF_EXTRA_DEBUG + if (likely(node != 0)) + my_atomic_add32(&allocator->mallocs, 1); +#endif + break; + } + if (my_atomic_casptr((void **)(char *)&allocator->top, + (void *)&node, anext_node(node))) + break; + } + _lf_unpin(pins, 0); + return node; +} + +/* + count the number of objects in a pool. + + NOTE + This is NOT thread-safe !!! +*/ +uint lf_alloc_pool_count(LF_ALLOCATOR *allocator) +{ + uint i; + uchar *node; + for (node= allocator->top, i= 0; node; node= anext_node(node), i++) + /* no op */; + return i; +} + diff --git a/mysys/lf_dynarray.c b/mysys/lf_dynarray.c new file mode 100644 index 00000000000..b1cdce698a9 --- /dev/null +++ b/mysys/lf_dynarray.c @@ -0,0 +1,207 @@ +/* Copyright (C) 2006 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/* + Analog of DYNAMIC_ARRAY that never reallocs + (so no pointer into the array may ever become invalid). + + Memory is allocated in non-contiguous chunks. + This data structure is not space efficient for sparse arrays. + + Every element is aligned to sizeof(element) boundary + (to avoid false sharing if element is big enough). + + LF_DYNARRAY is a recursive structure. On the zero level + LF_DYNARRAY::level[0] it's an array of LF_DYNARRAY_LEVEL_LENGTH elements, + on the first level it's an array of LF_DYNARRAY_LEVEL_LENGTH pointers + to arrays of elements, on the second level it's an array of pointers + to arrays of pointers to arrays of elements. And so on. + + With four levels the number of elements is limited to 4311810304 + (but as in all functions index is uint, the real limit is 2^32-1) + + Actually, it's wait-free, not lock-free ;-) +*/ + +#include +#include +#include +#include + +void lf_dynarray_init(LF_DYNARRAY *array, uint element_size) +{ + bzero(array, sizeof(*array)); + array->size_of_element= element_size; + my_atomic_rwlock_init(&array->lock); +} + +static void recursive_free(void **alloc, int level) +{ + if (!alloc) + return; + + if (level) + { + int i; + for (i= 0; i < LF_DYNARRAY_LEVEL_LENGTH; i++) + recursive_free(alloc[i], level-1); + my_free((void *)alloc, MYF(0)); + } + else + my_free(alloc[-1], MYF(0)); +} + +void lf_dynarray_destroy(LF_DYNARRAY *array) +{ + int i; + for (i= 0; i < LF_DYNARRAY_LEVELS; i++) + recursive_free(array->level[i], i); + my_atomic_rwlock_destroy(&array->lock); +} + +static const ulong dynarray_idxes_in_prev_levels[LF_DYNARRAY_LEVELS]= +{ + 0, /* +1 here to to avoid -1's below */ + LF_DYNARRAY_LEVEL_LENGTH, + LF_DYNARRAY_LEVEL_LENGTH * LF_DYNARRAY_LEVEL_LENGTH + + LF_DYNARRAY_LEVEL_LENGTH, + LF_DYNARRAY_LEVEL_LENGTH * LF_DYNARRAY_LEVEL_LENGTH * + LF_DYNARRAY_LEVEL_LENGTH + LF_DYNARRAY_LEVEL_LENGTH * + LF_DYNARRAY_LEVEL_LENGTH + LF_DYNARRAY_LEVEL_LENGTH +}; + +static const ulong dynarray_idxes_in_prev_level[LF_DYNARRAY_LEVELS]= +{ + 0, /* +1 here to to avoid -1's below */ + LF_DYNARRAY_LEVEL_LENGTH, + LF_DYNARRAY_LEVEL_LENGTH * LF_DYNARRAY_LEVEL_LENGTH, + LF_DYNARRAY_LEVEL_LENGTH * LF_DYNARRAY_LEVEL_LENGTH * + LF_DYNARRAY_LEVEL_LENGTH, +}; + +/* + Returns a valid lvalue pointer to the element number 'idx'. + Allocates memory if necessary. +*/ +void *_lf_dynarray_lvalue(LF_DYNARRAY *array, uint idx) +{ + void * ptr, * volatile * ptr_ptr= 0; + int i; + + for (i= LF_DYNARRAY_LEVELS-1; idx < dynarray_idxes_in_prev_levels[i]; i--) + /* no-op */; + ptr_ptr= &array->level[i]; + idx-= dynarray_idxes_in_prev_levels[i]; + for (; i > 0; i--) + { + if (!(ptr= *ptr_ptr)) + { + void *alloc= my_malloc(LF_DYNARRAY_LEVEL_LENGTH * sizeof(void *), + MYF(MY_WME|MY_ZEROFILL)); + if (unlikely(!alloc)) + return(NULL); + if (my_atomic_casptr(ptr_ptr, &ptr, alloc)) + ptr= alloc; + else + my_free(alloc, MYF(0)); + } + ptr_ptr= ((void **)ptr) + idx / dynarray_idxes_in_prev_level[i]; + idx%= dynarray_idxes_in_prev_level[i]; + } + if (!(ptr= *ptr_ptr)) + { + uchar *alloc, *data; + alloc= my_malloc(LF_DYNARRAY_LEVEL_LENGTH * array->size_of_element + + max(array->size_of_element, sizeof(void *)), + MYF(MY_WME|MY_ZEROFILL)); + if (unlikely(!alloc)) + return(NULL); + /* reserve the space for free() address */ + data= alloc + sizeof(void *); + { /* alignment */ + intptr mod= ((intptr)data) % array->size_of_element; + if (mod) + data+= array->size_of_element - mod; + } + ((void **)data)[-1]= alloc; /* free() will need the original pointer */ + if (my_atomic_casptr(ptr_ptr, &ptr, data)) + ptr= data; + else + my_free(alloc, MYF(0)); + } + return ((uchar*)ptr) + array->size_of_element * idx; +} + +/* + Returns a pointer to the element number 'idx' + or NULL if an element does not exists +*/ +void *_lf_dynarray_value(LF_DYNARRAY *array, uint idx) +{ + void * ptr, * volatile * ptr_ptr= 0; + int i; + + for (i= LF_DYNARRAY_LEVELS-1; idx < dynarray_idxes_in_prev_levels[i]; i--) + /* no-op */; + ptr_ptr= &array->level[i]; + idx-= dynarray_idxes_in_prev_levels[i]; + for (; i > 0; i--) + { + if (!(ptr= *ptr_ptr)) + return(NULL); + ptr_ptr= ((void **)ptr) + idx / dynarray_idxes_in_prev_level[i]; + idx %= dynarray_idxes_in_prev_level[i]; + } + if (!(ptr= *ptr_ptr)) + return(NULL); + return ((uchar*)ptr) + array->size_of_element * idx; +} + +static int recursive_iterate(LF_DYNARRAY *array, void *ptr, int level, + lf_dynarray_func func, void *arg) +{ + int res, i; + if (!ptr) + return 0; + if (!level) + return func(ptr, arg); + for (i= 0; i < LF_DYNARRAY_LEVEL_LENGTH; i++) + if ((res= recursive_iterate(array, ((void **)ptr)[i], level-1, func, arg))) + return res; + return 0; +} + +/* + Calls func(array, arg) on every array of LF_DYNARRAY_LEVEL_LENGTH elements + in lf_dynarray. + + DESCRIPTION + lf_dynarray consists of a set of arrays, LF_DYNARRAY_LEVEL_LENGTH elements + each. _lf_dynarray_iterate() calls user-supplied function on every array + from the set. It is the fastest way to scan the array, faster than + for (i=0; i < N; i++) { func(_lf_dynarray_value(dynarray, i)); } + + NOTE + if func() returns non-zero, the scan is aborted +*/ +int _lf_dynarray_iterate(LF_DYNARRAY *array, lf_dynarray_func func, void *arg) +{ + int i, res; + for (i= 0; i < LF_DYNARRAY_LEVELS; i++) + if ((res= recursive_iterate(array, array->level[i], i, func, arg))) + return res; + return 0; +} + diff --git a/mysys/lf_hash.c b/mysys/lf_hash.c new file mode 100644 index 00000000000..f478196c7c8 --- /dev/null +++ b/mysys/lf_hash.c @@ -0,0 +1,505 @@ +/* Copyright (C) 2006-2008 MySQL AB, 2008-2009 Sun Microsystems, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/* + extensible hash + + TODO + try to get rid of dummy nodes ? + for non-unique hash, count only _distinct_ values + (but how to do it in lf_hash_delete ?) +*/ +#include +#include +#include +#include +#include + +LF_REQUIRE_PINS(3) + +/* An element of the list */ +typedef struct { + intptr volatile link; /* a pointer to the next element in a listand a flag */ + uint32 hashnr; /* reversed hash number, for sorting */ + const uchar *key; + size_t keylen; + /* + data is stored here, directly after the keylen. + thus the pointer to data is (void*)(slist_element_ptr+1) + */ +} LF_SLIST; + +const int LF_HASH_OVERHEAD= sizeof(LF_SLIST); + +/* + a structure to pass the context (pointers two the three successive elements + in a list) from lfind to linsert/ldelete +*/ +typedef struct { + intptr volatile *prev; + LF_SLIST *curr, *next; +} CURSOR; + +/* + the last bit in LF_SLIST::link is a "deleted" flag. + the helper macros below convert it to a pure pointer or a pure flag +*/ +#define PTR(V) (LF_SLIST *)((V) & (~(intptr)1)) +#define DELETED(V) ((V) & 1) + +/* + DESCRIPTION + Search for hashnr/key/keylen in the list starting from 'head' and + position the cursor. The list is ORDER BY hashnr, key + + RETURN + 0 - not found + 1 - found + + NOTE + cursor is positioned in either case + pins[0..2] are used, they are NOT removed on return +*/ +static int lfind(LF_SLIST * volatile *head, CHARSET_INFO *cs, uint32 hashnr, + const uchar *key, uint keylen, CURSOR *cursor, LF_PINS *pins) +{ + uint32 cur_hashnr; + const uchar *cur_key; + uint cur_keylen; + intptr link; + +retry: + cursor->prev= (intptr *)head; + do { /* PTR() isn't necessary below, head is a dummy node */ + cursor->curr= (LF_SLIST *)(*cursor->prev); + _lf_pin(pins, 1, cursor->curr); + } while (*cursor->prev != (intptr)cursor->curr && LF_BACKOFF); + for (;;) + { + if (unlikely(!cursor->curr)) + return 0; /* end of the list */ + do { + /* QQ: XXX or goto retry ? */ + link= cursor->curr->link; + cursor->next= PTR(link); + _lf_pin(pins, 0, cursor->next); + } while (link != cursor->curr->link && LF_BACKOFF); + cur_hashnr= cursor->curr->hashnr; + cur_key= cursor->curr->key; + cur_keylen= cursor->curr->keylen; + if (*cursor->prev != (intptr)cursor->curr) + { + (void)LF_BACKOFF; + goto retry; + } + if (!DELETED(link)) + { + if (cur_hashnr >= hashnr) + { + int r= 1; + if (cur_hashnr > hashnr || + (r= my_strnncoll(cs, (uchar*) cur_key, cur_keylen, (uchar*) key, + keylen)) >= 0) + return !r; + } + cursor->prev= &(cursor->curr->link); + _lf_pin(pins, 2, cursor->curr); + } + else + { + /* + we found a deleted node - be nice, help the other thread + and remove this deleted node + */ + if (my_atomic_casptr((void **)cursor->prev, + (void **)&cursor->curr, cursor->next)) + _lf_alloc_free(pins, cursor->curr); + else + { + (void)LF_BACKOFF; + goto retry; + } + } + cursor->curr= cursor->next; + _lf_pin(pins, 1, cursor->curr); + } +} + +/* + DESCRIPTION + insert a 'node' in the list that starts from 'head' in the correct + position (as found by lfind) + + RETURN + 0 - inserted + not 0 - a pointer to a duplicate (not pinned and thus unusable) + + NOTE + it uses pins[0..2], on return all pins are removed. + if there're nodes with the same key value, a new node is added before them. +*/ +static LF_SLIST *linsert(LF_SLIST * volatile *head, CHARSET_INFO *cs, + LF_SLIST *node, LF_PINS *pins, uint flags) +{ + CURSOR cursor; + int res; + + for (;;) + { + if (lfind(head, cs, node->hashnr, node->key, node->keylen, + &cursor, pins) && + (flags & LF_HASH_UNIQUE)) + { + res= 0; /* duplicate found */ + break; + } + else + { + node->link= (intptr)cursor.curr; + DBUG_ASSERT(node->link != (intptr)node); /* no circular references */ + DBUG_ASSERT(cursor.prev != &node->link); /* no circular references */ + if (my_atomic_casptr((void **)cursor.prev, (void **)&cursor.curr, node)) + { + res= 1; /* inserted ok */ + break; + } + } + } + _lf_unpin(pins, 0); + _lf_unpin(pins, 1); + _lf_unpin(pins, 2); + /* + Note that cursor.curr is not pinned here and the pointer is unreliable, + the object may dissapear anytime. But if it points to a dummy node, the + pointer is safe, because dummy nodes are never freed - initialize_bucket() + uses this fact. + */ + return res ? 0 : cursor.curr; +} + +/* + DESCRIPTION + deletes a node as identified by hashnr/keey/keylen from the list + that starts from 'head' + + RETURN + 0 - ok + 1 - not found + + NOTE + it uses pins[0..2], on return all pins are removed. +*/ +static int ldelete(LF_SLIST * volatile *head, CHARSET_INFO *cs, uint32 hashnr, + const uchar *key, uint keylen, LF_PINS *pins) +{ + CURSOR cursor; + int res; + + for (;;) + { + if (!lfind(head, cs, hashnr, key, keylen, &cursor, pins)) + { + res= 1; /* not found */ + break; + } + else + { + /* mark the node deleted */ + if (my_atomic_casptr((void **)&(cursor.curr->link), + (void **)&cursor.next, + (void *)(((intptr)cursor.next) | 1))) + { + /* and remove it from the list */ + if (my_atomic_casptr((void **)cursor.prev, + (void **)&cursor.curr, cursor.next)) + _lf_alloc_free(pins, cursor.curr); + else + { + /* + somebody already "helped" us and removed the node ? + Let's check if we need to help that someone too! + (to ensure the number of "set DELETED flag" actions + is equal to the number of "remove from the list" actions) + */ + lfind(head, cs, hashnr, key, keylen, &cursor, pins); + } + res= 0; + break; + } + } + } + _lf_unpin(pins, 0); + _lf_unpin(pins, 1); + _lf_unpin(pins, 2); + return res; +} + +/* + DESCRIPTION + searches for a node as identified by hashnr/keey/keylen in the list + that starts from 'head' + + RETURN + 0 - not found + node - found + + NOTE + it uses pins[0..2], on return the pin[2] keeps the node found + all other pins are removed. +*/ +static LF_SLIST *lsearch(LF_SLIST * volatile *head, CHARSET_INFO *cs, + uint32 hashnr, const uchar *key, uint keylen, + LF_PINS *pins) +{ + CURSOR cursor; + int res= lfind(head, cs, hashnr, key, keylen, &cursor, pins); + if (res) + _lf_pin(pins, 2, cursor.curr); + _lf_unpin(pins, 0); + _lf_unpin(pins, 1); + return res ? cursor.curr : 0; +} + +static inline const uchar* hash_key(const LF_HASH *hash, + const uchar *record, size_t *length) +{ + if (hash->get_key) + return (*hash->get_key)(record, length, 0); + *length= hash->key_length; + return record + hash->key_offset; +} + +/* + Compute the hash key value from the raw key. + + @note, that the hash value is limited to 2^31, because we need one + bit to distinguish between normal and dummy nodes. +*/ +static inline uint calc_hash(LF_HASH *hash, const uchar *key, uint keylen) +{ + ulong nr1= 1, nr2= 4; + hash->charset->coll->hash_sort(hash->charset, (uchar*) key, keylen, + &nr1, &nr2); + return nr1 & INT_MAX32; +} + +#define MAX_LOAD 1.0 /* average number of elements in a bucket */ + +static int initialize_bucket(LF_HASH *, LF_SLIST * volatile*, uint, LF_PINS *); + +/* + Initializes lf_hash, the arguments are compatible with hash_init + + @note element_size sets both the size of allocated memory block for + lf_alloc and a size of memcpy'ed block size in lf_hash_insert. Typically + they are the same, indeed. But LF_HASH::element_size can be decreased + after lf_hash_init, and then lf_alloc will allocate larger block that + lf_hash_insert will copy over. It is desireable if part of the element + is expensive to initialize - for example if there is a mutex or + DYNAMIC_ARRAY. In this case they should be initialize in the + LF_ALLOCATOR::constructor, and lf_hash_insert should not overwrite them. + See wt_init() for example. +*/ +void lf_hash_init(LF_HASH *hash, uint element_size, uint flags, + uint key_offset, uint key_length, my_hash_get_key get_key, + CHARSET_INFO *charset) +{ + lf_alloc_init(&hash->alloc, sizeof(LF_SLIST)+element_size, + offsetof(LF_SLIST, key)); + lf_dynarray_init(&hash->array, sizeof(LF_SLIST *)); + hash->size= 1; + hash->count= 0; + hash->element_size= element_size; + hash->flags= flags; + hash->charset= charset ? charset : &my_charset_bin; + hash->key_offset= key_offset; + hash->key_length= key_length; + hash->get_key= get_key; + DBUG_ASSERT(get_key ? !key_offset && !key_length : key_length); +} + +void lf_hash_destroy(LF_HASH *hash) +{ + LF_SLIST *el, **head= (LF_SLIST **)_lf_dynarray_value(&hash->array, 0); + + if (unlikely(!head)) + return; + el= *head; + + while (el) + { + intptr next= el->link; + if (el->hashnr & 1) + lf_alloc_direct_free(&hash->alloc, el); /* normal node */ + else + my_free((void *)el, MYF(0)); /* dummy node */ + el= (LF_SLIST *)next; + } + lf_alloc_destroy(&hash->alloc); + lf_dynarray_destroy(&hash->array); +} + +/* + DESCRIPTION + inserts a new element to a hash. it will have a _copy_ of + data, not a pointer to it. + + RETURN + 0 - inserted + 1 - didn't (unique key conflict) + -1 - out of memory + + NOTE + see linsert() for pin usage notes +*/ +int lf_hash_insert(LF_HASH *hash, LF_PINS *pins, const void *data) +{ + int csize, bucket, hashnr; + LF_SLIST *node, * volatile *el; + + lf_rwlock_by_pins(pins); + node= (LF_SLIST *)_lf_alloc_new(pins); + if (unlikely(!node)) + return -1; + memcpy(node+1, data, hash->element_size); + node->key= hash_key(hash, (uchar *)(node+1), &node->keylen); + hashnr= calc_hash(hash, node->key, node->keylen); + bucket= hashnr % hash->size; + el= _lf_dynarray_lvalue(&hash->array, bucket); + if (unlikely(!el)) + return -1; + if (*el == NULL && unlikely(initialize_bucket(hash, el, bucket, pins))) + return -1; + node->hashnr= my_reverse_bits(hashnr) | 1; /* normal node */ + if (linsert(el, hash->charset, node, pins, hash->flags)) + { + _lf_alloc_free(pins, node); + lf_rwunlock_by_pins(pins); + return 1; + } + csize= hash->size; + if ((my_atomic_add32(&hash->count, 1)+1.0) / csize > MAX_LOAD) + my_atomic_cas32(&hash->size, &csize, csize*2); + lf_rwunlock_by_pins(pins); + return 0; +} + +/* + DESCRIPTION + deletes an element with the given key from the hash (if a hash is + not unique and there're many elements with this key - the "first" + matching element is deleted) + RETURN + 0 - deleted + 1 - didn't (not found) + -1 - out of memory + NOTE + see ldelete() for pin usage notes +*/ +int lf_hash_delete(LF_HASH *hash, LF_PINS *pins, const void *key, uint keylen) +{ + LF_SLIST * volatile *el; + uint bucket, hashnr= calc_hash(hash, (uchar *)key, keylen); + + bucket= hashnr % hash->size; + lf_rwlock_by_pins(pins); + el= _lf_dynarray_lvalue(&hash->array, bucket); + if (unlikely(!el)) + return -1; + /* + note that we still need to initialize_bucket here, + we cannot return "node not found", because an old bucket of that + node may've been split and the node was assigned to a new bucket + that was never accessed before and thus is not initialized. + */ + if (*el == NULL && unlikely(initialize_bucket(hash, el, bucket, pins))) + return -1; + if (ldelete(el, hash->charset, my_reverse_bits(hashnr) | 1, + (uchar *)key, keylen, pins)) + { + lf_rwunlock_by_pins(pins); + return 1; + } + my_atomic_add32(&hash->count, -1); + lf_rwunlock_by_pins(pins); + return 0; +} + +/* + RETURN + a pointer to an element with the given key (if a hash is not unique and + there're many elements with this key - the "first" matching element) + NULL if nothing is found + MY_ERRPTR if OOM + + NOTE + see lsearch() for pin usage notes +*/ +void *lf_hash_search(LF_HASH *hash, LF_PINS *pins, const void *key, uint keylen) +{ + LF_SLIST * volatile *el, *found; + uint bucket, hashnr= calc_hash(hash, (uchar *)key, keylen); + + bucket= hashnr % hash->size; + lf_rwlock_by_pins(pins); + el= _lf_dynarray_lvalue(&hash->array, bucket); + if (unlikely(!el)) + return MY_ERRPTR; + if (*el == NULL && unlikely(initialize_bucket(hash, el, bucket, pins))) + return MY_ERRPTR; + found= lsearch(el, hash->charset, my_reverse_bits(hashnr) | 1, + (uchar *)key, keylen, pins); + lf_rwunlock_by_pins(pins); + return found ? found+1 : 0; +} + +static const uchar *dummy_key= (uchar*)""; + +/* + RETURN + 0 - ok + -1 - out of memory +*/ +static int initialize_bucket(LF_HASH *hash, LF_SLIST * volatile *node, + uint bucket, LF_PINS *pins) +{ + uint parent= my_clear_highest_bit(bucket); + LF_SLIST *dummy= (LF_SLIST *)my_malloc(sizeof(LF_SLIST), MYF(MY_WME)); + LF_SLIST **tmp= 0, *cur; + LF_SLIST * volatile *el= _lf_dynarray_lvalue(&hash->array, parent); + if (unlikely(!el || !dummy)) + return -1; + if (*el == NULL && bucket && + unlikely(initialize_bucket(hash, el, parent, pins))) + return -1; + dummy->hashnr= my_reverse_bits(bucket) | 0; /* dummy node */ + dummy->key= dummy_key; + dummy->keylen= 0; + if ((cur= linsert(el, hash->charset, dummy, pins, LF_HASH_UNIQUE))) + { + my_free((void *)dummy, MYF(0)); + dummy= cur; + } + my_atomic_casptr((void **)node, (void **)&tmp, dummy); + /* + note that if the CAS above failed (after linsert() succeeded), + it would mean that some other thread has executed linsert() for + the same dummy node, its linsert() failed, it picked up our + dummy node (in "dummy= cur") and executed the same CAS as above. + Which means that even if CAS above failed we don't need to retry, + and we should not free(dummy) - there's no memory leak here + */ + return 0; +} diff --git a/mysys/my_thr_init.c b/mysys/my_thr_init.c index b2972faf0f8..ba59c483012 100644 --- a/mysys/my_thr_init.c +++ b/mysys/my_thr_init.c @@ -284,6 +284,9 @@ my_bool my_thread_init(void) pthread_cond_init(&tmp->suspend, NULL); tmp->init= 1; + tmp->stack_ends_here= (char*)&tmp + + STACK_DIRECTION * (long)my_thread_stack_size; + pthread_mutex_lock(&THR_LOCK_threads); tmp->id= ++thread_id; ++THR_thread_count; diff --git a/unittest/mysys/Makefile.am b/unittest/mysys/Makefile.am index 6e8058c4d9b..d83b2909048 100644 --- a/unittest/mysys/Makefile.am +++ b/unittest/mysys/Makefile.am @@ -23,7 +23,7 @@ LDADD = $(top_builddir)/unittest/mytap/libmytap.a \ $(top_builddir)/dbug/libdbug.a \ $(top_builddir)/strings/libmystrings.a -noinst_PROGRAMS = bitmap-t base64-t my_vsnprintf-t +noinst_PROGRAMS = bitmap-t base64-t lf-t my_vsnprintf-t if NEED_THREAD # my_atomic-t is used to check thread functions, so it is safe to diff --git a/unittest/mysys/lf-t.c b/unittest/mysys/lf-t.c new file mode 100644 index 00000000000..61b7ae08cf5 --- /dev/null +++ b/unittest/mysys/lf-t.c @@ -0,0 +1,178 @@ +/* Copyright (C) 2008-2008 MySQL AB, 2008-2009 Sun Microsystems, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/** + @file + + Unit tests for lock-free algorithms of mysys +*/ + +#include "thr_template.c" + +#include + +int32 inserts= 0, N; +LF_ALLOCATOR lf_allocator; +LF_HASH lf_hash; + +/* + pin allocator - alloc and release an element in a loop +*/ +pthread_handler_t test_lf_pinbox(void *arg) +{ + int m= *(int *)arg; + int32 x= 0; + LF_PINS *pins; + + my_thread_init(); + + pins= lf_pinbox_get_pins(&lf_allocator.pinbox); + + for (x= ((int)(intptr)(&m)); m ; m--) + { + lf_pinbox_put_pins(pins); + pins= lf_pinbox_get_pins(&lf_allocator.pinbox); + } + lf_pinbox_put_pins(pins); + pthread_mutex_lock(&mutex); + if (!--running_threads) pthread_cond_signal(&cond); + pthread_mutex_unlock(&mutex); + my_thread_end(); + return 0; +} + +/* + thread local data area, allocated using lf_alloc. + union is required to enforce the minimum required element size (sizeof(ptr)) +*/ +typedef union { + int32 data; + void *not_used; +} TLA; + +pthread_handler_t test_lf_alloc(void *arg) +{ + int m= (*(int *)arg)/2; + int32 x,y= 0; + LF_PINS *pins; + + my_thread_init(); + + pins= lf_alloc_get_pins(&lf_allocator); + + for (x= ((int)(intptr)(&m)); m ; m--) + { + TLA *node1, *node2; + x= (x*m+0x87654321) & INT_MAX32; + node1= (TLA *)lf_alloc_new(pins); + node1->data= x; + y+= node1->data; + node1->data= 0; + node2= (TLA *)lf_alloc_new(pins); + node2->data= x; + y-= node2->data; + node2->data= 0; + lf_alloc_free(pins, node1); + lf_alloc_free(pins, node2); + } + lf_alloc_put_pins(pins); + pthread_mutex_lock(&mutex); + bad+= y; + + if (--N == 0) + { + diag("%d mallocs, %d pins in stack", + lf_allocator.mallocs, lf_allocator.pinbox.pins_in_array); +#ifdef MY_LF_EXTRA_DEBUG + bad|= lf_allocator.mallocs - lf_alloc_pool_count(&lf_allocator); +#endif + } + if (!--running_threads) pthread_cond_signal(&cond); + pthread_mutex_unlock(&mutex); + my_thread_end(); + return 0; +} + +#define N_TLH 1000 +pthread_handler_t test_lf_hash(void *arg) +{ + int m= (*(int *)arg)/(2*N_TLH); + int32 x,y,z,sum= 0, ins= 0; + LF_PINS *pins; + + my_thread_init(); + + pins= lf_hash_get_pins(&lf_hash); + + for (x= ((int)(intptr)(&m)); m ; m--) + { + int i; + y= x; + for (i= 0; i < N_TLH; i++) + { + x= (x*(m+i)+0x87654321) & INT_MAX32; + z= (x<0) ? -x : x; + if (lf_hash_insert(&lf_hash, pins, &z)) + { + sum+= z; + ins++; + } + } + for (i= 0; i < N_TLH; i++) + { + y= (y*(m+i)+0x87654321) & INT_MAX32; + z= (y<0) ? -y : y; + if (lf_hash_delete(&lf_hash, pins, (uchar *)&z, sizeof(z))) + sum-= z; + } + } + lf_hash_put_pins(pins); + pthread_mutex_lock(&mutex); + bad+= sum; + inserts+= ins; + + if (--N == 0) + { + diag("%d mallocs, %d pins in stack, %d hash size, %d inserts", + lf_hash.alloc.mallocs, lf_hash.alloc.pinbox.pins_in_array, + lf_hash.size, inserts); + bad|= lf_hash.count; + } + if (!--running_threads) pthread_cond_signal(&cond); + pthread_mutex_unlock(&mutex); + my_thread_end(); + return 0; +} + + +void do_tests() +{ + plan(4); + + lf_alloc_init(&lf_allocator, sizeof(TLA), offsetof(TLA, not_used)); + lf_hash_init(&lf_hash, sizeof(int), LF_HASH_UNIQUE, 0, sizeof(int), 0, + &my_charset_bin); + + bad= my_atomic_initialize(); + ok(!bad, "my_atomic_initialize() returned %d", bad); + + test_concurrently("lf_pinbox", test_lf_pinbox, N= THREADS, CYCLES); + test_concurrently("lf_alloc", test_lf_alloc, N= THREADS, CYCLES); + test_concurrently("lf_hash", test_lf_hash, N= THREADS, CYCLES/10); + + lf_hash_destroy(&lf_hash); + lf_alloc_destroy(&lf_allocator); +} + From 48ea0b83a0a19b2107df4300bda211206b37ea20 Mon Sep 17 00:00:00 2001 From: Marc Alff Date: Tue, 17 Nov 2009 21:29:26 -0700 Subject: [PATCH 36/40] Port the unit test framework to windows Backport from 6.0.14 to 5.6.0 Original code from Guilhem Bichot --- CMakeLists.txt | 11 ++++++- mysys/CMakeLists.txt | 4 ++- unittest/examples/CMakeLists.txt | 38 +++++++++++++++++++++++++ unittest/examples/core-t.c | 2 +- unittest/examples/no_plan-t.c | 2 +- unittest/examples/skip_all-t.c | 2 +- unittest/examples/todo-t.c | 2 +- unittest/mytap/CMakeLists.txt | 21 ++++++++++++++ unittest/mytap/Makefile.am | 7 ++--- unittest/mytap/tap.c | 29 ++++++++++++++++--- unittest/mytap/tap.h | 49 +++++++++++++++++++++++++++++--- unittest/unit.pl | 18 +++++++++--- 12 files changed, 163 insertions(+), 22 deletions(-) create mode 100644 unittest/examples/CMakeLists.txt create mode 100644 unittest/mytap/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 6e53ff30159..7a5ee9223bc 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (C) 2006 MySQL AB +# Copyright (C) 2006 MySQL AB, 2009 Sun Microsystems, Inc # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -284,10 +284,16 @@ CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/sql/sql_builtin.cc.in SET (ENGINE_BUILD_TYPE "STATIC") FOREACH(DIR ${STATIC_ENGINE_DIRECTORIES}) ADD_SUBDIRECTORY(${DIR}) + IF(EXISTS ${DIR}/unittest) + ADD_SUBDIRECTORY(${DIR}/unittest) + ENDIF(EXISTS ${DIR}/unittest) ENDFOREACH(DIR ${STATIC_ENGINE_DIRECTORIES}) SET (ENGINE_BUILD_TYPE "DYNAMIC") FOREACH(DIR ${DYNAMIC_ENGINE_DIRECTORIES}) + IF(EXISTS ${DIR}/unittest) + ADD_SUBDIRECTORY(${DIR}/unittest) + ENDIF(EXISTS ${DIR}/unittest) ADD_SUBDIRECTORY(${DIR}) ENDFOREACH(DIR ${DYNAMIC_ENGINE_DIRECTORIES}) @@ -313,6 +319,9 @@ ADD_SUBDIRECTORY(sql) ADD_SUBDIRECTORY(libmysql) ADD_SUBDIRECTORY(libservices) ADD_SUBDIRECTORY(tests) +ADD_SUBDIRECTORY(unittest/mytap) +ADD_SUBDIRECTORY(unittest/examples) +ADD_SUBDIRECTORY(unittest/mysys) IF(WITH_EMBEDDED_SERVER) ADD_SUBDIRECTORY(libmysqld) ADD_SUBDIRECTORY(libmysqld/examples) diff --git a/mysys/CMakeLists.txt b/mysys/CMakeLists.txt index 21b04e7c968..757e37aa0a9 100755 --- a/mysys/CMakeLists.txt +++ b/mysys/CMakeLists.txt @@ -39,7 +39,9 @@ SET(MYSYS_SOURCES array.c charset-def.c charset.c checksum.c default.c default_ my_static.c my_symlink.c my_symlink2.c my_sync.c my_thr_init.c my_wincond.c my_winerr.c my_winfile.c my_windac.c my_winthread.c my_write.c ptr_cmp.c queues.c stacktrace.c rijndael.c safemalloc.c sha1.c string.c thr_alarm.c thr_lock.c thr_mutex.c - thr_rwlock.c tree.c typelib.c my_vle.c base64.c my_memmem.c my_getpagesize.c) + thr_rwlock.c tree.c typelib.c my_vle.c base64.c my_memmem.c my_getpagesize.c + lf_alloc-pin.c lf_dynarray.c lf_hash.c + my_atomic.c my_getncpus.c) IF(NOT SOURCE_SUBLIBS) ADD_LIBRARY(mysys ${MYSYS_SOURCES}) diff --git a/unittest/examples/CMakeLists.txt b/unittest/examples/CMakeLists.txt new file mode 100644 index 00000000000..a5aa5a93985 --- /dev/null +++ b/unittest/examples/CMakeLists.txt @@ -0,0 +1,38 @@ +# Copyright (C) 2007 MySQL AB +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/zlib + ${CMAKE_SOURCE_DIR}/sql + ${CMAKE_SOURCE_DIR}/regex + ${CMAKE_SOURCE_DIR}/extra/yassl/include + ${CMAKE_SOURCE_DIR}/unittest/mytap) +ADD_EXECUTABLE(simple-t simple-t.c) +TARGET_LINK_LIBRARIES(simple-t mytap) + +ADD_EXECUTABLE(skip-t skip-t.c) +TARGET_LINK_LIBRARIES(skip-t mytap) + +ADD_EXECUTABLE(todo-t todo-t.c) +TARGET_LINK_LIBRARIES(todo-t mytap) + +ADD_EXECUTABLE(skip_all-t skip_all-t.c) +TARGET_LINK_LIBRARIES(skip_all-t mytap) + +ADD_EXECUTABLE(no_plan-t no_plan-t.c) +TARGET_LINK_LIBRARIES(no_plan-t mytap) + +ADD_EXECUTABLE(core-t core-t.c) +TARGET_LINK_LIBRARIES(core-t mytap) diff --git a/unittest/examples/core-t.c b/unittest/examples/core-t.c index cafe2df9954..a9b798d3064 100644 --- a/unittest/examples/core-t.c +++ b/unittest/examples/core-t.c @@ -13,7 +13,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#include "my_config.h" +#include #include #include diff --git a/unittest/examples/no_plan-t.c b/unittest/examples/no_plan-t.c index 56aabd6d752..06378e81218 100644 --- a/unittest/examples/no_plan-t.c +++ b/unittest/examples/no_plan-t.c @@ -13,7 +13,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#include "my_config.h" +#include #include #include diff --git a/unittest/examples/skip_all-t.c b/unittest/examples/skip_all-t.c index a4c8648fbe4..11c1ef13276 100644 --- a/unittest/examples/skip_all-t.c +++ b/unittest/examples/skip_all-t.c @@ -13,7 +13,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#include "my_config.h" +#include #include #include diff --git a/unittest/examples/todo-t.c b/unittest/examples/todo-t.c index 2de409447ba..027d6d6b65e 100644 --- a/unittest/examples/todo-t.c +++ b/unittest/examples/todo-t.c @@ -13,7 +13,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#include "my_config.h" +#include #include #include diff --git a/unittest/mytap/CMakeLists.txt b/unittest/mytap/CMakeLists.txt new file mode 100644 index 00000000000..9875f46697d --- /dev/null +++ b/unittest/mytap/CMakeLists.txt @@ -0,0 +1,21 @@ +# Copyright (C) 2007 MySQL AB +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/zlib + ${CMAKE_SOURCE_DIR}/sql + ${CMAKE_SOURCE_DIR}/regex + ${CMAKE_SOURCE_DIR}/extra/yassl/include) +ADD_LIBRARY(mytap tap.c) diff --git a/unittest/mytap/Makefile.am b/unittest/mytap/Makefile.am index c02bcd3b49d..d36dc25d0b5 100644 --- a/unittest/mytap/Makefile.am +++ b/unittest/mytap/Makefile.am @@ -13,14 +13,13 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -AM_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include -I$(srcdir) +AM_CPPFLAGS = -I$(top_srcdir)/include noinst_LIBRARIES = libmytap.a noinst_HEADERS = tap.h libmytap_a_SOURCES = tap.c -SUBDIRS = . t +EXTRA_DIST = CMakeLists.txt -# Don't update the files from bitkeeper -%::SCCS/s.% +SUBDIRS = . t diff --git a/unittest/mytap/tap.c b/unittest/mytap/tap.c index 4e053e3e745..5cdbfeb428c 100644 --- a/unittest/mytap/tap.c +++ b/unittest/mytap/tap.c @@ -19,7 +19,7 @@ #include "tap.h" -#include "my_config.h" +#include "my_global.h" #include #include @@ -27,6 +27,16 @@ #include #include +/* + Visual Studio 2003 does not know vsnprintf but knows _vsnprintf. + We don't put this #define in config-win.h because we prefer + my_vsnprintf everywhere instead, except when linking with libmysys + is not desirable - the case here. +*/ +#if defined(_MSC_VER) && ( _MSC_VER == 1310 ) +#define vsnprintf _vsnprintf +#endif + /** @defgroup MyTAP_Internal MyTAP Internals @@ -150,8 +160,10 @@ static signal_entry install_signal[]= { { SIGILL, handle_core_signal }, { SIGABRT, handle_core_signal }, { SIGFPE, handle_core_signal }, - { SIGSEGV, handle_core_signal }, - { SIGBUS, handle_core_signal } + { SIGSEGV, handle_core_signal } +#ifdef SIGBUS + , { SIGBUS, handle_core_signal } +#endif #ifdef SIGXCPU , { SIGXCPU, handle_core_signal } #endif @@ -166,13 +178,22 @@ static signal_entry install_signal[]= { #endif }; +int skip_big_tests= 1; + void plan(int const count) { + char *config= getenv("MYTAP_CONFIG"); + size_t i; + + if (config) + skip_big_tests= strcmp(config, "big"); + + setvbuf(tapout, 0, _IONBF, 0); /* provide output at once */ /* Install signal handler */ - size_t i; + for (i= 0; i < sizeof(install_signal)/sizeof(*install_signal); ++i) signal(install_signal[i].signo, install_signal[i].handler); diff --git a/unittest/mytap/tap.h b/unittest/mytap/tap.h index 31ec47d1ef2..d8f617c88fb 100644 --- a/unittest/mytap/tap.h +++ b/unittest/mytap/tap.h @@ -61,6 +61,24 @@ typedef struct TEST_DATA { extern "C" { #endif +/** + Defines whether "big" tests should be skipped. + + This variable is set by plan() function unless MYTAP_CONFIG environment + variable is set to the string "big". It is supposed to be used as + + @code + if (skip_big_tests) { + skip(1, "Big test skipped"); + } else { + ok(life_universe_and_everything() == 42, "The answer is CORRECT"); + } + @endcode + + @see SKIP_BIG_TESTS +*/ +extern int skip_big_tests; + /** @defgroup MyTAP_API MyTAP API @@ -81,10 +99,15 @@ extern "C" { that generate a core, so if you want to override these signals, do it after you have called the plan() function. - @param count The planned number of tests to run. + It will also set skip_big_tests variable if MYTAP_CONFIG environment + variable is defined. + + @see skip_big_tests + + @param count The planned number of tests to run. */ -void plan(int count); +void plan(int const count); /** @@ -103,7 +126,7 @@ void plan(int count); which case nothing is printed. */ -void ok(int pass, char const *fmt, ...) +void ok(int const pass, char const *fmt, ...) __attribute__((format(printf,2,3))); @@ -135,7 +158,7 @@ void ok(int pass, char const *fmt, ...) @param reason A reason for skipping the tests */ -void skip(int how_many, char const *reason, ...) +void skip(int how_many, char const *const reason, ...) __attribute__((format(printf,2,3))); @@ -160,6 +183,24 @@ void skip(int how_many, char const *reason, ...) if (SKIP_IF_TRUE) skip((COUNT),(REASON)); else +/** + Helper macro to skip a group of "big" tests. It is used in the following + manner: + + @code + SKIP_BIG_TESTS(1) + { + ok(life_universe_and_everything() == 42, "The answer is CORRECT"); + } + @endcode + + @see skip_big_tests + */ + +#define SKIP_BIG_TESTS(COUNT) \ + if (skip_big_tests) skip((COUNT), "big test"); else + + /** Print a diagnostics message. diff --git a/unittest/unit.pl b/unittest/unit.pl index 9d328985012..a1aab376fdf 100644 --- a/unittest/unit.pl +++ b/unittest/unit.pl @@ -14,8 +14,9 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -use Test::Harness qw(&runtests $verbose); +use Test::Harness; use File::Find; +use Getopt::Long; use strict; @@ -31,10 +32,19 @@ unit - Run unit tests in directory =head1 SYNOPSIS - unit run + unit [--[no]big] [--[no]verbose] run [tests to run] =cut +my $big= $ENV{'MYTAP_CONFIG'} eq 'big'; + +my $result = GetOptions ( + "big!" => \$big, + "verbose!" => \$Test::Harness::verbose, +); + +$ENV{'MYTAP_CONFIG'} = $big ? 'big' : ''; + my $cmd = shift; if (defined $cmd && exists $dispatch{$cmd}) { @@ -56,7 +66,7 @@ sub _find_test_files (@) { my @files; find sub { $File::Find::prune = 1 if /^SCCS$/; - push(@files, $File::Find::name) if -x _ && /-t\z/; + push(@files, $File::Find::name) if -x _ && (/-t\z/ || /-t\.exe\z/); }, @dirs; return @files; } @@ -92,7 +102,7 @@ sub run_cmd (@) { if (@files > 0) { # Removing the first './' from the file names foreach (@files) { s!^\./!! } - $ENV{'HARNESS_PERL_SWITCHES'} .= q" -e 'exec @ARGV'"; + $ENV{'HARNESS_PERL_SWITCHES'} .= ' -e "exec @ARGV"'; runtests @files; } } From dd383cadec0fbfba626bba72051f2dc548171015 Mon Sep 17 00:00:00 2001 From: He Zhenxing Date: Sat, 21 Nov 2009 12:28:01 +0800 Subject: [PATCH 37/40] BUG#37148 Most callers of mysql_bin_log.write ignore the return result This is the non-ndb part of the patch. The return value of mysql_bin_log.write was ignored by most callers, which may lead to inconsistent on master and slave if the transaction was committed while the binlog was not correctly written. If my_error() is call in mysql_bin_log.write, this could also lead to assertion issue if my_ok() or my_error() is called after. This fixed the problem by let the caller to check and handle the return value of mysql_bin_log.write. This patch only adresses the simple cases. mysql-test/include/binlog_inject_error.inc: inject binlog write error when doing a query mysql-test/suite/binlog/t/binlog_write_error.test: Simple test case to check if proper error is reported when injecting binlog write errors. sql/events.cc: check return value of mysql_bin_log.write sql/log.cc: check return value of mysql_bin_log.write sql/log_event.cc: check return value of mysql_bin_log.write sql/log_event_old.cc: check return value of mysql_bin_log.write sql/mysql_priv.h: Change write_bin_log to return int instead of void sql/rpl_injector.cc: check return value of writing binlog sql/sp.cc: check return value of writing binlog sql/sp_head.cc: return 1 if writing binlog failed sql/sql_acl.cc: check return value of writing binlog sql/sql_base.cc: check return value of writing binlog sql/sql_class.h: Change binlog_show_create_table to return int sql/sql_db.cc: Change write_to_binlog to return int check return value of writing binlog sql/sql_delete.cc: check return value of writing binlog sql/sql_insert.cc: check return value of writing binlog sql/sql_load.cc: check return value of writing binlog sql/sql_parse.cc: check return value of writing binlog sql/sql_partition.cc: check return value of writing binlog sql/sql_rename.cc: check return value of writing binlog sql/sql_repl.cc: check return value of writing binlog sql/sql_table.cc: Change write_bin_log to return int, and return 1 if there was error writing binlog sql/sql_tablespace.cc: check return value of writing binlog sql/sql_trigger.cc: check return value of writing binlog sql/sql_udf.cc: check return value of writing binlog sql/sql_update.cc: check return value of writing binlog sql/sql_view.cc: check return value of writing binlog --- mysql-test/include/binlog_inject_error.inc | 22 ++++ .../suite/binlog/r/binlog_write_error.result | 108 ++++++++++++++++++ .../suite/binlog/t/binlog_write_error.test | 101 ++++++++++++++++ sql/events.cc | 6 +- sql/log.cc | 4 +- sql/log_event.cc | 6 +- sql/log_event_old.cc | 15 ++- sql/mysql_priv.h | 4 +- sql/rpl_injector.cc | 41 ++++--- sql/sp.cc | 13 ++- sql/sp_head.cc | 1 + sql/sql_acl.cc | 25 ++-- sql/sql_base.cc | 18 ++- sql/sql_class.h | 2 +- sql/sql_db.cc | 35 ++++-- sql/sql_delete.cc | 12 +- sql/sql_insert.cc | 51 +++++---- sql/sql_load.cc | 32 +++--- sql/sql_parse.cc | 17 +-- sql/sql_partition.cc | 5 +- sql/sql_rename.cc | 9 +- sql/sql_repl.cc | 13 ++- sql/sql_table.cc | 61 +++++----- sql/sql_tablespace.cc | 4 +- sql/sql_trigger.cc | 2 +- sql/sql_udf.cc | 8 +- sql/sql_update.cc | 7 +- sql/sql_view.cc | 8 +- 28 files changed, 471 insertions(+), 159 deletions(-) create mode 100644 mysql-test/include/binlog_inject_error.inc create mode 100644 mysql-test/suite/binlog/r/binlog_write_error.result create mode 100644 mysql-test/suite/binlog/t/binlog_write_error.test diff --git a/mysql-test/include/binlog_inject_error.inc b/mysql-test/include/binlog_inject_error.inc new file mode 100644 index 00000000000..6465f7943a4 --- /dev/null +++ b/mysql-test/include/binlog_inject_error.inc @@ -0,0 +1,22 @@ +# +# === Name +# +# binlog_inject_error.inc +# +# === Description +# +# Inject binlog write error when running the query, verifies that the +# query is ended with the proper error (ER_ERROR_ON_WRITE). +# +# === Usage +# +# let query= 'CREATE TABLE t1 (a INT)'; +# source include/binlog_inject_error.inc; +# + +SET GLOBAL debug='d,injecting_fault_writing'; +--echo $query; +--replace_regex /(errno: .*)/(errno: #)/ +--error ER_ERROR_ON_WRITE +--eval $query +SET GLOBAL debug=''; diff --git a/mysql-test/suite/binlog/r/binlog_write_error.result b/mysql-test/suite/binlog/r/binlog_write_error.result new file mode 100644 index 00000000000..dd8bdb9715a --- /dev/null +++ b/mysql-test/suite/binlog/r/binlog_write_error.result @@ -0,0 +1,108 @@ +# +# Initialization +# +DROP TABLE IF EXISTS t1, t2; +DROP FUNCTION IF EXISTS f1; +DROP FUNCTION IF EXISTS f2; +DROP PROCEDURE IF EXISTS p1; +DROP PROCEDURE IF EXISTS p2; +DROP TRIGGER IF EXISTS tr1; +DROP TRIGGER IF EXISTS tr2; +DROP VIEW IF EXISTS v1, v2; +# +# Test injecting binlog write error when executing queries +# +SET GLOBAL debug='d,injecting_fault_writing'; +CREATE TABLE t1 (a INT); +CREATE TABLE t1 (a INT); +ERROR HY000: Error writing file 'master-bin' ((errno: #) +SET GLOBAL debug=''; +INSERT INTO t1 VALUES (1),(2),(3); +SET GLOBAL debug='d,injecting_fault_writing'; +INSERT INTO t1 VALUES (4),(5),(6); +INSERT INTO t1 VALUES (4),(5),(6); +ERROR HY000: Error writing file 'master-bin' ((errno: #) +SET GLOBAL debug=''; +SET GLOBAL debug='d,injecting_fault_writing'; +UPDATE t1 set a=a+1; +UPDATE t1 set a=a+1; +ERROR HY000: Error writing file 'master-bin' ((errno: #) +SET GLOBAL debug=''; +SET GLOBAL debug='d,injecting_fault_writing'; +DELETE FROM t1; +DELETE FROM t1; +ERROR HY000: Error writing file 'master-bin' ((errno: #) +SET GLOBAL debug=''; +SET GLOBAL debug='d,injecting_fault_writing'; +CREATE TRIGGER tr1 AFTER INSERT ON t1 FOR EACH ROW INSERT INTO t1 VALUES (new.a + 100); +CREATE TRIGGER tr1 AFTER INSERT ON t1 FOR EACH ROW INSERT INTO t1 VALUES (new.a + 100); +ERROR HY000: Error writing file 'master-bin' ((errno: #) +SET GLOBAL debug=''; +SET GLOBAL debug='d,injecting_fault_writing'; +DROP TRIGGER tr1; +DROP TRIGGER tr1; +ERROR HY000: Error writing file 'master-bin' ((errno: #) +SET GLOBAL debug=''; +SET GLOBAL debug='d,injecting_fault_writing'; +ALTER TABLE t1 ADD (b INT); +ALTER TABLE t1 ADD (b INT); +ERROR HY000: Error writing file 'master-bin' ((errno: #) +SET GLOBAL debug=''; +SET GLOBAL debug='d,injecting_fault_writing'; +CREATE VIEW v1 AS SELECT a FROM t1; +CREATE VIEW v1 AS SELECT a FROM t1; +ERROR HY000: Error writing file 'master-bin' ((errno: #) +SET GLOBAL debug=''; +SET GLOBAL debug='d,injecting_fault_writing'; +DROP VIEW v1; +DROP VIEW v1; +ERROR HY000: Error writing file 'master-bin' ((errno: #) +SET GLOBAL debug=''; +SET GLOBAL debug='d,injecting_fault_writing'; +CREATE PROCEDURE p1(OUT rows INT) SELECT count(*) INTO rows FROM t1; +CREATE PROCEDURE p1(OUT rows INT) SELECT count(*) INTO rows FROM t1; +ERROR HY000: Error writing file 'master-bin' ((errno: #) +SET GLOBAL debug=''; +SET GLOBAL debug='d,injecting_fault_writing'; +DROP PROCEDURE p1; +DROP PROCEDURE p1; +ERROR HY000: Error writing file 'master-bin' ((errno: #) +SET GLOBAL debug=''; +SET GLOBAL debug='d,injecting_fault_writing'; +DROP TABLE t1; +DROP TABLE t1; +ERROR HY000: Error writing file 'master-bin' ((errno: #) +SET GLOBAL debug=''; +SET GLOBAL debug='d,injecting_fault_writing'; +CREATE FUNCTION f1() RETURNS INT return 1; +CREATE FUNCTION f1() RETURNS INT return 1; +ERROR HY000: Error writing file 'master-bin' ((errno: #) +SET GLOBAL debug=''; +SET GLOBAL debug='d,injecting_fault_writing'; +DROP FUNCTION f1; +DROP FUNCTION f1; +ERROR HY000: Error writing file 'master-bin' ((errno: #) +SET GLOBAL debug=''; +SET GLOBAL debug='d,injecting_fault_writing'; +CREATE USER user1; +CREATE USER user1; +ERROR HY000: Error writing file 'master-bin' ((errno: #) +SET GLOBAL debug=''; +SET GLOBAL debug='d,injecting_fault_writing'; +REVOKE ALL PRIVILEGES, GRANT OPTION FROM user1; +REVOKE ALL PRIVILEGES, GRANT OPTION FROM user1; +ERROR HY000: Error writing file 'master-bin' ((errno: #) +SET GLOBAL debug=''; +SET GLOBAL debug='d,injecting_fault_writing'; +DROP USER user1; +DROP USER user1; +ERROR HY000: Error writing file 'master-bin' ((errno: #) +SET GLOBAL debug=''; +# +# Cleanup +# +DROP TABLE IF EXISTS t1, t2; +DROP FUNCTION IF EXISTS f1; +DROP PROCEDURE IF EXISTS p1; +DROP TRIGGER IF EXISTS tr1; +DROP VIEW IF EXISTS v1, v2; diff --git a/mysql-test/suite/binlog/t/binlog_write_error.test b/mysql-test/suite/binlog/t/binlog_write_error.test new file mode 100644 index 00000000000..0e57db3d9a0 --- /dev/null +++ b/mysql-test/suite/binlog/t/binlog_write_error.test @@ -0,0 +1,101 @@ +# +# === Name === +# +# binlog_write_error.test +# +# === Description === +# +# This test case check if the error of writing binlog file is properly +# reported and handled when executing statements. +# +# === Related Bugs === +# +# BUG#37148 +# + +source include/have_log_bin.inc; +source include/have_debug.inc; + +--echo # +--echo # Initialization +--echo # + +disable_warnings; +DROP TABLE IF EXISTS t1, t2; +DROP FUNCTION IF EXISTS f1; +DROP FUNCTION IF EXISTS f2; +DROP PROCEDURE IF EXISTS p1; +DROP PROCEDURE IF EXISTS p2; +DROP TRIGGER IF EXISTS tr1; +DROP TRIGGER IF EXISTS tr2; +DROP VIEW IF EXISTS v1, v2; +enable_warnings; + +--echo # +--echo # Test injecting binlog write error when executing queries +--echo # + +let $query= CREATE TABLE t1 (a INT); +source include/binlog_inject_error.inc; + +INSERT INTO t1 VALUES (1),(2),(3); + +let $query= INSERT INTO t1 VALUES (4),(5),(6); +source include/binlog_inject_error.inc; + +let $query= UPDATE t1 set a=a+1; +source include/binlog_inject_error.inc; + +let $query= DELETE FROM t1; +source include/binlog_inject_error.inc; + +let $query= CREATE TRIGGER tr1 AFTER INSERT ON t1 FOR EACH ROW INSERT INTO t1 VALUES (new.a + 100); +source include/binlog_inject_error.inc; + +let $query= DROP TRIGGER tr1; +source include/binlog_inject_error.inc; + +let $query= ALTER TABLE t1 ADD (b INT); +source include/binlog_inject_error.inc; + +let $query= CREATE VIEW v1 AS SELECT a FROM t1; +source include/binlog_inject_error.inc; + +let $query= DROP VIEW v1; +source include/binlog_inject_error.inc; + +let $query= CREATE PROCEDURE p1(OUT rows INT) SELECT count(*) INTO rows FROM t1; +source include/binlog_inject_error.inc; + +let $query= DROP PROCEDURE p1; +source include/binlog_inject_error.inc; + +let $query= DROP TABLE t1; +source include/binlog_inject_error.inc; + +let $query= CREATE FUNCTION f1() RETURNS INT return 1; +source include/binlog_inject_error.inc; + +let $query= DROP FUNCTION f1; +source include/binlog_inject_error.inc; + +let $query= CREATE USER user1; +source include/binlog_inject_error.inc; + +let $query= REVOKE ALL PRIVILEGES, GRANT OPTION FROM user1; +source include/binlog_inject_error.inc; + +let $query= DROP USER user1; +source include/binlog_inject_error.inc; + +--echo # +--echo # Cleanup +--echo # + +disable_warnings; +DROP TABLE IF EXISTS t1, t2; +DROP FUNCTION IF EXISTS f1; +DROP PROCEDURE IF EXISTS p1; +DROP TRIGGER IF EXISTS tr1; +DROP VIEW IF EXISTS v1, v2; +enable_warnings; diff --git a/sql/events.cc b/sql/events.cc index f7ff2b0ccf1..af40b31c80e 100644 --- a/sql/events.cc +++ b/sql/events.cc @@ -475,7 +475,7 @@ Events::create_event(THD *thd, Event_parse_data *parse_data, } /* If the definer is not set or set to CURRENT_USER, the value of CURRENT_USER will be written into the binary log as the definer for the SQL thread. */ - write_bin_log(thd, TRUE, log_query.c_ptr(), log_query.length()); + ret= write_bin_log(thd, TRUE, log_query.c_ptr(), log_query.length()); } } pthread_mutex_unlock(&LOCK_event_metadata); @@ -596,7 +596,7 @@ Events::update_event(THD *thd, Event_parse_data *parse_data, new_element); /* Binlog the alter event. */ DBUG_ASSERT(thd->query() && thd->query_length()); - write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + ret= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); } } pthread_mutex_unlock(&LOCK_event_metadata); @@ -671,7 +671,7 @@ Events::drop_event(THD *thd, LEX_STRING dbname, LEX_STRING name, bool if_exists) event_queue->drop_event(thd, dbname, name); /* Binlog the drop event. */ DBUG_ASSERT(thd->query() && thd->query_length()); - write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + ret= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); } pthread_mutex_unlock(&LOCK_event_metadata); DBUG_RETURN(ret); diff --git a/sql/log.cc b/sql/log.cc index e7deebad196..e34b54f5c3b 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -1477,7 +1477,7 @@ binlog_end_trans(THD *thd, binlog_trx_data *trx_data, if (all || !(thd->options & (OPTION_BEGIN | OPTION_NOT_AUTOCOMMIT))) { if (trx_data->has_incident()) - mysql_bin_log.write_incident(thd, TRUE); + error= mysql_bin_log.write_incident(thd, TRUE); trx_data->reset(); } else // ...statement @@ -4506,7 +4506,7 @@ bool MYSQL_BIN_LOG::write_incident(THD *thd, bool lock) Incident_log_event ev(thd, incident, write_error_msg); if (lock) pthread_mutex_lock(&LOCK_log); - ev.write(&log_file); + error= ev.write(&log_file); if (lock) { if (!error && !(error= flush_and_sync(0))) diff --git a/sql/log_event.cc b/sql/log_event.cc index 3a54717a45f..2c8f7d8d761 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -5875,7 +5875,7 @@ Slave_log_event::Slave_log_event(const char* buf, uint event_len) int Slave_log_event::do_apply_event(Relay_log_info const *rli) { if (mysql_bin_log.is_open()) - mysql_bin_log.write(this); + return mysql_bin_log.write(this); return 0; } #endif /* !MYSQL_CLIENT */ @@ -7604,7 +7604,7 @@ static int rows_event_stmt_cleanup(Relay_log_info const *rli, THD * thd) (assume the last master's transaction is ignored by the slave because of replicate-ignore rules). */ - thd->binlog_flush_pending_rows_event(true); + error= thd->binlog_flush_pending_rows_event(true); /* If this event is not in a transaction, the call below will, if some @@ -7615,7 +7615,7 @@ static int rows_event_stmt_cleanup(Relay_log_info const *rli, THD * thd) are involved, commit the transaction and flush the pending event to the binlog. */ - error= ha_autocommit_or_rollback(thd, 0); + error|= ha_autocommit_or_rollback(thd, error); /* Now what if this is not a transactional engine? we still need to diff --git a/sql/log_event_old.cc b/sql/log_event_old.cc index fbcbb388236..72affd2bee9 100644 --- a/sql/log_event_old.cc +++ b/sql/log_event_old.cc @@ -1513,7 +1513,15 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) NOTE: For this new scheme there should be no pending event: need to add code to assert that is the case. */ - thd->binlog_flush_pending_rows_event(false); + error= thd->binlog_flush_pending_rows_event(false); + if (error) + { + rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, + ER(ER_SLAVE_FATAL_ERROR), + "call to binlog_flush_pending_rows_event() failed"); + thd->is_slave_error= 1; + DBUG_RETURN(error); + } TABLE_LIST *tables= rli->tables_to_lock; close_tables_for_reopen(thd, &tables); @@ -1803,7 +1811,7 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) (assume the last master's transaction is ignored by the slave because of replicate-ignore rules). */ - thd->binlog_flush_pending_rows_event(true); + int binlog_error= thd->binlog_flush_pending_rows_event(true); /* If this event is not in a transaction, the call below will, if some @@ -1814,12 +1822,13 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) are involved, commit the transaction and flush the pending event to the binlog. */ - if ((error= ha_autocommit_or_rollback(thd, 0))) + if ((error= ha_autocommit_or_rollback(thd, binlog_error))) rli->report(ERROR_LEVEL, error, "Error in %s event: commit of row events failed, " "table `%s`.`%s`", get_type_str(), m_table->s->db.str, m_table->s->table_name.str); + error|= binlog_error; /* Now what if this is not a transactional engine? we still need to diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 3cb26c30e13..b3e614e0c7a 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -977,8 +977,8 @@ struct Query_cache_query_flags #define query_cache_is_cacheable_query(L) 0 #endif /*HAVE_QUERY_CACHE*/ -void write_bin_log(THD *thd, bool clear_error, - char const *query, ulong query_length); +int write_bin_log(THD *thd, bool clear_error, + char const *query, ulong query_length); /* sql_connect.cc */ int check_user(THD *thd, enum enum_server_command command, diff --git a/sql/rpl_injector.cc b/sql/rpl_injector.cc index 684655d1c3b..666622dbac4 100644 --- a/sql/rpl_injector.cc +++ b/sql/rpl_injector.cc @@ -58,10 +58,14 @@ injector::transaction::~transaction() my_free(the_memory, MYF(0)); } +/** + @retval 0 transaction committed + @retval 1 transaction rolled back + */ int injector::transaction::commit() { DBUG_ENTER("injector::transaction::commit()"); - m_thd->binlog_flush_pending_rows_event(true); + int error= m_thd->binlog_flush_pending_rows_event(true); /* Cluster replication does not preserve statement or transaction boundaries of the master. Instead, a new @@ -81,9 +85,9 @@ int injector::transaction::commit() is committed by committing the statement transaction explicitly. */ - ha_autocommit_or_rollback(m_thd, 0); - end_trans(m_thd, COMMIT); - DBUG_RETURN(0); + error |= ha_autocommit_or_rollback(m_thd, error); + end_trans(m_thd, error ? ROLLBACK : COMMIT); + DBUG_RETURN(error); } int injector::transaction::use_table(server_id_type sid, table tbl) @@ -109,16 +113,17 @@ int injector::transaction::write_row (server_id_type sid, table tbl, record_type record) { DBUG_ENTER("injector::transaction::write_row(...)"); - - if (int error= check_state(ROW_STATE)) + + int error= 0; + if (error= check_state(ROW_STATE)) DBUG_RETURN(error); server_id_type save_id= m_thd->server_id; m_thd->set_server_id(sid); - m_thd->binlog_write_row(tbl.get_table(), tbl.is_transactional(), - cols, colcnt, record); + error= m_thd->binlog_write_row(tbl.get_table(), tbl.is_transactional(), + cols, colcnt, record); m_thd->set_server_id(save_id); - DBUG_RETURN(0); + DBUG_RETURN(error); } @@ -128,15 +133,16 @@ int injector::transaction::delete_row(server_id_type sid, table tbl, { DBUG_ENTER("injector::transaction::delete_row(...)"); - if (int error= check_state(ROW_STATE)) + int error= 0; + if (error= check_state(ROW_STATE)) DBUG_RETURN(error); server_id_type save_id= m_thd->server_id; m_thd->set_server_id(sid); - m_thd->binlog_delete_row(tbl.get_table(), tbl.is_transactional(), - cols, colcnt, record); + error= m_thd->binlog_delete_row(tbl.get_table(), tbl.is_transactional(), + cols, colcnt, record); m_thd->set_server_id(save_id); - DBUG_RETURN(0); + DBUG_RETURN(error); } @@ -146,15 +152,16 @@ int injector::transaction::update_row(server_id_type sid, table tbl, { DBUG_ENTER("injector::transaction::update_row(...)"); - if (int error= check_state(ROW_STATE)) + int error= 0; + if (error= check_state(ROW_STATE)) DBUG_RETURN(error); server_id_type save_id= m_thd->server_id; m_thd->set_server_id(sid); - m_thd->binlog_update_row(tbl.get_table(), tbl.is_transactional(), - cols, colcnt, before, after); + error= m_thd->binlog_update_row(tbl.get_table(), tbl.is_transactional(), + cols, colcnt, before, after); m_thd->set_server_id(save_id); - DBUG_RETURN(0); + DBUG_RETURN(error); } diff --git a/sql/sp.cc b/sql/sp.cc index f756e8ddb18..3e8ef5d4dc8 100644 --- a/sql/sp.cc +++ b/sql/sp.cc @@ -949,9 +949,10 @@ sp_create_routine(THD *thd, int type, sp_head *sp) /* restore sql_mode when binloging */ thd->variables.sql_mode= saved_mode; /* Such a statement can always go directly to binlog, no trans cache */ - thd->binlog_query(THD::MYSQL_QUERY_TYPE, - log_query.c_ptr(), log_query.length(), - FALSE, FALSE, 0); + if (thd->binlog_query(THD::MYSQL_QUERY_TYPE, + log_query.c_ptr(), log_query.length(), + FALSE, FALSE, 0)) + ret= SP_INTERNAL_ERROR; thd->variables.sql_mode= 0; } @@ -1010,7 +1011,8 @@ sp_drop_routine(THD *thd, int type, sp_name *name) if (ret == SP_OK) { - write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + if (write_bin_log(thd, TRUE, thd->query(), thd->query_length())) + ret= SP_INTERNAL_ERROR; sp_cache_invalidate(); } @@ -1080,7 +1082,8 @@ sp_update_routine(THD *thd, int type, sp_name *name, st_sp_chistics *chistics) if (ret == SP_OK) { - write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + if (write_bin_log(thd, TRUE, thd->query(), thd->query_length())) + ret= SP_INTERNAL_ERROR; sp_cache_invalidate(); } diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 7cabae255cc..19e6a9ffb72 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -1807,6 +1807,7 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR, "Invoked ROUTINE modified a transactional table but MySQL " "failed to reflect this change in the binary log"); + err_status= TRUE; } reset_dynamic(&thd->user_var_events); /* Forget those values, in case more function calls are binlogged: */ diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index 46d7f3ce89d..26eb3bf8a24 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -1654,8 +1654,8 @@ bool change_password(THD *thd, const char *host, const char *user, acl_user->host.hostname ? acl_user->host.hostname : "", new_password)); thd->clear_error(); - thd->binlog_query(THD::MYSQL_QUERY_TYPE, buff, query_length, - FALSE, FALSE, 0); + result= thd->binlog_query(THD::MYSQL_QUERY_TYPE, buff, query_length, + FALSE, FALSE, 0); } end: close_thread_tables(thd); @@ -3202,7 +3202,7 @@ int mysql_table_grant(THD *thd, TABLE_LIST *table_list, if (!result) /* success */ { - write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + result= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); } rw_unlock(&LOCK_grant); @@ -3367,7 +3367,8 @@ bool mysql_routine_grant(THD *thd, TABLE_LIST *table_list, bool is_proc, if (write_to_binlog) { - write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + if (write_bin_log(thd, TRUE, thd->query(), thd->query_length())) + result= TRUE; } rw_unlock(&LOCK_grant); @@ -3479,7 +3480,7 @@ bool mysql_grant(THD *thd, const char *db, List &list, if (!result) { - write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + result= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); } rw_unlock(&LOCK_grant); @@ -5803,7 +5804,7 @@ bool mysql_create_user(THD *thd, List &list) my_error(ER_CANNOT_USER, MYF(0), "CREATE USER", wrong_users.c_ptr_safe()); if (some_users_created) - write_bin_log(thd, FALSE, thd->query(), thd->query_length()); + result |= write_bin_log(thd, FALSE, thd->query(), thd->query_length()); rw_unlock(&LOCK_grant); close_thread_tables(thd); @@ -5876,7 +5877,7 @@ bool mysql_drop_user(THD *thd, List &list) my_error(ER_CANNOT_USER, MYF(0), "DROP USER", wrong_users.c_ptr_safe()); if (some_users_deleted) - write_bin_log(thd, FALSE, thd->query(), thd->query_length()); + result |= write_bin_log(thd, FALSE, thd->query(), thd->query_length()); rw_unlock(&LOCK_grant); close_thread_tables(thd); @@ -5961,7 +5962,7 @@ bool mysql_rename_user(THD *thd, List &list) my_error(ER_CANNOT_USER, MYF(0), "RENAME USER", wrong_users.c_ptr_safe()); if (some_users_renamed && mysql_bin_log.is_open()) - write_bin_log(thd, FALSE, thd->query(), thd->query_length()); + result |= write_bin_log(thd, FALSE, thd->query(), thd->query_length()); rw_unlock(&LOCK_grant); close_thread_tables(thd); @@ -6143,15 +6144,17 @@ bool mysql_revoke_all(THD *thd, List &list) VOID(pthread_mutex_unlock(&acl_cache->lock)); - write_bin_log(thd, FALSE, thd->query(), thd->query_length()); + int binlog_error= + write_bin_log(thd, FALSE, thd->query(), thd->query_length()); rw_unlock(&LOCK_grant); close_thread_tables(thd); - if (result) + /* error for writing binary log has already been reported */ + if (result && !binlog_error) my_message(ER_REVOKE_GRANTS, ER(ER_REVOKE_GRANTS), MYF(0)); - DBUG_RETURN(result); + DBUG_RETURN(result || binlog_error); } diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 2c0ba87262a..435bd053472 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -1344,7 +1344,7 @@ void close_thread_tables(THD *thd) handled either before writing a query log event (inside binlog_query()) or when preparing a pending event. */ - thd->binlog_flush_pending_rows_event(TRUE); + (void)thd->binlog_flush_pending_rows_event(TRUE); mysql_unlock_tables(thd, thd->lock); thd->lock=0; } @@ -1558,7 +1558,11 @@ void close_temporary_tables(THD *thd) qinfo.db= db.ptr(); qinfo.db_len= db.length(); thd->variables.character_set_client= cs_save; - mysql_bin_log.write(&qinfo); + if (mysql_bin_log.write(&qinfo)) + { + push_warning(thd, MYSQL_ERROR::WARN_LEVEL_ERROR, MYF(0), + "Failed to write the DROP statement for temporary tables to binary log"); + } thd->variables.pseudo_thread_id= save_pseudo_thread_id; } else @@ -4034,9 +4038,13 @@ retry: end = strxmov(strmov(query, "DELETE FROM `"), share->db.str,"`.`",share->table_name.str,"`", NullS); int errcode= query_error_code(thd, TRUE); - thd->binlog_query(THD::STMT_QUERY_TYPE, - query, (ulong)(end-query), - FALSE, FALSE, errcode); + if (thd->binlog_query(THD::STMT_QUERY_TYPE, + query, (ulong)(end-query), + FALSE, FALSE, errcode)) + { + my_free(query, MYF(0)); + goto err; + } my_free(query, MYF(0)); } else diff --git a/sql/sql_class.h b/sql/sql_class.h index 01ac3c12641..0015a97e319 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -2620,7 +2620,7 @@ public: {} int prepare(List &list, SELECT_LEX_UNIT *u); - void binlog_show_create_table(TABLE **tables, uint count); + int binlog_show_create_table(TABLE **tables, uint count); void store_values(List &values); void send_error(uint errcode,const char *err); bool send_eof(); diff --git a/sql/sql_db.cc b/sql/sql_db.cc index 3198791d5d1..b8b68e1086e 100644 --- a/sql/sql_db.cc +++ b/sql/sql_db.cc @@ -178,13 +178,13 @@ uchar* dboptions_get_key(my_dbopt_t *opt, size_t *length, Helper function to write a query to binlog used by mysql_rm_db() */ -static inline void write_to_binlog(THD *thd, char *query, uint q_len, - char *db, uint db_len) +static inline int write_to_binlog(THD *thd, char *query, uint q_len, + char *db, uint db_len) { Query_log_event qinfo(thd, query, q_len, 0, 0, 0); qinfo.db= db; qinfo.db_len= db_len; - mysql_bin_log.write(&qinfo); + return mysql_bin_log.write(&qinfo); } @@ -747,7 +747,11 @@ not_silent: qinfo.db_len = strlen(db); /* These DDL methods and logging protected with LOCK_mysql_create_db */ - mysql_bin_log.write(&qinfo); + if (mysql_bin_log.write(&qinfo)) + { + error= -1; + goto exit; + } } my_ok(thd, result); } @@ -825,7 +829,8 @@ bool mysql_alter_db(THD *thd, const char *db, HA_CREATE_INFO *create_info) thd->clear_error(); /* These DDL methods and logging protected with LOCK_mysql_create_db */ - mysql_bin_log.write(&qinfo); + if (error= mysql_bin_log.write(&qinfo)) + goto exit; } my_ok(thd, result); @@ -976,7 +981,11 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent) thd->clear_error(); /* These DDL methods and logging protected with LOCK_mysql_create_db */ - mysql_bin_log.write(&qinfo); + if (mysql_bin_log.write(&qinfo)) + { + error= -1; + goto exit; + } } thd->clear_error(); thd->server_status|= SERVER_STATUS_DB_DROPPED; @@ -1004,7 +1013,11 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent) if (query_pos + tbl_name_len + 1 >= query_end) { /* These DDL methods and logging protected with LOCK_mysql_create_db */ - write_to_binlog(thd, query, query_pos -1 - query, db, db_len); + if (write_to_binlog(thd, query, query_pos -1 - query, db, db_len)) + { + error= -1; + goto exit; + } query_pos= query_data_start; } @@ -1017,7 +1030,11 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent) if (query_pos != query_data_start) { /* These DDL methods and logging protected with LOCK_mysql_create_db */ - write_to_binlog(thd, query, query_pos -1 - query, db, db_len); + if (write_to_binlog(thd, query, query_pos -1 - query, db, db_len)) + { + error= -1; + goto exit; + } } } @@ -1968,7 +1985,7 @@ bool mysql_upgrade_db(THD *thd, LEX_STRING *old_db) Query_log_event qinfo(thd, thd->query(), thd->query_length(), 0, TRUE, errcode); thd->clear_error(); - mysql_bin_log.write(&qinfo); + error|= mysql_bin_log.write(&qinfo); } /* Step9: Let's do "use newdb" if we renamed the current database */ diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 6b59cbc14c1..23470b4abc9 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -849,9 +849,10 @@ void multi_delete::abort() if (mysql_bin_log.is_open()) { int errcode= query_error_code(thd, thd->killed == THD::NOT_KILLED); - thd->binlog_query(THD::ROW_QUERY_TYPE, - thd->query(), thd->query_length(), - transactional_tables, FALSE, errcode); + /* possible error of writing binary log is ignored deliberately */ + (void) thd->binlog_query(THD::ROW_QUERY_TYPE, + thd->query(), thd->query_length(), + transactional_tables, FALSE, errcode); } thd->transaction.all.modified_non_trans_table= true; } @@ -1181,8 +1182,9 @@ end: TRUNCATE must always be statement-based binlogged (not row-based) so we don't test current_stmt_binlog_row_based. */ - write_bin_log(thd, TRUE, thd->query(), thd->query_length()); - my_ok(thd); // This should return record count + error= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + if (!error) + my_ok(thd); // This should return record count } VOID(pthread_mutex_lock(&LOCK_open)); unlock_table_name(thd, table_list); diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 173e7f1ca2f..681ab81eeaf 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -2734,10 +2734,11 @@ bool Delayed_insert::handle_inserts(void) will be binlogged together as one single Table_map event and one single Rows event. */ - thd.binlog_query(THD::ROW_QUERY_TYPE, - row->query.str, row->query.length, - FALSE, FALSE, errcode); - + if (thd.binlog_query(THD::ROW_QUERY_TYPE, + row->query.str, row->query.length, + FALSE, FALSE, errcode)) + goto err; + thd.time_zone_used = backup_time_zone_used; thd.variables.time_zone = backup_time_zone; } @@ -2811,8 +2812,9 @@ bool Delayed_insert::handle_inserts(void) TODO: Move the logging to last in the sequence of rows. */ - if (thd.current_stmt_binlog_row_based) - thd.binlog_flush_pending_rows_event(TRUE); + if (thd.current_stmt_binlog_row_based && + thd.binlog_flush_pending_rows_event(TRUE)) + goto err; if ((error=table->file->extra(HA_EXTRA_NO_CACHE))) { // This shouldn't happen @@ -3265,16 +3267,21 @@ bool select_insert::send_eof() events are in the transaction cache and will be written when ha_autocommit_or_rollback() is issued below. */ - if (mysql_bin_log.is_open()) + if (mysql_bin_log.is_open() && + (!error || thd->transaction.stmt.modified_non_trans_table)) { int errcode= 0; if (!error) thd->clear_error(); else errcode= query_error_code(thd, killed_status == THD::NOT_KILLED); - thd->binlog_query(THD::ROW_QUERY_TYPE, + if (thd->binlog_query(THD::ROW_QUERY_TYPE, thd->query(), thd->query_length(), - trans_table, FALSE, errcode); + trans_table, FALSE, errcode)) + { + table->file->ha_release_auto_increment(); + DBUG_RETURN(1); + } } table->file->ha_release_auto_increment(); @@ -3345,9 +3352,10 @@ void select_insert::abort() { if (mysql_bin_log.is_open()) { int errcode= query_error_code(thd, thd->killed == THD::NOT_KILLED); - thd->binlog_query(THD::ROW_QUERY_TYPE, thd->query(), - thd->query_length(), - transactional_table, FALSE, errcode); + /* error of writing binary log is ignored */ + (void) thd->binlog_query(THD::ROW_QUERY_TYPE, thd->query(), + thd->query_length(), + transactional_table, FALSE, errcode); } if (!thd->current_stmt_binlog_row_based && !can_rollback_data()) thd->transaction.all.modified_non_trans_table= TRUE; @@ -3602,7 +3610,8 @@ select_create::prepare(List &values, SELECT_LEX_UNIT *u) !table->s->tmp_table && !ptr->get_create_info()->table_existed) { - ptr->binlog_show_create_table(tables, count); + if (int error= ptr->binlog_show_create_table(tables, count)) + return error; } return 0; } @@ -3709,7 +3718,7 @@ select_create::prepare(List &values, SELECT_LEX_UNIT *u) DBUG_RETURN(0); } -void +int select_create::binlog_show_create_table(TABLE **tables, uint count) { /* @@ -3748,12 +3757,13 @@ select_create::binlog_show_create_table(TABLE **tables, uint count) if (mysql_bin_log.is_open()) { int errcode= query_error_code(thd, thd->killed == THD::NOT_KILLED); - thd->binlog_query(THD::STMT_QUERY_TYPE, - query.ptr(), query.length(), - /* is_trans */ TRUE, - /* suppress_use */ FALSE, - errcode); + result= thd->binlog_query(THD::STMT_QUERY_TYPE, + query.ptr(), query.length(), + /* is_trans */ TRUE, + /* suppress_use */ FALSE, + errcode); } + return result; } void select_create::store_values(List &values) @@ -3851,7 +3861,8 @@ void select_create::abort() select_insert::abort(); thd->transaction.stmt.modified_non_trans_table= FALSE; reenable_binlog(thd); - thd->binlog_flush_pending_rows_event(TRUE); + /* possible error of writing binary log is ignored deliberately */ + (void)thd->binlog_flush_pending_rows_event(TRUE); if (m_plock) { diff --git a/sql/sql_load.cc b/sql/sql_load.cc index 34e899fc536..8792969dfd5 100644 --- a/sql/sql_load.cc +++ b/sql/sql_load.cc @@ -155,7 +155,7 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, char name[FN_REFLEN]; File file; TABLE *table= NULL; - int error; + int error= 0; String *field_term=ex->field_term,*escaped=ex->escaped; String *enclosed=ex->enclosed; bool is_fifo=0; @@ -536,18 +536,20 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, { int errcode= query_error_code(thd, killed_status == THD::NOT_KILLED); + /* since there is already an error, the possible error of + writing binary log will be ignored */ if (thd->transaction.stmt.modified_non_trans_table) - write_execute_load_query_log_event(thd, ex, - table_list->db, - table_list->table_name, - handle_duplicates, ignore, - transactional_table, - errcode); + (void) write_execute_load_query_log_event(thd, ex, + table_list->db, + table_list->table_name, + handle_duplicates, ignore, + transactional_table, + errcode); else { Delete_file_log_event d(thd, db, transactional_table); d.flags|= LOG_EVENT_UPDATE_TABLE_MAP_VERSION_F; - mysql_bin_log.write(&d); + (void) mysql_bin_log.write(&d); } } } @@ -574,7 +576,7 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, after this point. */ if (thd->current_stmt_binlog_row_based) - thd->binlog_flush_pending_rows_event(true); + error= thd->binlog_flush_pending_rows_event(true); else { /* @@ -586,13 +588,15 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, if (lf_info.wrote_create_file) { int errcode= query_error_code(thd, killed_status == THD::NOT_KILLED); - write_execute_load_query_log_event(thd, ex, - table_list->db, table_list->table_name, - handle_duplicates, ignore, - transactional_table, - errcode); + error= write_execute_load_query_log_event(thd, ex, + table_list->db, table_list->table_name, + handle_duplicates, ignore, + transactional_table, + errcode); } } + if (error) + goto err; } #endif /*!EMBEDDED_LIBRARY*/ diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 1edb37026fb..2f65ba20b77 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -3092,7 +3092,7 @@ end_with_restore_list: /* Presumably, REPAIR and binlog writing doesn't require synchronization */ - write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + res= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); } select_lex->table_list.first= (uchar*) first_table; lex->query_tables=all_tables; @@ -3124,7 +3124,7 @@ end_with_restore_list: /* Presumably, ANALYZE and binlog writing doesn't require synchronization */ - write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + res= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); } select_lex->table_list.first= (uchar*) first_table; lex->query_tables=all_tables; @@ -3147,7 +3147,7 @@ end_with_restore_list: /* Presumably, OPTIMIZE and binlog writing doesn't require synchronization */ - write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + res= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); } select_lex->table_list.first= (uchar*) first_table; lex->query_tables=all_tables; @@ -3288,7 +3288,7 @@ end_with_restore_list: if (incident) { Incident_log_event ev(thd, incident); - mysql_bin_log.write(&ev); + (void) mysql_bin_log.write(&ev); /* error is ignored */ mysql_bin_log.rotate_and_purge(RP_FORCE_ROTATE); } DBUG_PRINT("debug", ("Just after generate_incident()")); @@ -4129,7 +4129,8 @@ end_with_restore_list: */ if (!lex->no_write_to_binlog && write_to_binlog) { - write_bin_log(thd, FALSE, thd->query(), thd->query_length()); + if (res= write_bin_log(thd, FALSE, thd->query(), thd->query_length())) + break; } my_ok(thd); } @@ -4701,12 +4702,12 @@ create_sp_error: case SP_KEY_NOT_FOUND: if (lex->drop_if_exists) { - write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + res= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, ER_SP_DOES_NOT_EXIST, ER(ER_SP_DOES_NOT_EXIST), SP_COM_STRING(lex), lex->spname->m_name.str); - res= FALSE; - my_ok(thd); + if (!res) + my_ok(thd); break; } my_error(ER_SP_DOES_NOT_EXIST, MYF(0), diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc index 417b23c076e..9cd3ac78a77 100644 --- a/sql/sql_partition.cc +++ b/sql/sql_partition.cc @@ -4341,8 +4341,9 @@ static int fast_end_partition(THD *thd, ulonglong copied, } if ((!is_empty) && (!written_bin_log) && - (!thd->lex->no_write_to_binlog)) - write_bin_log(thd, FALSE, thd->query(), thd->query_length()); + (!thd->lex->no_write_to_binlog) && + write_bin_log(thd, FALSE, thd->query(), thd->query_length())) + DBUG_RETURN(TRUE); my_snprintf(tmp_name, sizeof(tmp_name), ER(ER_INSERT_INFO), (ulong) (copied + deleted), diff --git a/sql/sql_rename.cc b/sql/sql_rename.cc index dac96f2e9c4..e85e730db5b 100644 --- a/sql/sql_rename.cc +++ b/sql/sql_rename.cc @@ -34,6 +34,7 @@ static TABLE_LIST *reverse_table_list(TABLE_LIST *table_list); bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent) { bool error= 1; + bool binlog_error= 0; TABLE_LIST *ren_table= 0; int to_table; char *rename_log_table[2]= {NULL, NULL}; @@ -174,11 +175,11 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent) */ pthread_mutex_unlock(&LOCK_open); - /* Lets hope this doesn't fail as the result will be messy */ if (!silent && !error) { - write_bin_log(thd, TRUE, thd->query(), thd->query_length()); - my_ok(thd); + binlog_error= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + if (!binlog_error) + my_ok(thd); } if (!error) @@ -190,7 +191,7 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent) err: start_waiting_global_read_lock(thd); - DBUG_RETURN(error); + DBUG_RETURN(error || binlog_error); } diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 4e5ce08ab5d..346915435c9 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -1219,8 +1219,8 @@ int reset_slave(THD *thd, Master_info* mi) MY_STAT stat_area; char fname[FN_REFLEN]; int thread_mask= 0, error= 0; - uint sql_errno=0; - const char* errmsg=0; + uint sql_errno=ER_UNKNOWN_ERROR; + const char* errmsg= "Unknown error occured while reseting slave"; DBUG_ENTER("reset_slave"); lock_slave_threads(mi); @@ -1960,7 +1960,8 @@ err: replication events along LOAD DATA processing. @param file pointer to io-cache - @return 0 + @retval 0 success + @retval 1 failure */ int log_loaded_block(IO_CACHE* file) { @@ -1987,7 +1988,8 @@ int log_loaded_block(IO_CACHE* file) Append_block_log_event a(lf_info->thd, lf_info->thd->db, buffer, min(block_len, max_event_size), lf_info->log_delayed); - mysql_bin_log.write(&a); + if (mysql_bin_log.write(&a)) + DBUG_RETURN(1); } else { @@ -1995,7 +1997,8 @@ int log_loaded_block(IO_CACHE* file) buffer, min(block_len, max_event_size), lf_info->log_delayed); - mysql_bin_log.write(&b); + if (mysql_bin_log.write(&b)) + DBUG_RETURN(1); lf_info->wrote_create_file= 1; DBUG_SYNC_POINT("debug_lock.created_file_event",10); } diff --git a/sql/sql_table.cc b/sql/sql_table.cc index cc46dc1ae7a..2bfcabb957e 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -1735,9 +1735,10 @@ end: file */ -void write_bin_log(THD *thd, bool clear_error, - char const *query, ulong query_length) +int write_bin_log(THD *thd, bool clear_error, + char const *query, ulong query_length) { + int error= 0; if (mysql_bin_log.is_open()) { int errcode= 0; @@ -1745,9 +1746,10 @@ void write_bin_log(THD *thd, bool clear_error, thd->clear_error(); else errcode= query_error_code(thd, TRUE); - thd->binlog_query(THD::STMT_QUERY_TYPE, - query, query_length, FALSE, FALSE, errcode); + error= thd->binlog_query(THD::STMT_QUERY_TYPE, + query, query_length, FALSE, FALSE, errcode); } + return error; } @@ -2099,7 +2101,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, tables). In this case, we can write the original query into the binary log. */ - write_bin_log(thd, !error, thd->query(), thd->query_length()); + error |= write_bin_log(thd, !error, thd->query(), thd->query_length()); } else if (thd->current_stmt_binlog_row_based && tmp_table_deleted) @@ -2121,7 +2123,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, */ built_query.chop(); // Chop of the last comma built_query.append(" /* generated by server */"); - write_bin_log(thd, !error, built_query.ptr(), built_query.length()); + error|= write_bin_log(thd, !error, built_query.ptr(), built_query.length()); } /* @@ -2140,7 +2142,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, */ built_tmp_query.chop(); // Chop of the last comma built_tmp_query.append(" /* generated by server */"); - write_bin_log(thd, !error, built_tmp_query.ptr(), built_tmp_query.length()); + error|= write_bin_log(thd, !error, built_tmp_query.ptr(), built_tmp_query.length()); } } @@ -3581,9 +3583,9 @@ void sp_prepare_create_field(THD *thd, Create_field *sql_field) RETURN VALUES NONE */ -static inline void write_create_table_bin_log(THD *thd, - const HA_CREATE_INFO *create_info, - bool internal_tmp_table) +static inline int write_create_table_bin_log(THD *thd, + const HA_CREATE_INFO *create_info, + bool internal_tmp_table) { /* Don't write statement if: @@ -3596,7 +3598,7 @@ static inline void write_create_table_bin_log(THD *thd, (!thd->current_stmt_binlog_row_based || (thd->current_stmt_binlog_row_based && !(create_info->options & HA_LEX_CREATE_TMP_TABLE)))) - write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + return write_bin_log(thd, TRUE, thd->query(), thd->query_length()); } @@ -3865,8 +3867,7 @@ bool mysql_create_table_no_lock(THD *thd, push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, ER_TABLE_EXISTS_ERROR, ER(ER_TABLE_EXISTS_ERROR), alias); - error= 0; - write_create_table_bin_log(thd, create_info, internal_tmp_table); + error= write_create_table_bin_log(thd, create_info, internal_tmp_table); goto err; } my_error(ER_TABLE_EXISTS_ERROR, MYF(0), alias); @@ -3987,8 +3988,7 @@ bool mysql_create_table_no_lock(THD *thd, thd->thread_specific_used= TRUE; } - write_create_table_bin_log(thd, create_info, internal_tmp_table); - error= FALSE; + error= write_create_table_bin_log(thd, create_info, internal_tmp_table); unlock_and_end: VOID(pthread_mutex_unlock(&LOCK_open)); @@ -4003,7 +4003,7 @@ warn: ER_TABLE_EXISTS_ERROR, ER(ER_TABLE_EXISTS_ERROR), alias); create_info->table_existed= 1; // Mark that table existed - write_create_table_bin_log(thd, create_info, internal_tmp_table); + error= write_create_table_bin_log(thd, create_info, internal_tmp_table); goto unlock_and_end; } @@ -5470,17 +5470,19 @@ binlog: create_info, FALSE /* show_database */); DBUG_ASSERT(result == 0); // store_create_info() always return 0 - write_bin_log(thd, TRUE, query.ptr(), query.length()); + if (write_bin_log(thd, TRUE, query.ptr(), query.length())) + goto err; } else // Case 1 - write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + if (write_bin_log(thd, TRUE, thd->query(), thd->query_length())) + goto err; } /* Case 3 and 4 does nothing under RBR */ } - else - write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + else if (write_bin_log(thd, TRUE, thd->query(), thd->query_length())) + goto err; res= FALSE; @@ -5568,7 +5570,7 @@ mysql_discard_or_import_tablespace(THD *thd, error=1; if (error) goto err; - write_bin_log(thd, FALSE, thd->query(), thd->query_length()); + error= write_bin_log(thd, FALSE, thd->query(), thd->query_length()); err: ha_autocommit_or_rollback(thd, error); @@ -6620,11 +6622,13 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, thd->clear_error(); Query_log_event qinfo(thd, thd->query(), thd->query_length(), 0, FALSE, 0); - mysql_bin_log.write(&qinfo); + if (error= mysql_bin_log.write(&qinfo)) + goto view_err_unlock; } my_ok(thd); } +view_err_unlock: unlock_table_names(thd, table_list, (TABLE_LIST*) 0); view_err: @@ -6872,8 +6876,9 @@ view_err: if (!error) { - write_bin_log(thd, TRUE, thd->query(), thd->query_length()); - my_ok(thd); + error= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + if (!error) + my_ok(thd); } else if (error > 0) { @@ -7361,8 +7366,9 @@ view_err: if (rename_temporary_table(thd, new_table, new_db, new_name)) goto err1; /* We don't replicate alter table statement on temporary tables */ - if (!thd->current_stmt_binlog_row_based) - write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + if (!thd->current_stmt_binlog_row_based && + write_bin_log(thd, TRUE, thd->query(), thd->query_length())) + DBUG_RETURN(TRUE); goto end_temporary; } @@ -7525,7 +7531,8 @@ view_err: DBUG_ASSERT(!(mysql_bin_log.is_open() && thd->current_stmt_binlog_row_based && (create_info->options & HA_LEX_CREATE_TMP_TABLE))); - write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + if (write_bin_log(thd, TRUE, thd->query(), thd->query_length())) + DBUG_RETURN(TRUE); if (ha_check_storage_engine_flag(old_db_type, HTON_FLUSH_AFTER_RENAME)) { diff --git a/sql/sql_tablespace.cc b/sql/sql_tablespace.cc index ef81c7d847e..4db3083eb04 100644 --- a/sql/sql_tablespace.cc +++ b/sql/sql_tablespace.cc @@ -66,6 +66,6 @@ int mysql_alter_tablespace(THD *thd, st_alter_tablespace *ts_info) ha_resolve_storage_engine_name(hton), "TABLESPACE or LOGFILE GROUP"); } - write_bin_log(thd, FALSE, thd->query(), thd->query_length()); - DBUG_RETURN(FALSE); + error= write_bin_log(thd, FALSE, thd->query(), thd->query_length()); + DBUG_RETURN(error); } diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index 61da4617610..aa2ed498de1 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -507,7 +507,7 @@ end: if (!result) { - write_bin_log(thd, TRUE, stmt_query.ptr(), stmt_query.length()); + result= write_bin_log(thd, TRUE, stmt_query.ptr(), stmt_query.length()); } VOID(pthread_mutex_unlock(&LOCK_open)); diff --git a/sql/sql_udf.cc b/sql/sql_udf.cc index a1a0d9633b7..bc491fe3a9e 100644 --- a/sql/sql_udf.cc +++ b/sql/sql_udf.cc @@ -506,8 +506,8 @@ int mysql_create_function(THD *thd,udf_func *udf) rw_unlock(&THR_LOCK_udf); /* Binlog the create function. */ - write_bin_log(thd, TRUE, thd->query(), thd->query_length()); - + if (write_bin_log(thd, TRUE, thd->query(), thd->query_length())) + DBUG_RETURN(1); DBUG_RETURN(0); err: @@ -581,8 +581,8 @@ int mysql_drop_function(THD *thd,const LEX_STRING *udf_name) rw_unlock(&THR_LOCK_udf); /* Binlog the drop function. */ - write_bin_log(thd, TRUE, thd->query(), thd->query_length()); - + if (write_bin_log(thd, TRUE, thd->query(), thd->query_length())) + DBUG_RETURN(1); DBUG_RETURN(0); err: rw_unlock(&THR_LOCK_udf); diff --git a/sql/sql_update.cc b/sql/sql_update.cc index e7b4eb22e78..21d0243a403 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -1871,9 +1871,10 @@ void multi_update::abort() into repl event. */ int errcode= query_error_code(thd, thd->killed == THD::NOT_KILLED); - thd->binlog_query(THD::ROW_QUERY_TYPE, - thd->query(), thd->query_length(), - transactional_tables, FALSE, errcode); + /* the error of binary logging is ignored */ + (void)thd->binlog_query(THD::ROW_QUERY_TYPE, + thd->query(), thd->query_length(), + transactional_tables, FALSE, errcode); } thd->transaction.all.modified_non_trans_table= TRUE; } diff --git a/sql/sql_view.cc b/sql/sql_view.cc index 9abbadb8c6b..64c1167688c 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -662,8 +662,9 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views, buff.append(views->source.str, views->source.length); int errcode= query_error_code(thd, TRUE); - thd->binlog_query(THD::STMT_QUERY_TYPE, - buff.ptr(), buff.length(), FALSE, FALSE, errcode); + if (thd->binlog_query(THD::STMT_QUERY_TYPE, + buff.ptr(), buff.length(), FALSE, FALSE, errcode)) + res= TRUE; } VOID(pthread_mutex_unlock(&LOCK_open)); @@ -1653,7 +1654,8 @@ bool mysql_drop_view(THD *thd, TABLE_LIST *views, enum_drop_mode drop_mode) /* if something goes wrong, bin-log with possible error code, otherwise bin-log with error code cleared. */ - write_bin_log(thd, !something_wrong, thd->query(), thd->query_length()); + if (write_bin_log(thd, !something_wrong, thd->query(), thd->query_length())) + something_wrong= 1; } VOID(pthread_mutex_unlock(&LOCK_open)); From 23da26eb85357079123603cffe78ee33ba8dfbb8 Mon Sep 17 00:00:00 2001 From: He Zhenxing Date: Sat, 21 Nov 2009 19:50:15 +0800 Subject: [PATCH 38/40] Post fix for previous patch of bug#37148 sql/sql_table.cc: Add the missing return 0 --- sql/sql_table.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 2bfcabb957e..691b248fb2e 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -3599,6 +3599,7 @@ static inline int write_create_table_bin_log(THD *thd, (thd->current_stmt_binlog_row_based && !(create_info->options & HA_LEX_CREATE_TMP_TABLE)))) return write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + return 0; } From a4bcf99f3846d77a958c66c7d418681d7e66d3da Mon Sep 17 00:00:00 2001 From: Alexey Kopytov Date: Sat, 21 Nov 2009 20:31:00 +0300 Subject: [PATCH 39/40] Backport of the fix for bug #33969: Updating a text field via a left join When creating a temporary TEXT/BLOB field from an Item in Item::make_string_field(), the field's type was unconditionally set to the one corresponding to the maximum length (i.e. LONGTEXT/ LONGBLOB). This resulted in problems when exactly the same TEXT/BLOB is type required in cases like CREATE ... SELECT or creating internal temporary tables for joins. Fixed by calling a different constructor for Field_blob so that an appropriate type is used depending on the Item's max_length value. --- mysql-test/r/type_blob.result | 18 ++++++++++++++++++ mysql-test/r/type_ranges.result | 4 ++-- mysql-test/t/type_blob.test | 20 ++++++++++++++++++++ sql/item.cc | 2 +- 4 files changed, 41 insertions(+), 3 deletions(-) diff --git a/mysql-test/r/type_blob.result b/mysql-test/r/type_blob.result index d11ab236c34..4d1c451f01a 100644 --- a/mysql-test/r/type_blob.result +++ b/mysql-test/r/type_blob.result @@ -974,3 +974,21 @@ ERROR 42000: Display width out of range for column 'cast as char' (max = 4294967 explain select convert(1, binary(999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999)); ERROR 42000: Display width out of range for column 'cast as char' (max = 4294967295) End of 5.0 tests +CREATE TABLE t1(id INT NOT NULL); +CREATE TABLE t2(id INT NOT NULL, c TEXT NOT NULL); +INSERT INTO t1 VALUES (1); +INSERT INTO t2 VALUES (1, ''); +UPDATE t2 SET c = REPEAT('1', 70000); +Warnings: +Warning 1265 Data truncated for column 'c' at row 1 +SELECT LENGTH(c) FROM t2; +LENGTH(c) +65535 +UPDATE t1 LEFT JOIN t2 USING(id) SET t2.c = REPEAT('1', 70000) WHERE t1.id = 1; +Warnings: +Warning 1265 Data truncated for column 'c' at row 1 +SELECT LENGTH(c) FROM t2; +LENGTH(c) +65535 +DROP TABLE t1, t2; +End of 5.1 tests diff --git a/mysql-test/r/type_ranges.result b/mysql-test/r/type_ranges.result index 6e08067d8a4..5e915fbfa2d 100644 --- a/mysql-test/r/type_ranges.result +++ b/mysql-test/r/type_ranges.result @@ -276,8 +276,8 @@ t1 int(1) NULL NO 0 # t2 varchar(1) latin1_swedish_ci NO # t3 varchar(256) latin1_swedish_ci NO # t4 varbinary(256) NULL NO # -t5 longtext latin1_swedish_ci NO NULL # -t6 longblob NULL NO NULL # +t5 text latin1_swedish_ci NO NULL # +t6 blob NULL NO NULL # t7 char(0) latin1_swedish_ci NO # t8 binary(0) NULL NO # select t1,t2,length(t3),length(t4),length(t5),length(t6),t7,t8 from t2; diff --git a/mysql-test/t/type_blob.test b/mysql-test/t/type_blob.test index 460da1c1614..85705d664f4 100644 --- a/mysql-test/t/type_blob.test +++ b/mysql-test/t/type_blob.test @@ -612,3 +612,23 @@ explain select convert(1, binary(4294967296)); explain select convert(1, binary(999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999)); --echo End of 5.0 tests + +# +# Bug #33969: Updating a text field via a left join +# + +CREATE TABLE t1(id INT NOT NULL); +CREATE TABLE t2(id INT NOT NULL, c TEXT NOT NULL); + +INSERT INTO t1 VALUES (1); +INSERT INTO t2 VALUES (1, ''); + +UPDATE t2 SET c = REPEAT('1', 70000); +SELECT LENGTH(c) FROM t2; + +UPDATE t1 LEFT JOIN t2 USING(id) SET t2.c = REPEAT('1', 70000) WHERE t1.id = 1; +SELECT LENGTH(c) FROM t2; + +DROP TABLE t1, t2; + +--echo End of 5.1 tests diff --git a/sql/item.cc b/sql/item.cc index b59a82f8045..f8fd82fda67 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -5025,7 +5025,7 @@ Field *Item::make_string_field(TABLE *table) DBUG_ASSERT(collation.collation); if (max_length/collation.collation->mbmaxlen > CONVERT_IF_BIGGER_TO_BLOB) field= new Field_blob(max_length, maybe_null, name, - collation.collation); + collation.collation, TRUE); /* Item_type_holder holds the exact type, do not change it */ else if (max_length > 0 && (type() != Item::TYPE_HOLDER || field_type() != MYSQL_TYPE_STRING)) From d64f29d0ecdb606aecbf9abd0514702c42513465 Mon Sep 17 00:00:00 2001 From: Jonathan Perkin Date: Mon, 23 Nov 2009 13:34:39 +0000 Subject: [PATCH 40/40] bug#36462: mysql_install_db fails when run as user root and root-directory not writable Rather than use the -w test, attempt the chown and bail out if it fails. --- scripts/mysql_install_db.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scripts/mysql_install_db.sh b/scripts/mysql_install_db.sh index 7e9b8a69677..b4cfc054b35 100644 --- a/scripts/mysql_install_db.sh +++ b/scripts/mysql_install_db.sh @@ -358,9 +358,15 @@ do mkdir -p $dir chmod 700 $dir fi - if test -w / -a ! -z "$user" + if test -n "$user" then chown $user $dir + if test $? -ne 0 + then + echo "Cannot change ownership of the database directories to the '$user'" + echo "user. Check that you have the necessary permissions and try again." + exit 1 + fi fi done