mirror of
https://github.com/MariaDB/server.git
synced 2025-01-29 02:05:57 +01:00
* 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).
This commit is contained in:
parent
8272be9412
commit
69b8b3ff7c
6 changed files with 150 additions and 24 deletions
|
@ -49,21 +49,48 @@ show databases;
|
|||
Database
|
||||
mysql
|
||||
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;
|
||||
show databases;
|
||||
Database
|
||||
bar
|
||||
foo
|
||||
foo2
|
||||
mysql
|
||||
test
|
||||
use foo;
|
||||
show tables;
|
||||
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;
|
||||
show tables;
|
||||
Tables_in_bar
|
||||
t1
|
||||
t2
|
||||
t3
|
||||
select * from bar.t1;
|
||||
n s
|
||||
1 one bar
|
||||
|
@ -74,6 +101,9 @@ n s
|
|||
11 eleven bar
|
||||
12 twelve bar
|
||||
13 thirteen bar
|
||||
select * from bar.t3;
|
||||
n s
|
||||
1 original bar.t3
|
||||
insert into bar.t1 values (4, 'four bar');
|
||||
select * from bar.t1;
|
||||
n s
|
||||
|
@ -81,5 +111,23 @@ n s
|
|||
2 two bar
|
||||
3 three 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 foo;
|
||||
drop database foo;
|
||||
drop database foo2;
|
||||
|
|
|
@ -60,16 +60,45 @@ sync_with_master;
|
|||
|
||||
# This should show that the slave is empty at this point
|
||||
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;
|
||||
|
||||
# Now let's check if we have the right tables and the right data in them
|
||||
show databases;
|
||||
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;
|
||||
show tables;
|
||||
show tables; # should contain master's copied t1&t2, slave's original t3
|
||||
select * from bar.t1;
|
||||
select * from bar.t2;
|
||||
select * from bar.t3;
|
||||
|
||||
# Now let's see if replication works
|
||||
connection master;
|
||||
|
@ -79,6 +108,26 @@ connection slave;
|
|||
sync_with_master;
|
||||
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
|
||||
connection master;
|
||||
drop database bar;
|
||||
|
@ -86,4 +135,5 @@ drop database foo;
|
|||
save_master_pos;
|
||||
connection slave;
|
||||
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,
|
|||
if (!tables_ok(thd, &table))
|
||||
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 0;
|
||||
|
@ -819,8 +820,11 @@ int load_master_data(THD* thd)
|
|||
char* db = row[0];
|
||||
|
||||
/*
|
||||
Do not replicate databases excluded by rules
|
||||
also skip mysql database - in most cases the user will
|
||||
Do not replicate databases excluded by rules. We also test
|
||||
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
|
||||
he actually means to - in this case, he is up for a surprise if
|
||||
his priv tables get dropped and downloaded from master
|
||||
|
@ -830,14 +834,14 @@ int load_master_data(THD* thd)
|
|||
*/
|
||||
|
||||
if (!db_ok(db, replicate_do_db, replicate_ignore_db) ||
|
||||
!db_ok_with_wild_table(db) ||
|
||||
!strcmp(db,"mysql"))
|
||||
{
|
||||
*cur_table_res = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mysql_rm_db(thd, db, 1,1) ||
|
||||
mysql_create_db(thd, db, 0, 1))
|
||||
if (mysql_create_db(thd, db, HA_LEX_CREATE_IF_NOT_EXISTS, 1))
|
||||
{
|
||||
send_error(&thd->net, 0, 0);
|
||||
cleanup_mysql_results(db_res, cur_table_res - 1, table_res);
|
||||
|
|
45
sql/slave.cc
45
sql/slave.cc
|
@ -72,7 +72,7 @@ static int safe_sleep(THD* thd, int sec, CHECK_KILLED_FUNC thread_killed,
|
|||
void* thread_killed_arg);
|
||||
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,
|
||||
const char* table_name);
|
||||
const char* table_name, bool overwrite);
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
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,
|
||||
const char* table_name)
|
||||
const char* table_name, bool overwrite)
|
||||
{
|
||||
ulong packet_len = my_net_read(net); // read create table statement
|
||||
char *query;
|
||||
char* save_db;
|
||||
Vio* save_vio;
|
||||
HA_CHECK_OPT check_opt;
|
||||
TABLE_LIST tables;
|
||||
|
@ -1078,13 +1088,24 @@ static int create_table_from_dump(THD* thd, NET* net, const char* db,
|
|||
thd->current_tablenr = 0;
|
||||
thd->query_error = 0;
|
||||
thd->net.no_send_ok = 1;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* we do not want to log create table statement */
|
||||
/* Create the table. We do not want to log the "create table" statement */
|
||||
save_options = thd->options;
|
||||
thd->options &= ~(ulong) (OPTION_BIN_LOG);
|
||||
thd->proc_info = "Creating table from master dump";
|
||||
// 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;
|
||||
mysql_parse(thd, thd->query, packet_len); // run create table
|
||||
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,
|
|||
if (thd->query_error)
|
||||
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";
|
||||
tables.lock_type = TL_WRITE;
|
||||
if (!open_ltable(thd, &tables, TL_WRITE))
|
||||
{
|
||||
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,
|
|||
|
||||
file = tables.table->file;
|
||||
thd->proc_info = "Reading master dump table data";
|
||||
/* Copy the data file */
|
||||
if (file->net_read_dump(net))
|
||||
{
|
||||
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()");
|
||||
goto err;
|
||||
}
|
||||
|
@ -1125,6 +1144,7 @@ static int create_table_from_dump(THD* thd, NET* net, const char* db,
|
|||
*/
|
||||
save_vio = thd->net.vio;
|
||||
thd->net.vio = 0;
|
||||
/* Rebuild the index file from the copied data file (with REPAIR) */
|
||||
error=file->repair(thd,&check_opt) != 0;
|
||||
thd->net.vio = save_vio;
|
||||
if (error)
|
||||
|
@ -1137,7 +1157,7 @@ err:
|
|||
}
|
||||
|
||||
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;
|
||||
const char *errmsg=0;
|
||||
|
@ -1169,9 +1189,10 @@ int fetch_master_table(THD *thd, const char *db_name, const char *table_name,
|
|||
errmsg= "Failed on table dump request";
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (create_table_from_dump(thd, &mysql->net, db_name,
|
||||
table_name))
|
||||
goto err; // create_table_from_dump will have sent the error already
|
||||
table_name, overwrite))
|
||||
goto err; // create_table_from_dump will have send_error already
|
||||
error = 0;
|
||||
|
||||
err:
|
||||
|
|
|
@ -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,
|
||||
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,
|
||||
MASTER_INFO* mi, MYSQL* mysql);
|
||||
MASTER_INFO* mi, MYSQL* mysql, bool overwrite);
|
||||
|
||||
int show_master_info(THD* thd, MASTER_INFO* mi);
|
||||
int show_binlog_info(THD* thd);
|
||||
|
|
|
@ -1568,9 +1568,12 @@ mysql_execute_command(void)
|
|||
goto error;
|
||||
}
|
||||
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,
|
||||
active_mi, 0))
|
||||
active_mi, 0, 0))
|
||||
{
|
||||
send_ok(&thd->net);
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue