diff --git a/mysql-test/r/rpl_multi_update2.result b/mysql-test/r/rpl_multi_update2.result new file mode 100644 index 00000000000..40193f43e49 --- /dev/null +++ b/mysql-test/r/rpl_multi_update2.result @@ -0,0 +1,42 @@ +slave stop; +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; +slave start; +CREATE TABLE t1 ( +a int unsigned not null auto_increment primary key, +b int unsigned +) TYPE=MyISAM; +CREATE TABLE t2 ( +a int unsigned not null auto_increment primary key, +b int unsigned +) TYPE=MyISAM; +INSERT INTO t1 VALUES (NULL, 0); +INSERT INTO t1 SELECT NULL, 0 FROM t1; +INSERT INTO t2 VALUES (NULL, 0), (NULL,1); +SELECT * FROM t1 ORDER BY a; +a b +1 0 +2 0 +SELECT * FROM t2 ORDER BY a; +a b +1 0 +2 1 +UPDATE t1, t2 SET t1.b = (t2.b+4) WHERE t1.a = t2.a; +SELECT * FROM t1 ORDER BY a; +a b +1 4 +2 5 +SELECT * FROM t2 ORDER BY a; +a b +1 0 +2 1 +SELECT * FROM t1 ORDER BY a; +a b +1 4 +2 5 +SELECT * FROM t2 ORDER BY a; +a b +1 0 +2 1 diff --git a/mysql-test/t/rpl_multi_update2-slave.opt b/mysql-test/t/rpl_multi_update2-slave.opt new file mode 100644 index 00000000000..17d4171af0e --- /dev/null +++ b/mysql-test/t/rpl_multi_update2-slave.opt @@ -0,0 +1 @@ +--replicate-ignore-table=nothing.sensible diff --git a/mysql-test/t/rpl_multi_update2.test b/mysql-test/t/rpl_multi_update2.test new file mode 100644 index 00000000000..8216056aca5 --- /dev/null +++ b/mysql-test/t/rpl_multi_update2.test @@ -0,0 +1,33 @@ +# Let's verify that multi-update is not always skipped by slave if +# some replicate-* rules exist. +# (BUG#7011) + +source include/master-slave.inc; + +CREATE TABLE t1 ( + a int unsigned not null auto_increment primary key, + b int unsigned +) TYPE=MyISAM; + +CREATE TABLE t2 ( + a int unsigned not null auto_increment primary key, + b int unsigned +) TYPE=MyISAM; + +INSERT INTO t1 VALUES (NULL, 0); +INSERT INTO t1 SELECT NULL, 0 FROM t1; + +INSERT INTO t2 VALUES (NULL, 0), (NULL,1); + +SELECT * FROM t1 ORDER BY a; +SELECT * FROM t2 ORDER BY a; + +UPDATE t1, t2 SET t1.b = (t2.b+4) WHERE t1.a = t2.a; +SELECT * FROM t1 ORDER BY a; +SELECT * FROM t2 ORDER BY a; + +save_master_pos; +connection slave; +sync_with_master; +SELECT * FROM t1 ORDER BY a; +SELECT * FROM t2 ORDER BY a; diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index e71833e6af6..06a17acd2a1 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -564,6 +564,10 @@ int mysql_multi_update(THD *thd, TABLE_LIST *table_list, COND *conds, ulong options, enum enum_duplicates handle_duplicates, bool ignore, SELECT_LEX_UNIT *unit, SELECT_LEX *select_lex); +int mysql_multi_update_lock(THD *thd, + TABLE_LIST *table_list, + List *fields, + SELECT_LEX *select_lex); int mysql_prepare_insert(THD *thd, TABLE_LIST *table_list, TABLE_LIST *insert_table_list, TABLE *table, List &fields, List_item *values, diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index d3a5617d01e..85cd24c4fbb 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -52,6 +52,8 @@ static int check_for_max_user_connections(THD *thd, USER_CONN *uc); #endif static void decrease_user_connections(USER_CONN *uc); static bool check_db_used(THD *thd,TABLE_LIST *tables); +static bool check_multi_update_lock(THD *thd, TABLE_LIST *tables, + List *fields, SELECT_LEX *select_lex); static void remove_escape(char *name); static void refresh_status(void); static bool append_file_to_dir(THD *thd, const char **filename_ptr, @@ -1883,6 +1885,8 @@ mysql_execute_command(THD *thd) { int res= 0; LEX *lex= thd->lex; + bool slave_fake_lock= 0; + MYSQL_LOCK *fake_prev_lock= 0; SELECT_LEX *select_lex= &lex->select_lex; TABLE_LIST *tables= (TABLE_LIST*) select_lex->table_list.first; SELECT_LEX_UNIT *unit= &lex->unit; @@ -1900,6 +1904,23 @@ mysql_execute_command(THD *thd) #ifdef HAVE_REPLICATION if (thd->slave_thread) { + if (lex->sql_command == SQLCOM_UPDATE_MULTI) + { + DBUG_PRINT("info",("need faked locked tables")); + + if (check_multi_update_lock(thd, tables, &select_lex->item_list, + select_lex)) + goto error; + + /* Fix for replication, the tables are opened and locked, + now we pretend that we have performed a LOCK TABLES action */ + + fake_prev_lock= thd->locked_tables; + if (thd->lock) + thd->locked_tables= thd->lock; + thd->lock= 0; + slave_fake_lock= 1; + } /* Skip if we are in the slave thread, some table rules have been given and the table list says the query should not be replicated @@ -3582,6 +3603,14 @@ purposes internal to the MySQL server", MYF(0)); send_error(thd,thd->killed ? ER_SERVER_SHUTDOWN : 0); error: + if (unlikely(slave_fake_lock)) + { + DBUG_PRINT("info",("undoing faked lock")); + thd->lock= thd->locked_tables; + thd->locked_tables= fake_prev_lock; + if (thd->lock == thd->locked_tables) + thd->lock= 0; + } DBUG_VOID_RETURN; } @@ -5012,6 +5041,58 @@ bool check_simple_select() return 0; } +/* + Setup locking for multi-table updates. Used by the replication slave. + Replication slave SQL thread examines (all_tables_not_ok()) the + locking state of referenced tables to determine if the query has to + be executed or ignored. Since in multi-table update, the + 'default' lock is read-only, this lock is corrected early enough by + calling this function, before the slave decides to execute/ignore. + + SYNOPSIS + check_multi_update_lock() + thd Current thread + tables List of user-supplied tables + fields List of fields requiring update + + RETURN VALUES + 0 ok + 1 error +*/ +static bool check_multi_update_lock(THD *thd, TABLE_LIST *tables, + List *fields, SELECT_LEX *select_lex) +{ + bool res= 1; + TABLE_LIST *table; + DBUG_ENTER("check_multi_update_lock"); + + if (check_db_used(thd, tables)) + goto error; + + /* + Ensure that we have UPDATE or SELECT privilege for each table + The exact privilege is checked in mysql_multi_update() + */ + for (table= tables ; table ; table= table->next) + { + TABLE_LIST *save= table->next; + table->next= 0; + if ((check_access(thd, UPDATE_ACL, table->db, &table->grant.privilege,0,1) || + (grant_option && check_grant(thd, UPDATE_ACL, table,0,1,1))) && + check_one_table_access(thd, SELECT_ACL, table)) + goto error; + table->next= save; + } + + if (mysql_multi_update_lock(thd, tables, fields, select_lex)) + goto error; + + res= 0; + +error: + DBUG_RETURN(res); +} + Comp_creator *comp_eq_creator(bool invert) { diff --git a/sql/sql_update.cc b/sql/sql_update.cc index c2894a0c86b..6ca794283f1 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -464,30 +464,23 @@ static table_map get_table_map(List *items) } - /* - Setup multi-update handling and call SELECT to do the join + Prepare tables for multi-update + Analyse which tables need specific privileges and perform locking + as required */ -int mysql_multi_update(THD *thd, - TABLE_LIST *table_list, - List *fields, - List *values, - COND *conds, - ulong options, - enum enum_duplicates handle_duplicates, bool ignore, - SELECT_LEX_UNIT *unit, SELECT_LEX *select_lex) +int mysql_multi_update_lock(THD *thd, + TABLE_LIST *table_list, + List *fields, + SELECT_LEX *select_lex) { int res; - multi_update *result; TABLE_LIST *tl; TABLE_LIST *update_list= (TABLE_LIST*) thd->lex->select_lex.table_list.first; - List total_list; const bool using_lock_tables= thd->locked_tables != 0; bool initialized_dervied= 0; - DBUG_ENTER("mysql_multi_update"); - - select_lex->select_limit= HA_POS_ERROR; + DBUG_ENTER("mysql_multi_update_lock"); /* The following loop is here to to ensure that we only lock tables @@ -593,7 +586,7 @@ int mysql_multi_update(THD *thd, (grant_option && check_grant(thd, wants, tl, 0, 0, 0))) { tl->next= save; - DBUG_RETURN(0); + DBUG_RETURN(1); } tl->next= save; } @@ -616,11 +609,7 @@ int mysql_multi_update(THD *thd, /* Relock the tables with the correct modes */ res= lock_tables(thd, table_list, table_count); if (using_lock_tables) - { - if (res) - DBUG_RETURN(res); break; // Don't have to do setup_field() - } /* We must setup fields again as the file may have been reopened @@ -651,6 +640,32 @@ int mysql_multi_update(THD *thd, */ close_thread_tables(thd); } + + DBUG_RETURN(res); +} + +/* + Setup multi-update handling and call SELECT to do the join +*/ + +int mysql_multi_update(THD *thd, + TABLE_LIST *table_list, + List *fields, + List *values, + COND *conds, + ulong options, + enum enum_duplicates handle_duplicates, bool ignore, + SELECT_LEX_UNIT *unit, SELECT_LEX *select_lex) +{ + int res; + TABLE_LIST *tl; + TABLE_LIST *update_list= (TABLE_LIST*) thd->lex->select_lex.table_list.first; + List total_list; + multi_update *result; + DBUG_ENTER("mysql_multi_update"); + + if ((res= mysql_multi_update_lock(thd, table_list, fields, select_lex))) + DBUG_RETURN(res); /* Setup timestamp handling */ for (tl= update_list; tl; tl= tl->next)