Commit 69b8b3ff authored by unknown's avatar unknown

* Fix for BUG#1248: "LOAD DATA FROM MASTER drops the slave's db unexpectedly".

Now LOAD DATA FROM MASTER does not drop the database, instead it only tries to
create it, and drops/creates table-by-table.
* replicate_wild_ignore_table='db1.%' is now considered as "ignore the 'db1'
database as a whole", as it already works for CREATE DATABASE and DROP DATABASE.


mysql-test/r/rpl000009.result:
  result update
mysql-test/t/rpl000009.test:
  test that LOAD DATA FROM MASTER does not drop databases,
  but rather table by table, thus preserving non-replicated tables.
  Test that LOAD DATA FROM MASTER reports the error when a table could not
  be dropped (system's "permission denied" for example).
  Test that LOAD TABLE FROM MASTER reports the error when the table already exists.
sql/repl_failsafe.cc:
  * replicate_wild_ignore_table='db1.%' is now considered as "ignore the 'db1'
  database as a whole", as it already works for CREATE DATABASE and DROP DATABASE.
  * If a db matches replicate_*_db rules, we don't drop/recreate it because this
  could drop some tables in this db which could be slave-specific. Instead,
  we do a CREATE DATABASE IF EXISTS, and we will drop each table which has
  an equivalent on the master, table-by-table.
sql/slave.cc:
  New argument to drop the table in create_table_from_dump() 
  (LOAD TABLE/DATA FROM MASTER are the only places where this function is used).
  This is needed because LOAD DATA FROM MASTER does not drop the database anymore.
  The behaviour when the table exists is unchanged: LOAD DATA silently replaces
  the table, LOAD TABLE gives error.
sql/slave.h:
  new argument to drop the table in fetch_master_table
sql/sql_parse.cc:
  do not drop the table in LOAD TABLE FROM MASTER (this behaviour is already
  true; but changes in LOAD DATA FROM MASTER made the argument needed).
parent 8272be94
...@@ -49,21 +49,48 @@ show databases; ...@@ -49,21 +49,48 @@ show databases;
Database Database
mysql mysql
test test
create database foo;
create table foo.t1(n int, s char(20));
insert into foo.t1 values (1, 'original foo.t1');
create table foo.t3(n int, s char(20));
insert into foo.t3 values (1, 'original foo.t3');
create database foo2;
create table foo2.t1(n int, s char(20));
insert into foo2.t1 values (1, 'original foo2.t1');
create database bar;
create table bar.t1(n int, s char(20));
insert into bar.t1 values (1, 'original bar.t1');
create table bar.t3(n int, s char(20));
insert into bar.t3 values (1, 'original bar.t3');
load data from master; load data from master;
show databases; show databases;
Database Database
bar bar
foo foo
foo2
mysql mysql
test test
use foo; use foo;
show tables; show tables;
Tables_in_foo Tables_in_foo
t1
t3
select * from t1;
n s
1 original foo.t1
use foo2;
show tables;
Tables_in_foo2
t1
select * from t1;
n s
1 original foo2.t1
use bar; use bar;
show tables; show tables;
Tables_in_bar Tables_in_bar
t1 t1
t2 t2
t3
select * from bar.t1; select * from bar.t1;
n s n s
1 one bar 1 one bar
...@@ -74,6 +101,9 @@ n s ...@@ -74,6 +101,9 @@ n s
11 eleven bar 11 eleven bar
12 twelve bar 12 twelve bar
13 thirteen bar 13 thirteen bar
select * from bar.t3;
n s
1 original bar.t3
insert into bar.t1 values (4, 'four bar'); insert into bar.t1 values (4, 'four bar');
select * from bar.t1; select * from bar.t1;
n s n s
...@@ -81,5 +111,23 @@ n s ...@@ -81,5 +111,23 @@ n s
2 two bar 2 two bar
3 three bar 3 three bar
4 four bar 4 four bar
insert into bar.t1 values(10, 'should be there');
flush tables;
load data from master;
Error on delete of './bar/t1.MYI' (Errcode: 13)
select * from bar.t1;
n s
1 one bar
2 two bar
3 three bar
4 four bar
10 should be there
load table bar.t1 from master;
Table 't1' already exists
drop table bar.t1;
load table bar.t1 from master;
start slave;
drop database bar; drop database bar;
drop database foo; drop database foo;
drop database foo;
drop database foo2;
...@@ -60,16 +60,45 @@ sync_with_master; ...@@ -60,16 +60,45 @@ sync_with_master;
# This should show that the slave is empty at this point # This should show that the slave is empty at this point
show databases; show databases;
# Create foo and foo2 on slave; we expect that LOAD DATA FROM MASTER will
# neither touch database foo nor foo2.
create database foo;
create table foo.t1(n int, s char(20));
insert into foo.t1 values (1, 'original foo.t1');
create table foo.t3(n int, s char(20));
insert into foo.t3 values (1, 'original foo.t3');
create database foo2;
create table foo2.t1(n int, s char(20));
insert into foo2.t1 values (1, 'original foo2.t1');
# Create bar, and bar.t1, to check that it gets replaced,
# and bar.t3 to check that it is not touched (there is no bar.t3 on master)
create database bar;
create table bar.t1(n int, s char(20));
insert into bar.t1 values (1, 'original bar.t1');
create table bar.t3(n int, s char(20));
insert into bar.t3 values (1, 'original bar.t3');
load data from master; load data from master;
# Now let's check if we have the right tables and the right data in them # Now let's check if we have the right tables and the right data in them
show databases; show databases;
use foo; use foo;
show tables; # LOAD DATA FROM MASTER uses only replicate_*_db rules to decide which databases
# have to be copied. So it thinks "foo" has to be copied. Before 4.0.16 it would
# first drop "foo", then create "foo". This "drop" is a bug; in that case t3
# would disappear.
# So here the effect of this bug (BUG#1248) would be to leave an empty "foo" on
# the slave.
show tables; # should be t1 & t3
select * from t1; # should be slave's original
use foo2;
show tables; # should be t1
select * from t1; # should be slave's original
use bar; use bar;
show tables; show tables; # should contain master's copied t1&t2, slave's original t3
select * from bar.t1; select * from bar.t1;
select * from bar.t2; select * from bar.t2;
select * from bar.t3;
# Now let's see if replication works # Now let's see if replication works
connection master; connection master;
...@@ -79,6 +108,26 @@ connection slave; ...@@ -79,6 +108,26 @@ connection slave;
sync_with_master; sync_with_master;
select * from bar.t1; select * from bar.t1;
# Check that LOAD DATA FROM MASTER reports the error if it can't drop a
# table to be overwritten.
insert into bar.t1 values(10, 'should be there');
flush tables;
system chmod 500 var/slave-data/bar/;
--error 6
load data from master; # should fail (errno 13)
system chmod 700 var/slave-data/bar/;
select * from bar.t1; # should contain the row (10, ...)
# Check that LOAD TABLE FROM MASTER fails if the table exists on slave
--error 1050
load table bar.t1 from master;
drop table bar.t1;
load table bar.t1 from master;
# as LOAD DATA FROM MASTER failed it did not restart slave threads
start slave;
# Now time for cleanup # Now time for cleanup
connection master; connection master;
drop database bar; drop database bar;
...@@ -86,4 +135,5 @@ drop database foo; ...@@ -86,4 +135,5 @@ drop database foo;
save_master_pos; save_master_pos;
connection slave; connection slave;
sync_with_master; sync_with_master;
drop database foo;
drop database foo2;
...@@ -717,7 +717,8 @@ static int fetch_db_tables(THD *thd, MYSQL *mysql, const char *db, ...@@ -717,7 +717,8 @@ static int fetch_db_tables(THD *thd, MYSQL *mysql, const char *db,
if (!tables_ok(thd, &table)) if (!tables_ok(thd, &table))
continue; continue;
} }
if ((error= fetch_master_table(thd, db, table_name, mi, mysql))) /* download master's table and overwrite slave's table */
if ((error= fetch_master_table(thd, db, table_name, mi, mysql, 1)))
return error; return error;
} }
return 0; return 0;
...@@ -819,8 +820,11 @@ int load_master_data(THD* thd) ...@@ -819,8 +820,11 @@ int load_master_data(THD* thd)
char* db = row[0]; char* db = row[0];
/* /*
Do not replicate databases excluded by rules Do not replicate databases excluded by rules. We also test
also skip mysql database - in most cases the user will replicate_wild_*_table rules (replicate_wild_ignore_table='db1.%' will
be considered as "ignore the 'db1' database as a whole, as it already
works for CREATE DATABASE and DROP DATABASE).
Also skip 'mysql' database - in most cases the user will
mess up and not exclude mysql database with the rules when mess up and not exclude mysql database with the rules when
he actually means to - in this case, he is up for a surprise if he actually means to - in this case, he is up for a surprise if
his priv tables get dropped and downloaded from master his priv tables get dropped and downloaded from master
...@@ -830,14 +834,14 @@ int load_master_data(THD* thd) ...@@ -830,14 +834,14 @@ int load_master_data(THD* thd)
*/ */
if (!db_ok(db, replicate_do_db, replicate_ignore_db) || if (!db_ok(db, replicate_do_db, replicate_ignore_db) ||
!db_ok_with_wild_table(db) ||
!strcmp(db,"mysql")) !strcmp(db,"mysql"))
{ {
*cur_table_res = 0; *cur_table_res = 0;
continue; continue;
} }
if (mysql_rm_db(thd, db, 1,1) || if (mysql_create_db(thd, db, HA_LEX_CREATE_IF_NOT_EXISTS, 1))
mysql_create_db(thd, db, 0, 1))
{ {
send_error(&thd->net, 0, 0); send_error(&thd->net, 0, 0);
cleanup_mysql_results(db_res, cur_table_res - 1, table_res); cleanup_mysql_results(db_res, cur_table_res - 1, table_res);
......
...@@ -72,7 +72,7 @@ static int safe_sleep(THD* thd, int sec, CHECK_KILLED_FUNC thread_killed, ...@@ -72,7 +72,7 @@ static int safe_sleep(THD* thd, int sec, CHECK_KILLED_FUNC thread_killed,
void* thread_killed_arg); void* thread_killed_arg);
static int request_table_dump(MYSQL* mysql, const char* db, const char* table); static int request_table_dump(MYSQL* mysql, const char* db, const char* table);
static int create_table_from_dump(THD* thd, NET* net, const char* db, static int create_table_from_dump(THD* thd, NET* net, const char* db,
const char* table_name); const char* table_name, bool overwrite);
static int check_master_version(MYSQL* mysql, MASTER_INFO* mi); static int check_master_version(MYSQL* mysql, MASTER_INFO* mi);
...@@ -1033,12 +1033,22 @@ static int check_master_version(MYSQL* mysql, MASTER_INFO* mi) ...@@ -1033,12 +1033,22 @@ static int check_master_version(MYSQL* mysql, MASTER_INFO* mi)
return 0; return 0;
} }
/*
Used by fetch_master_table (used by LOAD TABLE tblname FROM MASTER and LOAD
DATA FROM MASTER). Drops the table (if 'overwrite' is true) and recreates it
from the dump. Honours replication inclusion/exclusion rules.
RETURN VALUES
0 success
1 error
*/
static int create_table_from_dump(THD* thd, NET* net, const char* db, static int create_table_from_dump(THD* thd, NET* net, const char* db,
const char* table_name) const char* table_name, bool overwrite)
{ {
ulong packet_len = my_net_read(net); // read create table statement ulong packet_len = my_net_read(net); // read create table statement
char *query; char *query;
char* save_db;
Vio* save_vio; Vio* save_vio;
HA_CHECK_OPT check_opt; HA_CHECK_OPT check_opt;
TABLE_LIST tables; TABLE_LIST tables;
...@@ -1079,12 +1089,23 @@ static int create_table_from_dump(THD* thd, NET* net, const char* db, ...@@ -1079,12 +1089,23 @@ static int create_table_from_dump(THD* thd, NET* net, const char* db,
thd->query_error = 0; thd->query_error = 0;
thd->net.no_send_ok = 1; thd->net.no_send_ok = 1;
/* we do not want to log create table statement */ bzero((char*) &tables,sizeof(tables));
tables.db = (char*)db;
tables.alias= tables.real_name= (char*)table_name;
/* Drop the table if 'overwrite' is true */
if (overwrite && mysql_rm_table(thd,&tables,1)) /* drop if exists */
{
send_error(&thd->net);
sql_print_error("create_table_from_dump: failed to drop the table");
goto err;
}
/* Create the table. We do not want to log the "create table" statement */
save_options = thd->options; save_options = thd->options;
thd->options &= ~(ulong) (OPTION_BIN_LOG); thd->options &= ~(ulong) (OPTION_BIN_LOG);
thd->proc_info = "Creating table from master dump"; thd->proc_info = "Creating table from master dump";
// save old db in case we are creating in a different database // save old db in case we are creating in a different database
char* save_db = thd->db; save_db = thd->db;
thd->db = (char*)db; thd->db = (char*)db;
mysql_parse(thd, thd->query, packet_len); // run create table mysql_parse(thd, thd->query, packet_len); // run create table
thd->db = save_db; // leave things the way the were before thd->db = save_db; // leave things the way the were before
...@@ -1093,11 +1114,8 @@ static int create_table_from_dump(THD* thd, NET* net, const char* db, ...@@ -1093,11 +1114,8 @@ static int create_table_from_dump(THD* thd, NET* net, const char* db,
if (thd->query_error) if (thd->query_error)
goto err; // mysql_parse took care of the error send goto err; // mysql_parse took care of the error send
bzero((char*) &tables,sizeof(tables));
tables.db = (char*)db;
tables.alias= tables.real_name= (char*)table_name;
tables.lock_type = TL_WRITE;
thd->proc_info = "Opening master dump table"; thd->proc_info = "Opening master dump table";
tables.lock_type = TL_WRITE;
if (!open_ltable(thd, &tables, TL_WRITE)) if (!open_ltable(thd, &tables, TL_WRITE))
{ {
send_error(&thd->net,0,0); // Send error from open_ltable send_error(&thd->net,0,0); // Send error from open_ltable
...@@ -1107,10 +1125,11 @@ static int create_table_from_dump(THD* thd, NET* net, const char* db, ...@@ -1107,10 +1125,11 @@ static int create_table_from_dump(THD* thd, NET* net, const char* db,
file = tables.table->file; file = tables.table->file;
thd->proc_info = "Reading master dump table data"; thd->proc_info = "Reading master dump table data";
/* Copy the data file */
if (file->net_read_dump(net)) if (file->net_read_dump(net))
{ {
net_printf(&thd->net, ER_MASTER_NET_READ); net_printf(&thd->net, ER_MASTER_NET_READ);
sql_print_error("create_table_from_dump::failed in\ sql_print_error("create_table_from_dump: failed in\
handler::net_read_dump()"); handler::net_read_dump()");
goto err; goto err;
} }
...@@ -1125,6 +1144,7 @@ static int create_table_from_dump(THD* thd, NET* net, const char* db, ...@@ -1125,6 +1144,7 @@ static int create_table_from_dump(THD* thd, NET* net, const char* db,
*/ */
save_vio = thd->net.vio; save_vio = thd->net.vio;
thd->net.vio = 0; thd->net.vio = 0;
/* Rebuild the index file from the copied data file (with REPAIR) */
error=file->repair(thd,&check_opt) != 0; error=file->repair(thd,&check_opt) != 0;
thd->net.vio = save_vio; thd->net.vio = save_vio;
if (error) if (error)
...@@ -1137,7 +1157,7 @@ err: ...@@ -1137,7 +1157,7 @@ err:
} }
int fetch_master_table(THD *thd, const char *db_name, const char *table_name, int fetch_master_table(THD *thd, const char *db_name, const char *table_name,
MASTER_INFO *mi, MYSQL *mysql) MASTER_INFO *mi, MYSQL *mysql, bool overwrite)
{ {
int error= 1; int error= 1;
const char *errmsg=0; const char *errmsg=0;
...@@ -1169,9 +1189,10 @@ int fetch_master_table(THD *thd, const char *db_name, const char *table_name, ...@@ -1169,9 +1189,10 @@ int fetch_master_table(THD *thd, const char *db_name, const char *table_name,
errmsg= "Failed on table dump request"; errmsg= "Failed on table dump request";
goto err; goto err;
} }
if (create_table_from_dump(thd, &mysql->net, db_name, if (create_table_from_dump(thd, &mysql->net, db_name,
table_name)) table_name, overwrite))
goto err; // create_table_from_dump will have sent the error already goto err; // create_table_from_dump will have send_error already
error = 0; error = 0;
err: err:
......
...@@ -384,9 +384,9 @@ int start_slave_thread(pthread_handler h_func, pthread_mutex_t* start_lock, ...@@ -384,9 +384,9 @@ int start_slave_thread(pthread_handler h_func, pthread_mutex_t* start_lock,
int mysql_table_dump(THD* thd, const char* db, int mysql_table_dump(THD* thd, const char* db,
const char* tbl_name, int fd = -1); const char* tbl_name, int fd = -1);
/* retrieve non-exitent table from master */ /* retrieve table from master and copy to slave*/
int fetch_master_table(THD* thd, const char* db_name, const char* table_name, int fetch_master_table(THD* thd, const char* db_name, const char* table_name,
MASTER_INFO* mi, MYSQL* mysql); MASTER_INFO* mi, MYSQL* mysql, bool overwrite);
int show_master_info(THD* thd, MASTER_INFO* mi); int show_master_info(THD* thd, MASTER_INFO* mi);
int show_binlog_info(THD* thd); int show_binlog_info(THD* thd);
......
...@@ -1568,9 +1568,12 @@ mysql_execute_command(void) ...@@ -1568,9 +1568,12 @@ mysql_execute_command(void)
goto error; goto error;
} }
LOCK_ACTIVE_MI; LOCK_ACTIVE_MI;
// fetch_master_table will send the error to the client on failure /*
fetch_master_table will send the error to the client on failure.
Give error if the table already exists.
*/
if (!fetch_master_table(thd, tables->db, tables->real_name, if (!fetch_master_table(thd, tables->db, tables->real_name,
active_mi, 0)) active_mi, 0, 0))
{ {
send_ok(&thd->net); send_ok(&thd->net);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment