mirror of
https://github.com/MariaDB/server.git
synced 2025-01-17 04:22:27 +01:00
99438424ac
Unify method naming -> create/update/drop_event Move class Event_timed to event_timed.h class Events is in events.h (renamed from event.h) The implementation is in events.cc (renamed from event.h) BitKeeper/deleted/.del-event_executor.cc~f4a4645b973838ab: Delete: sql/event_executor.cc include/my_time.h: add a boundary libmysqld/CMakeLists.txt: event.cc -> events.cc libmysqld/Makefile.am: event.cc -> event.cc sql/CMakeLists.txt: event.cc -> events.cc sql/Makefile.am: event.cc -> events.cc event_priv.h -> events_priv.h sql/event_scheduler.cc: event.h -> events.h add_event -> create_event replace_event -> update_event() event_timed_compare_q is only used in event_scheduler.cc , so moving it from event.cc and making it static sql/event_scheduler.h: add_event -> create_event replace_event -> update_event sql/event_timed.cc: moved extern interval_type_to_name to mysql_priv.h sql/mysql_priv.h: moved my_time_compare to time.cc sql/mysqld.cc: event.h -> events.h sql/sql_db.cc: event.h -> events.h sql/sql_parse.cc: event.h -> events.h class Event_timed moved to event_timed.h sql/sql_show.cc: event.h -> events.h class Event_timed moved to event_timed.h sql/sql_yacc.yy: event.h -> events.h class Event_timed moved to event_timed.h sql/events.cc: add_event -> create_event replace_event -> update_event event_timed_compare_q moved to event_scheduler.cc sql/events.h: class Event_timed moved to event_timed.h sql/events_priv.h: my_time_compare moved to mysql_priv.h event_timed_compare_q is static in event_scheduler.cc sql/time.cc: moved interval_type_to_name from event.cc to here. BitKeeper/etc/ignore: Added libmysqld/events.cc to the ignore list
1877 lines
51 KiB
C++
1877 lines
51 KiB
C++
/* 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 */
|
|
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.
|
|
|
|
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(<ime, TIME_NO_ZERO_DATE)))
|
|
DBUG_RETURN(ER_WRONG_VALUE);
|
|
|
|
if (TIME_to_ulonglong_datetime(<ime) <
|
|
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(<ime,t=TIME_to_timestamp(thd,<ime,¬_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(<ime, 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(<ime)));
|
|
if (TIME_to_ulonglong_datetime(<ime) <
|
|
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(<ime,t=TIME_to_timestamp(thd, <ime, ¬_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(<ime, 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(<ime,t=TIME_to_timestamp(thd, <ime, ¬_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, <ime) != -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(<ime_now, thd->query_start());
|
|
if (my_time_compare(<ime_now, <ime) == 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, µsec_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;
|
|
}
|