summaryrefslogtreecommitdiff
path: root/sql/event_data_objects.cc
diff options
context:
space:
mode:
Diffstat (limited to 'sql/event_data_objects.cc')
-rw-r--r--sql/event_data_objects.cc1583
1 files changed, 1583 insertions, 0 deletions
diff --git a/sql/event_data_objects.cc b/sql/event_data_objects.cc
new file mode 100644
index 00000000000..0eba357b632
--- /dev/null
+++ b/sql/event_data_objects.cc
@@ -0,0 +1,1583 @@
+/* Copyright (C) 2004-2006 MySQL AB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+#define MYSQL_LEX 1
+#include "mysql_priv.h"
+#include "events.h"
+#include "event_data_objects.h"
+#include "event_db_repository.h"
+#include "sp_head.h"
+
+/**
+ @addtogroup Event_Scheduler
+ @{
+*/
+
+/*************************************************************************/
+
+/**
+ Event_creation_ctx -- creation context of events.
+*/
+
+class Event_creation_ctx :public Stored_program_creation_ctx,
+ public Sql_alloc
+{
+public:
+ static bool load_from_db(THD *thd,
+ MEM_ROOT *event_mem_root,
+ const char *db_name,
+ const char *event_name,
+ TABLE *event_tbl,
+ Stored_program_creation_ctx **ctx);
+
+public:
+ virtual Stored_program_creation_ctx *clone(MEM_ROOT *mem_root)
+ {
+ return new (mem_root)
+ Event_creation_ctx(m_client_cs, m_connection_cl, m_db_cl);
+ }
+
+protected:
+ virtual Object_creation_ctx *create_backup_ctx(THD *thd) const
+ {
+ /*
+ We can avoid usual backup/restore employed in stored programs since we
+ know that this is a top level statement and the worker thread is
+ allocated exclusively to execute this event.
+ */
+
+ return NULL;
+ }
+
+private:
+ Event_creation_ctx(CHARSET_INFO *client_cs,
+ CHARSET_INFO *connection_cl,
+ CHARSET_INFO *db_cl)
+ : Stored_program_creation_ctx(client_cs, connection_cl, db_cl)
+ { }
+};
+
+/**************************************************************************
+ Event_creation_ctx implementation.
+**************************************************************************/
+
+bool
+Event_creation_ctx::load_from_db(THD *thd,
+ MEM_ROOT *event_mem_root,
+ const char *db_name,
+ const char *event_name,
+ TABLE *event_tbl,
+ Stored_program_creation_ctx **ctx)
+{
+ /* Load character set/collation attributes. */
+
+ CHARSET_INFO *client_cs;
+ CHARSET_INFO *connection_cl;
+ CHARSET_INFO *db_cl;
+
+ bool invalid_creation_ctx= FALSE;
+
+ if (load_charset(event_mem_root,
+ event_tbl->field[ET_FIELD_CHARACTER_SET_CLIENT],
+ thd->variables.character_set_client,
+ &client_cs))
+ {
+ sql_print_warning("Event '%s'.'%s': invalid value "
+ "in column mysql.event.character_set_client.",
+ (const char *) db_name,
+ (const char *) event_name);
+
+ invalid_creation_ctx= TRUE;
+ }
+
+ if (load_collation(event_mem_root,
+ event_tbl->field[ET_FIELD_COLLATION_CONNECTION],
+ thd->variables.collation_connection,
+ &connection_cl))
+ {
+ sql_print_warning("Event '%s'.'%s': invalid value "
+ "in column mysql.event.collation_connection.",
+ (const char *) db_name,
+ (const char *) event_name);
+
+ invalid_creation_ctx= TRUE;
+ }
+
+ if (load_collation(event_mem_root,
+ event_tbl->field[ET_FIELD_DB_COLLATION],
+ NULL,
+ &db_cl))
+ {
+ sql_print_warning("Event '%s'.'%s': invalid value "
+ "in column mysql.event.db_collation.",
+ (const char *) db_name,
+ (const char *) event_name);
+
+ invalid_creation_ctx= TRUE;
+ }
+
+ /*
+ If we failed to resolve the database collation, load the default one
+ from the disk.
+ */
+
+ if (!db_cl)
+ db_cl= get_default_db_collation(thd, db_name);
+
+ /* Create the context. */
+
+ *ctx= new Event_creation_ctx(client_cs, connection_cl, db_cl);
+
+ return invalid_creation_ctx;
+}
+
+/*************************************************************************/
+
+/*
+ Initiliazes dbname and name of an Event_queue_element_for_exec
+ object
+
+ SYNOPSIS
+ Event_queue_element_for_exec::init()
+
+ RETURN VALUE
+ FALSE OK
+ TRUE Error (OOM)
+*/
+
+bool
+Event_queue_element_for_exec::init(LEX_STRING db, LEX_STRING n)
+{
+ if (!(dbname.str= my_strndup(db.str, dbname.length= db.length, MYF(MY_WME))))
+ return TRUE;
+ if (!(name.str= my_strndup(n.str, name.length= n.length, MYF(MY_WME))))
+ {
+ my_free((uchar*) dbname.str, MYF(0));
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/*
+ Destructor
+
+ SYNOPSIS
+ Event_queue_element_for_exec::~Event_queue_element_for_exec()
+*/
+
+Event_queue_element_for_exec::~Event_queue_element_for_exec()
+{
+ my_free((uchar*) dbname.str, MYF(0));
+ my_free((uchar*) name.str, MYF(0));
+}
+
+
+/*
+ Constructor
+
+ SYNOPSIS
+ Event_basic::Event_basic()
+*/
+
+Event_basic::Event_basic()
+{
+ DBUG_ENTER("Event_basic::Event_basic");
+ /* init memory root */
+ init_alloc_root(&mem_root, 256, 512);
+ dbname.str= name.str= NULL;
+ dbname.length= name.length= 0;
+ time_zone= NULL;
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Destructor
+
+ SYNOPSIS
+ Event_basic::Event_basic()
+*/
+
+Event_basic::~Event_basic()
+{
+ DBUG_ENTER("Event_basic::~Event_basic");
+ free_root(&mem_root, MYF(0));
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Short function to load a char column into a LEX_STRING
+
+ SYNOPSIS
+ Event_basic::load_string_field()
+ field_name The field( enum_events_table_field is not actually used
+ because it's unknown in event_data_objects.h)
+ fields The Field array
+ field_value The value
+*/
+
+bool
+Event_basic::load_string_fields(Field **fields, ...)
+{
+ bool ret= FALSE;
+ va_list args;
+ enum enum_events_table_field field_name;
+ LEX_STRING *field_value;
+
+ DBUG_ENTER("Event_basic::load_string_fields");
+
+ va_start(args, fields);
+ field_name= (enum enum_events_table_field) va_arg(args, int);
+ while (field_name < ET_FIELD_COUNT)
+ {
+ field_value= va_arg(args, LEX_STRING *);
+ if ((field_value->str= get_field(&mem_root, fields[field_name])) == NullS)
+ {
+ ret= TRUE;
+ break;
+ }
+ field_value->length= strlen(field_value->str);
+
+ field_name= (enum enum_events_table_field) va_arg(args, int);
+ }
+ va_end(args);
+
+ DBUG_RETURN(ret);
+}
+
+
+bool
+Event_basic::load_time_zone(THD *thd, const LEX_STRING tz_name)
+{
+ String str(tz_name.str, &my_charset_latin1);
+ time_zone= my_tz_find(thd, &str);
+
+ return (time_zone == NULL);
+}
+
+
+/*
+ Constructor
+
+ SYNOPSIS
+ Event_queue_element::Event_queue_element()
+*/
+
+Event_queue_element::Event_queue_element():
+ status_changed(FALSE), last_executed_changed(FALSE),
+ on_completion(Event_parse_data::ON_COMPLETION_DROP),
+ status(Event_parse_data::ENABLED), expression(0), dropped(FALSE),
+ execution_count(0)
+{
+ DBUG_ENTER("Event_queue_element::Event_queue_element");
+
+ starts= ends= execute_at= last_executed= 0;
+ starts_null= ends_null= execute_at_null= TRUE;
+
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Destructor
+
+ SYNOPSIS
+ Event_queue_element::Event_queue_element()
+*/
+Event_queue_element::~Event_queue_element()
+{
+}
+
+
+/*
+ Constructor
+
+ SYNOPSIS
+ Event_timed::Event_timed()
+*/
+
+Event_timed::Event_timed():
+ created(0), modified(0), sql_mode(0)
+{
+ DBUG_ENTER("Event_timed::Event_timed");
+ init();
+ DBUG_VOID_RETURN;
+}
+
+
+/*
+ Destructor
+
+ SYNOPSIS
+ Event_timed::~Event_timed()
+*/
+
+Event_timed::~Event_timed()
+{
+}
+
+
+/*
+ Constructor
+
+ SYNOPSIS
+ Event_job_data::Event_job_data()
+*/
+
+Event_job_data::Event_job_data()
+ :sql_mode(0)
+{
+}
+
+/*
+ Init all member variables
+
+ SYNOPSIS
+ Event_timed::init()
+*/
+
+void
+Event_timed::init()
+{
+ DBUG_ENTER("Event_timed::init");
+
+ definer_user.str= definer_host.str= body.str= comment.str= NULL;
+ definer_user.length= definer_host.length= body.length= comment.length= 0;
+
+ sql_mode= 0;
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Load an event's body from a row from mysql.event.
+
+ @details 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.
+
+ @return Operation status
+ @retval FALSE OK
+ @retval TRUE Error
+*/
+
+bool
+Event_job_data::load_from_row(THD *thd, TABLE *table)
+{
+ char *ptr;
+ size_t len;
+ LEX_STRING tz_name;
+
+ DBUG_ENTER("Event_job_data::load_from_row");
+
+ if (!table)
+ DBUG_RETURN(TRUE);
+
+ if (table->s->fields < ET_FIELD_COUNT)
+ DBUG_RETURN(TRUE);
+
+ if (load_string_fields(table->field,
+ ET_FIELD_DB, &dbname,
+ ET_FIELD_NAME, &name,
+ ET_FIELD_BODY, &body,
+ ET_FIELD_DEFINER, &definer,
+ ET_FIELD_TIME_ZONE, &tz_name,
+ ET_FIELD_COUNT))
+ DBUG_RETURN(TRUE);
+
+ if (load_time_zone(thd, tz_name))
+ DBUG_RETURN(TRUE);
+
+ Event_creation_ctx::load_from_db(thd, &mem_root, dbname.str, name.str, table,
+ &creation_ctx);
+
+ ptr= strchr(definer.str, '@');
+
+ if (! ptr)
+ ptr= definer.str;
+
+ len= ptr - definer.str;
+ definer_user.str= strmake_root(&mem_root, definer.str, len);
+ definer_user.length= len;
+ len= definer.length - len - 1;
+ /* 1:because of @ */
+ definer_host.str= strmake_root(&mem_root, ptr + 1, len);
+ definer_host.length= len;
+
+ sql_mode= (ulong) table->field[ET_FIELD_SQL_MODE]->val_int();
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Load an event's body from a row from mysql.event.
+
+ @details 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.
+
+ @return Operation status
+ @retval FALSE OK
+ @retval TRUE Error
+*/
+
+bool
+Event_queue_element::load_from_row(THD *thd, TABLE *table)
+{
+ char *ptr;
+ MYSQL_TIME time;
+ LEX_STRING tz_name;
+
+ DBUG_ENTER("Event_queue_element::load_from_row");
+
+ if (!table)
+ DBUG_RETURN(TRUE);
+
+ if (table->s->fields < ET_FIELD_COUNT)
+ DBUG_RETURN(TRUE);
+
+ if (load_string_fields(table->field,
+ ET_FIELD_DB, &dbname,
+ ET_FIELD_NAME, &name,
+ ET_FIELD_DEFINER, &definer,
+ ET_FIELD_TIME_ZONE, &tz_name,
+ ET_FIELD_COUNT))
+ DBUG_RETURN(TRUE);
+
+ if (load_time_zone(thd, tz_name))
+ DBUG_RETURN(TRUE);
+
+ starts_null= table->field[ET_FIELD_STARTS]->is_null();
+ my_bool not_used= FALSE;
+ if (!starts_null)
+ {
+ table->field[ET_FIELD_STARTS]->get_date(&time, TIME_NO_ZERO_DATE);
+ starts= my_tz_OFFSET0->TIME_to_gmt_sec(&time,&not_used);
+ }
+
+ ends_null= table->field[ET_FIELD_ENDS]->is_null();
+ if (!ends_null)
+ {
+ table->field[ET_FIELD_ENDS]->get_date(&time, TIME_NO_ZERO_DATE);
+ ends= my_tz_OFFSET0->TIME_to_gmt_sec(&time,&not_used);
+ }
+
+ if (!table->field[ET_FIELD_INTERVAL_EXPR]->is_null())
+ expression= table->field[ET_FIELD_INTERVAL_EXPR]->val_int();
+ else
+ expression= 0;
+ /*
+ If neigher STARTS and ENDS is set, then both fields are empty.
+ Hence, if ET_FIELD_EXECUTE_AT is empty there is an error.
+ */
+ execute_at_null= table->field[ET_FIELD_EXECUTE_AT]->is_null();
+ DBUG_ASSERT(!(starts_null && ends_null && !expression && execute_at_null));
+ if (!expression && !execute_at_null)
+ {
+ if (table->field[ET_FIELD_EXECUTE_AT]->get_date(&time,
+ TIME_NO_ZERO_DATE))
+ DBUG_RETURN(TRUE);
+ execute_at= my_tz_OFFSET0->TIME_to_gmt_sec(&time,&not_used);
+ }
+
+ /*
+ We load the interval type from disk as string and then map it to
+ an integer. This decouples the values of enum interval_type
+ and values actually stored on disk. Therefore the type can be
+ reordered without risking incompatibilities of data between versions.
+ */
+ if (!table->field[ET_FIELD_TRANSIENT_INTERVAL]->is_null())
+ {
+ int i;
+ char buff[MAX_FIELD_WIDTH];
+ String str(buff, sizeof(buff), &my_charset_bin);
+ LEX_STRING tmp;
+
+ table->field[ET_FIELD_TRANSIENT_INTERVAL]->val_str(&str);
+ if (!(tmp.length= str.length()))
+ DBUG_RETURN(TRUE);
+
+ tmp.str= str.c_ptr_safe();
+
+ i= find_string_in_array(interval_type_to_name, &tmp, system_charset_info);
+ if (i < 0)
+ DBUG_RETURN(TRUE);
+ interval= (interval_type) i;
+ }
+
+ if (!table->field[ET_FIELD_LAST_EXECUTED]->is_null())
+ {
+ table->field[ET_FIELD_LAST_EXECUTED]->get_date(&time,
+ TIME_NO_ZERO_DATE);
+ last_executed= my_tz_OFFSET0->TIME_to_gmt_sec(&time,&not_used);
+ }
+ last_executed_changed= FALSE;
+
+ if ((ptr= get_field(&mem_root, table->field[ET_FIELD_STATUS])) == NullS)
+ DBUG_RETURN(TRUE);
+
+ DBUG_PRINT("load_from_row", ("Event [%s] is [%s]", name.str, ptr));
+
+ /* Set event status (ENABLED | SLAVESIDE_DISABLED | DISABLED) */
+ switch (ptr[0])
+ {
+ case 'E' :
+ status = Event_parse_data::ENABLED;
+ break;
+ case 'S' :
+ status = Event_parse_data::SLAVESIDE_DISABLED;
+ break;
+ case 'D' :
+ default:
+ status = Event_parse_data::DISABLED;
+ break;
+ }
+ if ((ptr= get_field(&mem_root, table->field[ET_FIELD_ORIGINATOR])) == NullS)
+ DBUG_RETURN(TRUE);
+ originator = table->field[ET_FIELD_ORIGINATOR]->val_int();
+
+ /* ToDo : Andrey . Find a way not to allocate ptr on event_mem_root */
+ if ((ptr= get_field(&mem_root,
+ table->field[ET_FIELD_ON_COMPLETION])) == NullS)
+ DBUG_RETURN(TRUE);
+
+ on_completion= (ptr[0]=='D'? Event_parse_data::ON_COMPLETION_DROP:
+ Event_parse_data::ON_COMPLETION_PRESERVE);
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Load an event's body from a row from mysql.event.
+
+ @details 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.
+
+ @return Operation status
+ @retval FALSE OK
+ @retval TRUE Error
+*/
+
+bool
+Event_timed::load_from_row(THD *thd, TABLE *table)
+{
+ char *ptr;
+ size_t len;
+
+ DBUG_ENTER("Event_timed::load_from_row");
+
+ if (Event_queue_element::load_from_row(thd, table))
+ DBUG_RETURN(TRUE);
+
+ if (load_string_fields(table->field,
+ ET_FIELD_BODY, &body,
+ ET_FIELD_BODY_UTF8, &body_utf8,
+ ET_FIELD_COUNT))
+ DBUG_RETURN(TRUE);
+
+ if (Event_creation_ctx::load_from_db(thd, &mem_root, dbname.str, name.str,
+ table, &creation_ctx))
+ {
+ push_warning_printf(thd,
+ MYSQL_ERROR::WARN_LEVEL_WARN,
+ ER_EVENT_INVALID_CREATION_CTX,
+ ER(ER_EVENT_INVALID_CREATION_CTX),
+ (const char *) dbname.str,
+ (const char *) name.str);
+ }
+
+ ptr= strchr(definer.str, '@');
+
+ if (! ptr)
+ ptr= definer.str;
+
+ len= ptr - definer.str;
+ definer_user.str= strmake_root(&mem_root, definer.str, len);
+ definer_user.length= len;
+ len= definer.length - len - 1;
+ /* 1:because of @ */
+ definer_host.str= strmake_root(&mem_root, ptr + 1, len);
+ definer_host.length= len;
+
+ created= table->field[ET_FIELD_CREATED]->val_int();
+ modified= table->field[ET_FIELD_MODIFIED]->val_int();
+
+ comment.str= get_field(&mem_root, table->field[ET_FIELD_COMMENT]);
+ if (comment.str != NullS)
+ comment.length= strlen(comment.str);
+ else
+ comment.length= 0;
+
+ sql_mode= (ulong) table->field[ET_FIELD_SQL_MODE]->val_int();
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ add_interval() adds a specified interval to time 'ltime' in time
+ zone 'time_zone', and returns the result converted to the number of
+ seconds since epoch (aka Unix time; in UTC time zone). Zero result
+ means an error.
+*/
+static
+my_time_t
+add_interval(MYSQL_TIME *ltime, const Time_zone *time_zone,
+ interval_type scale, INTERVAL interval)
+{
+ if (date_add_interval(ltime, scale, interval))
+ return 0;
+
+ my_bool not_used;
+ return time_zone->TIME_to_gmt_sec(ltime, &not_used);
+}
+
+
+/*
+ Computes the sum of a timestamp plus interval.
+
+ SYNOPSIS
+ get_next_time()
+ time_zone event time zone
+ next the sum
+ start add interval_value to this time
+ time_now current time
+ i_value quantity of time type interval to add
+ i_type type of interval to add (SECOND, MINUTE, HOUR, WEEK ...)
+
+ RETURN VALUE
+ 0 OK
+ 1 Error
+
+ NOTES
+ 1) If the interval is conversible to SECOND, like MINUTE, HOUR, DAY, WEEK.
+ Then we use TIMEDIFF()'s implementation as underlying and number of
+ seconds as resolution for computation.
+ 2) In all other cases - MONTH, QUARTER, YEAR we use MONTH as resolution
+ and PERIOD_DIFF()'s implementation
+*/
+
+static
+bool get_next_time(const Time_zone *time_zone, my_time_t *next,
+ my_time_t start, my_time_t time_now,
+ int i_value, interval_type i_type)
+{
+ DBUG_ENTER("get_next_time");
+ DBUG_PRINT("enter", ("start: %lu now: %lu", (long) start, (long) time_now));
+
+ DBUG_ASSERT(start <= time_now);
+
+ longlong months=0, seconds=0;
+
+ switch (i_type) {
+ case INTERVAL_YEAR:
+ months= i_value*12;
+ break;
+ case INTERVAL_QUARTER:
+ /* Has already been converted to months */
+ case INTERVAL_YEAR_MONTH:
+ case INTERVAL_MONTH:
+ months= i_value;
+ break;
+ case INTERVAL_WEEK:
+ /* WEEK has already been converted to days */
+ case INTERVAL_DAY:
+ seconds= i_value*24*3600;
+ break;
+ case INTERVAL_DAY_HOUR:
+ case INTERVAL_HOUR:
+ seconds= i_value*3600;
+ break;
+ case INTERVAL_DAY_MINUTE:
+ case INTERVAL_HOUR_MINUTE:
+ case INTERVAL_MINUTE:
+ seconds= i_value*60;
+ break;
+ case INTERVAL_DAY_SECOND:
+ case INTERVAL_HOUR_SECOND:
+ case INTERVAL_MINUTE_SECOND:
+ case INTERVAL_SECOND:
+ seconds= i_value;
+ break;
+ case INTERVAL_DAY_MICROSECOND:
+ case INTERVAL_HOUR_MICROSECOND:
+ case INTERVAL_MINUTE_MICROSECOND:
+ case INTERVAL_SECOND_MICROSECOND:
+ case INTERVAL_MICROSECOND:
+ /*
+ We should return an error here so SHOW EVENTS/ SELECT FROM I_S.EVENTS
+ would give an error then.
+ */
+ DBUG_RETURN(1);
+ break;
+ case INTERVAL_LAST:
+ DBUG_ASSERT(0);
+ }
+ DBUG_PRINT("info", ("seconds: %ld months: %ld", (long) seconds, (long) months));
+
+ MYSQL_TIME local_start;
+ MYSQL_TIME local_now;
+
+ /* Convert times from UTC to local. */
+ {
+ time_zone->gmt_sec_to_TIME(&local_start, start);
+ time_zone->gmt_sec_to_TIME(&local_now, time_now);
+ }
+
+ INTERVAL interval;
+ bzero(&interval, sizeof(interval));
+ my_time_t next_time= 0;
+
+ if (seconds)
+ {
+ longlong seconds_diff;
+ long microsec_diff;
+ bool negative= calc_time_diff(&local_now, &local_start, 1,
+ &seconds_diff, &microsec_diff);
+ if (!negative)
+ {
+ /*
+ The formula below returns the interval that, when added to
+ local_start, will always give the time in the future.
+ */
+ interval.second= seconds_diff - seconds_diff % seconds + seconds;
+ next_time= add_interval(&local_start, time_zone,
+ INTERVAL_SECOND, interval);
+ if (next_time == 0)
+ goto done;
+ }
+
+ if (next_time <= time_now)
+ {
+ /*
+ If 'negative' is true above, then 'next_time == 0', and
+ 'next_time <= time_now' is also true. If negative is false,
+ then next_time was set, but perhaps to the value that is less
+ then time_now. See below for elaboration.
+ */
+ DBUG_ASSERT(negative || next_time > 0);
+
+ /*
+ If local_now < local_start, i.e. STARTS time is in the future
+ according to the local time (it always in the past according
+ to UTC---this is a prerequisite of this function), then
+ STARTS is almost always in the past according to the local
+ time too. However, in the time zone that has backward
+ Daylight Saving Time shift, the following may happen: suppose
+ we have a backward DST shift at certain date after 2:59:59,
+ i.e. local time goes 1:59:59, 2:00:00, ... , 2:59:59, (shift
+ here) 2:00:00 (again), ... , 2:59:59 (again), 3:00:00, ... .
+ Now suppose the time has passed the first 2:59:59, has been
+ shifted backward, and now is (the second) 2:20:00. The user
+ does CREATE EVENT with STARTS 'current-date 2:40:00'. Local
+ time 2:40:00 from create statement is treated by time
+ functions as the first such time, so according to UTC it comes
+ before the second 2:20:00. But according to local time it is
+ obviously in the future, so we end up in this branch.
+
+ Since we are in the second pass through 2:00:00--2:59:59, and
+ any local time form this interval is treated by system
+ functions as the time from the first pass, we have to find the
+ time for the next execution that is past the DST-affected
+ interval (past the second 2:59:59 for our example,
+ i.e. starting from 3:00:00). We do this in the loop until the
+ local time is mapped onto future UTC time. 'start' time is in
+ the past, so we may use 'do { } while' here, and add the first
+ interval right away.
+
+ Alternatively, it could be that local_now >= local_start. Now
+ for the example above imagine we do CREATE EVENT with STARTS
+ 'current-date 2:10:00'. Local start 2:10 is in the past (now
+ is local 2:20), so we add an interval, and get next execution
+ time, say, 2:40. It is in the future according to local time,
+ but, again, since we are in the second pass through
+ 2:00:00--2:59:59, 2:40 will be converted into UTC time in the
+ past. So we will end up in this branch again, and may add
+ intervals in a 'do { } while' loop.
+
+ Note that for any given event we may end up here only if event
+ next execution time will map to the time interval that is
+ passed twice, and only if the server was started during the
+ second pass, or the event is being created during the second
+ pass. After that, we never will get here (unless we again
+ start the server during the second pass). In other words,
+ such a condition is extremely rare.
+ */
+ interval.second= seconds;
+ do
+ {
+ next_time= add_interval(&local_start, time_zone,
+ INTERVAL_SECOND, interval);
+ if (next_time == 0)
+ goto done;
+ }
+ while (next_time <= time_now);
+ }
+ }
+ else
+ {
+ long diff_months= (long) (local_now.year - local_start.year)*12 +
+ (local_now.month - local_start.month);
+ /*
+ Unlike for seconds above, the formula below returns the interval
+ that, when added to the local_start, will give the time in the
+ past, or somewhere in the current month. We are interested in
+ the latter case, to see if this time has already passed, or is
+ yet to come this month.
+
+ Note that the time is guaranteed to be in the past unless
+ (diff_months % months == 0), but no good optimization is
+ possible here, because (diff_months % months == 0) is what will
+ happen most of the time, as get_next_time() will be called right
+ after the execution of the event. We could pass last_executed
+ time to this function, and see if the execution has already
+ happened this month, but for that we will have to convert
+ last_executed from seconds since epoch to local broken-down
+ time, and this will greatly reduce the effect of the
+ optimization. So instead we keep the code simple and clean.
+ */
+ interval.month= (ulong) (diff_months - diff_months % months);
+ next_time= add_interval(&local_start, time_zone,
+ INTERVAL_MONTH, interval);
+ if (next_time == 0)
+ goto done;
+
+ if (next_time <= time_now)
+ {
+ interval.month= (ulong) months;
+ next_time= add_interval(&local_start, time_zone,
+ INTERVAL_MONTH, interval);
+ if (next_time == 0)
+ goto done;
+ }
+ }
+
+ DBUG_ASSERT(time_now < next_time);
+
+ *next= next_time;
+
+done:
+ DBUG_PRINT("info", ("next_time: %ld", (long) next_time));
+ DBUG_RETURN(next_time == 0);
+}
+
+
+/*
+ Computes next execution time.
+
+ SYNOPSIS
+ Event_queue_element::compute_next_execution_time()
+
+ RETURN VALUE
+ FALSE OK
+ TRUE Error
+
+ NOTES
+ The time is set in execute_at, if no more executions the latter is
+ set to 0.
+*/
+
+bool
+Event_queue_element::compute_next_execution_time()
+{
+ my_time_t time_now;
+ DBUG_ENTER("Event_queue_element::compute_next_execution_time");
+ DBUG_PRINT("enter", ("starts: %lu ends: %lu last_executed: %lu this: 0x%lx",
+ (long) starts, (long) ends, (long) last_executed,
+ (long) this));
+
+ if (status != Event_parse_data::ENABLED)
+ {
+ 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)
+ {
+ DBUG_PRINT("info",("One-time event %s.%s of was already executed",
+ dbname.str, name.str));
+ dropped= (on_completion == Event_parse_data::ON_COMPLETION_DROP);
+ DBUG_PRINT("info",("One-time event will be dropped: %d.", dropped));
+
+ status= Event_parse_data::DISABLED;
+ status_changed= TRUE;
+ }
+ goto ret;
+ }
+
+ time_now= (my_time_t) current_thd->query_start();
+
+ DBUG_PRINT("info",("NOW: [%lu]", (ulong) time_now));
+
+ /* if time_now is after ends don't execute anymore */
+ if (!ends_null && ends < time_now)
+ {
+ DBUG_PRINT("info", ("NOW after ENDS, don't execute anymore"));
+ /* time_now is after ends. don't execute anymore */
+ execute_at= 0;
+ execute_at_null= TRUE;
+ if (on_completion == Event_parse_data::ON_COMPLETION_DROP)
+ dropped= TRUE;
+ DBUG_PRINT("info", ("Dropped: %d", dropped));
+ status= Event_parse_data::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_null && time_now <= starts)
+ {
+ if (time_now == starts && starts == last_executed)
+ {
+ /*
+ do nothing or we will schedule for second time execution at starts.
+ */
+ }
+ else
+ {
+ DBUG_PRINT("info", ("STARTS is future, NOW <= STARTS,sched for STARTS"));
+ /*
+ starts is in the future
+ time_now before starts. Scheduling for starts
+ */
+ execute_at= starts;
+ execute_at_null= FALSE;
+ goto ret;
+ }
+ }
+
+ if (!starts_null && !ends_null)
+ {
+ /*
+ 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 MYSQL_TIME is
+ after m_ends set execute_at to 0. And check for on_completion
+ If not set then schedule for now.
+ */
+ DBUG_PRINT("info", ("Both STARTS & ENDS are set"));
+ if (!last_executed)
+ {
+ DBUG_PRINT("info", ("Not executed so far."));
+ }
+
+ {
+ my_time_t next_exec;
+
+ if (get_next_time(time_zone, &next_exec, starts, time_now,
+ (int) expression, interval))
+ goto err;
+
+ /* There was previous execution */
+ if (ends < next_exec)
+ {
+ DBUG_PRINT("info", ("Next execution of %s after ENDS. Stop executing.",
+ name.str));
+ /* Next execution after ends. No more executions */
+ execute_at= 0;
+ execute_at_null= TRUE;
+ if (on_completion == Event_parse_data::ON_COMPLETION_DROP)
+ dropped= TRUE;
+ status= Event_parse_data::DISABLED;
+ status_changed= TRUE;
+ }
+ else
+ {
+ DBUG_PRINT("info",("Next[%lu]", (ulong) next_exec));
+ execute_at= next_exec;
+ execute_at_null= FALSE;
+ }
+ }
+ goto ret;
+ }
+ else if (starts_null && ends_null)
+ {
+ /* starts is always set, so this is a dead branch !! */
+ DBUG_PRINT("info", ("Neither STARTS nor ENDS are set"));
+ /*
+ Both starts and m_ends are not set, so we schedule for the next
+ based on last_executed.
+ */
+ if (last_executed)
+ {
+ my_time_t next_exec;
+ if (get_next_time(time_zone, &next_exec, starts, time_now,
+ (int) expression, interval))
+ goto err;
+ execute_at= next_exec;
+ DBUG_PRINT("info",("Next[%lu]", (ulong) next_exec));
+ }
+ else
+ {
+ /* last_executed not set. Schedule the event for now */
+ DBUG_PRINT("info", ("Execute NOW"));
+ execute_at= time_now;
+ }
+ execute_at_null= FALSE;
+ }
+ else
+ {
+ /* either starts or m_ends is set */
+ if (!starts_null)
+ {
+ DBUG_PRINT("info", ("STARTS is set"));
+ /*
+ - 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
+ */
+ if (!last_executed)
+ {
+ DBUG_PRINT("info", ("Not executed so far."));
+ }
+
+ {
+ my_time_t next_exec;
+ if (get_next_time(time_zone, &next_exec, starts, time_now,
+ (int) expression, interval))
+ goto err;
+ execute_at= next_exec;
+ DBUG_PRINT("info",("Next[%lu]", (ulong) next_exec));
+ }
+ execute_at_null= FALSE;
+ }
+ else
+ {
+ /* this is a dead branch, because starts is always set !!! */
+ DBUG_PRINT("info", ("STARTS is not set. ENDS is set"));
+ /*
+ - 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
+ */
+
+ if (!last_executed)
+ execute_at= time_now;
+ else
+ {
+ my_time_t next_exec;
+
+ if (get_next_time(time_zone, &next_exec, starts, time_now,
+ (int) expression, interval))
+ goto err;
+
+ if (ends < next_exec)
+ {
+ DBUG_PRINT("info", ("Next execution after ENDS. Stop executing."));
+ execute_at= 0;
+ execute_at_null= TRUE;
+ status= Event_parse_data::DISABLED;
+ status_changed= TRUE;
+ if (on_completion == Event_parse_data::ON_COMPLETION_DROP)
+ dropped= TRUE;
+ }
+ else
+ {
+ DBUG_PRINT("info", ("Next[%lu]", (ulong) next_exec));
+ execute_at= next_exec;
+ execute_at_null= FALSE;
+ }
+ }
+ }
+ goto ret;
+ }
+ret:
+ DBUG_PRINT("info", ("ret: 0 execute_at: %lu", (long) execute_at));
+ DBUG_RETURN(FALSE);
+err:
+ DBUG_PRINT("info", ("ret=1"));
+ DBUG_RETURN(TRUE);
+}
+
+
+/*
+ Set the internal last_executed MYSQL_TIME struct to now. NOW is the
+ time according to thd->query_start(), so the THD's clock.
+
+ SYNOPSIS
+ Event_queue_element::mark_last_executed()
+ thd thread context
+*/
+
+void
+Event_queue_element::mark_last_executed(THD *thd)
+{
+ last_executed= (my_time_t) thd->query_start();
+ last_executed_changed= TRUE;
+
+ execution_count++;
+}
+
+
+/*
+ Saves status and last_executed_at to the disk if changed.
+
+ SYNOPSIS
+ Event_queue_element::update_timing_fields()
+ thd - thread context
+
+ RETURN VALUE
+ FALSE OK
+ TRUE Error while opening mysql.event for writing or during
+ write on disk
+*/
+
+bool
+Event_queue_element::update_timing_fields(THD *thd)
+{
+ Event_db_repository *db_repository= Events::get_db_repository();
+ int ret;
+
+ DBUG_ENTER("Event_queue_element::update_timing_fields");
+
+ DBUG_PRINT("enter", ("name: %*s", (int) name.length, name.str));
+
+ /* No need to update if nothing has changed */
+ if (!(status_changed || last_executed_changed))
+ DBUG_RETURN(0);
+
+ ret= db_repository->update_timing_fields_for_event(thd,
+ dbname, name,
+ last_executed_changed,
+ last_executed,
+ status_changed,
+ (ulonglong) status);
+ last_executed_changed= status_changed= FALSE;
+ DBUG_RETURN(ret);
+}
+
+
+static
+void
+append_datetime(String *buf, Time_zone *time_zone, my_time_t secs,
+ const char *name, uint len)
+{
+ char dtime_buff[20*2+32];/* +32 to make my_snprintf_{8bit|ucs2} happy */
+ buf->append(STRING_WITH_LEN(" "));
+ buf->append(name, len);
+ buf->append(STRING_WITH_LEN(" '"));
+ /*
+ Pass the buffer and the second param tells fills the buffer and
+ returns the number of chars to copy.
+ */
+ MYSQL_TIME time;
+ time_zone->gmt_sec_to_TIME(&time, secs);
+ buf->append(dtime_buff, my_datetime_to_str(&time, dtime_buff));
+ buf->append(STRING_WITH_LEN("'"));
+}
+
+
+/*
+ Get SHOW CREATE EVENT as string
+
+ SYNOPSIS
+ Event_timed::get_create_event(THD *thd, String *buf)
+ thd Thread
+ buf String*, should be already allocated. CREATE EVENT goes inside.
+
+ RETURN VALUE
+ 0 OK
+ EVEX_MICROSECOND_UNSUP Error (for now if mysql.event has been
+ tampered and MICROSECONDS interval or
+ derivative has been put there.
+*/
+
+int
+Event_timed::get_create_event(THD *thd, String *buf)
+{
+ char tmp_buf[2 * STRING_BUFFER_USUAL_SIZE];
+ String expr_buf(tmp_buf, sizeof(tmp_buf), system_charset_info);
+ expr_buf.length(0);
+
+ DBUG_ENTER("get_create_event");
+ DBUG_PRINT("ret_info",("body_len=[%d]body=[%s]",
+ (int) body.length, body.str));
+
+ if (expression && Events::reconstruct_interval_expression(&expr_buf, interval,
+ expression))
+ DBUG_RETURN(EVEX_MICROSECOND_UNSUP);
+
+ buf->append(STRING_WITH_LEN("CREATE EVENT "));
+ append_identifier(thd, buf, name.str, name.length);
+
+ if (expression)
+ {
+ buf->append(STRING_WITH_LEN(" ON SCHEDULE EVERY "));
+ buf->append(expr_buf);
+ buf->append(' ');
+ LEX_STRING *ival= &interval_type_to_name[interval];
+ buf->append(ival->str, ival->length);
+
+ if (!starts_null)
+ append_datetime(buf, time_zone, starts, STRING_WITH_LEN("STARTS"));
+
+ if (!ends_null)
+ append_datetime(buf, time_zone, ends, STRING_WITH_LEN("ENDS"));
+ }
+ else
+ {
+ append_datetime(buf, time_zone, execute_at,
+ STRING_WITH_LEN("ON SCHEDULE AT"));
+ }
+
+ if (on_completion == Event_parse_data::ON_COMPLETION_DROP)
+ buf->append(STRING_WITH_LEN(" ON COMPLETION NOT PRESERVE "));
+ else
+ buf->append(STRING_WITH_LEN(" ON COMPLETION PRESERVE "));
+
+ if (status == Event_parse_data::ENABLED)
+ buf->append(STRING_WITH_LEN("ENABLE"));
+ else if (status == Event_parse_data::SLAVESIDE_DISABLED)
+ buf->append(STRING_WITH_LEN("DISABLE ON SLAVE"));
+ else
+ buf->append(STRING_WITH_LEN("DISABLE"));
+
+ if (comment.length)
+ {
+ buf->append(STRING_WITH_LEN(" COMMENT "));
+ append_unescaped(buf, comment.str, comment.length);
+ }
+ buf->append(STRING_WITH_LEN(" DO "));
+ buf->append(body.str, body.length);
+
+ DBUG_RETURN(0);
+}
+
+
+/**
+ Get an artificial stored procedure to parse as an event definition.
+*/
+
+bool
+Event_job_data::construct_sp_sql(THD *thd, String *sp_sql)
+{
+ LEX_STRING buffer;
+ const uint STATIC_SQL_LENGTH= 44;
+
+ DBUG_ENTER("Event_job_data::construct_sp_sql");
+
+ /*
+ Allocate a large enough buffer on the thread execution memory
+ root to avoid multiple [re]allocations on system heap
+ */
+ buffer.length= STATIC_SQL_LENGTH + name.length + body.length;
+ if (! (buffer.str= (char*) thd->alloc(buffer.length)))
+ DBUG_RETURN(TRUE);
+
+ sp_sql->set(buffer.str, buffer.length, system_charset_info);
+ sp_sql->length(0);
+
+
+ sp_sql->append(C_STRING_WITH_LEN("CREATE "));
+ sp_sql->append(C_STRING_WITH_LEN("PROCEDURE "));
+ /*
+ Let's use the same name as the event name to perhaps produce a
+ better error message in case it is a part of some parse error.
+ We're using append_identifier here to successfully parse
+ events with reserved names.
+ */
+ append_identifier(thd, sp_sql, name.str, name.length);
+
+ /*
+ The default SQL security of a stored procedure is DEFINER. We
+ have already activated the security context of the event, so
+ let's execute the procedure with the invoker rights to save on
+ resets of security contexts.
+ */
+ sp_sql->append(C_STRING_WITH_LEN("() SQL SECURITY INVOKER "));
+
+ sp_sql->append(body.str, body.length);
+
+ DBUG_RETURN(thd->is_fatal_error);
+}
+
+
+/**
+ Get DROP EVENT statement to binlog the drop of ON COMPLETION NOT
+ PRESERVE event.
+*/
+
+bool
+Event_job_data::construct_drop_event_sql(THD *thd, String *sp_sql)
+{
+ LEX_STRING buffer;
+ const uint STATIC_SQL_LENGTH= 14;
+
+ DBUG_ENTER("Event_job_data::construct_drop_event_sql");
+
+ buffer.length= STATIC_SQL_LENGTH + name.length*2 + dbname.length*2;
+ if (! (buffer.str= (char*) thd->alloc(buffer.length)))
+ DBUG_RETURN(TRUE);
+
+ sp_sql->set(buffer.str, buffer.length, system_charset_info);
+ sp_sql->length(0);
+
+ sp_sql->append(C_STRING_WITH_LEN("DROP EVENT "));
+ append_identifier(thd, sp_sql, dbname.str, dbname.length);
+ sp_sql->append('.');
+ append_identifier(thd, sp_sql, name.str, name.length);
+
+ DBUG_RETURN(thd->is_fatal_error);
+}
+
+/**
+ Compiles and executes the event (the underlying sp_head object)
+
+ @retval TRUE error (reported to the error log)
+ @retval FALSE success
+*/
+
+bool
+Event_job_data::execute(THD *thd, bool drop)
+{
+ String sp_sql;
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ Security_context event_sctx, *save_sctx= NULL;
+#endif
+ List<Item> empty_item_list;
+ bool ret= TRUE;
+
+ DBUG_ENTER("Event_job_data::execute");
+
+ mysql_reset_thd_for_next_command(thd);
+
+ /*
+ MySQL parser currently assumes that current database is either
+ present in THD or all names in all statements are fully specified.
+ And yet not fully specified names inside stored programs must be
+ be supported, even if the current database is not set:
+ CREATE PROCEDURE db1.p1() BEGIN CREATE TABLE t1; END//
+ -- in this example t1 should be always created in db1 and the statement
+ must parse even if there is no current database.
+
+ To support this feature and still address the parser limitation,
+ we need to set the current database here.
+ We don't have to call mysql_change_db, since the checks performed
+ in it are unnecessary for the purpose of parsing, and
+ mysql_change_db will be invoked anyway later, to activate the
+ procedure database before it's executed.
+ */
+ thd->set_db(dbname.str, dbname.length);
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ if (event_sctx.change_security_context(thd,
+ &definer_user, &definer_host,
+ &dbname, &save_sctx))
+ {
+ sql_print_error("Event Scheduler: "
+ "[%s].[%s.%s] execution failed, "
+ "failed to authenticate the user.",
+ definer.str, dbname.str, name.str);
+ goto end_no_lex_start;
+ }
+#endif
+
+ if (check_access(thd, EVENT_ACL, dbname.str,
+ 0, 0, 0, is_schema_db(dbname.str)))
+ {
+ /*
+ This aspect of behavior is defined in the worklog,
+ and this is how triggers work too: if TRIGGER
+ privilege is revoked from trigger definer,
+ triggers are not executed.
+ */
+ sql_print_error("Event Scheduler: "
+ "[%s].[%s.%s] execution failed, "
+ "user no longer has EVENT privilege.",
+ definer.str, dbname.str, name.str);
+ goto end_no_lex_start;
+ }
+
+ if (construct_sp_sql(thd, &sp_sql))
+ goto end_no_lex_start;
+
+ /*
+ Set up global thread attributes to reflect the properties of
+ this Event. We can simply reset these instead of usual
+ backup/restore employed in stored programs since we know that
+ this is a top level statement and the worker thread is
+ allocated exclusively to execute this event.
+ */
+
+ thd->variables.sql_mode= sql_mode;
+ thd->variables.time_zone= time_zone;
+
+ /*
+ Peculiar initialization order is a crutch to avoid races in SHOW
+ PROCESSLIST which reads thd->{query/query_length} without a mutex.
+ */
+ thd->query_length= 0;
+ thd->query= sp_sql.c_ptr_safe();
+ thd->query_length= sp_sql.length();
+
+ {
+ Parser_state parser_state(thd, thd->query, thd->query_length);
+ lex_start(thd);
+
+ if (parse_sql(thd, & parser_state, creation_ctx))
+ {
+ sql_print_error("Event Scheduler: "
+ "%serror during compilation of %s.%s",
+ thd->is_fatal_error ? "fatal " : "",
+ (const char *) dbname.str, (const char *) name.str);
+ goto end;
+ }
+ }
+
+ {
+ sp_head *sphead= thd->lex->sphead;
+
+ DBUG_ASSERT(sphead);
+
+ if (thd->enable_slow_log)
+ sphead->m_flags|= sp_head::LOG_SLOW_STATEMENTS;
+ sphead->m_flags|= sp_head::LOG_GENERAL_LOG;
+
+ sphead->set_info(0, 0, &thd->lex->sp_chistics, sql_mode);
+ sphead->set_creation_ctx(creation_ctx);
+ sphead->optimize();
+
+ ret= sphead->execute_procedure(thd, &empty_item_list);
+ /*
+ There is no pre-locking and therefore there should be no
+ tables open and locked left after execute_procedure.
+ */
+ }
+
+end:
+ if (thd->lex->sphead) /* NULL only if a parse error */
+ {
+ delete thd->lex->sphead;
+ thd->lex->sphead= NULL;
+ }
+
+end_no_lex_start:
+ if (drop && !thd->is_fatal_error)
+ {
+ /*
+ We must do it here since here we're under the right authentication
+ ID of the event definer.
+ */
+ sql_print_information("Event Scheduler: Dropping %s.%s",
+ (const char *) dbname.str, (const char *) name.str);
+ /*
+ Construct a query for the binary log, to ensure the event is dropped
+ on the slave
+ */
+ if (construct_drop_event_sql(thd, &sp_sql))
+ ret= 1;
+ else
+ {
+ ulong saved_master_access;
+ /*
+ Peculiar initialization order is a crutch to avoid races in SHOW
+ PROCESSLIST which reads thd->{query/query_length} without a mutex.
+ */
+ thd->query_length= 0;
+ thd->query= sp_sql.c_ptr_safe();
+ thd->query_length= sp_sql.length();
+
+ /*
+ NOTE: even if we run in read-only mode, we should be able to lock
+ the mysql.event table for writing. In order to achieve this, we
+ should call mysql_lock_tables() under the super-user.
+ */
+
+ saved_master_access= thd->security_ctx->master_access;
+ thd->security_ctx->master_access |= SUPER_ACL;
+
+ ret= Events::drop_event(thd, dbname, name, FALSE);
+
+ thd->security_ctx->master_access= saved_master_access;
+ }
+ }
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ if (save_sctx)
+ event_sctx.restore_security_context(thd, save_sctx);
+#endif
+ lex_end(thd->lex);
+ thd->lex->unit.cleanup();
+ thd->end_statement();
+ thd->cleanup_after_query();
+ /* Avoid races with SHOW PROCESSLIST */
+ thd->query_length= 0;
+ thd->query= NULL;
+
+ DBUG_PRINT("info", ("EXECUTED %s.%s ret: %d", dbname.str, name.str, ret));
+
+ DBUG_RETURN(ret);
+}
+
+
+/*
+ Checks whether two events are in the same schema
+
+ SYNOPSIS
+ event_basic_db_equal()
+ db Schema
+ et Compare et->dbname to `db`
+
+ RETURN VALUE
+ TRUE Equal
+ FALSE Not equal
+*/
+
+bool
+event_basic_db_equal(LEX_STRING db, Event_basic *et)
+{
+ return !sortcmp_lex_string(et->dbname, db, system_charset_info);
+}
+
+
+/*
+ Checks whether an event has equal `db` and `name`
+
+ SYNOPSIS
+ event_basic_identifier_equal()
+ db Schema
+ name Name
+ et The event object
+
+ RETURN VALUE
+ TRUE Equal
+ FALSE Not equal
+*/
+
+bool
+event_basic_identifier_equal(LEX_STRING db, LEX_STRING name, Event_basic *b)
+{
+ return !sortcmp_lex_string(name, b->name, system_charset_info) &&
+ !sortcmp_lex_string(db, b->dbname, system_charset_info);
+}
+
+/**
+ @} (End of group Event_Scheduler)
+*/