diff options
author | unknown <dlenev@mockturtle.local> | 2006-09-14 23:53:35 +0400 |
---|---|---|
committer | unknown <dlenev@mockturtle.local> | 2006-09-14 23:53:35 +0400 |
commit | f1e6d23944e48d8293d38acaccb91a423b1e6ac8 (patch) | |
tree | a407ae8ad0f336c756ce35ff39da6ccfac735913 /sql | |
parent | 6a490444a615b9fd8ee057f71c215175cd6eb4cc (diff) | |
parent | 2d8bd8767527864e4b9b2328e3d9b8d4bf676e4a (diff) | |
download | mariadb-git-f1e6d23944e48d8293d38acaccb91a423b1e6ac8.tar.gz |
Merge bk-internal.mysql.com:/home/bk/mysql-5.1
into mockturtle.local:/home/dlenev/src/mysql-5.1-rt-merge
BitKeeper/etc/ignore:
auto-union
client/mysqltest.c:
Auto merged
libmysqld/Makefile.am:
Auto merged
mysql-test/mysql-test-run.pl:
Auto merged
sql/CMakeLists.txt:
Auto merged
sql/Makefile.am:
Auto merged
sql/mysqld.cc:
Auto merged
sql/set_var.cc:
Auto merged
sql/sql_class.cc:
Auto merged
sql/sql_class.h:
Auto merged
sql/sql_lex.cc:
Auto merged
sql/sql_lex.h:
Auto merged
sql/share/errmsg.txt:
Auto merged
sql/sql_parse.cc:
Auto merged
sql/sql_show.cc:
Auto merged
sql/sql_yacc.yy:
Auto merged
Diffstat (limited to 'sql')
-rw-r--r-- | sql/CMakeLists.txt | 3 | ||||
-rw-r--r-- | sql/Makefile.am | 8 | ||||
-rw-r--r-- | sql/event_data_objects.cc (renamed from sql/event_timed.cc) | 1356 | ||||
-rw-r--r-- | sql/event_data_objects.h | 285 | ||||
-rw-r--r-- | sql/event_db_repository.cc | 979 | ||||
-rw-r--r-- | sql/event_db_repository.h | 102 | ||||
-rw-r--r-- | sql/event_queue.cc | 956 | ||||
-rw-r--r-- | sql/event_queue.h | 121 | ||||
-rw-r--r-- | sql/event_scheduler.cc | 2449 | ||||
-rw-r--r-- | sql/event_scheduler.h | 212 | ||||
-rw-r--r-- | sql/event_timed.h | 217 | ||||
-rw-r--r-- | sql/events.cc | 1368 | ||||
-rw-r--r-- | sql/events.h | 147 | ||||
-rw-r--r-- | sql/events_priv.h | 79 | ||||
-rw-r--r-- | sql/lex.h | 1 | ||||
-rw-r--r-- | sql/mysqld.cc | 43 | ||||
-rw-r--r-- | sql/set_var.cc | 58 | ||||
-rw-r--r-- | sql/set_var.h | 6 | ||||
-rw-r--r-- | sql/share/errmsg.txt | 4 | ||||
-rw-r--r-- | sql/sp_head.cc | 3 | ||||
-rw-r--r-- | sql/sql_class.cc | 1 | ||||
-rw-r--r-- | sql/sql_db.cc | 2 | ||||
-rw-r--r-- | sql/sql_lex.cc | 6 | ||||
-rw-r--r-- | sql/sql_lex.h | 14 | ||||
-rw-r--r-- | sql/sql_parse.cc | 118 | ||||
-rw-r--r-- | sql/sql_show.cc | 185 | ||||
-rw-r--r-- | sql/sql_show.h | 2 | ||||
-rw-r--r-- | sql/sql_test.cc | 4 | ||||
-rw-r--r-- | sql/sql_yacc.yy | 420 | ||||
-rw-r--r-- | sql/table.cc | 44 | ||||
-rw-r--r-- | sql/table.h | 6 |
31 files changed, 4453 insertions, 4746 deletions
diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index a089278448c..8d97edd4652 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -50,7 +50,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 rpl_injector.cc sql_locale.cc ${PROJECT_SOURCE_DIR}/sql/sql_yacc.cc diff --git a/sql/Makefile.am b/sql/Makefile.am index d1ebea45d0c..e472e6ae8f0 100644 --- a/sql/Makefile.am +++ b/sql/Makefile.am @@ -63,8 +63,9 @@ 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_plugin.h authors.h sql_partition.h event_timed.h \ + sql_array.h sql_cursor.h events.h \ + event_db_repository.h event_queue.h \ + sql_plugin.h authors.h sql_partition.h event_data_objects.h \ partition_info.h partition_element.h event_scheduler.h \ contributors.h mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \ @@ -101,7 +102,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 event_data_objects.cc \ + event_queue.cc event_db_repository.cc events.cc \ sql_plugin.cc sql_binlog.cc \ sql_builtin.cc sql_tablespace.cc partition_info.cc diff --git a/sql/event_timed.cc b/sql/event_data_objects.cc index 98369e0e055..6f865a2bcac 100644 --- a/sql/event_timed.cc +++ b/sql/event_data_objects.cc @@ -16,111 +16,63 @@ #define MYSQL_LEX 1 #include "mysql_priv.h" -#include "events_priv.h" #include "events.h" -#include "event_timed.h" +#include "event_data_objects.h" +#include "event_db_repository.h" #include "sp_head.h" -/* - Constructor - - SYNOPSIS - Event_timed::Event_timed() -*/ +#define EVEX_MAX_INTERVAL_VALUE 1000000000L -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(); -} +static bool +event_change_security_context(THD *thd, LEX_STRING user, LEX_STRING host, + LEX_STRING db, Security_context *backup); +static void +event_restore_security_context(THD *thd, Security_context *backup); /* - Destructor + Returns a new instance 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 + Event_parse_data::new_instance() - SYNOPSIS - Event_timed::is_running() + RETURN VALUE + Address or NULL in case of error + + NOTE + Created on THD's mem_root */ -bool -Event_timed::is_running() +Event_parse_data * +Event_parse_data::new_instance(THD *thd) { - bool ret; - - VOID(pthread_mutex_lock(&this->LOCK_running)); - ret= running; - VOID(pthread_mutex_unlock(&this->LOCK_running)); - - return ret; + return new (thd->mem_root) Event_parse_data; } /* - Init all member variables + Constructor SYNOPSIS - Event_timed::init() + Event_parse_data::Event_parse_data() */ -void -Event_timed::init() +Event_parse_data::Event_parse_data() + :on_completion(ON_COMPLETION_DROP), status(ENABLED), + item_starts(NULL), item_ends(NULL), item_execute_at(NULL), + starts_null(TRUE), ends_null(TRUE), execute_at_null(TRUE), + item_expression(NULL), expression(0) { - DBUG_ENTER("Event_timed::init"); - - dbname.str= name.str= body.str= comment.str= 0; - dbname.length= name.length= body.length= comment.length= 0; + DBUG_ENTER("Event_parse_data::Event_parse_data"); + /* 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; + body.str= comment.str= NULL; + body.length= comment.length= 0; DBUG_VOID_RETURN; } @@ -130,30 +82,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 */ dbname.length= spn->m_db.length; - dbname.str= strmake_root(root, spn->m_db.str, spn->m_db.length); + dbname.str= thd->strmake(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); + name.str= thd->strmake(spn->m_name.str, spn->m_name.length); if (spn->m_qname.length == 0) spn->init_qname(thd); - DBUG_PRINT("dbname", ("len=%d db=%s",dbname.length, dbname.str)); - DBUG_PRINT("name", ("len=%d name=%s",name.length, name.str)); - DBUG_VOID_RETURN; } @@ -162,22 +109,22 @@ 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 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) +Event_parse_data::init_body(THD *thd) { - DBUG_ENTER("Event_timed::init_body"); - DBUG_PRINT("info", ("body=[%s] body_begin=0x%ld end=0x%ld", body_begin, + DBUG_ENTER("Event_parse_data::init_body"); + DBUG_PRINT("info", ("body=[%s] body_begin=0x%lx end=0x%lx", body_begin, body_begin, thd->lex->ptr)); body.length= thd->lex->ptr - body_begin; @@ -195,7 +142,7 @@ Event_timed::init_body(THD *thd) continue; } - /* + /* consume closing comments This is arguably wrong, but it's the best we have until the parser is @@ -212,7 +159,7 @@ Event_timed::init_body(THD *thd) */ if ((*(body_end - 1) == '*') && (*body_end == '/')) { - DBUG_PRINT("info", ("consumend one '*" "/' comment in the query '%s'", + DBUG_PRINT("info", ("consumend one '*" "/' comment in the query '%s'", body_begin)); body.length-= 2; body_end-= 2; @@ -228,54 +175,58 @@ 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; } /* - Set time for execution for one time events. + Sets time for execution for one-time event. SYNOPSIS - Event_timed::init_execute_at() - expr when (datetime) + Event_parse_data::init_execute_at() + thd Thread 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 + 0 OK + ER_WRONG_VALUE Wrong value for execute at (reported) */ int -Event_timed::init_execute_at(THD *thd, Item *expr) +Event_parse_data::init_execute_at(THD *thd) { 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); + DBUG_ENTER("Event_parse_data::init_execute_at"); + + if (!item_execute_at) + DBUG_RETURN(0); + + if (item_execute_at->fix_fields(thd, &item_execute_at)) + goto wrong_value; /* 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, + 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 ((not_used= item_execute_at->get_date(<ime, TIME_NO_ZERO_DATE))) + goto wrong_value; if (TIME_to_ulonglong_datetime(<ime) < TIME_to_ulonglong_datetime(&time_tmp)) - DBUG_RETURN(EVEX_BAD_PARAMS); + { + my_error(ER_EVENT_EXEC_TIME_IN_THE_PAST, MYF(0)); + DBUG_RETURN(ER_WRONG_VALUE); + } /* This may result in a 1970-01-01 date if ltime is > 2037-xx-xx. @@ -287,48 +238,64 @@ Event_timed::init_execute_at(THD *thd, Item *expr) if (!t) { DBUG_PRINT("error", ("Execute AT after year 2037")); - DBUG_RETURN(ER_WRONG_VALUE); + goto wrong_value; } execute_at_null= FALSE; execute_at= ltime; DBUG_RETURN(0); + +wrong_value: + report_bad_value("AT", item_execute_at); + DBUG_RETURN(ER_WRONG_VALUE); } /* - Set time for execution for transient events. + Sets time for execution of multi-time event.s SYNOPSIS - Event_timed::init_interval() - expr how much? - new_interval what is the interval + Event_parse_data::init_interval() + thd Thread RETURN VALUE - 0 OK - EVEX_PARSE_ERROR fix_fields failed - EVEX_BAD_PARAMS Interval is not positive - EVEX_MICROSECOND_UNSUP Microseconds are not supported. + 0 OK + EVEX_BAD_PARAMS Interval is not positive or MICROSECOND (reported) + ER_WRONG_VALUE Wrong value for interval (reported) */ int -Event_timed::init_interval(THD *thd, Item *expr, interval_type new_interval) +Event_parse_data::init_interval(THD *thd) { String value; INTERVAL interval_tmp; - DBUG_ENTER("Event_timed::init_interval"); + DBUG_ENTER("Event_parse_data::init_interval"); + if (!item_expression) + DBUG_RETURN(0); - if (expr->fix_fields(thd, &expr)) - DBUG_RETURN(EVEX_PARSE_ERROR); + switch (interval) { + case INTERVAL_MINUTE_MICROSECOND: + case INTERVAL_HOUR_MICROSECOND: + case INTERVAL_DAY_MICROSECOND: + case INTERVAL_SECOND_MICROSECOND: + case INTERVAL_MICROSECOND: + my_error(ER_NOT_SUPPORTED_YET, MYF(0), "MICROSECOND"); + DBUG_RETURN(EVEX_BAD_PARAMS); + default: + break; + } + + if (item_expression->fix_fields(thd, &item_expression)) + goto wrong_value; 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); + if (get_interval_value(item_expression, interval, &value, &interval_tmp)) + goto wrong_value; expression= 0; - switch (new_interval) { + switch (interval) { case INTERVAL_YEAR: expression= interval_tmp.year; break; @@ -366,44 +333,37 @@ Event_timed::init_interval(THD *thd, Item *expr, interval_type new_interval) 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); + default: + ;/* these are the microsec stuff */ } if (interval_tmp.neg || expression > EVEX_MAX_INTERVAL_VALUE) + { + my_error(ER_EVENT_INTERVAL_NOT_POSITIVE_OR_TOO_BIG, MYF(0)); DBUG_RETURN(EVEX_BAD_PARAMS); + } - interval= new_interval; DBUG_RETURN(0); + +wrong_value: + report_bad_value("INTERVAL", item_execute_at); + DBUG_RETURN(ER_WRONG_VALUE); } /* - Set activation time. + Sets STARTS. SYNOPSIS - Event_timed::init_starts() - expr how much? - interval what is the interval + Event_parse_data::init_starts() + expr how much? NOTES Note that activation time is not execution time. @@ -414,25 +374,26 @@ Event_timed::init_interval(THD *thd, Item *expr, interval_type new_interval) same time. RETURN VALUE - 0 OK - EVEX_PARSE_ERROR fix_fields failed - EVEX_BAD_PARAMS starts before now + 0 OK + ER_WRONG_VALUE Starts before now */ int -Event_timed::init_starts(THD *thd, Item *new_starts) +Event_parse_data::init_starts(THD *thd) { 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 (!item_starts) + DBUG_RETURN(0); - if (new_starts->fix_fields(thd, &new_starts)) - DBUG_RETURN(EVEX_PARSE_ERROR); + if (item_starts->fix_fields(thd, &item_starts)) + goto wrong_value; - if ((not_used= new_starts->get_date(<ime, TIME_NO_ZERO_DATE))) - DBUG_RETURN(EVEX_BAD_PARAMS); + if ((not_used= item_starts->get_date(<ime, TIME_NO_ZERO_DATE))) + goto wrong_value; /* Let's check whether time is in the past */ thd->variables.time_zone->gmt_sec_to_TIME(&time_tmp, @@ -442,7 +403,7 @@ Event_timed::init_starts(THD *thd, Item *new_starts) 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); + goto wrong_value; /* This may result in a 1970-01-01 date if ltime is > 2037-xx-xx. @@ -452,24 +413,24 @@ Event_timed::init_starts(THD *thd, Item *new_starts) */ 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); - } + goto wrong_value; starts= ltime; starts_null= FALSE; DBUG_RETURN(0); + +wrong_value: + report_bad_value("STARTS", item_starts); + DBUG_RETURN(ER_WRONG_VALUE); } /* - Set deactivation time. + Sets ENDS (deactivation time). SYNOPSIS - Event_timed::init_ends() + Event_parse_data::init_ends() thd THD - new_ends when? NOTES Note that activation time is not execution time. @@ -481,26 +442,26 @@ Event_timed::init_starts(THD *thd, Item *new_starts) 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 + EVEX_BAD_PARAMS Error (reported) */ int -Event_timed::init_ends(THD *thd, Item *new_ends) +Event_parse_data::init_ends(THD *thd) { 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 (!item_ends) + DBUG_RETURN(0); - if (new_ends->fix_fields(thd, &new_ends)) - DBUG_RETURN(EVEX_PARSE_ERROR); + if (item_ends->fix_fields(thd, &item_ends)) + goto error_bad_params; DBUG_PRINT("info", ("convert to TIME")); - if ((not_used= new_ends->get_date(<ime, TIME_NO_ZERO_DATE))) - DBUG_RETURN(EVEX_BAD_PARAMS); + if ((not_used= item_ends->get_date(<ime, TIME_NO_ZERO_DATE))) + goto error_bad_params; /* This may result in a 1970-01-01 date if ltime is > 2037-xx-xx. @@ -511,15 +472,12 @@ Event_timed::init_ends(THD *thd, Item *new_ends) 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); - } + goto error_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); + goto error_bad_params; /* The parser forces starts to be provided but one day STARTS could be @@ -529,32 +487,65 @@ Event_timed::init_ends(THD *thd, Item *new_ends) 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); + goto error_bad_params; ends= ltime; ends_null= FALSE; DBUG_RETURN(0); + +error_bad_params: + my_error(ER_EVENT_ENDS_BEFORE_STARTS, MYF(0)); + DBUG_RETURN(EVEX_BAD_PARAMS); } /* - Sets comment. + Prints an error message about invalid value. Internally used + during input data verification SYNOPSIS - Event_timed::init_comment() - thd THD - used for memory allocation - comment the string. + Event_parse_data::report_bad_value() + item_name The name of the parameter + bad_item The parameter */ void -Event_timed::init_comment(THD *thd, LEX_STRING *set_comment) +Event_parse_data::report_bad_value(const char *item_name, Item *bad_item) { - DBUG_ENTER("Event_timed::init_comment"); + 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), item_name, str2? str2->c_ptr_safe():"NULL"); +} - comment.str= strmake_root(thd->mem_root, set_comment->str, - comment.length= set_comment->length); - DBUG_VOID_RETURN; +/* + Checks for validity the data gathered during the parsing phase. + + SYNOPSIS + Event_parse_data::check_parse_data() + thd Thread + + RETURN VALUE + FALSE OK + TRUE Error (reported) +*/ + +bool +Event_parse_data::check_parse_data(THD *thd) +{ + bool ret; + DBUG_ENTER("Event_parse_data::check_parse_data"); + DBUG_PRINT("info", ("execute_at=0x%lx expr=0x%lx starts=0x%lx ends=0x%lx", + item_execute_at, item_expression, item_starts, item_ends)); + + init_name(thd, identifier); + + init_definer(thd); + + ret= init_execute_at(thd) || init_interval(thd) || init_starts(thd) || + init_ends(thd); + DBUG_RETURN(ret); } @@ -562,51 +553,235 @@ Event_timed::init_comment(THD *thd, LEX_STRING *set_comment) Inits definer (definer_user and definer_host) during parsing. SYNOPSIS - Event_timed::init_definer() - - RETURN VALUE - 0 OK + Event_parse_data::init_definer() + thd Thread */ -int -Event_timed::init_definer(THD *thd) +void +Event_parse_data::init_definer(THD *thd) { - DBUG_ENTER("Event_timed::init_definer"); + 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.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); + 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.length + definer_host.length + 1; - definer.str= alloc_root(thd->mem_root, definer.length + 1); + 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, definer_user.str, definer_user.length); - definer.str[definer_user.length]= '@'; + 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.length + 1, definer_host.str, - definer_host.length); + memcpy(definer.str + definer_user_len + 1, thd->security_ctx->priv_host, + definer_host_len); definer.str[definer.length]= '\0'; - DBUG_PRINT("info",("definer initted")); + DBUG_PRINT("info",("definer [%s] initted", definer.str)); - DBUG_RETURN(0); + DBUG_VOID_RETURN; } /* - Loads an event from a row from mysql.event + Constructor SYNOPSIS - Event_timed::load_from_row(MEM_ROOT *mem_root, TABLE *table) + Event_basic::Event_basic() +*/ + +Event_basic::Event_basic() +{ + DBUG_ENTER("Event_basic::Event_basic"); + /* init memory root */ + init_alloc_root(&mem_root, 256, 512); + dbname.str= name.str= NULL; + dbname.length= name.length= 0; + DBUG_VOID_RETURN; +} + + +/* + Destructor + + SYNOPSIS + Event_basic::Event_basic() +*/ + +Event_basic::~Event_basic() +{ + DBUG_ENTER("Event_basic::~Event_basic"); + free_root(&mem_root, MYF(0)); + DBUG_VOID_RETURN; +} + + +/* + Short function to load a char column into a LEX_STRING + + SYNOPSIS + Event_basic::load_string_field() + field_name The field( enum_events_table_field is not actually used + because it's unknown in event_data_objects.h) + fields The Field array + field_value The value +*/ + +bool +Event_basic::load_string_fields(Field **fields, ...) +{ + bool ret= FALSE; + va_list args; + enum enum_events_table_field field_name; + LEX_STRING *field_value; + + DBUG_ENTER("Event_basic::load_string_fields"); + + va_start(args, fields); + field_name= (enum enum_events_table_field) va_arg(args, int); + while (field_name != ET_FIELD_COUNT) + { + field_value= va_arg(args, LEX_STRING *); + if ((field_value->str= get_field(&mem_root, fields[field_name])) == NullS) + { + ret= TRUE; + break; + } + field_value->length= strlen(field_value->str); + + field_name= (enum enum_events_table_field) va_arg(args, int); + } + va_end(args); + + DBUG_RETURN(ret); +} + + +/* + Constructor + + SYNOPSIS + Event_queue_element::Event_queue_element() +*/ + +Event_queue_element::Event_queue_element(): + status_changed(FALSE), last_executed_changed(FALSE), + on_completion(ON_COMPLETION_DROP), status(ENABLED), + expression(0), dropped(FALSE), execution_count(0) +{ + DBUG_ENTER("Event_queue_element::Event_queue_element"); + + 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; + + DBUG_VOID_RETURN; +} + + +/* + Destructor + + SYNOPSIS + Event_queue_element::Event_queue_element() +*/ +Event_queue_element::~Event_queue_element() +{ +} + + +/* + Constructor + + SYNOPSIS + Event_timed::Event_timed() +*/ + +Event_timed::Event_timed(): + created(0), modified(0), sql_mode(0) +{ + DBUG_ENTER("Event_timed::Event_timed"); + init(); + DBUG_VOID_RETURN; +} + + +/* + Destructor + + SYNOPSIS + Event_timed::~Event_timed() +*/ + +Event_timed::~Event_timed() +{ +} + + +/* + Constructor + + SYNOPSIS + Event_job_data::Event_job_data() +*/ + +Event_job_data::Event_job_data() + :thd(NULL), sphead(NULL), sql_mode(0) +{ +} + + +/* + Destructor + + SYNOPSIS + Event_timed::~Event_timed() +*/ + +Event_job_data::~Event_job_data() +{ + DBUG_ENTER("Event_job_data::~Event_job_data"); + delete sphead; + sphead= NULL; + DBUG_VOID_RETURN; +} + + +/* + Init all member variables + + SYNOPSIS + Event_timed::init() +*/ + +void +Event_timed::init() +{ + DBUG_ENTER("Event_timed::init"); + + definer_user.str= definer_host.str= body.str= comment.str= NULL; + definer_user.length= definer_host.length= body.length= comment.length= 0; + + sql_mode= 0; + + DBUG_VOID_RETURN; +} + + +/* + Loads an event's body from a row from mysql.event + + SYNOPSIS + Event_job_data::load_from_row(MEM_ROOT *mem_root, TABLE *table) RETURN VALUE 0 OK @@ -619,124 +794,185 @@ Event_timed::init_definer(THD *thd) */ int -Event_timed::load_from_row(MEM_ROOT *mem_root, TABLE *table) +Event_job_data::load_from_row(TABLE *table) { char *ptr; - Event_timed *et; uint len; - bool res1, res2; - - DBUG_ENTER("Event_timed::load_from_row"); + DBUG_ENTER("Event_job_data::load_from_row"); if (!table) goto error; - 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) - goto error; + load_string_fields(table->field, ET_FIELD_DB, &dbname, ET_FIELD_NAME, &name, + ET_FIELD_BODY, &body, ET_FIELD_DEFINER, &definer, + ET_FIELD_COUNT); - et->dbname.length= strlen(et->dbname.str); + ptr= strchr(definer.str, '@'); - if ((et->name.str= get_field(mem_root, - table->field[Events::FIELD_NAME])) == NULL) - goto error; + if (! ptr) + ptr= definer.str; - et->name.length= strlen(et->name.str); + len= ptr - definer.str; + definer_user.str= strmake_root(&mem_root, definer.str, len); + definer_user.length= len; + len= definer.length - len - 1; + /* 1:because of @ */ + definer_host.str= strmake_root(&mem_root, ptr + 1, len); + definer_host.length= len; - if ((et->body.str= get_field(mem_root, - table->field[Events::FIELD_BODY])) == NULL) - goto error; + sql_mode= (ulong) table->field[ET_FIELD_SQL_MODE]->val_int(); + + DBUG_RETURN(0); +error: + DBUG_RETURN(EVEX_GET_FIELD_FAILED); +} + + +/* + Loads an event from a row from mysql.event - et->body.length= strlen(et->body.str); + SYNOPSIS + Event_queue_element::load_from_row(MEM_ROOT *mem_root, TABLE *table) + + RETURN VALUE + 0 OK + EVEX_GET_FIELD_FAILED Error - if ((et->definer.str= get_field(mem_root, - table->field[Events::FIELD_DEFINER])) == NullS) + 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_queue_element::load_from_row(TABLE *table) +{ + char *ptr; + bool res1, res2; + + DBUG_ENTER("Event_queue_element::load_from_row"); + + if (!table) goto error; - et->definer.length= strlen(et->definer.str); - ptr= strchr(et->definer.str, '@'); + if (table->s->fields != ET_FIELD_COUNT) + goto error; - if (! ptr) - ptr= et->definer.str; + load_string_fields(table->field, ET_FIELD_DB, &dbname, ET_FIELD_NAME, &name, + ET_FIELD_DEFINER, &definer, ET_FIELD_COUNT); - len= ptr - et->definer.str; + starts_null= table->field[ET_FIELD_STARTS]->is_null(); + res1= table->field[ET_FIELD_STARTS]->get_date(&starts, TIME_NO_ZERO_DATE); - 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); + ends_null= table->field[ET_FIELD_ENDS]->is_null(); + res2= table->field[ET_FIELD_ENDS]->get_date(&ends, 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(); + if (!table->field[ET_FIELD_INTERVAL_EXPR]->is_null()) + expression= table->field[ET_FIELD_INTERVAL_EXPR]->val_int(); else - et->expression= 0; + 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. + If res1 and res2 are TRUE then both fields are empty. + Hence, if ET_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)) + execute_at_null= table->field[ET_FIELD_EXECUTE_AT]->is_null(); + DBUG_ASSERT(!(starts_null && ends_null && !expression && execute_at_null)); + if (!expression && + table->field[ET_FIELD_EXECUTE_AT]->get_date(&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); + if (!table->field[ET_FIELD_TRANSIENT_INTERVAL]->is_null()) + interval= (interval_type) ((ulonglong) + table->field[ET_FIELD_TRANSIENT_INTERVAL]->val_int() - 1); else - et->interval= (interval_type) 0; + interval= (interval_type) 0; + + table->field[ET_FIELD_LAST_EXECUTED]->get_date(&last_executed, + TIME_NO_ZERO_DATE); + last_executed_changed= FALSE; - 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); + if ((ptr= get_field(&mem_root, table->field[ET_FIELD_STATUS])) == NullS) + goto error; - last_executed_changed= false; + DBUG_PRINT("load_from_row", ("Event [%s] is [%s]", name.str, ptr)); + status= (ptr[0]=='E'? Event_queue_element::ENABLED: + Event_queue_element::DISABLED); /* 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_ON_COMPLETION])) == 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); + on_completion= (ptr[0]=='D'? Event_queue_element::ON_COMPLETION_DROP: + Event_queue_element::ON_COMPLETION_PRESERVE); - /* 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) + DBUG_RETURN(0); +error: + DBUG_RETURN(EVEX_GET_FIELD_FAILED); +} + + +/* + 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(TABLE *table) +{ + char *ptr; + uint len; + + DBUG_ENTER("Event_timed::load_from_row"); + + if (Event_queue_element::load_from_row(table)) goto error; - et->on_completion= (ptr[0]=='D'? Event_timed::ON_COMPLETION_DROP: - Event_timed::ON_COMPLETION_PRESERVE); + load_string_fields(table->field, ET_FIELD_BODY, &body, ET_FIELD_COUNT); - et->comment.str= get_field(mem_root, table->field[Events::FIELD_COMMENT]); - if (et->comment.str != NullS) - et->comment.length= strlen(et->comment.str); + ptr= strchr(definer.str, '@'); + + if (! ptr) + ptr= definer.str; + + len= ptr - definer.str; + definer_user.str= strmake_root(&mem_root, definer.str, len); + definer_user.length= len; + len= definer.length - len - 1; + /* 1:because of @ */ + definer_host.str= strmake_root(&mem_root, ptr + 1, len); + definer_host.length= len; + + created= table->field[ET_FIELD_CREATED]->val_int(); + modified= table->field[ET_FIELD_MODIFIED]->val_int(); + + comment.str= get_field(&mem_root, table->field[ET_FIELD_COMMENT]); + if (comment.str != NullS) + comment.length= strlen(comment.str); else - et->comment.length= 0; - + comment.length= 0; - et->sql_mode= (ulong) table->field[Events::FIELD_SQL_MODE]->val_int(); + sql_mode= (ulong) table->field[ET_FIELD_SQL_MODE]->val_int(); DBUG_RETURN(0); error: @@ -755,7 +991,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 @@ -835,7 +1071,7 @@ bool get_next_time(TIME *next, TIME *start, TIME *time_now, TIME *last_exec, { longlong seconds_diff; long microsec_diff; - + if (calc_time_diff(time_now, start, 1, &seconds_diff, µsec_diff)) { DBUG_PRINT("error", ("negative difference")); @@ -877,20 +1113,22 @@ bool get_next_time(TIME *next, TIME *start, TIME *time_now, TIME *last_exec, 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. + 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) + 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) { @@ -914,7 +1152,7 @@ done: Computes next execution time. SYNOPSIS - Event_timed::compute_next_execution_time() + Event_queue_element::compute_next_execution_time() RETURN VALUE FALSE OK @@ -926,18 +1164,18 @@ done: */ bool -Event_timed::compute_next_execution_time() +Event_queue_element::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", + DBUG_ENTER("Event_queue_element::compute_next_execution_time"); + DBUG_PRINT("enter", ("starts=%llu ends=%llu last_executed=%llu this=0x%lx", TIME_to_ulonglong_datetime(&starts), TIME_to_ulonglong_datetime(&ends), - TIME_to_ulonglong_datetime(&last_executed))); + TIME_to_ulonglong_datetime(&last_executed), this)); - if (status == Event_timed::DISABLED) + if (status == Event_queue_element::DISABLED) { DBUG_PRINT("compute_next_execution_time", ("Event %s is DISABLED", name.str)); @@ -951,11 +1189,11 @@ Event_timed::compute_next_execution_time() { 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); + dropped= (on_completion == Event_queue_element::ON_COMPLETION_DROP); DBUG_PRINT("info",("One-time event will be dropped=%d.", dropped)); - status= Event_timed::DISABLED; - status_changed= true; + status= Event_queue_element::DISABLED; + status_changed= TRUE; } goto ret; } @@ -971,11 +1209,11 @@ Event_timed::compute_next_execution_time() /* 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; + if (on_completion == Event_queue_element::ON_COMPLETION_DROP) + dropped= TRUE; DBUG_PRINT("info", ("Dropped=%d", dropped)); - status= Event_timed::DISABLED; - status_changed= true; + status= Event_queue_element::DISABLED; + status_changed= TRUE; goto ret; } @@ -1037,10 +1275,10 @@ Event_timed::compute_next_execution_time() /* 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; + if (on_completion == Event_queue_element::ON_COMPLETION_DROP) + dropped= TRUE; + status= Event_queue_element::DISABLED; + status_changed= TRUE; } else { @@ -1051,7 +1289,7 @@ Event_timed::compute_next_execution_time() } goto ret; } - else if (starts_null && ends_null) + 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")); @@ -1095,7 +1333,7 @@ Event_timed::compute_next_execution_time() { TIME next_exec; - if (get_next_time(&next_exec, &starts, &time_now, + if (get_next_time(&next_exec, &starts, &time_now, last_executed.year? &last_executed:&starts, expression, interval)) goto err; @@ -1130,10 +1368,10 @@ Event_timed::compute_next_execution_time() 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; + status= Event_queue_element::DISABLED; + status_changed= TRUE; + if (on_completion == Event_queue_element::ON_COMPLETION_DROP) + dropped= TRUE; } else { @@ -1147,11 +1385,12 @@ Event_timed::compute_next_execution_time() goto ret; } ret: - DBUG_PRINT("info", ("ret=0")); - DBUG_RETURN(false); + DBUG_PRINT("info", ("ret=0 execute_at=%llu", + TIME_to_ulonglong_datetime(&execute_at))); + DBUG_RETURN(FALSE); err: DBUG_PRINT("info", ("ret=1")); - DBUG_RETURN(true); + DBUG_RETURN(TRUE); } @@ -1160,12 +1399,12 @@ err: time according to thd->query_start(), so the THD's clock. SYNOPSIS - Event_timed::drop() + Event_queue_element::mark_last_executed() thd thread context */ void -Event_timed::mark_last_executed(THD *thd) +Event_queue_element::mark_last_executed(THD *thd) { TIME time_now; @@ -1173,7 +1412,9 @@ Event_timed::mark_last_executed(THD *thd) 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; + last_executed_changed= TRUE; + + execution_count++; } @@ -1181,7 +1422,7 @@ Event_timed::mark_last_executed(THD *thd) Drops the event SYNOPSIS - Event_timed::drop() + Event_queue_element::drop() thd thread context RETURN VALUE @@ -1194,12 +1435,12 @@ Event_timed::mark_last_executed(THD *thd) */ int -Event_timed::drop(THD *thd) +Event_queue_element::drop(THD *thd) { - uint tmp= 0; - DBUG_ENTER("Event_timed::drop"); + DBUG_ENTER("Event_queue_element::drop"); - DBUG_RETURN(db_drop_event(thd, this, false, &tmp)); + DBUG_RETURN(Events::get_instance()-> + drop_event(thd, dbname, name, FALSE, TRUE)); } @@ -1207,26 +1448,24 @@ Event_timed::drop(THD *thd) Saves status and last_executed_at to the disk if changed. SYNOPSIS - Event_timed::update_fields() + Event_queue_element::update_timing_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. + FALSE OK + TRUE Error while opening mysql.event for writing or during + write on disk */ bool -Event_timed::update_fields(THD *thd) +Event_queue_element::update_timing_fields(THD *thd) { TABLE *table; + Field **fields; Open_tables_state backup; - int ret; + int ret= FALSE; - DBUG_ENTER("Event_timed::update_time_fields"); + DBUG_ENTER("Event_queue_element::update_timing_fields"); DBUG_PRINT("enter", ("name: %*s", name.length, name.str)); @@ -1236,14 +1475,14 @@ 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; + ret= TRUE; goto done; } - - - if ((ret= evex_db_find_event_by_name(thd, dbname, name, table))) + fields= table->field; + if ((ret= Events::get_instance()->db_repository-> + find_named_event(thd, dbname, name, table))) goto done; store_record(table,record[1]); @@ -1252,20 +1491,20 @@ 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, + fields[ET_FIELD_LAST_EXECUTED]->set_notnull(); + fields[ET_FIELD_LAST_EXECUTED]->store_time(&last_executed, MYSQL_TIMESTAMP_DATETIME); - last_executed_changed= false; + 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; + fields[ET_FIELD_STATUS]->set_notnull(); + fields[ET_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; + if ((table->file->ha_update_row(table->record[1], table->record[0]))) + ret= TRUE; done: close_thread_tables(thd); @@ -1294,8 +1533,8 @@ 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); + char tmp_buf[2 * STRING_BUFFER_USUAL_SIZE]; + String expr_buf(tmp_buf, sizeof(tmp_buf), system_charset_info); expr_buf.length(0); DBUG_ENTER("get_create_event"); @@ -1308,10 +1547,9 @@ Event_timed::get_create_event(THD *thd, String *buf) 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(STRING_WITH_LEN(" ON SCHEDULE EVERY ")); buf->append(expr_buf); buf->append(' '); LEX_STRING *ival= &interval_type_to_name[interval]; @@ -1320,7 +1558,7 @@ Event_timed::get_create_event(THD *thd, String *buf) else { char dtime_buff[20*2+32];/* +32 to make my_snprintf_{8bit|ucs2} happy */ - buf->append(STRING_WITH_LEN("AT '")); + buf->append(STRING_WITH_LEN(" ON SCHEDULE AT '")); /* Pass the buffer and the second param tells fills the buffer and returns the number of chars to copy. @@ -1352,46 +1590,64 @@ Event_timed::get_create_event(THD *thd, String *buf) /* + Get SHOW CREATE EVENT as string + + SYNOPSIS + Event_job_data::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_job_data::get_fake_create_event(THD *thd, String *buf) +{ + DBUG_ENTER("Event_job_data::get_create_event"); + buf->append(STRING_WITH_LEN("CREATE EVENT anonymous ON SCHEDULE " + "EVERY 3337 HOUR DO ")); + buf->append(body.str, body.length); + + DBUG_RETURN(0); +} + + +/* Executes the event (the underlying sp_head object); SYNOPSIS - evex_fill_row() + Event_job_data::execute() 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) +Event_job_data::execute(THD *thd) { + Security_context save_ctx; /* 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)); + DBUG_ENTER("Event_job_data::execute"); + DBUG_PRINT("info", ("EXECUTING %s.%s", dbname.str, name.str)); - if (!sphead && (ret= compile(thd, mem_root))) + if ((ret= compile(thd, NULL))) goto done; + + event_change_security_context(thd, definer_user, definer_host, dbname, + &save_ctx); /* - 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::~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; @@ -1412,41 +1668,14 @@ Event_timed::execute(THD *thd, MEM_ROOT *mem_root) 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)); - + event_restore_security_context(thd, &save_ctx); 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)); + thd->end_statement(); + thd->cleanup_after_query(); - DBUG_RETURN(ret); -} + DBUG_PRINT("info", ("EXECUTED %s.%s ret=%d", dbname.str, name.str, 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; + DBUG_RETURN(ret); } @@ -1455,7 +1684,7 @@ Event_timed::free_sp() sp_head object held by the event SYNOPSIS - Event_timed::compile() + Event_job_data::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 @@ -1467,7 +1696,7 @@ Event_timed::free_sp() */ int -Event_timed::compile(THD *thd, MEM_ROOT *mem_root) +Event_job_data::compile(THD *thd, MEM_ROOT *mem_root) { int ret= 0; MEM_ROOT *tmp_mem_root= 0; @@ -1477,22 +1706,19 @@ Event_timed::compile(THD *thd, MEM_ROOT *mem_root) char *old_query; uint old_query_len; ulong old_sql_mode= thd->variables.sql_mode; - char create_buf[2048]; + char create_buf[15 * STRING_BUFFER_USUAL_SIZE]; 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; + Security_context save_ctx; - DBUG_ENTER("Event_timed::compile"); + DBUG_ENTER("Event_job_data::compile"); show_create.length(0); - switch (get_create_event(thd, &show_create)) { + switch (get_fake_create_event(thd, &show_create)) { case EVEX_MICROSECOND_UNSUP: - sql_print_error("Scheduler"); DBUG_RETURN(EVEX_MICROSECOND_UNSUP); case 0: break; @@ -1526,15 +1752,14 @@ Event_timed::compile(THD *thd, MEM_ROOT *mem_root) thd->db= dbname.str; thd->db_length= dbname.length; - thd->query= show_create.c_ptr(); + thd->query= show_create.c_ptr_safe(); 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); + event_change_security_context(thd, definer_user, definer_host, dbname, + &save_ctx); thd->lex= &lex; - lex_start(thd, (uchar*)thd->query, thd->query_length); - lex.et_compile_phase= TRUE; + mysql_init_query(thd, (uchar*) thd->query, thd->query_length); if (MYSQLparse((void *)thd) || thd->is_fatal_error) { DBUG_PRINT("error", ("error during compile or thd->is_fatal_error=%d", @@ -1543,33 +1768,28 @@ Event_timed::compile(THD *thd, MEM_ROOT *mem_root) 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", + sql_print_error("SCHEDULER: Error during compilation 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; - } + + lex.unit.cleanup(); + delete lex.sphead; + sphead= lex.sphead= NULL; 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= lex.sphead; 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); + event_restore_security_context(thd, &save_ctx); DBUG_PRINT("note", ("return old data on its place. set back NAMES")); thd->lex= old_lex; @@ -1591,276 +1811,104 @@ done: } -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() + event_basic_db_equal() + db Schema + et Compare et->dbname to `db` 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 + TRUE Equal + FALSE Not equal */ bool -event_timed_definer_equal(Event_timed *et, LEX_STRING *definer) +event_basic_db_equal(LEX_STRING db, Event_basic *et) { - return !sortcmp_lex_string(et->definer, *definer, system_charset_info); + return !sortcmp_lex_string(et->dbname, db, system_charset_info); } /* - Checks whether two events are equal by identifiers + Checks whether an event has equal `db` and `name` SYNOPSIS - event_timed_identifier_equal() + event_basic_identifier_equal() + db Schema + name Name + et The event object RETURN VALUE - TRUE equal - FALSE not equal + TRUE Equal + FALSE Not equal */ bool -event_timed_identifier_equal(Event_timed *a, Event_timed *b) +event_basic_identifier_equal(LEX_STRING db, LEX_STRING name, Event_basic *b) { - return event_timed_name_equal(a, &b->name) && - event_timed_db_equal(a, &b->dbname) && - event_timed_definer_equal(a, &b->definer); + return !sortcmp_lex_string(name, b->name, system_charset_info) && + !sortcmp_lex_string(db, b->dbname, system_charset_info); } /* - Switches the security context + Switches the security context. + SYNOPSIS - change_security_context() + event_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) + FALSE OK + TRUE 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) +static bool +event_change_security_context(THD *thd, LEX_STRING user, LEX_STRING host, + LEX_STRING db, Security_context *backup) { - DBUG_ENTER("change_security_context"); + DBUG_ENTER("event_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)) + + *backup= thd->main_security_ctx; + if (acl_getroot_no_password(&thd->main_security_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; + thd->security_ctx= &thd->main_security_ctx; #endif DBUG_RETURN(FALSE); -} +} /* - Restores the security context + Restores the security context. + SYNOPSIS - restore_security_context() - thd - thread - backup - switch to this context + event_restore_security_context() + thd Thread + backup Context to switch to */ -void -restore_security_context(THD *thd, Security_context *backup) +static void +event_restore_security_context(THD *thd, Security_context *backup) { - DBUG_ENTER("restore_security_context"); + DBUG_ENTER("event_restore_security_context"); #ifndef NO_EMBEDDED_ACCESS_CHECKS if (backup) - thd->security_ctx= backup; + { + thd->main_security_ctx= *backup; + thd->security_ctx= &thd->main_security_ctx; + } #endif DBUG_VOID_RETURN; } diff --git a/sql/event_data_objects.h b/sql/event_data_objects.h new file mode 100644 index 00000000000..e7e96d299fb --- /dev/null +++ b/sql/event_data_objects.h @@ -0,0 +1,285 @@ +#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_GET_FIELD_FAILED -2 +#define EVEX_COMPILE_ERROR -3 +#define EVEX_GENERAL_ERROR -4 +#define EVEX_BAD_PARAMS -5 +#define EVEX_MICROSECOND_UNSUP -6 + + +class sp_head; +class Sql_alloc; + + +class Event_basic +{ +protected: + MEM_ROOT mem_root; + +public: + LEX_STRING dbname; + LEX_STRING name; + LEX_STRING definer;// combination of user and host + + Event_basic(); + virtual ~Event_basic(); + + virtual int + load_from_row(TABLE *table) = 0; + +protected: + bool + load_string_fields(Field **fields, ...); +}; + + + +class Event_queue_element : public Event_basic +{ +protected: + 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 + }; + + enum enum_on_completion on_completion; + enum enum_status status; + TIME last_executed; + + TIME execute_at; + TIME starts; + TIME ends; + my_bool starts_null; + my_bool ends_null; + my_bool execute_at_null; + + longlong expression; + interval_type interval; + + bool dropped; + + uint execution_count; + + Event_queue_element(); + virtual ~Event_queue_element(); + + virtual int + load_from_row(TABLE *table); + + bool + compute_next_execution_time(); + + int + drop(THD *thd); + + void + mark_last_executed(THD *thd); + + bool + update_timing_fields(THD *thd); + + static void *operator new(size_t size) + { + void *p; + DBUG_ENTER("Event_queue_element::new(size)"); + p= my_malloc(size, MYF(0)); + DBUG_PRINT("info", ("alloc_ptr=0x%lx", p)); + DBUG_RETURN(p); + } + + static void operator delete(void *ptr, size_t size) + { + DBUG_ENTER("Event_queue_element::delete(ptr,size)"); + DBUG_PRINT("enter", ("free_ptr=0x%lx", ptr)); + TRASH(ptr, size); + my_free((gptr) ptr, MYF(0)); + DBUG_VOID_RETURN; + } +}; + + +class Event_timed : public Event_queue_element +{ + Event_timed(const Event_timed &); /* Prevent use of these */ + void operator=(Event_timed &); + +public: + LEX_STRING body; + + LEX_STRING definer_user; + LEX_STRING definer_host; + + LEX_STRING comment; + + ulonglong created; + ulonglong modified; + + ulong sql_mode; + + Event_timed(); + virtual ~Event_timed(); + + void + init(); + + virtual int + load_from_row(TABLE *table); + + int + get_create_event(THD *thd, String *buf); +}; + + +class Event_job_data : public Event_basic +{ +public: + THD *thd; + sp_head *sphead; + + LEX_STRING body; + LEX_STRING definer_user; + LEX_STRING definer_host; + + ulong sql_mode; + + uint execution_count; + + Event_job_data(); + virtual ~Event_job_data(); + + virtual int + load_from_row(TABLE *table); + + int + execute(THD *thd); + + int + compile(THD *thd, MEM_ROOT *mem_root); +private: + int + get_fake_create_event(THD *thd, String *buf); + + Event_job_data(const Event_job_data &); /* Prevent use of these */ + void operator=(Event_job_data &); +}; + + +class Event_parse_data : public Sql_alloc +{ +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 definer;// combination of user and host + LEX_STRING body; + 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; + + sp_name *identifier; + Item* item_expression; + longlong expression; + interval_type interval; + + static Event_parse_data * + new_instance(THD *thd); + + bool + check_parse_data(THD *thd); + + void + init_body(THD *thd); + +private: + + void + init_definer(THD *thd); + + void + init_name(THD *thd, sp_name *spn); + + int + init_execute_at(THD *thd); + + int + init_interval(THD *thd); + + int + init_starts(THD *thd); + + int + init_ends(THD *thd); + + Event_parse_data(); + ~Event_parse_data(); + + void + report_bad_value(const char *item_name, Item *bad_item); + + Event_parse_data(const Event_parse_data &); /* Prevent use of these */ + void operator=(Event_parse_data &); +}; + + +/* Compares only the schema part of the identifier */ +bool +event_basic_db_equal(LEX_STRING db, Event_basic *et); + +/* Compares the whole identifier*/ +bool +event_basic_identifier_equal(LEX_STRING db, LEX_STRING name, Event_basic *b); + + +#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..464c26044c7 --- /dev/null +++ b/sql/event_db_repository.cc @@ -0,0 +1,979 @@ +/* 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" +#include "events.h" +#include "sql_show.h" +#include "sp.h" +#include "sp_head.h" + + +static +time_t mysql_event_last_create_time= 0L; + +static +const TABLE_FIELD_W_TYPE event_table_fields[ET_FIELD_COUNT] = +{ + { + { C_STRING_WITH_LEN("db") }, + { C_STRING_WITH_LEN("char(64)") }, + { C_STRING_WITH_LEN("utf8") } + }, + { + { C_STRING_WITH_LEN("name") }, + { C_STRING_WITH_LEN("char(64)") }, + { C_STRING_WITH_LEN("utf8") } + }, + { + { C_STRING_WITH_LEN("body") }, + { C_STRING_WITH_LEN("longblob") }, + {NULL, 0} + }, + { + { C_STRING_WITH_LEN("definer") }, + { C_STRING_WITH_LEN("char(77)") }, + { C_STRING_WITH_LEN("utf8") } + }, + { + { C_STRING_WITH_LEN("execute_at") }, + { C_STRING_WITH_LEN("datetime") }, + {NULL, 0} + }, + { + { C_STRING_WITH_LEN("interval_value") }, + { C_STRING_WITH_LEN("int(11)") }, + {NULL, 0} + }, + { + { C_STRING_WITH_LEN("interval_field") }, + { C_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} + }, + { + { C_STRING_WITH_LEN("created") }, + { C_STRING_WITH_LEN("timestamp") }, + {NULL, 0} + }, + { + { C_STRING_WITH_LEN("modified") }, + { C_STRING_WITH_LEN("timestamp") }, + {NULL, 0} + }, + { + { C_STRING_WITH_LEN("last_executed") }, + { C_STRING_WITH_LEN("datetime") }, + {NULL, 0} + }, + { + { C_STRING_WITH_LEN("starts") }, + { C_STRING_WITH_LEN("datetime") }, + {NULL, 0} + }, + { + { C_STRING_WITH_LEN("ends") }, + { C_STRING_WITH_LEN("datetime") }, + {NULL, 0} + }, + { + { C_STRING_WITH_LEN("status") }, + { C_STRING_WITH_LEN("enum('ENABLED','DISABLED')") }, + {NULL, 0} + }, + { + { C_STRING_WITH_LEN("on_completion") }, + { C_STRING_WITH_LEN("enum('DROP','PRESERVE')") }, + {NULL, 0} + }, + { + { C_STRING_WITH_LEN("sql_mode") }, + { C_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} + }, + { + { C_STRING_WITH_LEN("comment") }, + { C_STRING_WITH_LEN("char(64)") }, + { C_STRING_WITH_LEN("utf8") } + } +}; + + +/* + Puts some data common to CREATE and ALTER EVENT into a row. + + SYNOPSIS + mysql_event_fill_row() + thd THD + table The row to fill out + et Event's data + is_update CREATE EVENT or ALTER EVENT + + 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 +mysql_event_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 f_num; + Field **fields= table->field; + + DBUG_ENTER("mysql_event_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 (fields[f_num= ET_FIELD_DEFINER]-> + store(et->definer.str, et->definer.length, scs)) + goto err_truncate; + + if (fields[f_num= ET_FIELD_DB]->store(et->dbname.str, et->dbname.length, scs)) + goto err_truncate; + + if (fields[f_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()*/ + fields[ET_FIELD_ON_COMPLETION]->store((longlong)et->on_completion, TRUE); + + fields[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) + { + fields[ET_FIELD_SQL_MODE]->store((longlong)thd->variables.sql_mode, TRUE); + if (fields[f_num= ET_FIELD_BODY]->store(et->body.str, et->body.length, scs)) + goto err_truncate; + } + + if (et->expression) + { + fields[ET_FIELD_INTERVAL_EXPR]->set_notnull(); + fields[ET_FIELD_INTERVAL_EXPR]->store((longlong)et->expression, TRUE); + + fields[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! + */ + fields[ET_FIELD_TRANSIENT_INTERVAL]->store((longlong)et->interval+1, TRUE); + + fields[ET_FIELD_EXECUTE_AT]->set_null(); + + if (!et->starts_null) + { + fields[ET_FIELD_STARTS]->set_notnull(); + fields[ET_FIELD_STARTS]->store_time(&et->starts, MYSQL_TIMESTAMP_DATETIME); + } + + if (!et->ends_null) + { + fields[ET_FIELD_ENDS]->set_notnull(); + fields[ET_FIELD_ENDS]->store_time(&et->ends, MYSQL_TIMESTAMP_DATETIME); + } + } + else if (et->execute_at.year) + { + fields[ET_FIELD_INTERVAL_EXPR]->set_null(); + fields[ET_FIELD_TRANSIENT_INTERVAL]->set_null(); + fields[ET_FIELD_STARTS]->set_null(); + fields[ET_FIELD_ENDS]->set_null(); + + fields[ET_FIELD_EXECUTE_AT]->set_notnull(); + fields[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 *)fields[ET_FIELD_MODIFIED])->set_time(); + + if (et->comment.str) + { + if (fields[f_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), fields[f_num]->field_name); + DBUG_RETURN(EVEX_GENERAL_ERROR); +} + + +/* + 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) + db For which schema to do an index scan. + + RETURN VALUE + 0 OK + 1 Error +*/ + +bool +Event_db_repository::index_read_for_db_for_i_s(THD *thd, TABLE *schema_table, + TABLE *event_table, + const 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(FALSE); + + DBUG_RETURN(TRUE); +} + + +/* + 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. + + RETURN VALUE + FALSE OK + TRUE Error +*/ + +bool +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? FALSE:TRUE); +} + + +/* + 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 + + RETURN VALUE + FALSE OK + TRUE Error +*/ + +int +Event_db_repository::fill_schema_events(THD *thd, TABLE_LIST *tables, + const 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); +} + + +/* + Open mysql.event table for read + + SYNOPSIS + Events::open_event_table() + thd [in] Thread context + lock_type [in] How to lock the table + table [out] 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); +} + + +/* + Checks parameters which we got from the parsing phase. + + SYNOPSIS + check_parse_params() + thd Thread context + parse_data Event's data + + RETURN VALUE + FALSE OK + TRUE Error (reported) +*/ + +static int +check_parse_params(THD *thd, Event_parse_data *parse_data) +{ + DBUG_ENTER("check_parse_params"); + + if (parse_data->check_parse_data(thd)) + DBUG_RETURN(EVEX_BAD_PARAMS); + + 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); + + DBUG_RETURN(0); +} + + +/* + Creates an event in mysql.event + + SYNOPSIS + Event_db_repository::create_event() + thd [in] THD + parse_data [in] Object containing info about the event + create_if_not [in] Whether to generate anwarning in case event exists + + RETURN VALUE + 0 OK + EVEX_GENERAL_ERROR Failure + + DESCRIPTION + Creates an event. Relies on mysql_event_fill_row which is shared with + ::update_event. The name of the event is inside "et". +*/ + +bool +Event_db_repository::create_event(THD *thd, Event_parse_data *parse_data, + my_bool create_if_not) +{ + int ret= 0; + CHARSET_INFO *scs= system_charset_info; + TABLE *table= NULL; + char old_db_buf[NAME_LEN+1]; + LEX_STRING old_db= { old_db_buf, sizeof(old_db_buf) }; + bool dbchanged= FALSE; + + DBUG_ENTER("Event_db_repository::create_event"); + + if (check_parse_params(thd, parse_data)) + goto err; + + 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", ("name: %.*s", parse_data->name.length, + parse_data->name.str)); + + DBUG_PRINT("info", ("check existance of an event with the same name")); + if (!find_named_event(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), + parse_data->name.str); + goto ok; + } + 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, parse_data->dbname, &old_db, 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, parse_data->dbname.str, + parse_data->dbname.str + parse_data->dbname.length) > + table->field[ET_FIELD_DB]->char_length()) + { + my_error(ER_TOO_LONG_IDENT, MYF(0), parse_data->dbname.str); + goto err; + } + + if (system_charset_info->cset-> + numchars(system_charset_info, parse_data->name.str, + parse_data->name.str + parse_data->name.length) > + table->field[ET_FIELD_NAME]->char_length()) + { + my_error(ER_TOO_LONG_IDENT, MYF(0), parse_data->name.str); + goto err; + } + + if (parse_data->body.length > table->field[ET_FIELD_BODY]->field_length) + { + my_error(ER_TOO_LONG_BODY, MYF(0), parse_data->name.str); + goto err; + } + + 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)); + goto err; + } + + ((Field_timestamp *)table->field[ET_FIELD_CREATED])->set_time(); + + /* + mysql_event_fill_row() calls my_error() in case of error so no need to + handle it here + */ + if ((ret= mysql_event_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), parse_data->name.str, ret); + goto err; + } + +ok: + if (dbchanged) + (void) mysql_change_db(thd, old_db.str, 1); + /* + This statement may cause a spooky valgrind warning at startup + inside init_key_cache on my system (ahristov, 2006/08/10) + */ + close_thread_tables(thd); + DBUG_RETURN(FALSE); + +err: + if (dbchanged) + (void) mysql_change_db(thd, old_db.str, 1); + if (table) + close_thread_tables(thd); + DBUG_RETURN(TRUE); +} + + +/* + 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 + FALSE OK + TRUE Error (reported) + + NOTES + sp_name is passed since this is the name of the event to + alter in case of RENAME TO. +*/ + +bool +Event_db_repository::update_event(THD *thd, Event_parse_data *parse_data, + LEX_STRING *new_dbname, LEX_STRING *new_name) +{ + CHARSET_INFO *scs= system_charset_info; + TABLE *table= NULL; + DBUG_ENTER("Event_db_repository::update_event"); + + if (open_event_table(thd, TL_WRITE, &table)) + { + my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0)); + goto err; + } + + if (check_parse_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_dbname) + DBUG_PRINT("info", ("rename to: %s@%s", new_dbname->str, new_name->str)); + + /* first look whether we overwrite */ + if (new_name) + { + if (!sortcmp_lex_string(parse_data->name, *new_name, scs) && + !sortcmp_lex_string(parse_data->dbname, *new_dbname, scs)) + { + my_error(ER_EVENT_SAME_NAME, MYF(0), parse_data->name.str); + goto err; + } + + if (!find_named_event(thd, *new_dbname, *new_name, table)) + { + my_error(ER_EVENT_ALREADY_EXISTS, MYF(0), new_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 (find_named_event(thd, parse_data->dbname, parse_data->name, table)) + { + my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), parse_data->name.str); + goto err; + } + + store_record(table,record[1]); + + /* Don't update create on row update. */ + table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; + + /* + mysql_event_fill_row() calls my_error() in case of error so no need to + handle it here + */ + if (mysql_event_fill_row(thd, table, parse_data, TRUE)) + goto err; + + if (new_dbname) + { + table->field[ET_FIELD_DB]->store(new_dbname->str, new_dbname->length, scs); + table->field[ET_FIELD_NAME]->store(new_name->str, new_name->length, scs); + } + + /* Close active transaction only if We are going to modify disk */ + if (end_active_trans(thd)) + goto err; + + int res; + if ((res= table->file->ha_update_row(table->record[1], table->record[0]))) + { + my_error(ER_EVENT_STORE_FAILED, MYF(0), parse_data->name.str, res); + goto err; + } + + /* close mysql.event or we crash later when loading the event from disk */ + close_thread_tables(thd); + DBUG_RETURN(FALSE); + +err: + if (table) + close_thread_tables(thd); + DBUG_RETURN(TRUE); +} + + +/* + Drops an event + + SYNOPSIS + Event_db_repository::drop_event() + thd [in] THD + db [in] Database name + name [in] Event's name + drop_if_exists [in] If set and the event not existing => warning + onto the stack + + RETURN VALUE + FALSE OK + TRUE Error (reported) +*/ + +bool +Event_db_repository::drop_event(THD *thd, LEX_STRING db, LEX_STRING name, + bool drop_if_exists) +{ + TABLE *table= NULL; + Open_tables_state backup; + int ret; + + DBUG_ENTER("Event_db_repository::drop_event"); + DBUG_PRINT("enter", ("%s@%s", db.str, name.str)); + + thd->reset_n_backup_open_tables_state(&backup); + if ((ret= open_event_table(thd, TL_WRITE, &table))) + { + my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0)); + goto done; + } + + if (!(ret= find_named_event(thd, db, name, table))) + { + /* Close active transaction only if we are actually going to modify disk */ + if (!(ret= end_active_trans(thd)) && + (ret= table->file->ha_delete_row(table->record[0]))) + my_error(ER_EVENT_CANNOT_DELETE, MYF(0)); + } + else + { + 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: + if (table) + close_thread_tables(thd); + thd->restore_backup_open_tables_state(&backup); + + DBUG_RETURN(ret); +} + + +/* + Positions the internal pointer of `table` to the place where (db, name) + is stored. + + SYNOPSIS + Event_db_repository::find_named_event() + thd Thread + db Schema + name Event name + table Opened mysql.event + + RETURN VALUE + FALSE OK + TRUE No such event +*/ + +bool +Event_db_repository::find_named_event(THD *thd, LEX_STRING db, LEX_STRING name, + TABLE *table) +{ + byte key[MAX_KEY_LENGTH]; + DBUG_ENTER("Event_db_repository::find_named_event"); + 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(TRUE); + + 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(TRUE); + } + + DBUG_PRINT("info", ("Row found!")); + DBUG_RETURN(FALSE); +} + + +/* + Drops all events in the selected database, from mysql.event. + + SYNOPSIS + Event_db_repository::drop_schema_events() + thd Thread + schema The database to clean from events +*/ + +void +Event_db_repository::drop_schema_events(THD *thd, LEX_STRING schema) +{ + DBUG_ENTER("Event_db_repository::drop_schema_events"); + drop_events_by_field(thd, ET_FIELD_DB, schema); + DBUG_VOID_RETURN; +} + + +/* + Drops all events by field which has specific value of the field + + SYNOPSIS + Event_db_repository::drop_events_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 +*/ + +void +Event_db_repository::drop_events_by_field(THD *thd, + enum enum_events_table_field field, + LEX_STRING field_value) +{ + int ret= 0; + TABLE *table= NULL; + READ_RECORD read_record_info; + DBUG_ENTER("Event_db_repository::drop_events_by_field"); + DBUG_PRINT("enter", ("field=%d field_value=%s", field, field_value.str)); + + if (open_event_table(thd, TL_WRITE, &table)) + { + /* + Currently being used only for DROP DATABASE - In this case we don't need + error message since the OK packet has been sent. But for DROP USER we + could need it. + + my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0)); + */ + DBUG_VOID_RETURN; + } + + /* 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")); + ret= table->file->ha_delete_row(table->record[0]); + } + } + end_read_record(&read_record_info); + close_thread_tables(thd); + + DBUG_VOID_RETURN; +} + + +/* + Looks for a named event in mysql.event and then loads it from + the table, compiles and inserts it into the cache. + + SYNOPSIS + Event_db_repository::load_named_event() + thd [in] Thread context + dbname [in] Event's db name + name [in] Event's name + etn [out] The loaded event + + RETURN VALUE + FALSE OK + TRUE Error (reported) +*/ + +bool +Event_db_repository::load_named_event(THD *thd, LEX_STRING dbname, + LEX_STRING name, Event_basic *etn) +{ + TABLE *table= NULL; + int ret= 0; + Open_tables_state backup; + + DBUG_ENTER("Event_db_repository::load_named_event"); + DBUG_PRINT("enter",("thd=0x%lx name:%*s",thd, name.length, name.str)); + + thd->reset_n_backup_open_tables_state(&backup); + + if ((ret= open_event_table(thd, TL_READ, &table))) + my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0)); + else if ((ret= find_named_event(thd, dbname, name, table))) + my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), name.str); + else if ((ret= etn->load_from_row(table))) + my_error(ER_CANNOT_LOAD_FROM_TABLE, MYF(0), "event"); + + if (table) + close_thread_tables(thd); + + thd->restore_backup_open_tables_state(&backup); + /* In this case no memory was allocated so we don't need to clean */ + + DBUG_RETURN(ret); +} diff --git a/sql/event_db_repository.h b/sql/event_db_repository.h new file mode 100644 index 00000000000..ed74edd7e19 --- /dev/null +++ b/sql/event_db_repository.h @@ -0,0 +1,102 @@ +#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 */ + +#define EVEX_OPEN_TABLE_FAILED -1 + +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 +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); + +class Event_basic; +class Event_parse_data; + +class Event_db_repository +{ +public: + Event_db_repository(){} + + bool + create_event(THD *thd, Event_parse_data *parse_data, my_bool create_if_not); + + bool + update_event(THD *thd, Event_parse_data *parse_data, LEX_STRING *new_dbname, + LEX_STRING *new_name); + + bool + drop_event(THD *thd, LEX_STRING db, LEX_STRING name, bool drop_if_exists); + + void + drop_schema_events(THD *thd, LEX_STRING schema); + + bool + find_named_event(THD *thd, LEX_STRING db, LEX_STRING name, TABLE *table); + + bool + load_named_event(THD *thd, LEX_STRING dbname, LEX_STRING name, Event_basic *et); + + int + open_event_table(THD *thd, enum thr_lock_type lock_type, TABLE **table); + + int + fill_schema_events(THD *thd, TABLE_LIST *tables, const char *db); + +private: + void + drop_events_by_field(THD *thd, enum enum_events_table_field field, + LEX_STRING field_value); + bool + index_read_for_db_for_i_s(THD *thd, TABLE *schema_table, TABLE *event_table, + const char *db); + + bool + table_scan_all_for_i_s(THD *thd, TABLE *schema_table, TABLE *event_table); + + static bool + check_system_tables(THD *thd); + + /* 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_queue.cc b/sql/event_queue.cc new file mode 100644 index 00000000000..539d0ab2734 --- /dev/null +++ b/sql/event_queue.cc @@ -0,0 +1,956 @@ +/* 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" +#include "event_db_repository.h" + + +#define EVENT_QUEUE_INITIAL_SIZE 30 +#define EVENT_QUEUE_EXTENT 30 + +#ifdef __GNUC__ +#if __GNUC__ >= 2 +#define SCHED_FUNC __FUNCTION__ +#endif +#else +#define SCHED_FUNC "<unknown>" +#endif + +#define LOCK_QUEUE_DATA() lock_data(SCHED_FUNC, __LINE__) +#define UNLOCK_QUEUE_DATA() unlock_data(SCHED_FUNC, __LINE__) + +struct event_queue_param +{ + THD *thd; + Event_queue *queue; + pthread_mutex_t LOCK_loaded; + pthread_cond_t COND_loaded; + bool loading_finished; +}; + + +/* + Compares the execute_at members of two Event_queue_element instances. + Used as callback for the prioritized queue when shifting + elements inside. + + SYNOPSIS + event_queue_element_data_compare_q() + vptr Not used (set it to NULL) + a First Event_queue_element object + b Second Event_queue_element 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_queue_element_compare_q(void *vptr, byte* a, byte *b) +{ + return my_time_compare(&((Event_queue_element *)a)->execute_at, + &((Event_queue_element *)b)->execute_at); +} + + +/* + Constructor of class Event_queue. + + SYNOPSIS + Event_queue::Event_queue() +*/ + +Event_queue::Event_queue() + :mutex_last_unlocked_at_line(0), mutex_last_locked_at_line(0), + mutex_last_attempted_lock_at_line(0), + mutex_queue_data_locked(FALSE), mutex_queue_data_attempting_lock(FALSE) +{ + mutex_last_unlocked_in_func= mutex_last_locked_in_func= + mutex_last_attempted_lock_in_func= ""; + set_zero_time(&next_activation_at, MYSQL_TIMESTAMP_DATETIME); +} + + +/* + Inits mutexes. + + SYNOPSIS + Event_queue::init_mutexes() +*/ + +void +Event_queue::init_mutexes() +{ + pthread_mutex_init(&LOCK_event_queue, MY_MUTEX_INIT_FAST); +} + + +/* + Destroys mutexes. + + SYNOPSIS + Event_queue::deinit_mutexes() +*/ + +void +Event_queue::deinit_mutexes() +{ + pthread_mutex_destroy(&LOCK_event_queue); +} + + +/* + This is a queue's constructor. Until this method is called, the + queue is unusable. We don't use a C++ constructor instead in + order to be able to check the return value. The queue is + initialized once at server startup. Initialization can fail in + case of a failure reading events from the database or out of + memory. + + SYNOPSIS + Event_queue::init() + + RETURN VALUE + FALSE OK + TRUE Error +*/ + +bool +Event_queue::init_queue(THD *thd, Event_db_repository *db_repo) +{ + bool res; + struct event_queue_param *event_queue_param_value= NULL; + + DBUG_ENTER("Event_queue::init_queue"); + DBUG_PRINT("enter", ("this=0x%lx", this)); + + LOCK_QUEUE_DATA(); + db_repository= db_repo; + + if (init_queue_ex(&queue, EVENT_QUEUE_INITIAL_SIZE , 0 /*offset*/, + 0 /*max_on_top*/, event_queue_element_compare_q, + NULL, EVENT_QUEUE_EXTENT)) + { + sql_print_error("SCHEDULER: Can't initialize the execution queue"); + goto err; + } + + 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); + goto err; + } + + res= load_events_from_db(thd); + UNLOCK_QUEUE_DATA(); + if (res) + deinit_queue(); + + DBUG_RETURN(res); + +err: + UNLOCK_QUEUE_DATA(); + DBUG_RETURN(TRUE); +} + + +/* + Deinits the queue. Remove all elements from it and destroys them + too. + + SYNOPSIS + Event_queue::deinit_queue() +*/ + +void +Event_queue::deinit_queue() +{ + DBUG_ENTER("Event_queue::deinit_queue"); + + LOCK_QUEUE_DATA(); + empty_queue(); + delete_queue(&queue); + UNLOCK_QUEUE_DATA(); + + DBUG_VOID_RETURN; +} + + +/* + Adds an event to the queue. + + SYNOPSIS + Event_queue::create_event() + dbname The schema of the new event + name The name of the new event + + RETURN VALUE + OP_OK OK or scheduler not working + OP_LOAD_ERROR Error during loading from disk +*/ + +int +Event_queue::create_event(THD *thd, LEX_STRING dbname, LEX_STRING name) +{ + int res; + Event_queue_element *new_element; + DBUG_ENTER("Event_queue::create_event"); + DBUG_PRINT("enter", ("thd=0x%lx et=%s.%s",thd, dbname.str, name.str)); + + new_element= new Event_queue_element(); + res= db_repository->load_named_event(thd, dbname, name, new_element); + if (res || new_element->status == Event_queue_element::DISABLED) + delete new_element; + else + { + new_element->compute_next_execution_time(); + + LOCK_QUEUE_DATA(); + DBUG_PRINT("info", ("new event in the queue 0x%lx", new_element)); + queue_insert_safe(&queue, (byte *) new_element); + dbug_dump_queue(thd->query_start()); + pthread_cond_broadcast(&COND_queue_state); + UNLOCK_QUEUE_DATA(); + } + + DBUG_RETURN(res); +} + + +/* + Updates an event from the scheduler queue + + SYNOPSIS + Event_queue::update_event() + thd Thread + dbname Schema of the event + name Name of the event + new_schema New schema, in case of RENAME TO, otherwise NULL + new_name New name, in case of RENAME TO, otherwise NULL + + RETURN VALUE + OP_OK OK or scheduler not working + OP_LOAD_ERROR Error during loading from disk +*/ + +int +Event_queue::update_event(THD *thd, LEX_STRING dbname, LEX_STRING name, + LEX_STRING *new_schema, LEX_STRING *new_name) +{ + int res; + Event_queue_element *new_element; + + DBUG_ENTER("Event_queue::update_event"); + DBUG_PRINT("enter", ("thd=0x%lx et=[%s.%s]", thd, dbname.str, name.str)); + + new_element= new Event_queue_element(); + + res= db_repository->load_named_event(thd, new_schema ? *new_schema:dbname, + new_name ? *new_name:name, new_element); + if (res) + { + delete new_element; + goto end; + } + else if (new_element->status == Event_queue_element::DISABLED) + { + DBUG_PRINT("info", ("The event is disabled.")); + /* + Destroy the object but don't skip to end: because we may have to remove + object from the cache. + */ + delete new_element; + new_element= NULL; + } + else + new_element->compute_next_execution_time(); + + LOCK_QUEUE_DATA(); + find_n_remove_event(dbname, name); + + /* If not disabled event */ + if (new_element) + { + DBUG_PRINT("info", ("new event in the Q 0x%lx", new_element)); + queue_insert_safe(&queue, (byte *) new_element); + pthread_cond_broadcast(&COND_queue_state); + } + + dbug_dump_queue(thd->query_start()); + UNLOCK_QUEUE_DATA(); + +end: + DBUG_PRINT("info", ("res=%d", res)); + DBUG_RETURN(res); +} + + +/* + Drops an event from the queue + + SYNOPSIS + Event_queue::drop_event() + thd Thread + dbname Schema of the event to drop + name Name of the event to drop +*/ + +void +Event_queue::drop_event(THD *thd, LEX_STRING dbname, LEX_STRING name) +{ + DBUG_ENTER("Event_queue::drop_event"); + DBUG_PRINT("enter", ("thd=0x%lx db=%s name=%s", thd, dbname.str, name.str)); + + LOCK_QUEUE_DATA(); + find_n_remove_event(dbname, name); + dbug_dump_queue(thd->query_start()); + UNLOCK_QUEUE_DATA(); + + /* + We don't signal here because the scheduler will catch the change + next time it wakes up. + */ + + DBUG_VOID_RETURN; +} + + +/* + 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 + >=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)(LEX_STRING, Event_basic *)) +{ + uint i= 0; + DBUG_ENTER("Event_queue::drop_matching_events"); + DBUG_PRINT("enter", ("pattern=%s", pattern.str)); + + while (i < queue.elements) + { + Event_queue_element *et= (Event_queue_element *) queue_element(&queue, i); + DBUG_PRINT("info", ("[%s.%s]?", et->dbname.str, et->name.str)); + if (comparator(pattern, et)) + { + /* + 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); + delete et; + } + else + i++; + } + /* + We don't call pthread_cond_broadcast(&COND_queue_state); + If we remove the top event: + 1. The queue is empty. The scheduler will wake up at some time and + realize that the queue is empty. If create_event() comes inbetween + it will signal the scheduler + 2. The queue is not empty, but the next event after the previous top, + won't be executed any time sooner than the element we removed. Hence, + we may not notify the scheduler and it will realize the change when it + wakes up from timedwait. + */ + + 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 HD + schema The schema name +*/ + +void +Event_queue::drop_schema_events(THD *thd, LEX_STRING schema) +{ + DBUG_ENTER("Event_queue::drop_schema_events"); + LOCK_QUEUE_DATA(); + drop_matching_events(thd, schema, event_basic_db_equal); + UNLOCK_QUEUE_DATA(); + DBUG_VOID_RETURN; +} + + +/* + Searches for an event in the queue + + SYNOPSIS + Event_queue::find_n_remove_event() + db The schema of the event to find + name The event to find + + NOTE + The caller should do the locking also the caller is responsible for + actual signalling in case an event is removed from the queue. +*/ + +void +Event_queue::find_n_remove_event(LEX_STRING db, LEX_STRING name) +{ + uint i; + DBUG_ENTER("Event_queue::find_n_remove_event"); + + for (i= 0; i < queue.elements; ++i) + { + Event_queue_element *et= (Event_queue_element *) queue_element(&queue, i); + DBUG_PRINT("info", ("[%s.%s]==[%s.%s]?", db.str, name.str, + et->dbname.str, et->name.str)); + if (event_basic_identifier_equal(db, name, et)) + { + queue_remove(&queue, i); + delete et; + break; + } + } + + DBUG_VOID_RETURN; +} + + +/* + Loads all ENABLED events from mysql.event into the prioritized + queue. Called during scheduler main thread initialization. Compiles + the events. Creates Event_queue_element 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= TRUE; + + DBUG_ENTER("Event_queue::load_events_from_db"); + DBUG_PRINT("enter", ("thd=0x%lx", 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_read_record(&read_record_info, thd, table ,NULL,1,0); + while (!(read_record_info.read_record(&read_record_info))) + { + Event_queue_element *et; + if (!(et= new Event_queue_element)) + { + DBUG_PRINT("info", ("Out of memory")); + break; + } + DBUG_PRINT("info", ("Loading event from row.")); + + if ((ret= et->load_from_row(table))) + { + sql_print_error("SCHEDULER: Error while loading from mysql.event. " + "Table probably corrupted"); + break; + } + if (et->status != Event_queue_element::ENABLED) + { + DBUG_PRINT("info",("%s is disabled",et->name.str)); + delete et; + continue; + } + + /* 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; + } + + { + Event_job_data temp_job_data; + DBUG_PRINT("info", ("Event %s loaded from row. ", et->name.str)); + + temp_job_data.load_from_row(table); + + /* + We load only on scheduler root just to check whether the body + compiles. + */ + switch (ret= temp_job_data.compile(thd, thd->mem_root)) { + case EVEX_MICROSECOND_UNSUP: + sql_print_error("SCHEDULER: mysql.event is tampered. MICROSECOND is not " + "supported but found in mysql.event"); + break; + case EVEX_COMPILE_ERROR: + sql_print_error("SCHEDULER: Error while compiling %s.%s. Aborting load", + et->dbname.str, et->name.str); + break; + default: + break; + } + thd->end_statement(); + thd->cleanup_after_query(); + } + if (ret) + { + delete et; + goto end; + } + + DBUG_PRINT("load_events_from_db", ("Adding 0x%lx to the exec list.")); + queue_insert_safe(&queue, (byte *) et); + count++; + } + clean_the_queue= FALSE; +end: + end_read_record(&read_record_info); + + if (clean_the_queue) + { + empty_queue(); + ret= -1; + } + else + { + ret= 0; + sql_print_information("SCHEDULER: Loaded %d event%s", count, + (count == 1)?"":"s"); + } + + close_thread_tables(thd); + + DBUG_PRINT("info", ("Status code %d. Loaded %d event(s)", ret, count)); + DBUG_RETURN(ret); +} + + +/* + Recalculates activation times in the queue. There is one reason for + that. Because the values (execute_at) by which the queue is ordered are + changed by calls to compute_next_execution_time() on a request from the + scheduler thread, if it is not running then the values won't be updated. + Once the scheduler is started again the values has to be recalculated + so they are right for the current time. + + SYNOPSIS + Event_queue::recalculate_activation_times() + thd Thread +*/ + +void +Event_queue::recalculate_activation_times(THD *thd) +{ + uint i; + DBUG_ENTER("Event_queue::recalculate_activation_times"); + + LOCK_QUEUE_DATA(); + DBUG_PRINT("info", ("%u loaded events to be recalculated", queue.elements)); + for (i= 0; i < queue.elements; i++) + { + ((Event_queue_element*)queue_element(&queue, i))->compute_next_execution_time(); + ((Event_queue_element*)queue_element(&queue, i))->update_timing_fields(thd); + } + queue_fix(&queue); + UNLOCK_QUEUE_DATA(); + + DBUG_VOID_RETURN; +} + + +/* + Empties the queue and destroys the Event_queue_element objects in the + queue. + + SYNOPSIS + Event_queue::empty_queue() + + NOTE + Should be called with LOCK_event_queue locked +*/ + +void +Event_queue::empty_queue() +{ + uint i; + DBUG_ENTER("Event_queue::empty_queue"); + DBUG_PRINT("enter", ("Purging the queue. %d element(s)", queue.elements)); + sql_print_information("SCHEDULER: Purging queue. %u events", queue.elements); + /* empty the queue */ + for (i= 0; i < queue.elements; ++i) + { + Event_queue_element *et= (Event_queue_element *) queue_element(&queue, i); + delete et; + } + resize_queue(&queue, 0); + DBUG_VOID_RETURN; +} + + +/* + Dumps the queue to the trace log. + + SYNOPSIS + Event_queue::dbug_dump_queue() + now Current timestamp +*/ + +void +Event_queue::dbug_dump_queue(time_t now) +{ +#ifndef DBUG_OFF + Event_queue_element *et; + uint i; + DBUG_ENTER("Event_queue::dbug_dump_queue"); + DBUG_PRINT("info", ("Dumping queue . Elements=%u", queue.elements)); + for (i = 0; i < queue.elements; i++) + { + et= ((Event_queue_element*)queue_element(&queue, i)); + DBUG_PRINT("info",("et=0x%lx db=%s name=%s",et, et->dbname.str, et->name.str)); + DBUG_PRINT("info", ("exec_at=%llu starts=%llu ends=%llu execs_so_far=%u" + " expr=%lld et.exec_at=%d now=%d (et.exec_at - now)=%d if=%d", + TIME_to_ulonglong_datetime(&et->execute_at), + TIME_to_ulonglong_datetime(&et->starts), + TIME_to_ulonglong_datetime(&et->ends), + et->execution_count, + et->expression, sec_since_epoch_TIME(&et->execute_at), now, + (int)(sec_since_epoch_TIME(&et->execute_at) - now), + sec_since_epoch_TIME(&et->execute_at) <= now)); + } + DBUG_VOID_RETURN; +#endif +} + +static const char *queue_empty_msg= "Waiting on empty queue"; +static const char *queue_wait_msg= "Waiting for next activation"; + +/* + Checks whether the top of the queue is elligible for execution and + returns an Event_job_data instance in case it should be executed. + `now` is compared against `execute_at` of the top element in the queue. + + SYNOPSIS + Event_queue::get_top_for_execution_if_time() + thd [in] Thread + now [in] Current timestamp + job_data [out] The object to execute + abstime [out] Time to sleep + + RETURN VALUE + FALSE No error. If *job_data==NULL then top not elligible for execution. + Could be that there is no top. If abstime->tv_sec is set to value + greater than zero then use abstime with pthread_cond_timedwait(). + If abstime->tv_sec is zero then sleep with pthread_cond_wait(). + abstime->tv_nsec is always zero. + TRUE Error + +*/ + +bool +Event_queue::get_top_for_execution_if_time(THD *thd, Event_job_data **job_data) +{ + bool ret= FALSE; + struct timespec top_time; + struct timespec *abstime; + Event_queue_element *top= NULL; + bool to_free= FALSE; + bool to_drop= FALSE; + *job_data= NULL; + DBUG_ENTER("Event_queue::get_top_for_execution_if_time"); + + top_time.tv_nsec= 0; + LOCK_QUEUE_DATA(); + for (;;) + { + int res; + + thd->end_time(); + time_t now= thd->query_start(); + abstime= NULL; + + if (queue.elements) + { + top= ((Event_queue_element*) queue_element(&queue, 0)); + top_time.tv_sec= sec_since_epoch_TIME(&top->execute_at); + + abstime= &top_time; + } + + if (!abstime || abstime->tv_sec > now) + { + const char *msg; + if (abstime) + { + next_activation_at= top->execute_at; + msg= queue_wait_msg; + } + else + { + set_zero_time(&next_activation_at, MYSQL_TIMESTAMP_DATETIME); + msg= queue_wait_msg; + } + + cond_wait(thd, abstime, msg, SCHED_FUNC, __LINE__); + if (thd->killed) + { + DBUG_PRINT("info", ("thd->killed=%d", thd->killed)); + goto end; + } + /* + The queue could have been emptied. Therefore it's safe to start from + the beginning. Moreover, this way we will get also the new top, if + the element at the top has been changed. + */ + continue; + } + + DBUG_PRINT("info", ("Ready for execution")); + if (!(*job_data= new Event_job_data())) + { + ret= TRUE; + break; + } + if ((res= db_repository->load_named_event(thd, top->dbname, top->name, + *job_data))) + { + DBUG_PRINT("error", ("Got %d from load_named_event", res)); + delete *job_data; + *job_data= NULL; + ret= TRUE; + break; + } + + top->mark_last_executed(thd); + if (top->compute_next_execution_time()) + top->status= Event_queue_element::DISABLED; + DBUG_PRINT("info", ("event %s status is %d", top->name.str, top->status)); + + (*job_data)->execution_count= top->execution_count; + + top->update_timing_fields(thd); + if (((top->execute_at.year && !top->expression) || top->execute_at_null) || + (top->status == Event_queue_element::DISABLED)) + { + DBUG_PRINT("info", ("removing from the queue")); + sql_print_information("SCHEDULER: Last execution of %s.%s. %s", + top->dbname.str, top->name.str, + top->dropped? "Dropping.":""); + to_free= TRUE; + to_drop= top->dropped; + queue_remove(&queue, 0); + } + else + queue_replaced(&queue); + + dbug_dump_queue(now); + break; + } +end: + UNLOCK_QUEUE_DATA(); + if (to_drop) + { + DBUG_PRINT("info", ("Dropping from disk")); + top->drop(thd); + } + if (to_free) + delete top; + + DBUG_PRINT("info", ("returning %d. et_new=0x%lx abstime.tv_sec=%d ", + ret, *job_data, abstime? abstime->tv_sec:0)); + + if (*job_data) + DBUG_PRINT("info", ("db=%s name=%s definer=%s", (*job_data)->dbname.str, + (*job_data)->name.str, (*job_data)->definer.str)); + + DBUG_RETURN(ret); +} + + +/* + Auxiliary function for locking LOCK_event_queue. Used by the + LOCK_QUEUE_DATA macro + + SYNOPSIS + Event_queue::lock_data() + func Which function is requesting mutex lock + line On which line mutex lock is requested +*/ + +void +Event_queue::lock_data(const char *func, uint line) +{ + DBUG_ENTER("Event_queue::lock_data"); + DBUG_PRINT("enter", ("func=%s line=%u", func, line)); + mutex_last_attempted_lock_in_func= func; + mutex_last_attempted_lock_at_line= line; + mutex_queue_data_attempting_lock= TRUE; + pthread_mutex_lock(&LOCK_event_queue); + mutex_last_attempted_lock_in_func= ""; + mutex_last_attempted_lock_at_line= 0; + mutex_queue_data_attempting_lock= FALSE; + + mutex_last_locked_in_func= func; + mutex_last_locked_at_line= line; + mutex_queue_data_locked= TRUE; + + DBUG_VOID_RETURN; +} + + +/* + Auxiliary function for unlocking LOCK_event_queue. Used by the + UNLOCK_QUEUE_DATA macro + + SYNOPSIS + Event_queue::unlock_data() + func Which function is requesting mutex unlock + line On which line mutex unlock is requested +*/ + +void +Event_queue::unlock_data(const char *func, uint line) +{ + DBUG_ENTER("Event_queue::unlock_data"); + DBUG_PRINT("enter", ("func=%s line=%u", 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; +} + + +/* + Wrapper for pthread_cond_wait/timedwait + + SYNOPSIS + Event_queue::cond_wait() + thd Thread (Could be NULL during shutdown procedure) + msg Message for thd->proc_info + abstime If not null then call pthread_cond_timedwait() + func Which function is requesting cond_wait + line On which line cond_wait is requested +*/ + +void +Event_queue::cond_wait(THD *thd, struct timespec *abstime, const char* msg, + const char *func, uint line) +{ + DBUG_ENTER("Event_queue::cond_wait"); + waiting_on_cond= TRUE; + mutex_last_unlocked_at_line= line; + mutex_queue_data_locked= FALSE; + mutex_last_unlocked_in_func= func; + + thd->enter_cond(&COND_queue_state, &LOCK_event_queue, msg); + + DBUG_PRINT("info", ("pthread_cond_%swait", abstime? "timed":"")); + if (!abstime) + pthread_cond_wait(&COND_queue_state, &LOCK_event_queue); + else + pthread_cond_timedwait(&COND_queue_state, &LOCK_event_queue, abstime); + + mutex_last_locked_in_func= func; + mutex_last_locked_at_line= line; + mutex_queue_data_locked= TRUE; + waiting_on_cond= FALSE; + + /* + This will free the lock so we need to relock. Not the best thing to + do but we need to obey cond_wait() + */ + thd->exit_cond(""); + lock_data(func, line); + + DBUG_VOID_RETURN; +} + + +/* + Dumps the internal status of the queue + + SYNOPSIS + Event_queue::dump_internal_status() +*/ + +void +Event_queue::dump_internal_status() +{ + DBUG_ENTER("Event_queue::dump_internal_status"); + + /* element count */ + puts(""); + puts("Event queue status:"); + printf("Element count : %u\n", queue.elements); + printf("Data locked : %s\n", mutex_queue_data_locked? "YES":"NO"); + printf("Attempting lock : %s\n", mutex_queue_data_attempting_lock? "YES":"NO"); + printf("LLA : %s:%u\n", mutex_last_locked_in_func, + mutex_last_locked_at_line); + printf("LUA : %s:%u\n", mutex_last_unlocked_in_func, + mutex_last_unlocked_at_line); + if (mutex_last_attempted_lock_at_line) + printf("Last lock attempt at: %s:%u\n", mutex_last_attempted_lock_in_func, + mutex_last_attempted_lock_at_line); + printf("WOC : %s\n", waiting_on_cond? "YES":"NO"); + printf("Next activation : %04d-%02d-%02d %02d:%02d:%02d\n", + next_activation_at.year, next_activation_at.month, + next_activation_at.day, next_activation_at.hour, + next_activation_at.minute, next_activation_at.second); + + DBUG_VOID_RETURN; +} diff --git a/sql/event_queue.h b/sql/event_queue.h new file mode 100644 index 00000000000..5b70506d388 --- /dev/null +++ b/sql/event_queue.h @@ -0,0 +1,121 @@ +#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 */ + +class Event_basic; +class Event_db_repository; +class Event_job_data; +class Event_queue_element; + +class THD; +class Event_scheduler; + +class Event_queue +{ +public: + Event_queue(); + + void + init_mutexes(); + + void + deinit_mutexes(); + + bool + init_queue(THD *thd, Event_db_repository *db_repo); + + void + deinit_queue(); + + /* Methods for queue management follow */ + + int + create_event(THD *thd, LEX_STRING dbname, LEX_STRING name); + + int + update_event(THD *thd, LEX_STRING dbname, LEX_STRING name, + LEX_STRING *new_schema, LEX_STRING *new_name); + + void + drop_event(THD *thd, LEX_STRING dbname, LEX_STRING name); + + void + drop_schema_events(THD *thd, LEX_STRING schema); + + void + recalculate_activation_times(THD *thd); + + bool + get_top_for_execution_if_time(THD *thd, Event_job_data **job_data); + + void + dump_internal_status(); + + int + load_events_from_db(THD *thd); + +protected: + void + find_n_remove_event(LEX_STRING db, LEX_STRING name); + + + void + drop_matching_events(THD *thd, LEX_STRING pattern, + bool (*)(LEX_STRING, Event_basic *)); + + void + empty_queue(); + + void + dbug_dump_queue(time_t now); + + /* LOCK_event_queue is the mutex which protects the access to the queue. */ + pthread_mutex_t LOCK_event_queue; + pthread_cond_t COND_queue_state; + + Event_db_repository *db_repository; + + Event_scheduler *scheduler; + + /* The sorted queue with the Event_job_data objects */ + QUEUE queue; + + TIME next_activation_at; + + uint mutex_last_locked_at_line; + uint mutex_last_unlocked_at_line; + uint mutex_last_attempted_lock_at_line; + const char* mutex_last_locked_in_func; + const char* mutex_last_unlocked_in_func; + const char* mutex_last_attempted_lock_in_func; + bool mutex_queue_data_locked; + bool mutex_queue_data_attempting_lock; + bool waiting_on_cond; + + /* helper functions for working with mutexes & conditionals */ + void + lock_data(const char *func, uint line); + + void + unlock_data(const char *func, uint line); + + void + cond_wait(THD *thd, struct timespec *abstime, const char* msg, + const char *func, uint line); +}; + +#endif /* _EVENT_QUEUE_H_ */ diff --git a/sql/event_scheduler.cc b/sql/event_scheduler.cc index 1b4a0d290e6..6f9f6887c12 100644 --- a/sql/event_scheduler.cc +++ b/sql/event_scheduler.cc @@ -15,228 +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_timed.h" +#include "event_data_objects.h" #include "event_scheduler.h" -#include "sp_head.h" - -/* - ToDo: - 1. Talk to Alik to get a check for configure.in for my_time_t and time_t - 2. Look at guardian.h|cc to see its life cycle, has similarities. -*/ - - -/* - The scheduler is implemented as class Event_scheduler. Only one instance is - kept during the runtime of the server, by implementing the Singleton DP. - Object instance is always there because the memory is allocated statically - and initialized when the OS loader loads mysqld. This initialization is - bare. Extended initialization is done during the call to - Event_scheduler::init() in Events::init(). The reason for that late initialization - is that some subsystems needed to boot the Scheduler are not available at - earlier stages of the mysqld boot procedure. Events::init() is called in - mysqld.cc . If the mysqld is started with --event-scheduler=0 then - no initialization takes place and the scheduler is unavailable during this - server run. The server should be started with --event-scheduler=1 to have - the scheduler initialized and able to execute jobs. This starting alwa - s implies that the jobs execution will start immediately. If the server - is started with --event-scheduler=2 then the scheduler is started in suspended - state. Default state, if --event-scheduler is not specified is 2. - - The scheduler only manages execution of the events. Their creation, - alteration and deletion is delegated to other routines found in event.cc . - These routines interact with the scheduler : - - CREATE EVENT -> Event_scheduler::create_event() - - ALTER EVENT -> Event_scheduler::update_event() - - DROP EVENT -> Event_scheduler::drop_event() - - There is one mutex in the single Event_scheduler object which controls - the simultaneous access to the objects invariants. Using one lock makes - it easy to follow the workflow. This mutex is LOCK_scheduler_data. It is - initialized in Event_scheduler::init(). Which in turn is called by the - Facade class Events in event.cc, coming from init_thread_environment() from - mysqld.cc -> no concurrency at this point. It's destroyed in - Events::destroy_mutexes() called from clean_up_mutexes() in mysqld.cc . - - The full initialization is done in Event_scheduler::init() called from - Events::init(). It's done before any requests coming in, so this is a - guarantee for not having concurrency. - - The scheduler is started with Event_scheduler::start() and stopped with - Event_scheduler::stop(). When the scheduler starts it loads all events - from mysql.event table. Unfortunately, there is a race condition between - the event disk management functions and the scheduler ones - (add/replace/drop_event & load_events_from_db()), because the operations - do not happen under one global lock but the disk operations are guarded - by the MYISAM lock on mysql.event. In the same time, the queue operations - are guarded by LOCK_scheduler_data. If the scheduler is start()-ed during - server startup and stopped()-ed during server shutdown (in Events::shutdown() - called by kill_server() in mysqld.cc) these races does not exist. - - Since the user may want to temporarily inhibit execution of events the - scheduler can be suspended and then it can be forced to resume its - operations. The API call to perform these is - Event_scheduler::suspend_or_resume(enum enum_suspend_or_resume) . - When the scheduler is suspended the main scheduler thread, which ATM - happens to have thread_id 1, locks on a condition COND_suspend_or_resume. - When this is signal is sent for the reverse operation the main scheduler - loops continues to roll and execute events. - - When the scheduler is suspended all add/replace/drop_event() operations - work as expected and the modify the queue but no events execution takes - place. - - In contrast to the previous scheduler implementation, found in - event_executor.cc, the start, shutdown, suspend and resume are synchronous - operations. As a whole all operations are synchronized and no busy waits - are used except in stop_all_running_events(), which waits until all - running event worker threads have finished. It would have been nice to - use a conditional on which this method will wait and the last thread to - finish would signal it but this implies subclassing THD. - - The scheduler does not keep a counter of how many event worker threads are - running, at any specific moment, because this will copy functionality - already existing in the server. Namely, all THDs are registered in the - global `threads` array. THD has member variable system_thread which - identifies the type of thread. Connection threads being NON_SYSTEM_THREAD, - all other have their enum value. Important for the scheduler are - SYSTEM_THREAD_EVENT_SCHEDULER and SYSTEM_THREAD_EVENT_WORKER. - - Class THD subclasses class ilink, which is the linked list of all threads. - When a THD instance is destroyed it's being removed from threads, thus - no manual intervention is needed. On the contrary registering is manual - with threads.append() . Traversing the threads array every time a subclass - of THD, for instance if we would have had THD_scheduler_worker to see - how many events we have and whether the scheduler is shutting down will - take much time and lead to a deadlock. stop_all_running_events() is called - under LOCK_scheduler_data. If the THD_scheduler_worker was aware of - the single Event_scheduler instance it will try to check - Event_scheduler::state but for this it would need to acquire - LOCK_scheduler_data => deadlock. Thus stop_all_running_events() uses a - busy wait. - - DROP DATABASE DDL should drop all events defined in a specific schema. - DROP USER also should drop all events who has as definer the user being - dropped (this one is not addressed at the moment but a hook exists). For - this specific needs Event_scheduler::drop_matching_events() is - implemented. Which expects a callback to be applied on every object in - the queue. Thus events that match specific schema or user, will be - removed from the queue. The exposed interface is : - - Event_scheduler::drop_schema_events() - - Event_scheduler::drop_user_events() - - This bulk dropping happens under LOCK_scheduler_data, thus no two or - more threads can execute it in parallel. However, DROP DATABASE is also - synchronized, currently, in the server thus this does not impact the - overall performance. In addition, DROP DATABASE is not that often - executed DDL. - - Though the interface to the scheduler is only through the public methods - of class Event_scheduler, there are currently few functions which are - used during its operations. Namely : - - static evex_print_warnings() - After every event execution all errors/warnings are dumped, so the user - can see in case of a problem what the problem was. - - - static init_event_thread() - This function is both used by event_scheduler_thread() and - event_worker_thread(). It initializes the THD structure. The - initialization looks pretty similar to the one in slave.cc done for the - replication threads. However, though the similarities it cannot be - factored out to have one routine. - - - static event_scheduler_thread() - Because our way to register functions to be used by the threading library - does not allow usage of static methods this function is used to start the - scheduler in it. It does THD initialization and then calls - Event_scheduler::run(). - - - static event_worker_thread() - With already stated the reason for not being able to use methods, this - function executes the worker threads. - - The execution of events is, to some extent, synchronized to inhibit race - conditions when Event_timed::thread_id is being updated with the thread_id of - the THD in which the event is being executed. The thread_id is in the - Event_timed object because we need to be able to kill quickly a specific - event during ALTER/DROP EVENT without traversing the global `threads` array. - However, this makes the scheduler's code more complicated. The event worker - thread is started by Event_timed::spawn_now(), which in turn calls - pthread_create(). The thread_id which will be associated in init_event_thread - is not known in advance thus the registering takes place in - event_worker_thread(). This registering has to be synchronized under - LOCK_scheduler_data, so no kill_event() on a object in - replace_event/drop_event/drop_matching_events() could take place. - - This synchronization is done through class Worker_thread_param that is - local to this file. Event_scheduler::execute_top() is called under - LOCK_scheduler_data. This method : - 1. Creates an instance of Worker_thread_param on the stack - 2. Locks Worker_thread_param::LOCK_started - 3. Calls Event_timed::spawn_now() which in turn creates a new thread. - 4. Locks on Worker_thread_param::COND_started_or_stopped and waits till the - worker thread send signal. The code is spurious wake-up safe because - Worker_thread_param::started is checked. - 5. The worker thread initializes its THD, then sets Event_timed::thread_id, - sets Worker_thread_param::started to TRUE and sends back - Worker_thread_param::COND_started. From this moment on, the event - is being executed and could be killed by using Event_timed::thread_id. - When Event_timed::spawn_thread_finish() is called in the worker thread, - it sets thread_id to 0. From this moment on, the worker thread should not - touch the Event_timed instance. - - - The life-cycle of the server is a FSA. - enum enum_state Event_scheduler::state keeps the state of the scheduler. - - The states are: - - |---UNINITIALIZED - | - | |------------------> IN_SHUTDOWN - --> INITIALIZED -> COMMENCING ---> RUNNING ----------| - ^ ^ | | ^ | - | |- CANTSTART <--| | |- SUSPENDED <-| - |______________________________| - - - UNINITIALIZED :The object is created and only the mutex is initialized - - INITIALIZED :All member variables are initialized - - COMMENCING :The scheduler is starting, no other attempt to start - should succeed before the state is back to INITIALIZED. - - CANTSTART :Set by the ::run() method in case it can't start for some - reason. In this case the connection thread that tries to - start the scheduler sees that some error has occurred and - returns an error to the user. Finally, the connection - thread sets the state to INITIALIZED, so further attempts - to start the scheduler could be made. - - RUNNING :The scheduler is running. New events could be added, - dropped, altered. The scheduler could be stopped. - - SUSPENDED :Like RUNNING but execution of events does not take place. - Operations on the memory queue are possible. - - IN_SHUTDOWN :The scheduler is shutting down, due to request by setting - the global event_scheduler to 0/FALSE, or because of a - KILL command sent by a user to the master thread. - - In every method the macros LOCK_SCHEDULER_DATA() and UNLOCK_SCHEDULER_DATA() - are used for (un)locking purposes. They are used to save the programmer - from typing everytime - lock_data(__FUNCTION__, __LINE__); - All locking goes through Event_scheduler::lock_data() and ::unlock_data(). - These two functions then record in variables where for last time - LOCK_scheduler_data was locked and unlocked (two different variables). In - multithreaded environment, in some cases they make no sense but are useful for - inspecting deadlocks without having the server debug log turned on and the - server is still running. - - The same strategy is used for conditional variables. - Event_scheduler::cond_wait() is invoked from all places with parameter - an enum enum_cond_vars. In this manner, it's possible to inspect the last - on which condition the last call to cond_wait() was waiting. If the server - was started with debug trace switched on, the trace file also holds information - about conditional variables used. -*/ +#include "event_queue.h" #ifdef __GNUC__ #if __GNUC__ >= 2 @@ -246,102 +28,41 @@ #define SCHED_FUNC "<unknown>" #endif -#define LOCK_SCHEDULER_DATA() lock_data(SCHED_FUNC, __LINE__) -#define UNLOCK_SCHEDULER_DATA() unlock_data(SCHED_FUNC, __LINE__) +#define LOCK_DATA() lock_data(SCHED_FUNC, __LINE__) +#define UNLOCK_DATA() unlock_data(SCHED_FUNC, __LINE__) +#define COND_STATE_WAIT(mythd, abstime, msg) \ + cond_wait(mythd, abstime, msg, SCHED_FUNC, __LINE__) +extern pthread_attr_t connection_attrib; -#ifndef DBUG_OFF static -LEX_STRING states_names[] = -{ - {(char*) STRING_WITH_LEN("UNINITIALIZED")}, - {(char*) STRING_WITH_LEN("INITIALIZED")}, - {(char*) STRING_WITH_LEN("COMMENCING")}, - {(char*) STRING_WITH_LEN("CANTSTART")}, - {(char*) STRING_WITH_LEN("RUNNING")}, - {(char*) STRING_WITH_LEN("SUSPENDED")}, - {(char*) STRING_WITH_LEN("IN_SHUTDOWN")} -}; -#endif - - -Event_scheduler -Event_scheduler::singleton; - - -const char * const -Event_scheduler::cond_vars_names[Event_scheduler::COND_LAST] = +const LEX_STRING scheduler_states_names[] = { - "new work", - "started or stopped", - "suspend or resume" + { C_STRING_WITH_LEN("UNINITIALIZED") }, + { C_STRING_WITH_LEN("INITIALIZED") }, + { C_STRING_WITH_LEN("RUNNING") }, + { C_STRING_WITH_LEN("STOPPING") } }; - -class Worker_thread_param -{ -public: - Event_timed *et; - pthread_mutex_t LOCK_started; - pthread_cond_t COND_started; - bool started; - - Worker_thread_param(Event_timed *etn):et(etn), started(FALSE) - { - pthread_mutex_init(&LOCK_started, MY_MUTEX_INIT_FAST); - pthread_cond_init(&COND_started, NULL); - } - - ~Worker_thread_param() - { - pthread_mutex_destroy(&LOCK_started); - pthread_cond_destroy(&COND_started); - } +struct scheduler_param { + THD *thd; + Event_scheduler *scheduler; }; /* - 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 checked later. SYNOPSIS evex_print_warnings - thd - thread used during the execution of the event - et - the event itself + thd Thread used during the execution of the event + et The event itself */ static void -evex_print_warnings(THD *thd, Event_timed *et) +evex_print_warnings(THD *thd, Event_job_data *et) { MYSQL_ERROR *err; DBUG_ENTER("evex_print_warnings"); @@ -354,9 +75,7 @@ evex_print_warnings(THD *thd, Event_timed *et) prefix.length(0); prefix.append("SCHEDULER: ["); - append_identifier(thd, &prefix, et->definer_user.str, et->definer_user.length); - prefix.append('@'); - append_identifier(thd, &prefix, et->definer_host.str, et->definer_host.length); + append_identifier(thd, &prefix, et->definer.str, et->definer.length); prefix.append("][", 2); append_identifier(thd,&prefix, et->dbname.str, et->dbname.length); prefix.append('.'); @@ -381,86 +100,116 @@ evex_print_warnings(THD *thd, Event_timed *et) /* - Inits an scheduler thread handler, both the main and a worker + Performs post initialization of structures in a new thread. + + SYNOPSIS + post_init_event_thread() + thd Thread +*/ + +bool +post_init_event_thread(THD *thd) +{ + my_thread_init(); + pthread_detach_this_thread(); + thd->real_id= pthread_self(); + if (init_thr_lock() || thd->store_globals()) + { + thd->cleanup(); + return TRUE; + } + +#if !defined(__WIN__) && !defined(OS2) && !defined(__NETWARE__) + sigset_t set; + VOID(sigemptyset(&set)); // Get mask in use + VOID(pthread_sigmask(SIG_UNBLOCK,&set,&thd->block_signals)); +#endif + pthread_mutex_lock(&LOCK_thread_count); + threads.append(thd); + thread_count++; + thread_running++; + pthread_mutex_unlock(&LOCK_thread_count); + + return FALSE; +} + + +/* + Cleans up the THD and the threaded environment of the thread. + + SYNOPSIS + deinit_event_thread() + thd Thread +*/ + +void +deinit_event_thread(THD *thd) +{ + thd->proc_info= "Clearing"; + DBUG_ASSERT(thd->net.buff != 0); + net_end(&thd->net); + DBUG_PRINT("exit", ("Event thread finishing")); + pthread_mutex_lock(&LOCK_thread_count); + thread_count--; + thread_running--; + delete thd; + pthread_mutex_unlock(&LOCK_thread_count); + + my_thread_end(); +} + + +/* + Performs pre- pthread_create() initialisation of THD. Do this + in the thread that will pass THD to the child thread. In the + child thread call post_init_event_thread(). SYNOPSIS - init_event_thread() - thd - the THD of the thread. Has to be allocated by the caller. + pre_init_event_thread() + thd The THD of the thread. Has to be allocated by the caller. NOTES 1. The host of the thead is my_localhost 2. thd->net is initted with NULL - no communication. - - RETURN VALUE - 0 OK - -1 Error */ -static int -init_event_thread(THD** t, enum enum_thread_type thread_type) +void +pre_init_event_thread(THD* thd) { - THD *thd= *t; - thd->thread_stack= (char*)t; // remember where our stack is - DBUG_ENTER("init_event_thread"); + DBUG_ENTER("pre_init_event_thread"); thd->client_capabilities= 0; thd->security_ctx->master_access= 0; thd->security_ctx->db_access= 0; thd->security_ctx->host_or_ip= (char*)my_localhost; - my_net_init(&thd->net, 0); + my_net_init(&thd->net, NULL); + thd->security_ctx->set_user((char*)"event_scheduler"); thd->net.read_timeout= slave_net_timeout; thd->slave_thread= 0; thd->options|= OPTION_AUTO_IS_NULL; thd->client_capabilities|= CLIENT_MULTI_RESULTS; - thd->real_id=pthread_self(); - VOID(pthread_mutex_lock(&LOCK_thread_count)); + pthread_mutex_lock(&LOCK_thread_count); thd->thread_id= thread_id++; - threads.append(thd); - thread_count++; - thread_running++; - VOID(pthread_mutex_unlock(&LOCK_thread_count)); - - if (init_thr_lock() || thd->store_globals()) - { - thd->cleanup(); - DBUG_RETURN(-1); - } - -#if !defined(__WIN__) && !defined(OS2) && !defined(__NETWARE__) - sigset_t set; - VOID(sigemptyset(&set)); // Get mask in use - VOID(pthread_sigmask(SIG_UNBLOCK,&set,&thd->block_signals)); -#endif + pthread_mutex_unlock(&LOCK_thread_count); /* Guarantees that we will see the thread in SHOW PROCESSLIST though its vio is NULL. */ - thd->system_thread= thread_type; thd->proc_info= "Initialized"; thd->version= refresh_version; thd->set_time(); - DBUG_RETURN(0); + DBUG_VOID_RETURN; } /* - Inits the main scheduler thread and then calls Event_scheduler::run() - of arg. + Function that executes the scheduler, SYNOPSIS event_scheduler_thread() - arg void* ptr to Event_scheduler - - NOTES - 1. The host of the thead is my_localhost - 2. thd->net is initted with NULL - no communication. - 3. The reason to have a proxy function is that it's not possible to - use a method as function to be executed in a spawned thread: - - our pthread_hander_t macro uses extern "C" - - separating thread setup from the real execution loop is also to be - considered good. + arg Pointer to `struct scheduler_param` RETURN VALUE 0 OK @@ -470,58 +219,21 @@ pthread_handler_t event_scheduler_thread(void *arg) { /* needs to be first for thread_stack */ - THD *thd= NULL; - Event_scheduler *scheduler= (Event_scheduler *) arg; + THD *thd= (THD *)((struct scheduler_param *) arg)->thd; + Event_scheduler *scheduler= ((struct scheduler_param *) arg)->scheduler; - DBUG_ENTER("event_scheduler_thread"); + my_free((char*)arg, MYF(0)); - my_thread_init(); - pthread_detach_this_thread(); + thd->thread_stack= (char *)&thd; // remember where our stack is - /* note that constructor of THD uses DBUG_ ! */ - if (!(thd= new THD) || init_event_thread(&thd, SYSTEM_THREAD_EVENT_SCHEDULER)) - { - sql_print_error("SCHEDULER: Cannot init manager event thread."); - scheduler->report_error_during_start(); - } - else - { - thd->security_ctx->set_user((char*)"event_scheduler"); + DBUG_ENTER("event_scheduler_thread"); - sql_print_information("SCHEDULER: Manager thread booting"); - if (Event_scheduler::check_system_tables(thd)) - scheduler->report_error_during_start(); - else - scheduler->run(thd); + if (!post_init_event_thread(thd)) + scheduler->run(thd); - /* - NOTE: Don't touch `scheduler` after this point because we have notified - the - thread which shuts us down that we have finished cleaning. In this - very moment a new scheduler thread could be started and a crash is - not welcome. - */ - } + deinit_event_thread(thd); - /* - If we cannot create THD then don't decrease because we haven't touched - thread_count and thread_running in init_event_thread() which was never - called. In init_event_thread() thread_count and thread_running are - always increased even in the case the method returns an error. - */ - if (thd) - { - thd->proc_info= "Clearing"; - DBUG_ASSERT(thd->net.buff != 0); - net_end(&thd->net); - pthread_mutex_lock(&LOCK_thread_count); - thread_count--; - thread_running--; - delete thd; - pthread_mutex_unlock(&LOCK_thread_count); - } - my_thread_end(); - DBUG_RETURN(0); // Can't return anything here + DBUG_RETURN(0); // Against gcc warnings } @@ -531,7 +243,7 @@ event_scheduler_thread(void *arg) SYNOPSIS event_worker_thread() - arg The Event_timed object to be processed + arg The Event_job_data object to be processed RETURN VALUE 0 OK @@ -540,1241 +252,392 @@ event_scheduler_thread(void *arg) pthread_handler_t event_worker_thread(void *arg) { - THD *thd; /* needs to be first for thread_stack */ - Worker_thread_param *param= (Worker_thread_param *) arg; - Event_timed *event= param->et; + /* needs to be first for thread_stack */ + THD *thd; + Event_job_data *event= (Event_job_data *)arg; int ret; - bool startup_error= FALSE; - Security_context *save_ctx; - /* this one is local and not needed after exec */ - Security_context security_ctx; - DBUG_ENTER("event_worker_thread"); - DBUG_PRINT("enter", ("event=[%s.%s]", event->dbname.str, event->name.str)); + thd= event->thd; - my_thread_init(); - pthread_detach_this_thread(); + thd->thread_stack= (char *) &thd; // remember where our stack is + DBUG_ENTER("event_worker_thread"); - if (!(thd= new THD) || init_event_thread(&thd, SYSTEM_THREAD_EVENT_WORKER)) + if (!post_init_event_thread(thd)) { - sql_print_error("SCHEDULER: Startup failure."); - startup_error= TRUE; - event->spawn_thread_finish(thd); - } - else - event->set_thread_id(thd->thread_id); + DBUG_PRINT("info", ("Baikonur, time is %d, BURAN reporting and operational." + "THD=0x%lx", time(NULL), thd)); + + sql_print_information("SCHEDULER: [%s.%s of %s] executing in thread %lu. " + "Execution %u", + event->dbname.str, event->name.str, + event->definer.str, thd->thread_id, + event->execution_count); - DBUG_PRINT("info", ("master_access=%d db_access=%d", - thd->security_ctx->master_access, thd->security_ctx->db_access)); - /* - If we don't change it before we send the signal back, then an intermittent - DROP EVENT will take LOCK_scheduler_data and try to kill this thread, because - event->thread_id is already real. However, because thd->security_ctx->user - is not initialized then a crash occurs in kill_one_thread(). Thus, we have - 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); - DBUG_PRINT("info", ("master_access=%d db_access=%d", - thd->security_ctx->master_access, thd->security_ctx->db_access)); - - /* Signal the scheduler thread that we have started successfully */ - pthread_mutex_lock(¶m->LOCK_started); - param->started= TRUE; - pthread_cond_signal(¶m->COND_started); - pthread_mutex_unlock(¶m->LOCK_started); - - if (!startup_error) - { - thd->init_for_queries(); thd->enable_slow_log= TRUE; - event->set_thread_id(thd->thread_id); - sql_print_information("SCHEDULER: [%s.%s of %s] executing in thread %lu", - event->dbname.str, event->name.str, - event->definer.str, thd->thread_id); + ret= event->execute(thd); - ret= event->execute(thd, thd->mem_root); evex_print_warnings(thd, event); - sql_print_information("SCHEDULER: [%s.%s of %s] executed. RetCode=%d", - event->dbname.str, event->name.str, - event->definer.str, ret); + + sql_print_information("SCHEDULER: [%s.%s of %s] executed in thread %lu. " + "RetCode=%d", event->dbname.str, event->name.str, + event->definer.str, thd->thread_id, ret); if (ret == EVEX_COMPILE_ERROR) sql_print_information("SCHEDULER: COMPILE ERROR for event %s.%s of %s", event->dbname.str, event->name.str, event->definer.str); else if (ret == EVEX_MICROSECOND_UNSUP) sql_print_information("SCHEDULER: MICROSECOND is not supported"); - - DBUG_PRINT("info", ("master_access=%d db_access=%d", - thd->security_ctx->master_access, thd->security_ctx->db_access)); - - /* If true is returned, we are expected to free it */ - if (event->spawn_thread_finish(thd)) - { - DBUG_PRINT("info", ("Freeing object pointer")); - delete event; - } } + DBUG_PRINT("info", ("BURAN %s.%s is landing!", event->dbname.str, + event->name.str)); + delete event; - if (thd) - { - thd->proc_info= "Clearing"; - DBUG_ASSERT(thd->net.buff != 0); - /* - Free it here because net.vio is NULL for us => THD::~THD will check it - and won't call net_end(&net); See also replication code. - */ - net_end(&thd->net); - DBUG_PRINT("info", ("Worker thread %lu exiting", thd->thread_id)); - VOID(pthread_mutex_lock(&LOCK_thread_count)); - thread_count--; - thread_running--; - delete thd; - VOID(pthread_mutex_unlock(&LOCK_thread_count)); - } + deinit_event_thread(thd); - my_thread_end(); DBUG_RETURN(0); // Can't return anything here } /* - Constructor of class Event_scheduler. - - SYNOPSIS - Event_scheduler::Event_scheduler() -*/ - -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) -{ -} - - -/* - Returns the singleton instance of the class. + Performs initialization of the scheduler data, outside of the + threading primitives. SYNOPSIS - Event_scheduler::get_instance() - - RETURN VALUE - address + Event_scheduler::init_scheduler() */ -Event_scheduler* -Event_scheduler::get_instance() -{ - DBUG_ENTER("Event_scheduler::get_instance"); - DBUG_RETURN(&singleton); -} - - -/* - The implementation of full-fledged initialization. - - SYNOPSIS - Event_scheduler::init() - - RETURN VALUE - FALSE OK - TRUE Error -*/ - -bool -Event_scheduler::init() +void +Event_scheduler::init_scheduler(Event_queue *q) { - int i= 0; - bool ret= FALSE; - DBUG_ENTER("Event_scheduler::init"); - DBUG_PRINT("enter", ("this=%p", this)); - - LOCK_SCHEDULER_DATA(); - for (;i < COND_LAST; i++) - if (pthread_cond_init(&cond_vars[i], NULL)) - { - sql_print_error("SCHEDULER: Unable to initalize conditions"); - 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; - } - + LOCK_DATA(); + queue= q; + started_events= 0; + scheduler_thd= NULL; state= INITIALIZED; -end: - UNLOCK_SCHEDULER_DATA(); - DBUG_RETURN(ret); + UNLOCK_DATA(); } -/* - Frees all memory allocated by the scheduler object. - - SYNOPSIS - Event_scheduler::destroy() - - RETURN VALUE - FALSE OK - TRUE Error -*/ - void -Event_scheduler::destroy() -{ - DBUG_ENTER("Event_scheduler"); - - 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]); - state= UNINITIALIZED; - break; - default: - sql_print_error("SCHEDULER: Destroying while state is %d", state); - /* I trust my code but ::safe() > ::sorry() */ - DBUG_ASSERT(0); - break; - } - UNLOCK_SCHEDULER_DATA(); - - DBUG_VOID_RETURN; -} - - -/* - 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 -*/ - -enum Event_scheduler::enum_error_code -Event_scheduler::create_event(THD *thd, Event_timed *et, bool check_existence) -{ - enum enum_error_code 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, FALSE)) - { - res= OP_ALREADY_EXISTS; - goto end; - } - - /* We need to load the event on scheduler_root */ - if (!(res= load_named_event(thd, et, &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, Event_timed *et) -{ - 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)); - - 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, 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 -*/ - -enum Event_scheduler::enum_error_code -Event_scheduler::update_event(THD *thd, Event_timed *et, - LEX_STRING *new_schema, - LEX_STRING *new_name) -{ - enum enum_error_code res; - 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, 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= load_named_event(thd, et, &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; - } - - 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); -} +Event_scheduler::deinit_scheduler() {} /* - Searches for an event in the scheduler queue + Inits scheduler's threading primitives. 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); -} - - -/* - 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 + Event_scheduler::init_mutexes() */ void -Event_scheduler::drop_matching_events(THD *thd, LEX_STRING *pattern, - bool (*comparator)(Event_timed *,LEX_STRING *)) +Event_scheduler::init_mutexes() { - 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; + pthread_mutex_init(&LOCK_scheduler_state, MY_MUTEX_INIT_FAST); + pthread_cond_init(&COND_state, NULL); } /* - Drops all events from the in-memory queue and disk that are from - certain schema. + Deinits scheduler's threading primitives. SYNOPSIS - Event_scheduler::drop_schema_events() - thd THD - db The schema name - - RETURN VALUE - -1 Scheduler not working - >=0 Number of dropped events + Event_scheduler::deinit_mutexes() */ -int -Event_scheduler::drop_schema_events(THD *thd, LEX_STRING *schema) +void +Event_scheduler::deinit_mutexes() { - 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); - - ret= db_drop_events_from_table(thd, schema); - UNLOCK_SCHEDULER_DATA(); - - DBUG_RETURN(ret); + pthread_mutex_destroy(&LOCK_scheduler_state); + pthread_cond_destroy(&COND_state); } -extern pthread_attr_t connection_attrib; - - /* - Starts the event scheduler + Starts the scheduler (again). Creates a new THD and passes it to + a forked thread. Does not wait for acknowledgement from the new + thread that it has started. Asynchronous starting. Most of the + needed initializations are done in the current thread to minimize + the chance of failure in the spawned thread. SYNOPSIS Event_scheduler::start() RETURN VALUE FALSE OK - TRUE Error + TRUE Error (not reported) */ bool Event_scheduler::start() { + THD *new_thd= NULL; bool ret= FALSE; pthread_t th; + struct scheduler_param *scheduler_param_value; DBUG_ENTER("Event_scheduler::start"); - LOCK_SCHEDULER_DATA(); - /* If already working or starting don't make another attempt */ - DBUG_ASSERT(state == INITIALIZED); + LOCK_DATA(); + DBUG_PRINT("info", ("state before action %s", scheduler_states_names[state])); if (state > INITIALIZED) - { - DBUG_PRINT("info", ("scheduler is already running or starting")); - ret= TRUE; goto end; - } - /* - Now if another thread calls start it will bail-out because the branch - above will be executed. Thus no two or more child threads will be forked. - If the child thread cannot start for some reason then `state` is set - to CANTSTART and COND_started is also signaled. In this case we - set `state` back to INITIALIZED so another attempt to start the scheduler - can be made. - */ - state= COMMENCING; - /* Fork */ - if (pthread_create(&th, &connection_attrib, event_scheduler_thread, - (void*)this)) + if (!(new_thd= new THD)) { - DBUG_PRINT("error", ("cannot create a new thread")); - state= INITIALIZED; + sql_print_error("SCHEDULER: Cannot init manager event thread"); ret= TRUE; goto end; } + pre_init_event_thread(new_thd); + new_thd->system_thread= SYSTEM_THREAD_EVENT_SCHEDULER; + new_thd->command= COM_DAEMON; - /* 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); + scheduler_param_value= + (struct scheduler_param *)my_malloc(sizeof(struct scheduler_param), MYF(0)); + scheduler_param_value->thd= new_thd; + scheduler_param_value->scheduler= this; - /* - If we cannot start for some reason then don't prohibit further attempts. - Set back to INITIALIZED. - */ - if (state == CANTSTART) + scheduler_thd= new_thd; + DBUG_PRINT("info", ("Setting state go RUNNING")); + state= RUNNING; + DBUG_PRINT("info", ("Forking new thread for scheduduler. THD=0x%lx", new_thd)); + if (pthread_create(&th, &connection_attrib, event_scheduler_thread, + (void*)scheduler_param_value)) { + DBUG_PRINT("error", ("cannot create a new thread")); state= INITIALIZED; + scheduler_thd= NULL; ret= TRUE; - goto end; - } + new_thd->proc_info= "Clearing"; + DBUG_ASSERT(new_thd->net.buff != 0); + net_end(&new_thd->net); + pthread_mutex_lock(&LOCK_thread_count); + thread_count--; + thread_running--; + delete new_thd; + pthread_mutex_unlock(&LOCK_thread_count); + } end: - UNLOCK_SCHEDULER_DATA(); - DBUG_RETURN(ret); -} - - -/* - Starts the event scheduler in suspended mode. - - SYNOPSIS - Event_scheduler::start_suspended() + UNLOCK_DATA(); - RETURN VALUE - TRUE OK - FALSE Error -*/ - -bool -Event_scheduler::start_suspended() -{ - DBUG_ENTER("Event_scheduler::start_suspended"); - start_scheduler_suspended= TRUE; - DBUG_RETURN(start()); -} - - - -/* - Report back that we cannot start. Used for ocasions where - we can't go into ::run() and have to report externally. - - SYNOPSIS - Event_scheduler::report_error_during_start() -*/ - -inline void -Event_scheduler::report_error_during_start() -{ - DBUG_ENTER("Event_scheduler::report_error_during_start"); - - LOCK_SCHEDULER_DATA(); - state= CANTSTART; - DBUG_PRINT("info", ("Sending back COND_started_or_stopped")); - pthread_cond_signal(&cond_vars[COND_started_or_stopped]); - UNLOCK_SCHEDULER_DATA(); - - DBUG_VOID_RETURN; + DBUG_RETURN(ret); } /* - The internal loop of the event scheduler + The main loop of the scheduler. SYNOPSIS Event_scheduler::run() thd Thread RETURN VALUE - FALSE OK - TRUE Failure + FALSE OK + TRUE Error (Serious error) */ bool Event_scheduler::run(THD *thd) { - int ret; + int res= FALSE; struct timespec abstime; + Event_job_data *job_data; DBUG_ENTER("Event_scheduler::run"); - DBUG_PRINT("enter", ("thd=%p", thd)); - - LOCK_SCHEDULER_DATA(); - ret= load_events_from_db(thd); - - if (!ret) - { - thread_id= thd->thread_id; - state= start_scheduler_suspended? SUSPENDED:RUNNING; - start_scheduler_suspended= FALSE; - } - else - state= CANTSTART; - - DBUG_PRINT("info", ("Sending back COND_started_or_stopped")); - pthread_cond_signal(&cond_vars[COND_started_or_stopped]); - if (ret) - { - UNLOCK_SCHEDULER_DATA(); - DBUG_RETURN(TRUE); - } - if (!check_n_suspend_if_needed(thd)) - UNLOCK_SCHEDULER_DATA(); sql_print_information("SCHEDULER: Manager thread started with id %lu", thd->thread_id); - abstime.tv_nsec= 0; - while (is_running_or_suspended()) - { - Event_timed *et; - - LOCK_SCHEDULER_DATA(); - if (check_n_wait_for_non_empty_queue(thd)) - continue; - - /* On TRUE data is unlocked, go back to the beginning */ - if (check_n_suspend_if_needed(thd)) - continue; + /* + Recalculate the values in the queue because there could have been stops + in executions of the scheduler and some times could have passed by. + */ + queue->recalculate_activation_times(thd); - /* Guaranteed locked here */ - if (state == IN_SHUTDOWN || shutdown_in_progress) + while (is_running()) + { + /* Gets a minimized version */ + if (queue->get_top_for_execution_if_time(thd, &job_data)) { - UNLOCK_SCHEDULER_DATA(); + sql_print_information("SCHEDULER: Serious error during getting next " + "event to execute. Stopping"); break; } - DBUG_ASSERT(state == RUNNING); - - et= (Event_timed *)queue_top(&queue); - - /* Skip disabled events */ - if (et->status != Event_timed::ENABLED) - { - /* - It could be a one-timer scheduled for a time, already in the past when the - scheduler was suspended. - */ - sql_print_information("SCHEDULER: Found a disabled event %*s.%*s in the queue", - et->dbname.length, et->dbname.str, et->name.length, - et->name.str); - queue_remove(&queue, 0); - /* ToDo: check this again */ - if (et->dropped) - et->drop(thd); - delete et; - UNLOCK_SCHEDULER_DATA(); - continue; - } - thd->proc_info= (char *)"Computing"; - DBUG_PRINT("evex manager",("computing time to sleep till next exec")); - /* Timestamp is in UTC */ - abstime.tv_sec= sec_since_epoch_TIME(&et->execute_at); - thd->end_time(); - if (abstime.tv_sec > thd->query_start()) + DBUG_PRINT("info", ("get_top returned job_data=0x%lx", job_data)); + if (job_data) { - /* Event trigger time is in the future */ - thd->proc_info= (char *)"Sleep"; - DBUG_PRINT("info", ("Going to sleep. Should wakeup after approx %d secs", - abstime.tv_sec - thd->query_start())); - DBUG_PRINT("info", ("Entering condition because waiting for activation")); - /* - Use THD::enter_cond()/exit_cond() or we won't be able to kill a - sleeping thread. Though ::stop() can do it by sending COND_new_work - an user can't by just issuing 'KILL x'; . In the latter case - pthread_cond_timedwait() will wait till `abstime`. - "Sleeping until next time" - */ - thd->enter_cond(&cond_vars[COND_new_work],&LOCK_scheduler_data,"Sleeping"); - - pthread_cond_timedwait(&cond_vars[COND_new_work], &LOCK_scheduler_data, - &abstime); - - DBUG_PRINT("info", ("Manager woke up. state is %d", state)); - /* - If we get signal we should recalculate the whether it's the right time - because there could be : - 1. Spurious wake-up - 2. The top of the queue was changed (new one becase of add/drop/replace) - */ - /* This will do implicit UNLOCK_SCHEDULER_DATA() */ - thd->exit_cond(""); + if ((res= execute_top(thd, job_data))) + break; } else { - thd->proc_info= (char *)"Executing"; - /* - Execute the event. An error may occur if a thread cannot be forked. - In this case stop the manager. - We should enter ::execute_top() with locked LOCK_scheduler_data. - */ - int ret= execute_top(thd); - UNLOCK_SCHEDULER_DATA(); - if (ret) - break; + DBUG_ASSERT(thd->killed); + DBUG_PRINT("info", ("job_data is NULL, the thread was killed")); } + DBUG_PRINT("info", ("state=%s", scheduler_states_names[state].str)); } - - thd->proc_info= (char *)"Cleaning"; - - LOCK_SCHEDULER_DATA(); - /* - It's possible that a user has used (SQL)COM_KILL. Hence set the appropriate - state because it is only set by ::stop(). - */ - if (state != IN_SHUTDOWN) - { - DBUG_PRINT("info", ("We got KILL but the but not from ::stop()")); - state= IN_SHUTDOWN; - } - UNLOCK_SCHEDULER_DATA(); - - sql_print_information("SCHEDULER: Shutting down"); - - thd->proc_info= (char *)"Cleaning queue"; - clean_queue(thd); - THD_CHECK_SENTRY(thd); - - /* free mamager_root memory but don't destroy the root */ - thd->proc_info= (char *)"Cleaning memory root"; - free_root(&scheduler_root, MYF(0)); - THD_CHECK_SENTRY(thd); - - /* - We notify the waiting thread which shutdowns us that we have cleaned. - There are few more instructions to be executed in this pthread but - they don't affect manager structures thus it's safe to signal already - at this point. - */ - LOCK_SCHEDULER_DATA(); - thd->proc_info= (char *)"Sending shutdown signal"; - DBUG_PRINT("info", ("Sending COND_started_or_stopped")); - if (state == IN_SHUTDOWN) - pthread_cond_signal(&cond_vars[COND_started_or_stopped]); - + LOCK_DATA(); + DBUG_PRINT("info", ("Signalling back to the stopper COND_state")); state= INITIALIZED; - /* - We set it here because ::run() can stop not only because of ::stop() - call but also because of `KILL x` - */ - thread_id= 0; + pthread_cond_signal(&COND_state); + UNLOCK_DATA(); sql_print_information("SCHEDULER: Stopped"); - UNLOCK_SCHEDULER_DATA(); - - /* We have modified, we set back */ - thd->query= NULL; - thd->query_length= 0; - DBUG_RETURN(FALSE); + DBUG_RETURN(res); } /* - Executes the top element of the queue. Auxiliary method for ::run(). + Creates a new THD instance and then forks a new thread, while passing + the THD pointer and job_data to it. SYNOPSIS Event_scheduler::execute_top() RETURN VALUE - FALSE OK - TRUE Failure - - NOTE - NO locking is done. EXPECTED is that the caller should have locked - the queue (w/ LOCK_scheduler_data). + FALSE OK + TRUE Error (Serious error) */ bool -Event_scheduler::execute_top(THD *thd) +Event_scheduler::execute_top(THD *thd, Event_job_data *job_data) { - int spawn_ret_code; - bool ret= FALSE; + THD *new_thd; + pthread_t th; + int res= 0; 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); - - pthread_mutex_lock(¶m.LOCK_started); - /* - We don't lock LOCK_scheduler_data fpr workers_increment() because it's a - pre-requisite for calling the current_method. - */ - switch ((spawn_ret_code= et->spawn_now(event_worker_thread, ¶m))) { - case EVENT_EXEC_CANT_FORK: - /* - We don't lock LOCK_scheduler_data here because it's a pre-requisite - for calling the current_method. - */ - sql_print_error("SCHEDULER: Problem while trying to create a thread"); - ret= TRUE; - break; - case EVENT_EXEC_ALREADY_EXEC: - /* - We don't lock LOCK_scheduler_data here because it's a pre-requisite - for calling the current_method. - */ - sql_print_information("SCHEDULER: %s.%s in execution. Skip this time.", - et->dbname.str, et->name.str); - if ((et->flags & EVENT_EXEC_NO_MORE) || et->status == Event_timed::DISABLED) - queue_remove(&queue, 0);// 0 is top, internally 1 - else - queue_replaced(&queue); - break; - default: - DBUG_ASSERT(!spawn_ret_code); - if ((et->flags & EVENT_EXEC_NO_MORE) || et->status == Event_timed::DISABLED) - queue_remove(&queue, 0);// 0 is top, internally 1 - else - queue_replaced(&queue); - /* - We don't lock LOCK_scheduler_data here because it's a pre-requisite - for calling the current_method. - */ - if (likely(!spawn_ret_code)) - { - /* Wait the forked thread to start */ - do { - pthread_cond_wait(¶m.COND_started, ¶m.LOCK_started); - } while (!param.started); - } - /* - param was allocated on the stack so no explicit delete as well as - in this moment it's no more used in the spawned thread so it's safe - to be deleted. - */ - break; - } - pthread_mutex_unlock(¶m.LOCK_started); - /* `param` is on the stack and will be destructed by the compiler */ - - DBUG_RETURN(ret); -} - + if (!(new_thd= new THD())) + goto error; -/* - Cleans the scheduler's queue. Auxiliary method for ::run(). - - SYNOPSIS - Event_scheduler::clean_queue() - thd Thread -*/ + pre_init_event_thread(new_thd); + new_thd->system_thread= SYSTEM_THREAD_EVENT_WORKER; + job_data->thd= new_thd; + DBUG_PRINT("info", ("BURAN %s@%s ready for start t-3..2..1..0..ignition", + job_data->dbname.str, job_data->name.str)); -void -Event_scheduler::clean_queue(THD *thd) -{ - CHARSET_INFO *scs= system_charset_info; - uint i; - DBUG_ENTER("Event_scheduler::clean_queue"); - DBUG_PRINT("enter", ("thd=%p", thd)); + /* Major failure */ + if ((res= pthread_create(&th, &connection_attrib, event_worker_thread, + job_data))) + goto error; - LOCK_SCHEDULER_DATA(); - stop_all_running_events(thd); - UNLOCK_SCHEDULER_DATA(); + ++started_events; - sql_print_information("SCHEDULER: Emptying the queue"); + DBUG_PRINT("info", ("Launch succeeded. BURAN is in THD=0x%lx", new_thd)); + DBUG_RETURN(FALSE); - /* empty the queue */ - for (i= 0; i < queue.elements; ++i) +error: + DBUG_PRINT("error", ("Baikonur, we have a problem! res=%d", res)); + if (new_thd) { - Event_timed *et= (Event_timed *) queue_element(&queue, i); - et->free_sp(); - delete et; + new_thd->proc_info= "Clearing"; + DBUG_ASSERT(new_thd->net.buff != 0); + net_end(&new_thd->net); + pthread_mutex_lock(&LOCK_thread_count); + thread_count--; + thread_running--; + delete new_thd; + pthread_mutex_unlock(&LOCK_thread_count); } - resize_queue(&queue, 0); - - DBUG_VOID_RETURN; + delete job_data; + DBUG_RETURN(TRUE); } /* - Stops all running events + Checks whether the state of the scheduler is RUNNING SYNOPSIS - Event_scheduler::stop_all_running_events() - thd Thread - - NOTE - LOCK_scheduler data must be acquired prior to call to this method + Event_scheduler::is_running() + + RETURN VALUE + TRUE RUNNING + FALSE Not RUNNING */ -void -Event_scheduler::stop_all_running_events(THD *thd) +bool +Event_scheduler::is_running() { - CHARSET_INFO *scs= system_charset_info; - uint i; - DYNAMIC_ARRAY running_threads; - THD *tmp; - DBUG_ENTER("Event_scheduler::stop_all_running_events"); - DBUG_PRINT("enter", ("workers_count=%d", workers_count())); - - my_init_dynamic_array(&running_threads, sizeof(ulong), 10, 10); - - bool had_super= FALSE; - VOID(pthread_mutex_lock(&LOCK_thread_count)); // For unlink from list - I_List_iterator<THD> it(threads); - while ((tmp=it++)) - { - if (tmp->command == COM_DAEMON) - continue; - if (tmp->system_thread == SYSTEM_THREAD_EVENT_WORKER) - push_dynamic(&running_threads, (gptr) &tmp->thread_id); - } - VOID(pthread_mutex_unlock(&LOCK_thread_count)); - - /* We need temporarily SUPER_ACL to be able to kill our offsprings */ - if (!(thd->security_ctx->master_access & SUPER_ACL)) - thd->security_ctx->master_access|= SUPER_ACL; - else - had_super= TRUE; - - char tmp_buff[10*STRING_BUFFER_USUAL_SIZE]; - char int_buff[STRING_BUFFER_USUAL_SIZE]; - String tmp_string(tmp_buff, sizeof(tmp_buff), scs); - String int_string(int_buff, sizeof(int_buff), scs); - tmp_string.length(0); - - for (i= 0; i < running_threads.elements; ++i) - { - int ret; - ulong thd_id= *dynamic_element(&running_threads, i, ulong*); - - int_string.set((longlong) thd_id,scs); - tmp_string.append(int_string); - if (i < running_threads.elements - 1) - tmp_string.append(' '); - - if ((ret= kill_one_thread(thd, thd_id, FALSE))) - { - sql_print_error("SCHEDULER: Error killing %lu code=%d", thd_id, ret); - break; - } - } - if (running_threads.elements) - sql_print_information("SCHEDULER: Killing workers :%s", tmp_string.c_ptr()); - - if (!had_super) - thd->security_ctx->master_access &= ~SUPER_ACL; - - delete_dynamic(&running_threads); - - sql_print_information("SCHEDULER: Waiting for worker threads to finish"); - - while (workers_count()) - my_sleep(100000); - - DBUG_VOID_RETURN; + LOCK_DATA(); + bool ret= (state == RUNNING); + UNLOCK_DATA(); + return ret; } /* - Stops the event scheduler + Stops the scheduler (again). Waits for acknowledgement from the + scheduler that it has stopped - synchronous stopping. SYNOPSIS Event_scheduler::stop() RETURN VALUE - OP_OK OK - OP_CANT_KILL Error during stopping of manager thread - OP_NOT_RUNNING Manager not working - - NOTE - The caller must have acquited LOCK_scheduler_data. + FALSE OK + TRUE Error (not reported) */ -enum Event_scheduler::enum_error_code +bool Event_scheduler::stop() { THD *thd= current_thd; DBUG_ENTER("Event_scheduler::stop"); - DBUG_PRINT("enter", ("thd=%p", current_thd)); - - LOCK_SCHEDULER_DATA(); - if (!is_running_or_suspended()) - { - /* - 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. - */ - DBUG_PRINT("info", ("manager not running but %d. doing nothing", state)); - UNLOCK_SCHEDULER_DATA(); - DBUG_RETURN(OP_NOT_RUNNING); - } - state= IN_SHUTDOWN; + DBUG_PRINT("enter", ("thd=0x%lx", current_thd)); - DBUG_PRINT("info", ("Manager thread has id %d", thread_id)); - sql_print_information("SCHEDULER: Killing manager thread %lu", thread_id); - - /* - Sending the COND_new_work to ::run() is a way to get this working without - race conditions. If we use kill_one_thread() it will call THD::awake() and - because in ::run() both THD::enter_cond()/::exit_cond() are used, - THD::awake() will try to lock LOCK_scheduler_data. If we UNLOCK it before, - then the pthread_cond_signal(COND_started_or_stopped) could be signaled in - ::run() and we can miss the signal before we relock. A way is to use - another mutex for this shutdown procedure but better not. - */ - pthread_cond_signal(&cond_vars[COND_new_work]); - /* Or we are suspended - then we should wake up */ - pthread_cond_signal(&cond_vars[COND_suspend_or_resume]); + LOCK_DATA(); + DBUG_PRINT("info", ("state before action %s", scheduler_states_names[state])); + if (state != RUNNING) + goto end; /* Guarantee we don't catch spurious signals */ - sql_print_information("SCHEDULER: Waiting the manager thread to reply"); - while (state != INITIALIZED) - { + do { 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); - } - DBUG_PRINT("info", ("Manager thread has cleaned up. Set state to INIT")); - UNLOCK_SCHEDULER_DATA(); - - DBUG_RETURN(OP_OK); -} - - -/* - Suspends or resumes the scheduler. - SUSPEND - it won't execute any event till resumed. - RESUME - it will resume if suspended. - - SYNOPSIS - Event_scheduler::suspend_or_resume() - - RETURN VALUE - OP_OK OK -*/ - -enum Event_scheduler::enum_error_code -Event_scheduler::suspend_or_resume( - enum Event_scheduler::enum_suspend_or_resume action) -{ - DBUG_ENTER("Event_scheduler::suspend_or_resume"); - DBUG_PRINT("enter", ("action=%d", action)); - - LOCK_SCHEDULER_DATA(); + "thread. Current value of state is %s . " + "workers count=%d", scheduler_states_names[state].str, + workers_count())); + /* + NOTE: We don't use kill_one_thread() because it can't kill COM_DEAMON + threads. In addition, kill_one_thread() requires THD but during shutdown + current_thd is NULL. Hence, if kill_one_thread should be used it has to + be modified to kill also daemons, by adding a flag, and also we have to + create artificial THD here. To save all this work, we just do what + kill_one_thread() does to kill a thread. See also sql_repl.cc for similar + usage. + */ - if ((action == SUSPEND && state == SUSPENDED) || - (action == RESUME && state == RUNNING)) - { - DBUG_PRINT("info", ("Either trying to suspend suspended or resume " - "running scheduler. Doing nothing.")); - } - else - { - /* Wake the main thread up if he is asleep */ - DBUG_PRINT("info", ("Sending signal")); - if (action==SUSPEND) - { - state= SUSPENDED; - pthread_cond_signal(&cond_vars[COND_new_work]); - } - else - { - state= RUNNING; - 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); - DBUG_PRINT("info", ("Got response")); - } - UNLOCK_SCHEDULER_DATA(); - DBUG_RETURN(OP_OK); + state= STOPPING; + DBUG_PRINT("info", ("Manager thread has id %d", scheduler_thd->thread_id)); + /* Lock from delete */ + pthread_mutex_lock(&scheduler_thd->LOCK_delete); + /* This will wake up the thread if it waits on Queue's conditional */ + sql_print_information("SCHEDULER: Killing manager thread %lu", + scheduler_thd->thread_id); + scheduler_thd->awake(THD::KILL_CONNECTION); + pthread_mutex_unlock(&scheduler_thd->LOCK_delete); + + /* thd could be 0x0, when shutting down */ + sql_print_information("SCHEDULER: Waiting the manager thread to reply"); + COND_STATE_WAIT(thd, NULL, "Waiting scheduler to stop"); + } while (state == STOPPING); + DBUG_PRINT("info", ("Manager thread has cleaned up. Set state to INIT")); + /* + The rationale behind setting it to NULL here but not destructing it + beforehand is because the THD will be deinited in event_scheduler_thread(). + It's more clear when the post_init and the deinit is done in one function. + Here we just mark that the scheduler doesn't have a THD anymore. Though for + milliseconds the old thread could exist we can't use it anymore. When we + unlock the mutex in this function a little later the state will be + INITIALIZED. Therefore, a connection thread could enter the critical section + and will create a new THD object. + */ + scheduler_thd= NULL; +end: + UNLOCK_DATA(); + DBUG_RETURN(FALSE); } /* - Returns the number of executing events. + Returns the number of living event worker threads. SYNOPSIS Event_scheduler::workers_count() @@ -1787,157 +650,33 @@ Event_scheduler::workers_count() uint count= 0; DBUG_ENTER("Event_scheduler::workers_count"); - VOID(pthread_mutex_lock(&LOCK_thread_count)); // For unlink from list + pthread_mutex_lock(&LOCK_thread_count); // For unlink from list I_List_iterator<THD> it(threads); while ((tmp=it++)) - { - if (tmp->command == COM_DAEMON) - continue; if (tmp->system_thread == SYSTEM_THREAD_EVENT_WORKER) ++count; - } - VOID(pthread_mutex_unlock(&LOCK_thread_count)); + pthread_mutex_unlock(&LOCK_thread_count); DBUG_PRINT("exit", ("%d", count)); - DBUG_RETURN(count); + DBUG_RETURN(count); } /* - Checks and suspends if needed - - 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. - - NOTE - The caller should have locked LOCK_scheduler_data! - The mutex will be unlocked in case this function returns TRUE -*/ - -bool -Event_scheduler::check_n_suspend_if_needed(THD *thd) -{ - bool was_suspended= FALSE; - DBUG_ENTER("Event_scheduler::check_n_suspend_if_needed"); - if (thd->killed && !shutdown_in_progress) - { - state= SUSPENDED; - thd->killed= THD::NOT_KILLED; - } - if (state == SUSPENDED) - { - 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]); - sql_print_information("SCHEDULER: Suspending operations"); - was_suspended= TRUE; - } - while (state == SUSPENDED) - { - cond_wait(COND_suspend_or_resume, &LOCK_scheduler_data); - DBUG_PRINT("info", ("Woke up after waiting on COND_suspend_or_resume")); - if (state != SUSPENDED) - { - pthread_cond_signal(&cond_vars[COND_suspend_or_resume]); - sql_print_information("SCHEDULER: Resuming operations"); - } - } - 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); - } - /* This will implicitly unlock LOCK_scheduler_data */ - thd->exit_cond(""); - } - DBUG_RETURN(was_suspended); -} - - -/* - Checks for empty queue and waits till new element gets in - - SYNOPSIS - Event_scheduler::check_n_wait_for_non_empty_queue() - thd Thread - - RETURN VALUE - FALSE Did not wait - LOCK_scheduler_data still locked. - TRUE Waited - LOCK_scheduler_data unlocked. - - NOTE - The caller should have locked LOCK_scheduler_data! -*/ - -bool -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])); - - if (!queue.elements) - 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) - { - slept= TRUE; - DBUG_PRINT("info", ("Entering condition because of empty queue")); - cond_wait(COND_new_work, &LOCK_scheduler_data); - DBUG_PRINT("info", ("Manager woke up. Hope we have events now. state=%d", - state)); - /* - exit_cond does implicit mutex_UNLOCK, we needed it locked if - 1. we loop again - 2. end the current loop and start doing calculations - */ - } - if (slept) - thd->exit_cond(""); - - DBUG_PRINT("exit", ("q.elements=%lu state=%s thd->killed=%d", - queue.elements, states_names[state], thd->killed)); - - DBUG_RETURN(slept); -} - - -/* - Wrapper for pthread_mutex_lock + Auxiliary function for locking LOCK_scheduler_state. Used + by the LOCK_DATA macro. 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() + func Which function is requesting mutex lock + line On which line mutex lock is requested */ -inline void +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); + DBUG_ENTER("Event_scheduler::lock_data"); + DBUG_PRINT("enter", ("func=%s line=%u", func, line)); + pthread_mutex_lock(&LOCK_scheduler_state); mutex_last_locked_in_func= func; mutex_last_locked_at_line= line; mutex_scheduler_data_locked= TRUE; @@ -1946,518 +685,98 @@ Event_scheduler::lock_data(const char *func, uint line) /* - Wrapper for pthread_mutex_unlock + Auxiliary function for unlocking LOCK_scheduler_state. Used + by the UNLOCK_DATA macro. SYNOPSIS Event_scheduler::unlock_data() - mutex Mutex to unlock - line The line number on which the unlock is done + func Which function is requesting mutex unlock + line On which line mutex unlock is requested */ -inline void +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)); + DBUG_ENTER("Event_scheduler::unlock_data"); + DBUG_PRINT("enter", ("func=%s line=%u", 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); + pthread_mutex_unlock(&LOCK_scheduler_state); DBUG_VOID_RETURN; } /* - Wrapper for pthread_cond_wait + Wrapper for pthread_cond_wait/timedwait SYNOPSIS Event_scheduler::cond_wait() - cond Conditional to wait for - mutex Mutex of the conditional - - RETURN VALUE - Error code of pthread_cond_wait() + thd Thread (Could be NULL during shutdown procedure) + abstime If not null then call pthread_cond_timedwait() + msg Message for thd->proc_info + func Which function is requesting cond_wait + line On which line cond_wait is requested */ -inline int -Event_scheduler::cond_wait(enum Event_scheduler::enum_cond_vars cond, - pthread_mutex_t *mutex) +void +Event_scheduler::cond_wait(THD *thd, struct timespec *abstime, const char* msg, + const char *func, uint line) { - 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 - - SYNOPSIS - Event_scheduler::get_state() -*/ - -enum Event_scheduler::enum_state -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); - DBUG_RETURN(ret); -} - - -/* - Returns whether the scheduler was initialized. - - SYNOPSIS - Event_scheduler::initialized() - - RETURN VALUE - FALSE Was not initialized so far - TRUE Was initialized -*/ - -bool -Event_scheduler::initialized() -{ - DBUG_ENTER("Event_scheduler::initialized"); - DBUG_RETURN(Event_scheduler::get_state() != UNINITIALIZED); -} - - -/* - 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); -} - - -/* - 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); + waiting_on_cond= TRUE; + mutex_last_unlocked_at_line= line; + mutex_scheduler_data_locked= FALSE; + mutex_last_unlocked_in_func= func; + if (thd) + thd->enter_cond(&COND_state, &LOCK_scheduler_state, msg); - if (et_loaded->status != Event_timed::ENABLED) + DBUG_PRINT("info", ("pthread_cond_%swait", abstime? "timed":"")); + if (!abstime) + pthread_cond_wait(&COND_state, &LOCK_scheduler_state); + else + pthread_cond_timedwait(&COND_state, &LOCK_scheduler_state, abstime); + if (thd) { /* - 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. + This will free the lock so we need to relock. Not the best thing to + do but we need to obey cond_wait() */ - delete et_loaded; - DBUG_RETURN(OP_DISABLED_EVENT); - } - - et_loaded->compute_next_execution_time(); - *etn_new= et_loaded; - - DBUG_RETURN(OP_OK); -} - - -/* - 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= Events::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->exit_cond(""); + LOCK_DATA(); } - - 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); + mutex_last_locked_in_func= func; + mutex_last_locked_at_line= line; + mutex_scheduler_data_locked= TRUE; + waiting_on_cond= FALSE; + DBUG_VOID_RETURN; } /* - Destroys mutexes. + Dumps the internal status of the scheduler SYNOPSIS - Event_scheduler::destroy_mutexes() + Event_scheduler::dump_internal_status() */ void -Event_scheduler::destroy_mutexes() -{ - pthread_mutex_destroy(&singleton.LOCK_scheduler_data); -} - - -/* - Dumps some data about the internal status of the scheduler. - - SYNOPSIS - Event_scheduler::dump_internal_status() - thd THD +Event_scheduler::dump_internal_status() +{ + DBUG_ENTER("Event_scheduler::dump_internal_status"); + + puts(""); + puts("Event scheduler status:"); + printf("State : %s\n", scheduler_states_names[state].str); + printf("Thread id : %lu\n", scheduler_thd? scheduler_thd->thread_id : 0); + printf("LLA : %s:%u\n", mutex_last_locked_in_func, + mutex_last_locked_at_line); + printf("LUA : %s:%u\n", mutex_last_unlocked_in_func, + mutex_last_unlocked_at_line); + printf("WOC : %s\n", waiting_on_cond? "YES":"NO"); + printf("Workers : %u\n", workers_count()); + printf("Executed : %llu\n", started_events); + printf("Data locked: %s\n", mutex_scheduler_data_locked ? "YES":"NO"); - RETURN VALUE - 0 OK - 1 Error -*/ - -int -Event_scheduler::dump_internal_status(THD *thd) -{ - DBUG_ENTER("dump_internal_status"); -#ifndef DBUG_OFF - CHARSET_INFO *scs= system_charset_info; - Protocol *protocol= thd->protocol; - List<Item> field_list; - int ret; - char tmp_buff[5*STRING_BUFFER_USUAL_SIZE]; - char int_buff[STRING_BUFFER_USUAL_SIZE]; - String tmp_string(tmp_buff, sizeof(tmp_buff), scs); - String int_string(int_buff, sizeof(int_buff), scs); - tmp_string.length(0); - int_string.length(0); - - field_list.push_back(new Item_empty_string("Name", 20)); - field_list.push_back(new Item_empty_string("Value",20)); - if (protocol->send_fields(&field_list, Protocol::SEND_NUM_ROWS | - Protocol::SEND_EOF)) - DBUG_RETURN(1); - - protocol->prepare_for_resend(); - protocol->store(STRING_WITH_LEN("state"), scs); - protocol->store(states_names[singleton.state].str, - states_names[singleton.state].length, - scs); - - ret= protocol->write(); - /* - If not initialized - don't show anything else. get_instance() - will otherwise implicitly initialize it. We don't want that. - */ - if (singleton.state >= INITIALIZED) - { - /* last locked at*/ - /* - The first thing to do, or get_instance() will overwrite the values. - mutex_last_locked_at_line / mutex_last_unlocked_at_line - */ - protocol->prepare_for_resend(); - 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)); - protocol->store(&tmp_string); - ret= protocol->write(); - - /* last unlocked at*/ - protocol->prepare_for_resend(); - 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)); - protocol->store(&tmp_string); - ret= protocol->write(); - - /* waiting on */ - protocol->prepare_for_resend(); - protocol->store(STRING_WITH_LEN("waiting on condition"), scs); - 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]: - "NONE")); - protocol->store(&tmp_string); - ret= protocol->write(); - - Event_scheduler *scheduler= get_instance(); - - /* workers_count */ - protocol->prepare_for_resend(); - protocol->store(STRING_WITH_LEN("workers_count"), scs); - int_string.set((longlong) scheduler->workers_count(), scs); - protocol->store(&int_string); - ret= protocol->write(); - - /* queue.elements */ - protocol->prepare_for_resend(); - protocol->store(STRING_WITH_LEN("queue.elements"), scs); - int_string.set((longlong) scheduler->queue.elements, scs); - protocol->store(&int_string); - ret= protocol->write(); - - /* scheduler_data_locked */ - protocol->prepare_for_resend(); - protocol->store(STRING_WITH_LEN("scheduler data locked"), scs); - int_string.set((longlong) scheduler->mutex_scheduler_data_locked, scs); - protocol->store(&int_string); - ret= protocol->write(); - } - send_eof(thd); -#endif - DBUG_RETURN(0); + DBUG_VOID_RETURN; } diff --git a/sql/event_scheduler.h b/sql/event_scheduler.h index 5ae310bab2a..dffaf8c056c 100644 --- a/sql/event_scheduler.h +++ b/sql/event_scheduler.h @@ -16,235 +16,105 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -class Event_timed; +class Event_queue; +class Event_job_data; -class THD; -typedef bool * (*event_timed_identifier_comparator)(Event_timed*, Event_timed*); +void +pre_init_event_thread(THD* thd); -int -events_init(); +bool +post_init_event_thread(THD* thd); void -events_shutdown(); +deinit_event_thread(THD *thd); 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 - { - UNINITIALIZED= 0, - INITIALIZED, - COMMENCING, - CANTSTART, - RUNNING, - SUSPENDED, - IN_SHUTDOWN - }; - - enum enum_suspend_or_resume - { - SUSPEND= 1, - RESUME= 2 - }; - - /* Singleton access */ - static Event_scheduler* - get_instance(); - - /* Methods for queue management follow */ - - enum enum_error_code - create_event(THD *thd, Event_timed *et, bool check_existence); - - enum enum_error_code - update_event(THD *thd, Event_timed *et, LEX_STRING *new_schema, - LEX_STRING *new_name); - - bool - drop_event(THD *thd, Event_timed *et); - - - int - drop_schema_events(THD *thd, LEX_STRING *schema); - - int - drop_user_events(THD *thd, LEX_STRING *definer, uint *dropped_num) - { DBUG_ASSERT(0); return 0;} - - uint - events_count(); + Event_scheduler():state(UNINITIALIZED){} + ~Event_scheduler(){} /* State changing methods follow */ bool start(); - enum enum_error_code - stop(); - bool - start_suspended(); + stop(); + /* + Need to be public because has to be called from the function + passed to pthread_create. + */ bool run(THD *thd); - enum enum_error_code - suspend_or_resume(enum enum_suspend_or_resume action); - - bool - init(); + void + init_scheduler(Event_queue *queue); void - destroy(); + deinit_scheduler(); - static void + void init_mutexes(); - - static void - destroy_mutexes(); void - report_error_during_start(); + deinit_mutexes(); /* Information retrieving methods follow */ - - enum enum_state - get_state(); - bool - initialized(); - - static int - dump_internal_status(THD *thd); + is_running(); - static bool - check_system_tables(THD *thd); + void + dump_internal_status(); private: - Event_timed * - find_event(Event_timed *etn, bool remove_from_q); - uint workers_count(); - bool - is_running_or_suspended(); /* helper functions */ bool - execute_top(THD *thd); + execute_top(THD *thd, Event_job_data *job_data); + /* helper functions for working with mutexes & conditionals */ void - clean_queue(THD *thd); - - 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); + lock_data(const char *func, uint line); void - drop_matching_events(THD *thd, LEX_STRING *pattern, - bool (*)(Event_timed *,LEX_STRING *)); + unlock_data(const char *func, uint line); - bool - check_n_suspend_if_needed(THD *thd); + void + cond_wait(THD *thd, struct timespec *abstime, const char* msg, + const char *func, uint line); - bool - check_n_wait_for_non_empty_queue(THD *thd); + pthread_mutex_t LOCK_scheduler_state; - /* Singleton DP is used */ - Event_scheduler(); - - enum enum_cond_vars + enum enum_state { - 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 + UNINITIALIZED = 0, + INITIALIZED, + RUNNING, + STOPPING }; - /* Singleton instance */ - static Event_scheduler singleton; - - /* This is the current status of the life-cycle of the manager. */ + /* This is the current status of the life-cycle of the scheduler. */ 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 - kill_one_thread(). The latter wake ups a thread if it is waiting on a - conditional variable and sets thd->killed to non-zero. - */ - ulong thread_id; + THD *scheduler_thd; - pthread_cond_t cond_vars[COND_LAST]; - static const char * const cond_vars_names[COND_LAST]; + pthread_cond_t COND_state; - /* The MEM_ROOT of the object */ - MEM_ROOT scheduler_root; + Event_queue *queue; - /* 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; bool mutex_scheduler_data_locked; + bool waiting_on_cond; - /* helper functions for working with mutexes & conditionals */ - void - lock_data(const char *func, uint line); - - void - unlock_data(const char *func, uint line); - - int - cond_wait(enum enum_cond_vars, pthread_mutex_t *mutex); + ulonglong started_events; private: /* Prevent use of these */ 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 210cc2c4735..10a8be948ef 100644 --- a/sql/events.cc +++ b/sql/events.cc @@ -15,11 +15,11 @@ 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_timed.h" +#include "event_data_objects.h" +#include "event_db_repository.h" +#include "event_queue.h" #include "event_scheduler.h" -#include "sp.h" #include "sp_head.h" /* @@ -41,144 +41,85 @@ - Add logging to file -Warning: - - For now parallel execution is not possible because the same sp_head cannot - be executed few times!!! There is still no lock attached to particular - event. */ -MEM_ROOT evex_mem_root; -time_t mysql_event_last_create_time= 0L; - +/* + If the user (un)intentionally removes an event directly from mysql.event + the following sequence has to be used to be able to remove the in-memory + counterpart. + 1. CREATE EVENT the_name ON SCHEDULE EVERY 1 SECOND DISABLE DO SELECT 1; + 2. DROP EVENT the_name + + In other words, the first one will create a row in mysql.event . In the + second step because there will be a line, disk based drop will pass and + the scheduler will remove the memory counterpart. The reason is that + in-memory queue does not check whether the event we try to drop from memory + is disabled. Disabled events are not kept in-memory because they are not + eligible for execution. +*/ -const char *event_scheduler_state_names[]= - { "OFF", "0", "ON", "1", "SUSPEND", "2", NullS }; +/* + Keep the order of the first to as in var_typelib + sys_var_event_scheduler::value_ptr() references this array. Keep in + mind! +*/ +static const char *opt_event_scheduler_state_names[]= + { "OFF", "ON", "0", "1", "DISABLED", NullS }; TYPELIB Events::opt_typelib= { - array_elements(event_scheduler_state_names)-1, + array_elements(opt_event_scheduler_state_names)-1, "", - event_scheduler_state_names, + opt_event_scheduler_state_names, NULL }; -ulong Events::opt_event_scheduler= 2; +/* + The order should not be changed. We consider OFF to be equivalent of INT 0 + And ON of 1. If OFF & ON are interchanged the logic in + sys_var_event_scheduler::update() will be broken! +*/ +static const char *var_event_scheduler_state_names[]= { "OFF", "ON", NullS }; -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")} - } +TYPELIB Events::var_typelib= +{ + array_elements(var_event_scheduler_state_names)-1, + "", + var_event_scheduler_state_names, + NULL }; +static +Event_queue events_event_queue; + +static +Event_scheduler events_event_scheduler; + +static +Event_db_repository events_event_db_repository; + +Events Events::singleton; + +enum Events::enum_opt_event_scheduler Events::opt_event_scheduler= + Events::EVENTS_OFF; + + /* Compares 2 LEX strings regarding case. SYNOPSIS sortcmp_lex_string() - - s - first LEX_STRING - t - second LEX_STRING - cs - charset + s First LEX_STRING + t Second LEX_STRING + cs Charset RETURN VALUE - -1 - s < t - 0 - s == t - 1 - s > t - - Notes - TIME.second_part is not considered during comparison + -1 s < t + 0 s == t + 1 s > t */ int sortcmp_lex_string(LEX_STRING s, LEX_STRING t, CHARSET_INFO *cs) @@ -189,6 +130,24 @@ int sortcmp_lex_string(LEX_STRING s, LEX_STRING t, CHARSET_INFO *cs) /* + Accessor for the singleton instance. + + SYNOPSIS + Events::get_instance() + + RETURN VALUE + address +*/ + +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: For @@ -197,19 +156,18 @@ int sortcmp_lex_string(LEX_STRING s, LEX_STRING t, CHARSET_INFO *cs) SYNOPSIS Events::reconstruct_interval_expression() - buf - preallocated String buffer to add the value to - interval - the interval type (for instance YEAR_MONTH) - expression - the value in the lowest entity + buf Preallocated String buffer to add the value to + interval The interval type (for instance YEAR_MONTH) + expression The value in the lowest entity - RETURNS - 0 - OK - 1 - Error + RETURN VALUE + 0 OK + 1 Error */ 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; @@ -269,7 +227,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; @@ -292,7 +250,7 @@ common_1_lev_code: expr= tmp_expr - (tmp_expr/60)*60; /* the code after the switch will finish */ } - break; + break; case INTERVAL_DAY_MICROSECOND: case INTERVAL_HOUR_MICROSECOND: case INTERVAL_MINUTE_MICROSECOND: @@ -321,9 +279,25 @@ common_1_lev_code: return 0; } +/* + Constructor of Events class. It's called when events.o + is loaded. Assigning addressed of static variables in this + object file. + + SYNOPSIS + Events::Events() +*/ + +Events::Events() +{ + scheduler= &events_event_scheduler; + event_queue= &events_event_queue; + db_repository= &events_event_db_repository; +} + /* - Open mysql.event table for read + Opens mysql.event table with specified lock SYNOPSIS Events::open_event_table() @@ -339,1004 +313,594 @@ common_1_lev_code: int Events::open_event_table(THD *thd, enum thr_lock_type lock_type, - TABLE **table) + 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); + return db_repository->open_event_table(thd, lock_type, table); } /* - Find row in open mysql.event table representing event + The function exported to the world for creating of events. SYNOPSIS - evex_db_find_event_aux() - thd Thread context - et event_timed object containing dbname & name - table TABLE object for open mysql.event table. + Events::create_event() + thd [in] THD + parse_data [in] Event's data from parsing stage + if_not_exists [in] Whether IF NOT EXISTS was specified in the DDL 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); -} + FALSE OK + TRUE Error (Reported) - -/* - 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 + NOTES + In case there is an event with the same name (db) and + IF NOT EXISTS is specified, an warning is put into the stack. */ -int -evex_db_find_event_by_name(THD *thd, const LEX_STRING dbname, - const LEX_STRING ev_name, - TABLE *table) +bool +Events::create_event(THD *thd, Event_parse_data *parse_data, bool if_not_exists) { - byte key[MAX_KEY_LENGTH]; - DBUG_ENTER("evex_db_find_event_by_name"); - DBUG_PRINT("enter", ("name: %.*s", ev_name.length, ev_name.str)); + int ret; + DBUG_ENTER("Events::create_event"); + if (unlikely(check_system_tables_error)) + { + my_error(ER_EVENTS_DB_ERROR, MYF(0)); + DBUG_RETURN(TRUE); + } - /* - 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)) + pthread_mutex_lock(&LOCK_event_metadata); + /* On error conditions my_error() is called so no need to handle here */ + if (!(ret= db_repository->create_event(thd, parse_data, if_not_exists))) { - DBUG_PRINT("info", ("Row not found")); - DBUG_RETURN(EVEX_KEY_NOT_FOUND); + if ((ret= event_queue->create_event(thd, parse_data->dbname, + parse_data->name))) + { + DBUG_ASSERT(ret == OP_LOAD_ERROR); + my_error(ER_EVENT_MODIFY_QUEUE_ERROR, MYF(0)); + } } + pthread_mutex_unlock(&LOCK_event_metadata); - DBUG_PRINT("info", ("Row found!")); - DBUG_RETURN(0); + DBUG_RETURN(ret); } /* - Puts some data common to CREATE and ALTER EVENT into a row. + The function exported to the world for alteration of events. SYNOPSIS - evex_fill_row() - thd THD - table the row to fill out - et Event's data + Events::update_event() + thd [in] THD + parse_data [in] Event's data from parsing stage + rename_to [in] Set in case of RENAME TO. RETURN VALUE - 0 - OK - EVEX_GENERAL_ERROR - bad data - EVEX_GET_FIELD_FAILED - field count does not match. table corrupted? + FALSE OK + TRUE Error - DESCRIPTION - Used both when an event is created and when it is altered. + NOTES + et contains data about dbname and event name. + new_name is the new name of the event, if not null this means + that RENAME TO was specified in the query */ -static int -evex_fill_row(THD *thd, TABLE *table, Event_timed *et, my_bool is_update) +bool +Events::update_event(THD *thd, Event_parse_data *parse_data, sp_name *rename_to) { - 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) + int ret; + DBUG_ENTER("Events::update_event"); + LEX_STRING *new_dbname= rename_to ? &rename_to->m_db : NULL; + LEX_STRING *new_name= rename_to ? &rename_to->m_name : NULL; + if (unlikely(check_system_tables_error)) { - 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; + my_error(ER_EVENTS_DB_ERROR, MYF(0)); + DBUG_RETURN(TRUE); } - if (et->expression) + pthread_mutex_lock(&LOCK_event_metadata); + /* On error conditions my_error() is called so no need to handle here */ + if (!(ret= db_repository->update_event(thd, parse_data, new_dbname, new_name))) { - 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) + if ((ret= event_queue->update_event(thd, parse_data->dbname, + parse_data->name, new_dbname, new_name))) { - table->field[Events::FIELD_ENDS]->set_notnull(); - table->field[Events::FIELD_ENDS]-> - store_time(&et->ends, MYSQL_TIMESTAMP_DATETIME); + DBUG_ASSERT(ret == OP_LOAD_ERROR); + my_error(ER_EVENT_MODIFY_QUEUE_ERROR, MYF(0)); } } - 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(); + pthread_mutex_unlock(&LOCK_event_metadata); - 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); + DBUG_RETURN(ret); } /* - Creates an event in mysql.event + Drops an 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 + Events::drop_event() + thd [in] THD + dbname [in] Event's schema + name [in] Event's name + if_exists [in] When set and the event does not exist => + warning onto the stack + only_from_disk [in] Whether to remove the event from the queue too. + In case of Event_job_data::drop() it's needed to + do only disk drop because Event_queue will handle + removal from memory queue. 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". + FALSE OK + TRUE Error (reported) */ -int -db_create_event(THD *thd, Event_timed *et, my_bool create_if_not, - uint *rows_affected) +bool +Events::drop_event(THD *thd, LEX_STRING dbname, LEX_STRING name, bool if_exists, + bool only_from_disk) { - int ret= 0; - CHARSET_INFO *scs= system_charset_info; - TABLE *table; - char old_db_buf[NAME_LEN+1]; - LEX_STRING old_db= { old_db_buf, sizeof(old_db_buf) }; - 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, &old_db, 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])) + int ret; + DBUG_ENTER("Events::drop_event"); + if (unlikely(check_system_tables_error)) { - my_error(ER_EVENT_STORE_FAILED, MYF(0), et->name.str, ret); - goto err; + my_error(ER_EVENTS_DB_ERROR, MYF(0)); + DBUG_RETURN(TRUE); } -#ifdef USE_THIS_CODE_AS_TEMPLATE_WHEN_EVENT_REPLICATION_IS_AGREED - if (mysql_bin_log.is_open()) + pthread_mutex_lock(&LOCK_event_metadata); + /* On error conditions my_error() is called so no need to handle here */ + if (!(ret= db_repository->drop_event(thd, dbname, name, if_exists))) { - 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); + if (!only_from_disk) + event_queue->drop_event(thd, dbname, name); } -#endif - - *rows_affected= 1; -ok: - if (dbchanged) - (void) mysql_change_db(thd, old_db.str, 1); - if (table) - close_thread_tables(thd); - DBUG_RETURN(EVEX_OK); - -err: - if (dbchanged) - (void) mysql_change_db(thd, old_db.str, 1); - if (table) - close_thread_tables(thd); - DBUG_RETURN(EVEX_GENERAL_ERROR); + pthread_mutex_unlock(&LOCK_event_metadata); + DBUG_RETURN(ret); } /* - Used to execute ALTER EVENT. Pendant to Events::update_event(). + Drops all events from a schema 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. + Events::drop_schema_events() + thd Thread + db ASCIIZ schema name */ -static int -db_update_event(THD *thd, Event_timed *et, sp_name *new_name) +void +Events::drop_schema_events(THD *thd, char *db) { - 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; - } + LEX_STRING const db_lex= { db, strlen(db) }; - /* 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]))) + DBUG_ENTER("Events::drop_schema_events"); + DBUG_PRINT("enter", ("dropping events from %s", db)); + if (unlikely(check_system_tables_error)) { - my_error(ER_EVENT_STORE_FAILED, MYF(0), et->name.str, ret); - goto err; + my_error(ER_EVENTS_DB_ERROR, MYF(0)); + DBUG_VOID_RETURN; } - /* close mysql.event or we crash later when loading the event from disk */ - close_thread_tables(thd); - DBUG_RETURN(0); + pthread_mutex_lock(&LOCK_event_metadata); + event_queue->drop_schema_events(thd, db_lex); + db_repository->drop_schema_events(thd, db_lex); + pthread_mutex_unlock(&LOCK_event_metadata); -err: - if (table) - close_thread_tables(thd); - DBUG_RETURN(EVEX_GENERAL_ERROR); + DBUG_VOID_RETURN; } /* - Looks for a named event in mysql.event and in case of success returns - an object will data loaded from the table. + SHOW CREATE EVENT 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 + Events::show_create_event() + thd Thread context + spn The name of the event (db, name) RETURN VALUE - 0 ok In this case *ett is set to the event - # error *ett == 0 + FALSE OK + TRUE Error during writing to the wire */ -int -db_find_event(THD *thd, sp_name *name, Event_timed **ett, TABLE *tbl, - MEM_ROOT *root) +bool +Events::show_create_event(THD *thd, LEX_STRING dbname, LEX_STRING name) { - TABLE *table; + CHARSET_INFO *scs= system_charset_info; int ret; - Event_timed *et= NULL; - DBUG_ENTER("db_find_event"); - DBUG_PRINT("enter", ("name: %*s", name->m_name.length, name->m_name.str)); + Event_timed *et= new Event_timed(); - if (!root) - root= &evex_mem_root; - - if (tbl) - table= tbl; - else if (Events::open_event_table(thd, TL_READ, &table)) + DBUG_ENTER("Events::show_create_event"); + DBUG_PRINT("enter", ("name: %s@%s", dbname.str, name.str)); + if (unlikely(check_system_tables_error)) { - my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0)); - ret= EVEX_GENERAL_ERROR; - goto done; + my_error(ER_EVENTS_DB_ERROR, MYF(0)); + DBUG_RETURN(TRUE); } - 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 + ret= db_repository->load_named_event(thd, dbname, name, et); - 2)::load_from_row() is silent on error therefore we emit error msg here - */ - if ((ret= et->load_from_row(root, table))) + if (!ret) { - my_error(ER_CANNOT_LOAD_FROM_TABLE, MYF(0)); - goto done; - } + Protocol *protocol= thd->protocol; + char show_str_buf[10 * STRING_BUFFER_USUAL_SIZE]; + String show_str(show_str_buf, sizeof(show_str_buf), scs); + List<Item> field_list; + byte *sql_mode_str; + ulong sql_mode_len=0; -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); -} + show_str.length(0); + show_str.set_charset(system_charset_info); + if (et->get_create_event(thd, &show_str)) + goto err; -/* - The function exported to the world for creating of events. + field_list.push_back(new Item_empty_string("Event", NAME_LEN)); - SYNOPSIS - Events::create_event() - thd THD - et event's data - create_options Options specified when in the query. We are - interested whether there is IF NOT EXISTS - rows_affected How many rows were affected + sql_mode_str= + sys_var_thd_sql_mode::symbolic_mode_representation(thd, et->sql_mode, + &sql_mode_len); - RETURN VALUE - 0 OK - !0 Error + field_list.push_back(new Item_empty_string("sql_mode", sql_mode_len)); - NOTES - - in case there is an event with the same name (db) and - IF NOT EXISTS is specified, an warning is put into the W stack. -*/ + field_list.push_back(new Item_empty_string("Create Event", + show_str.length())); -int -Events::create_event(THD *thd, Event_timed *et, uint create_options, - uint *rows_affected) -{ - int ret; + if (protocol->send_fields(&field_list, Protocol::SEND_NUM_ROWS | + Protocol::SEND_EOF)) + goto err; - DBUG_ENTER("Events::create_event"); - DBUG_PRINT("enter", ("name: %*s options:%d", et->name.length, - et->name.str, create_options)); + protocol->prepare_for_resend(); + protocol->store(et->name.str, et->name.length, scs); - if (!(ret = db_create_event(thd, et, - 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))) - 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 */ + protocol->store((char*) sql_mode_str, sql_mode_len, scs); + protocol->store(show_str.c_ptr(), show_str.length(), scs); + ret= protocol->write(); + send_eof(thd); + } + delete et; DBUG_RETURN(ret); +err: + delete et; + DBUG_RETURN(TRUE); } /* - The function exported to the world for alteration of events. + Proxy for Event_db_repository::fill_schema_events. + Callback for I_S from sql_show.cc SYNOPSIS - Events::update_event() - thd THD - et event's data - new_name set in case of RENAME TO. + Events::fill_schema_events() + thd Thread context + tables The schema table + cond Unused RETURN VALUE - 0 OK - !0 Error - - NOTES - et contains data about dbname and event name. - new_name is the new name of the event, if not null (this means - that RENAME TO was specified in the query) + 0 OK + !0 Error */ int -Events::update_event(THD *thd, Event_timed *et, sp_name *new_name, - uint *rows_affected) +Events::fill_schema_events(THD *thd, TABLE_LIST *tables, COND * /* cond */) { - int ret; + char *db= NULL; + DBUG_ENTER("Events::fill_schema_events"); + Events *myself= get_instance(); + if (unlikely(myself->check_system_tables_error)) + { + my_error(ER_EVENTS_DB_ERROR, MYF(0)); + DBUG_RETURN(TRUE); + } - 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 it's SHOW EVENTS then thd->lex->select_lex.db is guaranteed not to + be NULL. Let's do an assert anyway. */ - if (!(ret= db_update_event(thd, et, new_name))) + if (thd->lex->sql_command == SQLCOM_SHOW_EVENTS) { - 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))) - my_error(ER_EVENT_MODIFY_QUEUE_ERROR, MYF(0), ret); + 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(ret); + DBUG_RETURN(myself->db_repository->fill_schema_events(thd, tables, db)); } /* - Drops an event + Inits the scheduler's structures. 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 + Events::init() + + NOTES + This function is not synchronized. RETURN VALUE - 0 OK - !0 Error (my_error() called) + FALSE OK + TRUE Error in case the scheduler can't start */ -int db_drop_event(THD *thd, Event_timed *et, bool drop_if_exists, - uint *rows_affected) +bool +Events::init() { - TABLE *table; - Open_tables_state backup; - int ret; + THD *thd; + bool res= FALSE; + DBUG_ENTER("Events::init"); - DBUG_ENTER("db_drop_event"); - ret= EVEX_OPEN_TABLE_FAILED; + if (opt_event_scheduler == Events::EVENTS_DISABLED) + DBUG_RETURN(FALSE); - thd->reset_n_backup_open_tables_state(&backup); - if (Events::open_event_table(thd, TL_WRITE, &table)) + /* We need a temporary THD during boot */ + if (!(thd= new THD())) { - my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0)); - goto done; + res= TRUE; + goto end; } + /* + The thread stack does not start from this function but we cannot + guess the real value. So better some value that doesn't assert than + no value. + */ + thd->thread_stack= (char*) &thd; + thd->store_globals(); - if (!(ret= evex_db_find_event_aux(thd, et, table))) + if (check_system_tables(thd)) { - if ((ret= table->file->ha_delete_row(table->record[0]))) - { - my_error(ER_EVENT_CANNOT_DELETE, MYF(0)); - goto done; - } + check_system_tables_error= TRUE; + sql_print_error("SCHEDULER: The system tables are damaged. " + "The scheduler subsystem will be unusable during this run."); + goto end; } - 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", et->name.str); - ret= 0; - } else - my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), et->name.str); - goto done; + check_system_tables_error= FALSE; + + if (event_queue->init_queue(thd, db_repository)) + { + sql_print_error("SCHEDULER: Error while loading from disk."); + goto end; } + scheduler->init_scheduler(event_queue); + DBUG_ASSERT(opt_event_scheduler == Events::EVENTS_ON || + opt_event_scheduler == Events::EVENTS_OFF); + if (opt_event_scheduler == Events::EVENTS_ON) + res= scheduler->start(); -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); +end: + delete thd; + /* Remember that we don't have a THD */ + my_pthread_setspecific_ptr(THR_THD, NULL); + + DBUG_RETURN(res); } /* - Drops an event + Cleans up scheduler's resources. Called at server shutdown. SYNOPSIS - Events::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 + Events::deinit() - RETURN VALUE - 0 OK - !0 Error (reported) + NOTES + This function is not synchronized. */ -int -Events::drop_event(THD *thd, Event_timed *et, bool drop_if_exists, - uint *rows_affected) +void +Events::deinit() { - int ret; - - DBUG_ENTER("Events::drop_event"); - if (!(ret= db_drop_event(thd, et, drop_if_exists, rows_affected))) + DBUG_ENTER("Events::deinit"); + if (likely(!check_system_tables_error)) { - Event_scheduler *scheduler= Event_scheduler::get_instance(); - if (scheduler->initialized() && (ret= scheduler->drop_event(thd, et))) - my_error(ER_EVENT_MODIFY_QUEUE_ERROR, MYF(0), ret); + scheduler->stop(); + scheduler->deinit_scheduler(); + + event_queue->deinit_queue(); } - DBUG_RETURN(ret); + DBUG_VOID_RETURN; } /* - SHOW CREATE EVENT + Inits Events mutexes SYNOPSIS - Events::show_create_event() - thd THD - spn the name of the event (db, name) - - RETURN VALUE - 0 OK - 1 Error during writing to the wire + Events::init_mutexes() + thd Thread */ -int -Events::show_create_event(THD *thd, sp_name *spn) +void +Events::init_mutexes() { - int ret; - Event_timed *et= NULL; - Open_tables_state backup; - - DBUG_ENTER("evex_update_event"); - 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); - thd->restore_backup_open_tables_state(&backup); - - if (!ret) - { - Protocol *protocol= thd->protocol; - char show_str_buf[768]; - String show_str(show_str_buf, sizeof(show_str_buf), system_charset_info); - List<Item> field_list; - byte *sql_mode_str; - ulong sql_mode_len=0; - - show_str.length(0); - show_str.set_charset(system_charset_info); - - if (et->get_create_event(thd, &show_str)) - goto err; - - field_list.push_back(new Item_empty_string("Event", NAME_LEN)); - - sql_mode_str= - sys_var_thd_sql_mode::symbolic_mode_representation(thd, et->sql_mode, - &sql_mode_len); - - field_list.push_back(new Item_empty_string("sql_mode", sql_mode_len)); + pthread_mutex_init(&LOCK_event_metadata, MY_MUTEX_INIT_FAST); + event_queue->init_mutexes(); + scheduler->init_mutexes(); +} - field_list.push_back(new Item_empty_string("Create Event", - show_str.length())); - if (protocol->send_fields(&field_list, Protocol::SEND_NUM_ROWS | - Protocol::SEND_EOF)) - goto err; - protocol->prepare_for_resend(); - protocol->store(et->name.str, et->name.length, system_charset_info); +/* + Destroys Events mutexes - protocol->store((char*) sql_mode_str, sql_mode_len, system_charset_info); + SYNOPSIS + Events::destroy_mutexes() +*/ - protocol->store(show_str.c_ptr(), show_str.length(), system_charset_info); - ret= protocol->write(); - send_eof(thd); - } - delete et; - DBUG_RETURN(ret); -err: - delete et; - DBUG_RETURN(1); +void +Events::destroy_mutexes() +{ + event_queue->deinit_mutexes(); + scheduler->deinit_mutexes(); + pthread_mutex_destroy(&LOCK_event_metadata); } /* - Drops all events from a schema + Dumps the internal status of the scheduler and the memory cache + into a table with two columns - Name & Value. Different properties + which could be useful for debugging for instance deadlocks are + returned. SYNOPSIS - Events::drop_schema_events() - thd Thread - db ASCIIZ schema name - - RETURN VALUE - 0 OK - !0 Error + Events::dump_internal_status() */ -int -Events::drop_schema_events(THD *thd, char *db) +void +Events::dump_internal_status() { - int ret= 0; - LEX_STRING db_lex= {db, strlen(db)}; - - DBUG_ENTER("evex_drop_db_events"); - DBUG_PRINT("enter", ("dropping events from %s", db)); + DBUG_ENTER("Events::dump_internal_status"); + puts("\n\n\nEvents status:"); + puts("LLA = Last Locked At LUA = Last Unlocked At"); + puts("WOC = Waiting On Condition DL = Data Locked"); - 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); + scheduler->dump_internal_status(); + event_queue->dump_internal_status(); - DBUG_RETURN(ret); + DBUG_VOID_RETURN; } /* - Drops all events in the selected database, from mysql.event. + Starts execution of events by the scheduler SYNOPSIS - evex_drop_db_events_from_table() - thd Thread - db Schema name + Events::start_execution_of_events() RETURN VALUE - 0 OK - !0 Error from ha_delete_row + FALSE OK + TRUE Error */ -int -db_drop_events_from_table(THD *thd, LEX_STRING *db) +bool +Events::start_execution_of_events() { - 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))) + DBUG_ENTER("Events::start_execution_of_events"); + if (unlikely(check_system_tables_error)) { - if (my_errno != ENOENT) - sql_print_error("Table mysql.event is damaged. Got error %d on open", - my_errno); - DBUG_RETURN(ret); + my_error(ER_EVENTS_DB_ERROR, MYF(0)); + DBUG_RETURN(TRUE); } - /* 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); - - DBUG_RETURN(ret); + DBUG_RETURN(scheduler->start()); } - /* - Inits the scheduler's structures. + Stops execution of events by the scheduler. + Already running events will not be stopped. If the user needs + them stopped manual intervention is needed. SYNOPSIS - Events::init() - - NOTES - This function is not synchronized. + Events::stop_execution_of_events() RETURN VALUE - 0 OK - 1 Error + FALSE OK + TRUE Error */ -int -Events::init() +bool +Events::stop_execution_of_events() { - int ret= 0; - DBUG_ENTER("Events::init"); - - /* it should be an assignment! */ - if (opt_event_scheduler) + DBUG_ENTER("Events::stop_execution_of_events"); + if (unlikely(check_system_tables_error)) { - Event_scheduler *scheduler= Event_scheduler::get_instance(); - DBUG_ASSERT(opt_event_scheduler == 1 || opt_event_scheduler == 2); - DBUG_RETURN(scheduler->init() || - (opt_event_scheduler == 1? scheduler->start(): - scheduler->start_suspended())); + my_error(ER_EVENTS_DB_ERROR, MYF(0)); + DBUG_RETURN(TRUE); } - DBUG_RETURN(0); + DBUG_RETURN(scheduler->stop()); } /* - Cleans up scheduler's resources. Called at server shutdown. + Checks whether the scheduler is running or not. SYNOPSIS - Events::shutdown() + Events::is_started() - NOTES - This function is not synchronized. + RETURN VALUE + TRUE Yes + FALSE No */ -void -Events::shutdown() +bool +Events::is_execution_of_events_started() { - DBUG_ENTER("Events::shutdown"); - Event_scheduler *scheduler= Event_scheduler::get_instance(); - if (scheduler->initialized()) + DBUG_ENTER("Events::is_execution_of_events_started"); + if (unlikely(check_system_tables_error)) { - scheduler->stop(); - scheduler->destroy(); + my_error(ER_EVENTS_DB_ERROR, MYF(0)); + DBUG_RETURN(FALSE); } - - DBUG_VOID_RETURN; + DBUG_RETURN(scheduler->is_running()); } + /* - Proxy for Event_scheduler::dump_internal_status + 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 - Events::dump_internal_status() + Events::check_system_tables() thd Thread - + RETURN VALUE - 0 OK - !0 Error + FALSE OK + TRUE Error */ -int -Events::dump_internal_status(THD *thd) +bool +Events::check_system_tables(THD *thd) { - return Event_scheduler::dump_internal_status(thd); -} + TABLE_LIST tables; + Open_tables_state backup; + bool ret= FALSE; + DBUG_ENTER("Events::check_system_tables"); + DBUG_PRINT("enter", ("thd=0x%lx", thd)); -/* - Inits Events mutexes + thd->reset_n_backup_open_tables_state(&backup); - SYNOPSIS - Events::init_mutexes() - thd Thread -*/ + bzero((char*) &tables, sizeof(tables)); + tables.db= (char*) "mysql"; + tables.table_name= tables.alias= (char*) "db"; + tables.lock_type= TL_READ; -void -Events::init_mutexes() -{ - Event_scheduler::init_mutexes(); -} + if ((ret= simple_open_n_lock_tables(thd, &tables))) + { + sql_print_error("SCHEDULER: Cannot open mysql.db"); + ret= TRUE; + } + 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); + bzero((char*) &tables, sizeof(tables)); + tables.db= (char*) "mysql"; + tables.table_name= tables.alias= (char*) "user"; + tables.lock_type= TL_READ; -/* - Destroys Events mutexes + if (simple_open_n_lock_tables(thd, &tables)) + { + sql_print_error("SCHEDULER: Cannot open mysql.user"); + ret= TRUE; + } + 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 %d", + 29); + ret= TRUE; + } + close_thread_tables(thd); + } - SYNOPSIS - Events::destroy_mutexes() -*/ + thd->restore_backup_open_tables_state(&backup); -void -Events::destroy_mutexes() -{ - Event_scheduler::destroy_mutexes(); + DBUG_RETURN(ret); } diff --git a/sql/events.h b/sql/events.h index 66cce6e7777..b5d2866aa38 100644 --- a/sql/events.h +++ b/sql/events.h @@ -16,78 +16,125 @@ 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_parse_data; +class Event_db_repository; +class Event_queue; +class Event_queue_element; +class Event_scheduler; + +/* 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 Event_timed; class Events { public: - static ulong opt_event_scheduler; - static TYPELIB opt_typelib; - - enum enum_table_field + /* + 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_queue_element; + + /* The order should match the order in opt_typelib */ + enum enum_opt_event_scheduler { - 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 :) */ + EVENTS_OFF= 0, + EVENTS_ON= 1, + EVENTS_DISABLED= 5 }; - static int - create_event(THD *thd, Event_timed *et, uint create_options, - uint *rows_affected); + static enum_opt_event_scheduler opt_event_scheduler; + static TYPELIB opt_typelib; + static TYPELIB var_typelib; - static int - update_event(THD *thd, Event_timed *et, sp_name *new_name, - uint *rows_affected); + bool + init(); - static int - drop_event(THD *thd, Event_timed *et, bool drop_if_exists, - uint *rows_affected); + void + deinit(); - static int + void + init_mutexes(); + + void + destroy_mutexes(); + + bool + start_execution_of_events(); + + bool + stop_execution_of_events(); + + bool + is_execution_of_events_started(); + + static Events * + get_instance(); + + bool + create_event(THD *thd, Event_parse_data *parse_data, bool if_exists); + + bool + update_event(THD *thd, Event_parse_data *parse_data, sp_name *rename_to); + + bool + drop_event(THD *thd, LEX_STRING dbname, LEX_STRING name, bool if_exists, + bool only_from_disk); + + void + drop_schema_events(THD *thd, char *db); + + int open_event_table(THD *thd, enum thr_lock_type lock_type, TABLE **table); - static int - show_create_event(THD *thd, sp_name *spn); + bool + show_create_event(THD *thd, LEX_STRING dbname, LEX_STRING name); + /* Needed for both SHOW CREATE EVENT and INFORMATION_SCHEMA */ static int reconstruct_interval_expression(String *buf, interval_type interval, longlong expression); static int - drop_schema_events(THD *thd, char *db); - - static int - dump_internal_status(THD *thd); - - static int - init(); - - static void - shutdown(); - - static void - init_mutexes(); - - static void - destroy_mutexes(); + fill_schema_events(THD *thd, TABLE_LIST *tables, COND * /* cond */); + void + dump_internal_status(); private: + bool + check_system_tables(THD *thd); + + /* Singleton DP is used */ + Events(); + ~Events(){} + + /* Singleton instance */ + static Events singleton; + + Event_queue *event_queue; + Event_scheduler *scheduler; + Event_db_repository *db_repository; + + pthread_mutex_t LOCK_event_metadata; + + bool check_system_tables_error; + /* 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 ed02cb7055b..00000000000 --- a/sql/events_priv.h +++ /dev/null @@ -1,79 +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; - -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, Event_timed *et, 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); - - -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/lex.h b/sql/lex.h index b8d6a662754..f19c9413e0e 100644 --- a/sql/lex.h +++ b/sql/lex.h @@ -452,7 +452,6 @@ static SYMBOL symbols[] = { { "RTREE", SYM(RTREE_SYM)}, { "SAVEPOINT", SYM(SAVEPOINT_SYM)}, { "SCHEDULE", SYM(SCHEDULE_SYM)}, - { "SCHEDULER", SYM(SCHEDULER_SYM)}, { "SCHEMA", SYM(DATABASE)}, { "SCHEMAS", SYM(DATABASES)}, { "SECOND", SYM(SECOND_SYM)}, diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 445027fa368..6ccd4707248 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -837,7 +837,7 @@ static void close_connections(void) DBUG_PRINT("quit",("Informing thread %ld that it's time to die", tmp->thread_id)); /* We skip slave threads & scheduler on this first loop through. */ - if (tmp->slave_thread || tmp->system_thread == SYSTEM_THREAD_EVENT_SCHEDULER) + if (tmp->slave_thread) continue; tmp->killed= THD::KILL_CONNECTION; @@ -856,7 +856,7 @@ static void close_connections(void) } (void) pthread_mutex_unlock(&LOCK_thread_count); // For unlink from list - Events::shutdown(); + Events::get_instance()->deinit(); end_slave(); if (thread_count) @@ -1294,7 +1294,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 @@ -2872,7 +2872,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, @@ -3658,7 +3658,8 @@ we force server id to 2, but this MySQL server will not act as a slave."); if (!opt_noacl) { - Events::init(); + if (Events::get_instance()->init()) + unireg_abort(1); } #if defined(__NT__) || defined(HAVE_SMEM) handle_connections_methods(); @@ -5001,9 +5002,9 @@ struct my_option my_long_options[] = (gptr*) &global_system_variables.engine_condition_pushdown, (gptr*) &global_system_variables.engine_condition_pushdown, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + /* See how it's handled in get_one_option() */ {"event-scheduler", OPT_EVENT_SCHEDULER, "Enable/disable the event scheduler.", - (gptr*) &Events::opt_event_scheduler, (gptr*) &Events::opt_event_scheduler, 0, GET_ULONG, - REQUIRED_ARG, 2/*default*/, 0/*min-value*/, 2/*max-value*/, 0, 0, 0}, + NULL, NULL, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, {"exit-info", 'T', "Used for debugging; Use at your own risk!", 0, 0, 0, GET_LONG, OPT_ARG, 0, 0, 0, 0, 0, 0}, {"external-locking", OPT_USE_LOCKING, "Use system (external) locking (disabled by default). With this option enabled you can run myisamchk to test (not repair) tables while the MySQL server is running. Disable with --skip-external-locking.", @@ -7305,20 +7306,30 @@ get_one_option(int optid, const struct my_option *opt __attribute__((unused)), #endif case OPT_EVENT_SCHEDULER: if (!argument) - Events::opt_event_scheduler= 2; + Events::opt_event_scheduler= Events::EVENTS_DISABLED; else { int type; - if ((type=find_type(argument, &Events::opt_typelib, 1)) <= 0) - { - fprintf(stderr,"Unknown option to event-scheduler: %s\n",argument); - exit(1); - } /* - type= 1 2 3 4 5 6 - (OFF | 0) - (ON | 1) - (2 | SUSPEND) + type= 5 1 2 3 4 + (DISABLE ) - (OFF | ON) - (0 | 1) */ - Events::opt_event_scheduler= (type-1) / 2; + switch ((type=find_type(argument, &Events::opt_typelib, 1))) { + case 0: + fprintf(stderr, "Unknown option to event-scheduler: %s\n",argument); + exit(1); + case 5: /* OPT_DISABLED */ + Events::opt_event_scheduler= Events::EVENTS_DISABLED; + break; + case 2: /* OPT_ON */ + case 4: /* 1 */ + Events::opt_event_scheduler= Events::EVENTS_ON; + break; + case 1: /* OPT_OFF */ + case 3: /* 0 */ + Events::opt_event_scheduler= Events::EVENTS_OFF; + break; + } } break; case (int) OPT_SKIP_NEW: diff --git a/sql/set_var.cc b/sql/set_var.cc index bc2f0e0d823..e09c75573eb 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -57,7 +57,7 @@ #include <myisam.h> #include <my_dir.h> -#include "event_scheduler.h" +#include "events.h" /* WITH_INNOBASE_STORAGE_ENGINE */ extern uint innobase_flush_log_at_trx_commit; @@ -3899,6 +3899,7 @@ bool sys_var_thd_dbug::update(THD *thd, set_var *var) return 0; } + byte *sys_var_thd_dbug::value_ptr(THD *thd, enum_var_type type, LEX_STRING *b) { char buf[256]; @@ -3910,6 +3911,12 @@ byte *sys_var_thd_dbug::value_ptr(THD *thd, enum_var_type type, LEX_STRING *b) } +bool sys_var_event_scheduler::check(THD *thd, set_var *var) +{ + return check_enum(thd, var, &Events::var_typelib); +} + + /* The update method of the global variable event_scheduler. If event_scheduler is switched from 0 to 1 then the scheduler main @@ -3928,30 +3935,30 @@ 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; - Event_scheduler *scheduler= Event_scheduler::get_instance(); + int res; /* here start the thread if not running. */ DBUG_ENTER("sys_var_event_scheduler::update"); + if (Events::opt_event_scheduler == Events::EVENTS_DISABLED) + { + my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--event-scheduler=DISABLED"); + DBUG_RETURN(TRUE); + } DBUG_PRINT("new_value", ("%lu", (bool)var->save_result.ulong_value)); - if (!scheduler->initialized()) + + Item_result var_type= var->value->result_type(); + + if (var->save_result.ulong_value == Events::EVENTS_ON) + res= Events::get_instance()->start_execution_of_events(); + else if (var->save_result.ulong_value == Events::EVENTS_OFF) + res= Events::get_instance()->stop_execution_of_events(); + else { - my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--event-scheduler=0"); - DBUG_RETURN(true); + DBUG_ASSERT(0); } + if (res) + my_error(ER_EVENT_SET_VAR_ERROR, MYF(0)); - if (var->save_result.ulonglong_value < 1 || - var->save_result.ulonglong_value > 2) - { - char buf[64]; - my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), "event_scheduler", - llstr(var->save_result.ulonglong_value, buf)); - DBUG_RETURN(true); - } - if ((res= scheduler->suspend_or_resume(var->save_result.ulonglong_value == 1? - Event_scheduler::RESUME : - Event_scheduler::SUSPEND))) - my_error(ER_EVENT_SET_VAR_ERROR, MYF(0), (uint) res); DBUG_RETURN((bool) res); } @@ -3959,16 +3966,15 @@ sys_var_event_scheduler::update(THD *thd, set_var *var) byte *sys_var_event_scheduler::value_ptr(THD *thd, enum_var_type type, LEX_STRING *base) { - Event_scheduler *scheduler= Event_scheduler::get_instance(); - - if (!scheduler->initialized()) - thd->sys_var_tmp.long_value= 0; - else if (scheduler->get_state() == Event_scheduler::RUNNING) - thd->sys_var_tmp.long_value= 1; + int state; + if (Events::opt_event_scheduler == Events::EVENTS_DISABLED) + state= Events::EVENTS_DISABLED; // This should be DISABLED + else if (Events::get_instance()->is_execution_of_events_started()) + state= Events::EVENTS_ON; // This should be ON else - thd->sys_var_tmp.long_value= 2; + state= Events::EVENTS_OFF; // This should be OFF - return (byte*) &thd->sys_var_tmp; + return (byte*) Events::opt_typelib.type_names[state]; } diff --git a/sql/set_var.h b/sql/set_var.h index a63bcc4a55d..01669b378e1 100644 --- a/sql/set_var.h +++ b/sql/set_var.h @@ -932,6 +932,12 @@ public: sys_var_long_ptr(name_arg, NULL, NULL) {}; bool update(THD *thd, set_var *var); byte *value_ptr(THD *thd, enum_var_type type, LEX_STRING *base); + SHOW_TYPE type() { return SHOW_CHAR; } + bool check(THD *thd, set_var *var); + bool check_update_type(Item_result type) + { + return type != STRING_RESULT && type != INT_RESULT; + } }; #ifdef HAVE_ROW_BASED_REPLICATION diff --git a/sql/share/errmsg.txt b/sql/share/errmsg.txt index 28d81447c66..37f54899615 100644 --- a/sql/share/errmsg.txt +++ b/sql/share/errmsg.txt @@ -5978,6 +5978,10 @@ ER_BAD_LOG_ENGINE eng "One can use only CSV and MyISAM engines for the log tables" ER_CANT_DROP_LOG_TABLE eng "Cannot drop log table if log is enabled" +ER_EVENT_RECURSIVITY_FORBIDDEN + eng "Recursivity of EVENT DDL statements is forbidden when body is present" +ER_EVENTS_DB_ERROR + eng "Cannot proceed because the tables used by events were found damaged at server start" ER_ONLY_INTEGERS_ALLOWED eng "Only normal integers allowed as number here" ER_USERNAME diff --git a/sql/sp_head.cc b/sql/sp_head.cc index fc4aa5e26d6..1ff33368a37 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -196,7 +196,6 @@ sp_get_flags_for_command(LEX *lex) case SQLCOM_SHOW_PRIVILEGES: case SQLCOM_SHOW_PROCESSLIST: case SQLCOM_SHOW_PROC_CODE: - case SQLCOM_SHOW_SCHEDULER_STATUS: case SQLCOM_SHOW_SLAVE_HOSTS: case SQLCOM_SHOW_SLAVE_STAT: case SQLCOM_SHOW_STATUS: @@ -657,10 +656,12 @@ sp_head::create(THD *thd) sp_head::~sp_head() { + DBUG_ENTER("sp_head::~sp_head"); destroy(); delete m_next_cached_sp; if (m_thd) restore_thd_mem_root(m_thd); + DBUG_VOID_RETURN; } void diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 3fc182e20f3..e3181fb67cf 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -2084,6 +2084,7 @@ bool Security_context::set_user(char *user_arg) return user == 0; } + /**************************************************************************** Handling of open and locked tables states. diff --git a/sql/sql_db.cc b/sql/sql_db.cc index 372a350566f..3960236e828 100644 --- a/sql/sql_db.cc +++ b/sql/sql_db.cc @@ -949,7 +949,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); + 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 diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index b9e52b62244..2b00a69aa60 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -169,14 +169,16 @@ void lex_start(THD *thd, const uchar *buf, uint length) lex->sql_command= SQLCOM_END; lex->duplicates= DUP_ERROR; lex->ignore= 0; + lex->spname= NULL; 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->expr_allows_subselect= TRUE; 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 ce0af39a041..b7781c9aae8 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -27,7 +27,7 @@ class sp_instr; class sp_pcontext; class st_alter_tablespace; class partition_info; -class Event_timed; +class Event_parse_data; #ifdef MYSQL_SERVER /* @@ -113,7 +113,6 @@ enum enum_sql_command { SQLCOM_SHOW_CONTRIBUTORS, SQLCOM_CREATE_EVENT, SQLCOM_ALTER_EVENT, SQLCOM_DROP_EVENT, SQLCOM_SHOW_CREATE_EVENT, SQLCOM_SHOW_EVENTS, - SQLCOM_SHOW_SCHEDULER_STATUS, /* This should be the last !!! */ @@ -958,6 +957,14 @@ typedef struct st_lex : public Query_tables_list */ nesting_map allow_sum_func; enum_sql_command sql_command; + /* + Usually `expr` rule of yacc is quite reused but some commands better + not support subqueries which comes standard with this rule, like + KILL, HA_READ, CREATE/ALTER EVENT etc. Set this to `false` to get + syntax error back. + */ + bool expr_allows_subselect; + thr_lock_type lock_option; enum SSL_type ssl_type; /* defined in violite.h */ enum my_lex_states next_state; @@ -1035,8 +1042,7 @@ typedef struct st_lex : public Query_tables_list st_sp_chistics sp_chistics; - Event_timed *et; - bool et_compile_phase; + Event_parse_data *event_parse_data; bool only_view; /* used for SHOW CREATE TABLE/VIEW */ /* diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 7c600f7463e..55c090e1982 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 /* @@ -3888,73 +3888,43 @@ end_with_restore_list: } case SQLCOM_CREATE_EVENT: case SQLCOM_ALTER_EVENT: - case SQLCOM_DROP_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::create_event(thd, lex->et, - (uint) lex->create_info.options, - &rows_affected); - break; - case SQLCOM_ALTER_EVENT: - res= Events::update_event(thd, lex->et, lex->spname, - &rows_affected); - break; - case SQLCOM_DROP_EVENT: - res= Events::drop_event(thd, lex->et, lex->drop_if_exists, - &rows_affected); - 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, + lex->create_info.options & HA_LEX_CREATE_IF_NOT_EXISTS); + break; + case SQLCOM_ALTER_EVENT: + res= Events::get_instance()->update_event(thd, lex->event_parse_data, + lex->spname); + break; + default: + DBUG_ASSERT(0); + } + DBUG_PRINT("info",("DDL error code=%d", res)); + if (!res) + send_ok(thd); - /* lex->unit.cleanup() is called outside, no need to call it here */ - } while (0); + /* Don't do it, if we are inside a SP */ 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: 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)); - res= true; - break; + goto error; } if (check_access(thd, EVENT_ACL, lex->spname->m_db.str, 0, 0, 0, is_schema_db(lex->spname->m_db.str))) @@ -3963,18 +3933,25 @@ 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); - break; - } -#ifndef DBUG_OFF - case SQLCOM_SHOW_SCHEDULER_STATUS: - { - res= Events::dump_internal_status(thd); + + if (lex->sql_command == SQLCOM_SHOW_CREATE_EVENT) + res= Events::get_instance()->show_create_event(thd, lex->spname->m_db, + lex->spname->m_name); + else + { + uint affected= 1; + if (!(res= Events::get_instance()->drop_event(thd, + lex->spname->m_db, + lex->spname->m_name, + lex->drop_if_exists, + FALSE))) + send_ok(thd); + } break; } -#endif case SQLCOM_CREATE_FUNCTION: // UDF function { if (check_access(thd,INSERT_ACL,"mysql",0,1,0,0)) @@ -6065,14 +6042,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 { @@ -6109,13 +6078,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_show.cc b/sql/sql_show.cc index 9a41c139f4e..eddba067d3a 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 <my_dir.h> #ifdef WITH_PARTITION_STORAGE_ENGINE @@ -4219,7 +4219,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; @@ -4230,7 +4230,7 @@ copy_event_to_schema_table(THD *thd, TABLE *sch_table, TABLE *event_table) restore_record(sch_table, s->default_values); - if (et.load_from_row(thd->mem_root, event_table)) + if (et.load_from_row(event_table)) { my_error(ER_CANNOT_LOAD_FROM_TABLE, MYF(0)); DBUG_RETURN(1); @@ -4354,183 +4354,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[Events::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->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::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->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"); @@ -5633,7 +5456,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 */ diff --git a/sql/sql_test.cc b/sql/sql_test.cc index b28aa4a1ce5..c4c40ea63c8 100644 --- a/sql/sql_test.cc +++ b/sql/sql_test.cc @@ -28,6 +28,8 @@ #include <sys/malloc.h> #endif +#include "events.h" + static const char *lock_descriptions[] = { "No lock", @@ -532,5 +534,7 @@ Estimated memory (with thread stack): %ld\n", (int) info.keepcost, (long) (thread_count * thread_stack + info.hblkhd + info.arena)); #endif + + Events::get_instance()->dump_internal_status(); puts(""); } diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index b1a1e9cb9af..a9539a3df98 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 <myisam.h> #include <myisammrg.h> @@ -590,7 +590,6 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token RTREE_SYM %token SAVEPOINT_SYM %token SCHEDULE_SYM -%token SCHEDULER_SYM %token SECOND_MICROSECOND_SYM %token SECOND_SYM %token SECURITY_SYM @@ -907,7 +906,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 @@ -1284,29 +1284,41 @@ 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->alter_tablespace_info->ts_cmd_type= CREATE_LOGFILE_GROUP; + } + | CREATE TABLESPACE tablespace_info + { + 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 */ { - LEX *lex=Lex; - - if (lex->et) - { - /* - Recursive events 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() + 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,11 +1328,9 @@ create: $<ulong_num>$= YYTHD->client_capabilities & CLIENT_MULTI_QUERIES; YYTHD->client_capabilities &= (~CLIENT_MULTI_QUERIES); - if (!lex->et_compile_phase) - { - lex->et->init_name(YYTHD, $4); - lex->et->init_definer(YYTHD); - } + Lex->sql_command= SQLCOM_CREATE_EVENT; + /* We need that for disallowing subqueries */ + Lex->expr_allows_subselect= FALSE; } ON SCHEDULE_SYM ev_schedule_time opt_ev_on_completion @@ -1330,160 +1340,62 @@ 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 |= $<ulong_num>5; + YYTHD->client_capabilities |= $<ulong_num>4; /* sql_command is set here because some rules in ev_sql_stmt can overwrite it */ Lex->sql_command= SQLCOM_CREATE_EVENT; + Lex->expr_allows_subselect= TRUE; } - | 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 { - 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; - } - } + Lex->event_parse_data->item_expression= $2; + Lex->event_parse_data->interval= $3; } ev_starts ev_ends | AT_SYM expr { - 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; - } - } + Lex->event_parse_data->item_execute_at= $2; } ; opt_ev_status: /* empty */ { $$= 0; } | ENABLE_SYM { - LEX *lex=Lex; - if (!lex->et_compile_phase) - lex->et->status= Event_timed::ENABLED; + Lex->event_parse_data->status= Event_parse_data::ENABLED; $$= 1; } | DISABLE_SYM { - LEX *lex=Lex; - - if (!lex->et_compile_phase) - lex->et->status= Event_timed::DISABLED; + Lex->event_parse_data->status= Event_parse_data::DISABLED; $$= 1; } ; ev_starts: /* empty */ { - Lex->et->init_starts(YYTHD, new Item_func_now_local()); + Lex->event_parse_data->item_starts= new Item_func_now_local(); } | STARTS_SYM expr { - 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; - } - } - } + Lex->event_parse_data->item_starts= $2; } ; ev_ends: /* empty */ | ENDS_SYM expr { - 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; - } - } + Lex->event_parse_data->item_ends= $2; } ; @@ -1494,16 +1406,14 @@ opt_ev_on_completion: /* empty */ { $$= 0; } ev_on_completion: ON COMPLETION_SYM PRESERVE_SYM { - LEX *lex=Lex; - if (!lex->et_compile_phase) - lex->et->on_completion= Event_timed::ON_COMPLETION_PRESERVE; + Lex->event_parse_data->on_completion= + Event_parse_data::ON_COMPLETION_PRESERVE; $$= 1; } | ON COMPLETION_SYM NOT_SYM PRESERVE_SYM { - LEX *lex=Lex; - if (!lex->et_compile_phase) - lex->et->on_completion= Event_timed::ON_COMPLETION_DROP; + Lex->event_parse_data->on_completion= + Event_parse_data::ON_COMPLETION_DROP; $$= 1; } ; @@ -1511,64 +1421,65 @@ ev_on_completion: opt_ev_comment: /* empty */ { $$= 0; } | COMMENT_SYM TEXT_STRING_sys { - LEX *lex= Lex; - if (!lex->et_compile_phase) - { - lex->comment= $2; - lex->et->init_comment(YYTHD, &$2); - } - $$= 1; + Lex->comment= Lex->event_parse_data->comment= $2; } ; ev_sql_stmt: { LEX *lex= Lex; - sp_head *sp; - - $<sphead>$= 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->init_sp_name(YYTHD, Lex->event_parse_data->identifier); - 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; - if (!lex->et_compile_phase) - lex->et->body_begin= lex->ptr; } ev_sql_stmt_inner { LEX *lex=Lex; - if (!$<sphead>1) - { - sp_head *sp= lex->sphead; - // return back to the original memory root ASAP - sp->init_strings(YYTHD, lex); - sp->restore_thd_mem_root(YYTHD); + /* return back to the original memory root ASAP */ + lex->sphead->init_strings(YYTHD, lex); + 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; - } - if (!lex->et_compile_phase) - { - lex->et->init_body(YYTHD); - } + Lex->event_parse_data->init_body(YYTHD); } ; @@ -1643,10 +1554,10 @@ create_function_tail: if (lex->definer != NULL) { /* - DEFINER is a concept meaningful when interpreting SQL code. - UDF functions are compiled. - Using DEFINER with UDF has therefore no semantic, - and is considered a parsing error. + DEFINER is a concept meaningful when interpreting SQL code. + UDF functions are compiled. + Using DEFINER with UDF has therefore no semantic, + and is considered a parsing error. */ my_error(ER_WRONG_USAGE, MYF(0), "SONAME", "DEFINER"); YYABORT; @@ -1685,10 +1596,10 @@ create_function_tail: sp->m_type= TYPE_ENUM_FUNCTION; lex->sphead= sp; /* - * 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 off CLIENT_MULTI_QUERIES while parsing a + stored procedure, otherwise yylex will chop it into pieces + at each ';'. + */ $<ulong_num>$= YYTHD->client_capabilities & CLIENT_MULTI_QUERIES; YYTHD->client_capabilities &= ~CLIENT_MULTI_QUERIES; lex->sphead->m_param_begin= lex->tok_start+1; @@ -4790,41 +4701,33 @@ alter: {} | ALTER EVENT_SYM sp_name /* - BE CAREFUL when you add a new rule to update the block where - YYTHD->client_capabilities is set back to original value + BE CAREFUL when you add a new rule to update the block where + YYTHD->client_capabilities is set back to original value */ { - 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 + /* + It is safe to use Lex->spname because + ALTER EVENT xxx RENATE TO yyy DO ALTER EVENT RENAME TO + is not allowed. Lex->spname is used in the case of RENAME TO + If it had to be supported spname had to be added to + Event_parse_data. + */ - if (!(et= new (YYTHD->mem_root) Event_timed()))// implicitly calls Event_timed::init() + if (!(Lex->event_parse_data= Event_parse_data::new_instance(YYTHD))) YYABORT; - lex->et = et; - - if (!lex->et_compile_phase) - { - et->init_definer(YYTHD); - et->init_name(YYTHD, $3); - } + Lex->event_parse_data->identifier= $3; /* - 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 off CLIENT_MULTI_QUERIES while parsing a + stored procedure, otherwise yylex will chop it into pieces + at each ';'. */ $<ulong_num>$= YYTHD->client_capabilities & CLIENT_MULTI_QUERIES; YYTHD->client_capabilities &= ~CLIENT_MULTI_QUERIES; + + Lex->sql_command= SQLCOM_ALTER_EVENT; + /* we need that for disallowing subqueries */ + Lex->expr_allows_subselect= FALSE; } ev_alter_on_schedule_completion opt_ev_rename_to @@ -4840,16 +4743,17 @@ alter: */ YYTHD->client_capabilities |= $<ulong_num>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; + Lex->expr_allows_subselect= TRUE; } | ALTER TABLESPACE alter_tablespace_info { @@ -4882,9 +4786,11 @@ ev_alter_on_schedule_completion: /* empty */ { $$= 0;} opt_ev_rename_to: /* empty */ { $$= 0;} | RENAME TO_SYM sp_name { - LEX *lex=Lex; - lex->spname= $3; //use lex's spname to hold the new name - //the original name is in the Event_timed object + /* + Use lex's spname to hold the new name. + The original name is in the Event_parse_data object + */ + Lex->spname= $3; $$= 1; } ; @@ -4908,7 +4814,7 @@ alter_commands: | remove_partitioning | partitioning /* - This part was added for release 5.1 by Mikael Ronström. + This part was added for release 5.1 by Mikael Ronström. From here we insert a number of commands to manage the partitions of a partitioned table such as adding partitions, dropping partitions, reorganising partitions in various manners. In future releases the list @@ -7176,8 +7082,7 @@ 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->expr_allows_subselect) { yyerror(ER(ER_SYNTAX_ERROR)); YYABORT; @@ -7796,29 +7701,9 @@ drop: } | DROP EVENT_SYM if_exists sp_name { - 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 { @@ -8226,15 +8111,6 @@ show_param: if (prepare_schema_table(YYTHD, lex, 0, SCH_EVENTS)) YYABORT; } - | SCHEDULER_SYM STATUS_SYM - { -#ifndef DBUG_OFF - Lex->sql_command= SQLCOM_SHOW_SCHEDULER_STATUS; -#else - yyerror(ER(ER_SYNTAX_ERROR)); - YYABORT; -#endif - } | TABLE_SYM STATUS_SYM opt_db wild_and_where { LEX *lex= Lex; @@ -8511,12 +8387,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; } ; @@ -8696,11 +8568,17 @@ purge_option: /* kill threads */ kill: - KILL_SYM { Lex->sql_command= SQLCOM_KILL; } kill_option expr + KILL_SYM + { + Lex->sql_command= SQLCOM_KILL; + Lex->expr_allows_subselect= FALSE; + } + kill_option expr { LEX *lex=Lex; lex->value_list.empty(); lex->value_list.push_front($4); + Lex->expr_allows_subselect= TRUE; }; kill_option: @@ -9615,7 +9493,6 @@ keyword_sp: | ROW_SYM {} | RTREE_SYM {} | SCHEDULE_SYM {} - | SCHEDULER_SYM {} | SECOND_SYM {} | SERIAL_SYM {} | SERIALIZABLE_SYM {} @@ -10187,6 +10064,7 @@ handler: my_error(ER_SP_BADSTATEMENT, MYF(0), "HANDLER"); YYABORT; } + lex->expr_allows_subselect= FALSE; lex->sql_command = SQLCOM_HA_READ; lex->ha_rkey_mode= HA_READ_KEY_EXACT; /* Avoid purify warnings */ lex->current_select->select_limit= new Item_int((int32) 1); @@ -10194,7 +10072,10 @@ handler: if (!lex->current_select->add_table_to_list(lex->thd, $2, 0, 0)) YYABORT; } - handler_read_or_scan where_clause opt_limit_clause {} + handler_read_or_scan where_clause opt_limit_clause + { + Lex->expr_allows_subselect= TRUE; + } ; handler_read_or_scan: @@ -10819,8 +10700,7 @@ subselect_start: '(' SELECT_SYM { LEX *lex=Lex; - if (lex->sql_command == (int)SQLCOM_HA_READ || - lex->sql_command == (int)SQLCOM_KILL) + if (!lex->expr_allows_subselect) { yyerror(ER(ER_SYNTAX_ERROR)); YYABORT; @@ -10844,20 +10724,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 + {} ; /************************************************************************** diff --git a/sql/table.cc b/sql/table.cc index fe83398115d..d20818e3644 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -2365,28 +2365,28 @@ bool check_column_name(const char *name) Checks whether a table is intact. Should be done *just* after the table has been opened. - Synopsis + SYNOPSIS table_check_intact() - table - the table to check - table_f_count - expected number of columns in the table - table_def - expected structure of the table (column name and type) - last_create_time- the table->file->create_time of the table in memory - we have checked last time - error_num - ER_XXXX from the error messages file. When 0 no error - is sent to the client in case types does not match. - If different col number either - ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE or - ER_COL_COUNT_DOESNT_MATCH_CORRUPTED is used + table The table to check + table_f_count Expected number of columns in the table + table_def Expected structure of the table (column name and type) + last_create_time The table->file->create_time of the table in memory + we have checked last time + error_num ER_XXXX from the error messages file. When 0 no error + is sent to the client in case types does not match. + If different col number either + ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE or + ER_COL_COUNT_DOESNT_MATCH_CORRUPTED is used RETURNS - 0 - OK - 1 - There was an error + FALSE OK + TRUE There was an error */ my_bool -table_check_intact(TABLE *table, uint table_f_count, - TABLE_FIELD_W_TYPE *table_def, time_t *last_create_time, - int error_num) +table_check_intact(TABLE *table, const uint table_f_count, + const TABLE_FIELD_W_TYPE *table_def, + time_t *last_create_time, int error_num) { uint i; my_bool error= FALSE; @@ -2401,7 +2401,7 @@ table_check_intact(TABLE *table, uint table_f_count, DBUG_PRINT("info", ("I am suspecting, checking table")); if (fields_diff_count) { - // previous MySQL version + /* previous MySQL version */ error= TRUE; if (MYSQL_VERSION_ID > table->s->mysql_version) { @@ -2424,22 +2424,22 @@ table_check_intact(TABLE *table, uint table_f_count, else { /* - moving from newer mysql to older one -> let's say not an error but + Moving from newer mysql to older one -> let's say not an error but will check the definition afterwards. If a column was added at the end then we don't care much since it's not in the middle. */ error= FALSE; } } - //definitely something has changed + /* definitely something has changed */ char buffer[255]; for (i=0 ; i < table_f_count; i++, table_def++) { String sql_type(buffer, sizeof(buffer), system_charset_info); sql_type.length(0); /* - name changes are not fatal, we use sequence numbers => no prob for us - but this can show tampered table or broken table. + Name changes are not fatal, we use sequence numbers => no problem + for us but this can show tampered table or broken table. */ if (i < table->s->fields) { @@ -2453,7 +2453,7 @@ table_check_intact(TABLE *table, uint table_f_count, } /* - IF the type does not match than something is really wrong + If the type does not match than something is really wrong Check up to length - 1. Why? 1. datetime -> datetim -> the same 2. int(11) -> int(11 -> the same diff --git a/sql/table.h b/sql/table.h index 3fb7222cb0d..b90ee280f91 100644 --- a/sql/table.h +++ b/sql/table.h @@ -1029,9 +1029,9 @@ typedef struct st_table_field_w_type my_bool -table_check_intact(TABLE *table, uint table_f_count, - TABLE_FIELD_W_TYPE *table_def, time_t *last_create_time, - int error_num); +table_check_intact(TABLE *table, const uint table_f_count, + const TABLE_FIELD_W_TYPE *table_def, + time_t *last_create_time, int error_num); static inline my_bitmap_map *tmp_use_all_columns(TABLE *table, MY_BITMAP *bitmap) |