Skip to content

Commit e92f735

Browse files
committed
MDEV-30645: CREATE TRIGGER FOR { STARTUP | SHUTDOWN }
Core implementation - Extended the sql grammar to support creation of system triggers on startup/shutdown - System triggers metadata is loaded from the table mysql.event - Events and system triggers share the same name space since both are stored in the table mysql.event declared with the PRIMARY KEY (`db`,`name`). In result, attempt to create an event with the same name as existent trigger and vice versa results in failure with the new error ER_TRG_EVENT_CONFLICTS_NAME - System triggers can be created only by users with the SUPER privilege - Added the option --sys_triggers for mysqldump to dump system triggers. The option is turned off by default.
1 parent 33a0e61 commit e92f735

28 files changed

Lines changed: 2733 additions & 124 deletions

client/mysqldump.cc

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
*/
4848
#define FIRST_SEQUENCE_VERSION 100300
4949

50+
#define FIRST_SYS_TRIGGER_VERSION 130100
51+
5052
#include <my_global.h>
5153
#include <my_sys.h>
5254
#include <my_user.h>
@@ -133,7 +135,8 @@ static my_bool verbose= 0, opt_no_create_info= 0, opt_no_data= 0, opt_no_data_m
133135
opt_events= 0, opt_comments_used= 0,
134136
opt_alltspcs=0, opt_notspcs= 0, opt_logging,
135137
opt_header=0, opt_update_history= 0,
136-
opt_drop_trigger= 0, opt_dump_history= 0, opt_wildcards= 0;
138+
opt_drop_trigger= 0, opt_dump_history= 0, opt_wildcards= 0,
139+
opt_dump_sys_triggers= 0;
137140
static my_bool opt_galera_info= 0;
138141

139142
#define OPT_SYSTEM_ALL 1
@@ -605,6 +608,9 @@ static struct my_option my_long_options[] =
605608
{"triggers", 0, "Dump triggers for each dumped table.",
606609
&opt_dump_triggers, &opt_dump_triggers, 0, GET_BOOL,
607610
NO_ARG, 1, 0, 0, 0, 0, 0},
611+
{"sys_triggers", 0, "Dump system triggers.",
612+
&opt_dump_sys_triggers, &opt_dump_sys_triggers, 0, GET_BOOL,
613+
NO_ARG, 0, 0, 0, 0, 0, 0},
608614
{"tz-utc", 0,
609615
"Set connection time zone to UTC before commencing the dump and add "
610616
"SET TIME_ZONE=´+00:00´ to the top of the dump file.",
@@ -5601,12 +5607,26 @@ static int dump_all_databases()
56015607
MYSQL_ROW row;
56025608
MYSQL_RES *tableres;
56035609
int result=0;
5610+
char mysql_db[]= "mysql";
56045611

56055612
if (mysql_query_with_error_report(mysql, &tableres, "SHOW DATABASES"))
56065613
return 1;
5614+
5615+
/*
5616+
First dump mysql database since it contains the table mysql.event where
5617+
triggers' metadata stored
5618+
*/
5619+
if (dump_all_tables_in_db(mysql_db))
5620+
return 1;
5621+
56075622
while ((row= mysql_fetch_row(tableres)))
56085623
{
5609-
if (is_IS_or_PS(row[0]) || is_SyS(row[0]))
5624+
if (is_IS_or_PS(row[0]) || is_SyS(row[0]) ||
5625+
/*
5626+
Skip the `mysql` database since it is dumped before entering into
5627+
the while loop
5628+
*/
5629+
!cmp_database(row[0], mysql_db))
56105630
continue;
56115631

56125632
if (include_database(row[0]))
@@ -7594,6 +7614,44 @@ void dump_databases_wild(int n_patterns, char **db_patterns)
75947614
DBUG_VOID_RETURN;
75957615
}
75967616

7617+
static void dump_sys_triggers()
7618+
{
7619+
char query_buff[QUERY_LENGTH];
7620+
MYSQL_RES *triggers_list_rs= nullptr;
7621+
7622+
my_snprintf(query_buff, sizeof(query_buff),
7623+
"SELECT name FROM mysql.event WHERE kind IN "
7624+
"('STARTUP', 'SHUTDOWN')");
7625+
if (mysql_query_with_error_report(mysql, &triggers_list_rs, query_buff))
7626+
{
7627+
mysql_free_result(triggers_list_rs);
7628+
return;
7629+
}
7630+
7631+
if (mysql_num_rows(triggers_list_rs))
7632+
{
7633+
MYSQL_ROW row;
7634+
while ((row= mysql_fetch_row(triggers_list_rs)))
7635+
{
7636+
MYSQL_RES *show_create_trigger_rs;
7637+
char name_buff[NAME_LEN*4+3];
7638+
7639+
my_snprintf(query_buff, sizeof (query_buff), "SHOW CREATE TRIGGER %s",
7640+
quote_name(row[0], name_buff, true));
7641+
7642+
if (mysql_query_with_error_report(mysql, &show_create_trigger_rs,
7643+
query_buff))
7644+
{
7645+
mysql_free_result(triggers_list_rs);
7646+
maybe_exit(EX_MYSQLERR);
7647+
}
7648+
dump_trigger(md_result_file, show_create_trigger_rs, "", "");
7649+
mysql_free_result(show_create_trigger_rs);
7650+
}
7651+
}
7652+
mysql_free_result(triggers_list_rs);
7653+
}
7654+
75977655

75987656
int main(int argc, char **argv)
75997657
{
@@ -7794,6 +7852,13 @@ int main(int argc, char **argv)
77947852
}
77957853
}
77967854

