summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xBUILD/SETUP.sh2
-rw-r--r--include/my_sys.h1
-rw-r--r--include/queues.h6
-rw-r--r--libmysqld/Makefile.am4
-rw-r--r--libmysqld/event.h210
-rw-r--r--mysql-test/lib/init_db.sql33
-rw-r--r--mysql-test/t/events.test19
-rw-r--r--mysys/array.c25
-rw-r--r--mysys/queues.c67
-rw-r--r--scripts/mysql_fix_privilege_tables.sql38
-rw-r--r--sql/Makefile.am3
-rw-r--r--sql/event.cc909
-rw-r--r--sql/event.h218
-rw-r--r--sql/event_executor.cc588
-rw-r--r--sql/event_priv.h98
-rw-r--r--sql/event_timed.cc970
-rw-r--r--sql/lex.h8
-rw-r--r--sql/mysqld.cc13
-rw-r--r--sql/set_var.cc5
-rw-r--r--sql/set_var.h11
-rw-r--r--sql/share/errmsg.txt30
-rw-r--r--sql/sp_head.h1
-rw-r--r--sql/sql_acl.cc12
-rw-r--r--sql/sql_acl.h5
-rw-r--r--sql/sql_lex.cc4
-rw-r--r--sql/sql_lex.h7
-rw-r--r--sql/sql_parse.cc68
-rw-r--r--sql/sql_show.cc1
-rw-r--r--sql/sql_yacc.yy456
-rw-r--r--sql/table.cc3
-rw-r--r--sql/tztime.cc12
-rw-r--r--sql/tztime.h1
32 files changed, 3784 insertions, 44 deletions
diff --git a/BUILD/SETUP.sh b/BUILD/SETUP.sh
index eece41d72e6..7881459d086 100755
--- a/BUILD/SETUP.sh
+++ b/BUILD/SETUP.sh
@@ -79,7 +79,7 @@ fast_cflags="-O3 -fno-omit-frame-pointer"
reckless_cflags="-O3 -fomit-frame-pointer "
debug_cflags="-DUNIV_MUST_NOT_INLINE -DEXTRA_DEBUG -DFORCE_INIT_OF_VARS -DSAFEMALLOC -DPEDANTIC_SAFEMALLOC -DSAFE_MUTEX"
-debug_extra_cflags="-O1 -Wuninitialized"
+debug_extra_cflags="-O0"
base_cxxflags="-felide-constructors -fno-exceptions -fno-rtti"
amd64_cxxflags="" # If dropping '--with-big-tables', add here "-DBIG_TABLES"
diff --git a/include/my_sys.h b/include/my_sys.h
index 44fe383bf4f..a814b7f66f6 100644
--- a/include/my_sys.h
+++ b/include/my_sys.h
@@ -751,6 +751,7 @@ extern void get_dynamic(DYNAMIC_ARRAY *array,gptr element,uint array_index);
extern void delete_dynamic(DYNAMIC_ARRAY *array);
extern void delete_dynamic_element(DYNAMIC_ARRAY *array, uint array_index);
extern void freeze_size(DYNAMIC_ARRAY *array);
+extern int get_index_dynamic(DYNAMIC_ARRAY *array, gptr element);
#define dynamic_array_ptr(array,array_index) ((array)->buffer+(array_index)*(array)->size_of_element)
#define dynamic_element(array,array_index,type) ((type)((array)->buffer) +(array_index))
#define push_dynamic(A,B) insert_dynamic(A,B)
diff --git a/include/queues.h b/include/queues.h
index a8b676b763c..8cb053831f2 100644
--- a/include/queues.h
+++ b/include/queues.h
@@ -35,6 +35,7 @@ typedef struct st_queue {
uint offset_to_key; /* compare is done on element+offset */
int max_at_top; /* Set if queue_top gives max */
int (*compare)(void *, byte *,byte *);
+ uint auto_extent;
} QUEUE;
#define queue_top(queue) ((queue)->root[1])
@@ -49,14 +50,19 @@ typedef int (*queue_compare)(void *,byte *, byte *);
int init_queue(QUEUE *queue,uint max_elements,uint offset_to_key,
pbool max_at_top, queue_compare compare,
void *first_cmp_arg);
+int init_queue_ex(QUEUE *queue,uint max_elements,uint offset_to_key,
+ pbool max_at_top, queue_compare compare,
+ void *first_cmp_arg, uint auto_extent);
int reinit_queue(QUEUE *queue,uint max_elements,uint offset_to_key,
pbool max_at_top, queue_compare compare,
void *first_cmp_arg);
int resize_queue(QUEUE *queue, uint max_elements);
void delete_queue(QUEUE *queue);
void queue_insert(QUEUE *queue,byte *element);
+int queue_insert_safe(QUEUE *queue, byte *element);
byte *queue_remove(QUEUE *queue,uint idx);
#define queue_remove_all(queue) { (queue)->elements= 0; }
+#define queue_is_full(queue) (queue->elements == queue->max_elements)
void _downheap(QUEUE *queue,uint idx);
void queue_fix(QUEUE *queue);
#define is_queue_inited(queue) ((queue)->root != 0)
diff --git a/libmysqld/Makefile.am b/libmysqld/Makefile.am
index da0418eaf9c..4eae790c9db 100644
--- a/libmysqld/Makefile.am
+++ b/libmysqld/Makefile.am
@@ -62,8 +62,8 @@ sqlsources = derror.cc field.cc field_conv.cc strfunc.cc filesort.cc \
unireg.cc uniques.cc stacktrace.c sql_union.cc hash_filo.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 \
- rpl_filter.cc sql_partition.cc handlerton.cc sql_plugin.cc
+ parse_file.cc sql_view.cc sql_trigger.cc my_decimal.cc event.cc \
+ rpl_filter.cc sql_partition.cc handlerton.cc sql_plugin.cc
libmysqld_int_a_SOURCES= $(libmysqld_sources) $(libmysqlsources) $(sqlsources)
EXTRA_libmysqld_a_SOURCES = ha_innodb.cc ha_berkeley.cc ha_archive.cc \
diff --git a/libmysqld/event.h b/libmysqld/event.h
new file mode 100644
index 00000000000..5ba96e401ce
--- /dev/null
+++ b/libmysqld/event.h
@@ -0,0 +1,210 @@
+/* -*- C++ -*- */
+#ifndef _EVENT_H_
+#define _EVENT_H_
+#include "sp_head.h"
+
+
+extern ulong opt_event_executor;
+
+#define EVEX_OK 0
+#define EVEX_KEY_NOT_FOUND -1
+#define EVEX_OPEN_TABLE_FAILED -2
+#define EVEX_WRITE_ROW_FAILED -3
+#define EVEX_DELETE_ROW_FAILED -4
+#define EVEX_GET_FIELD_FAILED -5
+#define EVEX_PARSE_ERROR -6
+#define EVEX_INTERNAL_ERROR -7
+#define EVEX_NO_DB_ERROR -8
+#define EVEX_GENERAL_ERROR -9
+#define EVEX_BAD_PARAMS -10
+#define EVEX_NOT_RUNNING -11
+
+#define EVENT_EXEC_NO_MORE (1L << 0)
+#define EVENT_NOT_USED (1L << 1)
+
+
+enum enum_event_on_completion
+{
+ MYSQL_EVENT_ON_COMPLETION_DROP = 1,
+ MYSQL_EVENT_ON_COMPLETION_PRESERVE
+};
+
+enum enum_event_status
+{
+ MYSQL_EVENT_ENABLED = 1,
+ MYSQL_EVENT_DISABLED
+};
+
+
+class event_timed
+{
+ event_timed(const event_timed &); /* Prevent use of these */
+ void operator=(event_timed &);
+
+public:
+ LEX_STRING m_db;
+ LEX_STRING m_name;
+ LEX_STRING m_qname; // db.name
+ LEX_STRING m_body;
+
+ LEX_STRING m_definer_user;
+ LEX_STRING m_definer_host;
+ LEX_STRING m_definer;// combination of user and host
+
+ LEX_STRING m_comment;
+ TIME m_starts;
+ TIME m_ends;
+ TIME m_execute_at;
+ longlong m_expr;
+ interval_type m_interval;
+ longlong m_created;
+ longlong m_modified;
+ TIME m_last_executed;
+ enum enum_event_on_completion m_on_completion;
+ enum enum_event_status m_status;
+ sp_head *m_sphead;
+
+
+
+ uint m_old_cmq; // Old CLIENT_MULTI_QUERIES value
+ const uchar *m_body_begin;
+
+ bool m_dropped;
+ bool m_free_sphead_on_delete;
+ uint m_flags;//all kind of purposes
+ bool m_last_executed_changed;
+ bool m_status_changed;
+
+ event_timed():m_expr(0), m_created(0), m_modified(0),
+ m_on_completion(MYSQL_EVENT_ON_COMPLETION_DROP),
+ m_status(MYSQL_EVENT_ENABLED), m_sphead(0), m_dropped(false),
+ m_free_sphead_on_delete(true), m_flags(0),
+ m_last_executed_changed(false), m_status_changed(false)
+ { init(); }
+
+ ~event_timed()
+ {
+ if (m_free_sphead_on_delete)
+ free_sp();
+ }
+
+ void
+ init();
+
+ int
+ init_definer(THD *thd);
+
+ int
+ init_execute_at(THD *thd, Item *expr);
+
+ int
+ init_interval(THD *thd, Item *expr, interval_type interval);
+
+ void
+ init_name(THD *thd, sp_name *name);
+
+ int
+ init_starts(THD *thd, Item *starts);
+
+ int
+ init_ends(THD *thd, Item *ends);
+
+ void
+ event_timed::init_body(THD *thd);
+
+ void
+ init_comment(THD *thd, LEX_STRING *comment);
+
+ void
+ set_on_completion_drop(bool drop);
+
+ void
+ set_event_status(bool enabled);
+
+ int
+ load_from_row(MEM_ROOT *mem_root, TABLE *table);
+
+ bool
+ compute_next_execution_time();
+
+ void
+ mark_last_executed();
+
+ bool
+ drop(THD *thd);
+
+ bool
+ update_fields(THD *thd);
+
+ char *
+ get_show_create_event(THD *thd, uint *length);
+
+ int
+ execute(THD *thd, MEM_ROOT *mem_root);
+
+ int
+ compile(THD *thd, MEM_ROOT *mem_root);
+
+ void free_sp()
+ {
+ if (m_sphead)
+ {
+ delete m_sphead;
+ m_sphead= 0;
+ }
+ }
+};
+
+
+int
+evex_create_event(THD *thd, event_timed *et, uint create_options);
+
+int
+evex_update_event(THD *thd, sp_name *name, event_timed *et);
+
+int
+evex_drop_event(THD *thd, event_timed *et, bool drop_if_exists);
+
+
+int
+init_events();
+
+void
+shutdown_events();
+
+/*
+typedef struct st_event_item {
+ my_time_t execute_at;
+ sp_head *proc;
+ char *definer_user;
+ char *definer_host;
+} EVENT_ITEM;
+*/
+
+
+/*
+CREATE TABLE `event` (
+ `db` varchar(64) character set latin1 collate latin1_bin NOT NULL default '',
+ `name` varchar(64) NOT NULL default '',
+ `body` blob NOT NULL,
+ `definer` varchar(77) character set latin1 collate latin1_bin NOT NULL default '',
+ `execute_at` datetime default NULL,
+ `transient_expression` int(11) default NULL,
+ `interval_type` 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') DEFAULT NULL,
+ `created` timestamp NOT NULL default '0000-00-00 00:00:00',
+ `modified` timestamp NOT NULL default '0000-00-00 00:00:00',
+ `last_executed` datetime default NULL,
+ `starts` datetime default NULL,
+ `ends` datetime default NULL,
+ `status` enum('ENABLED','DISABLED') NOT NULL default 'ENABLED',
+ `on_completion` enum('DROP','PRESERVE') NOT NULL default 'DROP',
+ `comment` varchar(64) character set latin1 collate latin1_bin NOT NULL default '',
+ PRIMARY KEY (`db`,`name`)
+) ENGINE=MyISAM DEFAULT CHARSET=latin1
+*/
+
+#endif /* _EVENT_H_ */
diff --git a/mysql-test/lib/init_db.sql b/mysql-test/lib/init_db.sql
index 85faf7869d4..4ba7184d551 100644
--- a/mysql-test/lib/init_db.sql
+++ b/mysql-test/lib/init_db.sql
@@ -89,6 +89,7 @@ CREATE TABLE user (
Create_routine_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL,
Alter_routine_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL,
Create_user_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL,
+ Event_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL,
ssl_type enum('','ANY','X509', 'SPECIFIED') COLLATE utf8_general_ci DEFAULT '' NOT NULL,
ssl_cipher BLOB NOT NULL,
x509_issuer BLOB NOT NULL,
@@ -103,9 +104,9 @@ CHARACTER SET utf8 COLLATE utf8_bin
comment='Users and global privileges';
-INSERT INTO user VALUES ('localhost' ,'root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0);
-INSERT INTO user VALUES ('@HOSTNAME@%' ,'root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0);
-REPLACE INTO user VALUES ('127.0.0.1' ,'root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0);
+INSERT INTO user VALUES ('localhost' ,'root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0);
+INSERT INTO user VALUES ('@HOSTNAME@%' ,'root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0);
+REPLACE INTO user VALUES ('127.0.0.1' ,'root','','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','','','','',0,0,0,0);
INSERT INTO user (host,user) VALUES ('localhost','');
INSERT INTO user (host,user) VALUES ('@HOSTNAME@%','');
@@ -566,3 +567,29 @@ CREATE TABLE proc (
comment char(64) collate utf8_bin DEFAULT '' NOT NULL,
PRIMARY KEY (db,name,type)
) character set utf8 comment='Stored Procedures';
+
+
+CREATE TABLE event (
+ 'db' VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL default '',
+ 'name' VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL default '',
+ 'body' longblob NOT NULL,
+ 'definer' VARCHAR(77) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL default '',
+ 'execute_at' DATETIME default NULL,
+ 'transient_expression' int(11) default NULL,
+ 'interval_type' 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') default NULL,
+ 'created' TIMESTAMP NOT NULL default '0000-00-00 00:00:00',
+ 'modified' TIMESTAMP NOT NULL default '0000-00-00 00:00:00',
+ 'last_executed' DATETIME default NULL,
+ 'starts' DATETIME default NULL,
+ 'ends' DATETIME default NULL,
+ 'status' ENUM('ENABLED','DISABLED') NOT NULL default 'ENABLED',
+ 'on_completion' ENUM('DROP','PRESERVE') NOT NULL default 'DROP',
+ 'comment' varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL default '',
+ PRIMARY KEY ('db','name')
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT 'Events';
diff --git a/mysql-test/t/events.test b/mysql-test/t/events.test
new file mode 100644
index 00000000000..9285b38dc6b
--- /dev/null
+++ b/mysql-test/t/events.test
@@ -0,0 +1,19 @@
+create database events_test;
+use events_test;
+drop event if exists event1;
+create event event1 on schedule every 15 minute starts now() ends date_add(now(), interval 5 hour) DO begin end;
+alter event event1 rename to event2;
+alter event event2 disable;
+
+drop event event2;
+create event event2 on schedule every 2 second starts now() ends date_add(now(), interval 5 hour) comment "some" DO begin end;
+drop event event2;
+
+create table t_event3 (a int, b float);
+drop event if exists event3;
+create event event3 on schedule every 50 + 10 minute starts date_add("20010101", interval 5 minute) ends date_add("20151010", interval 5 day) comment "portokala_comment" DO insert into t_event3 values (unix_timestamp(), rand());
+set max_allowed_packet=128000000;
+select sha1(space(9999999));
+select count(*) from t_event3;
+drop event event3;
+drop database events_test;
diff --git a/mysys/array.c b/mysys/array.c
index 6d00585f24d..a50d8b78178 100644
--- a/mysys/array.c
+++ b/mysys/array.c
@@ -278,3 +278,28 @@ void freeze_size(DYNAMIC_ARRAY *array)
array->max_element=elements;
}
}
+
+
+/*
+ Get the index of a dynamic element
+
+ SYNOPSIS
+ get_index_dynamic()
+ array Array
+ element Whose element index
+
+*/
+
+int get_index_dynamic(DYNAMIC_ARRAY *array, gptr element)
+{
+ uint ret;
+ if (array->buffer > element)
+ return -1;
+
+ ret= (element - array->buffer) / array->size_of_element;
+ if (ret > array->elements)
+ return -1;
+
+ return ret;
+
+}
diff --git a/mysys/queues.c b/mysys/queues.c
index 0e4e251f7e7..8e572a0f195 100644
--- a/mysys/queues.c
+++ b/mysys/queues.c
@@ -19,7 +19,7 @@
Implemention of queues from "Algoritms in C" by Robert Sedgewick.
An optimisation of _downheap suggested in Exercise 7.51 in "Data
Structures & Algorithms in C++" by Mark Allen Weiss, Second Edition
- was implemented by Mikael Ronström 2005. Also the O(N) algorithm
+ was implemented by Mikael Ronstrom 2005. Also the O(N) algorithm
of queue_fix was implemented.
*/
@@ -67,6 +67,46 @@ int init_queue(QUEUE *queue, uint max_elements, uint offset_to_key,
}
+
+/*
+ Init queue, uses init_queue internally for init work but also accepts
+ auto_extent as parameter
+
+ SYNOPSIS
+ init_queue_ex()
+ queue Queue to initialise
+ max_elements Max elements that will be put in queue
+ offset_to_key Offset to key in element stored in queue
+ Used when sending pointers to compare function
+ max_at_top Set to 1 if you want biggest element on top.
+ compare Compare function for elements, takes 3 arguments.
+ first_cmp_arg First argument to compare function
+ auto_extent When the queue is full and there is insert operation
+ extend the queue.
+
+ NOTES
+ Will allocate max_element pointers for queue array
+
+ RETURN
+ 0 ok
+ 1 Could not allocate memory
+*/
+
+int init_queue_ex(QUEUE *queue, uint max_elements, uint offset_to_key,
+ pbool max_at_top, int (*compare) (void *, byte *, byte *),
+ void *first_cmp_arg, uint auto_extent)
+{
+ int ret;
+ DBUG_ENTER("init_queue_ex");
+
+ if ((ret= init_queue(queue, max_elements, offset_to_key, max_at_top, compare,
+ first_cmp_arg)))
+ DBUG_RETURN(ret);
+
+ queue->auto_extent= auto_extent;
+ DBUG_RETURN(0);
+}
+
/*
Reinitialize queue for other usage
@@ -192,6 +232,31 @@ void queue_insert(register QUEUE *queue, byte *element)
}
}
+/*
+ Does safe insert. If no more space left on the queue resize it.
+ Return codes:
+ 0 - OK
+ 1 - Cannot allocate more memory
+ 2 - auto_extend is 0, the operation would
+
+*/
+
+int queue_insert_safe(register QUEUE *queue, byte *element)
+{
+
+ if (queue->elements == queue->max_elements)
+ {
+ if (!queue->auto_extent)
+ return 2;
+ else if (resize_queue(queue, queue->max_elements + queue->auto_extent))
+ return 1;
+ }
+
+ queue_insert(queue, element);
+ return 0;
+}
+
+
/* Remove item from queue */
/* Returns pointer to removed element */
diff --git a/scripts/mysql_fix_privilege_tables.sql b/scripts/mysql_fix_privilege_tables.sql
index f8f10438505..2949049afe8 100644
--- a/scripts/mysql_fix_privilege_tables.sql
+++ b/scripts/mysql_fix_privilege_tables.sql
@@ -526,3 +526,41 @@ ALTER TABLE proc MODIFY db
char(77) collate utf8_bin DEFAULT '' NOT NULL,
MODIFY comment
char(64) collate utf8_bin DEFAULT '' NOT NULL;
+
+#
+# EVENT table
+#
+
+
+CREATE TABLE event (
+ 'db' VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL default '',
+ 'name' VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL default '',
+ 'body' longblob NOT NULL,
+ 'definer' VARCHAR(77) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL default '',
+ 'execute_at' DATETIME default NULL,
+ 'transient_expression' int(11) default NULL,
+ 'interval_type' 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') default NULL,
+ 'created' TIMESTAMP NOT NULL default '0000-00-00 00:00:00',
+ 'modified' TIMESTAMP NOT NULL default '0000-00-00 00:00:00',
+ 'last_executed' DATETIME default NULL,
+ 'starts' DATETIME default NULL,
+ 'ends' DATETIME default NULL,
+ 'status' ENUM('ENABLED','DISABLED') NOT NULL default 'ENABLED',
+ 'on_completion' ENUM('DROP','PRESERVE') NOT NULL default 'DROP',
+ 'comment' varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL default '',
+ PRIMARY KEY ('db','name')
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT 'Events';
+
+
+#
+# EVENT privilege
+#
+
+ALTER TABLE mysql.user add Event_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL AFTER Create_user_priv;
+ALTER TABLE mysql.db add Event_priv enum('N','Y') COLLATE utf8_general_ci DEFAULT 'N' NOT NULL;
diff --git a/sql/Makefile.am b/sql/Makefile.am
index 1090c6d48c7..af08085563d 100644
--- a/sql/Makefile.am
+++ b/sql/Makefile.am
@@ -61,7 +61,7 @@ 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 \
+ sql_array.h sql_cursor.h event.h event_priv.h \
sql_plugin.h authors.h
mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \
item.cc item_sum.cc item_buff.cc item_func.cc \
@@ -95,6 +95,7 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \
tztime.cc my_time.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_executor.cc event.cc event_timed.cc \
sql_plugin.cc\
handlerton.cc
EXTRA_mysqld_SOURCES = ha_innodb.cc ha_berkeley.cc ha_archive.cc \
diff --git a/sql/event.cc b/sql/event.cc
new file mode 100644
index 00000000000..806780e5097
--- /dev/null
+++ b/sql/event.cc
@@ -0,0 +1,909 @@
+/* Copyright (C) 2004-2005 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 "event_priv.h"
+#include "event.h"
+#include "sp.h"
+
+/*
+ TODO list :
+ - The default value of created/modified should not be 0000-00-00 because of
+ STRICT mode restricions.
+
+ - CREATE EVENT should not go into binary log! Does it now? The SQL statements
+ issued by the EVENT are replicated.
+ I have an idea how to solve the problem at failover. So the status field
+ will be ENUM('DISABLED', 'ENABLED', 'SLAVESIDE_DISABLED').
+ In this case when CREATE EVENT is replicated it should go into the binary
+ as SLAVESIDE_DISABLED if it is ENABLED, when it's created as DISABLEd it
+ should be replicated as disabled. If an event is ALTERed as DISABLED the
+ query should go untouched into the binary log, when ALTERed as enable then
+ it should go as SLAVESIDE_DISABLED. This is regarding the SQL interface.
+ TT routines however modify mysql.event internally and this does not go the log
+ so in this case queries has to be injected into the log...somehow... or
+ maybe a solution is RBR for this case, because the event may go only from
+ ENABLED to DISABLED status change and this is safe for replicating. As well
+ an event may be deleted which is also safe for RBR.
+
+ - Maybe move all allocations during parsing to evex_mem_root thus saving
+ double parsing in evex_create_event!
+
+ - If the server is killed (stopping) try to kill executing events?
+
+ - What happens if one renames an event in the DB while it is in memory?
+ Or even deleting it?
+
+ - Consider using conditional variable when doing shutdown instead of
+ waiting till all worker threads end.
+
+ - Make event_timed::get_show_create_event() work
+
+ - Add function documentation whenever needed.
+
+ - Add logging to file
+
+ - Move comparison code to class event_timed
+
+Warning:
+ - For now parallel execution is not possible because the same sp_head cannot be
+ executed few times!!! There is still no lock attached to particular event.
+
+*/
+
+
+QUEUE EVEX_EQ_NAME;
+MEM_ROOT evex_mem_root;
+
+
+void
+evex_queue_init(EVEX_QUEUE_TYPE *queue)
+{
+ if (init_queue_ex(queue, 100 /*num_el*/, 0 /*offset*/,
+ 0 /*smallest_on_top*/, event_timed_compare_q, NULL,
+ 100 /*auto_extent*/))
+ sql_print_error("Insufficient memory to initialize executing queue.");
+}
+
+
+static
+int sortcmp_lex_string(LEX_STRING s, LEX_STRING t, CHARSET_INFO *cs)
+{
+ return cs->coll->strnncollsp(cs,
+ (unsigned char *) s.str,s.length,
+ (unsigned char *) t.str,t.length, 0);
+}
+
+
+int
+my_time_compare(TIME *a, TIME *b)
+{
+/*
+ Or maybe it is faster to use TIME_to_ulonglong_datetime
+ for "a" and "b"
+*/
+
+ DBUG_ENTER("my_time_compare");
+
+ if (a->year > b->year)
+ DBUG_RETURN(1);
+
+ if (a->year < b->year)
+ DBUG_RETURN(-1);
+
+ if (a->month > b->month)
+ DBUG_RETURN(1);
+
+ if (a->month < b->month)
+ DBUG_RETURN(-1);
+
+ if (a->day > b->day)
+ DBUG_RETURN(1);
+
+ if (a->day < b->day)
+ DBUG_RETURN(-1);
+
+ if (a->hour > b->hour)
+ DBUG_RETURN(1);
+
+ if (a->hour < b->hour)
+ DBUG_RETURN(-1);
+
+ if (a->minute > b->minute)
+ DBUG_RETURN(1);
+
+ if (a->minute < b->minute)
+ DBUG_RETURN(-1);
+
+ if (a->second > b->second)
+ DBUG_RETURN(1);
+
+ if (a->second < b->second)
+ DBUG_RETURN(-1);
+
+
+ if (a->second_part > b->second_part)
+ DBUG_RETURN(1);
+
+ if (a->second_part < b->second_part)
+ DBUG_RETURN(-1);
+
+
+ DBUG_RETURN(0);
+}
+
+
+int
+evex_time_diff(TIME *a, TIME *b)
+{
+ my_bool in_gap;
+ DBUG_ENTER("my_time_diff");
+
+ return sec_since_epoch_TIME(a) - sec_since_epoch_TIME(b);
+}
+
+
+inline int
+event_timed_compare(event_timed **a, event_timed **b)
+{
+ my_ulonglong a_t, b_t;
+ a_t= TIME_to_ulonglong_datetime(&(*a)->execute_at)*100L +
+ (*a)->execute_at.second_part;
+ b_t= TIME_to_ulonglong_datetime(&(*b)->execute_at)*100L +
+ (*b)->execute_at.second_part;
+
+ if (a_t > b_t)
+ return 1;
+ else if (a_t < b_t)
+ return -1;
+ else
+ return 0;
+
+}
+
+
+int
+event_timed_compare_q(void *vptr, byte* a, byte *b)
+{
+ return event_timed_compare((event_timed **)&a, (event_timed **)&b);
+}
+
+
+/*
+ Open mysql.event table for read
+
+ SYNOPSIS
+ evex_open_event_table_for_read()
+ thd Thread context
+ lock_type How to lock the table
+ RETURN
+ 0 Error
+ # Pointer to TABLE object
+*/
+
+TABLE *evex_open_event_table(THD *thd, enum thr_lock_type lock_type)
+{
+ TABLE_LIST tables;
+ bool not_used;
+ DBUG_ENTER("open_proc_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(0);
+
+ DBUG_RETURN(tables.table);
+}
+
+
+/*
+ Find row in open mysql.event table representing event
+
+ SYNOPSIS
+ evex_db_find_event_aux()
+ 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_aux(THD *thd, const LEX_STRING dbname,
+ const LEX_STRING ev_name, TABLE *table)
+{
+ byte key[MAX_KEY_LENGTH];
+ DBUG_ENTER("evex_db_find_event_aux");
+ 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[EVEX_FIELD_DB]->field_length ||
+ ev_name.length > table->field[EVEX_FIELD_NAME]->field_length)
+ DBUG_RETURN(EVEX_KEY_NOT_FOUND);
+
+ table->field[0]->store(dbname.str, dbname.length, &my_charset_bin);
+ table->field[1]->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_RETURN(EVEX_KEY_NOT_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
+
+ 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)
+{
+ DBUG_ENTER("evex_fill_row");
+
+ if (table->s->fields != EVEX_FIELD_COUNT)
+ {
+ my_error(ER_EVENT_COL_COUNT_DOESNT_MATCH, MYF(0), "mysql", "event");
+ DBUG_RETURN(EVEX_GET_FIELD_FAILED);
+ }
+
+ DBUG_PRINT("info", ("dbname.len=%d",et->dbname.length));
+ DBUG_PRINT("info", ("name.len=%d",et->name.length));
+
+ table->field[EVEX_FIELD_DB]->
+ store(et->dbname.str, et->dbname.length, system_charset_info);
+ table->field[EVEX_FIELD_NAME]->
+ store(et->name.str, et->name.length, system_charset_info);
+
+ table->field[EVEX_FIELD_ON_COMPLETION]->set_notnull();
+ table->field[EVEX_FIELD_ON_COMPLETION]->store((longlong)et->on_completion);
+
+ table->field[EVEX_FIELD_STATUS]->set_notnull();
+ table->field[EVEX_FIELD_STATUS]->store((longlong)et->status);
+// et->status_changed= false;
+
+ // ToDo: Andrey. How to use users current charset?
+ if (et->body.str)
+ table->field[EVEX_FIELD_BODY]->
+ store(et->body.str, et->body.length, system_charset_info);
+
+ if (et->starts.year)
+ {
+ table->field[EVEX_FIELD_STARTS]->set_notnull();// set NULL flag to OFF
+ table->field[EVEX_FIELD_STARTS]->store_time(&et->starts, MYSQL_TIMESTAMP_DATETIME);
+ }
+
+ if (et->ends.year)
+ {
+ table->field[EVEX_FIELD_ENDS]->set_notnull();
+ table->field[EVEX_FIELD_ENDS]->store_time(&et->ends, MYSQL_TIMESTAMP_DATETIME);
+ }
+
+ if (et->expression)
+ {
+ table->field[EVEX_FIELD_INTERVAL_EXPR]->set_notnull();
+ table->field[EVEX_FIELD_INTERVAL_EXPR]->store((longlong)et->expression);
+
+ table->field[EVEX_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[EVEX_FIELD_TRANSIENT_INTERVAL]->store((longlong)et->interval + 1);
+ }
+ else if (et->execute_at.year)
+ {
+ // fix_fields already called in init_execute_at
+ table->field[EVEX_FIELD_EXECUTE_AT]->set_notnull();
+ table->field[EVEX_FIELD_EXECUTE_AT]->store_time(&et->execute_at,
+ MYSQL_TIMESTAMP_DATETIME);
+
+ //this will make it NULL because we don't call set_notnull
+ table->field[EVEX_FIELD_TRANSIENT_INTERVAL]->store((longlong) 0);
+ }
+ 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[EVEX_FIELD_MODIFIED])->set_time();
+
+ if (et->comment.length)
+ table->field[EVEX_FIELD_COMMENT]->
+ store(et->comment.str, et->comment.length, system_charset_info);
+
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Creates an event in mysql.event
+
+ SYNOPSIS
+ db_create_event()
+ thd THD
+ et event_timed object containing information for the event
+
+ 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".
+*/
+
+static int
+db_create_event(THD *thd, event_timed *et)
+{
+ int ret= EVEX_OK;
+ TABLE *table;
+ char definer[HOSTNAME_LENGTH+USERNAME_LENGTH+2];
+ char olddb[128];
+ bool dbchanged= false;
+ DBUG_ENTER("db_create_event");
+ DBUG_PRINT("enter", ("name: %.*s", et->name.length, et->name.str));
+
+
+ DBUG_PRINT("info", ("open mysql.event for update"));
+ if (!(table= evex_open_event_table(thd, TL_WRITE)))
+ {
+ 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->dbname, et->name, table))
+ {
+ 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.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 (et->name.length > table->field[EVEX_FIELD_NAME]->field_length)
+ {
+ my_error(ER_TOO_LONG_IDENT, MYF(0), et->name.str);
+ goto err;
+ }
+ if (et->body.length > table->field[EVEX_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;
+ }
+
+ strxmov(definer, et->definer_user.str, "@", et->definer_host.str, NullS);
+ if ((ret=table->field[EVEX_FIELD_DEFINER]->
+ store(definer, et->definer_user.length + 1 + et->definer_host.length,
+ system_charset_info)))
+ {
+ my_error(ER_EVENT_STORE_FAILED, MYF(0), et->name.str, ret);
+ goto err;
+ }
+
+ ((Field_timestamp *)table->field[EVEX_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->write_row(table->record[0]))
+ {
+ my_error(ER_EVENT_STORE_FAILED, MYF(0), et->name.str, ret);
+ goto err;
+ }
+
+ if (mysql_bin_log.is_open())
+ {
+ thd->clear_error();
+ /* Such a statement can always go directly to binlog, no trans cache */
+ Query_log_event qinfo(thd, thd->query, thd->query_length, 0, FALSE);
+ mysql_bin_log.write(&qinfo);
+ }
+
+ 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 evex_update_event().
+
+ SYNOPSIS
+ db_update_event()
+ thd THD
+ sp_name the name of the event to alter
+ et event's data
+
+ 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)
+{
+ TABLE *table;
+ int ret= EVEX_OPEN_TABLE_FAILED;
+ DBUG_ENTER("db_update_event");
+ DBUG_PRINT("enter", ("name: %.*s", et->name.length, et->name.str));
+ if (new_name)
+ DBUG_PRINT("enter", ("rename to: %.*s", new_name->m_name.length,
+ new_name->m_name.str));
+
+ if (!(table= evex_open_event_table(thd, TL_WRITE)))
+ {
+ 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, system_charset_info) &&
+ !sortcmp_lex_string(et->dbname, new_name->m_db, system_charset_info))
+ {
+ my_error(ER_EVENT_SAME_NAME, MYF(0), et->name.str);
+ goto err;
+ }
+
+ if (!evex_db_find_event_aux(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 whether 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->dbname, et->name,
+ 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[EVEX_FIELD_DB]->
+ store(new_name->m_db.str, new_name->m_db.length, system_charset_info);
+ table->field[EVEX_FIELD_NAME]->
+ store(new_name->m_name.str, new_name->m_name.length, system_charset_info);
+ }
+
+ if ((ret= table->file->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
+*/
+
+static int
+db_find_event(THD *thd, sp_name *name, event_timed **ett, TABLE *tbl)
+{
+ TABLE *table;
+ int ret;
+ const char *definer;
+ char *ptr;
+ event_timed *et;
+ DBUG_ENTER("db_find_event");
+ DBUG_PRINT("enter", ("name: %*s", name->m_name.length, name->m_name.str));
+
+ if (tbl)
+ table= tbl;
+ else if (!(table= evex_open_event_table(thd, TL_READ)))
+ {
+ my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0));
+ ret= EVEX_GENERAL_ERROR;
+ goto done;
+ }
+
+ if ((ret= evex_db_find_event_aux(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(&evex_mem_root, table)))
+ {
+ my_error(ER_EVENT_CANNOT_LOAD_FROM_TABLE, MYF(0));
+ goto done;
+ }
+
+done:
+ if (ret && et)
+ {
+ 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);
+}
+
+
+/*
+ Looks for a named event in mysql.event and then loads it from
+ the table, compiles it and insert it into the cache.
+
+ SYNOPSIS
+ evex_load_and_compile_event()
+ thd THD
+ spn the name of the event to alter
+ use_lock whether to obtain a lock on LOCK_event_arrays or not
+
+ RETURN VALUE
+ 0 - OK
+ < 0 - error (in this case underlying functions call my_error()).
+
+*/
+
+static int
+evex_load_and_compile_event(THD * thd, sp_name *spn, bool use_lock)
+{
+ int ret= 0;
+ MEM_ROOT *tmp_mem_root;
+ event_timed *ett;
+
+ DBUG_ENTER("db_load_and_compile_event");
+ DBUG_PRINT("enter", ("name: %*s", spn->m_name.length, spn->m_name.str));
+
+ tmp_mem_root= thd->mem_root;
+ thd->mem_root= &evex_mem_root;
+
+ // no need to use my_error() here because db_find_event() has done it
+ if ((ret= db_find_event(thd, spn, &ett, NULL)))
+ goto done;
+
+ /*
+ allocate on evex_mem_root. if you call without evex_mem_root
+ then sphead will not be cleared!
+ */
+ if ((ret= ett->compile(thd, &evex_mem_root)))
+ goto done;
+
+ ett->compute_next_execution_time();
+ if (use_lock)
+ VOID(pthread_mutex_lock(&LOCK_event_arrays));
+
+ evex_queue_insert(&EVEX_EQ_NAME, (EVEX_PTOQEL) ett);
+
+ /*
+ There is a copy in the array which we don't need. sphead won't be
+ destroyed.
+ */
+
+ if (use_lock)
+ VOID(pthread_mutex_unlock(&LOCK_event_arrays));
+
+done:
+ if (thd->mem_root != tmp_mem_root)
+ thd->mem_root= tmp_mem_root;
+
+ DBUG_RETURN(ret);
+}
+
+
+static int
+evex_remove_from_cache(LEX_STRING *db, LEX_STRING *name, bool use_lock)
+{
+ uint i;
+
+ DBUG_ENTER("evex_remove_from_cache");
+ /*
+ It is possible that 2 (or 1) pass(es) won't find the event in memory.
+ The reason is that DISABLED events are not cached.
+ */
+
+ if (use_lock)
+ VOID(pthread_mutex_lock(&LOCK_event_arrays));
+
+ for (i= 0; i < evex_queue_num_elements(EVEX_EQ_NAME); ++i)
+ {
+ event_timed *et= evex_queue_element(&EVEX_EQ_NAME, i, event_timed*);
+ DBUG_PRINT("info", ("[%s.%s]==[%s.%s]?",db->str,name->str, et->dbname.str,
+ et->name.str));
+ if (!sortcmp_lex_string(*name, et->name, system_charset_info) &&
+ !sortcmp_lex_string(*db, et->dbname, system_charset_info))
+ {
+ et->free_sp();
+ delete et;
+ evex_queue_delete_element(&EVEX_EQ_NAME, i);
+ // ok, we have cleaned
+ goto done;
+ }
+ }
+
+done:
+ if (use_lock)
+ VOID(pthread_mutex_unlock(&LOCK_event_arrays));
+
+ DBUG_RETURN(0);
+}
+
+
+
+
+/*
+ The function exported to the world for creating of events.
+
+ SYNOPSIS
+ evex_create_event()
+ thd THD
+ et event's data
+ create_options Options specified when in the query. We are
+ interested whether there is IF NOT EXISTS
+
+ 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.
+*/
+
+int
+evex_create_event(THD *thd, event_timed *et, uint create_options)
+{
+ int ret = 0;
+
+ DBUG_ENTER("evex_create_event");
+ DBUG_PRINT("enter", ("name: %*s options:%d", et->name.length,
+ et->name.str, create_options));
+
+ if ((ret = db_create_event(thd, et)) == EVEX_WRITE_ROW_FAILED &&
+ (create_options & HA_LEX_CREATE_IF_NOT_EXISTS))
+ {
+ push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
+ ER_DB_CREATE_EXISTS, ER(ER_DB_CREATE_EXISTS),
+ "EVENT", et->name.str);
+ ret= 0;
+ goto done;
+ }
+ /*
+ A warning is thrown only when create_options is set to
+ HA_LEX_CREATE_IF_NOT_EXISTS. In this case if EVEX_WRITE_ROW_FAILED,
+ which means that we have duplicated key -> warning. In all
+ other cases -> error.
+ */
+ if (ret)
+ goto done;
+
+ VOID(pthread_mutex_lock(&LOCK_evex_running));
+ if (evex_is_running && et->status == MYSQL_EVENT_ENABLED)
+ {
+ sp_name spn(et->dbname, et->name);
+ ret= evex_load_and_compile_event(thd, &spn, true);
+ }
+ VOID(pthread_mutex_unlock(&LOCK_evex_running));
+
+done:
+ // No need to close the table, it will be closed in sql_parse::do_command
+
+ DBUG_RETURN(ret);
+}
+
+
+/*
+ The function exported to the world for alteration of events.
+
+ SYNOPSIS
+ evex_update_event()
+ thd THD
+ et event's data
+ new_name set in case of RENAME TO.
+
+ NOTES
+ et contains data about dbname and event name.
+ name is the new name of the event, if not null this means
+ that RENAME TO was specified in the query.
+*/
+
+int
+evex_update_event(THD *thd, event_timed *et, sp_name *new_name)
+{
+ int ret, i;
+ bool need_second_pass= true;
+
+ DBUG_ENTER("evex_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)))
+ goto done;
+
+ VOID(pthread_mutex_lock(&LOCK_evex_running));
+ if (!evex_is_running)
+ UNLOCK_MUTEX_AND_BAIL_OUT(LOCK_evex_running, done);
+
+ VOID(pthread_mutex_lock(&LOCK_event_arrays));
+ evex_remove_from_cache(&et->dbname, &et->name, false);
+ if (et->status == MYSQL_EVENT_ENABLED)
+ {
+ if (new_name)
+ ret= evex_load_and_compile_event(thd, new_name, false);
+ else
+ {
+ sp_name spn(et->dbname, et->name);
+ ret= evex_load_and_compile_event(thd, &spn, false);
+ }
+ if (ret == EVEX_COMPILE_ERROR)
+ my_error(ER_EVENT_COMPILE_ERROR, MYF(0));
+ }
+ VOID(pthread_mutex_unlock(&LOCK_event_arrays));
+ VOID(pthread_mutex_unlock(&LOCK_evex_running));
+
+done:
+ DBUG_RETURN(ret);
+}
+
+
+/*
+ Drops an event
+
+ SYNOPSIS
+ evex_drop_event()
+ thd THD
+ et event's name
+ drop_if_exists if set and the event not existing => warning onto the stack
+
+*/
+
+int
+evex_drop_event(THD *thd, event_timed *et, bool drop_if_exists)
+{
+ TABLE *table;
+ int ret= EVEX_OPEN_TABLE_FAILED;
+ bool opened;
+ DBUG_ENTER("evex_drop_event");
+
+ if (!(table= evex_open_event_table(thd, TL_WRITE)))
+ {
+ my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0));
+ goto done;
+ }
+
+ if (!(ret= evex_db_find_event_aux(thd, et->dbname, et->name, table)))
+ {
+ if ((ret= table->file->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;
+ }
+
+ VOID(pthread_mutex_lock(&LOCK_evex_running));
+ if (evex_is_running)
+ ret= evex_remove_from_cache(&et->dbname, &et->name, true);
+ VOID(pthread_mutex_unlock(&LOCK_evex_running));
+
+done:
+ /*
+ No need to close the table, it will be closed in sql_parse::do_command()
+ and evex_remove_from_cache does not try to open a table
+ */
+
+ DBUG_RETURN(ret);
+}
+
diff --git a/sql/event.h b/sql/event.h
new file mode 100644
index 00000000000..f3b49a99488
--- /dev/null
+++ b/sql/event.h
@@ -0,0 +1,218 @@
+/* Copyright (C) 2004-2005 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 */
+
+#ifndef _EVENT_H_
+#define _EVENT_H_
+
+#include "sp.h"
+#include "sp_head.h"
+
+#define EVEX_OK SP_OK
+#define EVEX_KEY_NOT_FOUND SP_KEY_NOT_FOUND
+#define EVEX_OPEN_TABLE_FAILED SP_OPEN_TABLE_FAILED
+#define EVEX_WRITE_ROW_FAILED SP_WRITE_ROW_FAILED
+#define EVEX_DELETE_ROW_FAILED SP_DELETE_ROW_FAILED
+#define EVEX_GET_FIELD_FAILED SP_GET_FIELD_FAILED
+#define EVEX_PARSE_ERROR SP_PARSE_ERROR
+#define EVEX_INTERNAL_ERROR SP_INTERNAL_ERROR
+#define EVEX_NO_DB_ERROR SP_NO_DB_ERROR
+#define EVEX_COMPILE_ERROR -19
+#define EVEX_GENERAL_ERROR -20
+#define EVEX_BAD_IDENTIFIER SP_BAD_IDENTIFIER
+#define EVEX_BODY_TOO_LONG SP_BODY_TOO_LONG
+#define EVEX_BAD_PARAMS -21
+#define EVEX_NOT_RUNNING -22
+
+#define EVENT_EXEC_NO_MORE (1L << 0)
+#define EVENT_NOT_USED (1L << 1)
+
+
+extern ulong opt_event_executor;
+
+enum enum_event_on_completion
+{
+ MYSQL_EVENT_ON_COMPLETION_DROP = 1,
+ MYSQL_EVENT_ON_COMPLETION_PRESERVE
+};
+
+enum enum_event_status
+{
+ MYSQL_EVENT_ENABLED = 1,
+ MYSQL_EVENT_DISABLED
+};
+
+
+class event_timed
+{
+ event_timed(const event_timed &); /* Prevent use of these */
+ void operator=(event_timed &);
+ my_bool running;
+ pthread_mutex_t LOCK_running;
+
+ bool status_changed;
+ bool last_executed_changed;
+ TIME last_executed;
+
+public:
+ 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;
+
+ longlong expression;
+ interval_type interval;
+
+ longlong created;
+ longlong modified;
+ enum enum_event_on_completion on_completion;
+ enum enum_event_status status;
+ sp_head *sphead;
+
+ const uchar *body_begin;
+
+ bool dropped;
+ bool free_sphead_on_delete;
+ uint flags;//all kind of purposes
+
+ event_timed():running(0), status_changed(false), last_executed_changed(false),
+ expression(0), created(0), modified(0),
+ on_completion(MYSQL_EVENT_ON_COMPLETION_DROP),
+ status(MYSQL_EVENT_ENABLED), sphead(0), dropped(false),
+ free_sphead_on_delete(true), flags(0)
+
+ {
+ pthread_mutex_init(&LOCK_running, MY_MUTEX_INIT_FAST);
+ init();
+ }
+
+ ~event_timed()
+ {
+ pthread_mutex_destroy(&LOCK_running);
+ if (free_sphead_on_delete)
+ free_sp();
+ }
+
+ void
+ init();
+
+ int
+ init_definer(THD *thd);
+
+ int
+ init_execute_at(THD *thd, Item *expr);
+
+ int
+ init_interval(THD *thd, Item *expr, interval_type new_interval);
+
+ void
+ init_name(THD *thd, sp_name *spn);
+
+ int
+ init_starts(THD *thd, Item *starts);
+
+ int
+ init_ends(THD *thd, Item *ends);
+
+ void
+ event_timed::init_body(THD *thd);
+
+ void
+ init_comment(THD *thd, LEX_STRING *set_comment);
+
+ int
+ load_from_row(MEM_ROOT *mem_root, TABLE *table);
+
+ bool
+ compute_next_execution_time();
+
+ void
+ mark_last_executed();
+
+ bool
+ drop(THD *thd);
+
+ bool
+ update_fields(THD *thd);
+
+ char *
+ get_show_create_event(THD *thd, uint *length);
+
+ int
+ execute(THD *thd, MEM_ROOT *mem_root= NULL);
+
+ int
+ compile(THD *thd, MEM_ROOT *mem_root= NULL);
+
+ void free_sp()
+ {
+ delete sphead;
+ sphead= 0;
+ }
+};
+
+
+int
+evex_create_event(THD *thd, event_timed *et, uint create_options);
+
+int
+evex_update_event(THD *thd, event_timed *et, sp_name *new_name);
+
+int
+evex_drop_event(THD *thd, event_timed *et, bool drop_if_exists);
+
+
+int
+init_events();
+
+void
+shutdown_events();
+
+
+// auxiliary
+int
+event_timed_compare(event_timed **a, event_timed **b);
+
+
+/*
+CREATE TABLE `event` (
+ `db` varchar(64) character set utf8 collate utf8_bin NOT NULL default '',
+ `name` varchar(64) character set utf8 collate utf8_bin NOT NULL default '',
+ `body` longblob NOT NULL,
+ `definer` varchar(77) character set utf8 collate utf8_bin NOT NULL default '',
+ `execute_at` datetime default NULL,
+ `transient_expression` int(11) default NULL,
+ `interval_type` 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') default NULL,
+ `created` timestamp NOT NULL,
+ `modified` timestamp NOT NULL,
+ `last_executed` datetime default NULL,
+ `starts` datetime default NULL,
+ `ends` datetime default NULL,
+ `status` enum('ENABLED','DISABLED') NOT NULL default 'ENABLED',
+ `on_completion` enum('DROP','PRESERVE') NOT NULL default 'DROP',
+ `comment` varchar(64) character set utf8 collate utf8_bin NOT NULL default '',
+ PRIMARY KEY (`db`,`name`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8
+*/
+
+#endif /* _EVENT_H_ */
diff --git a/sql/event_executor.cc b/sql/event_executor.cc
new file mode 100644
index 00000000000..623e45bf118
--- /dev/null
+++ b/sql/event_executor.cc
@@ -0,0 +1,588 @@
+/* Copyright (C) 2004-2005 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 "event_priv.h"
+#include "event.h"
+#include "sp.h"
+
+
+/*
+ Make this define DBUG_FAULTY_THR to be able to put breakpoints inside
+ code used by the scheduler's thread(s). In this case user connections
+ are not possible because the scheduler thread code is ran inside the
+ main thread (no spawning takes place. If you want to debug client
+ connection then start with --one-thread and make the define
+ DBUG_FAULTY_THR !
+*/
+#define DBUG_FAULTY_THR2
+
+extern ulong thread_created;
+
+
+pthread_mutex_t LOCK_event_arrays,
+ LOCK_workers_count,
+ LOCK_evex_running;
+
+
+bool evex_is_running= false;
+
+ulonglong evex_main_thread_id= 0;
+ulong opt_event_executor;
+volatile my_bool event_executor_running_global_var;
+static my_bool evex_mutexes_initted= false;
+static uint workers_count;
+
+static int
+evex_load_events_from_db(THD *thd);
+
+
+
+/*
+ TODO Andrey: Check for command line option whether to start
+ the main thread or not.
+*/
+
+pthread_handler_t
+event_executor_worker(void *arg);
+
+pthread_handler_t
+event_executor_main(void *arg);
+
+static
+void evex_init_mutexes()
+{
+ if (evex_mutexes_initted)
+ return;
+
+ evex_mutexes_initted= true;
+ pthread_mutex_init(&LOCK_event_arrays, MY_MUTEX_INIT_FAST);
+ pthread_mutex_init(&LOCK_workers_count, MY_MUTEX_INIT_FAST);
+ pthread_mutex_init(&LOCK_evex_running, MY_MUTEX_INIT_FAST);
+
+ event_executor_running_global_var= opt_event_executor;
+}
+
+
+int
+init_events()
+{
+ pthread_t th;
+
+ DBUG_ENTER("init_events");
+
+ DBUG_PRINT("info",("Starting events main thread"));
+
+ evex_init_mutexes();
+
+ VOID(pthread_mutex_lock(&LOCK_evex_running));
+ evex_is_running= false;
+ VOID(pthread_mutex_unlock(&LOCK_evex_running));
+
+#ifndef DBUG_FAULTY_THR
+ //TODO Andrey: Change the error code returned!
+ if (pthread_create(&th, NULL, event_executor_main, (void*)NULL))
+ DBUG_RETURN(ER_SLAVE_THREAD);
+#else
+ event_executor_main(NULL);
+#endif
+
+ DBUG_RETURN(0);
+}
+
+
+void
+shutdown_events()
+{
+ DBUG_ENTER("shutdown_events");
+
+ VOID(pthread_mutex_lock(&LOCK_evex_running));
+ VOID(pthread_mutex_unlock(&LOCK_evex_running));
+
+ pthread_mutex_destroy(&LOCK_event_arrays);
+ pthread_mutex_destroy(&LOCK_workers_count);
+ pthread_mutex_destroy(&LOCK_evex_running);
+
+ DBUG_VOID_RETURN;
+}
+
+
+static int
+init_event_thread(THD* thd)
+{
+ DBUG_ENTER("init_event_thread");
+ thd->client_capabilities= 0;
+ thd->security_ctx->skip_grants();
+ 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_LOCAL_FILES;
+ thd->real_id=pthread_self();
+ VOID(pthread_mutex_lock(&LOCK_thread_count));
+ thd->thread_id= thread_id++;
+ VOID(pthread_mutex_unlock(&LOCK_thread_count));
+
+ if (init_thr_lock() || thd->store_globals())
+ {
+ thd->cleanup();
+ delete thd;
+ 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
+
+ thd->proc_info= "Initialized";
+ thd->version= refresh_version;
+ thd->set_time();
+ DBUG_RETURN(0);
+}
+
+pthread_handler_t
+event_executor_main(void *arg)
+{
+ THD *thd; /* needs to be first for thread_stack */
+ ulonglong iter_num= 0;
+ uint i=0, j=0;
+ my_ulonglong cnt= 0;
+
+ DBUG_ENTER("event_executor_main");
+ DBUG_PRINT("event_executor_main", ("EVEX thread started"));
+
+
+ // init memory root
+ init_alloc_root(&evex_mem_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC);
+
+
+ // needs to call my_thread_init(), otherwise we get a coredump in DBUG_ stuff
+ my_thread_init();
+
+ //TODO Andrey: Check for NULL
+ if (!(thd = new THD)) // note that contructor of THD uses DBUG_ !
+ {
+ sql_print_error("Cannot create THD for event_executor_main");
+ goto err_no_thd;
+ }
+ thd->thread_stack = (char*)&thd; // remember where our stack is
+
+ pthread_detach_this_thread();
+
+ if (init_event_thread(thd))
+ goto err;
+
+ // make this thread invisible it has no vio -> show processlist won't see
+ thd->system_thread= 1;
+
+ VOID(pthread_mutex_lock(&LOCK_thread_count));
+ threads.append(thd);
+ thread_count++;
+ thread_running++;
+ VOID(pthread_mutex_unlock(&LOCK_thread_count));
+
+ DBUG_PRINT("EVEX main thread", ("Initing events_queuey"));
+
+ /*
+ eventually manifest that we are running, not to crashe because of
+ usage of non-initialized memory structures.
+ */
+ VOID(pthread_mutex_lock(&LOCK_evex_running));
+ VOID(pthread_mutex_lock(&LOCK_event_arrays));
+ evex_queue_init(&EVEX_EQ_NAME);
+ VOID(pthread_mutex_unlock(&LOCK_event_arrays));
+ evex_is_running= true;
+ VOID(pthread_mutex_unlock(&LOCK_evex_running));
+
+ if (evex_load_events_from_db(thd))
+ goto err;
+
+ THD_CHECK_SENTRY(thd);
+ /* Read queries from the IO/THREAD until this thread is killed */
+ evex_main_thread_id= thd->thread_id;
+ sql_print_information("Scheduler thread started");
+ while (!thd->killed)
+ {
+ TIME time_now;
+ my_time_t now;
+ event_timed *et;
+
+ cnt++;
+ DBUG_PRINT("info", ("EVEX External Loop %d", cnt));
+
+ thd->proc_info = "Sleeping";
+ if (!evex_queue_num_elements(EVEX_EQ_NAME) ||
+ !event_executor_running_global_var)
+ {
+ my_sleep(1000000);// sleep 1s
+ continue;
+ }
+
+ {
+ int t2sleep;
+
+
+ /*
+ now let's see how much time to sleep, we know there is at least 1
+ element in the queue.
+ */
+ VOID(pthread_mutex_lock(&LOCK_event_arrays));
+ if (!evex_queue_num_elements(EVEX_EQ_NAME))
+ {
+ VOID(pthread_mutex_unlock(&LOCK_event_arrays));
+ continue;
+ }
+ et= evex_queue_first_element(&EVEX_EQ_NAME, event_timed*);
+
+ time(&now);
+ my_tz_UTC->gmt_sec_to_TIME(&time_now, now);
+ t2sleep= evex_time_diff(&et->execute_at, &time_now);
+ VOID(pthread_mutex_unlock(&LOCK_event_arrays));
+ if (t2sleep > 0)
+ {
+ sql_print_information("Sleeping for %d seconds.", t2sleep);
+ printf("\nWHEN=%llu NOW=%llu\n", TIME_to_ulonglong_datetime(&et->execute_at), TIME_to_ulonglong_datetime(&time_now));
+ /*
+ We sleep t2sleep seconds but we check every second whether this thread
+ has been killed, or there is new candidate
+ */
+ while (t2sleep-- && !thd->killed &&
+ evex_queue_num_elements(EVEX_EQ_NAME) &&
+ (evex_queue_first_element(&EVEX_EQ_NAME, event_timed*) == et))
+ my_sleep(1000000);
+ sql_print_information("Finished sleeping");
+ }
+ if (!event_executor_running_global_var)
+ continue;
+
+ }
+
+
+ VOID(pthread_mutex_lock(&LOCK_event_arrays));
+
+ if (!evex_queue_num_elements(EVEX_EQ_NAME))
+ {
+ VOID(pthread_mutex_unlock(&LOCK_event_arrays));
+ continue;
+ }
+ et= evex_queue_first_element(&EVEX_EQ_NAME, event_timed*);
+
+ /*
+ if this is the first event which is after time_now then no
+ more need to iterate over more elements since the array is sorted.
+ */
+ if (et->execute_at.year > 1969 &&
+ my_time_compare(&time_now, &et->execute_at) == -1)
+ {
+ VOID(pthread_mutex_unlock(&LOCK_event_arrays));
+ continue;
+ }
+
+ if (et->status == MYSQL_EVENT_ENABLED)
+ {
+ pthread_t th;
+
+ DBUG_PRINT("info", (" Spawning a thread %d", ++iter_num));
+ sql_print_information(" Spawning a thread %d", ++iter_num);
+#ifndef DBUG_FAULTY_THR
+ sql_print_information(" Thread is not debuggable!");
+ if (pthread_create(&th, NULL, event_executor_worker, (void*)et))
+ {
+ sql_print_error("Problem while trying to create a thread");
+ UNLOCK_MUTEX_AND_BAIL_OUT(LOCK_event_arrays, err);
+ }
+#else
+ event_executor_worker((void *) et);
+#endif
+ printf("[%10s] exec at [%llu]\n", et->name.str,TIME_to_ulonglong_datetime(&et->execute_at));
+ et->mark_last_executed();
+ et->compute_next_execution_time();
+ printf("[%10s] next at [%llu]\n\n\n", et->name.str,TIME_to_ulonglong_datetime(&et->execute_at));
+ et->update_fields(thd);
+ if ((et->execute_at.year && !et->expression) ||
+ TIME_to_ulonglong_datetime(&et->execute_at) == 0L)
+ et->flags |= EVENT_EXEC_NO_MORE;
+ }
+ if ((et->flags & EVENT_EXEC_NO_MORE) || et->status == MYSQL_EVENT_DISABLED)
+ {
+ if (et->dropped)
+ et->drop(thd);
+ delete et;
+ evex_queue_delete_element(&EVEX_EQ_NAME, 1);// 1 is top
+ } else
+ evex_queue_first_updated(&EVEX_EQ_NAME);
+
+ VOID(pthread_mutex_unlock(&LOCK_event_arrays));
+ }// while
+
+err:
+ // First manifest that this thread does not work and then destroy
+ VOID(pthread_mutex_lock(&LOCK_evex_running));
+ evex_is_running= false;
+ evex_main_thread_id= 0;
+ VOID(pthread_mutex_unlock(&LOCK_evex_running));
+
+ sql_print_information("Event scheduler stopping");
+
+ /*
+ TODO: A better will be with a conditional variable
+ */
+ /*
+ Read workers_count without lock, no need for locking.
+ In the worst case we have to wait 1sec more.
+ */
+ while (workers_count)
+ my_sleep(1000000);// 1s
+
+ /*
+ LEX_STRINGs reside in the memory root and will be destroyed with it.
+ Hence no need of delete but only freeing of SP
+ */
+ // First we free all objects ...
+ for (i= 0; i < evex_queue_num_elements(EVEX_EQ_NAME); ++i)
+ {
+ event_timed *et= evex_queue_element(&EVEX_EQ_NAME, i, event_timed*);
+ et->free_sp();
+ delete et;
+ }
+ // ... then we can thras the whole queue at once
+ evex_queue_destroy(&EVEX_EQ_NAME);
+
+ thd->proc_info = "Clearing";
+ DBUG_ASSERT(thd->net.buff != 0);
+ net_end(&thd->net); // destructor will not free it, because we are weird
+ THD_CHECK_SENTRY(thd);
+
+ pthread_mutex_lock(&LOCK_thread_count);
+ thread_count--;
+ thread_running--;
+#ifndef DBUG_FAULTY_THR
+ THD_CHECK_SENTRY(thd);
+ delete thd;
+#endif
+ pthread_mutex_unlock(&LOCK_thread_count);
+
+
+err_no_thd:
+ VOID(pthread_mutex_lock(&LOCK_evex_running));
+ evex_is_running= false;
+ VOID(pthread_mutex_unlock(&LOCK_evex_running));
+
+ free_root(&evex_mem_root, MYF(0));
+ sql_print_information("Event scheduler stopped");
+
+#ifndef DBUG_FAULTY_THR
+ my_thread_end();
+ pthread_exit(0);
+#endif
+ DBUG_RETURN(0);// Can't return anything here
+}
+
+
+pthread_handler_t
+event_executor_worker(void *event_void)
+{
+ THD *thd; /* needs to be first for thread_stack */
+ event_timed *event = (event_timed *) event_void;
+ MEM_ROOT worker_mem_root;
+
+ DBUG_ENTER("event_executor_worker");
+ VOID(pthread_mutex_lock(&LOCK_workers_count));
+ ++workers_count;
+ VOID(pthread_mutex_unlock(&LOCK_workers_count));
+
+ init_alloc_root(&worker_mem_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC);
+
+#ifndef DBUG_FAULTY_THR
+ my_thread_init();
+
+ if (!(thd = new THD)) // note that contructor of THD uses DBUG_ !
+ {
+ sql_print_error("Cannot create a THD structure in a scheduler worker thread");
+ goto err_no_thd;
+ }
+ thd->thread_stack = (char*)&thd; // remember where our stack is
+ thd->mem_root= &worker_mem_root;
+
+ pthread_detach(pthread_self());
+
+ if (init_event_thread(thd))
+ goto err;
+
+ thd->init_for_queries();
+
+ // make this thread visible it has no vio -> show processlist needs this flag
+ thd->system_thread= 1;
+
+ VOID(pthread_mutex_lock(&LOCK_thread_count));
+ threads.append(thd);
+ thread_count++;
+ thread_running++;
+ VOID(pthread_mutex_unlock(&LOCK_thread_count));
+#else
+ thd= current_thd;
+#endif
+
+ // thd->security_ctx->priv_host is char[MAX_HOSTNAME]
+
+ strxnmov(thd->security_ctx->priv_host, sizeof(thd->security_ctx->priv_host),
+ event->definer_host.str, NullS);
+
+ thd->security_ctx->priv_user= event->definer_user.str;
+
+ thd->db= event->dbname.str;
+ if (!check_access(thd, EVENT_ACL, event->dbname.str, 0, 0, 0,
+ is_schema_db(event->dbname.str)))
+ {
+ char exec_time[200];
+ int ret;
+ my_TIME_to_str(&event->execute_at, exec_time);
+ DBUG_PRINT("info", (" EVEX EXECUTING event for event %s.%s [EXPR:%d][EXECUTE_AT:%s]", event->dbname.str, event->name.str,(int) event->expression, exec_time));
+ sql_print_information(" EVEX EXECUTING event for event %s.%s [EXPR:%d][EXECUTE_AT:%s]", event->dbname.str, event->name.str,(int) event->expression, exec_time);
+ ret= event->execute(thd, &worker_mem_root);
+ sql_print_information(" EVEX EXECUTED event for event %s.%s [EXPR:%d][EXECUTE_AT:%s]. RetCode=%d", event->dbname.str, event->name.str,(int) event->expression, exec_time, ret);
+ DBUG_PRINT("info", (" EVEX EXECUTED event for event %s.%s [EXPR:%d][EXECUTE_AT:%s]", event->dbname.str, event->name.str,(int) event->expression, exec_time));
+ }
+ thd->db= 0;
+
+err:
+ VOID(pthread_mutex_lock(&LOCK_thread_count));
+#ifndef DBUG_FAULTY_THR
+ thread_count--;
+ thread_running--;
+ /*
+ Some extra safety, which should not been needed (normally, event deletion
+ should already have done these assignments (each event which sets these
+ variables is supposed to set them to 0 before terminating)).
+ */
+ VOID(pthread_mutex_unlock(&LOCK_thread_count));
+
+ thd->proc_info = "Clearing";
+ DBUG_ASSERT(thd->net.buff != 0);
+ net_end(&thd->net); // destructor will not free it, because we are weird
+ THD_CHECK_SENTRY(thd);
+
+ VOID(pthread_mutex_lock(&LOCK_thread_count));
+ THD_CHECK_SENTRY(thd);
+ delete thd;
+#endif
+ VOID(pthread_mutex_unlock(&LOCK_thread_count));
+
+err_no_thd:
+
+ free_root(&worker_mem_root, MYF(0));
+
+ VOID(pthread_mutex_lock(&LOCK_workers_count));
+ --workers_count;
+ VOID(pthread_mutex_unlock(&LOCK_workers_count));
+
+#ifndef DBUG_FAULTY_THR
+ my_thread_end();
+ pthread_exit(0);
+#endif
+ DBUG_RETURN(0); // Can't return anything here
+}
+
+
+static int
+evex_load_events_from_db(THD *thd)
+{
+ TABLE *table;
+ READ_RECORD read_record_info;
+ MYSQL_LOCK *lock;
+ int ret= -1;
+
+ DBUG_ENTER("evex_load_events_from_db");
+
+ if (!(table= evex_open_event_table(thd, TL_READ)))
+ DBUG_RETURN(SP_OPEN_TABLE_FAILED);
+
+ VOID(pthread_mutex_lock(&LOCK_event_arrays));
+
+ 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("evex_load_events_from_db", ("Out of memory"));
+ ret= -1;
+ goto end;
+ }
+ DBUG_PRINT("evex_load_events_from_db", ("Loading event from row."));
+
+ if ((ret= et->load_from_row(&evex_mem_root, table)))
+ {
+ sql_print_error("Error while loading from mysql.event. "
+ "Table probably corrupted");
+ goto end;
+ }
+ if (et->status != MYSQL_EVENT_ENABLED)
+ {
+ DBUG_PRINT("evex_load_events_from_db",("Event %s is disabled", et->name.str));
+ delete et;
+ continue;
+ }
+
+ DBUG_PRINT("evex_load_events_from_db",
+ ("Event %s loaded from row. Time to compile", et->name.str));
+
+ if ((ret= et->compile(thd, &evex_mem_root)))
+ {
+ sql_print_error("Error while compiling %s.%s. Aborting load.",
+ et->dbname.str, et->name.str);
+ goto end;
+ }
+ // let's find when to be executed
+ et->compute_next_execution_time();
+
+ DBUG_PRINT("evex_load_events_from_db", ("Adding to the exec list."));
+
+ evex_queue_insert(&EVEX_EQ_NAME, (EVEX_PTOQEL) et);
+ DBUG_PRINT("evex_load_events_from_db", ("%p %*s",
+ et, et->name.length,et->name.str));
+ }
+
+ ret= 0;
+
+end:
+ VOID(pthread_mutex_unlock(&LOCK_event_arrays));
+ end_read_record(&read_record_info);
+
+ thd->version--; // Force close to free memory
+
+ close_thread_tables(thd);
+
+ DBUG_PRINT("info", ("Finishing with status code %d", ret));
+ DBUG_RETURN(ret);
+}
+
+
+bool sys_var_event_executor::update(THD *thd, set_var *var)
+{
+ // here start the thread if not running.
+ VOID(pthread_mutex_lock(&LOCK_evex_running));
+ *value= var->save_result.ulong_value;
+ if ((my_bool) *value && !evex_is_running)
+ {
+ VOID(pthread_mutex_unlock(&LOCK_evex_running));
+ init_events();
+ } else
+ VOID(pthread_mutex_unlock(&LOCK_evex_running));
+ return 0;
+}
+
diff --git a/sql/event_priv.h b/sql/event_priv.h
new file mode 100644
index 00000000000..cb45a700cb8
--- /dev/null
+++ b/sql/event_priv.h
@@ -0,0 +1,98 @@
+/* Copyright (C) 2004-2005 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 */
+
+#ifndef _EVENT_PRIV_H_
+#define _EVENT_PRIV_H_
+#include "mysql_priv.h"
+
+
+#define EVEX_USE_QUEUE
+
+#define UNLOCK_MUTEX_AND_BAIL_OUT(__mutex, __label) \
+ { VOID(pthread_mutex_unlock(&__mutex)); goto __label; }
+
+enum
+{
+ EVEX_FIELD_DB = 0,
+ EVEX_FIELD_NAME,
+ EVEX_FIELD_BODY,
+ EVEX_FIELD_DEFINER,
+ EVEX_FIELD_EXECUTE_AT,
+ EVEX_FIELD_INTERVAL_EXPR,
+ EVEX_FIELD_TRANSIENT_INTERVAL,
+ EVEX_FIELD_CREATED,
+ EVEX_FIELD_MODIFIED,
+ EVEX_FIELD_LAST_EXECUTED,
+ EVEX_FIELD_STARTS,
+ EVEX_FIELD_ENDS,
+ EVEX_FIELD_STATUS,
+ EVEX_FIELD_ON_COMPLETION,
+ EVEX_FIELD_COMMENT,
+ EVEX_FIELD_COUNT /* a cool trick to count the number of fields :) */
+};
+
+
+
+int
+my_time_compare(TIME *a, TIME *b);
+
+int
+evex_db_find_event_aux(THD *thd, const LEX_STRING dbname,
+ const LEX_STRING rname, TABLE *table);
+
+TABLE *
+evex_open_event_table(THD *thd, enum thr_lock_type lock_type);
+
+int
+event_timed_compare_q(void *vptr, byte* a, byte *b);
+
+int
+evex_time_diff(TIME *a, TIME *b);
+
+
+
+#define EXEC_QUEUE_QUEUE_NAME executing_queue
+#define EXEC_QUEUE_DARR_NAME evex_executing_queue
+
+
+#define EVEX_QUEUE_TYPE QUEUE
+#define EVEX_PTOQEL byte *
+
+#define EVEX_EQ_NAME executing_queue
+#define evex_queue_first_element(queue, __cast) ((__cast)queue_top(queue))
+#define evex_queue_element(queue, idx, __cast) ((__cast)queue_element(queue, idx))
+#define evex_queue_delete_element(queue, idx) queue_remove(queue, idx)
+#define evex_queue_destroy(queue) delete_queue(queue)
+#define evex_queue_first_updated(queue) queue_replaced(queue)
+#define evex_queue_insert(queue, element) queue_insert_safe(queue, element);
+
+
+
+void
+evex_queue_init(EVEX_QUEUE_TYPE *queue);
+
+#define evex_queue_num_elements(queue) queue.elements
+
+
+extern bool evex_is_running;
+extern MEM_ROOT evex_mem_root;
+extern pthread_mutex_t LOCK_event_arrays,
+ LOCK_workers_count,
+ LOCK_evex_running;
+extern ulonglong evex_main_thread_id;
+extern QUEUE EVEX_EQ_NAME;
+
+#endif /* _EVENT_PRIV_H_ */
diff --git a/sql/event_timed.cc b/sql/event_timed.cc
new file mode 100644
index 00000000000..747eab4558c
--- /dev/null
+++ b/sql/event_timed.cc
@@ -0,0 +1,970 @@
+/* Copyright (C) 2004-2005 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 "event_priv.h"
+#include "event.h"
+#include "sp.h"
+
+
+
+extern int yyparse(void *thd);
+
+/*
+ Init all member variables
+
+ SYNOPSIS
+ event_timed::init()
+*/
+
+void
+event_timed::init()
+{
+ DBUG_ENTER("event_timed::init");
+
+ dbname.str= name.str= body.str= comment.str= 0;
+ dbname.length= name.length= body.length= comment.length= 0;
+
+ 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);
+
+ definer_user.str= definer_host.str= 0;
+ definer_user.length= definer_host.length= 0;
+
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Set a name of the event
+
+ SYNOPSIS
+ event_timed::init_name()
+ thd THD
+ spn the name extracted in the parser
+*/
+
+void
+event_timed::init_name(THD *thd, sp_name *spn)
+{
+ DBUG_ENTER("event_timed::init_name");
+ uint n; /* Counter for nul trimming */
+ /* During parsing, we must use thd->mem_root */
+ MEM_ROOT *root= thd->mem_root;
+
+ /* We have to copy strings to get them into the right memroot */
+ if (spn)
+ {
+ dbname.length= spn->m_db.length;
+ if (spn->m_db.length == 0)
+ dbname.str= NULL;
+ else
+ dbname.str= strmake_root(root, spn->m_db.str, spn->m_db.length);
+ name.length= spn->m_name.length;
+ name.str= strmake_root(root, spn->m_name.str, spn->m_name.length);
+
+ if (spn->m_qname.length == 0)
+ spn->init_qname(thd);
+ }
+ else if (thd->db)
+ {
+ dbname.length= thd->db_length;
+ dbname.str= strmake_root(root, thd->db, dbname.length);
+ }
+
+ DBUG_PRINT("dbname", ("len=%d db=%s",dbname.length, dbname.str));
+ DBUG_PRINT("name", ("len=%d name=%s",name.length, name.str));
+
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Set body of the event - what should be executed.
+
+ SYNOPSIS
+ event_timed::init_body()
+ thd THD
+
+ NOTE
+ The body is extracted by copying all data between the
+ start of the body set by another method and the current pointer in Lex.
+*/
+
+void
+event_timed::init_body(THD *thd)
+{
+ DBUG_ENTER("event_timed::init_body");
+ MEM_ROOT *root= thd->mem_root;
+
+ body.length= thd->lex->ptr - body_begin;
+ // Trim nuls at the end
+ while (body.length && body_begin[body.length-1] == '\0')
+ body.length--;
+
+ body.str= strmake_root(root, (char *)body_begin, body.length);
+
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Set time for execution for one time events.
+
+ SYNOPSIS
+ event_timed::init_execute_at()
+ expr when (datetime)
+
+ RETURNS
+ 0 - OK
+ EVEX_PARSE_ERROR - fix_fields failed
+ EVEX_BAD_PARAMS - datetime is in the past
+*/
+
+int
+event_timed::init_execute_at(THD *thd, Item *expr)
+{
+ my_bool not_used;
+ TIME ltime;
+ my_time_t my_time_tmp;
+
+ TIME time_tmp;
+ DBUG_ENTER("event_timed::init_execute_at");
+
+ if (expr->fix_fields(thd, &expr))
+ DBUG_RETURN(EVEX_PARSE_ERROR);
+
+ if (expr->val_int() == MYSQL_TIMESTAMP_ERROR)
+ DBUG_RETURN(EVEX_BAD_PARAMS);
+
+ // 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 (expr->val_int() < TIME_to_ulonglong_datetime(&time_tmp))
+ DBUG_RETURN(EVEX_BAD_PARAMS);
+
+ if ((not_used= expr->get_date(&ltime, TIME_NO_ZERO_DATE)))
+ DBUG_RETURN(EVEX_BAD_PARAMS);
+
+ /*
+ This may result in a 1970-01-01 date if ltime is > 2037-xx-xx
+ CONVERT_TZ has similar problem
+ */
+ my_tz_UTC->gmt_sec_to_TIME(&ltime, TIME_to_timestamp(thd,&ltime, &not_used));
+
+
+ execute_at= ltime;
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Set time for execution for transient events.
+
+ SYNOPSIS
+ event_timed::init_interval()
+ expr how much?
+ new_interval what is the interval
+
+ RETURNS
+ 0 - OK
+ EVEX_PARSE_ERROR - fix_fields failed
+ EVEX_BAD_PARAMS - Interval is not positive
+*/
+
+int
+event_timed::init_interval(THD *thd, Item *expr, interval_type new_interval)
+{
+ longlong tmp;
+ DBUG_ENTER("event_timed::init_interval");
+
+ if (expr->fix_fields(thd, &expr))
+ DBUG_RETURN(EVEX_PARSE_ERROR);
+
+ if ((tmp= expr->val_int()) <= 0)
+ DBUG_RETURN(EVEX_BAD_PARAMS);
+
+ expression= tmp;
+ interval= new_interval;
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Set activation time.
+
+ SYNOPSIS
+ event_timed::init_starts()
+ expr how much?
+ interval what is the interval
+
+ NOTES
+ Note that activation time is not execution time.
+ EVERY 5 MINUTE STARTS "2004-12-12 10:00:00" means that
+ the event will be executed every 5 minutes but this will
+ start at the date shown above. Expressions are possible :
+ DATE_ADD(NOW(), INTERVAL 1 DAY) -- start tommorow at
+ same time.
+
+ RETURNS
+ 0 - OK
+ EVEX_PARSE_ERROR - fix_fields failed
+*/
+
+int
+event_timed::init_starts(THD *thd, Item *new_starts)
+{
+ my_bool not_used;
+ TIME ltime;
+ my_time_t my_time_tmp;
+
+ DBUG_ENTER("event_timed::init_starts");
+
+ if (new_starts->fix_fields(thd, &new_starts))
+ DBUG_RETURN(EVEX_PARSE_ERROR);
+
+ if (new_starts->val_int() == MYSQL_TIMESTAMP_ERROR)
+ DBUG_RETURN(EVEX_BAD_PARAMS);
+
+ if ((not_used= new_starts->get_date(&ltime, TIME_NO_ZERO_DATE)))
+ DBUG_RETURN(EVEX_BAD_PARAMS);
+
+ /*
+ This may result in a 1970-01-01 date if ltime is > 2037-xx-xx
+ CONVERT_TZ has similar problem
+ */
+ my_tz_UTC->gmt_sec_to_TIME(&ltime, TIME_to_timestamp(thd, &ltime, &not_used));
+
+ starts= ltime;
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Set deactivation time.
+
+ SYNOPSIS
+ event_timed::init_ends()
+ thd THD
+ new_ends when?
+
+ NOTES
+ Note that activation time is not execution time.
+ EVERY 5 MINUTE ENDS "2004-12-12 10:00:00" means that
+ the event will be executed every 5 minutes but this will
+ end at the date shown above. Expressions are possible :
+ DATE_ADD(NOW(), INTERVAL 1 DAY) -- end tommorow at
+ same time.
+
+ RETURNS
+ 0 - OK
+ EVEX_PARSE_ERROR - fix_fields failed
+ EVEX_BAD_PARAMS - ENDS before STARTS
+*/
+
+int
+event_timed::init_ends(THD *thd, Item *new_ends)
+{
+ TIME ltime;
+ my_time_t my_time_tmp;
+ my_bool not_used;
+
+ DBUG_ENTER("event_timed::init_ends");
+
+ if (new_ends->fix_fields(thd, &new_ends))
+ DBUG_RETURN(EVEX_PARSE_ERROR);
+
+ // the field was already fixed in init_ends
+ if ((not_used= new_ends->get_date(&ltime, TIME_NO_ZERO_DATE)))
+ DBUG_RETURN(EVEX_BAD_PARAMS);
+
+ /*
+ This may result in a 1970-01-01 date if ltime is > 2037-xx-xx
+ CONVERT_TZ has similar problem
+ */
+ my_tz_UTC->gmt_sec_to_TIME(&ltime, TIME_to_timestamp(thd, &ltime, &not_used));
+
+ if (starts.year && my_time_compare(&starts, &ltime) != -1)
+ DBUG_RETURN(EVEX_BAD_PARAMS);
+
+ ends= ltime;
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Sets comment.
+
+ SYNOPSIS
+ event_timed::init_comment()
+ thd THD - used for memory allocation
+ comment the string.
+*/
+
+void
+event_timed::init_comment(THD *thd, LEX_STRING *set_comment)
+{
+ DBUG_ENTER("event_timed::init_comment");
+
+ comment.str= strmake_root(thd->mem_root, set_comment->str,
+ comment.length= set_comment->length);
+
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Inits definer (definer_user and definer_host) during
+ parsing.
+
+ SYNOPSIS
+ event_timed::init_definer()
+*/
+
+int
+event_timed::init_definer(THD *thd)
+{
+ DBUG_ENTER("event_timed::init_definer");
+
+ definer_user.str= strdup_root(thd->mem_root, thd->security_ctx->priv_user);
+ definer_user.length= strlen(thd->security_ctx->priv_user);
+
+ definer_host.str= strdup_root(thd->mem_root, thd->security_ctx->priv_host);
+ definer_host.length= strlen(thd->security_ctx->priv_host);
+
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Loads an event from a row from mysql.event
+
+ SYNOPSIS
+ event_timed::load_from_row()
+
+ REMARKS
+ 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(MEM_ROOT *mem_root, TABLE *table)
+{
+ longlong created;
+ longlong modified;
+ char *ptr;
+ event_timed *et;
+ uint len;
+ bool res1, res2;
+
+ DBUG_ENTER("event_timed::load_from_row");
+
+ if (!table)
+ goto error;
+
+ et= this;
+
+ if (table->s->fields != EVEX_FIELD_COUNT)
+ goto error;
+
+ if ((et->dbname.str= get_field(mem_root,
+ table->field[EVEX_FIELD_DB])) == NULL)
+ goto error;
+
+ et->dbname.length= strlen(et->dbname.str);
+
+ if ((et->name.str= get_field(mem_root,
+ table->field[EVEX_FIELD_NAME])) == NULL)
+ goto error;
+
+ et->name.length= strlen(et->name.str);
+
+ if ((et->body.str= get_field(mem_root,
+ table->field[EVEX_FIELD_BODY])) == NULL)
+ goto error;
+
+ et->body.length= strlen(et->body.str);
+
+ if ((et->definer.str= get_field(mem_root,
+ table->field[EVEX_FIELD_DEFINER])) == NullS)
+ goto error;
+ et->definer.length= strlen(et->definer.str);
+
+ ptr= strchr(et->definer.str, '@');
+
+ if (! ptr)
+ ptr= et->definer.str;
+
+ len= ptr - et->definer.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;
+
+
+ res1= table->field[EVEX_FIELD_STARTS]->
+ get_date(&et->starts, TIME_NO_ZERO_DATE);
+
+ res2= table->field[EVEX_FIELD_ENDS]->
+ get_date(&et->ends, TIME_NO_ZERO_DATE);
+
+ et->expression= table->field[EVEX_FIELD_INTERVAL_EXPR]->val_int();
+
+ /*
+ If res1 and res2 are true then both fields are empty.
+ Hence if EVEX_FIELD_EXECUTE_AT is empty there is an error.
+ */
+ if (res1 && res2 && !et->expression && table->field[EVEX_FIELD_EXECUTE_AT]->
+ get_date(&et->execute_at, TIME_NO_ZERO_DATE))
+ goto error;
+
+ /*
+ In DB the values start from 1 but enum interval_type starts
+ from 0
+ */
+ et->interval= (interval_type)
+ ((ulonglong) table->field[EVEX_FIELD_TRANSIENT_INTERVAL]->val_int() - 1);
+
+ et->modified= table->field[EVEX_FIELD_CREATED]->val_int();
+ et->created= table->field[EVEX_FIELD_MODIFIED]->val_int();
+
+ /*
+ ToDo Andrey : Ask PeterG & Serg what to do in this case.
+ Whether on load last_executed_at should be loaded
+ or it must be 0ed. If last_executed_at is loaded
+ then an event can be scheduled for execution
+ instantly. Let's say an event has to be executed
+ every 15 mins. The server has been stopped for
+ more than this time and then started. If L_E_AT
+ is loaded from DB, execution at L_E_AT+15min
+ will be scheduled. However this time is in the past.
+ Hence immediate execution. Due to patch of
+ ::mark_last_executed() last_executed gets time_now
+ and not execute_at. If not like this a big
+ queue can be scheduled for times which are still in
+ the past (2, 3 and more executions which will be
+ consequent).
+ */
+ set_zero_time(&last_executed, MYSQL_TIMESTAMP_DATETIME);
+#ifdef ANDREY_0
+ table->field[EVEX_FIELD_LAST_EXECUTED]->
+ get_date(&et->last_executed, TIME_NO_ZERO_DATE);
+#endif
+ last_executed_changed= false;
+
+ // ToDo : Andrey . Find a way not to allocate ptr on event_mem_root
+ if ((ptr= get_field(mem_root, table->field[EVEX_FIELD_STATUS])) == NullS)
+ goto error;
+
+ DBUG_PRINT("load_from_row", ("Event [%s] is [%s]", et->name.str, ptr));
+ et->status= (ptr[0]=='E'? MYSQL_EVENT_ENABLED:
+ MYSQL_EVENT_DISABLED);
+
+ // ToDo : Andrey . Find a way not to allocate ptr on event_mem_root
+ if ((ptr= get_field(mem_root,
+ table->field[EVEX_FIELD_ON_COMPLETION])) == NullS)
+ goto error;
+
+ et->on_completion= (ptr[0]=='D'? MYSQL_EVENT_ON_COMPLETION_DROP:
+ MYSQL_EVENT_ON_COMPLETION_PRESERVE);
+
+ et->comment.str= get_field(mem_root, table->field[EVEX_FIELD_COMMENT]);
+ if (et->comment.str != NullS)
+ et->comment.length= strlen(et->comment.str);
+ else
+ et->comment.length= 0;
+
+ DBUG_RETURN(0);
+error:
+ DBUG_RETURN(EVEX_GET_FIELD_FAILED);
+}
+
+
+/*
+ Note: In the comments this->ends is referenced as m_ends
+
+*/
+
+bool
+event_timed::compute_next_execution_time()
+{
+ TIME time_now;
+ my_time_t now;
+ int tmp;
+
+ DBUG_ENTER("event_timed::compute_next_execution_time");
+
+ if (status == MYSQL_EVENT_DISABLED)
+ {
+ DBUG_PRINT("compute_next_execution_time",
+ ("Event %s is DISABLED", name.str));
+ goto ret;
+ }
+ //if one-time no need to do computation
+ if (!expression)
+ {
+ //let's check whether it was executed
+ if (last_executed.year)
+ {
+ DBUG_PRINT("compute_next_execution_time",
+ ("One-time event %s was already executed", name.str));
+ if (on_completion == MYSQL_EVENT_ON_COMPLETION_DROP)
+ {
+ DBUG_PRINT("compute_next_execution_time",
+ ("One-time event will be dropped."));
+ dropped= true;
+ }
+ status= MYSQL_EVENT_DISABLED;
+ status_changed= true;
+ }
+ goto ret;
+ }
+ time(&now);
+ my_tz_UTC->gmt_sec_to_TIME(&time_now, now);
+/*
+ sql_print_information("[%s.%s]", dbname.str, name.str);
+ sql_print_information("time_now : [%d-%d-%d %d:%d:%d ]",
+ time_now.year, time_now.month, time_now.day,
+ time_now.hour, time_now.minute, time_now.second);
+ sql_print_information("starts : [%d-%d-%d %d:%d:%d ]", starts.year,
+ starts.month, starts.day, starts.hour,
+ starts.minute, starts.second);
+ sql_print_information("ends : [%d-%d-%d %d:%d:%d ]", ends.year,
+ ends.month, ends.day, ends.hour,
+ ends.minute, ends.second);
+ sql_print_information("m_last_ex: [%d-%d-%d %d:%d:%d ]", last_executed.year,
+ last_executed.month, last_executed.day,
+ last_executed.hour, last_executed.minute,
+ last_executed.second);
+*/
+ //if time_now is after ends don't execute anymore
+ if (ends.year && (tmp= my_time_compare(&ends, &time_now)) == -1)
+ {
+ // time_now is after ends. don't execute anymore
+ set_zero_time(&execute_at, MYSQL_TIMESTAMP_DATETIME);
+ if (on_completion == MYSQL_EVENT_ON_COMPLETION_DROP)
+ dropped= true;
+ status= MYSQL_EVENT_DISABLED;
+ status_changed= true;
+
+ goto ret;
+ }
+
+ /*
+ Here time_now is before or equals ends if the latter is set.
+ Let's check whether time_now is before starts.
+ If so schedule for starts
+ */
+ if (starts.year && (tmp= my_time_compare(&time_now, &starts)) < 1)
+ {
+ if (tmp == 0 && my_time_compare(&starts, &last_executed) == 0)
+ {
+ /*
+ time_now = starts = last_executed
+ do nothing or we will schedule for second time execution at starts.
+ */
+ }
+ else
+ {
+ /*
+ starts is in the future
+ time_now before starts. Scheduling for starts
+ */
+ execute_at= starts;
+ goto ret;
+ }
+ }
+
+ if (starts.year && ends.year)
+ {
+ /*
+ Both starts and m_ends are set and time_now is between them (incl.)
+ If last_executed is set then increase with m_expression. The new TIME is
+ after m_ends set execute_at to 0. And check for on_completion
+ If not set then schedule for now.
+ */
+ if (!last_executed.year)
+ execute_at= time_now;
+ else
+ {
+ my_time_t last, ll_ends;
+
+ // There was previous execution
+ last= sec_since_epoch_TIME(&last_executed) + expression;
+ ll_ends= sec_since_epoch_TIME(&ends);
+ //now convert back to TIME
+ //ToDo Andrey: maybe check for error here?
+ if (ll_ends < last)
+ {
+ // Next execution after ends. No more executions
+ set_zero_time(&execute_at, MYSQL_TIMESTAMP_DATETIME);
+ if (on_completion == MYSQL_EVENT_ON_COMPLETION_DROP)
+ dropped= true;
+ }
+ else
+ my_tz_UTC->gmt_sec_to_TIME(&execute_at, last);
+ }
+ goto ret;
+ }
+ else if (!starts.year && !ends.year)
+ {
+ // both starts and m_ends are not set, se we schedule for the next
+ // based on last_executed
+ if (!last_executed.year)
+ //last_executed not set. Schedule the event for now
+ execute_at= time_now;
+ else
+ //ToDo Andrey: maybe check for error here?
+ my_tz_UTC->gmt_sec_to_TIME(&execute_at,
+ sec_since_epoch_TIME(&last_executed) + expression);
+ goto ret;
+ }
+ else
+ {
+ //either starts or m_ends is set
+ if (starts.year)
+ {
+ /*
+ - starts is set.
+ - starts is not in the future according to check made before
+ Hence schedule for starts + m_expression in case last_executed
+ is not set, otherwise to last_executed + m_expression
+ */
+ my_time_t last;
+
+ //convert either last_executed or starts to seconds
+ if (last_executed.year)
+ last= sec_since_epoch_TIME(&last_executed) + expression;
+ else
+ last= sec_since_epoch_TIME(&starts);
+
+ //now convert back to TIME
+ //ToDo Andrey: maybe check for error here?
+ my_tz_UTC->gmt_sec_to_TIME(&execute_at, last);
+ }
+ else
+ {
+ /*
+ - m_ends is set
+ - m_ends is after time_now or is equal
+ Hence check for m_last_execute and increment with m_expression.
+ If last_executed is not set then schedule for now
+ */
+ my_time_t last, ll_ends;
+
+ if (!last_executed.year)
+ execute_at= time_now;
+ else
+ {
+ last= sec_since_epoch_TIME(&last_executed);
+ ll_ends= sec_since_epoch_TIME(&ends);
+ last+= expression;
+ //now convert back to TIME
+ //ToDo Andrey: maybe check for error here?
+ if (ll_ends < last)
+ {
+ set_zero_time(&execute_at, MYSQL_TIMESTAMP_DATETIME);
+ if (on_completion == MYSQL_EVENT_ON_COMPLETION_DROP)
+ dropped= true;
+ }
+ else
+ my_tz_UTC->gmt_sec_to_TIME(&execute_at, last);
+ }
+ }
+ goto ret;
+ }
+ret:
+
+ DBUG_RETURN(false);
+}
+
+
+void
+event_timed::mark_last_executed()
+{
+ TIME time_now;
+ my_time_t now;
+
+ time(&now);
+ my_tz_UTC->gmt_sec_to_TIME(&time_now, now);
+
+ last_executed= time_now; // was execute_at
+#ifdef ANDREY_0
+ last_executed= execute_at;
+#endif
+ last_executed_changed= true;
+}
+
+
+bool
+event_timed::drop(THD *thd)
+{
+ return (bool) evex_drop_event(thd, this, false);
+}
+
+
+bool
+event_timed::update_fields(THD *thd)
+{
+ TABLE *table;
+ Open_tables_state backup;
+ int ret= 0;
+ bool opened;
+
+ DBUG_ENTER("event_timed::update_time_fields");
+
+ DBUG_PRINT("enter", ("name: %*s", name.length, name.str));
+
+ //no need to update if nothing has changed
+ if (!(status_changed || last_executed_changed))
+ goto done;
+
+ thd->reset_n_backup_open_tables_state(&backup);
+
+ if (!(table= evex_open_event_table(thd, TL_WRITE)))
+ {
+ ret= SP_OPEN_TABLE_FAILED;
+ goto done;
+ }
+
+
+ if ((ret= evex_db_find_event_aux(thd, dbname, name, table)))
+ goto done;
+
+ store_record(table,record[1]);
+ table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; // Don't update create on row update.
+
+ if (last_executed_changed)
+ {
+ table->field[EVEX_FIELD_LAST_EXECUTED]->set_notnull();
+ table->field[EVEX_FIELD_LAST_EXECUTED]->store_time(&last_executed,
+ MYSQL_TIMESTAMP_DATETIME);
+ last_executed_changed= false;
+ }
+ if (status_changed)
+ {
+ table->field[EVEX_FIELD_STATUS]->set_notnull();
+ table->field[EVEX_FIELD_STATUS]->store((longlong)status);
+ status_changed= false;
+ }
+
+ if ((table->file->update_row(table->record[1],table->record[0])))
+ ret= EVEX_WRITE_ROW_FAILED;
+
+done:
+ close_thread_tables(thd);
+ thd->restore_backup_open_tables_state(&backup);
+
+ DBUG_RETURN(ret);
+}
+
+
+char *
+event_timed::get_show_create_event(THD *thd, uint *length)
+{
+ char *dst, *ret;
+ uint len, tmp_len;
+
+ len = strlen("CREATE EVENT ") + dbname.length + strlen(".") + name.length +
+ strlen(" ON SCHEDULE EVERY 5 MINUTE DO ") + body.length + strlen(";");
+
+ ret= dst= (char*) alloc_root(thd->mem_root, len + 1);
+ memcpy(dst, "CREATE EVENT ", tmp_len= strlen("CREATE EVENT "));
+ dst+= tmp_len;
+ memcpy(dst, dbname.str, tmp_len=dbname.length);
+ dst+= tmp_len;
+ memcpy(dst, ".", tmp_len= strlen("."));
+ dst+= tmp_len;
+ memcpy(dst, name.str, tmp_len= name.length);
+ dst+= tmp_len;
+ memcpy(dst, " ON SCHEDULE EVERY 5 MINUTE DO ",
+ tmp_len= strlen(" ON SCHEDULE EVERY 5 MINUTE DO "));
+ dst+= tmp_len;
+
+ memcpy(dst, body.str, tmp_len= body.length);
+ dst+= tmp_len;
+ memcpy(dst, ";", 1);
+ ++dst;
+ *dst= '\0';
+
+ *length= len;
+
+ sql_print_information("%d %d[%s]", len, dst-ret, ret);
+ return ret;
+}
+
+
+/*
+ Executes the event (the underlying sp_head object);
+
+ SYNOPSIS
+ evex_fill_row()
+ thd THD
+ mem_root If != NULL use it to compile the event on it
+
+ Returns
+ 0 - success
+ -100 - event in execution (parallel execution is impossible)
+ others - retcodes of sp_head::execute_procedure()
+
+*/
+
+int
+event_timed::execute(THD *thd, MEM_ROOT *mem_root)
+{
+ List<Item> empty_item_list;
+ int ret= 0;
+
+ DBUG_ENTER("event_timed::execute");
+
+ VOID(pthread_mutex_lock(&LOCK_running));
+ if (running)
+ {
+ VOID(pthread_mutex_unlock(&LOCK_running));
+ DBUG_RETURN(-100);
+ }
+ running= true;
+ VOID(pthread_mutex_unlock(&LOCK_running));
+
+ // TODO Andrey : make this as member variable and delete in destructor
+ empty_item_list.empty();
+
+ if (!sphead && (ret= compile(thd, mem_root)))
+ goto done;
+
+ ret= sphead->execute_procedure(thd, &empty_item_list);
+
+ VOID(pthread_mutex_lock(&LOCK_running));
+ running= false;
+ VOID(pthread_mutex_unlock(&LOCK_running));
+
+done:
+ // Don't cache sphead if allocated on another mem_root
+ if (mem_root && sphead)
+ {
+ delete sphead;
+ sphead= 0;
+ }
+
+ DBUG_RETURN(ret);
+}
+
+
+/*
+ Returns
+ 0 - Success
+ EVEX_COMPILE_ERROR - Error during compilation
+
+*/
+
+
+int
+event_timed::compile(THD *thd, MEM_ROOT *mem_root)
+{
+ int ret= 0;
+ MEM_ROOT *tmp_mem_root= 0;
+ LEX *old_lex= thd->lex, lex;
+ char *old_db;
+ event_timed *ett;
+ sp_name *spn;
+ char *old_query;
+ uint old_query_len;
+ st_sp_chistics *p;
+ CHARSET_INFO *old_character_set_client, *old_collation_connection,
+ *old_character_set_results;
+
+ old_character_set_client= thd->variables.character_set_client;
+ old_character_set_results= thd->variables.character_set_results;
+ old_collation_connection= thd->variables.collation_connection;
+
+ thd->variables.character_set_client=
+ thd->variables.character_set_results=
+ thd->variables.collation_connection=
+ get_charset_by_csname("utf8", MY_CS_PRIMARY, MYF(MY_WME));
+
+ thd->update_charset();
+
+ DBUG_ENTER("event_timed::compile");
+ // change the memory root for the execution time
+ if (mem_root)
+ {
+ tmp_mem_root= thd->mem_root;
+ thd->mem_root= mem_root;
+ }
+ old_query_len= thd->query_length;
+ old_query= thd->query;
+ old_db= thd->db;
+ thd->db= dbname.str;
+ thd->query= get_show_create_event(thd, &thd->query_length);
+ DBUG_PRINT("event_timed::compile", ("query:%s",thd->query));
+
+ thd->lex= &lex;
+ lex_start(thd, (uchar*)thd->query, thd->query_length);
+ lex.et_compile_phase= TRUE;
+ if (yyparse((void *)thd) || thd->is_fatal_error)
+ {
+ // Free lex associated resources
+ // QQ: Do we really need all this stuff here ?
+ if (lex.sphead)
+ {
+ if (&lex != thd->lex)
+ thd->lex->sphead->restore_lex(thd);
+ delete lex.sphead;
+ lex.sphead= 0;
+ }
+ // QQ: anything else ?
+ lex_end(&lex);
+ thd->lex= old_lex;
+
+ ret= EVEX_COMPILE_ERROR;
+ goto done;
+ }
+
+ sphead= lex.sphead;
+ sphead->m_db= dbname;
+ //copy also chistics since they will vanish otherwise we get 0x0 pointer
+ // Todo : Handle sql_mode !!
+ sphead->set_definer(definer.str, definer.length);
+ sphead->set_info(0, 0, &lex.sp_chistics, 0/*sql_mode*/);
+ sphead->optimize();
+ ret= 0;
+done:
+ delete lex.et;
+ lex_end(&lex);
+ thd->lex= old_lex;
+ thd->query= old_query;
+ thd->query_length= old_query_len;
+ thd->db= old_db;
+
+ thd->variables.character_set_client= old_character_set_client;
+ thd->variables.character_set_results= old_character_set_results;
+ thd->variables.collation_connection= old_collation_connection;
+ thd->update_charset();
+
+ /*
+ Change the memory root for the execution time.
+ */
+ if (mem_root)
+ thd->mem_root= tmp_mem_root;
+
+ DBUG_RETURN(ret);
+}
+
diff --git a/sql/lex.h b/sql/lex.h
index e3cbebf4629..41cbae0adea 100644
--- a/sql/lex.h
+++ b/sql/lex.h
@@ -74,6 +74,7 @@ static SYMBOL symbols[] = {
{ "ASC", SYM(ASC)},
{ "ASCII", SYM(ASCII_SYM)},
{ "ASENSITIVE", SYM(ASENSITIVE_SYM)},
+ { "AT", SYM(AT_SYM)},
{ "AUTHORS", SYM(AUTHORS_SYM)},
{ "AUTO_INCREMENT", SYM(AUTO_INC)},
{ "AVG", SYM(AVG_SYM)},
@@ -121,6 +122,7 @@ static SYMBOL symbols[] = {
{ "COMMIT", SYM(COMMIT_SYM)},
{ "COMMITTED", SYM(COMMITTED_SYM)},
{ "COMPACT", SYM(COMPACT_SYM)},
+ { "COMPLETION", SYM(COMPLETION_SYM)},
{ "COMPRESSED", SYM(COMPRESSED_SYM)},
{ "CONCURRENT", SYM(CONCURRENT)},
{ "CONDITION", SYM(CONDITION_SYM)},
@@ -180,13 +182,16 @@ static SYMBOL symbols[] = {
{ "ENABLE", SYM(ENABLE_SYM)},
{ "ENCLOSED", SYM(ENCLOSED)},
{ "END", SYM(END)},
+ { "ENDS", SYM(ENDS_SYM)},
{ "ENGINE", SYM(ENGINE_SYM)},
{ "ENGINES", SYM(ENGINES_SYM)},
{ "ENUM", SYM(ENUM)},
{ "ERRORS", SYM(ERRORS)},
{ "ESCAPE", SYM(ESCAPE_SYM)},
{ "ESCAPED", SYM(ESCAPED)},
+ { "EVENT", SYM(EVENT_SYM)},
{ "EVENTS", SYM(EVENTS_SYM)},
+ { "EVERY", SYM(EVERY_SYM)},
{ "EXECUTE", SYM(EXECUTE_SYM)},
{ "EXISTS", SYM(EXISTS)},
{ "EXIT", SYM(EXIT_SYM)},
@@ -384,6 +389,7 @@ static SYMBOL symbols[] = {
{ "POLYGON", SYM(POLYGON)},
{ "PRECISION", SYM(PRECISION)},
{ "PREPARE", SYM(PREPARE_SYM)},
+ { "PRESERVE", SYM(PRESERVE_SYM)},
{ "PREV", SYM(PREV_SYM)},
{ "PRIMARY", SYM(PRIMARY_SYM)},
{ "PRIVILEGES", SYM(PRIVILEGES)},
@@ -436,6 +442,7 @@ static SYMBOL symbols[] = {
{ "ROW_FORMAT", SYM(ROW_FORMAT_SYM)},
{ "RTREE", SYM(RTREE_SYM)},
{ "SAVEPOINT", SYM(SAVEPOINT_SYM)},
+ { "SCHEDULE", SYM(SCHEDULE_SYM)},
{ "SCHEMA", SYM(DATABASE)},
{ "SCHEMAS", SYM(DATABASES)},
{ "SECOND", SYM(SECOND_SYM)},
@@ -484,6 +491,7 @@ static SYMBOL symbols[] = {
{ "SSL", SYM(SSL_SYM)},
{ "START", SYM(START_SYM)},
{ "STARTING", SYM(STARTING)},
+ { "STARTS", SYM(STARTS_SYM)},
{ "STATUS", SYM(STATUS_SYM)},
{ "STOP", SYM(STOP_SYM)},
{ "STORAGE", SYM(STORAGE_SYM)},
diff --git a/sql/mysqld.cc b/sql/mysqld.cc
index f344becffde..f0461106452 100644
--- a/sql/mysqld.cc
+++ b/sql/mysqld.cc
@@ -24,6 +24,7 @@
#include "stacktrace.h"
#include "mysqld_suffix.h"
#include "mysys_err.h"
+#include "event.h"
#include "ha_myisam.h"
@@ -3505,6 +3506,8 @@ we force server id to 2, but this MySQL server will not act as a slave.");
}
}
+ init_events();
+
create_shutdown_thread();
create_maintenance_thread();
@@ -3568,6 +3571,7 @@ we force server id to 2, but this MySQL server will not act as a slave.");
clean_up(1);
wait_for_signal_thread_to_end();
clean_up_mutexes();
+ shutdown_events();
my_end(opt_endinfo ? MY_CHECK_ERROR | MY_GIVE_INFO : 0);
exit(0);
@@ -4529,7 +4533,7 @@ enum options_mysqld
OPT_MAX_BINLOG_DUMP_EVENTS, OPT_SPORADIC_BINLOG_DUMP_FAIL,
OPT_SAFE_USER_CREATE, OPT_SQL_MODE,
OPT_HAVE_NAMED_PIPE,
- OPT_DO_PSTACK, OPT_REPORT_HOST,
+ OPT_DO_PSTACK, OPT_EVENT_EXECUTOR, OPT_REPORT_HOST,
OPT_REPORT_USER, OPT_REPORT_PASSWORD, OPT_REPORT_PORT,
OPT_SHOW_SLAVE_AUTH_INFO,
OPT_SLAVE_LOAD_TMPDIR, OPT_NO_MIX_TYPE,
@@ -4807,6 +4811,9 @@ Disable with --skip-bdb (will save memory).",
(gptr*) &global_system_variables.engine_condition_pushdown,
(gptr*) &global_system_variables.engine_condition_pushdown,
0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
+ {"event-scheduler", OPT_EVENT_EXECUTOR, "Enable/disable the event scheduler.",
+ (gptr*) &opt_event_executor, (gptr*) &opt_event_executor, 0, GET_BOOL, NO_ARG,
+ 0/*default*/, 0/*min-value*/, 1/*max-value*/, 0, 0, 0},
{"exit-info", 'T', "Used for debugging; Use at your own risk!", 0, 0, 0,
GET_LONG, OPT_ARG, 0, 0, 0, 0, 0, 0},
{"external-locking", OPT_USE_LOCKING, "Use system (external) locking. With this option enabled you can run myisamchk to test (not repair) tables while the MySQL server is running.",
@@ -6031,6 +6038,7 @@ struct show_var_st status_vars[]= {
{"Bytes_sent", (char*) offsetof(STATUS_VAR, bytes_sent), SHOW_LONG_STATUS},
{"Com_admin_commands", (char*) offsetof(STATUS_VAR, com_other), SHOW_LONG_STATUS},
{"Com_alter_db", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_ALTER_DB]), SHOW_LONG_STATUS},
+ {"Com_alter_event", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_ALTER_EVENT]), SHOW_LONG_STATUS},
{"Com_alter_table", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_ALTER_TABLE]), SHOW_LONG_STATUS},
{"Com_analyze", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_ANALYZE]), SHOW_LONG_STATUS},
{"Com_backup_table", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_BACKUP_TABLE]), SHOW_LONG_STATUS},
@@ -6041,6 +6049,7 @@ struct show_var_st status_vars[]= {
{"Com_checksum", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CHECKSUM]), SHOW_LONG_STATUS},
{"Com_commit", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_COMMIT]), SHOW_LONG_STATUS},
{"Com_create_db", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CREATE_DB]), SHOW_LONG_STATUS},
+ {"Com_create_event", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CREATE_EVENT]), SHOW_LONG_STATUS},
{"Com_create_function", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CREATE_FUNCTION]), SHOW_LONG_STATUS},
{"Com_create_index", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CREATE_INDEX]), SHOW_LONG_STATUS},
{"Com_create_table", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_CREATE_TABLE]), SHOW_LONG_STATUS},
@@ -6049,6 +6058,7 @@ struct show_var_st status_vars[]= {
{"Com_delete_multi", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DELETE_MULTI]), SHOW_LONG_STATUS},
{"Com_do", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DO]), SHOW_LONG_STATUS},
{"Com_drop_db", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DROP_DB]), SHOW_LONG_STATUS},
+ {"Com_drop_event", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DROP_EVENT]), SHOW_LONG_STATUS},
{"Com_drop_function", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DROP_FUNCTION]), SHOW_LONG_STATUS},
{"Com_drop_index", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DROP_INDEX]), SHOW_LONG_STATUS},
{"Com_drop_table", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_DROP_TABLE]), SHOW_LONG_STATUS},
@@ -6090,6 +6100,7 @@ struct show_var_st status_vars[]= {
{"Com_show_collations", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_COLLATIONS]), SHOW_LONG_STATUS},
{"Com_show_column_types", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_COLUMN_TYPES]), SHOW_LONG_STATUS},
{"Com_show_create_db", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_CREATE_DB]), SHOW_LONG_STATUS},
+ {"Com_show_create_event", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_CREATE_EVENT]), SHOW_LONG_STATUS},
{"Com_show_create_table", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_CREATE]), SHOW_LONG_STATUS},
{"Com_show_databases", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_DATABASES]), SHOW_LONG_STATUS},
{"Com_show_engine_logs", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_ENGINE_LOGS]), SHOW_LONG_STATUS},
diff --git a/sql/set_var.cc b/sql/set_var.cc
index 19dc2265347..34942390170 100644
--- a/sql/set_var.cc
+++ b/sql/set_var.cc
@@ -104,6 +104,7 @@ extern ulong ndb_cache_check_time;
+extern my_bool event_executor_running_global_var;
static HASH system_variable_hash;
const char *bool_type_names[]= { "OFF", "ON", NullS };
@@ -208,6 +209,8 @@ sys_var_long_ptr sys_delayed_insert_timeout("delayed_insert_timeout",
&delayed_insert_timeout);
sys_var_long_ptr sys_delayed_queue_size("delayed_queue_size",
&delayed_queue_size);
+sys_var_event_executor sys_event_executor("event_scheduler",
+ &event_executor_running_global_var);
sys_var_long_ptr sys_expire_logs_days("expire_logs_days",
&expire_logs_days);
sys_var_bool_ptr sys_flush("flush", &myisam_flush);
@@ -666,6 +669,7 @@ struct show_var_st init_vars[]= {
{sys_div_precincrement.name,(char*) &sys_div_precincrement,SHOW_SYS},
{sys_engine_condition_pushdown.name,
(char*) &sys_engine_condition_pushdown, SHOW_SYS},
+ {sys_event_executor.name, (char*) &sys_event_executor, SHOW_SYS},
{sys_expire_logs_days.name, (char*) &sys_expire_logs_days, SHOW_SYS},
{sys_flush.name, (char*) &sys_flush, SHOW_SYS},
{sys_flush_time.name, (char*) &sys_flush_time, SHOW_SYS},
@@ -3362,6 +3366,7 @@ bool sys_var_trust_routine_creators::update(THD *thd, set_var *var)
return sys_var_bool_ptr::update(thd, var);
}
+
/****************************************************************************
Used templates
****************************************************************************/
diff --git a/sql/set_var.h b/sql/set_var.h
index 14059f7e9b7..7d2a7999ddc 100644
--- a/sql/set_var.h
+++ b/sql/set_var.h
@@ -782,6 +782,17 @@ public:
bool update(THD *thd, set_var *var);
};
+
+class sys_var_event_executor :public sys_var_bool_ptr
+{
+ /* We need a derived class only to have a warn_deprecated() */
+public:
+ sys_var_event_executor(const char *name_arg, my_bool *value_arg) :
+ sys_var_bool_ptr(name_arg, value_arg) {};
+ bool update(THD *thd, set_var *var);
+};
+
+
/****************************************************************************
Classes for parsing of the SET command
****************************************************************************/
diff --git a/sql/share/errmsg.txt b/sql/share/errmsg.txt
index f556ad9db65..abd4b746aa8 100644
--- a/sql/share/errmsg.txt
+++ b/sql/share/errmsg.txt
@@ -5721,3 +5721,33 @@ ER_DROP_PARTITION_WHEN_FK_DEFINED
swe "Kan inte ta bort en partition när en främmande nyckel är definierad på tabellen"
ER_PLUGIN_IS_NOT_LOADED
eng "Plugin '%-.64s' is not loaded"
+ER_EVENT_ALREADY_EXISTS
+ eng "Event %s already exists"
+ER_EVENT_STORE_FAILED
+ eng "Failed to store event %s. Error code %d from storage engine."
+ER_EVENT_DOES_NOT_EXIST
+ eng "Event %s does not exist"
+ER_EVENT_CANT_ALTER
+ eng "Failed to alter event %s"
+ER_EVENT_DROP_FAILED
+ eng "Failed to drop %s"
+ER_EVENT_INTERVAL_NOT_POSITIVE
+ eng "INTERVAL must be positive"
+ER_EVENT_ENDS_BEFORE_STARTS
+ eng "ENDS must be after STARTS"
+ER_EVENT_EXEC_TIME_IN_THE_PAST
+ eng "Activation (AT) time is in the past"
+ER_EVENT_OPEN_TABLE_FAILED
+ eng "Failed to open mysql.event"
+ER_EVENT_NEITHER_M_EXPR_NOR_M_AT
+ eng "No datetime expression provided"
+ER_EVENT_COL_COUNT_DOESNT_MATCH
+ eng "Column count of %s.%s is wrong. Table probably corrupted"
+ER_EVENT_CANNOT_LOAD_FROM_TABLE
+ eng "Cannot load from mysql.event. Table probably corrupted"
+ER_EVENT_CANNOT_DELETE
+ eng "Failed to delete the event from mysql.event"
+ER_EVENT_COMPILE_ERROR
+ eng "Error during compilation of event's body"
+ER_EVENT_SAME_NAME
+ eng "Same old and new event name"
diff --git a/sql/sp_head.h b/sql/sp_head.h
index 90ce53db9db..b928330e82d 100644
--- a/sql/sp_head.h
+++ b/sql/sp_head.h
@@ -130,7 +130,6 @@ public:
uint m_returns_len; // For FUNCTIONs only
uint m_returns_pack; // For FUNCTIONs only
const uchar *m_tmp_query; // Temporary pointer to sub query string
- uint m_old_cmq; // Old CLIENT_MULTI_QUERIES value
st_sp_chistics *m_chistics;
ulong m_sql_mode; // For SHOW CREATE and execution
LEX_STRING m_qname; // db.name
diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc
index 59f1e91e2e5..8613bd6b503 100644
--- a/sql/sql_acl.cc
+++ b/sql/sql_acl.cc
@@ -352,6 +352,14 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables)
if (table->s->fields <= 36 && (user.access & GRANT_ACL))
user.access|= CREATE_USER_ACL;
+
+ /*
+ if it is pre 5.1.4 privilege table then map CREATE privilege on
+ CREATE|ALTER|DROP|EXECUTE EVENT
+ */
+ if (table->s->fields <= 37 && (user.access & CREATE_ACL))
+ user.access|= EVENT_ACL;
+
user.sort= get_sort(2,user.host.hostname,user.user);
user.hostname_length= (user.host.hostname ?
(uint) strlen(user.host.hostname) : 0);
@@ -3971,13 +3979,13 @@ static const char *command_array[]=
"ALTER", "SHOW DATABASES", "SUPER", "CREATE TEMPORARY TABLES",
"LOCK TABLES", "EXECUTE", "REPLICATION SLAVE", "REPLICATION CLIENT",
"CREATE VIEW", "SHOW VIEW", "CREATE ROUTINE", "ALTER ROUTINE",
- "CREATE USER"
+ "CREATE USER", "EVENT"
};
static uint command_lengths[]=
{
6, 6, 6, 6, 6, 4, 6, 8, 7, 4, 5, 10, 5, 5, 14, 5, 23, 11, 7, 17, 18, 11, 9,
- 14, 13, 11
+ 14, 13, 11, 5
};
diff --git a/sql/sql_acl.h b/sql/sql_acl.h
index 0a9c6ba785f..44e42b961a2 100644
--- a/sql/sql_acl.h
+++ b/sql/sql_acl.h
@@ -42,6 +42,7 @@
#define CREATE_PROC_ACL (1L << 23)
#define ALTER_PROC_ACL (1L << 24)
#define CREATE_USER_ACL (1L << 25)
+#define EVENT_ACL (1L << 26)
/*
don't forget to update
1. static struct show_privileges_st sys_privileges[]
@@ -56,7 +57,7 @@
(UPDATE_ACL | SELECT_ACL | INSERT_ACL | DELETE_ACL | CREATE_ACL | DROP_ACL | \
GRANT_ACL | REFERENCES_ACL | INDEX_ACL | ALTER_ACL | CREATE_TMP_ACL | \
LOCK_TABLES_ACL | EXECUTE_ACL | CREATE_VIEW_ACL | SHOW_VIEW_ACL | \
- CREATE_PROC_ACL | ALTER_PROC_ACL)
+ CREATE_PROC_ACL | ALTER_PROC_ACL | EVENT_ACL)
#define TABLE_ACLS \
(SELECT_ACL | INSERT_ACL | UPDATE_ACL | DELETE_ACL | CREATE_ACL | DROP_ACL | \
@@ -78,7 +79,7 @@
REFERENCES_ACL | INDEX_ACL | ALTER_ACL | SHOW_DB_ACL | SUPER_ACL | \
CREATE_TMP_ACL | LOCK_TABLES_ACL | REPL_SLAVE_ACL | REPL_CLIENT_ACL | \
EXECUTE_ACL | CREATE_VIEW_ACL | SHOW_VIEW_ACL | CREATE_PROC_ACL | \
- ALTER_PROC_ACL | CREATE_USER_ACL)
+ ALTER_PROC_ACL | CREATE_USER_ACL | EVENT_ACL)
#define DEFAULT_CREATE_PROC_ACLS \
(ALTER_PROC_ACL | EXECUTE_ACL)
diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc
index 4d32e26f1b7..f2f065510da 100644
--- a/sql/sql_lex.cc
+++ b/sql/sql_lex.cc
@@ -177,7 +177,9 @@ void lex_start(THD *thd, const uchar *buf, uint length)
lex->spcont= NULL;
lex->proc_list.first= 0;
lex->query_tables_own_last= 0;
- lex->escape_used= FALSE;
+ lex->escape_used= lex->et_compile_phase= FALSE;
+
+ lex->et= NULL;
if (lex->sroutines.records)
my_hash_reset(&lex->sroutines);
diff --git a/sql/sql_lex.h b/sql/sql_lex.h
index d7ad28a95f5..fdcd785f920 100644
--- a/sql/sql_lex.h
+++ b/sql/sql_lex.h
@@ -26,6 +26,7 @@ class sp_name;
class sp_instr;
class sp_pcontext;
class partition_info;
+class event_timed;
/*
The following hack is needed because mysql_yacc.cc does not define
@@ -94,6 +95,8 @@ enum enum_sql_command {
SQLCOM_SHOW_PROC_CODE, SQLCOM_SHOW_FUNC_CODE,
SQLCOM_INSTALL_PLUGIN, SQLCOM_UNINSTALL_PLUGIN,
SQLCOM_SHOW_AUTHORS,
+ SQLCOM_CREATE_EVENT, SQLCOM_ALTER_EVENT, SQLCOM_DROP_EVENT,
+ SQLCOM_SHOW_CREATE_EVENT,
/* This should be the last !!! */
SQLCOM_END
@@ -890,6 +893,10 @@ typedef struct st_lex
uint sroutines_list_own_elements;
st_sp_chistics sp_chistics;
+
+ event_timed *et;
+ bool et_compile_phase;
+
bool only_view; /* used for SHOW CREATE TABLE/VIEW */
/*
field_list was created for view and should be removed before PS/SP
diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc
index f6e94c118af..3a729ddd9fa 100644
--- a/sql/sql_parse.cc
+++ b/sql/sql_parse.cc
@@ -25,6 +25,7 @@
#include "sp_head.h"
#include "sp.h"
#include "sp_cache.h"
+#include "event.h"
#ifdef HAVE_OPENSSL
/*
@@ -642,6 +643,9 @@ void init_update_queries(void)
uc_update_queries[SQLCOM_DROP_INDEX]=1;
uc_update_queries[SQLCOM_CREATE_VIEW]=1;
uc_update_queries[SQLCOM_DROP_VIEW]=1;
+ uc_update_queries[SQLCOM_CREATE_EVENT]=1;
+ uc_update_queries[SQLCOM_ALTER_EVENT]=1;
+ uc_update_queries[SQLCOM_DROP_EVENT]=1;
}
bool is_update_query(enum enum_sql_command command)
@@ -3667,6 +3671,60 @@ end_with_restore_list:
res=mysqld_show_create_db(thd,lex->name,&lex->create_info);
break;
}
+ case SQLCOM_CREATE_EVENT:
+ case SQLCOM_ALTER_EVENT:
+ case SQLCOM_DROP_EVENT:
+ {
+ DBUG_ASSERT(lex->et);
+ do {
+ if (! lex->et->dbname.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)))
+ break;
+
+ switch (lex->sql_command) {
+ case SQLCOM_CREATE_EVENT:
+ res= evex_create_event(thd, lex->et, (uint) lex->create_info.options);
+ break;
+ case SQLCOM_ALTER_EVENT:
+ res= evex_update_event(thd, lex->et, lex->spname);
+ break;
+ case SQLCOM_DROP_EVENT:
+ evex_drop_event(thd, lex->et, lex->drop_if_exists);
+ default:;
+ }
+ if (!res)
+ send_ok(thd, 1);
+
+ /* lex->unit.cleanup() is called outside, no need to call it here */
+ } while (0);
+ delete lex->et;
+ delete lex->sphead;
+ lex->et= 0;
+ lex->sphead= 0;
+ break;
+ }
+ case SQLCOM_SHOW_CREATE_EVENT:
+ {
+ if (check_access(thd, EVENT_ACL, lex->spname->m_db.str, 0, 0, 0,
+ is_schema_db(lex->spname->m_db.str)))
+ break;
+
+ if (lex->spname->m_name.length > NAME_LEN)
+ {
+ my_error(ER_TOO_LONG_IDENT, MYF(0), lex->spname->m_name.str);
+ goto error;
+ }
+ /* TODO : Implement it */
+ send_ok(thd, 1);
+ break;
+ }
case SQLCOM_CREATE_FUNCTION: // UDF function
{
if (check_access(thd,INSERT_ACL,"mysql",0,1,0,0))
@@ -5598,6 +5656,11 @@ void mysql_parse(THD *thd, char *inBuf, uint length)
delete thd->lex->sphead;
thd->lex->sphead= NULL;
}
+ if (thd->lex->et)
+ {
+ delete thd->lex->et;
+ thd->lex->et= NULL;
+ }
}
else
{
@@ -5633,6 +5696,11 @@ void mysql_parse(THD *thd, char *inBuf, uint length)
delete thd->lex->sphead;
thd->lex->sphead= NULL;
}
+ if (thd->lex->et)
+ {
+ delete thd->lex->et;
+ thd->lex->et= NULL;
+ }
}
thd->proc_info="freeing items";
thd->end_statement();
diff --git a/sql/sql_show.cc b/sql/sql_show.cc
index badc15f2ab5..783fad41cf1 100644
--- a/sql/sql_show.cc
+++ b/sql/sql_show.cc
@@ -147,6 +147,7 @@ static struct show_privileges_st sys_privileges[]=
{"Create user", "Server Admin", "To create new users"},
{"Delete", "Tables", "To delete existing rows"},
{"Drop", "Databases,Tables", "To drop databases, tables, and views"},
+ {"Event","Server Admin","Creation, alteration, deletion and execution of events."},
{"Execute", "Functions,Procedures", "To execute stored routines"},
{"File", "File access on server", "To read and write files on the server"},
{"Grant option", "Databases,Tables,Functions,Procedures", "To give to other users those privileges you possess"},
diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy
index c79ad97be4e..4b3e694b911 100644
--- a/sql/sql_yacc.yy
+++ b/sql/sql_yacc.yy
@@ -38,6 +38,7 @@
#include "sp_pcontext.h"
#include "sp_rcontext.h"
#include "sp.h"
+#include "event.h"
#include <myisam.h>
#include <myisammrg.h>
@@ -136,6 +137,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize);
%token ASC
%token ASCII_SYM
%token ASENSITIVE_SYM
+%token AT_SYM
%token ATAN
%token AUTHORS_SYM
%token AUTO_INC
@@ -186,6 +188,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize);
%token COMMITTED_SYM
%token COMMIT_SYM
%token COMPACT_SYM
+%token COMPLETION_SYM
%token COMPRESSED_SYM
%token CONCAT
%token CONCAT_WS
@@ -254,6 +257,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize);
%token ENCODE_SYM
%token ENCRYPT
%token END
+%token ENDS_SYM
%token ENGINES_SYM
%token ENGINE_SYM
%token ENUM
@@ -262,7 +266,9 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize);
%token ERRORS
%token ESCAPED
%token ESCAPE_SYM
+%token EVENT_SYM
%token EVENTS_SYM
+%token EVERY_SYM
%token EXECUTE_SYM
%token EXISTS
%token EXIT_SYM
@@ -488,6 +494,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize);
%token POSITION_SYM
%token PRECISION
%token PREPARE_SYM
+%token PRESERVE_SYM
%token PREV_SYM
%token PRIMARY_SYM
%token PRIVILEGES
@@ -544,6 +551,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize);
%token ROW_SYM
%token RTREE_SYM
%token SAVEPOINT_SYM
+%token SCHEDULE_SYM
%token SECOND_MICROSECOND_SYM
%token SECOND_SYM
%token SECURITY_SYM
@@ -583,6 +591,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize);
%token SSL_SYM
%token STARTING
%token START_SYM
+%token STARTS_SYM
%token STATUS_SYM
%token STD_SYM
%token STDDEV_SAMP_SYM
@@ -676,6 +685,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize);
%token YEAR_SYM
%token ZEROFILL
+
%left JOIN_SYM INNER_SYM STRAIGHT_JOIN CROSS LEFT RIGHT
/* A dummy token to force the priority of table_ref production in a join. */
%left TABLE_REF_PRIORITY
@@ -857,6 +867,12 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize);
END_OF_INPUT
%type <NONE> call sp_proc_stmts sp_proc_stmts1 sp_proc_stmt
+%type <NONE> sp_proc_stmt_statement sp_proc_stmt_return
+%type <NONE> sp_proc_stmt_if sp_proc_stmt_case_simple sp_proc_stmt_case
+%type <NONE> sp_labeled_control sp_proc_stmt_unlabeled sp_proc_stmt_leave
+%type <NONE> sp_proc_stmt_iterate sp_proc_stmt_label sp_proc_stmt_goto
+%type <NONE> sp_proc_stmt_open sp_proc_stmt_fetch sp_proc_stmt_close
+
%type <num> sp_decl_idents sp_opt_inout sp_handler_type sp_hcond_list
%type <spcondtype> sp_cond sp_hcond
%type <spblock> sp_decls sp_decl
@@ -1245,7 +1261,7 @@ create:
* stored procedure, otherwise yylex will chop it into pieces
* at each ';'.
*/
- sp->m_old_cmq= YYTHD->client_capabilities & CLIENT_MULTI_QUERIES;
+ $<ulong_num>$= YYTHD->client_capabilities & CLIENT_MULTI_QUERIES;
YYTHD->client_capabilities &= (~CLIENT_MULTI_QUERIES);
}
'('
@@ -1278,9 +1294,9 @@ create:
YYABORT;
sp->init_strings(YYTHD, lex, $3);
lex->sql_command= SQLCOM_CREATE_PROCEDURE;
+
/* Restore flag if it was cleared above */
- if (sp->m_old_cmq)
- YYTHD->client_capabilities |= CLIENT_MULTI_QUERIES;
+ YYTHD->client_capabilities |= $<ulong_num>3;
sp->restore_thd_mem_root(YYTHD);
}
| CREATE
@@ -1295,7 +1311,228 @@ create:
{
Lex->sql_command = SQLCOM_CREATE_USER;
}
- ;
+ | CREATE 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;
+
+ if (!(lex->et= new event_timed())) // implicitly calls event_timed::init()
+ YYABORT;
+
+ /*
+ 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);
+
+ if (!lex->et_compile_phase)
+ lex->et->init_name(YYTHD, $4);
+
+ lex->sphead= 0;//defensive programming
+ }
+ ON SCHEDULE_SYM ev_schedule_time
+ ev_on_completion
+ ev_status
+ ev_comment
+ DO_SYM ev_sql_stmt
+ {
+ /*
+ Restore flag if it was cleared above
+ $1 - CREATE
+ $2 - EVENT_SYM
+ $3 - opt_if_not_exists
+ $4 - sp_name
+ $5 - the block above
+ */
+ YYTHD->client_capabilities |= $<ulong_num>5;
+
+ /*
+ sql_command is set here because some rules in ev_sql_stmt
+ can overwrite it
+ */
+ Lex->sql_command= SQLCOM_CREATE_EVENT;
+ }
+ ;
+
+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, MYF(0));
+ YYABORT;
+ break;
+ }
+ }
+ }
+ 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 EVEX_BAD_PARAMS:
+ my_error(ER_EVENT_EXEC_TIME_IN_THE_PAST, MYF(0));
+ YYABORT;
+ break;
+ }
+ }
+ }
+ ;
+
+ev_status: /* empty */
+ | ENABLE_SYM
+ {
+ LEX *lex=Lex;
+ if (!lex->et_compile_phase)
+ lex->et->status= MYSQL_EVENT_ENABLED;
+ }
+ | DISABLE_SYM
+ {
+ LEX *lex=Lex;
+
+ if (!lex->et_compile_phase)
+ lex->et->status= MYSQL_EVENT_DISABLED;
+ }
+ ;
+ev_starts: /* empty */
+ | STARTS_SYM expr
+ {
+ LEX *lex= Lex;
+ if (!lex->et_compile_phase)
+ lex->et->init_starts(YYTHD, $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;
+ }
+ }
+ }
+ ;
+ev_on_completion: /* empty */
+ | ON COMPLETION_SYM PRESERVE_SYM
+ {
+ LEX *lex=Lex;
+ if (!lex->et_compile_phase)
+ lex->et->on_completion= MYSQL_EVENT_ON_COMPLETION_PRESERVE;
+ }
+ | ON COMPLETION_SYM NOT_SYM PRESERVE_SYM
+ {
+ LEX *lex=Lex;
+ if (!lex->et_compile_phase)
+ lex->et->on_completion= MYSQL_EVENT_ON_COMPLETION_DROP;
+ }
+ ;
+ev_comment: /* empty */
+ | COMMENT_SYM TEXT_STRING_sys
+ {
+ LEX *lex= Lex;
+ if (!lex->et_compile_phase)
+ {
+ lex->comment= $2;
+ lex->et->init_comment(YYTHD, &$2);
+ }
+ }
+ ;
+
+ev_sql_stmt:
+ {
+ LEX *lex= Lex;
+ sp_head *sp;
+
+ if (!(sp= new sp_head()))
+ YYABORT;
+
+ sp->reset_thd_mem_root(YYTHD);
+ sp->init(lex);
+
+ sp->m_type= TYPE_ENUM_PROCEDURE;
+ lex->sphead= sp;
+
+ bzero((char *)&lex->sp_chistics, sizeof(st_sp_chistics));
+ lex->sphead->m_chistics= &lex->sp_chistics;
+
+ lex->sphead->m_body_begin= lex->ptr;
+ if (!lex->et_compile_phase)
+ lex->et->body_begin= lex->ptr;
+ }
+ ev_sql_stmt_inner
+ {
+ LEX *lex=Lex;
+ 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);
+
+ lex->sp_chistics.suid= SP_IS_SUID;//always the definer!
+
+ if (!lex->et_compile_phase)
+ {
+ lex->et->init_body(YYTHD);
+ lex->et->init_definer(YYTHD);
+ }
+ }
+ ;
+
+ev_sql_stmt_inner:
+ sp_proc_stmt_statement
+ | sp_proc_stmt_return
+ | sp_proc_stmt_if
+ | sp_proc_stmt_case_simple
+ | sp_proc_stmt_case
+ | sp_labeled_control {}
+ | sp_proc_stmt_unlabeled
+ | sp_proc_stmt_leave
+ | sp_proc_stmt_iterate
+ | sp_proc_stmt_label
+ | sp_proc_stmt_goto
+ | sp_proc_stmt_open
+ | sp_proc_stmt_fetch
+ | sp_proc_stmt_close
+ ;
clear_privileges:
/* Nothing */
@@ -1355,7 +1592,7 @@ create_function_tail:
* stored procedure, otherwise yylex will chop it into pieces
* at each ';'.
*/
- sp->m_old_cmq= YYTHD->client_capabilities & CLIENT_MULTI_QUERIES;
+ $<ulong_num>$= YYTHD->client_capabilities & CLIENT_MULTI_QUERIES;
YYTHD->client_capabilities &= ~CLIENT_MULTI_QUERIES;
lex->sphead->m_param_begin= lex->tok_start+1;
}
@@ -1434,9 +1671,9 @@ create_function_tail:
YYABORT;
lex->sql_command= SQLCOM_CREATE_SPFUNCTION;
sp->init_strings(YYTHD, lex, lex->spname);
+
/* Restore flag if it was cleared above */
- if (sp->m_old_cmq)
- YYTHD->client_capabilities |= CLIENT_MULTI_QUERIES;
+ YYTHD->client_capabilities |= $<ulong_num>2;
sp->restore_thd_mem_root(YYTHD);
}
;
@@ -1905,6 +2142,28 @@ sp_opt_default:
;
sp_proc_stmt:
+ sp_proc_stmt_statement
+ | sp_proc_stmt_return
+ | sp_proc_stmt_if
+ | sp_proc_stmt_case_simple
+ | sp_proc_stmt_case
+ | sp_labeled_control
+ {}
+ | sp_proc_stmt_unlabeled
+ | sp_proc_stmt_leave
+ | sp_proc_stmt_iterate
+ | sp_proc_stmt_label
+ | sp_proc_stmt_goto
+ | sp_proc_stmt_open
+ | sp_proc_stmt_fetch
+ | sp_proc_stmt_close
+ ;
+
+sp_proc_stmt_if:
+ IF sp_if END IF {}
+ ;
+
+sp_proc_stmt_statement:
{
LEX *lex= Lex;
@@ -1947,7 +2206,10 @@ sp_proc_stmt:
}
sp->restore_lex(YYTHD);
}
- | RETURN_SYM
+ ;
+
+sp_proc_stmt_return:
+ RETURN_SYM
{ Lex->sphead->reset_lex(YYTHD); }
expr
{
@@ -1970,13 +2232,18 @@ sp_proc_stmt:
}
sp->restore_lex(YYTHD);
}
- | IF sp_if END IF {}
- | CASE_SYM WHEN_SYM
+ ;
+
+sp_proc_stmt_case_simple:
+ CASE_SYM WHEN_SYM
{
Lex->sphead->m_flags&= ~sp_head::IN_SIMPLE_CASE;
}
sp_case END CASE_SYM {}
- | CASE_SYM
+ ;
+
+sp_proc_stmt_case:
+ CASE_SYM
{ Lex->sphead->reset_lex(YYTHD); }
expr WHEN_SYM
{
@@ -2000,9 +2267,10 @@ sp_proc_stmt:
{
Lex->spcont->pop_pvar();
}
- | sp_labeled_control
- {}
- | { /* Unlabeled controls get a secret label. */
+ ;
+
+sp_proc_stmt_unlabeled:
+ { /* Unlabeled controls get a secret label. */
LEX *lex= Lex;
lex->spcont->push_label((char *)"", lex->sphead->instructions());
@@ -2013,7 +2281,10 @@ sp_proc_stmt:
lex->sphead->backpatch(lex->spcont->pop_label());
}
- | LEAVE_SYM label_ident
+ ;
+
+sp_proc_stmt_leave:
+ LEAVE_SYM label_ident
{
LEX *lex= Lex;
sp_head *sp = lex->sphead;
@@ -2043,7 +2314,10 @@ sp_proc_stmt:
sp->add_instr(i);
}
}
- | ITERATE_SYM label_ident
+ ;
+
+sp_proc_stmt_iterate:
+ ITERATE_SYM label_ident
{
LEX *lex= Lex;
sp_head *sp= lex->sphead;
@@ -2071,7 +2345,10 @@ sp_proc_stmt:
sp->add_instr(i);
}
}
- | LABEL_SYM IDENT
+ ;
+
+sp_proc_stmt_label:
+ LABEL_SYM IDENT
{
#ifdef SP_GOTO
LEX *lex= Lex;
@@ -2096,7 +2373,10 @@ sp_proc_stmt:
YYABORT;
#endif
}
- | GOTO_SYM IDENT
+ ;
+
+sp_proc_stmt_goto:
+ GOTO_SYM IDENT
{
#ifdef SP_GOTO
LEX *lex= Lex;
@@ -2156,7 +2436,10 @@ sp_proc_stmt:
YYABORT;
#endif
}
- | OPEN_SYM ident
+ ;
+
+sp_proc_stmt_open:
+ OPEN_SYM ident
{
LEX *lex= Lex;
sp_head *sp= lex->sphead;
@@ -2171,7 +2454,10 @@ sp_proc_stmt:
i= new sp_instr_copen(sp->instructions(), lex->spcont, offset);
sp->add_instr(i);
}
- | FETCH_SYM sp_opt_fetch_noise ident INTO
+ ;
+
+sp_proc_stmt_fetch:
+ FETCH_SYM sp_opt_fetch_noise ident INTO
{
LEX *lex= Lex;
sp_head *sp= lex->sphead;
@@ -2188,7 +2474,10 @@ sp_proc_stmt:
}
sp_fetch_list
{ }
- | CLOSE_SYM ident
+ ;
+
+sp_proc_stmt_close:
+ CLOSE_SYM ident
{
LEX *lex= Lex;
sp_head *sp= lex->sphead;
@@ -3913,8 +4202,83 @@ alter:
}
view_list_opt AS view_select view_check_option
{}
- ;
+ | ALTER EVENT_SYM 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;
+ event_timed *et;
+
+ 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->spname= 0;//defensive programming
+
+ et= new event_timed();// implicitly calls event_timed::init()
+ lex->et = et;
+ et->init_name(YYTHD, $3);
+
+ /*
+ 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;
+
+ /*
+ defensive. in sql_parse.cc it is checked whether is not null
+ and then deleted
+ */
+ lex->sphead= 0;
+ }
+ ev_on_schedule
+ ev_rename_to
+ ev_on_completion
+ ev_status
+ ev_comment
+ ev_opt_sql_stmt
+ {
+ /*
+ $1 - ALTER
+ $2 - EVENT_SYM
+ $3 - sp_name
+ $4 - the block above
+ */
+ YYTHD->client_capabilities |= $<ulong_num>4;
+
+ /*
+ sql_command is set here because some rules in ev_sql_stmt
+ can overwrite it
+ */
+ Lex->sql_command= SQLCOM_ALTER_EVENT;
+ }
+ ;
+ev_on_schedule: /* empty */
+ | ON SCHEDULE_SYM ev_schedule_time;
+
+ev_opt_sql_stmt: /* empty*/
+ | DO_SYM ev_sql_stmt;
+
+ev_rename_to: /* empty */
+ | RENAME TO_SYM sp_name
+ {
+ LEX *lex=Lex;
+ lex->spname= $3; //use lex's spname to hold the new name
+ //the original name is in the event_timed object
+ }
+ ;
+
ident_or_empty:
/* empty */ { $$= 0; }
| ident { $$= $1.str; };
@@ -6620,7 +6984,29 @@ drop:
lex->sql_command= SQLCOM_DROP_TRIGGER;
lex->spname= $3;
}
- ;
+ | DROP EVENT_SYM if_exists sp_name
+ {
+ LEX *lex=Lex;
+
+ if (lex->et)
+ {
+ // ToDo Andrey : Change the error message
+ /*
+ 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 event_timed()))
+ YYABORT;
+ lex->et->init_name(YYTHD, $4);
+
+ lex->sql_command = SQLCOM_DROP_EVENT;
+ lex->drop_if_exists= $3;
+ }
+ ;
table_list:
table_name
@@ -7272,7 +7658,12 @@ show_param:
Lex->spname= $3;
#endif
}
- ;
+ | CREATE EVENT_SYM sp_name
+ {
+ Lex->sql_command = SQLCOM_SHOW_CREATE_EVENT;
+ Lex->spname= $3;
+ };
+ ;
show_engine_param:
STATUS_SYM
@@ -8159,6 +8550,7 @@ keyword_sp:
| AGGREGATE_SYM {}
| ALGORITHM_SYM {}
| ANY_SYM {}
+ | AT_SYM {}
| AUTO_INC {}
| AVG_ROW_LENGTH {}
| AVG_SYM {}
@@ -8179,6 +8571,7 @@ keyword_sp:
| COLUMNS {}
| COMMITTED_SYM {}
| COMPACT_SYM {}
+ | COMPLETION_SYM {}
| COMPRESSED_SYM {}
| CONCURRENT {}
| CONSISTENT_SYM {}
@@ -8195,13 +8588,16 @@ keyword_sp:
| DUMPFILE {}
| DUPLICATE_SYM {}
| DYNAMIC_SYM {}
+ | ENDS_SYM {}
| ENUM {}
| ENGINE_SYM {}
| ENGINES_SYM {}
| ERRORS {}
| ESCAPE_SYM {}
+ | EVENT_SYM {}
| EVENTS_SYM {}
- | EXPANSION_SYM {}
+ | EVERY_SYM {}
+ | EXPANSION_SYM {}
| EXTENDED_SYM {}
| FAST_SYM {}
| FOUND_SYM {}
@@ -8293,6 +8689,7 @@ keyword_sp:
| PHASE_SYM {}
| POINT_SYM {}
| POLYGON {}
+ | PRESERVE_SYM {}
| PREV_SYM {}
| PRIVILEGES {}
| PROCESS {}
@@ -8322,6 +8719,7 @@ keyword_sp:
| ROW_FORMAT_SYM {}
| ROW_SYM {}
| RTREE_SYM {}
+ | SCHEDULE_SYM {}
| SECOND_SYM {}
| SERIAL_SYM {}
| SERIALIZABLE_SYM {}
@@ -8335,6 +8733,7 @@ keyword_sp:
| SQL_BUFFER_RESULT {}
| SQL_NO_CACHE_SYM {}
| SQL_THREAD {}
+ | STARTS_SYM {}
| STATUS_SYM {}
| STORAGE_SYM {}
| STRING_SYM {}
@@ -9061,6 +9460,7 @@ object_privilege:
| CREATE ROUTINE_SYM { Lex->grant |= CREATE_PROC_ACL; }
| ALTER ROUTINE_SYM { Lex->grant |= ALTER_PROC_ACL; }
| CREATE USER { Lex->grant |= CREATE_USER_ACL; }
+ | EVENT_SYM { Lex->grant |= EVENT_ACL;}
;
@@ -9711,7 +10111,7 @@ trigger_tail:
stored procedure, otherwise yylex will chop it into pieces
at each ';'.
*/
- sp->m_old_cmq= YYTHD->client_capabilities & CLIENT_MULTI_QUERIES;
+ $<ulong_num>$= YYTHD->client_capabilities & CLIENT_MULTI_QUERIES;
YYTHD->client_capabilities &= ~CLIENT_MULTI_QUERIES;
bzero((char *)&lex->sp_chistics, sizeof(st_sp_chistics));
@@ -9726,8 +10126,8 @@ trigger_tail:
lex->sql_command= SQLCOM_CREATE_TRIGGER;
sp->init_strings(YYTHD, lex, $3);
/* Restore flag if it was cleared above */
- if (sp->m_old_cmq)
- YYTHD->client_capabilities |= CLIENT_MULTI_QUERIES;
+
+ YYTHD->client_capabilities |= $<ulong_num>11;
sp->restore_thd_mem_root(YYTHD);
if (sp->is_not_allowed_in_function("trigger"))
diff --git a/sql/table.cc b/sql/table.cc
index ff29a33ef03..e4f47ccf1eb 100644
--- a/sql/table.cc
+++ b/sql/table.cc
@@ -282,7 +282,8 @@ int open_table_def(THD *thd, TABLE_SHARE *share, uint db_flags)
*/
if (share->db.length == 5 &&
!my_strcasecmp(system_charset_info, share->db.str, "mysql") &&
- !my_strcasecmp(system_charset_info, share->table_name.str, "proc"))
+ (!my_strcasecmp(system_charset_info, share->table_name.str, "proc") ||
+ !my_strcasecmp(system_charset_info, share->table_name.str, "event")))
share->system_table= 1;
error_given= 1;
}
diff --git a/sql/tztime.cc b/sql/tztime.cc
index eba2f8f4a7b..50c3cf1fe4f 100644
--- a/sql/tztime.cc
+++ b/sql/tztime.cc
@@ -807,6 +807,18 @@ sec_since_epoch(int year, int mon, int mday, int hour, int min ,int sec)
}
+ /*
+ Works like sec_since_epoch but expects TIME structure as parameter.
+*/
+
+my_time_t
+sec_since_epoch_TIME(TIME *t)
+{
+ return sec_since_epoch(t->year, t->month, t->day,
+ t->hour, t->minute, t->second);
+}
+
+
/*
Converts local time in broken down TIME representation to my_time_t
representation.
diff --git a/sql/tztime.h b/sql/tztime.h
index a168fe4fb73..2d574eadf18 100644
--- a/sql/tztime.h
+++ b/sql/tztime.h
@@ -64,6 +64,7 @@ extern Time_zone * my_tz_find(const String *name, TABLE_LIST *tz_tables);
extern Time_zone * my_tz_find_with_opening_tz_tables(THD *thd, const String *name);
extern my_bool my_tz_init(THD *org_thd, const char *default_tzname, my_bool bootstrap);
extern void my_tz_free();
+extern my_time_t sec_since_epoch_TIME(TIME *t);
extern TABLE_LIST fake_time_zone_tables_list;