summaryrefslogtreecommitdiff
path: root/sql/event_timed.cc
diff options
context:
space:
mode:
Diffstat (limited to 'sql/event_timed.cc')
-rw-r--r--sql/event_timed.cc1866
1 files changed, 1866 insertions, 0 deletions
diff --git a/sql/event_timed.cc b/sql/event_timed.cc
new file mode 100644
index 00000000000..98369e0e055
--- /dev/null
+++ b/sql/event_timed.cc
@@ -0,0 +1,1866 @@
+/* Copyright (C) 2004-2006 MySQL AB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+#define MYSQL_LEX 1
+#include "mysql_priv.h"
+#include "events_priv.h"
+#include "events.h"
+#include "event_timed.h"
+#include "sp_head.h"
+
+
+/*
+ Constructor
+
+ SYNOPSIS
+ Event_timed::Event_timed()
+*/
+
+Event_timed::Event_timed():in_spawned_thread(0),locked_by_thread_id(0),
+ running(0), thread_id(0), status_changed(false),
+ last_executed_changed(false), expression(0),
+ created(0), modified(0),
+ on_completion(Event_timed::ON_COMPLETION_DROP),
+ status(Event_timed::ENABLED), sphead(0),
+ sql_mode(0), body_begin(0), dropped(false),
+ free_sphead_on_delete(true), flags(0)
+
+{
+ pthread_mutex_init(&this->LOCK_running, MY_MUTEX_INIT_FAST);
+ pthread_cond_init(&this->COND_finished, NULL);
+ init();
+}
+
+
+/*
+ Destructor
+
+ SYNOPSIS
+ Event_timed::~Event_timed()
+*/
+
+Event_timed::~Event_timed()
+{
+ deinit_mutexes();
+
+ if (free_sphead_on_delete)
+ free_sp();
+}
+
+
+/*
+ Destructor
+
+ SYNOPSIS
+ Event_timed::~deinit_mutexes()
+*/
+
+void
+Event_timed::deinit_mutexes()
+{
+ pthread_mutex_destroy(&this->LOCK_running);
+ pthread_cond_destroy(&this->COND_finished);
+}
+
+
+/*
+ Checks whether the event is running
+
+ SYNOPSIS
+ Event_timed::is_running()
+*/
+
+bool
+Event_timed::is_running()
+{
+ bool ret;
+
+ VOID(pthread_mutex_lock(&this->LOCK_running));
+ ret= running;
+ VOID(pthread_mutex_unlock(&this->LOCK_running));
+
+ return ret;
+}
+
+
+/*
+ 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);
+ starts_null= ends_null= execute_at_null= TRUE;
+
+ definer_user.str= definer_host.str= 0;
+ definer_user.length= definer_host.length= 0;
+
+ sql_mode= 0;
+
+ 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");
+ /* 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 */
+ dbname.length= spn->m_db.length;
+ 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);
+
+ 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.
+
+ Some questionable removal of characters is done in here, and that part
+ should be refactored when the parser is smarter.
+*/
+
+void
+Event_timed::init_body(THD *thd)
+{
+ DBUG_ENTER("Event_timed::init_body");
+ DBUG_PRINT("info", ("body=[%s] body_begin=0x%ld end=0x%ld", body_begin,
+ body_begin, thd->lex->ptr));
+
+ body.length= thd->lex->ptr - body_begin;
+ const uchar *body_end= body_begin + body.length - 1;
+
+ /* Trim nuls or close-comments ('*'+'/') or spaces at the end */
+ while (body_begin < body_end)
+ {
+
+ if ((*body_end == '\0') ||
+ (my_isspace(thd->variables.character_set_client, *body_end)))
+ { /* consume NULs and meaningless whitespace */
+ --body.length;
+ --body_end;
+ continue;
+ }
+
+ /*
+ consume closing comments
+
+ This is arguably wrong, but it's the best we have until the parser is
+ changed to be smarter. FIXME PARSER
+
+ See also the sp_head code, where something like this is done also.
+
+ One idea is to keep in the lexer structure the count of the number of
+ open-comments we've entered, and scan left-to-right looking for a
+ closing comment IFF the count is greater than zero.
+
+ Another idea is to remove the closing comment-characters wholly in the
+ parser, since that's where it "removes" the opening characters.
+ */
+ if ((*(body_end - 1) == '*') && (*body_end == '/'))
+ {
+ DBUG_PRINT("info", ("consumend one '*" "/' comment in the query '%s'",
+ body_begin));
+ body.length-= 2;
+ body_end-= 2;
+ continue;
+ }
+
+ break; /* none were found, so we have excised all we can. */
+ }
+
+ /* the first is always whitespace which I cannot skip in the parser */
+ while (my_isspace(thd->variables.character_set_client, *body_begin))
+ {
+ ++body_begin;
+ --body.length;
+ }
+ body.str= strmake_root(thd->mem_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)
+
+ RETURN VALUE
+ 0 OK
+ EVEX_PARSE_ERROR fix_fields failed
+ EVEX_BAD_PARAMS datetime is in the past
+ ER_WRONG_VALUE wrong value for execute at
+*/
+
+int
+Event_timed::init_execute_at(THD *thd, Item *expr)
+{
+ my_bool not_used;
+ TIME ltime;
+ my_time_t t;
+
+ TIME time_tmp;
+ DBUG_ENTER("Event_timed::init_execute_at");
+
+ if (expr->fix_fields(thd, &expr))
+ DBUG_RETURN(EVEX_PARSE_ERROR);
+
+ /* no starts and/or ends in case of execute_at */
+ DBUG_PRINT("info", ("starts_null && ends_null should be 1 is %d",
+ (starts_null && ends_null)));
+ DBUG_ASSERT(starts_null && ends_null);
+
+ /* let's check whether time is in the past */
+ thd->variables.time_zone->gmt_sec_to_TIME(&time_tmp,
+ (my_time_t) thd->query_start());
+
+ if ((not_used= expr->get_date(&ltime, TIME_NO_ZERO_DATE)))
+ DBUG_RETURN(ER_WRONG_VALUE);
+
+ if (TIME_to_ulonglong_datetime(&ltime) <
+ TIME_to_ulonglong_datetime(&time_tmp))
+ 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.
+ mysql_priv.h currently lists
+ #define TIMESTAMP_MAX_YEAR 2038 (see TIME_to_timestamp())
+ */
+ my_tz_UTC->gmt_sec_to_TIME(&ltime,t=TIME_to_timestamp(thd,&ltime,&not_used));
+ if (!t)
+ {
+ DBUG_PRINT("error", ("Execute AT after year 2037"));
+ DBUG_RETURN(ER_WRONG_VALUE);
+ }
+
+ execute_at_null= FALSE;
+ 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
+
+ RETURN VALUE
+ 0 OK
+ EVEX_PARSE_ERROR fix_fields failed
+ EVEX_BAD_PARAMS Interval is not positive
+ EVEX_MICROSECOND_UNSUP Microseconds are not supported.
+*/
+
+int
+Event_timed::init_interval(THD *thd, Item *expr, interval_type new_interval)
+{
+ String value;
+ INTERVAL interval_tmp;
+
+ DBUG_ENTER("Event_timed::init_interval");
+
+ if (expr->fix_fields(thd, &expr))
+ DBUG_RETURN(EVEX_PARSE_ERROR);
+
+ value.alloc(MAX_DATETIME_FULL_WIDTH*MY_CHARSET_BIN_MB_MAXLEN);
+ if (get_interval_value(expr, new_interval, &value, &interval_tmp))
+ DBUG_RETURN(EVEX_PARSE_ERROR);
+
+ expression= 0;
+
+ switch (new_interval) {
+ case INTERVAL_YEAR:
+ expression= interval_tmp.year;
+ break;
+ case INTERVAL_QUARTER:
+ case INTERVAL_MONTH:
+ expression= interval_tmp.month;
+ break;
+ case INTERVAL_WEEK:
+ case INTERVAL_DAY:
+ expression= interval_tmp.day;
+ break;
+ case INTERVAL_HOUR:
+ expression= interval_tmp.hour;
+ break;
+ case INTERVAL_MINUTE:
+ expression= interval_tmp.minute;
+ break;
+ case INTERVAL_SECOND:
+ expression= interval_tmp.second;
+ break;
+ case INTERVAL_YEAR_MONTH: // Allow YEAR-MONTH YYYYYMM
+ expression= interval_tmp.year* 12 + interval_tmp.month;
+ break;
+ case INTERVAL_DAY_HOUR:
+ expression= interval_tmp.day* 24 + interval_tmp.hour;
+ break;
+ case INTERVAL_DAY_MINUTE:
+ expression= (interval_tmp.day* 24 + interval_tmp.hour) * 60 +
+ interval_tmp.minute;
+ break;
+ case INTERVAL_HOUR_SECOND: /* day is anyway 0 */
+ case INTERVAL_DAY_SECOND:
+ /* DAY_SECOND having problems because of leap seconds? */
+ expression= ((interval_tmp.day* 24 + interval_tmp.hour) * 60 +
+ interval_tmp.minute)*60
+ + interval_tmp.second;
+ break;
+ case INTERVAL_MINUTE_MICROSECOND: /* day and hour are 0 */
+ case INTERVAL_HOUR_MICROSECOND: /* day is anyway 0 */
+ case INTERVAL_DAY_MICROSECOND:
+ DBUG_RETURN(EVEX_MICROSECOND_UNSUP);
+ expression= ((((interval_tmp.day*24) + interval_tmp.hour)*60+
+ interval_tmp.minute)*60 +
+ interval_tmp.second) * 1000000L + interval_tmp.second_part;
+ break;
+ case INTERVAL_HOUR_MINUTE:
+ expression= interval_tmp.hour * 60 + interval_tmp.minute;
+ break;
+ case INTERVAL_MINUTE_SECOND:
+ expression= interval_tmp.minute * 60 + interval_tmp.second;
+ break;
+ case INTERVAL_SECOND_MICROSECOND:
+ DBUG_RETURN(EVEX_MICROSECOND_UNSUP);
+ expression= interval_tmp.second * 1000000L + interval_tmp.second_part;
+ break;
+ case INTERVAL_MICROSECOND:
+ DBUG_RETURN(EVEX_MICROSECOND_UNSUP);
+ case INTERVAL_LAST:
+ DBUG_ASSERT(0);
+ }
+ if (interval_tmp.neg || expression > EVEX_MAX_INTERVAL_VALUE)
+ DBUG_RETURN(EVEX_BAD_PARAMS);
+
+ 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.
+
+ RETURN VALUE
+ 0 OK
+ EVEX_PARSE_ERROR fix_fields failed
+ EVEX_BAD_PARAMS starts before now
+*/
+
+int
+Event_timed::init_starts(THD *thd, Item *new_starts)
+{
+ my_bool not_used;
+ TIME ltime, time_tmp;
+ my_time_t t;
+
+ DBUG_ENTER("Event_timed::init_starts");
+
+ if (new_starts->fix_fields(thd, &new_starts))
+ DBUG_RETURN(EVEX_PARSE_ERROR);
+
+ if ((not_used= new_starts->get_date(&ltime, TIME_NO_ZERO_DATE)))
+ 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());
+
+ DBUG_PRINT("info",("now =%lld", TIME_to_ulonglong_datetime(&time_tmp)));
+ DBUG_PRINT("info",("starts=%lld", TIME_to_ulonglong_datetime(&ltime)));
+ if (TIME_to_ulonglong_datetime(&ltime) <
+ TIME_to_ulonglong_datetime(&time_tmp))
+ 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.
+ mysql_priv.h currently lists
+ #define TIMESTAMP_MAX_YEAR 2038 (see TIME_to_timestamp())
+ */
+ my_tz_UTC->gmt_sec_to_TIME(&ltime,t=TIME_to_timestamp(thd, &ltime, &not_used));
+ if (!t)
+ {
+ DBUG_PRINT("error", ("STARTS after year 2037"));
+ DBUG_RETURN(EVEX_BAD_PARAMS);
+ }
+
+ starts= ltime;
+ starts_null= FALSE;
+ 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.
+
+ RETURN VALUE
+ 0 OK
+ EVEX_PARSE_ERROR fix_fields failed
+ ER_WRONG_VALUE starts distant date (after year 2037)
+ EVEX_BAD_PARAMS ENDS before STARTS
+*/
+
+int
+Event_timed::init_ends(THD *thd, Item *new_ends)
+{
+ TIME ltime, ltime_now;
+ my_bool not_used;
+ my_time_t t;
+
+ DBUG_ENTER("Event_timed::init_ends");
+
+ if (new_ends->fix_fields(thd, &new_ends))
+ DBUG_RETURN(EVEX_PARSE_ERROR);
+
+ DBUG_PRINT("info", ("convert to TIME"));
+ 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.
+ mysql_priv.h currently lists
+ #define TIMESTAMP_MAX_YEAR 2038 (see TIME_to_timestamp())
+ */
+ DBUG_PRINT("info", ("get the UTC time"));
+ my_tz_UTC->gmt_sec_to_TIME(&ltime,t=TIME_to_timestamp(thd, &ltime, &not_used));
+ if (!t)
+ {
+ DBUG_PRINT("error", ("ENDS after year 2037"));
+ DBUG_RETURN(EVEX_BAD_PARAMS);
+ }
+
+ /* Check whether ends is after starts */
+ DBUG_PRINT("info", ("ENDS after STARTS?"));
+ if (!starts_null && my_time_compare(&starts, &ltime) != -1)
+ DBUG_RETURN(EVEX_BAD_PARAMS);
+
+ /*
+ The parser forces starts to be provided but one day STARTS could be
+ set before NOW() and in this case the following check should be done.
+ Check whether ENDS is not in the past.
+ */
+ DBUG_PRINT("info", ("ENDS after NOW?"));
+ my_tz_UTC->gmt_sec_to_TIME(&ltime_now, thd->query_start());
+ if (my_time_compare(&ltime_now, &ltime) == 1)
+ DBUG_RETURN(EVEX_BAD_PARAMS);
+
+ ends= ltime;
+ ends_null= FALSE;
+ 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()
+
+ RETURN VALUE
+ 0 OK
+*/
+
+int
+Event_timed::init_definer(THD *thd)
+{
+ DBUG_ENTER("Event_timed::init_definer");
+
+ DBUG_PRINT("info",("init definer_user thd->mem_root=0x%lx "
+ "thd->sec_ctx->priv_user=0x%lx", thd->mem_root,
+ thd->security_ctx->priv_user));
+ definer_user.str= strdup_root(thd->mem_root, thd->security_ctx->priv_user);
+ definer_user.length= strlen(thd->security_ctx->priv_user);
+
+ DBUG_PRINT("info",("init definer_host thd->s_c->priv_host=0x%lx",
+ thd->security_ctx->priv_host));
+ definer_host.str= strdup_root(thd->mem_root, thd->security_ctx->priv_host);
+ definer_host.length= strlen(thd->security_ctx->priv_host);
+
+ DBUG_PRINT("info",("init definer as whole"));
+ definer.length= definer_user.length + definer_host.length + 1;
+ definer.str= alloc_root(thd->mem_root, definer.length + 1);
+
+ DBUG_PRINT("info",("copy the user"));
+ memcpy(definer.str, definer_user.str, definer_user.length);
+ definer.str[definer_user.length]= '@';
+
+ DBUG_PRINT("info",("copy the host"));
+ memcpy(definer.str + definer_user.length + 1, definer_host.str,
+ definer_host.length);
+ definer.str[definer.length]= '\0';
+ DBUG_PRINT("info",("definer initted"));
+
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Loads an event from a row from mysql.event
+
+ SYNOPSIS
+ Event_timed::load_from_row(MEM_ROOT *mem_root, TABLE *table)
+
+ RETURN VALUE
+ 0 OK
+ EVEX_GET_FIELD_FAILED Error
+
+ NOTES
+ This method is silent on errors and should behave like that. Callers
+ should handle throwing of error messages. The reason is that the class
+ should not know about how to deal with communication.
+*/
+
+int
+Event_timed::load_from_row(MEM_ROOT *mem_root, TABLE *table)
+{
+ 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 != Events::FIELD_COUNT)
+ goto error;
+
+ if ((et->dbname.str= get_field(mem_root,
+ table->field[Events::FIELD_DB])) == NULL)
+ goto error;
+
+ et->dbname.length= strlen(et->dbname.str);
+
+ if ((et->name.str= get_field(mem_root,
+ table->field[Events::FIELD_NAME])) == NULL)
+ goto error;
+
+ et->name.length= strlen(et->name.str);
+
+ if ((et->body.str= get_field(mem_root,
+ table->field[Events::FIELD_BODY])) == NULL)
+ goto error;
+
+ et->body.length= strlen(et->body.str);
+
+ if ((et->definer.str= get_field(mem_root,
+ table->field[Events::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;
+
+ et->starts_null= table->field[Events::FIELD_STARTS]->is_null();
+ res1= table->field[Events::FIELD_STARTS]->
+ get_date(&et->starts,TIME_NO_ZERO_DATE);
+
+ et->ends_null= table->field[Events::FIELD_ENDS]->is_null();
+ res2= table->field[Events::FIELD_ENDS]->get_date(&et->ends, TIME_NO_ZERO_DATE);
+
+ if (!table->field[Events::FIELD_INTERVAL_EXPR]->is_null())
+ et->expression= table->field[Events::FIELD_INTERVAL_EXPR]->val_int();
+ else
+ et->expression= 0;
+ /*
+ If res1 and res2 are true then both fields are empty.
+ Hence if Events::FIELD_EXECUTE_AT is empty there is an error.
+ */
+ et->execute_at_null=
+ table->field[Events::FIELD_EXECUTE_AT]->is_null();
+ DBUG_ASSERT(!(et->starts_null && et->ends_null && !et->expression &&
+ et->execute_at_null));
+ if (!et->expression &&
+ table->field[Events::FIELD_EXECUTE_AT]-> get_date(&et->execute_at,
+ TIME_NO_ZERO_DATE))
+ goto error;
+
+ /*
+ In DB the values start from 1 but enum interval_type starts
+ from 0
+ */
+ if (!table->field[Events::FIELD_TRANSIENT_INTERVAL]->is_null())
+ et->interval= (interval_type) ((ulonglong)
+ table->field[Events::FIELD_TRANSIENT_INTERVAL]->val_int() - 1);
+ else
+ et->interval= (interval_type) 0;
+
+ et->created= table->field[Events::FIELD_CREATED]->val_int();
+ et->modified= table->field[Events::FIELD_MODIFIED]->val_int();
+
+ table->field[Events::FIELD_LAST_EXECUTED]->
+ get_date(&et->last_executed, TIME_NO_ZERO_DATE);
+
+ 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[Events::FIELD_STATUS])) == NullS)
+ goto error;
+
+ DBUG_PRINT("load_from_row", ("Event [%s] is [%s]", et->name.str, ptr));
+ et->status= (ptr[0]=='E'? Event_timed::ENABLED:Event_timed::DISABLED);
+
+ /* ToDo : Andrey . Find a way not to allocate ptr on event_mem_root */
+ if ((ptr= get_field(mem_root,
+ table->field[Events::FIELD_ON_COMPLETION])) == NullS)
+ goto error;
+
+ et->on_completion= (ptr[0]=='D'? Event_timed::ON_COMPLETION_DROP:
+ Event_timed::ON_COMPLETION_PRESERVE);
+
+ et->comment.str= get_field(mem_root, table->field[Events::FIELD_COMMENT]);
+ if (et->comment.str != NullS)
+ et->comment.length= strlen(et->comment.str);
+ else
+ et->comment.length= 0;
+
+
+ et->sql_mode= (ulong) table->field[Events::FIELD_SQL_MODE]->val_int();
+
+ DBUG_RETURN(0);
+error:
+ DBUG_RETURN(EVEX_GET_FIELD_FAILED);
+}
+
+
+/*
+ Computes the sum of a timestamp plus interval. Presumed is that at least one
+ previous execution has occured.
+
+ SYNOPSIS
+ get_next_time(TIME *start, int interval_value, interval_type interval)
+ 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
+ 3) We get the difference between time_now and `start`, then divide it
+ by the months, respectively seconds and round up. Then we multiply
+ monts/seconds by the rounded value and add it to `start` -> we get
+ the next execution time.
+*/
+
+static
+bool get_next_time(TIME *next, TIME *start, TIME *time_now, TIME *last_exec,
+ int i_value, interval_type i_type)
+{
+ bool ret;
+ INTERVAL interval;
+ TIME tmp;
+ longlong months=0, seconds=0;
+ DBUG_ENTER("get_next_time");
+ DBUG_PRINT("enter", ("start=%llu now=%llu", TIME_to_ulonglong_datetime(start),
+ TIME_to_ulonglong_datetime(time_now)));
+
+ bzero(&interval, sizeof(interval));
+
+ 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", seconds, months));
+ if (seconds)
+ {
+ longlong seconds_diff;
+ long microsec_diff;
+
+ if (calc_time_diff(time_now, start, 1, &seconds_diff, &microsec_diff))
+ {
+ DBUG_PRINT("error", ("negative difference"));
+ DBUG_ASSERT(0);
+ }
+ uint multiplier= seconds_diff / seconds;
+ /*
+ Increase the multiplier is the modulus is not zero to make round up.
+ Or if time_now==start then we should not execute the same
+ event two times for the same time
+ get the next exec if the modulus is not
+ */
+ DBUG_PRINT("info", ("multiplier=%d", multiplier));
+ if (seconds_diff % seconds || (!seconds_diff && last_exec->year) ||
+ TIME_to_ulonglong_datetime(time_now) ==
+ TIME_to_ulonglong_datetime(last_exec))
+ ++multiplier;
+ interval.second= seconds * multiplier;
+ DBUG_PRINT("info", ("multiplier=%u interval.second=%u", multiplier,
+ interval.second));
+ tmp= *start;
+ if (!(ret= date_add_interval(&tmp, INTERVAL_SECOND, interval)))
+ *next= tmp;
+ }
+ else
+ {
+ /* PRESUMED is that at least one execution took already place */
+ int diff_months= (time_now->year - start->year)*12 +
+ (time_now->month - start->month);
+ /*
+ Note: If diff_months is 0 that means we are in the same month as the
+ last execution which is also the first execution.
+ */
+ /*
+ First we try with the smaller if not then + 1, because if we try with
+ directly with +1 we will be after the current date but it could be that
+ we will be 1 month ahead, so 2 steps are necessary.
+ */
+ interval.month= (diff_months / months)*months;
+ /*
+ Check if the same month as last_exec (always set - prerequisite)
+ An event happens at most once per month so there is no way to schedule
+ it two times for the current month. This saves us from two calls to
+ date_add_interval() if the event was just executed. But if the scheduler
+ is started and there was at least 1 scheduled date skipped this one does
+ not help and two calls to date_add_interval() will be done, which is a
+ bit more expensive but compared to the rareness of the case is neglectable.
+ */
+ if (time_now->year==last_exec->year && time_now->month==last_exec->month)
+ interval.month+= months;
+
+ tmp= *start;
+ if ((ret= date_add_interval(&tmp, INTERVAL_MONTH, interval)))
+ goto done;
+
+ /* If `tmp` is still before time_now just add one more time the interval */
+ if (my_time_compare(&tmp, time_now) == -1)
+ {
+ interval.month+= months;
+ tmp= *start;
+ if ((ret= date_add_interval(&tmp, INTERVAL_MONTH, interval)))
+ goto done;
+ }
+ *next= tmp;
+ /* assert on that the next is after now */
+ DBUG_ASSERT(1==my_time_compare(next, time_now));
+ }
+
+done:
+ DBUG_PRINT("info", ("next=%llu", TIME_to_ulonglong_datetime(next)));
+ DBUG_RETURN(ret);
+}
+
+
+/*
+ Computes next execution time.
+
+ SYNOPSIS
+ Event_timed::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
+ 0000-00-00.
+*/
+
+bool
+Event_timed::compute_next_execution_time()
+{
+ TIME time_now;
+ int tmp;
+
+ DBUG_ENTER("Event_timed::compute_next_execution_time");
+ DBUG_PRINT("enter", ("starts=%llu ends=%llu last_executed=%llu",
+ TIME_to_ulonglong_datetime(&starts),
+ TIME_to_ulonglong_datetime(&ends),
+ TIME_to_ulonglong_datetime(&last_executed)));
+
+ if (status == Event_timed::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("info",("One-time event %s.%s of was already executed",
+ dbname.str, name.str, definer.str));
+ dropped= (on_completion == Event_timed::ON_COMPLETION_DROP);
+ DBUG_PRINT("info",("One-time event will be dropped=%d.", dropped));
+
+ status= Event_timed::DISABLED;
+ status_changed= true;
+ }
+ goto ret;
+ }
+
+ my_tz_UTC->gmt_sec_to_TIME(&time_now, current_thd->query_start());
+
+ DBUG_PRINT("info",("NOW=[%llu]", TIME_to_ulonglong_datetime(&time_now)));
+
+ /* if time_now is after ends don't execute anymore */
+ if (!ends_null && (tmp= my_time_compare(&ends, &time_now)) == -1)
+ {
+ DBUG_PRINT("info", ("NOW after ENDS, don't execute anymore"));
+ /* time_now is after ends. don't execute anymore */
+ set_zero_time(&execute_at, MYSQL_TIMESTAMP_DATETIME);
+ execute_at_null= TRUE;
+ if (on_completion == Event_timed::ON_COMPLETION_DROP)
+ dropped= true;
+ DBUG_PRINT("info", ("Dropped=%d", dropped));
+ status= Event_timed::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 && (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
+ {
+ 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 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.year)
+ {
+ DBUG_PRINT("info", ("Not executed so far."));
+ }
+
+ {
+ TIME next_exec;
+
+ if (get_next_time(&next_exec, &starts, &time_now,
+ last_executed.year? &last_executed:&starts,
+ expression, interval))
+ goto err;
+
+ /* There was previous execution */
+ if (my_time_compare(&ends, &next_exec) == -1)
+ {
+ DBUG_PRINT("info", ("Next execution of %s after ENDS. Stop executing.",
+ name.str));
+ /* Next execution after ends. No more executions */
+ set_zero_time(&execute_at, MYSQL_TIMESTAMP_DATETIME);
+ execute_at_null= TRUE;
+ if (on_completion == Event_timed::ON_COMPLETION_DROP)
+ dropped= true;
+ status= Event_timed::DISABLED;
+ status_changed= true;
+ }
+ else
+ {
+ DBUG_PRINT("info",("Next[%llu]",TIME_to_ulonglong_datetime(&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.year)
+ {
+ TIME next_exec;
+ if (get_next_time(&next_exec, &starts, &time_now, &last_executed,
+ expression, interval))
+ goto err;
+ execute_at= next_exec;
+ DBUG_PRINT("info",("Next[%llu]",TIME_to_ulonglong_datetime(&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.year)
+ {
+ DBUG_PRINT("info", ("Not executed so far."));
+ }
+
+ {
+ TIME next_exec;
+ if (get_next_time(&next_exec, &starts, &time_now,
+ last_executed.year? &last_executed:&starts,
+ expression, interval))
+ goto err;
+ execute_at= next_exec;
+ DBUG_PRINT("info",("Next[%llu]",TIME_to_ulonglong_datetime(&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.year)
+ execute_at= time_now;
+ else
+ {
+ TIME next_exec;
+
+ if (get_next_time(&next_exec, &starts, &time_now, &last_executed,
+ expression, interval))
+ goto err;
+
+ if (my_time_compare(&ends, &next_exec) == -1)
+ {
+ DBUG_PRINT("info", ("Next execution after ENDS. Stop executing."));
+ set_zero_time(&execute_at, MYSQL_TIMESTAMP_DATETIME);
+ execute_at_null= TRUE;
+ status= Event_timed::DISABLED;
+ status_changed= true;
+ if (on_completion == Event_timed::ON_COMPLETION_DROP)
+ dropped= true;
+ }
+ else
+ {
+ DBUG_PRINT("info", ("Next[%llu]",
+ TIME_to_ulonglong_datetime(&next_exec)));
+ execute_at= next_exec;
+ execute_at_null= FALSE;
+ }
+ }
+ }
+ goto ret;
+ }
+ret:
+ DBUG_PRINT("info", ("ret=0"));
+ DBUG_RETURN(false);
+err:
+ DBUG_PRINT("info", ("ret=1"));
+ DBUG_RETURN(true);
+}
+
+
+/*
+ Set the internal last_executed TIME struct to now. NOW is the
+ time according to thd->query_start(), so the THD's clock.
+
+ SYNOPSIS
+ Event_timed::drop()
+ thd thread context
+*/
+
+void
+Event_timed::mark_last_executed(THD *thd)
+{
+ TIME time_now;
+
+ thd->end_time();
+ my_tz_UTC->gmt_sec_to_TIME(&time_now, (my_time_t) thd->query_start());
+
+ last_executed= time_now; /* was execute_at */
+ last_executed_changed= true;
+}
+
+
+/*
+ Drops the event
+
+ SYNOPSIS
+ Event_timed::drop()
+ thd thread context
+
+ RETURN VALUE
+ 0 OK
+ -1 Cannot open mysql.event
+ -2 Cannot find the event in mysql.event (already deleted?)
+
+ others return code from SE in case deletion of the event row
+ failed.
+*/
+
+int
+Event_timed::drop(THD *thd)
+{
+ uint tmp= 0;
+ DBUG_ENTER("Event_timed::drop");
+
+ DBUG_RETURN(db_drop_event(thd, this, false, &tmp));
+}
+
+
+/*
+ Saves status and last_executed_at to the disk if changed.
+
+ SYNOPSIS
+ Event_timed::update_fields()
+ thd - thread context
+
+ RETURN VALUE
+ 0 OK
+ EVEX_OPEN_TABLE_FAILED Error while opening mysql.event for writing
+ EVEX_WRITE_ROW_FAILED On error to write to disk
+
+ others return code from SE in case deletion of the event
+ row failed.
+*/
+
+bool
+Event_timed::update_fields(THD *thd)
+{
+ TABLE *table;
+ Open_tables_state backup;
+ int ret;
+
+ 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))
+ DBUG_RETURN(0);
+
+ thd->reset_n_backup_open_tables_state(&backup);
+
+ if (Events::open_event_table(thd, TL_WRITE, &table))
+ {
+ ret= EVEX_OPEN_TABLE_FAILED;
+ goto done;
+ }
+
+
+ if ((ret= evex_db_find_event_by_name(thd, dbname, name, table)))
+ goto done;
+
+ store_record(table,record[1]);
+ /* Don't update create on row update. */
+ table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET;
+
+ if (last_executed_changed)
+ {
+ table->field[Events::FIELD_LAST_EXECUTED]->set_notnull();
+ table->field[Events::FIELD_LAST_EXECUTED]->store_time(&last_executed,
+ MYSQL_TIMESTAMP_DATETIME);
+ last_executed_changed= false;
+ }
+ if (status_changed)
+ {
+ table->field[Events::FIELD_STATUS]->set_notnull();
+ table->field[Events::FIELD_STATUS]->store((longlong)status, true);
+ status_changed= false;
+ }
+
+ if ((table->file->ha_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);
+}
+
+
+/*
+ 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)
+{
+ int multipl= 0;
+ char tmp_buff[128];
+ String expr_buf(tmp_buff, sizeof(tmp_buff), system_charset_info);
+ expr_buf.length(0);
+
+ DBUG_ENTER("get_create_event");
+ DBUG_PRINT("ret_info",("body_len=[%d]body=[%s]", 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);
+
+ buf->append(STRING_WITH_LEN(" ON SCHEDULE "));
+ if (expression)
+ {
+ buf->append(STRING_WITH_LEN("EVERY "));
+ buf->append(expr_buf);
+ buf->append(' ');
+ LEX_STRING *ival= &interval_type_to_name[interval];
+ buf->append(ival->str, ival->length);
+ }
+ else
+ {
+ char dtime_buff[20*2+32];/* +32 to make my_snprintf_{8bit|ucs2} happy */
+ buf->append(STRING_WITH_LEN("AT '"));
+ /*
+ Pass the buffer and the second param tells fills the buffer and
+ returns the number of chars to copy.
+ */
+ buf->append(dtime_buff, my_datetime_to_str(&execute_at, dtime_buff));
+ buf->append(STRING_WITH_LEN("'"));
+ }
+
+ if (on_completion == Event_timed::ON_COMPLETION_DROP)
+ buf->append(STRING_WITH_LEN(" ON COMPLETION NOT PRESERVE "));
+ else
+ buf->append(STRING_WITH_LEN(" ON COMPLETION PRESERVE "));
+
+ if (status == Event_timed::ENABLED)
+ buf->append(STRING_WITH_LEN("ENABLE"));
+ 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);
+}
+
+
+/*
+ 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
+
+ RETURN VALUE
+ 0 success
+ -99 No rights on this.dbname.str
+ -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)
+{
+ /* this one is local and not needed after exec */
+ Security_context security_ctx;
+ int ret= 0;
+
+ DBUG_ENTER("Event_timed::execute");
+ DBUG_PRINT("info", (" EVEX EXECUTING event %s.%s [EXPR:%d]",
+ dbname.str, name.str, (int) expression));
+
+ VOID(pthread_mutex_lock(&this->LOCK_running));
+ if (running)
+ {
+ VOID(pthread_mutex_unlock(&this->LOCK_running));
+ DBUG_RETURN(-100);
+ }
+ running= true;
+ VOID(pthread_mutex_unlock(&this->LOCK_running));
+
+ if (!sphead && (ret= compile(thd, mem_root)))
+ goto done;
+ /*
+ THD::~THD will clean this or if there is DROP DATABASE in the SP then
+ it will be free there. It should not point to our buffer which is allocated
+ on a mem_root.
+ */
+ thd->db= my_strdup(dbname.str, MYF(0));
+ thd->db_length= dbname.length;
+ if (!check_access(thd, EVENT_ACL,dbname.str, 0, 0, 0,is_schema_db(dbname.str)))
+ {
+ List<Item> empty_item_list;
+ empty_item_list.empty();
+ if (thd->enable_slow_log)
+ sphead->m_flags|= sp_head::LOG_SLOW_STATEMENTS;
+ sphead->m_flags|= sp_head::LOG_GENERAL_LOG;
+
+ ret= sphead->execute_procedure(thd, &empty_item_list);
+ }
+ else
+ {
+ DBUG_PRINT("error", ("%s@%s has no rights on %s", definer_user.str,
+ definer_host.str, dbname.str));
+ ret= -99;
+ }
+
+ VOID(pthread_mutex_lock(&this->LOCK_running));
+ running= false;
+ /* Will compile every time a new sp_head on different root */
+ free_sp();
+ VOID(pthread_mutex_unlock(&this->LOCK_running));
+
+done:
+ /*
+ 1. Don't cache sphead if allocated on another mem_root
+ 2. Don't call security_ctx.destroy() because this will free our dbname.str
+ name.str and definer.str
+ */
+ if (mem_root && sphead)
+ {
+ delete sphead;
+ sphead= 0;
+ }
+ DBUG_PRINT("info", (" EVEX EXECUTED event %s.%s [EXPR:%d]. RetCode=%d",
+ dbname.str, name.str, (int) expression, ret));
+
+ DBUG_RETURN(ret);
+}
+
+
+/*
+ Frees the memory of the sp_head object we hold
+ SYNOPSIS
+ Event_timed::free_sp()
+*/
+
+void
+Event_timed::free_sp()
+{
+ delete sphead;
+ sphead= 0;
+}
+
+
+/*
+ Compiles an event before it's execution. Compiles the anonymous
+ sp_head object held by the event
+
+ SYNOPSIS
+ Event_timed::compile()
+ thd thread context, used for memory allocation mostly
+ mem_root if != NULL then this memory root is used for allocs
+ instead of thd->mem_root
+
+ RETURN VALUE
+ 0 success
+ EVEX_COMPILE_ERROR error during compilation
+ EVEX_MICROSECOND_UNSUP mysql.event was tampered
+*/
+
+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;
+ int old_db_length;
+ char *old_query;
+ uint old_query_len;
+ ulong old_sql_mode= thd->variables.sql_mode;
+ char create_buf[2048];
+ String show_create(create_buf, sizeof(create_buf), system_charset_info);
+ CHARSET_INFO *old_character_set_client,
+ *old_collation_connection,
+ *old_character_set_results;
+ Security_context *save_ctx;
+ /* this one is local and not needed after exec */
+ Security_context security_ctx;
+
+ DBUG_ENTER("Event_timed::compile");
+
+ show_create.length(0);
+
+ switch (get_create_event(thd, &show_create)) {
+ case EVEX_MICROSECOND_UNSUP:
+ sql_print_error("Scheduler");
+ DBUG_RETURN(EVEX_MICROSECOND_UNSUP);
+ case 0:
+ break;
+ default:
+ DBUG_ASSERT(0);
+ }
+
+ 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_PRINT("info",("old_sql_mode=%d new_sql_mode=%d",old_sql_mode, sql_mode));
+ thd->variables.sql_mode= this->sql_mode;
+ /* 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;
+ old_db_length= thd->db_length;
+ thd->db= dbname.str;
+ thd->db_length= dbname.length;
+
+ thd->query= show_create.c_ptr();
+ thd->query_length= show_create.length();
+ DBUG_PRINT("info", ("query:%s",thd->query));
+
+ change_security_context(thd, definer_user, definer_host, dbname,
+ &security_ctx, &save_ctx);
+ thd->lex= &lex;
+ lex_start(thd, (uchar*)thd->query, thd->query_length);
+ lex.et_compile_phase= TRUE;
+ if (MYSQLparse((void *)thd) || thd->is_fatal_error)
+ {
+ DBUG_PRINT("error", ("error during compile or thd->is_fatal_error=%d",
+ thd->is_fatal_error));
+ /*
+ Free lex associated resources
+ QQ: Do we really need all this stuff here?
+ */
+ sql_print_error("error during compile of %s.%s or thd->is_fatal_error=%d",
+ dbname.str, name.str, thd->is_fatal_error);
+ if (lex.sphead)
+ {
+ if (&lex != thd->lex)
+ thd->lex->sphead->restore_lex(thd);
+ delete lex.sphead;
+ lex.sphead= 0;
+ }
+ ret= EVEX_COMPILE_ERROR;
+ goto done;
+ }
+ DBUG_PRINT("note", ("success compiling %s.%s", dbname.str, name.str));
+
+ sphead= lex.et->sphead;
+ sphead->m_db= dbname;
+
+ sphead->set_definer(definer.str, definer.length);
+ sphead->set_info(0, 0, &lex.sp_chistics, sql_mode);
+ sphead->optimize();
+ ret= 0;
+done:
+ lex.et->free_sphead_on_delete= false;
+ lex.et->deinit_mutexes();
+
+ lex_end(&lex);
+ restore_security_context(thd, save_ctx);
+ DBUG_PRINT("note", ("return old data on its place. set back NAMES"));
+
+ thd->lex= old_lex;
+ thd->query= old_query;
+ thd->query_length= old_query_len;
+ thd->db= old_db;
+
+ thd->variables.sql_mode= old_sql_mode;
+ 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);
+}
+
+
+extern pthread_attr_t connection_attrib;
+
+/*
+ Checks whether is possible and forks a thread. Passes self as argument.
+
+ RETURN VALUE
+ EVENT_EXEC_STARTED OK
+ EVENT_EXEC_ALREADY_EXEC Thread not forked, already working
+ EVENT_EXEC_CANT_FORK Unable to spawn thread (error)
+*/
+
+int
+Event_timed::spawn_now(void * (*thread_func)(void*), void *arg)
+{
+ THD *thd= current_thd;
+ int ret= EVENT_EXEC_STARTED;
+ DBUG_ENTER("Event_timed::spawn_now");
+ DBUG_PRINT("info", ("[%s.%s]", dbname.str, name.str));
+
+ VOID(pthread_mutex_lock(&this->LOCK_running));
+
+ DBUG_PRINT("info", ("SCHEDULER: execute_at of %s is %lld", name.str,
+ TIME_to_ulonglong_datetime(&execute_at)));
+ mark_last_executed(thd);
+ if (compute_next_execution_time())
+ {
+ sql_print_error("SCHEDULER: Error while computing time of %s.%s . "
+ "Disabling after execution.", dbname.str, name.str);
+ status= DISABLED;
+ }
+ DBUG_PRINT("evex manager", ("[%10s] next exec at [%llu]", name.str,
+ TIME_to_ulonglong_datetime(&execute_at)));
+ /*
+ 1. For one-time event : year is > 0 and expression is 0
+ 2. For recurring, expression is != -=> check execute_at_null in this case
+ */
+ if ((execute_at.year && !expression) || execute_at_null)
+ {
+ sql_print_information("SCHEDULER: [%s.%s of %s] no more executions "
+ "after this one", dbname.str, name.str,
+ definer.str);
+ flags |= EVENT_EXEC_NO_MORE | EVENT_FREE_WHEN_FINISHED;
+ }
+
+ update_fields(thd);
+
+ if (!in_spawned_thread)
+ {
+ pthread_t th;
+ in_spawned_thread= true;
+
+ if (pthread_create(&th, &connection_attrib, thread_func, arg))
+ {
+ DBUG_PRINT("info", ("problem while spawning thread"));
+ ret= EVENT_EXEC_CANT_FORK;
+ in_spawned_thread= false;
+ }
+ }
+ else
+ {
+ DBUG_PRINT("info", ("already in spawned thread. skipping"));
+ ret= EVENT_EXEC_ALREADY_EXEC;
+ }
+ VOID(pthread_mutex_unlock(&this->LOCK_running));
+
+ DBUG_RETURN(ret);
+}
+
+
+bool
+Event_timed::spawn_thread_finish(THD *thd)
+{
+ bool should_free;
+ DBUG_ENTER("Event_timed::spawn_thread_finish");
+ VOID(pthread_mutex_lock(&LOCK_running));
+ in_spawned_thread= false;
+ DBUG_PRINT("info", ("Sending COND_finished for thread %d", thread_id));
+ thread_id= 0;
+ if (dropped)
+ drop(thd);
+ pthread_cond_broadcast(&COND_finished);
+ should_free= flags & EVENT_FREE_WHEN_FINISHED;
+ VOID(pthread_mutex_unlock(&LOCK_running));
+ DBUG_RETURN(should_free);
+}
+
+
+/*
+ Kills a running event
+ SYNOPSIS
+ Event_timed::kill_thread()
+
+ RETURN VALUE
+ 0 OK
+ -1 EVEX_CANT_KILL
+ !0 Error
+*/
+
+int
+Event_timed::kill_thread(THD *thd)
+{
+ int ret= 0;
+ DBUG_ENTER("Event_timed::kill_thread");
+ pthread_mutex_lock(&LOCK_running);
+ DBUG_PRINT("info", ("thread_id=%lu", thread_id));
+
+ if (thread_id == thd->thread_id)
+ {
+ /*
+ We don't kill ourselves in cases like :
+ alter event e_43 do alter event e_43 do set @a = 4 because
+ we will never receive COND_finished.
+ */
+ DBUG_PRINT("info", ("It's not safe to kill ourselves in self altering queries"));
+ ret= EVEX_CANT_KILL;
+ }
+ else if (thread_id && !(ret= kill_one_thread(thd, thread_id, false)))
+ {
+ thd->enter_cond(&COND_finished, &LOCK_running, "Waiting for finished");
+ DBUG_PRINT("info", ("Waiting for COND_finished from thread %d", thread_id));
+ while (thread_id)
+ pthread_cond_wait(&COND_finished, &LOCK_running);
+
+ DBUG_PRINT("info", ("Got COND_finished"));
+ /* This will implicitly unlock LOCK_running. Hence we return before that */
+ thd->exit_cond("");
+
+ DBUG_RETURN(0);
+ }
+ else if (!thread_id && in_spawned_thread)
+ {
+ /*
+ Because the manager thread waits for the forked thread to update thread_id
+ this situation is impossible.
+ */
+ DBUG_ASSERT(0);
+ }
+ pthread_mutex_unlock(&LOCK_running);
+ DBUG_PRINT("exit", ("%d", ret));
+ DBUG_RETURN(ret);
+}
+
+
+/*
+ Checks whether two events have the same name
+
+ SYNOPSIS
+ event_timed_name_equal()
+
+ RETURN VALUE
+ TRUE names are equal
+ FALSE names are not equal
+*/
+
+bool
+event_timed_name_equal(Event_timed *et, LEX_STRING *name)
+{
+ return !sortcmp_lex_string(et->name, *name, system_charset_info);
+}
+
+
+/*
+ Checks whether two events are in the same schema
+
+ SYNOPSIS
+ event_timed_db_equal()
+
+ RETURN VALUE
+ TRUE schemas are equal
+ FALSE schemas are not equal
+*/
+
+bool
+event_timed_db_equal(Event_timed *et, LEX_STRING *db)
+{
+ return !sortcmp_lex_string(et->dbname, *db, system_charset_info);
+}
+
+
+/*
+ Checks whether two events have the same definer
+
+ SYNOPSIS
+ event_timed_definer_equal()
+
+ Returns
+ TRUE definers are equal
+ FALSE definers are not equal
+*/
+
+bool
+event_timed_definer_equal(Event_timed *et, LEX_STRING *definer)
+{
+ return !sortcmp_lex_string(et->definer, *definer, system_charset_info);
+}
+
+
+/*
+ Checks whether two events are equal by identifiers
+
+ SYNOPSIS
+ event_timed_identifier_equal()
+
+ RETURN VALUE
+ TRUE equal
+ FALSE not equal
+*/
+
+bool
+event_timed_identifier_equal(Event_timed *a, Event_timed *b)
+{
+ return event_timed_name_equal(a, &b->name) &&
+ event_timed_db_equal(a, &b->dbname) &&
+ event_timed_definer_equal(a, &b->definer);
+}
+
+
+/*
+ Switches the security context
+ SYNOPSIS
+ change_security_context()
+ thd Thread
+ user The user
+ host The host of the user
+ db The schema for which the security_ctx will be loaded
+ s_ctx Security context to load state into
+ backup Where to store the old context
+
+ RETURN VALUE
+ 0 - OK
+ 1 - Error (generates error too)
+*/
+
+bool
+change_security_context(THD *thd, LEX_STRING user, LEX_STRING host,
+ LEX_STRING db, Security_context *s_ctx,
+ Security_context **backup)
+{
+ DBUG_ENTER("change_security_context");
+ DBUG_PRINT("info",("%s@%s@%s", user.str, host.str, db.str));
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ s_ctx->init();
+ *backup= 0;
+ if (acl_getroot_no_password(s_ctx, user.str, host.str, host.str, db.str))
+ {
+ my_error(ER_NO_SUCH_USER, MYF(0), user.str, host.str);
+ DBUG_RETURN(TRUE);
+ }
+ *backup= thd->security_ctx;
+ thd->security_ctx= s_ctx;
+#endif
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Restores the security context
+ SYNOPSIS
+ restore_security_context()
+ thd - thread
+ backup - switch to this context
+*/
+
+void
+restore_security_context(THD *thd, Security_context *backup)
+{
+ DBUG_ENTER("restore_security_context");
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ if (backup)
+ thd->security_ctx= backup;
+#endif
+ DBUG_VOID_RETURN;
+}