7855+
if (opt_dump_sys_triggers &&
7856+
mysql_get_server_version(mysql) >= FIRST_SYS_TRIGGER_VERSION)
7857+
{
7858+
DBUG_PRINT("info", ("Dumping system trigger"));
7859+
dump_sys_triggers();
7860+
}
7861+
77977862
if (opt_system & OPT_SYSTEM_PLUGINS)
77987863
dump_all_plugins();
77997864

mysql-test/main/trigger.result

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2131,7 +2131,7 @@ insert into t2 values (1),(2);
21312131
delete from t1 where i=1;
21322132
show status like 'Opened_tables';
21332133
Variable_name Value
2134-
Opened_tables 6
2134+
Opened_tables 7
21352135
select * from t1;
21362136
i
21372137
2
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
#
2+
# MDEV-30645: CREATE TRIGGER FOR { STARTUP | SHUTDOWN }
3+
#
4+
# Test 1: Check that AFTER STARTUP trigger and BEFORE SHUTDOWN trigger
5+
# are fired at right time
6+
CREATE TABLE t1 (a VARCHAR(100));
7+
CREATE TRIGGER IF NOT EXISTS trg1 AFTER STARTUP INSERT INTO t1 VALUES('Startup');
8+
# BEFORE SHUTDOWN should be in action just after it has been created.
9+
# So, after restarting the server the record about server shutdown should
10+
# be inserted into the table t1.
11+
CREATE TRIGGER IF NOT EXISTS trg2 BEFORE SHUTDOWN INSERT INTO t1 VALUES('Shutdown');
12+
# restart
13+
SELECT * FROM t1;
14+
a
15+
Shutdown
16+
Startup
17+
# Clean up
18+
DROP TABLE t1;
19+
DROP TRIGGER trg1;
20+
DROP TRIGGER trg2;
21+
#
22+
# Test 2: Check that ON STARTUP/ON SHUTDOWN can't be created
23+
# w/o the SUPER privilege
24+
#
25+
# Check that ON STARTUP/ON SHUTDOWN can be created w/o the SUPER privilege
26+
CREATE USER u1;
27+
GRANT SELECT ON test.* TO u1;
28+
connect con1, localhost, u1, , test;
29+
# The following CREATE TRIGGER AFTER STARTUP statement should fail with
30+
# the error ER_SPECIFIC_ACCESS_DENIED_ERROR since the user `u1` doesn't
31+
# have the SUPER privilege
32+
CREATE TRIGGER IF NOT EXISTS trg1 AFTER STARTUP SET @a=1;
33+
ERROR 42000: Access denied; you need (at least one of) the SUPER privilege(s) for this operation
34+
# The following CREATE TRIGGER BEFORE SHUTDOWN statement should fail with
35+
# the error ER_SPECIFIC_ACCESS_DENIED_ERROR since the user `u1` doesn't
36+
# have the SUPER privilege
37+
CREATE TRIGGER IF NOT EXISTS trg1 BEFORE SHUTDOWN SET @b=1;
38+
ERROR 42000: Access denied; you need (at least one of) the SUPER privilege(s) for this operation
39+
connection default;
40+
disconnect con1;
41+
DROP USER u1;
42+
#
43+
# Test 3: Check that a user with the explicitly granted the SUPER privilege
44+
# can create a system trigger on STARTUP/on SHUTDOWN
45+
#
46+
CREATE USER u1;
47+
GRANT SELECT ON test.* TO u1;
48+
GRANT SUPER ON *.* TO u1;
49+
connect con1, localhost, u1, , test;
50+
# The following CREATE TRIGGER AFTER STARTUP statement and
51+
# CREATE TRIGGER BEFORE SHUTDOWN statement should succeed
52+
# since the SUPER privilege is granted to the user `u1`
53+
CREATE TRIGGER IF NOT EXISTS trg1 AFTER STARTUP INSERT INTO t1 VALUES('Startup');
54+
CREATE TRIGGER IF NOT EXISTS trg2 BEFORE SHUTDOWN INSERT INTO t1 VALUES('Shutdown');
55+
connection default;
56+
disconnect con1;
57+
# Clean up
58+
DROP TRIGGER trg1;
59+
DROP TRIGGER trg2;
60+
DROP USER u1;
61+
#
62+
# Test 4: Check that it is not possible to create a System trigger
63+
# and a DML trigger with the same name
64+
#
65+
CREATE TABLE t1 (a INT);
66+
# First check that DML trigger can 't be created with the same name
67+
# as the existing system trigger
68+
CREATE TRIGGER trg1 BEFORE INSERT ON t1 FOR EACH ROW SET @a=1;
69+
CREATE TRIGGER trg1 AFTER STARTUP SET @b=1;
70+
ERROR HY000: Trigger 'test.trg1' already exists
71+
DROP TRIGGER trg1;
72+
# And then check in reverse order: a system trigger can't be created
73+
# on presence of DML trigger with the same name
74+
CREATE TRIGGER trg1 AFTER STARTUP SET @b=1;
75+
CREATE TRIGGER trg1 BEFORE INSERT ON t1 FOR EACH ROW SET @a=1;
76+
ERROR HY000: Trigger 'test.trg1' already exists
77+
# Clean up
78+
DROP TRIGGER trg1;
79+
DROP TABLE t1;
80+
#
81+
# Test 5: Check that multiple triggers can be created on the same
82+
# system event
83+
#
84+
CREATE TABLE t1 (a VARCHAR(100));
85+
CREATE TRIGGER IF NOT EXISTS trg1_ast AFTER STARTUP INSERT INTO t1 VALUES('Startup: first action');
86+
CREATE TRIGGER IF NOT EXISTS trg2_ast AFTER STARTUP INSERT INTO t1 VALUES('Startup: second action');
87+
# BEFORE SHUTDOWN should be in action just after it has been created.
88+
# So, after restarting the server the record about server shutdown should
89+
# be inserted into the table t1.
90+
CREATE TRIGGER IF NOT EXISTS trg3_bshd BEFORE SHUTDOWN INSERT INTO t1 VALUES('Shutdown: first action');
91+
CREATE TRIGGER IF NOT EXISTS trg4_bshd BEFORE SHUTDOWN INSERT INTO t1 VALUES('Shutdown: second action');
92+
# restart
93+
SELECT * FROM t1;
94+
a
95+
Shutdown: first action
96+
Shutdown: second action
97+
Startup: first action
98+
Startup: second action
99+
# Clean up
100+
DROP TABLE t1;
101+
DROP TRIGGER trg1_ast;
102+
DROP TRIGGER trg2_ast;
103+
DROP TRIGGER trg3_bshd;
104+
DROP TRIGGER trg4_bshd;
105+
#
106+
# Test 6: SHOW TRIGGERS on a system trigger
107+
#
108+
CREATE TRIGGER trg1 AFTER STARTUP SET @b=1;
109+
SHOW TRIGGERS;
110+
Trigger Event Table Statement Timing Created sql_mode Definer character_set_client collation_connection Database Collation
111+
trg1 STARTUP SET @b=1 AFTER # STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION root@localhost latin1 latin1_swedish_ci utf8mb4_uca1400_ai_ci
112+
# Clean up
113+
DROP TRIGGER trg1;
114+
#
115+
# Test 7: SHOW CREATE TRIGGER on a system trigger
116+
#
117+
CREATE TRIGGER trg1 AFTER STARTUP SET @b=1;
118+
SHOW CREATE TRIGGER trg1;
119+
Trigger sql_mode SQL Original Statement character_set_client collation_connection Database Collation Created
120+
trg1 STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION CREATE DEFINER=`root`@`localhost` TRIGGER trg1 AFTER STARTUP SET @b=1 latin1 latin1_swedish_ci utf8mb4_uca1400_ai_ci #
121+
# Clean up
122+
DROP TRIGGER trg1;
123+
#
124+
# Test 8: Check that triggers and events have the same namespace
125+
#
126+
# Test 8.1 Check that attempt to create a trigger with the same name
127+
# as existent event result in error
128+
CREATE EVENT ev1 ON SCHEDULE EVERY 1 YEAR DO SET @aaa=1;
129+
Warnings:
130+
Warning 1105 Event scheduler is switched off, use SET GLOBAL event_scheduler=ON to enable it.
131+
CREATE TRIGGER ev1 AFTER STARTUP SET @bbb=1;
132+
ERROR HY000: Event with the same name 'ev1' already exists
133+
# Clean up
134+
DROP EVENT ev1;
135+
# Test 8.2 Check that attempt to create an event with the same name as
136+
# as existen trigger result in error
137+
CREATE TRIGGER trg1 AFTER STARTUP SET @bbb=1;
138+
CREATE EVENT trg1 ON SCHEDULE EVERY 1 YEAR DO SET @aaa=1;
139+
ERROR HY000: Trigger with the same name 'trg1' already exists
140+
# Clean up
141+
DROP TRIGGER trg1;

0 commit comments

Comments
 (0)