#At file:///ext/mysql/bzr/backup/wl4326/
2637 Rafal Somla 2008-06-25
WL#4326 (Disable events and triggers during restore)
This patch implements design described in the WL. The events and triggers are not created
when the metadata section of backup image is read but stored in a restore catalogue.
They are re-created after all other objects and all table data is restored.
added:
mysql-test/r/backup_triggers_and_events.result
mysql-test/t/backup_triggers_and_events.test
modified:
sql/backup/backup_kernel.h
sql/backup/data_backup.cc
sql/backup/kernel.cc
per-file messages:
mysql-test/t/backup_triggers_and_events.test
New test checking behaviour of events and triggers during restore process.
sql/backup/backup_kernel.h
Added new helper method restore_triggers_and_events().
sql/backup/data_backup.cc
Added synchronization point in restore_table_data(), after all restore drivers have finished
their job and before they are shut down.
sql/backup/kernel.cc
- Implementation of Backup_restore_ctx::restore_triggers_and_events()
- In do_restore(), re-create triggers and events after table data has been restored.
- In bcat_create_item() (called when metadata is read) do not re-create events and triggers,
only store them in the catalogue.
=== added file 'mysql-test/r/backup_triggers_and_events.result'
--- a/mysql-test/r/backup_triggers_and_events.result 1970-01-01 00:00:00 +0000
+++ b/mysql-test/r/backup_triggers_and_events.result 2008-06-25 10:03:42 +0000
@@ -0,0 +1,202 @@
+SET GLOBAL event_scheduler=off;
+SET DEBUG_SYNC = 'RESET';
+Creating log table.
+DROP TABLE IF EXISTS test.logt;
+CREATE TABLE test.logt(ts timestamp, db char(8), msg text);
+Creating database db and its objects.
+DROP DATABASE IF EXISTS db;
+CREATE DATABASE db;
+USE db;
+CREATE TABLE t1 (a int);
+INSERT INTO t1 VALUES (0),(1),(2),(3),(4),(5),(6);
+CREATE EVENT ev ON SCHEDULE EVERY 1 second DO
+BEGIN
+INSERT INTO test.logt(db, msg) VALUES ('db','Db event fired!');
+END;
+||
+CREATE PROCEDURE trg_msg(a int)
+BEGIN
+INSERT INTO test.logt(db, msg) VALUES ('db','Db trigger fired!');
+END;
+||
+CREATE TRIGGER after_ins AFTER INSERT ON t1 FOR EACH ROW
+CALL trg_msg(NEW.a);
+||
+CREATE TRIGGER after_upd AFTER UPDATE ON t1 FOR EACH ROW
+CALL trg_msg(NEW.a);
+||
+CREATE TRIGGER after_del AFTER DELETE ON t1 FOR EACH ROW
+CALL trg_msg(OLD.a);
+||
+CREATE TRIGGER before_ins BEFORE INSERT ON t1 FOR EACH ROW
+CALL trg_msg(NEW.a);
+||
+CREATE TRIGGER before_upd BEFORE UPDATE ON t1 FOR EACH ROW
+CALL trg_msg(NEW.a);
+||
+CREATE TRIGGER before_del BEFORE DELETE ON t1 FOR EACH ROW
+CALL trg_msg(OLD.a);
+||
+USE test||
+DROP EVENT IF EXISTS ev||
+Warnings:
+Note 1305 Event ev does not exist
+DROP TABLE IF EXISTS t1||
+Warnings:
+Note 1051 Unknown table 't1'
+DROP TRIGGER IF EXISTS trg||
+Warnings:
+Note 1360 Trigger does not exist
+CREATE EVENT ev ON SCHEDULE EVERY 1 second DO
+BEGIN
+INSERT INTO test.logt(db, msg) VALUES ('test','Test event fired!');
+END;
+||
+CREATE TABLE t1 (a int)||
+CREATE TRIGGER trg AFTER INSERT ON t1 FOR EACH ROW
+BEGIN
+INSERT INTO test.logt(db, msg) VALUES ('test','Test trigger fired');
+END;
+||
+Backing-up database db and dropping it.
+BACKUP DATABASE db TO 'db.bak';
+backup_id
+#
+DROP DATABASE db;
+Enabling event scheduler.
+SET GLOBAL event_scheduler=on;
+con1: clearing log table and starting RESTORE operation.
+con1: RESTORE will pause after restoring table data.
+SET DEBUG_SYNC = 'restore_table_data_before_end SIGNAL waiting WAIT_FOR continue';
+DELETE FROM test.logt;
+RESTORE FROM 'db.bak';
+SELECT now() INTO @start;
+con2: checking that there are no triggers and events at the end of RESTORE execution.
+SET DEBUG_SYNC = 'now WAIT_FOR waiting';
+SHOW TRIGGERS FROM db;
+SHOW EVENTS IN db;
+con2: activating trigger in test database.
+INSERT INTO test.t1 VALUES (1);
+con2: ensuring that RESTORE takes at least 3 secs.
+SET DEBUG_SYNC = 'now SIGNAL continue';
+con1: finishing RESTORE operation.
+backup_id
+#
+SET GLOBAL event_scheduler=off;
+con2: checking that RESTORE took more than 2 secs.
+SELECT timediff(now(),@start) > 2;
+timediff(now(),@start) > 2
+1
+Checking that objects have been restored.
+USE db;
+SHOW TABLES IN db;
+Tables_in_db
+t1
+SELECT count(*) FROM db.t1;
+count(*)
+7
+SHOW TRIGGERS FROM db;
+Trigger before_ins
+Event INSERT
+Table t1
+Statement CALL trg_msg(NEW.a)
+Timing BEFORE
+Created NULL
+sql_mode
+Definer root@localhost
+character_set_client #
+collation_connection latin1_swedish_ci
+Database Collation latin1_swedish_ci
+Trigger after_ins
+Event INSERT
+Table t1
+Statement CALL trg_msg(NEW.a)
+Timing AFTER
+Created NULL
+sql_mode
+Definer root@localhost
+character_set_client #
+collation_connection latin1_swedish_ci
+Database Collation latin1_swedish_ci
+Trigger before_upd
+Event UPDATE
+Table t1
+Statement CALL trg_msg(NEW.a)
+Timing BEFORE
+Created NULL
+sql_mode
+Definer root@localhost
+character_set_client #
+collation_connection latin1_swedish_ci
+Database Collation latin1_swedish_ci
+Trigger after_upd
+Event UPDATE
+Table t1
+Statement CALL trg_msg(NEW.a)
+Timing AFTER
+Created NULL
+sql_mode
+Definer root@localhost
+character_set_client #
+collation_connection latin1_swedish_ci
+Database Collation latin1_swedish_ci
+Trigger before_del
+Event DELETE
+Table t1
+Statement CALL trg_msg(OLD.a)
+Timing BEFORE
+Created NULL
+sql_mode
+Definer root@localhost
+character_set_client #
+collation_connection latin1_swedish_ci
+Database Collation latin1_swedish_ci
+Trigger after_del
+Event DELETE
+Table t1
+Statement CALL trg_msg(OLD.a)
+Timing AFTER
+Created NULL
+sql_mode
+Definer root@localhost
+character_set_client #
+collation_connection latin1_swedish_ci
+Database Collation latin1_swedish_ci
+SHOW EVENTS IN db;
+Db db
+Name ev
+Definer root@localhost
+Time zone SYSTEM
+Type RECURRING
+Execute at NULL
+Interval value 1
+Interval field SECOND
+Starts #
+Ends NULL
+Status ENABLED
+Originator 1
+character_set_client latin1
+collation_connection latin1_swedish_ci
+Database Collation latin1_swedish_ci
+Checking that no db event or trigger fired during RESTORE.
+SELECT * FROM test.logt WHERE db = 'db' AND timediff(ts,@start) < 2;
+ts db msg
+Checking that test event and trigger could fire.
+SELECT count(*) > 0 FROM test.logt
+WHERE db = 'test'
+AND msg LIKE '%trigger fired%'
+AND timediff(ts,@start) < 2;
+count(*) > 0
+1
+SELECT count(*) > 0 FROM test.logt
+WHERE db = 'test'
+AND msg LIKE '%event fired%'
+AND timediff(ts,@start) < 2;
+count(*) > 0
+1
+Cleaning up.
+DROP EVENT test.ev;
+DROP TRIGGER test.trg;
+DROP TABLE test.logt;
+DROP TABLE test.t1;
+DROP DATABASE db;
=== added file 'mysql-test/t/backup_triggers_and_events.test'
--- a/mysql-test/t/backup_triggers_and_events.test 1970-01-01 00:00:00 +0000
+++ b/mysql-test/t/backup_triggers_and_events.test 2008-06-25 10:03:42 +0000
@@ -0,0 +1,214 @@
+--source include/have_debug_sync.inc
+--source include/not_embedded.inc
+
+# This test checks that re-created events or triggers are not fired during
+# RESTORE operation.
+#
+# Author: Rafal Somla
+
+--disable_warnings
+--error 0,1
+--remove_file $MYSQL_TEST_DIR/var/master-data/db.bak
+--enable_warnings
+
+SET GLOBAL event_scheduler=off;
+SET DEBUG_SYNC = 'RESET';
+
+# We need a separate connection to measure timing for RESTORE command. This is
+# because of BUG#35806: time stops in a thread executing RESTORE command.
+
+connect(con1, localhost, root,,);
+connect(con2, localhost, root,,);
+
+--connection con1
+
+# Events and triggers will insert entries into a log table so that we can see
+# if they have fired.
+
+--echo Creating log table.
+
+--disable_warnings
+DROP TABLE IF EXISTS test.logt;
+--enable_warnings
+
+CREATE TABLE test.logt(ts timestamp, db char(8), msg text);
+
+--echo Creating database db and its objects.
+
+--disable_warnings
+DROP DATABASE IF EXISTS db;
+--enable_warnings
+
+CREATE DATABASE db;
+USE db;
+
+CREATE TABLE t1 (a int);
+INSERT INTO t1 VALUES (0),(1),(2),(3),(4),(5),(6);
+
+delimiter ||;
+
+CREATE EVENT ev ON SCHEDULE EVERY 1 second DO
+BEGIN
+ INSERT INTO test.logt(db, msg) VALUES ('db','Db event fired!');
+END;
+||
+
+CREATE PROCEDURE trg_msg(a int)
+BEGIN
+ INSERT INTO test.logt(db, msg) VALUES ('db','Db trigger fired!');
+END;
+||
+
+CREATE TRIGGER after_ins AFTER INSERT ON t1 FOR EACH ROW
+CALL trg_msg(NEW.a);
+||
+
+CREATE TRIGGER after_upd AFTER UPDATE ON t1 FOR EACH ROW
+CALL trg_msg(NEW.a);
+||
+
+CREATE TRIGGER after_del AFTER DELETE ON t1 FOR EACH ROW
+CALL trg_msg(OLD.a);
+||
+
+CREATE TRIGGER before_ins BEFORE INSERT ON t1 FOR EACH ROW
+CALL trg_msg(NEW.a);
+||
+
+CREATE TRIGGER before_upd BEFORE UPDATE ON t1 FOR EACH ROW
+CALL trg_msg(NEW.a);
+||
+
+CREATE TRIGGER before_del BEFORE DELETE ON t1 FOR EACH ROW
+CALL trg_msg(OLD.a);
+||
+
+# Create an event and trigger in test database to see that they are not
+# affected by RESTORE of another database.
+
+USE test||
+
+--disable_warnigns
+DROP EVENT IF EXISTS ev||
+DROP TABLE IF EXISTS t1||
+DROP TRIGGER IF EXISTS trg||
+--enable_warnings
+
+CREATE EVENT ev ON SCHEDULE EVERY 1 second DO
+BEGIN
+ INSERT INTO test.logt(db, msg) VALUES ('test','Test event fired!');
+END;
+||
+
+CREATE TABLE t1 (a int)||
+
+CREATE TRIGGER trg AFTER INSERT ON t1 FOR EACH ROW
+BEGIN
+ INSERT INTO test.logt(db, msg) VALUES ('test','Test trigger fired');
+END;
+||
+
+delimiter ;||
+
+--echo Backing-up database db and dropping it.
+
+--replace_column 1 #
+BACKUP DATABASE db TO 'db.bak';
+DROP DATABASE db;
+
+--echo Enabling event scheduler.
+SET GLOBAL event_scheduler=on;
+
+--connection con1
+
+--echo con1: clearing log table and starting RESTORE operation.
+--echo con1: RESTORE will pause after restoring table data.
+
+# Synchronization point 'restore_table_data_before_end' is inside RESTORE code,
+# after restore drivers have finished their job but before they have been shoot
+# down.
+
+SET DEBUG_SYNC = 'restore_table_data_before_end SIGNAL waiting WAIT_FOR continue';
+DELETE FROM test.logt;
+--send RESTORE FROM 'db.bak'
+
+--connection con2
+
+# Record the time when RESTORE has started.
+SELECT now() INTO @start;
+
+--echo con2: checking that there are no triggers and events at the end of RESTORE execution.
+
+# Wait until RESTORE reaches the moment when all table data is restored.
+SET DEBUG_SYNC = 'now WAIT_FOR waiting';
+# There should be no triggers and no events at this moment (they are created
+# after table data is restored)
+--query_vertical SHOW TRIGGERS FROM db
+--query_vertical SHOW EVENTS IN db
+
+--echo con2: activating trigger in test database.
+INSERT INTO test.t1 VALUES (1);
+
+--echo con2: ensuring that RESTORE takes at least 3 secs.
+
+# This is so that db.ev event has chance to fire if it is not correctly handled
+# (e.g. enabled during table data restore).
+--sleep 3
+SET DEBUG_SYNC = 'now SIGNAL continue';
+
+--connection con1
+
+--echo con1: finishing RESTORE operation.
+--replace_column 1 #
+--reap
+SET GLOBAL event_scheduler=off;
+
+--connection con2
+
+-- echo con2: checking that RESTORE took more than 2 secs.
+
+SELECT timediff(now(),@start) > 2;
+
+--echo Checking that objects have been restored.
+
+USE db;
+
+SHOW TABLES IN db;
+SELECT count(*) FROM db.t1;
+--replace_column 9 #
+--query_vertical SHOW TRIGGERS FROM db
+--replace_column 9 #
+--query_vertical SHOW EVENTS IN db
+
+--echo Checking that no db event or trigger fired during RESTORE.
+
+# There should be no entries in the log table from the time when RESTORE
+# was running (but there could be entries inserted by event firing *after*
+# RESTORE has completed). We know that RESTORE took at least 3 sec and we
+# take 2 sec window form the beginning of the operation. This is enough
+# to see db.ev in case it fired during RESTORE operation (this event is sheduled
+# to fire every second).
+
+SELECT * FROM test.logt WHERE db = 'db' AND timediff(ts,@start) < 2;
+
+--echo Checking that test event and trigger could fire.
+
+# Checking that the trigger has fired.
+SELECT count(*) > 0 FROM test.logt
+WHERE db = 'test'
+AND msg LIKE '%trigger fired%'
+AND timediff(ts,@start) < 2;
+
+# Checking that the event has fired.
+SELECT count(*) > 0 FROM test.logt
+WHERE db = 'test'
+AND msg LIKE '%event fired%'
+AND timediff(ts,@start) < 2;
+
+--echo Cleaning up.
+DROP EVENT test.ev;
+DROP TRIGGER test.trg;
+DROP TABLE test.logt;
+DROP TABLE test.t1;
+DROP DATABASE db;
+--remove_file $MYSQL_TEST_DIR/var/master-data/db.bak
=== modified file 'sql/backup/backup_kernel.h'
--- a/sql/backup/backup_kernel.h 2008-05-05 15:06:40 +0000
+++ b/sql/backup/backup_kernel.h 2008-06-25 10:03:42 +0000
@@ -113,6 +113,7 @@ class Backup_restore_ctx: public backup:
int prepare(LEX_STRING location);
void disable_fkey_constraints();
+ int restore_triggers_and_events();
friend class Backup_info;
friend class Restore_info;
=== modified file 'sql/backup/data_backup.cc'
--- a/sql/backup/data_backup.cc 2008-06-05 12:26:31 +0000
+++ b/sql/backup/data_backup.cc 2008-06-25 10:03:42 +0000
@@ -1569,6 +1569,8 @@ int restore_table_data(THD*, Restore_inf
DBUG_PRINT("restore",("state is %d", state));
}
+ DEBUG_SYNC(::current_thd, "restore_table_data_before_end");
+
{ // Shutting down drivers
String bad_drivers;
=== modified file 'sql/backup/kernel.cc'
--- a/sql/backup/kernel.cc 2008-05-21 10:45:55 +0000
+++ b/sql/backup/kernel.cc 2008-06-25 10:03:42 +0000
@@ -769,6 +769,81 @@ int Backup_restore_ctx::do_backup()
DBUG_RETURN(0);
}
+/**
+ Create all triggers and events from restore catalogue.
+
+ This helper method iterates over all triggers and events stored in the
+ restore catalogue and creates them. When metadata section of the backup image
+ is read, trigger and event objects are materialized and stored in the
+ catalogue but they are not executed then (see @c bcat_create_item()).
+ This method can be used to re-create the corresponding server objects after
+ all other objects and table data have been restored.
+
+ Note that we first restore all triggers and then the events.
+
+ @returns 0 on success, error code otherwise.
+*/
+int Backup_restore_ctx::restore_triggers_and_events()
+{
+ using namespace backup;
+
+ DBUG_ASSERT(m_catalog);
+
+ Image_info::Iterator *dbit= m_catalog->get_dbs();
+ Image_info::Obj *obj;
+ List<Image_info::Obj> events;
+ Image_info::Obj::describe_buf buf;
+
+ DBUG_ENTER("restore_triggers_and_events");
+
+ // create all trigers and collect events in the events list
+
+ while ((obj= (*dbit)++))
+ {
+ Image_info::Iterator *it=
+ m_catalog->get_db_objects(*static_cast<Image_info::Db*>(obj));
+
+ while ((obj= (*it)++))
+ switch (obj->type()) {
+
+ case BSTREAM_IT_EVENT:
+ DBUG_ASSERT(obj->m_obj_ptr);
+ events.push_back(obj);
+ break;
+
+ case BSTREAM_IT_TRIGGER:
+ DBUG_ASSERT(obj->m_obj_ptr);
+ if (obj->m_obj_ptr->execute(m_thd))
+ {
+ delete it;
+ delete dbit;
+ fatal_error(ER_BACKUP_CANT_RESTORE_TRIGGER,obj->describe(buf));
+ DBUG_RETURN(m_error);
+ }
+ break;
+
+ default: break;
+ }
+
+ delete it;
+ }
+
+ delete dbit;
+
+ // now create all events
+
+ List_iterator<Image_info::Obj> it(events);
+ Image_info::Obj *ev;
+
+ while ((ev= it++))
+ if (ev->m_obj_ptr->execute(m_thd))
+ {
+ fatal_error(ER_BACKUP_CANT_RESTORE_EVENT,ev->describe(buf));
+ DBUG_RETURN(m_error);
+ };
+
+ DBUG_RETURN(0);
+}
/**
Restore objects saved in backup image.
@@ -830,6 +905,21 @@ int Backup_restore_ctx::do_restore()
DBUG_RETURN(m_error);
}
+ /*
+ Re-create all triggers and events (it was not done in @c bcat_create_item()).
+ */
+
+ if (restore_triggers_and_events())
+ DBUG_RETURN(ER_BACKUP_RESTORE);
+
+ /*
+ FIXME: this call is here because object services doesn't clean the
+ statement execution context properly, which leads to assertion failure.
+ It should be fixed inside object services implementation and then the
+ following line should be removed.
+ */
+ m_thd->main_da.reset_diagnostics_area();
+
report_stats_post(info);
DBUG_RETURN(0);
@@ -1436,6 +1526,22 @@ int bcat_create_item(st_bstream_image_he
return BSTREAM_ERROR;
}
+ /*
+ If the item we are creating is an event or trigger, we don't execute it
+ yet. It will be done in @c Backup_restore_ctx::do_restore() after table
+ data has been restored.
+ */
+
+ switch (item->type) {
+
+ case BSTREAM_IT_EVENT:
+ case BSTREAM_IT_TRIGGER:
+ return BSTREAM_OK;
+
+ default: break;
+
+ }
+
// If we are to create a tablespace, first check if it already exists.
if (item->type == BSTREAM_IT_TABLESPACE)