diff options
author | unknown <andrey@lmy004.> | 2006-07-10 14:42:29 +0200 |
---|---|---|
committer | unknown <andrey@lmy004.> | 2006-07-10 14:42:29 +0200 |
commit | 5d91dc2560836ae28351baf446df7bee97e2cd05 (patch) | |
tree | 28a786dfaf77132889807ad9f7ff5dbd568f9707 | |
parent | e8699b56140571305b898c29bdc45a5bab117a45 (diff) | |
parent | 974eecc246db18e29e5ef06db6f48a0f4b3f7951 (diff) | |
download | mariadb-git-5d91dc2560836ae28351baf446df7bee97e2cd05.tar.gz |
Manual merge
BitKeeper/etc/ignore:
auto-union
mysql-test/r/events_logs_tests.result:
Auto merged
mysql-test/t/events_logs_tests.test:
Auto merged
mysql-test/t/events_stress.test:
Auto merged
sql/mysqld.cc:
Auto merged
sql/set_var.cc:
Auto merged
sql/sql_class.cc:
Auto merged
sql/sql_class.h:
Auto merged
sql/sql_db.cc:
Auto merged
sql/sql_lex.h:
Auto merged
sql/sql_parse.cc:
Auto merged
sql/share/errmsg.txt:
Auto merged
sql/sql_yacc.yy:
Auto merged
sql/event_data_objects.cc:
manual merge
sql/events.cc:
manual merge
43 files changed, 4816 insertions, 4974 deletions
diff --git a/.bzrignore b/.bzrignore index 2fdd5b3e68f..3cb00e96541 100644 --- a/.bzrignore +++ b/.bzrignore @@ -444,6 +444,7 @@ libmysql/mysys_priv.h libmysql/net.c libmysql/release/libmysql.exp libmysql/vio_priv.h +libmysql/viosocket.o.6WmSJk libmysql_r/*.c libmysql_r/acconfig.h libmysql_r/conf_to_src @@ -461,7 +462,10 @@ libmysqld/discover.cc libmysqld/emb_qcache.cpp libmysqld/errmsg.c libmysqld/event.cc +libmysqld/event_data_objects.cc +libmysqld/event_db_repository.cc libmysqld/event_executor.cc +libmysqld/event_queue.cc libmysqld/event_scheduler.cc libmysqld/event_timed.cc libmysqld/events.cc diff --git a/libmysqld/Makefile.am b/libmysqld/Makefile.am index b91944ac258..2e03ba0f0a9 100644 --- a/libmysqld/Makefile.am +++ b/libmysqld/Makefile.am @@ -68,7 +68,8 @@ sqlsources = derror.cc field.cc field_conv.cc strfunc.cc filesort.cc \ spatial.cc gstream.cc sql_help.cc tztime.cc sql_cursor.cc \ sp_head.cc sp_pcontext.cc sp.cc sp_cache.cc sp_rcontext.cc \ parse_file.cc sql_view.cc sql_trigger.cc my_decimal.cc \ - event_scheduler.cc events.cc event_timed.cc \ + event_scheduler.cc events.cc event_data_objects.cc \ + event_queue.cc event_db_repository.cc \ rpl_filter.cc sql_partition.cc sql_builtin.cc sql_plugin.cc \ sql_tablespace.cc \ rpl_injector.cc my_user.c partition_info.cc diff --git a/mysql-test/r/events.result b/mysql-test/r/events.result index e115e077535..2128624786d 100644 --- a/mysql-test/r/events.result +++ b/mysql-test/r/events.result @@ -85,13 +85,24 @@ SHOW EVENTS; Db Name Definer Type Execute at Interval value Interval field Starts Ends Status events_test event_starts_test root@localhost RECURRING NULL 20 SECOND # # ENABLED DROP EVENT event_starts_test; +create table test_nested(a int); create event e_43 on schedule every 1 second do set @a = 5; -set global event_scheduler = 1; alter event e_43 do alter event e_43 do set @a = 4; +ERROR HY000: Recursivity of EVENT DDL statements is forbidden when body is present +alter event e_43 do +begin +alter event e_43 on schedule every 5 minute; +insert into test_nested values(1); +end| +set global event_scheduler = 1; select db, name, body, status, interval_field, interval_value from mysql.event; db name body status interval_field interval_value -events_test e_43 set @a = 4 ENABLED SECOND 1 +events_test e_43 begin +alter event e_43 on schedule every 5 minute; +insert into test_nested values(1); +end ENABLED MINUTE 5 drop event e_43; +drop table test_nested; "Let's check whether we can use non-qualified names" create table non_qualif(a int); create event non_qualif_ev on schedule every 10 minute do insert into non_qualif values (800219); @@ -195,6 +206,10 @@ ERROR 42000: This version of MySQL doesn't yet support 'MICROSECOND' SHOW EVENTS; ERROR 42000: This version of MySQL doesn't yet support 'MICROSECOND' drop event root22; +create event root23 on schedule every -100 year do select 1; +ERROR HY000: INTERVAL is either not positive or too big +create event root23 on schedule every 222222222222222222222 year do select 1; +ERROR HY000: INTERVAL is either not positive or too big drop event root6; drop event root7; drop event root8; @@ -285,9 +300,9 @@ select db, name, body, definer, convert_tz(execute_at, 'UTC', 'SYSTEM'), on_comp db name body definer convert_tz(execute_at, 'UTC', 'SYSTEM') on_completion events_test e_26 set @a = 5 root@localhost 2017-01-01 00:00:00 DROP drop event e_26; -create event e_26 on schedule at NULL disabled do set @a = 5; +create event e_26 on schedule at NULL disable do set @a = 5; ERROR HY000: Incorrect AT value: 'NULL' -create event e_26 on schedule at 'definitely not a datetime' disabled do set @a = 5; +create event e_26 on schedule at 'definitely not a datetime' disable do set @a = 5; ERROR HY000: Incorrect AT value: 'definitely not a datetime' set names utf8; create event задачка on schedule every 123 minute starts now() ends now() + interval 1 month do select 1; @@ -311,7 +326,6 @@ root@localhost закачка events_test "Should be only 1 process" select /*1*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; user host db command state info -event_scheduler localhost NULL Connect Suspended NULL select release_lock("test_lock1"); release_lock("test_lock1") 1 @@ -331,7 +345,7 @@ create event закачка on schedule every 10 hour do select get_lock("test_l "Should have only 2 processes: the scheduler and the locked event" select /*2*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; user host db command state info -event_scheduler localhost NULL Connect Sleeping NULL +event_scheduler localhost NULL Daemon Waiting for next activation NULL root localhost events_test Connect User lock select get_lock("test_lock2", 20) "Release the mutex, the event worker should finish." "Release the mutex, the event worker should finish." @@ -347,18 +361,17 @@ create event закачка21 on schedule every 10 hour do select get_lock("test "Should have only 3 processes: the scheduler, our conn and the locked event" select /*3*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; user host db command state info -event_scheduler localhost NULL Connect Sleeping NULL +event_scheduler localhost NULL Daemon Waiting for next activation NULL root localhost events_test Connect User lock select get_lock("test_lock2_1", 20) set global event_scheduler=2; "Should have only our process now:" select /*4*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; user host db command state info -event_scheduler localhost NULL Connect Suspended NULL root localhost events_test Connect User lock select get_lock("test_lock2_1", 20) drop event закачка21; create table t_16 (s1 int); create trigger t_16_bi before insert on t_16 for each row create event e_16 on schedule every 1 second do set @a=5; -ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger. +ERROR HY000: Recursivity of EVENT DDL statements is forbidden when body is present drop table t_16; create event white_space on schedule every 10 hour diff --git a/mysql-test/r/events_bugs.result b/mysql-test/r/events_bugs.result index a7c2964a253..e6115161cbb 100644 --- a/mysql-test/r/events_bugs.result +++ b/mysql-test/r/events_bugs.result @@ -17,26 +17,26 @@ DROP EVENT ДОЛЕÐ_региÑÑ‚ÑŠÑ€_утф8; SET NAMES latin1; set @a=3; CREATE PROCEDURE p_16 () CREATE EVENT e_16 ON SCHEDULE EVERY @a SECOND DO SET @a=5; -call p_16(); -"Here we used to crash!" -call p_16(); -ERROR HY000: Event 'e_16' already exists -call p_16(); -ERROR HY000: Event 'e_16' already exists -DROP EVENT e_16; -CALL p_16(); -CALL p_16(); -ERROR HY000: Event 'e_16' already exists -DROP PROCEDURE p_16; -DROP EVENT e_16; +ERROR HY000: Recursivity of EVENT DDL statements is forbidden when body is present create event e_55 on schedule at 99990101000000 do drop table t; ERROR HY000: Incorrect AT value: '99990101000000' create event e_55 on schedule every 10 hour starts 99990101000000 do drop table t; ERROR HY000: Incorrect STARTS value: '99990101000000' create event e_55 on schedule every 10 minute ends 99990101000000 do drop table t; ERROR HY000: ENDS is either invalid or before STARTS +create event e_55 on schedule at 10000101000000 do drop table t; +ERROR HY000: Activation (AT) time is in the past +create event e_55 on schedule at 20000101000000 do drop table t; +ERROR HY000: Activation (AT) time is in the past +create event e_55 on schedule at 20200101000000 starts 10000101000000 do drop table t; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'starts 10000101000000 do drop table t' at line 1 +create event e_55 on schedule at 20200101000000 ends 10000101000000 do drop table t; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'ends 10000101000000 do drop table t' at line 1 +create event e_55 on schedule at 20200101000000 starts 10000101000000 ends 10000101000000 do drop table t; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'starts 10000101000000 ends 10000101000000 do drop table t' at line 1 +create event e_55 on schedule every 10 hour starts 10000101000000 do drop table t; +ERROR HY000: Incorrect STARTS value: '10000101000000' set global event_scheduler=2; -"Wait a bit to settle down" delete from mysql.event; set global event_scheduler= 1; set @old_sql_mode:=@@sql_mode; @@ -52,7 +52,7 @@ end| "Now if everything is fine the event has compiled and is locked select /*1*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; user host db command state info -event_scheduler localhost NULL Connect Sleeping NULL +event_scheduler localhost NULL Daemon Waiting for next activation NULL root localhost events_test Connect User lock select get_lock('test_bug16407', 60) select release_lock('test_bug16407'); release_lock('test_bug16407') @@ -68,6 +68,11 @@ select event_schema, event_name, sql_mode from information_schema.events order b event_schema event_name sql_mode events_test e_16407 STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,TRADITIONAL,NO_AUTO_CREATE_USER drop event e_16407; +set sql_mode="ansi"; +select get_lock('ee_16407_2', 60); +get_lock('ee_16407_2', 60) +1 +set global event_scheduler= 1; "Another sql_mode test" set sql_mode="traditional"; create table events_smode_test(ev_name char(10), a date) engine=myisam; @@ -75,6 +80,7 @@ create table events_smode_test(ev_name char(10), a date) engine=myisam; create event ee_16407_2 on schedule every 60 second do begin select get_lock('ee_16407_2', 60) /*ee_16407_2*/; +select release_lock('ee_16407_2'); insert into events_test.events_smode_test values('ee_16407_2','1980-19-02'); end| insert into events_smode_test values ('test','1980-19-02')| @@ -83,6 +89,7 @@ ERROR 22007: Incorrect date value: '1980-19-02' for column 'a' at row 1 create event ee_16407_3 on schedule every 60 second do begin select get_lock('ee_16407_2', 60) /*ee_16407_3*/; +select release_lock('ee_16407_2'); insert into events_test.events_smode_test values ('ee_16407_3','1980-02-19'); insert into events_test.events_smode_test values ('ee_16407_3','1980-02-29'); end| @@ -91,6 +98,7 @@ set sql_mode=""| create event ee_16407_4 on schedule every 60 second do begin select get_lock('ee_16407_2', 60) /*ee_16407_4*/; +select release_lock('ee_16407_2'); insert into events_test.events_smode_test values ('ee_16407_4','10-11-1956'); end| select event_schema, event_name, sql_mode from information_schema.events order by event_schema, event_name; @@ -98,14 +106,9 @@ event_schema event_name sql_mode events_test ee_16407_2 STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,TRADITIONAL,NO_AUTO_CREATE_USER events_test ee_16407_3 STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,TRADITIONAL,NO_AUTO_CREATE_USER events_test ee_16407_4 -set sql_mode="ansi"; -select get_lock('ee_16407_2', 60); -get_lock('ee_16407_2', 60) -1 -set global event_scheduler= 1; select /*2*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; user host db command state info -event_scheduler localhost NULL Connect Sleeping NULL +event_scheduler localhost NULL Daemon Waiting for next activation NULL root localhost events_test Connect User lock select get_lock('ee_16407_2', 60) /*ee_16407_2*/ root localhost events_test Connect User lock select get_lock('ee_16407_2', 60) /*ee_16407_3*/ root localhost events_test Connect User lock select get_lock('ee_16407_2', 60) /*ee_16407_4*/ @@ -114,7 +117,7 @@ release_lock('ee_16407_2') 1 select /*3*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; user host db command state info -event_scheduler localhost NULL Connect Sleeping NULL +event_scheduler localhost NULL Daemon Waiting for next activation NULL set global event_scheduler= 2; select * from events_smode_test order by ev_name, a; ev_name a @@ -132,28 +135,30 @@ drop event ee_16407_3; drop event ee_16407_4; "And now one last test regarding sql_mode and call of SP from an event" delete from events_smode_test; +set sql_mode='ansi'; +select get_lock('ee_16407_5', 60); +get_lock('ee_16407_5', 60) +1 +set global event_scheduler= 1; set sql_mode='traditional'; create procedure ee_16407_5_pendant() begin insert into events_test.events_smode_test values('ee_16407_5','2001-02-29'); end| create procedure ee_16407_6_pendant() begin insert into events_test.events_smode_test values('ee_16407_6','2004-02-29'); end| create event ee_16407_5 on schedule every 60 second do begin select get_lock('ee_16407_5', 60) /*ee_16407_5*/; +select release_lock('ee_16407_5'); call events_test.ee_16407_5_pendant(); end| create event ee_16407_6 on schedule every 60 second do begin select get_lock('ee_16407_5', 60) /*ee_16407_6*/; +select release_lock('ee_16407_5'); call events_test.ee_16407_6_pendant(); end| -set sql_mode='ansi'; -select get_lock('ee_16407_5', 60); -get_lock('ee_16407_5', 60) -1 -set global event_scheduler= 1; "Should have 2 locked processes" select /*4*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; user host db command state info -event_scheduler localhost NULL Connect Sleeping NULL +event_scheduler localhost NULL Daemon Waiting for next activation NULL root localhost events_test Connect User lock select get_lock('ee_16407_5', 60) /*ee_16407_5*/ root localhost events_test Connect User lock select get_lock('ee_16407_5', 60) /*ee_16407_6*/ select release_lock('ee_16407_5'); @@ -162,7 +167,7 @@ release_lock('ee_16407_5') "Should have 0 processes locked" select /*5*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; user host db command state info -event_scheduler localhost NULL Connect Sleeping NULL +event_scheduler localhost NULL Daemon Waiting for next activation NULL select * from events_smode_test order by ev_name, a; ev_name a ee_16407_6 2004-02-29 @@ -201,4 +206,12 @@ events_test mysqltest_user1 mysqltest_user1@localhost RECURRING ENABLED drop event events_test.mysqltest_user1; drop user mysqltest_user1@localhost; drop database mysqltest_db1; +create event e_53 on schedule at (select s1 from ttx) do drop table t; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'select s1 from ttx) do drop table t' at line 1 +create event e_53 on schedule every (select s1 from ttx) second do drop table t; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'select s1 from ttx) second do drop table t' at line 1 +create event e_53 on schedule every 5 second starts (select s1 from ttx) do drop table t; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'select s1 from ttx) do drop table t' at line 1 +create event e_53 on schedule every 5 second ends (select s1 from ttx) do drop table t; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'select s1 from ttx) do drop table t' at line 1 drop database events_test; diff --git a/mysql-test/r/events_logs_tests.result b/mysql-test/r/events_logs_tests.result index 950090399d5..f56ebe3e472 100644 --- a/mysql-test/r/events_logs_tests.result +++ b/mysql-test/r/events_logs_tests.result @@ -9,7 +9,7 @@ SELECT user_host, argument FROM mysql.general_log WHERE argument LIKE '%alabala% END| "Check General Query Log" SET GLOBAL event_scheduler=2; -create event log_general on schedule every 1 minute do SELect 'alabala', sleep(3) from dual; +create event log_general on schedule every 1 minute do SELect 'alabala', sleep(1) from dual; TRUNCATE mysql.general_log; "1 row, the current statement!" call select_general_log(); @@ -19,7 +19,7 @@ SET GLOBAL event_scheduler=1; "Should see 3 rows - the 'SELect' is in the middle. The other two are selects from general_log" call select_general_log(); user_host argument -USER_HOST SELect 'alabala', sleep(3) from dual +USER_HOST SELect 'alabala', sleep(1) from dual DROP PROCEDURE select_general_log; DROP EVENT log_general; SET GLOBAL event_scheduler=2; @@ -52,10 +52,11 @@ TRUNCATE mysql.slow_log; CREATE TABLE slow_event_test (slo_val tinyint, val tinyint); SET SESSION long_query_time=1; "This won't go to the slow log" -CREATE EVENT long_event ON SCHEDULE EVERY 1 MINUTE DO INSERT INTO slow_event_test SELECT @@long_query_time, SLEEP(3); SELECT * FROM slow_event_test; slo_val val +SET SESSION long_query_time=1; SET GLOBAL event_scheduler=1; +CREATE EVENT long_event ON SCHEDULE EVERY 1 MINUTE DO INSERT INTO slow_event_test SELECT @@long_query_time, SLEEP(1.5); "Sleep some more time than the actual event run will take" SHOW VARIABLES LIKE 'event_scheduler'; Variable_name Value @@ -64,7 +65,7 @@ event_scheduler 1 SELECT * FROM slow_event_test; slo_val val 4 0 -"Check slow log. Should not see anything because 3 is under the threshold of 4 for GLOBAL, though over SESSION which is 2" +"Check slow log. Should not see anything because 1.5 is under the threshold of 300 for GLOBAL, though over SESSION which is 2" SELECT user_host, query_time, db, sql_text FROM mysql.slow_log; user_host query_time db sql_text "This should go to the slow log" diff --git a/mysql-test/r/events_scheduling.result b/mysql-test/r/events_scheduling.result index eb44751c176..d7b7241db51 100644 --- a/mysql-test/r/events_scheduling.result +++ b/mysql-test/r/events_scheduling.result @@ -39,7 +39,7 @@ DROP EVENT start_n_end; DROP EVENT only_one_time; ERROR HY000: Unknown event 'only_one_time' "Should be preserved" -SELECT EVENT_NAME, STATUS FROM INFORMATION_SCHEMA.EVENTS; +SELECT EVENT_NAME, STATUS FROM INFORMATION_SCHEMA.EVENTS ORDER BY EVENT_NAME; EVENT_NAME STATUS E19170 ENABLED two_time DISABLED diff --git a/mysql-test/r/ps_1general.result b/mysql-test/r/ps_1general.result index 1a1d6432411..d0b773dfe34 100644 --- a/mysql-test/r/ps_1general.result +++ b/mysql-test/r/ps_1general.result @@ -299,7 +299,7 @@ t9 MyISAM 10 Dynamic 2 216 432 # 2048 0 NULL # # # latin1_swedish_ci NULL prepare stmt4 from ' show status like ''Threads_running'' '; execute stmt4; Variable_name Value -Threads_running 2 +Threads_running 1 prepare stmt4 from ' show variables like ''sql_mode'' '; execute stmt4; Variable_name Value diff --git a/mysql-test/r/skip_name_resolve.result b/mysql-test/r/skip_name_resolve.result index 855876825ad..8ef52e75238 100644 --- a/mysql-test/r/skip_name_resolve.result +++ b/mysql-test/r/skip_name_resolve.result @@ -10,6 +10,5 @@ user() # show processlist; Id User Host db Command Time State Info -<id> event_scheduler <host> NULL <command> <time> <state> <info> <id> root <host> test <command> <time> <state> <info> <id> root <host> test <command> <time> <state> <info> diff --git a/mysql-test/r/sp-threads.result b/mysql-test/r/sp-threads.result index 3cba437e0a6..c516d7a643f 100644 --- a/mysql-test/r/sp-threads.result +++ b/mysql-test/r/sp-threads.result @@ -34,7 +34,6 @@ lock tables t2 write; call bug9486(); show processlist; Id User Host db Command Time State Info -# event_scheduler localhost NULL Connect # Suspended NULL # root localhost test Sleep # NULL # root localhost test Query # Locked update t1, t2 set val= 1 where id1=id2 # root localhost test Query # NULL show processlist diff --git a/mysql-test/r/sp_notembedded.result b/mysql-test/r/sp_notembedded.result index c5d60446e0a..c8cafe5ace1 100644 --- a/mysql-test/r/sp_notembedded.result +++ b/mysql-test/r/sp_notembedded.result @@ -18,11 +18,9 @@ show processlist; end| call bug4902_2()| Id User Host db Command Time State Info -# event_scheduler localhost NULL Connect # Suspended NULL # root localhost test Query # NULL show processlist call bug4902_2()| Id User Host db Command Time State Info -# event_scheduler localhost NULL Connect # Suspended NULL # root localhost test Query # NULL show processlist drop procedure bug4902_2| drop function if exists bug5278| diff --git a/mysql-test/r/status.result b/mysql-test/r/status.result index 83c6a6f5288..45b84219f2a 100644 --- a/mysql-test/r/status.result +++ b/mysql-test/r/status.result @@ -52,22 +52,22 @@ drop table t1; FLUSH STATUS; SHOW STATUS LIKE 'max_used_connections'; Variable_name Value -Max_used_connections 2 +Max_used_connections 1 SET @save_thread_cache_size=@@thread_cache_size; SET GLOBAL thread_cache_size=3; SHOW STATUS LIKE 'max_used_connections'; Variable_name Value -Max_used_connections 4 +Max_used_connections 3 FLUSH STATUS; SHOW STATUS LIKE 'max_used_connections'; Variable_name Value -Max_used_connections 3 +Max_used_connections 2 SHOW STATUS LIKE 'max_used_connections'; Variable_name Value -Max_used_connections 4 +Max_used_connections 3 SHOW STATUS LIKE 'max_used_connections'; Variable_name Value -Max_used_connections 5 +Max_used_connections 4 SET GLOBAL thread_cache_size=@save_thread_cache_size; show status like 'com_show_status'; Variable_name Value diff --git a/mysql-test/t/events.test b/mysql-test/t/events.test index aac13a55dd3..34c00a6387a 100644 --- a/mysql-test/t/events.test +++ b/mysql-test/t/events.test @@ -18,7 +18,7 @@ CREATE EVENT e_x2 ON SCHEDULE EVERY 1 SECOND DO DROP TABLE x_table; connection default; SHOW DATABASES LIKE 'db_x'; SET GLOBAL event_scheduler=1; ---sleep 1.5 +--sleep 0.8 SHOW DATABASES LIKE 'db_x'; SHOW TABLES FROM db_x; SET GLOBAL event_scheduler=2; @@ -81,19 +81,27 @@ SHOW EVENTS; DROP EVENT event_starts_test; # # +create table test_nested(a int); create event e_43 on schedule every 1 second do set @a = 5; -set global event_scheduler = 1; ---sleep 2 +--error 1562 alter event e_43 do alter event e_43 do set @a = 4; ---sleep 2 +delimiter |; +alter event e_43 do +begin + alter event e_43 on schedule every 5 minute; + insert into test_nested values(1); +end| +delimiter ;| +set global event_scheduler = 1; +--sleep 3 select db, name, body, status, interval_field, interval_value from mysql.event; drop event e_43; ---sleep 1 +drop table test_nested; --echo "Let's check whether we can use non-qualified names" create table non_qualif(a int); create event non_qualif_ev on schedule every 10 minute do insert into non_qualif values (800219); ---sleep 1 +--sleep 0.5 select * from non_qualif; drop event non_qualif_ev; drop table non_qualif; @@ -156,6 +164,10 @@ show create event root22; --error ER_NOT_SUPPORTED_YET SHOW EVENTS; drop event root22; +--error ER_EVENT_INTERVAL_NOT_POSITIVE_OR_TOO_BIG +create event root23 on schedule every -100 year do select 1; +--error ER_EVENT_INTERVAL_NOT_POSITIVE_OR_TOO_BIG +create event root23 on schedule every 222222222222222222222 year do select 1; drop event root6; drop event root7; drop event root8; @@ -246,9 +258,9 @@ create event e_26 on schedule at '2017-01-01 00:00:00' disable do set @a = 5; select db, name, body, definer, convert_tz(execute_at, 'UTC', 'SYSTEM'), on_completion from mysql.event; drop event e_26; --error ER_WRONG_VALUE -create event e_26 on schedule at NULL disabled do set @a = 5; +create event e_26 on schedule at NULL disable do set @a = 5; --error ER_WRONG_VALUE -create event e_26 on schedule at 'definitely not a datetime' disabled do set @a = 5; +create event e_26 on schedule at 'definitely not a datetime' disable do set @a = 5; set names utf8; create event задачка on schedule every 123 minute starts now() ends now() + interval 1 month do select 1; @@ -285,7 +297,7 @@ select get_lock("test_lock2", 20); --echo "Create an event which tries to acquire a mutex. The event locks on the mutex" create event закачка on schedule every 10 hour do select get_lock("test_lock2", 20); --echo "Let some time pass to the event starts" ---sleep 1 +--sleep 0.5 --echo "Should have only 2 processes: the scheduler and the locked event" select /*2*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info;--echo "Release the mutex, the event worker should finish." --echo "Release the mutex, the event worker should finish." @@ -303,10 +315,11 @@ drop event закачка; set global event_scheduler=1; select get_lock("test_lock2_1", 20); create event закачка21 on schedule every 10 hour do select get_lock("test_lock2_1", 20); ---sleep 1 +--sleep 0.5 --echo "Should have only 3 processes: the scheduler, our conn and the locked event" select /*3*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; set global event_scheduler=2; +--sleep 0.3 --echo "Should have only our process now:" select /*4*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; drop event закачка21; @@ -315,7 +328,7 @@ drop event закачка21; # Bug #16410 Events: CREATE EVENT is legal in a CREATE TRIGGER statement # create table t_16 (s1 int); ---error 1422 +--error 1562 create trigger t_16_bi before insert on t_16 for each row create event e_16 on schedule every 1 second do set @a=5; drop table t_16; # diff --git a/mysql-test/t/events_bugs.test b/mysql-test/t/events_bugs.test index 9434de7be7d..7fd4c684b4b 100644 --- a/mysql-test/t/events_bugs.test +++ b/mysql-test/t/events_bugs.test @@ -30,19 +30,8 @@ SET NAMES latin1; # START - BUG#16408: Events: crash for an event in a procedure # set @a=3; +--error 1562 CREATE PROCEDURE p_16 () CREATE EVENT e_16 ON SCHEDULE EVERY @a SECOND DO SET @a=5; -call p_16(); ---echo "Here we used to crash!" ---error ER_EVENT_ALREADY_EXISTS -call p_16(); ---error ER_EVENT_ALREADY_EXISTS -call p_16(); -DROP EVENT e_16; -CALL p_16(); ---error ER_EVENT_ALREADY_EXISTS -CALL p_16(); -DROP PROCEDURE p_16; -DROP EVENT e_16; # # END - BUG#16408: Events: crash for an event in a procedure # @@ -56,6 +45,19 @@ create event e_55 on schedule at 99990101000000 do drop table t; create event e_55 on schedule every 10 hour starts 99990101000000 do drop table t; --error ER_EVENT_ENDS_BEFORE_STARTS create event e_55 on schedule every 10 minute ends 99990101000000 do drop table t; +--error ER_EVENT_EXEC_TIME_IN_THE_PAST +create event e_55 on schedule at 10000101000000 do drop table t; +--error ER_EVENT_EXEC_TIME_IN_THE_PAST +create event e_55 on schedule at 20000101000000 do drop table t; +--error ER_PARSE_ERROR +create event e_55 on schedule at 20200101000000 starts 10000101000000 do drop table t; +--error ER_PARSE_ERROR +create event e_55 on schedule at 20200101000000 ends 10000101000000 do drop table t; +--error ER_PARSE_ERROR +create event e_55 on schedule at 20200101000000 starts 10000101000000 ends 10000101000000 do drop table t; +--error ER_WRONG_VALUE +create event e_55 on schedule every 10 hour starts 10000101000000 do drop table t; + # # End - 16396: Events: Distant-future dates become past dates # @@ -64,8 +66,6 @@ create event e_55 on schedule every 10 minute ends 99990101000000 do drop table # Start - 16407: Events: Changes in sql_mode won't be taken into account # set global event_scheduler=2; ---echo "Wait a bit to settle down" ---sleep 1 delete from mysql.event; set global event_scheduler= 1; set @old_sql_mode:=@@sql_mode; @@ -78,11 +78,13 @@ begin drop table "hashed_num"; end| delimiter ;| ---sleep 1 +--sleep 0.5 --echo "Now if everything is fine the event has compiled and is locked select /*1*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; select release_lock('test_bug16407'); + set global event_scheduler= 2; + select event_schema, event_name, sql_mode from information_schema.events order by event_schema, event_name; --echo "Let's check whether we change the sql_mode on ALTER EVENT" set sql_mode='traditional'; @@ -90,6 +92,10 @@ alter event e_16407 do select 1; select event_schema, event_name, sql_mode from information_schema.events order by event_schema, event_name; drop event e_16407; +set sql_mode="ansi"; +select get_lock('ee_16407_2', 60); + +set global event_scheduler= 1; --echo "Another sql_mode test" set sql_mode="traditional"; create table events_smode_test(ev_name char(10), a date) engine=myisam; @@ -98,6 +104,7 @@ delimiter |; create event ee_16407_2 on schedule every 60 second do begin select get_lock('ee_16407_2', 60) /*ee_16407_2*/; + select release_lock('ee_16407_2'); insert into events_test.events_smode_test values('ee_16407_2','1980-19-02'); end| --error ER_TRUNCATED_WRONG_VALUE @@ -106,6 +113,7 @@ insert into events_smode_test values ('test','1980-19-02')| create event ee_16407_3 on schedule every 60 second do begin select get_lock('ee_16407_2', 60) /*ee_16407_3*/; + select release_lock('ee_16407_2'); insert into events_test.events_smode_test values ('ee_16407_3','1980-02-19'); insert into events_test.events_smode_test values ('ee_16407_3','1980-02-29'); end| @@ -114,17 +122,15 @@ set sql_mode=""| create event ee_16407_4 on schedule every 60 second do begin select get_lock('ee_16407_2', 60) /*ee_16407_4*/; + select release_lock('ee_16407_2'); insert into events_test.events_smode_test values ('ee_16407_4','10-11-1956'); end| delimiter ;| select event_schema, event_name, sql_mode from information_schema.events order by event_schema, event_name; -set sql_mode="ansi"; -select get_lock('ee_16407_2', 60); -set global event_scheduler= 1; ---sleep 1 +--sleep 0.5 select /*2*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; select release_lock('ee_16407_2'); ---sleep 2 +--sleep 0.8 select /*3*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; set global event_scheduler= 2; select * from events_smode_test order by ev_name, a; @@ -137,6 +143,11 @@ drop event ee_16407_4; --echo "And now one last test regarding sql_mode and call of SP from an event" delete from events_smode_test; +set sql_mode='ansi'; +select get_lock('ee_16407_5', 60); + +set global event_scheduler= 1; + set sql_mode='traditional'; delimiter |; create procedure ee_16407_5_pendant() begin insert into events_test.events_smode_test values('ee_16407_5','2001-02-29'); end| @@ -144,22 +155,21 @@ create procedure ee_16407_6_pendant() begin insert into events_test.events_smode create event ee_16407_5 on schedule every 60 second do begin select get_lock('ee_16407_5', 60) /*ee_16407_5*/; + select release_lock('ee_16407_5'); call events_test.ee_16407_5_pendant(); end| create event ee_16407_6 on schedule every 60 second do begin select get_lock('ee_16407_5', 60) /*ee_16407_6*/; + select release_lock('ee_16407_5'); call events_test.ee_16407_6_pendant(); end| delimiter ;| -set sql_mode='ansi'; -select get_lock('ee_16407_5', 60); -set global event_scheduler= 1; ---sleep 1 +--sleep 0.5 --echo "Should have 2 locked processes" select /*4*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; select release_lock('ee_16407_5'); ---sleep 2 +--sleep 0.8 --echo "Should have 0 processes locked" select /*5*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; select * from events_smode_test order by ev_name, a; @@ -209,4 +219,19 @@ drop database mysqltest_db1; # END - 18897: Events: unauthorized action possible with alter event rename # +# +# START - BUG#16394: Events: Crash if schedule contains SELECT +# +--error ER_PARSE_ERROR +create event e_53 on schedule at (select s1 from ttx) do drop table t; +--error ER_PARSE_ERROR +create event e_53 on schedule every (select s1 from ttx) second do drop table t; +--error ER_PARSE_ERROR +create event e_53 on schedule every 5 second starts (select s1 from ttx) do drop table t; +--error ER_PARSE_ERROR +create event e_53 on schedule every 5 second ends (select s1 from ttx) do drop table t; +# +# END - BUG#16394: Events: Crash if schedule contains SELECT +# + drop database events_test; diff --git a/mysql-test/t/events_logs_tests.test b/mysql-test/t/events_logs_tests.test index 5c252b1174b..28818083efa 100644 --- a/mysql-test/t/events_logs_tests.test +++ b/mysql-test/t/events_logs_tests.test @@ -14,7 +14,7 @@ END| delimiter ;| --echo "Check General Query Log" SET GLOBAL event_scheduler=2; -create event log_general on schedule every 1 minute do SELect 'alabala', sleep(3) from dual; +create event log_general on schedule every 1 minute do SELect 'alabala', sleep(1) from dual; TRUNCATE mysql.general_log; --echo "1 row, the current statement!" --replace_column 1 USER_HOST @@ -22,13 +22,12 @@ call select_general_log(); SET GLOBAL event_scheduler=1; --echo "Wait the scheduler to start" --echo "Should see 3 rows - the 'SELect' is in the middle. The other two are selects from general_log" ---sleep 2 +--sleep 0.7 --replace_column 1 USER_HOST call select_general_log(); DROP PROCEDURE select_general_log; DROP EVENT log_general; SET GLOBAL event_scheduler=2; ---sleep 1 --echo "Check slow query log" --disable_query_log @@ -72,15 +71,16 @@ TRUNCATE mysql.slow_log; CREATE TABLE slow_event_test (slo_val tinyint, val tinyint); SET SESSION long_query_time=1; --echo "This won't go to the slow log" -CREATE EVENT long_event ON SCHEDULE EVERY 1 MINUTE DO INSERT INTO slow_event_test SELECT @@long_query_time, SLEEP(3); SELECT * FROM slow_event_test; +SET SESSION long_query_time=1; SET GLOBAL event_scheduler=1; +CREATE EVENT long_event ON SCHEDULE EVERY 1 MINUTE DO INSERT INTO slow_event_test SELECT @@long_query_time, SLEEP(1.5); --echo "Sleep some more time than the actual event run will take" ---sleep 5 +--sleep 2 SHOW VARIABLES LIKE 'event_scheduler'; --echo "Check our table. Should see 1 row" SELECT * FROM slow_event_test; ---echo "Check slow log. Should not see anything because 3 is under the threshold of 4 for GLOBAL, though over SESSION which is 2" +--echo "Check slow log. Should not see anything because 1.5 is under the threshold of 300 for GLOBAL, though over SESSION which is 2" SELECT user_host, query_time, db, sql_text FROM mysql.slow_log; --echo "This should go to the slow log" DROP EVENT long_event; @@ -88,7 +88,7 @@ SET SESSION long_query_time=10; SET GLOBAL long_query_time=1; CREATE EVENT long_event2 ON SCHEDULE EVERY 1 MINUTE DO INSERT INTO slow_event_test SELECT @@long_query_time, SLEEP(2); --echo "Sleep some more time than the actual event run will take" ---sleep 3 +--sleep 2.5 --echo "Check our table. Should see 2 rows" SELECT * FROM slow_event_test; --echo "Check slow log. Should see 1 row because 4 is over the threshold of 3 for GLOBAL, though under SESSION which is 10" diff --git a/mysql-test/t/events_scheduling.test b/mysql-test/t/events_scheduling.test index 987939bc162..dc4460999a7 100644 --- a/mysql-test/t/events_scheduling.test +++ b/mysql-test/t/events_scheduling.test @@ -34,7 +34,7 @@ DROP EVENT start_n_end; --error ER_EVENT_DOES_NOT_EXIST DROP EVENT only_one_time; --echo "Should be preserved" -SELECT EVENT_NAME, STATUS FROM INFORMATION_SCHEMA.EVENTS; +SELECT EVENT_NAME, STATUS FROM INFORMATION_SCHEMA.EVENTS ORDER BY EVENT_NAME; DROP EVENT two_time; DROP TABLE table_1; DROP TABLE table_2; diff --git a/mysql-test/t/events_stress.test b/mysql-test/t/events_stress.test index 6546bce3a76..a0101861282 100644 --- a/mysql-test/t/events_stress.test +++ b/mysql-test/t/events_stress.test @@ -62,7 +62,7 @@ while ($1) --enable_query_log SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_conn1_test2'; SET GLOBAL event_scheduler=1; ---sleep 6 +--sleep 2.5 DROP DATABASE events_conn1_test2; SET GLOBAL event_scheduler=2; @@ -101,7 +101,7 @@ while ($1) } --enable_query_log SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_conn1_test2'; ---sleep 6 +--sleep 2.5 connection conn2; --send DROP DATABASE events_conn2_db; diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index 9a97b79813b..58f3fb4409e 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -51,7 +51,8 @@ ADD_EXECUTABLE(mysqld ../sql-common/client.c derror.cc des_key_file.cc sql_table.cc sql_test.cc sql_trigger.cc sql_udf.cc sql_union.cc sql_update.cc sql_view.cc strfunc.cc table.cc thr_malloc.cc time.cc tztime.cc uniques.cc unireg.cc item_xmlfunc.cc - rpl_tblmap.cc sql_binlog.cc event_scheduler.cc event_timed.cc + rpl_tblmap.cc sql_binlog.cc event_scheduler.cc event_data_objects.cc + event_queue.cc event_db_repository.cc sql_tablespace.cc events.cc ../sql-common/my_user.c partition_info.cc rpl_injector.cc ${PROJECT_SOURCE_DIR}/sql/sql_yacc.cc diff --git a/sql/Makefile.am b/sql/Makefile.am index 387f18c2ae9..4d76cdb5080 100644 --- a/sql/Makefile.am +++ b/sql/Makefile.am @@ -64,9 +64,10 @@ noinst_HEADERS = item.h item_func.h item_sum.h item_cmpfunc.h \ tztime.h my_decimal.h\ sp_head.h sp_pcontext.h sp_rcontext.h sp.h sp_cache.h \ parse_file.h sql_view.h sql_trigger.h \ - sql_array.h sql_cursor.h events.h events_priv.h \ - sql_plugin.h authors.h sql_partition.h event_timed.h \ - partition_info.h partition_element.h event_scheduler.h \ + sql_array.h sql_cursor.h events.h \ + sql_plugin.h authors.h sql_partition.h event_data_objects.h \ + event_queue.h event_db_repository.h \ + partition_info.h partition_element.h event_scheduler_ng.h \ contributors.h mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \ item.cc item_sum.cc item_buff.cc item_func.cc \ @@ -103,8 +104,9 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \ gstream.cc spatial.cc sql_help.cc sql_cursor.cc \ tztime.cc my_time.c my_user.c my_decimal.cc\ sp_head.cc sp_pcontext.cc sp_rcontext.cc sp.cc \ - sp_cache.cc parse_file.cc sql_trigger.cc \ - event_scheduler.cc events.cc event_timed.cc \ + sp_cache.cc parse_file.cc sql_trigger.cc event_scheduler.cc\ + event_scheduler_ng.cc events.cc event_data_objects.cc \ + event_queue.cc event_db_repository.cc \ sql_plugin.cc sql_binlog.cc \ sql_builtin.cc sql_tablespace.cc partition_info.cc diff --git a/sql/event_timed.cc b/sql/event_data_objects.cc index 98369e0e055..5153553d06b 100644 --- a/sql/event_timed.cc +++ b/sql/event_data_objects.cc @@ -16,111 +16,59 @@ #define MYSQL_LEX 1 #include "mysql_priv.h" -#include "events_priv.h" #include "events.h" -#include "event_timed.h" +#include "event_data_objects.h" +#include "event_db_repository.h" #include "sp_head.h" -/* - Constructor - - SYNOPSIS - Event_timed::Event_timed() -*/ - -Event_timed::Event_timed():in_spawned_thread(0),locked_by_thread_id(0), - running(0), thread_id(0), status_changed(false), - last_executed_changed(false), expression(0), - created(0), modified(0), - on_completion(Event_timed::ON_COMPLETION_DROP), - status(Event_timed::ENABLED), sphead(0), - sql_mode(0), body_begin(0), dropped(false), - free_sphead_on_delete(true), flags(0) - -{ - pthread_mutex_init(&this->LOCK_running, MY_MUTEX_INIT_FAST); - pthread_cond_init(&this->COND_finished, NULL); - init(); -} - - -/* - Destructor - - SYNOPSIS - Event_timed::~Event_timed() -*/ - -Event_timed::~Event_timed() -{ - deinit_mutexes(); - - if (free_sphead_on_delete) - free_sp(); -} +#define EVEX_MAX_INTERVAL_VALUE 1000000000L /* - Destructor + Returns a new instance SYNOPSIS - Event_timed::~deinit_mutexes() -*/ - -void -Event_timed::deinit_mutexes() -{ - pthread_mutex_destroy(&this->LOCK_running); - pthread_cond_destroy(&this->COND_finished); -} - - -/* - Checks whether the event is running + Event_parse_data::new_instance() - SYNOPSIS - Event_timed::is_running() + RETURN VALUE + Address or NULL in case of error + + NOTE + Created on THD's mem_root */ -bool -Event_timed::is_running() +Event_parse_data * +Event_parse_data::new_instance(THD *thd) { - bool ret; - - VOID(pthread_mutex_lock(&this->LOCK_running)); - ret= running; - VOID(pthread_mutex_unlock(&this->LOCK_running)); - - return ret; + return new (thd->mem_root) Event_parse_data; } /* - Init all member variables + Constructor SYNOPSIS - Event_timed::init() + Event_parse_data::Event_parse_data() */ -void -Event_timed::init() +Event_parse_data::Event_parse_data() { - DBUG_ENTER("Event_timed::init"); + DBUG_ENTER("Event_parse_data::Event_parse_data"); - dbname.str= name.str= body.str= comment.str= 0; - dbname.length= name.length= body.length= comment.length= 0; + item_execute_at= item_expression= item_starts= item_ends= NULL; + status= ENABLED; + on_completion= ON_COMPLETION_DROP; + expression= 0; + /* Actually in the parser STARTS is always set */ set_zero_time(&starts, MYSQL_TIMESTAMP_DATETIME); set_zero_time(&ends, MYSQL_TIMESTAMP_DATETIME); set_zero_time(&execute_at, MYSQL_TIMESTAMP_DATETIME); - set_zero_time(&last_executed, MYSQL_TIMESTAMP_DATETIME); starts_null= ends_null= execute_at_null= TRUE; - definer_user.str= definer_host.str= 0; - definer_user.length= definer_host.length= 0; - - sql_mode= 0; + body.str= comment.str= NULL; + body.length= comment.length= 0; DBUG_VOID_RETURN; } @@ -130,15 +78,15 @@ Event_timed::init() Set a name of the event SYNOPSIS - Event_timed::init_name() + Event_parse_data::init_name() thd THD spn the name extracted in the parser */ void -Event_timed::init_name(THD *thd, sp_name *spn) +Event_parse_data::init_name(THD *thd, sp_name *spn) { - DBUG_ENTER("Event_timed::init_name"); + DBUG_ENTER("Event_parse_data::init_name"); /* During parsing, we must use thd->mem_root */ MEM_ROOT *root= thd->mem_root; @@ -162,7 +110,7 @@ Event_timed::init_name(THD *thd, sp_name *spn) Set body of the event - what should be executed. SYNOPSIS - Event_timed::init_body() + Event_parse_data::init_body() thd THD NOTE @@ -174,10 +122,10 @@ Event_timed::init_name(THD *thd, sp_name *spn) */ void -Event_timed::init_body(THD *thd) +Event_parse_data::init_body(THD *thd) { - DBUG_ENTER("Event_timed::init_body"); - DBUG_PRINT("info", ("body=[%s] body_begin=0x%ld end=0x%ld", body_begin, + DBUG_ENTER("Event_parse_data::init_body"); + DBUG_PRINT("info", ("body=[%s] body_begin=0x%lx end=0x%lx", body_begin, body_begin, thd->lex->ptr)); body.length= thd->lex->ptr - body_begin; @@ -195,7 +143,7 @@ Event_timed::init_body(THD *thd) continue; } - /* + /* consume closing comments This is arguably wrong, but it's the best we have until the parser is @@ -228,17 +176,60 @@ Event_timed::init_body(THD *thd) ++body_begin; --body.length; } - body.str= strmake_root(thd->mem_root, (char *)body_begin, body.length); + body.str= thd->strmake((char *)body_begin, body.length); DBUG_VOID_RETURN; } /* + Inits definer (definer_user and definer_host) during parsing. + + SYNOPSIS + Event_parse_data::init_definer() + + RETURN VALUE + 0 OK +*/ + +int +Event_parse_data::init_definer(THD *thd) +{ + int definer_user_len; + int definer_host_len; + DBUG_ENTER("Event_parse_data::init_definer"); + + DBUG_PRINT("info",("init definer_user thd->mem_root=0x%lx " + "thd->sec_ctx->priv_user=0x%lx", thd->mem_root, + thd->security_ctx->priv_user)); + + definer_user_len= strlen(thd->security_ctx->priv_user); + definer_host_len= strlen(thd->security_ctx->priv_host); + + /* + 1 for @ */ + DBUG_PRINT("info",("init definer as whole")); + definer.length= definer_user_len + definer_host_len + 1; + definer.str= thd->alloc(definer.length + 1); + + DBUG_PRINT("info",("copy the user")); + memcpy(definer.str, thd->security_ctx->priv_user, definer_user_len); + definer.str[definer_user_len]= '@'; + + DBUG_PRINT("info",("copy the host")); + memcpy(definer.str + definer_user_len + 1, thd->security_ctx->priv_host, + definer_host_len); + definer.str[definer.length]= '\0'; + DBUG_PRINT("info",("definer [%s] initted", definer.str)); + + DBUG_RETURN(0); +} + + +/* Set time for execution for one time events. SYNOPSIS - Event_timed::init_execute_at() + Event_parse_data::init_execute_at() expr when (datetime) RETURN VALUE @@ -249,33 +240,39 @@ Event_timed::init_body(THD *thd) */ int -Event_timed::init_execute_at(THD *thd, Item *expr) +Event_parse_data::init_execute_at(THD *thd) { my_bool not_used; TIME ltime; my_time_t t; - TIME time_tmp; - DBUG_ENTER("Event_timed::init_execute_at"); - if (expr->fix_fields(thd, &expr)) - DBUG_RETURN(EVEX_PARSE_ERROR); + DBUG_ENTER("Event_parse_data::init_execute_at"); + + if (!item_execute_at) + DBUG_RETURN(0); + + if (item_execute_at->fix_fields(thd, &item_execute_at)) + goto wrong_value; /* no starts and/or ends in case of execute_at */ DBUG_PRINT("info", ("starts_null && ends_null should be 1 is %d", (starts_null && ends_null))); DBUG_ASSERT(starts_null && ends_null); - + /* let's check whether time is in the past */ thd->variables.time_zone->gmt_sec_to_TIME(&time_tmp, (my_time_t) thd->query_start()); - if ((not_used= expr->get_date(<ime, TIME_NO_ZERO_DATE))) - DBUG_RETURN(ER_WRONG_VALUE); + if ((not_used= item_execute_at->get_date(<ime, TIME_NO_ZERO_DATE))) + goto wrong_value; if (TIME_to_ulonglong_datetime(<ime) < TIME_to_ulonglong_datetime(&time_tmp)) - DBUG_RETURN(EVEX_BAD_PARAMS); + { + my_error(ER_EVENT_EXEC_TIME_IN_THE_PAST, MYF(0)); + DBUG_RETURN(ER_WRONG_VALUE); + } /* This may result in a 1970-01-01 date if ltime is > 2037-xx-xx. @@ -287,12 +284,16 @@ Event_timed::init_execute_at(THD *thd, Item *expr) if (!t) { DBUG_PRINT("error", ("Execute AT after year 2037")); - DBUG_RETURN(ER_WRONG_VALUE); + goto wrong_value; } execute_at_null= FALSE; execute_at= ltime; DBUG_RETURN(0); + +wrong_value: + report_bad_value("AT", item_execute_at); + DBUG_RETURN(ER_WRONG_VALUE); } @@ -300,35 +301,49 @@ Event_timed::init_execute_at(THD *thd, Item *expr) Set time for execution for transient events. SYNOPSIS - Event_timed::init_interval() + Event_parse_data::init_interval() expr how much? new_interval what is the interval RETURN VALUE - 0 OK - EVEX_PARSE_ERROR fix_fields failed - EVEX_BAD_PARAMS Interval is not positive - EVEX_MICROSECOND_UNSUP Microseconds are not supported. + 0 OK + EVEX_PARSE_ERROR fix_fields failed (reported) + EVEX_BAD_PARAMS Interval is not positive (reported) + EVEX_MICROSECOND_UNSUP Microseconds are not supported (reported) */ int -Event_timed::init_interval(THD *thd, Item *expr, interval_type new_interval) +Event_parse_data::init_interval(THD *thd) { String value; INTERVAL interval_tmp; - DBUG_ENTER("Event_timed::init_interval"); + DBUG_ENTER("Event_parse_data::init_interval"); + if (!item_expression) + DBUG_RETURN(0); + + switch (interval) { + case INTERVAL_MINUTE_MICROSECOND: + case INTERVAL_HOUR_MICROSECOND: + case INTERVAL_DAY_MICROSECOND: + case INTERVAL_SECOND_MICROSECOND: + case INTERVAL_MICROSECOND: + my_error(ER_NOT_SUPPORTED_YET, MYF(0), "MICROSECOND"); + DBUG_RETURN(EVEX_BAD_PARAMS); + default: + break; + } - if (expr->fix_fields(thd, &expr)) - DBUG_RETURN(EVEX_PARSE_ERROR); + if (item_expression->fix_fields(thd, &item_expression)) + goto wrong_value; value.alloc(MAX_DATETIME_FULL_WIDTH*MY_CHARSET_BIN_MB_MAXLEN); - if (get_interval_value(expr, new_interval, &value, &interval_tmp)) - DBUG_RETURN(EVEX_PARSE_ERROR); + if (get_interval_value(item_expression, interval, &value, &interval_tmp)) + goto wrong_value; expression= 0; - switch (new_interval) { + switch (interval) { case INTERVAL_YEAR: expression= interval_tmp.year; break; @@ -366,44 +381,38 @@ Event_timed::init_interval(THD *thd, Item *expr, interval_type new_interval) interval_tmp.minute)*60 + interval_tmp.second; break; - case INTERVAL_MINUTE_MICROSECOND: /* day and hour are 0 */ - case INTERVAL_HOUR_MICROSECOND: /* day is anyway 0 */ - case INTERVAL_DAY_MICROSECOND: - DBUG_RETURN(EVEX_MICROSECOND_UNSUP); - expression= ((((interval_tmp.day*24) + interval_tmp.hour)*60+ - interval_tmp.minute)*60 + - interval_tmp.second) * 1000000L + interval_tmp.second_part; - break; case INTERVAL_HOUR_MINUTE: expression= interval_tmp.hour * 60 + interval_tmp.minute; break; case INTERVAL_MINUTE_SECOND: expression= interval_tmp.minute * 60 + interval_tmp.second; break; - case INTERVAL_SECOND_MICROSECOND: - DBUG_RETURN(EVEX_MICROSECOND_UNSUP); - expression= interval_tmp.second * 1000000L + interval_tmp.second_part; - break; - case INTERVAL_MICROSECOND: - DBUG_RETURN(EVEX_MICROSECOND_UNSUP); case INTERVAL_LAST: DBUG_ASSERT(0); + default: + ;/* these are the microsec stuff */ } if (interval_tmp.neg || expression > EVEX_MAX_INTERVAL_VALUE) + { + my_error(ER_EVENT_INTERVAL_NOT_POSITIVE_OR_TOO_BIG, MYF(0)); DBUG_RETURN(EVEX_BAD_PARAMS); + } - interval= new_interval; DBUG_RETURN(0); + +wrong_value: + report_bad_value("INTERVAL", item_execute_at); + DBUG_RETURN(ER_WRONG_VALUE); } /* - Set activation time. + Sets activation time. SYNOPSIS - Event_timed::init_starts() - expr how much? - interval what is the interval + Event_parse_data::init_starts() + expr how much? + interval what is the interval NOTES Note that activation time is not execution time. @@ -420,19 +429,21 @@ Event_timed::init_interval(THD *thd, Item *expr, interval_type new_interval) */ int -Event_timed::init_starts(THD *thd, Item *new_starts) +Event_parse_data::init_starts(THD *thd) { my_bool not_used; TIME ltime, time_tmp; my_time_t t; - DBUG_ENTER("Event_timed::init_starts"); + DBUG_ENTER("Event_parse_data::init_starts"); + if (!item_starts) + DBUG_RETURN(0); - if (new_starts->fix_fields(thd, &new_starts)) - DBUG_RETURN(EVEX_PARSE_ERROR); + if (item_starts->fix_fields(thd, &item_starts)) + goto wrong_value; - if ((not_used= new_starts->get_date(<ime, TIME_NO_ZERO_DATE))) - DBUG_RETURN(EVEX_BAD_PARAMS); + if ((not_used= item_starts->get_date(<ime, TIME_NO_ZERO_DATE))) + goto wrong_value; /* Let's check whether time is in the past */ thd->variables.time_zone->gmt_sec_to_TIME(&time_tmp, @@ -442,7 +453,7 @@ Event_timed::init_starts(THD *thd, Item *new_starts) DBUG_PRINT("info",("starts=%lld", TIME_to_ulonglong_datetime(<ime))); if (TIME_to_ulonglong_datetime(<ime) < TIME_to_ulonglong_datetime(&time_tmp)) - DBUG_RETURN(EVEX_BAD_PARAMS); + goto wrong_value; /* This may result in a 1970-01-01 date if ltime is > 2037-xx-xx. @@ -452,24 +463,25 @@ Event_timed::init_starts(THD *thd, Item *new_starts) */ my_tz_UTC->gmt_sec_to_TIME(<ime,t=TIME_to_timestamp(thd, <ime, ¬_used)); if (!t) - { - DBUG_PRINT("error", ("STARTS after year 2037")); - DBUG_RETURN(EVEX_BAD_PARAMS); - } + goto wrong_value; starts= ltime; starts_null= FALSE; DBUG_RETURN(0); + +wrong_value: + report_bad_value("STARTS", item_starts); + DBUG_RETURN(ER_WRONG_VALUE); } /* - Set deactivation time. + Sets deactivation time. SYNOPSIS - Event_timed::init_ends() + Event_parse_data::init_ends() thd THD - new_ends when? + new_ends When? NOTES Note that activation time is not execution time. @@ -481,26 +493,26 @@ Event_timed::init_starts(THD *thd, Item *new_starts) RETURN VALUE 0 OK - EVEX_PARSE_ERROR fix_fields failed - ER_WRONG_VALUE starts distant date (after year 2037) - EVEX_BAD_PARAMS ENDS before STARTS + EVEX_BAD_PARAMS Error (reported) */ int -Event_timed::init_ends(THD *thd, Item *new_ends) +Event_parse_data::init_ends(THD *thd) { TIME ltime, ltime_now; my_bool not_used; my_time_t t; - DBUG_ENTER("Event_timed::init_ends"); + DBUG_ENTER("Event_parse_data::init_ends"); + if (!item_ends) + DBUG_RETURN(0); - if (new_ends->fix_fields(thd, &new_ends)) - DBUG_RETURN(EVEX_PARSE_ERROR); + if (item_ends->fix_fields(thd, &item_ends)) + goto error_bad_params; DBUG_PRINT("info", ("convert to TIME")); - if ((not_used= new_ends->get_date(<ime, TIME_NO_ZERO_DATE))) - DBUG_RETURN(EVEX_BAD_PARAMS); + if ((not_used= item_ends->get_date(<ime, TIME_NO_ZERO_DATE))) + goto error_bad_params; /* This may result in a 1970-01-01 date if ltime is > 2037-xx-xx. @@ -511,15 +523,12 @@ Event_timed::init_ends(THD *thd, Item *new_ends) DBUG_PRINT("info", ("get the UTC time")); my_tz_UTC->gmt_sec_to_TIME(<ime,t=TIME_to_timestamp(thd, <ime, ¬_used)); if (!t) - { - DBUG_PRINT("error", ("ENDS after year 2037")); - DBUG_RETURN(EVEX_BAD_PARAMS); - } + goto error_bad_params; /* Check whether ends is after starts */ DBUG_PRINT("info", ("ENDS after STARTS?")); if (!starts_null && my_time_compare(&starts, <ime) != -1) - DBUG_RETURN(EVEX_BAD_PARAMS); + goto error_bad_params; /* The parser forces starts to be provided but one day STARTS could be @@ -529,84 +538,259 @@ Event_timed::init_ends(THD *thd, Item *new_ends) DBUG_PRINT("info", ("ENDS after NOW?")); my_tz_UTC->gmt_sec_to_TIME(<ime_now, thd->query_start()); if (my_time_compare(<ime_now, <ime) == 1) - DBUG_RETURN(EVEX_BAD_PARAMS); + goto error_bad_params; ends= ltime; ends_null= FALSE; DBUG_RETURN(0); + +error_bad_params: + my_error(ER_EVENT_ENDS_BEFORE_STARTS, MYF(0)); + DBUG_RETURN(EVEX_BAD_PARAMS); } /* - Sets comment. + Prints an error message about invalid value. Internally used + during input data verification SYNOPSIS - Event_timed::init_comment() - thd THD - used for memory allocation - comment the string. + Event_parse_data::report_bad_value() + item_name The name of the parameter + bad_item The parameter */ void -Event_timed::init_comment(THD *thd, LEX_STRING *set_comment) +Event_parse_data::report_bad_value(const char *item_name, Item *bad_item) +{ + char buff[120]; + String str(buff,(uint32) sizeof(buff), system_charset_info); + String *str2= bad_item->fixed? bad_item->val_str(&str):NULL; + my_error(ER_WRONG_VALUE, MYF(0), item_name, str2? str2->c_ptr_safe():"NULL"); +} + + +/* + Performs checking of the data gathered during the parsing phase. + + SYNOPSIS + Event_parse_data::check_parse_data() + thd Thread + + RETURN VALUE + FALSE OK + TRUE Error (reported) +*/ + +bool +Event_parse_data::check_parse_data(THD *thd) +{ + bool ret; + DBUG_ENTER("Event_parse_data::check_parse_data"); + DBUG_PRINT("info", ("execute_at=0x%lx expr=0x%lx starts=0x%lx ends=0x%lx", + item_execute_at, item_expression, item_starts, item_ends)); + + init_name(thd, identifier); + + init_definer(thd); + + ret= init_execute_at(thd) || init_interval(thd) || init_starts(thd) || + init_ends(thd); + DBUG_RETURN(ret); +} + +/* + Constructor + + SYNOPSIS + Event_basic::Event_basic() +*/ + +Event_basic::Event_basic() { - DBUG_ENTER("Event_timed::init_comment"); + DBUG_ENTER("Event_basic::Event_basic"); + /* init memory root */ + init_alloc_root(&mem_root, 256, 512); + dbname.str= name.str= NULL; + dbname.length= name.length= 0; + DBUG_VOID_RETURN; +} + + +/* + Destructor - comment.str= strmake_root(thd->mem_root, set_comment->str, - comment.length= set_comment->length); + SYNOPSIS + Event_basic::Event_basic() +*/ +Event_basic::~Event_basic() +{ + DBUG_ENTER("Event_basic::~Event_basic"); + free_root(&mem_root, MYF(0)); DBUG_VOID_RETURN; } /* - Inits definer (definer_user and definer_host) during parsing. + Short function to load a char column into a LEX_STRING SYNOPSIS - Event_timed::init_definer() + Event_basic::load_string_field() + field_name The field( enum_events_table_field is not actually used + because it's unknown in event_data_objects.h) + fields The Field array + field_value The value +*/ + +bool +Event_basic::load_string_fields(Field **fields, ...) +{ + bool ret= FALSE; + va_list args; + enum enum_events_table_field field_name; + LEX_STRING *field_value; + + DBUG_ENTER("Event_basic::load_string_fields"); - RETURN VALUE - 0 OK + va_start(args, fields); + field_name= (enum enum_events_table_field) va_arg(args, int); + while (field_name != ET_FIELD_COUNT) + { + field_value= va_arg(args, LEX_STRING *); + if ((field_value->str= get_field(&mem_root, fields[field_name])) == NullS) + { + ret= TRUE; + break; + } + field_value->length= strlen(field_value->str); + + field_name= (enum enum_events_table_field) va_arg(args, int); + } + va_end(args); + + DBUG_RETURN(ret); +} + + +/* + Constructor + + SYNOPSIS + Event_queue_element::Event_queue_element() */ -int -Event_timed::init_definer(THD *thd) +Event_queue_element::Event_queue_element(): + status_changed(FALSE), last_executed_changed(FALSE), + on_completion(ON_COMPLETION_DROP), status(ENABLED), + expression(0), dropped(FALSE), flags(0) { - DBUG_ENTER("Event_timed::init_definer"); + DBUG_ENTER("Event_queue_element::Event_queue_element"); - DBUG_PRINT("info",("init definer_user thd->mem_root=0x%lx " - "thd->sec_ctx->priv_user=0x%lx", thd->mem_root, - thd->security_ctx->priv_user)); - definer_user.str= strdup_root(thd->mem_root, thd->security_ctx->priv_user); - definer_user.length= strlen(thd->security_ctx->priv_user); + set_zero_time(&starts, MYSQL_TIMESTAMP_DATETIME); + set_zero_time(&ends, MYSQL_TIMESTAMP_DATETIME); + set_zero_time(&execute_at, MYSQL_TIMESTAMP_DATETIME); + set_zero_time(&last_executed, MYSQL_TIMESTAMP_DATETIME); + starts_null= ends_null= execute_at_null= TRUE; - DBUG_PRINT("info",("init definer_host thd->s_c->priv_host=0x%lx", - thd->security_ctx->priv_host)); - definer_host.str= strdup_root(thd->mem_root, thd->security_ctx->priv_host); - definer_host.length= strlen(thd->security_ctx->priv_host); + DBUG_VOID_RETURN; +} - DBUG_PRINT("info",("init definer as whole")); - definer.length= definer_user.length + definer_host.length + 1; - definer.str= alloc_root(thd->mem_root, definer.length + 1); - DBUG_PRINT("info",("copy the user")); - memcpy(definer.str, definer_user.str, definer_user.length); - definer.str[definer_user.length]= '@'; +/* + Destructor - DBUG_PRINT("info",("copy the host")); - memcpy(definer.str + definer_user.length + 1, definer_host.str, - definer_host.length); - definer.str[definer.length]= '\0'; - DBUG_PRINT("info",("definer initted")); + SYNOPSIS + Event_queue_element::Event_queue_element() +*/ +Event_queue_element::~Event_queue_element() +{ +} - DBUG_RETURN(0); + +/* + Constructor + + SYNOPSIS + Event_timed::Event_timed() +*/ + +Event_timed::Event_timed(): + created(0), modified(0), sql_mode(0) +{ + DBUG_ENTER("Event_timed::Event_timed"); + init(); + DBUG_VOID_RETURN; } /* - Loads an event from a row from mysql.event + Destructor SYNOPSIS - Event_timed::load_from_row(MEM_ROOT *mem_root, TABLE *table) + Event_timed::~Event_timed() +*/ + +Event_timed::~Event_timed() +{ +} + + +/* + Constructor + + SYNOPSIS + Event_job_data::Event_job_data() +*/ + +Event_job_data::Event_job_data(): + thd(NULL), sphead(0), sql_mode(0) +{ +} + + +/* + Destructor + + SYNOPSIS + Event_timed::~Event_timed() +*/ + +Event_job_data::~Event_job_data() +{ + free_sp(); +} + + +/* + Init all member variables + + SYNOPSIS + Event_timed::init() +*/ + +void +Event_timed::init() +{ + DBUG_ENTER("Event_timed::init"); + + body.str= comment.str= NULL; + body.length= comment.length= 0; + + definer_user.str= definer_host.str= 0; + definer_user.length= definer_host.length= 0; + + sql_mode= 0; + + DBUG_VOID_RETURN; +} + + +/* + Loads an event's body from a row from mysql.event + + SYNOPSIS + Event_job_data::load_from_row(MEM_ROOT *mem_root, TABLE *table) RETURN VALUE 0 OK @@ -619,124 +803,222 @@ Event_timed::init_definer(THD *thd) */ int -Event_timed::load_from_row(MEM_ROOT *mem_root, TABLE *table) +Event_job_data::load_from_row(TABLE *table) { char *ptr; - Event_timed *et; uint len; - bool res1, res2; - - DBUG_ENTER("Event_timed::load_from_row"); + DBUG_ENTER("Event_job_data::load_from_row"); if (!table) goto error; - et= this; + if (table->s->fields != ET_FIELD_COUNT) + goto error; + + load_string_fields(table->field, ET_FIELD_DB, &dbname, ET_FIELD_NAME, &name, + ET_FIELD_BODY, &body, ET_FIELD_DEFINER, &definer, + ET_FIELD_COUNT); - if (table->s->fields != Events::FIELD_COUNT) +/* + if ((dbname.str= get_field(&mem_root, table->field[ET_FIELD_DB])) == NullS) goto error; + dbname.length= strlen(dbname.str); - if ((et->dbname.str= get_field(mem_root, - table->field[Events::FIELD_DB])) == NULL) + if ((name.str= get_field(&mem_root, table->field[ET_FIELD_NAME])) == NullS) goto error; + name.length= strlen(name.str); - et->dbname.length= strlen(et->dbname.str); + if ((body.str= get_field(&mem_root, table->field[ET_FIELD_BODY])) == NullS) + goto error; + body.length= strlen(body.str); - if ((et->name.str= get_field(mem_root, - table->field[Events::FIELD_NAME])) == NULL) + if ((definer.str= get_field(&mem_root, + table->field[ET_FIELD_DEFINER])) == NullS) goto error; - et->name.length= strlen(et->name.str); + definer.length= strlen(definer.str); +*/ + ptr= strchr(definer.str, '@'); + + if (! ptr) + ptr= definer.str; + + len= ptr - definer.str; + definer_user.str= strmake_root(&mem_root, definer.str, len); + definer_user.length= len; + len= definer.length - len - 1; + /* 1:because of @ */ + definer_host.str= strmake_root(&mem_root, ptr + 1, len); + definer_host.length= len; - if ((et->body.str= get_field(mem_root, - table->field[Events::FIELD_BODY])) == NULL) - goto error; + sql_mode= (ulong) table->field[ET_FIELD_SQL_MODE]->val_int(); + + DBUG_RETURN(0); +error: + DBUG_RETURN(EVEX_GET_FIELD_FAILED); +} + + +/* + Loads an event from a row from mysql.event + + SYNOPSIS + Event_queue_element::load_from_row(MEM_ROOT *mem_root, TABLE *table) + + RETURN VALUE + 0 OK + EVEX_GET_FIELD_FAILED Error + + NOTES + This method is silent on errors and should behave like that. Callers + should handle throwing of error messages. The reason is that the class + should not know about how to deal with communication. +*/ + +int +Event_queue_element::load_from_row(TABLE *table) +{ + char *ptr; + bool res1, res2; - et->body.length= strlen(et->body.str); + DBUG_ENTER("Event_queue_element::load_from_row"); - if ((et->definer.str= get_field(mem_root, - table->field[Events::FIELD_DEFINER])) == NullS) + if (!table) goto error; - et->definer.length= strlen(et->definer.str); - ptr= strchr(et->definer.str, '@'); + if (table->s->fields != ET_FIELD_COUNT) + goto error; - if (! ptr) - ptr= et->definer.str; + load_string_fields(table->field, ET_FIELD_DB, &dbname, ET_FIELD_NAME, &name, + ET_FIELD_DEFINER, &definer, ET_FIELD_COUNT); +/* + if ((dbname.str= get_field(&mem_root, table->field[ET_FIELD_DB])) == NullS) + goto error; + dbname.length= strlen(dbname.str); - len= ptr - et->definer.str; + if ((name.str= get_field(&mem_root, table->field[ET_FIELD_NAME])) == NullS) + goto error; + name.length= strlen(name.str); - et->definer_user.str= strmake_root(mem_root, et->definer.str, len); - et->definer_user.length= len; - len= et->definer.length - len - 1; //1 is because of @ - et->definer_host.str= strmake_root(mem_root, ptr + 1, len);/* 1:because of @*/ - et->definer_host.length= len; - - et->starts_null= table->field[Events::FIELD_STARTS]->is_null(); - res1= table->field[Events::FIELD_STARTS]-> - get_date(&et->starts,TIME_NO_ZERO_DATE); + if ((definer.str= get_field(&mem_root, + table->field[ET_FIELD_DEFINER])) == NullS) + goto error; + definer.length= strlen(definer.str); +*/ + starts_null= table->field[ET_FIELD_STARTS]->is_null(); + res1= table->field[ET_FIELD_STARTS]->get_date(&starts, TIME_NO_ZERO_DATE); - et->ends_null= table->field[Events::FIELD_ENDS]->is_null(); - res2= table->field[Events::FIELD_ENDS]->get_date(&et->ends, TIME_NO_ZERO_DATE); + ends_null= table->field[ET_FIELD_ENDS]->is_null(); + res2= table->field[ET_FIELD_ENDS]->get_date(&ends, TIME_NO_ZERO_DATE); - if (!table->field[Events::FIELD_INTERVAL_EXPR]->is_null()) - et->expression= table->field[Events::FIELD_INTERVAL_EXPR]->val_int(); + if (!table->field[ET_FIELD_INTERVAL_EXPR]->is_null()) + expression= table->field[ET_FIELD_INTERVAL_EXPR]->val_int(); else - et->expression= 0; + expression= 0; /* - If res1 and res2 are true then both fields are empty. - Hence if Events::FIELD_EXECUTE_AT is empty there is an error. + If res1 and res2 are TRUE then both fields are empty. + Hence if ET_FIELD_EXECUTE_AT is empty there is an error. */ - et->execute_at_null= - table->field[Events::FIELD_EXECUTE_AT]->is_null(); - DBUG_ASSERT(!(et->starts_null && et->ends_null && !et->expression && - et->execute_at_null)); - if (!et->expression && - table->field[Events::FIELD_EXECUTE_AT]-> get_date(&et->execute_at, - TIME_NO_ZERO_DATE)) + execute_at_null= table->field[ET_FIELD_EXECUTE_AT]->is_null(); + DBUG_ASSERT(!(starts_null && ends_null && !expression && execute_at_null)); + if (!expression && + table->field[ET_FIELD_EXECUTE_AT]->get_date(&execute_at, + TIME_NO_ZERO_DATE)) goto error; /* In DB the values start from 1 but enum interval_type starts from 0 */ - if (!table->field[Events::FIELD_TRANSIENT_INTERVAL]->is_null()) - et->interval= (interval_type) ((ulonglong) - table->field[Events::FIELD_TRANSIENT_INTERVAL]->val_int() - 1); + if (!table->field[ET_FIELD_TRANSIENT_INTERVAL]->is_null()) + interval= (interval_type) ((ulonglong) + table->field[ET_FIELD_TRANSIENT_INTERVAL]->val_int() - 1); else - et->interval= (interval_type) 0; + interval= (interval_type) 0; + + table->field[ET_FIELD_LAST_EXECUTED]->get_date(&last_executed, + TIME_NO_ZERO_DATE); + last_executed_changed= FALSE; - et->created= table->field[Events::FIELD_CREATED]->val_int(); - et->modified= table->field[Events::FIELD_MODIFIED]->val_int(); - table->field[Events::FIELD_LAST_EXECUTED]-> - get_date(&et->last_executed, TIME_NO_ZERO_DATE); + if ((ptr= get_field(&mem_root, table->field[ET_FIELD_STATUS])) == NullS) + goto error; - last_executed_changed= false; + DBUG_PRINT("load_from_row", ("Event [%s] is [%s]", name.str, ptr)); + status= (ptr[0]=='E'? Event_queue_element::ENABLED: + Event_queue_element::DISABLED); /* ToDo : Andrey . Find a way not to allocate ptr on event_mem_root */ - if ((ptr= get_field(mem_root, table->field[Events::FIELD_STATUS])) == NullS) + if ((ptr= get_field(&mem_root, + table->field[ET_FIELD_ON_COMPLETION])) == NullS) goto error; - DBUG_PRINT("load_from_row", ("Event [%s] is [%s]", et->name.str, ptr)); - et->status= (ptr[0]=='E'? Event_timed::ENABLED:Event_timed::DISABLED); + on_completion= (ptr[0]=='D'? Event_queue_element::ON_COMPLETION_DROP: + Event_queue_element::ON_COMPLETION_PRESERVE); - /* ToDo : Andrey . Find a way not to allocate ptr on event_mem_root */ - if ((ptr= get_field(mem_root, - table->field[Events::FIELD_ON_COMPLETION])) == NullS) + DBUG_RETURN(0); +error: + DBUG_RETURN(EVEX_GET_FIELD_FAILED); +} + + +/* + Loads an event from a row from mysql.event + + SYNOPSIS + Event_timed::load_from_row(MEM_ROOT *mem_root, TABLE *table) + + RETURN VALUE + 0 OK + EVEX_GET_FIELD_FAILED Error + + NOTES + This method is silent on errors and should behave like that. Callers + should handle throwing of error messages. The reason is that the class + should not know about how to deal with communication. +*/ + +int +Event_timed::load_from_row(TABLE *table) +{ + char *ptr; + uint len; + + DBUG_ENTER("Event_timed::load_from_row"); + + if (Event_queue_element::load_from_row(table)) + goto error; + + load_string_fields(table->field, ET_FIELD_BODY, &body, ET_FIELD_COUNT); +/* + if ((body.str= get_field(&mem_root, table->field[ET_FIELD_BODY])) == NullS) goto error; - et->on_completion= (ptr[0]=='D'? Event_timed::ON_COMPLETION_DROP: - Event_timed::ON_COMPLETION_PRESERVE); + body.length= strlen(body.str); +*/ + ptr= strchr(definer.str, '@'); - et->comment.str= get_field(mem_root, table->field[Events::FIELD_COMMENT]); - if (et->comment.str != NullS) - et->comment.length= strlen(et->comment.str); + if (! ptr) + ptr= definer.str; + + len= ptr - definer.str; + definer_user.str= strmake_root(&mem_root, definer.str, len); + definer_user.length= len; + len= definer.length - len - 1; + /* 1:because of @ */ + definer_host.str= strmake_root(&mem_root, ptr + 1, len); + definer_host.length= len; + + created= table->field[ET_FIELD_CREATED]->val_int(); + modified= table->field[ET_FIELD_MODIFIED]->val_int(); + + comment.str= get_field(&mem_root, table->field[ET_FIELD_COMMENT]); + if (comment.str != NullS) + comment.length= strlen(comment.str); else - et->comment.length= 0; - + comment.length= 0; - et->sql_mode= (ulong) table->field[Events::FIELD_SQL_MODE]->val_int(); + sql_mode= (ulong) table->field[ET_FIELD_SQL_MODE]->val_int(); DBUG_RETURN(0); error: @@ -755,7 +1037,7 @@ error: time_now current time i_value quantity of time type interval to add i_type type of interval to add (SECOND, MINUTE, HOUR, WEEK ...) - + RETURN VALUE 0 OK 1 Error @@ -890,7 +1172,7 @@ bool get_next_time(TIME *next, TIME *start, TIME *time_now, TIME *last_exec, tmp= *start; if ((ret= date_add_interval(&tmp, INTERVAL_MONTH, interval))) goto done; - + /* If `tmp` is still before time_now just add one more time the interval */ if (my_time_compare(&tmp, time_now) == -1) { @@ -914,7 +1196,7 @@ done: Computes next execution time. SYNOPSIS - Event_timed::compute_next_execution_time() + Event_queue_element::compute_next_execution_time() RETURN VALUE FALSE OK @@ -926,18 +1208,18 @@ done: */ bool -Event_timed::compute_next_execution_time() +Event_queue_element::compute_next_execution_time() { TIME time_now; int tmp; - DBUG_ENTER("Event_timed::compute_next_execution_time"); - DBUG_PRINT("enter", ("starts=%llu ends=%llu last_executed=%llu", + DBUG_ENTER("Event_queue_element::compute_next_execution_time"); + DBUG_PRINT("enter", ("starts=%llu ends=%llu last_executed=%llu this=0x%lx", TIME_to_ulonglong_datetime(&starts), TIME_to_ulonglong_datetime(&ends), - TIME_to_ulonglong_datetime(&last_executed))); + TIME_to_ulonglong_datetime(&last_executed), this)); - if (status == Event_timed::DISABLED) + if (status == Event_queue_element::DISABLED) { DBUG_PRINT("compute_next_execution_time", ("Event %s is DISABLED", name.str)); @@ -951,11 +1233,11 @@ Event_timed::compute_next_execution_time() { DBUG_PRINT("info",("One-time event %s.%s of was already executed", dbname.str, name.str, definer.str)); - dropped= (on_completion == Event_timed::ON_COMPLETION_DROP); + dropped= (on_completion == Event_queue_element::ON_COMPLETION_DROP); DBUG_PRINT("info",("One-time event will be dropped=%d.", dropped)); - status= Event_timed::DISABLED; - status_changed= true; + status= Event_queue_element::DISABLED; + status_changed= TRUE; } goto ret; } @@ -971,11 +1253,11 @@ Event_timed::compute_next_execution_time() /* time_now is after ends. don't execute anymore */ set_zero_time(&execute_at, MYSQL_TIMESTAMP_DATETIME); execute_at_null= TRUE; - if (on_completion == Event_timed::ON_COMPLETION_DROP) - dropped= true; + if (on_completion == Event_queue_element::ON_COMPLETION_DROP) + dropped= TRUE; DBUG_PRINT("info", ("Dropped=%d", dropped)); - status= Event_timed::DISABLED; - status_changed= true; + status= Event_queue_element::DISABLED; + status_changed= TRUE; goto ret; } @@ -1037,10 +1319,10 @@ Event_timed::compute_next_execution_time() /* Next execution after ends. No more executions */ set_zero_time(&execute_at, MYSQL_TIMESTAMP_DATETIME); execute_at_null= TRUE; - if (on_completion == Event_timed::ON_COMPLETION_DROP) - dropped= true; - status= Event_timed::DISABLED; - status_changed= true; + if (on_completion == Event_queue_element::ON_COMPLETION_DROP) + dropped= TRUE; + status= Event_queue_element::DISABLED; + status_changed= TRUE; } else { @@ -1130,10 +1412,10 @@ Event_timed::compute_next_execution_time() DBUG_PRINT("info", ("Next execution after ENDS. Stop executing.")); set_zero_time(&execute_at, MYSQL_TIMESTAMP_DATETIME); execute_at_null= TRUE; - status= Event_timed::DISABLED; - status_changed= true; - if (on_completion == Event_timed::ON_COMPLETION_DROP) - dropped= true; + status= Event_queue_element::DISABLED; + status_changed= TRUE; + if (on_completion == Event_queue_element::ON_COMPLETION_DROP) + dropped= TRUE; } else { @@ -1147,11 +1429,12 @@ Event_timed::compute_next_execution_time() goto ret; } ret: - DBUG_PRINT("info", ("ret=0")); - DBUG_RETURN(false); + DBUG_PRINT("info", ("ret=0 execute_at=%llu", + TIME_to_ulonglong_datetime(&execute_at))); + DBUG_RETURN(FALSE); err: DBUG_PRINT("info", ("ret=1")); - DBUG_RETURN(true); + DBUG_RETURN(TRUE); } @@ -1160,12 +1443,12 @@ err: time according to thd->query_start(), so the THD's clock. SYNOPSIS - Event_timed::drop() + Event_queue_element::mark_last_executed() thd thread context */ void -Event_timed::mark_last_executed(THD *thd) +Event_queue_element::mark_last_executed(THD *thd) { TIME time_now; @@ -1173,7 +1456,7 @@ Event_timed::mark_last_executed(THD *thd) my_tz_UTC->gmt_sec_to_TIME(&time_now, (my_time_t) thd->query_start()); last_executed= time_now; /* was execute_at */ - last_executed_changed= true; + last_executed_changed= TRUE; } @@ -1181,7 +1464,7 @@ Event_timed::mark_last_executed(THD *thd) Drops the event SYNOPSIS - Event_timed::drop() + Event_queue_element::drop() thd thread context RETURN VALUE @@ -1194,12 +1477,13 @@ Event_timed::mark_last_executed(THD *thd) */ int -Event_timed::drop(THD *thd) +Event_queue_element::drop(THD *thd) { uint tmp= 0; - DBUG_ENTER("Event_timed::drop"); + DBUG_ENTER("Event_queue_element::drop"); - DBUG_RETURN(db_drop_event(thd, this, false, &tmp)); + DBUG_RETURN(Events::get_instance()->drop_event(thd, dbname, name, FALSE, + &tmp, TRUE)); } @@ -1207,12 +1491,12 @@ Event_timed::drop(THD *thd) Saves status and last_executed_at to the disk if changed. SYNOPSIS - Event_timed::update_fields() + Event_queue_element::update_timing_fields() thd - thread context RETURN VALUE 0 OK - EVEX_OPEN_TABLE_FAILED Error while opening mysql.event for writing + EVEX_OPEN_TABLE_FAILED Error while opening mysql.event for writing EVEX_WRITE_ROW_FAILED On error to write to disk others return code from SE in case deletion of the event @@ -1220,13 +1504,13 @@ Event_timed::drop(THD *thd) */ bool -Event_timed::update_fields(THD *thd) +Event_queue_element::update_timing_fields(THD *thd) { TABLE *table; Open_tables_state backup; int ret; - DBUG_ENTER("Event_timed::update_time_fields"); + DBUG_ENTER("Event_queue_element::update_timing_fields"); DBUG_PRINT("enter", ("name: %*s", name.length, name.str)); @@ -1236,14 +1520,14 @@ Event_timed::update_fields(THD *thd) thd->reset_n_backup_open_tables_state(&backup); - if (Events::open_event_table(thd, TL_WRITE, &table)) + if (Events::get_instance()->open_event_table(thd, TL_WRITE, &table)) { ret= EVEX_OPEN_TABLE_FAILED; goto done; } - - if ((ret= evex_db_find_event_by_name(thd, dbname, name, table))) + if ((ret= Events::get_instance()->db_repository-> + find_event_by_name(thd, dbname, name, table))) goto done; store_record(table,record[1]); @@ -1252,16 +1536,16 @@ Event_timed::update_fields(THD *thd) if (last_executed_changed) { - table->field[Events::FIELD_LAST_EXECUTED]->set_notnull(); - table->field[Events::FIELD_LAST_EXECUTED]->store_time(&last_executed, + table->field[ET_FIELD_LAST_EXECUTED]->set_notnull(); + table->field[ET_FIELD_LAST_EXECUTED]->store_time(&last_executed, MYSQL_TIMESTAMP_DATETIME); - last_executed_changed= false; + last_executed_changed= FALSE; } if (status_changed) { - table->field[Events::FIELD_STATUS]->set_notnull(); - table->field[Events::FIELD_STATUS]->store((longlong)status, true); - status_changed= false; + table->field[ET_FIELD_STATUS]->set_notnull(); + table->field[ET_FIELD_STATUS]->store((longlong)status, TRUE); + status_changed= FALSE; } if ((table->file->ha_update_row(table->record[1],table->record[0]))) @@ -1294,8 +1578,8 @@ int Event_timed::get_create_event(THD *thd, String *buf) { int multipl= 0; - char tmp_buff[128]; - String expr_buf(tmp_buff, sizeof(tmp_buff), system_charset_info); + char tmp_buf[2 * STRING_BUFFER_USUAL_SIZE]; + String expr_buf(tmp_buf, sizeof(tmp_buf), system_charset_info); expr_buf.length(0); DBUG_ENTER("get_create_event"); @@ -1352,10 +1636,37 @@ Event_timed::get_create_event(THD *thd, String *buf) /* + Get SHOW CREATE EVENT as string + + SYNOPSIS + Event_job_data::get_create_event(THD *thd, String *buf) + thd Thread + buf String*, should be already allocated. CREATE EVENT goes inside. + + RETURN VALUE + 0 OK + EVEX_MICROSECOND_UNSUP Error (for now if mysql.event has been + tampered and MICROSECONDS interval or + derivative has been put there. +*/ + +int +Event_job_data::get_fake_create_event(THD *thd, String *buf) +{ + DBUG_ENTER("Event_job_data::get_create_event"); + buf->append(STRING_WITH_LEN("CREATE EVENT test.anonymous ON SCHEDULE " + "EVERY 3337 HOUR DO ")); + buf->append(body.str, body.length); + + DBUG_RETURN(0); +} + + +/* Executes the event (the underlying sp_head object); SYNOPSIS - evex_fill_row() + Event_job_data::execute() thd THD mem_root If != NULL use it to compile the event on it @@ -1367,24 +1678,18 @@ Event_timed::get_create_event(THD *thd, String *buf) */ int -Event_timed::execute(THD *thd, MEM_ROOT *mem_root) +Event_job_data::execute(THD *thd, MEM_ROOT *mem_root) { + Security_context *save_ctx; /* this one is local and not needed after exec */ Security_context security_ctx; int ret= 0; - DBUG_ENTER("Event_timed::execute"); - DBUG_PRINT("info", (" EVEX EXECUTING event %s.%s [EXPR:%d]", - dbname.str, name.str, (int) expression)); + DBUG_ENTER("Event_job_data::execute"); + DBUG_PRINT("info", ("EXECUTING %s.%s", dbname.str, name.str)); - VOID(pthread_mutex_lock(&this->LOCK_running)); - if (running) - { - VOID(pthread_mutex_unlock(&this->LOCK_running)); - DBUG_RETURN(-100); - } - running= true; - VOID(pthread_mutex_unlock(&this->LOCK_running)); + thd->change_security_context(definer_user, definer_host, dbname, + &security_ctx, &save_ctx); if (!sphead && (ret= compile(thd, mem_root))) goto done; @@ -1411,14 +1716,11 @@ Event_timed::execute(THD *thd, MEM_ROOT *mem_root) definer_host.str, dbname.str)); ret= -99; } - - VOID(pthread_mutex_lock(&this->LOCK_running)); - running= false; /* Will compile every time a new sp_head on different root */ free_sp(); - VOID(pthread_mutex_unlock(&this->LOCK_running)); done: + thd->restore_security_context(save_ctx); /* 1. Don't cache sphead if allocated on another mem_root 2. Don't call security_ctx.destroy() because this will free our dbname.str @@ -1429,8 +1731,7 @@ done: delete sphead; sphead= 0; } - DBUG_PRINT("info", (" EVEX EXECUTED event %s.%s [EXPR:%d]. RetCode=%d", - dbname.str, name.str, (int) expression, ret)); + DBUG_PRINT("info", ("EXECUTED %s.%s ret=%d", dbname.str, name.str, ret)); DBUG_RETURN(ret); } @@ -1439,14 +1740,14 @@ done: /* Frees the memory of the sp_head object we hold SYNOPSIS - Event_timed::free_sp() + Event_job_data::free_sp() */ void -Event_timed::free_sp() +Event_job_data::free_sp() { delete sphead; - sphead= 0; + sphead= NULL; } @@ -1455,7 +1756,7 @@ Event_timed::free_sp() sp_head object held by the event SYNOPSIS - Event_timed::compile() + Event_job_data::compile() thd thread context, used for memory allocation mostly mem_root if != NULL then this memory root is used for allocs instead of thd->mem_root @@ -1467,7 +1768,7 @@ Event_timed::free_sp() */ int -Event_timed::compile(THD *thd, MEM_ROOT *mem_root) +Event_job_data::compile(THD *thd, MEM_ROOT *mem_root) { int ret= 0; MEM_ROOT *tmp_mem_root= 0; @@ -1477,7 +1778,7 @@ Event_timed::compile(THD *thd, MEM_ROOT *mem_root) char *old_query; uint old_query_len; ulong old_sql_mode= thd->variables.sql_mode; - char create_buf[2048]; + char create_buf[15 * STRING_BUFFER_USUAL_SIZE]; String show_create(create_buf, sizeof(create_buf), system_charset_info); CHARSET_INFO *old_character_set_client, *old_collation_connection, @@ -1486,11 +1787,11 @@ Event_timed::compile(THD *thd, MEM_ROOT *mem_root) /* this one is local and not needed after exec */ Security_context security_ctx; - DBUG_ENTER("Event_timed::compile"); + DBUG_ENTER("Event_job_data::compile"); show_create.length(0); - switch (get_create_event(thd, &show_create)) { + switch (get_fake_create_event(thd, &show_create)) { case EVEX_MICROSECOND_UNSUP: sql_print_error("Scheduler"); DBUG_RETURN(EVEX_MICROSECOND_UNSUP); @@ -1526,15 +1827,14 @@ Event_timed::compile(THD *thd, MEM_ROOT *mem_root) thd->db= dbname.str; thd->db_length= dbname.length; - thd->query= show_create.c_ptr(); + thd->query= show_create.c_ptr_safe(); thd->query_length= show_create.length(); DBUG_PRINT("info", ("query:%s",thd->query)); - change_security_context(thd, definer_user, definer_host, dbname, - &security_ctx, &save_ctx); + thd->change_security_context(definer_user, definer_host, dbname, + &security_ctx, &save_ctx); thd->lex= &lex; lex_start(thd, (uchar*)thd->query, thd->query_length); - lex.et_compile_phase= TRUE; if (MYSQLparse((void *)thd) || thd->is_fatal_error) { DBUG_PRINT("error", ("error during compile or thd->is_fatal_error=%d", @@ -1557,7 +1857,7 @@ Event_timed::compile(THD *thd, MEM_ROOT *mem_root) } DBUG_PRINT("note", ("success compiling %s.%s", dbname.str, name.str)); - sphead= lex.et->sphead; + sphead= lex.sphead; sphead->m_db= dbname; sphead->set_definer(definer.str, definer.length); @@ -1565,11 +1865,9 @@ Event_timed::compile(THD *thd, MEM_ROOT *mem_root) sphead->optimize(); ret= 0; done: - lex.et->free_sphead_on_delete= false; - lex.et->deinit_mutexes(); lex_end(&lex); - restore_security_context(thd, save_ctx); + thd->restore_security_context(save_ctx); DBUG_PRINT("note", ("return old data on its place. set back NAMES")); thd->lex= old_lex; @@ -1591,276 +1889,38 @@ done: } -extern pthread_attr_t connection_attrib; - -/* - Checks whether is possible and forks a thread. Passes self as argument. - - RETURN VALUE - EVENT_EXEC_STARTED OK - EVENT_EXEC_ALREADY_EXEC Thread not forked, already working - EVENT_EXEC_CANT_FORK Unable to spawn thread (error) -*/ - -int -Event_timed::spawn_now(void * (*thread_func)(void*), void *arg) -{ - THD *thd= current_thd; - int ret= EVENT_EXEC_STARTED; - DBUG_ENTER("Event_timed::spawn_now"); - DBUG_PRINT("info", ("[%s.%s]", dbname.str, name.str)); - - VOID(pthread_mutex_lock(&this->LOCK_running)); - - DBUG_PRINT("info", ("SCHEDULER: execute_at of %s is %lld", name.str, - TIME_to_ulonglong_datetime(&execute_at))); - mark_last_executed(thd); - if (compute_next_execution_time()) - { - sql_print_error("SCHEDULER: Error while computing time of %s.%s . " - "Disabling after execution.", dbname.str, name.str); - status= DISABLED; - } - DBUG_PRINT("evex manager", ("[%10s] next exec at [%llu]", name.str, - TIME_to_ulonglong_datetime(&execute_at))); - /* - 1. For one-time event : year is > 0 and expression is 0 - 2. For recurring, expression is != -=> check execute_at_null in this case - */ - if ((execute_at.year && !expression) || execute_at_null) - { - sql_print_information("SCHEDULER: [%s.%s of %s] no more executions " - "after this one", dbname.str, name.str, - definer.str); - flags |= EVENT_EXEC_NO_MORE | EVENT_FREE_WHEN_FINISHED; - } - - update_fields(thd); - - if (!in_spawned_thread) - { - pthread_t th; - in_spawned_thread= true; - - if (pthread_create(&th, &connection_attrib, thread_func, arg)) - { - DBUG_PRINT("info", ("problem while spawning thread")); - ret= EVENT_EXEC_CANT_FORK; - in_spawned_thread= false; - } - } - else - { - DBUG_PRINT("info", ("already in spawned thread. skipping")); - ret= EVENT_EXEC_ALREADY_EXEC; - } - VOID(pthread_mutex_unlock(&this->LOCK_running)); - - DBUG_RETURN(ret); -} - - -bool -Event_timed::spawn_thread_finish(THD *thd) -{ - bool should_free; - DBUG_ENTER("Event_timed::spawn_thread_finish"); - VOID(pthread_mutex_lock(&LOCK_running)); - in_spawned_thread= false; - DBUG_PRINT("info", ("Sending COND_finished for thread %d", thread_id)); - thread_id= 0; - if (dropped) - drop(thd); - pthread_cond_broadcast(&COND_finished); - should_free= flags & EVENT_FREE_WHEN_FINISHED; - VOID(pthread_mutex_unlock(&LOCK_running)); - DBUG_RETURN(should_free); -} - - -/* - Kills a running event - SYNOPSIS - Event_timed::kill_thread() - - RETURN VALUE - 0 OK - -1 EVEX_CANT_KILL - !0 Error -*/ - -int -Event_timed::kill_thread(THD *thd) -{ - int ret= 0; - DBUG_ENTER("Event_timed::kill_thread"); - pthread_mutex_lock(&LOCK_running); - DBUG_PRINT("info", ("thread_id=%lu", thread_id)); - - if (thread_id == thd->thread_id) - { - /* - We don't kill ourselves in cases like : - alter event e_43 do alter event e_43 do set @a = 4 because - we will never receive COND_finished. - */ - DBUG_PRINT("info", ("It's not safe to kill ourselves in self altering queries")); - ret= EVEX_CANT_KILL; - } - else if (thread_id && !(ret= kill_one_thread(thd, thread_id, false))) - { - thd->enter_cond(&COND_finished, &LOCK_running, "Waiting for finished"); - DBUG_PRINT("info", ("Waiting for COND_finished from thread %d", thread_id)); - while (thread_id) - pthread_cond_wait(&COND_finished, &LOCK_running); - - DBUG_PRINT("info", ("Got COND_finished")); - /* This will implicitly unlock LOCK_running. Hence we return before that */ - thd->exit_cond(""); - - DBUG_RETURN(0); - } - else if (!thread_id && in_spawned_thread) - { - /* - Because the manager thread waits for the forked thread to update thread_id - this situation is impossible. - */ - DBUG_ASSERT(0); - } - pthread_mutex_unlock(&LOCK_running); - DBUG_PRINT("exit", ("%d", ret)); - DBUG_RETURN(ret); -} - - -/* - Checks whether two events have the same name - - SYNOPSIS - event_timed_name_equal() - - RETURN VALUE - TRUE names are equal - FALSE names are not equal -*/ - -bool -event_timed_name_equal(Event_timed *et, LEX_STRING *name) -{ - return !sortcmp_lex_string(et->name, *name, system_charset_info); -} - - /* Checks whether two events are in the same schema SYNOPSIS - event_timed_db_equal() + event_basic_db_equal() RETURN VALUE - TRUE schemas are equal - FALSE schemas are not equal + TRUE Equal + FALSE Not equal */ bool -event_timed_db_equal(Event_timed *et, LEX_STRING *db) +event_basic_db_equal(LEX_STRING *db, Event_basic *et) { return !sortcmp_lex_string(et->dbname, *db, system_charset_info); } /* - Checks whether two events have the same definer - - SYNOPSIS - event_timed_definer_equal() - - Returns - TRUE definers are equal - FALSE definers are not equal -*/ - -bool -event_timed_definer_equal(Event_timed *et, LEX_STRING *definer) -{ - return !sortcmp_lex_string(et->definer, *definer, system_charset_info); -} - - -/* Checks whether two events are equal by identifiers SYNOPSIS - event_timed_identifier_equal() + event_basic_identifier_equal() RETURN VALUE - TRUE equal - FALSE not equal + TRUE Equal + FALSE Not equal */ bool -event_timed_identifier_equal(Event_timed *a, Event_timed *b) +event_basic_identifier_equal(LEX_STRING db, LEX_STRING name, Event_basic *b) { - return event_timed_name_equal(a, &b->name) && - event_timed_db_equal(a, &b->dbname) && - event_timed_definer_equal(a, &b->definer); -} - - -/* - Switches the security context - SYNOPSIS - change_security_context() - thd Thread - user The user - host The host of the user - db The schema for which the security_ctx will be loaded - s_ctx Security context to load state into - backup Where to store the old context - - RETURN VALUE - 0 - OK - 1 - Error (generates error too) -*/ - -bool -change_security_context(THD *thd, LEX_STRING user, LEX_STRING host, - LEX_STRING db, Security_context *s_ctx, - Security_context **backup) -{ - DBUG_ENTER("change_security_context"); - DBUG_PRINT("info",("%s@%s@%s", user.str, host.str, db.str)); -#ifndef NO_EMBEDDED_ACCESS_CHECKS - s_ctx->init(); - *backup= 0; - if (acl_getroot_no_password(s_ctx, user.str, host.str, host.str, db.str)) - { - my_error(ER_NO_SUCH_USER, MYF(0), user.str, host.str); - DBUG_RETURN(TRUE); - } - *backup= thd->security_ctx; - thd->security_ctx= s_ctx; -#endif - DBUG_RETURN(FALSE); -} - - -/* - Restores the security context - SYNOPSIS - restore_security_context() - thd - thread - backup - switch to this context -*/ - -void -restore_security_context(THD *thd, Security_context *backup) -{ - DBUG_ENTER("restore_security_context"); -#ifndef NO_EMBEDDED_ACCESS_CHECKS - if (backup) - thd->security_ctx= backup; -#endif - DBUG_VOID_RETURN; + return !sortcmp_lex_string(name, b->name, system_charset_info) && + !sortcmp_lex_string(db, b->dbname, system_charset_info); } diff --git a/sql/event_timed.h b/sql/event_data_objects.h index 0652cece361..d405cafe42d 100644 --- a/sql/event_timed.h +++ b/sql/event_data_objects.h @@ -1,5 +1,5 @@ -#ifndef _EVENT_TIMED_H_ -#define _EVENT_TIMED_H_ +#ifndef _EVENT_DATA_OBJECTS_H_ +#define _EVENT_DATA_OBJECTS_H_ /* Copyright (C) 2004-2006 MySQL AB This program is free software; you can redistribute it and/or modify @@ -40,19 +40,49 @@ #define EVENT_FREE_WHEN_FINISHED (1L << 2) +#define EVENT_EXEC_STARTED 0 +#define EVENT_EXEC_ALREADY_EXEC 1 +#define EVENT_EXEC_CANT_FORK 2 + + class sp_head; +class Sql_alloc; +class Event_basic; + +/* Compares only the schema part of the identifier */ +bool +event_basic_db_equal( LEX_STRING *db, Event_basic *et); -class Event_timed +/* Compares the whole identifier*/ +bool +event_basic_identifier_equal(LEX_STRING db, LEX_STRING name, Event_basic *b); + +class Event_basic { - Event_timed(const Event_timed &); /* Prevent use of these */ - void operator=(Event_timed &); - my_bool in_spawned_thread; - ulong locked_by_thread_id; - my_bool running; - ulong thread_id; - pthread_mutex_t LOCK_running; - pthread_cond_t COND_finished; +protected: + MEM_ROOT mem_root; +public: + LEX_STRING dbname; + LEX_STRING name; + LEX_STRING definer;// combination of user and host + + Event_basic(); + virtual ~Event_basic(); + + virtual int + load_from_row(TABLE *table) = 0; + +protected: + bool + load_string_fields(Field **fields, ...); +}; + + + +class Event_queue_element : public Event_basic +{ +protected: bool status_changed; bool last_executed_changed; @@ -69,17 +99,10 @@ public: ON_COMPLETION_PRESERVE }; + enum enum_on_completion on_completion; + enum enum_status status; TIME last_executed; - LEX_STRING dbname; - LEX_STRING name; - LEX_STRING body; - - LEX_STRING definer_user; - LEX_STRING definer_host; - LEX_STRING definer;// combination of user and host - - LEX_STRING comment; TIME starts; TIME ends; TIME execute_at; @@ -90,128 +113,194 @@ public: longlong expression; interval_type interval; - ulonglong created; - ulonglong modified; - enum enum_on_completion on_completion; - enum enum_status status; - sp_head *sphead; - ulong sql_mode; - const uchar *body_begin; + uint flags;//all kind of purposes bool dropped; - bool free_sphead_on_delete; - uint flags;//all kind of purposes + + Event_queue_element(); + virtual ~Event_queue_element(); + + virtual int + load_from_row(TABLE *table); + + bool + compute_next_execution_time(); + + int + drop(THD *thd); + + void + mark_last_executed(THD *thd); + + bool + update_timing_fields(THD *thd); static void *operator new(size_t size) { void *p; - DBUG_ENTER("Event_timed::new(size)"); + DBUG_ENTER("Event_queue_element::new(size)"); p= my_malloc(size, MYF(0)); DBUG_PRINT("info", ("alloc_ptr=0x%lx", p)); DBUG_RETURN(p); } - static void *operator new(size_t size, MEM_ROOT *mem_root) - { return (void*) alloc_root(mem_root, (uint) size); } - static void operator delete(void *ptr, size_t size) { - DBUG_ENTER("Event_timed::delete(ptr,size)"); + DBUG_ENTER("Event_queue_element::delete(ptr,size)"); DBUG_PRINT("enter", ("free_ptr=0x%lx", ptr)); TRASH(ptr, size); my_free((gptr) ptr, MYF(0)); DBUG_VOID_RETURN; } +}; - static void operator delete(void *ptr, MEM_ROOT *mem_root) - { - /* - Don't free the memory it will be done by the mem_root but - we need to call the destructor because we free other resources - which are not allocated on the root but on the heap, or we - deinit mutexes. - */ - DBUG_ASSERT(0); - } - Event_timed(); +class Event_timed : public Event_queue_element +{ + Event_timed(const Event_timed &); /* Prevent use of these */ + void operator=(Event_timed &); + +public: + LEX_STRING body; + + LEX_STRING definer_user; + LEX_STRING definer_host; + + LEX_STRING comment; + + ulonglong created; + ulonglong modified; - ~Event_timed(); + ulong sql_mode; + + Event_timed(); + virtual ~Event_timed(); void init(); - void - deinit_mutexes(); + virtual int + load_from_row(TABLE *table); int - init_definer(THD *thd); + get_create_event(THD *thd, String *buf); +}; - int - init_execute_at(THD *thd, Item *expr); - int - init_interval(THD *thd, Item *expr, interval_type new_interval); +class Event_job_data : public Event_basic +{ +public: + THD *thd; + sp_head *sphead; - void - init_name(THD *thd, sp_name *spn); + LEX_STRING body; + LEX_STRING definer_user; + LEX_STRING definer_host; + + ulong sql_mode; + + Event_job_data(); + virtual ~Event_job_data(); + virtual int + load_from_row(TABLE *table); + + int + execute(THD *thd, MEM_ROOT *mem_root); +private: int - init_starts(THD *thd, Item *starts); + get_fake_create_event(THD *thd, String *buf); int - init_ends(THD *thd, Item *ends); + compile(THD *thd, MEM_ROOT *mem_root); void - init_body(THD *thd); + free_sp(); - void - init_comment(THD *thd, LEX_STRING *set_comment); + Event_job_data(const Event_job_data &); /* Prevent use of these */ + void operator=(Event_job_data &); +}; - int - load_from_row(MEM_ROOT *mem_root, TABLE *table); + +class Event_parse_data : public Sql_alloc +{ +public: + enum enum_status + { + ENABLED = 1, + DISABLED + }; + + enum enum_on_completion + { + ON_COMPLETION_DROP = 1, + ON_COMPLETION_PRESERVE + }; + enum enum_on_completion on_completion; + enum enum_status status; + + const uchar *body_begin; + + LEX_STRING dbname; + LEX_STRING name; + LEX_STRING definer;// combination of user and host + LEX_STRING body; + LEX_STRING comment; + + Item* item_starts; + Item* item_ends; + Item* item_execute_at; + + TIME starts; + TIME ends; + TIME execute_at; + my_bool starts_null; + my_bool ends_null; + my_bool execute_at_null; + + sp_name *identifier; + Item* item_expression; + longlong expression; + interval_type interval; + + static Event_parse_data * + new_instance(THD *thd); bool - compute_next_execution_time(); + check_parse_data(THD *); + + void + init_body(THD *thd); + +private: int - drop(THD *thd); + init_definer(THD *thd); void - mark_last_executed(THD *thd); + init_name(THD *thd, sp_name *spn); - bool - update_fields(THD *thd); + int + init_execute_at(THD *thd); int - get_create_event(THD *thd, String *buf); + init_interval(THD *thd); int - execute(THD *thd, MEM_ROOT *mem_root); + init_starts(THD *thd); int - compile(THD *thd, MEM_ROOT *mem_root); + init_ends(THD *thd); - bool - is_running(); + Event_parse_data(); + ~Event_parse_data(); - int - spawn_now(void * (*thread_func)(void*), void *arg); - - bool - spawn_thread_finish(THD *thd); - void - free_sp(); + report_bad_value(const char *item_name, Item *bad_item); - bool - has_equal_db(Event_timed *etn); + Event_parse_data(const Event_parse_data &); /* Prevent use of these */ + void operator=(Event_parse_data &); +}; - int - kill_thread(THD *thd); - void - set_thread_id(ulong tid) { thread_id= tid; } -}; - -#endif /* _EVENT_H_ */ +#endif /* _EVENT_DATA_OBJECTS_H_ */ diff --git a/sql/event_db_repository.cc b/sql/event_db_repository.cc new file mode 100644 index 00000000000..22dcdd97a48 --- /dev/null +++ b/sql/event_db_repository.cc @@ -0,0 +1,1040 @@ +/* Copyright (C) 2004-2006 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "mysql_priv.h" +#include "event_db_repository.h" +#include "event_data_objects.h" +#include "sp_head.h" +#include "sp.h" +#include "events.h" +#include "sql_show.h" + +#define EVEX_DB_FIELD_LEN 64 +#define EVEX_NAME_FIELD_LEN 64 + + +time_t mysql_event_last_create_time= 0L; + +static +TABLE_FIELD_W_TYPE event_table_fields[ET_FIELD_COUNT] = +{ + { + {(char *) STRING_WITH_LEN("db")}, + {(char *) STRING_WITH_LEN("char(64)")}, + {(char *) STRING_WITH_LEN("utf8")} + }, + { + {(char *) STRING_WITH_LEN("name")}, + {(char *) STRING_WITH_LEN("char(64)")}, + {(char *) STRING_WITH_LEN("utf8")} + }, + { + {(char *) STRING_WITH_LEN("body")}, + {(char *) STRING_WITH_LEN("longblob")}, + {NULL, 0} + }, + { + {(char *) STRING_WITH_LEN("definer")}, + {(char *) STRING_WITH_LEN("char(77)")}, + {(char *) STRING_WITH_LEN("utf8")} + }, + { + {(char *) STRING_WITH_LEN("execute_at")}, + {(char *) STRING_WITH_LEN("datetime")}, + {NULL, 0} + }, + { + {(char *) STRING_WITH_LEN("interval_value")}, + {(char *) STRING_WITH_LEN("int(11)")}, + {NULL, 0} + }, + { + {(char *) STRING_WITH_LEN("interval_field")}, + {(char *) STRING_WITH_LEN("enum('YEAR','QUARTER','MONTH','DAY'," + "'HOUR','MINUTE','WEEK','SECOND','MICROSECOND','YEAR_MONTH','DAY_HOUR'," + "'DAY_MINUTE','DAY_SECOND','HOUR_MINUTE','HOUR_SECOND','MINUTE_SECOND'," + "'DAY_MICROSECOND','HOUR_MICROSECOND','MINUTE_MICROSECOND'," + "'SECOND_MICROSECOND')")}, + {NULL, 0} + }, + { + {(char *) STRING_WITH_LEN("created")}, + {(char *) STRING_WITH_LEN("timestamp")}, + {NULL, 0} + }, + { + {(char *) STRING_WITH_LEN("modified")}, + {(char *) STRING_WITH_LEN("timestamp")}, + {NULL, 0} + }, + { + {(char *) STRING_WITH_LEN("last_executed")}, + {(char *) STRING_WITH_LEN("datetime")}, + {NULL, 0} + }, + { + {(char *) STRING_WITH_LEN("starts")}, + {(char *) STRING_WITH_LEN("datetime")}, + {NULL, 0} + }, + { + {(char *) STRING_WITH_LEN("ends")}, + {(char *) STRING_WITH_LEN("datetime")}, + {NULL, 0} + }, + { + {(char *) STRING_WITH_LEN("status")}, + {(char *) STRING_WITH_LEN("enum('ENABLED','DISABLED')")}, + {NULL, 0} + }, + { + {(char *) STRING_WITH_LEN("on_completion")}, + {(char *) STRING_WITH_LEN("enum('DROP','PRESERVE')")}, + {NULL, 0} + }, + { + {(char *) STRING_WITH_LEN("sql_mode")}, + {(char *) STRING_WITH_LEN("set('REAL_AS_FLOAT','PIPES_AS_CONCAT','ANSI_QUOTES'," + "'IGNORE_SPACE','NOT_USED','ONLY_FULL_GROUP_BY','NO_UNSIGNED_SUBTRACTION'," + "'NO_DIR_IN_CREATE','POSTGRESQL','ORACLE','MSSQL','DB2','MAXDB'," + "'NO_KEY_OPTIONS','NO_TABLE_OPTIONS','NO_FIELD_OPTIONS','MYSQL323','MYSQL40'," + "'ANSI','NO_AUTO_VALUE_ON_ZERO','NO_BACKSLASH_ESCAPES','STRICT_TRANS_TABLES'," + "'STRICT_ALL_TABLES','NO_ZERO_IN_DATE','NO_ZERO_DATE','INVALID_DATES'," + "'ERROR_FOR_DIVISION_BY_ZERO','TRADITIONAL','NO_AUTO_CREATE_USER'," + "'HIGH_NOT_PRECEDENCE')")}, + {NULL, 0} + }, + { + {(char *) STRING_WITH_LEN("comment")}, + {(char *) STRING_WITH_LEN("char(64)")}, + {(char *) STRING_WITH_LEN("utf8")} + } +}; + + +/* + Puts some data common to CREATE and ALTER EVENT into a row. + + SYNOPSIS + mysql_event_fill_row() + thd THD + table The row to fill out + et Event's data + is_update CREATE EVENT or ALTER EVENT + + RETURN VALUE + 0 OK + EVEX_GENERAL_ERROR Bad data + EVEX_GET_FIELD_FAILED Field count does not match. table corrupted? + + DESCRIPTION + Used both when an event is created and when it is altered. +*/ + +static int +mysql_event_fill_row(THD *thd, TABLE *table, Event_parse_data *et, + my_bool is_update) +{ + CHARSET_INFO *scs= system_charset_info; + enum enum_events_table_field f_num; + Field **fields= table->field; + + DBUG_ENTER("mysql_event_fill_row"); + + DBUG_PRINT("info", ("dbname=[%s]", et->dbname.str)); + DBUG_PRINT("info", ("name =[%s]", et->name.str)); + DBUG_PRINT("info", ("body =[%s]", et->body.str)); + + if (fields[f_num= ET_FIELD_DEFINER]-> + store(et->definer.str, et->definer.length, scs)) + goto err_truncate; + + if (fields[f_num= ET_FIELD_DB]->store(et->dbname.str, et->dbname.length, scs)) + goto err_truncate; + + if (fields[f_num= ET_FIELD_NAME]->store(et->name.str, et->name.length, scs)) + goto err_truncate; + + /* both ON_COMPLETION and STATUS are NOT NULL thus not calling set_notnull()*/ + fields[ET_FIELD_ON_COMPLETION]->store((longlong)et->on_completion, TRUE); + + fields[ET_FIELD_STATUS]->store((longlong)et->status, TRUE); + + /* + Change the SQL_MODE only if body was present in an ALTER EVENT and of course + always during CREATE EVENT. + */ + if (et->body.str) + { + fields[ET_FIELD_SQL_MODE]->store((longlong)thd->variables.sql_mode, TRUE); + if (fields[f_num= ET_FIELD_BODY]->store(et->body.str, et->body.length, scs)) + goto err_truncate; + } + + if (et->expression) + { + fields[ET_FIELD_INTERVAL_EXPR]->set_notnull(); + fields[ET_FIELD_INTERVAL_EXPR]->store((longlong)et->expression, TRUE); + + fields[ET_FIELD_TRANSIENT_INTERVAL]->set_notnull(); + /* + In the enum (C) intervals start from 0 but in mysql enum valid values + start from 1. Thus +1 offset is needed! + */ + fields[ET_FIELD_TRANSIENT_INTERVAL]->store((longlong)et->interval+1, TRUE); + + fields[ET_FIELD_EXECUTE_AT]->set_null(); + + if (!et->starts_null) + { + fields[ET_FIELD_STARTS]->set_notnull(); + fields[ET_FIELD_STARTS]->store_time(&et->starts, MYSQL_TIMESTAMP_DATETIME); + } + + if (!et->ends_null) + { + fields[ET_FIELD_ENDS]->set_notnull(); + fields[ET_FIELD_ENDS]->store_time(&et->ends, MYSQL_TIMESTAMP_DATETIME); + } + } + else if (et->execute_at.year) + { + fields[ET_FIELD_INTERVAL_EXPR]->set_null(); + fields[ET_FIELD_TRANSIENT_INTERVAL]->set_null(); + fields[ET_FIELD_STARTS]->set_null(); + fields[ET_FIELD_ENDS]->set_null(); + + fields[ET_FIELD_EXECUTE_AT]->set_notnull(); + fields[ET_FIELD_EXECUTE_AT]-> + store_time(&et->execute_at, MYSQL_TIMESTAMP_DATETIME); + } + else + { + DBUG_ASSERT(is_update); + /* + it is normal to be here when the action is update + this is an error if the action is create. something is borked + */ + } + + ((Field_timestamp *)fields[ET_FIELD_MODIFIED])->set_time(); + + if (et->comment.str) + { + if (fields[f_num= ET_FIELD_COMMENT]-> + store(et->comment.str, et->comment.length, scs)) + goto err_truncate; + } + + DBUG_RETURN(0); + +err_truncate: + my_error(ER_EVENT_DATA_TOO_LONG, MYF(0), fields[f_num]->field_name); + DBUG_RETURN(EVEX_GENERAL_ERROR); +} + + +/* + Performs an index scan of event_table (mysql.event) and fills schema_table. + + Synopsis + Event_db_repository::index_read_for_db_for_i_s() + thd Thread + schema_table The I_S.EVENTS table + event_table The event table to use for loading (mysql.event) + + Returns + 0 OK + 1 Error +*/ + +int +Event_db_repository::index_read_for_db_for_i_s(THD *thd, TABLE *schema_table, + TABLE *event_table, char *db) +{ + int ret=0; + CHARSET_INFO *scs= system_charset_info; + KEY *key_info; + uint key_len; + byte *key_buf= NULL; + LINT_INIT(key_buf); + + DBUG_ENTER("Event_db_repository::index_read_for_db_for_i_s"); + + DBUG_PRINT("info", ("Using prefix scanning on PK")); + event_table->file->ha_index_init(0, 1); + event_table->field[ET_FIELD_DB]->store(db, strlen(db), scs); + key_info= event_table->key_info; + key_len= key_info->key_part[0].store_length; + + if (!(key_buf= (byte *)alloc_root(thd->mem_root, key_len))) + { + ret= 1; + /* Don't send error, it would be done by sql_alloc_error_handler() */ + } + else + { + key_copy(key_buf, event_table->record[0], key_info, key_len); + if (!(ret= event_table->file->index_read(event_table->record[0], key_buf, + key_len, HA_READ_PREFIX))) + { + DBUG_PRINT("info",("Found rows. Let's retrieve them. ret=%d", ret)); + do + { + ret= copy_event_to_schema_table(thd, schema_table, event_table); + if (ret == 0) + ret= event_table->file->index_next_same(event_table->record[0], + key_buf, key_len); + } while (ret == 0); + } + DBUG_PRINT("info", ("Scan finished. ret=%d", ret)); + } + event_table->file->ha_index_end(); + /* ret is guaranteed to be != 0 */ + if (ret == HA_ERR_END_OF_FILE || ret == HA_ERR_KEY_NOT_FOUND) + DBUG_RETURN(0); + DBUG_RETURN(1); +} + + +/* + Performs a table scan of event_table (mysql.event) and fills schema_table. + + Synopsis + Events_db_repository::table_scan_all_for_i_s() + thd Thread + schema_table The I_S.EVENTS in memory table + event_table The event table to use for loading. + + Returns + 0 OK + 1 Error +*/ + +int +Event_db_repository::table_scan_all_for_i_s(THD *thd, TABLE *schema_table, + TABLE *event_table) +{ + int ret; + READ_RECORD read_record_info; + + DBUG_ENTER("Event_db_repository::table_scan_all_for_i_s"); + init_read_record(&read_record_info, thd, event_table, NULL, 1, 0); + + /* + rr_sequential, in read_record(), returns 137==HA_ERR_END_OF_FILE, + but rr_handle_error returns -1 for that reason. Thus, read_record() + returns -1 eventually. + */ + do + { + ret= read_record_info.read_record(&read_record_info); + if (ret == 0) + ret= copy_event_to_schema_table(thd, schema_table, event_table); + } while (ret == 0); + + DBUG_PRINT("info", ("Scan finished. ret=%d", ret)); + end_read_record(&read_record_info); + + /* ret is guaranteed to be != 0 */ + DBUG_RETURN(ret == -1? 0:1); +} + + +/* + Fills I_S.EVENTS with data loaded from mysql.event. Also used by + SHOW EVENTS + + Synopsis + Event_db_repository::fill_schema_events() + thd Thread + tables The schema table + db If not NULL then get events only from this schema + + Returns + 0 OK + 1 Error +*/ + +int +Event_db_repository::fill_schema_events(THD *thd, TABLE_LIST *tables, char *db) +{ + TABLE *schema_table= tables->table; + TABLE *event_table= NULL; + Open_tables_state backup; + int ret= 0; + + DBUG_ENTER("Event_db_repository::fill_schema_events"); + DBUG_PRINT("info",("db=%s", db? db:"(null)")); + + thd->reset_n_backup_open_tables_state(&backup); + if (open_event_table(thd, TL_READ, &event_table)) + { + sql_print_error("Table mysql.event is damaged."); + thd->restore_backup_open_tables_state(&backup); + DBUG_RETURN(1); + } + + /* + 1. SELECT I_S => use table scan. I_S.EVENTS does not guarantee order + thus we won't order it. OTOH, SHOW EVENTS will be + ordered. + 2. SHOW EVENTS => PRIMARY KEY with prefix scanning on (db) + Reasoning: Events are per schema, therefore a scan over an index + will save use from doing a table scan and comparing + every single row's `db` with the schema which we show. + */ + if (db) + ret= index_read_for_db_for_i_s(thd, schema_table, event_table, db); + else + ret= table_scan_all_for_i_s(thd, schema_table, event_table); + + close_thread_tables(thd); + thd->restore_backup_open_tables_state(&backup); + + DBUG_PRINT("info", ("Return code=%d", ret)); + DBUG_RETURN(ret); +} + + +/* + Open mysql.event table for read + + SYNOPSIS + Events::open_event_table() + thd [in] Thread context + lock_type [in] How to lock the table + table [out] We will store the open table here + + RETURN VALUE + 1 Cannot lock table + 2 The table is corrupted - different number of fields + 0 OK +*/ + +int +Event_db_repository::open_event_table(THD *thd, enum thr_lock_type lock_type, + TABLE **table) +{ + TABLE_LIST tables; + DBUG_ENTER("Event_db_repository::open_event_table"); + + bzero((char*) &tables, sizeof(tables)); + tables.db= (char*) "mysql"; + tables.table_name= tables.alias= (char*) "event"; + tables.lock_type= lock_type; + + if (simple_open_n_lock_tables(thd, &tables)) + DBUG_RETURN(1); + + if (table_check_intact(tables.table, ET_FIELD_COUNT, + event_table_fields, + &mysql_event_last_create_time, + ER_CANNOT_LOAD_FROM_TABLE)) + { + close_thread_tables(thd); + DBUG_RETURN(2); + } + *table= tables.table; + tables.table->use_all_columns(); + DBUG_RETURN(0); +} + + +/* + Checks parameters which we got from the parsing phase. + + SYNOPSIS + check_parse_params() + thd THD + et event's data + + RETURNS + 0 OK + EVEX_BAD_PARAMS Error (reported) +*/ + +static int +check_parse_params(THD *thd, Event_parse_data *parse_data) +{ + const char *pos= NULL; + Item *bad_item; + int res; + + DBUG_ENTER("check_parse_params"); + + if (parse_data->check_parse_data(thd)) + DBUG_RETURN(EVEX_BAD_PARAMS); + + if (!parse_data->dbname.str || + (thd->lex->sql_command == SQLCOM_ALTER_EVENT && thd->lex->spname && + !thd->lex->spname->m_db.str)) + { + my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR), MYF(0)); + DBUG_RETURN(EVEX_BAD_PARAMS); + } + + if (check_access(thd, EVENT_ACL, parse_data->dbname.str, 0, 0, 0, + is_schema_db(parse_data->dbname.str)) || + (thd->lex->sql_command == SQLCOM_ALTER_EVENT && thd->lex->spname && + (check_access(thd, EVENT_ACL, thd->lex->spname->m_db.str, 0, 0, 0, + is_schema_db(thd->lex->spname->m_db.str))))) + DBUG_RETURN(EVEX_BAD_PARAMS); + + DBUG_RETURN(0); +} + + +/* + Creates an event in mysql.event + + SYNOPSIS + Event_db_repository::create_event() + thd [in] THD + et [in] Object containing info about the event + create_if_not [in] Whether to generate anwarning in case event exists + rows_affected [out] How many rows were affected + + RETURN VALUE + 0 - OK + EVEX_GENERAL_ERROR - Failure + + DESCRIPTION + Creates an event. Relies on mysql_event_fill_row which is shared with + ::update_event. The name of the event is inside "et". +*/ + +int +Event_db_repository::create_event(THD *thd, Event_parse_data *parse_data, + my_bool create_if_not, uint *rows_affected) +{ + int ret= 0; + CHARSET_INFO *scs= system_charset_info; + TABLE *table; + char olddb[128]; + bool dbchanged= FALSE; + DBUG_ENTER("Event_db_repository::create_event"); + + *rows_affected= 0; + DBUG_PRINT("info", ("open mysql.event for update")); + if (open_event_table(thd, TL_WRITE, &table)) + { + my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0)); + goto err; + } + + if (check_parse_params(thd, parse_data)) + goto err; + + DBUG_PRINT("info", ("name: %.*s", parse_data->name.length, + parse_data->name.str)); + + DBUG_PRINT("info", ("check existance of an event with the same name")); + if (!find_event_by_name(thd, parse_data->dbname, parse_data->name, table)) + { + if (create_if_not) + { + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + ER_EVENT_ALREADY_EXISTS, ER(ER_EVENT_ALREADY_EXISTS), + parse_data->name.str); + goto ok; + } + my_error(ER_EVENT_ALREADY_EXISTS, MYF(0), parse_data->name.str); + goto err; + } + + DBUG_PRINT("info", ("non-existant, go forward")); + if ((ret= sp_use_new_db(thd, parse_data->dbname.str, olddb, sizeof(olddb), 0, + &dbchanged))) + { + my_error(ER_BAD_DB_ERROR, MYF(0)); + goto err; + } + + restore_record(table, s->default_values); // Get default values for fields + + if (system_charset_info->cset-> + numchars(system_charset_info, parse_data->dbname.str, + parse_data->dbname.str + + parse_data->dbname.length) > EVEX_DB_FIELD_LEN) + { + my_error(ER_TOO_LONG_IDENT, MYF(0), parse_data->dbname.str); + goto err; + } + if (system_charset_info->cset-> + numchars(system_charset_info, parse_data->name.str, + parse_data->name.str + + parse_data->name.length) > EVEX_DB_FIELD_LEN) + { + my_error(ER_TOO_LONG_IDENT, MYF(0), parse_data->name.str); + goto err; + } + + if (parse_data->body.length > table->field[ET_FIELD_BODY]->field_length) + { + my_error(ER_TOO_LONG_BODY, MYF(0), parse_data->name.str); + goto err; + } + + if (!(parse_data->expression) && !(parse_data->execute_at.year)) + { + DBUG_PRINT("error", ("neither expression nor execute_at are set!")); + my_error(ER_EVENT_NEITHER_M_EXPR_NOR_M_AT, MYF(0)); + goto err; + } + + ((Field_timestamp *)table->field[ET_FIELD_CREATED])->set_time(); + + /* + mysql_event_fill_row() calls my_error() in case of error so no need to + handle it here + */ + if ((ret= mysql_event_fill_row(thd, table, parse_data, FALSE))) + goto err; + + /* Close active transaction only if We are going to modify disk */ + if (end_active_trans(thd)) + goto err; + + if (table->file->ha_write_row(table->record[0])) + { + my_error(ER_EVENT_STORE_FAILED, MYF(0), parse_data->name.str, ret); + goto err; + } + + *rows_affected= 1; +ok: + if (dbchanged) + (void) mysql_change_db(thd, olddb, 1); + if (table) + close_thread_tables(thd); + DBUG_RETURN(EVEX_OK); + +err: + if (dbchanged) + (void) mysql_change_db(thd, olddb, 1); + if (table) + close_thread_tables(thd); + DBUG_RETURN(EVEX_GENERAL_ERROR); +} + + +/* + Used to execute ALTER EVENT. Pendant to Events::update_event(). + + SYNOPSIS + Event_db_repository::update_event() + thd THD + sp_name the name of the event to alter + et event's data + + RETURN VALUE + 0 OK + EVEX_GENERAL_ERROR Error occured and reported + + NOTES + sp_name is passed since this is the name of the event to + alter in case of RENAME TO. +*/ + +int +Event_db_repository::update_event(THD *thd, Event_parse_data *parse_data, + sp_name *new_name) +{ + CHARSET_INFO *scs= system_charset_info; + TABLE *table; + int ret= EVEX_OPEN_TABLE_FAILED; + DBUG_ENTER("Event_db_repository::update_event"); + + if (open_event_table(thd, TL_WRITE, &table)) + { + my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0)); + goto err; + } + + if (check_parse_params(thd, parse_data)) + goto err; + + DBUG_PRINT("info", ("dbname: %s", parse_data->dbname.str)); + DBUG_PRINT("info", ("name: %s", parse_data->name.str)); + DBUG_PRINT("info", ("user: %s", parse_data->definer.str)); + if (new_name) + DBUG_PRINT("info", ("rename to: %s", new_name->m_name.str)); + + /* first look whether we overwrite */ + if (new_name) + { + if (!sortcmp_lex_string(parse_data->name, new_name->m_name, scs) && + !sortcmp_lex_string(parse_data->dbname, new_name->m_db, scs)) + { + my_error(ER_EVENT_SAME_NAME, MYF(0), parse_data->name.str); + goto err; + } + + if (!find_event_by_name(thd, new_name->m_db, new_name->m_name, table)) + { + my_error(ER_EVENT_ALREADY_EXISTS, MYF(0), new_name->m_name.str); + goto err; + } + } + /* + ...and then if there is such an event. Don't exchange the blocks + because you will get error 120 from table handler because new_name will + overwrite the key and SE will tell us that it cannot find the already found + row (copied into record[1] later + */ + if (EVEX_KEY_NOT_FOUND == find_event_by_name(thd, parse_data->dbname, + parse_data->name, table)) + { + my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), parse_data->name.str); + goto err; + } + + store_record(table,record[1]); + + /* Don't update create on row update. */ + table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; + + /* + mysql_event_fill_row() calls my_error() in case of error so no need to + handle it here + */ + if ((ret= mysql_event_fill_row(thd, table, parse_data, TRUE))) + goto err; + + if (new_name) + { + table->field[ET_FIELD_DB]-> + store(new_name->m_db.str, new_name->m_db.length, scs); + table->field[ET_FIELD_NAME]-> + store(new_name->m_name.str, new_name->m_name.length, scs); + } + + /* Close active transaction only if We are going to modify disk */ + if (end_active_trans(thd)) + goto err; + + if ((ret= table->file->ha_update_row(table->record[1], table->record[0]))) + { + my_error(ER_EVENT_STORE_FAILED, MYF(0), parse_data->name.str, ret); + goto err; + } + + /* close mysql.event or we crash later when loading the event from disk */ + close_thread_tables(thd); + DBUG_RETURN(0); + +err: + if (table) + close_thread_tables(thd); + DBUG_RETURN(EVEX_GENERAL_ERROR); +} + + +/* + Drops an event + + SYNOPSIS + Event_db_repository::drop_event() + thd [in] THD + db [in] Database name + name [in] Event's name + drop_if_exists [in] If set and the event not existing => warning + onto the stack + rows_affected [out] Affected number of rows is returned heres + + RETURN VALUE + 0 OK + !0 Error (my_error() called) +*/ + +int +Event_db_repository::drop_event(THD *thd, LEX_STRING db, LEX_STRING name, + bool drop_if_exists, uint *rows_affected) +{ + TABLE *table; + Open_tables_state backup; + int ret; + + DBUG_ENTER("Event_db_repository::drop_event"); + DBUG_PRINT("enter", ("db=%s name=%s", db.str, name.str)); + ret= EVEX_OPEN_TABLE_FAILED; + + thd->reset_n_backup_open_tables_state(&backup); + if (open_event_table(thd, TL_WRITE, &table)) + { + my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0)); + goto done; + } + + switch ((ret= find_event_by_name(thd, db, name, table))) { + case 0: + /* Close active transaction only if we are actually going to modify disk */ + if ((ret= end_active_trans(thd))) + break; + + if ((ret= table->file->ha_delete_row(table->record[0]))) + my_error(ER_EVENT_CANNOT_DELETE, MYF(0)); + break; + case EVEX_KEY_NOT_FOUND: + if (drop_if_exists) + { + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + ER_SP_DOES_NOT_EXIST, ER(ER_SP_DOES_NOT_EXIST), + "Event", name.str); + ret= 0; + } else + my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), name.str); + break; + default: + ; + } + +done: + close_thread_tables(thd); + thd->restore_backup_open_tables_state(&backup); + DBUG_RETURN(ret); +} + + +/* + Positions the internal pointer of `table` to the place where (db, name) + is stored. + + SYNOPSIS + Event_db_repository::find_event_by_name() + thd Thread + db Schema + name Event name + table Opened mysql.event + + RETURN VALUE + 0 OK + EVEX_KEY_NOT_FOUND No such event +*/ + +int +Event_db_repository::find_event_by_name(THD *thd, LEX_STRING db, + LEX_STRING name, TABLE *table) +{ + byte key[MAX_KEY_LENGTH]; + DBUG_ENTER("Event_db_repository::find_event_by_name"); + DBUG_PRINT("enter", ("name: %.*s", name.length, name.str)); + + /* + Create key to find row. We have to use field->store() to be able to + handle VARCHAR and CHAR fields. + Assumption here is that the two first fields in the table are + 'db' and 'name' and the first key is the primary key over the + same fields. + */ + if (db.length > table->field[ET_FIELD_DB]->field_length || + name.length > table->field[ET_FIELD_NAME]->field_length) + DBUG_RETURN(EVEX_KEY_NOT_FOUND); + + table->field[ET_FIELD_DB]->store(db.str, db.length, &my_charset_bin); + table->field[ET_FIELD_NAME]->store(name.str, name.length, &my_charset_bin); + + key_copy(key, table->record[0], table->key_info, table->key_info->key_length); + + if (table->file->index_read_idx(table->record[0], 0, key, + table->key_info->key_length, + HA_READ_KEY_EXACT)) + { + DBUG_PRINT("info", ("Row not found")); + DBUG_RETURN(EVEX_KEY_NOT_FOUND); + } + + DBUG_PRINT("info", ("Row found!")); + DBUG_RETURN(0); +} + + +/* + Drops all events in the selected database, from mysql.event. + + SYNOPSIS + Event_db_repository::drop_schema_events() + thd Thread + schema The database to clean from events + + RETURN VALUE + 0 OK + !0 Error (Reported) +*/ + +int +Event_db_repository::drop_schema_events(THD *thd, LEX_STRING schema) +{ + return drop_events_by_field(thd, ET_FIELD_DB, schema); +} + + +/* + Drops all events by field which has specific value of the field + + SYNOPSIS + Event_db_repository::drop_events_by_field() + thd Thread + table mysql.event TABLE + field Which field of the row to use for matching + field_value The value that should match + + RETURN VALUE + 0 OK + !0 Error from ha_delete_row +*/ + +int +Event_db_repository::drop_events_by_field(THD *thd, + enum enum_events_table_field field, + LEX_STRING field_value) +{ + int ret= 0; + TABLE *table; + Open_tables_state backup; + READ_RECORD read_record_info; + DBUG_ENTER("Event_db_repository::drop_events_by_field"); + DBUG_PRINT("enter", ("field=%d field_value=%s", field, field_value.str)); + + if (open_event_table(thd, TL_WRITE, &table)) + { + my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0)); + DBUG_RETURN(1); + } + + /* only enabled events are in memory, so we go now and delete the rest */ + init_read_record(&read_record_info, thd, table, NULL, 1, 0); + while (!ret && !(read_record_info.read_record(&read_record_info)) ) + { + char *et_field= get_field(thd->mem_root, table->field[field]); + + LEX_STRING et_field_lex= {et_field, strlen(et_field)}; + DBUG_PRINT("info", ("Current event %s name=%s", et_field, + get_field(thd->mem_root, table->field[ET_FIELD_NAME]))); + + if (!sortcmp_lex_string(et_field_lex, field_value, system_charset_info)) + { + DBUG_PRINT("info", ("Dropping")); + if ((ret= table->file->ha_delete_row(table->record[0]))) + my_error(ER_EVENT_DROP_FAILED, MYF(0), + get_field(thd->mem_root, table->field[ET_FIELD_NAME])); + } + } + end_read_record(&read_record_info); + thd->version--; /* Force close to free memory */ + + DBUG_RETURN(ret); +} + + +/* + Looks for a named event in mysql.event and in case of success returns + an object will data loaded from the table. + + SYNOPSIS + Event_db_repository::find_event() + thd [in] THD + name [in] The name of the event to find + ett [out] Event's data if event is found + tbl [in] TABLE object to use when not NULL + + NOTES + 1) Use sp_name for look up, return in **ett if found + 2) tbl is not closed at exit + + RETURN VALUE + 0 ok In this case *ett is set to the event + # error *ett == 0 +*/ + +int +Event_db_repository::find_event(THD *thd, LEX_STRING dbname, LEX_STRING name, + Event_basic *et) +{ + TABLE *table; + int ret; + DBUG_ENTER("Event_db_repository::find_event"); + DBUG_PRINT("enter", ("name: %*s", name.length, name.str)); + + if (open_event_table(thd, TL_READ, &table)) + { + my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0)); + ret= EVEX_GENERAL_ERROR; + goto done; + } + + if ((ret= find_event_by_name(thd, dbname, name, table))) + { + my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), name.str); + goto done; + } + /* + 1)The table should not be closed beforehand. ::load_from_row() only loads + and does not compile + + 2)::load_from_row() is silent on error therefore we emit error msg here + */ + if ((ret= et->load_from_row(table))) + { + my_error(ER_CANNOT_LOAD_FROM_TABLE, MYF(0), "event"); + goto done; + } + +done: + if (table) + close_thread_tables(thd); + + DBUG_RETURN(ret); +} + + +/* + Looks for a named event in mysql.event and then loads it from + the table, compiles and inserts it into the cache. + + SYNOPSIS + Event_db_repository::load_named_event() + thd [in] THD + dbname [in] Event's db name + name [in] Event's name + etn_new [out] The loaded event + + RETURN VALUE + OP_OK OK + OP_LOAD_ERROR Error during loading from disk +*/ + +int +Event_db_repository::load_named_event(THD *thd, LEX_STRING dbname, + LEX_STRING name, Event_basic *etn) +{ + int ret= 0; + Open_tables_state backup; + + DBUG_ENTER("Event_db_repository::load_named_event"); + DBUG_PRINT("enter",("thd=0x%lx name:%*s",thd, name.length, name.str)); + + thd->reset_n_backup_open_tables_state(&backup); + /* No need to use my_error() here because find_event() has done it */ + ret= find_event(thd, dbname, name, etn); + thd->restore_backup_open_tables_state(&backup); + /* In this case no memory was allocated so we don't need to clean */ + if (ret) + DBUG_RETURN(OP_LOAD_ERROR); + + DBUG_RETURN(OP_OK); +} diff --git a/sql/event_db_repository.h b/sql/event_db_repository.h new file mode 100644 index 00000000000..d13538ce10b --- /dev/null +++ b/sql/event_db_repository.h @@ -0,0 +1,107 @@ +#ifndef _EVENT_DB_REPOSITORY_H_ +#define _EVENT_DB_REPOSITORY_H_ +/* Copyright (C) 2004-2006 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +enum enum_events_table_field +{ + ET_FIELD_DB = 0, + ET_FIELD_NAME, + ET_FIELD_BODY, + ET_FIELD_DEFINER, + ET_FIELD_EXECUTE_AT, + ET_FIELD_INTERVAL_EXPR, + ET_FIELD_TRANSIENT_INTERVAL, + ET_FIELD_CREATED, + ET_FIELD_MODIFIED, + ET_FIELD_LAST_EXECUTED, + ET_FIELD_STARTS, + ET_FIELD_ENDS, + ET_FIELD_STATUS, + ET_FIELD_ON_COMPLETION, + ET_FIELD_SQL_MODE, + ET_FIELD_COMMENT, + ET_FIELD_COUNT /* a cool trick to count the number of fields :) */ +}; + + +int +events_table_index_read_for_db(THD *thd, TABLE *schema_table, + TABLE *event_table); + +int +events_table_scan_all(THD *thd, TABLE *schema_table, TABLE *event_table); + +int +fill_schema_events(THD *thd, TABLE_LIST *tables, COND * /* cond */); + +class Event_basic; +class Event_parse_data; + +class Event_db_repository +{ +public: + Event_db_repository(){} + + int + create_event(THD *thd, Event_parse_data *parse_data, my_bool create_if_not, + uint *rows_affected); + + int + update_event(THD *thd, Event_parse_data *parse_data, sp_name *new_name); + + int + drop_event(THD *thd, LEX_STRING db, LEX_STRING name, bool drop_if_exists, + uint *rows_affected); + + int + drop_schema_events(THD *thd, LEX_STRING schema); + + int + find_event(THD *thd, LEX_STRING dbname, LEX_STRING name, Event_basic *et); + + int + load_named_event(THD *thd, LEX_STRING dbname, LEX_STRING name, Event_basic *et); + + int + find_event_by_name(THD *thd, LEX_STRING db, LEX_STRING name, TABLE *table); + + int + open_event_table(THD *thd, enum thr_lock_type lock_type, TABLE **table); + + int + fill_schema_events(THD *thd, TABLE_LIST *tables, char *db); + +private: + int + drop_events_by_field(THD *thd, enum enum_events_table_field field, + LEX_STRING field_value); + int + index_read_for_db_for_i_s(THD *thd, TABLE *schema_table, TABLE *event_table, + char *db); + + int + table_scan_all_for_i_s(THD *thd, TABLE *schema_table, TABLE *event_table); + + static bool + check_system_tables(THD *thd); + + /* Prevent use of these */ + Event_db_repository(const Event_db_repository &); + void operator=(Event_db_repository &); +}; + +#endif /* _EVENT_DB_REPOSITORY_H_ */ diff --git a/sql/event_queue.cc b/sql/event_queue.cc new file mode 100644 index 00000000000..bd8809ba708 --- /dev/null +++ b/sql/event_queue.cc @@ -0,0 +1,945 @@ +/* Copyright (C) 2004-2006 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "mysql_priv.h" +#include "events.h" +#include "event_scheduler_ng.h" +#include "event_queue.h" +#include "event_data_objects.h" +#include "event_db_repository.h" +#include "sp_head.h" + + +#ifdef __GNUC__ +#if __GNUC__ >= 2 +#define SCHED_FUNC __FUNCTION__ +#endif +#else +#define SCHED_FUNC "<unknown>" +#endif + +#define LOCK_QUEUE_DATA() lock_data(SCHED_FUNC, __LINE__) +#define UNLOCK_QUEUE_DATA() unlock_data(SCHED_FUNC, __LINE__) + + +/* + Compares the execute_at members of 2 Event_queue_element instances. + Used as callback for the prioritized queue when shifting + elements inside. + + SYNOPSIS + event_queue_element_data_compare_q() + + vptr - not used (set it to NULL) + a - first Event_queue_element object + b - second Event_queue_element object + + RETURN VALUE + -1 - a->execute_at < b->execute_at + 0 - a->execute_at == b->execute_at + 1 - a->execute_at > b->execute_at + + NOTES + execute_at.second_part is not considered during comparison +*/ + +static int +event_queue_element_compare_q(void *vptr, byte* a, byte *b) +{ + return my_time_compare(&((Event_queue_element *)a)->execute_at, + &((Event_queue_element *)b)->execute_at); +} + + +/* + Constructor of class Event_queue. + + SYNOPSIS + Event_queue::Event_queue() +*/ + +Event_queue::Event_queue() +{ + mutex_last_unlocked_at_line= mutex_last_locked_at_line= 0; + mutex_last_unlocked_in_func= mutex_last_locked_in_func= ""; + mutex_queue_data_locked= FALSE; +} + + +/* + Inits mutexes. + + SYNOPSIS + Event_queue::init_mutexes() +*/ + +void +Event_queue::init_mutexes() +{ + pthread_mutex_init(&LOCK_event_queue, MY_MUTEX_INIT_FAST); +} + + +/* + Destroys mutexes. + + SYNOPSIS + Event_queue::deinit_mutexes() +*/ + +void +Event_queue::deinit_mutexes() +{ + pthread_mutex_destroy(&LOCK_event_queue); +} + + +/* + Signals the main scheduler thread that the queue has changed + its state. + + SYNOPSIS + Event_queue::notify_observers() +*/ + +void +Event_queue::notify_observers() +{ + DBUG_ENTER("Event_queue::notify_observers"); + DBUG_PRINT("info", ("Signalling change of the queue")); + scheduler->queue_changed(); + DBUG_VOID_RETURN; +} + + +/* + Inits the queue + + SYNOPSIS + Event_queue::init() + + RETURN VALUE + FALSE OK + TRUE Error +*/ + +bool +Event_queue::init_queue(Event_db_repository *db_repo, Event_scheduler_ng *sched) +{ + int i= 0; + bool ret= FALSE; + DBUG_ENTER("Event_queue::init_queue"); + DBUG_PRINT("enter", ("this=0x%lx", this)); + + LOCK_QUEUE_DATA(); + db_repository= db_repo; + scheduler= sched; + + if (init_queue_ex(&queue, 30 /*num_el*/, 0 /*offset*/, 0 /*smallest_on_top*/, + event_queue_element_compare_q, NULL, 30 /*auto_extent*/)) + { + sql_print_error("SCHEDULER: Can't initialize the execution queue"); + ret= TRUE; + goto end; + } + + if (sizeof(my_time_t) != sizeof(time_t)) + { + sql_print_error("SCHEDULER: sizeof(my_time_t) != sizeof(time_t) ." + "The scheduler may not work correctly. Stopping."); + DBUG_ASSERT(0); + ret= TRUE; + goto end; + } + +end: + UNLOCK_QUEUE_DATA(); + DBUG_RETURN(ret); +} + + +/* + Deinits the queue + + SYNOPSIS + Event_queue::deinit_queue() +*/ + +void +Event_queue::deinit_queue() +{ + DBUG_ENTER("Event_queue::deinit_queue"); + + LOCK_QUEUE_DATA(); + empty_queue(); + delete_queue(&queue); + UNLOCK_QUEUE_DATA(); + + DBUG_VOID_RETURN; +} + + +/* + Creates an event in the scheduler queue + + SYNOPSIS + Event_queue::create_event() + et The event to add + check_existence Whether to check if already loaded. + + RETURN VALUE + OP_OK OK or scheduler not working + OP_LOAD_ERROR Error during loading from disk +*/ + +int +Event_queue::create_event(THD *thd, LEX_STRING dbname, LEX_STRING name) +{ + int res; + Event_queue_element *element_new; + DBUG_ENTER("Event_queue::create_event"); + DBUG_PRINT("enter", ("thd=0x%lx et=%s.%s",thd, dbname.str, name.str)); + + element_new= new Event_queue_element(); + res= db_repository->load_named_event(thd, dbname, name, element_new); + if (res || element_new->status == Event_queue_element::DISABLED) + delete element_new; + else + { + element_new->compute_next_execution_time(); + + LOCK_QUEUE_DATA(); + DBUG_PRINT("info", ("new event in the queue 0x%lx", element_new)); + queue_insert_safe(&queue, (byte *) element_new); + UNLOCK_QUEUE_DATA(); + + notify_observers(); + } + + DBUG_RETURN(res); +} + + +/* + Updates an event from the scheduler queue + + SYNOPSIS + Event_queue::update_event() + thd Thread + dbname Schema of the event + name Name of the event + new_schema New schema, in case of RENAME TO, otherwise NULL + new_name New name, in case of RENAME TO, otherwise NULL + + RETURN VALUE + OP_OK OK or scheduler not working + OP_LOAD_ERROR Error during loading from disk +*/ + +int +Event_queue::update_event(THD *thd, LEX_STRING dbname, LEX_STRING name, + LEX_STRING *new_schema, LEX_STRING *new_name) +{ + int res; + Event_queue_element *element_old= NULL, + *element_new; + + DBUG_ENTER("Event_queue::update_event"); + DBUG_PRINT("enter", ("thd=0x%lx et=[%s.%s]", thd, dbname.str, name.str)); + + element_new= new Event_queue_element(); + + res= db_repository->load_named_event(thd, new_schema? *new_schema:dbname, + new_name? *new_name:name, element_new); + if (res) + { + delete element_new; + goto end; + } + else if (element_new->status == Event_queue_element::DISABLED) + { + DBUG_PRINT("info", ("The event is disabled.")); + /* + Destroy the object but don't skip to end: because we may have to remove + object from the cache. + */ + delete element_new; + element_new= NULL; + } + else + element_new->compute_next_execution_time(); + + LOCK_QUEUE_DATA(); + if (!(element_old= find_event(dbname, name, TRUE))) + { + DBUG_PRINT("info", ("%s.%s not cached, probably was DISABLED", + dbname.str, name.str)); + } + /* If not disabled event */ + if (element_new) + { + DBUG_PRINT("info", ("new event in the Q 0x%lx old 0x%lx", + element_new, element_old)); + queue_insert_safe(&queue, (byte *) element_new); + } + UNLOCK_QUEUE_DATA(); + + notify_observers(); + + if (element_old) + delete element_old; +end: + DBUG_PRINT("info", ("res=%d", res)); + DBUG_RETURN(res); +} + + +/* + Drops an event from the queue + + SYNOPSIS + Event_queue::drop_event() + thd Thread + dbname Schema of the event to drop + name Name of the event to drop +*/ + +void +Event_queue::drop_event(THD *thd, LEX_STRING dbname, LEX_STRING name) +{ + int res; + Event_queue_element *element; + DBUG_ENTER("Event_queue::drop_event"); + DBUG_PRINT("enter", ("thd=0x%lx name=0x%lx", thd, name)); + + LOCK_QUEUE_DATA(); + element= find_event(dbname, name, TRUE); + UNLOCK_QUEUE_DATA(); + + if (element) + delete element; + else + DBUG_PRINT("info", ("No such event found, probably DISABLED")); + + /* + We don't signal here because the scheduler will catch the change + next time it wakes up. + */ + + DBUG_VOID_RETURN; +} + + +/* + Searches for an event in the queue + + SYNOPSIS + Event_queue::find_event() + db The schema of the event to find + name The event to find + remove_from_q If found whether to remove from the Q + + RETURN VALUE + NULL Not found + otherwise Address + + NOTE + The caller should do the locking also the caller is responsible for + actual signalling in case an event is removed from the queue + (signalling COND_new_work for instance). +*/ + +Event_queue_element * +Event_queue::find_event(LEX_STRING db, LEX_STRING name, bool remove_from_q) +{ + uint i; + DBUG_ENTER("Event_queue::find_event"); + + for (i= 0; i < queue.elements; ++i) + { + Event_queue_element *et= (Event_queue_element *) queue_element(&queue, i); + DBUG_PRINT("info", ("[%s.%s]==[%s.%s]?", db.str, name.str, + et->dbname.str, et->name.str)); + if (event_basic_identifier_equal(db, name, et)) + { + if (remove_from_q) + queue_remove(&queue, i); + DBUG_RETURN(et); + } + } + + DBUG_RETURN(NULL); +} + + +/* + Drops all events from the in-memory queue and disk that match + certain pattern evaluated by a comparator function + + SYNOPSIS + Event_queue::drop_matching_events() + thd THD + pattern A pattern string + comparator The function to use for comparing + + RETURN VALUE + >=0 Number of dropped events + + NOTE + Expected is the caller to acquire lock on LOCK_event_queue +*/ + +void +Event_queue::drop_matching_events(THD *thd, LEX_STRING pattern, + bool (*comparator)(LEX_STRING *, Event_basic *)) +{ + DBUG_ENTER("Event_queue::drop_matching_events"); + DBUG_PRINT("enter", ("pattern=%*s state=%d", pattern.length, pattern.str)); + + uint i= 0; + while (i < queue.elements) + { + Event_queue_element *et= (Event_queue_element *) queue_element(&queue, i); + DBUG_PRINT("info", ("[%s.%s]?", et->dbname.str, et->name.str)); + if (comparator(&pattern, et)) + { + /* + The queue is ordered. If we remove an element, then all elements after + it will shift one position to the left, if we imagine it as an array + from left to the right. In this case we should not increment the + counter and the (i < queue.elements) condition is ok. + */ + queue_remove(&queue, i); + delete et; + } + else + i++; + } + /* + We don't call notify_observers() . If we remove the top event: + 1. The queue is empty. The scheduler will wake up at some time and realize + that the queue is empty. If create_event() comes inbetween it will + signal the scheduler + 2. The queue is not empty, but the next event after the previous top, won't + be executed any time sooner than the element we removed. Hence, we may + not notify the scheduler and it will realize the change when it + wakes up from timedwait. + */ + + DBUG_VOID_RETURN; +} + + +/* + Drops all events from the in-memory queue and disk that are from + certain schema. + + SYNOPSIS + Event_queue::drop_schema_events() + thd THD + db The schema name + + RETURN VALUE + >=0 Number of dropped events +*/ + +void +Event_queue::drop_schema_events(THD *thd, LEX_STRING schema) +{ + DBUG_ENTER("Event_queue::drop_schema_events"); + LOCK_QUEUE_DATA(); + drop_matching_events(thd, schema, event_basic_db_equal); + UNLOCK_QUEUE_DATA(); + DBUG_VOID_RETURN; +} + + +/* + Returns the number of elements in the queue + + SYNOPSIS + Event_queue::events_count() + + RETURN VALUE + Number of Event_queue_element objects in the queue +*/ + +uint +Event_queue::events_count() +{ + uint n; + DBUG_ENTER("Event_scheduler::events_count"); + LOCK_QUEUE_DATA(); + n= queue.elements; + UNLOCK_QUEUE_DATA(); + DBUG_PRINT("info", ("n=%u", n)); + DBUG_RETURN(n); +} + + +/* + Loads all ENABLED events from mysql.event into the prioritized + queue. Called during scheduler main thread initialization. Compiles + the events. Creates Event_queue_element instances for every ENABLED event + from mysql.event. + + SYNOPSIS + Event_queue::load_events_from_db() + thd - Thread context. Used for memory allocation in some cases. + + RETURN VALUE + 0 OK + !0 Error (EVEX_OPEN_TABLE_FAILED, EVEX_MICROSECOND_UNSUP, + EVEX_COMPILE_ERROR) - in all these cases mysql.event was + tampered. + + NOTES + Reports the error to the console +*/ + +int +Event_queue::load_events_from_db(THD *thd) +{ + TABLE *table; + READ_RECORD read_record_info; + int ret= -1; + uint count= 0; + bool clean_the_queue= FALSE; + /* Compile the events on this root but only for syntax check, then discard */ + MEM_ROOT boot_root; + + DBUG_ENTER("Event_queue::load_events_from_db"); + DBUG_PRINT("enter", ("thd=0x%lx", thd)); + + if ((ret= db_repository->open_event_table(thd, TL_READ, &table))) + { + sql_print_error("SCHEDULER: Table mysql.event is damaged. Can not open."); + DBUG_RETURN(EVEX_OPEN_TABLE_FAILED); + } + + init_read_record(&read_record_info, thd, table ,NULL,1,0); + while (!(read_record_info.read_record(&read_record_info))) + { + Event_queue_element *et; + if (!(et= new Event_queue_element)) + { + DBUG_PRINT("info", ("Out of memory")); + clean_the_queue= TRUE; + break; + } + DBUG_PRINT("info", ("Loading event from row.")); + + if ((ret= et->load_from_row(table))) + { + clean_the_queue= TRUE; + sql_print_error("SCHEDULER: Error while loading from mysql.event. " + "Table probably corrupted"); + break; + } + if (et->status != Event_queue_element::ENABLED) + { + DBUG_PRINT("info",("%s is disabled",et->name.str)); + delete et; + continue; + } +#if 0 + init_alloc_root(&boot_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC); + DBUG_PRINT("info", ("Event %s loaded from row. ", et->name.str)); + + /* We load only on scheduler root just to check whether the body compiles */ + switch (ret= et->compile(thd, &boot_root)) { + case EVEX_MICROSECOND_UNSUP: + et->free_sp(); + sql_print_error("SCHEDULER: mysql.event is tampered. MICROSECOND is not " + "supported but found in mysql.event"); + goto end; + case EVEX_COMPILE_ERROR: + sql_print_error("SCHEDULER: Error while compiling %s.%s. Aborting load.", + et->dbname.str, et->name.str); + goto end; + default: + /* Free it, it will be compiled again on the worker thread */ + et->free_sp(); + break; + } + free_root(&boot_root, MYF(0)); + + /* let's find when to be executed */ + if (et->compute_next_execution_time()) + { + sql_print_error("SCHEDULER: Error while computing execution time of %s.%s." + " Skipping", et->dbname.str, et->name.str); + continue; + } +#endif + DBUG_PRINT("load_events_from_db", ("Adding 0x%lx to the exec list.")); + queue_insert_safe(&queue, (byte *) et); + count++; + } +end: + end_read_record(&read_record_info); + + if (clean_the_queue) + { + empty_queue(); + ret= -1; + } + else + { + ret= 0; + sql_print_information("SCHEDULER: Loaded %d event%s", count, (count == 1)?"":"s"); + } + + /* Force close to free memory */ + thd->version--; + + close_thread_tables(thd); + + DBUG_PRINT("info", ("Status code %d. Loaded %d event(s)", ret, count)); + DBUG_RETURN(ret); +} + + +/* + Opens mysql.db and mysql.user and checks whether: + 1. mysql.db has column Event_priv at column 20 (0 based); + 2. mysql.user has column Event_priv at column 29 (0 based); + + SYNOPSIS + Event_queue::check_system_tables() +*/ + +bool +Event_queue::check_system_tables(THD *thd) +{ + TABLE_LIST tables; + bool not_used; + Open_tables_state backup; + bool ret; + + DBUG_ENTER("Event_queue::check_system_tables"); + DBUG_PRINT("enter", ("thd=0x%lx", thd)); + + thd->reset_n_backup_open_tables_state(&backup); + + bzero((char*) &tables, sizeof(tables)); + tables.db= (char*) "mysql"; + tables.table_name= tables.alias= (char*) "db"; + tables.lock_type= TL_READ; + + if ((ret= simple_open_n_lock_tables(thd, &tables))) + sql_print_error("Cannot open mysql.db"); + else + { + ret= table_check_intact(tables.table, MYSQL_DB_FIELD_COUNT, + mysql_db_table_fields, &mysql_db_table_last_check, + ER_CANNOT_LOAD_FROM_TABLE); + close_thread_tables(thd); + } + if (ret) + DBUG_RETURN(TRUE); + + bzero((char*) &tables, sizeof(tables)); + tables.db= (char*) "mysql"; + tables.table_name= tables.alias= (char*) "user"; + tables.lock_type= TL_READ; + + if ((ret= simple_open_n_lock_tables(thd, &tables))) + sql_print_error("Cannot open mysql.db"); + else + { + if (tables.table->s->fields < 29 || + strncmp(tables.table->field[29]->field_name, + STRING_WITH_LEN("Event_priv"))) + { + sql_print_error("mysql.user has no `Event_priv` column at position 29"); + ret= TRUE; + } + close_thread_tables(thd); + } + + thd->restore_backup_open_tables_state(&backup); + + DBUG_RETURN(ret); +} + + +/* + Recalculates activation times in the queue. There is one reason for + that. Because the values (execute_at) by which the queue is ordered are + changed by calls to compute_next_execution_time() on a request from the + scheduler thread, if it is not running then the values won't be updated. + Once the scheduler is started again the values has to be recalculated + so they are right for the current time. + + SYNOPSIS + Event_queue::recalculate_activation_times() + thd Thread +*/ + +void +Event_queue::recalculate_activation_times(THD *thd) +{ + uint i; + DBUG_ENTER("Event_queue::recalculate_activation_times"); + + LOCK_QUEUE_DATA(); + DBUG_PRINT("info", ("%u loaded events to be recalculated", queue.elements)); + for (i= 0; i < queue.elements; i++) + { + ((Event_queue_element*)queue_element(&queue, i))->compute_next_execution_time(); + ((Event_queue_element*)queue_element(&queue, i))->update_timing_fields(thd); + } + queue_fix(&queue); + UNLOCK_QUEUE_DATA(); + + DBUG_VOID_RETURN; +} + + +/* + Empties the queue and destroys the Event_queue_element objects in the + queue. + + SYNOPSIS + Event_queue::empty_queue() + + NOTE + Should be called with LOCK_event_queue locked +*/ + +void +Event_queue::empty_queue() +{ + uint i; + DBUG_ENTER("Event_queue::empty_queue"); + DBUG_PRINT("enter", ("Purging the queue. %d element(s)", queue.elements)); + /* empty the queue */ + for (i= 0; i < queue.elements; ++i) + { + Event_queue_element *et= (Event_queue_element *) queue_element(&queue, i); + delete et; + } + resize_queue(&queue, 0); + DBUG_VOID_RETURN; +} + + +inline void +Event_queue::dbug_dump_queue(time_t now) +{ +#ifndef DBUG_OFF + Event_queue_element *et; + uint i; + DBUG_PRINT("info", ("Dumping queue . Elements=%u", queue.elements)); + for (i = 0; i < queue.elements; i++) + { + et= ((Event_queue_element*)queue_element(&queue, i)); + DBUG_PRINT("info",("et=0x%lx db=%s name=%s",et, et->dbname.str, et->name.str)); + DBUG_PRINT("info", ("exec_at=%llu starts=%llu ends=%llu " + " expr=%lld et.exec_at=%d now=%d (et.exec_at - now)=%d if=%d", + TIME_to_ulonglong_datetime(&et->execute_at), + TIME_to_ulonglong_datetime(&et->starts), + TIME_to_ulonglong_datetime(&et->ends), + et->expression, sec_since_epoch_TIME(&et->execute_at), now, + (int)(sec_since_epoch_TIME(&et->execute_at) - now), + sec_since_epoch_TIME(&et->execute_at) <= now)); + } +#endif +} + +Event_job_data * +Event_queue::get_top_for_execution_if_time(THD *thd, time_t now, + struct timespec *abstime) +{ + struct timespec top_time; + Event_job_data *et_new= NULL; + DBUG_ENTER("Event_queue::get_top_for_execution_if_time"); + DBUG_PRINT("enter", ("thd=0x%lx now=%d", thd, now)); + abstime->tv_nsec= 0; + LOCK_QUEUE_DATA(); + do { + int res; + if (!queue.elements) + { + abstime->tv_sec= 0; + break; + } + dbug_dump_queue(now); + + Event_queue_element *et= ((Event_queue_element*) queue_element(&queue, 0)); + top_time.tv_sec= sec_since_epoch_TIME(&et->execute_at); + + if (top_time.tv_sec <= now) + { + DBUG_PRINT("info", ("Ready for execution")); + abstime->tv_sec= 0; + et_new= new Event_job_data(); + if ((res= db_repository->load_named_event(thd, et->dbname, et->name, + et_new))) + { + delete et_new; + et_new= NULL; + DBUG_ASSERT(0); + break; + } + + et->mark_last_executed(thd); + if (et->compute_next_execution_time()) + et->status= Event_queue_element::DISABLED; + DBUG_PRINT("info", ("event's status is %d", et->status)); + + et->update_timing_fields(thd); + if (((et->execute_at.year && !et->expression) || et->execute_at_null) || + (et->status == Event_queue_element::DISABLED)) + { + DBUG_PRINT("info", ("removing from the queue")); + if (et->dropped) + et->drop(thd); + delete et; + queue_remove(&queue, 0); + } + else + queue_replaced(&queue); + } + else + { + abstime->tv_sec= top_time.tv_sec; + DBUG_PRINT("info", ("Have to wait %d till %d", abstime->tv_sec - now, + abstime->tv_sec)); + } + } while (0); + UNLOCK_QUEUE_DATA(); + + DBUG_PRINT("info", ("returning. et_new=0x%lx abstime.tv_sec=%d ", et_new, + abstime->tv_sec)); + if (et_new) + DBUG_PRINT("info", ("db=%s name=%s definer=%s", + et_new->dbname.str, et_new->name.str, et_new->definer.str)); + DBUG_RETURN(et_new); +} + + +/* + Auxiliary function for locking LOCK_event_queue. Used by the + LOCK_QUEUE_DATA macro + + SYNOPSIS + Event_queue::lock_data() + func Which function is requesting mutex lock + line On which line mutex lock is requested +*/ + +void +Event_queue::lock_data(const char *func, uint line) +{ + DBUG_ENTER("Event_queue::lock_data"); + DBUG_PRINT("enter", ("func=%s line=%u", func, line)); + pthread_mutex_lock(&LOCK_event_queue); + mutex_last_locked_in_func= func; + mutex_last_locked_at_line= line; + mutex_queue_data_locked= TRUE; + DBUG_VOID_RETURN; +} + + +/* + Auxiliary function for unlocking LOCK_event_queue. Used by the + UNLOCK_QUEUE_DATA macro + + SYNOPSIS + Event_queue::unlock_data() + func Which function is requesting mutex unlock + line On which line mutex unlock is requested +*/ + +void +Event_queue::unlock_data(const char *func, uint line) +{ + DBUG_ENTER("Event_queue::unlock_data"); + DBUG_PRINT("enter", ("func=%s line=%u", func, line)); + mutex_last_unlocked_at_line= line; + mutex_queue_data_locked= FALSE; + mutex_last_unlocked_in_func= func; + pthread_mutex_unlock(&LOCK_event_queue); + DBUG_VOID_RETURN; +} + + +/* + Dumps the internal status of the queue + + SYNOPSIS + Event_queue::dump_internal_status() + thd Thread + + RETURN VALUE + FALSE OK + TRUE Error +*/ + +bool +Event_queue::dump_internal_status(THD *thd) +{ + DBUG_ENTER("Event_queue::dump_internal_status"); +#ifndef DBUG_OFF + CHARSET_INFO *scs= system_charset_info; + Protocol *protocol= thd->protocol; + List<Item> field_list; + int ret; + char tmp_buff[5*STRING_BUFFER_USUAL_SIZE]; + char int_buff[STRING_BUFFER_USUAL_SIZE]; + String tmp_string(tmp_buff, sizeof(tmp_buff), scs); + String int_string(int_buff, sizeof(int_buff), scs); + tmp_string.length(0); + int_string.length(0); + + /* workers_count */ + protocol->prepare_for_resend(); + protocol->store(STRING_WITH_LEN("queue element count"), scs); + int_string.set((longlong) queue.elements, scs); + protocol->store(&int_string); + ret= protocol->write(); + + /* queue_data_locked */ + protocol->prepare_for_resend(); + protocol->store(STRING_WITH_LEN("queue data locked"), scs); + int_string.set((longlong) mutex_queue_data_locked, scs); + protocol->store(&int_string); + ret= protocol->write(); + + /* last locked at*/ + protocol->prepare_for_resend(); + protocol->store(STRING_WITH_LEN("queue last locked at"), scs); + tmp_string.length(scs->cset->snprintf(scs, (char*) tmp_string.ptr(), + tmp_string.alloced_length(), "%s::%d", + mutex_last_locked_in_func, + mutex_last_locked_at_line)); + protocol->store(&tmp_string); + ret= protocol->write(); + + /* last unlocked at*/ + protocol->prepare_for_resend(); + protocol->store(STRING_WITH_LEN("queue last unlocked at"), scs); + tmp_string.length(scs->cset->snprintf(scs, (char*) tmp_string.ptr(), + tmp_string.alloced_length(), "%s::%d", + mutex_last_unlocked_in_func, + mutex_last_unlocked_at_line)); + protocol->store(&tmp_string); + ret= protocol->write(); +#endif + DBUG_RETURN(FALSE); +} diff --git a/sql/event_queue.h b/sql/event_queue.h new file mode 100644 index 00000000000..142a866e5ba --- /dev/null +++ b/sql/event_queue.h @@ -0,0 +1,119 @@ +#ifndef _EVENT_QUEUE_H_ +#define _EVENT_QUEUE_H_ +/* Copyright (C) 2004-2006 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +class sp_name; +class Event_basic; +class Event_db_repository; +class Event_job_data; +class Event_queue_element; + +class THD; +class Event_scheduler_ng; + +class Event_queue +{ +public: + Event_queue(); + + void + init_mutexes(); + + void + deinit_mutexes(); + + bool + init_queue(Event_db_repository *db_repo, Event_scheduler_ng *sched); + + void + deinit_queue(); + + /* Methods for queue management follow */ + + int + create_event(THD *thd, LEX_STRING dbname, LEX_STRING name); + + int + update_event(THD *thd, LEX_STRING dbname, LEX_STRING name, + LEX_STRING *new_schema, LEX_STRING *new_name); + + void + drop_event(THD *thd, LEX_STRING dbname, LEX_STRING name); + + void + drop_schema_events(THD *thd, LEX_STRING schema); + + uint + events_count(); + + static bool + check_system_tables(THD *thd); + + void + recalculate_activation_times(THD *thd); + + Event_job_data * + get_top_for_execution_if_time(THD *thd, time_t now, struct timespec *abstime); + + bool + dump_internal_status(THD *thd); + +protected: + Event_queue_element * + find_event(LEX_STRING db, LEX_STRING name, bool remove_from_q); + + int + load_events_from_db(THD *thd); + + void + drop_matching_events(THD *thd, LEX_STRING pattern, + bool (*)(LEX_STRING *, Event_basic *)); + + void + empty_queue(); + + /* LOCK_event_queue is the mutex which protects the access to the queue. */ + pthread_mutex_t LOCK_event_queue; + + Event_db_repository *db_repository; + + uint mutex_last_locked_at_line; + uint mutex_last_unlocked_at_line; + const char* mutex_last_locked_in_func; + const char* mutex_last_unlocked_in_func; + bool mutex_queue_data_locked; + + /* helper functions for working with mutexes & conditionals */ + void + lock_data(const char *func, uint line); + + void + unlock_data(const char *func, uint line); + + void + notify_observers(); + + void + dbug_dump_queue(time_t now); + + Event_scheduler_ng *scheduler; + + /* The sorted queue with the Event_job_data objects */ + QUEUE queue; +}; + +#endif /* _EVENT_QUEUE_H_ */ diff --git a/sql/event_scheduler.cc b/sql/event_scheduler.cc index 1b4a0d290e6..f1c7d8394e3 100644 --- a/sql/event_scheduler.cc +++ b/sql/event_scheduler.cc @@ -14,2450 +14,3 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "mysql_priv.h" -#include "events_priv.h" -#include "events.h" -#include "event_timed.h" -#include "event_scheduler.h" -#include "sp_head.h" - -/* - ToDo: - 1. Talk to Alik to get a check for configure.in for my_time_t and time_t - 2. Look at guardian.h|cc to see its life cycle, has similarities. -*/ - - -/* - The scheduler is implemented as class Event_scheduler. Only one instance is - kept during the runtime of the server, by implementing the Singleton DP. - Object instance is always there because the memory is allocated statically - and initialized when the OS loader loads mysqld. This initialization is - bare. Extended initialization is done during the call to - Event_scheduler::init() in Events::init(). The reason for that late initialization - is that some subsystems needed to boot the Scheduler are not available at - earlier stages of the mysqld boot procedure. Events::init() is called in - mysqld.cc . If the mysqld is started with --event-scheduler=0 then - no initialization takes place and the scheduler is unavailable during this - server run. The server should be started with --event-scheduler=1 to have - the scheduler initialized and able to execute jobs. This starting alwa - s implies that the jobs execution will start immediately. If the server - is started with --event-scheduler=2 then the scheduler is started in suspended - state. Default state, if --event-scheduler is not specified is 2. - - The scheduler only manages execution of the events. Their creation, - alteration and deletion is delegated to other routines found in event.cc . - These routines interact with the scheduler : - - CREATE EVENT -> Event_scheduler::create_event() - - ALTER EVENT -> Event_scheduler::update_event() - - DROP EVENT -> Event_scheduler::drop_event() - - There is one mutex in the single Event_scheduler object which controls - the simultaneous access to the objects invariants. Using one lock makes - it easy to follow the workflow. This mutex is LOCK_scheduler_data. It is - initialized in Event_scheduler::init(). Which in turn is called by the - Facade class Events in event.cc, coming from init_thread_environment() from - mysqld.cc -> no concurrency at this point. It's destroyed in - Events::destroy_mutexes() called from clean_up_mutexes() in mysqld.cc . - - The full initialization is done in Event_scheduler::init() called from - Events::init(). It's done before any requests coming in, so this is a - guarantee for not having concurrency. - - The scheduler is started with Event_scheduler::start() and stopped with - Event_scheduler::stop(). When the scheduler starts it loads all events - from mysql.event table. Unfortunately, there is a race condition between - the event disk management functions and the scheduler ones - (add/replace/drop_event & load_events_from_db()), because the operations - do not happen under one global lock but the disk operations are guarded - by the MYISAM lock on mysql.event. In the same time, the queue operations - are guarded by LOCK_scheduler_data. If the scheduler is start()-ed during - server startup and stopped()-ed during server shutdown (in Events::shutdown() - called by kill_server() in mysqld.cc) these races does not exist. - - Since the user may want to temporarily inhibit execution of events the - scheduler can be suspended and then it can be forced to resume its - operations. The API call to perform these is - Event_scheduler::suspend_or_resume(enum enum_suspend_or_resume) . - When the scheduler is suspended the main scheduler thread, which ATM - happens to have thread_id 1, locks on a condition COND_suspend_or_resume. - When this is signal is sent for the reverse operation the main scheduler - loops continues to roll and execute events. - - When the scheduler is suspended all add/replace/drop_event() operations - work as expected and the modify the queue but no events execution takes - place. - - In contrast to the previous scheduler implementation, found in - event_executor.cc, the start, shutdown, suspend and resume are synchronous - operations. As a whole all operations are synchronized and no busy waits - are used except in stop_all_running_events(), which waits until all - running event worker threads have finished. It would have been nice to - use a conditional on which this method will wait and the last thread to - finish would signal it but this implies subclassing THD. - - The scheduler does not keep a counter of how many event worker threads are - running, at any specific moment, because this will copy functionality - already existing in the server. Namely, all THDs are registered in the - global `threads` array. THD has member variable system_thread which - identifies the type of thread. Connection threads being NON_SYSTEM_THREAD, - all other have their enum value. Important for the scheduler are - SYSTEM_THREAD_EVENT_SCHEDULER and SYSTEM_THREAD_EVENT_WORKER. - - Class THD subclasses class ilink, which is the linked list of all threads. - When a THD instance is destroyed it's being removed from threads, thus - no manual intervention is needed. On the contrary registering is manual - with threads.append() . Traversing the threads array every time a subclass - of THD, for instance if we would have had THD_scheduler_worker to see - how many events we have and whether the scheduler is shutting down will - take much time and lead to a deadlock. stop_all_running_events() is called - under LOCK_scheduler_data. If the THD_scheduler_worker was aware of - the single Event_scheduler instance it will try to check - Event_scheduler::state but for this it would need to acquire - LOCK_scheduler_data => deadlock. Thus stop_all_running_events() uses a - busy wait. - - DROP DATABASE DDL should drop all events defined in a specific schema. - DROP USER also should drop all events who has as definer the user being - dropped (this one is not addressed at the moment but a hook exists). For - this specific needs Event_scheduler::drop_matching_events() is - implemented. Which expects a callback to be applied on every object in - the queue. Thus events that match specific schema or user, will be - removed from the queue. The exposed interface is : - - Event_scheduler::drop_schema_events() - - Event_scheduler::drop_user_events() - - This bulk dropping happens under LOCK_scheduler_data, thus no two or - more threads can execute it in parallel. However, DROP DATABASE is also - synchronized, currently, in the server thus this does not impact the - overall performance. In addition, DROP DATABASE is not that often - executed DDL. - - Though the interface to the scheduler is only through the public methods - of class Event_scheduler, there are currently few functions which are - used during its operations. Namely : - - static evex_print_warnings() - After every event execution all errors/warnings are dumped, so the user - can see in case of a problem what the problem was. - - - static init_event_thread() - This function is both used by event_scheduler_thread() and - event_worker_thread(). It initializes the THD structure. The - initialization looks pretty similar to the one in slave.cc done for the - replication threads. However, though the similarities it cannot be - factored out to have one routine. - - - static event_scheduler_thread() - Because our way to register functions to be used by the threading library - does not allow usage of static methods this function is used to start the - scheduler in it. It does THD initialization and then calls - Event_scheduler::run(). - - - static event_worker_thread() - With already stated the reason for not being able to use methods, this - function executes the worker threads. - - The execution of events is, to some extent, synchronized to inhibit race - conditions when Event_timed::thread_id is being updated with the thread_id of - the THD in which the event is being executed. The thread_id is in the - Event_timed object because we need to be able to kill quickly a specific - event during ALTER/DROP EVENT without traversing the global `threads` array. - However, this makes the scheduler's code more complicated. The event worker - thread is started by Event_timed::spawn_now(), which in turn calls - pthread_create(). The thread_id which will be associated in init_event_thread - is not known in advance thus the registering takes place in - event_worker_thread(). This registering has to be synchronized under - LOCK_scheduler_data, so no kill_event() on a object in - replace_event/drop_event/drop_matching_events() could take place. - - This synchronization is done through class Worker_thread_param that is - local to this file. Event_scheduler::execute_top() is called under - LOCK_scheduler_data. This method : - 1. Creates an instance of Worker_thread_param on the stack - 2. Locks Worker_thread_param::LOCK_started - 3. Calls Event_timed::spawn_now() which in turn creates a new thread. - 4. Locks on Worker_thread_param::COND_started_or_stopped and waits till the - worker thread send signal. The code is spurious wake-up safe because - Worker_thread_param::started is checked. - 5. The worker thread initializes its THD, then sets Event_timed::thread_id, - sets Worker_thread_param::started to TRUE and sends back - Worker_thread_param::COND_started. From this moment on, the event - is being executed and could be killed by using Event_timed::thread_id. - When Event_timed::spawn_thread_finish() is called in the worker thread, - it sets thread_id to 0. From this moment on, the worker thread should not - touch the Event_timed instance. - - - The life-cycle of the server is a FSA. - enum enum_state Event_scheduler::state keeps the state of the scheduler. - - The states are: - - |---UNINITIALIZED - | - | |------------------> IN_SHUTDOWN - --> INITIALIZED -> COMMENCING ---> RUNNING ----------| - ^ ^ | | ^ | - | |- CANTSTART <--| | |- SUSPENDED <-| - |______________________________| - - - UNINITIALIZED :The object is created and only the mutex is initialized - - INITIALIZED :All member variables are initialized - - COMMENCING :The scheduler is starting, no other attempt to start - should succeed before the state is back to INITIALIZED. - - CANTSTART :Set by the ::run() method in case it can't start for some - reason. In this case the connection thread that tries to - start the scheduler sees that some error has occurred and - returns an error to the user. Finally, the connection - thread sets the state to INITIALIZED, so further attempts - to start the scheduler could be made. - - RUNNING :The scheduler is running. New events could be added, - dropped, altered. The scheduler could be stopped. - - SUSPENDED :Like RUNNING but execution of events does not take place. - Operations on the memory queue are possible. - - IN_SHUTDOWN :The scheduler is shutting down, due to request by setting - the global event_scheduler to 0/FALSE, or because of a - KILL command sent by a user to the master thread. - - In every method the macros LOCK_SCHEDULER_DATA() and UNLOCK_SCHEDULER_DATA() - are used for (un)locking purposes. They are used to save the programmer - from typing everytime - lock_data(__FUNCTION__, __LINE__); - All locking goes through Event_scheduler::lock_data() and ::unlock_data(). - These two functions then record in variables where for last time - LOCK_scheduler_data was locked and unlocked (two different variables). In - multithreaded environment, in some cases they make no sense but are useful for - inspecting deadlocks without having the server debug log turned on and the - server is still running. - - The same strategy is used for conditional variables. - Event_scheduler::cond_wait() is invoked from all places with parameter - an enum enum_cond_vars. In this manner, it's possible to inspect the last - on which condition the last call to cond_wait() was waiting. If the server - was started with debug trace switched on, the trace file also holds information - about conditional variables used. -*/ - -#ifdef __GNUC__ -#if __GNUC__ >= 2 -#define SCHED_FUNC __FUNCTION__ -#endif -#else -#define SCHED_FUNC "<unknown>" -#endif - -#define LOCK_SCHEDULER_DATA() lock_data(SCHED_FUNC, __LINE__) -#define UNLOCK_SCHEDULER_DATA() unlock_data(SCHED_FUNC, __LINE__) - - -#ifndef DBUG_OFF -static -LEX_STRING states_names[] = -{ - {(char*) STRING_WITH_LEN("UNINITIALIZED")}, - {(char*) STRING_WITH_LEN("INITIALIZED")}, - {(char*) STRING_WITH_LEN("COMMENCING")}, - {(char*) STRING_WITH_LEN("CANTSTART")}, - {(char*) STRING_WITH_LEN("RUNNING")}, - {(char*) STRING_WITH_LEN("SUSPENDED")}, - {(char*) STRING_WITH_LEN("IN_SHUTDOWN")} -}; -#endif - - -Event_scheduler -Event_scheduler::singleton; - - -const char * const -Event_scheduler::cond_vars_names[Event_scheduler::COND_LAST] = -{ - "new work", - "started or stopped", - "suspend or resume" -}; - - -class Worker_thread_param -{ -public: - Event_timed *et; - pthread_mutex_t LOCK_started; - pthread_cond_t COND_started; - bool started; - - Worker_thread_param(Event_timed *etn):et(etn), started(FALSE) - { - pthread_mutex_init(&LOCK_started, MY_MUTEX_INIT_FAST); - pthread_cond_init(&COND_started, NULL); - } - - ~Worker_thread_param() - { - pthread_mutex_destroy(&LOCK_started); - pthread_cond_destroy(&COND_started); - } -}; - - -/* - Compares the execute_at members of 2 Event_timed instances. - Used as callback for the prioritized queue when shifting - elements inside. - - SYNOPSIS - event_timed_compare_q() - - vptr - not used (set it to NULL) - a - first Event_timed object - b - second Event_timed object - - RETURN VALUE - -1 - a->execute_at < b->execute_at - 0 - a->execute_at == b->execute_at - 1 - a->execute_at > b->execute_at - - NOTES - execute_at.second_part is not considered during comparison -*/ - -static int -event_timed_compare_q(void *vptr, byte* a, byte *b) -{ - return my_time_compare(&((Event_timed *)a)->execute_at, - &((Event_timed *)b)->execute_at); -} - - -/* - Prints the stack of infos, warnings, errors from thd to - the console so it can be fetched by the logs-into-tables and - checked later. - - SYNOPSIS - evex_print_warnings - thd - thread used during the execution of the event - et - the event itself -*/ - -static void -evex_print_warnings(THD *thd, Event_timed *et) -{ - MYSQL_ERROR *err; - DBUG_ENTER("evex_print_warnings"); - if (!thd->warn_list.elements) - DBUG_VOID_RETURN; - - char msg_buf[10 * STRING_BUFFER_USUAL_SIZE]; - char prefix_buf[5 * STRING_BUFFER_USUAL_SIZE]; - String prefix(prefix_buf, sizeof(prefix_buf), system_charset_info); - prefix.length(0); - prefix.append("SCHEDULER: ["); - - append_identifier(thd, &prefix, et->definer_user.str, et->definer_user.length); - prefix.append('@'); - append_identifier(thd, &prefix, et->definer_host.str, et->definer_host.length); - prefix.append("][", 2); - append_identifier(thd,&prefix, et->dbname.str, et->dbname.length); - prefix.append('.'); - append_identifier(thd,&prefix, et->name.str, et->name.length); - prefix.append("] ", 2); - - List_iterator_fast<MYSQL_ERROR> it(thd->warn_list); - while ((err= it++)) - { - String err_msg(msg_buf, sizeof(msg_buf), system_charset_info); - /* set it to 0 or we start adding at the end. That's the trick ;) */ - err_msg.length(0); - err_msg.append(prefix); - err_msg.append(err->msg, strlen(err->msg), system_charset_info); - err_msg.append("]"); - DBUG_ASSERT(err->level < 3); - (sql_print_message_handlers[err->level])("%*s", err_msg.length(), - err_msg.c_ptr()); - } - DBUG_VOID_RETURN; -} - - -/* - Inits an scheduler thread handler, both the main and a worker - - SYNOPSIS - init_event_thread() - thd - the THD of the thread. Has to be allocated by the caller. - - NOTES - 1. The host of the thead is my_localhost - 2. thd->net is initted with NULL - no communication. - - RETURN VALUE - 0 OK - -1 Error -*/ - -static int -init_event_thread(THD** t, enum enum_thread_type thread_type) -{ - THD *thd= *t; - thd->thread_stack= (char*)t; // remember where our stack is - DBUG_ENTER("init_event_thread"); - thd->client_capabilities= 0; - thd->security_ctx->master_access= 0; - thd->security_ctx->db_access= 0; - thd->security_ctx->host_or_ip= (char*)my_localhost; - my_net_init(&thd->net, 0); - thd->net.read_timeout= slave_net_timeout; - thd->slave_thread= 0; - thd->options|= OPTION_AUTO_IS_NULL; - thd->client_capabilities|= CLIENT_MULTI_RESULTS; - thd->real_id=pthread_self(); - VOID(pthread_mutex_lock(&LOCK_thread_count)); - thd->thread_id= thread_id++; - threads.append(thd); - thread_count++; - thread_running++; - VOID(pthread_mutex_unlock(&LOCK_thread_count)); - - if (init_thr_lock() || thd->store_globals()) - { - thd->cleanup(); - DBUG_RETURN(-1); - } - -#if !defined(__WIN__) && !defined(OS2) && !defined(__NETWARE__) - sigset_t set; - VOID(sigemptyset(&set)); // Get mask in use - VOID(pthread_sigmask(SIG_UNBLOCK,&set,&thd->block_signals)); -#endif - - /* - Guarantees that we will see the thread in SHOW PROCESSLIST though its - vio is NULL. - */ - thd->system_thread= thread_type; - - thd->proc_info= "Initialized"; - thd->version= refresh_version; - thd->set_time(); - - DBUG_RETURN(0); -} - - -/* - Inits the main scheduler thread and then calls Event_scheduler::run() - of arg. - - SYNOPSIS - event_scheduler_thread() - arg void* ptr to Event_scheduler - - NOTES - 1. The host of the thead is my_localhost - 2. thd->net is initted with NULL - no communication. - 3. The reason to have a proxy function is that it's not possible to - use a method as function to be executed in a spawned thread: - - our pthread_hander_t macro uses extern "C" - - separating thread setup from the real execution loop is also to be - considered good. - - RETURN VALUE - 0 OK -*/ - -pthread_handler_t -event_scheduler_thread(void *arg) -{ - /* needs to be first for thread_stack */ - THD *thd= NULL; - Event_scheduler *scheduler= (Event_scheduler *) arg; - - DBUG_ENTER("event_scheduler_thread"); - - my_thread_init(); - pthread_detach_this_thread(); - - /* note that constructor of THD uses DBUG_ ! */ - if (!(thd= new THD) || init_event_thread(&thd, SYSTEM_THREAD_EVENT_SCHEDULER)) - { - sql_print_error("SCHEDULER: Cannot init manager event thread."); - scheduler->report_error_during_start(); - } - else - { - thd->security_ctx->set_user((char*)"event_scheduler"); - - sql_print_information("SCHEDULER: Manager thread booting"); - if (Event_scheduler::check_system_tables(thd)) - scheduler->report_error_during_start(); - else - scheduler->run(thd); - - /* - NOTE: Don't touch `scheduler` after this point because we have notified - the - thread which shuts us down that we have finished cleaning. In this - very moment a new scheduler thread could be started and a crash is - not welcome. - */ - } - - /* - If we cannot create THD then don't decrease because we haven't touched - thread_count and thread_running in init_event_thread() which was never - called. In init_event_thread() thread_count and thread_running are - always increased even in the case the method returns an error. - */ - if (thd) - { - thd->proc_info= "Clearing"; - DBUG_ASSERT(thd->net.buff != 0); - net_end(&thd->net); - pthread_mutex_lock(&LOCK_thread_count); - thread_count--; - thread_running--; - delete thd; - pthread_mutex_unlock(&LOCK_thread_count); - } - my_thread_end(); - DBUG_RETURN(0); // Can't return anything here -} - - -/* - Function that executes an event in a child thread. Setups the - environment for the event execution and cleans after that. - - SYNOPSIS - event_worker_thread() - arg The Event_timed object to be processed - - RETURN VALUE - 0 OK -*/ - -pthread_handler_t -event_worker_thread(void *arg) -{ - THD *thd; /* needs to be first for thread_stack */ - Worker_thread_param *param= (Worker_thread_param *) arg; - Event_timed *event= param->et; - int ret; - bool startup_error= FALSE; - Security_context *save_ctx; - /* this one is local and not needed after exec */ - Security_context security_ctx; - - DBUG_ENTER("event_worker_thread"); - DBUG_PRINT("enter", ("event=[%s.%s]", event->dbname.str, event->name.str)); - - my_thread_init(); - pthread_detach_this_thread(); - - if (!(thd= new THD) || init_event_thread(&thd, SYSTEM_THREAD_EVENT_WORKER)) - { - sql_print_error("SCHEDULER: Startup failure."); - startup_error= TRUE; - event->spawn_thread_finish(thd); - } - else - event->set_thread_id(thd->thread_id); - - DBUG_PRINT("info", ("master_access=%d db_access=%d", - thd->security_ctx->master_access, thd->security_ctx->db_access)); - /* - If we don't change it before we send the signal back, then an intermittent - DROP EVENT will take LOCK_scheduler_data and try to kill this thread, because - event->thread_id is already real. However, because thd->security_ctx->user - is not initialized then a crash occurs in kill_one_thread(). Thus, we have - to change the context before sending the signal. We are under - LOCK_scheduler_data being held by Event_scheduler::run() -> ::execute_top(). - */ - change_security_context(thd, event->definer_user, event->definer_host, - event->dbname, &security_ctx, &save_ctx); - DBUG_PRINT("info", ("master_access=%d db_access=%d", - thd->security_ctx->master_access, thd->security_ctx->db_access)); - - /* Signal the scheduler thread that we have started successfully */ - pthread_mutex_lock(¶m->LOCK_started); - param->started= TRUE; - pthread_cond_signal(¶m->COND_started); - pthread_mutex_unlock(¶m->LOCK_started); - - if (!startup_error) - { - thd->init_for_queries(); - thd->enable_slow_log= TRUE; - - event->set_thread_id(thd->thread_id); - sql_print_information("SCHEDULER: [%s.%s of %s] executing in thread %lu", - event->dbname.str, event->name.str, - event->definer.str, thd->thread_id); - - ret= event->execute(thd, thd->mem_root); - evex_print_warnings(thd, event); - sql_print_information("SCHEDULER: [%s.%s of %s] executed. RetCode=%d", - event->dbname.str, event->name.str, - event->definer.str, ret); - if (ret == EVEX_COMPILE_ERROR) - sql_print_information("SCHEDULER: COMPILE ERROR for event %s.%s of %s", - event->dbname.str, event->name.str, - event->definer.str); - else if (ret == EVEX_MICROSECOND_UNSUP) - sql_print_information("SCHEDULER: MICROSECOND is not supported"); - - DBUG_PRINT("info", ("master_access=%d db_access=%d", - thd->security_ctx->master_access, thd->security_ctx->db_access)); - - /* If true is returned, we are expected to free it */ - if (event->spawn_thread_finish(thd)) - { - DBUG_PRINT("info", ("Freeing object pointer")); - delete event; - } - } - - if (thd) - { - thd->proc_info= "Clearing"; - DBUG_ASSERT(thd->net.buff != 0); - /* - Free it here because net.vio is NULL for us => THD::~THD will check it - and won't call net_end(&net); See also replication code. - */ - net_end(&thd->net); - DBUG_PRINT("info", ("Worker thread %lu exiting", thd->thread_id)); - VOID(pthread_mutex_lock(&LOCK_thread_count)); - thread_count--; - thread_running--; - delete thd; - VOID(pthread_mutex_unlock(&LOCK_thread_count)); - } - - my_thread_end(); - DBUG_RETURN(0); // Can't return anything here -} - - -/* - Constructor of class Event_scheduler. - - SYNOPSIS - Event_scheduler::Event_scheduler() -*/ - -Event_scheduler::Event_scheduler() - :state(UNINITIALIZED), start_scheduler_suspended(FALSE), - thread_id(0), mutex_last_locked_at_line(0), - mutex_last_unlocked_at_line(0), mutex_last_locked_in_func(""), - mutex_last_unlocked_in_func(""), cond_waiting_on(COND_NONE), - mutex_scheduler_data_locked(FALSE) -{ -} - - -/* - Returns the singleton instance of the class. - - SYNOPSIS - Event_scheduler::get_instance() - - RETURN VALUE - address -*/ - -Event_scheduler* -Event_scheduler::get_instance() -{ - DBUG_ENTER("Event_scheduler::get_instance"); - DBUG_RETURN(&singleton); -} - - -/* - The implementation of full-fledged initialization. - - SYNOPSIS - Event_scheduler::init() - - RETURN VALUE - FALSE OK - TRUE Error -*/ - -bool -Event_scheduler::init() -{ - int i= 0; - bool ret= FALSE; - DBUG_ENTER("Event_scheduler::init"); - DBUG_PRINT("enter", ("this=%p", this)); - - LOCK_SCHEDULER_DATA(); - for (;i < COND_LAST; i++) - if (pthread_cond_init(&cond_vars[i], NULL)) - { - sql_print_error("SCHEDULER: Unable to initalize conditions"); - ret= TRUE; - goto end; - } - - /* init memory root */ - init_alloc_root(&scheduler_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC); - - if (init_queue_ex(&queue, 30 /*num_el*/, 0 /*offset*/, 0 /*smallest_on_top*/, - event_timed_compare_q, NULL, 30 /*auto_extent*/)) - { - sql_print_error("SCHEDULER: Can't initialize the execution queue"); - ret= TRUE; - goto end; - } - - if (sizeof(my_time_t) != sizeof(time_t)) - { - sql_print_error("SCHEDULER: sizeof(my_time_t) != sizeof(time_t) ." - "The scheduler may not work correctly. Stopping."); - DBUG_ASSERT(0); - ret= TRUE; - goto end; - } - - state= INITIALIZED; -end: - UNLOCK_SCHEDULER_DATA(); - DBUG_RETURN(ret); -} - - -/* - Frees all memory allocated by the scheduler object. - - SYNOPSIS - Event_scheduler::destroy() - - RETURN VALUE - FALSE OK - TRUE Error -*/ - -void -Event_scheduler::destroy() -{ - DBUG_ENTER("Event_scheduler"); - - LOCK_SCHEDULER_DATA(); - switch (state) { - case UNINITIALIZED: - break; - case INITIALIZED: - delete_queue(&queue); - free_root(&scheduler_root, MYF(0)); - int i; - for (i= 0; i < COND_LAST; i++) - pthread_cond_destroy(&cond_vars[i]); - state= UNINITIALIZED; - break; - default: - sql_print_error("SCHEDULER: Destroying while state is %d", state); - /* I trust my code but ::safe() > ::sorry() */ - DBUG_ASSERT(0); - break; - } - UNLOCK_SCHEDULER_DATA(); - - DBUG_VOID_RETURN; -} - - -/* - Creates an event in the scheduler queue - - SYNOPSIS - Event_scheduler::create_event() - et The event to add - check_existence Whether to check if already loaded. - - RETURN VALUE - OP_OK OK or scheduler not working - OP_LOAD_ERROR Error during loading from disk -*/ - -enum Event_scheduler::enum_error_code -Event_scheduler::create_event(THD *thd, Event_timed *et, bool check_existence) -{ - enum enum_error_code res; - Event_timed *et_new; - DBUG_ENTER("Event_scheduler::create_event"); - DBUG_PRINT("enter", ("thd=%p et=%p lock=%p",thd,et,&LOCK_scheduler_data)); - - LOCK_SCHEDULER_DATA(); - if (!is_running_or_suspended()) - { - DBUG_PRINT("info", ("scheduler not running but %d. doing nothing", state)); - UNLOCK_SCHEDULER_DATA(); - DBUG_RETURN(OP_OK); - } - if (check_existence && find_event(et, FALSE)) - { - res= OP_ALREADY_EXISTS; - goto end; - } - - /* We need to load the event on scheduler_root */ - if (!(res= load_named_event(thd, et, &et_new))) - { - queue_insert_safe(&queue, (byte *) et_new); - DBUG_PRINT("info", ("Sending COND_new_work")); - pthread_cond_signal(&cond_vars[COND_new_work]); - } - else if (res == OP_DISABLED_EVENT) - res= OP_OK; -end: - UNLOCK_SCHEDULER_DATA(); - DBUG_RETURN(res); -} - - -/* - Drops an event from the scheduler queue - - SYNOPSIS - Event_scheduler::drop_event() - etn The event to drop - state Wait the event or kill&drop - - RETURN VALUE - FALSE OK (replaced or scheduler not working) - TRUE Failure -*/ - -bool -Event_scheduler::drop_event(THD *thd, Event_timed *et) -{ - int res; - Event_timed *et_old; - DBUG_ENTER("Event_scheduler::drop_event"); - DBUG_PRINT("enter", ("thd=%p et=%p lock=%p",thd,et,&LOCK_scheduler_data)); - - LOCK_SCHEDULER_DATA(); - if (!is_running_or_suspended()) - { - DBUG_PRINT("info", ("scheduler not running but %d. doing nothing", state)); - UNLOCK_SCHEDULER_DATA(); - DBUG_RETURN(OP_OK); - } - - if (!(et_old= find_event(et, TRUE))) - DBUG_PRINT("info", ("No such event found, probably DISABLED")); - - UNLOCK_SCHEDULER_DATA(); - - /* See comments in ::replace_event() why this is split in two parts. */ - if (et_old) - { - switch ((res= et_old->kill_thread(thd))) { - case EVEX_CANT_KILL: - /* Don't delete but continue */ - et_old->flags |= EVENT_FREE_WHEN_FINISHED; - break; - case 0: - /* - kill_thread() waits till the spawned thread finishes after it's - killed. Hence, we delete here memory which is no more referenced from - a running thread. - */ - delete et_old; - /* - We don't signal COND_new_work here because: - 1. Even if the dropped event is on top of the queue this will not - move another one to be executed before the time the one on the - top (but could be at the same second as the dropped one) - 2. If this was the last event on the queue, then pthread_cond_timedwait - in ::run() will finish and then see that the queue is empty and - call cond_wait(). Hence, no need to interrupt the blocked - ::run() thread. - */ - break; - default: - sql_print_error("SCHEDULER: Got unexpected error %d", res); - DBUG_ASSERT(0); - } - } - - DBUG_RETURN(FALSE); -} - - -/* - Updates an event from the scheduler queue - - SYNOPSIS - Event_scheduler::replace_event() - et The event to replace(add) into the queue - state Async or sync stopping - - RETURN VALUE - OP_OK OK or scheduler not working - OP_LOAD_ERROR Error during loading from disk - OP_ALREADY_EXISTS Event already in the queue -*/ - -enum Event_scheduler::enum_error_code -Event_scheduler::update_event(THD *thd, Event_timed *et, - LEX_STRING *new_schema, - LEX_STRING *new_name) -{ - enum enum_error_code res; - Event_timed *et_old, *et_new= NULL; - LEX_STRING old_schema, old_name; - - LINT_INIT(old_schema.str); - LINT_INIT(old_schema.length); - LINT_INIT(old_name.str); - LINT_INIT(old_name.length); - - DBUG_ENTER("Event_scheduler::update_event"); - DBUG_PRINT("enter", ("thd=%p et=%p et=[%s.%s] lock=%p", - thd, et, et->dbname.str, et->name.str, &LOCK_scheduler_data)); - - LOCK_SCHEDULER_DATA(); - if (!is_running_or_suspended()) - { - DBUG_PRINT("info", ("scheduler not running but %d. doing nothing", state)); - UNLOCK_SCHEDULER_DATA(); - DBUG_RETURN(OP_OK); - } - - if (!(et_old= find_event(et, TRUE))) - DBUG_PRINT("info", ("%s.%s not found cached, probably was DISABLED", - et->dbname.str, et->name.str)); - - if (new_schema && new_name) - { - old_schema= et->dbname; - old_name= et->name; - et->dbname= *new_schema; - et->name= *new_name; - } - /* - We need to load the event (it's strings but on the object itself) - on scheduler_root. et_new could be NULL : - 1. Error occured - 2. If the replace is DISABLED, we don't load it into the queue. - */ - if (!(res= load_named_event(thd, et, &et_new))) - { - queue_insert_safe(&queue, (byte *) et_new); - DBUG_PRINT("info", ("Sending COND_new_work")); - pthread_cond_signal(&cond_vars[COND_new_work]); - } - else if (res == OP_DISABLED_EVENT) - res= OP_OK; - - if (new_schema && new_name) - { - et->dbname= old_schema; - et->name= old_name; - } - - UNLOCK_SCHEDULER_DATA(); - /* - Andrey: Is this comment still truthful ??? - - We don't move this code above because a potential kill_thread will call - THD::awake(). Which in turn will try to acqure mysys_var->current_mutex, - which is LOCK_scheduler_data on which the COND_new_work in ::run() locks. - Hence, we try to acquire a lock which we have already acquired and we run - into an assert. Holding LOCK_scheduler_data however is not needed because - we don't touch any invariant of the scheduler anymore. ::drop_event() does - the same. - */ - if (et_old) - { - switch (et_old->kill_thread(thd)) { - case EVEX_CANT_KILL: - /* Don't delete but continue */ - et_old->flags |= EVENT_FREE_WHEN_FINISHED; - break; - case 0: - /* - kill_thread() waits till the spawned thread finishes after it's - killed. Hence, we delete here memory which is no more referenced from - a running thread. - */ - delete et_old; - /* - We don't signal COND_new_work here because: - 1. Even if the dropped event is on top of the queue this will not - move another one to be executed before the time the one on the - top (but could be at the same second as the dropped one) - 2. If this was the last event on the queue, then pthread_cond_timedwait - in ::run() will finish and then see that the queue is empty and - call cond_wait(). Hence, no need to interrupt the blocked - ::run() thread. - */ - break; - default: - DBUG_ASSERT(0); - } - } - - DBUG_RETURN(res); -} - - -/* - Searches for an event in the scheduler queue - - SYNOPSIS - Event_scheduler::find_event() - etn The event to find - comparator The function to use for comparing - remove_from_q If found whether to remove from the Q - - RETURN VALUE - NULL Not found - otherwise Address - - NOTE - The caller should do the locking also the caller is responsible for - actual signalling in case an event is removed from the queue - (signalling COND_new_work for instance). -*/ - -Event_timed * -Event_scheduler::find_event(Event_timed *etn, bool remove_from_q) -{ - uint i; - DBUG_ENTER("Event_scheduler::find_event"); - - for (i= 0; i < queue.elements; ++i) - { - Event_timed *et= (Event_timed *) queue_element(&queue, i); - DBUG_PRINT("info", ("[%s.%s]==[%s.%s]?", etn->dbname.str, etn->name.str, - et->dbname.str, et->name.str)); - if (event_timed_identifier_equal(etn, et)) - { - if (remove_from_q) - queue_remove(&queue, i); - DBUG_RETURN(et); - } - } - - DBUG_RETURN(NULL); -} - - -/* - Drops all events from the in-memory queue and disk that match - certain pattern evaluated by a comparator function - - SYNOPSIS - Event_scheduler::drop_matching_events() - thd THD - pattern A pattern string - comparator The function to use for comparing - - RETURN VALUE - -1 Scheduler not working - >=0 Number of dropped events - - NOTE - Expected is the caller to acquire lock on LOCK_scheduler_data -*/ - -void -Event_scheduler::drop_matching_events(THD *thd, LEX_STRING *pattern, - bool (*comparator)(Event_timed *,LEX_STRING *)) -{ - DBUG_ENTER("Event_scheduler::drop_matching_events"); - DBUG_PRINT("enter", ("pattern=%*s state=%d", pattern->length, pattern->str, - state)); - if (is_running_or_suspended()) - { - uint i= 0, dropped= 0; - while (i < queue.elements) - { - Event_timed *et= (Event_timed *) queue_element(&queue, i); - DBUG_PRINT("info", ("[%s.%s]?", et->dbname.str, et->name.str)); - if (comparator(et, pattern)) - { - /* - The queue is ordered. If we remove an element, then all elements after - it will shift one position to the left, if we imagine it as an array - from left to the right. In this case we should not increment the - counter and the (i < queue.elements) condition is ok. - */ - queue_remove(&queue, i); - - /* See replace_event() */ - switch (et->kill_thread(thd)) { - case EVEX_CANT_KILL: - /* Don't delete but continue */ - et->flags |= EVENT_FREE_WHEN_FINISHED; - ++dropped; - break; - case 0: - delete et; - ++dropped; - break; - default: - DBUG_ASSERT(0); - } - } - else - i++; - } - DBUG_PRINT("info", ("Dropped %lu", dropped)); - } - /* - Don't send COND_new_work because no need to wake up the scheduler thread. - When it wakes next time up it will recalculate how much more it should - sleep if the top of the queue has been changed by this method. - */ - - DBUG_VOID_RETURN; -} - - -/* - Drops all events from the in-memory queue and disk that are from - certain schema. - - SYNOPSIS - Event_scheduler::drop_schema_events() - thd THD - db The schema name - - RETURN VALUE - -1 Scheduler not working - >=0 Number of dropped events -*/ - -int -Event_scheduler::drop_schema_events(THD *thd, LEX_STRING *schema) -{ - int ret; - DBUG_ENTER("Event_scheduler::drop_schema_events"); - LOCK_SCHEDULER_DATA(); - if (is_running_or_suspended()) - drop_matching_events(thd, schema, event_timed_db_equal); - - ret= db_drop_events_from_table(thd, schema); - UNLOCK_SCHEDULER_DATA(); - - DBUG_RETURN(ret); -} - - -extern pthread_attr_t connection_attrib; - - -/* - Starts the event scheduler - - SYNOPSIS - Event_scheduler::start() - - RETURN VALUE - FALSE OK - TRUE Error -*/ - -bool -Event_scheduler::start() -{ - bool ret= FALSE; - pthread_t th; - DBUG_ENTER("Event_scheduler::start"); - - LOCK_SCHEDULER_DATA(); - /* If already working or starting don't make another attempt */ - DBUG_ASSERT(state == INITIALIZED); - if (state > INITIALIZED) - { - DBUG_PRINT("info", ("scheduler is already running or starting")); - ret= TRUE; - goto end; - } - - /* - Now if another thread calls start it will bail-out because the branch - above will be executed. Thus no two or more child threads will be forked. - If the child thread cannot start for some reason then `state` is set - to CANTSTART and COND_started is also signaled. In this case we - set `state` back to INITIALIZED so another attempt to start the scheduler - can be made. - */ - state= COMMENCING; - /* Fork */ - if (pthread_create(&th, &connection_attrib, event_scheduler_thread, - (void*)this)) - { - DBUG_PRINT("error", ("cannot create a new thread")); - state= INITIALIZED; - ret= TRUE; - goto end; - } - - /* Wait till the child thread has booted (w/ or wo success) */ - while (!is_running_or_suspended() && state != CANTSTART) - cond_wait(COND_started_or_stopped, &LOCK_scheduler_data); - - /* - If we cannot start for some reason then don't prohibit further attempts. - Set back to INITIALIZED. - */ - if (state == CANTSTART) - { - state= INITIALIZED; - ret= TRUE; - goto end; - } - -end: - UNLOCK_SCHEDULER_DATA(); - DBUG_RETURN(ret); -} - - -/* - Starts the event scheduler in suspended mode. - - SYNOPSIS - Event_scheduler::start_suspended() - - RETURN VALUE - TRUE OK - FALSE Error -*/ - -bool -Event_scheduler::start_suspended() -{ - DBUG_ENTER("Event_scheduler::start_suspended"); - start_scheduler_suspended= TRUE; - DBUG_RETURN(start()); -} - - - -/* - Report back that we cannot start. Used for ocasions where - we can't go into ::run() and have to report externally. - - SYNOPSIS - Event_scheduler::report_error_during_start() -*/ - -inline void -Event_scheduler::report_error_during_start() -{ - DBUG_ENTER("Event_scheduler::report_error_during_start"); - - LOCK_SCHEDULER_DATA(); - state= CANTSTART; - DBUG_PRINT("info", ("Sending back COND_started_or_stopped")); - pthread_cond_signal(&cond_vars[COND_started_or_stopped]); - UNLOCK_SCHEDULER_DATA(); - - DBUG_VOID_RETURN; -} - - -/* - The internal loop of the event scheduler - - SYNOPSIS - Event_scheduler::run() - thd Thread - - RETURN VALUE - FALSE OK - TRUE Failure -*/ - -bool -Event_scheduler::run(THD *thd) -{ - int ret; - struct timespec abstime; - DBUG_ENTER("Event_scheduler::run"); - DBUG_PRINT("enter", ("thd=%p", thd)); - - LOCK_SCHEDULER_DATA(); - ret= load_events_from_db(thd); - - if (!ret) - { - thread_id= thd->thread_id; - state= start_scheduler_suspended? SUSPENDED:RUNNING; - start_scheduler_suspended= FALSE; - } - else - state= CANTSTART; - - DBUG_PRINT("info", ("Sending back COND_started_or_stopped")); - pthread_cond_signal(&cond_vars[COND_started_or_stopped]); - if (ret) - { - UNLOCK_SCHEDULER_DATA(); - DBUG_RETURN(TRUE); - } - if (!check_n_suspend_if_needed(thd)) - UNLOCK_SCHEDULER_DATA(); - - sql_print_information("SCHEDULER: Manager thread started with id %lu", - thd->thread_id); - abstime.tv_nsec= 0; - while (is_running_or_suspended()) - { - Event_timed *et; - - LOCK_SCHEDULER_DATA(); - if (check_n_wait_for_non_empty_queue(thd)) - continue; - - /* On TRUE data is unlocked, go back to the beginning */ - if (check_n_suspend_if_needed(thd)) - continue; - - /* Guaranteed locked here */ - if (state == IN_SHUTDOWN || shutdown_in_progress) - { - UNLOCK_SCHEDULER_DATA(); - break; - } - DBUG_ASSERT(state == RUNNING); - - et= (Event_timed *)queue_top(&queue); - - /* Skip disabled events */ - if (et->status != Event_timed::ENABLED) - { - /* - It could be a one-timer scheduled for a time, already in the past when the - scheduler was suspended. - */ - sql_print_information("SCHEDULER: Found a disabled event %*s.%*s in the queue", - et->dbname.length, et->dbname.str, et->name.length, - et->name.str); - queue_remove(&queue, 0); - /* ToDo: check this again */ - if (et->dropped) - et->drop(thd); - delete et; - UNLOCK_SCHEDULER_DATA(); - continue; - } - thd->proc_info= (char *)"Computing"; - DBUG_PRINT("evex manager",("computing time to sleep till next exec")); - /* Timestamp is in UTC */ - abstime.tv_sec= sec_since_epoch_TIME(&et->execute_at); - - thd->end_time(); - if (abstime.tv_sec > thd->query_start()) - { - /* Event trigger time is in the future */ - thd->proc_info= (char *)"Sleep"; - DBUG_PRINT("info", ("Going to sleep. Should wakeup after approx %d secs", - abstime.tv_sec - thd->query_start())); - DBUG_PRINT("info", ("Entering condition because waiting for activation")); - /* - Use THD::enter_cond()/exit_cond() or we won't be able to kill a - sleeping thread. Though ::stop() can do it by sending COND_new_work - an user can't by just issuing 'KILL x'; . In the latter case - pthread_cond_timedwait() will wait till `abstime`. - "Sleeping until next time" - */ - thd->enter_cond(&cond_vars[COND_new_work],&LOCK_scheduler_data,"Sleeping"); - - pthread_cond_timedwait(&cond_vars[COND_new_work], &LOCK_scheduler_data, - &abstime); - - DBUG_PRINT("info", ("Manager woke up. state is %d", state)); - /* - If we get signal we should recalculate the whether it's the right time - because there could be : - 1. Spurious wake-up - 2. The top of the queue was changed (new one becase of add/drop/replace) - */ - /* This will do implicit UNLOCK_SCHEDULER_DATA() */ - thd->exit_cond(""); - } - else - { - thd->proc_info= (char *)"Executing"; - /* - Execute the event. An error may occur if a thread cannot be forked. - In this case stop the manager. - We should enter ::execute_top() with locked LOCK_scheduler_data. - */ - int ret= execute_top(thd); - UNLOCK_SCHEDULER_DATA(); - if (ret) - break; - } - } - - thd->proc_info= (char *)"Cleaning"; - - LOCK_SCHEDULER_DATA(); - /* - It's possible that a user has used (SQL)COM_KILL. Hence set the appropriate - state because it is only set by ::stop(). - */ - if (state != IN_SHUTDOWN) - { - DBUG_PRINT("info", ("We got KILL but the but not from ::stop()")); - state= IN_SHUTDOWN; - } - UNLOCK_SCHEDULER_DATA(); - - sql_print_information("SCHEDULER: Shutting down"); - - thd->proc_info= (char *)"Cleaning queue"; - clean_queue(thd); - THD_CHECK_SENTRY(thd); - - /* free mamager_root memory but don't destroy the root */ - thd->proc_info= (char *)"Cleaning memory root"; - free_root(&scheduler_root, MYF(0)); - THD_CHECK_SENTRY(thd); - - /* - We notify the waiting thread which shutdowns us that we have cleaned. - There are few more instructions to be executed in this pthread but - they don't affect manager structures thus it's safe to signal already - at this point. - */ - LOCK_SCHEDULER_DATA(); - thd->proc_info= (char *)"Sending shutdown signal"; - DBUG_PRINT("info", ("Sending COND_started_or_stopped")); - if (state == IN_SHUTDOWN) - pthread_cond_signal(&cond_vars[COND_started_or_stopped]); - - state= INITIALIZED; - /* - We set it here because ::run() can stop not only because of ::stop() - call but also because of `KILL x` - */ - thread_id= 0; - sql_print_information("SCHEDULER: Stopped"); - UNLOCK_SCHEDULER_DATA(); - - /* We have modified, we set back */ - thd->query= NULL; - thd->query_length= 0; - - DBUG_RETURN(FALSE); -} - - -/* - Executes the top element of the queue. Auxiliary method for ::run(). - - SYNOPSIS - Event_scheduler::execute_top() - - RETURN VALUE - FALSE OK - TRUE Failure - - NOTE - NO locking is done. EXPECTED is that the caller should have locked - the queue (w/ LOCK_scheduler_data). -*/ - -bool -Event_scheduler::execute_top(THD *thd) -{ - int spawn_ret_code; - bool ret= FALSE; - DBUG_ENTER("Event_scheduler::execute_top"); - DBUG_PRINT("enter", ("thd=%p", thd)); - - Event_timed *et= (Event_timed *)queue_top(&queue); - - /* Is it good idea to pass a stack address ?*/ - Worker_thread_param param(et); - - pthread_mutex_lock(¶m.LOCK_started); - /* - We don't lock LOCK_scheduler_data fpr workers_increment() because it's a - pre-requisite for calling the current_method. - */ - switch ((spawn_ret_code= et->spawn_now(event_worker_thread, ¶m))) { - case EVENT_EXEC_CANT_FORK: - /* - We don't lock LOCK_scheduler_data here because it's a pre-requisite - for calling the current_method. - */ - sql_print_error("SCHEDULER: Problem while trying to create a thread"); - ret= TRUE; - break; - case EVENT_EXEC_ALREADY_EXEC: - /* - We don't lock LOCK_scheduler_data here because it's a pre-requisite - for calling the current_method. - */ - sql_print_information("SCHEDULER: %s.%s in execution. Skip this time.", - et->dbname.str, et->name.str); - if ((et->flags & EVENT_EXEC_NO_MORE) || et->status == Event_timed::DISABLED) - queue_remove(&queue, 0);// 0 is top, internally 1 - else - queue_replaced(&queue); - break; - default: - DBUG_ASSERT(!spawn_ret_code); - if ((et->flags & EVENT_EXEC_NO_MORE) || et->status == Event_timed::DISABLED) - queue_remove(&queue, 0);// 0 is top, internally 1 - else - queue_replaced(&queue); - /* - We don't lock LOCK_scheduler_data here because it's a pre-requisite - for calling the current_method. - */ - if (likely(!spawn_ret_code)) - { - /* Wait the forked thread to start */ - do { - pthread_cond_wait(¶m.COND_started, ¶m.LOCK_started); - } while (!param.started); - } - /* - param was allocated on the stack so no explicit delete as well as - in this moment it's no more used in the spawned thread so it's safe - to be deleted. - */ - break; - } - pthread_mutex_unlock(¶m.LOCK_started); - /* `param` is on the stack and will be destructed by the compiler */ - - DBUG_RETURN(ret); -} - - -/* - Cleans the scheduler's queue. Auxiliary method for ::run(). - - SYNOPSIS - Event_scheduler::clean_queue() - thd Thread -*/ - -void -Event_scheduler::clean_queue(THD *thd) -{ - CHARSET_INFO *scs= system_charset_info; - uint i; - DBUG_ENTER("Event_scheduler::clean_queue"); - DBUG_PRINT("enter", ("thd=%p", thd)); - - LOCK_SCHEDULER_DATA(); - stop_all_running_events(thd); - UNLOCK_SCHEDULER_DATA(); - - sql_print_information("SCHEDULER: Emptying the queue"); - - /* empty the queue */ - for (i= 0; i < queue.elements; ++i) - { - Event_timed *et= (Event_timed *) queue_element(&queue, i); - et->free_sp(); - delete et; - } - resize_queue(&queue, 0); - - DBUG_VOID_RETURN; -} - - -/* - Stops all running events - - SYNOPSIS - Event_scheduler::stop_all_running_events() - thd Thread - - NOTE - LOCK_scheduler data must be acquired prior to call to this method -*/ - -void -Event_scheduler::stop_all_running_events(THD *thd) -{ - CHARSET_INFO *scs= system_charset_info; - uint i; - DYNAMIC_ARRAY running_threads; - THD *tmp; - DBUG_ENTER("Event_scheduler::stop_all_running_events"); - DBUG_PRINT("enter", ("workers_count=%d", workers_count())); - - my_init_dynamic_array(&running_threads, sizeof(ulong), 10, 10); - - bool had_super= FALSE; - VOID(pthread_mutex_lock(&LOCK_thread_count)); // For unlink from list - I_List_iterator<THD> it(threads); - while ((tmp=it++)) - { - if (tmp->command == COM_DAEMON) - continue; - if (tmp->system_thread == SYSTEM_THREAD_EVENT_WORKER) - push_dynamic(&running_threads, (gptr) &tmp->thread_id); - } - VOID(pthread_mutex_unlock(&LOCK_thread_count)); - - /* We need temporarily SUPER_ACL to be able to kill our offsprings */ - if (!(thd->security_ctx->master_access & SUPER_ACL)) - thd->security_ctx->master_access|= SUPER_ACL; - else - had_super= TRUE; - - char tmp_buff[10*STRING_BUFFER_USUAL_SIZE]; - char int_buff[STRING_BUFFER_USUAL_SIZE]; - String tmp_string(tmp_buff, sizeof(tmp_buff), scs); - String int_string(int_buff, sizeof(int_buff), scs); - tmp_string.length(0); - - for (i= 0; i < running_threads.elements; ++i) - { - int ret; - ulong thd_id= *dynamic_element(&running_threads, i, ulong*); - - int_string.set((longlong) thd_id,scs); - tmp_string.append(int_string); - if (i < running_threads.elements - 1) - tmp_string.append(' '); - - if ((ret= kill_one_thread(thd, thd_id, FALSE))) - { - sql_print_error("SCHEDULER: Error killing %lu code=%d", thd_id, ret); - break; - } - } - if (running_threads.elements) - sql_print_information("SCHEDULER: Killing workers :%s", tmp_string.c_ptr()); - - if (!had_super) - thd->security_ctx->master_access &= ~SUPER_ACL; - - delete_dynamic(&running_threads); - - sql_print_information("SCHEDULER: Waiting for worker threads to finish"); - - while (workers_count()) - my_sleep(100000); - - DBUG_VOID_RETURN; -} - - -/* - Stops the event scheduler - - SYNOPSIS - Event_scheduler::stop() - - RETURN VALUE - OP_OK OK - OP_CANT_KILL Error during stopping of manager thread - OP_NOT_RUNNING Manager not working - - NOTE - The caller must have acquited LOCK_scheduler_data. -*/ - -enum Event_scheduler::enum_error_code -Event_scheduler::stop() -{ - THD *thd= current_thd; - DBUG_ENTER("Event_scheduler::stop"); - DBUG_PRINT("enter", ("thd=%p", current_thd)); - - LOCK_SCHEDULER_DATA(); - if (!is_running_or_suspended()) - { - /* - One situation to be here is if there was a start that forked a new - thread but the new thread did not acquire yet LOCK_scheduler_data. - Hence, in this case return an error. - */ - DBUG_PRINT("info", ("manager not running but %d. doing nothing", state)); - UNLOCK_SCHEDULER_DATA(); - DBUG_RETURN(OP_NOT_RUNNING); - } - state= IN_SHUTDOWN; - - DBUG_PRINT("info", ("Manager thread has id %d", thread_id)); - sql_print_information("SCHEDULER: Killing manager thread %lu", thread_id); - - /* - Sending the COND_new_work to ::run() is a way to get this working without - race conditions. If we use kill_one_thread() it will call THD::awake() and - because in ::run() both THD::enter_cond()/::exit_cond() are used, - THD::awake() will try to lock LOCK_scheduler_data. If we UNLOCK it before, - then the pthread_cond_signal(COND_started_or_stopped) could be signaled in - ::run() and we can miss the signal before we relock. A way is to use - another mutex for this shutdown procedure but better not. - */ - pthread_cond_signal(&cond_vars[COND_new_work]); - /* Or we are suspended - then we should wake up */ - pthread_cond_signal(&cond_vars[COND_suspend_or_resume]); - - /* Guarantee we don't catch spurious signals */ - sql_print_information("SCHEDULER: Waiting the manager thread to reply"); - while (state != INITIALIZED) - { - DBUG_PRINT("info", ("Waiting for COND_started_or_stopped from the manager " - "thread. Current value of state is %d . " - "workers count=%d", state, workers_count())); - cond_wait(COND_started_or_stopped, &LOCK_scheduler_data); - } - DBUG_PRINT("info", ("Manager thread has cleaned up. Set state to INIT")); - UNLOCK_SCHEDULER_DATA(); - - DBUG_RETURN(OP_OK); -} - - -/* - Suspends or resumes the scheduler. - SUSPEND - it won't execute any event till resumed. - RESUME - it will resume if suspended. - - SYNOPSIS - Event_scheduler::suspend_or_resume() - - RETURN VALUE - OP_OK OK -*/ - -enum Event_scheduler::enum_error_code -Event_scheduler::suspend_or_resume( - enum Event_scheduler::enum_suspend_or_resume action) -{ - DBUG_ENTER("Event_scheduler::suspend_or_resume"); - DBUG_PRINT("enter", ("action=%d", action)); - - LOCK_SCHEDULER_DATA(); - - if ((action == SUSPEND && state == SUSPENDED) || - (action == RESUME && state == RUNNING)) - { - DBUG_PRINT("info", ("Either trying to suspend suspended or resume " - "running scheduler. Doing nothing.")); - } - else - { - /* Wake the main thread up if he is asleep */ - DBUG_PRINT("info", ("Sending signal")); - if (action==SUSPEND) - { - state= SUSPENDED; - pthread_cond_signal(&cond_vars[COND_new_work]); - } - else - { - state= RUNNING; - pthread_cond_signal(&cond_vars[COND_suspend_or_resume]); - } - DBUG_PRINT("info", ("Waiting on COND_suspend_or_resume")); - cond_wait(COND_suspend_or_resume, &LOCK_scheduler_data); - DBUG_PRINT("info", ("Got response")); - } - UNLOCK_SCHEDULER_DATA(); - DBUG_RETURN(OP_OK); -} - - -/* - Returns the number of executing events. - - SYNOPSIS - Event_scheduler::workers_count() -*/ - -uint -Event_scheduler::workers_count() -{ - THD *tmp; - uint count= 0; - - DBUG_ENTER("Event_scheduler::workers_count"); - VOID(pthread_mutex_lock(&LOCK_thread_count)); // For unlink from list - I_List_iterator<THD> it(threads); - while ((tmp=it++)) - { - if (tmp->command == COM_DAEMON) - continue; - if (tmp->system_thread == SYSTEM_THREAD_EVENT_WORKER) - ++count; - } - VOID(pthread_mutex_unlock(&LOCK_thread_count)); - DBUG_PRINT("exit", ("%d", count)); - DBUG_RETURN(count); -} - - -/* - Checks and suspends if needed - - SYNOPSIS - Event_scheduler::check_n_suspend_if_needed() - thd Thread - - RETURN VALUE - FALSE Not suspended, we haven't slept - TRUE We were suspended. LOCK_scheduler_data is unlocked. - - NOTE - The caller should have locked LOCK_scheduler_data! - The mutex will be unlocked in case this function returns TRUE -*/ - -bool -Event_scheduler::check_n_suspend_if_needed(THD *thd) -{ - bool was_suspended= FALSE; - DBUG_ENTER("Event_scheduler::check_n_suspend_if_needed"); - if (thd->killed && !shutdown_in_progress) - { - state= SUSPENDED; - thd->killed= THD::NOT_KILLED; - } - if (state == SUSPENDED) - { - thd->enter_cond(&cond_vars[COND_suspend_or_resume], &LOCK_scheduler_data, - "Suspended"); - /* Send back signal to the thread that asked us to suspend operations */ - pthread_cond_signal(&cond_vars[COND_suspend_or_resume]); - sql_print_information("SCHEDULER: Suspending operations"); - was_suspended= TRUE; - } - while (state == SUSPENDED) - { - cond_wait(COND_suspend_or_resume, &LOCK_scheduler_data); - DBUG_PRINT("info", ("Woke up after waiting on COND_suspend_or_resume")); - if (state != SUSPENDED) - { - pthread_cond_signal(&cond_vars[COND_suspend_or_resume]); - sql_print_information("SCHEDULER: Resuming operations"); - } - } - if (was_suspended) - { - if (queue.elements) - { - uint i; - DBUG_PRINT("info", ("We have to recompute the execution times")); - - for (i= 0; i < queue.elements; i++) - { - ((Event_timed*)queue_element(&queue, i))->compute_next_execution_time(); - ((Event_timed*)queue_element(&queue, i))->update_fields(thd); - } - queue_fix(&queue); - } - /* This will implicitly unlock LOCK_scheduler_data */ - thd->exit_cond(""); - } - DBUG_RETURN(was_suspended); -} - - -/* - Checks for empty queue and waits till new element gets in - - SYNOPSIS - Event_scheduler::check_n_wait_for_non_empty_queue() - thd Thread - - RETURN VALUE - FALSE Did not wait - LOCK_scheduler_data still locked. - TRUE Waited - LOCK_scheduler_data unlocked. - - NOTE - The caller should have locked LOCK_scheduler_data! -*/ - -bool -Event_scheduler::check_n_wait_for_non_empty_queue(THD *thd) -{ - bool slept= FALSE; - DBUG_ENTER("Event_scheduler::check_n_wait_for_non_empty_queue"); - DBUG_PRINT("enter", ("q.elements=%lu state=%s", - queue.elements, states_names[state])); - - if (!queue.elements) - thd->enter_cond(&cond_vars[COND_new_work], &LOCK_scheduler_data, - "Empty queue, sleeping"); - - /* Wait in a loop protecting against catching spurious signals */ - while (!queue.elements && state == RUNNING) - { - slept= TRUE; - DBUG_PRINT("info", ("Entering condition because of empty queue")); - cond_wait(COND_new_work, &LOCK_scheduler_data); - DBUG_PRINT("info", ("Manager woke up. Hope we have events now. state=%d", - state)); - /* - exit_cond does implicit mutex_UNLOCK, we needed it locked if - 1. we loop again - 2. end the current loop and start doing calculations - */ - } - if (slept) - thd->exit_cond(""); - - DBUG_PRINT("exit", ("q.elements=%lu state=%s thd->killed=%d", - queue.elements, states_names[state], thd->killed)); - - DBUG_RETURN(slept); -} - - -/* - Wrapper for pthread_mutex_lock - - SYNOPSIS - Event_scheduler::lock_data() - mutex Mutex to lock - line The line number on which the lock is done - - RETURN VALUE - Error code of pthread_mutex_lock() -*/ - -inline void -Event_scheduler::lock_data(const char *func, uint line) -{ - DBUG_ENTER("Event_scheduler::lock_mutex"); - DBUG_PRINT("enter", ("mutex_lock=%p func=%s line=%u", - &LOCK_scheduler_data, func, line)); - pthread_mutex_lock(&LOCK_scheduler_data); - mutex_last_locked_in_func= func; - mutex_last_locked_at_line= line; - mutex_scheduler_data_locked= TRUE; - DBUG_VOID_RETURN; -} - - -/* - Wrapper for pthread_mutex_unlock - - SYNOPSIS - Event_scheduler::unlock_data() - mutex Mutex to unlock - line The line number on which the unlock is done -*/ - -inline void -Event_scheduler::unlock_data(const char *func, uint line) -{ - DBUG_ENTER("Event_scheduler::UNLOCK_mutex"); - DBUG_PRINT("enter", ("mutex_unlock=%p func=%s line=%u", - &LOCK_scheduler_data, func, line)); - mutex_last_unlocked_at_line= line; - mutex_scheduler_data_locked= FALSE; - mutex_last_unlocked_in_func= func; - pthread_mutex_unlock(&LOCK_scheduler_data); - DBUG_VOID_RETURN; -} - - -/* - Wrapper for pthread_cond_wait - - SYNOPSIS - Event_scheduler::cond_wait() - cond Conditional to wait for - mutex Mutex of the conditional - - RETURN VALUE - Error code of pthread_cond_wait() -*/ - -inline int -Event_scheduler::cond_wait(enum Event_scheduler::enum_cond_vars cond, - pthread_mutex_t *mutex) -{ - int ret; - DBUG_ENTER("Event_scheduler::cond_wait"); - DBUG_PRINT("enter", ("cond=%s mutex=%p", cond_vars_names[cond], mutex)); - ret= pthread_cond_wait(&cond_vars[cond_waiting_on=cond], mutex); - cond_waiting_on= COND_NONE; - DBUG_RETURN(ret); -} - - -/* - Checks whether the scheduler is in a running or suspended state. - - SYNOPSIS - Event_scheduler::is_running_or_suspended() - - RETURN VALUE - TRUE Either running or suspended - FALSE IN_SHUTDOWN, not started, etc. -*/ - -inline bool -Event_scheduler::is_running_or_suspended() -{ - return (state == SUSPENDED || state == RUNNING); -} - - -/* - Returns the current state of the scheduler - - SYNOPSIS - Event_scheduler::get_state() -*/ - -enum Event_scheduler::enum_state -Event_scheduler::get_state() -{ - enum Event_scheduler::enum_state ret; - DBUG_ENTER("Event_scheduler::get_state"); - /* lock_data & unlock_data are not static */ - pthread_mutex_lock(&singleton.LOCK_scheduler_data); - ret= singleton.state; - pthread_mutex_unlock(&singleton.LOCK_scheduler_data); - DBUG_RETURN(ret); -} - - -/* - Returns whether the scheduler was initialized. - - SYNOPSIS - Event_scheduler::initialized() - - RETURN VALUE - FALSE Was not initialized so far - TRUE Was initialized -*/ - -bool -Event_scheduler::initialized() -{ - DBUG_ENTER("Event_scheduler::initialized"); - DBUG_RETURN(Event_scheduler::get_state() != UNINITIALIZED); -} - - -/* - Returns the number of elements in the queue - - SYNOPSIS - Event_scheduler::events_count() - - RETURN VALUE - 0 Number of Event_timed objects in the queue -*/ - -uint -Event_scheduler::events_count() -{ - uint n; - DBUG_ENTER("Event_scheduler::events_count"); - LOCK_SCHEDULER_DATA(); - n= queue.elements; - UNLOCK_SCHEDULER_DATA(); - - DBUG_RETURN(n); -} - - -/* - Looks for a named event in mysql.event and then loads it from - the table, compiles and inserts it into the cache. - - SYNOPSIS - Event_scheduler::load_named_event() - thd THD - etn The name of the event to load and compile on scheduler's root - etn_new The loaded event - - RETURN VALUE - NULL Error during compile or the event is non-enabled. - otherwise Address -*/ - -enum Event_scheduler::enum_error_code -Event_scheduler::load_named_event(THD *thd, Event_timed *etn, Event_timed **etn_new) -{ - int ret= 0; - MEM_ROOT *tmp_mem_root; - Event_timed *et_loaded= NULL; - Open_tables_state backup; - - DBUG_ENTER("Event_scheduler::load_and_compile_event"); - DBUG_PRINT("enter",("thd=%p name:%*s",thd, etn->name.length, etn->name.str)); - - thd->reset_n_backup_open_tables_state(&backup); - /* No need to use my_error() here because db_find_event() has done it */ - { - sp_name spn(etn->dbname, etn->name); - ret= db_find_event(thd, &spn, &et_loaded, NULL, &scheduler_root); - } - thd->restore_backup_open_tables_state(&backup); - /* In this case no memory was allocated so we don't need to clean */ - if (ret) - DBUG_RETURN(OP_LOAD_ERROR); - - if (et_loaded->status != Event_timed::ENABLED) - { - /* - We don't load non-enabled events. - In db_find_event() `et_new` was allocated on the heap and not on - scheduler_root therefore we delete it here. - */ - delete et_loaded; - DBUG_RETURN(OP_DISABLED_EVENT); - } - - et_loaded->compute_next_execution_time(); - *etn_new= et_loaded; - - DBUG_RETURN(OP_OK); -} - - -/* - Loads all ENABLED events from mysql.event into the prioritized - queue. Called during scheduler main thread initialization. Compiles - the events. Creates Event_timed instances for every ENABLED event - from mysql.event. - - SYNOPSIS - Event_scheduler::load_events_from_db() - thd - Thread context. Used for memory allocation in some cases. - - RETURN VALUE - 0 OK - !0 Error (EVEX_OPEN_TABLE_FAILED, EVEX_MICROSECOND_UNSUP, - EVEX_COMPILE_ERROR) - in all these cases mysql.event was - tampered. - - NOTES - Reports the error to the console -*/ - -int -Event_scheduler::load_events_from_db(THD *thd) -{ - TABLE *table; - READ_RECORD read_record_info; - int ret= -1; - uint count= 0; - bool clean_the_queue= FALSE; - /* Compile the events on this root but only for syntax check, then discard */ - MEM_ROOT boot_root; - - DBUG_ENTER("Event_scheduler::load_events_from_db"); - DBUG_PRINT("enter", ("thd=%p", thd)); - - if (state > COMMENCING) - { - DBUG_ASSERT(0); - sql_print_error("SCHEDULER: Trying to load events while already running."); - DBUG_RETURN(EVEX_GENERAL_ERROR); - } - - if ((ret= Events::open_event_table(thd, TL_READ, &table))) - { - sql_print_error("SCHEDULER: Table mysql.event is damaged. Can not open."); - DBUG_RETURN(EVEX_OPEN_TABLE_FAILED); - } - - init_alloc_root(&boot_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC); - init_read_record(&read_record_info, thd, table ,NULL,1,0); - while (!(read_record_info.read_record(&read_record_info))) - { - Event_timed *et; - if (!(et= new Event_timed)) - { - DBUG_PRINT("info", ("Out of memory")); - clean_the_queue= TRUE; - break; - } - DBUG_PRINT("info", ("Loading event from row.")); - - if ((ret= et->load_from_row(&scheduler_root, table))) - { - clean_the_queue= TRUE; - sql_print_error("SCHEDULER: Error while loading from mysql.event. " - "Table probably corrupted"); - break; - } - if (et->status != Event_timed::ENABLED) - { - DBUG_PRINT("info",("%s is disabled",et->name.str)); - delete et; - continue; - } - - DBUG_PRINT("info", ("Event %s loaded from row. ", et->name.str)); - - /* We load only on scheduler root just to check whether the body compiles */ - switch (ret= et->compile(thd, &boot_root)) { - case EVEX_MICROSECOND_UNSUP: - et->free_sp(); - sql_print_error("SCHEDULER: mysql.event is tampered. MICROSECOND is not " - "supported but found in mysql.event"); - goto end; - case EVEX_COMPILE_ERROR: - sql_print_error("SCHEDULER: Error while compiling %s.%s. Aborting load.", - et->dbname.str, et->name.str); - goto end; - default: - /* Free it, it will be compiled again on the worker thread */ - et->free_sp(); - break; - } - - /* let's find when to be executed */ - if (et->compute_next_execution_time()) - { - sql_print_error("SCHEDULER: Error while computing execution time of %s.%s." - " Skipping", et->dbname.str, et->name.str); - continue; - } - - DBUG_PRINT("load_events_from_db", ("Adding %p to the exec list.")); - queue_insert_safe(&queue, (byte *) et); - count++; - } -end: - end_read_record(&read_record_info); - free_root(&boot_root, MYF(0)); - - if (clean_the_queue) - { - for (count= 0; count < queue.elements; ++count) - queue_remove(&queue, 0); - ret= -1; - } - else - { - ret= 0; - sql_print_information("SCHEDULER: Loaded %d event%s", count, (count == 1)?"":"s"); - } - - /* Force close to free memory */ - thd->version--; - - close_thread_tables(thd); - - DBUG_PRINT("info", ("Status code %d. Loaded %d event(s)", ret, count)); - DBUG_RETURN(ret); -} - - -/* - Opens mysql.db and mysql.user and checks whether: - 1. mysql.db has column Event_priv at column 20 (0 based); - 2. mysql.user has column Event_priv at column 29 (0 based); - - SYNOPSIS - Event_scheduler::check_system_tables() -*/ - -bool -Event_scheduler::check_system_tables(THD *thd) -{ - TABLE_LIST tables; - bool not_used; - Open_tables_state backup; - bool ret; - - DBUG_ENTER("Event_scheduler::check_system_tables"); - DBUG_PRINT("enter", ("thd=%p", thd)); - - thd->reset_n_backup_open_tables_state(&backup); - - bzero((char*) &tables, sizeof(tables)); - tables.db= (char*) "mysql"; - tables.table_name= tables.alias= (char*) "db"; - tables.lock_type= TL_READ; - - if ((ret= simple_open_n_lock_tables(thd, &tables))) - sql_print_error("Cannot open mysql.db"); - else - { - ret= table_check_intact(tables.table, MYSQL_DB_FIELD_COUNT, - mysql_db_table_fields, &mysql_db_table_last_check, - ER_CANNOT_LOAD_FROM_TABLE); - close_thread_tables(thd); - } - if (ret) - DBUG_RETURN(TRUE); - - bzero((char*) &tables, sizeof(tables)); - tables.db= (char*) "mysql"; - tables.table_name= tables.alias= (char*) "user"; - tables.lock_type= TL_READ; - - if ((ret= simple_open_n_lock_tables(thd, &tables))) - sql_print_error("Cannot open mysql.db"); - else - { - if (tables.table->s->fields < 29 || - strncmp(tables.table->field[29]->field_name, - STRING_WITH_LEN("Event_priv"))) - { - sql_print_error("mysql.user has no `Event_priv` column at position 29"); - ret= TRUE; - } - close_thread_tables(thd); - } - - thd->restore_backup_open_tables_state(&backup); - - DBUG_RETURN(ret); -} - - -/* - Inits mutexes. - - SYNOPSIS - Event_scheduler::init_mutexes() -*/ - -void -Event_scheduler::init_mutexes() -{ - pthread_mutex_init(&singleton.LOCK_scheduler_data, MY_MUTEX_INIT_FAST); -} - - -/* - Destroys mutexes. - - SYNOPSIS - Event_scheduler::destroy_mutexes() -*/ - -void -Event_scheduler::destroy_mutexes() -{ - pthread_mutex_destroy(&singleton.LOCK_scheduler_data); -} - - -/* - Dumps some data about the internal status of the scheduler. - - SYNOPSIS - Event_scheduler::dump_internal_status() - thd THD - - RETURN VALUE - 0 OK - 1 Error -*/ - -int -Event_scheduler::dump_internal_status(THD *thd) -{ - DBUG_ENTER("dump_internal_status"); -#ifndef DBUG_OFF - CHARSET_INFO *scs= system_charset_info; - Protocol *protocol= thd->protocol; - List<Item> field_list; - int ret; - char tmp_buff[5*STRING_BUFFER_USUAL_SIZE]; - char int_buff[STRING_BUFFER_USUAL_SIZE]; - String tmp_string(tmp_buff, sizeof(tmp_buff), scs); - String int_string(int_buff, sizeof(int_buff), scs); - tmp_string.length(0); - int_string.length(0); - - field_list.push_back(new Item_empty_string("Name", 20)); - field_list.push_back(new Item_empty_string("Value",20)); - if (protocol->send_fields(&field_list, Protocol::SEND_NUM_ROWS | - Protocol::SEND_EOF)) - DBUG_RETURN(1); - - protocol->prepare_for_resend(); - protocol->store(STRING_WITH_LEN("state"), scs); - protocol->store(states_names[singleton.state].str, - states_names[singleton.state].length, - scs); - - ret= protocol->write(); - /* - If not initialized - don't show anything else. get_instance() - will otherwise implicitly initialize it. We don't want that. - */ - if (singleton.state >= INITIALIZED) - { - /* last locked at*/ - /* - The first thing to do, or get_instance() will overwrite the values. - mutex_last_locked_at_line / mutex_last_unlocked_at_line - */ - protocol->prepare_for_resend(); - protocol->store(STRING_WITH_LEN("last locked at"), scs); - tmp_string.length(scs->cset->snprintf(scs, (char*) tmp_string.ptr(), - tmp_string.alloced_length(), "%s::%d", - singleton.mutex_last_locked_in_func, - singleton.mutex_last_locked_at_line)); - protocol->store(&tmp_string); - ret= protocol->write(); - - /* last unlocked at*/ - protocol->prepare_for_resend(); - protocol->store(STRING_WITH_LEN("last unlocked at"), scs); - tmp_string.length(scs->cset->snprintf(scs, (char*) tmp_string.ptr(), - tmp_string.alloced_length(), "%s::%d", - singleton.mutex_last_unlocked_in_func, - singleton.mutex_last_unlocked_at_line)); - protocol->store(&tmp_string); - ret= protocol->write(); - - /* waiting on */ - protocol->prepare_for_resend(); - protocol->store(STRING_WITH_LEN("waiting on condition"), scs); - tmp_string.length(scs->cset-> - snprintf(scs, (char*) tmp_string.ptr(), - tmp_string.alloced_length(), "%s", - (singleton.cond_waiting_on != COND_NONE) ? - cond_vars_names[singleton.cond_waiting_on]: - "NONE")); - protocol->store(&tmp_string); - ret= protocol->write(); - - Event_scheduler *scheduler= get_instance(); - - /* workers_count */ - protocol->prepare_for_resend(); - protocol->store(STRING_WITH_LEN("workers_count"), scs); - int_string.set((longlong) scheduler->workers_count(), scs); - protocol->store(&int_string); - ret= protocol->write(); - - /* queue.elements */ - protocol->prepare_for_resend(); - protocol->store(STRING_WITH_LEN("queue.elements"), scs); - int_string.set((longlong) scheduler->queue.elements, scs); - protocol->store(&int_string); - ret= protocol->write(); - - /* scheduler_data_locked */ - protocol->prepare_for_resend(); - protocol->store(STRING_WITH_LEN("scheduler data locked"), scs); - int_string.set((longlong) scheduler->mutex_scheduler_data_locked, scs); - protocol->store(&int_string); - ret= protocol->write(); - } - send_eof(thd); -#endif - DBUG_RETURN(0); -} diff --git a/sql/event_scheduler.h b/sql/event_scheduler.h index 5ae310bab2a..acd0debe391 100644 --- a/sql/event_scheduler.h +++ b/sql/event_scheduler.h @@ -16,240 +16,4 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -class Event_timed; - -class THD; -typedef bool * (*event_timed_identifier_comparator)(Event_timed*, Event_timed*); - -int -events_init(); - -void -events_shutdown(); - -class Event_scheduler -{ -public: - /* Return codes */ - enum enum_error_code - { - OP_OK= 0, - OP_NOT_RUNNING, - OP_CANT_KILL, - OP_CANT_INIT, - OP_DISABLED_EVENT, - OP_LOAD_ERROR, - OP_ALREADY_EXISTS - }; - - enum enum_state - { - UNINITIALIZED= 0, - INITIALIZED, - COMMENCING, - CANTSTART, - RUNNING, - SUSPENDED, - IN_SHUTDOWN - }; - - enum enum_suspend_or_resume - { - SUSPEND= 1, - RESUME= 2 - }; - - /* Singleton access */ - static Event_scheduler* - get_instance(); - - /* Methods for queue management follow */ - - enum enum_error_code - create_event(THD *thd, Event_timed *et, bool check_existence); - - enum enum_error_code - update_event(THD *thd, Event_timed *et, LEX_STRING *new_schema, - LEX_STRING *new_name); - - bool - drop_event(THD *thd, Event_timed *et); - - - int - drop_schema_events(THD *thd, LEX_STRING *schema); - - int - drop_user_events(THD *thd, LEX_STRING *definer, uint *dropped_num) - { DBUG_ASSERT(0); return 0;} - - uint - events_count(); - - /* State changing methods follow */ - - bool - start(); - - enum enum_error_code - stop(); - - bool - start_suspended(); - - bool - run(THD *thd); - - enum enum_error_code - suspend_or_resume(enum enum_suspend_or_resume action); - - bool - init(); - - void - destroy(); - - static void - init_mutexes(); - - static void - destroy_mutexes(); - - void - report_error_during_start(); - - /* Information retrieving methods follow */ - - enum enum_state - get_state(); - - bool - initialized(); - - static int - dump_internal_status(THD *thd); - - static bool - check_system_tables(THD *thd); - -private: - Event_timed * - find_event(Event_timed *etn, bool remove_from_q); - - uint - workers_count(); - - bool - is_running_or_suspended(); - - /* helper functions */ - bool - execute_top(THD *thd); - - void - clean_queue(THD *thd); - - void - stop_all_running_events(THD *thd); - - enum enum_error_code - load_named_event(THD *thd, Event_timed *etn, Event_timed **etn_new); - - int - load_events_from_db(THD *thd); - - void - drop_matching_events(THD *thd, LEX_STRING *pattern, - bool (*)(Event_timed *,LEX_STRING *)); - - bool - check_n_suspend_if_needed(THD *thd); - - bool - check_n_wait_for_non_empty_queue(THD *thd); - - /* Singleton DP is used */ - Event_scheduler(); - - enum enum_cond_vars - { - COND_NONE= -1, - /* - COND_new_work is a conditional used to signal that there is a change - of the queue that should inform the executor thread that new event should - be executed sooner than previously expected, because of add/replace event. - */ - COND_new_work= 0, - /* - COND_started is a conditional used to synchronize the thread in which - ::start() was called and the spawned thread. ::start() spawns a new thread - and then waits on COND_started but also checks when awaken that `state` is - either RUNNING or CANTSTART. Then it returns back. - */ - COND_started_or_stopped, - /* - Conditional used for signalling from the scheduler thread back to the - thread that calls ::suspend() or ::resume. Synchronizing the calls. - */ - COND_suspend_or_resume, - /* Must be always last */ - COND_LAST - }; - - /* Singleton instance */ - static Event_scheduler singleton; - - /* This is the current status of the life-cycle of the manager. */ - enum enum_state state; - - /* Set to start the scheduler in suspended state */ - bool start_scheduler_suspended; - - /* - LOCK_scheduler_data is the mutex which protects the access to the - manager's queue as well as used when signalling COND_new_work, - COND_started and COND_shutdown. - */ - pthread_mutex_t LOCK_scheduler_data; - - /* - Holds the thread id of the executor thread or 0 if the executor is not - running. It is used by ::shutdown() to know which thread to kill with - kill_one_thread(). The latter wake ups a thread if it is waiting on a - conditional variable and sets thd->killed to non-zero. - */ - ulong thread_id; - - pthread_cond_t cond_vars[COND_LAST]; - static const char * const cond_vars_names[COND_LAST]; - - /* The MEM_ROOT of the object */ - MEM_ROOT scheduler_root; - - /* The sorted queue with the Event_timed objects */ - QUEUE queue; - - uint mutex_last_locked_at_line; - uint mutex_last_unlocked_at_line; - const char* mutex_last_locked_in_func; - const char* mutex_last_unlocked_in_func; - enum enum_cond_vars cond_waiting_on; - bool mutex_scheduler_data_locked; - - /* helper functions for working with mutexes & conditionals */ - void - lock_data(const char *func, uint line); - - void - unlock_data(const char *func, uint line); - - int - cond_wait(enum enum_cond_vars, pthread_mutex_t *mutex); - -private: - /* Prevent use of these */ - Event_scheduler(const Event_scheduler &); - void operator=(Event_scheduler &); -}; - #endif /* _EVENT_SCHEDULER_H_ */ diff --git a/sql/event_scheduler_ng.cc b/sql/event_scheduler_ng.cc new file mode 100644 index 00000000000..4e2820b8f65 --- /dev/null +++ b/sql/event_scheduler_ng.cc @@ -0,0 +1,875 @@ +/* Copyright (C) 2004-2006 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "mysql_priv.h" +#include "events.h" +#include "event_data_objects.h" +#include "event_scheduler_ng.h" +#include "event_queue.h" + +#ifdef __GNUC__ +#if __GNUC__ >= 2 +#define SCHED_FUNC __FUNCTION__ +#endif +#else +#define SCHED_FUNC "<unknown>" +#endif + +#define LOCK_SCHEDULER_DATA() lock_data(SCHED_FUNC, __LINE__) +#define UNLOCK_SCHEDULER_DATA() unlock_data(SCHED_FUNC, __LINE__) +#define COND_STATE_WAIT(timer) cond_wait(timer, SCHED_FUNC, __LINE__) + +extern pthread_attr_t connection_attrib; + +struct scheduler_param +{ + THD *thd; + Event_scheduler_ng *scheduler; +}; + +struct scheduler_param scheduler_param_value; + + + +static +LEX_STRING scheduler_states_names[] = +{ + { C_STRING_WITH_LEN("INITIALIZED")}, + { C_STRING_WITH_LEN("RUNNING")}, + { C_STRING_WITH_LEN("STOPPING")} +}; + + +/* + Prints the stack of infos, warnings, errors from thd to + the console so it can be fetched by the logs-into-tables and + checked later. + + SYNOPSIS + evex_print_warnings + thd Thread used during the execution of the event + et The event itself +*/ + +static void +evex_print_warnings(THD *thd, Event_job_data *et) +{ + MYSQL_ERROR *err; + DBUG_ENTER("evex_print_warnings"); + if (!thd->warn_list.elements) + DBUG_VOID_RETURN; + + char msg_buf[10 * STRING_BUFFER_USUAL_SIZE]; + char prefix_buf[5 * STRING_BUFFER_USUAL_SIZE]; + String prefix(prefix_buf, sizeof(prefix_buf), system_charset_info); + prefix.length(0); + prefix.append("SCHEDULER: ["); + + append_identifier(thd, &prefix, et->definer.str, et->definer.length); + prefix.append("][", 2); + append_identifier(thd,&prefix, et->dbname.str, et->dbname.length); + prefix.append('.'); + append_identifier(thd,&prefix, et->name.str, et->name.length); + prefix.append("] ", 2); + + List_iterator_fast<MYSQL_ERROR> it(thd->warn_list); + while ((err= it++)) + { + String err_msg(msg_buf, sizeof(msg_buf), system_charset_info); + /* set it to 0 or we start adding at the end. That's the trick ;) */ + err_msg.length(0); + err_msg.append(prefix); + err_msg.append(err->msg, strlen(err->msg), system_charset_info); + err_msg.append("]"); + DBUG_ASSERT(err->level < 3); + (sql_print_message_handlers[err->level])("%*s", err_msg.length(), + err_msg.c_ptr()); + } + DBUG_VOID_RETURN; +} + + +/* + Inits an scheduler thread handler, both the main and a worker + + SYNOPSIS + init_event_thread() + thd - the THD of the thread. Has to be allocated by the caller. + + NOTES + 1. The host of the thead is my_localhost + 2. thd->net is initted with NULL - no communication. + + RETURN VALUE + 0 OK + -1 Error +*/ + +static int +init_scheduler_thread(THD* thd) +{ + DBUG_ENTER("init_event_thread"); + thd->client_capabilities= 0; + thd->security_ctx->master_access= 0; + thd->security_ctx->db_access= 0; + thd->security_ctx->host_or_ip= (char*)my_localhost; + thd->security_ctx->set_user((char*)"event_scheduler"); + my_net_init(&thd->net, NULL); + thd->net.read_timeout= slave_net_timeout; + thd->slave_thread= 0; + thd->options|= OPTION_AUTO_IS_NULL; + thd->client_capabilities|= CLIENT_MULTI_RESULTS; + pthread_mutex_lock(&LOCK_thread_count); + thd->thread_id= thread_id++; + threads.append(thd); + thread_count++; + thread_running++; + pthread_mutex_unlock(&LOCK_thread_count); + + /* + Guarantees that we will see the thread in SHOW PROCESSLIST though its + vio is NULL. + */ + + thd->proc_info= "Initialized"; + thd->version= refresh_version; + thd->set_time(); + + DBUG_RETURN(0); +} + + +/* + Cleans up the THD and the threaded environment of the thread. + + SYNOPSIS + deinit_event_thread() + thd Thread +*/ + +static void +deinit_event_thread(THD *thd) +{ + thd->proc_info= "Clearing"; + DBUG_ASSERT(thd->net.buff != 0); + net_end(&thd->net); + DBUG_PRINT("exit", ("Scheduler thread finishing")); + pthread_mutex_lock(&LOCK_thread_count); + thread_count--; + thread_running--; + delete thd; + pthread_mutex_unlock(&LOCK_thread_count); + + my_thread_end(); +} + +/* + Function that executes the scheduler, + + SYNOPSIS + event_scheduler_ng_thread() + arg Pointer to `struct scheduler_param` + + RETURN VALUE + 0 OK +*/ + +pthread_handler_t +event_scheduler_ng_thread(void *arg) +{ + /* needs to be first for thread_stack */ + THD *thd= (THD *)(*(struct scheduler_param *) arg).thd; + + thd->thread_stack= (char *)&thd; // remember where our stack is + DBUG_ENTER("event_scheduler_ng_thread"); + + my_thread_init(); + pthread_detach_this_thread(); + thd->real_id=pthread_self(); + if (init_thr_lock() || thd->store_globals()) + { + thd->cleanup(); + goto end; + } + +#if !defined(__WIN__) && !defined(OS2) && !defined(__NETWARE__) + sigset_t set; + VOID(sigemptyset(&set)); // Get mask in use + VOID(pthread_sigmask(SIG_UNBLOCK,&set,&thd->block_signals)); +#endif + + ((struct scheduler_param *) arg)->scheduler->run(thd); + +end: + deinit_event_thread(thd); + + DBUG_RETURN(0); // Against gcc warnings +} + + +/* + Function that executes an event in a child thread. Setups the + environment for the event execution and cleans after that. + + SYNOPSIS + event_worker_ng_thread() + arg The Event_job_data object to be processed + + RETURN VALUE + 0 OK +*/ + +pthread_handler_t +event_worker_ng_thread(void *arg) +{ + /* needs to be first for thread_stack */ + THD *thd; + Event_job_data *event= (Event_job_data *)arg; + int ret; + + thd= event->thd; + thd->thread_stack= (char *) &thd; + + + my_thread_init(); + pthread_detach_this_thread(); + thd->real_id=pthread_self(); + if (init_thr_lock() || thd->store_globals()) + { + thd->cleanup(); + goto end; + } + + +#if !defined(__WIN__) && !defined(OS2) && !defined(__NETWARE__) + sigset_t set; + VOID(sigemptyset(&set)); // Get mask in use + VOID(pthread_sigmask(SIG_UNBLOCK, &set, &thd->block_signals)); +#endif + thd->init_for_queries(); + + DBUG_ENTER("event_worker_ng_thread"); + DBUG_PRINT("info", ("Baikonur, time is %d, BURAN reporting and operational." + "THD=0x%lx", time(NULL), thd)); + + sql_print_information("SCHEDULER: [%s.%s of %s] executing in thread %lu", + event->dbname.str, event->name.str, + event->definer.str, thd->thread_id); + + thd->enable_slow_log= TRUE; + + ret= event->execute(thd, thd->mem_root); + + evex_print_warnings(thd, event); + + sql_print_information("SCHEDULER: [%s.%s of %s] executed " + " in thread thread %lu. RetCode=%d", + event->dbname.str, event->name.str, + event->definer.str, thd->thread_id, ret); + if (ret == EVEX_COMPILE_ERROR) + sql_print_information("SCHEDULER: COMPILE ERROR for event %s.%s of %s", + event->dbname.str, event->name.str, + event->definer.str); + else if (ret == EVEX_MICROSECOND_UNSUP) + sql_print_information("SCHEDULER: MICROSECOND is not supported"); + +end: + DBUG_PRINT("info", ("BURAN %s.%s is landing!", event->dbname.str, + event->name.str)); + delete event; + + deinit_event_thread(thd); + + DBUG_RETURN(0); // Against gcc warnings +} + + +/* + Performs initialization of the scheduler data, outside of the + threading primitives. + + SYNOPSIS + Event_scheduler_ng::init_scheduler() +*/ + +bool +Event_scheduler_ng::init_scheduler(Event_queue *q) +{ + LOCK_SCHEDULER_DATA(); + thread_id= 0; + state= INITIALIZED; + queue= q; + started_events= 0; + UNLOCK_SCHEDULER_DATA(); + + return FALSE; +} + + +void +Event_scheduler_ng::deinit_scheduler() {} + + +/* + Inits scheduler's threading primitives. + + SYNOPSIS + Event_scheduler_ng::init_mutexes() +*/ + +void +Event_scheduler_ng::init_mutexes() +{ + pthread_mutex_init(&LOCK_scheduler_state, MY_MUTEX_INIT_FAST); + pthread_cond_init(&COND_state, NULL); +} + + +/* + Deinits scheduler's threading primitives. + + SYNOPSIS + Event_scheduler_ng::deinit_mutexes() +*/ + +void +Event_scheduler_ng::deinit_mutexes() +{ + pthread_mutex_destroy(&LOCK_scheduler_state); + pthread_cond_destroy(&COND_state); +} + + +/* + Starts the scheduler (again). Creates a new THD and passes it to + a forked thread. Does not wait for acknowledgement from the new + thread that it has started. Asynchronous starting. Most of the + needed initializations are done in the current thread to minimize + the chance of failure in the spawned thread. + + SYNOPSIS + Event_scheduler_ng::start() + + RETURN VALUE + FALSE OK + TRUE Error (not reported) +*/ + +bool +Event_scheduler_ng::start() +{ + THD *new_thd= NULL; + bool ret= FALSE; + pthread_t th; + DBUG_ENTER("Event_scheduler_ng::start"); + + LOCK_SCHEDULER_DATA(); + DBUG_PRINT("info", ("state before action %s", scheduler_states_names[state])); + if (state > INITIALIZED) + goto end; + + if (!(new_thd= new THD) || init_scheduler_thread(new_thd)) + { + sql_print_error("SCHEDULER: Cannot init manager event thread."); + ret= TRUE; + goto end; + } + new_thd->system_thread= SYSTEM_THREAD_EVENT_SCHEDULER; + new_thd->command= COM_DAEMON; + + scheduler_param_value.thd= new_thd; + scheduler_param_value.scheduler= this; + + DBUG_PRINT("info", ("Forking new thread for scheduduler. THD=0x%lx", new_thd)); + if (pthread_create(&th, &connection_attrib, event_scheduler_ng_thread, + (void*)&scheduler_param_value)) + { + DBUG_PRINT("error", ("cannot create a new thread")); + state= INITIALIZED; + ret= TRUE; + } + DBUG_PRINT("info", ("Setting state go RUNNING")); + state= RUNNING; +end: + UNLOCK_SCHEDULER_DATA(); + + if (ret && new_thd) + { + DBUG_PRINT("info", ("There was an error during THD creation. Clean up")); + new_thd->proc_info= "Clearing"; + DBUG_ASSERT(new_thd->net.buff != 0); + net_end(&new_thd->net); + pthread_mutex_lock(&LOCK_thread_count); + thread_count--; + thread_running--; + delete new_thd; + pthread_mutex_unlock(&LOCK_thread_count); + } + DBUG_RETURN(ret); +} + + +/* + Stops the scheduler (again). Waits for acknowledgement from the + scheduler that it has stopped - synchronous stopping. + + SYNOPSIS + Event_scheduler_ng::stop() + + RETURN VALUE + FALSE OK + TRUE Error (not reported) +*/ + +bool +Event_scheduler_ng::stop() +{ + THD *thd= current_thd; + DBUG_ENTER("Event_scheduler_ng::stop"); + DBUG_PRINT("enter", ("thd=0x%lx", current_thd)); + + LOCK_SCHEDULER_DATA(); + DBUG_PRINT("info", ("state before action %s", scheduler_states_names[state])); + if (state != RUNNING) + goto end; + + state= STOPPING; + + DBUG_PRINT("info", ("Manager thread has id %d", thread_id)); + sql_print_information("SCHEDULER: Killing manager thread %lu", thread_id); + + pthread_cond_signal(&COND_state); + + /* Guarantee we don't catch spurious signals */ + sql_print_information("SCHEDULER: Waiting the manager thread to reply"); + do { + DBUG_PRINT("info", ("Waiting for COND_started_or_stopped from the manager " + "thread. Current value of state is %s . " + "workers count=%d", scheduler_states_names[state].str, + workers_count())); + /* thd could be 0x0, when shutting down */ + COND_STATE_WAIT(NULL); + } while (state == STOPPING); + DBUG_PRINT("info", ("Manager thread has cleaned up. Set state to INIT")); + + thread_id= 0; +end: + UNLOCK_SCHEDULER_DATA(); + DBUG_RETURN(FALSE); +} + + +/* + The main loop of the scheduler. + + SYNOPSIS + Event_scheduler_ng::run() + thd Thread + + RETURN VALUE + FALSE OK + TRUE Error (Serious error) +*/ + +bool +Event_scheduler_ng::run(THD *thd) +{ + int res; + struct timespec abstime; + Event_job_data *job_data; + DBUG_ENTER("Event_scheduler_ng::run"); + + LOCK_SCHEDULER_DATA(); + + thread_id= thd->thread_id; + sql_print_information("SCHEDULER: Manager thread started with id %lu", + thread_id); + /* + Recalculate the values in the queue because there could have been stops + in executions of the scheduler and some times could have passed by. + */ + queue->recalculate_activation_times(thd); + while (state == RUNNING) + { + thd->end_time(); + /* Gets a minimized version */ + job_data= queue-> + get_top_for_execution_if_time(thd, thd->query_start(), &abstime); + + DBUG_PRINT("info", ("get_top returned job_data=0x%lx now=%d " + "abs_time.tv_sec=%d", + job_data, thd->query_start(), abstime.tv_sec)); + if (!job_data && !abstime.tv_sec) + { + DBUG_PRINT("info", ("The queue is empty. Going to sleep")); + thd->enter_cond(&COND_state, &LOCK_scheduler_state, + "Waiting on empty queue"); + COND_STATE_WAIT(NULL); + thd->exit_cond(""); + DBUG_PRINT("info", ("Woke up. Got COND_state")); + LOCK_SCHEDULER_DATA(); + } + else if (abstime.tv_sec) + { + DBUG_PRINT("info", ("Have to sleep some time %u till", + abstime.tv_sec - thd->query_start(), abstime.tv_sec)); + + thd->enter_cond(&COND_state, &LOCK_scheduler_state, + "Waiting for next activation"); + COND_STATE_WAIT(&abstime); + /* + If we get signal we should recalculate the whether it's the right time + because there could be : + 1. Spurious wake-up + 2. The top of the queue was changed (new one becase of create/update) + */ + /* This will do implicit UNLOCK_SCHEDULER_DATA() */ + thd->exit_cond(""); + DBUG_PRINT("info", ("Woke up. Got COND_stat or time for execution.")); + LOCK_SCHEDULER_DATA(); + } + else + { + UNLOCK_SCHEDULER_DATA(); + res= execute_top(thd, job_data); + LOCK_SCHEDULER_DATA(); + if (res) + break; + ++started_events; + } + DBUG_PRINT("info", ("state=%s", scheduler_states_names[state].str)); + } + DBUG_PRINT("info", ("Signalling back to the stopper COND_state")); + pthread_cond_signal(&COND_state); +error: + state= INITIALIZED; + UNLOCK_SCHEDULER_DATA(); + sql_print_information("SCHEDULER: Stopped"); + + DBUG_RETURN(res); +} + + +/* + Creates a new THD instance and then forks a new thread, while passing + the THD pointer and job_data to it. + + SYNOPSIS + Event_scheduler_ng::execute_top() + + RETURN VALUE + FALSE OK + TRUE Error (Serious error) +*/ + +bool +Event_scheduler_ng::execute_top(THD *thd, Event_job_data *job_data) +{ + THD *new_thd; + pthread_t th; + int res= 0; + DBUG_ENTER("Event_scheduler_ng::execute_top"); + if (!(new_thd= new THD) || init_scheduler_thread(new_thd)) + goto error; + + new_thd->system_thread= SYSTEM_THREAD_EVENT_WORKER; + job_data->thd= new_thd; + DBUG_PRINT("info", ("BURAN %s@%s ready for start t-3..2..1..0..ignition", + job_data->dbname.str, job_data->name.str)); + + /* Major failure */ + if ((res= pthread_create(&th, &connection_attrib, event_worker_ng_thread, + job_data))) + goto error; + + DBUG_PRINT("info", ("Launch succeeded. BURAN is in THD=0x%lx", new_thd)); + DBUG_RETURN(FALSE); + +error: + DBUG_PRINT("error", ("Baikonur, we have a problem! res=%d", res)); + if (new_thd) + { + new_thd->proc_info= "Clearing"; + DBUG_ASSERT(new_thd->net.buff != 0); + net_end(&new_thd->net); + pthread_mutex_lock(&LOCK_thread_count); + thread_count--; + thread_running--; + delete new_thd; + pthread_mutex_unlock(&LOCK_thread_count); + } + delete job_data; + DBUG_RETURN(TRUE); +} + + +/* + Returns the current state of the scheduler + + SYNOPSIS + Event_scheduler_ng::get_state() + + RETURN VALUE + The state of the scheduler (INITIALIZED | RUNNING | STOPPING) +*/ + +enum Event_scheduler_ng::enum_state +Event_scheduler_ng::get_state() +{ + enum Event_scheduler_ng::enum_state ret; + LOCK_SCHEDULER_DATA(); + ret= state; + UNLOCK_SCHEDULER_DATA(); + return ret; +} + + +/* + Returns the number of living event worker threads. + + SYNOPSIS + Event_scheduler_ng::workers_count() +*/ + +uint +Event_scheduler_ng::workers_count() +{ + THD *tmp; + uint count= 0; + + DBUG_ENTER("Event_scheduler_ng::workers_count"); + pthread_mutex_lock(&LOCK_thread_count); // For unlink from list + I_List_iterator<THD> it(threads); + while ((tmp=it++)) + { + if (tmp->command == COM_DAEMON) + continue; + if (tmp->system_thread == SYSTEM_THREAD_EVENT_WORKER) + ++count; + } + pthread_mutex_unlock(&LOCK_thread_count); + DBUG_PRINT("exit", ("%d", count)); + DBUG_RETURN(count); +} + + +/* + Signals the main scheduler thread that the queue has changed + its state. + + SYNOPSIS + Event_scheduler_ng::queue_changed() +*/ + +void +Event_scheduler_ng::queue_changed() +{ + DBUG_ENTER("Event_scheduler_ng::queue_changed"); + DBUG_PRINT("info", ("Sending COND_state. state (read wo lock)=%s ", + scheduler_states_names[state].str)); + pthread_cond_signal(&COND_state); + DBUG_VOID_RETURN; +} + + +/* + Auxiliary function for locking LOCK_scheduler_state. Used + by the LOCK_SCHEDULER_DATA macro. + + SYNOPSIS + Event_scheduler_ng::lock_data() + func Which function is requesting mutex lock + line On which line mutex lock is requested +*/ + +void +Event_scheduler_ng::lock_data(const char *func, uint line) +{ + DBUG_ENTER("Event_scheduler_ng::lock_data"); + DBUG_PRINT("enter", ("func=%s line=%u", func, line)); + pthread_mutex_lock(&LOCK_scheduler_state); + mutex_last_locked_in_func= func; + mutex_last_locked_at_line= line; + mutex_scheduler_data_locked= TRUE; + DBUG_VOID_RETURN; +} + + +/* + Auxiliary function for unlocking LOCK_scheduler_state. Used + by the UNLOCK_SCHEDULER_DATA macro. + + SYNOPSIS + Event_scheduler_ng::unlock_data() + func Which function is requesting mutex unlock + line On which line mutex unlock is requested +*/ + +void +Event_scheduler_ng::unlock_data(const char *func, uint line) +{ + DBUG_ENTER("Event_scheduler_ng::unlock_data"); + DBUG_PRINT("enter", ("func=%s line=%u", func, line)); + mutex_last_unlocked_at_line= line; + mutex_scheduler_data_locked= FALSE; + mutex_last_unlocked_in_func= func; + pthread_mutex_unlock(&LOCK_scheduler_state); + DBUG_VOID_RETURN; +} + + +/* + Wrapper for pthread_cond_wait/timedwait + + SYNOPSIS + Event_scheduler_ng::cond_wait() + cond Conditional to wait for + mutex Mutex of the conditional + + RETURN VALUE + Error code of pthread_cond_wait() +*/ + +void +Event_scheduler_ng::cond_wait(struct timespec *abstime, + const char *func, uint line) +{ + DBUG_ENTER("Event_scheduler_ng::cond_wait"); + waiting_on_cond= TRUE; + mutex_last_unlocked_at_line= line; + mutex_scheduler_data_locked= FALSE; + mutex_last_unlocked_in_func= func; + + if (abstime) + pthread_cond_timedwait(&COND_state, &LOCK_scheduler_state, abstime); + else + pthread_cond_wait(&COND_state, &LOCK_scheduler_state); + + mutex_last_locked_in_func= func; + mutex_last_locked_at_line= line; + mutex_scheduler_data_locked= TRUE; + waiting_on_cond= FALSE; + + DBUG_VOID_RETURN; +} + + +/* + Dumps the internal status of the scheduler + + SYNOPSIS + Event_scheduler_ng::dump_internal_status() + thd Thread + + RETURN VALUE + FALSE OK + TRUE Error +*/ + +bool +Event_scheduler_ng::dump_internal_status(THD *thd) +{ + int ret= 0; + DBUG_ENTER("Event_scheduler_ng::dump_internal_status"); + +#ifndef DBUG_OFF + CHARSET_INFO *scs= system_charset_info; + Protocol *protocol= thd->protocol; + char tmp_buff[5*STRING_BUFFER_USUAL_SIZE]; + char int_buff[STRING_BUFFER_USUAL_SIZE]; + String tmp_string(tmp_buff, sizeof(tmp_buff), scs); + String int_string(int_buff, sizeof(int_buff), scs); + tmp_string.length(0); + int_string.length(0); + + protocol->prepare_for_resend(); + protocol->store(STRING_WITH_LEN("scheduler state"), scs); + protocol->store(scheduler_states_names[state].str, + scheduler_states_names[state].length, scs); + + if ((ret= protocol->write())) + goto end; + + /* thread_id */ + protocol->prepare_for_resend(); + protocol->store(STRING_WITH_LEN("thread_id"), scs); + if (thread_id) + { + int_string.set((longlong) thread_id, scs); + protocol->store(&int_string); + } + else + protocol->store_null(); + if ((ret= protocol->write())) + goto end; + + /* last locked at*/ + protocol->prepare_for_resend(); + protocol->store(STRING_WITH_LEN("scheduler last locked at"), scs); + tmp_string.length(scs->cset->snprintf(scs, (char*) tmp_string.ptr(), + tmp_string.alloced_length(), "%s::%d", + mutex_last_locked_in_func, + mutex_last_locked_at_line)); + protocol->store(&tmp_string); + if ((ret= protocol->write())) + goto end; + + /* last unlocked at*/ + protocol->prepare_for_resend(); + protocol->store(STRING_WITH_LEN("scheduler last unlocked at"), scs); + tmp_string.length(scs->cset->snprintf(scs, (char*) tmp_string.ptr(), + tmp_string.alloced_length(), "%s::%d", + mutex_last_unlocked_in_func, + mutex_last_unlocked_at_line)); + protocol->store(&tmp_string); + if ((ret= protocol->write())) + goto end; + + /* waiting on */ + protocol->prepare_for_resend(); + protocol->store(STRING_WITH_LEN("scheduler waiting on condition"), scs); + int_string.set((longlong) waiting_on_cond, scs); + protocol->store(&int_string); + if ((ret= protocol->write())) + goto end; + + /* workers_count */ + protocol->prepare_for_resend(); + protocol->store(STRING_WITH_LEN("scheduler workers count"), scs); + int_string.set((longlong) workers_count(), scs); + protocol->store(&int_string); + if ((ret= protocol->write())) + goto end; + + /* workers_count */ + protocol->prepare_for_resend(); + protocol->store(STRING_WITH_LEN("scheduler executed events"), scs); + int_string.set((longlong) started_events, scs); + protocol->store(&int_string); + if ((ret= protocol->write())) + goto end; + + /* scheduler_data_locked */ + protocol->prepare_for_resend(); + protocol->store(STRING_WITH_LEN("scheduler data locked"), scs); + int_string.set((longlong) mutex_scheduler_data_locked, scs); + protocol->store(&int_string); + ret= protocol->write(); +end: +#endif + + DBUG_RETURN(ret); +} diff --git a/sql/event_scheduler_ng.h b/sql/event_scheduler_ng.h new file mode 100644 index 00000000000..e4f3f0588f9 --- /dev/null +++ b/sql/event_scheduler_ng.h @@ -0,0 +1,123 @@ +#ifndef _EVENT_SCHEDULER_NG_H_ +#define _EVENT_SCHEDULER_NG_H_ +/* Copyright (C) 2004-2006 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +class Event_queue; +class Event_job_data; + +class Event_scheduler_ng +{ +public: + Event_scheduler_ng(){} + ~Event_scheduler_ng(){} + + enum enum_state + { + INITIALIZED = 0, + RUNNING, + STOPPING + }; + + /* State changing methods follow */ + + bool + start(); + + bool + stop(); + + /* + Need to be public because has to be called from the function + passed to pthread_create. + */ + bool + run(THD *thd); + + bool + init_scheduler(Event_queue *queue); + + void + deinit_scheduler(); + + void + init_mutexes(); + + void + deinit_mutexes(); + + /* Information retrieving methods follow */ + + enum enum_state + get_state(); + + void + queue_changed(); + + bool + dump_internal_status(THD *thd); + +private: + uint + workers_count(); + + /* helper functions */ + bool + execute_top(THD *thd, Event_job_data *job_data); + + /* helper functions for working with mutexes & conditionals */ + void + lock_data(const char *func, uint line); + + void + unlock_data(const char *func, uint line); + + void + cond_wait(struct timespec *abstime, const char *func, uint line); + + pthread_mutex_t LOCK_scheduler_state; + + /* This is the current status of the life-cycle of the scheduler. */ + enum enum_state state; + + /* + Holds the thread id of the executor thread or 0 if the scheduler is not + running. It is used by ::shutdown() to know which thread to kill with + kill_one_thread(). The latter wake ups a thread if it is waiting on a + conditional variable and sets thd->killed to non-zero. + */ + ulong thread_id; + + pthread_cond_t COND_state; + + Event_queue *queue; + + uint mutex_last_locked_at_line; + uint mutex_last_unlocked_at_line; + const char* mutex_last_locked_in_func; + const char* mutex_last_unlocked_in_func; + bool mutex_scheduler_data_locked; + bool waiting_on_cond; + + ulonglong started_events; + +private: + /* Prevent use of these */ + Event_scheduler_ng(const Event_scheduler_ng &); + void operator=(Event_scheduler_ng &); +}; + +#endif /* _EVENT_SCHEDULER_NG_H_ */ diff --git a/sql/events.cc b/sql/events.cc index 210cc2c4735..08e17a9578a 100644 --- a/sql/events.cc +++ b/sql/events.cc @@ -15,11 +15,11 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "mysql_priv.h" -#include "events_priv.h" #include "events.h" -#include "event_timed.h" -#include "event_scheduler.h" -#include "sp.h" +#include "event_data_objects.h" +#include "event_db_repository.h" +#include "event_queue.h" +#include "event_scheduler_ng.h" #include "sp_head.h" /* @@ -48,9 +48,20 @@ Warning: */ -MEM_ROOT evex_mem_root; -time_t mysql_event_last_create_time= 0L; - +/* + If the user (un)intentionally removes an event directly from mysql.event + the following sequence has to be used to be able to remove the in-memory + counterpart. + 1. CREATE EVENT the_name ON SCHEDULE EVERY 1 SECOND DISABLE DO SELECT 1; + 2. DROP EVENT the_name + + In other words, the first one will create a row in mysql.event . In the + second step because there will be a line, disk based drop will pass and + the scheduler will remove the memory counterpart. The reason is that + in-memory queue does not check whether the event we try to drop from memory + is disabled. Disabled events are not kept in-memory because they are not + eligible for execution. +*/ const char *event_scheduler_state_names[]= { "OFF", "0", "ON", "1", "SUSPEND", "2", NullS }; @@ -63,104 +74,10 @@ TYPELIB Events::opt_typelib= NULL }; +Events Events::singleton; ulong Events::opt_event_scheduler= 2; -static -TABLE_FIELD_W_TYPE event_table_fields[Events::FIELD_COUNT] = { - { - {(char *) STRING_WITH_LEN("db")}, - {(char *) STRING_WITH_LEN("char(64)")}, - {(char *) STRING_WITH_LEN("utf8")} - }, - { - {(char *) STRING_WITH_LEN("name")}, - {(char *) STRING_WITH_LEN("char(64)")}, - {(char *) STRING_WITH_LEN("utf8")} - }, - { - {(char *) STRING_WITH_LEN("body")}, - {(char *) STRING_WITH_LEN("longblob")}, - {NULL, 0} - }, - { - {(char *) STRING_WITH_LEN("definer")}, - {(char *) STRING_WITH_LEN("char(77)")}, - {(char *) STRING_WITH_LEN("utf8")} - }, - { - {(char *) STRING_WITH_LEN("execute_at")}, - {(char *) STRING_WITH_LEN("datetime")}, - {NULL, 0} - }, - { - {(char *) STRING_WITH_LEN("interval_value")}, - {(char *) STRING_WITH_LEN("int(11)")}, - {NULL, 0} - }, - { - {(char *) STRING_WITH_LEN("interval_field")}, - {(char *) STRING_WITH_LEN("enum('YEAR','QUARTER','MONTH','DAY'," - "'HOUR','MINUTE','WEEK','SECOND','MICROSECOND','YEAR_MONTH','DAY_HOUR'," - "'DAY_MINUTE','DAY_SECOND','HOUR_MINUTE','HOUR_SECOND','MINUTE_SECOND'," - "'DAY_MICROSECOND','HOUR_MICROSECOND','MINUTE_MICROSECOND'," - "'SECOND_MICROSECOND')")}, - {NULL, 0} - }, - { - {(char *) STRING_WITH_LEN("created")}, - {(char *) STRING_WITH_LEN("timestamp")}, - {NULL, 0} - }, - { - {(char *) STRING_WITH_LEN("modified")}, - {(char *) STRING_WITH_LEN("timestamp")}, - {NULL, 0} - }, - { - {(char *) STRING_WITH_LEN("last_executed")}, - {(char *) STRING_WITH_LEN("datetime")}, - {NULL, 0} - }, - { - {(char *) STRING_WITH_LEN("starts")}, - {(char *) STRING_WITH_LEN("datetime")}, - {NULL, 0} - }, - { - {(char *) STRING_WITH_LEN("ends")}, - {(char *) STRING_WITH_LEN("datetime")}, - {NULL, 0} - }, - { - {(char *) STRING_WITH_LEN("status")}, - {(char *) STRING_WITH_LEN("enum('ENABLED','DISABLED')")}, - {NULL, 0} - }, - { - {(char *) STRING_WITH_LEN("on_completion")}, - {(char *) STRING_WITH_LEN("enum('DROP','PRESERVE')")}, - {NULL, 0} - }, - { - {(char *) STRING_WITH_LEN("sql_mode")}, - {(char *) STRING_WITH_LEN("set('REAL_AS_FLOAT','PIPES_AS_CONCAT','ANSI_QUOTES'," - "'IGNORE_SPACE','NOT_USED','ONLY_FULL_GROUP_BY','NO_UNSIGNED_SUBTRACTION'," - "'NO_DIR_IN_CREATE','POSTGRESQL','ORACLE','MSSQL','DB2','MAXDB'," - "'NO_KEY_OPTIONS','NO_TABLE_OPTIONS','NO_FIELD_OPTIONS','MYSQL323','MYSQL40'," - "'ANSI','NO_AUTO_VALUE_ON_ZERO','NO_BACKSLASH_ESCAPES','STRICT_TRANS_TABLES'," - "'STRICT_ALL_TABLES','NO_ZERO_IN_DATE','NO_ZERO_DATE','INVALID_DATES'," - "'ERROR_FOR_DIVISION_BY_ZERO','TRADITIONAL','NO_AUTO_CREATE_USER'," - "'HIGH_NOT_PRECEDENCE')")}, - {NULL, 0} - }, - { - {(char *) STRING_WITH_LEN("comment")}, - {(char *) STRING_WITH_LEN("char(64)")}, - {(char *) STRING_WITH_LEN("utf8")} - } -}; - /* Compares 2 LEX strings regarding case. @@ -187,6 +104,23 @@ int sortcmp_lex_string(LEX_STRING s, LEX_STRING t, CHARSET_INFO *cs) (uchar *) t.str,t.length, 0); } +/* + Accessor for the singleton instance. + + SYNOPSIS + Events::get_instance() + + RETURN VALUE + address +*/ + +Events * +Events::get_instance() +{ + DBUG_ENTER("Events::get_instance"); + DBUG_RETURN(&singleton); +} + /* Reconstructs interval expression from interval type and expression @@ -201,15 +135,14 @@ int sortcmp_lex_string(LEX_STRING s, LEX_STRING t, CHARSET_INFO *cs) interval - the interval type (for instance YEAR_MONTH) expression - the value in the lowest entity - RETURNS + RETURN VALUE 0 - OK 1 - Error */ int -Events::reconstruct_interval_expression(String *buf, - interval_type interval, - longlong expression) +Events::reconstruct_interval_expression(String *buf, interval_type interval, + longlong expression) { ulonglong expr= expression; char tmp_buff[128], *end; @@ -269,7 +202,7 @@ common_1_lev_code: expr= tmp_expr - (tmp_expr/60)*60; /* the code after the switch will finish */ } - break; + break; case INTERVAL_DAY_SECOND: { ulonglong tmp_expr= expr; @@ -341,545 +274,7 @@ int Events::open_event_table(THD *thd, enum thr_lock_type lock_type, TABLE **table) { - TABLE_LIST tables; - DBUG_ENTER("open_events_table"); - - bzero((char*) &tables, sizeof(tables)); - tables.db= (char*) "mysql"; - tables.table_name= tables.alias= (char*) "event"; - tables.lock_type= lock_type; - - if (simple_open_n_lock_tables(thd, &tables)) - DBUG_RETURN(1); - - if (table_check_intact(tables.table, Events::FIELD_COUNT, - event_table_fields, - &mysql_event_last_create_time, - ER_CANNOT_LOAD_FROM_TABLE)) - { - close_thread_tables(thd); - DBUG_RETURN(2); - } - *table= tables.table; - tables.table->use_all_columns(); - DBUG_RETURN(0); -} - - -/* - Find row in open mysql.event table representing event - - SYNOPSIS - evex_db_find_event_aux() - thd Thread context - et event_timed object containing dbname & name - table TABLE object for open mysql.event table. - - RETURN VALUE - 0 - Routine found - EVEX_KEY_NOT_FOUND - No routine with given name -*/ - -inline int -evex_db_find_event_aux(THD *thd, Event_timed *et, TABLE *table) -{ - return evex_db_find_event_by_name(thd, et->dbname, et->name, table); -} - - -/* - Find row in open mysql.event table representing event - - SYNOPSIS - evex_db_find_event_by_name() - thd Thread context - dbname Name of event's database - rname Name of the event inside the db - table TABLE object for open mysql.event table. - - RETURN VALUE - 0 - Routine found - EVEX_KEY_NOT_FOUND - No routine with given name -*/ - -int -evex_db_find_event_by_name(THD *thd, const LEX_STRING dbname, - const LEX_STRING ev_name, - TABLE *table) -{ - byte key[MAX_KEY_LENGTH]; - DBUG_ENTER("evex_db_find_event_by_name"); - DBUG_PRINT("enter", ("name: %.*s", ev_name.length, ev_name.str)); - - /* - Create key to find row. We have to use field->store() to be able to - handle VARCHAR and CHAR fields. - Assumption here is that the two first fields in the table are - 'db' and 'name' and the first key is the primary key over the - same fields. - */ - if (dbname.length > table->field[Events::FIELD_DB]->field_length || - ev_name.length > table->field[Events::FIELD_NAME]->field_length) - - DBUG_RETURN(EVEX_KEY_NOT_FOUND); - - table->field[Events::FIELD_DB]->store(dbname.str, dbname.length, - &my_charset_bin); - table->field[Events::FIELD_NAME]->store(ev_name.str, ev_name.length, - &my_charset_bin); - - key_copy(key, table->record[0], table->key_info, - table->key_info->key_length); - - if (table->file->index_read_idx(table->record[0], 0, key, - table->key_info->key_length, - HA_READ_KEY_EXACT)) - { - DBUG_PRINT("info", ("Row not found")); - DBUG_RETURN(EVEX_KEY_NOT_FOUND); - } - - DBUG_PRINT("info", ("Row found!")); - DBUG_RETURN(0); -} - - -/* - Puts some data common to CREATE and ALTER EVENT into a row. - - SYNOPSIS - evex_fill_row() - thd THD - table the row to fill out - et Event's data - - RETURN VALUE - 0 - OK - EVEX_GENERAL_ERROR - bad data - EVEX_GET_FIELD_FAILED - field count does not match. table corrupted? - - DESCRIPTION - Used both when an event is created and when it is altered. -*/ - -static int -evex_fill_row(THD *thd, TABLE *table, Event_timed *et, my_bool is_update) -{ - CHARSET_INFO *scs= system_charset_info; - enum Events::enum_table_field field_num; - - DBUG_ENTER("evex_fill_row"); - - DBUG_PRINT("info", ("dbname=[%s]", et->dbname.str)); - DBUG_PRINT("info", ("name =[%s]", et->name.str)); - DBUG_PRINT("info", ("body =[%s]", et->body.str)); - - if (table->field[field_num= Events::FIELD_DEFINER]-> - store(et->definer.str, et->definer.length, scs)) - goto err_truncate; - - if (table->field[field_num= Events::FIELD_DB]-> - store(et->dbname.str, et->dbname.length, scs)) - goto err_truncate; - - if (table->field[field_num= Events::FIELD_NAME]-> - store(et->name.str, et->name.length, scs)) - goto err_truncate; - - /* both ON_COMPLETION and STATUS are NOT NULL thus not calling set_notnull()*/ - table->field[Events::FIELD_ON_COMPLETION]-> - store((longlong)et->on_completion, true); - - table->field[Events::FIELD_STATUS]->store((longlong)et->status, true); - - /* - Change the SQL_MODE only if body was present in an ALTER EVENT and of course - always during CREATE EVENT. - */ - if (et->body.str) - { - table->field[Events::FIELD_SQL_MODE]-> - store((longlong)thd->variables.sql_mode, true); - - if (table->field[field_num= Events::FIELD_BODY]-> - store(et->body.str, et->body.length, scs)) - goto err_truncate; - } - - if (et->expression) - { - table->field[Events::FIELD_INTERVAL_EXPR]->set_notnull(); - table->field[Events::FIELD_INTERVAL_EXPR]-> - store((longlong)et->expression, true); - - table->field[Events::FIELD_TRANSIENT_INTERVAL]->set_notnull(); - /* - In the enum (C) intervals start from 0 but in mysql enum valid values start - from 1. Thus +1 offset is needed! - */ - table->field[Events::FIELD_TRANSIENT_INTERVAL]-> - store((longlong)et->interval+1, true); - - table->field[Events::FIELD_EXECUTE_AT]->set_null(); - - if (!et->starts_null) - { - table->field[Events::FIELD_STARTS]->set_notnull(); - table->field[Events::FIELD_STARTS]-> - store_time(&et->starts, MYSQL_TIMESTAMP_DATETIME); - } - - if (!et->ends_null) - { - table->field[Events::FIELD_ENDS]->set_notnull(); - table->field[Events::FIELD_ENDS]-> - store_time(&et->ends, MYSQL_TIMESTAMP_DATETIME); - } - } - else if (et->execute_at.year) - { - table->field[Events::FIELD_INTERVAL_EXPR]->set_null(); - table->field[Events::FIELD_TRANSIENT_INTERVAL]->set_null(); - table->field[Events::FIELD_STARTS]->set_null(); - table->field[Events::FIELD_ENDS]->set_null(); - - table->field[Events::FIELD_EXECUTE_AT]->set_notnull(); - table->field[Events::FIELD_EXECUTE_AT]-> - store_time(&et->execute_at, MYSQL_TIMESTAMP_DATETIME); - } - else - { - DBUG_ASSERT(is_update); - /* - it is normal to be here when the action is update - this is an error if the action is create. something is borked - */ - } - - ((Field_timestamp *)table->field[Events::FIELD_MODIFIED])->set_time(); - - if (et->comment.str) - { - if (table->field[field_num= Events::FIELD_COMMENT]-> - store(et->comment.str, et->comment.length, scs)) - goto err_truncate; - } - - DBUG_RETURN(0); -err_truncate: - my_error(ER_EVENT_DATA_TOO_LONG, MYF(0), table->field[field_num]->field_name); - DBUG_RETURN(EVEX_GENERAL_ERROR); -} - - -/* - Creates an event in mysql.event - - SYNOPSIS - db_create_event() - thd THD - et Event_timed object containing information for the event - create_if_not If an warning should be generated in case event exists - rows_affected How many rows were affected - - RETURN VALUE - 0 - OK - EVEX_GENERAL_ERROR - Failure - - DESCRIPTION - Creates an event. Relies on evex_fill_row which is shared with - db_update_event. The name of the event is inside "et". -*/ - -int -db_create_event(THD *thd, Event_timed *et, my_bool create_if_not, - uint *rows_affected) -{ - int ret= 0; - CHARSET_INFO *scs= system_charset_info; - TABLE *table; - char old_db_buf[NAME_LEN+1]; - LEX_STRING old_db= { old_db_buf, sizeof(old_db_buf) }; - bool dbchanged= FALSE; - DBUG_ENTER("db_create_event"); - DBUG_PRINT("enter", ("name: %.*s", et->name.length, et->name.str)); - - *rows_affected= 0; - DBUG_PRINT("info", ("open mysql.event for update")); - if (Events::open_event_table(thd, TL_WRITE, &table)) - { - my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0)); - goto err; - } - - DBUG_PRINT("info", ("check existance of an event with the same name")); - if (!evex_db_find_event_aux(thd, et, table)) - { - if (create_if_not) - { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_EVENT_ALREADY_EXISTS, ER(ER_EVENT_ALREADY_EXISTS), - et->name.str); - goto ok; - } - my_error(ER_EVENT_ALREADY_EXISTS, MYF(0), et->name.str); - goto err; - } - - DBUG_PRINT("info", ("non-existant, go forward")); - if ((ret= sp_use_new_db(thd, et->dbname, &old_db, 0, &dbchanged))) - { - my_error(ER_BAD_DB_ERROR, MYF(0)); - goto err; - } - - restore_record(table, s->default_values); // Get default values for fields - - if (system_charset_info->cset->numchars(system_charset_info, et->dbname.str, - et->dbname.str + et->dbname.length) - > EVEX_DB_FIELD_LEN) - { - my_error(ER_TOO_LONG_IDENT, MYF(0), et->dbname.str); - goto err; - } - if (system_charset_info->cset->numchars(system_charset_info, et->name.str, - et->name.str + et->name.length) - > EVEX_DB_FIELD_LEN) - { - my_error(ER_TOO_LONG_IDENT, MYF(0), et->name.str); - goto err; - } - - if (et->body.length > table->field[Events::FIELD_BODY]->field_length) - { - my_error(ER_TOO_LONG_BODY, MYF(0), et->name.str); - goto err; - } - - if (!(et->expression) && !(et->execute_at.year)) - { - DBUG_PRINT("error", ("neither expression nor execute_at are set!")); - my_error(ER_EVENT_NEITHER_M_EXPR_NOR_M_AT, MYF(0)); - goto err; - } - - ((Field_timestamp *)table->field[Events::FIELD_CREATED])->set_time(); - - /* - evex_fill_row() calls my_error() in case of error so no need to - handle it here - */ - if ((ret= evex_fill_row(thd, table, et, false))) - goto err; - - if (table->file->ha_write_row(table->record[0])) - { - my_error(ER_EVENT_STORE_FAILED, MYF(0), et->name.str, ret); - goto err; - } - -#ifdef USE_THIS_CODE_AS_TEMPLATE_WHEN_EVENT_REPLICATION_IS_AGREED - if (mysql_bin_log.is_open()) - { - thd->clear_error(); - /* Such a statement can always go directly to binlog, no trans cache */ - thd->binlog_query(THD::MYSQL_QUERY_TYPE, thd->query, thd->query_length, - FALSE, FALSE); - } -#endif - - *rows_affected= 1; -ok: - if (dbchanged) - (void) mysql_change_db(thd, old_db.str, 1); - if (table) - close_thread_tables(thd); - DBUG_RETURN(EVEX_OK); - -err: - if (dbchanged) - (void) mysql_change_db(thd, old_db.str, 1); - if (table) - close_thread_tables(thd); - DBUG_RETURN(EVEX_GENERAL_ERROR); -} - - -/* - Used to execute ALTER EVENT. Pendant to Events::update_event(). - - SYNOPSIS - db_update_event() - thd THD - sp_name the name of the event to alter - et event's data - - RETURN VALUE - 0 OK - EVEX_GENERAL_ERROR Error occured (my_error() called) - - NOTES - sp_name is passed since this is the name of the event to - alter in case of RENAME TO. -*/ - -static int -db_update_event(THD *thd, Event_timed *et, sp_name *new_name) -{ - CHARSET_INFO *scs= system_charset_info; - TABLE *table; - int ret= EVEX_OPEN_TABLE_FAILED; - DBUG_ENTER("db_update_event"); - DBUG_PRINT("enter", ("dbname: %.*s", et->dbname.length, et->dbname.str)); - DBUG_PRINT("enter", ("name: %.*s", et->name.length, et->name.str)); - DBUG_PRINT("enter", ("user: %.*s", et->definer.length, et->definer.str)); - if (new_name) - DBUG_PRINT("enter", ("rename to: %.*s", new_name->m_name.length, - new_name->m_name.str)); - - if (Events::open_event_table(thd, TL_WRITE, &table)) - { - my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0)); - goto err; - } - - /* first look whether we overwrite */ - if (new_name) - { - if (!sortcmp_lex_string(et->name, new_name->m_name, scs) && - !sortcmp_lex_string(et->dbname, new_name->m_db, scs)) - { - my_error(ER_EVENT_SAME_NAME, MYF(0), et->name.str); - goto err; - } - - if (!evex_db_find_event_by_name(thd,new_name->m_db,new_name->m_name,table)) - { - my_error(ER_EVENT_ALREADY_EXISTS, MYF(0), new_name->m_name.str); - goto err; - } - } - /* - ...and then if there is such an event. Don't exchange the blocks - because you will get error 120 from table handler because new_name will - overwrite the key and SE will tell us that it cannot find the already found - row (copied into record[1] later - */ - if (EVEX_KEY_NOT_FOUND == evex_db_find_event_aux(thd, et, table)) - { - my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), et->name.str); - goto err; - } - - store_record(table,record[1]); - - /* Don't update create on row update. */ - table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; - - /* - evex_fill_row() calls my_error() in case of error so no need to - handle it here - */ - if ((ret= evex_fill_row(thd, table, et, true))) - goto err; - - if (new_name) - { - table->field[Events::FIELD_DB]-> - store(new_name->m_db.str, new_name->m_db.length, scs); - table->field[Events::FIELD_NAME]-> - store(new_name->m_name.str, new_name->m_name.length, scs); - } - - if ((ret= table->file->ha_update_row(table->record[1], table->record[0]))) - { - my_error(ER_EVENT_STORE_FAILED, MYF(0), et->name.str, ret); - goto err; - } - - /* close mysql.event or we crash later when loading the event from disk */ - close_thread_tables(thd); - DBUG_RETURN(0); - -err: - if (table) - close_thread_tables(thd); - DBUG_RETURN(EVEX_GENERAL_ERROR); -} - - -/* - Looks for a named event in mysql.event and in case of success returns - an object will data loaded from the table. - - SYNOPSIS - db_find_event() - thd THD - name the name of the event to find - ett event's data if event is found - tbl TABLE object to use when not NULL - - NOTES - 1) Use sp_name for look up, return in **ett if found - 2) tbl is not closed at exit - - RETURN VALUE - 0 ok In this case *ett is set to the event - # error *ett == 0 -*/ - -int -db_find_event(THD *thd, sp_name *name, Event_timed **ett, TABLE *tbl, - MEM_ROOT *root) -{ - TABLE *table; - int ret; - Event_timed *et= NULL; - DBUG_ENTER("db_find_event"); - DBUG_PRINT("enter", ("name: %*s", name->m_name.length, name->m_name.str)); - - if (!root) - root= &evex_mem_root; - - if (tbl) - table= tbl; - else if (Events::open_event_table(thd, TL_READ, &table)) - { - my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0)); - ret= EVEX_GENERAL_ERROR; - goto done; - } - - if ((ret= evex_db_find_event_by_name(thd, name->m_db, name->m_name, table))) - { - my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), name->m_name.str); - goto done; - } - et= new Event_timed; - - /* - 1)The table should not be closed beforehand. ::load_from_row() only loads - and does not compile - - 2)::load_from_row() is silent on error therefore we emit error msg here - */ - if ((ret= et->load_from_row(root, table))) - { - my_error(ER_CANNOT_LOAD_FROM_TABLE, MYF(0)); - goto done; - } - -done: - if (ret) - { - delete et; - et= 0; - } - /* don't close the table if we haven't opened it ourselves */ - if (!tbl && table) - close_thread_tables(thd); - *ett= et; - DBUG_RETURN(ret); + return db_repository->open_event_table(thd, lock_type, table); } @@ -896,33 +291,33 @@ done: RETURN VALUE 0 OK - !0 Error + !0 Error (Reported) NOTES - - in case there is an event with the same name (db) and - IF NOT EXISTS is specified, an warning is put into the W stack. + In case there is an event with the same name (db) and + IF NOT EXISTS is specified, an warning is put into the stack. */ int -Events::create_event(THD *thd, Event_timed *et, uint create_options, +Events::create_event(THD *thd, Event_parse_data *parse_data, bool if_not_exists, uint *rows_affected) { int ret; - DBUG_ENTER("Events::create_event"); - DBUG_PRINT("enter", ("name: %*s options:%d", et->name.length, - et->name.str, create_options)); - if (!(ret = db_create_event(thd, et, - create_options & HA_LEX_CREATE_IF_NOT_EXISTS, - rows_affected))) + pthread_mutex_lock(&LOCK_event_metadata); + /* On error conditions my_error() is called so no need to handle here */ + if (!(ret= db_repository->create_event(thd, parse_data, if_not_exists, + rows_affected))) { - Event_scheduler *scheduler= Event_scheduler::get_instance(); - if (scheduler->initialized() && - (ret= scheduler->create_event(thd, et, true))) - my_error(ER_EVENT_MODIFY_QUEUE_ERROR, MYF(0), ret); + if ((ret= event_queue->create_event(thd, parse_data->dbname, + parse_data->name))) + { + DBUG_ASSERT(ret == OP_LOAD_ERROR); + my_error(ER_EVENT_MODIFY_QUEUE_ERROR, MYF(0)); + } } - /* No need to close the table, it will be closed in sql_parse::do_command */ + pthread_mutex_unlock(&LOCK_event_metadata); DBUG_RETURN(ret); } @@ -934,8 +329,8 @@ Events::create_event(THD *thd, Event_timed *et, uint create_options, SYNOPSIS Events::update_event() thd THD - et event's data - new_name set in case of RENAME TO. + et Event's data from parsing stage + new_name Set in case of RENAME TO. RETURN VALUE 0 OK @@ -948,27 +343,28 @@ Events::create_event(THD *thd, Event_timed *et, uint create_options, */ int -Events::update_event(THD *thd, Event_timed *et, sp_name *new_name, - uint *rows_affected) +Events::update_event(THD *thd, Event_parse_data *parse_data, sp_name *new_name, + uint *rows_affected) { int ret; - DBUG_ENTER("Events::update_event"); - DBUG_PRINT("enter", ("name: %*s", et->name.length, et->name.str)); - /* - db_update_event() opens & closes the table to prevent - crash later in the code when loading and compiling the new definition. - Also on error conditions my_error() is called so no need to handle here - */ - if (!(ret= db_update_event(thd, et, new_name))) + + pthread_mutex_lock(&LOCK_event_metadata); + /* On error conditions my_error() is called so no need to handle here */ + if (!(ret= db_repository->update_event(thd, parse_data, new_name))) { - Event_scheduler *scheduler= Event_scheduler::get_instance(); - if (scheduler->initialized() && - (ret= scheduler->update_event(thd, et, - new_name? &new_name->m_db: NULL, - new_name? &new_name->m_name: NULL))) - my_error(ER_EVENT_MODIFY_QUEUE_ERROR, MYF(0), ret); + if ((ret= event_queue->update_event(thd, + parse_data->dbname, + parse_data->name, + new_name? &new_name->m_db: NULL, + new_name? &new_name->m_name: NULL))) + { + DBUG_ASSERT(ret == OP_LOAD_ERROR); + my_error(ER_EVENT_MODIFY_QUEUE_ERROR, MYF(0)); + } } + pthread_mutex_unlock(&LOCK_event_metadata); + DBUG_RETURN(ret); } @@ -977,95 +373,69 @@ Events::update_event(THD *thd, Event_timed *et, sp_name *new_name, Drops an event SYNOPSIS - db_drop_event() + Events::drop_event() thd THD - et event's name - drop_if_exists if set and the event not existing => warning onto the stack - rows_affected affected number of rows is returned heres + dbname Event's schema + name Event's name + if_exists When set and the event does not exist => warning onto + the stack + rows_affected Affected number of rows is returned heres + only_from_disk Whether to remove the event from the queue too. In case + of Event_job_data::drop() it's needed to do only disk + drop because Event_queue will handle removal from memory + queue. RETURN VALUE - 0 OK - !0 Error (my_error() called) + 0 OK + !0 Error (reported) */ -int db_drop_event(THD *thd, Event_timed *et, bool drop_if_exists, - uint *rows_affected) +int +Events::drop_event(THD *thd, LEX_STRING dbname, LEX_STRING name, bool if_exists, + uint *rows_affected, bool only_from_disk) { - TABLE *table; - Open_tables_state backup; int ret; + DBUG_ENTER("Events::drop_event"); - DBUG_ENTER("db_drop_event"); - ret= EVEX_OPEN_TABLE_FAILED; - - thd->reset_n_backup_open_tables_state(&backup); - if (Events::open_event_table(thd, TL_WRITE, &table)) - { - my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0)); - goto done; - } - - if (!(ret= evex_db_find_event_aux(thd, et, table))) + pthread_mutex_lock(&LOCK_event_metadata); + /* On error conditions my_error() is called so no need to handle here */ + if (!(ret= db_repository->drop_event(thd, dbname, name, if_exists, + rows_affected))) { - if ((ret= table->file->ha_delete_row(table->record[0]))) - { - my_error(ER_EVENT_CANNOT_DELETE, MYF(0)); - goto done; - } - } - else if (ret == EVEX_KEY_NOT_FOUND) - { - if (drop_if_exists) - { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_SP_DOES_NOT_EXIST, ER(ER_SP_DOES_NOT_EXIST), - "Event", et->name.str); - ret= 0; - } else - my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), et->name.str); - goto done; + if (!only_from_disk) + event_queue->drop_event(thd, dbname, name); } - - -done: - /* - evex_drop_event() is used by Event_timed::drop therefore - we have to close our thread tables. - */ - close_thread_tables(thd); - thd->restore_backup_open_tables_state(&backup); + pthread_mutex_unlock(&LOCK_event_metadata); DBUG_RETURN(ret); } /* - Drops an event + Drops all events from a schema SYNOPSIS - Events::drop_event() - thd THD - et event's name - drop_if_exists if set and the event not existing => warning onto the stack - rows_affected affected number of rows is returned heres + Events::drop_schema_events() + thd Thread + db ASCIIZ schema name RETURN VALUE - 0 OK - !0 Error (reported) + 0 OK + !0 Error */ int -Events::drop_event(THD *thd, Event_timed *et, bool drop_if_exists, - uint *rows_affected) +Events::drop_schema_events(THD *thd, char *db) { - int ret; + int ret= 0; + LEX_STRING db_lex= {db, strlen(db)}; + + DBUG_ENTER("evex_drop_db_events"); + DBUG_PRINT("enter", ("dropping events from %s", db)); - DBUG_ENTER("Events::drop_event"); - if (!(ret= db_drop_event(thd, et, drop_if_exists, rows_affected))) - { - Event_scheduler *scheduler= Event_scheduler::get_instance(); - if (scheduler->initialized() && (ret= scheduler->drop_event(thd, et))) - my_error(ER_EVENT_MODIFY_QUEUE_ERROR, MYF(0), ret); - } + pthread_mutex_lock(&LOCK_event_metadata); + event_queue->drop_schema_events(thd, db_lex); + ret= db_repository->drop_schema_events(thd, db_lex); + pthread_mutex_unlock(&LOCK_event_metadata); DBUG_RETURN(ret); } @@ -1088,14 +458,14 @@ int Events::show_create_event(THD *thd, sp_name *spn) { int ret; - Event_timed *et= NULL; + Event_timed *et= new Event_timed(); Open_tables_state backup; - DBUG_ENTER("evex_update_event"); + DBUG_ENTER("Events::show_create_event"); DBUG_PRINT("enter", ("name: %*s", spn->m_name.length, spn->m_name.str)); thd->reset_n_backup_open_tables_state(&backup); - ret= db_find_event(thd, spn, &et, NULL, thd->mem_root); + ret= db_repository->find_event(thd, spn->m_db, spn->m_name, et); thd->restore_backup_open_tables_state(&backup); if (!ret) @@ -1140,102 +510,46 @@ Events::show_create_event(THD *thd, sp_name *spn) DBUG_RETURN(ret); err: delete et; - DBUG_RETURN(1); -} - - -/* - Drops all events from a schema - - SYNOPSIS - Events::drop_schema_events() - thd Thread - db ASCIIZ schema name - - RETURN VALUE - 0 OK - !0 Error -*/ - -int -Events::drop_schema_events(THD *thd, char *db) -{ - int ret= 0; - LEX_STRING db_lex= {db, strlen(db)}; - - DBUG_ENTER("evex_drop_db_events"); - DBUG_PRINT("enter", ("dropping events from %s", db)); - - Event_scheduler *scheduler= Event_scheduler::get_instance(); - if (scheduler->initialized()) - ret= scheduler->drop_schema_events(thd, &db_lex); - else - ret= db_drop_events_from_table(thd, &db_lex); - - DBUG_RETURN(ret); + DBUG_RETURN(1); } /* - Drops all events in the selected database, from mysql.event. + Proxy for Event_db_repository::fill_schema_events. + Callback for I_S from sql_show.cc SYNOPSIS - evex_drop_db_events_from_table() - thd Thread - db Schema name + Events::fill_schema_events() + thd Thread + tables The schema table + cond Unused RETURN VALUE - 0 OK - !0 Error from ha_delete_row + 0 OK + !0 Error */ int -db_drop_events_from_table(THD *thd, LEX_STRING *db) +Events::fill_schema_events(THD *thd, TABLE_LIST *tables, COND * /* cond */) { - int ret; - TABLE *table; - READ_RECORD read_record_info; - DBUG_ENTER("db_drop_events_from_table"); - DBUG_PRINT("info", ("dropping events from %s", db->str)); - - if ((ret= Events::open_event_table(thd, TL_WRITE, &table))) - { - if (my_errno != ENOENT) - sql_print_error("Table mysql.event is damaged. Got error %d on open", - my_errno); - DBUG_RETURN(ret); - } - /* only enabled events are in memory, so we go now and delete the rest */ - init_read_record(&read_record_info, thd, table, NULL, 1, 0); - while (!(read_record_info.read_record(&read_record_info)) && !ret) + char *db= NULL; + DBUG_ENTER("Events::fill_schema_events"); + /* + If it's SHOW EVENTS then thd->lex->select_lex.db is guaranteed not to + be NULL. Let's do an assert anyway. + */ + if (thd->lex->sql_command == SQLCOM_SHOW_EVENTS) { - char *et_db= get_field(thd->mem_root, - table->field[Events::FIELD_DB]); - - LEX_STRING et_db_lex= {et_db, strlen(et_db)}; - DBUG_PRINT("info", ("Current event %s.%s", et_db, - get_field(thd->mem_root, - table->field[Events::FIELD_NAME]))); - - if (!sortcmp_lex_string(et_db_lex, *db, system_charset_info)) - { - DBUG_PRINT("info", ("Dropping")); - if ((ret= table->file->ha_delete_row(table->record[0]))) - my_error(ER_EVENT_DROP_FAILED, MYF(0), - get_field(thd->mem_root, - table->field[Events::FIELD_NAME])); - } + DBUG_ASSERT(thd->lex->select_lex.db); + if (check_access(thd, EVENT_ACL, thd->lex->select_lex.db, 0, 0, 0, + is_schema_db(thd->lex->select_lex.db))) + DBUG_RETURN(1); + db= thd->lex->select_lex.db; } - end_read_record(&read_record_info); - thd->version--; /* Force close to free memory */ - - close_thread_tables(thd); - - DBUG_RETURN(ret); + DBUG_RETURN(get_instance()->db_repository->fill_schema_events(thd, tables, db)); } - /* Inits the scheduler's structures. @@ -1254,17 +568,19 @@ int Events::init() { int ret= 0; + Event_db_repository *db_repo; DBUG_ENTER("Events::init"); + event_queue->init_queue(db_repository, scheduler_ng); + scheduler_ng->init_scheduler(event_queue); /* it should be an assignment! */ if (opt_event_scheduler) { - Event_scheduler *scheduler= Event_scheduler::get_instance(); DBUG_ASSERT(opt_event_scheduler == 1 || opt_event_scheduler == 2); - DBUG_RETURN(scheduler->init() || - (opt_event_scheduler == 1? scheduler->start(): - scheduler->start_suspended())); + if (opt_event_scheduler == 1) + DBUG_RETURN(scheduler_ng->start()); } + DBUG_RETURN(0); } @@ -1273,70 +589,157 @@ Events::init() Cleans up scheduler's resources. Called at server shutdown. SYNOPSIS - Events::shutdown() + Events::deinit() NOTES This function is not synchronized. */ void -Events::shutdown() +Events::deinit() { - DBUG_ENTER("Events::shutdown"); - Event_scheduler *scheduler= Event_scheduler::get_instance(); - if (scheduler->initialized()) - { - scheduler->stop(); - scheduler->destroy(); - } + DBUG_ENTER("Events::deinit"); + + scheduler_ng->stop(); + scheduler_ng->deinit_scheduler(); + + event_queue->deinit_queue(); DBUG_VOID_RETURN; } /* + Inits Events mutexes + + SYNOPSIS + Events::init_mutexes() + thd Thread +*/ + +void +Events::init_mutexes() +{ + pthread_mutex_init(&LOCK_event_metadata, MY_MUTEX_INIT_FAST); + + db_repository= new Event_db_repository; + + event_queue= new Event_queue; + event_queue->init_mutexes(); + + scheduler_ng= new Event_scheduler_ng(); + scheduler_ng->init_mutexes(); +} + + +/* + Destroys Events mutexes + + SYNOPSIS + Events::destroy_mutexes() +*/ + +void +Events::destroy_mutexes() +{ + event_queue->deinit_mutexes(); + scheduler_ng->deinit_mutexes(); + + delete scheduler_ng; + delete db_repository; + + pthread_mutex_destroy(&LOCK_event_metadata); +} + + +/* Proxy for Event_scheduler::dump_internal_status SYNOPSIS Events::dump_internal_status() thd Thread - + RETURN VALUE - 0 OK - !0 Error + FALSE OK + TRUE Error */ -int +bool Events::dump_internal_status(THD *thd) { - return Event_scheduler::dump_internal_status(thd); + DBUG_ENTER("Events::dump_internal_status"); + Protocol *protocol= thd->protocol; + List<Item> field_list; + + field_list.push_back(new Item_empty_string("Name", 30)); + field_list.push_back(new Item_empty_string("Value",20)); + if (protocol->send_fields(&field_list, Protocol::SEND_NUM_ROWS | + Protocol::SEND_EOF)) + DBUG_RETURN(TRUE); + + if (scheduler_ng->dump_internal_status(thd) || + event_queue->dump_internal_status(thd)) + DBUG_RETURN(TRUE); + + send_eof(thd); + DBUG_RETURN(FALSE); } /* - Inits Events mutexes + Starts execution of events by the scheduler SYNOPSIS - Events::init_mutexes() - thd Thread + Events::start_execution_of_events() + + RETURN VALUE + FALSE OK + TRUE Error */ -void -Events::init_mutexes() +bool +Events::start_execution_of_events() { - Event_scheduler::init_mutexes(); + DBUG_ENTER("Events::start_execution_of_events"); + DBUG_RETURN(scheduler_ng->start()); } /* - Destroys Events mutexes + Stops execution of events by the scheduler. + Already running events will not be stopped. If the user needs + them stopped manual intervention is needed. SYNOPSIS - Events::destroy_mutexes() + Events::stop_execution_of_events() + + RETURN VALUE + FALSE OK + TRUE Error */ -void -Events::destroy_mutexes() +bool +Events::stop_execution_of_events() +{ + DBUG_ENTER("Events::stop_execution_of_events"); + DBUG_RETURN(scheduler_ng->stop()); +} + + +/* + Checks whether the scheduler is running or not. + + SYNOPSIS + Events::is_started() + + RETURN VALUE + TRUE Yes + FALSE No +*/ + +bool +Events::is_started() { - Event_scheduler::destroy_mutexes(); + DBUG_ENTER("Events::is_started"); + DBUG_RETURN(scheduler_ng->get_state() == Event_scheduler_ng::RUNNING); } diff --git a/sql/events.h b/sql/events.h index 66cce6e7777..f825a75934f 100644 --- a/sql/events.h +++ b/sql/events.h @@ -16,78 +16,112 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +class sp_name; +class Event_parse_data; +class Event_db_repository; +class Event_queue; +class Event_queue_element; +class Event_scheduler_ng; + +/* Return codes */ +enum enum_events_error_code +{ + OP_OK= 0, + OP_NOT_RUNNING, + OP_CANT_KILL, + OP_CANT_INIT, + OP_DISABLED_EVENT, + OP_LOAD_ERROR, + OP_ALREADY_EXISTS +}; + +int +sortcmp_lex_string(LEX_STRING s, LEX_STRING t, CHARSET_INFO *cs); -class Event_timed; class Events { public: + friend class Event_queue_element; + /* + Quite NOT the best practice and will be removed once + Event_timed::drop() and Event_timed is fixed not do drop directly + or other scheme will be found. + */ + static ulong opt_event_scheduler; static TYPELIB opt_typelib; - enum enum_table_field - { - FIELD_DB = 0, - FIELD_NAME, - FIELD_BODY, - FIELD_DEFINER, - FIELD_EXECUTE_AT, - FIELD_INTERVAL_EXPR, - FIELD_TRANSIENT_INTERVAL, - FIELD_CREATED, - FIELD_MODIFIED, - FIELD_LAST_EXECUTED, - FIELD_STARTS, - FIELD_ENDS, - FIELD_STATUS, - FIELD_ON_COMPLETION, - FIELD_SQL_MODE, - FIELD_COMMENT, - FIELD_COUNT /* a cool trick to count the number of fields :) */ - }; + int + init(); + + void + deinit(); - static int - create_event(THD *thd, Event_timed *et, uint create_options, + void + init_mutexes(); + + void + destroy_mutexes(); + + bool + start_execution_of_events(); + + bool + stop_execution_of_events(); + + bool + is_started(); + + static Events* + get_instance(); + + int + create_event(THD *thd, Event_parse_data *parse_data, bool if_exists, uint *rows_affected); - static int - update_event(THD *thd, Event_timed *et, sp_name *new_name, + int + update_event(THD *thd, Event_parse_data *parse_data, sp_name *new_name, uint *rows_affected); - static int - drop_event(THD *thd, Event_timed *et, bool drop_if_exists, - uint *rows_affected); + int + drop_event(THD *thd, LEX_STRING dbname, LEX_STRING name, bool if_exists, + uint *rows_affected, bool only_from_disk); - static int + int + drop_schema_events(THD *thd, char *db); + + int open_event_table(THD *thd, enum thr_lock_type lock_type, TABLE **table); - static int + int show_create_event(THD *thd, sp_name *spn); + /* Needed for both SHOW CREATE EVENT and INFORMATION_SCHEMA */ static int reconstruct_interval_expression(String *buf, interval_type interval, longlong expression); static int - drop_schema_events(THD *thd, char *db); + fill_schema_events(THD *thd, TABLE_LIST *tables, COND * /* cond */); - static int + bool dump_internal_status(THD *thd); - - static int - init(); - - static void - shutdown(); - static void - init_mutexes(); - - static void - destroy_mutexes(); +private: + /* Singleton DP is used */ + Events(){} + ~Events(){} + /* Singleton instance */ + static Events singleton; + + Event_queue *event_queue; + Event_scheduler_ng *scheduler_ng; + Event_db_repository *db_repository; + + pthread_mutex_t LOCK_event_metadata; -private: /* Prevent use of these */ Events(const Events &); void operator=(Events &); diff --git a/sql/events_priv.h b/sql/events_priv.h deleted file mode 100644 index ed02cb7055b..00000000000 --- a/sql/events_priv.h +++ /dev/null @@ -1,79 +0,0 @@ -#ifndef _EVENT_PRIV_H_ -#define _EVENT_PRIV_H_ -/* Copyright (C) 2004-2006 MySQL AB - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - -#define EVENT_EXEC_STARTED 0 -#define EVENT_EXEC_ALREADY_EXEC 1 -#define EVENT_EXEC_CANT_FORK 2 - -#define EVEX_DB_FIELD_LEN 64 -#define EVEX_NAME_FIELD_LEN 64 -#define EVEX_MAX_INTERVAL_VALUE 2147483647L - -class Event_timed; - -int -evex_db_find_event_by_name(THD *thd, const LEX_STRING dbname, - const LEX_STRING ev_name, - TABLE *table); - -int -db_drop_event(THD *thd, Event_timed *et, bool drop_if_exists, - uint *rows_affected); -int -db_find_event(THD *thd, sp_name *name, Event_timed **ett, TABLE *tbl, - MEM_ROOT *root); - -int -db_create_event(THD *thd, Event_timed *et, my_bool create_if_not, - uint *rows_affected); - -int -db_drop_events_from_table(THD *thd, LEX_STRING *db); - -int -sortcmp_lex_string(LEX_STRING s, LEX_STRING t, CHARSET_INFO *cs); - -/* Compares only the name part of the identifier */ -bool -event_timed_name_equal(Event_timed *et, LEX_STRING *name); - -/* Compares only the schema part of the identifier */ -bool -event_timed_db_equal(Event_timed *et, LEX_STRING *db); - -/* - Compares only the definer part of the identifier. Use during DROP USER - to drop user's events. (Still not implemented) -*/ -bool -event_timed_definer_equal(Event_timed *et, LEX_STRING *definer); - -/* Compares the whole identifier*/ -bool -event_timed_identifier_equal(Event_timed *a, Event_timed *b); - - -bool -change_security_context(THD *thd, LEX_STRING user, LEX_STRING host, - LEX_STRING db, Security_context *s_ctx, - Security_context **backup); - -void -restore_security_context(THD *thd, Security_context *backup); - -#endif /* _EVENT_PRIV_H_ */ diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 6e57993a61a..8fc6155087e 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -864,7 +864,7 @@ static void close_connections(void) DBUG_PRINT("quit",("Informing thread %ld that it's time to die", tmp->thread_id)); /* We skip slave threads & scheduler on this first loop through. */ - if (tmp->slave_thread || tmp->system_thread == SYSTEM_THREAD_EVENT_SCHEDULER) + if (tmp->slave_thread) continue; tmp->killed= THD::KILL_CONNECTION; @@ -883,7 +883,7 @@ static void close_connections(void) } (void) pthread_mutex_unlock(&LOCK_thread_count); // For unlink from list - Events::shutdown(); + Events::get_instance()->deinit(); end_slave(); if (thread_count) @@ -1321,7 +1321,7 @@ static void clean_up_mutexes() (void) pthread_mutex_destroy(&LOCK_bytes_sent); (void) pthread_mutex_destroy(&LOCK_bytes_received); (void) pthread_mutex_destroy(&LOCK_user_conn); - Events::destroy_mutexes(); + Events::get_instance()->destroy_mutexes(); #ifdef HAVE_OPENSSL (void) pthread_mutex_destroy(&LOCK_des_key_file); #ifndef HAVE_YASSL @@ -2884,7 +2884,7 @@ static int init_thread_environment() (void) pthread_mutex_init(&LOCK_server_started, MY_MUTEX_INIT_FAST); (void) pthread_cond_init(&COND_server_started,NULL); sp_cache_init(); - Events::init_mutexes(); + Events::get_instance()->init_mutexes(); /* Parameter for threads created for connections */ (void) pthread_attr_init(&connection_attrib); (void) pthread_attr_setdetachstate(&connection_attrib, @@ -3675,7 +3675,7 @@ we force server id to 2, but this MySQL server will not act as a slave."); if (!opt_noacl) { - Events::init(); + Events::get_instance()->init(); } #if defined(__NT__) || defined(HAVE_SMEM) handle_connections_methods(); diff --git a/sql/set_var.cc b/sql/set_var.cc index bb9ef4d453f..4347ca84a75 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -57,7 +57,7 @@ #include <myisam.h> #include <my_dir.h> -#include "event_scheduler.h" +#include "events.h" /* WITH_BERKELEY_STORAGE_ENGINE */ extern bool berkeley_shared_data; @@ -3892,30 +3892,32 @@ byte *sys_var_thd_dbug::value_ptr(THD *thd, enum_var_type type, LEX_STRING *b) bool sys_var_event_scheduler::update(THD *thd, set_var *var) { - enum Event_scheduler::enum_error_code res; - Event_scheduler *scheduler= Event_scheduler::get_instance(); + int res; /* here start the thread if not running. */ DBUG_ENTER("sys_var_event_scheduler::update"); - - DBUG_PRINT("new_value", ("%lu", (bool)var->save_result.ulong_value)); - if (!scheduler->initialized()) + if (Events::opt_event_scheduler == 0) { my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--event-scheduler=0"); - DBUG_RETURN(true); + DBUG_RETURN(TRUE); } + DBUG_PRINT("new_value", ("%lu", (bool)var->save_result.ulong_value)); + if (var->save_result.ulonglong_value < 1 || var->save_result.ulonglong_value > 2) { char buf[64]; my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), "event_scheduler", llstr(var->save_result.ulonglong_value, buf)); - DBUG_RETURN(true); + DBUG_RETURN(TRUE); } - if ((res= scheduler->suspend_or_resume(var->save_result.ulonglong_value == 1? - Event_scheduler::RESUME : - Event_scheduler::SUSPEND))) - my_error(ER_EVENT_SET_VAR_ERROR, MYF(0), (uint) res); + if (var->save_result.ulonglong_value == 1) + res= Events::get_instance()->start_execution_of_events(); + else + res= Events::get_instance()->stop_execution_of_events(); + + if (res) + my_error(ER_EVENT_SET_VAR_ERROR, MYF(0)); DBUG_RETURN((bool) res); } @@ -3923,11 +3925,9 @@ sys_var_event_scheduler::update(THD *thd, set_var *var) byte *sys_var_event_scheduler::value_ptr(THD *thd, enum_var_type type, LEX_STRING *base) { - Event_scheduler *scheduler= Event_scheduler::get_instance(); - - if (!scheduler->initialized()) + if (Events::opt_event_scheduler == 0) thd->sys_var_tmp.long_value= 0; - else if (scheduler->get_state() == Event_scheduler::RUNNING) + else if (Events::get_instance()->is_started()) thd->sys_var_tmp.long_value= 1; else thd->sys_var_tmp.long_value= 2; diff --git a/sql/share/errmsg.txt b/sql/share/errmsg.txt index 8d2822370f2..dbac6863fb3 100644 --- a/sql/share/errmsg.txt +++ b/sql/share/errmsg.txt @@ -5829,9 +5829,9 @@ ER_CANT_CHANGE_TX_ISOLATION 25001 ER_DUP_ENTRY_AUTOINCREMENT_CASE eng "ALTER TABLE causes auto_increment resequencing, resulting in duplicate entry '%-.64s' for key '%-.64s'" ER_EVENT_MODIFY_QUEUE_ERROR - eng "Internal scheduler error %d" + eng "Error during loading event from disk. mysql.event damaged?" ER_EVENT_SET_VAR_ERROR - eng "Error during starting/stopping of the scheduler. Error code %u" + eng "Error during starting/stopping of the scheduler." ER_PARTITION_MERGE_ERROR eng "%s handler cannot be used in partitioned tables" swe "%s kan inte användas i en partitionerad tabell" @@ -5839,3 +5839,6 @@ ER_CANT_ACTIVATE_LOG eng "Cannot activate '%-.64s' log." ER_RBR_NOT_AVAILABLE eng "The server was not built with row-based replication" +ER_EVENT_RECURSIVITY_FORBIDDEN + eng "Recursivity of EVENT DDL statements is forbidden when body is present" + diff --git a/sql/sql_class.cc b/sql/sql_class.cc index c8c8ff16199..47381c6aecb 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -2068,6 +2068,63 @@ bool Security_context::set_user(char *user_arg) return user == 0; } +/* + Switches the security context + SYNOPSIS + THD::change_security_context() + user The user + host The host of the user + db The schema for which the security_ctx will be loaded + s_ctx Security context to load state into + backup Where to store the old context + + RETURN VALUE + FALSE OK + TRUE Error (generates error too) +*/ + +bool +THD::change_security_context(LEX_STRING user, LEX_STRING host, + LEX_STRING db, Security_context *s_ctx, + Security_context **backup) +{ + DBUG_ENTER("change_security_context"); + DBUG_PRINT("info",("%s@%s@%s", user.str, host.str, db.str)); +#ifndef NO_EMBEDDED_ACCESS_CHECKS + s_ctx->init(); + *backup= 0; + if (acl_getroot_no_password(s_ctx, user.str, host.str, host.str, db.str)) + { + my_error(ER_NO_SUCH_USER, MYF(0), user.str, host.str); + DBUG_RETURN(TRUE); + } + *backup= security_ctx; + security_ctx= s_ctx; +#endif + DBUG_RETURN(FALSE); +} + + +/* + Restores the security context + SYNOPSIS + restore_security_context() + thd Thread + backup Context to switch to +*/ + +void +THD::restore_security_context(Security_context *backup) +{ + DBUG_ENTER("restore_security_context"); +#ifndef NO_EMBEDDED_ACCESS_CHECKS + if (backup) + security_ctx= backup; +#endif + DBUG_VOID_RETURN; +} + + /**************************************************************************** Handling of open and locked tables states. diff --git a/sql/sql_class.h b/sql/sql_class.h index b79f0753603..18cc53daf6d 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -868,6 +868,14 @@ public: char *db, *catalog; Security_context main_security_ctx; Security_context *security_ctx; + + bool + change_security_context(LEX_STRING user, LEX_STRING host, + LEX_STRING db, Security_context *s_ctx, + Security_context **backup); + + void + restore_security_context(Security_context *backup); /* remote (peer) port */ uint16 peer_port; diff --git a/sql/sql_db.cc b/sql/sql_db.cc index 41e7e5df1f7..541ed9c7cc4 100644 --- a/sql/sql_db.cc +++ b/sql/sql_db.cc @@ -904,7 +904,7 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent) exit: (void)sp_drop_db_routines(thd, db); /* QQ Ignore errors for now */ - error= Events::drop_schema_events(thd, db); + error= Events::get_instance()->drop_schema_events(thd, db); /* If this database was the client's selected database, we silently change the client's selected database to nothing (to have an empty SELECT DATABASE() diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index f6031a1f2fd..386dece8813 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -174,11 +174,11 @@ void lex_start(THD *thd, const uchar *buf, uint length) lex->sphead= NULL; lex->spcont= NULL; lex->proc_list.first= 0; - lex->escape_used= lex->et_compile_phase= FALSE; + lex->escape_used= FALSE; lex->reset_query_tables_list(FALSE); lex->name= 0; - lex->et= NULL; + lex->event_parse_data= NULL; lex->nest_level=0 ; lex->allow_sum_func= 0; diff --git a/sql/sql_lex.h b/sql/sql_lex.h index c889c2c5f94..77630065d3a 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -27,7 +27,7 @@ class sp_instr; class sp_pcontext; class st_alter_tablespace; class partition_info; -class Event_timed; +class Event_parse_data; #ifdef MYSQL_SERVER /* @@ -1021,8 +1021,7 @@ typedef struct st_lex : public Query_tables_list st_sp_chistics sp_chistics; - Event_timed *et; - bool et_compile_phase; + Event_parse_data *event_parse_data; bool only_view; /* used for SHOW CREATE TABLE/VIEW */ /* diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 5b860cc0623..e68d23cfdcd 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -27,7 +27,7 @@ #include "sp.h" #include "sp_cache.h" #include "events.h" -#include "event_timed.h" +#include "event_data_objects.h" #ifdef HAVE_OPENSSL /* @@ -3875,73 +3875,44 @@ end_with_restore_list: } case SQLCOM_CREATE_EVENT: case SQLCOM_ALTER_EVENT: - case SQLCOM_DROP_EVENT: { - uint rows_affected= 1; - DBUG_ASSERT(lex->et); - do { - if (! lex->et->dbname.str || - (lex->sql_command == SQLCOM_ALTER_EVENT && lex->spname && - !lex->spname->m_db.str)) - { - my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR), MYF(0)); - res= true; - break; - } - - if (check_access(thd, EVENT_ACL, lex->et->dbname.str, 0, 0, 0, - is_schema_db(lex->et->dbname.str)) || - (lex->sql_command == SQLCOM_ALTER_EVENT && lex->spname && - (check_access(thd, EVENT_ACL, lex->spname->m_db.str, 0, 0, 0, - is_schema_db(lex->spname->m_db.str))))) - break; - - if (end_active_trans(thd)) - { - res= -1; - break; - } - - switch (lex->sql_command) { - case SQLCOM_CREATE_EVENT: - res= Events::create_event(thd, lex->et, - (uint) lex->create_info.options, - &rows_affected); - break; - case SQLCOM_ALTER_EVENT: - res= Events::update_event(thd, lex->et, lex->spname, - &rows_affected); - break; - case SQLCOM_DROP_EVENT: - res= Events::drop_event(thd, lex->et, lex->drop_if_exists, - &rows_affected); - default:; - } - DBUG_PRINT("info", ("CREATE/ALTER/DROP returned error code=%d af_rows=%d", - res, rows_affected)); - if (!res) - send_ok(thd, rows_affected); + uint affected= 1; + DBUG_ASSERT(lex->event_parse_data); + switch (lex->sql_command) { + case SQLCOM_CREATE_EVENT: + res= Events::get_instance()-> + create_event(thd, lex->event_parse_data, + lex->create_info.options & HA_LEX_CREATE_IF_NOT_EXISTS, + &affected); + break; + case SQLCOM_ALTER_EVENT: + res= Events::get_instance()-> + update_event(thd, lex->event_parse_data, lex->spname, &affected); + break; + default:; + } + DBUG_PRINT("info",("DDL error code=%d affected=%d", res, affected)); + if (!res) + send_ok(thd, affected); - /* lex->unit.cleanup() is called outside, no need to call it here */ - } while (0); + /* Don't do it, if we are inside a SP */ if (!thd->spcont) { - lex->et->free_sphead_on_delete= true; - lex->et->free_sp(); - lex->et->deinit_mutexes(); + delete lex->sphead; + lex->sphead= NULL; } - + + /* lex->unit.cleanup() is called outside, no need to call it here */ break; } + case SQLCOM_DROP_EVENT: case SQLCOM_SHOW_CREATE_EVENT: { DBUG_ASSERT(lex->spname); - DBUG_ASSERT(lex->et); if (! lex->spname->m_db.str) { my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR), MYF(0)); - res= true; - break; + goto error; } if (check_access(thd, EVENT_ACL, lex->spname->m_db.str, 0, 0, 0, is_schema_db(lex->spname->m_db.str))) @@ -3950,15 +3921,29 @@ end_with_restore_list: if (lex->spname->m_name.length > NAME_LEN) { my_error(ER_TOO_LONG_IDENT, MYF(0), lex->spname->m_name.str); + /* this jumps to the end of the function and skips own messaging */ goto error; } - res= Events::show_create_event(thd, lex->spname); + + if (lex->sql_command == SQLCOM_SHOW_CREATE_EVENT) + res= Events::get_instance()->show_create_event(thd, lex->spname); + else + { + uint affected= 1; + if (!(res= Events::get_instance()->drop_event(thd, + lex->spname->m_db, + lex->spname->m_name, + lex->drop_if_exists, + &affected, + FALSE))) + send_ok(thd, affected); + } break; } #ifndef DBUG_OFF case SQLCOM_SHOW_SCHEDULER_STATUS: { - res= Events::dump_internal_status(thd); + res= Events::get_instance()->dump_internal_status(thd); break; } #endif @@ -6036,14 +6021,6 @@ void mysql_parse(THD *thd, char *inBuf, uint length) { delete lex->sphead; lex->sphead= NULL; - if (lex->et) - { - lex->et->free_sphead_on_delete= true; - /* alloced on thd->mem_root so no real memory free but dtor call */ - lex->et->free_sp(); - lex->et->deinit_mutexes(); - lex->et= NULL; - } } else { @@ -6080,13 +6057,6 @@ void mysql_parse(THD *thd, char *inBuf, uint length) delete lex->sphead; lex->sphead= NULL; } - if (lex->et) - { - lex->et->free_sphead_on_delete= true; - lex->et->free_sp(); - lex->et->deinit_mutexes(); - lex->et= NULL; - } } thd->proc_info="freeing items"; thd->end_statement(); diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 8f8c84c2db5..52bd8da3785 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -27,7 +27,7 @@ #include "authors.h" #include "contributors.h" #include "events.h" -#include "event_timed.h" +#include "event_data_objects.h" #include <my_dir.h> #ifdef WITH_PARTITION_STORAGE_ENGINE @@ -4166,7 +4166,7 @@ extern LEX_STRING interval_type_to_name[]; 1 Error */ -static int +int copy_event_to_schema_table(THD *thd, TABLE *sch_table, TABLE *event_table) { const char *wild= thd->lex->wild ? thd->lex->wild->ptr() : NullS; @@ -4177,7 +4177,7 @@ copy_event_to_schema_table(THD *thd, TABLE *sch_table, TABLE *event_table) restore_record(sch_table, s->default_values); - if (et.load_from_row(thd->mem_root, event_table)) + if (et.load_from_row(event_table)) { my_error(ER_CANNOT_LOAD_FROM_TABLE, MYF(0)); DBUG_RETURN(1); @@ -4301,183 +4301,6 @@ copy_event_to_schema_table(THD *thd, TABLE *sch_table, TABLE *event_table) } -/* - Performs an index scan of event_table (mysql.event) and fills schema_table. - - Synopsis - events_table_index_read_for_db() - thd Thread - schema_table The I_S.EVENTS table - event_table The event table to use for loading (mysql.event) - - Returns - 0 OK - 1 Error -*/ - -static -int events_table_index_read_for_db(THD *thd, TABLE *schema_table, - TABLE *event_table) -{ - int ret=0; - CHARSET_INFO *scs= system_charset_info; - KEY *key_info; - uint key_len; - byte *key_buf= NULL; - LINT_INIT(key_buf); - - DBUG_ENTER("schema_events_do_index_scan"); - - DBUG_PRINT("info", ("Using prefix scanning on PK")); - event_table->file->ha_index_init(0, 1); - event_table->field[Events::FIELD_DB]-> - store(thd->lex->select_lex.db, strlen(thd->lex->select_lex.db), scs); - key_info= event_table->key_info; - key_len= key_info->key_part[0].store_length; - - if (!(key_buf= (byte *)alloc_root(thd->mem_root, key_len))) - { - ret= 1; - /* don't send error, it would be done by sql_alloc_error_handler() */ - } - else - { - key_copy(key_buf, event_table->record[0], key_info, key_len); - if (!(ret= event_table->file->index_read(event_table->record[0], key_buf, - key_len, HA_READ_PREFIX))) - { - DBUG_PRINT("info",("Found rows. Let's retrieve them. ret=%d", ret)); - do - { - ret= copy_event_to_schema_table(thd, schema_table, event_table); - if (ret == 0) - ret= event_table->file->index_next_same(event_table->record[0], - key_buf, key_len); - } while (ret == 0); - } - DBUG_PRINT("info", ("Scan finished. ret=%d", ret)); - } - event_table->file->ha_index_end(); - /* ret is guaranteed to be != 0 */ - if (ret == HA_ERR_END_OF_FILE || ret == HA_ERR_KEY_NOT_FOUND) - DBUG_RETURN(0); - DBUG_RETURN(1); -} - - -/* - Performs a table scan of event_table (mysql.event) and fills schema_table. - - Synopsis - events_table_scan_all() - thd Thread - schema_table The I_S.EVENTS in memory table - event_table The event table to use for loading. - - Returns - 0 OK - 1 Error -*/ - -static -int events_table_scan_all(THD *thd, TABLE *schema_table, - TABLE *event_table) -{ - int ret; - READ_RECORD read_record_info; - - DBUG_ENTER("schema_events_do_table_scan"); - init_read_record(&read_record_info, thd, event_table, NULL, 1, 0); - - /* - rr_sequential, in read_record(), returns 137==HA_ERR_END_OF_FILE, - but rr_handle_error returns -1 for that reason. Thus, read_record() - returns -1 eventually. - */ - do - { - ret= read_record_info.read_record(&read_record_info); - if (ret == 0) - ret= copy_event_to_schema_table(thd, schema_table, event_table); - } - while (ret == 0); - - DBUG_PRINT("info", ("Scan finished. ret=%d", ret)); - end_read_record(&read_record_info); - - /* ret is guaranteed to be != 0 */ - DBUG_RETURN(ret == -1? 0:1); -} - - -/* - Fills I_S.EVENTS with data loaded from mysql.event. Also used by - SHOW EVENTS - - Synopsis - fill_schema_events() - thd Thread - tables The schema table - cond Unused - - Returns - 0 OK - 1 Error -*/ - -int fill_schema_events(THD *thd, TABLE_LIST *tables, COND * /* cond */) -{ - TABLE *schema_table= tables->table; - TABLE *event_table= NULL; - Open_tables_state backup; - int ret= 0; - - DBUG_ENTER("fill_schema_events"); - /* - If it's SHOW EVENTS then thd->lex->select_lex.db is guaranteed not to - be NULL. Let's do an assert anyway. - */ - if (thd->lex->sql_command == SQLCOM_SHOW_EVENTS) - { - DBUG_ASSERT(thd->lex->select_lex.db); - if (check_access(thd, EVENT_ACL, thd->lex->select_lex.db, 0, 0, 0, - is_schema_db(thd->lex->select_lex.db))) - DBUG_RETURN(1); - } - - DBUG_PRINT("info",("db=%s", thd->lex->select_lex.db? - thd->lex->select_lex.db:"(null)")); - - thd->reset_n_backup_open_tables_state(&backup); - if (Events::open_event_table(thd, TL_READ, &event_table)) - { - sql_print_error("Table mysql.event is damaged."); - thd->restore_backup_open_tables_state(&backup); - DBUG_RETURN(1); - } - - /* - 1. SELECT I_S => use table scan. I_S.EVENTS does not guarantee order - thus we won't order it. OTOH, SHOW EVENTS will be - ordered. - 2. SHOW EVENTS => PRIMARY KEY with prefix scanning on (db) - Reasoning: Events are per schema, therefore a scan over an index - will save use from doing a table scan and comparing - every single row's `db` with the schema which we show. - */ - if (thd->lex->sql_command == SQLCOM_SHOW_EVENTS) - ret= events_table_index_read_for_db(thd, schema_table, event_table); - else - ret= events_table_scan_all(thd, schema_table, event_table); - - close_thread_tables(thd); - thd->restore_backup_open_tables_state(&backup); - - DBUG_PRINT("info", ("Return code=%d", ret)); - DBUG_RETURN(ret); -} - - int fill_open_tables(THD *thd, TABLE_LIST *tables, COND *cond) { DBUG_ENTER("fill_open_tables"); @@ -5574,7 +5397,7 @@ ST_SCHEMA_TABLE schema_tables[]= {"ENGINES", engines_fields_info, create_schema_table, fill_schema_engines, make_old_format, 0, -1, -1, 0}, {"EVENTS", events_fields_info, create_schema_table, - fill_schema_events, make_old_format, 0, -1, -1, 0}, + Events::fill_schema_events, make_old_format, 0, -1, -1, 0}, {"FILES", files_fields_info, create_schema_table, fill_schema_files, 0, 0, -1, -1, 0}, {"KEY_COLUMN_USAGE", key_column_usage_fields_info, create_schema_table, diff --git a/sql/sql_show.h b/sql/sql_show.h index 6fce5e94ca3..681d1232b39 100644 --- a/sql/sql_show.h +++ b/sql/sql_show.h @@ -14,4 +14,6 @@ int store_create_info(THD *thd, TABLE_LIST *table_list, String *packet, HA_CREATE_INFO *create_info_arg); int view_store_create_info(THD *thd, TABLE_LIST *table, String *buff); +int copy_event_to_schema_table(THD *thd, TABLE *sch_table, TABLE *event_table); + #endif /* SQL_SHOW_H */ diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 68aeefc1278..1cf16a051e3 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -38,7 +38,7 @@ #include "sp_pcontext.h" #include "sp_rcontext.h" #include "sp.h" -#include "event_timed.h" +#include "event_data_objects.h" #include <myisam.h> #include <myisammrg.h> @@ -880,7 +880,8 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); sp_c_chistics sp_a_chistics sp_chistic sp_c_chistic xa load_data opt_field_or_var_spec fields_or_vars opt_load_data_set_spec definer view_replace_or_algorithm view_replace view_algorithm_opt - view_algorithm view_or_trigger_or_sp view_or_trigger_or_sp_tail + view_algorithm view_or_trigger_or_sp_or_event + view_or_trigger_or_sp_or_event_tail view_suid view_tail view_list_opt view_list view_select view_check_option trigger_tail sp_tail install uninstall partition_entry binlog_base64_event @@ -1257,29 +1258,43 @@ create: lex->name=$4.str; lex->create_info.options=$3; } - | CREATE EVENT_SYM opt_if_not_exists sp_name + | CREATE + { + Lex->create_view_mode= VIEW_CREATE_NEW; + Lex->create_view_algorithm= VIEW_ALGORITHM_UNDEFINED; + Lex->create_view_suid= TRUE; + } + view_or_trigger_or_sp_or_event + {} + | CREATE USER clear_privileges grant_list + { + Lex->sql_command = SQLCOM_CREATE_USER; + } + | CREATE LOGFILE_SYM GROUP logfile_group_info + { + LEX *lex= Lex; + lex->alter_tablespace_info->ts_cmd_type= CREATE_LOGFILE_GROUP; + } + | CREATE TABLESPACE tablespace_info + { + LEX *lex= Lex; + lex->alter_tablespace_info->ts_cmd_type= CREATE_TABLESPACE; + } + ; + + +event_tail: + EVENT_SYM opt_if_not_exists sp_name /* BE CAREFUL when you add a new rule to update the block where YYTHD->client_capabilities is set back to original value */ { - LEX *lex=Lex; - - if (lex->et) - { - /* - Recursive events are not possible because recursive SPs - are not also possible. lex->sp_head is not stacked. - */ - // ToDo Andrey : Change the error message - my_error(ER_SP_NO_RECURSIVE_CREATE, MYF(0), "EVENT"); - YYABORT; - } - - lex->create_info.options= $3; + Lex->create_info.options= $2; - if (!(lex->et= new(YYTHD->mem_root) Event_timed())) // implicitly calls Event_timed::init() + if (!(Lex->event_parse_data= Event_parse_data::new_instance(YYTHD))) YYABORT; + Lex->event_parse_data->identifier= $3; /* We have to turn of CLIENT_MULTI_QUERIES while parsing a @@ -1289,11 +1304,8 @@ create: $<ulong_num>$= YYTHD->client_capabilities & CLIENT_MULTI_QUERIES; YYTHD->client_capabilities &= (~CLIENT_MULTI_QUERIES); - if (!lex->et_compile_phase) - { - lex->et->init_name(YYTHD, $4); - lex->et->init_definer(YYTHD); - } + /* We need that for disallowing subqueries */ + Lex->sql_command= SQLCOM_CREATE_EVENT; } ON SCHEDULE_SYM ev_schedule_time opt_ev_on_completion @@ -1303,13 +1315,12 @@ create: { /* Restore flag if it was cleared above - $1 - CREATE - $2 - EVENT_SYM - $3 - opt_if_not_exists - $4 - sp_name - $5 - the block above + $1 - EVENT_SYM + $2 - opt_if_not_exists + $3 - sp_name + $4 - the block above */ - YYTHD->client_capabilities |= $<ulong_num>5; + YYTHD->client_capabilities |= $<ulong_num>4; /* sql_command is set here because some rules in ev_sql_stmt @@ -1317,146 +1328,48 @@ create: */ Lex->sql_command= SQLCOM_CREATE_EVENT; } - | CREATE - { - Lex->create_view_mode= VIEW_CREATE_NEW; - Lex->create_view_algorithm= VIEW_ALGORITHM_UNDEFINED; - Lex->create_view_suid= TRUE; - } - view_or_trigger_or_sp - {} - | CREATE USER clear_privileges grant_list - { - Lex->sql_command = SQLCOM_CREATE_USER; - } - | CREATE LOGFILE_SYM GROUP logfile_group_info - { - LEX *lex= Lex; - lex->alter_tablespace_info->ts_cmd_type= CREATE_LOGFILE_GROUP; - } - | CREATE TABLESPACE tablespace_info - { - LEX *lex= Lex; - lex->alter_tablespace_info->ts_cmd_type= CREATE_TABLESPACE; - } - ; ev_schedule_time: EVERY_SYM expr interval { - LEX *lex=Lex; - if (!lex->et_compile_phase) - { - switch (lex->et->init_interval(YYTHD , $2, $3)) { - case EVEX_PARSE_ERROR: - yyerror(ER(ER_SYNTAX_ERROR)); - YYABORT; - break; - case EVEX_BAD_PARAMS: - my_error(ER_EVENT_INTERVAL_NOT_POSITIVE_OR_TOO_BIG, MYF(0)); - case EVEX_MICROSECOND_UNSUP: - my_error(ER_NOT_SUPPORTED_YET, MYF(0), "MICROSECOND"); - YYABORT; - break; - } - } + Lex->event_parse_data->item_expression= $2; + Lex->event_parse_data->interval= $3; } ev_starts ev_ends | AT_SYM expr { - LEX *lex=Lex; - if (!lex->et_compile_phase) - { - switch (lex->et->init_execute_at(YYTHD, $2)) { - case EVEX_PARSE_ERROR: - yyerror(ER(ER_SYNTAX_ERROR)); - YYABORT; - break; - case ER_WRONG_VALUE: - { - char buff[120]; - String str(buff,(uint32) sizeof(buff), system_charset_info); - String *str2= $2->val_str(&str); - my_error(ER_WRONG_VALUE, MYF(0), "AT", - str2? str2->c_ptr_safe():"NULL"); - YYABORT; - break; - } - case EVEX_BAD_PARAMS: - my_error(ER_EVENT_EXEC_TIME_IN_THE_PAST, MYF(0)); - YYABORT; - break; - } - } + Lex->event_parse_data->item_execute_at= $2; } ; opt_ev_status: /* empty */ { $$= 0; } | ENABLE_SYM { - LEX *lex=Lex; - if (!lex->et_compile_phase) - lex->et->status= Event_timed::ENABLED; + Lex->event_parse_data->status= Event_parse_data::ENABLED; $$= 1; } | DISABLE_SYM { - LEX *lex=Lex; - - if (!lex->et_compile_phase) - lex->et->status= Event_timed::DISABLED; + Lex->event_parse_data->status= Event_parse_data::DISABLED; $$= 1; } ; ev_starts: /* empty */ { - Lex->et->init_starts(YYTHD, new Item_func_now_local()); + Lex->event_parse_data->item_starts= new Item_func_now_local(); } | STARTS_SYM expr { - LEX *lex= Lex; - if (!lex->et_compile_phase) - { - - switch (lex->et->init_starts(YYTHD, $2)) { - case EVEX_PARSE_ERROR: - yyerror(ER(ER_SYNTAX_ERROR)); - YYABORT; - break; - case EVEX_BAD_PARAMS: - { - char buff[20]; - String str(buff,(uint32) sizeof(buff), system_charset_info); - String *str2= $2->val_str(&str); - my_error(ER_WRONG_VALUE, MYF(0), "STARTS", - str2 ? str2->c_ptr_safe() : NULL); - YYABORT; - break; - } - } - } + Lex->event_parse_data->item_starts= $2; } ; ev_ends: /* empty */ | ENDS_SYM expr { - LEX *lex= Lex; - if (!lex->et_compile_phase) - { - switch (lex->et->init_ends(YYTHD, $2)) { - case EVEX_PARSE_ERROR: - yyerror(ER(ER_SYNTAX_ERROR)); - YYABORT; - break; - case EVEX_BAD_PARAMS: - my_error(ER_EVENT_ENDS_BEFORE_STARTS, MYF(0)); - YYABORT; - break; - } - } + Lex->event_parse_data->item_ends= $2; } ; @@ -1467,16 +1380,14 @@ opt_ev_on_completion: /* empty */ { $$= 0; } ev_on_completion: ON COMPLETION_SYM PRESERVE_SYM { - LEX *lex=Lex; - if (!lex->et_compile_phase) - lex->et->on_completion= Event_timed::ON_COMPLETION_PRESERVE; + Lex->event_parse_data->on_completion= + Event_parse_data::ON_COMPLETION_PRESERVE; $$= 1; } | ON COMPLETION_SYM NOT_SYM PRESERVE_SYM { - LEX *lex=Lex; - if (!lex->et_compile_phase) - lex->et->on_completion= Event_timed::ON_COMPLETION_DROP; + Lex->event_parse_data->on_completion= + Event_parse_data::ON_COMPLETION_DROP; $$= 1; } ; @@ -1484,64 +1395,64 @@ ev_on_completion: opt_ev_comment: /* empty */ { $$= 0; } | COMMENT_SYM TEXT_STRING_sys { - LEX *lex= Lex; - if (!lex->et_compile_phase) - { - lex->comment= $2; - lex->et->init_comment(YYTHD, &$2); - } - $$= 1; + Lex->comment= Lex->event_parse_data->comment= $2; } ; ev_sql_stmt: { LEX *lex= Lex; - sp_head *sp; - - $<sphead>$= lex->sphead; - if (!lex->sphead) + /* + This stops the following : + - CREATE EVENT ... DO CREATE EVENT ...; + - ALTER EVENT ... DO CREATE EVENT ...; + - CREATE EVENT ... DO ALTER EVENT DO ....; + - CREATE PROCEDURE ... BEGIN CREATE EVENT ... END| + This allows: + - CREATE EVENT ... DO DROP EVENT yyy; + - CREATE EVENT ... DO ALTER EVENT yyy; + (the nested ALTER EVENT can have anything but DO clause) + - ALTER EVENT ... DO ALTER EVENT yyy; + (the nested ALTER EVENT can have anything but DO clause) + - ALTER EVENT ... DO DROP EVENT yyy; + - CREATE PROCEDURE ... BEGIN ALTER EVENT ... END| + (the nested ALTER EVENT can have anything but DO clause) + - CREATE PROCEDURE ... BEGIN DROP EVENT ... END| + */ + if (lex->sphead) { - if (!(sp= new sp_head())) - YYABORT; - - sp->reset_thd_mem_root(YYTHD); - sp->init(lex); + my_error(ER_EVENT_RECURSIVITY_FORBIDDEN, MYF(0)); + YYABORT; + } + + if (!(lex->sphead= new sp_head())) + YYABORT; - sp->m_type= TYPE_ENUM_PROCEDURE; + lex->sphead->reset_thd_mem_root(YYTHD); + lex->sphead->init(lex); - lex->sphead= sp; + lex->sphead->m_type= TYPE_ENUM_PROCEDURE; - bzero((char *)&lex->sp_chistics, sizeof(st_sp_chistics)); - lex->sphead->m_chistics= &lex->sp_chistics; + bzero((char *)&lex->sp_chistics, sizeof(st_sp_chistics)); + lex->sphead->m_chistics= &lex->sp_chistics; - lex->sphead->m_body_begin= lex->ptr; - } + lex->sphead->m_body_begin= lex->ptr; + + Lex->event_parse_data->body_begin= lex->ptr; - if (!lex->et_compile_phase) - lex->et->body_begin= lex->ptr; } ev_sql_stmt_inner { LEX *lex=Lex; - if (!$<sphead>1) - { - sp_head *sp= lex->sphead; - // return back to the original memory root ASAP - sp->init_strings(YYTHD, lex, NULL); - sp->restore_thd_mem_root(YYTHD); + // return back to the original memory root ASAP + lex->sphead->init_strings(YYTHD, lex, NULL); + lex->sphead->restore_thd_mem_root(YYTHD); - lex->sp_chistics.suid= SP_IS_SUID;//always the definer! + lex->sp_chistics.suid= SP_IS_SUID;//always the definer! - lex->et->sphead= lex->sphead; - lex->sphead= NULL; - } - if (!lex->et_compile_phase) - { - lex->et->init_body(YYTHD); - } + Lex->event_parse_data->init_body(YYTHD); } ; @@ -4740,37 +4651,22 @@ alter: YYTHD->client_capabilities is set back to original value */ { - LEX *lex=Lex; - Event_timed *et; + Lex->spname= NULL; - if (lex->et) - { - /* - Recursive events are not possible because recursive SPs - are not also possible. lex->sp_head is not stacked. - */ - my_error(ER_SP_NO_RECURSIVE_CREATE, MYF(0), "EVENT"); + if (!(Lex->event_parse_data= Event_parse_data::new_instance(YYTHD))) YYABORT; - } - lex->spname= 0;//defensive programming - - if (!(et= new (YYTHD->mem_root) Event_timed()))// implicitly calls Event_timed::init() - YYABORT; - lex->et = et; - - if (!lex->et_compile_phase) - { - et->init_definer(YYTHD); - et->init_name(YYTHD, $3); - } + Lex->event_parse_data->identifier= $3; /* - We have to turn of CLIENT_MULTI_QUERIES while parsing a - stored procedure, otherwise yylex will chop it into pieces - at each ';'. + We have to turn of CLIENT_MULTI_QUERIES while parsing a + stored procedure, otherwise yylex will chop it into pieces + at each ';'. */ $<ulong_num>$= YYTHD->client_capabilities & CLIENT_MULTI_QUERIES; YYTHD->client_capabilities &= ~CLIENT_MULTI_QUERIES; + + /* we need that for disallowing subqueries */ + Lex->sql_command= SQLCOM_ALTER_EVENT; } ev_alter_on_schedule_completion opt_ev_rename_to @@ -4786,15 +4682,15 @@ alter: */ YYTHD->client_capabilities |= $<ulong_num>4; - /* - sql_command is set here because some rules in ev_sql_stmt - can overwrite it - */ if (!($5 || $6 || $7 || $8 || $9)) { yyerror(ER(ER_SYNTAX_ERROR)); YYABORT; } + /* + sql_command is set here because some rules in ev_sql_stmt + can overwrite it + */ Lex->sql_command= SQLCOM_ALTER_EVENT; } | ALTER TABLESPACE alter_tablespace_info @@ -4830,7 +4726,7 @@ opt_ev_rename_to: /* empty */ { $$= 0;} { LEX *lex=Lex; lex->spname= $3; //use lex's spname to hold the new name - //the original name is in the Event_timed object + //the original name is in the Event_parse_data object $$= 1; } ; @@ -7104,8 +7000,10 @@ select_derived2: { LEX *lex= Lex; lex->derived_tables|= DERIVED_SUBQUERY; - if (lex->sql_command == (int)SQLCOM_HA_READ || - lex->sql_command == (int)SQLCOM_KILL) + if (lex->sql_command == SQLCOM_HA_READ || + lex->sql_command == SQLCOM_KILL || + lex->sql_command == SQLCOM_CREATE_EVENT || + lex->sql_command == SQLCOM_ALTER_EVENT) { yyerror(ER(ER_SYNTAX_ERROR)); YYABORT; @@ -7699,29 +7597,9 @@ drop: } | DROP EVENT_SYM if_exists sp_name { - LEX *lex=Lex; - - if (lex->et) - { - /* - Recursive events are not possible because recursive SPs - are not also possible. lex->sp_head is not stacked. - */ - my_error(ER_SP_NO_RECURSIVE_CREATE, MYF(0), "EVENT"); - YYABORT; - } - - if (!(lex->et= new (YYTHD->mem_root) Event_timed())) - YYABORT; - - if (!lex->et_compile_phase) - { - lex->et->init_name(YYTHD, $4); - lex->et->init_definer(YYTHD); - } - - lex->sql_command = SQLCOM_DROP_EVENT; - lex->drop_if_exists= $3; + Lex->drop_if_exists= $3; + Lex->spname= $4; + Lex->sql_command = SQLCOM_DROP_EVENT; } | DROP TRIGGER_SYM sp_name { @@ -8451,12 +8329,8 @@ show_param: } | CREATE EVENT_SYM sp_name { - Lex->sql_command = SQLCOM_SHOW_CREATE_EVENT; Lex->spname= $3; - Lex->et= new (YYTHD->mem_root) Event_timed(); - if (!Lex->et) - YYABORT; - Lex->et->init_definer(YYTHD); + Lex->sql_command = SQLCOM_SHOW_CREATE_EVENT; } ; @@ -10763,8 +10637,10 @@ subselect_start: '(' SELECT_SYM { LEX *lex=Lex; - if (lex->sql_command == (int)SQLCOM_HA_READ || - lex->sql_command == (int)SQLCOM_KILL) + if (lex->sql_command == SQLCOM_HA_READ || + lex->sql_command == SQLCOM_KILL || + lex->sql_command == SQLCOM_CREATE_EVENT || + lex->sql_command == SQLCOM_ALTER_EVENT) { yyerror(ER(ER_SYNTAX_ERROR)); YYABORT; @@ -10788,20 +10664,22 @@ subselect_end: **************************************************************************/ -view_or_trigger_or_sp: - definer view_or_trigger_or_sp_tail +view_or_trigger_or_sp_or_event: + definer view_or_trigger_or_sp_or_event_tail {} | view_replace_or_algorithm definer view_tail {} ; -view_or_trigger_or_sp_tail: +view_or_trigger_or_sp_or_event_tail: view_tail {} | trigger_tail {} | sp_tail {} + | event_tail + {} ; /************************************************************************** |