From e5f8163b88d019942c5eb205a023a24c3e9cee04 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) --- libmysqld/Makefile.am | 3 +- 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 +- 17 files changed, 2273 insertions(+), 2103 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 diff --git a/libmysqld/Makefile.am b/libmysqld/Makefile.am index b91944ac258..2e03ba0f0a9 100644 --- a/libmysqld/Makefile.am +++ b/libmysqld/Makefile.am @@ -68,7 +68,8 @@ sqlsources = derror.cc field.cc field_conv.cc strfunc.cc filesort.cc \ spatial.cc gstream.cc sql_help.cc tztime.cc sql_cursor.cc \ sp_head.cc sp_pcontext.cc sp.cc sp_cache.cc sp_rcontext.cc \ parse_file.cc sql_view.cc sql_trigger.cc my_decimal.cc \ - event_scheduler.cc events.cc event_timed.cc \ + event_scheduler.cc events.cc event_data_objects.cc \ + event_queue.cc event_db_repository.cc \ rpl_filter.cc sql_partition.cc sql_builtin.cc sql_plugin.cc \ sql_tablespace.cc \ rpl_injector.cc my_user.c partition_info.cc 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 From d2db48c69bf473101cceffe8305a5922dd1a2af2 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 27 Jun 2006 10:53:26 +0200 Subject: WL#3337 (Events new infrasctructure) Second cut of separating parsing phase from execution phase Separate Event_timed from parsing phase and introducing Event_parse_data. sql/event_data_objects.cc: second cut, copy Event_timed::init_body() to Event_parse_data::init_body() Init the body during parsing, everything else keep as a pointer to Item or some other pointer to use for later initialization. sql/event_data_objects.h: get the identifier as sp_name*, later will initialize our structures sql/events.cc: for easy transition add temporarily parse_data, later Event_timed *et will be removed. Do slow transition because Event_timed is so tightly integrated that a front-attack by removing things from this class was unsuccessful. Do things step by step by eliminating dependencies. Hence, the code could be checked with the current test suite too (early testing) sql/events.h: for easy transition add temporarily parse_data, later Event_timed *et will be removed. Do slow transition because Event_timed is so tightly integrated that a front-attack by removing things from this class was unsuccessful. Do things step by step by eliminating dependencies. Hence, the code could be checked with the current test suite too (early testing) BitKeeper/etc/ignore: Added libmysql/viosocket.o.6WmSJk libmysqld/event_data_objects.cc libmysqld/event_db_repository.cc libmysqld/event_queue.cc server-tools/instance-manager/net_serv.cc to the ignore list sql/share/errmsg.txt: remove this message, not used and needed for now sql/sql_lex.h: for easy transition add temporarily parse_data, later Event_timed *et will be removed. Do slow transition because Event_timed is so tightly integrated that a front-attack by removing things from this class was unsuccessful. Do things step by step by eliminating dependencies. Hence, the code could be checked with the current test suite too (early testing) sql/sql_parse.cc: for easy transition add temporarily parse_data, later Event_timed *et will be removed. Do slow transition because Event_timed is so tightly integrated that a front-attack by removing things from this class was unsuccessful. Do things step by step by eliminating dependencies. Hence, the code could be checked with the current test suite too (early testing) sql/sql_yacc.yy: for easy transition add temporarily parse_data, later Event_timed *et will be removed. Do slow transition because Event_timed is so tightly integrated that a front-attack by removing things from this class was unsuccessful. Do things step by step by eliminating dependencies. Hence, the code could be checked with the current test suite too (early testing) --- .bzrignore | 5 +++ sql/event_data_objects.cc | 89 +++++++++++++++++++++++++++++++++++++++++++++++ sql/event_data_objects.h | 9 ++--- sql/events.cc | 14 ++++---- sql/events.h | 13 +++---- sql/share/errmsg.txt | 3 -- sql/sql_lex.h | 2 ++ sql/sql_parse.cc | 11 +++--- sql/sql_yacc.yy | 41 +++++++++++++++++----- 9 files changed, 151 insertions(+), 36 deletions(-) diff --git a/.bzrignore b/.bzrignore index e07aa0cf86a..f9753c20fac 100644 --- a/.bzrignore +++ b/.bzrignore @@ -1778,3 +1778,8 @@ vio/viotest-sslconnect.cpp vio/viotest.cpp zlib/*.ds? zlib/*.vcproj +libmysql/viosocket.o.6WmSJk +libmysqld/event_data_objects.cc +libmysqld/event_db_repository.cc +libmysqld/event_queue.cc +server-tools/instance-manager/net_serv.cc diff --git a/sql/event_data_objects.cc b/sql/event_data_objects.cc index ebd45dd1a23..6d15ad66bcb 100644 --- a/sql/event_data_objects.cc +++ b/sql/event_data_objects.cc @@ -22,6 +22,95 @@ #include "sp_head.h" +Event_parse_data * +Event_parse_data::new_instance(THD *thd) +{ + return new (thd->mem_root) Event_parse_data; +} + + +Event_parse_data::Event_parse_data() +{ + item_execute_at= item_expression= item_starts= item_ends= NULL; +} + + +/* + 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_parse_data::init_body(THD *thd) +{ + DBUG_ENTER("Event_parse_data::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= thd->strmake((char *)body_begin, body.length); + + DBUG_VOID_RETURN; +} + + /* Constructor diff --git a/sql/event_data_objects.h b/sql/event_data_objects.h index 0c122211a9d..a9483465a8d 100644 --- a/sql/event_data_objects.h +++ b/sql/event_data_objects.h @@ -259,19 +259,16 @@ public: my_bool ends_null; my_bool execute_at_null; + sp_name *identifier; 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() {} + Event_parse_data(); + ~Event_parse_data(); int init_definer(THD *thd); diff --git a/sql/events.cc b/sql/events.cc index 391ecd6ba16..679b571ff5c 100644 --- a/sql/events.cc +++ b/sql/events.cc @@ -904,8 +904,8 @@ done: */ int -Events::create_event(THD *thd, Event_timed *et, uint create_options, - uint *rows_affected) +Events::create_event(THD *thd, Event_timed *et, Event_parse_data *parse_data, + uint create_options, uint *rows_affected) { int ret; @@ -948,8 +948,8 @@ Events::create_event(THD *thd, Event_timed *et, uint create_options, */ int -Events::update_event(THD *thd, Event_timed *et, sp_name *new_name, - uint *rows_affected) +Events::update_event(THD *thd, Event_timed *et, Event_parse_data *parse_data, + sp_name *new_name, uint *rows_affected) { int ret; @@ -1054,8 +1054,8 @@ done: */ int -Events::drop_event(THD *thd, Event_timed *et, bool drop_if_exists, - uint *rows_affected) +Events::drop_event(THD *thd, Event_timed *et, Event_parse_data *parse_data, + bool drop_if_exists, uint *rows_affected) { int ret; @@ -1091,7 +1091,7 @@ Events::show_create_event(THD *thd, sp_name *spn) Event_timed *et= NULL; Open_tables_state backup; - DBUG_ENTER("evex_update_event"); + DBUG_ENTER("Events::show_create_event"); DBUG_PRINT("enter", ("name: %*s", spn->m_name.length, spn->m_name.str)); thd->reset_n_backup_open_tables_state(&backup); diff --git a/sql/events.h b/sql/events.h index 66cce6e7777..055c9a371a4 100644 --- a/sql/events.h +++ b/sql/events.h @@ -18,6 +18,7 @@ class Event_timed; +class Event_parse_data; class Events { @@ -47,16 +48,16 @@ public: }; static int - create_event(THD *thd, Event_timed *et, uint create_options, - uint *rows_affected); + create_event(THD *thd, Event_timed *et, Event_parse_data *parse_data, + uint create_options, uint *rows_affected); static int - update_event(THD *thd, Event_timed *et, sp_name *new_name, - uint *rows_affected); + update_event(THD *thd, Event_timed *et, Event_parse_data *parse_data, + sp_name *new_name, uint *rows_affected); static int - drop_event(THD *thd, Event_timed *et, bool drop_if_exists, - uint *rows_affected); + drop_event(THD *thd, Event_timed *et, Event_parse_data *parse_data, + bool drop_if_exists, uint *rows_affected); static int open_event_table(THD *thd, enum thr_lock_type lock_type, TABLE **table); diff --git a/sql/share/errmsg.txt b/sql/share/errmsg.txt index ac4f2dd9237..476bc2f2f02 100644 --- a/sql/share/errmsg.txt +++ b/sql/share/errmsg.txt @@ -5839,6 +5839,3 @@ 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_lex.h b/sql/sql_lex.h index 15a94041d24..6e7456b61bc 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -28,6 +28,7 @@ class sp_pcontext; class st_alter_tablespace; class partition_info; class Event_timed; +class Event_parse_data; #ifdef MYSQL_SERVER /* @@ -1017,6 +1018,7 @@ typedef struct st_lex : public Query_tables_list st_sp_chistics sp_chistics; Event_timed *et; + Event_parse_data *event_parse_data; bool et_compile_phase; bool only_view; /* used for SHOW CREATE TABLE/VIEW */ diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 3ebf700b715..bca140739f3 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -3848,17 +3848,17 @@ end_with_restore_list: switch (lex->sql_command) { case SQLCOM_CREATE_EVENT: - res= Events::create_event(thd, lex->et, + res= Events::create_event(thd, lex->et, lex->event_parse_data, (uint) lex->create_info.options, &rows_affected); break; case SQLCOM_ALTER_EVENT: - res= Events::update_event(thd, lex->et, lex->spname, - &rows_affected); + res= Events::update_event(thd, lex->et, lex->event_parse_data, + lex->spname, &rows_affected); break; case SQLCOM_DROP_EVENT: - res= Events::drop_event(thd, lex->et, lex->drop_if_exists, - &rows_affected); + res= Events::drop_event(thd, lex->et, lex->event_parse_data, + lex->drop_if_exists, &rows_affected); default:; } DBUG_PRINT("info", ("CREATE/ALTER/DROP returned error code=%d af_rows=%d", @@ -3880,7 +3880,6 @@ end_with_restore_list: case SQLCOM_SHOW_CREATE_EVENT: { DBUG_ASSERT(lex->spname); - DBUG_ASSERT(lex->et); if (! lex->spname->m_db.str) { my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR), MYF(0)); diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index e9e00828b7e..a40500afa90 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1280,6 +1280,8 @@ create: if (!(lex->et= new(YYTHD->mem_root) Event_timed())) // implicitly calls Event_timed::init() YYABORT; + if (!(lex->event_parse_data= Event_parse_data::new_instance(YYTHD))) + YYABORT; /* We have to turn of CLIENT_MULTI_QUERIES while parsing a @@ -1289,6 +1291,9 @@ create: $$= YYTHD->client_capabilities & CLIENT_MULTI_QUERIES; YYTHD->client_capabilities &= (~CLIENT_MULTI_QUERIES); + + lex->event_parse_data->identifier= $4; + if (!lex->et_compile_phase) { lex->et->init_name(YYTHD, $4); @@ -1344,6 +1349,8 @@ create: ev_schedule_time: EVERY_SYM expr interval { + Lex->event_parse_data->item_expression= $2; + Lex->event_parse_data->interval= $3; LEX *lex=Lex; if (!lex->et_compile_phase) { @@ -1365,6 +1372,7 @@ ev_schedule_time: EVERY_SYM expr interval ev_ends | AT_SYM expr { + Lex->event_parse_data->item_execute_at= $2; LEX *lex=Lex; if (!lex->et_compile_phase) { @@ -1395,6 +1403,7 @@ ev_schedule_time: EVERY_SYM expr interval opt_ev_status: /* empty */ { $$= 0; } | ENABLE_SYM { + Lex->event_parse_data->status= Event_parse_data::ENABLED; LEX *lex=Lex; if (!lex->et_compile_phase) lex->et->status= Event_timed::ENABLED; @@ -1402,6 +1411,7 @@ opt_ev_status: /* empty */ { $$= 0; } } | DISABLE_SYM { + Lex->event_parse_data->status= Event_parse_data::DISABLED; LEX *lex=Lex; if (!lex->et_compile_phase) @@ -1412,10 +1422,12 @@ opt_ev_status: /* empty */ { $$= 0; } ev_starts: /* empty */ { + Lex->event_parse_data->item_starts= new Item_func_now_local(); Lex->et->init_starts(YYTHD, new Item_func_now_local()); } | STARTS_SYM expr { + Lex->event_parse_data->item_starts= $2; LEX *lex= Lex; if (!lex->et_compile_phase) { @@ -1443,6 +1455,7 @@ ev_starts: /* empty */ ev_ends: /* empty */ | ENDS_SYM expr { + Lex->event_parse_data->item_ends= $2; LEX *lex= Lex; if (!lex->et_compile_phase) { @@ -1467,6 +1480,8 @@ opt_ev_on_completion: /* empty */ { $$= 0; } ev_on_completion: ON COMPLETION_SYM PRESERVE_SYM { + Lex->event_parse_data->on_completion= + Event_parse_data::ON_COMPLETION_PRESERVE; LEX *lex=Lex; if (!lex->et_compile_phase) lex->et->on_completion= Event_timed::ON_COMPLETION_PRESERVE; @@ -1474,6 +1489,8 @@ ev_on_completion: } | ON COMPLETION_SYM NOT_SYM PRESERVE_SYM { + Lex->event_parse_data->on_completion= + Event_parse_data::ON_COMPLETION_DROP; LEX *lex=Lex; if (!lex->et_compile_phase) lex->et->on_completion= Event_timed::ON_COMPLETION_DROP; @@ -1484,6 +1501,7 @@ ev_on_completion: opt_ev_comment: /* empty */ { $$= 0; } | COMMENT_SYM TEXT_STRING_sys { + Lex->comment= Lex->event_parse_data->comment= $2; LEX *lex= Lex; if (!lex->et_compile_phase) { @@ -1518,6 +1536,8 @@ ev_sql_stmt: lex->sphead->m_body_begin= lex->ptr; } + + Lex->event_parse_data->body_begin= lex->ptr; if (!lex->et_compile_phase) lex->et->body_begin= lex->ptr; @@ -1538,6 +1558,7 @@ ev_sql_stmt: lex->et->sphead= lex->sphead; lex->sphead= NULL; } + Lex->event_parse_data->init_body(YYTHD); if (!lex->et_compile_phase) { lex->et->init_body(YYTHD); @@ -4728,6 +4749,10 @@ alter: } lex->spname= 0;//defensive programming + if (!(Lex->event_parse_data= Event_parse_data::new_instance(YYTHD))) + YYABORT; + Lex->event_parse_data->identifier= $3; + if (!(et= new (YYTHD->mem_root) Event_timed()))// implicitly calls Event_timed::init() YYABORT; lex->et = et; @@ -4739,9 +4764,9 @@ alter: } /* - We have to turn of CLIENT_MULTI_QUERIES while parsing a - stored procedure, otherwise yylex will chop it into pieces - at each ';'. + We have to turn of CLIENT_MULTI_QUERIES while parsing a + stored procedure, otherwise yylex will chop it into pieces + at each ';'. */ $$= YYTHD->client_capabilities & CLIENT_MULTI_QUERIES; YYTHD->client_capabilities &= ~CLIENT_MULTI_QUERIES; @@ -7661,6 +7686,10 @@ drop: } | DROP EVENT_SYM if_exists sp_name { + if (!(Lex->event_parse_data= Event_parse_data::new_instance(YYTHD))) + YYABORT; + Lex->event_parse_data->identifier= $4; + LEX *lex=Lex; if (lex->et) @@ -8430,12 +8459,8 @@ show_param: } | CREATE EVENT_SYM sp_name { - Lex->sql_command = SQLCOM_SHOW_CREATE_EVENT; Lex->spname= $3; - Lex->et= new (YYTHD->mem_root) Event_timed(); - if (!Lex->et) - YYABORT; - Lex->et->init_definer(YYTHD); + Lex->sql_command = SQLCOM_SHOW_CREATE_EVENT; } ; -- cgit v1.2.1 From ef9a97e6856ec8128b66a47b124ce3c09f0fd1bc Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 27 Jun 2006 11:51:11 +0200 Subject: WL#3337 (Event scheduler new architecture) Third cut to simplify parsing phase. Now DROP EVENT works. Overloaded few functions to be able to use either sp_name or pass two LEX_STRINGs instead of a Event_timed pointer. This is transitional and eventually the old functions will be removed. For now DROP EVENT also works, does not need anymore a parsing object (Event_timed) and definer initialization because everyone who has EVENT_ACL can drop events, and this is checked on execution time in sql_parse.cc from the security context, as it should be. sql/event_data_objects.cc: overload few functions sql/event_scheduler.cc: Event_scheduler::drop_event() actually does not need Event_timed object but just an identifier, hence pass only sp_name. Overloaded Event_scheduler::find_event() to work with sp_name object. Eventually the old version will be removed. This is being done as transitional step to be able to test frequently code. sql/event_scheduler.h: Event_scheduler::drop_event() actually does not need Event_timed object but just an identifier, hence pass only sp_name. Overloaded Event_scheduler::find_event() to work with sp_name object. Eventually the old version will be removed. This is being done as transitional step to be able to test frequently code. sql/events.cc: Change db_drop_event() not to use Event_timed, either coming from parsing or from Event_timed::drop, but use LEX_STRINGs. sp_name is not convinient because in Event_timed::drop a temporary object has to be created. Hence, dereference the sp_name in Events::drop_event() and pass the LEX_STRINGs. sql/events.h: Change db_drop_event() not to use Event_timed, either coming from parsing or from Event_timed::drop, but use LEX_STRINGs. sp_name is not convinient because in Event_timed::drop a temporary object has to be created. Hence, dereference the sp_name in Events::drop_event() and pass the LEX_STRINGs. sql/events_priv.h: Change db_drop_event() not to use Event_timed, either coming from parsing or from Event_timed::drop, but use LEX_STRINGs. sp_name is not convinient because in Event_timed::drop a temporary object has to be created. Hence, dereference the sp_name in Events::drop_event() and pass the LEX_STRINGs. sql/sql_parse.cc: SQLCOM_DROP_EVENT does not need lex->event_parse_data object and is more like SQLCOM_SHOW_CREATE_EVENT. Therefore, move it to the block that handles the latter. sql/sql_yacc.yy: DROP EVENT does not need a parsing object, just a name. Store it as lex->spname. Pretty similar handling to the one of SHOW CREATE EVENT. --- sql/event_data_objects.cc | 60 ++++++++++++++++++++++++++++++++++++++++++++--- sql/event_scheduler.cc | 49 +++++++++++++++++++++++++++++++++++--- sql/event_scheduler.h | 6 ++++- sql/events.cc | 27 ++++++++++----------- sql/events.h | 5 ++-- sql/events_priv.h | 17 +++++++++++++- sql/sql_parse.cc | 22 +++++++++++++---- sql/sql_yacc.yy | 30 +++--------------------- 8 files changed, 158 insertions(+), 58 deletions(-) diff --git a/sql/event_data_objects.cc b/sql/event_data_objects.cc index 6d15ad66bcb..71739bf0f1c 100644 --- a/sql/event_data_objects.cc +++ b/sql/event_data_objects.cc @@ -1299,7 +1299,7 @@ Event_timed::drop(THD *thd) uint tmp= 0; DBUG_ENTER("Event_timed::drop"); - DBUG_RETURN(db_drop_event(thd, this, false, &tmp)); + DBUG_RETURN(db_drop_event(thd, dbname, name, false, &tmp)); } @@ -1903,8 +1903,62 @@ 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); + event_timed_db_equal(a, &b->dbname); +} + + +/* + 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(sp_name *name, LEX_STRING *event_name) +{ + return !sortcmp_lex_string(name->m_name, *event_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(sp_name *name, LEX_STRING *db) +{ + return !sortcmp_lex_string(name->m_db, *db, 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(sp_name *a, Event_timed *b) +{ + return event_timed_name_equal(a, &b->name) && + event_timed_db_equal(a, &b->dbname); } diff --git a/sql/event_scheduler.cc b/sql/event_scheduler.cc index fc2ad75b272..f83bc5f25a3 100644 --- a/sql/event_scheduler.cc +++ b/sql/event_scheduler.cc @@ -833,12 +833,13 @@ end: */ bool -Event_scheduler::drop_event(THD *thd, Event_timed *et) +Event_scheduler::drop_event(THD *thd, sp_name *name) { int res; Event_timed *et_old; DBUG_ENTER("Event_scheduler::drop_event"); - DBUG_PRINT("enter", ("thd=%p et=%p lock=%p",thd,et,&LOCK_scheduler_data)); + DBUG_PRINT("enter", ("thd=%p name=%p lock=%p", thd, name, + &LOCK_scheduler_data)); LOCK_SCHEDULER_DATA(); if (!is_running_or_suspended()) @@ -848,7 +849,7 @@ Event_scheduler::drop_event(THD *thd, Event_timed *et) DBUG_RETURN(OP_OK); } - if (!(et_old= find_event(et, TRUE))) + if (!(et_old= find_event(name, TRUE))) DBUG_PRINT("info", ("No such event found, probably DISABLED")); UNLOCK_SCHEDULER_DATA(); @@ -1049,6 +1050,48 @@ Event_scheduler::find_event(Event_timed *etn, bool remove_from_q) } +/* + Searches for an event in the scheduler queue + + SYNOPSIS + Event_scheduler::find_event() + name The event to find + comparator The function to use for comparing + remove_from_q If found whether to remove from the Q + + RETURN VALUE + NULL Not found + otherwise Address + + NOTE + The caller should do the locking also the caller is responsible for + actual signalling in case an event is removed from the queue + (signalling COND_new_work for instance). +*/ + +Event_timed * +Event_scheduler::find_event(sp_name *name, bool remove_from_q) +{ + uint i; + DBUG_ENTER("Event_scheduler::find_event"); + + for (i= 0; i < queue.elements; ++i) + { + Event_timed *et= (Event_timed *) queue_element(&queue, i); + DBUG_PRINT("info", ("[%s.%s]==[%s.%s]?", name->m_db.str, name->m_name.str, + et->dbname.str, et->name.str)); + if (event_timed_identifier_equal(name, et)) + { + if (remove_from_q) + queue_remove(&queue, i); + DBUG_RETURN(et); + } + } + + DBUG_RETURN(NULL); +} + + /* Drops all events from the in-memory queue and disk that match certain pattern evaluated by a comparator function diff --git a/sql/event_scheduler.h b/sql/event_scheduler.h index 5ae310bab2a..183ef450be2 100644 --- a/sql/event_scheduler.h +++ b/sql/event_scheduler.h @@ -16,6 +16,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +class sp_name; class Event_timed; class THD; @@ -73,7 +74,7 @@ public: LEX_STRING *new_name); bool - drop_event(THD *thd, Event_timed *et); + drop_event(THD *thd, sp_name *name); int @@ -136,6 +137,9 @@ private: Event_timed * find_event(Event_timed *etn, bool remove_from_q); + Event_timed * + find_event(sp_name *name, bool remove_from_q); + uint workers_count(); diff --git a/sql/events.cc b/sql/events.cc index 679b571ff5c..c9d05994873 100644 --- a/sql/events.cc +++ b/sql/events.cc @@ -988,14 +988,15 @@ Events::update_event(THD *thd, Event_timed *et, Event_parse_data *parse_data, !0 Error (my_error() called) */ -int db_drop_event(THD *thd, Event_timed *et, bool drop_if_exists, - uint *rows_affected) +int db_drop_event(THD *thd, LEX_STRING db, LEX_STRING name, + bool drop_if_exists, uint *rows_affected) { TABLE *table; Open_tables_state backup; int ret; DBUG_ENTER("db_drop_event"); + DBUG_PRINT("enter", ("db=%s name=%s", db.str, name.str)); ret= EVEX_OPEN_TABLE_FAILED; thd->reset_n_backup_open_tables_state(&backup); @@ -1005,13 +1006,10 @@ int db_drop_event(THD *thd, Event_timed *et, bool drop_if_exists, goto done; } - if (!(ret= evex_db_find_event_aux(thd, et, table))) + if (!(ret= evex_db_find_event_by_name(thd, db, name, table))) { if ((ret= table->file->ha_delete_row(table->record[0]))) - { my_error(ER_EVENT_CANNOT_DELETE, MYF(0)); - goto done; - } } else if (ret == EVEX_KEY_NOT_FOUND) { @@ -1019,14 +1017,12 @@ int db_drop_event(THD *thd, Event_timed *et, bool drop_if_exists, { push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, ER_SP_DOES_NOT_EXIST, ER(ER_SP_DOES_NOT_EXIST), - "Event", et->name.str); + "Event", name.str); ret= 0; } else - my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), et->name.str); - goto done; + my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), name.str); } - done: /* evex_drop_event() is used by Event_timed::drop therefore @@ -1044,7 +1040,7 @@ done: SYNOPSIS Events::drop_event() thd THD - et event's name + name event's name drop_if_exists if set and the event not existing => warning onto the stack rows_affected affected number of rows is returned heres @@ -1054,16 +1050,17 @@ done: */ int -Events::drop_event(THD *thd, Event_timed *et, Event_parse_data *parse_data, - bool drop_if_exists, uint *rows_affected) +Events::drop_event(THD *thd, sp_name *name, bool drop_if_exists, + uint *rows_affected) { int ret; DBUG_ENTER("Events::drop_event"); - if (!(ret= db_drop_event(thd, et, drop_if_exists, rows_affected))) + if (!(ret= db_drop_event(thd, name->m_db, name->m_name, drop_if_exists, + rows_affected))) { Event_scheduler *scheduler= Event_scheduler::get_instance(); - if (scheduler->initialized() && (ret= scheduler->drop_event(thd, et))) + if (scheduler->initialized() && (ret= scheduler->drop_event(thd, name))) my_error(ER_EVENT_MODIFY_QUEUE_ERROR, MYF(0), ret); } diff --git a/sql/events.h b/sql/events.h index 055c9a371a4..d56a544493d 100644 --- a/sql/events.h +++ b/sql/events.h @@ -16,7 +16,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - +class sp_name; class Event_timed; class Event_parse_data; @@ -56,8 +56,7 @@ public: sp_name *new_name, uint *rows_affected); static int - drop_event(THD *thd, Event_timed *et, Event_parse_data *parse_data, - bool drop_if_exists, uint *rows_affected); + drop_event(THD *thd, sp_name *name, bool drop_if_exists, uint *rows_affected); static int open_event_table(THD *thd, enum thr_lock_type lock_type, TABLE **table); diff --git a/sql/events_priv.h b/sql/events_priv.h index ed02cb7055b..fe208ace280 100644 --- a/sql/events_priv.h +++ b/sql/events_priv.h @@ -25,6 +25,7 @@ #define EVEX_MAX_INTERVAL_VALUE 2147483647L class Event_timed; +class sp_name; int evex_db_find_event_by_name(THD *thd, const LEX_STRING dbname, @@ -32,7 +33,7 @@ evex_db_find_event_by_name(THD *thd, const LEX_STRING dbname, TABLE *table); int -db_drop_event(THD *thd, Event_timed *et, bool drop_if_exists, +db_drop_event(THD *thd, LEX_STRING db, LEX_STRING name, bool drop_if_exists, uint *rows_affected); int db_find_event(THD *thd, sp_name *name, Event_timed **ett, TABLE *tbl, @@ -68,6 +69,20 @@ bool event_timed_identifier_equal(Event_timed *a, Event_timed *b); + +/* Compares only the name part of the identifier */ +bool +event_timed_name_equal(sp_name *name, LEX_STRING *event_name); + +/* Compares only the schema part of the identifier */ +bool +event_timed_db_equal(sp_name *name, LEX_STRING *db); + +/* Compares the whole identifier*/ +bool +event_timed_identifier_equal(sp_name *a, Event_timed *b); + + bool change_security_context(THD *thd, LEX_STRING user, LEX_STRING host, LEX_STRING db, Security_context *s_ctx, diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index bca140739f3..4f75339e5a2 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -3819,7 +3819,6 @@ end_with_restore_list: } case SQLCOM_CREATE_EVENT: case SQLCOM_ALTER_EVENT: - case SQLCOM_DROP_EVENT: { uint rows_affected= 1; DBUG_ASSERT(lex->et); @@ -3856,9 +3855,6 @@ end_with_restore_list: res= Events::update_event(thd, lex->et, lex->event_parse_data, lex->spname, &rows_affected); break; - case SQLCOM_DROP_EVENT: - res= Events::drop_event(thd, lex->et, lex->event_parse_data, - lex->drop_if_exists, &rows_affected); default:; } DBUG_PRINT("info", ("CREATE/ALTER/DROP returned error code=%d af_rows=%d", @@ -3877,6 +3873,7 @@ end_with_restore_list: break; } + case SQLCOM_DROP_EVENT: case SQLCOM_SHOW_CREATE_EVENT: { DBUG_ASSERT(lex->spname); @@ -3893,9 +3890,24 @@ end_with_restore_list: if (lex->spname->m_name.length > NAME_LEN) { my_error(ER_TOO_LONG_IDENT, MYF(0), lex->spname->m_name.str); + /* this jumps to the end of the function and skips own messaging */ goto error; } - res= Events::show_create_event(thd, lex->spname); + + if (lex->sql_command == SQLCOM_SHOW_CREATE_EVENT) + res= Events::show_create_event(thd, lex->spname); + else + { + uint rows_affected= 1; + if (end_active_trans(thd)) + { + res= -1; + break; + } + if (!(res= Events::drop_event(thd, lex->spname, lex->drop_if_exists, + &rows_affected))) + send_ok(thd, rows_affected); + } break; } #ifndef DBUG_OFF diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index a40500afa90..4dfabbf0c27 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -7686,33 +7686,9 @@ drop: } | DROP EVENT_SYM if_exists sp_name { - if (!(Lex->event_parse_data= Event_parse_data::new_instance(YYTHD))) - YYABORT; - Lex->event_parse_data->identifier= $4; - - LEX *lex=Lex; - - if (lex->et) - { - /* - Recursive events are not possible because recursive SPs - are not also possible. lex->sp_head is not stacked. - */ - my_error(ER_SP_NO_RECURSIVE_CREATE, MYF(0), "EVENT"); - YYABORT; - } - - if (!(lex->et= new (YYTHD->mem_root) Event_timed())) - YYABORT; - - if (!lex->et_compile_phase) - { - lex->et->init_name(YYTHD, $4); - lex->et->init_definer(YYTHD); - } - - lex->sql_command = SQLCOM_DROP_EVENT; - lex->drop_if_exists= $3; + Lex->drop_if_exists= $3; + Lex->spname= $4; + Lex->sql_command = SQLCOM_DROP_EVENT; } | DROP TRIGGER_SYM sp_name { -- cgit v1.2.1 From cace147c63a03b4dc9bd3e55ae3bda4f5e9db97b Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 27 Jun 2006 13:15:40 +0200 Subject: WL#3337 (Event scheduler new architecture) Fourth cut of refactoring the parsing. Next step will be to refactor of usage of Event_timed during Events::create_event() and Events::update_event(). Disallow: - CREATE EVENT ... DO CREATE EVENT ...; - ALTER EVENT ... DO CREATE EVENT ...; - CREATE EVENT ... DO ALTER EVENT DO ....; - CREATE PROCEDURE ... BEGIN CREATE EVENT ... END| Allowed: - CREATE EVENT ... DO DROP EVENT yyy; - CREATE EVENT ... DO ALTER EVENT yyy; (the nested ALTER EVENT can have anything but DO clause) - ALTER EVENT ... DO ALTER EVENT yyy; (the nested ALTER EVENT can have anything but DO clause) - ALTER EVENT ... DO DROP EVENT yyy; - CREATE PROCEDURE ... BEGIN ALTER EVENT ... END| (the nested ALTER EVENT can have anything but DO clause) - CREATE PROCEDURE ... BEGIN DROP EVENT ... END| mysql-test/r/events.result: update results mysql-test/r/events_bugs.result: update results mysql-test/t/events.test: use number as error, mysql-test-run does not like the name. will come back to this later, now it's enough to pass the test. disable nested events / events in SP, when the nested event has a body. If no body is provided, namely DROP EVENT or ALTER that changes the characteristics, then these are allowed. mysql-test/t/events_bugs.test: use number as error, mysql-test-run does not like the name. will come back to this later, now it's enough to pass the test. disable nested events / events in SP, when the nested event has a body. If no body is provided, namely DROP EVENT or ALTER that changes the characteristics, then these are allowed. sql/share/errmsg.txt: new message sql/sql_yacc.yy: refactor CREATE EVENT parsing to fit into the structure CREATE DEFINER=xxx EVENT The actual definer part is not used, but parsed, for now. Disable nested CREATE EVENTS, CREATE EVENT inside CREATE PROCEDURE. And an event DDL with body inside an ALTER. This stops the following : - CREATE EVENT ... DO CREATE EVENT ...; - ALTER EVENT ... DO CREATE EVENT ...; - CREATE EVENT ... DO ALTER EVENT DO ....; - CREATE PROCEDURE ... BEGIN CREATE EVENT ... END| This allows: - CREATE EVENT ... DO DROP EVENT yyy; - CREATE EVENT ... DO ALTER EVENT yyy; (the nested ALTER EVENT can have anything but DO clause) - ALTER EVENT ... DO ALTER EVENT yyy; (the nested ALTER EVENT can have anything but DO clause) - ALTER EVENT ... DO DROP EVENT yyy; - CREATE PROCEDURE ... BEGIN ALTER EVENT ... END| (the nested ALTER EVENT can have anything but DO clause) - CREATE PROCEDURE ... BEGIN DROP EVENT ... END| --- mysql-test/r/events.result | 16 ++++- mysql-test/r/events_bugs.result | 13 +--- mysql-test/t/events.test | 17 +++-- mysql-test/t/events_bugs.test | 13 +--- sql/share/errmsg.txt | 3 + sql/sql_yacc.yy | 156 +++++++++++++++++++++------------------- 6 files changed, 114 insertions(+), 104 deletions(-) diff --git a/mysql-test/r/events.result b/mysql-test/r/events.result index e115e077535..f95953cdc86 100644 --- a/mysql-test/r/events.result +++ b/mysql-test/r/events.result @@ -85,13 +85,25 @@ SHOW EVENTS; Db Name Definer Type Execute at Interval value Interval field Starts Ends Status events_test event_starts_test root@localhost RECURRING NULL 20 SECOND # # ENABLED DROP EVENT event_starts_test; +create table test_nested(a int); create event e_43 on schedule every 1 second do set @a = 5; set global event_scheduler = 1; alter event e_43 do alter event e_43 do set @a = 4; +ERROR HY000: Recursivity of EVENT DDL statements is forbidden when body is present +alter event e_43 do +begin +alter event e_43 on schedule every 5 minute; +insert into test_nested values(1); +end| +set global event_scheduler = 1; select db, name, body, status, interval_field, interval_value from mysql.event; db name body status interval_field interval_value -events_test e_43 set @a = 4 ENABLED SECOND 1 +events_test e_43 begin +alter event e_43 on schedule every 5 minute; +insert into test_nested values(1); +end ENABLED MINUTE 5 drop event e_43; +drop table test_nested; "Let's check whether we can use non-qualified names" create table non_qualif(a int); create event non_qualif_ev on schedule every 10 minute do insert into non_qualif values (800219); @@ -358,7 +370,7 @@ root localhost events_test Connect User lock select get_lock("test_lock2_1", 20) drop event закачка21; create table t_16 (s1 int); create trigger t_16_bi before insert on t_16 for each row create event e_16 on schedule every 1 second do set @a=5; -ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger. +ERROR HY000: Recursivity of EVENT DDL statements is forbidden when body is present drop table t_16; create event white_space on schedule every 10 hour diff --git a/mysql-test/r/events_bugs.result b/mysql-test/r/events_bugs.result index a7c2964a253..e1f8551c2fd 100644 --- a/mysql-test/r/events_bugs.result +++ b/mysql-test/r/events_bugs.result @@ -17,18 +17,7 @@ DROP EVENT ДОЛЕН_регистър_утф8; SET NAMES latin1; set @a=3; CREATE PROCEDURE p_16 () CREATE EVENT e_16 ON SCHEDULE EVERY @a SECOND DO SET @a=5; -call p_16(); -"Here we used to crash!" -call p_16(); -ERROR HY000: Event 'e_16' already exists -call p_16(); -ERROR HY000: Event 'e_16' already exists -DROP EVENT e_16; -CALL p_16(); -CALL p_16(); -ERROR HY000: Event 'e_16' already exists -DROP PROCEDURE p_16; -DROP EVENT e_16; +ERROR HY000: Recursivity of EVENT DDL statements is forbidden when body is present create event e_55 on schedule at 99990101000000 do drop table t; ERROR HY000: Incorrect AT value: '99990101000000' create event e_55 on schedule every 10 hour starts 99990101000000 do drop table t; diff --git a/mysql-test/t/events.test b/mysql-test/t/events.test index aac13a55dd3..1940016a7a1 100644 --- a/mysql-test/t/events.test +++ b/mysql-test/t/events.test @@ -81,14 +81,23 @@ SHOW EVENTS; DROP EVENT event_starts_test; # # +create table test_nested(a int); create event e_43 on schedule every 1 second do set @a = 5; set global event_scheduler = 1; ---sleep 2 +--error 1562 alter event e_43 do alter event e_43 do set @a = 4; ---sleep 2 +delimiter |; +alter event e_43 do +begin + alter event e_43 on schedule every 5 minute; + insert into test_nested values(1); +end| +delimiter ;| +set global event_scheduler = 1; +--sleep 1 select db, name, body, status, interval_field, interval_value from mysql.event; drop event e_43; ---sleep 1 +drop table test_nested; --echo "Let's check whether we can use non-qualified names" create table non_qualif(a int); @@ -315,7 +324,7 @@ drop event закачка21; # Bug #16410 Events: CREATE EVENT is legal in a CREATE TRIGGER statement # create table t_16 (s1 int); ---error 1422 +--error 1562 create trigger t_16_bi before insert on t_16 for each row create event e_16 on schedule every 1 second do set @a=5; drop table t_16; # diff --git a/mysql-test/t/events_bugs.test b/mysql-test/t/events_bugs.test index 9434de7be7d..e07707e7734 100644 --- a/mysql-test/t/events_bugs.test +++ b/mysql-test/t/events_bugs.test @@ -30,19 +30,8 @@ SET NAMES latin1; # START - BUG#16408: Events: crash for an event in a procedure # set @a=3; +--error 1562 CREATE PROCEDURE p_16 () CREATE EVENT e_16 ON SCHEDULE EVERY @a SECOND DO SET @a=5; -call p_16(); ---echo "Here we used to crash!" ---error ER_EVENT_ALREADY_EXISTS -call p_16(); ---error ER_EVENT_ALREADY_EXISTS -call p_16(); -DROP EVENT e_16; -CALL p_16(); ---error ER_EVENT_ALREADY_EXISTS -CALL p_16(); -DROP PROCEDURE p_16; -DROP EVENT e_16; # # END - BUG#16408: Events: crash for an event in a procedure # 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_yacc.yy b/sql/sql_yacc.yy index 4dfabbf0c27..23201a81f88 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -880,7 +880,8 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); sp_c_chistics sp_a_chistics sp_chistic sp_c_chistic xa load_data opt_field_or_var_spec fields_or_vars opt_load_data_set_spec definer view_replace_or_algorithm view_replace view_algorithm_opt - view_algorithm view_or_trigger_or_sp view_or_trigger_or_sp_tail + view_algorithm view_or_trigger_or_sp_or_event + view_or_trigger_or_sp_or_event_tail view_suid view_tail view_list_opt view_list view_select view_check_option trigger_tail sp_tail install uninstall partition_entry binlog_base64_event @@ -1257,7 +1258,33 @@ create: lex->name=$4.str; lex->create_info.options=$3; } - | CREATE EVENT_SYM opt_if_not_exists sp_name + | CREATE + { + Lex->create_view_mode= VIEW_CREATE_NEW; + Lex->create_view_algorithm= VIEW_ALGORITHM_UNDEFINED; + Lex->create_view_suid= TRUE; + } + view_or_trigger_or_sp_or_event + {} + | CREATE USER clear_privileges grant_list + { + Lex->sql_command = SQLCOM_CREATE_USER; + } + | CREATE LOGFILE_SYM GROUP logfile_group_info + { + LEX *lex= Lex; + lex->alter_tablespace_info->ts_cmd_type= CREATE_LOGFILE_GROUP; + } + | CREATE TABLESPACE tablespace_info + { + LEX *lex= Lex; + lex->alter_tablespace_info->ts_cmd_type= CREATE_TABLESPACE; + } + ; + + +event_tail: + EVENT_SYM opt_if_not_exists sp_name /* BE CAREFUL when you add a new rule to update the block where YYTHD->client_capabilities is set back to original value @@ -1268,15 +1295,14 @@ create: if (lex->et) { /* - Recursive events are not possible because recursive SPs - are not also possible. lex->sp_head is not stacked. + Recursive CREATE EVENT statement are not possible because + recursive SPs are not also possible. lex->sp_head is not stacked. */ - // ToDo Andrey : Change the error message my_error(ER_SP_NO_RECURSIVE_CREATE, MYF(0), "EVENT"); YYABORT; } - lex->create_info.options= $3; + lex->create_info.options= $2; if (!(lex->et= new(YYTHD->mem_root) Event_timed())) // implicitly calls Event_timed::init() YYABORT; @@ -1292,11 +1318,11 @@ create: YYTHD->client_capabilities &= (~CLIENT_MULTI_QUERIES); - lex->event_parse_data->identifier= $4; + lex->event_parse_data->identifier= $3; if (!lex->et_compile_phase) { - lex->et->init_name(YYTHD, $4); + lex->et->init_name(YYTHD, $3); lex->et->init_definer(YYTHD); } } @@ -1308,13 +1334,12 @@ create: { /* Restore flag if it was cleared above - $1 - CREATE - $2 - EVENT_SYM - $3 - opt_if_not_exists - $4 - sp_name - $5 - the block above + $1 - EVENT_SYM + $2 - opt_if_not_exists + $3 - sp_name + $4 - the block above */ - YYTHD->client_capabilities |= $5; + YYTHD->client_capabilities |= $4; /* sql_command is set here because some rules in ev_sql_stmt @@ -1322,29 +1347,6 @@ create: */ Lex->sql_command= SQLCOM_CREATE_EVENT; } - | CREATE - { - Lex->create_view_mode= VIEW_CREATE_NEW; - Lex->create_view_algorithm= VIEW_ALGORITHM_UNDEFINED; - Lex->create_view_suid= TRUE; - } - view_or_trigger_or_sp - {} - | CREATE USER clear_privileges grant_list - { - Lex->sql_command = SQLCOM_CREATE_USER; - } - | CREATE LOGFILE_SYM GROUP logfile_group_info - { - LEX *lex= Lex; - lex->alter_tablespace_info->ts_cmd_type= CREATE_LOGFILE_GROUP; - } - | CREATE TABLESPACE tablespace_info - { - LEX *lex= Lex; - lex->alter_tablespace_info->ts_cmd_type= CREATE_TABLESPACE; - } - ; ev_schedule_time: EVERY_SYM expr interval @@ -1517,25 +1519,41 @@ ev_sql_stmt: LEX *lex= Lex; sp_head *sp; - $$= lex->sphead; - - if (!lex->sphead) + /* + This stops the following : + - CREATE EVENT ... DO CREATE EVENT ...; + - ALTER EVENT ... DO CREATE EVENT ...; + - CREATE EVENT ... DO ALTER EVENT DO ....; + - CREATE PROCEDURE ... BEGIN CREATE EVENT ... END| + This allows: + - CREATE EVENT ... DO DROP EVENT yyy; + - CREATE EVENT ... DO ALTER EVENT yyy; + (the nested ALTER EVENT can have anything but DO clause) + - ALTER EVENT ... DO ALTER EVENT yyy; + (the nested ALTER EVENT can have anything but DO clause) + - ALTER EVENT ... DO DROP EVENT yyy; + - CREATE PROCEDURE ... BEGIN ALTER EVENT ... END| + (the nested ALTER EVENT can have anything but DO clause) + - CREATE PROCEDURE ... BEGIN DROP EVENT ... END| + */ + if (lex->sphead) { - if (!(sp= new sp_head())) - YYABORT; - - sp->reset_thd_mem_root(YYTHD); - sp->init(lex); + my_error(ER_EVENT_RECURSIVITY_FORBIDDEN, MYF(0)); + YYABORT; + } + + if (!(lex->sphead= new sp_head())) + YYABORT; - sp->m_type= TYPE_ENUM_PROCEDURE; + lex->sphead->reset_thd_mem_root(YYTHD); + lex->sphead->init(lex); - lex->sphead= sp; + lex->sphead->m_type= TYPE_ENUM_PROCEDURE; - bzero((char *)&lex->sp_chistics, sizeof(st_sp_chistics)); - lex->sphead->m_chistics= &lex->sp_chistics; + bzero((char *)&lex->sp_chistics, sizeof(st_sp_chistics)); + lex->sphead->m_chistics= &lex->sp_chistics; - lex->sphead->m_body_begin= lex->ptr; - } + lex->sphead->m_body_begin= lex->ptr; Lex->event_parse_data->body_begin= lex->ptr; @@ -1546,18 +1564,15 @@ ev_sql_stmt: { LEX *lex=Lex; - if (!$1) - { - sp_head *sp= lex->sphead; - // return back to the original memory root ASAP - sp->init_strings(YYTHD, lex, NULL); - sp->restore_thd_mem_root(YYTHD); + // return back to the original memory root ASAP + lex->sphead->init_strings(YYTHD, lex, NULL); + lex->sphead->restore_thd_mem_root(YYTHD); - lex->sp_chistics.suid= SP_IS_SUID;//always the definer! + lex->sp_chistics.suid= SP_IS_SUID;//always the definer! + + lex->et->sphead= lex->sphead; + lex->sphead= NULL; - lex->et->sphead= lex->sphead; - lex->sphead= NULL; - } Lex->event_parse_data->init_body(YYTHD); if (!lex->et_compile_phase) { @@ -4738,16 +4753,7 @@ alter: LEX *lex=Lex; Event_timed *et; - if (lex->et) - { - /* - Recursive events are not possible because recursive SPs - are not also possible. lex->sp_head is not stacked. - */ - my_error(ER_SP_NO_RECURSIVE_CREATE, MYF(0), "EVENT"); - YYABORT; - } - lex->spname= 0;//defensive programming + lex->spname= NULL; if (!(Lex->event_parse_data= Event_parse_data::new_instance(YYTHD))) YYABORT; @@ -10767,20 +10773,22 @@ subselect_end: **************************************************************************/ -view_or_trigger_or_sp: - definer view_or_trigger_or_sp_tail +view_or_trigger_or_sp_or_event: + definer view_or_trigger_or_sp_or_event_tail {} | view_replace_or_algorithm definer view_tail {} ; -view_or_trigger_or_sp_tail: +view_or_trigger_or_sp_or_event_tail: view_tail {} | trigger_tail {} | sp_tail {} + | event_tail + {} ; /************************************************************************** -- cgit v1.2.1 From acefb78bc3fbf28376d8713e1dc9f056dc3cdbf6 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 28 Jun 2006 01:28:03 +0200 Subject: WL#3337 (Events new architecture) 5th cut, moved DB related code to Event_db_repository and updated accordingly the remanining code. Moved change/restore_security_context() to class THD Removed events_priv.h Next step is to reorganize create/update_event() and parsing for them. But probably some other refactoring could be done in the meanwhile. The changes so far pass the test suite. BitKeeper/deleted/.del-events_priv.h~2e8bce2cf35997df: Delete: sql/events_priv.h sql/Makefile.am: events_priv.h is no more sql/event_data_objects.cc: reorganize events code sql/event_data_objects.h: reorganize events code sql/event_db_repository.cc: reorganize events code sql/event_db_repository.h: reorganize events code sql/event_scheduler.cc: reorganize events code sql/event_scheduler.h: reorganize events code sql/events.cc: reorganize events code sql/events.h: reorganize events code sql/mysqld.cc: reorganize events code sql/set_var.cc: reorganize events code sql/sql_class.cc: add ::change_security_context() and restore_security_context() sql/sql_class.h: add ::change_security_context() and restore_security_context() sql/sql_db.cc: reorganize Events code sql/sql_parse.cc: reorganize Events code sql/sql_show.cc: reorganize Events code --- sql/Makefile.am | 2 +- sql/event_data_objects.cc | 88 ++--- sql/event_data_objects.h | 32 ++ sql/event_db_repository.cc | 854 +++++++++++++++++++++++++++++++++++++++++++++ sql/event_db_repository.h | 82 +++++ sql/event_scheduler.cc | 91 +---- sql/event_scheduler.h | 37 +- sql/events.cc | 812 ++---------------------------------------- sql/events.h | 95 ++--- sql/events_priv.h | 94 ----- sql/mysqld.cc | 2 +- sql/set_var.cc | 2 +- sql/sql_class.cc | 57 +++ sql/sql_class.h | 8 + sql/sql_db.cc | 2 +- sql/sql_parse.cc | 20 +- sql/sql_show.cc | 4 +- 17 files changed, 1210 insertions(+), 1072 deletions(-) delete mode 100644 sql/events_priv.h diff --git a/sql/Makefile.am b/sql/Makefile.am index 29645302ecc..49f85b3921d 100644 --- a/sql/Makefile.am +++ b/sql/Makefile.am @@ -64,7 +64,7 @@ noinst_HEADERS = item.h item_func.h item_sum.h item_cmpfunc.h \ tztime.h my_decimal.h\ sp_head.h sp_pcontext.h sp_rcontext.h sp.h sp_cache.h \ parse_file.h sql_view.h sql_trigger.h \ - sql_array.h sql_cursor.h events.h events_priv.h \ + sql_array.h sql_cursor.h events.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 \ diff --git a/sql/event_data_objects.cc b/sql/event_data_objects.cc index 71739bf0f1c..4b72c7e55a7 100644 --- a/sql/event_data_objects.cc +++ b/sql/event_data_objects.cc @@ -16,12 +16,15 @@ #define MYSQL_LEX 1 #include "mysql_priv.h" -#include "events_priv.h" #include "events.h" #include "event_data_objects.h" +#include "event_db_repository.h" #include "sp_head.h" +#define EVEX_MAX_INTERVAL_VALUE 2147483647L + + Event_parse_data * Event_parse_data::new_instance(THD *thd) { @@ -733,29 +736,29 @@ Event_timed::load_from_row(MEM_ROOT *mem_root, TABLE *table) et= this; - if (table->s->fields != Events::FIELD_COUNT) + if (table->s->fields != ET_FIELD_COUNT) goto error; if ((et->dbname.str= get_field(mem_root, - table->field[Events::FIELD_DB])) == NULL) + table->field[ET_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) + table->field[ET_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) + table->field[ET_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) + table->field[ET_FIELD_DEFINER])) == NullS) goto error; et->definer.length= strlen(et->definer.str); @@ -772,27 +775,27 @@ Event_timed::load_from_row(MEM_ROOT *mem_root, TABLE *table) 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]-> + et->starts_null= table->field[ET_FIELD_STARTS]->is_null(); + res1= table->field[ET_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); + et->ends_null= table->field[ET_FIELD_ENDS]->is_null(); + res2= table->field[ET_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(); + if (!table->field[ET_FIELD_INTERVAL_EXPR]->is_null()) + et->expression= table->field[ET_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. + Hence if ET_FIELD_EXECUTE_AT is empty there is an error. */ et->execute_at_null= - table->field[Events::FIELD_EXECUTE_AT]->is_null(); + table->field[ET_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, + table->field[ET_FIELD_EXECUTE_AT]-> get_date(&et->execute_at, TIME_NO_ZERO_DATE)) goto error; @@ -800,22 +803,22 @@ Event_timed::load_from_row(MEM_ROOT *mem_root, TABLE *table) In DB the values start from 1 but enum interval_type starts from 0 */ - if (!table->field[Events::FIELD_TRANSIENT_INTERVAL]->is_null()) + if (!table->field[ET_FIELD_TRANSIENT_INTERVAL]->is_null()) et->interval= (interval_type) ((ulonglong) - table->field[Events::FIELD_TRANSIENT_INTERVAL]->val_int() - 1); + table->field[ET_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(); + et->created= table->field[ET_FIELD_CREATED]->val_int(); + et->modified= table->field[ET_FIELD_MODIFIED]->val_int(); - table->field[Events::FIELD_LAST_EXECUTED]-> + table->field[ET_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) + if ((ptr= get_field(mem_root, table->field[ET_FIELD_STATUS])) == NullS) goto error; DBUG_PRINT("load_from_row", ("Event [%s] is [%s]", et->name.str, ptr)); @@ -823,20 +826,20 @@ Event_timed::load_from_row(MEM_ROOT *mem_root, TABLE *table) /* 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) + table->field[ET_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]); + et->comment.str= get_field(mem_root, table->field[ET_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(); + et->sql_mode= (ulong) table->field[ET_FIELD_SQL_MODE]->val_int(); DBUG_RETURN(0); error: @@ -1277,6 +1280,7 @@ Event_timed::mark_last_executed(THD *thd) } + /* Drops the event @@ -1299,7 +1303,8 @@ Event_timed::drop(THD *thd) uint tmp= 0; DBUG_ENTER("Event_timed::drop"); - DBUG_RETURN(db_drop_event(thd, dbname, name, false, &tmp)); + DBUG_RETURN(Events::get_instance()-> + db_repository.drop_event(thd, dbname, name, false, &tmp)); } @@ -1336,7 +1341,7 @@ Event_timed::update_fields(THD *thd) thd->reset_n_backup_open_tables_state(&backup); - if (Events::open_event_table(thd, TL_WRITE, &table)) + if (Events::get_instance()->open_event_table(thd, TL_WRITE, &table)) { ret= EVEX_OPEN_TABLE_FAILED; goto done; @@ -1352,15 +1357,15 @@ Event_timed::update_fields(THD *thd) if (last_executed_changed) { - table->field[Events::FIELD_LAST_EXECUTED]->set_notnull(); - table->field[Events::FIELD_LAST_EXECUTED]->store_time(&last_executed, + table->field[ET_FIELD_LAST_EXECUTED]->set_notnull(); + table->field[ET_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); + table->field[ET_FIELD_STATUS]->set_notnull(); + table->field[ET_FIELD_STATUS]->store((longlong)status, true); status_changed= false; } @@ -1630,8 +1635,8 @@ Event_timed::compile(THD *thd, MEM_ROOT *mem_root) 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->change_security_context(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; @@ -1669,7 +1674,7 @@ done: lex.et->deinit_mutexes(); lex_end(&lex); - restore_security_context(thd, save_ctx); + thd->restore_security_context(save_ctx); DBUG_PRINT("note", ("return old data on its place. set back NAMES")); thd->lex= old_lex; @@ -1870,23 +1875,6 @@ event_timed_db_equal(Event_timed *et, LEX_STRING *db) } -/* - 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 diff --git a/sql/event_data_objects.h b/sql/event_data_objects.h index a9483465a8d..ff547cebd5b 100644 --- a/sql/event_data_objects.h +++ b/sql/event_data_objects.h @@ -40,9 +40,35 @@ #define EVENT_FREE_WHEN_FINISHED (1L << 2) +#define EVENT_EXEC_STARTED 0 +#define EVENT_EXEC_ALREADY_EXEC 1 +#define EVENT_EXEC_CANT_FORK 2 + + class sp_head; class Sql_alloc; +class Event_timed; + +/* Compares only the schema part of the identifier */ +bool +event_timed_db_equal(Event_timed *et, LEX_STRING *db); + + +/* Compares the whole identifier*/ +bool +event_timed_identifier_equal(Event_timed *a, Event_timed *b); + +/* Compares only the schema part of the identifier */ +bool +event_timed_db_equal(sp_name *name, LEX_STRING *db); + + +/* Compares the whole identifier*/ +bool +event_timed_identifier_equal(sp_name *a, Event_timed *b); + + class Event_timed { Event_timed(const Event_timed &); /* Prevent use of these */ @@ -296,4 +322,10 @@ public: }; + +class Event_queue_element : public Event_timed +{ + +}; + #endif /* _EVENT_DATA_OBJECTS_H_ */ diff --git a/sql/event_db_repository.cc b/sql/event_db_repository.cc index 547e1e6887b..249910c5f50 100644 --- a/sql/event_db_repository.cc +++ b/sql/event_db_repository.cc @@ -17,3 +17,857 @@ #include "mysql_priv.h" #include "event_db_repository.h" #include "event_data_objects.h" +#include "sp_head.h" +#include "sp.h" +#include "events.h" + +#define EVEX_DB_FIELD_LEN 64 +#define EVEX_NAME_FIELD_LEN 64 + + +time_t mysql_event_last_create_time= 0L; + +static +TABLE_FIELD_W_TYPE event_table_fields[ET_FIELD_COUNT] = { + { + {(char *) STRING_WITH_LEN("db")}, + {(char *) STRING_WITH_LEN("char(64)")}, + {(char *) STRING_WITH_LEN("utf8")} + }, + { + {(char *) STRING_WITH_LEN("name")}, + {(char *) STRING_WITH_LEN("char(64)")}, + {(char *) STRING_WITH_LEN("utf8")} + }, + { + {(char *) STRING_WITH_LEN("body")}, + {(char *) STRING_WITH_LEN("longblob")}, + {NULL, 0} + }, + { + {(char *) STRING_WITH_LEN("definer")}, + {(char *) STRING_WITH_LEN("char(77)")}, + {(char *) STRING_WITH_LEN("utf8")} + }, + { + {(char *) STRING_WITH_LEN("execute_at")}, + {(char *) STRING_WITH_LEN("datetime")}, + {NULL, 0} + }, + { + {(char *) STRING_WITH_LEN("interval_value")}, + {(char *) STRING_WITH_LEN("int(11)")}, + {NULL, 0} + }, + { + {(char *) STRING_WITH_LEN("interval_field")}, + {(char *) STRING_WITH_LEN("enum('YEAR','QUARTER','MONTH','DAY'," + "'HOUR','MINUTE','WEEK','SECOND','MICROSECOND','YEAR_MONTH','DAY_HOUR'," + "'DAY_MINUTE','DAY_SECOND','HOUR_MINUTE','HOUR_SECOND','MINUTE_SECOND'," + "'DAY_MICROSECOND','HOUR_MICROSECOND','MINUTE_MICROSECOND'," + "'SECOND_MICROSECOND')")}, + {NULL, 0} + }, + { + {(char *) STRING_WITH_LEN("created")}, + {(char *) STRING_WITH_LEN("timestamp")}, + {NULL, 0} + }, + { + {(char *) STRING_WITH_LEN("modified")}, + {(char *) STRING_WITH_LEN("timestamp")}, + {NULL, 0} + }, + { + {(char *) STRING_WITH_LEN("last_executed")}, + {(char *) STRING_WITH_LEN("datetime")}, + {NULL, 0} + }, + { + {(char *) STRING_WITH_LEN("starts")}, + {(char *) STRING_WITH_LEN("datetime")}, + {NULL, 0} + }, + { + {(char *) STRING_WITH_LEN("ends")}, + {(char *) STRING_WITH_LEN("datetime")}, + {NULL, 0} + }, + { + {(char *) STRING_WITH_LEN("status")}, + {(char *) STRING_WITH_LEN("enum('ENABLED','DISABLED')")}, + {NULL, 0} + }, + { + {(char *) STRING_WITH_LEN("on_completion")}, + {(char *) STRING_WITH_LEN("enum('DROP','PRESERVE')")}, + {NULL, 0} + }, + { + {(char *) STRING_WITH_LEN("sql_mode")}, + {(char *) STRING_WITH_LEN("set('REAL_AS_FLOAT','PIPES_AS_CONCAT','ANSI_QUOTES'," + "'IGNORE_SPACE','NOT_USED','ONLY_FULL_GROUP_BY','NO_UNSIGNED_SUBTRACTION'," + "'NO_DIR_IN_CREATE','POSTGRESQL','ORACLE','MSSQL','DB2','MAXDB'," + "'NO_KEY_OPTIONS','NO_TABLE_OPTIONS','NO_FIELD_OPTIONS','MYSQL323','MYSQL40'," + "'ANSI','NO_AUTO_VALUE_ON_ZERO','NO_BACKSLASH_ESCAPES','STRICT_TRANS_TABLES'," + "'STRICT_ALL_TABLES','NO_ZERO_IN_DATE','NO_ZERO_DATE','INVALID_DATES'," + "'ERROR_FOR_DIVISION_BY_ZERO','TRADITIONAL','NO_AUTO_CREATE_USER'," + "'HIGH_NOT_PRECEDENCE')")}, + {NULL, 0} + }, + { + {(char *) STRING_WITH_LEN("comment")}, + {(char *) STRING_WITH_LEN("char(64)")}, + {(char *) STRING_WITH_LEN("utf8")} + } +}; + + +/* + Puts some data common to CREATE and ALTER EVENT into a row. + + SYNOPSIS + evex_fill_row() + thd THD + table the row to fill out + et Event's data + + RETURN VALUE + 0 - OK + EVEX_GENERAL_ERROR - bad data + EVEX_GET_FIELD_FAILED - field count does not match. table corrupted? + + DESCRIPTION + Used both when an event is created and when it is altered. +*/ + +static int +evex_fill_row(THD *thd, TABLE *table, Event_timed *et, my_bool is_update) +{ + CHARSET_INFO *scs= system_charset_info; + enum enum_events_table_field field_num; + + DBUG_ENTER("evex_fill_row"); + + DBUG_PRINT("info", ("dbname=[%s]", et->dbname.str)); + DBUG_PRINT("info", ("name =[%s]", et->name.str)); + DBUG_PRINT("info", ("body =[%s]", et->body.str)); + + if (table->field[field_num= ET_FIELD_DEFINER]-> + store(et->definer.str, et->definer.length, scs)) + goto err_truncate; + + if (table->field[field_num= ET_FIELD_DB]-> + store(et->dbname.str, et->dbname.length, scs)) + goto err_truncate; + + if (table->field[field_num= ET_FIELD_NAME]-> + store(et->name.str, et->name.length, scs)) + goto err_truncate; + + /* both ON_COMPLETION and STATUS are NOT NULL thus not calling set_notnull()*/ + table->field[ET_FIELD_ON_COMPLETION]-> + store((longlong)et->on_completion, true); + + table->field[ET_FIELD_STATUS]->store((longlong)et->status, true); + + /* + Change the SQL_MODE only if body was present in an ALTER EVENT and of course + always during CREATE EVENT. + */ + if (et->body.str) + { + table->field[ET_FIELD_SQL_MODE]-> + store((longlong)thd->variables.sql_mode, true); + + if (table->field[field_num= ET_FIELD_BODY]-> + store(et->body.str, et->body.length, scs)) + goto err_truncate; + } + + if (et->expression) + { + table->field[ET_FIELD_INTERVAL_EXPR]->set_notnull(); + table->field[ET_FIELD_INTERVAL_EXPR]-> + store((longlong)et->expression, true); + + table->field[ET_FIELD_TRANSIENT_INTERVAL]->set_notnull(); + /* + In the enum (C) intervals start from 0 but in mysql enum valid values start + from 1. Thus +1 offset is needed! + */ + table->field[ET_FIELD_TRANSIENT_INTERVAL]-> + store((longlong)et->interval+1, true); + + table->field[ET_FIELD_EXECUTE_AT]->set_null(); + + if (!et->starts_null) + { + table->field[ET_FIELD_STARTS]->set_notnull(); + table->field[ET_FIELD_STARTS]-> + store_time(&et->starts, MYSQL_TIMESTAMP_DATETIME); + } + + if (!et->ends_null) + { + table->field[ET_FIELD_ENDS]->set_notnull(); + table->field[ET_FIELD_ENDS]-> + store_time(&et->ends, MYSQL_TIMESTAMP_DATETIME); + } + } + else if (et->execute_at.year) + { + table->field[ET_FIELD_INTERVAL_EXPR]->set_null(); + table->field[ET_FIELD_TRANSIENT_INTERVAL]->set_null(); + table->field[ET_FIELD_STARTS]->set_null(); + table->field[ET_FIELD_ENDS]->set_null(); + + table->field[ET_FIELD_EXECUTE_AT]->set_notnull(); + table->field[ET_FIELD_EXECUTE_AT]-> + store_time(&et->execute_at, MYSQL_TIMESTAMP_DATETIME); + } + else + { + DBUG_ASSERT(is_update); + /* + it is normal to be here when the action is update + this is an error if the action is create. something is borked + */ + } + + ((Field_timestamp *)table->field[ET_FIELD_MODIFIED])->set_time(); + + if (et->comment.str) + { + if (table->field[field_num= ET_FIELD_COMMENT]-> + store(et->comment.str, et->comment.length, scs)) + goto err_truncate; + } + + DBUG_RETURN(0); +err_truncate: + my_error(ER_EVENT_DATA_TOO_LONG, MYF(0), table->field[field_num]->field_name); + DBUG_RETURN(EVEX_GENERAL_ERROR); +} + + +/* + Find row in open mysql.event table representing event + + SYNOPSIS + evex_db_find_event_by_name() + thd Thread context + dbname Name of event's database + rname Name of the event inside the db + table TABLE object for open mysql.event table. + + RETURN VALUE + 0 - Routine found + EVEX_KEY_NOT_FOUND - No routine with given name +*/ + +int +evex_db_find_event_by_name(THD *thd, const LEX_STRING dbname, + const LEX_STRING ev_name, + TABLE *table) +{ + return Events::get_instance()->db_repository. + find_event_by_name(thd, dbname, ev_name, table); +} + + +/* + Looks for a named event in mysql.event and in case of success returns + an object will data loaded from the table. + + SYNOPSIS + db_find_event() + thd THD + name the name of the event to find + ett event's data if event is found + tbl TABLE object to use when not NULL + + NOTES + 1) Use sp_name for look up, return in **ett if found + 2) tbl is not closed at exit + + RETURN VALUE + 0 ok In this case *ett is set to the event + # error *ett == 0 +*/ + +int +Event_db_repository::find_event(THD *thd, sp_name *name, Event_timed **ett, + TABLE *tbl, MEM_ROOT *root) +{ + TABLE *table; + int ret; + Event_timed *et= NULL; + DBUG_ENTER("db_find_event"); + DBUG_PRINT("enter", ("name: %*s", name->m_name.length, name->m_name.str)); + + if (tbl) + table= tbl; + else if (Events::get_instance()->db_repository.open_event_table(thd, TL_READ, &table)) + { + my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0)); + ret= EVEX_GENERAL_ERROR; + goto done; + } + + if ((ret= evex_db_find_event_by_name(thd, name->m_db, name->m_name, table))) + { + my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), name->m_name.str); + goto done; + } + et= new Event_timed; + + /* + 1)The table should not be closed beforehand. ::load_from_row() only loads + and does not compile + + 2)::load_from_row() is silent on error therefore we emit error msg here + */ + if ((ret= et->load_from_row(root, table))) + { + my_error(ER_CANNOT_LOAD_FROM_TABLE, MYF(0)); + goto done; + } + +done: + if (ret) + { + delete et; + et= 0; + } + /* don't close the table if we haven't opened it ourselves */ + if (!tbl && table) + close_thread_tables(thd); + *ett= et; + DBUG_RETURN(ret); +} + + +int +Event_db_repository::init_repository() +{ + init_alloc_root(&repo_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC); + return 0; +} + + +void +Event_db_repository::deinit_repository() +{ + free_root(&repo_root, MYF(0)); +} + + +/* + Open mysql.event table for read + + SYNOPSIS + Events::open_event_table() + thd Thread context + lock_type How to lock the table + table We will store the open table here + + RETURN VALUE + 1 Cannot lock table + 2 The table is corrupted - different number of fields + 0 OK +*/ + +int +Event_db_repository::open_event_table(THD *thd, enum thr_lock_type lock_type, + TABLE **table) +{ + TABLE_LIST tables; + DBUG_ENTER("Event_db_repository::open_event_table"); + + bzero((char*) &tables, sizeof(tables)); + tables.db= (char*) "mysql"; + tables.table_name= tables.alias= (char*) "event"; + tables.lock_type= lock_type; + + if (simple_open_n_lock_tables(thd, &tables)) + DBUG_RETURN(1); + + if (table_check_intact(tables.table, ET_FIELD_COUNT, + event_table_fields, + &mysql_event_last_create_time, + ER_CANNOT_LOAD_FROM_TABLE)) + { + close_thread_tables(thd); + DBUG_RETURN(2); + } + *table= tables.table; + tables.table->use_all_columns(); + DBUG_RETURN(0); +} + + +/* + Creates an event in mysql.event + + SYNOPSIS + Event_db_repository::create_event() + thd THD + et Event_timed object containing information for the event + create_if_not If an warning should be generated in case event exists + rows_affected How many rows were affected + + RETURN VALUE + 0 - OK + EVEX_GENERAL_ERROR - Failure + + DESCRIPTION + Creates an event. Relies on evex_fill_row which is shared with + db_update_event. The name of the event is inside "et". +*/ + +int +Event_db_repository::create_event(THD *thd, Event_timed *et, + my_bool create_if_not, uint *rows_affected) +{ + int ret= 0; + CHARSET_INFO *scs= system_charset_info; + TABLE *table; + char olddb[128]; + bool dbchanged= false; + DBUG_ENTER("Event_db_repository::create_event"); + DBUG_PRINT("enter", ("name: %.*s", et->name.length, et->name.str)); + + *rows_affected= 0; + DBUG_PRINT("info", ("open mysql.event for update")); + if (open_event_table(thd, TL_WRITE, &table)) + { + my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0)); + goto err; + } + + DBUG_PRINT("info", ("check existance of an event with the same name")); + if (!evex_db_find_event_by_name(thd, et->dbname, et->name, table)) + { + if (create_if_not) + { + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + ER_EVENT_ALREADY_EXISTS, ER(ER_EVENT_ALREADY_EXISTS), + et->name.str); + goto ok; + } + my_error(ER_EVENT_ALREADY_EXISTS, MYF(0), et->name.str); + goto err; + } + + DBUG_PRINT("info", ("non-existant, go forward")); + if ((ret= sp_use_new_db(thd, et->dbname.str,olddb, sizeof(olddb),0, + &dbchanged))) + { + my_error(ER_BAD_DB_ERROR, MYF(0)); + goto err; + } + + restore_record(table, s->default_values); // Get default values for fields + + if (system_charset_info->cset->numchars(system_charset_info, et->dbname.str, + et->dbname.str + et->dbname.length) + > EVEX_DB_FIELD_LEN) + { + my_error(ER_TOO_LONG_IDENT, MYF(0), et->dbname.str); + goto err; + } + if (system_charset_info->cset->numchars(system_charset_info, et->name.str, + et->name.str + et->name.length) + > EVEX_DB_FIELD_LEN) + { + my_error(ER_TOO_LONG_IDENT, MYF(0), et->name.str); + goto err; + } + + if (et->body.length > table->field[ET_FIELD_BODY]->field_length) + { + my_error(ER_TOO_LONG_BODY, MYF(0), et->name.str); + goto err; + } + + if (!(et->expression) && !(et->execute_at.year)) + { + DBUG_PRINT("error", ("neither expression nor execute_at are set!")); + my_error(ER_EVENT_NEITHER_M_EXPR_NOR_M_AT, MYF(0)); + goto err; + } + + ((Field_timestamp *)table->field[ET_FIELD_CREATED])->set_time(); + + /* + evex_fill_row() calls my_error() in case of error so no need to + handle it here + */ + if ((ret= evex_fill_row(thd, table, et, false))) + goto err; + + if (table->file->ha_write_row(table->record[0])) + { + my_error(ER_EVENT_STORE_FAILED, MYF(0), et->name.str, ret); + goto err; + } + +#ifdef USE_THIS_CODE_AS_TEMPLATE_WHEN_EVENT_REPLICATION_IS_AGREED + if (mysql_bin_log.is_open()) + { + thd->clear_error(); + /* Such a statement can always go directly to binlog, no trans cache */ + thd->binlog_query(THD::MYSQL_QUERY_TYPE, thd->query, thd->query_length, + FALSE, FALSE); + } +#endif + + *rows_affected= 1; +ok: + if (dbchanged) + (void) mysql_change_db(thd, olddb, 1); + if (table) + close_thread_tables(thd); + DBUG_RETURN(EVEX_OK); + +err: + if (dbchanged) + (void) mysql_change_db(thd, olddb, 1); + if (table) + close_thread_tables(thd); + DBUG_RETURN(EVEX_GENERAL_ERROR); +} + + +/* + Used to execute ALTER EVENT. Pendant to Events::update_event(). + + SYNOPSIS + Event_db_repository::update_event() + thd THD + sp_name the name of the event to alter + et event's data + + RETURN VALUE + 0 OK + EVEX_GENERAL_ERROR Error occured (my_error() called) + + NOTES + sp_name is passed since this is the name of the event to + alter in case of RENAME TO. +*/ + +int +Event_db_repository::update_event(THD *thd, Event_timed *et, sp_name *new_name) +{ + CHARSET_INFO *scs= system_charset_info; + TABLE *table; + int ret= EVEX_OPEN_TABLE_FAILED; + DBUG_ENTER("Event_db_repository::update_event"); + DBUG_PRINT("enter", ("dbname: %.*s", et->dbname.length, et->dbname.str)); + DBUG_PRINT("enter", ("name: %.*s", et->name.length, et->name.str)); + DBUG_PRINT("enter", ("user: %.*s", et->definer.length, et->definer.str)); + if (new_name) + DBUG_PRINT("enter", ("rename to: %.*s", new_name->m_name.length, + new_name->m_name.str)); + + if (open_event_table(thd, TL_WRITE, &table)) + { + my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0)); + goto err; + } + + /* first look whether we overwrite */ + if (new_name) + { + if (!sortcmp_lex_string(et->name, new_name->m_name, scs) && + !sortcmp_lex_string(et->dbname, new_name->m_db, scs)) + { + my_error(ER_EVENT_SAME_NAME, MYF(0), et->name.str); + goto err; + } + + if (!evex_db_find_event_by_name(thd,new_name->m_db,new_name->m_name,table)) + { + my_error(ER_EVENT_ALREADY_EXISTS, MYF(0), new_name->m_name.str); + goto err; + } + } + /* + ...and then if there is such an event. Don't exchange the blocks + because you will get error 120 from table handler because new_name will + overwrite the key and SE will tell us that it cannot find the already found + row (copied into record[1] later + */ + if (EVEX_KEY_NOT_FOUND == find_event_by_name(thd, et->dbname, et->name, table)) + { + my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), et->name.str); + goto err; + } + + store_record(table,record[1]); + + /* Don't update create on row update. */ + table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; + + /* + evex_fill_row() calls my_error() in case of error so no need to + handle it here + */ + if ((ret= evex_fill_row(thd, table, et, true))) + goto err; + + if (new_name) + { + table->field[ET_FIELD_DB]-> + store(new_name->m_db.str, new_name->m_db.length, scs); + table->field[ET_FIELD_NAME]-> + store(new_name->m_name.str, new_name->m_name.length, scs); + } + + if ((ret= table->file->ha_update_row(table->record[1], table->record[0]))) + { + my_error(ER_EVENT_STORE_FAILED, MYF(0), et->name.str, ret); + goto err; + } + + /* close mysql.event or we crash later when loading the event from disk */ + close_thread_tables(thd); + DBUG_RETURN(0); + +err: + if (table) + close_thread_tables(thd); + DBUG_RETURN(EVEX_GENERAL_ERROR); +} + + +/* + Drops an event + + SYNOPSIS + Event_db_repository::drop_event() + thd THD + db database name + name event's name + drop_if_exists if set and the event not existing => warning onto the stack + rows_affected affected number of rows is returned heres + + RETURN VALUE + 0 OK + !0 Error (my_error() called) +*/ + +int +Event_db_repository::drop_event(THD *thd, LEX_STRING db, LEX_STRING name, + bool drop_if_exists, uint *rows_affected) +{ + TABLE *table; + Open_tables_state backup; + int ret; + + DBUG_ENTER("Event_db_repository::drop_event"); + DBUG_PRINT("enter", ("db=%s name=%s", db.str, name.str)); + ret= EVEX_OPEN_TABLE_FAILED; + + thd->reset_n_backup_open_tables_state(&backup); + if (open_event_table(thd, TL_WRITE, &table)) + { + my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0)); + goto done; + } + + if (!(ret= evex_db_find_event_by_name(thd, db, name, table))) + { + if ((ret= table->file->ha_delete_row(table->record[0]))) + my_error(ER_EVENT_CANNOT_DELETE, MYF(0)); + } + else if (ret == EVEX_KEY_NOT_FOUND) + { + if (drop_if_exists) + { + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + ER_SP_DOES_NOT_EXIST, ER(ER_SP_DOES_NOT_EXIST), + "Event", name.str); + ret= 0; + } else + my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), name.str); + } + +done: + /* + evex_drop_event() is used by Event_timed::drop therefore + we have to close our thread tables. + */ + close_thread_tables(thd); + thd->restore_backup_open_tables_state(&backup); + DBUG_RETURN(ret); +} + + +int +Event_db_repository::find_event_by_name(THD *thd, LEX_STRING db, + LEX_STRING name, TABLE *table) +{ + byte key[MAX_KEY_LENGTH]; + DBUG_ENTER("Event_db_repository::find_event_by_name"); + DBUG_PRINT("enter", ("name: %.*s", name.length, name.str)); + + /* + Create key to find row. We have to use field->store() to be able to + handle VARCHAR and CHAR fields. + Assumption here is that the two first fields in the table are + 'db' and 'name' and the first key is the primary key over the + same fields. + */ + if (db.length > table->field[ET_FIELD_DB]->field_length || + name.length > table->field[ET_FIELD_NAME]->field_length) + + DBUG_RETURN(EVEX_KEY_NOT_FOUND); + + table->field[ET_FIELD_DB]->store(db.str, db.length, &my_charset_bin); + table->field[ET_FIELD_NAME]->store(name.str, name.length, &my_charset_bin); + + key_copy(key, table->record[0], table->key_info, + table->key_info->key_length); + + if (table->file->index_read_idx(table->record[0], 0, key, + table->key_info->key_length, + HA_READ_KEY_EXACT)) + { + DBUG_PRINT("info", ("Row not found")); + DBUG_RETURN(EVEX_KEY_NOT_FOUND); + } + + DBUG_PRINT("info", ("Row found!")); + DBUG_RETURN(0); +} + + +int +Event_db_repository::drop_schema_events(THD *thd, LEX_STRING schema) +{ + return drop_events_by_field(thd, ET_FIELD_DB, schema); +} + + +int +Event_db_repository::drop_user_events(THD *thd, LEX_STRING definer) +{ + return drop_events_by_field(thd, ET_FIELD_DEFINER, definer); +} + + +/* + Drops all events in the selected database, from mysql.event. + + SYNOPSIS + drop_events_from_table_by_field() + thd Thread + table mysql.event TABLE + field Which field of the row to use for matching + field_value The value that should match + + RETURN VALUE + 0 OK + !0 Error from ha_delete_row +*/ + +int +Event_db_repository::drop_events_by_field(THD *thd, + enum enum_events_table_field field, + LEX_STRING field_value) +{ + int ret= 0; + TABLE *table; + Open_tables_state backup; + READ_RECORD read_record_info; + DBUG_ENTER("drop_events_from_table_by_field"); + DBUG_PRINT("enter", ("field=%d field_value=%s", field, field_value.str)); + + if (open_event_table(thd, TL_WRITE, &table)) + { + my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0)); + DBUG_RETURN(1); + } + + /* only enabled events are in memory, so we go now and delete the rest */ + init_read_record(&read_record_info, thd, table, NULL, 1, 0); + while (!ret && !(read_record_info.read_record(&read_record_info)) ) + { + char *et_field= get_field(thd->mem_root, table->field[field]); + + LEX_STRING et_field_lex= {et_field, strlen(et_field)}; + DBUG_PRINT("info", ("Current event %s name=%s", et_field, + get_field(thd->mem_root, table->field[ET_FIELD_NAME]))); + + if (!sortcmp_lex_string(et_field_lex, field_value, system_charset_info)) + { + DBUG_PRINT("info", ("Dropping")); + if ((ret= table->file->ha_delete_row(table->record[0]))) + my_error(ER_EVENT_DROP_FAILED, MYF(0), + get_field(thd->mem_root, table->field[ET_FIELD_NAME])); + } + } + end_read_record(&read_record_info); + thd->version--; /* Force close to free memory */ + + DBUG_RETURN(ret); +} + + +/* + Looks for a named event in mysql.event and then loads it from + the table, compiles and inserts it into the cache. + + SYNOPSIS + Event_scheduler::load_named_event() + thd THD + etn The name of the event to load and compile on scheduler's root + etn_new The loaded event + + RETURN VALUE + NULL Error during compile or the event is non-enabled. + otherwise Address +*/ + +int +Event_db_repository::load_named_event(THD *thd, Event_timed *etn, + Event_timed **etn_new) +{ + int ret= 0; + MEM_ROOT *tmp_mem_root; + Event_timed *et_loaded= NULL; + Open_tables_state backup; + + DBUG_ENTER("Event_scheduler::load_and_compile_event"); + DBUG_PRINT("enter",("thd=%p name:%*s",thd, etn->name.length, etn->name.str)); + + thd->reset_n_backup_open_tables_state(&backup); + /* No need to use my_error() here because db_find_event() has done it */ + { + sp_name spn(etn->dbname, etn->name); + ret= find_event(thd, &spn, &et_loaded, NULL, &repo_root); + } + thd->restore_backup_open_tables_state(&backup); + /* In this case no memory was allocated so we don't need to clean */ + if (ret) + DBUG_RETURN(OP_LOAD_ERROR); + + if (et_loaded->status != Event_timed::ENABLED) + { + /* + We don't load non-enabled events. + In db_find_event() `et_new` was allocated on the heap and not on + scheduler_root therefore we delete it here. + */ + delete et_loaded; + DBUG_RETURN(OP_DISABLED_EVENT); + } + + et_loaded->compute_next_execution_time(); + *etn_new= et_loaded; + + DBUG_RETURN(OP_OK); +} diff --git a/sql/event_db_repository.h b/sql/event_db_repository.h index d8a8784089e..c0cfccf215a 100644 --- a/sql/event_db_repository.h +++ b/sql/event_db_repository.h @@ -16,5 +16,87 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +enum enum_events_table_field +{ + ET_FIELD_DB = 0, + ET_FIELD_NAME, + ET_FIELD_BODY, + ET_FIELD_DEFINER, + ET_FIELD_EXECUTE_AT, + ET_FIELD_INTERVAL_EXPR, + ET_FIELD_TRANSIENT_INTERVAL, + ET_FIELD_CREATED, + ET_FIELD_MODIFIED, + ET_FIELD_LAST_EXECUTED, + ET_FIELD_STARTS, + ET_FIELD_ENDS, + ET_FIELD_STATUS, + ET_FIELD_ON_COMPLETION, + ET_FIELD_SQL_MODE, + ET_FIELD_COMMENT, + ET_FIELD_COUNT /* a cool trick to count the number of fields :) */ +}; + + +int +evex_db_find_event_by_name(THD *thd, const LEX_STRING dbname, + const LEX_STRING ev_name, + TABLE *table); + +class Event_queue_element; + +class Event_db_repository +{ +public: + Event_db_repository(){} + ~Event_db_repository(){} + + int + init_repository(); + + void + deinit_repository(); + + int + open_event_table(THD *thd, enum thr_lock_type lock_type, TABLE **table); + + int + create_event(THD *thd, Event_timed *et, my_bool create_if_not, + uint *rows_affected); + + int + update_event(THD *thd, Event_timed *et, sp_name *new_name); + + int + drop_event(THD *thd, LEX_STRING db, LEX_STRING name, bool drop_if_exists, + uint *rows_affected); + + int + drop_schema_events(THD *thd, LEX_STRING schema); + + int + drop_user_events(THD *thd, LEX_STRING definer); + + int + find_event(THD *thd, sp_name *name, Event_timed **ett, TABLE *tbl, + MEM_ROOT *root); + + int + load_named_event(THD *thd, Event_timed *etn, Event_timed **etn_new); + + int + find_event_by_name(THD *thd, LEX_STRING db, LEX_STRING name, TABLE *table); +private: + + int + drop_events_by_field(THD *thd, enum enum_events_table_field field, + LEX_STRING field_value); + + MEM_ROOT repo_root; + + /* Prevent use of these */ + Event_db_repository(const Event_db_repository &); + void operator=(Event_db_repository &); +}; #endif /* _EVENT_DB_REPOSITORY_H_ */ diff --git a/sql/event_scheduler.cc b/sql/event_scheduler.cc index f83bc5f25a3..60c9cda0a56 100644 --- a/sql/event_scheduler.cc +++ b/sql/event_scheduler.cc @@ -15,10 +15,10 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "mysql_priv.h" -#include "events_priv.h" #include "events.h" #include "event_data_objects.h" #include "event_scheduler.h" +#include "event_db_repository.h" #include "sp_head.h" /* @@ -574,8 +574,8 @@ event_worker_thread(void *arg) to change the context before sending the signal. We are under LOCK_scheduler_data being held by Event_scheduler::run() -> ::execute_top(). */ - change_security_context(thd, event->definer_user, event->definer_host, - event->dbname, &security_ctx, &save_ctx); + thd->change_security_context(event->definer_user, event->definer_host, + event->dbname, &security_ctx, &save_ctx); DBUG_PRINT("info", ("master_access=%d db_access=%d", thd->security_ctx->master_access, thd->security_ctx->db_access)); @@ -687,7 +687,7 @@ Event_scheduler::get_instance() */ bool -Event_scheduler::init() +Event_scheduler::init(Event_db_repository *db_repo) { int i= 0; bool ret= FALSE; @@ -695,6 +695,7 @@ Event_scheduler::init() DBUG_PRINT("enter", ("this=%p", this)); LOCK_SCHEDULER_DATA(); + db_repository= db_repo; for (;i < COND_LAST; i++) if (pthread_cond_init(&cond_vars[i], NULL)) { @@ -783,10 +784,10 @@ Event_scheduler::destroy() OP_LOAD_ERROR Error during loading from disk */ -enum Event_scheduler::enum_error_code +int Event_scheduler::create_event(THD *thd, Event_timed *et, bool check_existence) { - enum enum_error_code res; + int res; Event_timed *et_new; DBUG_ENTER("Event_scheduler::create_event"); DBUG_PRINT("enter", ("thd=%p et=%p lock=%p",thd,et,&LOCK_scheduler_data)); @@ -805,7 +806,7 @@ Event_scheduler::create_event(THD *thd, Event_timed *et, bool check_existence) } /* We need to load the event on scheduler_root */ - if (!(res= load_named_event(thd, et, &et_new))) + if (!(res= db_repository->load_named_event(thd, et, &et_new))) { queue_insert_safe(&queue, (byte *) et_new); DBUG_PRINT("info", ("Sending COND_new_work")); @@ -904,12 +905,12 @@ Event_scheduler::drop_event(THD *thd, sp_name *name) OP_ALREADY_EXISTS Event already in the queue */ -enum Event_scheduler::enum_error_code +int Event_scheduler::update_event(THD *thd, Event_timed *et, LEX_STRING *new_schema, LEX_STRING *new_name) { - enum enum_error_code res; + int res= OP_OK; Event_timed *et_old, *et_new= NULL; LEX_STRING old_schema, old_name; @@ -947,7 +948,7 @@ Event_scheduler::update_event(THD *thd, Event_timed *et, 1. Error occured 2. If the replace is DISABLED, we don't load it into the queue. */ - if (!(res= load_named_event(thd, et, &et_new))) + if (!(res= db_repository->load_named_event(thd, et, &et_new))) { queue_insert_safe(&queue, (byte *) et_new); DBUG_PRINT("info", ("Sending COND_new_work")); @@ -961,7 +962,7 @@ Event_scheduler::update_event(THD *thd, Event_timed *et, et->dbname= old_schema; et->name= old_name; } - + DBUG_PRINT("info", ("res=%d", res)); UNLOCK_SCHEDULER_DATA(); /* Andrey: Is this comment still truthful ??? @@ -1111,11 +1112,11 @@ Event_scheduler::find_event(sp_name *name, bool remove_from_q) */ void -Event_scheduler::drop_matching_events(THD *thd, LEX_STRING *pattern, +Event_scheduler::drop_matching_events(THD *thd, LEX_STRING pattern, bool (*comparator)(Event_timed *,LEX_STRING *)) { DBUG_ENTER("Event_scheduler::drop_matching_events"); - DBUG_PRINT("enter", ("pattern=%*s state=%d", pattern->length, pattern->str, + DBUG_PRINT("enter", ("pattern=%*s state=%d", pattern.length, pattern.str, state)); if (is_running_or_suspended()) { @@ -1124,7 +1125,7 @@ Event_scheduler::drop_matching_events(THD *thd, LEX_STRING *pattern, { Event_timed *et= (Event_timed *) queue_element(&queue, i); DBUG_PRINT("info", ("[%s.%s]?", et->dbname.str, et->name.str)); - if (comparator(et, pattern)) + if (comparator(et, &pattern)) { /* The queue is ordered. If we remove an element, then all elements after @@ -1179,7 +1180,7 @@ Event_scheduler::drop_matching_events(THD *thd, LEX_STRING *pattern, */ int -Event_scheduler::drop_schema_events(THD *thd, LEX_STRING *schema) +Event_scheduler::drop_schema_events(THD *thd, LEX_STRING schema) { int ret; DBUG_ENTER("Event_scheduler::drop_schema_events"); @@ -1187,7 +1188,6 @@ Event_scheduler::drop_schema_events(THD *thd, LEX_STRING *schema) if (is_running_or_suspended()) drop_matching_events(thd, schema, event_timed_db_equal); - ret= db_drop_events_from_table(thd, schema); UNLOCK_SCHEDULER_DATA(); DBUG_RETURN(ret); @@ -1713,7 +1713,7 @@ Event_scheduler::stop_all_running_events(THD *thd) The caller must have acquited LOCK_scheduler_data. */ -enum Event_scheduler::enum_error_code +int Event_scheduler::stop() { THD *thd= current_thd; @@ -1778,7 +1778,7 @@ Event_scheduler::stop() OP_OK OK */ -enum Event_scheduler::enum_error_code +int Event_scheduler::suspend_or_resume( enum Event_scheduler::enum_suspend_or_resume action) { @@ -2116,59 +2116,6 @@ Event_scheduler::events_count() } -/* - Looks for a named event in mysql.event and then loads it from - the table, compiles and inserts it into the cache. - - SYNOPSIS - Event_scheduler::load_named_event() - thd THD - etn The name of the event to load and compile on scheduler's root - etn_new The loaded event - - RETURN VALUE - NULL Error during compile or the event is non-enabled. - otherwise Address -*/ - -enum Event_scheduler::enum_error_code -Event_scheduler::load_named_event(THD *thd, Event_timed *etn, Event_timed **etn_new) -{ - int ret= 0; - MEM_ROOT *tmp_mem_root; - Event_timed *et_loaded= NULL; - Open_tables_state backup; - - DBUG_ENTER("Event_scheduler::load_and_compile_event"); - DBUG_PRINT("enter",("thd=%p name:%*s",thd, etn->name.length, etn->name.str)); - - thd->reset_n_backup_open_tables_state(&backup); - /* No need to use my_error() here because db_find_event() has done it */ - { - sp_name spn(etn->dbname, etn->name); - ret= db_find_event(thd, &spn, &et_loaded, NULL, &scheduler_root); - } - thd->restore_backup_open_tables_state(&backup); - /* In this case no memory was allocated so we don't need to clean */ - if (ret) - DBUG_RETURN(OP_LOAD_ERROR); - - if (et_loaded->status != Event_timed::ENABLED) - { - /* - We don't load non-enabled events. - In db_find_event() `et_new` was allocated on the heap and not on - scheduler_root therefore we delete it here. - */ - delete et_loaded; - DBUG_RETURN(OP_DISABLED_EVENT); - } - - et_loaded->compute_next_execution_time(); - *etn_new= et_loaded; - - DBUG_RETURN(OP_OK); -} /* @@ -2212,7 +2159,7 @@ Event_scheduler::load_events_from_db(THD *thd) DBUG_RETURN(EVEX_GENERAL_ERROR); } - if ((ret= Events::open_event_table(thd, TL_READ, &table))) + if ((ret= Events::get_instance()->open_event_table(thd, TL_READ, &table))) { sql_print_error("SCHEDULER: Table mysql.event is damaged. Can not open."); DBUG_RETURN(EVEX_OPEN_TABLE_FAILED); diff --git a/sql/event_scheduler.h b/sql/event_scheduler.h index 183ef450be2..bd099d10839 100644 --- a/sql/event_scheduler.h +++ b/sql/event_scheduler.h @@ -18,6 +18,7 @@ class sp_name; class Event_timed; +class Event_db_repository; class THD; typedef bool * (*event_timed_identifier_comparator)(Event_timed*, Event_timed*); @@ -31,17 +32,6 @@ events_shutdown(); class Event_scheduler { public: - /* Return codes */ - enum enum_error_code - { - OP_OK= 0, - OP_NOT_RUNNING, - OP_CANT_KILL, - OP_CANT_INIT, - OP_DISABLED_EVENT, - OP_LOAD_ERROR, - OP_ALREADY_EXISTS - }; enum enum_state { @@ -66,10 +56,10 @@ public: /* Methods for queue management follow */ - enum enum_error_code + int create_event(THD *thd, Event_timed *et, bool check_existence); - enum enum_error_code + int update_event(THD *thd, Event_timed *et, LEX_STRING *new_schema, LEX_STRING *new_name); @@ -78,10 +68,10 @@ public: int - drop_schema_events(THD *thd, LEX_STRING *schema); + drop_schema_events(THD *thd, LEX_STRING schema); int - drop_user_events(THD *thd, LEX_STRING *definer, uint *dropped_num) + drop_user_events(THD *thd, LEX_STRING *definer) { DBUG_ASSERT(0); return 0;} uint @@ -92,20 +82,24 @@ public: bool start(); - enum enum_error_code + int stop(); bool start_suspended(); + /* + Need to be public because has to be called from the function + passed to pthread_create. + */ bool run(THD *thd); - enum enum_error_code + int suspend_or_resume(enum enum_suspend_or_resume action); bool - init(); + init(Event_db_repository *db_repo); void destroy(); @@ -156,14 +150,11 @@ private: void stop_all_running_events(THD *thd); - enum enum_error_code - load_named_event(THD *thd, Event_timed *etn, Event_timed **etn_new); - int load_events_from_db(THD *thd); void - drop_matching_events(THD *thd, LEX_STRING *pattern, + drop_matching_events(THD *thd, LEX_STRING pattern, bool (*)(Event_timed *,LEX_STRING *)); bool @@ -230,6 +221,8 @@ private: /* The MEM_ROOT of the object */ MEM_ROOT scheduler_root; + Event_db_repository *db_repository; + /* The sorted queue with the Event_timed objects */ QUEUE queue; diff --git a/sql/events.cc b/sql/events.cc index c9d05994873..52e814043d8 100644 --- a/sql/events.cc +++ b/sql/events.cc @@ -15,11 +15,10 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "mysql_priv.h" -#include "events_priv.h" #include "events.h" #include "event_data_objects.h" #include "event_scheduler.h" -#include "sp.h" +#include "event_db_repository.h" #include "sp_head.h" /* @@ -48,10 +47,6 @@ Warning: */ -MEM_ROOT evex_mem_root; -time_t mysql_event_last_create_time= 0L; - - const char *event_scheduler_state_names[]= { "OFF", "0", "ON", "1", "SUSPEND", "2", NullS }; @@ -63,104 +58,10 @@ TYPELIB Events::opt_typelib= NULL }; +Events Events::singleton; ulong Events::opt_event_scheduler= 2; -static -TABLE_FIELD_W_TYPE event_table_fields[Events::FIELD_COUNT] = { - { - {(char *) STRING_WITH_LEN("db")}, - {(char *) STRING_WITH_LEN("char(64)")}, - {(char *) STRING_WITH_LEN("utf8")} - }, - { - {(char *) STRING_WITH_LEN("name")}, - {(char *) STRING_WITH_LEN("char(64)")}, - {(char *) STRING_WITH_LEN("utf8")} - }, - { - {(char *) STRING_WITH_LEN("body")}, - {(char *) STRING_WITH_LEN("longblob")}, - {NULL, 0} - }, - { - {(char *) STRING_WITH_LEN("definer")}, - {(char *) STRING_WITH_LEN("char(77)")}, - {(char *) STRING_WITH_LEN("utf8")} - }, - { - {(char *) STRING_WITH_LEN("execute_at")}, - {(char *) STRING_WITH_LEN("datetime")}, - {NULL, 0} - }, - { - {(char *) STRING_WITH_LEN("interval_value")}, - {(char *) STRING_WITH_LEN("int(11)")}, - {NULL, 0} - }, - { - {(char *) STRING_WITH_LEN("interval_field")}, - {(char *) STRING_WITH_LEN("enum('YEAR','QUARTER','MONTH','DAY'," - "'HOUR','MINUTE','WEEK','SECOND','MICROSECOND','YEAR_MONTH','DAY_HOUR'," - "'DAY_MINUTE','DAY_SECOND','HOUR_MINUTE','HOUR_SECOND','MINUTE_SECOND'," - "'DAY_MICROSECOND','HOUR_MICROSECOND','MINUTE_MICROSECOND'," - "'SECOND_MICROSECOND')")}, - {NULL, 0} - }, - { - {(char *) STRING_WITH_LEN("created")}, - {(char *) STRING_WITH_LEN("timestamp")}, - {NULL, 0} - }, - { - {(char *) STRING_WITH_LEN("modified")}, - {(char *) STRING_WITH_LEN("timestamp")}, - {NULL, 0} - }, - { - {(char *) STRING_WITH_LEN("last_executed")}, - {(char *) STRING_WITH_LEN("datetime")}, - {NULL, 0} - }, - { - {(char *) STRING_WITH_LEN("starts")}, - {(char *) STRING_WITH_LEN("datetime")}, - {NULL, 0} - }, - { - {(char *) STRING_WITH_LEN("ends")}, - {(char *) STRING_WITH_LEN("datetime")}, - {NULL, 0} - }, - { - {(char *) STRING_WITH_LEN("status")}, - {(char *) STRING_WITH_LEN("enum('ENABLED','DISABLED')")}, - {NULL, 0} - }, - { - {(char *) STRING_WITH_LEN("on_completion")}, - {(char *) STRING_WITH_LEN("enum('DROP','PRESERVE')")}, - {NULL, 0} - }, - { - {(char *) STRING_WITH_LEN("sql_mode")}, - {(char *) STRING_WITH_LEN("set('REAL_AS_FLOAT','PIPES_AS_CONCAT','ANSI_QUOTES'," - "'IGNORE_SPACE','NOT_USED','ONLY_FULL_GROUP_BY','NO_UNSIGNED_SUBTRACTION'," - "'NO_DIR_IN_CREATE','POSTGRESQL','ORACLE','MSSQL','DB2','MAXDB'," - "'NO_KEY_OPTIONS','NO_TABLE_OPTIONS','NO_FIELD_OPTIONS','MYSQL323','MYSQL40'," - "'ANSI','NO_AUTO_VALUE_ON_ZERO','NO_BACKSLASH_ESCAPES','STRICT_TRANS_TABLES'," - "'STRICT_ALL_TABLES','NO_ZERO_IN_DATE','NO_ZERO_DATE','INVALID_DATES'," - "'ERROR_FOR_DIVISION_BY_ZERO','TRADITIONAL','NO_AUTO_CREATE_USER'," - "'HIGH_NOT_PRECEDENCE')")}, - {NULL, 0} - }, - { - {(char *) STRING_WITH_LEN("comment")}, - {(char *) STRING_WITH_LEN("char(64)")}, - {(char *) STRING_WITH_LEN("utf8")} - } -}; - /* Compares 2 LEX strings regarding case. @@ -188,6 +89,14 @@ int sortcmp_lex_string(LEX_STRING s, LEX_STRING t, CHARSET_INFO *cs) } +Events * +Events::get_instance() +{ + DBUG_ENTER("Events::get_instance"); + DBUG_RETURN(&singleton); +} + + /* Reconstructs interval expression from interval type and expression value that is in form of a value of the smalles entity: @@ -207,9 +116,8 @@ int sortcmp_lex_string(LEX_STRING s, LEX_STRING t, CHARSET_INFO *cs) */ int -Events::reconstruct_interval_expression(String *buf, - interval_type interval, - longlong expression) +Events::reconstruct_interval_expression(String *buf, interval_type interval, + longlong expression) { ulonglong expr= expression; char tmp_buff[128], *end; @@ -341,545 +249,7 @@ int Events::open_event_table(THD *thd, enum thr_lock_type lock_type, TABLE **table) { - TABLE_LIST tables; - DBUG_ENTER("open_events_table"); - - bzero((char*) &tables, sizeof(tables)); - tables.db= (char*) "mysql"; - tables.table_name= tables.alias= (char*) "event"; - tables.lock_type= lock_type; - - if (simple_open_n_lock_tables(thd, &tables)) - DBUG_RETURN(1); - - if (table_check_intact(tables.table, Events::FIELD_COUNT, - event_table_fields, - &mysql_event_last_create_time, - ER_CANNOT_LOAD_FROM_TABLE)) - { - close_thread_tables(thd); - DBUG_RETURN(2); - } - *table= tables.table; - tables.table->use_all_columns(); - DBUG_RETURN(0); -} - - -/* - Find row in open mysql.event table representing event - - SYNOPSIS - evex_db_find_event_aux() - thd Thread context - et event_timed object containing dbname & name - table TABLE object for open mysql.event table. - - RETURN VALUE - 0 - Routine found - EVEX_KEY_NOT_FOUND - No routine with given name -*/ - -inline int -evex_db_find_event_aux(THD *thd, Event_timed *et, TABLE *table) -{ - return evex_db_find_event_by_name(thd, et->dbname, et->name, table); -} - - -/* - Find row in open mysql.event table representing event - - SYNOPSIS - evex_db_find_event_by_name() - thd Thread context - dbname Name of event's database - rname Name of the event inside the db - table TABLE object for open mysql.event table. - - RETURN VALUE - 0 - Routine found - EVEX_KEY_NOT_FOUND - No routine with given name -*/ - -int -evex_db_find_event_by_name(THD *thd, const LEX_STRING dbname, - const LEX_STRING ev_name, - TABLE *table) -{ - byte key[MAX_KEY_LENGTH]; - DBUG_ENTER("evex_db_find_event_by_name"); - DBUG_PRINT("enter", ("name: %.*s", ev_name.length, ev_name.str)); - - /* - Create key to find row. We have to use field->store() to be able to - handle VARCHAR and CHAR fields. - Assumption here is that the two first fields in the table are - 'db' and 'name' and the first key is the primary key over the - same fields. - */ - if (dbname.length > table->field[Events::FIELD_DB]->field_length || - ev_name.length > table->field[Events::FIELD_NAME]->field_length) - - DBUG_RETURN(EVEX_KEY_NOT_FOUND); - - table->field[Events::FIELD_DB]->store(dbname.str, dbname.length, - &my_charset_bin); - table->field[Events::FIELD_NAME]->store(ev_name.str, ev_name.length, - &my_charset_bin); - - key_copy(key, table->record[0], table->key_info, - table->key_info->key_length); - - if (table->file->index_read_idx(table->record[0], 0, key, - table->key_info->key_length, - HA_READ_KEY_EXACT)) - { - DBUG_PRINT("info", ("Row not found")); - DBUG_RETURN(EVEX_KEY_NOT_FOUND); - } - - DBUG_PRINT("info", ("Row found!")); - DBUG_RETURN(0); -} - - -/* - Puts some data common to CREATE and ALTER EVENT into a row. - - SYNOPSIS - evex_fill_row() - thd THD - table the row to fill out - et Event's data - - RETURN VALUE - 0 - OK - EVEX_GENERAL_ERROR - bad data - EVEX_GET_FIELD_FAILED - field count does not match. table corrupted? - - DESCRIPTION - Used both when an event is created and when it is altered. -*/ - -static int -evex_fill_row(THD *thd, TABLE *table, Event_timed *et, my_bool is_update) -{ - CHARSET_INFO *scs= system_charset_info; - enum Events::enum_table_field field_num; - - DBUG_ENTER("evex_fill_row"); - - DBUG_PRINT("info", ("dbname=[%s]", et->dbname.str)); - DBUG_PRINT("info", ("name =[%s]", et->name.str)); - DBUG_PRINT("info", ("body =[%s]", et->body.str)); - - if (table->field[field_num= Events::FIELD_DEFINER]-> - store(et->definer.str, et->definer.length, scs)) - goto err_truncate; - - if (table->field[field_num= Events::FIELD_DB]-> - store(et->dbname.str, et->dbname.length, scs)) - goto err_truncate; - - if (table->field[field_num= Events::FIELD_NAME]-> - store(et->name.str, et->name.length, scs)) - goto err_truncate; - - /* both ON_COMPLETION and STATUS are NOT NULL thus not calling set_notnull()*/ - table->field[Events::FIELD_ON_COMPLETION]-> - store((longlong)et->on_completion, true); - - table->field[Events::FIELD_STATUS]->store((longlong)et->status, true); - - /* - Change the SQL_MODE only if body was present in an ALTER EVENT and of course - always during CREATE EVENT. - */ - if (et->body.str) - { - table->field[Events::FIELD_SQL_MODE]-> - store((longlong)thd->variables.sql_mode, true); - - if (table->field[field_num= Events::FIELD_BODY]-> - store(et->body.str, et->body.length, scs)) - goto err_truncate; - } - - if (et->expression) - { - table->field[Events::FIELD_INTERVAL_EXPR]->set_notnull(); - table->field[Events::FIELD_INTERVAL_EXPR]-> - store((longlong)et->expression, true); - - table->field[Events::FIELD_TRANSIENT_INTERVAL]->set_notnull(); - /* - In the enum (C) intervals start from 0 but in mysql enum valid values start - from 1. Thus +1 offset is needed! - */ - table->field[Events::FIELD_TRANSIENT_INTERVAL]-> - store((longlong)et->interval+1, true); - - table->field[Events::FIELD_EXECUTE_AT]->set_null(); - - if (!et->starts_null) - { - table->field[Events::FIELD_STARTS]->set_notnull(); - table->field[Events::FIELD_STARTS]-> - store_time(&et->starts, MYSQL_TIMESTAMP_DATETIME); - } - - if (!et->ends_null) - { - table->field[Events::FIELD_ENDS]->set_notnull(); - table->field[Events::FIELD_ENDS]-> - store_time(&et->ends, MYSQL_TIMESTAMP_DATETIME); - } - } - else if (et->execute_at.year) - { - table->field[Events::FIELD_INTERVAL_EXPR]->set_null(); - table->field[Events::FIELD_TRANSIENT_INTERVAL]->set_null(); - table->field[Events::FIELD_STARTS]->set_null(); - table->field[Events::FIELD_ENDS]->set_null(); - - table->field[Events::FIELD_EXECUTE_AT]->set_notnull(); - table->field[Events::FIELD_EXECUTE_AT]-> - store_time(&et->execute_at, MYSQL_TIMESTAMP_DATETIME); - } - else - { - DBUG_ASSERT(is_update); - /* - it is normal to be here when the action is update - this is an error if the action is create. something is borked - */ - } - - ((Field_timestamp *)table->field[Events::FIELD_MODIFIED])->set_time(); - - if (et->comment.str) - { - if (table->field[field_num= Events::FIELD_COMMENT]-> - store(et->comment.str, et->comment.length, scs)) - goto err_truncate; - } - - DBUG_RETURN(0); -err_truncate: - my_error(ER_EVENT_DATA_TOO_LONG, MYF(0), table->field[field_num]->field_name); - DBUG_RETURN(EVEX_GENERAL_ERROR); -} - - -/* - Creates an event in mysql.event - - SYNOPSIS - db_create_event() - thd THD - et Event_timed object containing information for the event - create_if_not If an warning should be generated in case event exists - rows_affected How many rows were affected - - RETURN VALUE - 0 - OK - EVEX_GENERAL_ERROR - Failure - - DESCRIPTION - Creates an event. Relies on evex_fill_row which is shared with - db_update_event. The name of the event is inside "et". -*/ - -int -db_create_event(THD *thd, Event_timed *et, my_bool create_if_not, - uint *rows_affected) -{ - int ret= 0; - CHARSET_INFO *scs= system_charset_info; - TABLE *table; - char olddb[128]; - bool dbchanged= false; - DBUG_ENTER("db_create_event"); - DBUG_PRINT("enter", ("name: %.*s", et->name.length, et->name.str)); - - *rows_affected= 0; - DBUG_PRINT("info", ("open mysql.event for update")); - if (Events::open_event_table(thd, TL_WRITE, &table)) - { - my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0)); - goto err; - } - - DBUG_PRINT("info", ("check existance of an event with the same name")); - if (!evex_db_find_event_aux(thd, et, table)) - { - if (create_if_not) - { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_EVENT_ALREADY_EXISTS, ER(ER_EVENT_ALREADY_EXISTS), - et->name.str); - goto ok; - } - my_error(ER_EVENT_ALREADY_EXISTS, MYF(0), et->name.str); - goto err; - } - - DBUG_PRINT("info", ("non-existant, go forward")); - if ((ret= sp_use_new_db(thd, et->dbname.str,olddb, sizeof(olddb),0, - &dbchanged))) - { - my_error(ER_BAD_DB_ERROR, MYF(0)); - goto err; - } - - restore_record(table, s->default_values); // Get default values for fields - - if (system_charset_info->cset->numchars(system_charset_info, et->dbname.str, - et->dbname.str + et->dbname.length) - > EVEX_DB_FIELD_LEN) - { - my_error(ER_TOO_LONG_IDENT, MYF(0), et->dbname.str); - goto err; - } - if (system_charset_info->cset->numchars(system_charset_info, et->name.str, - et->name.str + et->name.length) - > EVEX_DB_FIELD_LEN) - { - my_error(ER_TOO_LONG_IDENT, MYF(0), et->name.str); - goto err; - } - - if (et->body.length > table->field[Events::FIELD_BODY]->field_length) - { - my_error(ER_TOO_LONG_BODY, MYF(0), et->name.str); - goto err; - } - - if (!(et->expression) && !(et->execute_at.year)) - { - DBUG_PRINT("error", ("neither expression nor execute_at are set!")); - my_error(ER_EVENT_NEITHER_M_EXPR_NOR_M_AT, MYF(0)); - goto err; - } - - ((Field_timestamp *)table->field[Events::FIELD_CREATED])->set_time(); - - /* - evex_fill_row() calls my_error() in case of error so no need to - handle it here - */ - if ((ret= evex_fill_row(thd, table, et, false))) - goto err; - - if (table->file->ha_write_row(table->record[0])) - { - my_error(ER_EVENT_STORE_FAILED, MYF(0), et->name.str, ret); - goto err; - } - -#ifdef USE_THIS_CODE_AS_TEMPLATE_WHEN_EVENT_REPLICATION_IS_AGREED - if (mysql_bin_log.is_open()) - { - thd->clear_error(); - /* Such a statement can always go directly to binlog, no trans cache */ - thd->binlog_query(THD::MYSQL_QUERY_TYPE, thd->query, thd->query_length, - FALSE, FALSE); - } -#endif - - *rows_affected= 1; -ok: - if (dbchanged) - (void) mysql_change_db(thd, olddb, 1); - if (table) - close_thread_tables(thd); - DBUG_RETURN(EVEX_OK); - -err: - if (dbchanged) - (void) mysql_change_db(thd, olddb, 1); - if (table) - close_thread_tables(thd); - DBUG_RETURN(EVEX_GENERAL_ERROR); -} - - -/* - Used to execute ALTER EVENT. Pendant to Events::update_event(). - - SYNOPSIS - db_update_event() - thd THD - sp_name the name of the event to alter - et event's data - - RETURN VALUE - 0 OK - EVEX_GENERAL_ERROR Error occured (my_error() called) - - NOTES - sp_name is passed since this is the name of the event to - alter in case of RENAME TO. -*/ - -static int -db_update_event(THD *thd, Event_timed *et, sp_name *new_name) -{ - CHARSET_INFO *scs= system_charset_info; - TABLE *table; - int ret= EVEX_OPEN_TABLE_FAILED; - DBUG_ENTER("db_update_event"); - DBUG_PRINT("enter", ("dbname: %.*s", et->dbname.length, et->dbname.str)); - DBUG_PRINT("enter", ("name: %.*s", et->name.length, et->name.str)); - DBUG_PRINT("enter", ("user: %.*s", et->definer.length, et->definer.str)); - if (new_name) - DBUG_PRINT("enter", ("rename to: %.*s", new_name->m_name.length, - new_name->m_name.str)); - - if (Events::open_event_table(thd, TL_WRITE, &table)) - { - my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0)); - goto err; - } - - /* first look whether we overwrite */ - if (new_name) - { - if (!sortcmp_lex_string(et->name, new_name->m_name, scs) && - !sortcmp_lex_string(et->dbname, new_name->m_db, scs)) - { - my_error(ER_EVENT_SAME_NAME, MYF(0), et->name.str); - goto err; - } - - if (!evex_db_find_event_by_name(thd,new_name->m_db,new_name->m_name,table)) - { - my_error(ER_EVENT_ALREADY_EXISTS, MYF(0), new_name->m_name.str); - goto err; - } - } - /* - ...and then if there is such an event. Don't exchange the blocks - because you will get error 120 from table handler because new_name will - overwrite the key and SE will tell us that it cannot find the already found - row (copied into record[1] later - */ - if (EVEX_KEY_NOT_FOUND == evex_db_find_event_aux(thd, et, table)) - { - my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), et->name.str); - goto err; - } - - store_record(table,record[1]); - - /* Don't update create on row update. */ - table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; - - /* - evex_fill_row() calls my_error() in case of error so no need to - handle it here - */ - if ((ret= evex_fill_row(thd, table, et, true))) - goto err; - - if (new_name) - { - table->field[Events::FIELD_DB]-> - store(new_name->m_db.str, new_name->m_db.length, scs); - table->field[Events::FIELD_NAME]-> - store(new_name->m_name.str, new_name->m_name.length, scs); - } - - if ((ret= table->file->ha_update_row(table->record[1], table->record[0]))) - { - my_error(ER_EVENT_STORE_FAILED, MYF(0), et->name.str, ret); - goto err; - } - - /* close mysql.event or we crash later when loading the event from disk */ - close_thread_tables(thd); - DBUG_RETURN(0); - -err: - if (table) - close_thread_tables(thd); - DBUG_RETURN(EVEX_GENERAL_ERROR); -} - - -/* - Looks for a named event in mysql.event and in case of success returns - an object will data loaded from the table. - - SYNOPSIS - db_find_event() - thd THD - name the name of the event to find - ett event's data if event is found - tbl TABLE object to use when not NULL - - NOTES - 1) Use sp_name for look up, return in **ett if found - 2) tbl is not closed at exit - - RETURN VALUE - 0 ok In this case *ett is set to the event - # error *ett == 0 -*/ - -int -db_find_event(THD *thd, sp_name *name, Event_timed **ett, TABLE *tbl, - MEM_ROOT *root) -{ - TABLE *table; - int ret; - Event_timed *et= NULL; - DBUG_ENTER("db_find_event"); - DBUG_PRINT("enter", ("name: %*s", name->m_name.length, name->m_name.str)); - - if (!root) - root= &evex_mem_root; - - if (tbl) - table= tbl; - else if (Events::open_event_table(thd, TL_READ, &table)) - { - my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0)); - ret= EVEX_GENERAL_ERROR; - goto done; - } - - if ((ret= evex_db_find_event_by_name(thd, name->m_db, name->m_name, table))) - { - my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), name->m_name.str); - goto done; - } - et= new Event_timed; - - /* - 1)The table should not be closed beforehand. ::load_from_row() only loads - and does not compile - - 2)::load_from_row() is silent on error therefore we emit error msg here - */ - if ((ret= et->load_from_row(root, table))) - { - my_error(ER_CANNOT_LOAD_FROM_TABLE, MYF(0)); - goto done; - } - -done: - if (ret) - { - delete et; - et= 0; - } - /* don't close the table if we haven't opened it ourselves */ - if (!tbl && table) - close_thread_tables(thd); - *ett= et; - DBUG_RETURN(ret); + return db_repository.open_event_table(thd, lock_type, table); } @@ -913,9 +283,10 @@ Events::create_event(THD *thd, Event_timed *et, Event_parse_data *parse_data, DBUG_PRINT("enter", ("name: %*s options:%d", et->name.length, et->name.str, create_options)); - if (!(ret = db_create_event(thd, et, - create_options & HA_LEX_CREATE_IF_NOT_EXISTS, - rows_affected))) + if (!(ret= db_repository. + create_event(thd, et, + create_options & HA_LEX_CREATE_IF_NOT_EXISTS, + rows_affected))) { Event_scheduler *scheduler= Event_scheduler::get_instance(); if (scheduler->initialized() && @@ -960,7 +331,7 @@ Events::update_event(THD *thd, Event_timed *et, Event_parse_data *parse_data, crash later in the code when loading and compiling the new definition. Also on error conditions my_error() is called so no need to handle here */ - if (!(ret= db_update_event(thd, et, new_name))) + if (!(ret= db_repository.update_event(thd, et, new_name))) { Event_scheduler *scheduler= Event_scheduler::get_instance(); if (scheduler->initialized() && @@ -973,67 +344,6 @@ Events::update_event(THD *thd, Event_timed *et, Event_parse_data *parse_data, } -/* - Drops an event - - SYNOPSIS - db_drop_event() - thd THD - et event's name - drop_if_exists if set and the event not existing => warning onto the stack - rows_affected affected number of rows is returned heres - - RETURN VALUE - 0 OK - !0 Error (my_error() called) -*/ - -int db_drop_event(THD *thd, LEX_STRING db, LEX_STRING name, - bool drop_if_exists, uint *rows_affected) -{ - TABLE *table; - Open_tables_state backup; - int ret; - - DBUG_ENTER("db_drop_event"); - DBUG_PRINT("enter", ("db=%s name=%s", db.str, name.str)); - ret= EVEX_OPEN_TABLE_FAILED; - - thd->reset_n_backup_open_tables_state(&backup); - if (Events::open_event_table(thd, TL_WRITE, &table)) - { - my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0)); - goto done; - } - - if (!(ret= evex_db_find_event_by_name(thd, db, name, table))) - { - if ((ret= table->file->ha_delete_row(table->record[0]))) - my_error(ER_EVENT_CANNOT_DELETE, MYF(0)); - } - else if (ret == EVEX_KEY_NOT_FOUND) - { - if (drop_if_exists) - { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_SP_DOES_NOT_EXIST, ER(ER_SP_DOES_NOT_EXIST), - "Event", name.str); - ret= 0; - } else - my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), name.str); - } - -done: - /* - evex_drop_event() is used by Event_timed::drop therefore - we have to close our thread tables. - */ - close_thread_tables(thd); - thd->restore_backup_open_tables_state(&backup); - DBUG_RETURN(ret); -} - - /* Drops an event @@ -1056,14 +366,14 @@ Events::drop_event(THD *thd, sp_name *name, bool drop_if_exists, int ret; DBUG_ENTER("Events::drop_event"); - if (!(ret= db_drop_event(thd, name->m_db, name->m_name, drop_if_exists, - rows_affected))) + + if (!(ret= db_repository.drop_event(thd, name->m_db, name->m_name, + drop_if_exists, rows_affected))) { Event_scheduler *scheduler= Event_scheduler::get_instance(); if (scheduler->initialized() && (ret= scheduler->drop_event(thd, name))) my_error(ER_EVENT_MODIFY_QUEUE_ERROR, MYF(0), ret); } - DBUG_RETURN(ret); } @@ -1092,7 +402,7 @@ Events::show_create_event(THD *thd, sp_name *spn) DBUG_PRINT("enter", ("name: %*s", spn->m_name.length, spn->m_name.str)); thd->reset_n_backup_open_tables_state(&backup); - ret= db_find_event(thd, spn, &et, NULL, thd->mem_root); + ret= db_repository.find_event(thd, spn, &et, NULL, thd->mem_root); thd->restore_backup_open_tables_state(&backup); if (!ret) @@ -1164,75 +474,13 @@ Events::drop_schema_events(THD *thd, char *db) DBUG_PRINT("enter", ("dropping events from %s", db)); Event_scheduler *scheduler= Event_scheduler::get_instance(); - if (scheduler->initialized()) - ret= scheduler->drop_schema_events(thd, &db_lex); - else - ret= db_drop_events_from_table(thd, &db_lex); - - DBUG_RETURN(ret); -} - - -/* - Drops all events in the selected database, from mysql.event. - - SYNOPSIS - evex_drop_db_events_from_table() - thd Thread - db Schema name - - RETURN VALUE - 0 OK - !0 Error from ha_delete_row -*/ - -int -db_drop_events_from_table(THD *thd, LEX_STRING *db) -{ - int ret; - TABLE *table; - READ_RECORD read_record_info; - DBUG_ENTER("db_drop_events_from_table"); - DBUG_PRINT("info", ("dropping events from %s", db->str)); - - if ((ret= Events::open_event_table(thd, TL_WRITE, &table))) - { - if (my_errno != ENOENT) - sql_print_error("Table mysql.event is damaged. Got error %d on open", - my_errno); - DBUG_RETURN(ret); - } - /* only enabled events are in memory, so we go now and delete the rest */ - init_read_record(&read_record_info, thd, table, NULL, 1, 0); - while (!(read_record_info.read_record(&read_record_info)) && !ret) - { - char *et_db= get_field(thd->mem_root, - table->field[Events::FIELD_DB]); - - LEX_STRING et_db_lex= {et_db, strlen(et_db)}; - DBUG_PRINT("info", ("Current event %s.%s", et_db, - get_field(thd->mem_root, - table->field[Events::FIELD_NAME]))); - - if (!sortcmp_lex_string(et_db_lex, *db, system_charset_info)) - { - DBUG_PRINT("info", ("Dropping")); - if ((ret= table->file->ha_delete_row(table->record[0]))) - my_error(ER_EVENT_DROP_FAILED, MYF(0), - get_field(thd->mem_root, - table->field[Events::FIELD_NAME])); - } - } - end_read_record(&read_record_info); - thd->version--; /* Force close to free memory */ - - close_thread_tables(thd); + ret= scheduler->drop_schema_events(thd, db_lex); + ret= db_repository.drop_schema_events(thd, db_lex); DBUG_RETURN(ret); } - /* Inits the scheduler's structures. @@ -1251,14 +499,17 @@ int Events::init() { int ret= 0; + Event_db_repository *db_repo; DBUG_ENTER("Events::init"); + db_repo= &get_instance()->db_repository; + db_repo->init_repository(); /* it should be an assignment! */ if (opt_event_scheduler) { Event_scheduler *scheduler= Event_scheduler::get_instance(); DBUG_ASSERT(opt_event_scheduler == 1 || opt_event_scheduler == 2); - DBUG_RETURN(scheduler->init() || + DBUG_RETURN(scheduler->init(db_repo) || (opt_event_scheduler == 1? scheduler->start(): scheduler->start_suspended())); } @@ -1270,22 +521,23 @@ Events::init() Cleans up scheduler's resources. Called at server shutdown. SYNOPSIS - Events::shutdown() + Events::deinit() NOTES This function is not synchronized. */ void -Events::shutdown() +Events::deinit() { - DBUG_ENTER("Events::shutdown"); + DBUG_ENTER("Events::deinit"); Event_scheduler *scheduler= Event_scheduler::get_instance(); if (scheduler->initialized()) { scheduler->stop(); scheduler->destroy(); } + get_instance()->db_repository.deinit_repository(); DBUG_VOID_RETURN; } diff --git a/sql/events.h b/sql/events.h index d56a544493d..339f87c3806 100644 --- a/sql/events.h +++ b/sql/events.h @@ -20,74 +20,91 @@ class sp_name; class Event_timed; class Event_parse_data; +#include "event_db_repository.h" + +/* Return codes */ +enum enum_events_error_code +{ + OP_OK= 0, + OP_NOT_RUNNING, + OP_CANT_KILL, + OP_CANT_INIT, + OP_DISABLED_EVENT, + OP_LOAD_ERROR, + OP_ALREADY_EXISTS +}; + +int +sortcmp_lex_string(LEX_STRING s, LEX_STRING t, CHARSET_INFO *cs); + + class Events { public: + /* + Quite NOT the best practice and will be removed once + Event_timed::drop() and Event_timed is fixed not do drop directly + or other scheme will be found. + */ + friend class Event_timed; + static ulong opt_event_scheduler; static TYPELIB opt_typelib; - enum enum_table_field - { - FIELD_DB = 0, - FIELD_NAME, - FIELD_BODY, - FIELD_DEFINER, - FIELD_EXECUTE_AT, - FIELD_INTERVAL_EXPR, - FIELD_TRANSIENT_INTERVAL, - FIELD_CREATED, - FIELD_MODIFIED, - FIELD_LAST_EXECUTED, - FIELD_STARTS, - FIELD_ENDS, - FIELD_STATUS, - FIELD_ON_COMPLETION, - FIELD_SQL_MODE, - FIELD_COMMENT, - FIELD_COUNT /* a cool trick to count the number of fields :) */ - }; - static int + init(); + + static void + deinit(); + + static void + init_mutexes(); + + static void + destroy_mutexes(); + + static Events* + get_instance(); + + int create_event(THD *thd, Event_timed *et, Event_parse_data *parse_data, uint create_options, uint *rows_affected); - static int + int update_event(THD *thd, Event_timed *et, Event_parse_data *parse_data, sp_name *new_name, uint *rows_affected); - static int + int drop_event(THD *thd, sp_name *name, bool drop_if_exists, uint *rows_affected); - static int + int open_event_table(THD *thd, enum thr_lock_type lock_type, TABLE **table); - static int + int show_create_event(THD *thd, sp_name *spn); + /* Needed for both SHOW CREATE EVENT and INFORMATION_SCHEMA */ static int reconstruct_interval_expression(String *buf, interval_type interval, longlong expression); - static int + int drop_schema_events(THD *thd, char *db); - static int + int dump_internal_status(THD *thd); - - static int - init(); - - static void - shutdown(); - - static void - init_mutexes(); - - static void - destroy_mutexes(); + Event_db_repository db_repository; private: + /* Singleton DP is used */ + Events(){} + ~Events(){} + + /* Singleton instance */ + static Events singleton; + + /* Prevent use of these */ Events(const Events &); void operator=(Events &); diff --git a/sql/events_priv.h b/sql/events_priv.h deleted file mode 100644 index fe208ace280..00000000000 --- a/sql/events_priv.h +++ /dev/null @@ -1,94 +0,0 @@ -#ifndef _EVENT_PRIV_H_ -#define _EVENT_PRIV_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 EVENT_EXEC_STARTED 0 -#define EVENT_EXEC_ALREADY_EXEC 1 -#define EVENT_EXEC_CANT_FORK 2 - -#define EVEX_DB_FIELD_LEN 64 -#define EVEX_NAME_FIELD_LEN 64 -#define EVEX_MAX_INTERVAL_VALUE 2147483647L - -class Event_timed; -class sp_name; - -int -evex_db_find_event_by_name(THD *thd, const LEX_STRING dbname, - const LEX_STRING ev_name, - TABLE *table); - -int -db_drop_event(THD *thd, LEX_STRING db, LEX_STRING name, bool drop_if_exists, - uint *rows_affected); -int -db_find_event(THD *thd, sp_name *name, Event_timed **ett, TABLE *tbl, - MEM_ROOT *root); - -int -db_create_event(THD *thd, Event_timed *et, my_bool create_if_not, - uint *rows_affected); - -int -db_drop_events_from_table(THD *thd, LEX_STRING *db); - -int -sortcmp_lex_string(LEX_STRING s, LEX_STRING t, CHARSET_INFO *cs); - -/* Compares only the name part of the identifier */ -bool -event_timed_name_equal(Event_timed *et, LEX_STRING *name); - -/* Compares only the schema part of the identifier */ -bool -event_timed_db_equal(Event_timed *et, LEX_STRING *db); - -/* - Compares only the definer part of the identifier. Use during DROP USER - to drop user's events. (Still not implemented) -*/ -bool -event_timed_definer_equal(Event_timed *et, LEX_STRING *definer); - -/* Compares the whole identifier*/ -bool -event_timed_identifier_equal(Event_timed *a, Event_timed *b); - - - -/* Compares only the name part of the identifier */ -bool -event_timed_name_equal(sp_name *name, LEX_STRING *event_name); - -/* Compares only the schema part of the identifier */ -bool -event_timed_db_equal(sp_name *name, LEX_STRING *db); - -/* Compares the whole identifier*/ -bool -event_timed_identifier_equal(sp_name *a, Event_timed *b); - - -bool -change_security_context(THD *thd, LEX_STRING user, LEX_STRING host, - LEX_STRING db, Security_context *s_ctx, - Security_context **backup); - -void -restore_security_context(THD *thd, Security_context *backup); - -#endif /* _EVENT_PRIV_H_ */ diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 3f93de00594..a82f66acd19 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -886,7 +886,7 @@ static void close_connections(void) } (void) pthread_mutex_unlock(&LOCK_thread_count); // For unlink from list - Events::shutdown(); + Events::deinit(); end_slave(); if (thread_count) diff --git a/sql/set_var.cc b/sql/set_var.cc index e6b0625f097..98bcf3be6b2 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -3891,7 +3891,7 @@ byte *sys_var_thd_dbug::value_ptr(THD *thd, enum_var_type type, LEX_STRING *b) bool sys_var_event_scheduler::update(THD *thd, set_var *var) { - enum Event_scheduler::enum_error_code res; + int res; Event_scheduler *scheduler= Event_scheduler::get_instance(); /* here start the thread if not running. */ DBUG_ENTER("sys_var_event_scheduler::update"); diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 23603afc038..97fc6feb5f7 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -2048,6 +2048,63 @@ bool Security_context::set_user(char *user_arg) return user == 0; } +/* + Switches the security context + SYNOPSIS + THD::change_security_context() + 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 + FALSE OK + TRUE Error (generates error too) +*/ + +bool +THD::change_security_context(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= security_ctx; + security_ctx= s_ctx; +#endif + DBUG_RETURN(FALSE); +} + + +/* + Restores the security context + SYNOPSIS + restore_security_context() + thd Thread + backup Context to switch to +*/ + +void +THD::restore_security_context(Security_context *backup) +{ + DBUG_ENTER("restore_security_context"); +#ifndef NO_EMBEDDED_ACCESS_CHECKS + if (backup) + security_ctx= backup; +#endif + DBUG_VOID_RETURN; +} + + /**************************************************************************** Handling of open and locked tables states. diff --git a/sql/sql_class.h b/sql/sql_class.h index fa4a29ff112..191ecc2ab18 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -868,6 +868,14 @@ public: char *db, *catalog; Security_context main_security_ctx; Security_context *security_ctx; + + bool + change_security_context(LEX_STRING user, LEX_STRING host, + LEX_STRING db, Security_context *s_ctx, + Security_context **backup); + + void + restore_security_context(Security_context *backup); /* remote (peer) port */ uint16 peer_port; diff --git a/sql/sql_db.cc b/sql/sql_db.cc index 8dd62fc8494..83516da38d4 100644 --- a/sql/sql_db.cc +++ b/sql/sql_db.cc @@ -904,7 +904,7 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent) exit: (void)sp_drop_db_routines(thd, db); /* QQ Ignore errors for now */ - error= Events::drop_schema_events(thd, db); + error= Events::get_instance()->drop_schema_events(thd, db); /* If this database was the client's selected database, we silently change the client's selected database to nothing (to have an empty SELECT DATABASE() diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 4f75339e5a2..0d52bc41a48 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -3847,13 +3847,14 @@ end_with_restore_list: switch (lex->sql_command) { case SQLCOM_CREATE_EVENT: - res= Events::create_event(thd, lex->et, lex->event_parse_data, - (uint) lex->create_info.options, - &rows_affected); + res= Events::get_instance()-> + create_event(thd, lex->et, lex->event_parse_data, + (uint) lex->create_info.options, &rows_affected); break; case SQLCOM_ALTER_EVENT: - res= Events::update_event(thd, lex->et, lex->event_parse_data, - lex->spname, &rows_affected); + res= Events::get_instance()-> + update_event(thd, lex->et, lex->event_parse_data, + lex->spname, &rows_affected); break; default:; } @@ -3895,7 +3896,7 @@ end_with_restore_list: } if (lex->sql_command == SQLCOM_SHOW_CREATE_EVENT) - res= Events::show_create_event(thd, lex->spname); + res= Events::get_instance()->show_create_event(thd, lex->spname); else { uint rows_affected= 1; @@ -3904,8 +3905,9 @@ end_with_restore_list: res= -1; break; } - if (!(res= Events::drop_event(thd, lex->spname, lex->drop_if_exists, - &rows_affected))) + if (!(res= Events::get_instance()->drop_event(thd, lex->spname, + lex->drop_if_exists, + &rows_affected))) send_ok(thd, rows_affected); } break; @@ -3913,7 +3915,7 @@ end_with_restore_list: #ifndef DBUG_OFF case SQLCOM_SHOW_SCHEDULER_STATUS: { - res= Events::dump_internal_status(thd); + res= Events::get_instance()->dump_internal_status(thd); break; } #endif diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 33fa9758c6c..77906973a38 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -4324,7 +4324,7 @@ int events_table_index_read_for_db(THD *thd, TABLE *schema_table, DBUG_PRINT("info", ("Using prefix scanning on PK")); event_table->file->ha_index_init(0, 1); - event_table->field[Events::FIELD_DB]-> + event_table->field[ET_FIELD_DB]-> store(thd->lex->select_lex.db, strlen(thd->lex->select_lex.db), scs); key_info= event_table->key_info; key_len= key_info->key_part[0].store_length; @@ -4443,7 +4443,7 @@ int fill_schema_events(THD *thd, TABLE_LIST *tables, COND * /* cond */) thd->lex->select_lex.db:"(null)")); thd->reset_n_backup_open_tables_state(&backup); - if (Events::open_event_table(thd, TL_READ, &event_table)) + if (Events::get_instance()->open_event_table(thd, TL_READ, &event_table)) { sql_print_error("Table mysql.event is damaged."); thd->restore_backup_open_tables_state(&backup); -- cgit v1.2.1 From 8ca78787a54f5d86f23147ca734e8e167a56b7b4 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 28 Jun 2006 14:22:14 +0200 Subject: WL#3337 (Events new architecture) Cut number 6. Move code from sql_show.cc to event_db_repository.cc that more belongs to the latter. sql/event_db_repository.cc: move code that works with mysql.event from sql_show.cc to event_db_repository.cc . Route through Event_db_repository's interface which is proxied by class Events. The code relies on a function from sql_show.cc which does the actual storage in the schema table. I think it's better to leave the function there because the structure of I_S.EVENTS is defined in sql_show.cc sql/event_db_repository.h: I_S / SHOW EVENTS handling hooks sql/event_scheduler.cc: use the pointer to db_repository which Event_scheduler already has sql/events.cc: Put a comment to get_instance sql/events.h: callback for I_S (sql_show.cc) sql/sql_show.cc: move code that belongs more to Event_db_repository than to here. Use a callback of class Events. Only 1 function is left here, because it copies data into the actual rows of I_S.EVENTS and belongs to this file. sql/sql_show.h: export this function will be called from event_db_repository.cc --- sql/event_db_repository.cc | 173 ++++++++++++++++++++++++++++++++++++++++++- sql/event_db_repository.h | 28 ++++++- sql/event_scheduler.cc | 2 +- sql/events.cc | 47 +++++++++++- sql/events.h | 3 + sql/sql_show.cc | 181 +-------------------------------------------- sql/sql_show.h | 2 + 7 files changed, 247 insertions(+), 189 deletions(-) diff --git a/sql/event_db_repository.cc b/sql/event_db_repository.cc index 249910c5f50..96d2b6c957f 100644 --- a/sql/event_db_repository.cc +++ b/sql/event_db_repository.cc @@ -20,6 +20,7 @@ #include "sp_head.h" #include "sp.h" #include "events.h" +#include "sql_show.h" #define EVEX_DB_FIELD_LEN 64 #define EVEX_NAME_FIELD_LEN 64 @@ -188,8 +189,7 @@ evex_fill_row(THD *thd, TABLE *table, Event_timed *et, my_bool is_update) if (et->expression) { table->field[ET_FIELD_INTERVAL_EXPR]->set_notnull(); - table->field[ET_FIELD_INTERVAL_EXPR]-> - store((longlong)et->expression, true); + table->field[ET_FIELD_INTERVAL_EXPR]->store((longlong)et->expression, true); table->field[ET_FIELD_TRANSIENT_INTERVAL]->set_notnull(); /* @@ -276,16 +276,181 @@ evex_db_find_event_by_name(THD *thd, const LEX_STRING dbname, } +/* + Performs an index scan of event_table (mysql.event) and fills schema_table. + + Synopsis + Event_db_repository::index_read_for_db_for_i_s() + thd Thread + schema_table The I_S.EVENTS table + event_table The event table to use for loading (mysql.event) + + Returns + 0 OK + 1 Error +*/ + +int +Event_db_repository::index_read_for_db_for_i_s(THD *thd, TABLE *schema_table, + TABLE *event_table, char *db) +{ + int ret=0; + CHARSET_INFO *scs= system_charset_info; + KEY *key_info; + uint key_len; + byte *key_buf= NULL; + LINT_INIT(key_buf); + + DBUG_ENTER("Event_db_repository::index_read_for_db_for_i_s"); + + DBUG_PRINT("info", ("Using prefix scanning on PK")); + event_table->file->ha_index_init(0, 1); + event_table->field[ET_FIELD_DB]->store(db, strlen(db), scs); + key_info= event_table->key_info; + key_len= key_info->key_part[0].store_length; + + if (!(key_buf= (byte *)alloc_root(thd->mem_root, key_len))) + { + ret= 1; + /* don't send error, it would be done by sql_alloc_error_handler() */ + } + else + { + key_copy(key_buf, event_table->record[0], key_info, key_len); + if (!(ret= event_table->file->index_read(event_table->record[0], key_buf, + key_len, HA_READ_PREFIX))) + { + DBUG_PRINT("info",("Found rows. Let's retrieve them. ret=%d", ret)); + do + { + ret= copy_event_to_schema_table(thd, schema_table, event_table); + if (ret == 0) + ret= event_table->file->index_next_same(event_table->record[0], + key_buf, key_len); + } while (ret == 0); + } + DBUG_PRINT("info", ("Scan finished. ret=%d", ret)); + } + event_table->file->ha_index_end(); + /* ret is guaranteed to be != 0 */ + if (ret == HA_ERR_END_OF_FILE || ret == HA_ERR_KEY_NOT_FOUND) + DBUG_RETURN(0); + DBUG_RETURN(1); +} + + +/* + Performs a table scan of event_table (mysql.event) and fills schema_table. + + Synopsis + Events_db_repository::table_scan_all_for_i_s() + thd Thread + schema_table The I_S.EVENTS in memory table + event_table The event table to use for loading. + + Returns + 0 OK + 1 Error +*/ + +int +Event_db_repository::table_scan_all_for_i_s(THD *thd, TABLE *schema_table, + TABLE *event_table) +{ + int ret; + READ_RECORD read_record_info; + + DBUG_ENTER("Event_db_repository::table_scan_all_for_i_s"); + init_read_record(&read_record_info, thd, event_table, NULL, 1, 0); + + /* + rr_sequential, in read_record(), returns 137==HA_ERR_END_OF_FILE, + but rr_handle_error returns -1 for that reason. Thus, read_record() + returns -1 eventually. + */ + do + { + ret= read_record_info.read_record(&read_record_info); + if (ret == 0) + ret= copy_event_to_schema_table(thd, schema_table, event_table); + } + while (ret == 0); + + DBUG_PRINT("info", ("Scan finished. ret=%d", ret)); + end_read_record(&read_record_info); + + /* ret is guaranteed to be != 0 */ + DBUG_RETURN(ret == -1? 0:1); +} + + +/* + Fills I_S.EVENTS with data loaded from mysql.event. Also used by + SHOW EVENTS + + Synopsis + Event_db_repository::fill_schema_events() + thd Thread + tables The schema table + db If not NULL then get events only from this schema + + Returns + 0 OK + 1 Error +*/ + +int +Event_db_repository::fill_schema_events(THD *thd, TABLE_LIST *tables, char *db) +{ + TABLE *schema_table= tables->table; + TABLE *event_table= NULL; + Open_tables_state backup; + int ret= 0; + + DBUG_ENTER("Event_db_repository::fill_schema_events"); + DBUG_PRINT("info",("db=%s", db? db:"(null)")); + + thd->reset_n_backup_open_tables_state(&backup); + if (open_event_table(thd, TL_READ, &event_table)) + { + sql_print_error("Table mysql.event is damaged."); + thd->restore_backup_open_tables_state(&backup); + DBUG_RETURN(1); + } + + /* + 1. SELECT I_S => use table scan. I_S.EVENTS does not guarantee order + thus we won't order it. OTOH, SHOW EVENTS will be + ordered. + 2. SHOW EVENTS => PRIMARY KEY with prefix scanning on (db) + Reasoning: Events are per schema, therefore a scan over an index + will save use from doing a table scan and comparing + every single row's `db` with the schema which we show. + */ + if (db) + ret= index_read_for_db_for_i_s(thd, schema_table, event_table, db); + else + ret= table_scan_all_for_i_s(thd, schema_table, event_table); + + close_thread_tables(thd); + thd->restore_backup_open_tables_state(&backup); + + DBUG_PRINT("info", ("Return code=%d", ret)); + DBUG_RETURN(ret); +} + + /* Looks for a named event in mysql.event and in case of success returns an object will data loaded from the table. SYNOPSIS - db_find_event() + Event_db_repository::find_event() thd THD name the name of the event to find ett event's data if event is found tbl TABLE object to use when not NULL + root On which root to load the event NOTES 1) Use sp_name for look up, return in **ett if found @@ -308,7 +473,7 @@ Event_db_repository::find_event(THD *thd, sp_name *name, Event_timed **ett, if (tbl) table= tbl; - else if (Events::get_instance()->db_repository.open_event_table(thd, TL_READ, &table)) + else if (open_event_table(thd, TL_READ, &table)) { my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0)); ret= EVEX_GENERAL_ERROR; diff --git a/sql/event_db_repository.h b/sql/event_db_repository.h index c0cfccf215a..1cbee96b68f 100644 --- a/sql/event_db_repository.h +++ b/sql/event_db_repository.h @@ -43,6 +43,17 @@ evex_db_find_event_by_name(THD *thd, const LEX_STRING dbname, const LEX_STRING ev_name, TABLE *table); +int +events_table_index_read_for_db(THD *thd, TABLE *schema_table, + TABLE *event_table); + +int +events_table_scan_all(THD *thd, TABLE *schema_table, TABLE *event_table); + +int +fill_schema_events(THD *thd, TABLE_LIST *tables, COND * /* cond */); + + class Event_queue_element; class Event_db_repository @@ -57,9 +68,6 @@ public: void deinit_repository(); - int - open_event_table(THD *thd, enum thr_lock_type lock_type, TABLE **table); - int create_event(THD *thd, Event_timed *et, my_bool create_if_not, uint *rows_affected); @@ -86,11 +94,23 @@ public: int find_event_by_name(THD *thd, LEX_STRING db, LEX_STRING name, TABLE *table); -private: + int + open_event_table(THD *thd, enum thr_lock_type lock_type, TABLE **table); + + int + fill_schema_events(THD *thd, TABLE_LIST *tables, char *db); + +private: int drop_events_by_field(THD *thd, enum enum_events_table_field field, LEX_STRING field_value); + int + index_read_for_db_for_i_s(THD *thd, TABLE *schema_table, TABLE *event_table, + char *db); + + int + table_scan_all_for_i_s(THD *thd, TABLE *schema_table, TABLE *event_table); MEM_ROOT repo_root; diff --git a/sql/event_scheduler.cc b/sql/event_scheduler.cc index 60c9cda0a56..cb500de53b9 100644 --- a/sql/event_scheduler.cc +++ b/sql/event_scheduler.cc @@ -2159,7 +2159,7 @@ Event_scheduler::load_events_from_db(THD *thd) DBUG_RETURN(EVEX_GENERAL_ERROR); } - if ((ret= Events::get_instance()->open_event_table(thd, TL_READ, &table))) + if ((ret= db_repository->open_event_table(thd, TL_READ, &table))) { sql_print_error("SCHEDULER: Table mysql.event is damaged. Can not open."); DBUG_RETURN(EVEX_OPEN_TABLE_FAILED); diff --git a/sql/events.cc b/sql/events.cc index 52e814043d8..a5af196af2a 100644 --- a/sql/events.cc +++ b/sql/events.cc @@ -88,6 +88,15 @@ int sortcmp_lex_string(LEX_STRING s, LEX_STRING t, CHARSET_INFO *cs) (unsigned char *) t.str,t.length, 0); } +/* + Accessor for the singleton instance. + + SYNOPSIS + Events::get_instance() + + RETURN VALUE + address +*/ Events * Events::get_instance() @@ -110,7 +119,7 @@ Events::get_instance() interval - the interval type (for instance YEAR_MONTH) expression - the value in the lowest entity - RETURNS + RETURN VALUE 0 - OK 1 - Error */ @@ -562,6 +571,42 @@ Events::dump_internal_status(THD *thd) } +/* + Proxy for Event_db_repository::fill_schema_events. + Callback for I_S from sql_show.cc + + SYNOPSIS + Events::fill_schema_events() + thd Thread + tables The schema table + cond Unused + + RETURN VALUE + 0 OK + !0 Error +*/ + +int +Events::fill_schema_events(THD *thd, TABLE_LIST *tables, COND * /* cond */) +{ + char *db= NULL; + DBUG_ENTER("Events::fill_schema_events"); + /* + If it's SHOW EVENTS then thd->lex->select_lex.db is guaranteed not to + be NULL. Let's do an assert anyway. + */ + if (thd->lex->orig_sql_command == SQLCOM_SHOW_EVENTS) + { + DBUG_ASSERT(thd->lex->select_lex.db); + if (check_access(thd, EVENT_ACL, thd->lex->select_lex.db, 0, 0, 0, + is_schema_db(thd->lex->select_lex.db))) + DBUG_RETURN(1); + db= thd->lex->select_lex.db; + } + DBUG_RETURN(get_instance()->db_repository.fill_schema_events(thd, tables, db)); +} + + /* Inits Events mutexes diff --git a/sql/events.h b/sql/events.h index 339f87c3806..3a7d1f8bc5e 100644 --- a/sql/events.h +++ b/sql/events.h @@ -90,6 +90,9 @@ public: int drop_schema_events(THD *thd, char *db); + + static int + fill_schema_events(THD *thd, TABLE_LIST *tables, COND * /* cond */); int dump_internal_status(THD *thd); diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 77906973a38..bf69d3a0986 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -4160,7 +4160,7 @@ extern LEX_STRING interval_type_to_name[]; 1 Error */ -static int +int copy_event_to_schema_table(THD *thd, TABLE *sch_table, TABLE *event_table) { const char *wild= thd->lex->wild ? thd->lex->wild->ptr() : NullS; @@ -4295,183 +4295,6 @@ copy_event_to_schema_table(THD *thd, TABLE *sch_table, TABLE *event_table) } -/* - Performs an index scan of event_table (mysql.event) and fills schema_table. - - Synopsis - events_table_index_read_for_db() - thd Thread - schema_table The I_S.EVENTS table - event_table The event table to use for loading (mysql.event) - - Returns - 0 OK - 1 Error -*/ - -static -int events_table_index_read_for_db(THD *thd, TABLE *schema_table, - TABLE *event_table) -{ - int ret=0; - CHARSET_INFO *scs= system_charset_info; - KEY *key_info; - uint key_len; - byte *key_buf= NULL; - LINT_INIT(key_buf); - - DBUG_ENTER("schema_events_do_index_scan"); - - DBUG_PRINT("info", ("Using prefix scanning on PK")); - event_table->file->ha_index_init(0, 1); - event_table->field[ET_FIELD_DB]-> - store(thd->lex->select_lex.db, strlen(thd->lex->select_lex.db), scs); - key_info= event_table->key_info; - key_len= key_info->key_part[0].store_length; - - if (!(key_buf= (byte *)alloc_root(thd->mem_root, key_len))) - { - ret= 1; - /* don't send error, it would be done by sql_alloc_error_handler() */ - } - else - { - key_copy(key_buf, event_table->record[0], key_info, key_len); - if (!(ret= event_table->file->index_read(event_table->record[0], key_buf, - key_len, HA_READ_PREFIX))) - { - DBUG_PRINT("info",("Found rows. Let's retrieve them. ret=%d", ret)); - do - { - ret= copy_event_to_schema_table(thd, schema_table, event_table); - if (ret == 0) - ret= event_table->file->index_next_same(event_table->record[0], - key_buf, key_len); - } while (ret == 0); - } - DBUG_PRINT("info", ("Scan finished. ret=%d", ret)); - } - event_table->file->ha_index_end(); - /* ret is guaranteed to be != 0 */ - if (ret == HA_ERR_END_OF_FILE || ret == HA_ERR_KEY_NOT_FOUND) - DBUG_RETURN(0); - DBUG_RETURN(1); -} - - -/* - Performs a table scan of event_table (mysql.event) and fills schema_table. - - Synopsis - events_table_scan_all() - thd Thread - schema_table The I_S.EVENTS in memory table - event_table The event table to use for loading. - - Returns - 0 OK - 1 Error -*/ - -static -int events_table_scan_all(THD *thd, TABLE *schema_table, - TABLE *event_table) -{ - int ret; - READ_RECORD read_record_info; - - DBUG_ENTER("schema_events_do_table_scan"); - init_read_record(&read_record_info, thd, event_table, NULL, 1, 0); - - /* - rr_sequential, in read_record(), returns 137==HA_ERR_END_OF_FILE, - but rr_handle_error returns -1 for that reason. Thus, read_record() - returns -1 eventually. - */ - do - { - ret= read_record_info.read_record(&read_record_info); - if (ret == 0) - ret= copy_event_to_schema_table(thd, schema_table, event_table); - } - while (ret == 0); - - DBUG_PRINT("info", ("Scan finished. ret=%d", ret)); - end_read_record(&read_record_info); - - /* ret is guaranteed to be != 0 */ - DBUG_RETURN(ret == -1? 0:1); -} - - -/* - Fills I_S.EVENTS with data loaded from mysql.event. Also used by - SHOW EVENTS - - Synopsis - fill_schema_events() - thd Thread - tables The schema table - cond Unused - - Returns - 0 OK - 1 Error -*/ - -int fill_schema_events(THD *thd, TABLE_LIST *tables, COND * /* cond */) -{ - TABLE *schema_table= tables->table; - TABLE *event_table= NULL; - Open_tables_state backup; - int ret= 0; - - DBUG_ENTER("fill_schema_events"); - /* - If it's SHOW EVENTS then thd->lex->select_lex.db is guaranteed not to - be NULL. Let's do an assert anyway. - */ - if (thd->lex->orig_sql_command == SQLCOM_SHOW_EVENTS) - { - DBUG_ASSERT(thd->lex->select_lex.db); - if (check_access(thd, EVENT_ACL, thd->lex->select_lex.db, 0, 0, 0, - is_schema_db(thd->lex->select_lex.db))) - DBUG_RETURN(1); - } - - DBUG_PRINT("info",("db=%s", thd->lex->select_lex.db? - thd->lex->select_lex.db:"(null)")); - - thd->reset_n_backup_open_tables_state(&backup); - if (Events::get_instance()->open_event_table(thd, TL_READ, &event_table)) - { - sql_print_error("Table mysql.event is damaged."); - thd->restore_backup_open_tables_state(&backup); - DBUG_RETURN(1); - } - - /* - 1. SELECT I_S => use table scan. I_S.EVENTS does not guarantee order - thus we won't order it. OTOH, SHOW EVENTS will be - ordered. - 2. SHOW EVENTS => PRIMARY KEY with prefix scanning on (db) - Reasoning: Events are per schema, therefore a scan over an index - will save use from doing a table scan and comparing - every single row's `db` with the schema which we show. - */ - if (thd->lex->orig_sql_command == SQLCOM_SHOW_EVENTS) - ret= events_table_index_read_for_db(thd, schema_table, event_table); - else - ret= events_table_scan_all(thd, schema_table, event_table); - - close_thread_tables(thd); - thd->restore_backup_open_tables_state(&backup); - - DBUG_PRINT("info", ("Return code=%d", ret)); - DBUG_RETURN(ret); -} - - int fill_open_tables(THD *thd, TABLE_LIST *tables, COND *cond) { DBUG_ENTER("fill_open_tables"); @@ -5568,7 +5391,7 @@ ST_SCHEMA_TABLE schema_tables[]= {"ENGINES", engines_fields_info, create_schema_table, fill_schema_engines, make_old_format, 0, -1, -1, 0}, {"EVENTS", events_fields_info, create_schema_table, - fill_schema_events, make_old_format, 0, -1, -1, 0}, + Events::fill_schema_events, make_old_format, 0, -1, -1, 0}, {"FILES", files_fields_info, create_schema_table, fill_schema_files, 0, 0, -1, -1, 0}, {"KEY_COLUMN_USAGE", key_column_usage_fields_info, create_schema_table, diff --git a/sql/sql_show.h b/sql/sql_show.h index 6fce5e94ca3..681d1232b39 100644 --- a/sql/sql_show.h +++ b/sql/sql_show.h @@ -14,4 +14,6 @@ int store_create_info(THD *thd, TABLE_LIST *table_list, String *packet, HA_CREATE_INFO *create_info_arg); int view_store_create_info(THD *thd, TABLE_LIST *table, String *buff); +int copy_event_to_schema_table(THD *thd, TABLE *sch_table, TABLE *event_table); + #endif /* SQL_SHOW_H */ -- cgit v1.2.1 From 400276c2f577f63ffd3871a91bce207f65d5e682 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 28 Jun 2006 15:14:05 +0200 Subject: WL#3337 (Events new architecture) Cut 7 (refactoring) db_repository is no more embedded in the Events singleton. Therefore a change to Events_db_repository won't mean recompile of all files in the server which include events.h sql/event_data_objects.cc: db_repository is no more embedded in the Events singleton. Therefore a change to Events_db_repository won't mean recompile of all files in the server which include events.h sql/event_db_repository.cc: db_repository is no more embedded in the Events singleton. Therefore a change to Events_db_repository won't mean recompile of all files in the server which include events.h sql/events.cc: db_repository is no more embedded in the Events singleton. Therefore a change to Events_db_repository won't mean recompile of all files in the server which include events.h sql/events.h: db_repository is no more embedded in the Events singleton. Therefore a change to Events_db_repository won't mean recompile of all files in the server which include events.h sql/mysqld.cc: db_repository is no more embedded in the Events singleton. Therefore a change to Events_db_repository won't mean recompile of all files in the server which include events.h --- sql/event_data_objects.cc | 2 +- sql/event_db_repository.cc | 2 +- sql/events.cc | 84 ++++++++++++++++++++++++---------------------- sql/events.h | 20 +++++------ sql/mysqld.cc | 8 ++--- 5 files changed, 59 insertions(+), 57 deletions(-) diff --git a/sql/event_data_objects.cc b/sql/event_data_objects.cc index 4b72c7e55a7..228f1a4ff4a 100644 --- a/sql/event_data_objects.cc +++ b/sql/event_data_objects.cc @@ -1304,7 +1304,7 @@ Event_timed::drop(THD *thd) DBUG_ENTER("Event_timed::drop"); DBUG_RETURN(Events::get_instance()-> - db_repository.drop_event(thd, dbname, name, false, &tmp)); + db_repository->drop_event(thd, dbname, name, false, &tmp)); } diff --git a/sql/event_db_repository.cc b/sql/event_db_repository.cc index 96d2b6c957f..fc771b6bac3 100644 --- a/sql/event_db_repository.cc +++ b/sql/event_db_repository.cc @@ -271,7 +271,7 @@ evex_db_find_event_by_name(THD *thd, const LEX_STRING dbname, const LEX_STRING ev_name, TABLE *table) { - return Events::get_instance()->db_repository. + return Events::get_instance()->db_repository-> find_event_by_name(thd, dbname, ev_name, table); } diff --git a/sql/events.cc b/sql/events.cc index a5af196af2a..21da9a2fd8a 100644 --- a/sql/events.cc +++ b/sql/events.cc @@ -258,7 +258,7 @@ int Events::open_event_table(THD *thd, enum thr_lock_type lock_type, TABLE **table) { - return db_repository.open_event_table(thd, lock_type, table); + return db_repository->open_event_table(thd, lock_type, table); } @@ -292,7 +292,7 @@ Events::create_event(THD *thd, Event_timed *et, Event_parse_data *parse_data, DBUG_PRINT("enter", ("name: %*s options:%d", et->name.length, et->name.str, create_options)); - if (!(ret= db_repository. + if (!(ret= db_repository-> create_event(thd, et, create_options & HA_LEX_CREATE_IF_NOT_EXISTS, rows_affected))) @@ -340,7 +340,7 @@ Events::update_event(THD *thd, Event_timed *et, Event_parse_data *parse_data, crash later in the code when loading and compiling the new definition. Also on error conditions my_error() is called so no need to handle here */ - if (!(ret= db_repository.update_event(thd, et, new_name))) + if (!(ret= db_repository->update_event(thd, et, new_name))) { Event_scheduler *scheduler= Event_scheduler::get_instance(); if (scheduler->initialized() && @@ -376,7 +376,7 @@ Events::drop_event(THD *thd, sp_name *name, bool drop_if_exists, DBUG_ENTER("Events::drop_event"); - if (!(ret= db_repository.drop_event(thd, name->m_db, name->m_name, + if (!(ret= db_repository->drop_event(thd, name->m_db, name->m_name, drop_if_exists, rows_affected))) { Event_scheduler *scheduler= Event_scheduler::get_instance(); @@ -411,7 +411,7 @@ Events::show_create_event(THD *thd, sp_name *spn) DBUG_PRINT("enter", ("name: %*s", spn->m_name.length, spn->m_name.str)); thd->reset_n_backup_open_tables_state(&backup); - ret= db_repository.find_event(thd, spn, &et, NULL, thd->mem_root); + ret= db_repository->find_event(thd, spn, &et, NULL, thd->mem_root); thd->restore_backup_open_tables_state(&backup); if (!ret) @@ -484,7 +484,7 @@ Events::drop_schema_events(THD *thd, char *db) Event_scheduler *scheduler= Event_scheduler::get_instance(); ret= scheduler->drop_schema_events(thd, db_lex); - ret= db_repository.drop_schema_events(thd, db_lex); + ret= db_repository->drop_schema_events(thd, db_lex); DBUG_RETURN(ret); } @@ -510,15 +510,14 @@ Events::init() int ret= 0; Event_db_repository *db_repo; DBUG_ENTER("Events::init"); - db_repo= &get_instance()->db_repository; - db_repo->init_repository(); + db_repository->init_repository(); /* it should be an assignment! */ if (opt_event_scheduler) { Event_scheduler *scheduler= Event_scheduler::get_instance(); DBUG_ASSERT(opt_event_scheduler == 1 || opt_event_scheduler == 2); - DBUG_RETURN(scheduler->init(db_repo) || + DBUG_RETURN(scheduler->init(db_repository) || (opt_event_scheduler == 1? scheduler->start(): scheduler->start_suspended())); } @@ -540,18 +539,52 @@ void Events::deinit() { DBUG_ENTER("Events::deinit"); + Event_scheduler *scheduler= Event_scheduler::get_instance(); if (scheduler->initialized()) { scheduler->stop(); scheduler->destroy(); } - get_instance()->db_repository.deinit_repository(); + + db_repository->deinit_repository(); DBUG_VOID_RETURN; } +/* + Inits Events mutexes + + SYNOPSIS + Events::init_mutexes() + thd Thread +*/ + +void +Events::init_mutexes() +{ + db_repository= new Event_db_repository; + Event_scheduler::init_mutexes(); +} + + +/* + Destroys Events mutexes + + SYNOPSIS + Events::destroy_mutexes() +*/ + +void +Events::destroy_mutexes() +{ + Event_scheduler::destroy_mutexes(); + delete db_repository; + db_repository= NULL; +} + + /* Proxy for Event_scheduler::dump_internal_status @@ -603,34 +636,5 @@ Events::fill_schema_events(THD *thd, TABLE_LIST *tables, COND * /* cond */) DBUG_RETURN(1); db= thd->lex->select_lex.db; } - DBUG_RETURN(get_instance()->db_repository.fill_schema_events(thd, tables, db)); -} - - -/* - Inits Events mutexes - - SYNOPSIS - Events::init_mutexes() - thd Thread -*/ - -void -Events::init_mutexes() -{ - Event_scheduler::init_mutexes(); -} - - -/* - Destroys Events mutexes - - SYNOPSIS - Events::destroy_mutexes() -*/ - -void -Events::destroy_mutexes() -{ - Event_scheduler::destroy_mutexes(); + DBUG_RETURN(get_instance()->db_repository->fill_schema_events(thd, tables, db)); } diff --git a/sql/events.h b/sql/events.h index 3a7d1f8bc5e..45a0db13980 100644 --- a/sql/events.h +++ b/sql/events.h @@ -19,8 +19,7 @@ class sp_name; class Event_timed; class Event_parse_data; - -#include "event_db_repository.h" +class Event_db_repository; /* Return codes */ enum enum_events_error_code @@ -51,16 +50,16 @@ public: static ulong opt_event_scheduler; static TYPELIB opt_typelib; - static int + int init(); - static void + void deinit(); - static void + void init_mutexes(); - static void + void destroy_mutexes(); static Events* @@ -77,6 +76,9 @@ public: int drop_event(THD *thd, sp_name *name, bool drop_if_exists, uint *rows_affected); + int + drop_schema_events(THD *thd, char *db); + int open_event_table(THD *thd, enum thr_lock_type lock_type, TABLE **table); @@ -88,16 +90,13 @@ public: reconstruct_interval_expression(String *buf, interval_type interval, longlong expression); - int - drop_schema_events(THD *thd, char *db); - static int fill_schema_events(THD *thd, TABLE_LIST *tables, COND * /* cond */); int dump_internal_status(THD *thd); - Event_db_repository db_repository; + Event_db_repository *db_repository; private: /* Singleton DP is used */ @@ -107,7 +106,6 @@ private: /* Singleton instance */ static Events singleton; - /* Prevent use of these */ Events(const Events &); void operator=(Events &); diff --git a/sql/mysqld.cc b/sql/mysqld.cc index a82f66acd19..099779f91d0 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -886,7 +886,7 @@ static void close_connections(void) } (void) pthread_mutex_unlock(&LOCK_thread_count); // For unlink from list - Events::deinit(); + Events::get_instance()->deinit(); end_slave(); if (thread_count) @@ -1321,7 +1321,7 @@ static void clean_up_mutexes() (void) pthread_mutex_destroy(&LOCK_bytes_sent); (void) pthread_mutex_destroy(&LOCK_bytes_received); (void) pthread_mutex_destroy(&LOCK_user_conn); - Events::destroy_mutexes(); + Events::get_instance()->destroy_mutexes(); #ifdef HAVE_OPENSSL (void) pthread_mutex_destroy(&LOCK_des_key_file); #ifndef HAVE_YASSL @@ -2884,7 +2884,7 @@ static int init_thread_environment() (void) pthread_mutex_init(&LOCK_server_started, MY_MUTEX_INIT_FAST); (void) pthread_cond_init(&COND_server_started,NULL); sp_cache_init(); - Events::init_mutexes(); + Events::get_instance()->init_mutexes(); /* Parameter for threads created for connections */ (void) pthread_attr_init(&connection_attrib); (void) pthread_attr_setdetachstate(&connection_attrib, @@ -3673,7 +3673,7 @@ we force server id to 2, but this MySQL server will not act as a slave."); if (!opt_noacl) { - Events::init(); + Events::get_instance()->init(); } #if defined(__NT__) || defined(HAVE_SMEM) handle_connections_methods(); -- cgit v1.2.1 From 739ea377bd702a6438d1491222e8ec671ad0fbf3 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 28 Jun 2006 16:02:20 +0200 Subject: after merge fix --- sql/events.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/events.cc b/sql/events.cc index cd4d3dff244..5fe5f019c87 100644 --- a/sql/events.cc +++ b/sql/events.cc @@ -628,7 +628,7 @@ Events::fill_schema_events(THD *thd, TABLE_LIST *tables, COND * /* cond */) If it's SHOW EVENTS then thd->lex->select_lex.db is guaranteed not to be NULL. Let's do an assert anyway. */ - if (thd->lex->orig_sql_command == SQLCOM_SHOW_EVENTS) + if (thd->lex->sql_command == SQLCOM_SHOW_EVENTS) { DBUG_ASSERT(thd->lex->select_lex.db); if (check_access(thd, EVENT_ACL, thd->lex->select_lex.db, 0, 0, 0, -- cgit v1.2.1 From 9fa9378b2e5713250e1262879c7bc4d999e1bb40 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 29 Jun 2006 00:42:25 +0200 Subject: WL#3337 (Events new architecture) This cut No 7 should finish the part of fixing the parsing of the events : - Event_timed is no more used during parsing. Less problems because it has a mutex. Event_parse_data class is used during parsing. It is suited only for this purpose. It's pretty lightweight - Late checking of data from parsing is being performed. This should solve the problems of nested events in SP or other events (for the situation of no nested bodies). Before if an ALTER EVENT was in a SP, then when the SP was compiled, and not executed, the actual init_xxx methods of Event_timed were called, which is wrong. - It could be a side effect of using a specialized class, but test events_stress is now 25% quicker. Cut No8 will start splitting Event_scheduler into 2 parts, the QUEUE will be moved to Event_queue. mysql-test/r/events.result: update result mysql-test/t/events.test: disabled is actually wrong, should be disable, but because of the early checking it was never parsed. sql/event_data_objects.cc: move add init_xxx methods from Event_timed to Event_parse_data Event_parse data does not need definer_user and definer_host in Event_timed::compile() do not use lex.et, well there is no more lex.et :) sql/event_data_objects.h: move parsing responsibilities from Event_timed to Event_parse_data sql/event_db_repository.cc: No more Event_timed comes from parsing but Event_parse_data The initialization of Item*-s from parsing is done late, and not during the actual parsing. This is the right way to go because if an ALTER EVENT is inside a SP or CREATE EVENT it should not be executed (initialized) during parsing, as it was done. sql/event_db_repository.h: No more Event_timed comes from parsing but Event_parse_data The initialization of Item*-s from parsing is done late, and not during the actual parsing. This is the right way to go because if an ALTER EVENT is inside a SP or CREATE EVENT it should not be executed (initialized) during parsing, as it was done. sql/event_scheduler.cc: No more Event_timed comes from parsing but Event_parse_data The initialization of Item*-s from parsing is done late, and not during the actual parsing. This is the right way to go because if an ALTER EVENT is inside a SP or CREATE EVENT it should not be executed (initialized) during parsing, as it was done. sql/event_scheduler.h: No more Event_timed comes from parsing but Event_parse_data The initialization of Item*-s from parsing is done late, and not during the actual parsing. This is the right way to go because if an ALTER EVENT is inside a SP or CREATE EVENT it should not be executed (initialized) during parsing, as it was done. sql/events.cc: No more Event_timed comes from parsing but Event_parse_data The initialization of Item*-s from parsing is done late, and not during the actual parsing. This is the right way to go because if an ALTER EVENT is inside a SP or CREATE EVENT it should not be executed (initialized) during parsing, as it was done. sql/events.h: No more Event_timed comes from parsing but Event_parse_data The initialization of Item*-s from parsing is done late, and not during the actual parsing. This is the right way to go because if an ALTER EVENT is inside a SP or CREATE EVENT it should not be executed (initialized) during parsing, as it was done. sql/sql_lex.cc: lex->et_compile_phase and lex->et are no more. Use lex->event_parse_data sql/sql_lex.h: lex->et_compile_phase and lex->et are no more. Use lex->event_parse_data sql/sql_parse.cc: lex->et_compile_phase and lex->et are no more. Use lex->event_parse_data ACL checks were moved inside the Events subsystem. Also ending of the transaction is performed only just before doing disk operation. Therefore only when needed. sql/sql_yacc.yy: lex->et and lex->et_parse_phase are no more Use the specialized for parsing Event_parse_data --- mysql-test/r/events.result | 4 +- mysql-test/t/events.test | 4 +- sql/event_data_objects.cc | 551 ++++++++++++++------------------------------- sql/event_data_objects.h | 63 +----- sql/event_db_repository.cc | 381 ++++++++++++++++++++++++++----- sql/event_db_repository.h | 15 +- sql/event_scheduler.cc | 78 ++----- sql/event_scheduler.h | 11 +- sql/events.cc | 32 ++- sql/events.h | 10 +- sql/sql_lex.cc | 4 +- sql/sql_lex.h | 3 - sql/sql_parse.cc | 88 ++------ sql/sql_yacc.yy | 151 +------------ 14 files changed, 569 insertions(+), 826 deletions(-) diff --git a/mysql-test/r/events.result b/mysql-test/r/events.result index f95953cdc86..473acbd5984 100644 --- a/mysql-test/r/events.result +++ b/mysql-test/r/events.result @@ -297,9 +297,9 @@ select db, name, body, definer, convert_tz(execute_at, 'UTC', 'SYSTEM'), on_comp db name body definer convert_tz(execute_at, 'UTC', 'SYSTEM') on_completion events_test e_26 set @a = 5 root@localhost 2017-01-01 00:00:00 DROP drop event e_26; -create event e_26 on schedule at NULL disabled do set @a = 5; +create event e_26 on schedule at NULL disable do set @a = 5; ERROR HY000: Incorrect AT value: 'NULL' -create event e_26 on schedule at 'definitely not a datetime' disabled do set @a = 5; +create event e_26 on schedule at 'definitely not a datetime' disable do set @a = 5; ERROR HY000: Incorrect AT value: 'definitely not a datetime' set names utf8; create event задачка on schedule every 123 minute starts now() ends now() + interval 1 month do select 1; diff --git a/mysql-test/t/events.test b/mysql-test/t/events.test index 1940016a7a1..5dff48c203b 100644 --- a/mysql-test/t/events.test +++ b/mysql-test/t/events.test @@ -255,9 +255,9 @@ create event e_26 on schedule at '2017-01-01 00:00:00' disable do set @a = 5; select db, name, body, definer, convert_tz(execute_at, 'UTC', 'SYSTEM'), on_completion from mysql.event; drop event e_26; --error ER_WRONG_VALUE -create event e_26 on schedule at NULL disabled do set @a = 5; +create event e_26 on schedule at NULL disable do set @a = 5; --error ER_WRONG_VALUE -create event e_26 on schedule at 'definitely not a datetime' disabled do set @a = 5; +create event e_26 on schedule at 'definitely not a datetime' disable do set @a = 5; set names utf8; create event задачка on schedule every 123 minute starts now() ends now() + interval 1 month do select 1; diff --git a/sql/event_data_objects.cc b/sql/event_data_objects.cc index 228f1a4ff4a..a47a3e9e936 100644 --- a/sql/event_data_objects.cc +++ b/sql/event_data_objects.cc @@ -35,186 +35,18 @@ Event_parse_data::new_instance(THD *thd) Event_parse_data::Event_parse_data() { item_execute_at= item_expression= item_starts= item_ends= NULL; -} - - -/* - 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_parse_data::init_body(THD *thd) -{ - DBUG_ENTER("Event_parse_data::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= thd->strmake((char *)body_begin, body.length); - - DBUG_VOID_RETURN; -} - - -/* - 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; + status= ENABLED; + on_completion= ON_COMPLETION_DROP; + expression= 0; + /* Actually in the parser STARTS is always set */ 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; + body.str= comment.str= 0; + body.length= comment.length= 0; } @@ -222,37 +54,25 @@ Event_timed::init() Set a name of the event SYNOPSIS - Event_timed::init_name() + Event_parse_data::init_name() thd THD spn the name extracted in the parser */ void -Event_timed::init_name(THD *thd, sp_name *spn) +Event_parse_data::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; + DBUG_ENTER("Event_parse_data::init_name"); /* 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_ASSERT(spn); + dbname.length= spn->m_db.length; + if (spn->m_db.length == 0) + dbname.str= NULL; + else + dbname.str= thd->strmake(spn->m_db.str, spn->m_db.length); + name.length= spn->m_name.length; + name.str= thd->strmake(spn->m_name.str, spn->m_name.length); DBUG_PRINT("dbname", ("len=%d db=%s",dbname.length, dbname.str)); DBUG_PRINT("name", ("len=%d name=%s",name.length, name.str)); @@ -265,7 +85,7 @@ Event_timed::init_name(THD *thd, sp_name *spn) Set body of the event - what should be executed. SYNOPSIS - Event_timed::init_body() + Event_parse_data::init_body() thd THD NOTE @@ -277,9 +97,9 @@ Event_timed::init_name(THD *thd, sp_name *spn) */ void -Event_timed::init_body(THD *thd) +Event_parse_data::init_body(THD *thd) { - DBUG_ENTER("Event_timed::init_body"); + DBUG_ENTER("Event_parse_data::init_body"); DBUG_PRINT("info", ("body=[%s] body_begin=0x%ld end=0x%ld", body_begin, body_begin, thd->lex->ptr)); @@ -331,17 +151,60 @@ Event_timed::init_body(THD *thd) ++body_begin; --body.length; } - body.str= strmake_root(thd->mem_root, (char *)body_begin, body.length); + body.str= thd->strmake((char *)body_begin, body.length); DBUG_VOID_RETURN; } +/* + Inits definer (definer_user and definer_host) during parsing. + + SYNOPSIS + Event_parse_data::init_definer() + + RETURN VALUE + 0 OK +*/ + +int +Event_parse_data::init_definer(THD *thd) +{ + int definer_user_len; + int definer_host_len; + DBUG_ENTER("Event_parse_data::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_len= strlen(thd->security_ctx->priv_user); + definer_host_len= strlen(thd->security_ctx->priv_host); + + /* + 1 for @ */ + DBUG_PRINT("info",("init definer as whole")); + definer.length= definer_user_len + definer_host_len + 1; + definer.str= thd->alloc(definer.length + 1); + + DBUG_PRINT("info",("copy the user")); + memcpy(definer.str, thd->security_ctx->priv_user, definer_user_len); + definer.str[definer_user_len]= '@'; + + DBUG_PRINT("info",("copy the host")); + memcpy(definer.str + definer_user_len + 1, thd->security_ctx->priv_host, + definer_host_len); + definer.str[definer.length]= '\0'; + DBUG_PRINT("info",("definer [%s] initted", definer.str)); + + DBUG_RETURN(0); +} + + /* Set time for execution for one time events. SYNOPSIS - Event_timed::init_execute_at() + Event_parse_data::init_execute_at() expr when (datetime) RETURN VALUE @@ -352,14 +215,14 @@ Event_timed::init_body(THD *thd) */ int -Event_timed::init_execute_at(THD *thd, Item *expr) +Event_parse_data::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"); + DBUG_ENTER("Event_parse_data::init_execute_at"); if (expr->fix_fields(thd, &expr)) DBUG_RETURN(EVEX_PARSE_ERROR); @@ -368,7 +231,7 @@ Event_timed::init_execute_at(THD *thd, Item *expr) 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()); @@ -403,7 +266,7 @@ Event_timed::init_execute_at(THD *thd, Item *expr) Set time for execution for transient events. SYNOPSIS - Event_timed::init_interval() + Event_parse_data::init_interval() expr how much? new_interval what is the interval @@ -415,12 +278,12 @@ Event_timed::init_execute_at(THD *thd, Item *expr) */ int -Event_timed::init_interval(THD *thd, Item *expr, interval_type new_interval) +Event_parse_data::init_interval(THD *thd, Item *expr, interval_type new_interval) { String value; INTERVAL interval_tmp; - DBUG_ENTER("Event_timed::init_interval"); + DBUG_ENTER("Event_parse_data::init_interval"); if (expr->fix_fields(thd, &expr)) DBUG_RETURN(EVEX_PARSE_ERROR); @@ -504,9 +367,9 @@ Event_timed::init_interval(THD *thd, Item *expr, interval_type new_interval) Set activation time. SYNOPSIS - Event_timed::init_starts() - expr how much? - interval what is the interval + Event_parse_data::init_starts() + expr how much? + interval what is the interval NOTES Note that activation time is not execution time. @@ -523,13 +386,13 @@ Event_timed::init_interval(THD *thd, Item *expr, interval_type new_interval) */ int -Event_timed::init_starts(THD *thd, Item *new_starts) +Event_parse_data::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"); + DBUG_ENTER("Event_parse_data::init_starts"); if (new_starts->fix_fields(thd, &new_starts)) DBUG_RETURN(EVEX_PARSE_ERROR); @@ -570,7 +433,7 @@ Event_timed::init_starts(THD *thd, Item *new_starts) Set deactivation time. SYNOPSIS - Event_timed::init_ends() + Event_parse_data::init_ends() thd THD new_ends when? @@ -590,13 +453,13 @@ Event_timed::init_starts(THD *thd, Item *new_starts) */ int -Event_timed::init_ends(THD *thd, Item *new_ends) +Event_parse_data::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"); + DBUG_ENTER("Event_parse_data::init_ends"); if (new_ends->fix_fields(thd, &new_ends)) DBUG_RETURN(EVEX_PARSE_ERROR); @@ -641,67 +504,106 @@ Event_timed::init_ends(THD *thd, Item *new_ends) /* - Sets comment. + Constructor SYNOPSIS - Event_timed::init_comment() - thd THD - used for memory allocation - comment the string. + Event_timed::Event_timed() */ -void -Event_timed::init_comment(THD *thd, LEX_STRING *set_comment) +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), dropped(false), + free_sphead_on_delete(true), flags(0) + { - DBUG_ENTER("Event_timed::init_comment"); + pthread_mutex_init(&this->LOCK_running, MY_MUTEX_INIT_FAST); + pthread_cond_init(&this->COND_finished, NULL); + init(); +} - comment.str= strmake_root(thd->mem_root, set_comment->str, - comment.length= set_comment->length); - DBUG_VOID_RETURN; +/* + Destructor + + SYNOPSIS + Event_timed::~Event_timed() +*/ + +Event_timed::~Event_timed() +{ + deinit_mutexes(); + + if (free_sphead_on_delete) + free_sp(); } /* - Inits definer (definer_user and definer_host) during parsing. + Destructor SYNOPSIS - Event_timed::init_definer() - - RETURN VALUE - 0 OK + Event_timed::deinit_mutexes() */ -int -Event_timed::init_definer(THD *thd) +void +Event_timed::deinit_mutexes() { - DBUG_ENTER("Event_timed::init_definer"); + pthread_mutex_destroy(&this->LOCK_running); + pthread_cond_destroy(&this->COND_finished); +} - 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); +/* + Checks whether the event is running - 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); + SYNOPSIS + Event_timed::is_running() +*/ - DBUG_PRINT("info",("copy the user")); - memcpy(definer.str, definer_user.str, definer_user.length); - definer.str[definer_user.length]= '@'; +bool +Event_timed::is_running() +{ + bool ret; - 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")); + VOID(pthread_mutex_lock(&this->LOCK_running)); + ret= running; + VOID(pthread_mutex_unlock(&this->LOCK_running)); - DBUG_RETURN(0); + 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; } @@ -739,20 +641,17 @@ Event_timed::load_from_row(MEM_ROOT *mem_root, TABLE *table) if (table->s->fields != ET_FIELD_COUNT) goto error; - if ((et->dbname.str= get_field(mem_root, - table->field[ET_FIELD_DB])) == NULL) + if ((et->dbname.str= get_field(mem_root, table->field[ET_FIELD_DB])) == NULL) goto error; et->dbname.length= strlen(et->dbname.str); - if ((et->name.str= get_field(mem_root, - table->field[ET_FIELD_NAME])) == NULL) + if ((et->name.str= get_field(mem_root, table->field[ET_FIELD_NAME])) == NULL) goto error; et->name.length= strlen(et->name.str); - if ((et->body.str= get_field(mem_root, - table->field[ET_FIELD_BODY])) == NULL) + if ((et->body.str= get_field(mem_root, table->field[ET_FIELD_BODY])) == NULL) goto error; et->body.length= strlen(et->body.str); @@ -837,7 +736,6 @@ Event_timed::load_from_row(MEM_ROOT *mem_root, TABLE *table) et->comment.length= strlen(et->comment.str); else et->comment.length= 0; - et->sql_mode= (ulong) table->field[ET_FIELD_SQL_MODE]->val_int(); @@ -858,7 +756,7 @@ error: 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 @@ -1639,7 +1537,6 @@ Event_timed::compile(THD *thd, MEM_ROOT *mem_root) &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", @@ -1662,7 +1559,7 @@ Event_timed::compile(THD *thd, MEM_ROOT *mem_root) } DBUG_PRINT("note", ("success compiling %s.%s", dbname.str, name.str)); - sphead= lex.et->sphead; + sphead= lex.sphead; sphead->m_db= dbname; sphead->set_definer(definer.str, definer.length); @@ -1670,8 +1567,6 @@ Event_timed::compile(THD *thd, MEM_ROOT *mem_root) sphead->optimize(); ret= 0; done: - lex.et->free_sphead_on_delete= false; - lex.et->deinit_mutexes(); lex_end(&lex); thd->restore_security_context(save_ctx); @@ -1709,7 +1604,7 @@ extern pthread_attr_t connection_attrib; 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"); @@ -1875,7 +1770,6 @@ event_timed_db_equal(Event_timed *et, LEX_STRING *db) } - /* Checks whether two events are equal by identifiers @@ -1888,121 +1782,8 @@ event_timed_db_equal(Event_timed *et, LEX_STRING *db) */ bool -event_timed_identifier_equal(Event_timed *a, Event_timed *b) +event_timed_identifier_equal(LEX_STRING db, LEX_STRING name, Event_timed *b) { - return event_timed_name_equal(a, &b->name) && - event_timed_db_equal(a, &b->dbname); -} - - -/* - 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(sp_name *name, LEX_STRING *event_name) -{ - return !sortcmp_lex_string(name->m_name, *event_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(sp_name *name, LEX_STRING *db) -{ - return !sortcmp_lex_string(name->m_db, *db, 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(sp_name *a, Event_timed *b) -{ - return event_timed_name_equal(a, &b->name) && - event_timed_db_equal(a, &b->dbname); -} - - -/* - 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; + return !sortcmp_lex_string(name, b->name, system_charset_info) && + !sortcmp_lex_string(db, b->dbname, system_charset_info); } diff --git a/sql/event_data_objects.h b/sql/event_data_objects.h index ff547cebd5b..d8df8dd1e6c 100644 --- a/sql/event_data_objects.h +++ b/sql/event_data_objects.h @@ -54,19 +54,9 @@ class Event_timed; bool event_timed_db_equal(Event_timed *et, LEX_STRING *db); - -/* Compares the whole identifier*/ -bool -event_timed_identifier_equal(Event_timed *a, Event_timed *b); - -/* Compares only the schema part of the identifier */ -bool -event_timed_db_equal(sp_name *name, LEX_STRING *db); - - /* Compares the whole identifier*/ bool -event_timed_identifier_equal(sp_name *a, Event_timed *b); +event_timed_identifier_equal(LEX_STRING db, LEX_STRING name, Event_timed *b); class Event_timed @@ -123,7 +113,6 @@ public: enum enum_status status; sp_head *sphead; ulong sql_mode; - const uchar *body_begin; bool dropped; bool free_sphead_on_delete; @@ -138,9 +127,6 @@ public: 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)"); @@ -150,17 +136,6 @@ public: 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(); @@ -171,30 +146,6 @@ public: 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); @@ -231,9 +182,6 @@ public: void free_sp(); - bool - has_equal_db(Event_timed *etn); - int kill_thread(THD *thd); @@ -268,12 +216,9 @@ public: LEX_STRING dbname; LEX_STRING name; LEX_STRING body; - - LEX_STRING definer_user; - LEX_STRING definer_host; LEX_STRING definer;// combination of user and host - LEX_STRING comment; + Item* item_starts; Item* item_ends; Item* item_execute_at; @@ -316,10 +261,6 @@ public: void init_body(THD *thd); - - void - init_comment(THD *thd, LEX_STRING *set_comment); - }; diff --git a/sql/event_db_repository.cc b/sql/event_db_repository.cc index fc771b6bac3..68d8234cc90 100644 --- a/sql/event_db_repository.cc +++ b/sql/event_db_repository.cc @@ -34,7 +34,7 @@ TABLE_FIELD_W_TYPE event_table_fields[ET_FIELD_COUNT] = { {(char *) STRING_WITH_LEN("db")}, {(char *) STRING_WITH_LEN("char(64)")}, {(char *) STRING_WITH_LEN("utf8")} - }, + }, { {(char *) STRING_WITH_LEN("name")}, {(char *) STRING_WITH_LEN("char(64)")}, @@ -44,7 +44,7 @@ TABLE_FIELD_W_TYPE event_table_fields[ET_FIELD_COUNT] = { {(char *) STRING_WITH_LEN("body")}, {(char *) STRING_WITH_LEN("longblob")}, {NULL, 0} - }, + }, { {(char *) STRING_WITH_LEN("definer")}, {(char *) STRING_WITH_LEN("char(77)")}, @@ -54,7 +54,7 @@ TABLE_FIELD_W_TYPE event_table_fields[ET_FIELD_COUNT] = { {(char *) STRING_WITH_LEN("execute_at")}, {(char *) STRING_WITH_LEN("datetime")}, {NULL, 0} - }, + }, { {(char *) STRING_WITH_LEN("interval_value")}, {(char *) STRING_WITH_LEN("int(11)")}, @@ -68,7 +68,7 @@ TABLE_FIELD_W_TYPE event_table_fields[ET_FIELD_COUNT] = { "'DAY_MICROSECOND','HOUR_MICROSECOND','MINUTE_MICROSECOND'," "'SECOND_MICROSECOND')")}, {NULL, 0} - }, + }, { {(char *) STRING_WITH_LEN("created")}, {(char *) STRING_WITH_LEN("timestamp")}, @@ -78,7 +78,7 @@ TABLE_FIELD_W_TYPE event_table_fields[ET_FIELD_COUNT] = { {(char *) STRING_WITH_LEN("modified")}, {(char *) STRING_WITH_LEN("timestamp")}, {NULL, 0} - }, + }, { {(char *) STRING_WITH_LEN("last_executed")}, {(char *) STRING_WITH_LEN("datetime")}, @@ -88,7 +88,7 @@ TABLE_FIELD_W_TYPE event_table_fields[ET_FIELD_COUNT] = { {(char *) STRING_WITH_LEN("starts")}, {(char *) STRING_WITH_LEN("datetime")}, {NULL, 0} - }, + }, { {(char *) STRING_WITH_LEN("ends")}, {(char *) STRING_WITH_LEN("datetime")}, @@ -98,7 +98,7 @@ TABLE_FIELD_W_TYPE event_table_fields[ET_FIELD_COUNT] = { {(char *) STRING_WITH_LEN("status")}, {(char *) STRING_WITH_LEN("enum('ENABLED','DISABLED')")}, {NULL, 0} - }, + }, { {(char *) STRING_WITH_LEN("on_completion")}, {(char *) STRING_WITH_LEN("enum('DROP','PRESERVE')")}, @@ -186,6 +186,133 @@ evex_fill_row(THD *thd, TABLE *table, Event_timed *et, my_bool is_update) goto err_truncate; } + if (et->expression) + { + table->field[ET_FIELD_INTERVAL_EXPR]->set_notnull(); + table->field[ET_FIELD_INTERVAL_EXPR]->store((longlong)et->expression, true); + + table->field[ET_FIELD_TRANSIENT_INTERVAL]->set_notnull(); + /* + In the enum (C) intervals start from 0 but in mysql enum valid values start + from 1. Thus +1 offset is needed! + */ + table->field[ET_FIELD_TRANSIENT_INTERVAL]-> + store((longlong)et->interval+1, true); + + table->field[ET_FIELD_EXECUTE_AT]->set_null(); + + if (!et->starts_null) + { + table->field[ET_FIELD_STARTS]->set_notnull(); + table->field[ET_FIELD_STARTS]-> + store_time(&et->starts, MYSQL_TIMESTAMP_DATETIME); + } + + if (!et->ends_null) + { + table->field[ET_FIELD_ENDS]->set_notnull(); + table->field[ET_FIELD_ENDS]-> + store_time(&et->ends, MYSQL_TIMESTAMP_DATETIME); + } + } + else if (et->execute_at.year) + { + table->field[ET_FIELD_INTERVAL_EXPR]->set_null(); + table->field[ET_FIELD_TRANSIENT_INTERVAL]->set_null(); + table->field[ET_FIELD_STARTS]->set_null(); + table->field[ET_FIELD_ENDS]->set_null(); + + table->field[ET_FIELD_EXECUTE_AT]->set_notnull(); + table->field[ET_FIELD_EXECUTE_AT]-> + store_time(&et->execute_at, MYSQL_TIMESTAMP_DATETIME); + } + else + { + DBUG_ASSERT(is_update); + /* + it is normal to be here when the action is update + this is an error if the action is create. something is borked + */ + } + + ((Field_timestamp *)table->field[ET_FIELD_MODIFIED])->set_time(); + + if (et->comment.str) + { + if (table->field[field_num= ET_FIELD_COMMENT]-> + store(et->comment.str, et->comment.length, scs)) + goto err_truncate; + } + + DBUG_RETURN(0); +err_truncate: + my_error(ER_EVENT_DATA_TOO_LONG, MYF(0), table->field[field_num]->field_name); + DBUG_RETURN(EVEX_GENERAL_ERROR); +} + + +/* + Puts some data common to CREATE and ALTER EVENT into a row. + + SYNOPSIS + evex_fill_row() + thd THD + table the row to fill out + et Event's data + + RETURN VALUE + 0 - OK + EVEX_GENERAL_ERROR - bad data + EVEX_GET_FIELD_FAILED - field count does not match. table corrupted? + + DESCRIPTION + Used both when an event is created and when it is altered. +*/ + +static int +evex_fill_row(THD *thd, TABLE *table, Event_parse_data *et, my_bool is_update) +{ + CHARSET_INFO *scs= system_charset_info; + enum enum_events_table_field field_num; + + DBUG_ENTER("evex_fill_row"); + + DBUG_PRINT("info", ("dbname=[%s]", et->dbname.str)); + DBUG_PRINT("info", ("name =[%s]", et->name.str)); + DBUG_PRINT("info", ("body =[%s]", et->body.str)); + + if (table->field[field_num= ET_FIELD_DEFINER]-> + store(et->definer.str, et->definer.length, scs)) + goto err_truncate; + + if (table->field[field_num= ET_FIELD_DB]-> + store(et->dbname.str, et->dbname.length, scs)) + goto err_truncate; + + if (table->field[field_num= ET_FIELD_NAME]-> + store(et->name.str, et->name.length, scs)) + goto err_truncate; + + /* both ON_COMPLETION and STATUS are NOT NULL thus not calling set_notnull()*/ + table->field[ET_FIELD_ON_COMPLETION]-> + store((longlong)et->on_completion, true); + + table->field[ET_FIELD_STATUS]->store((longlong)et->status, true); + + /* + Change the SQL_MODE only if body was present in an ALTER EVENT and of course + always during CREATE EVENT. + */ + if (et->body.str) + { + table->field[ET_FIELD_SQL_MODE]-> + store((longlong)thd->variables.sql_mode, true); + + if (table->field[field_num= ET_FIELD_BODY]-> + store(et->body.str, et->body.length, scs)) + goto err_truncate; + } + if (et->expression) { table->field[ET_FIELD_INTERVAL_EXPR]->set_notnull(); @@ -462,14 +589,15 @@ Event_db_repository::fill_schema_events(THD *thd, TABLE_LIST *tables, char *db) */ int -Event_db_repository::find_event(THD *thd, sp_name *name, Event_timed **ett, +Event_db_repository::find_event(THD *thd, LEX_STRING dbname, LEX_STRING name, + Event_timed **ett, TABLE *tbl, MEM_ROOT *root) { TABLE *table; int ret; Event_timed *et= NULL; DBUG_ENTER("db_find_event"); - DBUG_PRINT("enter", ("name: %*s", name->m_name.length, name->m_name.str)); + DBUG_PRINT("enter", ("name: %*s", name.length, name.str)); if (tbl) table= tbl; @@ -480,22 +608,22 @@ Event_db_repository::find_event(THD *thd, sp_name *name, Event_timed **ett, goto done; } - if ((ret= evex_db_find_event_by_name(thd, name->m_db, name->m_name, table))) + if ((ret= evex_db_find_event_by_name(thd, dbname, name, table))) { - my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), name->m_name.str); - goto done; + my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), name.str); + goto done; } et= new Event_timed; - + /* - 1)The table should not be closed beforehand. ::load_from_row() only loads + 1)The table should not be closed beforehand. ::load_from_row() only loads and does not compile 2)::load_from_row() is silent on error therefore we emit error msg here */ if ((ret= et->load_from_row(root, table))) { - my_error(ER_CANNOT_LOAD_FROM_TABLE, MYF(0)); + my_error(ER_CANNOT_LOAD_FROM_TABLE, MYF(0), "event"); goto done; } @@ -557,7 +685,7 @@ Event_db_repository::open_event_table(THD *thd, enum thr_lock_type lock_type, if (simple_open_n_lock_tables(thd, &tables)) DBUG_RETURN(1); - + if (table_check_intact(tables.table, ET_FIELD_COUNT, event_table_fields, &mysql_event_last_create_time, @@ -572,6 +700,118 @@ Event_db_repository::open_event_table(THD *thd, enum thr_lock_type lock_type, } +/* + Checks parameters which we got from the parsing phase. + + SYNOPSIS + evex_check_params() + thd THD + et event's data + + RETURNS + 0 OK + EVEX_BAD_PARAMS Error + + REMARKS + Issues error messages +*/ + +int +evex_check_params(THD *thd, Event_parse_data *parse_data) +{ + const char *pos= NULL; + Item *bad_item; + + DBUG_ENTER("evex_check_timing_params"); + DBUG_PRINT("info", ("execute_at=0x%d expr=0x%d starts=0x%d ends=0x%d", + parse_data->item_execute_at, + parse_data->item_expression, + parse_data->item_starts, + parse_data->item_ends)); + + parse_data->init_name(thd, parse_data->identifier); + parse_data->init_definer(thd); + + if (!parse_data->dbname.str || + (thd->lex->sql_command == SQLCOM_ALTER_EVENT && thd->lex->spname && + !thd->lex->spname->m_db.str)) + { + my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR), MYF(0)); + DBUG_RETURN(EVEX_BAD_PARAMS); + } + + if (check_access(thd, EVENT_ACL, parse_data->dbname.str, 0, 0, 0, + is_schema_db(parse_data->dbname.str)) || + (thd->lex->sql_command == SQLCOM_ALTER_EVENT && thd->lex->spname && + (check_access(thd, EVENT_ACL, thd->lex->spname->m_db.str, 0, 0, 0, + is_schema_db(thd->lex->spname->m_db.str))))) + DBUG_RETURN(EVEX_BAD_PARAMS); + + if (parse_data->item_execute_at) + { + DBUG_PRINT("info", ("ONE TIME")); + if (parse_data->init_execute_at(thd, parse_data->item_execute_at)) + { + pos= "AT"; + bad_item= parse_data->item_execute_at; + goto wrong_value; + } + } + else + { + int res; + DBUG_PRINT("info", ("RECURRING")); + + if (parse_data->item_expression && + (res= parse_data->init_interval(thd, parse_data->item_expression, + parse_data->interval))) + { + switch (res) { + case EVEX_BAD_PARAMS: + my_error(ER_EVENT_INTERVAL_NOT_POSITIVE_OR_TOO_BIG, MYF(0)); + break; + case EVEX_MICROSECOND_UNSUP: + my_error(ER_NOT_SUPPORTED_YET, MYF(0), "MICROSECOND"); + break; + default: + pos= "INTERVAL"; + bad_item= parse_data->item_expression; + goto wrong_value; + } + DBUG_RETURN(EVEX_BAD_PARAMS); + } + + if (parse_data->item_starts && + parse_data->init_starts(thd, parse_data->item_starts)) + { + pos= "STARTS"; + bad_item= parse_data->item_starts; + goto wrong_value; + } + + if (parse_data->item_ends && + parse_data->init_ends(thd, parse_data->item_ends)) + { + /* + despite the error name the value is + eng "ENDS is either invalid or before STARTS" + */ + my_error(ER_EVENT_ENDS_BEFORE_STARTS, MYF(0)); + DBUG_RETURN(EVEX_BAD_PARAMS); + } + } + DBUG_RETURN(0); +wrong_value: + { + char buff[120]; + String str(buff,(uint32) sizeof(buff), system_charset_info); + String *str2= bad_item->fixed? bad_item->val_str(&str):NULL; + my_error(ER_WRONG_VALUE, MYF(0), pos, str2? str2->c_ptr():"NULL"); + DBUG_RETURN(EVEX_BAD_PARAMS); + } +} + + /* Creates an event in mysql.event @@ -592,7 +832,7 @@ Event_db_repository::open_event_table(THD *thd, enum thr_lock_type lock_type, */ int -Event_db_repository::create_event(THD *thd, Event_timed *et, +Event_db_repository::create_event(THD *thd, Event_parse_data *parse_data, my_bool create_if_not, uint *rows_affected) { int ret= 0; @@ -601,7 +841,6 @@ Event_db_repository::create_event(THD *thd, Event_timed *et, char olddb[128]; bool dbchanged= false; DBUG_ENTER("Event_db_repository::create_event"); - DBUG_PRINT("enter", ("name: %.*s", et->name.length, et->name.str)); *rows_affected= 0; DBUG_PRINT("info", ("open mysql.event for update")); @@ -611,22 +850,28 @@ Event_db_repository::create_event(THD *thd, Event_timed *et, goto err; } + if (evex_check_params(thd, parse_data)) + goto err; + + DBUG_PRINT("info", ("name: %.*s", parse_data->name.length, + parse_data->name.str)); + DBUG_PRINT("info", ("check existance of an event with the same name")); - if (!evex_db_find_event_by_name(thd, et->dbname, et->name, table)) + if (!evex_db_find_event_by_name(thd, parse_data->dbname, parse_data->name, table)) { if (create_if_not) { push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, ER_EVENT_ALREADY_EXISTS, ER(ER_EVENT_ALREADY_EXISTS), - et->name.str); + parse_data->name.str); goto ok; } - my_error(ER_EVENT_ALREADY_EXISTS, MYF(0), et->name.str); + my_error(ER_EVENT_ALREADY_EXISTS, MYF(0), parse_data->name.str); goto err; } DBUG_PRINT("info", ("non-existant, go forward")); - if ((ret= sp_use_new_db(thd, et->dbname.str,olddb, sizeof(olddb),0, + if ((ret= sp_use_new_db(thd, parse_data->dbname.str, olddb, sizeof(olddb), 0, &dbchanged))) { my_error(ER_BAD_DB_ERROR, MYF(0)); @@ -635,28 +880,30 @@ Event_db_repository::create_event(THD *thd, Event_timed *et, restore_record(table, s->default_values); // Get default values for fields - if (system_charset_info->cset->numchars(system_charset_info, et->dbname.str, - et->dbname.str + et->dbname.length) - > EVEX_DB_FIELD_LEN) + if (system_charset_info->cset-> + numchars(system_charset_info, parse_data->dbname.str, + parse_data->dbname.str + + parse_data->dbname.length) > EVEX_DB_FIELD_LEN) { - my_error(ER_TOO_LONG_IDENT, MYF(0), et->dbname.str); + my_error(ER_TOO_LONG_IDENT, MYF(0), parse_data->dbname.str); goto err; } - if (system_charset_info->cset->numchars(system_charset_info, et->name.str, - et->name.str + et->name.length) - > EVEX_DB_FIELD_LEN) + if (system_charset_info->cset-> + numchars(system_charset_info, parse_data->name.str, + parse_data->name.str + + parse_data->name.length) > EVEX_DB_FIELD_LEN) { - my_error(ER_TOO_LONG_IDENT, MYF(0), et->name.str); + my_error(ER_TOO_LONG_IDENT, MYF(0), parse_data->name.str); goto err; } - if (et->body.length > table->field[ET_FIELD_BODY]->field_length) + if (parse_data->body.length > table->field[ET_FIELD_BODY]->field_length) { - my_error(ER_TOO_LONG_BODY, MYF(0), et->name.str); + my_error(ER_TOO_LONG_BODY, MYF(0), parse_data->name.str); goto err; } - if (!(et->expression) && !(et->execute_at.year)) + if (!(parse_data->expression) && !(parse_data->execute_at.year)) { DBUG_PRINT("error", ("neither expression nor execute_at are set!")); my_error(ER_EVENT_NEITHER_M_EXPR_NOR_M_AT, MYF(0)); @@ -669,12 +916,16 @@ Event_db_repository::create_event(THD *thd, Event_timed *et, evex_fill_row() calls my_error() in case of error so no need to handle it here */ - if ((ret= evex_fill_row(thd, table, et, false))) + if ((ret= evex_fill_row(thd, table, parse_data, false))) goto err; + /* Close active transaction only if We are going to modify disk */ + if (end_active_trans(thd)) + goto err; + if (table->file->ha_write_row(table->record[0])) { - my_error(ER_EVENT_STORE_FAILED, MYF(0), et->name.str, ret); + my_error(ER_EVENT_STORE_FAILED, MYF(0), parse_data->name.str, ret); goto err; } @@ -724,40 +975,44 @@ err: */ int -Event_db_repository::update_event(THD *thd, Event_timed *et, sp_name *new_name) +Event_db_repository::update_event(THD *thd, Event_parse_data *parse_data, + sp_name *new_name) { CHARSET_INFO *scs= system_charset_info; TABLE *table; int ret= EVEX_OPEN_TABLE_FAILED; DBUG_ENTER("Event_db_repository::update_event"); - DBUG_PRINT("enter", ("dbname: %.*s", et->dbname.length, et->dbname.str)); - DBUG_PRINT("enter", ("name: %.*s", et->name.length, et->name.str)); - DBUG_PRINT("enter", ("user: %.*s", et->definer.length, et->definer.str)); - if (new_name) - DBUG_PRINT("enter", ("rename to: %.*s", new_name->m_name.length, - new_name->m_name.str)); if (open_event_table(thd, TL_WRITE, &table)) { my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0)); goto err; } - + + if (evex_check_params(thd, parse_data)) + goto err; + + DBUG_PRINT("info", ("dbname: %s", parse_data->dbname.str)); + DBUG_PRINT("info", ("name: %s", parse_data->name.str)); + DBUG_PRINT("info", ("user: %s", parse_data->definer.str)); + if (new_name) + DBUG_PRINT("info", ("rename to: %s", new_name->m_name.str)); + /* first look whether we overwrite */ if (new_name) { - if (!sortcmp_lex_string(et->name, new_name->m_name, scs) && - !sortcmp_lex_string(et->dbname, new_name->m_db, scs)) + if (!sortcmp_lex_string(parse_data->name, new_name->m_name, scs) && + !sortcmp_lex_string(parse_data->dbname, new_name->m_db, scs)) { - my_error(ER_EVENT_SAME_NAME, MYF(0), et->name.str); - goto err; + my_error(ER_EVENT_SAME_NAME, MYF(0), parse_data->name.str); + goto err; } - + if (!evex_db_find_event_by_name(thd,new_name->m_db,new_name->m_name,table)) { my_error(ER_EVENT_ALREADY_EXISTS, MYF(0), new_name->m_name.str); goto err; - } + } } /* ...and then if there is such an event. Don't exchange the blocks @@ -765,9 +1020,10 @@ Event_db_repository::update_event(THD *thd, Event_timed *et, sp_name *new_name) overwrite the key and SE will tell us that it cannot find the already found row (copied into record[1] later */ - if (EVEX_KEY_NOT_FOUND == find_event_by_name(thd, et->dbname, et->name, table)) + if (EVEX_KEY_NOT_FOUND == find_event_by_name(thd, parse_data->dbname, + parse_data->name, table)) { - my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), et->name.str); + my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), parse_data->name.str); goto err; } @@ -780,20 +1036,24 @@ Event_db_repository::update_event(THD *thd, Event_timed *et, sp_name *new_name) evex_fill_row() calls my_error() in case of error so no need to handle it here */ - if ((ret= evex_fill_row(thd, table, et, true))) + if ((ret= evex_fill_row(thd, table, parse_data, true))) goto err; if (new_name) - { + { table->field[ET_FIELD_DB]-> store(new_name->m_db.str, new_name->m_db.length, scs); table->field[ET_FIELD_NAME]-> store(new_name->m_name.str, new_name->m_name.length, scs); } + /* Close active transaction only if We are going to modify disk */ + if (end_active_trans(thd)) + goto err; + if ((ret= table->file->ha_update_row(table->record[1], table->record[0]))) { - my_error(ER_EVENT_STORE_FAILED, MYF(0), et->name.str, ret); + my_error(ER_EVENT_STORE_FAILED, MYF(0), parse_data->name.str, ret); goto err; } @@ -845,6 +1105,10 @@ Event_db_repository::drop_event(THD *thd, LEX_STRING db, LEX_STRING name, if (!(ret= evex_db_find_event_by_name(thd, db, name, table))) { + /* Close active transaction only if We are going to modify disk */ + if ((ret= end_active_trans(thd))) + goto done; + if ((ret= table->file->ha_delete_row(table->record[0]))) my_error(ER_EVENT_CANNOT_DELETE, MYF(0)); } @@ -998,7 +1262,7 @@ Event_db_repository::drop_events_by_field(THD *thd, */ int -Event_db_repository::load_named_event(THD *thd, Event_timed *etn, +Event_db_repository::load_named_event(THD *thd, LEX_STRING dbname, LEX_STRING name, Event_timed **etn_new) { int ret= 0; @@ -1007,14 +1271,11 @@ Event_db_repository::load_named_event(THD *thd, Event_timed *etn, Open_tables_state backup; DBUG_ENTER("Event_scheduler::load_and_compile_event"); - DBUG_PRINT("enter",("thd=%p name:%*s",thd, etn->name.length, etn->name.str)); + DBUG_PRINT("enter",("thd=%p name:%*s",thd, name.length, name.str)); thd->reset_n_backup_open_tables_state(&backup); /* No need to use my_error() here because db_find_event() has done it */ - { - sp_name spn(etn->dbname, etn->name); - ret= find_event(thd, &spn, &et_loaded, NULL, &repo_root); - } + ret= find_event(thd, dbname, name, &et_loaded, NULL, &repo_root); thd->restore_backup_open_tables_state(&backup); /* In this case no memory was allocated so we don't need to clean */ if (ret) diff --git a/sql/event_db_repository.h b/sql/event_db_repository.h index 1cbee96b68f..625d5bfb993 100644 --- a/sql/event_db_repository.h +++ b/sql/event_db_repository.h @@ -53,7 +53,8 @@ events_table_scan_all(THD *thd, TABLE *schema_table, TABLE *event_table); int fill_schema_events(THD *thd, TABLE_LIST *tables, COND * /* cond */); - +class Event_timed; +class Event_parse_data; class Event_queue_element; class Event_db_repository @@ -69,11 +70,11 @@ public: deinit_repository(); int - create_event(THD *thd, Event_timed *et, my_bool create_if_not, - uint *rows_affected); + create_event(THD *thd, Event_parse_data *parse_data, my_bool create_if_not, + uint *rows_affected); int - update_event(THD *thd, Event_timed *et, sp_name *new_name); + update_event(THD *thd, Event_parse_data *parse_data, sp_name *new_name); int drop_event(THD *thd, LEX_STRING db, LEX_STRING name, bool drop_if_exists, @@ -86,11 +87,11 @@ public: drop_user_events(THD *thd, LEX_STRING definer); int - find_event(THD *thd, sp_name *name, Event_timed **ett, TABLE *tbl, - MEM_ROOT *root); + find_event(THD *thd, LEX_STRING dbname, LEX_STRING name, Event_timed **ett, + TABLE *tbl, MEM_ROOT *root); int - load_named_event(THD *thd, Event_timed *etn, Event_timed **etn_new); + load_named_event(THD *thd, LEX_STRING dbname, LEX_STRING name, Event_timed **etn_new); int find_event_by_name(THD *thd, LEX_STRING db, LEX_STRING name, TABLE *table); diff --git a/sql/event_scheduler.cc b/sql/event_scheduler.cc index cb500de53b9..35c30e5fc5a 100644 --- a/sql/event_scheduler.cc +++ b/sql/event_scheduler.cc @@ -785,7 +785,7 @@ Event_scheduler::destroy() */ int -Event_scheduler::create_event(THD *thd, Event_timed *et, bool check_existence) +Event_scheduler::create_event(THD *thd, Event_parse_data *et, bool check_existence) { int res; Event_timed *et_new; @@ -799,14 +799,15 @@ Event_scheduler::create_event(THD *thd, Event_timed *et, bool check_existence) UNLOCK_SCHEDULER_DATA(); DBUG_RETURN(OP_OK); } - if (check_existence && find_event(et, FALSE)) + if (check_existence && find_event(et->dbname, et->name, FALSE)) { res= OP_ALREADY_EXISTS; goto end; } /* We need to load the event on scheduler_root */ - if (!(res= db_repository->load_named_event(thd, et, &et_new))) + if (!(res= db_repository-> + load_named_event(thd, et->dbname, et->name, &et_new))) { queue_insert_safe(&queue, (byte *) et_new); DBUG_PRINT("info", ("Sending COND_new_work")); @@ -850,7 +851,7 @@ Event_scheduler::drop_event(THD *thd, sp_name *name) DBUG_RETURN(OP_OK); } - if (!(et_old= find_event(name, TRUE))) + if (!(et_old= find_event(name->m_db, name->m_name, TRUE))) DBUG_PRINT("info", ("No such event found, probably DISABLED")); UNLOCK_SCHEDULER_DATA(); @@ -906,7 +907,7 @@ Event_scheduler::drop_event(THD *thd, sp_name *name) */ int -Event_scheduler::update_event(THD *thd, Event_timed *et, +Event_scheduler::update_event(THD *thd, Event_parse_data *et, LEX_STRING *new_schema, LEX_STRING *new_name) { @@ -931,7 +932,7 @@ Event_scheduler::update_event(THD *thd, Event_timed *et, DBUG_RETURN(OP_OK); } - if (!(et_old= find_event(et, TRUE))) + if (!(et_old= find_event(et->dbname, et->name, TRUE))) DBUG_PRINT("info", ("%s.%s not found cached, probably was DISABLED", et->dbname.str, et->name.str)); @@ -948,7 +949,8 @@ Event_scheduler::update_event(THD *thd, Event_timed *et, 1. Error occured 2. If the replace is DISABLED, we don't load it into the queue. */ - if (!(res= db_repository->load_named_event(thd, et, &et_new))) + if (!(res= db_repository-> + load_named_event(thd, et->dbname, et->name, &et_new))) { queue_insert_safe(&queue, (byte *) et_new); DBUG_PRINT("info", ("Sending COND_new_work")); @@ -1014,50 +1016,8 @@ Event_scheduler::update_event(THD *thd, Event_timed *et, SYNOPSIS Event_scheduler::find_event() - etn The event to find - comparator The function to use for comparing - remove_from_q If found whether to remove from the Q - - RETURN VALUE - NULL Not found - otherwise Address - - NOTE - The caller should do the locking also the caller is responsible for - actual signalling in case an event is removed from the queue - (signalling COND_new_work for instance). -*/ - -Event_timed * -Event_scheduler::find_event(Event_timed *etn, bool remove_from_q) -{ - uint i; - DBUG_ENTER("Event_scheduler::find_event"); - - for (i= 0; i < queue.elements; ++i) - { - Event_timed *et= (Event_timed *) queue_element(&queue, i); - DBUG_PRINT("info", ("[%s.%s]==[%s.%s]?", etn->dbname.str, etn->name.str, - et->dbname.str, et->name.str)); - if (event_timed_identifier_equal(etn, et)) - { - if (remove_from_q) - queue_remove(&queue, i); - DBUG_RETURN(et); - } - } - - DBUG_RETURN(NULL); -} - - -/* - Searches for an event in the scheduler queue - - SYNOPSIS - Event_scheduler::find_event() + db The schema of the event to find name The event to find - comparator The function to use for comparing remove_from_q If found whether to remove from the Q RETURN VALUE @@ -1071,7 +1031,7 @@ Event_scheduler::find_event(Event_timed *etn, bool remove_from_q) */ Event_timed * -Event_scheduler::find_event(sp_name *name, bool remove_from_q) +Event_scheduler::find_event(LEX_STRING db, LEX_STRING name, bool remove_from_q) { uint i; DBUG_ENTER("Event_scheduler::find_event"); @@ -1079,9 +1039,9 @@ Event_scheduler::find_event(sp_name *name, bool remove_from_q) for (i= 0; i < queue.elements; ++i) { Event_timed *et= (Event_timed *) queue_element(&queue, i); - DBUG_PRINT("info", ("[%s.%s]==[%s.%s]?", name->m_db.str, name->m_name.str, + DBUG_PRINT("info", ("[%s.%s]==[%s.%s]?", db.str, name.str, et->dbname.str, et->name.str)); - if (event_timed_identifier_equal(name, et)) + if (event_timed_identifier_equal(db, name, et)) { if (remove_from_q) queue_remove(&queue, i); @@ -1624,7 +1584,7 @@ Event_scheduler::clean_queue(THD *thd) SYNOPSIS Event_scheduler::stop_all_running_events() thd Thread - + NOTE LOCK_scheduler data must be acquired prior to call to this method */ @@ -1726,7 +1686,7 @@ Event_scheduler::stop() /* One situation to be here is if there was a start that forked a new thread but the new thread did not acquire yet LOCK_scheduler_data. - Hence, in this case return an error. + Hence, in this case return an error. */ DBUG_PRINT("info", ("manager not running but %d. doing nothing", state)); UNLOCK_SCHEDULER_DATA(); @@ -1841,7 +1801,7 @@ Event_scheduler::workers_count() } VOID(pthread_mutex_unlock(&LOCK_thread_count)); DBUG_PRINT("exit", ("%d", count)); - DBUG_RETURN(count); + DBUG_RETURN(count); } @@ -1851,7 +1811,7 @@ Event_scheduler::workers_count() SYNOPSIS Event_scheduler::check_n_suspend_if_needed() thd Thread - + RETURN VALUE FALSE Not suspended, we haven't slept TRUE We were suspended. LOCK_scheduler_data is unlocked. @@ -1936,7 +1896,7 @@ Event_scheduler::check_n_wait_for_non_empty_queue(THD *thd) if (!queue.elements) thd->enter_cond(&cond_vars[COND_new_work], &LOCK_scheduler_data, - "Empty queue, sleeping"); + "Empty queue, sleeping"); /* Wait in a loop protecting against catching spurious signals */ while (!queue.elements && state == RUNNING) @@ -2079,7 +2039,7 @@ Event_scheduler::get_state() SYNOPSIS Event_scheduler::initialized() - + RETURN VALUE FALSE Was not initialized so far TRUE Was initialized diff --git a/sql/event_scheduler.h b/sql/event_scheduler.h index bd099d10839..a274636b38b 100644 --- a/sql/event_scheduler.h +++ b/sql/event_scheduler.h @@ -57,11 +57,11 @@ public: /* Methods for queue management follow */ int - create_event(THD *thd, Event_timed *et, bool check_existence); + create_event(THD *thd, Event_parse_data *et, bool check_existence); int - update_event(THD *thd, Event_timed *et, LEX_STRING *new_schema, - LEX_STRING *new_name); + update_event(THD *thd, Event_parse_data *et, LEX_STRING *new_schema, + LEX_STRING *new_name); bool drop_event(THD *thd, sp_name *name); @@ -129,10 +129,7 @@ public: private: Event_timed * - find_event(Event_timed *etn, bool remove_from_q); - - Event_timed * - find_event(sp_name *name, bool remove_from_q); + find_event(LEX_STRING db, LEX_STRING name, bool remove_from_q); uint workers_count(); diff --git a/sql/events.cc b/sql/events.cc index 5fe5f019c87..28c57d6b493 100644 --- a/sql/events.cc +++ b/sql/events.cc @@ -186,7 +186,7 @@ common_1_lev_code: expr= tmp_expr - (tmp_expr/60)*60; /* the code after the switch will finish */ } - break; + break; case INTERVAL_DAY_SECOND: { ulonglong tmp_expr= expr; @@ -283,23 +283,19 @@ Events::open_event_table(THD *thd, enum thr_lock_type lock_type, */ int -Events::create_event(THD *thd, Event_timed *et, Event_parse_data *parse_data, - uint create_options, uint *rows_affected) +Events::create_event(THD *thd, Event_parse_data *parse_data, uint create_options, + uint *rows_affected) { int ret; - DBUG_ENTER("Events::create_event"); - DBUG_PRINT("enter", ("name: %*s options:%d", et->name.length, - et->name.str, create_options)); - if (!(ret= db_repository-> - create_event(thd, et, + create_event(thd, parse_data, create_options & HA_LEX_CREATE_IF_NOT_EXISTS, rows_affected))) { Event_scheduler *scheduler= Event_scheduler::get_instance(); if (scheduler->initialized() && - (ret= scheduler->create_event(thd, et, true))) + (ret= scheduler->create_event(thd, parse_data, true))) my_error(ER_EVENT_MODIFY_QUEUE_ERROR, MYF(0), ret); } /* No need to close the table, it will be closed in sql_parse::do_command */ @@ -315,7 +311,7 @@ Events::create_event(THD *thd, Event_timed *et, Event_parse_data *parse_data, Events::update_event() thd THD et event's data - new_name set in case of RENAME TO. + new_name set in case of RENAME TO. RETURN VALUE 0 OK @@ -328,25 +324,23 @@ Events::create_event(THD *thd, Event_timed *et, Event_parse_data *parse_data, */ int -Events::update_event(THD *thd, Event_timed *et, Event_parse_data *parse_data, - sp_name *new_name, uint *rows_affected) +Events::update_event(THD *thd, Event_parse_data *parse_data, sp_name *new_name, + uint *rows_affected) { int ret; - DBUG_ENTER("Events::update_event"); - DBUG_PRINT("enter", ("name: %*s", et->name.length, et->name.str)); /* db_update_event() opens & closes the table to prevent crash later in the code when loading and compiling the new definition. Also on error conditions my_error() is called so no need to handle here */ - if (!(ret= db_repository->update_event(thd, et, new_name))) + if (!(ret= db_repository->update_event(thd, parse_data, new_name))) { Event_scheduler *scheduler= Event_scheduler::get_instance(); if (scheduler->initialized() && - (ret= scheduler->update_event(thd, et, - new_name? &new_name->m_db: NULL, - new_name? &new_name->m_name: NULL))) + (ret= scheduler->update_event(thd, parse_data, + new_name? &new_name->m_db: NULL, + new_name? &new_name->m_name: NULL))) my_error(ER_EVENT_MODIFY_QUEUE_ERROR, MYF(0), ret); } DBUG_RETURN(ret); @@ -411,7 +405,7 @@ Events::show_create_event(THD *thd, sp_name *spn) DBUG_PRINT("enter", ("name: %*s", spn->m_name.length, spn->m_name.str)); thd->reset_n_backup_open_tables_state(&backup); - ret= db_repository->find_event(thd, spn, &et, NULL, thd->mem_root); + ret= db_repository->find_event(thd, spn->m_db, spn->m_name, &et, NULL, thd->mem_root); thd->restore_backup_open_tables_state(&backup); if (!ret) diff --git a/sql/events.h b/sql/events.h index 45a0db13980..1239cf58c7d 100644 --- a/sql/events.h +++ b/sql/events.h @@ -17,7 +17,6 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ class sp_name; -class Event_timed; class Event_parse_data; class Event_db_repository; @@ -45,7 +44,6 @@ public: Event_timed::drop() and Event_timed is fixed not do drop directly or other scheme will be found. */ - friend class Event_timed; static ulong opt_event_scheduler; static TYPELIB opt_typelib; @@ -66,12 +64,12 @@ public: get_instance(); int - create_event(THD *thd, Event_timed *et, Event_parse_data *parse_data, - uint create_options, uint *rows_affected); + create_event(THD *thd, Event_parse_data *parse_data, uint create_options, + uint *rows_affected); int - update_event(THD *thd, Event_timed *et, Event_parse_data *parse_data, - sp_name *new_name, uint *rows_affected); + update_event(THD *thd, Event_parse_data *parse_data, sp_name *new_name, + uint *rows_affected); int drop_event(THD *thd, sp_name *name, bool drop_if_exists, uint *rows_affected); diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index f6031a1f2fd..386dece8813 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -174,11 +174,11 @@ void lex_start(THD *thd, const uchar *buf, uint length) lex->sphead= NULL; lex->spcont= NULL; lex->proc_list.first= 0; - lex->escape_used= lex->et_compile_phase= FALSE; + lex->escape_used= FALSE; lex->reset_query_tables_list(FALSE); lex->name= 0; - lex->et= NULL; + lex->event_parse_data= NULL; lex->nest_level=0 ; lex->allow_sum_func= 0; diff --git a/sql/sql_lex.h b/sql/sql_lex.h index e2cf213cd17..0558d60bc28 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -27,7 +27,6 @@ class sp_instr; class sp_pcontext; class st_alter_tablespace; class partition_info; -class Event_timed; class Event_parse_data; #ifdef MYSQL_SERVER @@ -1017,9 +1016,7 @@ typedef struct st_lex : public Query_tables_list st_sp_chistics sp_chistics; - Event_timed *et; Event_parse_data *event_parse_data; - bool et_compile_phase; bool only_view; /* used for SHOW CREATE TABLE/VIEW */ /* diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 5ec4e7b3b68..80e5fff5a58 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -3833,57 +3833,31 @@ end_with_restore_list: case SQLCOM_ALTER_EVENT: { uint rows_affected= 1; - DBUG_ASSERT(lex->et); - do { - if (! lex->et->dbname.str || - (lex->sql_command == SQLCOM_ALTER_EVENT && lex->spname && - !lex->spname->m_db.str)) - { - my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR), MYF(0)); - res= true; - break; - } - - if (check_access(thd, EVENT_ACL, lex->et->dbname.str, 0, 0, 0, - is_schema_db(lex->et->dbname.str)) || - (lex->sql_command == SQLCOM_ALTER_EVENT && lex->spname && - (check_access(thd, EVENT_ACL, lex->spname->m_db.str, 0, 0, 0, - is_schema_db(lex->spname->m_db.str))))) - break; - - if (end_active_trans(thd)) - { - res= -1; - break; - } - - switch (lex->sql_command) { - case SQLCOM_CREATE_EVENT: - res= Events::get_instance()-> - create_event(thd, lex->et, lex->event_parse_data, - (uint) lex->create_info.options, &rows_affected); - break; - case SQLCOM_ALTER_EVENT: - res= Events::get_instance()-> - update_event(thd, lex->et, lex->event_parse_data, - lex->spname, &rows_affected); - break; - default:; - } - DBUG_PRINT("info", ("CREATE/ALTER/DROP returned error code=%d af_rows=%d", - res, rows_affected)); - if (!res) - send_ok(thd, rows_affected); + DBUG_ASSERT(lex->event_parse_data); + switch (lex->sql_command) { + case SQLCOM_CREATE_EVENT: + res= Events::get_instance()->create_event(thd, lex->event_parse_data, + (uint) lex->create_info.options, + &rows_affected); + break; + case SQLCOM_ALTER_EVENT: + res= Events::get_instance()->update_event(thd, lex->event_parse_data, + lex->spname, &rows_affected); + break; + default:; + } + DBUG_PRINT("info", ("CREATE/ALTER/DROP returned error code=%d af_rows=%d", + res, rows_affected)); + if (!res) + send_ok(thd, rows_affected); - /* lex->unit.cleanup() is called outside, no need to call it here */ - } while (0); if (!thd->spcont) { - lex->et->free_sphead_on_delete= true; - lex->et->free_sp(); - lex->et->deinit_mutexes(); + delete lex->sphead; + lex->sphead= NULL; } - + + /* lex->unit.cleanup() is called outside, no need to call it here */ break; } case SQLCOM_DROP_EVENT: @@ -3912,11 +3886,6 @@ end_with_restore_list: else { uint rows_affected= 1; - if (end_active_trans(thd)) - { - res= -1; - break; - } if (!(res= Events::get_instance()->drop_event(thd, lex->spname, lex->drop_if_exists, &rows_affected))) @@ -6020,14 +5989,6 @@ void mysql_parse(THD *thd, char *inBuf, uint length) { delete lex->sphead; lex->sphead= NULL; - if (lex->et) - { - lex->et->free_sphead_on_delete= true; - /* alloced on thd->mem_root so no real memory free but dtor call */ - lex->et->free_sp(); - lex->et->deinit_mutexes(); - lex->et= NULL; - } } else { @@ -6064,13 +6025,6 @@ void mysql_parse(THD *thd, char *inBuf, uint length) delete lex->sphead; lex->sphead= NULL; } - if (lex->et) - { - lex->et->free_sphead_on_delete= true; - lex->et->free_sp(); - lex->et->deinit_mutexes(); - lex->et= NULL; - } } thd->proc_info="freeing items"; thd->end_statement(); diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index bda51faeeb8..453e3634ef4 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1290,24 +1290,11 @@ event_tail: YYTHD->client_capabilities is set back to original value */ { - LEX *lex=Lex; - - if (lex->et) - { - /* - Recursive CREATE EVENT statement are not possible because - recursive SPs are not also possible. lex->sp_head is not stacked. - */ - my_error(ER_SP_NO_RECURSIVE_CREATE, MYF(0), "EVENT"); - YYABORT; - } - - lex->create_info.options= $2; + Lex->create_info.options= $2; - if (!(lex->et= new(YYTHD->mem_root) Event_timed())) // implicitly calls Event_timed::init() - YYABORT; - if (!(lex->event_parse_data= Event_parse_data::new_instance(YYTHD))) + if (!(Lex->event_parse_data= Event_parse_data::new_instance(YYTHD))) YYABORT; + Lex->event_parse_data->identifier= $3; /* We have to turn of CLIENT_MULTI_QUERIES while parsing a @@ -1316,15 +1303,6 @@ event_tail: */ $$= YYTHD->client_capabilities & CLIENT_MULTI_QUERIES; YYTHD->client_capabilities &= (~CLIENT_MULTI_QUERIES); - - - lex->event_parse_data->identifier= $3; - - if (!lex->et_compile_phase) - { - lex->et->init_name(YYTHD, $3); - lex->et->init_definer(YYTHD); - } } ON SCHEDULE_SYM ev_schedule_time opt_ev_on_completion @@ -1353,52 +1331,12 @@ ev_schedule_time: EVERY_SYM expr interval { Lex->event_parse_data->item_expression= $2; Lex->event_parse_data->interval= $3; - LEX *lex=Lex; - if (!lex->et_compile_phase) - { - switch (lex->et->init_interval(YYTHD , $2, $3)) { - case EVEX_PARSE_ERROR: - yyerror(ER(ER_SYNTAX_ERROR)); - YYABORT; - break; - case EVEX_BAD_PARAMS: - my_error(ER_EVENT_INTERVAL_NOT_POSITIVE_OR_TOO_BIG, MYF(0)); - case EVEX_MICROSECOND_UNSUP: - my_error(ER_NOT_SUPPORTED_YET, MYF(0), "MICROSECOND"); - YYABORT; - break; - } - } } ev_starts ev_ends | AT_SYM expr { Lex->event_parse_data->item_execute_at= $2; - LEX *lex=Lex; - if (!lex->et_compile_phase) - { - switch (lex->et->init_execute_at(YYTHD, $2)) { - case EVEX_PARSE_ERROR: - yyerror(ER(ER_SYNTAX_ERROR)); - YYABORT; - break; - case ER_WRONG_VALUE: - { - char buff[120]; - String str(buff,(uint32) sizeof(buff), system_charset_info); - String *str2= $2->val_str(&str); - my_error(ER_WRONG_VALUE, MYF(0), "AT", - str2? str2->c_ptr_safe():"NULL"); - YYABORT; - break; - } - case EVEX_BAD_PARAMS: - my_error(ER_EVENT_EXEC_TIME_IN_THE_PAST, MYF(0)); - YYABORT; - break; - } - } } ; @@ -1406,18 +1344,11 @@ opt_ev_status: /* empty */ { $$= 0; } | ENABLE_SYM { Lex->event_parse_data->status= Event_parse_data::ENABLED; - LEX *lex=Lex; - if (!lex->et_compile_phase) - lex->et->status= Event_timed::ENABLED; $$= 1; } | DISABLE_SYM { Lex->event_parse_data->status= Event_parse_data::DISABLED; - LEX *lex=Lex; - - if (!lex->et_compile_phase) - lex->et->status= Event_timed::DISABLED; $$= 1; } ; @@ -1425,32 +1356,10 @@ opt_ev_status: /* empty */ { $$= 0; } ev_starts: /* empty */ { Lex->event_parse_data->item_starts= new Item_func_now_local(); - Lex->et->init_starts(YYTHD, new Item_func_now_local()); } | STARTS_SYM expr { Lex->event_parse_data->item_starts= $2; - LEX *lex= Lex; - if (!lex->et_compile_phase) - { - - switch (lex->et->init_starts(YYTHD, $2)) { - case EVEX_PARSE_ERROR: - yyerror(ER(ER_SYNTAX_ERROR)); - YYABORT; - break; - case EVEX_BAD_PARAMS: - { - char buff[20]; - String str(buff,(uint32) sizeof(buff), system_charset_info); - String *str2= $2->val_str(&str); - my_error(ER_WRONG_VALUE, MYF(0), "STARTS", - str2 ? str2->c_ptr_safe() : NULL); - YYABORT; - break; - } - } - } } ; @@ -1458,20 +1367,6 @@ ev_ends: /* empty */ | ENDS_SYM expr { Lex->event_parse_data->item_ends= $2; - LEX *lex= Lex; - if (!lex->et_compile_phase) - { - switch (lex->et->init_ends(YYTHD, $2)) { - case EVEX_PARSE_ERROR: - yyerror(ER(ER_SYNTAX_ERROR)); - YYABORT; - break; - case EVEX_BAD_PARAMS: - my_error(ER_EVENT_ENDS_BEFORE_STARTS, MYF(0)); - YYABORT; - break; - } - } } ; @@ -1484,18 +1379,12 @@ ev_on_completion: { Lex->event_parse_data->on_completion= Event_parse_data::ON_COMPLETION_PRESERVE; - LEX *lex=Lex; - if (!lex->et_compile_phase) - lex->et->on_completion= Event_timed::ON_COMPLETION_PRESERVE; $$= 1; } | ON COMPLETION_SYM NOT_SYM PRESERVE_SYM { Lex->event_parse_data->on_completion= Event_parse_data::ON_COMPLETION_DROP; - LEX *lex=Lex; - if (!lex->et_compile_phase) - lex->et->on_completion= Event_timed::ON_COMPLETION_DROP; $$= 1; } ; @@ -1504,20 +1393,12 @@ opt_ev_comment: /* empty */ { $$= 0; } | COMMENT_SYM TEXT_STRING_sys { Lex->comment= Lex->event_parse_data->comment= $2; - LEX *lex= Lex; - if (!lex->et_compile_phase) - { - lex->comment= $2; - lex->et->init_comment(YYTHD, &$2); - } - $$= 1; } ; ev_sql_stmt: { LEX *lex= Lex; - sp_head *sp; /* This stops the following : @@ -1557,8 +1438,6 @@ ev_sql_stmt: Lex->event_parse_data->body_begin= lex->ptr; - if (!lex->et_compile_phase) - lex->et->body_begin= lex->ptr; } ev_sql_stmt_inner { @@ -1570,14 +1449,7 @@ ev_sql_stmt: lex->sp_chistics.suid= SP_IS_SUID;//always the definer! - lex->et->sphead= lex->sphead; - lex->sphead= NULL; - Lex->event_parse_data->init_body(YYTHD); - if (!lex->et_compile_phase) - { - lex->et->init_body(YYTHD); - } } ; @@ -4753,25 +4625,12 @@ alter: YYTHD->client_capabilities is set back to original value */ { - LEX *lex=Lex; - Event_timed *et; - - lex->spname= NULL; + Lex->spname= NULL; if (!(Lex->event_parse_data= Event_parse_data::new_instance(YYTHD))) YYABORT; Lex->event_parse_data->identifier= $3; - if (!(et= new (YYTHD->mem_root) Event_timed()))// implicitly calls Event_timed::init() - YYABORT; - lex->et = et; - - if (!lex->et_compile_phase) - { - et->init_definer(YYTHD); - et->init_name(YYTHD, $3); - } - /* We have to turn of CLIENT_MULTI_QUERIES while parsing a stored procedure, otherwise yylex will chop it into pieces @@ -4838,7 +4697,7 @@ opt_ev_rename_to: /* empty */ { $$= 0;} { LEX *lex=Lex; lex->spname= $3; //use lex's spname to hold the new name - //the original name is in the Event_timed object + //the original name is in the Event_parse_data object $$= 1; } ; -- cgit v1.2.1 From 8d961c45e2e83f04da92cbfc31d2975a6949743f Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 29 Jun 2006 11:53:51 +0200 Subject: fix for bug#16394 "Events: Crash if schedule contains SELECT" Parsing of CREATE/ALTER EVENT statement was crashing because of early initialization done during parsing, instead in the after parsing phase. Moreover, we don't want SUBqueries in CREATE/ALTER EVENT therefore we disable them, though it is possible to make them work. It can be emulated inside SP with a cursor and SP variable (CREATE/ALTER EVENT can still accept variables as values). mysql-test/r/events_bugs.result: update result mysql-test/t/events_bugs.test: tests for bug#16384 sql/sql_yacc.yy: disallow subqueries when SQLCOM_CREATE_EVENT | SQLCOM_ALTER_EVENT The fix is not big, though lex->forbid_subqueries could have been introduced. Easier is just to set the sql_command and check in both rules where subqueries enter. --- mysql-test/r/events_bugs.result | 8 ++++++++ mysql-test/t/events_bugs.test | 15 +++++++++++++++ sql/sql_yacc.yy | 26 ++++++++++++++++++-------- 3 files changed, 41 insertions(+), 8 deletions(-) diff --git a/mysql-test/r/events_bugs.result b/mysql-test/r/events_bugs.result index e1f8551c2fd..afd2a439fac 100644 --- a/mysql-test/r/events_bugs.result +++ b/mysql-test/r/events_bugs.result @@ -190,4 +190,12 @@ events_test mysqltest_user1 mysqltest_user1@localhost RECURRING ENABLED drop event events_test.mysqltest_user1; drop user mysqltest_user1@localhost; drop database mysqltest_db1; +create event e_53 on schedule at (select s1 from ttx) do drop table t; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'select s1 from ttx) do drop table t' at line 1 +create event e_53 on schedule every (select s1 from ttx) second do drop table t; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'select s1 from ttx) second do drop table t' at line 1 +create event e_53 on schedule every 5 second starts (select s1 from ttx) do drop table t; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'select s1 from ttx) do drop table t' at line 1 +create event e_53 on schedule every 5 second ends (select s1 from ttx) do drop table t; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'select s1 from ttx) do drop table t' at line 1 drop database events_test; diff --git a/mysql-test/t/events_bugs.test b/mysql-test/t/events_bugs.test index e07707e7734..4ff114555ca 100644 --- a/mysql-test/t/events_bugs.test +++ b/mysql-test/t/events_bugs.test @@ -198,4 +198,19 @@ drop database mysqltest_db1; # END - 18897: Events: unauthorized action possible with alter event rename # +# +# START - BUG#16394: Events: Crash if schedule contains SELECT +# +--error ER_PARSE_ERROR +create event e_53 on schedule at (select s1 from ttx) do drop table t; +--error ER_PARSE_ERROR +create event e_53 on schedule every (select s1 from ttx) second do drop table t; +--error ER_PARSE_ERROR +create event e_53 on schedule every 5 second starts (select s1 from ttx) do drop table t; +--error ER_PARSE_ERROR +create event e_53 on schedule every 5 second ends (select s1 from ttx) do drop table t; +# +# END - BUG#16394: Events: Crash if schedule contains SELECT +# + drop database events_test; diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 453e3634ef4..64049c762dd 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1303,6 +1303,9 @@ event_tail: */ $$= YYTHD->client_capabilities & CLIENT_MULTI_QUERIES; YYTHD->client_capabilities &= (~CLIENT_MULTI_QUERIES); + + /* We need that for disallowing subqueries */ + Lex->sql_command= SQLCOM_CREATE_EVENT; } ON SCHEDULE_SYM ev_schedule_time opt_ev_on_completion @@ -4638,6 +4641,9 @@ alter: */ $$= YYTHD->client_capabilities & CLIENT_MULTI_QUERIES; YYTHD->client_capabilities &= ~CLIENT_MULTI_QUERIES; + + /* we need that for disallowing subqueries */ + Lex->sql_command= SQLCOM_ALTER_EVENT; } ev_alter_on_schedule_completion opt_ev_rename_to @@ -4653,15 +4659,15 @@ alter: */ YYTHD->client_capabilities |= $4; - /* - sql_command is set here because some rules in ev_sql_stmt - can overwrite it - */ if (!($5 || $6 || $7 || $8 || $9)) { yyerror(ER(ER_SYNTAX_ERROR)); YYABORT; } + /* + sql_command is set here because some rules in ev_sql_stmt + can overwrite it + */ Lex->sql_command= SQLCOM_ALTER_EVENT; } | ALTER TABLESPACE alter_tablespace_info @@ -6959,8 +6965,10 @@ select_derived2: { LEX *lex= Lex; lex->derived_tables|= DERIVED_SUBQUERY; - if (lex->sql_command == (int)SQLCOM_HA_READ || - lex->sql_command == (int)SQLCOM_KILL) + if (lex->sql_command == SQLCOM_HA_READ || + lex->sql_command == SQLCOM_KILL || + lex->sql_command == SQLCOM_CREATE_EVENT || + lex->sql_command == SQLCOM_ALTER_EVENT) { yyerror(ER(ER_SYNTAX_ERROR)); YYABORT; @@ -10592,8 +10600,10 @@ subselect_start: '(' SELECT_SYM { LEX *lex=Lex; - if (lex->sql_command == (int)SQLCOM_HA_READ || - lex->sql_command == (int)SQLCOM_KILL) + if (lex->sql_command == SQLCOM_HA_READ || + lex->sql_command == SQLCOM_KILL || + lex->sql_command == SQLCOM_CREATE_EVENT || + lex->sql_command == SQLCOM_ALTER_EVENT) { yyerror(ER(ER_SYNTAX_ERROR)); YYABORT; -- cgit v1.2.1 From 377446fa3497ffbc0f2a17614d848bfb79f52662 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 3 Jul 2006 11:20:08 +0200 Subject: WL#3337 (Event scheduler new architecture) This is the first cut of separating Event_scheduler in two classes which are more specialized. Inheritance was used to separate methods and member variables. Still Event_scheduler is a child of Event_queue. This dependency will be removed soon. sql/event_data_objects.cc: add comments sql/event_db_repository.cc: coding style sql/event_db_repository.h: add a call, will be implemented later sql/event_queue.cc: Event_queue, still as super-class of Event_scheduler sql/event_queue.h: Event_queue as super-class of Event_scheduler. Trying to separate the two classes sql/event_scheduler.cc: Event_scheduler as child class of Event_queue. Trying to separate both classes. sql/event_scheduler.h: Event_scheduler as child class of Event_queue. Trying to separate both classes. sql/events.cc: Don't allocate on the stack the scheduler but on the heap. The exact way it is done will be changed, that's ok for now. --- sql/event_data_objects.cc | 20 + sql/event_db_repository.cc | 1 + sql/event_db_repository.h | 3 + sql/event_queue.cc | 841 ++++++++++++++++++++++++++++++++++++ sql/event_queue.h | 104 ++++- sql/event_scheduler.cc | 1006 ++++++++------------------------------------ sql/event_scheduler.h | 154 +++---- sql/events.cc | 1 + 8 files changed, 1182 insertions(+), 948 deletions(-) diff --git a/sql/event_data_objects.cc b/sql/event_data_objects.cc index a47a3e9e936..f4147d72c3d 100644 --- a/sql/event_data_objects.cc +++ b/sql/event_data_objects.cc @@ -25,6 +25,19 @@ #define EVEX_MAX_INTERVAL_VALUE 2147483647L +/* + Returns a new instance + + SYNOPSIS + Event_parse_data::new_instance() + + RETURN VALUE + Address or NULL in case of error + + NOTE + Created on THD's mem_root +*/ + Event_parse_data * Event_parse_data::new_instance(THD *thd) { @@ -32,6 +45,13 @@ Event_parse_data::new_instance(THD *thd) } +/* + Constructor + + SYNOPSIS + Event_parse_data::Event_parse_data() +*/ + Event_parse_data::Event_parse_data() { item_execute_at= item_expression= item_starts= item_ends= NULL; diff --git a/sql/event_db_repository.cc b/sql/event_db_repository.cc index 68d8234cc90..8886992c839 100644 --- a/sql/event_db_repository.cc +++ b/sql/event_db_repository.cc @@ -1297,3 +1297,4 @@ Event_db_repository::load_named_event(THD *thd, LEX_STRING dbname, LEX_STRING na DBUG_RETURN(OP_OK); } + diff --git a/sql/event_db_repository.h b/sql/event_db_repository.h index 625d5bfb993..e1c64c8aded 100644 --- a/sql/event_db_repository.h +++ b/sql/event_db_repository.h @@ -113,6 +113,9 @@ private: int table_scan_all_for_i_s(THD *thd, TABLE *schema_table, TABLE *event_table); + static bool + check_system_tables(THD *thd); + MEM_ROOT repo_root; /* Prevent use of these */ diff --git a/sql/event_queue.cc b/sql/event_queue.cc index 46f965678c6..32c5a076a62 100644 --- a/sql/event_queue.cc +++ b/sql/event_queue.cc @@ -15,5 +15,846 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "mysql_priv.h" +#include "events.h" +#include "event_scheduler.h" #include "event_queue.h" #include "event_data_objects.h" +#include "event_db_repository.h" +#include "sp_head.h" + + +#ifdef __GNUC__ +#if __GNUC__ >= 2 +#define SCHED_FUNC __FUNCTION__ +#endif +#else +#define SCHED_FUNC "" +#endif + +#define LOCK_QUEUE_DATA() lock_data(SCHED_FUNC, __LINE__) +#define UNLOCK_QUEUE_DATA() unlock_data(SCHED_FUNC, __LINE__) + + +Event_scheduler* +Event_queue::singleton= NULL; + + +/* + Compares the execute_at members of 2 Event_timed instances. + Used as callback for the prioritized queue when shifting + elements inside. + + SYNOPSIS + event_timed_compare_q() + + vptr - not used (set it to NULL) + a - first Event_timed object + b - second Event_timed object + + RETURN VALUE + -1 - a->execute_at < b->execute_at + 0 - a->execute_at == b->execute_at + 1 - a->execute_at > b->execute_at + + NOTES + execute_at.second_part is not considered during comparison +*/ + +static int +event_timed_compare_q(void *vptr, byte* a, byte *b) +{ + return my_time_compare(&((Event_timed *)a)->execute_at, + &((Event_timed *)b)->execute_at); +} + + + +/* + Constructor of class Event_queue. + + SYNOPSIS + Event_queue::Event_queue() +*/ + +Event_queue::Event_queue() +{ + mutex_last_unlocked_at_line= mutex_last_locked_at_line= 0; + mutex_last_unlocked_in_func= mutex_last_locked_in_func= ""; + mutex_queue_data_locked= FALSE; +} + +/* + Creates an event in the scheduler queue + + SYNOPSIS + Event_queue::create_event() + et The event to add + check_existence Whether to check if already loaded. + + RETURN VALUE + OP_OK OK or scheduler not working + OP_LOAD_ERROR Error during loading from disk +*/ + +int +Event_queue::create_event(THD *thd, Event_parse_data *et, bool check_existence) +{ + int res; + Event_timed *et_new; + DBUG_ENTER("Event_queue::create_event"); + DBUG_PRINT("enter", ("thd=%p et=%p lock=%p",thd,et, &LOCK_event_queue)); + + LOCK_QUEUE_DATA(); + if (check_existence && find_event(et->dbname, et->name, FALSE)) + { + res= OP_ALREADY_EXISTS; + goto end; + } + + /* We need to load the event on scheduler_root */ + if (!(res= db_repository-> + load_named_event(thd, et->dbname, et->name, &et_new))) + { + queue_insert_safe(&queue, (byte *) et_new); + on_queue_change(); + } + else if (res == OP_DISABLED_EVENT) + res= OP_OK; +end: + UNLOCK_QUEUE_DATA(); + DBUG_RETURN(res); +} + + +/* + Updates an event from the scheduler queue + + SYNOPSIS + Event_scheduler::update_event() + thd Thread + et The event to replace(add) into the queue + new_schema New schema + new_name New name + + RETURN VALUE + OP_OK OK or scheduler not working + OP_LOAD_ERROR Error during loading from disk + OP_ALREADY_EXISTS Event already in the queue +*/ + +int +Event_queue::update_event(THD *thd, Event_parse_data *et, + LEX_STRING *new_schema, + LEX_STRING *new_name) +{ + int res= OP_OK; + Event_timed *et_old, *et_new= NULL; + LEX_STRING old_schema, old_name; + + LINT_INIT(old_schema.str); + LINT_INIT(old_schema.length); + LINT_INIT(old_name.str); + LINT_INIT(old_name.length); + + DBUG_ENTER("Event_queue::update_event"); + DBUG_PRINT("enter", ("thd=%p et=%p et=[%s.%s] lock=%p", + thd, et, et->dbname.str, et->name.str, &LOCK_event_queue)); + + LOCK_QUEUE_DATA(); + if (!(et_old= find_event(et->dbname, et->name, TRUE))) + DBUG_PRINT("info", ("%s.%s not found cached, probably was DISABLED", + et->dbname.str, et->name.str)); + + if (new_schema && new_name) + { + old_schema= et->dbname; + old_name= et->name; + et->dbname= *new_schema; + et->name= *new_name; + } + /* + We need to load the event (it's strings but on the object itself) + on scheduler_root. et_new could be NULL : + 1. Error occured + 2. If the replace is DISABLED, we don't load it into the queue. + */ + if (!(res= db_repository-> + load_named_event(thd, et->dbname, et->name, &et_new))) + { + queue_insert_safe(&queue, (byte *) et_new); + on_queue_change(); + } + else if (res == OP_DISABLED_EVENT) + res= OP_OK; + + if (new_schema && new_name) + { + et->dbname= old_schema; + et->name= old_name; + } + DBUG_PRINT("info", ("res=%d", res)); + UNLOCK_QUEUE_DATA(); + /* + Andrey: Is this comment still truthful ??? + + We don't move this code above because a potential kill_thread will call + THD::awake(). Which in turn will try to acqure mysys_var->current_mutex, + which is LOCK_event_queue on which the COND_new_work in ::run() locks. + Hence, we try to acquire a lock which we have already acquired and we run + into an assert. Holding LOCK_event_queue however is not needed because + we don't touch any invariant of the scheduler anymore. ::drop_event() does + the same. + */ + if (et_old) + { + switch (et_old->kill_thread(thd)) { + case EVEX_CANT_KILL: + /* Don't delete but continue */ + et_old->flags |= EVENT_FREE_WHEN_FINISHED; + break; + case 0: + /* + kill_thread() waits till the spawned thread finishes after it's + killed. Hence, we delete here memory which is no more referenced from + a running thread. + */ + delete et_old; + /* + We don't signal COND_new_work here because: + 1. Even if the dropped event is on top of the queue this will not + move another one to be executed before the time the one on the + top (but could be at the same second as the dropped one) + 2. If this was the last event on the queue, then pthread_cond_timedwait + in ::run() will finish and then see that the queue is empty and + call cond_wait(). Hence, no need to interrupt the blocked + ::run() thread. + */ + break; + default: + DBUG_ASSERT(0); + } + } + + DBUG_RETURN(res); +} + + +/* + Drops an event from the scheduler queue + + SYNOPSIS + Event_queue::drop_event() + thd Thread + name The event to drop + + RETURN VALUE + FALSE OK (replaced or scheduler not working) + TRUE Failure +*/ + +bool +Event_queue::drop_event(THD *thd, sp_name *name) +{ + int res; + Event_timed *et_old; + DBUG_ENTER("Event_queue::drop_event"); + DBUG_PRINT("enter", ("thd=%p name=%p lock=%p", thd, name, + &LOCK_event_queue)); + + LOCK_QUEUE_DATA(); + if (!(et_old= find_event(name->m_db, name->m_name, TRUE))) + DBUG_PRINT("info", ("No such event found, probably DISABLED")); + + UNLOCK_QUEUE_DATA(); + + /* See comments in ::replace_event() why this is split in two parts. */ + if (et_old) + { + switch ((res= et_old->kill_thread(thd))) { + case EVEX_CANT_KILL: + /* Don't delete but continue */ + et_old->flags |= EVENT_FREE_WHEN_FINISHED; + break; + case 0: + /* + kill_thread() waits till the spawned thread finishes after it's + killed. Hence, we delete here memory which is no more referenced from + a running thread. + */ + delete et_old; + /* + We don't signal COND_new_work here because: + 1. Even if the dropped event is on top of the queue this will not + move another one to be executed before the time the one on the + top (but could be at the same second as the dropped one) + 2. If this was the last event on the queue, then pthread_cond_timedwait + in ::run() will finish and then see that the queue is empty and + call cond_wait(). Hence, no need to interrupt the blocked + ::run() thread. + */ + break; + default: + sql_print_error("SCHEDULER: Got unexpected error %d", res); + DBUG_ASSERT(0); + } + } + + DBUG_RETURN(FALSE); +} + + + + +/* + Searches for an event in the scheduler queue + + SYNOPSIS + Event_queue::find_event() + db The schema of the event to find + name The event to find + remove_from_q If found whether to remove from the Q + + RETURN VALUE + NULL Not found + otherwise Address + + NOTE + The caller should do the locking also the caller is responsible for + actual signalling in case an event is removed from the queue + (signalling COND_new_work for instance). +*/ + +Event_timed * +Event_queue::find_event(LEX_STRING db, LEX_STRING name, bool remove_from_q) +{ + uint i; + DBUG_ENTER("Event_queue::find_event"); + + for (i= 0; i < queue.elements; ++i) + { + Event_timed *et= (Event_timed *) queue_element(&queue, i); + DBUG_PRINT("info", ("[%s.%s]==[%s.%s]?", db.str, name.str, + et->dbname.str, et->name.str)); + if (event_timed_identifier_equal(db, name, et)) + { + if (remove_from_q) + queue_remove(&queue, i); + DBUG_RETURN(et); + } + } + + DBUG_RETURN(NULL); +} + + +/* + Drops all events from the in-memory queue and disk that match + certain pattern evaluated by a comparator function + + SYNOPSIS + Event_queue::drop_matching_events() + thd THD + pattern A pattern string + comparator The function to use for comparing + + RETURN VALUE + -1 Scheduler not working + >=0 Number of dropped events + + NOTE + Expected is the caller to acquire lock on LOCK_event_queue +*/ + +void +Event_queue::drop_matching_events(THD *thd, LEX_STRING pattern, + bool (*comparator)(Event_timed *,LEX_STRING *)) +{ + DBUG_ENTER("Event_queue::drop_matching_events"); + DBUG_PRINT("enter", ("pattern=%*s state=%d", pattern.length, pattern.str)); + + uint i= 0, dropped= 0; + while (i < queue.elements) + { + Event_timed *et= (Event_timed *) queue_element(&queue, i); + DBUG_PRINT("info", ("[%s.%s]?", et->dbname.str, et->name.str)); + if (comparator(et, &pattern)) + { + /* + The queue is ordered. If we remove an element, then all elements after + it will shift one position to the left, if we imagine it as an array + from left to the right. In this case we should not increment the + counter and the (i < queue.elements) condition is ok. + */ + queue_remove(&queue, i); + + /* See replace_event() */ + switch (et->kill_thread(thd)) { + case EVEX_CANT_KILL: + /* Don't delete but continue */ + et->flags |= EVENT_FREE_WHEN_FINISHED; + ++dropped; + break; + case 0: + delete et; + ++dropped; + break; + default: + DBUG_ASSERT(0); + } + } + else + i++; + } + DBUG_PRINT("info", ("Dropped %lu", dropped)); + /* + Don't send COND_new_work because no need to wake up the scheduler thread. + When it wakes next time up it will recalculate how much more it should + sleep if the top of the queue has been changed by this method. + */ + + DBUG_VOID_RETURN; +} + + +/* + Drops all events from the in-memory queue and disk that are from + certain schema. + + SYNOPSIS + Event_queue::drop_schema_events() + thd THD + db The schema name + + RETURN VALUE + -1 Scheduler not working + >=0 Number of dropped events +*/ + +int +Event_queue::drop_schema_events(THD *thd, LEX_STRING schema) +{ + int ret; + DBUG_ENTER("Event_queue::drop_schema_events"); + LOCK_QUEUE_DATA(); + drop_matching_events(thd, schema, event_timed_db_equal); + UNLOCK_QUEUE_DATA(); + + DBUG_RETURN(ret); +} + + +/* + Wrapper for pthread_mutex_lock + + SYNOPSIS + Event_queue::lock_data() + mutex Mutex to lock + line The line number on which the lock is done + + RETURN VALUE + Error code of pthread_mutex_lock() +*/ + +void +Event_queue::lock_data(const char *func, uint line) +{ + DBUG_ENTER("Event_queue::lock_mutex"); + DBUG_PRINT("enter", ("mutex_lock=%p func=%s line=%u", + &LOCK_event_queue, func, line)); + pthread_mutex_lock(&LOCK_event_queue); + mutex_last_locked_in_func= func; + mutex_last_locked_at_line= line; + mutex_queue_data_locked= TRUE; + DBUG_VOID_RETURN; +} + + +/* + Wrapper for pthread_mutex_unlock + + SYNOPSIS + Event_queue::unlock_data() + mutex Mutex to unlock + line The line number on which the unlock is done +*/ + +void +Event_queue::unlock_data(const char *func, uint line) +{ + DBUG_ENTER("Event_queue::UNLOCK_mutex"); + DBUG_PRINT("enter", ("mutex_unlock=%p func=%s line=%u", + &LOCK_event_queue, func, line)); + mutex_last_unlocked_at_line= line; + mutex_queue_data_locked= FALSE; + mutex_last_unlocked_in_func= func; + pthread_mutex_unlock(&LOCK_event_queue); + DBUG_VOID_RETURN; +} + + +/* + Returns the number of elements in the queue + + SYNOPSIS + Event_queue::events_count() + + RETURN VALUE + 0 Number of Event_timed objects in the queue +*/ + +uint +Event_queue::events_count() +{ + uint n; + DBUG_ENTER("Event_scheduler::events_count"); + LOCK_QUEUE_DATA(); + n= queue.elements; + UNLOCK_QUEUE_DATA(); + + DBUG_RETURN(n); +} + + +/* + Returns the number of elements in the queue + + SYNOPSIS + Event_queue::events_count_no_lock() + + RETURN VALUE + 0 Number of Event_timed objects in the queue +*/ + +uint +Event_queue::events_count_no_lock() +{ + uint n; + DBUG_ENTER("Event_scheduler::events_count_no_lock"); + + n= queue.elements; + + DBUG_RETURN(n); +} + + +/* + Loads all ENABLED events from mysql.event into the prioritized + queue. Called during scheduler main thread initialization. Compiles + the events. Creates Event_timed instances for every ENABLED event + from mysql.event. + + SYNOPSIS + Event_queue::load_events_from_db() + thd - Thread context. Used for memory allocation in some cases. + + RETURN VALUE + 0 OK + !0 Error (EVEX_OPEN_TABLE_FAILED, EVEX_MICROSECOND_UNSUP, + EVEX_COMPILE_ERROR) - in all these cases mysql.event was + tampered. + + NOTES + Reports the error to the console +*/ + +int +Event_queue::load_events_from_db(THD *thd) +{ + TABLE *table; + READ_RECORD read_record_info; + int ret= -1; + uint count= 0; + bool clean_the_queue= FALSE; + /* Compile the events on this root but only for syntax check, then discard */ + MEM_ROOT boot_root; + + DBUG_ENTER("Event_queue::load_events_from_db"); + DBUG_PRINT("enter", ("thd=%p", thd)); + + if ((ret= db_repository->open_event_table(thd, TL_READ, &table))) + { + sql_print_error("SCHEDULER: Table mysql.event is damaged. Can not open."); + DBUG_RETURN(EVEX_OPEN_TABLE_FAILED); + } + + init_alloc_root(&boot_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC); + init_read_record(&read_record_info, thd, table ,NULL,1,0); + while (!(read_record_info.read_record(&read_record_info))) + { + Event_timed *et; + if (!(et= new Event_timed)) + { + DBUG_PRINT("info", ("Out of memory")); + clean_the_queue= TRUE; + break; + } + DBUG_PRINT("info", ("Loading event from row.")); + + if ((ret= et->load_from_row(&scheduler_root, table))) + { + clean_the_queue= TRUE; + sql_print_error("SCHEDULER: Error while loading from mysql.event. " + "Table probably corrupted"); + break; + } + if (et->status != Event_timed::ENABLED) + { + DBUG_PRINT("info",("%s is disabled",et->name.str)); + delete et; + continue; + } + + DBUG_PRINT("info", ("Event %s loaded from row. ", et->name.str)); + + /* We load only on scheduler root just to check whether the body compiles */ + switch (ret= et->compile(thd, &boot_root)) { + case EVEX_MICROSECOND_UNSUP: + et->free_sp(); + sql_print_error("SCHEDULER: mysql.event is tampered. MICROSECOND is not " + "supported but found in mysql.event"); + goto end; + case EVEX_COMPILE_ERROR: + sql_print_error("SCHEDULER: Error while compiling %s.%s. Aborting load.", + et->dbname.str, et->name.str); + goto end; + default: + /* Free it, it will be compiled again on the worker thread */ + et->free_sp(); + break; + } + + /* let's find when to be executed */ + if (et->compute_next_execution_time()) + { + sql_print_error("SCHEDULER: Error while computing execution time of %s.%s." + " Skipping", et->dbname.str, et->name.str); + continue; + } + + DBUG_PRINT("load_events_from_db", ("Adding %p to the exec list.")); + queue_insert_safe(&queue, (byte *) et); + count++; + } +end: + end_read_record(&read_record_info); + free_root(&boot_root, MYF(0)); + + if (clean_the_queue) + { + for (count= 0; count < queue.elements; ++count) + queue_remove(&queue, 0); + ret= -1; + } + else + { + ret= 0; + sql_print_information("SCHEDULER: Loaded %d event%s", count, (count == 1)?"":"s"); + } + + /* Force close to free memory */ + thd->version--; + + close_thread_tables(thd); + + DBUG_PRINT("info", ("Status code %d. Loaded %d event(s)", ret, count)); + DBUG_RETURN(ret); +} + + +/* + Opens mysql.db and mysql.user and checks whether: + 1. mysql.db has column Event_priv at column 20 (0 based); + 2. mysql.user has column Event_priv at column 29 (0 based); + + SYNOPSIS + Event_queue::check_system_tables() +*/ + +bool +Event_queue::check_system_tables(THD *thd) +{ + TABLE_LIST tables; + bool not_used; + Open_tables_state backup; + bool ret; + + DBUG_ENTER("Event_queue::check_system_tables"); + DBUG_PRINT("enter", ("thd=%p", thd)); + + thd->reset_n_backup_open_tables_state(&backup); + + bzero((char*) &tables, sizeof(tables)); + tables.db= (char*) "mysql"; + tables.table_name= tables.alias= (char*) "db"; + tables.lock_type= TL_READ; + + if ((ret= simple_open_n_lock_tables(thd, &tables))) + sql_print_error("Cannot open mysql.db"); + else + { + ret= table_check_intact(tables.table, MYSQL_DB_FIELD_COUNT, + mysql_db_table_fields, &mysql_db_table_last_check, + ER_CANNOT_LOAD_FROM_TABLE); + close_thread_tables(thd); + } + if (ret) + DBUG_RETURN(TRUE); + + bzero((char*) &tables, sizeof(tables)); + tables.db= (char*) "mysql"; + tables.table_name= tables.alias= (char*) "user"; + tables.lock_type= TL_READ; + + if ((ret= simple_open_n_lock_tables(thd, &tables))) + sql_print_error("Cannot open mysql.db"); + else + { + if (tables.table->s->fields < 29 || + strncmp(tables.table->field[29]->field_name, + STRING_WITH_LEN("Event_priv"))) + { + sql_print_error("mysql.user has no `Event_priv` column at position 29"); + ret= TRUE; + } + close_thread_tables(thd); + } + + thd->restore_backup_open_tables_state(&backup); + + DBUG_RETURN(ret); +} + + +/* + Inits mutexes. + + SYNOPSIS + Event_queue::init_mutexes() +*/ + +void +Event_queue::init_mutexes() +{ + pthread_mutex_init(&singleton->LOCK_event_queue, MY_MUTEX_INIT_FAST); +} + + +/* + Destroys mutexes. + + SYNOPSIS + Event_queue::destroy_mutexes() +*/ + +void +Event_queue::destroy_mutexes() +{ + pthread_mutex_destroy(&singleton->LOCK_event_queue); +} + + +/* + Signals the main scheduler thread that the queue has changed + its state. + + SYNOPSIS + Event_queue::on_queue_change() +*/ + +void +Event_queue::on_queue_change() +{ + DBUG_ENTER("Event_queue::on_queue_change"); + DBUG_PRINT("info", ("Sending COND_new_work")); + singleton->queue_changed(); + DBUG_VOID_RETURN; +} + + +/* + The implementation of full-fledged initialization. + + SYNOPSIS + Event_scheduler::init() + + RETURN VALUE + FALSE OK + TRUE Error +*/ + +bool +Event_queue::init(Event_db_repository *db_repo) +{ + int i= 0; + bool ret= FALSE; + DBUG_ENTER("Event_scheduler::init"); + DBUG_PRINT("enter", ("this=%p", this)); + + LOCK_QUEUE_DATA(); + db_repository= db_repo; + /* init memory root */ + init_alloc_root(&scheduler_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC); + + if (init_queue_ex(&queue, 30 /*num_el*/, 0 /*offset*/, 0 /*smallest_on_top*/, + event_timed_compare_q, NULL, 30 /*auto_extent*/)) + { + sql_print_error("SCHEDULER: Can't initialize the execution queue"); + ret= TRUE; + goto end; + } + + if (sizeof(my_time_t) != sizeof(time_t)) + { + sql_print_error("SCHEDULER: sizeof(my_time_t) != sizeof(time_t) ." + "The scheduler may not work correctly. Stopping."); + DBUG_ASSERT(0); + ret= TRUE; + goto end; + } + +end: + UNLOCK_QUEUE_DATA(); + DBUG_RETURN(ret); +} + + +void +Event_queue::deinit() +{ + DBUG_ENTER("Event_queue::deinit"); + + LOCK_QUEUE_DATA(); + delete_queue(&queue); + free_root(&scheduler_root, MYF(0)); + UNLOCK_QUEUE_DATA(); + + DBUG_VOID_RETURN; +} + + +void +Event_queue::recalculate_queue(THD *thd) +{ + int i; + for (i= 0; i < queue.elements; i++) + { + ((Event_timed*)queue_element(&queue, i))->compute_next_execution_time(); + ((Event_timed*)queue_element(&queue, i))->update_fields(thd); + } + queue_fix(&queue); +} + + +void +Event_queue::empty_queue() +{ + int i; + /* empty the queue */ + for (i= 0; i < events_count_no_lock(); ++i) + { + Event_timed *et= (Event_timed *) queue_element(&queue, i); + et->free_sp(); + delete et; + } + resize_queue(&queue, 0); +} diff --git a/sql/event_queue.h b/sql/event_queue.h index b3aa6133840..8c11d7a2042 100644 --- a/sql/event_queue.h +++ b/sql/event_queue.h @@ -16,5 +16,107 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - +class sp_name; +class Event_timed; +class Event_db_repository; + +class THD; +typedef bool * (*event_timed_identifier_comparator)(Event_timed*, Event_timed*); + +class Event_scheduler; + +class Event_queue +{ +public: + Event_queue(); + + static void + init_mutexes(); + + static void + destroy_mutexes(); + + bool + init(Event_db_repository *db_repo); + + void + deinit(); + + /* Methods for queue management follow */ + + int + create_event(THD *thd, Event_parse_data *et, bool check_existence); + + int + update_event(THD *thd, Event_parse_data *et, LEX_STRING *new_schema, + LEX_STRING *new_name); + + bool + drop_event(THD *thd, sp_name *name); + + int + drop_schema_events(THD *thd, LEX_STRING schema); + + int + drop_user_events(THD *thd, LEX_STRING *definer) + { DBUG_ASSERT(0); return 0;} + + uint + events_count(); + + uint + events_count_no_lock(); + + static bool + check_system_tables(THD *thd); + + void + recalculate_queue(THD *thd); + + void + empty_queue(); + +///////////////protected + Event_timed * + find_event(LEX_STRING db, LEX_STRING name, bool remove_from_q); + + int + load_events_from_db(THD *thd); + + void + drop_matching_events(THD *thd, LEX_STRING pattern, + bool (*)(Event_timed *,LEX_STRING *)); + + /* LOCK_event_queue is the mutex which protects the access to the queue. */ + pthread_mutex_t LOCK_event_queue; + + Event_db_repository *db_repository; + + /* The MEM_ROOT of the object */ + MEM_ROOT scheduler_root; + + /* The sorted queue with the Event_timed objects */ + QUEUE queue; + + uint mutex_last_locked_at_line; + uint mutex_last_unlocked_at_line; + const char* mutex_last_locked_in_func; + const char* mutex_last_unlocked_in_func; + bool mutex_queue_data_locked; + + /* helper functions for working with mutexes & conditionals */ + void + lock_data(const char *func, uint line); + + void + unlock_data(const char *func, uint line); + + static void + on_queue_change(); +protected: + /* Singleton instance */ + static Event_scheduler *singleton; + +}; + #endif /* _EVENT_QUEUE_H_ */ diff --git a/sql/event_scheduler.cc b/sql/event_scheduler.cc index 35c30e5fc5a..fb60ce8ae6d 100644 --- a/sql/event_scheduler.cc +++ b/sql/event_scheduler.cc @@ -264,11 +264,6 @@ LEX_STRING states_names[] = }; #endif - -Event_scheduler -Event_scheduler::singleton; - - const char * const Event_scheduler::cond_vars_names[Event_scheduler::COND_LAST] = { @@ -278,6 +273,13 @@ Event_scheduler::cond_vars_names[Event_scheduler::COND_LAST] = }; +/* +Event_scheduler* +Event_scheduler::singleton= NULL; +*/ + + + class Worker_thread_param { public: @@ -300,35 +302,6 @@ public: }; -/* - Compares the execute_at members of 2 Event_timed instances. - Used as callback for the prioritized queue when shifting - elements inside. - - SYNOPSIS - event_timed_compare_q() - - vptr - not used (set it to NULL) - a - first Event_timed object - b - second Event_timed object - - RETURN VALUE - -1 - a->execute_at < b->execute_at - 0 - a->execute_at == b->execute_at - 1 - a->execute_at > b->execute_at - - NOTES - execute_at.second_part is not considered during comparison -*/ - -static int -event_timed_compare_q(void *vptr, byte* a, byte *b) -{ - return my_time_compare(&((Event_timed *)a)->execute_at, - &((Event_timed *)b)->execute_at); -} - - /* Prints the stack of infos, warnings, errors from thd to the console so it can be fetched by the logs-into-tables and @@ -640,6 +613,8 @@ event_worker_thread(void *arg) } + + /* Constructor of class Event_scheduler. @@ -648,15 +623,35 @@ event_worker_thread(void *arg) */ Event_scheduler::Event_scheduler() - :state(UNINITIALIZED), start_scheduler_suspended(FALSE), - thread_id(0), mutex_last_locked_at_line(0), - mutex_last_unlocked_at_line(0), mutex_last_locked_in_func(""), - mutex_last_unlocked_in_func(""), cond_waiting_on(COND_NONE), - mutex_scheduler_data_locked(FALSE) { + thread_id= 0; + mutex_last_unlocked_at_line_nr= mutex_last_locked_at_line_nr= 0; + mutex_last_unlocked_in_func_name= mutex_last_locked_in_func_name= ""; + cond_waiting_on= COND_NONE; + mutex_scheduler_data_locked= FALSE; + state= UNINITIALIZED; + start_scheduler_suspended= FALSE; + LOCK_scheduler_data= &LOCK_event_queue; } + +/* + Returns the singleton instance of the class. + + SYNOPSIS + Event_scheduler::create_instance() + + RETURN VALUE + address +*/ + +void +Event_scheduler::create_instance() +{ + singleton= new Event_scheduler(); +} + /* Returns the singleton instance of the class. @@ -671,7 +666,7 @@ Event_scheduler* Event_scheduler::get_instance() { DBUG_ENTER("Event_scheduler::get_instance"); - DBUG_RETURN(&singleton); + DBUG_RETURN(singleton); } @@ -693,9 +688,9 @@ Event_scheduler::init(Event_db_repository *db_repo) bool ret= FALSE; DBUG_ENTER("Event_scheduler::init"); DBUG_PRINT("enter", ("this=%p", this)); - + + Event_queue::init(db_repo); LOCK_SCHEDULER_DATA(); - db_repository= db_repo; for (;i < COND_LAST; i++) if (pthread_cond_init(&cond_vars[i], NULL)) { @@ -703,27 +698,6 @@ Event_scheduler::init(Event_db_repository *db_repo) ret= TRUE; goto end; } - - /* init memory root */ - init_alloc_root(&scheduler_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC); - - if (init_queue_ex(&queue, 30 /*num_el*/, 0 /*offset*/, 0 /*smallest_on_top*/, - event_timed_compare_q, NULL, 30 /*auto_extent*/)) - { - sql_print_error("SCHEDULER: Can't initialize the execution queue"); - ret= TRUE; - goto end; - } - - if (sizeof(my_time_t) != sizeof(time_t)) - { - sql_print_error("SCHEDULER: sizeof(my_time_t) != sizeof(time_t) ." - "The scheduler may not work correctly. Stopping."); - DBUG_ASSERT(0); - ret= TRUE; - goto end; - } - state= INITIALIZED; end: UNLOCK_SCHEDULER_DATA(); @@ -746,14 +720,12 @@ void Event_scheduler::destroy() { DBUG_ENTER("Event_scheduler"); - + Event_queue::deinit(); LOCK_SCHEDULER_DATA(); switch (state) { case UNINITIALIZED: break; case INITIALIZED: - delete_queue(&queue); - free_root(&scheduler_root, MYF(0)); int i; for (i= 0; i < COND_LAST; i++) pthread_cond_destroy(&cond_vars[i]); @@ -771,389 +743,6 @@ Event_scheduler::destroy() } -/* - Creates an event in the scheduler queue - - SYNOPSIS - Event_scheduler::create_event() - et The event to add - check_existence Whether to check if already loaded. - - RETURN VALUE - OP_OK OK or scheduler not working - OP_LOAD_ERROR Error during loading from disk -*/ - -int -Event_scheduler::create_event(THD *thd, Event_parse_data *et, bool check_existence) -{ - int res; - Event_timed *et_new; - DBUG_ENTER("Event_scheduler::create_event"); - DBUG_PRINT("enter", ("thd=%p et=%p lock=%p",thd,et,&LOCK_scheduler_data)); - - LOCK_SCHEDULER_DATA(); - if (!is_running_or_suspended()) - { - DBUG_PRINT("info", ("scheduler not running but %d. doing nothing", state)); - UNLOCK_SCHEDULER_DATA(); - DBUG_RETURN(OP_OK); - } - if (check_existence && find_event(et->dbname, et->name, FALSE)) - { - res= OP_ALREADY_EXISTS; - goto end; - } - - /* We need to load the event on scheduler_root */ - if (!(res= db_repository-> - load_named_event(thd, et->dbname, et->name, &et_new))) - { - queue_insert_safe(&queue, (byte *) et_new); - DBUG_PRINT("info", ("Sending COND_new_work")); - pthread_cond_signal(&cond_vars[COND_new_work]); - } - else if (res == OP_DISABLED_EVENT) - res= OP_OK; -end: - UNLOCK_SCHEDULER_DATA(); - DBUG_RETURN(res); -} - - -/* - Drops an event from the scheduler queue - - SYNOPSIS - Event_scheduler::drop_event() - etn The event to drop - state Wait the event or kill&drop - - RETURN VALUE - FALSE OK (replaced or scheduler not working) - TRUE Failure -*/ - -bool -Event_scheduler::drop_event(THD *thd, sp_name *name) -{ - int res; - Event_timed *et_old; - DBUG_ENTER("Event_scheduler::drop_event"); - DBUG_PRINT("enter", ("thd=%p name=%p lock=%p", thd, name, - &LOCK_scheduler_data)); - - LOCK_SCHEDULER_DATA(); - if (!is_running_or_suspended()) - { - DBUG_PRINT("info", ("scheduler not running but %d. doing nothing", state)); - UNLOCK_SCHEDULER_DATA(); - DBUG_RETURN(OP_OK); - } - - if (!(et_old= find_event(name->m_db, name->m_name, TRUE))) - DBUG_PRINT("info", ("No such event found, probably DISABLED")); - - UNLOCK_SCHEDULER_DATA(); - - /* See comments in ::replace_event() why this is split in two parts. */ - if (et_old) - { - switch ((res= et_old->kill_thread(thd))) { - case EVEX_CANT_KILL: - /* Don't delete but continue */ - et_old->flags |= EVENT_FREE_WHEN_FINISHED; - break; - case 0: - /* - kill_thread() waits till the spawned thread finishes after it's - killed. Hence, we delete here memory which is no more referenced from - a running thread. - */ - delete et_old; - /* - We don't signal COND_new_work here because: - 1. Even if the dropped event is on top of the queue this will not - move another one to be executed before the time the one on the - top (but could be at the same second as the dropped one) - 2. If this was the last event on the queue, then pthread_cond_timedwait - in ::run() will finish and then see that the queue is empty and - call cond_wait(). Hence, no need to interrupt the blocked - ::run() thread. - */ - break; - default: - sql_print_error("SCHEDULER: Got unexpected error %d", res); - DBUG_ASSERT(0); - } - } - - DBUG_RETURN(FALSE); -} - - -/* - Updates an event from the scheduler queue - - SYNOPSIS - Event_scheduler::replace_event() - et The event to replace(add) into the queue - state Async or sync stopping - - RETURN VALUE - OP_OK OK or scheduler not working - OP_LOAD_ERROR Error during loading from disk - OP_ALREADY_EXISTS Event already in the queue -*/ - -int -Event_scheduler::update_event(THD *thd, Event_parse_data *et, - LEX_STRING *new_schema, - LEX_STRING *new_name) -{ - int res= OP_OK; - Event_timed *et_old, *et_new= NULL; - LEX_STRING old_schema, old_name; - - LINT_INIT(old_schema.str); - LINT_INIT(old_schema.length); - LINT_INIT(old_name.str); - LINT_INIT(old_name.length); - - DBUG_ENTER("Event_scheduler::update_event"); - DBUG_PRINT("enter", ("thd=%p et=%p et=[%s.%s] lock=%p", - thd, et, et->dbname.str, et->name.str, &LOCK_scheduler_data)); - - LOCK_SCHEDULER_DATA(); - if (!is_running_or_suspended()) - { - DBUG_PRINT("info", ("scheduler not running but %d. doing nothing", state)); - UNLOCK_SCHEDULER_DATA(); - DBUG_RETURN(OP_OK); - } - - if (!(et_old= find_event(et->dbname, et->name, TRUE))) - DBUG_PRINT("info", ("%s.%s not found cached, probably was DISABLED", - et->dbname.str, et->name.str)); - - if (new_schema && new_name) - { - old_schema= et->dbname; - old_name= et->name; - et->dbname= *new_schema; - et->name= *new_name; - } - /* - We need to load the event (it's strings but on the object itself) - on scheduler_root. et_new could be NULL : - 1. Error occured - 2. If the replace is DISABLED, we don't load it into the queue. - */ - if (!(res= db_repository-> - load_named_event(thd, et->dbname, et->name, &et_new))) - { - queue_insert_safe(&queue, (byte *) et_new); - DBUG_PRINT("info", ("Sending COND_new_work")); - pthread_cond_signal(&cond_vars[COND_new_work]); - } - else if (res == OP_DISABLED_EVENT) - res= OP_OK; - - if (new_schema && new_name) - { - et->dbname= old_schema; - et->name= old_name; - } - DBUG_PRINT("info", ("res=%d", res)); - UNLOCK_SCHEDULER_DATA(); - /* - Andrey: Is this comment still truthful ??? - - We don't move this code above because a potential kill_thread will call - THD::awake(). Which in turn will try to acqure mysys_var->current_mutex, - which is LOCK_scheduler_data on which the COND_new_work in ::run() locks. - Hence, we try to acquire a lock which we have already acquired and we run - into an assert. Holding LOCK_scheduler_data however is not needed because - we don't touch any invariant of the scheduler anymore. ::drop_event() does - the same. - */ - if (et_old) - { - switch (et_old->kill_thread(thd)) { - case EVEX_CANT_KILL: - /* Don't delete but continue */ - et_old->flags |= EVENT_FREE_WHEN_FINISHED; - break; - case 0: - /* - kill_thread() waits till the spawned thread finishes after it's - killed. Hence, we delete here memory which is no more referenced from - a running thread. - */ - delete et_old; - /* - We don't signal COND_new_work here because: - 1. Even if the dropped event is on top of the queue this will not - move another one to be executed before the time the one on the - top (but could be at the same second as the dropped one) - 2. If this was the last event on the queue, then pthread_cond_timedwait - in ::run() will finish and then see that the queue is empty and - call cond_wait(). Hence, no need to interrupt the blocked - ::run() thread. - */ - break; - default: - DBUG_ASSERT(0); - } - } - - DBUG_RETURN(res); -} - - -/* - Searches for an event in the scheduler queue - - SYNOPSIS - Event_scheduler::find_event() - db The schema of the event to find - name The event to find - remove_from_q If found whether to remove from the Q - - RETURN VALUE - NULL Not found - otherwise Address - - NOTE - The caller should do the locking also the caller is responsible for - actual signalling in case an event is removed from the queue - (signalling COND_new_work for instance). -*/ - -Event_timed * -Event_scheduler::find_event(LEX_STRING db, LEX_STRING name, bool remove_from_q) -{ - uint i; - DBUG_ENTER("Event_scheduler::find_event"); - - for (i= 0; i < queue.elements; ++i) - { - Event_timed *et= (Event_timed *) queue_element(&queue, i); - DBUG_PRINT("info", ("[%s.%s]==[%s.%s]?", db.str, name.str, - et->dbname.str, et->name.str)); - if (event_timed_identifier_equal(db, name, et)) - { - if (remove_from_q) - queue_remove(&queue, i); - DBUG_RETURN(et); - } - } - - DBUG_RETURN(NULL); -} - - -/* - Drops all events from the in-memory queue and disk that match - certain pattern evaluated by a comparator function - - SYNOPSIS - Event_scheduler::drop_matching_events() - thd THD - pattern A pattern string - comparator The function to use for comparing - - RETURN VALUE - -1 Scheduler not working - >=0 Number of dropped events - - NOTE - Expected is the caller to acquire lock on LOCK_scheduler_data -*/ - -void -Event_scheduler::drop_matching_events(THD *thd, LEX_STRING pattern, - bool (*comparator)(Event_timed *,LEX_STRING *)) -{ - DBUG_ENTER("Event_scheduler::drop_matching_events"); - DBUG_PRINT("enter", ("pattern=%*s state=%d", pattern.length, pattern.str, - state)); - if (is_running_or_suspended()) - { - uint i= 0, dropped= 0; - while (i < queue.elements) - { - Event_timed *et= (Event_timed *) queue_element(&queue, i); - DBUG_PRINT("info", ("[%s.%s]?", et->dbname.str, et->name.str)); - if (comparator(et, &pattern)) - { - /* - The queue is ordered. If we remove an element, then all elements after - it will shift one position to the left, if we imagine it as an array - from left to the right. In this case we should not increment the - counter and the (i < queue.elements) condition is ok. - */ - queue_remove(&queue, i); - - /* See replace_event() */ - switch (et->kill_thread(thd)) { - case EVEX_CANT_KILL: - /* Don't delete but continue */ - et->flags |= EVENT_FREE_WHEN_FINISHED; - ++dropped; - break; - case 0: - delete et; - ++dropped; - break; - default: - DBUG_ASSERT(0); - } - } - else - i++; - } - DBUG_PRINT("info", ("Dropped %lu", dropped)); - } - /* - Don't send COND_new_work because no need to wake up the scheduler thread. - When it wakes next time up it will recalculate how much more it should - sleep if the top of the queue has been changed by this method. - */ - - DBUG_VOID_RETURN; -} - - -/* - Drops all events from the in-memory queue and disk that are from - certain schema. - - SYNOPSIS - Event_scheduler::drop_schema_events() - thd THD - db The schema name - - RETURN VALUE - -1 Scheduler not working - >=0 Number of dropped events -*/ - -int -Event_scheduler::drop_schema_events(THD *thd, LEX_STRING schema) -{ - int ret; - DBUG_ENTER("Event_scheduler::drop_schema_events"); - LOCK_SCHEDULER_DATA(); - if (is_running_or_suspended()) - drop_matching_events(thd, schema, event_timed_db_equal); - - UNLOCK_SCHEDULER_DATA(); - - DBUG_RETURN(ret); -} - - extern pthread_attr_t connection_attrib; @@ -1205,8 +794,8 @@ Event_scheduler::start() } /* Wait till the child thread has booted (w/ or wo success) */ - while (!is_running_or_suspended() && state != CANTSTART) - cond_wait(COND_started_or_stopped, &LOCK_scheduler_data); + while (!(state == SUSPENDED || state == RUNNING) && state != CANTSTART) + cond_wait(COND_started_or_stopped, LOCK_scheduler_data); /* If we cannot start for some reason then don't prohibit further attempts. @@ -1314,7 +903,7 @@ Event_scheduler::run(THD *thd) sql_print_information("SCHEDULER: Manager thread started with id %lu", thd->thread_id); abstime.tv_nsec= 0; - while (is_running_or_suspended()) + while ((state == SUSPENDED || state == RUNNING)) { Event_timed *et; @@ -1374,9 +963,9 @@ Event_scheduler::run(THD *thd) pthread_cond_timedwait() will wait till `abstime`. "Sleeping until next time" */ - thd->enter_cond(&cond_vars[COND_new_work],&LOCK_scheduler_data,"Sleeping"); + thd->enter_cond(&cond_vars[COND_new_work],LOCK_scheduler_data,"Sleeping"); - pthread_cond_timedwait(&cond_vars[COND_new_work], &LOCK_scheduler_data, + pthread_cond_timedwait(&cond_vars[COND_new_work], LOCK_scheduler_data, &abstime); DBUG_PRINT("info", ("Manager woke up. state is %d", state)); @@ -1397,7 +986,7 @@ Event_scheduler::run(THD *thd) In this case stop the manager. We should enter ::execute_top() with locked LOCK_scheduler_data. */ - int ret= execute_top(thd); + int ret= execute_top(thd, et); UNLOCK_SCHEDULER_DATA(); if (ret) break; @@ -1421,7 +1010,7 @@ Event_scheduler::run(THD *thd) sql_print_information("SCHEDULER: Shutting down"); thd->proc_info= (char *)"Cleaning queue"; - clean_queue(thd); + clean_memory(thd); THD_CHECK_SENTRY(thd); /* free mamager_root memory but don't destroy the root */ @@ -1474,15 +1063,13 @@ Event_scheduler::run(THD *thd) */ bool -Event_scheduler::execute_top(THD *thd) +Event_scheduler::execute_top(THD *thd, Event_timed *et) { int spawn_ret_code; bool ret= FALSE; DBUG_ENTER("Event_scheduler::execute_top"); DBUG_PRINT("enter", ("thd=%p", thd)); - Event_timed *et= (Event_timed *)queue_top(&queue); - /* Is it good idea to pass a stack address ?*/ Worker_thread_param param(et); @@ -1552,7 +1139,7 @@ Event_scheduler::execute_top(THD *thd) */ void -Event_scheduler::clean_queue(THD *thd) +Event_scheduler::clean_memory(THD *thd) { CHARSET_INFO *scs= system_charset_info; uint i; @@ -1565,14 +1152,7 @@ Event_scheduler::clean_queue(THD *thd) sql_print_information("SCHEDULER: Emptying the queue"); - /* empty the queue */ - for (i= 0; i < queue.elements; ++i) - { - Event_timed *et= (Event_timed *) queue_element(&queue, i); - et->free_sp(); - delete et; - } - resize_queue(&queue, 0); + empty_queue(); DBUG_VOID_RETURN; } @@ -1681,7 +1261,7 @@ Event_scheduler::stop() DBUG_PRINT("enter", ("thd=%p", current_thd)); LOCK_SCHEDULER_DATA(); - if (!is_running_or_suspended()) + if (!(state == SUSPENDED || state == RUNNING)) { /* One situation to be here is if there was a start that forked a new @@ -1717,7 +1297,7 @@ Event_scheduler::stop() DBUG_PRINT("info", ("Waiting for COND_started_or_stopped from the manager " "thread. Current value of state is %d . " "workers count=%d", state, workers_count())); - cond_wait(COND_started_or_stopped, &LOCK_scheduler_data); + cond_wait(COND_started_or_stopped, LOCK_scheduler_data); } DBUG_PRINT("info", ("Manager thread has cleaned up. Set state to INIT")); UNLOCK_SCHEDULER_DATA(); @@ -1768,7 +1348,7 @@ Event_scheduler::suspend_or_resume( pthread_cond_signal(&cond_vars[COND_suspend_or_resume]); } DBUG_PRINT("info", ("Waiting on COND_suspend_or_resume")); - cond_wait(COND_suspend_or_resume, &LOCK_scheduler_data); + cond_wait(COND_suspend_or_resume, LOCK_scheduler_data); DBUG_PRINT("info", ("Got response")); } UNLOCK_SCHEDULER_DATA(); @@ -1833,7 +1413,7 @@ Event_scheduler::check_n_suspend_if_needed(THD *thd) } if (state == SUSPENDED) { - thd->enter_cond(&cond_vars[COND_suspend_or_resume], &LOCK_scheduler_data, + thd->enter_cond(&cond_vars[COND_suspend_or_resume], LOCK_scheduler_data, "Suspended"); /* Send back signal to the thread that asked us to suspend operations */ pthread_cond_signal(&cond_vars[COND_suspend_or_resume]); @@ -1842,7 +1422,7 @@ Event_scheduler::check_n_suspend_if_needed(THD *thd) } while (state == SUSPENDED) { - cond_wait(COND_suspend_or_resume, &LOCK_scheduler_data); + cond_wait(COND_suspend_or_resume, LOCK_scheduler_data); DBUG_PRINT("info", ("Woke up after waiting on COND_suspend_or_resume")); if (state != SUSPENDED) { @@ -1852,18 +1432,7 @@ Event_scheduler::check_n_suspend_if_needed(THD *thd) } if (was_suspended) { - if (queue.elements) - { - uint i; - DBUG_PRINT("info", ("We have to recompute the execution times")); - - for (i= 0; i < queue.elements; i++) - { - ((Event_timed*)queue_element(&queue, i))->compute_next_execution_time(); - ((Event_timed*)queue_element(&queue, i))->update_fields(thd); - } - queue_fix(&queue); - } + recalculate_queue(thd); /* This will implicitly unlock LOCK_scheduler_data */ thd->exit_cond(""); } @@ -1892,18 +1461,18 @@ Event_scheduler::check_n_wait_for_non_empty_queue(THD *thd) bool slept= FALSE; DBUG_ENTER("Event_scheduler::check_n_wait_for_non_empty_queue"); DBUG_PRINT("enter", ("q.elements=%lu state=%s", - queue.elements, states_names[state])); + events_count_no_lock(), states_names[state])); - if (!queue.elements) - thd->enter_cond(&cond_vars[COND_new_work], &LOCK_scheduler_data, + if (!events_count_no_lock()) + thd->enter_cond(&cond_vars[COND_new_work], LOCK_scheduler_data, "Empty queue, sleeping"); /* Wait in a loop protecting against catching spurious signals */ - while (!queue.elements && state == RUNNING) + while (!events_count_no_lock() && state == RUNNING) { slept= TRUE; DBUG_PRINT("info", ("Entering condition because of empty queue")); - cond_wait(COND_new_work, &LOCK_scheduler_data); + cond_wait(COND_new_work, LOCK_scheduler_data); DBUG_PRINT("info", ("Manager woke up. Hope we have events now. state=%d", state)); /* @@ -1916,104 +1485,12 @@ Event_scheduler::check_n_wait_for_non_empty_queue(THD *thd) thd->exit_cond(""); DBUG_PRINT("exit", ("q.elements=%lu state=%s thd->killed=%d", - queue.elements, states_names[state], thd->killed)); + events_count_no_lock(), states_names[state], thd->killed)); DBUG_RETURN(slept); } -/* - Wrapper for pthread_mutex_lock - - SYNOPSIS - Event_scheduler::lock_data() - mutex Mutex to lock - line The line number on which the lock is done - - RETURN VALUE - Error code of pthread_mutex_lock() -*/ - -inline void -Event_scheduler::lock_data(const char *func, uint line) -{ - DBUG_ENTER("Event_scheduler::lock_mutex"); - DBUG_PRINT("enter", ("mutex_lock=%p func=%s line=%u", - &LOCK_scheduler_data, func, line)); - pthread_mutex_lock(&LOCK_scheduler_data); - mutex_last_locked_in_func= func; - mutex_last_locked_at_line= line; - mutex_scheduler_data_locked= TRUE; - DBUG_VOID_RETURN; -} - - -/* - Wrapper for pthread_mutex_unlock - - SYNOPSIS - Event_scheduler::unlock_data() - mutex Mutex to unlock - line The line number on which the unlock is done -*/ - -inline void -Event_scheduler::unlock_data(const char *func, uint line) -{ - DBUG_ENTER("Event_scheduler::UNLOCK_mutex"); - DBUG_PRINT("enter", ("mutex_unlock=%p func=%s line=%u", - &LOCK_scheduler_data, func, line)); - mutex_last_unlocked_at_line= line; - mutex_scheduler_data_locked= FALSE; - mutex_last_unlocked_in_func= func; - pthread_mutex_unlock(&LOCK_scheduler_data); - DBUG_VOID_RETURN; -} - - -/* - Wrapper for pthread_cond_wait - - SYNOPSIS - Event_scheduler::cond_wait() - cond Conditional to wait for - mutex Mutex of the conditional - - RETURN VALUE - Error code of pthread_cond_wait() -*/ - -inline int -Event_scheduler::cond_wait(enum Event_scheduler::enum_cond_vars cond, - pthread_mutex_t *mutex) -{ - int ret; - DBUG_ENTER("Event_scheduler::cond_wait"); - DBUG_PRINT("enter", ("cond=%s mutex=%p", cond_vars_names[cond], mutex)); - ret= pthread_cond_wait(&cond_vars[cond_waiting_on=cond], mutex); - cond_waiting_on= COND_NONE; - DBUG_RETURN(ret); -} - - -/* - Checks whether the scheduler is in a running or suspended state. - - SYNOPSIS - Event_scheduler::is_running_or_suspended() - - RETURN VALUE - TRUE Either running or suspended - FALSE IN_SHUTDOWN, not started, etc. -*/ - -inline bool -Event_scheduler::is_running_or_suspended() -{ - return (state == SUSPENDED || state == RUNNING); -} - - /* Returns the current state of the scheduler @@ -2027,9 +1504,9 @@ Event_scheduler::get_state() enum Event_scheduler::enum_state ret; DBUG_ENTER("Event_scheduler::get_state"); /* lock_data & unlock_data are not static */ - pthread_mutex_lock(&singleton.LOCK_scheduler_data); - ret= singleton.state; - pthread_mutex_unlock(&singleton.LOCK_scheduler_data); + pthread_mutex_lock(singleton->LOCK_scheduler_data); + ret= singleton->state; + pthread_mutex_unlock(singleton->LOCK_scheduler_data); DBUG_RETURN(ret); } @@ -2053,252 +1530,6 @@ Event_scheduler::initialized() } -/* - Returns the number of elements in the queue - - SYNOPSIS - Event_scheduler::events_count() - - RETURN VALUE - 0 Number of Event_timed objects in the queue -*/ - -uint -Event_scheduler::events_count() -{ - uint n; - DBUG_ENTER("Event_scheduler::events_count"); - LOCK_SCHEDULER_DATA(); - n= queue.elements; - UNLOCK_SCHEDULER_DATA(); - - DBUG_RETURN(n); -} - - - - -/* - Loads all ENABLED events from mysql.event into the prioritized - queue. Called during scheduler main thread initialization. Compiles - the events. Creates Event_timed instances for every ENABLED event - from mysql.event. - - SYNOPSIS - Event_scheduler::load_events_from_db() - thd - Thread context. Used for memory allocation in some cases. - - RETURN VALUE - 0 OK - !0 Error (EVEX_OPEN_TABLE_FAILED, EVEX_MICROSECOND_UNSUP, - EVEX_COMPILE_ERROR) - in all these cases mysql.event was - tampered. - - NOTES - Reports the error to the console -*/ - -int -Event_scheduler::load_events_from_db(THD *thd) -{ - TABLE *table; - READ_RECORD read_record_info; - int ret= -1; - uint count= 0; - bool clean_the_queue= FALSE; - /* Compile the events on this root but only for syntax check, then discard */ - MEM_ROOT boot_root; - - DBUG_ENTER("Event_scheduler::load_events_from_db"); - DBUG_PRINT("enter", ("thd=%p", thd)); - - if (state > COMMENCING) - { - DBUG_ASSERT(0); - sql_print_error("SCHEDULER: Trying to load events while already running."); - DBUG_RETURN(EVEX_GENERAL_ERROR); - } - - if ((ret= db_repository->open_event_table(thd, TL_READ, &table))) - { - sql_print_error("SCHEDULER: Table mysql.event is damaged. Can not open."); - DBUG_RETURN(EVEX_OPEN_TABLE_FAILED); - } - - init_alloc_root(&boot_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC); - init_read_record(&read_record_info, thd, table ,NULL,1,0); - while (!(read_record_info.read_record(&read_record_info))) - { - Event_timed *et; - if (!(et= new Event_timed)) - { - DBUG_PRINT("info", ("Out of memory")); - clean_the_queue= TRUE; - break; - } - DBUG_PRINT("info", ("Loading event from row.")); - - if ((ret= et->load_from_row(&scheduler_root, table))) - { - clean_the_queue= TRUE; - sql_print_error("SCHEDULER: Error while loading from mysql.event. " - "Table probably corrupted"); - break; - } - if (et->status != Event_timed::ENABLED) - { - DBUG_PRINT("info",("%s is disabled",et->name.str)); - delete et; - continue; - } - - DBUG_PRINT("info", ("Event %s loaded from row. ", et->name.str)); - - /* We load only on scheduler root just to check whether the body compiles */ - switch (ret= et->compile(thd, &boot_root)) { - case EVEX_MICROSECOND_UNSUP: - et->free_sp(); - sql_print_error("SCHEDULER: mysql.event is tampered. MICROSECOND is not " - "supported but found in mysql.event"); - goto end; - case EVEX_COMPILE_ERROR: - sql_print_error("SCHEDULER: Error while compiling %s.%s. Aborting load.", - et->dbname.str, et->name.str); - goto end; - default: - /* Free it, it will be compiled again on the worker thread */ - et->free_sp(); - break; - } - - /* let's find when to be executed */ - if (et->compute_next_execution_time()) - { - sql_print_error("SCHEDULER: Error while computing execution time of %s.%s." - " Skipping", et->dbname.str, et->name.str); - continue; - } - - DBUG_PRINT("load_events_from_db", ("Adding %p to the exec list.")); - queue_insert_safe(&queue, (byte *) et); - count++; - } -end: - end_read_record(&read_record_info); - free_root(&boot_root, MYF(0)); - - if (clean_the_queue) - { - for (count= 0; count < queue.elements; ++count) - queue_remove(&queue, 0); - ret= -1; - } - else - { - ret= 0; - sql_print_information("SCHEDULER: Loaded %d event%s", count, (count == 1)?"":"s"); - } - - /* Force close to free memory */ - thd->version--; - - close_thread_tables(thd); - - DBUG_PRINT("info", ("Status code %d. Loaded %d event(s)", ret, count)); - DBUG_RETURN(ret); -} - - -/* - Opens mysql.db and mysql.user and checks whether: - 1. mysql.db has column Event_priv at column 20 (0 based); - 2. mysql.user has column Event_priv at column 29 (0 based); - - SYNOPSIS - Event_scheduler::check_system_tables() -*/ - -bool -Event_scheduler::check_system_tables(THD *thd) -{ - TABLE_LIST tables; - bool not_used; - Open_tables_state backup; - bool ret; - - DBUG_ENTER("Event_scheduler::check_system_tables"); - DBUG_PRINT("enter", ("thd=%p", thd)); - - thd->reset_n_backup_open_tables_state(&backup); - - bzero((char*) &tables, sizeof(tables)); - tables.db= (char*) "mysql"; - tables.table_name= tables.alias= (char*) "db"; - tables.lock_type= TL_READ; - - if ((ret= simple_open_n_lock_tables(thd, &tables))) - sql_print_error("Cannot open mysql.db"); - else - { - ret= table_check_intact(tables.table, MYSQL_DB_FIELD_COUNT, - mysql_db_table_fields, &mysql_db_table_last_check, - ER_CANNOT_LOAD_FROM_TABLE); - close_thread_tables(thd); - } - if (ret) - DBUG_RETURN(TRUE); - - bzero((char*) &tables, sizeof(tables)); - tables.db= (char*) "mysql"; - tables.table_name= tables.alias= (char*) "user"; - tables.lock_type= TL_READ; - - if ((ret= simple_open_n_lock_tables(thd, &tables))) - sql_print_error("Cannot open mysql.db"); - else - { - if (tables.table->s->fields < 29 || - strncmp(tables.table->field[29]->field_name, - STRING_WITH_LEN("Event_priv"))) - { - sql_print_error("mysql.user has no `Event_priv` column at position 29"); - ret= TRUE; - } - close_thread_tables(thd); - } - - thd->restore_backup_open_tables_state(&backup); - - DBUG_RETURN(ret); -} - - -/* - Inits mutexes. - - SYNOPSIS - Event_scheduler::init_mutexes() -*/ - -void -Event_scheduler::init_mutexes() -{ - pthread_mutex_init(&singleton.LOCK_scheduler_data, MY_MUTEX_INIT_FAST); -} - - -/* - Destroys mutexes. - - SYNOPSIS - Event_scheduler::destroy_mutexes() -*/ - -void -Event_scheduler::destroy_mutexes() -{ - pthread_mutex_destroy(&singleton.LOCK_scheduler_data); -} /* @@ -2337,8 +1568,8 @@ Event_scheduler::dump_internal_status(THD *thd) protocol->prepare_for_resend(); protocol->store(STRING_WITH_LEN("state"), scs); - protocol->store(states_names[singleton.state].str, - states_names[singleton.state].length, + protocol->store(states_names[singleton->state].str, + states_names[singleton->state].length, scs); ret= protocol->write(); @@ -2346,7 +1577,7 @@ Event_scheduler::dump_internal_status(THD *thd) If not initialized - don't show anything else. get_instance() will otherwise implicitly initialize it. We don't want that. */ - if (singleton.state >= INITIALIZED) + if (singleton->state >= INITIALIZED) { /* last locked at*/ /* @@ -2357,8 +1588,8 @@ Event_scheduler::dump_internal_status(THD *thd) protocol->store(STRING_WITH_LEN("last locked at"), scs); tmp_string.length(scs->cset->snprintf(scs, (char*) tmp_string.ptr(), tmp_string.alloced_length(), "%s::%d", - singleton.mutex_last_locked_in_func, - singleton.mutex_last_locked_at_line)); + singleton->mutex_last_locked_in_func, + singleton->mutex_last_locked_at_line)); protocol->store(&tmp_string); ret= protocol->write(); @@ -2367,8 +1598,8 @@ Event_scheduler::dump_internal_status(THD *thd) protocol->store(STRING_WITH_LEN("last unlocked at"), scs); tmp_string.length(scs->cset->snprintf(scs, (char*) tmp_string.ptr(), tmp_string.alloced_length(), "%s::%d", - singleton.mutex_last_unlocked_in_func, - singleton.mutex_last_unlocked_at_line)); + singleton->mutex_last_unlocked_in_func, + singleton->mutex_last_unlocked_at_line)); protocol->store(&tmp_string); ret= protocol->write(); @@ -2378,8 +1609,8 @@ Event_scheduler::dump_internal_status(THD *thd) tmp_string.length(scs->cset-> snprintf(scs, (char*) tmp_string.ptr(), tmp_string.alloced_length(), "%s", - (singleton.cond_waiting_on != COND_NONE) ? - cond_vars_names[singleton.cond_waiting_on]: + (singleton->cond_waiting_on != COND_NONE) ? + cond_vars_names[singleton->cond_waiting_on]: "NONE")); protocol->store(&tmp_string); ret= protocol->write(); @@ -2396,7 +1627,7 @@ Event_scheduler::dump_internal_status(THD *thd) /* queue.elements */ protocol->prepare_for_resend(); protocol->store(STRING_WITH_LEN("queue.elements"), scs); - int_string.set((longlong) scheduler->queue.elements, scs); + int_string.set((longlong) scheduler->events_count_no_lock(), scs); protocol->store(&int_string); ret= protocol->write(); @@ -2411,3 +1642,94 @@ Event_scheduler::dump_internal_status(THD *thd) #endif DBUG_RETURN(0); } + + +/* + Wrapper for pthread_mutex_lock + + SYNOPSIS + Event_scheduler::lock_data() + mutex Mutex to lock + line The line number on which the lock is done + + RETURN VALUE + Error code of pthread_mutex_lock() +*/ + +void +Event_scheduler::lock_data(const char *func, uint line) +{ + DBUG_ENTER("Event_scheduler::lock_mutex"); + DBUG_PRINT("enter", ("mutex_lock=%p func=%s line=%u", + &LOCK_scheduler_data, func, line)); + pthread_mutex_lock(LOCK_scheduler_data); + mutex_last_locked_in_func_name= func; + mutex_last_locked_at_line_nr= line; + mutex_scheduler_data_locked= TRUE; + DBUG_VOID_RETURN; +} + + +/* + Wrapper for pthread_mutex_unlock + + SYNOPSIS + Event_scheduler::unlock_data() + mutex Mutex to unlock + line The line number on which the unlock is done +*/ + +void +Event_scheduler::unlock_data(const char *func, uint line) +{ + DBUG_ENTER("Event_scheduler::UNLOCK_mutex"); + DBUG_PRINT("enter", ("mutex_unlock=%p func=%s line=%u", + LOCK_scheduler_data, func, line)); + mutex_last_unlocked_at_line_nr= line; + mutex_scheduler_data_locked= FALSE; + mutex_last_unlocked_in_func_name= func; + pthread_mutex_unlock(LOCK_scheduler_data); + DBUG_VOID_RETURN; +} + + +/* + Wrapper for pthread_cond_wait + + SYNOPSIS + Event_scheduler::cond_wait() + cond Conditional to wait for + mutex Mutex of the conditional + + RETURN VALUE + Error code of pthread_cond_wait() +*/ + +int +Event_scheduler::cond_wait(int cond, pthread_mutex_t *mutex) +{ + int ret; + DBUG_ENTER("Event_scheduler::cond_wait"); + DBUG_PRINT("enter", ("cond=%s mutex=%p", cond_vars_names[cond], mutex)); + ret= pthread_cond_wait(&cond_vars[cond_waiting_on=cond], mutex); + cond_waiting_on= COND_NONE; + DBUG_RETURN(ret); +} + + +/* + Signals the main scheduler thread that the queue has changed + its state. + + SYNOPSIS + Event_scheduler::queue_changed() +*/ + +void +Event_scheduler::queue_changed() +{ + DBUG_ENTER("Event_scheduler::queue_changed"); + DBUG_PRINT("info", ("Sending COND_new_work")); + pthread_cond_signal(&cond_vars[COND_new_work]); + DBUG_VOID_RETURN; +} diff --git a/sql/event_scheduler.h b/sql/event_scheduler.h index a274636b38b..b4007d88976 100644 --- a/sql/event_scheduler.h +++ b/sql/event_scheduler.h @@ -21,7 +21,6 @@ class Event_timed; class Event_db_repository; class THD; -typedef bool * (*event_timed_identifier_comparator)(Event_timed*, Event_timed*); int events_init(); @@ -29,10 +28,12 @@ events_init(); void events_shutdown(); -class Event_scheduler +#include "event_queue.h" +#include "event_scheduler.h" + +class Event_scheduler : public Event_queue { public: - enum enum_state { UNINITIALIZED= 0, @@ -50,32 +51,22 @@ public: RESUME= 2 }; - /* Singleton access */ - static Event_scheduler* - get_instance(); - - /* Methods for queue management follow */ + /* This is the current status of the life-cycle of the scheduler. */ + enum enum_state state; - int - create_event(THD *thd, Event_parse_data *et, bool check_existence); - - int - update_event(THD *thd, Event_parse_data *et, LEX_STRING *new_schema, - LEX_STRING *new_name); - - bool - drop_event(THD *thd, sp_name *name); + static void + create_instance(); - int - drop_schema_events(THD *thd, LEX_STRING schema); + /* Singleton access */ + static Event_scheduler* + get_instance(); - int - drop_user_events(THD *thd, LEX_STRING *definer) - { DBUG_ASSERT(0); return 0;} + bool + init(Event_db_repository *db_repo); - uint - events_count(); + void + destroy(); /* State changing methods follow */ @@ -97,19 +88,13 @@ public: int suspend_or_resume(enum enum_suspend_or_resume action); - - bool - init(Event_db_repository *db_repo); - - void - destroy(); - +/* static void init_mutexes(); static void destroy_mutexes(); - +*/ void report_error_during_start(); @@ -124,35 +109,34 @@ public: static int dump_internal_status(THD *thd); - static bool - check_system_tables(THD *thd); + /* helper functions for working with mutexes & conditionals */ + void + lock_data(const char *func, uint line); -private: - Event_timed * - find_event(LEX_STRING db, LEX_STRING name, bool remove_from_q); + void + unlock_data(const char *func, uint line); + + int + cond_wait(int cond, pthread_mutex_t *mutex); + + void + queue_changed(); + +protected: uint workers_count(); - bool - is_running_or_suspended(); - /* helper functions */ bool - execute_top(THD *thd); + execute_top(THD *thd, Event_timed *et); void - clean_queue(THD *thd); + clean_memory(THD *thd); void stop_all_running_events(THD *thd); - int - load_events_from_db(THD *thd); - - void - drop_matching_events(THD *thd, LEX_STRING pattern, - bool (*)(Event_timed *,LEX_STRING *)); bool check_n_suspend_if_needed(THD *thd); @@ -163,47 +147,13 @@ private: /* Singleton DP is used */ Event_scheduler(); - enum enum_cond_vars - { - COND_NONE= -1, - /* - COND_new_work is a conditional used to signal that there is a change - of the queue that should inform the executor thread that new event should - be executed sooner than previously expected, because of add/replace event. - */ - COND_new_work= 0, - /* - COND_started is a conditional used to synchronize the thread in which - ::start() was called and the spawned thread. ::start() spawns a new thread - and then waits on COND_started but also checks when awaken that `state` is - either RUNNING or CANTSTART. Then it returns back. - */ - COND_started_or_stopped, - /* - Conditional used for signalling from the scheduler thread back to the - thread that calls ::suspend() or ::resume. Synchronizing the calls. - */ - COND_suspend_or_resume, - /* Must be always last */ - COND_LAST - }; - /* Singleton instance */ - static Event_scheduler singleton; + pthread_mutex_t *LOCK_scheduler_data; - /* This is the current status of the life-cycle of the manager. */ - enum enum_state state; /* Set to start the scheduler in suspended state */ bool start_scheduler_suspended; - /* - LOCK_scheduler_data is the mutex which protects the access to the - manager's queue as well as used when signalling COND_new_work, - COND_started and COND_shutdown. - */ - pthread_mutex_t LOCK_scheduler_data; - /* Holds the thread id of the executor thread or 0 if the executor is not running. It is used by ::shutdown() to know which thread to kill with @@ -212,33 +162,27 @@ private: */ ulong thread_id; - pthread_cond_t cond_vars[COND_LAST]; - static const char * const cond_vars_names[COND_LAST]; - - /* The MEM_ROOT of the object */ - MEM_ROOT scheduler_root; - - Event_db_repository *db_repository; + enum enum_cond_vars + { + COND_NONE= -1, + COND_new_work= 0, + COND_started_or_stopped, + COND_suspend_or_resume, + /* Must be always last */ + COND_LAST + }; - /* The sorted queue with the Event_timed objects */ - QUEUE queue; - - uint mutex_last_locked_at_line; - uint mutex_last_unlocked_at_line; - const char* mutex_last_locked_in_func; - const char* mutex_last_unlocked_in_func; - enum enum_cond_vars cond_waiting_on; + uint mutex_last_locked_at_line_nr; + uint mutex_last_unlocked_at_line_nr; + const char* mutex_last_locked_in_func_name; + const char* mutex_last_unlocked_in_func_name; + int cond_waiting_on; bool mutex_scheduler_data_locked; - /* helper functions for working with mutexes & conditionals */ - void - lock_data(const char *func, uint line); - void - unlock_data(const char *func, uint line); + static const char * const cond_vars_names[COND_LAST]; - int - cond_wait(enum enum_cond_vars, pthread_mutex_t *mutex); + pthread_cond_t cond_vars[COND_LAST]; private: /* Prevent use of these */ diff --git a/sql/events.cc b/sql/events.cc index 28c57d6b493..09d5ee21a4f 100644 --- a/sql/events.cc +++ b/sql/events.cc @@ -559,6 +559,7 @@ void Events::init_mutexes() { db_repository= new Event_db_repository; + Event_scheduler::create_instance(); Event_scheduler::init_mutexes(); } -- cgit v1.2.1 From a5dfeb02e991e6e5e9e332443522de1bb4592df8 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 4 Jul 2006 18:44:35 +0200 Subject: WL #3337 (Event scheduler new architecture) Cut Nr. 8. All tests pass. Separated Event_scheduler into Event_queue and Event_scheduler. Added new Event_scheduler_ng which is the new scheduler and is used system-wide. Will be moved to the event_scheduler.cc in the future. Using Event_timed in Event_queue as well as cloned during execution. Next step is to have Event_worker_data which will be used during execution and will take ::compile()/::execute() out of Event_timed. mysql-test/r/events.result: update result mysql-test/r/events_bugs.result: update result mysql-test/r/ps_1general.result: update result mysql-test/r/skip_name_resolve.result: update result mysql-test/r/sp-threads.result: update result mysql-test/r/sp_notembedded.result: update result mysql-test/r/status.result: update result mysql-test/t/events_stress.test: Make event_stress a bit longer sql/Makefile.am: Add new event_scheduler_ng.h/cc . These are only to be in the experimental clone. Later their content will be moved to event_scheduler.h/cc sql/event_data_objects.cc: Allocate strings memory on own memory root, instead on the schedulers. Thus don't "leak" memory. This should fix bug#18683 memory leak in event scheduler sql/event_data_objects.h: add mem_root add THD - this is only temporal, will be moved to class Event_job_data once Event_job_data is responsible for the execution. sql/event_db_repository.cc: Remove unused code. Cosmetic changes sql/event_queue.cc: Now use the Event_scheduler_ng (NextGen) sql/event_queue.h: Now use the Event_scheduler_ng (NextGen) sql/event_scheduler.cc: This file is no more used, but will be soon. sql/event_scheduler.h: This file is no more used but will be soon sql/events.cc: Now use the Event_scheduler_ng (NextGen) sql/events.h: Now use the Event_scheduler_ng (NextGen) sql/mysqld.cc: Make it again possible to kill the scheduler thread sql/set_var.cc: Now use the Event_scheduler_ng (NextGen) sql/share/errmsg.txt: Shorten the message. sql/sql_show.cc: Loading is on a own root, then don't use thd->mem_root --- mysql-test/r/events.result | 6 +- mysql-test/r/events_bugs.result | 10 +- mysql-test/r/ps_1general.result | 2 +- mysql-test/r/skip_name_resolve.result | 1 - mysql-test/r/sp-threads.result | 1 - mysql-test/r/sp_notembedded.result | 2 - mysql-test/r/status.result | 10 +- mysql-test/t/events_stress.test | 4 +- sql/Makefile.am | 6 +- sql/event_data_objects.cc | 47 ++- sql/event_data_objects.h | 33 +- sql/event_db_repository.cc | 145 +------ sql/event_queue.cc | 169 +++++++-- sql/event_queue.h | 30 +- sql/event_scheduler.cc | 302 +++------------ sql/event_scheduler.h | 31 +- sql/event_scheduler_ng.cc | 686 ++++++++++++++++++++++++++++++++++ sql/event_scheduler_ng.h | 121 ++++++ sql/events.cc | 78 ++-- sql/events.h | 13 + sql/mysqld.cc | 2 +- sql/set_var.cc | 26 +- sql/share/errmsg.txt | 2 +- sql/sql_show.cc | 2 +- 24 files changed, 1198 insertions(+), 531 deletions(-) create mode 100644 sql/event_scheduler_ng.cc create mode 100644 sql/event_scheduler_ng.h diff --git a/mysql-test/r/events.result b/mysql-test/r/events.result index 473acbd5984..a6d385e17ef 100644 --- a/mysql-test/r/events.result +++ b/mysql-test/r/events.result @@ -323,7 +323,6 @@ root@localhost закачка events_test "Should be only 1 process" select /*1*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; user host db command state info -event_scheduler localhost NULL Connect Suspended NULL select release_lock("test_lock1"); release_lock("test_lock1") 1 @@ -343,7 +342,7 @@ create event закачка on schedule every 10 hour do select get_lock("test_l "Should have only 2 processes: the scheduler and the locked event" select /*2*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; user host db command state info -event_scheduler localhost NULL Connect Sleeping NULL +event_scheduler localhost NULL Connect Waiting for next activation NULL root localhost events_test Connect User lock select get_lock("test_lock2", 20) "Release the mutex, the event worker should finish." "Release the mutex, the event worker should finish." @@ -359,13 +358,12 @@ create event закачка21 on schedule every 10 hour do select get_lock("test "Should have only 3 processes: the scheduler, our conn and the locked event" select /*3*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; user host db command state info -event_scheduler localhost NULL Connect Sleeping NULL +event_scheduler localhost NULL Connect Waiting for next activation NULL root localhost events_test Connect User lock select get_lock("test_lock2_1", 20) set global event_scheduler=2; "Should have only our process now:" select /*4*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; user host db command state info -event_scheduler localhost NULL Connect Suspended NULL root localhost events_test Connect User lock select get_lock("test_lock2_1", 20) drop event закачка21; create table t_16 (s1 int); diff --git a/mysql-test/r/events_bugs.result b/mysql-test/r/events_bugs.result index afd2a439fac..416312438b4 100644 --- a/mysql-test/r/events_bugs.result +++ b/mysql-test/r/events_bugs.result @@ -41,7 +41,7 @@ end| "Now if everything is fine the event has compiled and is locked select /*1*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; user host db command state info -event_scheduler localhost NULL Connect Sleeping NULL +event_scheduler localhost NULL Connect Waiting for next activation NULL root localhost events_test Connect User lock select get_lock('test_bug16407', 60) select release_lock('test_bug16407'); release_lock('test_bug16407') @@ -94,7 +94,7 @@ get_lock('ee_16407_2', 60) set global event_scheduler= 1; select /*2*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; user host db command state info -event_scheduler localhost NULL Connect Sleeping NULL +event_scheduler localhost NULL Connect Waiting for next activation NULL root localhost events_test Connect User lock select get_lock('ee_16407_2', 60) /*ee_16407_2*/ root localhost events_test Connect User lock select get_lock('ee_16407_2', 60) /*ee_16407_3*/ root localhost events_test Connect User lock select get_lock('ee_16407_2', 60) /*ee_16407_4*/ @@ -103,7 +103,7 @@ release_lock('ee_16407_2') 1 select /*3*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; user host db command state info -event_scheduler localhost NULL Connect Sleeping NULL +event_scheduler localhost NULL Connect Waiting for next activation NULL set global event_scheduler= 2; select * from events_smode_test order by ev_name, a; ev_name a @@ -142,7 +142,7 @@ set global event_scheduler= 1; "Should have 2 locked processes" select /*4*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; user host db command state info -event_scheduler localhost NULL Connect Sleeping NULL +event_scheduler localhost NULL Connect Waiting for next activation NULL root localhost events_test Connect User lock select get_lock('ee_16407_5', 60) /*ee_16407_5*/ root localhost events_test Connect User lock select get_lock('ee_16407_5', 60) /*ee_16407_6*/ select release_lock('ee_16407_5'); @@ -151,7 +151,7 @@ release_lock('ee_16407_5') "Should have 0 processes locked" select /*5*/ user, host, db, command, state, info from information_schema.processlist where info is null or info not like '%processlist%' order by info; user host db command state info -event_scheduler localhost NULL Connect Sleeping NULL +event_scheduler localhost NULL Connect Waiting for next activation NULL select * from events_smode_test order by ev_name, a; ev_name a ee_16407_6 2004-02-29 diff --git a/mysql-test/r/ps_1general.result b/mysql-test/r/ps_1general.result index 1a1d6432411..d0b773dfe34 100644 --- a/mysql-test/r/ps_1general.result +++ b/mysql-test/r/ps_1general.result @@ -299,7 +299,7 @@ t9 MyISAM 10 Dynamic 2 216 432 # 2048 0 NULL # # # latin1_swedish_ci NULL prepare stmt4 from ' show status like ''Threads_running'' '; execute stmt4; Variable_name Value -Threads_running 2 +Threads_running 1 prepare stmt4 from ' show variables like ''sql_mode'' '; execute stmt4; Variable_name Value diff --git a/mysql-test/r/skip_name_resolve.result b/mysql-test/r/skip_name_resolve.result index 855876825ad..8ef52e75238 100644 --- a/mysql-test/r/skip_name_resolve.result +++ b/mysql-test/r/skip_name_resolve.result @@ -10,6 +10,5 @@ user() # show processlist; Id User Host db Command Time State Info - event_scheduler NULL