From 2ca34120b32df1de4a0c6558a560832acf2c93c7 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 27 Jun 2006 08:48:50 +0200 Subject: first cut of WL#3337 (New event scheduler locking infrastructure). Infrastructure built. Added the foreseen files and change Makefile.am/CMakeLists.txt accordingly. libmysqld/Makefile.am: add new files. this is first cut of WL3337u sql/CMakeLists.txt: add more files to build sql/Makefile.am: add new files. this is first cut of WL3337 sql/event_scheduler.cc: event_timed.h -> event_data_objects.h (WL#3337) sql/events.cc: event_timed.h -> event_data_objects.h (WL#3337) sql/share/errmsg.txt: new error message sql/event_data_objects.cc: event_timed.h -> event_data_objects.h (WL#3337) sql/event_data_objects.h: event_timed.h -> event_data_objects.h (WL#3337) sql/sql_parse.cc: event_timed.h -> event_data_objects.h (WL#3337) sql/sql_show.cc: event_timed.h -> event_data_objects.h (WL#3337) sql/sql_yacc.yy: event_timed.h -> event_data_objects.h (WL#3337) --- sql/CMakeLists.txt | 3 +- sql/Makefile.am | 6 +- sql/event_data_objects.cc | 1877 ++++++++++++++++++++++++++++++++++++++++++++ sql/event_data_objects.h | 302 +++++++ sql/event_db_repository.cc | 19 + sql/event_db_repository.h | 20 + sql/event_queue.cc | 19 + sql/event_queue.h | 20 + sql/event_scheduler.cc | 2 +- sql/event_timed.cc | 1877 -------------------------------------------- sql/event_timed.h | 217 ----- sql/events.cc | 2 +- sql/share/errmsg.txt | 3 + sql/sql_parse.cc | 2 +- sql/sql_show.cc | 2 +- sql/sql_yacc.yy | 2 +- 16 files changed, 2271 insertions(+), 2102 deletions(-) create mode 100644 sql/event_data_objects.cc create mode 100644 sql/event_data_objects.h create mode 100644 sql/event_db_repository.cc create mode 100644 sql/event_db_repository.h create mode 100644 sql/event_queue.cc create mode 100644 sql/event_queue.h delete mode 100644 sql/event_timed.cc delete mode 100644 sql/event_timed.h (limited to 'sql') diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index d80a51bb829..9c4d544233f 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -51,7 +51,8 @@ ADD_EXECUTABLE(mysqld ../sql-common/client.c derror.cc des_key_file.cc sql_table.cc sql_test.cc sql_trigger.cc sql_udf.cc sql_union.cc sql_update.cc sql_view.cc strfunc.cc table.cc thr_malloc.cc time.cc tztime.cc uniques.cc unireg.cc item_xmlfunc.cc - rpl_tblmap.cc sql_binlog.cc event_scheduler.cc event_timed.cc + rpl_tblmap.cc sql_binlog.cc event_scheduler.cc event_data_objects.cc + event_queue.cc event_db_repository.cc sql_tablespace.cc events.cc ../sql-common/my_user.c partition_info.cc ${PROJECT_SOURCE_DIR}/sql/sql_yacc.cc diff --git a/sql/Makefile.am b/sql/Makefile.am index 387f18c2ae9..29645302ecc 100644 --- a/sql/Makefile.am +++ b/sql/Makefile.am @@ -65,7 +65,8 @@ noinst_HEADERS = item.h item_func.h item_sum.h item_cmpfunc.h \ sp_head.h sp_pcontext.h sp_rcontext.h sp.h sp_cache.h \ parse_file.h sql_view.h sql_trigger.h \ sql_array.h sql_cursor.h events.h events_priv.h \ - sql_plugin.h authors.h sql_partition.h event_timed.h \ + sql_plugin.h authors.h sql_partition.h event_data_objects.h \ + event_queue.h event_db_repository.h \ partition_info.h partition_element.h event_scheduler.h \ contributors.h mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \ @@ -104,7 +105,8 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \ tztime.cc my_time.c my_user.c my_decimal.cc\ sp_head.cc sp_pcontext.cc sp_rcontext.cc sp.cc \ sp_cache.cc parse_file.cc sql_trigger.cc \ - event_scheduler.cc events.cc event_timed.cc \ + event_scheduler.cc events.cc event_data_objects.cc \ + event_queue.cc event_db_repository.cc \ sql_plugin.cc sql_binlog.cc \ sql_builtin.cc sql_tablespace.cc partition_info.cc diff --git a/sql/event_data_objects.cc b/sql/event_data_objects.cc new file mode 100644 index 00000000000..ebd45dd1a23 --- /dev/null +++ b/sql/event_data_objects.cc @@ -0,0 +1,1877 @@ +/* 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_data_objects.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 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; +} diff --git a/sql/event_data_objects.h b/sql/event_data_objects.h new file mode 100644 index 00000000000..0c122211a9d --- /dev/null +++ b/sql/event_data_objects.h @@ -0,0 +1,302 @@ +#ifndef _EVENT_DATA_OBJECTS_H_ +#define _EVENT_DATA_OBJECTS_H_ +/* Copyright (C) 2004-2006 MySQL AB + + This program is free software; you can redistribute it and/or modify + 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 EVEX_OK 0 +#define EVEX_KEY_NOT_FOUND -1 +#define EVEX_OPEN_TABLE_FAILED -2 +#define EVEX_WRITE_ROW_FAILED -3 +#define EVEX_DELETE_ROW_FAILED -4 +#define EVEX_GET_FIELD_FAILED -5 +#define EVEX_PARSE_ERROR -6 +#define EVEX_INTERNAL_ERROR -7 +#define EVEX_NO_DB_ERROR -8 +#define EVEX_COMPILE_ERROR -19 +#define EVEX_GENERAL_ERROR -20 +#define EVEX_BAD_IDENTIFIER -21 +#define EVEX_BODY_TOO_LONG -22 +#define EVEX_BAD_PARAMS -23 +#define EVEX_NOT_RUNNING -24 +#define EVEX_MICROSECOND_UNSUP -25 +#define EVEX_CANT_KILL -26 + +#define EVENT_EXEC_NO_MORE (1L << 0) +#define EVENT_NOT_USED (1L << 1) +#define EVENT_FREE_WHEN_FINISHED (1L << 2) + + +class sp_head; +class Sql_alloc; + +class Event_timed +{ + Event_timed(const Event_timed &); /* Prevent use of these */ + void operator=(Event_timed &); + my_bool in_spawned_thread; + ulong locked_by_thread_id; + my_bool running; + ulong thread_id; + pthread_mutex_t LOCK_running; + pthread_cond_t COND_finished; + + bool status_changed; + bool last_executed_changed; + +public: + enum enum_status + { + ENABLED = 1, + DISABLED + }; + + enum enum_on_completion + { + ON_COMPLETION_DROP = 1, + ON_COMPLETION_PRESERVE + }; + + TIME last_executed; + + LEX_STRING dbname; + LEX_STRING name; + LEX_STRING body; + + LEX_STRING definer_user; + LEX_STRING definer_host; + LEX_STRING definer;// combination of user and host + + LEX_STRING comment; + TIME starts; + TIME ends; + TIME execute_at; + my_bool starts_null; + my_bool ends_null; + my_bool execute_at_null; + + longlong expression; + interval_type interval; + + ulonglong created; + ulonglong modified; + enum enum_on_completion on_completion; + enum enum_status status; + sp_head *sphead; + ulong sql_mode; + const uchar *body_begin; + + bool dropped; + bool free_sphead_on_delete; + uint flags;//all kind of purposes + + static void *operator new(size_t size) + { + void *p; + DBUG_ENTER("Event_timed::new(size)"); + p= my_malloc(size, MYF(0)); + DBUG_PRINT("info", ("alloc_ptr=0x%lx", p)); + DBUG_RETURN(p); + } + + static void *operator new(size_t size, MEM_ROOT *mem_root) + { return (void*) alloc_root(mem_root, (uint) size); } + + static void operator delete(void *ptr, size_t size) + { + DBUG_ENTER("Event_timed::delete(ptr,size)"); + DBUG_PRINT("enter", ("free_ptr=0x%lx", ptr)); + TRASH(ptr, size); + my_free((gptr) ptr, MYF(0)); + DBUG_VOID_RETURN; + } + + static void operator delete(void *ptr, MEM_ROOT *mem_root) + { + /* + Don't free the memory it will be done by the mem_root but + we need to call the destructor because we free other resources + which are not allocated on the root but on the heap, or we + deinit mutexes. + */ + DBUG_ASSERT(0); + } + + Event_timed(); + + ~Event_timed(); + + void + init(); + + void + deinit_mutexes(); + + int + init_definer(THD *thd); + + int + init_execute_at(THD *thd, Item *expr); + + int + init_interval(THD *thd, Item *expr, interval_type new_interval); + + void + init_name(THD *thd, sp_name *spn); + + int + init_starts(THD *thd, Item *starts); + + int + init_ends(THD *thd, Item *ends); + + void + init_body(THD *thd); + + void + init_comment(THD *thd, LEX_STRING *set_comment); + + int + load_from_row(MEM_ROOT *mem_root, TABLE *table); + + bool + compute_next_execution_time(); + + int + drop(THD *thd); + + void + mark_last_executed(THD *thd); + + bool + update_fields(THD *thd); + + int + get_create_event(THD *thd, String *buf); + + int + execute(THD *thd, MEM_ROOT *mem_root); + + int + compile(THD *thd, MEM_ROOT *mem_root); + + bool + is_running(); + + int + spawn_now(void * (*thread_func)(void*), void *arg); + + bool + spawn_thread_finish(THD *thd); + + void + free_sp(); + + bool + has_equal_db(Event_timed *etn); + + int + kill_thread(THD *thd); + + void + set_thread_id(ulong tid) { thread_id= tid; } +}; + + +class Event_parse_data : public Sql_alloc +{ + Event_parse_data(const Event_parse_data &); /* Prevent use of these */ + void operator=(Event_parse_data &); + +public: + enum enum_status + { + ENABLED = 1, + DISABLED + }; + + enum enum_on_completion + { + ON_COMPLETION_DROP = 1, + ON_COMPLETION_PRESERVE + }; + + enum enum_on_completion on_completion; + enum enum_status status; + + const uchar *body_begin; + + LEX_STRING dbname; + LEX_STRING name; + LEX_STRING body; + + LEX_STRING definer_user; + LEX_STRING definer_host; + LEX_STRING definer;// combination of user and host + + LEX_STRING comment; + Item* item_starts; + Item* item_ends; + Item* item_execute_at; + + TIME starts; + TIME ends; + TIME execute_at; + my_bool starts_null; + my_bool ends_null; + my_bool execute_at_null; + + Item* item_expression; + Item* item_interval; + longlong expression; + interval_type interval; + +// ulonglong created; +// ulonglong modified; + + static Event_parse_data * + new_instance(THD *thd); + + Event_parse_data() {} + ~Event_parse_data() {} + + int + init_definer(THD *thd); + + int + init_execute_at(THD *thd, Item *expr); + + int + init_interval(THD *thd, Item *expr, interval_type new_interval); + + void + init_name(THD *thd, sp_name *spn); + + int + init_starts(THD *thd, Item *starts); + + int + init_ends(THD *thd, Item *ends); + + void + init_body(THD *thd); + + void + init_comment(THD *thd, LEX_STRING *set_comment); + +}; + +#endif /* _EVENT_DATA_OBJECTS_H_ */ diff --git a/sql/event_db_repository.cc b/sql/event_db_repository.cc new file mode 100644 index 00000000000..547e1e6887b --- /dev/null +++ b/sql/event_db_repository.cc @@ -0,0 +1,19 @@ +/* Copyright (C) 2004-2006 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "mysql_priv.h" +#include "event_db_repository.h" +#include "event_data_objects.h" diff --git a/sql/event_db_repository.h b/sql/event_db_repository.h new file mode 100644 index 00000000000..d8a8784089e --- /dev/null +++ b/sql/event_db_repository.h @@ -0,0 +1,20 @@ +#ifndef _EVENT_DB_REPOSITORY_H_ +#define _EVENT_DB_REPOSITORY_H_ +/* Copyright (C) 2004-2006 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + + +#endif /* _EVENT_DB_REPOSITORY_H_ */ diff --git a/sql/event_queue.cc b/sql/event_queue.cc new file mode 100644 index 00000000000..46f965678c6 --- /dev/null +++ b/sql/event_queue.cc @@ -0,0 +1,19 @@ +/* Copyright (C) 2004-2006 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "mysql_priv.h" +#include "event_queue.h" +#include "event_data_objects.h" diff --git a/sql/event_queue.h b/sql/event_queue.h new file mode 100644 index 00000000000..b3aa6133840 --- /dev/null +++ b/sql/event_queue.h @@ -0,0 +1,20 @@ +#ifndef _EVENT_QUEUE_H_ +#define _EVENT_QUEUE_H_ +/* Copyright (C) 2004-2006 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + + +#endif /* _EVENT_QUEUE_H_ */ diff --git a/sql/event_scheduler.cc b/sql/event_scheduler.cc index 1b4a0d290e6..fc2ad75b272 100644 --- a/sql/event_scheduler.cc +++ b/sql/event_scheduler.cc @@ -17,7 +17,7 @@ #include "mysql_priv.h" #include "events_priv.h" #include "events.h" -#include "event_timed.h" +#include "event_data_objects.h" #include "event_scheduler.h" #include "sp_head.h" diff --git a/sql/event_timed.cc b/sql/event_timed.cc deleted file mode 100644 index 4ec875f32a3..00000000000 --- a/sql/event_timed.cc +++ /dev/null @@ -1,1877 +0,0 @@ -/* 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 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; -} diff --git a/sql/event_timed.h b/sql/event_timed.h deleted file mode 100644 index 0652cece361..00000000000 --- a/sql/event_timed.h +++ /dev/null @@ -1,217 +0,0 @@ -#ifndef _EVENT_TIMED_H_ -#define _EVENT_TIMED_H_ -/* Copyright (C) 2004-2006 MySQL AB - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - - -#define EVEX_OK 0 -#define EVEX_KEY_NOT_FOUND -1 -#define EVEX_OPEN_TABLE_FAILED -2 -#define EVEX_WRITE_ROW_FAILED -3 -#define EVEX_DELETE_ROW_FAILED -4 -#define EVEX_GET_FIELD_FAILED -5 -#define EVEX_PARSE_ERROR -6 -#define EVEX_INTERNAL_ERROR -7 -#define EVEX_NO_DB_ERROR -8 -#define EVEX_COMPILE_ERROR -19 -#define EVEX_GENERAL_ERROR -20 -#define EVEX_BAD_IDENTIFIER -21 -#define EVEX_BODY_TOO_LONG -22 -#define EVEX_BAD_PARAMS -23 -#define EVEX_NOT_RUNNING -24 -#define EVEX_MICROSECOND_UNSUP -25 -#define EVEX_CANT_KILL -26 - -#define EVENT_EXEC_NO_MORE (1L << 0) -#define EVENT_NOT_USED (1L << 1) -#define EVENT_FREE_WHEN_FINISHED (1L << 2) - - -class sp_head; - -class Event_timed -{ - Event_timed(const Event_timed &); /* Prevent use of these */ - void operator=(Event_timed &); - my_bool in_spawned_thread; - ulong locked_by_thread_id; - my_bool running; - ulong thread_id; - pthread_mutex_t LOCK_running; - pthread_cond_t COND_finished; - - bool status_changed; - bool last_executed_changed; - -public: - enum enum_status - { - ENABLED = 1, - DISABLED - }; - - enum enum_on_completion - { - ON_COMPLETION_DROP = 1, - ON_COMPLETION_PRESERVE - }; - - TIME last_executed; - - LEX_STRING dbname; - LEX_STRING name; - LEX_STRING body; - - LEX_STRING definer_user; - LEX_STRING definer_host; - LEX_STRING definer;// combination of user and host - - LEX_STRING comment; - TIME starts; - TIME ends; - TIME execute_at; - my_bool starts_null; - my_bool ends_null; - my_bool execute_at_null; - - longlong expression; - interval_type interval; - - ulonglong created; - ulonglong modified; - enum enum_on_completion on_completion; - enum enum_status status; - sp_head *sphead; - ulong sql_mode; - const uchar *body_begin; - - bool dropped; - bool free_sphead_on_delete; - uint flags;//all kind of purposes - - static void *operator new(size_t size) - { - void *p; - DBUG_ENTER("Event_timed::new(size)"); - p= my_malloc(size, MYF(0)); - DBUG_PRINT("info", ("alloc_ptr=0x%lx", p)); - DBUG_RETURN(p); - } - - static void *operator new(size_t size, MEM_ROOT *mem_root) - { return (void*) alloc_root(mem_root, (uint) size); } - - static void operator delete(void *ptr, size_t size) - { - DBUG_ENTER("Event_timed::delete(ptr,size)"); - DBUG_PRINT("enter", ("free_ptr=0x%lx", ptr)); - TRASH(ptr, size); - my_free((gptr) ptr, MYF(0)); - DBUG_VOID_RETURN; - } - - static void operator delete(void *ptr, MEM_ROOT *mem_root) - { - /* - Don't free the memory it will be done by the mem_root but - we need to call the destructor because we free other resources - which are not allocated on the root but on the heap, or we - deinit mutexes. - */ - DBUG_ASSERT(0); - } - - Event_timed(); - - ~Event_timed(); - - void - init(); - - void - deinit_mutexes(); - - int - init_definer(THD *thd); - - int - init_execute_at(THD *thd, Item *expr); - - int - init_interval(THD *thd, Item *expr, interval_type new_interval); - - void - init_name(THD *thd, sp_name *spn); - - int - init_starts(THD *thd, Item *starts); - - int - init_ends(THD *thd, Item *ends); - - void - init_body(THD *thd); - - void - init_comment(THD *thd, LEX_STRING *set_comment); - - int - load_from_row(MEM_ROOT *mem_root, TABLE *table); - - bool - compute_next_execution_time(); - - int - drop(THD *thd); - - void - mark_last_executed(THD *thd); - - bool - update_fields(THD *thd); - - int - get_create_event(THD *thd, String *buf); - - int - execute(THD *thd, MEM_ROOT *mem_root); - - int - compile(THD *thd, MEM_ROOT *mem_root); - - bool - is_running(); - - int - spawn_now(void * (*thread_func)(void*), void *arg); - - bool - spawn_thread_finish(THD *thd); - - void - free_sp(); - - bool - has_equal_db(Event_timed *etn); - - int - kill_thread(THD *thd); - - void - set_thread_id(ulong tid) { thread_id= tid; } -}; - -#endif /* _EVENT_H_ */ diff --git a/sql/events.cc b/sql/events.cc index b4e50cdedee..391ecd6ba16 100644 --- a/sql/events.cc +++ b/sql/events.cc @@ -17,7 +17,7 @@ #include "mysql_priv.h" #include "events_priv.h" #include "events.h" -#include "event_timed.h" +#include "event_data_objects.h" #include "event_scheduler.h" #include "sp.h" #include "sp_head.h" diff --git a/sql/share/errmsg.txt b/sql/share/errmsg.txt index 476bc2f2f02..ac4f2dd9237 100644 --- a/sql/share/errmsg.txt +++ b/sql/share/errmsg.txt @@ -5839,3 +5839,6 @@ ER_CANT_ACTIVATE_LOG eng "Cannot activate '%-.64s' log." ER_RBR_NOT_AVAILABLE eng "The server was not built with row-based replication" +ER_EVENT_RECURSIVITY_FORBIDDEN + eng "Recursivity of EVENT DDL statements is forbidden when body is present" + diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 6e89b91ff4c..3ebf700b715 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -27,7 +27,7 @@ #include "sp.h" #include "sp_cache.h" #include "events.h" -#include "event_timed.h" +#include "event_data_objects.h" #ifdef HAVE_OPENSSL /* diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 8776189b493..33fa9758c6c 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -27,7 +27,7 @@ #include "authors.h" #include "contributors.h" #include "events.h" -#include "event_timed.h" +#include "event_data_objects.h" #include #ifdef WITH_PARTITION_STORAGE_ENGINE diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 5d89e8a618b..e9e00828b7e 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -38,7 +38,7 @@ #include "sp_pcontext.h" #include "sp_rcontext.h" #include "sp.h" -#include "event_timed.h" +#include "event_data_objects.h" #include #include -- cgit v1.2.1