From 02f305516f3663533ca9abd68e73a64f7da51455 Mon Sep 17 00:00:00 2001 From: "Tatiana A. Nurnberg" Date: Mon, 18 Aug 2008 13:05:51 +0200 Subject: [PATCH 01/10] Bug#35981: ALTER EVENT causes the server to change the PRESERVE option. If [NOT] PRESERVE was not given, parser always defaulted to NOT PRESERVE, making it impossible for the "not given = no change" rule to work in ALTER EVENT. Leaving out the PRESERVE-clause defaults to NOT PRESERVE on CREATE now, and to "no change" in ALTER. mysql-test/r/events_2.result: show that giving no PRESERVE-clause to ALTER EVENT results in no change. show that giving no PRESERVE-clause to CREATE EVENT defaults to NOT PRESERVE as per the docs. Show specifically that this is also handled correctly when trying to ALTER EVENTs into the past. mysql-test/t/events_2.test: show that giving no PRESERVE-clause to ALTER EVENT results in no change. show that giving no PRESERVE-clause to CREATE EVENT defaults to NOT PRESERVE as per the docs. Show specifically that this is also handled correctly when trying to ALTER EVENTs into the past. sql/event_db_repository.cc: If ALTER EVENT was given no PRESERVE-clause (meaning "no change"), we don't know the previous PRESERVE-setting by the time we check the parse-data. If ALTER EVENT was given dates that are in the past, we don't know how to react, lacking the PRESERVE-setting. Heal this by running the check later when we have actually read the previous EVENT-data. sql/event_parse_data.cc: Change default for ON COMPLETION to indicate, "not specified." Also defer throwing errors when ALTER EVENT is given dates in the past but not PRESERVE-clause until we know the previous PRESERVE-value. sql/event_parse_data.h: Add third state for ON COMPLETION [NOT] PRESERVE (preserve, don't, not specified). Make check_dates() public so we can defer this check until deeper in the callstack where we have all the required data. sql/sql_yacc.yy: If CREATE EVENT is not given ON COMPLETION [NOT] PRESERVE, we default to NOT, as per the docs. --- mysql-test/r/events_2.result | 77 ++++++++++++++++++++++++++ mysql-test/t/events_2.test | 102 +++++++++++++++++++++++++++++++++++ sql/event_db_repository.cc | 14 +++++ sql/event_parse_data.cc | 44 ++++++++++++++- sql/event_parse_data.h | 10 +++- sql/sql_yacc.yy | 2 + 6 files changed, 247 insertions(+), 2 deletions(-) diff --git a/mysql-test/r/events_2.result b/mysql-test/r/events_2.result index 64d643505c5..db503f7aa6d 100644 --- a/mysql-test/r/events_2.result +++ b/mysql-test/r/events_2.result @@ -328,4 +328,81 @@ create event очень_очень_очень_очень_очень_очень_очень_очень_длинная_строка_66 on schedule every 2 year do select 1; ERROR 42000: Identifier name 'очень_очень_очень_очень_очень_очень_очень_очень_длинна' is too long +create event event_35981 on schedule every 6 month on completion preserve +disable +do +select 1; +The following SELECTs should all give 1 +select count(*) from information_schema.events +where event_schema = database() and event_name = 'event_35981' and +on_completion = 'PRESERVE'; +count(*) +1 +alter event event_35981 enable; +select count(*) from information_schema.events +where event_schema = database() and event_name = 'event_35981' and +on_completion = 'PRESERVE'; +count(*) +1 +alter event event_35981 on completion not preserve; +select count(*) from information_schema.events +where event_schema = database() and event_name = 'event_35981' and +on_completion = 'NOT PRESERVE'; +count(*) +1 +alter event event_35981 disable; +select count(*) from information_schema.events +where event_schema = database() and event_name = 'event_35981' and +on_completion = 'NOT PRESERVE'; +count(*) +1 +alter event event_35981 on completion preserve; +select count(*) from information_schema.events +where event_schema = database() and event_name = 'event_35981' and +on_completion = 'PRESERVE'; +count(*) +1 +drop event event_35981; +create event event_35981 on schedule every 6 month disable +do +select 1; +select count(*) from information_schema.events +where event_schema = database() and event_name = 'event_35981' and +on_completion = 'NOT PRESERVE'; +count(*) +1 +drop event event_35981; +create event event_35981 on schedule every 1 hour starts current_timestamp +on completion not preserve +do +select 1; +alter event event_35981 on schedule every 1 hour starts '1999-01-01 00:00:00' + ends '1999-01-02 00:00:00'; +ERROR HY000: Event execution time is in the past and ON COMPLETION NOT PRESERVE is set. The event was dropped immediately after creation. +drop event event_35981; +create event event_35981 on schedule every 1 hour starts current_timestamp +on completion not preserve +do +select 1; +alter event event_35981 on schedule every 1 hour starts '1999-01-01 00:00:00' + ends '1999-01-02 00:00:00' on completion preserve; +Warnings: +Note 1544 Event execution time is in the past. Event has been disabled +drop event event_35981; +create event event_35981 on schedule every 1 hour starts current_timestamp +on completion preserve +do +select 1; +alter event event_35981 on schedule every 1 hour starts '1999-01-01 00:00:00' + ends '1999-01-02 00:00:00'; +Warnings: +Note 1544 Event execution time is in the past. Event has been disabled +alter event event_35981 on schedule every 1 hour starts '1999-01-01 00:00:00' + ends '1999-01-02 00:00:00' on completion not preserve; +ERROR HY000: Event execution time is in the past and ON COMPLETION NOT PRESERVE is set. The event was dropped immediately after creation. +alter event event_35981 on schedule every 1 hour starts '1999-01-01 00:00:00' + ends '1999-01-02 00:00:00' on completion preserve; +Warnings: +Note 1544 Event execution time is in the past. Event has been disabled +drop event event_35981; drop database events_test; diff --git a/mysql-test/t/events_2.test b/mysql-test/t/events_2.test index 58a6fffe6f5..a50255e9f8b 100644 --- a/mysql-test/t/events_2.test +++ b/mysql-test/t/events_2.test @@ -411,6 +411,108 @@ create event очень_очень_очень_очень_очень_очень_очень_очень_длинная_строка_66 on schedule every 2 year do select 1; +# +# Bug#35981: ALTER EVENT causes the server to change the PRESERVE option. +# + +create event event_35981 on schedule every 6 month on completion preserve +disable +do + select 1; + +echo The following SELECTs should all give 1; + +# show current ON_COMPLETION +select count(*) from information_schema.events +where event_schema = database() and event_name = 'event_35981' and + on_completion = 'PRESERVE'; + +# show ON_COMPLETION remains "PRESERVE" when not given in ALTER EVENT +alter event event_35981 enable; +select count(*) from information_schema.events +where event_schema = database() and event_name = 'event_35981' and + on_completion = 'PRESERVE'; + +# show we can change ON_COMPLETION +alter event event_35981 on completion not preserve; +select count(*) from information_schema.events +where event_schema = database() and event_name = 'event_35981' and + on_completion = 'NOT PRESERVE'; + +# show ON_COMPLETION remains "NOT PRESERVE" when not given in ALTER EVENT +alter event event_35981 disable; +select count(*) from information_schema.events +where event_schema = database() and event_name = 'event_35981' and + on_completion = 'NOT PRESERVE'; + +# show we can change ON_COMPLETION +alter event event_35981 on completion preserve; +select count(*) from information_schema.events +where event_schema = database() and event_name = 'event_35981' and + on_completion = 'PRESERVE'; + + +drop event event_35981; + +create event event_35981 on schedule every 6 month disable +do + select 1; + +# show that the defaults for CREATE EVENT are still correct (NOT PRESERVE) +select count(*) from information_schema.events +where event_schema = database() and event_name = 'event_35981' and + on_completion = 'NOT PRESERVE'; + +drop event event_35981; + + +# show that backdating doesn't break + +create event event_35981 on schedule every 1 hour starts current_timestamp + on completion not preserve +do + select 1; + +# should fail thanks to above's NOT PRESERVE +--error ER_EVENT_CANNOT_ALTER_IN_THE_PAST +alter event event_35981 on schedule every 1 hour starts '1999-01-01 00:00:00' + ends '1999-01-02 00:00:00'; + +drop event event_35981; + +create event event_35981 on schedule every 1 hour starts current_timestamp + on completion not preserve +do + select 1; + +# succeed with warning +alter event event_35981 on schedule every 1 hour starts '1999-01-01 00:00:00' + ends '1999-01-02 00:00:00' on completion preserve; + +drop event event_35981; + + + +create event event_35981 on schedule every 1 hour starts current_timestamp + on completion preserve +do + select 1; + +# this should succeed thanks to above PRESERVE! give a warning though. +alter event event_35981 on schedule every 1 hour starts '1999-01-01 00:00:00' + ends '1999-01-02 00:00:00'; + +# this should fail, as the event would have passed already +--error ER_EVENT_CANNOT_ALTER_IN_THE_PAST +alter event event_35981 on schedule every 1 hour starts '1999-01-01 00:00:00' + ends '1999-01-02 00:00:00' on completion not preserve; + +# should succeed giving a warning +alter event event_35981 on schedule every 1 hour starts '1999-01-01 00:00:00' + ends '1999-01-02 00:00:00' on completion preserve; + +drop event event_35981; + # # End of tests # diff --git a/sql/event_db_repository.cc b/sql/event_db_repository.cc index 401f76f5d26..e33cae18cee 100644 --- a/sql/event_db_repository.cc +++ b/sql/event_db_repository.cc @@ -185,6 +185,8 @@ mysql_event_fill_row(THD *thd, DBUG_PRINT("info", ("dbname=[%s]", et->dbname.str)); DBUG_PRINT("info", ("name =[%s]", et->name.str)); + DBUG_ASSERT(et->on_completion != Event_parse_data::ON_COMPLETION_DEFAULT); + if (table->s->fields < ET_FIELD_COUNT) { /* @@ -745,6 +747,18 @@ Event_db_repository::update_event(THD *thd, Event_parse_data *parse_data, store_record(table,record[1]); + /* + We check whether ALTER EVENT was given dates that are in the past. + However to know how to react, we need the ON COMPLETION type. The + check is deferred to this point because by now we have the previous + setting (from the event-table) to fall back on if nothing was specified + in the ALTER EVENT-statement. + */ + + if (parse_data->check_dates(thd, + table->field[ET_FIELD_ON_COMPLETION]->val_int())) + goto end; + /* Don't update create on row update. */ table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; diff --git a/sql/event_parse_data.cc b/sql/event_parse_data.cc index e87e4593f8f..df419e92d0d 100644 --- a/sql/event_parse_data.cc +++ b/sql/event_parse_data.cc @@ -45,7 +45,7 @@ Event_parse_data::new_instance(THD *thd) */ Event_parse_data::Event_parse_data() - :on_completion(Event_parse_data::ON_COMPLETION_DROP), + :on_completion(Event_parse_data::ON_COMPLETION_DEFAULT), status(Event_parse_data::ENABLED), do_not_create(FALSE), body_changed(FALSE), @@ -114,6 +114,12 @@ Event_parse_data::check_if_in_the_past(THD *thd, my_time_t ltime_utc) if (ltime_utc >= (my_time_t) thd->query_start()) return; + /* + We'll come back later when we have the real on_completion value + */ + if (on_completion == Event_parse_data::ON_COMPLETION_DEFAULT) + return; + if (on_completion == Event_parse_data::ON_COMPLETION_DROP) { switch (thd->lex->sql_command) { @@ -141,6 +147,42 @@ Event_parse_data::check_if_in_the_past(THD *thd, my_time_t ltime_utc) } +/* + Check time/dates in ALTER EVENT + + We check whether ALTER EVENT was given dates that are in the past. + However to know how to react, we need the ON COMPLETION type. Hence, + the check is deferred until we have the previous ON COMPLETION type + from the event-db to fall back on if nothing was specified in the + ALTER EVENT-statement. + + SYNOPSIS + Event_parse_data::check_dates() + thd Thread + on_completion ON COMPLETION value currently in event-db. + Will be overridden by value in ALTER EVENT if given. + + RETURN VALUE + TRUE an error occurred, do not ALTER + FALSE OK +*/ + +bool +Event_parse_data::check_dates(THD *thd, int previous_on_completion) +{ + if (on_completion == Event_parse_data::ON_COMPLETION_DEFAULT) + { + on_completion= previous_on_completion; + if (!ends_null) + check_if_in_the_past(thd, ends); + if (!execute_at_null) + check_if_in_the_past(thd, execute_at); + } + return do_not_create; +} + + + /* Sets time for execution for one-time event. diff --git a/sql/event_parse_data.h b/sql/event_parse_data.h index 221bf92664f..87a800c2078 100644 --- a/sql/event_parse_data.h +++ b/sql/event_parse_data.h @@ -38,7 +38,12 @@ public: enum enum_on_completion { - ON_COMPLETION_DROP = 1, + /* + On CREATE EVENT, DROP is the DEFAULT as per the docs. + On ALTER EVENT, "no change" is the DEFAULT. + */ + ON_COMPLETION_DEFAULT = 0, + ON_COMPLETION_DROP, ON_COMPLETION_PRESERVE }; @@ -80,6 +85,9 @@ public: bool check_parse_data(THD *thd); + bool + check_dates(THD *thd, int previous_on_completion); + private: void diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 54399c3791c..90dad3994c6 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1786,6 +1786,8 @@ event_tail: if (!(lex->event_parse_data= Event_parse_data::new_instance(thd))) MYSQL_YYABORT; lex->event_parse_data->identifier= $3; + lex->event_parse_data->on_completion= + Event_parse_data::ON_COMPLETION_DROP; /* We have to turn of CLIENT_MULTI_QUERIES while parsing a From 6e162ea9eb01a5e0509ab2b6a6dba31d8f4ba903 Mon Sep 17 00:00:00 2001 From: "Tatiana A. Nurnberg" Date: Thu, 11 Sep 2008 07:46:43 +0200 Subject: [PATCH 02/10] Bug#31434 mysqldump dumps view as table mysqldump creates stand-in tables before dumping the actual view. Those tables were of the default type; if the view had more columns than that (a pathological case, arguably), loading the dump would fail. We now make the temporary stand-ins MyISAM tables to prevent this. client/mysqldump.c: When creating a stand-in table, specify its type to avoid defaulting to a type with a column-number limit (like Inno). The type is always MyISAM as we know that to be available. mysql-test/r/mysqldump.result: mysqldump sets engine-type (MyISAM) for stand-in tables for views now. Update test results. --- client/mysqldump.c | 8 +++++++- mysql-test/r/mysqldump.result | 22 +++++++++++----------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/client/mysqldump.c b/client/mysqldump.c index 364e80ee56b..ced34f16212 100644 --- a/client/mysqldump.c +++ b/client/mysqldump.c @@ -1836,7 +1836,13 @@ static uint get_table_structure(char *table, char *db, char *table_type, fprintf(sql_file, ",\n %s %s", quote_name(row[0], name_buff, 0), row[1]); } - fprintf(sql_file, "\n) */;\n"); + /* + Stand-in tables are always MyISAM tables as the default + engine might have a column-limit that's lower than the + number of columns in the view, and MyISAM support is + guaranteed to be in the server anyway. + */ + fprintf(sql_file, "\n) ENGINE=MyISAM */;\n"); check_io(sql_file); } } diff --git a/mysql-test/r/mysqldump.result b/mysql-test/r/mysqldump.result index b484cf7c51d..163231c9f66 100644 --- a/mysql-test/r/mysqldump.result +++ b/mysql-test/r/mysqldump.result @@ -1996,7 +1996,7 @@ DROP TABLE IF EXISTS `v2`; /*!50001 DROP VIEW IF EXISTS `v2`*/; /*!50001 CREATE TABLE `v2` ( `a` varchar(30) -) */; +) ENGINE=MyISAM */; /*!50001 DROP TABLE `v2`*/; /*!50001 DROP VIEW IF EXISTS `v2`*/; /*!50001 CREATE ALGORITHM=UNDEFINED */ @@ -2078,7 +2078,7 @@ DROP TABLE IF EXISTS `v1`; /*!50001 DROP VIEW IF EXISTS `v1`*/; /*!50001 CREATE TABLE `v1` ( `a` int(11) -) */; +) ENGINE=MyISAM */; /*!50001 DROP TABLE `v1`*/; /*!50001 DROP VIEW IF EXISTS `v1`*/; /*!50001 CREATE ALGORITHM=UNDEFINED */ @@ -2140,7 +2140,7 @@ DROP TABLE IF EXISTS `v2`; /*!50001 DROP VIEW IF EXISTS `v2`*/; /*!50001 CREATE TABLE `v2` ( `a` varchar(30) -) */; +) ENGINE=MyISAM */; /*!50001 DROP TABLE `v2`*/; /*!50001 DROP VIEW IF EXISTS `v2`*/; /*!50001 CREATE ALGORITHM=UNDEFINED */ @@ -2244,19 +2244,19 @@ DROP TABLE IF EXISTS `v1`; `a` int(11), `b` int(11), `c` varchar(30) -) */; +) ENGINE=MyISAM */; DROP TABLE IF EXISTS `v2`; /*!50001 DROP VIEW IF EXISTS `v2`*/; /*!50001 CREATE TABLE `v2` ( `a` int(11) -) */; +) ENGINE=MyISAM */; DROP TABLE IF EXISTS `v3`; /*!50001 DROP VIEW IF EXISTS `v3`*/; /*!50001 CREATE TABLE `v3` ( `a` int(11), `b` int(11), `c` varchar(30) -) */; +) ENGINE=MyISAM */; /*!50001 DROP TABLE `v1`*/; /*!50001 DROP VIEW IF EXISTS `v1`*/; /*!50001 CREATE ALGORITHM=UNDEFINED */ @@ -2860,21 +2860,21 @@ DROP TABLE IF EXISTS `v0`; `a` int(11), `b` varchar(32), `c` varchar(32) -) */; +) ENGINE=MyISAM */; DROP TABLE IF EXISTS `v1`; /*!50001 DROP VIEW IF EXISTS `v1`*/; /*!50001 CREATE TABLE `v1` ( `a` int(11), `b` varchar(32), `c` varchar(32) -) */; +) ENGINE=MyISAM */; DROP TABLE IF EXISTS `v2`; /*!50001 DROP VIEW IF EXISTS `v2`*/; /*!50001 CREATE TABLE `v2` ( `a` int(11), `b` varchar(32), `c` varchar(32) -) */; +) ENGINE=MyISAM */; USE `test`; /*!50001 DROP TABLE `v0`*/; @@ -3198,7 +3198,7 @@ DROP TABLE IF EXISTS `v1`; /*!50001 DROP VIEW IF EXISTS `v1`*/; /*!50001 CREATE TABLE `v1` ( `id` int(11) -) */; +) ENGINE=MyISAM */; USE `mysqldump_test_db`; /*!50001 DROP TABLE `v1`*/; @@ -3246,7 +3246,7 @@ CREATE DATABASE /*!32312 IF NOT EXISTS*/ `mysqldump_views` /*!40100 DEFAULT CHAR USE `mysqldump_views`; /*!50001 CREATE TABLE `nasishnasifu` ( `id` bigint(20) unsigned -) */; +) ENGINE=MyISAM */; USE `mysqldump_tables`; From e9cb71fc3a2f4e83720f1bab4f470e10ca878eb6 Mon Sep 17 00:00:00 2001 From: Gleb Shchepa Date: Thu, 18 Sep 2008 13:38:44 +0500 Subject: [PATCH 03/10] Bug#26020: User-Defined Variables are not consistent with columns data types The "SELECT @lastId, @lastId := Id FROM t" query returns different result sets depending on the type of the Id column (INT or BIGINT). Note: this fix doesn't cover the case when a select query references an user variable and stored function that updates a value of that variable, in this case a result is indeterminate. The server uses incorrect assumption about a constantness of an user variable value as a select list item: The server caches a last query number where that variable was changed and compares this number with a current query number. If these numbers are different, the server guesses, that the variable is not updating in the current query, so a respective select list item is a constant. However, in some common cases the server updates cached query number too late. The server has been modified to memorize user variable assignments during the parse phase to take them into account on the next (query preparation) phase independently of the order of user variable references/assignments in a select item list. mysql-test/r/user_var.result: Added test case for bug #26020. mysql-test/t/user_var.test: Added test case for bug #26020. sql/item_func.cc: An update of entry and update_query_id variables has been moved from Item_func_set_user_var::fix_fields() to a separate method, Item_func_set_user_var::set_entry(). sql/item_func.h: 1. The Item_func_set_user_var::set_entry() method has been added to update Item_func_set_user_var::entry. 2. The Item_func_set_user_var::entry_thd field has beend added to update Item_func_set_user_var::entry only when needed. sql/sql_base.cc: Fix: setup_fiedls() calls Item_func_set_user_var::set_entry() for all items from the thd->lex->set_var_list before the first call of ::fix_fields(). sql/sql_lex.cc: The lex_start function has been modified to reset the st_lex::set_var_list list. sql/sql_lex.h: New st_lex::set_var_list field has been added to memorize all user variable assignments in the current select query. sql/sql_yacc.yy: The variable_aux rule has been modified to memorize in-query user variable assignments in the st_lex::set_var_list list. --- mysql-test/r/user_var.result | 33 +++++++++++++++++++++++++++++++-- mysql-test/t/user_var.test | 22 ++++++++++++++++++++++ sql/item_func.cc | 27 +++++++++++++++++++-------- sql/item_func.h | 15 ++++++++++++++- sql/sql_base.cc | 16 ++++++++++++++++ sql/sql_lex.cc | 1 + sql/sql_lex.h | 1 + sql/sql_yacc.yy | 4 +++- 8 files changed, 107 insertions(+), 12 deletions(-) diff --git a/mysql-test/r/user_var.result b/mysql-test/r/user_var.result index 2cd132ce03c..2d91835d723 100644 --- a/mysql-test/r/user_var.result +++ b/mysql-test/r/user_var.result @@ -121,8 +121,8 @@ select @a:=0; select @a+0, @a:=@a+0+count(*), count(*), @a+0 from t1 group by i; @a+0 @a:=@a+0+count(*) count(*) @a+0 0 1 1 0 -1 3 2 0 -3 6 3 0 +0 2 2 0 +0 3 3 0 set @a=0; select @a,@a:="hello",@a,@a:=3,@a,@a:="hello again" from t1 group by i; @a @a:="hello" @a @a:=3 @a @a:="hello again" @@ -370,4 +370,33 @@ select @rownum := @rownum + 1 as row, @prev_score := a as score from t1 order by score desc; drop table t1; +create table t1(b bigint); +insert into t1 (b) values (10), (30), (10); +set @var := 0; +select if(b=@var, 999, b) , @var := b from t1 order by b; +if(b=@var, 999, b) @var := b +10 10 +999 10 +30 30 +drop table t1; +create temporary table t1 (id int); +insert into t1 values (2), (3), (3), (4); +set @lastid=-1; +select @lastid != id, @lastid, @lastid := id from t1; +@lastid != id @lastid @lastid := id +1 -1 2 +1 2 3 +0 3 3 +1 3 4 +drop table t1; +create temporary table t1 (id bigint); +insert into t1 values (2), (3), (3), (4); +set @lastid=-1; +select @lastid != id, @lastid, @lastid := id from t1; +@lastid != id @lastid @lastid := id +1 -1 2 +1 2 3 +0 3 3 +1 3 4 +drop table t1; End of 5.1 tests diff --git a/mysql-test/t/user_var.test b/mysql-test/t/user_var.test index f2699ab03d3..5d916e410e3 100644 --- a/mysql-test/t/user_var.test +++ b/mysql-test/t/user_var.test @@ -263,4 +263,26 @@ from t1 order by score desc; --enable_result_log drop table t1; +# +# Bug#26020: User-Defined Variables are not consistent with columns data types +# + +create table t1(b bigint); +insert into t1 (b) values (10), (30), (10); +set @var := 0; +select if(b=@var, 999, b) , @var := b from t1 order by b; +drop table t1; + +create temporary table t1 (id int); +insert into t1 values (2), (3), (3), (4); +set @lastid=-1; +select @lastid != id, @lastid, @lastid := id from t1; +drop table t1; + +create temporary table t1 (id bigint); +insert into t1 values (2), (3), (3), (4); +set @lastid=-1; +select @lastid != id, @lastid, @lastid := id from t1; +drop table t1; + --echo End of 5.1 tests diff --git a/sql/item_func.cc b/sql/item_func.cc index 8bb6bb30117..c1caf4039ed 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -3805,6 +3805,24 @@ static user_var_entry *get_variable(HASH *hash, LEX_STRING &name, return entry; } + +bool Item_func_set_user_var::set_entry(THD *thd, bool create_if_not_exists) +{ + if (thd == entry_thd && entry) + return FALSE; + entry_thd= thd; + if (!(entry= get_variable(&thd->user_vars, name, create_if_not_exists))) + return TRUE; + /* + Remember the last query which updated it, this way a query can later know + if this variable is a constant item in the query (it is if update_query_id + is different from query_id). + */ + entry->update_query_id= thd->query_id; + return FALSE; +} + + /* When a user variable is updated (in a SET command or a query like SELECT @a:= ). @@ -3814,15 +3832,8 @@ bool Item_func_set_user_var::fix_fields(THD *thd, Item **ref) { DBUG_ASSERT(fixed == 0); /* fix_fields will call Item_func_set_user_var::fix_length_and_dec */ - if (Item_func::fix_fields(thd, ref) || - !(entry= get_variable(&thd->user_vars, name, 1))) + if (Item_func::fix_fields(thd, ref) || set_entry(thd, TRUE)) return TRUE; - /* - Remember the last query which updated it, this way a query can later know - if this variable is a constant item in the query (it is if update_query_id - is different from query_id). - */ - entry->update_query_id= thd->query_id; /* As it is wrong and confusing to associate any character set with NULL, @a should be latin2 diff --git a/sql/item_func.h b/sql/item_func.h index 02631d7643d..d84abdb6e56 100644 --- a/sql/item_func.h +++ b/sql/item_func.h @@ -1294,6 +1294,17 @@ class Item_func_set_user_var :public Item_func { enum Item_result cached_result_type; user_var_entry *entry; + /* + The entry_thd variable is used: + 1) to skip unnecessary updates of the entry field (see above); + 2) to reset the entry field that was initialized in the other thread + (for example, an item tree of a trigger that updates user variables + may be shared between several connections, and the entry_thd field + prevents updates of one connection user variables from a concurrent + connection calling the same trigger that initially updated some + user variable it the first connection context). + */ + THD *entry_thd; char buffer[MAX_FIELD_WIDTH]; String value; my_decimal decimal_buff; @@ -1309,7 +1320,8 @@ class Item_func_set_user_var :public Item_func public: LEX_STRING name; // keep it public Item_func_set_user_var(LEX_STRING a,Item *b) - :Item_func(b), cached_result_type(INT_RESULT), name(a) + :Item_func(b), cached_result_type(INT_RESULT), + entry(NULL), entry_thd(NULL), name(a) {} enum Functype functype() const { return SUSERVAR_FUNC; } double val_real(); @@ -1340,6 +1352,7 @@ public: } void save_org_in_field(Field *field) { (void)save_in_field(field, 1, 0); } bool register_field_in_read_map(uchar *arg); + bool set_entry(THD *thd, bool create_if_not_exists); }; diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 39030462f0b..6c5e58e2ad5 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -7296,6 +7296,22 @@ bool setup_fields(THD *thd, Item **ref_pointer_array, if (ref_pointer_array) bzero(ref_pointer_array, sizeof(Item *) * fields.elements); + /* + We call set_entry() there (before fix_fields() of the whole list of field + items) because: + 1) the list of field items has same order as in the query, and the + Item_func_get_user_var item may go before the Item_func_set_user_var: + SELECT @a, @a := 10 FROM t; + 2) The entry->update_query_id value controls constantness of + Item_func_get_user_var items, so in presence of Item_func_set_user_var + items we have to refresh their entries before fixing of + Item_func_get_user_var items. + */ + List_iterator li(thd->lex->set_var_list); + Item_func_set_user_var *var; + while ((var= li++)) + var->set_entry(thd, FALSE); + Item **ref= ref_pointer_array; thd->lex->current_select->cur_pos_in_select_list= 0; while ((item= it++)) diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 1822176f00a..18c61910e89 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -293,6 +293,7 @@ void lex_start(THD *thd) lex->select_lex.init_query(); lex->value_list.empty(); lex->update_list.empty(); + lex->set_var_list.empty(); lex->param_list.empty(); lex->view_list.empty(); lex->prepared_stmt_params.empty(); diff --git a/sql/sql_lex.h b/sql/sql_lex.h index bb3dc00fc8d..7674b5c7ac2 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -1549,6 +1549,7 @@ typedef struct st_lex : public Query_tables_list List *insert_list,field_list,value_list,update_list; List many_values; List var_list; + List set_var_list; // in-query assignment list List param_list; List view_list; // view list (list of field names in view) /* diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 7ed35b65789..4a8796f20e5 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -8064,11 +8064,13 @@ variable: variable_aux: ident_or_text SET_VAR expr { - $$= new (YYTHD->mem_root) Item_func_set_user_var($1, $3); + Item_func_set_user_var *item; + $$= item= new (YYTHD->mem_root) Item_func_set_user_var($1, $3); if ($$ == NULL) MYSQL_YYABORT; LEX *lex= Lex; lex->uncacheable(UNCACHEABLE_RAND); + lex->set_var_list.push_back(item); } | ident_or_text { From 14ce62ffbbbb136f8d913a4dd7986be78749d55a Mon Sep 17 00:00:00 2001 From: Gleb Shchepa Date: Thu, 18 Sep 2008 17:24:09 +0500 Subject: [PATCH 04/10] Post-push fix for bug #26020: User-Defined Variables are not consistence with columns data types. --ps-protocol problem has been fixed. sql/item_func.cc: Added update of Item_func_set_user_var::entry->update_query_id for every PS execution. --- sql/item_func.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sql/item_func.cc b/sql/item_func.cc index c1caf4039ed..d7e6fc1f8f2 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -3809,7 +3809,7 @@ static user_var_entry *get_variable(HASH *hash, LEX_STRING &name, bool Item_func_set_user_var::set_entry(THD *thd, bool create_if_not_exists) { if (thd == entry_thd && entry) - return FALSE; + goto end; // update entry->update_query_id for PS entry_thd= thd; if (!(entry= get_variable(&thd->user_vars, name, create_if_not_exists))) return TRUE; @@ -3818,6 +3818,7 @@ bool Item_func_set_user_var::set_entry(THD *thd, bool create_if_not_exists) if this variable is a constant item in the query (it is if update_query_id is different from query_id). */ +end: entry->update_query_id= thd->query_id; return FALSE; } From 71fe19017206da71c053cbe794a382eb94f30741 Mon Sep 17 00:00:00 2001 From: Georgi Kodinov Date: Thu, 18 Sep 2008 15:55:36 +0300 Subject: [PATCH 05/10] Bug #39353: Multiple conditions on timestamp column crashes server The fix for bug 31887 was incomplete : it assumes that all the field types returned by the IS_NUM macro are descendants of Item_num and tries to zero-fill the values before doing constant substitution with such fields when they are compared to constant string values. The only exception to this is Field_timestamp : it's in the IS_NUM macro, but is not a descendant of Field_num. Fixed by excluding timestamp fields (Field_timestamp) when zero-filling when converting the constant to compare with to a string. Note that this will not exclude the timestamp columns from const propagation. mysql-test/r/compare.result: Bug #39353: test case mysql-test/t/compare.test: Bug #39353: test case sql/item.cc: Bug #39353: don't zero-fill timestamp fields when const propagating to a string : they'll be converted to a string in a date/time format and not as an integer. --- mysql-test/r/compare.result | 5 +++++ mysql-test/t/compare.test | 9 +++++++++ sql/item.cc | 7 ++++++- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/mysql-test/r/compare.result b/mysql-test/r/compare.result index c9ef41e0582..748e8a5884b 100644 --- a/mysql-test/r/compare.result +++ b/mysql-test/r/compare.result @@ -90,4 +90,9 @@ Note 1276 Field or reference 'test.t2.a' of SELECT #2 was resolved in SELECT #1 Note 1276 Field or reference 'test.t2.a' of SELECT #2 was resolved in SELECT #1 Note 1003 select `test`.`t2`.`a` AS `a`,(select count(0) AS `COUNT(*)` from `test`.`t1` where ((`test`.`t1`.`b` = `test`.`t2`.`a`) and (concat(`test`.`t1`.`b`,`test`.`t1`.`c`) = concat(_latin1'0',`test`.`t2`.`a`,_latin1'01')))) AS `x` from `test`.`t2` order by `test`.`t2`.`a` DROP TABLE t1,t2; +CREATE TABLE t1 (a TIMESTAMP); +INSERT INTO t1 VALUES (NOW()),(NOW()),(NOW()); +SELECT * FROM t1 WHERE a > '2008-01-01' AND a = '0000-00-00'; +a +DROP TABLE t1; End of 5.0 tests diff --git a/mysql-test/t/compare.test b/mysql-test/t/compare.test index 8863ed825c2..103244eb2f7 100644 --- a/mysql-test/t/compare.test +++ b/mysql-test/t/compare.test @@ -76,4 +76,13 @@ FROM t2 ORDER BY a; DROP TABLE t1,t2; +# +# Bug #39353: Multiple conditions on timestamp column crashes server +# + +CREATE TABLE t1 (a TIMESTAMP); +INSERT INTO t1 VALUES (NOW()),(NOW()),(NOW()); +SELECT * FROM t1 WHERE a > '2008-01-01' AND a = '0000-00-00'; +DROP TABLE t1; + --echo End of 5.0 tests diff --git a/sql/item.cc b/sql/item.cc index a392931f2cd..84eac3a58cf 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -4220,7 +4220,12 @@ Item *Item_field::equal_fields_propagator(byte *arg) item= this; else if (field && (field->flags & ZEROFILL_FLAG) && IS_NUM(field->type())) { - if (item && cmp_context != INT_RESULT) + /* + We don't need to zero-fill timestamp columns here because they will be + first converted to a string (in date/time format) and compared as such if + compared with another string. + */ + if (item && field->type() != FIELD_TYPE_TIMESTAMP && cmp_context != INT_RESULT) convert_zerofill_number_to_string(&item, (Field_num *)field); else item= this; From 3e1d88d188b13a9b7ad2e40b5fee264d44c618e2 Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Thu, 18 Sep 2008 22:49:34 +0300 Subject: [PATCH 06/10] Bug#30573: Ordered range scan over partitioned tables returns some rows twice and Bug#33555: Group By Query does not correctly aggregate partitions Backport of bug-33257 which is the same bug. read_range_*() calls was not passed to the partition handlers, but was translated to index_read/next family calls. Resulting in duplicates rows and wrong aggregations. mysql-test/r/partition_range.result: Bug#30573: Ordered range scan over partitioned tables returns some rows twice Updated result file mysql-test/t/partition_range.test: Bug#30573: Ordered range scan over partitioned tables returns some rows twice Re-enabled the test sql/ha_partition.cc: Bug#30573: Ordered range scan over partitioned tables returns some rows twice backport of bug-33257, correct handling of read_range_* calls, without converting them to index_read/next calls sql/ha_partition.h: Bug#30573: Ordered range scan over partitioned tables returns some rows twice backport of bug-33257, correct handling of read_range_* calls, without converting them to index_read/next calls --- mysql-test/r/partition_range.result | 20 ++++ mysql-test/t/partition_range.test | 36 +++--- sql/ha_partition.cc | 180 ++++++++++++++++++---------- sql/ha_partition.h | 9 +- 4 files changed, 157 insertions(+), 88 deletions(-) diff --git a/mysql-test/r/partition_range.result b/mysql-test/r/partition_range.result index e4e5a748b0d..23a38ff3885 100644 --- a/mysql-test/r/partition_range.result +++ b/mysql-test/r/partition_range.result @@ -742,3 +742,23 @@ WHERE (a >= '2004-07-01' AND a <= '2004-09-30') OR id select_type table partitions type possible_keys key key_len ref rows Extra 1 SIMPLE t1 p407,p408,p409,p507,p508,p509 ALL NULL NULL NULL NULL 18 Using where DROP TABLE t1; +create table t1 (a int); +insert into t1 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); +CREATE TABLE t2 ( +defid int(10) unsigned NOT NULL, +day int(10) unsigned NOT NULL, +count int(10) unsigned NOT NULL, +filler char(200), +KEY (defid,day) +) +PARTITION BY RANGE (day) ( +PARTITION p7 VALUES LESS THAN (20070401) , +PARTITION p8 VALUES LESS THAN (20070501)); +insert into t2 select 20, 20070311, 1, 'filler' from t1 A, t1 B; +insert into t2 select 20, 20070411, 1, 'filler' from t1 A, t1 B; +insert into t2 values(52, 20070321, 123, 'filler') ; +insert into t2 values(52, 20070322, 456, 'filler') ; +select sum(count) from t2 ch where ch.defid in (50,52) and ch.day between 20070320 and 20070401 group by defid; +sum(count) +579 +drop table t1, t2; diff --git a/mysql-test/t/partition_range.test b/mysql-test/t/partition_range.test index bc4231d1d71..c02d9049f2e 100644 --- a/mysql-test/t/partition_range.test +++ b/mysql-test/t/partition_range.test @@ -807,24 +807,24 @@ DROP TABLE t1; # # BUG#30573: get wrong result with "group by" on PARTITIONed table # -#create table t1 (a int); -#insert into t1 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); -#CREATE TABLE t2 ( -# defid int(10) unsigned NOT NULL, -# day int(10) unsigned NOT NULL, -# count int(10) unsigned NOT NULL, -# filler char(200), -# KEY (defid,day) -#) -#PARTITION BY RANGE (day) ( -# PARTITION p7 VALUES LESS THAN (20070401) , -# PARTITION p8 VALUES LESS THAN (20070501)); +create table t1 (a int); +insert into t1 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); +CREATE TABLE t2 ( + defid int(10) unsigned NOT NULL, + day int(10) unsigned NOT NULL, + count int(10) unsigned NOT NULL, + filler char(200), + KEY (defid,day) +) +PARTITION BY RANGE (day) ( + PARTITION p7 VALUES LESS THAN (20070401) , + PARTITION p8 VALUES LESS THAN (20070501)); -#insert into t2 select 20, 20070311, 1, 'filler' from t1 A, t1 B; -#insert into t2 select 20, 20070411, 1, 'filler' from t1 A, t1 B; -#insert into t2 values(52, 20070321, 123, 'filler') ; -#insert into t2 values(52, 20070322, 456, 'filler') ; +insert into t2 select 20, 20070311, 1, 'filler' from t1 A, t1 B; +insert into t2 select 20, 20070411, 1, 'filler' from t1 A, t1 B; +insert into t2 values(52, 20070321, 123, 'filler') ; +insert into t2 values(52, 20070322, 456, 'filler') ; -#select sum(count) from t2 ch where ch.defid in (50,52) and ch.day between 20070320 and 20070401 group by defid; -#drop table t1, t2; +select sum(count) from t2 ch where ch.defid in (50,52) and ch.day between 20070320 and 20070401 group by defid; +drop table t1, t2; diff --git a/sql/ha_partition.cc b/sql/ha_partition.cc index 34cd160e7e4..4d521e9d3a0 100644 --- a/sql/ha_partition.cc +++ b/sql/ha_partition.cc @@ -3679,10 +3679,12 @@ int ha_partition::index_read_map(uchar *buf, const uchar *key, enum ha_rkey_function find_flag) { DBUG_ENTER("ha_partition::index_read_map"); - end_range= 0; m_index_scan_type= partition_index_read; - DBUG_RETURN(common_index_read(buf, key, keypart_map, find_flag)); + m_start_key.key= key; + m_start_key.keypart_map= keypart_map; + m_start_key.flag= find_flag; + DBUG_RETURN(common_index_read(buf, TRUE)); } @@ -3690,41 +3692,63 @@ int ha_partition::index_read_map(uchar *buf, const uchar *key, Common routine for a number of index_read variants SYNOPSIS - common_index_read - - see index_read for rest + ha_partition::common_index_read() + buf Buffer where the record should be returned + have_start_key TRUE <=> the left endpoint is available, i.e. + we're in index_read call or in read_range_first + call and the range has left endpoint + + FALSE <=> there is no left endpoint (we're in + read_range_first() call and the range has no left + endpoint) + + DESCRIPTION + Start scanning the range (when invoked from read_range_first()) or doing + an index lookup (when invoked from index_read_XXX): + - If possible, perform partition selection + - Find the set of partitions we're going to use + - Depending on whether we need ordering: + NO: Get the first record from first used partition (see + handle_unordered_scan_next_partition) + YES: Fill the priority queue and get the record that is the first in + the ordering + + RETURN + 0 OK + other HA_ERR_END_OF_FILE or other error code. */ -int ha_partition::common_index_read(uchar *buf, const uchar *key, - key_part_map keypart_map, - enum ha_rkey_function find_flag) +int ha_partition::common_index_read(uchar *buf, bool have_start_key) { int error; + uint key_len; bool reverse_order= FALSE; - uint key_len= calculate_key_len(table, active_index, key, keypart_map); DBUG_ENTER("ha_partition::common_index_read"); + LINT_INIT(key_len); /* used if have_start_key==TRUE */ - memcpy((void*)m_start_key.key, key, key_len); - m_start_key.keypart_map= keypart_map; - m_start_key.length= key_len; - m_start_key.flag= find_flag; - - if ((error= partition_scan_set_up(buf, TRUE))) + if (have_start_key) + { + m_start_key.length= key_len= calculate_key_len(table, active_index, + m_start_key.key, + m_start_key.keypart_map); + } + if ((error= partition_scan_set_up(buf, have_start_key))) { DBUG_RETURN(error); } - if (find_flag == HA_READ_PREFIX_LAST || - find_flag == HA_READ_PREFIX_LAST_OR_PREV || - find_flag == HA_READ_BEFORE_KEY) + + if (have_start_key && + (m_start_key.flag == HA_READ_PREFIX_LAST || + m_start_key.flag == HA_READ_PREFIX_LAST_OR_PREV || + m_start_key.flag == HA_READ_BEFORE_KEY)) { reverse_order= TRUE; m_ordered_scan_ongoing= TRUE; } if (!m_ordered_scan_ongoing || - (find_flag == HA_READ_KEY_EXACT && - (key_len >= m_curr_key_info->key_length || - key_len == 0))) - { + (have_start_key && m_start_key.flag == HA_READ_KEY_EXACT && + (key_len >= m_curr_key_info->key_length || key_len == 0))) + { /* We use unordered index scan either when read_range is used and flag is set to not use ordered or when an exact key is used and in this @@ -3815,7 +3839,7 @@ int ha_partition::index_last(uchar * buf) Common routine for index_first/index_last SYNOPSIS - common_index_first_last + ha_partition::common_first_last() see index_first for rest */ @@ -3859,7 +3883,10 @@ int ha_partition::index_read_last_map(uchar *buf, const uchar *key, m_ordered= TRUE; // Safety measure end_range= 0; m_index_scan_type= partition_index_read_last; - DBUG_RETURN(common_index_read(buf, key, keypart_map, HA_READ_PREFIX_LAST)); + m_start_key.key= key; + m_start_key.keypart_map= keypart_map; + m_start_key.flag= HA_READ_PREFIX_LAST; + DBUG_RETURN(common_index_read(buf, TRUE)); } @@ -3990,23 +4017,15 @@ int ha_partition::read_range_first(const key_range *start_key, ((end_key->flag == HA_READ_BEFORE_KEY) ? 1 : (end_key->flag == HA_READ_AFTER_KEY) ? -1 : 0); } - range_key_part= m_curr_key_info->key_part; - if (!start_key) // Read first record - { - if (m_ordered) - m_index_scan_type= partition_index_first; - else - m_index_scan_type= partition_index_first_unordered; - error= common_first_last(m_rec0); - } + range_key_part= m_curr_key_info->key_part; + if (start_key) + m_start_key= *start_key; else - { - m_index_scan_type= partition_index_read; - error= common_index_read(m_rec0, - start_key->key, - start_key->keypart_map, start_key->flag); - } + m_start_key.key= NULL; + + m_index_scan_type= partition_read_range; + error= common_index_read(m_rec0, test(start_key)); DBUG_RETURN(error); } @@ -4028,26 +4047,36 @@ int ha_partition::read_range_next() if (m_ordered) { - DBUG_RETURN(handler::read_range_next()); + DBUG_RETURN(handle_ordered_next(table->record[0], eq_range)); } - DBUG_RETURN(handle_unordered_next(m_rec0, eq_range)); + DBUG_RETURN(handle_unordered_next(table->record[0], eq_range)); } /* - Common routine to set up scans + Common routine to set up index scans SYNOPSIS - buf Buffer to later return record in - idx_read_flag Is it index scan + ha_partition::partition_scan_set_up() + buf Buffer to later return record in (this function + needs it to calculcate partitioning function + values) + + idx_read_flag TRUE <=> m_start_key has range start endpoint which + probably can be used to determine the set of partitions + to scan. + FALSE <=> there is no start endpoint. + + DESCRIPTION + Find out which partitions we'll need to read when scanning the specified + range. + + If we need to scan only one partition, set m_ordered_scan_ongoing=FALSE + as we will not need to do merge ordering. RETURN VALUE >0 Error code 0 Success - - DESCRIPTION - This is where we check which partitions to actually scan if not all - of them */ int ha_partition::partition_scan_set_up(uchar * buf, bool idx_read_flag) @@ -4138,10 +4167,19 @@ int ha_partition::handle_unordered_next(uchar *buf, bool is_next_same) DBUG_ENTER("ha_partition::handle_unordered_next"); /* - We should consider if this should be split into two functions as - next_same is alwas a local constant + We should consider if this should be split into three functions as + partition_read_range is_next_same are always local constants */ - if (is_next_same) + + if (m_index_scan_type == partition_read_range) + { + if (!(error= file->read_range_next())) + { + m_last_part= m_part_spec.start_part; + DBUG_RETURN(0); + } + } + else if (is_next_same) { if (!(error= file->index_next_same(buf, m_start_key.key, m_start_key.length))) @@ -4150,15 +4188,13 @@ int ha_partition::handle_unordered_next(uchar *buf, bool is_next_same) DBUG_RETURN(0); } } - else if (!(error= file->index_next(buf))) + else { - if (!(file->index_flags(active_index, 0, 1) & HA_READ_ORDER) || - compare_key(end_range) <= 0) + if (!(error= file->index_next(buf))) { m_last_part= m_part_spec.start_part; DBUG_RETURN(0); // Row was in range } - error= HA_ERR_END_OF_FILE; } if (error == HA_ERR_END_OF_FILE) @@ -4202,6 +4238,11 @@ int ha_partition::handle_unordered_scan_next_partition(uchar * buf) file= m_file[i]; m_part_spec.start_part= i; switch (m_index_scan_type) { + case partition_read_range: + DBUG_PRINT("info", ("read_range_first on partition %d", i)); + error= file->read_range_first(m_start_key.key? &m_start_key: NULL, + end_range, eq_range, FALSE); + break; case partition_index_read: DBUG_PRINT("info", ("index_read on partition %d", i)); error= file->index_read_map(buf, m_start_key.key, @@ -4230,13 +4271,8 @@ int ha_partition::handle_unordered_scan_next_partition(uchar * buf) } if (!error) { - if (!(file->index_flags(active_index, 0, 1) & HA_READ_ORDER) || - compare_key(end_range) <= 0) - { - m_last_part= i; - DBUG_RETURN(0); - } - error= HA_ERR_END_OF_FILE; + m_last_part= i; + DBUG_RETURN(0); } if ((error != HA_ERR_END_OF_FILE) && (error != HA_ERR_KEY_NOT_FOUND)) DBUG_RETURN(error); @@ -4315,6 +4351,17 @@ int ha_partition::handle_ordered_index_scan(uchar *buf, bool reverse_order) m_start_key.keypart_map); reverse_order= TRUE; break; + case partition_read_range: + { + /* + This can only read record to table->record[0], as it was set when + the table was being opened. We have to memcpy data ourselves. + */ + error= file->read_range_first(&m_start_key, end_range, eq_range, TRUE); + memcpy(rec_buf_ptr, table->record[0], m_rec_length); + reverse_order= FALSE; + break; + } default: DBUG_ASSERT(FALSE); DBUG_RETURN(HA_ERR_END_OF_FILE); @@ -4395,8 +4442,13 @@ int ha_partition::handle_ordered_next(uchar *buf, bool is_next_same) uint part_id= m_top_entry; handler *file= m_file[part_id]; DBUG_ENTER("ha_partition::handle_ordered_next"); - - if (!is_next_same) + + if (m_index_scan_type == partition_read_range) + { + error= file->read_range_next(); + memcpy(rec_buf(part_id), table->record[0], m_rec_length); + } + else if (!is_next_same) error= file->index_next(rec_buf(part_id)); else error= file->index_next_same(rec_buf(part_id), m_start_key.key, diff --git a/sql/ha_partition.h b/sql/ha_partition.h index 97f5624608f..459b566b6d8 100644 --- a/sql/ha_partition.h +++ b/sql/ha_partition.h @@ -49,7 +49,8 @@ private: partition_index_first_unordered= 2, partition_index_last= 3, partition_index_read_last= 4, - partition_no_index_scan= 5 + partition_read_range = 5, + partition_no_index_scan= 6 }; /* Data for the partition handler */ int m_mode; // Open mode @@ -63,8 +64,6 @@ private: handler **m_reorged_file; // Reorganised partitions handler **m_added_file; // Added parts kept for errors partition_info *m_part_info; // local reference to partition - uchar *m_start_key_ref; // Reference of start key in current - // index scan info Field **m_part_field_array; // Part field array locally to save acc uchar *m_ordered_rec_buffer; // Row and key buffer for ord. idx scan KEY *m_curr_key_info; // Current index @@ -429,9 +428,7 @@ public: virtual int read_range_next(); private: - int common_index_read(uchar * buf, const uchar * key, - key_part_map keypart_map, - enum ha_rkey_function find_flag); + int common_index_read(uchar * buf, bool have_start_key); int common_first_last(uchar * buf); int partition_scan_set_up(uchar * buf, bool idx_read_flag); int handle_unordered_next(uchar * buf, bool next_same); From 598a7542959a7bffc40ca6b031a8dec95c79beb8 Mon Sep 17 00:00:00 2001 From: Georgi Kodinov Date: Fri, 19 Sep 2008 16:24:32 +0300 Subject: [PATCH 07/10] fixed a problem with the push of bug #31434 --- mysql-test/t/mysqldump-max.test | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mysql-test/t/mysqldump-max.test b/mysql-test/t/mysqldump-max.test index 1876d759372..1e8b9647503 100644 --- a/mysql-test/t/mysqldump-max.test +++ b/mysql-test/t/mysqldump-max.test @@ -1114,9 +1114,9 @@ CREATE VIEW v1 AS SELECT * FROM t1; INSERT INTO t1 VALUES(); SELECT COUNT(*) FROM v1; ---exec $MYSQL_DUMP --allow-keywords --single-transaction --quick --verbose test --result-file $MYSQL_TEST_DIR/var/tmp/bug31434.sql ---exec $MYSQL test < $MYSQL_TEST_DIR/var/tmp/bug31434.sql ---remove_file $MYSQL_TEST_DIR/var/tmp/bug31434.sql +--exec $MYSQL_DUMP --allow-keywords --single-transaction --quick --verbose test --result-file $MYSQLTEST_VARDIR/tmp/bug31434.sql +--exec $MYSQL test < $MYSQLTEST_VARDIR/tmp/bug31434.sql +--remove_file $MYSQLTEST_VARDIR/tmp/bug31434.sql SELECT COUNT(*) FROM v1; From a2841cf1c17cd3b6487225d2723974130e8eeafd Mon Sep 17 00:00:00 2001 From: He Zhenxing Date: Fri, 26 Sep 2008 17:39:47 +0800 Subject: [PATCH 08/10] BUG#35843 Slow replication slave when using partitioned myisam table In order to improve the performance when replicating to partitioned myisam tables with row-based format, the number of rows of current rows log event is estimated and used to setup storage engine for bulk inserts. --- sql/log_event.cc | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/sql/log_event.cc b/sql/log_event.cc index 27e81c8ecd3..fe9eba81f80 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -8061,7 +8061,6 @@ Write_rows_log_event::do_before_row_operations(const Slave_reporting_capability */ } - m_table->file->ha_start_bulk_insert(0); /* We need TIMESTAMP_NO_AUTO_SET otherwise ha_write_row() will not use fill any TIMESTAMP column with data from the row but instead will use @@ -8200,7 +8199,16 @@ Rows_log_event::write_row(const Relay_log_info *const rli, /* unpack row into table->record[0] */ error= unpack_current_row(rli); // TODO: how to handle errors? - + if (m_curr_row == m_rows_buf) + { + /* this is the first row to be inserted, we estimate the rows with + the size of the first row and use that value to initialize + storage engine for bulk insertion */ + ulong estimated_rows= (m_rows_end - m_curr_row) / (m_curr_row_end - m_curr_row); + m_table->file->ha_start_bulk_insert(estimated_rows); + } + + #ifndef DBUG_OFF DBUG_DUMP("record[0]", table->record[0], table->s->reclength); DBUG_PRINT_BITSET("debug", "write_set = %s", table->write_set); From 0406d409ea8550073465c1744425c599d0107553 Mon Sep 17 00:00:00 2001 From: Davi Arnaut Date: Mon, 29 Sep 2008 10:53:40 -0300 Subject: [PATCH 09/10] Bug#34306: Can't make copy of log tables when server binary log is enabled The problem is that when statement-based replication was enabled, statements such as INSERT INTO .. SELECT FROM .. and CREATE TABLE .. SELECT FROM need to grab a read lock on the source table that does not permit concurrent inserts, which would in turn be denied if the source table is a log table because log tables can't be locked exclusively. The solution is to not take such a lock when the source table is a log table as it is unsafe to replicate log tables under statement based replication. Furthermore, the read lock that does not permits concurrent inserts is now only taken if statement-based replication is enabled and if the source table is not a log table. include/thr_lock.h: Introduce yet another lock type that my get upgraded depending on the binary log format. This is not a optimal solution but can be easily improved later. mysql-test/r/log_tables.result: Add test case result for Bug#34306 mysql-test/suite/binlog/r/binlog_stm_row.result: Add test case result for Bug#34306 mysql-test/suite/binlog/t/binlog_stm_row.test: Add test case for Bug#34306 mysql-test/t/log_tables.test: Add test case for Bug#34306 sql/lock.cc: Assert that TL_READ_DEFAULT is not a real lock type. sql/mysql_priv.h: Export new function. sql/mysqld.cc: Remove using_update_log. sql/sql_base.cc: Introduce function that returns the appropriate read lock type depending on how the statement is going to be replicated. It will only take a TL_READ_NO_INSERT log if the binary is enabled and the binary log format is statement-based and the table is not a log table. sql/sql_parse.cc: Remove using_update_log. sql/sql_update.cc: Use new function to choose read lock type. sql/sql_yacc.yy: The lock type is now decided at open_tables time. This old behavior was actually misleading as the binary log format can be dynamically switched and this would not change for statements that have already been parsed when the binary log format is changed (ie: prepared statements). --- include/thr_lock.h | 8 ++ mysql-test/r/log_tables.result | 29 +++++ .../suite/binlog/r/binlog_stm_row.result | 71 ++++++++++++ mysql-test/suite/binlog/t/binlog_stm_row.test | 107 ++++++++++++++++++ mysql-test/t/log_tables.test | 46 ++++++++ sql/lock.cc | 2 +- sql/mysql_priv.h | 3 +- sql/mysqld.cc | 10 +- sql/sql_base.cc | 41 ++++++- sql/sql_parse.cc | 2 +- sql/sql_update.cc | 2 +- sql/sql_yacc.yy | 4 +- 12 files changed, 310 insertions(+), 15 deletions(-) create mode 100644 mysql-test/suite/binlog/r/binlog_stm_row.result create mode 100644 mysql-test/suite/binlog/t/binlog_stm_row.test diff --git a/include/thr_lock.h b/include/thr_lock.h index a4ca6e6ddf2..77d428d1805 100644 --- a/include/thr_lock.h +++ b/include/thr_lock.h @@ -29,6 +29,14 @@ extern ulong locks_immediate,locks_waited ; enum thr_lock_type { TL_IGNORE=-1, TL_UNLOCK, /* UNLOCK ANY LOCK */ + /* + Parser only! At open_tables() becomes TL_READ or + TL_READ_NO_INSERT depending on the binary log format + (SBR/RBR) and on the table category (log table). + Used for tables that are read by statements which + modify tables. + */ + TL_READ_DEFAULT, TL_READ, /* Read lock */ TL_READ_WITH_SHARED_LOCKS, /* High prior. than TL_WRITE. Allow concurrent insert */ diff --git a/mysql-test/r/log_tables.result b/mysql-test/r/log_tables.result index 2a4cee9fbbc..c5228538788 100644 --- a/mysql-test/r/log_tables.result +++ b/mysql-test/r/log_tables.result @@ -832,6 +832,35 @@ Execute select '000 001 002 003 004 005 006 007 008 009010 011 012 013 014 015 0 Query set global general_log = off deallocate prepare long_query; set global general_log = @old_general_log_state; +DROP TABLE IF EXISTS log_count; +DROP TABLE IF EXISTS slow_log_copy; +DROP TABLE IF EXISTS general_log_copy; +CREATE TABLE log_count (count BIGINT(21)); +SET @old_general_log_state = @@global.general_log; +SET @old_slow_log_state = @@global.slow_query_log; +SET GLOBAL general_log = ON; +SET GLOBAL slow_query_log = ON; +CREATE TABLE slow_log_copy SELECT * FROM mysql.slow_log; +INSERT INTO slow_log_copy SELECT * FROM mysql.slow_log; +INSERT INTO log_count (count) VALUES ((SELECT count(*) FROM mysql.slow_log)); +DROP TABLE slow_log_copy; +CREATE TABLE general_log_copy SELECT * FROM mysql.general_log; +INSERT INTO general_log_copy SELECT * FROM mysql.general_log; +INSERT INTO log_count (count) VALUES ((SELECT count(*) FROM mysql.general_log)); +DROP TABLE general_log_copy; +SET GLOBAL general_log = OFF; +SET GLOBAL slow_query_log = OFF; +CREATE TABLE slow_log_copy SELECT * FROM mysql.slow_log; +INSERT INTO slow_log_copy SELECT * FROM mysql.slow_log; +INSERT INTO log_count (count) VALUES ((SELECT count(*) FROM mysql.slow_log)); +DROP TABLE slow_log_copy; +CREATE TABLE general_log_copy SELECT * FROM mysql.general_log; +INSERT INTO general_log_copy SELECT * FROM mysql.general_log; +INSERT INTO log_count (count) VALUES ((SELECT count(*) FROM mysql.general_log)); +DROP TABLE general_log_copy; +SET GLOBAL general_log = @old_general_log_state; +SET GLOBAL slow_query_log = @old_slow_log_state; +DROP TABLE log_count; SET @old_slow_log_state = @@global.slow_query_log; SET SESSION long_query_time = 0; SET GLOBAL slow_query_log = ON; diff --git a/mysql-test/suite/binlog/r/binlog_stm_row.result b/mysql-test/suite/binlog/r/binlog_stm_row.result new file mode 100644 index 00000000000..97ec38cfb3e --- /dev/null +++ b/mysql-test/suite/binlog/r/binlog_stm_row.result @@ -0,0 +1,71 @@ +DROP TABLE IF EXISTS t1; +DROP TABLE IF EXISTS t2; +SET GLOBAL BINLOG_FORMAT = STATEMENT; +SET SESSION BINLOG_FORMAT = STATEMENT; +CREATE TABLE t1 (a INT); +CREATE TABLE t2 LIKE t1; +select @@SESSION.BINLOG_FORMAT; +@@SESSION.BINLOG_FORMAT +STATEMENT +INSERT INTO t1 VALUES(1); +INSERT INTO t2 VALUES(2); +# +# Ensure that INSERT INTO .. SELECT FROM under SBR takes a read +# lock that will prevent the source table from being modified. +# +# con1 +SELECT GET_LOCK('Bug#34306', 120); +GET_LOCK('Bug#34306', 120) +1 +# con2 +PREPARE stmt FROM "INSERT INTO t1 SELECT * FROM t2 WHERE GET_LOCK('Bug#34306', 120)"; +EXECUTE stmt;; +# default +INSERT INTO t2 VALUES (3);; +# con1 +SELECT RELEASE_LOCK('Bug#34306'); +RELEASE_LOCK('Bug#34306') +1 +# con2 +SELECT RELEASE_LOCK('Bug#34306'); +RELEASE_LOCK('Bug#34306') +1 +# default +# +# Ensure that INSERT INTO .. SELECT FROM prepared under SBR does +# not prevent the source table from being modified if under RBR. +# +# con2 +SET SESSION BINLOG_FORMAT = ROW; +# con1 +SELECT GET_LOCK('Bug#34306', 120); +GET_LOCK('Bug#34306', 120) +1 +# con2 +EXECUTE stmt;; +# default +# con1 +INSERT INTO t2 VALUES (4); +SELECT RELEASE_LOCK('Bug#34306'); +RELEASE_LOCK('Bug#34306') +1 +# con2 +# default +# Show binlog events +show binlog events from ; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Query # # use `test`; DROP TABLE IF EXISTS t1 +master-bin.000001 # Query # # use `test`; DROP TABLE IF EXISTS t2 +master-bin.000001 # Query # # use `test`; CREATE TABLE t1 (a INT) +master-bin.000001 # Query # # use `test`; CREATE TABLE t2 LIKE t1 +master-bin.000001 # Query # # use `test`; INSERT INTO t1 VALUES(1) +master-bin.000001 # Query # # use `test`; INSERT INTO t2 VALUES(2) +master-bin.000001 # Query # # use `test`; INSERT INTO t1 SELECT * FROM t2 WHERE GET_LOCK('Bug#34306', 120) +master-bin.000001 # Query # # use `test`; INSERT INTO t2 VALUES (3) +master-bin.000001 # Query # # use `test`; INSERT INTO t2 VALUES (4) +master-bin.000001 # Query # # use `test`; BEGIN +master-bin.000001 # Table_map # # table_id: # (test.t1) +master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F +master-bin.000001 # Query # # use `test`; COMMIT +DROP TABLE t1; +DROP TABLE t2; diff --git a/mysql-test/suite/binlog/t/binlog_stm_row.test b/mysql-test/suite/binlog/t/binlog_stm_row.test new file mode 100644 index 00000000000..4944e65c9f3 --- /dev/null +++ b/mysql-test/suite/binlog/t/binlog_stm_row.test @@ -0,0 +1,107 @@ +--source include/have_log_bin.inc +--source include/have_binlog_format_row_or_statement.inc + +# Get rid of previous tests binlog +--disable_query_log +reset master; +--enable_query_log + +# +# Bug#34306: Can't make copy of log tables when server binary log is enabled +# +# This is an additional test for Bug#34306 in order to ensure that INSERT INTO +# .. SELECT FROM is properly replicated under SBR and RBR and that the proper +# read lock type are acquired. +# + +--disable_warnings +DROP TABLE IF EXISTS t1; +DROP TABLE IF EXISTS t2; +--enable_warnings + +SET GLOBAL BINLOG_FORMAT = STATEMENT; +SET SESSION BINLOG_FORMAT = STATEMENT; + +CREATE TABLE t1 (a INT); +CREATE TABLE t2 LIKE t1; +select @@SESSION.BINLOG_FORMAT; +INSERT INTO t1 VALUES(1); +INSERT INTO t2 VALUES(2); + +--connect(con1,localhost,root,,) +--connect(con2,localhost,root,,) + +--echo # +--echo # Ensure that INSERT INTO .. SELECT FROM under SBR takes a read +--echo # lock that will prevent the source table from being modified. +--echo # + +--connection con1 +--echo # con1 +SELECT GET_LOCK('Bug#34306', 120); +--connection con2 +--echo # con2 +PREPARE stmt FROM "INSERT INTO t1 SELECT * FROM t2 WHERE GET_LOCK('Bug#34306', 120)"; +--send EXECUTE stmt; +--connection default +--echo # default +let $wait_condition= + SELECT COUNT(*) = 1 FROM information_schema.processlist WHERE + state = "User lock" AND + info = "INSERT INTO t1 SELECT * FROM t2 WHERE GET_LOCK('Bug#34306', 120)"; +--source include/wait_condition.inc +--send INSERT INTO t2 VALUES (3); +--connection con1 +--echo # con1 +let $wait_condition= + SELECT COUNT(*) = 1 FROM information_schema.processlist WHERE + state = "Locked" and info = "INSERT INTO t2 VALUES (3)"; +--source include/wait_condition.inc +SELECT RELEASE_LOCK('Bug#34306'); +--connection con2 +--echo # con2 +--reap +SELECT RELEASE_LOCK('Bug#34306'); +--connection default +--echo # default +--reap + +--echo # +--echo # Ensure that INSERT INTO .. SELECT FROM prepared under SBR does +--echo # not prevent the source table from being modified if under RBR. +--echo # + +--connection con2 +--echo # con2 +SET SESSION BINLOG_FORMAT = ROW; +--connection con1 +--echo # con1 +SELECT GET_LOCK('Bug#34306', 120); +--connection con2 +--echo # con2 +--send EXECUTE stmt; +--connection default +--echo # default +let $wait_condition= + SELECT COUNT(*) = 1 FROM information_schema.processlist WHERE + state = "User lock" AND + info = "INSERT INTO t1 SELECT * FROM t2 WHERE GET_LOCK('Bug#34306', 120)"; +--source include/wait_condition.inc +--connection con1 +--echo # con1 +INSERT INTO t2 VALUES (4); +SELECT RELEASE_LOCK('Bug#34306'); +--connection con2 +--echo # con2 +--reap + +--disconnect con1 +--disconnect con2 +--connection default +--echo # default + +--echo # Show binlog events +source include/show_binlog_events.inc; + +DROP TABLE t1; +DROP TABLE t2; diff --git a/mysql-test/t/log_tables.test b/mysql-test/t/log_tables.test index 3047d16d3b6..34086336fa8 100644 --- a/mysql-test/t/log_tables.test +++ b/mysql-test/t/log_tables.test @@ -936,6 +936,52 @@ select command_type, argument from mysql.general_log where thread_id = @thread_i deallocate prepare long_query; set global general_log = @old_general_log_state; +# +# Bug#34306: Can't make copy of log tables when server binary log is enabled +# + +--disable_warnings +DROP TABLE IF EXISTS log_count; +DROP TABLE IF EXISTS slow_log_copy; +DROP TABLE IF EXISTS general_log_copy; +--enable_warnings + +CREATE TABLE log_count (count BIGINT(21)); + +SET @old_general_log_state = @@global.general_log; +SET @old_slow_log_state = @@global.slow_query_log; + +SET GLOBAL general_log = ON; +SET GLOBAL slow_query_log = ON; + +CREATE TABLE slow_log_copy SELECT * FROM mysql.slow_log; +INSERT INTO slow_log_copy SELECT * FROM mysql.slow_log; +INSERT INTO log_count (count) VALUES ((SELECT count(*) FROM mysql.slow_log)); +DROP TABLE slow_log_copy; + +CREATE TABLE general_log_copy SELECT * FROM mysql.general_log; +INSERT INTO general_log_copy SELECT * FROM mysql.general_log; +INSERT INTO log_count (count) VALUES ((SELECT count(*) FROM mysql.general_log)); +DROP TABLE general_log_copy; + +SET GLOBAL general_log = OFF; +SET GLOBAL slow_query_log = OFF; + +CREATE TABLE slow_log_copy SELECT * FROM mysql.slow_log; +INSERT INTO slow_log_copy SELECT * FROM mysql.slow_log; +INSERT INTO log_count (count) VALUES ((SELECT count(*) FROM mysql.slow_log)); +DROP TABLE slow_log_copy; + +CREATE TABLE general_log_copy SELECT * FROM mysql.general_log; +INSERT INTO general_log_copy SELECT * FROM mysql.general_log; +INSERT INTO log_count (count) VALUES ((SELECT count(*) FROM mysql.general_log)); +DROP TABLE general_log_copy; + +SET GLOBAL general_log = @old_general_log_state; +SET GLOBAL slow_query_log = @old_slow_log_state; + +DROP TABLE log_count; + # # Bug #31700: thd->examined_row_count not incremented for 'const' type queries # diff --git a/sql/lock.cc b/sql/lock.cc index 675b94c2175..faddb8c586c 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -854,7 +854,7 @@ static MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, if ((table=table_ptr[i])->s->tmp_table == NON_TRANSACTIONAL_TMP_TABLE) continue; lock_type= table->reginfo.lock_type; - DBUG_ASSERT (lock_type != TL_WRITE_DEFAULT); + DBUG_ASSERT(lock_type != TL_WRITE_DEFAULT && lock_type != TL_READ_DEFAULT); if (lock_type >= TL_WRITE_ALLOW_WRITE) { *write_lock_used=table; diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 3a52c5c0130..c0a75f90802 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -1265,6 +1265,7 @@ bool fix_merge_after_open(TABLE_LIST *old_child_list, TABLE_LIST **old_last, TABLE_LIST *new_child_list, TABLE_LIST **new_last); bool reopen_table(TABLE *table); bool reopen_tables(THD *thd,bool get_locks,bool in_refresh); +thr_lock_type read_lock_type_for_table(THD *thd, TABLE *table); void close_data_files_and_morph_locks(THD *thd, const char *db, const char *table_name); void close_handle_and_leave_table_as_lock(TABLE *table); @@ -1938,7 +1939,7 @@ extern bool opt_using_transactions; extern bool mysqld_embedded; #endif /* MYSQL_SERVER || INNODB_COMPATIBILITY_HOOKS */ #ifdef MYSQL_SERVER -extern bool using_update_log, opt_large_files, server_id_supplied; +extern bool opt_large_files, server_id_supplied; extern bool opt_update_log, opt_bin_log, opt_error_log; extern my_bool opt_log, opt_slow_log; extern ulong log_output_options; diff --git a/sql/mysqld.cc b/sql/mysqld.cc index a3b0123ee4a..b04d4e3cecd 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -382,7 +382,7 @@ my_bool opt_character_set_client_handshake= 1; bool server_id_supplied = 0; bool opt_endinfo, using_udf_functions; my_bool locked_in_memory; -bool opt_using_transactions, using_update_log; +bool opt_using_transactions; bool volatile abort_loop; bool volatile shutdown_in_progress; /** @@ -3815,12 +3815,6 @@ server."); { unireg_abort(1); } - - /* - Used to specify which type of lock we need to use for queries of type - INSERT ... SELECT. This will change when we have row level logging. - */ - using_update_log=1; } /* call ha_init_key_cache() on all key caches to init them */ @@ -7431,7 +7425,7 @@ static void mysql_init_variables(void) slave_open_temp_tables= 0; cached_thread_count= 0; opt_endinfo= using_udf_functions= 0; - opt_using_transactions= using_update_log= 0; + opt_using_transactions= 0; abort_loop= select_thread_in_use= signal_thread_in_use= 0; ready_to_exit= shutdown_in_progress= grant_option= 0; aborted_threads= aborted_connects= 0; diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 6c5e58e2ad5..49efc72ba68 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -4355,6 +4355,38 @@ bool fix_merge_after_open(TABLE_LIST *old_child_list, TABLE_LIST **old_last, } +/* + Return a appropriate read lock type given a table object. + + @param thd Thread context + @param table TABLE object for table to be locked + + @remark Due to a statement-based replication limitation, statements such as + INSERT INTO .. SELECT FROM .. and CREATE TABLE .. SELECT FROM need + to grab a TL_READ_NO_INSERT lock on the source table in order to + prevent the replication of a concurrent statement that modifies the + source table. If such a statement gets applied on the slave before + the INSERT .. SELECT statement finishes, data on the master could + differ from data on the slave and end-up with a discrepancy between + the binary log and table state. Furthermore, this does not apply to + I_S and log tables as it's always unsafe to replicate such tables + under statement-based replication as the table on the slave might + contain other data (ie: general_log is enabled on the slave). The + statement will be marked as unsafe for SBR in decide_logging_format(). +*/ + +thr_lock_type read_lock_type_for_table(THD *thd, TABLE *table) +{ + bool log_on= mysql_bin_log.is_open() && (thd->options & OPTION_BIN_LOG); + ulong binlog_format= thd->variables.binlog_format; + if ((log_on == FALSE) || (binlog_format == BINLOG_FORMAT_ROW) || + (table->s->table_category == TABLE_CATEGORY_PERFORMANCE)) + return TL_READ; + else + return TL_READ_NO_INSERT; +} + + /* Open all tables in list @@ -4629,6 +4661,9 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) { if (tables->lock_type == TL_WRITE_DEFAULT) tables->table->reginfo.lock_type= thd->update_lock_default; + else if (tables->lock_type == TL_READ_DEFAULT) + tables->table->reginfo.lock_type= + read_lock_type_for_table(thd, tables->table); else if (tables->table->s->tmp_table == NO_TMP_TABLE) tables->table->reginfo.lock_type= tables->lock_type; } @@ -5036,7 +5071,11 @@ int decide_logging_format(THD *thd, TABLE_LIST *tables) void* prev_ht= NULL; for (TABLE_LIST *table= tables; table; table= table->next_global) { - if (!table->placeholder() && table->lock_type >= TL_WRITE_ALLOW_WRITE) + if (table->placeholder()) + continue; + if (table->table->s->table_category == TABLE_CATEGORY_PERFORMANCE) + thd->lex->set_stmt_unsafe(); + if (table->lock_type >= TL_WRITE_ALLOW_WRITE) { ulonglong const flags= table->table->file->ha_table_flags(); DBUG_PRINT("info", ("table: %s; ha_table_flags: %s%s", diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 9f94893ffe3..30fef9c7ee7 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -5628,7 +5628,7 @@ void mysql_init_multi_delete(LEX *lex) lex->select_lex.select_limit= 0; lex->unit.select_limit_cnt= HA_POS_ERROR; lex->select_lex.table_list.save_and_clear(&lex->auxiliary_table_list); - lex->lock_option= using_update_log ? TL_READ_NO_INSERT : TL_READ; + lex->lock_option= TL_READ_DEFAULT; lex->query_tables= 0; lex->query_tables_last= &lex->query_tables; } diff --git a/sql/sql_update.cc b/sql/sql_update.cc index b9ad88ee663..e2ed3371711 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -1039,7 +1039,7 @@ reopen_tables: correct order of statements. Otherwise, we use a TL_READ lock to improve performance. */ - tl->lock_type= using_update_log ? TL_READ_NO_INSERT : TL_READ; + tl->lock_type= read_lock_type_for_table(thd, table); tl->updating= 0; /* Update TABLE::lock_type accordingly. */ if (!tl->placeholder() && !using_lock_tables) diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 4a8796f20e5..278fe88c336 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -4301,7 +4301,7 @@ create_select: SELECT_SYM { LEX *lex=Lex; - lex->lock_option= using_update_log ? TL_READ_NO_INSERT : TL_READ; + lex->lock_option= TL_READ_DEFAULT; if (lex->sql_command == SQLCOM_INSERT) lex->sql_command= SQLCOM_INSERT_SELECT; else if (lex->sql_command == SQLCOM_REPLACE) @@ -9398,7 +9398,7 @@ insert: lex->duplicates= DUP_ERROR; mysql_init_select(lex); /* for subselects */ - lex->lock_option= (using_update_log) ? TL_READ_NO_INSERT : TL_READ; + lex->lock_option= TL_READ_DEFAULT; } insert_lock_option opt_ignore insert2 From 6816cf6a6518bb5108fe7907f71e58be994db6e5 Mon Sep 17 00:00:00 2001 From: Davi Arnaut Date: Tue, 30 Sep 2008 10:47:01 -0300 Subject: [PATCH 10/10] Bug#34306: Can't make copy of log tables when server binary log is enabled Post-merge bug fix: lock_type is a enumeration type and not a bit mask. sql/sql_cache.cc: Check for lock type explicitly. Also err on the safe side and invalidate the query cache for any write lock. --- sql/sql_cache.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sql/sql_cache.cc b/sql/sql_cache.cc index f5566acbc6f..81d0d447ac1 100644 --- a/sql/sql_cache.cc +++ b/sql/sql_cache.cc @@ -1542,10 +1542,9 @@ void Query_cache::invalidate_locked_for_write(TABLE_LIST *tables_used) for (; tables_used; tables_used= tables_used->next_local) { thd_proc_info(thd, "invalidating query cache entries (table)"); - if (tables_used->lock_type & (TL_WRITE_LOW_PRIORITY | TL_WRITE) && + if (tables_used->lock_type >= TL_WRITE_ALLOW_WRITE && tables_used->table) { - THD *thd= current_thd; invalidate_table(thd, tables_used->table); } }